本文介绍基于ASP.NET Core
的Chatbot Restful API
开发,通过调用大语言模型的SDK,完成一个简单的示例。并且通过容器化进行部署.
安装
首先需要安装.NET环境,笔者在Ubuntu 22.04通过二进制包进行安装,Windows和Mac下都有installer可以用. 选择的版本是.NET 8
.
技术栈
我们分析一下要用到的技术栈,想要达到一个基本的demo需求,需要实现API,以及数据的查询存储,以及快速搭建运行环境.
因此我们选择postgresql
存储数据,docker
容器化实现快速部署. LLM
服务选用了OpenAI
.
Solution
首先创建一个solution,可以使用如下命令,或者使用IDE.
dotnet new sln
然后新建webapi工程并添加到solution中
mkdir WebApi
cd WebApi
dotnet new webapi
cd ..
dotnet sln add WebApi
完成创建后可以尝试restore并且简单build一下确认环境是否正常
dotnet restore
dotnet build
创建控制器
新建的webapi
工程默认给了一个Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
这个代码模板已经实现了一个简单的API,但不是我们需要的基于controller
的模式,为了使用controller,我们还要安装一些依赖包.
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet tool uninstall -g dotnet-aspnet-codegenerator
dotnet tool install -g dotnet-aspnet-codegenerator
dotnet tool update -g dotnet-aspnet-codegenerator
在Microsoft官方tutorial中给出的是:
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet tool uninstall -g dotnet-aspnet-codegenerator
dotnet tool install -g dotnet-aspnet-codegenerator
dotnet tool update -g dotnet-aspnet-codegenerator
因为我们不使用EntityFrameworkCore
,所以去掉了关于EntityFrameworkCore
的引用.
然后是创建controller,例如我们命名为ChatController
:
dotnet aspnet-codegenerator controller -name ChatController -api -outDir Controllers
创建后,我们可以得到ChatController.cs
文件,稍作修改如下
using Microsoft.AspNetCore.Mvc;
namespace WebApi.Controllers;
[Route("api/[controller]")]
[ApiController]
public class ChatController(IChatService chatService) : ControllerBase
{
[HttpPost]
[Route("[action]")]
public ActionResult<ChatResponse> Completions([FromBody] ChatRequest request)
{
var input = request.Text;
var result = chatService.Complete(request);
return Ok(result);
}
}
这时,如果我们直接运行工程,会发现在swagger的页面中,找不到刚才添加的Chat endpoint,因为还需要一些配置
在Program.cs
中,我们需要让服务映射我们新增的controller.
builder.Services.AddControllers();
app.MapControllers();
上面是两行需要增加的代码,使整个Program.cs
看起来这样
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapControllers();
//entry point
app.Run();
这时,再运行就会发现swagger中已经有相关的endpoint。
OpenAI SDK
使用OpenAI SDK
非常简单,直接安装依赖包
dotnet add package OpenAI-DotNet --version 8.3.0
准备好一个Open AI的key,配置到环境变量中,创建一个Factory获取ChatClient
(这是和OpenAI交互的客户端). 笔者使用的模型是gpt-3.5-turbo
,单纯是实惠,lol.
public abstract class OpenAiClientFactory
{
public static ChatClient Create()
{
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
?? throw new InvalidOperationException("OpenAI API key not set");
return new ChatClient("gpt-3.5-turbo", apiKey);
}
}
有了ChatClient
后,就可以进行交互了. 这里涉及到的知识有AI对话点角色问题,涉及到3个角色:
- system
- assistant
- user
相关的概念可以查看文档.
进行一次AI对话就很方便了,直接调用complete方法即可
var prompt = $"""
You are an NLU expert help to do intent recognition, slot filling work. Now you are going to understand user input then give a proper result.
""";
var systemChatMessage = new SystemChatMessage(prompt);
var userChatMessage = new UserChatMessage(input);
var response = chatClient.CompleteChat([systemChatMessage, userChatMessage]);
这是一个模板代码,可以按照自己的需求修改.
容器化
为了快速运行,可以在项目中增加一个Dockrfile
FROM mcr.microsoft.com/dotnet/sdk:8.0
EXPOSE 5114
COPY . /app
WORKDIR /app
RUN dotnet restore && dotnet build
CMD [ "dotnet","run","--project","./WebApi" ]
通过docker快速打包后就能在所有支持docker的平台上快速演示demo.
在容器化方面,我们还可以使用docker compose来把数据库也和主程序一起启动。
Docker Compose 是 Docker 官方编排工具,用于定义和运行多容器 Docker 应用程序。使用 Docker Compose,你可以通过一个 YAML 文件来配置你的应用服务,然后使用一个简单的命令来启动和停止所有服务。
Docker Compose 的主要特点包括:
-
多容器编排:可以定义多个容器服务,并管理它们之间的依赖关系。
-
服务隔离:每个服务运行在独立的容器中,可以单独配置环境变量、卷挂载、网络等。
-
一键部署:通过
docker-compose up
命令,可以一次性启动配置文件中定义的所有服务。 -
版本控制:Docker Compose 文件(通常是
docker-compose.yml
)可以被版本控制系统跟踪,方便团队协作和持续集成。 -
环境一致性:在开发、测试和生产环境中使用相同的配置文件,确保环境一致性。
-
网络管理:可以定义服务之间的网络连接,使得服务之间可以通过服务名进行通信。
-
数据卷管理:可以定义数据卷,实现数据的持久化和共享。
-
扩展性:支持通过环境变量和扩展文件来扩展配置。
-
命令行工具:提供了丰富的命令行工具,用于管理服务的生命周期,如启动、停止、重建、日志查看等。
一个基本的 docker-compose.yml
文件示例如下:
version: '3'
services:
web:
image: "nginx:latest"
ports:
- "80:80"
volumes:
- "/var/www:/usr/share/nginx/html"
depends_on:
- db
db:
image: "postgres:latest"
environment:
POSTGRES_DB: "mydb"
POSTGRES_USER: "user"
POSTGRES_PASSWORD: "password"
在我们的例子中,还需要一个功能,就是在docker启动的时候,需要执行一段sql脚本初始化数据库,这个需求postgresql的image已经考虑到,只需要把要执行的脚本映射到一个特殊的路径即可。映射后,在container启动的时候,会执行sql.
- ./db.sql:/docker-entrypoint-initdb.d/init.sql
那么最终我们的docker-compose文件可能是这样:
version: '3'
services:
web:
build:
context: .
dockerfile: Dockerfile
environment:
- ConnectionStrings__DefaultConnection=Host=db;Port=5432;Database=db;Username=postgres;
- ASPNETCORE_ENVIRONMENT=Development
- OPENAI_API_KEY=sk-1234567890abcdef1234567890abcdef # REPLACE WITH YOUR OPENAI KEY
ports:
- "5000:5000"
depends_on:
- db
db:
image: bitnami/postgresql:latest
container_name: pg-container
restart: always
environment:
POSTGRES_USER: postgres
ALLOW_EMPTY_PASSWORD: yes
POSTGRES_DB: db
volumes:
- ./db.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
总结
使用ASP.NET Core
开发restful api相当方便,而借助容器的强大功能,部署到云环境进行快速验证也得以实现.