一个轻量的 AI 代理(Python / FastAPI)。核心用途:压缩发给模型的工具信息,减少 token 消耗,同时提升工具调用的性能与准确率。 *目前仅支持 Responses API(/v1/responses)格式——客户端与上游均为该格式(如 LM Studio /v1/responses)。
工具调用类任务的输入开销主要来自 tools 字段:每把工具都要携带完整的 description 和 parameters(JSON Schema)。当工具数量达到几十上百个时:
TSTP 的做法是 两跳式工具选择(calltool 藏工具):
benchmark/TOOLS_API_unique.json,73 个工具)里的工具全部藏起来,只给模型一个 call_tool 选择工具。call_tool 的描述里只列出工具名(不含描述、不含参数),提示词引导模型一次选出所有与请求相关的工具(可多选;除非任务明显只与某一个工具紧密相关,才单选)——第一跳的输入开销被压到最低;call_tool 等中间过程,客户端零改动。效果:输入 token 大幅下降(不再每次携带全部工具 schema),且模型每次只聚焦于相关工具的完整定义,选择更准、调用更稳。第一跳还能用 summary_limit_chars 进一步压缩。
# 1) 安装依赖(建议虚拟环境)
python -m venv .venv
.venv\Scripts\activate # Windows;Linux/macOS: source .venv/bin/activate
pip install -r requirements.txt
# 2) 配置上游
copy .env.example .env # Windows;Linux/macOS: cp .env.example .env
# 编辑 .env:填 UPSTREAM_BASE_URL(默认 https://api.openai.com/v1)和 UPSTREAM_API_KEY
# 3) 启动
python run.py # 默认 http://127.0.0.1:8000
启动后可访问:
http://127.0.0.1:8000/docs — Swagger 交互文档http://127.0.0.1:8000/api/tools/rules — 查看当前 tools 修改规则客户端零改动:把 base_url 指向本代理即可(官方 openai SDK 无缝接入),代理会把请求里的 tools 按规则修改后再转发上游。
from openai import OpenAI
# 代理地址(.env 中 HOST/PORT 默认 127.0.0.1:8000)
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="占位即可")
resp = client.responses.create(
model="gpt-4o-mini",
input=[{"role": "user", "content": "查一下今天的 AI 新闻"}],
tools=[...], # 客户端实际发送的工具(Responses API 平铺格式);代理会按规则过滤/改名/注入/藏起
tool_choice="auto",
)
完整示例见 examples/client_responses.py(含流式与非流式两种调用)。
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /v1/responses | 工具调用代理(Responses API):修改 tools → 转发上游 /v1/responses → 对外只渲染最终一跳(流式 SSE / 非流式 JSON 均支持) |
| GET | /v1/models | 透传上游模型列表(方便 SDK 探测) |
ADMIN_API_KEY 鉴权)| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/tools/rules | 查看当前 tools 修改规则 |
| PUT | /api/tools/rules | 整体替换规则({"rules": {...}}),立即生效并落盘 |
| PATCH | /api/tools/rules | 局部更新规则(深度合并,{"patch": {...}}) |
| POST | /api/tools/reload | 从磁盘重载 config/tools_rules.json 与"工具大全"目录 |
| POST | /api/tools/apply | dry-run:传入任意请求体,返回 tools 被修改前后的对比与最终请求体,不真正转发 |
| GET | /api/audit | 最近 N 条修改审计(logs/audit.jsonl) |
apply 示例(不连上游,直接看改了什么):
curl -X POST http://127.0.0.1:8000/api/tools/apply \
-H "Content-Type: application/json" \
-d '{"request_body": {"model": "gpt-4o-mini", "messages": [], "tools": [{"type":"function","function":{"name":"delete_all","description":"删库","parameters":{"type":"object","properties":{}}}}]}}'
返回 original_tools / modified_tools / operations / final_request 的完整对比。
完整示例见 examples/admin_api_demo.py。
规则按以下顺序应用:
| 规则 | 说明 | 示例 |
|---|---|---|
deny | 黑名单过滤(支持 *、前缀* 通配) | ["delete_*", "dangerous_tool_*"] |
allow | 白名单;配置后只保留名单内工具 | ["get_weather"] |
rename | 重命名(改名后的名字参与后续所有步骤) | {"search_web": "web_search_v2"} |
description_override | 覆盖描述;或 {"description":…, "append":…} 覆盖并追加 | {"web_search_v2": "搜索互联网。"} |
parameters_patch | 深度合并 JSON Schema(properties 逐字段合并) | {"get_weather": {"properties": {"unit": {...}}}} |
drop_fields | 删除 function 内指定字段(如 strict) | ["strict"] |
inject | 注入自定义工具(同名自动去重) | 完整 tool 对象数组 |
tool_choice | 强制 none/auto/required/指定工具;null 表示删除客户端传值 | "auto" |
parallel_tool_calls | true/false;null 表示删除 | false |
calltool | 藏工具(核心省 token 功能):把"工具大全"里的工具藏起来,换成唯一的 call_tool 选择工具(见下) | {"enabled": true, "catalog_file": "benchmark/TOOLS_API_unique.json"} |
自适应处理:若过滤后工具为空,自动移除 tools/tool_choice,避免上游 4xx;若 tool_choice 指定的工具被过滤掉,自动降级为 auto 并记录审计。
开启后,凡是出现在"工具大全"(默认 benchmark/TOOLS_API_unique.json,73 个工具)里的工具都会被代理藏起来,避免模型面对几十个工具定义烧 token 又挑花眼:
call_tool 小工具。
call_tool 的参数是 names(字符串数组,一次可传多个工具名),描述为英文,
引导模型"选择所有与请求相关的工具"(Select ALL tools that are relevant to the user's request;除非任务明显只与某一个工具紧密相关才单选),并在描述里逐条列出
本次请求中被藏工具的工具名(按名字去重、只列名字——不含描述、不含参数,默认不截断)。
不在大全里的自定义工具保持原样、模型可直接调用。call_tool({"names": [...]}) 选好一个或多个工具后,
代理在单个续跳里一次性把全部选中工具的完整定义亮给模型(tools 里只有这些
真实工具、没有 call_tool),并自动补齐 call_tool 的执行结果:
Please pick one or more tools you need to call from the detailed tool list. If necessary, a single tool may be called multiple times.
模型在该轮直接发起所有需要的真实调用(可并行多条、可同一工具多次调用)。
若模型填的名字不在此次请求中(乱编),则维持打包状态让模型重选(上限 max_continue_rounds,超限返回 422)。call_tool,代理会重新打包。整个判断只看"这次请求聊天记录里最近的
一次模型工具调用",不保存任何服务器状态,可随意重启、多开。配置项(挂在 rules["calltool"] 下):
| 配置 | 默认 | 说明 |
|---|---|---|
enabled | false | 总开关 |
catalog_file | benchmark/TOOLS_API_unique.json | "工具大全"文件(相对项目根或绝对路径) |
call_tool_name | call_tool | 选择工具的名字 |
param_name | name | 兼容回退键:旧格式 {"name": "X"} 仍可解析;新格式主参数是 names(数组) |
result_message | Please pick one or more tools you need to call from the detailed tool list. If necessary, a single tool may be called multiple times. | 选择工具的"执行结果"文案 |
override_client_result | true | 客户端已传选择结果时是否用上述文案覆盖 |
list_all_in_request | false | true 时列出本次请求全部工具;false 只列被藏的 |
summary_limit_chars | (不限制) | 第一跳工具名列表总字数上限;默认不设置 = 被藏工具的工具名全部列出、不做任何截断;显式设置后超长截断并注明省略 |
auto_continue | true | 代理恒为接管模式:模型调 call_tool 后,代理在服务器端自动续跑下一轮,对外只返回真实工具调用,客户端无需配合。该配置保留仅为兼容,实际恒开启 |
max_continue_rounds | 3 | 接管模式下内部续跳上限;模型连续多轮只调 call_tool 不收敛 → 返回 422 tool_selection_not_converged |
hop_thinking | off | 续跳思考等级:off=续跳请求 reasoning:{"effort":"none"}(responses 模式实测完全关闭思考,0 reasoning tokens);low/medium/high=对应 effort;inherit=保持客户端。只影响内部续跳——第一跳保留思考、不做任何额外限制(不关、不限长),其他请求保持客户端等级 |
一键启用:把 config/tools_rules_calltool_demo.json 的内容 PUT 到 /api/tools/rules
(或直接编辑 config/tools_rules.json 把 calltool.enabled 改为 true 后
POST /api/tools/reload)。
模型可以在第一跳用一条 call_tool 调用传入多个工具名({"names": ["A", "B"]},
也可发多条调用)。代理会:
get_current_weather,一条续跳里发两次)。function_call 合并为对外一条 Responses API 响应
(output 里的 function_call 项,usage 累加;流式同样以 Responses API SSE 事件输出)。names 里重复的名字只亮出一次。仅当所选名字全部无效(乱编)时
维持打包态让模型重选。代理只支持 Responses API:客户端与上游都是 /v1/responses 格式。上游需要支持
自定义 tools 与 reasoning 关思考(如 LM Studio /v1/responses;OpenAI /v1/responses 亦可)。
说明:续跳请求默认不回传历史(输入 = 客户端原始消息,上下文干净);代理对外 始终渲染"最终一跳",中间过程只写审计,不泄漏给客户端。
python -m pytest tests/ -v # 规则引擎 / 目录 / 转发层纯逻辑单测,无网络依赖
另有 mock 上游集成冒烟测试(scripts/mock_upstream.py + scripts/smoke_test.py 等)与
BFCL 跑分脚本(benchmark/,见 benchmark/USE.md)。
| 变量 | 默认 | 说明 |
|---|---|---|
UPSTREAM_BASE_URL | http://127.0.0.1:1234/v1 | 上游地址(需支持 /v1/responses,如 LM Studio) |
UPSTREAM_API_KEY | 空 | 上游 Key;留空则把客户端 Authorization 透传上游 |
PROXY_API_KEY | 空 | 设置后客户端访问 /v1/* 需带 Bearer <该值> |
ADMIN_API_KEY | 空 | 设置后访问 /api/* 需带 Bearer <该值> |
HOST / PORT | 127.0.0.1 / 8000 | 监听地址 |
RULES_FILE | config/tools_rules.json | 规则文件路径 |
AUDIT_FILE | logs/audit.jsonl | 审计日志路径 |
CATALOG_FILE | benchmark/TOOLS_API_unique.json | "工具大全"文件(calltool 藏工具用) |
UPSTREAM_TIMEOUT | 1200 | 上游请求超时(秒),本地模型排队时给足 |
function_call.name 是修改后的名字,
客户端按「代理暴露的工具集」(可用 /api/tools/apply 预览)来分派即可,无需关心原始名。tools_in → tools_out 与全部操作,方便排查「上游到底收到了什么」。在 BFCL v4 题组(15 题 = multiple×5 + parallel_multiple×4 + live_multiple×4 + live_parallel_multiple×2)上,
用本机 LM Studio(http://127.0.0.1:1234,Gemma 4) 实测「直连」vs「经代理(calltool 藏工具 + 接管 + 续跳关思考)」
各一轮(直连 capture_direct/run3、代理 capture_proxy/run7):
| 指标 | 直连 | 经代理 | 变化 |
|---|---|---|---|
| BFCL 正确率 | 13/15 (86.7%) | 14/15 (93.3%) | +1 题(+6.7pp) |
| 错题 | parallel_multiple_9(少发调用)、live_multiple_5-3-0(参数缺 , China) | 仅 live_parallel_multiple_1-1-0(中文参数 '广州, 中国' 应为 'Guangzhou, China') | 直连 2 错全被代理纠正,错题不重叠 |
直连的两处稳定错误(并行调用个数、地名格式)经代理「藏工具 → 按 call_tool 逐步选定 → 续跳精确传参」后均被纠正; 代理唯一错题为中文题干下的参数取值问题,属模型侧取值细节。
| 指标 | 直连 | 经代理 | 变化 |
|---|---|---|---|
| 输入 token | 12,037 | 6,957 | -42.2%(藏工具收益) |
| 输出 token | 2,097 | 2,911 | +38.8%(两跳累计) |
| 总 token | 14,134 | 9,868 | -30.2% |
| 思考 token | 2,097 | 2,308 | +10.1% |
费用模型:令输入单价 = 输出单价 × R,代理总成本更低 ⇔ R > 输出增量/输入节省量(本组实测门槛 R ≈ 0.16)。
即只要输入单价 ≥ 输出单价的 0.16 倍(绝大多数 API 定价都满足),经代理的总调用成本即低于直连:
按 R=1.0(输入:输出 = 1:1)计价约省 30%,R=0.5 约省 21%,R=2.0 约省 36%(输入越贵,藏工具省输入的价值越大)。
成本优势的核心来自大工具集场景:如 37 工具题输入 token 从 5,264 → 652(-88%);排除该题后门槛升至 1.62 倍。代理输出增加部分中,思考 token 仅占约 1/4(非思考的续跳正式调用输出占约 3/4)。
# 前置:LM Studio 已在 1234 加载 google/gemma-4-12b-qat
# 跑分(直连一轮 + 代理一轮):
pwsh -File benchmark\re_run_capture.ps1 -Way capture_direct -Round 3 -Model local/gemma-4-12b-qat-FC-RESP
pwsh -File benchmark\re_run_capture.ps1 -Way capture_proxy -Round 7 -UseProxy -Model local/gemma-4-12b-qat-FC
# 一键对比两侧最新有效结果(自动跳过失败 run,生成 HTML/MD 报告 + 逐题详情):
pwsh -File benchmark\compare_latest.ps1
完整逐题对比报告(含思考全文对比、工具定义、每题"排除"联动统计、成本计算器)见
benchmark/runs/compares/compare_capture_direct_run3_vs_capture_proxy_run7.html(同目录 .md 摘要;
逐题详情在 details_* 子目录)。代理转发正确性冒烟测试(过滤/重命名/注入、SSE)可用
scripts\run_live_proxy.ps1 一键复跑(检查 1234 → 启动代理 → 跑全部断言 → 清理)。
app/
main.py FastAPI 应用:代理端点(POST /v1/responses)+ tools 管理接口
rules_engine.py tools 修改规则引擎(核心,含 calltool 藏工具阶段)
catalog.py "工具大全"索引(calltool 用,只出工具名列表)
upstream.py 上游转发(非流式)
config.py 配置加载 + 规则文件读写
audit.py 审计日志
toolcall_relay.py calltool 接管循环的纯辅助函数
responses_relay.py /v1/responses 转发层(唯一协议:客户端与上游均为该格式)
config/tools_rules.json 默认规则(calltool 默认关闭)
config/tools_rules_calltool_demo.json calltool 一键启用示例
benchmark/ 工具大全数据与 BFCL 题库/跑分(见 benchmark/USE.md)
examples/ 演示:SDK 客户端(responses)+ 管理接口
scripts/ mock 上游与冒烟测试
tests/ 单元测试
28 commits
Python
98.1%
PowerShell
1.8%
一个轻量的 AI 代理(Python / FastAPI)。核心用途:压缩发给模型的工具信息,减少 token 消耗,同时提升工具调用的性能与准确率。 *目前仅支持 Responses API(/v1/responses)格式——客户端与上游均为该格式(如 LM Studio /v1/responses)。
工具调用类任务的输入开销主要来自 tools 字段:每把工具都要携带完整的 description 和 parameters(JSON Schema)。当工具数量达到几十上百个时:
TSTP 的做法是 两跳式工具选择(calltool 藏工具):
benchmark/TOOLS_API_unique.json,73 个工具)里的工具全部藏起来,只给模型一个 call_tool 选择工具。call_tool 的描述里只列出工具名(不含描述、不含参数),提示词引导模型一次选出所有与请求相关的工具(可多选;除非任务明显只与某一个工具紧密相关,才单选)——第一跳的输入开销被压到最低;call_tool 等中间过程,客户端零改动。效果:输入 token 大幅下降(不再每次携带全部工具 schema),且模型每次只聚焦于相关工具的完整定义,选择更准、调用更稳。第一跳还能用 summary_limit_chars 进一步压缩。
# 1) 安装依赖(建议虚拟环境)
python -m venv .venv
.venv\Scripts\activate # Windows;Linux/macOS: source .venv/bin/activate
pip install -r requirements.txt
# 2) 配置上游
copy .env.example .env # Windows;Linux/macOS: cp .env.example .env
# 编辑 .env:填 UPSTREAM_BASE_URL(默认 https://api.openai.com/v1)和 UPSTREAM_API_KEY
# 3) 启动
python run.py # 默认 http://127.0.0.1:8000
启动后可访问:
http://127.0.0.1:8000/docs — Swagger 交互文档http://127.0.0.1:8000/api/tools/rules — 查看当前 tools 修改规则客户端零改动:把 base_url 指向本代理即可(官方 openai SDK 无缝接入),代理会把请求里的 tools 按规则修改后再转发上游。
from openai import OpenAI
# 代理地址(.env 中 HOST/PORT 默认 127.0.0.1:8000)
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="占位即可")
resp = client.responses.create(
model="gpt-4o-mini",
input=[{"role": "user", "content": "查一下今天的 AI 新闻"}],
tools=[...], # 客户端实际发送的工具(Responses API 平铺格式);代理会按规则过滤/改名/注入/藏起
tool_choice="auto",
)
完整示例见 examples/client_responses.py(含流式与非流式两种调用)。
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /v1/responses | 工具调用代理(Responses API):修改 tools → 转发上游 /v1/responses → 对外只渲染最终一跳(流式 SSE / 非流式 JSON 均支持) |
| GET | /v1/models | 透传上游模型列表(方便 SDK 探测) |
ADMIN_API_KEY 鉴权)| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/tools/rules | 查看当前 tools 修改规则 |
| PUT | /api/tools/rules | 整体替换规则({"rules": {...}}),立即生效并落盘 |
| PATCH | /api/tools/rules | 局部更新规则(深度合并,{"patch": {...}}) |
| POST | /api/tools/reload | 从磁盘重载 config/tools_rules.json 与"工具大全"目录 |
| POST | /api/tools/apply | dry-run:传入任意请求体,返回 tools 被修改前后的对比与最终请求体,不真正转发 |
| GET | /api/audit | 最近 N 条修改审计(logs/audit.jsonl) |
apply 示例(不连上游,直接看改了什么):
curl -X POST http://127.0.0.1:8000/api/tools/apply \
-H "Content-Type: application/json" \
-d '{"request_body": {"model": "gpt-4o-mini", "messages": [], "tools": [{"type":"function","function":{"name":"delete_all","description":"删库","parameters":{"type":"object","properties":{}}}}]}}'
返回 original_tools / modified_tools / operations / final_request 的完整对比。
完整示例见 examples/admin_api_demo.py。
规则按以下顺序应用:
| 规则 | 说明 | 示例 |
|---|---|---|
deny | 黑名单过滤(支持 *、前缀* 通配) | ["delete_*", "dangerous_tool_*"] |
allow | 白名单;配置后只保留名单内工具 | ["get_weather"] |
rename | 重命名(改名后的名字参与后续所有步骤) | {"search_web": "web_search_v2"} |
description_override | 覆盖描述;或 {"description":…, "append":…} 覆盖并追加 | {"web_search_v2": "搜索互联网。"} |
parameters_patch | 深度合并 JSON Schema(properties 逐字段合并) | {"get_weather": {"properties": {"unit": {...}}}} |
drop_fields | 删除 function 内指定字段(如 strict) | ["strict"] |
inject | 注入自定义工具(同名自动去重) | 完整 tool 对象数组 |
tool_choice | 强制 none/auto/required/指定工具;null 表示删除客户端传值 | "auto" |
parallel_tool_calls | true/false;null 表示删除 | false |
calltool | 藏工具(核心省 token 功能):把"工具大全"里的工具藏起来,换成唯一的 call_tool 选择工具(见下) | {"enabled": true, "catalog_file": "benchmark/TOOLS_API_unique.json"} |
自适应处理:若过滤后工具为空,自动移除 tools/tool_choice,避免上游 4xx;若 tool_choice 指定的工具被过滤掉,自动降级为 auto 并记录审计。
开启后,凡是出现在"工具大全"(默认 benchmark/TOOLS_API_unique.json,73 个工具)里的工具都会被代理藏起来,避免模型面对几十个工具定义烧 token 又挑花眼:
call_tool 小工具。
call_tool 的参数是 names(字符串数组,一次可传多个工具名),描述为英文,
引导模型"选择所有与请求相关的工具"(Select ALL tools that are relevant to the user's request;除非任务明显只与某一个工具紧密相关才单选),并在描述里逐条列出
本次请求中被藏工具的工具名(按名字去重、只列名字——不含描述、不含参数,默认不截断)。
不在大全里的自定义工具保持原样、模型可直接调用。call_tool({"names": [...]}) 选好一个或多个工具后,
代理在单个续跳里一次性把全部选中工具的完整定义亮给模型(tools 里只有这些
真实工具、没有 call_tool),并自动补齐 call_tool 的执行结果:
Please pick one or more tools you need to call from the detailed tool list. If necessary, a single tool may be called multiple times.
模型在该轮直接发起所有需要的真实调用(可并行多条、可同一工具多次调用)。
若模型填的名字不在此次请求中(乱编),则维持打包状态让模型重选(上限 max_continue_rounds,超限返回 422)。call_tool,代理会重新打包。整个判断只看"这次请求聊天记录里最近的
一次模型工具调用",不保存任何服务器状态,可随意重启、多开。配置项(挂在 rules["calltool"] 下):
| 配置 | 默认 | 说明 |
|---|---|---|
enabled | false | 总开关 |
catalog_file | benchmark/TOOLS_API_unique.json | "工具大全"文件(相对项目根或绝对路径) |
call_tool_name | call_tool | 选择工具的名字 |
param_name | name | 兼容回退键:旧格式 {"name": "X"} 仍可解析;新格式主参数是 names(数组) |
result_message | Please pick one or more tools you need to call from the detailed tool list. If necessary, a single tool may be called multiple times. | 选择工具的"执行结果"文案 |
override_client_result | true | 客户端已传选择结果时是否用上述文案覆盖 |
list_all_in_request | false | true 时列出本次请求全部工具;false 只列被藏的 |
summary_limit_chars | (不限制) | 第一跳工具名列表总字数上限;默认不设置 = 被藏工具的工具名全部列出、不做任何截断;显式设置后超长截断并注明省略 |
auto_continue | true | 代理恒为接管模式:模型调 call_tool 后,代理在服务器端自动续跑下一轮,对外只返回真实工具调用,客户端无需配合。该配置保留仅为兼容,实际恒开启 |
max_continue_rounds | 3 | 接管模式下内部续跳上限;模型连续多轮只调 call_tool 不收敛 → 返回 422 tool_selection_not_converged |
hop_thinking | off | 续跳思考等级:off=续跳请求 reasoning:{"effort":"none"}(responses 模式实测完全关闭思考,0 reasoning tokens);low/medium/high=对应 effort;inherit=保持客户端。只影响内部续跳——第一跳保留思考、不做任何额外限制(不关、不限长),其他请求保持客户端等级 |
一键启用:把 config/tools_rules_calltool_demo.json 的内容 PUT 到 /api/tools/rules
(或直接编辑 config/tools_rules.json 把 calltool.enabled 改为 true 后
POST /api/tools/reload)。
模型可以在第一跳用一条 call_tool 调用传入多个工具名({"names": ["A", "B"]},
也可发多条调用)。代理会:
get_current_weather,一条续跳里发两次)。function_call 合并为对外一条 Responses API 响应
(output 里的 function_call 项,usage 累加;流式同样以 Responses API SSE 事件输出)。names 里重复的名字只亮出一次。仅当所选名字全部无效(乱编)时
维持打包态让模型重选。代理只支持 Responses API:客户端与上游都是 /v1/responses 格式。上游需要支持
自定义 tools 与 reasoning 关思考(如 LM Studio /v1/responses;OpenAI /v1/responses 亦可)。
说明:续跳请求默认不回传历史(输入 = 客户端原始消息,上下文干净);代理对外 始终渲染"最终一跳",中间过程只写审计,不泄漏给客户端。
python -m pytest tests/ -v # 规则引擎 / 目录 / 转发层纯逻辑单测,无网络依赖
另有 mock 上游集成冒烟测试(scripts/mock_upstream.py + scripts/smoke_test.py 等)与
BFCL 跑分脚本(benchmark/,见 benchmark/USE.md)。
| 变量 | 默认 | 说明 |
|---|---|---|
UPSTREAM_BASE_URL | http://127.0.0.1:1234/v1 | 上游地址(需支持 /v1/responses,如 LM Studio) |
UPSTREAM_API_KEY | 空 | 上游 Key;留空则把客户端 Authorization 透传上游 |
PROXY_API_KEY | 空 | 设置后客户端访问 /v1/* 需带 Bearer <该值> |
ADMIN_API_KEY | 空 | 设置后访问 /api/* 需带 Bearer <该值> |
HOST / PORT | 127.0.0.1 / 8000 | 监听地址 |
RULES_FILE | config/tools_rules.json | 规则文件路径 |
AUDIT_FILE | logs/audit.jsonl | 审计日志路径 |
CATALOG_FILE | benchmark/TOOLS_API_unique.json | "工具大全"文件(calltool 藏工具用) |
UPSTREAM_TIMEOUT | 1200 | 上游请求超时(秒),本地模型排队时给足 |
function_call.name 是修改后的名字,
客户端按「代理暴露的工具集」(可用 /api/tools/apply 预览)来分派即可,无需关心原始名。tools_in → tools_out 与全部操作,方便排查「上游到底收到了什么」。在 BFCL v4 题组(15 题 = multiple×5 + parallel_multiple×4 + live_multiple×4 + live_parallel_multiple×2)上,
用本机 LM Studio(http://127.0.0.1:1234,Gemma 4) 实测「直连」vs「经代理(calltool 藏工具 + 接管 + 续跳关思考)」
各一轮(直连 capture_direct/run3、代理 capture_proxy/run7):
| 指标 | 直连 | 经代理 | 变化 |
|---|---|---|---|
| BFCL 正确率 | 13/15 (86.7%) | 14/15 (93.3%) | +1 题(+6.7pp) |
| 错题 | parallel_multiple_9(少发调用)、live_multiple_5-3-0(参数缺 , China) | 仅 live_parallel_multiple_1-1-0(中文参数 '广州, 中国' 应为 'Guangzhou, China') | 直连 2 错全被代理纠正,错题不重叠 |
直连的两处稳定错误(并行调用个数、地名格式)经代理「藏工具 → 按 call_tool 逐步选定 → 续跳精确传参」后均被纠正; 代理唯一错题为中文题干下的参数取值问题,属模型侧取值细节。
| 指标 | 直连 | 经代理 | 变化 |
|---|---|---|---|
| 输入 token | 12,037 | 6,957 | -42.2%(藏工具收益) |
| 输出 token | 2,097 | 2,911 | +38.8%(两跳累计) |
| 总 token | 14,134 | 9,868 | -30.2% |
| 思考 token | 2,097 | 2,308 | +10.1% |
费用模型:令输入单价 = 输出单价 × R,代理总成本更低 ⇔ R > 输出增量/输入节省量(本组实测门槛 R ≈ 0.16)。
即只要输入单价 ≥ 输出单价的 0.16 倍(绝大多数 API 定价都满足),经代理的总调用成本即低于直连:
按 R=1.0(输入:输出 = 1:1)计价约省 30%,R=0.5 约省 21%,R=2.0 约省 36%(输入越贵,藏工具省输入的价值越大)。
成本优势的核心来自大工具集场景:如 37 工具题输入 token 从 5,264 → 652(-88%);排除该题后门槛升至 1.62 倍。代理输出增加部分中,思考 token 仅占约 1/4(非思考的续跳正式调用输出占约 3/4)。
# 前置:LM Studio 已在 1234 加载 google/gemma-4-12b-qat
# 跑分(直连一轮 + 代理一轮):
pwsh -File benchmark\re_run_capture.ps1 -Way capture_direct -Round 3 -Model local/gemma-4-12b-qat-FC-RESP
pwsh -File benchmark\re_run_capture.ps1 -Way capture_proxy -Round 7 -UseProxy -Model local/gemma-4-12b-qat-FC
# 一键对比两侧最新有效结果(自动跳过失败 run,生成 HTML/MD 报告 + 逐题详情):
pwsh -File benchmark\compare_latest.ps1
完整逐题对比报告(含思考全文对比、工具定义、每题"排除"联动统计、成本计算器)见
benchmark/runs/compares/compare_capture_direct_run3_vs_capture_proxy_run7.html(同目录 .md 摘要;
逐题详情在 details_* 子目录)。代理转发正确性冒烟测试(过滤/重命名/注入、SSE)可用
scripts\run_live_proxy.ps1 一键复跑(检查 1234 → 启动代理 → 跑全部断言 → 清理)。
app/
main.py FastAPI 应用:代理端点(POST /v1/responses)+ tools 管理接口
rules_engine.py tools 修改规则引擎(核心,含 calltool 藏工具阶段)
catalog.py "工具大全"索引(calltool 用,只出工具名列表)
upstream.py 上游转发(非流式)
config.py 配置加载 + 规则文件读写
audit.py 审计日志
toolcall_relay.py calltool 接管循环的纯辅助函数
responses_relay.py /v1/responses 转发层(唯一协议:客户端与上游均为该格式)
config/tools_rules.json 默认规则(calltool 默认关闭)
config/tools_rules_calltool_demo.json calltool 一键启用示例
benchmark/ 工具大全数据与 BFCL 题库/跑分(见 benchmark/USE.md)
examples/ 演示:SDK 客户端(responses)+ 管理接口
scripts/ mock 上游与冒烟测试
tests/ 单元测试
28 commits
Python
98.1%
PowerShell
1.8%