最近把一个专为 DeepSeek 优化的 Agent 框架开源了,叫 Reasonix:
- GitHub: https://github.com/esengine/reasonix
- npm:
npm install -g reasonix && reasonix chat
这篇文章聊聊为什么值得给 DeepSeek 做一个专属框架,以及具体怎么做的。
## 起因:通用框架接 DeepSeek 是浪费
LangChain、LlamaIndex 这些通用框架有个共同缺陷:它们把 DeepSeek 当成"base URL 不一样的 OpenAI"。能用,但**DeepSeek
独有的几个特性完全没被利用**:
- 自动 prefix 缓存:DeepSeek 的缓存命中 token 只按 10% 计费
- R1 的
reasoning_content:暴露了模型的推理链 - 便宜:比 Claude Sonnet 便宜 20 倍,某些"研究玩法"变成日常可用
Reasonix 的三个 Pillar 就围绕这三点设计。
## Pillar 1 — Cache-First Loop
### 问题
DeepSeek 缓存的触发条件是请求的 byte prefix 与上次完全一致。通用框架每轮都在:
- 重排历史顺序
- 注入新的 timestamp
- 重构 system prompt
- 工具列表每次序列化结果不同
结果:实测命中率常常 <20%。
### 解决
把每次请求的上下文拆成三区,各自有严格不变性:
┌─────────────────────────────────────┐
│ IMMUTABLE PREFIX │ ← 整个会话永不变
│ system + tool_specs + few_shots │ 这是缓存的靶子
├─────────────────────────────────────┤
│ APPEND-ONLY LOG │ ← 只能追加
│ [user₁][assistant₁][tool₁]... │ 旧 turn 作为新 turn 的 prefix
├─────────────────────────────────────┤
│ VOLATILE SCRATCH │ ← 每轮重置
│ R1 思考、临时 plan state │ 永不上传
└─────────────────────────────────────┘Prefix 一启动就 hash 冻结。Log 的 append() 方法禁止任何 mutate。Scratch 每轮 reset()。
### 实测
5 轮中文多轮对话(deepseek-chat):
| 指标 | 数值 |
|---|---|
| 缓存命中率 | 85.2% |
| 总成本 | $0.000923 |
| 如果跑 Claude Sonnet 4.6 | $0.015174 |
| 节省 | 93.9% |
同样的对话上 tool-use(计算器 tool),2 轮:命中率 94.9%,省 95.8%。
数字不是预估,是实打实跑 DeepSeek API 拿到的。
## Pillar 2 — R1 Thought Harvesting
### 问题
deepseek-reasoner 会在 reasoning_content 里输出很长的思考链。通用框架:
- 要么把它直接回传给下一轮(DeepSeek 官方明确不推荐,会降低效果)
- 要么把它显示给用户看一眼就扔掉
里面的规划信号完全没利用。
### 解决
R1 思考结束后,再发一次 V3 请求(便宜,~$0.0001)在 JSON 模式下提取:
interface TypedPlanState {
subgoals: string[]; // R1 识别出的子目标
hypotheses: string[]; // R1 探讨的假设
uncertainties: string[]; // R1 标记的不确定点
rejectedPaths: string[]; // R1 考虑后放弃的路径
}实际效果(问一道"3 个盒子标签全错,怎么只摸一个水果确定全部内容"逻辑题):
‹ subgoals (3): 列出所有可能的标签与内容组合 · 确定从哪个盒子摸水果 · 验证唯一性
‹ hypotheses (3): 从"苹果"标签盒摸 · 从"橘子"标签盒摸 · 从"混合"标签盒摸
‹ uncertainties (2): 摸到水果是否能唯一确定 · 混合盒摸到的概率
‹ rejected (2): 从"苹果"盒摸(信息量不足) · 从"橘子"盒摸(对称问题)这不是幻觉——真实对应 R1 思考链里的实际内容。
Opt-in,默认关。开启方式:CLI --harvest 或 TUI 内 /harvest on。
## Pillar 3 — Tool-Call Repair
DeepSeek 的 function calling 有几个已知 bug,通用框架不处理就直接崩:
- 深嵌套 schema 丢参数:工具 schema 有 >10 个参数或嵌套 >2 层时,V3/R1 经常漏字段
- R1 把 tool call JSON 藏在
<think>里:忘了冒泡到tool_calls字段 - max_tokens 截断 tool arguments JSON:导致下游 JSON.parse 崩
- Call-storm:同一个工具 + 同样参数连续调用
Reasonix 的 Repair 层四个模块对应修复:
// 1. Auto-flatten 深 schema
ToolRegistry.register({
name: "updateProfile",
parameters: {
type: "object",
properties: {
user: { type: "object", properties: {
profile: { type: "object", properties: {
name: { type: "string" },
age: { type: "integer" },
}},
}},
},
},
fn: ({ user }) => updateInDB(user),
});
// → 自动转成 {"user.profile.name": "...", "user.profile.age": ...} 发给模型
// → dispatch 时自动嵌套回 {user: {profile: {...}}} 再调用
// 2. Scavenge 从 <think> 捞回工具调用
// 3. Truncation 恢复(闭合括号、补 null、去尾逗号)
// 4. Call-storm 滑动窗口熔断全部默认开启,用户不用配置。
## 附加:Self-Consistency Branching
DeepSeek 便宜到什么程度?3 个并行 R1 样本还是比单次 Claude 便宜。
于是把研究论文里的"self-consistency sampling"变成了日常可用:
# 一键开
reasonix chat --branch 3
# 或 TUI 内: /preset maxTUI 里 3 路采样并行完成后显示:
🔀 branched 3 samples → picked #1 #0 T=0.0 u=2 ▸#1 T=0.5 u=0 #2 T=1.0 u=3u= 是 Pillar 2 harvest 出来的 uncertainties.length。默认选择器挑 u 最少的那路。
实测:R1 中等难度题,3 路分支能稳定提升正确率 ~10-15pp,成本约单次 Claude 的 1/5。
## 对比 LangChain 裸接 DeepSeek
| Reasonix 默认 | 通用框架 | |
|---|---|---|
| Prefix 稳定 → 缓存命中 | ✅ 85-95% | ❌ 每轮 prefix 漂移 |
| 深 schema 自动拍平 | ✅ | ❌ 丢参数 |
| 429/503 指数退避重试 | ✅ | ❌ 自己接 callback |
Tool call 从 <think> 捞 | ✅ | ❌ |
| Call-storm 熔断 | ✅ | ❌ |
| 实时成本/命中率面板 | ✅ TUI | ❌ |
| 零配置开箱 | ✅ npx reasonix chat | ❌ |
即便完全不开 harvest / branching,默认配置相对"LangChain + DeepSeek"已经天然省 40%+(光 Pillar 1 贡献)。
## 上手
npm install -g reasonix
reasonix chat第一次跑会弹输入框让你粘 DeepSeek API key(https://platform.deepseek.com/api_keys 免费额度),存到
~/.reasonix/config.json。之后一条命令即用。
### 进阶
TUI 里所有功能用 slash 命令切换,不用记 CLI 参数:
/preset fast 默认配置(deepseek-chat,最便宜)
/preset smart reasoner + harvest(~10x 成本)
/preset max reasoner + harvest + branch3(~30x 成本,质量最高)
/model deepseek-reasoner 只切模型
/harvest on 只开 harvest
/branch 3 只开 3 路采样
/sessions 列出所有会话
/forget 删除当前会话
/help 完整列表会话默认持久化——下次进来自动恢复上次的上下文。用过 Claude Code 的熟悉这个模式。
## 技术栈
- TypeScript + Node 20+
- Ink(React-based TUI,Claude Code 同款)
- 原生 fetch + SSE 流式
- Vitest(135 个测试通过)
- Biome lint
- tsup 打包,ESM 输出
MIT 开源,目前 v0.0.6 pre-alpha。
## 最后
这个项目是故意窄的:
- ❌ 不做多 provider 兼容(LiteLLM 专门做这个)
- ❌ 不做多 agent 编排(LangGraph 专门做这个)
- ❌ 不做 RAG(LlamaIndex 专门做这个)
- ✅ 只做 DeepSeek,做到深
如果你有在 DeepSeek 上跑 agent 的工作负载,欢迎试试 → https://github.com/esengine/reasonix
issue 和 PR 都欢迎。尤其是 Pillar 2 的 branching 选择策略目前只看
uncertainties.length,想听听大家对改进方向的意见——比如能不能用 tool call 成功率、回答长度比、 cross-sample
相似度等信号一起判断。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。