For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
主导航

函数调用

让模型访问新的功能和数据,用于遵循指令和响应提示。

函数调用 (也称为 工具调用)为 OpenAI 模型提供了一种强大而灵活的方式,使其能够与外部系统交互,并访问训练数据之外的数据。本指南介绍如何将模型连接到您的应用程序提供的数据和操作。我们将演示如何使用函数工具(通过 JSON 模式定义),以及使用自由格式文本作为输入和输出的自定义工具。

对于 Agents API 会话,请使用函数来注册函数并处理会话操作请求。本指南中的示例展示了与 Responses API 和 Chat Completions 的集成。

如果您的应用程序包含大量函数或庞大的模式,可以将函数调用与工具搜索结合使用,推迟加载不常用的工具,仅在模型需要时才加载。只有 gpt-5.4 及更新的模型支持 tool_search

GPT-6 Astra 需要使用 Responses API 进行工具调用。为确保兼容性,Chat Completions 示例使用 GPT-5.6。如需更新现有的 集成,请参阅迁移 指南

工作原理

首先,我们来了解几个与工具调用有关的关键术语。统一对这些术语的理解后,我们将通过一些实际示例演示如何进行工具调用。

工具调用流程

工具调用是您的应用程序与模型通过 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",
        "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"],
        },
    },
]


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,
                }
            )

print("Final input:")
print(input_list)

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("Final output:")
print(response.model_dump_json(indent=2))
print("\n" + 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
}

由于 parametersJSON 模式定义,您可以使用其丰富的功能,例如属性类型、枚举、描述、嵌套对象和递归对象。

定义命名空间

使用命名空间按领域对相关工具进行分组,例如 crmbillingshipping。命名空间有助于组织相似的工具。当模型必须在服务于不同系统或用途的工具之间做出选择时,命名空间尤其有用,例如一个搜索工具用于您的 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
    • 合并始终按顺序调用的函数。 例如,如果您总是在调用 query_location() 后调用 mark_location(),只需将标记逻辑移入查询函数中。
  4. 减少初始可用函数的数量,以提高准确性。

    • 在不同函数数量下评估实际效果
    • 尽量将每轮开始时同时可用的函数数量控制在 20 个以下 ,不过这只是建议,并非硬性要求。
    • 使用工具搜索 来延迟加载工具集中体积较大或不常用的部分,而不是一开始就提供所有工具。
  5. 利用 OpenAI 资源。

    • Playground生成并迭代优化函数模式
    • 对于函数数量较多或任务较复杂的情况,考虑通过微调来提高函数调用的准确性 。(Cookbook

Token 用量

在底层实现中,函数会以模型训练时学过的语法注入系统消息。这意味着可调用函数的定义会占用模型的上下文额度,并按输入 Token 计费。如果您遇到 Token 限制,我们建议减少预先加载的函数数量,尽可能缩短描述,或使用工具搜索,让延迟加载的工具仅在需要时加载。

如果您的工具规范中定义了大量函数,也可以使用微调来减少 Token 用量。

处理函数调用

当模型调用函数时,您必须执行该函数并返回结果。模型响应可能包含零次、一次或多次调用,因此最佳实践是按可能存在多次调用的情况来处理。

响应的 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\"}"
    }
]

如果您使用工具搜索,还可能在 function_call 之前看到 tool_search_calltool_search_output 条目。函数加载后,按此处展示的方式处理函数调用即可。

执行函数调用并追加结果
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,
)

print(response.output_text)
最终响应
"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. 对于 parameters 中的每个对象,都必须将 additionalProperties 设为 false
  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",
        "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 字符串,而是将它们汇总为经过编码的 arguments JSON 对象。

当模型调用一个或多个函数时,每次函数调用都会发出一个类型为 response.output_item.added 的事件,其中包含以下字段:

字段说明
response_id函数调用所属响应的 ID
output_index输出项在响应中的索引,用于标识响应中的各个函数调用。
item正在进行的函数调用项,包含 nameargumentsid 字段

随后,您会收到一系列类型为 response.function_call_arguments.delta 的事件,其中包含 arguments 字段的 delta。这些事件包含以下字段:

字段说明
response_id函数调用所属响应的 ID
item_id增量所属函数调用项的 ID
output_index输出项在响应中的索引,用于标识响应中的各个函数调用。
deltaarguments 字段的增量。

下面的代码片段演示了如何将多个 delta 汇总为最终的 tool_call 对象。

累积 tool_call 增量
final_tool_calls = {}

for event in stream:
    if event.type == "response.output_item.added":
        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函数调用项,包含 nameargumentsid 字段。

自定义工具

自定义工具的工作方式与由 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。目前,定义文法时支持两种 CFG 语法形式:larkregex

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 使用文法之前,确认所需的文法与 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 使用终结符定义词法分析器的 Token(按惯例使用 UPPERCASE),使用规则定义语法分析器的产生式(按惯例使用 lowercase)。要确保文法不超出支持的子集并避免意外行为,最实用的方法是明确编写文法,避免不必要的复杂性,并清晰划分终结符与规则的职责。

终结符使用的正则表达式语法是 Rust regex crate 的语法,而不是 Python 的 re 模块语法。

核心概念与最佳实践

词法分析器先于语法分析器运行

在应用任何 CFG 规则逻辑之前,词法分析器就会匹配终结符(采用贪婪匹配,以最长匹配为准)。如果您试图通过将终结符拆分到多个规则中来控制其匹配方式,这些规则并不能引导词法分析器,只有终结符的正则表达式才能做到。

从自由格式文本片段中提取文本时,优先使用单个终结符

如果您需要识别任意文本中嵌入的模式(例如,锚点之间可以包含“任意内容”的自然语言文本),请将其表示为单个终结符。不要尝试将自由文本终结符与语法分析规则交错使用;采用贪婪匹配的词法分析器不会遵循您预期的边界,模型也极有可能产生分布外输出。

使用规则组合独立的 Token

当您需要将边界明确的终结符(数字、关键字、标点符号)组合成更大的结构时,规则是理想的选择。但规则并不适合用来约束两个终结符“之间的内容”。

让终结符职责单一、范围有限且自成一体

优先使用明确的字符类和有界量词(使用 {0,10},不要到处使用无界的 *)。如果您需要匹配“直到句点为止的任意文本”,请优先使用类似 /[^.\n]{0,10}*\./ 的表达式,而不是 /.+\./,以避免匹配长度失控。

使用规则组合 Token,而不是控制正则表达式的内部行为

合理使用规则的示例:

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

显式处理空白字符

不要依赖无界的 %ignore 指令。使用无界的忽略指令可能会导致文法过于复杂,也可能导致模型偏离原有分布。建议在所有允许出现空白字符的位置显式加入相应的终结符。

故障排除

  • 如果 API 因文法过于复杂而拒绝它,请简化规则和终结符,并移除无界的 %ignore 指令。
  • 如果自定义工具调用中出现意外的 Token,请确认终结符的匹配范围没有重叠,并检查词法分析器的贪婪匹配行为。
  • 当模型偏离原有分布时(表现为模型生成过长或重复的输出,语法有效但语义错误):
    • 收紧文法约束。
    • 迭代调整提示(添加少样本示例)和工具描述(解释文法,并指示模型进行推理、遵循文法)。
    • 尝试更高的推理强度(例如,从“中”提升到“高”)。

正则表达式 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 crate 的语法,而不是 Python 的 re 模块语法。

不支持以下正则表达式功能:

  • 环视断言
  • 惰性修饰符(*?+???

核心概念与最佳实践

模式必须写在同一行

如果您需要匹配输入中的换行符,请使用转义序列 \n。不要使用允许模式跨越多行的详细模式或扩展模式。

以纯模式字符串的形式提供正则表达式

不要将模式包裹在 // 中。