이 가이드에서는 OpenAI 함수 호출(도구 호출)을 실제 애플리케이션에 연결하는 흐름을 구현합니다. 도구를 정의하고, OpenAI에 전달하고, 모델이 반환한 도구 호출을 파싱한 뒤, 구조화된 인수로 직접 함수를 실행합니다. 이후 엄격 모드와 병렬 호출을 설정하고, Apidog로 도구 인수와 하위 API 계약을 검증합니다. 공식 기준은 OpenAI의 함수 호출 문서를 참고하고, 더 큰 그림은 OpenAI Agents SDK로 에이전트 구축을 함께 확인하세요.
시작하기 전에 필요한 것
함수 호출은 모델이 코드 또는 외부 시스템과 상호작용하는 방식입니다. 앱에서 사용할 수 있는 함수를 모델에 설명하면, 모델은 사용자 요청을 해석하고 적절한 함수 이름과 인수를 JSON 형태로 반환합니다.
중요한 점은 모델이 함수를 직접 실행하지 않는다는 것입니다.
흐름은 다음과 같습니다.
- 개발자가 함수 스키마를 정의합니다.
- 모델이 사용자 요청에 맞는 함수 호출을 생성합니다.
- 개발자가
arguments를 파싱합니다. - 개발자 코드가 실제 함수를 실행합니다.
- 실행 결과를 다시 모델에 전달합니다.
- 모델이 최종 응답을 생성합니다.
예를 들어 사용자가 “파리의 날씨를 알려줘”라고 말하면, 모델은 직접 날씨 API를 호출하지 않고 다음과 같은 구조화된 호출을 반환합니다.
get_weather({
location: "Paris, France"
})
따라하려면 다음이 필요합니다.
- OpenAI API 키
- 모델이 호출할 수 있는 자체 함수
- 함수가 기대하는 입력 스키마
- 선택적으로 하위 API를 테스트하거나 모의 처리할 도구
OpenAI에서는 Chat Completions API와 Responses API 모두 함수 호출을 지원합니다. 이 글에서는 두 방식의 차이도 함께 다룹니다.
1단계: 도구 정의하기
도구는 모델이 읽을 수 있는 함수 정의입니다. 일반적으로 다음 정보를 포함합니다.
- 함수 이름
- 함수 설명
- 인수 스키마
- 필수 필드
- 허용 가능한 값
- 추가 필드 허용 여부
설명은 단순한 라벨이 아니라 모델에게 “언제 이 함수를 사용해야 하는지” 알려주는 지침입니다.
Chat Completions API용 도구 정의
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city. Use when the user asks about temperature or conditions.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and country, e.g. Bogotá, Colombia"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"],
"additionalProperties": false
}
}
}
Responses API용 도구 정의
Responses API에서는 function 래퍼 없이 더 평탄한 형태를 사용합니다.
{
"type": "function",
"name": "get_weather",
"description": "Get the current weather for a city. Use when the user asks about temperature or conditions.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and country, e.g. Bogotá, Colombia"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"],
"additionalProperties": false
}
}
이미 OpenAPI 사양을 관리하고 있다면 함수 인수 스키마를 거의 그대로 재사용할 수 있습니다. OpenAPI 사양에서 테스트 컬렉션 생성 가이드도 함께 참고하세요.
2단계: 첫 번째 요청 보내기
사용자 메시지와 도구 정의를 함께 모델에 전달합니다.
Chat Completions API 예시는 다음과 같습니다.
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4.1",
"messages": [
{
"role": "user",
"content": "What is the weather in Paris right now?"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string"
}
},
"required": ["location"],
"additionalProperties": false
}
}
}
]
}'
tools 배열에는 이번 턴에서 모델에게 노출할 함수들을 넣습니다. 모델은 사용자 메시지를 읽고 다음 중 하나를 선택합니다.
- 일반 텍스트 응답
- 하나 이상의 도구 호출
- 도구 호출 없이 추가 설명
날씨 요청처럼 함수가 적합한 경우, 모델은 일반 문장이 아니라 도구 호출을 반환합니다.
3단계: 모델이 반환한 도구 호출 읽기
모델이 함수를 호출하기로 결정하면 응답 구조 안에 호출 정보가 들어 있습니다.
Chat Completions API 응답 형태
Chat Completions에서는 assistant 메시지의 tool_calls 배열을 확인합니다.
{
"id": "call_12345xyz",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}
}
여기서 확인할 필드는 다음입니다.
-
id: 나중에 함수 결과를 되돌려줄 때 사용 -
function.name: 실행할 함수 이름 -
function.arguments: JSON 문자열 형태의 인수
Responses API 응답 형태
Responses API에서는 output 배열 안에 function_call 항목이 들어갑니다.
{
"type": "function_call",
"call_id": "call_12345xyz",
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}
중요한 점은 arguments가 객체가 아니라 JSON으로 인코딩된 문자열이라는 것입니다. 따라서 반드시 직접 파싱해야 합니다.
const toolCall = {
id: "call_12345xyz",
type: "function",
function: {
name: "get_weather",
arguments: "{\"location\":\"Paris, France\"}"
}
};
const args = JSON.parse(toolCall.function.arguments);
console.log(args.location);
// Paris, France
실제 코드에서는 파싱 직후 스키마 검증도 수행하는 것이 좋습니다.
4단계: 직접 함수 실행하기
모델은 함수 이름과 인수만 제공합니다. 실제 실행은 애플리케이션 코드가 담당합니다.
예를 들어 Node.js에서는 다음처럼 매핑할 수 있습니다.
async function getWeather({ location, unit = "celsius" }) {
// 실제로는 외부 날씨 API 또는 내부 서비스를 호출
return {
location,
unit,
temperature: 18,
condition: "cloudy"
};
}
const availableFunctions = {
get_weather: getWeather
};
async function executeToolCall(toolCall) {
const functionName = toolCall.function.name;
const args = JSON.parse(toolCall.function.arguments);
const fn = availableFunctions[functionName];
if (!fn) {
throw new Error(`Unknown function: ${functionName}`);
}
return await fn(args);
}
이 단계에서 해야 할 검증은 다음과 같습니다.
- 함수 이름이 허용된 목록에 있는지 확인
-
arguments가 올바른 JSON인지 확인 - 필수 필드가 있는지 확인
- enum 값이 허용 범위 안에 있는지 확인
- 비즈니스 규칙에 맞는지 확인
예를 들어 location이 문자열이어도 서비스하지 않는 지역일 수 있습니다. 스키마 검증과 비즈니스 검증은 별도로 처리해야 합니다.
5단계: 실행 결과를 모델에 반환하기
함수 실행이 끝나면 결과를 모델에 다시 보내야 합니다. 그래야 모델이 사용자에게 최종 답변을 생성할 수 있습니다.
Chat Completions API 방식
Chat Completions에서는 tool 역할 메시지를 추가하고, 원래 도구 호출의 id를 tool_call_id로 연결합니다.
{
"role": "tool",
"tool_call_id": "call_12345xyz",
"content": "{\"location\":\"Paris, France\",\"temperature\":18,\"unit\":\"celsius\",\"condition\":\"cloudy\"}"
}
전체 흐름은 다음과 같습니다.
- 사용자 메시지 전송
- 모델이
tool_calls반환 - 앱이 함수 실행
-
tool메시지로 결과 전달 - 모델이 최종 답변 생성
Responses API 방식
Responses API에서는 function_call_output 항목을 사용하고 call_id로 연결합니다.
{
"type": "function_call_output",
"call_id": "call_12345xyz",
"output": "{\"location\":\"Paris, France\",\"temperature\":18,\"unit\":\"celsius\",\"condition\":\"cloudy\"}"
}
핵심은 동일합니다.
모델이 요청하고, 애플리케이션이 실행하고, 결과를 다시 모델에 전달합니다.
6단계: 병렬 호출과 엄격 모드 설정하기
기본 루프가 동작하면 신뢰성과 실행 방식을 조정합니다.
병렬 도구 호출
기본적으로 모델은 한 번의 턴에서 여러 도구 호출을 반환할 수 있습니다.
예를 들어 사용자가 다음처럼 요청할 수 있습니다.
서울, 파리, 뉴욕의 현재 날씨를 알려줘.
모델은 세 개의 get_weather 호출을 동시에 반환할 수 있습니다.
[
{
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"Seoul, South Korea\"}"
}
},
{
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}
},
{
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"New York, USA\"}"
}
}
]
각 호출이 독립적이라면 병렬 실행이 유리합니다.
const results = await Promise.all(
toolCalls.map((toolCall) => executeToolCall(toolCall))
);
반대로 호출 순서가 중요하거나 이전 결과에 의존한다면 parallel_tool_calls를 false로 설정합니다.
{
"parallel_tool_calls": false
}
엄격 모드
엄격 모드는 모델이 반환하는 인수가 JSON 스키마와 일치하도록 강제합니다.
OpenAI는 엄격 모드 사용을 권장합니다.
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"strict": true,
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string"
},
"unit": {
"type": ["string", "null"],
"enum": ["celsius", "fahrenheit", null]
}
},
"required": ["location", "unit"],
"additionalProperties": false
}
}
}
엄격 모드에서는 다음 규칙을 지켜야 합니다.
- 모든 객체에
additionalProperties: false를 설정합니다. -
properties의 모든 필드는required에 포함합니다. - 선택적 필드는 생략하지 않고
null을 허용합니다.
예를 들어 unit이 선택 사항이라면 required에서 제거하는 대신 다음처럼 정의합니다.
{
"unit": {
"type": ["string", "null"],
"enum": ["celsius", "fahrenheit", null]
}
}
이렇게 하면 모델은 다음 중 하나를 반환합니다.
{
"location": "Paris, France",
"unit": "celsius"
}
또는:
{
"location": "Paris, France",
"unit": null
}
키가 항상 존재하므로 파싱 코드가 단순해집니다.
주요 설정 요약
| 설정 | 제어하는 내용 | 기본값 | 변경 시점 |
|---|---|---|---|
parallel_tool_calls |
한 턴에서 여러 도구 호출을 반환할 수 있는지 여부 | true |
호출 순서가 중요하거나 서로 의존할 때 false
|
strict |
인수가 스키마와 일치해야 하는지 여부 | 미설정 시 최선 노력 | 프로덕션 또는 안정적인 파싱이 필요할 때 활성화 |
tool_choice |
모델이 도구를 사용할 수 있는지, 특정 도구를 강제할지 여부 | auto |
호출 강제는 required, 비활성화는 none, 특정 함수 지정 가능 |
엄격 모드는 잘못된 형식의 JSON을 줄이는 데 도움이 됩니다. 하지만 값이 비즈니스적으로 유효한지는 보장하지 않습니다.
예를 들어 다음 값은 스키마상 문자열이므로 유효할 수 있습니다.
{
"location": "Unknown City",
"unit": "celsius"
}
하지만 실제 서비스에서는 지원하지 않는 지역일 수 있습니다. 따라서 별도의 비즈니스 검증과 오류 처리가 필요합니다.
Apidog에서 테스트하는 방법
모델이 도구 호출을 반환하더라도, 실제 함수에 연결하기 전에 두 가지를 확인해야 합니다.
- 모델이 만든
arguments가 함수 스키마와 일치하는가? - 함수가 호출할 하위 API가 예상대로 동작하는가?
Apidog는 이 두 영역을 검증하는 데 사용할 수 있습니다.
Apidog가 직접 애플리케이션 함수를 실행하는 것은 아닙니다. 대신 함수 주변의 API 계약을 검증하고 모의 처리합니다.
1. 도구 호출 인수 검증하기
OpenAI 응답에서 arguments 문자열을 추출합니다.
"{\"location\":\"Paris, France\",\"unit\":\"celsius\"}"
이를 JSON으로 파싱하면 다음과 같습니다.
{
"location": "Paris, France",
"unit": "celsius"
}
Apidog에서 이 JSON을 요청 본문처럼 다루고 다음을 검증할 수 있습니다.
-
location이 존재하는지 -
location이 문자열인지 -
unit이 허용된 enum 값인지 - 필수 필드가 모두 있는지
- 추가 필드가 없는지
필드 추출에는 JSONPath 표현식을 사용할 수 있습니다. 더 엄격한 구조 검사는 JSON 스키마 유효성 검사를 적용하면 됩니다.
권장 방식은 OpenAI 도구 정의에 사용한 스키마와 동일한 스키마를 Apidog 검증에도 반영하는 것입니다. 그러면 모델 출력과 함수 입력 계약을 같은 기준으로 확인할 수 있습니다.
2. 함수가 호출할 하위 API 모의 처리하기
get_weather 함수는 보통 외부 날씨 API 또는 내부 서비스를 호출합니다. 하지만 개발 중에는 다음 문제가 있을 수 있습니다.
- 실제 API가 아직 준비되지 않음
- 호출 비용이 발생함
- 속도 제한이 있음
- 오류 응답을 재현하기 어려움
- 테스트 데이터가 불안정함
이 경우 Apidog에서 모의 API를 만들고 함수가 해당 모의 API를 호출하도록 설정합니다.
예를 들어 모의 응답은 다음처럼 구성할 수 있습니다.
{
"location": "Paris, France",
"temperature": 18,
"unit": "celsius",
"condition": "cloudy"
}
오류 케이스도 직접 만들 수 있습니다.
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests"
}
}
이렇게 하면 실제 API를 소모하지 않고도 다음을 테스트할 수 있습니다.
- 정상 응답 처리
- 타임아웃 처리
- 429 응답 처리
- 잘못된 응답 형식 처리
- 빈 데이터 처리
- 하위 API 장애 시 사용자 응답 처리
권장 워크플로우는 다음과 같습니다.
- OpenAI에서 도구 호출을 캡처합니다.
-
arguments를 파싱합니다. - Apidog에서 JSON 스키마로 인수를 검증합니다.
- 함수가 호출할 하위 API를 Apidog 모의 API로 대체합니다.
- 정상 응답과 오류 응답을 모두 테스트합니다.
- 검증이 끝나면 실제 API로 전환합니다.
자주 묻는 질문
함수 호출은 Chat Completions와 Responses API 모두에서 작동하나요?
예. 두 엔드포인트 모두 함수 호출을 지원합니다.
차이는 응답과 도구 정의의 형태입니다.
Chat Completions는 함수를 function 키 아래에 중첩하고, 응답에서 tool_calls를 반환합니다.
Responses API는 더 평탄한 도구 정의를 사용하고, output 배열 안에 function_call 항목을 반환합니다.
모델이 인수를 객체가 아니라 문자열로 반환하는 이유는 무엇인가요?
arguments 필드는 JSON으로 인코딩된 문자열입니다. 따라서 사용 전 반드시 파싱해야 합니다.
const args = JSON.parse(toolCall.function.arguments);
파싱 후에는 스키마 검증을 수행하는 것이 안전합니다. JSON 스키마 유효성 검사를 적용하면 잘못된 페이로드가 실제 함수에 도달하기 전에 차단할 수 있습니다.
엄격 모드는 함수 성공을 보장하나요?
아니요. 엄격 모드는 인수 구조가 JSON 스키마와 일치하도록 돕습니다.
하지만 다음은 보장하지 않습니다.
- 외부 API 성공
- 비즈니스 규칙 충족
- 사용자가 요청한 값의 실제 존재 여부
- 함수 내부 로직 성공
- 네트워크 장애 방지
따라서 엄격 모드와 별도로 값 검증, 예외 처리, 재시도, 타임아웃 처리를 구현해야 합니다.
Apidog가 실제 함수를 실행할 수 있나요?
아니요. Apidog는 애플리케이션 함수를 대신 실행하지 않습니다.
Apidog의 역할은 다음에 가깝습니다.
- 모델이 생성한 인수 구조 검증
- JSON 스키마 기반 계약 확인
- 함수가 의존하는 API 모의 처리
- 정상 및 오류 응답 테스트
- API 문서와 테스트 컬렉션 관리
실제 함수 실행은 여전히 애플리케이션 코드가 담당합니다.
마무리
OpenAI 함수 호출 구현의 핵심 루프는 단순합니다.
- 도구를 명확하게 정의합니다.
- 사용자 메시지와 함께 도구를 모델에 전달합니다.
- 모델이 반환한
tool_calls또는function_call을 읽습니다. -
arguments를 JSON으로 파싱합니다. - 허용된 함수만 실행합니다.
- 실행 결과를 모델에 다시 전달합니다.
- 모델이 최종 응답을 생성합니다.
프로덕션에 가까워질수록 다음 설정을 추가하세요.
-
strict: true로 인수 구조 안정화 -
additionalProperties: false로 예상 외 필드 차단 -
parallel_tool_calls로 병렬 실행 제어 - 스키마 검증으로 잘못된 입력 차단
- Apidog 모의 API로 하위 API 의존성 테스트
테스트 측면을 강화하고 싶다면 Apidog를 다운로드하여 도구 호출 인수를 스키마에 대해 확인하고, 함수가 의존하는 API를 한 곳에서 모의 처리해 보세요.

Top comments (0)