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
}

由於 parameters 是以 JSON 結構描述定義,你可以運用其豐富的功能,例如屬性型別、列舉、描述、巢狀物件及遞迴物件。

定義命名空間

使用命名空間,依領域將相關工具分組,例如 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\"}"}}

不過,此時你要將各個區塊彙整為編碼後的 arguments JSON 物件,而非單一 content 字串。

當模型呼叫一或多個函式時,每個函式呼叫都會發出一個類型為 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。請勿使用允許模式跨越多行的詳細/擴充模式。

以純模式字串提供正規表示式

不要用 // 包住模式。