SmallCode 上下文预算管理深度分析

Codex 核验(2026-10-09):本文件是项目源码研究与 Hermes 移植提案,未完成集成。当前 Ollama 支持 think,具体布尔/等级控制依模型;2000 thinking budget 不能跨模型统一生效,响应后的 32000 字符截断也不能限制已生成或已计费推理。Hermes 主配置是 config.yaml。工具名合法仍须校验参数 schema、权限与作用域;正则修 JSON 可能误改字符串。固定提示有利缓存,但命中取决于服务和前缀;70% 窗口预算需包含系统提示、工具及回复预留,schema 减少比例不能等同总费用降幅。

基于 SmallCode v1.3.0 (https://github.com/Doorman11991/smallcode)
研究日期:2026-05-28
分析目标:提取核心上下文预算管理策略,迁移为 Hermes Agent 的 small-model-compensation skill


一、概述

SmallCode 是一个专为小型 LLM(8B-35B 参数)设计的终端原生编码代理。与假设前沿模型(128k+ 上下文、完美 JSON 输出)的工具不同,SmallCode 从相反的假设出发:模型可能只有 8-32k 上下文窗口,输出的工具调用格式不规范,并且会在三步之后忘记自己在做什么。

核心成就:使用仅 4B 活跃参数的 Gemma 4 E4B 模型,在单文件任务上达到 87% 成功率,超越使用 3-4 倍大模型的竞品。

1.1 设计哲学

SmallCode 的每个架构决策都源于一个核心约束:从限制中提取有用工作。它不是更聪明的 Cursor,而是从消费级硬件上运行的模型中榨取效能。

1.2 与 Hermes Agent 的对比

维度SmallCode 方案Hermes Agent 当前状态
目标模型8B-35B 本地模型通用(强模型优先)
上下文管理预算控制、压缩、逐出无显式管理
工具调用宽容多格式解析器假设标准格式
编辑方式Patch 优先支持但非首选
规划TODO 文件分解无显式规划

二、上下文预算引擎(Context Budget Engine)

这是 SmallCode 最核心的创新,直接解决小模型上下文窗口有限的痛点。

2.1 三层预算控制

┌─────────────────────────────────────────────────┐
│  Layer 1: 预调用检查 (assertWithinBudget)       │
│  ・每分钟最多 30 轮 (run_turn)                   │
│  ・每小时最多 500K tokens                        │
│  ・超出则抛 BUDGET_EXCEEDED                      │
├─────────────────────────────────────────────────┤
│  Layer 2: 调用中预算 (per trace)                │
│  ・LLM_BUDGET_TOKENS_PER_TRACE (token 上限)     │
│  ・LLM_BUDGET_USD_PER_TRACE (USD 上限)          │
│  ・成本类: tiny/small/medium/large               │
├─────────────────────────────────────────────────┤
│  Layer 3: 上下文窗口管理 (运行时)               │
│  ・max_budget_pct = 70% 窗口                     │
│  ・每 3 次工具调用检查一次                       │
│  ・超出 60% 窗口 → 触发压缩                      │
└─────────────────────────────────────────────────┘

2.2 轮中逐出(Mid-Turn Eviction)

当 estimated_history_tokens > detected_window * 0.6:

第一步:截断旧的 tool_call 参数

  • 找到最后一个 assistant 消息之前的旧消息
  • 将大于 200 字符的 JSON 参数替换为最小化版本
  • 字符串值截断到 80 字符后加 …
  • 如果已经是无效 JSON,重置为 {}

第二步:逐出旧的 tool 结果

  • 从最旧的消息开始,仅当 owner 在前半部分历史中
  • 将 tool 结果替换为 [evicted: N tokens] 标记
  • 孤立结果(owner 已经被移除)直接 splice 删除
  • 释放的 token 计入 midEst 递减

第三步:验证

  • 如果仍超出 maxBudget * 0.7,继续逐出
  • 记录逐出次数到 tokenMonitor.recordEviction()

2.3 工具结果上限(Tool Result Capping)

SMALLCODE_MAX_TOOL_RESULT_CHARS = 8000  (默认)

对于读操作(read_file, find_and_read 等),使用上下文感知守卫:

// 检查两个触发条件
condition1 = context_usage > budget_pct          // 实时上下文已超预算
condition2 = file_tokens * 2 > context_window    // 单文件占窗口 50%+
 
if (condition1 || condition2) {
    // 激进裁剪:仅保留前 30 行 + 提示
    // "Context budget is tight. Use search/find_files..."
} else {
    // 标准裁剪:保留头 70% + 尾 20% + 中间截断标记
}

2.4 大文件自动摘要

  • 文件 > 200 行 → 自动 LLM 摘要
  • 提取函数签名和关键逻辑
  • 按内容哈希缓存(1h TTL)
  • 目标 token 数:600
  • 摘要工具结果替代原始文件内容

2.5 系统提示缓存分离(Cache-Split)

这是针对 llama.cpp KV-cache 的优化:

静态部分(每次相同,可缓存):

  • 身份、OS、工作目录
  • 核心规则(patch 优先、复合工具、保持简洁)
  • 大文件规则(60 行/8KB 限制)

动态部分(作为单独的 [CONTEXT] 用户消息):

  • 相关记忆(得分 ≥ 2,最多 800 tokens)
  • Skill 上下文(最多 1000 tokens)
  • 知识笔记(最多 1500 tokens)

效果:llama.cpp 每次调用不再使 KV-cache 失效,减少重复计算。


三、宽容工具调用解析器(Forgiving Tool Call Parser)

小模型输出不规则,SmallCode 设计了多层恢复机制。

3.1 嵌入式工具调用检测

当模型将工具调用作为文本嵌入 content 而非 tool_calls 字段:

格式正则模式来源
<tool_call>{...}</tool_call>Hermes/Qwen2.5 原生格式
```json\n{...}\n```fenced JSON 块Qwen-coder 等
```tool_call\n{...}\n```fenced tool_callLlama3 微调版
纯 JSON 对象{ "name": "...", "arguments": {...} }最后的尝试

处理流程:

message.tool_calls exists? → 直接使用
  ↓ 否
匹配 <tool_call> 标签 → 提取 JSON
  ↓ 无匹配
匹配 ```json/```tool_call 代码块 → 提取 JSON
  ↓ 无匹配
匹配纯 JSON 对象(内容基本都是 JSON)→ 提取
  ↓ 无匹配
保持原样(作为文本回复处理)

3.2 JSON 修复(Repair)

当 JSON.parse 失败时:

  1. LLM 自修复:将 original_call + error + schema 发回模型
  2. 正则后备:针对 write_file,用正则从损坏 JSON 中提取 path/content
  3. 空后备:全部失败则返回 {} 并记录错误

3.3 格式标准化

将各种工具调用格式统一为标准形式:

{ name, arguments }         → { name, arguments }
{ function: { name, arguments } }  → { name, arguments }  
{ tool, args }              → { name, arguments }

只在工具名在已知列表中时接受。

3.4 JSON 清理

自动去除尾随逗号(Qwen 常见问题):

cleaned = trimmed.replace(/,\s*([}\]])/g, '$1');

支持多行 JSON 对象(换行符分隔)逐个解析。


四、Patch 优先编辑模式

4.1 为什么 Patch 优先

小模型的全文件重写有三个主要问题:

  1. 截断:超出 token 限制后直接截断
  2. 幻觉导入:添加不存在的 import
  3. 缩进漂移:逐行生成时缩进逐渐变化

Patch(搜索-替换)方式:

  • 只修改需要改动的 10-20 行
  • old_str 必须精确匹配一个位置 → 模型必须准确
  • 上下文成本远低于全文件重写

4.2 语义合并回退

当 patch 失败(old_str 不匹配,因之前编辑导致文件变动):

  1. 将当前文件内容和预期的改动发回模型
  2. 模型返回整个文件的修正版本
  3. 这是最后手段,非首选

4.3 读前写守卫(Read-Before-Write Guard)

首次对未读文件执行 write_file → 拒绝,提示 "先读取文件"
第二次尝试 → 允许(合法覆盖)

防止小模型在未理解文件内容的情况下直接覆盖。

4.4 大小限制

write_file:  max 60 行 / 8KB      → 骨架文件 + 多次 patch 填充
append_file: max 60 行 / 8KB/次   → 增量构建大文件
patch:       无内容限制            → 精确改动

llama.cpp 的 JSON 解析器在 ~13KB 的 tool_call 参数上会崩溃,这是硬限制的根源。


五、两阶段工具路由(Two-Stage Tool Routing)

5.1 机制

对 ≤ 16k 上下文窗口的模型:

阶段 1:模型选择类别(仅 ~200 tokens):

read: 读取文件、查找文件
write: 创建文件、patch
search: 正则搜索、图谱搜索
run: shell 命令
plan: 记忆、BoneScript

阶段 2:系统仅注入该类别的工具 schema(~400 tokens)

直接模式:对 > 16k 上下文的模型,一次性发送所有工具。

5.2 节省计算

全部工具 schemas: ~2000 tokens
选择器工具:       ~100 tokens
类别工具:         ~400 tokens
节省:             约 75%

5.3 确认守卫(Affirmation Guard)

当模型在任务中间回复 “yes”/“ok” 时,保持之前的工具类别而不是重新分类为 respond。否则分类器会将确认作为无工具类别处理,导致模型失去编写工具。

5.4 确定性路由

正则分类器基于 8 个类别的加权评分:

  • 正向信号(提升分数)+ 反向反信号(降低分数)
  • “Explain” 降低 write 分数
  • “All uses of” 提升 search 分数
  • 平局时优先级:write > run > code-intelligence > search > plan > read > web > respond

六、改善循环(Improvement Loop)

6.1 自校验流程

每次 write/patch 后:

  1. 语义评审(LLM-based):检查编辑是否有逻辑问题
  2. 语法检查:py_compile, node --check, tsc --noEmit, go build
  3. 重试循环:错误反馈给模型(最多 2 次)
  4. 分解策略:重试耗尽后切换策略
  5. 升级:本地模型失败后可选升级到云端模型

6.2 分解策略(Decompose)

当 2 次重试都失败时:

  • 用 LLM 分析失败原因,选择新策略
  • 策略类型:重写整个文件、重写特定部分、完全从零开始
  • 如果分解也失败 2 次,触发升级

6.3 信任衰减(Trust Decay)

追踪每个工具的连续失败次数:

  • 3+ 次失败且置信度 < 0.35 → 从 schema 列表中移除
  • 提供替代工具建议
  • 跨 session 持久化

6.4 快照与自动回滚

每次 agent 轮次前创建快照:

  • 记录每个编辑前的文件内容
  • 验证全面失败后自动回滚
  • 存储在 .smallcode/snapshots/

七、早期停止检测(Early-Stop Detection)

7.1 重复循环检测

在流式输出期间检查:

  • 窗口大小:50/80/120 字符
  • 阈值:3+ 次重复
  • 动作:注入修正提示

7.2 Patch 螺旋检测

当模型在同一个文件上卡住:

  • 4+ 次 patch 失败 → 强制全文件重写
  • 6+ 次总尝试 → 同样处理
  • 空操作 patch(old_str === new_str)也计为失败

7.3 只读循环检测

连续只读工具调用:

  • 5 次无输出 → 温和提示
  • 8 次无输出 → 强制停止 + 注入 “你已经有足够上下文,停止阅读,开始编写”

7.4 问候回归检测

如果模型在任务中间输出问候语:

  • 检测模式:“how can i help”, “hello!”, “what can i do for you”
  • 注入:“你输出了问候语而非完成任务,继续之前的工作”

八、思考预算控制(Thinking Budget Control)

8.1 背景

推理模型(Qwen3, DeepSeek R1, Claude with thinking)在给出答案前会输出大量 “thinking” 令牌。如果不加控制,一个简单的重命名操作可能消耗 8000 tokens 的思考。

8.2 预算注入

DEFAULT_BUDGET_TOKENS = 2000   // SMALLCODE_THINKING_BUDGET
HARD_CAP_CHARS = 32000         // SMALLCODE_THINKING_HARD_CAP

按提供商差异化:

提供商字段
Anthropicthinking: { type: "enabled", budget_tokens: N }
OpenAI (o1/o3/o4)reasoning_effort: "low"/"medium"/"high"
llama.cpp/LM Studiochat_template_kwargs: { enable_thinking, thinking_budget }
Ollama当前支持 think;按模型能力映射,不能统一套用数字预算

8.3 思考提取与截断

  1. 从回复中提取 <think>...</think> 块
  2. extractThinking(content) → { thinking, answer }
  3. answer 存入对话历史,thinking 仅用于显示
  4. 超硬上限截断:保留前 60% + 后 30%,中间替换为 [...thinking truncated: N chars omitted...]

8.4 重试时禁用思考

在修复循环(attempt > 1)时自动禁用思考——模型已经在第一次超常思考,快速确定性修复更好。


九、TODO 驱动的规划系统

9.1 计划提取

对于多步骤任务:

  1. 注入一次性指令,要求模型在工具调用前输出编号计划
  2. LLM 提取器将自然语言中的计划结构化为步骤列表
  3. 正则表达式作为后备

9.2 计划锚重新注入

每轮将计划注入系统提示:

ACTIVE PLAN (step 3 of 5):
✓ 1. Read the existing auth module
✓ 2. Identify the JWT validation function
→ 3. Add the refresh token handler
  4. Update the route middleware
  5. Run tests

9.3 依赖图

新功能:检查计划步骤是否触及同一文件 → 拓扑排序 → 并行批次


十、可迁移到 Hermes Agent 的技术

10.1 高优先级(P0)

技术SmallCode 实现Hermes Agent 集成方式
上下文预算控制max_budget_pct=70%, detected_window提案:在 Hermes config.yaml 中设计 small_model 配置;需另行实现并验证
工具结果上限8000 字符 + 上下文感知 30 行守卫覆盖 _executeToolModule 的 result 处理
Patch 优先编辑搜索-替换 + 语义合并回退强化 patch 工具,弱化 write_file
宽容 JSON 解析5 种格式 + LLM 修复 + 正则后备在 tool 执行前加一层 tool_call_extractor

10.2 中优先级(P1)

技术说明
两阶段工具路由对小上下文模型只发类别选择器,第二批发特定类别工具
改善循环写文件后自动验证 + 重试 + 分解 + 升级
早期停止检测重复/螺旋/只读循环/问候回归 四类检测
思考预算为推理模型设置思考 token 上限

10.3 低优先级(P2-P3)

技术说明
系统提示缓存分离静态+动态分离,避免 KV-cache 失效
TODO 规划计划提取+锚注入+依赖图
信任衰减工具级别置信度追踪
工具评分贝叶斯学习

10.4 不直接迁移的部分

特性原因
BoneScript特定于 Node.js 后端的编译系统
MarrowScriptSmallCode 专属的编译层
MCP 代码图谱依赖 budget-aware-mcp 扩展
模型升级Hermes Agent 自有模型管理
快照/回滚SmallCode 特有的 session 系统

十一、架构图视角

┌─────────────────────────────────────────────────────────────────────┐
│                    SmallCode 上下文预算管理全景                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  用户输入                                                          │
│     │                                                              │
│     ▼                                                              │
│  ┌─────────────────────┐                                           │
│  │ 1. 输入预处理        │  ← 正则分类器(0 token 成本)             │
│  │   ・模糊检测          │                                          │
│  │   ・@file 展开        │                                          │
│  │   ・git diff 注入     │                                          │
│  └─────────┬───────────┘                                           │
│            ▼                                                       │
│  ┌─────────────────────┐                                           │
│  │ 2. 工具路由          │  ← 两阶段模式节省 ~75% 工具 schema tokens│
│  │   ・确定性类别选择    │                                          │
│  │   ・两阶段(≤16k)   │                                          │
│  └─────────┬───────────┘                                           │
│            ▼                                                       │
│  ┌─────────────────────┐                                           │
│  │ 3. 系统提示构建      │  ← 缓存分离(静态+动态)                  │
│  │   ・记忆/知识注入    │                                          │
│  │   ・计划/测试注入    │                                          │
│  │   ・所有部分都有上限 │                                          │
│  └─────────┬───────────┘                                           │
│            ▼                                                       │
│  ┌─────────────────────┐                                           │
│  │ 4. API 调用          │  ← 思考预算注入                          │
│  │   ・思考提取+截断    │                                          │
│  └─────────┬───────────┘                                           │
│            ▼                                                       │
│  ┌─────────────────────┐                                           │
│  │ 5. 工具调用处理      │  ← 宽容解析(5 种格式+修复)             │
│  │   ・调用→执行        │                                          │
│  │   ・结果→上限+守卫   │                                          │
│  │   ・每 3 轮检查上下文│                                          │
│  └─────────┬───────────┘                                           │
│            ▼                                                       │
│  ┌─────────────────────┐                                           │
│  │ 6. 改善循环          │  ← 验证+重试+分解+升级                   │
│  │   ・语义评审+语法    │                                          │
│  └─────────┬───────────┘                                           │
│            ▼                                                       │
│  ┌─────────────────────┐                                           │
│  │ 7. 早期停止          │  ← 4 类退化检测                          │
│  │   ・重复/螺旋/只读   │                                          │
│  │   ・问候回归          │                                          │
│  └─────────────────────┘                                           │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

十二、结论与建议

12.1 关键发现

  1. 上下文预算是第一优先级:SmallCode 在每 3 次工具调用后检查上下文使用情况,这比后端简单截断消息历史更精确
  2. 宽容解析器是多步任务生命线:小模型经常输出格式不正确的工具调用,没有修复机制的话大多数多步任务会失败
  3. Patch 优先比直觉更有效:搜索-替换比全文件重写更省 token 且更可靠,尤其是对经常截断的小模型
  4. 系统提示缓存分离很巧妙:直接解决本地推理引擎(llama.cpp)的 KV-cache 失效问题
  5. Improvement Loop 用工程弥补模型能力:模型不需要一次做对,系统自动验证和重试

12.2 迁移建议

small-model-compensation skill 的 MVP(最小可行) 应包含:

  1. 上下文预算控制 → 防止 8k 窗口溢出
  2. 工具结果上限 → 控制每次调用的 token 消耗
  3. 宽容 JSON 解析 → 让 Hermes 能处理 Qwen/Llama 等模型的非标准输出
  4. Patch 模式强调 → 减少全文件写入操作

扩展功能(后续迭代): 5. 两阶段工具路由 → 进一步减少 token 消耗 6. 改善循环 → 自动验证+重试 7. 早期停止 → 避免 token 浪费在退化行为上

12.3 风险与注意事项

  • Latency 增加:改善循环、LLM 修复调用会产生额外延迟
  • 过度工程:对于已有大模型(GPT-4, Claude 4)用户,这些补偿无用且增加复杂度
  • 配置复杂度:过多的环境变量和配置项可能需要简化
  • 模型适配性:不同小模型有不同的弱点,需要可配置的 profile 系统

本分析基于 SmallCode v1.3.0 (2026-05-28),MIT 许可证。 代码库:https://github.com/Doorman11991/smallcode Skill 草案位置:~/.hermes/skills/small-model-compensation/SKILL.md