Skip to content

工具循环、流式事件与会话 ​

工具意图还没有执行 ​

模型产生 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;不调用在线模型,直接断言这两种结果不同。

先记录自己的输出与解释,再核对本章答案。

用心记录每一步成长