For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
기본 탐색 메뉴
Theme

함수 호출

모델이 지시를 따르고 프롬프트에 응답하는 데 필요한 새로운 기능과 데이터를 제공하세요.

함수 호출(도구 호출이라고도 함)을 사용하면 OpenAI 모델이 외부 시스템과 유연하게 연동하여 학습 데이터에 없는 정보까지 활용할 수 있습니다. 이 가이드에서는 모델이 애플리케이션의 데이터를 조회하고 작업을 수행하도록 연결하는 방법을 설명합니다. JSON 스키마로 정의하는 함수 도구와 자유 형식 텍스트를 입력과 출력으로 사용하는 사용자 정의 도구를 살펴봅니다.

Agents API 세션에서는 함수를 사용해 함수를 등록하고 세션의 작업 요청을 처리하세요. 이 가이드의 예제는 Responses API와 Chat Completions 연동을 다룹니다.

애플리케이션에 함수가 많거나 스키마가 크다면 함수 호출과 도구 검색을 함께 사용할 수 있습니다. 자주 쓰지 않는 도구는 로드를 미뤘다가 모델에 필요할 때만 로드할 수 있습니다. gpt-5.4 이상의 모델만 tool_search를 지원합니다.

GPT-6 Astra와 GPT-6.1 Sol에서 도구를 호출하려면 Responses API를 사용해야 합니다. Chat Completions 예제에서는 GPT-5.6 Terra의 추론을 끄고 사용합니다. 기존 연동을 업데이트하려면 마이그레이션 가이드를 참고하세요.

작동 방식

먼저 도구 호출에 쓰이는 주요 용어를 살펴봅니다. 이어서 실제 예제를 통해 도구를 호출하는 방법을 알아봅니다.

도구 호출 플로우

도구 호출은 애플리케이션과 모델이 OpenAI API를 통해 여러 단계에 걸쳐 요청과 응답을 주고받는 과정입니다. 도구 호출 플로우는 크게 다섯 단계로 나뉩니다.

  1. 호출할 수 있는 도구를 포함해 모델에 요청을 보냅니다.
  2. 모델에서 도구 호출을 받습니다.
  3. 애플리케이션에서 도구 호출의 입력으로 코드를 실행합니다.
  4. 도구 출력을 포함해 모델에 두 번째 요청을 보냅니다.
  5. 모델에서 최종 응답 또는 추가 도구 호출을 받습니다.

함수 호출 단계를 보여 주는 다이어그램

Responses를 사용하면 작업에 필요한 도구 호출 횟수만큼 이 플로우를 반복할 수 있습니다. 반복되는 호출을 조율하는 프레임워크가 필요하다면 Responses API와 Agents SDK 비교를 참고하세요.

함수 도구 예제

별자리별 오늘의 운세를 조회하는 get_horoscope 함수로 도구 호출 플로우를 처음부터 끝까지 살펴보겠습니다.

도구 호출 전체 예제
from openai import OpenAI
import json

client = OpenAI()

# 1. Define a list of callable tools for the model
tools = [
    {
        "type": "function",
        "name": "get_horoscope",
        "strict": True,
        "description": "Get today's horoscope for an astrological sign.",
        "parameters": {
            "type": "object",
            "properties": {
                "sign": {
                    "type": "string",
                    "description": "An astrological sign like Taurus or Aquarius",
                },
            },
            "required": ["sign"],
            "additionalProperties": False,
        },
    },
]


def get_horoscope(sign):
    return f"{sign}: Next Tuesday you will befriend a baby otter."


# Create a running input list we will add to over time
input_list = [{"role": "user", "content": "What is my horoscope? I am an Aquarius."}]

# 2. Prompt the model with tools defined
response = client.responses.create(
    model="gpt-6-astra",
    tools=tools,
    input=input_list,
)

# Save function call outputs for subsequent requests
input_list += response.output

for item in response.output:
    if item.type == "function_call":
        if item.name == "get_horoscope":
            # 3. Execute the function logic for get_horoscope
            sign = json.loads(item.arguments)["sign"]
            horoscope = get_horoscope(sign)

            # 4. Provide function call results to the model
            input_list.append(
                {
                    "type": "function_call_output",
                    "call_id": item.call_id,
                    "output": horoscope,
                }
            )

response = client.responses.create(
    model="gpt-6-astra",
    instructions="Respond only with a horoscope generated by a tool.",
    tools=tools,
    input=input_list,
)

# 5. The model should be able to give a response!
print(response.output_text)

GPT-5나 o4-mini 같은 추론 모델의 응답에 도구 호출과 함께 추론 항목이 포함되어 있다면 도구 호출 출력을 보낼 때 해당 추론 항목도 모두 다시 보내야 합니다. 모델이 이 항목들을 생성하는 데 사용한 대화 기록을 보존하는 방법은 프리픽스 변경 시 권장 사항을 참고하세요.

함수 정의

함수는 일반적으로 각 API 요청의 tools 매개변수에 선언합니다. 도구 검색을 사용하면 애플리케이션이 로드를 미룬 함수를 대화 도중에 로드할 수도 있습니다. 어느 방식이든 호출 가능한 함수의 스키마 구조는 같습니다. 함수 정의에는 다음 속성이 있습니다.

필드 설명
type 항상 function으로 설정
name 함수 이름(예: get_weather)
description 함수를 언제 어떻게 사용하는지에 대한 설명
parameters 함수의 입력 인수를 정의하는 JSON 스키마
strict 함수 호출에 엄격 모드를 적용할지 여부

다음은 get_weather 함수의 정의 예제입니다.

{
  "type": "function",
  "name": "get_weather",
  "description": "Retrieves current weather for the given location.",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City and country e.g. Bogotá, Colombia"
      },
      "units": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Units the temperature will be returned in."
      }
    },
    "required": ["location", "units"],
    "additionalProperties": false
  },
  "strict": true
}

parameters는 JSON 스키마로 정의하므로 속성 타입, 열거형, 설명, 중첩 객체, 재귀 객체 등 스키마가 제공하는 다양한 기능을 활용할 수 있습니다.

네임스페이스 정의

네임스페이스를 사용하면 관련 도구를 crm, billing, shipping 같은 도메인별로 묶을 수 있습니다. 네임스페이스는 비슷한 도구를 정리하는 데 도움이 됩니다. 특히 CRM 검색 도구와 지원 티켓 검색 도구처럼 모델이 시스템이나 용도가 다른 도구 중 하나를 골라야 할 때 유용합니다.

{
  "type": "namespace",
  "name": "crm",
  "description": "CRM tools for customer lookup and order management.",
  "tools": [
    {
      "type": "function",
      "name": "get_customer_profile",
      "description": "Fetch a customer profile by customer ID.",
      "parameters": {
        "type": "object",
        "properties": {
          "customer_id": { "type": "string" }
        },
        "required": ["customer_id"],
        "additionalProperties": false
      }
    },
    {
      "type": "function",
      "name": "list_open_orders",
      "description": "List open orders for a customer ID.",
      "defer_loading": true,
      "parameters": {
        "type": "object",
        "properties": {
          "customer_id": { "type": "string" }
        },
        "required": ["customer_id"],
        "additionalProperties": false
      }
    }
  ]
}

도구 검색

모델에 많은 도구를 제공해야 한다면 tool_search로 도구 일부 또는 전체의 로드를 미룰 수 있습니다. tool_search 도구를 사용하면 모델이 관련 도구를 검색하고 컨텍스트에 추가한 뒤 사용할 수 있습니다. gpt-5.4 이상의 모델만 이 기능을 지원합니다. 자세한 내용은 도구 검색 가이드를 참고하세요.

함수 정의 권장 사항

  1. 함수 이름, 매개변수 설명, 지시 사항을 명확하고 구체적으로 작성하세요.

    • 함수의 목적과 각 매개변수의 용도 및 형식을 명시하고 출력이 무엇을 나타내는지 설명하세요.
    • 각 함수를 언제 사용하고 언제 사용하지 말아야 하는지 시스템 프롬프트에 설명하세요. 일반적으로 모델이 해야 할 일을 정확하게 알려 주는 것이 좋습니다.
    • 예제와 경계 사례를 포함하세요. 특히 같은 오류가 반복될 때 도움이 됩니다. (참고: 예제를 추가하면 추론 모델의 성능이 저하될 수 있습니다.)
    • 로드를 미루는 도구는 함수 설명에 자세한 지침을 넣고 네임스페이스 설명은 간결하게 작성하세요. 네임스페이스 설명은 모델이 무엇을 로드할지 선택하는 데 도움이 되고, 함수 설명은 로드한 도구를 올바르게 사용하는 데 도움이 됩니다.
  2. 소프트웨어 엔지니어링의 모범 사례를 적용하세요.

    • 함수가 직관적이고 예상대로 동작하도록 설계하세요. (최소 놀람의 원칙)
    • 열거형을 활용하고 객체 구조를 설계해 유효하지 않은 상태를 방지하세요. 예를 들어 toggle_light(on: bool, off: bool)은 잘못된 인수 조합도 허용합니다.
    • 인턴도 사용할 수 있는지 확인하세요. 처음 접하는 사람이 모델에 제공한 정보만 보고 함수를 올바르게 사용할 수 있나요? (그렇지 않다면 어떤 질문을 하는지 확인하고 그 답을 프롬프트에 추가하세요.)
  3. 가능한 작업은 코드로 처리해 모델의 부담을 줄이세요.

    • 이미 알고 있는 인수는 모델이 채우게 하지 마세요. 예를 들어 이전 메뉴에서 order_id를 확보했다면 order_id 매개변수를 포함하지 마세요. 대신 submit_refund()를 매개변수 없이 정의하고 코드에서 order_id를 전달하세요.
    • 항상 연달아 호출하는 함수는 하나로 합치세요. 예를 들어 mark_location()을 항상 query_location() 직후에 호출한다면 위치를 표시하는 로직을 조회 함수 안으로 옮기세요.
  4. 정확도를 높이려면 처음에 제공하는 함수 수를 적게 유지하세요.

    • 함수 수를 바꿔 가며 성능을 평가하세요.
    • 엄격한 기준은 아니지만 각 턴을 시작할 때 사용할 수 있는 함수는 20개 미만을 목표로 하세요.
    • 도구 검색을 활용해 규모가 크거나 자주 사용하지 않는 도구 모음은 처음부터 모두 노출하지 말고 로드를 미루세요.
  5. OpenAI 리소스를 활용하세요.

    • 함수 스키마를 생성하고 반복해서 개선하려면 Playground를 사용하세요.
    • 함수 수가 많거나 작업이 어렵다면 파인튜닝으로 함수 호출 정확도를 높이는 방법을 고려하세요. (쿡북)

토큰 사용량

내부적으로 함수 정의는 모델이 학습한 구문으로 시스템 메시지에 삽입됩니다. 따라서 호출 가능한 함수 정의도 모델의 컨텍스트 한도에 포함되며 입력 토큰으로 과금됩니다. 토큰 한도에 도달하면 처음에 로드하는 함수 수를 줄이거나, 가능하면 설명을 짧게 작성하거나, 도구 검색을 사용해 로드를 미룬 도구를 필요할 때만 로드하세요.

도구 명세에 정의한 함수가 많다면 파인튜닝으로 토큰 사용량을 줄일 수도 있습니다.

함수 호출 처리

모델이 함수를 호출하면 애플리케이션에서 함수를 실행하고 결과를 반환해야 합니다. 모델 응답에는 호출이 없을 수도 있고 하나 이상 포함될 수도 있으므로 여러 호출을 처리할 수 있도록 구현하는 것이 좋습니다.

응답의 output 배열에는 type 값이 function_call인 항목이 있습니다. 각 항목에는 나중에 함수 결과를 제출할 때 사용하는 call_id, 함수 이름인 name, JSON으로 인코딩된 arguments가 들어 있습니다.

여러 함수 호출이 포함된 응답 예제
[
    {
        "id": "fc_12345xyz",
        "call_id": "call_12345xyz",
        "type": "function_call",
        "name": "get_weather",
        "arguments": "{\"location\":\"Paris, France\"}"
    },
    {
        "id": "fc_67890abc",
        "call_id": "call_67890abc",
        "type": "function_call",
        "name": "get_weather",
        "arguments": "{\"location\":\"Bogotá, Colombia\"}"
    },
    {
        "id": "fc_99999def",
        "call_id": "call_99999def",
        "type": "function_call",
        "name": "send_email",
        "arguments": "{\"to\":\"bob@email.com\",\"body\":\"Hi bob\"}"
    }
]

도구 검색을 사용하면 tool_search_call과 tool_search_output 항목이 function_call 앞에 나타날 수도 있습니다. 함수를 로드한 후에는 여기서 설명한 방식대로 함수 호출을 처리하세요.

함수 호출 실행 및 결과 추가
input_messages += response.output

for tool_call in response.output:
    if tool_call.type != "function_call":
        continue

    name = tool_call.name
    args = json.loads(tool_call.arguments)

    result = call_function(name, args)
    input_messages.append(
        {
            "type": "function_call_output",
            "call_id": tool_call.call_id,
            "output": json.dumps(result),
        }
    )

위 예제에서는 각 호출을 해당 함수로 연결하기 위해 call_function이라는 함수를 가정했습니다. 다음과 같이 구현할 수 있습니다.

함수 호출 실행 및 결과 추가
def call_function(name, args):
    if name == "get_weather":
        return get_weather(**args)
    if name == "send_email":
        return send_email(**args)
    raise ValueError(f"Unknown function: {name}")

결과 형식 지정

function_call_output 메시지로 전달하는 결과는 일반적으로 문자열이어야 합니다. 문자열의 형식은 JSON, 오류 코드, 일반 텍스트 등으로 자유롭게 정할 수 있습니다. 모델은 필요에 따라 이 문자열을 해석합니다.

이미지나 파일을 반환하는 함수라면 문자열 대신 이미지 또는 파일 객체의 배열을 전달할 수 있습니다.

함수에 반환값이 없다면(예: send_email) "success"처럼 성공 또는 실패 여부를 나타내는 문자열을 반환하세요.

응답에 결과 반영

결과를 input에 추가한 뒤 모델에 다시 보내면 최종 응답을 받을 수 있습니다.

모델에 결과 다시 보내기
response = client.responses.create(
    model="gpt-6-astra",
    input=input_messages,
    tools=responses_tools,
)
최종 응답
"It's about 15°C in Paris, 18°C in Bogotá, and I've sent that email to Bob."

추가 설정

도구 선택

기본적으로 모델이 언제 몇 개의 도구를 사용할지 결정합니다. tool_choice 매개변수로 특정 동작을 강제할 수 있습니다.

  1. 자동: (기본값) 함수를 호출하지 않거나 하나 이상 호출합니다. tool_choice: "auto"
  2. 호출 필수: 함수를 하나 이상 호출합니다. tool_choice: "required"
  3. 함수 지정: 지정한 함수 하나만 정확히 한 번 호출합니다. tool_choice: {"type": "function", "name": "get_weather"}
  4. 허용 도구: 모델에 제공된 도구 중 지정한 일부만 호출할 수 있도록 제한합니다.

allowed_tools를 사용하는 경우

여러 모델 요청에서 전달하는 도구 목록은 그대로 두고 요청별로 일부 도구만 사용할 수 있게 하려면 allowed_tools 목록을 설정하세요. 이렇게 하면 프롬프트 캐싱의 비용 절감 효과를 극대화할 수 있습니다.

"tool_choice": {
    "type": "allowed_tools",
    "mode": "auto",
    "tools": [
        { "type": "function", "name": "get_weather" },
        { "type": "function", "name": "search_docs" }
    ]
  }
}

tool_choice를 "none"으로 설정하면 함수를 하나도 전달하지 않았을 때처럼 동작합니다.

도구 검색을 사용할 때도 tool_choice는 현재 턴에서 호출 가능한 도구에 적용됩니다. 특히 일부 도구를 로드한 뒤 모델이 그 도구만 사용하도록 제한할 때 유용합니다.

병렬 함수 호출

GPT-5부터 병렬 함수 호출을 지원하는 모델에서는 내장 도구를 함께 제공해도 함수를 병렬로 호출할 수 있습니다. 단, 병렬로 호출하는 함수 묶음에 내장 도구를 포함할 수는 없습니다.

모델은 한 턴에서 여러 함수를 호출할 수 있습니다. 이를 방지하려면 parallel_tool_calls를 false로 설정하세요. 그러면 도구를 호출하지 않거나 하나만 호출합니다.

참고: 현재 파인튜닝한 모델이 한 턴에서 여러 함수를 호출하면 해당 호출에는 엄격 모드가 적용되지 않습니다.

gpt-4.1-nano-2025-04-14 사용 시 참고: 이 gpt-4.1-nano 스냅샷에서는 병렬 도구 호출을 켜면 간혹 같은 도구에 대한 호출이 여러 번 포함될 수 있습니다. 이 스냅샷을 사용할 때는 병렬 도구 호출을 끄는 것이 좋습니다.

엄격 모드

strict를 true로 설정하면 함수 호출이 함수 스키마를 반드시 따르도록 할 수 있습니다. 엄격 모드를 항상 켜는 것이 좋습니다.

엄격 모드는 내부적으로 구조화된 출력값 기능을 사용하므로 다음 두 가지 요건을 충족해야 합니다.

  1. additionalProperties를 false로 설정하세요. parameters에 포함된 모든 객체에 이 설정이 필요합니다.
  2. properties의 모든 필드를 required로 지정하세요.

선택적 필드는 null을 type 옵션에 추가해 나타낼 수 있습니다(아래 예제 참고).

strict: true를 보냈을 때 스키마가 위 요건을 충족하지 않으면 요청이 거부되며 누락된 제약 조건에 대한 설명이 반환됩니다. strict를 생략하면 기본 동작은 API에 따라 달라집니다. Responses 요청에서는 가능한 경우 스키마를 엄격 모드에 맞게 정규화합니다. 스키마를 호환 가능한 형태로 변환할 수 없으면 엄격 모드를 해제하고 스키마 준수를 보장하지 않는 방식으로 함수를 호출합니다. 이 경우 응답의 도구 정의에 strict: false가 표시됩니다. Chat Completions 요청은 기본적으로 엄격 모드를 사용하지 않습니다. Responses에서도 엄격 모드를 사용하지 않고 스키마 준수를 보장하지 않는 호출 방식을 유지하려면 strict: false를 명시하세요.

{
    "type": "function",
    "name": "get_weather",
    "description": "Retrieves current weather for the given location.",
    "strict": true,
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "City and country e.g. Bogotá, Colombia"
            },
            "units": {
                "type": ["string", "null"],
                "enum": ["celsius", "fahrenheit"],
                "description": "Units the temperature will be returned in."
            }
        },
        "required": ["location", "units"],
        "additionalProperties": false
    }
}

Playground에서 생성한 모든 스키마에는 엄격 모드가 켜져 있습니다.

엄격 모드 사용을 권장하지만 몇 가지 제한 사항이 있습니다.

  1. JSON 스키마의 일부 기능은 지원되지 않습니다. (지원되는 스키마 참고)

파인튜닝한 모델에는 다음 제한 사항도 적용됩니다.

  1. 첫 요청에서 스키마를 추가로 처리한 뒤 캐싱합니다. 요청마다 스키마가 달라지면 지연 시간이 늘어날 수 있습니다.
  2. 성능을 위해 스키마를 캐싱하므로 스키마는 데이터 비보관 적용 대상에서 제외됩니다.

스트리밍

스트리밍을 사용하면 모델이 인수를 생성하는 동안 어떤 함수를 호출하는지 보여 주고 인수까지 실시간으로 표시해 진행 상황을 알릴 수 있습니다.

함수 호출 스트리밍은 일반 응답 스트리밍과 같은 방식으로 작동합니다. stream을 true로 설정하면 다양한 event 객체를 받습니다.

함수 호출 스트리밍
from openai import OpenAI

client = OpenAI()

tools = [
    {
        "type": "function",
        "name": "get_weather",
        "strict": True,
        "description": "Get current temperature for a given location.",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City and country e.g. Bogotá, Colombia",
                }
            },
            "required": ["location"],
            "additionalProperties": False,
        },
    }
]

stream = client.responses.create(
    model="gpt-6-astra",
    input=[{"role": "user", "content": "What's the weather like in Paris today?"}],
    tools=tools,
    stream=True,
)

for event in stream:
    print(event)
출력 이벤트
{"type":"response.output_item.added","response_id":"resp_1234xyz","output_index":0,"item":{"type":"function_call","id":"fc_1234xyz","call_id":"call_1234xyz","name":"get_weather","arguments":""}}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"{\""}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"location"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"\":\""}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"Paris"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":","}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":" France"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"\"}"}
{"type":"response.function_call_arguments.done","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"arguments":"{\"location\":\"Paris, France\"}"}
{"type":"response.output_item.done","response_id":"resp_1234xyz","output_index":0,"item":{"type":"function_call","id":"fc_1234xyz","call_id":"call_1234xyz","name":"get_weather","arguments":"{\"location\":\"Paris, France\"}"}}

다만 청크를 합쳐 하나의 content 문자열을 만드는 대신, JSON으로 인코딩된 arguments 값을 구성합니다.

모델이 함수를 하나 이상 호출하면 각 함수 호출에 대해 response.output_item.added 타입의 이벤트가 발생합니다. 이벤트에는 다음 필드가 있습니다.

필드 설명
response_id 함수 호출이 속한 응답의 ID
output_index 응답 내 출력 항목의 인덱스. 응답에 포함된 개별 함수 호출을 구분합니다.
item 진행 중인 함수 호출 항목. name, arguments, id 필드를 포함합니다.

이후 response.function_call_arguments.delta 타입의 이벤트가 차례로 도착합니다. 각 이벤트의 delta에는 arguments 필드에 새로 추가된 내용이 담깁니다. 이벤트의 필드는 다음과 같습니다.

필드 설명
response_id 함수 호출이 속한 응답의 ID
item_id 델타가 속한 함수 호출 항목의 ID
output_index 응답 내 출력 항목의 인덱스. 응답에 포함된 개별 함수 호출을 구분합니다.
delta arguments 필드에 새로 추가된 내용

다음 코드는 여러 delta를 합쳐 최종 tool_call 객체를 구성하는 방법을 보여 줍니다.

tool_call 델타 누적
final_tool_calls = {}

for event in stream:
    if (
        event.type == "response.output_item.added"
        and event.item.type == "function_call"
    ):
        final_tool_calls[event.output_index] = event.item
    elif event.type == "response.function_call_arguments.delta":
        index = event.output_index

        if final_tool_calls[index]:
            final_tool_calls[index].arguments += event.delta
누적 결과 final_tool_calls[0]
{
    "type": "function_call",
    "id": "fc_1234xyz",
    "call_id": "call_2345abc",
    "name": "get_weather",
    "arguments": "{\"location\":\"Paris, France\"}"
}

모델이 함수 호출을 마치면 response.function_call_arguments.done 타입의 이벤트가 발생합니다. 이 이벤트에는 다음 필드를 포함한 전체 함수 호출이 담깁니다.

필드 설명
response_id 함수 호출이 속한 응답의 ID
output_index 응답 내 출력 항목의 인덱스. 응답에 포함된 개별 함수 호출을 구분합니다.
item 함수 호출 항목. name, arguments, id 필드를 포함합니다.

사용자 정의 도구

사용자 정의 도구는 JSON 스키마 기반 함수 도구와 대체로 같은 방식으로 작동합니다. 다만 도구에 필요한 입력 형식을 모델에 명시하는 대신 모델이 임의의 문자열을 도구 입력으로 전달할 수 있습니다. 응답을 불필요하게 JSON으로 감싸지 않아도 되고, 아래에서 설명하는 것처럼 응답에 사용자 정의 문법을 적용할 수도 있습니다.

다음 코드는 모델의 응답에서 Python 코드가 담긴 텍스트 문자열을 입력으로 받는 사용자 정의 도구를 만드는 예제입니다.

사용자 정의 도구 호출 예제
from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-6-astra",
    input="Use the code_exec tool to print hello world to the console.",
    tools=[
        {
            "type": "custom",
            "name": "code_exec",
            "description": "Executes arbitrary Python code.",
        }
    ],
)
print(response.output)

앞선 예제와 마찬가지로 output 배열에는 모델이 생성한 도구 호출이 들어 있습니다. 이번에는 도구 호출의 입력이 일반 텍스트로 제공됩니다.

[
  {
    "id": "rs_6890e972fa7c819ca8bc561526b989170694874912ae0ea6",
    "type": "reasoning",
    "content": [],
    "summary": []
  },
  {
    "id": "ctc_6890e975e86c819c9338825b3e1994810694874912ae0ea6",
    "type": "custom_tool_call",
    "status": "completed",
    "call_id": "call_aGiFQkRWSWAIsMQ19fKqxUgb",
    "input": "print(\"hello world\")",
    "name": "code_exec"
  }
]

문맥 자유 문법

문맥 자유 문법(CFG)은 특정 형식에 맞는 텍스트를 생성하는 방법을 정의한 규칙 집합입니다. 사용자 정의 도구에 CFG를 제공하면 모델이 도구에 전달하는 텍스트 입력을 해당 문법으로 제한할 수 있습니다.

사용자 정의 도구를 설정할 때 grammar 매개변수로 사용자 정의 CFG를 전달할 수 있습니다. 현재 문법 정의에는 lark와 regex 두 가지 구문을 지원합니다.

Lark CFG

Lark 문맥 자유 문법 예제
from openai import OpenAI

client = OpenAI()

grammar = """
start: expr
expr: term (SP ADD SP term)* -> add
| term
term: factor (SP MUL SP factor)* -> mul
| factor
factor: INT
SP: " "
ADD: "+"
MUL: "*"
%import common.INT
"""

response = client.responses.create(
    model="gpt-6-astra",
    input="Use the math_exp tool to add four plus four.",
    tools=[
        {
            "type": "custom",
            "name": "math_exp",
            "description": "Creates valid mathematical expressions",
            "format": {
                "type": "grammar",
                "syntax": "lark",
                "definition": grammar,
            },
        }
    ],
)
print(response.output)

이렇게 설정하면 도구 출력은 정의한 Lark CFG를 따르게 됩니다.

[
  {
    "id": "rs_6890ed2b6374819dbbff5353e6664ef103f4db9848be4829",
    "type": "reasoning",
    "content": [],
    "summary": []
  },
  {
    "id": "ctc_6890ed2f32e8819daa62bef772b8c15503f4db9848be4829",
    "type": "custom_tool_call",
    "status": "completed",
    "call_id": "call_pmlLjmvG33KJdyVdC4MVdk5N",
    "input": "4 + 4",
    "name": "math_exp"
  }
]

문법은 Lark 구문을 일부 변형한 형식으로 작성합니다. 모델 샘플링은 LLGuidance를 사용해 제한합니다. 다음 Lark 기능은 지원되지 않습니다.

  • 렉서 정규식의 전후방 탐색
  • 렉서 정규식의 비탐욕적 수량자(*?, +?, ??)
  • 터미널 우선순위
  • 템플릿
  • 가져오기(내장 %import common 제외)
  • %declare 선언

사용자 정의 문법을 실험할 때는 Lark IDE를 사용하는 것이 좋습니다.

문법 복잡도 제한

문법에는 도구에 필요한 규칙과 패턴만 포함하세요. 문법이 너무 복잡하면 OpenAI API가 오류를 반환할 수 있으므로 API에서 사용하기 전에 해당 문법이 호환되는지 확인하세요.

Lark 문법을 의도대로 작성하기는 쉽지 않습니다. 단순한 문법일수록 안정적으로 작동합니다. 복잡한 문법에서는 모델이 학습 분포를 벗어나지 않도록 문법 정의 자체와 프롬프트, 도구 설명을 여러 차례 조정해야 하는 경우가 많습니다.

올바른 패턴과 잘못된 패턴

다음은 경계가 정해진 단일 터미널을 사용하는 올바른 예입니다.

start: SENTENCE
SENTENCE: /[A-Za-z, ]*(the hero|a dragon|an old man|the princess)[A-Za-z, ]*(fought|saved|found|lost)[A-Za-z, ]*(a treasure|the kingdom|a secret|his way)[A-Za-z, ]*\./

다음처럼 여러 규칙이나 터미널로 나누지 마세요. 이 패턴은 규칙을 사용해 터미널 사이의 자유 형식 텍스트를 나누려 하지만, 렉서가 텍스트 조각을 가능한 한 길게 매칭하므로 의도한 경계를 제어할 수 없습니다.

start: sentence
sentence: /[A-Za-z, ]+/ subject /[A-Za-z, ]+/ verb /[A-Za-z, ]+/ object /[A-Za-z, ]+/

입력에서 터미널을 어디서 나눌지는 터미널 정의만 결정합니다. 소문자로 작성하는 규칙은 이 과정에 영향을 주지 않습니다. 기준 패턴 사이에 자유 형식 텍스트가 필요하다면 전체를 하나의 정규식 터미널로 표현하세요. 그러면 렉서가 의도한 구조 전체를 정확히 한 번 매칭합니다.

터미널과 규칙

Lark에서는 터미널로 렉서 토큰을 정의하고(관례상 UPPERCASE), 규칙으로 파서의 생성 규칙을 정의합니다(관례상 lowercase). 지원되는 기능 범위 안에서 예측 가능한 결과를 얻으려면 문법을 명시적으로 작성하고 불필요한 복잡성을 줄이세요. 터미널과 규칙이 맡는 역할도 명확히 구분하세요.

터미널의 정규식은 Rust regex 크레이트 구문을 사용합니다. Python의 re 모듈과는 구문이 다릅니다.

주요 개념과 권장 사항

렉서는 파서보다 먼저 실행됩니다

CFG 규칙을 적용하기 전에 렉서가 터미널을 매칭합니다. 이때 가능한 한 길게 일치하는 텍스트를 선택합니다. 하나의 터미널을 여러 규칙으로 나눠 형태를 제어하려 해도 렉서는 그 규칙을 참조하지 않습니다. 렉서의 매칭을 결정하는 것은 터미널 정규식뿐입니다.

자유 형식 텍스트에서 패턴을 추출할 때는 단일 터미널을 사용하세요

임의의 텍스트에 포함된 패턴(예: 기준 패턴 사이에 어떤 내용이든 들어갈 수 있는 자연어)을 인식해야 한다면 하나의 터미널로 표현하세요. 자유 형식 텍스트 터미널과 파서 규칙을 번갈아 배치하지 마세요. 렉서는 가능한 한 길게 매칭하므로 의도한 경계를 지키지 않으며, 모델이 학습 분포를 벗어날 가능성이 매우 높습니다.

개별 토큰을 조합할 때는 규칙을 사용하세요

규칙은 경계가 명확한 터미널(숫자, 키워드, 문장 부호 등)을 조합해 더 큰 구조를 만들 때 적합합니다. 두 터미널 사이에 들어갈 내용을 제한하는 용도로는 적합하지 않습니다.

각 터미널의 역할과 범위를 정하고 독립적으로 정의하세요

명시적인 문자 클래스와 범위가 정해진 수량자를 사용하세요. 예를 들어 {0,10}처럼 범위를 정하고 모든 곳에 무제한 *를 쓰지 마세요. “마침표까지의 임의 텍스트”를 매칭하려면 /[^.\n]{0,10}*\./ 같은 패턴이 /.+\./보다 좋습니다. 매칭되는 텍스트가 지나치게 길어지는 것을 방지할 수 있습니다.

규칙은 토큰을 조합하는 데 사용하고 정규식 내부 동작은 정규식으로 제어하세요

규칙을 적절히 사용한 예제입니다.

start: expr
NUMBER: /[0-9]+/
PLUS: "+"
MINUS: "-"
expr: term (("+"|"-") term)*
term: NUMBER

공백을 명시적으로 처리하세요

범위가 정해지지 않은 %ignore 지시문에 의존하지 마세요. 제한 없이 입력을 무시하면 문법이 지나치게 복잡해지거나 모델이 학습 분포를 벗어날 수 있습니다. 공백이 허용되는 위치마다 공백을 나타내는 터미널을 명시하세요.

문제 해결

  • 문법이 너무 복잡해서 API가 거부한다면 규칙과 터미널을 단순화하고 범위가 정해지지 않은 %ignore 지시문을 제거하세요.
  • 사용자 정의 도구 호출에 예상하지 못한 토큰이 들어 있다면 터미널 패턴이 겹치지 않는지, 렉서가 어떤 텍스트를 가장 길게 매칭하는지 확인하세요.
  • 모델이 학습 분포를 벗어나면 문법적으로는 유효하지만 의미가 잘못된 출력이 생성됩니다. 출력이 지나치게 길거나 같은 내용이 반복되는 것이 징후입니다. 이 경우 다음을 시도하세요.
    • 문법의 제약을 강화하세요.
    • 프롬프트에 퓨샷 예제를 추가하고 도구 설명을 다듬으세요. 설명에 문법을 소개하고 모델이 이를 고려해 추론하고 문법에 맞게 응답하도록 지시하세요.
    • 추론 수준을 높여 보세요. 예를 들어 medium에서 high로 변경하세요.

정규식 CFG

정규식 문맥 자유 문법 예제
from openai import OpenAI

client = OpenAI()

grammar = r"^(?P<month>January|February|March|April|May|June|July|August|September|October|November|December)\s+(?P<day>\d{1,2})(?:st|nd|rd|th)?\s+(?P<year>\d{4})\s+at\s+(?P<hour>0?[1-9]|1[0-2])(?P<ampm>AM|PM)$"

response = client.responses.create(
    model="gpt-6-astra",
    input="Use the timestamp tool to save a timestamp for August 7th 2025 at 10AM.",
    tools=[
        {
            "type": "custom",
            "name": "timestamp",
            "description": "Saves a timestamp in date + time in 24-hr format.",
            "format": {
                "type": "grammar",
                "syntax": "regex",
                "definition": grammar,
            },
        }
    ],
)
print(response.output)

이렇게 설정하면 도구 출력은 정의한 정규식 CFG를 따르게 됩니다.

[
  {
    "id": "rs_6894f7a3dd4c81a1823a723a00bfa8710d7962f622d1c260",
    "type": "reasoning",
    "content": [],
    "summary": []
  },
  {
    "id": "ctc_6894f7ad7fb881a1bffa1f377393b1a40d7962f622d1c260",
    "type": "custom_tool_call",
    "status": "completed",
    "call_id": "call_8m4XCnYvEmFlzHgDHbaOCFlK",
    "input": "August 7th 2025 at 10AM",
    "name": "timestamp"
  }
]

Lark 구문에서와 마찬가지로 정규식은 Rust regex 크레이트 구문을 사용합니다. Python의 re 모듈과는 구문이 다릅니다.

다음 정규식 기능은 지원되지 않습니다.

  • 전후방 탐색
  • 비탐욕적 수량자(*?, +?, ??)

주요 개념과 권장 사항

패턴은 한 줄로 작성해야 합니다

입력의 줄바꿈과 일치시키려면 이스케이프 시퀀스 \n을 사용하세요. 패턴을 여러 줄에 걸쳐 작성할 수 있는 상세 모드나 확장 모드는 사용하지 마세요.

정규식은 패턴 문자열 그대로 전달하세요

패턴을 //로 감싸지 마세요.