目录
modulesettings.development.json
介绍
本文将介绍如何通过调整现有.NET代码并添加适配器来为 CodeProject.AI Server 创建模块。这将使原始代码的功能能够由CodeProject.AI Server作为HTTP端点公开。该过程类似于将自己的Python模块添加到CodeProject.AI,只不过我们将使用.NET而不是Python。
在开始之前,可能值得确保您已阅读向CodeProject.AI添加新模块。
选择模块
您可以从头开始编写自己的模块,也可以使用当前可用的大量开源AI项目之一。有数千个,对于本文,我选择了 DotNet 示例存储库中的 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 CodeProject.AI Server中为该模块创建的队列中的命令。最简单的方法是在src/AnalysisLayer下的文件夹中创建一个Worker Service项目。CodeProject.AI Server扫描此文件夹中的目录中的模块元数据,从而允许服务器启动模块。
执行此操作的步骤如下:
- 右键单击“解决方案资源管理器”中的“src/AnalyisLayer”文件夹
- 选择“添加”-“>新项目”
- 选择适用于C#的“辅助角色服务”项目模板
- 单击“下一步”
这将打开“项目配置”对话框:
- 将“项目名称”设置为“SentimentAnalysis”
- 将“位置”设置为CodeProject.AI解决方案副本中的src\AnalysisLayer目录。
- 单击“下一步”。
这将打开“其他信息”对话框:
我们不需要在此处进行任何更改,因此请单击“创建”。这将创建具有以下结构的项目:
创建modulesettings.json文件
modulesettings.json文件为
- 是否应该启动
- 如何开始
- 它在什么平台上运行
- CodeProject.AI Server将为此模块公开的端点。在这种情况下,我们将
- 公开 http://localhost:5000/v1/text/sentiment
- 使用HTTP POST方法
- 发送一个表单变量text,该变量将包含要分析的文本
- 并期望JSON有效负载响应
- 一个Boolean success属性,该属性指示操作是否已成功完成
- 一个Boolean 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文件值。在这种情况下,可执行文件的位置将位于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>();
为
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成为前端项目的生成依赖项,以便在生成 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('http://localhost: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>
若要查看此操作,请在调试器的调试配置中运行前端项目(CodeProject.AI服务器),然后在所选的浏览器中打开test.html文件。将一些文本复制到文本框中,然后按提交。我使用了亚马逊评论中的文字。您应该看到与此类似的内容:
附加奖励——在Windows上部署XCOPY模块
NET 6模块不像Python模块那样依赖于任何运行时的安装或虚拟环境的设置。NET 6运行时已由CodeProject.AI Server安装程序安装。因此,可以将内部版本的发布版本分箱部署到CodeProject.AI Server的现有Windows安装中。执行此操作的步骤如下:
- 在发布模式下生成模块项目。
- 在c:\Program Files\CodeProject\AI\AnalysisLayer目录中创建一个文件夹。此目录的名称应与modulesettings.json文件中的FilePath目录同名。对于此示例,这将是“SentimentAnalysis”。
- 将项目的bin\Release\net6.0目录的内容复制到在上一步中创建的目录。
- 使用“服务”应用重新启动CodeProject.AI Server服务。现在,新模块将在modulesettings.json文件中定义的终结点上公开。
总结
向CodeProject.AI Server添加新模块通常并不困难。你需要
- 选择一个自包含的模块,该模块可以作为方法调用公开功能。
- 在AnalysisServices文件夹中创建一个项目来存放您的项目,然后复制代码
- 创建一个适配器,该适配器将在CodeProject.AI服务器和代码之间建立接口
- 确保模型和依赖项就位
- 创建一个modulesettings.json文件,向服务器描述如何启动模块
- 对要添加的模块进行任何需要的微小更改,以允许它与适配器一起运行,并修改Program.cs文件以使事情顺利进行
您的模块现在是CodeProject.AI生态系统的一部分,任何使用该服务器的客户端现在都可以无缝访问您的新模块。
https://www.codeproject.com/Articles/5334462/Adding-a-NET-AI-Module-to-CodeProject-AI-Server