따라하기 — 사내 상품 조회 + 웹검색 두 도구를 함께 쥐여 주기
한 줄 요약
TOOLS 딕셔너리로 도구를 묶고, config 에 함께 넘깁니다. 그리고 모델이 도구 두 개를 순서대로 쓰는 모습을 실행 결과로 확인합니다.
1. 도구를 묶는 방법
아래는 code/ch04_web_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
TOOLS = {"get_product_info": get_product_info, "search_web": search_web}
이름 → 함수 딕셔너리입니다. 왜 리스트가 아니라 딕셔너리일까요?
모델이 돌려주는 요청서에는 함수 이름이 문자열로 들어 있습니다.
fc.name = "search_web" ← 문자열이다
이 문자열로 실제 함수를 찾아야 합니다.
아래는 code/ch04_web_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
result = TOOLS[fc.name](**dict(fc.args))
└─────────┘
"search_web" 이라는 문자열로 함수를 꺼낸다
딕셔너리면 TOOLS["search_web"] 한 번에 찾습니다. 리스트였다면 이름으로 뒤져야 하죠.
모델에게 넘길 때는 값(함수)만 꺼내 씁니다.
아래는 code/ch04_web_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
config = types.GenerateContentConfig(
tools=list(TOOLS.values()), # [get_product_info, search_web]
...
)
이 패턴은 앞으로 계속 나옵니다. 도구를 추가하려면
TOOLS에 한 줄만 넣으면 됩니다.config도 실행 코드도 안 고칩니다.
python TOOLS = {..., "get_order_status": get_order_status} # 3장 도구 추가
2. 자동 실행은 끕니다
아래는 code/ch04_web_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
config = types.GenerateContentConfig(
tools=list(TOOLS.values()),
system_instruction=SYSTEM,
temperature=0,
# 자동 실행을 꺼야 우리가 루프를 직접 제어할 수 있다
automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),
)
3장에서는 켜 두고 SDK에 맡겼습니다. 이번엔 끕니다. 이유가 셋입니다.
- 단계를 보고 싶다 — 어떤 도구를 어떤 순서로 쓰는지
- 횟수를 통제하고 싶다 —
MAX_STEPS로 상한을 건다 - 루프의 내부를 배우려고 — 5장부터는 안 보이게 된다
3. 실행 — [2] 부분
터미널에서 실행 — 프로젝트 폴더
agentic_ai에서,(agentic)표시를 확인한 뒤.
python code/ch04_web_agent.py
==============================================================
[2] 두 도구가 모두 필요한 질문 — ReAct 루프가 도는 모습
질문: 승승 스마트워치 Fit 5는 얼마인가요? 요즘 스마트워치 시장 가격대와
비교하면 어느 수준인지도 알려주세요.
[1단계] get_product_info({'product_name': '승승 스마트워치 Fit 5'})
└ 승승 스마트워치 Fit 5 | 카테고리 전자기기 | 판매가 159,000원 ...
[2단계] search_web({'query': '스마트워치 시장 가격대'})
└ - 스마트워치 시장 규모 및 점유율, 성장 예측 2026-2035: ...
[3단계] 도구 호출 없음 → 최종 답변
[답변]
승승 스마트워치 Fit 5는 159,000원입니다. 현재 스마트워치 시장은
5만 원대 가성비 모델부터 30만 원대 프리미엄 모델까지 형성되어 있으며,
Fit 5는 이 중 중간 가격대에 해당합니다.
==============================================================
관찰: 사내 데이터(가격)는 CSV에서, 시장 정보는 웹에서 각각 가져와 합쳤습니다.
어떤 도구를 먼저 쓸지는 우리가 안 정했습니다. 모델이 스스로 정했습니다.
검색 결과는 실행할 때마다 다릅니다. 단계의 구조(1단계 CSV → 2단계 웹 → 3단계 답변)가 같으면 성공입니다.
4. 결과에서 확인할 것 네 가지
① 순서를 모델이 정했다
우리 코드 어디에도 이런 게 없습니다.
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
# 우리가 짜지 않은 코드
result1 = get_product_info(...)
result2 = search_web(...)
앞 절에서 예측해 보라고 했던 순서입니다. 가격을 먼저 알아본 이유는, 비교하려면 기준이 있어야 하기 때문입니다. 모델이 그 순서를 스스로 잡았습니다.
② 도구를 정확히 나눠 썼다
자사 가격 → get_product_info (CSV)
시장 정보 → search_web (인터넷)
독스트링에 적어 둔 사용 범위가 반영된 결과입니다. 앞 절에서 본 그대로죠.
③ 검색어를 문장에서 뽑아냈다
질문 안의 표현: "요즘 스마트워치 시장 가격대와 비교하면 어느 수준인지도"
실제 검색어: "스마트워치 시장 가격대"
④ 3단계에서 스스로 멈췄다
[3단계] 도구 호출 없음 → 최종 답변
MAX_STEPS 는 5인데 3단계에서 끝났습니다. 더 부를 도구가 없다고 모델이 판단한 것입니다. 우리가 "이제 그만"이라고 하지 않았습니다.
도구를 2번 썼는데 모델 호출은 3번입니다. 도구를 N번 쓰면 호출은 N+1번 — 마지막 한 번은 "이제 답한다"는 호출입니다. 3장에서 말한 "도구 한 번에 호출 두 번" 은 N=1인 경우였습니다. 2장 7절의 비용 계산도 이 식으로 하면 맞습니다.
5. history 안을 들여다보기
3단계가 끝난 시점의 history 는 이렇게 생겼습니다. 요소 다섯 개짜리 리스트입니다.
history[0] user 질문 원문
history[1] model get_product_info 호출 요청 ← 모델의 결정
history[2] user 함수 실행 결과 (159,000원 ...) ← 우리가 넣은 관찰
history[3] model search_web 호출 요청 ← 모델의 결정
history[4] user 함수 실행 결과 (시장 정보 ...) ← 우리가 넣은 관찰
직접 찍어 보고 싶다면 run_agent 의 return 직전에 넣어 보세요.
직접 고쳐 보기 —
agentic_ai/code/ch04_web_agent.py를 열어 아래처럼 바꾸거나 덧붙인 뒤 다시 실행하세요. (확인이 끝나면 지워도 됩니다.)
for i, c in enumerate(history):
kind = "텍스트" if c.parts[0].text else "도구 관련"
print(f" history[{i}] role={c.role:5s} {kind}")
여기서 확인할 것 —
role이model인 것과user인 것이 번갈아 나옵니다. 그런데history[2]와history[4]는 사람이 말한 게 아닌데도user입니다. 도구 실행 결과를 "바깥에서 들어온 정보"로 취급하기 때문입니다. 처음 보면 어색하지만, 모델 입장에서는 자기가 만든 것(model)과 밖에서 온 것(user) 두 종류뿐이라 이렇게 됩니다.
6. 한 단계에 도구를 여러 개 부를 수도 있습니다
아래는 code/ch04_web_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
for fc in resp.function_calls: # 복수형인 이유
result = TOOLS[fc.name](**dict(fc.args))
3장 6절에서 봤듯 모델이 한 번에 여러 도구를 요청할 수 있습니다. 그러면 한 단계에서 여러 줄이 찍힙니다.
[1단계] get_product_info({'product_name': '스마트워치'})
└ 승승 스마트워치 Fit 5 | 카테고리 전자기기 | 판매가 159,000원 ...
[1단계] get_product_info({'product_name': '로봇청소기'})
└ 장구 로봇청소기 CleanX | 카테고리 가전 | 판매가 449,000원 ...
서로 의존하지 않는 일이면 모델이 한꺼번에 요청합니다. 반대로 앞 결과가 있어야 다음을 정할 수 있으면 단계를 나눕니다. 우리 예제가 후자였죠.
스스로 해 보기
- 질문을 "승승 스마트워치 Fit 5 얼마예요?" 로만 바꿔 보세요. 몇 단계에서 끝나나요? 왜 그럴까요?
- 질문을 "승승 스마트워치 Fit 5와 장구 로봇청소기 가격을 알려주세요" 로 바꿔 보세요. 도구가 몇 번, 몇 단계에 걸쳐 불리나요?
("승승 로봇청소기"라고 쓰면 안 됩니다. 실제 상품명은장구 로봇청소기 CleanX라서 부분 일치로 찾을 수 없습니다.products.csv를 열어 이름을 확인하고 쓰는 것도 연습입니다.) verbose=False로 실행해 보세요 (run_agent(q, verbose=False)). 5장부터 보게 될 화면이 이것입니다.SYSTEM에서 도구 이름을 언급한 부분을 지우고 실행해 보세요. 도구 선택이 달라지나요?
핵심 정리
- 도구는
TOOLS딕셔너리(이름 → 함수) 로 묶습니다. 모델이 이름 문자열로 요청하기 때문입니다. - 모델에게 넘길 때는
list(TOOLS.values()). 도구 추가는 딕셔너리에 한 줄이면 됩니다. - 자동 실행을 끕니다. 단계를 보고, 횟수를 통제하고, 안을 배우기 위해서입니다.
- 결과에서 확인할 것 — ① 순서를 모델이 정했다 · ② 도구를 정확히 나눠 썼다 · ③ 검색어를 뽑아냈다 · ④ 스스로 멈췄다.
history에는 질문 · 모델의 결정 · 실행 결과가 번갈아 쌓입니다. 도구 결과의role은user입니다.- 서로 의존하지 않는 일은 한 단계에서 여러 도구를 부르기도 합니다.