random_coffee_fact 不带参数(空对象)。这些定义通过 API 调用中的 tools 参数传递,让模型知道可用的工具。
import os
from openai import OpenAI
import json
client = OpenAI(api_key = os.environ["OPENAI_API_KEY"])
tools =[{"type":"function","name":"make_coffee","description":"Gives a simple recipe for making a coffee drink.","parameters":{"type":"object","properties":{"coffee_type":{"type":"string","description":"The coffee drink, e.g. espresso, cappuccino, latte"}},"required":["coffee_type"],},},{"type":"function","name":"random_coffee_fact","description":"Returns a fun fact about coffee.","parameters":{"type":"object","properties":{}}}]技术支持
3. 实现工具
您需要实现实际执行工作的 Python 函数:
make_coffee(coffee_type) 根据请求的饮品返回简明配方字符串。
random_coffee_fact() 返回一条简短的咖啡小知识。
两者均返回可 JSON 序列化的字典,便于回传给模型。
defmake_coffee(coffee_type):
recipes ={"espresso":"Grind fine, 18g coffee → 36g espresso in ~28s.","cappuccino":"Brew 1 espresso shot, steam 150ml milk, pour and top with foam.","latte":"Brew 1 espresso shot, steam 250ml milk, pour for silky texture.",}return{"coffee_type": coffee_type,"recipe": recipes.get(coffee_type.lower(),"Unknown coffee type!")}defrandom_coffee_fact():return{"fact":"Coffee is the second most traded commodity in the world, after oil."}技术支持
4. 开始对话
用用户问题开启对话:{"role": "user", "content": "How do I make a latte?"}
# Track used tools
used_tools =[]
input_list =[{"role":"user","content":"How do I make a latte?"}]
response = client.responses.create(
model="gpt-5",
tools=tools,input=input_list,)
input_list += response.output技术支持
final = client.responses.create(
model="gpt-5",
tools=tools,input=input_list,
instructions="Answer using only the tool results.")print("Final output:\n", final.output_text)print("\n--- Tool Usage ---")for t in tools:
status ="USED ✅"if t["name"]in used_tools else"NOT USED ❌"print(f"{t['name']}: {status}")技术支持
Final output:{"coffee_type":"latte","recipe":"Brew 1 espresso shot, steam 250ml milk, pour for silky texture."}--- Tool Usage ---
make_coffee: USED ✅
random_coffee_fact: NOT USED ❌技术支持
from openai import OpenAI
import json
import random
client = OpenAI()# --- Custom tool ---
tools =[{"type":"custom","name":"meal_planner","description":"Takes ONLY a comma-separated list of ingredients and suggests a meal idea."}]# --- Fake meal planner ---defplan_meal(ingredients:str)->str:
ideas =[f"Stir-fry: {ingredients} with garlic & soy sauce.",f"One-pot rice: cook {ingredients} together in broth until fluffy.",f"Soup: simmer {ingredients} in stock with herbs.",f"Sheet-pan bake: roast {ingredients} at 200°C for ~20 min."]return random.choice(ideas)# --- Start conversation ---
messages =[{"role":"user","content":"I only have chicken, rice, and broccoli. Any dinner ideas?"}]# 1) Ask model with clear instruction
resp = client.responses.create(
model="gpt-5",
tools=tools,input=messages,
instructions="If the user mentions ingredients, call the meal_planner tool with ONLY a comma-separated list like 'chicken, rice, broccoli'.")
messages += resp.output
# 2) Find the tool call
tool_call =next((x for x in resp.output ifgetattr(x,"type","")=="custom_tool_call"),None)assert tool_call,"No tool call found!"# Get clean CSV input
ingredients_csv = tool_call.input.strip()# 3) Run the tool
meal_result = plan_meal(ingredients_csv)# 4) Send tool output back
messages.append({"type":"function_call_output","call_id": tool_call.call_id,"output": meal_result
})# 5) Final model response
final = client.responses.create(
model="gpt-5",
tools=tools,input=messages,
instructions="Turn the meal idea into a short recipe with 3-4 steps.")print("\n--- Tool Call ---")print("Name:", tool_call.name)print("Input:", ingredients_csv)print("Output:", meal_result)print("\n--- Final Output ---\n", final.output_text)技术支持
由此,我们获得了所用工具的统计信息,包括安装、输入和输出。此外,最终输出给出了完整的烹饪步骤。
--- Tool Call ---
Name: meal_planner
Input: chicken, rice, broccoli
Output: One-pot rice: cook chicken, rice, broccoli together in broth until fluffy.
--- Final Output ---
One-Pot Chicken, Rice & Broccoli
- Season bite-size chicken pieces with salt and pepper; sear in a little oil in a pot until lightly browned.
- Add 1 cup rinsed rice and 2 cups broth (or water + salt). Bring to a boil, then cover and simmer on low for12 minutes.
- Scatter 2 cups small broccoli florets on top, cover, and cook 5-7 more minutes until rice is fluffy and broccoli is tender.
- Rest 5 minutes off heat, fluff, and adjust seasoning. Optional: stir in a knob of butter or a splash of soy.技术支持
from openai import OpenAI
import json
client = OpenAI()# --- Local fake evaluator ---defeval_expression(expr:str)->str:try:# Evaluate safely using Python's eval on restricted globals
result =eval(expr,{"__builtins__":{}},{})returnf"{expr} = {result}"except Exception as e:returnf"Error evaluating expression: {e}"# --- Custom tool with grammar constraint ---
tools =[{"type":"custom","name":"math_solver","description":"Solve a math problem by outputting ONLY a valid arithmetic expression.","format":{"type":"grammar","syntax":"lark","definition":r"""
start: expr
?expr: term
| expr "+" term -> add
| expr "-" term -> sub
?term: factor
| term "*" factor -> mul
| term "/" factor -> div
?factor: NUMBER
| "(" expr ")"
%import common.NUMBER
%ignore " "
"""},}]# --- User asks ---
msgs =[{"role":"user","content":"What is (12 + 8) * 3 minus 5 divided by 5?"}]# 1) First model call: GPT-5 must emit a grammar-valid expression
resp = client.responses.create(
model="gpt-5",
tools=tools,input=msgs,
instructions="Always call the math_solver tool with a grammar-valid arithmetic expression like '(12 + 8) * 3 - 5 / 5'.")
msgs += resp.output
tool_call =next(
x for x in resp.output
ifgetattr(x,"type","")in("custom_tool_call","tool_call","function_call"))
expr =getattr(tool_call,"input","")orgetattr(tool_call,"arguments","")print("\n=== Grammar-Constrained Expression ===")print(expr)# 2) Run the local evaluator
tool_result = eval_expression(expr)print("\n=== Tool Execution Result ===")print(tool_result)# 3) Return tool output
msgs.append({"type":"function_call_output","call_id": tool_call.call_id,"output": tool_result
})# 4) Final pass: GPT-5 presents answer nicely
final = client.responses.create(
model="gpt-5",input=msgs,
tools=tools,
instructions="Present the evaluated result clearly in natural language.")print("\n=== Final Output ===")print(final.output_text)技术支持
from openai import OpenAI
from firecrawl import Firecrawl
import json
import os
client = OpenAI()
firecrawl = Firecrawl(api_key=os.environ["FIRECRAWL_API_KEY"])# --- Full toolset ---
tools =[{"type":"function","name":"dummy_web_search","description":"A fake web search that always returns static dummy results.","parameters":{"type":"object","properties":{"query":{"type":"string"},"limit":{"type":"integer"}},"required":["query"]},},{"type":"function","name":"firecrawl_search","description":"Perform a real web search using Firecrawl.","parameters":{"type":"object","properties":{"query":{"type":"string"},"limit":{"type":"integer"}},"required":["query"]},},]# --- Dummy implementation ---defdummy_web_search(query, limit=3):return{"query": query,"results":[f"[Dummy] Result {i+1} for '{query}'"for i inrange(limit)]}# --- User asks ---
messages =[{"role":"user","content":"Find me the latest info about the stock market."}]# --- Restrict to Firecrawl only ---
resp = client.responses.create(
model="gpt-5",
tools=tools,input=messages,
instructions="Only the firecrawl_search tool is allowed. Do not use dummy_web_search.",
tool_choice={"type":"allowed_tools","mode":"required",# force Firecrawl"tools":[{"type":"function","name":"firecrawl_search"}],},)for item in resp.output:ifgetattr(item,"type","")in("function_call","tool_call"):print("\n--- Tool Call ---")print("Tool name:", item.name)print("Arguments:", item.arguments)if item.name =="firecrawl_search":
args = json.loads(item.arguments)# Firecrawl returns a SearchData object → convert it
results_obj = firecrawl.search(
query=args["query"],
limit=args.get("limit",3),)# 🔥 Convert to JSON-serializable dictifhasattr(results_obj,"to_dict"):
results = results_obj.to_dict()elifhasattr(results_obj,"dict"):
results = results_obj.dict()else:
results = json.loads(results_obj.json())ifhasattr(results_obj,"json")else results_obj
print("\n--- Firecrawl Raw Results ---")print(json.dumps(results, indent=2)[:500])# ✅ Extract only the data portion for summarization
safe_results = results.get("data", results)# Feed back to GPT
messages += resp.output
messages.append({"type":"function_call_output","call_id": item.call_id,"output": json.dumps(safe_results)# now safe})# Final pass
final = client.responses.create(
model="gpt-5",
tools=tools,input=messages,
instructions="Summarize the Firecrawl search results into 3-4 bullet points with clickable [Title](URL) links.")print("\n--- Final Natural Language Answer ---")print(final.output_text)elif item.name =="dummy_web_search":
args = json.loads(item.arguments)
results = dummy_web_search(args["query"], args.get("limit",3))print("\n--- Dummy Web Search Results ---")print(json.dumps(results, indent=2))技术支持
from openai import OpenAI
import json
import random
client = OpenAI()# --- Fake tool implementation ---defmedical_advice(symptom:str):
remedies ={"headache":"You can take acetaminophen (paracetamol) or ibuprofen, rest in a quiet room, and stay hydrated.","cough":"Drink warm fluids, use honey in tea, and consider over-the-counter cough syrup.","fever":"Use acetaminophen to reduce fever, stay hydrated, and rest. See a doctor if >39°C.",}return{"advice": remedies.get(symptom.lower(),"Please consult a healthcare provider for guidance.")}# --- Tool definition ---
tools =[{"type":"function","name":"medical_advice","description":"Provide safe, general over-the-counter advice for common symptoms.","parameters":{"type":"object","properties":{"symptom":{"type":"string"}},"required":["symptom"],"additionalProperties":False},"strict":True,}]# --- Messages (system preamble instruction) ---
messages =[{"role":"system","content":"Before you call a tool, explain why you are calling it in ONE short sentence prefixed with 'Preamble:'."},{"role":"user","content":"What should I take for a headache?"}]# 1) First call: expect preamble + tool call
resp = client.responses.create(model="gpt-5",input=messages, tools=tools)print("=== First Response ===")for item in resp.output:
t =getattr(item,"type",None)if t =="message":# Extract just the model's text
content =getattr(item,"content",None)
text =Noneifisinstance(content,list):
text ="".join([c.text for c in content ifhasattr(c,"text")])elif content:
text =str(content)if text:# ✅ Only print onceprint(text)if t in("function_call","tool_call","custom_tool_call"):print("Tool:",getattr(item,"name",None))print("Args:",getattr(item,"arguments",None))# Extract tool call
tool_call =next(x for x in resp.output ifgetattr(x,"type",None)in("function_call","tool_call","custom_tool_call"))
messages += resp.output
# 2) Execute tool locally
args = json.loads(getattr(tool_call,"arguments","{}"))
symptom = args.get("symptom","")
tool_result = medical_advice(symptom)# 3) Return tool result
messages.append({"type":"function_call_output","call_id": tool_call.call_id,"output": json.dumps(tool_result)})# 4) Final model call → natural answer
final = client.responses.create(model="gpt-5",input=messages, tools=tools)print("\n=== Final Answer ===")print(final.output_text)技术支持
您可以看到,在调用工具之前,它会先解释调用原因。这对于可观测性与调试十分有益。
总结
GPT-5 是 OpenAI 迄今最先进的模型,尤其在编码与基于代理的工作流方面表现卓越。其最新 API 功能让从头到尾构建生产级系统变得直观易行。
随着GPT-5 的发布,OpenAI 在 API 中扩展了工具/函数调用,新增了自由格式的工具调用(原始文本、无需 JSON)、通过 Lark/CFG 的语法约束、工具允许列表,以及改进的工具使用推理。它们共同让 GPT-5 成为真正的代理型模型;您可以将 API、数据库和自定义工具连接到 Responses API,生成有据可依的答案,甚至自动化工作流。
在本教程中,我将讲解函数工具、自定义工具、语法约束、工具允许列表以及前言(Preamble),并配以代码示例,清晰说明其工作原理及使用场景。
GPT-5 中的函数调用:是什么与为何
GPT-5 的函数调用允许您用应用的数据与动作增强模型。模型可以自行判断何时调用某个工具,而您负责处理逻辑并返回结果,以生成最终且有依据的回复。
函数调用类型:
什么是函数调用?
GPT-5 同时支持使用 JSON Schema 的结构化函数工具,以及接收自由格式文本负载(如 SQL、脚本或配置)的自定义工具,以便与外部运行时无缝集成。
函数工具由 JSON Schema 定义,确保模型精确理解应传递的参数,并允许严格的输入校验。您可在我们的OpenAI 函数调用教程中了解更多。
为什么要使用函数调用?
函数调用如何工作?
来源:函数调用 - OpenAI API
何时使用函数工具与自定义工具
函数工具(JSON Schema):发送结构化数据
结合 JSON Schema 使用函数工具可从模型获得可预测、结构化的结果。模型会评估您声明的工具,决定何时使用某个工具,提出通过 JSON 校验的参数,然后在您的代码中执行相应函数。
之后,您可以要求模型仅基于工具返回的数据生成回复,确保输出有据可依、机器可读、便于集成。
下面的示例展示了几个关键概念:
1. 环境准备
pip install openai安装官方 OpenAI Python SDK。2. 定义工具
您需要提供工具列表,包括名称、描述和参数的 JSON Schema。该 Schema 可帮助模型提供格式良好的参数。
make_coffee期望一个字符串参数:coffee_type。random_coffee_fact不带参数(空对象)。这些定义通过 API 调用中的tools参数传递,让模型知道可用的工具。3. 实现工具
您需要实现实际执行工作的 Python 函数:
make_coffee(coffee_type)根据请求的饮品返回简明配方字符串。random_coffee_fact()返回一条简短的咖啡小知识。两者均返回可 JSON 序列化的字典,便于回传给模型。
4. 开始对话
用用户问题开启对话:{
"role": "user", "content": "How do I make a latte?"}模型可能返回包含
function_call项的消息,例如make_coffee,并带有{"coffee_type":"latte"}。随后您应将模型消息追加回input_list: input_list += response.output。这有助于在下一轮中维护对话状态。5. 执行工具调用并附加输出
您应遍历
response.output,识别item.type等于"function_call"的条目。对于这些条目,解析item.arguments,并将请求分发至您的 Python 函数。对于
make_coffee函数,使用make_coffee(args["coffee_type"])调用。对于random_coffee_fact,直接调用random_coffee_fact()即可。执行函数后,追加一条包含以下内容的
function_call_output消息:call_id(用于将该输出与模型请求关联)output(您的函数结果的 JSON 字符串)。6. 生成最终答案
进行第二次
client.responses.create调用,传入已包含工具输出的更新后input_list。used_tools变量记录了模型函数调用中出现的工具名称。我们将用它来展示哪些工具被使用。由于用户询问拿铁,模型选择了
make_coffee,并设置coffee_type = "latte";您的代码执行了该函数,最终响应完全由该工具结果构建。自定义工具(自由格式):发送原始文本
使用 GPT-5 的自由格式自定义工具,可让模型将原始文本直接发送给您的工具,例如代码、SQL、Shell 命令,或简单的 CSV,而无需 JSON Schema。
此能力超越了以往仅限 JSON 的函数调用,为与执行器、查询引擎或领域特定语言(DSL)解释器的集成提供了更大的灵活性。
在自由格式模式下,模型会生成包含非结构化文本负载的自定义工具调用,您可以将其路由到运行时。然后您返回结果,供模型用来完善面向用户的最终回答。
接下来我们将编写代码,使模型能选择合适的工具,并以非结构化原始文本作为输出。该输出随后将传递给一个 Python 函数生成结果。这样,用户可以提供手头食材,模型将生成贴合需求的食谱。
在本示例代码中,我们:
meal_planner的自定义工具,接受原始文本输入。plan_meal,接收以逗号分隔的食材字符串并返回晚餐创意。plan_meal来执行工具,然后将结果作为通过call_id关联的function_call_output返回给模型。由此,我们获得了所用工具的统计信息,包括安装、输入和输出。此外,最终输出给出了完整的烹饪步骤。
语法约束(Lark):约束工具输出
GPT-5 现已支持上下文无关语法(CFG),以确保输出格式受到严格控制。
通过对响应应用语法(例如 SQL 或领域特定语言 DSL),GPT-5 能保证输出始终遵循所需结构。CFG 对自动化流程和高风险工作流尤为重要。
GPT-5 可以使用 Lark 等格式定义的语法,在生成过程中限制工具输出。这种受约束的解码方式通过防止格式漂移并确保结构一致性来提升可靠性。
接下来我们将编写代码,让模型选择一个工具并仅输出一个符合语法的算术表达式;然后在 Python 中对该表达式求值,将结果返回到对话,并最终以自然语言清晰呈现。
在本示例代码中,我们:
math_solver,其输出必须仅为符合语法的算术表达式(Lark CFG)。math_solver。call_id进行关联。如您所见,模型选择了工具并生成了受语法约束的表达式。随后通过 Python 函数得到答案,最终以自然语言给出清晰的结果。
工具允许列表:安全限制模型可用工具
allowed_tools设置可将模型限制在您的完整工具集中的安全子集内。这提升了可预测性并防止意外的工具调用,同时仍在允许范围内给予模型灵活性。您可以让模型在允许的工具中自由选择,或要求其必须使用其中一个,这与其他技术栈中的“强制工具”行为一致,并符合工具调用编排的一般最佳实践。
接下来我们将编写代码,只允许一个工具,让模型仅调用该工具,运行它,将结果回传,然后生成简明的用户可读摘要。
注意: 请创建一个免费的 Firecrawl 账户,生成 API 密钥,保存为环境变量
FIRECRAWL_API_KEY,并使用pip install firecrawl-py安装 Firecrawl Python SDK。在本示例代码中,我们:
dummy_web_search与firecrawl_search。tool_choice = { type: "allowed_tools", mode: "required", tools: [...] }将模型限制为仅可使用firecrawl_search。firecrawl_search时,反序列化参数,执行 Firecrawl,将其响应转换为可 JSON 序列化的字典,并提取安全的数据负载。function_call_output(通过call_id关联)回传到对话,然后进行最后一次模型调用,将结果总结为 3–4 个要点并包含 Markdown 链接。它选择了 Firecrawl 搜索,并据此以 Markdown 格式生成了最终响应。
前言(Preamble):告知模型如何调用工具
前言是模型在调用工具前生成的简短说明,用于阐明调用原因。它能提升透明度、建立用户信任,并在复杂工作流中改进调试清晰度。您可以通过类似“在调用工具前先解释”的简洁指令启用它,通常不会显著增加延迟。
接下来我们将编写代码,让模型先生成一行以“Preamble:”开头的简短说明,解释为何调用某工具,然后调用工具,您在本地执行,返回结果,最后模型给出清晰的最终答案。
在本示例代码中,我们:
medical_advice)。“Preamble:”开头的说明。call_id关联回传给模型。您可以看到,在调用工具之前,它会先解释调用原因。这对于可观测性与调试十分有益。
总结
GPT-5 是 OpenAI 迄今最先进的模型,尤其在编码与基于代理的工作流方面表现卓越。其最新 API 功能让从头到尾构建生产级系统变得直观易行。
在本教程中,您学会了:
若想进一步了解可用的 OpenAI 工具,我建议您学习我们的OpenAI 基础技能路径与《使用 OpenAI API》课程。