함수 호출 심화(Function Calling Deep Dive) — OpenAI, Anthropic, Gemini

세 최전선 제공자(Provider)는 2024년에 같은 도구 호출 루프(Tool-Call Loop)로 수렴했지만, 그 외의 거의 모든 표면은 서로 갈라졌습니다. OpenAI는 toolstool_calls를 사용합니다. Anthropic은 tool_usetool_result 블록(Block)을 사용합니다. Gemini는 functionDeclarations와 고유 식별자 상관관계(Unique-id Correlation)를 사용합니다. 이 강의는 세 제공자를 나란히 비교해서, 한 제공자에서 잘 돌던 코드가 다른 제공자로 이식될 때 배관(Plumbing) 차이 때문에 깨지지 않도록 도와줍니다.

유형: Build 언어: Python (표준 라이브러리, 스키마 변환기) 선수 지식: Phase 13 · 01 (도구 인터페이스) 예상 시간: 약 75분

학습 목표

  • OpenAI, Anthropic, Gemini의 함수 호출(Function-Calling) 페이로드(Payload)가 선언(Declaration), 호출(Call), 결과(Result)에서 어떻게 다른지 세 가지 형태 차이를 말로 설명합니다.
  • 하나의 도구 선언을 세 제공자 형식으로 모두 변환하고, 엄격 모드(Strict-Mode) 제약이 어디에서 달라지는지 예측합니다.
  • 각 제공자의 tool_choice를 사용해서 도구 호출을 강제하거나 금지하거나, 모델이 자동으로 고르게 합니다.
  • 제공자별 고정 한도(Hard Limit), 즉 도구 개수, 스키마 깊이(Schema Depth), 인자 길이(Argument Length)와, 한도를 어겼을 때 나오는 오류 시그니처(Error Signature)를 압니다.

문제

함수 호출 요청의 형태는 제공자마다 다릅니다. 2026년 프로덕션 스택의 구체적인 예시는 다음과 같습니다.

OpenAI Chat Completions / Responses API. tools: [{type: "function", function: {name, description, parameters, strict}}]를 전달합니다. 모델 응답에는 choices[0].message.tool_calls: [{id, type: "function", function: {name, arguments}}]가 들어 있고, 여기서 arguments는 직접 파싱(Parsing)해야 하는 JSON 문자열입니다. 엄격 모드(strict: true)는 제약 디코딩(Constrained Decoding)을 통해 스키마 준수를 강제합니다.

Anthropic Messages API. tools: [{name, description, input_schema}]를 전달합니다. 응답은 content: [{type: "text"}, {type: "tool_use", id, name, input}] 형태로 돌아옵니다. input은 이미 파싱된 객체이며 문자열이 아닙니다. 결과를 보낼 때는 {type: "tool_result", tool_use_id, content} 블록이 들어 있는 새 user 메시지로 응답합니다.

Google Gemini API. tools: [{functionDeclarations: [{name, description, parameters}]}]를 전달합니다. 선언이 functionDeclarations 아래에 중첩되어 있습니다. 응답은 candidates[0].content.parts: [{functionCall: {name, args, id}}]로 도착합니다. Gemini 3 이상에서 id는 병렬 호출 상관관계(Parallel-Call Correlation)를 위해 고유하게 부여됩니다. 결과는 {functionResponse: {name, id, response}} 형태로 회신합니다.

루프 구조는 같습니다. 하지만 필드 이름, 중첩 위치, 문자열 대 객체 관례, 상관관계 기제(Correlation Mechanism)가 모두 다릅니다. OpenAI 위에서 날씨 에이전트(Weather Agent)를 만든 팀은, 같은 코드를 Anthropic으로 옮기는 데 배관 수정만 이틀, Gemini로 옮기는 데 또 하루를 씁니다.

이 강의는 세 형식을 하나의 정규 도구 선언(Canonical Tool Declaration)으로 통합하고, 가장자리(Edge)에서 분기 라우팅하는 변환기(Translator)를 만듭니다. Phase 13 · 17은 같은 패턴을 LLM 게이트웨이(Gateway)로 일반화합니다.

사전 테스트

2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요

1.함수 호출 심화가 해결하는 핵심 과제는?

2.함수 호출 심화 이전의 주요 한계는?

0/2 답변 완료

개념

공통 구조

모든 제공자에는 다섯 가지가 필요합니다.

  1. 도구 목록(Tool List). 도구별 이름, 설명, 입력 스키마.
  2. 도구 선택(Tool Choice). 특정 도구를 강제하거나, 도구 사용을 금지하거나, 모델이 결정하게 합니다.
  3. 호출 방출(Call Emission). 도구 이름과 인자를 담은 구조화된 출력입니다.
  4. 호출 식별자(Call Id). 결과를 올바른 호출과 짝지어 줍니다. 병렬 호출에서 특히 중요합니다.
  5. 결과 주입(Result Injection). 결과를 원래 호출과 다시 이어 주는 메시지 또는 블록입니다.

필드별 형태 차이

관점OpenAIAnthropicGemini
선언 봉투(Envelope){type: "function", function: {...}}{name, description, input_schema}{functionDeclarations: [{...}]}
스키마 필드parametersinput_schemaparameters
응답 컨테이너어시스턴트 메시지의 tool_calls[]tool_use 타입의 content[]functionCall 타입의 parts[]
인자(Arguments) 타입문자열화된 JSON파싱된 객체파싱된 객체
식별자 형식call_... (OpenAI 생성)toolu_... (Anthropic)UUID (Gemini 3+)
결과 블록역할 tool, tool_call_idtool_result, tool_use_id를 가진 user일치하는 id를 가진 functionResponse
특정 도구 강제tool_choice: {type: "function", function: {name}}tool_choice: {type: "tool", name}tool_config: {function_calling_config: {mode: "ANY"}}
도구 금지tool_choice: "none"tool_choice: {type: "none"}mode: "NONE"
엄격 스키마(Strict Schema)strict: true스키마 자체가 계약(항상 적용)요청 수준의 responseSchema

실제로 부딪히는 한도

  • OpenAI. 요청당 도구 128개. 스키마 깊이 5. 인자 문자열은 8192 바이트 이하. 엄격 모드는 $ref를 허용하지 않고, 범위가 겹치는 oneOf/anyOf/allOf를 허용하지 않으며, 모든 속성(Property)을 required에 나열해야 합니다.
  • Anthropic. 요청당 도구 64개. 스키마 깊이는 사실상 제한이 없지만 실용적 한계는 10 정도입니다. 엄격 모드 플래그(Flag)는 따로 없습니다. 스키마는 계약이며 모델은 대체로 준수합니다.
  • Gemini. 요청당 함수 64개. 스키마 타입은 OpenAPI 3.0 부분집합(Subset)입니다. JSON Schema 2020-12와 약간 차이가 있습니다. Gemini 3부터는 병렬 호출에 고유 식별자(Unique Id)가 붙습니다.

tool_choice 동작

모든 제공자가 이름만 다르게 세 가지 모드를 지원합니다.

  • 자동(Auto). 모델이 도구 사용과 텍스트 응답 중 선택합니다. 기본값입니다.
  • 필수 / 임의(Required / Any). 모델이 최소한 하나의 도구를 호출해야 합니다.
  • 금지(None). 모델이 도구를 호출하면 안 됩니다.

각 제공자에 고유한 모드도 있습니다.

  • OpenAI. 특정 도구 이름을 강제할 수 있습니다.
  • Anthropic. 특정 도구 이름을 강제할 수 있고, disable_parallel_tool_use 플래그가 단일 호출과 다중 호출을 분리합니다.
  • Gemini. mode: "VALIDATED"는 모델 의도와 관계없이 모든 응답을 스키마 검증기(Schema Validator)로 통과시킵니다.

병렬 호출

OpenAI의 parallel_tool_calls: true는 기본값이며, 하나의 어시스턴트 메시지 안에서 여러 호출을 한 번에 방출합니다. 호스트는 모두 실행한 뒤, tool_call_id 별 항목이 들어 있는 일괄 도구 역할 메시지(Batched Tool-Role Message)로 답합니다. Anthropic은 과거에는 단일 호출 중심이었지만, Claude 3.5 기준 기본값인 disable_parallel_tool_use: false가 다중 호출을 가능하게 합니다. Gemini 2는 병렬 호출은 허용했지만 안정된 식별자(Stable Id)가 없었습니다. Gemini 3는 UUID를 추가해서 순서가 뒤섞인 응답도 깔끔히 연결할 수 있게 했습니다.

스트리밍(Streaming)

세 제공자 모두 스트리밍 도구 호출(Streamed Tool Call)을 지원합니다. 전송 형식(Wire Format)은 다릅니다.

  • OpenAI. tool_calls[i].function.arguments의 델타 청크(Delta Chunk)가 조금씩 도착합니다. finish_reason: "tool_calls"가 올 때까지 누적합니다.
  • Anthropic. block-start / block-delta / block-stop 이벤트(Event)가 옵니다. input_json_delta 청크가 부분 인자를 담습니다.
  • Gemini. Gemini 3에서 새로 추가된 streamFunctionCallArgumentsfunctionCallId와 함께 청크를 방출해서, 여러 병렬 호출이 섞여 도착해도 처리할 수 있습니다.

Phase 13 · 03은 병렬 호출과 스트리밍 재조립(Reassembly)을 깊게 다룹니다. 이 강의는 선언과 단일 호출 형태에 집중합니다.

오류와 복구

잘못된 인자(Argument) 오류도 제공자마다 다른 모습으로 드러납니다.

  • OpenAI(비엄격, Non-strict). 모델이 arguments: "{bad json}"을 반환하고 JSON 파싱이 실패합니다. 오류 메시지를 넣어 다시 호출합니다.
  • OpenAI(엄격, Strict). 디코딩 중에 검증(Validation)이 일어나므로 잘못된 JSON은 나올 수 없지만, 거절(Refusal) 블록은 나올 수 있습니다.
  • Anthropic. input에 예상하지 못한 필드가 섞여 들어올 수 있습니다. 스키마는 권고(Advisory) 성격이 강하므로 서버 측 검증이 필요합니다.
  • Gemini. OpenAPI 3.0의 묘한 차이(Quirk)가 있습니다. 객체 필드의 enum이 조용히 무시될 수 있으므로 직접 검증해야 합니다.

변환기 패턴

코드 안에서 사용하는 정규 도구 선언은 다음과 같은 형태입니다. 구체적인 형태는 여러분이 정합니다.

Tool(
    name="get_weather",
    description="Use when ...",
    input_schema={"type": "object", "properties": {...}, "required": [...]},
    strict=True,
)

작은 함수 세 개가 이를 세 제공자 형태로 변환합니다. code/main.py의 하니스(Harness)가 정확히 이 일을 하고, 가짜 도구 호출을 각 제공자의 응답 형태로 왕복(Round-trip)시켜 보여 줍니다. 네트워크는 필요 없습니다. 이 강의는 HTTP 통신이 아니라 형태(Shape)를 가르치기 때문입니다.

프로덕션 팀은 이런 변환기를 AbstractToolset(Pydantic AI), UniversalToolNode(LangGraph), BaseTool(LlamaIndex) 같은 추상화로 감쌉니다. Phase 13 · 17은 세 제공자 앞단에 OpenAI 형태 API를 노출하는 게이트웨이를 출시합니다.

사용해보기

code/main.py는 하나의 정규 Tool 데이터 클래스와, OpenAI / Anthropic / Gemini 선언 JSON을 만드는 변환기 세 개를 정의합니다. 그다음에 손으로 만든 제공자별 응답을 같은 정규 호출 객체(Canonical Call Object)로 파싱해서, 표면 아래의 의미(Semantics)가 같다는 것을 보여 줍니다. 실행한 뒤 세 선언을 나란히 비교해 봅니다.

볼 지점은 다음과 같습니다.

  • 세 선언 블록은 봉투(Envelope)와 필드 이름만 다릅니다.
  • 세 응답 블록은 호출이 어디에 자리하는지가 다릅니다. 최상위 tool_calls, content[] 블록, parts[] 항목 순으로 위치가 옮겨 갑니다.
  • 하나의 canonical_call() 함수가 세 응답 형태 모두에서 {id, name, args}를 추출합니다.

산출물 만들기

이 강의는 outputs/skill-provider-portability-audit.md를 만듭니다. 한 제공자에 맞춰 만든 함수 호출 통합(Integration)이 주어지면, 이 스킬(Skill)은 이식성 감사(Portability Audit)를 만듭니다. 어떤 제공자별 한도에 의존하는지, 어떤 필드 이름을 바꿔야 하는지, 다른 제공자로 옮길 때 무엇이 깨지는지를 점검합니다.

연습문제

  1. (쉬움) code/main.py를 실행하고, 세 제공자 선언 JSON이 모두 같은 기반 Tool 객체를 직렬화한 결과인지 확인합니다. 정규 도구에 enum 매개변수를 추가하고, Gemini 변환기만 OpenAPI 묘한 차이를 별도로 처리해야 하는지 확인합니다.

  2. (중간) 각 제공자에 대해 ListToolsResponse 파서를 추가해서, 모델이 list_tools나 디스커버리 호출(Discovery Call) 뒤에 반환하는 도구 목록을 추출합니다. OpenAI에는 이 기능이 기본 제공되지 않는다는 비대칭을 기록합니다.

  3. (중간) tool_choice 변환을 구현합니다. 정규 ToolChoice(mode="force", tool_name="x")를 세 제공자 형태로 사상(Mapping)합니다. 그 뒤 mode="any"mode="none"도 사상합니다. 강의의 차이 비교 표와 대조해 봅니다.

  4. (중간) 세 제공자 중 하나를 골라 함수 호출 가이드를 끝까지 읽습니다. 해당 스키마 사양에서 다른 두 제공자가 지원하지 않는 필드 하나를 찾습니다. 후보는 OpenAI strict, Anthropic disable_parallel_tool_use, Gemini function_calling_config.allowed_function_names입니다.

  5. (어려움) 테스트 벡터를 작성합니다. 선언된 스키마를 위반하는 인자를 가진 도구 호출입니다. 각 제공자의 검증기 또는 Lesson 01의 표준 라이브러리 검증기를 대리(Proxy)로 사용해서 어떤 오류가 나는지 기록합니다. 프로덕션에서 엄격성(Strictness) 관점으로 어떤 제공자를 쓸지 문서화합니다.

핵심 용어

용어흔한 설명실제 의미
함수 호출(Function Calling)"도구 사용"구조화된 도구 호출을 방출하기 위한 제공자 수준 API
도구 선언(Tool Declaration)"도구 스펙"이름 + 설명 + JSON Schema 입력 페이로드
tool_choice"강제 / 금지"자동 / 필수 / 금지 / 특정 이름 모드
엄격 모드(Strict Mode)"스키마 강제(Schema Enforcement)"스키마와 일치하도록 디코딩을 제약하는 OpenAI 플래그
tool_use 블록"Anthropic의 호출 형태"id, name, input을 담는 인라인 콘텐츠 블록
functionCall 파트"Gemini의 호출 형태"name, args, id를 담은 parts[] 항목
문자열형 인자(Arguments-as-string)"문자열화된 JSON"OpenAI는 인자를 객체가 아니라 JSON 문자열로 반환한다
병렬 도구 호출(Parallel Tool Calls)"한 턴(Turn) 안의 팬아웃(Fan-out)"하나의 어시스턴트 메시지 안에 여러 도구 호출
거절(Refusal)"모델의 거절"엄격 모드에서 호출 대신 등장하는 거절 블록
OpenAPI 3.0 부분집합"Gemini 스키마의 묘한 차이"Gemini가 사용하는, JSON Schema와 유사하지만 미세하게 다른 방언(Dialect)

더 읽을거리

실습 코드

이 강의의 실습 코드 1개

main
Code

산출물

이 강의에서 생성된 프롬프트, 스킬, 코드 산출물 1개

provider-portability-audit

Audit a function-calling integration against one provider for what breaks when ported to the other two.

Skill

확인 문제

3문제 · 모두 맞추면 완료 표시가 가능합니다

1.프로덕션에서 함수 호출 심화의 가장 중요한 설계 원칙은?

2.함수 호출 심화가 올바른 선택이 아닌 경우는?

3.함수 호출 심화는 AI 생태계에 어떻게 들어맞나요?

0/3 답변 완료

추가 문제 풀기

AI가 강의 내용을 바탕으로 새로운 문제를 생성합니다