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

4장 코드와 나란히 놓고 보기

한 줄 요약

같은 일을 하는 두 코드를 나란히 놓습니다. 30줄이 1줄이 되었지만, 하는 일은 똑같습니다. 무엇이 사라졌고 무엇이 남았는지 눈으로 확인합니다.


1. 나란히 놓기

4장 — 손으로 짠 루프

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

def run_agent(question: str, verbose: bool = True) -> str:
    config = types.GenerateContentConfig(
        tools=list(TOOLS.values()),
        system_instruction=SYSTEM,
        temperature=0,
        automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),
    )
    history = [types.Content(role="user", parts=[types.Part(text=question)])]

    for step in range(1, MAX_STEPS + 1):
        resp = client.models.generate_content(
            model=GEMINI_MODEL, contents=history, config=config)

        if not resp.function_calls:
            return resp.text.strip()

        history.append(resp.candidates[0].content)

        for fc in resp.function_calls:
            result = TOOLS[fc.name](**dict(fc.args))
            history.append(types.Content(role="user", parts=[
                types.Part.from_function_response(
                    name=fc.name, response={"result": result})]))

    return "(최대 단계에 도달해 중단했습니다)"

5장 — LangChain

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

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


def ask(question: str) -> str:
    out = agent.invoke(
        {"messages": [{"role": "user", "content": question}]},
        {"recursion_limit": 10},          # 4장의 MAX_STEPS 자리
    )
    return out["messages"][-1].text.strip()

2. 한 줄씩 대응시키기

4장 5장 누가 하나
config = GenerateContentConfig(tools=..., system_instruction=...) create_agent(llm, tools=..., system_prompt=...) 우리
history = [question] {"messages": [{"role":"user", ...}]} 우리
MAX_STEPS = 5 (상한값) {"recursion_limit": 10} 우리
for step in range(1, MAX_STEPS+1): (루프 자체) 라이브러리
generate_content(contents=history, ...) 라이브러리
if not resp.function_calls: return 라이브러리
history.append(resp.candidates[0].content) 라이브러리
TOOLS[fc.name](**dict(fc.args)) 라이브러리
Part.from_function_response(...) 라이브러리
return resp.text.strip() out["messages"][-1].text.strip() 우리

중간 6줄이 통째로 사라졌습니다. 남은 것은 재료를 넣고 결과를 꺼내는 부분뿐입니다.


3. 사라지지 않은 것

여기가 이 절의 핵심입니다. 두 코드에서 똑같이 필요한 것들입니다.

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

# 4장에도, 5장에도 필요한 것

def tool_function(arg: str) -> str:
    """모델이 읽을 설명서."""     ← 여전히 우리가 쓴다
    ...                          ← 여전히 우리가 짠다
    return text                ← 여전히 우리가 정한다

SYSTEM = "..."                   ← 여전히 우리가 쓴다
4장 5장
도구 함수 본체 우리 우리
독스트링 우리 우리
타입힌트 우리 우리
반환값 설계 우리 우리
실패 처리 우리 우리
시스템 지시 우리 우리
안전장치 우리 우리
루프 우리 라이브러리

루프 한 줄만 넘어갔습니다. 나머지는 전부 그대로입니다.

"라이브러리를 쓰면 쉬워진다"는 말의 정확한 뜻 — 반복되는 연결 작업이 사라지는 것이지, 설계가 사라지는 게 아닙니다. 도구를 잘못 만들면 4장에서도 5장에서도 똑같이 틀립니다.


4. 대신 잃은 것

편해진 대가로 몇 가지를 잃었습니다.

① 단계가 안 보인다

[4장]                              [5장]
  [1단계] get_product_info(...)        (침묵 10초)
  [2단계] search_web(...)              A: 가전제품 90,229,700원, ...
  [3단계] 도구 호출 없음 → 최종 답변

되찾는 법out["messages"] 를 찍으면 됩니다. 앞 절에서 해 봤죠.

② 중간에 끼어들 수 없다

4장에서는 도구 실행 직전에 원하는 검사를 넣을 수 있었습니다.

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

for fc in resp.function_calls:
    if is_dangerous(fc):              # ← 여기에 무엇이든 끼워 넣을 수 있었다
        continue
    result = TOOLS[fc.name](...)

5장에서는 이 자리가 라이브러리 안에 있습니다.

되찾는 법도구 함수 안에서 검사합니다. 7절에서 한 것이 정확히 그것입니다.

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

@tool
def run_pandas(expr: str) -> str:
    log_expr(expr)                  # ← 무엇을 실행했는지 기록
    if call_limit_exceeded():                  # ← 횟수 제한
        return "오늘 조회 한도를 넘었습니다."
    result = run_in_sandbox(expr)        # ← 진짜 방어
    return str(result)

7절에서 확인한 대로 문자열로 위험한 단어를 걸러내려는 시도는 소용없습니다. 끼어들 자리가 도구 안에 있다는 것이 요점이지, 거기에 무엇을 넣을지는 위협 모델에 따라 다릅니다.

원칙 하나 — 라이브러리를 쓰면 끼어들 자리가 도구 안으로 옮겨 갑니다. 그래서 도구를 튼튼하게 만드는 것이 더 중요해집니다.

③ 무슨 일이 일어나는지 모른 채 쓰게 된다

여러분은 내부 동작을 이미 알고 있습니다. 4장을 손으로 해 봤으니까요.


5. 그래서 언제 무엇을 쓰나

상황 권장
처음 배울 때 손으로 — 안이 보여야 한다
루프 안에 특별한 처리가 필요할 때 손으로
일반적인 에이전트를 만들 때 라이브러리
여러 에이전트를 팀으로 묶을 때 라이브러리 (8장)
기억·상태가 필요할 때 라이브러리 (7장)

대부분은 라이브러리입니다. 손으로 짜는 것은 배울 때와 특수한 경우뿐입니다.


6. 다시 확인 — 하는 일은 똑같습니다

              생각 → 행동 → 관찰 → 다시 생각 → ... → 답변

[4장]  for step in range(...):        ← 우리가 돌린다
[5장]  create_agent(...)              ← 라이브러리가 돌린다
[7장]  StateGraph                     ← 우리가 그래프로 그린다
[8장]  플래너 + 워커 여러 개            ← 이 루프가 여러 겹으로

8장까지 이 구조가 계속됩니다. 겹이 늘어날 뿐입니다.


핵심 정리

  • 4장의 30줄이 5장에서 1줄이 됐습니다. 하는 일은 똑같습니다.
  • 사라진 것은 루프 관리 6줄뿐입니다.
  • 사라지지 않은 것 — 도구 함수, 독스트링, 타입힌트, 반환값 설계, 실패 처리, 시스템 지시, 안전장치. 전부 여전히 사람의 일입니다.
  • 잃은 것 — ① 단계가 안 보임 · ② 중간에 끼어들 수 없음.
  • 되찾는 법 — ①은 out["messages"] 로, ②는 도구 함수 안에서 검사로.
  • 라이브러리를 쓰면 끼어들 자리가 도구 안으로 옮겨 갑니다. 그래서 도구를 튼튼하게 만드는 게 더 중요해집니다.
  • 8장까지 같은 루프 구조가 겹을 늘려 가며 계속됩니다.
← 이전 절08. 따라하기 한국어로 묻고 집계 결과 받기다음 절 →10. 정리 체크리스트