feat(models): unifyllm 网关接入,5 个国际旗舰模型进 pro 档(bump 0.57.0)
- config/models/unifyllm.yaml:Claude Fable 5 / Opus 4.8 / Sonnet 4.6 / GPT-5.6 Sol / Gemini 3.1 Pro,litellm openai/ 前缀 + api_base 覆盖,零代码 - 实测:gpt-5.x litellm 硬拦 temp!=1、gemini-3.1-pro temp=0.3 返回空, 两者 optimal_temperature=1.0;流式+tool calling+parallel 5 模型全通 - scripts/diag_unifyllm.py 冒烟脚本(--stream/--temp) - 服务器需代理出口(直连 unifyllm.ai TLS 失败),RUN.md env 段已注明 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
daf0db854a
commit
ba2afa9e98
|
|
@ -5,6 +5,10 @@
|
||||||
> 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。
|
> 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。
|
||||||
> 工程口径的完整记录见 `PROGRESS.md` / git log。
|
> 工程口径的完整记录见 `PROGRESS.md` / git log。
|
||||||
|
|
||||||
|
## 0.57.0 — 2026-07-10
|
||||||
|
|
||||||
|
- Pro 档新增 5 个国际旗舰模型:Claude Fable 5、Claude Opus 4.8、Claude Sonnet 4.6、GPT-5.6 Sol、Gemini 3.1 Pro,模型选单中直接选用。
|
||||||
|
|
||||||
## 0.56.0 — 2026-07-10
|
## 0.56.0 — 2026-07-10
|
||||||
|
|
||||||
- 长时间运行的脚本 / 命令支持**后台执行**:耗时任务(大批量数据处理、长时间计算等)自动转入后台独立运行,**不再被超时打断,服务更新也不中断**。
|
- 长时间运行的脚本 / 命令支持**后台执行**:耗时任务(大批量数据处理、长时间计算等)自动转入后台独立运行,**不再被超时打断,服务更新也不中断**。
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
### 2026-07
|
### 2026-07
|
||||||
|
|
||||||
|
- **07-10 / 0.57.0**:**unifyllm 网关接入(5 个国际旗舰模型进 pro 档)**:新档案 `config/models/unifyllm.yaml`(fable5/opus48/sonnet46/gpt56_sol/gemini31_pro,litellm `openai/` 前缀 + `api_base=https://unifyllm.ai/v1` + `UNIFYLLM_API_KEY`,零代码改动),`agent.yaml model_tiers.pro` 加 5 条。实测坑(全记在 yaml 头注释):gpt-5.x 被 litellm 硬拦 temp≠1、gemini-3.1-pro temp=0.3 返回空 → 两者 `optimal_temperature: 1.0`;`parallel_tool_calls` 参数网关接受(claude 实测 2 并行);流式 + include_usage 5 模型全通。新 `scripts/diag_unifyllm.py`(--stream/--temp,列模型 GET /v1/models)。**部署注意:服务器直连 unifyllm.ai TLS 握手失败,需代理出口(HTTPS_PROXY),部署前先跑 diag 验证**。价格字段暂 0(网关价目未知,成本先记 0)。
|
||||||
- **07-10 / 0.56.0**:**后台进程 bg proc(DESIGN §8.12)**——解掉"长脚本被工具超时掐死 / 占死 run / 蓝绿切换陪葬"三连:`shell`/`run_python` 加 `background=true`(模型按预计时长自选,前台超时报错里提示改走后台;默认上限 7200s、cap 86400s、每用户并发 3 个 `ZCBOT_MAX_BG_PROCS`),新 `check_process` 工具查状态/日志尾部/kill。状态协议纯文件(`<user_root>/.zcbot_procs/<task_id>/<proc_id>/{proc.json,output.log,exit_code}`,dotfile 用户不可见,exit_code 出现即终态),无 DB 无队列组件。host 模式 detach 独立 wrapper(`core/proc_wrapper.py`,stdlib-only,限时+杀树+截尾 10MB);docker 模式**专用容器** `zcbot-proc-<id>`(`pool.run_proc_container`,同款硬化/iptables init,product=proc + 无 instance label → 与 sandbox 的 idle reaper/shutdown_all 生命周期解耦,dockerd 托管扛蓝绿)。回收:check_process 见终态顺手 rm 容器 + web lifespan 每小时 `procs.sweep`(终态目录 7d TTL、exited 孤儿容器)。**前端可视 + 通知**:`GET /v1/procs`(用户级)+ `POST /v1/tasks/{id}/procs/{pid}/kill` 两端点(纯文件读取无 DB);`procs.js` 把 `[Background]` 工具结果卡活化(spinner+summary 跳秒+停止按钮,与前台工具卡同体验;历史重渲同样恢复活态/终态定格 exit·耗时)+ running→终态弹 toast(右下角,跨 task 也提醒、点击跳转对应任务)+ **对话锁**(bg proc 运行期 composer 发送→停止、Enter 拦截,观感与前台执行一致,完成的那次轮询解锁;锁仅前端,服务端不 409——停止入口须可达);轮询式(仅有 running proc 时 5s 一拉,触发点:登录/选 task/run 收尾/[Background] 工具结果)——不走 SSE,proc 完成时刻往往没有活跃 run。LLM 超时最终定为**显式化默认 600s + env 可调**(长工具已走后台,LLM 单调用无正当 600s 静默;被掐再调 `ZCBOT_LLM_TIMEOUT_S`)。手工验证 `scripts/test_bgproc_manual.py` 全过(win host 模式:启动/探活/exit 0/日志/kill 137/sweep),unittest 全量 201 过。同批带上:**seedance `resume_task_id` 续查**(超时/中断后拿 cgt_id 跳过提交直接续轮询,不过配额闸不重复计费;fast/pro poll_timeout 统一 1200s)+ **LLM 超时显式化**(`core/llm.py` 显式传 litellm timeout,env `ZCBOT_LLM_TIMEOUT_S` 可调,见后)。
|
- **07-10 / 0.56.0**:**后台进程 bg proc(DESIGN §8.12)**——解掉"长脚本被工具超时掐死 / 占死 run / 蓝绿切换陪葬"三连:`shell`/`run_python` 加 `background=true`(模型按预计时长自选,前台超时报错里提示改走后台;默认上限 7200s、cap 86400s、每用户并发 3 个 `ZCBOT_MAX_BG_PROCS`),新 `check_process` 工具查状态/日志尾部/kill。状态协议纯文件(`<user_root>/.zcbot_procs/<task_id>/<proc_id>/{proc.json,output.log,exit_code}`,dotfile 用户不可见,exit_code 出现即终态),无 DB 无队列组件。host 模式 detach 独立 wrapper(`core/proc_wrapper.py`,stdlib-only,限时+杀树+截尾 10MB);docker 模式**专用容器** `zcbot-proc-<id>`(`pool.run_proc_container`,同款硬化/iptables init,product=proc + 无 instance label → 与 sandbox 的 idle reaper/shutdown_all 生命周期解耦,dockerd 托管扛蓝绿)。回收:check_process 见终态顺手 rm 容器 + web lifespan 每小时 `procs.sweep`(终态目录 7d TTL、exited 孤儿容器)。**前端可视 + 通知**:`GET /v1/procs`(用户级)+ `POST /v1/tasks/{id}/procs/{pid}/kill` 两端点(纯文件读取无 DB);`procs.js` 把 `[Background]` 工具结果卡活化(spinner+summary 跳秒+停止按钮,与前台工具卡同体验;历史重渲同样恢复活态/终态定格 exit·耗时)+ running→终态弹 toast(右下角,跨 task 也提醒、点击跳转对应任务)+ **对话锁**(bg proc 运行期 composer 发送→停止、Enter 拦截,观感与前台执行一致,完成的那次轮询解锁;锁仅前端,服务端不 409——停止入口须可达);轮询式(仅有 running proc 时 5s 一拉,触发点:登录/选 task/run 收尾/[Background] 工具结果)——不走 SSE,proc 完成时刻往往没有活跃 run。LLM 超时最终定为**显式化默认 600s + env 可调**(长工具已走后台,LLM 单调用无正当 600s 静默;被掐再调 `ZCBOT_LLM_TIMEOUT_S`)。手工验证 `scripts/test_bgproc_manual.py` 全过(win host 模式:启动/探活/exit 0/日志/kill 137/sweep),unittest 全量 201 过。同批带上:**seedance `resume_task_id` 续查**(超时/中断后拿 cgt_id 跳过提交直接续轮询,不过配额闸不重复计费;fast/pro poll_timeout 统一 1200s)+ **LLM 超时显式化**(`core/llm.py` 显式传 litellm timeout,env `ZCBOT_LLM_TIMEOUT_S` 可调,见后)。
|
||||||
- **07-10 / 0.55.2**:文件面板根目录隐藏系统工作目录:`/v1/files`(仅根层)与 `/v1/folders`(新建任务目录候选)过滤定时任务执行目录 / 渠道镜像对话目录(`web/app.py::_system_wd_names`,DB 回查 `scheduled_job_id`/`channel` 判定而非硬编码名字前缀 —— 孤儿目录(task 已删)无任务入口,刻意留在根目录可见)。只是列表降噪非权限拦截:带 path 直接访问照常放行,点定时任务运行历史 / 微信卡片时文件面板自动跳入该目录不受影响(chat.js 既有逻辑,前端零改动)。
|
- **07-10 / 0.55.2**:文件面板根目录隐藏系统工作目录:`/v1/files`(仅根层)与 `/v1/folders`(新建任务目录候选)过滤定时任务执行目录 / 渠道镜像对话目录(`web/app.py::_system_wd_names`,DB 回查 `scheduled_job_id`/`channel` 判定而非硬编码名字前缀 —— 孤儿目录(task 已删)无任务入口,刻意留在根目录可见)。只是列表降噪非权限拦截:带 path 直接访问照常放行,点定时任务运行历史 / 微信卡片时文件面板自动跳入该目录不受影响(chat.js 既有逻辑,前端零改动)。
|
||||||
- **07-10 / 0.55.1**:per-user 磁盘配额 5GB→20GB(`config/agent.yaml quotas.disk_bytes_per_user`;RUN 故障兜底行、CHANGELOG 同步)。仍是应用层软配额,OS 层 xfs prjquota 兜底照旧留待外部用户开放前(§7.5 #4)。
|
- **07-10 / 0.55.1**:per-user 磁盘配额 5GB→20GB(`config/agent.yaml quotas.disk_bytes_per_user`;RUN 故障兜底行、CHANGELOG 同步)。仍是应用层软配额,OS 层 xfs prjquota 兜底照旧留待外部用户开放前(§7.5 #4)。
|
||||||
|
|
|
||||||
5
RUN.md
5
RUN.md
|
|
@ -42,6 +42,11 @@
|
||||||
# 共享同一台推理服务 http://182.54.21.126:9000/v1)。涉密任务用户显式选 `local.r1` / `local.qwq`
|
# 共享同一台推理服务 http://182.54.21.126:9000/v1)。涉密任务用户显式选 `local.r1` / `local.qwq`
|
||||||
# 代替默认 deepseek_v4.flash;未设 env 时这两条 variant 调用即抛 RuntimeError(其他模型不影响)
|
# 代替默认 deepseek_v4.flash;未设 env 时这两条 variant 调用即抛 RuntimeError(其他模型不影响)
|
||||||
LOCAL_LLM_API_KEY=...
|
LOCAL_LLM_API_KEY=...
|
||||||
|
# unifyllm 网关(config/models/unifyllm.yaml,Claude/GPT/Gemini 国际旗舰,pro 档):可选。
|
||||||
|
# 未设:unifyllm.* 5 个 variant 调用即抛 RuntimeError(其他模型不影响)。
|
||||||
|
# ★ 服务器直连 unifyllm.ai TLS 握手失败,需代理出口(HTTPS_PROXY);
|
||||||
|
# 部署前 `.venv/Scripts/python.exe scripts/diag_unifyllm.py` 冒烟验证(5 模型 tool-calling)
|
||||||
|
UNIFYLLM_API_KEY=sk-...
|
||||||
ZCBOT_DB_URL=postgresql://user:pass@host:5432/zcbot
|
ZCBOT_DB_URL=postgresql://user:pass@host:5432/zcbot
|
||||||
# main.py web 必填(probe/db/user 不验)
|
# main.py web 必填(probe/db/user 不验)
|
||||||
PLATFORM_KEY=<≥16 字符随机串,platform 机器对机器入口校验>
|
PLATFORM_KEY=<≥16 字符随机串,platform 机器对机器入口校验>
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,11 @@ model_tiers:
|
||||||
- doubao.evolving
|
- doubao.evolving
|
||||||
- glm.pro
|
- glm.pro
|
||||||
- glm.pro52
|
- glm.pro52
|
||||||
|
- unifyllm.fable5 # unifyllm 网关(海外旗舰,走代理出口)
|
||||||
|
- unifyllm.opus48
|
||||||
|
- unifyllm.sonnet46
|
||||||
|
- unifyllm.gpt56_sol
|
||||||
|
- unifyllm.gemini31_pro
|
||||||
- seedream_5
|
- seedream_5
|
||||||
- seedance_2_fast
|
- seedance_2_fast
|
||||||
- seedance_2_pro
|
- seedance_2_pro
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
# unifyllm 网关模型档案(https://unifyllm.ai,多协议 LLM 网关)
|
||||||
|
# 走 OpenAI 兼容端点:litellm `openai/` 前缀 + api_base 覆盖,key 用 Bearer 认证。
|
||||||
|
# 网关上还有 claude-sonnet-5 / opus 4-5~4-7 / gpt-5.4/5.5 / gemini-3.5-flash 等,
|
||||||
|
# 按需加 variant 即可(GET /v1/models 可拉全量,见 scripts/diag_unifyllm.py)。
|
||||||
|
#
|
||||||
|
# 注意事项(2026-07-10 实测,见 diag_unifyllm.py):
|
||||||
|
# - gpt-5.x:litellm 客户端硬拦 temperature != 1,optimal_temperature 必须 1.0
|
||||||
|
# - gemini-3.1-pro:temperature 0.3 时返回空内容(0 tool_call 0 text),必须 1.0
|
||||||
|
# - parallel_tool_calls 参数网关接受(claude 实测返回 2 个并行调用)
|
||||||
|
# - thinking/reasoning_effort 透传未验证,先全 false(同 glm.yaml 的 TODO 思路)
|
||||||
|
# - 价格字段留 0:网关价目未知,拿到价目表后补(成本先记 0)
|
||||||
|
# - 服务器直连 unifyllm.ai TLS 握手失败,需代理出口(HTTPS_PROXY);部署前先跑 diag 验证
|
||||||
|
family: unifyllm
|
||||||
|
|
||||||
|
variants:
|
||||||
|
fable5:
|
||||||
|
display_name: Claude Fable 5
|
||||||
|
model_id: openai/claude-fable-5
|
||||||
|
api_base: https://unifyllm.ai/v1
|
||||||
|
api_key_env: UNIFYLLM_API_KEY
|
||||||
|
max_context: 200000
|
||||||
|
reliable_context: 120000
|
||||||
|
max_output: 8192
|
||||||
|
parallel_tools: true
|
||||||
|
tool_calling_quality: excellent
|
||||||
|
thinking_mode: false
|
||||||
|
reasoning_effort_levels: []
|
||||||
|
default_reasoning_effort: ""
|
||||||
|
code_quality: excellent
|
||||||
|
enable_run_python: true
|
||||||
|
max_iterations: 150
|
||||||
|
optimal_temperature: 0.3
|
||||||
|
prompt_caching: false # anthropic-beta header 走 OpenAI 兼容端点无效,不发
|
||||||
|
extended_thinking: false
|
||||||
|
|
||||||
|
opus48:
|
||||||
|
display_name: Claude Opus 4.8
|
||||||
|
model_id: openai/claude-opus-4-8
|
||||||
|
api_base: https://unifyllm.ai/v1
|
||||||
|
api_key_env: UNIFYLLM_API_KEY
|
||||||
|
max_context: 200000
|
||||||
|
reliable_context: 120000
|
||||||
|
max_output: 8192
|
||||||
|
parallel_tools: true
|
||||||
|
tool_calling_quality: excellent
|
||||||
|
thinking_mode: false
|
||||||
|
reasoning_effort_levels: []
|
||||||
|
default_reasoning_effort: ""
|
||||||
|
code_quality: excellent
|
||||||
|
enable_run_python: true
|
||||||
|
max_iterations: 150
|
||||||
|
optimal_temperature: 0.3
|
||||||
|
prompt_caching: false
|
||||||
|
extended_thinking: false
|
||||||
|
|
||||||
|
sonnet46:
|
||||||
|
display_name: Claude Sonnet 4.6
|
||||||
|
model_id: openai/claude-sonnet-4-6
|
||||||
|
api_base: https://unifyllm.ai/v1
|
||||||
|
api_key_env: UNIFYLLM_API_KEY
|
||||||
|
max_context: 200000
|
||||||
|
reliable_context: 120000
|
||||||
|
max_output: 8192
|
||||||
|
parallel_tools: true
|
||||||
|
tool_calling_quality: excellent
|
||||||
|
thinking_mode: false
|
||||||
|
reasoning_effort_levels: []
|
||||||
|
default_reasoning_effort: ""
|
||||||
|
code_quality: excellent
|
||||||
|
enable_run_python: true
|
||||||
|
max_iterations: 120
|
||||||
|
optimal_temperature: 0.3
|
||||||
|
prompt_caching: false
|
||||||
|
extended_thinking: false
|
||||||
|
|
||||||
|
gpt56_sol:
|
||||||
|
display_name: GPT-5.6 Sol
|
||||||
|
model_id: openai/gpt-5.6-sol
|
||||||
|
api_base: https://unifyllm.ai/v1
|
||||||
|
api_key_env: UNIFYLLM_API_KEY
|
||||||
|
max_context: 262144
|
||||||
|
reliable_context: 131072
|
||||||
|
max_output: 8192
|
||||||
|
parallel_tools: true
|
||||||
|
tool_calling_quality: excellent
|
||||||
|
thinking_mode: false
|
||||||
|
reasoning_effort_levels: []
|
||||||
|
default_reasoning_effort: ""
|
||||||
|
code_quality: excellent
|
||||||
|
enable_run_python: true
|
||||||
|
max_iterations: 120
|
||||||
|
optimal_temperature: 1.0 # 硬约束:litellm 拦 gpt-5* 的 temp != 1
|
||||||
|
prompt_caching: false
|
||||||
|
extended_thinking: false
|
||||||
|
|
||||||
|
gemini31_pro:
|
||||||
|
display_name: Gemini 3.1 Pro
|
||||||
|
model_id: openai/gemini-3.1-pro-preview
|
||||||
|
api_base: https://unifyllm.ai/v1
|
||||||
|
api_key_env: UNIFYLLM_API_KEY
|
||||||
|
max_context: 1000000
|
||||||
|
reliable_context: 262144
|
||||||
|
max_output: 8192
|
||||||
|
parallel_tools: false # gemini 走网关未实测该参数,保守关闭
|
||||||
|
tool_calling_quality: good
|
||||||
|
thinking_mode: false
|
||||||
|
reasoning_effort_levels: []
|
||||||
|
default_reasoning_effort: ""
|
||||||
|
code_quality: excellent
|
||||||
|
enable_run_python: true
|
||||||
|
max_iterations: 120
|
||||||
|
optimal_temperature: 1.0 # 硬约束:0.3 时返回空内容(实测)
|
||||||
|
prompt_caching: false
|
||||||
|
extended_thinking: false
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
|
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
|
||||||
# 改版本只动这一行。
|
# 改版本只动这一行。
|
||||||
__version__ = "0.56.0"
|
__version__ = "0.57.0"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
"""unifyllm 网关连通性 / tool-calling 冒烟测试。
|
||||||
|
|
||||||
|
用法(.env 在仓库根, 含 UNIFYLLM_API_KEY):
|
||||||
|
.venv/Scripts/python.exe scripts/diag_unifyllm.py [model_id ...]
|
||||||
|
|
||||||
|
不带参数时测默认 5 个精选模型。走 litellm openai/ 前缀 + api_base,
|
||||||
|
与 core/llm.py 线上路径一致。输出一律 ASCII(Windows 控制台 GBK)。
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
env_file = os.path.join(ROOT, ".env")
|
||||||
|
if os.path.exists(env_file):
|
||||||
|
for line in open(env_file, encoding="utf-8"):
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith("#") and "=" in line:
|
||||||
|
k, v = line.split("=", 1)
|
||||||
|
os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))
|
||||||
|
|
||||||
|
os.environ.setdefault("LITELLM_LOCAL_MODEL_COST_MAP", "True")
|
||||||
|
import litellm # noqa: E402
|
||||||
|
|
||||||
|
API_BASE = "https://unifyllm.ai/v1"
|
||||||
|
DEFAULT_MODELS = [
|
||||||
|
"claude-fable-5",
|
||||||
|
"claude-opus-4-8",
|
||||||
|
"claude-sonnet-4-6",
|
||||||
|
"gpt-5.6-sol",
|
||||||
|
"gemini-3.1-pro-preview",
|
||||||
|
]
|
||||||
|
|
||||||
|
TOOLS = [{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "get_weather",
|
||||||
|
"description": "Get current weather for a city",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {"city": {"type": "string"}},
|
||||||
|
"required": ["city"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
key = os.environ.get("UNIFYLLM_API_KEY")
|
||||||
|
if not key:
|
||||||
|
print("[FAIL] UNIFYLLM_API_KEY not set (check .env)")
|
||||||
|
return 1
|
||||||
|
args = sys.argv[1:]
|
||||||
|
force_temp = None
|
||||||
|
stream = False
|
||||||
|
for a in list(args):
|
||||||
|
if a.startswith("--temp="):
|
||||||
|
force_temp = float(a.split("=", 1)[1])
|
||||||
|
args.remove(a)
|
||||||
|
elif a == "--stream":
|
||||||
|
stream = True
|
||||||
|
args.remove(a)
|
||||||
|
models = args or DEFAULT_MODELS
|
||||||
|
failed = []
|
||||||
|
for m in models:
|
||||||
|
# litellm 客户端硬拦 gpt-5* 的 temperature != 1
|
||||||
|
temp = force_temp if force_temp is not None else (1.0 if m.startswith("gpt-5") else 0.3)
|
||||||
|
try:
|
||||||
|
kwargs = dict(
|
||||||
|
model=f"openai/{m}",
|
||||||
|
api_base=API_BASE,
|
||||||
|
api_key=key,
|
||||||
|
messages=[{"role": "user", "content": "What's the weather in Beijing? Use the tool."}],
|
||||||
|
tools=TOOLS,
|
||||||
|
temperature=temp,
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
if stream:
|
||||||
|
# 与 core/llm.py chat_stream 同路径:流式 + include_usage,chunk 拼回完整 response
|
||||||
|
chunks = list(litellm.completion(**kwargs, stream=True,
|
||||||
|
stream_options={"include_usage": True}))
|
||||||
|
resp = litellm.stream_chunk_builder(chunks)
|
||||||
|
else:
|
||||||
|
resp = litellm.completion(**kwargs)
|
||||||
|
msg = resp.choices[0].message
|
||||||
|
tc = msg.tool_calls or []
|
||||||
|
usage = resp.usage
|
||||||
|
if tc:
|
||||||
|
print(f"[OK] {m}: tool_call={tc[0].function.name}({tc[0].function.arguments.strip()}) "
|
||||||
|
f"tokens={usage.prompt_tokens}+{usage.completion_tokens}")
|
||||||
|
else:
|
||||||
|
text = (msg.content or "")[:80].replace("\n", " ")
|
||||||
|
print(f"[WARN] {m}: no tool_call, text={text!r}")
|
||||||
|
except Exception as e:
|
||||||
|
failed.append(m)
|
||||||
|
print(f"[FAIL] {m}: {type(e).__name__}: {str(e)[:200]}")
|
||||||
|
return 1 if failed else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Loading…
Reference in New Issue