실무 Multi-Agent 오케스트레이션 5장 · Function Calling 원리 4 / 7 ← 이전목차다음 → TechLead Cro

5장. Function Calling 원리

왕복을 코드로 옮기기 — 호출 두 번, 대화 세 덩어리

한 줄 요약

왕복은 generate_content를 두 번 부르는 일입니다. 첫 번째 호출에서 모델의 결정을 받고, 두 번째 호출에서 질문 + 모델의 결정 + 실행 결과 세 가지를 차례로 담아 보내 답변을 받습니다.


1. 첫 번째 호출 — 결정을 받는다

response = client.models.generate_content(
    model=MODEL,
    contents=[user_content],
    config=types.GenerateContentConfig(
        tools=[tool],
        automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),
    ),
)

fc = response.function_calls[0]  # 모델의 결정
줄 뜻
tools=[tool] 도구 선언의 꾸러미를 모델에게 줍니다
automatic_function_calling=...(disable=True) SDK의 자동 실행을 끕니다. 왕복을 직접 보기 위해서입니다
response.function_calls 모델이 요청한 function call의 목록입니다. 도구를 쓰지 않기로 했으면 비어 있습니다
fc.name, fc.args 요청한 함수의 이름과 인자입니다

이 호출의 응답에는 답변 문장이 없습니다. response.text가 아니라 response.function_calls를 봐야 합니다.


2. 실행 — 우리 코드의 차례

result = get_order_status(**fc.args)                 # ← 실행 주체는 코드

fc.args는 {'order_id': 'HR20260701023'} 같은 딕셔너리입니다. 앞의 **는 딕셔너리를 풀어 get_order_status(order_id='HR20260701023')처럼 넘기는 파이썬 문법입니다.

이 한 줄이 검사를 넣을 자리입니다. 지금은 모델이 요청한 대로 바로 실행하지만, 실행 전에 인자를 확인하거나 실행을 거부하는 코드를 이 줄 앞에 둘 수 있습니다. 조회하려는 주문이 지금 묻는 고객의 것인지 확인하는 일은 8장에서 합니다.


3. 두 번째 호출 — 세 덩어리를 차례로 보낸다

function_response_part = types.Part.from_function_response(
    name=fc.name,
    response={"result": result},
)
final = client.models.generate_content(
    model=MODEL,
    contents=[
        user_content,                          # 원래 질문
        response.candidates[0].content,        # 모델의 function call
        types.Content(role="user", parts=[function_response_part]),  # 실행 결과
    ],
    config=types.GenerateContentConfig(tools=[tool]),
)

contents에 세 덩어리가 시간 순서대로 들어갑니다.

순서 내용 누가 한 말인가 (role)
1 고객의 질문 user
2 모델이 보낸 function call model
3 함수의 실행 결과 user

왜 세 덩어리를 다 보내는가

2장에서 본 대로 모델은 앞의 호출을 기억하지 못합니다. 두 번째 호출에 실행 결과만 보내면 모델은 그것이 무엇에 대한 결과인지 알 수 없습니다. "이런 질문이 있었고, 네가 이 도구를 요청했고, 그 결과가 이것"이라는 흐름 전체를 다시 보내야 합니다.

두 번째 덩어리는 손대지 않는다

response.candidates[0].content는 첫 번째 호출에서 모델이 보낸 응답을 그대로 넣은 것입니다. 이 안에는 함수 이름과 인자 말고도 모델이 다음 호출에서 필요로 하는 정보가 함께 들어 있습니다. 새로 만들어 넣지 않고 받은 것을 그대로 돌려줍니다.

실행 결과는 user 차례로 보낸다

이 과정에서 쓰는 대화의 차례(role)는 user와 model 둘입니다. 함수의 실행 결과는 모델이 한 말이 아니므로 user 차례에 담습니다. 다만 보통의 글이 아니라 Part.from_function_response(...)로 만든 "함수 응답" 조각이어서, 모델은 이것을 고객의 말이 아니라 도구의 결과로 읽습니다.

다른 자료에서 이 자리에 role="tool"을 쓴 예를 볼 수 있습니다. 이 과정은 Gemini API의 함수 결과 전달 형식에 맞춥니다. 이 과정에서는 role="user"로 보냅니다.

결과는 딕셔너리로 싼다

response={"result": result}처럼 실행 결과를 딕셔너리에 담아 보냅니다. 조회 결과가 없을 때도 같은 방식으로, 그 사실을 딕셔너리에 담아 보냅니다(6장).


4. 모델이 도구를 쓰지 않을 수도 있다

tools를 줬다고 모델이 반드시 도구를 부르는 것은 아닙니다. 기본 동작에서는 모델이 필요하다고 판단할 때만 function call을 보냅니다. 인사말처럼 도구가 필요 없는 질문에는 바로 문장으로 답합니다.

그래서 일반적으로는 response.function_calls가 비어 있는지 먼저 확인한 뒤 꺼내 씁니다. 이번 장의 질문은 주문번호가 들어 있어 도구가 불립니다. 질문에 따라 모델의 결정이 어떻게 달라지는지는 「실습문제와 해답」 문제 1에서 확인합니다.


5. 공식 문서를 볼 때

Gemini 공식 문서의 함수 호출 예제(https://ai.google.dev/gemini-api/docs/function-calling)는 2026년 10월 기준 Interactions API라는 새 호출 방식(client.interactions.create)으로 적혀 있어, 이 과정의 코드와 모양이 다릅니다. 공식 문서는 이 과정이 쓰는 generate_content 방식도 계속 지원한다고 안내합니다(https://ai.google.dev/gemini-api/docs/interactions).

모양은 달라도 선언 → 결정 → 실행 → 반환의 네 단계와 "결정은 모델, 실행은 코드"라는 원리는 같습니다. 원리를 알고 있으면 어느 쪽 예제든 읽을 수 있습니다.


핵심 정리

  • 왕복은 generate_content 두 번입니다. 첫 번째는 결정, 두 번째는 답변을 받습니다.
  • 첫 번째 응답은 response.text가 아니라 response.function_calls 에 들어 있습니다.
  • 두 번째 호출의 contents에는 질문, 모델의 function call, 실행 결과를 차례로 넣습니다.
  • 모델은 앞의 호출을 기억하지 못하므로 흐름 전체를 다시 보냅니다.
  • 실행 결과는 Part.from_function_response(...)로 만들어 role="user" 로 보냅니다.
  • 모델은 도구를 쓰지 않기로 할 수도 있습니다.
← 이전 절도구 선언 — 모델이 읽는 안내문다음 절 →따라하기 — 첫 도구 왕복
오명운 · macro@prag-ai.com