5

最近把一个专为 DeepSeek 优化的 Agent 框架开源了,叫 Reasonix

这篇文章聊聊为什么值得给 DeepSeek 做一个专属框架,以及具体怎么做的。

## 起因:通用框架接 DeepSeek 是浪费

LangChain、LlamaIndex 这些通用框架有个共同缺陷:它们把 DeepSeek 当成"base URL 不一样的 OpenAI"。能用,但**DeepSeek
独有的几个特性完全没被利用**:

  1. 自动 prefix 缓存:DeepSeek 的缓存命中 token 只按 10% 计费
  2. R1 的 reasoning_content:暴露了模型的推理链
  3. 便宜:比 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,通用框架不处理就直接崩:

  1. 深嵌套 schema 丢参数:工具 schema 有 >10 个参数或嵌套 >2 层时,V3/R1 经常漏字段
  2. R1 把 tool call JSON 藏在 <think>:忘了冒泡到 tool_calls 字段
  3. max_tokens 截断 tool arguments JSON:导致下游 JSON.parse 崩
  4. 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 max

TUI 里 3 路采样并行完成后显示:

🔀 branched 3 samples → picked #1   #0 T=0.0 u=2   ▸#1 T=0.5 u=0   #2 T=1.0 u=3

u= 是 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
相似度等信号一起判断。


谦逊的钥匙_oQUBJ
7 声望5 粉丝