在 .NET 应用中集成大语言模型:使用 Microsoft.Extensions.AI 的实战指南

作者:微信公众号:【架构师老卢】
3-2 9:37
13

我一直在尝试将大语言模型(LLMs)集成到 .NET 应用中的不同方法,并希望分享我在使用 Microsoft.Extensions.AI 时学到的东西。

大语言模型(LLMs)已经彻底改变了我们构建 AI 驱动应用的方式。虽然许多开发者熟悉基于云的解决方案(如 OpenAI 的 GPT 模型),但得益于像 Ollama 这样的项目,本地运行 LLMs 变得越来越容易。

在本文中,我们将探讨如何使用 Microsoft.Extensions.AI 在 .NET 应用中使用 LLMs。这是一个强大的抽象层,扩展了 Semantic Kernel SDK 的功能。


理解基础组件

大语言模型(LLMs)

LLMs 是基于大量数据训练的深度学习模型,能够理解和生成类似人类的文本。这些模型可以执行各种任务,如文本补全、摘要、分类和对话。虽然传统上通过云 API 访问,但最近的进展使得在标准硬件上本地运行它们成为可能。

Ollama

Ollama 是一个开源项目,简化了在本地运行 LLMs 的过程。它提供了一个 Docker 容器,可以运行各种开源模型(如 Llama),使得在不依赖云服务的情况下进行 AI 实验变得容易。Ollama 处理模型管理和优化,并提供了一个简单的 API 用于交互。

Microsoft.Extensions.AI

Microsoft.Extensions.AI 是一个为 .NET 应用提供统一接口的库,用于与 LLMs 交互。它基于 Microsoft 的 Semantic Kernel 构建,抽象了不同 LLM 实现的复杂性,使开发者可以在不更改应用代码的情况下切换提供者(如 Ollama、Azure 或 OpenAI)。


入门指南

在深入示例之前,以下是本地运行 LLMs 所需的内容:

  1. Docker 在您的机器上运行。
  2. Ollama 容器 运行 llama3 模型:
    # 拉取 Ollama 容器
    docker run --gpus all -d -v ollama_data:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
    
    # 拉取 llama3 模型
    docker exec -it ollama ollama pull llama3
    
  3. 安装一些 NuGet 包(我使用 .NET 9 控制台应用程序构建):
    Install-Package Microsoft.Extensions.AI # 基础 AI 库
    Install-Package Microsoft.Extensions.AI.Ollama # Ollama 提供者实现
    Install-Package Microsoft.Extensions.Hosting # 用于构建 DI 容器
    

简单聊天补全

让我们从一个基本的聊天补全示例开始。以下是最小设置:

var builder = Host.CreateApplicationBuilder();

builder.Services.AddChatClient(new OllamaChatClient(new Uri("http://localhost:11434"), "llama3"));

var app = builder.Build();

var chatClient = app.Services.GetRequiredService<IChatClient>();

var chatCompletion = await chatClient.CompleteAsync("What is .NET? Reply in 50 words max.");

Console.WriteLine(chatCompletion.Message.Text);

这里没有什么花哨的东西——我们只是设置了依赖注入并提出了一个简单的问题。如果您习惯于使用原始 API 调用,您会注意到这种方式是多么简洁。

AddChatClient 扩展方法将聊天客户端注册到 DI 容器中。这允许您将 IChatClient 注入到您的服务中,并使用简单的 API 与 LLMs 交互。该实现使用 OllamaChatClient 与本地运行的 Ollama 容器通信。


实现带历史的聊天

基于前面的示例,我们可以创建一个交互式聊天,维护对话历史记录。这对于上下文感知的交互和实时聊天应用程序非常有用。我们只需要一个 List<ChatMessage> 来存储聊天历史:

var chatHistory = new List<ChatMessage>();

while (true)
{
   Console.WriteLine("Enter your prompt:");
   var userPrompt = Console.ReadLine();
   chatHistory.Add(new ChatMessage(ChatRole.User, userPrompt));

   Console.WriteLine("Response from AI:");
   var chatResponse = "";
   await foreach (var item in chatClient.CompleteStreamingAsync(chatHistory))
   {
       // 我们正在流式传输响应,因此每条消息到达时都会显示
       Console.Write(item.Text);
       chatResponse += item.Text;
   }
   chatHistory.Add(new ChatMessage(ChatRole.Assistant, chatResponse));
   Console.WriteLine();
}

这里的亮点是流式响应——您会像在 ChatGPT 中那样逐渐看到文本的出现。我们还维护了聊天历史记录,这使得模型能够理解之前消息的上下文,使对话感觉更自然。


实际应用:文章摘要

让我们尝试一些更有用的东西——自动摘要文章。我一直在使用这个功能来处理博客文章:

var posts = Directory.GetFiles("posts").Take(5).ToArray();
foreach (var post in posts)
{
   string prompt = $$"""
         You will receive an input text and the desired output format.
         You need to analyze the text and produce the desired output format.
         You not allow to change code, text, or other references.

         # Desired response

         Only provide a RFC8259 compliant JSON response following this format without deviation.

         {
            "title": "Title pulled from the front matter section",
            "summary": "Summarize the article in no more than 100 words"
         }

         # Article content:

         {{File.ReadAllText(post)}}
         """;

   var chatCompletion = await chatClient.CompleteAsync(prompt);
   Console.WriteLine(chatCompletion.Message.Text);
   Console.WriteLine(Environment.NewLine);
}

专业提示:明确指定输出格式(如要求 RFC8259 兼容的 JSON)有助于获得一致的结果。我在处理偶尔格式错误的响应后学到了这一点!


更进一步:智能分类

这里变得非常有趣——我们可以直接从 LLM 获取强类型响应:

class PostCategory
{
    public string Title { get; set; } = string.Empty;
    public string[] Tags { get; set; } = [];
}

var posts = Directory.GetFiles("posts").Take(5).ToArray();
foreach (var post in posts)
{
    string prompt = $$"""
          You will receive an input text and the desired output format.
          You need to analyze the text and produce the desired output format.
          You not allow to change code, text, or other references.

          # Desired response

          Only provide a RFC8259 compliant JSON response following this format without deviation.

          {
             "title": "Title pulled from the front matter section",
             "tags": "Array of tags based on analyzing the article content. Tags should be lowercase."
          }

          # Article content:

          {{File.ReadAllText(post)}}
          """;

    var chatCompletion = await chatClient.CompleteAsync<PostCategory>(prompt);

    Console.WriteLine(
      $"{chatCompletion.Result.Title}. Tags: {string.Join(",",chatCompletion.Result.Tags)}");
}

强类型方法提供了编译时安全性和更好的 IDE 支持,使得维护和重构与 LLM 响应交互的代码更加容易。


不同 LLM 提供者的灵活性

Microsoft.Extensions.AI 的一个关键优势是支持不同的提供者。虽然我们的示例使用 Ollama,但您可以轻松切换到其他提供者:

// 使用 Azure OpenAI
builder.Services.AddChatClient(new AzureOpenAIClient(
        new Uri("AZURE_OPENAI_ENDPOINT"),
        new DefaultAzureCredential())
            .AsChatClient());

// 使用 OpenAI
builder.Services.AddChatClient(new OpenAIClient("OPENAI_API_KEY").AsChatClient());

这种灵活性使您可以:

  • 使用本地模型开始开发
  • 迁移到云提供者进行生产
  • 在不更改应用代码的情况下切换提供者
  • 为不同用例(分类、图像识别等)混合使用不同的提供者

Microsoft.Extensions.AI 使得将 LLMs 集成到 .NET 应用中变得非常简单。无论您是构建聊天界面、处理文档,还是为您的应用添加 AI 驱动的功能,该库都提供了一个干净、一致的 API,适用于不同的 LLM 提供者。

我在这里只是浅尝辄止。自从将其集成到我的项目中以来,我发现了无数用途:

  • 用户提交的自动内容审核
  • 自动支持工单分类
  • 新闻稿的内容摘要
相关留言评论
昵称:
邮箱:
阅读排行