6장. 데이터 조회 도구 구현
자동 함수 호출 — SDK가 왕복을 대신한다
한 줄 요약
파이썬 함수를 tools=[...]에 그대로 넘기면, SDK가 함수의 이름·타입힌트·독스트링으로 도구 선언을 만들고, 모델의 function call을 받아 함수를 실행하고 결과를 돌려주는 왕복까지 대신합니다. 5장에서 손으로 한 일 그대로입니다.
1. 5장과 무엇이 달라지는가
| 5장 (손으로) | 6장 (자동으로) | |
|---|---|---|
| 도구 선언 | FunctionDeclaration을 직접 썼다 |
SDK가 함수에서 만든다 |
| 함수 실행 | get_order_status(**fc.args)를 직접 불렀다 |
SDK가 부른다 |
| 결과 반환 | contents에 세 덩어리를 담아 다시 호출했다 |
SDK가 한다 |
generate_content 호출 |
우리 코드에 두 번 | 우리 코드에 한 번 |
| 돌아오는 것 | 첫 번째는 function call, 두 번째는 답변 | 바로 답변 |
호출 코드는 이렇게 줄어듭니다.
response = client.models.generate_content(
model=MODEL,
contents=question,
config=types.GenerateContentConfig(
system_instruction=SYSTEM,
temperature=1.0,
tools=[get_order_status, track_shipping], # 파이썬 함수를 그대로 전달
automatic_function_calling=types.AutomaticFunctionCallingConfig(
maximum_remote_calls=4
),
),
)
print(response.text)
모델을 부르는 횟수가 줄어든 것은 아닙니다. SDK 안에서 여전히 두 번 이상 호출됩니다. 우리 눈에 보이지 않을 뿐입니다.
2. 독스트링이 도구 선언이 된다
독스트링(docstring) 은 함수 바로 아래 """...""" 안에 적는 설명글이고, 타입힌트(type hint) 는 order_id: str처럼 인자의 타입을 적어 둔 표시입니다. 보통은 사람을 위한 것이어서 없어도 코드는 돕니다.
자동 방식에서는 이 둘이 모델에게 가는 안내문의 재료가 됩니다.
def track_shipping(order_id: str) -> dict:
"""주문번호로 택배사·송장번호·출고일·배송완료일을 조회한다. 배송 위치 문의에 사용한다.
Args:
order_id: 하루마켓 주문번호. 'HR'로 시작한다.
"""
SDK가 이 함수로 만드는 선언을 출력해 보면 이렇습니다(「실습문제와 해답」 문제 3에서 직접 출력합니다).
{
"description": "주문번호로 택배사·송장번호·출고일·배송완료일을 조회한다. 배송 위치 문의에 사용한다.\n\nArgs:\n order_id: 하루마켓 주문번호. 'HR'로 시작한다.",
"name": "track_shipping",
"parameters": {
"properties": {
"order_id": {
"type": "STRING"
}
},
"required": [
"order_id"
],
"type": "OBJECT"
}
}
| 함수의 이것이 | 선언의 이것이 된다 |
|---|---|
함수 이름 track_shipping |
name |
독스트링 전체 (Args: 부분 포함) |
description |
타입힌트 order_id: str |
parameters의 인자 이름과 타입 |
| 기본값이 없는 인자 | required |
눈여겨볼 점이 있습니다. Args: 아래에 쓴 인자 설명은 5장처럼 인자 칸에 따로 들어가지 않고, 도구 설명 글 안에 함께 실려 갑니다. 모델에게 전달되기는 하므로 "'HR'로 시작한다" 같은 단서는 여전히 쓸모가 있습니다.
자동 방식에서 독스트링은 문서가 아니라 기능입니다. 지우면 모델은 이 도구가 무엇인지 모르게 되고, 모호하게 쓰면 엉뚱하게 불립니다.
track_shipping의 독스트링에 있는 "배송 위치 문의에 사용한다" 를 봅니다. 도구가 둘이 되면 모델은 골라야 하고, 고르는 근거는 이 글뿐입니다. 무엇을 하는 도구인지에 더해 언제 쓰는 도구인지를 적어 주었습니다.
3. 횟수 상한 — maximum_remote_calls
모델은 도구 결과를 보고 다른 도구를 또 부를 수 있습니다. 자동 방식에서는 SDK가 이 반복을 계속 이어 갑니다.
automatic_function_calling=types.AutomaticFunctionCallingConfig(
maximum_remote_calls=4
)
maximum_remote_calls는 SDK가 자동으로 이어 갈 수 있는 호출 횟수의 상한입니다. 정하지 않으면 SDK의 기본값 10이 쓰입니다(이 과정에서 설치한 google-genai 2.8 기준).
상한이 필요한 이유는 비용입니다. 도구를 한 번 부를 때마다 모델을 한 번 더 호출하고, 그때마다 지금까지의 대화 전체가 다시 입력 토큰으로 들어갑니다. 주문 조회 한 건에 호출이 열 번씩 필요할 일은 없으므로 4로 낮춰 두었습니다. 이런 상한을 왜, 어떻게 거는지는 10장에서 자세히 봅니다.
4. 숨은 왕복을 들여다보는 창 — history
왕복이 SDK 안으로 숨었으므로, 모델이 어떤 도구를 어떤 값으로 불렀는지 따로 확인해야 합니다. 응답의 automatic_function_calling_history 에 왕복 과정의 대화가 차례로 담겨 있습니다.
if response.automatic_function_calling_history:
for content in response.automatic_function_calling_history:
for part in content.parts or []:
if part.function_call:
print(f" [도구 호출] {part.function_call.name}"
f"({dict(part.function_call.args)})")
이 안에는 5장에서 contents에 손으로 담았던 것과 같은 덩어리들(질문, 모델의 function call, 실행 결과)이 들어 있습니다. 그중 function call만 골라 한 줄씩 출력하는 코드입니다.
답만 보면 "맞는 것 같다"에서 끝납니다. 이 출력이 있어야 어떤 도구가 어떤 인자로 불려서 그 답이 나왔는지를 말할 수 있습니다. 도구를 쓰는 코드에서는 이 출력을 늘 켜 둡니다.
5. 시스템 프롬프트로 사실의 경계를 긋는다
도구가 사실을 가져와도, 모델은 도구가 주지 않은 빈자리를 짐작으로 채울 수 있습니다. orders.csv에는 도착 예정일이 없는데, 고객은 "언제 와요?"라고 묻습니다.
SYSTEM = """당신은 하루마켓 고객지원 상담원 '하루'입니다.
주문 관련 문의는 반드시 도구로 조회한 실제 데이터로만 답합니다.
조회 결과에 없는 내용(도착 예정 시간 등)은 추측하지 않습니다.
존댓말로 3~5문장 이내로 답하고, 마지막에 다음 행동을 안내합니다."""
둘째, 셋째 줄이 이번 장에서 더한 규칙입니다. 도구는 사실을 가져오고, 프롬프트는 그 사실 밖으로 나가지 못하게 묶습니다. 넷째 줄은 3장에서 배운 대로 길이를 숫자로 정한 것입니다.
6. 자동 방식의 값
편해진 만큼 손에서 떠난 것이 있습니다.
| 손으로 할 때 있던 것 | 자동 방식에서 |
|---|---|
| 실행 직전에 검사를 넣을 자리 | 우리 코드 밖에 있습니다. 검사는 도구 함수 안에 넣어야 합니다 |
| 모델이 요청한 이름을 확인할 자리 | SDK가 요청받은 함수를 바로 실행합니다. 이름을 직접 확인하고 싶으면 왕복을 손으로 돌립니다 |
| 한 단계씩 보는 출력 | automatic_function_calling_history로 나중에 봅니다 |
그래서 왕복을 더 세밀하게 다뤄야 할 때는 다시 손으로 돌립니다(9장).
핵심 정리
- 파이썬 함수를
tools=[...]에 넘기면 SDK가 선언을 만들고 왕복을 대신합니다. - 함수 이름, 독스트링, 타입힌트가 도구 선언이 됩니다. 독스트링은 문서가 아니라 기능입니다.
- 독스트링에는 무엇을 하는지와 함께 언제 쓰는지를 적습니다.
maximum_remote_calls로 자동 왕복의 횟수에 상한을 겁니다.automatic_function_calling_history로 어떤 도구가 불렸는지 확인합니다.- 시스템 프롬프트로 답변을 조회 결과 안에 묶습니다.