Semantic Kernel推广

作者:技术拾光

时间:2026-06-29

浏览:200

提示词工程通过具体化、格式化、提供示例、设定拒答策略及上下文等策略提升大模型输出可预测性。SemanticKernel支持内联提示、变量定义及txt/yaml文件模板,并具备变量嵌入、外部函数调用等模板特性。

文章目录

提示词工程:让大模型输出更可预测的实操指南

想让大模型的输出更可靠、更可控?提示词工程是最直接、最有效的方法。不过,方法虽多,真正落地时仍有不少注意事项。下面结合 Semantic Kernel 的实践,介绍几个关键策略和具体实现方式。

提示词工程的核心方法

为了让大模型的结果更可预测,最有效的做法就是运用提示工程。常见且有效的方法包括:

Semantic Kernel推广

  • 使提示更具体
  • 使用格式化输出为结果添加结构
  • 提供带有少量示例的提示
  • 告诉AI该做什么、避免什么(设定拒答策略)
  • 为AI提供上下文
  • 在聊天完成提示中使用角色
  • 鼓励AI运用思维链
  • 使用更优质的模型

详细内容可参考 Prompt Engineering 相关文档。值得注意的是,在 Semantic Kernel 中定义角色的写法如下:


测试准备工作

// 根据上面的文章获取IChatCompletionService
var chatCompletionService = kernel.GetRequiredService();

内联提示词

内联提示词,即提示词直接写在代码中。对于简单提示词,直接内联在代码中无可厚非;但对于复杂提示词,尤其是涉及参数配置时,代码管理变得不便。下面是一个“将文案改写为小红书风格的提示词”的内联示例:

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之间的值,惩罚新标记在文本中的现有频率,减少其重复的可能性。
定义变量

上述内联提示词使用的是字符串拼接方式。实际上,Semantic Kernel 提供了更优雅的语法 {{$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号超强台风“摩羯”在海南文昌沿海地区强势登陆。 风力强劲到何种程度?记者们只能紧紧“抱团”出现在镜头前。
#台风#海南#强风

文件模板提示词

以上演示了如何创建和运行内联提示,并使用了变量。但在实际项目中,更常见的做法是将提示词放在单独的文件里,以便跨项目复用和共享。

txt 格式提示模板 (skprompt.txt & config.json)

首先来看如何用 txt 格式将提示词模板化并保存到文件中。创建如下目录结构:

 Prompts
 │
 └─── RewriteRedBookStyle
      |└─── config.json
      └─── skprompt.txt

RewriteRedBookStyle 代表一个 Plugin,而 skprompt.txt 则是一个语义函数(Semantic Function),具体细节在后续介绍 Plugin 时会展开。

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 格式提示模板

txt 格式将提示词和配置拆分为两个文件,这是较为主流的方案。但 Semantic Kernel 也支持将二者合并为一个 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 提示词模板

Semantic Kernel 自带的提示词模板是一种使用纯文本定义和组合 AI 函数的简单方法。你可以用它来创建自然语言提示、生成响应、提取信息、调用其他提示,或者执行任何其他可以用文本表示的任务。该模板支持三个基本特性:

  • 包含变量
  • 调用外部函数
  • 将参数传递给函数

无需编写任何代码或导入外部库,只需使用双花括号 {{…}} 在提示词中嵌入表达式。语义内核会解析模板并执行其背后的逻辑。这样一来,你可以轻松地将 AI 集成到产品中。

Variables(变量)

要在提示符中包含变量值,使用 {{$VariableName}} 语法。

例如,如果你有一个名为 name 的变量来保存用户名,可以这样写:

Hello {{$name}}, welcome to Semantic Kernel!

花括号中的空格可被忽略,也可以为提高可读性而适当添加空格:

Hello {{ $name }}, welcome to Semantic Kernel!

Function calls(函数调用)

要调用外部函数并将结果嵌入提示符中,使用 {{namespace.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( stringlocation )
    {
        return $"Sunny, 23℃";
    }
}
var prompt = "The weather today in {{ $location }} is {{GetForecast}}.";
kernel.Plugins.Clear();
kernel.Plugins.AddFromType();
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();
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:documentsai' }} ...

前两个示例都会渲染为:... 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

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章