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장까지 같은 루프 구조가 겹을 늘려 가며 계속됩니다.