logo

코드 실행을 통한 증강

숫자 퍼즐 코드를 API로 생성하고, exec와 Monty 샌드박스에서 실행하는 방법 및 외부 샌드박스 서비스를 비교한다.

숫자 퍼즐의 모든 해 찾기

1부터 9까지의 수에서 서로 다른 세 수를 골라, 합이 15가 되는 모든 조합을 찾아라.

  • 순서만 다른 조합은 하나로 취급. 예: [1, 5, 9]와 [9, 1, 5]는 같은 조합.
  • 몇 가지 답을 떠올리는 데서 끝내지 않고, 가능한 경우를 빠짐없이 검사하는 것이 목표.
  • 모델이 탐색 코드를 작성하고, 파이썬이 실제로 실행.

Mermaid 다이어그램 원문

sequenceDiagram
    autonumber
    participant L as LLM
    participant A as 애플리케이션
    participant E as 실행기
    A->>L: 작업 요청과 코드 작성 지시
    L-->>A: 생성한 코드
    A->>E: 코드와 입력 자료
    E-->>A: 실행 결과
    A->>L: 실행 결과
    L-->>A: 최종 답변

대화형 다이어그램을 불러오기 전에는 원문으로 표시됩니다.

  • LLM의 코드 생성 → 실행기의 코드 실행 → 실행 결과를 바탕으로 한 LLM의 최종 답변 순서.
  • 아래 실습은 코드 생성·실행·결과 검증까지 진행. 그림의 마지막 두 단계는 실행 결과를 모델에 전달해 답변으로 정리하는 확장 흐름.
  • 실행기는 exec() 또는 Monty. exec()는 실행 후 변수를 읽고, Monty는 마지막 표현식의 값을 반환.

API로 탐색 코드 작성하기

  • 함수 호출 실습에서 준비한 client, model, json, Monty를 같은 노트북에서 이어 사용. 별도 자료 파일 불필요.
target = 15  # 세 수의 합으로 만들 목표값
  • 목표값은 target 변수로 전달. 답을 미리 적는 대신 가능한 조합을 검사하도록 요청.
  • 결과 형식: 조합마다 세 수를 오름차순으로 정렬한 리스트. 전체 결과는 result에 저장.
task = f"""
1부터 9까지 서로 다른 세 수를 골라 합이 {target}인 모든 조합을 찾는 파이썬 코드를 작성해라.
- 실행 환경에 target 변수가 주어진다. 코드에서 target을 덮어쓰지 말고 사용해라.
- 가능한 조합을 직접 검사하고 순서만 다른 조합은 중복해서 넣지 마라.
- 각 조합은 오름차순인 세 정수의 리스트, 전체 result는 조합들을 오름차순 정렬한 리스트로 저장해라.
- 답을 미리 적지 말고, 파이썬 기본 기능만 사용해라.
- import, 파일·네트워크 접근, 명령어 실행, print는 쓰지 마라.
- code 키에 코드 문자열을 넣은 JSON 객체로만 응답해라. 코드 펜스는 넣지 마라.
"""
response = client.chat.completions.create(
    model=model,
    messages=[
        {"role": "system", "content": "숫자 퍼즐을 푸는 파이썬 코드를 JSON으로 반환하세요."},
        {"role": "user", "content": task},
    ],
    response_format={"type": "json_object"},
)
code = json.loads(response.choices[0].message.content)["code"]
print(code)  # 생성된 여러 줄 코드를 읽기 위해 출력
  • json.loads(): JSON 응답에서 코드 문자열을 꺼내기 위한 변환.
  • 확인: 서로 다른 세 수를 검사하는가? 합을 target과 비교하는가? 중복 조합을 제외하는가?
  • API가 코드를 반환한 시점에는 아직 퍼즐을 탐색하지 않은 상태.

방법 1: exec로 실행하기

함수 용도
eval() 1 + 2 같은 단일 표현식을 평가해 값 반환
exec() 대입·반복문·조건문을 포함하는 여러 줄 프로그램 실행
  • 이번에는 반복해서 조합을 검사하는 프로그램이므로 exec() 사용.
  • execution: 코드에 전달할 변수와 실행 후 만들어진 변수를 보관하는 사전.
execution = {"target": target}
exec(code, execution)
result = execution["result"]
result
  • exec() 자체의 반환값은 None. 생성 코드가 만든 result를 실행 공간에서 꺼내야 함.
  • 모델이 글로 제시한 답 대신 실제 탐색 결과를 확인하는 단계.

방법 2: Monty 샌드박스에서 실행하기

  • 샌드박스(sandbox): 실행 코드가 접근할 기능과 사용할 자원을 제한한 환경.
  • Monty: 제한된 파이썬을 실행하는 별도 실행기. 파일 마운트·호스트 함수를 제공하지 않으면 호스트의 파일·환경 변수·네트워크에 접근할 수 없음.
  • OS 전체를 격리하는 가상머신과는 구분. 외부 패키지를 그대로 실행하는 환경은 아니지만 이번 퍼즐의 반복문·리스트는 사용 가능.
  • 설치·불러오기는 함수 호출 실습의 공통 준비 코드에 포함. Monty 실행 자체에는 별도 서비스 API 키 불필요.
limits = {
    "max_feed_duration_secs": 1,  # 코드 실행 시간 제한, 초
    "max_memory": 10_000_000,     # 할당 메모리 제한, 바이트
}

같은 생성 코드에 같은 목표값을 전달해 실행.

with Monty(request_timeout=2) as sandbox:  # 워커 응답 대기 제한, 초
    with sandbox.checkout(limits=limits) as session:
        monty_result = session.feed_run(
            code + "\nresult",  # 마지막 표현식의 값을 반환
            inputs={"target": target},
        )

monty_result
  • inputs: 실행 코드에 전달할 변수. 앞선 execution 사전에 넣은 목표값과 같은 역할.
  • feed_run(): 코드를 실행하고 마지막 표현식의 값을 반환. result를 덧붙여 생성 코드가 만든 결과를 꺼냄.
  • exec()와의 차이: 현재 파이썬의 권한으로 직접 실행하는 대신, Monty의 제한된 실행기에서 설정한 시간·메모리 범위 안에 실행.
  • 메모리 설정은 실행기의 할당량 제한이며 프로세스 전체 메모리 사용량의 상한은 아님. 코드의 계산 정확성은 별도로 확인.

출처: Monty 시작 안내, 보안 모델, 지원 범위.

결과 확인하기

  • 아래는 비교를 위해 작성한 기준 코드. API가 생성한 코드의 실행 결과와 구분.
  • a < b < c가 되도록 검사하면 같은 수의 반복과 순서만 다른 조합을 제외 가능.
reference_code = """
result = []
for a in range(1, 10):
    for b in range(a + 1, 10):
        for c in range(b + 1, 10):
            if a + b + c == target:
                result.append([a, b, c])
"""
reference_execution = {"target": target}
exec(reference_code, reference_execution)
reference_result = reference_execution["result"]
reference_result
실행 결과
[[1, 5, 9],
 [1, 6, 8],
 [2, 4, 9],
 [2, 5, 8],
 [2, 6, 7],
 [3, 4, 8],
 [3, 5, 7],
 [4, 5, 6]]
  • range(1, 10): 1부터 9까지 검사. 끝값 10은 제외.
  • 다음 수를 앞선 수보다 크게 고르므로 중복 없이 탐색.
  • append(): 조건을 만족한 조합을 결과 리스트에 추가.
len(reference_result)  # 조건을 만족하는 조합 수
실행 결과
8
result == monty_result == reference_result  # 두 실행 방식과 기준 결과 비교

API를 호출하지 않고도, 같은 기준 코드를 Monty에서 실행해 결과 비교 가능.

with Monty(request_timeout=2) as sandbox:
    with sandbox.checkout(limits=limits) as session:
        monty_reference = session.feed_run(
            reference_code + "\nresult",
            inputs={"target": target},
        )

monty_reference == reference_result
실행 결과
True
  • 조합의 개수뿐 아니라 내용도 비교. 같은 개수여도 누락된 해와 잘못된 해가 섞일 수 있음.
  • 이번 문제에서 결과가 같다고 모든 숫자 퍼즐을 올바르게 푼다는 뜻은 아님.

목표값 바꾸기

  • 목표값을 18로 바꾼 뒤 같은 생성 코드를 다시 실행. 새로운 코드를 작성하지 않고도 입력에 따라 다른 조합을 탐색.
target = 18
execution = {"target": target}
exec(code, execution)
execution["result"]
  • 확인: 답을 코드에 미리 적었다면 목표값을 바꿔도 결과가 그대로일 수 있음.
  • 역할 분담: 모델은 탐색 절차 작성, 파이썬은 가능한 경우 검사, 사람은 문제 조건과 결과 확인.

LLM용 샌드박스 서비스

  • 코드를 작성하는 모델과 코드를 실행하는 서비스는 별개. 생성한 코드 문자열을 외부 실행 환경에 보내고 출력·파일·오류를 돌려받는 구조.
  • NumPy·pandas 같은 외부 패키지나 파일 작업, 터미널이 필요한 경우에는 이런 실행 환경을 검토.
서비스 제공하는 실행 환경 활용 예
E2B Python SDK로 코드 실행과 파일 작업을 요청하는 샌드박스 모델이 만든 분석 코드를 실행하고 결과 회수
Daytona 코드·프로세스 실행, 파일·터미널 등을 제공하는 샌드박스 코딩 에이전트가 파일을 수정하고 프로그램 테스트
Vercel Sandbox Firecracker microVM으로 격리한 실행 환경 생성한 코드나 앱을 실행·검사
  • 공통 흐름: 인증 → 실행 환경 생성 → 코드·자료 전달 → 실행 결과 회수 → 환경 정리.
  • 서비스별 계정·인증·사용량 비용과 네트워크·파일 접근 정책 확인. 격리 환경이라는 이유만으로 인터넷 연결까지 차단됐다고 가정하지 않기.
  • 이번 실습은 exec()와 Monty만 실행. 외부 서비스는 실행 환경을 선택하는 예시로 소개.

출처: Python exec·eval, OpenRouter 응답 형식.

퀴즈

문제 1 / 4맞음: 0힌트: 0틀림: 0채점중: 0남음: 4

이번 퍼즐에서 코드를 실행하는 목적은?

  • ○가능한 조합을 빠짐없이 검사하기 위해
  • ○모델이 예상한 답을 문장으로 바꾸기 위해
  • ○문제에 나온 숫자를 모델에 학습시키기 위해
  • ○API 키를 코드 안에 저장하기 위해

퀴즈를 풀려면 대화형 기능을 불러와야 합니다.