xueyiming пре 5 дана
родитељ
комит
14c760e042
2 измењених фајлова са 335 додато и 0 уклоњено
  1. 333 0
      find_agent_v2/AGENT_FLOW.md
  2. 2 0
      find_agent_v2/README.md

+ 333 - 0
find_agent_v2/AGENT_FLOW.md

@@ -0,0 +1,333 @@
+# 寻找 Agent v2 当前实现流程
+
+本文档描述 `find_agent_v2` 当前代码的真实执行路径、LLM 与宿主程序的职责边界、工具权限、
+数据表和状态流转。文档基于 2026-08-12 的实现。
+
+## 1. 总体架构
+
+v2 使用“Python 外层业务循环 + 单轮固定 DAG + 节点内 ReAct”的结构。业务数据全部写入
+`find_agent_v2_*` 新表,和旧寻找 Agent 的运行表、工具和日志隔离。
+
+```mermaid
+flowchart TD
+    A["创建 v2 run<br/>status=running"] --> B["启动 Obagent run"]
+    B --> C["读取轮次开始快照"]
+    C --> D["创建 round<br/>phase=planning"]
+
+    subgraph G["单轮固定 DAG"]
+        D --> P["Planner<br/>生成搜索计划"]
+        P --> S["Search<br/>搜索并写入候选"]
+        S --> Q{"存在 pending_evaluation?"}
+        Q -- 否 --> X["结束本轮"]
+        Q -- 是 --> E["Evidence<br/>补详情和画像"]
+        E --> V["Evaluator<br/>评分并申请分池"]
+        V --> R{"pending 已清零?"}
+        R -- 否 --> V
+        R -- 是 --> X
+    end
+
+    X --> T{"valid primary ≥ 目标数?"}
+    T -- 是 --> F["finalize: goal_met"]
+    T -- 否 --> N{"仍有 pending?"}
+    N -- 是 --> Z["finalize: failed"]
+    N -- 否 --> I{"本轮有新增候选且未达最大轮数?"}
+    I -- 是 --> C
+    I -- 否 --> O["finalize: partial 或 no_match"]
+    F --> RP["Report 只读生成报告"]
+    O --> RP
+    Z --> END["结束,不执行 Report"]
+    RP --> END
+```
+
+默认参数:
+
+- 最大业务轮数:`2`
+- 目标有效 Primary:`5`
+- 默认模型:`google/gemini-3-flash-preview`
+- 节点温度:`0.2`
+
+## 2. 调用入口
+
+```mermaid
+sequenceDiagram
+    participant Caller as 调用方
+    participant Runner as runner.py
+    participant Service as FindAgentV2Service
+    participant Agent as FindAgentV2
+    participant Ob as Obagent
+
+    Caller->>Runner: create_find_agent_v2_run(...)
+    Runner->>Service: create_run(...)
+    Service-->>Caller: run_id
+    Caller->>Runner: run_find_agent_v2(user_input, run_id)
+    Runner->>Agent: arun(run_id, user_input)
+    Agent->>Service: require_run(status=running)
+    Agent->>Ob: observe.run(project=find_agent_v2)
+    Agent->>Service: 保存 obagent_run_uid
+    Agent->>Agent: 执行业务轮次和单轮 DAG
+    Agent->>Service: finalize(...)
+    Agent->>Ob: finish(final_output)
+    Agent-->>Caller: FindAgentResult
+```
+
+已有 run 也可以通过 `run_prepared_find_agent_v2(run_id)` 执行。此入口从
+`find_agent_v2_run.input_json` 读取创建时保存的 `user_input`。
+
+## 3. 单轮节点和工具权限
+
+每个节点都会创建一个新的 `supply_agent.Agent` 实例,只注册本节点允许的工具,并移除通用
+`load_skill` 工具。节点之间通过数据库状态和轻量内存状态衔接,不共享一个长期 ReAct 会话。
+
+| 节点 | LLM 职责 | 可用工具 | 上下文范围 | 最大 ReAct 轮数 |
+|---|---|---|---|---:|
+| Planner | 生成最多 3 个互补搜索方向 | 无 | 全量 run 状态 | 2 |
+| Search | 执行搜索计划,不补证、不评分 | `search_videos_v2`、`query_find_agent_v2_state` | 全量状态 | 10 |
+| Evidence | 为候选补视频详情和双侧年龄画像 | `fetch_candidate_details_v2`、`fetch_candidate_portraits_v2`、`query_pending_candidates_v2` | 仅 pending 候选 | 12 |
+| Evaluator | 给出 R/E/S/V,申请 `primary` 或 `rejected` | `evaluate_candidates_v2`、`query_pending_candidates_v2` | 仅 pending 候选 | 每次 12 |
+| Report | 汇总最终结果 | `query_find_agent_v2_state` | 全量最终状态 | 4 |
+
+Evaluator 支持分批消费:宿主最多重新进入 Evaluator 64 次,直到 pending 清零。如果连续 3 次
+调用后 pending 数量都没有下降,则判定为技术失败,防止模型空响应造成无限循环。
+
+## 4. 搜索与证据链路
+
+```mermaid
+flowchart LR
+    SP["Planner 搜索计划"] --> ST["search_videos_v2"]
+    ST -->|provider=tikhub| TK["TikHub 搜索接口"]
+    ST -->|其他 provider| IK["内部关键词搜索接口"]
+    TK --> SR["find_agent_v2_search"]
+    IK --> SR
+    SR --> C["find_agent_v2_candidate<br/>pending_evaluation"]
+
+    C --> DT["fetch_candidate_details_v2<br/>每批最多 8 条"]
+    C --> PT["fetch_candidate_portraits_v2<br/>每批最多 8 条"]
+    DT --> DE["详情接口"]
+    PT --> CP["内容画像接口"]
+    PT --> AP["账号画像接口"]
+    DE --> C
+    CP --> NM["v2 年龄画像归一化"]
+    AP --> NM
+    NM --> C
+    DE --> EV["find_agent_v2_evidence"]
+    NM --> EV
+```
+
+证据阶段写入的主要字段:
+
+- 详情:发布时间、时长、播放、点赞、评论、收藏、分享、作者和链接。
+- 画像:内容侧与账号侧的原始画像,以及归一化后的
+  `content_50_plus_ratio`、`account_50_plus_ratio`。
+- 状态:`detail_status`、`portrait_status`,取值通常为 `pending/success/failed`。
+
+LLM 在 Evaluator 阶段拿到归一化后的真实值和证据状态,不只拿门禁 reason code。原始接口响应
+保存在候选 JSON 字段和 `find_agent_v2_evidence.raw_json` 中。
+
+## 5. 评估、确定性门禁和分池
+
+LLM 给每个候选提供:
+
+- `relevance_score`(R)
+- `elder_score`(E)
+- `share_score`(S)
+- `value_score`(V)
+- 申请的 `decision_bucket`:`primary` 或 `rejected`
+- 决策说明和可选淘汰原因
+
+LLM 的 `primary` 只是申请。`FindAgentV2Service.evaluate()` 会在写库前调用 v2 自有的
+`evaluate_candidate_gate()`,由程序强制执行门禁。
+
+```mermaid
+flowchart TD
+    L["LLM 输出 R/E/S/V<br/>并申请 primary/rejected"] --> G["确定性门禁"]
+    G --> TM["时效检查"]
+    G --> DU["时长检查<br/>默认 ≥ 30 秒"]
+    G --> SH["分享量检查<br/>默认 ≥ 1000"]
+    G --> PO["50+画像检查<br/>内容侧或账号侧默认 ≥ 20%"]
+    TM --> ALL{"全部门禁通过?"}
+    DU --> ALL
+    SH --> ALL
+    PO --> ALL
+    ALL -- 否 --> RJ["强制 rejected<br/>gate_status=fail"]
+    ALL -- 是且LLM申请primary --> PR["primary<br/>gate_status=pass"]
+    ALL -- 是但LLM申请rejected --> RJ2["rejected<br/>gate_status=pass"]
+```
+
+最终通过视频的查询条件是:
+
+```sql
+SELECT *
+FROM find_agent_v2_candidate
+WHERE run_id = :run_id
+  AND decision_bucket = 'primary'
+  AND gate_status = 'pass';
+```
+
+### 当前补偿规则
+
+当前门禁不是“证据必须全部成功”的严格模式。强评分候选可以对部分缺失事实进行补偿:
+
+- V ≥ 0.65,或 R/E/S 分别达到 0.70/0.70/0.65,视为强候选。
+- 时长缺失可由强评分或高分享量补偿。
+- 分享量缺失可由强评分、高点赞或高播放补偿。
+- 内容侧和账号侧画像都缺失时,可由强评分补偿。
+
+因此当前实现中可能出现:
+
+```text
+decision_bucket = primary
+gate_status = pass
+detail_status = pending
+portrait_status = pending
+```
+
+这属于现行规则允许的结果,并非状态写入错误。如果业务要求“详情、画像都成功才可通过”,需要
+额外增加 `detail_status=success` 和 `portrait_status=success` 的硬门禁,并取消相应补偿。
+
+常见门禁原因码包括:
+
+| 原因码 | 含义 |
+|---|---|
+| `TEMPORAL_UNKNOWN` | 发布时间缺失且未满足补偿 |
+| `RELATIVE_DATE_EXPIRED` | 标题含相对日期,但内容已非当天 |
+| `EVENT_EXPIRED` | 事件型内容超出时效 |
+| `SEASONAL_EXPIRED` | 季节型内容超出时效 |
+| `DURATION_UNKNOWN` | 时长缺失且未满足补偿 |
+| `DURATION_TOO_SHORT` | 时长低于阈值 |
+| `SHARE_COUNT_UNKNOWN` | 分享量缺失且未满足补偿 |
+| `SHARE_COUNT_TOO_LOW` | 分享量低于阈值 |
+| `CONTENT_PORTRAIT_MISSING` | 内容侧和账号侧都没有画像,且未满足补偿 |
+| `PORTRAIT_50_PLUS_TOO_LOW` | 至少一侧有画像,但内容侧和账号侧 50+ 比例均未达阈值 |
+
+## 6. 轮次停止与运行终态
+
+每轮完成后,宿主按以下顺序判断:
+
+```mermaid
+flowchart TD
+    A["单轮结束"] --> B{"valid_primary_count ≥ target?"}
+    B -- 是 --> G["goal_met / finished"]
+    B -- 否 --> C{"pending_count > 0?"}
+    C -- 是 --> F["failed / failed"]
+    C -- 否 --> D{"candidate_count 有增长?"}
+    D -- 否 --> S["停止扩展"]
+    D -- 是 --> E{"达到 max_rounds?"}
+    E -- 否 --> NR["进入下一轮"]
+    E -- 是 --> S
+    S --> H{"有效 primary 数量"}
+    H -- 大于 0 --> P["partial / finished"]
+    H -- 等于 0 --> N["no_match / finished"]
+```
+
+技术异常统一进入 `failed/failed`。非技术失败的终态会继续运行只读 Report 节点;Report 失败只
+记录到内存 `state.failures`,不会反向改变已完成的业务终态。
+
+注意:`finalize()` 当前以固定数量 `5` 判断 `goal_met`,而外层 Agent 的
+`target_primary_count` 可以在构造时配置。默认值一致;如果将目标数配置为其他值,两处判断可能
+产生不一致,后续应统一为同一个规则快照字段。
+
+## 7. 数据表和状态归属
+
+```mermaid
+erDiagram
+    FIND_AGENT_V2_RUN ||--o{ FIND_AGENT_V2_ROUND : has
+    FIND_AGENT_V2_RUN ||--o{ FIND_AGENT_V2_SEARCH : has
+    FIND_AGENT_V2_RUN ||--o{ FIND_AGENT_V2_CANDIDATE : has
+    FIND_AGENT_V2_SEARCH ||--o{ FIND_AGENT_V2_CANDIDATE : first_discovers
+    FIND_AGENT_V2_CANDIDATE ||--o{ FIND_AGENT_V2_EVIDENCE : has
+
+    FIND_AGENT_V2_RUN {
+        string run_id
+        string status
+        string outcome_status
+        int current_round
+        int valid_primary_count
+        string obagent_run_uid
+    }
+    FIND_AGENT_V2_ROUND {
+        string run_id
+        int round_index
+        string phase
+        string status
+    }
+    FIND_AGENT_V2_SEARCH {
+        string run_id
+        string keyword
+        string provider
+        string status
+    }
+    FIND_AGENT_V2_CANDIDATE {
+        string aweme_id
+        string detail_status
+        string portrait_status
+        string decision_bucket
+        string gate_status
+        string reject_reason_code
+    }
+    FIND_AGENT_V2_EVIDENCE {
+        bigint candidate_id
+        string evidence_type
+        string provider
+        string status
+    }
+```
+
+状态归属:
+
+| 层级 | 字段 | 主要取值 |
+|---|---|---|
+| Run 技术状态 | `find_agent_v2_run.status` | `running`、`finished`、`failed` |
+| Run 业务结果 | `outcome_status` | `goal_met`、`partial`、`no_match`、`failed` |
+| Round 阶段 | `find_agent_v2_round.phase` | `planning`、`searching`、`evidence`、`evaluating`、`done` |
+| Round 状态 | `find_agent_v2_round.status` | `open`、`done`、`failed` |
+| 候选证据状态 | `detail_status/portrait_status` | `pending`、`success`、`failed` |
+| 候选分池 | `decision_bucket` | `pending_evaluation`、`primary`、`rejected` |
+| 候选门禁 | `gate_status` | `pass`、`fail`,未评估时为 `NULL` |
+
+## 8. Obagent 可视化映射
+
+v2 只接入新的 Obagent 可视化,不使用旧项目 JSONL、OSS 或本地日志可视化。
+
+```mermaid
+flowchart TD
+    R["observe.run<br/>project=find_agent_v2<br/>agent=find_agent_v2"]
+    R --> G1["graph · 第 1 轮"]
+    R --> G2["graph · 第 2 轮(如需要)"]
+    G1 --> P1["planner"]
+    G1 --> S1["search"]
+    G1 --> E1["evidence"]
+    G1 --> V1["evaluator"]
+    G2 --> P2["planner/search/evidence/evaluator"]
+    R --> RP["report"]
+```
+
+每个节点记录:
+
+- 稳定的输入槽:当前轮次、原始任务、数据库快照、本轮搜索计划。
+- 实际 system prompt、模型和工具列表。
+- 完整 LLM 消息链、usage、工具参数、工具返回、异常和迭代次数。
+- 节点最终输出。
+
+`find_agent_v2_run.obagent_run_uid` 保存可视化 run UID,控制台地址格式为:
+
+```text
+{OBAGENT_ENDPOINT}/#client_uid={obagent_run_uid}
+```
+
+## 9. 代码索引
+
+| 文件 | 职责 |
+|---|---|
+| `runner.py` | 创建 run,提供同步/异步/已准备任务入口 |
+| `agent.py` | 外层业务轮次、停止条件、终态和 Report |
+| `graph.py` | 单轮固定 DAG 和 Evaluator 消费保护 |
+| `runtime.py` | 每节点新建 ReAct Agent,执行物理工具隔离 |
+| `prompts.py` | 节点提示词和公共业务规则 |
+| `tools.py` | v2 工具定义和节点 allowlist |
+| `providers.py` | 搜索、详情、画像接口与响应归一化 |
+| `gates.py` | v2 确定性门禁和规则快照 |
+| `service.py` | v2 表事务、快照、评估和 finalize |
+| `models.py` | 五张独立 ORM 表 |
+| `observability.py` | Obagent run、graph、node 和 ReAct 轨迹 |
+| `state.py` | 轻量流程状态和返回结构 |
+

+ 2 - 0
find_agent_v2/README.md

@@ -3,6 +3,8 @@
 `find_agent_v2` 是完全自包含的新实现。它不导入旧寻找 Agent 的提示词、工具、provider、画像
 归一化、门禁、数据服务或日志模块,也不共享运行、搜索、候选或证据表。
 
+完整流程图、状态机、工具权限和数据流说明见 [AGENT_FLOW.md](./AGENT_FLOW.md)。
+
 ## 结构
 
 ```text