콘텐츠로 이동

Function Calling 비교

에이전트 시스템을 설계할 때 특정 LLM 제공자에 종속(lock-in)되면, 더 나은 모델로 전환하거나 멀티 제공자 전략을 취할 때 비용이 큽니다. 세 주요 제공자의 Function Calling 방식을 이해하면 적절한 추상화 레이어를 설계할 수 있습니다.

세 제공자 모두 JSON Schema를 기반으로 하지만, 래핑 구조가 다릅니다.

const tools: OpenAI.Tool[] = [
{
type: 'function',
function: {
name: 'get_weather',
description: '특정 도시의 현재 날씨를 조회합니다',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: '도시명' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] },
},
required: ['city'],
},
},
},
];
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: '서울 날씨 알려줘' }],
tools,
tool_choice: 'auto', // 'none' | 'auto' | { type: 'function', function: { name } }
});
const tools: Anthropic.Tool[] = [
{
name: 'get_weather',
description: '특정 도시의 현재 날씨를 조회합니다',
input_schema: { // OpenAI의 parameters → input_schema
type: 'object',
properties: {
city: { type: 'string', description: '도시명' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] },
},
required: ['city'],
},
},
];
const response = await anthropic.messages.create({
model: 'claude-opus-4-5',
max_tokens: 1024,
messages: [{ role: 'user', content: '서울 날씨 알려줘' }],
tools,
tool_choice: { type: 'auto' }, // { type: 'auto' | 'any' | 'tool', name?: string }
});
const tools: GoogleGenerativeAI.Tool[] = [
{
functionDeclarations: [ // OpenAI의 tools 배열 내 items → functionDeclarations
{
name: 'get_weather',
description: '특정 도시의 현재 날씨를 조회합니다',
parameters: {
type: 'OBJECT', // 대문자 타입 문자열
properties: {
city: { type: 'STRING', description: '도시명' },
unit: { type: 'STRING', enum: ['celsius', 'fahrenheit'] },
},
required: ['city'],
},
},
],
},
];
const model = genai.getGenerativeModel({
model: 'gemini-2.0-flash',
tools,
toolConfig: { functionCallingConfig: { mode: 'AUTO' } },
});

각 제공자가 도구 호출 요청을 반환하는 방식도 다릅니다.

const message = response.choices[0].message;
if (message.tool_calls) {
for (const toolCall of message.tool_calls) {
const name = toolCall.function.name;
const args = JSON.parse(toolCall.function.arguments); // 문자열 파싱 필요
const result = await executeToolCall(name, args);
// 도구 결과를 messages에 추가
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(result),
});
}
}
const content = response.content;
for (const block of content) {
if (block.type === 'tool_use') { // OpenAI의 tool_calls → tool_use 블록
const name = block.name;
const args = block.input; // 이미 객체, 파싱 불필요
const result = await executeToolCall(name, args);
// 도구 결과는 user role의 tool_result 블록으로
messages.push({
role: 'user',
content: [
{
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify(result),
},
],
});
}
}
const candidates = response.candidates;
const parts = candidates[0].content.parts;
for (const part of parts) {
if (part.functionCall) {
const name = part.functionCall.name;
const args = part.functionCall.args; // 이미 객체
const result = await executeToolCall(name, args);
// functionResponse로 결과 전달
history.push({
role: 'function',
parts: [{ functionResponse: { name, response: result } }],
});
}
}
기능OpenAIAnthropicGoogle
도구 정의 키tools[].function.parameterstools[].input_schemafunctionDeclarations[].parameters
타입 표기소문자 (string)소문자 (string)대문자 (STRING)
응답 위치choices[0].message.tool_callscontent[].tool_use 블록candidates[0].content.parts[].functionCall
인수 형식JSON 문자열 (파싱 필요)객체 (파싱 불필요)객체 (파싱 불필요)
병렬 도구 호출지원 (배열로 반환)지원 (여러 블록)지원 (여러 parts)
강제 도구 사용tool_choice: {type: 'function', function: {name}}tool_choice: {type: 'tool', name}functionCallingConfig: {mode: 'ANY', allowedFunctionNames}
스트리밍 중 도구지원지원지원

제공자 차이를 흡수하는 통합 인터페이스를 만들면 모델 전환이 쉬워집니다.

interface UnifiedTool {
name: string;
description: string;
parameters: JsonSchema; // 소문자 표준 JSON Schema
}
interface UnifiedToolCall {
id: string;
name: string;
arguments: Record<string, unknown>; // 항상 파싱된 객체
}
// 제공자별 변환기
function toAnthropicTools(tools: UnifiedTool[]): Anthropic.Tool[] {
return tools.map((t) => ({
name: t.name,
description: t.description,
input_schema: t.parameters,
}));
}
function toOpenAITools(tools: UnifiedTool[]): OpenAI.Tool[] {
return tools.map((t) => ({
type: 'function',
function: {
name: t.name,
description: t.description,
parameters: t.parameters,
},
}));
}
// 응답에서 도구 호출 추출 (제공자 무관)
function extractToolCalls(response: ProviderResponse): UnifiedToolCall[] {
if (isOpenAIResponse(response)) {
return (response.choices[0].message.tool_calls ?? []).map((tc) => ({
id: tc.id,
name: tc.function.name,
arguments: JSON.parse(tc.function.arguments),
}));
}
if (isAnthropicResponse(response)) {
return response.content
.filter((b) => b.type === 'tool_use')
.map((b) => ({ id: b.id, name: b.name, arguments: b.input }));
}
// Google...
}

세 제공자의 Function Calling은 JSON Schema 기반이라는 공통점을 가지지만, 래핑 구조와 응답 형식이 다릅니다. OpenAI는 인수를 문자열로 반환하므로 파싱이 필요하고, Anthropic은 tool_use 블록, Google은 functionCall 파트를 사용합니다. 통합 인터페이스를 설계하면 모델 전환 비용을 크게 줄일 수 있습니다.