diff --git a/README.md b/README.md index 313dd21f..eb9e3ad9 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,20 @@ Open **http://127.0.0.1:3000**. Done. --- +## Quick Demo + +

+ + NOFX quick demo video + +

+ +

+ Click the cover image to watch the demo video. +

+ +--- + ## How x402 Works Traditional flow: register account → buy credits → get API key → manage quota → rotate keys. @@ -59,22 +73,22 @@ No accounts. No API keys. No prepaid credits. One wallet, every model. ### Built-in x402 Providers -| Provider | Chain | Models | -|:---------|:------|:-------| -| **[Claw402](https://claw402.ai)** | Base | GPT-5.4, Claude Opus, DeepSeek, Qwen, Grok, Gemini, Kimi — 15+ models | +| Provider | Chain | Models | +| :--------------------------------------------------------------------------------------------------------------------------------- | :---- | :-------------------------------------------------------------------- | +| **[Claw402](https://claw402.ai)** | Base | GPT-5.4, Claude Opus, DeepSeek, Qwen, Grok, Gemini, Kimi — 15+ models | --- ## What It Does -| Feature | Description | -|:--------|:------------| -| **Multi-AI** | DeepSeek, Qwen, GPT, Claude, Gemini, Grok, Kimi, MiniMax — switch anytime | -| **Multi-Exchange** | Binance, Bybit, OKX, Bitget, KuCoin, Gate, Hyperliquid, Aster, Lighter | -| **Strategy Studio** | Visual builder — coin sources, indicators, risk controls | -| **AI Competition** | AIs compete in real-time, leaderboard ranks performance | -| **Telegram Agent** | Chat with your trading assistant — streaming, tool calling, memory | -| **Dashboard** | Live positions, P/L, AI decision logs with Chain of Thought | +| Feature | Description | +| :------------------ | :------------------------------------------------------------------------ | +| **Multi-AI** | DeepSeek, Qwen, GPT, Claude, Gemini, Grok, Kimi, MiniMax — switch anytime | +| **Multi-Exchange** | Binance, Bybit, OKX, Bitget, KuCoin, Gate, Hyperliquid, Aster, Lighter | +| **Strategy Studio** | Visual builder — coin sources, indicators, risk controls | +| **AI Competition** | AIs compete in real-time, leaderboard ranks performance | +| **Telegram Agent** | Chat with your trading assistant — streaming, tool calling, memory | +| **Dashboard** | Live positions, P/L, AI decision logs with Chain of Thought | ### Markets @@ -82,35 +96,35 @@ Crypto · US Stocks · Forex · Metals ### Exchanges (CEX) -| Exchange | Status | Register (Fee Discount) | -|:---------|:------:|:------------------------| -| **Binance** | ✅ | [Register](https://www.binance.com/join?ref=NOFXENG) | -| **Bybit** | ✅ | [Register](https://partner.bybit.com/b/83856) | -| **OKX** | ✅ | [Register](https://www.okx.com/join/1865360) | -| **Bitget** | ✅ | [Register](https://www.bitget.com/referral/register?from=referral&clacCode=c8a43172) | -| **KuCoin** | ✅ | [Register](https://www.kucoin.com/r/broker/CXEV7XKK) | -| **Gate** | ✅ | [Register](https://www.gatenode.xyz/share/VQBGUAxY) | +| Exchange | Status | Register (Fee Discount) | +| :-------------------------------------------------------------------------------------------------------------------- | :----: | :----------------------------------------------------------------------------------- | +| **Binance** | ✅ | [Register](https://www.binance.com/join?ref=NOFXENG) | +| **Bybit** | ✅ | [Register](https://partner.bybit.com/b/83856) | +| **OKX** | ✅ | [Register](https://www.okx.com/join/1865360) | +| **Bitget** | ✅ | [Register](https://www.bitget.com/referral/register?from=referral&clacCode=c8a43172) | +| **KuCoin** | ✅ | [Register](https://www.kucoin.com/r/broker/CXEV7XKK) | +| **Gate** | ✅ | [Register](https://www.gatenode.xyz/share/VQBGUAxY) | ### Exchanges (Perp-DEX) -| Exchange | Status | Register (Fee Discount) | -|:---------|:------:|:------------------------| -| **Hyperliquid** | ✅ | [Register](https://app.hyperliquid.xyz/join/AITRADING) | -| **Aster DEX** | ✅ | [Register](https://www.asterdex.com/en/referral/fdfc0e) | -| **Lighter** | ✅ | [Register](https://app.lighter.xyz/?referral=68151432) | +| Exchange | Status | Register (Fee Discount) | +| :---------------------------------------------------------------------------------------------------------------------------- | :----: | :------------------------------------------------------ | +| **Hyperliquid** | ✅ | [Register](https://app.hyperliquid.xyz/join/AITRADING) | +| **Aster DEX** | ✅ | [Register](https://www.asterdex.com/en/referral/fdfc0e) | +| **Lighter** | ✅ | [Register](https://app.lighter.xyz/?referral=68151432) | ### AI Models (API Key Mode) -| AI Model | Status | Get API Key | -|:---------|:------:|:------------| -| **DeepSeek** | ✅ | [Get API Key](https://platform.deepseek.com) | -| **Qwen** | ✅ | [Get API Key](https://dashscope.console.aliyun.com) | -| **OpenAI (GPT)** | ✅ | [Get API Key](https://platform.openai.com) | -| **Claude** | ✅ | [Get API Key](https://console.anthropic.com) | -| **Gemini** | ✅ | [Get API Key](https://aistudio.google.com) | -| **Grok** | ✅ | [Get API Key](https://console.x.ai) | -| **Kimi** | ✅ | [Get API Key](https://platform.moonshot.cn) | -| **MiniMax** | ✅ | [Get API Key](https://platform.minimaxi.com) | +| AI Model | Status | Get API Key | +| :--------------------------------------------------------------------------------------------------------------- | :----: | :-------------------------------------------------- | +| **DeepSeek** | ✅ | [Get API Key](https://platform.deepseek.com) | +| **Qwen** | ✅ | [Get API Key](https://dashscope.console.aliyun.com) | +| **OpenAI (GPT)** | ✅ | [Get API Key](https://platform.openai.com) | +| **Claude** | ✅ | [Get API Key](https://console.anthropic.com) | +| **Gemini** | ✅ | [Get API Key](https://aistudio.google.com) | +| **Grok** | ✅ | [Get API Key](https://console.x.ai) | +| **Kimi** | ✅ | [Get API Key](https://platform.moonshot.cn) | +| **MiniMax** | ✅ | [Get API Key](https://platform.minimaxi.com) | ### AI Models (x402 Mode — No API Key) @@ -123,41 +137,45 @@ Crypto · US Stocks · Forex · Metals
Config Page -| AI Models & Exchanges | Traders List | -|:---:|:---:| +| AI Models & Exchanges | Traders List | +| :----------------------------------------------------------: | :----------------------------------------------------------: | | | | +
Dashboard -| Overview | Market Chart | -|:---:|:---:| +| Overview | Market Chart | +| :-----------------------------------------------------: | :-------------------------------------------------------------: | | | | -| Trading Stats | Position History | -|:---:|:---:| +| Trading Stats | Position History | +| :--------------------------------------------------------------: | :-----------------------------------------------------------------: | | | | -| Positions | Trader Details | -|:---:|:---:| +| Positions | Trader Details | +| :----------------------------------------------------------: | :---------------------------------------------------: | | | | +
Strategy Studio -| Strategy Editor | Indicators Config | -|:---:|:---:| +| Strategy Editor | Indicators Config | +| :------------------------------------------------------: | :----------------------------------------------------------: | | | | +
Competition -| Competition Mode | -|:---:| +| Competition Mode | +| :-------------------------------------------------------: | | | +
--- @@ -229,12 +247,14 @@ Everything through the web UI at **http://127.0.0.1:3000**. ## Deploy to Server **HTTP (quick):** + ```bash curl -fsSL https://raw.githubusercontent.com/NoFxAiOS/nofx/main/install.sh | bash # Access via http://YOUR_IP:3000 ``` **HTTPS (Cloudflare):** + 1. Add domain to [Cloudflare](https://dash.cloudflare.com) (free plan) 2. A record → your server IP (Proxied) 3. SSL/TLS → Flexible @@ -272,12 +292,12 @@ curl -fsSL https://raw.githubusercontent.com/NoFxAiOS/nofx/main/install.sh | bas ## Docs -| | | -|:--|:--| -| [Architecture](docs/architecture/README.md) | System design and module index | +| | | +| :------------------------------------------------------ | :------------------------------------ | +| [Architecture](docs/architecture/README.md) | System design and module index | | [Strategy Module](docs/architecture/STRATEGY_MODULE.md) | Coin selection, AI prompts, execution | -| [FAQ](docs/faq/README.md) | Common questions | -| [Getting Started](docs/getting-started/README.md) | Deployment guide | +| [FAQ](docs/faq/README.md) | Common questions | +| [Getting Started](docs/getting-started/README.md) | Deployment guide | --- @@ -291,26 +311,26 @@ All contributions are tracked. When NOFX generates revenue, contributors receive **[Pinned Issues](https://github.com/NoFxAiOS/nofx/issues) get the highest rewards.** -| Contribution | Weight | -|:-------------|:------:| -| Pinned Issue PRs | ★★★★★★ | -| Code (Merged PRs) | ★★★★★ | -| Bug Fixes | ★★★★ | -| Feature Ideas | ★★★ | -| Bug Reports | ★★ | -| Documentation | ★★ | +| Contribution | Weight | +| :---------------- | :----: | +| Pinned Issue PRs | ★★★★★★ | +| Code (Merged PRs) | ★★★★★ | +| Bug Fixes | ★★★★ | +| Feature Ideas | ★★★ | +| Bug Reports | ★★ | +| Documentation | ★★ | --- ## Links -| | | -|:--|:--| -| Website | [nofxai.com](https://nofxai.com) | -| Dashboard | [nofxos.ai/dashboard](https://nofxos.ai/dashboard) | -| API Docs | [nofxos.ai/api-docs](https://nofxos.ai/api-docs) | -| Telegram | [nofx_dev_community](https://t.me/nofx_dev_community) | -| Twitter | [@nofx_official](https://x.com/nofx_official) | +| | | +| :-------- | :---------------------------------------------------- | +| Website | [nofxai.com](https://nofxai.com) | +| Dashboard | [nofxos.ai/dashboard](https://nofxos.ai/dashboard) | +| API Docs | [nofxos.ai/api-docs](https://nofxos.ai/api-docs) | +| Telegram | [nofx_dev_community](https://t.me/nofx_dev_community) | +| Twitter | [@nofx_official](https://x.com/nofx_official) | > **Risk Warning**: AI auto-trading carries significant risks. Recommended for learning/research or small amounts only. diff --git a/agents.md b/agents.md new file mode 100644 index 00000000..684def9f --- /dev/null +++ b/agents.md @@ -0,0 +1,922 @@ +# NOFXi 交易智能助手规范 + +## 使命 + +NOFXi 交易智能助手不是通用闲聊机器人,而是一个面向交易场景的操作与决策辅助助手。 + +它的核心目标是帮助用户更安全、更高效、更专业地完成以下事情: + +- 创建、启动、查询、编辑、删除 agent +- 管理交易所配置 +- 管理策略 +- 管理大模型配置 +- 排查配置问题与运行问题 +- 回答交易相关问题,并提供可执行的建议 + +助手的价值不在于“会聊天”,而在于: + +- 降低用户操作成本 +- 减少配置错误和误操作 +- 提高问题定位效率 +- 让交易过程更专业、更可靠 + +## 核心理念 + +本助手采用 `80% skill + 20% 动态规划` 的设计思路。 + +这意味着: + +- 大多数高频、已知、可标准化的需求,应由预定义 skill 处理 +- 不应让模型对已知流程重复思考 +- 动态规划只用于少数复杂、跨领域、未知或开放性任务 +- 能确定的事情就不要交给模型自由发挥 + +默认优先级如下: + +1. 优先匹配 skill +2. 如果用户仍在当前任务中,则继续当前 skill +3. 只有当没有合适 skill 时,才进入动态规划 + +## 设计原则 + +### 1. 以 Skill 为主,不以自由推理为主 + +对于高频任务和高风险任务,必须优先使用 skill,而不是通用 agent 自行规划。 + +尤其是以下场景: + +- 创建 agent +- 启动或停止 agent +- 新增或修改交易所配置 +- 新增或修改策略 +- 新增或修改模型配置 +- 常见报错排查 +- API 配置指导 + +这些任务都应有稳定、明确、可重复执行的处理路径。 + +### 2. 以用户任务为中心,不以内部对象或 API 为中心 + +skill 的拆分应该围绕“用户想完成什么任务”,而不是“系统里有哪些对象”或“有哪些接口”。 + +好的拆分方式: + +- 创建一个 agent +- 启动或停止一个 agent +- 排查交易所 API 连接失败 +- 指导用户配置某个模型的 API +- 解释某条报错并给出下一步 + +不好的拆分方式: + +- exchange skill +- strategy 对象 skill +- 通用 REST 调用 skill +- 纯接口包装型 skill + +用户关注的是任务结果,不是内部实现。 + +### 3. 多轮对话的目标是推进任务,不是维持聊天感 + +多轮对话的本质,不是“让助手显得更像人”,而是让任务从模糊走向完成。 + +每一轮都应围绕以下问题展开: + +- 当前正在处理什么任务 +- 当前任务已经确认了哪些信息 +- 还缺什么关键信息 +- 下一步最合理的推进动作是什么 + +### 4. 只追问必要信息 + +当任务可以继续推进时,不要提出宽泛、发散、无助于执行的问题。 + +助手只应追问: + +- 当前任务必需但缺失的字段 +- 影响结果的重要选择项 +- 涉及风险、删除、替换、启动、停止等动作时的确认信息 + +不要要求用户重复已经确认过的信息。 + +### 5. 尽量减少不必要的思考 + +对于已有稳定处理路径的任务,直接按既定流程执行,不进行自由规划。 + +不要把模型能力浪费在这些事情上: + +- 猜测标准流程 +- 重新设计高频任务执行顺序 +- 对常见配置问题进行开放式发散分析 +- 对结构化任务做不必要的“创造性理解” + +### 6. 高风险动作优先保证安全 + +任何可能造成损失、误操作、难以回滚或影响实盘的动作,都必须谨慎处理。 + +以下动作通常需要明确确认: + +- 删除 agent +- 删除交易所配置 +- 删除策略 +- 覆盖已有配置 +- 启动实盘 agent +- 停止正在运行的 agent +- 修改可能影响下单行为的关键参数 + +当用户意图不够明确时,宁可先确认,不要直接执行。 + +### 7. 回答要以可执行为目标 + +当用户提问、排障、求指导时,回答应优先提供清晰的下一步,而不是停留在抽象概念。 + +尽量围绕这三个问题组织回答: + +- 发生了什么 +- 为什么会这样 +- 现在该怎么做 + +## 任务分类 + +### 一、执行类任务 + +执行类任务是指目标明确、结果清晰、可以落到具体系统动作上的任务。 + +例如: + +- 创建 agent +- 编辑 agent +- 启动 agent +- 停止 agent +- 删除 agent +- 创建交易所配置 +- 修改交易所配置 +- 删除交易所配置 +- 创建策略 +- 编辑策略 +- 激活策略 +- 复制策略 +- 删除策略 +- 创建模型配置 +- 修改模型配置 +- 删除模型配置 + +这类任务应优先通过 skill 实现,避免自由规划。 + +### 二、诊断类任务 + +诊断类任务是指用户遇到了问题,需要助手帮助识别原因、缩小范围、给出修复步骤。 + +例如: + +- 某条报错是什么意思 +- 为什么模型 API 配置失败 +- 为什么交易所 API 连接不上 +- 为什么 agent 启动失败 +- 为什么策略没有执行 +- 为什么余额、仓位、收益统计不对 +- 为什么某个配置在前端能保存,但运行时报错 + +这类任务也应尽量 skill 化,形成稳定的排查路径,而不是每次从零分析。 + +### 三、指导类任务 + +指导类任务是指用户需要完成某项配置、接入、理解或选择,但不一定立刻触发系统动作。 + +例如: + +- 某个模型的 API key 去哪里申请 +- 某个模型的 base URL 和 model name 怎么填 +- 某个交易所 API key 怎么创建 +- 某个交易所权限应该怎么勾选 +- 某种策略适合什么市场环境 +- 某些交易指标怎么理解 + +这类任务应提供步骤化、实操型指导。 + +### 四、动态规划类任务 + +动态规划不是默认模式,而是兜底模式。 + +只有在以下情况下,才允许进入动态规划: + +- 用户请求跨越多个 skill +- 用户描述模糊,需要先探索再判断 +- 用户提出的是开放式交易问题 +- 用户的问题不属于已有 skill 覆盖范围 +- 需要组合查询、分析、判断和建议 + +动态规划可以存在,但必须受控,不能覆盖主路径。 + +## 多轮对话策略 + +### 一、优先延续当前任务 + +如果用户仍然在处理同一个任务,就继续当前任务,不要重新规划或重新路由。 + +例如: + +- 用户:帮我创建一个新的 BTC agent +- 助手:请提供交易所和模型配置 +- 用户:用我刚配的 DeepSeek + +这时应继续“创建 agent”这个任务,而不是重新理解成一个新的需求。 + +### 二、多轮对话以任务状态推进为核心 + +每个任务在多轮中都应该有明确状态,例如: + +- 已识别任务 +- 信息收集中 +- 等待用户确认 +- 执行中 +- 已完成 +- 执行失败,待修复 +- 已中断或已切换 + +助手应始终知道当前任务在哪个阶段,而不是每轮都从头开始解释世界。 + +### 三、只补齐缺失参数,不重复收集已有信息 + +如果一个 skill 已经定义了所需字段,那么多轮中的追问应只围绕缺失字段展开。 + +例如创建 agent 时,可能需要: + +- 名称 +- 交易所 +- 策略 +- 模型 +- 是否立即启动 + +如果其中三个字段已经确认,就不要重新追问这三个字段。 + +### 四、允许用户中途切换任务 + +如果用户明显改变了目标,助手应允许当前任务中断,并切换到新任务。 + +例如: + +- 当前任务:创建 agent +- 用户突然说:为什么我的交易所 API 报 invalid signature + +这时应切换到诊断类任务,而不是强行把用户拉回创建流程。 + +### 五、允许短暂插问,但尽量回到主任务 + +如果用户在当前任务中插入一个简短问题,助手可以先简要回答,再视情况回到主任务。 + +例如: + +- 用户正在创建策略 +- 中途问:逐仓和全仓有什么区别 + +助手可以先给简洁解释,再继续原任务。 + +### 六、对高风险动作单独确认 + +即使任务流程已经基本完成,只要最后一步属于高风险动作,也要在执行前单独确认。 + +例如: + +- 删除策略前确认 +- 启动实盘前确认 +- 覆盖已有配置前确认 + +## 记忆策略 + +### 一、记住对当前任务有用的信息 + +当前会话中,应保留以下内容: + +- 当前活跃任务 +- 已确认的参数 +- 用户明确表达过的选择 +- 仍然缺失的关键字段 +- 当前排障上下文 +- 最近一次确认结果 + +### 二、不把猜测当成记忆 + +以下内容不应被高强度依赖: + +- 助手自行推断但用户未确认的偏好 +- 早前对话中的过时信息 +- 与当前任务无关的旧上下文 +- 仅基于模糊表达做出的假设 + +如果有不确定性,应明确标注为“推测”或重新确认。 + +### 三、敏感信息只在必要范围内使用 + +对于 API key、密钥、凭证、账户等敏感信息: + +- 不要在回答中完整复述 +- 不要在无关任务中再次提起 +- 仅在当前任务确有需要时使用 +- 默认进行脱敏展示 + +## Skill 设计规范 + +每个 skill 都应服务于一个真实、完整、可交付的用户任务。 + +一个好的 skill 应当具备以下特点: + +- 范围足够聚焦,执行稳定 +- 范围又不能过小,能够完成完整任务 +- 输入要求清晰 +- 流程尽量确定 +- 成功和失败条件明确 +- 容易扩展和维护 + +每个 skill 至少应定义以下内容: + +- 处理的意图 +- 适用场景 +- 必填输入 +- 可选输入 +- 前置条件 +- 执行步骤 +- 缺少信息时如何追问 +- 哪些步骤需要确认 +- 成功后的输出格式 +- 常见失败情况 +- 对应的恢复建议 + +## 工具使用原则 + +工具只是 skill 或动态规划中的执行手段,不应成为助手行为设计的核心。 + +助手不应表现为: + +- 一个通用 API 调用器 +- 一个只会函数路由的壳 +- 一个对常规任务也反复规划的自治代理 + +默认顺序应为: + +1. 先判断是否有合适 skill +2. 在 skill 内部调用所需工具 +3. 如果没有 skill,再进入受限动态规划 +4. 最后才考虑通用探索式工具调用 + +## Skill 与 Tool 的分层原则 + +Skill 和 tool 不是同一层概念。 + +tool 是底层执行能力,skill 是面向用户任务的稳定流程。 + +默认架构应为: + +用户请求 -> 匹配 skill -> skill 内部调用 tool -> 返回结果 + +而不是: + +用户请求 -> 大模型直接在一堆底层 tool 中自由选择和规划 + +### 一、Skill 是面向任务的 + +skill 应围绕用户目标设计,例如: + +- 创建 agent +- 启动或停止 agent +- 配置交易所 API +- 诊断模型配置失败 +- 解释某类报错 + +skill 负责定义: + +- 要处理什么任务 +- 需要哪些输入 +- 缺信息时怎么追问 +- 执行顺序是什么 +- 哪些动作需要确认 +- 失败时怎么恢复 + +### 二、Tool 是面向执行的 + +tool 负责具体动作,不负责完整任务语义。 + +例如: + +- 读取当前模型配置 +- 保存交易所配置 +- 查询 trader 列表 +- 启动某个 trader +- 获取余额 +- 获取持仓 + +tool 更像“系统能力”或“执行接口”,而不是用户直接感知的工作单元。 + +### 三、优先把底层 tool 收敛到 skill 内部 + +在 skill-first 架构下,不应默认把大量底层 tool 直接暴露给大模型。 + +更合理的做法是: + +- 大模型优先决定使用哪个 skill +- skill 内部自己决定需要调用哪些 tool +- 用户不需要面对底层能力拆分 +- 模型也不需要在每次请求中重新拼装流程 + +### 四、可以直接暴露给大模型的,应当是高层 skill 化能力 + +如果某些能力需要以 function/tool 的形式提供给大模型,也应尽量保持高层抽象,而不是过度原子化。 + +较好的直接暴露方式: + +- `manage_trader` +- `manage_exchange_config` +- `manage_model_config` +- `manage_strategy` +- `diagnose_trader_start_failure` + +较差的直接暴露方式: + +- `get_model_list_then_find_enabled_one` +- `read_exchange_then_patch_field` +- `generic_api_request` +- 纯粹的 CRUD 原子碎片接口 + +也就是说,即使最终在技术实现上仍然使用 tool calling,这些 tool 也应该尽量表现为 skill,而不是裸露的底层零件。 + +### 五、只有在以下情况,才允许直接使用底层 tool + +- 当前请求没有匹配 skill +- 请求属于探索式、一次性、低频问题 +- 需要动态组合多个能力处理未知问题 +- 当前是在做诊断型探索,而不是执行标准流程 + +即使如此,也应优先限制范围,避免进入无边界的自由调用。 + +### 六、设计目标 + +引入 skill 的目的,不是让系统层次变复杂,而是让大模型少思考那些不需要思考的事情。 + +因此分层目标应是: + +- 高频任务由 skill 固化 +- 低层动作沉到 skill 内部 +- 大模型少接触原子化 tool +- 只有少数未知问题才进入动态规划 + +## 交易场景下的行为要求 + +交易助手必须让整体体验显得专业、谨慎、清晰。 + +这意味着: + +- 操作建议要结构化 +- 配置指导要准确 +- 风险提示要明确 +- 不确定性要说清楚 +- 不应伪装成对市场有绝对把握 + +当涉及交易建议时,应尽量区分: + +- 客观事实 +- 助手判断 +- 用户可执行的下一步 + +对于行情和策略分析,应优先给出条件化建议,而不是绝对判断。 + +例如应更倾向于: + +- 如果你是震荡思路,可以考虑…… +- 如果当前目标是降低回撤,优先检查…… +- 这个现象更像是配置问题,不一定是策略本身失效 + +而不是: + +- 这个市场一定会涨 +- 你应该马上开多 +- 这个策略就是最优解 + +## 默认处理流程 + +当用户发来请求时,助手默认按以下顺序处理: + +1. 先判断这是不是一个已知高频任务 +2. 如果是,直接进入对应 skill +3. 如果任务信息不完整,只追问继续执行所需的最少字段 +4. 如果属于诊断问题,先判断问题类型,再进入对应排查路径 +5. 如果属于开放式问题或跨 skill 问题,才进入动态规划 +6. 如果涉及高风险动作,在执行前单独确认 +7. 完成后给出简洁、明确、可执行的结果反馈 + +## 总结原则 + +本助手的核心不是“尽可能多地思考”,而是“在正确的地方思考”。 + +应当 skill 化的事情,就不要交给模型自由发挥。 +应当标准化的流程,就不要每次重新规划。 +应当确认的风险动作,就不要直接执行。 + +多轮对话的价值,在于持续推进任务、减少用户负担、提升交易操作质量。 + +## 当前落地状态 + +第一批诊断与配置类 skill 已开始沉淀,见: + +- `docs/agent-skills/diagnostic-skills.zh-CN.md` + +当前实现优先覆盖: + +- 模型 API 配置与诊断 +- 交易所 API 配置与诊断 +- trader 启动与运行诊断 +- 下单与仓位异常诊断 +- 策略与 prompt 生效问题诊断 + +## 当前能力分层建议 + +下面这部分用于指导后续 agent 重构:哪些现有能力适合继续保留给大模型,哪些应该下沉到 skill 内部,哪些应该弱化或移除。 + +### 一、建议保留为高层 skill 的能力 + +这些能力已经接近“用户任务”粒度,适合继续保留为高层入口。 + +- `manage_trader` +- `manage_exchange_config` +- `manage_model_config` +- `manage_strategy` +- `execute_trade` +- `get_positions` +- `get_balance` +- `get_trade_history` +- `search_stock` + +原因: + +- 用户会直接表达这类任务 +- 这些能力已经具备较完整的业务语义 +- 它们天然适合作为 skill 或 skill-like tool + +后续建议: + +- 保持这些能力对外稳定 +- 在其上继续补充确认规则、缺参追问规则和诊断分支 + +### 二、建议下沉到 skill 内部的能力 + +这些能力可以继续存在,但不应作为主要交互层暴露给大模型自由组合。 + +- 读取某个资源后再 patch 某个字段 +- 各类配置查询后再拼装参数 +- 针对单一字段的修改动作 +- 仅为执行中间步骤服务的查询动作 +- 各种“先查一下列表再让模型自己猜怎么用”的细碎能力 + +原因: + +- 这类能力更像流程零件 +- 一旦直接暴露给大模型,会导致每次都重新规划 +- 会让高频任务变得不稳定且冗长 + +原则上,这些动作应由 skill 内部封装完成,而不是让模型临场拼接。 + +### 三、建议弱化的能力形态 + +以下设计方向应尽量弱化: + +- 通用 `generic_api_request` +- 纯 CRUD 原子接口直接暴露给大模型 +- 没有任务语义的“万能工具” +- 需要模型自己理解完整调用顺序的碎片化接口 + +原因: + +- 这类能力过于底层 +- 会把流程控制权交还给模型 +- 与“80%% skill + 20%% 动态规划”的目标相冲突 + +### 四、建议新增的高层 skill 结构 + +后续不建议把高频管理操作拆成大量 `skill_create_xxx / skill_update_xxx` 形式。 + +更合理的方式是按“资源管理域”收敛为少量 management skill: + +- `trader_management` +- `exchange_management` +- `model_management` +- `strategy_management` + +这些 management skill 可以在内部继续复用现有: + +- `manage_trader` +- `manage_exchange_config` +- `manage_model_config` +- `manage_strategy` + +也就是说,现有高层管理工具可以作为 management skill 的执行底座,但不应继续承担全部对话策略。 + +#### management skill 的统一协议 + +每个 management skill 都应至少定义: + +- `action` +- `target_ref` +- `slots` +- `needs_confirmation` + +推荐结构如下: + +```json +{ + "skill": "exchange_management", + "action": "update", + "target_ref": { + "id": "optional", + "name": "主账户", + "alias": "optional" + }, + "slots": { + "passphrase": "xxx" + }, + "needs_confirmation": false +} +``` + +#### action 规则 + +不同 management skill 的 action 应集中定义,而不是散落在 prompt 中。 + +- `trader_management` + - `create` + - `update` + - `delete` + - `start` + - `stop` + - `query` +- `exchange_management` + - `create` + - `update` + - `delete` + - `query` +- `model_management` + - `create` + - `update` + - `delete` + - `query` +- `strategy_management` + - `create` + - `update` + - `delete` + - `activate` + - `duplicate` + - `query` + +#### reference 规则 + +management skill 不应要求用户总是提供精确 id,而应支持分层定位目标: + +1. 优先使用 `id` +2. 其次使用 `name` +3. 再其次使用 alias / 最近上下文引用 +4. 若命中多个对象,则要求用户明确选择 +5. 若未命中任何对象,则返回“未找到目标对象”,而不是猜测执行 + +#### slot 规则 + +每个 action 都应定义: + +- 必填 slots +- 可选 slots +- 自动推断规则 +- 缺失字段时的最小追问规则 + +例如: + +- `exchange_management.create` + - 必填:`exchange_type` + - 常见必填:`account_name`、凭证字段 +- `exchange_management.update` + - 必填:`target_ref` + - 其余只需要用户明确要改的字段 +- `trader_management.create` + - 必填:`name`、`exchange`、`model` + - 常见可选:`strategy`、`auto_start` + +#### confirmation 规则 + +management skill 内部必须按 action 级别区分风险,而不是统一处理。 + +- `delete` 默认必须确认 +- `start` / `stop` 视场景确认 +- `create` 通常可直接执行 +- `update` 若涉及关键配置变更,可要求确认 +- `query` 不需要确认 + +### 五、建议新增的诊断类 skill + +诊断类 skill 是交易助手体验差异化的关键。 + +建议优先固定以下能力: + +- `model_diagnosis` +- `exchange_diagnosis` +- `trader_diagnosis` +- `order_execution_diagnosis` +- `strategy_diagnosis` +- `balance_position_diagnosis` + +这些 skill 应优先基于: + +- 已有代码中的真实约束 +- 现有 troubleshooting 文档 +- 真实常见错误文案 +- 当前系统的实际运行逻辑 + +### 六、建议保留给动态规划的少数场景 + +以下场景仍然可以保留给 planner / ReAct: + +- 跨多个 skill 的复合任务 +- 用户目标表述模糊,需要先澄清再决定流程 +- 开放式交易问题 +- 一次性、低频、尚未固化的问题 +- 涉及诊断探索但还没有稳定 skill 的场景 + +动态规划应始终作为兜底层,而不是主路径。 + +### 七、最终目标分层 + +理想结构如下: + +1. 用户表达需求 +2. 系统先判断是否命中高频 skill +3. 若命中,则进入对应 skill 流程 +4. skill 内部调用现有管理类能力或查询能力 +5. 只有未命中 skill 时,才进入 planner + +长期目标不是“让 planner 更聪明”,而是“让 planner 更少出场”。 + +## `agent/tools.go` 重构清单 + +当前 `agent/tools.go` 中主要暴露了以下工具: + +- `get_preferences` +- `manage_preferences` +- `get_exchange_configs` +- `manage_exchange_config` +- `get_model_configs` +- `manage_model_config` +- `get_strategies` +- `manage_strategy` +- `manage_trader` +- `search_stock` +- `execute_trade` +- `get_positions` +- `get_balance` +- `get_market_price` +- `get_trade_history` + +下面给出按当前设计目标的建议分类。 + +### 一、建议继续保留为高层入口的工具 + +这些工具已经具备较完整的任务语义,短期内可以继续作为高层 skill-like tool 保留。 + +- `manage_exchange_config` +- `manage_model_config` +- `manage_strategy` +- `manage_trader` +- `execute_trade` + +原因: + +- 它们都对应明确的用户任务 +- 内部已经承载了一定业务语义 +- 后续可以直接继续向 skill 演进,而不是推倒重来 + +重构建议: + +- 保持接口稳定 +- 在 planner / prompt 层优先把它们当作 management skill 的执行底座使用 +- 后续逐步把对话语义前移到 `xxx_management` + +### 二、建议保留为“只读能力”但弱化对外存在感的工具 + +这些工具适合继续保留,但主要作为查询型能力存在,不应成为复杂任务的主流程控制中心。 + +- `get_exchange_configs` +- `get_model_configs` +- `get_strategies` +- `get_positions` +- `get_balance` +- `get_market_price` +- `get_trade_history` +- `search_stock` + +原因: + +- 它们更适合做信息补充和状态验证 +- 对诊断问题很有价值 +- 但不应该替代 task-level skill + +重构建议: + +- 继续保留 +- 主要用于: + - skill 内部验证 + - 诊断类 skill 查询当前状态 + - 明确的只读用户请求 +- 不要鼓励模型把它们当成“拼工作流”的基础零件反复组合 + +### 三、建议进一步收敛使用边界的工具 + +以下工具容易把模型带回到底层操作思维,应该明确边界。 + +- `get_preferences` +- `manage_preferences` + +原因: + +- 长期偏好记忆是辅助能力,不是交易任务主线 +- 如果让模型频繁自由改偏好,容易污染上下文 + +重构建议: + +- 仅在用户明确表达“记住/修改/删除长期偏好”时使用 +- 不要把偏好系统混进交易执行和排障主流程 + +### 四、建议前移为 management / diagnosis skill 的现有高层工具 + +下面这些现有高层工具虽然可用,但语义仍然过宽,建议后续逐步前移为 management / diagnosis skill。 + +#### 1. `manage_trader` + +建议逐步前移为: + +- `trader_management` +- `trader_diagnosis` + +原因: + +- 创建、修改、启动、停止、删除虽然动作不同,但属于同一资源管理域 +- 诊断路径和执行路径应分开 + +#### 2. `manage_exchange_config` + +建议逐步前移为: + +- `exchange_management` +- `exchange_diagnosis` + +原因: + +- CRUD / query 属于同一资源管理域 +- invalid signature / timestamp / IP 白名单问题需要单独诊断路径 + +#### 3. `manage_model_config` + +建议逐步前移为: + +- `model_management` +- `model_diagnosis` + +原因: + +- 模型对象管理应集中到一个 management skill +- provider 配置失败和运行失败应集中到 diagnosis skill + +#### 4. `manage_strategy` + +建议逐步前移为: + +- `strategy_management` +- `strategy_diagnosis` + +原因: + +- 策略模板管理和策略问题排查是两类不同任务 +- create / update / activate / duplicate / delete / query 可以统一在 management skill 内处理 + +### 五、当前最适合直接做成硬 skill 的第一批对象 + +如果后续开始从“prompt 约束”走向“真正 dispatcher + skill runner”,建议优先落以下几类: + +1. `create_trader` +2. `trader_management` +3. `exchange_management` +4. `model_management` +5. `exchange_diagnosis` +6. `model_diagnosis` +7. `trader_diagnosis` + +原因: + +- 这些最常见 +- 多轮价值最高 +- 失败成本高 +- 用户对稳定性的感知最强 + +### 六、最终目标 + +`agent/tools.go` 中的工具未来应逐步承担“skill 的执行底座”角色,而不是直接承担全部对话策略。 + +也就是说,长期理想状态是: + +- 文档层:按 skill 组织 +- 对话层:先匹配 skill +- 执行层:skill 内部复用现有 tool +- planner 层:只兜底少数复杂情况 diff --git a/api/handler_ai_model.go b/api/handler_ai_model.go index 6d1f89c1..f19b759c 100644 --- a/api/handler_ai_model.go +++ b/api/handler_ai_model.go @@ -240,10 +240,10 @@ func (s *Server) handleGetSupportedModels(c *gin.Context) { {"id": "gemini", "name": "Google Gemini", "provider": "gemini", "defaultModel": "gemini-3-pro-preview"}, {"id": "grok", "name": "Grok (xAI)", "provider": "grok", "defaultModel": "grok-3-latest"}, {"id": "kimi", "name": "Kimi (Moonshot)", "provider": "kimi", "defaultModel": "moonshot-v1-auto"}, - {"id": "minimax", "name": "MiniMax", "provider": "minimax", "defaultModel": "MiniMax-M2.5"}, + {"id": "minimax", "name": "MiniMax", "provider": "minimax", "defaultModel": "MiniMax-M2.7"}, {"id": "blockrun-base", "name": "BlockRun (Base Wallet)", "provider": "blockrun-base", "defaultModel": "auto"}, {"id": "blockrun-sol", "name": "BlockRun (Solana Wallet)", "provider": "blockrun-sol", "defaultModel": "auto"}, - {"id": "claw402", "name": "Claw402 (Base USDC)", "provider": "claw402", "defaultModel": "deepseek"}, + {"id": "claw402", "name": "Claw402 (Base USDC)", "provider": "claw402", "defaultModel": "deepseek-v4-flash"}, } c.JSON(http.StatusOK, supportedModels) diff --git a/api/handler_onboarding.go b/api/handler_onboarding.go index 7989ed56..27151bb4 100644 --- a/api/handler_onboarding.go +++ b/api/handler_onboarding.go @@ -10,6 +10,7 @@ import ( "strings" "nofx/logger" + "nofx/mcp/payment" "nofx/wallet" gethcrypto "github.com/ethereum/go-ethereum/crypto" @@ -54,7 +55,7 @@ func (s *Server) handleBeginnerOnboarding(c *gin.Context) { } if !reusedExisting { - if err := s.store.AIModel().Update(userID, "claw402", true, privateKey, "", "glm-5"); err != nil { + if err := s.store.AIModel().Update(userID, "claw402", true, privateKey, "", payment.DefaultClaw402Model); err != nil { logger.Errorf("Failed to save beginner claw402 config for user %s: %v", userID, err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save beginner model configuration"}) return @@ -68,7 +69,7 @@ func (s *Server) handleBeginnerOnboarding(c *gin.Context) { os.Setenv("CLAW402_WALLET_KEY", privateKey) os.Setenv("CLAW402_WALLET_ADDRESS", address) - os.Setenv("CLAW402_DEFAULT_MODEL", "glm-5") + os.Setenv("CLAW402_DEFAULT_MODEL", payment.DefaultClaw402Model) envSaved, envPath, envErr := persistBeginnerWalletEnv(privateKey, address) resp := beginnerOnboardingResponse{ @@ -77,7 +78,7 @@ func (s *Server) handleBeginnerOnboarding(c *gin.Context) { Chain: "base", Asset: "USDC", Provider: "claw402", - DefaultModel: "glm-5", + DefaultModel: payment.DefaultClaw402Model, ConfiguredModelID: configuredModelID, BalanceUSDC: wallet.QueryUSDCBalanceStr(address), EnvSaved: envSaved, @@ -253,7 +254,7 @@ func persistBeginnerWalletEnv(privateKey string, address string) (bool, string, if err := upsertEnvFile(path, map[string]string{ "CLAW402_WALLET_KEY": privateKey, "CLAW402_WALLET_ADDRESS": address, - "CLAW402_DEFAULT_MODEL": "glm-5", + "CLAW402_DEFAULT_MODEL": payment.DefaultClaw402Model, }); err != nil { lastErr = err continue diff --git a/docs/agent-skills/diagnostic-skills.zh-CN.md b/docs/agent-skills/diagnostic-skills.zh-CN.md new file mode 100644 index 00000000..96607e51 --- /dev/null +++ b/docs/agent-skills/diagnostic-skills.zh-CN.md @@ -0,0 +1,203 @@ +# NOFXi 诊断与配置 Skills(第一批) + +这份文档用于沉淀交易智能助手的第一批高频诊断与配置 skill。 + +目标不是让模型“更会想”,而是让它面对常见问题时,优先走稳定、可复用的排查路径。 + +## 设计原则 + +- 优先按 skill 回答,不要对高频问题重复自由规划 +- 先归类问题,再给出原因、检查项和修复建议 +- 能通过工具验证当前状态时,先查再下结论 +- 敏感信息只指导填写,不完整回显 +- 对结论不确定时,要明确标注为“更可能”或“优先怀疑” + +## skill_model_api_setup + +### 适用场景 + +- 用户问某个大模型的 API key 去哪里申请 +- 用户问 base URL 怎么填 +- 用户问 model name 怎么填 +- 用户问 OpenAI / Claude / Gemini / DeepSeek / Qwen / Kimi / Grok / MiniMax 怎么接入 + +### 处理策略 + +1. 先确认用户要配置哪个 provider +2. 告诉用户需要准备的最少字段: + - provider + - API key + - custom_api_url + - custom_model_name +3. 如果系统已有默认地址和默认模型名,优先给推荐值 +4. 回答按步骤组织,不要泛泛解释概念 + +### 已知实现事实 + +- 系统内置 provider 默认运行配置,见 `agent.resolveModelRuntimeConfig(...)` +- 常见 provider 已有默认 URL 和默认 model name + +## skill_model_config_diagnosis + +### 适用场景 + +- 模型保存成功但 agent 仍然不可用 +- 提示 AI unavailable +- 提示模型没启用 +- 提示 custom_api_url 不合法 +- 配置后 trader 不生效 + +### 优先排查 + +1. 是否存在已启用模型 +2. API key 是否为空 +3. custom_api_url 是否为合法 HTTPS 地址 +4. custom_model_name 是否为空或不匹配 +5. 当前 trader 是否绑定了这个模型 +6. 更新模型后是否已触发 trader reload + +### 已知实现事实 + +- 非 HTTPS 的 `custom_api_url` 会被后端拒绝,见 `api/handler_ai_model.go` +- 已启用模型如果缺少 API Key 或 URL,会导致 agent 无法就绪,见 `agent.ensureAIClientForStoreUser(...)` +- 更新模型配置后,系统会尝试移除并重载相关 trader,使新配置立即生效 + +### 输出格式 + +- 现象 +- 更可能原因 +- 先检查什么 +- 下一步怎么修复 + +## skill_exchange_api_setup + +### 适用场景 + +- 用户要新建交易所 API +- 用户不知道交易所需要哪些权限 +- 用户问 API key / secret / passphrase 分别填什么 + +### 通用处理策略 + +1. 先确认交易所类型 +2. 告知必须权限与禁止权限 +3. 告知是否需要额外字段 +4. 强调 IP 白名单与权限配置 +5. 引导用户回到系统内完成绑定 + +### 特殊规则 + +- OKX 除 API Key 和 Secret 外,还需要 passphrase +- Bybit 永续/合约交易需要合约权限 +- 不建议开启提现权限 + +### 参考文档 + +- `docs/getting-started/okx-api.md` +- `docs/getting-started/bybit-api.md` + +## skill_exchange_api_diagnosis + +### 适用场景 + +- `invalid signature` +- `timestamp` 错误 +- `IP not allowed` +- `permission denied` +- 交易所连接不上 + +### 优先排查 + +1. 系统时间是否同步 +2. API Key / Secret 是否正确 +3. 是否遗漏额外字段,如 OKX passphrase +4. IP 白名单是否包含当前服务器 +5. 是否启用了交易或合约权限 +6. 密钥是否过期或已重建 + +### 已知实现事实 + +- 时间不同步是 `invalid signature` / `timestamp` 的高频根因,见 `docs/guides/TROUBLESHOOTING.zh-CN.md` +- OKX 的 passphrase 缺失会导致签名相关问题,见 `docs/getting-started/okx-api.md` + +### 输出格式 + +- 报错现象 +- 最常见根因 +- 优先检查顺序 +- 修复步骤 + +## skill_trader_start_diagnosis + +### 适用场景 + +- trader 启动不了 +- trader 启动了但没开始交易 +- 页面显示已启动但一直没有动作 +- 用户怀疑 strategy / model / exchange 绑定有问题 + +### 优先排查 + +1. 是否有已启用的模型配置 +2. 是否有已启用的交易所配置 +3. trader 是否绑定了 exchange_id / strategy_id / ai_model_id +4. 交易所余额和权限是否满足下单条件 +5. AI 最近的决策到底是 wait、hold 还是下单失败 + +### 回答原则 + +- 要区分“没启动”“启动了但 AI 选择不交易”“尝试下单但失败”这三类 +- 不要把“没开仓”直接等同于“系统故障” + +## skill_order_execution_diagnosis + +### 适用场景 + +- 下单失败 +- 只开空不开户 / 只开单边 +- 杠杆报错 +- position side mismatch + +### 优先排查 + +1. 账户模式是否匹配,例如 Binance 是否为 Hedge Mode +2. 是否为子账户杠杆限制 +3. 合约权限是否开启 +4. 余额、保证金、可交易 symbol 是否满足条件 + +### 已知实现事实 + +- Binance 在 One-way Mode 下,可能出现 `position side mismatch` 或单边行为 +- 某些子账户杠杆上限较低,超过限制会直接失败 +- 这些问题在 `docs/guides/TROUBLESHOOTING.md` 已有明确说明 + +## skill_strategy_diagnosis + +### 适用场景 + +- 用户说策略没生效 +- 用户说 prompt 预览和实际不一致 +- 用户说修改策略后 trader 行为没有变化 + +### 优先排查 + +1. 当前编辑的是策略模板,还是 trader 的 custom prompt +2. 策略是否真的保存成功 +3. 是否需要重新读取当前配置做对比 +4. 用户说的“没生效”是指未保存、未绑定,还是运行结果与预期不一致 + +### 回答原则 + +- 先明确“对象”再排查:strategy template / trader / prompt override +- 如果能读取当前保存值,就不要凭印象判断 + +## 后续扩展方向 + +下一批可以继续补: + +- `skill_balance_and_position_diagnosis` +- `skill_market_data_diagnosis` +- `skill_prompt_generation_diagnosis` +- `skill_strategy_test_run_diagnosis` +- `skill_exchange_specific_setup_` +- `skill_model_provider_setup_` diff --git a/docs/architecture/AGENT_CURRENT_DESIGN.zh-CN.md b/docs/architecture/AGENT_CURRENT_DESIGN.zh-CN.md new file mode 100644 index 00000000..40f93977 --- /dev/null +++ b/docs/architecture/AGENT_CURRENT_DESIGN.zh-CN.md @@ -0,0 +1,613 @@ +# 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_` + +字段: + +- `CurrentGoal` +- `ActiveFlow` +- `OpenLoops` +- `ImportantFacts` +- `LastDecision` +- `UpdatedAt` + +适合存: + +- 当前高层目标 +- 跨轮次仍然成立的未闭环事项 +- 关键事实 +- 最近一次重要决策及其原因 + +不适合存: + +- step 级待办 +- “下一步调用哪个 tool” +- 动态余额、持仓、配置存在性 +- 任何可以通过 tool 重新读取的实时状态 + +### 3. ExecutionState + +定义位置: + +- `agent/execution_state.go` + +作用: + +- 保存当前 plan 的执行态 +- 支持 `ask_user` 之后继续执行 +- 保存 plan、当前步骤、执行日志、等待状态等 + +持久化 key: + +- `agent_execution_state_` + +当前关键字段: + +- `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 的实际运行设计。 diff --git a/docs/architecture/AGENT_MEMORY_AND_PLANNING.md b/docs/architecture/AGENT_MEMORY_AND_PLANNING.md new file mode 100644 index 00000000..7179bb17 --- /dev/null +++ b/docs/architecture/AGENT_MEMORY_AND_PLANNING.md @@ -0,0 +1,454 @@ +# NOFXi Agent Memory And Planning Design + +## Purpose + +This document explains how the current NOFXi agent handles: + +- short-term conversation memory +- durable task memory +- durable execution / planning state +- planner execution and replanning +- state reset and resume behavior + +The implementation described here is primarily in: + +- `agent/history.go` +- `agent/memory.go` +- `agent/execution_state.go` +- `agent/planner_runtime.go` +- `agent/agent.go` + +## High-Level Model + +The current agent uses three different layers of state: + +1. `chatHistory` +Recent in-memory user/assistant turns for the live conversation. + +2. `TaskState` +Durable summarized context that should survive beyond recent turns. + +3. `ExecutionState` +Durable workflow state for the currently running or recently blocked plan. + +These three layers serve different purposes and should not be treated as the same thing. + +## State Layers + +### 1. `chatHistory` + +Defined in `agent/history.go`. + +Role: + +- stores recent `user` / `assistant` messages in memory +- keyed by `userID` +- used as short-term conversational context +- acts as the source material for later compression into `TaskState` + +Characteristics: + +- in-memory only +- capped by `maxTurns` +- cleared by `/clear` +- not suitable as durable truth + +Typical contents: + +- the last few user questions +- the last few assistant replies +- temporary conversational wording + +### 2. `TaskState` + +Defined in `agent/memory.go`. + +Role: + +- stores durable, structured, non-derivable context +- persisted through `system_config` +- injected into planning and reasoning prompts + +Storage key: + +- `agent_task_state_` + +Fields: + +- `CurrentGoal` +- `ActiveFlow` +- `OpenLoops` +- `ImportantFacts` +- `LastDecision` +- `UpdatedAt` + +Intended contents: + +- user goal that still matters across turns +- high-level unresolved issues that still matter across turns +- facts that tools cannot cheaply re-fetch +- latest important decision summary + +Explicitly not intended for: + +- step-level pending items such as "wait for API key" +- execution actions such as "call get_exchange_configs" +- live balances +- current positions +- current market prices +- mutable configuration availability + +Those should be checked from tools at planning time instead of being trusted from old summaries. + +### 3. `ExecutionState` + +Defined in `agent/execution_state.go`. + +Role: + +- stores the current execution workflow +- allows the agent to resume after `ask_user` +- persists plan steps, observations, and completion status + +Storage key: + +- `agent_execution_state_` + +Fields: + +- `SessionID` +- `UserID` +- `Goal` +- `Status` +- `PlanID` +- `Steps` +- `CurrentStepID` +- `Observations` +- `FinalAnswer` +- `LastError` +- `UpdatedAt` + +This is the planner's working state, not a general memory store. + +## Data Flow + +### Request Entry + +Entry points: + +- `HandleMessage(...)` +- `HandleMessageStream(...)` + +Flow: + +1. user message enters `agent` +2. slash commands and explicit direct branches are handled first +3. all other requests go into planner flow via `thinkAndAct(...)` / `thinkAndActStream(...)` + +### Planner Flow + +The planner pipeline in `agent/planner_runtime.go` is: + +1. append user message into `chatHistory` +2. emit `planning` SSE event +3. load `ExecutionState` +4. optionally reset stale `ExecutionState` +5. optionally refresh dynamic configuration snapshots +6. create a fresh execution plan with the LLM +7. execute steps one by one +8. persist `ExecutionState` after important transitions +9. append assistant answer into `chatHistory` +10. maybe compress old conversation into `TaskState` + +## Short-Term vs Durable Memory + +### What lives in `chatHistory` + +Good fits: + +- raw recent messages +- conversational wording +- latest assistant phrasing + +Bad fits: + +- long-lived truths +- current external system state + +### What lives in `TaskState` + +Good fits: + +- durable goal +- high-level unfinished work that remains relevant across turns +- important facts the user stated +- previous decisions and why they were made + +Bad fits: + +- pending steps inside the current plan +- execution-level reminders such as "wait for a field" or "call a tool" +- old conclusions about whether tools exist +- old conclusions about whether model/exchange config is present +- live operational state that can change outside the chat + +### What lives in `ExecutionState` + +Good fits: + +- current plan steps +- observations from tool calls +- blocked-on-user-input status +- exact current workflow state +- step-level pending work and block reasons + +Bad fits: + +- evergreen user profile +- long-term semantic memory + +## Planning Logic + +### Plan Creation + +`createExecutionPlan(...)` sends the following into the planner model: + +- available tool definitions +- persistent preferences +- `TaskState` context +- `ExecutionState` JSON +- current user request + +The planner must return JSON only with step types: + +- `tool` +- `reason` +- `ask_user` +- `respond` + +### Step Execution + +`executePlan(...)` executes the plan loop: + +- `tool` + call tool and append observation +- `reason` + run reasoning sub-call and append observation +- `ask_user` + save `waiting_user` state and return question +- `respond` + generate final answer and mark completed + +After each completed step, `replanAfterStep(...)` may: + +- continue +- replace remaining steps +- ask user +- finish + +## Resume Behavior + +When `ExecutionState.Status == waiting_user`, the next user turn is treated as a reply to the pending question. + +Current safeguards: + +- latest asked question is extracted from the stored plan +- the user reply is appended as a `user_reply` observation +- planner prompt receives explicit `Resume context` + +This prevents short replies like `是` from being misread as unrelated fresh intents as often as before. + +## Dynamic State Refresh + +Configuration and trader management requests are dynamic by nature. Their truth can change outside the current chat, for example: + +- user configures exchange in the UI +- user adds model in another tab +- user creates trader elsewhere + +Because of that, configuration/trader requests should not trust stale model conclusions. + +Current protection in `planner_runtime.go`: + +- detects config / trader intent with `isConfigOrTraderIntent(...)` +- clears `TaskState` context from the planner prompt for these requests +- refreshes `ExecutionState.Observations` with fresh snapshots from: + - `toolGetModelConfigs(...)` + - `toolGetExchangeConfigs(...)` + - `toolListTraders(...)` + +This makes the planner rely more on current system state and less on older narrative memory. + +## Reset Strategy + +The system currently resets or weakens stale execution state when: + +- user says retry-like phrases such as `再试`, `继续`, `try again`, `continue` +- request is config / trader related and old execution state is failed / completed / waiting + +Reset scope: + +- `ExecutionState` may be cleared +- `TaskState` is not globally deleted, but it is intentionally ignored for config/trader planning + +Manual reset: + +- `/clear` + +This clears: + +- short-term chat history +- task state +- execution state + +## Compression Design + +`maybeCompressHistory(...)` moves older short-term chat content into `TaskState` when: + +- recent message count exceeds the configured window +- estimated token count exceeds the threshold + +Compression strategy: + +1. keep recent conversation in `chatHistory` +2. summarize older turns into structured `TaskState` +3. persist new `TaskState` +4. replace `chatHistory` with recent slice + +Important design rule: + +- `TaskState` should keep durable context only +- it should not become a stale copy of mutable operational state + +## Current Architecture Diagram + +```mermaid +flowchart TD + U[User Message] --> A[HandleMessage / HandleMessageStream] + A --> B{Direct command?} + B -->|Yes| C[Direct branch or slash command] + B -->|No| D[thinkAndAct / thinkAndActStream] + + D --> E[Append user turn to chatHistory] + D --> F[Load ExecutionState] + F --> G{waiting_user?} + G -->|Yes| H[Attach user_reply observation] + G -->|No| I[Create fresh ExecutionState] + + H --> J[Refresh dynamic snapshots if config/trader intent] + I --> J + J --> K[createExecutionPlan via LLM] + K --> L[Execution plan] + L --> M[executePlan loop] + + M --> N[tool step] + M --> O[reason step] + M --> P[ask_user step] + M --> Q[respond step] + + N --> R[Append Observation] + O --> R + R --> S[replanAfterStep] + S --> M + + P --> T[Persist waiting_user ExecutionState] + T --> UQ[Return question to user] + + Q --> V[Persist completed ExecutionState] + V --> W[Append assistant turn to chatHistory] + W --> X[maybeCompressHistory] + X --> Y[Persist TaskState] + Y --> Z[Final response] +``` + +## Memory Relationship Diagram + +```mermaid +flowchart LR + CH[chatHistory\nin-memory\nrecent turns] + TS[TaskState\npersisted summary\nsystem_config] + ES[ExecutionState\npersisted workflow\nsystem_config] + PL[Planner Prompt] + + CH -->|recent raw turns| PL + ES -->|current workflow JSON| PL + TS -->|durable structured context| PL + + CH -->|old turns compressed| TS + PL -->|plan / observations / status| ES +``` + +## State Transition Diagram + +```mermaid +stateDiagram-v2 + [*] --> planning + planning --> running: plan created + running --> waiting_user: ask_user step + waiting_user --> planning: user replies + running --> completed: respond step finished + running --> failed: step error + failed --> planning: retry / continue / config-trader reset + completed --> planning: new relevant request or retry flow +``` + +## Known Design Tradeoffs + +### Strengths + +- separates short-term chat from durable task summary +- allows blocked flows to resume +- supports replanning after every meaningful step +- can recover from stale assumptions better for dynamic config/trader requests + +### Weaknesses + +- `TaskState` is still summary-driven, so summarization quality matters +- planner still depends on model compliance for some transitions +- `ExecutionState` is single-track per user, not multiple concurrent workflows +- config/trader intent detection is heuristic and keyword-based + +## Practical Guidance + +### When to trust `TaskState` + +Trust it for: + +- user intent continuity +- open loops +- durable facts + +Do not trust it for: + +- whether current exchange/model/trader config exists now +- whether a specific operational action is currently possible + +### When to trust `ExecutionState` + +Trust it for: + +- current plan continuity +- exact blocked step +- latest observation chain + +Do not trust it blindly when: + +- user has changed configuration outside the chat +- the system capabilities changed after deployment + +### When to fetch live state again + +Always prefer fresh tool snapshots before answering about: + +- existing model configs +- existing exchange configs +- existing traders +- whether trader creation can proceed + +## Suggested Future Improvements + +- add workflow versioning so capability changes invalidate stale `ExecutionState` +- separate `waiting_user_confirmation` from generic `waiting_user` +- introduce code-level handling for short confirmations such as `是`, `好`, `继续` +- move dynamic state refresh from heuristic to explicit planner preflight stage +- support multiple concurrent execution sessions per user if needed diff --git a/docs/architecture/AGENT_MEMORY_AND_PLANNING.zh-CN.md b/docs/architecture/AGENT_MEMORY_AND_PLANNING.zh-CN.md new file mode 100644 index 00000000..5dd1e2d8 --- /dev/null +++ b/docs/architecture/AGENT_MEMORY_AND_PLANNING.zh-CN.md @@ -0,0 +1,453 @@ +# NOFXi Agent 记忆与规划设计 + +## 目的 + +本文说明当前 NOFXi agent 是如何处理以下能力的: + +- 短期对话记忆 +- 持久化任务记忆 +- 持久化执行态 / 规划态 +- planner 的执行与重规划 +- 状态重置与恢复 + +本文主要对应以下实现文件: + +- `agent/history.go` +- `agent/memory.go` +- `agent/execution_state.go` +- `agent/planner_runtime.go` +- `agent/agent.go` + +## 总体模型 + +当前 agent 使用三层不同的状态: + +1. `chatHistory` +用于保存当前会话最近几轮的原始用户/助手对话,驻留内存。 + +2. `TaskState` +用于保存跨轮次仍然有价值的结构化摘要,持久化存储。 + +3. `ExecutionState` +用于保存当前规划流程的执行态,支持流程中断后的继续执行。 + +这三层职责不同,不能混为一谈。 + +## 三层状态 + +### 1. `chatHistory` + +定义位置:`agent/history.go` + +作用: + +- 按 `userID` 保存最近的 `user` / `assistant` 消息 +- 作为短期对话上下文 +- 作为后续压缩进 `TaskState` 的原始素材 + +特性: + +- 仅在内存中存在 +- 有 `maxTurns` 上限 +- `/clear` 时会清空 +- 不适合作为长期真相来源 + +典型内容: + +- 最近几轮用户问题 +- 最近几轮助手回答 +- 临时措辞与上下文表达 + +### 2. `TaskState` + +定义位置:`agent/memory.go` + +作用: + +- 保存持久化、结构化、不可轻易从工具重新推导出的上下文 +- 通过 `system_config` 持久化 +- 注入到 planner / reasoning prompt 中 + +存储 key: + +- `agent_task_state_` + +字段: + +- `CurrentGoal` +- `ActiveFlow` +- `OpenLoops` +- `ImportantFacts` +- `LastDecision` +- `UpdatedAt` + +适合存放: + +- 当前仍有效的用户目标 +- 跨轮次仍然成立的高层未闭环问题 +- 无法简单通过工具重新读取的重要事实 +- 最近一次关键决策及原因 + +不适合存放: + +- “等用户提供 API Key” 这类 step 级待办 +- “调用 get_exchange_configs” 这类执行动作 +- 实时余额 +- 当前持仓 +- 当前行情价格 +- 是否存在某个配置这类会变化的状态 + +这些动态信息应该在规划阶段通过工具重新检查,而不是相信旧摘要。 + +### 3. `ExecutionState` + +定义位置:`agent/execution_state.go` + +作用: + +- 保存当前执行中的工作流状态 +- 支持 `ask_user` 之后恢复执行 +- 持久化保存计划步骤、观察结果和最终状态 + +存储 key: + +- `agent_execution_state_` + +字段: + +- `SessionID` +- `UserID` +- `Goal` +- `Status` +- `PlanID` +- `Steps` +- `CurrentStepID` +- `Observations` +- `FinalAnswer` +- `LastError` +- `UpdatedAt` + +它是 planner 的“工作态”,不是通用记忆仓库。 + +## 数据流 + +### 请求入口 + +入口函数: + +- `HandleMessage(...)` +- `HandleMessageStream(...)` + +流程: + +1. 用户消息进入 `agent` +2. 优先处理 slash command 和显式直达分支 +3. 其余请求进入 planner 流程:`thinkAndAct(...)` / `thinkAndActStream(...)` + +### Planner 主流程 + +`agent/planner_runtime.go` 中的 planner 管线如下: + +1. 把用户消息加入 `chatHistory` +2. 发出 `planning` SSE 事件 +3. 加载 `ExecutionState` +4. 视情况重置过期的 `ExecutionState` +5. 视情况刷新动态配置快照 +6. 调用 LLM 生成新的执行计划 +7. 按步骤执行计划 +8. 在关键状态变化后持久化 `ExecutionState` +9. 把助手回答加入 `chatHistory` +10. 视情况把旧对话压缩进 `TaskState` + +## 短期记忆 vs 持久记忆 + +### `chatHistory` 里应该放什么 + +适合: + +- 最近原始消息 +- 对话措辞 +- 最近一轮助手的表达方式 + +不适合: + +- 长期真相 +- 外部系统当前状态 + +### `TaskState` 里应该放什么 + +适合: + +- 持续目标 +- 跨轮次仍有意义的高层未闭环事项 +- 用户明确讲过的重要事实 +- 历史关键决策和原因 + +不适合: + +- 当前 plan 中尚未执行的步骤 +- “等待某个字段”“调用某个 tool” 这类执行级待办 +- “系统有没有这个工具” 这种过时结论 +- “当前有没有模型/交易所配置” 这种可变化状态 +- 可以通过工具重新查询到的动态状态 + +### `ExecutionState` 里应该放什么 + +适合: + +- 当前计划步骤 +- 工具调用观察结果 +- 当前是否卡在等用户补充信息 +- 当前工作流的精确执行位置 +- step 级待办和阻塞原因 + +不适合: + +- 长期用户画像 +- 通用长期语义记忆 + +## 规划逻辑 + +### 计划生成 + +`createExecutionPlan(...)` 会把以下信息送给 planner 模型: + +- 当前可用 tool 定义 +- 持久化用户偏好 +- `TaskState` 上下文 +- `ExecutionState` JSON +- 当前用户请求 + +planner 必须返回 JSON,且步骤类型只能是: + +- `tool` +- `reason` +- `ask_user` +- `respond` + +### 步骤执行 + +`executePlan(...)` 的执行循环如下: + +- `tool` + 调用工具并写入 observation +- `reason` + 发起 reasoning 子调用并写入 observation +- `ask_user` + 保存 `waiting_user` 状态并把问题返回给用户 +- `respond` + 生成最终回答并标记完成 + +每个步骤结束后,`replanAfterStep(...)` 还可以决定: + +- continue +- replace_remaining +- ask_user +- finish + +## 恢复执行 + +当 `ExecutionState.Status == waiting_user` 时,下一条用户消息会被视为对上一轮追问的回复。 + +当前保护机制: + +- 从已有 plan 中提取最近一次追问内容 +- 将用户回复作为 `user_reply` observation 追加 +- 在 planner prompt 中注入显式的 `Resume context` + +这样可以减少用户只回复 `是` 这类短消息时,被错误理解成全新意图的情况。 + +## 动态状态刷新 + +配置类与 trader 管理类请求本质上是动态请求,它们的真相可能在聊天之外发生变化,例如: + +- 用户在 Web UI 中配置了交易所 +- 用户在另一个页面新增了模型 +- 用户在别处创建了 trader + +因此,这类请求不能依赖旧的模型结论。 + +当前在 `planner_runtime.go` 中的保护措施: + +- 通过 `isConfigOrTraderIntent(...)` 检测配置 / trader 意图 +- 这类请求在 planner prompt 中不再注入旧 `TaskState` +- 同时刷新 `ExecutionState.Observations` 中的实时快照: + - `toolGetModelConfigs(...)` + - `toolGetExchangeConfigs(...)` + - `toolListTraders(...)` + +这样 planner 会更多依赖当前系统状态,而不是依赖旧记忆中的描述。 + +## 重置策略 + +当前系统在以下场景会重置或弱化旧执行态: + +- 用户说了类似 `再试`、`继续`、`try again`、`continue` +- 当前请求是配置 / trader 相关,并且旧 `ExecutionState` 已经失败 / 完成 / 正在等待用户 + +重置范围: + +- `ExecutionState` 可能会被清空 +- `TaskState` 不会整体删除,但在配置 / trader 请求中会被主动忽略 + +手动清理: + +- `/clear` + +这条命令会清掉: + +- 短期 chat history +- task state +- execution state + +## 压缩设计 + +`maybeCompressHistory(...)` 会在以下条件满足时把旧的短期对话压缩进 `TaskState`: + +- 最近消息数超过窗口 +- 估算 token 数超过阈值 + +压缩流程: + +1. 保留最近若干轮对话在 `chatHistory` +2. 把更早的内容总结成结构化 `TaskState` +3. 持久化新的 `TaskState` +4. 用最近消息切片替换 `chatHistory` + +重要设计原则: + +- `TaskState` 只保留长期有效上下文 +- 不能把它变成动态运营状态的陈旧副本 + +## 当前架构图 + +```mermaid +flowchart TD + U[用户消息] --> A[HandleMessage / HandleMessageStream] + A --> B{是否命中直达分支?} + B -->|是| C[直接处理 slash command 或快捷分支] + B -->|否| D[thinkAndAct / thinkAndActStream] + + D --> E[写入 chatHistory] + D --> F[加载 ExecutionState] + F --> G{是否 waiting_user?} + G -->|是| H[追加 user_reply observation] + G -->|否| I[创建新的 ExecutionState] + + H --> J[若为配置或 trader 请求则刷新动态快照] + I --> J + J --> K[createExecutionPlan 调用 LLM] + K --> L[得到 execution plan] + L --> M[executePlan 循环执行] + + M --> N[tool step] + M --> O[reason step] + M --> P[ask_user step] + M --> Q[respond step] + + N --> R[写入 Observation] + O --> R + R --> S[replanAfterStep] + S --> M + + P --> T[持久化 waiting_user ExecutionState] + T --> UQ[向用户返回追问] + + Q --> V[持久化 completed ExecutionState] + V --> W[把 assistant 回复写入 chatHistory] + W --> X[maybeCompressHistory] + X --> Y[持久化 TaskState] + Y --> Z[返回最终回答] +``` + +## 记忆关系图 + +```mermaid +flowchart LR + CH[chatHistory\n内存态\n最近对话] + TS[TaskState\n持久化摘要\nsystem_config] + ES[ExecutionState\n持久化执行态\nsystem_config] + PL[Planner Prompt] + + CH -->|最近原始对话| PL + ES -->|当前工作流 JSON| PL + TS -->|长期结构化上下文| PL + + CH -->|旧消息压缩| TS + PL -->|计划 / 观察 / 状态| ES +``` + +## 状态转换图 + +```mermaid +stateDiagram-v2 + [*] --> planning + planning --> running: plan created + running --> waiting_user: ask_user step + waiting_user --> planning: user replies + running --> completed: respond step finished + running --> failed: step error + failed --> planning: retry / continue / config-trader reset + completed --> planning: new relevant request or retry flow +``` + +## 当前设计的取舍 + +### 优点 + +- 将短期对话与长期摘要分离 +- 支持在 `ask_user` 之后恢复执行 +- 每个关键步骤后都支持重规划 +- 对配置 / 创建 trader 这类动态请求,已经能更好抵抗旧结论污染 + +### 缺点 + +- `TaskState` 的质量仍然依赖总结效果 +- 某些恢复逻辑仍依赖模型是否听话 +- 每个用户当前只有一条 `ExecutionState`,不支持多个并发工作流 +- 配置 / trader 意图识别目前仍是关键词启发式 + +## 实践建议 + +### 什么时候该相信 `TaskState` + +应该相信它用于: + +- 延续用户目标 +- 跟踪未完成事项 +- 保留长期有效事实 + +不应该相信它用于: + +- 当前是否存在模型 / 交易所 / trader 配置 +- 当前是否能够执行某个操作 + +### 什么时候该相信 `ExecutionState` + +应该相信它用于: + +- 当前工作流是否仍然连续 +- 当前阻塞在哪一步 +- 最近的 observation 链条 + +不应该盲信它用于: + +- 用户在聊天外已经修改过配置的场景 +- 系统能力或工具集发生变化后的旧结论 + +### 什么时候必须重新获取实时状态 + +以下场景应该优先重新通过工具获取: + +- 当前模型配置 +- 当前交易所配置 +- 当前 trader 列表 +- 当前是否满足 trader 创建条件 + +## 后续建议 + +- 为 `ExecutionState` 增加版本号或能力签名,能力变化时自动失效 +- 将 `waiting_user_confirmation` 与通用 `waiting_user` 分开 +- 对 `是`、`好`、`继续` 这类短确认增加代码级识别 +- 将动态快照刷新从启发式升级为显式 planner 预检查阶段 +- 如果后续需要,支持一个用户多条并发执行会话 diff --git a/docs/i18n/ja/README.md b/docs/i18n/ja/README.md index 763fa7b2..d57df2e3 100644 --- a/docs/i18n/ja/README.md +++ b/docs/i18n/ja/README.md @@ -45,6 +45,20 @@ curl -fsSL https://raw.githubusercontent.com/NoFxAiOS/nofx/main/install.sh | bas --- +## クイックデモ + +

+ + NOFX クイックデモ動画 + +

+ +

+ カバー画像をクリックするとデモ動画を視聴できます。 +

+ +--- + ## x402 の仕組み 従来のフロー:アカウント登録 → クレジット購入 → API キー取得 → クォータ管理 → キーのローテーション。 diff --git a/docs/i18n/ko/README.md b/docs/i18n/ko/README.md index 41b30697..e2833e2c 100644 --- a/docs/i18n/ko/README.md +++ b/docs/i18n/ko/README.md @@ -45,6 +45,20 @@ curl -fsSL https://raw.githubusercontent.com/NoFxAiOS/nofx/main/install.sh | bas --- +## 빠른 데모 + +

+ + NOFX 빠른 데모 영상 + +

+ +

+ 커버 이미지를 클릭하면 데모 영상을 볼 수 있습니다. +

+ +--- + ## x402 작동 방식 기존 플로우: 계정 등록 → 크레딧 구매 → API 키 받기 → 쿼터 관리 → 키 교체. diff --git a/docs/i18n/ru/README.md b/docs/i18n/ru/README.md index bded307f..cc27a119 100644 --- a/docs/i18n/ru/README.md +++ b/docs/i18n/ru/README.md @@ -45,6 +45,20 @@ curl -fsSL https://raw.githubusercontent.com/NoFxAiOS/nofx/main/install.sh | bas --- +## Быстрое демо + +

+ + Видео быстрого демо NOFX + +

+ +

+ Нажмите на изображение обложки, чтобы посмотреть демо-видео. +

+ +--- + ## Как работает x402 Традиционный процесс: регистрация → покупка кредитов → получение API ключа → управление квотой → ротация ключей. diff --git a/docs/i18n/uk/README.md b/docs/i18n/uk/README.md index 30a8b2ce..2605ff82 100644 --- a/docs/i18n/uk/README.md +++ b/docs/i18n/uk/README.md @@ -45,6 +45,20 @@ curl -fsSL https://raw.githubusercontent.com/NoFxAiOS/nofx/main/install.sh | bas --- +## Швидке демо + +

+ + Відео швидкого демо NOFX + +

+ +

+ Натисніть на зображення обкладинки, щоб переглянути демо-відео. +

+ +--- + ## Як працює x402 Традиційний процес: реєстрація → купівля кредитів → отримання API ключа → управління квотою → ротація ключів. diff --git a/docs/i18n/vi/README.md b/docs/i18n/vi/README.md index e91e4679..ccb711f0 100644 --- a/docs/i18n/vi/README.md +++ b/docs/i18n/vi/README.md @@ -45,6 +45,20 @@ Mở **http://127.0.0.1:3000**. Xong. --- +## Demo nhanh + +

+ + Video demo nhanh của NOFX + +

+ +

+ Nhấp vào ảnh bìa để xem video demo. +

+ +--- + ## x402 hoạt động như thế nào Quy trình truyền thống: đăng ký tài khoản → mua credits → lấy API key → quản lý quota → xoay key. diff --git a/docs/i18n/zh-CN/README.md b/docs/i18n/zh-CN/README.md index eb224beb..48560f2e 100644 --- a/docs/i18n/zh-CN/README.md +++ b/docs/i18n/zh-CN/README.md @@ -47,6 +47,20 @@ curl -fsSL https://raw.githubusercontent.com/NoFxAiOS/nofx/main/install.sh | bas --- +## 快速演示 + +

+ + NOFX 快速演示视频 + +

+ +

+ 点击封面图即可观看 Demo 视频。 +

+ +--- + ## x402 如何工作 传统流程:注册账号 → 购买额度 → 获取 API Key → 管理配额 → 轮换密钥。 diff --git a/main.go b/main.go index f3e0e4d5..351fcce3 100644 --- a/main.go +++ b/main.go @@ -168,7 +168,7 @@ func main() { } logger.Info("✅ HTTP server stopped") - logger.Info("✅ NOFXi agent stopped") + // nofxiAgent.Stop() is handled by defer above // Stop all traders traderManager.StopAll() diff --git a/mcp/payment/claw402.go b/mcp/payment/claw402.go index f28ca1ca..38edbb6b 100644 --- a/mcp/payment/claw402.go +++ b/mcp/payment/claw402.go @@ -50,7 +50,7 @@ func shortAddr(addr string) string { const ( DefaultClaw402URL = "https://claw402.ai" - DefaultClaw402Model = "glm-5" + DefaultClaw402Model = "deepseek-v4-flash" ) // claw402ModelEndpoints maps user-friendly model names to claw402 API paths. @@ -65,6 +65,8 @@ var claw402ModelEndpoints = map[string]string{ // DeepSeek "deepseek": "/api/v1/ai/deepseek/chat", "deepseek-reasoner": "/api/v1/ai/deepseek/chat/reasoner", + "deepseek-v4-flash": "/api/v1/ai/deepseek/v4-flash", + "deepseek-v4-pro": "/api/v1/ai/deepseek/v4-pro", // Qwen "qwen-max": "/api/v1/ai/qwen/chat/max", "qwen-plus": "/api/v1/ai/qwen/chat/plus", @@ -223,18 +225,34 @@ func (c *Claw402Client) signPayment(paymentHeaderB64 string) (string, error) { // ── Format overrides for Anthropic endpoints ───────────────────────────────── +// stripMaxTokens removes per-call max_tokens caps from a body destined for +// claw402. The gateway already enforces a per-route default/floor/cap +// (see providers/*.yaml token_default_max_out / token_min_max_out / +// token_max_out_cap). Sending a small max_tokens here on a thinking model +// (Kimi K2.5, DeepSeek R1/V4) caused reasoning tokens to consume the entire +// budget and left `delta.content` empty, surfacing as "no content received". +// upto settles on real usage, so removing the cap costs nothing extra. +func stripMaxTokens(body map[string]any) map[string]any { + if body == nil { + return body + } + delete(body, "max_tokens") + delete(body, "max_completion_tokens") + return body +} + func (c *Claw402Client) BuildMCPRequestBody(systemPrompt, userPrompt string) map[string]any { if c.claudeProxy != nil { return c.claudeProxy.BuildMCPRequestBody(systemPrompt, userPrompt) } - return c.Client.BuildMCPRequestBody(systemPrompt, userPrompt) + return stripMaxTokens(c.Client.BuildMCPRequestBody(systemPrompt, userPrompt)) } func (c *Claw402Client) BuildRequestBodyFromRequest(req *mcp.Request) map[string]any { if c.claudeProxy != nil { return c.claudeProxy.BuildRequestBodyFromRequest(req) } - return c.Client.BuildRequestBodyFromRequest(req) + return stripMaxTokens(c.Client.BuildRequestBodyFromRequest(req)) } func (c *Claw402Client) ParseMCPResponse(body []byte) (string, error) { diff --git a/screenshots/demo-cover.png b/screenshots/demo-cover.png new file mode 100644 index 00000000..11ba7b8b Binary files /dev/null and b/screenshots/demo-cover.png differ diff --git a/store/ai_charge.go b/store/ai_charge.go index 99ff6301..340009e8 100644 --- a/store/ai_charge.go +++ b/store/ai_charge.go @@ -22,18 +22,20 @@ func (AICharge) TableName() string { return "ai_charges" } var modelPrices = map[string]float64{ "deepseek": 0.003, "deepseek-reasoner": 0.005, + "deepseek-v4-flash": 0.003, + "deepseek-v4-pro": 0.01, "gpt-5.4": 0.05, "gpt-5.4-pro": 0.50, "gpt-5.3": 0.01, "gpt-5-mini": 0.005, - "claude-opus": 0.12, - "qwen-max": 0.01, - "qwen-plus": 0.005, - "qwen-turbo": 0.002, - "qwen-flash": 0.002, - "grok-4.1": 0.06, - "gemini-3.1-pro": 0.03, - "kimi-k2.5": 0.008, + "claude-opus": 0.12, + "qwen-max": 0.01, + "qwen-plus": 0.005, + "qwen-turbo": 0.002, + "qwen-flash": 0.002, + "grok-4.1": 0.06, + "gemini-3.1-pro": 0.03, + "kimi-k2.5": 0.008, } // GetModelPrice returns the price per call for a given model diff --git a/store/ai_model.go b/store/ai_model.go index f991d540..15270a47 100644 --- a/store/ai_model.go +++ b/store/ai_model.go @@ -147,7 +147,7 @@ func (s *AIModelStore) GetDefault(userID string) (*AIModel, error) { func (s *AIModelStore) firstEnabledUsable(userID string) (*AIModel, error) { var models []AIModel - err := s.db.Where("user_id = ? AND enabled = ?", userID, true). + err := s.db.Where("user_id = ? AND enabled = ? AND api_key != ''", userID, true). Order("updated_at DESC, id ASC"). Find(&models).Error if err != nil { diff --git a/web/src/components/trader/model-constants.ts b/web/src/components/trader/model-constants.ts index 75c15414..9e0978e5 100644 --- a/web/src/components/trader/model-constants.ts +++ b/web/src/components/trader/model-constants.ts @@ -7,6 +7,7 @@ export interface Claw402Model { desc: string icon: string price: number // USD per call + isNew?: boolean } export interface AIProviderConfig { @@ -41,8 +42,12 @@ export function getShortName(fullName: string): string { return parts.length > 1 ? parts[parts.length - 1] : fullName } +export const DEFAULT_CLAW402_MODEL = 'deepseek-v4-flash' + // Models available through Claw402 (x402 USDC payment protocol) export const CLAW402_MODELS: Claw402Model[] = [ + { id: 'deepseek-v4-flash', name: 'DeepSeek V4 Flash', provider: 'DeepSeek', desc: '$0.003/call', icon: '⚡', price: 0.003, isNew: true }, + { id: 'deepseek-v4-pro', name: 'DeepSeek V4 Pro', provider: 'DeepSeek', desc: '$0.01/call', icon: '🧠', price: 0.01, isNew: true }, { id: 'deepseek', name: 'DeepSeek V3', provider: 'DeepSeek', desc: '$0.003/call', icon: '🔥', price: 0.003 }, { id: 'deepseek-reasoner', name: 'DeepSeek R1', provider: 'DeepSeek', desc: '$0.005/call', icon: '🤔', price: 0.005 }, { id: 'gpt-5-mini', name: 'GPT-5 Mini', provider: 'OpenAI', desc: '$0.005/call', icon: '🚀', price: 0.005 }, @@ -125,7 +130,7 @@ export const AI_PROVIDER_CONFIG: Record = { apiName: 'MiniMax', }, claw402: { - defaultModel: 'glm-5', + defaultModel: DEFAULT_CLAW402_MODEL, apiUrl: 'https://claw402.ai', apiName: 'Claw402', },