Skip to content

数据流转 — Agent 是怎么工作的

这页解释一次用户查询从 WeChat 输入到带 cite 的回答之间,数据在 5 层架构里怎么流。读完应该能回答:

  • "为什么我的 600519 价格不是当前价?" — 见 价格新鲜度
  • "agent 凭什么不能编数字?" — 见 Verifier
  • "akshare 还是 Tushare 出的数?" — 见 served_by

整体序列

User                     Hermes        Skill            core/                External
(WeChat 等)              gateway       stock-research   data layer           data source
   │                       │              │                  │                    │
   │ "查 600519 当前价格"   │              │                  │                    │
   ├──────────────────────►│              │                  │                    │
   │                       │ route        │                  │                    │
   │                       ├─────────────►│                  │                    │
   │                       │              │ fetch_price.py   │                    │
   │                       │              │ 600519           │                    │
   │                       │              ├─────────────────►│                    │
   │                       │              │                  │ get_price(...)     │
   │                       │              │                  │                    │
   │                       │              │                  ├ is_market_open?  ─►│ Tushare
   │                       │              │                  │                    │ trade_cal
   │                       │              │                  │ ├ yes              │
   │                       │              │                  ├──────────────────► │ akshare
   │                       │              │                  │                    │ Xueqiu spot
   │                       │              │                  │ ◄──────────────────┤
   │                       │              │                  │   value + as_of    │
   │                       │              │                  │                    │
   │                       │              │ ◄────────────────┤                    │
   │                       │              │   cite envelope  │                    │
   │                       │              │   (served_by)    │                    │
   │                       │              │                  │                    │
   │                       │              ├ Verifier check ──┘                    │
   │                       │              │ - tool_call_id 在 trace?              │
   │                       │              │ - value 与 trace 一致?                │
   │                       │              │ - cite 字段齐全?                       │
   │                       │              │                                       │
   │                       │ ◄────────────┤                                       │
   │                       │ verified     │                                       │
   │                       │ JSON         │                                       │
   │ ◄─────────────────────┤              │                                       │
   │  "当前价 ¥1801.23      │              │                                       │
   │   (akshare,            │              │                                       │
   │    2026-05-07)"        │              │                                       │

每一步在干什么

1. User → Hermes gateway

用户在 WeChat 发消息。Hermes 的 platform adapter(channel_directory.json 里注册)把消息送进 gateway,gateway 进入 agent loop。

2. Hermes → Skill

LLM 根据 SKILL.md 的 trigger conditions 决定调哪个 skill。stock-research 的触发条件包括"用户提到股票代码"和"用户问 P/E、估值、技术面"。详见 skill/stock-research/SKILL.md §Trigger Conditions。

3. Skill → core data layer

skill scripts (fetch_price.py 等) 是无状态的 CLI 入口。它们通过 _client.py:get_client() 拿到一个客户端:

  • TWILIGHT_SERVICE_URL 已设 → ServiceClient 走 HTTP 后端(P1.0 backend,待发)
  • 否则 → DirectClient 直接 import core.data.schemas

v0.1.0 现状:所有 profile 都跑 Direct 模式。P1.0 backend 上线后 user profile 切到 Service 模式,token 由 backend 持。

4. core 数据层路由

core/core/data/schemas.py:get_price 做自动路由:

条件走哪metric延迟
trade_date 显式给值Tushare dailycloseT+1
trade_date 为 None + 市场开盘akshare Xueqiucurrent_price实时(秒级)
trade_date 为 None + 市场关闭Tushare dailyclose最近交易日收盘

"市场是否开盘"由 core/core/data/_market.py:is_market_open() 判断,调 Tushare trade_cal 校验当日是否交易日(缓存当天结果)。失败时降级为"周一到周五 09:30–15:00 即开盘"。

5. cite 信封返回

每次成功查询返回一个固定形态的 envelope:

json
{
  "value": 1801.23,
  "metric": "current_price",
  "code": "600519.SH",
  "as_of": "2026-05-07",
  "cite": {
    "kind": "tool",
    "source": "akshare",
    "table": "stock_individual_spot_xq",
    "fetched_at": "2026-05-07T05:30:12.456+00:00",
    "tool_call_id": "tc_71387d9cc564",
    "served_by": "akshare"
  }
}

下游消费者(Verifier、agent prose)只看 envelope,不重做网络调用。

6. Verifier 把关

core/core/verifier.py 是确定性代码(不是另一个 LLM)。每个声明必须满足:

  • cite.tool_call_id 在 trace 里能找到
  • value 与 trace 里记录的值一致(浮点容差 1e-9)
  • competence_id(如果是 knowledge cite)已注册
  • as_of 与 staleness budget 不冲突(默认 365 × 10 天,metric 级可覆盖)

校验失败 → 带 feedback 重试一次 → 仍失败则直接报错给 user,不静默降级。

7. Hermes → User

verified JSON 通过 LLM 转写成自然语言:「当前价 ¥1801.23(akshare,2026-05-07 实时)」。SKILL.md 要求 LLM 在叙述里区分 current_price(说"当前价")和 close(说"最近收盘")。

价格新鲜度

为什么会区分两条路径?

fetch_price.py 600519 在交易时段(09:30–15:00 BJT 工作日)调用 → Tushare daily API 设计上只返回已收盘交易日的收盘价(T+1 可用),所以 11:00 调 → 拿到昨天收盘。akshare 的 Xueqiu 单 code 端点(stock_individual_spot_xq)返回当前实时价,弥补了这个 gap。

这也是 v0.1.0 → v0.1.1 hotfix 的根本原因。详见 docs/planning/06-v0.1.1-hotfix-and-p1.md(草稿)。

served_by

cite 信封的 served_by 字段是 v0.1.1 引入的(向后兼容:老 cite 没这字段也能解析)。它告诉下游 "这次查询究竟是哪个 adapter 出的数",对两类场景关键:

  • Verifier 跨源比对:P1.2 多源数据落地后,仓库会存 Tushare + pytdx3 + Yahoo 的同一日数据;如果三者差 > 0.01% 要预警。served_by 让审计直接知道这条 claim 选了谁。
  • Agent 叙述风格served_by="akshare" → 说"实时";served_by="tushare" 不带 trade_date → 说"昨日收盘"。

进一步阅读

团队内部文档