# pricing-api-integrator Hunt 调研报告 v1 > 任务: T-mt60zv33 | 阶段: FDE Hunt | 日期: 2026-08-24 > 目的: 为 Digest 阶段设计 benchmark 打地基,不是从零调研,而是深挖三块存量信息 + 探一批可用于 Evolve 真实自测的免鉴权 API --- ## 1. 仓库内先例对照:youtube-crawler(benchmark_score=95.5) ### 1.1 先例的产出结构(本体训练完整走过 FDE 四阶段) | 阶段 | 产出目录 | 关键文件 | |---|---|---| | Hunt | `workspace/hunt/` | `knowhow.md`(方案对比+API细节)、`seed_dataset.json`、`api_validation.md` | | Digest | `workspace/digest_v2/` | `benchmark_v2.md`(8维度加权评分表+11条自动化测试用例)、`train.json`/`test.json`、`industry_avg.json` | | Evolve | `workspace/evolve_v2/` | `youtube_crawler.js`(生产代码)、`benchmark_v2_test.js`(自动化打分器)、`benchmark_v2_result.json`、`verification_report.md` | | 汇总 | `workspace/fde_report_v2.md` | 全流程综合报告 | 出处:`system/agents/youtube-crawler/workspace/{fde_report_v2.md, hunt/knowhow.md, digest_v2/benchmark_v2.md, evolve_v2/evolution_report_v2.md, evolve_v2/verification_report.md}` ### 1.2 三级降级链的判定时机怎么设计的(可直接对照,但**不可直接照搬**——见 1.4 的关键差异) `digest_v2/benchmark_v2.md` §2「API使用规范」检查项明确写死判定条件,不是笼统的"要有降级": - 触发降级的具体信号:`403`(区分 `quotaExceeded` 停止当天 vs 普通 403 走降级)、`429` 重试耗尽后降级、超时(`AbortSignal.timeout(30000)`)后降级。 - 降级顺序固定:API → yt-dlp → Invidious,任何一级失败自动尝试下一级(`SKILL.md` §1)。 - **验收用自动化测试直接断言链路,不是靠人读代码判断**:`benchmark_v2.md` TC-011 `test_fallback_chain()` 预期 `API(403) -> yt-dlp(timeout) -> Invidious(success)`,即测试用例本身构造三段式的失败注入,逐级验证真的切换了数据源。 - 重试参数写死到具体数字,不是"合理重试"这种模糊描述:429 base 2s / max 60s / retry 5 次;503 base 5s / max 120s / retry 5 次;500/502 最多重试 2-3 次(`evolution_report_v2.md` §4 与 `benchmark_v2.md` §6 两处数字一致,互相印证不是笔误)。 ### 1.3 配额/缓存管理怎么验收 - 配额:`benchmark_v2.md` §2 检查项「是否有配额监控与计数器(10,000 units/day)」——验收点是"有没有计数器"这个工程实体,不是"有没有用超配额"这种结果论。 - 缓存:§7「性能与效率」检查项「缓存时间是否合理(建议 fetch_interval_hours: 4)」+ SKILL.md 写死 TTL 15 分钟、缓存键 `{regionCode}:{maxResults}:{videoCategoryId}`——验收落到了具体的缓存粒度设计,能直接对着代码查有没有这个 key 结构。 - 评分公式是纯加权和(`总分 = Σ 维度分数 × 权重`),8 个维度里"安全与合规"独立占 10%(API Key 环境变量注入、无硬编码、robots.txt),这点对 pricing-api-integrator 同样适用(见 §3)。 ### 1.4 关键差异:先例的降级模式对 pricing-api-integrator **不能直接搬** 这是本次调研最重要的一条结论,直接影响 Digest 阶段怎么设计维度权重: youtube-crawler 的核心可靠性策略是"**换数据源**"(API 不行就换 yt-dlp,再不行换 Invidious)——因为它抓的是公开可见的同一份热榜信息,三个源理论上返回同一件事实,换源不改变数据的真实性含义。 pricing-api-integrator 的 `agent.md` 硬约束第 7 条明确**禁止**这种模式:"不许改上游给的目标清单来迁就实现……不要偷偷跳过不说、也不要换成别的数据源静默交付"。因为竞品定价是每家私有数据,A 家的价格不能拿 B 家的价格顶替——"换源"在这里等于伪造数据,与硬约束第 2 条的红线直接冲突。 **结论**:Digest 阶段设计 benchmark 时,「错误处理与降级策略」这一维度的评分标准不能照抄 youtube-crawler 的"三级降级链完整度",而应该改成"**状态分类正确性**"(ok/blocked_on_credential/unavailable 三态判断准不准、有没有在 status≠ok 时依然塞了 price 值)。这是两个本体在同一个维度名下、完全不同的验收内核,混着抄会把 benchmark 设计错方向。 --- ## 2. 本体现状漏洞排查(agent.md 9条硬约束 + SKILL.md 价格快照 schema) 出处:`system/agents/pricing-api-integrator/agent.md`、`.claude/skills/main_skill/SKILL.md`。以下每条是手工走边界场景后发现的规则模糊/矛盾/未覆盖点,不是复述规则本身。 ### 2.1 「API 返回 200 但 body 是空数组」该判什么 status?—— **确认的规则空白** SKILL.md §「价格快照输出格式」写死 `status` 只能是 `ok` / `blocked_on_credential` / `unavailable`,且 `unavailable` 的定义是"超时、限流、接口不存在等其它失败"(SKILL.md L28)。 走一遍这个场景:请求成功、HTTP 200、body 是 `[]`(比如查询的 SKU 在该竞品的商品库里确实不存在,或者该地区没有上架)——这**不是失败**(请求本身没出错),但也没有价格可报。三个枚举值都套不上: - 判 `ok` 但 `price` 给 null?—— schema 规定 `status != "ok"` 时 `price` 才必须为 null(SKILL.md L77),反过来意味着 `status == "ok"` 隐含"这条有价格",`ok` + `price:null` 是未定义组合,下游 sdd/benchmark 消费时大概率按"有效价格条目"计数,会把"这个 SKU 不存在"误算成"抓到了价格"。 - 判 `unavailable`?—— 语义上牵强,`unavailable` 描述的是"该拿的东西没拿到"(限流/超时/无接口),不是"接口如实告诉你这东西不存在"。把这两种情况合并会让下游没法区分"这次该重试"还是"这个目标本来就没有这个 SKU,重试也没用"。 **建议 Digest 阶段设计验收判据时把这个场景做成显式测试用例**(类比 youtube-crawler TC-002/TC-010 的做法),逼 Evolve 阶段的实现在 PLAN 里就把这个分支写清楚,而不是留给运行时现场发挥。 ### 2.2 「同一竞品清单里一半目标缺 key 一半不缺,继续处理其它目标」具体怎么落地不卡死?—— **确认的操作化空白** 规则原文(agent.md 硬约束 6 + SKILL.md「缺 API Key 时的求助流程」)说"标记该条 blocked_on_credential,继续处理清单里其它目标",但没有回答几个必然会遇到的操作问题: 1. **去重粒度**:如果清单里 5 个目标共用同一个环境变量名(比如都靠 `VENDOR_A_API_KEY` 鉴权,因为都是同一家聚合平台的子品牌),human request 是每个目标调一次(5 次重复求助同一把 key),还是按环境变量名去重只求助一次?SKILL.md 的示例是单目标视角("抓取<竞品名>定价需要"),没有给多目标共享同一凭证时的批处理指引。5 次重复 human request 对下游人类是噪音,但规则没禁止,也没要求去重。 2. **求助后是否等待、等多久**:human request 之后,是当场阻塞等人类回应(拖慢其它不缺 key 的目标),还是发完请求立刻标记 blocked_on_credential 就跳到下一个目标,同一次 EXECUTE 内不再回头检查这把 key 有没有配上?规则原文倾向后者("继续处理其它目标"暗示不阻塞),但没有明写,也没规定"如果 EXECUTE 阶段还没结束、人类已经把 key 配上了,要不要回头重抓那个被标记 blocked 的目标"。按当前四段式设计(一次流转清空对话记忆),大概率答案是"不回头,留到下一轮任务重跑",但这个隐含结论值得在 Digest 阶段的验收清单里显式写出来,否则不同实现会做出不同选择,benchmark 打分时容易产生分歧。 3. **「缺失」与「无效」的边界没有覆盖**:SKILL.md 的检测方法是 `echo $VAR` 判断环境变量存不存在(L84, L41)。但如果变量存在、值也非空,只是这把 key 已过期/权限不够,API 返回 401/403——这不是"缺失",规则完全没提这种情况该走 `blocked_on_credential`(复用求助流程,因为本质也是"当前环境缺一把能用的 key")还是走 `unavailable`(因为技术上请求确实发出去也拿到响应了,只是被拒)。两条路径下游处理逻辑不同:前者会触发再次 human request,后者不会。这个判断留白,实现者只能自己猜。 ### 2.3 「超时重试次数 SKILL.md 里写的是'按 _plan.md 定的次数'但没给 default」—— **确认属于规则漏洞,且是本次调研最值得带回 Digest 的一条** 核实原文:SKILL.md §EXECUTE「遇到超时/限流:按 `_plan.md` 里定的重试次数重试;重试耗尽仍失败,标 `unavailable` 并写明原因,不要无限重试卡死任务」(L98)。通篇 agent.md + SKILL.md **没有任何地方**给出: - 默认重试次数是几次 - 退避策略是固定间隔还是指数退避、base 是多少、上限是多少 - 429(限流)和纯网络超时是否要用同一套重试参数,还是分开处理 - 单个目标的总耗时上限(避免一个目标反复重试拖爆整个任务的时间预算) 对照 1.2 节:youtube-crawler 在 SKILL.md/`benchmark_v2.md` 里把这些数字**写死在技能本体层**(429: base 2s max 60s retry 5;503: base 5s max 120s retry 5;网络超时 30s),任何一次训练/复训都有统一基线可对齐,Evolve 阶段的 benchmark 测试脚本能直接断言这些具体数字。而 pricing-api-integrator 把这个决定完全下放给每次任务的 `_plan.md`——即由 PLAN 阶段的 Agent 现场决定重试次数。这带来两个后果: 1. 不同任务、不同次训练可能拍出不同的重试参数,Digest 阶段没有一个稳定基线可以拿来自动化打分(youtube-crawler 的 benchmark 可以直接检查"429 base 2s max 60s retry 5 是否命中",pricing-api-integrator 目前做不到,因为"标准答案"每次都不一样)。 2. 如果 PLAN 阶段的 Agent 图省事,拍一个明显不合理的数字(比如 retry=0 或 retry=999),当前规则没有任何机制拦——SKILL.md 只要求"不要无限重试卡死任务"这种模糊上限,没有数值兜底。 **建议**:这不是"等 Evolve 阶段实现时再说"的细节,而是 Digest 阶段就该定的基线数字(比照 youtube-crawler 的量级:超时 20-30s、限流指数退避 base 1-2s / retry 3-5 次是行业常见区间,见第 3 节),否则 benchmark 无法对"重试策略是否合理"这一项打出稳定、可复现的分数。 ### 2.4 其它顺带发现的小缺口(未在任务描述里点名,但走查中冒出来的) - **acceptance 清单不在本体文件里**:agent.md 硬约束 5 提到"acceptance 是判定标准",但验收清单本身"路径不在本文件里,去任务描述取"(agent.md L7-L10 同一模式)——即 pricing-api-integrator 目前**没有类似 youtube-crawler `benchmark_v2.md` 那样固化在技能本体里的评分表/自动化测试脚本**,验收标准完全靠每次任务临时下发。这与 1.4 节呼应:Digest 阶段的核心产出物,恰恰应该是把这份目前不存在的 `benchmark_v1.md`(+ 配套测试用例)**产出来**,而不是假设它已经存在只需要复用。 - **`status: unavailable` 缺少子分类**:SKILL.md 把"超时、限流、接口不存在"三种性质不同的失败都塞进同一个 `unavailable` 值(L28),但这三者对下游"要不要重试/什么时候重试"的含义完全不同(超时和限流值得下次任务重试,"接口不存在"重试无意义)。当前 schema 没有 `reason` 枚举,只在 `blocked_on_credential` 的示例里出现了自由文本 `reason` 字段(L70),`unavailable` 该不该也强制填 `reason` 且做成受控枚举,规则没写清楚。 - **config.json 尚未挂 skill 版本**(`system/agents/pricing-api-integrator/config.json`):对照 youtube-crawler config 里的 `skills.youtube-crawling.{version, benchmark_score, status}` 结构,pricing-api-integrator 当前 config.json 没有 `skills` 字段,只有裸的 `type/status/cli/name/description`——印证这确实是首训(status: training),Digest/Evolve 完成后要记得回填这个结构,否则后续复训无法读到上一版 benchmark_score 做对比。 --- ## 3. 外部方法论:API 集成可靠性通用最佳实践 ### 3.1 幂等性(Idempotency) - 对写操作(本体目前是只读抓价,暂不适用,但如果后续扩展到"标记该条价格已核实"之类的写回操作要留意)应使用幂等键(idempotency key,通常是每个逻辑操作生成一个 UUID 放在请求头),确保重试不会造成重复副作用。 - 对 pricing-api-integrator 当前的纯读场景,"幂等"体现为:同一个 `(competitor, product_id)` 目标在同一次 EXECUTE 里只应该产生一条最终快照记录,重试不应该在快照里出现同一目标的多条冲突数据。 ### 3.2 退避重试(Exponential Backoff + Jitter) - 标准模式:捕获限流/服务端错误 → 优先读 `Retry-After` 响应头(若目标 API 提供)→ 没有该头则用指数退避(1s→2s→4s→8s…)。 - "Full Jitter"(在每次退避时间上叠加随机抖动)相比无抖动的纯指数退避,在 100 并发客户端场景下能把总请求量降低超过 50%——因为它打散了同步重试造成的请求洪峰。这点对本体虽不是并发场景(单 Agent 顺序处理目标清单),但如果未来同一竞品清单在多个任务并发跑时值得留意。 - 数量级参考(与第 1.2 节 youtube-crawler 的实际参数一致,可作为 Digest 阶段定基线数字时的外部佐证):连接超时建议 5s、读超时按目标 API 的 p99 延迟设定、重试总次数设上限并配合总耗时截止时间(deadline),避免"重试到天荒地老"。 ### 3.3 错误分类(哪些该重试,哪些不该) - **应重试**:HTTP 429(限流)、500/502/503/504(服务端错误)、DNS 解析失败、连接超时。 - **不应重试**(重试没有意义,属于"结果确定"的失败):多数 4xx(401/403 鉴权问题、400 参数错误、404 资源不存在)——这类错误换个时间点重试大概率还是同样的结果,应该直接分类落地,不占重试预算。 - 这条对应 2.1 节的空白:pricing-api-integrator 目前的 `unavailable` 分类没有区分"该重试"和"不该重试"的失败,是个可以直接引用这条外部实践去补的具体依据。 ### 3.4 限流处理(429) - 优先读 `Retry-After` header 的精确等待时间;没有该 header 才退化到指数退避估算。 - 本体当前的 curl 调用示例(SKILL.md L34-L37)没有演示读取 `Retry-After`,只记录状态码和响应体——建议 Digest 阶段的验收清单里加一条"429 响应时是否检查并遵循 `Retry-After` header"。 ### 3.5 超时分类 - 连接超时(connect timeout)与读超时(read timeout)应该分开设置和处理:前者通常意味着网络/DNS 问题,后者意味着服务端处理慢。本体当前规则(agent.md/SKILL.md)没有区分这两种超时,统一按"超时"笼统处理——与 2.3 节发现的空白是同一类问题的不同侧面。 ### 3.6 凭证缺失的正确处置 - 通用共识:API 凭证缺失应该被当作"配置问题"而非"运行时错误"来对待——即在请求发出前先做前置校验(本体的 `echo $VAR` 检测已经是这个模式),失败时给出明确、可操作的报错(本体的 `blocked_on_credential` + human request 流程同样符合这个共识),而不是让请求带着空凭证发出去、拿到一个含糊的 401 再回头猜是不是缺 key。 - 本体在这点上的设计**是对的**,与行业共识一致,值得在 Digest 阶段作为"已经做好、不用扣分"的项保留,而不是把它当成待改进点。 ### 3.7 LLM Agent 反幻觉对策(专门针对本体的核心红线) - 这类问题在业界被称为 **"tool-use hallucination"**(工具调用幻觉),区别于一般的事实性幻觉——Agent 可能"声称"调用了 API、查了实时价格,但实际上什么都没做,直接编了一个看起来合理的数字。已知的三种子类型: 1. **参数幻觉**(Parameter Hallucination):调用了正确的工具,但参数是编的/缺失的/格式错的。 2. **工具选择幻觉**(Tool-Selection Hallucination):选错工具,或者干脆编造一个不存在的工具调用。 3. **工具绕过错误**(Tool-Bypass Error):不调用工具,直接编一个答案冒充调用结果——**这正是 pricing-api-integrator 硬约束第 2/9 条要防的场景**("用估算/缓存/编造数据充当实时价格")。 - 已知对策,且与本体现有设计逐条对得上: 1. **严格的工具/参数 schema**——本体的价格快照 JSON schema(SKILL.md L50-L77)本身就是这种对策的体现:把"status 只能是三选一,非 ok 时 price 必须为 null"做成结构化约束,比靠 Agent 自觉不编数字更可靠。 2. **执行可追溯性**(Execution Traceability)——要求记录"思考→行动"全链路:具体 prompt、生成的请求参数、真实的 API 响应。这正对应本体硬约束第 5 条"acceptance 交付前自己把验收命令跑一遍,把真实输出贴进日志",以及 VERIFY 阶段"抽查快照里每一条价格能不能对应到日志里一次真实请求/响应"(agent.md L51, SKILL.md L104)——这是本体已经内置的、业界公认有效的反幻觉机制,Digest 阶段设计 benchmark 时应该把这条"日志与快照的可交叉验证性"作为独立的高权重评分维度,而不只是流程合规检查。 3. 已知薄弱点:即使 Agent 真的调用了工具,**在"解释/转述工具返回结果"这一步依然可能出现幻觉**(比如把响应里的某个字段读错、编了个响应里没有的细节)。本体当前的 VERIFY 阶段做法(抽查快照条目能否对应日志里的真实响应)部分覆盖了这个风险,但只覆盖"有没有调用",没有专门覆盖"转述得对不对"(比如把 `"price": {"amount": 29.99}` 抄成了 30 或者抄错了货币单位)——这是 Digest 阶段可以补的验收点:不仅要求日志里有对应请求,还要求日志里的原始响应数值与快照里的最终数值逐字段核对一致。 --- ## 4. 真实、公开、免鉴权、返回真实数值型数据的候选 API(供 Evolve 阶段自测替代 mock) 沙盒已验证有出网权限。以下三个端点已实测(2026-08-24),全部返回 200 + 真实数值,且均为无需注册/无需 API Key 的公开端点,可以直接在 Evolve 阶段用来做"真实调用而非纯 mock"的自测替代品——用它们模拟"竞品定价 API"的真实调用场景(价格数值 + 状态码 + 无鉴权/有鉴权两种模式都能覆盖,见下方注记)。 | # | 名称 | 端点 | 实测响应 | HTTP | 备注 | |---|---|---|---|---|---| | 1 | CoinGecko Simple Price | `GET https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd` | `{"bitcoin":{"usd":77304},"ethereum":{"usd":2444.97}}` | 200 | Keyless 公共 API,免费层 **30 次/分钟**,月配额 10,000 次;比另两个更贴近"多目标批量查价"场景(一次请求可传多个 `ids`,可模拟"一次抓一批竞品") | | 2 | Coinbase Spot Price | `GET https://api.coinbase.com/v2/prices/BTC-USD/spot` | `{"data":{"amount":"77374.715","base":"BTC","currency":"USD"}}` | 200 | 无需鉴权,公开端点限速 3 req/s(突发 6 req/s);单目标查询,字段结构最接近 pricing-api-integrator 快照里 `price.amount` 的形态 | | 3 | Binance Ticker Price | `GET https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT` | `{"symbol":"BTCUSDT","price":"77397.55000000"}` | 200 | 无需鉴权,权重制限流(单 symbol 权重 1,2400 权重/分钟 ≈ 2400 次/分钟);最适合用来测"高频轮询 + 限流应对"场景 | **用法建议**: - 用 #1(CoinGecko)模拟"多目标批量清单"场景,测试硬约束第 6/7 条(部分目标缺条件时继续处理其它目标)——可以人为传一个不存在的 `ids`(如 `notarealcoin`)混进请求,观察真实 API 对"无效目标"的响应形态(是整体报错还是该 key 静默缺失),直接对应 2.1 节发现的"空结果该判什么 status"的规则空白,用真实数据而非假设去验证。 - 用 #2(Coinbase)模拟"单目标精确查价"场景,字段形状(`amount`/`currency`)可以直接对着 SKILL.md 的价格快照 schema 做映射测试。 - 用 #3(Binance)因限流额度最宽松,适合用来测试"请求间隔/退避策略"逻辑本身(比如故意打高频请求触发它的限流响应,验证 Evolve 产出的重试/退避代码是不是真的按参数工作),弥补 2.3/3.4 节发现的"重试参数无默认值"空白——用真实限流响应校准出合理的 base/max/retry 默认值,而不是拍脑袋定数字。 - 三者都不需要鉴权,所以不能直接测试"缺 API Key → human request" 分支(2.2 节场景);如果 Evolve 阶段需要真实测这条分支,需要另外找一个免费但要求注册 Key 的 API(如 exchangerate-api.com 免费层需要注册但可申请,未在本次范围内实测,留给 Evolve 阶段按需处理)。 --- ## 5. 交给 Digest 阶段的关键结论摘要 1. **不能照抄 youtube-crawler 的"降级链完整度"维度**——本体设计禁止换数据源,benchmark 的对应维度应改成"状态分类正确性 + 日志可交叉验证性"(§1.4, §3.7-2)。 2. **`unavailable` 状态缺子分类**,"空结果/超时/限流/接口不存在/凭证无效"五种性质不同的失败目前全部混在一个枚举值里,建议 Digest 阶段的 benchmark 检查项里显式拆开验收(§2.1, §2.4, §3.3, §3.5)。 3. **重试/退避参数没有基线数字**,建议参考 youtube-crawler 与业界共识(限流 base 1-2s / retry 3-5 次、超时 20-30s)在 Digest 阶段定死,否则无法自动化打分(§2.3, §3.2-3.4)。 4. **凭证缺失处置流程设计是对的**,属于已达标项,不用在 benchmark 里额外扣分空间(§3.6)。 5. **反幻觉的核心机制(日志与快照交叉验证)已经内置在 VERIFY 阶段**,建议 Digest 把这条设计成高权重独立维度,并补上"转述准确性"这个当前没覆盖的子项(§3.7)。 6. **三个免鉴权真实 API 已验证可用**,可供 Evolve 阶段做非 mock 自测,覆盖批量目标/精确查价/限流压测三种场景(§4)。