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
迭代优化步骤
-
建立测试集
var testCases = new[] { ("我想买明天的票", "购票"), ("帮我退票", "退票"), ("天气怎么样", "其他"), // ... 更多测试用例 }; -
批量测试
foreach (var (input, expected) in testCases) { var result = await RecognizeIntentAsync(chatClient, input); var isCorrect = result.Intent == expected; Console.WriteLine($"{(isCorrect ? "✅" : "❌")} {input}"); } -
分析失败案例
- 收集识别错误的输入
- 分类错误类型(误判、漏判、实体提取错误等)
- 针对性地调整提示词
-
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