65.9K
CodeProject 正在变化。 阅读更多。
Home

开发集成 OpenAI 到 .NET 应用程序的客户端包

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2023 年 10 月 23 日

CPOL

6分钟阅读

viewsIcon

7646

本文将通过解释开发、构建、测试和使用来介绍如何开发一个集成 OpenAI 到 .NET 应用程序的客户端包。

引言

ConnectingApps.Refit.OpenAI NuGet 包提供了一种将 OpenAI API 集成到 .NET 应用程序的便捷方法。本文将深入探讨该包的功能及其使用方法,帮助开发者充分利用 OpenAI 的强大能力。

背景

OpenAI API 提供了对 ChatGPT 等先进 AI 模型(能够理解和生成文本)的访问。这对于开发需要聊天机器人、游戏中的模拟角色或任何涉及动态文本生成和理解功能的应用程序的开发人员来说非常有价值。该 API 提供了一个简单的用于提示-响应交互的接口。

Using the Code

要使用 ConnectingApps.Refit.OpenAI NuGet 包,请首先设置您的 OpenAI API 密钥。

var apiKey = Environment.GetEnvironmentVariable("OPENAI_KEY");

此行从环境变量中获取 OpenAI API 密钥,这是存储和检索 API 密钥等敏感信息的安全方式。

接下来,实例化 ICompletion(或其他)Refit 接口。

using ConnectingApps.Refit.OpenAI;
using ConnectingApps.Refit.OpenAI.Completions;
// ...
var completionApi = RestService.For<ICompletion>(new HttpClient
{
    BaseAddress = new Uri("https://api.openai.com")
}, OpenAiRefitSettings.RefitSettings);

RestService.For 方法是 Refit 库的一部分。它创建指定接口的实例,在本例中是 ICompletion,该接口提供了与 OpenAI API 交互的方法。该方法接受一个 HttpClient,其基地址指向 OpenAI API 端点,以及 Refit 的设置。这种设置允许使用创建的接口实例轻松方便地进行 REST API 调用。

接口描述

ICompletion 接口是 ConnectingApps.Refit.OpenAI 包的关键组成部分。它定义了允许与 OpenAI API 交互的方法。该接口包含以下方法:

  • CreateCompletionAsync:此方法用于向 OpenAI API 发送完成请求。它接受一个 ChatRequest 对象,该对象指定要使用的模型、随机性温度设置以及要发送的消息。该方法返回一个 ChatResponse 对象,其中包含 API 的响应。

这是 ICompletion 接口的内容:

using System.Threading.Tasks;
using ConnectingApps.Refit.OpenAI.Completions.Request;
using ConnectingApps.Refit.OpenAI.Completions.Response;
using Refit;

namespace ConnectingApps.Refit.OpenAI.Completions
{
    public interface ICompletion
    {
        [Post("/v1/chat/completions")]
        Task<ApiResponse<ChatResponse>> CreateCompletionAsync([Body] 
          ChatRequest chatRequest, [Header("Authorization")] string authorization);
    }
}

除了 ICompletion 之外,您还可以根据所需的功能实例化其他几个接口。以下是概述:

  • ICompletion 用于完成
  • IVariation 用于图像变体
  • IAudioTranslation 用于音频翻译
  • ITrancription 用于音频转录
  • IModeration 用于审核帖子的内容
  • ImageCreation 用于根据您的需求创建图像
  • IFiles 用于提交文件(例如用于微调)
  • IEmbedding 用于将帖子的文本转换为向量,以便可以对帖子进行数学比较。
  • IFineTune 微调是使预训练模型更好地适应特定任务的典型 AI 过程。稍后将对此进行更详细的解释。

如果您想自己为另一个 REST API 构建客户端包,我建议您这样做:

  • 也请使用 Refit。如果您不熟悉它,请阅读 文档。您仍然可以使用自己配置的 HttpClient,但您无需编写调用实现,因为由于使用了属性,它已经为您配置好了。
  • 使用 .NET Standard 2.0。这样,您可以同时支持 .NET (Core) 和 .NET Framework(甚至更多框架)。

测试代码

确保代码的可靠性和正确性至关重要。ConnectingApps.Refit.OpenAI 包包含集成测试以验证其功能。其中一项测试是 CompletionTest 类,它测试该包的完成功能。

CompletionTest 类包含两个测试方法:

  1. CapitalOfFrance:此测试检查询问法国首府时的响应。它期望响应包含单词“Paris”。
  2. CapitalOfFranceTopP:与第一个测试类似,但使用不同的参数设置进行完成请求。它也期望响应包含“Paris”。

这两个测试都利用了 CompletionCaller 委托,该委托负责向 OpenAI API 发出完成请求。测试确保响应成功(HTTP 状态码 200),并且响应的内容与预期输出相符。

这是 CompletionTest.cs 文件的内容:

using System.Net;
using ConnectingApps.Refit.OpenAI.Completions;
using ConnectingApps.Refit.OpenAI.Completions.Request;
using FluentAssertions;
using Refit;
using ConnectingApps.Refit.OpenAI.Completions.Response;

namespace ConnectingApps.Refit.OpenAI.IntegrationTest
{
    public class CompletionTest
    {
        private static readonly 
           Func<ChatRequest, Task<ApiResponse<ChatResponse>>> CompletionCaller;

        static CompletionTest()
        {
            var apiKey = Environment.GetEnvironmentVariable("OPENAI_KEY");
            apiKey.Should().NotBeNullOrEmpty
                   ("OPENAI_KEY environment variable must be set");
            var completionApi = RestService.For<ICompletion>
                ("https://api.openai.com", OpenAiRefitSettings.RefitSettings);
            CompletionCaller = chatRequest => completionApi.CreateCompletionAsync
                               (chatRequest, $"Bearer {apiKey}");
        }

        [Fact]
        public async Task CapitalOfFrance()
        {
            var response = await CompletionCaller(new ChatRequest
            {
                Model = "gpt-3.5-turbo",
                Temperature = 0.7,
                Messages = new List<Message>
                {
                    new()
                    {
                        Role = "user",
                        Content = "What is the capital of the France?",
                    }
                }
            });
            (response.Error?.Content, response.StatusCode).Should().Be
                             ((null, HttpStatusCode.OK));
            response.Content!.Choices!.First().Message!.Content.Should().Contain("Paris");
        }

        [Fact]
        public async Task CapitalOfFranceTopP()
        {
            var response = await CompletionCaller(new ChatRequest
            {
                Model = "gpt-3.5-turbo",
                TopP = 1,
                Messages = new List<Message>
                {
                    new()
                    {
                        Role = "user",
                        Content = "What is the capital of the France?",
                    }
                }
            });
            (response.Error?.Content, response.StatusCode).Should().Be
                                      ((null, HttpStatusCode.OK));
            response.Content!.Choices!.First().Message!.Content.Should().Contain("Paris");
        }
    }
}

此测试类清楚地展示了如何使用该包的功能并验证其正确性。断言是使用 Fluent Assertions 完成的。

微调

如前所述,微调是 OpenAI 支持的典型 AI 功能,因此也得到了 NuGet 包的支持。它的使用有点复杂,因此需要更详细的解释。微调是使预训练模型更好地适应特定任务的过程。通过微调 OpenAI 模型,您可以提高模型在特定用例上的性能。OpenAI 提供了一个 API 来促进这一点,允许您上传训练文件并根据该数据启动微调作业。

首先,使用 OpenAI API 密钥初始化 IFineTune 接口。该接口将用于与 OpenAI 的微调功能进行交互。

var apiKey = Environment.GetEnvironmentVariable("OPENAI_KEY");
var fineTuneApi = RestService.For<IFineTune>(new HttpClient
{
    BaseAddress = new Uri("https://api.openai.com")
}, OpenAiRefitSettings.RefitSettings);
var token = $"Bearer {apiKey}";

之后,GetJobsAsync 方法检索当前的微调作业。示例将检索限制为 200 个作业。

var jobs = await fineTuneApi.GetJobsAsync(token, limit: 200);

现在使用 PostFileAsync 方法将(mydata.jsonl)上传到 OpenAI 的服务器。

await using (var fineTuneDataStream = 
      new FileStream("mydata.jsonl", FileMode.Open, FileAccess.Read))
{
    var openAiApi = RestService.For<IFiles>
        ("https://api.openai.com", OpenAiRefitSettings.RefitSettings);
    var streamPart = new StreamPart(fineTuneDataStream, "mydata.jsonl");
    var postFileResponse = await openAiApi.PostFileAsync(token, streamPart, "fine-tune");
    newTraingFile = postFileResponse.Content!.Id;
}

上传后,通过使用 PostJobAsync 方法启动一个新的微调作业,该方法需要上传的训练文件的 ID 以及要微调的模型。

var newJobResponse = await fineTuneApi.PostJobAsync(new FineTuneRequest
{
    TrainingFile = newTraingFile,
    Model = "gpt-3.5-turbo"
}, token);

由于有一个新作业,可以使用 GetJobAsyncCancelJobAsync 方法分别获取新启动作业的状态和详细信息,以及取消该作业。

var newJob = await fineTuneApi.GetJobAsync(newJobResponse.Content!.Id, token);
var cancelResponse = await fineTuneApi.CancelJobAsync(newJobResponse.Content!.Id, token);

这就是 API 调用在 C# 中应该如何编码。如果您想了解有关概念和数据的更多信息,请阅读 OpenAI 关于此主题的文档

构建管道描述

ConnectingApps.Refit.OpenAI NuGet 包的构建管道定义在 dotnet-desktop.yml 文件中。该管道负责构建、测试、打包并将 NuGet 包发布到 NuGet.org。

这是代码

name: .NET CI

on:
  push:
    branches:
      - main
      - release
      - develop
      - feature/✶✶
      - bugfix/✶✶

jobs:
  build_and_test:
    name: Build and Test
    runs-on: ubuntu-latest

    env:  # Setting environment variable at the job level
      OPENAI_KEY: ${{ secrets.OPENAI_KEY }}  # Accessing the secret 
                      and assigning it to an environment variable

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Setup .NET SDK
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '6.0.x' # Adjust the version as necessary

      - name: Run another one-line script
        run: echo Hello, ${{ vars.PUBLISH_NUGET }}!     

      - name: Restore dependencies
        run: dotnet restore OpenAI.sln

      - name: Build Solution
        run: dotnet build OpenAI.sln --configuration Release --no-restore

      - name: Run Tests
        run: python -c "import os; os.system('dotnet test OpenAI.sln 
             --configuration Release --no-build --verbosity normal  --logger trx');"  

      - name: Publish Test Results
        uses: dorny/test-reporter@v1
        with:
          name: 'Test Results'
          path: '✶✶/TestResults/✶✶/✶.trx'
          reporter: 'dotnet-trx'

  package:
    name: Package
    needs: build_and_test
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Setup .NET SDK
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '6.0.x' # Adjust the version as necessary

      - name: Restore dependencies
        run: dotnet restore OpenAI.sln
  
      - name: Build Solution
        run: dotnet build OpenAI.sln --configuration Release --no-restore          

      - name: Pack
        run: dotnet pack ConnectingApps.Refit.OpenAI/ConnectingApps.Refit.OpenAI.csproj 
             --configuration Release --no-build -o out

      - name: Find package file
        run: find | grep 'nupkg'       

      - name: Publish Artifacts
        uses: actions/upload-artifact@v3
        with:
          name: nuget-package
          path: out/✶.nupkg

      - name: Publish to NuGet
        if: ${{ vars.PUBLISH_NUGET == 'true' }} # Conditional execution based 
                                                # on PUBLISH_NUGET environment variable
        run: dotnet nuget push "out/✶.nupkg" --api-key ${{ secrets.NUGET_KEY }} 
                                     --source https:∕∕api.nuget.org∕v3∕index.json     

以下是管道的详细说明:

  • 触发器:管道在推送到 main、release、develop、feature 和 bugfix 分支时触发。
  • 环境变量:管道使用从 GitHub Secrets 获取的 OPENAI_KEY 环境变量。
  • 构建和测试:管道在 Ubuntu-latest 机器上运行。它检出代码,设置 .NET SDK,还原依赖项,以 Release 配置构建解决方案,并运行测试。然后发布测试结果。
  • 打包:管道将项目打包成 NuGet 包并将其存储在 out 目录中。
  • 发布:如果 PUBLISH_NUGET 环境变量设置为 true,管道将使用 NUGET_KEY Secret 进行身份验证,将 NuGet 包推送到 NuGet.org。

此管道确保 NuGet 包以一致且自动化的方式进行构建、测试和发布,从而保证质量并便于分发。

关注点

在集成 ConnectingApps.Refit.OpenAI 包时,可以明显看出 OpenAI 的功能与 Refit 用户友好的 REST 功能之间的协同作用非常强大。新功能(如图像变体、音频翻译等)的添加使得该包对于寻求在其项目中利用高级 AI 的开发人员来说更加通用。此外,构建管道确保该包始终保持高质量并高效分发。

可以查看 源代码以更详细地了解这一切是如何工作的。

历史

  • 2023 年 10 月 22 日
    • 本文的初始版本
    • 之前,已发布了一些提示性文章。
  • 2023 年 10 月 25 日
    • 解释了微调
© . All rights reserved.