CATEGORY

카테고리 (677)
AI (75)
Language & Specs (260)
FrameWork (36)
Library (20)
App (41)
Git (10)
Build & Dependency (2)
AWS (15)
DataBase (45)
OS (33)
Tool (17)
IT (120)
SEEMINGLY ONLINE

Seemingly
Online

이모저모 방방곡곡 두루두루 개발지식 저장소

RECENT POSTS

AI/Gemini

gemini function calling — python 예제와 자동 호출

반응형

Gemini API의 function calling은 모델이 함수를 직접 실행하지 않고 "이 함수를 이 인자로 불러 달라"는 요청만 돌려주는 기능입니다. 지금 기준(Interactions API)으로는 client.interactions.create()에 tools로 함수 선언을 넘기면 응답의 steps에 function_call이 오고, 내 코드가 실행한 결과를 function_result로 call_id와 previous_interaction_id를 붙여 다시 보내면 최종 답이 옵니다. 파이썬이라면 함수를 그대로 넘겨 SDK가 실행까지 대신하는 자동 호출도 있습니다. 아래 예제는 google-genai 파이썬 SDK 2.28.0으로 직접 돌려 확인했습니다(2026년 10월 기준).

1. gemini function calling 흐름 — 4단계

공식 문서는 function calling을 내 애플리케이션과 모델, 외부 함수 사이의 4단계로 설명합니다.

  1. 함수 선언 — 함수 이름, 인자, 용도를 모델에게 알려 준다
  2. 선언과 함께 모델 호출 — 사용자 질문과 함수 선언을 같이 보낸다
  3. 함수 실행 (내 몫) — 모델이 돌려준 이름과 인자로 내 코드가 실행한다
  4. 결과 전달 — 실행 결과를 다시 보내 사용자에게 줄 최종 답을 받는다

3번에서 문서가 따로 강조한 대목이 있습니다. 모델은 함수를 스스로 실행하지 않습니다. 날씨 API 호출이든 DB 조회든 실제 실행은 전부 내 쪽입니다. 이 왕복은 여러 번 반복될 수 있고, 한 번에 여러 함수를 부르거나(병렬) 앞 결과를 보고 다음 함수를 부를(합성) 수도 있습니다.

2. gemini function calling python 예제

도시 이름을 받아 날씨를 돌려주는 get_weather를 붙인 예제입니다. 모델 이름은 공식 문서 예제와 같은 gemini-3.8-flash를 썼고, API 키는 GEMINI_API_KEY 환경 변수에서 읽습니다(키가 없다면 gemini api 키 발급부터 하고 오세요). 패키지는 pip install google-genai입니다.

import json
from google import genai

client = genai.Client()

# 1. 함수 선언 — "type": "function" 이 꼭 있어야 한다
get_weather_decl = {
    "type": "function",
    "name": "get_weather",
    "description": "도시 이름을 받아 현재 날씨를 돌려준다.",
    "parameters": {
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "도시 이름 예: 서울"}
        },
        "required": ["city"],
    },
}

# 실제로 실행되는 내 함수 (예제라 값을 고정해 둠)
def get_weather(city: str) -> dict:
    return {"city": city, "temp_c": 18, "sky": "맑음"}

# 2. 선언과 함께 호출
interaction = client.interactions.create(
    model="gemini-3.8-flash",
    input="서울 날씨 알려줘",
    tools=[get_weather_decl],
)

# 3. 모델이 고른 함수를 내 코드가 실행
fc = next(s for s in interaction.steps if s.type == "function_call")
result = get_weather(**fc.arguments)   # arguments 는 이미 dict 다

# 4. 결과를 call_id 와 함께 돌려준다
final = client.interactions.create(
    model="gemini-3.8-flash",
    input=[{
        "type": "function_result",
        "name": fc.name,
        "call_id": fc.id,
        "result": [{"type": "text", "text": json.dumps(result, ensure_ascii=False)}],
    }],
    tools=[get_weather_decl],
    previous_interaction_id=interaction.id,
)
print(final.output_text)

SDK가 실제로 보내는 요청을 가로채 보면, 두 번째 요청 본문은 이렇게 실려 갑니다. 처음 질문과 모델의 호출을 다시 보내지 않고 previous_interaction_id 하나로 앞 대화를 잇습니다. 대화 기록은 서버가 들고 있다는 뜻입니다(서버에 맡기기 싫다면 3절의 상태 없는 방식).

POST /v1beta/interactions
{
  "model": "gemini-3.8-flash",
  "input": [{"type": "function_result", "name": "get_weather", "call_id": "call_abc",
             "result": [{"type": "text", "text": "{\"city\": \"서울\", \"temp_c\": 18, \"sky\": \"맑음\"}"}]}],
  "previous_interaction_id": "int_1",
  "tools": [ ...첫 요청과 같은 선언... ]
}
💡 fc.arguments는 이미 파이썬 dict입니다(SDK 타입 정의가 Dict[str, Any]). OpenAI는 인자를 JSON 문자열로 줘서 json.loads가 필요한데, 제미나이는 그대로 **fc.arguments로 풀면 됩니다. 반대로 결과는 SDK 타입상 문자열 또는 콘텐츠 리스트라서, dict를 돌려줄 땐 위처럼 json.dumps로 글자로 만들어 넣습니다. 한글이 있으면 ensure_ascii=False를 주세요.

3. function_result 필드와 상태 없이 쓰기

결과를 돌려주는 function_result 항목은 SDK 타입 기준으로 이렇게 생겼습니다.

필드 필수 뜻
type✔항상 "function_result"
call_id✔모델이 준 function_call의 id. 이걸로 짝을 맞춘다
result✔실행 결과. 문자열 또는 [{"type":"text","text":...}] 같은 콘텐츠 리스트
name선택호출된 함수 이름
is_error선택함수 실행이 실패했다고 알릴 때 True

대화 상태를 서버에 맡기기 싫다면 store=False로 상태 없이 쓸 수 있습니다. 이때는 previous_interaction_id 대신 매 요청의 input에 지금까지의 대화를 전부 넣어야 하고, 공식 문서는 그 순서를 이렇게 못 박습니다.

  1. 처음 보낸 user_input
  2. 첫 응답에서 받은 모델의 스텝 전부(thought, function_call 포함) — 받은 그대로
  3. 내가 만든 function_result
history = [{"type": "user_input",
            "content": [{"type": "text", "text": "서울 날씨 알려줘"}]}]

interaction = client.interactions.create(
    model="gemini-3.8-flash", store=False, input=history, tools=[get_weather_decl])

for step in interaction.steps:          # 모델 스텝을 손대지 말고 그대로 쌓는다
    history.append(step.model_dump())

fc = next(s for s in interaction.steps if s.type == "function_call")
history.append({"type": "function_result", "name": fc.name, "call_id": fc.id,
                "result": json.dumps(get_weather(**fc.arguments), ensure_ascii=False)})

final = client.interactions.create(
    model="gemini-3.8-flash", store=False, input=history, tools=[get_weather_decl])

2번에서 function_call만 골라 넣지 말고 thought 스텝까지 받은 그대로 넣으라는 점이 요구 사항입니다. 공식 문서는 Gemini 3 계열이 내부 사고(thinking)를 거쳐 함수를 고르고, 그 사고 서명(thought signature)은 SDK가 알아서 처리한다고 설명합니다. 위처럼 step.model_dump()로 통째로 쌓으면 됩니다.

4. 자동 함수 호출 — 파이썬 함수를 그대로 넘기기

파이썬 SDK에는 위 3~4단계를 대신 해 주는 자동 함수 호출(Automatic Function Calling)이 있습니다. 공식 문서도 파이썬 SDK 전용 기능이라고 적습니다. 타입 힌트와 docstring이 달린 함수를 tools에 넣으면 SDK가 선언을 만들고, 모델이 부르면 함수를 실행하고, 결과를 돌려보내 최종 답만 줍니다. 다만 이 기능은 interactions가 아니라 generate_content 계열(채팅 포함)에서 동작합니다.

from google import genai
from google.genai import types

def get_current_temperature(location: str) -> dict:
    """Gets the current temperature for a given location.

    Args:
        location: The city and state, e.g. San Francisco, CA
    """
    return {"temperature": 25, "unit": "Celsius"}

client = genai.Client()
chat = client.chats.create(
    model="gemini-3.8-flash",
    config=types.GenerateContentConfig(tools=[get_current_temperature]),  # 함수 자체를 넘긴다
)
response = chat.send_message("What's the temperature in Boston?")
print(response.text)   # 함수 실행까지 끝난 최종 답

목 서버로 돌려 보니 HTTP 요청이 2번 나가고, 그 사이에 내 함수가 1번 실행되고, 두 번째 요청 끝에 functionResponse가 자동으로 붙었습니다. 코드는 한 줄인데 왕복은 똑같이 일어나는 셈입니다.

⚠️ 공식 문서 예제처럼 client.models.generate_content(config=...tools=[함수])로 바로 부르면 동작은 하지만 SDK 2.28.0이 "Direct use of automatic function calling (AFC) in Models.generate_content is not recommended. Instead, we recommend to use AFC in Chat.send_message." 경고를 찍습니다. 위 예제처럼 chats.create() → send_message()로 쓰면 경고 없이 같은 결과가 나옵니다.

함수를 자동으로 실행시키기 싫고 호출 내용만 받고 싶다면 끌 수 있습니다. 이때는 요청이 1번만 나가고 함수는 실행되지 않으며, response.text는 None, 호출 내용은 response.function_calls에 들어 있습니다.

config = types.GenerateContentConfig(
    tools=[get_current_temperature],
    automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),
)
response = client.models.generate_content(
    model="gemini-3.8-flash", contents="What's the temperature in Boston?", config=config)
print(response.function_calls)   # [FunctionCall(name='get_current_temperature', args={'location': 'Boston'})]

자동 호출에서 SDK가 함수로 어떤 선언을 만드는지는 types.FunctionDeclaration.from_callable()로 미리 볼 수 있습니다. 위 함수로 찍어 보면 이렇습니다.

{"description": "Gets the current temperature for a given location.\n\nArgs:\n    location: The city and state, e.g. San Francisco, CA",
 "name": "get_current_temperature",
 "parameters": {"properties": {"location": {"type": "STRING"}},
                "required": ["location"], "type": "OBJECT"}}

docstring 전체가 함수 설명으로 통째로 들어가고, location 인자의 설명 칸은 비어 있습니다. 공식 문서도 "SDK는 인자 설명을 각 속성의 description으로 나눠 넣지 않는다"고 적고 있습니다. 인자마다 설명을 정확히 주고 싶다면 2절처럼 선언을 직접 쓰는 편이 낫습니다.

5. 병렬 호출과 tool_choice 모드

"서울이랑 부산 날씨"처럼 독립된 호출이 여럿이면 모델이 function_call 스텝을 여러 개 돌려줄 수 있습니다(병렬 호출). 그래서 next()로 하나만 집지 말고 전부 돌면서 결과를 call_id로 짝지어 한 번에 보냅니다. 아래 형태로 두 결과가 한 요청에 실리는 것을 확인했습니다.

calls = [s for s in interaction.steps if s.type == "function_call"]

results = [{
    "type": "function_result",
    "name": c.name,
    "call_id": c.id,
    "result": json.dumps(get_weather(**c.arguments), ensure_ascii=False),
} for c in calls]

final = client.interactions.create(
    model="gemini-3.8-flash", input=results,
    tools=[get_weather_decl], previous_interaction_id=interaction.id)

모델이 함수를 부를지 말지는 generation_config의 tool_choice로 정합니다.

값 동작
"auto" (기본)함수를 부를지 바로 답할지 모델이 정한다
"any"반드시 함수 호출을 돌려준다
"none"함수를 부르지 못한다
"validated"함수 스키마를 지키도록 보장한다
# 무조건 호출하게
generation_config = {"tool_choice": "any"}

# 여러 함수 중 get_weather 만 쓰게 좁히기
generation_config = {
    "tool_choice": {"allowed_tools": {"mode": "any", "tools": ["get_weather"]}}
}

client.interactions.create(model="gemini-3.8-flash", input="서울 날씨",
                           tools=[get_weather_decl], generation_config=generation_config)

공식 문서의 주의점이 하나 있습니다. any 모드에서는 너무 크거나 깊게 중첩된 스키마를 API가 거부할 수 있습니다. 또 한 번에 켜 두는 도구는 10~20개 이하로 두라고 권합니다.

6. gemini function calling 안 될 때

① Tool: expected object with 'type' field
함수 선언에 "type": "function"을 빼면 요청이 나가기도 전에 SDK가 pydantic ValidationError로 막습니다. 옛 generate_content 예제의 {"function_declarations": [...]} 형식을 그대로 넣어도 같은 에러입니다. Interactions API에서는 type·name·description·parameters가 한 층에 평평하게 놓여야 합니다.

⚠️ 더 위험한 건 에러가 안 나는 경우입니다. OpenAI Chat Completions 형식({"type": "function", "function": {"name": ..., "parameters": ...}})을 옮겨 오면 type이 있으니 검사를 통과하는데, SDK 2.28.0이 실제로 보낸 요청을 보면 "tools": [{"type": "function"}] — 이름과 파라미터가 통째로 빠져 있었습니다. 에러 없이 "함수를 안 부른다"로만 보이니, 다른 진영 코드를 옮겨 왔다면 선언 모양부터 확인하세요.

② interactions.create(tools=[파이썬함수])가 안 된다
같은 ValidationError가 납니다. 자동 함수 호출은 4절대로 chats/generate_content 쪽 기능입니다. Interactions API에서는 선언을 dict로 써야 합니다.

③ 결과를 보냈는데 모델이 무슨 호출인지 모른다
previous_interaction_id를 빠뜨렸거나, store=False인데 히스토리를 다 안 넣은 경우입니다. 그리고 call_id에는 function_call의 id 값을 그대로 넣어야 합니다(필드 이름이 서로 다르니 헷갈리기 쉽습니다).

④ 자동 호출에서 dict 인자가 엉뚱하게 채워진다
공식 문서는 dict[str, int] 같은 dict 타입은 잘 지원되지 않으니 쓰지 말라고 합니다. 실제로 from_callable로 찍어 보면 {"type": "OBJECT"}만 남고 값 타입 정보가 사라집니다. int·float·bool·str·리스트·Pydantic 모델로 바꾸세요.

⑤ 호출이 자꾸 429로 막힌다
함수 호출 한 번이 요청 두 번 이상이라 무료 한도를 빨리 씁니다. 원인 구분은 gemini api 429 에러 글을 보세요.

자주 묻는 질문 (FAQ)

Q. 답을 JSON으로만 받고 싶은데 function calling을 써야 하나요?
아닙니다. 모델을 내 시스템 기능에 연결할 때가 function calling이고, 답의 형태만 고정하려면 구조화 출력이 맞습니다. 방법은 gemini api json 출력 글에 정리해 두었습니다.

Q. OpenAI function calling과 뭐가 다른가요?
흐름(정의 → 호출 → 실행 → 결과 전달)은 같습니다. 코드에서 걸리는 차이는 둘입니다. 제미나이는 인자가 dict로 오고(OpenAI는 JSON 문자열이라 json.loads가 필요), 결과 항목이 function_result(OpenAI는 function_call_output)입니다. 선언 모양이 비슷해 보여도 그대로 옮기면 안 되는 이유는 6절 ①에 적었습니다. OpenAI 쪽 형식은 openai function calling 예제에 있습니다.

Q. 함수 실행이 실패하면 뭘 돌려주나요?
실패 이유를 result에 글자로 넣고, function_result의 선택 필드 is_error를 True로 주면 실패한 호출이라고 표시할 수 있습니다(SDK 타입 정의에 "Whether the tool call resulted in an error"로 적혀 있습니다).

Q. 두 번째 요청에도 tools를 넣어야 하나요?
공식 예제는 넣습니다. 모델이 결과를 보고 함수를 또 부를 수도 있기 때문입니다(앞 결과로 다음 함수를 부르는 합성 호출).

마무리

정리하면 gemini function calling은 선언 → 모델의 function_call → 내 코드 실행 → function_result 전달 → 최종 답의 왕복입니다. Interactions API에서는 선언에 "type": "function"을 꼭 넣고, 결과는 call_id로 짝을 맞춰 previous_interaction_id와 함께 보냅니다. 인자가 이미 dict라 바로 풀어 쓸 수 있는 점이 편하고, 파이썬이라면 chats의 자동 함수 호출로 왕복을 통째로 맡길 수도 있습니다. 대신 자동 호출은 인자 설명을 따로 넣지 않으니, 설명이 중요한 함수는 선언을 직접 쓰세요.


📚 참고 출처 (2026년 10월 7일 확인 · google-genai 파이썬 SDK 2.28.0)
· Function calling with the Gemini API — Google AI for Developers
· Function calling (generateContent) — Google AI for Developers

반응형

COMMENTS