mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-06-06 05:51:19 +08:00
614 lines
12 KiB
Markdown
614 lines
12 KiB
Markdown
# NOFXi Agent 当前设计说明
|
||
|
||
## 目的
|
||
|
||
本文描述当前 NOFXi Agent 的实际设计,而不是早期版本的理想设计。重点回答这些问题:
|
||
|
||
- 用户消息从哪里进入
|
||
- 什么请求会进入 planner
|
||
- 当前有哪些记忆层
|
||
- planner 如何生成与执行 plan
|
||
- tool 现在是怎么设计的
|
||
- 动态快照和当前引用分别解决什么问题
|
||
- 为什么某些问题会出现“看起来有历史,但模型还是会追问”
|
||
|
||
本文对应的主要实现文件:
|
||
|
||
- `agent/agent.go`
|
||
- `agent/web.go`
|
||
- `api/agent_routes.go`
|
||
- `agent/planner_runtime.go`
|
||
- `agent/execution_state.go`
|
||
- `agent/memory.go`
|
||
- `agent/history.go`
|
||
- `agent/tools.go`
|
||
|
||
## 一句话总览
|
||
|
||
当前 Agent 的运行模型可以概括为:
|
||
|
||
1. 前端把消息发到 `/api/agent/chat/stream`
|
||
2. 后端把登录用户身份放进 context
|
||
3. Agent 除 `/clear` 和 `/status` 外,其他消息全部进入 planner
|
||
4. planner 结合多层记忆、动态快照和 tool schema 生成 plan
|
||
5. 执行 plan 中的 `tool / reason / ask_user / respond`
|
||
6. 在执行过程中持续更新执行态、短期原话、长期摘要和当前对象引用
|
||
|
||
## 请求入口
|
||
|
||
### 前端入口
|
||
|
||
前端 Agent 页面在:
|
||
|
||
- `web/src/pages/AgentChatPage.tsx`
|
||
|
||
当前聊天使用:
|
||
|
||
- `POST /api/agent/chat/stream`
|
||
|
||
请求体里会传:
|
||
|
||
- `message`
|
||
- `lang`
|
||
- `user_key`
|
||
|
||
### 后端路由入口
|
||
|
||
路由注册在:
|
||
|
||
- `api/agent_routes.go`
|
||
|
||
这里会:
|
||
|
||
1. 经过 `authMiddleware`
|
||
2. 从登录态里取出 `user_id`
|
||
3. 通过 `agent.WithStoreUserID(...)` 写入 request context
|
||
|
||
### Agent Web Handler
|
||
|
||
真正的 HTTP handler 在:
|
||
|
||
- `agent/web.go`
|
||
|
||
主要入口:
|
||
|
||
- `HandleChat(...)`
|
||
- `HandleChatStream(...)`
|
||
|
||
再往下进入:
|
||
|
||
- `HandleMessageForStoreUser(...)`
|
||
- `HandleMessageStreamForStoreUser(...)`
|
||
|
||
## 最外层分流
|
||
|
||
当前外层分流已经被收口。
|
||
|
||
在 `agent/agent.go` 中,除了这两个命令之外,其他输入全部交给 planner:
|
||
|
||
- `/clear`
|
||
- `/status`
|
||
|
||
也就是说,现在这些都不再在外层直接处理:
|
||
|
||
- setup flow
|
||
- trade confirmation
|
||
- direct trade regex
|
||
- 自然语言配置流程
|
||
- 自然语言策略创建
|
||
|
||
这些都统一进入 planner。
|
||
|
||
这是当前设计里一个很重要的原则:
|
||
|
||
- 外层分流越少,行为边界越清晰
|
||
- 自然语言理解尽量统一交给 planner + tool
|
||
|
||
## 当前的 5 层记忆
|
||
|
||
当前不是 3 层,也不是 4 层,而是 5 层:
|
||
|
||
1. `chatHistory`
|
||
2. `TaskState`
|
||
3. `ExecutionState`
|
||
4. `CurrentReferences`
|
||
5. `Persistent Preferences`
|
||
|
||
### 1. chatHistory
|
||
|
||
定义位置:
|
||
|
||
- `agent/history.go`
|
||
|
||
作用:
|
||
|
||
- 保存最近几轮用户 / assistant 原始消息
|
||
- 给模型保留最近原话上下文
|
||
- 为后续摘要成 `TaskState` 提供原始素材
|
||
|
||
特点:
|
||
|
||
- 只保留短期原话
|
||
- 内存态
|
||
- `/clear` 时清空
|
||
|
||
适合存:
|
||
|
||
- 最近几轮对话原文
|
||
- 用户的最新措辞
|
||
- 刚刚的自然语言上下文
|
||
|
||
不适合存:
|
||
|
||
- 长期真相
|
||
- 当前外部系统状态
|
||
- 当前流程精确执行位置
|
||
|
||
### 2. TaskState
|
||
|
||
定义位置:
|
||
|
||
- `agent/memory.go`
|
||
|
||
作用:
|
||
|
||
- 保存跨轮次仍然有意义的高层摘要
|
||
- 注入 planner / reasoning / final response
|
||
|
||
持久化 key:
|
||
|
||
- `agent_task_state_<userID>`
|
||
|
||
字段:
|
||
|
||
- `CurrentGoal`
|
||
- `ActiveFlow`
|
||
- `OpenLoops`
|
||
- `ImportantFacts`
|
||
- `LastDecision`
|
||
- `UpdatedAt`
|
||
|
||
适合存:
|
||
|
||
- 当前高层目标
|
||
- 跨轮次仍然成立的未闭环事项
|
||
- 关键事实
|
||
- 最近一次重要决策及其原因
|
||
|
||
不适合存:
|
||
|
||
- step 级待办
|
||
- “下一步调用哪个 tool”
|
||
- 动态余额、持仓、配置存在性
|
||
- 任何可以通过 tool 重新读取的实时状态
|
||
|
||
### 3. ExecutionState
|
||
|
||
定义位置:
|
||
|
||
- `agent/execution_state.go`
|
||
|
||
作用:
|
||
|
||
- 保存当前 plan 的执行态
|
||
- 支持 `ask_user` 之后继续执行
|
||
- 保存 plan、当前步骤、执行日志、等待状态等
|
||
|
||
持久化 key:
|
||
|
||
- `agent_execution_state_<userID>`
|
||
|
||
当前关键字段:
|
||
|
||
- `SessionID`
|
||
- `Goal`
|
||
- `Status`
|
||
- `PlanID`
|
||
- `Steps`
|
||
- `CurrentStepID`
|
||
- `DynamicSnapshots`
|
||
- `ExecutionLog`
|
||
- `SummaryNotes`
|
||
- `Waiting`
|
||
- `CurrentReferences`
|
||
- `FinalAnswer`
|
||
- `LastError`
|
||
|
||
### 4. CurrentReferences
|
||
|
||
定义位置:
|
||
|
||
- `agent/execution_state.go`
|
||
|
||
作用:
|
||
|
||
- 记录当前对话里“这个 / 那个 / 刚才那个”到底指的是谁
|
||
|
||
当前支持的引用对象:
|
||
|
||
- `strategy`
|
||
- `trader`
|
||
- `model`
|
||
- `exchange`
|
||
|
||
这是为了解决一种常见问题:
|
||
|
||
- 用户明明前一轮刚说过“激进策略”
|
||
- 下一轮说“改一下这个策略”
|
||
- 如果没有结构化引用,模型虽然有聊天历史,也容易重新追问
|
||
|
||
`CurrentReferences` 不是系统状态快照,而是:
|
||
|
||
- 当前对话焦点对象
|
||
- 当前代词绑定对象
|
||
|
||
### 5. Persistent Preferences
|
||
|
||
对应工具:
|
||
|
||
- `get_preferences`
|
||
- `manage_preferences`
|
||
|
||
作用:
|
||
|
||
- 保存用户长期偏好
|
||
|
||
适合存:
|
||
|
||
- 默认中文回复
|
||
- 偏好激进风格
|
||
- 更关注 BTC / ETH
|
||
- 不喜欢高频
|
||
- 每天固定时间简报
|
||
|
||
它和 `TaskState` 的区别是:
|
||
|
||
- `TaskState` 偏向当前任务摘要
|
||
- `Persistent Preferences` 偏向长期用户画像
|
||
|
||
## DynamicSnapshots 是什么
|
||
|
||
`DynamicSnapshots` 是当前真实系统状态的快照。
|
||
|
||
它不是历史,也不是长期记忆,而是 planner 在规划前或执行中插入的“当前事实”。
|
||
|
||
当前会进入快照的典型信息包括:
|
||
|
||
- 当前模型配置列表
|
||
- 当前交易所配置列表
|
||
- 当前策略列表
|
||
- 当前 trader 列表
|
||
- 当前余额
|
||
- 当前持仓
|
||
- 最近交易历史
|
||
|
||
作用:
|
||
|
||
- 防止 planner 盲信旧结论
|
||
- 避免“之前没配置,现在其实已经配好了却还说没有”
|
||
- 避免“之前余额是 A,现在拿旧 observation 继续回答”
|
||
|
||
一句话:
|
||
|
||
- `DynamicSnapshots` = 当前世界里真实有什么
|
||
|
||
## CurrentReferences 和 DynamicSnapshots 的区别
|
||
|
||
这两个容易混淆,但职责完全不同。
|
||
|
||
`DynamicSnapshots`:
|
||
|
||
- 当前系统状态快照
|
||
- 是候选集合 / 当前事实
|
||
- 例如当前有两个策略:`激进`、`新策略`
|
||
|
||
`CurrentReferences`:
|
||
|
||
- 当前对话焦点对象
|
||
- 是“这个”到底指谁
|
||
- 例如用户现在说的“这个策略”就是 `激进`
|
||
|
||
可以这样理解:
|
||
|
||
- `DynamicSnapshots` 是地图
|
||
- `CurrentReferences` 是你手指现在指着地图上的哪个点
|
||
|
||
## Planner 的输入
|
||
|
||
planner 主逻辑在:
|
||
|
||
- `agent/planner_runtime.go`
|
||
|
||
生成计划时,当前会把这些东西一起送给模型:
|
||
|
||
- 当前用户请求
|
||
- tool schema
|
||
- `Persistent Preferences`
|
||
- `TaskState`
|
||
- `ExecutionState`
|
||
- `Resume context`
|
||
- `Structured waiting state`
|
||
- `Observation context`
|
||
|
||
其中 observation context 不是旧版单数组,而是分层后的:
|
||
|
||
- `dynamic_snapshots`
|
||
- `execution_log`
|
||
- `summary_notes`
|
||
|
||
## Plan 的结构
|
||
|
||
当前 planner 只允许这 4 类 step:
|
||
|
||
- `tool`
|
||
- `reason`
|
||
- `ask_user`
|
||
- `respond`
|
||
|
||
这意味着现在的 Agent 不是一个“自由发挥的回复器”,而是:
|
||
|
||
- 先规划
|
||
- 再执行步骤
|
||
- 必要时重规划
|
||
|
||
## 步骤执行流程
|
||
|
||
`executePlan(...)` 的核心逻辑是:
|
||
|
||
1. 找下一个 pending step
|
||
2. 标记 step 为 running
|
||
3. 执行对应类型
|
||
4. 写回 `ExecutionState`
|
||
5. 必要时触发 replanning
|
||
|
||
不同 step 类型行为如下:
|
||
|
||
### tool
|
||
|
||
- 调内部 tool
|
||
- 把结果写入 `ExecutionLog`
|
||
- 根据结果更新 `CurrentReferences`
|
||
- 必要时触发 replanner
|
||
|
||
### reason
|
||
|
||
- 发起一次短 reasoning 调用
|
||
- 生成一段简短中间推理
|
||
- 写入 `ExecutionLog`
|
||
|
||
### ask_user
|
||
|
||
- 进入 `waiting_user`
|
||
- 保存 `WaitingState`
|
||
- 把问题直接回给用户
|
||
|
||
### respond
|
||
|
||
- 生成最终回答
|
||
- 标记当前执行完成
|
||
|
||
## WaitingState 是什么
|
||
|
||
`WaitingState` 用来解决:
|
||
|
||
- 用户回复 `是`
|
||
- 用户回复 `继续`
|
||
- 用户回复 `那个就行`
|
||
|
||
这类短回复如果没有结构化等待状态,很容易丢上下文。
|
||
|
||
当前字段包括:
|
||
|
||
- `Question`
|
||
- `Intent`
|
||
- `PendingFields`
|
||
- `ConfirmationTarget`
|
||
- `CreatedAt`
|
||
|
||
它的作用是:
|
||
|
||
- 告诉 planner 上一轮到底在等什么
|
||
- 让这轮短回复更容易被理解成“对上一问的回答”
|
||
|
||
## CurrentReferences 如何更新
|
||
|
||
当前是双路径更新:
|
||
|
||
### 1. 用户消息命中对象名时更新
|
||
|
||
如果用户说:
|
||
|
||
- `修改激进策略`
|
||
- `停止 lky`
|
||
- `用 DeepSeek`
|
||
|
||
系统会去当前用户的策略 / trader / model / exchange 列表里尝试匹配名称或 ID。
|
||
|
||
匹配成功后,更新 `CurrentReferences`。
|
||
|
||
### 2. tool 成功返回对象时更新
|
||
|
||
比如:
|
||
|
||
- `manage_strategy(create/update/activate)`
|
||
- `manage_trader(create/update)`
|
||
- `manage_model_config(update)`
|
||
- `manage_exchange_config(update)`
|
||
|
||
只要 tool 返回了具体对象,系统就会把对应 ID / name 写回当前引用。
|
||
|
||
## Tool 设计
|
||
|
||
当前 tool 是“资源型 tool”设计,不是“页面动作型 tool”。
|
||
|
||
### 当前主要工具
|
||
|
||
配置资源:
|
||
|
||
- `get_exchange_configs`
|
||
- `manage_exchange_config`
|
||
- `get_model_configs`
|
||
- `manage_model_config`
|
||
|
||
策略资源:
|
||
|
||
- `get_strategies`
|
||
- `manage_strategy`
|
||
|
||
trader 资源:
|
||
|
||
- `manage_trader`
|
||
|
||
交易 / 查询资源:
|
||
|
||
- `search_stock`
|
||
- `execute_trade`
|
||
- `get_positions`
|
||
- `get_balance`
|
||
- `get_market_price`
|
||
- `get_trade_history`
|
||
|
||
### 为什么这么设计
|
||
|
||
优点:
|
||
|
||
- tool schema 稳定
|
||
- 行为边界清晰
|
||
- planner 更容易学会
|
||
- 资源增删改查统一
|
||
|
||
当前 `manage_strategy` 支持:
|
||
|
||
- `list`
|
||
- `get_default_config`
|
||
- `create`
|
||
- `update`
|
||
- `delete`
|
||
- `activate`
|
||
- `duplicate`
|
||
|
||
当前 `manage_trader` 支持:
|
||
|
||
- `list`
|
||
- `create`
|
||
- `update`
|
||
- `delete`
|
||
- `start`
|
||
- `stop`
|
||
|
||
## 为什么“创建策略”不该默认依赖交易所和模型
|
||
|
||
当前设计里,策略模板应该是独立资源:
|
||
|
||
- `strategy`
|
||
|
||
而运行态对象是:
|
||
|
||
- `trader`
|
||
|
||
更合理的边界是:
|
||
|
||
- 创建策略模板:用 `manage_strategy`
|
||
- 把策略跑起来:用 `manage_trader`
|
||
|
||
也就是说:
|
||
|
||
- 策略不默认依赖交易所和模型
|
||
- 只有当用户要求“运行 / 部署 / 创建 trader”时,才需要进一步关联 exchange / model / trader
|
||
|
||
## 当前一个完整例子
|
||
|
||
用户输入:
|
||
|
||
`帮我创建一个新的激进策略模板,名字就叫激进。创建完后,再把这个策略绑定到 trader lky。`
|
||
|
||
当前大致流程:
|
||
|
||
1. 前端请求 `/api/agent/chat/stream`
|
||
2. 后端注入 `store_user_id`
|
||
3. Agent 进入 planner
|
||
4. planner 刷新动态快照:
|
||
- 当前策略
|
||
- 当前 trader
|
||
5. 生成 plan,例如:
|
||
- `get_strategies`
|
||
- `manage_strategy(create)`
|
||
- `manage_trader(update)`
|
||
- `respond`
|
||
6. 执行 `manage_strategy(create)` 后:
|
||
- 写入 `ExecutionLog`
|
||
- 更新 `CurrentReferences.strategy`
|
||
7. 执行 `manage_trader(update)` 时:
|
||
- 直接使用刚创建策略的 ID
|
||
8. 输出最终回复
|
||
|
||
如果此后用户继续说:
|
||
|
||
`把这个策略的 prompt 改激进一点`
|
||
|
||
系统会优先从 `CurrentReferences.strategy` 理解“这个策略”。
|
||
|
||
## 为什么看起来“有历史”,模型还是会追问
|
||
|
||
因为“有聊天历史”不等于“有结构化对象绑定”。
|
||
|
||
如果没有 `CurrentReferences`:
|
||
|
||
- 模型只能依赖原话文本推断“这个策略”是谁
|
||
- 一旦中间插入多条消息,或者有多个候选策略
|
||
- 就容易重新追问
|
||
|
||
所以当前设计里,`CurrentReferences` 是补齐这一块的关键。
|
||
|
||
## 当前已知限制
|
||
|
||
### 1. 外层虽然已经大幅收口,但仍然不是纯 graph runtime
|
||
|
||
现在比之前更统一,但整体仍然是:
|
||
|
||
- Agent 主入口
|
||
- Planner
|
||
- Tool 执行
|
||
|
||
而不是完整 node-graph 引擎。
|
||
|
||
### 2. ExecutionState 仍然是按 userID 单槽位
|
||
|
||
这意味着:
|
||
|
||
- 同一用户的多个并行任务仍然可能相互影响
|
||
|
||
更彻底的方向应该是:
|
||
|
||
- 按 thread / session 多实例存储
|
||
|
||
### 3. CurrentReferences 目前还是轻量实现
|
||
|
||
当前只覆盖:
|
||
|
||
- strategy
|
||
- trader
|
||
- model
|
||
- exchange
|
||
|
||
后面如果要更强,需要考虑:
|
||
|
||
- 多候选冲突消解
|
||
- 昵称映射
|
||
- 跨更长会话的稳定实体绑定
|
||
|
||
## 当前设计的核心思想
|
||
|
||
一句话总结:
|
||
|
||
- `chatHistory` 记原话
|
||
- `Persistent Preferences` 记长期偏好
|
||
- `TaskState` 记高层摘要
|
||
- `ExecutionState` 记当前流程
|
||
- `DynamicSnapshots` 记当前事实
|
||
- `CurrentReferences` 记当前指代对象
|
||
- `planner` 决定步骤
|
||
- `tools` 执行落地动作
|
||
|
||
这就是当前 NOFXi Agent 的实际运行设计。
|