From 691385fb015bd782a60ae25ea0817b0bd63d7fa0 Mon Sep 17 00:00:00 2001 From: z30057876 Date: Wed, 22 Oct 2025 14:52:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0LLM=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/models/llm.py | 2 + apps/routers/llm.py | 21 +++++++++- apps/routers/user.py | 41 +----------------- apps/scheduler/call/core.py | 13 +++++- apps/schemas/request_data.py | 1 + apps/schemas/response_data.py | 45 ++++++++++++-------- apps/services/llm.py | 78 +++++++++++++++++++++++------------ 7 files changed, 114 insertions(+), 87 deletions(-) diff --git a/apps/models/llm.py b/apps/models/llm.py index cb7324f7..c953f42c 100644 --- a/apps/models/llm.py +++ b/apps/models/llm.py @@ -63,6 +63,8 @@ class LLMData(Base): ) extraConfig: Mapped[dict[str, Any]] = mapped_column(JSONB, default_factory=dict, nullable=False) # noqa: N815 """大模型API类型""" + llmDescription: Mapped[str] = mapped_column(String(2000), default="", nullable=False) # noqa: N815 + """大模型描述""" createdAt: Mapped[DateTime] = mapped_column( # noqa: N815 DateTime, default_factory=lambda: datetime.now(tz=UTC), diff --git a/apps/routers/llm.py b/apps/routers/llm.py index ab9cf0d9..dea8e9cc 100644 --- a/apps/routers/llm.py +++ b/apps/routers/llm.py @@ -9,6 +9,7 @@ from apps.schemas.request_data import ( UpdateLLMReq, ) from apps.schemas.response_data import ( + ListLLMAdminRsp, ListLLMRsp, ResponseData, ) @@ -33,12 +34,12 @@ admin_router = APIRouter( ) -@router.get("", response_model=ListLLMRsp, +@router.get("/provider", response_model=ListLLMRsp, responses={status.HTTP_404_NOT_FOUND: {"model": ResponseData}}, ) async def list_llm(llmId: str | None = None) -> JSONResponse: # noqa: N803 """GET /llm: 获取大模型列表""" - llm_list = await LLMManager.list_llm(llmId) + llm_list = await LLMManager.list_provider(llmId) return JSONResponse( status_code=status.HTTP_200_OK, content=ListLLMRsp( @@ -49,6 +50,22 @@ async def list_llm(llmId: str | None = None) -> JSONResponse: # noqa: N803 ) +@admin_router.get("/config", response_model=ListLLMAdminRsp, + responses={status.HTTP_404_NOT_FOUND: {"model": ResponseData}}, +) +async def admin_list_llm(llmId: str | None = None) -> JSONResponse: # noqa: N803 + """GET /llm/config: 获取大模型配置列表(管理员视图)""" + llm_list = await LLMManager.list_llm(llmId) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=ListLLMAdminRsp( + code=status.HTTP_200_OK, + message="success", + result=llm_list, + ).model_dump(exclude_none=True, by_alias=True), + ) + + @admin_router.put("", responses={status.HTTP_404_NOT_FOUND: {"model": ResponseData}}, ) diff --git a/apps/routers/user.py b/apps/routers/user.py index 7316ffb2..67fa0b34 100644 --- a/apps/routers/user.py +++ b/apps/routers/user.py @@ -5,11 +5,10 @@ from fastapi import APIRouter, Depends, Request, status from fastapi.responses import JSONResponse from apps.dependency import verify_personal_token, verify_session -from apps.schemas.request_data import UpdateUserSelectedLLMReq, UserUpdateRequest -from apps.schemas.response_data import ResponseData, UserGetMsp, UserGetRsp, UserSelectedLLMData +from apps.schemas.request_data import UserUpdateRequest +from apps.schemas.response_data import ResponseData, UserGetMsp, UserGetRsp from apps.schemas.tag import UserTagListResponse from apps.schemas.user import UserInfo -from apps.services.llm import LLMManager from apps.services.user import UserManager from apps.services.user_tag import UserTagManager @@ -58,42 +57,6 @@ async def list_user( ) -@router.put("/llm", - responses={status.HTTP_404_NOT_FOUND: {"model": ResponseData}}, -) -async def update_user_llm( - request: Request, - llm_request: UpdateUserSelectedLLMReq, -) -> JSONResponse: - """更新用户所选的EmbeddingLLM和FunctionLLM""" - try: - await LLMManager.update_user_selected_llm(request.state.user_sub, llm_request) - - # 返回更新后的LLM配置 - result_data = UserSelectedLLMData.model_validate({ - "functionLLM": llm_request.functionLLM, - "embeddingLLM": llm_request.embeddingLLM, - }) - - return JSONResponse( - status_code=status.HTTP_200_OK, - content=ResponseData( - code=status.HTTP_200_OK, - message="用户LLM配置更新成功", - result=result_data, - ).model_dump(exclude_none=True, by_alias=True), - ) - except ValueError as e: - return JSONResponse( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content=ResponseData( - code=status.HTTP_500_INTERNAL_SERVER_ERROR, - message=str(e), - result=None, - ).model_dump(exclude_none=True, by_alias=True), - ) - - @router.get("/tag", responses={status.HTTP_404_NOT_FOUND: {"model": ResponseData}}, ) diff --git a/apps/scheduler/call/core.py b/apps/scheduler/call/core.py index cf116203..dffe3962 100644 --- a/apps/scheduler/call/core.py +++ b/apps/scheduler/call/core.py @@ -188,8 +188,19 @@ class CoreCall(BaseModel): async def _llm(self, messages: list[dict[str, Any]], *, streaming: bool = False) -> AsyncGenerator[str, None]: """Call可直接使用的LLM非流式调用""" + think_tag_opened = False async for chunk in self._llm_obj.reasoning.call(messages, streaming=streaming): - yield chunk.content or "" + if chunk.reasoning_content: + if not think_tag_opened: + yield "" + think_tag_opened = True + yield chunk.reasoning_content + + if chunk.content: + if think_tag_opened: + yield "" + think_tag_opened = False + yield chunk.content async def _json(self, messages: list[dict[str, Any]], schema: dict[str, Any]) -> dict[str, Any]: diff --git a/apps/schemas/request_data.py b/apps/schemas/request_data.py index 7d1916b8..c644b62b 100644 --- a/apps/schemas/request_data.py +++ b/apps/schemas/request_data.py @@ -58,6 +58,7 @@ class UpdateLLMReq(BaseModel): max_tokens: int = Field(default=8192, description="最大token数", alias="maxTokens") provider: LLMProvider = Field(description="大模型提供商", alias="provider") ctx_length: int = Field(description="上下文长度", alias="ctxLength") + llm_description: str = Field(default="", description="大模型描述", alias="llmDescription") extra_data: dict[str, Any] | None = Field(default=None, description="额外数据", alias="extraData") diff --git a/apps/schemas/response_data.py b/apps/schemas/response_data.py index cdf370f2..c9bf04a2 100644 --- a/apps/schemas/response_data.py +++ b/apps/schemas/response_data.py @@ -6,6 +6,8 @@ from typing import Any from pydantic import BaseModel, Field +from apps.models import LLMType + from .parameters import ( BoolOperate, DictOperate, @@ -102,19 +104,10 @@ class LLMProviderInfo(BaseModel): """LLM数据结构""" llm_id: str = Field(alias="llmId", description="LLM ID") - openai_base_url: str = Field( - default="https://api.openai.com/v1", - description="OpenAI API Base URL", - alias="openaiBaseUrl", - ) - openai_api_key: str = Field( - description="OpenAI API Key", - alias="openaiApiKey", - default="", - ) + llm_description: str = Field(default="", alias="llmDescription", description="LLM描述") + llm_type: list[LLMType] = Field(default=[], alias="llmType", description="LLM类型") model_name: str = Field(description="模型名称", alias="modelName") max_tokens: int | None = Field(default=None, description="最大token数", alias="maxTokens") - is_editable: bool = Field(default=True, description="是否可编辑", alias="isEditable") class ListLLMRsp(ResponseData): @@ -123,6 +116,28 @@ class ListLLMRsp(ResponseData): result: list[LLMProviderInfo] = Field(default=[], title="Result") +class LLMAdminInfo(BaseModel): + """LLM管理员视图数据结构""" + + llm_id: str = Field(alias="llmId", description="LLM ID") + llm_description: str = Field(default="", alias="llmDescription", description="LLM描述") + llm_type: list[LLMType] = Field(default=[], alias="llmType", description="LLM类型") + base_url: str = Field(alias="baseUrl", description="API Base URL") + api_key: str = Field(alias="apiKey", description="API Key") + model_name: str = Field(alias="modelName", description="模型名称") + max_tokens: int = Field(alias="maxTokens", description="最大token数") + ctx_length: int = Field(alias="ctxLength", description="上下文长度") + temperature: float = Field(default=0.7, description="温度") + provider: str | None = Field(default=None, description="提供商") + extra_config: dict[str, Any] = Field(default_factory=dict, alias="extraConfig", description="额外配置") + + +class ListLLMAdminRsp(ResponseData): + """GET /api/llm/config 返回数据结构""" + + result: list[LLMAdminInfo] = Field(default=[], title="Result") + + class ParamsNode(BaseModel): """参数数据结构""" @@ -164,14 +179,8 @@ class GetOperaRsp(ResponseData): result: list[OperateAndBindType] = Field(..., title="Result") -class UserSelectedLLMData(BaseModel): +class SelectedSpecialLLMID(BaseModel): """用户选择的LLM数据结构""" functionLLM: str | None = Field(default=None, description="函数模型ID") # noqa: N815 embeddingLLM: str | None = Field(default=None, description="向量模型ID") # noqa: N815 - - -class UserSelectedLLMRsp(ResponseData): - """GET /api/user/llm 返回数据结构""" - - result: UserSelectedLLMData = Field(..., title="Result") diff --git a/apps/services/llm.py b/apps/services/llm.py index f4c6b423..c0b52ffd 100644 --- a/apps/services/llm.py +++ b/apps/services/llm.py @@ -14,8 +14,9 @@ from apps.schemas.request_data import ( UpdateUserSelectedLLMReq, ) from apps.schemas.response_data import ( + LLMAdminInfo, LLMProviderInfo, - UserSelectedLLMData, + SelectedSpecialLLMID, ) logger = logging.getLogger(__name__) @@ -24,28 +25,6 @@ logger = logging.getLogger(__name__) class LLMManager: """大模型管理""" - @staticmethod - async def get_user_selected_llm(user_sub: str) -> UserSelectedLLMData | None: - """ - 通过用户ID获取大模型ID - - :param user_sub: 用户ID - :return: 大模型ID - """ - async with postgres.session() as session: - user = (await session.scalars( - select(User).where(User.userSub == user_sub), - )).one_or_none() - if not user: - logger.error("[LLMManager] 用户 %s 不存在", user_sub) - return None - - return UserSelectedLLMData( - functionLLM=user.functionLLM, - embeddingLLM=user.embeddingLLM, - ) - - @staticmethod async def get_llm(llm_id: str) -> LLMData | None: """ @@ -68,7 +47,7 @@ class LLMManager: @staticmethod - async def list_llm(llm_id: str | None) -> list[LLMProviderInfo]: + async def list_provider(llm_id: str | None) -> list[LLMProviderInfo]: """ 获取大模型列表 @@ -95,8 +74,8 @@ class LLMManager: for llm in llm_list: llm_item = LLMProviderInfo( llmId=llm.id, - openaiBaseUrl=llm.baseUrl, - openaiApiKey=llm.apiKey, + llmDescription=llm.llmDescription, + llmType=llm.llmType, modelName=llm.modelName, maxTokens=llm.maxToken, ) @@ -104,6 +83,49 @@ class LLMManager: return provider_list + @staticmethod + async def list_llm(llm_id: str | None) -> list[LLMAdminInfo]: + """ + 获取大模型数据列表(管理员视图) + + :param llm_id: 大模型ID + :return: 大模型管理信息列表 + """ + async with postgres.session() as session: + if llm_id: + llm_list = (await session.scalars( + select(LLMData).where( + LLMData.id == llm_id, + ), + )).all() + else: + llm_list = (await session.scalars( + select(LLMData), + )).all() + if not llm_list: + logger.error("[LLMManager] 无法找到大模型 %s", llm_id) + return [] + + # 构建管理员视图列表 + admin_list = [] + for llm in llm_list: + llm_item = LLMAdminInfo( + llmId=llm.id, + llmDescription=llm.llmDescription, + llmType=llm.llmType, + baseUrl=llm.baseUrl, + apiKey=llm.apiKey, + modelName=llm.modelName, + maxTokens=llm.maxToken, + ctxLength=llm.ctxLength, + temperature=llm.temperature, + provider=llm.provider.value if llm.provider else None, + extraConfig=llm.extraConfig, + ) + admin_list.append(llm_item) + return admin_list + + @staticmethod async def update_llm(llm_id: str, req: UpdateLLMReq) -> str: """ @@ -128,6 +150,7 @@ class LLMManager: llm.maxToken = req.max_tokens llm.provider = req.provider llm.ctxLength = req.ctx_length + llm.llmDescription = req.llm_description llm.extraConfig = req.extra_data or {} await session.commit() else: @@ -139,6 +162,7 @@ class LLMManager: maxToken=req.max_tokens, provider=req.provider, ctxLength=req.ctx_length, + llmDescription=req.llm_description, extraConfig=req.extra_data or {}, ) session.add(llm) @@ -182,7 +206,7 @@ class LLMManager: @staticmethod - async def update_user_selected_llm( + async def update_special_llm( user_sub: str, req: UpdateUserSelectedLLMReq, ) -> None: -- Gitee