AI 提示工程实战

引言

在 AI 应用开发中,提示词(Prompt) 的质量直接决定了 AI 输出的准确性和实用性。然而,很多开发者在使用大语言模型时,常常遇到以下困境:

  • ❌ AI 的回答不符合预期,需要反复调整
  • ❌ 输出格式混乱,难以解析和使用
  • ❌ 模型理解错误,答非所问
  • ❌ 缺乏系统的优化方法,只能凭感觉调试

本文将基于 Microsoft.Extensions.AI (MEAI) 框架,通过一个真实的铁路票务意图识别案例,带你系统掌握提示工程的核心技巧,从零构建高质量的提示词。

什么是提示工程?

提示工程(Prompt Engineering) 是设计和优化与大语言模型交互的文本指令的艺术与科学。一个好的提示词能够:

✅ 精确传达任务目标
✅ 引导模型生成期望格式的输出
✅ 提供必要的上下文和示例
✅ 避免歧义和误解

提示词的四大基础组件

根据提示工程的最佳实践,一个完整的提示词通常由以下四部分组成:

graph LR
    A[完整提示词] --> B[1. 指令<br/>Instruction]
    A --> C[2. 上下文<br/>Context]
    A --> D[3. 输入<br/>Input]
    A --> E[4. 输出<br/>Output]
    
    B --> B1[明确任务目标]
    C --> C1[提供背景知识]
    D --> D1[描述数据结构]
    E --> E1[定义响应格式]

1. 指令(Instruction)

作用:告诉模型要做什么任务。

// ❌ 不好的指令(模糊)
"分析这段话"

// ✅ 好的指令(明确)
"从用户的输入中识别票务相关意图,判断用户是想购票、退票还是改签"

2. 上下文(Context)

作用:提供背景信息和约束条件。

// ✅ 提供业务上下文
@"你是一个铁路票务系统的智能客服助手。
业务规则:
- 购票需要出发地、目的地、日期
- 退票需要订单号
- 改签需要原订单号和新的出行信息"

3. 输入(Input)

作用:描述输入数据的格式和内容。

// ✅ 明确输入格式
"用户输入: {用户的自然语言查询}"

4. 输出(Output)

作用:定义期望的响应格式。

// ✅ 结构化输出格式
@"以 JSON 格式返回,包含以下字段:
{
  ""intent"": ""购票 | 退票 | 改签 | 其他"",
  ""confidence"": 0.0-1.0,
  ""entities"": { ... }
}"

八大核心提示工程技巧

让我们通过一个真实案例,逐步应用这些技巧来优化提示词。

案例背景:铁路票务意图识别

需求:识别用户的票务意图(购票、退票、改签)并提取关键信息。

示例输入

  • “我想买明天从北京到上海的高铁票”
  • “帮我退掉订单号 12345 的票”
  • “能不能把后天的车改到大后天”

技巧 1:明确任务边界

原则:清晰定义任务范围,避免模型过度发挥。

// ❌ 版本 1.0:任务不明确
var systemMessage = "你是一个客服助手";

// ✅ 版本 2.0:明确任务范围
var systemMessage = @"你是一个铁路票务意图识别系统。
任务:识别用户意图并提取关键信息。
支持的意图:购票、退票、改签。
不支持的请求请标记为'其他'。";

效果对比

输入

V1.0 输出

V2.0 输出

“天气怎么样?”

“今天天气很好…” ❌

意图: 其他 ✅


技巧 2:结构化输出

原则:使用 JSON Schema 强制模型返回可解析的数据。

// ❌ 版本 2.0:自由文本输出
var response = await chatClient.GetResponseAsync(messages);
// 输出: "用户想要购买火车票,出发地是北京..."(难以解析)

// ✅ 版本 3.0:结构化输出
public record IntentResult(
    string Intent,           // "购票" | "退票" | "改签" | "其他"
    float Confidence,        // 0.0-1.0
    Dictionary<string, string> Entities  // 提取的实体
);

var response = await chatClient.GetResponseAsync<IntentResult>(messages);
var result = response.Result;  // 强类型对象,直接使用
Console.WriteLine($"意图: {result.Intent}, 置信度: {result.Confidence}");

MEAI 实现

// 使用 ChatOptions 配置结构化输出
var options = new ChatOptions
{
    ResponseFormat = ChatResponseFormatJson.ForJsonSchema(
        typeof(IntentResult),
        jsonSchemaName: "intent_result"
    )
};

// 或者使用泛型方法(更简洁)
var response = await chatClient.GetResponseAsync<IntentResult>(
    messages, 
    options
);


技巧 3:少样本学习(Few-Shot Learning)

原则:提供 2-5 个示例,让模型理解期望的输入输出模式。

// ✅ 版本 4.0:添加示例
var systemMessage = @"你是一个票务意图识别系统。

示例:
输入: '我想买明天从北京到上海的票'
输出: {
  ""intent"": ""购票"",
  ""confidence"": 0.95,
  ""entities"": {
    ""departure"": ""北京"",
    ""destination"": ""上海"",
    ""date"": ""明天""
  }
}

输入: '帮我退掉订单 12345'
输出: {
  ""intent"": ""退票"",
  ""confidence"": 0.9,
  ""entities"": {
    ""order_id"": ""12345""
  }
}

输入: '天气怎么样'
输出: {
  ""intent"": ""其他"",
  ""confidence"": 0.8,
  ""entities"": {}
}";

示例选择原则

  • 覆盖所有主要意图类型
  • 包含边界情况(如"其他"类别)
  • 展示理想的输出格式
  • 数量适中(2-5个,避免过多)

技巧 4:角色设定

原则:赋予模型明确的角色和专业领域。

// ❌ 普通角色
"你是一个AI助手"

// ✅ 专业角色
@"你是一个拥有10年经验的铁路票务系统专家。
你熟悉中国铁路的所有业务流程和术语。
你的任务是准确识别用户的票务需求。"

效果:角色设定能让模型更好地理解领域特定的术语和逻辑。


技巧 5:添加业务上下文

原则:提供必要的业务规则和约束条件。

// ✅ 版本 5.0:添加业务规则
var systemMessage = @"你是一个票务意图识别系统。

业务规则:
1. 购票必须包含:出发地、目的地、日期(至少两个)
2. 退票必须包含:订单号或车次信息
3. 改签必须包含:原订单信息 + 新的出行计划
4. 如果信息不完整,在 entities 中标记缺失的字段

支持的日期表达:
- 明天、后天、大后天
- 具体日期(如:12月15日)
- 星期(如:下周一)";


技巧 6:拒答策略

原则:明确告诉模型如何处理无法回答的问题。

// ✅ 版本 6.0:添加拒答策略
var systemMessage = @"...

拒答规则:
1. 对于非票务相关的问题,intent 设为'其他'
2. 对于无法识别的意图,confidence 设为 0.5 以下
3. 不要尝试回答或执行任何票务操作,只做意图识别";

示例

// 输入: "帮我订一张票"
// ❌ 没有拒答策略:模型可能尝试执行订票操作
// ✅ 有拒答策略:返回 {"intent": "购票", "confidence": 0.4, "note": "信息不完整"}


技巧 7:思维链引导(Chain-of-Thought)

原则:让模型展示推理过程,提高复杂任务的准确性。

// ✅ 版本 7.0:添加推理步骤
var systemMessage = @"分析步骤:
1. 识别关键词(买票、退票、改签、订单号、地名、日期)
2. 判断意图类型
3. 提取实体信息
4. 评估置信度(信息完整度)

示例推理:
输入: '我想把后天的车改到大后天'
步骤1: 关键词 - '改', '后天', '大后天'
步骤2: 意图类型 - 改签
步骤3: 实体 - 原日期(后天), 新日期(大后天)
步骤4: 置信度 - 0.7(缺少车次信息)";

适用场景

  • 复杂的多步骤推理
  • 需要解释的决策过程
  • 边界情况的判断

技巧 8:选择更强的模型

原则:不同任务需要不同能力的模型。

var options = new ChatOptions
{
    // ❌ 简单任务使用强模型:浪费成本
    ModelId = "gpt-4o",  // 每1M tokens $15
    
    // ✅ 根据任务选择模型
    ModelId = userQuery.IsComplex ? "gpt-4o" : "gpt-3.5-turbo"
};

模型选择指南

任务复杂度

推荐模型

成本

适用场景

简单

gpt-3.5-turbo

意图识别、分类

中等

gpt-4o-mini

实体提取、摘要

复杂

gpt-4o

复杂推理、代码生成


生成参数调优

除了提示词本身,ChatOptions 中的生成参数也会显著影响输出质量。

关键参数详解

1. Temperature(温度)

作用:控制输出的随机性和创造性。

var options = new ChatOptions
{
    // 值范围: 0.0 - 2.0
    Temperature = 0.7f
};

推荐值

任务类型

Temperature

原因

意图识别、分类

0.0 - 0.3

需要确定性强的输出

文本生成、摘要

0.5 - 0.7

平衡创造性和一致性

创意写作、头脑风暴

0.8 - 1.2

需要多样性和创造性

示例对比

// Temperature = 0.0(确定性)
输入: "明天北京到上海"
输出: {"intent": "购票", "confidence": 0.95}  // 每次相同

// Temperature = 1.0(创造性)
输入: "明天北京到上海"
输出1: {"intent": "购票", "confidence": 0.9}
输出2: {"intent": "咨询", "confidence": 0.7}  // 每次可能不同

2. TopP(核采样)

作用:限制模型从累积概率达到 P 的候选词中采样。

var options = new ChatOptions
{
    TopP = 0.9f  // 考虑累积概率前 90% 的词
};

使用建议

  • TopP = 1.0:考虑所有可能的词(默认)
  • TopP = 0.9:适合大多数任务
  • TopP = 0.5:输出更集中,适合精确任务
  • 不要同时调整 Temperature 和 TopP

3. MaxOutputTokens(最大输出长度)

var options = new ChatOptions
{
    MaxOutputTokens = 500  // 限制输出长度
};

推荐值

  • 意图识别:200-500
  • 摘要生成:500-1000
  • 文章生成:2000-4000

4. StopSequences(停止序列)

作用:遇到指定文本时停止生成。

var options = new ChatOptions
{
    StopSequences = new[] { "\n\n", "END", "---" }
};

使用场景

  • 防止模型生成不必要的额外内容
  • 精确控制输出边界

完整实战案例

让我们整合所有技巧,构建一个生产级的意图识别系统:

using Microsoft.Extensions.AI;

// 1. 定义数据模型
public record IntentResult(
    string Intent,
    float Confidence,
    Dictionary<string, string> Entities,
    string? Note = null
);

// 2. 构建优化后的提示词
var systemPrompt = @"你是一个专业的铁路票务意图识别系统。

## 任务
从用户输入中识别票务意图并提取关键信息。

## 支持的意图
- 购票: 需要出发地、目的地、日期
- 退票: 需要订单号或车次信息
- 改签: 需要原订单信息 + 新出行计划
- 其他: 非票务相关的查询

## 分析步骤
1. 识别关键词(地名、日期、订单号等)
2. 判断意图类型
3. 提取实体信息
4. 评估置信度

## 示例
输入: '我想买明天从北京到上海的票'
输出: {
  ""intent"": ""购票"",
  ""confidence"": 0.95,
  ""entities"": {
    ""departure"": ""北京"",
    ""destination"": ""上海"",
    ""date"": ""明天""
  }
}

输入: '天气怎么样'
输出: {
  ""intent"": ""其他"",
  ""confidence"": 0.9,
  ""entities"": {},
  ""note"": ""非票务相关查询""
}

## 规则
- 信息不完整时在 note 中说明
- 对非票务问题返回 intent='其他'
- 只做意图识别,不执行操作";

// 3. 配置最优参数
var options = new ChatOptions
{
    ModelId = "gpt-4o-mini",  // 平衡成本和性能
    Temperature = 0.2f,       // 低温度保证一致性
    MaxOutputTokens = 500,    // 限制输出长度
    ResponseFormat = ChatResponseFormatJson.ForJsonSchema(
        typeof(IntentResult),
        jsonSchemaName: "intent_result"
    )
};

// 4. 执行识别
public async Task<IntentResult> RecognizeIntentAsync(
    IChatClient chatClient,
    string userInput)
{
    var messages = new List<ChatMessage>
    {
        new(ChatRole.System, systemPrompt),
        new(ChatRole.User, userInput)
    };
    
    var response = await chatClient.GetResponseAsync<IntentResult>(
        messages, 
        options
    );
    
    return response.Result;
}

// 5. 测试
var result = await RecognizeIntentAsync(
    chatClient, 
    "我想把后天从上海到杭州的车改到大后天"
);

Console.WriteLine($@"
意图: {result.Intent}
置信度: {result.Confidence}
实体: {string.Join(", ", result.Entities.Select(kv => $"{kv.Key}={kv.Value}"))}
备注: {result.Note}
");

// 输出:
// 意图: 改签
// 置信度: 0.85
// 实体: original_date=后天, departure=上海, destination=杭州, new_date=大后天
// 备注: 缺少原订单号


提示优化闭环

提示工程是一个持续迭代的过程:

graph LR
    A[编写初始提示] --> B[测试评估]
    B --> C[收集问题案例]
    C --> D[分析失败原因]
    D --> E[优化提示词]
    E --> B
    
    style A fill:#4CAF50
    style E fill:#2196F3

迭代优化步骤

  1. 建立测试集

    var testCases = new[]
    {
        ("我想买明天的票", "购票"),
        ("帮我退票", "退票"),
        ("天气怎么样", "其他"),
        // ... 更多测试用例
    };
    
    
  2. 批量测试

    foreach (var (input, expected) in testCases)
    {
        var result = await RecognizeIntentAsync(chatClient, input);
        var isCorrect = result.Intent == expected;
        Console.WriteLine($"{(isCorrect ? "✅" : "❌")} {input}");
    }
    
    
  3. 分析失败案例

    • 收集识别错误的输入
    • 分类错误类型(误判、漏判、实体提取错误等)
    • 针对性地调整提示词
  4. A/B 测试

    // 对比不同版本的提示词效果
    var resultV1 = await TestPrompt(promptV1, testCases);
    var resultV2 = await TestPrompt(promptV2, testCases);
    
    Console.WriteLine($"V1 准确率: {resultV1.Accuracy}%");
    Console.WriteLine($"V2 准确率: {resultV2.Accuracy}%");
    
    

常见陷阱与解决方案

陷阱 1:提示词过长

问题:提示词太长导致成本上升和延迟增加。

解决方案

// ❌ 每次都包含所有示例
var prompt = "示例1...\n示例2...\n示例3...\n示例4...\n示例5...";

// ✅ 使用缓存或动态选择相关示例
var relevantExamples = SelectRelevantExamples(userInput, allExamples, count: 2);
var prompt = BuildPrompt(relevantExamples);

陷阱 2:过度依赖示例

问题:模型只会模仿示例,无法泛化。

解决方案

// ✅ 示例 + 明确的规则
var prompt = @"规则:
1. 如果包含'买'、'购'、'订',判定为购票
2. 如果包含'退'、'取消',判定为退票

示例(仅供参考格式):
输入: '我要买票'
输出: {""intent"": ""购票""}";

陷阱 3:忽略边界情况

问题:只测试常见输入,忽略特殊情况。

解决方案

// ✅ 包含边界情况的测试集
var testCases = new[]
{
    ("买票", "购票"),  // 极简输入
    ("我想买明天从北京到上海晚上8点的高铁票", "购票"),  // 详细输入
    ("", "其他"),  // 空输入
    ("asdfghjkl", "其他"),  // 乱码
    ("买票买票买票", "购票"),  // 重复
};


工具和资源

1. 提示词调试工具

// 开启详细日志查看模型的实际输入输出
var chatClient = new OpenAIClient(...)
    .AsChatClient("gpt-4")
    .UseLogging(loggerFactory);  // 记录所有 prompt 和 response

2. Token 计数器

// 估算 prompt 的 token 数量(成本相关)
var tokenCount = Encoding.UTF8.GetByteCount(prompt) / 4;  // 粗略估算
Console.WriteLine($"预估 tokens: {tokenCount}");

3. 性能监控

var sw = Stopwatch.StartNew();
var result = await chatClient.GetResponseAsync(messages);
sw.Stop();

Console.WriteLine($@"
响应时间: {sw.ElapsedMilliseconds}ms
Token 使用: {result.Usage.TotalTokens}
成本估算: ${result.Usage.TotalTokens * 0.00002}  // gpt-3.5 价格
");


总结

提示工程的核心要点:

四大组件:指令、上下文、输入、输出缺一不可
八大技巧:边界明确、结构化输出、少样本学习等系统化应用
参数调优:Temperature、TopP 等参数针对任务优化
持续迭代:建立测试集、分析失败、A/B 测试形成闭环

记住:好的提示词 = 清晰的任务定义 + 合适的示例 + 精确的输出格式 + 持续的优化


作者: [Your Name]
日期: 2025年12月11日
标签: #AI #PromptEngineering #MEAI #BestPractices