.NET 中的提示工程

本文介绍重要的提示工程概念。 许多 AI 模型都基于提示,这意味着它们使用预测算法生成的响应(完成)来响应用户输入的文本(提示)。 较新的模型通常还支持聊天形式的完成,其中包含基于角色(系统、用户、助手)的消息和聊天历史记录以保存对话。

使用提示

请考虑以下文本生成示例,其中的提示是用户输入,完成是模型输出

提示:“任期最短的总统是”

完成:“Pedro Lascurain。”

完成看起来是正确的,但如果应用旨在帮助美国历史系的学生,那么结果呢? Pedro Lascurain 的任期只有 45 分钟,在历届总统中任期最短,但他是担任墨西哥的总统。 美国历史系的学生可能正在查找“William Henry Harrison”的信息。 显然,如果提供一些背景信息,该应用可能对其目标用户更有帮助。

提示工程通过提供指示示例线索来为提示添加上下文,以帮助模型生成更好的完成

支持文本生成的模型通常不需要任何特定的格式,但你应该组织提示,以便清楚地知道什么是指示、什么是示例。 支持基于聊天的应用的模型使用三个角色来组织完成:控制聊天的系统角色、代表用户输入的用户角色和响应用户的助手角色。 将提示划分为针对每个角色的消息:

  • 系统消息向模型提供有关助手的指示。 一个提示只能有一个系统消息,并且它必须是第一个消息。
  • 用户消息包含来自用户的提示并显示示例、历史提示,或包含有关助手的指令。 示例聊天完成必须至少包含一个用户消息。
  • 助手消息显示示例或历史完成,并且必须包含对前一个用户消息的响应。 助手消息不是必需的,但如果包含助手消息,则必须将其与用户消息配对以形成示例。

使用指示来改善完成

指示是告知模型如何响应的文本。 指示可以是指令,也可以是命令:

  • 指令告知模型如何工作,但并不是简单的命令 – 以即兴演员的角色设置为例:“你正在帮助学生了解美国历史,因此除非他们特别询问其他国家的情况,否则请谈论美国。”
  • 命令是模型必须遵循的明确命令。 “翻译成他加禄语:”

指令比命令更加开放且灵活:

  • 可以将多个指令组合在一个指示中。
  • 在将指示与示例一起使用时,指示通常效果更好。 但是,由于命令式是明确的命令,因此模型不需要示例来理解它们(尽管可以使用示例来向模型表明如何格式化响应)。 由于指令不会告知模型确切要做什么,因此每个示例都可以帮助模型更好地工作。
  • 通常,最好将复杂的指示分解为一系列步骤,然后使用一系列指令来进行处理。 还应该告知模型输出每个步骤的结果,以便可以轻松地进行精细调整。 虽然你可以自行将指示分解为几个步骤,但更简单的方法是直接告知模型执行该指示,然后输出每个步骤的结果。 此方法称为思路提示

主要内容和支持内容添加上下文

可以添加内容以向指示提供更多上下文。

主要内容是你希望模型根据指示处理的文本。 无论指示需要什么操作,模型都会针对主要内容执行以生成完成。

支持内容是在指示中引用的文本,但不是指示的目标。 模型使用支持内容来完成指示,这意味着支持内容也会出现在完成中,通常作为某种结构(例如标题或列标签)出现。

在指示内容中使用标签可帮助模型确定如何根据指示使用内容。 不要太担心精度 – 标签不必与指示完全匹配,因为模型将处理诸如词形和大写之类的问题。

假设你要使用指示“汇总美国总统的成就”来生成一个列表。 模型可以通过多种方式来组织和排序该列表。 但是,如果你希望该列表按照特定的一组类别对成就进行分组,该怎么做? 使用支持内容将该信息添加到指示中。

调整指示,以便模型按类别分组,并追加指定这些类别的支持内容:

prompt = """
Instructions: Summarize US Presidential accomplishments, grouped by category.
Categories: Domestic Policy, US Economy, Foreign Affairs, Space Exploration.
Accomplishments: 'George Washington
- First president of the United States.
- First president to have been a military veteran.
- First president to be elected to a second term in office.
- Received votes from every presidential elector in an election.
- Filled the entire body of the United States federal judges; including the Supreme Court.
- First president to be declared an honorary citizen of a foreign country, and an honorary citizen of France.
John Adams ...' ///Text truncated
""";

使用示例来引导模型

示例是通过提供示例用户输入和模型输出来表明模型如何响应的文本。 模型使用示例来推理完成中要包含的内容。 示例可以出现在工程提示中的指示之前或之后,但两者不应交错。

示例以提示开头,可以选择性地包含完成。 示例中的完成不必包含逐字响应 - 它可以只包含格式化的单词、无序列表中的第一个项目符号或类似内容,以指示每个完成应如何开始。

根据示例是否包含逐字完成,可将其分类为零样本学习或少样本学习

  • 零样本学习示例包括没有逐字完成的提示。 此方法无需提供示例数据输出即可测试模型的响应。 零样本提示可以有包含线索的完成,例如通过包含“1.”作为完成来指示模型应该输出有序列表
  • 小样本学习示例包括几对带有逐字完成的提示。 小样本学习可以通过补充现有知识来改变模型的行为。

了解线索

线索是传达所需输出结构或格式的文本。 与指示类似,模型不会将线索当作用户输入来处理。 与示例一样,线索向模型表明你想要什么,而不是告诉它该做什么。 可以根据需要添加任意数量的线索,以便可以通过迭代来获得所需的结果。 线索与指示或示例一起使用,应位于提示的末尾。

假设你使用一条指示来告知模型按类别列出总统成就,以及告知模型使用哪些类别的支持内容。 你希望模型生成一个按类别(全部大写)列出的嵌套列表,其中每位总统在每个类别中的成就都列在以其姓名开头的一行上,并按时间顺序列出总统。 在指示和支持内容之后,可以添加三个线索来向模型表明如何构建和格式化该列表:

prompt = """
Instructions: Summarize US Presidential accomplishments, grouped by category.
Categories: Domestic Policy, US Economy, Foreign Affairs, Space Exploration.
Accomplishments: George Washington
First president of the United States.
First president to have been a military veteran.
First president to be elected to a second term in office.
First president to receive votes from every presidential elector in an election.
First president to fill the entire body of the United States federal judges; including the Supreme Court.
First president to be declared an honorary citizen of a foreign country, and an honorary citizen of France.
John Adams ...  /// Text truncated

DOMESTIC POLICY
- George Washington: 
- John Adams:
""";
  • DOMESTIC POLICY 向模型表明你希望模型以大写字母作为每个组的开头
  • - George Washington:向模型表明要在每个部分以一行列出 George Washington 的成就作为开头
  • - John Adams:向模型表明要按时间顺序列出余下的总统

使用 .NET 的示例提示

.NET 提供各种工具来生成提示并与不同的 AI 模型聊天。 使用语义内核连接到各种 AI 模型和服务以及其他 SDK,例如官方的 OpenAI .NET 库。 语义内核包含用于通过不同角色创建提示和维护聊天历史记录的工具,以及其他许多功能。

请考虑以下代码示例:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;

// Create a kernel with OpenAI chat completion
#pragma warning disable SKEXP0010
Kernel kernel = Kernel.CreateBuilder()
                    .AddOpenAIChatCompletion(
                        modelId: "phi3:mini",
                        endpoint: new Uri("http://localhost:11434"),
                        apiKey: "")
                    .Build();

var aiChatService = kernel.GetRequiredService<IChatCompletionService>();
var chatHistory = new ChatHistory();
chatHistory.Add(
    new ChatMessageContent(AuthorRole.System, "You are a helpful AI Assistant."));

while (true)
{
    // Get user prompt and add to chat history
    Console.WriteLine("Your prompt:");
    chatHistory.Add(new ChatMessageContent(AuthorRole.User, Console.ReadLine()));

    // Stream the AI response and add to chat history
    Console.WriteLine("AI Response:");
    var response = "";
    await foreach (var item in
        aiChatService.GetStreamingChatMessageContentsAsync(chatHistory))
    {
        Console.Write(item.Content);
        response += item.Content;
    }
    chatHistory.Add(new ChatMessageContent(AuthorRole.Assistant, response));
    Console.WriteLine();
}

上述代码提供了以下概念的示例:

  • 创建聊天历史记录服务,以根据作者角色提示 AI 模型完成。
  • 使用 AuthorRole.System 消息配置 AI。
  • 接受用户输入,以允许在 AuthorRole.User 上下文中创建不同类型的提示。
  • 异步流式处理来自 AI 的完成以提供动态聊天体验。

扩展提示工程技术

还可以使用更高级的提示工程技术来增强提示的能力,这两种技术在各自相关的文章中有深入介绍。

  • LLM 具有标记输入限制,将会约束提示中可以容纳的文本量。 使用嵌入矢量数据库解决方案来减少表示给定文本片段所需的标记数量。
  • 除非你自己训练 LLM,否则 LLM 不会基于数据进行训练,而自己训练 LLM 既耗费成本,又耗费时间。 使用检索增强的生成 (RAG) 将数据提供给 LLM,而无需对其进行训练。