DeepSeek私域数据训练之封装Anything LLM的API 【net 9】

360影视 欧美动漫 2025-06-05 10:01 2

摘要:书接上回,自从重金购买了学习AI的设备后,总得找点事情干! 这不,就有个小需求,需要训练私域数据,那么我们就从Anything LLM的API封装做起。在当今快速发展的人工智能领域,大型语言模型(LLM)已成为企业应用的重要组成部分。Anything LLM作

书接上回,自从重金购买了学习AI的设备后,总得找点事情干! 这不,就有个小需求,需要训练私域数据,那么我们就从Anything LLM的API封装做起。在当今快速发展的人工智能领域,大型语言模型(LLM)已成为企业应用的重要组成部分。Anything LLM作为一款功能强大的开源LLM管理工具,提供了丰富的API接口,使开发者能够轻松集成AI能力到自己的应用中。本文将详细介绍如何使用最新的.NET 9框架对Anything LLM API进行封装,构建一个高效、可靠的SDK,以及避坑指南。

Anything LLM是一个全功能的LLM管理平台,它允许开发者:

管理和部署多种大型语言模型;

创建和管理知识库 ;

构建对话式AI应用;

实现文档检索和问答系统

我们这里就以其为训练的基础,开启.net 9编写LLM接口的辉煌。

其分为:

授权

管理接口

文档接口,重要的资料,私域数据等等

工作空间,可以按使用用户隔离出来的私有空间

系统配置,配置参数等操作

空间线程,类似于可以在一个空间启动多个聊天窗口

用户管理

兼容OpenAI的接口

嵌入式文档接口,同文档接口,也是兼容接口之一。

2.1 核心类结构publicclassAnythingLLMClient
{
// HTTP客户端和配置
privatereadonlyHttpClient _HttpClient;
privatereadonlyJSONSerializerOptions _jsonSerializerOptions;
privatereadonlyJsonSerializerOptions _jsonDeserializerOptions;
privatereadonlyILogger_logger;

// 服务模块,暂时实现这么多,后续可以扩展实现
publicAuthenticationService Authentication {get;}
publicAdminService Admin {get;}
publicDocumentsService Documents {get;}
publicWorkspacesService Workspaces {get;}
}
2.2 模块化设计

这里将API功能划分为几个服务模块:

AuthenticationService:处理认证相关操作

AdminService:管理系统设置和配置

DocumentsService:管理文档和知识库

WorkspacesService:管理工作空间和对话

按照这种模块化设计提高了代码的可维护性和可扩展性,使用起来手感也略有提升。

3.1 初始化配置publicAnythingLLMClient(string baseUrl,string apiKey,ILoggerlogger,HttpClient httpClient =)
{
this._logger = logger;
_httpClient = httpClient ??newHttpClient;
_httpClient.BaseAddress =newUri(baseUrl);
_httpClient.DefaultRequestHeaders.Authorization =
newAuthenticationHeaderValue("Bearer", apiKey);

// 配置语言首选项
_httpClient.DefaultRequestHeaders.AcceptLanguage.Clear;
_httpClient.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-GB,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,en-US;q=0.6");

// JSON序列化配置
_jsonSerializerOptions =newJsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive =false
WriteIndented =false
};

_jsonDeserializerOptions =newJsonSerializerOptions
{

false
WriteIndented =false
};

// 初始化服务模块
Authentication =newAuthenticationService(this);
Admin =newAdminService(this);
Documents =newDocumentsService(this);
Workspaces =newWorkspacesService(this);

// 注册编码提供程序
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}
3.2 HTTP请求处理

我们实现了四种基本的HTTP方法:

GetAsync(string endpoint)
{
var response =await _httpClient.GetAsync(endpoint);
returnawaitHandleResponse(response);
}

internalasyncTaskPostAsync(string endpoint,object data =)
{
var content = data !=
? JsonContent.Create(data,options: _jsonSerializerOptions)
:;
var response =await _httpClient.PostAsync(endpoint, content);
returnawaitHandleResponse(string endpoint,MultipartFormDataContent content)
{
var response =await _httpClient.PostAsync(endpoint, content);
returnawaitHandleResponseDeleteAsync(string endpoint)
{
var response =await _httpClient.DeleteAsync(endpoint);
returnawaitHandleResponse(response);
}
3.3 响应处理

统一的响应处理机制确保了错误的一致性和可追踪性:

HandleResponse(HttpResponseMessage response)
{
if(!response.IsSuccessStatusCode)
{
var error =await response.Content.ReadAsStringAsync;
this._logger.LogError($"LLM: 访问 {response.RequestMessage.Tostring}失败 {response.StatusCode}, {error}");
thrownewAnythingLLMApiException(
$"API request failed: {response.StatusCode} - {error}",
(int)response.StatusCode);
}

var info =await response.Content.ReadAsStringAsync;
this._logger.LogInformation($"LLM: 访问 {response.RequestMessage.ToString},返回信息 {response.StatusCode}, {info}");
return JsonSerializer.Deserialize(info, _jsonDeserializerOptions);
}
publicclassAuthenticationService
{
privatereadonlyAnythingLLMClient _client;

publicAuthenticationService(AnythingLLMClient client)
{
_client = client;
}

publicasyncTaskLogin(string username,string password)
{
var request =new{ username, password };
returnawait _client.PostAsync("api/auth/login", request);
}

publicasyncTaskLogout
{
returnawait _client.DeleteAsync("api/auth/logout");
}
}
4.2 文档服务(DocumentsService)publicclassDocumentsService
{
privatereadonlyAnythingLLMClient _client;

publicDocumentsService(AnythingLLMClient client)
{
_client = client;
}

publicasyncTaskUploadDocument(string filePath,string workspaceId)
{
if(!File.Exists(filePath))
thrownewFileNotFoundException("文件不存在", filePath);

usingvar content =newMultipartFormDataContent;
usingvar fileStream = File.OpenRead(filePath);
usingvar streamContent =newStreamContent(fileStream);

string fileName = Path.GetFileName(filePath);
var contentDisposition =newContentDispositionHeaderValue("form-data")
{
Name ="\"file\"",
FileName ="\""+ fileName +"\"",
FileNameStar = fileName
};
streamContent.Headers.ContentDisposition = contentDisposition;

content.Add(streamContent,"file");
content.Add(newStringContent(workspaceId),"workspaceId");

returnawait _client.PostMultipartAsync("api/documents", content);
}

publicasyncTask>GetDocuments(string workspaceId)
{
returnawait _client.GetAsync>($"api/documents?workspaceId={workspaceId}");
}
}
尝试了多种编码,Anything LLM的API就是不给力,一直返回错误Invalid file upload. NOENT: no such file or directory, open 'C:\Users\Administrator\AppData\Roaming\anythingllm-desktop\storage\hotdir\???\‘,经过仔细查阅github,发现这个问题在早期版本已经解决,但是.net就是无法正常传递中文文件名。

经过查阅源码,发现如下代码:

filename:function(req, file, cb){
file.originalname = Buffer.from(file.originalname,"latin1").toString(
"utf8"
);

// Set origin for watching
if(
req.headers.hasOwnProperty("x-file-origin")&&
typeof req.headers["x-file-origin"]==="string"
)
file.localPath =decodeURI(req.headers["x-file-origin"]);

cb(, file.originalname);
},

针对中文文件名问题,可以看到Anything LLM官方的处理方法如下:

Buffer.from(file.originalname, "latin1").toString("utf8")

作用:将上传文件名从 Latin1(ISO-8859-1)编码转换成 UTF-8。

原因:部分浏览器或客户端上传中文文件名时编码成 Latin1,在服务器上会显示乱码。

req.headers["x-file-origin"]

这是一个自定义的 HTTP 请求头(如你前端上传时手动加的),用于携带文件的来源路径(或原始目录等信息)。

file.localPath = decodeURI(...):把这个头的值(可能包含中文路径)解码回来,存入 file.localPath 字段供后续使用。

cb(, file.originalname)cb 是 Multer 内部用来异步设置文件名的回调函数。

有了以上服务器的处理方式,那么我们就可以针对这个方式进行编码了。

由于 .NET 会自动将 FileName 的值编码为符合 MIME 标准的 ASCII-safe 字符串,并且如果包含非 ASCII 字符(如中文),将被编码成 MIME encoded-word 格式; 例如:

filename="=?UTF-8?B?5paH5Lu2LnR4dA==?=" 总体而言,这是一种 Base64 编码的 UTF-8 字符串,用来避免非 ASCII 直接放在 HTTP 头中;但是,但是,许多后端框架(如 Node.js 的 Multer)不会解析这种 MIME encoded-word 格式,导致 file.originalname 变成乱码。

因此,我们进行手工编码,如下:

usingMultipartFormDataContent content =newMultipartFormDataContent;

var addToWorkspacesContent =newByteArrayContent(
Encoding.UTF8.GetBytes(request.AddToWorkspaces ??"")// 关键点:使用ASCII编码避免UTF-8标记
);
addToWorkspacesContent.Headers.Remove("Content-Type");;// 不设置Content-Type,避免影响多部分表单数据的处理
content.Add(addToWorkspacesContent,"addToWorkspaces");
usingvar fileStream =newFileStream(request.FilePath, FileMode.Open, FileAccess.Read);
usingvar streamContent =newStreamContent(fileStream);
streamContent.Headers.ContentType =newMediaTypeHeaderValue(GetMimeType(request.FilePath));
var fileName = Path.GetFileName(request.FilePath);

// 先将文件名编码成 UTF-8 字节
var utf8Bytes = Encoding.UTF8.GetBytes(fileName);

// 再将 UTF-8 字节强制“解读”为 Latin1 字符串(每个字节变成 Latin1 字符)
var latin1String = Encoding.GetEncoding("ISO-8859-1").GetString(utf8Bytes);

var contentDisposition =$"form-data; name=\"file\"; filename=\"{latin1String}\"";
streamContent.Headers.TryAddWithoutValidation("Content-Disposition", contentDisposition);
content.Add(streamContent);
var encodedOriginPath = Uri.EscapeUriString(request.FilePath);// 对路径进行 URI 编码
content.Headers.Add("x-file-origin", encodedOriginPath);
returnawait _client.PostMultipartAsync(
BuildEndpoint("v1","document","upload"), content);

这样处理后,终于可以愉快的支持中文了。

[Fact]
publicasyncTaskGetDocuments_ShouldReturnDocuments
{
// 准备
var mockHttp =newMock;
var expectedDocuments =newList{newDocument{ Id ="1", Name ="Test"}};

mockHttp.Protected
.Setup>(
"SendAsync",
ItExpr.IsAny,
ItExpr.IsAny)
.ReturnsAsync(newHttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content =newStringContent(JsonSerializer.Serialize(expectedDocuments))
});

var client =newHttpClient(mockHttp.Object);
var llmClient =newAnythingLLMClient("http://test.com","api-key", Mock.Of>, client);

// 执行
var result =await llmClient.Documents.GetDocuments("workspace1");

// 断言
Assert.Single(result);
Assert.Equal("Test", result[0 ].Name);
}

有压力就有动力,一旦出手了,就得努力进行学习和沉淀。

目前这个SDK提供了对Anything LLM API的完整封装,后续就可以利用该SDK进行二次开发,充分利用大型语言模型的强大能力。

—— 风已起,你是否准备好迎接这场AI革命? 🌪️🚀

你学废了吗?

👓都收藏了,还在乎一个评论吗?

来源:opendotnet

相关推荐