提示词工程
为了大模型的结果更可预测,最简单有效的办法之一就是应用提示工程,其中最常见且有效的方法如下:
- 使提示更具体
- 使用格式化为输出添加结构
- 提供带有少量提示的示例
- 告诉AI该怎么做以避免做错什么(设定据答策略)
- 为AI提供上下文
- 在聊天完成提示中使用角色
- 鼓励AI
- 运用思维链
- 使用更优秀的模型
详情参见Prompt Engineering-提示工程,值得注意的是在SemanticKernel中定义角色是这样定义的
<message role="user"></message>
<message role="system"></message>
<message role="assistant"></message>
测试准备工作
//根据上面的文章获取IChatCompletionService
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
内联提示词
内联提示词,顾名思义,提示词直接写在了代码中,对于简单的提示词,直接内联在代码中,无可厚非,对于复杂的提示词将不太好进行代码管理,尤其是还要涉及一些参数配置。如下就是一个将文案改写为小红书风格的提示词的内联提示词的示例:
using Microsoft.SemanticKernel.Connectors.OpenAI;
var article = """
9月6日16时20分,今年第11号超强台风“摩羯”在海南文昌沿海登陆。超强台风“摩羯”风力有多大?记者只能“抱团”出镜。
""";
var prompt = $"""
请使用 Emoji 风格编辑以下段落,该风格以引人入胜的标题、每个段落中包含表情符号和在末尾添加相关标签为特点。
请确保保持原文的意思。
{article}
""";
// 参数配置
PromptExecutionSettings settings = new OpenAIPromptExecutionSettings(){
Temperature = 1,
TopP = 1,
FrequencyPenalty = 0,
PresencePenalty = 0
};
settings.Display();
var response = await chatCompletionService.GetChatMessageContentAsync(prompt, settings);
response.Content.Display();
🌪️🌊 超强台风“摩羯”来袭!🌬️🏝️
9月6日16时20分,超强台风“摩羯”(今年第11号)🌀猛烈袭击了海南文昌沿海。风力到底有多强?💨记者们只能“抱团”出镜报道,现场形势严峻!📹🙈
#台风摩羯 #海南天气 #记者报道
| 参数名称 | 类型 | 是否必需 | 默认值 | 描述 |
|---|---|---|---|---|
| max_tokens | 数字 | 可选 | null | 生成的最大标记数。提示的标记数加上max_tokens不能超过模型的上下文长度。 |
| temperature | 数字 | 可选 | 1 | 采样温度。值越高,模型越大胆。适合创意写作或需要明确答案的应用。 |
| top_p | 数字 | 可选 | 1 | 核采样方法,仅考虑累积概率质量大于或等于top_p的最小标记集。 |
| presence_penalty | 数字 | 可选 | 0 | 介于-2到2之间的值,惩罚新标记是否出现在文本中,减少其出现的可能性。 |
| frequency_penalty | 数字 | 可选 | 0 | 介于-2到2之间的值,惩罚新标记在文本中的现有频率,减少其重复的可能性。 |
定义变量
在以上的内联提示词中我们使用的是字符串拼接的方式构造的提示词,但在SK中其提供了特定的语法{{$variableName}}进行变量定义,举例而言,以上示例可以改写为:
var prompt = """
请使用 Emoji 风格编辑以下段落,该风格以引人入胜的标题、每个段落中包含表情符号和在末尾添加相关标签为特点。
请确保保持原文的意思。
{{ $article }}
""";
// 参数配置
PromptExecutionSettings settings = new OpenAIPromptExecutionSettings(){
Temperature = 1,
TopP = 1,
FrequencyPenalty = 0,
PresencePenalty = 0
};
var rewriteFunction = kernel.CreateFunctionFromPrompt(prompt, settings);
var article = """
9月6日16时20分,今年第11号超强台风“摩羯”在海南文昌沿海登陆。超强台风“摩羯”风力有多大?记者只能“抱团”出镜。
""";
var rewriteResult = await kernel.InvokeAsync(rewriteFunction, new() { ["article"] = article });
以上示例,如果不需要指定参数的情况下,也可以简写为以下模式:
var prompt = """
请使用 Emoji 风格编辑以下段落,该风格以引人入胜的标题、每个段落中包含表情符号和在末尾添加相关标签为特点。
请确保保持原文的意思。
{{$article}}
""";
var article = """
9月6日16时20分,今年第11号超强台风“摩羯”在海南文昌沿海登陆。超强台风“摩羯”风力有多大?记者只能“抱团”出镜。
""";
var result = await kernel.InvokePromptAsync(prompt, new() { ["article"] = article });
Console.WriteLine(result);
🌪️ **超强台风“摩羯”登陆海南!** 🌀
9月6日16时20分,今年的第11号超强台风“摩羯”在海南文昌沿海地区强势登陆。🌊 风力强劲到何种程度?记者们只能紧紧“抱团”出现在镜头前。📸 #台风#海南#强风
文件模板提示词
在上面的示例中演示了如何创建和运行内联提示并使用变量。但是,在大多数情况下,需要在单独的文件中创建提示词,以便可以轻松地将它们导入到多个Sk的项目中的中并进行共享。
txt format prompt template (skprompt.txt & config.json)
接下来就来介绍下如何将提示词使用txt格式模板化并保存到文件中。首先创建以下的目录文件结构。
Prompts
│
└─── RewriteRedBookStyle
|
└─── config.json
└─── skprompt.txt
其中RewriteRedBookStyle 就代表一个Plugin,具体的skprompt.txt 就代表一个语义函数(Semantic Function),这个后续讲解Plugin 再具体讲解。
其中skprompt.txt用来定义具体的提示词,config.json用来进行具体的配置。
首先将上面小红书的提示词保存到skprompt.txt 的文件中:
请使用 Emoji 风格编辑以下段落,该风格以引人入胜的标题、每个段落中包含表情符号和在末尾添加相关标签为特点。请确保保持原文的意思。
{{$article}}
在将config.json 配置如下:
{
"schema": 1,
"type": "completion",
"description": "A function that rewrite with Emoji style",
"execution_settings": {
"default": {
"max_tokens": 1000,
"temperature": 0
}
},
"input_variables": [
{
"name": "article",
"description": "The user's request.",
"required": true
}
]
}
其中配置的参数解释如下:
| 英文术语 | 中文翻译 | 描述 |
|---|---|---|
| type | 类型 | 提示的类型。在这种情况下,我们使用的是completion 类型。 |
| description | 描述 | 提示的作用描述。用于自动编排计划与功能。 |
| completion | 完成设置 | 完成模型的设置。对于OpenAI模型,包括最大令牌数和温度属性。 |
| max_tokens | 最大令牌数 | 模型生成的最大令牌数。 |
| temperature | 温度 | 控制生成文本的随机性。 |
| input | 输入 | 定义提示中使用的变量(例如,输入)。 |
接下来就可以通过以下方式来使用文件中定义的提示词模板:
var kernelPlugins = kernel.CreatePluginFromPromptDirectory("Prompts");
var article = @"9月6日16时20分,今年第11号超强台风“摩羯”在海南文昌沿海登陆。超强台风“摩羯”风力有多大?记者只能“抱团”出镜。";
var result = await kernel.InvokeAsync(kernelPlugins["RewriteRedBookStyle"], new() { ["article"] = article });
result.Display();
yaml format prompt template
以上使用的txt格式的提示词模板,将提示词和配置一拆为二,是比较主流的方案,但SK 也支持将二者合二为一,具体就是通过yaml 格式进行定义,但需要用到额外的NuGet包:Microsoft.SemanticKernel.Yaml。
#r "nuget:Microsoft.SemanticKernel.Yaml"
接下来还是以上例为基础进行改写,在Prompts 文件夹下定义一个RewriteEmojiStyle的文件夹,然后定义一个rewritwithemoji.yaml。
Prompts
│
└─── RewriteEmojiStyle
|
└─── rewritwithemoji.yaml
具体rewritewithemoji.yaml 的内容如下:
name: RewriteEmojiStyle
template: |
请使用 Emoji 风格编辑以下段落,该风格以引人入胜的标题、每个段落中包含表情符号和在末尾添加相关标签为特点。请确保保持原文的意思。
{{$article}}
template_format: semantic-kernel
description: A function that rewrite with Emoji style.
input_variables:
- name: article
description: The user's request.
is_required: true
output_variable:
description: The rewrite emoji content.
execution_settings:
default:
temperature: 0.6
接下来就可以使用以下方式来测试yaml格式的提示词模板:
var prompt = await File.ReadAllTextAsync("Prompts/RewriteEmojiStyle/rewirtewithemoji.yaml");
var rewriteFunction = kernel.CreateFunctionFromPromptYaml(prompt);
var article = @"9月6日16时20分,今年第11号超强台风“摩羯”在海南文昌沿海登陆。超强台风“摩羯”风力有多大?记者只能“抱团”出镜。";
var result = await kernel.InvokeAsync(rewriteFunction, new() { ["article"] = article });
result.Display();
SK 提示词模板
SK自带的提示词模板是一种使用纯文本定义和组合AI函数的简单方法。您可以使用它来创建自然语言提示、生成响应、提取信息、调用其他提示或执行任何其他可以用文本表示的任务。
该模板支持三个基本特性,允许:
- 包含变量
- 调用外部函数
- 将参数传递给函数
无需编写任何代码或导入任何外部库,只需使用双花括号{{…}}在提示词中嵌入表达式。语义内核将解析您的模板并执行其背后的逻辑。这样,您可以轻松地将AI集成到您的产品中。
Variables(变量)
要在提示符中包含变量值,请使用{{$VariableName}}语法。
例如,如果您有一个名为name的变量来保存用户名,您可以编写:
Hello {{$name}}, welcome to Semantic Kernel!
花括号中的空格会被忽略,如果为了提高可读性,可以适当添加空格:
Hello {{ $name }}, welcome to Semantic Kernel!
Function calls (函数调用)
要调用外部函数并将结果嵌入到提示符中,请使用{{namesspace.FunctionName}}语法。
例如,如果您有一个名为weather.getForecast (位于weather命名空间下)的函数,它返回给定位置的天气预报。
namespace weather;
[KernelFunction("getForecast")]
[Description("获取给定位置的天气预报")]
[return: Description("指定位置的天气预报")]
public Weather GetForecast(string localtion){
//....
}
您可以这样编写提示词:
The weather today is {{weather.getForecast}}.
上面的提示词等价于:
The weather today is {{weather.getForecast $localtion}}
这是因为SK 调用函数时会自动根据函数定义的参数名来传递函数参数。
Function Parameters(函数参数)
调用外部函数并向其传递参数,使用{{namespace.functionName $varName}} 和 {{namespace.functionName "value"}} 语法.
例如,如果要向天气预报函数传递不同的输入,可以编写:
The weather today in {{$city}} is {{weather.getForecast $city}}.
using System.ComponentModel;
using Microsoft.SemanticKernel;
public class WeatherPlugin
{
[KernelFunction("GetForecast")]
[Description("获取给定位置的天气预报")]
[return: Description("指定位置的天气预报")]
public static string GetForecast( string location )
{
return $"Sunny, 23℃";
}
}
var prompt = "The weather today in {{ $location }} is {{GetForecast}}.";
kernel.Plugins.Clear();
kernel.Plugins.AddFromType<WeatherPlugin>();
var res = await kernel.InvokePromptAsync(prompt, new (){ ["location"]= "Shenzhen" });
res.Display();
The weather in Shenzhen today is sunny with a temperature of 23 degrees Celsius.
var prompt = "The weather today in {{$city}} is {{GetForecast $city}}.";
kernel.Plugins.Clear();
kernel.Plugins.AddFromType<WeatherPlugin>();
var res = await kernel.InvokePromptAsync(prompt, new (){ ["city"]= "Shenzhen" });
res.Display();
The weather in Shenzhen today is sunny with a temperature of 23 degrees Celsius.
注意事项
关于特殊字符的注意事项:
语义函数模板是文本文件,因此无需转义特殊字符像换行符和制表符。但是,有两种情况需要特殊语法:
提示模板中需要包含双花括号
双花括号有一个特殊的用例,它们用于注入变量、值和函数到模板中。
如果您需要在提示中包含{{和}}序列,这可能会触发特殊的渲染逻辑,最好的解决方案是使用引号括起来即可,比如:
{{ "{{" }} and {{ "}}" }}
举例而言:
{{ "{{" }} and {{ "}}" }} are special SK sequences.
以上提示词会渲染成:
{{ and }} are special SK sequences.
var prompt = """
{{ "{{" }} and {{ "}}" }} are special SK sequences.
""";
var promptTemplateFactory = new KernelPromptTemplateFactory();
promptTemplateFactory.TryCreate(new PromptTemplateConfig(prompt), out var template);
var renderedPrompt = await template.RenderAsync(kernel);
renderedPrompt.Display();
{{ and }} are special SK sequences.
包含引号
值可以用单引号和双引号括起来。为避免需要特殊语法:
- 在使用包含单引号的值,我们建议用双引号括起来。
...text... {{ functionName "one 'quoted' word" }} ...text...
- 当使用包含双引号的值,用单引号括起来。
...text... {{ functionName 'one "quoted" word' }} ...text...
简而言之,就是用双引号括住单引号,用单引号括住双引号 。
var prompt = """
{{ "'shengjie'" }}
{{'"shengjie"' }}
""";
var promptTemplateFactory = new KernelPromptTemplateFactory();
var config = new PromptTemplateConfig(prompt);
config.AllowDangerouslySetContent = true;
promptTemplateFactory.TryCreate(config, out var template);
var renderedPrompt = await template.RenderAsync(kernel);
renderedPrompt.Display();
'shengjie'
"shengjie"
使用转移符
对于那些值同时包含单引号和双引号的情况,您将需要使用\符号进行转义。
- 对于需要双引号,使用
\":... {{ "quotes' \"escaping\" example" }} ... - 对于需要单引号,使用
\':... {{ 'quotes\' "escaping" example' }} ... - 对于需要反斜杠,使用
\\:... {{ 'c:\\documents\\ai' }} ...
前两个示例都会渲染为:... quotes' "escaping" example ...
注意:\' 总是会渲染为',\"总是会渲染为"。,因此如果需要渲染\' 或\":则需要使用以下\\\' 或\\\"。
var prompt = """
{{ 'quotes\' "escaping" example' }}
""";
var promptTemplateFactory = new KernelPromptTemplateFactory();
var config = new PromptTemplateConfig(prompt);
config.AllowDangerouslySetContent = true;
promptTemplateFactory.TryCreate(config, out var template);
var renderedPrompt = await template.RenderAsync(kernel);
renderedPrompt.Display();
quotes' "escaping" example
816

被折叠的 条评论
为什么被折叠?



