# find_agent 工具验证报告 真实接口验证日期:2026-07-23 当前契约同步日期:2026-07-28 > 当前 `find_agent` 明确不使用视频理解。`qwen_video_analyze` 未注册,视频画面、语音、 > 字幕及多模态解析不属于在线能力。数据库和 ORM 不包含视频理解相关列。 > > 最终等级只允许 `primary / rejected`;`pending_evaluation` 仅为过程状态,不存在 > `backup`、补充推荐或人工备选。 ## 验证口径 - **真实通过**:实际调用当前配置的外部接口,并检查关键字段。 - **契约通过**:使用模拟接口/Repository 检查参数、分页、解析和错误格式。 - **受阻**:缺少密钥、数据库表或明确授权,不能宣称真实可用。 ## 本轮回归结果 - 2026-07-28 移除工具错误分类后,find_agent、AgentLoop、工具框架和失败声明 定向测试合计 `39 passed`;Ruff 与 `git diff --check` 通过; - 调度测试文件全量为 `4 passed, 3 failed`;失败来自既有测试环境问题:两个 SQLite 用例未为 MySQL `BIGINT` 主键提供自增兼容,一个测试夹具使用普通 `object()` 代替 `FindDemandContext`。三项均与本次故障终止修改无关; - 2026-07-23 历史 find_agent 与 LLM 重试定向测试:`19 passed`; - Ruff 与 `git diff --check`:通过; - 三张 MySQL 表结构、列、唯一键和索引:真实检查通过; - 持久化测试数据:按专用 `run_id` 全部清理,残留数为0; - 项目全量 pytest:在两个非 find_agent 模块的收集阶段中止,分别是缺少 `agents.demand_grade_orchestrator_agent.common.plan_builder`,以及 `supply_infra.scheduler.jobs.grade_demand_pool` 未导出测试引用的 `_execute_plan_tasks_with_retries`。 ## 真实 Agent 干跑 测试输入为“个人养老金税收优惠”,模型为 `google/gemini-2.5-flash`。 第一次运行在第5轮收到 OpenRouter/Google 的 `MALFORMED_FUNCTION_CALL`。原框架将该供应方错误解析为空答案并提前结束。现已在同步和 异步 LLM 调用中增加最多2次有限重试,重试耗尽后显式抛错;对应3个测试均通过。 修复后第二次运行完成了: - 27次工具调用; - 3个自主搜索词; - 6个已保存搜索页,并执行了分页; - 候选详情和批量双侧画像; - 候选评分持久化、流程审计和状态恢复; - 工具参数错误后的自主修正:首次评分漏传 `run_id`、首次审计多传 `run_id`, Agent 均在下一次调用中修正。 但完整 Agent 验收**未通过**: - 最后一次审计 `can_finish=false`; - 仍有3个生产性搜索页未完成后续翻页; - 5个已入池候选在审计输入中缺失视频画像/作者画像“已尝试”标记; - Agent 未调用 `normalize_age_portraits`; - 搜索轨迹只有 `demand / pagination`,没有形成标签扩展分支; - 审计未通过时模型仍停止,并输出“接下来重新获取详情和画像”的过程性半截文本, 没有给出最终主推荐和淘汰候选表。 两次运行的数据库测试记录均已按实际 `run_id` 清理,残留数为0。 ## 工具逐项结果 | 工具 | 验证方式 | 结果 | |---|---|---| | `load_skill` | 实际调用不存在技能 | 通过错误契约;当前 skills 目录无可用技能 | | `douyin_search` | 真实搜索“个人养老金税收优惠”第一页、第二页 | 真实通过;8/0条,cursor 0→10;第二页关闭分页 | | `douyin_search_tikhub` | 真实搜索“老年人高血压管理”两页 | 真实通过;7/6条,cursor 0→8→16,`search_id/backtrace` 可用于翻页 | | `douyin_user_videos` | 使用 TikHub 结果中的真实 `sec_uid` 查询作者最热作品 | 真实通过;返回20条,统一候选结构与下一页游标完整 | | `douyin_detail` | 真实查询视频 `7665332856776764843` | 真实通过;详情、标签、分享数、播放地址齐全 | | `get_content_fans_portrait` | 真实查询上述视频 | 接口通过;该样例没有内容画像,正确返回 `has_portrait=false` | | `get_account_fans_portrait` | 真实查询上述作者 | 真实通过;返回年龄桶且被标准化为强老年信号 | | `batch_fetch_portraits` | 对上述候选真实请求双侧画像 | 真实通过;内容侧缺失时作者侧仍成功返回 | | `normalize_age_portraits` | 使用本轮真实双侧返回测试 | 通过;输出 `account_only`、作者侧 `strong`、E上限0.65 | | `audit_video_discovery_process` | 完整流/提前停止/错误淘汰场景 | 契约已同步为仅审计 `primary / rejected`,不再要求视频理解 | | `audit_video_discovery_run` | 注册与完成顺序契约测试 | 已注册;候选评估后执行,且仅 `can_finish=true` 可进入最终状态查询 | | `create_video_discovery_run` | 真实 MySQL 端到端测试 | 真实通过;运行记录成功创建 | | `record_video_search_page` | 真实 MySQL 保存与重复页测试 | 真实通过;3条候选入库,重复保存新增数为0 | | `batch_save_video_candidate_evaluations` | 候选分池契约 | 当前只接受 `primary / rejected`;`backup` 会返回输入错误 | | `query_video_discovery_state` | 查询运行、搜索树和候选 | 当前返回主推荐、淘汰候选及过程中的 `pending_evaluation` | ## 真实样例观察 - 搜索首条视频分享数为 `304,973`,详情接口返回值一致。 - 作者作品接口首条作品分享数为 `917,007`,说明作者分支能找到关键词搜索之外的高传播内容。 - 作者年龄画像实际使用 `50-` 表示50岁以上,占比 `19.63%`、TGI `84.84`; 因此不能只识别“50岁以上”文字。 - 内容画像可能没有数据,双侧画像工具的作者兜底是必要能力。 - 本轮内部搜索第一页声明 `has_more=true`,第二页返回0条并关闭分页;Agent 应保存空页 和停止信号,不能把“无新增”误写成调用失败。 - TikHub 第二页与第一页出现1条重复,说明跨页去重不能依赖供应方; 当前 `(run_id, aweme_id)` 唯一键和幂等合并逻辑能够处理。 - MySQL 历史端到端测试覆盖创建运行、保存搜索页、重复页幂等和状态查询; 测试记录已按 `run_id` 清理,残留数为0。 ## 当前阻塞 1. TikHub Key、三张 MySQL 表和全部持久化工具均已完成真实验证,没有相关阻塞。 2. 历史完整 Agent 干跑的最终审计未通过;完成守卫现已接入,但仍需重新执行真实干跑 才能宣称 Agent 可稳定完成任务。 3. 持久化候选需要保存 `detail_verified / content_portrait_attempted / account_portrait_attempted / age_portraits_normalized` 等审计状态;视频理解字段不 属于当前审计契约。 4. find_agent 完成守卫已启用;正常结果仍要求完整成功顺序。Agent 明确输出 `任务未完成(工具故障)` 时允许失败结束;程序不解析工具返回,也不判断错误是否 可恢复。 ## 下一步修复 1. 确保候选表的详情、双画像尝试和年龄标准化状态由 `query_video_discovery_state` 原样恢复;搜索状态同时补回 `parent_search_id`。 2. 将年龄标准化合并到批画像或候选保存流程,避免模型跳过强制证据处理。 3. 调整搜索和详情预算。当前详情按每条约10秒串行,真实任务延迟偏高;应先用分享量、 相关性和画像可得性做更强预筛。 4. 使用同一输入重新干跑,直到最终审计通过并输出完整 `primary / rejected` 报告。