1. Iris 397B 被调起时,先看 Search Agent 的 OpenAI 兼容端点
当 Iris 397B 被 Search Agent 调起,评测脚本里最先暴露的问题通常不是权重加载,而是 OpenAI 兼容端点没有回填 Key:本地 Harness 能启动,搜索规划一发起请求就 401。先把供应商切到 TaoToken,在官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=iris_eval_intro 创建 Key,再把 OpenAI 兼容base_url写成https://taotoken.net/api。这样 Search Agent 的搜索规划、工具调用参数生成、答案汇总都能走同一条 API 路径。
AllSpark 团队开源了 Search Agent 模型 Iris,公开了 35B 与 397B 两个规格,并放出权重与评测入口。外部热点值得关注,但落到本地可复现评测,真正要处理的是三件事:第一,评测 Harness 从哪里读 API Key;第二,Search Agent 在哪些阶段会发起模型调用;第三,日志里如何确认 Token 消耗来自搜索规划还是答案汇总。本文不写新闻评论,直接按接入、排障、配置、日志对照的顺序拆开。
建议你先明确评测目标。如果只是验证 Search Agent 控制流能否跑通,用最小数据集跑 3 到 5 条问题即可;如果要对照 Iris 35B 与 397B 的表现,则需要固定数据集、固定温度、固定最大输出长度,并把每次调用的usage写入 JSONL。所有命令都在本地终端执行,不要连接生产数据库,也不要把评测脚本挂到线上关键服务上。
2. 在 TaoToken 拿 Key 并确认 Base URL:控制台、环境变量、Smoke Test
第一步不是改代码,而是拿到可用的 Key。打开 TaoToken 官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=iris_eval_get_key ,进入控制台后创建 API Key。创建时建议按用途命名,例如iris-search-agent-eval,方便后续轮换。不要把 Key 写进 Git 仓库,也不要把 Key 贴到日志开头。推荐使用环境变量注入。
export TAOTOKEN_API_KEY="YOUR_API_KEY" export OPENAI_API_KEY="$TAOTOKEN_API_KEY" export OPENAI_BASE_URL="https://taotoken.net/api"OpenAI 兼容调用里,base_url使用产品提供的 API 地址,不带 UTM 参数。注意区分:官网页面链接用于注册、查看文档和创建 Key;工具配置里的 Base URL 只用https://taotoken.net/api。如果你把带查询参数的官网地址填进 SDK,通常会导致请求路径异常。
先跑一个最小 Smoke Test,确认 Key、Base URL、模型 ID 三者匹配。下面示例使用 OpenAI Python SDK,模型 ID 请替换为你在 TaoToken 控制台或模型列表中可用的实际值。
import os from openai import OpenAI client = OpenAI( api_key=os.environ["OPENAI_API_KEY"], base_url=os.environ["OPENAI_BASE_URL"], ) resp = client.chat.completions.create( model="YOUR_MODEL_ID", messages=[ {"role": "system", "content": "你是一个搜索规划助手,只输出下一步搜索动作。"}, {"role": "user", "content": "帮我规划:如何评测一个 Search Agent 的答案汇总能力?"}, ], temperature=0.2, max_tokens=256, ) print(resp.choices[0].message.content) print(resp.usage)如果这里报 401,优先检查 Key 是否有多余空格、是否把YOUR_API_KEY原样带入。如果报 404,检查base_url是否误写成官网地址、是否多加了路径。如果返回空内容,检查模型 ID 是否正确、max_tokens是否过小。Smoke Test 通过后,再把同样的环境变量带进 Iris 评测脚本。
3. 本地评测目录怎么摆:把 Iris 评测脚本的 endpoint 指向 TaoToken
Search Agent 评测通常不是一个单轮问答,而是“问题输入 -> 搜索规划 -> 工具调用 -> 结果读取 -> 答案汇总”的链路。Iris 35B/397B 作为 Search Agent 模型,被调起时可能参与规划与汇总。你要做的是把评测 Harness 中所有 OpenAI 兼容请求统一指向 TaoToken,而不是只改其中一个函数。
建议目录结构如下:
iris-eval/ ├── data/ │ └── search_eval.jsonl ├── logs/ │ └── iris_eval_YYYYMMDD.jsonl ├── eval_iris_smoke.py ├── eval_iris_batch.py └── .env.example.env.example只写占位符,不写真实 Key:
TAOTOKEN_API_KEY=YOUR_API_KEY OPENAI_BASE_URL=https://taotoken.net/api EVAL_MODEL=YOUR_MODEL_ID EVAL_CONCURRENCY=1评测脚本里不要硬编码供应商地址。推荐统一从环境变量读取。下面是一个最小可运行的eval_iris_smoke.py,它把搜索规划与答案汇总分成两个阶段,并分别记录usage。
import os import json import time from datetime import datetime from openai import OpenAI client = OpenAI( api_key=os.environ["OPENAI_API_KEY"], base_url=os.environ["OPENAI_BASE_URL"], ) MODEL = os.environ.get("EVAL_MODEL", "YOUR_MODEL_ID") LOG_PATH = os.environ.get("EVAL_LOG", "logs/iris_eval_smoke.jsonl") def call_model(stage, messages, temperature=0.2, max_tokens=512): start = time.time() resp = client.chat.completions.create( model=MODEL, messages=messages, temperature=temperature, max_tokens=max_tokens, ) latency_ms = int((time.time() - start) * 1000) record = { "ts": datetime.utcnow().isoformat(), "stage": stage, "model": MODEL, "latency_ms": latency_ms, "finish_reason": resp.choices[0].finish_reason, "usage": resp.usage.model_dump() if resp.usage else None, "content_preview": resp.choices[0].message.content[:200], } with open(LOG_PATH, "a", encoding="utf-8") as f: f.write(json.dumps(record, ensure_ascii=False) + "\n") return resp.choices[0].message.content def run_one(question): plan = call_model( "search_plan", [ {"role": "system", "content": "你是搜索规划器。输出需要检索的关键词与步骤,不要直接回答。"}, {"role": "user", "content": question}, ], max_tokens=256, ) answer = call_model( "answer_summary", [ {"role": "system", "content": "你是答案汇总器。根据给定问题与搜索规划,给出简洁结论。"}, {"role": "user", "content": f"问题:{question}\n搜索规划:{plan}"}, ], max_tokens=512, ) return {"question": question, "plan": plan, "answer": answer} if __name__ == "__main__": os.makedirs("logs", exist_ok=True) result = run_one("Search Agent 评测中,为什么要区分搜索规划与答案汇总的 Token 消耗?") print(json.dumps(result, ensure_ascii=False, indent=2))运行前确保目录存在:
mkdir -p logs data python eval_iris_smoke.py如果 Iris 仓库提供了官方评测命令,做法相同:在 README 给出的环境变量或配置文件里,把 OpenAI 兼容 endpoint 改为https://taotoken.net/api,把 Key 回填为YOUR_API_KEY,模型名按控制台可用 ID 填写。不要改动评测集本身,也不要为了跑通而伪造工具返回。
4. 跑通最小评测命令:一次搜索规划与一次答案汇总
最小评测的目标不是刷榜,而是确认链路闭环。你需要观察四件事:请求是否成功、日志是否落盘、usage是否非空、搜索规划与答案汇总是否被分开记录。下面给出一个批量评测入口示例,它读取 JSONL 数据集,逐条调用,并保持并发为 1,避免一开始就触发限流。
import os import json from eval_iris_smoke import run_one def load_questions(path): with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue yield json.loads(line) def main(): dataset_path = os.environ.get("DATASET", "data/search_eval.jsonl") out_path = os.environ.get("OUTPUT", "logs/iris_eval_batch.jsonl") os.makedirs(os.path.dirname(out_path), exist_ok=True) with open(out_path, "w", encoding="utf-8") as out: for item in load_questions(dataset_path): question = item.get("question") or item.get("query") if not question: continue try: result = run_one(question) result["id"] = item.get("id") out.write(json.dumps(result, ensure_ascii=False) + "\n") out.flush() print(f"[OK] {item.get('id')}") except Exception as e: err = {"id": item.get("id"), "error": str(e), "question": question} out.write(json.dumps(err, ensure_ascii=False) + "\n") out.flush() print(f"[ERR] {item.get('id')} -> {e}") if __name__ == "__main__": main()评测命令示例:
export OPENAI_API_KEY="$TAOTOKEN_API_KEY" export OPENAI_BASE_URL="https://taotoken.net/api" export EVAL_MODEL="YOUR_MODEL_ID" export DATASET="data/search_eval.jsonl" export OUTPUT="logs/iris_eval_batch.jsonl" python eval_iris_batch.py跑完后先看日志行数是否等于数据集条数。再看每行是否都有usage.total_tokens。如果某一行只有error,先解决报错,不要急着对比 Iris 35B 与 397B 的效果。Search Agent 的评测质量高度依赖链路稳定性,失败请求混进统计会直接污染结论。
如果你要对照 35B 与 397B,建议固定同一份数据集、同一套 prompt、同一个temperature和max_tokens。模型规格不同,Token 消耗和延迟会明显不同,但评测口径必须一致。不要在一次批量任务里同时改模型、改提示词、改并发,否则日志无法归因。
5. 日志对照:从 usage 字段判断 Token 花在规划还是汇总
Search Agent 消耗 Token 的大头通常在搜索规划与答案汇总。搜索规划可能多轮发生,每轮都要带上一部分上下文;答案汇总则可能把多个检索片段拼进 prompt。只看总 Token 不够,最好按stage拆开。
一条可对照的日志长这样:
{ "ts": "2025-01-01T10:00:00.000000", "stage": "search_plan", "model": "YOUR_MODEL_ID", "latency_ms": 842, "finish_reason": "stop", "usage": { "prompt_tokens": 312, "completion_tokens": 96, "total_tokens": 408 }, "content_preview": "关键词:Search Agent 评测、搜索规划、答案汇总..." }另一条answer_summary可能 prompt_tokens 更高,因为要把问题和规划一起带入:
{ "ts": "2025-01-01T10:00:01.000000", "stage": "answer_summary", "model": "YOUR_MODEL_ID", "latency_ms": 1260, "finish_reason": "stop", "usage": { "prompt_tokens": 680, "completion_tokens": 210, "total_tokens": 890 }, "content_preview": "结论:评测 Search Agent 时应拆分规划与汇总的 Token..." }对照时重点看四点:
search_plan的completion_tokens是否异常高。如果规划阶段输出很长,可能是系统提示没有约束“只输出检索步骤”。answer_summary的prompt_tokens是否持续膨胀。如果每轮都把历史搜索片段全量拼入,汇总阶段会快速吃掉 Token。finish_reason是否为length。如果是,说明输出被截断,答案可能不完整,需要调整max_tokens或精简 prompt。latency_ms是否稳定。如果规划阶段延迟波动很大,先降低并发,再排查网络与模型侧队列。
你可以用一条命令快速统计日志里的 Token 分布:
python - <<'PY' import json from collections import defaultdict stats = defaultdict(lambda: {"count": 0, "prompt": 0, "completion": 0, "total": 0}) with open("logs/iris_eval_batch.jsonl", "r", encoding="utf-8") as f: for line in f: try: row = json.loads(line) except json.JSONDecodeError: continue if "error" in row: stats["error"]["count"] += 1 continue # 这里假设 run_one 内部写入 smoke 日志;批量结果可按需合并 usage = row.get("usage") if usage: stage = row.get("stage", "unknown") stats[stage]["count"] += 1 stats[stage]["prompt"] += usage.get("prompt_tokens", 0) stats[stage]["completion"] += usage.get("completion_tokens", 0) stats[stage]["total"] += usage.get("total_tokens", 0) for stage, s in stats.items(): print(stage, s) PY如果日志里完全没有usage,先确认响应对象是否被流式处理截断,或者 SDK 版本是否兼容。不要把没有usage的调用计入 Token 对比。对于 Search Agent 这类多阶段任务,建议把trace_id也写进日志,确保同一条问题的规划和汇总可以关联起来。
6. Claude Code、Codex、CC Switch 配置分开写,别把 ANTHROPIC_* 塞给 Codex
评测 Search Agent 时,很多人会同时使用 Claude Code、Codex 或 CC Switch 做辅助开发。这里必须强调:不同工具读不同配置文件,不要把 Claude Code 的ANTHROPIC_*变量复制到 Codex 的config.toml,也不要把 Codex 的 provider 配置塞进 Claude Code 的settings.json。
Claude Code 常用settings.json管理环境变量。配置字段位置如下,具体可用模型与端点行为请以 TaoToken 文档为准:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_MODEL": "YOUR_MODEL_ID" } }Codex 使用config.toml,走的是另一套 provider 配置。不要在 Codex 里写ANTHROPIC_AUTH_TOKEN,而应该用自定义 provider 的env_key:
model = "YOUR_MODEL_ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat" [profiles.taotoken] model_provider = "taotoken" model = "YOUR_MODEL_ID"对应环境变量:
export TAOTOKEN_API_KEY="YOUR_API_KEY"如果你用 CC Switch 做多供应商切换,把它理解成三件套:供应商名称、Base URL、API Key。填写时通常是:
Provider Name: TaoToken Base URL: https://taotoken.net/api API Key: YOUR_API_KEY Model: YOUR_MODEL_IDCC Switch 只负责切换配置,不会替你验证 Key 是否可用。切换后仍要跑一次 Smoke Test。需要查看密钥与额度入口时,可以回到 TaoToken 官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=iris_eval_console 控制台操作。Claude Code 的接入细节可参考专门的文档入口,文末 CTA 会给出。
再提醒一次:Search Agent 评测脚本通常使用 OpenAI 兼容变量OPENAI_API_KEY与OPENAI_BASE_URL;Claude Code 使用ANTHROPIC_*;Codex 使用config.toml里的 provider。三者不要混用。混用最典型的现象是 Key 明明有效,但某个工具一直报认证失败或路径 404。
7. 批量评测与排障清单:401、404、429、空响应
批量评测比单条 Smoke Test 更容易暴露配置问题。下面给一份按顺序执行的排障清单,全部在本地终端完成。
第一,401 或 403。检查环境变量是否真的传入当前进程:
python - <<'PY' import os print("OPENAI_BASE_URL=", os.environ.get("OPENAI_BASE_URL")) print("OPENAI_API_KEY prefix=", (os.environ.get("OPENAI_API_KEY") or "")[:6]) print("EVAL_MODEL=", os.environ.get("EVAL_MODEL")) PY如果 Key 前缀为空,说明 export 没有生效。不要把完整 Key 打印到共享屏幕。
第二,404。检查base_url是否被误写为https://taotoken.net/?utm_source=...。工具配置只使用https://taotoken.net/api。如果你在代码里手写请求路径,确认不要重复拼接/v1或/chat/completions。以 SDK 和文档推荐写法为准。
第三,429。批量任务把并发降到 1,再逐步增加。Search Agent 的搜索规划阶段可能一轮问题触发多次调用,实际 QPS 比看起来高。日志里记录stage后,你可以看出是哪一阶段触发限流。
第四,空响应或finish_reason=length。增加max_tokens或缩短上下文。答案汇总阶段不要把全部检索原文无脑拼入,先做去重和截断。
第五,日志缺失。确认写文件和读文件不是同一个路径。批量脚本最好在每条结果写入后flush(),避免中途退出导致数据丢失。
第六,模型 ID 错误。不同供应商的模型命名不统一,填写前以控制台可用列表为准。不要在脚本里猜测模型名。
下面是带重试的批量调用片段,适合本地评测:
import time from openai import RateLimitError, APIError def call_with_retry(client, model, messages, max_retries=3): for i in range(max_retries): try: return client.chat.completions.create( model=model, messages=messages, temperature=0.2, max_tokens=512, ) except RateLimitError: wait = 2 ** i print(f"rate limited, retry after {wait}s") time.sleep(wait) except APIError as e: print(f"api error: {e}") time.sleep(1) raise RuntimeError("max retries exceeded")重试只解决临时错误,不解决 Key、Base URL、模型 ID 配置错误。因此每次批量评测前,先跑 Smoke Test,再跑 5 条小样本,最后跑全量。
8. 文末 CTA:模型对话、Coding Plan、API Keys、Claude Code 文档
如果你准备把 Iris 35B/397B 的 Search Agent 评测链路跑起来,建议按下面顺序走一遍:先用模型对话验证 Key 与模型 ID,再根据评测频率选择 Coding Plan,然后到 API Keys 创建或轮换密钥,Claude Code 用户再看专门文档。
模型对话入口: https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=iris_eval_chat
Coding Plan: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=iris_eval_coding_plan
创建 API Key: https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=iris_eval_api_keys
Claude Code 文档: https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=iris_eval_claude_code_doc
回到评测本身,记住三个固定动作:把 OpenAI 兼容调用的base_url设为https://taotoken.net/api,把 Key 回填为YOUR_API_KEY,把搜索规划与答案汇总的usage分别写入日志。只要这三步稳定,Iris 的本地评测就不再是一次性脚本,而是可对照、可复现、可排障的工程流程。