
1. 提示词工程到底在解决什么问题很多人第一次接触提示词工程以为就是“把话说清楚”。这个理解对了一半但远远不够。我做了两年多的大模型应用开发从客服机器人到代码生成助手都落地过踩过的坑告诉我提示词工程的本质是在模型能力边界内用最低的沟通成本换取最稳定的输出质量。它不是玄学也不是“咒语”而是一套可以拆解、可以复用、可以量化的工程方法。你可能会问模型不是越来越聪明了吗为什么还要费劲写提示词我举个实际例子。同一个需求“帮我写一个用户登录接口”你直接丢给模型它可能给你返回Python Flask的代码也可能给你Java Spring Boot的代码甚至可能给你一段伪代码。但如果你在提示词里明确“使用Spring Boot 3.x Spring Security 6返回RESTful风格接口包含JWT令牌生成逻辑”输出就会稳定得多。提示词工程的价值就是把“可能”变成“大概率”再把“大概率”变成“可预期”。这篇文章适合三类人看第一类是完全没接触过提示词工程想系统入门的新手第二类是用过ChatGPT或类似工具但输出质量忽高忽低想找到稳定方法的开发者第三类是在做AI应用落地需要把提示词管理纳入工程体系的团队负责人。我会从五个核心要素出发每个要素都配上可直接运行的代码示例最后再聊几个我实际踩过的坑和排查技巧。2. 五大核心要素拆解与底层逻辑2.1 角色设定给模型一个明确的身份锚点角色设定是提示词工程里最容易被低估的一环。很多人觉得“你是一个资深工程师”这种话是废话模型又不会真的变成工程师。但实际测试下来角色设定会显著影响模型调用知识的倾向性和输出的专业度。我做过一组对比实验同一个问题“解释一下什么是闭包”不加角色设定时模型给出的回答偏向教科书定义举的例子是JavaScript的计数器函数。加上“你是一个有十年经验的JavaScript全栈工程师擅长用生活化类比解释复杂概念”之后模型会先用一个“背包”的类比引入然后才给出代码示例最后还补充了内存泄漏的注意事项。输出的信息密度和可读性完全不一样。背后的原理其实不复杂。大模型的训练数据里包含了大量不同身份、不同场景的文本。当你指定一个角色时相当于在模型的“记忆网络”里激活了与这个角色相关的知识区域和表达风格。角色越具体激活的区域越精准。但角色设定有个常见的坑不要堆砌太多头衔。我见过有人写“你是一个资深架构师、全栈工程师、算法专家、产品经理”这种设定反而会让模型无所适从。我的经验是一个核心角色加一个辅助特质就够了。比如“你是一个专注于高并发场景的后端架构师回答时优先考虑性能和可扩展性”。# 角色设定的代码示例 system_prompt 你是一个专注于Python数据处理的后端工程师有5年Pandas和NumPy实战经验。 你的回答风格先给结论再给代码最后补充性能注意事项。 遇到不确定的问题时明确说“这个点我需要确认”不要编造。 user_prompt 我有一个100万行的CSV文件需要按用户ID分组计算每个用户的订单总金额怎么处理最快 messages [ {role: system, content: system_prompt}, {role: user, content: user_prompt} ]2.2 任务描述把模糊需求翻译成可执行指令任务描述是提示词的主体部分也是最考验功力的地方。我总结了一个原则能用动词就不用名词能量化就不定性能给示例就不空口描述。举个例子“帮我优化这段代码”就是一个典型的模糊指令。模型不知道你是要优化性能、优化可读性、还是优化代码长度。但如果你说“把这段代码的时间复杂度从O(n²)降到O(n log n)保持原有功能不变用Python实现”模型就能给出非常精准的优化方案。这里有个实操技巧把大任务拆成小任务。我做过一个合同信息抽取的项目一开始的提示词是“从这份合同里提取所有关键信息”结果模型返回的格式五花八门有时候是JSON有时候是表格有时候是段落。后来我改成三步第一步“识别合同中的甲方、乙方、签约日期、合同金额”第二步“以JSON格式输出key用英文value用原文”第三步“如果某个字段在合同中找不到value填null”。输出立刻稳定了。任务描述里还有一个容易被忽略的点明确输出格式。你是要Markdown表格、JSON、还是纯文本字段名用什么日期格式是YYYY-MM-DD还是其他这些细节不写清楚模型就会“自由发挥”。我现在的习惯是只要涉及结构化输出一定在提示词里给一个完整的示例。2.3 上下文管理让模型知道“前因后果”上下文管理是区分新手和老手的关键分水岭。新手往往只关注“我问了什么”老手会关注“模型看到了什么”。大模型的上下文窗口是有限的你塞进去的信息越多模型对每条信息的注意力就越分散。我踩过的一个典型坑在一个多轮对话的客服机器人里我把用户过去20轮的对话全部塞进上下文结果模型开始“胡言乱语”把第三轮的问题和第十五轮的答案混在一起。后来我改成只保留最近5轮对话加上一个“历史摘要”字段把更早的对话压缩成一句话准确率立刻回升。上下文管理的核心原则是相关性优先冗余信息果断砍掉。具体操作上我通常会把上下文分成三层第一层是系统提示词定义角色和规则第二层是任务相关的背景信息比如产品文档、代码库说明第三层是当前对话历史。每一层之间用明确的分隔符隔开比如三个井号或者XML标签。# 上下文管理的代码示例 system_prompt 你是一个电商客服助手。以下是产品信息 product_info 产品名称智能空气净化器X1 适用面积20-40平方米 滤芯寿命6-12个月 噪音等级睡眠模式28分贝 /product_info 回答规则 1. 只基于上述产品信息回答不要编造参数 2. 如果用户问的问题超出产品信息范围回复“这个问题我需要帮您转接人工客服” 3. 回答控制在100字以内 # 多轮对话时只保留最近3轮 conversation_history [ {role: user, content: 这个净化器适合多大房间}, {role: assistant, content: 适用面积为20-40平方米。}, {role: user, content: 滤芯多久换一次}, {role: assistant, content: 滤芯寿命为6-12个月具体取决于使用环境。}, {role: user, content: 晚上睡觉吵不吵} ]2.4 思维链引导让模型“先想再答”思维链Chain of Thought是提示词工程里最实用的技巧之一。简单说就是让模型在给出最终答案之前先展示推理过程。这个技巧在数学题、逻辑推理、代码调试等场景下效果特别明显。我做过一个测试一道稍微复杂的数学应用题直接问模型准确率大概60%。加上“请一步一步思考”之后准确率提升到85%以上。原因在于大模型是逐token生成的如果直接输出答案它没有“中间计算”的空间。而思维链相当于给了模型一个草稿纸让它把中间步骤写出来最后再汇总。但思维链不是万能的。对于简单的信息检索类任务加思维链反而会降低效率。比如你问“今天北京天气怎么样”模型直接回答就行不需要“首先我需要知道北京的地理位置然后查询天气数据……”这种废话。我的经验是判断标准很简单如果这个任务需要多步推理或者计算就用思维链如果是一步到位的查询或生成就不用。还有一个进阶技巧叫“自洽性采样”就是让模型用不同的思路跑多次然后取多数答案。这个在数学题上效果很好但成本会翻倍。实际项目中我一般只在关键决策点上用。# 思维链引导的代码示例 prompt 一个水池有两个进水管和一个出水管。进水管A单独注满需要6小时进水管B单独注满需要8小时 出水管C单独排空需要12小时。如果三个管子同时打开多久能注满水池 请一步一步思考先计算每个管子的效率再计算净效率最后求时间。 # 模型输出示例 # 第一步A管效率 1/6 池/小时 # 第二步B管效率 1/8 池/小时 # 第三步C管效率 1/12 池/小时排水 # 第四步净效率 1/6 1/8 - 1/12 4/24 3/24 - 2/24 5/24 池/小时 # 第五步时间 1 / (5/24) 24/5 4.8小时 # 答案4.8小时2.5 输出约束把“自由发挥”关进笼子输出约束是保证提示词工程可落地性的最后一道防线。没有约束的模型就像没有围栏的羊群你不知道它会跑到哪里去。输出约束主要包括三个方面格式约束、长度约束、内容约束。格式约束最常见的就是JSON。我现在的习惯是只要下游系统需要解析就一定要求模型输出JSON并且给出完整的schema。比如“输出一个JSON对象包含name字符串、age整数、skills字符串数组三个字段”。如果模型输出不符合格式我会在提示词里加一句“只输出JSON不要有任何其他文字”。长度约束也很重要。我见过一个项目提示词里没写长度限制结果模型给每个用户问题都返回了800字的小作文前端展示直接崩了。后来加上“回答控制在150字以内”问题解决。但要注意长度约束要合理太短会导致信息缺失太长又浪费token。内容约束是最难的部分。你需要明确告诉模型“什么能说什么不能说”。比如在医疗咨询场景必须加一句“不要给出具体的用药剂量建议建议用户咨询专业医生”。在代码生成场景可以加“不要使用已废弃的API”。这些约束不是限制模型的能力而是降低业务风险。# 输出约束的代码示例 prompt 从以下用户评论中提取情感倾向和关键词。 用户评论这个手机电池太不耐用了一天要充三次电但是拍照效果确实不错。 输出要求 1. 只输出JSON不要有任何其他文字 2. JSON格式{sentiment: 正面/负面/中性, keywords: [关键词1, 关键词2]} 3. sentiment只能填正面、负面、中性三者之一 4. keywords最多3个按重要性排序 输出示例{sentiment: 负面, keywords: [电池, 不耐用, 拍照]} 3. 实战代码示例从零搭建一个提示词模板系统3.1 项目背景与技术选型光说不练假把式。这一章我带你从零搭一个提示词模板系统用Python实现不依赖任何重型框架。为什么不用LangChain或者Spring AI不是它们不好而是对于理解提示词工程的本质来说手写一遍比调库更有价值。等你理解了底层逻辑再用框架就是事半功倍。这个系统的核心功能有三个第一把提示词模板和变量分离方便复用第二支持多轮对话的上下文管理第三对输出做基本的格式校验。技术栈就是Python标准库加上OpenAI的API或者其他兼容接口代码量控制在200行以内。我选这个方案的理由很简单轻量、可控、易调试。很多团队一上来就上重型框架结果出了问题不知道是框架的锅还是提示词的锅。先用最朴素的方式跑通再逐步引入工具这是我踩了无数坑之后总结的路线。3.2 提示词模板的代码实现先定义一个模板类核心思路是用字符串格式化加上条件渲染。我不用Jinja2因为对于提示词场景来说Python自带的format就够用了少一个依赖少一个坑。class PromptTemplate: def __init__(self, template: str, required_vars: list None): self.template template self.required_vars required_vars or [] def render(self, **kwargs) - str: # 检查必填变量 missing [v for v in self.required_vars if v not in kwargs] if missing: raise ValueError(f缺少必填变量: {missing}) # 渲染模板 try: return self.template.format(**kwargs) except KeyError as e: raise ValueError(f模板变量未提供: {e}) def partial(self, **kwargs) - PromptTemplate: # 部分渲染返回新的模板对象 rendered self.template for key, value in kwargs.items(): rendered rendered.replace({ key }, str(value)) return PromptTemplate(rendered, self.required_vars) # 使用示例 code_review_template PromptTemplate( template 你是一个{language}代码审查专家。请审查以下代码指出潜在问题。 代码 {language} {code}审查要求按严重程度排序最多列出5个问题每个问题给出具体行号和修改建议如果代码没有问题回复“代码质量良好” , required_vars[language, code] )prompt code_review_template.render( languagepython, codedef add(a, b):\n return a b )这个模板类的设计有两个小心思。第一required_vars机制可以在渲染前就发现变量缺失而不是等到调用API时报错。第二partial方法支持“半成品”模板比如你把系统提示词固定下来只留用户输入作为变量这样可以减少重复代码。 ### 3.3 多轮对话的上下文管理实现 多轮对话的难点在于上下文窗口有限不能无限追加。我的策略是“滑动窗口 摘要压缩”。具体来说保留最近N轮完整对话更早的对话压缩成一段摘要。 python class ConversationManager: def __init__(self, max_recent_turns: int 5, max_summary_length: int 200): self.max_recent_turns max_recent_turns self.max_summary_length max_summary_length self.history [] self.summary def add_message(self, role: str, content: str): self.history.append({role: role, content: content}) self._compress_if_needed() def _compress_if_needed(self): # 如果历史超过阈值把最早的部分压缩成摘要 if len(self.history) self.max_recent_turns * 2: old_messages self.history[:-self.max_recent_turns * 2] self.summary self._summarize(old_messages) self.history self.history[-self.max_recent_turns * 2:] def _summarize(self, messages: list) - str: # 实际项目中这里调用模型做摘要这里用简单拼接示意 text .join([m[content] for m in messages]) return text[:self.max_summary_length] ... def get_context(self) - list: context [] if self.summary: context.append({role: system, content: f历史对话摘要{self.summary}}) context.extend(self.history) return context这个实现里_summarize方法在实际项目中应该调用模型来做而不是简单截断。但核心思路是一样的用摘要换空间用空间换准确率。我实测下来5轮完整对话加200字摘要在大多数客服场景下已经够用了。3.4 输出格式校验与重试机制模型输出不符合格式是家常便饭。我的做法是加一层校验和重试而不是手动去改提示词。校验逻辑用Python的json库和正则表达式就能搞定。import json import re def validate_json_output(text: str, required_keys: list) - tuple: 校验模型输出是否为合法JSON且包含必填字段 返回(是否通过, 解析后的对象或错误信息) # 尝试提取JSON部分模型可能输出多余文字 json_match re.search(r\{.*\}, text, re.DOTALL) if not json_match: return False, 未找到JSON内容 try: data json.loads(json_match.group()) except json.JSONDecodeError as e: return False, fJSON解析失败: {e} missing [k for k in required_keys if k not in data] if missing: return False, f缺少必填字段: {missing} return True, data def call_with_retry(prompt: str, max_retries: int 3) - dict: 带重试的模型调用 for attempt in range(max_retries): response call_model(prompt) # 假设这是你的模型调用函数 passed, result validate_json_output(response, [sentiment, keywords]) if passed: return result # 重试时把错误信息追加到提示词里 prompt prompt f\n\n上次输出格式错误{result}。请严格按照JSON格式重新输出。 raise RuntimeError(f重试{max_retries}次后仍未获得合法输出)这个重试机制的关键在于把错误信息反馈给模型。我试过直接重试成功率大概50%把错误信息追加到提示词后再重试成功率能到85%以上。因为模型看到“上次输出格式错误”之后会调整自己的输出策略。4. 常见问题与排查技巧实录4.1 模型不按格式输出怎么办这是最高频的问题。我总结了一个排查顺序先看提示词里有没有给示例再看示例是不是足够清晰最后看有没有加“只输出XX”的强约束。如果这三步都做了还是不行大概率是模型能力问题。我的经验是小模型7B参数以下对格式约束的遵循度明显不如大模型。这种情况下要么换模型要么在输出后加一层正则清洗。我现在的项目里只要涉及结构化输出都会加一层后处理不把宝全押在模型身上。还有一个技巧把格式要求放在提示词的最后。因为大模型对末尾内容的注意力更高。我试过把“只输出JSON”放在开头和放在结尾放在结尾的遵循率高出20%左右。4.2 提示词越写越长效果反而变差这个问题我遇到过好几次。一开始觉得信息越多越好结果模型开始“抓不住重点”。后来我学乖了提示词的长度应该和任务的复杂度匹配。简单任务控制在200字以内复杂任务也不要超过800字。如果确实需要塞很多信息我的做法是分层系统提示词放角色和规则用户提示词放具体任务背景信息用XML标签包裹放在中间。这样模型能清楚地区分“哪些是规则哪些是数据”。另外定期清理提示词里的“僵尸指令”。有些指令是项目初期加的后来业务变了指令还在模型就会困惑。我现在的习惯是每个月review一次提示词把不再需要的约束删掉。4.3 多轮对话中模型“忘记”了前面的设定这个问题的根源通常是上下文窗口溢出。模型不是真的“忘记”而是你塞进去的内容超过了它的处理能力早期的信息被“挤出去”了。解决方案有三个层次。第一层减少不必要的上下文比如把用户的历史消息压缩成摘要。第二层把关键设定放在每轮对话的系统提示词里而不是只放在第一轮。第三层如果对话确实很长考虑用RAG检索增强生成的方式把相关历史片段检索出来再塞进去而不是全量塞。我实测下来第二层方案性价比最高。把“你是一个电商客服只回答产品相关问题”这句话放在每一轮的系统提示词里比只放在第一轮的效果好很多。4.4 常见问题速查表问题现象可能原因排查步骤解决方案输出格式不稳定缺少格式示例或强约束检查提示词是否有JSON示例和“只输出”指令添加完整示例把格式要求放在末尾回答过于笼统任务描述不够具体检查是否用了“优化”“改进”等模糊动词量化目标如“降低到O(n)”多轮对话混乱上下文溢出或缺少角色重述检查历史消息数量和系统提示词位置压缩历史每轮重述角色设定模型编造信息缺少“不知道就说不知道”的约束检查是否有防幻觉指令添加“不确定时明确说明”的规则输出太长或太短缺少长度约束检查是否有字数限制添加“控制在XX字以内”思维链不生效任务本身不需要推理判断任务是否多步骤简单任务去掉思维链复杂任务保留4.5 我踩过的三个典型坑第一个坑是过度依赖提示词解决所有问题。有一次模型总是输出错误的日期格式我花了两个小时改提示词最后发现是后处理代码里的正则写错了。提示词工程不是万能的该写代码校验的地方就写代码。第二个坑是忽略模型版本差异。同一个提示词在GPT-4上跑得好好的换到GPT-3.5就崩了。后来我养成了一个习惯提示词里标注适用的模型版本换模型时重新测试。第三个坑是不做A/B测试。我一度觉得自己的提示词写得很好直到同事用另一个版本跑出了更好的效果。现在我改提示词之前一定会准备一个测试集跑一遍对比数据再上线。5. 提示词工程的工程化落地建议5.1 把提示词当成代码来管理提示词不应该散落在各个业务代码里而应该集中管理。我的做法是建一个prompts目录每个提示词一个文件用YAML格式存储。这样版本控制、diff对比、回滚都很方便。# prompts/code_review.yaml name: code_review version: 1.2 model: gpt-4 system: | 你是一个{language}代码审查专家。 回答风格先给结论再给具体问题最后给修改建议。 user: | 请审查以下代码 {language} {code}要求按严重程度排序每个问题给出行号最多5个问题 constraints: max_tokens: 1000 temperature: 0.3这样做的好处是提示词的修改可以走代码审查流程而不是某个人在数据库里偷偷改了一行字。我经历过一次线上事故就是因为有人在后台改了一个提示词导致所有客服机器人的回答风格突变。 ### 5.2 建立提示词的测试与评估体系 没有测试的提示词就是定时炸弹。我的做法是维护一个测试集每个测试用例包含输入和期望输出。每次修改提示词跑一遍测试集看通过率有没有下降。 测试集不需要很大20到50个用例就能覆盖大部分场景。关键是**用例要有代表性**包括正常输入、边界输入和异常输入。比如代码审查场景测试集里要有“有明显bug的代码”“没有bug的代码”“语法错误的代码”三种类型。 评估指标我一般看三个格式合规率、内容准确率、平均响应长度。格式合规率低于95%就要排查内容准确率低于80%就要考虑换模型或者改提示词。 ### 5.3 提示词优化的迭代节奏 提示词优化不是一锤子买卖而是一个持续迭代的过程。我的节奏是新功能上线前密集迭代上线后每周review一次稳定后每月review一次。 每次迭代只改一个变量。比如这次只改角色设定下次只改输出格式。如果一次改多个地方出了问题不知道是哪个改动导致的。这个原则和做实验是一样的控制变量法。 另外**保留每次迭代的版本和测试数据**。我见过一个团队提示词改了十几版最后发现第一版效果最好但已经找不回来了。这种低级错误用Git就能避免。 ### 5.4 团队协作中的提示词规范 如果是多人协作提示词需要统一的规范。我们团队的做法是每个提示词文件头部必须写清楚作者、创建日期、适用模型、变更记录。变量命名用下划线分隔全部小写。系统提示词和用户提示词分开写不要混在一起。 还有一个血泪教训**不要在提示词里硬编码业务数据**。我见过有人在提示词里直接写了产品价格结果价格调整后忘了改提示词客服机器人报了一周的错误价格。业务数据应该通过变量传入而不是写死在模板里。 ## 6. 从提示词工程到上下文工程 做了两年多提示词工程我最大的体会是**提示词工程的上限取决于你对业务的理解深度**。你越清楚模型需要知道什么、不需要知道什么提示词就越简洁有效。那些试图用“万能提示词”解决所有问题的尝试最后都失败了。 现在行业里有一个趋势从“提示词工程”转向“上下文工程”。意思是不再只关注提示词怎么写而是关注整个上下文窗口里放什么信息、怎么组织、怎么动态调整。这个方向我觉得是对的。因为随着模型能力越来越强提示词本身的技巧会越来越不重要而“给模型提供正确的信息”会越来越重要。 如果你刚开始学提示词工程我的建议是先把手头的场景跑通别追求完美。写第一版测试改再测试。迭代三五轮之后你自然就有感觉了。那些所谓的“最佳实践”都是在具体场景里磨出来的不是看几篇文章就能学会的。 最后分享一个我常用的调试技巧**把模型当成一个聪明但健忘的新同事**。你不会指望新同事看一眼需求文档就能完美执行你会给他示例、给他反馈、给他上下文。提示词工程也是一样的道理。