代码生成与辅助
利用 LangGraph 构建支持多步骤规划、生成和评审的编码助手
📚 学习目标
学完这篇文章后,你将能够:
- 实现“规划 -> 生成 -> 评审”的代码生成流水线
- 使用 LangGraph 协调多个“专家 Agent”(如前端专家、后端专家)
- 构建自动化的代码重构工作流
前置知识
在开始学习之前,建议先阅读:
1 为什么代码生成需要 Graph?
简单的 "Write me a game" Prompt 往往只能生成简单的 Demo。要生成高质量的复杂项目,需要模仿人类工程师的工作流:
- Tech Spec: 编写技术方案。
- Structure: 设计文件结构。
- Implementation: 逐步实现每个模块。
- Review: 代码审查和测试。
这天然适合用 LangGraph 建模。
你可以把它理解为:
- Prompt 适合“一次性输出”
- Graph 适合“可回环的工程流程”(生成失败就修、修完再测)
2 架构设计:Co-Coding Graph
3 把“评审”做成闭环:Generate -> Validate -> Fix
比起“让模型自己说它写得对不对”,更可靠的是引入明确的验证点。
验证可以是:
- TypeScript 编译
- ESLint
- 单元测试
- 结构化约束(比如必须导出某个函数、必须包含某个字段)
4 状态管理
在代码生成场景中,我们需要维护一个“虚拟文件系统”的状态。
import { Annotation } from '@langchain/langgraph';
const CodingState = Annotation.Root({
requirements: Annotation<string>(),
plan: Annotation<string>(),
files: Annotation<Record<string, string>>({
// Reducer: 合并文件更新
reducer: (state, update) => ({ ...state, ...update }),
default: () => ({}),
}),
reviewComments: Annotation<string[]>(),
});如果你要一次生成多个文件,建议让 files 的 reducer 合并更新(你已经这么做了)。
5 多文件生成:一次输出一组文件(更接近真实项目)
很多“工程级输出”不是单文件,而是:组件 + types + test + README。
import { z } from 'zod';
const fileSchema = z.object({
filename: z.string(),
content: z.string(),
});
const multiFileSchema = z.object({
files: z.array(fileSchema).min(1),
});
const coderNode = async (state) => {
const out = await coderModel
.withStructuredOutput(multiFileSchema)
.invoke(state.messages);
const patch: Record<string, string> = {};
for (const f of out.files) patch[f.filename] = f.content;
return { files: patch };
};💡 提示
结构化输出的核心价值:你能把“代码生成”当成数据处理,而不是纯文本对话。
关键节点:文件写入
程序员 Agent 的输出应该包含结构化的文件内容,而不是散乱的文本。可以使用 LLM 的 with_structured_output 功能。
import { z } from 'zod';
const fileSchema = z.object({
filename: z.string(),
content: z.string(),
});
const coderNode = async (state) => {
const result = await coderModel
.withStructuredOutput(fileSchema)
.invoke(state.messages);
// 更新虚拟文件系统
return { files: { [result.filename]: result.content } };
};6 反思与修正 (Reflexion)
Reviewer 节点至关重要。它不仅检查语法错误,还可以检查是否符合 Planner 制定的规范。如果发现问题,将错误信息写回 messages,并路由回 Coder 节点,形成闭环。
一个务实建议:Reviewer 的输出也要结构化。
approved: booleancomments: string[]severity: "blocker" | "minor"
这样你可以在路由逻辑里做明确决策,而不是靠字符串匹配。
7 验证节点:把“跑测试/跑编译”变成工具
如果你希望 Graph 真正做到“写完就验证”,需要一个执行工具(sandbox/runner)。
下面是一个教学版伪代码(真实项目里必须隔离执行环境):
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
const runCommand = tool(
async ({ cmd }) => {
// 伪代码:真实项目请用隔离沙箱,不要直接 exec
return { ok: true, stdout: '...', stderr: '' };
},
{
name: 'run_command',
description: 'Run a shell command in a sandbox and return stdout/stderr.',
schema: z.object({ cmd: z.string() }),
},
);有了这个工具,你就可以在 Reviewer 里触发:
pnpm testpnpm typecheck
然后把失败日志喂回 Coder 继续修。
8 多代理分工示例
在复杂项目中,常见的分工是:
- Planner:输出技术方案与任务拆分
- Coder:生成具体代码
- Reviewer:检查错误与规范
const plannerNode = async (state) => ({ plan: '拆分为 UI / API / Tests' });
const coderNode = async (state) => ({ files: { 'app.tsx': '...' } });
const reviewerNode = async (state) => ({
approved: false,
comments: ['缺少测试'],
});💡 说明
职责越清晰,迭代修复越稳定。
9 自动修复闭环
让 Reviewer 输出结构化结果,再路由回 Coder:
const reviewSchema = z.object({
approved: z.boolean(),
comments: z.array(z.string()),
});
const reviewerNode = async (state) => {
return await reviewerModel
.withStructuredOutput(reviewSchema)
.invoke(state.messages);
};
const routeAfterReview = (state) => (state.approved ? END : 'coder');⚠️ 注意
路由条件必须基于结构化字段,而不是字符串匹配。
10 安全边界
代码生成常见风险:
- 误生成危险命令
- 过度访问文件系统
- 外部依赖不可控
💡 建议
执行节点必须在隔离沙箱中运行,且只能执行白名单命令。
11 多文件生成与文件树
项目通常需要生成多文件结构(组件、样式、测试):
const fileSchema = z.object({
filename: z.string(),
content: z.string(),
});
const outputSchema = z.object({
files: z.array(fileSchema).min(1),
});
const coderNode = async (state) => {
const out = await coderModel
.withStructuredOutput(outputSchema)
.invoke(state.messages);
const patch: Record<string, string> = {};
for (const f of out.files) patch[f.filename] = f.content;
return { files: patch };
};💡 提示
结构化输出可以避免“模型输出混乱”,更容易写入虚拟文件系统。
12 重构工作流
当你需要“在现有代码上优化”,可以用 refactor 节点:
const refactorNode = async (state) => {
const prompt = `请基于以下代码进行可读性优化:\n${state.files['app.tsx']}`;
const response = await model.invoke([new HumanMessage(prompt)]);
return { files: { 'app.tsx': String(response.content) } };
};⚠️ 注意
重构节点应搭配测试或 lint,避免引入回归。
13 代码风格约束
给 coder 提供明确风格约束,能显著提升一致性:
const system = new SystemMessage(
'代码需使用 TypeScript,禁止使用 any,函数需添加注释。',
);ℹ️ 说明
风格约束越清晰,后续 review 的负担越小。
14 验证策略与 CI 思路
验证节点可以执行测试或静态分析,并把结果回传给 Reviewer:
const runCommand = tool(
async ({ cmd }) => ({ ok: true, stdout: '...', stderr: '' }),
{
name: 'run_command',
description: 'Run in sandbox',
schema: z.object({ cmd: z.string() }),
},
);
const validateNode = async () => ({
report: await runCommand.invoke({ cmd: 'pnpm test' }),
});💡 说明
把“通过/失败”转成结构化字段,路由更稳定。
15 质量评估指标
可以为 Reviewer 提供可量化的指标:
- 测试覆盖率
- Lint 错误数
- 关键功能检查点
⚠️ 注意
评估指标太多会拖慢流程,优先保留关键指标。
💡 练习题
-
设计题:设计一个“单元测试生成器” Graph。输入一个源文件代码,它应该:- 生成测试用例。- 尝试运行测试(使用执行工具)。- 如果测试失败,根据错误信息修改测试代码或源代码,直到通过。
点击查看答案
用 Reviewer 输出结构化评论,路由回 Coder 直至
approved。 -
操作题:为多文件生成场景设计
filesreducer。点击查看答案
使用
{ ...state, ...update }合并文件内容,避免覆盖丢失。 -
思考题:为什么验证节点比“让模型自评”更可靠?
点击查看答案
验证节点使用真实的编译/测试结果,比模型主观判断更可信。
-
操作题:给 Reviewer 输出加上
severity字段,并根据严重程度决定是否回退。点击查看答案
当
severity === 'blocker'才路由回 Coder,否则直接通过。 -
思考题:代码生成流程中,哪些步骤最容易失控?
点击查看答案
长链路、缺少验证、执行环境不安全都会导致失控。
📚 参考资源
官方文档
本项目相关内容
✅ 总结
本章要点:
- 复杂代码生成任务需要分解为多个明确的步骤。
- 维护结构化的
files状态比纯文本对话更有效。 - 引入
Reviewer循环是提升代码质量的关键。
下一步:如果你准备把“编码助手”做成可长期运行的工程,请继续阅读:
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。