换掉 OpenDots 默认模型后,工具调用格式不一定还兼容。它取决于新模型返回工具调用的字段路径、参数组织形式、流式分片和结束原因,是否与 OpenDots 当前解析逻辑以及你的业务代码预期一致。短问答通过,不代表工具调用和长上下文也通过,所以建议把这次改动当成一次可回退的配置变更:先备份旧配置,用同一条工具调用请求对比新旧原始返回,再决定是直接切换、加适配层,还是回滚默认模型。
判断:换模型后先别改业务代码。适用场景是 OpenDots 里替换默认模型名并保留旧配置。操作动作:备份配置,固定一条工具调用请求,分别在新旧模型下记录原始响应,再验证长上下文是否被截断。验证方式:比对工具名、参数、调用 ID、finish_reason 和流式分片。风险边界:不同模型和 OpenDots 版本字段可能不同,以实际日志为准;参数无法稳定解析时,优先回滚或把差异收进适配层。
在配置里替换模型名并保留旧配置备份
改动位置通常在 OpenDots 的模型配置文件和启动环境变量里。先找到当前生效的模型名,再找与它关联的 provider、base_url、api_version、鉴权字段,不要只改一个 model 值。备份时把整个配置目录复制到项目外,并记录当前环境变量;如果配置来自版本库,另存一个分支或补丁。示例命令只用于说明动作,路径替换成你的实际配置路径。
cp -a ./config ./config.bak.$(date +%Y%m%d%H%M%S)
env | sort | grep -i -E 'model|provider|api|token' > env.before.log
需要一并检查的关联字段包括:工具调用开关或 tools/function_calling 相关配置、tool_choice 默认值、并行工具调用开关、max_tokens 与上下文窗口限制、stream 流式开关、超时时间,以及 system 提示里是否写死了工具说明。以上字段名以 OpenDots 当前配置文件为准,不要凭记忆改。
对比新旧模型的工具调用返回结构
先固定一条最小工具调用请求,例如要求模型调用 get_weather,参数 city=北京。分别用旧默认模型和新模型跑一次,日志里打印未经过业务解析的原始响应。重点比对以下字段。
- 工具调用位置:在 choices[0].message.tool_calls 数组,还是在 message.function_call 单对象,或混在 content 文本里。
- 调用标识:id 是否存在、是否每个调用唯一;type 是 function 还是 tool。
- 函数名:字段叫 function.name、name 还是其他写法,大小写是否一致。
- 参数结构:arguments 是 JSON 字符串还是已解析对象;键名、值类型、空参数是空对象还是 null。
- 多调用行为:数组长度、顺序、是否出现并行调用,以及 index 是否稳定。
- 结束原因:finish_reason 是 tool_calls、function_call 还是 stop。
- 流式模式:delta 中工具调用分片挂在哪个路径,arguments 是否按 index 累积。
验证时不要先跑完整业务,只打印原始返回即可。下面的骨架是通用示意,调用方法和字段名按你当前 OpenDots 接入方式替换。
# 通用骨架:只观察原始返回
resp = client.chat(messages=[...], tools=[...], stream=False)
print(resp)
# 检查 message.tool_calls / function_call / finish_reason
验证长上下文场景是否被截断
换模型后上下文窗口可能变化,短请求正常不代表长输入仍能保留前面的工具参数。构造长输入时,把同一个关键标记放在开头、中间和结尾,然后逐步增加重复文本长度。关键标记可以是订单号、用户 ID 或必须传给工具的参数。判断是否被截断,不能只看模型是否回答,要看原始日志和工具参数。
python - <<'PY'
base = '关键标记A:订单号 12345。请记住它。 ' * 200
print(base + '最后问题:订单号是多少?')
PY
观察点包括:最终工具调用参数是否还包含最前面的订单号;末尾指令是否被执行;日志里 prompt token 或上下文计数是否接近上限;是否出现 truncated、max context、length 等字段;finish_reason 是 length 还是 stop。如果新模型在长输入下丢掉开头参数,而旧模型没有,就先不要切到新模型,或缩短输入并调整调用策略。
写一层适配把差异挡在业务代码外
更稳的做法是在 OpenDots 返回结果与业务代码之间加一层适配。位置可以放在你自己的模型客户端封装、adapter 模块或消息中间件里,业务代码只依赖适配后的统一结构,不直接读取某个模型特有的字段路径。输入约定是原始 message 或流式 chunk,输出约定建议包含 name、arguments 对象、id、finish_reason 和 raw 原始响应。不要在这里发明未确认的接口细节,按实际返回补齐分支即可。
def adapt_tool_calls(raw_message):
calls = []
# 分支1:tool_calls 数组
# 分支2:function_call 单对象
# 分支3:content 内嵌 JSON
# 每个调用统一为 {'name': ..., 'arguments': {...}, 'id': ...}
return calls
流式响应要按 index 聚合完再解析 arguments,避免半个 JSON 字符串被业务代码误读。验证方式是同一请求分别指向新旧模型,适配层输出应一致;不一致时只改适配层,不要散落修改所有工具调用点。
换回默认模型的回滚步骤
回滚要能一次执行完。第一步,停止 OpenDots 服务或当前调用进程。第二步,用备份覆盖新配置,检查环境变量里是否还残留新模型名或新 base_url。第三步,重启并确认启动日志中加载的是默认模型。第四步,先跑一个不调用工具的纯文本最小请求,再跑一条工具调用请求。第五步,对比返回结构、工具名、arguments 和 finish_reason 是否与备份日志一致,确认业务代码不报解析错误。
预期结果是:默认模型恢复后,工具调用仍按原来的字段路径被解析,长上下文测试回到改动前表现。若没有旧日志,至少以最小请求能正常解析和调用为准。建议保留新旧两份配置和原始响应日志,回滚演练完成后再决定是否继续适配新模型。边界是:模型和 OpenDots 版本都可能改变字段,任何兼容性判断都应以你当前环境里的原始返回为准。