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





5.00/5 (6投票s)
简要介绍如何为 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,我们需要执行以下操作(稍后我们将详细介绍每个步骤):
-
在 CodeProject.AI Server 解决方案中创建一个新项目来存放我们的模块。
-
在该项目中创建一个 modulesettings.json 文件,用于配置 CodeProject.AI Server 为该模块公开的端点。CodeProject.AI Server 将在启动时发现此文件,配置定义的端点,并启动模块的可执行文件。
-
将您要添加的模块的代码复制到您刚刚创建的项目中。为了整洁起见,最好在项目中创建一个子文件夹,其中包含其他模块的全部代码。
目标是确保易于包含更新。您使用的代码无疑会随着时间的推移而更新。能够获取更新的代码并将其放入同一个子文件夹中,可以实现即时升级。稍加调整即可。
-
添加对原始代码引用的任何 NuGet 包的引用。
-
如果需要,请重构复制的代码以供通用使用。这通常是必需的,因为您找到的许多代码片段可能包含硬编码的值或位置,可能设计为作为 API 或命令行调用,或者位于 Web 应用程序的控制器深处。
您可能需要进行一些(希望是微小的)更改,以公开一个可供您的适配器调用的函数,或者用通过环境变量提供的值替换硬编码的值。同样:需要进行的更改越少越好,但有些可能是不可避免的。
-
创建一个 CodeProject.AI Server 适配器,通过派生一个类来处理从 CodeProject.AI Server 收到的请求并返回响应。您将派生自一个抽象基类,只需提供一个处理请求的方法。所有样板化的服务器/模块通信和错误处理都已为您处理。
-
对项目的 Program.cs 文件进行少量更改,以配置程序运行上述代码。
-
测试。这可以通过使用 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
这将打开 **项目配置** 对话框
- 将 **项目名称** 设置为 **SentimentAnalysis**
- 将 **位置** 设置为您复制的 CodeProject.AI 解决方案中的 src\AnalysisLayer 目录。
- 点击**下一步**。
这将打开 **其他信息** 对话框
我们无需在此处进行任何更改,因此请点击 **创建**。这将创建项目,其结构如下:
创建 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>();
toservices.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 通常并不困难。您需要:
- 选择一个独立的模块,可以将其功能公开为方法调用。
- 在 AnalysisServices 文件夹中创建一个项目来存放您的项目,并复制代码
- 创建一个适配器,该适配器将在 CodeProject.AI Server 和您的代码之间进行接口
- 确保模型和依赖项已到位
- 创建一个 modulesettings.json 文件,向服务器描述如何启动模块
- 对您要添加的模块进行任何必要的微小更改,以使其能够与您的适配器一起工作,并修改 Program.cs 文件以启动所有工作
您的模块现在是 CodeProject.AI 生态系统的一部分,任何使用该服务器的客户端现在都可以无缝访问您的新模块。恭喜!