mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-20 20:39:55 +00:00
feat(ai): 新增智能助手功能模块
feat(backend): 添加ChromaDB向量数据库支持 feat(backend): 实现智能体配置、知识库和文档管理 feat(backend): 重构WebSocket聊天服务为AgentService feat(frontend): 实现完整的聊天界面组件 feat(frontend): 添加智能体配置、知识库和文档管理页面 fix(user): 修复用户导入时性别转换问题 style(import): 优化导入组件加载状态处理
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from langchain_chroma import Chroma
|
||||
from langchain_core.documents import Document
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
from app.config.setting import settings
|
||||
|
||||
|
||||
class DummyEmbeddings(Embeddings):
|
||||
"""虚拟嵌入类,用于在没有嵌入模型时使用"""
|
||||
|
||||
def embed_documents(self, texts: list[str]) -> list[list[float]]:
|
||||
return [[0.0] * 1536 for _ in texts]
|
||||
|
||||
def embed_query(self, text: str) -> list[float]:
|
||||
return [0.0] * 1536
|
||||
|
||||
|
||||
class ChromaDBManager:
|
||||
"""ChromaDB 管理类 - 使用 langchain-chroma"""
|
||||
|
||||
_instance = None
|
||||
_vectorstore = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
if self._vectorstore is None:
|
||||
self._initialize_vectorstore()
|
||||
|
||||
def _initialize_vectorstore(self):
|
||||
"""初始化 ChromaDB 向量存储"""
|
||||
os.makedirs(settings.CHROMA_PERSIST_DIR, exist_ok=True)
|
||||
self._vectorstore = Chroma(
|
||||
collection_name=settings.CHROMA_COLLECTION_NAME,
|
||||
persist_directory=settings.CHROMA_PERSIST_DIR,
|
||||
embedding_function=DummyEmbeddings(),
|
||||
)
|
||||
|
||||
def get_vectorstore(self) -> Chroma:
|
||||
"""获取向量存储实例"""
|
||||
if self._vectorstore is None:
|
||||
self._initialize_vectorstore()
|
||||
assert self._vectorstore is not None
|
||||
return self._vectorstore
|
||||
|
||||
def add_documents(
|
||||
self,
|
||||
ids: list[str],
|
||||
embeddings: list[list[float]],
|
||||
documents: list[str],
|
||||
metadatas: list[dict[str, Any]] | None = None,
|
||||
):
|
||||
"""添加文档到 ChromaDB"""
|
||||
vectorstore = self.get_vectorstore()
|
||||
|
||||
docs = [
|
||||
Document(page_content=doc, metadata=meta if meta else {})
|
||||
for doc, meta in zip(documents, metadatas or [{}] * len(documents), strict=False)
|
||||
]
|
||||
|
||||
vectorstore.add_documents(
|
||||
documents=docs,
|
||||
ids=ids,
|
||||
embeddings=embeddings,
|
||||
)
|
||||
|
||||
def query_documents(
|
||||
self,
|
||||
query_embeddings: list[list[float]],
|
||||
n_results: int = 5,
|
||||
where: dict[str, Any] | None = None,
|
||||
where_document: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""查询文档"""
|
||||
vectorstore = self.get_vectorstore()
|
||||
|
||||
results = vectorstore.similarity_search_by_vector(
|
||||
embedding=query_embeddings[0],
|
||||
k=n_results,
|
||||
filter=where,
|
||||
)
|
||||
|
||||
return {
|
||||
"documents": [[doc.page_content for doc in results]],
|
||||
"metadatas": [[doc.metadata for doc in results]],
|
||||
}
|
||||
|
||||
def delete_documents(
|
||||
self,
|
||||
ids: list[str],
|
||||
):
|
||||
"""删除文档"""
|
||||
vectorstore = self.get_vectorstore()
|
||||
vectorstore.delete(ids=ids)
|
||||
|
||||
def get_documents(
|
||||
self,
|
||||
ids: list[str] | None = None,
|
||||
where: dict[str, Any] | None = None,
|
||||
limit: int | None = None,
|
||||
offset: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""获取文档"""
|
||||
vectorstore = self.get_vectorstore()
|
||||
|
||||
if ids:
|
||||
results = vectorstore.get_by_ids(ids)
|
||||
else:
|
||||
results = []
|
||||
|
||||
return {
|
||||
"documents": [[doc.page_content for doc in results]] if results else [[]],
|
||||
"metadatas": [[doc.metadata for doc in results]] if results else [[]],
|
||||
}
|
||||
|
||||
def update_documents(
|
||||
self,
|
||||
ids: list[str],
|
||||
embeddings: list[list[float]] | None = None,
|
||||
documents: list[str] | None = None,
|
||||
metadatas: list[dict[str, Any]] | None = None,
|
||||
):
|
||||
"""更新文档 - 通过删除旧文档再添加新文档的方式实现"""
|
||||
vectorstore = self.get_vectorstore()
|
||||
|
||||
if documents or metadatas:
|
||||
vectorstore.delete(ids=ids)
|
||||
|
||||
if documents:
|
||||
docs = [
|
||||
Document(page_content=doc, metadata=meta if meta else {})
|
||||
for doc, meta in zip(documents, metadatas or [{}] * len(documents), strict=False)
|
||||
]
|
||||
vectorstore.add_documents(
|
||||
documents=docs,
|
||||
ids=ids,
|
||||
embeddings=embeddings,
|
||||
)
|
||||
|
||||
def reset(self):
|
||||
"""重置 ChromaDB"""
|
||||
if self._vectorstore is not None:
|
||||
self._vectorstore.delete_collection()
|
||||
self._vectorstore = None
|
||||
|
||||
|
||||
chroma_manager = ChromaDBManager()
|
||||
@@ -1,7 +1,6 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, Path
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.common.request import PaginationService
|
||||
@@ -12,26 +11,190 @@ from app.core.logger import log
|
||||
from app.core.router_class import OperationLogRoute
|
||||
|
||||
from .schema import (
|
||||
AgentConfigCreateSchema,
|
||||
AgentConfigOutSchema,
|
||||
AgentConfigQueryParam,
|
||||
AgentConfigUpdateSchema,
|
||||
ChatQuerySchema,
|
||||
McpCreateSchema,
|
||||
McpOutSchema,
|
||||
McpQueryParam,
|
||||
McpUpdateSchema,
|
||||
KnowledgeCreateSchema,
|
||||
KnowledgeDocumentCreateSchema,
|
||||
KnowledgeDocumentOutSchema,
|
||||
KnowledgeDocumentQueryParam,
|
||||
KnowledgeDocumentUpdateSchema,
|
||||
KnowledgeOutSchema,
|
||||
KnowledgeQueryParam,
|
||||
KnowledgeUpdateSchema,
|
||||
)
|
||||
from .service import McpService
|
||||
from .service import AgentConfigService, AgentService, KnowledgeService
|
||||
|
||||
AIRouter = APIRouter(route_class=OperationLogRoute, prefix="/ai", tags=["MCP智能助手"])
|
||||
AIRouter = APIRouter(route_class=OperationLogRoute, prefix="/ai", tags=["智能助手"])
|
||||
|
||||
|
||||
@AIRouter.get(
|
||||
"/agent-config/detail/{id}",
|
||||
summary="获取智能体配置详情",
|
||||
description="获取智能体配置详情",
|
||||
response_model=ResponseSchema[AgentConfigOutSchema],
|
||||
)
|
||||
async def agent_config_detail_controller(
|
||||
id: Annotated[int, Path(description="智能体配置ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:agent-config:detail"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
获取智能体配置详情接口
|
||||
|
||||
参数:
|
||||
- id (int): 智能体配置ID
|
||||
|
||||
返回:
|
||||
- SuccessResponse: 包含智能体配置详情的响应
|
||||
"""
|
||||
result_dict = await AgentConfigService.get_by_id_service(auth=auth, id=id)
|
||||
log.info(f"获取智能体配置详情成功 {id}")
|
||||
return SuccessResponse(data=result_dict, msg="获取智能体配置详情成功")
|
||||
|
||||
|
||||
@AIRouter.get(
|
||||
"/agent-config/default",
|
||||
summary="获取默认智能体配置",
|
||||
description="获取默认智能体配置",
|
||||
response_model=ResponseSchema[AgentConfigOutSchema],
|
||||
)
|
||||
async def agent_config_default_controller(
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:agent-config:query"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
获取默认智能体配置接口
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- SuccessResponse: 包含默认智能体配置的响应
|
||||
"""
|
||||
result_dict = await AgentConfigService.get_default_service(auth=auth)
|
||||
log.info("获取默认智能体配置成功")
|
||||
return SuccessResponse(data=result_dict, msg="获取默认智能体配置成功")
|
||||
|
||||
|
||||
@AIRouter.get(
|
||||
"/agent-config/list",
|
||||
summary="查询智能体配置列表",
|
||||
description="查询智能体配置列表",
|
||||
response_model=ResponseSchema[list[AgentConfigOutSchema]],
|
||||
)
|
||||
async def agent_config_list_controller(
|
||||
page: Annotated[PaginationQueryParam, Depends()],
|
||||
search: Annotated[AgentConfigQueryParam, Depends()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:agent-config:query"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
查询智能体配置列表接口
|
||||
|
||||
参数:
|
||||
- page (PaginationQueryParam): 分页查询参数模型
|
||||
- search (AgentConfigQueryParam): 查询参数模型
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- SuccessResponse: 包含智能体配置列表的响应
|
||||
"""
|
||||
result_dict = await AgentConfigService.get_list_service(auth=auth, query_params=search)
|
||||
result_dict = await PaginationService.paginate(
|
||||
data_list=result_dict["data"],
|
||||
page_no=page.page_no,
|
||||
page_size=page.page_size,
|
||||
)
|
||||
log.info("查询智能体配置列表成功")
|
||||
return SuccessResponse(data=result_dict, msg="查询智能体配置列表成功")
|
||||
|
||||
|
||||
@AIRouter.post(
|
||||
"/agent-config/create",
|
||||
summary="创建智能体配置",
|
||||
description="创建智能体配置",
|
||||
response_model=ResponseSchema[AgentConfigOutSchema],
|
||||
)
|
||||
async def agent_config_create_controller(
|
||||
data: AgentConfigCreateSchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:agent-config:create"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
创建智能体配置接口
|
||||
|
||||
参数:
|
||||
- data (AgentConfigCreateSchema): 创建智能体配置模型
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- SuccessResponse: 包含创建智能体配置结果的响应
|
||||
"""
|
||||
result_dict = await AgentConfigService.create_service(auth=auth, data=data)
|
||||
log.info(f"创建智能体配置成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="创建智能体配置成功")
|
||||
|
||||
|
||||
@AIRouter.put(
|
||||
"/agent-config/update/{id}",
|
||||
summary="修改智能体配置",
|
||||
description="修改智能体配置",
|
||||
response_model=ResponseSchema[AgentConfigOutSchema],
|
||||
)
|
||||
async def agent_config_update_controller(
|
||||
data: AgentConfigUpdateSchema,
|
||||
id: Annotated[int, Path(description="智能体配置ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:agent-config:update"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
修改智能体配置接口
|
||||
|
||||
参数:
|
||||
- data (AgentConfigUpdateSchema): 修改智能体配置模型
|
||||
- id (int): 智能体配置ID
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- SuccessResponse: 包含修改智能体配置结果的响应
|
||||
"""
|
||||
result_dict = await AgentConfigService.update_service(auth=auth, id=id, data=data)
|
||||
log.info(f"修改智能体配置成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="修改智能体配置成功")
|
||||
|
||||
|
||||
@AIRouter.delete(
|
||||
"/agent-config/delete",
|
||||
summary="删除智能体配置",
|
||||
description="删除智能体配置",
|
||||
response_model=ResponseSchema[None],
|
||||
)
|
||||
async def agent_config_delete_controller(
|
||||
ids: Annotated[list[int], Body(description="ID列表")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:agent-config:delete"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
删除智能体配置接口
|
||||
|
||||
参数:
|
||||
- ids (list[int]): 智能体配置ID列表
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- SuccessResponse: 包含删除智能体配置结果的响应
|
||||
"""
|
||||
await AgentConfigService.delete_service(auth=auth, ids=ids)
|
||||
log.info(f"删除智能体配置成功: {ids}")
|
||||
return SuccessResponse(msg="删除智能体配置成功")
|
||||
|
||||
|
||||
@AIRouter.post(
|
||||
"/chat",
|
||||
summary="智能对话",
|
||||
description="与MCP智能助手进行对话",
|
||||
description="与智能助手进行对话",
|
||||
)
|
||||
async def chat_controller(
|
||||
query: ChatQuerySchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:ai:chat"]))],
|
||||
) -> StreamingResponse:
|
||||
) -> StreamResponse:
|
||||
"""
|
||||
智能对话接口
|
||||
|
||||
@@ -46,8 +209,7 @@ async def chat_controller(
|
||||
|
||||
async def generate_response():
|
||||
try:
|
||||
async for chunk in McpService.chat_query(query=query):
|
||||
# 确保返回的是字节串
|
||||
async for chunk in AgentService.chat_query(query=query):
|
||||
if chunk:
|
||||
yield (chunk.encode("utf-8") if isinstance(chunk, str) else chunk)
|
||||
except Exception as e:
|
||||
@@ -58,52 +220,52 @@ async def chat_controller(
|
||||
|
||||
|
||||
@AIRouter.get(
|
||||
"/detail/{id}",
|
||||
summary="获取 MCP 服务器详情",
|
||||
description="获取 MCP 服务器详情",
|
||||
response_model=ResponseSchema[McpOutSchema],
|
||||
"/knowledge/detail/{id}",
|
||||
summary="获取知识库详情",
|
||||
description="获取知识库详情",
|
||||
response_model=ResponseSchema[KnowledgeOutSchema],
|
||||
)
|
||||
async def detail_controller(
|
||||
id: Annotated[int, Path(description="MCP ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:ai:query"]))],
|
||||
) -> JSONResponse:
|
||||
async def knowledge_detail_controller(
|
||||
id: Annotated[int, Path(description="知识库ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:knowledge:query"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
获取 MCP 服务器详情接口
|
||||
获取知识库详情接口
|
||||
|
||||
参数:
|
||||
- id (int): MCP 服务器ID
|
||||
- id (int): 知识库ID
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含 MCP 服务器详情的 JSON 响应
|
||||
- SuccessResponse: 包含知识库详情的响应
|
||||
"""
|
||||
result_dict = await McpService.detail_service(auth=auth, id=id)
|
||||
log.info(f"获取 MCP 服务器详情成功 {id}")
|
||||
return SuccessResponse(data=result_dict, msg="获取 MCP 服务器详情成功")
|
||||
result_dict = await KnowledgeService.detail_service(auth=auth, id=id)
|
||||
log.info(f"获取知识库详情成功 {id}")
|
||||
return SuccessResponse(data=result_dict, msg="获取知识库详情成功")
|
||||
|
||||
|
||||
@AIRouter.get(
|
||||
"/list",
|
||||
summary="查询 MCP 服务器列表",
|
||||
description="查询 MCP 服务器列表",
|
||||
response_model=ResponseSchema[list[McpOutSchema]],
|
||||
"/knowledge/list",
|
||||
summary="查询知识库列表",
|
||||
description="查询知识库列表",
|
||||
response_model=ResponseSchema[list[KnowledgeOutSchema]],
|
||||
)
|
||||
async def list_controller(
|
||||
async def knowledge_list_controller(
|
||||
page: Annotated[PaginationQueryParam, Depends()],
|
||||
search: Annotated[McpQueryParam, Depends()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:ai:query"]))],
|
||||
) -> JSONResponse:
|
||||
search: Annotated[KnowledgeQueryParam, Depends()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:knowledge:query"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
查询 MCP 服务器列表接口
|
||||
查询知识库列表接口
|
||||
|
||||
参数:
|
||||
- page (PaginationQueryParam): 分页查询参数模型
|
||||
- search (McpQueryParam): 查询参数模型
|
||||
- search (KnowledgeQueryParam): 查询参数模型
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含 MCP 服务器列表的 JSON 响应
|
||||
- SuccessResponse: 包含知识库列表的响应
|
||||
"""
|
||||
result_dict_list = await McpService.list_service(
|
||||
result_dict_list = await KnowledgeService.list_service(
|
||||
auth=auth, search=search, order_by=page.order_by
|
||||
)
|
||||
result_dict = await PaginationService.paginate(
|
||||
@@ -111,82 +273,217 @@ async def list_controller(
|
||||
page_no=page.page_no,
|
||||
page_size=page.page_size,
|
||||
)
|
||||
log.info("查询 MCP 服务器列表成功")
|
||||
return SuccessResponse(data=result_dict, msg="查询 MCP 服务器列表成功")
|
||||
log.info("查询知识库列表成功")
|
||||
return SuccessResponse(data=result_dict, msg="查询知识库列表成功")
|
||||
|
||||
|
||||
@AIRouter.post(
|
||||
"/create",
|
||||
summary="创建 MCP 服务器",
|
||||
description="创建 MCP 服务器",
|
||||
response_model=ResponseSchema[McpOutSchema],
|
||||
"/knowledge/create",
|
||||
summary="创建知识库",
|
||||
description="创建知识库",
|
||||
response_model=ResponseSchema[KnowledgeOutSchema],
|
||||
)
|
||||
async def create_controller(
|
||||
data: McpCreateSchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:ai:create"]))],
|
||||
) -> JSONResponse:
|
||||
async def knowledge_create_controller(
|
||||
data: KnowledgeCreateSchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:knowledge:create"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
创建 MCP 服务器接口
|
||||
创建知识库接口
|
||||
|
||||
参数:
|
||||
- data (McpCreateSchema): 创建 MCP 服务器模型
|
||||
- data (KnowledgeCreateSchema): 创建知识库模型
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含创建 MCP 服务器结果的 JSON 响应
|
||||
- SuccessResponse: 包含创建知识库结果的响应
|
||||
"""
|
||||
result_dict = await McpService.create_service(auth=auth, data=data)
|
||||
log.info(f"创建 MCP 服务器成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="创建 MCP 服务器成功")
|
||||
result_dict = await KnowledgeService.create_service(auth=auth, data=data)
|
||||
log.info(f"创建知识库成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="创建知识库成功")
|
||||
|
||||
|
||||
@AIRouter.put(
|
||||
"/update/{id}",
|
||||
summary="修改 MCP 服务器",
|
||||
description="修改 MCP 服务器",
|
||||
response_model=ResponseSchema[McpOutSchema],
|
||||
"/knowledge/update/{id}",
|
||||
summary="修改知识库",
|
||||
description="修改知识库",
|
||||
response_model=ResponseSchema[KnowledgeOutSchema],
|
||||
)
|
||||
async def update_controller(
|
||||
data: McpUpdateSchema,
|
||||
id: Annotated[int, Path(description="MCP ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:ai:update"]))],
|
||||
) -> JSONResponse:
|
||||
async def knowledge_update_controller(
|
||||
data: KnowledgeUpdateSchema,
|
||||
id: Annotated[int, Path(description="知识库ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:knowledge:update"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
修改 MCP 服务器接口
|
||||
修改知识库接口
|
||||
|
||||
参数:
|
||||
- data (McpUpdateSchema): 修改 MCP 服务器模型
|
||||
- id (int): MCP 服务器ID
|
||||
- data (KnowledgeUpdateSchema): 修改知识库模型
|
||||
- id (int): 知识库ID
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含修改 MCP 服务器结果的 JSON 响应
|
||||
- SuccessResponse: 包含修改知识库结果的响应
|
||||
"""
|
||||
result_dict = await McpService.update_service(auth=auth, id=id, data=data)
|
||||
log.info(f"修改 MCP 服务器成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="修改 MCP 服务器成功")
|
||||
result_dict = await KnowledgeService.update_service(auth=auth, id=id, data=data)
|
||||
log.info(f"修改知识库成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="修改知识库成功")
|
||||
|
||||
|
||||
@AIRouter.delete(
|
||||
"/delete",
|
||||
summary="删除 MCP 服务器",
|
||||
description="删除 MCP 服务器",
|
||||
"/knowledge/delete",
|
||||
summary="删除知识库",
|
||||
description="删除知识库",
|
||||
response_model=ResponseSchema[None],
|
||||
)
|
||||
async def delete_controller(
|
||||
async def knowledge_delete_controller(
|
||||
ids: Annotated[list[int], Body(description="ID列表")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:ai:delete"]))],
|
||||
) -> JSONResponse:
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:knowledge:delete"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
删除 MCP 服务器接口
|
||||
删除知识库接口
|
||||
|
||||
参数:
|
||||
- ids (list[int]): MCP 服务器ID列表
|
||||
- ids (list[int]): 知识库ID列表
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含删除 MCP 服务器结果的 JSON 响应
|
||||
- SuccessResponse: 包含删除知识库结果的响应
|
||||
"""
|
||||
await McpService.delete_service(auth=auth, ids=ids)
|
||||
log.info(f"删除 MCP 服务器成功: {ids}")
|
||||
return SuccessResponse(msg="删除 MCP 服务器成功")
|
||||
await KnowledgeService.delete_service(auth=auth, ids=ids)
|
||||
log.info(f"删除知识库成功: {ids}")
|
||||
return SuccessResponse(msg="删除知识库成功")
|
||||
|
||||
|
||||
@AIRouter.get(
|
||||
"/document/detail/{id}",
|
||||
summary="获取知识库文档详情",
|
||||
description="获取知识库文档详情",
|
||||
response_model=ResponseSchema[KnowledgeDocumentOutSchema],
|
||||
)
|
||||
async def document_detail_controller(
|
||||
id: Annotated[int, Path(description="文档ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:document:query"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
获取知识库文档详情接口
|
||||
|
||||
参数:
|
||||
- id (int): 文档ID
|
||||
|
||||
返回:
|
||||
- SuccessResponse: 包含文档详情的响应
|
||||
"""
|
||||
result_dict = await KnowledgeService.document_detail_service(auth=auth, id=id)
|
||||
log.info(f"获取知识库文档详情成功 {id}")
|
||||
return SuccessResponse(data=result_dict, msg="获取知识库文档详情成功")
|
||||
|
||||
|
||||
@AIRouter.get(
|
||||
"/document/list",
|
||||
summary="查询知识库文档列表",
|
||||
description="查询知识库文档列表",
|
||||
response_model=ResponseSchema[list[KnowledgeDocumentOutSchema]],
|
||||
)
|
||||
async def document_list_controller(
|
||||
page: Annotated[PaginationQueryParam, Depends()],
|
||||
search: Annotated[KnowledgeDocumentQueryParam, Depends()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:document:query"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
查询知识库文档列表接口
|
||||
|
||||
参数:
|
||||
- page (PaginationQueryParam): 分页查询参数模型
|
||||
- search (KnowledgeDocumentQueryParam): 查询参数模型
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- SuccessResponse: 包含文档列表的响应
|
||||
"""
|
||||
result_dict_list = await KnowledgeService.document_list_service(
|
||||
auth=auth, search=search, order_by=page.order_by
|
||||
)
|
||||
result_dict = await PaginationService.paginate(
|
||||
data_list=result_dict_list,
|
||||
page_no=page.page_no,
|
||||
page_size=page.page_size,
|
||||
)
|
||||
log.info("查询知识库文档列表成功")
|
||||
return SuccessResponse(data=result_dict, msg="查询知识库文档列表成功")
|
||||
|
||||
|
||||
@AIRouter.post(
|
||||
"/document/create",
|
||||
summary="创建知识库文档",
|
||||
description="创建知识库文档",
|
||||
response_model=ResponseSchema[KnowledgeDocumentOutSchema],
|
||||
)
|
||||
async def document_create_controller(
|
||||
data: KnowledgeDocumentCreateSchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:document:create"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
创建知识库文档接口
|
||||
|
||||
参数:
|
||||
- data (KnowledgeDocumentCreateSchema): 创建文档模型
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- SuccessResponse: 包含创建文档结果的响应
|
||||
"""
|
||||
result_dict = await KnowledgeService.document_create_service(auth=auth, data=data)
|
||||
log.info(f"创建知识库文档成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="创建知识库文档成功")
|
||||
|
||||
|
||||
@AIRouter.put(
|
||||
"/document/update/{id}",
|
||||
summary="修改知识库文档",
|
||||
description="修改知识库文档",
|
||||
response_model=ResponseSchema[KnowledgeDocumentOutSchema],
|
||||
)
|
||||
async def document_update_controller(
|
||||
data: KnowledgeDocumentUpdateSchema,
|
||||
id: Annotated[int, Path(description="文档ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:document:update"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
修改知识库文档接口
|
||||
|
||||
参数:
|
||||
- data (KnowledgeDocumentUpdateSchema): 修改文档模型
|
||||
- id (int): 文档ID
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- SuccessResponse: 包含修改文档结果的响应
|
||||
"""
|
||||
result_dict = await KnowledgeService.document_update_service(auth=auth, id=id, data=data)
|
||||
log.info(f"修改知识库文档成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="修改知识库文档成功")
|
||||
|
||||
|
||||
@AIRouter.delete(
|
||||
"/document/delete",
|
||||
summary="删除知识库文档",
|
||||
description="删除知识库文档",
|
||||
response_model=ResponseSchema[None],
|
||||
)
|
||||
async def document_delete_controller(
|
||||
ids: Annotated[list[int], Body(description="ID列表")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_application:document:delete"]))],
|
||||
) -> SuccessResponse:
|
||||
"""
|
||||
删除知识库文档接口
|
||||
|
||||
参数:
|
||||
- ids (list[int]): 文档ID列表
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- SuccessResponse: 包含删除文档结果的响应
|
||||
"""
|
||||
await KnowledgeService.document_delete_service(auth=auth, ids=ids)
|
||||
log.info(f"删除知识库文档成功: {ids}")
|
||||
return SuccessResponse(msg="删除知识库文档成功")
|
||||
|
||||
@@ -4,12 +4,19 @@ from typing import Any
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.core.base_crud import CRUDBase
|
||||
|
||||
from .model import McpModel
|
||||
from .schema import McpCreateSchema, McpUpdateSchema
|
||||
from .model import AgentConfigModel, KnowledgeDocumentModel, KnowledgeModel
|
||||
from .schema import (
|
||||
AgentConfigCreateSchema,
|
||||
AgentConfigUpdateSchema,
|
||||
KnowledgeCreateSchema,
|
||||
KnowledgeDocumentCreateSchema,
|
||||
KnowledgeDocumentUpdateSchema,
|
||||
KnowledgeUpdateSchema,
|
||||
)
|
||||
|
||||
|
||||
class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]):
|
||||
"""MCP 服务器数据层"""
|
||||
class AgentConfigCRUD(CRUDBase[AgentConfigModel, AgentConfigCreateSchema, AgentConfigUpdateSchema]):
|
||||
"""智能体配置数据层"""
|
||||
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
"""
|
||||
@@ -19,35 +26,142 @@ class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]):
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
"""
|
||||
self.auth = auth
|
||||
super().__init__(model=McpModel, auth=auth)
|
||||
super().__init__(model=AgentConfigModel, auth=auth)
|
||||
|
||||
async def get_by_id_crud(
|
||||
self, id: int, preload: list[str | Any] | None = None
|
||||
) -> McpModel | None:
|
||||
) -> AgentConfigModel | None:
|
||||
"""
|
||||
获取MCP服务器详情
|
||||
获取智能体配置详情
|
||||
|
||||
参数:
|
||||
- id (int): MCP服务器ID
|
||||
- id (int): 智能体配置ID
|
||||
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
|
||||
|
||||
返回:
|
||||
- McpModel | None: MCP服务器模型实例(如果存在)
|
||||
- AgentConfigModel | None: 智能体配置模型实例(如果存在)
|
||||
"""
|
||||
return await self.get(id=id, preload=preload)
|
||||
|
||||
async def get_default_crud(
|
||||
self, preload: list[str | Any] | None = None
|
||||
) -> AgentConfigModel | None:
|
||||
"""
|
||||
获取默认智能体配置
|
||||
|
||||
参数:
|
||||
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
|
||||
|
||||
返回:
|
||||
- AgentConfigModel | None: 智能体配置模型实例(如果存在)
|
||||
"""
|
||||
return await self.get(is_default=1, preload=preload)
|
||||
|
||||
async def get_list_crud(
|
||||
self,
|
||||
search: dict | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
preload: list[str | Any] | None = None,
|
||||
) -> Sequence[AgentConfigModel]:
|
||||
"""
|
||||
列表查询智能体配置
|
||||
|
||||
参数:
|
||||
- search (dict | None): 查询参数字典
|
||||
- order_by (list[dict[str, str]] | None): 排序参数列表
|
||||
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
|
||||
|
||||
返回:
|
||||
- Sequence[AgentConfigModel]: 智能体配置模型实例序列
|
||||
"""
|
||||
return await self.list(
|
||||
search=search or {},
|
||||
order_by=order_by or [{"id": "asc"}],
|
||||
preload=preload,
|
||||
)
|
||||
|
||||
async def create_crud(
|
||||
self, data: AgentConfigCreateSchema
|
||||
) -> AgentConfigModel | None:
|
||||
"""
|
||||
创建智能体配置
|
||||
|
||||
参数:
|
||||
- data (AgentConfigCreateSchema): 创建智能体配置模型
|
||||
|
||||
返回:
|
||||
- Optional[AgentConfigModel]: 创建的智能体配置模型实例(如果成功)
|
||||
"""
|
||||
return await self.create(data=data)
|
||||
|
||||
async def update_crud(
|
||||
self, id: int, data: AgentConfigUpdateSchema
|
||||
) -> AgentConfigModel | None:
|
||||
"""
|
||||
更新智能体配置
|
||||
|
||||
参数:
|
||||
- id (int): 智能体配置ID
|
||||
- data (AgentConfigUpdateSchema): 更新智能体配置模型
|
||||
|
||||
返回:
|
||||
- AgentConfigModel | None: 更新的智能体配置模型实例(如果成功)
|
||||
"""
|
||||
return await self.update(id=id, data=data)
|
||||
|
||||
async def delete_crud(self, ids: list[int]) -> None:
|
||||
"""
|
||||
批量删除智能体配置
|
||||
|
||||
参数:
|
||||
- ids (list[int]): 智能体配置ID列表
|
||||
|
||||
返回:
|
||||
- None
|
||||
"""
|
||||
return await self.delete(ids=ids)
|
||||
|
||||
|
||||
class KnowledgeCRUD(CRUDBase[KnowledgeModel, KnowledgeCreateSchema, KnowledgeUpdateSchema]):
|
||||
"""知识库数据层"""
|
||||
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
"""
|
||||
初始化CRUD
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
"""
|
||||
self.auth = auth
|
||||
super().__init__(model=KnowledgeModel, auth=auth)
|
||||
|
||||
async def get_by_id_crud(
|
||||
self, id: int, preload: list[str | Any] | None = None
|
||||
) -> KnowledgeModel | None:
|
||||
"""
|
||||
获取知识库详情
|
||||
|
||||
参数:
|
||||
- id (int): 知识库ID
|
||||
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
|
||||
|
||||
返回:
|
||||
- KnowledgeModel | None: 知识库模型实例(如果存在)
|
||||
"""
|
||||
return await self.get(id=id, preload=preload)
|
||||
|
||||
async def get_by_name_crud(
|
||||
self, name: str, preload: list[str | Any] | None = None
|
||||
) -> McpModel | None:
|
||||
) -> KnowledgeModel | None:
|
||||
"""
|
||||
通过名称获取MCP服务器
|
||||
通过名称获取知识库
|
||||
|
||||
参数:
|
||||
- name (str): MCP服务器名称
|
||||
- name (str): 知识库名称
|
||||
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
|
||||
|
||||
返回:
|
||||
- Optional[McpModel]: MCP服务器模型实例(如果存在)
|
||||
- Optional[KnowledgeModel]: 知识库模型实例(如果存在)
|
||||
"""
|
||||
return await self.get(name=name, preload=preload)
|
||||
|
||||
@@ -56,9 +170,9 @@ class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]):
|
||||
search: dict | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
preload: list[str | Any] | None = None,
|
||||
) -> Sequence[McpModel]:
|
||||
) -> Sequence[KnowledgeModel]:
|
||||
"""
|
||||
列表查询MCP服务器
|
||||
列表查询知识库
|
||||
|
||||
参数:
|
||||
- search (dict | None): 查询参数字典
|
||||
@@ -66,7 +180,7 @@ class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]):
|
||||
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
|
||||
|
||||
返回:
|
||||
- Sequence[McpModel]: MCP服务器模型实例序列
|
||||
- Sequence[KnowledgeModel]: 知识库模型实例序列
|
||||
"""
|
||||
return await self.list(
|
||||
search=search or {},
|
||||
@@ -74,37 +188,147 @@ class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]):
|
||||
preload=preload,
|
||||
)
|
||||
|
||||
async def create_crud(self, data: McpCreateSchema) -> McpModel | None:
|
||||
async def create_crud(self, data: KnowledgeCreateSchema) -> KnowledgeModel | None:
|
||||
"""
|
||||
创建MCP服务器
|
||||
创建知识库
|
||||
|
||||
参数:
|
||||
- data (McpCreateSchema): 创建MCP服务器模型
|
||||
- data (KnowledgeCreateSchema): 创建知识库模型
|
||||
|
||||
返回:
|
||||
- Optional[McpModel]: 创建的MCP服务器模型实例(如果成功)
|
||||
- Optional[KnowledgeModel]: 创建的知识库模型实例(如果成功)
|
||||
"""
|
||||
return await self.create(data=data)
|
||||
|
||||
async def update_crud(self, id: int, data: McpUpdateSchema) -> McpModel | None:
|
||||
async def update_crud(
|
||||
self, id: int, data: KnowledgeUpdateSchema
|
||||
) -> KnowledgeModel | None:
|
||||
"""
|
||||
更新MCP服务器
|
||||
更新知识库
|
||||
|
||||
参数:
|
||||
- id (int): MCP服务器ID
|
||||
- data (McpUpdateSchema): 更新MCP服务器模型
|
||||
- id (int): 知识库ID
|
||||
- data (KnowledgeUpdateSchema): 更新知识库模型
|
||||
|
||||
返回:
|
||||
- McpModel | None: 更新的MCP服务器模型实例(如果成功)
|
||||
- KnowledgeModel | None: 更新的知识库模型实例(如果成功)
|
||||
"""
|
||||
return await self.update(id=id, data=data)
|
||||
|
||||
async def delete_crud(self, ids: list[int]) -> None:
|
||||
"""
|
||||
批量删除MCP服务器
|
||||
批量删除知识库
|
||||
|
||||
参数:
|
||||
- ids (list[int]): MCP服务器ID列表
|
||||
- ids (list[int]): 知识库ID列表
|
||||
|
||||
返回:
|
||||
- None
|
||||
"""
|
||||
return await self.delete(ids=ids)
|
||||
|
||||
|
||||
class KnowledgeDocumentCRUD(CRUDBase[KnowledgeDocumentModel, KnowledgeDocumentCreateSchema, KnowledgeDocumentUpdateSchema]):
|
||||
"""知识库文档数据层"""
|
||||
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
"""
|
||||
初始化CRUD
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
"""
|
||||
self.auth = auth
|
||||
super().__init__(model=KnowledgeDocumentModel, auth=auth)
|
||||
|
||||
async def get_by_id_crud(
|
||||
self, id: int, preload: list[str | Any] | None = None
|
||||
) -> KnowledgeDocumentModel | None:
|
||||
"""
|
||||
获取知识库文档详情
|
||||
|
||||
参数:
|
||||
- id (int): 文档ID
|
||||
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
|
||||
|
||||
返回:
|
||||
- KnowledgeDocumentModel | None: 知识库文档模型实例(如果存在)
|
||||
"""
|
||||
return await self.get(id=id, preload=preload)
|
||||
|
||||
async def get_by_knowledge_id_crud(
|
||||
self, knowledge_id: int, preload: list[str | Any] | None = None
|
||||
) -> Sequence[KnowledgeDocumentModel]:
|
||||
"""
|
||||
通过知识库ID获取文档列表
|
||||
|
||||
参数:
|
||||
- knowledge_id (int): 知识库ID
|
||||
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
|
||||
|
||||
返回:
|
||||
- Sequence[KnowledgeDocumentModel]: 知识库文档模型实例序列
|
||||
"""
|
||||
return await self.list(search={"knowledge_id": (0, knowledge_id)}, preload=preload)
|
||||
|
||||
async def get_list_crud(
|
||||
self,
|
||||
search: dict | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
preload: list[str | Any] | None = None,
|
||||
) -> Sequence[KnowledgeDocumentModel]:
|
||||
"""
|
||||
列表查询知识库文档
|
||||
|
||||
参数:
|
||||
- search (dict | None): 查询参数字典
|
||||
- order_by (list[dict[str, str]] | None): 排序参数列表
|
||||
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
|
||||
|
||||
返回:
|
||||
- Sequence[KnowledgeDocumentModel]: 知识库文档模型实例序列
|
||||
"""
|
||||
return await self.list(
|
||||
search=search or {},
|
||||
order_by=order_by or [{"id": "asc"}],
|
||||
preload=preload,
|
||||
)
|
||||
|
||||
async def create_crud(
|
||||
self, data: KnowledgeDocumentCreateSchema
|
||||
) -> KnowledgeDocumentModel | None:
|
||||
"""
|
||||
创建知识库文档
|
||||
|
||||
参数:
|
||||
- data (KnowledgeDocumentCreateSchema): 创建知识库文档模型
|
||||
|
||||
返回:
|
||||
- Optional[KnowledgeDocumentModel]: 创建的知识库文档模型实例(如果成功)
|
||||
"""
|
||||
return await self.create(data=data)
|
||||
|
||||
async def update_crud(
|
||||
self, id: int, data: KnowledgeDocumentUpdateSchema
|
||||
) -> KnowledgeDocumentModel | None:
|
||||
"""
|
||||
更新知识库文档
|
||||
|
||||
参数:
|
||||
- id (int): 文档ID
|
||||
- data (KnowledgeDocumentUpdateSchema): 更新知识库文档模型
|
||||
|
||||
返回:
|
||||
- KnowledgeDocumentModel | None: 更新的知识库文档模型实例(如果成功)
|
||||
"""
|
||||
return await self.update(id=id, data=data)
|
||||
|
||||
async def delete_crud(self, ids: list[int]) -> None:
|
||||
"""
|
||||
批量删除知识库文档
|
||||
|
||||
参数:
|
||||
- ids (list[int]): 文档ID列表
|
||||
|
||||
返回:
|
||||
- None
|
||||
|
||||
@@ -1,24 +1,60 @@
|
||||
from sqlalchemy import JSON, Integer, String
|
||||
from sqlalchemy import JSON, Integer, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.core.base_model import ModelMixin, UserMixin
|
||||
|
||||
|
||||
class McpModel(ModelMixin, UserMixin):
|
||||
class AgentConfigModel(ModelMixin, UserMixin):
|
||||
"""
|
||||
MCP 服务器表
|
||||
MCP类型:
|
||||
- 0: stdio (标准输入输出)
|
||||
- 1: sse (Server-Sent Events)
|
||||
智能体配置表
|
||||
"""
|
||||
|
||||
__tablename__: str = "app_ai_mcp"
|
||||
__table_args__: dict[str, str] = {"comment": "MCP 服务器表"}
|
||||
__tablename__: str = "app_ai_agent_config"
|
||||
__table_args__: dict[str, str] = {"comment": "智能体配置表"}
|
||||
__loader_options__: list[str] = ["created_by", "updated_by"]
|
||||
|
||||
name: Mapped[str] = mapped_column(String(50), comment="MCP 名称")
|
||||
type: Mapped[int] = mapped_column(Integer, default=0, comment="MCP 类型(0:stdio 1:sse)")
|
||||
url: Mapped[str | None] = mapped_column(String(255), default=None, comment="远程 SSE 地址")
|
||||
command: Mapped[str | None] = mapped_column(String(255), default=None, comment="MCP 命令")
|
||||
args: Mapped[str | None] = mapped_column(String(255), default=None, comment="MCP 命令参数")
|
||||
env: Mapped[dict[str, str] | None] = mapped_column(JSON(), default=None, comment="MCP 环境变量")
|
||||
name: Mapped[str] = mapped_column(String(100), comment="智能体名称")
|
||||
provider: Mapped[str] = mapped_column(String(50), default="openai", comment="LLM 供应商")
|
||||
model: Mapped[str] = mapped_column(String(100), comment="LLM 名称")
|
||||
api_key: Mapped[str] = mapped_column(String(500), comment="LLM API Key")
|
||||
base_url: Mapped[str | None] = mapped_column(String(500), default=None, comment="自定义 LLM API 地址")
|
||||
temperature: Mapped[float] = mapped_column(Integer, default=70, comment="温度参数 (0-100)")
|
||||
system_prompt: Mapped[str] = mapped_column(Text, comment="系统提示词")
|
||||
is_default: Mapped[bool] = mapped_column(Integer, default=0, comment="是否默认配置")
|
||||
is_active: Mapped[bool] = mapped_column(Integer, default=1, comment="是否启用")
|
||||
|
||||
|
||||
class KnowledgeModel(ModelMixin, UserMixin):
|
||||
"""
|
||||
知识库表
|
||||
"""
|
||||
|
||||
__tablename__: str = "app_ai_knowledge"
|
||||
__table_args__: dict[str, str] = {"comment": "知识库表"}
|
||||
__loader_options__: list[str] = ["created_by", "updated_by"]
|
||||
|
||||
name: Mapped[str] = mapped_column(String(100), comment="知识库名称")
|
||||
description: Mapped[str | None] = mapped_column(String(500), default=None, comment="知识库描述")
|
||||
embedding_model: Mapped[str] = mapped_column(String(100), default="openai", comment="嵌入模型")
|
||||
chunk_size: Mapped[int] = mapped_column(Integer, default=500, comment="分块大小")
|
||||
chunk_overlap: Mapped[int] = mapped_column(Integer, default=50, comment="分块重叠大小")
|
||||
is_active: Mapped[bool] = mapped_column(Integer, default=1, comment="是否启用")
|
||||
|
||||
|
||||
class KnowledgeDocumentModel(ModelMixin, UserMixin):
|
||||
"""
|
||||
知识库文档表
|
||||
"""
|
||||
|
||||
__tablename__: str = "app_ai_knowledge_document"
|
||||
__table_args__: dict[str, str] = {"comment": "知识库文档表"}
|
||||
__loader_options__: list[str] = ["created_by", "updated_by"]
|
||||
|
||||
knowledge_id: Mapped[int] = mapped_column(Integer, comment="知识库ID")
|
||||
title: Mapped[str] = mapped_column(String(200), comment="文档标题")
|
||||
content: Mapped[str] = mapped_column(Text, comment="文档内容")
|
||||
file_type: Mapped[str] = mapped_column(String(50), default="text", comment="文件类型")
|
||||
file_path: Mapped[str | None] = mapped_column(String(500), default=None, comment="文件路径")
|
||||
meta_data: Mapped[dict[str, str] | None] = mapped_column(JSON(), default=None, comment="元数据")
|
||||
chunk_count: Mapped[int] = mapped_column(Integer, default=0, comment="分块数量")
|
||||
is_indexed: Mapped[bool] = mapped_column(Integer, default=0, comment="是否已索引")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from fastapi import Query
|
||||
from pydantic import BaseModel, ConfigDict, Field, HttpUrl
|
||||
|
||||
from app.common.enums import McpLLMProvider, McpType, QueueEnum
|
||||
from fastapi import Query
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from app.common.enums import QueueEnum
|
||||
from app.core.base_schema import BaseSchema, UserBySchema
|
||||
from app.core.validator import DateTimeStr
|
||||
|
||||
@@ -10,37 +11,67 @@ class ChatQuerySchema(BaseModel):
|
||||
"""聊天查询模型"""
|
||||
|
||||
message: str = Field(..., min_length=1, max_length=4000, description="聊天消息")
|
||||
knowledge_ids: list[int] | None = Field(None, description="知识库ID列表,用于RAG检索")
|
||||
agent_config_id: int | None = Field(None, description="智能体配置ID,用于指定使用的智能体配置")
|
||||
|
||||
|
||||
class McpCreateSchema(BaseModel):
|
||||
"""创建 MCP 服务器参数"""
|
||||
class AgentConfigSchema(BaseModel):
|
||||
"""智能体配置参数"""
|
||||
|
||||
name: str = Field(..., max_length=64, description="MCP 名称")
|
||||
type: McpType = Field(McpType.stdio, description="MCP 类型")
|
||||
description: str | None = Field(None, max_length=255, description="MCP 描述")
|
||||
url: HttpUrl | None = Field(None, description="远程 SSE 地址")
|
||||
command: str | None = Field(None, max_length=255, description="MCP 命令")
|
||||
args: str | None = Field(None, max_length=255, description="MCP 命令参数")
|
||||
env: dict[str, str] | None = Field(None, description="MCP 环境变量")
|
||||
provider: str = Field("openai", description="LLM 供应商")
|
||||
model: str = Field(..., description="LLM 名称")
|
||||
api_key: str = Field(..., description="LLM API Key")
|
||||
base_url: str | None = Field(None, description="自定义 LLM API 地址")
|
||||
temperature: float = Field(0.7, description="温度参数,控制随机性")
|
||||
system_prompt: str = Field("你是一个有用的AI助手,可以帮助用户回答问题和提供帮助。请用中文回答用户的问题。", description="系统提示词")
|
||||
|
||||
|
||||
class McpUpdateSchema(McpCreateSchema):
|
||||
"""更新 MCP 服务器参数"""
|
||||
class AgentConfigCreateSchema(BaseModel):
|
||||
"""创建智能体配置参数"""
|
||||
|
||||
name: str = Field(..., max_length=100, description="智能体名称")
|
||||
provider: str = Field("openai", max_length=50, description="LLM 供应商")
|
||||
model: str = Field(..., max_length=100, description="LLM 名称")
|
||||
api_key: str = Field(..., max_length=500, description="LLM API Key")
|
||||
base_url: str | None = Field(None, max_length=500, description="自定义 LLM API 地址")
|
||||
temperature: float = Field(0.7, ge=0.0, le=2.0, description="温度参数,控制随机性")
|
||||
system_prompt: str = Field(
|
||||
"你是一个有用的AI助手,可以帮助用户回答问题和提供帮助。请用中文回答用户的问题。",
|
||||
description="系统提示词",
|
||||
)
|
||||
is_default: bool = Field(False, description="是否默认配置")
|
||||
is_active: bool = Field(True, description="是否启用")
|
||||
|
||||
|
||||
class McpOutSchema(McpCreateSchema, BaseSchema, UserBySchema):
|
||||
"""MCP 服务器详情"""
|
||||
class AgentConfigUpdateSchema(BaseModel):
|
||||
"""更新智能体配置参数"""
|
||||
|
||||
name: str | None = Field(None, max_length=100, description="智能体名称")
|
||||
provider: str | None = Field(None, max_length=50, description="LLM 供应商")
|
||||
model: str | None = Field(None, max_length=100, description="LLM 名称")
|
||||
api_key: str | None = Field(None, max_length=500, description="LLM API Key")
|
||||
base_url: str | None = Field(None, max_length=500, description="自定义 LLM API 地址")
|
||||
temperature: float | None = Field(None, ge=0.0, le=2.0, description="温度参数,控制随机性")
|
||||
system_prompt: str | None = Field(None, description="系统提示词")
|
||||
is_default: bool | None = Field(None, description="是否默认配置")
|
||||
is_active: bool | None = Field(None, description="是否启用")
|
||||
|
||||
|
||||
class AgentConfigOutSchema(AgentConfigCreateSchema, BaseSchema, UserBySchema):
|
||||
"""智能体配置详情"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class McpQueryParam:
|
||||
"""MCP 服务器查询参数"""
|
||||
class AgentConfigQueryParam:
|
||||
"""智能体配置查询参数"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str | None = Query(None, description="MCP 名称"),
|
||||
type: McpType | None = Query(None, description="MCP 类型"),
|
||||
name: str | None = Query(None, description="智能体名称"),
|
||||
provider: str | None = Query(None, description="LLM 供应商"),
|
||||
is_default: bool | None = Query(None, description="是否默认配置"),
|
||||
is_active: bool | None = Query(None, description="是否启用"),
|
||||
created_time: list[DateTimeStr] | None = Query(
|
||||
None,
|
||||
description="创建时间范围",
|
||||
@@ -54,27 +85,132 @@ class McpQueryParam:
|
||||
created_id: int | None = Query(None, description="创建人"),
|
||||
updated_id: int | None = Query(None, description="更新人"),
|
||||
) -> None:
|
||||
# 模糊查询字段
|
||||
self.name = (QueueEnum.like.value, name)
|
||||
|
||||
# 精确查询字段
|
||||
self.type = (QueueEnum.eq.value, type)
|
||||
self.provider = (QueueEnum.eq.value, provider)
|
||||
self.is_default = (QueueEnum.eq.value, is_default)
|
||||
self.is_active = (QueueEnum.eq.value, is_active)
|
||||
self.created_id = (QueueEnum.eq.value, created_id)
|
||||
self.updated_id = (QueueEnum.eq.value, updated_id)
|
||||
|
||||
# 时间范围查询
|
||||
if created_time and len(created_time) == 2:
|
||||
self.created_time = (QueueEnum.between.value, (created_time[0], created_time[1]))
|
||||
if updated_time and len(updated_time) == 2:
|
||||
self.updated_time = (QueueEnum.between.value, (updated_time[0], updated_time[1]))
|
||||
|
||||
|
||||
class McpChatParam(BaseSchema):
|
||||
"""MCP 聊天参数"""
|
||||
class KnowledgeCreateSchema(BaseModel):
|
||||
"""创建知识库参数"""
|
||||
|
||||
pk: list[int] = Field(..., description="MCP ID 列表")
|
||||
provider: McpLLMProvider = Field(McpLLMProvider.openai, description="LLM 供应商")
|
||||
model: str = Field(..., description="LLM 名称")
|
||||
key: str = Field(..., description="LLM API Key")
|
||||
base_url: str | None = Field(None, description="自定义 LLM API 地址,必须兼容 openai 供应商")
|
||||
prompt: str = Field(..., description="用户提示词")
|
||||
name: str = Field(..., max_length=100, description="知识库名称")
|
||||
description: str | None = Field(None, max_length=500, description="知识库描述")
|
||||
embedding_model: str = Field("openai", max_length=100, description="嵌入模型")
|
||||
chunk_size: int = Field(500, ge=100, le=2000, description="分块大小")
|
||||
chunk_overlap: int = Field(50, ge=0, le=500, description="分块重叠大小")
|
||||
is_active: bool = Field(True, description="是否启用")
|
||||
|
||||
|
||||
class KnowledgeUpdateSchema(KnowledgeCreateSchema):
|
||||
"""更新知识库参数"""
|
||||
|
||||
|
||||
class KnowledgeOutSchema(KnowledgeCreateSchema, BaseSchema, UserBySchema):
|
||||
"""知识库详情"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class KnowledgeQueryParam:
|
||||
"""知识库查询参数"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str | None = Query(None, description="知识库名称"),
|
||||
is_active: bool | None = Query(None, description="是否启用"),
|
||||
created_time: list[DateTimeStr] | None = Query(
|
||||
None,
|
||||
description="创建时间范围",
|
||||
examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"],
|
||||
),
|
||||
updated_time: list[DateTimeStr] | None = Query(
|
||||
None,
|
||||
description="更新时间范围",
|
||||
examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"],
|
||||
),
|
||||
created_id: int | None = Query(None, description="创建人"),
|
||||
updated_id: int | None = Query(None, description="更新人"),
|
||||
) -> None:
|
||||
self.name = (QueueEnum.like.value, name)
|
||||
self.is_active = (QueueEnum.eq.value, is_active)
|
||||
self.created_id = (QueueEnum.eq.value, created_id)
|
||||
self.updated_id = (QueueEnum.eq.value, updated_id)
|
||||
|
||||
if created_time and len(created_time) == 2:
|
||||
self.created_time = (QueueEnum.between.value, (created_time[0], created_time[1]))
|
||||
if updated_time and len(updated_time) == 2:
|
||||
self.updated_time = (QueueEnum.between.value, (updated_time[0], updated_time[1]))
|
||||
|
||||
|
||||
class KnowledgeDocumentCreateSchema(BaseModel):
|
||||
"""创建知识库文档参数"""
|
||||
|
||||
knowledge_id: int = Field(..., description="知识库ID")
|
||||
title: str = Field(..., max_length=200, description="文档标题")
|
||||
content: str = Field(..., description="文档内容")
|
||||
file_type: str = Field("text", max_length=50, description="文件类型")
|
||||
file_path: str | None = Field(None, max_length=500, description="文件路径")
|
||||
meta_data: dict[str, str] | None = Field(None, description="元数据")
|
||||
|
||||
|
||||
class KnowledgeDocumentUpdateSchema(BaseModel):
|
||||
"""更新知识库文档参数"""
|
||||
|
||||
title: str | None = Field(None, max_length=200, description="文档标题")
|
||||
content: str | None = Field(None, description="文档内容")
|
||||
meta_data: dict[str, str] | None = Field(None, description="元数据")
|
||||
chunk_count: int | None = Field(None, description="分块数量")
|
||||
is_indexed: bool | None = Field(None, description="是否已索引")
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
class KnowledgeDocumentOutSchema(KnowledgeDocumentCreateSchema, BaseSchema, UserBySchema):
|
||||
"""知识库文档详情"""
|
||||
|
||||
chunk_count: int = Field(..., description="分块数量")
|
||||
is_indexed: bool = Field(..., description="是否已索引")
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class KnowledgeDocumentQueryParam:
|
||||
"""知识库文档查询参数"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
knowledge_id: int | None = Query(None, description="知识库ID"),
|
||||
title: str | None = Query(None, description="文档标题"),
|
||||
file_type: str | None = Query(None, description="文件类型"),
|
||||
is_indexed: bool | None = Query(None, description="是否已索引"),
|
||||
created_time: list[DateTimeStr] | None = Query(
|
||||
None,
|
||||
description="创建时间范围",
|
||||
examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"],
|
||||
),
|
||||
created_id: int | None = Query(None, description="创建人"),
|
||||
) -> None:
|
||||
self.knowledge_id = (QueueEnum.eq.value, knowledge_id)
|
||||
self.title = (QueueEnum.like.value, title)
|
||||
self.file_type = (QueueEnum.eq.value, file_type)
|
||||
self.is_indexed = (QueueEnum.eq.value, is_indexed)
|
||||
self.created_id = (QueueEnum.eq.value, created_id)
|
||||
|
||||
if created_time and len(created_time) == 2:
|
||||
self.created_time = (QueueEnum.between.value, (created_time[0], created_time[1]))
|
||||
|
||||
|
||||
class RAGQuerySchema(BaseModel):
|
||||
"""RAG检索查询参数"""
|
||||
|
||||
query: str = Field(..., description="检索查询")
|
||||
knowledge_ids: list[int] = Field(..., description="知识库ID列表")
|
||||
top_k: int = Field(3, ge=1, le=10, description="返回最相关的文档数量")
|
||||
|
||||
@@ -1,151 +1,304 @@
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any
|
||||
|
||||
from langchain_core.documents import Document
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
|
||||
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
||||
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.core.exceptions import CustomException
|
||||
from app.config.setting import settings
|
||||
from app.core.exceptions import CustomException
|
||||
from app.core.logger import log
|
||||
|
||||
from .crud import McpCRUD
|
||||
from .chroma import chroma_manager
|
||||
from .crud import AgentConfigCRUD, KnowledgeCRUD, KnowledgeDocumentCRUD
|
||||
from .schema import (
|
||||
AgentConfigCreateSchema,
|
||||
AgentConfigOutSchema,
|
||||
AgentConfigSchema,
|
||||
AgentConfigUpdateSchema,
|
||||
ChatQuerySchema,
|
||||
McpCreateSchema,
|
||||
McpOutSchema,
|
||||
McpQueryParam,
|
||||
McpUpdateSchema,
|
||||
KnowledgeCreateSchema,
|
||||
KnowledgeDocumentCreateSchema,
|
||||
KnowledgeDocumentOutSchema,
|
||||
KnowledgeDocumentUpdateSchema,
|
||||
KnowledgeOutSchema,
|
||||
KnowledgeQueryParam,
|
||||
KnowledgeUpdateSchema,
|
||||
)
|
||||
|
||||
class McpService:
|
||||
"""MCP服务层"""
|
||||
|
||||
class AgentConfigService:
|
||||
"""智能体配置服务层"""
|
||||
|
||||
@classmethod
|
||||
async def detail_service(cls, auth: AuthSchema, id: int) -> dict[str, Any]:
|
||||
async def get_by_id_service(
|
||||
cls, auth: AuthSchema, id: int
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
获取MCP服务器详情
|
||||
获取智能体配置详情
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- id (int): MCP服务器ID
|
||||
- id (int): 智能体配置ID
|
||||
|
||||
返回:
|
||||
- dict[str, Any]: MCP服务器详情字典
|
||||
- dict[str, Any]: 智能体配置详情字典
|
||||
"""
|
||||
obj = await McpCRUD(auth).get_by_id_crud(id=id)
|
||||
obj = await AgentConfigCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="MCP 服务器不存在")
|
||||
return McpOutSchema.model_validate(obj).model_dump()
|
||||
raise CustomException(msg="智能体配置不存在")
|
||||
return AgentConfigOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def list_service(
|
||||
cls,
|
||||
auth: AuthSchema,
|
||||
search: McpQueryParam | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
async def get_default_service(
|
||||
cls, auth: AuthSchema
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
列表查询MCP服务器
|
||||
获取默认智能体配置
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- search (McpQueryParam | None): 查询参数模型
|
||||
- order_by (list[dict[str, str]] | None): 排序参数列表
|
||||
|
||||
返回:
|
||||
- list[dict[str, Any]]: MCP服务器详情字典列表
|
||||
- dict[str, Any]: 智能体配置详情字典
|
||||
"""
|
||||
search_dict = search.__dict__ if search else None
|
||||
obj_list = await McpCRUD(auth).get_list_crud(search=search_dict, order_by=order_by)
|
||||
return [McpOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
||||
obj = await AgentConfigCRUD(auth).get_default_crud()
|
||||
if not obj:
|
||||
raise CustomException(msg="默认智能体配置不存在")
|
||||
return AgentConfigOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def create_service(cls, auth: AuthSchema, data: McpCreateSchema) -> dict[str, Any]:
|
||||
async def get_list_service(
|
||||
cls, auth: AuthSchema, query_params: Any
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
创建MCP服务器
|
||||
列表查询智能体配置
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- data (McpCreateSchema): 创建MCP服务器模型
|
||||
- query_params (Any): 查询参数
|
||||
|
||||
返回:
|
||||
- dict[str, Any]: 创建的MCP服务器详情字典
|
||||
- dict[str, Any]: 智能体配置列表字典
|
||||
"""
|
||||
obj = await McpCRUD(auth).get_by_name_crud(name=data.name)
|
||||
if obj:
|
||||
raise CustomException(msg="创建失败,MCP 服务器已存在")
|
||||
obj = await McpCRUD(auth).create_crud(data=data)
|
||||
return McpOutSchema.model_validate(obj).model_dump()
|
||||
search = {}
|
||||
if query_params.name:
|
||||
search["name"] = query_params.name
|
||||
if query_params.provider:
|
||||
search["provider"] = query_params.provider
|
||||
if query_params.is_default is not None:
|
||||
search["is_default"] = query_params.is_default
|
||||
if query_params.is_active is not None:
|
||||
search["is_active"] = query_params.is_active
|
||||
if query_params.created_id:
|
||||
search["created_id"] = query_params.created_id
|
||||
if query_params.updated_id:
|
||||
search["updated_id"] = query_params.updated_id
|
||||
if hasattr(query_params, "created_time") and query_params.created_time:
|
||||
search["created_time"] = query_params.created_time
|
||||
if hasattr(query_params, "updated_time") and query_params.updated_time:
|
||||
search["updated_time"] = query_params.updated_time
|
||||
|
||||
objs = await AgentConfigCRUD(auth).get_list_crud(
|
||||
search=search, order_by=[{"id": "desc"}]
|
||||
)
|
||||
data = [AgentConfigOutSchema.model_validate(obj).model_dump() for obj in objs]
|
||||
return {"total": len(data), "data": data}
|
||||
|
||||
@classmethod
|
||||
async def create_service(
|
||||
cls, auth: AuthSchema, data: AgentConfigCreateSchema
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
创建智能体配置
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- data (AgentConfigCreateSchema): 创建智能体配置模型
|
||||
|
||||
返回:
|
||||
- dict[str, Any]: 创建的智能体配置字典
|
||||
"""
|
||||
if data.is_default:
|
||||
existing_default = await AgentConfigCRUD(auth).get_default_crud()
|
||||
if existing_default:
|
||||
update_data = AgentConfigUpdateSchema.model_construct(is_default=False)
|
||||
await AgentConfigCRUD(auth).update_crud(id=existing_default.id, data=update_data)
|
||||
|
||||
obj = await AgentConfigCRUD(auth).create_crud(data=data)
|
||||
if not obj:
|
||||
raise CustomException(msg="创建智能体配置失败")
|
||||
return AgentConfigOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def update_service(
|
||||
cls, auth: AuthSchema, id: int, data: McpUpdateSchema
|
||||
cls, auth: AuthSchema, id: int, data: AgentConfigUpdateSchema
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
更新MCP服务器
|
||||
更新智能体配置
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- id (int): MCP服务器ID
|
||||
- data (McpUpdateSchema): 更新MCP服务器模型
|
||||
- id (int): 智能体配置ID
|
||||
- data (AgentConfigUpdateSchema): 更新智能体配置模型
|
||||
|
||||
返回:
|
||||
- dict[str, Any]: 更新的MCP服务器详情字典
|
||||
- dict[str, Any]: 更新的智能体配置字典
|
||||
"""
|
||||
obj = await McpCRUD(auth).get_by_id_crud(id=id)
|
||||
obj = await AgentConfigCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="更新失败,该数据不存在")
|
||||
exist_obj = await McpCRUD(auth).get_by_name_crud(name=data.name)
|
||||
if exist_obj and exist_obj.id != id:
|
||||
raise CustomException(msg="更新失败,MCP 服务器名称重复")
|
||||
obj = await McpCRUD(auth).update_crud(id=id, data=data)
|
||||
return McpOutSchema.model_validate(obj).model_dump()
|
||||
raise CustomException(msg="智能体配置不存在")
|
||||
|
||||
if data.is_default:
|
||||
existing_default = await AgentConfigCRUD(auth).get_default_crud()
|
||||
if existing_default and existing_default.id != id:
|
||||
update_data = AgentConfigUpdateSchema.model_construct(is_default=False)
|
||||
await AgentConfigCRUD(auth).update_crud(id=existing_default.id, data=update_data)
|
||||
|
||||
obj = await AgentConfigCRUD(auth).update_crud(id=id, data=data)
|
||||
if not obj:
|
||||
raise CustomException(msg="更新智能体配置失败")
|
||||
return AgentConfigOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def delete_service(cls, auth: AuthSchema, ids: list[int]) -> None:
|
||||
"""
|
||||
批量删除MCP服务器
|
||||
批量删除智能体配置
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- ids (list[int]): MCP服务器ID列表
|
||||
- ids (list[int]): 智能体配置ID列表
|
||||
|
||||
返回:
|
||||
- None
|
||||
"""
|
||||
if len(ids) < 1:
|
||||
raise CustomException(msg="删除失败,删除对象不能为空")
|
||||
for id in ids:
|
||||
obj = await McpCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="删除失败,该数据不存在")
|
||||
await McpCRUD(auth).delete_crud(ids=ids)
|
||||
await AgentConfigCRUD(auth).delete_crud(ids=ids)
|
||||
|
||||
|
||||
class RAGService:
|
||||
"""RAG 检索增强生成服务层"""
|
||||
|
||||
@classmethod
|
||||
async def chat_query(cls, query: ChatQuerySchema) -> AsyncGenerator[str, Any]:
|
||||
async def retrieve_documents(
|
||||
cls,
|
||||
query: str,
|
||||
knowledge_ids: list[int],
|
||||
top_k: int = 3,
|
||||
auth: AuthSchema | None = None,
|
||||
) -> list[Document]:
|
||||
"""
|
||||
从知识库中检索相关文档
|
||||
|
||||
参数:
|
||||
- query (str): 查询文本
|
||||
- knowledge_ids (list[int]): 知识库ID列表
|
||||
- top_k (int): 返回最相关的文档数量
|
||||
- auth (AuthSchema | None): 认证信息模型
|
||||
|
||||
返回:
|
||||
- list[Document]: 相关文档列表
|
||||
"""
|
||||
embeddings = OpenAIEmbeddings(
|
||||
api_key=lambda: settings.OPENAI_API_KEY,
|
||||
base_url=settings.OPENAI_BASE_URL,
|
||||
)
|
||||
|
||||
query_embedding = await embeddings.aembed_query(query)
|
||||
|
||||
results = chroma_manager.query_documents(
|
||||
query_embeddings=[query_embedding],
|
||||
n_results=top_k,
|
||||
where={"knowledge_id": {"$in": knowledge_ids}},
|
||||
)
|
||||
|
||||
documents = []
|
||||
if results and "documents" in results and len(results["documents"]) > 0:
|
||||
for i, doc_content in enumerate(results["documents"][0]):
|
||||
metadata = results["metadatas"][0][i] if "metadatas" in results and len(results["metadatas"]) > 0 else {}
|
||||
documents.append(
|
||||
Document(
|
||||
page_content=doc_content,
|
||||
metadata=metadata,
|
||||
)
|
||||
)
|
||||
|
||||
return documents
|
||||
|
||||
@classmethod
|
||||
def format_context(cls, documents: list[Document]) -> str:
|
||||
"""
|
||||
格式化检索到的文档为上下文
|
||||
|
||||
参数:
|
||||
- documents (list[Document]): 文档列表
|
||||
|
||||
返回:
|
||||
- str: 格式化的上下文文本
|
||||
"""
|
||||
if not documents:
|
||||
return "没有找到相关的知识库内容。"
|
||||
|
||||
context_parts = []
|
||||
for i, doc in enumerate(documents, 1):
|
||||
title = doc.metadata.get("title", "未知文档")
|
||||
content = doc.page_content
|
||||
context_parts.append(f"[文档 {i}] {title}\n{content}")
|
||||
|
||||
return "\n\n".join(context_parts)
|
||||
|
||||
|
||||
class AgentService:
|
||||
"""智能体服务层"""
|
||||
|
||||
@classmethod
|
||||
async def chat_query(
|
||||
cls, query: ChatQuerySchema, config: AgentConfigSchema | None = None
|
||||
) -> AsyncGenerator[str, Any]:
|
||||
"""
|
||||
处理聊天查询
|
||||
|
||||
参数:
|
||||
- query (ChatQuerySchema): 聊天查询模型
|
||||
- config (AgentConfigSchema | None): 智能体配置模型
|
||||
|
||||
返回:
|
||||
- AsyncGenerator[str, None]: 异步生成器,每次返回一个聊天响应
|
||||
"""
|
||||
# 创建MCP客户端实例
|
||||
lll_model = ChatOpenAI(
|
||||
api_key=lambda: settings.OPENAI_API_KEY,
|
||||
model=settings.OPENAI_MODEL,
|
||||
base_url=settings.OPENAI_BASE_URL,
|
||||
temperature=0.7,
|
||||
streaming=True,
|
||||
)
|
||||
if config is None:
|
||||
config = AgentConfigSchema(
|
||||
provider="openai",
|
||||
model=settings.OPENAI_MODEL,
|
||||
api_key=settings.OPENAI_API_KEY,
|
||||
base_url=settings.OPENAI_BASE_URL,
|
||||
temperature=0.7,
|
||||
system_prompt="你是一个有用的AI助手,可以帮助用户回答问题和提供帮助。请用中文回答用户的问题。",
|
||||
)
|
||||
|
||||
system_prompt = (
|
||||
"""你是一个有用的AI助手,可以帮助用户回答问题和提供帮助。请用中文回答用户的问题。"""
|
||||
system_prompt = config.system_prompt
|
||||
|
||||
if query.knowledge_ids:
|
||||
retrieved_docs = await RAGService.retrieve_documents(
|
||||
query=query.message,
|
||||
knowledge_ids=query.knowledge_ids,
|
||||
top_k=3,
|
||||
)
|
||||
context = RAGService.format_context(retrieved_docs)
|
||||
system_prompt = f"""{config.system_prompt}
|
||||
|
||||
以下是从知识库中检索到的相关内容,请参考这些内容回答用户的问题:
|
||||
|
||||
{context}
|
||||
|
||||
如果检索到的内容与问题无关,请忽略这些内容,直接回答用户的问题。"""
|
||||
|
||||
llm = ChatOpenAI(
|
||||
api_key=lambda: config.api_key,
|
||||
model=config.model,
|
||||
base_url=config.base_url,
|
||||
temperature=config.temperature,
|
||||
streaming=True,
|
||||
)
|
||||
|
||||
messages = [
|
||||
@@ -154,13 +307,12 @@ class McpService:
|
||||
]
|
||||
|
||||
try:
|
||||
# 使用LangChain的流式响应
|
||||
async for chunk in lll_model.astream(messages):
|
||||
async for chunk in llm.astream(messages):
|
||||
yield chunk.text
|
||||
|
||||
except Exception as e:
|
||||
log.debug(f"关闭AIClient时发生异常(预期行为,服务可能正在关闭): {e}")
|
||||
|
||||
log.debug(f"关闭 LLM 客户端时发生异常(预期行为,服务可能正在关闭): {e}")
|
||||
|
||||
status_code = getattr(e, "status_code", None)
|
||||
body = getattr(e, "body", None)
|
||||
message = None
|
||||
@@ -178,36 +330,400 @@ class McpService:
|
||||
text = str(e)
|
||||
msg = message or text
|
||||
|
||||
# 特定错误映射
|
||||
# 欠费/账户状态异常
|
||||
if (
|
||||
(error_code == "Arrearage")
|
||||
or (error_type == "Arrearage")
|
||||
or ("in good standing" in (msg or ""))
|
||||
):
|
||||
raise ValueError("账户欠费或结算异常,访问被拒绝。请检查账号状态或更换有效的 API Key。")
|
||||
# 鉴权失败
|
||||
raise ValueError(
|
||||
"账户欠费或结算异常,访问被拒绝。请检查账号状态或更换有效的 API Key。"
|
||||
)
|
||||
if status_code == 401 or "invalid api key" in msg.lower():
|
||||
raise ValueError("鉴权失败,API Key 无效或已过期。请检查系统配置中的 API Key。")
|
||||
# 权限不足或被拒绝
|
||||
if status_code == 403 or error_type in {
|
||||
"PermissionDenied",
|
||||
"permission_denied",
|
||||
}:
|
||||
raise ValueError("访问被拒绝,权限不足或账号受限。请检查账户权限设置。")
|
||||
# 配额不足或限流
|
||||
if status_code == 429 or error_type in {
|
||||
"insufficient_quota",
|
||||
"rate_limit_exceeded",
|
||||
}:
|
||||
raise ValueError("请求过于频繁或配额已用尽。请稍后重试或提升账户配额。")
|
||||
# 客户端错误
|
||||
if status_code == 400:
|
||||
raise ValueError(f"请求参数错误或服务拒绝:{message or '请检查输入内容。'}")
|
||||
# 服务端错误
|
||||
if status_code in {500, 502, 503, 504}:
|
||||
raise ValueError("服务暂时不可用,请稍后重试。")
|
||||
|
||||
# 默认兜底
|
||||
raise CustomException(f"处理您的请求时出现错误:{msg}")
|
||||
|
||||
|
||||
class KnowledgeService:
|
||||
"""知识库服务层"""
|
||||
|
||||
@classmethod
|
||||
async def detail_service(cls, auth: AuthSchema, id: int) -> dict[str, Any]:
|
||||
"""
|
||||
获取知识库详情
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- id (int): 知识库ID
|
||||
|
||||
返回:
|
||||
- dict[str, Any]: 知识库详情字典
|
||||
"""
|
||||
obj = await KnowledgeCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="知识库不存在")
|
||||
return KnowledgeOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def list_service(
|
||||
cls,
|
||||
auth: AuthSchema,
|
||||
search: KnowledgeQueryParam | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""
|
||||
列表查询知识库
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- search (KnowledgeQueryParam | None): 查询参数模型
|
||||
- order_by (list[dict[str, str]] | None): 排序参数列表
|
||||
|
||||
返回:
|
||||
- list[dict[str, Any]]: 知识库详情字典列表
|
||||
"""
|
||||
search_dict = search.__dict__ if search else None
|
||||
obj_list = await KnowledgeCRUD(auth).get_list_crud(search=search_dict, order_by=order_by)
|
||||
return [KnowledgeOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
||||
|
||||
@classmethod
|
||||
async def create_service(cls, auth: AuthSchema, data: KnowledgeCreateSchema) -> dict[str, Any]:
|
||||
"""
|
||||
创建知识库
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- data (KnowledgeCreateSchema): 创建知识库模型
|
||||
|
||||
返回:
|
||||
- dict[str, Any]: 创建的知识库详情字典
|
||||
"""
|
||||
obj = await KnowledgeCRUD(auth).get_by_name_crud(name=data.name)
|
||||
if obj:
|
||||
raise CustomException(msg="创建失败,知识库已存在")
|
||||
obj = await KnowledgeCRUD(auth).create_crud(data=data)
|
||||
return KnowledgeOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def update_service(
|
||||
cls, auth: AuthSchema, id: int, data: KnowledgeUpdateSchema
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
更新知识库
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- id (int): 知识库ID
|
||||
- data (KnowledgeUpdateSchema): 更新知识库模型
|
||||
|
||||
返回:
|
||||
- dict[str, Any]: 更新的知识库详情字典
|
||||
"""
|
||||
obj = await KnowledgeCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="更新失败,该数据不存在")
|
||||
exist_obj = await KnowledgeCRUD(auth).get_by_name_crud(name=data.name)
|
||||
if exist_obj and exist_obj.id != id:
|
||||
raise CustomException(msg="更新失败,知识库名称重复")
|
||||
obj = await KnowledgeCRUD(auth).update_crud(id=id, data=data)
|
||||
return KnowledgeOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def delete_service(cls, auth: AuthSchema, ids: list[int]) -> None:
|
||||
"""
|
||||
批量删除知识库
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- ids (list[int]): 知识库ID列表
|
||||
|
||||
返回:
|
||||
- None
|
||||
"""
|
||||
if len(ids) < 1:
|
||||
raise CustomException(msg="删除失败,删除对象不能为空")
|
||||
for id in ids:
|
||||
obj = await KnowledgeCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="删除失败,该数据不存在")
|
||||
await KnowledgeCRUD(auth).delete_crud(ids=ids)
|
||||
|
||||
@classmethod
|
||||
async def document_detail_service(cls, auth: AuthSchema, id: int) -> dict[str, Any]:
|
||||
"""
|
||||
获取知识库文档详情
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- id (int): 文档ID
|
||||
|
||||
返回:
|
||||
- dict[str, Any]: 文档详情字典
|
||||
"""
|
||||
obj = await KnowledgeDocumentCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="文档不存在")
|
||||
return KnowledgeDocumentOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def document_list_service(
|
||||
cls,
|
||||
auth: AuthSchema,
|
||||
search: Any | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""
|
||||
列表查询知识库文档
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- search (Any | None): 查询参数模型
|
||||
- order_by (list[dict[str, str]] | None): 排序参数列表
|
||||
|
||||
返回:
|
||||
- list[dict[str, Any]]: 文档详情字典列表
|
||||
"""
|
||||
search_dict = search.__dict__ if search else None
|
||||
obj_list = await KnowledgeDocumentCRUD(auth).get_list_crud(
|
||||
search=search_dict, order_by=order_by
|
||||
)
|
||||
return [KnowledgeDocumentOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
||||
|
||||
@classmethod
|
||||
async def document_create_service(
|
||||
cls, auth: AuthSchema, data: KnowledgeDocumentCreateSchema
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
创建知识库文档
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- data (KnowledgeDocumentCreateSchema): 创建文档模型
|
||||
|
||||
返回:
|
||||
- dict[str, Any]: 创建的文档详情字典
|
||||
"""
|
||||
knowledge = await KnowledgeCRUD(auth).get_by_id_crud(id=data.knowledge_id)
|
||||
if not knowledge:
|
||||
raise CustomException(msg="创建失败,知识库不存在")
|
||||
|
||||
obj = await KnowledgeDocumentCRUD(auth).create_crud(data=data)
|
||||
if not obj:
|
||||
raise CustomException(msg="创建文档失败")
|
||||
|
||||
try:
|
||||
text_splitter = RecursiveCharacterTextSplitter(
|
||||
chunk_size=knowledge.chunk_size,
|
||||
chunk_overlap=knowledge.chunk_overlap,
|
||||
)
|
||||
chunks = text_splitter.split_text(data.content)
|
||||
|
||||
if settings.OPENAI_API_KEY and settings.OPENAI_BASE_URL:
|
||||
try:
|
||||
embeddings = OpenAIEmbeddings(
|
||||
model=settings.OPENAI_MODEL,
|
||||
)
|
||||
|
||||
chunk_embeddings = await embeddings.aembed_documents(chunks)
|
||||
|
||||
ids = []
|
||||
documents = []
|
||||
metadatas = []
|
||||
for i, chunk in enumerate(chunks):
|
||||
chunk_id = f"{obj.id}_chunk_{i}"
|
||||
ids.append(chunk_id)
|
||||
documents.append(chunk)
|
||||
metadatas.append(
|
||||
{
|
||||
"document_id": obj.id,
|
||||
"knowledge_id": obj.knowledge_id,
|
||||
"title": obj.title,
|
||||
"chunk_index": i,
|
||||
}
|
||||
)
|
||||
|
||||
chroma_manager.add_documents(
|
||||
ids=ids,
|
||||
embeddings=chunk_embeddings,
|
||||
documents=documents,
|
||||
metadatas=metadatas,
|
||||
)
|
||||
|
||||
update_data = KnowledgeDocumentUpdateSchema.model_construct(
|
||||
chunk_count=len(chunks), is_indexed=True
|
||||
)
|
||||
except Exception as e:
|
||||
log.warning(f"嵌入生成失败,使用虚拟嵌入: {e!s}")
|
||||
chunk_embeddings = [[0.0] * 1536 for _ in chunks]
|
||||
|
||||
ids = []
|
||||
documents = []
|
||||
metadatas = []
|
||||
for i, chunk in enumerate(chunks):
|
||||
chunk_id = f"{obj.id}_chunk_{i}"
|
||||
ids.append(chunk_id)
|
||||
documents.append(chunk)
|
||||
metadatas.append(
|
||||
{
|
||||
"document_id": obj.id,
|
||||
"knowledge_id": obj.knowledge_id,
|
||||
"title": obj.title,
|
||||
"chunk_index": i,
|
||||
}
|
||||
)
|
||||
|
||||
chroma_manager.add_documents(
|
||||
ids=ids,
|
||||
embeddings=chunk_embeddings,
|
||||
documents=documents,
|
||||
metadatas=metadatas,
|
||||
)
|
||||
|
||||
update_data = KnowledgeDocumentUpdateSchema.model_construct(
|
||||
chunk_count=len(chunks), is_indexed=True
|
||||
)
|
||||
else:
|
||||
log.info("未配置嵌入模型,跳过向量索引")
|
||||
update_data = KnowledgeDocumentUpdateSchema.model_construct(
|
||||
chunk_count=len(chunks), is_indexed=False
|
||||
)
|
||||
|
||||
await KnowledgeDocumentCRUD(auth).update_crud(id=obj.id, data=update_data)
|
||||
|
||||
updated_obj = await KnowledgeDocumentCRUD(auth).get_by_id_crud(id=obj.id)
|
||||
return KnowledgeDocumentOutSchema.model_validate(updated_obj).model_dump()
|
||||
except CustomException:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"创建知识库文档时发生错误: {e!s}")
|
||||
raise CustomException(msg=f"创建知识库文档失败: {e!s}")
|
||||
|
||||
@classmethod
|
||||
async def document_update_service(
|
||||
cls, auth: AuthSchema, id: int, data: KnowledgeDocumentUpdateSchema
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
更新知识库文档
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- id (int): 文档ID
|
||||
- data (KnowledgeDocumentUpdateSchema): 更新文档模型
|
||||
|
||||
返回:
|
||||
- dict[str, Any]: 更新的文档详情字典
|
||||
"""
|
||||
obj = await KnowledgeDocumentCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="更新失败,该数据不存在")
|
||||
|
||||
content_changed = data.content is not None and data.content != obj.content
|
||||
|
||||
if content_changed:
|
||||
chunk_ids = [f"{obj.id}_chunk_{i}" for i in range(obj.chunk_count or 0)]
|
||||
chroma_manager.delete_documents(ids=chunk_ids)
|
||||
|
||||
obj = await KnowledgeDocumentCRUD(auth).update_crud(id=id, data=data)
|
||||
if not obj:
|
||||
raise CustomException(msg="更新文档失败")
|
||||
|
||||
if content_changed and obj.content:
|
||||
knowledge = await KnowledgeCRUD(auth).get_by_id_crud(id=obj.knowledge_id)
|
||||
if knowledge:
|
||||
text_splitter = RecursiveCharacterTextSplitter(
|
||||
chunk_size=knowledge.chunk_size,
|
||||
chunk_overlap=knowledge.chunk_overlap,
|
||||
)
|
||||
chunks = text_splitter.split_text(obj.content)
|
||||
|
||||
if settings.OPENAI_API_KEY and settings.OPENAI_BASE_URL:
|
||||
try:
|
||||
embeddings = OpenAIEmbeddings(
|
||||
api_key=lambda: settings.OPENAI_API_KEY,
|
||||
base_url=settings.OPENAI_BASE_URL,
|
||||
)
|
||||
|
||||
chunk_embeddings = await embeddings.aembed_documents(chunks)
|
||||
|
||||
ids = []
|
||||
documents = []
|
||||
metadatas = []
|
||||
for i, chunk in enumerate(chunks):
|
||||
chunk_id = f"{obj.id}_chunk_{i}"
|
||||
ids.append(chunk_id)
|
||||
documents.append(chunk)
|
||||
metadatas.append(
|
||||
{
|
||||
"document_id": obj.id,
|
||||
"knowledge_id": obj.knowledge_id,
|
||||
"title": obj.title,
|
||||
"chunk_index": i,
|
||||
}
|
||||
)
|
||||
|
||||
chroma_manager.add_documents(
|
||||
ids=ids,
|
||||
embeddings=chunk_embeddings,
|
||||
documents=documents,
|
||||
metadatas=metadatas,
|
||||
)
|
||||
|
||||
update_data = KnowledgeDocumentUpdateSchema.model_construct(
|
||||
chunk_count=len(chunks), is_indexed=True
|
||||
)
|
||||
except Exception as e:
|
||||
log.warning(f"嵌入生成失败,跳过向量索引: {e!s}")
|
||||
update_data = KnowledgeDocumentUpdateSchema.model_construct(
|
||||
chunk_count=len(chunks), is_indexed=False
|
||||
)
|
||||
else:
|
||||
log.info("未配置嵌入模型,跳过向量索引")
|
||||
update_data = KnowledgeDocumentUpdateSchema.model_construct(
|
||||
chunk_count=len(chunks), is_indexed=False
|
||||
)
|
||||
|
||||
obj = await KnowledgeDocumentCRUD(auth).update_crud(id=obj.id, data=update_data)
|
||||
|
||||
return KnowledgeDocumentOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def document_delete_service(cls, auth: AuthSchema, ids: list[int]) -> None:
|
||||
"""
|
||||
批量删除知识库文档
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
- ids (list[int]): 文档ID列表
|
||||
|
||||
返回:
|
||||
- None
|
||||
"""
|
||||
if len(ids) < 1:
|
||||
raise CustomException(msg="删除失败,删除对象不能为空")
|
||||
|
||||
chunk_ids = []
|
||||
for id in ids:
|
||||
obj = await KnowledgeDocumentCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="删除失败,该数据不存在")
|
||||
if obj.chunk_count:
|
||||
chunk_ids.extend([f"{obj.id}_chunk_{i}" for i in range(obj.chunk_count)])
|
||||
|
||||
if chunk_ids:
|
||||
chroma_manager.delete_documents(ids=chunk_ids)
|
||||
|
||||
await KnowledgeDocumentCRUD(auth).delete_crud(ids=ids)
|
||||
|
||||
@@ -4,12 +4,12 @@ from app.core.logger import log
|
||||
from app.core.router_class import OperationLogRoute
|
||||
|
||||
from .schema import ChatQuerySchema
|
||||
from .service import McpService
|
||||
from .service import AgentService
|
||||
|
||||
WS_AI = APIRouter(
|
||||
route_class=OperationLogRoute,
|
||||
prefix="/application/ai",
|
||||
tags=["MCP智能助手WebSocket"],
|
||||
tags=["智能助手WebSocket"],
|
||||
)
|
||||
|
||||
|
||||
@@ -20,15 +20,18 @@ async def websocket_chat_controller(
|
||||
"""
|
||||
WebSocket聊天接口
|
||||
|
||||
支持两种消息格式:
|
||||
1. 纯文本:直接发送消息内容
|
||||
2. JSON格式:{"message": "消息内容", "knowledge_ids": [1, 2], "agent_config_id": 1}
|
||||
|
||||
ws://127.0.0.1:8001/api/v1/application/ai/ws
|
||||
"""
|
||||
await websocket.accept()
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
# 流式发送响应
|
||||
try:
|
||||
async for chunk in McpService.chat_query(query=ChatQuerySchema(message=data)):
|
||||
async for chunk in AgentService.chat_query(query=ChatQuerySchema(message=data, knowledge_ids=[], agent_config_id=None)):
|
||||
if chunk:
|
||||
await websocket.send_text(chunk)
|
||||
except Exception as e:
|
||||
@@ -38,7 +41,6 @@ async def websocket_chat_controller(
|
||||
log.error(f"WebSocket聊天出错: {e!s}")
|
||||
finally:
|
||||
try:
|
||||
# 检查WebSocket连接状态,避免重复关闭已关闭的连接
|
||||
if websocket.client_state != websocket.client_state.DISCONNECTED:
|
||||
await websocket.close()
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user