模型管理
本文将详细介绍 maxk-kb 系统中模型管理的实现,包括其接口设计、后端代码实现以及整体架构。通过这一介绍,您将了解如何通过 API 进行模型的添加、更新、查询和删除,以及后端如何处理这些请求。
目录
接口设计
tio-boot 提供了一系列 RESTful API,用于管理不同提供者的模型。这些接口涵盖了模型提供者的获取、模型类型和列表的查询、模型的增删改查等操作。以下将逐一介绍这些接口及其响应格式。
获取模型提供者列表
请求
GET http://192.168.3.8:3000/api/provider
响应
{
"code": 200,
"message": "成功",
"data": [
{
"provider": "model_azure_provider",
"name": "Azure OpenAI",
"icon": "<svg ...></svg>"
},
{
"provider": "model_wenxin_provider",
"name": "千帆大模型",
"icon": "<svg ...></svg>"
}
// 其他提供者
]
}
说明
该接口返回所有可用的模型提供者,包括提供者名称、标识符以及图标。
获取模型列表
根据不同的查询参数,模型列表的获取有所不同。
根据模型名称查询
请求
GET http://192.168.3.8:3000/api/model?name=<model_name>
响应
{ "message": null, "data": [], "code": 200 }
说明
通过指定模型名称查询模型,返回匹配的模型列表。
根据提供者查询
请求
GET http://192.168.3.8:3000/api/model?name=&provider=model_openai_provider
响应
{ "message": null, "data": [], "code": 200 }
说明
通过指定模型提供者查询模型,返回该提供者下的所有模型。
获取模型类型列表
请求
GET http://192.168.3.8:3000/api/provider/model_type_list?provider=model_openai_provider
响应
{
"message": "成功",
"data": [
{
"value": "LLM",
"key": "大语言模型"
},
{
"value": "EMBEDDING",
"key": "向量模型"
}
],
"code": 200
}
说明
该接口返回指定提供者支持的模型类型列表,如大语言模型(LLM)和向量模型(EMBEDDING)。
获取模型详情
请求
GET http://localhost:3000/api/model/442895624269676544
响应
{
"code": 200,
"message": "成功",
"data": {
"id": "111",
"provider": "model_openai_provider",
"name": "text-embbeding-3-large",
"model_type": "EMBEDDING",
"model_name": "text-embedding-ada-002",
"status": "SUCCESS",
"meta": {},
"credential": {
"api_base": "https://api.openai.com/v1",
"api_key": "sk-Mt7nR***************eaG9"
},
"permission_type": "PRIVATE"
}
}
说明
通过模型 ID 获取具体模型的详细信息,包括提供者、模型类型、名称、状态及凭证信息等。
添加模型
添加向量模型
请求
POST http://192.168.3.8:3000/api/model
请求体
{ "name": "text-embedding-3-large", "model_type": "EMBEDDING", "model_name": "text-embedding-3-large", "permission_type": "PRIVATE", "credential": { "api_base": "https://api.openai.com/v1", "api_key": "xxxx" }, "provider": "model_openai_provider" }
添加大语言模型
请求
POST http://localhost:3000/api/model
请求体
{ "name": "gpt-4o-mini", "model_type": "LLM", "model_name": "gpt-4o-mini", "permission_type": "PRIVATE", "credential": { "api_base": "https://api.openai.com/v1", "api_key": "sk-xxx" }, "provider": "model_openai_provider" }
说明
通过 POST
请求可以添加新的模型,需提供模型名称、类型、具体名称、权限类型、凭证信息及所属提供者。
更新模型
请求
PUT http://localhost:3000/api/model/442895624269676544
请求体
{
"name": "text-embedding-3-larg",
"permission_type": "PRIVATE",
"model_type": "EMBEDDING",
"model_name": "text-embedding-3-large",
"credential": {
"api_base": "https://api.openai.com",
"api_key": "sk-M*******************************************eaG9"
}
}
说明
通过 PUT
请求更新指定 ID 的模型信息,包括名称、类型、权限及凭证信息。
删除模型
请求
DELETE http://localhost:3000/api/model/442895624269676544
说明
通过 DELETE
请求删除指定 ID 的模型。
后端代码实现
maxk-kb 的后端采用 Java 语言的 tio-boot 框架实现,主要包括控制器、服务层和数据访问层(DAO)。以下将详细解析各部分代码及其功能。
ApiProviderController 控制器
package com.litongjava.maxkb.controller;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import com.jfinal.kit.Kv;
import com.litongjava.annotation.RequestPath;
import com.litongjava.maxkb.enumeration.ModelProvider;
import com.litongjava.model.result.ResultVo;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.utils.hutool.FileUtil;
import com.litongjava.tio.utils.hutool.ResourceUtil;
import lombok.extern.slf4j.Slf4j;
@RequestPath("/api/provider")
@Slf4j
public class ApiProviderController {
@RequestPath
public HttpResponse index() {
URL resource = ResourceUtil.getResource("json/api_provider.json");
StringBuilder jsonString = FileUtil.readURLAsString(resource);
HttpResponse response = TioRequestContext.getResponse();
response.setJson(jsonString.toString());
return response;
}
@RequestPath("/model_type_list")
public ResultVo model_type_list() {
List<Kv> kvs = new ArrayList<>();
kvs.add(Kv.by("key", "大语言模型").set("value", "LLM"));
kvs.add(Kv.by("key", "向量模型").set("value", "EMBEDDING"));
return ResultVo.ok("成功", kvs);
}
@RequestPath("/model_list")
public HttpResponse model_list(String provider, String model_type) {
model_type = model_type.toLowerCase();
String filename = provider + "_" + model_type + ".json";
log.info("filename:{}", filename);
URL resource = ResourceUtil.getResource("json/" + filename);
StringBuilder jsonString = FileUtil.readURLAsString(resource);
HttpResponse response = TioRequestContext.getResponse();
response.setJson(jsonString.toString());
return response;
}
public HttpResponse model_form(String provider, String model_type, String model_name) {
String filename = null;
if (ModelProvider.model_openai_provider.getName().equals(provider)) {
filename = provider + "_model_form.json";
}
log.info("filename:{}", filename);
URL resource = ResourceUtil.getResource("json/" + filename);
StringBuilder jsonString = FileUtil.readURLAsString(resource);
HttpResponse response = TioRequestContext.getResponse();
response.setJson(jsonString.toString());
return response;
}
}
功能说明
index
方法:处理/api/provider
的GET
请求,读取api_provider.json
文件,返回所有模型提供者的信息。model_type_list
方法:处理/api/provider/model_type_list
的GET
请求,返回指定提供者支持的模型类型列表。model_list
方法:处理/api/provider/model_list
的GET
请求,根据提供者和模型类型返回相应的模型列表。model_form
方法:根据提供者、模型类型和模型名称返回模型表单配置,用于前端动态生成表单。
ApiModelController 控制器
package com.litongjava.maxkb.controller;
import com.litongjava.annotation.Delete;
import com.litongjava.annotation.EnableCORS;
import com.litongjava.annotation.Get;
import com.litongjava.annotation.Post;
import com.litongjava.annotation.Put;
import com.litongjava.annotation.RequestPath;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.maxkb.service.ModelService;
import com.litongjava.maxkb.vo.ModelVo;
import com.litongjava.model.result.ResultVo;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.utils.json.FastJson2Utils;
@RequestPath("/api/model")
@EnableCORS
public class ApiModelController {
ModelService modelService = Aop.get(ModelService.class);
@Get
public ResultVo index(HttpRequest request) {
String name = request.getParam("name");
return modelService.list(name);
}
@Post
public ResultVo save(HttpRequest request) {
Long userId = TioRequestContext.getUserIdLong();
String bodyString = request.getBodyString();
ModelVo modelVo = FastJson2Utils.parse(bodyString, ModelVo.class);
return modelService.save(userId, modelVo);
}
@Put("/{id}")
public ResultVo update(HttpRequest request, Long id) {
Long userId = TioRequestContext.getUserIdLong();
String bodyString = request.getBodyString();
ModelVo modelVo = FastJson2Utils.parse(bodyString, ModelVo.class);
modelVo.setId(id);
return modelService.save(userId, modelVo);
}
@Delete("/{id}")
public ResultVo delete(Long id) {
return modelService.delete(id);
}
@Get("/{id}")
public ResultVo get(Long id) {
return modelService.get(id);
}
}
功能说明
index
方法:处理/api/model
的GET
请求,获取查询参数name
并调用服务层方法返回模型列表。save
方法:处理/api/model
的POST
请求,解析请求体中的模型信息,调用服务层保存模型。update
方法:处理/api/model/{id}
的PUT
请求,更新指定 ID 的模型信息。delete
方法:处理/api/model/{id}
的DELETE
请求,删除指定 ID 的模型。get
方法:处理/api/model/{id}
的GET
请求,获取指定 ID 的模型详情。
ModelService 服务层
package com.litongjava.maxkb.service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.postgresql.util.PGobject;
import com.jfinal.kit.Kv;
import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Record;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.kit.RecordUtils;
import com.litongjava.maxkb.constant.TableNames;
import com.litongjava.maxkb.dao.ModelDao;
import com.litongjava.maxkb.enumeration.ModelProvider;
import com.litongjava.maxkb.enumeration.ModelType;
import com.litongjava.maxkb.vo.CredentialVo;
import com.litongjava.maxkb.vo.ModelVo;
import com.litongjava.model.result.ResultVo;
import com.litongjava.openai.chat.ChatMessage;
import com.litongjava.openai.chat.OpenAiChatRequestVo;
import com.litongjava.openai.client.OpenAiClient;
import com.litongjava.openai.constants.OpenAiModels;
import com.litongjava.openai.embedding.EmbeddingRequestVo;
import com.litongjava.tio.utils.hutool.DataMaskingUtil;
import com.litongjava.tio.utils.json.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
@Slf4j
public class ModelService {
/**
* 获取模型列表
*
* @param name 模型名称
* @return ResultVo
*/
public ResultVo list(String name) {
String[] jsonFields = new String[] { "meta" };
String sql = null;
if (name == null) {
sql = "select id,provider,name,model_type,model_name,status,meta,permission_type,user_id from %s";
sql = String.format(sql, TableNames.max_kb_model);
List<Record> list = Db.findWithJsonField(sql, jsonFields);
List<Kv> kvs = new ArrayList<>();
for (Record r : list) {
Kv kv = r.toKv();
kv.set("id", kv.get("id").toString());
kv.set("user_id", kv.get("user_id").toString());
PGobject meta = kv.getAs("meta");
if (meta == null || meta.isNull()) {
kv.set("meta", "{}");
} else {
kv.set("meta", JsonUtils.parseObject(meta.getValue()));
}
kvs.add(kv);
}
return ResultVo.ok(kvs);
}
sql = "select id,provider,name,model_type,model_name,status,meta,permission_type,user_id from %s where name=?";
sql = String.format(sql, TableNames.max_kb_model);
List<Record> list = Db.findWithJsonField(sql, jsonFields, name);
List<Kv> recordsToKv = RecordUtils.recordsToKv(list, false);
return ResultVo.ok(recordsToKv);
}
/**
* 保存或更新模型
*
* @param userId 用户ID
* @param modelVo 模型信息
* @return ResultVo
*/
public ResultVo save(Long userId, ModelVo modelVo) {
String name = modelVo.getName();
log.info("name:{}", name);
if (Db.exists(TableNames.max_kb_model, "name", name)) {
return ResultVo.fail(400, "模型名称【" + name + "】已存在");
}
String model_type = modelVo.getModel_type();
String provider = modelVo.getProvider();
if (ModelProvider.model_openai_provider.getName().equals(provider)) {
if (ModelType.EMBEDDING.getName().equals(model_type)) {
EmbeddingRequestVo embeddingRequestVo = new EmbeddingRequestVo();
embeddingRequestVo.input("Hi").model(OpenAiModels.text_embedding_3_small);
String api_base = modelVo.getCredential().getApi_base();
String api_key = modelVo.getCredential().getApi_key();
String bodyString = JsonUtils.toJson(embeddingRequestVo);
// 发送请求
try (Response response = OpenAiClient.embeddings(api_base, api_key, bodyString)) {
if (!response.isSuccessful()) {
// 获取响应内容
String string = response.body().string();
return ResultVo.fail(400, "校验失败,请检查参数是否正确:" + string);
}
} catch (IOException e) {
e.printStackTrace();
return ResultVo.fail(500, e.getMessage());
}
} else {
if (ModelType.LLM.getName().equals(model_type)) {
String api_base = modelVo.getCredential().getApi_base();
String api_key = modelVo.getCredential().getApi_key();
// 消息
List<ChatMessage> messages = new ArrayList<>();
ChatMessage message = new ChatMessage().role("user").content("hi");
messages.add(message);
OpenAiChatRequestVo openAiChatRequestVo = new OpenAiChatRequestVo();
openAiChatRequestVo.setStream(false);
openAiChatRequestVo.setModel(OpenAiModels.gpt_4o_mini);
openAiChatRequestVo.setMessages(messages);
String bodyString = JsonUtils.toJson(openAiChatRequestVo);
// 发送请求
try (Response response = OpenAiClient.chatCompletions(api_base, api_key, bodyString)) {
if (!response.isSuccessful()) {
// 获取响应内容
String string = response.body().string();
return ResultVo.fail(500, "校验失败,请检查参数是否正确:" + string);
}
} catch (IOException e) {
e.printStackTrace();
return ResultVo.fail(500, e.getMessage());
}
}
}
}
Aop.get(ModelDao.class).saveOrUpdate(userId, modelVo);
return ResultVo.ok();
}
/**
* 删除模型
*
* @param id 模型ID
* @return ResultVo
*/
public ResultVo delete(Long id) {
boolean ok = Aop.get(ModelDao.class).deleteById(id);
if (ok) {
return ResultVo.ok();
} else {
return ResultVo.fail();
}
}
/**
* 获取模型详情
*
* @param id 模型ID
* @return ResultVo
*/
public ResultVo get(Long id) {
Record record = Db.findById(TableNames.max_kb_model, id);
Object credential = record.getColumns().remove("credential");
Kv kv = record.toKv();
if (credential instanceof String) {
String credentialStr = (String) credential;
CredentialVo credentialVo = JsonUtils.parse(credentialStr, CredentialVo.class);
String api_key = credentialVo.getApi_key();
credentialVo.setApi_key(DataMaskingUtil.maskApiKey(api_key));
kv.set("credential", credentialVo);
}
return ResultVo.ok(kv);
}
}
功能说明
list
方法:根据模型名称查询模型列表。如果未提供名称,则返回所有模型。查询结果包括模型的基本信息,如 ID、提供者、类型、名称等。save
方法:保存或更新模型信息。在保存前,先验证模型名称是否已存在。对于 OpenAI 提供者的模型,根据模型类型(LLM 或 EMBEDDING)进行不同的校验,通过向 OpenAI 发送测试请求验证凭证的有效性。如果验证通过,则调用 DAO 层保存或更新模型信息。delete
方法:删除指定 ID 的模型。get
方法:获取指定 ID 的模型详情,返回模型的所有信息,并对敏感信息(如 API Key)进行脱敏处理。
ModelDao 数据访问层
package com.litongjava.maxkb.dao;
import java.util.Map;
import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Record;
import com.litongjava.maxkb.constant.TableNames;
import com.litongjava.maxkb.vo.ModelVo;
import com.litongjava.tio.utils.snowflake.SnowflakeIdUtils;
public class ModelDao {
/**
* 保存模型
*
* @param map 模型信息
*/
public void save(Map<String, Object> map) {
Db.save(TableNames.max_kb_model, Record.fromMap(map));
}
/**
* 根据ID删除模型
*
* @param id 模型ID
* @return 是否成功
*/
public boolean deleteById(Long id) {
return Db.deleteById(TableNames.max_kb_model, id);
}
/**
* 保存或更新模型
*
* @param userId 用户ID
* @param modelVo 模型信息
* @return 是否成功
*/
public boolean saveOrUpdate(Long userId, ModelVo modelVo) {
Record record = new Record();
record.set("name", modelVo.getName())
.set("model_type", modelVo.getModel_type())
.set("model_name", modelVo.getModel_name())
.set("permission_type", modelVo.getPermission_type())
.set("credential", modelVo.getCredential())
.set("user_id", userId)
.set("status", "SUCCESS");
String provider = modelVo.getProvider();
if (provider != null) {
record.set("provider", provider);
}
Long id = modelVo.getId();
if (id != null) {
record.set("id", id);
return Db.update(TableNames.max_kb_model, "id", record, new String[] { "credential" });
} else {
record.set("id", SnowflakeIdUtils.id());
return Db.save(TableNames.max_kb_model, record, new String[] { "credential" });
}
}
}
功能说明
save
方法:保存新的模型记录。deleteById
方法:根据模型 ID 删除模型记录。saveOrUpdate
方法:根据是否存在 ID 决定是保存新模型还是更新现有模型。生成唯一 ID 使用 Snowflake 算法。
总结
通过本文的介绍,我们详细了解了 maxk-kb 系统中模型管理的实现,包括 API 接口设计和后端代码实现。系统通过清晰的 RESTful API 提供了模型的增删改查功能,后端采用分层架构,控制器负责处理请求,服务层处理业务逻辑,数据访问层负责与数据库交互。同时,通过与 OpenAI 等模型提供者的集成,确保了模型凭证的有效性和安全性。
这种模块化和层次化的设计不仅提高了系统的可维护性和扩展性,也为未来集成更多模型提供者和类型打下了坚实的基础。对于需要管理多种机器学习模型的系统,maxk-kb 的实现提供了一个优秀的参考范例。