Prompt 模板在代码生成 Agent 中的最佳实践:从需求到可运行代码
Prompt 模板在代码生成 Agent 中的最佳实践从需求到可运行代码一、深度引言与场景痛点大家好我是赵咕咕。去年年底我们在内部工具平台里接了一个AI 代码生成功能。产品经理的原话是用户用自然语言描述需求Agent 直接生成可运行代码然后一键部署。听起来很美好。第一个月的效果让我崩溃——同样输入写一个 FastAPI 接口接收文本 query返回 RAG 检索结果十次生成十种不同的代码。有时候用 LangChain有时候用原生 OpenAI SDK有时候直接写了一个不存在的假库。更离谱的是有一次生成的代码里import rag_magic——这是 LLM 脑补出来的库。问题的根源不是模型能力不够而是Prompt 模板缺少约束。代码生成 Agent 需要一个精心设计的 Prompt 模板来引导 LLM 在给定的技术栈、代码规范和架构约束下生成代码。这篇文章我把代码生成 Agent 的 Prompt 模板设计方法论和工程实践整理出来。二、底层机制与原理深度剖析2.1 为什么简单的 Prompt 不够在代码生成场景下LLM 面对的是一个欠约束的问题。自然语言描述的需求天然包含歧义——写一个 RAG 检索服务可以解读为使用 LangChain 框架使用 LlamaIndex 框架自己手写 embedding search用 Rust 写如果你没限制语言所以 Prompt 的核心任务不是告诉 LLM 做什么而是消除需求中的歧义把开放问题变成约束问题。2.2 Prompt 模板的五层约束模型这五层约束是递进的全局技术约束圈定技术边界。语言只能用 Python 3.11Web 框架只能用 FastAPI代码规范必须通过 Ruff。架构约束指定代码的组织方式。服务必须放在src/services/接口必须是async def Pydantic。上下文注入通过 RAG 检索项目的现有代码让 LLM 知道项目里已经有什么生成代码时能复用已有的类型和函数。生成指令明确对生成代码的要求。要不要类型标注要不要异常处理要不要日志每一条都是约束。自检要求让 LLM 在生成代码后进行自我检查——语法对吗导入的库存在吗类型检查能过吗2.3 为什么需要自检层第五层的自检要求是整个模板最重要的设计。LLM 生成代码后如果不做自检错误率可能在 15-20%。加上自检指令后错误率下降到 5% 以内。原因是 LLM 的幻觉在代码场景表现为假 import。让 LLM 检查所有 import 的模块是否存在相当于让它在输出前做一次事实核查。三、生产级代码实现import asyncio import ast import logging from pathlib import Path from typing import Any from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from pydantic import BaseModel, Field logger logging.getLogger(__name__) class CodeGenRequest(BaseModel): 代码生成请求。 requirement: str Field(..., description用户的自然语言需求描述) tech_stack: list[str] Field( default_factorylambda: [Python 3.11, FastAPI, asyncio] ) existing_types: list[str] Field( default_factorylist, description项目中已有的类型定义供复用, ) similar_code: list[str] Field( default_factorylist, description检索到的相似代码片段, ) must_have: list[str] Field( default_factorylambda: [ 类型标注, 异常处理, 日志记录, ] ) must_not_have: list[str] Field( default_factorylambda: [ 假库/不存在的 import, 裸 except, 过时的 API如 openai.ChatCompletion, 同步阻塞调用如 time.sleep, ] ) class CodeGenResult(BaseModel): 代码生成结果。 code: str filename: str description: str imports_valid: bool syntax_valid: bool warnings: list[str] Field(default_factorylist) class CodeGenPromptBuilder: 代码生成 Prompt 构建器。 SYSTEM_TEMPLATE 你是一个 Python 代码生成专家。请严格遵循以下约束生成代码。 ## 四、边界分析与架构权衡 {tech_stack} ## 五、总结 - 所有函数必须有类型标注 - 异步函数使用 async def避免同步阻塞 - 使用 logging 模块记录关键操作 - 异常必须显式处理禁止裸 except: - 遵循项目现有代码风格 ## 项目上下文已有代码 已有类型定义 {existing_types} 相似代码片段参考风格和模式 {similar_code} ## 输出要求 {output_requirements} ## 禁止事项 {forbidden_items} ## 最终自检 生成代码后你的回答末尾必须包含一个自检段落自检结果语法正确ast.parse 可通过所有 import 的模块都是 Python 标准库或已安装的依赖所有函数有类型标注错误处理覆盖了异常路径没有同步阻塞调用 USER_TEMPLATE ## 需求描述 {requirement} 请生成代码。 def __init__(self): self.template ChatPromptTemplate.from_messages([ (system, self.SYSTEM_TEMPLATE), (human, self.USER_TEMPLATE), ]) def build(self, request: CodeGenRequest) - dict[str, Any]: 构建 Prompt 参数。 return { tech_stack: \n.join( f- {t} for t in request.tech_stack ), existing_types: ( \n.join(fpython\n{t}\n for t in request.existing_types) if request.existing_types else 无 ), similar_code: ( \n---\n.join( fpython\n{c}\n for c in request.similar_code ) if request.similar_code else 无 ), output_requirements: \n.join( f- {r} for r in request.must_have ), forbidden_items: \n.join( f- {r} for r in request.must_not_have ), requirement: request.requirement, } class CodeValidator: 代码静态验证器。 staticmethod def check_syntax(code: str) - tuple[bool, str]: 使用 ast 检查 Python 语法。 try: ast.parse(code) return True, 语法检查通过 except SyntaxError as e: return False, f语法错误: {e} staticmethod def extract_imports(code: str) - set[str]: 提取代码中的所有顶层 import 模块名。 try: tree ast.parse(code) imports set() for node in ast.walk(tree): if isinstance(node, ast.Import): for alias in node.names: imports.add(alias.name.split(.)[0]) elif isinstance(node, ast.ImportFrom): if node.module: imports.add(node.module.split(.)[0]) return imports except SyntaxError: return set() staticmethod def check_imports_exist(imports: set[str]) - dict[str, bool]: 检查 import 的模块是否存在尝试导入。 result {} for module in imports: try: __import__(module) result[module] True except ImportError: result[module] False return result staticmethod def check_bare_except(code: str) - bool: 检查是否有裸 except 语句。 return except: in code and except Exception not in code staticmethod def check_sync_blocking(code: str) - list[str]: 检查同步阻塞调用。 blocking_patterns [ time.sleep, requests.get, requests.post, ] found [] for pattern in blocking_patterns: if pattern in code: found.append(pattern) return found class CodeGenAgent: 代码生成 Agent。 组合 Prompt 模板 LLM 验证器。 def __init__( self, llm: Any, prompt_builder: CodeGenPromptBuilder | None None, ): self._llm llm self._builder prompt_builder or CodeGenPromptBuilder() self._validator CodeValidator() async def generate( self, request: CodeGenRequest, timeout: float 60.0 ) - CodeGenResult: 主入口生成代码并验证。 try: return await asyncio.wait_for( self._generate_impl(request), timeouttimeout ) except asyncio.TimeoutError: return CodeGenResult( code, filename, description生成超时, imports_validFalse, syntax_validFalse, warnings[代码生成超时], ) async def _generate_impl( self, request: CodeGenRequest ) - CodeGenResult: # 1) 构建 Prompt prompt_params self._builder.build(request) chain self._builder.template | self._llm | StrOutputParser() # 2) 调用 LLM try: raw_output await chain.ainvoke(prompt_params) except Exception as e: logger.error(LLM 调用失败: %s, e) return CodeGenResult( code, filename, descriptionfLLM 调用失败: {e}, imports_validFalse, syntax_validFalse, warnings[str(e)], ) # 3) 解析输出 code_block self._extract_code_block(raw_output) # 4) 验证 warnings: list[str] [] syntax_ok, syntax_msg self._validator.check_syntax(code_block) if not syntax_ok: warnings.append(syntax_msg) imports self._validator.extract_imports(code_block) import_status self._validator.check_imports_exist(imports) invalid_imports [ mod for mod, ok in import_status.items() if not ok ] if invalid_imports: warnings.append(f不存在的 import: {, .join(invalid_imports)}) if self._validator.check_bare_except(code_block): warnings.append(检测到裸 except 语句) blocking self._validator.check_sync_blocking(code_block) if blocking: warnings.append(f检测到同步阻塞调用: {, .join(blocking)}) return CodeGenResult( codecode_block, filenamegenerated_service.py, descriptionf从需求生成: {request.requirement[:50]}..., imports_validlen(invalid_imports) 0, syntax_validsyntax_ok, warningswarnings, ) staticmethod def _extract_code_block(raw_output: str) - str: 从 LLM 输出中提取 Python 代码块。 # 尝试匹配 python ... import re match re.search( rpython\n(.*?), raw_output, re.DOTALL ) if match: return match.group(1).strip() # 尝试匹配 ... match re.search( r\n(.*?), raw_output, re.DOTALL ) if match: code match.group(1).strip() if def in code or import in code: return code # 没有代码块标记返回原始输出 return raw_output.strip() async def main(): from langchain_openai import ChatOpenAI llm ChatOpenAI(modelgpt-4o, temperature0) agent CodeGenAgent(llmllm) request CodeGenRequest( requirement( 写一个 FastAPI 路由接收 POST /search 请求 body 包含 query 字段返回 RAG 检索结果的 top-5 文档 ), existing_types[ class RetrievalResult(BaseModel):\n content: str\n score: float\n source: str, ], ) result await agent.generate(request) print(f语法: {result.syntax_valid}) print(f导入: {result.imports_valid}) print(f警告: {result.warnings}) print(f代码:\n{result.code[:500]}...) if __name__ __main__: asyncio.run(main())代码中几个重要的设计五层约束映射到 Prompt 参数build()方法把CodeGenRequest的五类约束转换为 system prompt 的具体内容。每类约束都是可配置的不同项目可以覆盖不同的约束列表。验证器独立于生成器CodeValidator是一个纯静态检查器不依赖 LLM。即使 LLM 自检声称通过了代码仍然走一遍实际的ast.parse和__import__验证。代码块解析_extract_code_block处理了 LLM 输出的各种格式有/无 markdown 标记、纯文本等确保稳定提取。四、边界分析与架构权衡4.1 Prompt 模板应该多严格约束越严格生成的代码越规范但灵活性越低。对于内部工具平台的代码生成约束可以很严格因为技术栈是固定的。对于面向公众的代码生成约束应该适度放宽否则用户体验会很差——我就想生成一个脚本为什么要求我必须用 FastAPI4.2 上下文注入的规模控制RAG 检索到的相似代码片段可能很长。如果注入太多上下文Prompt 会超出模型的 context window。建议相似代码片段用函数签名 前 10 行 ... 的方式展示而不是完整贴入。LLM 需要的是知道有这个函数而不是读完整实现。4.3 验证器应该在 LLM 内还是 LLM 外两者的关系是LLM 先自查代码再外查检查项LLM 自检外部验证语法正确不可靠LLM 不知道自己的语法ast.parse 可靠导入存在70% 准确import100% 准确类型标注可靠mypy 验证代码风格基本可靠Ruff 验证逻辑正确不可靠需要测试用例LLM 自检能减少 50% 的低级错误但不可替代外部验证器。4.4 什么场景下需要代码生成 Prompt 模板场景是否需要模板一次性脚本不需要简单 Prompt 即可内部工具平台标准化代码生成必须有模板公开 API 代码生成模板 宽松约束遗留系统代码迁移模板 大量上下文多语言代码生成每种语言独立模板五、总结代码生成 Agent 的 Prompt 模板设计本质上是一场约束工程约束越多质量越高——但别过度。找到让代码可用的最小约束集。上下文比 Prompt 本身更重要——告诉 LLM 项目里已有的函数和类型比教它写代码规范更有效。自检 外检——LLM 自查是辅助ast.parse__import__mypy才是门神。验证器要独立于 LLM——不要在 Prompt 里让 LLM 保证代码正确它做不到。你用 Python 解释器来保证。设计良好的 Prompt 模板就像写好了一个代码规范文档——新来的 LLM 读一遍就能按你的风格写代码。设计不好的 Prompt 模板就像你跟一个实习生说随便写——出来的东西大概率不是你要的。下一篇预告智能运维 Agent 的日志分析 RAG海量日志的实时索引与异常检索方案。