Prompt 工程实战——写好 prompt 的方法论:思维链、少样本示例、从差到好

核心论点:前七篇全在讲"怎么喂上下文给 AI"------Rules、@ 引用、搜索、Plan 模式。但如果 prompt 本身写得模糊,喂再多上下文也没用。这一篇回到源头:Prompt 本身怎么写。


一个被忽略的事实

整个系列的核心哲学是"AI 编码的瓶颈在项目认知输入"。但这有一个前提:prompt 本身的质量是合格的

对比:

复制代码
Prompt A(模糊):
  给订单模块加个缓存

Prompt B(清晰):
  @ .../redis_cache_service.py  @ .../routers/order.py
  在 order_query() 里加 Redis 缓存:
  - 查询前先 await cache.get(order_key),命中直接返回
  - 未命中查 DB 后 await cache.set(order_key, data, ttl=600)
  - order_update() 里加 await cache.delete(order_key) 防脏缓存

Prompt B 比 A 多了两样东西:上下文(@ 了文件)和具体指令(查哪里、怎么查、ttl 多少、哪里需要清缓存)。同样的需求,prompt 写法不同,结果天差地别。


四类编码 prompt 的实战模式

三段式 prompt(最高频)

复制代码
[上下文] @ 涉及的文件
[约束]   要遵守的规则
[动作]   要做什么 + 验收标准

示例:
@ .../llm_service.py  @ .../config.py
- 不新建类,扩展现有方法
- 遵循 async/await 规范
给 chat() 加 temperature 参数,默认值从 config 读,透传到 openai 调用。

格式:上下文 + 约束 + 动作

为什么三段式比混乱叙述有效:AI 按顺序处理信息。先看上下文(这是项目),再看约束(这是边界),最后看动作(这是任务)。顺序对了,输出质量明显提升。

少样本示例(教 AI 你想要的输出格式)

当你要的是一类重复操作时,给一个例子比描述 10 句更有效:

复制代码
@ tests/test_conversation_service.py
参考 test_summarize_normal 的写法,给 get_messages() 写测试:

# 参考例子
@pytest.mark.asyncio
async def test_summarize_normal(mock_llm_response):
    """正常生成摘要"""
    service = ConversationService()
    result = await service.summarize(conversation_id="conv_1")
    assert result is not None
    assert len(result) > 0
    assert "摘要" in result

# 任务
给 get_messages() 写同样风格的测试,覆盖:
- 正常获取消息列表
- 空对话返回空列表
- 分页参数的正确性

少样本示例的价值:AI 不需要猜"你要的测试长什么样"------你给了一个例子,格式、命名、断言风格都明确了。

思维链(让 AI 展示推理过程)

涉及复杂决策时,要求 AI 先输出推理过程:

复制代码
@ .../tool_registry.py  @ .../mcp_server.py  @ .../a2a_routers.py
重构工具调度逻辑。不要直接写代码------先分析:
1. 当前 tool_registry 的调度流程(dispatch 的调用链)
2. MCP Server 和 A2A 分别怎么调用 tool_registry
3. 现有设计的问题在哪里
4. 重构方案(3 个选项 + 推荐)
5. 确认后,按推荐方案实施

思维链解决什么问题:复杂任务中,直接要代码 → AI 可能在错误方向上狂奔。先要求推理 → AI 自己理顺了逻辑 → 再写代码,方向正确率大幅提升。

反面约束(告诉 AI "不要这样")

markdown 复制代码
@ .../core/experiment_service.py
加 experiment 的关闭逻辑。
❌ 不要:
- 新建 ExperimentManager 类
- 直接调 Redis 做状态管理
- 在 router 里加 close 端点

✅ 要:
- 在 experiment_service.py 里加 close() 方法
- 状态用已有的 experiment_store(MySQL)
- 关闭后分流一律返回 control 组

反面约束比正面约束更有效------因为 AI 的"默认行为"往往是写一个新类、调一个新依赖。"不要"比"要"更精准地拦截了默认行为。


从差到好:三个常见 prompt 的进化

缓存功能

复制代码
Bad    "加个缓存"
       → AI 自己写 class Cache,内存 dict

Good   "@ .../redis_cache_service.py  在 order_query() 里加缓存,
         用已有的 redis_cache_service,TTL=600"

Better "@ .../redis_cache_service.py  @ .../routers/order.py
         在 order_query() 里加 Redis 缓存:
         1. 查前 get(order_key),命中直接返回
         2. 未命中查 DB 后 set(order_key, data, ttl=600)
         3. order_update() 加 delete(order_key) 防脏缓存
         ❌ 不新建 cache 类"

进化路径:模糊描述 → 指定组件 → 指定步骤 + 边界处理 + 禁止项

重构

复制代码
Bad    "重构 tool_registry,太乱了"
       → AI 可能重写整个文件,改动远超预期

Good   "重构 tool_registry.py 的 dispatch() 方法:
        保持对外接口不变,只重构内部实现。
        先输出当前 dispatch() 的问题分析,再出重构方案。"

Better "重构 tool_registry.py 的 dispatch() 方法。
        约束:保持所有 public 方法签名不变。
        步骤:
        1. 先列出所有调用 dispatch() 的地方(搜索引用)
        2. 分析 dispatch() 当前的问题
        3. 出两个方案(轻量重构 vs 深度重构)
        4. 确认后实施
        ❌ 不改 public 接口签名
        ❌ 不改 config 和 router"

进化路径:情绪化描述 → 加边界约束 → 加步骤 + 二选一方案 + 禁止项

Bug 修复

复制代码
Bad    "fix the bug"
       → AI 猜你要修什么 bug,改错地方

Good   "format_time() 返回的时间比实际早 8 小时。
        看 datetime_utils.py line 42,应该是 UTC 和 CST 的问题。"

Better "@ .../datetime_utils.py  @ .../tests/test_datetime_utils.py
         format_time() 返回 UTC+0 而非 UTC+8。用 pytz 修复。
         修复后更新 test_format_time_utc8 的期望值。
         同时检查项目里还有没有其他用 datetime.utcnow() 的地方。"

进化路径:无信息 → 定位问题 → 定位 + 解决方案 + 影响范围 + 测试


什么时候应该用 Ask 而不是直接写 prompt

这篇讲的是 prompt 技巧,但要记住第四篇的核心:方案不确定时,先用 Ask 讨论

复制代码
错误做法(在 Craft 里写长 prompt):
  给订单模块加个缓存。可以用 Redis 也可以用内存。
  如果并发不高就内存,高就 Redis。你看看哪个合适。
  → AI 需要自己做决策,结果不确定

正确做法(先用 Ask 讨论):
  Ask: "订单模块要加缓存,项目里已经有 redis_cache_service。
        直接用它会不会太重?有没有更轻量方案?"
  AI: [分析]
  你: "好,用 Redis。在 order_query() 里加。"
  
  然后切 Craft:
  "@ .../redis_cache_service.py  @ .../routers/order.py
   在 order_query() 里加 Redis 缓存... [三段式]"

原则:如果 prompt 里有"或者"、"你看看"、"你觉得哪个"------说明方案不确定,应该先 Ask。


团队级 prompt 规范:把好 prompt 编码成 Rules

个人 prompt 技巧 → 团队规范的方式:把高频的好 prompt 模式写进 Rules。

markdown 复制代码
# .codebuddy/rules/prompt-template/RULE.mdc

## 新功能开发
使用 `/new-feature` skill 启动标准流程。

## Bug 修复
Bug 报告必须包含:
- 问题现象(预期 vs 实际)
- 出错文件 + 行号
- 复现步骤或测试用例

## 重构
重构请求必须包含:
- 约束:哪些接口不能变
- 范围:哪些文件在改动范围内
- 验证方式:用什么测试确认行为不变

把 prompt 规范写进 Rules 后,每个团队成员和 AI 交互时都能看到------相当于团队共享了 prompt 最佳实践。


核心要点

  1. "三段式"是最小的有效 prompt 结构:上下文(@ 文件) + 约束(规则/禁止项) + 动作(步骤 + 验收标准)。一旦习惯这个结构,AI 输出质量稳定提升。
  2. 少样本示例 > 描述------给一个例子比写十句说明更有效。特别是在测试、文档、重复性操作中。
  3. 复杂任务用思维链------先让 AI 分析、推理、出方案,确认后再写代码。不要一步到位要代码。
  4. "反面约束"比"正面约束"拦截力更强------"不要" > "要"。因为 AI 的默认行为是写通用代码,反面约束直接拦截了默认路径。
  5. 好的 prompt 是三层漏斗的"源动力"------Rules 建立项目认知底线(AI 自动知道有什么/不能做什么)、@ 引用提供精准上下文(每次对话喂什么)、Plan 模式控制流程(什么时候做什么)。但如果 prompt 本身是废的,三层漏斗也救不回来。