따라하기 — 모델이 무엇을 부르려 했는지 들여다보기
한 줄 요약
자동 실행을 꺼 놓고 호출하면, 모델의 요청서 그 자체를 볼 수 있습니다. 2절에서 말로만 설명한 경계선을 눈으로 확인하는 절입니다.
1. 코드 — peek()
아래는 code/ch03_order_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
def peek(question: str):
"""모델이 '무엇을 부르려 했는지'만 들여다본다(실행은 하지 않는다)."""
resp = client.models.generate_content(
model=GEMINI_MODEL,
contents=question,
config=types.GenerateContentConfig(
tools=[get_order_status],
system_instruction=SYSTEM,
temperature=0,
# 자동 실행을 꺼서, 모델의 '결정'만 확인한다
automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),
),
)
return resp.function_calls or []
ask() 와 두 곳이 다릅니다.
ask() |
peek() |
|
|---|---|---|
| 자동 실행 | 켜짐(기본) | disable=True 로 끔 |
| 돌려주는 것 | resp.text (최종 답변) |
resp.function_calls (요청서) |
2. 실행 — [2] 부분
==============================================================
[2] 모델은 무엇을 부르려 하는가 (아직 실행 전)
부르려는 함수: get_order_status
채워 넣은 값 : {'order_id': 'O001203'}
→ 모델은 '이 함수를 이 값으로 불러 달라'고 요청만 합니다. 실행은 우리 쪽 몫입니다.
이것이 2절에서 말한 요청서의 실제 모습입니다.
get_order_status— 함수 이름{'order_id': 'O001203'}— 채워 넣은 인자
orders.csv 는 아직 읽히지 않았습니다. 아무것도 실행되지 않았습니다. 모델은 여기까지만 합니다.
3. 이 두 줄을 만들어 낸 것
모델이 이 요청서를 만들 수 있었던 이유를 되짚어 봅시다.
모델은 독스트링 한 줄을 근거로 두 가지 판단을 모두 내렸습니다. 3절에서 "독스트링이 곧 사용 설명서"라고 한 것이 이런 뜻입니다.
4. 여러 개를 한 번에 요청하기도 합니다
peek() 가 리스트를 돌려주는 이유가 있습니다. 모델이 여러 도구를 한꺼번에 요청할 수 있기 때문입니다.
[2] 의 질문을 이렇게 바꿔 보세요.
직접 고쳐 보기 —
agentic_ai/code/ch03_order_agent.py를 열어 아래처럼 바꾸거나 덧붙인 뒤 다시 실행하세요.
for fc in peek("주문 O001203 과 O000533 둘 다 상태 알려주세요"):
print(f" 부르려는 함수: {fc.name}")
print(f" 채워 넣은 값 : {dict(fc.args)}")
부르려는 함수: get_order_status
채워 넣은 값 : {'order_id': 'O001203'}
부르려는 함수: get_order_status
채워 넣은 값 : {'order_id': 'O000533'}
요청이 두 개 나왔습니다. 4장에서 루프를 직접 짤 때, 이 목록을 전부 돌면서 실행하게 됩니다.
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
for fc in resp.function_calls: # 4장에서 쓸 형태
result = TOOLS[fc.name](**dict(fc.args))
**dict(fc.args) 는 딕셔너리를 인자로 펼쳐 넣는 문법입니다. {'order_id': 'O001203'} 이 get_order_status(order_id='O001203') 이 됩니다.
5. 도구를 안 부를 때는 빈 목록
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
for fc in peek("오늘 날씨 어때요?"):
...
아무것도 출력되지 않습니다. resp.function_calls 가 비어 있기 때문입니다.
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
return resp.function_calls or [] # None 일 수도 있어서 or [] 로 감쌌다
"부를 도구가 없다 = 이제 답할 수 있다" — 이 판정이 4장 루프의 종료 조건이 됩니다. 미리 봐 두세요.
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
# 4장에서 쓸 형태
if not resp.function_calls:
return resp.text.strip() # 도구 요청이 없으면 최종 답변이다
6. 언제 자동을 끄고 언제 켜나
| 상황 | 자동 함수 호출 | 이유 |
|---|---|---|
| 도구 하나, 단순 조회 | 켬 (3장 ask) |
짧고 편하다 |
| 도구 여럿, 순서가 궁금 | 끔 (4장) | 어떤 순서로 쓰는지 봐야 한다 |
| 실행 전 검사가 필요 | 끔 | 위험한 요청을 걸러야 한다 |
| 학습·디버깅 | 끔 | 안이 보여야 한다 |
5장부터는 이 선택 자체가 사라집니다. LangChain을 쓰면 루프를 라이브러리가 돌리기 때문입니다. 그래서 4장까지 손으로 해 보는 것입니다. 5장에서
create_agent한 줄을 쓸 때, 그 안에서 무슨 일이 일어나는지 이미 봤기 때문에 안심하고 쓸 수 있습니다.
스스로 해 보기
- 독스트링에서
예: 'O001203'를 지우고peek("주문 O001203 상태 알려주세요")를 실행해 보세요. 여전히 인자를 정확히 뽑나요? - 독스트링을
"""상태 조회"""로 확 줄이고 실행해 보세요. 어떻게 되나요? 왜 그런지 한 문장으로 적어 보세요. peek("어제 주문한 거 어떻게 됐어요?")— 주문번호가 없는 질문입니다. 모델은 무엇을 요청할까요? 예측하고 확인하세요.
핵심 정리
automatic_function_calling=...(disable=True)로 자동 실행을 끄면 모델의 요청서를 직접 볼 수 있습니다.- 요청서는
resp.function_calls에 들어 있고,fc.name과fc.args로 꺼냅니다. - 이 시점에는 아무것도 실행되지 않았습니다. 모델은 여기까지만 합니다.
- 요청서는 여러 개일 수 있습니다. 4장에서 전부 돌면서 실행하게 됩니다.
function_calls가 비어 있으면 "이제 답할 수 있다" 는 뜻입니다. 이것이 4장 루프의 종료 조건입니다.- 도구가 하나면 자동에 맡기고, 여럿이거나 검사가 필요하면 끕니다.