构建 AI 驱动的代码编辑器:(第2部分)类似 LLM 的解释器

发布: (2025年12月30日 GMT+8 06:42)
12 min read
原文: Dev.to

Source: Dev.to

(请提供需要翻译的正文内容,我将为您翻译成简体中文并保持原有的格式。)

Source:

洞察

在构建 LLM CodeForge(一个让 LLM 能够自主读取、修改和测试代码的代理编辑器)时,我在写了 5 000 个 token 的指令后意识到:

我并不是在写提示词。我在构建一种嵌入自然语言中的 领域特定语言(DSL)。

本文分析了这种区分为何根本重要——以及你可以从中为自己的代理系统学到什么。

模型在 CodeForge 中的行为

方面模型 会做 的事模型 不会做 的事
决策制定选择 协议的哪个分支进行不决定 要做什么
问题求解执行 用自然语言描述的过程 创造性地解决问题
本质字节码解释器文本驱动的有限状态机,或 具有封闭动作的规划器依赖确定性的控制流,而非开放式推理
可靠性因为我 接受 LLM 本质上不可靠而工作N/A

DSL 控制流

每个请求遵循 步:

[UNDERSTAND] → [GATHER] → [EXECUTE] → [RESPOND]

第 1 步 – UNDERSTAND

分类请求类型

类型关键词下一步
解释“what is”, “explain”[RESPOND] (text)
修改“add”, “change”[GATHER] → [EXECUTE]
分析“analyze”, “show”[GATHER] → [RESPOND]

不是 经典意义上的链式思考。这是确定性的任务路由——一个将 输入 → 工作流 的决策表。模型并不“思考”;它执行 条件跳转

不变规则

🚨 关键规则:您 不能 对在本次对话中未读取的文件使用 update_file

在任何 update_file 之前进行自检

  • 我是否已收到系统提供的内容?
  • 我是否确切了解当前状态?
  • 我是否基于实际代码进行修改?

如果 任何 答案为 输出 read_file 操作,然后 停止

这是一种用自然语言定义前置条件的尝试。它类似于:

def update_file(path, content):
    assert path in conversation_state.read_files
    # ... actual update

没有类型系统或自动运行时强制执行时,该规则 降低(但并未消除)LLM 在未先读取文件的情况下修改文件的概率。在测试中,我观察到 ~85‑90 % 的稳定性,但服务器端验证在关键情况下仍然至关重要。

多文件任务:提示再生成

我实现的最有效技术是 动态再生成提示,以强制 LLM 按多步骤计划执行。

具体场景

用户请求:“为项目添加身份验证。”

  1. 规划 – LLM 生成一个计划:
{
  "plan": "I will modify these files in order:",
  "files_to_modify": ["Auth.js", "Login.jsx", "App.jsx"]
}
  1. 第一个文件 – LLM 在 Auth.js 上工作并完成它。

  2. 技巧 – 与其让 LLM “记住计划”,我 再生成提示,并在其中加入明确的 “下一步操作” 块:

### ⚠️ MULTI‑FILE TASK IN PROGRESS

You completed: Auth.js  
Remaining files: Login.jsx, App.jsx

### 🚨 REQUIRED ACTION
Your next output MUST be:
{"action":"continue_multi_file","next_file":{"path":"Login.jsx"}}

Do NOT do anything else. Do NOT deviate from the plan.

LLM 不再需要 “记住” 任何内容。唯一可能的操作已经写进提示里。

为什么它很强大

  • 状态(哪些文件已完成,哪些仍待处理)保存在 外部 JavaScript 中,而不在 LLM 的记忆里。
  • 在每一步,我 再生成 包含更新后状态的提示。
  • LLM 始终只看到 单一、明确的指令

实现示例

// In the code
function buildPrompt(multiFileState) {
  let prompt = BASE_PROMPT;

  if (multiFileState) {
    prompt += `
### TASK IN PROGRESS
Completed: ${multiFileState.completed.join(', ')}
Next: ${multiFileState.next}
Your ONLY valid action: continue_multi_file with ${multiFileState.next}
`;
  }

  return prompt;
}

这就是 状态注入:外部状态完全控制 LLM 接下来能做的事情。

混合内容的自定义分隔符

为了处理不同的“类型”(JSON、代码、元数据),DSL 使用 自定义分隔符

#[json-data]
{"action":"create_file","file":{"path":"App.jsx"}}
#[end-json-data]

#[file-message]
This file implements the main app component.
#[end-file-message]

#[content-file]
export default function App() {
  return Hello World;
}
#[end-content-file]

为什么不使用普通的 JSON 或 XML?

  • 内容可能包含 {}<> 等,需要复杂的转义。
  • #[tag]…#[end-tag] 语法唯一,使用正则即可轻松解析,并且与嵌入的语言无关。
  • 它表现为一种 上下文无关文法,用于分离语义层。

Source:

错误示例指南

在 DSL 中直接嵌入“错误示例”可以教会模型常见的失败模式——语言的内联单元测试

❌ 错误✅ 正确
{"action":"start_multi_file","plan":{},"first_file":{...}}{"action":"start_multi_file","plan":{},"first_file":{...}}
#[json-data]{...}#[file-message]...#[json-data]{...}#[end-json-data]#[file-message]...

自然语言 DSL 的权衡

限制后果
❌ 无可验证的类型无静态类型检查
❌ 无自动语法验证错误必须在运行时捕获
❌ 无用于转换的抽象语法树 (AST)无编译时优化

补偿措施

  • 庞大的验证清单(8+ 项)
  • 语义冗余 – 同一规则以多种方式表达
  • 详尽的反模式文档

在使用自然语言构建 DSL 时,这些权衡是不可避免的,但最终的系统仍然可以是 健壮透明可控 的。

概览

The parser is a probabilistic LLM rather than a deterministic compiler.
If I were to evolve CodeForge in the future, a true mini‑DSL (JSON Schema + codegen) would reduce the prompt size by 30‑40 %. In the browser sandbox, however, the current choice is justified.

发送前检查清单

每一次 回复之前,进行验证:

#检查项失败时的修复
1JSON 有效修正结构
2标签完整添加缺失的 #[end-*] 标签

单独使用时,这只能提供 40‑60 % 的可靠性。在我的系统中,它可以达到 80‑90 %,因为它在以下情况下充当 稳定性乘数

  • 模型已经 通道化(决策协议)
  • 格式 刚性(自定义分隔符)
  • 下一步操作是 确定性的(状态注入)

元验证 不是 主要功能——它是已经受限系统中的最终安全网。

Model Compatibility

✅ Works well with Claude 3.5, GPT‑4  
❌ Smaller models will fail  
❌ Less‑aligned models will ignore sections

我在暗示:该系统需要 “严肃”模型
这是我接受的 架构约束 —— 就像说 “此库需要 Python 3.10+。”

上下文重新锚定

采用 “读‑写前检查” 规则:

  • 出现在 决策协议(规划时)
  • 出现在 可用操作(执行时)
  • 出现在 发送前验证(验证时)
  • 出现在 黄金法则(作为一般原则)

这是一种 战略性重复,而非随机冗余。它映射到安全关键系统:

  • 相同的不变式
  • 在多个层面进行验证
  • 为每个上下文提供特定的措辞

模式示例

不良 vs. 良好模式

// BAD: Relying on the model's "memory"
"Remember that you have already read these files..."

// GOOD: Injecting explicit state
prompt += `Files already read: ${readFiles.join(', ')}`

// BAD: Giving open choices
"Decide which operation to perform"

// GOOD: Forcing the only legal move
"Your NEXT action MUST be: continue_multi_file"

输入 → 操作 → 下一个状态

输入模式操作下一个状态
"add X"GATHEREXECUTE
"explain Y"RESPONDEND

而不是 “think what to do”,请使用 “if X then Y”

嵌入任意内容

  • 不要 使用 JSON – 转义噩梦
  • 不要 使用 XML – 与 HTML/JSX 冲突
  • 使用 唯一标签:#[content]…#[end-content]

Pattern #5 – 冗余 = 覆盖, 而非噪声

  • 重复关键规则
    • 以不同的表述方式(semantic reinforcement)
    • 在不同的上下文中(contextual re‑anchoring)
    • 提供不同的理由(why, not just what)

在 5 000 个 token 并经过数月迭代后,最重要的教训是:

这个提示 并非“美观”。 它是 有效 的。

优化转变

❌ 不再寻找✅ 开始优化
最短的提示边缘情况的鲁棒性
最优雅的表述故障模式覆盖
最通用的抽象故障时的调试清晰度

结果:

  • 冗余?
  • 冗长?绝对
  • 有效?始终如一

未来方向:我可能的去向

如果我要演进 CodeForge 2.0,我会探索一种 双代理 架构,而不是单一的 5 000 token 代理:

代理Token 预算角色
规划代理2 000决定策略
执行代理2 000实施行动

好处

  • 关注点分离
  • 每个代理的上下文更少
  • 可实现并行执行

有效的关键技术

评级技术
⭐⭐⭐⭐⭐状态注入 + 强制下一步操作
⭐⭐⭐⭐⭐用于任务路由的决策表
⭐⭐⭐⭐⭐用于结构化输出的自定义分隔符
⭐⭐⭐⭐⭐不变式的上下文重新锚定
⭐⭐⭐⭐作为安全网的元验证
⭐⭐⭐视觉层次(有用但非关键)

基本原则:
不要让 LLM “理解”——而是强迫它 “执行”。

将协议视为 DSL,而非对话。外部状态必须约束可能的操作,且验证必须始终在 服务器端或客户端 进行,绝不例外。冗余可以是 特性,而非缺陷。

行动号召

尝试 CodeForge:

该项目是 开源 — 完整的提示和验证系统实现可在仓库中获取。

社区提问

  1. 你是否曾经在自然语言中构建过嵌入式 DSL?
  2. 在你的提示中,“认知开销”的成本是多少?
  3. 双代理架构 vs. 单代理:有什么经验?

在评论中分享 — 这仍是大多未被探索的领域。

Back to Blog

相关文章

阅读更多 »