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

为 CodeProject.AI 服务器添加 .NET AI 模块

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2022 年 6 月 8 日

CPOL

9分钟阅读

viewsIcon

16592

简要介绍如何为 CodeProject.AI 服务器添加新的 .NET AI 模块

引言

本文将介绍如何通过改编现有的 .NET 代码并添加一个适配器来为 CodeProject.AI Server 创建一个模块。这将使原始代码的功能能够通过 CodeProject.AI Server 作为 HTTP 端点公开。这个过程与 添加自己的 Python 模块到 CodeProject.AI 类似,只是我们使用 .NET 而不是 Python。

在开始之前,您可能需要确保已阅读 为 CodeProject.AI 添加新模块

选择一个模块

您可以从头开始编写自己的模块,或者使用目前可用的众多开源 AI 项目之一。市面上有成千上万的项目,本文我选择了 DotNet Samples 存储库中的 TextClassificationTF 示例。该项目接收一些文本,并返回该文本是积极还是消极情绪。

将模块添加到 CodeProject.AI 服务器

首先,请确保您已从 GitHub 克隆(或下载)了 CodeProject.AI Server 代码

要将我们选择的模块添加到 CodeProject.AI,我们需要执行以下操作(稍后我们将详细介绍每个步骤):

  1. 在 CodeProject.AI Server 解决方案中创建一个新项目来存放我们的模块。

  2. 在该项目中创建一个 modulesettings.json 文件,用于配置 CodeProject.AI Server 为该模块公开的端点。CodeProject.AI Server 将在启动时发现此文件,配置定义的端点,并启动模块的可执行文件。

  3. 将您要添加的模块的代码复制到您刚刚创建的项目中。为了整洁起见,最好在项目中创建一个子文件夹,其中包含其他模块的全部代码。

    目标是确保易于包含更新。您使用的代码无疑会随着时间的推移而更新。能够获取更新的代码并将其放入同一个子文件夹中,可以实现即时升级。稍加调整即可。

  4. 添加对原始代码引用的任何 NuGet 包的引用。

  5. 如果需要,请重构复制的代码以供通用使用。这通常是必需的,因为您找到的许多代码片段可能包含硬编码的值或位置,可能设计为作为 API 或命令行调用,或者位于 Web 应用程序的控制器深处。

    您可能需要进行一些(希望是微小的)更改,以公开一个可供您的适配器调用的函数,或者用通过环境变量提供的值替换硬编码的值。同样:需要进行的更改越少越好,但有些可能是不可避免的。

  6. 创建一个 CodeProject.AI Server 适配器,通过派生一个类来处理从 CodeProject.AI Server 收到的请求并返回响应。您将派生自一个抽象基类,只需提供一个处理请求的方法。所有样板化的服务器/模块通信和错误处理都已为您处理。

  7. 对项目的 Program.cs 文件进行少量更改,以配置程序运行上述代码。

  8. 测试。这可以通过使用 Postman 等工具或编写一个简单的网页来调用 CodeProject.AI Server 上的新端点来完成。

创建模块项目

当您用 .NET 6 编写 CodeProject.AI 模块时,您创建的是一个轮询 CodeProject.AI Server 的工作服务,用于处理为模块创建的队列中的命令。最简单的方法是在 src/AnalysisLayer 下的文件夹中创建一个 **Worker Service** 项目。**CodeProject.AI Server** 会扫描此文件夹中的目录以获取模块元数据,从而允许服务器启动模块。

执行此操作的步骤如下:

  • 在解决方案资源管理器中右键单击 src/AnalysisLayer 文件夹

    • 选择 **添加** -> **新建项目**
    • 选择 C# 的 **Worker Service** 项目模板
    • Click Next

这将打开 **项目配置** 对话框

Configuration Dialog

  • 将 **项目名称** 设置为 **SentimentAnalysis**
  • 将 **位置** 设置为您复制的 CodeProject.AI 解决方案中的 src\AnalysisLayer 目录。
  • 点击**下一步**。

这将打开 **其他信息** 对话框

Additional Information Dialog

我们无需在此处进行任何更改,因此请点击 **创建**。这将创建项目,其结构如下:

Project Structure

创建 modulesettings.json 文件

modulesettings.json 文件配置模块的:

  • 是否应启动它
  • 如何启动它
  • 它运行的平台
  • CodeProject.AI Server 为该模块公开的端点。在这种情况下,我们将:
    • 公开 https://:5000/v1/text/sentiment
    • 使用 HTTP POST 方法
    • 发送一个表单变量 text,其中包含要分析的文本
    • 并期望一个 JSON 载荷响应,其中包含:
      • 一个布尔值 success 属性,指示操作是否成功完成
      • 一个布尔值 is_positive 属性,指示输入文本是否具有积极情绪
      • 一个浮点数 positive_probability,表示输入文本具有积极情绪的概率,其中 0.5 为中性。

在我们的例子中,modulesettings.json 文件将如下所示:

{
  "Modules": {
    "SentimentAnalysis": {
      "Name": "Sentiment Analysis",
      "Activate": true,
      "Description": "Determines if a comment is positive or negative",
      "FilePath": "SentimentAnalysis\\SentimentAnalysis.dll",
      "Runtime": "dotnet",
      "Platforms": [ "windows", "linux", "docker" ],
      "RouteMaps": [
        {
          "Name": "Sentiment Analysis",
          "Path": "text/sentiment",
          "Method": "POST",
          "Queue": "sentimentanalysis_queue",
          "Command": "sentiment",
          "Description": "Determines if the supplied text has a positive 
                          or negative sentiment",
          "Inputs": [
            {
              "Name": "text",
              "Type": "Text",
              "Description": "The text to be analyzed."
            }
          ],
          "Outputs": [
            {
              "Name": "success",
              "Type": "Boolean",
              "Description": "True if successful."
            },
            {
              "Name": "is_positive",
              "Type": "Boolean",
              "Description": "Whether the input text had a positive sentiment."
            },
            {
              "Name": "positive_probability",
              "Type": "Float",
              "Description": "The probability the input text has a positive sentiment."
            }
          ]
        }
      ]
    }
  }
}

modulesettings.development.json

此文件会覆盖 modulesettings.json 文件中针对 Development 环境的一些值。在这种情况下,可执行文件的位置将在 bin\debug\net6.0 目录中找到,而不是在模块的根文件夹中。

{
  "Modules": {
    "SentimentAnalysis": {
      "FilePath": "SentimentAnalysis\\bin\\debug\\net6.0\\SentimentAnalysis.dll"
    }
  }
}

从示例代码复制代码和资源

情感分析代码使用的数据和模型包含在示例存储库的 sentiment_model 文件夹中。将此文件夹复制到新的模块项目中。

使用数据和模型用于的代码全部包含在示例代码的 Program.cs 文件中。要复制这些代码:

  • 创建一个新的类文件 TextClassifier.cs
  • 将此文件中的 TextClassifier 类内容替换为示例代码 Program.cs 文件中 Program 类的内容。我们将在下一步进行修复。

包含其他 NuGet 和项目依赖项

为了构建此项目,必须包含一些依赖项:

  • 使用 Microsoft ML.NET 框架及其对 TensorFlow 模型支持所需的 NuGet 包。
    • Microsoft.ML
    • Microsoft.ML.SampleUtils
    • Microsoft.ML.TensorFlow
    • SciScharp.TensorFlow.Redist
  • 用于使用 CodeProject.AI .NET SDK 的项目
    • CodeProject.AI.AnalsisLayer.SDK

重构示例代码以供我们使用

示例代码中的代码是作为一个特定示例,其中包含硬编码的输入和大量的 Console.WriteLine 语句,用于显示代码操作的详细信息。我们通过以下方式更新了代码:

  • main 方法转换为类构造函数 TextClassifier
  • 将某些变量转换为字段
  • 更改 PredictSentiment 方法以接受参数而不是使用硬编码的值。

由于更改的实际细节不是我们在此文章中要完成的内容,因此此处不显示代码。这些更改的结果可以在存储库中的代码中看到。

创建请求处理器类

倒数第二个编码步骤是创建后台工作程序,该程序将从 CodeProject.AI Server 中检索请求,处理请求,并返回结果。使用更新的 SDK,其中大部分已封装在抽象类 CommandQueueWorker 中。我们只需创建一个新的类文件 SentimentAnalysisWorker.cs,并在该文件中:

  • 创建一个响应类 SentimentAnalysisResponse,派生自 BackendSuccessResponse,它定义了模块响应的结构。
  • 重写 SentimentAnalysisWorker.ProcessRequest 方法,并且
  • 创建 SentimentAnalysisWorker 构造函数以初始化模块特有的功能。

完成的 SentimentAnalysisWorker.cs 文件是:

using CodeProject.AI.AnalysisLayer.SDK;

namespace SentimentAnalysis
{
    class SentimentAnalysisResponse : BackendSuccessResponse
    {
        /// <summary>
        /// Gets or set a value indicating whether the text is positive.
        /// </summary>
        public bool? is_positive { get; set; }

        /// <summary>
        /// Gets or sets the probablity of being positive.
        /// </summary>
        public float? positive_probability { get; set; }
    }

    public class SentimentAnalysisWorker : CommandQueueWorker
    {
        private const string _defaultModuleId  = "sentiment-analysis";
        private const string _defaultQueueName = "sentimentanalysis_queue";
        private const string _moduleName       = "Sentiment Analysis";

        private readonly TextClassifier _textClassifier;

        /// <summary>
        /// Initializes a new instance of the SentimentAnalysisWorker.
        /// </summary>
        /// <param name="logger">The Logger.</param>
        /// <param name="textClassifier">The TextClassifier.</param>
        /// <param name="configuration">The app configuration values.</param>
        public SentimentAnalysisWorker(ILogger<SentimentAnalysisWorker> logger,
                                       TextClassifier textClassifier,  
                                       IConfiguration configuration)
            : base(logger, configuration, _moduleName, _defaultQueueName, _defaultModuleId)
        {
            _textClassifier  = textClassifier;
        }

        /// <summary>
        /// The work happens here.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns>The response.</returns>
        public override BackendResponseBase ProcessRequest(BackendRequest request)
        {
            string text = request.payload.GetValue("text");
            if (text is null)
                return new BackendErrorResponse(-1, $"{ModuleName} missing 'text' parameter.");

            var result = _textClassifier.PredictSentiment(text);

            if (result is null)
                return new BackendErrorResponse(-1, $"{ModuleName} PredictSentiment returned null.");

            var response = new SentimentAnalysisResponse
            {
                is_positive          = result?.Prediction?[1] > 0.5f,
                positive_probability = result?.Prediction?[1]
            };

            return response;
        }
    }
}

连接一切

连接所有内容很简单。在 Program.cs 文件中:

  • 更改行
    services.AddHostedService<Worker>();
    
    to
    services.AddHostedService<SentimentAnalysisWorker>();
    
  • 通过添加以下行将 TextClassifier 添加到 DI 容器:
    services.AddSingleton<TextClassifier>();
    就在上一行之前。文件应如下所示:
    using SentimentAnalysis;
    
    IHost host = Host.CreateDefaultBuilder(args)
        .ConfigureServices(services =>
        {
            services.AddSingleton<TextClassifier>();
            services.AddHostedService<SentimentAnalysisWorker>();
        })
        .Build();
    
    await host.RunAsync();

您希望将 SentimentAnalysis 设置为 Frontend 项目的构建依赖项,以便在生成 **CodeProject.AI Server** 时构建它。

测试它

为了测试这一点,我创建了一个简单的 test.html 页面,该页面接受一些文本,将其发送到 **CodeProject.AI Server**,并处理和显示结果。我尽可能地将其做得简单,以展示使用新功能的简便性。

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Sentiment Analysis Module Test</title>
    <script type="text/javascript">

        function doAnalysis(textToSend) {
            var formData = new FormData();
            formData.append('text', textToSend);
            fetch('https://:5000/v1/text/sentiment', {
                    method: 'POST',
                    body: formData,
                    cache: "no-cache"
                })
                .then(response => {
                    if (!response.ok) {
                        result.innerText = `Http error! Status : ${response.status}`;
                    }
                    return response.json().then(data => {
                        var resultHTML = data.is_positive
                                ? `<p>The text sentiment was positive 
                                with a probablity of ${data.positive_probability}</p>`
                                : `<p>The text sentiment was negative with a 
                                probablity of ${1.0 - data.positive_probability}</p>`;
                        result.innerHTML = resultHTML;
                    });
                });
        }
    </script>
</head>
<body>
    <h1>Sentiment Analysis Module Test</h1>
    <form method="post" action="" enctype="multipart/form-data" id="myform">
        <div>
            <label for="textToAnalyze">Text to analyze:</label>
            <div>
                <textarea id="textToAnalyze" name="textToAnalyze" 
                 rows="8" cols="80" style="border:solid thin black"></textarea>
            </div>        
        </div>
        <div>
            <button type="button" 
             onclick="doAnalysis(textToAnalyze.value)">Submit</button>
        </div>
        <br />
        <div>
            <label for="result">Result</label>
            <div id="result" name="result" style="border:solid thin black"></div>
        </div>
    </form>
</body>
</html>

要查看此效果,请在 Debugger 中以 Debug 配置运行 **Frontend** 项目(CodeProject.AI Server),然后打开您选择的浏览器中的 test.html 文件。将一些文本复制到文本框中,然后按提交。我使用了亚马逊评论中的文本。您应该会看到类似以下的内容:

额外奖励 - XCOPY 部署 Windows 上的模块

.NET 6 模块不像 Python 模块那样依赖于安装任何运行时或设置虚拟环境。**.NET 6 运行时已由 CodeProject.AI Server 安装程序安装。** 因此,构建的 Release 版本可以 XCOPY 部署到 CodeProject.AI Server 的现有 Windows 安装中。执行此操作的步骤是:

  • 以 **Release** 模式构建模块项目。
  • c:\Program Files\CodeProject\AI\AnalysisLayer 目录中创建一个文件夹。此目录的名称应与 modulesettings.json 文件中 FilePath 的目录名称相同。在此示例中,这将是“SentimentAnalysis”。
  • 将项目 bin\Release\net6.0 目录的内容复制到上一步创建的目录中。
  • 使用 **Service** 应用程序,重新启动 **CodeProject.AI Server** 服务。新模块现在将在 modulesettings.json 文件中定义的端点上公开。

总结

将新模块添加到 CodeProject.AI Server 通常并不困难。您需要:

  1. 选择一个独立的模块,可以将其功能公开为方法调用。
  2. AnalysisServices 文件夹中创建一个项目来存放您的项目,并复制代码
  3. 创建一个适配器,该适配器将在 CodeProject.AI Server 和您的代码之间进行接口
  4. 确保模型和依赖项已到位
  5. 创建一个 modulesettings.json 文件,向服务器描述如何启动模块
  6. 对您要添加的模块进行任何必要的微小更改,以使其能够与您的适配器一起工作,并修改 Program.cs 文件以启动所有工作

您的模块现在是 CodeProject.AI 生态系统的一部分,任何使用该服务器的客户端现在都可以无缝访问您的新模块。恭喜!

© . All rights reserved.