工具循环、流式事件与会话
工具意图还没有执行
模型产生 function_call,应用执行本地函数,再提交带相同 call_id 的 function_call_output。response ID 关联生成轮次,call ID 关联工具结果,二者不能互换。工具描述影响选择,参数校验与权限决定是否执行。
沿用上一章虚拟环境与两个环境变量。以下保存为 tools.py,运行会产生最多 4 次模型请求,只调用只读本地库存函数。
python
import json, os
from openai import OpenAI
client = OpenAI(timeout=30, max_retries=0)
tools = [{'type':'function','name':'stock','description':'查询图书库存',
'parameters':{'type':'object','properties':{'sku':{'type':'string'}},
'required':['sku'],'additionalProperties':False},'strict':True}]
items = [{'role':'user','content':'BOOK-1 还有几本?'}]
for step in range(4):
r = client.responses.create(model=os.environ['OPENAI_MODEL'],input=items,
tools=tools,store=False)
if r.status != 'completed':
raise RuntimeError(r.status)
items.extend(r.output)
calls = [x for x in r.output if x.type == 'function_call']
if not calls:
print(r.output_text)
break
for call in calls:
args = json.loads(call.arguments)
if call.name != 'stock' or set(args) != {'sku'} or not isinstance(args['sku'],str):
raise ValueError('invalid call')
result = {'sku':args['sku'],'count':{'BOOK-1':3}.get(args['sku']),
'found':args['sku'] == 'BOOK-1'}
items.append({'type':'function_call_output','call_id':call.call_id,
'output':json.dumps(result)})
else:
raise RuntimeError('tool step budget exhausted')保留 r.output 全部项目很重要:有些模型的推理项目也参与后续输入,不要只拼接文字。未知商品返回 found=false 与 count=null,不能用 0 混淆不存在与售罄。循环上限保护的是应用,不要求模型自觉停止。多工具只读调用可以并行,但结果仍逐个绑定 ID;修改同一资源的写调用需要业务排序。
展示进度与提交结果
在新文件 stream.py 中运行:
python
import os
from openai import OpenAI
client = OpenAI(timeout=30,max_retries=0)
completed = False
with client.responses.create(model=os.environ['OPENAI_MODEL'],
input='解释幂等性',stream=True,store=False) as events:
for event in events:
if event.type == 'response.output_text.delta':
print(event.delta,end='',flush=True)
elif event.type == 'response.completed':
completed = True
elif event.type in {'response.failed','response.incomplete','error'}:
raise RuntimeError(event.type)
if not completed:
raise RuntimeError('stream ended before completion')文本 delta 可以显示;工具参数 delta 只能累积,不能每收到一个片段就执行一次。流关闭不等于成功,最后一个文本块也不等于整个响应完成。用户关闭页面时应取消本地等待;已经在外部服务发生的副作用必须查询或补偿。
状态管理有两种清楚的选择:像工具例子一样由应用保存全部输入项目,或用 previous_response_id 接续服务端 response。使用后者时仍需重发本轮必要的 instructions;不要假定先前 instructions 会自动继承。不要同时发送重复历史又链接上一轮,把同一用户内容算两遍。
练习
给库存函数增加 BOOK-2,其库存是 0。要求 BOOK-2 返回 found=true,未知书返回 found=false;不调用在线模型,直接断言这两种结果不同。
先记录自己的输出与解释,再核对本章答案。