ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • LLM에 코드 실행 능력 붙이기 — PythonAstREPLTool
    IT 2026. 6. 24. 21:00
    LLM에 코드 실행 능력 붙이기 — PythonAstREPLTool

    ChatGPT의 고급 분석(Advanced Data Analysis) 기능을 써 본 사람이라면 한 가지 낯선 경험을 했을 것이다. 프롬프트에 "이 CSV를 분석해줘"라고 하면 LLM이 직접 계산 결과를 내놓는다. 정확한 수치다. 환각이 아니다. 어떻게 가능한 걸까?

    비밀은 간단하다. LLM이 직접 계산한 게 아니다. LLM은 코드를 생성하고, 별도의 Python 실행기가 그 코드를 실제로 실행한 다음 결과를 LLM에 돌려준다. LLM은 텍스트 생성 기계다 — 코드도 텍스트다. 실행은 다른 존재의 몫이다.

    LLM이 코드를 "실행"할 수 없는 이유

    LLM의 내부를 생각해보면 이건 당연하다. 모델은 입력 토큰에서 다음 토큰 확률을 계산해 텍스트를 생성한다. 어떤 수학 연산도, 어떤 메모리 읽기도, 어떤 파일 접근도 그 과정에 끼어들 자리가 없다. 2 + 2를 묻는다면 모델은 "4"가 자주 이어지는 패턴을 학습했기 때문에 맞힌다. 314159265 × 271828182를 묻는다면? 아마 틀린다. 계산이 아니라 패턴 추측이기 때문이다.

    아래 다이어그램은 LLM 혼자일 때 무슨 일이 일어나는지 보여준다.

    diagram

    LLM 단독 한계. LLM은 분석 코드를 완벽하게 작성할 수 있다. 하지만 그 코드를 실행할 방법이 없다. 결과가 필요하면 두 가지 중 하나다 — 코드만 출력하고 끝내거나, 계산 결과를 추측해서 적어버리거나. 후자가 바로 "환각"이 발생하는 전형적인 시나리오다. 복잡한 집계, 통계, 그룹별 비교처럼 패턴으로 맞추기 어려운 질문일수록 오차가 커진다.

    PythonAstREPLTool — 코드 실행 능력을 주입하는 도구

    PythonAstREPLTool은 LangChain Experimental 패키지에 들어 있는 에이전트 도구다. 이름 중간의 "AST"는 Abstract Syntax Tree(추상 구문 트리 — 파이썬이 코드를 파싱할 때 내부에서 만드는 트리 구조)를 뜻하고, "REPL"은 Read-Eval-Print Loop(코드를 읽고 실행해 결과를 출력하는 인터랙티브 셸)을 뜻한다. 즉, "파이썬 코드를 AST 수준에서 파싱해서 직접 실행하는 REPL 도구"다.

    이 도구의 핵심 특징 하나: locals 파라미터로 파이썬 객체를 도구 안으로 주입할 수 있다. 데이터프레임, 딕셔너리, 어떤 객체든 이름을 붙여 넘기면 에이전트가 생성한 코드에서 그 이름으로 바로 쓸 수 있다. 이게 데이터 분석의 핵심이다 — LLM이 코드를 짤 때 "df가 어떻게 생겼는지"를 알고 있으므로(대화에서 설명했다면) 맞는 컬럼명, 맞는 집계 방식으로 코드를 생성할 수 있다.

    다음 다이어그램은 도구가 연결된 후 어떻게 흐름이 바뀌는지 보여준다.

    diagram

    PythonAstREPLTool 연결 후 흐름. 에이전트가 코드를 생성하면 도구가 즉시 실행해 결과를 돌려준다. 에이전트는 그 결과를 보고 코드가 맞는지 확인하거나 추가 분석을 이어간다. LLM이 "추측"하는 구간이 없다 — 모든 수치는 실행 결과다. 이 루프가 ChatGPT 고급 분석이 동작하는 원리와 같다.

    실제 코드 구조

    아래는 CSV 데이터를 분석하는 에이전트를 만드는 최소 예시다.

    from langchain_experimental.tools import PythonAstREPLTool
    import pandas as pd
    
    # 1단: 분석할 데이터프레임 준비
    df = pd.read_csv("sales_data.csv")
    
    # 2단: df를 도구 내부 locals에 주입 — 에이전트 코드에서 df로 바로 접근 가능
    python_tool = PythonAstREPLTool(locals={"df": df})
    
    # 3단: 데이터 분석 전용 에이전트 생성
    #       시스템 프롬프트에서 "df를 쓰라"고 명시 — LLM이 변수명을 맞게 씀
    data_agent = create_agent(
        model=model,
        tools=[python_tool],
        system_prompt="""
        당신은 데이터 분석 전문가입니다.
        df 변수를 사용해 질문에 답하세요.
        실제 코드를 실행해 수치 결과를 반환하세요.
        """,
    )
    
    # 4단: 에이전트 호출 — LLM이 코드를 생성하고, 도구가 실행하고, 결과를 LLM이 해석
    result = data_agent.invoke({
        "messages": [{"role": "user", "content": "서울과 부산의 총 매출을 비교해줘"}]
    })
    

    코드 구조 풀이. 핵심은 locals={"df": df} 한 줄이다. 이 줄이 없으면 에이전트가 df를 사용하는 코드를 생성해도 NameError: name 'df' is not defined가 난다. 실행 환경이 격리된 REPL이기 때문이다. locals에 넘긴 객체만이 에이전트 코드의 실행 환경 안에 존재한다. 그래서 시스템 프롬프트에 df 변수를 사용하라고 명시하는 것도 중요하다 — LLM이 어떤 변수를 쓸 수 있는지 알아야 올바른 코드를 생성한다.

    함정 하나: langchain_experimental 패키지에 있다는 점에 주의할 필요가 있다. "Experimental"은 "실험적 — 프로덕션 환경에서 코드 실행을 허용한다는 뜻"이기도 하다. 사용자 입력이 그대로 Python 인터프리터에 전달될 수 있으므로, 신뢰할 수 없는 입력이 들어오는 환경에서는 샌드박스(Docker 컨테이너, gVisor 등) 안에서 실행해야 한다.

    실전 팁 — 더 안전한 대안: 명시적 @tool 함수

    PythonAstREPLTool은 강력하지만 LLM이 임의의 Python 코드를 자유롭게 실행할 수 있다는 점에서 권한 범위가 넓다. 허용할 연산을 세밀하게 제어해야 하는 경우라면, Retriever 도구와 마찬가지로 @tool 데코레이터로 명시적 함수를 감싸는 패턴이 더 안전하다. 특정 집계 연산만 함수로 노출하면 에이전트가 그 외의 코드는 실행할 수 없다.

    from langchain_core.tools import tool
    
    # 대안: 특정 연산만 허용하는 명시적 도구
    @tool
    def calculate_city_sales(city: str) -> str:
        """특정 도시의 총 매출을 계산한다."""
        result = df[df["city"] == city]["sales"].sum()
        return f"{city} 총 매출: {result:,.0f}원"
    # PythonAstREPLTool과 달리 허용된 연산만 실행 가능
    

    명시적 @tool 함수의 장점. calculate_city_sales는 city별 합산만 허용한다. 에이전트가 "파일 삭제", "환경변수 조회" 같은 코드를 생성해도 실행할 방법 자체가 없다. 반면 PythonAstREPLToollocals에 넘긴 객체를 제외한 나머지 Python 표준 라이브러리도 접근 가능하다. 두 접근의 트레이드오프: 명시적 @tool은 권한 범위가 좁아 안전하지만, 허용할 연산마다 함수를 작성해야 한다. PythonAstREPLTool은 임의의 분석 코드를 실행할 수 있어 유연하지만, 샌드박스 없이 프로덕션에 노출하면 위험하다. 신뢰할 수 없는 입력이 들어오는 환경일수록 명시적 @tool 패턴을 우선 고려해야 한다.

    왜 이 패턴이 중요한가

    LLM에 코드 실행 도구를 붙이는 패턴은 단순히 "계산을 대신해주는 기능"이 아니다. LLM의 약점(연산 정확도)을 도구의 강점(결정적 실행)으로 보완하는 구조적 설계다. 이 설계 원칙은 여러 곳에 적용된다 — 데이터프레임 분석뿐 아니라 SQL 실행, 테스트 자동화, 수치 시뮬레이션 모두 같은 패턴으로 동작한다. LLM은 "무엇을 해야 하는가"를 판단하고, 도구는 "실제로 한다". 이 역할 분리가 에이전트 시스템이 신뢰할 수 있는 수치를 다룰 수 있는 이유다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.