📚 Agentic AI - 기업용 자율 에이전트 개발 5장 · 에이전트 ③ CSV 데이터분석 Agentic AI란

create_agent 한 줄이 대신해 주는 것

한 줄 요약

create_agent(llm, tools, system_prompt) 한 줄이 4장의 루프 30줄을 대신합니다. 무엇을 대신해 주고 무엇은 여전히 우리 일인지 분명히 해 둡니다.


1. 한 줄

아래는 6절에서 만들 code/ch05_csv_agent.py 의 일부를 미리 보는 것입니다 — 지금 붙여 넣지 않아도 됩니다.

from langchain.agents import create_agent

llm = get_chat(temperature=0)
agent = create_agent(llm, tools=[run_pandas], system_prompt=SYSTEM)

인자 셋입니다.

인자 무엇 4장에서는
첫 번째 모델 객체 client + GEMINI_MODEL
tools 도구 목록 TOOLS 딕셔너리
system_prompt 시스템 지시 configsystem_instruction

4장에서 config 에 넣던 것과 같은 재료입니다. 다만 루프는 안 짭니다.


2. 부르는 법

아래는 6절에서 만들 code/ch05_csv_agent.py 의 일부를 미리 보는 것입니다 — 지금 붙여 넣지 않아도 됩니다.

def ask(question: str) -> str:
    """에이전트에게 한국어로 묻고 답을 받는다."""
    out = agent.invoke(
        {"messages": [{"role": "user", "content": question}]},
        # 4장의 MAX_STEPS 에 해당한다. 단위가 달라서(노드 1회 = 1) 두 배쯤 잡는다.
        # 안 넣으면 기본값이 10007 이라 사실상 무제한이다.
        {"recursion_limit": 10},
    )
    return out["messages"][-1].text.strip()      # 마지막 메시지가 최종 답변

두 번째 인자 {"recursion_limit": 10} 은 반복 상한입니다. 6번에서 따로 봅니다. 지금은 첫 번째 인자만 보세요.

넣는 것

아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.

{"messages": [{"role": "user", "content": question}]}

messages 라는 키에 대화 목록을 넣습니다. 2장의 history 와 같은 자리입니다.

지금은 한 개짜리지만, 7장에서 이 목록이 자동으로 쌓이게 됩니다. 그게 멀티턴이죠.

받는 것

아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.

out["messages"][-1].text

돌아오는 것도 messages 목록입니다. 질문 하나를 넣었는데 여러 개가 돌아옵니다.

out["messages"] = [
  HumanMessage("카테고리별 매출 상위 3개는?"),
  AIMessage(도구 호출 요청),                    ← 모델의 결정
  ToolMessage("가전 90229700\n전자기기 ..."),   ← 도구 실행 결과
  AIMessage("가전제품 90,229,700원, ..."),      ← 최종 답변
]

4장에서 우리가 history 에 쌓던 것과 똑같습니다. 라이브러리가 대신 쌓아 준 것을 통째로 돌려줍니다.

평범한 함수 하나가 무엇이 되어 어디로 들어가고, 무엇이 되어 돌아오는지 하나의 흐름으로 살펴보세요.

def run_pandas(expr: str) -> str:독스트링 — 모델이 읽는 설명서@tool도구 객체name · description · argscreate_agent(모델, tools=[…], system_prompt=…)agentinvoke({"messages": [질문 1개]})반환되는 messages (4개)4장의 history와 비교HumanMessage(질문)history[0]AIMessage(도구 호출 요청)history[1]ToolMessage(도구 실행 결과)history[2]AIMessage(최종 답변)history[3][-1].text메시지 1개를 보내면 4개가 반환됩니다. 4장에서 직접 쌓던 history가 그대로 담겨 있습니다.
함수에 @tool 을 붙여 도구로 만들고, create_agent 에 넘기면 messages 네 개가 돌아온다

넣는 것은 하나인데 받는 것은 네 개 — 그 사이에 4장의 루프가 통째로 들어 있기 때문입니다.

그래서 마지막 것([-1])이 최종 답변입니다.

여기서 4장을 직접 구현해 본 보람을 느낄 수 있습니다. out["messages"] 안에 왜 4개가 들어 있는지, ToolMessage 가 무엇인지 — 손으로 쌓아 본 사람에게는 설명이 필요 없습니다.


3. 안에서 무슨 일이 일어나나

create_agent 가 만든 것은 사실 그래프입니다.

모델도구 호출이 있나?있다없다도구 실행끝 (답변)모델로 돌아감4장에서 for 문으로 짠 것과 같은 그림입니다. create_agent 한 줄이 이 루프를 대신 만들어 줍니다.
create_agent 가 대신 만들어 주는 루프 — 4장에서 for 문으로 짠 것과 같은 그림이다

4장의 for 루프와 같은 그림입니다. if not resp.function_calls: return 이 "없다 → 끝" 화살표죠.

그래프(graph)란할 일을 상자로 그리고 "다음엔 어디로"를 화살표로 이어 놓은 것입니다. 위 그림이 그래프입니다 — 상자 둘과 갈림길 하나. 라이브러리 이름 LangGraph 의 "Graph"가 이 뜻입니다.

그리고 상자 하나하나를 노드(node)라고 부릅니다. 위 그림에는 [모델][도구실행], 노드가 둘입니다. 한 바퀴는 노드 두 개를 지나므로, 4장의 "1단계" = 노드 2회입니다. 6절의 recursion_limit 표가 이 셈에서 나옵니다.

"그래프"라는 말이 나온 이유create_agentLangGraph 위에 만들어져 있습니다. 7장에서 우리가 직접 그래프를 조립할 때, "아, create_agent 도 이런 거였구나" 하게 됩니다. 그때 이 그림을 다시 보게 됩니다.


4. 무엇을 대신해 주나

4장에서 손으로 create_agent
history = [question] 자동
for step in range(...) 자동
contents=history 로 매번 전송 자동
if not resp.function_calls 판정 자동
history.append(모델의 결정) 자동
TOOLS[fc.name](**args) 실행 자동
Part.from_function_response 기록 자동
도구 실행 중 오류 처리 자동 (오류 메시지를 모델에게 전달)

루프 관리 전부입니다.


5. 무엇은 여전히 우리 일인가

이쪽이 더 중요합니다.

여전히 사람이
어떤 도구를 만들 것인가 라이브러리는 도구를 만들어 주지 않는다
도구 안의 로직 pandas 식을 어떻게 실행할지
독스트링 모델이 읽는 설명서
시스템 지시 업무 규칙
안전장치 위험한 코드를 어떻게 막을지 (다음다음 절)
반복 상한 recursion_limit 로 지정
결과 검증 나온 숫자가 말이 되는지

"돌아가는데 왜 알아야 하나"에 대한 답이 여기 있습니다. create_agent루프를 대신해 주지, 판단을 대신해 주지 않습니다. 도구를 잘못 만들면 잘못된 답이 아주 매끄럽게 나옵니다. 그리고 그건 라이브러리 탓이 아닙니다.


6. 반복 상한 걸기

4장의 MAX_STEPS 에 해당하는 것입니다. 실습 파일에 이미 들어 있습니다.

아래는 code/ch05_csv_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.

out = agent.invoke(
    {"messages": [{"role": "user", "content": question}]},
    # 4장의 MAX_STEPS 에 해당한다. 단위가 달라서(노드 1회 = 1) 두 배쯤 잡는다.
    # 안 넣으면 기본값이 10007 이라 사실상 무제한이다.
    {"recursion_limit": 10},
)

하는 일은 비슷하지만 세는 단위가 다릅니다.

무엇을 세나
4장 MAX_STEPS 루프 한 바퀴 (모델 호출 + 도구 실행)
recursion_limit 노드 실행 횟수 — 모델 1 + 도구 1 = 2

그래서 MAX_STEPS=5 에 해당하는 값이 recursion_limit=10 입니다.

안 넣으면 기본값이 10007 입니다. "무한 반복은 안 된다"는 말은 맞지만, 도구가 계속 오류를 내며 반복 호출되면 한 질문에 모델을 5,000번쯤 부를 수 있습니다. 요금 관점에서는 무한과 다를 바 없죠.

그래서 실습 코드에 넣어 두었습니다. 4장에서 MAX_STEPS 를 손으로 걸어 본 사람만 이 설정의 이름을 보고 무엇에 대응하는지 압니다.


7. 중간 단계를 다시 보고 싶다면

out["messages"] 를 전부 찍으면 됩니다.

아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.

out = agent.invoke({"messages": [{"role": "user", "content": q}]})
for m in out["messages"]:
    print(f"  {type(m).__name__:14s} {str(m.content)[:70]}")
  HumanMessage   카테고리별 매출 상위 3개를 알려주세요.
  AIMessage
  ToolMessage    category
가전       90229700
전자기기     53967950
스포츠레저    53259600
  AIMessage      가전제품 90,229,700원, 전자기기 53,967,950원, 스포츠/레저 53,259,600원으로 ...

4장의 단계별 출력과 같은 정보입니다. 두 번째 AIMessagecontent 가 비어 있는 것도 눈여겨보세요 — 그 메시지는 글이 아니라 도구 호출 요청을 담고 있습니다.

아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.

print(out["messages"][1].tool_calls)
[{'name': 'run_pandas', 'args': {'expr': "orders[~orders['status']..."}, ...}]

3장의 resp.function_calls 와 같은 것입니다. 이름만 tool_calls 로 바뀌었습니다.

디버깅할 때 여기를 보세요. 에이전트가 이상한 답을 내면 tool_calls 를 찍어 모델이 어떤 식을 만들었는지 확인하는 것이 가장 빠릅니다.


핵심 정리

  • create_agent(모델, tools=[...], system_prompt=...) 한 줄이 4장의 루프 30줄을 대신합니다.
  • 부를 때는 agent.invoke({"messages": [...]}), 답은 out["messages"][-1].text.
  • 돌아오는 messages 에는 질문 · 도구 호출 요청 · 실행 결과 · 최종 답변이 전부 들어 있습니다. 4장의 history 와 같습니다.
  • create_agent 는 내부적으로 그래프입니다 — 상자(노드)와 화살표로 그린 흐름. 7장에서 직접 조립하게 됩니다.
  • 대신해 주는 것은 루프 관리 전부입니다.
  • 여전히 사람의 일 — 도구 구성, 도구 로직, 독스트링, 시스템 지시, 안전장치, 결과 검증.
  • 반복 상한은 {"recursion_limit": 10} 으로 겁니다. 세는 단위가 달라 MAX_STEPS=5recursion_limit=10 이고, 기본값이 10007 이라 명시하지 않으면 사실상 무제한입니다.
  • 중간 단계는 out["messages"].tool_calls 로 다시 볼 수 있습니다. 디버깅의 출발점입니다.
← 이전 절03. tool 데코레이터 함수를 도구로 등록하기다음 절 →05. LLM은 계산기가 아니다 숫자는 코드에게