常见用例

代码生成与辅助

利用 LangGraph 构建支持多步骤规划、生成和评审的编码助手

📚 学习目标

学完这篇文章后,你将能够:

  • 实现“规划 -> 生成 -> 评审”的代码生成流水线
  • 使用 LangGraph 协调多个“专家 Agent”(如前端专家、后端专家)
  • 构建自动化的代码重构工作流

前置知识

在开始学习之前,建议先阅读:


1 为什么代码生成需要 Graph?

简单的 "Write me a game" Prompt 往往只能生成简单的 Demo。要生成高质量的复杂项目,需要模仿人类工程师的工作流:

  1. Tech Spec: 编写技术方案。
  2. Structure: 设计文件结构。
  3. Implementation: 逐步实现每个模块。
  4. 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: boolean
  • comments: 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 test
  • pnpm 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 错误数
  • 关键功能检查点

⚠️ 注意

评估指标太多会拖慢流程,优先保留关键指标。


💡 练习题

  1. 设计题:设计一个“单元测试生成器” Graph。输入一个源文件代码,它应该:- 生成测试用例。- 尝试运行测试(使用执行工具)。- 如果测试失败,根据错误信息修改测试代码或源代码,直到通过。

    点击查看答案

    用 Reviewer 输出结构化评论,路由回 Coder 直至 approved

  2. 操作题:为多文件生成场景设计 files reducer。

    点击查看答案

    使用 { ...state, ...update } 合并文件内容,避免覆盖丢失。

  3. 思考题:为什么验证节点比“让模型自评”更可靠?

    点击查看答案

    验证节点使用真实的编译/测试结果,比模型主观判断更可信。

  4. 操作题:给 Reviewer 输出加上 severity 字段,并根据严重程度决定是否回退。

    点击查看答案

    severity === 'blocker' 才路由回 Coder,否则直接通过。

  5. 思考题:代码生成流程中,哪些步骤最容易失控?

    点击查看答案

    长链路、缺少验证、执行环境不安全都会导致失控。


📚 参考资源

官方文档

本项目相关内容


✅ 总结

本章要点

  • 复杂代码生成任务需要分解为多个明确的步骤。
  • 维护结构化的 files 状态比纯文本对话更有效。
  • 引入 Reviewer 循环是提升代码质量的关键。

下一步:如果你准备把“编码助手”做成可长期运行的工程,请继续阅读:

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page