diff --git a/.dockerignore b/.dockerignore index 728281bcc8fecf11d1730acc96ae73e7ee0e442a..9e7e8498cf2e15e959462f02614827ff289ec2e3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,4 +9,5 @@ Dockerfile .git/ deploy/ docs/ +docs_for_openEuler/ .DS_Store diff --git a/.gitignore b/.gitignore index 64798293e2ad5a361b966d4e57d8e78573747901..723b472786fe1e6d55de7947d77dccda1ab1cb87 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ apps/embedding logs .git-credentials .ruff_cache/ +config diff --git a/ChangeLog b/ChangeLog deleted file mode 100644 index 8b01da76a29b22f247b26e239234ce9a2555d30d..0000000000000000000000000000000000000000 --- a/ChangeLog +++ /dev/null @@ -1,3 +0,0 @@ -## [0.9.5] - 2025/04/15 - -- 增加可视化工作流编辑页面 diff --git a/Dockerfile-base b/Dockerfile-base index 3510709591d2f077f1060309dde7ea26ab9b7011..0dd94323db86e8e85fa341a9dc647951cdb136d1 100644 --- a/Dockerfile-base +++ b/Dockerfile-base @@ -9,7 +9,7 @@ RUN sed -i 's|repo.openeuler.org|mirrors.nju.edu.cn/openeuler|g' /etc/yum.repos. sed -i '/metalink/d' /etc/yum.repos.d/openEuler.repo && \ sed -i '/metadata_expire/d' /etc/yum.repos.d/openEuler.repo && \ yum update -y &&\ - yum install -y python3 python3-pip shadow-utils findutils &&\ + yum install -y python3 python3-pip shadow-utils findutils git nodejs npm &&\ groupadd -g 1001 eulercopilot && useradd -u 1001 -g eulercopilot eulercopilot &&\ yum clean all diff --git a/README.md b/README.md index 66fecbc36e50e45262126d8abf4c196b081f38f8..fac2bd6f5567e6a9885f66b7778da7869f3443c6 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# EulerCopilot 大模型应用平台 +# openEuler 智能化解决方案 #### 介绍 -EulerCopilot 是一款基于openEuler原生os构建的大模型应用平台,主要有以下几个核心功能: +openEuler智能化解决方案(openEuler Intelligence) 是一个基于openEuler操作系统的大模型应用平台,主要有以下几个核心功能: - 多路增强RAG:单路->多路,检索后综合优选,回答更准确40%->90%; - 知识库管理:可视化、全流程覆盖、多用户空间保障数据隐私安全; diff --git a/apps/common/__init__.py b/apps/common/__init__.py index 6dfaec20b8a2672e82a6e0a41166b299c55a085b..a8b5806d99daa2b0f3f798f71d6c9b05b5d67e04 100644 --- a/apps/common/__init__.py +++ b/apps/common/__init__.py @@ -1,5 +1,2 @@ -""" -Framework 公共模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Framework 公共模块""" diff --git a/apps/common/config.py b/apps/common/config.py index 67c1a3a65ae2ac91a2df8137198d6b3ccb08f1fe..f5ced71c8523a735626445e4e8cb4222dab722a0 100644 --- a/apps/common/config.py +++ b/apps/common/config.py @@ -1,8 +1,6 @@ -""" -配置文件处理模块 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""配置文件处理模块""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" import os from copy import deepcopy from pathlib import Path @@ -22,7 +20,7 @@ class Config(metaclass=SingletonMeta): """读取配置文件;当PROD环境变量设置时,配置文件将在读取后删除""" config_file = os.getenv("CONFIG") if config_file is None: - config_file = "./config/config.toml" + config_file = Path(__file__).parents[2] / "config" / "config.toml" self._config = ConfigModel.model_validate(toml.load(config_file)) if os.getenv("PROD"): diff --git a/apps/common/cryptohub.py b/apps/common/cryptohub.py index 24c49a2894c0884f86aba48c92a1348f4de9caf8..e98ea2b81f58ea5019d6f42f18d90fb6ea8f41fc 100644 --- a/apps/common/cryptohub.py +++ b/apps/common/cryptohub.py @@ -1,8 +1,5 @@ -""" -加密解密模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""加密解密模块""" import hashlib diff --git a/apps/common/oidc.py b/apps/common/oidc.py index f4df6bdd3cc33184b3f6f7edd30f355c2171cd61..d57b57ec8eb1f9dc1a64a13ef4c749c9654b784a 100644 --- a/apps/common/oidc.py +++ b/apps/common/oidc.py @@ -1,8 +1,6 @@ -""" -OIDC模块 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""OIDC模块""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" import logging from datetime import UTC, datetime, timedelta from typing import Any @@ -33,7 +31,8 @@ class OIDCProvider: @staticmethod async def set_token(user_sub: str, access_token: str, refresh_token: str) -> None: """设置MongoDB中的OIDC Token到sessions集合""" - sessions_collection = MongoDB.get_collection("session") + mongo = MongoDB() + sessions_collection = mongo.get_collection("session") try: await sessions_collection.update_one( diff --git a/apps/common/oidc_provider/authhub.py b/apps/common/oidc_provider/authhub.py index f893a9da467726f83186004e376d4da463657daf..2301f4f2496a929be0ee4593285cbdebaaacf664 100644 --- a/apps/common/oidc_provider/authhub.py +++ b/apps/common/oidc_provider/authhub.py @@ -1,9 +1,10 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """Authhub OIDC Provider""" import logging from typing import Any -import aiohttp +import httpx from fastapi import status from apps.common.config import Config @@ -41,15 +42,18 @@ class AuthhubOIDCProvider(OIDCProviderBase): } url = await cls.get_access_token_url() result = None - async with ( - aiohttp.ClientSession() as session, - session.post(url, headers=headers, json=data, timeout=aiohttp.ClientTimeout(total=10)) as resp, - ): - if resp.status != status.HTTP_200_OK: - err = f"[Authhub] 获取OIDC Token失败: {resp.status},完整输出: {await resp.text()}" + async with httpx.AsyncClient() as client: + resp = await client.post( + url, + headers=headers, + json=data, + timeout=10, + ) + if resp.status_code != status.HTTP_200_OK: + err = f"[Authhub] 获取OIDC Token失败: {resp.status_code},完整输出: {resp.text}" raise RuntimeError(err) - logger.info("[Authhub] 获取OIDC Token成功: %s", await resp.text()) - result = await resp.json() + logger.info("[Authhub] 获取OIDC Token成功: %s", resp.text) + result = resp.json() return { "access_token": result["data"]["access_token"], "refresh_token": result["data"]["refresh_token"], @@ -72,15 +76,18 @@ class AuthhubOIDCProvider(OIDCProviderBase): "client_id": login_config.app_id, } result = None - async with ( - aiohttp.ClientSession() as session, - session.post(url, headers=headers, json=data, timeout=aiohttp.ClientTimeout(total=10)) as resp, - ): - if resp.status != status.HTTP_200_OK: - err = f"[Authhub] 获取用户信息失败: {resp.status},完整输出: {await resp.text()}" + async with httpx.AsyncClient() as client: + resp = await client.post( + url, + headers=headers, + json=data, + timeout=10, + ) + if resp.status_code != status.HTTP_200_OK: + err = f"[Authhub] 获取用户信息失败: {resp.status_code},完整输出: {resp.text}" raise RuntimeError(err) - logger.info("[Authhub] 获取用户信息成功: %s", await resp.text()) - result = await resp.json() + logger.info("[Authhub] 获取用户信息成功: %s", resp.text) + result = resp.json() return { "user_sub": result["data"], @@ -98,20 +105,18 @@ class AuthhubOIDCProvider(OIDCProviderBase): "Content-Type": "application/json", } url = login_config.host_inner.rstrip("/") + "/oauth2/login-status" - async with ( - aiohttp.ClientSession() as session, - session.post( + async with httpx.AsyncClient() as client: + resp = await client.post( url, headers=headers, json=data, cookies=cookie, - timeout=aiohttp.ClientTimeout(total=10), - ) as resp, - ): - if resp.status != status.HTTP_200_OK: - err = f"[Authhub] 获取登录状态失败: {resp.status},完整输出: {await resp.text()}" + timeout=10, + ) + if resp.status_code != status.HTTP_200_OK: + err = f"[Authhub] 获取登录状态失败: {resp.status_code},完整输出: {resp.text}" raise RuntimeError(err) - result = await resp.json() + result = resp.json() return { "access_token": result["data"]["access_token"], "refresh_token": result["data"]["refresh_token"], @@ -126,12 +131,15 @@ class AuthhubOIDCProvider(OIDCProviderBase): "Content-Type": "application/json", } url = login_config.host_inner.rstrip("/") + "/oauth2/logout" - async with ( - aiohttp.ClientSession() as session, - session.get(url, headers=headers, cookies=cookie, timeout=aiohttp.ClientTimeout(total=10)) as resp, - ): - if resp.status != status.HTTP_200_OK: - err = f"[Authhub] 登出失败: {resp.status},完整输出: {await resp.text()}" + async with httpx.AsyncClient() as client: + resp = await client.get( + url, + headers=headers, + cookies=cookie, + timeout=10, + ) + if resp.status_code != status.HTTP_200_OK: + err = f"[Authhub] 登出失败: {resp.status_code},完整输出: {resp.text}" raise RuntimeError(err) @classmethod diff --git a/apps/common/oidc_provider/base.py b/apps/common/oidc_provider/base.py index f05255c5c28746203cdf8894f5b758f37bb1d4d7..aea39e4df09c02be9c536e5bbcdf761250d9992c 100644 --- a/apps/common/oidc_provider/base.py +++ b/apps/common/oidc_provider/base.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """OIDC Provider Base""" from typing import Any diff --git a/apps/common/oidc_provider/openeuler.py b/apps/common/oidc_provider/openeuler.py index 278bac40d2af663510e2cac1d6f3e4ed90b9a5c4..28e938f128e4db50efb8d40cc3498f560757fe60 100644 --- a/apps/common/oidc_provider/openeuler.py +++ b/apps/common/oidc_provider/openeuler.py @@ -1,9 +1,10 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """OpenEuler OIDC Provider""" import logging from typing import Any -import aiohttp +import httpx from fastapi import status from apps.common.config import Config @@ -39,19 +40,22 @@ class OpenEulerOIDCProvider(OIDCProviderBase): "code": code, } url = await cls.get_access_token_url() - headers = { - "Content-Type": "application/x-www-form-urlencoded", - } - result = None - async with ( - aiohttp.ClientSession() as session, - session.post(url, headers=headers, data=data, timeout=aiohttp.ClientTimeout(total=10)) as resp, - ): - if resp.status != status.HTTP_200_OK: - err = f"[OpenEuler] 获取OIDC Token失败: {resp.status},完整输出: {await resp.text()}" + + async with httpx.AsyncClient() as client: + resp = await client.post( + url, + headers={ + "Content-Type": "application/x-www-form-urlencoded", + }, + data=data, + timeout=10.0, + ) + if resp.status_code != status.HTTP_200_OK: + err = f"[OpenEuler] 获取OIDC Token失败: {resp.status_code},完整输出: {resp.text}" raise RuntimeError(err) - logger.info("[OpenEuler] 获取OIDC Token成功: %s", await resp.text()) - result = await resp.json() + logger.info("[OpenEuler] 获取OIDC Token成功: %s", resp.text) + result = resp.json() + return { "access_token": result["access_token"], "refresh_token": result["refresh_token"], @@ -67,20 +71,20 @@ class OpenEulerOIDCProvider(OIDCProviderBase): err = "Access token is empty." raise RuntimeError(err) url = login_config.host_inner.rstrip("/") + "/oneid/oidc/user" - headers = { - "Authorization": access_token, - } - result = None - async with ( - aiohttp.ClientSession() as session, - session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=10)) as resp, - ): - if resp.status != status.HTTP_200_OK: - err = f"[OpenEuler] 获取OIDC用户失败: {resp.status},完整输出: {await resp.text()}" + async with httpx.AsyncClient() as client: + resp = await client.get( + url, + headers={ + "Authorization": access_token, + }, + timeout=10.0, + ) + if resp.status_code != status.HTTP_200_OK: + err = f"[OpenEuler] 获取OIDC用户失败: {resp.status_code},完整输出: {resp.text}" raise RuntimeError(err) - logger.info("[OpenEuler] 获取OIDC用户成功: %s", await resp.text()) - result = await resp.json() + logger.info("[OpenEuler] 获取OIDC用户成功: %s", resp.text) + result = resp.json() if not result["phone_number_verified"]: err = "Could not validate credentials." diff --git a/apps/common/queue.py b/apps/common/queue.py index ba1fe3ed80b6be3943dfb5de0c83dca16f58bf04..8b481efeb9696fe3bc38b50422b6726dcb57b8b7 100644 --- a/apps/common/queue.py +++ b/apps/common/queue.py @@ -1,4 +1,6 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """消息队列模块""" + import asyncio import json import logging diff --git a/apps/common/security.py b/apps/common/security.py index 8955c4bd96974b6cc67c9d17a49f180a28e0ee0d..7bae582e69b8e835df84033a416f53c91c90b73e 100644 --- a/apps/common/security.py +++ b/apps/common/security.py @@ -1,8 +1,5 @@ -""" -密文加密解密模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""密文加密解密模块""" import base64 import binascii diff --git a/apps/common/singleton.py b/apps/common/singleton.py index 6f9ed5599f0a05b5d69926e81c13bb1beecf32da..673bbd4905248ed5871af3e4c828ab5645036fea 100644 --- a/apps/common/singleton.py +++ b/apps/common/singleton.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """单例模式""" import threading @@ -8,7 +9,9 @@ class SingletonMeta(type): """单例元类""" _instances: ClassVar[dict[type, Any]] = {} - _lock: ClassVar[threading.Lock] = threading.Lock() + """单例实例字典""" + _lock: ClassVar[threading.RLock] = threading.RLock() + """可重入锁""" def __call__(cls, *args, **kwargs): # noqa: ANN002, ANN003, ANN204 """获取单例""" @@ -16,4 +19,4 @@ class SingletonMeta(type): if cls not in cls._instances: instance = super().__call__(*args, **kwargs) cls._instances[cls] = instance - return cls._instances[cls] + return cls._instances[cls] diff --git a/apps/common/wordscheck.py b/apps/common/wordscheck.py index 80ea7d31c8447d0a38ef1197d754dcd84403f2b3..67d87c9a1d837d645067f679dd27f7ff67171368 100644 --- a/apps/common/wordscheck.py +++ b/apps/common/wordscheck.py @@ -1,8 +1,5 @@ -""" -敏感词检查模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""敏感词检查模块""" import logging from pathlib import Path diff --git a/apps/constants.py b/apps/constants.py index ca5421612c1682187f92785d3e677814d6a183ab..27e3e92aa82c5fa57c86e0bec211488188fd46e4 100644 --- a/apps/constants.py +++ b/apps/constants.py @@ -18,10 +18,15 @@ SLIDE_WINDOW_QUESTION_COUNT = 10 MAX_API_RESPONSE_LENGTH = 8192 # Executor最大步骤历史数 STEP_HISTORY_SIZE = 3 - +# Session时间,单位为分钟 +SESSION_TTL = 30 * 24 * 60 +# JSON生成最大尝试次数 +JSON_GEN_MAX_TRIAL = 3 +# 推理开始标记 REASONING_BEGIN_TOKEN = [ "", ] +# 推理结束标记 REASONING_END_TOKEN = [ "", ] diff --git a/apps/dependency/__init__.py b/apps/dependency/__init__.py index 824f0b4369a2b95697b53f786ec821362b00473d..83c26ea2f11c0fdbaf704a9fdcd91b0eacb57825 100644 --- a/apps/dependency/__init__.py +++ b/apps/dependency/__init__.py @@ -1,11 +1,6 @@ -""" -FastAPI 依赖注入模块 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI 依赖注入模块""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" - -from apps.dependency.csrf import verify_csrf_token -from apps.dependency.session import VerifySessionMiddleware from apps.dependency.user import ( get_session, get_user, @@ -15,11 +10,9 @@ from apps.dependency.user import ( ) __all__ = [ - "VerifySessionMiddleware", "get_session", "get_user", "get_user_by_api_key", "verify_api_key", - "verify_csrf_token", "verify_user", ] diff --git a/apps/dependency/csrf.py b/apps/dependency/csrf.py deleted file mode 100644 index fb747b5ede895863f2625d4ebb17cf50b1854a70..0000000000000000000000000000000000000000 --- a/apps/dependency/csrf.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -CSRF Token校验 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" - -from fastapi import HTTPException, Request, Response, status - -from apps.common.config import Config -from apps.manager.session import SessionManager - - -async def verify_csrf_token(request: Request, response: Response) -> Response | None: - """验证CSRF Token""" - if not Config().get_config().fastapi.csrf: - return None - - csrf_token = request.headers["x-csrf-token"].strip('"') - session = request.cookies["ECSESSION"] - - if not await SessionManager.verify_csrf_token(session, csrf_token): - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="CSRF token is invalid.") - - new_csrf_token = await SessionManager.create_csrf_token(session) - if not new_csrf_token: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Renew CSRF token failed.") - - if Config().get_config().deploy.cookie == "DEBUG": - response.set_cookie("_csrf_tk", new_csrf_token, max_age=Config().get_config().fastapi.session_ttl * 60, - domain=Config().get_config().fastapi.domain) - else: - response.set_cookie("_csrf_tk", new_csrf_token, max_age=Config().get_config().fastapi.session_ttl * 60, - secure=True, domain=Config().get_config().fastapi.domain, samesite="strict") - return response - diff --git a/apps/dependency/session.py b/apps/dependency/session.py deleted file mode 100644 index e83a6a97ebedc03cd5c3f1fb729ebcbadd5ce502..0000000000000000000000000000000000000000 --- a/apps/dependency/session.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -浏览器Session校验 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" - -from typing import Any - -from fastapi import Response -from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint -from starlette.requests import Request - -from apps.common.config import Config -from apps.manager.session import SessionManager - -BYPASS_LIST = [ - "/health_check", - "/api/auth/login", - "/api/auth/logout", -] - - -class VerifySessionMiddleware(BaseHTTPMiddleware): - """浏览器Session校验中间件""" - - def _check_bypass_list(self, path: str) -> bool: - """检查请求路径是否需要跳过验证""" - return path in BYPASS_LIST - - def _validate_client(self, request: Request) -> str: - """验证客户端信息并返回主机地址""" - if request.client is None or request.client.host is None: - err = "[VerifySessionMiddleware] 无法检测请求来源IP!" - raise ValueError(err) - return request.client.host - - def _update_cookie_header(self, request: Request, session_id: str) -> None: - """更新请求头中的cookie信息""" - cookie_str = "" - for item in request.scope["headers"]: - if item[0] == b"cookie": - cookie_str = item[1].decode() - request.scope["headers"].remove(item) - break - - all_cookies = "" - if cookie_str: - other_headers = cookie_str.split(";") - all_cookies = "; ".join(item for item in other_headers if "ECSESSION" not in item) - - all_cookies = f"{all_cookies}; ECSESSION={session_id}" if all_cookies else f"ECSESSION={session_id}" - request.scope["headers"].append((b"cookie", all_cookies.encode())) - - def _set_response_cookie(self, response: Response, session_id: str) -> None: - """设置响应cookie""" - # 检查 是否其他dependence 设置过cookie - if "ECSESSION" in response.headers.get("set-cookie", ""): - return - - cookie_params: dict[str, Any] = { - "key": "ECSESSION", - "value": session_id, - "domain": Config().get_config().fastapi.domain, - } - - if Config().get_config().deploy.cookie != "DEBUG": - cookie_params.update({ - "httponly": True, - "secure": True, - "samesite": "strict", - "max_age": Config().get_config().fastapi.session_ttl * 60, - }) - - response.set_cookie(**cookie_params) - - async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response: - """浏览器Session校验中间件""" - if self._check_bypass_list(request.url.path): - return await call_next(request) - - host = self._validate_client(request) - cookie = request.cookies.get("ECSESSION", "") - session_id = await SessionManager.get_session(cookie, host) - - if session_id != cookie: - self._update_cookie_header(request, session_id) - - response = await call_next(request) - self._set_response_cookie(response, session_id) - return response diff --git a/apps/dependency/user.py b/apps/dependency/user.py index 4699edb47ad19e968262075c8df56139de31fc11..d4d258fee5e969c3b72c4c08713cc0beb6d787f7 100644 --- a/apps/dependency/user.py +++ b/apps/dependency/user.py @@ -1,19 +1,14 @@ -""" -用户鉴权 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""用户鉴权""" import logging -from fastapi import Depends, Response +from fastapi import Depends from fastapi.security import OAuth2PasswordBearer from starlette import status from starlette.exceptions import HTTPException from starlette.requests import HTTPConnection -from apps.common.config import Config -from apps.common.oidc import oidc_provider from apps.manager.api_key import ApiKeyManager from apps.manager.session import SessionManager @@ -21,76 +16,29 @@ logger = logging.getLogger(__name__) oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") -async def _verify_oidc_auth(request: HTTPConnection, response: Response) -> str: +async def _get_session_id_from_request(request: HTTPConnection) -> str | None: """ - 验证OIDC认证状态并获取用户信息 + 从请求中获取 session_id :param request: HTTP请求 - :return: 用户信息字典 - :raises: HTTPException 当OIDC验证失败时 + :return: session_id """ - try: - tokens = await oidc_provider.get_login_status(request.cookies) - except Exception as err: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="[OIDC] 检查OIDC登录状态失败") from err - - if not tokens: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="[OIDC] 检查OIDC登录状态失败") - - try: - user_info = await oidc_provider.get_oidc_user(tokens["access_token"]) - except Exception as err: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="[OIDC] 获取用户信息失败") from err - - if not user_info: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="[OIDC] 获取用户信息失败") - - # 创建新的session - if request.client is None: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="[OIDC] 获取登录IP失败") - - user_sub = user_info["user_sub"] - user_host = request.client.host - try: - current_session = request.cookies["ECSESSION"] - await SessionManager.delete_session(current_session) - except Exception: - logger.exception("[VerifySessionMiddleware] 删除session失败") - - current_session = await SessionManager.create_session(user_host, user_sub) - - # 设置cookie - if Config().get_config().deploy.cookie == "DEBUG": - response.set_cookie( - "ECSESSION", - current_session, - ) - else: - response.set_cookie( - "ECSESSION", - current_session, - max_age=Config().get_config().fastapi.session_ttl * 60, - secure=True, - domain=Config().get_config().fastapi.domain, - httponly=True, - samesite="strict", - ) + session_id = None + auth_header = request.headers.get("Authorization") + if auth_header and auth_header.startswith("Bearer "): + session_id = auth_header.split(" ", 1)[1] - return user_sub + return session_id -async def verify_user(request: HTTPConnection, response: Response) -> None: +async def verify_user(request: HTTPConnection) -> None: """ 验证Session是否已鉴权;未鉴权则抛出HTTP 401;接口级dependence :param request: HTTP请求 :return: None """ - session_id = request.cookies["ECSESSION"] - if await SessionManager.verify_user(session_id): - return - - await _verify_oidc_auth(request, response) + request.state.session_id = await get_session(request) async def get_session(request: HTTPConnection) -> str: @@ -100,26 +48,44 @@ async def get_session(request: HTTPConnection) -> str: :param request: HTTP请求 :return: Session ID """ - session_id = request.cookies["ECSESSION"] + session_id = await _get_session_id_from_request(request) + if not session_id: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Session ID 不存在", + ) if not await SessionManager.verify_user(session_id): - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication Error.") + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Session ID 鉴权失败", + ) return session_id -async def get_user(request: HTTPConnection, response: Response) -> str: +async def get_user(request: HTTPConnection) -> str: """ 验证Session是否已鉴权;若已鉴权,查询对应的user_sub;若未鉴权,抛出HTTP 401;参数级dependence :param request: HTTP请求体 :return: 用户sub """ - session_id = request.cookies["ECSESSION"] + session_id = await _get_session_id_from_request(request) + if not session_id: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Session ID 不存在", + ) - user = await SessionManager.get_user(session_id) - if user: - return user + user_sub = await SessionManager.get_user(session_id) + if not user_sub: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Session ID 鉴权失败", + ) - return await _verify_oidc_auth(request, response) + request.state.user_sub = user_sub + request.state.session_id = session_id + return user_sub async def verify_api_key(api_key: str = Depends(oauth2_scheme)) -> None: diff --git a/apps/entities/__init__.py b/apps/entities/__init__.py index eb970fbf3966fb8f4c9b4b801ffc0582fe8fb5ca..d44768392a78ae68e301f11652c231dd0ea027f6 100644 --- a/apps/entities/__init__.py +++ b/apps/entities/__init__.py @@ -1,5 +1,2 @@ -""" -Framework 数据结构定义 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Framework 数据结构定义""" diff --git a/apps/entities/agent.py b/apps/entities/agent.py new file mode 100644 index 0000000000000000000000000000000000000000..69426e90078aeaa6247fbfde64d40f4f4c7b6483 --- /dev/null +++ b/apps/entities/agent.py @@ -0,0 +1,23 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""agent 相关数据结构""" + +from pydantic import Field + +from apps.entities.enum_var import ( + AppType, + MetadataType, +) +from apps.entities.flow import Permission +from apps.entities.mcp import MCPMetadataBase + + +class AgentAppMetadata(MCPMetadataBase): + """智能体App的元数据""" + + type: MetadataType = MetadataType.APP + app_type: AppType = Field(default=AppType.AGENT, description="应用类型", frozen=True) + published: bool = Field(description="是否发布", default=False) + history_len: int = Field(description="对话轮次", default=3, le=10) + mcp_service: list[str] = Field(default=[], alias="mcpService", description="MCP服务id列表") + permission: Permission | None = Field(description="应用权限配置", default=None) + version: str = Field(description="元数据版本") diff --git a/apps/entities/api_key.py b/apps/entities/api_key.py index a2f2566da4895f7e56705e4f4d15c7dbc33b14c8..ccc6e2f4ee5b08d7e3735b4c0ad2bf8c89071651 100644 --- a/apps/entities/api_key.py +++ b/apps/entities/api_key.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """API密钥相关数据结构""" from pydantic import BaseModel diff --git a/apps/entities/appcenter.py b/apps/entities/appcenter.py index bf021c826b552ca64e7277fa52b311db0f0f03b6..1244f72525ab6de9c5d1e9246a9852e9bbbd7270 100644 --- a/apps/entities/appcenter.py +++ b/apps/entities/appcenter.py @@ -1,18 +1,16 @@ -""" -应用中心相关 API 基础数据结构定义 - -Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. +"""应用中心相关 API 基础数据结构定义""" from pydantic import BaseModel, Field -from apps.entities.enum_var import PermissionType +from apps.entities.enum_var import AppType, PermissionType class AppCenterCardItem(BaseModel): """应用中心卡片数据结构""" app_id: str = Field(..., alias="appId", description="应用ID") + app_type: AppType = Field(..., alias="appType", description="应用类型") icon: str = Field(..., description="应用图标") name: str = Field(..., description="应用名称") description: str = Field(..., description="应用简介") @@ -55,6 +53,7 @@ class AppFlowInfo(BaseModel): class AppData(BaseModel): """应用信息数据结构""" + app_type: AppType = Field(..., alias="appType", description="应用类型") icon: str = Field(default="", description="图标") name: str = Field(..., max_length=20, description="应用名称") description: str = Field(..., max_length=150, description="应用简介") @@ -65,3 +64,4 @@ class AppData(BaseModel): permission: AppPermissionData = Field( default_factory=lambda: AppPermissionData(authorizedUsers=None), description="权限配置") workflows: list[AppFlowInfo] = Field(default=[], description="工作流信息列表") + mcp_service: list[str] = Field(default=[], alias="mcpService", description="MCP服务id列表") diff --git a/apps/entities/collection.py b/apps/entities/collection.py index af0b5a88e128b2c40c6e86eb814a9889bde654aa..1d6aa097da435e611a794f3a7d93fa2da3f37861 100644 --- a/apps/entities/collection.py +++ b/apps/entities/collection.py @@ -1,15 +1,14 @@ -""" -MongoDB中的数据结构 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MongoDB中的数据结构""" import uuid from datetime import UTC, datetime from pydantic import BaseModel, Field +from apps.common.config import Config from apps.constants import NEW_CHAT +from apps.templates.generate_llm_operator_config import llm_provider_dict class Blacklist(BaseModel): @@ -56,12 +55,44 @@ class User(BaseModel): is_whitelisted: bool = False credit: int = 100 api_key: str | None = None - kb_id: str | None = None conversations: list[str] = [] domains: list[UserDomainData] = [] app_usage: dict[str, AppUsageData] = {} fav_apps: list[str] = [] fav_services: list[str] = [] + is_admin: bool = Field(default=False, description="是否为管理员") + + +class LLM(BaseModel): + """ + 大模型信息 + + Collection: llm + """ + + id: str = Field(default_factory=lambda: str(uuid.uuid4()), alias="_id") + user_sub: str = Field(default="", description="用户ID") + icon: str = Field(default=llm_provider_dict["ollama"]["icon"], description="图标") + openai_base_url: str = Field(default=Config().get_config().llm.endpoint) + openai_api_key: str = Field(default=Config().get_config().llm.key) + model_name: str = Field(default=Config().get_config().llm.model) + max_tokens: int | None = Field(default=Config().get_config().llm.max_tokens) + created_at: float = Field(default_factory=lambda: round(datetime.now(tz=UTC).timestamp(), 3)) + + +class LLMItem(BaseModel): + """大模型信息""" + + llm_id: str = Field(default="empty") + model_name: str = Field(default=Config().get_config().llm.model) + icon: str = Field(default=llm_provider_dict["ollama"]["icon"]) + + +class KnowledgeBaseItem(BaseModel): + """知识库信息""" + + kb_id: str + kb_name: str class Conversation(BaseModel): @@ -80,7 +111,9 @@ class Conversation(BaseModel): tasks: list[str] = [] unused_docs: list[str] = [] record_groups: list[str] = [] - debug : bool = Field(default=False) + debug: bool = Field(default=False) + llm: LLMItem | None = None + kb_list: list[KnowledgeBaseItem] = Field(default=[]) class Document(BaseModel): diff --git a/apps/entities/config.py b/apps/entities/config.py index bec3718e6fae8cf6f7d0be22449fafda204be311..b88a81f1afb018663713a3bf4c0b9b62436e59d4 100644 --- a/apps/entities/config.py +++ b/apps/entities/config.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """配置文件数据结构""" from typing import Literal @@ -55,8 +56,6 @@ class FastAPIConfig(BaseModel): """FastAPI配置""" domain: str = Field(description="当前实例的域名") - session_ttl: int = Field(description="用户需要刷新Token的间隔(min)", default=30) - csrf: bool = Field(description="是否启用CSRF Token功能", default=False) class MinioConfig(BaseModel): @@ -84,8 +83,8 @@ class LLMConfig(BaseModel): key: str = Field(description="LLM API密钥") endpoint: str = Field(description="LLM API URL地址") model: str = Field(description="LLM API 模型名") - max_tokens: int = Field(description="LLM API 最大Token数", default=8192) - temperature: float = Field(description="LLM API 温度", default=0.7) + max_tokens: int | None = Field(description="LLM API 最大Token数", default=None) + temperature: float | None = Field(description="LLM API 温度", default=None) class FunctionCallConfig(BaseModel): @@ -95,8 +94,8 @@ class FunctionCallConfig(BaseModel): model: str = Field(description="Function Call 模型名") endpoint: str = Field(description="Function Call API URL地址") api_key: str = Field(description="Function Call API密钥") - max_tokens: int = Field(description="Function Call 最大Token数", default=8192) - temperature: float = Field(description="Function Call 温度", default=0.7) + max_tokens: int | None = Field(description="Function Call 最大Token数", default=None) + temperature: float | None = Field(description="Function Call 温度", default=None) class SecurityConfig(BaseModel): diff --git a/apps/entities/enum_var.py b/apps/entities/enum_var.py index 281d53e07d3ecf9756bf7bf01ce28b1fad6a21b2..52a0bb163868873d381265ae67032d95661215e3 100644 --- a/apps/entities/enum_var.py +++ b/apps/entities/enum_var.py @@ -1,8 +1,5 @@ -""" -枚举类型 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""枚举类型""" from enum import Enum @@ -60,6 +57,7 @@ class MetadataType(str, Enum): SERVICE = "service" APP = "app" + MCP_SERVICE = "mcp_service" class EdgeType(str, Enum): @@ -155,3 +153,18 @@ class CommentType(str, Enum): LIKE = "liked" DISLIKE = "disliked" NONE = "none" + + +class MCPSearchType(str, Enum): + """搜索类型""" + + ALL = "all" + NAME = "name" + AUTHOR = "author" + + +class AppType(str, Enum): + """应用中心应用类型""" + + FLOW = "flow" + AGENT = "agent" diff --git a/apps/entities/flow.py b/apps/entities/flow.py index 22f278b5d7e8d71d5879e48e4c7b3bec9b3a6f50..8785b25eaf3d685f28dd6b41f4e90e2281f2e17d 100644 --- a/apps/entities/flow.py +++ b/apps/entities/flow.py @@ -1,8 +1,5 @@ -""" -App、Flow和Service等外置配置数据结构 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""App、Flow和Service等外置配置数据结构""" from typing import Any @@ -10,6 +7,7 @@ from pydantic import BaseModel, Field from apps.entities.appcenter import AppLink from apps.entities.enum_var import ( + AppType, EdgeType, MetadataType, PermissionType, @@ -134,6 +132,7 @@ class AppMetadata(MetadataBase): """App的元数据""" type: MetadataType = MetadataType.APP + app_type: AppType = Field(default=AppType.FLOW, description="应用类型", frozen=True) published: bool = Field(description="是否发布", default=False) links: list[AppLink] = Field(description="相关链接", default=[]) first_questions: list[str] = Field(description="首次提问", default=[]) diff --git a/apps/entities/flow_topology.py b/apps/entities/flow_topology.py index b547f09bf1b968665e53f7bab10f05656d0e292b..0c6463bb82f355ba05d475344683e970cfa46e00 100644 --- a/apps/entities/flow_topology.py +++ b/apps/entities/flow_topology.py @@ -1,8 +1,5 @@ -""" -前端展示flow用到的数据结构 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""前端展示flow用到的数据结构""" from typing import Any diff --git a/apps/entities/mcp.py b/apps/entities/mcp.py new file mode 100644 index 0000000000000000000000000000000000000000..252fd57db8cb3c75198ba2d5cb7bba2efdf0c2eb --- /dev/null +++ b/apps/entities/mcp.py @@ -0,0 +1,143 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MCP 相关数据结构""" + +from enum import Enum +from typing import Any + +from lancedb.pydantic import LanceModel, Vector +from pydantic import BaseModel, Field + +from apps.entities.enum_var import ( + MetadataType, +) + + +class MCPType(str, Enum): + """MCP 类型""" + + SSE = "sse" + STDIO = "stdio" + STREAMABLE = "stream" + + +class MCPMetadataBase(BaseModel): + """ + MCPService或MCPApp的元数据 + + 注意:hash字段在save和load的时候exclude + """ + + id: str = Field(description="元数据ID") + type: MetadataType = Field(description="元数据类型") + icon: str = Field(description="图标", default="") + name: str = Field(description="元数据名称") + description: str = Field(description="元数据描述") + author: str = Field(description="创建者的用户名") + hashes: dict[str, str] = Field(description="配置文件的hash值", default={}) + + +class MCPServerConfig(BaseModel): + """MCP 服务器配置""" + + name: str = Field(description="MCP 服务器自然语言名称", default="") + description: str = Field(description="MCP 服务器自然语言描述", default="") + type: MCPType = Field(description="MCP 服务器类型", default=MCPType.STDIO) + disabled: bool = Field(description="MCP 服务器是否禁用", default=False) + auto_install: bool = Field(description="是否自动安装MCP服务器", default=True, alias="autoInstall") + icon_path: str = Field(description="MCP 服务器图标路径", default="", alias="iconPath") + env: dict[str, str] = Field(description="MCP 服务器环境变量", default={}) + auto_approve: list[str] = Field(description="自动批准的MCP工具ID列表", default=[], alias="autoApprove") + + +class MCPServerStdioConfig(MCPServerConfig): + """MCP 服务器配置""" + + command: str = Field(description="MCP 服务器命令") + args: list[str] = Field(description="MCP 服务器命令参数") + + +class MCPServerSSEConfig(MCPServerConfig): + """MCP 服务器配置""" + + url: str = Field(description="MCP 服务器地址", default="") + + +class MCPConfig(BaseModel): + """MCP 配置""" + + mcp_servers: dict[ + str, + MCPServerSSEConfig | MCPServerStdioConfig, + ] = Field(description="MCP 服务器配置", alias="mcpServers") + + +class MCPTool(BaseModel): + """MCP工具""" + + id: str = Field(description="MCP工具ID") + name: str = Field(description="MCP工具名称") + description: str = Field(description="MCP工具描述") + mcp_id: str = Field(description="MCP ID") + input_schema: dict[str, Any] = Field(description="MCP工具输入参数") + + +class MCPCollection(BaseModel): + """MCP相关信息,存储在MongoDB的 ``mcp`` 集合中""" + + id: str = Field(description="MCP ID", alias="_id") + name: str = Field(description="MCP 自然语言名称") + description: str = Field(description="MCP 自然语言描述") + type: MCPType = Field(description="MCP 类型") + activated: list[str] = Field(description="激活该MCP的用户ID列表", default=[]) + tools: list[MCPTool] = Field(description="MCP工具列表", default=[]) + + +class MCPVector(LanceModel): + """MCP向量化数据,存储在LanceDB的 ``mcp`` 表中""" + + id: str = Field(description="MCP ID") + embedding: Vector(dim=1024) = Field(description="MCP描述的向量信息") # type: ignore[call-arg] + + +class MCPToolVector(LanceModel): + """MCP工具向量化数据,存储在LanceDB的 ``mcp_tool`` 表中""" + + id: str = Field(description="工具ID") + mcp_id: str = Field(description="MCP ID") + embedding: Vector(dim=1024) = Field(description="MCP工具描述的向量信息") # type: ignore[call-arg] + + +class MCPSelectResult(BaseModel): + """MCP选择结果""" + + mcp_id: str = Field(description="MCP Server的ID") + + +class MCPToolSelectResult(BaseModel): + """MCP工具选择结果""" + + name: str = Field(description="工具名称") + + +class MCPServiceMetadata(MCPMetadataBase): + """MCPService的元数据""" + + type: MetadataType = MetadataType.SERVICE + config: MCPConfig = Field(description="MCP服务配置") + config_str: str = Field(description="MCP服务配置字符串", alias="configStr") + tools: list[MCPTool] = Field(description="MCP服务Tools列表") + mcp_type: MCPType = Field(description="MCP 类型", alias="mcpType") + + +class MCPPlanItem(BaseModel): + """MCP 计划""" + + plan: str = Field(description="计划内容") + tool: str = Field(description="工具名称") + instruction: str = Field(description="工具指令") + + +class MCPPlan(BaseModel): + """MCP 计划""" + + plans: list[MCPPlanItem] = Field(description="计划列表") diff --git a/apps/entities/message.py b/apps/entities/message.py index 557d311b0ccb677dace093b320c1c0a012b29adc..0ae807790a6b2dbe0f41fe5e61bf587602c6ddc4 100644 --- a/apps/entities/message.py +++ b/apps/entities/message.py @@ -1,8 +1,5 @@ -""" -队列中的消息结构 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""队列中的消息结构""" from typing import Any diff --git a/apps/entities/node.py b/apps/entities/node.py index dcb458a88cf42c475100cd0a407171bca15f8819..fe25a690db02116b457c2c8483fd576376bb924f 100644 --- a/apps/entities/node.py +++ b/apps/entities/node.py @@ -1,4 +1,6 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """Node实体类""" + from typing import Any from pydantic import BaseModel, Field diff --git a/apps/entities/pool.py b/apps/entities/pool.py index 807a55a33e3ec5b2e11b0915db8689d021c2fb21..4b0c319eb1ff30aa4ccec19f69e2b66e51802fa0 100644 --- a/apps/entities/pool.py +++ b/apps/entities/pool.py @@ -1,8 +1,5 @@ -""" -App和Service等数据库内数据结构 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""App和Service等数据库内数据结构""" from datetime import UTC, datetime from typing import Any @@ -10,7 +7,7 @@ from typing import Any from pydantic import BaseModel, Field from apps.entities.appcenter import AppLink -from apps.entities.enum_var import CallType, PermissionType +from apps.entities.enum_var import AppType, CallType, PermissionType from apps.entities.flow import AppFlow, Permission @@ -102,6 +99,7 @@ class AppPool(BaseData): """ author: str = Field(description="作者的用户ID") + app_type: AppType = Field(description="应用类型", default=AppType.FLOW) type: str = Field(description="应用类型", default="default") icon: str = Field(description="应用图标", default="") published: bool = Field(description="是否发布", default=False) @@ -111,3 +109,4 @@ class AppPool(BaseData): permission: Permission = Field(description="应用权限配置", default=Permission()) flows: list[AppFlow] = Field(description="Flow列表", default=[]) hashes: dict[str, str] = Field(description="关联文件的hash值", default={}) + mcp_service: list[str] = Field(default=[], alias="mcpService", description="MCP服务id列表") diff --git a/apps/entities/rag_data.py b/apps/entities/rag_data.py index c66b989878078446881eafd9f366e7ac2ccadb98..2847bf387c9f4b0b8386d833c070664652c33f49 100644 --- a/apps/entities/rag_data.py +++ b/apps/entities/rag_data.py @@ -1,24 +1,24 @@ -""" -请求RAG相关接口时,使用的数据类型 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""请求RAG相关接口时,使用的数据类型""" from typing import Literal -from pydantic import BaseModel +from pydantic import BaseModel, Field class RAGQueryReq(BaseModel): """查询RAG时的POST请求体""" - question: str - history: list[dict[str, str]] = [] - language: str = "zh" - kb_sn: str | None = None - top_k: int = 5 - fetch_source: bool = False - document_ids: list[str] = [] + kb_ids: list[str] = Field(default=[], description="资产id", alias="kbIds") + query: str = Field(default="", description="查询内容") + top_k: int = Field(default=5, description="返回的结果数量", alias="topK") + doc_ids: list[str] = Field(default=None, description="文档id", alias="docIds") + search_method: str = Field(default="keyword_and_vector", + description="检索方法", alias="searchMethod") + is_related_surrounding: bool = Field(default=True, description="是否关联上下文", alias="isRelatedSurrounding") + is_classify_by_doc: bool = Field(default=True, description="是否按文档分类", alias="isClassifyByDoc") + is_rerank: bool = Field(default=False, description="是否重新排序", alias="isRerank") + tokens_limit: int = Field(default=8192, description="token限制", alias="tokensLimit") class RAGFileParseReqItem(BaseModel): diff --git a/apps/entities/record.py b/apps/entities/record.py index 68ee72885038ae9f33dcc391794905b2b264ac61..2f6306b035f71f5fa58ca94470a8adbb3b2dbab5 100644 --- a/apps/entities/record.py +++ b/apps/entities/record.py @@ -1,8 +1,5 @@ -""" -Record数据结构 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Record数据结构""" import uuid from datetime import UTC, datetime @@ -103,7 +100,7 @@ class Record(RecordData): user_sub: str key: dict[str, Any] = {} content: str - comment: RecordComment= Field(default=RecordComment()) + comment: RecordComment = Field(default=RecordComment()) flow: list[str] = Field(default=[]) diff --git a/apps/entities/request_data.py b/apps/entities/request_data.py index 89e94ae47e969e869e35f7dc596384efdceb9e10..61714aca1cbb218f91e695cc1812cbb22f2adccb 100644 --- a/apps/entities/request_data.py +++ b/apps/entities/request_data.py @@ -1,8 +1,5 @@ -""" -FastAPI 请求体 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI 请求体""" from typing import Any @@ -12,6 +9,7 @@ from apps.common.config import Config from apps.entities.appcenter import AppData from apps.entities.enum_var import CommentType from apps.entities.flow_topology import FlowItem +from apps.entities.mcp import MCPType class RequestDataApp(BaseModel): @@ -93,6 +91,23 @@ class ModFavAppRequest(BaseModel): favorited: bool = Field(..., description="是否收藏") +class UpdateMCPServiceRequest(BaseModel): + """POST /api/mcpservice 请求数据结构""" + + service_id: str | None = Field(None, alias="serviceId", description="服务ID(更新时传递)") + icon: str = Field(description="图标", default="") + name: str = Field(..., description="MCP服务名称") + description: str = Field(..., description="MCP服务描述") + config: str = Field(..., description="MCP服务配置") + mcp_type: MCPType = Field(description="MCP传输协议(Stdio/SSE/Streamable)", default=MCPType.STDIO, alias="mcpType") + + +class ActiveMCPServiceRequest(BaseModel): + """POST /api/mcp/{serviceId} 请求数据结构""" + + active: bool = Field(description="是否激活mcp服务") + + class UpdateServiceRequest(BaseModel): """POST /api/service 请求数据结构""" @@ -142,13 +157,29 @@ class PostDomainData(BaseModel): domain_description: str = Field(..., max_length=2000) -class PostKnowledgeIDData(BaseModel): - """添加知识库""" - - kb_id: str - - class PutFlowReq(BaseModel): """创建/修改流拓扑结构""" flow: FlowItem + + +class UpdateLLMReq(BaseModel): + """更新大模型请求体""" + + icon: str = Field(description="图标", default="") + openai_base_url: str = Field(default="", description="OpenAI API Base URL", alias="openaiBaseUrl") + openai_api_key: str = Field(default="", description="OpenAI API Key", alias="openaiApiKey") + model_name: str = Field(default="", description="模型名称", alias="modelName") + max_tokens: int = Field(default=8192, description="最大token数", alias="maxTokens") + + +class DeleteLLMReq(BaseModel): + """删除大模型请求体""" + + llm_id: str = Field(description="大模型ID", alias="llmId") + + +class UpdateKbReq(BaseModel): + """更新知识库请求体""" + + kb_ids: list[str] = Field(description="知识库ID列表", alias="kbIds", default=[]) diff --git a/apps/entities/response_data.py b/apps/entities/response_data.py index decb55dc5502a6676e684724f477e53e238b9be3..edcc2b5dcef0e8cc31a49ec50b4740bea3318b34 100644 --- a/apps/entities/response_data.py +++ b/apps/entities/response_data.py @@ -1,8 +1,5 @@ -""" -FastAPI 返回数据结构 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI 返回数据结构""" from typing import Any @@ -17,8 +14,10 @@ from apps.entities.flow_topology import ( NodeServiceItem, PositionItem, ) +from apps.entities.mcp import MCPTool, MCPType from apps.entities.record import RecordData from apps.entities.user import UserInfo +from apps.templates.generate_llm_operator_config import llm_provider_dict class ResponseData(BaseModel): @@ -47,6 +46,7 @@ class AuthUserMsg(BaseModel): user_sub: str revision: bool + is_admin: bool class AuthUserRsp(ResponseData): @@ -85,6 +85,21 @@ class GetBlacklistQuestionRsp(ResponseData): result: GetBlacklistQuestionMsg +class LLMIteam(BaseModel): + """GET /api/conversation Result数据结构""" + + icon: str = Field(default=llm_provider_dict["ollama"]["icon"]) + llm_id: str = Field(alias="llmId", default="empty") + model_name: str = Field(alias="modelName", default="Ollama LLM") + + +class KbIteam(BaseModel): + """GET /api/conversation Result数据结构""" + + kb_id: str = Field(alias="kbId") + kb_name: str = Field(alias="kbName") + + class ConversationListItem(BaseModel): """GET /api/conversation Result数据结构""" @@ -94,6 +109,8 @@ class ConversationListItem(BaseModel): created_time: str = Field(alias="createdTime") app_id: str = Field(alias="appId") debug: bool = Field(alias="debug") + llm: LLMIteam | None = Field(alias="llm", default=None) + kb_list: list[KbIteam] = Field(alias="kbList", default=[]) class ConversationListMsg(BaseModel): @@ -214,16 +231,33 @@ class OidcRedirectRsp(ResponseData): result: OidcRedirectMsg -class GetKnowledgeIDMsg(BaseModel): +class KnowledgeBaseItem(BaseModel): + """知识库列表项数据结构""" + + kb_id: str = Field(..., alias="kbId", description="知识库ID") + kb_name: str = Field(..., description="知识库名称", alias="kbName") + description: str = Field(..., description="知识库描述") + is_used: bool = Field(..., description="是否使用", alias="isUsed") + + +class TeamKnowledgeBaseItem(BaseModel): + """团队知识库列表项数据结构""" + + team_id: str = Field(..., alias="teamId", description="团队ID") + team_name: str = Field(..., alias="teamName", description="团队名称") + kb_list: list[KnowledgeBaseItem] = Field(default=[], description="知识库列表") + + +class ListTeamKnowledgeMsg(BaseModel): """GET /api/knowledge Result数据结构""" - kb_id: str + team_kb_list: list[TeamKnowledgeBaseItem] = Field(default=[], alias="teamKbList", description="团队知识库列表") -class GetKnowledgeIDRsp(ResponseData): +class ListTeamKnowledgeRsp(ResponseData): """GET /api/knowledge 返回数据结构""" - result: GetKnowledgeIDMsg + result: ListTeamKnowledgeMsg class BaseAppOperationMsg(BaseModel): @@ -396,6 +430,75 @@ class NodeServiceListRsp(ResponseData): result: NodeServiceListMsg +class MCPServiceCardItem(BaseModel): + """插件中心:MCP服务卡片数据结构""" + + mcpservice_id: str = Field(..., alias="mcpserviceId", description="mcp服务ID") + name: str = Field(..., description="mcp服务名称") + description: str = Field(..., description="mcp服务简介") + icon: str = Field(..., description="mcp服务图标") + author: str = Field(..., description="mcp服务作者") + is_active: bool = Field(alias="isActive", description="mcp服务是否激活", default=False) + + +class BaseMCPServiceOperationMsg(BaseModel): + """插件中心:MCP服务操作Result数据结构""" + + service_id: str = Field(..., alias="serviceId", description="服务ID") + + +class GetMCPServiceListMsg(BaseModel): + """GET /api/service Result数据结构""" + + current_page: int = Field(..., alias="currentPage", description="当前页码") + total_count: int = Field(..., alias="totalCount", description="总服务数") + services: list[MCPServiceCardItem] = Field(..., description="解析后的服务列表") + + +class GetMCPServiceListRsp(ResponseData): + """GET /api/service 返回数据结构""" + + result: GetMCPServiceListMsg = Field(..., title="Result") + + +class UpdateMCPServiceMsg(BaseModel): + """插件中心:MCP服务属性数据结构""" + + service_id: str = Field(..., alias="serviceId", description="MCP服务ID") + name: str = Field(..., description="MCP服务名称") + + +class UpdateMCPServiceRsp(ResponseData): + """POST /api/mcp_service 返回数据结构""" + + result: UpdateMCPServiceMsg = Field(..., title="Result") + + +class GetMCPServiceDetailMsg(BaseModel): + """GET /api/mcp_service/{serviceId} Result数据结构""" + + service_id: str = Field(..., alias="serviceId", description="MCP服务ID") + icon: str = Field(description="图标", default="") + name: str = Field(..., description="MCP服务名称") + description: str = Field(description="MCP服务描述") + data: str = Field(description="MCP服务配置") + tools: list[MCPTool] = Field(description="MCP服务Tools列表", default=[]) + is_active: bool = Field(alias="isActive", description="mcp服务是否激活", default=False) + mcp_type: MCPType = Field(alias="mcpType", description="MCP 类型") + + +class GetMCPServiceDetailRsp(ResponseData): + """GET /api/service/{serviceId} 返回数据结构""" + + result: GetMCPServiceDetailMsg = Field(..., title="Result") + + +class DeleteMCPServiceRsp(ResponseData): + """DELETE /api/service/{serviceId} 返回数据结构""" + + result: BaseMCPServiceOperationMsg = Field(..., title="Result") + + class NodeMetaDataRsp(ResponseData): """GET /api/flow/service/node 返回数据结构""" @@ -444,12 +547,54 @@ class FlowStructureDeleteRsp(ResponseData): result: FlowStructureDeleteMsg + class UserGetMsp(BaseModel): """GET /api/user result""" - user_info_list : list[UserInfo] = Field(alias="userInfoList", default=[]) + user_info_list: list[UserInfo] = Field(alias="userInfoList", default=[]) + class UserGetRsp(ResponseData): """GET /api/user 返回数据结构""" result: UserGetMsp + + +class ActiveMCPServiceRsp(ResponseData): + """POST /api/mcp/active/{serviceId} 返回数据结构""" + + result: BaseMCPServiceOperationMsg = Field(..., title="Result") + + +class LLMProvider(BaseModel): + """LLM提供商数据结构""" + + provider: str = Field(..., description="LLM提供商") + description: str = Field(..., description="LLM提供商描述") + url: str | None = Field(default=None, description="LLM提供商URL") + icon: str = Field(..., description="LLM提供商图标") + + +class ListLLMProviderRsp(ResponseData): + """GET /api/llm/provider 返回数据结构""" + + result: list[LLMProvider] = Field(default=[], title="Result") + + +class LLM(BaseModel): + """LLM数据结构""" + + llm_id: str = Field(..., alias="llmId", description="LLM ID") + icon: str = Field(default="", description="LLM图标", max_length=25536) + 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") + model_name: str = Field(description="模型名称", alias="modelName") + max_tokens: int = Field(description="最大token数", alias="maxTokens") + is_editable: bool = Field(default=True, description="是否可编辑", alias="isEditable") + + +class ListLLMRsp(ResponseData): + """GET /api/llm 返回数据结构""" + + result: list[LLM] = Field(default=[], title="Result") diff --git a/apps/entities/scheduler.py b/apps/entities/scheduler.py index c767b66b898397d012e9e604d3d36f4ec4ca5c53..c1e31405222d32de92a5891a4090ce977a7191ac 100644 --- a/apps/entities/scheduler.py +++ b/apps/entities/scheduler.py @@ -1,8 +1,5 @@ -""" -插件、工作流、步骤相关数据结构定义 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""插件、工作流、步骤相关数据结构定义""" from typing import Any diff --git a/apps/entities/session.py b/apps/entities/session.py index fb619833804463878e22c4ee60ffc20762154349..361987cc23d93e428e7fee12a520f65435946f61 100644 --- a/apps/entities/session.py +++ b/apps/entities/session.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """Session相关数据结构""" from datetime import datetime diff --git a/apps/entities/task.py b/apps/entities/task.py index e4eedbbb8e6defec047cd81b2cd68fd77cf2b40f..77d8c1a47f85454396ddaa6a16caeea0b70208a1 100644 --- a/apps/entities/task.py +++ b/apps/entities/task.py @@ -1,8 +1,5 @@ -""" -Task相关数据结构定义 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Task相关数据结构定义""" import uuid from datetime import UTC, datetime @@ -47,6 +44,7 @@ class ExecutorState(BaseModel): step_name: str = Field(description="当前步骤名称") app_id: str = Field(description="应用ID") slot: dict[str, Any] = Field(description="待填充参数的JSON Schema", default={}) + error_info: dict[str, Any] = Field(description="错误信息", default={}) class TaskIds(BaseModel): diff --git a/apps/entities/user.py b/apps/entities/user.py index 15a0c73b3282385c3cb9e53a330131af5ae1e58c..61aa2587b8ae6255dc3885b04a96f12310f70773 100644 --- a/apps/entities/user.py +++ b/apps/entities/user.py @@ -1,8 +1,5 @@ -""" -User用户信息数据结构 - -Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. +"""User用户信息数据结构""" from pydantic import BaseModel, Field diff --git a/apps/entities/vector.py b/apps/entities/vector.py index 361c0d5beac7d083f58d0e3680ebcdcb77c308ba..1cdc85c9b9fbe5aaa902e1b93238001e5bbe44c5 100644 --- a/apps/entities/vector.py +++ b/apps/entities/vector.py @@ -1,8 +1,5 @@ -""" -向量数据库数据结构;数据将存储在LanceDB中 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""向量数据库数据结构;数据将存储在LanceDB中""" from lancedb.pydantic import LanceModel, Vector diff --git a/apps/llm/__init__.py b/apps/llm/__init__.py index 8b01256ea0cc406c385ac49fe9115b09451bd38e..e28ad31d70fd0bbc692694ff57f16014fd7580d5 100644 --- a/apps/llm/__init__.py +++ b/apps/llm/__init__.py @@ -1,4 +1,2 @@ -"""模型调用模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""模型调用模块""" diff --git a/apps/llm/embedding.py b/apps/llm/embedding.py index b6faebba773d43632e0dc35c9d65796f36b9787e..28ab86b49a96d19cc6e2db83930d5aff48f82260 100644 --- a/apps/llm/embedding.py +++ b/apps/llm/embedding.py @@ -1,6 +1,6 @@ """Embedding模型""" -import aiohttp +import httpx from apps.common.config import Config @@ -9,6 +9,12 @@ class Embedding: """Embedding模型""" # TODO: 应当自动检测向量维度 + @classmethod + async def _get_embedding_dimension(cls) -> int: + """获取Embedding的维度""" + embedding = await cls.get_embedding(["测试文本"]) + return len(embedding[0]) + @classmethod async def _get_openai_embedding(cls, text: list[str]) -> list[list[float]]: @@ -26,16 +32,14 @@ class Embedding: if Config().get_config().embedding.api_key: headers["Authorization"] = f"Bearer {Config().get_config().embedding.api_key}" - async with ( - aiohttp.ClientSession() as session, - session.post( + async with httpx.AsyncClient() as client: + response = await client.post( api, json=data, headers=headers, - timeout=aiohttp.ClientTimeout(total=60), - ) as response, - ): - json = await response.json() + timeout=60.0, + ) + json = response.json() return [item["embedding"] for item in json["data"]] @classmethod @@ -48,22 +52,20 @@ class Embedding: if Config().get_config().embedding.api_key: headers["Authorization"] = f"Bearer {Config().get_config().embedding.api_key}" - session = aiohttp.ClientSession() - - result = [] - for single_text in text: - data = { - "inputs": single_text, - "normalize": True, - } - async with session.post( - api, json=data, headers=headers, timeout=aiohttp.ClientTimeout(total=60), - ) as response: - json = await response.json() + async with httpx.AsyncClient() as client: + result = [] + for single_text in text: + data = { + "inputs": single_text, + "normalize": True, + } + response = await client.post( + api, json=data, headers=headers, timeout=60.0, + ) + json = response.json() result.append(json[0]) - await session.close() - return result + return result @classmethod async def get_embedding(cls, text: list[str]) -> list[list[float]]: diff --git a/apps/llm/function.py b/apps/llm/function.py index a17370a0be450d3a64de747c28831088ba77bbc7..1f995fe7ba187cead03aa6fc62a4cbce1ec05a65 100644 --- a/apps/llm/function.py +++ b/apps/llm/function.py @@ -1,271 +1,305 @@ -""" -用于FunctionCall的大模型 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""用于FunctionCall的大模型""" import json +import logging +import re +from textwrap import dedent from typing import Any -from asyncer import asyncify +from jinja2 import BaseLoader +from jinja2.sandbox import SandboxedEnvironment +from jsonschema import Draft7Validator from apps.common.config import Config -from apps.constants import REASONING_BEGIN_TOKEN, REASONING_END_TOKEN -from apps.scheduler.json_schema import build_regex_from_schema +from apps.constants import JSON_GEN_MAX_TRIAL, REASONING_END_TOKEN +from apps.llm.prompt import JSON_GEN_BASIC + +logger = logging.getLogger(__name__) class FunctionLLM: """用于FunctionCall的模型""" - _client: Any - def __init__(self) -> None: """ 初始化用于FunctionCall的模型 目前支持: - - sglang - vllm - ollama + - function_call + - json_mode + - structured_output """ - if Config().get_config().function_call.backend == "sglang": - import sglang - from sglang.lang.chat_template import get_chat_template - - if not Config().get_config().function_call.api_key: - self._client = sglang.RuntimeEndpoint(Config().get_config().function_call.endpoint) - else: - self._client = sglang.RuntimeEndpoint( - Config().get_config().function_call.endpoint, api_key=Config().get_config().function_call.api_key, - ) - self._client.chat_template = get_chat_template("chatml") - - if ( - Config().get_config().function_call.backend == "vllm" - or Config().get_config().function_call.backend == "openai" - ): - import openai + # 暂存config;这里可以替代为从其他位置获取 + self._config = Config().get_config().function_call + if not self._config.model: + err_msg = "[FunctionCall] 未设置FuntionCall所用模型!" + logger.error(err_msg) + raise ValueError(err_msg) - if not Config().get_config().function_call.api_key: - self._client = openai.AsyncOpenAI(base_url=Config().get_config().function_call.endpoint + "/v1") - else: - self._client = openai.AsyncOpenAI( - base_url=Config().get_config().function_call.endpoint + "/v1", - api_key=Config().get_config().function_call.api_key, - ) + self._params = { + "model": self._config.model, + "messages": [], + } - if Config().get_config().function_call.backend == "ollama": + if self._config.backend == "ollama": import ollama - if not Config().get_config().function_call.api_key: - self._client = ollama.AsyncClient(host=Config().get_config().function_call.endpoint) + if not self._config.api_key: + self._client = ollama.AsyncClient(host=self._config.endpoint) else: self._client = ollama.AsyncClient( - host=Config().get_config().function_call.endpoint, + host=self._config.endpoint, headers={ - "Authorization": f"Bearer {Config().get_config().function_call.api_key}", + "Authorization": f"Bearer {self._config.api_key}", }, ) - @staticmethod - def _sglang_func( - s, messages: list[dict[str, Any]], schema: dict[str, Any], max_tokens: int, temperature: float, # noqa: ANN001 - ) -> None: - """ - 构建sglang需要的执行函数 + else: + import openai - :param s: sglang context - :param messages: 历史消息 - :param schema: 输出JSON Schema - :param max_tokens: 最大Token长度 - :param temperature: 大模型温度 - """ - for msg in messages: - if msg["role"] == "user": - s += s.user(msg["content"]) - elif msg["role"] == "assistant": - s += s.assistant(msg["content"]) - elif msg["role"] == "system": - s += s.system(msg["content"]) + if not self._config.api_key: + self._client = openai.AsyncOpenAI(base_url=self._config.endpoint) else: - err_msg = f"Unknown message role: {msg['role']}" - raise ValueError(err_msg) + self._client = openai.AsyncOpenAI( + base_url=self._config.endpoint, + api_key=self._config.api_key, + ) - # 如果Schema为空,认为是直接问答,不加输出限制 - if not schema: - s += s.assistant(s.gen(name="output", max_tokens=max_tokens, temperature=temperature)) - else: - s += s.assistant( - s.gen( - name="output", - regex=build_regex_from_schema(json.dumps(schema)), - max_tokens=max_tokens, - temperature=temperature, - ), - ) - - async def _call_vllm( - self, messages: list[dict[str, Any]], schema: dict[str, Any], max_tokens: int, temperature: float, + + async def _call_openai( + self, + messages: list[dict[str, str]], + schema: dict[str, Any], + max_tokens: int | None = None, + temperature: float | None = None, ) -> str: """ - 调用vllm模型生成JSON + 调用openai模型生成JSON - :param messages: 历史消息列表 - :param schema: 输出JSON Schema - :param max_tokens: 最大Token长度 - :param temperature: 大模型温度 + :param list[dict[str, str]] messages: 历史消息列表 + :param dict[str, Any] schema: 输出JSON Schema + :param int | None max_tokens: 最大Token长度 + :param float | None temperature: 大模型温度 :return: 生成的JSON + :rtype: str """ - model = Config().get_config().function_call.model - if not model: - err_msg = "未设置FuntionCall所用模型!" - raise ValueError(err_msg) - - param = { - "model": model, + self._params.update({ "messages": messages, "max_tokens": max_tokens, "temperature": temperature, - "stream": True, - } - - # 如果Schema不为空,认为是FunctionCall,需要指定输出格式 - if schema: - param["extra_body"] = {"guided_json": schema} - - chat = await self._client.chat.completions.create(**param) + }) + + if self._config.backend == "vllm": + self._params["extra_body"] = {"guided_json": schema} + + elif self._config.backend == "json_mode": + logger.warning("[FunctionCall] json_mode无法确保输出格式符合要求,使用效果将受到影响") + self._params["response_format"] = {"type": "json_object"} + + elif self._config.backend == "structured_output": + self._params["response_format"] = { + "type": "json_schema", + "json_schema": { + "name": "generate", + "description": "Generate answer based on the background information", + "schema": schema, + "strict": True, + }, + } - reasoning = False - result = "" - async for chunk in chat: - chunk_str = chunk.choices[0].delta.content or "" - for token in REASONING_BEGIN_TOKEN: - if token in chunk_str: - reasoning = True - break + elif self._config.backend == "function_call": + logger.warning("[FunctionCall] function_call无法确保一定调用工具,使用效果将受到影响") + self._params["tools"] = [ + { + "type": "function", + "function": { + "name": "generate", + "description": "Generate answer based on the background information", + "parameters": schema, + }, + }, + ] - for token in REASONING_END_TOKEN: - if token in chunk_str: - reasoning = False - chunk_str = "" - break + response = await self._client.chat.completions.create(**self._params) # type: ignore[arg-type] + try: + logger.info("[FunctionCall] 大模型输出:%s", response.choices[0].message.tool_calls[0].function.arguments) + return response.choices[0].message.tool_calls[0].function.arguments + except Exception: # noqa: BLE001 + ans = response.choices[0].message.content + logger.info("[FunctionCall] 大模型输出:%s", ans) + return await FunctionLLM.process_response(ans) - if not reasoning: - result += chunk_str - return result.strip().strip(" ").strip("\n") - async def _call_openai( - self, messages: list[dict[str, Any]], schema: dict[str, Any], max_tokens: int, temperature: float, - ) -> str: - """ - 调用openai模型生成JSON + @staticmethod + async def process_response(response: str) -> str: + """处理大模型的输出""" + # 去掉推理过程,避免干扰 + for token in REASONING_END_TOKEN: + response = response.split(token)[-1] + + # 尝试解析JSON + response = dedent(response).strip() + error_flag = False + try: + json.loads(response) + except Exception: # noqa: BLE001 + error_flag = True - :param messages: 历史消息列表 - :param schema: 输出JSON Schema - :param max_tokens: 最大Token长度 - :param temperature: 大模型温度 - :return: 生成的JSON - """ - model = Config().get_config().function_call.model - if not model: - err_msg = "未设置FuntionCall所用模型!" - raise ValueError(err_msg) + if not error_flag: + return response - param = { - "model": model, - "messages": messages, - "max_tokens": max_tokens, - "temperature": temperature, - } + # 尝试提取```json中的JSON + logger.warning("[FunctionCall] 直接解析失败!尝试提取```json中的JSON") + try: + json_str = re.findall(r"```json(.*)```", response, re.DOTALL)[-1] + json_str = dedent(json_str).strip() + json.loads(json_str) + except Exception: # noqa: BLE001 + # 尝试直接通过括号匹配JSON + logger.warning("[FunctionCall] 提取失败!尝试正则提取JSON") + try: + json_str = re.findall(r"\{.*\}", response, re.DOTALL)[-1] + json_str = dedent(json_str).strip() + json.loads(json_str) + except Exception: # noqa: BLE001 + json_str = "{}" - if schema: - tool_data = { - "type": "function", - "function": { - "name": "output", - "description": "Call the function to get the output", - "parameters": schema, - }, - } - param["tools"] = [tool_data] - param["tool_choice"] = "required" + return json_str - response = await self._client.chat.completions.create(**param) - try: - ans = response.choices[0].message.tool_calls[0].function.arguments or "" - except IndexError: - ans = "" - return ans async def _call_ollama( - self, messages: list[dict[str, Any]], schema: dict[str, Any], max_tokens: int, temperature: float, + self, + messages: list[dict[str, str]], + schema: dict[str, Any], + max_tokens: int | None = None, + temperature: float | None = None, ) -> str: """ 调用ollama模型生成JSON - :param messages: 历史消息列表 - :param schema: 输出JSON Schema - :param max_tokens: 最大Token长度 - :param temperature: 大模型温度 + :param list[dict[str, str]] messages: 历史消息列表 + :param dict[str, Any] schema: 输出JSON Schema + :param int | None max_tokens: 最大Token长度 + :param float | None temperature: 大模型温度 :return: 生成的对话回复 + :rtype: str """ - param = { - "model": Config().get_config().function_call.model, + self._params.update({ "messages": messages, "options": { "temperature": temperature, - "num_ctx": max_tokens, "num_predict": max_tokens, }, - } - # 如果Schema不为空,认为是FunctionCall,需要指定输出格式 - if schema: - param["format"] = schema + "format": schema, + }) - response = await self._client.chat(**param) - return response.message.content or "" + response = await self._client.chat(**self._params) # type: ignore[arg-type] + return await self.process_response(response.message.content or "") - async def _call_sglang( - self, messages: list[dict[str, Any]], schema: dict[str, Any], max_tokens: int, temperature: float, - ) -> str: - """ - 调用sglang模型生成JSON - :param messages: 历史消息 - :param schema: 输出JSON Schema - :param max_tokens: 最大Token长度 - :param temperature: 大模型温度 - :return: 生成的JSON - """ - # 构造sglang执行函数 - import sglang - - sglang.set_default_backend(self._client) - - sglang_func = sglang.function(self._sglang_func) - state = await asyncify(sglang_func.run)(messages, schema, max_tokens, temperature) # type: ignore[arg-type] - return state["output"] - - async def call(self, **kwargs) -> str: # noqa: ANN003 + async def call( + self, + messages: list[dict[str, Any]], + schema: dict[str, Any], + max_tokens: int | None = None, + temperature: float | None = None, + ) -> dict[str, Any]: """ 调用FunctionCall小模型 - 暂不开放流式输出 + 不开放流式输出 """ - if Config().get_config().function_call.backend == "vllm": - json_str = await self._call_vllm(**kwargs) + # 检查max_tokens和temperature是否设置 + if max_tokens is None: + max_tokens = self._config.max_tokens + if temperature is None: + temperature = self._config.temperature - elif Config().get_config().function_call.backend == "sglang": - json_str = await self._call_sglang(**kwargs) + if self._config.backend == "ollama": + json_str = await self._call_ollama(messages, schema, max_tokens, temperature) - elif Config().get_config().function_call.backend == "ollama": - json_str = await self._call_ollama(**kwargs) - - elif Config().get_config().function_call.backend == "openai": - json_str = await self._call_openai(**kwargs) + elif self._config.backend in ["function_call", "json_mode", "response_format", "vllm"]: + json_str = await self._call_openai(messages, schema, max_tokens, temperature) else: err = "未知的Function模型后端" raise ValueError(err) - return json_str + try: + return json.loads(json_str) + except Exception: # noqa: BLE001 + logger.error("[FunctionCall] 大模型JSON解析失败:%s", json_str) # noqa: TRY400 + return {} + + +class JsonGenerator: + """JSON生成器""" + + def __init__(self, query: str, conversation: list[dict[str, str]], schema: dict[str, Any]) -> None: + """初始化JSON生成器""" + self._query = query + self._conversation = conversation + self._schema = schema + + self._trial = {} + self._count = 0 + self._env = SandboxedEnvironment( + loader=BaseLoader(), + autoescape=False, + trim_blocks=True, + lstrip_blocks=True, + ) + self._err_info = "" + + + async def _assemble_message(self) -> str: + """组装消息""" + # 检查类型 + function_call = Config().get_config().function_call.backend == "function_call" + + # 渲染模板 + template = self._env.from_string(JSON_GEN_BASIC) + return template.render( + query=self._query, + conversation=self._conversation, + previous_trial=self._trial, + schema=self._schema, + function_call=function_call, + err_info=self._err_info, + ) + + async def _single_trial(self, max_tokens: int | None = None, temperature: float | None = None) -> dict[str, Any]: + """单次尝试""" + prompt = await self._assemble_message() + messages = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": prompt}, + ] + function = FunctionLLM() + return await function.call(messages, self._schema, max_tokens, temperature) + + + async def generate(self) -> dict[str, Any]: + """生成JSON""" + Draft7Validator.check_schema(self._schema) + validator = Draft7Validator(self._schema) + logger.info("[JSONGenerator] Schema:%s", self._schema) + + while self._count < JSON_GEN_MAX_TRIAL: + self._count += 1 + result = await self._single_trial() + logger.info("[JSONGenerator] 得到:%s", result) + try: + validator.validate(result) + except Exception as err: # noqa: BLE001 + err_info = str(err) + err_info = err_info.split("\n\n")[0] + self._err_info = err_info + logger.info("[JSONGenerator] 验证失败:%s", self._err_info) + continue + return result + + return {} diff --git a/apps/llm/patterns/__init__.py b/apps/llm/patterns/__init__.py index 65772e1f52e1052cb257f42e8a6e8707fb3c1293..b12f2a1201f8636fdbb2d6caadb40d4daa7e71cd 100644 --- a/apps/llm/patterns/__init__.py +++ b/apps/llm/patterns/__init__.py @@ -1,25 +1,18 @@ -""" -LLM大模型Prompt模板 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""LLM大模型Prompt模板""" from apps.llm.patterns.core import CorePattern -from apps.llm.patterns.domain import Domain from apps.llm.patterns.executor import ( ExecutorSummary, ExecutorThought, ) -from apps.llm.patterns.json_gen import Json from apps.llm.patterns.recommend import Recommend from apps.llm.patterns.select import Select __all__ = [ "CorePattern", - "Domain", "ExecutorSummary", "ExecutorThought", - "Json", "Recommend", "Select", ] diff --git a/apps/llm/patterns/core.py b/apps/llm/patterns/core.py index 756fa14331dae266d4b7e67078d9c7f98a59d273..4ef8133a9fed1b1e62f1ceb578c6bdb5a93b12a5 100644 --- a/apps/llm/patterns/core.py +++ b/apps/llm/patterns/core.py @@ -1,12 +1,8 @@ -""" -基础大模型范式抽象类 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""基础大模型范式抽象类""" from abc import ABC, abstractmethod from textwrap import dedent -from typing import Any, ClassVar class CorePattern(ABC): @@ -16,8 +12,6 @@ class CorePattern(ABC): """系统提示词""" user_prompt: str = "" """用户提示词""" - slot_schema: ClassVar[dict[str, Any]] = {} - """输出格式的JSON Schema""" input_tokens: int = 0 """输入Token数量""" output_tokens: int = 0 diff --git a/apps/llm/patterns/domain.py b/apps/llm/patterns/domain.py deleted file mode 100644 index 26bd8fa77a7481e4e357426da9b0fd1e0b11eb50..0000000000000000000000000000000000000000 --- a/apps/llm/patterns/domain.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -LLM Pattern: 从问答中提取领域信息 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" - -from typing import Any, ClassVar - -from apps.llm.patterns.core import CorePattern -from apps.llm.patterns.json_gen import Json -from apps.llm.reasoning import ReasoningLLM -from apps.llm.snippet import convert_context_to_prompt - - -class Domain(CorePattern): - """从问答中提取领域信息""" - - user_prompt: str = r""" - - - 根据对话上文,提取推荐系统所需的关键词标签,要求: - 1. 实体名词、技术术语、时间范围、地点、产品等关键信息均可作为关键词标签 - 2. 至少一个关键词与对话的话题有关 - 3. 标签需精简,不得重复,不得超过10个字 - 4. 使用JSON格式输出,不要包含XML标签,不要包含任何解释说明 - - - - - 北京天气如何? - 北京今天晴。 - - - - {{ - "keywords": ["北京", "天气"] - }} - - - - - {conversation} - - """ - """用户提示词""" - - slot_schema: ClassVar[dict[str, Any]] = { - "type": "object", - "properties": { - "keywords": { - "type": "array", - "description": "feature tags and categories, can be empty", - }, - }, - "required": ["keywords"], - } - """最终输出的JSON Schema""" - - def __init__(self, system_prompt: str | None = None, user_prompt: str | None = None) -> None: - """初始化Reflect模式""" - super().__init__(system_prompt, user_prompt) - - - async def generate(self, **kwargs) -> list[str]: # noqa: ANN003 - """从问答中提取领域信息""" - conversation = convert_context_to_prompt(kwargs["conversation"]) - messages = [ - {"role": "system", "content": self.system_prompt}, - {"role": "user", "content": self.user_prompt.format(conversation=conversation)}, - ] - - result = "" - llm = ReasoningLLM() - async for chunk in llm.call(messages, streaming=False): - result += chunk - self.input_tokens = llm.input_tokens - self.output_tokens = llm.output_tokens - - messages += [ - {"role": "assistant", "content": result}, - ] - - output = await Json().generate(conversation=messages, spec=self.slot_schema) - return output.get("keywords", []) diff --git a/apps/llm/patterns/executor.py b/apps/llm/patterns/executor.py index a879f1c7222b46ce48c166a84a94b11e773dcc77..33f34b6e1ef5f3e2bb34d4fcffd468b91729b972 100644 --- a/apps/llm/patterns/executor.py +++ b/apps/llm/patterns/executor.py @@ -1,8 +1,5 @@ -""" -使用大模型生成Executor的思考内容 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""使用大模型生成Executor的思考内容""" from typing import TYPE_CHECKING, Any @@ -54,7 +51,6 @@ class ExecutorThought(CorePattern): """处理Prompt""" super().__init__(system_prompt, user_prompt) - async def generate(self, **kwargs) -> str: # noqa: ANN003 """调用大模型,生成对话总结""" try: @@ -66,7 +62,7 @@ class ExecutorThought(CorePattern): raise ValueError(err) from e messages = [ - {"role": "system", "content": ""}, + {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": self.user_prompt.format( last_thought=last_thought, user_question=user_question, @@ -123,7 +119,7 @@ class ExecutorSummary(CorePattern): facts_str = facts_to_prompt(background.facts) messages = [ - {"role": "system", "content": self.system_prompt}, + {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": self.user_prompt.format( facts=facts_str, conversation=conversation_str, diff --git a/apps/llm/patterns/facts.py b/apps/llm/patterns/facts.py index 2586dba7c6cda79170c29e6afbda713cfc8255e2..0b0381ff40a0e6632fd204c9efcf834a13c4711f 100644 --- a/apps/llm/patterns/facts.py +++ b/apps/llm/patterns/facts.py @@ -1,16 +1,28 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """事实提取""" -from typing import Any, ClassVar +import logging + +from pydantic import BaseModel, Field + +from apps.llm.function import JsonGenerator from apps.llm.patterns.core import CorePattern -from apps.llm.patterns.json_gen import Json from apps.llm.reasoning import ReasoningLLM from apps.llm.snippet import convert_context_to_prompt +logger = logging.getLogger(__name__) + + +class FactsResult(BaseModel): + """事实提取结果""" + + facts: list[str] = Field(description="从对话中提取的事实列表,可以为空") + class Facts(CorePattern): """事实提取""" - system_prompt: str = "" + system_prompt: str = "You are a helpful assistant." """系统提示词(暂不使用)""" user_prompt: str = r""" @@ -54,22 +66,6 @@ class Facts(CorePattern): """ """用户提示词""" - slot_schema: ClassVar[dict[str, Any]] = { - "type": "object", - "properties": { - "facts": { - "type": "array", - "description": "The facts extracted from the conversation.", - "items": { - "type": "string", - "description": "A fact string.", - }, - }, - }, - "required": ["facts"], - } - """最终输出的JSON Schema""" - def __init__(self, system_prompt: str | None = None, user_prompt: str | None = None) -> None: """初始化Prompt""" @@ -91,8 +87,16 @@ class Facts(CorePattern): self.output_tokens = llm.output_tokens messages += [{"role": "assistant", "content": result}] - fact_dict = await Json().generate(conversation=messages, spec=self.slot_schema) - - if not fact_dict or "facts" not in fact_dict or not fact_dict["facts"]: + json_gen = JsonGenerator( + query="根据给定的背景信息,提取事实条目", + conversation=messages, + schema=FactsResult.model_json_schema(), + ) + + try: + fact_dict = FactsResult.model_validate(await json_gen.generate()) + except Exception: + logger.exception("[Facts] 事实提取失败") return [] - return fact_dict.get("facts", []) + + return fact_dict.facts diff --git a/apps/llm/patterns/json_gen.py b/apps/llm/patterns/json_gen.py deleted file mode 100644 index fa715c095d751ec70fba6bcf79575b105b9979e9..0000000000000000000000000000000000000000 --- a/apps/llm/patterns/json_gen.py +++ /dev/null @@ -1,202 +0,0 @@ -""" -JSON参数生成Prompt - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" - -import json -import logging -from copy import deepcopy -from typing import Any - -from jinja2 import BaseLoader -from jinja2.sandbox import SandboxedEnvironment - -from apps.common.config import Config -from apps.llm.function import FunctionLLM -from apps.llm.patterns.core import CorePattern - -logger = logging.getLogger(__name__) - - -class Json(CorePattern): - """使用FunctionCall范式,生成JSON参数""" - - user_prompt: str = r""" - - - 你是一个可以调用工具函数的智能助手。目前你正在使用工具函数,但是函数缺少必要的参数。 - 你需要根据对话内容和JSON Schema定义,提取信息并填充符合要求的JSON参数。 - 要求: - 1. 输出必须是有效的JSON格式,不包含任何注释或额外格式。 - 2. 如果找不到有效值,直接使用null作为值。 - 3. 不要编造参数,不要编造参数的值。 - 4. 示例值仅作为格式参考,不要直接输出示例值。 - - - - - 创建"任务1",并进行扫描 - - - - { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "扫描任务的名称", - "example": "Task 1" - }, - "enable": { - "type": "boolean", - "description": "是否启用该任务", - "pattern": "(true|false)" - } - }, - "required": ["name", "enable"] - } - - - - {"scan": [{"name": "Task 1", "enable": true}]} - - - - - - {{ conversation }} - - - - {{ slot_schema }} - - - - """ - """用户提示词""" - - def __init__(self, system_prompt: str | None = None, user_prompt: str | None = None) -> None: - """初始化Json模式""" - super().__init__(system_prompt, user_prompt) - - - @staticmethod - def _remove_null_params(input_val: Any) -> Any: - """ - 递归地移除输入数据中的空值参数。 - - :param input_val: 输入的数据,可以是字典、列表或其他类型。 - :return: 移除空值参数后的数据。 - """ - if isinstance(input_val, dict): - new_dict = {} - for key, value in input_val.items(): - nested = Json._remove_null_params(value) - if isinstance(nested, (bool, int, float)) or nested: - new_dict[key] = nested - return new_dict - if isinstance(input_val, list): - new_list = [] - for v in input_val: - cleaned_v = Json._remove_null_params(v) - if cleaned_v: - new_list.append(cleaned_v) - if len(new_list) > 0: - return new_list - return None - return input_val - - - @staticmethod - def _unstrict_spec(spec: dict[str, Any]) -> dict[str, Any]: # noqa: C901, PLR0912 - """移除spec中的required属性""" - # 设置必要字段 - new_spec = {} - new_spec["type"] = spec.get("type", "string") - new_spec["description"] = spec.get("description", "") - - # 处理对象和数组两种递归情况 - if "items" in spec: - new_spec["items"] = Json._unstrict_spec(spec["items"]) - if "properties" in spec: - new_spec["properties"] = {} - for key in spec["properties"]: - new_spec["properties"][key] = Json._unstrict_spec(spec["properties"][key]) - - # 把必要信息放到描述中,起提示作用 - if "pattern" in spec: - new_spec["description"] += f"\n正则表达式模式为:{spec['pattern']}" - if "example" in spec: - new_spec["description"] += f"\n示例:{spec['example']}" - if "default" in spec: - new_spec["description"] += f"\n默认值为:{spec['default']}" - if "enum" in spec: - new_spec["description"] += f"\n取值必须是以下之一:{', '.join(str(item) for item in spec['enum'])}" - if "minimum" in spec: - new_spec["description"] += f"\n值必须大于或等于:{spec['minimum']}" - if "maximum" in spec: - new_spec["description"] += f"\n值必须小于或等于:{spec['maximum']}" - if "minLength" in spec: - new_spec["description"] += f"\n长度必须至少为 {spec['minLength']} 个字符" - if "maxLength" in spec: - new_spec["description"] += f"\n长度不能超过 {spec['maxLength']} 个字符" - if "minItems" in spec: - new_spec["description"] += f"\n数组至少包含 {spec['minItems']} 个项目" - if "maxItems" in spec: - new_spec["description"] += f"\n数组最多包含 {spec['maxItems']} 个项目" - - return new_spec - - - async def generate(self, **kwargs) -> dict[str, Any]: # noqa: ANN003 - """调用大模型,生成JSON参数""" - spec: dict[str, Any] = kwargs["spec"] - strict = kwargs.get("strict", True) - if not strict: - spec = deepcopy(spec) - spec = Json._unstrict_spec(spec) - - # 转换对话记录 - conversation_str = "" - for item in kwargs["conversation"]: - if item["role"] == "user": - conversation_str += f"{item['content']}" - if item["role"] == "assistant": - conversation_str += f"{item['content']}" - if item["role"] == "tool": - conversation_str += f"{item['content']}" - - env = SandboxedEnvironment( - loader=BaseLoader(), - autoescape=False, - trim_blocks=True, - lstrip_blocks=True, - ) - user_input = env.from_string(self.user_prompt).render( - conversation=conversation_str, - slot_schema=spec, - ) - - # 使用FunctionLLM进行提参 - messages_list = [ - {"role": "user", "content": user_input}, - ] - - # 尝试FunctionCall - result = await FunctionLLM().call( - messages=messages_list, - schema=spec, - max_tokens=Config().get_config().llm.max_tokens, - temperature=Config().get_config().llm.temperature, - ) - - try: - logger.info("[Json] FunctionCall 结果: %s", result) - result = json.loads(result) - except json.JSONDecodeError as e: - err = "JSON解析失败" - raise ValueError(err) from e - - # 移除空值参数 - return Json._remove_null_params(result) diff --git a/apps/llm/patterns/recommend.py b/apps/llm/patterns/recommend.py index 066d3e125f1db6081bf04b80b83ea4347a42e2f3..d02c627c0329333c1d52e8b75e971dff0dd1cd1f 100644 --- a/apps/llm/patterns/recommend.py +++ b/apps/llm/patterns/recommend.py @@ -1,21 +1,28 @@ -""" -使用大模型进行推荐问题生成 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""使用大模型进行推荐问题生成""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +import logging -from typing import Any, ClassVar +from pydantic import BaseModel, Field +from apps.llm.function import JsonGenerator from apps.llm.patterns.core import CorePattern -from apps.llm.patterns.json_gen import Json from apps.llm.reasoning import ReasoningLLM from apps.llm.snippet import convert_context_to_prompt, history_questions_to_prompt +logger = logging.getLogger(__name__) + + +class RecommendResult(BaseModel): + """推荐问题生成结果""" + + predicted_questions: list[str] = Field(description="预测的问题列表") + class Recommend(CorePattern): """使用大模型进行推荐问题生成""" - system_prompt: str = "" + system_prompt: str = "You are a helpful assistant." """系统提示词""" user_prompt: str = r""" @@ -91,21 +98,6 @@ class Recommend(CorePattern): """ """用户提示词""" - slot_schema: ClassVar[dict[str, Any]] = { - "type": "object", - "properties": { - "predicted_questions": { - "type": "array", - "description": "推荐的问题列表", - "items": { - "type": "string", - }, - }, - }, - "required": ["predicted_questions"], - } - """最终输出的JSON Schema""" - def __init__(self, system_prompt: str | None = None, user_prompt: str | None = None) -> None: """初始化推荐问题生成Prompt""" @@ -144,9 +136,13 @@ class Recommend(CorePattern): messages += [{"role": "assistant", "content": result}] - question_dict = await Json().generate(conversation=messages, spec=self.slot_schema) - - if not question_dict or "predicted_questions" not in question_dict or not question_dict["predicted_questions"]: + json_gen = JsonGenerator( + query="根据给定的背景信息,生成预测问题", conversation=messages, schema=RecommendResult.model_json_schema(), + ) + try: + question_dict = RecommendResult.model_validate(await json_gen.generate()) + except Exception: + logger.exception("[Recommend] 推荐问题生成失败") return [] - return question_dict["predicted_questions"] + return question_dict.predicted_questions diff --git a/apps/llm/patterns/rewoo.py b/apps/llm/patterns/rewoo.py index fa64338c2e29d393adf20b22fa10b0a7d3e34193..ef78d92667d30fbd3b26d55ba4d87961181f3d48 100644 --- a/apps/llm/patterns/rewoo.py +++ b/apps/llm/patterns/rewoo.py @@ -1,8 +1,5 @@ -""" -规划生成命令行 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""规划生成命令行""" from apps.llm.patterns.core import CorePattern from apps.llm.reasoning import ReasoningLLM diff --git a/apps/llm/patterns/rewrite.py b/apps/llm/patterns/rewrite.py index fe2e02315b5323269f4f0c5929a9606b0e20272e..d72475fa98aa4d233b1163cbf387067ec123e570 100644 --- a/apps/llm/patterns/rewrite.py +++ b/apps/llm/patterns/rewrite.py @@ -1,15 +1,29 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """问题改写""" -from typing import Any, ClassVar +import logging +from pydantic import BaseModel, Field + +from apps.llm.function import JsonGenerator from apps.llm.patterns.core import CorePattern -from apps.llm.patterns.json_gen import Json from apps.llm.reasoning import ReasoningLLM +logger = logging.getLogger(__name__) + + +class QuestionRewriteResult(BaseModel): + """问题补全与重写结果""" + + question: str = Field(description="补全后的问题") + class QuestionRewrite(CorePattern): """问题补全与重写""" + system_prompt: str = "You are a helpful assistant." + """系统提示词""" + user_prompt: str = r""" @@ -18,7 +32,7 @@ class QuestionRewrite(CorePattern): 1. 请使用JSON格式输出,参考下面给出的样例;不要包含任何XML标签,不要包含任何解释说明; 2. 若用户当前提问内容与对话上文不相关,或你认为用户的提问内容已足够完整,请直接输出用户的提问内容。 3. 补全内容必须精准、恰当,不要编造任何内容。 - + 4. 请输出补全后的问题,不要输出其他内容。 输出格式样例: {{ "question": "补全后的问题" @@ -40,27 +54,16 @@ class QuestionRewrite(CorePattern): """ """用户提示词""" - slot_schema: ClassVar[dict[str, Any]] = { - "type": "object", - "properties": { - "question": { - "type": "string", - "description": "补全后的问题", - }, - }, - "required": ["question"], - } - """最终输出的JSON Schema""" - async def generate(self, **kwargs) -> str: # noqa: ANN003 """问题补全与重写""" + history = kwargs.get("history", []) question = kwargs["question"] messages = [ {"role": "system", "content": self.system_prompt}, {"role": "user", "content": self.user_prompt.format(question=question)}, ] - + messages = history+messages result = "" llm = ReasoningLLM() async for chunk in llm.call(messages, streaming=False): @@ -69,8 +72,15 @@ class QuestionRewrite(CorePattern): self.output_tokens = llm.output_tokens messages += [{"role": "assistant", "content": result}] - question_dict = await Json().generate(conversation=messages, spec=self.slot_schema) - - if not question_dict or "question" not in question_dict or not question_dict["question"]: + json_gen = JsonGenerator( + query="根据给定的背景信息,生成预测问题", + conversation=messages, + schema=QuestionRewriteResult.model_json_schema(), + ) + try: + question_dict = QuestionRewriteResult.model_validate(await json_gen.generate()) + except Exception: + logger.exception("[QuestionRewrite] 问题补全与重写失败") return question - return question_dict["question"] + + return question_dict.question diff --git a/apps/llm/patterns/select.py b/apps/llm/patterns/select.py index 3b9a647e4b3a7e0879c3fa2629c1cc011cf57e27..a6c496bdd0ef79631bbdc41935e717ca3e3668ea 100644 --- a/apps/llm/patterns/select.py +++ b/apps/llm/patterns/select.py @@ -1,8 +1,5 @@ -""" -使用大模型多轮投票,选择最优选项 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""使用大模型多轮投票,选择最优选项""" import asyncio import json @@ -10,8 +7,8 @@ import logging from collections import Counter from typing import Any, ClassVar +from apps.llm.function import JsonGenerator from apps.llm.patterns.core import CorePattern -from apps.llm.patterns.json_gen import Json from apps.llm.reasoning import ReasoningLLM from apps.llm.snippet import choices_to_prompt @@ -21,6 +18,9 @@ logger = logging.getLogger(__name__) class Select(CorePattern): """通过投票选择最佳答案""" + system_prompt: str = "You are a helpful assistant." + """系统提示词""" + user_prompt: str = r""" @@ -86,10 +86,12 @@ class Select(CorePattern): } """最终输出的JSON Schema""" + def __init__(self, system_prompt: str | None = None, user_prompt: str | None = None) -> None: """初始化Prompt""" super().__init__(system_prompt, user_prompt) + async def _generate_single_attempt(self, user_input: str, choice_list: list[str]) -> str: """使用ReasoningLLM进行单次尝试""" logger.info("[Select] 单次选择尝试: %s", user_input) @@ -110,9 +112,15 @@ class Select(CorePattern): schema["properties"]["choice"]["enum"] = choice_list messages += [{"role": "assistant", "content": result}] - function_result = await Json().generate(conversation=messages, spec=schema) + json_gen = JsonGenerator( + query="根据给定的背景信息,生成预测问题", + conversation=messages, + schema=schema, + ) + function_result = await json_gen.generate() return function_result["choice"] + async def generate(self, **kwargs) -> str: # noqa: ANN003 """使用大模型做出选择""" logger.info("[Select] 使用LLM选择") diff --git a/apps/llm/prompt.py b/apps/llm/prompt.py new file mode 100644 index 0000000000000000000000000000000000000000..7cf555be390bc72301323cd103c1a7ec0ab7a085 --- /dev/null +++ b/apps/llm/prompt.py @@ -0,0 +1,71 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""系统提示词模板""" + +from textwrap import dedent + +JSON_GEN_BASIC = dedent(r""" + Respond to the query according to the background information provided. + + # User query + + User query is given in XML tags. + + + {{ query }} + + + # Background + + Background information is given in XML tags. + + + Here are the conversations between you and the user: + + {% if conversation|length > 0 %} + {% for message in conversation %} + <{{ message.role }}> + {{ message.content }} + + {% endfor %} + {% else %} + [No conversation history available.] + {% endif %} + + {% if previous_trial %} + You tried to answer the query with one function, but the arguments are incorrect. + + The arguments you provided are: + + ```json + {{ previous_trial }} + ``` + + And the error information is: + + ``` + {{ err_info }} + ``` + {% endif %} + + + {% if not function_call %} + # Tools + + You must call one function to assist with the user query. + Attention: the key in the JSON object is a JSON pointer, which may contain "/", "~" or ".". + + You are provided with function signatures within XML tags: + + {"type": "function", "function": {"name": "generate", \ +"description": "Generate answer based on the background information", "parameters": {{ schema }}}} + + + Return a json object with function name and arguments within XML tags: + + {"name": , "arguments": } + + + # Output + + {% endif %} +""") diff --git a/apps/llm/reasoning.py b/apps/llm/reasoning.py index 38dc8da8a3e9c5084af8f8cc722a4dbe36ec399a..09f18cbe804ade41de5face6a9a04d1e8c7baf3e 100644 --- a/apps/llm/reasoning.py +++ b/apps/llm/reasoning.py @@ -1,8 +1,5 @@ -""" -问答大模型调用 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""问答大模型调用""" import logging from collections.abc import AsyncGenerator @@ -13,6 +10,7 @@ from openai.types.chat import ChatCompletionChunk from apps.common.config import Config from apps.constants import REASONING_BEGIN_TOKEN, REASONING_END_TOKEN +from apps.entities.config import LLMConfig from apps.llm.token import TokenCalculator logger = logging.getLogger(__name__) @@ -55,25 +53,36 @@ class ReasoningContent: reason = "" text = "" + content = chunk.choices[0].delta.content or "" + if not self.is_reasoning: - text = chunk.choices[0].delta.content or "" + # 非推理模式,直接返回内容作为文本 + text = content return reason, text if self.reasoning_type == "args": if hasattr(chunk.choices[0].delta, "reasoning_content"): reason = chunk.choices[0].delta.reasoning_content or "" # type: ignore[attr-defined] else: + # 推理结束,设置标志并添加结束标签 self.is_reasoning = False reason = "" + # 如果当前内容不是推理内容标签,将其作为文本返回 + if content and not content.startswith(""): + text = content elif self.reasoning_type == "tokens": for token in REASONING_END_TOKEN: - if token == (chunk.choices[0].delta.content or ""): + if token == content: + # 遇到结束标记,推理结束 self.is_reasoning = False reason = "" - text = "" break if self.is_reasoning: - reason = chunk.choices[0].delta.content or "" + # 仍在推理中,将内容作为推理内容 + reason = content + else: + # 推理已结束,将内容作为文本 + text = content return reason, text @@ -84,22 +93,26 @@ class ReasoningLLM: input_tokens: int = 0 output_tokens: int = 0 - def __init__(self) -> None: + def __init__(self, llm_config: LLMConfig | None = None) -> None: """判断配置文件里用了哪种大模型;初始化大模型客户端""" - self._config = Config().get_config() - self._init_client() + if not llm_config: + self._config: LLMConfig = Config().get_config().llm + self._init_client() + else: + self._config: LLMConfig = llm_config + self._init_client() def _init_client(self) -> None: """初始化OpenAI客户端""" - if not self._config.llm.key: + if not self._config.key: self._client = AsyncOpenAI( - base_url=self._config.llm.endpoint, + base_url=self._config.endpoint, ) return self._client = AsyncOpenAI( - api_key=self._config.llm.key, - base_url=self._config.llm.endpoint, + api_key=self._config.key, + base_url=self._config.endpoint, ) @staticmethod @@ -120,18 +133,21 @@ class ReasoningLLM: messages: list[dict[str, str]], max_tokens: int | None, temperature: float | None, + model: str | None = None, ) -> AsyncGenerator[ChatCompletionChunk, None]: """创建流式响应""" + if model is None: + model = self._config.model return await self._client.chat.completions.create( - model=self._config.llm.model, + model=model, messages=messages, # type: ignore[] - max_tokens=max_tokens or self._config.llm.max_tokens, - temperature=temperature or self._config.llm.temperature, + max_tokens=max_tokens or self._config.max_tokens, + temperature=temperature or self._config.temperature, stream=True, stream_options={"include_usage": True}, ) # type: ignore[] - async def call( # noqa: C901, PLR0912 + async def call( # noqa: C901, PLR0912, PLR0913 self, messages: list[dict[str, str]], max_tokens: int | None = None, @@ -139,8 +155,15 @@ class ReasoningLLM: *, streaming: bool = True, result_only: bool = True, + model: str | None = None, ) -> AsyncGenerator[str, None]: """调用大模型,分为流式和非流式两种""" + # 检查max_tokens和temperature + if max_tokens is None: + max_tokens = self._config.max_tokens + if temperature is None: + temperature = self._config.temperature + try: msg_list = self._validate_messages(messages) except ValueError as e: @@ -148,7 +171,7 @@ class ReasoningLLM: logger.exception(err) raise ValueError(err) from e - stream = await self._create_stream(msg_list, max_tokens, temperature) + stream = await self._create_stream(msg_list, max_tokens, temperature, model) reasoning = ReasoningContent() reasoning_content = "" result = "" diff --git a/apps/llm/snippet.py b/apps/llm/snippet.py index 2d4d8e65441cc13d08d436aaebb94c63e7025394..691aa612bfb5cf56b833dd5e778e221513250439 100644 --- a/apps/llm/snippet.py +++ b/apps/llm/snippet.py @@ -1,4 +1,6 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """上下文转提示词""" + from typing import Any diff --git a/apps/llm/token.py b/apps/llm/token.py index cdd2429d29dcc8569f545a0eb05ec49a12b12017..a405a62724ff19312576b7a2ea3ce6b16e65d08e 100644 --- a/apps/llm/token.py +++ b/apps/llm/token.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """用于计算Token消耗量""" from apps.common.singleton import SingletonMeta diff --git a/apps/main.py b/apps/main.py index 702a512cb9c7a534be4c3f54a94e265b07aec25c..92ebc9411ac4687b8bc8fdd93539975b324d355f 100644 --- a/apps/main.py +++ b/apps/main.py @@ -17,7 +17,6 @@ from rich.logging import RichHandler from apps.common.config import Config from apps.common.wordscheck import WordsCheck -from apps.dependency.session import VerifySessionMiddleware from apps.llm.token import TokenCalculator from apps.models.lance import LanceDB from apps.routers import ( @@ -26,13 +25,14 @@ from apps.routers import ( auth, blacklist, chat, - client, comment, conversation, document, flow, health, knowledge, + llm, + mcp_service, record, service, user, @@ -49,7 +49,6 @@ app.add_middleware( allow_methods=["*"], allow_headers=["*"], ) -app.add_middleware(VerifySessionMiddleware) # 关联API路由 app.include_router(conversation.router) app.include_router(auth.router) @@ -60,10 +59,11 @@ app.include_router(comment.router) app.include_router(record.router) app.include_router(health.router) app.include_router(chat.router) -app.include_router(client.router) app.include_router(blacklist.router) app.include_router(document.router) app.include_router(knowledge.router) +app.include_router(llm.router) +app.include_router(mcp_service.router) app.include_router(flow.router) app.include_router(user.router) @@ -85,7 +85,7 @@ async def init_resources() -> None: """初始化必要资源""" WordsCheck() await LanceDB().init() - await Pool().init() + await Pool.init() TokenCalculator() # 运行 diff --git a/apps/manager/__init__.py b/apps/manager/__init__.py index 95326997dffee25f4b06a813c51eefd8dd9097ec..8fd10ea626011a3ed852b8367e494a202e88f0f9 100644 --- a/apps/manager/__init__.py +++ b/apps/manager/__init__.py @@ -1,5 +1,2 @@ -""" -Manager模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Manager模块""" diff --git a/apps/manager/api_key.py b/apps/manager/api_key.py index 128484c72e210bf47900e4f11395fd1d11b5f927..b22b14c20bac5eb39e5fbd243aa380943b97c0d8 100644 --- a/apps/manager/api_key.py +++ b/apps/manager/api_key.py @@ -1,8 +1,5 @@ -""" -API Key Manager - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""API Key管理""" import hashlib import logging @@ -24,11 +21,12 @@ class ApiKeyManager: :param user_sub: 用户名 :return: API Key """ + mongo = MongoDB() api_key = str(uuid.uuid4().hex) api_key_hash = hashlib.sha256(api_key.encode()).hexdigest()[:16] try: - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") await user_collection.update_one( {"_id": user_sub}, {"$set": {"api_key": api_key_hash}}, @@ -47,10 +45,11 @@ class ApiKeyManager: :param user_sub: 用户ID :return: 删除API Key是否成功 """ + mongo = MongoDB() if not await ApiKeyManager.api_key_exists(user_sub): return False try: - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") await user_collection.update_one( {"_id": user_sub}, {"$unset": {"api_key": ""}}, @@ -68,8 +67,9 @@ class ApiKeyManager: :param user_sub: 用户ID :return: API Key是否存在 """ + mongo = MongoDB() try: - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") user_data = await user_collection.find_one({"_id": user_sub}, {"_id": 0, "api_key": 1}) return user_data is not None and ("api_key" in user_data and user_data["api_key"]) except Exception: @@ -84,9 +84,10 @@ class ApiKeyManager: :param api_key: API Key :return: 用户ID """ + mongo = MongoDB() api_key_hash = hashlib.sha256(api_key.encode()).hexdigest()[:16] try: - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") user_data = await user_collection.find_one({"api_key": api_key_hash}, {"_id": 1}) return user_data["_id"] if user_data else None except Exception: @@ -101,9 +102,10 @@ class ApiKeyManager: :param api_key: API Key :return: 验证API Key是否成功 """ + mongo = MongoDB() api_key_hash = hashlib.sha256(api_key.encode()).hexdigest()[:16] try: - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") key_data = await user_collection.find_one({"api_key": api_key_hash}, {"_id": 1}) except Exception: logger.exception("[ApiKeyManager] 验证API Key失败") @@ -119,12 +121,13 @@ class ApiKeyManager: :param user_sub: 用户ID :return: 更新后的API Key """ + mongo = MongoDB() if not await ApiKeyManager.api_key_exists(user_sub): return None api_key = str(uuid.uuid4().hex) api_key_hash = hashlib.sha256(api_key.encode()).hexdigest()[:16] try: - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") await user_collection.update_one( {"_id": user_sub}, {"$set": {"api_key": api_key_hash}}, diff --git a/apps/manager/appcenter.py b/apps/manager/appcenter.py index 6e45308a5fb92ccfc00e57e13b4104691b19fc63..b7f89e706c4f2ade30f3cd5c727b6632074c05df 100644 --- a/apps/manager/appcenter.py +++ b/apps/manager/appcenter.py @@ -1,18 +1,15 @@ -""" -应用中心 Manager - -Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. +"""应用中心 Manager""" import logging import uuid from datetime import UTC, datetime -import re from typing import Any +from apps.entities.agent import AgentAppMetadata from apps.entities.appcenter import AppCenterCardItem, AppData from apps.entities.collection import User -from apps.entities.enum_var import SearchType +from apps.entities.enum_var import AppType, SearchType from apps.entities.flow import AppMetadata, MetadataType, Permission from apps.entities.pool import AppPool from apps.entities.response_data import RecentAppList, RecentAppListItem @@ -64,6 +61,7 @@ class AppCenterManager: return [ AppCenterCardItem( appId=app.id, + appType=app.app_type, icon=app.icon, name=app.name, description=app.description, @@ -100,8 +98,7 @@ class AppCenterManager: if search_type == SearchType.AUTHOR: if keyword is not None and keyword not in user_sub: return [], 0 - else: - keyword = user_sub + keyword = user_sub base_filter = {"author": user_sub} filters: dict[str, Any] = ( AppCenterManager._build_filters( @@ -117,6 +114,7 @@ class AppCenterManager: return [ AppCenterCardItem( appId=app.id, + appType=app.app_type, icon=app.icon, name=app.name, description=app.description, @@ -168,6 +166,7 @@ class AppCenterManager: return [ AppCenterCardItem( appId=app.id, + appType=app.app_type, icon=app.icon, name=app.name, description=app.description, @@ -189,7 +188,8 @@ class AppCenterManager: :param app_id: 应用ID :return: 应用元数据 """ - app_collection = MongoDB.get_collection("app") + mongo = MongoDB() + app_collection = mongo.get_collection("app") db_data = await app_collection.find_one({"_id": app_id}) if not db_data: msg = "App not found" @@ -206,22 +206,44 @@ class AppCenterManager: :return: 应用ID """ app_id = str(uuid.uuid4()) - metadata = AppMetadata( - type=MetadataType.APP, - id=app_id, - icon=data.icon, - name=data.name, - description=data.description, - version="1.0.0", - author=user_sub, - links=data.links, - first_questions=data.first_questions, - history_len=data.history_len, - permission=Permission( - type=data.permission.type, - users=data.permission.users or [], - ), - ) + if data.app_type == AppType.FLOW: + metadata = AppMetadata( + app_type=data.app_type, + type=MetadataType.APP, + id=app_id, + icon=data.icon, + name=data.name, + description=data.description, + version="1.0.0", + author=user_sub, + links=data.links, + first_questions=data.first_questions, + history_len=data.history_len, + permission=Permission( + type=data.permission.type, + users=data.permission.users or [], + ), + ) + elif data.app_type == AppType.AGENT: + metadata = AgentAppMetadata( + app_type=data.app_type, + type=MetadataType.APP, + id=app_id, + icon=data.icon, + name=data.name, + description=data.description, + version="1.0.0", + author=user_sub, + mcpService=data.mcp_service, + history_len=data.history_len, + permission=Permission( + type=data.permission.type, + users=data.permission.users or [], + ), + ) + else: + msg = "Invalid app type" + raise ValueError(msg) app_loader = AppLoader() await app_loader.save(metadata, app_id) return app_id @@ -235,23 +257,8 @@ class AppCenterManager: :param app_id: 应用唯一标识 :param data: 应用数据 """ - metadata = AppMetadata( - type=MetadataType.APP, - id=app_id, - icon=data.icon, - name=data.name, - description=data.description, - version="1.0.0", - author=user_sub, - links=data.links, - first_questions=data.first_questions, - history_len=data.history_len, - permission=Permission( - type=data.permission.type, - users=data.permission.users or [], - ), - ) - app_collection = MongoDB.get_collection("app") + mongo = MongoDB() + app_collection = mongo.get_collection("app") app_data = AppPool.model_validate(await app_collection.find_one({"_id": app_id})) if not app_data: msg = "App not found" @@ -259,20 +266,60 @@ class AppCenterManager: if app_data.author != user_sub: msg = "Permission denied" raise InstancePermissionError(msg) - metadata.flows = app_data.flows - metadata.published = app_data.published + if app_data.app_type != data.app_type: + err = "Can not change app type" + raise ValueError(err) + if data.app_type == AppType.FLOW: + metadata = AppMetadata( + type=MetadataType.APP, + id=app_id, + icon=data.icon, + name=data.name, + description=data.description, + version="1.0.0", + author=user_sub, + links=data.links, + first_questions=data.first_questions, + history_len=data.history_len, + permission=Permission( + type=data.permission.type, + users=data.permission.users or [], + ), + ) + metadata.flows = app_data.flows + metadata.published = app_data.published + elif data.app_type == AppType.AGENT: + metadata = AgentAppMetadata( + type=MetadataType.APP, + id=app_id, + icon=data.icon, + name=data.name, + description=data.description, + version="1.0.0", + author=user_sub, + mcpService=data.mcp_service, + history_len=data.history_len, + permission=Permission( + type=data.permission.type, + users=data.permission.users or [], + ), + ) + else: + msg = "Invalid app type" + raise ValueError(msg) app_loader = AppLoader() await app_loader.save(metadata, app_id) @staticmethod - async def update_app_publish_status(app_id: str, user_sub: str) -> None: + async def update_app_publish_status(app_id: str, user_sub: str) -> bool: """ 发布应用 :param app_id: 应用唯一标识 :param user_sub: 用户唯一标识 """ - app_collection = MongoDB.get_collection("app") + mongo = MongoDB() + app_collection = mongo.get_collection("app") app_data = AppPool.model_validate(await app_collection.find_one({"_id": app_id})) if not app_data: msg = "App not found" @@ -289,21 +336,41 @@ class AppCenterManager: {"_id": app_id}, {"$set": {"published": published}}, ) - metadata = AppMetadata( - type=MetadataType.APP, - id=app_id, - icon=app_data.icon, - name=app_data.name, - description=app_data.description, - version="1.0.0", - author=user_sub, - links=app_data.links, - first_questions=app_data.first_questions, - history_len=app_data.history_len, - permission=app_data.permission, - published=published, - flows=app_data.flows, - ) + if app_data.app_type == AppType.FLOW: + metadata = AppMetadata( + type=MetadataType.APP, + id=app_id, + icon=app_data.icon, + name=app_data.name, + description=app_data.description, + version="1.0.0", + author=user_sub, + links=app_data.links, + first_questions=app_data.first_questions, + history_len=app_data.history_len, + permission=app_data.permission, + published=published, + flows=app_data.flows, + ) + metadata.flows = app_data.flows + metadata.published = app_data.published + elif app_data.app_type == AppType.AGENT: + metadata = AgentAppMetadata( + type=MetadataType.APP, + id=app_id, + icon=app_data.icon, + name=app_data.name, + description=app_data.description, + version="1.0.0", + author=user_sub, + mcpService=app_data.mcp_service, + history_len=app_data.history_len, + permission=app_data.permission, + published=published, + ) + else: + msg = "Invalid app type" + raise ValueError(msg) app_loader = AppLoader() await app_loader.save(metadata, app_id) return published @@ -317,8 +384,9 @@ class AppCenterManager: :param user_sub: 用户唯一标识 :param favorited: 是否收藏 """ - app_collection = MongoDB.get_collection("app") - user_collection = MongoDB.get_collection("user") + mongo = MongoDB() + app_collection = mongo.get_collection("app") + user_collection = mongo.get_collection("user") db_data = await app_collection.find_one({"_id": app_id}) if not db_data: msg = "App not found" @@ -353,7 +421,8 @@ class AppCenterManager: :param app_id: 应用唯一标识 :param user_sub: 用户唯一标识 """ - app_collection = MongoDB.get_collection("app") + mongo = MongoDB() + app_collection = mongo.get_collection("app") app_data = AppPool.model_validate(await app_collection.find_one({"_id": app_id})) if not app_data: msg = "App not found" @@ -362,8 +431,7 @@ class AppCenterManager: msg = "Permission denied" raise InstancePermissionError(msg) # 删除应用 - app_loader = AppLoader() - await app_loader.delete(app_id) + await AppLoader.delete(app_id) # 删除应用相关的工作流 for flow in app_data.flows: await FlowManager.delete_flow_by_app_and_flow_id(app_id, flow.id) @@ -377,8 +445,9 @@ class AppCenterManager: :param user_sub: 用户唯一标识 :return: 最近使用的应用列表 """ - user_collection = MongoDB.get_collection("user") - app_collection = MongoDB.get_collection("app") + mongo = MongoDB() + user_collection = mongo.get_collection("user") + app_collection = mongo.get_collection("app") # 校验用户信息 user_data = User.model_validate(await user_collection.find_one({"_id": user_sub})) # 获取最近使用的应用ID列表,按最后使用时间倒序排序 @@ -411,7 +480,8 @@ class AppCenterManager: if not app_id: return True try: - user_collection = MongoDB.get_collection("user") + mongo = MongoDB() + user_collection = mongo.get_collection("user") current_time = round(datetime.now(UTC).timestamp(), 3) result = await user_collection.update_one( {"_id": user_sub}, # 查询条件 @@ -443,7 +513,8 @@ class AppCenterManager: :return: 默认工作流ID """ try: - app_collection = MongoDB.get_collection("app") + mongo = MongoDB() + app_collection = mongo.get_collection("app") db_data = await app_collection.find_one({"_id": app_id}) if not db_data: logger.warning("[AppCenterManager] 应用不存在: %s", app_id) @@ -486,7 +557,8 @@ class AppCenterManager: ) -> tuple[list[AppPool], int]: """根据过滤条件搜索应用并计算总页数""" try: - app_collection = MongoDB.get_collection("app") + mongo = MongoDB() + app_collection = mongo.get_collection("app") total_apps = await app_collection.count_documents(search_conditions) db_data = ( await app_collection.find(search_conditions) @@ -506,7 +578,8 @@ class AppCenterManager: async def _get_favorite_app_ids_by_user(user_sub: str) -> list[str]: """获取用户收藏的应用ID""" try: - user_collection = MongoDB.get_collection("user") + mongo = MongoDB() + user_collection = mongo.get_collection("user") user_data = User.model_validate(await user_collection.find_one({"_id": user_sub})) except Exception: logger.exception("[AppCenterManager] 获取用户收藏应用ID失败") diff --git a/apps/manager/application.py b/apps/manager/application.py index be9ceb1cbb23808fa9e2f6f1c55b7745af253079..5ba2ad9f71541858dcb1f145d4863545318f68fb 100644 --- a/apps/manager/application.py +++ b/apps/manager/application.py @@ -1,8 +1,5 @@ -""" -应用管理器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""应用管理器""" import logging @@ -25,7 +22,8 @@ class AppManager: :return: 如果用户具有所需权限则返回True,否则返回False """ try: - app_collection = MongoDB.get_collection("app") + mongo = MongoDB() + app_collection = mongo.get_collection("app") query = { "_id": app_id, "$or": [ @@ -57,7 +55,8 @@ class AppManager: :return: 如果应用属于用户则返回True,否则返回False """ try: - app_collection = MongoDB.get_collection("app") # 获取应用集合' + mongo = MongoDB() + app_collection = mongo.get_collection("app") # 获取应用集合' query = { "_id": app_id, "author": user_sub, diff --git a/apps/manager/audit_log.py b/apps/manager/audit_log.py index 10dc2e25878ad19594e9eaa36a26e69899b9ce4a..790f5fcd4dc2f3e03e49391a25aef270a1fcc4db 100644 --- a/apps/manager/audit_log.py +++ b/apps/manager/audit_log.py @@ -1,8 +1,5 @@ -""" -审计日志Manager - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""审计日志Manager""" import logging @@ -23,7 +20,7 @@ class AuditLogManager: :return: 是否添加成功;True/False """ try: - collection = MongoDB.get_collection("audit") + collection = MongoDB().get_collection("audit") await collection.insert_one(data.model_dump(by_alias=True)) except Exception: logger.exception("[AuditLogManager] 添加审计日志失败") diff --git a/apps/manager/blacklist.py b/apps/manager/blacklist.py index a1481caf55014403b3fe6f287a8321adbae9528c..f8cdc5b59146546a9d384ef6671932c411f0527a 100644 --- a/apps/manager/blacklist.py +++ b/apps/manager/blacklist.py @@ -1,8 +1,5 @@ -""" -黑名单相关操作 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""黑名单相关操作""" import logging import re @@ -25,7 +22,7 @@ class QuestionBlacklistManager: async def check_blacklisted_questions(input_question: str) -> bool: """给定问题,查找问题是否在黑名单里""" try: - blacklist_collection = MongoDB.get_collection("blacklist") + blacklist_collection = MongoDB().get_collection("blacklist") result = await blacklist_collection.find_one( {"question": {"$regex": f"/{re.escape(input_question)}/i"}, "is_audited": True}, {"_id": 1}, ) @@ -50,7 +47,7 @@ class QuestionBlacklistManager: is_deletion标识是否为删除操作 """ try: - blacklist_collection = MongoDB.get_collection("blacklist") + blacklist_collection = MongoDB().get_collection("blacklist") if is_deletion: await blacklist_collection.find_one_and_delete({"_id": blacklist_id}) @@ -74,7 +71,7 @@ class QuestionBlacklistManager: async def get_blacklisted_questions(limit: int, offset: int, *, is_audited: bool) -> list[Blacklist]: """分页式获取目前所有的问题(待审核或已拉黑)黑名单""" try: - blacklist_collection = MongoDB.get_collection("blacklist") + blacklist_collection = MongoDB().get_collection("blacklist") return [ Blacklist.model_validate(item) async for item in blacklist_collection.find({"is_audited": is_audited}).skip(offset).limit(limit) @@ -92,7 +89,7 @@ class UserBlacklistManager: async def get_blacklisted_users(limit: int, offset: int) -> list[str]: """获取当前所有黑名单用户""" try: - user_collection = MongoDB.get_collection("user") + user_collection = MongoDB().get_collection("user") return [ user["_id"] async for user in user_collection.find({"credit": {"$lte": 0}}, {"_id": 1}) @@ -108,7 +105,7 @@ class UserBlacklistManager: async def check_blacklisted_users(user_sub: str) -> bool: """检测某用户是否已被拉黑""" try: - user_collection = MongoDB.get_collection("user") + user_collection = MongoDB().get_collection("user") result = await user_collection.find_one( {"user_sub": user_sub, "credit": {"$lte": 0}, "is_whitelisted": False}, {"_id": 1}, ) @@ -126,7 +123,7 @@ class UserBlacklistManager: """修改用户的信用分""" try: # 获取用户当前信用分 - user_collection = MongoDB.get_collection("user") + user_collection = MongoDB().get_collection("user") result = await user_collection.find_one({"user_sub": user_sub}, {"_id": 0, "credit": 1}) # 用户不存在 if result is None: @@ -172,7 +169,7 @@ class AbuseManager: """存储用户举报详情""" try: # 判断record_id是否合法 - record_group_collection = MongoDB.get_collection("record_group") + record_group_collection = MongoDB().get_collection("record_group") record = await record_group_collection.aggregate( [ {"$match": {"user_sub": user_sub}}, @@ -193,7 +190,7 @@ class AbuseManager: record_data = RecordContent.model_validate_json(record_data) # 检查该条目类似内容是否已被举报过 - blacklist_collection = MongoDB.get_collection("question_blacklist") + blacklist_collection = MongoDB().get_collection("question_blacklist") query = await blacklist_collection.find_one({"_id": record_id}) if query is not None: logger.info("[AbuseManager] 问题已被举报过") @@ -220,7 +217,7 @@ class AbuseManager: async def audit_abuse_report(question_id: str, *, is_deletion: bool = False) -> bool: """对某一特定的待审问题进行操作,包括批准审核与删除未审问题""" try: - blacklist_collection = MongoDB.get_collection("blacklist") + blacklist_collection = MongoDB().get_collection("blacklist") if is_deletion: await blacklist_collection.delete_one({"_id": question_id, "is_audited": False}) return True diff --git a/apps/manager/comment.py b/apps/manager/comment.py index bd1d79de2acf9e6395f213b24baa1aada6050233..01307499b5ee0710d6a6c014ed5f245481621dbd 100644 --- a/apps/manager/comment.py +++ b/apps/manager/comment.py @@ -1,8 +1,5 @@ -""" -评论 Manager - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""评论 Manager""" import logging @@ -24,7 +21,7 @@ class CommentManager: :return: 评论内容 """ try: - record_group_collection = MongoDB.get_collection("record_group") + record_group_collection = MongoDB().get_collection("record_group") result = await record_group_collection.aggregate( [ {"$match": {"_id": group_id, "records.id": record_id}}, @@ -50,7 +47,7 @@ class CommentManager: :return: 是否更新成功;True/False """ try: - record_group_collection = MongoDB.get_collection("record_group") + record_group_collection = MongoDB().get_collection("record_group") await record_group_collection.update_one( {"_id": group_id, "records.id": record_id}, {"$set": {"records.$.comment": data.model_dump(by_alias=True)}}, diff --git a/apps/manager/conversation.py b/apps/manager/conversation.py index ba3e09cbc6afc9c45479376a6559d5036569d888..39aa4789ffe8e4fdc4e8f6af32a28b0113e445cd 100644 --- a/apps/manager/conversation.py +++ b/apps/manager/conversation.py @@ -1,17 +1,18 @@ -""" -对话 Manager - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""对话 Manager""" import logging import uuid from datetime import UTC, datetime from typing import Any -from apps.entities.collection import Conversation +from apps.common.config import Config +from apps.entities.collection import Conversation, KnowledgeBaseItem, LLMItem +from apps.manager.knowledge import KnowledgeBaseManager +from apps.manager.llm import LLMManager from apps.manager.task import TaskManager from apps.models.mongo import MongoDB +from apps.templates.generate_llm_operator_config import llm_provider_dict logger = logging.getLogger(__name__) @@ -23,7 +24,7 @@ class ConversationManager: async def get_conversation_by_user_sub(user_sub: str) -> list[Conversation]: """根据用户ID获取对话列表,按时间由近到远排序""" try: - conv_collection = MongoDB.get_collection("conversation") + conv_collection = MongoDB().get_collection("conversation") return [ Conversation(**conv) async for conv in conv_collection.find({"user_sub": user_sub, "debug": False}).sort({"created_at": 1}) @@ -36,7 +37,7 @@ class ConversationManager: async def get_conversation_by_conversation_id(user_sub: str, conversation_id: str) -> Conversation | None: """通过ConversationID查询对话信息""" try: - conv_collection = MongoDB.get_collection("conversation") + conv_collection = MongoDB().get_collection("conversation") result = await conv_collection.find_one({"_id": conversation_id, "user_sub": user_sub}) if not result: return None @@ -46,20 +47,50 @@ class ConversationManager: return None @staticmethod - async def add_conversation_by_user_sub(user_sub: str, app_id: str, *, debug: bool) -> Conversation | None: + async def add_conversation_by_user_sub( + user_sub: str, app_id: str, llm_id: str, kb_ids: list[str], + *, debug: bool) -> Conversation | None: """通过用户ID新建对话""" + if llm_id == "empty": + llm_item = LLMItem( + llm_id="empty", + model_name=Config().get_config().llm.model, + icon=llm_provider_dict["ollama"]["icon"], + ) + else: + llm = await LLMManager.get_llm_by_id(user_sub, llm_id) + if llm is None: + logger.error("[ConversationManager] 获取大模型失败") + return None + llm_item = LLMItem( + llm_id=llm.id, + model_name=llm.model_name, + ) + kb_item_list = [] + team_kb_list = await KnowledgeBaseManager.get_team_kb_list_from_rag(user_sub, None, None) + for team_kb in team_kb_list: + for kb in team_kb["kbList"]: + if str(kb["kbId"]) in kb_ids: + kb_item = KnowledgeBaseItem( + kb_id=kb["kbId"], + kb_name=kb["kbName"], + ) + kb_item_list.append(kb_item) conversation_id = str(uuid.uuid4()) conv = Conversation( _id=conversation_id, user_sub=user_sub, app_id=app_id, + llm=llm_item, + kb_list=kb_item_list, debug=debug if debug else False, ) + mongo = MongoDB() try: - async with MongoDB.get_session() as session, await session.start_transaction(): - conv_collection = MongoDB.get_collection("conversation") + async with mongo.get_session() as session, await session.start_transaction(): + conv_collection = mongo.get_collection("conversation") await conv_collection.insert_one(conv.model_dump(by_alias=True), session=session) - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") update_data: dict[str, dict[str, Any]] = { "$push": {"conversations": conversation_id}, } @@ -85,7 +116,7 @@ class ConversationManager: async def update_conversation_by_conversation_id(user_sub: str, conversation_id: str, data: dict[str, Any]) -> bool: """通过ConversationID更新对话信息""" try: - conv_collection = MongoDB.get_collection("conversation") + conv_collection = MongoDB().get_collection("conversation") result = await conv_collection.update_one( {"_id": conversation_id, "user_sub": user_sub}, {"$set": data}, @@ -99,11 +130,12 @@ class ConversationManager: @staticmethod async def delete_conversation_by_conversation_id(user_sub: str, conversation_id: str) -> bool: """通过ConversationID删除对话""" - user_collection = MongoDB.get_collection("user") - conv_collection = MongoDB.get_collection("conversation") - record_group_collection = MongoDB.get_collection("record_group") + mongo = MongoDB() + user_collection = mongo.get_collection("user") + conv_collection = mongo.get_collection("conversation") + record_group_collection = mongo.get_collection("record_group") try: - async with MongoDB.get_session() as session, await session.start_transaction(): + async with mongo.get_session() as session, await session.start_transaction(): conversation_data = await conv_collection.find_one_and_delete( {"_id": conversation_id, "user_sub": user_sub}, session=session, ) diff --git a/apps/manager/document.py b/apps/manager/document.py index 0bff049aa9daf602396818ac2bc98e14709f5b60..d51103a0782c33904dd810d02506917696d9e816 100644 --- a/apps/manager/document.py +++ b/apps/manager/document.py @@ -1,8 +1,5 @@ -""" -文件Manager - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""文件Manager""" import base64 import logging @@ -58,8 +55,8 @@ class DocumentManager: async def storage_docs(cls, user_sub: str, conversation_id: str, documents: list[UploadFile]) -> list[Document]: """存储多个文件""" uploaded_files = [] - doc_collection = MongoDB.get_collection("document") - conversation_collection = MongoDB.get_collection("conversation") + doc_collection = MongoDB().get_collection("document") + conversation_collection = MongoDB().get_collection("conversation") for document in documents: try: if document.filename is None or document.size is None: @@ -95,8 +92,8 @@ class DocumentManager: @classmethod async def get_unused_docs(cls, user_sub: str, conversation_id: str) -> list[Document]: """获取Conversation中未使用的文件""" - conv_collection = MongoDB.get_collection("conversation") - doc_collection = MongoDB.get_collection("document") + conv_collection = MongoDB().get_collection("conversation") + doc_collection = MongoDB().get_collection("document") try: conv = await conv_collection.find_one({"_id": conversation_id, "user_sub": user_sub}) @@ -113,8 +110,8 @@ class DocumentManager: @classmethod async def get_used_docs_by_record_group(cls, user_sub: str, record_group_id: str) -> list[RecordDocument]: """获取RecordGroup关联的文件""" - record_group_collection = MongoDB.get_collection("record_group") - docs_collection = MongoDB.get_collection("document") + record_group_collection = MongoDB().get_collection("record_group") + docs_collection = MongoDB().get_collection("document") try: record_group = await record_group_collection.find_one({"_id": record_group_id, "user_sub": user_sub}) if not record_group: @@ -142,8 +139,8 @@ class DocumentManager: @classmethod async def get_used_docs(cls, user_sub: str, conversation_id: str, record_num: int | None = 10) -> list[Document]: """获取最后n次问答所用到的文件""" - docs_collection = MongoDB.get_collection("document") - record_group_collection = MongoDB.get_collection("record_group") + docs_collection = MongoDB().get_collection("document") + record_group_collection = MongoDB().get_collection("record_group") try: if record_num: record_groups = ( @@ -176,10 +173,11 @@ class DocumentManager: @classmethod async def delete_document(cls, user_sub: str, document_list: list[str]) -> bool: """从未使用文件列表中删除一个文件""" - doc_collection = MongoDB.get_collection("document") - conv_collection = MongoDB.get_collection("conversation") + mongo = MongoDB() + doc_collection = mongo.get_collection("document") + conv_collection = mongo.get_collection("conversation") try: - async with MongoDB.get_session() as session, await session.start_transaction(): + async with mongo.get_session() as session, await session.start_transaction(): for doc in document_list: doc_info = await doc_collection.find_one_and_delete( {"_id": doc, "user_sub": user_sub}, session=session, @@ -211,10 +209,11 @@ class DocumentManager: @classmethod async def delete_document_by_conversation_id(cls, user_sub: str, conversation_id: str) -> list[str]: """通过ConversationID删除文件""" - doc_collection = MongoDB.get_collection("document") + mongo = MongoDB() + doc_collection = mongo.get_collection("document") doc_ids = [] try: - async with MongoDB.get_session() as session, await session.start_transaction(): + async with mongo.get_session() as session, await session.start_transaction(): async for doc in doc_collection.find( {"user_sub": user_sub, "conversation_id": conversation_id}, session=session, ): @@ -231,14 +230,16 @@ class DocumentManager: @classmethod async def get_doc_count(cls, user_sub: str, conversation_id: str) -> int: """获取对话文件数量""" - doc_collection = MongoDB.get_collection("document") + mongo = MongoDB() + doc_collection = mongo.get_collection("document") return await doc_collection.count_documents({"user_sub": user_sub, "conversation_id": conversation_id}) @classmethod async def change_doc_status(cls, user_sub: str, conversation_id: str, record_group_id: str) -> None: """文件状态由unused改为used""" - record_group_collection = MongoDB.get_collection("record_group") - conversation_collection = MongoDB.get_collection("conversation") + mongo = MongoDB() + record_group_collection = mongo.get_collection("record_group") + conversation_collection = mongo.get_collection("conversation") try: # 查找Conversation中的unused_docs conversation = await conversation_collection.find_one({"user_sub": user_sub, "_id": conversation_id}) @@ -263,7 +264,7 @@ class DocumentManager: @classmethod async def save_answer_doc(cls, user_sub: str, record_group_id: str, doc_ids: list[str]) -> None: """保存与答案关联的文件""" - record_group_collection = MongoDB.get_collection("record_group") + record_group_collection = MongoDB().get_collection("record_group") try: for doc_id in doc_ids: doc_info = RecordGroupDocument(_id=doc_id, associated="answer") diff --git a/apps/manager/domain.py b/apps/manager/domain.py index a1701b883551f7fb8e0f74119732217ac8cdb723..2e4381bd96ba03dced49be3be56de9e830637f08 100644 --- a/apps/manager/domain.py +++ b/apps/manager/domain.py @@ -1,8 +1,5 @@ -""" -画像领域管理 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""画像领域管理""" import logging from datetime import UTC, datetime @@ -25,7 +22,7 @@ class DomainManager: :return: 领域信息列表 """ try: - domain_collection = MongoDB.get_collection("domain") + domain_collection = MongoDB().get_collection("domain") return [Domain(**domain) async for domain in domain_collection.find()] except Exception: logger.exception("[DomainManager] 获取领域失败") @@ -40,7 +37,7 @@ class DomainManager: :return: 领域信息 """ try: - domain_collection = MongoDB.get_collection("domain") + domain_collection = MongoDB().get_collection("domain") domain_data = await domain_collection.find_one({"domain_name": domain_name}) if domain_data: return Domain(**domain_data) @@ -62,7 +59,7 @@ class DomainManager: name=domain_data.domain_name, definition=domain_data.domain_description, ) - domain_collection = MongoDB.get_collection("domain") + domain_collection = MongoDB().get_collection("domain") await domain_collection.insert_one(domain.model_dump(by_alias=True)) except Exception: logger.exception("[DomainManager] 添加领域失败") @@ -83,7 +80,7 @@ class DomainManager: "definition": domain_data.domain_description, "updated_at": round(datetime.now(tz=UTC).timestamp(), 3), } - domain_collection = MongoDB.get_collection("domain") + domain_collection = MongoDB().get_collection("domain") await domain_collection.update_one( {"name": domain_data.domain_name}, {"$set": update_dict}, @@ -102,7 +99,7 @@ class DomainManager: :return: 删除成功返回True,否则返回False """ try: - domain_collection = MongoDB.get_collection("domain") + domain_collection = MongoDB().get_collection("domain") await domain_collection.delete_one({"name": domain_data.domain_name}) except Exception: logger.exception("[DomainManager] 通过领域名称删除领域失败") diff --git a/apps/manager/flow.py b/apps/manager/flow.py index 41742a87be431c9f00915be62d860e769a850884..babc34f40cc70df53ff2c4efa6bceff9e333986f 100644 --- a/apps/manager/flow.py +++ b/apps/manager/flow.py @@ -1,8 +1,5 @@ -""" -flow Manager - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""flow Manager""" import logging @@ -39,8 +36,8 @@ class FlowManager: :param service_id: 服务id :return: 如果用户具有所需权限则返回True,否则返回False """ - node_pool_collection = MongoDB.get_collection("node") - service_collection = MongoDB.get_collection("service") + node_pool_collection = MongoDB().get_collection("node") + service_collection = MongoDB().get_collection("service") try: node_pool_record = await node_pool_collection.find_one({"_id": node_meta_data_id}) @@ -79,7 +76,7 @@ class FlowManager: :param service_id: 服务id :return: 节点元数据的列表 """ - node_pool_collection = MongoDB.get_collection("node") # 获取节点集合 + node_pool_collection = MongoDB().get_collection("node") # 获取节点集合 try: cursor = node_pool_collection.find({"service_id": service_id}).sort("created_at", ASCENDING) @@ -119,8 +116,8 @@ class FlowManager: :user_sub: 用户的唯一标识符 :return: service的列表 """ - service_collection = MongoDB.get_collection("service") - user_collection = MongoDB.get_collection("user") + service_collection = MongoDB().get_collection("service") + user_collection = MongoDB().get_collection("user") try: db_result = await user_collection.find_one({"_id": user_sub}) user = User.model_validate(db_result) @@ -182,7 +179,7 @@ class FlowManager: :param node_meta_data_id: node_meta_data的id :return: node meta data id对应的节点源数据信息 """ - node_pool_collection = MongoDB.get_collection("node") # 获取节点集合 + node_pool_collection = MongoDB().get_collection("node") # 获取节点集合 try: node_pool_record = await node_pool_collection.find_one({"_id": node_meta_data_id}) if node_pool_record is None: @@ -206,7 +203,7 @@ class FlowManager: return None @staticmethod - async def get_flow_by_app_and_flow_id(app_id: str, flow_id: str) -> FlowItem | None: + async def get_flow_by_app_and_flow_id(app_id: str, flow_id: str) -> FlowItem | None: # noqa: C901, PLR0911, PLR0912 """ 通过appId flowId获取flow config的路径和focus,并通过flow config的路径获取flow config,并将其转换为flow item。 @@ -215,7 +212,7 @@ class FlowManager: :return: 流的item和用户在这个流上的视觉焦点 """ try: - app_collection = MongoDB.get_collection("app") + app_collection = MongoDB().get_collection("app") app_record = await app_collection.find_one({"_id": app_id}) if app_record is None: logger.error("[FlowManager] 应用 %s 不存在", app_id) @@ -368,7 +365,7 @@ class FlowManager: :return: 流的id """ try: - app_collection = MongoDB.get_collection("app") + app_collection = MongoDB().get_collection("app") app_record = await app_collection.find_one({"_id": app_id}) if app_record is None: logger.error("[FlowManager] 应用 %s 不存在", app_id) @@ -414,9 +411,8 @@ class FlowManager: if old_flow_config is None: error_msg = f"[FlowManager] 流 {flow_id} 不存在;可能为新创建" logger.error(error_msg) - else: - if flow_config.debug: - flow_config.debug = await FlowManager.is_flow_config_equal(old_flow_config, flow_config) + elif flow_config.debug: + flow_config.debug = await FlowManager.is_flow_config_equal(old_flow_config, flow_config) await flow_loader.save(app_id, flow_id, flow_config) except Exception: logger.exception("[FlowManager] 存储/更新流失败") @@ -435,7 +431,7 @@ class FlowManager: """ try: - app_collection = MongoDB.get_collection("app") + app_collection = MongoDB().get_collection("app") key = f"flow/{flow_id}.yaml" await app_collection.update_one({"_id": app_id}, {"$unset": {f"hashes.{key}": ""}}) await app_collection.update_one({"_id": app_id}, {"$pull": {"flows": {"id": flow_id}}}) diff --git a/apps/manager/knowledge.py b/apps/manager/knowledge.py index f607b28161bcc5fc9ad8a0c878a1a7a47a967565..2548051409e148281af47fe65eaa4e4f96531710 100644 --- a/apps/manager/knowledge.py +++ b/apps/manager/knowledge.py @@ -1,11 +1,17 @@ -""" -用户资产库管理 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""用户资产库管理""" import logging +from typing import Any + +import httpx +from fastapi import status +from apps.common.config import Config +from apps.entities.collection import KnowledgeBaseItem +from apps.entities.response_data import KnowledgeBaseItem as KnowledgeBaseItemResponse +from apps.entities.response_data import TeamKnowledgeBaseItem +from apps.manager.session import SessionManager from apps.models.mongo import MongoDB logger = logging.getLogger(__name__) @@ -15,31 +21,146 @@ class KnowledgeBaseManager: """用户资产库管理""" @staticmethod - async def change_kb_id(user_sub: str, kb_id: str) -> bool: - """修改当前用户的知识库ID""" - user_collection = MongoDB.get_collection("user") + async def get_kb_ids_by_conversation_id(user_sub: str, conversation_id: str) -> list[str]: + """ + 通过对话ID获取知识库ID + + :param user_sub: 用户ID + :param conversation_id: 对话ID + :return: 知识库ID列表 + """ try: - user = await user_collection.find_one({"_id": user_sub}, {"kb_id": 1}) - if user is None: - logger.error("[KnowledgeBaseManager] 修改知识库ID失败: 用户不存在") - return False - await user_collection.update_one({"_id": user_sub}, {"$set": {"kb_id": kb_id}}) + conv_collection = MongoDB().get_collection("conversation") + result = await conv_collection.find_one({"_id": conversation_id, "user_sub": user_sub}) + if not result: + err_msg = "[KnowledgeBaseManager] 获取知识库ID失败,未找到对话" + logger.error(err_msg) + return [] + kb_config_list = result.get("kb_list", []) + return [kb_config["kb_id"] for kb_config in kb_config_list] except Exception: - logger.exception("[KnowledgeBaseManager] 修改知识库ID失败") - return False - else: - return True + logger.exception("[KnowledgeBaseManager] 获取知识库ID失败") + return [] + + @staticmethod + async def get_team_kb_list_from_rag( + user_sub: str, + kb_id: str | None = None, + kb_name: str | None = None, + ) -> list[dict[str, Any]]: + """ + 从RAG获取知识库列表 + + :param user_sub: 用户sub + :param kb_id: 知识库ID + :param kb_name: 知识库名称 + :return: 知识库列表 + """ + try: + session_id = await SessionManager.get_session_by_user_sub(user_sub) + url = Config().get_config().rag.rag_service.rstrip("/")+"/kb" + headers = { + "Authorization": f"Bearer {session_id}", + "Content-Type": "application/json", + } + async with httpx.AsyncClient() as client: + data = { + "kbName": kb_name + } + if kb_id: + data["kbId"] = kb_id + resp = await client.get(url, headers=headers, params=data) + resp_data = resp.json() + if resp.status_code != status.HTTP_200_OK: + return [] + return resp_data["result"]["teamKnowledgebases"] + except Exception as e: + logger.exception("[KnowledgeBaseManager] 获取知识库ID失败") + return [] @staticmethod - async def get_kb_id(user_sub: str) -> str | None: - """获取当前用户的知识库ID""" - user_collection = MongoDB.get_collection("user") + async def list_team_kb( + user_sub: str, conversation_id: str, kb_id: str, kb_name: str) -> list[KnowledgeBaseItemResponse]: + """ + 获取当前用户的知识库ID + + :param user_sub: 用户sub + :return: 知识库ID列表 + """ try: - user_info = await user_collection.find_one({"_id": user_sub}, {"kb_id": 1}) - if not user_info: - logger.error("[KnowledgeBaseManager] 用户不存在: %s", user_sub) - return None - return user_info["kb_id"] + conv_collection = MongoDB().get_collection("conversation") + result = await conv_collection.find_one({"_id": conversation_id, "user_sub": user_sub}) + if not result: + err_msg = "[KnowledgeBaseManager] 获取知识库ID失败,未找到对话" + logger.error(err_msg) + return [] + kb_config_list = result.get("kb_list", []) + kb_ids_used = [kb_config["kb_id"] for kb_config in kb_config_list] + kb_ids_used = set(kb_ids_used) except Exception: logger.exception("[KnowledgeBaseManager] 获取知识库ID失败") - return None + return [] + try: + team_kb_item_list = [] + team_kb_list = await KnowledgeBaseManager.get_team_kb_list_from_rag(user_sub, kb_id, kb_name) + for team_kb in team_kb_list: + team_kb_item = TeamKnowledgeBaseItem( + teamId=team_kb["teamId"], + teamName=team_kb["teamName"], + ) + for kb in team_kb["kbList"]: + kb_item = KnowledgeBaseItemResponse( + kbId=kb["kbId"], + kbName=kb["kbName"], + description=kb["description"], + isUsed=kb["kbId"] in kb_ids_used + ) + team_kb_item.kb_list.append(kb_item) + team_kb_item_list.append(team_kb_item) + return team_kb_item_list + except Exception: + logger.exception("[KnowledgeBaseManager] 获取知识库ID失败") + return [] + + @staticmethod + async def update_conv_kb( + user_sub: str, + conversation_id: str, + kb_ids: list[str], + ) -> list[str]: + """ + 更新对话的知识库列表 + + :param user_sub: 用户sub + :param conversation_id: 对话ID + :param kb_list: 知识库列表 + :return: 是否更新成功 + """ + kb_ids = list(set(kb_ids)) + try: + conv_collection = MongoDB().get_collection("conversation") + conv_dict = await conv_collection.find_one({"_id": conversation_id, "user_sub": user_sub}) + if not conv_dict: + err_msg = "[KnowledgeBaseManager] 更新知识库失败,未找到对话" + logger.error(err_msg) + return [] + kb_ids_update_success = [] + kb_item_dict_list = [] + team_kb_list = await KnowledgeBaseManager.get_team_kb_list_from_rag(user_sub, None, None) + for team_kb in team_kb_list: + for kb in team_kb["kbList"]: + if str(kb["kbId"]) in kb_ids: + kb_item = KnowledgeBaseItem( + kb_id=kb["kbId"], + kb_name=kb["kbName"], + ) + kb_item_dict_list.append(kb_item.model_dump(by_alias=True)) + kb_ids_update_success.append(kb["kbId"]) + await conv_collection.update_one( + {"_id": conversation_id, "user_sub": user_sub}, + {"$set": {"kb_list": kb_item_dict_list}}, + ) + return kb_ids_update_success + except Exception: + logger.exception("[KnowledgeBaseManager] 更新知识库失败") + return [] diff --git a/apps/manager/llm.py b/apps/manager/llm.py new file mode 100644 index 0000000000000000000000000000000000000000..7ca75c4e2874aed74d6e2c260a7ef89d609e0554 --- /dev/null +++ b/apps/manager/llm.py @@ -0,0 +1,252 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""大模型管理""" + +import logging + +import httpx +import yaml +from fastapi import status + +from apps.common.config import Config +from apps.entities.collection import LLM, LLMItem +from apps.entities.request_data import ( + UpdateLLMReq, +) +from apps.entities.response_data import LLM as LLMResponse +from apps.entities.response_data import LLMProvider +from apps.manager.session import SessionManager +from apps.models.mongo import MongoDB +from apps.templates.generate_llm_operator_config import llm_provider_dict + +logger = logging.getLogger(__name__) + + +class LLMManager: + """大模型管理""" + + @staticmethod + async def list_llm_provider() -> list[LLMProvider]: + """ + 获取大模型提供商列表 + + :return: 大模型提供商列表 + """ + try: + llm_provider_item_list = [] + for llm_provider in llm_provider_dict.values(): + llm_provider_item = LLMProvider( + provider=llm_provider["provider"], + url=llm_provider["url"], + description=llm_provider["description"], + icon=llm_provider["icon"], + ) + llm_provider_item_list.append(llm_provider_item) + return llm_provider_item_list + except Exception as e: + logger.exception("[LLMManager] 获取大模型提供商失败") + return [] + + @staticmethod + async def get_llm_id_by_conversation_id(user_sub: str, conversation_id: str) -> str: + """ + 通过对话ID获取大模型ID + + :param user_sub: 用户ID + :param conversation_id: 对话ID + :return: 大模型ID + """ + try: + conv_collection = MongoDB().get_collection("conversation") + result = await conv_collection.find_one({"_id": conversation_id, "user_sub": user_sub}) + if not result: + return None + llm_id = result.get("llm", {}).get("llm_id", None) + return llm_id + except Exception as e: + logger.exception("[LLMManager] 获取大模型ID失败") + return None + + @staticmethod + async def get_llm_by_id(user_sub: str, llm_id: str) -> LLM: + """ + 通过ID获取大模型 + + :param user_sub: 用户ID + :param llm_id: 大模型ID + :return: 大模型对象 + """ + try: + llm_collection = MongoDB().get_collection("llm") + result = await llm_collection.find_one({"_id": llm_id, "user_sub": user_sub}) + if not result: + return None + return LLM.model_validate(result) + except Exception as e: + logger.exception("[LLMManager] 获取大模型失败") + return None + + @staticmethod + async def list_llm(user_sub: str, llm_id: str) -> list[LLMResponse]: + """ + 获取大模型列表 + + :param user_sub: 用户ID + :return: 大模型列表 + """ + try: + llm_collection = MongoDB().get_collection("llm") + filter = {"user_sub": user_sub} + if llm_id: + filter["llm_id"] = llm_id + result = await llm_collection.find(filter).sort({"created_at": 1}).to_list(length=None) + llm_item = LLMResponse( + llmId="empty", + icon=llm_provider_dict["ollama"]["icon"], + openaiBaseUrl=Config().get_config().llm.endpoint, + openaiApiKey=Config().get_config().llm.key, + modelName=Config().get_config().llm.model, + maxTokens=Config().get_config().llm.max_tokens, + isEditable=False + ) + llm_item_list = [llm_item] + for llm in result: + llm_item = LLMResponse( + llmId=llm["_id"], + icon=llm["icon"], + openaiBaseUrl=llm["openai_base_url"], + openaiApiKey=llm["openai_api_key"], + modelName=llm["model_name"], + maxTokens=llm["max_tokens"], + ) + llm_item_list.append(llm_item) + return llm_item_list + except Exception as e: + logger.exception("[LLMManager] 获取大模型失败") + return [] + + @staticmethod + async def update_llm(user_sub: str, llm_id: str, req: UpdateLLMReq) -> LLM: + """ + 创建大模型 + + :param user_sub: 用户ID + :param req: 创建大模型请求体 + :return: 大模型对象 + """ + try: + llm_collection = MongoDB().get_collection("llm") + if llm_id: + llm_dict = await llm_collection.find_one({"_id": llm_id, "user_sub": user_sub}) + if not llm_dict: + err = f"[LLMManager] LLM {llm_id} 不存在" + logger.error(err) + raise ValueError(err) + llm = LLM( + _id=llm_id, + user_sub=user_sub, + icon=llm_dict["icon"], + openai_base_url=req.openai_base_url, + openai_api_key=req.openai_api_key, + model_name=req.model_name, + max_tokens=req.max_tokens, + ) + await llm_collection.update_one({"_id": llm_id}, {"$set": llm.model_dump(by_alias=True)}) + else: + llm = LLM( + user_sub=user_sub, + icon=req.icon, + openai_base_url=req.openai_base_url, + openai_api_key=req.openai_api_key, + model_name=req.model_name, + max_tokens=req.max_tokens, + ) + await llm_collection.insert_one(llm.model_dump(by_alias=True)) + return llm.id + except Exception as e: + logger.exception("[LLMManager] 创建大模型失败") + return None + + @staticmethod + async def delete_llm(user_sub: str, llm_id: str) -> str: + """ + 删除大模型 + + :param user_sub: 用户ID + :param llm_id: 大模型ID + :return: 大模型ID + """ + try: + if llm_id == "empty": + err = "[LLMManager] 不能删除默认大模型" + logger.error(err) + raise ValueError(err) + llm_collection = MongoDB().get_collection("llm") + conv_collection = MongoDB().get_collection("conversation") + conv_dict = await conv_collection.find_one({"llm.llm_id": llm_id, "user_sub": user_sub}) + if conv_dict: + await conv_collection.update_many( + {"_id": conv_dict["_id"], "user_sub": user_sub}, + {"$set": + {"llm": + {"llm_id": "empty", + "icon": llm_provider_dict['ollama']['icon'], + "model_name": Config().get_config().llm.model} + } + }, + ) + llm_config = await llm_collection.find_one({"_id": llm_id, "user_sub": user_sub}) + if not llm_config: + err = f"[LLMManager] LLM {llm_id} 不存在" + logger.error(err) + raise ValueError(err) + await llm_collection.delete_one({"_id": llm_id, "user_sub": user_sub}) + return llm_id + except Exception as e: + logger.exception("[LLMManager] 删除大模型失败") + raise e + + @staticmethod + async def update_conversation_llm( + user_sub: str, + conversation_id: str, + llm_id: str, + ) -> str: + """更新对话的LLM""" + try: + conv_collection = MongoDB().get_collection("conversation") + llm_collection = MongoDB().get_collection("llm") + if llm_id != "empty": + llm_dict = await llm_collection.find_one({"_id": llm_id, "user_sub": user_sub}) + if not llm_dict: + err = f"[LLMManager] LLM {llm_id} 不存在" + logger.error(err) + return False + llm_dict = { + "llm_id": llm_dict["_id"], + "model_name": llm_dict["model_name"], + "icon": llm_dict["icon"], + } + else: + llm_dict = { + "llm_id": "empty", + "model_name": Config().get_config().llm.model, + "icon": llm_provider_dict["ollama"]["icon"], + } + conv_dict = await conv_collection.find_one({"_id": conversation_id, "user_sub": user_sub}) + if not conv_dict: + err_msg = "[LLMManager] 更新对话的LLM失败,未找到对话" + logger.error(err_msg) + return False + llm_item = LLMItem( + llm_id=llm_id, + model_name=llm_dict["model_name"], + icon=llm_dict["icon"], + ) + await conv_collection.update_one( + {"_id": conversation_id, "user_sub": user_sub}, + {"$set": {"llm": llm_item.model_dump(by_alias=True)}}, + ) + return conversation_id + except Exception as e: + logger.exception("[LLMManager] 更新对话的LLM失败") + raise e diff --git a/apps/manager/mcp_service.py b/apps/manager/mcp_service.py new file mode 100644 index 0000000000000000000000000000000000000000..9d5dec87c46daf69303b33fec303747a566c0eb3 --- /dev/null +++ b/apps/manager/mcp_service.py @@ -0,0 +1,343 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. +"""语义接口中心 Manager""" + +import json +import logging +import uuid +from hashlib import sha256 +from typing import Any + +from fastapi.encoders import jsonable_encoder + +from apps.entities.enum_var import MCPSearchType +from apps.entities.mcp import MCPConfig, MCPServiceMetadata, MCPTool, MCPType +from apps.entities.response_data import MCPServiceCardItem +from apps.exceptions import InstancePermissionError +from apps.models.mongo import MongoDB +from apps.scheduler.pool.loader.mcp import MCPLoader +from apps.scheduler.pool.mcp.pool import MCPPool + +logger = logging.getLogger(__name__) + + +class MCPServiceManager: + """MCP服务管理器""" + + @staticmethod + async def is_active(user_sub: str, mcp_id: str) -> bool: + """ + 判断用户MCP是否激活 + + :param str user_sub: 用户ID + :param str mcp_id: MCP模板ID + :return: bool + """ + mongo = MongoDB() + mcp_collection = mongo.get_collection("mcp") + mcp_list = await mcp_collection.find({"_id": mcp_id}, {"activated": True}).to_list(None) + return any(user_sub in db_item["activated"] for db_item in mcp_list) + + @staticmethod + async def delete(service_id: str) -> None: + """删除MCPService,并更新数据库""" + mongo = MongoDB() + service_collection = mongo.get_collection("mcp_service") + try: + await service_collection.delete_one({"id": service_id}) + except Exception as exp: + err = f"[MCPServiceLoader] 删除MCPService失败: {exp}" + logger.exception(err) + raise RuntimeError(err) from exp + + @staticmethod + async def _update_db(metadata: MCPServiceMetadata) -> None: + """更新数据库""" + if not metadata.hashes: + err = f"[MCPServiceLoader] MCP服务 {metadata.id} 的哈希值为空" + logger.error(err) + raise ValueError(err) + # 更新MongoDB + mongo = MongoDB() + service_collection = mongo.get_collection("mcp_service") + try: + # 插入或更新 Service + await service_collection.update_one( + {"id": metadata.id}, + {"$set": jsonable_encoder(metadata, by_alias=True)}, + upsert=True, + ) + except Exception as e: + err = f"[MCPServiceLoader] 更新 MongoDB 失败:{e}" + logger.exception(err) + raise RuntimeError(err) from e + + @staticmethod + async def load_mcp_config(description: str, config_str: str, mcp_type: MCPType = MCPType.STDIO) -> MCPConfig: + """字符串转换为MCPConfig""" + mcp_servers = json.loads(config_str) + result = MCPConfig.model_validate(mcp_servers) + for server_name, config in result.mcp_servers.items(): + config.name = server_name + config.description = description + config.type = mcp_type + return result + + @staticmethod + async def fetch_all_mcpservices( + search_type: MCPSearchType, + user_sub: str, + keyword: str | None, + page: int, + page_size: int, + ) -> tuple[list[MCPServiceCardItem], int]: + """获取所有MCP服务列表""" + filters = MCPServiceManager._build_filters({}, search_type, keyword) if keyword else {} + mcpservice_pools, total_count = await MCPServiceManager._search_mcpservice(filters, page, page_size) + mcpservices = [ + MCPServiceCardItem( + mcpserviceId=mcpservice_pool.id, + icon=mcpservice_pool.icon, + name=mcpservice_pool.name, + description=mcpservice_pool.description, + author=mcpservice_pool.author, + ) + for mcpservice_pool in mcpservice_pools + ] + for mcp in mcpservices: + mcp.is_active = await MCPServiceManager.is_active(user_sub, mcp.mcpservice_id) + return mcpservices, total_count + + @staticmethod + async def get_mcpservice_data( + user_sub: str, + mcpservice_id: str, + ) -> MCPServiceMetadata: + """获取服务数据""" + # 验证用户权限 + mcpservice_collection = MongoDB().get_collection("mcp_service") + match_conditions = { + {"author": user_sub} + } + query = {"$and": [{"service_id": mcpservice_id}, match_conditions]} + db_service = await mcpservice_collection.find_one(query, {"_id": False}) + if not db_service: + msg = "MCPService not found" + raise Exception(msg) + mcpservice_pool_store = MCPServiceMetadata.model_validate(db_service) + if mcpservice_pool_store.author != user_sub: + msg = "Permission denied" + raise InstancePermissionError(msg) + + return mcpservice_pool_store + + @staticmethod + async def get_service_details( + service_id: str, + ) -> MCPServiceMetadata: + """获取服务API列表""" + # 获取服务名称 + service_collection = MongoDB().get_collection("mcp_service") + db_service = await service_collection.find_one({"id": service_id}, {"_id": False}) + if not db_service: + msg = "MCPService not found" + raise Exception(msg) + mcpservice_pool_store = MCPServiceMetadata.model_validate(db_service) + mcpservice_pool_store.tools = await MCPServiceManager.get_service_tools(service_id) + return mcpservice_pool_store + + @staticmethod + async def get_service_tools( + service_id: str, + ) -> list[MCPTool]: + """获取服务API列表""" + # 获取服务名称 + service_collection = MongoDB().get_collection("mcp") + data = await service_collection.find({"_id": service_id}, {"tools": True}).to_list(None) + result = [] + for item in data: + for tool in item.get("tools", {}): + tool_data = MCPTool.model_validate(tool) + result.append(tool_data) + return result + + @staticmethod + async def _search_mcpservice( + search_conditions: dict, + page: int, + page_size: int, + ) -> tuple[list[MCPServiceMetadata], int]: + """基于输入条件获取MCP服务数据""" + mcpservice_collection = MongoDB().get_collection("mcp_service") + # 获取服务总数 + total = await mcpservice_collection.count_documents(search_conditions) + # 分页查询 + skip = (page - 1) * page_size + db_mcpservices = await mcpservice_collection.find(search_conditions, {"_id": False}).skip(skip).limit(page_size).to_list() + if not db_mcpservices: + logger.warning("[MCPServiceManager] 没有找到符合条件的MCP服务: %s", search_conditions) + return [], 0 + mcpservice_pools = [MCPServiceMetadata.model_validate(db_mcpservice) for db_mcpservice in db_mcpservices] + return mcpservice_pools, total + + @staticmethod + def _build_filters( + base_filters: dict[str, Any], + search_type: MCPSearchType, + keyword: str, + ) -> dict[str, Any]: + search_filters = [ + {"name": {"$regex": keyword, "$options": "i"}}, + {"description": {"$regex": keyword, "$options": "i"}}, + {"author": {"$regex": keyword, "$options": "i"}}, + ] + if search_type == MCPSearchType.ALL: + base_filters["$or"] = search_filters + elif search_type == MCPSearchType.NAME: + base_filters["name"] = {"$regex": keyword, "$options": "i"} + elif search_type == MCPSearchType.AUTHOR: + base_filters["author"] = {"$regex": keyword, "$options": "i"} + return base_filters + + @staticmethod + async def save(metadata: MCPServiceMetadata) -> None: + """保存mcp到数据库""" + metadata.hashes['config'] = sha256(metadata.config_str.encode(encoding='utf-8')).hexdigest() + await MCPServiceManager._update_db(metadata) + + @staticmethod + async def create_mcpservice( + user_sub: str, + name: str, + icon: str, + description: str, + config: MCPConfig, + config_str: str, + mcp_type: MCPType + ) -> str: + """创建MCP服务""" + if len(config.mcp_servers) != 1: + msg = "[MCPServiceManager] MCP服务配置不唯一" + raise Exception(msg) + mcpservice_id = str(uuid.uuid4()) + # 检查是否存在相同服务 + service_collection = MongoDB().get_collection("mcp_service") + db_service = await service_collection.find_one( + { + "name": name, + "description": description + }, + {"_id": False} + ) + if db_service: + msg = "[MCPServiceManager] 已存在相同名称和描述的MCP服务" + raise Exception(msg) + + tools = [] + + # 存入数据库 + service_metadata = MCPServiceMetadata( + id=mcpservice_id, + name=name, + icon=icon, + description=description, + author=user_sub, + config=config, + tools=tools, + configStr=config_str, + mcpType=mcp_type + ) + await MCPServiceManager.save(service_metadata) + mcp_loader = MCPLoader() + for _, server in config.mcp_servers.items(): + await mcp_loader.init_one_template(mcp_id=mcpservice_id, config=server) + await MCPServiceManager.active_mcpservice(user_sub=user_sub, service_id=mcpservice_id) + return mcpservice_id + + @staticmethod + async def update_mcpservice( + user_sub: str, + mcpservice_id: str, + name: str, + icon: str, + description: str, + config: MCPConfig, + config_str: str, + mcp_type: MCPType + ) -> str: + """更新服务""" + if len(config.mcp_servers) != 1: + msg = "[MCPServiceManager] MCP服务配置不唯一" + raise Exception(msg) + mcpservice_collection = MongoDB().get_collection("mcp_service") + db_service = await mcpservice_collection.find_one({"id": mcpservice_id}, {"_id": False}) + if not db_service: + msg = "MCPService not found" + raise Exception(msg) + service_pool_store = MCPServiceMetadata.model_validate(db_service) + + # 存入数据库 + mcpservice_metadata = MCPServiceMetadata( + id=mcpservice_id, + name=name, + icon=icon, + description=description, + author=user_sub, + config=config, + tools=service_pool_store.tools, + hashes=service_pool_store.hashes, + configStr=config_str, + mcpType=mcp_type + ) + await MCPServiceManager.save(mcpservice_metadata) + mcp_loader = MCPLoader() + await MCPServiceManager.deactive_mcpservice(user_sub=user_sub, service_id=mcpservice_id) + for _, server in config.mcp_servers.items(): + await mcp_loader.init_one_template(mcp_id=mcpservice_id, config=server) + await MCPServiceManager.active_mcpservice(user_sub=user_sub, service_id=mcpservice_id) + # 返回服务ID + return mcpservice_id + + @staticmethod + async def delete_mcpservice( + user_sub: str, + service_id: str, + ) -> bool: + """删除服务""" + service_collection = MongoDB().get_collection("mcp_service") + db_service = await service_collection.find_one({"id": service_id}, {"_id": False}) + if not db_service: + msg = "[MCPServiceCenterManager] Service未找到" + raise ValueError(msg) + # 验证用户权限 + service_pool_store = MCPServiceMetadata.model_validate(db_service) + if service_pool_store.author != user_sub: + msg = "Permission denied" + raise InstancePermissionError(msg) + # 删除服务 + await MCPServiceManager.delete(service_id) + await MCPLoader().user_deactive_template(user_sub, service_id) + return True + + @staticmethod + async def active_mcpservice( + user_sub: str, + service_id: str, + ) -> None: + """激活服务""" + loader = MCPLoader() + await loader.user_active_template(user_sub, service_id) + + @staticmethod + async def deactive_mcpservice( + user_sub: str, + service_id: str, + ) -> None: + """取消激活服务""" + mcp_pool = MCPPool() + await mcp_pool.get(mcp_id=service_id, user_sub=user_sub) + try: + await mcp_pool.stop(mcp_id=service_id, user_sub=user_sub) + except KeyError as e: + logger.warning(e) + loader = MCPLoader() + await loader.user_deactive_template(user_sub, service_id) diff --git a/apps/manager/node.py b/apps/manager/node.py index 41a3e4f71c31d731a9735c98580fc26f8136443c..dcdb07228ccbe4821db3eecbc51b362788fc722b 100644 --- a/apps/manager/node.py +++ b/apps/manager/node.py @@ -1,4 +1,6 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """Node管理器""" + import logging from typing import TYPE_CHECKING, Any @@ -49,7 +51,7 @@ class NodeManager: @staticmethod async def get_node_name(node_id: str) -> str: """获取node的名称""" - node_collection = MongoDB.get_collection("node") + node_collection = MongoDB().get_collection("node") # 查询 Node 集合获取对应的 name node_doc = await node_collection.find_one({"_id": node_id}, {"name": 1}) if not node_doc: diff --git a/apps/manager/record.py b/apps/manager/record.py index 72d0bd045b383b5bad2fa5a66fd650350de87e06..51ba964a79446699c77e49989b4bfc2595652abd 100644 --- a/apps/manager/record.py +++ b/apps/manager/record.py @@ -1,8 +1,5 @@ -""" -问答对Manager - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""问答对Manager""" import logging from typing import Literal @@ -22,8 +19,9 @@ class RecordManager: @staticmethod async def create_record_group(group_id: str, user_sub: str, conversation_id: str, task_id: str) -> str | None: """创建问答组""" - record_group_collection = MongoDB.get_collection("record_group") - conversation_collection = MongoDB.get_collection("conversation") + mongo = MongoDB() + record_group_collection = mongo.get_collection("record_group") + conversation_collection = mongo.get_collection("conversation") record_group = RecordGroup( _id=group_id, user_sub=user_sub, @@ -32,7 +30,7 @@ class RecordManager: ) try: - async with MongoDB.get_session() as session, await session.start_transaction(): + async with mongo.get_session() as session, await session.start_transaction(): # RecordGroup里面加一条记录 await record_group_collection.insert_one(record_group.model_dump(by_alias=True), session=session) # Conversation里面加一个ID @@ -48,7 +46,8 @@ class RecordManager: @staticmethod async def insert_record_data_into_record_group(user_sub: str, group_id: str, record: Record) -> str | None: """加密问答对,并插入MongoDB中的特定问答组""" - group_collection = MongoDB.get_collection("record_group") + mongo = MongoDB() + group_collection = mongo.get_collection("record_group") try: await group_collection.update_one( {"_id": group_id, "user_sub": user_sub}, @@ -74,7 +73,8 @@ class RecordManager: """ sort_order = -1 if order == "desc" else 1 - record_group_collection = MongoDB.get_collection("record_group") + mongo = MongoDB() + record_group_collection = mongo.get_collection("record_group") try: # 得到conversation的全部record_group id record_groups = await record_group_collection.aggregate( @@ -118,7 +118,7 @@ class RecordManager: 包含全部record_group及其关联的record """ - record_group_collection = MongoDB.get_collection("record_group") + record_group_collection = MongoDB().get_collection("record_group") try: pipeline = [ {"$match": {"conversation_id": conversation_id}}, @@ -142,7 +142,7 @@ class RecordManager: :return: 记录是否存在 """ try: - record_group_collection = MongoDB.get_collection("record_group") + record_group_collection = MongoDB().get_collection("record_group") record_data = await record_group_collection.find_one( {"_id": group_id, "user_sub": user_sub, "records.id": record_id}, ) @@ -155,7 +155,7 @@ class RecordManager: @staticmethod async def check_group_id(group_id: str, user_sub: str) -> bool: """检查group_id是否存在""" - record_group_collection = MongoDB.get_collection("record_group") + record_group_collection = MongoDB().get_collection("record_group") try: result = await record_group_collection.find_one({"_id": group_id, "user_sub": user_sub}) return bool(result) diff --git a/apps/manager/service.py b/apps/manager/service.py index f6ea26d83a28a2b210e8b146f4a72b705c752501..2bede58e22ac40dd47185c55a72fb2f40383571f 100644 --- a/apps/manager/service.py +++ b/apps/manager/service.py @@ -1,8 +1,5 @@ -""" -语义接口中心 Manager - -Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. +"""语义接口中心 Manager""" import logging import uuid @@ -71,8 +68,7 @@ class ServiceCenterManager: if search_type == SearchType.AUTHOR: if keyword is not None and keyword not in user_sub: return [], 0 - else: - keyword = user_sub + keyword = user_sub base_filter = {"author": user_sub} filters = ServiceCenterManager._build_filters(base_filter, search_type, keyword) if keyword else base_filter service_pools, total_count = await ServiceCenterManager._search_service(filters, page, page_size) @@ -126,7 +122,7 @@ class ServiceCenterManager: # 校验 OpenAPI 规范的 JSON Schema validated_data = await ServiceCenterManager._validate_service_data(data) # 检查是否存在相同服务 - service_collection = MongoDB.get_collection("service") + service_collection = MongoDB().get_collection("service") db_service = await service_collection.find_one( { "name": validated_data.id, @@ -159,7 +155,7 @@ class ServiceCenterManager: ) -> str: """更新服务""" # 验证用户权限 - service_collection = MongoDB.get_collection("service") + service_collection = MongoDB().get_collection("service") db_service = await service_collection.find_one({"_id": service_id}) if not db_service: msg = "Service not found" @@ -190,14 +186,14 @@ class ServiceCenterManager: ) -> tuple[str, list[ServiceApiData]]: """获取服务API列表""" # 获取服务名称 - service_collection = MongoDB.get_collection("service") + service_collection = MongoDB().get_collection("service") db_service = await service_collection.find_one({"_id": service_id}) if not db_service: msg = "Service not found" raise ServiceIDError(msg) service_pool_store = ServicePool.model_validate(db_service) # 根据 service_id 获取 API 列表 - node_collection = MongoDB.get_collection("node") + node_collection = MongoDB().get_collection("node") db_nodes = await node_collection.find({"service_id": service_id}).to_list() api_list: list[ServiceApiData] = [] for db_node in db_nodes: @@ -220,7 +216,7 @@ class ServiceCenterManager: ) -> tuple[str, dict[str, Any]]: """获取服务数据""" # 验证用户权限 - service_collection = MongoDB.get_collection("service") + service_collection = MongoDB().get_collection("service") match_conditions = [ {"author": user_sub}, {"permission.type": PermissionType.PUBLIC.value}, @@ -253,7 +249,7 @@ class ServiceCenterManager: service_id: str, ) -> ServiceMetadata: """获取服务元数据""" - service_collection = MongoDB.get_collection("service") + service_collection = MongoDB().get_collection("service") match_conditions = [ {"author": user_sub}, {"permission.type": PermissionType.PUBLIC.value}, @@ -283,8 +279,8 @@ class ServiceCenterManager: service_id: str, ) -> bool: """删除服务""" - service_collection = MongoDB.get_collection("service") - user_collection = MongoDB.get_collection("user") + service_collection = MongoDB().get_collection("service") + user_collection = MongoDB().get_collection("user") db_service = await service_collection.find_one({"_id": service_id}) if not db_service: msg = "[ServiceCenterManager] Service未找到" @@ -312,8 +308,8 @@ class ServiceCenterManager: favorited: bool, ) -> bool: """修改收藏状态""" - service_collection = MongoDB.get_collection("service") - user_collection = MongoDB.get_collection("user") + service_collection = MongoDB().get_collection("service") + user_collection = MongoDB().get_collection("user") db_service = await service_collection.find_one({"_id": service_id}) if not db_service: msg = f"[ServiceCenterManager] Service未找到: {service_id}" @@ -347,7 +343,7 @@ class ServiceCenterManager: page_size: int, ) -> tuple[list[ServicePool], int]: """基于输入条件获取服务数据""" - service_collection = MongoDB.get_collection("service") + service_collection = MongoDB().get_collection("service") # 获取服务总数 total = await service_collection.count_documents(search_conditions) # 分页查询 @@ -362,7 +358,7 @@ class ServiceCenterManager: @staticmethod async def _get_favorite_service_ids_by_user(user_sub: str) -> list[str]: """获取用户收藏的服务ID""" - user_collection = MongoDB.get_collection("user") + user_collection = MongoDB().get_collection("user") user_data = User.model_validate(await user_collection.find_one({"_id": user_sub})) return user_data.fav_services diff --git a/apps/manager/session.py b/apps/manager/session.py index b143b478fd21b76d8b03f50d6c0f6c5f632719dd..599e85f2427cc56c02360f837b548707270bfc0d 100644 --- a/apps/manager/session.py +++ b/apps/manager/session.py @@ -1,17 +1,12 @@ -""" -浏览器Session Manager +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""浏览器Session Manager""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" - -import base64 -import hashlib -import hmac import logging import secrets from datetime import UTC, datetime, timedelta from apps.common.config import Config +from apps.constants import SESSION_TTL from apps.entities.config import FixedUserConfig from apps.entities.session import Session from apps.exceptions import LoginSettingsError, SessionError @@ -35,7 +30,7 @@ class SessionManager: data = Session( _id=session_id, ip=ip, - expired_at=datetime.now(UTC) + timedelta(minutes=Config().get_config().fastapi.session_ttl), + expired_at=datetime.now(UTC) + timedelta(minutes=SESSION_TTL), ) if Config().get_config().login.provider == "disable": login_settings = Config().get_config().login.settings @@ -136,62 +131,15 @@ class SessionManager: return user_sub @staticmethod - async def create_csrf_token(session_id: str) -> str: - """创建CSRF Token""" - rand = secrets.token_hex(8) - + async def get_session_by_user_sub(user_sub: str) -> str | None: + """根据用户sub获取Session""" try: collection = MongoDB().get_collection("session") - await collection.update_one({"_id": session_id}, {"$set": {"nonce": rand}}) - except Exception as e: - err = "创建CSRF Token失败" - logger.exception("[SessionManager] %s", err) - raise SessionError(err) from e - - csrf_value = f"{session_id}{rand}" - csrf_b64 = base64.b64encode(bytes.fromhex(csrf_value)) - - jwt_key = base64.b64decode(Config().get_config().security.jwt_key) - hmac_processor = hmac.new(key=jwt_key, msg=csrf_b64, digestmod=hashlib.sha256) - signature = base64.b64encode(hmac_processor.digest()) - - csrf_b64 = csrf_b64.decode("utf-8") - signature = signature.decode("utf-8") - return f"{csrf_b64}.{signature}" - - @staticmethod - async def verify_csrf_token(session_id: str, token: str) -> bool: - """验证CSRF Token""" - if not token: - return False - - token_msg = token.split(".") - if len(token_msg) != 2: # noqa: PLR2004 - return False - - first_part = base64.b64decode(token_msg[0]).hex() - current_session_id = first_part[:32] - logger.error("current_session_id: %s, session_id: %s", current_session_id, session_id) - if current_session_id != session_id: - return False - - current_nonce = first_part[32:] - try: - collection = MongoDB().get_collection("session") - data = await collection.find_one({"_id": session_id}) + data = await collection.find_one({"user_sub": user_sub}) if not data: - return False - nonce = Session(**data).nonce - if nonce != current_nonce: - return False + return None + return Session(**data).id except Exception as e: - err = "从Session中获取CSRF Token失败" + err = "从Session中获取用户失败" logger.exception("[SessionManager] %s", err) raise SessionError(err) from e - - jwt_key = base64.b64decode(Config().get_config().security.jwt_key) - hmac_obj = hmac.new(key=jwt_key, msg=token_msg[0].encode("utf-8"), digestmod=hashlib.sha256) - signature = hmac_obj.digest() - current_signature = base64.b64decode(token_msg[1]) - - return hmac.compare_digest(signature, current_signature) diff --git a/apps/manager/task.py b/apps/manager/task.py index e784bf6962529130a6dee7973bbd7268a26ee19d..824ddb4eb4886f1bcd80d860d05bf9765d9bef08 100644 --- a/apps/manager/task.py +++ b/apps/manager/task.py @@ -1,8 +1,5 @@ -""" -获取和保存Task信息到数据库 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""获取和保存Task信息到数据库""" import logging import uuid @@ -39,7 +36,7 @@ class TaskManager: task_id = last_group.task_id # 查询最后一条问答组关联的任务 - task_collection = MongoDB.get_collection("task") + task_collection = MongoDB().get_collection("task") task = await task_collection.find_one({"_id": task_id}) if not task: # 任务不存在,新建Task @@ -52,8 +49,8 @@ class TaskManager: @staticmethod async def get_task_by_group_id(group_id: str, conversation_id: str) -> Task | None: """获取组ID的最后一条问答组关联的任务""" - task_collection = MongoDB.get_collection("task") - record_group_collection = MongoDB.get_collection("record_group") + task_collection = MongoDB().get_collection("task") + record_group_collection = MongoDB().get_collection("record_group") try: record_group = await record_group_collection.find_one({"conversation_id": conversation_id, "_id": group_id}) if not record_group: @@ -69,7 +66,7 @@ class TaskManager: @staticmethod async def get_task_by_task_id(task_id: str) -> Task | None: """根据task_id获取任务""" - task_collection = MongoDB.get_collection("task") + task_collection = MongoDB().get_collection("task") task = await task_collection.find_one({"_id": task_id}) if not task: return None @@ -79,8 +76,8 @@ class TaskManager: @staticmethod async def get_context_by_record_id(record_group_id: str, record_id: str) -> list[dict[str, Any]]: """根据record_group_id获取flow信息""" - record_group_collection = MongoDB.get_collection("record_group") - flow_context_collection = MongoDB.get_collection("flow_context") + record_group_collection = MongoDB().get_collection("record_group") + flow_context_collection = MongoDB().get_collection("flow_context") try: record_group = await record_group_collection.aggregate([ {"$match": {"_id": record_group_id}}, @@ -106,7 +103,7 @@ class TaskManager: @staticmethod async def get_context_by_task_id(task_id: str, length: int = 0) -> list[dict[str, Any]]: """根据task_id获取flow信息""" - flow_context_collection = MongoDB.get_collection("flow_context") + flow_context_collection = MongoDB().get_collection("flow_context") flow_context = [] try: @@ -126,7 +123,7 @@ class TaskManager: @staticmethod async def save_flow_context(task_id: str, flow_context: list[dict[str, Any]]) -> None: """保存flow信息到flow_context""" - flow_context_collection = MongoDB.get_collection("flow_context") + flow_context_collection = MongoDB().get_collection("flow_context") try: for history in flow_context: # 查找是否存在 @@ -148,7 +145,8 @@ class TaskManager: @staticmethod async def delete_task_by_task_id(task_id: str) -> None: """通过task_id删除Task信息""" - task_collection = MongoDB.get_collection("task") + mongo = MongoDB() + task_collection = mongo.get_collection("task") try: task = await task_collection.find_one({"_id": task_id}, {"_id": 1}) if task: @@ -160,10 +158,11 @@ class TaskManager: @staticmethod async def delete_tasks_by_conversation_id(conversation_id: str) -> None: """通过ConversationID删除Task信息""" - task_collection = MongoDB.get_collection("task") - flow_context_collection = MongoDB.get_collection("flow_context") + mongo = MongoDB() + task_collection = mongo.get_collection("task") + flow_context_collection = mongo.get_collection("flow_context") try: - async with MongoDB.get_session() as session, await session.start_transaction(): + async with mongo.get_session() as session, await session.start_transaction(): task_ids = [ task["_id"] async for task in task_collection.find( {"conversation_id": conversation_id}, @@ -226,7 +225,7 @@ class TaskManager: @classmethod async def save_task(cls, task_id: str, task: Task) -> None: """保存任务块""" - task_collection = MongoDB.get_collection("task") + task_collection = MongoDB().get_collection("task") # 更新已有的Task记录 await task_collection.update_one( diff --git a/apps/manager/token.py b/apps/manager/token.py index eb329783e003b423077f5a1e5835e41c1aa5b5d9..642083da65623bf35d201756df0f1e7387dcea74 100644 --- a/apps/manager/token.py +++ b/apps/manager/token.py @@ -1,13 +1,10 @@ -""" -Token Manager - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Token Manager""" import logging from datetime import UTC, datetime, timedelta -import aiohttp +import httpx from fastapi import status from apps.common.config import Config @@ -31,7 +28,8 @@ class TokenManager: err = "用户不存在!" raise ValueError(err) - collection = MongoDB().get_collection("session") + mongo = MongoDB() + collection = mongo.get_collection("session") token_data = await collection.find_one({ "_id": f"{plugin_name}_token_{user_sub}", }) @@ -69,7 +67,8 @@ class TokenManager: expire_time: int, ) -> str | None: """生成插件Token""" - collection = MongoDB().get_collection("session") + mongo = MongoDB() + collection = mongo.get_collection("session") # 获取OIDC token oidc_token = await collection.find_one({ @@ -109,16 +108,16 @@ class TokenManager: err = "Refresh token均过期,需要重新登录" raise RuntimeError(err) - async with aiohttp.ClientSession() as session: - response = await session.post( + async with httpx.AsyncClient() as client: + response = await client.post( url=access_token_url, json={ "client_id": oidc_config.app_id, "access_token": oidc_access_token, }, ) - ret = await response.json() - if response.status != status.HTTP_200_OK: + ret = response.json() + if response.status_code != status.HTTP_200_OK: logger.error("[TokenManager] 获取 %s 插件所需的token失败", plugin_name) return None @@ -141,7 +140,8 @@ class TokenManager: @staticmethod async def delete_plugin_token(user_sub: str) -> None: """删除插件token""" - collection = MongoDB().get_collection("token") + mongo = MongoDB() + collection = mongo.get_collection("token") await collection.delete_many({ "user_sub": user_sub, "$or": [ diff --git a/apps/manager/user.py b/apps/manager/user.py index f34881b7da5b74d504885a06e7f8c21044c21107..ba7590c1f06de58ee2514cded4fc10417e6304ec 100644 --- a/apps/manager/user.py +++ b/apps/manager/user.py @@ -1,8 +1,5 @@ -""" -用户 Manager - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""用户 Manager""" import logging from datetime import UTC, datetime @@ -25,8 +22,9 @@ class UserManager: :param user_sub: 用户sub :return: 是否添加成功 """ + mongo = MongoDB() try: - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") await user_collection.insert_one(User( _id=user_sub, ).model_dump(by_alias=True)) @@ -43,9 +41,10 @@ class UserManager: :return: 所有用户的sub列表 """ + mongo = MongoDB() result = [] try: - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") result = [user["_id"] async for user in user_collection.find({}, {"_id": 1})] except Exception: logger.exception("[UserManager] 获取所有用户失败") @@ -59,8 +58,9 @@ class UserManager: :param user_sub: 用户sub :return: 用户信息 """ + mongo = MongoDB() try: - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") user_data = await user_collection.find_one({"_id": user_sub}) return User(**user_data) if user_data else None except Exception: @@ -76,6 +76,7 @@ class UserManager: :param refresh_revision: 是否刷新revision :return: 更新后的用户信息 """ + mongo = MongoDB() user_data = await UserManager.get_userinfo_by_user_sub(user_sub) if not user_data: return await UserManager.add_userinfo(user_sub) @@ -87,7 +88,7 @@ class UserManager: if refresh_revision: update_dict["$set"]["status"] = "init" # type: ignore[assignment] try: - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") result = await user_collection.update_one({"_id": user_sub}, update_dict) except Exception: logger.exception("[UserManager] 更新用户信息失败") @@ -103,8 +104,9 @@ class UserManager: :param login_time: 登录时间 :return: 用户sub列表 """ + mongo = MongoDB() try: - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") return [user["_id"] async for user in user_collection.find({"login_time": {"$lt": login_time}}, {"_id": 1})] except Exception: logger.exception("[UserManager] 根据登录时间获取用户信息失败") @@ -118,8 +120,9 @@ class UserManager: :param user_sub: 用户sub :return: 是否删除成功 """ + mongo = MongoDB() try: - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") result = await user_collection.find_one_and_delete({"_id": user_sub}) if not result: return False diff --git a/apps/manager/user_domain.py b/apps/manager/user_domain.py index 119a76a2c310091a2e9c4dcfb6441b410f68e945..d96c1d19677fe9f108f85cda7f6724d3bde43118 100644 --- a/apps/manager/user_domain.py +++ b/apps/manager/user_domain.py @@ -1,8 +1,5 @@ -""" -用户画像管理 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""用户画像管理""" import logging @@ -18,7 +15,8 @@ class UserDomainManager: @staticmethod async def get_user_domain_by_user_sub_and_topk(user_sub: str, topk: int) -> list[str]: """根据用户ID,查询用户最常涉及的n个领域""" - user_collection = MongoDB.get_collection("user") + mongo = MongoDB() + user_collection = mongo.get_collection("user") try: domains = await user_collection.aggregate( [ @@ -38,8 +36,9 @@ class UserDomainManager: @staticmethod async def update_user_domain_by_user_sub_and_domain_name(user_sub: str, domain_name: str) -> bool: """增加特定用户特定领域的频次""" - domain_collection = MongoDB.get_collection("domain") - user_collection = MongoDB.get_collection("user") + mongo = MongoDB() + domain_collection = mongo.get_collection("domain") + user_collection = mongo.get_collection("user") try: # 检查领域是否存在 domain = await domain_collection.find_one({"_id": domain_name}) diff --git a/apps/models/__init__.py b/apps/models/__init__.py index 12c5e8b43b3108d3e718af7f89452dd441216bdb..8c4a18b2a6375d04f27f32d3adc427582ccc3efe 100644 --- a/apps/models/__init__.py +++ b/apps/models/__init__.py @@ -1,4 +1,2 @@ -"""数据库模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""数据库模块""" diff --git a/apps/models/lance.py b/apps/models/lance.py index c4dd9f0cec86accf63121dafc149daae5e2b2d16..caad549b17aba581b7177a5c3fe0250b71e524ca 100644 --- a/apps/models/lance.py +++ b/apps/models/lance.py @@ -1,14 +1,12 @@ -""" -向postgresql中存储向量化数据 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""向LanceDB中存储向量化数据""" import lancedb from lancedb.index import HnswSq from apps.common.config import Config from apps.common.singleton import SingletonMeta +from apps.entities.mcp import MCPToolVector, MCPVector from apps.entities.vector import ( CallPoolVector, FlowPoolVector, @@ -21,7 +19,13 @@ class LanceDB(metaclass=SingletonMeta): """LanceDB向量化存储""" async def init(self) -> None: - """初始化PostgreSQL""" + """ + 初始化LanceDB + + 此步骤包含创建LanceDB引擎、建表等操作 + + :return: 无 + """ self._engine = await lancedb.connect_async( Config().get_config().deploy.data_dir.rstrip("/") + "/vectors", ) @@ -47,13 +51,36 @@ class LanceDB(metaclass=SingletonMeta): schema=NodePoolVector, exist_ok=True, ) + await self._engine.create_table( + "mcp", + schema=MCPVector, + exist_ok=True, + ) + await self._engine.create_table( + "mcp_tool", + schema=MCPToolVector, + exist_ok=True, + ) + async def get_table(self, table_name: str) -> lancedb.AsyncTable: - """获取表""" + """ + 获取LanceDB中的表 + + :param str table_name: 表名 + :return: 表 + :rtype: lancedb.AsyncTable + """ return await self._engine.open_table(table_name) + async def create_index(self, table_name: str) -> None: - """创建索引""" + """ + 创建LanceDB中表的索引;使用HNSW算法 + + :param str table_name: 表名 + :return: 无 + """ table = await self.get_table(table_name) await table.create_index( "embedding", diff --git a/apps/models/minio.py b/apps/models/minio.py index 66e5dea31b6590e248bfa741569ddb9006a489e1..b5187dbb5b63674879c53def5a125cdd60348544 100644 --- a/apps/models/minio.py +++ b/apps/models/minio.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """MinIO客户端""" from typing import Any diff --git a/apps/models/mongo.py b/apps/models/mongo.py index 8647ffdf4100b470da5301869db3e51af992308b..74b79a1a90984b7ed35bc4e88cb59e2a0d0aeacf 100644 --- a/apps/models/mongo.py +++ b/apps/models/mongo.py @@ -1,52 +1,63 @@ -""" -MongoDB 连接 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" - -from __future__ import annotations +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MongoDB 连接器""" import logging import urllib.parse -from typing import TYPE_CHECKING from pymongo import AsyncMongoClient +from pymongo.asynchronous.client_session import AsyncClientSession +from pymongo.asynchronous.collection import AsyncCollection from apps.common.config import Config logger = logging.getLogger(__name__) -if TYPE_CHECKING: - from pymongo.asynchronous.client_session import AsyncClientSession - from pymongo.asynchronous.collection import AsyncCollection +class MongoDB: + """MongoDB连接器""" + def __init__(self) -> None: + """初始化MongoDB连接器""" + self._client = AsyncMongoClient( + f"mongodb://{urllib.parse.quote_plus(Config().get_config().mongodb.user)}:{urllib.parse.quote_plus(Config().get_config().mongodb.password)}@{Config().get_config().mongodb.host}:{Config().get_config().mongodb.port}/?directConnection=true&replicaSet=rs0", + ) -class MongoDB: - """MongoDB连接""" - _client: AsyncMongoClient = AsyncMongoClient( - f"mongodb://{urllib.parse.quote_plus(Config().get_config().mongodb.user)}:{urllib.parse.quote_plus(Config().get_config().mongodb.password)}@{Config().get_config().mongodb.host}:{Config().get_config().mongodb.port}/?directConnection=true&replicaSet=rs0", - ) + def get_collection(self, collection_name: str) -> AsyncCollection: + """ + 获取MongoDB集合 - @classmethod - def get_collection(cls, collection_name: str) -> AsyncCollection: - """获取MongoDB集合(表)""" + :param str collection_name: 集合名称 + :return: 集合对象 + :rtype: AsyncCollection + """ try: - return cls._client[Config().get_config().mongodb.database][collection_name] + return self._client[Config().get_config().mongodb.database][collection_name] except Exception as e: logger.exception("[MongoDB] 获取集合 %s 失败", collection_name) raise RuntimeError(str(e)) from e - @classmethod - async def clear_collection(cls, collection_name: str) -> None: - """清空MongoDB集合(表)""" + + async def clear_collection(self, collection_name: str) -> None: + """ + 清空MongoDB集合 + + :param str collection_name: 集合名称 + :return: 无 + """ try: - await cls._client[Config().get_config().mongodb.database][collection_name].delete_many({}) + await self._client[Config().get_config().mongodb.database][collection_name].delete_many({}) except Exception: logger.exception("[MongoDB] 清空集合 %s 失败", collection_name) - @classmethod - def get_session(cls) -> AsyncClientSession: - """获取MongoDB会话""" - return cls._client.start_session() + + def get_session(self) -> AsyncClientSession: + """ + 获取MongoDB会话 + + 一个Client可以创建多个会话,一个会话一般用于一个事务。 + + :return: 会话对象 + :rtype: AsyncClientSession + """ + return self._client.start_session() diff --git a/apps/routers/api_key.py b/apps/routers/api_key.py index 1f0c5ceec0c951b9d430275bc4c5a227589a4e65..893ba848992348fdf79a632907a9255c29f4a378 100644 --- a/apps/routers/api_key.py +++ b/apps/routers/api_key.py @@ -1,15 +1,11 @@ -""" -FastAPI API Key相关路由 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI API Key相关路由""" from typing import Annotated from fastapi import APIRouter, Depends, status from fastapi.responses import JSONResponse -from apps.dependency.csrf import verify_csrf_token from apps.dependency.user import get_user, verify_user from apps.entities.api_key import GetAuthKeyRsp, PostAuthKeyMsg, PostAuthKeyRsp from apps.entities.response_data import ResponseData @@ -35,7 +31,7 @@ async def check_api_key_existence(user_sub: Annotated[str, Depends(get_user)]) - ).model_dump(exclude_none=True, by_alias=True)) -@router.post("", dependencies=[Depends(verify_csrf_token)], responses={ +@router.post("", responses={ 400: {"model": ResponseData}, }, response_model=PostAuthKeyRsp) async def manage_api_key(action: str, user_sub: Annotated[str, Depends(get_user)]) -> JSONResponse: diff --git a/apps/routers/appcenter.py b/apps/routers/appcenter.py index 2c54551825d3976c95ea64db9c127e02419df4c8..75c510cffb765eebf86f8b3df3337d5cdfbead7a 100644 --- a/apps/routers/appcenter.py +++ b/apps/routers/appcenter.py @@ -1,8 +1,5 @@ -""" -FastAPI 应用中心相关路由 - -Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. +"""FastAPI 应用中心相关路由""" import logging from typing import Annotated @@ -10,7 +7,6 @@ from typing import Annotated from fastapi import APIRouter, Body, Depends, Path, Query, status from fastapi.responses import JSONResponse -from apps.dependency.csrf import verify_csrf_token from apps.dependency.user import get_user, verify_user from apps.entities.appcenter import AppFlowInfo, AppPermissionData from apps.entities.enum_var import SearchType @@ -96,7 +92,7 @@ async def get_applications( # noqa: PLR0913 ) -@router.post("", dependencies=[Depends(verify_csrf_token)], response_model=BaseAppOperationRsp | ResponseData) +@router.post("", response_model=BaseAppOperationRsp | ResponseData) async def create_or_update_application( request: Annotated[CreateAppRequest, Body(...)], user_sub: Annotated[str, Depends(get_user)], @@ -230,6 +226,7 @@ async def get_application( message="OK", result=GetAppPropertyMsg( appId=app_data.id, + appType=app_data.app_type, published=app_data.published, name=app_data.name, description=app_data.description, @@ -242,6 +239,7 @@ async def get_application( authorizedUsers=app_data.permission.users, ), workflows=workflows, + mcpService=app_data.mcp_service, ), ).model_dump(exclude_none=True, by_alias=True), ) @@ -249,7 +247,6 @@ async def get_application( @router.delete( "/{appId}", - dependencies=[Depends(verify_csrf_token)], response_model=BaseAppOperationRsp | ResponseData, ) async def delete_application( @@ -299,7 +296,7 @@ async def delete_application( ) -@router.post("/{appId}", dependencies=[Depends(verify_csrf_token)], response_model=BaseAppOperationRsp) +@router.post("/{appId}", response_model=BaseAppOperationRsp) async def publish_application( app_id: Annotated[str, Path(..., alias="appId", description="应用ID")], user_sub: Annotated[str, Depends(get_user)], @@ -350,7 +347,7 @@ async def publish_application( ) -@router.put("/{appId}", dependencies=[Depends(verify_csrf_token)], response_model=ModFavAppRsp | ResponseData) +@router.put("/{appId}", response_model=ModFavAppRsp | ResponseData) async def modify_favorite_application( app_id: Annotated[str, Path(..., alias="appId", description="应用ID")], request: Annotated[ModFavAppRequest, Body(...)], diff --git a/apps/routers/auth.py b/apps/routers/auth.py index d5a78ddf5ace8923935f01178ffd842b501ca406..e76ac12e7c2680abf37dae37ecb181575dfca9c8 100644 --- a/apps/routers/auth.py +++ b/apps/routers/auth.py @@ -1,18 +1,16 @@ -""" -FastAPI 用户认证相关路由 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI 用户认证相关路由""" import logging +from pathlib import Path from typing import Annotated -from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response, status -from fastapi.responses import JSONResponse, RedirectResponse +from fastapi import APIRouter, Depends, Request, status +from fastapi.responses import HTMLResponse, JSONResponse +from fastapi.templating import Jinja2Templates -from apps.common.config import Config from apps.common.oidc import oidc_provider -from apps.dependency import get_user, verify_csrf_token, verify_user +from apps.dependency import get_session, get_user, verify_user from apps.entities.collection import Audit from apps.entities.response_data import ( AuthUserMsg, @@ -32,21 +30,18 @@ router = APIRouter( ) logger = logging.getLogger(__name__) +templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates") + @router.get("/login") -async def oidc_login(request: Request, code: str, redirect_index: str | None = None) -> RedirectResponse: +async def oidc_login(request: Request, code: str) -> HTMLResponse: """ OIDC login :param request: Request object :param code: OIDC code - :param redirect_index: redirect index - :return: RedirectResponse + :return: HTMLResponse """ - if redirect_index: - response = RedirectResponse(redirect_index, status_code=status.HTTP_301_MOVED_PERMANENTLY) - else: - response = RedirectResponse("/", status_code=status.HTTP_301_MOVED_PERMANENTLY) try: token = await oidc_provider.get_oidc_token(code) user_info = await oidc_provider.get_oidc_user(token["access_token"]) @@ -56,13 +51,14 @@ async def oidc_login(request: Request, code: str, redirect_index: str | None = N await oidc_provider.set_token(user_sub, token["access_token"], token["refresh_token"]) except Exception as e: logger.exception("User login failed") - if "auth error" in str(e): - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="auth error") from e - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User login failed.") from e + status_code = status.HTTP_400_BAD_REQUEST if "auth error" in str(e) else status.HTTP_403_FORBIDDEN + return templates.TemplateResponse( + "login_failed.html.j2", + {"request": request, "reason": "无法验证登录信息,请关闭本窗口并重试。"}, + status_code=status_code, + ) - user_host = None - if request.client is not None: - user_host = request.client.host + user_host = request.client.host if request.client else None if not user_sub: logger.error("OIDC no user_sub associated.") @@ -73,45 +69,16 @@ async def oidc_login(request: Request, code: str, redirect_index: str | None = N message="/api/auth/login: OIDC no user_sub associated.", ) await AuditLogManager.add_audit_log(data) - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User login failed.") + return templates.TemplateResponse( + "login_failed.html.j2", + {"request": request, "reason": "未能获取用户信息,请关闭本窗口并重试。"}, + status_code=status.HTTP_403_FORBIDDEN, + ) await UserManager.update_userinfo_by_user_sub(user_sub) - try: - current_session = request.cookies["ECSESSION"] - await SessionManager.delete_session(current_session) - except Exception: - logger.exception("Change session failed") - current_session = await SessionManager.create_session(user_host, user_sub) - new_csrf_token = await SessionManager.create_csrf_token(current_session) - if Config().get_config().deploy.mode == "debug": - response.set_cookie( - "_csrf_tk", - new_csrf_token, - ) - response.set_cookie( - "ECSESSION", - current_session, - ) - else: - response.set_cookie( - "_csrf_tk", - new_csrf_token, - max_age=Config().get_config().fastapi.session_ttl * 60, - secure=True, - domain=Config().get_config().fastapi.domain, - samesite="strict", - ) - response.set_cookie( - "ECSESSION", - current_session, - max_age=Config().get_config().fastapi.session_ttl * 60, - secure=True, - domain=Config().get_config().fastapi.domain, - httponly=True, - samesite="strict", - ) + data = Audit( user_sub=user_sub, http_method="get", @@ -119,16 +86,22 @@ async def oidc_login(request: Request, code: str, redirect_index: str | None = N client_ip=user_host, message="/api/auth/login: User login.", ) - await AuditLogManager.add_audit_log(data) - return response + + return templates.TemplateResponse( + "login_success.html.j2", + {"request": request, "current_session": current_session}, + ) # 用户主动logout -@router.get("/logout", dependencies=[Depends(verify_csrf_token)], response_model=ResponseData) -async def logout(request: Request, response: Response, user_sub: Annotated[str, Depends(get_user)]) -> JSONResponse: +@router.get("/logout", response_model=ResponseData) +async def logout( + request: Request, + user_sub: Annotated[str, Depends(get_user)], + session_id: Annotated[str, Depends(get_session)], +) -> JSONResponse: """用户登出EulerCopilot""" - session_id = request.cookies["ECSESSION"] if not request.client: return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, @@ -140,18 +113,6 @@ async def logout(request: Request, response: Response, user_sub: Annotated[str, ) await TokenManager.delete_plugin_token(user_sub) await SessionManager.delete_session(session_id) - new_session = await SessionManager.create_session(request.client.host) - - response.set_cookie( - "ECSESSION", - new_session, - max_age=Config().get_config().fastapi.session_ttl * 60, - httponly=True, - secure=True, - samesite="strict", - domain=Config().get_config().fastapi.domain, - ) - response.delete_cookie("_csrf_tk") data = Audit( http_method="get", @@ -162,7 +123,6 @@ async def logout(request: Request, response: Response, user_sub: Annotated[str, ) await AuditLogManager.add_audit_log(data) - await oidc_provider.oidc_logout(request.cookies) return JSONResponse( status_code=status.HTTP_200_OK, content=ResponseData( @@ -174,40 +134,20 @@ async def logout(request: Request, response: Response, user_sub: Annotated[str, @router.get("/redirect", response_model=OidcRedirectRsp) -async def oidc_redirect(request: Request, action: Annotated[str, Query()] = "login") -> JSONResponse: +async def oidc_redirect() -> JSONResponse: """OIDC重定向URL""" - if action == "login": - redirect_url = await oidc_provider.get_redirect_url() - return JSONResponse( - status_code=status.HTTP_200_OK, - content=OidcRedirectRsp( - code=status.HTTP_200_OK, - message="success", - result=OidcRedirectMsg(url=redirect_url), - ).model_dump(exclude_none=True, by_alias=True), - ) - if action == "logout": - await oidc_provider.oidc_logout(request.cookies) - return JSONResponse( - status_code=status.HTTP_200_OK, - content=OidcRedirectRsp( - code=status.HTTP_200_OK, - message="success", - result=OidcRedirectMsg(url=Config().get_config().fastapi.domain), - ).model_dump(exclude_none=True, by_alias=True), - ) + redirect_url = await oidc_provider.get_redirect_url() return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content=ResponseData( - code=status.HTTP_400_BAD_REQUEST, - message="invalid action", - result={}, + status_code=status.HTTP_200_OK, + content=OidcRedirectRsp( + code=status.HTTP_200_OK, + message="success", + result=OidcRedirectMsg(url=redirect_url), ).model_dump(exclude_none=True, by_alias=True), ) # TODO(zwt): OIDC主动触发logout -# 002 @router.post("/logout", dependencies=[Depends(verify_user)], response_model=ResponseData) async def oidc_logout(token: str) -> JSONResponse: """OIDC主动触发登出""" @@ -215,7 +155,8 @@ async def oidc_logout(token: str) -> JSONResponse: @router.get("/user", response_model=AuthUserRsp) async def userinfo( - user_sub: Annotated[str, Depends(get_user)], _: Annotated[None, Depends(verify_user)], + user_sub: Annotated[str, Depends(get_user)], + _: Annotated[None, Depends(verify_user)], ) -> JSONResponse: """获取用户信息""" user = await UserManager.get_userinfo_by_user_sub(user_sub=user_sub) @@ -236,6 +177,7 @@ async def userinfo( result=AuthUserMsg( user_sub=user_sub, revision=user.is_active, + is_admin=user.is_admin, ), ).model_dump(exclude_none=True, by_alias=True), ) @@ -243,13 +185,13 @@ async def userinfo( @router.post( "/update_revision_number", - dependencies=[Depends(verify_user), Depends(verify_csrf_token)], + dependencies=[Depends(verify_user)], response_model=AuthUserRsp, responses={ status.HTTP_500_INTERNAL_SERVER_ERROR: {"model": ResponseData}, }, ) -async def update_revision_number(_post_body, user_sub: Annotated[str, Depends(get_user)]) -> JSONResponse: +async def update_revision_number(request: Request, user_sub: Annotated[str, Depends(get_user)]) -> JSONResponse: # noqa: ARG001 """更新用户协议信息""" ret: bool = await UserManager.update_userinfo_by_user_sub(user_sub, refresh_revision=True) if not ret: @@ -262,6 +204,17 @@ async def update_revision_number(_post_body, user_sub: Annotated[str, Depends(ge ).model_dump(exclude_none=True, by_alias=True), ) + user = await UserManager.get_userinfo_by_user_sub(user_sub) + if not user: + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=ResponseData( + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message="Get UserInfo failed.", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + return JSONResponse( status_code=status.HTTP_200_OK, content=AuthUserRsp( @@ -269,7 +222,8 @@ async def update_revision_number(_post_body, user_sub: Annotated[str, Depends(ge message="success", result=AuthUserMsg( user_sub=user_sub, - revision=False, + revision=user.is_active, + is_admin=user.is_admin, ), ).model_dump(exclude_none=True, by_alias=True), ) diff --git a/apps/routers/blacklist.py b/apps/routers/blacklist.py index ac2b763697f633edf5b879e267aaad9557e79bb4..388d332555c3f0de807dccafa98dce2024112335 100644 --- a/apps/routers/blacklist.py +++ b/apps/routers/blacklist.py @@ -1,15 +1,11 @@ -""" -FastAPI 黑名单相关路由 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI 黑名单相关路由""" from typing import Annotated from fastapi import APIRouter, Depends, status from fastapi.responses import JSONResponse -from apps.dependency.csrf import verify_csrf_token from apps.dependency.user import get_user, verify_user from apps.entities.request_data import ( AbuseProcessRequest, @@ -55,7 +51,7 @@ async def get_blacklist_user(page: int = 0): # noqa: ANN201 ).model_dump(exclude_none=True, by_alias=True)) -@router.post("/user", dependencies=[Depends(verify_csrf_token)], response_model=ResponseData) +@router.post("/user", response_model=ResponseData) async def change_blacklist_user(request: UserBlacklistRequest): # noqa: ANN201 """操作黑名单用户""" # 拉黑用户 @@ -102,7 +98,7 @@ async def get_blacklist_question(page: int = 0): # noqa: ANN201 result=GetBlacklistQuestionMsg(question_list=question_list), ).model_dump(exclude_none=True, by_alias=True)) -@router.post("/question", dependencies=[Depends(verify_csrf_token)], response_model=ResponseData) +@router.post("/question", response_model=ResponseData) async def change_blacklist_question(request: QuestionBlacklistRequest): # noqa: ANN201 """黑名单问题检测或操作""" # 删问题 @@ -135,7 +131,7 @@ async def change_blacklist_question(request: QuestionBlacklistRequest): # noqa: ).model_dump(exclude_none=True, by_alias=True)) -@router.post("/complaint", dependencies=[Depends(verify_csrf_token)], response_model=ResponseData) +@router.post("/complaint", response_model=ResponseData) async def abuse_report(request: AbuseRequest, user_sub: Annotated[str, Depends(get_user)]): # noqa: ANN201 """用户实施举报""" result = await AbuseManager.change_abuse_report( @@ -173,7 +169,7 @@ async def get_abuse_report(page: int = 0): # noqa: ANN201 result=GetBlacklistQuestionMsg(question_list=result), ).model_dump(exclude_none=True, by_alias=True)) -@router.post("/abuse", dependencies=[Depends(verify_csrf_token)], response_model=ResponseData) +@router.post("/abuse", response_model=ResponseData) async def change_abuse_report(request: AbuseProcessRequest): # noqa: ANN201 """对被举报问答对进行操作""" if request.is_deletion: diff --git a/apps/routers/chat.py b/apps/routers/chat.py index a822f8865d8a14b177994fd5f1b662e2138cae9c..2c7de268d699d20d027fb0e4609b4b0df96180be 100644 --- a/apps/routers/chat.py +++ b/apps/routers/chat.py @@ -1,8 +1,5 @@ -""" -FastAPI 聊天接口 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI 聊天接口""" import asyncio import logging @@ -15,12 +12,7 @@ from fastapi.responses import JSONResponse, StreamingResponse from apps.common.queue import MessageQueue from apps.common.wordscheck import WordsCheck -from apps.dependency import ( - get_session, - get_user, - verify_csrf_token, - verify_user, -) +from apps.dependency import get_session, get_user from apps.entities.request_data import RequestData from apps.entities.response_data import ResponseData from apps.entities.task import Task @@ -118,7 +110,7 @@ async def chat_generator(post_body: RequestData, user_sub: str, session_id: str) await Activity.remove_active(user_sub) -@router.post("/chat", dependencies=[Depends(verify_csrf_token), Depends(verify_user)]) +@router.post("/chat") async def chat( post_body: RequestData, user_sub: Annotated[str, Depends(get_user)], @@ -145,7 +137,7 @@ async def chat( ) -@router.post("/stop", response_model=ResponseData, dependencies=[Depends(verify_csrf_token)]) +@router.post("/stop", response_model=ResponseData) async def stop_generation(user_sub: Annotated[str, Depends(get_user)]): # noqa: ANN201 """停止生成""" await Activity.remove_active(user_sub) diff --git a/apps/routers/client.py b/apps/routers/client.py deleted file mode 100644 index 170b6adf64fdaa912836e8e961858f545a15785d..0000000000000000000000000000000000000000 --- a/apps/routers/client.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -FastAPI Shell端对接相关接口 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" - -from typing import Annotated - -from fastapi import APIRouter, Depends, status -from fastapi.responses import JSONResponse -from starlette.requests import HTTPConnection - -from apps.dependency.user import get_user_by_api_key -from apps.entities.request_data import ClientSessionData -from apps.entities.response_data import ( - PostClientSessionMsg, - PostClientSessionRsp, - ResponseData, -) -from apps.manager.session import SessionManager - -router = APIRouter( - prefix="/api/client", - tags=["client"], -) - - -@router.post("/session", response_model=PostClientSessionRsp, responses={ - status.HTTP_400_BAD_REQUEST: {"model": ResponseData}, -}) -async def get_session_id( - request: HTTPConnection, - post_body: ClientSessionData, - user_sub: Annotated[str, Depends(get_user_by_api_key)], -) -> JSONResponse: - """获取客户端会话ID""" - session_id: str | None = post_body.session_id - if not request.client: - return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST, content=ResponseData( - code=status.HTTP_400_BAD_REQUEST, - message="client not found", - result={}, - ).model_dump(exclude_none=True, by_alias=True)) - if (session_id and not await SessionManager.verify_user(session_id)) or not session_id: - return JSONResponse(status_code=status.HTTP_200_OK, content=ResponseData( - code=status.HTTP_200_OK, - message="gen new session id success", - result={ - "session_id": await SessionManager.create_session(request.client.host, user_sub), - }, - ).model_dump(exclude_none=True, by_alias=True)) - return JSONResponse(status_code=status.HTTP_200_OK, content=PostClientSessionRsp( - code=status.HTTP_200_OK, - message="verify session id success", - result=PostClientSessionMsg( - session_id=session_id, - ), - ).model_dump(exclude_none=True, by_alias=True)) diff --git a/apps/routers/comment.py b/apps/routers/comment.py index 781674c286ee60ddc30029f583d5d4ec592a4de3..79787cd3f9db2f07bb8a5a85fb8fdccf7a170cbb 100644 --- a/apps/routers/comment.py +++ b/apps/routers/comment.py @@ -1,8 +1,5 @@ -""" -FastAPI 评论相关接口 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI 评论相关接口""" import logging from datetime import UTC, datetime @@ -11,7 +8,7 @@ from typing import Annotated from fastapi import APIRouter, Depends, status from fastapi.responses import JSONResponse -from apps.dependency import get_user, verify_csrf_token, verify_user +from apps.dependency import get_user, verify_user from apps.entities.record import RecordComment from apps.entities.request_data import AddCommentData from apps.entities.response_data import ResponseData @@ -28,7 +25,7 @@ router = APIRouter( ) -@router.post("", dependencies=[Depends(verify_csrf_token)], response_model=ResponseData) +@router.post("", response_model=ResponseData) async def add_comment(post_body: AddCommentData, user_sub: Annotated[str, Depends(get_user)]) -> JSONResponse: """给Record添加评论""" if not await RecordManager.verify_record_in_group(post_body.group_id, post_body.record_id, user_sub): diff --git a/apps/routers/conversation.py b/apps/routers/conversation.py index 92872ed38f9c780c5904fe2b1138582ab018ebd3..b4429e81bb2f8d04d9bc8c4b832f3f2658bfeb98 100644 --- a/apps/routers/conversation.py +++ b/apps/routers/conversation.py @@ -1,18 +1,15 @@ -""" -FastAPI:对话相关接口 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI:对话相关接口""" import logging from datetime import datetime from typing import Annotated import pytz -from fastapi import APIRouter, Depends, Query, Request, status +from fastapi import APIRouter, Body, Depends, Query, Request, status from fastapi.responses import JSONResponse -from apps.dependency import get_user, verify_csrf_token, verify_user +from apps.dependency import get_user, verify_user from apps.entities.collection import Audit, Conversation from apps.entities.request_data import ( DeleteConversationData, @@ -26,6 +23,8 @@ from apps.entities.response_data import ( ConversationListRsp, DeleteConversationMsg, DeleteConversationRsp, + KbIteam, + LLMIteam, ResponseData, UpdateConversationRsp, ) @@ -45,10 +44,12 @@ router = APIRouter( logger = logging.getLogger(__name__) -async def create_new_conversation( +async def create_new_conversation( # noqa: PLR0913 user_sub: str, conv_list: list[Conversation], app_id: str = "", + llm_id: str = "empty", + kb_ids: list[str] = [], *, debug: bool = False, ) -> Conversation | None: @@ -69,7 +70,13 @@ async def create_new_conversation( if app_id and not await AppManager.validate_user_app_access(user_sub, app_id): err = "Invalid app_id." raise RuntimeError(err) - new_conv = await ConversationManager.add_conversation_by_user_sub(user_sub, app_id=app_id, debug=debug) + new_conv = await ConversationManager.add_conversation_by_user_sub( + user_sub, + app_id=app_id, + llm_id=llm_id, + kb_ids=kb_ids, + debug=debug, + ) if not new_conv: err = "Create new conversation failed." raise RuntimeError(err) @@ -88,8 +95,9 @@ async def get_conversation_list(user_sub: Annotated[str, Depends(get_user)]) -> """获取对话列表""" conversations = await ConversationManager.get_conversation_by_user_sub(user_sub) # 把已有对话转换为列表 - result_conversations = [ - ConversationListItem( + result_conversations = [] + for conv in conversations: + conversation_list_item = ConversationListItem( conversationId=conv.id, title=conv.title, docCount=await DocumentManager.get_doc_count(user_sub, conv.id), @@ -99,8 +107,27 @@ async def get_conversation_list(user_sub: Annotated[str, Depends(get_user)]) -> appId=conv.app_id if conv.app_id else "", debug=conv.debug if conv.debug else False, ) - for conv in conversations - ] + if conv.llm: + llm_item = LLMIteam( + llmId=conv.llm.llm_id, + modelName=conv.llm.model_name, + icon=conv.llm.icon, + ) + else: + llm_item = None + if conv.kb_list: + kb_item_list = [] + for kb in conv.kb_list: + kb_item = KbIteam( + kbId=kb.kb_id, + kbName=kb.kb_name, + ) + kb_item_list.append(kb_item) + else: + kb_item_list = [] + conversation_list_item.llm = llm_item + conversation_list_item.kb_list = kb_item_list + result_conversations.append(conversation_list_item) # 新建对话 try: @@ -139,10 +166,12 @@ async def get_conversation_list(user_sub: Annotated[str, Depends(get_user)]) -> ) -@router.post("", dependencies=[Depends(verify_csrf_token)], response_model=AddConversationRsp) +@router.post("", response_model=AddConversationRsp) async def add_conversation( user_sub: Annotated[str, Depends(get_user)], app_id: Annotated[str, Query(..., alias="appId")] = "", + llm_id: Annotated[str, Body(..., alias="llmId")] = "empty", + kb_ids: Annotated[list[str], Body(..., alias="kbIds")] = [], *, debug: Annotated[bool, Query()] = False, ) -> JSONResponse: @@ -152,7 +181,14 @@ async def add_conversation( try: app_id = app_id if app_id else "" debug = debug if debug is not None else False - new_conv = await create_new_conversation(user_sub, conversations, app_id=app_id, debug=debug) + new_conv = await create_new_conversation( + user_sub, + conversations, + app_id=app_id, + llm_id=llm_id, + kb_ids=kb_ids, + debug=debug, + ) except RuntimeError as e: return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -182,11 +218,11 @@ async def add_conversation( ) -@router.put("", response_model=UpdateConversationRsp, dependencies=[Depends(verify_csrf_token)]) +@router.put("", response_model=UpdateConversationRsp) async def update_conversation( - post_body: ModifyConversationData, - conversation_id: Annotated[str, Query(..., alias="conversationId")], user_sub: Annotated[str, Depends(get_user)], + conversation_id: Annotated[str, Query(..., alias="conversationId")], + post_body: ModifyConversationData, ) -> JSONResponse: """更新特定Conversation的数据""" # 判断Conversation是否合法 @@ -240,7 +276,7 @@ async def update_conversation( ) -@router.delete("", response_model=ResponseData, dependencies=[Depends(verify_csrf_token)]) +@router.delete("", response_model=ResponseData) async def delete_conversation( request: Request, post_body: DeleteConversationData, diff --git a/apps/routers/document.py b/apps/routers/document.py index dced25e87f23adf8fc45be40faa4914371e4e55c..04feb1e72c095ae790af35221fde569c6c704c71 100644 --- a/apps/routers/document.py +++ b/apps/routers/document.py @@ -9,7 +9,7 @@ from typing import Annotated from fastapi import APIRouter, Depends, File, Query, UploadFile, status from fastapi.responses import JSONResponse -from apps.dependency import get_user, verify_csrf_token, verify_user +from apps.dependency import get_user, verify_user from apps.entities.enum_var import DocumentStatus from apps.entities.response_data import ( ConversationDocumentItem, @@ -32,7 +32,7 @@ router = APIRouter( ) -@router.post("/{conversation_id}", dependencies=[Depends(verify_csrf_token)]) +@router.post("/{conversation_id}") async def document_upload( # noqa: ANN201 conversation_id: str, documents: Annotated[list[UploadFile], File(...)], @@ -141,16 +141,16 @@ async def delete_single_document(document_id: str, user_sub: Annotated[str, Depe ).model_dump(exclude_none=True, by_alias=False), ) # 在RAG侧删除 - result = await KnowledgeBaseService.delete_doc_from_rag([document_id]) - if not result: - return JSONResponse( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content=ResponseData( - code=status.HTTP_500_INTERNAL_SERVER_ERROR, - message="RAG端删除文件失败", - result={}, - ).model_dump(exclude_none=True, by_alias=False), - ) + # result = await KnowledgeBaseService.delete_doc_from_rag([document_id]) + # if not result: + # return JSONResponse( + # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + # content=ResponseData( + # code=status.HTTP_500_INTERNAL_SERVER_ERROR, + # message="RAG端删除文件失败", + # result={}, + # ).model_dump(exclude_none=True, by_alias=False), + # ) return JSONResponse( status_code=status.HTTP_200_OK, diff --git a/apps/routers/domain.py b/apps/routers/domain.py index d51e8572dd8e900e07b2e0bcb24618b250505787..b64da25f6afababb63a521873fa94d13d9480b9f 100644 --- a/apps/routers/domain.py +++ b/apps/routers/domain.py @@ -1,13 +1,9 @@ -""" -FastAPI 用户画像相关API - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI 用户画像相关API""" from fastapi import APIRouter, Depends, status from fastapi.responses import JSONResponse -from apps.dependency.csrf import verify_csrf_token from apps.dependency.user import verify_user from apps.entities.request_data import PostDomainData from apps.entities.response_data import ResponseData @@ -17,7 +13,6 @@ router = APIRouter( prefix="/api/domain", tags=["domain"], dependencies=[ - Depends(verify_csrf_token), Depends(verify_user), ], ) diff --git a/apps/routers/flow.py b/apps/routers/flow.py index 1ad7351b490dde227e6d5f2c6e1f57de0cf4aada..ea321bb8af0ab42b4ed05cf021732996c978fcf6 100644 --- a/apps/routers/flow.py +++ b/apps/routers/flow.py @@ -1,8 +1,5 @@ -""" -FastAPI Flow拓扑结构展示API - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI Flow拓扑结构展示API""" from typing import Annotated @@ -10,7 +7,6 @@ from fastapi import APIRouter, Body, Depends, Query, status from fastapi.responses import JSONResponse from apps.dependency import get_user -from apps.dependency.csrf import verify_csrf_token from apps.dependency.user import verify_user from apps.entities.request_data import PutFlowReq from apps.entities.response_data import ( @@ -24,8 +20,8 @@ from apps.entities.response_data import ( NodeServiceListRsp, ResponseData, ) -from apps.manager.application import AppManager from apps.manager.appcenter import AppCenterManager +from apps.manager.application import AppManager from apps.manager.flow import FlowManager from apps.service.flow import FlowService @@ -33,16 +29,18 @@ router = APIRouter( prefix="/api/flow", tags=["flow"], dependencies=[ - Depends(verify_csrf_token), Depends(verify_user), ], ) -@router.get("/service", responses={ - status.HTTP_200_OK: {"model": NodeServiceListRsp}, - status.HTTP_404_NOT_FOUND: {"model": ResponseData}, -}) +@router.get( + "/service", + responses={ + status.HTTP_200_OK: {"model": NodeServiceListRsp}, + status.HTTP_404_NOT_FOUND: {"model": ResponseData}, + }, +) async def get_services( user_sub: Annotated[str, Depends(get_user)], ) -> NodeServiceListRsp: @@ -62,40 +60,14 @@ async def get_services( ) -# @router.get("/service/node", response_model=NodeMetaDataRsp, responses={ -# status.HTTP_403_FORBIDDEN: {"model": ResponseData}, -# status.HTTP_404_NOT_FOUND: {"model": ResponseData}, -# }) -# async def get_node_metadatas( -# user_sub: Annotated[str, Depends(get_user)], -# node_metadata_id: Annotated[str, Query(alias="NodeMetadataId")], -# ): -# """获取节点元数据的详细信息""" -# if not await FlowManager.validate_user_node_meta_data_access(user_sub, node_metadata_id): -# return JSONResponse(status_code=status.HTTP_403_FORBIDDEN, content=NodeMetaDataRsp( -# code=status.HTTP_403_FORBIDDEN, -# message="用户没有权限访问该节点原数据", -# result=NodeItem(), -# ).model_dump(exclude_none=True, by_alias=True)) - -# result = await FlowManager.get_node_meta_data_by_node_meta_data_id(node_metadata_id) -# if result is None: -# return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content=NodeMetaDataRsp( -# code=status.HTTP_404_NOT_FOUND, -# message="节点元数据详细信息获取失败", -# result=NodeItem(), -# ).model_dump(exclude_none=True, by_alias=True)) -# return JSONResponse(status_code=status.HTTP_200_OK, content=NodeMetaDataRsp( -# code=status.HTTP_200_OK, -# message="节点元数据详细信息获取成功", -# result=result -# ).model_dump(exclude_none=True, by_alias=True)) - - -@router.get("", response_model=FlowStructureGetRsp, responses={ - status.HTTP_403_FORBIDDEN: {"model": ResponseData}, - status.HTTP_404_NOT_FOUND: {"model": ResponseData}, -}) +@router.get( + "", + response_model=FlowStructureGetRsp, + responses={ + status.HTTP_403_FORBIDDEN: {"model": ResponseData}, + status.HTTP_404_NOT_FOUND: {"model": ResponseData}, + }, +) async def get_flow( user_sub: Annotated[str, Depends(get_user)], app_id: Annotated[str, Query(alias="appId")], @@ -103,31 +75,44 @@ async def get_flow( ) -> JSONResponse: """获取流拓扑结构""" if not await AppManager.validate_user_app_access(user_sub, app_id): - return JSONResponse(status_code=status.HTTP_403_FORBIDDEN, content=FlowStructureGetRsp( - code=status.HTTP_403_FORBIDDEN, - message="用户没有权限访问该流", - result=FlowStructureGetMsg(), - ).model_dump(exclude_none=True, by_alias=True)) + return JSONResponse( + status_code=status.HTTP_403_FORBIDDEN, + content=FlowStructureGetRsp( + code=status.HTTP_403_FORBIDDEN, + message="用户没有权限访问该流", + result=FlowStructureGetMsg(), + ).model_dump(exclude_none=True, by_alias=True), + ) result = await FlowManager.get_flow_by_app_and_flow_id(app_id, flow_id) if result is None: - return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content=FlowStructureGetRsp( - code=status.HTTP_404_NOT_FOUND, - message="应用下流程获取失败", - result=FlowStructureGetMsg(), - ).model_dump(exclude_none=True, by_alias=True)) - return JSONResponse(status_code=status.HTTP_200_OK, content=FlowStructureGetRsp( - code=status.HTTP_200_OK, - message="应用下流程获取成功", - result=FlowStructureGetMsg(flow=result), - ).model_dump(exclude_none=True, by_alias=True)) + return JSONResponse( + status_code=status.HTTP_404_NOT_FOUND, + content=FlowStructureGetRsp( + code=status.HTTP_404_NOT_FOUND, + message="应用下流程获取失败", + result=FlowStructureGetMsg(), + ).model_dump(exclude_none=True, by_alias=True), + ) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=FlowStructureGetRsp( + code=status.HTTP_200_OK, + message="应用下流程获取成功", + result=FlowStructureGetMsg(flow=result), + ).model_dump(exclude_none=True, by_alias=True), + ) -@router.put("", response_model=FlowStructurePutRsp, responses={ - status.HTTP_400_BAD_REQUEST: {"model": ResponseData}, - status.HTTP_403_FORBIDDEN: {"model": ResponseData}, - status.HTTP_404_NOT_FOUND: {"model": ResponseData}, - status.HTTP_500_INTERNAL_SERVER_ERROR: {"model": ResponseData}, -}) +@router.put( + "", + response_model=FlowStructurePutRsp, + responses={ + status.HTTP_400_BAD_REQUEST: {"model": ResponseData}, + status.HTTP_403_FORBIDDEN: {"model": ResponseData}, + status.HTTP_404_NOT_FOUND: {"model": ResponseData}, + status.HTTP_500_INTERNAL_SERVER_ERROR: {"model": ResponseData}, + }, +) async def put_flow( user_sub: Annotated[str, Depends(get_user)], app_id: Annotated[str, Query(alias="appId")], @@ -136,39 +121,55 @@ async def put_flow( ) -> JSONResponse: """修改流拓扑结构""" if not await AppManager.validate_app_belong_to_user(user_sub, app_id): - return JSONResponse(status_code=status.HTTP_403_FORBIDDEN, content=FlowStructurePutRsp( - code=status.HTTP_403_FORBIDDEN, - message="用户没有权限访问该流", - result=FlowStructurePutMsg(), - ).model_dump(exclude_none=True, by_alias=True)) + return JSONResponse( + status_code=status.HTTP_403_FORBIDDEN, + content=FlowStructurePutRsp( + code=status.HTTP_403_FORBIDDEN, + message="用户没有权限访问该流", + result=FlowStructurePutMsg(), + ).model_dump(exclude_none=True, by_alias=True), + ) put_body.flow = await FlowService.remove_excess_structure_from_flow(put_body.flow) await FlowService.validate_flow_illegal(put_body.flow) put_body.flow.connectivity = await FlowService.validate_flow_connectivity(put_body.flow) result = await FlowManager.put_flow_by_app_and_flow_id(app_id, flow_id, put_body.flow) if result is None: - return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=FlowStructurePutRsp( - code=status.HTTP_500_INTERNAL_SERVER_ERROR, - message="应用下流更新失败", - result=FlowStructurePutMsg(), - ).model_dump(exclude_none=True, by_alias=True)) + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=FlowStructurePutRsp( + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message="应用下流更新失败", + result=FlowStructurePutMsg(), + ).model_dump(exclude_none=True, by_alias=True), + ) flow = await FlowManager.get_flow_by_app_and_flow_id(app_id, flow_id) await AppCenterManager.update_app_publish_status(app_id, user_sub) if flow is None: - return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=FlowStructurePutRsp( - code=status.HTTP_500_INTERNAL_SERVER_ERROR, - message="应用下流更新后获取失败", - result=FlowStructurePutMsg(), - ).model_dump(exclude_none=True, by_alias=True)) - return JSONResponse(status_code=status.HTTP_200_OK, content=FlowStructurePutRsp( - code=status.HTTP_200_OK, - message="应用下流更新成功", - result=FlowStructurePutMsg(flow=flow), - ).model_dump(exclude_none=True, by_alias=True)) + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=FlowStructurePutRsp( + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message="应用下流更新后获取失败", + result=FlowStructurePutMsg(), + ).model_dump(exclude_none=True, by_alias=True), + ) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=FlowStructurePutRsp( + code=status.HTTP_200_OK, + message="应用下流更新成功", + result=FlowStructurePutMsg(flow=flow), + ).model_dump(exclude_none=True, by_alias=True), + ) -@router.delete("", response_model=FlowStructureDeleteRsp, responses={ - status.HTTP_404_NOT_FOUND: {"model": ResponseData}, -}) +@router.delete( + "", + response_model=FlowStructureDeleteRsp, + responses={ + status.HTTP_404_NOT_FOUND: {"model": ResponseData}, + }, +) async def delete_flow( user_sub: Annotated[str, Depends(get_user)], app_id: Annotated[str, Query(alias="appId")], @@ -176,20 +177,29 @@ async def delete_flow( ) -> JSONResponse: """删除流拓扑结构""" if not await AppManager.validate_app_belong_to_user(user_sub, app_id): - return JSONResponse(status_code=status.HTTP_403_FORBIDDEN, content=FlowStructureDeleteRsp( - code=status.HTTP_403_FORBIDDEN, - message="用户没有权限访问该流", - result=FlowStructureDeleteMsg(), - ).model_dump(exclude_none=True, by_alias=True)) + return JSONResponse( + status_code=status.HTTP_403_FORBIDDEN, + content=FlowStructureDeleteRsp( + code=status.HTTP_403_FORBIDDEN, + message="用户没有权限访问该流", + result=FlowStructureDeleteMsg(), + ).model_dump(exclude_none=True, by_alias=True), + ) result = await FlowManager.delete_flow_by_app_and_flow_id(app_id, flow_id) if result is None: - return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content=FlowStructureDeleteRsp( - code=status.HTTP_404_NOT_FOUND, - message="应用下流程删除失败", - result=FlowStructureDeleteMsg(), - ).model_dump(exclude_none=True, by_alias=True)) - return JSONResponse(status_code=status.HTTP_200_OK, content=FlowStructureDeleteRsp( - code=status.HTTP_200_OK, - message="应用下流程删除成功", - result=FlowStructureDeleteMsg(flowId=result), - ).model_dump(exclude_none=True, by_alias=True)) + return JSONResponse( + status_code=status.HTTP_404_NOT_FOUND, + content=FlowStructureDeleteRsp( + code=status.HTTP_404_NOT_FOUND, + message="应用下流程删除失败", + result=FlowStructureDeleteMsg(), + ).model_dump(exclude_none=True, by_alias=True), + ) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=FlowStructureDeleteRsp( + code=status.HTTP_200_OK, + message="应用下流程删除成功", + result=FlowStructureDeleteMsg(flowId=result), + ).model_dump(exclude_none=True, by_alias=True), + ) diff --git a/apps/routers/health.py b/apps/routers/health.py index f6ecb2bdbfb55b53cbab22d5d02499f42790da45..9b0f9d518bb501643c5697073ea4da74295e782d 100644 --- a/apps/routers/health.py +++ b/apps/routers/health.py @@ -1,8 +1,5 @@ -""" -FastAPI 健康检查接口 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI 健康检查接口""" from fastapi import APIRouter, status from fastapi.responses import JSONResponse diff --git a/apps/routers/knowledge.py b/apps/routers/knowledge.py index 04ce2cf6600a3729f53fee9941756d8f46068b79..2d22dfd2c456985f9f2042c0b237b430e945d1ee 100644 --- a/apps/routers/knowledge.py +++ b/apps/routers/knowledge.py @@ -1,71 +1,66 @@ -""" -FastAPI 用户资产库路由 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI 用户资产库路由""" from typing import Annotated -from fastapi import APIRouter, Depends, status +from fastapi import APIRouter, Body, Depends, Query, status from fastapi.responses import JSONResponse from apps.dependency import get_user, verify_user from apps.entities.request_data import ( - PostKnowledgeIDData, + UpdateKbReq, ) from apps.entities.response_data import ( - GetKnowledgeIDMsg, - GetKnowledgeIDRsp, + ListTeamKnowledgeMsg, + ListTeamKnowledgeRsp, ResponseData, ) from apps.manager.knowledge import KnowledgeBaseManager router = APIRouter( prefix="/api/knowledge", - tags=["知识库"], + tags=["knowledge"], dependencies=[ Depends(verify_user), ], ) -@router.get("", response_model=GetKnowledgeIDRsp, responses={ - status.HTTP_404_NOT_FOUND: {"model": ResponseData}, - }, +@router.get("", response_model=ListTeamKnowledgeRsp, responses={ + status.HTTP_404_NOT_FOUND: {"model": ResponseData}, +}, ) -async def get_kb_id(user_sub: Annotated[str, Depends(get_user)]) -> JSONResponse: +async def list_kb( + user_sub: Annotated[str, Depends(get_user)], + conversation_id: Annotated[str, Query(alias="conversationId")], + kb_id: Annotated[str, Query(alias="kbId")] = None, + kb_name: Annotated[str, Query(alias="kbName")] = "", +) -> JSONResponse: """获取当前用户的知识库ID""" - kb_id = await KnowledgeBaseManager.get_kb_id(user_sub) - kb_id_str = "" if kb_id is None else kb_id + team_kb_list = await KnowledgeBaseManager.list_team_kb(user_sub, conversation_id, kb_id, kb_name) return JSONResponse( status_code=status.HTTP_200_OK, - content=GetKnowledgeIDRsp( + content=ListTeamKnowledgeRsp( code=status.HTTP_200_OK, message="success", - result=GetKnowledgeIDMsg(kb_id=kb_id_str), + result=ListTeamKnowledgeMsg(teamKbList=team_kb_list), ).model_dump(exclude_none=True, by_alias=True), ) -@router.post("", response_model=ResponseData) -async def change_kb_id(post_body: PostKnowledgeIDData, user_sub: Annotated[str, Depends(get_user)]) -> JSONResponse: - """修改当前用户的知识库ID""" - result = await KnowledgeBaseManager.change_kb_id(user_sub, post_body.kb_id) - if not result: - return JSONResponse( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content=ResponseData( - code=status.HTTP_500_INTERNAL_SERVER_ERROR, - message="change kb_id error", - result={}, - ).model_dump(exclude_none=True, by_alias=True), - ) +@router.put("", response_model=ResponseData) +async def update_conversation_kb( + user_sub: Annotated[str, Depends(get_user)], + conversation_id: Annotated[str, Query(alias="conversationId")], + put_body: Annotated[UpdateKbReq, Body(...)], +) -> JSONResponse: + """更新当前用户的知识库ID""" + kb_ids_update_success = await KnowledgeBaseManager.update_conv_kb(user_sub, conversation_id, put_body.kb_ids) return JSONResponse( status_code=status.HTTP_200_OK, content=ResponseData( code=status.HTTP_200_OK, message="success", - result={}, + result=kb_ids_update_success, ).model_dump(exclude_none=True, by_alias=True), ) - diff --git a/apps/routers/llm.py b/apps/routers/llm.py new file mode 100644 index 0000000000000000000000000000000000000000..234fef843764b213ae60fa28c9fa2bbabb90a902 --- /dev/null +++ b/apps/routers/llm.py @@ -0,0 +1,130 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI 大模型相关接口""" + +from typing import Annotated, Optional + +from fastapi import APIRouter, Body, Depends, Query, status +from fastapi.responses import JSONResponse + +from apps.dependency import get_user, verify_user +from apps.entities.request_data import ( + UpdateLLMReq, +) +from apps.entities.response_data import ( + ListLLMProviderRsp, + ListLLMRsp, + ResponseData, +) +from apps.manager.llm import LLMManager + +router = APIRouter( + prefix="/api/llm", + tags=["llm"], + dependencies=[ + Depends(verify_user), + ], +) + + +@router.get("/provider", response_model=ListLLMProviderRsp, responses={ + status.HTTP_404_NOT_FOUND: {"model": ResponseData}, +}, +) +async def list_llm_provider( + user_sub: Annotated[str, Depends(get_user)], +) -> JSONResponse: + llm_provider_list = await LLMManager.list_llm_provider() + return JSONResponse( + status_code=status.HTTP_200_OK, + content=ListLLMProviderRsp( + code=status.HTTP_200_OK, + message="success", + result=llm_provider_list, + ).model_dump(exclude_none=True, by_alias=True), + ) + + +@router.get("", response_model=ListLLMRsp, + responses={ + status.HTTP_404_NOT_FOUND: {"model": ResponseData}, + }, + ) +async def list_llm( + user_sub: Annotated[str, Depends(get_user)], + llm_id: Optional[str] = Query(default=None, description="大模型ID", alias="llmId"), +) -> JSONResponse: + llm_list = await LLMManager.list_llm(user_sub, llm_id) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=ListLLMRsp( + code=status.HTTP_200_OK, + message="success", + result=llm_list + ).model_dump(exclude_none=True, by_alias=True), + ) + + +@router.put( + "", + responses={ + status.HTTP_404_NOT_FOUND: {"model": ResponseData}, + }, +) +async def create_llm( + user_sub: Annotated[str, Depends(get_user)], + llm_id: Optional[str] = Query(default=None, description="大模型ID", alias="llmId"), + req: UpdateLLMReq = Body(...), +) -> JSONResponse: + llm_id = await LLMManager.update_llm(user_sub, llm_id, req) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=ResponseData( + code=status.HTTP_200_OK, + message="success", + result=llm_id, + ).model_dump(exclude_none=True, by_alias=True), + ) + + +@router.delete( + "", + responses={ + status.HTTP_404_NOT_FOUND: {"model": ResponseData}, + }, +) +async def delete_llm( + user_sub: Annotated[str, Depends(get_user)], + llm_id: Optional[str] = Query(default=None, description="大模型ID", alias="llmId"), +) -> JSONResponse: + llm_id = await LLMManager.delete_llm(user_sub, llm_id) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=ResponseData( + code=status.HTTP_200_OK, + message="success", + result=llm_id, + ).model_dump(exclude_none=True, by_alias=True), + ) + + +@router.put( + "/conv", + responses={ + status.HTTP_404_NOT_FOUND: {"model": ResponseData}, + }, +) +async def update_conv_llm( + user_sub: Annotated[str, Depends(get_user)], + conversation_id: Optional[str] = Query(default=None, description="对话ID", alias="conversationId"), + llm_id: str = Query(default="empty", description="llm ID", alias="llmId"), +) -> JSONResponse: + """更新对话的知识库""" + llm_id = await LLMManager.update_conversation_llm(user_sub, conversation_id, llm_id) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=ResponseData( + code=status.HTTP_200_OK, + message="success", + result=llm_id, + ).model_dump(exclude_none=True, by_alias=True), + ) diff --git a/apps/routers/mcp_service.py b/apps/routers/mcp_service.py new file mode 100644 index 0000000000000000000000000000000000000000..d1a25093526ecf161c480c4db419d7c33a1f6f94 --- /dev/null +++ b/apps/routers/mcp_service.py @@ -0,0 +1,359 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. +"""FastAPI 语义接口中心相关路由""" + +import logging +from typing import Annotated + +from fastapi import APIRouter, Depends, Path, Query, status +from fastapi.responses import JSONResponse + +from apps.dependency.user import get_user, verify_user +from apps.entities.enum_var import MCPSearchType +from apps.entities.request_data import ActiveMCPServiceRequest, UpdateMCPServiceRequest +from apps.entities.response_data import ( + ActiveMCPServiceRsp, + BaseMCPServiceOperationMsg, + DeleteMCPServiceRsp, + GetMCPServiceDetailMsg, + GetMCPServiceDetailRsp, + GetMCPServiceListMsg, + GetMCPServiceListRsp, + ResponseData, + UpdateMCPServiceMsg, + UpdateMCPServiceRsp, +) +from apps.exceptions import InstancePermissionError, ServiceIDError +from apps.manager.mcp_service import MCPServiceManager +from apps.manager.user import UserManager + +logger = logging.getLogger(__name__) +router = APIRouter( + prefix="/api/mcp", + tags=["mcp-service"], + dependencies=[Depends(verify_user)], +) + + +@router.get("", response_model=GetMCPServiceListRsp | ResponseData) +async def get_mcpservice_list( + user_sub: Annotated[str, Depends(get_user)], + search_type: Annotated[ + MCPSearchType, Query(..., alias="searchType", description="搜索类型"), + ] = MCPSearchType.ALL, + keyword: Annotated[str | None, Query(..., alias="keyword", description="搜索关键字")] = None, + page: Annotated[int, Query(..., alias="page", ge=1, description="页码")] = 1, + page_size: Annotated[int, Query(..., alias="pageSize", ge=1, le=100, description="每页数量")] = 16, +) -> JSONResponse: + """获取服务列表""" + try: + service_cards, total_count = await MCPServiceManager.fetch_all_mcpservices( + search_type, + user_sub, + keyword, + page, + page_size, + ) + except Exception as e: + err = f"[MCPServiceCenter] 获取MCP服务列表失败: {e}" + logger.exception(err) + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=ResponseData( + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message="ERROR", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + + if total_count == -1: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content=ResponseData( + code=status.HTTP_400_BAD_REQUEST, + message="INVALID_PARAMETER", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + + return JSONResponse( + status_code=status.HTTP_200_OK, + content=GetMCPServiceListRsp( + code=status.HTTP_200_OK, + message="OK", + result=GetMCPServiceListMsg( + currentPage=page, + totalCount=total_count, + services=service_cards, + ), + ).model_dump(exclude_none=True, by_alias=True), + ) + + +@router.post("", response_model=UpdateMCPServiceRsp) +async def create_or_update_mcpservice( # noqa: PLR0911 + user_sub: Annotated[str, Depends(get_user)], + data: UpdateMCPServiceRequest, +) -> JSONResponse: + """新建或更新MCP服务""" + user_data = await UserManager.get_userinfo_by_user_sub(user_sub) + if not user_data or not user_data.is_admin: + return JSONResponse( + status_code=status.HTTP_403_FORBIDDEN, + content=ResponseData( + code=status.HTTP_403_FORBIDDEN, + message="非管理员无法注册更新mcp", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + try: + config = await MCPServiceManager.load_mcp_config( + description=data.description, + config_str=data.config, + mcp_type=data.mcp_type, + ) + except Exception as e: + err = f"[MCPServiceCenter] 更新MCP服务失败: {e}" + logger.exception(err) + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=ResponseData( + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message=f"更新MCP服务失败: {e!s}", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + if not data.service_id: + try: + service_id = await MCPServiceManager.create_mcpservice( + user_sub=user_sub, + name=data.name, + icon=data.icon, + description=data.description, + config=config, + config_str=data.config, + mcp_type=data.mcp_type, + ) + except Exception as e: + logger.exception("[MCPServiceCenter] 创建MCP服务失败") + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=ResponseData( + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message=f"OpenAPI解析错误: {e!s}", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + else: + try: + service_id = await MCPServiceManager.update_mcpservice( + user_sub=user_sub, + mcpservice_id=data.service_id, + name=data.name, + icon=data.icon, + description=data.description, + config=config, + config_str=data.config, + mcp_type=data.mcp_type, + ) + except ServiceIDError: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content=ResponseData( + code=status.HTTP_400_BAD_REQUEST, + message="MCPService ID错误", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + except InstancePermissionError: + return JSONResponse( + status_code=status.HTTP_403_FORBIDDEN, + content=ResponseData( + code=status.HTTP_403_FORBIDDEN, + message="未授权访问", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + except Exception as e: + logger.exception("[MCPService] 更新MCP服务失败") + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=ResponseData( + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message=f"更新MCP服务失败: {e!s}", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + msg = UpdateMCPServiceMsg(serviceId=service_id, name=data.name) + rsp = UpdateMCPServiceRsp(code=status.HTTP_200_OK, message="OK", result=msg) + return JSONResponse(status_code=status.HTTP_200_OK, content=rsp.model_dump(exclude_none=True, by_alias=True)) + + +@router.get("/{serviceId}", response_model=GetMCPServiceDetailRsp) +async def get_service_detail( + user_sub: Annotated[str, Depends(get_user)], + service_id: Annotated[str, Path(..., alias="serviceId", description="服务ID")], + *, + edit: Annotated[bool, Query(..., description="是否为编辑模式")] = False, +) -> JSONResponse: + """获取MCP服务详情""" + # 示例:返回指定MCP服务的详情 + if edit: + try: + data = await MCPServiceManager.get_mcpservice_data(user_sub, service_id) + is_active = await MCPServiceManager.is_active(user_sub, service_id) + except ServiceIDError: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content=ResponseData( + code=status.HTTP_400_BAD_REQUEST, + message="MCPService ID错误", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + except InstancePermissionError: + return JSONResponse( + status_code=status.HTTP_403_FORBIDDEN, + content=ResponseData( + code=status.HTTP_403_FORBIDDEN, + message="未授权访问", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + except Exception as e: + err = f"[MCPService] 获取MCP服务数据失败: {e}" + logger.exception(err) + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=ResponseData( + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message="ERROR", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + detail = GetMCPServiceDetailMsg( + serviceId=data.service_id, + icon=data.icon, + name=data.name, + description=data.description, + data=data.config_str, + tools=data.tools, + isActive=is_active, + mcpType=data.mcp_type, + ) + else: + try: + data = await MCPServiceManager.get_service_details(service_id) + is_active = await MCPServiceManager.is_active(user_sub, service_id) + except ServiceIDError: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content=ResponseData( + code=status.HTTP_400_BAD_REQUEST, + message="MCPService ID错误", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + except Exception as e: + err = f"[MCPService] 获取MCP服务API失败: {e}" + logger.exception(err) + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=ResponseData( + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message="ERROR", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + detail = GetMCPServiceDetailMsg( + serviceId=service_id, + icon=data.icon, + name=data.name, + description=data.description, + tools=data.tools, + data=data.config_str, + isActive=is_active, + mcpType=data.mcp_type, + ) + rsp = GetMCPServiceDetailRsp(code=status.HTTP_200_OK, message="OK", result=detail) + return JSONResponse(status_code=status.HTTP_200_OK, content=rsp.model_dump(exclude_none=True, by_alias=True)) + + +@router.delete("/{serviceId}", response_model=DeleteMCPServiceRsp) +async def delete_service( + user_sub: Annotated[str, Depends(get_user)], + service_id: Annotated[str, Path(..., alias="serviceId", description="服务ID")], +) -> JSONResponse: + """删除服务""" + try: + await MCPServiceManager.delete_mcpservice(user_sub, service_id) + except ServiceIDError: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content=ResponseData( + code=status.HTTP_400_BAD_REQUEST, + message="MCPService ID错误", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + except InstancePermissionError: + return JSONResponse( + status_code=status.HTTP_403_FORBIDDEN, + content=ResponseData( + code=status.HTTP_403_FORBIDDEN, + message="未授权访问", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + except Exception as e: + err = f"[MCPServiceManager] 删除MCP服务失败: {e}" + logger.exception(err) + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=ResponseData( + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message="ERROR", + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + msg = BaseMCPServiceOperationMsg(serviceId=service_id) + rsp = DeleteMCPServiceRsp(code=status.HTTP_200_OK, message="OK", result=msg) + return JSONResponse(status_code=status.HTTP_200_OK, content=rsp.model_dump(exclude_none=True, by_alias=True)) + + +@router.post("/{serviceId}", response_model=ActiveMCPServiceRsp) +async def active_or_deactivate_mcp_service( + user_sub: Annotated[str, Depends(get_user)], + service_id: Annotated[str, Path(..., alias="serviceId", description="服务ID")], + data: ActiveMCPServiceRequest, +) -> JSONResponse: + """激活/取消激活mcp""" + try: + if data.active: + await MCPServiceManager.active_mcpservice(user_sub, service_id) + else: + await MCPServiceManager.deactive_mcpservice(user_sub, service_id) + except FileExistsError as e: + err = f"[MCPService] 激活mcp服务失败: {e}" if data.active else f"[MCPService] 取消激活mcp服务失败: {e}" + logger.exception(err) + return JSONResponse( + status_code=status.HTTP_403_FORBIDDEN, + content=ResponseData( + code=status.HTTP_403_FORBIDDEN, + message=err, + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + except Exception as e: + err = f"[MCPService] 激活mcp服务失败: {e}" if data.active else f"[MCPService] 取消激活mcp服务失败: {e}" + logger.exception(err) + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=ResponseData( + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message=err, + result={}, + ).model_dump(exclude_none=True, by_alias=True), + ) + msg = BaseMCPServiceOperationMsg(serviceId=service_id) + rsp = ActiveMCPServiceRsp(code=status.HTTP_200_OK, message="OK", result=msg) + return JSONResponse(status_code=status.HTTP_200_OK, content=rsp.model_dump(exclude_none=True, by_alias=True)) diff --git a/apps/routers/mock.py b/apps/routers/mock.py deleted file mode 100644 index aa36b3fe264e56f450a78936fc3cf1426760bd11..0000000000000000000000000000000000000000 --- a/apps/routers/mock.py +++ /dev/null @@ -1,543 +0,0 @@ -""" -问答大模型调用 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" - -import copy -import json -import logging -import random -import time -import traceback -from collections.abc import AsyncGenerator -from typing import Annotated, Any, Optional - -import aiohttp -from fastapi import APIRouter, Depends, status -from fastapi.responses import StreamingResponse -from openai import AsyncOpenAI -from pydantic import BaseModel, Field - -from apps.common.config import config -from apps.constants import REASONING_BEGIN_TOKEN, REASONING_END_TOKEN -from apps.dependency import ( - get_session, - get_user, - verify_csrf_token, - verify_user, -) -from apps.entities.pool import AppPool -from apps.entities.request_data import RequestData -from apps.entities.scheduler import CallError -from apps.manager.appcenter import AppCenterManager -from apps.manager.flow import FlowManager -from apps.scheduler.pool.loader.flow import FlowLoader - -logger = logging.getLogger(__name__) - - -class ReasoningLLM: - """调用用于问答的大模型""" - - def __init__(self) -> None: - """判断配置文件里用了哪种大模型;初始化大模型客户端""" - if not config["LLM_KEY"]: - self._client = AsyncOpenAI( - base_url=config["LLM_URL"], - ) - return - - self._client = AsyncOpenAI( - api_key=config["LLM_KEY"], - base_url=config["LLM_URL"], - ) - - def _validate_messages(self, messages: list[dict[str, str]]) -> list[dict[str, str]]: - """验证消息格式是否正确""" - if messages[0]["role"] != "system": - # 添加默认系统消息 - messages.insert(0, {"role": "system", "content": "You are a helpful assistant."}) - - if messages[-1]["role"] != "user": - err = f"消息格式错误,最后一个消息必须是用户消息:{messages[-1]}" - raise ValueError(err) - - return messages - - async def call( - self, - messages: list[dict[str, str]], # noqa: C901 - max_tokens: Optional[int] = None, - temperature: Optional[float] = None, - *, - streaming: bool = True, - result_only: bool = True, - ) -> AsyncGenerator[str, None]: - """调用大模型,分为流式和非流式两种""" - # input_tokens = self._calculate_token_length(messages) - try: - msg_list = self._validate_messages(messages) - except ValueError as e: - err = f"消息格式错误:{e}" - raise ValueError(err) from e - - if max_tokens is None: - max_tokens = config["LLM_MAX_TOKENS"] - if temperature is None: - temperature = config["LLM_TEMPERATURE"] - - stream = await self._client.chat.completions.create( - model=config["LLM_MODEL"], - messages=msg_list, # type: ignore[] - max_tokens=max_tokens, - temperature=temperature, - stream=True, - ) # type: ignore[] - - reasoning_content = "" - result = "" - - is_first_chunk = True - is_reasoning = False - reasoning_type = "" - - async for chunk in stream: - # 当前Chunk内的信息 - reason = "" - text = "" - - if is_first_chunk: - if hasattr(chunk.choices[0].delta, "reasoning_content"): - reason = "" + chunk.choices[0].delta.reasoning_content or "" - reasoning_type = "args" - is_reasoning = True - else: - for token in REASONING_BEGIN_TOKEN: - if token == (chunk.choices[0].delta.content or ""): - reason = "" - reasoning_type = "tokens" - is_reasoning = True - break - - # 当前已经不是第一个Chunk了 - is_first_chunk = False - - # 当前是正常问答 - if not is_reasoning: - text = chunk.choices[0].delta.content or "" - - # 当前处于推理状态 - if not is_first_chunk and is_reasoning: - # 如果推理内容用特殊参数传递 - if reasoning_type == "args": - # 还在推理 - if hasattr(chunk.choices[0].delta, "reasoning_content"): - reason = chunk.choices[0].delta.reasoning_content or "" - # 推理结束 - else: - is_reasoning = False - reason = "" - - # 如果推理内容用特殊token传递 - elif reasoning_type == "tokens": - # 结束推理 - for token in REASONING_END_TOKEN: - if token == (chunk.choices[0].delta.content or ""): - is_reasoning = False - reason = "" - text = "" - break - # 还在推理 - if is_reasoning: - reason = chunk.choices[0].delta.content or "" - - # 推送消息 - if streaming: - # 如果需要推送推理内容 - if reason and not result_only: - yield reason - - # 推送text - yield text - - # 整理结果 - reasoning_content += reason - result += text - - if not streaming: - if not result_only: - yield reasoning_content - yield result - - # logger.info(f"推理LLM:{reasoning_content}\n\n{result}") - - # output_tokens = self._calculate_token_length([{"role": "assistant", "content": result}], pure_text=True) - # task = ray.get_actor("task") - # await task.update_token_summary.remote(input_tokens, output_tokens) - - -class _RAGParams(BaseModel): - """RAG工具的参数""" - - knowledge_base: str = Field(description="知识库的id", alias="kb_sn") - top_k: int = Field(description="返回的答案数量(经过整合以及上下文关联)", default=5) - methods: Optional[list[str]] = Field(description="rag检索方法") - - -class _RAGOutputList(BaseModel): - """RAG工具的输出""" - - corpus: list[str] = Field(description="知识库的语料列表") - - -class _RAGOutput(BaseModel): - """RAG工具的输出""" - - output: _RAGOutputList = Field(description="RAG工具的输出") - - -router = APIRouter( - prefix="/api", - tags=["mock"], -) - - -async def mock_data( - post_body: RequestData, - user_sub: str, - session_id: str, -): - try: - if post_body.app and post_body.app.app_id and not post_body.app.flow_id: - app = await AppCenterManager.fetch_app_data_by_id(post_body.app.app_id) - if type(app) is AppPool and type(app.flows) is list: - post_body.app.flow_id = app.flows[0].id - # await Activity.set_active(user_sub) - - # # 生成group_id - # group_id = str(uuid.uuid4()) if not post_body.group_id else post_body.group_id - - # # 创建或还原Task - # task_pool = ray.get_actor("task") - # task = await task_pool.get_task.remote(session_id=session_id, post_body=post_body) - # task_id = task.record.task_id - - # task.record.group_id = group_id - # post_body.group_id = group_id - # await task_pool.set_task.remote(task_id, task) - - question = post_body.question - conversationId = post_body.conversation_id - appId = post_body.app.app_id if post_body.app else None - flowId = post_body.app.flow_id if post_body.app else None - - start_message = [ - { # 任务流开始 - "event": "flow.start", - "id": "0f9d3e6b-7845-44ab-b247-35c522d38f13", - "groupId": "8b9d3e6b-a892-4602-b247-35c522d38f13", - "conversationId": conversationId, - "taskId": "eb717bc7-3435-4172-82d1-6b69e62f3fd6", - "flow": { - "appId": appId, - "flowId": flowId, - "stepId": "start", - "stepStatus": "pending", - }, - "content": {}, - "metadata": {"inputTokens": 200, "outputTokens": 50, "timeCost": 0.5}, - }, - { - "event": "init", - "id": "0f9d3e6b-7845-44ab-b247-35c522d38f13", - "groupId": "8b9d3e6b-a892-4602-b247-35c522d38f13", - "conversationId": conversationId, - "taskId": "eb717bc7-3435-4172-82d1-6b69e62f3fd6", - "content": { - "feature": { - "enable_feedback": True, - "enable_regenerate": True, - "max_tokens": 2048, - "context_num": 2, - }, - }, - "metadata": {"input_tokens": 200, "output_tokens": 50, "timeCost": 0.5}, - }, - ] - sample_input = { # 开始节点 - "event": "step.input", - "id": "0f9d3e6b-7845-44ab-b247-35c522d38f13", - "groupId": "8b9d3e6b-a892-4602-b247-35c522d38f13", - "conversationId": conversationId, - "taskId": "eb717bc7-3435-4172-82d1-6b69e62f3fd6", - "flow": { - "appId": appId, - "flowId": flowId, - "stepId": "start", - "stepName": "开始", - "stepStatus": "running", - }, - "content": {"text": "测试输入"}, - "metadata": { - "inputTokens": 200, - "outputTokens": 50, - "timeCost": 0.5, - }, - } - sample_output = { # 开始节点 - "event": "step.output", - "id": "0f9d3e6b-7845-44ab-b247-35c522d38f13", - "groupId": "8b9d3e6b-a892-4602-b247-35c522d38f13", - "conversationId": conversationId, - "taskId": "eb717bc7-3435-4172-82d1-6b69e62f3fd6", - "flow": { - "appId": appId, - "flowId": flowId, - "stepId": "start", - "stepName": "开始", - "stepStatus": "success", - }, - "content": {"text": "测试输出"}, - "metadata": { - "inputTokens": 200, - "outputTokens": 50, - "timeCost": 0.5, - }, - } - messages = [] - for message in start_message: - messages.append(message) - for message in messages: - if message["event"] == "step.output": - t = message["metadata"]["timeCost"] - time.sleep(t) - elif message["event"] == "text.add": - t = random.uniform(0.15, 0.2) - time.sleep(t) - yield "data: " + json.dumps(message, ensure_ascii=False) + "\n\n" - mid_message = [] - - flow = None - if appId and flowId: - flow = await FlowLoader().load(appId, flowId) - - now_flow_item = "start" - start_time = time.time() - last_item = "" - mapp = {} - params = {} - params["question"] = question - - if flow is not None: - logger.info(json.dumps(flow.model_dump_json(exclude_none=True, by_alias=True), ensure_ascii=False)) - for step_id, step in flow.steps.items(): - mapp[step_id] = step.name, step.params - while now_flow_item != "end": - if last_item == now_flow_item: - break - last_item = now_flow_item - for edge in flow.edges: - if edge.edge_from.split(".")[0] == now_flow_item: - sample_input["flow"]["stepId"] = now_flow_item - sample_input["flow"]["stepName"], sample_input["content"] = mapp[now_flow_item] - sample_input["content"] = sample_input["content"] - if "content" in sample_input and type(sample_input["content"]) == dict: - for key, value in sample_input["content"].items(): - if key in params: - sample_input["content"][key] = params[key] - else: - params[key] = value - time.sleep(sample_input["metadata"]["timeCost"]) - yield "data: " + json.dumps(sample_input, ensure_ascii=False) + "\n\n" - sample_output["metadata"]["timeCost"] = random.uniform(1.5, 3.5) - sample_output["flow"]["stepId"] = now_flow_item - sample_output["flow"]["stepName"], sample_output["content"] = mapp[now_flow_item] - sample_output["content"] = sample_output["content"] - if sample_output["flow"]["stepName"] == "知识库": - sample_output["content"] = await call_rag(params) - if sample_output["flow"]["stepName"] == "大模型": - # sample_output["content"] = await call_llm(params) - sample_output["content"] = {"message": ""} - if "content" in sample_output and isinstance(sample_output["content"], dict): - for key, value in sample_output["content"].items(): - params[key] = copy.deepcopy(value) - time.sleep(sample_output["metadata"]["timeCost"]) - if sample_output["flow"]["stepName"] == "知识库": - for i in range(len(sample_output["content"]["chunk_list"])): - sample_output["content"]["chunk_list"][i] = sample_output["content"]["chunk_list"][i].replace("\n", "")[:100] + "..." - yield "data: " + json.dumps(sample_output, ensure_ascii=False) + "\n\n" - now_flow_item = edge.edge_to - - if now_flow_item == "end": - sample_input["content"]={} - sample_input["flow"]["stepId"] = now_flow_item - sample_input["flow"]["stepName"] = "结束" - yield "data: " + json.dumps(sample_input, ensure_ascii=False) + "\n\n" - sample_output["content"]={} - sample_output["flow"]["stepId"] = now_flow_item - sample_output["flow"]["stepName"] = "结束" - sample_output["metadata"]["timeCost"] = random.uniform(0.5, 1.5) - yield "data: " + json.dumps(sample_output, ensure_ascii=False) + "\n\n" - if appId and flowId: - await FlowManager.update_flow_debug_by_app_and_flow_id(appId, flowId, debug=True) - end_message = [ - { # flow结束 - "event": "flow.stop", - "id": "0f9d3e6b-7845-44ab-b247-35c522d38f13", - "groupId": "8b9d3e6b-a892-4602-b247-35c522d38f13", - "conversationId": conversationId, - "taskId": "eb717bc7-3435-4172-82d1-6b69e62f3fd6", - "flow": {"stepStatus": "success"}, - "content": {}, - "metadata": {"inputTokens": 0, "outputTokens": 0, "timeCost": random.uniform(0.5, 1.5)}, - }, - ] - messages = [] - for message in end_message: - messages.append(message) - for message in messages: - if message["event"] == "step.output": - t = message["metadata"]["timeCost"] - time.sleep(t) - elif message["event"] == "text.add": - t = random.uniform(0.15, 0.2) - time.sleep(t) - yield "data: " + json.dumps(message, ensure_ascii=False) + "\n\n" - - chat_message = call_llm_stream(params) - messages = [] - temp_messages = [] - async for message in chat_message: - yield "data: " + message + "\n\n" - yield json.dumps( - {"event": "text.end", "content": "|", "input_tokens": len(_encoder.encode(question)), "output_tokens": 290}, - ) - - # # 获取最终答案 - # task = await task_pool.get_task.remote(task_id) - # answer_text = task.record.content.answer - # if not answer_text: - # logger.error(msg="Answer is empty") - # yield "data: [ERROR]\n\n" - # await Activity.remove_active(user_sub) - # return - - # # 创建新Record,存入数据库 - # await save_data(task_id, user_sub, post_body, result.used_docs) - except Exception: - logger.error(f"Run mock_data failed:{traceback.format_exc()}") - yield "data: [ERROR]\n\n" - - -async def call_rag(params: dict = {}): - url = config["RAG_HOST"].rstrip("/") + "/chunk/get" - headers = { - "Content-Type": "application/json", - } - params_dict = { - "kb_sn": params["kb_sn"], - "top_k": params["top_k"], - "content": params["question"], - "retrieval_mode": params["retrieval_mode"], - } - # 发送 POST 请求 - async with aiohttp.ClientSession() as session: - async with session.post(url, headers=headers, json=params_dict) as response: - # 检查响应状态码 - if response.status == status.HTTP_200_OK: - result = await response.json() - chunk_list = result["data"] - return {"chunk_list": chunk_list} - text = await response.text() - raise CallError( - message=f"rag调用失败:{text}", - data={ - "question": params["question"], - "status": response.status, - "text": text, - }, - ) - - -async def call_llm(params: dict = {}): - # 构建请求 URL 和 headers - url = config["LLM_URL"] + "/chat/completions" - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {config['LLM_KEY']}", # 添加鉴权 Token - } - prompt = params.get("prompt", "") - chunk_list = params.get("chunk_list", "") - logger.info("LLM 接收", chunk_list) - user_call = "请回答问题" + params.get("quetion", "") + "下面是获得的信息:" - # 构建请求体 - payload = { - "model": params.get("model", config["LLM_MODEL"]), # 默认模型 - "messages": params.get( - "messages", - [{"role": "system", "content": prompt}, {"role": "user", "content": user_call + str(chunk_list) + "\n\n如果没有任何信息则回答知识库没有查询到相关信息"}], - ), # 消息列表 - "stream": params.get("stream", False), # 是否流式返回 - "n": params.get("n", 1), # 返回的候选答案数量 - "max_tokens": params.get("max_tokens", 4096), # 最大 token 数量 - } - - # 发送 POST 请求 - async with aiohttp.ClientSession() as session: - async with session.post(url, headers=headers, json=payload) as response: - # 检查响应状态码 - if response.status == status.HTTP_200_OK: - result = await response.json() - result = result["choices"][0]["message"]["content"] - logger.info(result) - result = result.replace("\n\n", "") - return {"content": result} - text = await response.text() - logger.error(f"LLM 调用失败:{text}") - return None - - -class _LLMOutput(BaseModel): - """定义LLM工具调用的输出""" - - message: str = Field(description="大模型输出的文字信息") - - -async def call_llm_stream(params: dict[str, Any] = {}): - prompt = "你是EulerCopilot,我们向你问了一个问题,需要你完成这个问题,我们会给出对应的信息" - question = params.get("question", "") - content = f"问题:{question}\n" + "信息:" + str(params.get("chunk_list", "")) - message = params.get("messages", [{"role": "system", "content": prompt}, {"role": "user", "content": content}]) - sum = 0 - async for chunk in ReasoningLLM().call(messages=message): - sum = sum + len(_encoder.encode(chunk)) - chunk = chunk.replace("\n\n", "") - output = { - "event": "text.add", - "content": {"text": chunk}, - "input_tokens": len(_encoder.encode(question)), - "output_tokens": sum, - } - yield json.dumps(output, ensure_ascii=False) - - -@router.post("/mock/chat", dependencies=[Depends(verify_csrf_token), Depends(verify_user)]) -async def chat( - post_body: RequestData, - user_sub: Annotated[str, Depends(get_user)], - session_id: Annotated[str, Depends(get_session)], -) -> StreamingResponse: - """LLM流式对话接口""" - res = mock_data( - post_body=post_body, - user_sub=user_sub, - session_id=session_id, - ) - return StreamingResponse( - content=res, - media_type="text/event-stream", - headers={ - "X-Accel-Buffering": "no", - }, - ) diff --git a/apps/routers/record.py b/apps/routers/record.py index ea5e7e8c799f189eedafdbad499e9a2223e9159e..0bb999dc3005b12c5868678de275608361fa073e 100644 --- a/apps/routers/record.py +++ b/apps/routers/record.py @@ -1,8 +1,5 @@ -""" -FastAPI Record相关接口 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""FastAPI Record相关接口""" import json from typing import Annotated diff --git a/apps/routers/service.py b/apps/routers/service.py index 97388aa7fc29adbdc4707aab8074fab485a54e0e..8c451630e2c60b96b5428fbf191b13867f561e34 100644 --- a/apps/routers/service.py +++ b/apps/routers/service.py @@ -1,8 +1,5 @@ -""" -FastAPI 语义接口中心相关路由 - -Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved. +"""FastAPI 语义接口中心相关路由""" import logging from typing import Annotated @@ -10,7 +7,6 @@ from typing import Annotated from fastapi import APIRouter, Body, Depends, Path, Query, status from fastapi.responses import JSONResponse -from apps.dependency.csrf import verify_csrf_token from apps.dependency.user import get_user, verify_user from apps.entities.enum_var import SearchType from apps.entities.request_data import ModFavServiceRequest, UpdateServiceRequest @@ -119,7 +115,7 @@ async def get_service_list( # noqa: PLR0913 ) -@router.post("", response_model=UpdateServiceRsp, dependencies=[Depends(verify_csrf_token)]) +@router.post("", response_model=UpdateServiceRsp) async def update_service( user_sub: Annotated[str, Depends(get_user)], data: Annotated[UpdateServiceRequest, Body(..., description="上传 YAML 文本对应数据对象")], @@ -254,7 +250,7 @@ async def get_service_detail( return JSONResponse(status_code=status.HTTP_200_OK, content=rsp.model_dump(exclude_none=True, by_alias=True)) -@router.delete("/{serviceId}", response_model=DeleteServiceRsp, dependencies=[Depends(verify_csrf_token)]) +@router.delete("/{serviceId}", response_model=DeleteServiceRsp) async def delete_service( user_sub: Annotated[str, Depends(get_user)], service_id: Annotated[str, Path(..., alias="serviceId", description="服务ID")], @@ -295,7 +291,7 @@ async def delete_service( return JSONResponse(status_code=status.HTTP_200_OK, content=rsp.model_dump(exclude_none=True, by_alias=True)) -@router.put("/{serviceId}", response_model=ModFavServiceRsp, dependencies=[Depends(verify_csrf_token)]) +@router.put("/{serviceId}", response_model=ModFavServiceRsp) async def modify_favorite_service( user_sub: Annotated[str, Depends(get_user)], service_id: Annotated[str, Path(..., alias="serviceId", description="服务ID")], diff --git a/apps/routers/user.py b/apps/routers/user.py index 8628377e0e6ad8b43e13b14fc0dd361024e8cc0a..87fcbb7e4d5a14e0bf5aa0c256061b2958a18375 100644 --- a/apps/routers/user.py +++ b/apps/routers/user.py @@ -1,19 +1,12 @@ -""" -用户相关接口 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""用户相关接口""" from typing import Annotated from fastapi import APIRouter, Depends, status from fastapi.responses import JSONResponse -from apps.dependency import ( - get_user, - verify_csrf_token, - verify_user, -) +from apps.dependency import get_user from apps.entities.response_data import UserGetMsp, UserGetRsp from apps.entities.user import UserInfo from apps.manager.user import UserManager @@ -23,7 +16,8 @@ router = APIRouter( tags=["user"], ) -@router.get("", dependencies=[Depends(verify_csrf_token), Depends(verify_user)]) + +@router.get("") async def chat( user_sub: Annotated[str, Depends(get_user)], ) -> JSONResponse: @@ -35,13 +29,16 @@ async def chat( if user == user_sub: continue info = UserInfo( - userName=user, - userSub=user, - ) + userName=user, + userSub=user, + ) user_info_list.append(info) - return JSONResponse(status_code=status.HTTP_200_OK, content=UserGetRsp( - code=status.HTTP_200_OK, - message="用户数据详细信息获取成功", - result=UserGetMsp(userInfoList=user_info_list), - ).model_dump(exclude_none=True, by_alias=True)) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=UserGetRsp( + code=status.HTTP_200_OK, + message="用户数据详细信息获取成功", + result=UserGetMsp(userInfoList=user_info_list), + ).model_dump(exclude_none=True, by_alias=True), + ) diff --git a/apps/scheduler/__init__.py b/apps/scheduler/__init__.py index c004fec74e47d81fbdf80717f27aabe8df1fe475..90047a73434a526650c36dfd6e8905f5cc6ece43 100644 --- a/apps/scheduler/__init__.py +++ b/apps/scheduler/__init__.py @@ -1,5 +1,2 @@ -""" -Framework Scheduler模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Framework Scheduler模块""" diff --git a/apps/scheduler/call/__init__.py b/apps/scheduler/call/__init__.py index 5c88cd2d98f36de96cbcd030133cb13cf015ebf9..2ee8b862885b0d88e519bf00a1678a49863df2bd 100644 --- a/apps/scheduler/call/__init__.py +++ b/apps/scheduler/call/__init__.py @@ -1,12 +1,10 @@ -""" -Agent工具部分 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Agent工具部分""" from apps.scheduler.call.api.api import API from apps.scheduler.call.graph.graph import Graph from apps.scheduler.call.llm.llm import LLM +from apps.scheduler.call.mcp.mcp import MCP from apps.scheduler.call.rag.rag import RAG from apps.scheduler.call.sql.sql import SQL from apps.scheduler.call.suggest.suggest import Suggestion @@ -15,6 +13,7 @@ from apps.scheduler.call.suggest.suggest import Suggestion __all__ = [ "API", "LLM", + "MCP", "RAG", "SQL", "Graph", diff --git a/apps/scheduler/call/api/__init__.py b/apps/scheduler/call/api/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2b9798494d845d45478fbe587dbc003aa7a35d16 --- /dev/null +++ b/apps/scheduler/call/api/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""API工具""" diff --git a/apps/scheduler/call/api/api.py b/apps/scheduler/call/api/api.py index 550f1edf185beca6049cf9e6a5b213344266235f..283be0647b3a33d69bac9cf30f46a377e85a0c5a 100644 --- a/apps/scheduler/call/api/api.py +++ b/apps/scheduler/call/api/api.py @@ -1,16 +1,13 @@ -""" -工具:API调用 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""API调用工具""" import json import logging from collections.abc import AsyncGenerator +from functools import partial from typing import Any -import aiohttp -from aiohttp.client import _RequestContextManager +import httpx from fastapi import status from pydantic import Field from pydantic.json_schema import SkipJsonSchema @@ -99,8 +96,7 @@ class API(CoreCall, input_model=APIInput, output_model=APIOutput): async def _exec(self, input_data: dict[str, Any]) -> AsyncGenerator[CallOutputChunk, None]: """调用API,然后返回LLM解析后的数据""" - self._session = aiohttp.ClientSession() - self._timeout = aiohttp.ClientTimeout(total=self.timeout) + self._client = httpx.AsyncClient(timeout=self.timeout) input_obj = APIInput.model_validate(input_data) try: result = await self._call_api(input_obj) @@ -109,10 +105,10 @@ class API(CoreCall, input_model=APIInput, output_model=APIOutput): content=result.model_dump(exclude_none=True, by_alias=True), ) finally: - await self._session.close() + await self._client.aclose() - async def _make_api_call(self, data: APIInput, files: aiohttp.FormData) -> _RequestContextManager: - """组装API请求Session""" + async def _make_api_call(self, data: APIInput, files: dict[str, tuple[str, bytes, str]]) -> httpx.Response: + """组装API请求""" # 获取必要参数 if self._auth: req_header, req_cookie, req_params = await self._apply_auth() @@ -121,16 +117,45 @@ class API(CoreCall, input_model=APIInput, output_model=APIOutput): req_cookie = {} req_params = {} + # 创建请求工厂 + request_factory = partial( + self._client.request, + method=self.method, + url=self.url, + cookies=req_cookie, + ) + + # 根据HTTP方法创建请求 if self.method in [HTTPMethod.GET.value, HTTPMethod.DELETE.value]: - return await self._handle_query_request(data, req_header, req_cookie, req_params) + # GET/DELETE 请求处理 + req_params.update(data.query) + return await request_factory(params=req_params) if self.method in [HTTPMethod.POST.value, HTTPMethod.PUT.value, HTTPMethod.PATCH.value]: - return await self._handle_body_request(data, files, req_header, req_cookie) + # POST/PUT/PATCH 请求处理 + if not self.content_type: + raise CallError(message="API接口的Content-Type未指定", data={}) - raise CallError( - message="API接口的HTTP Method不支持", - data={}, - ) + # 根据Content-Type设置请求参数 + req_body = data.body + req_header.update({"Content-Type": self.content_type}) + + # 根据Content-Type决定如何发送请求体 + content_type_handlers = { + ContentType.JSON.value: lambda body, _: {"json": body}, + ContentType.FORM_URLENCODED.value: lambda body, _: {"data": body}, + ContentType.MULTIPART_FORM_DATA.value: lambda body, files: {"data": body, "files": files}, + } + + handler = content_type_handlers.get(self.content_type) + if not handler: + raise CallError(message="API接口的Content-Type不支持", data={}) + + request_kwargs = {} + request_kwargs.update(handler(req_body, files)) + return await request_factory(**request_kwargs) + + raise CallError(message="API接口的HTTP Method不支持", data={}) async def _apply_auth(self) -> tuple[dict[str, str], dict[str, str], dict[str, str]]: """应用认证信息到请求参数中""" @@ -160,93 +185,24 @@ class API(CoreCall, input_model=APIInput, output_model=APIOutput): return req_header, req_cookie, req_params - async def _handle_query_request( - self, data: APIInput, req_header: dict[str, str], req_cookie: dict[str, str], req_params: dict[str, str], - ) -> _RequestContextManager: - """处理GET和DELETE请求""" - req_params.update(data.query) - return self._session.request( - self.method, - self.url, - params=req_params, - headers=req_header, - cookies=req_cookie, - timeout=self._timeout, - ) - - async def _handle_body_request( - self, data: APIInput, files: aiohttp.FormData, req_header: dict[str, str], req_cookie: dict[str, str], - ) -> _RequestContextManager: - """处理POST、PUT和PATCH请求""" - if not self.content_type: - raise CallError( - message="API接口的Content-Type未指定", - data={}, - ) - - req_body = data.body - - if self.content_type in [ContentType.FORM_URLENCODED.value, ContentType.MULTIPART_FORM_DATA.value]: - return await self._handle_form_request(req_body, files, req_header, req_cookie) - - if self.content_type == ContentType.JSON.value: - return await self._handle_json_request(req_body, req_header, req_cookie) - - raise CallError( - message="API接口的Content-Type不支持", - data={}, - ) - - async def _handle_form_request( - self, - req_body: dict[str, Any], - form_data: aiohttp.FormData, - req_header: dict[str, str], - req_cookie: dict[str, str], - ) -> _RequestContextManager: - """处理表单类型的请求""" - for key, val in req_body.items(): - form_data.add_field(key, val) - - return self._session.request( - self.method, - self.url, - data=form_data, - headers=req_header, - cookies=req_cookie, - timeout=self._timeout, - ) - - async def _handle_json_request( - self, req_body: dict[str, Any], req_header: dict[str, str], req_cookie: dict[str, str], - ) -> _RequestContextManager: - """处理JSON类型的请求""" - return self._session.request( - self.method, - self.url, - json=req_body, - headers=req_header, - cookies=req_cookie, - timeout=self._timeout, - ) - async def _call_api(self, final_data: APIInput) -> APIOutput: """实际调用API,并处理返回值""" # 获取必要参数 logger.info("[API] 调用接口 %s,请求数据为 %s", self.url, final_data) - session_context = await self._make_api_call(final_data, aiohttp.FormData()) - async with session_context as response: - if response.status not in SUCCESS_HTTP_CODES: - text = f"API发生错误:API返回状态码{response.status}, 原因为{response.reason}。" - logger.error(text) - raise CallError( - message=text, - data={"api_response_data": await response.text()}, - ) + files = {} # httpx需要使用字典格式的files参数 + response = await self._make_api_call(final_data, files) + + if response.status_code not in SUCCESS_HTTP_CODES: + text = f"API发生错误:API返回状态码{response.status_code}, 原因为{response.reason_phrase}。" + logger.error(text) + raise CallError( + message=text, + data={"api_response_data": response.text}, + ) - response_status = response.status - response_data = await response.text() + response_status = response.status_code + response_data = response.text logger.info("[API] 调用接口 %s,结果为 %s", self.url, response_data) diff --git a/apps/scheduler/call/api/schema.py b/apps/scheduler/call/api/schema.py index 91949ec80f3931b35b33a261985ff38ad1a5e774..055008a889676e4e2fbd5057912ba78421e52aa2 100644 --- a/apps/scheduler/call/api/schema.py +++ b/apps/scheduler/call/api/schema.py @@ -1,12 +1,10 @@ -""" -API调用工具的输入和输出 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""API调用工具的输入和输出""" from typing import Any from pydantic import Field +from pydantic.json_schema import SkipJsonSchema from apps.scheduler.call.core import DataBase @@ -14,11 +12,11 @@ from apps.scheduler.call.core import DataBase class APIInput(DataBase): """API调用工具的输入""" - url: str = Field(description="API调用工具的URL") - method: str = Field(description="API调用工具的HTTP方法") + url: SkipJsonSchema[str] = Field(description="API调用工具的URL") + method: SkipJsonSchema[str] = Field(description="API调用工具的HTTP方法") - query: dict[str, Any] = Field(description="API调用工具的请求参数") - body: dict[str, Any] = Field(description="API调用工具的请求体") + query: dict[str, Any] = Field(description="API调用工具的请求参数", default={}) + body: dict[str, Any] = Field(description="API调用工具的请求体", default={}) class APIOutput(DataBase): diff --git a/apps/scheduler/call/choice/__init__.py b/apps/scheduler/call/choice/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..73356b0e10444185d977614398dcdefb8a5aa32c --- /dev/null +++ b/apps/scheduler/call/choice/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""选择工具""" diff --git a/apps/scheduler/call/choice/choice.py b/apps/scheduler/call/choice/choice.py index f6a66e8531f3d61ab3e0b070871998aa03c46141..a5edf21afb2eeb308dd909a40696500751c9a086 100644 --- a/apps/scheduler/call/choice/choice.py +++ b/apps/scheduler/call/choice/choice.py @@ -1,8 +1,6 @@ -""" -工具:使用大模型或使用程序做出判断 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""使用大模型或使用程序做出判断""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" from enum import Enum from apps.scheduler.call.choice.schema import ChoiceInput, ChoiceOutput diff --git a/apps/scheduler/call/choice/schema.py b/apps/scheduler/call/choice/schema.py index ae20986895ff8dd1a7170737db53d8888ae2549a..60b62d09fd66adbf32295f44ec86398a537f38d5 100644 --- a/apps/scheduler/call/choice/schema.py +++ b/apps/scheduler/call/choice/schema.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """Choice Call的输入和输出""" from apps.scheduler.call.core import DataBase diff --git a/apps/scheduler/call/cmd/__init__.py b/apps/scheduler/call/cmd/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ae0a99e47f7d13c02f056b035ebcb3e174750614 100644 --- a/apps/scheduler/call/cmd/__init__.py +++ b/apps/scheduler/call/cmd/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""命令生成工具""" diff --git a/apps/scheduler/call/cmd/assembler.py b/apps/scheduler/call/cmd/assembler.py index 26294f6df9a254c9dc63349209d9e3b71063ea01..54c1d2ee6f7735eb4839d03f9c778288d3e7aee8 100644 --- a/apps/scheduler/call/cmd/assembler.py +++ b/apps/scheduler/call/cmd/assembler.py @@ -1,8 +1,5 @@ -""" -BTDL:命令行组装器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""命令行组装器""" import string from typing import Any, Literal, Optional diff --git a/apps/scheduler/call/cmd/cmd.py b/apps/scheduler/call/cmd/cmd.py index a7d1951f8399cb67ad2722d4143a42be86f1ca06..7f9d9f8c4f47e1c0cf3e4bcceea1b619014598fa 100644 --- a/apps/scheduler/call/cmd/cmd.py +++ b/apps/scheduler/call/cmd/cmd.py @@ -1,8 +1,5 @@ -""" -工具:自然语言生成命令 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""自然语言生成命令""" from typing import Any diff --git a/apps/scheduler/call/cmd/solver.py b/apps/scheduler/call/cmd/solver.py index 323ceb9234952dbfba940b639dfb1827d324c23f..7eeb0b6d8387dd4cb58dee134fc9e9b68b44f76b 100644 --- a/apps/scheduler/call/cmd/solver.py +++ b/apps/scheduler/call/cmd/solver.py @@ -1,7 +1,6 @@ -"""命令行解析器 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""命令行解析器""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" import copy import re from typing import Any diff --git a/apps/scheduler/call/convert/__init__.py b/apps/scheduler/call/convert/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..28cb9cf0550d11aef25ecef15f9aed237db3b616 --- /dev/null +++ b/apps/scheduler/call/convert/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""模板转换工具""" diff --git a/apps/scheduler/call/convert/convert.py b/apps/scheduler/call/convert/convert.py index 9952e041aff8c2a3fec38e7d41056757ff868969..aa910900dc01423550aa7ada3787f902c1730a4b 100644 --- a/apps/scheduler/call/convert/convert.py +++ b/apps/scheduler/call/convert/convert.py @@ -1,8 +1,6 @@ -""" -提取或格式化Step输出 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""提取或格式化Step输出""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" from collections.abc import AsyncGenerator from datetime import datetime from typing import Any diff --git a/apps/scheduler/call/convert/schema.py b/apps/scheduler/call/convert/schema.py index 1af354c42717c63312b74e793f3173d6d06b13c0..9a4fa1a76893d8bb1826cae3373e393abdd9d3a4 100644 --- a/apps/scheduler/call/convert/schema.py +++ b/apps/scheduler/call/convert/schema.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """Convert工具的Schema""" from pydantic import Field diff --git a/apps/scheduler/call/core.py b/apps/scheduler/call/core.py index 74c51a78f18fdfe9a2b47d9e968e0d9897f82386..820c1fea9bf5594e9a1237c96b4598e9ab91bbd5 100644 --- a/apps/scheduler/call/core.py +++ b/apps/scheduler/call/core.py @@ -1,8 +1,8 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """ -Core Call类,定义了所有Call的抽象类和基础参数。 +Core Call类是定义了所有Call都应具有的方法和参数的PyDantic类。 -所有Call类必须继承此类,并实现所有方法。 -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +所有Call类必须继承此类,并根据需求重载方法。 """ import logging @@ -23,6 +23,8 @@ from apps.entities.scheduler import ( CallVars, ) from apps.entities.task import FlowStepHistory +from apps.llm.function import FunctionLLM +from apps.llm.reasoning import ReasoningLLM if TYPE_CHECKING: from apps.scheduler.executor.step import StepExecutor @@ -45,13 +47,17 @@ class DataBase(BaseModel): class CoreCall(BaseModel): - """所有Call的父类,所有Call必须继承此类。""" + """所有Call的父类,包含通用的逻辑""" name: SkipJsonSchema[str] = Field(description="Step的名称", exclude=True) description: SkipJsonSchema[str] = Field(description="Step的描述", exclude=True) node: SkipJsonSchema[NodePool | None] = Field(description="节点信息", exclude=True) enable_filling: SkipJsonSchema[bool] = Field(description="是否需要进行自动参数填充", default=False, exclude=True) - tokens: SkipJsonSchema[CallTokens] = Field(description="Call的Tokens", default=CallTokens(), exclude=True) + tokens: SkipJsonSchema[CallTokens] = Field( + description="Call的输入输出Tokens信息", + default=CallTokens(), + exclude=True, + ) input_model: ClassVar[SkipJsonSchema[type[DataBase]]] = Field( description="Call的输入Pydantic类型;不包含override的模板", exclude=True, @@ -192,3 +198,21 @@ class CoreCall(BaseModel): async for chunk in self._exec(input_data): yield chunk await self._after_exec(input_data) + + + async def _llm(self, messages: list[dict[str, Any]]) -> str: + """Call可直接使用的LLM非流式调用""" + result = "" + llm = ReasoningLLM() + async for chunk in llm.call(messages, streaming=False): + result += chunk + self.input_tokens = llm.input_tokens + self.output_tokens = llm.output_tokens + return result + + + async def _json(self, messages: list[dict[str, Any]], schema: type[BaseModel]) -> BaseModel: + """Call可直接使用的JSON生成""" + json = FunctionLLM() + result = await json.call(messages=messages, schema=schema.model_json_schema()) + return schema.model_validate(result) diff --git a/apps/scheduler/call/empty.py b/apps/scheduler/call/empty.py index f33832c2fbc4e280c2f79965e5f80abe2bd93b05..bd32816e3fcfc3bfec22c8edb2f95ec7cded017e 100644 --- a/apps/scheduler/call/empty.py +++ b/apps/scheduler/call/empty.py @@ -1,8 +1,5 @@ -""" -空白Call - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""空白Call""" from collections.abc import AsyncGenerator from typing import Any @@ -17,16 +14,33 @@ class Empty(CoreCall, input_model=DataBase, output_model=DataBase): @classmethod def info(cls) -> CallInfo: - """返回Call的名称和描述""" + """ + 返回Call的名称和描述 + + :return: Call的名称和描述 + :rtype: CallInfo + """ return CallInfo(name="空白", description="空白节点,用于占位") async def _init(self, call_vars: CallVars) -> DataBase: - """初始化""" + """ + 初始化Call + + :param CallVars call_vars: 由Executor传入的变量,包含当前运行信息 + :return: Call的输入 + :rtype: DataBase + """ return DataBase() async def _exec(self, input_data: dict[str, Any]) -> AsyncGenerator[CallOutputChunk, None]: - """执行""" + """ + 执行Call + + :param dict[str, Any] input_data: 填充后的Call的最终输入 + :return: Call的输出 + :rtype: AsyncGenerator[CallOutputChunk, None] + """ output = CallOutputChunk(type=CallOutputType.DATA, content={}) yield output diff --git a/apps/scheduler/call/facts/__init__.py b/apps/scheduler/call/facts/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..559d3c571f85871a90f66d9e7b38ffa83c1156d4 --- /dev/null +++ b/apps/scheduler/call/facts/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""记忆提取工具""" diff --git a/apps/scheduler/call/facts/facts.py b/apps/scheduler/call/facts/facts.py index 544ddf8ef921de83d7d041b67b52da08a189fc7c..e673efcbfb3e65ab68728ea5b67cd94e9a0a9409 100644 --- a/apps/scheduler/call/facts/facts.py +++ b/apps/scheduler/call/facts/facts.py @@ -1,18 +1,25 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """提取事实工具""" from collections.abc import AsyncGenerator from typing import TYPE_CHECKING, Any, Self +from jinja2 import BaseLoader +from jinja2.sandbox import SandboxedEnvironment from pydantic import Field from apps.entities.enum_var import CallOutputType from apps.entities.pool import NodePool from apps.entities.scheduler import CallInfo, CallOutputChunk, CallVars -from apps.llm.patterns.domain import Domain -from apps.llm.patterns.facts import Facts from apps.manager.user_domain import UserDomainManager from apps.scheduler.call.core import CoreCall -from apps.scheduler.call.facts.schema import FactsInput, FactsOutput +from apps.scheduler.call.facts.prompt import DOMAIN_PROMPT, FACTS_PROMPT +from apps.scheduler.call.facts.schema import ( + DomainGen, + FactsGen, + FactsInput, + FactsOutput, +) if TYPE_CHECKING: from apps.scheduler.executor.step import StepExecutor @@ -62,27 +69,38 @@ class FactsCall(CoreCall, input_model=FactsInput, output_model=FactsOutput): async def _exec(self, input_data: dict[str, Any]) -> AsyncGenerator[CallOutputChunk, None]: """执行工具""" data = FactsInput(**input_data) + # jinja2 环境 + env = SandboxedEnvironment( + loader=BaseLoader(), + autoescape=False, + trim_blocks=True, + lstrip_blocks=True, + ) # 提取事实信息 - facts_obj = Facts() - facts = await facts_obj.generate(conversation=data.message) - self.tokens.input_tokens += facts_obj.input_tokens - self.tokens.output_tokens += facts_obj.output_tokens + facts_tpl = env.from_string(FACTS_PROMPT) + facts_prompt = facts_tpl.render(conversation=data.message) + facts_obj: FactsGen = await self._json([ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": facts_prompt}, + ], FactsGen) # type: ignore[arg-type] # 更新用户画像 - domain_obj = Domain() - domain_list = await domain_obj.generate(conversation=data.message) - self.tokens.input_tokens += domain_obj.input_tokens - self.tokens.output_tokens += domain_obj.output_tokens - - for domain in domain_list: + domain_tpl = env.from_string(DOMAIN_PROMPT) + domain_prompt = domain_tpl.render(conversation=data.message) + domain_list: DomainGen = await self._json([ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": domain_prompt}, + ], DomainGen) # type: ignore[arg-type] + + for domain in domain_list.keywords: await UserDomainManager.update_user_domain_by_user_sub_and_domain_name(data.user_sub, domain) yield CallOutputChunk( type=CallOutputType.DATA, content=FactsOutput( - facts=facts, - domain=domain_list, + facts=facts_obj.facts, + domain=domain_list.keywords, ).model_dump(by_alias=True, exclude_none=True), ) diff --git a/apps/scheduler/call/facts/prompt.py b/apps/scheduler/call/facts/prompt.py new file mode 100644 index 0000000000000000000000000000000000000000..b2b2513f2c28feb4d19f5fb4ee3eba1bd616a4dc --- /dev/null +++ b/apps/scheduler/call/facts/prompt.py @@ -0,0 +1,83 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""记忆提取工具的提示词""" + +from textwrap import dedent + +DOMAIN_PROMPT: str = dedent(r""" + + + 根据对话上文,提取推荐系统所需的关键词标签,要求: + 1. 实体名词、技术术语、时间范围、地点、产品等关键信息均可作为关键词标签 + 2. 至少一个关键词与对话的话题有关 + 3. 标签需精简,不得重复,不得超过10个字 + 4. 使用JSON格式输出,不要包含XML标签,不要包含任何解释说明 + + + + + 北京天气如何? + 北京今天晴。 + + + + { + "keywords": ["北京", "天气"] + } + + + + + + {% for item in conversation %} + <{{item['role']}}> + {{item['content']}} + + {% endfor %} + + +""") +FACTS_PROMPT: str = dedent(r""" + + + 从对话中提取关键信息,并将它们组织成独一无二的、易于理解的事实,包含用户偏好、关系、实体等有用信息。 + 以下是需要关注的信息类型以及有关如何处理输入数据的详细说明。 + + **你需要关注的信息类型** + 1. 实体:对话中涉及到的实体。例如:姓名、地点、组织、事件等。 + 2. 偏好:对待实体的态度。例如喜欢、讨厌等。 + 3. 关系:用户与实体之间,或两个实体之间的关系。例如包含、并列、互斥等。 + 4. 动作:对实体产生影响的具体动作。例如查询、搜索、浏览、点击等。 + + **要求** + 1. 事实必须准确,只能从对话中提取。不要将样例中的信息体现在输出中。 + 2. 事实必须清晰、简洁、易于理解。必须少于30个字。 + 3. 必须按照以下JSON格式输出: + + { + "facts": ["事实1", "事实2", "事实3"] + } + + + + + 杭州西湖有哪些景点? + 杭州西湖是中国浙江省杭州市的一个著名景点,以其美丽的自然风光和丰富的文化遗产而闻名。西湖周围有许多著名的景点,包括著名的苏堤、白堤、断桥、三潭印月等。西湖以其清澈的湖水和周围的山脉而著名,是中国最著名的湖泊之一。 + + + + { + "facts": ["杭州西湖有苏堤、白堤、断桥、三潭印月等景点"] + } + + + + + + {% for item in conversation %} + <{{item['role']}}> + {{item['content']}} + + {% endfor %} + + +""") diff --git a/apps/scheduler/call/facts/schema.py b/apps/scheduler/call/facts/schema.py index fc3910a98fbeb59decd11d3312bd6dc22441557e..c16b94ebca28351dede99ed0a39476573b93b6d8 100644 --- a/apps/scheduler/call/facts/schema.py +++ b/apps/scheduler/call/facts/schema.py @@ -1,10 +1,23 @@ -"""Facts工具的输入和输出""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""记忆提取工具的输入和输出""" -from pydantic import Field +from pydantic import BaseModel, Field from apps.scheduler.call.core import DataBase +class DomainGen(BaseModel): + """生成的领域信息结果""" + + keywords: list[str] = Field(description="关键词或标签列表,可以为空。") + + +class FactsGen(BaseModel): + """生成的提取事实结果""" + + facts: list[str] = Field(description="从对话中提取的事实条目,可以为空。") + + class FactsInput(DataBase): """提取事实工具的输入""" diff --git a/apps/scheduler/call/graph/__init__.py b/apps/scheduler/call/graph/__init__.py index 2a4869835e16717864af645b08f65b807d0ac17d..9e6af6caeafb5c5a83872f07b31d6d624f007cb5 100644 --- a/apps/scheduler/call/graph/__init__.py +++ b/apps/scheduler/call/graph/__init__.py @@ -1,5 +1,2 @@ -""" -Render工具 - -用于生成ECharts图表。 -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""图表生成工具""" diff --git a/apps/scheduler/call/graph/graph.py b/apps/scheduler/call/graph/graph.py index 1e8ff7f8d6cda71872da2004b91702695875bfb4..db2e5ae71303eb80e8799f48107ed2685e85c5dd 100644 --- a/apps/scheduler/call/graph/graph.py +++ b/apps/scheduler/call/graph/graph.py @@ -1,8 +1,5 @@ -""" -Call: Render,用于将SQL Tool查询出的数据转换为图表 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""图表生成工具""" import json from collections.abc import AsyncGenerator diff --git a/apps/scheduler/call/graph/schema.py b/apps/scheduler/call/graph/schema.py index d157f86f45e70aff5688cf8f7162a1db44ffa9a5..0674a4c48e5d2c04bc64fb60433c89dbbc30f400 100644 --- a/apps/scheduler/call/graph/schema.py +++ b/apps/scheduler/call/graph/schema.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """图表工具的输入输出""" from typing import Any diff --git a/apps/scheduler/call/graph/style.py b/apps/scheduler/call/graph/style.py index 210ca7956b474622ccd2fa650064a6dcefb4996f..631ea88acb9c0ef6b851e35cb3fffdecc567a901 100644 --- a/apps/scheduler/call/graph/style.py +++ b/apps/scheduler/call/graph/style.py @@ -1,15 +1,25 @@ -""" -Render Call: 选择图表样式 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""选择图表样式""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +import logging +from typing import Any, Literal -from typing import Any, ClassVar +from pydantic import BaseModel, Field +from apps.llm.function import JsonGenerator from apps.llm.patterns.core import CorePattern -from apps.llm.patterns.json_gen import Json from apps.llm.reasoning import ReasoningLLM +logger = logging.getLogger(__name__) + + +class RenderStyleResult(BaseModel): + """选择图表样式结果""" + + chart_type: Literal["bar", "pie", "line", "scatter"] = Field(description="图表类型") + additional_style: Literal["normal", "stacked", "ring"] | None = Field(description="图表样式") + scale_type: Literal["linear", "log"] = Field(description="图表比例") + class RenderStyle(CorePattern): """选择图表样式""" @@ -62,28 +72,6 @@ class RenderStyle(CorePattern): Let's think step by step. """ - slot_schema: ClassVar[dict[str, Any]] = { - "type": "object", - "properties": { - "chart_type": { - "type": "string", - "description": "The type of the chart.", - "enum": ["bar", "pie", "line", "scatter"], - }, - "additional_style": { - "type": "string", - "description": "The additional style of the chart.", - "enum": ["normal", "stacked", "ring"], - }, - "scale_type": { - "type": "string", - "description": "The scale of the chart.", - "enum": ["linear", "log"], - }, - }, - "required": ["chart_type", "scale_type"], - } - def __init__(self, system_prompt: str | None = None, user_prompt: str | None = None) -> None: """初始化RenderStyle Prompt""" super().__init__(system_prompt, user_prompt) @@ -109,4 +97,16 @@ class RenderStyle(CorePattern): ] # 使用FunctionLLM模型进行提取参数 - return await Json().generate(conversation=messages, spec=self.slot_schema) + json_gen = JsonGenerator( + query="根据给定的背景信息,生成预测问题", + conversation=messages, + schema=RenderStyleResult.model_json_schema(), + ) + try: + result_dict = await json_gen.generate() + RenderStyleResult.model_validate(result_dict) + except Exception: + logger.exception("[RenderStyle] 选择图表样式失败") + return {} + + return result_dict diff --git a/apps/scheduler/call/llm/__init__.py b/apps/scheduler/call/llm/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c56fc73189d73545cf19cb1a8023e8b3d820e048 --- /dev/null +++ b/apps/scheduler/call/llm/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""LLM工具""" diff --git a/apps/scheduler/call/llm/llm.py b/apps/scheduler/call/llm/llm.py index ef7f618a161d131cc3ce77ee14027fcae0f08868..00d5cafa1aad03cf4693f6a09e108f6bb5c9db46 100644 --- a/apps/scheduler/call/llm/llm.py +++ b/apps/scheduler/call/llm/llm.py @@ -1,8 +1,5 @@ -""" -工具:调用大模型 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""调用大模型""" import logging from collections.abc import AsyncGenerator @@ -23,7 +20,8 @@ from apps.entities.scheduler import ( ) from apps.llm.reasoning import ReasoningLLM from apps.scheduler.call.core import CoreCall -from apps.scheduler.call.llm.schema import LLM_CONTEXT_PROMPT, LLM_DEFAULT_PROMPT, LLMInput, LLMOutput +from apps.scheduler.call.llm.prompt import LLM_CONTEXT_PROMPT, LLM_DEFAULT_PROMPT +from apps.scheduler.call.llm.schema import LLMInput, LLMOutput logger = logging.getLogger(__name__) @@ -37,7 +35,7 @@ class LLM(CoreCall, input_model=LLMInput, output_model=LLMOutput): temperature: float = Field(description="大模型温度(随机化程度)", default=0.7) enable_context: bool = Field(description="是否启用上下文", default=True) step_history_size: int = Field(description="上下文信息中包含的步骤历史数量", default=3, ge=1, le=10) - system_prompt: str = Field(description="大模型系统提示词", default="") + system_prompt: str = Field(description="大模型系统提示词", default="You are a helpful assistant.") user_prompt: str = Field(description="大模型用户提示词", default=LLM_DEFAULT_PROMPT) diff --git a/apps/scheduler/call/llm/prompt.py b/apps/scheduler/call/llm/prompt.py new file mode 100644 index 0000000000000000000000000000000000000000..0f227dcaa618b11a2c888f55a61fa51f349d7d8b --- /dev/null +++ b/apps/scheduler/call/llm/prompt.py @@ -0,0 +1,99 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""大模型工具的提示词""" + +from textwrap import dedent + +LLM_CONTEXT_PROMPT = dedent( + r""" + 以下是对用户和AI间对话的简短总结,在中给出: + + {{ summary }} + + + 你作为AI,在回答用户的问题前,需要获取必要的信息。为此,你调用了一些工具,并获得了它们的输出: + 工具的输出数据将在中给出, 其中为工具的名称,为工具的输出数据。 + + {% for tool in history_data %} + + {{ tool.step_name }} + {{ tool.step_description }} + {{ tool.output_data }} + + {% endfor %} + + """, +).strip("\n") +LLM_DEFAULT_PROMPT = dedent( + r""" + + 你是一个乐于助人的智能助手。请结合给出的背景信息, 回答用户的提问。 + 当前时间:{{ time }},可以作为时间参照。 + 用户的问题将在中给出,上下文背景信息将在中给出。 + 注意:输出不要包含任何XML标签,不要编造任何信息。若你认为用户提问与背景信息无关,请忽略背景信息直接作答。 + + + + {{ question }} + + + + {{ context }} + + + 现在,输出你的回答: + """, +).strip("\n") +LLM_ERROR_PROMPT = dedent( + r""" + + 你是一位智能助手,能够根据用户的问题,使用Python工具获取信息,并作出回答。你在使用工具解决回答用户的问题时,发生了错误。 + 你的任务是:分析工具(Python程序)的异常信息,分析造成该异常可能的原因,并以通俗易懂的方式,将原因告知用户。 + + 当前时间:{{ time }},可以作为时间参照。 + 发生错误的程序异常信息将在中给出,用户的问题将在中给出,上下文背景信息将在中给出。 + 注意:输出不要包含任何XML标签,不要编造任何信息。若你认为用户提问与背景信息无关,请忽略背景信息。 + + + + {{ error_info }} + + + + {{ question }} + + + + {{ context }} + + + 现在,输出你的回答: + """, +).strip("\n") +RAG_ANSWER_PROMPT = dedent( + r""" + + 你是由openEuler社区构建的大型语言AI助手。请根据背景信息(包含对话上下文和文档片段),回答用户问题。 + 用户的问题将在中给出,上下文背景信息将在中给出,文档片段将在中给出。 + + 注意事项: + 1. 输出不要包含任何XML标签。请确保输出内容的正确性,不要编造任何信息。 + 2. 如果用户询问你关于你自己的问题,请统一回答:“我叫EulerCopilot,是openEuler社区的智能助手”。 + 3. 背景信息仅供参考,若背景信息与用户问题无关,请忽略背景信息直接作答。 + 4. 请在回答中使用Markdown格式,并**不要**将内容放在"```"中。 + + + + {{ question }} + + + + {{ context }} + + + + {{ document }} + + + 现在,请根据上述信息,回答用户的问题: + """, +).strip("\n") diff --git a/apps/scheduler/call/llm/schema.py b/apps/scheduler/call/llm/schema.py index 64fe93af6be31953b6e692afb5855f0f0581588d..c7bb50541d168a406fa478f25894c769b034ec9c 100644 --- a/apps/scheduler/call/llm/schema.py +++ b/apps/scheduler/call/llm/schema.py @@ -1,106 +1,10 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """LLM工具的输入输出定义""" -from textwrap import dedent - from pydantic import Field from apps.scheduler.call.core import DataBase -LLM_CONTEXT_PROMPT = dedent( - r""" - 以下是对用户和AI间对话的简短总结,在中给出: - - {{ summary }} - - - 你作为AI,在回答用户的问题前,需要获取必要的信息。为此,你调用了一些工具,并获得了它们的输出: - 工具的输出数据将在中给出, 其中为工具的名称,为工具的输出数据。 - - {% for tool in history_data %} - - {{ tool.step_name }} - {{ tool.step_description }} - {{ tool.output_data }} - - {% endfor %} - - """, -).strip("\n") -LLM_DEFAULT_PROMPT = dedent( - r""" - - 你是一个乐于助人的智能助手。请结合给出的背景信息, 回答用户的提问。 - 当前时间:{{ time }},可以作为时间参照。 - 用户的问题将在中给出,上下文背景信息将在中给出。 - 注意:输出不要包含任何XML标签,不要编造任何信息。若你认为用户提问与背景信息无关,请忽略背景信息直接作答。 - - - - {{ question }} - - - - {{ context }} - - - 现在,输出你的回答: - """, -).strip("\n") -LLM_ERROR_PROMPT = dedent( - r""" - - 你是一位智能助手,能够根据用户的问题,使用Python工具获取信息,并作出回答。你在使用工具解决回答用户的问题时,发生了错误。 - 你的任务是:分析工具(Python程序)的异常信息,分析造成该异常可能的原因,并以通俗易懂的方式,将原因告知用户。 - - 当前时间:{{ time }},可以作为时间参照。 - 发生错误的程序异常信息将在中给出,用户的问题将在中给出,上下文背景信息将在中给出。 - 注意:输出不要包含任何XML标签,不要编造任何信息。若你认为用户提问与背景信息无关,请忽略背景信息。 - - - - {{ error_info }} - - - - {{ question }} - - - - {{ context }} - - - 现在,输出你的回答: - """, -).strip("\n") -RAG_ANSWER_PROMPT = dedent( - r""" - - 你是由openEuler社区构建的大型语言AI助手。请根据背景信息(包含对话上下文和文档片段),回答用户问题。 - 用户的问题将在中给出,上下文背景信息将在中给出,文档片段将在中给出。 - - 注意事项: - 1. 输出不要包含任何XML标签。请确保输出内容的正确性,不要编造任何信息。 - 2. 如果用户询问你关于你自己的问题,请统一回答:“我叫EulerCopilot,是openEuler社区的智能助手”。 - 3. 背景信息仅供参考,若背景信息与用户问题无关,请忽略背景信息直接作答。 - 4. 请在回答中使用Markdown格式,并**不要**将内容放在"```"中。 - - - - {{ question }} - - - - {{ context }} - - - - {{ document }} - - - 现在,请根据上述信息,回答用户的问题: - """, -).strip("\n") - class LLMInput(DataBase): """定义LLM工具调用的输入""" diff --git a/apps/scheduler/call/mcp/__init__.py b/apps/scheduler/call/mcp/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d043535e07a93677d73478e651f734970da3ebcc --- /dev/null +++ b/apps/scheduler/call/mcp/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MCP工具""" diff --git a/apps/scheduler/call/mcp/mcp.py b/apps/scheduler/call/mcp/mcp.py new file mode 100644 index 0000000000000000000000000000000000000000..4a9268e7591df1efaeab24e10c943f5551c99b17 --- /dev/null +++ b/apps/scheduler/call/mcp/mcp.py @@ -0,0 +1,180 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MCP工具""" + +import logging +from collections.abc import AsyncGenerator +from copy import deepcopy +from typing import Any + +from pydantic import Field + +from apps.entities.enum_var import CallOutputType +from apps.entities.mcp import MCPPlanItem +from apps.entities.scheduler import ( + CallInfo, + CallOutputChunk, + CallVars, +) +from apps.scheduler.call.core import CallError, CoreCall +from apps.scheduler.call.mcp.schema import ( + MCPInput, + MCPMessage, + MCPMessageType, + MCPOutput, +) +from apps.scheduler.mcp import MCPHost, MCPPlanner, MCPSelector + +logger = logging.getLogger(__name__) + + +class MCP(CoreCall, input_model=MCPInput, output_model=MCPOutput): + """MCP工具""" + + mcp_list: list[str] = Field(description="MCP Server ID列表", max_length=5, min_length=1) + max_steps: int = Field(description="最大步骤数", default=6) + text_output: bool = Field(description="是否将结果以文本形式返回", default=True) + to_user: bool = Field(description="是否将结果返回给用户", default=True) + + + @classmethod + def info(cls) -> CallInfo: + """ + 返回Call的名称和描述 + + :return: Call的名称和描述 + :rtype: CallInfo + """ + return CallInfo(name="MCP", description="调用MCP Server,执行工具") + + + async def _init(self, call_vars: CallVars) -> MCPInput: + """初始化MCP""" + # 获取MCP交互类 + self._host = MCPHost(call_vars.ids.user_sub, call_vars.ids.task_id, call_vars.ids.flow_id, self.description) + self._tool_list = await self._host.get_tool_list(self.mcp_list) + self._call_vars = call_vars + + # 获取工具列表 + avaliable_tools = {} + for tool in self._tool_list: + if tool.mcp_id not in avaliable_tools: + avaliable_tools[tool.mcp_id] = [] + avaliable_tools[tool.mcp_id].append(tool.name) + + return MCPInput(avaliable_tools=avaliable_tools, max_steps=self.max_steps) + + + async def _exec(self, input_data: dict[str, Any]) -> AsyncGenerator[CallOutputChunk, None]: + """执行MCP""" + # 生成计划 + async for chunk in self._generate_plan(): + yield chunk + + # 执行计划 + plan_list = deepcopy(self._plan.plans) + while len(plan_list) > 0: + async for chunk in self._execute_plan_item(plan_list.pop(0)): + yield chunk + + # 生成总结 + async for chunk in self._generate_answer(): + yield chunk + + + async def _generate_plan(self) -> AsyncGenerator[CallOutputChunk, None]: + """生成执行计划""" + # 开始提示 + yield self._create_output("[MCP] 开始生成计划...\n\n\n\n", MCPMessageType.PLAN_BEGIN) + + # 选择工具并生成计划 + selector = MCPSelector() + top_tool = await selector.select_top_tool(self._call_vars.question, self.mcp_list) + planner = MCPPlanner(self._call_vars.question) + self._plan = await planner.create_plan(top_tool, self.max_steps) + + # 输出计划 + plan_str = "\n\n" + for plan_item in self._plan.plans: + plan_str += f"[+] {plan_item.plan}; {plan_item.tool}[{plan_item.instruction}]\n\n" + + yield self._create_output( + f"[MCP] 计划生成完成:\n\n{plan_str}\n\n\n\n", + MCPMessageType.PLAN_END, + data=self._plan.model_dump(), + ) + + + async def _execute_plan_item(self, plan_item: MCPPlanItem) -> AsyncGenerator[CallOutputChunk, None]: + """执行单个计划项""" + # 判断是否为Final + if plan_item.tool == "Final": + return + + # 获取工具 + tool = next((tool for tool in self._tool_list if tool.name == plan_item.tool), None) + if tool is None: + err = f"[MCP] 工具 {plan_item.tool} 不存在" + logger.error(err) + raise CallError(err, data={}) + + # 提示开始调用 + yield self._create_output( + f"[MCP] 正在调用工具 {tool.name}...\n\n", + MCPMessageType.TOOL_BEGIN, + ) + + # 调用工具 + try: + result = await self._host.call_tool(tool, plan_item) + except Exception as e: + err = f"[MCP] 工具 {tool.name} 调用失败: {e!s}" + logger.exception(err) + raise CallError(err, data={}) from e + + # 提示调用完成 + logger.info("[MCP] 工具 %s 调用完成, 结果: %s", tool.name, result) + yield self._create_output( + f"[MCP] 工具 {tool.name} 调用完成\n\n", + MCPMessageType.TOOL_END, + data={ + "data": result, + }, + ) + + + async def _generate_answer(self) -> AsyncGenerator[CallOutputChunk, None]: + """生成总结""" + # 提示开始总结 + yield self._create_output( + "[MCP] 正在总结任务结果...\n\n", + MCPMessageType.FINISH_BEGIN, + ) + + # 生成答案 + planner = MCPPlanner(self._call_vars.question) + answer = await planner.generate_answer(self._plan, await self._host.assemble_memory()) + + # 输出结果 + yield self._create_output( + f"[MCP] 任务完成\n\n---\n\n{answer}\n\n", + MCPMessageType.FINISH_END, + data=MCPOutput( + message=answer, + ).model_dump(), + ) + + + def _create_output( + self, + text: str, + msg_type: MCPMessageType, + data: dict[str, Any] | None = None, + ) -> CallOutputChunk: + """创建输出""" + if self.text_output: + return CallOutputChunk(type=CallOutputType.TEXT, content=text) + return CallOutputChunk(type=CallOutputType.DATA, content=MCPMessage( + msg_type=msg_type, + message=text.strip(), + data=data or {}, + ).model_dump_json()) diff --git a/apps/scheduler/call/mcp/schema.py b/apps/scheduler/call/mcp/schema.py new file mode 100644 index 0000000000000000000000000000000000000000..cb475d062e6f2874181c4801f8fba65890f7a7c1 --- /dev/null +++ b/apps/scheduler/call/mcp/schema.py @@ -0,0 +1,42 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MCP Call相关的数据结构""" + +from enum import Enum +from typing import Any + +from pydantic import BaseModel, Field + +from apps.scheduler.call.core import DataBase + + +class MCPInput(DataBase): + """MCP Call输入""" + + avaliable_tools: dict[str, list[str]] = Field(description="MCP Server ID及其可用的工具名称列表") + max_steps: int = Field(description="最大步骤数") + + +class MCPMessageType(str, Enum): + """MCP Message类型""" + + PLAN_BEGIN = "plan_begin" + PLAN_END = "plan_end" + TOOL_BEGIN = "tool_begin" + TOOL_END = "tool_end" + EVALUATE = "evaluate" + FINISH_BEGIN = "finish_begin" + FINISH_END = "finish_end" + + +class MCPMessage(BaseModel): + """MCP Message""" + + msg_type: MCPMessageType = Field(description="消息的类型") + message: str = Field(description="消息的内容") + data: dict[str, Any] = Field(description="工具的输出") + + +class MCPOutput(DataBase): + """MCP Call输出""" + + message: str = Field(description="MCP Server的自然语言输出") diff --git a/apps/scheduler/call/rag/__init__.py b/apps/scheduler/call/rag/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7333612d56533d33ce61a59b5e10a0ed04402b84 --- /dev/null +++ b/apps/scheduler/call/rag/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""RAG工具""" diff --git a/apps/scheduler/call/rag/rag.py b/apps/scheduler/call/rag/rag.py index 26612687e70ec46f0534e0174b294b08478f8d7d..aedcd6073553c287d313ef0e1107c2d33d70613a 100644 --- a/apps/scheduler/call/rag/rag.py +++ b/apps/scheduler/call/rag/rag.py @@ -1,14 +1,11 @@ -""" -RAG工具:查询知识库 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""RAG工具:查询知识库""" import logging from collections.abc import AsyncGenerator -from typing import Any, Literal +from typing import Any, Optional -import aiohttp +import httpx from fastapi import status from pydantic import Field @@ -22,78 +19,103 @@ from apps.entities.scheduler import ( ) from apps.llm.patterns.rewrite import QuestionRewrite from apps.scheduler.call.core import CoreCall -from apps.scheduler.call.rag.schema import RAGInput, RAGOutput, RetrievalMode - +from apps.scheduler.call.rag.schema import RAGInput, RAGOutput, SearchMethod +from apps.service.rag import RAG as RAGService logger = logging.getLogger(__name__) class RAG(CoreCall, input_model=RAGInput, output_model=RAGOutput): """RAG工具:查询知识库""" - knowledge_base: str | None = Field(description="知识库的id", alias="kb_sn", default=None) - top_k: int = Field(description="返回的答案数量(经过整合以及上下文关联)", default=5) - retrieval_mode: Literal["chunk", "full_text"] = Field(description="检索模式", default="chunk") - + user_sub: str = Field(description="用户的sub") + knowledge_base_ids: list[str] = Field(description="知识库的id列表", default=[]) + top_k: int = Field(description="返回的分片数量", default=5) + document_ids: list[str] | None = Field(description="文档id列表", default=None) + search_method: str = Field(description="检索方法", default=SearchMethod.KEYWORD_AND_VECTOR.value) + is_related_surrounding: bool = Field(description="是否关联上下文", default=True) + is_classify_by_doc: bool = Field(description="是否按文档分类", default=False) + is_rerank: bool = Field(description="是否重新排序", default=False) + is_compress: bool = Field(description="是否压缩", default=False) + tokens_limit: int = Field(description="token限制", default=8192) @classmethod def info(cls) -> CallInfo: """返回Call的名称和描述""" return CallInfo(name="知识库", description="查询知识库,从文档中获取必要信息") - async def _init(self, call_vars: CallVars) -> RAGInput: """初始化RAG工具""" return RAGInput( - content=call_vars.question, - kb_sn=self.knowledge_base, - top_k=self.top_k, - retrieval_mode=RetrievalMode(self.retrieval_mode), + session_id=call_vars.ids.session_id, + kbIds=self.knowledge_base_ids, + topK=self.top_k, + query=call_vars.question, + docIds=self.document_ids, + searchMethod=self.search_method, + isRelatedSurrounding=self.is_related_surrounding, + isClassifyByDoc=self.is_classify_by_doc, + isRerank=self.is_rerank, + isCompress=self.is_compress, + tokensLimit=self.tokens_limit, ) - async def _exec(self, input_data: dict[str, Any]) -> AsyncGenerator[CallOutputChunk, None]: """调用RAG工具""" data = RAGInput(**input_data) question_obj = QuestionRewrite() - question = await question_obj.generate(question=data.content) - data.content = question + question = await question_obj.generate(question=data.question) + data.question = question self.tokens.input_tokens += question_obj.input_tokens self.tokens.output_tokens += question_obj.output_tokens - url = Config().get_config().rag.rag_service.rstrip("/") + "/chunk/get" + url = Config().get_config().rag.rag_service.rstrip("/") + "/chunk/search" headers = { "Content-Type": "application/json", + "Authorization": f"Bearer {data.session_id}" } - # 发送 GET 请求 - async with aiohttp.ClientSession() as session, session.post(url, headers=headers, json=input_data) as response: + # 发送请求 + data_json = data.model_dump(exclude_none=True, by_alias=True) + del data_json["session_id"] + async with httpx.AsyncClient() as client: + response = await client.post(url, headers=headers, json=data_json) + # 检查响应状态码 - if response.status == status.HTTP_200_OK: - result = await response.json() - chunk_list = result["data"] + if response.status_code == status.HTTP_200_OK: + result = response.json() + doc_chunk_list = result["result"]["docChunks"] corpus = [] - for chunk in chunk_list: - clean_chunk = chunk.replace("\n", " ") - corpus.append(clean_chunk) - + for doc_chunk in doc_chunk_list: + for chunk in doc_chunk["chunks"]: + corpus.append(chunk["text"].replace("\n", "")) + new_corpus = [] + tokens = 0 + for i in range(len(corpus)): + if tokens+RAGService.get_tokens(corpus[i]) >= self.tokens_limit: + new_corpus.append(RAGService.get_k_tokens_words_from_content( + corpus[i], self.tokens_limit-tokens)) + break + new_corpus.append(corpus[i]) + tokens += RAGService.get_tokens(corpus[i]) + corpus = new_corpus yield CallOutputChunk( type=CallOutputType.DATA, content=RAGOutput( - question=data.content, + question=data.question, corpus=corpus, ).model_dump(exclude_none=True, by_alias=True), ) return - text = await response.text() + text = response.text logger.error("[RAG] 调用失败:%s", text) raise CallError( message=f"rag调用失败:{text}", data={ - "question": data.content, - "status": response.status, + "question": data.question, + "status": response.status_code, "text": text, }, ) diff --git a/apps/scheduler/call/rag/schema.py b/apps/scheduler/call/rag/schema.py index d5e7493ceccc33ca87f28dfab724e3bbbe968bd9..90246be5b2558d61305e49d45e3179695f50f663 100644 --- a/apps/scheduler/call/rag/schema.py +++ b/apps/scheduler/call/rag/schema.py @@ -1,5 +1,10 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """RAG工具的输入和输出""" +import uuid + +from typing import Optional + from enum import Enum from pydantic import Field @@ -7,11 +12,15 @@ from pydantic import Field from apps.scheduler.call.core import DataBase -class RetrievalMode(str, Enum): - """检索模式""" +class SearchMethod(str, Enum): + """搜索方法""" - CHUNK = "chunk" - FULL_TEXT = "full_text" + KEYWORD = "keyword" + VECTOR = "vector" + KEYWORD_AND_VECTOR = "keyword_and_vector" + DOC2CHUNK = "doc2chunk" + DOC2CHUNK_BFS = "doc2chunk_bfs" + ENHANCED_BY_LLM = "enhanced_by_llm" class RAGOutput(DataBase): @@ -24,7 +33,14 @@ class RAGOutput(DataBase): class RAGInput(DataBase): """RAG工具的输入""" - content: str = Field(description="用户输入") - knowledge_base: str | None = Field(description="知识库的id", alias="kb_sn", default=None) - top_k: int = Field(description="返回的答案数量(经过整合以及上下文关联)", default=5) - retrieval_mode: RetrievalMode = Field(description="检索模式", default=RetrievalMode.CHUNK) + session_id: str = Field(description="会话id") + knowledge_base_ids: list[str] = Field(description="知识库的id列表", default=[], alias="kbIds") + top_k: int = Field(description="返回的分片数量", default=5, alias="topK") + question: str = Field(description="用户输入", default="", alias="query") + document_ids: Optional[list[str]] = Field(description="文档id列表", default=None, alias="docIds") + search_method: str = Field(description="检索方法", default=SearchMethod.KEYWORD_AND_VECTOR.value, alias="searchMethod") + is_related_surrounding: bool = Field(description="是否关联上下文", default=True, alias="isRelatedSurrounding") + is_classify_by_doc: bool = Field(description="是否按文档分类", default=True, alias="isClassifyByDoc") + is_rerank: bool = Field(description="是否重新排序", default=False, alias="isRerank") + is_compress: bool = Field(description="是否压缩", default=False, alias="isCompress") + tokens_limit: int = Field(description="token限制", default=8192, alias="tokensLimit") diff --git a/apps/scheduler/call/search/__init__.py b/apps/scheduler/call/search/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cd72c95eaaeb74c6c9778889ee330978b81db4f4 --- /dev/null +++ b/apps/scheduler/call/search/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""搜索工具""" diff --git a/apps/scheduler/call/search/schema.py b/apps/scheduler/call/search/schema.py index 6813b3ae0b99b1178f10ba6eaf0a16663cb6d513..26f8e24a33125575b40824a832a02bc41ba406f3 100644 --- a/apps/scheduler/call/search/schema.py +++ b/apps/scheduler/call/search/schema.py @@ -10,10 +10,10 @@ from apps.scheduler.call.core import DataBase class SearchInput(DataBase): """搜索工具输入""" - query: str = Field(description="搜索关键词") + query: list[str] = Field(description="搜索关键词") -class SearchRet(DataBase): +class SearchOutput(DataBase): """搜索工具返回值""" data: list[dict[str, Any]] = Field(description="搜索结果") diff --git a/apps/scheduler/call/search/search.py b/apps/scheduler/call/search/search.py index 006938fa7cea23c306fe2dc54b8f89bf56b56f17..2b7424d9bccf7dc0f9a997081bebb37d7d457b12 100644 --- a/apps/scheduler/call/search/search.py +++ b/apps/scheduler/call/search/search.py @@ -1,25 +1,33 @@ """搜索工具""" -from typing import Any, ClassVar - -from apps.entities.scheduler import CallVars +from collections.abc import AsyncGenerator +from typing import Any + +from apps.entities.scheduler import ( + CallError, + CallInfo, + CallOutputChunk, + CallVars, +) from apps.scheduler.call.core import CoreCall -from apps.scheduler.call.search.schema import SearchInput, SearchRet +from apps.scheduler.call.search.schema import SearchInput, SearchOutput -class Search(CoreCall, input_model=SearchInput, output_model=SearchRet): +class Search(CoreCall, input_model=SearchInput, output_model=SearchOutput): """搜索工具""" - name: ClassVar[str] = "搜索" - description: ClassVar[str] = "获取搜索引擎的结果" + @classmethod + def info(cls) -> CallInfo: + """返回Call的名称和描述""" + return CallInfo(name="搜索", description="获取搜索引擎的结果") + - async def _init(self, call_vars: CallVars, **kwargs: Any) -> SearchInput: + async def _init(self, call_vars: CallVars) -> SearchInput: """初始化工具""" - self._query: str = kwargs["query"] - return SearchInput(query=self._query) + pass - async def _exec(self) -> dict[str, Any]: + async def _exec(self, input_data: dict[str, Any]) -> AsyncGenerator[CallOutputChunk, None]: """执行工具""" - return {} + pass diff --git a/apps/scheduler/call/slot/__init__.py b/apps/scheduler/call/slot/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..14123dbc9bb326cfbfaa4308c7fc04b44f2c18a1 --- /dev/null +++ b/apps/scheduler/call/slot/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""自动参数填充工具""" diff --git a/apps/scheduler/call/slot/prompt.py b/apps/scheduler/call/slot/prompt.py new file mode 100644 index 0000000000000000000000000000000000000000..e5650a4c5764a4ab739c6b66ad00838597c134a3 --- /dev/null +++ b/apps/scheduler/call/slot/prompt.py @@ -0,0 +1,88 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""自动参数填充工具的提示词""" + +SLOT_GEN_PROMPT = r""" + + 你是一个可以使用工具的AI助手,正尝试使用工具来完成任务。 + 目前,你正在生成一个JSON参数对象,以作为调用工具的输入。 + 请根据用户输入、背景信息、工具信息和JSON Schema内容,生成符合要求的JSON对象。 + + 背景信息将在中给出,工具信息将在中给出,JSON Schema将在中给出,\ + 用户的问题将在中给出。 + 请在中输出生成的JSON对象。 + + 要求: + 1. 严格按照JSON Schema描述的JSON格式输出,不要编造不存在的字段。 + 2. JSON字段的值优先使用用户输入中的内容。如果用户输入没有,则使用背景信息中的内容。 + 3. 只输出JSON对象,不要输出任何解释说明,不要输出任何其他内容。 + 4. 如果JSON Schema中描述的JSON字段是可选的,则可以不输出该字段。 + 5. example中仅为示例,不要照搬example中的内容,不要将example中的内容作为输出。 + + + + + 用户询问杭州今天的天气情况。AI回复杭州今天晴,温度20℃。用户询问杭州明天的天气情况。 + + + 杭州明天的天气情况如何? + + + 工具名称:check_weather + 工具描述:查询指定城市的天气信息 + + + { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "城市名称" + }, + "date": { + "type": "string", + "description": "查询日期" + }, + "required": ["city", "date"] + } + } + + + { + "city": "杭州", + "date": "明天" + } + + + + + 以下是对用户给你的任务的历史总结,在中给出: + + {{summary}} + + 附加的条目化信息: + {{ facts }} + + + 在本次任务中,你已经调用过一些工具,并获得了它们的输出,在中给出: + + {% for tool in history_data %} + + {{ tool.step_name }} + {{ tool.step_description }} + {{ tool.output_data }} + + {% endfor %} + + + + {{question}} + + + 工具名称:{{current_tool["name"]}} + 工具描述:{{current_tool["description"]}} + + + {{schema}} + + + """ diff --git a/apps/scheduler/call/slot/schema.py b/apps/scheduler/call/slot/schema.py index 31fc9abaa4d43c825984ee420a75c80aaa287c19..3d025c5714a5e3e06bcef127bce4ca4b7e1c093c 100644 --- a/apps/scheduler/call/slot/schema.py +++ b/apps/scheduler/call/slot/schema.py @@ -1,96 +1,12 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """参数填充工具的Schema""" + from typing import Any from pydantic import Field from apps.scheduler.call.core import DataBase -SLOT_GEN_PROMPT = r""" - - 你是一个可以使用工具的AI助手,正尝试使用工具来完成任务。 - 目前,你正在生成一个JSON参数对象,以作为调用工具的输入。 - 请根据用户输入、背景信息、工具信息和JSON Schema内容,生成符合要求的JSON对象。 - - 背景信息将在中给出,工具信息将在中给出,JSON Schema将在中给出,\ - 用户的问题将在中给出。 - 请在中输出生成的JSON对象。 - - 要求: - 1. 严格按照JSON Schema描述的JSON格式输出,不要编造不存在的字段。 - 2. JSON字段的值优先使用用户输入中的内容。如果用户输入没有,则使用背景信息中的内容。 - 3. 只输出JSON对象,不要输出任何解释说明,不要输出任何其他内容。 - 4. 如果JSON Schema中描述的JSON字段是可选的,则可以不输出该字段。 - 5. example中仅为示例,不要照搬example中的内容,不要将example中的内容作为输出。 - - - - - 用户询问杭州今天的天气情况。AI回复杭州今天晴,温度20℃。用户询问杭州明天的天气情况。 - - - 杭州明天的天气情况如何? - - - 工具名称:check_weather - 工具描述:查询指定城市的天气信息 - - - { - "type": "object", - "properties": { - "city": { - "type": "string", - "description": "城市名称" - }, - "date": { - "type": "string", - "description": "查询日期" - }, - "required": ["city", "date"] - } - } - - - { - "city": "杭州", - "date": "明天" - } - - - - - 以下是对用户给你的任务的历史总结,在中给出: - - {{summary}} - - 附加的条目化信息: - {{ facts }} - - - 在本次任务中,你已经调用过一些工具,并获得了它们的输出,在中给出: - - {% for tool in history_data %} - - {{ tool.step_name }} - {{ tool.step_description }} - {{ tool.output_data }} - - {% endfor %} - - - - {{question}} - - - 工具名称:{{current_tool["name"]}} - 工具描述:{{current_tool["description"]}} - - - {{schema}} - - - """ - class SlotInput(DataBase): """参数填充工具的输入""" diff --git a/apps/scheduler/call/slot/slot.py b/apps/scheduler/call/slot/slot.py index b197205e681109fa12ffd8c7da9097d84ce2c3c2..adcd1f603ba00087d14a2a9cb771a36896f9dc1b 100644 --- a/apps/scheduler/call/slot/slot.py +++ b/apps/scheduler/call/slot/slot.py @@ -1,5 +1,7 @@ -"""参数填充工具""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""自动参数填充工具""" +import json from collections.abc import AsyncGenerator from typing import TYPE_CHECKING, Any, Self @@ -10,10 +12,11 @@ from pydantic import Field from apps.entities.enum_var import CallOutputType from apps.entities.pool import NodePool from apps.entities.scheduler import CallInfo, CallOutputChunk, CallVars -from apps.llm.patterns.json_gen import Json +from apps.llm.function import FunctionLLM, JsonGenerator from apps.llm.reasoning import ReasoningLLM from apps.scheduler.call.core import CoreCall -from apps.scheduler.call.slot.schema import SLOT_GEN_PROMPT, SlotInput, SlotOutput +from apps.scheduler.call.slot.prompt import SLOT_GEN_PROMPT +from apps.scheduler.call.slot.schema import SlotInput, SlotOutput from apps.scheduler.slot.slot import Slot as SlotProcessor if TYPE_CHECKING: @@ -36,8 +39,8 @@ class Slot(CoreCall, input_model=SlotInput, output_model=SlotOutput): return CallInfo(name="参数自动填充", description="根据步骤历史,自动填充参数") - async def _llm_slot_fill(self, remaining_schema: dict[str, Any]) -> dict[str, Any]: - """使用LLM填充参数""" + async def _llm_slot_fill(self, remaining_schema: dict[str, Any]) -> tuple[str, dict[str, Any]]: + """使用大模型填充参数;若大模型解析度足够,则直接返回结果""" env = SandboxedEnvironment( loader=BaseLoader(), autoescape=False, @@ -47,7 +50,7 @@ class Slot(CoreCall, input_model=SlotInput, output_model=SlotOutput): template = env.from_string(SLOT_GEN_PROMPT) conversation = [ - {"role": "system", "content": ""}, + {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": template.render( current_tool={ "name": self.name, @@ -69,19 +72,25 @@ class Slot(CoreCall, input_model=SlotInput, output_model=SlotOutput): self.tokens.input_tokens += reasoning.input_tokens self.tokens.output_tokens += reasoning.output_tokens - conversation = [ - {"role": "user", "content": f""" - 用户问题:{self._question} + answer = await FunctionLLM.process_response(answer) + try: + data = json.loads(answer) + except Exception: # noqa: BLE001 + data = {} + return answer, data - 参考数据: - {answer} - """, - }, + async def _function_slot_fill(self, answer: str, remaining_schema: dict[str, Any]) -> dict[str, Any]: + """使用FunctionCall填充参数""" + conversation = [ + {"role": "user", "content": self._question}, + {"role": "assistant", "content": answer}, ] - return await Json().generate( + json_gen = JsonGenerator( + query=self._question, conversation=conversation, - spec=remaining_schema, + schema=remaining_schema, ) + return await json_gen.generate() @classmethod async def instance(cls, executor: "StepExecutor", node: NodePool | None, **kwargs: Any) -> Self: @@ -132,8 +141,14 @@ class Slot(CoreCall, input_model=SlotInput, output_model=SlotOutput): ).model_dump(by_alias=True, exclude_none=True), ) return - slot_data = await self._llm_slot_fill(data.remaining_schema) + answer, slot_data = await self._llm_slot_fill(data.remaining_schema) + slot_data = self._processor.convert_json(slot_data) + remaining_schema = self._processor.check_json(slot_data) + if remaining_schema: + slot_data = await self._function_slot_fill(answer, remaining_schema) + slot_data = self._processor.convert_json(slot_data) + remaining_schema = self._processor.check_json(slot_data) # 再次检查 remaining_schema = self._processor.check_json(slot_data) diff --git a/apps/scheduler/call/sql/__init__.py b/apps/scheduler/call/sql/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6e51b00d6f544a21dbb909d82e6b1ba0934d6c95 --- /dev/null +++ b/apps/scheduler/call/sql/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""SQL工具""" diff --git a/apps/scheduler/call/sql/schema.py b/apps/scheduler/call/sql/schema.py index 66c5945e2338a2527954a2796d3a19f0dbc64c2e..06ffb4f0611bb12a3d6080a166c2bead52d24e10 100644 --- a/apps/scheduler/call/sql/schema.py +++ b/apps/scheduler/call/sql/schema.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """SQL工具的输入输出""" from typing import Any diff --git a/apps/scheduler/call/sql/sql.py b/apps/scheduler/call/sql/sql.py index 2d4a973a87d1353c7917333321fc737ed9c7339a..d8ba659c93a0ef363853ea9ef472b2fd565c326b 100644 --- a/apps/scheduler/call/sql/sql.py +++ b/apps/scheduler/call/sql/sql.py @@ -1,15 +1,11 @@ -""" -SQL工具。 - -用于调用外置的Chat2DB工具的API,获得SQL语句;再在PostgreSQL中执行SQL语句,获得数据。 -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""SQL工具""" import logging from collections.abc import AsyncGenerator from typing import Any -import aiohttp +import httpx from fastapi import status from pydantic import Field @@ -66,19 +62,19 @@ class SQL(CoreCall, input_model=SQLInput, output_model=SQLOutput): while retry < max_retry and len(sql_list) < self.top_k: try: - async with aiohttp.ClientSession() as session, session.post( - Config().get_config().extra.sql_url + "/database/sql", - headers=headers, - json=post_data, - timeout=aiohttp.ClientTimeout(total=60), - ) as response: - if response.status == status.HTTP_200_OK: - result = await response.json() + async with httpx.AsyncClient() as client: + response = await client.post( + Config().get_config().extra.sql_url + "/database/sql", + headers=headers, + json=post_data, + timeout=60.0, + ) + if response.status_code == status.HTTP_200_OK: + result = response.json() if result["code"] == status.HTTP_200_OK: sql_list.extend(result["result"]["sql_list"]) else: - text = await response.text() - logger.error("[SQL] 生成失败:%s", text) + logger.error("[SQL] 生成失败:%s", response.text) retry += 1 except Exception: logger.exception("[SQL] 生成失败") @@ -96,22 +92,22 @@ class SQL(CoreCall, input_model=SQLInput, output_model=SQLOutput): for sql_dict in sql_list: try: - async with aiohttp.ClientSession() as session, session.post( - Config().get_config().extra.sql_url + "/sql/execute", - headers=headers, - json={ - "database_id": sql_dict["database_id"], - "sql": sql_dict["sql"], - }, - timeout=aiohttp.ClientTimeout(total=60), - ) as response: - if response.status == status.HTTP_200_OK: - result = await response.json() + async with httpx.AsyncClient() as client: + response = await client.post( + Config().get_config().extra.sql_url + "/sql/execute", + headers=headers, + json={ + "database_id": sql_dict["database_id"], + "sql": sql_dict["sql"], + }, + timeout=60.0, + ) + if response.status_code == status.HTTP_200_OK: + result = response.json() if result["code"] == status.HTTP_200_OK: return result["result"], sql_dict["sql"] else: - text = await response.text() - logger.error("[SQL] 调用失败:%s", text) + logger.error("[SQL] 调用失败:%s", response.text) except Exception: logger.exception("[SQL] 调用失败") diff --git a/apps/scheduler/call/suggest/__init__.py b/apps/scheduler/call/suggest/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..515e78c240f14550166d0861113d8427cda09717 --- /dev/null +++ b/apps/scheduler/call/suggest/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""问题推荐工具""" diff --git a/apps/scheduler/call/suggest/schema.py b/apps/scheduler/call/suggest/schema.py index d744107045f240fd7841d16f9aabcab6b41d91b0..72441a075f2b97e150f10e3c0dea18b450a0518f 100644 --- a/apps/scheduler/call/suggest/schema.py +++ b/apps/scheduler/call/suggest/schema.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """问题推荐工具的输入输出""" from pydantic import BaseModel, Field diff --git a/apps/scheduler/call/suggest/suggest.py b/apps/scheduler/call/suggest/suggest.py index 5df8adbed74eb9b7c8ccf7fcb4af1e9eaa0cd10b..f3b3ff168b71d76fefd7a0132fd97d60cf8aeb3f 100644 --- a/apps/scheduler/call/suggest/suggest.py +++ b/apps/scheduler/call/suggest/suggest.py @@ -1,8 +1,5 @@ -""" -用于问题推荐的工具 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""用于问题推荐的工具""" import random from collections.abc import AsyncGenerator diff --git a/apps/scheduler/call/summary/__init__.py b/apps/scheduler/call/summary/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..500ac09f9010bb12231dde3d1c2bdd533690ff1a --- /dev/null +++ b/apps/scheduler/call/summary/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""上下文理解工具""" diff --git a/apps/scheduler/call/summary/schema.py b/apps/scheduler/call/summary/schema.py index 98fb3d36fdeb66e88c71bd681e50709ca128df2b..ccefa0c896e70254f608cfd7606b2d4305c1ba47 100644 --- a/apps/scheduler/call/summary/schema.py +++ b/apps/scheduler/call/summary/schema.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """总结工具的输入和输出""" from pydantic import Field diff --git a/apps/scheduler/call/summary/summary.py b/apps/scheduler/call/summary/summary.py index ff6792f029fec37367f557b536ad1efad94477c2..35fd628e7b357d1246a7436bdc3e0046ed4f75e9 100644 --- a/apps/scheduler/call/summary/summary.py +++ b/apps/scheduler/call/summary/summary.py @@ -1,8 +1,5 @@ -""" -总结上下文工具 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""总结上下文工具""" from collections.abc import AsyncGenerator from typing import TYPE_CHECKING, Any, Self diff --git a/apps/scheduler/executor/__init__.py b/apps/scheduler/executor/__init__.py index 14bf0bac2b78b221a698fbb5b1d20f99c9127680..2e31bb1df8c8f8c411ac2b66abda789947aa4dc6 100644 --- a/apps/scheduler/executor/__init__.py +++ b/apps/scheduler/executor/__init__.py @@ -1,4 +1,2 @@ -"""Executor模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Executor模块""" diff --git a/apps/scheduler/executor/agent.py b/apps/scheduler/executor/agent.py index 937393089d6a774558098c662a9f38a1bab4beba..01da4b929e6ef9b76e6219c9cc8598d96c22aeba 100644 --- a/apps/scheduler/executor/agent.py +++ b/apps/scheduler/executor/agent.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """MCP Agent执行器""" diff --git a/apps/scheduler/executor/base.py b/apps/scheduler/executor/base.py index 9df3b0a97f52fe1b1eb67abca76e9f7bd993d762..1edc8ac573f6feb61999b5bb8e162fc1d4440e9d 100644 --- a/apps/scheduler/executor/base.py +++ b/apps/scheduler/executor/base.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """Executor基类""" import logging diff --git a/apps/scheduler/executor/flow.py b/apps/scheduler/executor/flow.py index 7fa8ddfeaa8a2b1a1f8fc54fb27b09379b4542f2..3971e5df8460a5af5337b4eb149f4f9a29139a61 100644 --- a/apps/scheduler/executor/flow.py +++ b/apps/scheduler/executor/flow.py @@ -1,8 +1,5 @@ -""" -Flow执行Executor - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Flow执行Executor""" import logging import uuid @@ -16,7 +13,7 @@ from apps.entities.flow import Flow, Step from apps.entities.request_data import RequestDataApp from apps.entities.task import ExecutorState, StepQueueItem from apps.manager.task import TaskManager -from apps.scheduler.call.llm.schema import LLM_ERROR_PROMPT +from apps.scheduler.call.llm.prompt import LLM_ERROR_PROMPT from apps.scheduler.executor.base import BaseExecutor from apps.scheduler.executor.step import StepExecutor @@ -39,16 +36,6 @@ FIXED_STEPS_AFTER_END = [ type=SpecialCallType.FACTS.value, ), ] -# 错误处理步骤 -ERROR_STEP = Step( - name="错误处理", - description="错误处理", - node=SpecialCallType.LLM.value, - type=SpecialCallType.LLM.value, - params={ - "user_prompt": LLM_ERROR_PROMPT, - }, -) # 单个流的执行工具 @@ -190,9 +177,20 @@ class FlowExecutor(BaseExecutor): self.step_queue.clear() self.step_queue.appendleft(StepQueueItem( step_id=str(uuid.uuid4()), - step=ERROR_STEP, + step=Step( + name="错误处理", + description="错误处理", + node=SpecialCallType.LLM.value, + type=SpecialCallType.LLM.value, + params={ + "user_prompt": LLM_ERROR_PROMPT.replace( + "{{ error_info }}", + self.task.state.error_info["err_msg"], # type: ignore[arg-type] + ), + }, + ), enable_filling=False, - to_user=True, + to_user=False, )) # 错误处理后结束 self._reached_end = True diff --git a/apps/scheduler/executor/node.py b/apps/scheduler/executor/node.py index 7584dce4f59c24f47ec4ef9e4abca1dd1840e38b..387c9c0cba7ad125e4239d1d67d5c802f1d0cbaf 100644 --- a/apps/scheduler/executor/node.py +++ b/apps/scheduler/executor/node.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """Node相关的函数,含Node转换为Call""" import inspect diff --git a/apps/scheduler/executor/step.py b/apps/scheduler/executor/step.py index 7deacdb10670795d38db70bd47bf5a70dfcbe8c9..a8c7d3237de92ae934eb666be0f5e26e0d9a05c5 100644 --- a/apps/scheduler/executor/step.py +++ b/apps/scheduler/executor/step.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """工作流中步骤相关函数""" import logging @@ -13,7 +14,7 @@ from apps.entities.enum_var import ( StepStatus, ) from apps.entities.message import TextAddContent -from apps.entities.scheduler import CallOutputChunk +from apps.entities.scheduler import CallError, CallOutputChunk from apps.entities.task import FlowStepHistory, StepQueueItem from apps.manager.node import NodeManager from apps.scheduler.call.slot.schema import SlotOutput @@ -180,10 +181,20 @@ class StepExecutor(BaseExecutor): try: content = await self._process_chunk(iterator, to_user=self.obj.to_user) - except Exception: - logger.exception("[StepExecutor] 运行步骤失败") + except Exception as e: # noqa: BLE001 + logger.error("[StepExecutor] 运行步骤失败: %s", e) # noqa: TRY400 self.task.state.status = StepStatus.ERROR # type: ignore[arg-type] await self.push_message(EventType.STEP_OUTPUT.value, {}) + if isinstance(e, CallError): + self.task.state.error_info = { # type: ignore[arg-type] + "err_msg": e.message, + "data": e.data, + } + else: + self.task.state.error_info = { # type: ignore[arg-type] + "err_msg": str(e), + "data": {}, + } return # 更新执行状态 diff --git a/apps/scheduler/json_schema.py b/apps/scheduler/json_schema.py deleted file mode 100644 index 79a4e3b78c69d83732a645477bd03f426e45acca..0000000000000000000000000000000000000000 --- a/apps/scheduler/json_schema.py +++ /dev/null @@ -1,429 +0,0 @@ -""" -JSON Schema转为正则表达式 - -来源:https://github.com/dottxt-ai/outlines/blob/main/outlines/fsm/json_schema.py -""" - -import json -import re -from typing import Any - -from jsonschema.protocols import Validator -from pydantic import BaseModel -from referencing import Registry, Resource -from referencing._core import Resolver -from referencing.jsonschema import DRAFT202012 - -# allow `\"`, `\\`, or any character which isn't a control sequence -STRING_INNER = r'([^"\\\x00-\x1F\x7F-\x9F]|\\["\\])' -STRING = f'"{STRING_INNER}*"' - -INTEGER = r"(-)?(0|[1-9][0-9]*)" -NUMBER = rf"({INTEGER})(\.[0-9]+)?([eE][+-][0-9]+)?" -BOOLEAN = r"(true|false)" -NULL = r"null" -WHITESPACE = r"[ ]?" - -type_to_regex = { - "string": STRING, - "integer": INTEGER, - "number": NUMBER, - "boolean": BOOLEAN, - "null": NULL, -} - -DATE_TIME = ( - r'"(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]{3})?(Z)?"' -) -DATE = r'"(?:\d{4})-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])"' -TIME = r'"(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\\.[0-9]+)?(Z)?"' -UUID = r'"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"' - -format_to_regex = { - "uuid": UUID, - "date-time": DATE_TIME, - "date": DATE, - "time": TIME, -} - - -def build_regex_from_schema(schema: str, whitespace_pattern: str | None = None) -> str: - """将JSON Schema转换为正则表达式""" - schema_dict: dict[str, Any] = json.loads(schema) - Validator.check_schema(schema_dict) - - # Build reference resolver - schema_resource = Resource(contents=schema_dict, specification=DRAFT202012) - uri = schema_resource.id() if schema_resource.id() is not None else "" - if not uri: - err = "schema_resource.id() is None" - raise ValueError(err) - registry = Registry().with_resource(uri=uri, resource=schema_resource) # type: ignore[arg-type] - resolver = registry.resolver() - - content = schema_resource.contents - return to_regex(resolver, content, whitespace_pattern) # type: ignore[arg-type] - - -def convert_json_schema_to_str(json_schema: dict | str | type[BaseModel]) -> str: - """将JSON Schema转换为字符串""" - if isinstance(json_schema, dict): - schema_str = json.dumps(json_schema) - elif isinstance(json_schema, str): - schema_str = json_schema - elif issubclass(json_schema, BaseModel): - schema_str = json.dumps(json_schema.model_json_schema()) - - return schema_str - - -def _get_num_items_pattern(min_items: int, max_items: int | None) -> str | None: - """用于数组和对象的辅助函数""" - min_items = int(min_items or 0) - if max_items is None: - return rf"{{{max(min_items - 1, 0)},}}" - - max_items = int(max_items) - if max_items < 1: - return None - return rf"{{{max(min_items - 1, 0)},{max_items - 1}}}" - - -def validate_quantifiers( - min_bound: str | None, - max_bound: str | None, - start_offset: int = 0, -) -> tuple[str, str]: - """确保数字的边界有效。边界用于正则表达式中的量化器""" - min_bound = "" if min_bound is None else str(int(min_bound) - start_offset) - max_bound = "" if max_bound is None else str(int(max_bound) - start_offset) - if min_bound and max_bound and int(max_bound) < int(min_bound): - err = "max bound must be greater than or equal to min bound" - raise ValueError(err) - return min_bound, max_bound - - -def to_regex( # noqa: C901, PLR0911, PLR0912, PLR0915 - resolver: Resolver, - instance: dict, - whitespace_pattern: str | None = None, -) -> str: - """将 JSON Schema 实例转换为对应的正则表达式""" - # set whitespace pattern - if whitespace_pattern is None: - whitespace_pattern = WHITESPACE - - if instance == {}: - # JSON Schema Spec: Empty object means unconstrained, any json type is legal - types = [ - {"type": "boolean"}, - {"type": "null"}, - {"type": "number"}, - {"type": "integer"}, - {"type": "string"}, - {"type": "array"}, - {"type": "object"}, - ] - regexes = [to_regex(resolver, t, whitespace_pattern) for t in types] - regexes = [rf"({r})" for r in regexes] - return rf"{'|'.join(regexes)}" - - if "properties" in instance: - regex = "" - regex += r"\{" - properties = instance["properties"] - required_properties = instance.get("required", []) - is_required = [item in required_properties for item in properties] - # If at least one property is required, we include the one in the lastest position - # without any comma. - # For each property before it (optional or required), we add with a comma after the property. - # For each property after it (optional), we add with a comma before the property. - if any(is_required): - last_required_pos = max([i for i, value in enumerate(is_required) if value]) - for i, (name, value) in enumerate(properties.items()): - subregex = f'{whitespace_pattern}"{re.escape(name)}"{whitespace_pattern}:{whitespace_pattern}' - subregex += to_regex(resolver, value, whitespace_pattern) - if i < last_required_pos: - subregex = f"{subregex}{whitespace_pattern}," - elif i > last_required_pos: - subregex = f"{whitespace_pattern},{subregex}" - regex += subregex if is_required[i] else f"({subregex})?" - # If no property is required, we have to create a possible pattern for each property in which - # it's the last one necessarilly present. Then, we add the others as optional before and after - # following the same strategy as described above. - # The whole block is made optional to allow the case in which no property is returned. - else: - property_subregexes = [] - for _, (name, value) in enumerate(properties.items()): - subregex = f'{whitespace_pattern}"{name}"{whitespace_pattern}:{whitespace_pattern}' - subregex += to_regex(resolver, value, whitespace_pattern) - property_subregexes.append(subregex) - possible_patterns = [] - for i in range(len(property_subregexes)): - pattern = "" - for subregex in property_subregexes[:i]: - pattern += f"({subregex}{whitespace_pattern},)?" - pattern += property_subregexes[i] - for subregex in property_subregexes[i + 1 :]: - pattern += f"({whitespace_pattern},{subregex})?" - possible_patterns.append(pattern) - regex += f"({'|'.join(possible_patterns)})?" - - regex += f"{whitespace_pattern}" + r"\}" - - return regex - - # To validate against allOf, the given data must be valid against all of the - # given subschemas. - if "allOf" in instance: - subregexes = [to_regex(resolver, t, whitespace_pattern) for t in instance["allOf"]] - subregexes_str = [f"{subregex}" for subregex in subregexes] - return rf"({''.join(subregexes_str)})" - - # To validate against `anyOf`, the given data must be valid against - # any (one or more) of the given subschemas. - if "anyOf" in instance: - subregexes = [to_regex(resolver, t, whitespace_pattern) for t in instance["anyOf"]] - return rf"({'|'.join(subregexes)})" - - # To validate against oneOf, the given data must be valid against exactly - # one of the given subschemas. - if "oneOf" in instance: - subregexes = [to_regex(resolver, t, whitespace_pattern) for t in instance["oneOf"]] - - xor_patterns = [f"(?:{subregex})" for subregex in subregexes] - - return rf"({'|'.join(xor_patterns)})" - - # Create pattern for tuples, per JSON Schema spec, `prefixItems` determines types at each idx - if "prefixItems" in instance: - element_patterns = [to_regex(resolver, t, whitespace_pattern) for t in instance["prefixItems"]] - comma_split_pattern = rf"{whitespace_pattern},{whitespace_pattern}" - tuple_inner = comma_split_pattern.join(element_patterns) - return rf"\[{whitespace_pattern}{tuple_inner}{whitespace_pattern}\]" - - # The enum keyword is used to restrict a value to a fixed set of values. It - # must be an array with at least one element, where each element is unique. - if "enum" in instance: - choices = [] - for choice in instance["enum"]: - if type(choice) in [int, float, bool, type(None), str]: - choices.append(re.escape(json.dumps(choice))) - elif isinstance(choice, dict): - choices.append(to_regex(resolver, choice, whitespace_pattern)) - else: - err = f"Unsupported data type in enum: {type(choice)}" - raise TypeError(err) - return f"({'|'.join(choices)})" - - if "const" in instance: - const = instance["const"] - if type(const) in [int, float, bool, type(None), str]: - const = re.escape(json.dumps(const)) - else: - err = f"Unsupported data type in const: {type(const)}" - raise TypeError(err) - return const - - if "$ref" in instance: - path = f"{instance['$ref']}" - instance = resolver.lookup(path).contents - return to_regex(resolver, instance, whitespace_pattern) - - # The type keyword may either be a string or an array: - # - If it's a string, it is the name of one of the basic types. - # - If it is an array, it must be an array of strings, where each string is - # the name of one of the basic types, and each element is unique. In this - # case, the JSON snippet is valid if it matches any of the given types. - if "type" in instance: - instance_type = instance["type"] - if instance_type == "string": - if "maxLength" in instance or "minLength" in instance: - max_items = instance.get("maxLength", "") - min_items = instance.get("minLength", "") - try: - if int(max_items) < int(min_items): - err = "maxLength must be greater than or equal to minLength" - raise ValueError(err) # noqa: TRY301 - except ValueError: - pass - return f'"{STRING_INNER}{{{min_items},{max_items}}}"' - if "pattern" in instance: - pattern = instance["pattern"] - if pattern[0] == "^" and pattern[-1] == "$": - return rf'("{pattern[1:-1]}")' - return rf'("{pattern}")' - if "format" in instance: - format = instance["format"] # noqa: A001 - if format == "date-time": - return format_to_regex["date-time"] - if format == "uuid": - return format_to_regex["uuid"] - if format == "date": - return format_to_regex["date"] - if format == "time": - return format_to_regex["time"] - - err = f"Format {format} is not supported." - raise NotImplementedError(err) - return type_to_regex["string"] - - if instance_type == "number": - bounds = { - "minDigitsInteger", - "maxDigitsInteger", - "minDigitsFraction", - "maxDigitsFraction", - "minDigitsExponent", - "maxDigitsExponent", - } - if bounds.intersection(set(instance.keys())): - min_digits_integer, max_digits_integer = validate_quantifiers( - instance.get("minDigitsInteger"), - instance.get("maxDigitsInteger"), - start_offset=1, - ) - min_digits_fraction, max_digits_fraction = validate_quantifiers( - instance.get("minDigitsFraction"), - instance.get("maxDigitsFraction"), - ) - min_digits_exponent, max_digits_exponent = validate_quantifiers( - instance.get("minDigitsExponent"), - instance.get("maxDigitsExponent"), - ) - integers_quantifier = ( - f"{{{min_digits_integer},{max_digits_integer}}}" - if min_digits_integer or max_digits_integer - else "*" - ) - fraction_quantifier = ( - f"{{{min_digits_fraction},{max_digits_fraction}}}" - if min_digits_fraction or max_digits_fraction - else "+" - ) - exponent_quantifier = ( - f"{{{min_digits_exponent},{max_digits_exponent}}}" - if min_digits_exponent or max_digits_exponent - else "+" - ) - return ( - rf"((-)?(0|[1-9][0-9]{integers_quantifier}))" - rf"(\.[0-9]{fraction_quantifier})?" - rf"([eE][+-][0-9]{exponent_quantifier})?" - ) - return type_to_regex["number"] - - if instance_type == "integer": - if "minDigits" in instance or "maxDigits" in instance: - min_digits, max_digits = validate_quantifiers( - instance.get("minDigits"), - instance.get("maxDigits"), - start_offset=1, - ) - return rf"(-)?(0|[1-9][0-9]{{{min_digits},{max_digits}}})" - return type_to_regex["integer"] - - if instance_type == "array": - num_repeats = _get_num_items_pattern( - instance["minItems"], - instance["maxItems"], - ) - if num_repeats is None: - return rf"\[{whitespace_pattern}\]" - - allow_empty = "?" if int(instance["minItems"]) == 0 else "" - - if "items" in instance: - items_regex = to_regex(resolver, instance["items"], whitespace_pattern) - return ( - rf"\[{whitespace_pattern}(({items_regex})" - rf",{whitespace_pattern}({items_regex})){num_repeats}){allow_empty}{whitespace_pattern}\]" - ) - - # Here we need to make the choice to exclude generating list of objects - # if the specification of the object is not given, even though a JSON - # object that contains an object here would be valid under the specification. - legal_types = [ - {"type": "boolean"}, - {"type": "null"}, - {"type": "number"}, - {"type": "integer"}, - {"type": "string"}, - ] - depth = instance.get("depth", 2) - if depth > 0: - legal_types.append({"type": "object", "depth": depth - 1}) - legal_types.append({"type": "array", "depth": depth - 1}) - - regexes = [to_regex(resolver, t, whitespace_pattern) for t in legal_types] - return ( - rf"\[{whitespace_pattern}({'|'.join(regexes)})" - rf",{whitespace_pattern}({'|'.join(regexes)})){num_repeats}{allow_empty}{whitespace_pattern}\]" - ) - - if instance_type == "object": - # pattern for json object with values defined by instance["additionalProperties"] - # enforces value type constraints recursively, "minProperties", and "maxProperties" - # doesn't enforce "required", "dependencies", "propertyNames" "any/all/on Of" - num_repeats = _get_num_items_pattern( - instance["minProperties"], - instance["maxProperties"], - ) - if num_repeats is None: - return rf"\{{{whitespace_pattern}\}}" - - allow_empty = "?" if int(instance["minProperties"]) == 0 else "" - - additional_properties = instance["additionalProperties"] - - if additional_properties is None or additional_properties is True: - # JSON Schema behavior: If the additionalProperties of an object is - # unset or True, it is unconstrained object. - # We handle this by setting additionalProperties to anyOf: {all types} - - legal_types = [ - {"type": "string"}, - {"type": "number"}, - {"type": "boolean"}, - {"type": "null"}, - ] - - # We set the object depth to 2 to keep the expression finite, but the "depth" - # key is not a true component of the JSON Schema specification. - depth = instance.get("depth", 2) - if depth > 0: - legal_types.append({"type": "object", "depth": depth - 1}) - legal_types.append({"type": "array", "depth": depth - 1}) - additional_properties = {"anyOf": legal_types} - - value_pattern = to_regex( - resolver, - additional_properties, - whitespace_pattern, - ) - key_value_pattern = f"{STRING}{whitespace_pattern}:{whitespace_pattern}{value_pattern}" - key_value_successor_pattern = f"{whitespace_pattern},{whitespace_pattern}{key_value_pattern}" - multiple_key_value_pattern = ( - f"({key_value_pattern}({key_value_successor_pattern}){num_repeats}){allow_empty}" - ) - - return r"\{" + whitespace_pattern + multiple_key_value_pattern + whitespace_pattern + r"\}" - - if instance_type == "boolean": - return type_to_regex["boolean"] - - if instance_type == "null": - return type_to_regex["null"] - - if isinstance(instance_type, list): - # Here we need to make the choice to exclude generating an object - # if the specification of the object is not give, even though a JSON - # object that contains an object here would be valid under the specification. - regexes = [to_regex(resolver, {"type": t}, whitespace_pattern) for t in instance_type if t != "object"] - return rf"({'|'.join(regexes)})" - - # 以上都没有匹配到,则抛出错误 - err = f"""Could not translate the instance {instance} to a - regular expression. Make sure it is valid to the JSON Schema specification. If - it is, please open an issue on the Outlines repository""" - raise NotImplementedError(err) diff --git a/apps/scheduler/mcp/__init__.py b/apps/scheduler/mcp/__init__.py index cd9a3855db1c97f47bcfc6eb95dca750d830446d..12f5cb68c12e4d19d830a8155eaeb0851fce897d 100644 --- a/apps/scheduler/mcp/__init__.py +++ b/apps/scheduler/mcp/__init__.py @@ -1,5 +1,8 @@ -""" -Scheduler MCP 模块 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Scheduler MCP 模块""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +from apps.scheduler.mcp.host import MCPHost +from apps.scheduler.mcp.plan import MCPPlanner +from apps.scheduler.mcp.select import MCPSelector + +__all__ = ["MCPHost", "MCPPlanner", "MCPSelector"] diff --git a/apps/scheduler/mcp/host.py b/apps/scheduler/mcp/host.py index 17c38bbe2ca2e45139806cfd4846f8ae94a81804..0d0e794b6bd105026b9cc6b5e8f11ae3bbf4d071 100644 --- a/apps/scheduler/mcp/host.py +++ b/apps/scheduler/mcp/host.py @@ -1,7 +1,193 @@ -"""MCP Host""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MCP宿主""" + +import json +import logging +from typing import Any + +from jinja2 import BaseLoader +from jinja2.sandbox import SandboxedEnvironment +from mcp.types import TextContent + +from apps.entities.enum_var import StepStatus +from apps.entities.mcp import MCPPlanItem, MCPTool +from apps.entities.task import FlowStepHistory +from apps.llm.function import JsonGenerator +from apps.manager.task import TaskManager +from apps.models.mongo import MongoDB +from apps.scheduler.mcp.prompt import MEMORY_TEMPLATE +from apps.scheduler.pool.mcp.client import ( + SSEMCPClient, + StdioMCPClient, +) +from apps.scheduler.pool.mcp.pool import MCPPool + +logger = logging.getLogger(__name__) class MCPHost: - """MCP Agent Host""" + """MCP宿主服务""" + + def __init__(self, user_sub: str, task_id: str, runtime_id: str, runtime_name: str) -> None: + """初始化MCP宿主""" + self._user_sub = user_sub + self._task_id = task_id + # 注意:runtime在工作流中是flow_id和step_description,在Agent中可为标识Agent的id和description + self._runtime_id = runtime_id + self._runtime_name = runtime_name + self._context_list = [] + self._env = SandboxedEnvironment( + loader=BaseLoader(), + autoescape=False, + trim_blocks=True, + lstrip_blocks=True, + ) + + + async def get_client(self, mcp_id: str) -> SSEMCPClient | StdioMCPClient | None: + """获取MCP客户端""" + mongo = MongoDB() + mcp_collection = mongo.get_collection("mcp") + + # 检查用户是否启用了这个mcp + mcp_db_result = await mcp_collection.find_one({"mcp_id": mcp_id, "activated": self._user_sub}) + if not mcp_db_result: + logger.warning("用户 %s 未启用MCP %s", self._user_sub, mcp_id) + return None + + # 获取MCP配置 + try: + return await MCPPool().get(self._user_sub, mcp_id) + except KeyError: + logger.warning("用户 %s 的MCP %s 没有运行中的实例,请检查环境", self._user_sub, mcp_id) + return None + + + async def assemble_memory(self) -> str: + """组装记忆""" + task = await TaskManager.get_task_by_task_id(self._task_id) + if not task: + logger.error("任务 %s 不存在", self._task_id) + return "" + + context_list = [] + for ctx_id in self._context_list: + context = next((ctx for ctx in task.context if ctx["_id"] == ctx_id), None) + if not context: + continue + context_list.append(context) + + memory = self._env.from_string(MEMORY_TEMPLATE).render( + context_list=context_list, + ) + return memory + + + async def _save_memory( + self, + tool: MCPTool, + plan_item: MCPPlanItem, + input_data: dict[str, Any], + result: str, + ) -> dict[str, Any]: + """保存记忆""" + try: + output_data = json.loads(result) + except Exception: + output_data = { + "message": result, + } + + # 创建context;注意用法 + context = FlowStepHistory( + task_id=self._task_id, + flow_id=self._runtime_id, + flow_name=self._runtime_name, + step_id=tool.name, + step_name=tool.name, + # description是规划的实际内容 + step_description=plan_item.plan, + status=StepStatus.SUCCESS, + input_data=input_data, + output_data=output_data, + ) + + # 保存到task + task = await TaskManager.get_task_by_task_id(self._task_id) + if not task: + logger.error("任务 %s 不存在", self._task_id) + return {} + self._context_list.append(context.id) + task.context.append(context.model_dump(by_alias=True, exclude_none=True)) + await TaskManager.save_task(self._task_id, task) + + return output_data + + + async def _fill_params(self, tool: MCPTool, query: str) -> dict[str, Any]: + """填充工具参数""" + # 更清晰的输入·指令,这样可以调用generate + llm_query = rf""" + 请使用参数生成工具,生成满足以下目标的工具参数: + + {query} + """ + + # 进行生成 + json_generator = JsonGenerator( + llm_query, + [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": await self.assemble_memory()}, + ], + tool.input_schema, + ) + return await json_generator.generate() + + + async def call_tool(self, tool: MCPTool, plan_item: MCPPlanItem) -> list[dict[str, Any]]: + """调用工具""" + # 拿到Client + client = await MCPPool().get(tool.mcp_id, self._user_sub) + if client is None: + err = f"[MCPHost] MCP Server不合法: {tool.mcp_id}" + logger.error(err) + raise ValueError(err) + + # 填充参数 + params = await self._fill_params(tool, plan_item.instruction) + # 调用工具 + result = await client.call_tool(tool.name, params) + # 保存记忆 + processed_result = [] + for item in result.content: + if not isinstance(item, TextContent): + logger.error("MCP结果类型不支持: %s", item) + continue + processed_result.append(await self._save_memory(tool, plan_item, params, item.text)) + + return processed_result + + + async def get_tool_list(self, mcp_id_list: list[str]) -> list[MCPTool]: + """获取工具列表""" + mongo = MongoDB() + mcp_collection = mongo.get_collection("mcp") + # 获取工具列表 + tool_list = [] + for mcp_id in mcp_id_list: + # 检查用户是否启用了这个mcp + mcp_db_result = await mcp_collection.find_one({"_id": mcp_id, "activated": self._user_sub}) + if not mcp_db_result: + logger.warning("用户 %s 未启用MCP %s", self._user_sub, mcp_id) + continue + # 获取MCP工具配置 + try: + for tool in mcp_db_result["tools"]: + tool_list.extend([MCPTool.model_validate(tool)]) + except KeyError: + logger.warning("用户 %s 的MCP Tool %s 配置错误", self._user_sub, mcp_id) + continue + return tool_list diff --git a/apps/scheduler/mcp/plan.py b/apps/scheduler/mcp/plan.py new file mode 100644 index 0000000000000000000000000000000000000000..a76a047c2cdfc8082f68fae1110211eddc0dc85d --- /dev/null +++ b/apps/scheduler/mcp/plan.py @@ -0,0 +1,110 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MCP 用户目标拆解与规划""" + +from jinja2 import BaseLoader +from jinja2.sandbox import SandboxedEnvironment + +from apps.entities.mcp import MCPPlan, MCPTool +from apps.llm.function import JsonGenerator +from apps.llm.reasoning import ReasoningLLM +from apps.scheduler.mcp.prompt import CREATE_PLAN, FINAL_ANSWER + + +class MCPPlanner: + """MCP 用户目标拆解与规划""" + + def __init__(self, user_goal: str) -> None: + """初始化MCP规划器""" + self.user_goal = user_goal + self._env = SandboxedEnvironment( + loader=BaseLoader, + autoescape=True, + trim_blocks=True, + lstrip_blocks=True, + ) + self.input_tokens = 0 + self.output_tokens = 0 + + + async def create_plan(self, tool_list: list[MCPTool], max_steps: int = 6) -> MCPPlan: + """规划下一步的执行流程,并输出""" + # 获取推理结果 + result = await self._get_reasoning_plan(tool_list, max_steps) + + # 解析为结构化数据 + return await self._parse_plan_result(result, max_steps) + + + async def _get_reasoning_plan(self, tool_list: list[MCPTool], max_steps: int) -> str: + """获取推理大模型的结果""" + # 格式化Prompt + template = self._env.from_string(CREATE_PLAN) + prompt = template.render( + goal=self.user_goal, + tools=tool_list, + max_num=max_steps, + ) + + # 调用推理大模型 + message = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": prompt}, + ] + reasoning_llm = ReasoningLLM() + result = "" + async for chunk in reasoning_llm.call( + message, + streaming=False, + temperature=0.07, + result_only=True, + ): + result += chunk + + # 保存token用量 + self.input_tokens = reasoning_llm.input_tokens + self.output_tokens = reasoning_llm.output_tokens + + return result + + + async def _parse_plan_result(self, result: str, max_steps: int) -> MCPPlan: + """将推理结果解析为结构化数据""" + # 格式化Prompt + schema = MCPPlan.model_json_schema() + schema["properties"]["plans"]["maxItems"] = max_steps + + # 使用Function模型解析结果 + json_generator = JsonGenerator( + result, + [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": result}, + ], + schema, + ) + plan = await json_generator.generate() + return MCPPlan.model_validate(plan) + + + async def generate_answer(self, plan: MCPPlan, memory: str) -> str: + """生成最终回答""" + template = self._env.from_string(FINAL_ANSWER) + prompt = template.render( + plan=plan, + memory=memory, + goal=self.user_goal, + ) + + llm = ReasoningLLM() + result = "" + async for chunk in llm.call( + [{"role": "user", "content": prompt}], + streaming=False, + temperature=0.07, + ): + result += chunk + + self.input_tokens = llm.input_tokens + self.output_tokens = llm.output_tokens + + return result diff --git a/apps/scheduler/mcp/prompt.py b/apps/scheduler/mcp/prompt.py new file mode 100644 index 0000000000000000000000000000000000000000..ed045154b867c8f4b783d311c3affeff3e06ad1c --- /dev/null +++ b/apps/scheduler/mcp/prompt.py @@ -0,0 +1,239 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MCP相关的大模型Prompt""" + +from textwrap import dedent + +MCP_SELECT = dedent(r""" + 你是一个乐于助人的智能助手。 + 你的任务是:根据当前目标,选择最合适的MCP Server。 + + ## 选择MCP Server时的注意事项: + + 1. 确保充分理解当前目标,选择最合适的MCP Server。 + 2. 请在给定的MCP Server列表中选择,不要自己生成MCP Server。 + 3. 请先给出你选择的理由,再给出你的选择。 + 4. 当前目标将在下面给出,MCP Server列表也会在下面给出。 + 请将你的思考过程放在"思考过程"部分,将你的选择放在"选择结果"部分。 + 5. 选择必须是JSON格式,严格按照下面的模板,不要输出任何其他内容: + + ```json + { + "mcp": "你选择的MCP Server的名称" + } + ``` + + 6. 下面的示例仅供参考,不要将示例中的内容作为选择MCP Server的依据。 + + ## 示例 + + ### 目标 + + 我需要一个MCP Server来完成一个任务。 + + ### MCP Server列表 + + - **mcp_1**: "MCP Server 1";MCP Server 1的描述 + - **mcp_2**: "MCP Server 2";MCP Server 2的描述 + + ### 请一步一步思考: + + 因为当前目标需要一个MCP Server来完成一个任务,所以选择mcp_1。 + + ### 选择结果 + + ```json + { + "mcp": "mcp_1" + } + ``` + + ## 现在开始! + + ### 目标 + + {{goal}} + + ### MCP Server列表 + + {% for mcp in mcp_list %} + - **{{mcp.id}}**: "{{mcp.name}}";{{mcp.description}} + {% endfor %} + + ### 请一步一步思考: + +""") +CREATE_PLAN = dedent(r""" + 你是一个计划生成器。 + 请分析用户的目标,并生成一个计划。你后续将根据这个计划,一步一步地完成用户的目标。 + + # 一个好的计划应该: + + 1. 能够成功完成用户的目标 + 2. 计划中的每一个步骤必须且只能使用一个工具。 + 3. 计划中的步骤必须具有清晰和逻辑的步骤,没有冗余或不必要的步骤。 + 4. 计划中的最后一步必须是Final工具,以确保计划执行结束。 + + # 生成计划时的注意事项: + + - 每一条计划包含3个部分: + - 计划内容:描述单个计划步骤的大致内容 + - 工具名称:必须从下文的工具列表中选择 + - 工具指令:改写用户的目标,使其更符合工具的输入要求 + - 必须按照如下格式生成计划,不要输出任何额外数据: + + ```json + { + "plans": [ + { + "content": "计划内容", + "tool": "工具名称", + "instruction": "工具指令" + } + ] + } + ``` + + - 在生成计划之前,请一步一步思考,解析用户的目标,并指导你接下来的生成。\ +思考过程应放置在 XML标签中。 + - 计划内容中,可以使用"Result[]"来引用之前计划步骤的结果。例如:"Result[3]"表示引用第三条计划执行后的结果。 + - 计划不得多于{{ max_num }}条,且每条计划内容应少于150字。 + + # 工具 + + 你可以访问并使用一些工具,这些工具将在 XML标签中给出。 + + + {% for tool in tools %} + - **{{ tool.name }}**: {{ tool.description }} + {% endfor %} + - **Final**: 结束步骤,当执行到这一步时,表示计划执行结束,所得到的结果将作为最终结果。 + + + # 样例 + + ## 目标 + + 在后台运行一个新的alpine:latest容器,将主机/root文件夹挂载至/data,并执行top命令。 + + ## 计划 + + + 1. 这个目标需要使用Docker来完成,首先需要选择合适的MCP Server + 2. 目标可以拆解为以下几个部分: + - 运行alpine:latest容器 + - 挂载主机目录 + - 在后台运行 + - 执行top命令 + 3. 需要先选择MCP Server,然后生成Docker命令,最后执行命令 + + + ```json + { + "plans": [ + { + "content": "选择一个支持Docker的MCP Server", + "tool": "MCP Selector", + "instruction": "需要一个支持Docker容器运行的MCP Server" + }, + { + "content": "使用Result[0]中选择的MCP Server,生成Docker命令", + "tool": "Command Generator", + "instruction": "生成Docker命令:在后台运行alpine:latest容器,挂载/root到/data,执行top命令" + }, + { + "content": "在Result[0]的MCP Server上执行Result[1]生成的命令", + "tool": "Command Executor", + "instruction": "执行Docker命令" + }, + { + "content": "任务执行完成,容器已在后台运行,结果为Result[2]", + "tool": "Final", + "instruction": "" + } + ] + } + ``` + + # 现在开始生成计划: + + ## 目标 + + {{goal}} + + # 计划 +""") +EVALUATE_PLAN = dedent(r""" + 你是一个计划评估器。 + 请根据给定的计划,和当前计划执行的实际情况,分析当前计划是否合理和完整,并生成改进后的计划。 + + # 一个好的计划应该: + + 1. 能够成功完成用户的目标 + 2. 计划中的每一个步骤必须且只能使用一个工具。 + 3. 计划中的步骤必须具有清晰和逻辑的步骤,没有冗余或不必要的步骤。 + 4. 计划中的最后一步必须是Final工具,以确保计划执行结束。 + + # 你此前的计划是: + + {{ plan }} + + # 这个计划的执行情况是: + + 计划的执行情况将放置在 XML标签中。 + + + {{ memory }} + + + # 进行评估时的注意事项: + + - 请一步一步思考,解析用户的目标,并指导你接下来的生成。思考过程应放置在 XML标签中。 + - 评估结果分为两个部分: + - 计划评估的结论 + - 改进后的计划 + - 请按照以下JSON格式输出评估结果: + + ```json + { + "evaluation": "评估结果", + "plan": [ + { + "content": "改进后的计划内容", + "tool": "工具名称", + "instruction": "工具指令" + } + ] + } + ``` + + # 现在开始评估计划: + +""") +FINAL_ANSWER = dedent(r""" + 综合理解计划执行结果和背景信息,向用户报告目标的完成情况。 + + # 用户目标 + + {{ goal }} + + # 计划执行情况 + + 为了完成上述目标,你实施了以下计划: + + {{ memory }} + + # 其他背景信息: + + {{ status }} + + # 现在,请根据以上信息,向用户报告目标的完成情况: + +""") +MEMORY_TEMPLATE = dedent(r""" + {% for ctx in context_list %} + - 第{{ loop.index }}步:{{ ctx.step_description }} + - 调用工具 `{{ ctx.step_id }}`,并提供参数 `{{ ctx.input_data }}` + - 执行状态:{{ ctx.status }} + - 得到数据:`{{ ctx.output_data }}` + {% endfor %} +""") diff --git a/apps/scheduler/mcp/select.py b/apps/scheduler/mcp/select.py new file mode 100644 index 0000000000000000000000000000000000000000..2694a63cb6497afe83ebfd0c785e26fa0a291eb4 --- /dev/null +++ b/apps/scheduler/mcp/select.py @@ -0,0 +1,181 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""选择MCP Server及其工具""" + +import logging + +from jinja2 import BaseLoader +from jinja2.sandbox import SandboxedEnvironment + +from apps.entities.mcp import ( + MCPCollection, + MCPSelectResult, + MCPTool, +) +from apps.llm.embedding import Embedding +from apps.llm.function import FunctionLLM +from apps.llm.reasoning import ReasoningLLM +from apps.models.lance import LanceDB +from apps.models.mongo import MongoDB +from apps.scheduler.mcp.prompt import ( + MCP_SELECT, +) + +logger = logging.getLogger(__name__) + + +class MCPSelector: + """MCP选择器""" + + def __init__(self) -> None: + """初始化助手类""" + self.input_tokens = 0 + self.output_tokens = 0 + + @staticmethod + def _assemble_sql(mcp_list: list[str]) -> str: + """组装SQL""" + sql = "(" + for mcp_id in mcp_list: + sql += f"'{mcp_id}', " + return sql.rstrip(", ") + ")" + + + async def _get_top_mcp_by_embedding( + self, + query: str, + mcp_list: list[str], + ) -> list[dict[str, str]]: + """通过向量检索获取Top5 MCP Server""" + logger.info("[MCPHelper] 查询MCP Server向量: %s, %s", query, mcp_list) + mcp_table = await LanceDB().get_table("mcp") + query_embedding = await Embedding.get_embedding([query]) + mcp_vecs = await (await mcp_table.search( + query=query_embedding, + vector_column_name="embedding", + )).where(f"id IN {MCPSelector._assemble_sql(mcp_list)}").limit(5).to_list() + + # 拿到名称和description + logger.info("[MCPHelper] 查询MCP Server名称和描述: %s", mcp_vecs) + mcp_collection = MongoDB().get_collection("mcp") + llm_mcp_list: list[dict[str, str]] = [] + for mcp_vec in mcp_vecs: + mcp_id = mcp_vec["id"] + mcp_data = await mcp_collection.find_one({"_id": mcp_id}) + if not mcp_data: + logger.warning("[MCPHelper] 查询MCP Server名称和描述失败: %s", mcp_id) + continue + mcp_data = MCPCollection.model_validate(mcp_data) + llm_mcp_list.extend([{ + "id": mcp_id, + "name": mcp_data.name, + "description": mcp_data.description, + }]) + return llm_mcp_list + + + async def _get_mcp_by_llm( + self, + query: str, + mcp_list: list[dict[str, str]], + mcp_ids: list[str], + ) -> MCPSelectResult: + """通过LLM选择最合适的MCP Server""" + # 初始化jinja2环境 + env = SandboxedEnvironment( + loader=BaseLoader, + autoescape=True, + trim_blocks=True, + lstrip_blocks=True, + ) + template = env.from_string(MCP_SELECT) + # 渲染模板 + mcp_prompt = template.render( + mcp_list=mcp_list, + goal=query, + ) + + # 调用大模型进行推理 + result = await self._call_reasoning(mcp_prompt) + + # 使用小模型提取JSON + return await self._call_function_mcp(result, mcp_ids) + + + async def _call_reasoning(self, prompt: str) -> str: + """调用大模型进行推理""" + logger.info("[MCPHelper] 调用推理大模型") + llm = ReasoningLLM() + message = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": prompt}, + ] + result = "" + async for chunk in llm.call(message): + result += chunk + self.input_tokens += llm.input_tokens + self.output_tokens += llm.output_tokens + return result + + + async def _call_function_mcp(self, reasoning_result: str, mcp_ids: list[str]) -> MCPSelectResult: + """调用结构化输出小模型提取JSON""" + logger.info("[MCPHelper] 调用结构化输出小模型") + llm = FunctionLLM() + message = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": reasoning_result}, + ] + schema = MCPSelectResult.model_json_schema() + # schema中加入选项 + schema["properties"]["mcp_id"]["enum"] = mcp_ids + result = await llm.call(messages=message, schema=schema) + try: + result = MCPSelectResult.model_validate(result) + except Exception: + logger.exception("[MCPHelper] 解析MCP Select Result失败") + raise + return result + + + async def select_top_mcp( + self, + query: str, + mcp_list: list[str], + ) -> MCPSelectResult: + """ + 选择最合适的MCP Server + + 先通过Embedding选择Top5,然后通过LLM选择Top 1 + """ + # 通过向量检索获取Top5 + llm_mcp_list = await self._get_top_mcp_by_embedding(query, mcp_list) + + # 通过LLM选择最合适的 + return await self._get_mcp_by_llm(query, llm_mcp_list, mcp_list) + + + @staticmethod + async def select_top_tool(query: str, mcp_list: list[str], top_n: int = 10) -> list[MCPTool]: + """选择最合适的工具""" + tool_vector = await LanceDB().get_table("mcp_tool") + query_embedding = await Embedding.get_embedding([query]) + tool_vecs = await (await tool_vector.search( + query=query_embedding, + vector_column_name="embedding", + )).where(f"mcp_id IN {MCPSelector._assemble_sql(mcp_list)}").limit(top_n).to_list() + + # 拿到工具 + logger.info("[MCPHelper] 查询MCP Tool名称和描述: %s", tool_vecs) + tool_collection = MongoDB().get_collection("mcp") + llm_tool_list = [] + + for tool_vec in tool_vecs: + # 到MongoDB里找对应的工具 + tool_data = await tool_collection.find_one({"_id": tool_vec["mcp_id"], "tools.id": tool_vec["id"]}) + if not tool_data: + logger.warning("[MCPHelper] 查询MCP Tool名称和描述失败: %s/%s", tool_vec["mcp_id"], tool_vec["id"]) + continue + tool_data = MCPTool.model_validate(tool_data["tools"][0]) + llm_tool_list.append(tool_data) + + return llm_tool_list diff --git a/apps/scheduler/openapi.py b/apps/scheduler/openapi.py index fdbaeb9691667b014284dbd58cb395e1a881bbb6..de89d89cbdd694fafcccd773f25db5d12a56768e 100644 --- a/apps/scheduler/openapi.py +++ b/apps/scheduler/openapi.py @@ -1,8 +1,5 @@ -""" -OpenAPI文档相关操作 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""OpenAPI文档相关操作""" from collections.abc import Sequence from copy import deepcopy diff --git a/apps/scheduler/pool/__init__.py b/apps/scheduler/pool/__init__.py index 636c1a7b517c92de598cc99f0cd064538f600e9e..54f23554a2e4504ffe2bbbd8775155ee95f8ee27 100644 --- a/apps/scheduler/pool/__init__.py +++ b/apps/scheduler/pool/__init__.py @@ -1,6 +1,6 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """ 数据池 包含Flow、Plugin、Call等的Loader -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """ diff --git a/apps/scheduler/pool/check.py b/apps/scheduler/pool/check.py index 562343bb96474ab601dd78036ab1a94b1fbb32aa..fc89545092d02caabae3553650a388bf1be40636 100644 --- a/apps/scheduler/pool/check.py +++ b/apps/scheduler/pool/check.py @@ -1,8 +1,5 @@ -""" -文件检查器;检查文件是否存在、Hash是否发生变化;生成更新列表和删除列表 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""文件检查器""" import logging from hashlib import sha256 @@ -56,10 +53,10 @@ class FileChecker: async def diff(self, check_type: MetadataType) -> tuple[list[str], list[str]]: """生成更新列表和删除列表""" if check_type == MetadataType.APP: - collection = MongoDB.get_collection("app") + collection = MongoDB().get_collection("app") self._dir_path = Path(Config().get_config().deploy.data_dir) / "semantics" / "app" elif check_type == MetadataType.SERVICE: - collection = MongoDB.get_collection("service") + collection = MongoDB().get_collection("service") self._dir_path = Path(Config().get_config().deploy.data_dir) / "semantics" / "service" changed_list = [] diff --git a/apps/scheduler/pool/loader/__init__.py b/apps/scheduler/pool/loader/__init__.py index e746420267f53eef566eb1a1d025e64442e5dfe9..4f229a1320062479b989ffd0c87437bec3ca9863 100644 --- a/apps/scheduler/pool/loader/__init__.py +++ b/apps/scheduler/pool/loader/__init__.py @@ -1,12 +1,16 @@ -""" -配置加载器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""配置加载器""" from apps.scheduler.pool.loader.app import AppLoader from apps.scheduler.pool.loader.call import CallLoader from apps.scheduler.pool.loader.flow import FlowLoader +from apps.scheduler.pool.loader.mcp import MCPLoader from apps.scheduler.pool.loader.service import ServiceLoader -__all__ = ["AppLoader", "CallLoader", "FlowLoader", "ServiceLoader"] +__all__ = [ + "AppLoader", + "CallLoader", + "FlowLoader", + "MCPLoader", + "ServiceLoader", +] diff --git a/apps/scheduler/pool/loader/app.py b/apps/scheduler/pool/loader/app.py index 3f8d9bb599cdc610c08b8a0e4cc5e0e9d426014b..3ad6751c97b96b256d7907cec23863fd0258946b 100644 --- a/apps/scheduler/pool/loader/app.py +++ b/apps/scheduler/pool/loader/app.py @@ -1,8 +1,5 @@ -""" -App加载器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""App加载器""" import logging import shutil @@ -11,6 +8,8 @@ from anyio import Path from fastapi.encoders import jsonable_encoder from apps.common.config import Config +from apps.entities.agent import AgentAppMetadata +from apps.entities.enum_var import AppType from apps.entities.flow import AppFlow, AppMetadata, MetadataType, Permission from apps.entities.pool import AppPool from apps.models.mongo import MongoDB @@ -19,6 +18,7 @@ from apps.scheduler.pool.loader.flow import FlowLoader from apps.scheduler.pool.loader.metadata import MetadataLoader logger = logging.getLogger(__name__) +BASE_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" / "app" class AppLoader: @@ -30,7 +30,7 @@ class AppLoader: :param app_id: 应用 ID """ - app_path = Path(Config().get_config().deploy.data_dir) / "semantics" / "app" / app_id + app_path = BASE_PATH / app_id metadata_path = app_path / "metadata.yaml" metadata = await MetadataLoader().load_one(metadata_path) if not metadata: @@ -38,45 +38,54 @@ class AppLoader: raise ValueError(err) metadata.hashes = hashes - if not isinstance(metadata, AppMetadata): + if not isinstance(metadata, (AppMetadata, AgentAppMetadata)): err = f"[AppLoader] 元数据类型错误: {metadata_path}" raise TypeError(err) - # 加载工作流 - flow_path = app_path / "flow" - flow_loader = FlowLoader() - - flow_ids = [app_flow.id for app_flow in metadata.flows] - new_flows: list[AppFlow] = [] - async for flow_file in flow_path.rglob("*.yaml"): - if flow_file.stem not in flow_ids: - logger.warning("[AppLoader] 工作流 %s 不在元数据中", flow_file) - flow = await flow_loader.load(app_id, flow_file.stem) - if not flow: - err = f"[AppLoader] 工作流 {flow_file} 加载失败" - raise ValueError(err) - if not flow.debug: - metadata.published = False - new_flows.append( - AppFlow( - id=flow_file.stem, - name=flow.name, - description=flow.description, - path=flow_file.as_posix(), - debug=flow.debug, - ), - ) - metadata.flows = new_flows - try: - metadata = AppMetadata.model_validate(metadata) - except Exception as e: - err = "[AppLoader] 元数据验证失败" - logger.exception(err) - raise RuntimeError(err) from e + if metadata.app_type == AppType.FLOW: + # 加载工作流 + flow_path = app_path / "flow" + flow_loader = FlowLoader() + + flow_ids = [app_flow.id for app_flow in metadata.flows] + new_flows: list[AppFlow] = [] + async for flow_file in flow_path.rglob("*.yaml"): + if flow_file.stem not in flow_ids: + logger.warning("[AppLoader] 工作流 %s 不在元数据中", flow_file) + flow = await flow_loader.load(app_id, flow_file.stem) + if not flow: + err = f"[AppLoader] 工作流 {flow_file} 加载失败" + raise ValueError(err) + if not flow.debug: + metadata.published = False + new_flows.append( + AppFlow( + id=flow_file.stem, + name=flow.name, + description=flow.description, + path=flow_file.as_posix(), + debug=flow.debug, + ), + ) + metadata.flows = new_flows + try: + metadata = AppMetadata.model_validate(metadata) + except Exception as e: + err = "[AppLoader] Flow应用元数据验证失败" + logger.exception(err) + raise RuntimeError(err) from e + elif metadata.app_type == AppType.AGENT: + # 加载模型 + try: + metadata = AgentAppMetadata.model_validate(metadata) + except Exception as e: + err = "[AppLoader] Agent应用元数据验证失败" + logger.exception(err) + raise RuntimeError(err) from e + pass await self._update_db(metadata) - - async def save(self, metadata: AppMetadata, app_id: str) -> None: + async def save(self, metadata: AppMetadata | AgentAppMetadata, app_id: str) -> None: """ 保存应用 @@ -84,7 +93,7 @@ class AppLoader: :param app_id: 应用 ID """ # 创建文件夹 - app_path = Path(Config().get_config().deploy.data_dir) / "semantics" / "app" / app_id + app_path = BASE_PATH / app_id if not await app_path.exists(): await app_path.mkdir(parents=True, exist_ok=True) # 保存元数据 @@ -95,16 +104,18 @@ class AppLoader: await self.load(app_id, file_checker.hashes[f"app/{app_id}"]) - async def delete(self, app_id: str, *, is_reload: bool = False) -> None: + @staticmethod + async def delete(app_id: str, *, is_reload: bool = False) -> None: """ 删除App,并更新数据库 :param app_id: 应用 ID """ + mongo = MongoDB() try: - app_collection = MongoDB.get_collection("app") + app_collection = mongo.get_collection("app") await app_collection.delete_one({"_id": app_id}) # 删除应用数据 - user_collection = MongoDB.get_collection("user") + user_collection = mongo.get_collection("user") # 删除用户使用记录 await user_collection.update_many( {f"app_usage.{app_id}": {"$exists": True}}, @@ -119,20 +130,21 @@ class AppLoader: logger.exception("[AppLoader] MongoDB删除App失败") if not is_reload: - app_path = Path(Config().get_config().deploy.data_dir) / "semantics" / "app" / app_id + app_path = BASE_PATH / app_id if await app_path.exists(): shutil.rmtree(str(app_path), ignore_errors=True) - - async def _update_db(self, metadata: AppMetadata) -> None: + @staticmethod + async def _update_db(metadata: AppMetadata) -> None: """更新数据库""" if not metadata.hashes: err = f"[AppLoader] 应用 {metadata.id} 的哈希值为空" logger.error(err) raise ValueError(err) # 更新应用数据 + mongo = MongoDB() try: - app_collection = MongoDB.get_collection("app") + app_collection = mongo.get_collection("app") metadata.permission = metadata.permission if metadata.permission else Permission() await app_collection.update_one( {"_id": metadata.id}, diff --git a/apps/scheduler/pool/loader/btdl.py b/apps/scheduler/pool/loader/btdl.py index a6158cff105d72967872316ec038eea3c99d8467..072303b7fa7e0ebd1ca4ed8ef3258ff64431aed8 100644 --- a/apps/scheduler/pool/loader/btdl.py +++ b/apps/scheduler/pool/loader/btdl.py @@ -1,8 +1,5 @@ -""" -BTDL文档加载器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""BTDL文档加载器""" import hashlib from typing import Any diff --git a/apps/scheduler/pool/loader/call.py b/apps/scheduler/pool/loader/call.py index bd465f17e4f0a100444151432b2fe8411ecc9d3b..a4ee7bcb9422397479d6fbb443c8a93bc747cbd1 100644 --- a/apps/scheduler/pool/loader/call.py +++ b/apps/scheduler/pool/loader/call.py @@ -1,8 +1,5 @@ -""" -Call 加载器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Call 加载器""" import importlib import logging @@ -12,6 +9,7 @@ from pathlib import Path import apps.scheduler.call as system_call from apps.common.config import Config +from apps.common.singleton import SingletonMeta from apps.entities.enum_var import CallType from apps.entities.pool import CallPool, NodePool from apps.entities.vector import CallPoolVector @@ -20,9 +18,10 @@ from apps.models.lance import LanceDB from apps.models.mongo import MongoDB logger = logging.getLogger(__name__) +BASE_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" / "call" -class CallLoader: +class CallLoader(metaclass=SingletonMeta): """ Call 加载器 @@ -55,7 +54,7 @@ class CallLoader: """加载单个Call package""" call_metadata = [] - call_dir = Path(Config().get_config().deploy.data_dir) / "semantics" / "call" / call_dir_name + call_dir = BASE_PATH / call_dir_name if not (call_dir / "__init__.py").exists(): logger.info("[CallLoader] 模块 %s 不存在__init__.py文件,尝试自动创建。", call_dir) try: @@ -104,7 +103,7 @@ class CallLoader: async def _load_all_user_call(self) -> list[CallPool]: """加载Python Call""" - call_dir = Path(Config().get_config().deploy.data_dir) / "semantics" / "call" + call_dir = BASE_PATH call_metadata = [] # 载入父包 @@ -138,7 +137,7 @@ class CallLoader: await self._delete_from_db(call_name) # 从Python中卸载模块 - call_dir = Path(Config().get_config().deploy.data_dir) / "semantics" / "call" / call_name + call_dir = BASE_PATH / call_name if call_dir.exists(): module_name = f"call.{call_name}" if module_name in sys.modules: @@ -147,8 +146,9 @@ class CallLoader: async def _delete_from_db(self, call_name: str) -> None: """从数据库中删除单个Call""" # 从MongoDB中删除 - call_collection = MongoDB.get_collection("call") - node_collection = MongoDB.get_collection("node") + mongo = MongoDB() + call_collection = mongo.get_collection("call") + node_collection = mongo.get_collection("node") try: await call_collection.delete_one({"_id": call_name}) await node_collection.delete_many({"call_id": call_name}) @@ -170,8 +170,9 @@ class CallLoader: async def _add_to_db(self, call_metadata: list[CallPool]) -> None: """更新数据库""" # 更新MongoDB - call_collection = MongoDB.get_collection("call") - node_collection = MongoDB.get_collection("node") + mongo = MongoDB() + call_collection = mongo.get_collection("call") + node_collection = mongo.get_collection("node") call_descriptions = [] try: for call in call_metadata: @@ -207,13 +208,16 @@ class CallLoader: embedding=vec, ), ) - await table.add(vector_data) + await table.merge_insert("id").when_matched_update_all().when_not_matched_insert_all().execute( + vector_data, + ) async def load(self) -> None: """初始化Call信息""" # 清空collection - call_collection = MongoDB.get_collection("call") - node_collection = MongoDB.get_collection("node") + mongo = MongoDB() + call_collection = mongo.get_collection("call") + node_collection = mongo.get_collection("node") try: await call_collection.delete_many({}) await node_collection.delete_many({"service_id": ""}) diff --git a/apps/scheduler/pool/loader/flow.py b/apps/scheduler/pool/loader/flow.py index 55f0b78e6f77be21321dc5a4160ffc038a596321..6d0048b6106c4753f6ed9118be29ae51d2e6d3f8 100644 --- a/apps/scheduler/pool/loader/flow.py +++ b/apps/scheduler/pool/loader/flow.py @@ -1,8 +1,5 @@ -""" -Flow加载器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Flow加载器""" import logging from hashlib import sha256 @@ -24,7 +21,7 @@ from apps.models.mongo import MongoDB from apps.scheduler.util import yaml_enum_presenter, yaml_str_presenter logger = logging.getLogger(__name__) - +BASE_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" / "app" class FlowLoader: """工作流加载器""" @@ -105,9 +102,7 @@ class FlowLoader: logger.info("[FlowLoader] 应用 %s:加载工作流 %s...", flow_id, app_id) # 构建工作流文件路径 - flow_path = ( - Path(Config().get_config().deploy.data_dir) / "semantics" / "app" / app_id / "flow" / f"{flow_id}.yaml" - ) + flow_path = BASE_PATH / app_id / "flow" / f"{flow_id}.yaml" if not await flow_path.exists(): logger.error("[FlowLoader] 应用 %s:工作流文件 %s 不存在", app_id, flow_path) return None @@ -146,9 +141,7 @@ class FlowLoader: async def save(self, app_id: str, flow_id: str, flow: Flow) -> None: """保存工作流""" - flow_path = ( - Path(Config().get_config().deploy.data_dir) / "semantics" / "app" / app_id / "flow" / f"{flow_id}.yaml" - ) + flow_path = BASE_PATH / app_id / "flow" / f"{flow_id}.yaml" if not await flow_path.parent.exists(): await flow_path.parent.mkdir(parents=True) @@ -177,9 +170,7 @@ class FlowLoader: async def delete(self, app_id: str, flow_id: str) -> bool: """删除指定工作流文件""" - flow_path = ( - Path(Config().get_config().deploy.data_dir) / "semantics" / "app" / app_id / "flow" / f"{flow_id}.yaml" - ) + flow_path = BASE_PATH / app_id / "flow" / f"{flow_id}.yaml" # 确保目标为文件且存在 if await flow_path.exists(): try: @@ -201,7 +192,7 @@ class FlowLoader: async def _update_db(self, app_id: str, metadata: AppFlow) -> None: """更新数据库""" try: - app_collection = MongoDB.get_collection("app") + app_collection = MongoDB().get_collection("app") # 获取当前的flows app_data = await app_collection.find_one({"_id": app_id}) if not app_data: @@ -229,14 +220,7 @@ class FlowLoader: }, upsert=True, ) - flow_path = ( - Path(Config().get_config().deploy.data_dir) - / "semantics" - / "app" - / app_id - / "flow" - / f"{metadata.id}.yaml" - ) + flow_path = BASE_PATH / app_id / "flow" / f"{metadata.id}.yaml" async with aiofiles.open(flow_path, "rb") as f: new_hash = sha256(await f.read()).hexdigest() @@ -263,4 +247,6 @@ class FlowLoader: embedding=service_embedding[0], ), ] - await table.add(vector_data) + await table.merge_insert("id").when_matched_update_all().when_not_matched_insert_all().execute( + vector_data, + ) diff --git a/apps/scheduler/pool/loader/mcp.py b/apps/scheduler/pool/loader/mcp.py new file mode 100644 index 0000000000000000000000000000000000000000..45ae68527f271d4c9ff9dfe06e1777ae950dae1e --- /dev/null +++ b/apps/scheduler/pool/loader/mcp.py @@ -0,0 +1,450 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MCP 加载器""" + +import json +import logging +import shutil + +import asyncer +from anyio import Path + +from apps.common.config import Config +from apps.common.singleton import SingletonMeta +from apps.entities.mcp import ( + MCPCollection, + MCPConfig, + MCPServerSSEConfig, + MCPServerStdioConfig, + MCPTool, + MCPToolVector, + MCPType, + MCPVector, +) +from apps.llm.embedding import Embedding +from apps.models.lance import LanceDB +from apps.models.mongo import MongoDB +from apps.scheduler.pool.mcp.client import SSEMCPClient, StdioMCPClient +from apps.scheduler.pool.mcp.install import install_npx, install_uvx + +logger = logging.getLogger(__name__) +MCP_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" / "mcp" + + +class MCPLoader(metaclass=SingletonMeta): + """ + MCP加载模块 + + 创建MCP Client,启动MCP进程,并将MCP基本信息(名称、描述、工具列表等)写入数据库 + """ + + @staticmethod + async def _check_dir() -> None: + """ + 检查MCP目录是否存在 + + :return: 无 + """ + if not await (MCP_PATH / "template").exists() or not await (MCP_PATH / "template").is_dir(): + logger.warning("[MCPLoader] template目录不存在,创建中") + await (MCP_PATH / "template").unlink(missing_ok=True) + await (MCP_PATH / "template").mkdir(parents=True, exist_ok=True) + + if not await (MCP_PATH / "users").exists() or not await (MCP_PATH / "users").is_dir(): + logger.warning("[MCPLoader] users目录不存在,创建中") + await (MCP_PATH / "users").unlink(missing_ok=True) + await (MCP_PATH / "users").mkdir(parents=True, exist_ok=True) + + + @staticmethod + async def _load_config(config_path: Path) -> MCPConfig: + """ + 加载 MCP 配置 + + :param Path config_path: MCP配置文件路径 + :return: MCP配置 + :raises FileNotFoundError: 如果配置文件不存在,则抛出异常 + """ + if not await config_path.exists(): + err = f"MCP配置文件不存在: {config_path}" + logger.error(err) + raise FileNotFoundError(err) + + f = await config_path.open("r", encoding="utf-8") + f_content = json.loads(await f.read()) + await f.aclose() + + return MCPConfig.model_validate(f_content) + + + @staticmethod + async def _install_template( + mcp_id: str, + config: MCPServerSSEConfig | MCPServerStdioConfig, + ) -> MCPServerSSEConfig | MCPServerStdioConfig: + """ + 初始化MCP模板 + + 单个MCP模板的初始化。若模板中 ``auto_install`` 为 ``True`` ,则自动安装MCP环境 + + :param str mcp_id: MCP模板ID + :param MCPServerSSEConfig | MCPServerStdioConfig config: MCP配置 + :return: 无 + """ + # 跳过自动安装 + if not config.auto_install: + logger.info("[MCPLoader] autoInstall为False,跳过自动安装: %s", mcp_id) + return config + + # 自动安装 + if isinstance(config, MCPServerStdioConfig): + logger.info("[MCPLoader] Stdio方式的MCP模板,开始自动安装: %s", mcp_id) + if "uv" in config.command: + config = await install_uvx(mcp_id, config) + elif "npx" in config.command: + config = await install_npx(mcp_id, config) + else: + logger.info("[MCPLoader] SSE方式的MCP模板,无法自动安装: %s", mcp_id) + + config.auto_install = False + return config + + + @staticmethod + async def init_one_template(mcp_id: str, config: MCPServerSSEConfig | MCPServerStdioConfig) -> None: + """ + 初始化单个MCP模板 + + :param str mcp_id: MCP模板ID + :param MCPServerSSEConfig | MCPServerStdioConfig config: MCP配置 + :return: 无 + """ + # 安装MCP模板 + new_server_config = await MCPLoader._install_template(mcp_id, config) + new_config = MCPConfig( + mcpServers={ + mcp_id: new_server_config, + }, + ) + + # 更新数据库 + await MCPLoader._insert_template_db(mcp_id, config) + + # 保存config + f = await (MCP_PATH / "template" / mcp_id / "config.json").open("w+", encoding="utf-8") + config_data = new_config.model_dump(by_alias=True, exclude_none=True) + await f.write(json.dumps(config_data, indent=4, ensure_ascii=False)) + await f.aclose() + + + @staticmethod + async def _init_all_template() -> None: + """ + 初始化所有MCP模板 + + 遍历 ``template`` 目录下的所有MCP模板,并初始化。在Framework启动时进行此流程,确保所有MCP均可正常使用。 + 这一过程会与数据库内的条目进行对比,若发生修改,则重新创建数据库条目。 + """ + template_path = MCP_PATH / "template" + logger.info("[MCPLoader] 初始化所有MCP模板: %s", template_path) + + # 遍历所有模板 + async for mcp_dir in template_path.iterdir(): + # 不是目录 + if not await mcp_dir.is_dir(): + logger.warning("[MCPLoader] 跳过非目录: %s", mcp_dir.as_posix()) + continue + + # 检查配置文件是否存在 + config_path = mcp_dir / "config.json" + if not await config_path.exists(): + logger.warning("[MCPLoader] 跳过没有配置文件的MCP模板: %s", mcp_dir.as_posix()) + continue + + # 读取配置并加载 + config = await MCPLoader._load_config(config_path) + if len(config.mcp_servers) == 0: + logger.warning("[MCPLoader] 跳过没有MCP Server的MCP模板: %s", mcp_dir.as_posix()) + continue + if len(config.mcp_servers) > 1: + logger.warning("[MCPLoader] MCP模板中包含多个MCP Server,只会使用第一个: %s", mcp_dir.as_posix()) + + # 初始化第一个MCP Server + server_id, server = next(iter(config.mcp_servers.items())) + logger.info("[MCPLoader] 初始化MCP模板: %s", mcp_dir.as_posix()) + await MCPLoader.init_one_template(mcp_dir.name, server) + + + @staticmethod + async def _get_template_tool( + mcp_id: str, + config: MCPServerSSEConfig | MCPServerStdioConfig, + user_sub: str | None = None, + ) -> list[MCPTool]: + """ + 获取MCP模板的工具列表 + + :param str mcp_id: MCP模板ID + :param MCPServerSSEConfig | MCPServerStdioConfig config: MCP配置 + :param str | None user_sub: 用户ID,默认为None + :return: 工具列表 + :rtype: list[str] + """ + # 创建客户端 + if config.type == MCPType.STDIO and isinstance(config, MCPServerStdioConfig): + client = StdioMCPClient() + elif config.type == MCPType.SSE and isinstance(config, MCPServerSSEConfig): + client = SSEMCPClient() + else: + err = f"MCP {mcp_id}:未知的MCP服务类型“{config.type}”" + logger.error(err) + raise ValueError(err) + + await client.init(user_sub, mcp_id, config) + + # 获取工具列表 + tool_list = [] + for item in client.tools: + tool_list += [MCPTool( + id=f"{mcp_id}/{item.name}", + name=item.name, + mcp_id=mcp_id, + description=item.description or "", + input_schema=item.inputSchema, + )] + await client.stop() + return tool_list + + + @staticmethod + async def _insert_template_db(mcp_id: str, config: MCPServerSSEConfig | MCPServerStdioConfig) -> None: + """ + 插入单个MCP Server模板信息到数据库 + + :param str mcp_id: MCP模板ID + :param MCPServerSSEConfig | MCPServerStdioConfig config: MCP配置 + :return: 无 + """ + # 获取工具列表 + tool_list = await MCPLoader._get_template_tool(mcp_id, config) + + # 基本信息插入数据库 + mcp_collection = MongoDB().get_collection("mcp") + await mcp_collection.update_one( + {"_id": mcp_id}, + { + "$set": MCPCollection( + _id=mcp_id, + name=config.name, + description=config.description, + type=config.type, + tools=tool_list, + ).model_dump(by_alias=True, exclude_none=True), + }, + upsert=True, + ) + + # 服务本身向量化 + embedding = await Embedding.get_embedding([config.description]) + mcp_table = await LanceDB().get_table("mcp") + await mcp_table.merge_insert("id").when_matched_update_all().when_not_matched_insert_all().execute([ + MCPVector( + id=mcp_id, + embedding=embedding[0], + ), + ]) + + # 工具向量化 + mcp_tool_table = await LanceDB().get_table("mcp_tool") + tool_desc_list = [tool.description for tool in tool_list] + tool_embedding = await Embedding.get_embedding(tool_desc_list) + for tool, embedding in zip(tool_list, tool_embedding, strict=True): + await mcp_tool_table.merge_insert("id").when_matched_update_all().when_not_matched_insert_all().execute([ + MCPToolVector( + id=tool.id, + mcp_id=mcp_id, + embedding=embedding, + ), + ]) + await LanceDB().create_index("mcp_tool") + + + @staticmethod + async def save_one(user_sub: str | None, mcp_id: str, config: MCPConfig) -> None: + """ + 保存单个MCP模板的配置文件(即``template``下的``config.json``文件) + + :param str mcp_id: MCP模板ID + :param MCPConfig config: MCP配置 + :return: 无 + """ + if user_sub: + config_path = MCP_PATH / "users" / user_sub / mcp_id / "config.json" + else: + config_path = MCP_PATH / "template" / mcp_id / "config.json" + await Path.mkdir(config_path.parent, parents=True, exist_ok=True) + + f = await config_path.open("w+", encoding="utf-8") + config_dict = config.model_dump(by_alias=True, exclude_none=True) + await f.write(json.dumps(config_dict, indent=4, ensure_ascii=False)) + await f.aclose() + + + @staticmethod + async def user_active_template(user_sub: str, mcp_id: str) -> None: + """ + 用户激活MCP模板 + + 激活MCP模板时,将已安装的环境拷贝一份到用户目录,并更新数据库 + + :param str user_sub: 用户ID + :param str mcp_id: MCP模板ID + :return: 无 + :raises FileExistsError: MCP模板已存在或有同名文件,无法激活 + """ + template_path = MCP_PATH / "template" / mcp_id + user_path = MCP_PATH / "users" / user_sub / mcp_id + + # 判断是否存在 + if user_path.exists(): + err = f"MCP模板“{mcp_id}”已存在或有同名文件,无法激活" + logger.error(err) + raise FileExistsError(err) + + # 拷贝文件 + asyncer.asyncify(shutil.copytree)(template_path.as_posix(), user_path.as_posix(), dirs_exist_ok=True) + + # 更新数据库 + mongo = MongoDB() + mcp_collection = mongo.get_collection("mcp") + await mcp_collection.update_one( + {"_id": mcp_id}, + {"$addToSet": {"activated": user_sub}}, + ) + + + @staticmethod + async def user_deactive_template(user_sub: str, mcp_id: str) -> None: + """ + 取消激活MCP模板 + + 取消激活MCP模板时,删除用户目录下对应的MCP环境文件夹,并更新数据库 + + :param str user_sub: 用户ID + :param str mcp_id: MCP模板ID + :return: 无 + """ + # 删除用户目录 + user_path = MCP_PATH / "users" / user_sub / mcp_id + asyncer.asyncify(shutil.rmtree)(user_path.as_posix(), ignore_errors=True) + + # 更新数据库 + mongo = MongoDB() + mcp_collection = mongo.get_collection("mcp") + await mcp_collection.update_one( + {"_id": mcp_id}, + {"$pull": {"activated": user_sub}}, + ) + + + @staticmethod + async def _find_deleted_mcp() -> list[str]: + """ + 查找在文件系统中被修改和被删除的MCP + + :return: 被修改的MCP列表和被删除的MCP列表 + :rtype: tuple[list[str], list[str]] + """ + deleted_mcp_list = [] + + mcp_collection = MongoDB().get_collection("mcp") + mcp_list = await mcp_collection.find({}, {"_id": 1}).to_list(None) + for db_item in mcp_list: + mcp_path: Path = MCP_PATH / "template" / db_item["_id"] + if not await mcp_path.exists(): + deleted_mcp_list.append(db_item["_id"]) + logger.info("[MCPLoader] 这些MCP在文件系统中被删除: %s", deleted_mcp_list) + return deleted_mcp_list + + + @staticmethod + async def remove_deleted_mcp(deleted_mcp_list: list[str]) -> None: + """ + 删除无效的MCP在数据库中的记录 + + :param list[str] deleted_mcp_list: 被删除的MCP列表 + :return: 无 + """ + # 从MongoDB中移除 + mcp_collection = MongoDB().get_collection("mcp") + await mcp_collection.delete_many({"_id": {"$in": deleted_mcp_list}}) + logger.info("[MCPLoader] 清除数据库中无效的MCP") + + # 从LanceDB中移除 + mcp_table = await LanceDB().get_table("mcp") + for mcp_id in deleted_mcp_list: + await mcp_table.delete(f"id == '{mcp_id}'") + logger.info("[MCPLoader] 清除LanceDB中无效的MCP") + + + @staticmethod + async def _load_user_mcp() -> None: + """ + 加载用户MCP + + :return: 用户MCP列表 + :rtype: dict[str, list[str]] + """ + user_path = MCP_PATH / "users" + if not await user_path.exists(): + logger.warning("[MCPLoader] users目录不存在,跳过加载用户MCP") + return + + mongo = MongoDB() + mcp_collection = mongo.get_collection("mcp") + + mcp_list = {} + # 遍历users目录 + async for user_dir in user_path.iterdir(): + if not await user_dir.is_dir(): + continue + + # 遍历单个用户的目录 + async for mcp_dir in user_dir.iterdir(): + # 检查数据库中是否有这个MCP + mcp_item = await mcp_collection.find_one({"_id": mcp_dir.name}) + if not mcp_item: + # 数据库中不存在,当前文件夹无效,删除 + asyncer.asyncify(shutil.rmtree)(mcp_dir.as_posix(), ignore_errors=True) + + # 添加到dict + if mcp_dir.name not in mcp_list: + mcp_list[mcp_dir.name] = [] + mcp_list[mcp_dir.name].append(user_dir.name) + + # 更新所有MCP的activated情况 + for mcp_id, user_list in mcp_list.items(): + await mcp_collection.update_one( + {"_id": mcp_id}, + {"$set": {"activated": user_list}}, + ) + + + @staticmethod + async def init() -> None: + """ + 初始化MCP加载器 + + :return: 无 + """ + # 清空数据库 + deleted_mcp_list = await MCPLoader._find_deleted_mcp() + await MCPLoader.remove_deleted_mcp(deleted_mcp_list) + + # 检查目录 + await MCPLoader._check_dir() + + # 初始化所有模板 + await MCPLoader._init_all_template() + + # 加载用户MCP + await MCPLoader._load_user_mcp() diff --git a/apps/scheduler/pool/loader/metadata.py b/apps/scheduler/pool/loader/metadata.py index 84f110442c9698ce45b82d4acee35554828ec17f..87a2ba4e515b8d4dfbfa039eb88dff28c258ec7e 100644 --- a/apps/scheduler/pool/loader/metadata.py +++ b/apps/scheduler/pool/loader/metadata.py @@ -1,8 +1,5 @@ -""" -元数据加载器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""元数据加载器""" import logging from typing import Any @@ -12,7 +9,8 @@ from anyio import Path from fastapi.encoders import jsonable_encoder from apps.common.config import Config -from apps.entities.enum_var import MetadataType +from apps.entities.agent import AgentAppMetadata +from apps.entities.enum_var import AppType, MetadataType from apps.entities.flow import ( AppMetadata, ServiceMetadata, @@ -20,12 +18,13 @@ from apps.entities.flow import ( from apps.scheduler.util import yaml_str_presenter logger = logging.getLogger(__name__) +BASE_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" class MetadataLoader: """元数据加载器""" - async def load_one(self, file_path: Path) -> AppMetadata | ServiceMetadata | None: + async def load_one(self, file_path: Path) -> AppMetadata | ServiceMetadata | AgentAppMetadata | None: """加载单个元数据""" # 检查yaml格式 try: @@ -44,13 +43,23 @@ class MetadataLoader: # 尝试匹配格式 if metadata_type == MetadataType.APP.value: - try: - app_id = file_path.parent.name - metadata = AppMetadata(id=app_id, **metadata_dict) - except Exception as e: - err = "[MetadataLoader] App metadata.yaml格式错误" - logger.exception(err) - raise RuntimeError(err) from e + app_type = metadata_dict.get("app_type", AppType.FLOW) + if app_type == AppType.FLOW: + try: + app_id = file_path.parent.name + metadata = AppMetadata(id=app_id, **metadata_dict) + except Exception as e: + err = "[MetadataLoader] App metadata.yaml格式错误" + logger.exception(err) + raise RuntimeError(err) from e + else: + try: + app_id = file_path.parent.name + metadata = AgentAppMetadata(id=app_id, **metadata_dict) + except Exception as e: + err = "[MetadataLoader] Agent app metadata.yaml格式错误" + logger.exception(err) + raise RuntimeError(err) from e elif metadata_type == MetadataType.SERVICE.value: try: service_id = file_path.parent.name @@ -69,24 +78,20 @@ class MetadataLoader: async def save_one( self, metadata_type: MetadataType, - metadata: dict[str, Any] | AppMetadata | ServiceMetadata, + metadata: dict[str, Any] | AppMetadata | ServiceMetadata | AgentAppMetadata, resource_id: str, ) -> None: """保存单个元数据""" class_dict = { - MetadataType.APP: AppMetadata, + MetadataType.APP: AppMetadata | AgentAppMetadata, MetadataType.SERVICE: ServiceMetadata, } # 检查资源路径 if metadata_type == MetadataType.APP.value: - resource_path = ( - Path(Config().get_config().deploy.data_dir) / "semantics" / "app" / resource_id / "metadata.yaml" - ) + resource_path = BASE_PATH / "app" / resource_id / "metadata.yaml" elif metadata_type == MetadataType.SERVICE.value: - resource_path = ( - Path(Config().get_config().deploy.data_dir) / "semantics" / "service" / resource_id / "metadata.yaml" - ) + resource_path = BASE_PATH / "service" / resource_id / "metadata.yaml" else: err = f"[MetadataLoader] metadata_type类型错误: {metadata_type}" logger.error(err) diff --git a/apps/scheduler/pool/loader/openapi.py b/apps/scheduler/pool/loader/openapi.py index 59379887025d68d5346608f54a50abd75c243328..158558a32b54f1de7881b481a403dfe66d7303ec 100644 --- a/apps/scheduler/pool/loader/openapi.py +++ b/apps/scheduler/pool/loader/openapi.py @@ -1,8 +1,5 @@ -""" -OpenAPI文档载入器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""OpenAPI文档载入器""" import logging from hashlib import shake_128 diff --git a/apps/scheduler/pool/loader/service.py b/apps/scheduler/pool/loader/service.py index 2a82e0877b792c09f20232d7c402729eb372cd6b..c48516cde92942c0a0fafff7e0b074de41aaa0fe 100644 --- a/apps/scheduler/pool/loader/service.py +++ b/apps/scheduler/pool/loader/service.py @@ -1,8 +1,5 @@ -""" -加载配置文件夹的Service部分 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""加载配置文件夹的Service部分""" import logging import shutil @@ -22,6 +19,7 @@ from apps.scheduler.pool.loader.metadata import MetadataLoader, MetadataType from apps.scheduler.pool.loader.openapi import OpenAPILoader logger = logging.getLogger(__name__) +BASE_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" / "service" class ServiceLoader: @@ -29,7 +27,7 @@ class ServiceLoader: async def load(self, service_id: str, hashes: dict[str, str]) -> None: """加载单个Service""" - service_path = Path(Config().get_config().deploy.data_dir) / "semantics" / "service" / service_id + service_path = BASE_PATH / service_id # 载入元数据 metadata = await MetadataLoader().load_one(service_path / "metadata.yaml") if not isinstance(metadata, ServiceMetadata): @@ -52,7 +50,7 @@ class ServiceLoader: async def save(self, service_id: str, metadata: ServiceMetadata, data: dict) -> None: """在文件系统上保存Service,并更新数据库""" - service_path = Path(Config().get_config().deploy.data_dir) / "semantics" / "service" / service_id + service_path = BASE_PATH / service_id # 创建文件夹 if not await service_path.exists(): await service_path.mkdir(parents=True, exist_ok=True) @@ -71,8 +69,9 @@ class ServiceLoader: async def delete(self, service_id: str, *, is_reload: bool = False) -> None: """删除Service,并更新数据库""" - service_collection = MongoDB.get_collection("service") - node_collection = MongoDB.get_collection("node") + mongo = MongoDB() + service_collection = mongo.get_collection("service") + node_collection = mongo.get_collection("node") try: await service_collection.delete_one({"_id": service_id}) await node_collection.delete_many({"service_id": service_id}) @@ -91,7 +90,7 @@ class ServiceLoader: logger.exception("[ServiceLoader] 删除数据库失败") if not is_reload: - path = Path(Config().get_config().deploy.data_dir) / "semantics" / "service" / service_id + path = BASE_PATH / service_id if await path.exists(): shutil.rmtree(path) @@ -103,8 +102,9 @@ class ServiceLoader: logger.error(err) raise ValueError(err) # 更新MongoDB - service_collection = MongoDB.get_collection("service") - node_collection = MongoDB.get_collection("node") + mongo = MongoDB() + service_collection = mongo.get_collection("service") + node_collection = mongo.get_collection("node") try: # 先删除旧的节点 await node_collection.delete_many({"service_id": metadata.id}) @@ -148,7 +148,9 @@ class ServiceLoader: embedding=service_vecs[0], ), ] - await service_table.add(service_vector_data) + await service_table.merge_insert("id").when_matched_update_all().when_not_matched_insert_all().execute( + service_vector_data, + ) node_descriptions = [] for node in nodes: @@ -164,4 +166,6 @@ class ServiceLoader: embedding=vec, ), ) - await node_table.add(node_vector_data) + await node_table.merge_insert("id").when_matched_update_all().when_not_matched_insert_all().execute( + node_vector_data, + ) diff --git a/apps/scheduler/pool/mcp/__init__.py b/apps/scheduler/pool/mcp/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..aef0df48db602eebc802fee579c5f9f63ab8e57c --- /dev/null +++ b/apps/scheduler/pool/mcp/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Pool中的MCP模块""" diff --git a/apps/scheduler/pool/mcp/client.py b/apps/scheduler/pool/mcp/client.py new file mode 100644 index 0000000000000000000000000000000000000000..36b8b91a901ccf2defbd4ced4f83ddacbf053aa2 --- /dev/null +++ b/apps/scheduler/pool/mcp/client.py @@ -0,0 +1,122 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MCP Client""" + +import logging +from abc import ABCMeta, abstractmethod + +from anyio import Path +from mcp import ClientSession, StdioServerParameters +from mcp.client.sse import sse_client +from mcp.client.stdio import stdio_client +from mcp.types import CallToolResult + +from apps.common.config import Config +from apps.entities.mcp import MCPServerSSEConfig, MCPServerStdioConfig + +logger = logging.getLogger(__name__) +MCP_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" / "mcp" + +class MCPClient(metaclass=ABCMeta): + """MCP客户端基类""" + + @abstractmethod + async def _create_client( + self, user_sub: str | None, mcp_id: str, config: MCPServerSSEConfig | MCPServerStdioConfig, + ) -> ClientSession: + """ + 创建MCP Client + + 抽象函数;作用为在初始化的时候使用MCP SDK创建Client + 由于目前MCP的实现中Client和Session是1:1的关系,所以直接创建了 :class:`~mcp.ClientSession` + + :param str user_sub: 用户ID + :param str mcp_id: MCP ID + :param MCPServerSSEConfig | MCPServerStdioConfig config: MCP配置 + :return: MCP ClientSession + :rtype: ClientSession + """ + ... + + async def init(self, user_sub: str | None, mcp_id: str, config: MCPServerSSEConfig | MCPServerStdioConfig) -> None: + """ + 初始化 MCP Client类 + + 初始化MCP Client,并创建MCP Server进程和ClientSession + + :param str user_sub: 用户ID + :param str mcp_id: MCP ID + :param MCPServerSSEConfig | MCPServerStdioConfig config: MCP配置 + :return: None + """ + # 初始化变量 + self._config = config + self._session = await self._create_client(user_sub, mcp_id, config) + + # 初始化逻辑 + await self._session.initialize() + self.tools = (await self._session.list_tools()).tools + + async def call_tool(self, tool_name: str, params: dict) -> CallToolResult: + """调用MCP Server的工具""" + return await self._session.call_tool(tool_name, params) + + async def stop(self) -> None: + """停止MCP Client""" + if self._session_context: # type: ignore[attr-defined] + await self._session_context.__aexit__(None, None, None) # type: ignore[attr-defined] + if self._streams_context: # type: ignore[attr-defined] + await self._streams_context.__aexit__(None, None, None) # type: ignore[attr-defined] + + +class SSEMCPClient(MCPClient): + """SSE协议的MCP Client""" + + async def _create_client(self, user_sub: str | None, mcp_id: str, config: MCPServerSSEConfig) -> ClientSession: + """ + 初始化 SSE协议的MCP Client + + :param str user_sub: 用户ID + :param str mcp_id: MCP ID + :param MCPServerSSEConfig config: MCP配置 + :return: SSE协议的MCP Client + :rtype: ClientSession + """ + self._streams_context = sse_client( + url=config.url, + headers=config.env, + ) + streams = await self._streams_context.__aenter__() + + self._session_context = ClientSession(*streams) + return await self._session_context.__aenter__() + + +class StdioMCPClient(MCPClient): + """Stdio协议的MCP Client""" + + async def _create_client(self, user_sub: str | None, mcp_id: str, config: MCPServerStdioConfig) -> ClientSession: + """ + 初始化 Stdio协议的MCP Client + + :param str user_sub: 用户ID + :param str mcp_id: MCP ID + :param MCPServerStdioConfig config: MCP配置 + :return: Stdio协议的MCP Client + :rtype: ClientSession + """ + if user_sub: + cwd = MCP_PATH / "users" / user_sub / mcp_id / "project" + else: + cwd = MCP_PATH / "template" / mcp_id / "project" + await cwd.mkdir(parents=True, exist_ok=True) + server_params = StdioServerParameters( + command=config.command, + args=config.args, + env=config.env, + cwd=cwd.as_posix(), + ) + + self._streams_context = stdio_client(server=server_params) + streams = await self._streams_context.__aenter__() + self._session_context = ClientSession(*streams) + return await self._session_context.__aenter__() diff --git a/apps/scheduler/pool/mcp/default.py b/apps/scheduler/pool/mcp/default.py new file mode 100644 index 0000000000000000000000000000000000000000..0cd0007149c768af7629fbdd1d1fa482c6d7077d --- /dev/null +++ b/apps/scheduler/pool/mcp/default.py @@ -0,0 +1,68 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MCP 默认配置""" + +import logging +import random + +from sqids.sqids import Sqids + +from apps.entities.mcp import ( + MCPConfig, + MCPServerSSEConfig, + MCPServerStdioConfig, + MCPType, +) + +logger = logging.getLogger(__name__) +sqids = Sqids(min_length=10) +DEFAULT_STDIO = MCPServerStdioConfig( + name="MCP服务名称", + description="MCP服务描述", + type=MCPType.STDIO, + command="uvx", + args=[ + "your_package", + ], + env={ + "EXAMPLE_ENV": "example_value", + }, +) +"""默认的Stdio协议MCP Server配置""" + +DEFAULT_SSE = MCPServerSSEConfig( + name="MCP服务名称", + description="MCP服务描述", + type=MCPType.SSE, + url="http://test.domain/sse", + env={ + "EXAMPLE_HEADER": "example_value", + }, +) +"""默认的SSE协议MCP Server配置""" + + +async def get_default(mcp_type: MCPType) -> MCPConfig: + """ + 用于获取默认的 MCP 配置 + + :param MCPType mcp_type: MCP类型 + :return: MCP配置 + :rtype: MCPConfig + :raises ValueError: 未找到默认的 MCP 配置 + """ + random_list = [random.randint(0, 1000000) for _ in range(5)] # noqa: S311 + if mcp_type == MCPType.STDIO: + return MCPConfig( + mcpServers={ + sqids.encode(random_list): DEFAULT_STDIO, + }, + ) + if mcp_type == MCPType.SSE: + return MCPConfig( + mcpServers={ + sqids.encode(random_list): DEFAULT_SSE, + }, + ) + err = f"未找到默认的 MCP 配置: {mcp_type}" + logger.error(err) + raise ValueError(err) diff --git a/apps/scheduler/pool/mcp/install.py b/apps/scheduler/pool/mcp/install.py new file mode 100644 index 0000000000000000000000000000000000000000..60790daf3b0f3ef13853ca3c20874d88dea50e40 --- /dev/null +++ b/apps/scheduler/pool/mcp/install.py @@ -0,0 +1,155 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MCP 安装""" + +import logging +from asyncio import subprocess + +from anyio import Path + +from apps.common.config import Config +from apps.entities.mcp import MCPServerStdioConfig + +logger = logging.getLogger(__name__) +MCP_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" / "mcp" + + +async def install_uvx(mcp_id: str, config: MCPServerStdioConfig) -> MCPServerStdioConfig: + """ + 安装使用uvx包管理器的MCP服务 + + 安装在 ``template`` 目录下,会作为可拷贝的MCP模板 + + :param str mcp_id: MCP模板ID + :param MCPServerStdioConfig config: MCP配置 + :return: MCP配置 + :rtype: MCPServerStdioConfig + :raises ValueError: 未找到MCP Server对应的Python包 + """ + # 创建文件夹 + mcp_path = MCP_PATH / "template" / mcp_id / "project" + await mcp_path.mkdir(parents=True, exist_ok=True) + + # 找到包名 + package = "" + for arg in config.args: + if not arg.startswith("-"): + package = arg + break + + if not package: + err = "未找到包名" + logger.error(err) + raise ValueError(err) + + # 如果有pyproject.toml文件,则使用sync + if await (mcp_path / "pyproject.toml").exists(): + pipe = await subprocess.create_subprocess_exec( + "uv", + "sync", + "--index-url", + "https://pypi.tuna.tsinghua.edu.cn/simple", + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + cwd=mcp_path, + ) + stdout, _ = await pipe.communicate() + if pipe.returncode != 0: + err = f"[MCPLoader] 检查依赖失败: {stdout.decode() if stdout else '(无输出信息)'}" + logger.error(err) + raise ValueError(err) + logger.info("[MCPLoader] 检查依赖成功: %s; %s", mcp_path, stdout.decode() if stdout else "(无输出信息)") + + config.command = "uv" + config.args = ["run", *config.args] + + return config + + # 否则,初始化uv项目 + pipe = await subprocess.create_subprocess_exec( + "uv", + "init", + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + cwd=mcp_path, + ) + stdout, _ = await pipe.communicate() + + # 安装Python包 + pipe = await subprocess.create_subprocess_exec( + "uv", + "add", + "--index-url", + "https://pypi.tuna.tsinghua.edu.cn/simple", + package, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + cwd=mcp_path, + ) + stdout, _ = await pipe.communicate() + if pipe.returncode != 0: + err = f"[MCPLoader] 安装 {package} 失败: {stdout.decode() if stdout else '(无输出信息)'}" + logger.error(err) + raise ValueError(err) + logger.info("[MCPLoader] 安装 %s 成功: %s; %s", package, mcp_path, stdout.decode() if stdout else "(无输出信息)") + + # 更新配置 + config.command = "uv" + config.args = ["run", *config.args] + + return config + + +async def install_npx(mcp_id: str, config: MCPServerStdioConfig) -> MCPServerStdioConfig: + """ + 安装使用npx包管理器的MCP服务 + + 安装在 ``template`` 目录下,会作为可拷贝的MCP模板 + + :param str mcp_id: MCP模板ID + :param MCPServerStdioConfig config: MCP配置 + :return: MCP配置 + :rtype: MCPServerStdioConfig + :raises ValueError: 未找到MCP Server对应的npm包 + """ + mcp_path = MCP_PATH / "template" / mcp_id / "project" + await mcp_path.mkdir(parents=True, exist_ok=True) + + # 如果有node_modules文件夹,则认为已安装 + if await (mcp_path / "node_modules").exists(): + config.command = "npm" + config.args = ["exec", *config.args] + return config + + # 查找package name + package = "" + for arg in config.args: + if not arg.startswith("-"): + package = arg + break + + if not package: + err = "未找到包名" + logger.error(err) + raise ValueError(err) + + # 安装NPM包 + pipe = await subprocess.create_subprocess_exec( + "npm", + "install", + package, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + cwd=mcp_path, + ) + stdout, _ = await pipe.communicate() + if pipe.returncode != 0: + err = f"[MCPLoader] 安装 {package} 失败: {stdout.decode() if stdout else '(无输出信息)'}" + logger.error(err) + raise ValueError(err) + logger.info("[MCPLoader] 安装 %s 成功: %s; %s", package, mcp_path, stdout.decode() if stdout else "(无输出信息)") + + # 更新配置 + config.command = "npm" + config.args = ["exec", *config.args] + + return config diff --git a/apps/scheduler/pool/mcp/pool.py b/apps/scheduler/pool/mcp/pool.py new file mode 100644 index 0000000000000000000000000000000000000000..081cb8f0b96f06c2cef1eccbe254b86402e649d0 --- /dev/null +++ b/apps/scheduler/pool/mcp/pool.py @@ -0,0 +1,93 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""MCP池""" + +import logging + +from anyio import Path + +from apps.common.config import Config +from apps.common.singleton import SingletonMeta +from apps.entities.mcp import MCPConfig, MCPType +from apps.models.mongo import MongoDB +from apps.scheduler.pool.mcp.client import SSEMCPClient, StdioMCPClient + +logger = logging.getLogger(__name__) +MCP_USER_PATH = MCP_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" / "mcp" / "users" + + +class MCPPool(metaclass=SingletonMeta): + """MCP池""" + + def __init__(self) -> None: + """初始化MCP池""" + self.pool = {} + + + async def _init_mcp(self, mcp_id: str, user_sub: str) -> SSEMCPClient | StdioMCPClient | None: + """初始化MCP池""" + mcp_math = MCP_USER_PATH / user_sub / mcp_id / "project" + config_path = MCP_USER_PATH / user_sub / mcp_id / "config.json" + + if not await mcp_math.exists() or not await mcp_math.is_dir(): + logger.warning("[MCPPool] 用户 %s 的MCP %s 未激活", user_sub, mcp_id) + return None + + config = MCPConfig.model_validate_json(await config_path.read_text()) + server_config = next(iter(config.mcp_servers.values())) + + if server_config.type == MCPType.SSE: + client = SSEMCPClient() + elif server_config.type == MCPType.STDIO: + client = StdioMCPClient() + else: + logger.warning("[MCPPool] 用户 %s 的MCP %s 类型错误", user_sub, mcp_id) + return None + + await client.init(user_sub, mcp_id, server_config) + return client + + + async def _get_from_dict(self, mcp_id: str, user_sub: str) -> SSEMCPClient | StdioMCPClient | None: + """从字典中获取MCP客户端""" + if user_sub not in self.pool: + return None + + if mcp_id not in self.pool[user_sub]: + return None + + return self.pool[user_sub][mcp_id] + + + async def _validate_user(self, mcp_id: str, user_sub: str) -> bool: + """验证用户是否已激活""" + mongo = MongoDB() + mcp_collection = mongo.get_collection("mcp") + mcp_db_result = await mcp_collection.find_one({"_id": mcp_id, "activated": user_sub}) + return mcp_db_result is not None + + + async def get(self, mcp_id: str, user_sub: str) -> SSEMCPClient | StdioMCPClient | None: + """获取MCP客户端""" + item = await self._get_from_dict(mcp_id, user_sub) + if item is None: + # 检查用户是否已激活 + if not await self._validate_user(mcp_id, user_sub): + logger.warning("用户 %s 未激活MCP %s", user_sub, mcp_id) + return None + + # 初始化进程 + item = await self._init_mcp(mcp_id, user_sub) + if item is None: + return None + + if user_sub not in self.pool: + self.pool[user_sub] = {} + + self.pool[user_sub][mcp_id] = item + + return item + + + async def stop(self, mcp_id: str, user_sub: str) -> None: + """停止MCP客户端""" + await self.pool[mcp_id][user_sub].stop() diff --git a/apps/scheduler/pool/pool.py b/apps/scheduler/pool/pool.py index e45926a2a67f763a00137e80cd5c6ae54aeab368..e394ea90d8922e7451fde0fed853d90ffe878ec7 100644 --- a/apps/scheduler/pool/pool.py +++ b/apps/scheduler/pool/pool.py @@ -1,8 +1,6 @@ -""" -资源池,包含语义接口、应用等的载入和保存 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""资源池,包含语义接口、应用等的载入和保存""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" import importlib import logging from typing import Any @@ -10,7 +8,6 @@ from typing import Any from anyio import Path from apps.common.config import Config -from apps.common.singleton import SingletonMeta from apps.entities.enum_var import MetadataType from apps.entities.flow import Flow from apps.entities.pool import AppFlow, CallPool @@ -20,34 +17,69 @@ from apps.scheduler.pool.loader import ( AppLoader, CallLoader, FlowLoader, + MCPLoader, ServiceLoader, ) logger = logging.getLogger(__name__) -class Pool(metaclass=SingletonMeta): - """资源池""" +class Pool: + """ + 资源池 + + 在Framework启动时,执行全局的载入流程;同时在内存中维持部分变量,满足MCP、Call等含Python类的模块能够驻留在内存中。 + """ @staticmethod async def check_dir() -> None: - """检查文件夹是否存在""" + """ + 检查必要的文件夹是否存在 + + 检查 ``data_dir`` 目录下是否存在 ``semantics/`` 目录, + 并检查是否存在 ``app/``、``service/``、``call/``、``mcp/`` 目录。 + 如果目录不存在,则自动创建目录。 + + :return: 无 + """ root_dir = Config().get_config().deploy.data_dir.rstrip("/") + "/semantics/" - if not await Path(root_dir + "app").exists(): + if not await Path(root_dir + "app").exists() or not await Path(root_dir + "app").is_dir(): logger.warning("[Pool] App目录%s不存在,创建中", root_dir + "app") + await Path(root_dir + "app").unlink(missing_ok=True) await Path(root_dir + "app").mkdir(parents=True, exist_ok=True) - if not await Path(root_dir + "service").exists(): + if not await Path(root_dir + "service").exists() or not await Path(root_dir + "service").is_dir(): logger.warning("[Pool] Service目录%s不存在,创建中", root_dir + "service") + await Path(root_dir + "service").unlink(missing_ok=True) await Path(root_dir + "service").mkdir(parents=True, exist_ok=True) - if not await Path(root_dir + "call").exists(): + if not await Path(root_dir + "call").exists() or not await Path(root_dir + "call").is_dir(): logger.warning("[Pool] Call目录%s不存在,创建中", root_dir + "call") + await Path(root_dir + "call").unlink(missing_ok=True) await Path(root_dir + "call").mkdir(parents=True, exist_ok=True) + if not await Path(root_dir + "mcp").exists() or not await Path(root_dir + "mcp").is_dir(): + logger.warning("[Pool] MCP目录%s不存在,创建中", root_dir + "mcp") + await Path(root_dir + "mcp").unlink(missing_ok=True) + await Path(root_dir + "mcp").mkdir(parents=True, exist_ok=True) - async def init(self) -> None: - """加载全部文件系统内的资源""" + @staticmethod + async def init() -> None: + """ + 加载全部文件系统内的资源 + + 包含: + + - 检查文件变动 + - 载入Call + - 载入Service + - 载入App + - 载入MCP + + 这一流程在Framework启动时执行。 + + :return: 无 + """ # 检查文件夹是否存在 - await self.check_dir() + await Pool.check_dir() # 加载Call logger.info("[Pool] 载入Call") @@ -91,10 +123,15 @@ class Pool(metaclass=SingletonMeta): if hash_key in checker.hashes: await app_loader.load(app, checker.hashes[hash_key]) + # 载入MCP + logger.info("[Pool] 载入MCP") + await MCPLoader.init() + async def get_flow_metadata(self, app_id: str) -> list[AppFlow]: """从数据库中获取特定App的全部Flow的元数据""" - app_collection = MongoDB.get_collection("app") + mongo = MongoDB() + app_collection = mongo.get_collection("app") flow_metadata_list = [] try: flow_list = await app_collection.find_one({"_id": app_id}, {"flows": 1}) @@ -119,7 +156,8 @@ class Pool(metaclass=SingletonMeta): async def get_call(self, call_id: str) -> Any: """[Exception] 拿到Call的信息""" # 从MongoDB里拿到数据 - call_collection = MongoDB.get_collection("call") + mongo = MongoDB() + call_collection = mongo.get_collection("call") call_db_data = await call_collection.find_one({"_id": call_id}) if not call_db_data: err = f"[Pool] Call{call_id}不存在" diff --git a/apps/scheduler/scheduler/__init__.py b/apps/scheduler/scheduler/__init__.py index c33a109c8cae5c8e3d933e687873fe61832b80da..a2f9eed40bd90e3bf9a3fbd6d1458dcb3c834978 100644 --- a/apps/scheduler/scheduler/__init__.py +++ b/apps/scheduler/scheduler/__init__.py @@ -1,7 +1,5 @@ -"""调度器模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""调度器模块""" from apps.scheduler.scheduler.scheduler import Scheduler diff --git a/apps/scheduler/scheduler/context.py b/apps/scheduler/scheduler/context.py index 6089e244397b447c5e1f7698e85d33bb29d49cb5..b185d7e8fc74442eee5c3e08bfba3813e40a7012 100644 --- a/apps/scheduler/scheduler/context.py +++ b/apps/scheduler/scheduler/context.py @@ -1,8 +1,6 @@ -""" -上下文管理 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""上下文管理""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" import logging from datetime import UTC, datetime @@ -50,6 +48,22 @@ async def get_docs(user_sub: str, post_body: RequestData) -> tuple[list[RecordDo return docs, doc_ids +async def assemble_history(history: list[dict[str, str]]) -> str: + """ + 组装历史问题 + + :param history: 历史问题列表 + :return: 组装后的字符串 + """ + history_str = "" + for item in history: + role = item.get("role") + content = item.get("content") + if role and content: + history_str += f"{role}: {content}\n" + return history_str.strip() + + async def get_context(user_sub: str, post_body: RequestData, n: int) -> tuple[list[dict[str, str]], list[str]]: """ 获取当前问答的上下文信息 diff --git a/apps/scheduler/scheduler/flow.py b/apps/scheduler/scheduler/flow.py index af38d06812b3b3b5ec51c457f455c777fd21b793..98e5c1f275159c7ec89ac6d2f3d53c7335cfc3dc 100644 --- a/apps/scheduler/scheduler/flow.py +++ b/apps/scheduler/scheduler/flow.py @@ -1,8 +1,5 @@ -""" -Scheduler中,关于Flow的逻辑 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Scheduler中,关于Flow的逻辑""" import logging @@ -10,7 +7,7 @@ from apps.entities.flow import Flow, FlowError, Step from apps.entities.request_data import RequestDataApp from apps.entities.task import Task from apps.llm.patterns import Select -from apps.scheduler.call.llm.schema import RAG_ANSWER_PROMPT +from apps.scheduler.call.llm.prompt import RAG_ANSWER_PROMPT from apps.scheduler.pool.pool import Pool logger = logging.getLogger(__name__) diff --git a/apps/scheduler/scheduler/graph.py b/apps/scheduler/scheduler/graph.py index 9dd562621a16b26f29fb2b4a28b7d0001d42d5a6..1a2560d5983e15420307756af31ad1c73c84e89f 100644 --- a/apps/scheduler/scheduler/graph.py +++ b/apps/scheduler/scheduler/graph.py @@ -1,2 +1,2 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """工作流DAG相关操作""" - diff --git a/apps/scheduler/scheduler/message.py b/apps/scheduler/scheduler/message.py index cb8d54bcfbc4894c034e7d42fe98bc50c35474e9..72ecc9591bbd7e368e00dfbf41f58b769814a9e0 100644 --- a/apps/scheduler/scheduler/message.py +++ b/apps/scheduler/scheduler/message.py @@ -1,8 +1,5 @@ -""" -Scheduler消息推送 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Scheduler消息推送""" import logging from datetime import UTC, datetime @@ -10,7 +7,7 @@ from textwrap import dedent from apps.common.config import Config from apps.common.queue import MessageQueue -from apps.entities.collection import Document +from apps.entities.collection import LLM, Document from apps.entities.enum_var import EventType from apps.entities.message import ( DocumentAddContent, @@ -62,12 +59,12 @@ async def push_init_message( async def push_rag_message( - task: Task, queue: MessageQueue, user_sub: str, rag_data: RAGQueryReq, + task: Task, queue: MessageQueue, user_sub: str, llm: LLM, history: list[dict[str, str]], rag_data: RAGQueryReq, ) -> Task: """推送RAG消息""" full_answer = "" - async for chunk in RAG.get_rag_result(user_sub, rag_data): + async for chunk in RAG.get_rag_result(user_sub, llm, history, rag_data): task, chunk_content = await _push_rag_chunk(task, queue, chunk) full_answer += chunk_content diff --git a/apps/scheduler/scheduler/scheduler.py b/apps/scheduler/scheduler/scheduler.py index dd1647373d101c792ddb704916495c9a84d31f58..ebf7201e60e36418e3ef13d6b9e37a906c8aad18 100644 --- a/apps/scheduler/scheduler/scheduler.py +++ b/apps/scheduler/scheduler/scheduler.py @@ -1,21 +1,21 @@ -""" -Scheduler模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Scheduler模块""" import asyncio import logging from datetime import UTC, datetime +from apps.common.config import Config from apps.common.queue import MessageQueue +from apps.entities.collection import LLM from apps.entities.enum_var import EventType from apps.entities.rag_data import RAGQueryReq from apps.entities.request_data import RequestData from apps.entities.scheduler import ExecutorBackground from apps.entities.task import Task from apps.manager.appcenter import AppCenterManager -from apps.manager.user import UserManager +from apps.manager.knowledge import KnowledgeBaseManager +from apps.manager.llm import LLMManager from apps.scheduler.executor.flow import FlowExecutor from apps.scheduler.pool.pool import Pool from apps.scheduler.scheduler.context import get_context, get_docs @@ -44,9 +44,44 @@ class Scheduler: self.queue = queue self.post_body = post_body - - async def run(self) -> None: + async def run(self) -> None: # noqa: PLR0911 """运行调度器""" + try: + # 获取当前会话使用的大模型 + llm_id = await LLMManager.get_llm_id_by_conversation_id( + self.task.ids.user_sub, self.task.ids.conversation_id, + ) + if not llm_id: + logger.error("[Scheduler] 获取大模型ID失败") + await self.queue.close() + return + if llm_id == "empty": + llm = LLM( + _id="empty", + user_sub=self.task.ids.user_sub, + openai_base_url=Config().get_config().llm.endpoint, + openai_api_key=Config().get_config().llm.key, + model_name=Config().get_config().llm.model, + max_tokens=Config().get_config().llm.max_tokens, + ) + else: + llm = await LLMManager.get_llm_by_id(self.task.ids.user_sub, llm_id) + if not llm: + logger.error("[Scheduler] 获取大模型失败") + await self.queue.close() + return + except Exception: + logger.exception("[Scheduler] 获取大模型失败") + await self.queue.close() + return + try: + # 获取当前会话使用的知识库 + kb_ids = await KnowledgeBaseManager.get_kb_ids_by_conversation_id( + self.task.ids.user_sub, self.task.ids.conversation_id) + except Exception: + logger.exception("[Scheduler] 获取知识库ID失败") + await self.queue.close() + return try: # 获取当前问答可供关联的文档 docs, doc_ids = await get_docs(self.task.ids.user_sub, self.post_body) @@ -54,13 +89,7 @@ class Scheduler: logger.exception("[Scheduler] 获取文档失败") await self.queue.close() return - - # 获取用户配置的kb_sn - user_info = await UserManager.get_userinfo_by_user_sub(self.task.ids.user_sub) - if not user_info: - logger.error("[Scheduler] 未找到用户") - await self.queue.close() - return + history, _ = await get_context(self.task.ids.user_sub, self.post_body, 3) # 已使用文档 self.used_docs = [] @@ -75,20 +104,12 @@ class Scheduler: self.used_docs.append(doc.id) self.task = await push_document_message(self.task, self.queue, doc) await asyncio.sleep(0.1) - - # 调用RAG - logger.info("[Scheduler] 获取上下文") - history, _ = await get_context(self.task.ids.user_sub, self.post_body, 3) - logger.info("[Scheduler] 调用RAG") rag_data = RAGQueryReq( - question=self.post_body.question, - language=self.post_body.language, - document_ids=doc_ids, - kb_sn=None if not user_info.kb_id else user_info.kb_id, - top_k=5, - history=history, + kbIds=kb_ids, + query=self.post_body.question, + tokensLimit=llm.max_tokens, ) - self.task = await push_rag_message(self.task, self.queue, self.task.ids.user_sub, rag_data) + self.task = await push_rag_message(self.task, self.queue, self.task.ids.user_sub, llm, history, rag_data) self.task.tokens.full_time = round(datetime.now(UTC).timestamp(), 2) - self.task.tokens.time else: # 查找对应的App元数据 @@ -118,7 +139,6 @@ class Scheduler: return - async def run_executor( self, queue: MessageQueue, post_body: RequestData, background: ExecutorBackground, ) -> None: diff --git a/apps/scheduler/slot/__init__.py b/apps/scheduler/slot/__init__.py index dedfffbe6a0f3ea1a4426c280649cf727c01db99..57537ef40247d7dde9e90fa78b9afa76aaa32f14 100644 --- a/apps/scheduler/slot/__init__.py +++ b/apps/scheduler/slot/__init__.py @@ -1,5 +1,2 @@ -""" -参数槽模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""参数槽模块""" diff --git a/apps/scheduler/slot/parser/__init__.py b/apps/scheduler/slot/parser/__init__.py index 29f5f0c44dcafae8a85fbc1209b148f99a3376aa..81ae4413f3b2855b282d38fd770059b29370dfb2 100644 --- a/apps/scheduler/slot/parser/__init__.py +++ b/apps/scheduler/slot/parser/__init__.py @@ -1,8 +1,5 @@ -""" -Slot处理模块 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""Slot处理模块""" from apps.scheduler.slot.parser.const import SlotConstParser from apps.scheduler.slot.parser.date import SlotDateParser diff --git a/apps/scheduler/slot/parser/const.py b/apps/scheduler/slot/parser/const.py index 3c0232302d51e8f6d4b050ae1c4e87f84e935b59..d0f3a1dcfe2a7f93e1fe47d45e8274f61933011e 100644 --- a/apps/scheduler/slot/parser/const.py +++ b/apps/scheduler/slot/parser/const.py @@ -1,8 +1,5 @@ -""" -固定值设置器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""固定值设置器""" from typing import Any diff --git a/apps/scheduler/slot/parser/date.py b/apps/scheduler/slot/parser/date.py index ee59318963a34ec7f1a2d23808a3b311617325b2..411e0f9becb5b70ae4a31033726b5867c5031ec9 100644 --- a/apps/scheduler/slot/parser/date.py +++ b/apps/scheduler/slot/parser/date.py @@ -1,8 +1,5 @@ -""" -日期解析器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""日期解析器""" import logging from datetime import datetime diff --git a/apps/scheduler/slot/parser/default.py b/apps/scheduler/slot/parser/default.py index 548d66b2c021be853443bd326edef3c654ee61a4..c1be0fbddcb2e3b1b38d86269926f077259f3604 100644 --- a/apps/scheduler/slot/parser/default.py +++ b/apps/scheduler/slot/parser/default.py @@ -1,8 +1,5 @@ -""" -默认值设置器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""默认值设置器""" from typing import Any diff --git a/apps/scheduler/slot/parser/timestamp.py b/apps/scheduler/slot/parser/timestamp.py index 9bb8c30f7efe67dbcd26bcb0b965b0104ee390b7..86b4a61695876afbe84c341efe534b609fff0d48 100644 --- a/apps/scheduler/slot/parser/timestamp.py +++ b/apps/scheduler/slot/parser/timestamp.py @@ -1,8 +1,5 @@ -""" -时间戳解析器 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""时间戳解析器""" import logging from datetime import datetime diff --git a/apps/scheduler/slot/slot.py b/apps/scheduler/slot/slot.py index 46a4f656d46b3002061f0f9c095332bfe2ac402a..4caab4d2dc945ec5ac14b7769770f94c39980a9f 100644 --- a/apps/scheduler/slot/slot.py +++ b/apps/scheduler/slot/slot.py @@ -1,8 +1,5 @@ -""" -参数槽位管理 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""参数槽位管理""" import json import logging @@ -154,51 +151,53 @@ class Slot: # 遍历JSON,处理每一个字段 return _process_json_value(json_data, self._schema) - def create_empty_slot(self) -> dict[str, Any]: - """创建一个空的槽位""" - def _generate_example(schema_node: dict) -> Any: # noqa: PLR0911 - if "default" in schema_node: - return schema_node["default"] + @staticmethod + def _generate_example(schema_node: dict) -> Any: # noqa: PLR0911 + """根据schema生成示例值""" + if "default" in schema_node: + return schema_node["default"] - if "type" not in schema_node: - return None + if "type" not in schema_node: + return None - # 处理类型为 object 的节点 - if schema_node["type"] == "object": - data = {} - properties = schema_node.get("properties", {}) - for name, schema in properties.items(): - data[name] = _generate_example(schema) - return data + # 处理类型为 object 的节点 + if schema_node["type"] == "object": + data = {} + properties = schema_node.get("properties", {}) + for name, schema in properties.items(): + data[name] = Slot._generate_example(schema) + return data - # 处理类型为 array 的节点 - if schema_node["type"] == "array": - items_schema = schema_node.get("items", {}) - return [_generate_example(items_schema)] + # 处理类型为 array 的节点 + if schema_node["type"] == "array": + items_schema = schema_node.get("items", {}) + return [Slot._generate_example(items_schema)] - # 处理类型为 string 的节点 - if schema_node["type"] == "string": - return "" + # 处理类型为 string 的节点 + if schema_node["type"] == "string": + return "" - # 处理类型为 number 或 integer 的节点 - if schema_node["type"] in ["number", "integer"]: - return 0 + # 处理类型为 number 或 integer 的节点 + if schema_node["type"] in ["number", "integer"]: + return 0 - # 处理类型为 boolean 的节点 - if schema_node["type"] == "boolean": - return False + # 处理类型为 boolean 的节点 + if schema_node["type"] == "boolean": + return False - # 处理其他类型或未定义类型 - return None + # 处理其他类型或未定义类型 + return None - return _generate_example(self._schema) + def create_empty_slot(self) -> dict[str, Any]: + """创建一个空的槽位""" + return self._generate_example(self._schema) def extract_type_desc_from_schema(self) -> dict[str, str]: """从JSON Schema中提取类型描述""" - def _extract_type_desc(schema_node: dict[str, Any]) -> None: + def _extract_type_desc(schema_node: dict[str, Any]) -> dict[str, Any]: if "type" not in schema_node and "anyOf" not in schema_node: - return None + return {} data = {"type": schema_node.get("type", ""), "description": schema_node.get("description", "")} if "anyOf" in schema_node: data["type"] = "anyOf" @@ -206,9 +205,8 @@ class Slot: if "anyOf" in schema_node: data["items"] = {} type_index = 0 - for sub_schema in schema_node["anyOf"]: + for type_index, sub_schema in enumerate(schema_node["anyOf"]): sub_result = _extract_type_desc(sub_schema) - type_index += 1 if sub_result: data["items"]["type_"+str(type_index)] = sub_result if schema_node.get("type", "") == "object": @@ -278,23 +276,73 @@ class Slot: logger.exception("[Slot] 错误schema不合法: %s", error.schema) return {}, [] + + def _assemble_patch( + self, + key: str, + val: Any, + json_data: Any, + schema: dict[str, Any], + ) -> list[dict[str, Any]]: + """将用户手动填充的参数专为真实JSON""" + patch_list = [] + key_path = key.split("/") + + current_path = "/" + current_schema = schema + for path in key_path: + if path == "": + # 空路径,跳过 + continue + if (current_path + path) == key: + # 当前路径与key相同,直接返回 + patch_list.append({"op": "add", "path": current_path + path, "value": val}) + return patch_list + + # 如果是数字,访问元素 + if path.isdigit() and isinstance(json_data, list): + try: + json_data = json_data[int(path)] + current_schema = current_schema["items"] + except (IndexError, KeyError): + # 检测当前数组元素的类型,并建一个空值 + empty_value = self._generate_example(current_schema["items"]) + patch_list.append({"op": "add", "path": current_path + "-", "value": [empty_value]}) + json_data = empty_value + current_schema = current_schema["items"] + # 如果是字符串,访问对象 + elif isinstance(json_data, dict): + try: + json_data = json_data[path] + current_schema = current_schema["properties"][path] + except (KeyError, IndexError): + patch_list.append({"op": "add", "path": current_path + path, "value": {}}) + json_data = {} + current_schema = current_schema["properties"][path] + else: + err = f"[Slot] 错误的路径: {key}" + logger.exception(err) + raise ValueError(err) + + current_path = current_path + path + "/" + + logger.info("[Slot] 组装patch: %s", patch_list) + return patch_list + + def convert_json(self, json_data: str | dict[str, Any]) -> dict[str, Any]: """将用户手动填充的参数专为真实JSON""" json_dict = json.loads(json_data) if isinstance(json_data, str) else json_data + final_json = {} # 对JSON进行处理 - patch_list = [] - plain_data = {} for key, val in json_dict.items(): # 如果是patch,则构建 if key[0] == "/": - patch_list.append({"op": "add", "path": key, "value": val}) + patch_list = self._assemble_patch(key, val, final_json, self._schema) + final_json = patch_json(patch_list, final_json) else: - plain_data[key] = val - - # 对JSON进行patch - final_json = patch_json(patch_list) - final_json.update(plain_data) + final_json[key] = val return final_json @@ -318,6 +366,7 @@ class Slot: if additional_path: pointer = pointer.rstrip("/") + "/" + "/".join(additional_path) schema_template["properties"][pointer] = slot_schema + schema_template["required"].append(pointer) # 如果有错误 if not empty: diff --git a/apps/scheduler/slot/util.py b/apps/scheduler/slot/util.py index cb7cdedcc339b863bf9c4a31e67a0d03a1640f08..8faa6bade5c3445625d39ed875abbdb86cd1d7e5 100644 --- a/apps/scheduler/slot/util.py +++ b/apps/scheduler/slot/util.py @@ -1,8 +1,5 @@ -""" -JSON处理函数 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""JSON处理函数""" import logging from typing import Any @@ -18,9 +15,12 @@ def escape_path(key: str) -> str: return key.replace("/", "~1") -def patch_json(operation_list: list[dict[str, Any]]) -> dict[str, Any]: +def patch_json(operation_list: list[dict[str, Any]], json_data: dict[str, Any] | None = None) -> dict[str, Any]: """应用JSON Patch,获得JSON数据""" - json_data = {} + if json_data is None: + json_data = {} + + operation_list.reverse() while operation_list: current_operation = operation_list.pop() diff --git a/apps/scheduler/util.py b/apps/scheduler/util.py index f4f44b667c446e0c9d2eb51c41c652c44b1d217f..9d6e4295cc9b8c559dce024093bbc351ca6ca0d9 100644 --- a/apps/scheduler/util.py +++ b/apps/scheduler/util.py @@ -1,3 +1,4 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """YAML表示器""" def yaml_str_presenter(dumper, data): # noqa: ANN001, ANN201, D103 diff --git a/apps/scripts/delete_user.py b/apps/scripts/delete_user.py index 42e6bf36887e83b03f4d806da7f745cb822b4a06..f2fb8396a4117ed2996489563e0b9c24d2ed9bdc 100644 --- a/apps/scripts/delete_user.py +++ b/apps/scripts/delete_user.py @@ -1,8 +1,5 @@ -""" -删除30天未登录用户 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""删除30天未登录用户""" import logging from datetime import UTC, datetime, timedelta @@ -24,7 +21,7 @@ async def _delete_user(timestamp: float) -> None: for user_id in user_ids: await UserManager.delete_userinfo_by_user_sub(user_id) # 查找用户关联的文件 - doc_collection = MongoDB.get_collection("document") + doc_collection = MongoDB().get_collection("document") docs = [doc["_id"] async for doc in doc_collection.find({"user_sub": user_id})] # 删除文件 try: diff --git a/apps/scripts/get_api_doc.py b/apps/scripts/get_api_doc.py index 7548f5dbae216ea51873cee26aa11b78fc837264..fd71a76c260fe3cdbb6b0f389f7e1002a19d6720 100644 --- a/apps/scripts/get_api_doc.py +++ b/apps/scripts/get_api_doc.py @@ -1,8 +1,5 @@ -""" -生成FastAPI OpenAPI文档 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""生成FastAPI OpenAPI文档""" from __future__ import annotations diff --git a/apps/scripts/user_exporter.py b/apps/scripts/user_exporter.py index cbd1a98bd9965e96ac2f0b776ed493c04d857d07..f31a6adc20bdfcad6724f758798bd2196552771d 100644 --- a/apps/scripts/user_exporter.py +++ b/apps/scripts/user_exporter.py @@ -1,8 +1,5 @@ -""" -用户导出工具 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""用户导出工具""" from __future__ import annotations diff --git a/apps/service/__init__.py b/apps/service/__init__.py index 624c7d040db45f7c8746d4fcc2d85fb78a9b1c71..36e7e9a1e71a2e1263a96f76448004c57f499070 100644 --- a/apps/service/__init__.py +++ b/apps/service/__init__.py @@ -1,7 +1,6 @@ -"""服务层 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""服务层""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" from apps.service.activity import Activity from apps.service.knowledge_base import KnowledgeBaseService from apps.service.rag import RAG diff --git a/apps/service/activity.py b/apps/service/activity.py index 65fcda1bb736169e693697864b4e4b14cdd4712e..fb1f6ca8d6b2e2898bee948ce33efee7d0b3b35e 100644 --- a/apps/service/activity.py +++ b/apps/service/activity.py @@ -1,8 +1,5 @@ -""" -用户限流 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""用户限流""" import uuid from datetime import UTC, datetime @@ -26,14 +23,14 @@ class Activity: time = round(datetime.now(UTC).timestamp(), 3) # 检查窗口内总请求数 - count = await MongoDB.get_collection("activity").count_documents( + count = await MongoDB().get_collection("activity").count_documents( {"timestamp": {"$gte": time - SLIDE_WINDOW_TIME, "$lte": time}}, ) if count >= SLIDE_WINDOW_QUESTION_COUNT: return True # 检查用户是否正在提问 - active = await MongoDB.get_collection("activity").find_one( + active = await MongoDB().get_collection("activity").find_one( {"user_sub": user_sub}, ) return bool(active) @@ -43,7 +40,7 @@ class Activity: """设置用户的活跃标识""" time = round(datetime.now(UTC).timestamp(), 3) # 设置用户活跃状态 - collection = MongoDB.get_collection("activity") + collection = MongoDB().get_collection("activity") active = await collection.find_one({"user_sub": user_sub}) if active: err = "用户正在提问" @@ -65,11 +62,11 @@ class Activity: """ time = round(datetime.now(UTC).timestamp(), 3) # 清除用户当前活动标识 - await MongoDB.get_collection("activity").delete_one( + await MongoDB().get_collection("activity").delete_one( {"user_sub": user_sub}, ) # 清除超出窗口范围的请求记录 - await MongoDB.get_collection("activity").delete_many( + await MongoDB().get_collection("activity").delete_many( {"timestamp": {"$lte": time - SLIDE_WINDOW_TIME}}, ) diff --git a/apps/service/flow.py b/apps/service/flow.py index f18d4e4f56dfa7c29035f82fce704442c709d2fc..a7d83fc96aeae6d6bdcfafb621beeff3c49791bc 100644 --- a/apps/service/flow.py +++ b/apps/service/flow.py @@ -1,12 +1,8 @@ -""" -flow拓扑相关函数 - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""flow拓扑相关函数""" import collections import logging -from typing import Any from apps.entities.enum_var import NodeType from apps.entities.flow_topology import EdgeItem, FlowItem, NodeItem diff --git a/apps/service/knowledge_base.py b/apps/service/knowledge_base.py index 2ce24d119b24dda7878cca3db4bb466fdd08b4b6..bbd2caffb051dee3cea46ab44fe003417edfb9ea 100644 --- a/apps/service/knowledge_base.py +++ b/apps/service/knowledge_base.py @@ -1,10 +1,7 @@ -""" -文件上传至RAG,作为临时语料 +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""文件上传至RAG,作为临时语料""" -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" - -import aiohttp +import httpx from fastapi import status from apps.common.config import Config @@ -36,9 +33,10 @@ class KnowledgeBaseService: ] post_data = RAGFileParseReq(document_list=rag_docs).model_dump(exclude_none=True, by_alias=True) - async with aiohttp.ClientSession() as session, session.post(_RAG_DOC_PARSE_URI, json=post_data) as resp: - resp_data = await resp.json() - if resp.status != status.HTTP_200_OK: + async with httpx.AsyncClient() as client: + resp = await client.post(_RAG_DOC_PARSE_URI, json=post_data) + resp_data = resp.json() + if resp.status_code != status.HTTP_200_OK: return [] return resp_data["data"] @@ -46,9 +44,10 @@ class KnowledgeBaseService: async def delete_doc_from_rag(doc_ids: list[str]) -> list[str]: """删除文件""" post_data = {"ids": doc_ids} - async with aiohttp.ClientSession() as session, session.post(_RAG_DOC_DELETE_URI, json=post_data) as resp: - resp_data = await resp.json() - if resp.status != status.HTTP_200_OK: + async with httpx.AsyncClient() as client: + resp = await client.post(_RAG_DOC_DELETE_URI, json=post_data) + resp_data = resp.json() + if resp.status_code != status.HTTP_200_OK: return [] return resp_data["data"] @@ -56,8 +55,9 @@ class KnowledgeBaseService: async def get_doc_status_from_rag(doc_ids: list[str]) -> list[RAGFileStatusRspItem]: """获取文件状态""" post_data = {"ids": doc_ids} - async with aiohttp.ClientSession() as session, session.post(_RAG_DOC_STATUS_URI, json=post_data) as resp: - resp_data = await resp.json() - if resp.status != status.HTTP_200_OK: + async with httpx.AsyncClient() as client: + resp = await client.post(_RAG_DOC_STATUS_URI, json=post_data) + resp_data = resp.json() + if resp.status_code != status.HTTP_200_OK: return [] return [RAGFileStatusRspItem.model_validate(item) for item in resp_data["data"]] diff --git a/apps/service/rag.py b/apps/service/rag.py index e3d68dc15c7b8d0698e82b10e66000e671e57ad1..52b5938e2512a4226e61c4fad48ae43e6462b923 100644 --- a/apps/service/rag.py +++ b/apps/service/rag.py @@ -1,51 +1,141 @@ -""" -对接Euler Copilot RAG - -Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -""" +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""对接Euler Copilot RAG""" import json import logging from collections.abc import AsyncGenerator -import aiohttp +import httpx +import tiktoken from fastapi import status from apps.common.config import Config +from apps.entities.collection import LLM +from apps.entities.config import LLMConfig from apps.entities.rag_data import RAGQueryReq +from apps.llm.patterns.rewrite import QuestionRewrite +from apps.llm.reasoning import ReasoningLLM +from apps.manager.session import SessionManager from apps.service import Activity logger = logging.getLogger(__name__) + class RAG: """调用RAG服务,获取知识库答案""" - @staticmethod - async def get_rag_result(user_sub: str, data: RAGQueryReq) -> AsyncGenerator[str, None]: - """获取RAG服务的结果""" - url = Config().get_config().rag.rag_service.rstrip("/") + "/kb/get_stream_answer" - headers = { - "Content-Type": "application/json", - } - - payload = json.dumps(data.model_dump(exclude_none=True, by_alias=True), ensure_ascii=False) - + system_prompt: str = "You are a helpful assistant." + """系统提示词""" + user_prompt = """' + + 你是openEuler社区的智能助手。请结合给出的背景信息, 回答用户的提问。 + 上下文背景信息将在中给出。 + 用户的提问将在中给出。 + 注意:输出不要包含任何XML标签,不要编造任何信息。若你认为用户提问与背景信息无关,请忽略背景信息直接作答。 + - # asyncio HTTP请求 - async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=300)) as session, session.post( - url, headers=headers, data=payload, ssl=False, - ) as response: - if response.status != status.HTTP_200_OK: - logger.error("[RAG] RAG服务返回错误码: %s\n%s", response.status, await response.text()) - return + + {bac_info} + + + {user_question} + + """ - async for line in response.content: - line_str = line.decode("utf-8") + @staticmethod + def get_tokens(content: str) -> int: + try: + enc = tiktoken.encoding_for_model("gpt-4") + return len(enc.encode(str(content))) + except Exception as e: + err = f"[TokenTool] 获取token失败 {e}" + logging.exception("[TokenTool] %s", err) + return 0 - if not await Activity.is_active(user_sub): - return + @staticmethod + def get_k_tokens_words_from_content(content: str, k: int = 16) -> list: + try: + if RAG.get_tokens(content) <= k: + return content + l = 0 + r = len(content) + while l + 1 < r: + mid = (l + r) // 2 + if RAG.get_tokens(content[:mid]) <= k: + l = mid + else: + r = mid + return content[:l] + except Exception as e: + err = f"[RAG] 获取k个token的词失败 {e}" + logging.exception("[RAG] %s", err) + return "" - if "data: [DONE]" in line_str: - return + @staticmethod + async def get_rag_result( + user_sub: str, llm: LLM, history: list[dict[str, str]], data: RAGQueryReq + ) -> AsyncGenerator[str, None]: + """获取RAG服务的结果""" + session_id = await SessionManager.get_session_by_user_sub(user_sub) + url = Config().get_config().rag.rag_service.rstrip("/") + "/chunk/search" + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {session_id}"} + data.tokens_limit = llm.max_tokens + llm_config = LLMConfig( + endpoint=llm.openai_base_url, + key=llm.openai_api_key, + model=llm.model_name, + max_tokens=llm.max_tokens, + ) + if history: + try: + question_obj = QuestionRewrite() + data.query = await question_obj.generate(history=history, question=data.query) + except Exception as e: + logger.error("[RAG] 问题重写失败: %s", e) + reasion_llm = ReasoningLLM(llm_config) + corpus = [] + async with httpx.AsyncClient() as client: + data_json = data.model_dump(exclude_none=True, by_alias=True) + response = await client.post(url, headers=headers, json=data_json) + # 检查响应状态码 + if response.status_code == status.HTTP_200_OK: + result = response.json() + doc_chunk_list = result["result"]["docChunks"] + for doc_chunk in doc_chunk_list: + for chunk in doc_chunk["chunks"]: + corpus.append(chunk["text"].replace("\n", "")) + text = "" + for i in range(len(corpus)): + text += corpus[i] + "\n" + text = RAG.get_k_tokens_words_from_content(text, llm.max_tokens) - yield line_str + messages = history + [ + {"role": "system", "content": RAG.system_prompt}, + { + "role": "user", + "content": RAG.user_prompt.format( + bac_info=text, + user_question=data.query, + ), + }, + ] + input_tokens = RAG.get_tokens(text) + output_tokens = 0 + async for chunk in reasion_llm.call( + messages, max_tokens=llm.max_tokens, streaming=True, temperature=0.7, result_only=False + ): + if not await Activity.is_active(user_sub): + return + output_tokens += RAG.get_tokens(chunk) + yield ( + "data: " + + json.dumps( + { + "content": chunk, + "input_tokens": input_tokens, + "output_tokens": output_tokens, + }, + ensure_ascii=False, + ) + + "\n\n" + ) diff --git a/apps/templates/generate_llm_operator_config.py b/apps/templates/generate_llm_operator_config.py new file mode 100644 index 0000000000000000000000000000000000000000..2dc270b6c327f8347139ef157fe175e4c185d1b2 --- /dev/null +++ b/apps/templates/generate_llm_operator_config.py @@ -0,0 +1,70 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""生成大模型操作符配置文件""" + +import base64 +import os + +llm_provider_dict={ + "baichuan":{ + "provider":"baichuan", + "url":"https://api.baichuan-ai.com/v1", + "description":"百川大模型平台", + "icon":"", + }, + "modelscope":{ + "provider":"modelscope", + "url":None, + "description":"基于魔塔部署的本地大模型服务", + "icon":"", + }, + "ollama":{ + "provider":"ollama", + "url":None, + "description":"基于Ollama部署的本地大模型服务", + "icon":"", + }, + "openai":{ + "provider":"openai", + "url":"https://api.openai.com/v1", + "description":"OpenAI大模型平台", + "icon":"", + }, + "qwen":{ + "provider":"qwen", + "url":"https://dashscope.aliyuncs.com/compatible-mode/v1", + "description":"阿里百炼大模型平台", + "icon":"", + }, + "spark":{ + "provider":"spark", + "url":"https://spark-api-open.xf-yun.com/v1", + "description":"讯飞星火大模型平台", + "icon":"", + }, + "vllm":{ + "provider":"vllm", + "url":None, + "description":"基于VLLM部署的本地大模型服务", + "icon":"", + }, + "wenxin":{ + "provider":"wenxin", + "url":"https://qianfan.baidubce.com/v2", + "description":"百度文心大模型平台", + "icon":"", + }, +} +icon_path="./apps/templates/llm_provider_icon" +icon_file_name_list=os.listdir(icon_path) +for file_name in icon_file_name_list: + provider_name=file_name.split('.')[0] + file_path=os.path.join(icon_path, file_name) + with open(file_path, 'r', encoding='utf-8') as file: + svg_content = file.read() + svg_bytes = svg_content.encode('utf-8') + base64_bytes = base64.b64encode(svg_bytes) + base64_string = base64_bytes.decode('utf-8') + for provider in llm_provider_dict.keys(): + if provider_name in provider: + llm_provider_dict[provider]['icon'] = f"data:image/svg+xml;base64,{base64_string}" + break diff --git a/apps/templates/llm_provider_icon/baichuan.svg b/apps/templates/llm_provider_icon/baichuan.svg new file mode 100644 index 0000000000000000000000000000000000000000..32dc155640515a10735b40e512a5695cddd9fbe0 --- /dev/null +++ b/apps/templates/llm_provider_icon/baichuan.svg @@ -0,0 +1 @@ +Baichuan \ No newline at end of file diff --git a/apps/templates/llm_provider_icon/modelscope.svg b/apps/templates/llm_provider_icon/modelscope.svg new file mode 100644 index 0000000000000000000000000000000000000000..afbaa171a2332dc407f4e134a157217598e7549e --- /dev/null +++ b/apps/templates/llm_provider_icon/modelscope.svg @@ -0,0 +1 @@ +ModelScope \ No newline at end of file diff --git a/apps/templates/llm_provider_icon/ollama.svg b/apps/templates/llm_provider_icon/ollama.svg new file mode 100644 index 0000000000000000000000000000000000000000..cc887e3dcfd2260c136e807a84909892dc139fc3 --- /dev/null +++ b/apps/templates/llm_provider_icon/ollama.svg @@ -0,0 +1 @@ +Ollama \ No newline at end of file diff --git a/apps/templates/llm_provider_icon/openai.svg b/apps/templates/llm_provider_icon/openai.svg new file mode 100644 index 0000000000000000000000000000000000000000..50d94d6c10850b193390316ae8479569227c5e10 --- /dev/null +++ b/apps/templates/llm_provider_icon/openai.svg @@ -0,0 +1 @@ +OpenAI \ No newline at end of file diff --git a/apps/templates/llm_provider_icon/qwen.svg b/apps/templates/llm_provider_icon/qwen.svg new file mode 100644 index 0000000000000000000000000000000000000000..e1199f82845e37e62a0587ce2a37d2e71121792b --- /dev/null +++ b/apps/templates/llm_provider_icon/qwen.svg @@ -0,0 +1 @@ +Qwen \ No newline at end of file diff --git a/apps/templates/llm_provider_icon/spark.svg b/apps/templates/llm_provider_icon/spark.svg new file mode 100644 index 0000000000000000000000000000000000000000..50c8faeae6bdfa28c95ad745c2fcd1352df84cbd --- /dev/null +++ b/apps/templates/llm_provider_icon/spark.svg @@ -0,0 +1 @@ +Spark \ No newline at end of file diff --git a/apps/templates/llm_provider_icon/vllm.svg b/apps/templates/llm_provider_icon/vllm.svg new file mode 100644 index 0000000000000000000000000000000000000000..54acc3de2d23ab2bc107f660114105988090f696 --- /dev/null +++ b/apps/templates/llm_provider_icon/vllm.svg @@ -0,0 +1 @@ +vLLM \ No newline at end of file diff --git a/apps/templates/llm_provider_icon/wenxin.svg b/apps/templates/llm_provider_icon/wenxin.svg new file mode 100644 index 0000000000000000000000000000000000000000..e3b48c9477c731fc9d4fe5e125c3fe3551cfa671 --- /dev/null +++ b/apps/templates/llm_provider_icon/wenxin.svg @@ -0,0 +1 @@ +Wenxin \ No newline at end of file diff --git a/apps/templates/login_failed.html.j2 b/apps/templates/login_failed.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..abfa5da27663cb52861717f8242abf4d551a8866 --- /dev/null +++ b/apps/templates/login_failed.html.j2 @@ -0,0 +1,16 @@ + + + + + 登录失败 + + + +
登录失败
+
{{ reason }}
+ + diff --git a/apps/templates/login_success.html.j2 b/apps/templates/login_success.html.j2 new file mode 100644 index 0000000000000000000000000000000000000000..5702e6ff1f9a99e34419b8fe45f8dd82f28b4843 --- /dev/null +++ b/apps/templates/login_success.html.j2 @@ -0,0 +1,35 @@ + + + + + 登录成功 + + + +
登录成功
+
正在处理登录…
+ + + diff --git a/assets/.config.example.toml b/assets/.config.example.toml index 5cf07e0bd2f4900d14efbb3dc80270975a76e4d9..7efb7d90a07d0e1f6dfdaee1bc864367d5be1aaf 100644 --- a/assets/.config.example.toml +++ b/assets/.config.example.toml @@ -14,8 +14,6 @@ app_secret = '' [fastapi] domain = 'www.eulercopilot.local' -session_ttl = 30 -csrf = false [security] half_key1 = '' diff --git a/deploy/chart/databases/templates/opengauss/opengauss-config.yaml b/deploy/chart/databases/templates/opengauss/opengauss-config.yaml index a5012a96e9894dcdfed75ce7654c0c6721093ebd..aca7d76ec00eb222bb5324e3fb6a83787a53f01e 100644 --- a/deploy/chart/databases/templates/opengauss/opengauss-config.yaml +++ b/deploy/chart/databases/templates/opengauss/opengauss-config.yaml @@ -13,3 +13,4 @@ data: #!/bin/bash su - omm -c "gs_guc reload -D /var/lib/opengauss/data -c \"behavior_compat_options = 'accept_empty_str'\"" {{- end -}} + diff --git a/deploy/chart/databases/templates/opengauss/opengauss.yaml b/deploy/chart/databases/templates/opengauss/opengauss.yaml index d77c6ef9a2bb74a99fc02e429f933eb014739e55..7f5657bb2a6ad1164ab03635aa1515615a173828 100644 --- a/deploy/chart/databases/templates/opengauss/opengauss.yaml +++ b/deploy/chart/databases/templates/opengauss/opengauss.yaml @@ -90,3 +90,4 @@ spec: - name: opengauss-log emptyDir: {} {{- end -}} + diff --git a/deploy/chart/euler_copilot/configs/framework/config.toml b/deploy/chart/euler_copilot/configs/framework/config.toml index 3157fe459a9f2db1f10a37691dcff696b4954475..0020263fca01998bc84e23400f06f8dd6ac2cae7 100644 --- a/deploy/chart/euler_copilot/configs/framework/config.toml +++ b/deploy/chart/euler_copilot/configs/framework/config.toml @@ -14,8 +14,6 @@ app_secret = '${clientSecret}' [fastapi] domain = '{{ default "www.eulercopilot.local" .Values.domain.euler_copilot }}' -session_ttl = 30 -csrf = false [security] half_key1 = '${halfKey1}' diff --git a/deploy/scripts/2-install-tools/install_tools.sh b/deploy/scripts/2-install-tools/install_tools.sh index 86673f6b615f235df8a13a45e048df3885252893..8a80dd0e83d6da9831a923fc72398566178fd8e8 100755 --- a/deploy/scripts/2-install-tools/install_tools.sh +++ b/deploy/scripts/2-install-tools/install_tools.sh @@ -106,7 +106,7 @@ install_basic_tools() { yum install -y tar vim curl wget python3 # 检查 pip 是否已安装 - if ! command -v pip &> /dev/null; then + if ! command -v pip3 &> /dev/null; then echo -e "pip could not be found, installing python3-pip..." yum install -y python3-pip else diff --git a/deploy/scripts/8-install-EulerCopilot/install_eulercopilot.sh b/deploy/scripts/8-install-EulerCopilot/install_eulercopilot.sh index e92ce73c5e769a3ace62cb9f1f52ee0b758e7bad..6458d7c8127c2d1f3ca62cb934e6db0b25031794 100755 --- a/deploy/scripts/8-install-EulerCopilot/install_eulercopilot.sh +++ b/deploy/scripts/8-install-EulerCopilot/install_eulercopilot.sh @@ -234,23 +234,41 @@ uninstall_eulercopilot() { echo -e "${GREEN}资源清理完成${NC}" } -# 修改配置文件 modify_yaml() { local host=$1 + local preserve_models=$2 # 新增参数,指示是否保留模型配置 echo -e "${BLUE}开始修改YAML配置文件...${NC}" >&2 + + # 构建参数数组 + local set_args=() + + # 添加其他必填参数 + set_args+=( + "--set" "login.client.id=${client_id}" + "--set" "login.client.secret=${client_secret}" + "--set" "domain.authhub=${authhub_domain}" + "--set" "domain.euler_copilot=${eulercopilot_domain}" + ) + + # 如果不需要保留模型配置,则添加模型相关的参数 + if [[ "$preserve_models" != [Yy]* ]]; then + set_args+=( + "--set" "models.answer.endpoint=http://$host:11434" + "--set" "models.answer.key=sk-123456" + "--set" "models.answer.name=deepseek-llm-7b-chat:latest" + "--set" "models.functionCall.backend=ollama" + "--set" "models.embedding.type=openai" + "--set" "models.embedding.endpoint=http://$host:11434/v1" + "--set" "models.embedding.key=sk-123456" + "--set" "models.embedding.name=bge-m3:latest" + ) + fi + + # 调用Python脚本,传递所有参数 python3 "${DEPLOY_DIR}/scripts/9-other-script/modify_eulercopilot_yaml.py" \ "${DEPLOY_DIR}/chart/euler_copilot/values.yaml" \ "${DEPLOY_DIR}/chart/euler_copilot/values.yaml" \ - --set "models.answer.endpoint=http://$host:11434" \ - --set "models.answer.key=sk-123456" \ - --set "models.answer.name=deepseek-llm-7b-chat:latest" \ - --set "models.embedding.endpoint=http://$host:11434/v1" \ - --set "models.embedding.key=sk-123456" \ - --set "models.embedding.name=bge-m3:latest" \ - --set "login.client.id=${client_id}" \ - --set "login.client.secret=${client_secret}" \ - --set "domain.authhub=${authhub_domain}" \ - --set "domain.euler_copilot=${eulercopilot_domain}" || { + "${set_args[@]}" || { echo -e "${RED}错误:YAML文件修改失败${NC}" >&2 exit 1 } @@ -325,13 +343,22 @@ main() { host=$(get_network_ip) || exit 1 uninstall_eulercopilot if ! get_client_info_auto; then - echo -e "${YELLOW}需要手动登录Authhub域名并创建应用,获取client信息${NC}" + echo -e "${YELLOW}需要手动登录Authhub域名并创建应用,获取client信息${NC}" get_client_info_manual fi check_directories - modify_yaml "$host" - execute_helm_install "$arch" + # 处理是否保留模型配置 + local preserve_models="N" # 非交互模式默认覆盖 + if [ -t 0 ]; then # 仅在交互式终端显示提示 + echo -e "${BLUE}是否保留现有的模型配置?(Y/n) ${NC}" + read -p "> " input_preserve + preserve_models=${input_preserve:-Y} + fi + + modify_yaml "$host" "$preserve_models" + execute_helm_install "$arch" + if check_pods_status; then echo -e "${GREEN}所有组件已就绪!${NC}" else diff --git a/deploy/scripts/9-other-script/save_images.sh b/deploy/scripts/9-other-script/save_images.sh index d65d719092aad45eb5810efdc98f7526c9d60f51..c8f59cf764fc8d4821af2a34f9d8932949a5716e 100755 --- a/deploy/scripts/9-other-script/save_images.sh +++ b/deploy/scripts/9-other-script/save_images.sh @@ -7,17 +7,85 @@ YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # 恢复默认颜色 -# 默认版本配置 +# 默认配置 eulercopilot_version="0.9.5" +ARCH_SUFFIX="" +OUTPUT_DIR="/home/eulercopilot/images/${eulercopilot_version}" + +# 显示帮助信息 +show_help() { + echo -e "${YELLOW}使用说明:${NC}" + echo -e " $0 [选项]" + echo -e "" + echo -e "${YELLOW}选项:${NC}" + echo -e " --help 显示此帮助信息" + echo -e " --version <版本> 指定 EulerCopilot 版本 (默认: ${eulercopilot_version})" + echo -e " --arch <架构> 指定系统架构 (arm/x86, 默认自动检测)" + echo -e "" + echo -e "${YELLOW}示例:${NC}" + echo -e " $0 --version 0.9.5 --arch arm" + echo -e " $0 --help" + exit 0 +} + +# 解析命令行参数 +while [[ $# -gt 0 ]]; do + case "$1" in + --help) + show_help + ;; + --version) + if [ -n "$2" ]; then + eulercopilot_version="$2" + OUTPUT_DIR="/home/eulercopilot/images/${eulercopilot_version}" + shift + else + echo -e "${RED}错误: --version 需要指定一个版本号${NC}" + exit 1 + fi + ;; + --arch) + if [ -n "$2" ]; then + case "$2" in + arm|x86) + ARCH_SUFFIX="$2" + ;; + *) + echo -e "${RED}错误: 不支持的架构 '$2',必须是 arm 或 x86${NC}" + exit 1 + ;; + esac + shift + else + echo -e "${RED}错误: --arch 需要指定一个架构 (arm/x86)${NC}" + exit 1 + fi + ;; + *) + echo -e "${RED}未知参数: $1${NC}" + show_help + ;; + esac + shift +done -# 检查输入参数 -if [ $# -ge 1 ]; then - eulercopilot_version="$1" - echo -e "${YELLOW}使用自定义版本: $eulercopilot_version${NC}" +# 自动检测架构(如果未通过参数指定) +if [ -z "$ARCH_SUFFIX" ]; then + ARCH=$(uname -m) + case $ARCH in + x86_64) + ARCH_SUFFIX="x86" + ;; + aarch64|armv*) + ARCH_SUFFIX="arm" + ;; + *) + echo -e "${RED}不支持的架构: $ARCH${NC}" + exit 1 + ;; + esac fi -# 定义存储路径 -OUTPUT_DIR="/home/eulercopilot/images/${eulercopilot_version}" mkdir -p "$OUTPUT_DIR" # 镜像列表(使用版本变量) @@ -58,21 +126,6 @@ if [ ${#BASE_IMAGES[@]} -ne ${#FILE_NAMES[@]} ]; then exit 1 fi -# 检测系统架构 -ARCH=$(uname -m) -case $ARCH in - x86_64) - ARCH_SUFFIX="x86" - ;; - aarch64|armv*) - ARCH_SUFFIX="arm" - ;; - *) - echo -e "${RED}不支持的架构: $ARCH${NC}" - exit 1 - ;; -esac - # 初始化计数器 total=${#BASE_IMAGES[@]} success=0 @@ -82,19 +135,19 @@ fail=0 process_image() { local raw_image=$1 local filename=$2 - + # 调整架构标签 local adjusted_image="${raw_image/-arm/-${ARCH_SUFFIX}}" local output_path="${OUTPUT_DIR}/${filename}" echo -e "\n${BLUE}正在处理:${adjusted_image}${NC}" - + # 拉取镜像 if ! docker pull "$adjusted_image"; then echo -e "${RED}拉取失败:${adjusted_image}${NC}" return 1 fi - + # 保存镜像 if docker save -o "$output_path" "$adjusted_image"; then echo -e "${GREEN}镜像已保存到:${output_path}${NC}" @@ -107,8 +160,8 @@ process_image() { # 打印执行信息 echo -e "${BLUE}==============================${NC}" -echo -e "${YELLOW}架构检测\t: ${ARCH_SUFFIX}${NC}" -echo -e "${YELLOW}目标版本\t: ${eulercopilot_version}${NC}" +echo -e "${YELLOW}架构\t: ${ARCH_SUFFIX}${NC}" +echo -e "${YELLOW}版本\t: ${eulercopilot_version}${NC}" echo -e "${YELLOW}存储目录\t: ${OUTPUT_DIR}${NC}" echo -e "${YELLOW}镜像数量\t: ${total}${NC}" echo -e "${BLUE}==============================${NC}" diff --git a/docs/design/appcenter.md b/docs/design/appcenter.md index c18e8d3f43a6bde2585dc9a4f7a1d600d42c7d10..60a81eb4af666f0ea00eba28b843512ac2cd2458 100644 --- a/docs/design/appcenter.md +++ b/docs/design/appcenter.md @@ -96,5 +96,4 @@ AppCenter 是 openEuler Copilot Framework 的应用中心模块, 主要提供以 ## 6. 安全性设计 - 接口鉴权: 依赖 `verify_user` 中间件进行用户认证 -- CSRF 防护: 使用 `verify_csrf_token` 中间件 - 权限控制: 应用删除、发布等操作需验证用户身份 diff --git a/docs_for_openEuler/ai_full_stack/ai_container_image_userguide/_toc.yaml b/docs_for_openEuler/ai_full_stack/ai_container_image_userguide/_toc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8782a2915974178dadfa7b97efcb19df99069a72 --- /dev/null +++ b/docs_for_openEuler/ai_full_stack/ai_container_image_userguide/_toc.yaml @@ -0,0 +1,6 @@ +label: AI容器镜像用户指南 +isManual: true +description: openEuler AI 容器镜像封装了 AI 框架等软件,提高 AI 应用开发或使用效率 +sections: + - label: AI容器镜像用户指南 + href: ./ai-container-image-user-guide.md diff --git a/docs_for_openEuler/ai_full_stack/ai_container_image_userguide/ai-container-image-user-guide.md b/docs_for_openEuler/ai_full_stack/ai_container_image_userguide/ai-container-image-user-guide.md new file mode 100644 index 0000000000000000000000000000000000000000..b433f2f4dcb8d609d014e8ecf3f77d37d5fe5773 --- /dev/null +++ b/docs_for_openEuler/ai_full_stack/ai_container_image_userguide/ai-container-image-user-guide.md @@ -0,0 +1,101 @@ +# openEuler AI 容器镜像用户指南 + +## 简介 + +openEuler AI 容器镜像封装了不同硬件算力的 SDK 以及 AI 框架、大模型应用等软件,用户只需要在目标环境中加载镜像并启动容器,即可进行 AI 应用开发或使用,大大减少了应用部署和环境配置的时间,提升效率。 + +## 获取镜像 + +目前,openEuler 已发布支持 Ascend 和 NVIDIA 平台的容器镜像,获取路径如下: + +- [openeuler/cann](https://hub.docker.com/r/openeuler/cann) 存放 SDK 类镜像,在 openEuler 基础镜像之上安装 CANN 系列软件,适用于 Ascend 环境。 +- [openeuler/cuda](https://hub.docker.com/r/openeuler/cuda) 存放 SDK 类镜像,在 openEuler 基础镜像之上安装 CUDA 系列软件,适用于 NVIDIA 环境。 +- [openeuler/pytorch](https://hub.docker.com/r/openeuler/pytorch) 存放 AI 框架类镜像,在 SDK 镜像基础之上安装 PyTorch,根据安装的 SDK 软件内容区分适用平台。 +- [openeuler/tensorflow](https://hub.docker.com/r/openeuler/tensorflow) 存放 AI 框架类镜像,在 SDK 镜像基础之上安装 TensorFlow,根据安装的 SDK 软件内容区分适用平台。 +- [openeuler/llm](https://hub.docker.com/r/openeuler/tensorrt) 存放模型应用类镜像,在 AI 框架镜像之上包含特定大模型及工具链,根据安装的 SDK 软件内容区分适用平台。 + +详细的 AI 容器镜像分类和镜像 tag 的规范说明见[oEEP-0014](https://gitee.com/openeuler/TC/blob/master/oEEP/oEEP-0014%20openEuler%20AI容器镜像软件栈规范.md)。 + +由于 AI 容器镜像的体积一般较大,推荐用户在启动容器前先通过如下命令将镜像拉取到开发环境中。 + +```sh +docker pull image:tag +``` + +其中,`image`为仓库名,如`openeuler/cann`,`tag`为目标镜像的 TAG,待镜像拉取完成后即可启动容器。注意,使用`docker pull`命令需按照下文方法安装`docker`软件。 + +## 启动容器 + +1. 在环境中安装`docker`,官方安装方法见[Install Docker Engine](https://docs.docker.com/engine/install/),也可直接通过如下命令进行安装。 + + ```sh + yum install -y docker + ``` + + 或 + + ```sh + apt-get install -y docker + ``` + +2. NVIDIA环境安装`nvidia-container` + + 1)配置yum或apt repo + - 使用yum安装时,执行: + + ```sh + curl -s -L https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo | \ + sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo + ``` + + - 使用apt安装时,执行: + + ```sh + curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg + ``` + + ```sh + curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ + sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ + sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list + ``` + + 2)安装`nvidia-container-toolkit`,`nvidia-container-runtime`,执行: + + ```sh + # yum安装 + yum install -y nvidia-container-toolkit nvidia-container-runtime + ``` + + ```sh + # apt安装 + apt-get install -y nvidia-container-toolkit nvidia-container-runtime + ``` + + 3)配置docker + + ```sh + nvidia-ctk runtime configure --runtime=docker + systemctl restart docker + ``` + + 非NVIDIA环境不执行此步骤。 + +3. 确保环境中安装`driver`及`firmware`,用户可从[NVIDIA](https://www.nvidia.com/)或[Ascend](https://www.hiascend.com/)官网获取正确版本进行安装。安装完成后 Ascend 平台使用`npu-smi`命令、NVIDIA 平台使用`nvidia-smi`进行测试,正确显示硬件信息则说明安装正常。 + +4. 完成上述操作后,即可使用`docker run`命令启动容器。 + +```sh +# Ascend环境启动容器 +docker run --rm --network host \ + --device /dev/davinci0:/dev/davinci0 \ + --device /dev/davinci_manager --device /dev/devmm_svm --device /dev/hisi_hdc \ + -v /usr/local/dcmi:/usr/local/dcmi -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \ + -v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \ + -ti image:tag +``` + +```sh +# NVIDIA环境启动容器 +docker run --gpus all -d -ti image:tag +``` diff --git a/docs_for_openEuler/ai_full_stack/ai_large_model_service_images_userguide/_toc.yaml b/docs_for_openEuler/ai_full_stack/ai_large_model_service_images_userguide/_toc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..bed3b23bcec452195f1b6a830c6c7e3a017413b0 --- /dev/null +++ b/docs_for_openEuler/ai_full_stack/ai_large_model_service_images_userguide/_toc.yaml @@ -0,0 +1,6 @@ +label: AI大模型服务镜像使用指南 +isManual: true +description: 支持百川、chatglm、星火等AI大模型的容器化封装 +sections: + - label: AI大模型服务镜像使用指南 + href: ./llm-service-image-user-guide.md diff --git a/docs_for_openEuler/ai_full_stack/ai_large_model_service_images_userguide/llm-service-image-user-guide.md b/docs_for_openEuler/ai_full_stack/ai_large_model_service_images_userguide/llm-service-image-user-guide.md new file mode 100644 index 0000000000000000000000000000000000000000..c7c492b104b74e25ac87980c2d8580885a43df0e --- /dev/null +++ b/docs_for_openEuler/ai_full_stack/ai_large_model_service_images_userguide/llm-service-image-user-guide.md @@ -0,0 +1,94 @@ +# 支持百川、chatglm、星火等AI大模型的容器化封装 + +已配好相关依赖,分为CPU和GPU版本,降低使用门槛,开箱即用。 + +## 拉取镜像(CPU版本) + +```bash +docker pull openeuler/llm-server:1.0.0-oe2203sp3 +``` + +## 拉取镜像(GPU版本) + +```bash +docker pull icewangds/llm-server:1.0.0 +``` + +## 下载模型, 并转换为gguf格式 + +```bash +# 安装huggingface +pip install huggingface-hub + +# 下载你想要部署的模型 +export HF_ENDPOINT=https://hf-mirror.com +huggingface-cli download --resume-download baichuan-inc/Baichuan2-13B-Chat --local-dir /root/models/Baichuan2-13B-Chat --local-dir-use-symlinks False + +# gguf格式转换 +cd /root/models/ +git clone https://github.com/ggerganov/llama.cpp.git +python llama.cpp/convert-hf-to-gguf.py ./Baichuan2-13B-Chat +# 生成的gguf格式的模型路径 /root/models/Baichuan2-13B-Chat/ggml-model-f16.gguf +``` + +## 启动方式 + +需要Docker v25.0.0及以上版本。 + +若使用GPU镜像,需要OS上安装nvidia-container-toolkit,安装方式见。 + +docker-compose.yaml: + +```yaml +version: '3' +services: + model: + image: : #镜像名称与tag + restart: on-failure:5 + ports: + - 8001:8000 #监听端口号,修改“8001”以更换端口 + volumes: + - /root/models:/models # 大模型挂载目录 + environment: + - MODEL=/models/Baichuan2-13B-Chat/ggml-model-f16.gguf # 容器内的模型文件路径 + - MODEL_NAME=baichuan13b # 自定义模型名称 + - KEY=sk-12345678 # 自定义API Key + - CONTEXT=8192 # 上下文大小 + - THREADS=8 # CPU线程数,仅CPU部署时需要 + deploy: # 指定GPU资源, 仅GPU部署时需要 + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] +``` + +```bash +docker-compose -f docker-compose.yaml up +``` + +docker run: + +```text +cpu部署: docker run -d --restart on-failure:5 -p 8001:8000 -v /root/models:/models -e MODEL=/models/Baichuan2-13B-Chat/ggml-model-f16.gguf -e MODEL_NAME=baichuan13b -e KEY=sk-12345678 openeuler/llm-server:1.0.0-oe2203sp3 + +gpu部署: docker run -d --gpus all --restart on-failure:5 -p 8001:8000 -v /root/models:/models -e MODEL=/models/Baichuan2-13B-Chat/ggml-model-f16.gguf -e MODEL_NAME=baichuan13b -e KEY=sk-12345678 icewangds/llm-server:1.0.0 +``` + +## 调用大模型接口测试,成功返回则表示大模型服务已部署成功 + +```bash +curl -X POST http://127.0.0.1:8001/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-12345678" \ + -d '{ + "model": "baichuan13b", + "messages": [ + {"role": "system", "content": "你是一个社区助手,请回答以下问题。"}, + {"role": "user", "content": "你是谁?"} + ], + "stream": false, + "max_tokens": 1024 + }' +``` diff --git a/docs_for_openEuler/intelligent_foundation/sysHAX/_toc.yaml b/docs_for_openEuler/intelligent_foundation/sysHAX/_toc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4336146e16b5e642686555ba704c654cac564b5a --- /dev/null +++ b/docs_for_openEuler/intelligent_foundation/sysHAX/_toc.yaml @@ -0,0 +1,6 @@ +label: sysHAX用户指南 +isManual: true +description: 异构协同加速运行 +sections: + - label: sysHAX部署指南 + href: ./deploy_guide/sysHax-deployment-guide.md diff --git a/docs_for_openEuler/intelligent_foundation/sysHAX/deploy_guide/sysHax-deployment-guide.md b/docs_for_openEuler/intelligent_foundation/sysHAX/deploy_guide/sysHax-deployment-guide.md new file mode 100644 index 0000000000000000000000000000000000000000..06025bedca3484c6e8176c13f985ce9743f6bcb6 --- /dev/null +++ b/docs_for_openEuler/intelligent_foundation/sysHAX/deploy_guide/sysHax-deployment-guide.md @@ -0,0 +1,151 @@ +# sysHAX部署指南 + +sysHAX当前处于快速迭代阶段,基于vllm v0.6.6+npu进行验证。vllm上游发布的正式支持npu的版本为v0.7.1rc1,而当前用的vllm版本处于验证阶段,未合入主线。因此,在当前创新版本中暂不以源码形式发布,而是以容器化的形式为大家提供技术尝鲜。也欢迎开发者在使用过程中有任何问题和建议,可以在sig-Intelligence组中进行充分交流。 + +vllm是一款**高吞吐、低内存占用**的**大语言模型(LLM)推理与服务引擎**,支持**CPU 计算加速**,提供高效的算子下发机制,包括: + +- **Schedule(调度)**:优化任务分发,提高并行计算效率 +- **Prepare Input(准备数据)**:高效的数据预处理,加速输入构建 +- **Ray 框架**:利用分布式计算提升推理吞吐 +- **Sample(模型后处理)**:优化采样策略,提升生成质量 +- **框架后处理**:融合多种优化策略,提升整体推理性能 + +该引擎结合**高效计算调度与优化策略**,为 LLM 推理提供**更快、更稳定、更可扩展**的解决方案。 + +## 环境准备 + +| 服务器型号 | Atlas 800T/I A2 训练/推理服务器 | +| --------------- | --------------------------------------------------------- | +| 操作系统 | openEuler 22.03 LTS及以上 | +| NPU驱动版本 | Ascend-hdk-910b-npu-driver_24.1.rc3_linux-aarch64.run | +| 固件版本 | Ascend-hdk-910b-firmware_7.5.0.1.129.run | + +### **安装驱动固件** + +- 创建驱动运行用户HwHiAiUser(运行驱动进程的用户),安装驱动时无需指定运行用户,默认即为HwHiAiUser。 + +```shell +groupadd -g 1000 HwHiAiUser +useradd -g HwHiAiUser -u 1000 -d /home/HwHiAiUser -m HwHiAiUser -s /bin/bash +``` + +- 将驱动包和固件包上传到服务器任意目录如“/home”。 +- 执行如下命令,增加驱动和固件包的可执行权限。 + +```shell +chmod +x Ascend-hdk-910b-npu-driver_24.1.rc3_linux-aarch64.run +chmod +x Ascend-hdk-910b-firmware_7.5.0.1.129.run +``` + +- 安装驱动 + +```shell +./Ascend-hdk-910b-npu-driver_24.1.rc3_linux-aarch64.run --full --install-for-all + +# 若执行上述安装命令出现类似如下回显信息 +# [ERROR]The list of missing tools: lspci,ifconfig, +# 请执行yum install -y net-tools pciutils + +# 若系统出现如下关键回显信息,则表示驱动安装成功。 +# Driver package installed successfully! +``` + +- 安装固件 + +```shell +./Ascend-hdk-910b-firmware_7.5.0.1.129.run --full + +# 若系统出现如下关键回显信息,表示固件安装成功。 +# Firmware package installed successfully! Reboot now or after driver installation for the installation/upgrade to take effect +``` + +- 执行reboot命令重启系统。 +- 执行npu-smi info查看驱动加载是否成功。 + +## 容器部署场景 + +### 部署Ascend-Docker(容器引擎插件) + +- 参考版本:"Ascend-docker-runtime_6.0.RC3.1_linux-aarch64.run" + +```shell +# 将软件包”Ascend-docker-runtime_6.0.RC3.1_linux-aarch64.run”上传到服务器任意目录(如“/home”)。 +chmod +x Ascend-docker-runtime_6.0.RC3.1_linux-aarch64.run +``` + +```shell +./Ascend-docker-runtime_6.0.RC3.1_linux-aarch64.run --install +# 安装完成后,若显示类似如下信息,则说明软件安装成功: +xxx install success +``` + +- 执行systemctl restart docker命令重启docker,使容器引擎插件在docker配置文件中添加的内容生效。 + +### 容器场景vllm搭建 + +```shell +docker pull hub.oepkgs.net/neocopilot/vllm@sha256:c72a0533b8f34ebd4d352ddac3a969d57638c3d0c9c4af9b78c88400c6edff7a + +# /home路径不要全部映射,防止覆盖/home/HwHiAiUser +docker run -itd \ + -p 1111:22 \ + --name vllm_oe \ + --shm-size 16G \ + --device /dev/davinci0 \ + --device /dev/davinci1 \ + --device /dev/davinci2 \ + --device /dev/davinci3 \ + --device /dev/davinci4 \ + --device /dev/davinci5 \ + --device /dev/davinci6 \ + --device /dev/davinci7 \ + --device /dev/davinci_manager \ + --device /dev/devmm_svm \ + --device /dev/hisi_hdc \ + -v /usr/local/dcmi:/usr/local/dcmi \ + -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \ + -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \ + -w /home \ + hub.oepkgs.net/neocopilot/vllm:0.6.6-aarch64-910B-oe2203-sp3 bash + +# 启动vllm,模型自行下载 +vllm serve /home/models/DeepSeek-R1-Distill-Llama-70B --distributed-executor-backend ray --tensor-parallel-size 8 --block-size 32 --preemption_mode swap +``` + +## 纯CPU推理环境部署 + +- 模型文件准备 + +1. 准备模型文件,放在`/home/model/`路径下 + + - **注意**:当前镜像版本支持DeepSeek 7B、32B以及Qwen系列模型 + +2. 拉取镜像,镜像地址:docker pull hub.oepkgs.net/neocopilot/syshax/vllm-cpu@sha256:3983071e1928b9fddc037a51f2fc6b044d41a35d5c1e75ff62c8c5e6b1c157a3 + +3. 启动容器: + +```bash +docker run --name vllm_server_sysHAX \ + -p 7001:7001 \ + -v /home/model:/home/model/ \ + -itd hub.oepkgs.net/neocopilot/syshax/vllm-cpu:0.1.2.4 bash +``` + +- 在容器中启动服务 + +```bash +cd /home/vllm_syshax +python3 vllm/entrypoints/openai/api_server.py \ + --model /home/model/DeepSeek-R1-Distill-Qwen-7B \ + --served-model-name=ds7b \ + --host 0.0.0.0 \ + --port 7001 \ + --dtype=half \ + --swap_space=16 \ + --block_size=16 \ + --preemption_mode=swap \ + --max_model_len=8192 & +``` + +**注意**:`--model`使用实际模型路径,`--served-model-name`可自己指定模型名字,端口两个需要对应,可不用7001 +部署完成,然后向7001端口发送请求即可,请求需满足OpenAPI格式。 diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/_toc.yaml b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/_toc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e616c2efa3d78f28d1c0fed63f753bf519380b56 --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/_toc.yaml @@ -0,0 +1,50 @@ +label: openEuler智能助手 +isManual: true +description: 部署和使用openEuler智能助手 +sections: + - label: 快速上手 + sections: + - label: web问答 + sections: + - label: 部署指南 + sections: + - label: 无网络环境下部署指南 + href: ./quick_start/smart_web/deploy_guide/offline.md + - label: 网络环境下部署指南 + href: ./quick_start/smart_web/deploy_guide/online.md + - label: 使用指导 + sections: + - label: 前言 + href: ./quick_start/smart_web/user_guide/introduction.md + - label: 注册与登录 + href: ./quick_start/smart_web/user_guide/registration_and_login.md + - label: 智能问答使用 + href: ./quick_start/smart_web/user_guide/qa_guide.md + - label: shell问答 + sections: + - label: 使用指导 + sections: + - label: API Key 获取 + href: ./quick_start/smart_shell/user_guide/API_key.md + - label: 命令行助手使用 + href: ./quick_start/smart_shell/user_guide/shell.md + - label: 进阶使用 + sections: + - label: 智能诊断 + sections: + - label: 部署指南 + href: ./advance/smart_diagnosis/deploy_guide/diagnosis_deployment.md + - label: 使用指导 + href: ./advance/smart_diagnosis/user_guide/diagnosis_guidance.md + - label: 智能调优 + sections: + - label: 部署指南 + href: ./advance/smart_tuning/deploy_guide/tune_deployment.md + - label: 使用指导 + href: ./advance/smart_tuning/user_guide/tune_guidance.md + - label: 知识库管理 + sections: + - label: 部署指南 + href: ./advance/knowledge_base/deploy_guide/witChainD_deployment.md + - label: 使用指导 + href: ./advance/knowledge_base/user_guide/witChainD_guidance.md diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/CPU\351\200\273\350\276\221\346\240\270\345\277\203.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/CPU\351\200\273\350\276\221\346\240\270\345\277\203.png" new file mode 100644 index 0000000000000000000000000000000000000000..74ae942b5a5217b8a5e34a2b2cd8d32a49be7a00 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/CPU\351\200\273\350\276\221\346\240\270\345\277\203.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/Copilot\345\244\247\346\250\241\345\236\213\351\203\250\347\275\262\345\267\256\345\274\202.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/Copilot\345\244\247\346\250\241\345\236\213\351\203\250\347\275\262\345\267\256\345\274\202.png" new file mode 100644 index 0000000000000000000000000000000000000000..8f1de7892e04be698310691d2cfdeb07cbfa579d Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/Copilot\345\244\247\346\250\241\345\236\213\351\203\250\347\275\262\345\267\256\345\274\202.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2761.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2761.png" new file mode 100644 index 0000000000000000000000000000000000000000..e59e8b669c3039341655eadd75ce1fda5cda1776 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2761.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2762.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2762.png" new file mode 100644 index 0000000000000000000000000000000000000000..68ae1c7cb11e663cabbf1225b188fdfd628bf549 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2762.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2763.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2763.png" new file mode 100644 index 0000000000000000000000000000000000000000..d90f6182fb6ec63f868a5c2598de73db093775f2 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2763.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\265\213\350\257\225\346\216\245\345\217\243\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\265\213\350\257\225\346\216\245\345\217\243\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..374c3a2cc0be67a012ef8bf0ddc7688f97702d79 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\265\213\350\257\225\346\216\245\345\217\243\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\350\275\273\351\207\217\345\214\226\351\203\250\347\275\262\350\247\206\345\233\276.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\350\275\273\351\207\217\345\214\226\351\203\250\347\275\262\350\247\206\345\233\276.png" new file mode 100644 index 0000000000000000000000000000000000000000..297ad86cac9226084483816f0c88c9116071b675 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\350\275\273\351\207\217\345\214\226\351\203\250\347\275\262\350\247\206\345\233\276.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/WEB\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/WEB\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..bb9be4e33ce470865fe5a07decbc056b9ee4e9bb Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/WEB\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/WEB\347\231\273\345\275\225\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/WEB\347\231\273\345\275\225\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..fddbab4df70b940d5d5ed26fb8ec688f1592b5e8 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/WEB\347\231\273\345\275\225\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/authhub\347\231\273\345\275\225\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/authhub\347\231\273\345\275\225\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..341828b1b6f728888d1dd52eec755033680155da Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/authhub\347\231\273\345\275\225\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/witchaind\347\231\273\345\275\225\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/witchaind\347\231\273\345\275\225\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..dfc28f4046fd4d61f48a0b0903ae2cf565ec5bc3 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/witchaind\347\231\273\345\275\225\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\345\233\236\345\210\260\351\246\226\351\241\265.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\345\233\236\345\210\260\351\246\226\351\241\265.png" new file mode 100644 index 0000000000000000000000000000000000000000..92685c5d977abe55f5d201aa57da479c8af84561 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\345\233\236\345\210\260\351\246\226\351\241\265.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\345\257\274\345\205\245\346\226\207\346\241\243\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\345\257\274\345\205\245\346\226\207\346\241\243\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..c4b71d6def0b6407f721cf3c137d714d923f86f1 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\345\257\274\345\205\245\346\226\207\346\241\243\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\345\257\274\345\207\272\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\345\257\274\345\207\272\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..3458c5330fad7b8c89cb0bc8efb70f875d6f17d2 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\345\257\274\345\207\272\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\345\257\274\345\207\272\350\265\204\344\272\247\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\345\257\274\345\207\272\350\265\204\344\272\247\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..469871fa9483a698b03374c3686b22156ad6e33a Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\345\257\274\345\207\272\350\265\204\344\272\247\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\345\257\274\345\205\245\346\210\220\345\212\237\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\345\257\274\345\205\245\346\210\220\345\212\237\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..8aba84e49c981c8f81cb91b14eee64f179bf0b38 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\345\257\274\345\205\245\346\210\220\345\212\237\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\345\257\274\345\205\245\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\345\257\274\345\205\245\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..7932773ccf59f58a283caccb92bd5af9475a7be9 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\345\257\274\345\205\245\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\346\255\243\345\234\250\345\257\274\345\205\245\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\346\255\243\345\234\250\345\257\274\345\205\245\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..50805afdb4764b74d9d16067999d7b39ce901d2a Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\346\255\243\345\234\250\345\257\274\345\205\245\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\226\260\345\273\272\350\265\204\344\272\247\345\272\223\345\241\253\345\206\231\345\261\225\347\244\272\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\226\260\345\273\272\350\265\204\344\272\247\345\272\223\345\241\253\345\206\231\345\261\225\347\244\272\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..8eb29b167f6ff1c2d951cd841f2340b027dec808 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\226\260\345\273\272\350\265\204\344\272\247\345\272\223\345\241\253\345\206\231\345\261\225\347\244\272\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\226\260\345\273\272\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\226\260\345\273\272\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..9da6121b1c1271c5b09c9292690ba3ab8d0a6cd2 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\226\260\345\273\272\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\237\245\347\234\213\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\237\245\347\234\213\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..a533772ce715bbf2c4a9f374b03e7fe20bf470a1 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\237\245\347\234\213\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\255\243\345\234\250\345\257\274\345\207\272\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\255\243\345\234\250\345\257\274\345\207\272\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..659ebeae5b25738043f7750c7cc44a1e80557ed8 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\346\255\243\345\234\250\345\257\274\345\207\272\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\347\202\271\345\207\273\351\200\200\345\207\272\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\347\202\271\345\207\273\351\200\200\345\207\272\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..22b02fff81fe1db3232b80607da6f10f710c8c64 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\347\202\271\345\207\273\351\200\200\345\207\272\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\344\270\255\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\344\270\255\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..913a5ce34a0a3e95af29e7c4433e5367c0adf008 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\344\270\255\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\346\210\220\345\212\237\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\346\210\220\345\212\237\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..a1c6dc638d0dbd51abc374d563da150ff328cbe3 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\346\210\220\345\212\237\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\346\226\271\346\263\225\351\200\211\346\213\251\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\346\226\271\346\263\225\351\200\211\346\213\251\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..f0449b134e1ebe5d54ca46099b57c6ad0b949eca Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\346\226\271\346\263\225\351\200\211\346\213\251\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\347\273\223\346\236\234\350\277\207\346\273\244\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\347\273\223\346\236\234\350\277\207\346\273\244\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..e3d3ba7727d53490b22ecc7a1b422d5ae03390d3 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\347\273\223\346\236\234\350\277\207\346\273\244\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\350\257\246\346\203\205\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\350\257\246\346\203\205\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..e018cb0904b414d63e1008209adb47c0b8afb858 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\350\257\246\346\203\205\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\265\204\344\272\247\345\272\223\345\210\233\345\273\272\345\256\214\346\210\220\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\265\204\344\272\247\345\272\223\345\210\233\345\273\272\345\256\214\346\210\220\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..7bf98b8217dda2358621fe9b11164407e2040ae8 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\265\204\344\272\247\345\272\223\345\210\233\345\273\272\345\256\214\346\210\220\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\277\233\345\205\245\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\277\233\345\205\245\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..795e99cdad03b2a3377fe77e51e336c6a6ca5b29 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\350\277\233\345\205\245\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\351\200\211\346\213\251\346\226\207\344\273\266.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\351\200\211\346\213\251\346\226\207\344\273\266.png" new file mode 100644 index 0000000000000000000000000000000000000000..8031fec14e15b0e80e596f21cf79fe2b58ff7293 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/witChainD/\351\200\211\346\213\251\346\226\207\344\273\266.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\345\210\233\345\273\272\345\272\224\347\224\250\346\210\220\345\212\237\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\345\210\233\345\273\272\345\272\224\347\224\250\346\210\220\345\212\237\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..a871907f348317e43633cf05f5241cb978476fb4 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\345\210\233\345\273\272\345\272\224\347\224\250\346\210\220\345\212\237\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\345\210\233\345\273\272\345\272\224\347\224\250\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\345\210\233\345\273\272\345\272\224\347\224\250\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..d82c736a94b106a30fd8d1f7b781f9e335bb441f Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\345\210\233\345\273\272\345\272\224\347\224\250\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/k8s\351\233\206\347\276\244\344\270\255postgres\346\234\215\345\212\241\347\232\204\345\220\215\347\247\260.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/k8s\351\233\206\347\276\244\344\270\255postgres\346\234\215\345\212\241\347\232\204\345\220\215\347\247\260.png" new file mode 100644 index 0000000000000000000000000000000000000000..473a0006c9710c92375e226a760c3a79989312f9 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/k8s\351\233\206\347\276\244\344\270\255postgres\346\234\215\345\212\241\347\232\204\345\220\215\347\247\260.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/postgres\346\234\215\345\212\241\347\253\257\345\217\243.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/postgres\346\234\215\345\212\241\347\253\257\345\217\243.png" new file mode 100644 index 0000000000000000000000000000000000000000..cfee6d88da56bc939886caece540f7de8cf77bbc Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/postgres\346\234\215\345\212\241\347\253\257\345\217\243.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/rag_port.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/rag_port.png" new file mode 100644 index 0000000000000000000000000000000000000000..b1d93f9c9d7587aa88a27d7e0bf185586583d438 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/rag_port.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/rag\351\205\215\347\275\256\344\277\241\346\201\257\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/rag\351\205\215\347\275\256\344\277\241\346\201\257\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..fec3cdaa2b260e50f5523477da3e58a9e14e2130 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/rag\351\205\215\347\275\256\344\277\241\346\201\257\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\233\345\273\272\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245\347\224\261\344\272\216\347\273\237\344\270\200\350\265\204\344\272\247\344\270\213\345\255\230\345\234\250\345\220\214\345\220\215\350\265\204\344\272\247\345\272\223.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\233\345\273\272\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245\347\224\261\344\272\216\347\273\237\344\270\200\350\265\204\344\272\247\344\270\213\345\255\230\345\234\250\345\220\214\345\220\215\350\265\204\344\272\247\345\272\223.png" new file mode 100644 index 0000000000000000000000000000000000000000..624459821de4542b635eeffa115eeba780929a4e Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\233\345\273\272\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245\347\224\261\344\272\216\347\273\237\344\270\200\350\265\204\344\272\247\344\270\213\345\255\230\345\234\250\345\220\214\345\220\215\350\265\204\344\272\247\345\272\223.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\233\345\273\272\350\265\204\344\272\247\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\233\345\273\272\350\265\204\344\272\247\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..3104717bfa8f6615ad6726577a24938bc29884b2 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\233\345\273\272\350\265\204\344\272\247\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\344\270\215\345\255\230\345\234\250\347\232\204\350\265\204\344\272\247\345\244\261\350\264\245.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\344\270\215\345\255\230\345\234\250\347\232\204\350\265\204\344\272\247\345\244\261\350\264\245.png" new file mode 100644 index 0000000000000000000000000000000000000000..454b9fdfa4b7f209dc370f78677a2f4e71ea49be Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\344\270\215\345\255\230\345\234\250\347\232\204\350\265\204\344\272\247\345\244\261\350\264\245.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\257\255\346\226\231.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\257\255\346\226\231.png" new file mode 100644 index 0000000000000000000000000000000000000000..d52d25d4778f6db2d2ec076d65018c40cd1da4d3 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\257\255\346\226\231.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245\357\274\214\350\265\204\344\272\247\344\270\213\344\270\215\345\255\230\345\234\250\345\257\271\345\272\224\350\265\204\344\272\247\345\272\223.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245\357\274\214\350\265\204\344\272\247\344\270\213\344\270\215\345\255\230\345\234\250\345\257\271\345\272\224\350\265\204\344\272\247\345\272\223.png" new file mode 100644 index 0000000000000000000000000000000000000000..82ed79c0154bd8e406621440c4e4a7caaab7e06e Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245\357\274\214\350\265\204\344\272\247\344\270\213\344\270\215\345\255\230\345\234\250\345\257\271\345\272\224\350\265\204\344\272\247\345\272\223.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\265\204\344\272\247\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\265\204\344\272\247\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..7dd2dea945f39ada1d7dd053d150a995b160f203 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\265\204\344\272\247\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\273\272\347\253\213\350\265\204\344\272\247\345\272\223.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\273\272\347\253\213\350\265\204\344\272\247\345\272\223.png" new file mode 100644 index 0000000000000000000000000000000000000000..84737b4185ce781d7b32ab42d39b8d2452138dad Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\273\272\347\253\213\350\265\204\344\272\247\345\272\223.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\214\207\345\256\232\344\270\215\345\255\230\345\234\250\347\232\204\350\265\204\344\272\247\345\210\233\345\273\272\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\214\207\345\256\232\344\270\215\345\255\230\345\234\250\347\232\204\350\265\204\344\272\247\345\210\233\345\273\272\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245.png" new file mode 100644 index 0000000000000000000000000000000000000000..be89bdfde2518bba3941eee5d475f52ad9124343 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\214\207\345\256\232\344\270\215\345\255\230\345\234\250\347\232\204\350\265\204\344\272\247\345\210\233\345\273\272\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\225\260\346\215\256\345\272\223\345\210\235\345\247\213\345\214\226.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\225\260\346\215\256\345\272\223\345\210\235\345\247\213\345\214\226.png" new file mode 100644 index 0000000000000000000000000000000000000000..27530840aaa5382a226e1ed8baea883895d9d75e Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\225\260\346\215\256\345\272\223\345\210\235\345\247\213\345\214\226.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\225\260\346\215\256\345\272\223\351\205\215\347\275\256\344\277\241\346\201\257\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\225\260\346\215\256\345\272\223\351\205\215\347\275\256\344\277\241\346\201\257\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..aa04e6f7f0648adfca1240c750ca5b79b88da5f9 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\225\260\346\215\256\345\272\223\351\205\215\347\275\256\344\277\241\346\201\257\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\227\240\350\265\204\344\272\247\346\227\266\346\237\245\350\257\242\350\265\204\344\272\247.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\227\240\350\265\204\344\272\247\346\227\266\346\237\245\350\257\242\350\265\204\344\272\247.png" new file mode 100644 index 0000000000000000000000000000000000000000..74905172c0c0a0acc4c4d0e35efd2493dc421c4e Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\227\240\350\265\204\344\272\247\346\227\266\346\237\245\350\257\242\350\265\204\344\272\247.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\347\234\213\346\226\207\346\241\243\344\272\247\347\224\237\347\211\207\346\256\265\346\200\273\346\225\260\345\222\214\344\270\212\344\274\240\346\210\220\345\212\237\346\200\273\346\225\260.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\347\234\213\346\226\207\346\241\243\344\272\247\347\224\237\347\211\207\346\256\265\346\200\273\346\225\260\345\222\214\344\270\212\344\274\240\346\210\220\345\212\237\346\200\273\346\225\260.png" new file mode 100644 index 0000000000000000000000000000000000000000..432fbfcd02f6d2220e7d2a8512aee893d67be24d Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\347\234\213\346\226\207\346\241\243\344\272\247\347\224\237\347\211\207\346\256\265\346\200\273\346\225\260\345\222\214\344\270\212\344\274\240\346\210\220\345\212\237\346\200\273\346\225\260.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\350\257\242\345\205\250\351\203\250\350\257\255\346\226\231.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\350\257\242\345\205\250\351\203\250\350\257\255\346\226\231.png" new file mode 100644 index 0000000000000000000000000000000000000000..a4f4ea8a3999a9ab659ccd9ea39b80b21ff46e84 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\350\257\242\345\205\250\351\203\250\350\257\255\346\226\231.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\350\257\242\350\265\204\344\272\247.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\350\257\242\350\265\204\344\272\247.png" new file mode 100644 index 0000000000000000000000000000000000000000..675b40297363664007f96948fb21b1cb90d6beea Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\350\257\242\350\265\204\344\272\247.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\216\267\345\217\226\346\225\260\346\215\256\345\272\223pod\345\220\215\347\247\260.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\216\267\345\217\226\346\225\260\346\215\256\345\272\223pod\345\220\215\347\247\260.png" new file mode 100644 index 0000000000000000000000000000000000000000..8fc0c988e8b3830c550c6be6e42b88ac13448d1a Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\216\267\345\217\226\346\225\260\346\215\256\345\272\223pod\345\220\215\347\247\260.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\344\270\212\344\274\240\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\344\270\212\344\274\240\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..5c897e9883e868bf5160d92cb106ea4e4e9bc356 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\344\270\212\344\274\240\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\345\210\240\351\231\244\345\244\261\350\264\245\357\274\214\346\234\252\346\237\245\350\257\242\345\210\260\347\233\270\345\205\263\350\257\255\346\226\231.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\345\210\240\351\231\244\345\244\261\350\264\245\357\274\214\346\234\252\346\237\245\350\257\242\345\210\260\347\233\270\345\205\263\350\257\255\346\226\231.png" new file mode 100644 index 0000000000000000000000000000000000000000..407e49b929b7ff4cf14703046a4ba0bfe1bb441e Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\345\210\240\351\231\244\345\244\261\350\264\245\357\274\214\346\234\252\346\237\245\350\257\242\345\210\260\347\233\270\345\205\263\350\257\255\346\226\231.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\346\237\245\350\257\242\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\346\237\245\350\257\242\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..a4f4ea8a3999a9ab659ccd9ea39b80b21ff46e84 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\346\237\245\350\257\242\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\344\270\213\346\234\252\346\237\245\350\257\242\345\210\260\350\265\204\344\272\247\345\272\223.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\344\270\213\346\234\252\346\237\245\350\257\242\345\210\260\350\265\204\344\272\247\345\272\223.png" new file mode 100644 index 0000000000000000000000000000000000000000..45ab521ec5f5afbd81ad54f023aae3b7a867dbf2 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\344\270\213\346\234\252\346\237\245\350\257\242\345\210\260\350\265\204\344\272\247\345\272\223.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\344\270\213\346\237\245\350\257\242\350\265\204\344\272\247\345\272\223\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\344\270\213\346\237\245\350\257\242\350\265\204\344\272\247\345\272\223\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..90ed5624ae93ff9784a750514c53293df4e961f0 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\344\270\213\346\237\245\350\257\242\350\265\204\344\272\247\345\272\223\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\233\345\273\272\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\233\345\273\272\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..7b2cc38a931c9c236517c14c86fa93e3eb2b6dcd Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\233\345\273\272\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\240\351\231\244\345\244\261\350\264\245\357\274\214\344\270\215\345\255\230\345\234\250\350\265\204\344\272\247.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\240\351\231\244\345\244\261\350\264\245\357\274\214\344\270\215\345\255\230\345\234\250\350\265\204\344\272\247.png" new file mode 100644 index 0000000000000000000000000000000000000000..1365a8d69467dec250d3451ac63e2615a2194c18 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\240\351\231\244\345\244\261\350\264\245\357\274\214\344\270\215\345\255\230\345\234\250\350\265\204\344\272\247.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\240\351\231\244\346\210\220\345\212\237png.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\240\351\231\244\346\210\220\345\212\237png.png" new file mode 100644 index 0000000000000000000000000000000000000000..1bd944264baa9369e6f8fbfd04cabcd12730c0e9 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\240\351\231\244\346\210\220\345\212\237png.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\346\237\245\350\257\242\345\244\261\350\264\245\357\274\214\344\270\215\345\255\230\345\234\250\350\265\204\344\272\247.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\346\237\245\350\257\242\345\244\261\350\264\245\357\274\214\344\270\215\345\255\230\345\234\250\350\265\204\344\272\247.png" new file mode 100644 index 0000000000000000000000000000000000000000..58bcd320e145dd29d9e5d49cb6d86964ebb83b51 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\346\237\245\350\257\242\345\244\261\350\264\245\357\274\214\344\270\215\345\255\230\345\234\250\350\265\204\344\272\247.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\344\270\255\351\227\264\345\261\202.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\344\270\255\351\227\264\345\261\202.png" new file mode 100644 index 0000000000000000000000000000000000000000..809b785b999b6663d9e9bd41fed953925093d6bd Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\344\270\255\351\227\264\345\261\202.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\346\272\220\347\233\256\345\275\225.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\346\272\220\347\233\256\345\275\225.png" new file mode 100644 index 0000000000000000000000000000000000000000..62ba5f6615f18deb3d5a71fd68ee8c929638d814 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\346\272\220\347\233\256\345\275\225.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\347\233\256\346\240\207\347\233\256\345\275\225.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\347\233\256\346\240\207\347\233\256\345\275\225.png" new file mode 100644 index 0000000000000000000000000000000000000000..d32c672fafcb0ef665bda0bcfdce19d2df44db01 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\347\233\256\346\240\207\347\233\256\345\275\225.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\207\215\345\244\215\345\210\233\345\273\272\350\265\204\344\272\247\345\244\261\350\264\245.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\207\215\345\244\215\345\210\233\345\273\272\350\265\204\344\272\247\345\244\261\350\264\245.png" new file mode 100644 index 0000000000000000000000000000000000000000..a5ecd6b65abc97320e7467f00d82ff1fd9bf0e44 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\207\215\345\244\215\345\210\233\345\273\272\350\265\204\344\272\247\345\244\261\350\264\245.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\351\203\250\347\275\262\350\247\206\345\233\276.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\351\203\250\347\275\262\350\247\206\345\233\276.png" new file mode 100644 index 0000000000000000000000000000000000000000..181bf1d2ddbe15cfd296c27df27d865bdbce8d69 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/pictures/\351\203\250\347\275\262\350\247\206\345\233\276.png" differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/witChainD_deployment.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/witChainD_deployment.md new file mode 100644 index 0000000000000000000000000000000000000000..7aa0d750ae4421365b42ed38e45d3e06d88767ff --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/deploy_guide/witChainD_deployment.md @@ -0,0 +1,122 @@ +# 本地资产库构建指南 + +本平台设计的目的为了为企业(用户)提供一个可视化入口实现对本地文档资产的管理,功能方面分为以下几个部分: + +- 用户管理: + + 您可以通过账号登入witchainD平台并在平台配置大模型,为后续某些文档解析模式提供能力。 +- 资产管理 + + 通过指定资产名称、资产简介、资产默认解析方法、资产默认分块大小和embedding模型等条目创建、删除、导入资产、编辑资产或资产信息 +- 文档管理 + - 支持用户上传限定大小和限定数量的文件,也允许用户下载自己上传的文件,用户可以通过点击资产卡片的形式进入资产,此时文档以条目的形式展示。 + - 支持txt、md、xlsx、docx和doc以及pdf文档的文档解析 + - 文档解析方式有三种general、ocr和enhanced模式,general模式下只提取文字和表格,ocr模式下不仅提取文字和表格嗨提取部分文档的图片内容,enhanced在ocr的基础上对图片中提取的内容进行总结。 + +- 文档解析结果管理: + + 通过指定chunk的类别和关键字过滤目标chunk或者可以指定chunk是否启用,来评判或消除chunk对检索造成的影响。 +- 任务管理 + + 查看当前导入导出资产和文档解析任务的状态、取消或移除导入导出资产库和文档解析任务 +- 检索增强: + + 本平台最终解析的结果通过向量化、关键字的形式对外呈现检索结果,也提供了token压缩和问题补全等技术增强结果命中的概率,也使用了上下文随机关联的形式补全检索结果。 + +## 登录管理平台 + +请在浏览器中输入 `https://$(wichaind域名)`访问 EulerCopilot 的 WitChainD 网页, +登入界面如下,输入账号(admin)、密码(123456)点击登录按钮登录系统。 + +![witchaind登录界面](./pictures/witChainD/witchaind登录界面.png) + +## 新建资产 + +### 1. 查看资产库 + +进入资产卡片显示页面,卡片展示了资产的名称、简介、文档篇数、创建时间和资产ID。 + +![查看资产库界面](./pictures/witChainD/查看资产库界面.png) + +可通过红框中的按钮将卡片展示的资产转换为条目显示。 + +### 2. 新建资产库 + +点击新建资产,会跳出资产信息配置的框图 + +![新建资产库界面](./pictures/witChainD/新建资产库界面.png) + +填写资产库名称、资产库描述(可选)、语言、嵌入模型、默认解析方法和默认文件分块大小后,点击确定。 +![新建资产库填写展示界面](./pictures/witChainD/新建资产库填写展示界面.png) + +资产库建立完毕之后会显示是否添加文档,点击确定 + +![资产库创建完成界面](./pictures/witChainD/资产库创建完成界面.png) + +点击确定完成后进入资产库 + +![资产库创建完成界面](./pictures/witChainD/进入资产库界面.png) + +## 导入文档 + +### 单次导入 + +点击导入文档按钮跳出文档上传框,选择本地文件并勾选进行上传 + +![导入文档界面](./pictures/witChainD/导入文档界面.png) + +![选择文件](./pictures/witChainD/选择文件.png) + +### 批量导入 + +点击1批量导入资产,2选择本地资产,3勾选本地资产,最后点击确定进行资产导入。 + +![批量导入界面](./pictures/witChainD/批量导入界面.png) + +资产导入中 + +![批量正在导入界面](./pictures/witChainD/批量正在导入界面.png) + +资产导入成功 + +![批量导入成功界面](./pictures/witChainD/批量导入成功界面.png) + +## 解析文档 + +等待解析中,点击取消可以停止文档解析。 + +![解析中界面](./pictures/witChainD/解析中界面.png) + +解析完成后,解析状态会显示解析成功。 + +![解析成功界面](./pictures/witChainD/解析成功界面.png) + +文档解析方式有三种general、ocr和enhanced模式,请根据需要选择合适的文档解析方法 + +![解析方法选择界面](./pictures/witChainD/解析方法选择界面.png) + +解析完毕可以通过点击文件名进入文档解析结果展示详情,可以查看文档解析结果,如下图所示: + +![解析详情界面](./pictures/witChainD/解析详情界面.png) + +可以通过1过滤文档解析的片段、表格和图片等内容,通过2可以通过关键字检索模糊检索对应的片段,通过3可以设定是否在检索中是否启用片段,如下图所示: + +![解析结果过滤界面](./pictures/witChainD/解析结果过滤界面.png) + +## 导出资产 + +点击回到首页 + +![回到首页](./pictures/witChainD/回到首页.png) + +再点击导出资产 + +![导出资产界面](./pictures/witChainD/导出资产界面.png) + +显示资产正在导出中,如下图所示: + +![正在导出界面](./pictures/witChainD/正在导出界面.png) + +导出成功点击下载,显示下载成功 + +![导出成功](./pictures/witChainD/导出成功.png) diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\345\257\274\345\205\245\346\226\207\346\241\243.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\345\257\274\345\205\245\346\226\207\346\241\243.png" new file mode 100644 index 0000000000000000000000000000000000000000..3d6818a10a728cd8bf7bd15b6f4f1a8e7817e9c4 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\345\257\274\345\205\245\346\226\207\346\241\243.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\345\257\274\345\207\272\350\265\204\344\272\247\345\272\223.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\345\257\274\345\207\272\350\265\204\344\272\247\345\272\223.png" new file mode 100644 index 0000000000000000000000000000000000000000..73f3d3b92800e51bf00c9b71c82d76cabd5352de Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\345\257\274\345\207\272\350\265\204\344\272\247\345\272\223.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\211\271\351\207\217\345\220\257\347\224\250.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\211\271\351\207\217\345\220\257\347\224\250.png" new file mode 100644 index 0000000000000000000000000000000000000000..3cf960c771ae2ce533f311a55584734c7853f07c Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\211\271\351\207\217\345\220\257\347\224\250.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\211\271\351\207\217\345\257\274\345\205\245\350\265\204\344\272\247\345\272\223.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\211\271\351\207\217\345\257\274\345\205\245\350\265\204\344\272\247\345\272\223.png" new file mode 100644 index 0000000000000000000000000000000000000000..e08bc79f363a862e2a0f3780487c5614c6415b64 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\211\271\351\207\217\345\257\274\345\205\245\350\265\204\344\272\247\345\272\223.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\220\234\347\264\242\346\226\207\346\241\243.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\220\234\347\264\242\346\226\207\346\241\243.png" new file mode 100644 index 0000000000000000000000000000000000000000..7f71660723fcc451152b73e12a0c630604efa390 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\220\234\347\264\242\346\226\207\346\241\243.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\207\346\234\254\345\235\227\347\273\223\346\236\234\351\242\204\350\247\210.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\207\346\234\254\345\235\227\347\273\223\346\236\234\351\242\204\350\247\210.png" new file mode 100644 index 0000000000000000000000000000000000000000..05e003a48f4fb0a452448b0dc8bf74b598e6936e Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\207\346\234\254\345\235\227\347\273\223\346\236\234\351\242\204\350\247\210.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\207\346\241\243\347\256\241\347\220\206\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\207\346\241\243\347\256\241\347\220\206\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..c17ea11b55489c10fa52eae2e9d8915313e3d39e Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\207\346\241\243\347\256\241\347\220\206\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\207\346\241\243\350\247\243\346\236\220.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\207\346\241\243\350\247\243\346\236\220.png" new file mode 100644 index 0000000000000000000000000000000000000000..2524ce76edb826092b5dc9611d64537bed08b3ec Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\207\346\241\243\350\247\243\346\236\220.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\207\346\241\243\350\247\243\346\236\2202.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\207\346\241\243\350\247\243\346\236\2202.png" new file mode 100644 index 0000000000000000000000000000000000000000..30dd2f5bef9b23c3dceb92b63817898076096a49 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\207\346\241\243\350\247\243\346\236\2202.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\260\345\242\236\350\265\204\344\272\247\345\272\223.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\260\345\242\236\350\265\204\344\272\247\345\272\223.png" new file mode 100644 index 0000000000000000000000000000000000000000..d728d99741a03ff2f82e2c59bd424b848614aebe Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\226\260\345\242\236\350\265\204\344\272\247\345\272\223.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\250\241\345\236\213\351\205\215\347\275\256.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\250\241\345\236\213\351\205\215\347\275\256.png" new file mode 100644 index 0000000000000000000000000000000000000000..97a489cc7637416306a88394a3faa7fa47cf9b95 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\346\250\241\345\236\213\351\205\215\347\275\256.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\347\274\226\350\276\221\346\226\207\346\241\243\351\205\215\347\275\256.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\347\274\226\350\276\221\346\226\207\346\241\243\351\205\215\347\275\256.png" new file mode 100644 index 0000000000000000000000000000000000000000..bd0ed29ba5d6a4eb4dca5851b8469bd161f70300 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\347\274\226\350\276\221\346\226\207\346\241\243\351\205\215\347\275\256.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\347\274\226\350\276\221\350\265\204\344\272\247\345\272\223.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\347\274\226\350\276\221\350\265\204\344\272\247\345\272\223.png" new file mode 100644 index 0000000000000000000000000000000000000000..3488720160efd58d2fd1f46046f04296f552b4d6 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\347\274\226\350\276\221\350\265\204\344\272\247\345\272\223.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\347\274\226\350\276\221\350\265\204\344\272\247\345\272\2230.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\347\274\226\350\276\221\350\265\204\344\272\247\345\272\2230.png" new file mode 100644 index 0000000000000000000000000000000000000000..64d0cc3f8637592007503972267751f2bbe87b96 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\347\274\226\350\276\221\350\265\204\344\272\247\345\272\2230.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\347\274\226\350\276\221\350\265\204\344\272\247\345\272\223\351\205\215\347\275\256.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\347\274\226\350\276\221\350\265\204\344\272\247\345\272\223\351\205\215\347\275\256.png" new file mode 100644 index 0000000000000000000000000000000000000000..e91dd94c7dc0a71e3f3ddee47c3d21926c27e619 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\347\274\226\350\276\221\350\265\204\344\272\247\345\272\223\351\205\215\347\275\256.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\350\247\243\346\236\220\345\256\214\346\210\220.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\350\247\243\346\236\220\345\256\214\346\210\220.png" new file mode 100644 index 0000000000000000000000000000000000000000..9e9968fc2e71ace3a58ec454e19b25bcd961f0c0 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\350\247\243\346\236\220\345\256\214\346\210\220.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\350\265\204\344\272\247\345\272\223\347\256\241\347\220\206\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\350\265\204\344\272\247\345\272\223\347\256\241\347\220\206\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..33b9a3e0852f8e5ae1e95da572dcfc13f6d59da2 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/pictures/\350\265\204\344\272\247\345\272\223\347\256\241\347\220\206\347\225\214\351\235\242.png" differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/witChainD_guidance.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/witChainD_guidance.md new file mode 100644 index 0000000000000000000000000000000000000000..4759a57baa4e35ee529e9f4da70e1d1405612e6e --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/knowledge_base/user_guide/witChainD_guidance.md @@ -0,0 +1,87 @@ +# witChainD 使用指南——知识库管理 + +完成 witChainD 部署之后,即可使用 witChainD 进行知识库管理管理。 + +下文会从页面的维度进行 witChainD 的功能介绍。 + +## 1. 资产库管理界面 + +该页面为资产库管理界面,用户登录后将会进入该界面。 + +![资产库管理界面](./pictures/资产库管理界面.png) + +**支持操作:** + +- 配置模型:点击右上角的设置按键,可以修改模型相关的配置。 + + ![模型配置](./pictures/模型配置.png) + +- 新增资产库:点击新建资产库按钮新建,支持自定义名称、描述、语言、嵌入模型、解析方法、文件分块大小、文档类别。注意:重复名称会自动将名称修改成资产库id。 + + ![新增资产库](./pictures/新增资产库.png) + +- 编辑资产库:点击资产库的编辑按钮编辑,支持修改名称、描述、语言、解析方法、文件分块大小、文档类别。注意:不能修改成重复名称。 + + ![编辑资产库](./pictures/编辑资产库0.png) + + ![编辑资产库](./pictures/编辑资产库.png) + +- 导出资产库:点击资产库的导出按钮导出,导出完成后需要按任务列表中的下载任务下载对应资产库到本地。 + + ![导出资产库](./pictures/导出资产库.png) + +- 批量导入资产库:点击批量导入,上传本地文件后选中即可导入。 + + ![批量导入资产库](./pictures/批量导入资产库.png) + +- 搜索资产库:在搜索栏中键入文本,可以搜索得到名称包含对应文本的资产库。 + +## 2. 文档管理界面 + +在资产管理界面点击对应资产库,可以进入文档管理界面。 + +![文档管理界面](./pictures/文档管理界面.png) + +**支持操作:** + +- 导入文档:点击导入文档,从本地上传文件导入,导入后会自动以该资产库默认配置开始解析。 + + ![导入文档](./pictures/导入文档.png) + +- 解析文档:点击操作中的解析,对文档进行解析。也可以选中多个文档批量解析。 + + ![文档解析](./pictures/文档解析.png) + + ![文档解析2](./pictures/文档解析2.png) + + ![解析完成](./pictures/解析完成.png) + +- 编辑文档配置:点击编辑对文档配置进行编辑,支持编辑文档名称、解析方法、类别、文件分块大小。 + + ![编辑文档配置](./pictures/编辑文档配置.png) + +- 下载文档:点击下载即可将文档下载至本地,也可以选中多个文档批量下载。 + +- 删除文档:点击删除即可将文档从资产库中删除,也可以选中多个文档批量删除。 + +- 搜索文档:点击文档名称旁的搜索键,在弹出的搜索框中键入搜索的文本,可以搜索得到名称包含这些文本的文档。 + + ![搜索文档](./pictures/搜索文档.png) + +- 编辑资产库配置:支持编辑资产库名称、描述、语言、默认解析方法、文件分块大小、文档信息类别。 + + ![编辑资产库配置](./pictures/编辑资产库配置.png) + +## 3. 解析结果管理界面 + +点击解析完成的文档,可以进入文档的解析结果管理界面。界面中会按照顺序显示文档解析后的文本块内容预览,每个文本块会附带一个标签,表示该文本块中的信息来源于文档中的段落、列表或者是图片。右侧的开关表示该文本块是否被启用。 + +![文本块结果预览](./pictures/文本块结果预览.png) + +**支持操作**: + +- 关闭/启用文本块:点击文本块右侧的开关即可关闭/启用对应文本块,也可以选中多个文本块批量关闭/启用。 + + ![批量启用](./pictures/批量启用.png) + +- 搜索文本块:在搜索框中键入内容,可以查找包含对应内容的文本块。 diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/deploy_guide/diagnosis_deployment.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/deploy_guide/diagnosis_deployment.md new file mode 100644 index 0000000000000000000000000000000000000000..7581b1bd17e804f72923d56b5e4f7a036ca2bd93 --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/deploy_guide/diagnosis_deployment.md @@ -0,0 +1,189 @@ +# 智能诊断部署指南 + +## 准备工作 + ++ 提前安装 [EulerCopilot 命令行(智能 Shell)客户端](../../../quick_start/smart_shell/user_guide/shell.md) + ++ 被诊断机器不能安装 crictl 和 isula,只能有 docker 一个容器管理工具 + ++ 在需要被诊断的机器上安装 gala-gopher 和 gala-anteater + +## 部署 gala-gopher + +### 1. 准备 BTF 文件 + +**如果Linux内核支持 BTF,则不需要准备 BTF 文件。**可以通过以下命令来查看Linux内核是否已经支持 BTF: + +```bash +cat /boot/config-$(uname -r) | grep CONFIG_DEBUG_INFO_BTF +``` + +如果输出结果为`CONFIG_DEBUG_INFO_BTF=y`,则表示内核支持BTF。否则表示内核不支持BTF。 +如果内核不支持BTF,需要手动制作BTF文件。步骤如下: + +1. 获取当前Linux内核版本的 vmlinux 文件 + + vmlinux 文件存放在 `kernel-debuginfo` 包里面,存放路径为 `/usr/lib/debug/lib/modules/$(uname -r)/vmlinux`。 + + 例如,对于 `kernel-debuginfo-5.10.0-136.65.0.145.oe2203sp1.aarch64`,对应的vmlinux路径为`/usr/lib/debug/lib/modules/5.10.0-136.65.0.145.oe2203sp1.aarch64/vmlinux`。 + +2. 制作 BTF 文件 + + 基于获取到 vmlinux 文件来制作 BTF 文件。这一步可以在自己的环境里操作。首先,需要安装相关的依赖包: + + ```bash + # 说明:dwarves 包中包含 pahole 命令,llvm 包中包含 llvm-objcopy 命令 + yum install -y llvm dwarves + ``` + + 执行下面的命令行,生成 BTF 文件。 + + ```bash + kernel_version=4.19.90-2112.8.0.0131.oe1.aarch64 # 说明:这里需要替换成目标内核版本,可通过 uname -r 命令获取 + pahole -J vmlinux + llvm-objcopy --only-section=.BTF --set-section-flags .BTF=alloc,readonly --strip-all vmlinux ${kernel_version}.btf + strip -x ${kernel_version}.btf + ``` + + 生成的 BTF 文件名称为`.btf`格式,其中 ``为目标机器的内核版本,可通过 `uname -r` 命令获取。 + +### 2. 下载 gala-gopher 容器镜像 + +#### 在线下载 + +gala-gopher 容器镜像已归档到 仓库中,可通过如下命令获取。 + +```bash +# 获取 aarch64 架构的镜像 +docker pull hub.oepkgs.net/a-ops/gala-gopher-profiling-aarch64:latest +# 获取 x86_64 架构的镜像 +docker pull hub.oepkgs.net/a-ops/gala-gopher-profiling-x86_64:latest +``` + +#### 离线下载 + +若无法通过在线下载的方式下载容器镜像,可联系我(何秀军 00465007)获取压缩包。 + +拿到压缩包后,放到目标机器上,解压并加载容器镜像,命令行如下: + +```bash +tar -zxvf gala-gopher-profiling-aarch64.tar.gz +docker load < gala-gopher-profiling-aarch64.tar +``` + +### 3. 启动 gala-gopher 容器 + +容器启动命令: + +```shell +docker run -d --name gala-gopher-profiling --privileged --pid=host --network=host -v /:/host -v /etc/localtime:/etc/localtime:ro -v /sys:/sys -v /usr/lib/debug:/usr/lib/debug -v /var/lib/docker:/var/lib/docker -v /tmp/$(uname -r).btf:/opt/gala-gopher/btf/$(uname -r).btf -e GOPHER_HOST_PATH=/host gala-gopher-profiling-aarch64:latest +``` + +启动配置参数说明: + ++ `-v /tmp/$(uname -r).btf:/opt/gala-gopher/btf/$(uname -r).btf` :如果内核支持 BTF,则删除该配置即可。如果内核不支持 BTF,则需要将前面准备好的 BTF 文件拷贝到目标机器上,并将 `/tmp/$(uname -r).btf` 替换为对应的路径。 ++ `gala-gopher-profiling-aarch64-0426` :gala-gopher容器对应的tag,替换成实际下载的tag。 + +探针启动: + ++ `container_id` 为需要观测的容器 id ++ 分别启动 sli 和 container 探针 + +```bash +curl -X PUT http://localhost:9999/sli -d json='{"cmd":{"check_cmd":""},"snoopers":{"container_id":[""]},"params":{"report_period":5},"state":"running"}' +``` + +```bash +curl -X PUT http://localhost:9999/container -d json='{"cmd":{"check_cmd":""},"snoopers":{"container_id":[""]},"params":{"report_period":5},"state":"running"}' +``` + +探针关闭 + +```bash +curl -X PUT http://localhost:9999/sli -d json='{"state": "stopped"}' +``` + +```bash +curl -X PUT http://localhost:9999/container -d json='{"state": "stopped"}' +``` + +## 部署 gala-anteater + +源码部署: + +```bash +# 请指定分支为 930eulercopilot +git clone https://gitee.com/GS-Stephen_Curry/gala-anteater.git +``` + +安装部署请参考 +(请留意python版本导致执行setup.sh install报错) + +镜像部署: + +```bash +docker pull hub.oepkgs.net/a-ops/gala-anteater:2.0.2 +``` + +`/etc/gala-anteater/config/gala-anteater.yaml` 中 Kafka 和 Prometheus 的 `server` 和 `port` 需要按照实际部署修改,`model_topic`、`meta_topic`、`group_id` 自定义 + +```yaml +Kafka: + server: "xxxx" + port: "xxxx" + model_topic: "xxxx" # 自定义,与rca配置中保持一致 + meta_topic: "xxxx" # 自定义,与rca配置中保持一致 + group_id: "xxxx" # 自定义,与rca配置中保持一致 + # auth_type: plaintext/sasl_plaintext, please set "" for no auth + auth_type: "" + username: "" + password: "" + +Prometheus: + server: "xxxx" + port: "xxxx" + steps: "5" +``` + +gala-anteater 中模型的训练依赖于 gala-gopher 采集的数据,因此请保证 gala-gopher 探针正常运行至少24小时,在运行 gala-anteater。 + +## 部署 gala-ops + +每个中间件的大致介绍: + +kafka : 一个数据库中间件, 分布式数据分流作用, 可以配置为当前的管理节点。 + +prometheus:性能监控, 配置需要监控的生产节点 ip list。 + +直接通过yum install安装kafka和prometheus,可参照安装脚本 + +只需要参照其中 kafka 和 prometheus 的安装即可 + +## 部署 euler-copilot-rca + +镜像拉取 + +```bash +docker pull hub.oepkgs.net/a-ops/euler-copilot-rca:0.9.1 +``` + ++ 修改 `config/config.json` 文件,配置 gala-gopher 镜像的 `container_id` 以及 `ip`,Kafka 和 Prometheus 的 `ip` 和 `port`(与上述 gala-anteater 配置保持一致) + +```yaml +"gopher_container_id": "xxxx", # gala-gopher的容器id + "remote_host": "xxxx" # gala-gopher的部署机器ip + }, + "kafka": { + "server": "xxxx", + "port": "xxxx", + "storage_topic": "usad_intermediate_results", + "anteater_result_topic": "xxxx", + "rca_result_topic": "xxxx", + "meta_topic": "xxxx" + }, + "prometheus": { + "server": "xxxx", + "port": "xxxx", + "steps": 5 + }, +``` diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/diagnosis_guidance.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/diagnosis_guidance.md new file mode 100644 index 0000000000000000000000000000000000000000..eb428ada7017effc1e0d58c18c71eae7ffe88050 --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/diagnosis_guidance.md @@ -0,0 +1,50 @@ +# 智能插件:智能诊断 + +部署智能诊断工具后,可以通过 EulerCopilot 智能体框架实现对本机进行诊断。 +在智能诊断模式提问,智能体框架服务可以调用本机的诊断工具诊断异常状况、分析并生成报告。 + +## 操作步骤 + +**步骤1** 切换到“智能插件”模式 + +```bash +copilot -p +``` + +![切换到智能插件模式](./pictures/shell-plugin-diagnose-switch-mode.png) + +**步骤2** 异常事件检测 + +```bash +帮我进行异常事件检测 +``` + +按下 `Ctrl + O` 键提问,然后在插件列表中选择“智能诊断”。 + +![异常事件检测](./pictures/shell-plugin-diagnose-detect.png) + +**步骤3** 查看异常事件详情 + +```bash +查看 XXX 容器的异常事件详情 +``` + +![查看异常事件详情](./pictures/shell-plugin-diagnose-detail.png) + +**步骤4** 执行异常事件分析 + +```bash +请对 XXX 容器的 XXX 指标执行 profiling 分析 +``` + +![异常事件分析](./pictures/shell-plugin-diagnose-profiling.png) + +**步骤5** 查看异常事件分析报告 + +等待 5 至 10 分钟,然后查看分析报告。 + +```bash +查看 对应的 profiling 报告 +``` + +![执行优化脚本](./pictures/shell-plugin-diagnose-report.png) diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-chat-ask.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-chat-ask.png new file mode 100644 index 0000000000000000000000000000000000000000..00d5cf5ecf894dd62366ec086bf96eae532f0b5d Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-chat-ask.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-chat-continue-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-chat-continue-result.png new file mode 100644 index 0000000000000000000000000000000000000000..f30f9fe7a015e775742bc184b8ac75790dc482fa Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-chat-continue-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-chat-continue.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-chat-continue.png new file mode 100644 index 0000000000000000000000000000000000000000..7e4801504fd53fab989574416e6220c4fa3f1d38 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-chat-continue.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-chat-exit.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-chat-exit.png new file mode 100644 index 0000000000000000000000000000000000000000..0bb81190a3039f6c5a311b365376ec230c1ad4b5 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-chat-exit.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-edit-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-edit-result.png new file mode 100644 index 0000000000000000000000000000000000000000..c5e6f8245e7d66cdbe5370f18d15a791a33a517a Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-edit-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-edit.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..bb6209373a6d2a1881728bee352e7c3b46cc91d7 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-edit.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-exec-multi-select.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-exec-multi-select.png new file mode 100644 index 0000000000000000000000000000000000000000..2dda108a39af54fc15a4ff8c0dca107de38b9cf0 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-exec-multi-select.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-exec-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-exec-result.png new file mode 100644 index 0000000000000000000000000000000000000000..f4fff6a62b8b4220b52fdf55b133f2ba37850569 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-exec-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-explain-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-explain-result.png new file mode 100644 index 0000000000000000000000000000000000000000..707dd36aa7c7eadae4f29254cf5fc18ce877f597 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-explain-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-explain-select.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-explain-select.png new file mode 100644 index 0000000000000000000000000000000000000000..bf58b69e241ea11a6945f21e3fc69d22a401be2e Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-explain-select.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-interact.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-interact.png new file mode 100644 index 0000000000000000000000000000000000000000..00bb3a288fbd2fb962b08f34fbe90c733afe0343 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd-interact.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..619172c8ed60a7b536364944a306fbf76fcbfb1f Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-cmd.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-help.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-help.png new file mode 100644 index 0000000000000000000000000000000000000000..97d0dedd3f7b1c749bc5fded471744923d766b8b Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-help.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-init.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-init.png new file mode 100644 index 0000000000000000000000000000000000000000..bbb2257eb1ff2bfec36110409fc6c55a26386c9e Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-init.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-detail.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-detail.png new file mode 100644 index 0000000000000000000000000000000000000000..7bd624e025eaae4b77c603d88bf1b9ad5e235fe7 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-detail.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-detect.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-detect.png new file mode 100644 index 0000000000000000000000000000000000000000..2b38259ff0c1c7045dbff9abf64f36a109a3377b Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-detect.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-profiling.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-profiling.png new file mode 100644 index 0000000000000000000000000000000000000000..0e63c01f35dbc291f805b56de749eac09e0a079d Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-profiling.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-report.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-report.png new file mode 100644 index 0000000000000000000000000000000000000000..c16f0184a2ad3d2468466b33d0e861d2a31bc4e2 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-report.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-switch-mode.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-switch-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..165c6c453353b70c3e1e2cb07d7f43d5ee3525e3 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-diagnose-switch-mode.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-result.png new file mode 100644 index 0000000000000000000000000000000000000000..3e3f45a974a0700d209f7d30af89eb2050a392d6 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-select.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-select.png new file mode 100644 index 0000000000000000000000000000000000000000..13959203c77eaa9f41051897cf9e847ff3642a8a Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-select.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-metrics-collect.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-metrics-collect.png new file mode 100644 index 0000000000000000000000000000000000000000..4d5678b7f77b05d48552fcb9656f4a4372dbbe61 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-metrics-collect.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-report.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-report.png new file mode 100644 index 0000000000000000000000000000000000000000..01daaa9a84c13158a95afddffeb8a7e3303f1e76 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-report.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-script-exec.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-script-exec.png new file mode 100644 index 0000000000000000000000000000000000000000..0b694c3fba6918ef39cca977b2072b2913d12b95 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-script-exec.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-script-gen.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-script-gen.png new file mode 100644 index 0000000000000000000000000000000000000000..6e95551767e213f59669d03fd4cceba05801a983 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-script-gen.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-script-view.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-script-view.png new file mode 100644 index 0000000000000000000000000000000000000000..c82c77bf6f4e4e19f400395aaadc9f99dc8d373c Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-script-view.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-switch-mode.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-switch-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..0f06c803ea3621a0f4fb83bbbe731e2bb4bba788 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin-tuning-switch-mode.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..4c1afd306a6aee029f5bda38aa7b1fce57227e31 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_diagnosis/user_guide/pictures/shell-plugin.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/deploy_guide/tune_deployment.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/deploy_guide/tune_deployment.md new file mode 100644 index 0000000000000000000000000000000000000000..a770bc81e7efd9ad9f3910d92396a97c62be7ea2 --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/deploy_guide/tune_deployment.md @@ -0,0 +1,126 @@ +# 智能调优部署指南 + +## 准备工作 + ++ 提前安装 [EulerCopilot 命令行(智能 Shell)客户端](../../../quick_start/smart_shell/user_guide/shell.md)) + ++ 被调优机器需要为 openEuler 22.03 LTS-SP3 + ++ 在需要被调优的机器上安装依赖 + +```bash +yum install -y sysstat perf +``` + ++ 被调优机器需要开启 SSH 22端口 + +## 编辑配置文件 + +修改values.yaml文件的tune部分,将 `enable` 字段改为 `True` ,并配置大模型设置、 +Embedding模型文件地址、以及需要调优的机器和对应机器上的 mysql 的账号名以及密码 + +```bash +vim /home/euler-copilot-framework/deploy/chart/agents/values.yaml +``` + +```yaml +tune: + # 【必填】是否启用智能调优Agent + enabled: true + # 镜像设置 + image: + # 镜像仓库。留空则使用全局设置。 + registry: "" + # 【必填】镜像名称 + name: euler-copilot-tune + # 【必填】镜像标签 + tag: "0.9.1" + # 拉取策略。留空则使用全局设置。 + imagePullPolicy: "" + # 【必填】容器根目录只读 + readOnly: false + # 性能限制设置 + resources: {} + # Service设置 + service: + # 【必填】Service类型,ClusterIP或NodePort + type: ClusterIP + nodePort: + # 大模型设置 + llm: + # 【必填】模型地址(需要包含v1后缀) + url: + # 【必填】模型名称 + name: "" + # 【必填】模型API Key + key: "" + # 【必填】模型最大Token数 + max_tokens: 8096 + # 【必填】Embedding模型文件地址 + embedding: "" + # 待优化机器信息 + machine: + # 【必填】IP地址 + ip: "" + # 【必填】Root用户密码 + # 注意:必需启用Root用户以密码形式SSH登录 + password: "" + # 待优化应用设置 + mysql: + # 【必填】数据库用户名 + user: "root" + # 【必填】数据库密码 + password: "" +``` + +## 安装智能调优插件 + +```bash +helm install -n euler-copilot agents . +``` + +如果之前有执行过安装,则按下面指令更新插件服务 + +```bash +helm upgrade-n euler-copilot agents . +``` + +如果 framework未重启,则需要重启framework配置 + +```bash +kubectl delete pod framework-deploy-service-bb5b58678-jxzqr -n eulercopilot +``` + +## 测试 + ++ 查看 tune 的 pod 状态 + + ```bash + NAME READY STATUS RESTARTS AGE + authhub-backend-deploy-authhub-64896f5cdc-m497f 2/2 Running 0 16d + authhub-web-deploy-authhub-7c48695966-h8d2p 1/1 Running 0 17d + pgsql-deploy-databases-86b4dc4899-ppltc 1/1 Running 0 17d + redis-deploy-databases-f8866b56-kj9jz 1/1 Running 0 17d + mysql-deploy-databases-57f5f94ccf-sbhzp 2/2 Running 0 17d + framework-deploy-service-bb5b58678-jxzqr 2/2 Running 0 16d + rag-deploy-service-5b7887644c-sm58z 2/2 Running 0 110m + web-deploy-service-74fbf7999f-r46rg 1/1 Running 0 2d + tune-deploy-agents-5d46bfdbd4-xph7b 1/1 Running 0 2d + ``` + ++ pod启动失败排查办法 + + 检查 euler-copilot-tune 目录下的 openapi.yaml 中 `servers.url` 字段,确保调优服务的启动地址被正确设置 + + 检查 `$plugin_dir` 插件文件夹的路径是否配置正确,该变量位于 `deploy/chart/euler_copilot/values.yaml` 中的 `framework`模块,如果插件目录不存在,需新建该目录,并需要将该目录下的 euler-copilot-tune 文件夹放到 `$plugin_dir` 中。 + + 检查sglang的地址和key填写是否正确,该变量位于 `vim /home/euler-copilot-framework/deploy/chart/euler_copilot/values.yaml` + + ```yaml + # 用于Function Call的模型 + scheduler: + # 推理框架类型 + backend: sglang + # 模型地址 + url: "" + # 模型 API Key + key: "" + # 数据库设置 + ``` diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-chat-ask.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-chat-ask.png new file mode 100644 index 0000000000000000000000000000000000000000..00d5cf5ecf894dd62366ec086bf96eae532f0b5d Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-chat-ask.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-chat-continue-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-chat-continue-result.png new file mode 100644 index 0000000000000000000000000000000000000000..f30f9fe7a015e775742bc184b8ac75790dc482fa Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-chat-continue-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-chat-continue.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-chat-continue.png new file mode 100644 index 0000000000000000000000000000000000000000..7e4801504fd53fab989574416e6220c4fa3f1d38 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-chat-continue.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-chat-exit.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-chat-exit.png new file mode 100644 index 0000000000000000000000000000000000000000..0bb81190a3039f6c5a311b365376ec230c1ad4b5 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-chat-exit.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-edit-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-edit-result.png new file mode 100644 index 0000000000000000000000000000000000000000..c5e6f8245e7d66cdbe5370f18d15a791a33a517a Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-edit-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-edit.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..bb6209373a6d2a1881728bee352e7c3b46cc91d7 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-edit.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-exec-multi-select.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-exec-multi-select.png new file mode 100644 index 0000000000000000000000000000000000000000..2dda108a39af54fc15a4ff8c0dca107de38b9cf0 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-exec-multi-select.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-exec-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-exec-result.png new file mode 100644 index 0000000000000000000000000000000000000000..f4fff6a62b8b4220b52fdf55b133f2ba37850569 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-exec-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-explain-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-explain-result.png new file mode 100644 index 0000000000000000000000000000000000000000..707dd36aa7c7eadae4f29254cf5fc18ce877f597 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-explain-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-explain-select.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-explain-select.png new file mode 100644 index 0000000000000000000000000000000000000000..bf58b69e241ea11a6945f21e3fc69d22a401be2e Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-explain-select.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-interact.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-interact.png new file mode 100644 index 0000000000000000000000000000000000000000..00bb3a288fbd2fb962b08f34fbe90c733afe0343 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd-interact.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..619172c8ed60a7b536364944a306fbf76fcbfb1f Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-cmd.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-help.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-help.png new file mode 100644 index 0000000000000000000000000000000000000000..97d0dedd3f7b1c749bc5fded471744923d766b8b Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-help.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-init.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-init.png new file mode 100644 index 0000000000000000000000000000000000000000..bbb2257eb1ff2bfec36110409fc6c55a26386c9e Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-init.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-detail.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-detail.png new file mode 100644 index 0000000000000000000000000000000000000000..7bd624e025eaae4b77c603d88bf1b9ad5e235fe7 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-detail.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-detect.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-detect.png new file mode 100644 index 0000000000000000000000000000000000000000..2b38259ff0c1c7045dbff9abf64f36a109a3377b Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-detect.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-profiling.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-profiling.png new file mode 100644 index 0000000000000000000000000000000000000000..0e63c01f35dbc291f805b56de749eac09e0a079d Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-profiling.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-report.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-report.png new file mode 100644 index 0000000000000000000000000000000000000000..c16f0184a2ad3d2468466b33d0e861d2a31bc4e2 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-report.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-switch-mode.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-switch-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..165c6c453353b70c3e1e2cb07d7f43d5ee3525e3 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-diagnose-switch-mode.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-result.png new file mode 100644 index 0000000000000000000000000000000000000000..3e3f45a974a0700d209f7d30af89eb2050a392d6 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-select.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-select.png new file mode 100644 index 0000000000000000000000000000000000000000..13959203c77eaa9f41051897cf9e847ff3642a8a Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-select.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-metrics-collect.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-metrics-collect.png new file mode 100644 index 0000000000000000000000000000000000000000..4d5678b7f77b05d48552fcb9656f4a4372dbbe61 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-metrics-collect.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-report.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-report.png new file mode 100644 index 0000000000000000000000000000000000000000..01daaa9a84c13158a95afddffeb8a7e3303f1e76 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-report.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-script-exec.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-script-exec.png new file mode 100644 index 0000000000000000000000000000000000000000..0b694c3fba6918ef39cca977b2072b2913d12b95 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-script-exec.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-script-gen.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-script-gen.png new file mode 100644 index 0000000000000000000000000000000000000000..6e95551767e213f59669d03fd4cceba05801a983 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-script-gen.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-script-view.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-script-view.png new file mode 100644 index 0000000000000000000000000000000000000000..c82c77bf6f4e4e19f400395aaadc9f99dc8d373c Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-script-view.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-switch-mode.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-switch-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..0f06c803ea3621a0f4fb83bbbe731e2bb4bba788 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin-tuning-switch-mode.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..4c1afd306a6aee029f5bda38aa7b1fce57227e31 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/pictures/shell-plugin.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/tune_guidance.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/tune_guidance.md new file mode 100644 index 0000000000000000000000000000000000000000..a9e915e1d80a36e8483d84729d319be5c8e873ec --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/advance/smart_tuning/user_guide/tune_guidance.md @@ -0,0 +1,53 @@ +# 智能插件:智能调优 + +部署智能调优工具后,可以通过 EulerCopilot 智能体框架实现对本机进行调优。 +在智能调优模式提问,智能体框架服务可以调用本机的调优工具采集性能指标,并生成性能分析报告和性能优化建议。 + +## 操作步骤 + +**步骤1** 切换到“智能调优”模式 + +```bash +copilot -t +``` + +![切换到智能调优模式](./pictures/shell-plugin-tuning-switch-mode.png) + +**步骤2** 采集性能指标 + +```bash +帮我进行性能指标采集 +``` + +![性能指标采集](./pictures/shell-plugin-tuning-metrics-collect.png) + +**步骤3** 生成性能分析报告 + +```bash +帮我生成性能分析报告 +``` + +![性能分析报告](./pictures/shell-plugin-tuning-report.png) + +**步骤4** 生成性能优化建议 + +```bash +请生成性能优化脚本 +``` + +![性能优化脚本](./pictures/shell-plugin-tuning-script-gen.png) + +**步骤5** 选择“执行命令”,运行优化脚本 + +![执行优化脚本](./pictures/shell-plugin-tuning-script-exec.png) + +- 脚本内容如图: + ![优化脚本内容](./pictures/shell-plugin-tuning-script-view.png) + +## 远程调优 + +如果需要对其他机器进行远程调优,请在上文示例的问题前面加上对应机器的 IP 地址。 + +例如:`请对 192.168.1.100 这台机器进行性能指标采集。` + +进行远程调优前请确保目标机器已部署智能调优工具,同时请确保 EulerCopilot 智能体框架能够访问目标机器。 diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/API_key.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/API_key.md new file mode 100644 index 0000000000000000000000000000000000000000..5fc8699eb097e6a7dcb17409519343c46b9801ca --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/API_key.md @@ -0,0 +1,29 @@ +# 获取 API Key + +## 前言 + +EulerCopilot 命令行助手使用 API Key 来验证用户身份,并获取 API 访问权限。 +因此,开始使用前,您需要先获取 API Key。 + +## 注意事项 + +- 请妥善保管您的 API Key,不要泄露给他人。 +- API Key 仅用于命令行助手与 DevStation 桌面端,不用于其他用途。 +- 每位用户仅可拥有一个 API Key,重复创建 API Key 将导致旧密钥失效。 +- API Key 仅在创建时显示一次,请务必及时保存。若密钥丢失,您需要重新创建。 +- 若您在使用过程中遇到“请求过于频繁”的错误,您的 API Key 可能已被他人使用,请及时前往官网刷新或撤销 API Key。 + +## 获取方法 + +1. 登录 EulerCopilot 网页端。 +2. 点击右上角头像,选择“API KEY”。 +3. 点击“新建”按钮。 +4. **请立即保存 API Key,它仅在创建时显示一次,请勿泄露给他人。** + +## 管理 API Key + +1. 登录 EulerCopilot 网页端。 +2. 点击右上角头像,选择“API KEY”。 +3. 点击“刷新”按钮,刷新 API Key;点击“撤销”按钮,撤销 API Key。 + +- 刷新 API Key 后,旧密钥失效,请立即保存新生成的 API Key。 diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-chat-ask.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-chat-ask.png new file mode 100644 index 0000000000000000000000000000000000000000..00d5cf5ecf894dd62366ec086bf96eae532f0b5d Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-chat-ask.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-chat-continue-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-chat-continue-result.png new file mode 100644 index 0000000000000000000000000000000000000000..f30f9fe7a015e775742bc184b8ac75790dc482fa Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-chat-continue-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-chat-continue.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-chat-continue.png new file mode 100644 index 0000000000000000000000000000000000000000..7e4801504fd53fab989574416e6220c4fa3f1d38 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-chat-continue.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-chat-exit.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-chat-exit.png new file mode 100644 index 0000000000000000000000000000000000000000..0bb81190a3039f6c5a311b365376ec230c1ad4b5 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-chat-exit.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-edit-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-edit-result.png new file mode 100644 index 0000000000000000000000000000000000000000..c5e6f8245e7d66cdbe5370f18d15a791a33a517a Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-edit-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-edit.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..bb6209373a6d2a1881728bee352e7c3b46cc91d7 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-edit.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-exec-multi-select.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-exec-multi-select.png new file mode 100644 index 0000000000000000000000000000000000000000..2dda108a39af54fc15a4ff8c0dca107de38b9cf0 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-exec-multi-select.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-exec-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-exec-result.png new file mode 100644 index 0000000000000000000000000000000000000000..f4fff6a62b8b4220b52fdf55b133f2ba37850569 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-exec-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-explain-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-explain-result.png new file mode 100644 index 0000000000000000000000000000000000000000..707dd36aa7c7eadae4f29254cf5fc18ce877f597 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-explain-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-explain-select.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-explain-select.png new file mode 100644 index 0000000000000000000000000000000000000000..bf58b69e241ea11a6945f21e3fc69d22a401be2e Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-explain-select.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-interact.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-interact.png new file mode 100644 index 0000000000000000000000000000000000000000..00bb3a288fbd2fb962b08f34fbe90c733afe0343 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd-interact.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..619172c8ed60a7b536364944a306fbf76fcbfb1f Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-cmd.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-help.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-help.png new file mode 100644 index 0000000000000000000000000000000000000000..97d0dedd3f7b1c749bc5fded471744923d766b8b Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-help.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-init.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-init.png new file mode 100644 index 0000000000000000000000000000000000000000..bbb2257eb1ff2bfec36110409fc6c55a26386c9e Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-init.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-detail.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-detail.png new file mode 100644 index 0000000000000000000000000000000000000000..7bd624e025eaae4b77c603d88bf1b9ad5e235fe7 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-detail.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-detect.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-detect.png new file mode 100644 index 0000000000000000000000000000000000000000..2b38259ff0c1c7045dbff9abf64f36a109a3377b Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-detect.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-profiling.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-profiling.png new file mode 100644 index 0000000000000000000000000000000000000000..0e63c01f35dbc291f805b56de749eac09e0a079d Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-profiling.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-report.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-report.png new file mode 100644 index 0000000000000000000000000000000000000000..c16f0184a2ad3d2468466b33d0e861d2a31bc4e2 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-report.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-switch-mode.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-switch-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..165c6c453353b70c3e1e2cb07d7f43d5ee3525e3 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-diagnose-switch-mode.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-result.png new file mode 100644 index 0000000000000000000000000000000000000000..3e3f45a974a0700d209f7d30af89eb2050a392d6 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-select.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-select.png new file mode 100644 index 0000000000000000000000000000000000000000..13959203c77eaa9f41051897cf9e847ff3642a8a Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-select.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-metrics-collect.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-metrics-collect.png new file mode 100644 index 0000000000000000000000000000000000000000..4d5678b7f77b05d48552fcb9656f4a4372dbbe61 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-metrics-collect.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-report.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-report.png new file mode 100644 index 0000000000000000000000000000000000000000..01daaa9a84c13158a95afddffeb8a7e3303f1e76 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-report.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-script-exec.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-script-exec.png new file mode 100644 index 0000000000000000000000000000000000000000..0b694c3fba6918ef39cca977b2072b2913d12b95 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-script-exec.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-script-gen.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-script-gen.png new file mode 100644 index 0000000000000000000000000000000000000000..6e95551767e213f59669d03fd4cceba05801a983 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-script-gen.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-script-view.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-script-view.png new file mode 100644 index 0000000000000000000000000000000000000000..c82c77bf6f4e4e19f400395aaadc9f99dc8d373c Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-script-view.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-switch-mode.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-switch-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..0f06c803ea3621a0f4fb83bbbe731e2bb4bba788 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin-tuning-switch-mode.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..4c1afd306a6aee029f5bda38aa7b1fce57227e31 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/pictures/shell-plugin.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/shell.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/shell.md new file mode 100644 index 0000000000000000000000000000000000000000..bb88c52f55a984d8115227454e67ed1970423583 --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_shell/user_guide/shell.md @@ -0,0 +1,157 @@ +# 命令行助手使用指南 + +## 简介 + +EulerCopilot 命令行助手是一个命令行(Shell)AI 助手,您可以通过它来快速生成 Shell 命令并执行,从而提高您的工作效率。除此之外,基于 Gitee AI 在线服务的标准版本还内置了 openEuler 的相关知识,可以助力您学习与使用 openEuler 操作系统。 + +## 环境要求 + +- 操作系统:openEuler 22.03 LTS SP3,或者 openEuler 24.03 LTS 及以上版本 +- 命令行软件: + - Linux 桌面环境:支持 GNOME、KDE、DDE 等桌面环境的内置终端 + - 远程 SSH 链接:支持兼容 xterm-256 与 UTF-8 字符集的终端 + +## 安装 + +EulerCopilot 命令行助手支持通过 OEPKGS 仓库进行安装。 + +### 配置 OEPKGS 仓库 + +```bash +sudo dnf config-manager --add-repo https://repo.oepkgs.net/openeuler/rpm/`sed 's/release //;s/[()]//g;s/ /-/g' /etc/openEuler-release`/extras/`uname -m` +``` + +```bash +sudo dnf clean all +``` + +```bash +sudo dnf makecache +``` + +### 安装命令行助手 + +```bash +sudo dnf install eulercopilot-cli +``` + +若遇到 `Error: GPG check FAILED` 错误,使用 `--nogpgcheck` 跳过检查。 + +```bash +sudo dnf install --nogpgcheck eulercopilot-cli +``` + +## 初始化 + +```bash +copilot --init +``` + +然后根据提示输入 API Key 完成配置。 + +![shell-init](./pictures/shell-init.png) + +初次使用前请先退出终端或重新连接 SSH 会话使配置生效。 + +- **查看助手帮助页面** + + ```bash + copilot --help + ``` + + ![shell-help](./pictures/shell-help.png) + +## 使用 + +在终端中输入问题,按下 `Ctrl + O` 提问。 + +### 快捷键 + +- 输入自然语言问题后,按下 `Ctrl + O` 可以直接向 AI 提问。 +- 直接按下 `Ctrl + O` 可以自动填充命令前缀 `copilot`,输入参数后按下 `Enter` 即可执行。 + +### 智能问答 + +命令行助手初始化完成后,默认处于智能问答模式。 +命令提示符**左上角**会显示当前模式。 +若当前模式不是“智能问答”,执行 `copilot -c` (`copilot --chat`) 切换到智能问答模式。 + +![chat-ask](./pictures/shell-chat-ask.png) + +AI 回答完毕后,会根据历史问答生成推荐问题,您可以复制、粘贴到命令行中进行追问。输入追问的问题后,按下 `Enter` 提问。 + +![chat-next](./pictures/shell-chat-continue.png) + +![chat-next-result](./pictures/shell-chat-continue-result.png) + +智能问答模式下支持连续追问,每次追问最多可以关联3条历史问答的上下文。 + +输入 `exit` 可以退出智能问答模式,回到 Linux 命令行。 + +![chat-exit](./pictures/shell-chat-exit.png) + +- 若问答过程中遇到程序错误,可以按下 `Ctrl + C` 立即退出当前问答,再尝试重新提问。 + +### Shell 命令 + +AI 会根据您的问题返回 Shell 命令,EulerCopilot 命令行助手可以解释、编辑或执行这些命令,并显示命令执行结果。 + +![shell-cmd](./pictures/shell-cmd.png) + +命令行助手会自动提取 AI 回答中的命令,并显示相关操作。您可以通过键盘上下键选择操作,按下 `Enter` 确认。 + +![shell-cmd-interact](./pictures/shell-cmd-interact.png) + +#### 解释 + +如果 AI 仅返回了一条命令,选择解释后会直接请求 AI 解释命令,并显示回答。 +若 AI 回答了多条命令,选择后会显示命令列表,您每次可以选择**一条**请求 AI 解释。 + +![shell-cmd-explain-select](./pictures/shell-cmd-explain-select.png) + +完成解释后,您可以继续选择其他操作。 + +![shell-cmd-explain-result](./pictures/shell-cmd-explain-result.png) + +#### 编辑 + +![shell-cmd-edit](./pictures/shell-cmd-edit.png) + +选择一条命令进行编辑,编辑完成后按下 `Enter` 确认。 + +![shell-cmd-edit-result](./pictures/shell-cmd-edit-result.png) + +完成编辑后,您可以继续编辑其他命令或选择其他操作。 + +#### 执行 + +如果 AI 仅返回了一条命令,选择执行后会直接执行命令,并显示执行结果。 +若 AI 回答了多条命令,选择后会显示命令列表,您每次可以选择**多条**命令来执行。 + +您可以通过键盘上下键移动光标,按下 `空格键` 选择命令,按下 `Enter` 执行所选命令。 +被选中的命令会显示**蓝色高亮**,如图所示。 + +![shell-cmd-exec-multi-select](./pictures/shell-cmd-exec-multi-select.png) + +若不选择任何命令,直接按下 `Enter`,则会跳过执行命令,直接进入下一轮问答。 + +按下 `Enter` 后,被选中的命令会从上到下依次执行。 + +![shell-cmd-exec-result](./pictures/shell-cmd-exec-result.png) + +若执行过程中遇到错误,命令行助手会显示错误信息,并**终止执行命令**,进入下一轮问答。 +您可以在下一轮问答中提示 AI 更正命令,或要求 AI 重新生成命令。 + +## 卸载 + +```bash +sudo dnf remove eulercopilot-cli +``` + +然后使用以下命令删除配置文件。 + +```bash +rm ~/.config/eulercopilot/config.json +``` + +卸载完成后请重启终端或重新连接 SSH 会话使配置还原。 diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/offline.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/offline.md new file mode 100644 index 0000000000000000000000000000000000000000..8167907479569db279a838dc9dcf3e17597abbfd --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/offline.md @@ -0,0 +1,620 @@ +# **EulerCopilot 智能助手部署指南** + +版本信息 +当前版本:v0.9.5 +更新日期:2025年4月1日 + +## 产品概述 + +EulerCopilot 是一款智能问答工具,使用 EulerCopilot 可以解决操作系统知识获取的便捷性,并且为OS领域模型赋能开发者及运维人员。作为获取操作系统知识,使能操作系统生产力工具 (如 A-Ops / A-Tune / x2openEuler / EulerMaker / EulerDevOps / StratoVirt / iSulad 等),颠覆传统命令交付方式,由传统命令交付方式向自然语义进化,并结合智能体任务规划能力,降低开发、使用操作系统特性的门槛。 + +本指南提供基于自动化脚本的EulerCopilot智能助手系统部署说明,支持一键自动部署和手动分步部署两种方式。 + +### 组件介绍 + +| 组件 | 端口 | 说明 | +| ----------------------------- | --------------- | -------------------- | +| euler-copilot-framework | 8002 (内部端口) | 智能体框架服务 | +| euler-copilot-web | 8080 | 智能体前端界面 | +| euler-copilot-rag | 9988 (内部端口) | 检索增强服务 | +| authhub-backend-service | 11120 (内部端口) | 鉴权服务后端 | +| authhub-web-service | 8000 | 鉴权服务前端 | +| mysql | 3306 (内部端口) | MySQL数据库 | +| redis | 6379 (内部端口) | Redis数据库 | +| minio | 9000 (内部端口) 9001(外部部端口) | minio数据库 | +| mongo | 27017 (内部端口) | mongo数据库 | +| postgres | 5432 (内部端口) | 向量数据库 | +| secret_inject | 无 | 配置文件安全复制工具 | + +### 软件要求 + +| 类型 | 版本要求 | 说明 | +|----------------| -------------------------------------|--------------------------------------| +| 操作系统 | openEuler 22.03 LTS 及以上版本 | 无 | +| K3s | >= v1.30.2,带有 Traefik Ingress 工具 | K3s 提供轻量级的 Kubernetes 集群,易于部署和管理 | +| Helm | >= v3.15.3 | Helm 是一个 Kubernetes 的包管理工具,其目的是快速安装、升级、卸载 EulerCopilot 服务 | +| python | >=3.9.9 | python3.9.9 以上版本为模型的下载和安装提供运行环境 | + +--- + +### 硬件规格 + +| 硬件资源 | 最小配置 | 推荐配置 | +|--------------|----------------------------|------------------------------| +| CPU | 4 核心 | 16 核心及以上 | +| RAM | 4 GB | 64 GB | +| 存储 | 32 GB | 64G | +| 大模型名称 | deepseek-llm-7b-chat | DeepSeek-R1-Llama-8B | +| 显存 (GPU) | NVIDIA RTX A4000 8GB | NVIDIA A100 80GB * 2 | + +**关键说明**: + +- 纯CPU环境,建议通过调用 OpenAI 接口或使用自带的模型部署方式来实现功能。 +- 如果k8s集群环境,则不需要单独安装k3s,要求version >= 1.28 + +--- + +### 部署视图 + +![部署图](./pictures/部署视图.png) + +--- + +### 域名配置 + +需准备以下两个服务域名: + +- authhub认证服务:authhub.eulercopilot.local +- EulerCopilot web服务:www.eulercopilot.local + +```bash +# 本地Windows主机中进行配置 +# 打开 C:\Windows\System32\drivers\etc\hosts 添加记录 +# 替换127.0.0.1为目标服务器的IP +127.0.0.1 authhub.eulercopilot.local +127.0.0.1 www.eulercopilot.local +``` + +## 快速开始 + +### 1. 资源获取 + +- **获取部署脚本** + [EulerCopilot 的官方Git仓库](https://gitee.com/openeuler/euler-copilot-framework) + 切换至dev分支点击下载ZIP, 并上传至目标服务器 + + ```bash + unzip euler-copilot-framework.tar -d /home + ``` + +- **资源清单** + + - **下载地址** + [EulerCopilot 资源仓库](https://repo.oepkgs.net/openEuler/rpm/openEuler-22.03-LTS/contrib/eulercopilot) + - **镜像文件** + + ```bash + # 使用脚本自动下载所有镜像(需在联网环境执行) + cd /home/euler-copilot-framework/deploy/scripts/9-other-script/ + # 执行镜像下载(版本号可替换) + ./save_images.sh 0.9.5 # 自动保存到目录/home/eulercopilot/images + # 上传至目标服务器 + scp /home/eulercopilot/images/* root@target-server:/home/eulercopilot/images/ + ``` + + - **模型部署文件**:`bge-m3-Q4_K_M.gguf`, `deepseek-llm-7b-chat-Q4_K_M.gguf`, `ollama-linux-arm64.tgz/ollama-linux-amd64.tgz` + - **工具包**:`helm-v3.15.0-linux-arm64.tar.gz/helm-v3.15.0-linux-amd64.tar.gz`, `k3s-airgap-images-arm64.tar.zst/k3s-airgap-images-amd64.tar.zst`,`k3s-arm64/k3s-amd64`, `k3s-install.sh` + +- **关键说明** + + - **网络要求** + - 手动下载需确保存在可访问外网文件的Windows环境,全部下载完成后传输至离线环境 + - 脚本下载需在联网服务器执行,仅完成镜像下载,完成传输至离线环境 + - **确保目标服务器存在以下目录** + + ```bash + /home/eulercopilot/ + ├── images/ # 存放镜像文件 + ├── models/ # 存放模型文件 + └── tools/ # 存放工具包 + ``` + +### 2. 部署EulerCopilot + +#### 一键部署 + +```bash +cd /home/euler-copilot-framework/deploy/scripts +``` + +```bash +bash deploy.sh +``` + +```bash +# 输入0进行一键自动部署 +============================== + 主部署菜单 +============================== +0) 一键自动部署 +1) 手动分步部署 +2) 重启服务 +3) 卸载所有组件并清除数据 +4) 退出程序 +============================== +请输入选项编号(0-3): 0 +``` + +--- + +#### 手动分步部署 + +```bash +# 选择1 -> 1 进入手动分步部署 +============================== + 主部署菜单 +============================== +0) 一键自动部署 +1) 手动分步部署 +2) 重启服务 +3) 卸载所有组件并清除数据 +4) 退出程序 +============================== +请输入选项编号(0-3): 1 +``` + +```bash +# 输入选项编号(0-9),逐步部署 +============================== + 手动分步部署菜单 +============================== +1) 执行环境检查脚本 +2) 安装k3s和helm +3) 安装Ollama +4) 部署Deepseek模型 +5) 部署Embedding模型 +6) 安装数据库 +7) 安装AuthHub +8) 安装EulerCopilot +9) 返回主菜单 +============================== +请输入选项编号(0-9): +``` + +--- + +**关键说明**: + +1. 安装部署前需要预先准备好部署所需的资源 +2. 在部署过程中,您需要输入 Authhub 域名和 EulerCopilot 域名, 不输入则使用默认域名`authhub.eulercopilot.local`, `www.eulercopilot.local`。 + +#### 重启服务 + +```bash +# 输入选项重启服务 +============================== + 服务重启菜单 +============================== +可重启的服务列表: +1) authhub-backend +2) authhub +3) framework +4) minio +5) mongo +6) mysql +7) pgsql +8) rag +9) rag-web +10) redis +11) web +12) 返回主菜单 +============================== +请输入要重启的服务编号(1-12): +``` + +#### 卸载所有组件 + +```bash +sudo ./deploy.sh +# 选择2进行完全卸载 +============================== + 主部署菜单 +============================== +0) 一键自动部署 +1) 手动分步部署 +2) 卸载所有组件并清除数据 +3) 退出程序 +============================== +请输入选项编号(0-3): 2 +``` + +--- + +**关键说明**: + +- 在部署过程中,您需要输入 Authhub 域名和 EulerCopilot 域名, 不输入则使用默认域名`authhub.eulercopilot.local`, `www.eulercopilot.local`。 +- 资源不足时可参考 FAQ 中的评估资源可用性解决 +- 查看组件日志 + +```bash +kubectl logs -n euler-copilot +``` + +- 查看服务状态 + +```bash +kubectl get pod -n euler-copilot +``` + +- 大模型配置修改并更新EulerCopilot + +```bash +cd /home/euler-copilot-framework/deploy/chart/euler-copilot +``` + +```bash +vim values.yaml +``` + +```bash +helm upgrade euler-copilot -n euler-copilot . +``` + +## 验证安装 + +恭喜您,**EulerCopilot** 已成功部署!为了开始您的体验,请在浏览器中输入 `https://您的EulerCopilot域名` 链接访问 EulerCopilot 的网页界面: + +首次访问时,您需要点击页面上的 **立即注册** 按钮来创建一个新的账号,并完成登录过程。 + +![Web登录界面](./pictures/WEB登录界面.png) +![Web 界面](./pictures/WEB界面.png) + +## 构建专有领域智能问答 + +点击知识库,可登录本地知识库管理页面,详细信息请参考文档 [本地资产库构建指南](../../../advance/knowledge_base/deploy_guide/witChainD_deployment.md) +**知识库登录默认账号 `admin`, 密码 `123456`** + +--- + +## 附录 + +### 大模型准备 + +#### GPU 环境 + +可直接使用部署的deepseek大模型参考以下方式进行部署 + +1. 下载模型文件 + + ```bash + huggingface-cli download --resume-download Qwen/Qwen1.5-14B-Chat --local-dir Qwen1.5-14B-Chat + ``` + +2. 创建终端 control + + ```bash + screen -S control + ``` + + ```bash + python3 -m fastchat.serve.controller + ``` + + 按 Ctrl A+D 置于后台 + +3. 创建新终端 api + + ```bash + screen -S api + ``` + + ```bash + python3 -m fastchat.serve.openai_api_server --host 0.0.0.0 --port 30000 --api-keys sk-123456 + ``` + + 按 Ctrl A+D 置于后台 + 如果当前环境的 Python 版本是 3.12 或者 3.9 可以创建 python3.10 的 conda 虚拟环境 + + ```bash + mkdir -p /root/py310 + ``` + + ```bash + conda create --prefix=/root/py310 python==3.10.14 + ``` + + ```bash + conda activate /root/py310 + ``` + +4. 创建新终端 worker + + ```bash + screen -S worker + ``` + + ```bash + screen -r worker + ``` + + 安装 fastchat 和 vllm + + ```bash + pip install fschat vllm + ``` + + 安装依赖: + + ```bash + pip install fschat[model_worker] + ``` + + ```bash + python3 -m fastchat.serve.vllm_worker --model-path /root/models/Qwen1.5-14B-Chat/ --model-name qwen1.5 --num-gpus 8 --gpu-memory-utilization=0.7 --dtype=half + ``` + + 按 Ctrl A+D 置于后台 + +5. 按照如下方式修改配置的大模型参数,并更新服务。 + + ```bash + vim /home/euler-copilot-framework/deploy/chart/euler_copilot/values.yaml + ``` + + 修改如下部分 + + ```yaml + # 模型设置 + models: + # 用于问答的大模型;需要为OpenAI兼容接口 + answer: + # [必填] 接口URL(无需带上“v1”后缀) + url: http://172.168.178.107:11434 + # [必填] 接口API Key;默认置空 + key: sk-123456 + # [必填] 模型名称 + name: deepseek-llm-7b-chat:latest + # [必填] 模型最大上下文数;建议>=8192 + ctx_length: 8192 + # 模型最大输出长度,建议>=2048 + max_tokens: 2048 + # 用于Function Call的模型;建议使用特定推理框架 + functioncall: + # 推理框架类型,默认为ollama + # 可用的框架类型:["vllm", "sglang", "ollama", "openai"] + backend: + # 模型地址;不填则与问答模型一致 + url: ollama + # API Key;不填则与问答模型一致 + key: + # 模型名称;不填则与问答模型一致 + name: + # 模型最大上下文数;不填则与问答模型一致 + ctx_length: + # 模型最大输出长度;不填则与问答模型一致 + max_tokens: + # 用于数据向量化(Embedding)的模型 + embedding: + # 推理框架类型,默认为openai + # [必填] Embedding接口类型:["openai", "mindie"] + type: openai + # [必填] Embedding URL(需要带上“v1”后缀) + url: http://172.168.178.107:11434 + # [必填] Embedding 模型API Key + key: sk-123456 + # [必填] Embedding 模型名称 + name: bge-m3:latest + ``` + + ```bash + # 更新服务 + helm upgrade -n euler-copilot euler-copilot . + # 重启framework服务 + kubectl get pod -n euler-copilot + kubectl delete pod framework-deploy-65b669fc58-q9bw7 -n euler-copilot + ``` + +#### NPU 环境 + +NPU 环境部署可参考链接 [MindIE安装指南](https://www.hiascend.com/document/detail/zh/mindie/10RC2/whatismindie/mindie_what_0001.html) + +### FAQ + +#### 1. 解决 Hugging Face 连接错误 + +如果遇到如下连接错误: + +```text +urllib3.exceptions.NewConnectionError: : Failed to establish a new connection: [Errno 101] Network is unreachable +``` + +尝试以下解决方案: + +- 更新 `huggingface_hub` 包到最新版本。 + + ```bash + pip3 install -U huggingface_hub + ``` + +- 如果网络问题依旧存在,可以尝试使用镜像站点作为端点。 + + ```bash + export HF_ENDPOINT=https://hf-mirror.com + ``` + +#### 2. 在 RAG 容器中调用问答接口 + +进入对应的 RAG Pod 后,可以通过 `curl` 命令发送 POST 请求来获取问答结果。请确保在请求体中提供具体的问题文本。 + +```bash +curl -k -X POST "http://localhost:9988/kb/get_answer" \ + -H "Content-Type: application/json" \ + -d '{ + "question": "您的问题", + "kb_sn": "default_test", + "fetch_source": true + }' +``` + +#### 3. 解决 `helm upgrade` 错误 + +当 Kubernetes 集群不可达时,您可能会遇到类似下面的错误信息: + +```text +Error: UPGRADE FAILED: Kubernetes cluster unreachable +``` + +确保设置了正确的 KUBECONFIG 环境变量指向有效的配置文件。 + +```bash +echo "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> /root/.bashrc +source /root/.bashrc +``` + +#### 4. 查看 Pod 日志失败 + +如果您遇到查看 Pod 日志时权限被拒绝的问题,检查是否正确配置了代理设置,并将本机 IP 地址添加到 `no_proxy` 环境变量中。 + +```bash +cat /etc/systemd/system/k3s.service.env +``` + +编辑文件并确保包含: + +```bash +no_proxy=XXX.XXX.XXX.XXX +``` + +#### 5. GPU环境中大模型流式回复问题 + +对于某些服务执行 curl 大模型时无法进行流式回复的情况,尝试修改请求中的 `"stream"` 参数为 `false`。此外,确认已安装兼容版本的 Pydantic 库。 + +```bash +pip install pydantic==1.10.13 +``` + +#### 6. sglang 模型部署指南 + +按照以下步骤部署基于 sglang 的模型: + +```bash +# 1. 激活名为 `myenv` 的 Conda 环境,该环境基于 Python 3.10 创建: +conda activate myenv + +# 2. 安装 sglang 及其所有依赖项,指定版本为 0.3.0 +pip install "sglang[all]==0.3.0" + +# 3. 从特定索引安装 flashinfer,确保与您的 CUDA 和 PyTorch 版本兼容 +pip install flashinfer -i https://flashinfer.ai/whl/cu121/torch2.4/ + +# 4. 使用 sglang 启动服务器,配置如下: +python -m sglang.launch_server \ + --served-model-name Qwen2.5-32B \ + --model-path Qwen2.5-32B-Instruct-AWQ \ + --host 0.0.0.0 \ + --port 8001 \ + --api-key "sk-12345" \ + --mem-fraction-static 0.5 \ + --tp 8 +``` + +- 验证安装 + + ```bash + pip show sglang + pip show flashinfer + ``` + +**注意事项:** + +- API Key:请确保 `--api-key` 参数中的 API 密钥是正确的 +- 模型路径: 确保 `--model-path` 参数中的路径是正确的,并且模型文件存在于该路径下。 +- CUDA 版本:确保你的系统上安装了 CUDA 12.1 和 PyTorch 2.4,因为 `flashinfer` 包依赖于这些特定版本。 +- 线程池大小:根据你的GPU资源和预期负载调整线程池大小。如果你有 8 个 GPU,那么可以选择 --tp 8 来充分利用这些资源。 + +#### 7. 获取 Embedding + +使用 curl 发送 POST 请求以获取 embedding 结果: + +```bash +curl -k -X POST http://localhost:11434/v1/embeddings \ + -H "Content-Type: application/json" \ + -d {"input": "The food was delicious and the waiter...", "model": "bge-m3", "encoding_format": "float"} +``` + +#### 8. 生成证书 + +为了生成自签名证书,首先下载 [mkcert](https://github.com/FiloSottile/mkcert/releases)工具,然后运行以下命令: + +```bash +mkcert -install +mkcert example.com +``` + +最后,将生成的证书和私钥拷贝到 values.yaml 中, 并应用至 Kubernetes Secret。 + +```bash +vim /home/euler-copilot-framework_openeuler/deploy/chart_ssl/traefik-secret.yaml +``` + +```bash +kubectl apply -f traefik-secret.yaml +``` + +#### 9. 问题排查方法 + +1. **获取集群事件信息** + + 为了更好地定位 Pod 失败的原因,请首先检查 Kubernetes 集群中的事件 (Events)。这可以提供有关 Pod 状态变化的上下文信息。 + + ```bash + kubectl get events -n euler-copilot + ``` + +2. **验证镜像拉取状态** + + 确认容器镜像是否成功拉取。如果镜像未能正确加载,可能是由于网络问题或镜像仓库配置错误。 + + ```bash + k3s crictl images + ``` + +3. **审查 Pod 日志** + + 检查相关 Pod 的日志,以寻找可能的错误信息或异常行为。这对于诊断应用程序级别的问题特别有用。 + + ```bash + kubectl logs rag-deploy-service-5b7887644c-sm58z -n euler-copilot + ``` + +4. **评估资源可用性** + + 确保 Kubernetes 集群有足够的资源(如 CPU、内存和存储)来支持 Pod 的运行。资源不足可能导致镜像拉取失败或其他性能问题,或使得 Pod 状态从 Running 变为 Pending 或 Completed。可查看磁盘空间并保证至少有 30% 的可用空间。这有助于维持 Pod 的稳定运行状态。参考该链接挂载空间较大的磁盘[How to move k3s data to another location](https://mrkandreev.name/snippets/how_to_move_k3s_data_to_another_location/) + + ```bash + kubectl top nodes + ``` + +5. **确认 k3s 版本兼容性** + + 如果遇到镜像拉取失败且镜像大小为 0 的问题,请检查您的 k3s 版本是否符合最低要求(v1.30.2 或更高)。较低版本可能存在不兼容的问题。 + + ```bash + k3s -v + ``` + +6. **检查配置** + + 检查 `values.yaml` 文件中关于 OIDC 配置和域名配置是否填写正确,确保配置无误后更新服务。 + + ```bash + cat /home/euler-copilot-framework/deploy/chart/euler_copilot + ``` + + ```bash + vim values.yaml | grep oidc + ``` + + ```bash + helm upgrade euler-copilot -n euler-copilot . + ``` diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/online.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/online.md new file mode 100644 index 0000000000000000000000000000000000000000..3e946173658dffbc296f616d94bfbd92a3d70890 --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/online.md @@ -0,0 +1,596 @@ +# **EulerCopilot 智能助手部署指南** + +版本信息 +当前版本:v0.9.5 +更新日期:2025年4月1日 + +## 产品概述 + +EulerCopilot 是一款智能问答工具,使用 EulerCopilot 可以解决操作系统知识获取的便捷性,并且为OS领域模型赋能开发者及运维人员。作为获取操作系统知识,使能操作系统生产力工具 (如 A-Ops / A-Tune / x2openEuler / EulerMaker / EulerDevOps / StratoVirt / iSulad 等),颠覆传统命令交付方式,由传统命令交付方式向自然语义进化,并结合智能体任务规划能力,降低开发、使用操作系统特性的门槛。 + +本指南提供基于自动化脚本的EulerCopilot智能助手系统部署说明,支持一键自动部署和手动分步部署两种方式。 + +### 组件介绍 + +| 组件 | 端口 | 说明 | +| ----------------------------- | --------------- | -------------------- | +| euler-copilot-framework | 8002 (内部端口) | 智能体框架服务 | +| euler-copilot-web | 8080 | 智能体前端界面 | +| euler-copilot-rag | 9988 (内部端口) | 检索增强服务 | +| authhub-backend-service | 11120 (内部端口) | 鉴权服务后端 | +| authhub-web-service | 8000 | 鉴权服务前端 | +| mysql | 3306 (内部端口) | MySQL数据库 | +| redis | 6379 (内部端口) | Redis数据库 | +| minio | 9000 (内部端口) 9001(外部部端口) | minio数据库 | +| mongo | 27017 (内部端口) | mongo数据库 | +| postgres | 5432 (内部端口) | 向量数据库 | +| secret_inject | 无 | 配置文件安全复制工具 | + +### 软件要求 + +| 类型 | 版本要求 | 说明 | +|----------------| -------------------------------------|--------------------------------------| +| 操作系统 | openEuler 22.03 LTS 及以上版本 | 无 | +| K3s | >= v1.30.2,带有 Traefik Ingress 工具 | K3s 提供轻量级的 Kubernetes 集群,易于部署和管理 | +| Helm | >= v3.15.3 | Helm 是一个 Kubernetes 的包管理工具,其目的是快速安装、升级、卸载 EulerCopilot 服务 | +| python | >=3.9.9 | python3.9.9 以上版本为模型的下载和安装提供运行环境 | + +--- + +### 硬件规格 + +| 硬件资源 | 最小配置 | 推荐配置 | +|--------------|----------------------------|------------------------------| +| CPU | 4 核心 | 16 核心及以上 | +| RAM | 4 GB | 64 GB | +| 存储 | 32 GB | 64G | +| 大模型名称 | deepseek-llm-7b-chat | DeepSeek-R1-Llama-8B | +| 显存 (GPU) | NVIDIA RTX A4000 8GB | NVIDIA A100 80GB * 2 | + +**关键说明**: + +- 纯CPU环境,建议通过调用 OpenAI 接口或使用自带的模型部署方式来实现功能。 +- 如果k8s集群环境,则不需要单独安装k3s,要求version >= 1.28 + +--- + +### 部署视图 + +![部署图](./pictures/部署视图.png) + +--- + +### 域名配置 + +需准备以下两个服务域名: + +- authhub认证服务:authhub.eulercopilot.local +- EulerCopilot web服务:www.eulercopilot.local + +```bash +# 本地Windows主机中进行配置 +# 打开 C:\Windows\System32\drivers\etc\hosts 添加记录 +# 替换127.0.0.1为目标服务器的IP +127.0.0.1 authhub.eulercopilot.local +127.0.0.1 www.eulercopilot.local +``` + +## 快速开始 + +### 1. 获取部署脚本 + +- 从 EulerCopilot 的官方Git仓库 [euler-copilot-framework](https://gitee.com/openeuler/euler-copilot-framework) 下载最新的部署仓库 +- 如果您正在使用 Kubernetes,则不需要安装 k3s 工具。 + +```bash +# 下载目录以 home 为例 +cd /home +``` + +```bash +git clone https://gitee.com/openeuler/euler-copilot-framework.git -b dev +``` + +```bash +cd euler-copilot-framework/deploy/scripts +``` + +```bash +# 为脚本文件添加可执行权限 +chmod -R +x ./* +``` + +### 2. 部署EulerCopilot + +#### **一键部署** + +```bash +cd /home/euler-copilot-framework/deploy/scripts +``` + +```bash +bash deploy.sh +``` + +```bash +# 输入0进行一键自动部署 +============================== + 主部署菜单 +============================== +0) 一键自动部署 +1) 手动分步部署 +2) 重启服务 +3) 卸载所有组件并清除数据 +4) 退出程序 +============================== +请输入选项编号(0-3): 0 +``` + +--- + +#### **分步部署** + +```bash +# 选择1 -> 1 进入手动分步部署 +============================== + 主部署菜单 +============================== +0) 一键自动部署 +1) 手动分步部署 +2) 重启服务 +3) 卸载所有组件并清除数据 +4) 退出程序 +============================== +请输入选项编号(0-3): 1 +``` + +```bash +# 输入选项编号(0-9),逐步部署 +============================== + 手动分步部署菜单 +============================== +1) 执行环境检查脚本 +2) 安装k3s和helm +3) 安装Ollama +4) 部署Deepseek模型 +5) 部署Embedding模型 +6) 安装数据库 +7) 安装AuthHub +8) 安装EulerCopilot +9) 返回主菜单 +============================== +请输入选项编号(0-9): +``` + +--- + +#### **重启服务** + +```bash +# 输入选项重启服务 +============================== + 服务重启菜单 +============================== +可重启的服务列表: +1) authhub-backend +2) authhub +3) framework +4) minio +5) mongo +6) mysql +7) pgsql +8) rag +9) rag-web +10) redis +11) web +12) 返回主菜单 +============================== +请输入要重启的服务编号(1-12): +``` + +#### **卸载所有组件** + +```bash +sudo ./deploy.sh +# 选择2进行完全卸载 +============================== + 主部署菜单 +============================== +0) 一键自动部署 +1) 手动分步部署 +2) 卸载所有组件并清除数据 +3) 退出程序 +============================== +请输入选项编号(0-3): 2 +``` + +--- + +**关键说明**: + +- 在部署过程中,您需要输入 Authhub 域名和 EulerCopilot 域名, 不输入则使用默认域名`authhub.eulercopilot.local`, `www.eulercopilot.local`。 +- 资源不足时可参考 FAQ 中的评估资源可用性解决 +- 查看组件日志 + +```bash +kubectl logs -n euler-copilot +``` + +- 查看服务状态 + +```bash +kubectl get pod -n euler-copilot +``` + +- 大模型配置修改并更新EulerCopilot + +```bash +cd /home/euler-copilot-framework/deploy/chart/euler-copilot +``` + +```bash +vim values.yaml +``` + +```bash +helm upgrade euler-copilot -n euler-copilot . +``` + +## 验证安装 + +恭喜您,**EulerCopilot** 已成功部署!为了开始您的体验,请在浏览器中输入 `https://您的EulerCopilot域名` 链接访问 EulerCopilot 的网页界面: + +首次访问时,您需要点击页面上的 **立即注册** 按钮来创建一个新的账号,并完成登录过程。 + +![Web登录界面](./pictures/WEB登录界面.png) +![Web 界面](./pictures/WEB界面.png) + +## 构建专有领域智能问答 + +点击知识库,可登录本地知识库管理页面,详细信息请参考文档 [本地资产库构建指南](../../../advance/knowledge_base/deploy_guide/witChainD_deployment.md) +**知识库登录默认账号 `admin`, 密码 `123456`** + +--- + +## 附录 + +### 大模型准备 + +#### GPU 环境 + +可直接使用部署的deepseek大模型参考以下方式进行部署 + +1. 下载模型文件: + + ```bash + huggingface-cli download --resume-download Qwen/Qwen1.5-14B-Chat --local-dir Qwen1.5-14B-Chat + ``` + +2. 创建终端 control + + ```bash + screen -S control + ``` + + ```bash + python3 -m fastchat.serve.controller + ``` + + 按 Ctrl A+D 置于后台 + +3. 创建新终端 api + + ```bash + screen -S api + ``` + + ```bash + python3 -m fastchat.serve.openai_api_server --host 0.0.0.0 --port 30000 --api-keys sk-123456 + ``` + + 按 Ctrl A+D 置于后台 + 如果当前环境的 Python 版本是 3.12 或者 3.9 可以创建 python3.10 的 conda 虚拟环境 + + ```bash + mkdir -p /root/py310 + ``` + + ```bash + conda create --prefix=/root/py310 python==3.10.14 + ``` + + ```bash + conda activate /root/py310 + ``` + +4. 创建新终端 worker + + ```bash + screen -S worker + ``` + + ```bash + screen -r worker + ``` + + 安装 fastchat 和 vllm + + ```bash + pip install fschat vllm + ``` + + 安装依赖: + + ```bash + pip install fschat[model_worker] + ``` + + ```bash + python3 -m fastchat.serve.vllm_worker --model-path /root/models/Qwen1.5-14B-Chat/ --model-name qwen1.5 --num-gpus 8 --gpu-memory-utilization=0.7 --dtype=half + ``` + + 按 Ctrl A+D 置于后台 + +5. 按照如下方式修改配置的大模型参数,并更新服务。 + + ```bash + vim /home/euler-copilot-framework/deploy/chart/euler_copilot/values.yaml + ``` + + 修改如下部分 + + ```yaml + # 模型设置 + models: + # 用于问答的大模型;需要为OpenAI兼容接口 + answer: + # [必填] 接口URL(无需带上“v1”后缀) + url: http://172.168.178.107:11434 + # [必填] 接口API Key;默认置空 + key: sk-123456 + # [必填] 模型名称 + name: deepseek-llm-7b-chat:latest + # [必填] 模型最大上下文数;建议>=8192 + ctx_length: 8192 + # 模型最大输出长度,建议>=2048 + max_tokens: 2048 + # 用于Function Call的模型;建议使用特定推理框架 + functioncall: + # 推理框架类型,默认为ollama + # 可用的框架类型:["vllm", "sglang", "ollama", "openai"] + backend: + # 模型地址;不填则与问答模型一致 + url: ollama + # API Key;不填则与问答模型一致 + key: + # 模型名称;不填则与问答模型一致 + name: + # 模型最大上下文数;不填则与问答模型一致 + ctx_length: + # 模型最大输出长度;不填则与问答模型一致 + max_tokens: + # 用于数据向量化(Embedding)的模型 + embedding: + # 推理框架类型,默认为openai + # [必填] Embedding接口类型:["openai", "mindie"] + type: openai + # [必填] Embedding URL(需要带上“v1”后缀) + url: http://172.168.178.107:11434 + # [必填] Embedding 模型API Key + key: sk-123456 + # [必填] Embedding 模型名称 + name: bge-m3:latest + ``` + + ```bash + # 更新服务 + helm upgrade -n euler-copilot euler-copilot . + # 重启framework服务 + kubectl get pod -n euler-copilot + kubectl delete pod framework-deploy-65b669fc58-q9bw7 -n euler-copilot + ``` + +#### NPU 环境 + +NPU 环境部署可参考链接 [MindIE安装指南](https://www.hiascend.com/document/detail/zh/mindie/10RC2/whatismindie/mindie_what_0001.html) + +### FAQ + +#### 1. 解决 Hugging Face 连接错误 + +如果遇到如下连接错误: + +```text +urllib3.exceptions.NewConnectionError: : Failed to establish a new connection: [Errno 101] Network is unreachable +``` + +尝试以下解决方案: + +- 更新 `huggingface_hub` 包到最新版本。 + + ```bash + pip3 install -U huggingface_hub + ``` + +- 如果网络问题依旧存在,可以尝试使用镜像站点作为端点。 + + ```bash + export HF_ENDPOINT=https://hf-mirror.com + ``` + +#### 2. 在 RAG 容器中调用问答接口 + +进入对应的 RAG Pod 后,可以通过 `curl` 命令发送 POST 请求来获取问答结果。请确保在请求体中提供具体的问题文本。 + +```bash +curl -k -X POST "http://localhost:9988/kb/get_answer" \ + -H "Content-Type: application/json" \ + -d '{ + "question": "您的问题", + "kb_sn": "default_test", + "fetch_source": true + }' +``` + +#### 3. 解决 `helm upgrade` 错误 + +当 Kubernetes 集群不可达时,您可能会遇到类似下面的错误信息: + +```text +Error: UPGRADE FAILED: Kubernetes cluster unreachable +``` + +确保设置了正确的 KUBECONFIG 环境变量指向有效的配置文件。 + +```bash +echo "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> /root/.bashrc +source /root/.bashrc +``` + +#### 4. 查看 Pod 日志失败 + +如果您遇到查看 Pod 日志时权限被拒绝的问题,检查是否正确配置了代理设置,并将本机 IP 地址添加到 `no_proxy` 环境变量中。 + +```bash +cat /etc/systemd/system/k3s.service.env +``` + +编辑文件并确保包含: + +```bash +no_proxy=XXX.XXX.XXX.XXX +``` + +#### 5. GPU环境中大模型流式回复问题 + +对于某些服务执行 curl 大模型时无法进行流式回复的情况,尝试修改请求中的 `"stream"` 参数为 `false`。此外,确认已安装兼容版本的 Pydantic 库。 + +```bash +pip install pydantic==1.10.13 +``` + +#### 6. sglang 模型部署指南 + +按照以下步骤部署基于 sglang 的模型: + +```bash +# 1. 激活名为 `myenv` 的 Conda 环境,该环境基于 Python 3.10 创建: +conda activate myenv + +# 2. 安装 sglang 及其所有依赖项,指定版本为 0.3.0 +pip install "sglang[all]==0.3.0" + +# 3. 从特定索引安装 flashinfer,确保与您的 CUDA 和 PyTorch 版本兼容 +pip install flashinfer -i https://flashinfer.ai/whl/cu121/torch2.4/ + +# 4. 使用 sglang 启动服务器,配置如下: +python -m sglang.launch_server \ + --served-model-name Qwen2.5-32B \ + --model-path Qwen2.5-32B-Instruct-AWQ \ + --host 0.0.0.0 \ + --port 8001 \ + --api-key "sk-12345" \ + --mem-fraction-static 0.5 \ + --tp 8 +``` + +- 验证安装 + + ```bash + pip show sglang + pip show flashinfer + ``` + +**注意事项:** + +- API Key:请确保 `--api-key` 参数中的 API 密钥是正确的 +- 模型路径: 确保 `--model-path` 参数中的路径是正确的,并且模型文件存在于该路径下。 +- CUDA 版本:确保你的系统上安装了 CUDA 12.1 和 PyTorch 2.4,因为 `flashinfer` 包依赖于这些特定版本。 +- 线程池大小:根据你的GPU资源和预期负载调整线程池大小。如果你有 8 个 GPU,那么可以选择 --tp 8 来充分利用这些资源。 + +#### 7. 获取 Embedding + +使用 curl 发送 POST 请求以获取 embedding 结果: + +```bash +curl -k -X POST http://localhost:11434/v1/embeddings \ + -H "Content-Type: application/json" \ + -d {"input": "The food was delicious and the waiter...", "model": "bge-m3", "encoding_format": "float"} +``` + +#### 8. 生成证书 + +为了生成自签名证书,首先下载 [mkcert](https://github.com/FiloSottile/mkcert/releases)工具,然后运行以下命令: + +```bash +mkcert -install +mkcert example.com +``` + +最后,将生成的证书和私钥拷贝到 values.yaml 中, 并应用至 Kubernetes Secret。 + +```bash +vim /home/euler-copilot-framework_openeuler/deploy/chart_ssl/traefik-secret.yaml +``` + +```bash +kubectl apply -f traefik-secret.yaml +``` + +#### 9. 问题排查方法 + +1. **获取集群事件信息** + + 为了更好地定位 Pod 失败的原因,请首先检查 Kubernetes 集群中的事件 (Events)。这可以提供有关 Pod 状态变化的上下文信息。 + + ```bash + kubectl get events -n euler-copilot + ``` + +2. **验证镜像拉取状态** + + 确认容器镜像是否成功拉取。如果镜像未能正确加载,可能是由于网络问题或镜像仓库配置错误。 + + ```bash + k3s crictl images + ``` + +3. **审查 Pod 日志** + + 检查相关 Pod 的日志,以寻找可能的错误信息或异常行为。这对于诊断应用程序级别的问题特别有用。 + + ```bash + kubectl logs rag-deploy-service-5b7887644c-sm58z -n euler-copilot + ``` + +4. **评估资源可用性** + + 确保 Kubernetes 集群有足够的资源(如 CPU、内存和存储)来支持 Pod 的运行。资源不足可能导致镜像拉取失败或其他性能问题,或使得 Pod 状态从 Running 变为 Pending 或 Completed。可查看磁盘空间并保证至少有 30% 的可用空间。这有助于维持 Pod 的稳定运行状态。参考该链接挂载空间较大的磁盘[How to move k3s data to another location](https://mrkandreev.name/snippets/how_to_move_k3s_data_to_another_location/) + + ```bash + kubectl top nodes + ``` + +5. **确认 k3s 版本兼容性** + + 如果遇到镜像拉取失败且镜像大小为 0 的问题,请检查您的 k3s 版本是否符合最低要求(v1.30.2 或更高)。较低版本可能存在不兼容的问题。 + + ```bash + k3s -v + ``` + +6. **检查配置** + + 检查 `values.yaml` 文件中关于 OIDC 配置和域名配置是否填写正确,确保配置无误后更新服务。 + + ```bash + cat /home/euler-copilot-framework/deploy/chart/euler_copilot + ``` + + ```bash + vim values.yaml | grep oidc + ``` + + ```bash + helm upgrade euler-copilot -n euler-copilot . + ``` diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/CPU\351\200\273\350\276\221\346\240\270\345\277\203.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/CPU\351\200\273\350\276\221\346\240\270\345\277\203.png" new file mode 100644 index 0000000000000000000000000000000000000000..74ae942b5a5217b8a5e34a2b2cd8d32a49be7a00 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/CPU\351\200\273\350\276\221\346\240\270\345\277\203.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/Copilot\345\244\247\346\250\241\345\236\213\351\203\250\347\275\262\345\267\256\345\274\202.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/Copilot\345\244\247\346\250\241\345\236\213\351\203\250\347\275\262\345\267\256\345\274\202.png" new file mode 100644 index 0000000000000000000000000000000000000000..8f1de7892e04be698310691d2cfdeb07cbfa579d Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/Copilot\345\244\247\346\250\241\345\236\213\351\203\250\347\275\262\345\267\256\345\274\202.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2761.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2761.png" new file mode 100644 index 0000000000000000000000000000000000000000..e59e8b669c3039341655eadd75ce1fda5cda1776 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2761.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2762.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2762.png" new file mode 100644 index 0000000000000000000000000000000000000000..68ae1c7cb11e663cabbf1225b188fdfd628bf549 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2762.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2763.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2763.png" new file mode 100644 index 0000000000000000000000000000000000000000..d90f6182fb6ec63f868a5c2598de73db093775f2 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\216\245\345\205\245copilot\346\225\210\346\236\234\345\233\2763.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\265\213\350\257\225\346\216\245\345\217\243\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\265\213\350\257\225\346\216\245\345\217\243\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..374c3a2cc0be67a012ef8bf0ddc7688f97702d79 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\346\265\213\350\257\225\346\216\245\345\217\243\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\350\275\273\351\207\217\345\214\226\351\203\250\347\275\262\350\247\206\345\233\276.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\350\275\273\351\207\217\345\214\226\351\203\250\347\275\262\350\247\206\345\233\276.png" new file mode 100644 index 0000000000000000000000000000000000000000..297ad86cac9226084483816f0c88c9116071b675 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/CPU\346\216\250\347\220\206\351\203\250\347\275\262/\350\275\273\351\207\217\345\214\226\351\203\250\347\275\262\350\247\206\345\233\276.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/WEB\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/WEB\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..bb9be4e33ce470865fe5a07decbc056b9ee4e9bb Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/WEB\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/WEB\347\231\273\345\275\225\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/WEB\347\231\273\345\275\225\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..fddbab4df70b940d5d5ed26fb8ec688f1592b5e8 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/WEB\347\231\273\345\275\225\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/authhub\347\231\273\345\275\225\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/authhub\347\231\273\345\275\225\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..341828b1b6f728888d1dd52eec755033680155da Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/authhub\347\231\273\345\275\225\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/witchaind\347\231\273\345\275\225\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/witchaind\347\231\273\345\275\225\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..dfc28f4046fd4d61f48a0b0903ae2cf565ec5bc3 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/witchaind\347\231\273\345\275\225\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\345\233\236\345\210\260\351\246\226\351\241\265.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\345\233\236\345\210\260\351\246\226\351\241\265.png" new file mode 100644 index 0000000000000000000000000000000000000000..92685c5d977abe55f5d201aa57da479c8af84561 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\345\233\236\345\210\260\351\246\226\351\241\265.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\345\257\274\345\205\245\346\226\207\346\241\243\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\345\257\274\345\205\245\346\226\207\346\241\243\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..c4b71d6def0b6407f721cf3c137d714d923f86f1 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\345\257\274\345\205\245\346\226\207\346\241\243\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\345\257\274\345\207\272\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\345\257\274\345\207\272\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..3458c5330fad7b8c89cb0bc8efb70f875d6f17d2 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\345\257\274\345\207\272\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\345\257\274\345\207\272\350\265\204\344\272\247\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\345\257\274\345\207\272\350\265\204\344\272\247\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..469871fa9483a698b03374c3686b22156ad6e33a Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\345\257\274\345\207\272\350\265\204\344\272\247\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\345\257\274\345\205\245\346\210\220\345\212\237\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\345\257\274\345\205\245\346\210\220\345\212\237\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..8aba84e49c981c8f81cb91b14eee64f179bf0b38 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\345\257\274\345\205\245\346\210\220\345\212\237\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\345\257\274\345\205\245\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\345\257\274\345\205\245\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..7932773ccf59f58a283caccb92bd5af9475a7be9 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\345\257\274\345\205\245\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\346\255\243\345\234\250\345\257\274\345\205\245\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\346\255\243\345\234\250\345\257\274\345\205\245\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..50805afdb4764b74d9d16067999d7b39ce901d2a Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\211\271\351\207\217\346\255\243\345\234\250\345\257\274\345\205\245\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\226\260\345\273\272\350\265\204\344\272\247\345\272\223\345\241\253\345\206\231\345\261\225\347\244\272\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\226\260\345\273\272\350\265\204\344\272\247\345\272\223\345\241\253\345\206\231\345\261\225\347\244\272\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..8eb29b167f6ff1c2d951cd841f2340b027dec808 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\226\260\345\273\272\350\265\204\344\272\247\345\272\223\345\241\253\345\206\231\345\261\225\347\244\272\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\226\260\345\273\272\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\226\260\345\273\272\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..9da6121b1c1271c5b09c9292690ba3ab8d0a6cd2 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\226\260\345\273\272\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\237\245\347\234\213\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\237\245\347\234\213\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..a533772ce715bbf2c4a9f374b03e7fe20bf470a1 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\237\245\347\234\213\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\255\243\345\234\250\345\257\274\345\207\272\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\255\243\345\234\250\345\257\274\345\207\272\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..659ebeae5b25738043f7750c7cc44a1e80557ed8 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\346\255\243\345\234\250\345\257\274\345\207\272\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\347\202\271\345\207\273\351\200\200\345\207\272\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\347\202\271\345\207\273\351\200\200\345\207\272\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..22b02fff81fe1db3232b80607da6f10f710c8c64 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\347\202\271\345\207\273\351\200\200\345\207\272\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\344\270\255\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\344\270\255\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..913a5ce34a0a3e95af29e7c4433e5367c0adf008 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\344\270\255\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\346\210\220\345\212\237\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\346\210\220\345\212\237\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..a1c6dc638d0dbd51abc374d563da150ff328cbe3 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\346\210\220\345\212\237\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\346\226\271\346\263\225\351\200\211\346\213\251\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\346\226\271\346\263\225\351\200\211\346\213\251\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..f0449b134e1ebe5d54ca46099b57c6ad0b949eca Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\346\226\271\346\263\225\351\200\211\346\213\251\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\347\273\223\346\236\234\350\277\207\346\273\244\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\347\273\223\346\236\234\350\277\207\346\273\244\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..e3d3ba7727d53490b22ecc7a1b422d5ae03390d3 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\347\273\223\346\236\234\350\277\207\346\273\244\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\350\257\246\346\203\205\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\350\257\246\346\203\205\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..e018cb0904b414d63e1008209adb47c0b8afb858 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\247\243\346\236\220\350\257\246\346\203\205\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\265\204\344\272\247\345\272\223\345\210\233\345\273\272\345\256\214\346\210\220\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\265\204\344\272\247\345\272\223\345\210\233\345\273\272\345\256\214\346\210\220\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..7bf98b8217dda2358621fe9b11164407e2040ae8 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\265\204\344\272\247\345\272\223\345\210\233\345\273\272\345\256\214\346\210\220\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\277\233\345\205\245\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\277\233\345\205\245\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..795e99cdad03b2a3377fe77e51e336c6a6ca5b29 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\350\277\233\345\205\245\350\265\204\344\272\247\345\272\223\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\351\200\211\346\213\251\346\226\207\344\273\266.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\351\200\211\346\213\251\346\226\207\344\273\266.png" new file mode 100644 index 0000000000000000000000000000000000000000..8031fec14e15b0e80e596f21cf79fe2b58ff7293 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/witChainD/\351\200\211\346\213\251\346\226\207\344\273\266.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\345\210\233\345\273\272\345\272\224\347\224\250\346\210\220\345\212\237\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\345\210\233\345\273\272\345\272\224\347\224\250\346\210\220\345\212\237\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..a871907f348317e43633cf05f5241cb978476fb4 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\345\210\233\345\273\272\345\272\224\347\224\250\346\210\220\345\212\237\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\345\210\233\345\273\272\345\272\224\347\224\250\347\225\214\351\235\242.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\345\210\233\345\273\272\345\272\224\347\224\250\347\225\214\351\235\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..d82c736a94b106a30fd8d1f7b781f9e335bb441f Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\345\210\233\345\273\272\345\272\224\347\224\250\347\225\214\351\235\242.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/k8s\351\233\206\347\276\244\344\270\255postgres\346\234\215\345\212\241\347\232\204\345\220\215\347\247\260.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/k8s\351\233\206\347\276\244\344\270\255postgres\346\234\215\345\212\241\347\232\204\345\220\215\347\247\260.png" new file mode 100644 index 0000000000000000000000000000000000000000..473a0006c9710c92375e226a760c3a79989312f9 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/k8s\351\233\206\347\276\244\344\270\255postgres\346\234\215\345\212\241\347\232\204\345\220\215\347\247\260.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/postgres\346\234\215\345\212\241\347\253\257\345\217\243.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/postgres\346\234\215\345\212\241\347\253\257\345\217\243.png" new file mode 100644 index 0000000000000000000000000000000000000000..cfee6d88da56bc939886caece540f7de8cf77bbc Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/postgres\346\234\215\345\212\241\347\253\257\345\217\243.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/rag_port.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/rag_port.png" new file mode 100644 index 0000000000000000000000000000000000000000..b1d93f9c9d7587aa88a27d7e0bf185586583d438 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/rag_port.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/rag\351\205\215\347\275\256\344\277\241\346\201\257\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/rag\351\205\215\347\275\256\344\277\241\346\201\257\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..fec3cdaa2b260e50f5523477da3e58a9e14e2130 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/rag\351\205\215\347\275\256\344\277\241\346\201\257\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\233\345\273\272\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245\347\224\261\344\272\216\347\273\237\344\270\200\350\265\204\344\272\247\344\270\213\345\255\230\345\234\250\345\220\214\345\220\215\350\265\204\344\272\247\345\272\223.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\233\345\273\272\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245\347\224\261\344\272\216\347\273\237\344\270\200\350\265\204\344\272\247\344\270\213\345\255\230\345\234\250\345\220\214\345\220\215\350\265\204\344\272\247\345\272\223.png" new file mode 100644 index 0000000000000000000000000000000000000000..624459821de4542b635eeffa115eeba780929a4e Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\233\345\273\272\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245\347\224\261\344\272\216\347\273\237\344\270\200\350\265\204\344\272\247\344\270\213\345\255\230\345\234\250\345\220\214\345\220\215\350\265\204\344\272\247\345\272\223.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\233\345\273\272\350\265\204\344\272\247\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\233\345\273\272\350\265\204\344\272\247\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..3104717bfa8f6615ad6726577a24938bc29884b2 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\233\345\273\272\350\265\204\344\272\247\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\344\270\215\345\255\230\345\234\250\347\232\204\350\265\204\344\272\247\345\244\261\350\264\245.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\344\270\215\345\255\230\345\234\250\347\232\204\350\265\204\344\272\247\345\244\261\350\264\245.png" new file mode 100644 index 0000000000000000000000000000000000000000..454b9fdfa4b7f209dc370f78677a2f4e71ea49be Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\344\270\215\345\255\230\345\234\250\347\232\204\350\265\204\344\272\247\345\244\261\350\264\245.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\257\255\346\226\231.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\257\255\346\226\231.png" new file mode 100644 index 0000000000000000000000000000000000000000..d52d25d4778f6db2d2ec076d65018c40cd1da4d3 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\257\255\346\226\231.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245\357\274\214\350\265\204\344\272\247\344\270\213\344\270\215\345\255\230\345\234\250\345\257\271\345\272\224\350\265\204\344\272\247\345\272\223.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245\357\274\214\350\265\204\344\272\247\344\270\213\344\270\215\345\255\230\345\234\250\345\257\271\345\272\224\350\265\204\344\272\247\345\272\223.png" new file mode 100644 index 0000000000000000000000000000000000000000..82ed79c0154bd8e406621440c4e4a7caaab7e06e Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245\357\274\214\350\265\204\344\272\247\344\270\213\344\270\215\345\255\230\345\234\250\345\257\271\345\272\224\350\265\204\344\272\247\345\272\223.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\265\204\344\272\247\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\265\204\344\272\247\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..7dd2dea945f39ada1d7dd053d150a995b160f203 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\210\240\351\231\244\350\265\204\344\272\247\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\273\272\347\253\213\350\265\204\344\272\247\345\272\223.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\273\272\347\253\213\350\265\204\344\272\247\345\272\223.png" new file mode 100644 index 0000000000000000000000000000000000000000..84737b4185ce781d7b32ab42d39b8d2452138dad Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\345\273\272\347\253\213\350\265\204\344\272\247\345\272\223.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\214\207\345\256\232\344\270\215\345\255\230\345\234\250\347\232\204\350\265\204\344\272\247\345\210\233\345\273\272\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\214\207\345\256\232\344\270\215\345\255\230\345\234\250\347\232\204\350\265\204\344\272\247\345\210\233\345\273\272\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245.png" new file mode 100644 index 0000000000000000000000000000000000000000..be89bdfde2518bba3941eee5d475f52ad9124343 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\214\207\345\256\232\344\270\215\345\255\230\345\234\250\347\232\204\350\265\204\344\272\247\345\210\233\345\273\272\350\265\204\344\272\247\345\272\223\345\244\261\350\264\245.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\225\260\346\215\256\345\272\223\345\210\235\345\247\213\345\214\226.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\225\260\346\215\256\345\272\223\345\210\235\345\247\213\345\214\226.png" new file mode 100644 index 0000000000000000000000000000000000000000..27530840aaa5382a226e1ed8baea883895d9d75e Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\225\260\346\215\256\345\272\223\345\210\235\345\247\213\345\214\226.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\225\260\346\215\256\345\272\223\351\205\215\347\275\256\344\277\241\346\201\257\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\225\260\346\215\256\345\272\223\351\205\215\347\275\256\344\277\241\346\201\257\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..aa04e6f7f0648adfca1240c750ca5b79b88da5f9 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\225\260\346\215\256\345\272\223\351\205\215\347\275\256\344\277\241\346\201\257\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\227\240\350\265\204\344\272\247\346\227\266\346\237\245\350\257\242\350\265\204\344\272\247.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\227\240\350\265\204\344\272\247\346\227\266\346\237\245\350\257\242\350\265\204\344\272\247.png" new file mode 100644 index 0000000000000000000000000000000000000000..74905172c0c0a0acc4c4d0e35efd2493dc421c4e Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\227\240\350\265\204\344\272\247\346\227\266\346\237\245\350\257\242\350\265\204\344\272\247.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\347\234\213\346\226\207\346\241\243\344\272\247\347\224\237\347\211\207\346\256\265\346\200\273\346\225\260\345\222\214\344\270\212\344\274\240\346\210\220\345\212\237\346\200\273\346\225\260.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\347\234\213\346\226\207\346\241\243\344\272\247\347\224\237\347\211\207\346\256\265\346\200\273\346\225\260\345\222\214\344\270\212\344\274\240\346\210\220\345\212\237\346\200\273\346\225\260.png" new file mode 100644 index 0000000000000000000000000000000000000000..432fbfcd02f6d2220e7d2a8512aee893d67be24d Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\347\234\213\346\226\207\346\241\243\344\272\247\347\224\237\347\211\207\346\256\265\346\200\273\346\225\260\345\222\214\344\270\212\344\274\240\346\210\220\345\212\237\346\200\273\346\225\260.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\350\257\242\345\205\250\351\203\250\350\257\255\346\226\231.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\350\257\242\345\205\250\351\203\250\350\257\255\346\226\231.png" new file mode 100644 index 0000000000000000000000000000000000000000..a4f4ea8a3999a9ab659ccd9ea39b80b21ff46e84 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\350\257\242\345\205\250\351\203\250\350\257\255\346\226\231.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\350\257\242\350\265\204\344\272\247.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\350\257\242\350\265\204\344\272\247.png" new file mode 100644 index 0000000000000000000000000000000000000000..675b40297363664007f96948fb21b1cb90d6beea Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\346\237\245\350\257\242\350\265\204\344\272\247.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\216\267\345\217\226\346\225\260\346\215\256\345\272\223pod\345\220\215\347\247\260.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\216\267\345\217\226\346\225\260\346\215\256\345\272\223pod\345\220\215\347\247\260.png" new file mode 100644 index 0000000000000000000000000000000000000000..8fc0c988e8b3830c550c6be6e42b88ac13448d1a Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\216\267\345\217\226\346\225\260\346\215\256\345\272\223pod\345\220\215\347\247\260.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\344\270\212\344\274\240\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\344\270\212\344\274\240\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..5c897e9883e868bf5160d92cb106ea4e4e9bc356 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\344\270\212\344\274\240\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\345\210\240\351\231\244\345\244\261\350\264\245\357\274\214\346\234\252\346\237\245\350\257\242\345\210\260\347\233\270\345\205\263\350\257\255\346\226\231.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\345\210\240\351\231\244\345\244\261\350\264\245\357\274\214\346\234\252\346\237\245\350\257\242\345\210\260\347\233\270\345\205\263\350\257\255\346\226\231.png" new file mode 100644 index 0000000000000000000000000000000000000000..407e49b929b7ff4cf14703046a4ba0bfe1bb441e Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\345\210\240\351\231\244\345\244\261\350\264\245\357\274\214\346\234\252\346\237\245\350\257\242\345\210\260\347\233\270\345\205\263\350\257\255\346\226\231.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\346\237\245\350\257\242\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\346\237\245\350\257\242\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..a4f4ea8a3999a9ab659ccd9ea39b80b21ff46e84 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\257\255\346\226\231\346\237\245\350\257\242\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\344\270\213\346\234\252\346\237\245\350\257\242\345\210\260\350\265\204\344\272\247\345\272\223.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\344\270\213\346\234\252\346\237\245\350\257\242\345\210\260\350\265\204\344\272\247\345\272\223.png" new file mode 100644 index 0000000000000000000000000000000000000000..45ab521ec5f5afbd81ad54f023aae3b7a867dbf2 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\344\270\213\346\234\252\346\237\245\350\257\242\345\210\260\350\265\204\344\272\247\345\272\223.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\344\270\213\346\237\245\350\257\242\350\265\204\344\272\247\345\272\223\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\344\270\213\346\237\245\350\257\242\350\265\204\344\272\247\345\272\223\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..90ed5624ae93ff9784a750514c53293df4e961f0 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\344\270\213\346\237\245\350\257\242\350\265\204\344\272\247\345\272\223\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\233\345\273\272\346\210\220\345\212\237.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\233\345\273\272\346\210\220\345\212\237.png" new file mode 100644 index 0000000000000000000000000000000000000000..7b2cc38a931c9c236517c14c86fa93e3eb2b6dcd Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\233\345\273\272\346\210\220\345\212\237.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\240\351\231\244\345\244\261\350\264\245\357\274\214\344\270\215\345\255\230\345\234\250\350\265\204\344\272\247.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\240\351\231\244\345\244\261\350\264\245\357\274\214\344\270\215\345\255\230\345\234\250\350\265\204\344\272\247.png" new file mode 100644 index 0000000000000000000000000000000000000000..1365a8d69467dec250d3451ac63e2615a2194c18 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\240\351\231\244\345\244\261\350\264\245\357\274\214\344\270\215\345\255\230\345\234\250\350\265\204\344\272\247.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\240\351\231\244\346\210\220\345\212\237png.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\240\351\231\244\346\210\220\345\212\237png.png" new file mode 100644 index 0000000000000000000000000000000000000000..1bd944264baa9369e6f8fbfd04cabcd12730c0e9 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\345\210\240\351\231\244\346\210\220\345\212\237png.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\346\237\245\350\257\242\345\244\261\350\264\245\357\274\214\344\270\215\345\255\230\345\234\250\350\265\204\344\272\247.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\346\237\245\350\257\242\345\244\261\350\264\245\357\274\214\344\270\215\345\255\230\345\234\250\350\265\204\344\272\247.png" new file mode 100644 index 0000000000000000000000000000000000000000..58bcd320e145dd29d9e5d49cb6d86964ebb83b51 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\350\265\204\344\272\247\345\272\223\346\237\245\350\257\242\345\244\261\350\264\245\357\274\214\344\270\215\345\255\230\345\234\250\350\265\204\344\272\247.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\344\270\255\351\227\264\345\261\202.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\344\270\255\351\227\264\345\261\202.png" new file mode 100644 index 0000000000000000000000000000000000000000..809b785b999b6663d9e9bd41fed953925093d6bd Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\344\270\255\351\227\264\345\261\202.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\346\272\220\347\233\256\345\275\225.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\346\272\220\347\233\256\345\275\225.png" new file mode 100644 index 0000000000000000000000000000000000000000..62ba5f6615f18deb3d5a71fd68ee8c929638d814 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\346\272\220\347\233\256\345\275\225.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\347\233\256\346\240\207\347\233\256\345\275\225.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\347\233\256\346\240\207\347\233\256\345\275\225.png" new file mode 100644 index 0000000000000000000000000000000000000000..d32c672fafcb0ef665bda0bcfdce19d2df44db01 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\205\215\347\275\256\346\230\240\345\260\204\347\233\256\346\240\207\347\233\256\345\275\225.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\207\215\345\244\215\345\210\233\345\273\272\350\265\204\344\272\247\345\244\261\350\264\245.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\207\215\345\244\215\345\210\233\345\273\272\350\265\204\344\272\247\345\244\261\350\264\245.png" new file mode 100644 index 0000000000000000000000000000000000000000..a5ecd6b65abc97320e7467f00d82ff1fd9bf0e44 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\346\234\254\345\234\260\350\265\204\344\272\247\345\272\223\346\236\204\345\273\272/\351\207\215\345\244\215\345\210\233\345\273\272\350\265\204\344\272\247\345\244\261\350\264\245.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\351\203\250\347\275\262\350\247\206\345\233\276.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\351\203\250\347\275\262\350\247\206\345\233\276.png" new file mode 100644 index 0000000000000000000000000000000000000000..181bf1d2ddbe15cfd296c27df27d865bdbce8d69 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/deploy_guide/pictures/\351\203\250\347\275\262\350\247\206\345\233\276.png" differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/introduction.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/introduction.md new file mode 100644 index 0000000000000000000000000000000000000000..b3f6abde54923c2b2dfb767a8cb03eb9281cd22c --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/introduction.md @@ -0,0 +1,68 @@ +# 前言 + +## 概述 + +本文档介绍了 EulerCopilot 的使用方法,对 EulerCopilot 线上服务的 Web 界面的各项功能做了详细介绍,同时提供了常见的问题解答,详细请参考对应手册。 + +## 读者对象 + +本文档主要适用于 EulerCopilot 的使用人员。使用人员必须具备以下经验和技能: + +- 熟悉 openEuler 操作系统相关情况。 +- 有 AI 对话使用经验。 + +## 修改记录 + +| 文档版本 | 发布日期 | 修改说明 | +|--------|------------|----------------| +| 03 | 2024-09-19 | 更新新版界面。 | +| 02 | 2024-05-13 | 优化智能对话操作指引。 | +| 01 | 2024-01-28 | 第一次正式发布。 | + +## 介绍 + +### 免责声明 + +- 使用过程中涉及的非工具本身验证功能所用的用户名和密码,不作他用,且不会被保存在系统环境中。 +- 在您进行对话或操作前应当确认您为应用程序的所有者或已获得所有者的充足授权同意。 +- 对话结果中可能包含您所分析应用的内部信息和相关数据,请妥善管理。 +- 除非法律法规或双方合同另有规定,openEuler 社区对分析结果不做任何明示或暗示的声明和保证,不对分析结果的适销性、满意度、非侵权性或特定用途适用性等作出任何保证或者承诺。 +- 您根据分析记录所采取的任何行为均应符合法律法规的要求,并由您自行承担风险。 +- 未经所有者授权,任何个人或组织均不得使用应用程序及相关分析记录从事任何活动。openEuler 社区不对由此造成的一切后果负责,亦不承担任何法律责任。必要时,将追究其法律责任。 + +### EulerCopilot 简介 + +EulerCopilot 是一个基于 openEuler 操作系统的人工智能助手,可以帮助用户解决各种技术问题,提供技术支持和咨询服务。它使用了最先进的自然语言处理技术和机器学习算法,能够理解用户的问题并提供相应的解决方案。 + +### 场景内容 + +1. OS 领域通用知识:EulerCopilot 可以咨询 Linux 常规知识、上游信息和工具链介绍和指导。 +2. openEuler 专业知识:EulerCopilot 可以咨询 openEuler 社区信息、技术原理和使用指导。 +3. openEuler 扩展知识:EulerCopilot 可以咨询 openEuler 周边硬件特性知识和ISV、OSV相关信息。 +4. openEuler 应用案例:EulerCopilot 可以提供 openEuler 技术案例、行业应用案例。 +5. shell 命令生成:EulerCopilot 可以帮助用户生成单条 shell 命令或者复杂命令。 + +总之,EulerCopilot 可以应用于各种场景,帮助用户提高工作效率和了解 Linux、openEuler 等的相关知识。 + +### 访问和使用 + +EulerCopilot 通过网址访问 Web 网页进行使用。账号注册与登录请参考[注册与登录](./registration_and_login.md)。使用方法请参考[智能问答使用指南](./qa_guide.md)。 + +### 界面说明 + +#### 界面分区 + +EulerCopilot 界面主要由如图 1 所示的区域组成,各个区域的作用如表 1 所示。 + +- 图 1 EulerCopilot 界面 + +![Copilot 界面](./pictures/main-page-sections.png) + +- 表 1 EulerCopilot 首页界面分区说明 + +| 区域 | 名称 | 说明 | +|-----|------------|----------------------------------------------------------------| +| 1 | 设置管理区 | 提供账号登录和退出操作入口和明亮/黑暗模式切换功能 | +| 2 | 对话管理区 | 用于用户新建对话、对话历史记录管理和对话历史记录批量删除操作 | +| 3 | 对话区 | 用于用户和 EulerCopilot 的对话聊天 | +| 4 | 服务协议和隐私政策区 | 提供查看服务协议和隐私政策入口 | diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/authhub-login-click2signup.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/authhub-login-click2signup.png new file mode 100644 index 0000000000000000000000000000000000000000..6e6f96b4a902d04c67eb2e299ad038423dcb04c7 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/authhub-login-click2signup.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/authhub-login.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/authhub-login.png new file mode 100644 index 0000000000000000000000000000000000000000..b5ea5a7577f2ce19fad4df5274847676134d95e0 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/authhub-login.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/authhub-signup.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/authhub-signup.png new file mode 100644 index 0000000000000000000000000000000000000000..c20a54d270988f56039a2b93eca6aa369d048884 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/authhub-signup.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/bulk-delete-confirmation.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/bulk-delete-confirmation.png new file mode 100644 index 0000000000000000000000000000000000000000..3cc5a6a25618eff0bfa9807e1c19e4f88edc7da4 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/bulk-delete-confirmation.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/bulk-delete-multi-select.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/bulk-delete-multi-select.png new file mode 100644 index 0000000000000000000000000000000000000000..772c51d903531cfe74245f08ecdca06d4677f935 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/bulk-delete-multi-select.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/bulk-delete.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/bulk-delete.png new file mode 100644 index 0000000000000000000000000000000000000000..929230cd06cc792b633ab183155225926d2c300d Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/bulk-delete.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/chat-area.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/chat-area.png new file mode 100644 index 0000000000000000000000000000000000000000..966432e02f08a6c769e8cd87b0468bd25f257f5e Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/chat-area.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/context-support.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/context-support.png new file mode 100644 index 0000000000000000000000000000000000000000..0bd5f091d0eff34d9b5f36eec6df63b191656daa Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/context-support.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/delete-session-confirmation.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/delete-session-confirmation.png new file mode 100644 index 0000000000000000000000000000000000000000..729096bdae14895b81e8725a8065d1f4bfcdbf6c Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/delete-session-confirmation.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/delete-session.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/delete-session.png new file mode 100644 index 0000000000000000000000000000000000000000..596af33f7be41d456a57e6a297820530f8485f34 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/delete-session.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/feedback-illegal.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/feedback-illegal.png new file mode 100644 index 0000000000000000000000000000000000000000..b6e84ba45977d911db960da97bdff714624ba18c Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/feedback-illegal.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/feedback-misinfo.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/feedback-misinfo.png new file mode 100644 index 0000000000000000000000000000000000000000..cc5505226add1e6fbde7b93ff09877038e8cfdce Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/feedback-misinfo.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/feedback.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/feedback.png new file mode 100644 index 0000000000000000000000000000000000000000..9fe1c27acb57d4d24a26c8dde61ee4272f954e46 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/feedback.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-ask-against-file.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-ask-against-file.png new file mode 100644 index 0000000000000000000000000000000000000000..2cf2c5e50c8c02c4c2713fde63c7e11c110c8bb2 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-ask-against-file.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-btn-prompt.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-btn-prompt.png new file mode 100644 index 0000000000000000000000000000000000000000..45e38672d0c46ccc2ded83669875f7c832f2c73d Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-btn-prompt.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-btn.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-btn.png new file mode 100644 index 0000000000000000000000000000000000000000..2f6a7cee51e2cb02b52baf6ffa7136f5601a26e1 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-btn.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-history-tag.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-history-tag.png new file mode 100644 index 0000000000000000000000000000000000000000..487a48e6f72cbe8f115d8ce2001808b9b4a74dec Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-history-tag.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-parsing.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-parsing.png new file mode 100644 index 0000000000000000000000000000000000000000..812090a59ee3594b11ecfcb55cc7a8b7361ca2bb Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-parsing.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-showcase.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-showcase.png new file mode 100644 index 0000000000000000000000000000000000000000..60234df165d16abb976ffdf74d0b1ad890387e57 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-showcase.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-uploading.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-uploading.png new file mode 100644 index 0000000000000000000000000000000000000000..7f29ba755ce71d08098d0d5950239b69e1d7f16a Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/file-upload-uploading.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-arrow-next.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-arrow-next.png new file mode 100644 index 0000000000000000000000000000000000000000..1a36c84e0965f9dbf1f90e9a3daadcd1a2560951 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-arrow-next.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-arrow-prev.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-arrow-prev.png new file mode 100644 index 0000000000000000000000000000000000000000..eb667e93cc6d51aa191a0ac7607e72d4d6923cbc Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-arrow-prev.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-cancel.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..34d4454b6f92ee12db6841dafe0e94a12c3b9584 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-cancel.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-confirm.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-confirm.png new file mode 100644 index 0000000000000000000000000000000000000000..1d650f8192e04fae8f7b7c08cd527227c91b833a Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-confirm.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-edit.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..f7b28aa605b5e899855a261d641d27a2674703eb Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-edit.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-search.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-search.png new file mode 100644 index 0000000000000000000000000000000000000000..7902923196c3394ae8eafaf5a2b6fdf7f19b1f40 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-search.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-thumb-down.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-thumb-down.png new file mode 100644 index 0000000000000000000000000000000000000000..cda14d196d92898da920ed64ad37fa9dd124c775 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-thumb-down.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-thumb-up.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-thumb-up.png new file mode 100644 index 0000000000000000000000000000000000000000..c75ce44bff456e24bc19040c18e4e644bbb77bd1 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-thumb-up.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-user.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-user.png new file mode 100644 index 0000000000000000000000000000000000000000..e6b06878b76d9e6d268d74070539b388129fa8c4 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/icon-user.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/login-popup.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/login-popup.png new file mode 100644 index 0000000000000000000000000000000000000000..7834248e8603aca100b8b7e33a93611777cc6ede Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/login-popup.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/logout.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/logout.png new file mode 100644 index 0000000000000000000000000000000000000000..da51441e632cb77dfbe0f86056e333f69485c500 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/logout.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/main-page-clean-ref.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/main-page-clean-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..2e00878b62408e75d8f82c40b3a1f5e0f4f878f6 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/main-page-clean-ref.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/main-page-sections.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/main-page-sections.png new file mode 100644 index 0000000000000000000000000000000000000000..9d8f013318c840a5b05b3010b9b08047870be822 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/main-page-sections.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/new-chat.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/new-chat.png new file mode 100644 index 0000000000000000000000000000000000000000..784a0da650df405e1df147409b785a026109e239 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/new-chat.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-list.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-list.png new file mode 100644 index 0000000000000000000000000000000000000000..90270b4c9d8991463e4a4129625ab0325ac09922 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-list.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-result.png new file mode 100644 index 0000000000000000000000000000000000000000..a810a8c123f34f51c06c2dd22c9fc1e9cb4efa06 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-selected.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..fa5342091d0a023a545c3edab8c4368654df8a90 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-selected.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-suggestion.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-suggestion.png new file mode 100644 index 0000000000000000000000000000000000000000..bb416881550349000f61b0c1bd3dd540878bd6ad Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-suggestion.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-case-step-1.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-case-step-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e961ddc5b9aa6b687c69e4587ea3a59f54b6ad27 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-case-step-1.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-case-step-2-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-case-step-2-result.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc52217a1595613a934c5860704d688a2876a37 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-case-step-2-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-case-step-2.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-case-step-2.png new file mode 100644 index 0000000000000000000000000000000000000000..0cb59551c2695151491aa1120163ea0c1aabb317 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-case-step-2.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-fill-in-param-result.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-fill-in-param-result.png new file mode 100644 index 0000000000000000000000000000000000000000..899ee2672ba8b5eb8518fb9b80104577159d1cb4 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-fill-in-param-result.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-fill-in-param.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-fill-in-param.png new file mode 100644 index 0000000000000000000000000000000000000000..4c03312d72f49c51868826d62bc59d0f2f925cc7 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/plugin-workflow-fill-in-param.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/privacy-policy-entry.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/privacy-policy-entry.png new file mode 100644 index 0000000000000000000000000000000000000000..d7efce3e6e8d477ef47a1bc8a9bba0d087cf8058 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/privacy-policy-entry.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/privacy-policy.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/privacy-policy.png new file mode 100644 index 0000000000000000000000000000000000000000..0bc0980a7dd78e055fc920d591a77d5394b5fb84 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/privacy-policy.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/recommend-questions.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/recommend-questions.png new file mode 100644 index 0000000000000000000000000000000000000000..076ec7092af7fe7987e5dc7c864a6b9f8b2b1160 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/recommend-questions.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/regenerate.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/regenerate.png new file mode 100644 index 0000000000000000000000000000000000000000..655c9d5002df4a17aaf84e8780fff4a0118c6c01 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/regenerate.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/rename-session-confirmation.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/rename-session-confirmation.png new file mode 100644 index 0000000000000000000000000000000000000000..d64708bd57d53deafdc5ddbb70d9deaeaca0d132 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/rename-session-confirmation.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/rename-session.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/rename-session.png new file mode 100644 index 0000000000000000000000000000000000000000..73e7e19c5ac8e8035df0e4b553a9b78ff5c9a051 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/rename-session.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/report-options.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/report-options.png new file mode 100644 index 0000000000000000000000000000000000000000..8a54fd2598d51fc40b57052f404dd830cf621f4d Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/report-options.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/report.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/report.png new file mode 100644 index 0000000000000000000000000000000000000000..471bcbe8614fc8bab4dcc1805fa1bf4574990fc8 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/report.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/search-history.png b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/search-history.png new file mode 100644 index 0000000000000000000000000000000000000000..2239d14a7aa8bc13a7b8d3ec71ba9ed71b95e850 Binary files /dev/null and b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/pictures/search-history.png differ diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/qa_guide.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/qa_guide.md new file mode 100644 index 0000000000000000000000000000000000000000..73f25bfce21a08d3eef048aa74e29ed0cb1f65d4 --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/qa_guide.md @@ -0,0 +1,179 @@ +# 智能问答使用指南 + +## 开始对话 + +在对话区下侧输入框即可输入对话想要提问的内容,输入 `Shift + Enter` 可进行换行,输入 `Enter` 即可发送对话提问内容,或者单击“发送”也可发送对话提问内容。 + +> **说明** +> +> 对话区位于页面的主体部分,如图 1 所示。 + +- 图 1 对话区 + ![对话区](./pictures/chat-area.png) + +### 多轮连续对话 + +EulerCopilot 智能问答支持多轮连续对话。只需要在同一个对话中继续追问即可使用,如图 2 所示。 + +- 图 2 多轮对话 + ![多轮对话](./pictures/context-support.png) + +### 重新生成 + +如遇到 AI 生成的内容有误或不完整的特殊情况,可以要求 AI 重新回答问题。单击对话回答左下侧的“重新生成”文字,可让 EulerCopilot 重新回答用户问题,重新回答后,在对话回答右下侧,会出现回答翻页的图标![向前翻页](./pictures/icon-arrow-prev.png)和![向后翻页](./pictures/icon-arrow-next.png),单击![向前翻页](./pictures/icon-arrow-prev.png)或![向后翻页](./pictures/icon-arrow-next.png)可查看不同的回答,如图 3 所示。 + +- 图 3 重新生成 + ![重新生成](./pictures/regenerate.png) + +### 推荐问题 + +在 AI 回答的下方,会展示一些推荐的问题,单击即可进行提问,如图 4 所示。 + +- 图 4 推荐问题 + ![推荐问题](./pictures/recommend-questions.png) + +## 自定义背景知识 + +EulerCopilot 支持上传文件功能。上传文件后,AI 会将上传的文件内容作为背景知识,在回答问题时,会结合背景知识进行回答。上传的背景知识只会作用于当前对话,不会影响其他对话。 + +### 上传文件 + +**步骤1** 单击对话区左下角的“上传文件”按钮,如图 5 所示。 + +- 图 5 上传文件按钮 + ![上传文件](./pictures/file-upload-btn.png) + +> **说明** +> +> 鼠标悬停到“上传文件”按钮上,会显示提示允许上传文件的规格和格式,如图 6 所示。 + +- 图 6 鼠标悬停显示上传文件规格提示 + ![上传文件提示](./pictures/file-upload-btn-prompt.png) + +**步骤2** 在弹出的文件选择框中,选择需要上传的文件,单击“打开”,即可上传文件。最多上传10个文件,总大小限制为64MB。接受 PDF、docx、doc、txt、md、xlsx。 + +开始上传后,对话区下方会显示上传进度,如图 7 所示。 + +- 图 7 同时上传的所有文件排列在问答输入框下方 + ![上传文件](./pictures/file-upload-uploading.png) + +文件上传完成后会自动解析,如图 8 所示,解析完成后,对话区下方会显示每个文件的大小信息。 + +- 图 8 文件上传至服务器后将显示“正在解析” + ![文件解析](./pictures/file-upload-parsing.png) + +文件上传成功后,左侧历史记录区会显示上传的文件数量,如图 9 所示。 + +- 图 9 对话历史记录标签上会展示上传文件数量 + ![历史记录标记](./pictures/file-upload-history-tag.png) + +### 针对文件提问 + +文件上传完成后,即可针对文件进行提问,提问方式同普通对话模式,如图 10 所示。 +回答结果如图 11 所示。 + +- 图 10 询问与上传的文件相关的问题 + ![针对文件提问](./pictures/file-upload-ask-against-file.png) + +- 图 11 AI 以上传的为背景知识进行回答 + ![根据自定义背景知识回答](./pictures/file-upload-showcase.png) + +## 管理对话 + +> **说明** +> +> 对话管理区在页面左侧。 + +### 新建对话 + +单击“新建对话”按钮即可新建对话,如图 12 所示。 + +- 图 12 “新建对话”按钮在页面左上方 + ![新建对话](./pictures/new-chat.png) + +### 对话历史记录搜索 + +在页面左侧历史记录搜索输入框输入关键词,然后单击![搜索](./pictures/icon-search.png)即可进行对话历史记录搜索如图 13 所示。 + +- 图 13 对话历史记录搜索框 + ![对话历史记录搜索](./pictures/search-history.png) + +### 对话历史记录单条管理 + +历史记录的列表位于历史记录搜索栏的下方,在每条对话历史记录的右侧,单击![编辑](./pictures/icon-edit.png)即可编辑对话历史记录的名字,如图 14 所示。 + +- 图 14 点击“编辑”图标重命名历史记录 + ![重命名历史记录](./pictures/rename-session.png) + +在对话历史记录名字重新书写完成后,单击右侧![确认](./pictures/icon-confirm.png)即可完成重命名,或者单击右侧![取消](./pictures/icon-cancel.png)放弃本次重命名,如图 15 所示。 + +- 图 15 完成/取消重命名历史记录 + ![完成/取消重命名历史记录](./pictures/rename-session-confirmation.png) + +另外,单击对话历史记录右侧的删除图标,如图 16 所示,即可对删除单条对话历史记录进行二次确认,在二次确认弹出框,如图 17 所示,单击“确认”,可确认删除单条对话历史记录,或者单击“取消”,取消本次删除。 + +- 图 16 点击“垃圾箱”图标删除单条历史记录 + ![删除单条历史记录](./pictures/delete-session.png) + +- 图 17 二次确认后删除历史记录 + ![删除单条历史记录二次确认](./pictures/delete-session-confirmation.png) + +### 对话历史记录批量删除 + +首先单击“批量删除”,如图 18 所示。 + +- 图 18 批量删除功能在历史记录搜索框右上方 + ![批量删除](./pictures/bulk-delete.png) + +然后可对历史记录进行选择删除,如图 19 所示。单击“全选”,即对所有历史记录选中,单击单条历史记录或历史记录左侧的选择框,可对单条历史记录进行选中。 + +- 图 19 在左侧勾选要批量删除历史记录 + ![批量删除历史记录选择](./pictures/bulk-delete-multi-select.png) + +最后需要对批量删除历史记录进行二次确认,如图 20 所示,单击“确认”,即删除,单击“取消”,即取消本次删除。 + +- 图 20 二次确认后删除所选的历史记录 + ![批量删除二次确认](./pictures/bulk-delete-confirmation.png) + +## 反馈与举报 + +在对话记录区,对话回答的右下侧,可进行对话回答反馈,如图 21 所示,单击![满意](./pictures/icon-thumb-up.png),可给对话回答点赞;单击![不满意](./pictures/icon-thumb-down.png),可以给对话回答反馈不满意的原因。 + +- 图 21 点赞和不满意反馈 + ![点赞和不满意反馈](./pictures/feedback.png) + +对于反馈不满意原因,如图 22 所示,在单击![不满意](./pictures/icon-thumb-down.png)之后,对话机器人会展示反馈内容填写的对话框,可选择相关的不满意原因的选项。 + +- 图 22 回答不满意反馈 + ![回答不满意反馈](./pictures/feedback-illegal.png) + +其中单击选择“存在错误信息”,需要填写参考答案链接和描述,如图 23 所示。 + +- 图 23 回答不满意反馈——存在错误信息 + ![回答不满意反馈——存在错误信息](./pictures/feedback-misinfo.png) + +### 举报 + +如果发现 AI 返回的内容中有违规信息,可以点击右下角按钮举报,如图 24 所示。点击举报后选择举报类型并提交,若没有合适的选项,请选择“其他”并输入原因,如图 25 所示。 + +- 图 24 举报按钮在对话块的右下角 + ![举报1](./pictures/report.png) + +- 图 25 点击后可选择举报类型 + ![举报2](./pictures/report-options.png) + +## 查看服务协议和隐私政策 + +单击文字“服务协议”,即可查看服务协议,单击文字“隐私政策”,即可查看隐私政策,如图 26、图 27 所示。 + +- 图 26 服务协议和隐私政策入口在页面底部信息栏 + ![服务协议和隐私政策入口](./pictures/privacy-policy-entry.png) + +- 图 27 点击后显示服务协议或隐私政策弹窗 + ![服务协议和隐私政策](./pictures/privacy-policy.png) + +## 附录 + +### 用户信息导出说明 + +EulerCopilot 后台存在用户信息导出功能,如用户需要,需主动通过 邮箱联系我们,运维会将导出的用户信息通过邮箱回送给用户。 diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/registration_and_login.md b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/registration_and_login.md new file mode 100644 index 0000000000000000000000000000000000000000..8f5cd63781e1f320af33ffe4baf276f65f4696fa --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_assistant/quick_start/smart_web/user_guide/registration_and_login.md @@ -0,0 +1,60 @@ +# 登录 EulerCopilot + +本章节介绍如何登录已部署的 EulerCopilot 网页端的具体步骤。 + +## 浏览器要求 + +浏览器要求如表 1 所示。 + +- 表 1 浏览器要求 + +| 浏览器类型 | 最低版本 | 推荐版本 | +| ----- | ----- | ----- | +| Google Chrome | 72 | 121 或更高版本 | +| Mozilla Firefox | 89 | 122 或更高版本 | +| Apple Safari | 11.0 | 16.3 或更高版本 | + +## 操作步骤 + +**步骤1** 打开本地 PC 机的浏览器,在地址栏输入部署指南中配置好的域名,按下 `Enter`。在未登录状态,进入 EulerCopilot,会出现登录提示弹出框,如图 1 所示。 + +- 图 1 未登录 + +![未登录](./pictures/login-popup.png) + +**步骤2** 登录 EulerCopilot(已注册账号)。 + +打开登录界面,如图 2 所示。 + +- 图 2 登录 EulerCopilot + +![登录 EulerCopilot](./pictures/authhub-login.png) + +## 注册 EulerCopilot 账号 + +**步骤1** 在登录信息输入框右下角单击“立即注册”,如图 3 所示。 + +- 图 3 立即注册 + +![立即注册](./pictures/authhub-login-click2signup.png) + +**步骤2** 进入账号注册页,根据页面提示填写相关内容,如图 4 所示。 + +- 图 4 账号注册 + +![账号注册](./pictures/authhub-signup.png) + +**步骤3** 按页面要求填写账号信息后,单击“注册”,即可注册成功。注册后即可返回登录。 + +## 退出登录 + +**步骤1** 单击![退出登录](./pictures/icon-user.png)后,会出现“退出登录”下拉框,如图 5 所示。 + +> **说明** +> 账号管理区位于页面的右上角部分,如图 5 所示。 + +- 图 5 账号管理区 + +![账号管理区](./pictures/logout.png) + +**步骤2** 单击“退出登录”即可退出登录,如图 5 所示。 diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/_toc.yaml b/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/_toc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b1a4ea7f0bc6c9169f6c9cc1ab084ad03074091b --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/_toc.yaml @@ -0,0 +1,6 @@ +label: 智能化漏洞修补用户指南 +isManual: true +description: 支持kernel仓库的漏洞修补 +sections: + - label: 智能化漏洞修补用户指南 + href: ./intelligent-vulnerability-patching-user-guide.md diff --git a/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/intelligent-vulnerability-patching-user-guide.md b/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/intelligent-vulnerability-patching-user-guide.md new file mode 100644 index 0000000000000000000000000000000000000000..b19e2c0025cf962506a663c72c6c484b67944a69 --- /dev/null +++ b/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/intelligent-vulnerability-patching-user-guide.md @@ -0,0 +1,58 @@ +# openEuler 智能化漏洞修补用户指南 + +## 简介 + +智能化漏洞修补提供了对openEuler的kernel仓库([https://gitee.com/openeuler/kernel](https://gitee.com/openeuler/kernel))进行智能化修补的能力,当前提供了CVE影响范围分析和补丁PR创建的功能。在代表CVE的issue下面评论/analyze和/create_pr命令来执行功能。 + +## 功能入口 + +在src-openEuler的kernel仓库([https://gitee.com/src-openeuler/kernel.git](https://gitee.com/src-openeuler/kernel.git))中,对代表CVE的issue下面进行评论。 + +![CVE截图](pictures/代表CVE的issue.png) + +## /analyze命令 + +`/analyze`命令提供了对CVE影响范围进行分析的能力。通过在issue下面评论`/analyze`,即可自动对当前维护范围内的openEuler版本进行分析,判断每一个openEuler版本是否引入该CVE,是否修复该CVE。 + +![/analyze命令](pictures/analyze命令.png) + +> [!NOTE]说明 +> /analyze命令无参数 + +CVE是否引入存在如下几种情况: + +* 无影响 +* 受影响 + +CVE是否修复存在如下几种情况: + +* 未修复 +* 已修复 + +在评论的最后,会贴上引入补丁链接与修复补丁链接。 + +## /create_pr命令 + +`/create_pr`命令提供了对CVE的补丁进行智能化修复的能力。通过在issue下面评论`/create_pr `,即可自动获得漏洞补丁,并通过创建PR来合入openEuler下的linux仓库([https://gitee.com/openeuler/kernel.git](https://gitee.com/openeuler/kernel.git))中。 +![/create_pr命令](pictures/create_pr命令.png) + +`/create_pr`命令存在参数,包括如下几种情况: + +```shell +# 对OLK-5.10分支创建补丁PR +/create_pr OLK-5.10 + +# 对OLK-5.10、OLK-6.6分支创建PR +/create_pr OLK-5.10 OLK-6.6 + +# 对当前所有的上游分支,包括openEuler-1.0-LTS、OLK-5.10、OLK-6.6三个分支 +/create_pr +``` + +返回结果如下: + +* pr创建成功 +* 没有修复补丁 +* 无法修复,存在冲突 + +如果补丁代码与修复分支存在冲突的话,会提示`无法修复,存在冲突`。该能力将会在后续的版本中进行迭代强化。 diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/pictures/analyze\345\221\275\344\273\244.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/pictures/analyze\345\221\275\344\273\244.png" new file mode 100644 index 0000000000000000000000000000000000000000..1cc921ba038a6a7bde20365c3bcae918ba98eb73 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/pictures/analyze\345\221\275\344\273\244.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/pictures/create_pr\345\221\275\344\273\244.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/pictures/create_pr\345\221\275\344\273\244.png" new file mode 100644 index 0000000000000000000000000000000000000000..0225b28f09084750871dac6f707a1dd74fb25e20 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/pictures/create_pr\345\221\275\344\273\244.png" differ diff --git "a/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/pictures/\344\273\243\350\241\250CVE\347\232\204issue.png" "b/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/pictures/\344\273\243\350\241\250CVE\347\232\204issue.png" new file mode 100644 index 0000000000000000000000000000000000000000..da36c3eb8486d8e9926841c7a9ddb6c7c75d5b22 Binary files /dev/null and "b/docs_for_openEuler/openEuler_intelligence/intelligent_vulnerability_patching/pictures/\344\273\243\350\241\250CVE\347\232\204issue.png" differ diff --git a/manual/.gitignore b/manual/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d16386367f7cd7dd3c1842c484239e9e82a25efc --- /dev/null +++ b/manual/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/manual/Makefile b/manual/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d0c3cbf1020d5c292abdedf27627c6abe25e2293 --- /dev/null +++ b/manual/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/manual/make.bat b/manual/make.bat new file mode 100644 index 0000000000000000000000000000000000000000..dc1312ab09ca6fb0267dee6b28a38e69c253631a --- /dev/null +++ b/manual/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/manual/source/call/core.rst b/manual/source/call/core.rst new file mode 100644 index 0000000000000000000000000000000000000000..3f9c339c9fd0180b6ff83042047eca859a3aaa71 --- /dev/null +++ b/manual/source/call/core.rst @@ -0,0 +1,9 @@ +#### +基类 +#### + +.. automodule:: apps.scheduler.call.core + :members: + :undoc-members: + :show-inheritance: + :private-members: diff --git a/manual/source/call/empty.rst b/manual/source/call/empty.rst new file mode 100644 index 0000000000000000000000000000000000000000..7e9899b2298c8c18f36332371de3a36b8ebf5563 --- /dev/null +++ b/manual/source/call/empty.rst @@ -0,0 +1,22 @@ +###### +空节点 +###### + + +- 模块位置:``apps.scheduler.call.empty`` +- 模块名称:空白 +- 模块描述:无任何实际流程的工具,用于占位 +- 模块功能:无 +- 是否展示在前端:否 +- 是否存在参数重载:否 + + +**** +逻辑 +**** + +.. automodule:: apps.scheduler.call.empty + :members: + :private-members: + :undoc-members: + :show-inheritance: diff --git a/manual/source/call/index.rst b/manual/source/call/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..65484d2c9e3f2a93296822d04150f818a5f1bebd --- /dev/null +++ b/manual/source/call/index.rst @@ -0,0 +1,17 @@ +#### +工具 +#### + +工具(即 ``apps.scheduler.call`` )定义了工作流中节点的相关逻辑。 + +每一个子文件夹都对应了一个工具种类,均为无状态的Python程序代码。 + +并不是每一个工具都会以节点的形式展示在前端侧边栏内。如 ``slot`` 等工具是系统内部依工作流情况自动使用的工具,用户无法手动调用。 + + +.. toctree:: + :maxdepth: 2 + + core + mcp + empty diff --git a/manual/source/call/mcp.rst b/manual/source/call/mcp.rst new file mode 100644 index 0000000000000000000000000000000000000000..383bc4677c1bf0bd35ca2ebdb25f8e477dae79b7 --- /dev/null +++ b/manual/source/call/mcp.rst @@ -0,0 +1,31 @@ +####### +MCP节点 +####### + +- 模块位置:``apps.scheduler.call.mcp`` +- 模块名称:MCP +- 模块描述:调用MCP Server,执行工具 +- 模块功能: + - 调用特定MCP Server中的特定Tool +- 是否展示在前端:是 +- 是否存在参数重载:否 + +**** +逻辑 +**** + +.. automodule:: apps.scheduler.call.mcp.mcp + :members: + :private-members: + :undoc-members: + :show-inheritance: + +******** +输入输出 +******** + +.. automodule:: apps.scheduler.call.mcp.schema + :members: + :private-members: + :undoc-members: + :show-inheritance: diff --git a/manual/source/conf.py b/manual/source/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..2085545c39db2095d37bc7e6aff5d2325a1b7186 --- /dev/null +++ b/manual/source/conf.py @@ -0,0 +1,28 @@ +"""Sphinx configuration file for the openEuler Intelligence Framework.""" + +import sys +from pathlib import Path + +project = "openEuler Intelligence Framework" +copyright = "2025, Huawei Technologies Co., Ltd." +author = "sig-intelligence" +release = "0.9.6" + +extensions = [ + "sphinx.ext.autodoc", + "sphinxcontrib.autodoc_pydantic", +] + +templates_path = ["_templates"] +exclude_patterns = [] + +language = "zh_CN" +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] + +autodoc_pydantic_model_show_config_summary = True +autodoc_pydantic_model_show_json = False +autodoc_pydantic_model_show_field_summary = True +autodoc_pydantic_field_show_default = True + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) diff --git a/manual/source/index.rst b/manual/source/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..676840dd65d9b3ca0b4a522b405073e65e50e190 --- /dev/null +++ b/manual/source/index.rst @@ -0,0 +1,10 @@ +##################################### +openEuler Intelligence Framework 文档 +##################################### + +.. toctree:: + :maxdepth: 2 + + pool/index + models/index + call/index diff --git a/manual/source/models/index.rst b/manual/source/models/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..d93bd1392c71a6ac88e2e7283729626b739150eb --- /dev/null +++ b/manual/source/models/index.rst @@ -0,0 +1,14 @@ +数据库读写模块 +=============== + +``apps.models`` 模块提供了数据库的读写功能,包括 LanceDB 数据库的读写、MongoDB 数据库的读写。 + +这一模块只包含了连接数据库和操作数据库的底层逻辑,不包含任何业务逻辑,也不包含数据库中数据的结构定义。 + + + +.. toctree:: + :maxdepth: 2 + + lance + mongo diff --git a/manual/source/models/lance.rst b/manual/source/models/lance.rst new file mode 100644 index 0000000000000000000000000000000000000000..43bf10a44df200104b8fa0ae6c9a9fd6ce88ec8a --- /dev/null +++ b/manual/source/models/lance.rst @@ -0,0 +1,21 @@ +############## +LanceDB 数据库 +############## + +LanceDB 是一个基于矢量数据库的存储系统,用于存储和检索向量化的数据。 + +LanceDB 的详细介绍请参考 `LanceDB 官方文档 `__ ,其特性如下: + +- Serverless,无需安装和部署服务 +- 支持大规模数据存储和检索 + + +************* +LanceDB连接器 +************* + +.. autoclass:: apps.models.lance.LanceDB + :members: + :undoc-members: + :private-members: + :inherited-members: diff --git a/manual/source/models/mongo.rst b/manual/source/models/mongo.rst new file mode 100644 index 0000000000000000000000000000000000000000..f26bd865c221b7e9d96b93292e9c5bbd7f3c98b2 --- /dev/null +++ b/manual/source/models/mongo.rst @@ -0,0 +1,25 @@ +############## +MongoDB 数据库 +############## + +MongoDB是一个NoSQL数据库,以集合(``collection``)和文档(``document``)的形式存储和查询数据。 + +MongoDB有诸多优秀的特性,例如: + +- 轻量化,占用系统资源较少 +- 文档实际上就是JSON,可以动态增删字段 +- 对文档的读写等操作始终原子化 +- 支持丰富多样的查询方式 +- 支持像Redis一样设置数据的过期时间 +- 支持 ``replicaSet`` 模式,可以实现高可用,有一定的事务能力 + + +************** +MongoDB 连接器 +************** + +.. autoclass:: apps.models.mongo.MongoDB + :members: + :undoc-members: + :private-members: + :inherited-members: diff --git a/manual/source/pool/index.rst b/manual/source/pool/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..02ea7573e34d59367ef1c7c5305c9e83193fe497 --- /dev/null +++ b/manual/source/pool/index.rst @@ -0,0 +1,14 @@ +###### +载入器 +###### + +载入器(即 ``apps.scheduler.pool.loader`` 模块)是用于从文件系统中加载资源,并将内存中的资源保存在文件系统中的模块。 + +载入器一般会在Framework启动时执行初始化动作。只有初始化全部结束后,Framework才会开始接受请求。 + + +.. toctree:: + :maxdepth: 2 + + mcp + pool diff --git a/manual/source/pool/mcp.rst b/manual/source/pool/mcp.rst new file mode 100644 index 0000000000000000000000000000000000000000..0d0579584d8c24df057c7e2d9e345d53daed24fc --- /dev/null +++ b/manual/source/pool/mcp.rst @@ -0,0 +1,66 @@ +########### +MCP载入器 +########### + +MCP载入器(Loader)根据MCP配置文件(一般为 ``.json`` 格式),自动(或手动)创建MCP Server的运行环境,并启动MCP进程。 + +******** +数据结构 +******** + +.. automodule:: apps.entities.mcp + :members: + :undoc-members: + :show-inheritance: + + +****** +客户端 +****** + +客户端是直接与MCP Server进行交互的模块,负责发送请求和接收响应。 + + +客户端类 +======== + +.. automodule:: apps.scheduler.pool.mcp.client + :members: + :private-members: + :undoc-members: + :show-inheritance: + + +客户端默认值 +============ + +.. automodule:: apps.scheduler.pool.mcp.default + :members: + :private-members: + :undoc-members: + :show-inheritance: + + +客户端自动安装 +============== + +目前仅支持自动安装使用 ``uvx`` 和 ``npx`` 启动的MCP Server。 + +.. automodule:: apps.scheduler.pool.mcp.install + :members: + :private-members: + :undoc-members: + :show-inheritance: + + +****** +加载器 +****** + +加载器是负责加载MCP配置文件并更新数据库条目的模块。 + +.. autoclass:: apps.scheduler.pool.loader.mcp.MCPLoader + :members: + :undoc-members: + :private-members: + :show-inheritance: diff --git a/manual/source/pool/pool.rst b/manual/source/pool/pool.rst new file mode 100644 index 0000000000000000000000000000000000000000..314642d64145881113520a1ac116792557500d31 --- /dev/null +++ b/manual/source/pool/pool.rst @@ -0,0 +1,9 @@ +###### +资源池 +###### + +.. autoclass:: apps.scheduler.pool.pool.Pool + :members: + :undoc-members: + :private-members: + :inherited-members: diff --git a/mock/make_data.py b/mock/make_data.py deleted file mode 100644 index a84c1203503cb491826f886eed0a9edc613abbe3..0000000000000000000000000000000000000000 --- a/mock/make_data.py +++ /dev/null @@ -1,716 +0,0 @@ -import asyncio -import urllib.parse -import uuid -from datetime import datetime, timezone -from enum import Enum -from typing import Any, Optional - -from pydantic import BaseModel, Field -from pymongo import MongoClient -from pymongo.errors import PyMongoError - -# 假设 PermissionType 已经在别处定义 - -class PermissionType(str, Enum): - PRIVATE = "private" - PUBLIC = "public" - - -# 定义模型类 - -class PoolBase(BaseModel): - id: str = Field(alias="_id") - name: str - description: str - created_at: float = Field(default_factory=lambda: round(datetime.now(tz=timezone.utc).timestamp(), 3)) - - -class ServiceApiInfo(BaseModel): - filename: str - description: str - path: str - - -class Permission(BaseModel): - type: PermissionType = Field(description="权限类型", default=PermissionType.PRIVATE) - users: list[str] = Field(description="可访问的用户列表", default=[]) - - -class ServicePool(PoolBase): - """外部服务信息 - - collection: service - """ - - author: str = Field(description="作者的用户ID") - permission: Permission = Field(description="服务可见性配置", default=Permission()) - favorites: list[str] = Field(description="收藏此服务的用户列表", default=[]) - openapi_hash: str = Field(description="服务关联的 OpenAPI YAML 文件哈希") - openapi_spec: dict = Field(description="服务关联的 OpenAPI 文件内容") - - -# MongoDB配置 -config = { - 'MONGODB_USER': '', - 'MONGODB_PWD': '', - 'MONGODB_HOST': '', - 'MONGODB_PORT': '', - 'MONGODB_DATABASE': '' -} - - - -class MongoDB: - _client = MongoClient( - f"mongodb://{urllib.parse.quote_plus(config['MONGODB_USER'])}:{urllib.parse.quote_plus(config['MONGODB_PWD'])}@{config['MONGODB_HOST']}:{config['MONGODB_PORT']}/?directConnection=true", - ) - - @classmethod - def get_collection(cls, collection_name: str): - try: - return cls._client[config["MONGODB_DATABASE"]][collection_name] - except Exception as e: - print(f"Get collection {collection_name} failed: {e}") - raise RuntimeError(str(e)) from e - - -async def insert_service_pool(): - # 示例数据 - sys_id = "6a08c845-abdc-45fb-853e-54a806437dab" - service_pool_sys = ServicePool( - _id=sys_id, - name="系统", - description="系统函数", - author="system", - permission=Permission(type=PermissionType.PUBLIC, users=[]), - favorites=[], - openapi_hash="hash1", - openapi_spec={}, - ) - aops_id = "1137ab09-20ae-4278-8346-524d4ce81d2f" - service_pool_a_ops = ServicePool( - _id=aops_id, - name="aops-apollo", - description="a-ops下cve相关组件", - author="test", - permission=Permission(type=PermissionType.PUBLIC, users=[]), - favorites=[], - openapi_hash="aops-apollo-hash", - openapi_spec={}, - ) - """插入ServicePool实例到MongoDB""" - collection = MongoDB.get_collection("service") - result = collection.delete_many({}) - # 将Pydantic模型转换为字典并插入到MongoDB中 - try: - result = collection.update_one( - {"_id": service_pool_sys.id}, # 查找条件 - {"$set": service_pool_sys.model_dump(by_alias=True)}, # 更新操作 - upsert=True, # 如果不存在则插入新文档 - ) - result = collection.update_one( - {"_id": service_pool_a_ops.id}, # 查找条件 - {"$set": service_pool_a_ops.model_dump(by_alias=True)}, # 更新操作 - upsert=True, # 如果不存在则插入新文档 - ) - print(f"Inserted document with id: {result.upserted_id}") - except PyMongoError as e: - print(f"An error occurred while inserting the document: {e}") - - -class Node(PoolBase): - """Node信息 - - collection: node - 注: - 1. 基类Call的ID,即meta_call,可以为None,表示该Node是系统Node - 2. 路径的格式: - 1. 系统Node的路径格式样例:“LLM” - 2. Python Node的路径格式样例:“tune::call.tune.CheckSystem” - """ - - id: str = Field(description="Node的ID", default_factory=lambda: str(uuid.uuid4()), alias="_id") - service_id: str = Field(description="Node所属的Service ID") - call_id: str = Field(description="所使用的Call的ID") - api_path: Optional[str] = Field(description="Call的API路径", default=None) - params_schema: dict[str, Any] = Field(description="Node的参数schema;只包含用户可以改变的参数", default={}) - output_schema: dict[str, Any] = Field(description="Node的输出schema;做输出的展示用", default={}) - - -async def insert_node_pool() -> None: - collection = MongoDB.get_collection("node") - result = collection.delete_many({}) # 清空集合中的所有文档(仅用于演示) - node_pools = [ - Node( - _id=str(uuid.uuid4()), # 自动生成一个唯一的 ID - service_id="6a08c845-abdc-45fb-853e-54a806437dab", # 使用 "test" 作为 service_id - call_id="knowledge_base", # 随机生成一个 call_id - name="【KNOWLEDGE】知识库", # 提供名称 - description="支持知识库中文档的查询", # 提供描述 - params_schema={ - "search_methods": [], - "rerank_methods": [], - "konwledge_base_id": "", - "query": "", - "top_k": 0, - }, - output_schema={"content": {"type": "string", "description": "回答"}}, - ), - Node( - _id=str(uuid.uuid4()), # 自动生成一个唯一的 ID - service_id="6a08c845-abdc-45fb-853e-54a806437dab", # 使用 "test" 作为 service_id - call_id="LLM", # 随机生成一个 call_id - name="【LLM】大模型", # 提供名称 - description="大模型调用", # 提供描述 - params_schema={ - "base_url": "", - "api_key": "", - "max_tokens": 0, - "is_stream": True, - "prompt": "", - "temperature": 0, - }, - output_schema={"content": {}}, - ), - Node( - _id=str(uuid.uuid4()), # 自动生成一个唯一的 ID - service_id="6a08c845-abdc-45fb-853e-54a806437dab", # 使用 "test" 作为 service_id - call_id="choice", # 随机生成一个 call_id - name="【LLM】意图识别", # 提供名称 - description="利用大模型能力选择分支", # 提供描述 - params_schema={ - "choices": [ - { - "branchId": "source_a", - "description": "IF A", - "purpose": "", - "variable_a": "", - }, - { - "branchId": "source_b", - "description": "ELSE B", - }, - ], - }, - output_schema={ - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "回答", - }, - }, - }, - ), - Node( - _id=str(uuid.uuid4()), # 自动生成一个唯一的 ID - service_id="6a08c845-abdc-45fb-853e-54a806437dab", # 使用 "test" 作为 service_id - call_id="choice", # 随机生成一个 call_id - name="【CHOICE】条件分支", # 提供名称 - description="条件分支节点", # 提供描述 - params_schema={ - "choices": [ - { - "branchId": "source_a", - "description": "IF A", - "operator": "", - "variable_a": "", - "variable_b": "", - }, - { - "branchId": "source_b", - "description": "ELSE B", - }, - ], - }, - output_schema={}, - ), - Node( - _id=str(uuid.uuid4()), # 自动生成一个唯一的 ID - service_id="6a08c845-abdc-45fb-853e-54a806437dab", # 使用 "test" 作为 service_id - call_id="loop_begin", # 随机生成一个 call_id - name="【LOOP】循环开始节点", # 提供名称 - description="", # 提供描述 - params_schema={"operation_exp": {}}, - output_schema={}, - ), - Node( - _id=str(uuid.uuid4()), # 自动生成一个唯一的 ID - service_id="6a08c845-abdc-45fb-853e-54a806437dab", # 使用 "test" 作为 service_id - call_id="loop_begin", # 随机生成一个 call_id - name="【LOOP】循环结束节点", # 提供名称 - description="", # 提供描述 - params_schema={"operation_exp": {}}, - output_schema={}, - ), - Node( - _id=str(uuid.uuid4()), # 自动生成一个唯一的 ID - service_id="6a08c845-abdc-45fb-853e-54a806437dab", # 使用 "test" 作为 service_id - call_id="template_exchange", # 随机生成一个 call_id - name="【LLM】模板转换", # 提供名称 - description="This is an example node pool for demonstration purposes.", # 提供描述 - params_schema={"input_schema": {}, "exchange_rule": [{}]}, - output_schema={ - "type": "object", - "properties": { - "output_schema": { - "type": "object", - "description": "嵌套字典结构", - "properties": { - "content": { - "type": "string", - "description": "回答", - }, - "task_id": { - "type": "string", - "description": "任务ID", - }, - }, - }, - }, - }, - ), - Node( - _id="343da7db-5da8-42ef-9b59-cc56df54d9aa", - service_id="1137ab09-20ae-4278-8346-524d4ce81d2f", - call_id="api", - name="【API】获取任务简介", - description="调用接口,获取已知的任务列表与任务的基本信息(名称、创建时间、任务类型等)", - params_schema={ - "full_url": "https://a-ops3.local/vulnerabilities/task/list/get", - "service_id": "aops-apollo", - "method": "post", - "input_data": { - "page": 1, - "page_size": 10, - "filter": { - "cluster_list": [], - }, - }, - "timeout": 300, - "output_key": [ - { - "key": "data.result", - "path": "task_list", - }, - ], - }, - output_schema={ - "type": "object", - "properties": { - "status_code": { - "type": "integer", - "description": "HTTP状态码", - }, - "data": { - "type": "object", - "description": "接口的返回数据", - "properties": { - "task_list": { - "type": "array", - "description": "任务列表", - "items": { - "type": "object", - "properties": { - "task_id": { - "type": "string", - "description": "任务ID", - }, - "task_name": { - "type": "string", - "description": "任务名称", - }, - "task_type": { - "type": "string", - "description": "任务类型", - "enum": ["cve_scan", "cve_fix"], - }, - }, - }, - }, - }, - }, - }, - }, - ), - Node( - _id="8841e328-da5b-45c7-8839-5b8054a92de7", - service_id="1137ab09-20ae-4278-8346-524d4ce81d2f", - call_id="choice", - name="【CHOICE】判断任务类型", - description="调用意图识别工具,判断任务列表中的第一个任务的类型", - params_schema={ - "choices": [ - { - "branchId": "is_scan", - "description": '任务类型为"CVE修复任务"', - "propose": '当值为cve_scan时,任务类型为"CVE修复任务",选择此分支', - "variable_a": "{{input.task_list[0].task_type}}", - }, - { - "branchId": "is_fix", - "description": '任务类型为"CVE修复任务"', - "propose": '当值为cve_fix时,任务类型为"CVE修复任务",选择此分支', - "variable_a": "{{input.task_list[0].task_type}}", - }, - ], - }, - output_schema={ - "type": "object", - "properties": {}, - }, - ), - Node( - _id="7377ad0d-f867-46fe-806a-d0c4535d2f1a", - service_id="1137ab09-20ae-4278-8346-524d4ce81d2f", - call_id="api", - name="【API】获取CVE修复的结果", - description="调用接口,获取特定CVE修复任务的最终结果", - params_schema={ - "full_url": "https://a-ops3.local/vulnerabilities/task/cve_fix/result/get", - "service_id": "aops-apollo", - "method": "post", - "input_data": {}, - "timeout": 300, - "output_key": [ - { - "key": "data", - "path": "result", - }, - ], - }, - output_schema={ - "type": "object", - "description": "API的返回信息", - "properties": { - "status_code": {"type": "integer", "description": "HTTP状态码"}, - "data": { - "type": "object", - "description": "接口的返回数据", - "properties": { - "result": { - "type": "object", - "properties": { - "last_execute_time": {"type": "integer"}, - "task_type": {"type": "string"}, - "task_result": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "timed": {"type": "boolean"}, - "rpms": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "avaliable_rpm": {"type": "string"}, - "result": {"type": "string"}, - }, - "required": ["avaliable_rpm", "result"], - }, - ], - }, - }, - "required": ["timed", "rpms"], - }, - ], - }, - }, - "required": ["last_execute_time", "task_type", "task_result"], - }, - }, - "required": ["result"], - }, - }, - }, - ), - Node( - _id="3d94b288-a0df-4717-b75c-fc2c67e24294", - service_id="1137ab09-20ae-4278-8346-524d4ce81d2f", - call_id="api", - name="【API】获取漏洞扫描结果", - description="调用接口,获取漏洞扫描结果", - params_schema={ - "full_url": "https://a-ops3.local/vulnerabilities/task/cve_scan/result/get", - "service_id": "aops-apollo", - "method": "post", - "input_data": {}, - "timeout": 300, - "output_key": [{"key": "data", "path": "result"}], - }, - output_schema={ - "type": "object", - "description": "API的返回信息", - "properties": { - "status_code": {"type": "integer", "description": "HTTP状态码"}, - "data": { - "type": "object", - "description": "接口的返回数据", - "properties": { - "result": { - "type": "object", - "properties": { - "last_execute_time": {"type": "integer"}, - "task_type": {"type": "string"}, - "task_result": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "timed": {"type": "boolean"}, - "cve_list": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "cve_id": {"type": "string"}, - "cve_description": {"type": "string"}, - "rpms": { - "type": "array", - "items": [{"type": "string"}], - }, - "severity": {"type": "string"}, - "cvss_score": {"type": "number"}, - }, - "required": [ - "cve_id", - "cve_description", - "rpms", - "severity", - "cvss_score", - ], - }, - ], - }, - }, - "required": ["timed", "cve_list"], - }, - ], - }, - }, - "required": ["last_execute_time", "task_type", "task_result"], - }, - }, - "required": ["result"], - }, - }, - "required": ["status_code", "data"], - }, - ), - Node( - _id="1a8ddfb9-c894-4819-ab9b-88fcb5f14c10", - service_id="1137ab09-20ae-4278-8346-524d4ce81d2f", - call_id="llm", - name="【LLM】生成报告", - description="调用 LLM 生成报告", - params_schema={ - "system_prompt": "你是一个专业的安全专家,擅长生成漏洞相关的分析报告。", - "user_prompt": "请根据历史对话(包括对话中AI助手的回答,和工具的输出),\ -生成一份漏洞相关任务执行的结果报告。\n\n要求如下:\n\ -1. 报告需要包含任务的详细信息,包括任务的名称、创建时间、任务类型等。\n\ -2. 报告需要以“CVE任务执行报告”为标题,且不得超过2000字。\n", - "temperature": 0.7, - "max_tokens": 2048, - }, - output_schema={ - "type": "object", - "properties": {"message": {"type": "string", "description": "大模型的输出内容"}}, - }, - ), - ] - collection = MongoDB.get_collection("node") - result = collection.delete_many({}) - # 将 NodePool 模型转换为字典并插入到 MongoDB 中 - try: - import time - - for node_pool in node_pools: - time.sleep(1) - print(node_pool.service_id) - result = collection.update_one( - {"_id": node_pool.id}, # 查找条件,这里假设 _id 即为 node_pool.id - {"$set": node_pool.dict(by_alias=True)}, # 更新操作 - upsert=True, # 如果不存在则插入新文档 - ) - print("updata success") - print(result.upserted_id) - except PyMongoError as e: - print(f"An error occurred while inserting the document: {e}") - raise # 或者根据需要选择是否重新抛出异常 - - -class PositionItem(BaseModel): - """请求/响应中的前端相对位置变量类""" - - x: float - y: float - - -class AppFlow(PoolBase): - """Flow的元数据;会被存储在App下面""" - - enabled: bool = Field(description="是否启用", default=True) - path: str = Field(description="Flow的路径") - focus_point: PositionItem = Field(description="Flow的视觉焦点", default=PositionItem(x=0, y=0)) - - -class AppLink(BaseModel): - """App的相关链接""" - - title: str = Field(description="链接标题") - url: str = Field(..., description="链接地址") - - -class AppPool(PoolBase): - """应用信息 - - collection: app - """ - - author: str = Field(description="作者的用户ID") - type: str = Field(description="应用类型", default="default") - icon: str = Field(description="应用图标") - published: bool = Field(description="是否发布", default=False) - links: list[AppLink] = Field(description="相关链接", default=[]) - first_questions: list[str] = Field(description="推荐问题", default=[]) - history_len: int = Field(3, ge=1, le=10, description="对话轮次(1~10)") - permission: Permission = Field( - description="应用权限配置", default=Permission(type=PermissionType.PRIVATE, users=[]), - ) - flows: list[AppFlow] = Field(description="Flow列表", default=[]) - favorites: list[str] = Field(description="收藏此应用的用户列表", default=[]) - hashes: dict[str, str] = Field(description="关联文件的hash值", default={}) - - -async def insert_app_pool(): - collection = MongoDB.get_collection("app") - result = collection.delete_many({}) # 清空集合中的所有文档(仅用于演示) - return - app_pool = AppPool( - _id="test", # 自动生成一个唯一的 ID - author="test", # 使用 "author_id" 作为作者ID - name="Example App Pool ", # 提供名称 - description="This is my test ", # 提供描述 - published=False, - icon="icon_url", # 提供图标URL - type="example_type", # 提供应用类型 - links=[AppLink(title="Example Link", url="http://example.com")], # 提供相关链接列表 - first_questions=["What is your name?", "How are you?"], # 提供推荐问题列表 - history_len=5, # 设置对话轮次 - permission=Permission(type=PermissionType.PUBLIC, users=["user1", "user2"]), # 设置权限配置 - flows=[ - AppFlow( - _id="test", # 提供一个唯一的标识符 - name="Main Flow", # 提供一个名称 - description="Description of the main flow", # 提供描述 - path="main_flow", - focus_point=PositionItem(x=0.5, y=0.5), - ) - ], # 添加Flows - favorites=["user3", "user4"], # 收藏此应用的用户列表 - hashes={"file1": "hash1", "file2": "hash2"}, # 关联文件的hash值 - ) - collection.update_one( - {"_id": app_pool.id}, # 查找条件,这里假设 _id 即为 app_pool.id - {"$set": app_pool.dict(by_alias=True)}, # 更新操作 - upsert=True, # 如果不存在则插入新文档 - ) - for i in range(100): - print(i) - app_pool = AppPool( - _id="my_test_" + str(i), # 自动生成一个唯一的 ID - author="test", # 使用 "author_id" 作为作者ID - name="Example App Pool " + str(i), # 提供名称 - description="This is my test " + str(i), # 提供描述 - published=i % 2, - icon="icon_url", # 提供图标URL - type="example_type", # 提供应用类型 - links=[AppLink(title="Example Link", url="http://example.com")], # 提供相关链接列表 - first_questions=["What is your name?", "How are you?"], # 提供推荐问题列表 - history_len=5, # 设置对话轮次 - permission=Permission(type=PermissionType.PUBLIC, users=["user1", "user2"]), # 设置权限配置 - flows=[ - AppFlow( - _id="test", # 提供一个唯一的标识符 - name="Main Flow", # 提供一个名称 - description="Description of the main flow", # 提供描述 - path="main_flow", - focus_point=PositionItem(x=0.5, y=0.5), - ) - ], # 添加Flows - favorites=["user3", "user4"], # 收藏此应用的用户列表 - hashes={"file1": "hash1", "file2": "hash2"}, # 关联文件的hash值 - ) - - # 将 AppPool 模型转换为字典并插入到 MongoDB 中 - try: - result = collection.update_one( - {"_id": app_pool.id}, # 查找条件,这里假设 _id 即为 app_pool.id - {"$set": app_pool.dict(by_alias=True)}, # 更新操作 - upsert=True, # 如果不存在则插入新文档 - ) - print(f"Inserted or updated document with id: {app_pool.id}") - except PyMongoError as e: - print(f"An error occurred while inserting the document: {e}") - raise # 或者根据需要选择是否重新抛出异常 - for i in range(100): - print(i) - app_pool = AppPool( - _id="test_" + str(i), # 自动生成一个唯一的 ID - author="test_" + str(i), # 使用 "author_id" 作为作者ID - name="Example App Pool", # 提供名称 - description="This is test " + str(i), # 提供描述 - published=i % 2, - icon="icon_url", # 提供图标URL - type="example_type", # 提供应用类型 - links=[AppLink(title="Example Link", url="http://example.com")], # 提供相关链接列表 - first_questions=["What is your name?", "How are you?"], # 提供推荐问题列表 - history_len=5, # 设置对话轮次 - permission=Permission(type=PermissionType.PUBLIC, users=["user1", "user2"]), # 设置权限配置 - flows=[ - AppFlow( - _id="test", # 提供一个唯一的标识符 - name="Main Flow", # 提供一个名称 - description="Description of the main flow", # 提供描述 - path="main_flow", - focus_point=PositionItem(x=0.5, y=0.5), - ) - ], # 添加Flows - favorites=["user3", "user4"], # 收藏此应用的用户列表 - hashes={"file1": "hash1", "file2": "hash2"}, # 关联文件的hash值 - ) - - # 将 AppPool 模型转换为字典并插入到 MongoDB 中 - try: - result = collection.update_one( - {"_id": app_pool.id}, # 查找条件,这里假设 _id 即为 app_pool.id - {"$set": app_pool.dict(by_alias=True)}, # 更新操作 - upsert=True, # 如果不存在则插入新文档 - ) - print(f"Inserted or updated document with id: {app_pool.id}") - except PyMongoError as e: - print(f"An error occurred while inserting the document: {e}") - raise # 或者根据需要选择是否重新抛出异常 - - -def query_all_target(tag: str): - """查询所有插入到MongoDB中的node数据""" - collection = MongoDB.get_collection(tag) - try: - nodes = list(collection.find({})) - for node in nodes: - print(node) - except PyMongoError as e: - print(f"An error occurred while querying the documents: {e}") - - -# 使用 asyncio 运行异步函数 -if __name__ == "__main__": - asyncio.run(insert_service_pool()) - asyncio.run(insert_node_pool()) - query_all_target("service") diff --git a/op.conf b/op.conf deleted file mode 100644 index be6200b3306ff60d5139b41a6ffff19f55c66b76..0000000000000000000000000000000000000000 --- a/op.conf +++ /dev/null @@ -1,7 +0,0 @@ -up: git pull -build: - docker build . -t hub.oepkgs.net/neocopilot/framework-dev:`(git rev-parse --short HEAD)` - docker push hub.oepkgs.net/neocopilot/framework-dev:`(git rev-parse --short HEAD)` - docker rmi hub.oepkgs.net/neocopilot/framework-dev:`(git rev-parse --short HEAD)` - docker builder prune -f - kubectl -n euler-copilot set image deployment/framework-deploy framework=hub.oepkgs.net/neocopilot/framework-dev:`(git rev-parse --short HEAD)` diff --git a/plugins/HelloWord/btdl/docker.yaml b/plugins/docker-cmd/btdl/docker.yaml similarity index 100% rename from plugins/HelloWord/btdl/docker.yaml rename to plugins/docker-cmd/btdl/docker.yaml diff --git a/plugins/HelloWord/plugin.json b/plugins/docker-cmd/plugin.json similarity index 100% rename from plugins/HelloWord/plugin.json rename to plugins/docker-cmd/plugin.json diff --git a/pyproject.toml b/pyproject.toml index fc8fb17230e34a4c509d0cc58f18f6e8ccf69302..54f1523c227a1993023d6d5adb9967677ca4134a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,34 +1,35 @@ [project] name = "euler-copilot-framework" -version = "0.9.5" +version = "0.9.6" description = "EulerCopilot 后端服务" requires-python = "==3.11.6" dependencies = [ - "aiofiles>=24.1.0", - "aiohttp>=3.11.14", - "asyncer>=0.0.8", - "cryptography>=44.0.2", - "fastapi>=0.115.12", - "jinja2>=3.1.6", - "jionlp>=1.5.20", - "jsonschema>=4.23.0", - "lancedb>=0.21.2", - "minio>=7.2.15", - "ollama>=0.4.7", - "openai>=1.68.2", - "pandas>=2.2.3", - "pymongo>=4.11.3", - "python-jsonpath>=1.3.0", - "python-magic>=0.4.27", - "python-multipart>=0.0.20", - "pytz>=2025.2", - "pyyaml>=6.0.2", - "referencing>=0.36.2", - "rich>=13.9.4", - "sglang>=0.4.4.post2", - "tiktoken>=0.9.0", - "toml>=0.10.2", - "uvicorn>=0.34.0", + "aiofiles==24.1.0", + "asyncer==0.0.8", + "cryptography==44.0.2", + "fastapi==0.115.12", + "httpx==0.28.1", + "httpx-sse==0.4.0", + "jinja2==3.1.6", + "jionlp==1.5.20", + "jsonschema==4.23.0", + "lancedb==0.21.2", + "mcp==1.8.1", + "minio==7.2.15", + "ollama==0.4.8", + "openai==1.78.1", + "pandas==2.2.3", + "pymongo==4.12.1", + "python-jsonpath==1.3.0", + "python-magic==0.4.27", + "python-multipart==0.0.20", + "pytz==2025.2", + "pyyaml==6.0.2", + "rich==13.9.4", + "sqids==0.5.1", + "tiktoken==0.9.0", + "toml==0.10.2", + "uvicorn==0.34.0", ] [[tool.uv.index]] @@ -37,9 +38,11 @@ url = "https://pypi.tuna.tsinghua.edu.cn/simple" [dependency-groups] dev = [ - "coverage>=7.7.1", - "httpx>=0.28.1", - "pytest>=8.3.5", - "pytest-mock>=3.14.0", - "ruff>=0.11.2", + "autodoc-pydantic==2.2.0", + "coverage==7.7.1", + "pytest==8.3.5", + "pytest-mock==3.14.0", + "ruff==0.11.2", + "sphinx==8.2.3", + "sphinx-rtd-theme==3.0.2", ] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 649bed7ee89f7f6bd9970d515d19ec74e7633f62..0000000000000000000000000000000000000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -testpaths = ./tests -addopts = --cov=tests \ No newline at end of file diff --git a/sample/README.md b/sample/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1c3b941dcc54907c0c41920f99e45f4760b88965 --- /dev/null +++ b/sample/README.md @@ -0,0 +1,25 @@ +该插件包样例为EulerCopilot本地数据文件夹内semantics/子文件夹的目录结构与示例。 + +结构定义如下: + +```text +- semantics/ +| - app/ # 应用相关数据 +| | - test_app/ # 样例应用 +| | | - metadata.yaml # 应用的元数据 +| | | - icon.png # 应用的图标 +| | | - flow/ # 应用中的工作流信息 +| | | | - test.yaml # 样例工作流 +| - call/ # 自定义Python工具相关数据 +| | - __init__.py +| | - test_call/ # 样例用户工具 +| | | - __init__.py +| | | - user_tool.py # Python工具代码 +| - service/ # 语义接口&MCP相关数据(服务) +| | - test_service/ # 样例服务 +| | | - metadata.yaml # 服务的元数据 +| | | - mcp-config.json # MCP服务的配置 +| | | - openapi/ # API相关信息 +| | | | - api.yaml # 样例API文件 +| | | - mcp/ # MCP程序文件夹 +``` \ No newline at end of file diff --git a/sample/README.txt b/sample/README.txt deleted file mode 100644 index d14b16ddad95b163021f63f7bae735497859b6ce..0000000000000000000000000000000000000000 --- a/sample/README.txt +++ /dev/null @@ -1,2 +0,0 @@ -该插件包样例为MinIO内文件的目录树结构, -用户可手动上传或下载MinIO内configs/子路径内的文件,实现批量预载和手动备份 \ No newline at end of file diff --git a/sample/app/test_app/flows/test.yaml b/sample/app/test_app/flow/test.yaml similarity index 100% rename from sample/app/test_app/flows/test.yaml rename to sample/app/test_app/flow/test.yaml diff --git a/sample/app/test_app/icon.ico b/sample/app/test_app/icon.ico deleted file mode 100644 index ba6134a0ab94b8dd83d098e059d3c4dd93dd1041..0000000000000000000000000000000000000000 Binary files a/sample/app/test_app/icon.ico and /dev/null differ diff --git a/sample/app/test_app/icon.png b/sample/app/test_app/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5dd92a34044657568593de6be089b0bdb7128064 Binary files /dev/null and b/sample/app/test_app/icon.png differ diff --git a/sample/mcp/template/test_mcp/config.json b/sample/mcp/template/test_mcp/config.json new file mode 100644 index 0000000000000000000000000000000000000000..079f5e7ab43511e50031dcf8c15183212a53b107 --- /dev/null +++ b/sample/mcp/template/test_mcp/config.json @@ -0,0 +1,20 @@ +{ + "mcpServers": { + "tavily-mcp": { + "name": "Tavily网页搜索", + "description": "调用Tavily API,获取关键词的搜索结果", + "type": "stdio", + "autoInstall": true, + "iconPath": "", + "command": "npx", + "args": [ + "-y", + "tavily-mcp" + ], + "env": { + "TAVILY_API_KEY": "your-api-key-here" + }, + "autoApprove": [] + } + } +} \ No newline at end of file diff --git a/sample/mcp/template/test_mcp/project/package.json b/sample/mcp/template/test_mcp/project/package.json new file mode 100644 index 0000000000000000000000000000000000000000..fa4e283982fbe072dca75296a9ff43743885a59b --- /dev/null +++ b/sample/mcp/template/test_mcp/project/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "tavily-mcp": "^0.1.4" + } +} diff --git a/sample/mcp/users/test_user/test_mcp/config.json b/sample/mcp/users/test_user/test_mcp/config.json new file mode 100644 index 0000000000000000000000000000000000000000..084a09a0528e66646da4909bd668b48cba0ed9a1 --- /dev/null +++ b/sample/mcp/users/test_user/test_mcp/config.json @@ -0,0 +1,17 @@ +{ + "mcpServers": { + "tavily-search": { + "name": "Tavily网页搜索", + "description": "调用Tavily API,进行网页搜索", + "type": "stdio", + "command": "npm", + "args": [ + "exec", + "tavily-mcp" + ], + "env": { + "TAVILY_API_KEY": "sk-xxxx" + } + } + } +} \ No newline at end of file diff --git a/sample/mcp/users/test_user/test_mcp/project/package.json b/sample/mcp/users/test_user/test_mcp/project/package.json new file mode 100644 index 0000000000000000000000000000000000000000..fa4e283982fbe072dca75296a9ff43743885a59b --- /dev/null +++ b/sample/mcp/users/test_user/test_mcp/project/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "tavily-mcp": "^0.1.4" + } +} diff --git a/uv.lock b/uv.lock index 5602c3eb90bf8393d1c77487e211d27a62923951..53b62602bc7713ccfc29cbe5f5e85b6409945b06 100644 --- a/uv.lock +++ b/uv.lock @@ -1,77 +1,32 @@ version = 1 -revision = 1 +revision = 2 requires-python = "==3.11.6" [[package]] name = "aiofiles" version = "24.1.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, ] [[package]] -name = "aiohappyeyeballs" -version = "2.6.1" +name = "alabaster" +version = "1.0.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 }, -] - -[[package]] -name = "aiohttp" -version = "3.11.15" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "yarl" }, -] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/18/31398875dc7b9815767370f60f44284155f2e1c1b87ec721c1b0ee61d1e5/aiohttp-3.11.15.tar.gz", hash = "sha256:b9b9a1e592ac8fcc4584baea240e41f77415e0de98932fdf19565aa3b6a02d0b", size = 7676625 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/31/5c8552b13041decee3facbe4a8e6342e9227cc472d7835df7f148049550a/aiohttp-3.11.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5bd37d615cd26d09321bd0168305f8508778712cf38aeffeed550274fb48a2ee", size = 708642 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/7f/4bf68f9eaabe8f06917832cd3828636346d6104454af8d5bb420bdc2876c/aiohttp-3.11.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d706afcc808f6add4208dfa13f911fd93c2a3dab6be484fee4fd0602a0867e", size = 468927 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/e5/7d931db76bf66f58853cb1d3e973c8a52fc4ba3832355949d2e8acdddef5/aiohttp-3.11.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43625253e3dc018d34867b70909149f15f29eac0382802afe027f2fbf17bcb9c", size = 456037 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/79/e463bcdcab7a5538211c4ef2bdadf3d31541d5bc21d09c93dc806c2978ec/aiohttp-3.11.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:437eee9e057a7907b11e4af2b18df56b6c795b28e0a3ac250691936cf6bf40eb", size = 1687945 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/a1/bcb81ee3c6231c440947bf30cad8846158a6da24987889a9a309c47a3a73/aiohttp-3.11.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec3dd04138bd30e6a3403dbd3ab5a5ccfb501597c5a95196cd816936ed55b777", size = 1752679 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/14/febdf2ccedf88633c7638d5ef8909f7cc86fbc35a2e816c7ae3a6c8543a6/aiohttp-3.11.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85d73479b79172e7d667b466c170ca6097a334c09ecd83c95c210546031251b5", size = 1791270 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/73/c16af0ad6703396abb3b2c357194808d23e97f1b439da2beba675dab4fba/aiohttp-3.11.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae3a5d9f2fbe736fec7d24be25c57aa78c2d78d96540439ea68a8abbed9906fc", size = 1674466 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/2d/79b72cecf79e5c72fdddaa319b731f81ca5121a2caa51ddf7afef0f1ad4e/aiohttp-3.11.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:269d145c593a65f78fb9a64dece90341561ddb2e91a96d42681132b2f706c42a", size = 1622282 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/a9/10b78cd9f21a820a6d0160682b80a0a08c2b359d9e7936da3a5a21e3f511/aiohttp-3.11.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0987dcf32e4c47f22634d32e4b0ffbc368bbcf2b33b408cd1a3d2dc0a6a5cd34", size = 1658241 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/01/04c934a64853b57e856d883436a839786a9c15ee27c212ee8b448f263e80/aiohttp-3.11.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7cf4b2b5a0f7a738ecd759eaeaef800fc7c57683b7be9d8a43fcb86ca62701b4", size = 1666669 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/2d/60c992f21586703d16d9f957b187da86e33be8be8f8d8a555f31337508ad/aiohttp-3.11.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f1e0369f0dc8c895e718ce37147f56d46142d37596be183ab7a34192c5e6e4c5", size = 1652782 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/e0/5857ef2de285ee68744eba276cbe4ba4716107214fbad5d15cecf028a260/aiohttp-3.11.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:82ddf7f642b9c0b08063f3cf4e2818b22901bce8ebad05c232d9e295e77436a0", size = 1735636 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/34/2f08d9bdc85a0a7299cbb945810720debed8b14b9b64b58935c50591d4a5/aiohttp-3.11.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c7eba0f90e27ec4af64db051f35387fa17128e6eeb58ee0f2318f2627168cc2", size = 1760389 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/51/a607071b04454f0fffa1f402b0d5bb09f15dec8ed630b1d591dbc56b1139/aiohttp-3.11.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5a61df20fa77792e83307e266f76790f7cb67980dd476948948de212ee7ec64c", size = 1694423 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/a7/723dd995b43cec56cee27fb54b2ba8fc0372f965c879c5553bb8092f29ee/aiohttp-3.11.15-cp311-cp311-win32.whl", hash = "sha256:be11989cbc0728f81c0d022cef140ef8adb20d3012ad8f0ac61853bef571eb52", size = 416773 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/f4/5849b44fe9243f88001fff0e701b57a855a34fd38499a4c03e786da65ee5/aiohttp-3.11.15-cp311-cp311-win_amd64.whl", hash = "sha256:357355c9d51c8b12bbc7de43b27ce4b51f14cce050e00b5a87d0d5527d779395", size = 442948 }, -] - -[[package]] -name = "aiosignal" -version = "1.3.2" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -dependencies = [ - { name = "frozenlist" }, -] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] @@ -83,9 +38,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, ] [[package]] @@ -107,27 +62,18 @@ source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104 }, -] - -[[package]] -name = "asttokens" -version = "3.0.0" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911, upload-time = "2021-12-01T08:52:55.68Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658, upload-time = "2021-12-01T09:09:17.016Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583, upload-time = "2021-12-01T09:09:19.546Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168, upload-time = "2021-12-01T09:09:21.445Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709, upload-time = "2021-12-01T09:09:18.182Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613, upload-time = "2021-12-01T09:09:22.741Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583, upload-time = "2021-12-01T09:09:24.177Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475, upload-time = "2021-12-01T09:09:26.673Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698, upload-time = "2021-12-01T09:09:27.87Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817, upload-time = "2021-12-01T09:09:30.267Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104, upload-time = "2021-12-01T09:09:31.335Z" }, ] [[package]] @@ -137,27 +83,49 @@ source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/67/7ea59c3e69eaeee42e7fc91a5be67ca5849c8979acac2b920249760c6af2/asyncer-0.0.8.tar.gz", hash = "sha256:a589d980f57e20efb07ed91d0dbe67f1d2fd343e7142c66d3a099f05c620739c", size = 18217 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/67/7ea59c3e69eaeee42e7fc91a5be67ca5849c8979acac2b920249760c6af2/asyncer-0.0.8.tar.gz", hash = "sha256:a589d980f57e20efb07ed91d0dbe67f1d2fd343e7142c66d3a099f05c620739c", size = 18217, upload-time = "2024-08-24T23:15:36.449Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/04/15b6ca6b7842eda2748bda0a0af73f2d054e9344320f8bba01f994294bcb/asyncer-0.0.8-py3-none-any.whl", hash = "sha256:5920d48fc99c8f8f0f1576e1882f5022885589c5fcbc46ce4224ec3e53776eeb", size = 9209 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/04/15b6ca6b7842eda2748bda0a0af73f2d054e9344320f8bba01f994294bcb/asyncer-0.0.8-py3-none-any.whl", hash = "sha256:5920d48fc99c8f8f0f1576e1882f5022885589c5fcbc46ce4224ec3e53776eeb", size = 9209, upload-time = "2024-08-24T23:15:35.317Z" }, ] [[package]] name = "attrs" version = "25.3.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "autodoc-pydantic" +version = "2.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "sphinx" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/df/87120e2195f08d760bc5cf8a31cfa2381a6887517aa89453b23f1ae3354f/autodoc_pydantic-2.2.0-py3-none-any.whl", hash = "sha256:8c6a36fbf6ed2700ea9c6d21ea76ad541b621fbdf16b5a80ee04673548af4d95", size = 34001, upload-time = "2024-04-27T10:57:00.542Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] [[package]] name = "certifi" -version = "2025.1.31" +version = "2025.4.26" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, ] [[package]] @@ -167,83 +135,83 @@ source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "pycparser" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.1" +version = "3.4.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, ] [[package]] name = "click" -version = "8.1.8" +version = "8.2.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857, upload-time = "2025-05-10T22:21:03.111Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "coverage" version = "7.7.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/bf/3effb7453498de9c14a81ca21e1f92e6723ce7ebdc5402ae30e4dcc490ac/coverage-7.7.1.tar.gz", hash = "sha256:199a1272e642266b90c9f40dec7fd3d307b51bf639fa0d15980dc0b3246c1393", size = 810332 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/bf/3effb7453498de9c14a81ca21e1f92e6723ce7ebdc5402ae30e4dcc490ac/coverage-7.7.1.tar.gz", hash = "sha256:199a1272e642266b90c9f40dec7fd3d307b51bf639fa0d15980dc0b3246c1393", size = 810332, upload-time = "2025-03-21T17:23:58.093Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/4c/5118ca60ed4141ec940c8cbaf1b2ebe8911be0f03bfc028c99f63de82c44/coverage-7.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1165490be0069e34e4f99d08e9c5209c463de11b471709dfae31e2a98cbd49fd", size = 211064 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/6c/0e9aac4cf5dba49feede79109fdfd2fafca3bdbc02992bcf9b25d58351dd/coverage-7.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:44af11c00fd3b19b8809487630f8a0039130d32363239dfd15238e6d37e41a48", size = 211501 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/1a/570666f276815722f0a94f92b61e7123d66b166238e0f9f224f1a38f17cf/coverage-7.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fbba59022e7c20124d2f520842b75904c7b9f16c854233fa46575c69949fb5b9", size = 244128 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/0d/cb23f89eb8c7018429c6cf8cc436b4eb917f43e81354d99c86c435ab1813/coverage-7.7.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af94fb80e4f159f4d93fb411800448ad87b6039b0500849a403b73a0d36bb5ae", size = 241818 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/fd/584a5d099bba4e79ac3893d57e0bd53034f7187c30f940e6a581bfd38c8f/coverage-7.7.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eae79f8e3501133aa0e220bbc29573910d096795882a70e6f6e6637b09522133", size = 243602 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/d7/a28b6a5ee64ff1e4a66fbd8cd7b9372471c951c3a0c4ec9d1d0f47819f53/coverage-7.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e33426a5e1dc7743dd54dfd11d3a6c02c5d127abfaa2edd80a6e352b58347d1a", size = 243247 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/9e/210814fae81ea7796f166529a32b443dead622a8c1ad315d0779520635c6/coverage-7.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b559adc22486937786731dac69e57296cb9aede7e2687dfc0d2696dbd3b1eb6b", size = 241422 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/5e/80ed1955fa8529bdb72dc11c0a3f02a838285250c0e14952e39844993102/coverage-7.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b838a91e84e1773c3436f6cc6996e000ed3ca5721799e7789be18830fad009a2", size = 241958 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/26/f0bafc8103284febc4e3a3cd947b49ff36c50711daf3d03b3e11b23bc73a/coverage-7.7.1-cp311-cp311-win32.whl", hash = "sha256:2c492401bdb3a85824669d6a03f57b3dfadef0941b8541f035f83bbfc39d4282", size = 213571 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/fe/fef0a0201af72422fb9634b5c6079786bb405ac09cce5661fdd54a66e773/coverage-7.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:1e6f867379fd033a0eeabb1be0cffa2bd660582b8b0c9478895c509d875a9d9e", size = 214488 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/4e/a501ec475ed455c1ee1570063527afe2c06ab1039f8ff18eefecfbdac8fd/coverage-7.7.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:5b7b02e50d54be6114cc4f6a3222fec83164f7c42772ba03b520138859b5fde1", size = 203014 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/26/9f53293ff4cc1d47d98367ce045ca2e62746d6be74a5c6851a474eabf59b/coverage-7.7.1-py3-none-any.whl", hash = "sha256:822fa99dd1ac686061e1219b67868e25d9757989cf2259f735a4802497d6da31", size = 203006 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/4c/5118ca60ed4141ec940c8cbaf1b2ebe8911be0f03bfc028c99f63de82c44/coverage-7.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1165490be0069e34e4f99d08e9c5209c463de11b471709dfae31e2a98cbd49fd", size = 211064, upload-time = "2025-03-21T17:22:34.148Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/6c/0e9aac4cf5dba49feede79109fdfd2fafca3bdbc02992bcf9b25d58351dd/coverage-7.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:44af11c00fd3b19b8809487630f8a0039130d32363239dfd15238e6d37e41a48", size = 211501, upload-time = "2025-03-21T17:22:35.775Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/1a/570666f276815722f0a94f92b61e7123d66b166238e0f9f224f1a38f17cf/coverage-7.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fbba59022e7c20124d2f520842b75904c7b9f16c854233fa46575c69949fb5b9", size = 244128, upload-time = "2025-03-21T17:22:37.435Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/0d/cb23f89eb8c7018429c6cf8cc436b4eb917f43e81354d99c86c435ab1813/coverage-7.7.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af94fb80e4f159f4d93fb411800448ad87b6039b0500849a403b73a0d36bb5ae", size = 241818, upload-time = "2025-03-21T17:22:38.646Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/fd/584a5d099bba4e79ac3893d57e0bd53034f7187c30f940e6a581bfd38c8f/coverage-7.7.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eae79f8e3501133aa0e220bbc29573910d096795882a70e6f6e6637b09522133", size = 243602, upload-time = "2025-03-21T17:22:39.892Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/d7/a28b6a5ee64ff1e4a66fbd8cd7b9372471c951c3a0c4ec9d1d0f47819f53/coverage-7.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e33426a5e1dc7743dd54dfd11d3a6c02c5d127abfaa2edd80a6e352b58347d1a", size = 243247, upload-time = "2025-03-21T17:22:41.122Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/9e/210814fae81ea7796f166529a32b443dead622a8c1ad315d0779520635c6/coverage-7.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b559adc22486937786731dac69e57296cb9aede7e2687dfc0d2696dbd3b1eb6b", size = 241422, upload-time = "2025-03-21T17:22:42.753Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/5e/80ed1955fa8529bdb72dc11c0a3f02a838285250c0e14952e39844993102/coverage-7.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b838a91e84e1773c3436f6cc6996e000ed3ca5721799e7789be18830fad009a2", size = 241958, upload-time = "2025-03-21T17:22:44.022Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/26/f0bafc8103284febc4e3a3cd947b49ff36c50711daf3d03b3e11b23bc73a/coverage-7.7.1-cp311-cp311-win32.whl", hash = "sha256:2c492401bdb3a85824669d6a03f57b3dfadef0941b8541f035f83bbfc39d4282", size = 213571, upload-time = "2025-03-21T17:22:45.308Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/fe/fef0a0201af72422fb9634b5c6079786bb405ac09cce5661fdd54a66e773/coverage-7.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:1e6f867379fd033a0eeabb1be0cffa2bd660582b8b0c9478895c509d875a9d9e", size = 214488, upload-time = "2025-03-21T17:22:46.978Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/4e/a501ec475ed455c1ee1570063527afe2c06ab1039f8ff18eefecfbdac8fd/coverage-7.7.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:5b7b02e50d54be6114cc4f6a3222fec83164f7c42772ba03b520138859b5fde1", size = 203014, upload-time = "2025-03-21T17:23:54.458Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/26/9f53293ff4cc1d47d98367ce045ca2e62746d6be74a5c6851a474eabf59b/coverage-7.7.1-py3-none-any.whl", hash = "sha256:822fa99dd1ac686061e1219b67868e25d9757989cf2259f735a4802497d6da31", size = 203006, upload-time = "2025-03-21T17:23:56.378Z" }, ] [[package]] @@ -253,45 +221,36 @@ source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/25/4ce80c78963834b8a9fd1cc1266be5ed8d1840785c0f2e1b73b8d128d505/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", size = 710807 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/ef/83e632cfa801b221570c5f58c0369db6fa6cef7d9ff859feab1aae1a8a0f/cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7", size = 6676361 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/ec/7ea7c1e4c8fc8329506b46c6c4a52e2f20318425d48e0fe597977c71dbce/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", size = 3952350 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/61/72e3afdb3c5ac510330feba4fc1faa0fe62e070592d6ad00c40bb69165e5/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", size = 4166572 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/e4/ba680f0b35ed4a07d87f9e98f3ebccb05091f3bf6b5a478b943253b3bbd5/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", size = 3958124 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/e8/44ae3e68c8b6d1cbc59040288056df2ad7f7f03bbcaca6b503c737ab8e73/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", size = 3678122 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/7b/664ea5e0d1eab511a10e480baf1c5d3e681c7d91718f60e149cec09edf01/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", size = 4191831 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/07/79554a9c40eb11345e1861f46f845fa71c9e25bf66d132e123d9feb8e7f9/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", size = 3960583 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/6d/858e356a49a4f0b591bd6789d821427de18432212e137290b6d8a817e9bf/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308", size = 4191753 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/80/62df41ba4916067fa6b125aa8c14d7e9181773f0d5d0bd4dcef580d8b7c6/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", size = 4079550 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/cd/2558cc08f7b1bb40683f99ff4327f8dcfc7de3affc669e9065e14824511b/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", size = 4298367 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/59/94ccc74788945bc3bd4cf355d19867e8057ff5fdbcac781b1ff95b700fb1/cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79", size = 2772843 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/2c/0d0bbaf61ba05acb32f0841853cfa33ebb7a9ab3d9ed8bb004bd39f2da6a/cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa", size = 3209057 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/be/7a26142e6d0f7683d8a382dd963745e65db895a79a280a30525ec92be890/cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3", size = 6677789 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/88/638865be7198a84a7713950b1db7343391c6066a20e614f8fa286eb178ed/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", size = 3951919 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/fc/99fe639bcdf58561dfad1faa8a7369d1dc13f20acd78371bb97a01613585/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", size = 4167812 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/7b/aafe60210ec93d5d7f552592a28192e51d3c6b6be449e7fd0a91399b5d07/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", size = 3958571 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/32/051f7ce79ad5a6ef5e26a92b37f172ee2d6e1cce09931646eef8de1e9827/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", size = 3679832 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/2b/999b2a1e1ba2206f2d3bca267d68f350beb2b048a41ea827e08ce7260098/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", size = 4193719 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/97/430e56e39a1356e8e8f10f723211a0e256e11895ef1a135f30d7d40f2540/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", size = 3960852 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/33/c1cf182c152e1d262cac56850939530c05ca6c8d149aa0dcee490b417e99/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", size = 4193906 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/99/87cf26d4f125380dc674233971069bc28d19b07f7755b29861570e513650/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", size = 4081572 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/9f/6a3e0391957cc0c5f84aef9fbdd763035f2b52e998a53f99345e3ac69312/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", size = 4298631 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/a5/5bc097adb4b6d22a24dea53c51f37e480aaec3465285c253098642696423/cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5", size = 2773792 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/d7/f30e75a6aa7d0f65031886fa4a1485c2fbfe25a1896953920f6a9cfe2d3b/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:909c97ab43a9c0c0b0ada7a1281430e4e5ec0458e6d9244c0e821bbf152f061d", size = 3887513 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/b4/7a494ce1032323ca9db9a3661894c66e0d7142ad2079a4249303402d8c71/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96e7a5e9d6e71f9f4fca8eebfd603f8e86c5225bb18eb621b2c1e50b290a9471", size = 4107432 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/f8/6b3ec0bc56123b344a8d2b3264a325646d2dcdbdd9848b5e6f3d37db90b3/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d1b3031093a366ac767b3feb8bcddb596671b3aaff82d4050f984da0c248b615", size = 3891421 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/ff/f3b4b2d007c2a646b0f69440ab06224f9cf37a977a72cdb7b50632174e8a/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:04abd71114848aa25edb28e225ab5f268096f44cf0127f3d36975bdf1bdf3390", size = 4107081 }, -] - -[[package]] -name = "decorator" -version = "5.2.1" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/25/4ce80c78963834b8a9fd1cc1266be5ed8d1840785c0f2e1b73b8d128d505/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", size = 710807, upload-time = "2025-03-02T00:01:37.692Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/ef/83e632cfa801b221570c5f58c0369db6fa6cef7d9ff859feab1aae1a8a0f/cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7", size = 6676361, upload-time = "2025-03-02T00:00:06.528Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/ec/7ea7c1e4c8fc8329506b46c6c4a52e2f20318425d48e0fe597977c71dbce/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", size = 3952350, upload-time = "2025-03-02T00:00:09.537Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/61/72e3afdb3c5ac510330feba4fc1faa0fe62e070592d6ad00c40bb69165e5/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", size = 4166572, upload-time = "2025-03-02T00:00:12.03Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/e4/ba680f0b35ed4a07d87f9e98f3ebccb05091f3bf6b5a478b943253b3bbd5/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", size = 3958124, upload-time = "2025-03-02T00:00:14.518Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/e8/44ae3e68c8b6d1cbc59040288056df2ad7f7f03bbcaca6b503c737ab8e73/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", size = 3678122, upload-time = "2025-03-02T00:00:17.212Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/7b/664ea5e0d1eab511a10e480baf1c5d3e681c7d91718f60e149cec09edf01/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", size = 4191831, upload-time = "2025-03-02T00:00:19.696Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/07/79554a9c40eb11345e1861f46f845fa71c9e25bf66d132e123d9feb8e7f9/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", size = 3960583, upload-time = "2025-03-02T00:00:22.488Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/6d/858e356a49a4f0b591bd6789d821427de18432212e137290b6d8a817e9bf/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308", size = 4191753, upload-time = "2025-03-02T00:00:25.038Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/80/62df41ba4916067fa6b125aa8c14d7e9181773f0d5d0bd4dcef580d8b7c6/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", size = 4079550, upload-time = "2025-03-02T00:00:26.929Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/cd/2558cc08f7b1bb40683f99ff4327f8dcfc7de3affc669e9065e14824511b/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", size = 4298367, upload-time = "2025-03-02T00:00:28.735Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/59/94ccc74788945bc3bd4cf355d19867e8057ff5fdbcac781b1ff95b700fb1/cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79", size = 2772843, upload-time = "2025-03-02T00:00:30.592Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/2c/0d0bbaf61ba05acb32f0841853cfa33ebb7a9ab3d9ed8bb004bd39f2da6a/cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa", size = 3209057, upload-time = "2025-03-02T00:00:33.393Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/be/7a26142e6d0f7683d8a382dd963745e65db895a79a280a30525ec92be890/cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3", size = 6677789, upload-time = "2025-03-02T00:00:36.009Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/88/638865be7198a84a7713950b1db7343391c6066a20e614f8fa286eb178ed/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", size = 3951919, upload-time = "2025-03-02T00:00:38.581Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/fc/99fe639bcdf58561dfad1faa8a7369d1dc13f20acd78371bb97a01613585/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", size = 4167812, upload-time = "2025-03-02T00:00:42.934Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/7b/aafe60210ec93d5d7f552592a28192e51d3c6b6be449e7fd0a91399b5d07/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", size = 3958571, upload-time = "2025-03-02T00:00:46.026Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/32/051f7ce79ad5a6ef5e26a92b37f172ee2d6e1cce09931646eef8de1e9827/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", size = 3679832, upload-time = "2025-03-02T00:00:48.647Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/2b/999b2a1e1ba2206f2d3bca267d68f350beb2b048a41ea827e08ce7260098/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", size = 4193719, upload-time = "2025-03-02T00:00:51.397Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/97/430e56e39a1356e8e8f10f723211a0e256e11895ef1a135f30d7d40f2540/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", size = 3960852, upload-time = "2025-03-02T00:00:53.317Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/33/c1cf182c152e1d262cac56850939530c05ca6c8d149aa0dcee490b417e99/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", size = 4193906, upload-time = "2025-03-02T00:00:56.49Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/99/87cf26d4f125380dc674233971069bc28d19b07f7755b29861570e513650/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", size = 4081572, upload-time = "2025-03-02T00:00:59.995Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/9f/6a3e0391957cc0c5f84aef9fbdd763035f2b52e998a53f99345e3ac69312/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", size = 4298631, upload-time = "2025-03-02T00:01:01.623Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/a5/5bc097adb4b6d22a24dea53c51f37e480aaec3465285c253098642696423/cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5", size = 2773792, upload-time = "2025-03-02T00:01:04.133Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957, upload-time = "2025-03-02T00:01:06.987Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/d7/f30e75a6aa7d0f65031886fa4a1485c2fbfe25a1896953920f6a9cfe2d3b/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:909c97ab43a9c0c0b0ada7a1281430e4e5ec0458e6d9244c0e821bbf152f061d", size = 3887513, upload-time = "2025-03-02T00:01:22.911Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/b4/7a494ce1032323ca9db9a3661894c66e0d7142ad2079a4249303402d8c71/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96e7a5e9d6e71f9f4fca8eebfd603f8e86c5225bb18eb621b2c1e50b290a9471", size = 4107432, upload-time = "2025-03-02T00:01:24.701Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/f8/6b3ec0bc56123b344a8d2b3264a325646d2dcdbdd9848b5e6f3d37db90b3/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d1b3031093a366ac767b3feb8bcddb596671b3aaff82d4050f984da0c248b615", size = 3891421, upload-time = "2025-03-02T00:01:26.335Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/ff/f3b4b2d007c2a646b0f69440ab06224f9cf37a977a72cdb7b50632174e8a/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:04abd71114848aa25edb28e225ab5f268096f44cf0127f3d36975bdf1bdf3390", size = 4107081, upload-time = "2025-03-02T00:01:28.938Z" }, ] [[package]] @@ -301,43 +260,54 @@ source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, ] [[package]] name = "distro" version = "1.9.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] [[package]] name = "dnspython" version = "2.7.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, ] [[package]] name = "euler-copilot-framework" -version = "0.9.5" +version = "0.9.6" source = { virtual = "." } dependencies = [ { name = "aiofiles" }, - { name = "aiohttp" }, { name = "asyncer" }, { name = "cryptography" }, { name = "fastapi" }, + { name = "httpx" }, + { name = "httpx-sse" }, { name = "jinja2" }, { name = "jionlp" }, { name = "jsonschema" }, { name = "lancedb" }, + { name = "mcp" }, { name = "minio" }, { name = "ollama" }, { name = "openai" }, @@ -348,9 +318,8 @@ dependencies = [ { name = "python-multipart" }, { name = "pytz" }, { name = "pyyaml" }, - { name = "referencing" }, { name = "rich" }, - { name = "sglang" }, + { name = "sqids" }, { name = "tiktoken" }, { name = "toml" }, { name = "uvicorn" }, @@ -358,58 +327,54 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "autodoc-pydantic" }, { name = "coverage" }, - { name = "httpx" }, { name = "pytest" }, { name = "pytest-mock" }, { name = "ruff" }, + { name = "sphinx" }, + { name = "sphinx-rtd-theme" }, ] [package.metadata] requires-dist = [ - { name = "aiofiles", specifier = ">=24.1.0" }, - { name = "aiohttp", specifier = ">=3.11.14" }, - { name = "asyncer", specifier = ">=0.0.8" }, - { name = "cryptography", specifier = ">=44.0.2" }, - { name = "fastapi", specifier = ">=0.115.12" }, - { name = "jinja2", specifier = ">=3.1.6" }, - { name = "jionlp", specifier = ">=1.5.20" }, - { name = "jsonschema", specifier = ">=4.23.0" }, - { name = "lancedb", specifier = ">=0.21.2" }, - { name = "minio", specifier = ">=7.2.15" }, - { name = "ollama", specifier = ">=0.4.7" }, - { name = "openai", specifier = ">=1.68.2" }, - { name = "pandas", specifier = ">=2.2.3" }, - { name = "pymongo", specifier = ">=4.11.3" }, - { name = "python-jsonpath", specifier = ">=1.3.0" }, - { name = "python-magic", specifier = ">=0.4.27" }, - { name = "python-multipart", specifier = ">=0.0.20" }, - { name = "pytz", specifier = ">=2025.2" }, - { name = "pyyaml", specifier = ">=6.0.2" }, - { name = "referencing", specifier = ">=0.36.2" }, - { name = "rich", specifier = ">=13.9.4" }, - { name = "sglang", specifier = ">=0.4.4.post2" }, - { name = "tiktoken", specifier = ">=0.9.0" }, - { name = "toml", specifier = ">=0.10.2" }, - { name = "uvicorn", specifier = ">=0.34.0" }, + { name = "aiofiles", specifier = "==24.1.0" }, + { name = "asyncer", specifier = "==0.0.8" }, + { name = "cryptography", specifier = "==44.0.2" }, + { name = "fastapi", specifier = "==0.115.12" }, + { name = "httpx", specifier = "==0.28.1" }, + { name = "httpx-sse", specifier = "==0.4.0" }, + { name = "jinja2", specifier = "==3.1.6" }, + { name = "jionlp", specifier = "==1.5.20" }, + { name = "jsonschema", specifier = "==4.23.0" }, + { name = "lancedb", specifier = "==0.21.2" }, + { name = "mcp", specifier = "==1.8.1" }, + { name = "minio", specifier = "==7.2.15" }, + { name = "ollama", specifier = "==0.4.8" }, + { name = "openai", specifier = "==1.78.1" }, + { name = "pandas", specifier = "==2.2.3" }, + { name = "pymongo", specifier = "==4.12.1" }, + { name = "python-jsonpath", specifier = "==1.3.0" }, + { name = "python-magic", specifier = "==0.4.27" }, + { name = "python-multipart", specifier = "==0.0.20" }, + { name = "pytz", specifier = "==2025.2" }, + { name = "pyyaml", specifier = "==6.0.2" }, + { name = "rich", specifier = "==13.9.4" }, + { name = "sqids", specifier = "==0.5.1" }, + { name = "tiktoken", specifier = "==0.9.0" }, + { name = "toml", specifier = "==0.10.2" }, + { name = "uvicorn", specifier = "==0.34.0" }, ] [package.metadata.requires-dev] dev = [ - { name = "coverage", specifier = ">=7.7.1" }, - { name = "httpx", specifier = ">=0.28.1" }, - { name = "pytest", specifier = ">=8.3.5" }, - { name = "pytest-mock", specifier = ">=3.14.0" }, - { name = "ruff", specifier = ">=0.11.2" }, -] - -[[package]] -name = "executing" -version = "2.2.0" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 }, + { name = "autodoc-pydantic", specifier = "==2.2.0" }, + { name = "coverage", specifier = "==7.7.1" }, + { name = "pytest", specifier = "==8.3.5" }, + { name = "pytest-mock", specifier = "==3.14.0" }, + { name = "ruff", specifier = "==0.11.2" }, + { name = "sphinx", specifier = "==8.2.3" }, + { name = "sphinx-rtd-theme", specifier = "==3.0.2" }, ] [[package]] @@ -421,55 +386,31 @@ dependencies = [ { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 }, -] - -[[package]] -name = "frozenlist" -version = "1.5.0" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/43/0bed28bf5eb1c9e4301003b74453b8e7aa85fb293b31dde352aac528dafc/frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", size = 94987 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/bf/b74e38f09a246e8abbe1e90eb65787ed745ccab6eaa58b9c9308e052323d/frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", size = 54584 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/31/ab01375682f14f7613a1ade30149f684c84f9b8823a4391ed950c8285656/frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", size = 52499 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/a8/d0ac0b9276e1404f58fec3ab6e90a4f76b778a49373ccaf6a563f100dfbc/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", size = 276357 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/c9/c7761084fa822f07dac38ac29f841d4587570dd211e2262544aa0b791d21/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", size = 287516 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/ff/cd7479e703c39df7bdab431798cef89dc75010d8aa0ca2514c5b9321db27/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", size = 283131 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/a0/370941beb47d237eca4fbf27e4e91389fd68699e6f4b0ebcc95da463835b/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", size = 261320 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/5f/c10123e8d64867bc9b4f2f510a32042a306ff5fcd7e2e09e5ae5100ee333/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", size = 274877 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/79/38c505601ae29d4348f21706c5d89755ceded02a745016ba2f58bd5f1ea6/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", size = 269592 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/e2/39f3a53191b8204ba9f0bb574b926b73dd2efba2a2b9d2d730517e8f7622/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", size = 265934 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/c9/3075eb7f7f3a91f1a6b00284af4de0a65a9ae47084930916f5528144c9dd/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", size = 283859 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/f5/549f44d314c29408b962fa2b0e69a1a67c59379fb143b92a0a065ffd1f0f/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", size = 287560 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/f8/cb09b3c24a3eac02c4c07a9558e11e9e244fb02bf62c85ac2106d1eb0c0b/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", size = 277150 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/48/38c2db3f54d1501e692d6fe058f45b6ad1b358d82cd19436efab80cfc965/frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", size = 45244 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/8c/2ddffeb8b60a4bce3b196c32fcc30d8830d4615e7b492ec2071da801b8ad/frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", size = 51634 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" }, ] [[package]] name = "h11" -version = "0.14.0" +version = "0.16.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.9" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] [[package]] @@ -482,73 +423,45 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, -] - -[[package]] -name = "idna" -version = "3.10" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] [[package]] -name = "iniconfig" -version = "2.1.0" +name = "httpx-sse" +version = "0.4.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, ] [[package]] -name = "ipython" -version = "9.0.2" +name = "idna" +version = "3.10" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, - { name = "ipython-pygments-lexers" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, - { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "stack-data" }, - { name = "traitlets" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/ce/012a0f40ca58a966f87a6e894d6828e2817657cbdf522b02a5d3a87d92ce/ipython-9.0.2.tar.gz", hash = "sha256:ec7b479e3e5656bf4f58c652c120494df1820f4f28f522fb7ca09e213c2aab52", size = 4366102 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/3a/917cb9e72f4e1a4ea13c862533205ae1319bd664119189ee5cc9e4e95ebf/ipython-9.0.2-py3-none-any.whl", hash = "sha256:143ef3ea6fb1e1bffb4c74b114051de653ffb7737a3f7ab1670e657ca6ae8c44", size = 600524 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] -name = "ipython-pygments-lexers" -version = "1.1.1" +name = "imagesize" +version = "1.4.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -dependencies = [ - { name = "pygments" }, -] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, ] [[package]] -name = "jedi" -version = "0.19.2" +name = "iniconfig" +version = "2.1.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -dependencies = [ - { name = "parso" }, -] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] @@ -558,9 +471,9 @@ source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] @@ -572,8 +485,8 @@ dependencies = [ { name = "pyyaml" }, ] wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/62/0dacd250a22c50cb52f7601f13cfca2ca96090165c7e60e9975cce01e307/jiojio-1.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c691b48bd106f9e176a213b658c2f10940cf883a5b557d257c4ee8fd8dd8b6f8", size = 85579252 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/3f/3bffece127003d709ede8b4816c4b10cfdf9b167fa3394e566dafb0c9dce/jiojio-1.2.7-py2.py3-none-any.whl", hash = "sha256:2ee87cfad4dc9b00892e8b32dd15fd23517f82ee79435bf72a0e432f9bfa3448", size = 85548031 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/62/0dacd250a22c50cb52f7601f13cfca2ca96090165c7e60e9975cce01e307/jiojio-1.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c691b48bd106f9e176a213b658c2f10940cf883a5b557d257c4ee8fd8dd8b6f8", size = 85579252, upload-time = "2024-12-30T01:59:44.602Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/3f/3bffece127003d709ede8b4816c4b10cfdf9b167fa3394e566dafb0c9dce/jiojio-1.2.7-py2.py3-none-any.whl", hash = "sha256:2ee87cfad4dc9b00892e8b32dd15fd23517f82ee79435bf72a0e432f9bfa3448", size = 85548031, upload-time = "2024-12-30T02:07:57.515Z" }, ] [[package]] @@ -587,27 +500,27 @@ dependencies = [ { name = "zipfile36" }, ] wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/10/c368792758532eaab2e50e567bc06e0be45e420231c15065fbe75c2e65b3/jionlp-1.5.20-py2.py3-none-any.whl", hash = "sha256:40186ca7364148a447855ff03065adb6acf9428545681ff23f0ca3b9acd7335b", size = 16323555 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/10/c368792758532eaab2e50e567bc06e0be45e420231c15065fbe75c2e65b3/jionlp-1.5.20-py2.py3-none-any.whl", hash = "sha256:40186ca7364148a447855ff03065adb6acf9428545681ff23f0ca3b9acd7335b", size = 16323555, upload-time = "2025-03-22T07:40:27.759Z" }, ] [[package]] name = "jiter" version = "0.9.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/c2/e4562507f52f0af7036da125bb699602ead37a2332af0788f8e0a3417f36/jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893", size = 162604 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/c2/e4562507f52f0af7036da125bb699602ead37a2332af0788f8e0a3417f36/jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893", size = 162604, upload-time = "2025-03-10T21:37:03.278Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/44/e241a043f114299254e44d7e777ead311da400517f179665e59611ab0ee4/jiter-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af", size = 314654 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/1b/a7e5e42db9fa262baaa9489d8d14ca93f8663e7f164ed5e9acc9f467fc00/jiter-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58", size = 320909 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/bf/8ebdfce77bc04b81abf2ea316e9c03b4a866a7d739cf355eae4d6fd9f6fe/jiter-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b", size = 341733 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/4e/754ebce77cff9ab34d1d0fa0fe98f5d42590fd33622509a3ba6ec37ff466/jiter-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b", size = 365097 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/2c/6019587e6f5844c612ae18ca892f4cd7b3d8bbf49461ed29e384a0f13d98/jiter-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5", size = 406603 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/e9/c9e6546c817ab75a1a7dab6dcc698e62e375e1017113e8e983fccbd56115/jiter-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572", size = 396625 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/bd/976b458add04271ebb5a255e992bd008546ea04bb4dcadc042a16279b4b4/jiter-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15", size = 351832 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/51/fe59e307aaebec9265dbad44d9d4381d030947e47b0f23531579b9a7c2df/jiter-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419", size = 384590 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/55/5dcd2693794d8e6f4889389ff66ef3be557a77f8aeeca8973a97a7c00557/jiter-0.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043", size = 520690 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/d5/9f51dc90985e9eb251fbbb747ab2b13b26601f16c595a7b8baba964043bd/jiter-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965", size = 512649 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/e5/4e385945179bcf128fa10ad8dca9053d717cbe09e258110e39045c881fe5/jiter-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2", size = 206920 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/47/5e0b94c603d8e54dd1faab439b40b832c277d3b90743e7835879ab663757/jiter-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd", size = 210119 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/44/e241a043f114299254e44d7e777ead311da400517f179665e59611ab0ee4/jiter-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af", size = 314654, upload-time = "2025-03-10T21:35:23.939Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/1b/a7e5e42db9fa262baaa9489d8d14ca93f8663e7f164ed5e9acc9f467fc00/jiter-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58", size = 320909, upload-time = "2025-03-10T21:35:26.127Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/bf/8ebdfce77bc04b81abf2ea316e9c03b4a866a7d739cf355eae4d6fd9f6fe/jiter-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b", size = 341733, upload-time = "2025-03-10T21:35:27.94Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/4e/754ebce77cff9ab34d1d0fa0fe98f5d42590fd33622509a3ba6ec37ff466/jiter-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b", size = 365097, upload-time = "2025-03-10T21:35:29.605Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/2c/6019587e6f5844c612ae18ca892f4cd7b3d8bbf49461ed29e384a0f13d98/jiter-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5", size = 406603, upload-time = "2025-03-10T21:35:31.696Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/e9/c9e6546c817ab75a1a7dab6dcc698e62e375e1017113e8e983fccbd56115/jiter-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572", size = 396625, upload-time = "2025-03-10T21:35:33.182Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/bd/976b458add04271ebb5a255e992bd008546ea04bb4dcadc042a16279b4b4/jiter-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15", size = 351832, upload-time = "2025-03-10T21:35:35.394Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/51/fe59e307aaebec9265dbad44d9d4381d030947e47b0f23531579b9a7c2df/jiter-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419", size = 384590, upload-time = "2025-03-10T21:35:37.171Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/55/5dcd2693794d8e6f4889389ff66ef3be557a77f8aeeca8973a97a7c00557/jiter-0.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043", size = 520690, upload-time = "2025-03-10T21:35:38.717Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/d5/9f51dc90985e9eb251fbbb747ab2b13b26601f16c595a7b8baba964043bd/jiter-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965", size = 512649, upload-time = "2025-03-10T21:35:40.157Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/e5/4e385945179bcf128fa10ad8dca9053d717cbe09e258110e39045c881fe5/jiter-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2", size = 206920, upload-time = "2025-03-10T21:35:41.72Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/47/5e0b94c603d8e54dd1faab439b40b832c277d3b90743e7835879ab663757/jiter-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd", size = 210119, upload-time = "2025-03-10T21:35:43.46Z" }, ] [[package]] @@ -620,21 +533,21 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778, upload-time = "2024-07-08T18:40:05.546Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462, upload-time = "2024-07-08T18:40:00.165Z" }, ] [[package]] name = "jsonschema-specifications" -version = "2024.10.1" +version = "2025.4.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, ] [[package]] @@ -650,13 +563,13 @@ dependencies = [ { name = "tqdm" }, ] wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/89/6ea4608489bf8eb988cd6174b1b7854a4fd03fdaeac8aaf4ff1649a0da45/lancedb-0.21.2-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:f588c5f7abbd984020e22743bde8b8168884c110f9596e9b9d2dd2c826dd7250", size = 31150191 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/08/63a35613a0cfd321b9195050cfa8db3c748548af774840fb91a23092e34c/lancedb-0.21.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5c2ef097977ef7f057d3f00606bcadffb5c111454d46c07cd1eb63da5a38d217", size = 29040833 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/2c/9379c6666cb6997000af05e5aad6f5cf96126428b10d78c523a75154521e/lancedb-0.21.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9bee1e4379d8fcd2a444567e73a769be9d40e0f8df5fbb24cbb923450fdb6de", size = 30541075 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/f5/e5a9f4b614da1312a681f1b7d78bd09a18a09906d9bfdc8db8ddcefce0d2/lancedb-0.21.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c83205c8dfe3ce23a7de9eed1fe3b6ca01129d9fde32909ddd8bbda44255913", size = 32883266 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/22/0e07acaf65a1384edd94ff6e8321b805e1fbc208458331f2895861e4e252/lancedb-0.21.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ac2cec9128540d18508eb28ac5fe4306631a21b15968611904bfd2616fa2ff8e", size = 30547621 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/40/9d49ea246fc58ca14e032a548fccb3aa72ec214c47bf332bdb28dd931660/lancedb-0.21.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:d8811e0d9c82d460fb067496de0ed017d714d4677ce738dae8916d4f8413cab1", size = 32921269 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/5d/bc7498393125acda16d942af945f7422d8fa13631d86241e7ebf3e0f0943/lancedb-0.21.2-cp39-abi3-win_amd64.whl", hash = "sha256:74fae065628c6c457663e608c70cd94dc441312de1a22edca4fa8e2597013897", size = 32076959 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/89/6ea4608489bf8eb988cd6174b1b7854a4fd03fdaeac8aaf4ff1649a0da45/lancedb-0.21.2-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:f588c5f7abbd984020e22743bde8b8168884c110f9596e9b9d2dd2c826dd7250", size = 31150191, upload-time = "2025-03-26T17:23:45.152Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/08/63a35613a0cfd321b9195050cfa8db3c748548af774840fb91a23092e34c/lancedb-0.21.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5c2ef097977ef7f057d3f00606bcadffb5c111454d46c07cd1eb63da5a38d217", size = 29040833, upload-time = "2025-03-26T16:59:50.281Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/2c/9379c6666cb6997000af05e5aad6f5cf96126428b10d78c523a75154521e/lancedb-0.21.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9bee1e4379d8fcd2a444567e73a769be9d40e0f8df5fbb24cbb923450fdb6de", size = 30541075, upload-time = "2025-03-26T17:02:13.44Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/f5/e5a9f4b614da1312a681f1b7d78bd09a18a09906d9bfdc8db8ddcefce0d2/lancedb-0.21.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c83205c8dfe3ce23a7de9eed1fe3b6ca01129d9fde32909ddd8bbda44255913", size = 32883266, upload-time = "2025-03-26T16:50:25.794Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/22/0e07acaf65a1384edd94ff6e8321b805e1fbc208458331f2895861e4e252/lancedb-0.21.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ac2cec9128540d18508eb28ac5fe4306631a21b15968611904bfd2616fa2ff8e", size = 30547621, upload-time = "2025-03-26T17:03:32.14Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/40/9d49ea246fc58ca14e032a548fccb3aa72ec214c47bf332bdb28dd931660/lancedb-0.21.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:d8811e0d9c82d460fb067496de0ed017d714d4677ce738dae8916d4f8413cab1", size = 32921269, upload-time = "2025-03-26T16:48:25.661Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/5d/bc7498393125acda16d942af945f7422d8fa13631d86241e7ebf3e0f0943/lancedb-0.21.2-cp39-abi3-win_amd64.whl", hash = "sha256:74fae065628c6c457663e608c70cd94dc441312de1a22edca4fa8e2597013897", size = 32076959, upload-time = "2025-03-26T17:06:31.646Z" }, ] [[package]] @@ -666,48 +579,56 @@ source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] [[package]] name = "markupsafe" version = "3.0.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, ] [[package]] -name = "matplotlib-inline" -version = "0.1.7" +name = "mcp" +version = "1.8.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ - { name = "traitlets" }, + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/13/16b712e8a3be6a736b411df2fc6b4e75eb1d3e99b1cd57a3a1decf17f612/mcp-1.8.1.tar.gz", hash = "sha256:ec0646271d93749f784d2316fb5fe6102fb0d1be788ec70a9e2517e8f2722c0e", size = 265605, upload-time = "2025-05-12T17:33:57.887Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/5d/91cf0d40e40ae9ecf8d4004e0f9611eea86085aa0b5505493e0ff53972da/mcp-1.8.1-py3-none-any.whl", hash = "sha256:948e03783859fa35abe05b9b6c0a1d5519be452fc079dc8d7f682549591c1770", size = 119761, upload-time = "2025-05-12T17:33:56.136Z" }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] @@ -721,69 +642,45 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/68/86a1cef80396e6a35a6fc4fafee5d28578c1a137bddd3ca2aa86f9b26a22/minio-7.2.15.tar.gz", hash = "sha256:5247df5d4dca7bfa4c9b20093acd5ad43e82d8710ceb059d79c6eea970f49f79", size = 138040 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/68/86a1cef80396e6a35a6fc4fafee5d28578c1a137bddd3ca2aa86f9b26a22/minio-7.2.15.tar.gz", hash = "sha256:5247df5d4dca7bfa4c9b20093acd5ad43e82d8710ceb059d79c6eea970f49f79", size = 138040, upload-time = "2025-01-19T08:57:26.626Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/6f/3690028e846fe432bfa5ba724a0dc37ec9c914965b7733e19d8ca2c4c48d/minio-7.2.15-py3-none-any.whl", hash = "sha256:c06ef7a43e5d67107067f77b6c07ebdd68733e5aa7eed03076472410ca19d876", size = 95075 }, -] - -[[package]] -name = "multidict" -version = "6.2.0" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/4a/7874ca44a1c9b23796c767dd94159f6c17e31c0e7d090552a1c623247d82/multidict-6.2.0.tar.gz", hash = "sha256:0085b0afb2446e57050140240a8595846ed64d1cbd26cef936bfab3192c673b8", size = 71066 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/aa/879cf5581bd56c19f1bd2682ee4ecfd4085a404668d4ee5138b0a08eaf2a/multidict-6.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e87a7d75fa36839a3a432286d719975362d230c70ebfa0948549cc38bd5b46", size = 49125 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/d8/e6d47c166c13c48be8efb9720afe0f5cdc4da4687547192cbc3c03903041/multidict-6.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8de4d42dffd5ced9117af2ce66ba8722402541a3aa98ffdf78dde92badb68932", size = 29689 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/20/f3f0a2ca142c81100b6d4cbf79505961b54181d66157615bba3955304442/multidict-6.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d91a230c7f8af86c904a5a992b8c064b66330544693fd6759c3d6162382ecf", size = 29975 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/2d/1724972c7aeb7aa1916a3276cb32f9c39e186456ee7ed621504e7a758322/multidict-6.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f6cad071960ba1914fa231677d21b1b4a3acdcce463cee41ea30bc82e6040cf", size = 135688 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/08/ea54e7e245aaf0bb1c758578e5afba394ffccb8bd80d229a499b9b83f2b1/multidict-6.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f74f2fc51555f4b037ef278efc29a870d327053aba5cb7d86ae572426c7cccc", size = 142703 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/76/960dee0424f38c71eda54101ee1ca7bb47c5250ed02f7b3e8e50b1ce0603/multidict-6.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14ed9ed1bfedd72a877807c71113deac292bf485159a29025dfdc524c326f3e1", size = 138559 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/35/969fd792e2e72801d80307f0a14f5b19c066d4a51d34dded22c71401527d/multidict-6.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac3fcf9a2d369bd075b2c2965544036a27ccd277fc3c04f708338cc57533081", size = 133312 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/b8/f96657a2f744d577cfda5a7edf9da04a731b80d3239eafbfe7ca4d944695/multidict-6.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fc6af8e39f7496047c7876314f4317736eac82bf85b54c7c76cf1a6f8e35d98", size = 125652 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/9d/97696d052297d8e2e08195a25c7aae873a6186c147b7635f979edbe3acde/multidict-6.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f8cb1329f42fadfb40d6211e5ff568d71ab49be36e759345f91c69d1033d633", size = 139015 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/a0/5c106e28d42f20288c10049bc6647364287ba049dc00d6ae4f1584eb1bd1/multidict-6.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5389445f0173c197f4a3613713b5fb3f3879df1ded2a1a2e4bc4b5b9c5441b7e", size = 132437 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/57/d5c60c075fef73422ae3b8f914221485b9ff15000b2db657c03bd190aee0/multidict-6.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:94a7bb972178a8bfc4055db80c51efd24baefaced5e51c59b0d598a004e8305d", size = 144037 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/56/a23f599c697a455bf65ecb0f69a5b052d6442c567d380ed423f816246824/multidict-6.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da51d8928ad8b4244926fe862ba1795f0b6e68ed8c42cd2f822d435db9c2a8f4", size = 138535 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/3a/a06ff9b5899090f4bbdbf09e237964c76cecfe75d2aa921e801356314017/multidict-6.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:063be88bd684782a0715641de853e1e58a2f25b76388538bd62d974777ce9bc2", size = 136885 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/28/489c0eca1df3800cb5d0a66278d5dd2a4deae747a41d1cf553e6a4c0a984/multidict-6.2.0-cp311-cp311-win32.whl", hash = "sha256:52b05e21ff05729fbea9bc20b3a791c3c11da61649ff64cce8257c82a020466d", size = 27044 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/b5/c7cd5ba9581add40bc743980f82426b90d9f42db0b56502011f1b3c929df/multidict-6.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1e2a2193d3aa5cbf5758f6d5680a52aa848e0cf611da324f71e5e48a9695cc86", size = 29145 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/fd/b247aec6add5601956d440488b7f23151d8343747e82c038af37b28d6098/multidict-6.2.0-py3-none-any.whl", hash = "sha256:5d26547423e5e71dcc562c4acdc134b900640a39abd9066d7326a7cc2324c530", size = 10266 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/6f/3690028e846fe432bfa5ba724a0dc37ec9c914965b7733e19d8ca2c4c48d/minio-7.2.15-py3-none-any.whl", hash = "sha256:c06ef7a43e5d67107067f77b6c07ebdd68733e5aa7eed03076472410ca19d876", size = 95075, upload-time = "2025-01-19T08:57:24.169Z" }, ] [[package]] name = "numpy" -version = "2.2.4" +version = "2.2.5" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920, upload-time = "2025-04-19T23:27:42.561Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/fb/09e778ee3a8ea0d4dc8329cca0a9c9e65fed847d08e37eba74cb7ed4b252/numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4", size = 21254989 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/0a/1212befdbecab5d80eca3cde47d304cad986ad4eec7d85a42e0b6d2cc2ef/numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4", size = 14425910 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/3e/e7247c1d4f15086bb106c8d43c925b0b2ea20270224f5186fa48d4fb5cbd/numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f", size = 5426490 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/fa/aa7cd6be51419b894c5787a8a93c3302a1ed4f82d35beb0613ec15bdd0e2/numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880", size = 6967754 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/ee/96457c943265de9fadeb3d2ffdbab003f7fba13d971084a9876affcda095/numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1", size = 14373079 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/5c/ceefca458559f0ccc7a982319f37ed07b0d7b526964ae6cc61f8ad1b6119/numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5", size = 16428819 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/31/9b2ac8eee99e001eb6add9fa27514ef5e9faf176169057a12860af52704c/numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687", size = 15881470 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/dc/8569b5f25ff30484b555ad8a3f537e0225d091abec386c9420cf5f7a2976/numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6", size = 18218144 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/05/463c023a39bdeb9bb43a99e7dee2c664cb68d5bb87d14f92482b9f6011cc/numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09", size = 6606368 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/72/10c1d2d82101c468a28adc35de6c77b308f288cfd0b88e1070f15b98e00c/numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91", size = 12947526 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/fb/e4e4c254ba40e8f0c78218f9e86304628c75b6900509b601c8433bdb5da7/numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b", size = 21256475, upload-time = "2025-04-19T22:34:24.174Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/32/dd1f7084f5c10b2caad778258fdaeedd7fbd8afcd2510672811e6138dfac/numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda", size = 14461474, upload-time = "2025-04-19T22:34:46.578Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/65/937cdf238ef6ac54ff749c0f66d9ee2b03646034c205cea9b6c51f2f3ad1/numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d", size = 5426875, upload-time = "2025-04-19T22:34:56.281Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/17/814515fdd545b07306eaee552b65c765035ea302d17de1b9cb50852d2452/numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54", size = 6969176, upload-time = "2025-04-19T22:35:07.518Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/32/a66db7a5c8b5301ec329ab36d0ecca23f5e18907f43dbd593c8ec326d57c/numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610", size = 14374850, upload-time = "2025-04-19T22:35:31.347Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/c9/1bf6ada582eebcbe8978f5feb26584cd2b39f94ededeea034ca8f84af8c8/numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b", size = 16430306, upload-time = "2025-04-19T22:35:57.573Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/f0/3f741863f29e128f4fcfdb99253cc971406b402b4584663710ee07f5f7eb/numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be", size = 15884767, upload-time = "2025-04-19T22:36:22.245Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/d9/4ccd8fd6410f7bf2d312cbc98892e0e43c2fcdd1deae293aeb0a93b18071/numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906", size = 18219515, upload-time = "2025-04-19T22:36:49.822Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/56/783237243d4395c6dd741cf16eeb1a9035ee3d4310900e6b17e875d1b201/numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175", size = 6607842, upload-time = "2025-04-19T22:37:01.624Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/89/0c93baaf0094bdaaaa0536fe61a27b1dce8a505fa262a865ec142208cfe9/numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd", size = 12949071, upload-time = "2025-04-19T22:37:21.098Z" }, ] [[package]] name = "ollama" -version = "0.4.7" +version = "0.4.8" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "httpx" }, { name = "pydantic" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/6d/dc77539c735bbed5d0c873fb029fb86aa9f0163df169b34152914331c369/ollama-0.4.7.tar.gz", hash = "sha256:891dcbe54f55397d82d289c459de0ea897e103b86a3f1fad0fdb1895922a75ff", size = 12843 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/64/709dc99030f8f46ec552f0a7da73bbdcc2da58666abfec4742ccdb2e800e/ollama-0.4.8.tar.gz", hash = "sha256:1121439d49b96fa8339842965d0616eba5deb9f8c790786cdf4c0b3df4833802", size = 12972, upload-time = "2025-04-16T21:55:14.101Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/83/c3ffac86906c10184c88c2e916460806b072a2cfe34cdcaf3a0c0e836d39/ollama-0.4.7-py3-none-any.whl", hash = "sha256:85505663cca67a83707be5fb3aeff0ea72e67846cea5985529d8eca4366564a1", size = 13210 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/3f/164de150e983b3a16e8bf3d4355625e51a357e7b3b1deebe9cc1f7cb9af8/ollama-0.4.8-py3-none-any.whl", hash = "sha256:04312af2c5e72449aaebac4a2776f52ef010877c554103419d3f36066fe8af4c", size = 13325, upload-time = "2025-04-16T21:55:12.779Z" }, ] [[package]] name = "openai" -version = "1.68.2" +version = "1.78.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "anyio" }, @@ -795,27 +692,27 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/6b/6b002d5d38794645437ae3ddb42083059d556558493408d39a0fcea608bc/openai-1.68.2.tar.gz", hash = "sha256:b720f0a95a1dbe1429c0d9bb62096a0d98057bcda82516f6e8af10284bdd5b19", size = 413429 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/3f/4e5e7b0548a15eabc4a755c93cd5f9564887e3d2fd45b6ff531352e5859d/openai-1.78.1.tar.gz", hash = "sha256:8b26b364531b100df1b961d03560042e5f5be11301d7d49a6cd1a2b9af824dca", size = 442985, upload-time = "2025-05-12T09:59:51.098Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/34/cebce15f64eb4a3d609a83ac3568d43005cc9a1cba9d7fde5590fd415423/openai-1.68.2-py3-none-any.whl", hash = "sha256:24484cb5c9a33b58576fdc5acf0e5f92603024a4e39d0b99793dfa1eb14c2b36", size = 606073 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/4c/3889bc332a6c743751eb78a4bada5761e50a8a847ff0e46c1bd23ce12362/openai-1.78.1-py3-none-any.whl", hash = "sha256:7368bf147ca499804cc408fe68cdb6866a060f38dec961bbc97b04f9d917907e", size = 680917, upload-time = "2025-05-12T09:59:48.948Z" }, ] [[package]] name = "overrides" version = "7.7.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, ] [[package]] name = "packaging" -version = "24.2" +version = "25.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] @@ -828,210 +725,160 @@ dependencies = [ { name = "pytz" }, { name = "tzdata" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 }, -] - -[[package]] -name = "parso" -version = "0.8.4" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, -] - -[[package]] -name = "pexpect" -version = "4.9.0" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -dependencies = [ - { name = "ptyprocess" }, -] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213, upload-time = "2024-09-20T13:10:04.827Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222, upload-time = "2024-09-20T13:08:56.254Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274, upload-time = "2024-09-20T13:08:58.645Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836, upload-time = "2024-09-20T19:01:57.571Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505, upload-time = "2024-09-20T13:09:01.501Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420, upload-time = "2024-09-20T19:02:00.678Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457, upload-time = "2024-09-20T13:09:04.105Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166, upload-time = "2024-09-20T13:09:06.917Z" }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, -] - -[[package]] -name = "prompt-toolkit" -version = "3.0.50" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, -] - -[[package]] -name = "propcache" -version = "0.3.1" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/c8/fdc6686a986feae3541ea23dcaa661bd93972d3940460646c6bb96e21c40/propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf", size = 43651 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/0f/5a5319ee83bd651f75311fcb0c492c21322a7fc8f788e4eef23f44243427/propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5", size = 80243 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/84/3db5537e0879942783e2256616ff15d870a11d7ac26541336fe1b673c818/propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371", size = 46503 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/c8/b649ed972433c3f0d827d7f0cf9ea47162f4ef8f4fe98c5f3641a0bc63ff/propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da", size = 45934 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/f9/4c0a5cf6974c2c43b1a6810c40d889769cc8f84cea676cbe1e62766a45f8/propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744", size = 233633 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/64/66f2f4d1b4f0007c6e9078bd95b609b633d3957fe6dd23eac33ebde4b584/propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0", size = 241124 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/bf/7b8c9fd097d511638fa9b6af3d986adbdf567598a567b46338c925144c1b/propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5", size = 240283 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/c9/e85aeeeaae83358e2a1ef32d6ff50a483a5d5248bc38510d030a6f4e2816/propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256", size = 232498 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/66/acb88e1f30ef5536d785c283af2e62931cb934a56a3ecf39105887aa8905/propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073", size = 221486 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/f9/233ddb05ffdcaee4448508ee1d70aa7deff21bb41469ccdfcc339f871427/propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d", size = 222675 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/b8/eb977e28138f9e22a5a789daf608d36e05ed93093ef12a12441030da800a/propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f", size = 215727 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/2d/5f52d9c579f67b8ee1edd9ec073c91b23cc5b7ff7951a1e449e04ed8fdf3/propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0", size = 217878 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/fd/5283e5ed8a82b00c7a989b99bb6ea173db1ad750bf0bf8dff08d3f4a4e28/propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a", size = 230558 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/38/ab17d75938ef7ac87332c588857422ae126b1c76253f0f5b1242032923ca/propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a", size = 233754 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/5d/3b921b9c60659ae464137508d3b4c2b3f52f592ceb1964aa2533b32fcf0b/propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9", size = 226088 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/6e/30a11f4417d9266b5a464ac5a8c5164ddc9dd153dfa77bf57918165eb4ae/propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005", size = 40859 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/3a/8a68dd867da9ca2ee9dfd361093e9cb08cb0f37e5ddb2276f1b5177d7731/propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7", size = 45153 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376 }, -] - -[[package]] -name = "ptyprocess" -version = "0.7.0" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, -] - -[[package]] -name = "pure-eval" -version = "0.2.3" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] [[package]] name = "pyarrow" -version = "19.0.1" +version = "20.0.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/09/a9046344212690f0632b9c709f9bf18506522feb333c894d0de81d62341a/pyarrow-19.0.1.tar.gz", hash = "sha256:3bf266b485df66a400f282ac0b6d1b500b9d2ae73314a153dbe97d6d5cc8a99e", size = 1129437 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/ee/a7810cb9f3d6e9238e61d312076a9859bf3668fd21c69744de9532383912/pyarrow-20.0.0.tar.gz", hash = "sha256:febc4a913592573c8d5805091a6c2b5064c8bd6e002131f01061797d91c783c1", size = 1125187, upload-time = "2025-04-27T12:34:23.264Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/55/f1a8d838ec07fe3ca53edbe76f782df7b9aafd4417080eebf0b42aab0c52/pyarrow-19.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:cc55d71898ea30dc95900297d191377caba257612f384207fe9f8293b5850f90", size = 30713987 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/12/428861540bb54c98a140ae858a11f71d041ef9e501e6b7eb965ca7909505/pyarrow-19.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:7a544ec12de66769612b2d6988c36adc96fb9767ecc8ee0a4d270b10b1c51e00", size = 32135613 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/8a/23d7cc5ae2066c6c736bce1db8ea7bc9ac3ef97ac7e1c1667706c764d2d9/pyarrow-19.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0148bb4fc158bfbc3d6dfe5001d93ebeed253793fff4435167f6ce1dc4bddeae", size = 41149147 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/7a/845d151bb81a892dfb368bf11db584cf8b216963ccce40a5cf50a2492a18/pyarrow-19.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f24faab6ed18f216a37870d8c5623f9c044566d75ec586ef884e13a02a9d62c5", size = 42178045 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/31/e7282d79a70816132cf6cae7e378adfccce9ae10352d21c2fecf9d9756dd/pyarrow-19.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:4982f8e2b7afd6dae8608d70ba5bd91699077323f812a0448d8b7abdff6cb5d3", size = 40532998 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/82/20f3c290d6e705e2ee9c1fa1d5a0869365ee477e1788073d8b548da8b64c/pyarrow-19.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:49a3aecb62c1be1d822f8bf629226d4a96418228a42f5b40835c1f10d42e4db6", size = 42084055 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/77/e62aebd343238863f2c9f080ad2ef6ace25c919c6ab383436b5b81cbeef7/pyarrow-19.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:008a4009efdb4ea3d2e18f05cd31f9d43c388aad29c636112c2966605ba33466", size = 25283133 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/a2/b7930824181ceadd0c63c1042d01fa4ef63eee233934826a7a2a9af6e463/pyarrow-20.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:24ca380585444cb2a31324c546a9a56abbe87e26069189e14bdba19c86c049f0", size = 30856035, upload-time = "2025-04-27T12:28:40.78Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/18/c765770227d7f5bdfa8a69f64b49194352325c66a5c3bb5e332dfd5867d9/pyarrow-20.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:95b330059ddfdc591a3225f2d272123be26c8fa76e8c9ee1a77aad507361cfdb", size = 32309552, upload-time = "2025-04-27T12:28:47.051Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/fb/dfb2dfdd3e488bb14f822d7335653092dde150cffc2da97de6e7500681f9/pyarrow-20.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f0fb1041267e9968c6d0d2ce3ff92e3928b243e2b6d11eeb84d9ac547308232", size = 41334704, upload-time = "2025-04-27T12:28:55.064Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/0d/08a95878d38808051a953e887332d4a76bc06c6ee04351918ee1155407eb/pyarrow-20.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8ff87cc837601532cc8242d2f7e09b4e02404de1b797aee747dd4ba4bd6313f", size = 42399836, upload-time = "2025-04-27T12:29:02.13Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/cd/efa271234dfe38f0271561086eedcad7bc0f2ddd1efba423916ff0883684/pyarrow-20.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7a3a5dcf54286e6141d5114522cf31dd67a9e7c9133d150799f30ee302a7a1ab", size = 40711789, upload-time = "2025-04-27T12:29:09.951Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/1f/7f02009bc7fc8955c391defee5348f510e589a020e4b40ca05edcb847854/pyarrow-20.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a6ad3e7758ecf559900261a4df985662df54fb7fdb55e8e3b3aa99b23d526b62", size = 42301124, upload-time = "2025-04-27T12:29:17.187Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/92/692c562be4504c262089e86757a9048739fe1acb4024f92d39615e7bab3f/pyarrow-20.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6bb830757103a6cb300a04610e08d9636f0cd223d32f388418ea893a3e655f1c", size = 42916060, upload-time = "2025-04-27T12:29:24.253Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/ec/9f5c7e7c828d8e0a3c7ef50ee62eca38a7de2fa6eb1b8fa43685c9414fef/pyarrow-20.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:96e37f0766ecb4514a899d9a3554fadda770fb57ddf42b63d80f14bc20aa7db3", size = 44547640, upload-time = "2025-04-27T12:29:32.782Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/96/46613131b4727f10fd2ffa6d0d6f02efcc09a0e7374eff3b5771548aa95b/pyarrow-20.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3346babb516f4b6fd790da99b98bed9708e3f02e734c84971faccb20736848dc", size = 25781491, upload-time = "2025-04-27T12:29:38.464Z" }, ] [[package]] name = "pycparser" version = "2.22" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, ] [[package]] name = "pycryptodome" version = "3.22.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/e6/099310419df5ada522ff34ffc2f1a48a11b37fc6a76f51a6854c182dbd3e/pycryptodome-3.22.0.tar.gz", hash = "sha256:fd7ab568b3ad7b77c908d7c3f7e167ec5a8f035c64ff74f10d47a4edd043d723", size = 4917300 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/e6/099310419df5ada522ff34ffc2f1a48a11b37fc6a76f51a6854c182dbd3e/pycryptodome-3.22.0.tar.gz", hash = "sha256:fd7ab568b3ad7b77c908d7c3f7e167ec5a8f035c64ff74f10d47a4edd043d723", size = 4917300, upload-time = "2025-03-15T23:03:36.506Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/65/a05831c3e4bcd1bf6c2a034e399f74b3d6f30bb4e37e36b9c310c09dc8c0/pycryptodome-3.22.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:009e1c80eea42401a5bd5983c4bab8d516aef22e014a4705622e24e6d9d703c6", size = 2490637 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/76/ff3c2e7a60d17c080c4c6120ebaf60f38717cd387e77f84da4dcf7f64ff0/pycryptodome-3.22.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3b76fa80daeff9519d7e9f6d9e40708f2fce36b9295a847f00624a08293f4f00", size = 1635372 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/7f/cc5d6da0dbc36acd978d80a72b228e33aadaec9c4f91c93221166d8bdc05/pycryptodome-3.22.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a31fa5914b255ab62aac9265654292ce0404f6b66540a065f538466474baedbc", size = 2177456 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/65/35f5063e68790602d892ad36e35ac723147232a9084d1999630045c34593/pycryptodome-3.22.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0092fd476701eeeb04df5cc509d8b739fa381583cda6a46ff0a60639b7cd70d", size = 2263744 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/67/46acdd35b1081c3dbc72dc466b1b95b80d2f64cad3520f994a9b6c5c7d00/pycryptodome-3.22.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d5b0ddc7cf69231736d778bd3ae2b3efb681ae33b64b0c92fb4626bb48bb89", size = 2303356 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/f9/a4f8a83384626098e3f55664519bec113002b9ef751887086ae63a53135a/pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f6cf6aa36fcf463e622d2165a5ad9963b2762bebae2f632d719dfb8544903cf5", size = 2176714 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/65/e5f8c3a885f70a6e05c84844cd5542120576f4369158946e8cfc623a464d/pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:aec7b40a7ea5af7c40f8837adf20a137d5e11a6eb202cde7e588a48fb2d871a8", size = 2337329 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/2a/25e0be2b509c28375c7f75c7e8d8d060773f2cce4856a1654276e3202339/pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d21c1eda2f42211f18a25db4eaf8056c94a8563cd39da3683f89fe0d881fb772", size = 2262255 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/58/60917bc4bbd91712e53ce04daf237a74a0ad731383a01288130672994328/pycryptodome-3.22.0-cp37-abi3-win32.whl", hash = "sha256:f02baa9f5e35934c6e8dcec91fcde96612bdefef6e442813b8ea34e82c84bbfb", size = 1763403 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/f4/244c621afcf7867e23f63cfd7a9630f14cfe946c9be7e566af6c3915bcde/pycryptodome-3.22.0-cp37-abi3-win_amd64.whl", hash = "sha256:d086aed307e96d40c23c42418cbbca22ecc0ab4a8a0e24f87932eeab26c08627", size = 1794568 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/65/a05831c3e4bcd1bf6c2a034e399f74b3d6f30bb4e37e36b9c310c09dc8c0/pycryptodome-3.22.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:009e1c80eea42401a5bd5983c4bab8d516aef22e014a4705622e24e6d9d703c6", size = 2490637, upload-time = "2025-03-15T23:02:43.111Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/76/ff3c2e7a60d17c080c4c6120ebaf60f38717cd387e77f84da4dcf7f64ff0/pycryptodome-3.22.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3b76fa80daeff9519d7e9f6d9e40708f2fce36b9295a847f00624a08293f4f00", size = 1635372, upload-time = "2025-03-15T23:02:45.564Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/7f/cc5d6da0dbc36acd978d80a72b228e33aadaec9c4f91c93221166d8bdc05/pycryptodome-3.22.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a31fa5914b255ab62aac9265654292ce0404f6b66540a065f538466474baedbc", size = 2177456, upload-time = "2025-03-15T23:02:47.688Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/65/35f5063e68790602d892ad36e35ac723147232a9084d1999630045c34593/pycryptodome-3.22.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0092fd476701eeeb04df5cc509d8b739fa381583cda6a46ff0a60639b7cd70d", size = 2263744, upload-time = "2025-03-15T23:02:49.548Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/67/46acdd35b1081c3dbc72dc466b1b95b80d2f64cad3520f994a9b6c5c7d00/pycryptodome-3.22.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d5b0ddc7cf69231736d778bd3ae2b3efb681ae33b64b0c92fb4626bb48bb89", size = 2303356, upload-time = "2025-03-15T23:02:52.122Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/f9/a4f8a83384626098e3f55664519bec113002b9ef751887086ae63a53135a/pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f6cf6aa36fcf463e622d2165a5ad9963b2762bebae2f632d719dfb8544903cf5", size = 2176714, upload-time = "2025-03-15T23:02:53.85Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/65/e5f8c3a885f70a6e05c84844cd5542120576f4369158946e8cfc623a464d/pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:aec7b40a7ea5af7c40f8837adf20a137d5e11a6eb202cde7e588a48fb2d871a8", size = 2337329, upload-time = "2025-03-15T23:02:56.11Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/2a/25e0be2b509c28375c7f75c7e8d8d060773f2cce4856a1654276e3202339/pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d21c1eda2f42211f18a25db4eaf8056c94a8563cd39da3683f89fe0d881fb772", size = 2262255, upload-time = "2025-03-15T23:02:58.055Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/58/60917bc4bbd91712e53ce04daf237a74a0ad731383a01288130672994328/pycryptodome-3.22.0-cp37-abi3-win32.whl", hash = "sha256:f02baa9f5e35934c6e8dcec91fcde96612bdefef6e442813b8ea34e82c84bbfb", size = 1763403, upload-time = "2025-03-15T23:03:00.616Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/f4/244c621afcf7867e23f63cfd7a9630f14cfe946c9be7e566af6c3915bcde/pycryptodome-3.22.0-cp37-abi3-win_amd64.whl", hash = "sha256:d086aed307e96d40c23c42418cbbca22ecc0ab4a8a0e24f87932eeab26c08627", size = 1794568, upload-time = "2025-03-15T23:03:03.189Z" }, ] [[package]] name = "pydantic" -version = "2.10.6" +version = "2.11.4" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/ab/5250d56ad03884ab5efd07f734203943c8a8ab40d551e208af81d0257bf2/pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d", size = 786540, upload-time = "2025-04-29T20:38:55.02Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/12/46b65f3534d099349e38ef6ec98b1a5a81f42536d17e0ba382c28c67ba67/pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb", size = 443900, upload-time = "2025-04-29T20:38:52.724Z" }, ] [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.33.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.9.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, ] [[package]] name = "pygments" version = "2.19.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, ] [[package]] name = "pymongo" -version = "4.11.3" +version = "4.12.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "dnspython" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/e6/cdb1105c14a86aa2b1663a6cccc6bf54722bb12fb5d479979628142dde42/pymongo-4.11.3.tar.gz", hash = "sha256:b6f24aec7c0cfcf0ea9f89e92b7d40ba18a1e18c134815758f111ecb0122e61c", size = 2054848 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/27/3634b2e8d88ad210ee6edac69259c698aefed4a79f0f7356cd625d5c423c/pymongo-4.12.1.tar.gz", hash = "sha256:8921bac7f98cccb593d76c4d8eaa1447e7d537ba9a2a202973e92372a05bd1eb", size = 2165515, upload-time = "2025-04-29T18:46:23.62Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/9a/11d68ecb0260454e46404302c5a1cb16d93c0d9ad0c8a7bc4df1859f95a7/pymongo-4.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31b5ad4ce148b201fa8426d0767517dc68424c3380ef4a981038d4d4350f10ee", size = 840506 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/db/bfe487b1b1b6c3e86b8152845550d7db15476c12516f5093ec122d840602/pymongo-4.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:505fb3facf54623b45c96e8e6ad6516f58bb8069f9456e1d7c0abdfdb6929c21", size = 840798 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/4b/d1378adbac16829745e57781b140ab7cdbd1046a18cdb796e3adf280c963/pymongo-4.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3f20467d695f49ce4c2d6cb87de458ebb3d098cbc951834a74f36a2e992a6bb", size = 1409884 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/97/4882a0b6be225d0358b431e6d0fe70fba368b2cedabf38c005f2a73917c9/pymongo-4.11.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65e8a397b03156880a099d55067daa1580a5333aaf4da3b0313bd7e1731e408f", size = 1460828 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/a8/fde60995524f5b2794bdf07cad98f5b369a3cfa7e90b6ec081fc57d3b5ea/pymongo-4.11.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0992917ed259f5ca3506ec8009e7c82d398737a4230a607bf44d102cae31e1d6", size = 1435261 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/42/d0ac7f445edd6abf5c7197ad83d9902ad1e8f4be767af257bd892684560a/pymongo-4.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f2f0c3ab8284e0e2674367fa47774411212c86482bbbe78e8ae9fb223b8f6ee", size = 1414380 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/02/dd67685b67f7408ed72d801b268988986343208f712b0e90c639358b2d19/pymongo-4.11.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2240126683f55160f83f587d76955ad1e419a72d5c09539a509bd9d1e20bd53", size = 1383026 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/60/07f61ad5ddd39c4d52466ac1ce089c0c8c3d337145efcadbfa61072b1913/pymongo-4.11.3-cp311-cp311-win32.whl", hash = "sha256:be89776c5b8272437a85c904d45e0f1bbc0f21bf11688341938380843dd7fe5f", size = 817664 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/f3/073f763f6673ecfb33c13568037cdba499284758cfa54c556cac8a406cb7/pymongo-4.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:c237780760f891cae79abbfc52fda55b584492d5d9452762040aadb2c64ac691", size = 831617 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/af/2cf9a4871615481841b791eb8f3cdf3e5b909ff66c258ae23cd91da90006/pymongo-4.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72b45f7e72b2db4cd7abd40c38c57ed4105d7be0d4dce85a6b77a730e8a613f7", size = 855938, upload-time = "2025-04-29T18:44:39.746Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/78/3196c9d9b08dbf5c600b485f610821cd755f978bfb9c51f51651139d0231/pymongo-4.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0f3104bd97642f508f70a83af256b9d88e9a7319e8048c27f1c8ca6572ad7b7f", size = 856228, upload-time = "2025-04-29T18:44:41.576Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/38/63a7cf5e76c99ee31fa9cadd5570525338e5a2fe5e8085a5e3af85cf4fa7/pymongo-4.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:730a19d96ef902ee8d8f9e84738142d355096becb677ec82489dc9ad8e54d8e9", size = 1425307, upload-time = "2025-04-29T18:44:43.708Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/f9/2d8a0d78da2891ea744fac4a9435640e3fa47d23eb68a1b346819d930d78/pymongo-4.12.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40dd2b771387e3ac297399b7b4d9a4bfffbaabba6f17c79996e8462cde3e7c30", size = 1476260, upload-time = "2025-04-29T18:44:45.651Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/1e/b5b6a994862e732f83638b8bc94e68bca1b135a2bcf1b21c5661eb49e5c6/pymongo-4.12.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5e5968da22f5534fc678dad58d3e9f7305bf53abc94968c800335b1f511ab8b", size = 1450686, upload-time = "2025-04-29T18:44:48.175Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/2a/56e0c160c19e2c8b7c7ae541db8acc17ef8091cec8d419be7c31ec20d2ee/pymongo-4.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc5fad32274a1de9dfe13d06da169cf2a405a98f049595aafda13af02921853e", size = 1429812, upload-time = "2025-04-29T18:44:49.949Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/34/bac2dfc038a7fb5e0520ab6803eebc7d05dc7dfe0629c442baceb4f43d95/pymongo-4.12.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:808168f5f4398c0057d15f21b1453de323157447915179c7afedf4334d2a1815", size = 1398454, upload-time = "2025-04-29T18:44:51.92Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/3d/2c9ef7bed070025438e08201323a08360a98381b01b9bae49781b9d27815/pymongo-4.12.1-cp311-cp311-win32.whl", hash = "sha256:ee69dba3e023e0fa1b547b4f7a41182618f2e612df09ff954bba32de0111a596", size = 833157, upload-time = "2025-04-29T18:44:54.118Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/5d/3e1f7177387ec84e1a1538d29eef05791ceb19cbb80b09a2ae9b74ad518c/pymongo-4.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:40e2812e5b546f7ceef4abf82c31d4790d9878f2a0d43a67a2645de3eb06bdca", size = 847091, upload-time = "2025-04-29T18:44:55.85Z" }, ] [[package]] @@ -1044,9 +891,9 @@ dependencies = [ { name = "packaging" }, { name = "pluggy" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, ] [[package]] @@ -1056,9 +903,9 @@ source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814, upload-time = "2024-03-21T22:14:04.964Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863, upload-time = "2024-03-21T22:14:02.694Z" }, ] [[package]] @@ -1068,62 +915,71 @@ source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, ] [[package]] name = "python-jsonpath" version = "1.3.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/45/0ae98e958fc53cfdc544bb45262d9e8786f415aac7eb35f94a79af740773/python_jsonpath-1.3.0.tar.gz", hash = "sha256:ea5eb4d9b1296c8c19cc53538eb0f20fc54128f84571559ee63539e57875fefe", size = 43050 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/45/0ae98e958fc53cfdc544bb45262d9e8786f415aac7eb35f94a79af740773/python_jsonpath-1.3.0.tar.gz", hash = "sha256:ea5eb4d9b1296c8c19cc53538eb0f20fc54128f84571559ee63539e57875fefe", size = 43050, upload-time = "2025-02-04T08:40:29.051Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/9d/5eed195961588f930db3008199b8023063ebd0253b9de9f0f13f3763b819/python_jsonpath-1.3.0-py3-none-any.whl", hash = "sha256:ce586ec5bd934ce97bc2f06600b00437d9684138b77273ced5b70694a8ef3a76", size = 54354 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/9d/5eed195961588f930db3008199b8023063ebd0253b9de9f0f13f3763b819/python_jsonpath-1.3.0-py3-none-any.whl", hash = "sha256:ce586ec5bd934ce97bc2f06600b00437d9684138b77273ced5b70694a8ef3a76", size = 54354, upload-time = "2025-02-04T08:40:26.979Z" }, ] [[package]] name = "python-magic" version = "0.4.27" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677, upload-time = "2022-06-07T20:16:59.508Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840, upload-time = "2022-06-07T20:16:57.763Z" }, ] [[package]] name = "python-multipart" version = "0.0.20" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, ] [[package]] name = "pytz" version = "2025.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] [[package]] name = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, ] [[package]] @@ -1135,32 +991,32 @@ dependencies = [ { name = "rpds-py" }, { name = "typing-extensions" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, ] [[package]] name = "regex" version = "2024.11.6" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494, upload-time = "2024-11-06T20:12:31.635Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669, upload-time = "2024-11-06T20:09:31.064Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684, upload-time = "2024-11-06T20:09:32.915Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589, upload-time = "2024-11-06T20:09:35.504Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121, upload-time = "2024-11-06T20:09:37.701Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275, upload-time = "2024-11-06T20:09:40.371Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257, upload-time = "2024-11-06T20:09:43.059Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727, upload-time = "2024-11-06T20:09:48.19Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667, upload-time = "2024-11-06T20:09:49.828Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963, upload-time = "2024-11-06T20:09:51.819Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700, upload-time = "2024-11-06T20:09:53.982Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592, upload-time = "2024-11-06T20:09:56.222Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929, upload-time = "2024-11-06T20:09:58.642Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213, upload-time = "2024-11-06T20:10:00.867Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734, upload-time = "2024-11-06T20:10:03.361Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052, upload-time = "2024-11-06T20:10:05.179Z" }, ] [[package]] @@ -1173,9 +1029,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, ] [[package]] @@ -1186,147 +1042,244 @@ dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149, upload-time = "2024-11-01T16:43:57.873Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424, upload-time = "2024-11-01T16:43:55.817Z" }, +] + +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, ] [[package]] name = "rpds-py" version = "0.24.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/b3/52b213298a0ba7097c7ea96bee95e1947aa84cc816d48cebb539770cdf41/rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e", size = 26863 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/e6/c1458bbfb257448fdb2528071f1f4e19e26798ed5ef6d47d7aab0cb69661/rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef", size = 377679 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/26/ea4181ef78f58b2c167548c6a833d7dc22408e5b3b181bda9dda440bb92d/rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97", size = 362571 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/fa/1ec54dd492c64c280a2249a047fc3369e2789dc474eac20445ebfc72934b/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e", size = 388012 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/be/bad8b0e0f7e58ef4973bb75e91c472a7d51da1977ed43b09989264bf065c/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d", size = 394730 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/56/ab417fc90c21826df048fc16e55316ac40876e4b790104ececcbce813d8f/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586", size = 448264 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/75/4c63862d5c05408589196c8440a35a14ea4ae337fa70ded1f03638373f06/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4", size = 446813 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/0c/91cf17dffa9a38835869797a9f041056091ebba6a53963d3641207e3d467/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae", size = 389438 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/b0/60e6c72727c978276e02851819f3986bc40668f115be72c1bc4d922c950f/rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc", size = 420416 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/d7/f46f85b9f863fb59fd3c534b5c874c48bee86b19e93423b9da8784605415/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c", size = 565236 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/d1/1467620ded6dd70afc45ec822cdf8dfe7139537780d1f3905de143deb6fd/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c", size = 592016 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/13/fb1ded2e6adfaa0c0833106c42feb290973f665300f4facd5bf5d7891d9c/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718", size = 560123 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/df/09fc1857ac7cc2eb16465a7199c314cbce7edde53c8ef21d615410d7335b/rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a", size = 222256 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/25/939b40bc4d54bf910e5ee60fb5af99262c92458f4948239e8c06b0b750e7/rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6", size = 234718 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/53/40bcc246a8354530d51a26d2b5b9afd1deacfb0d79e67295cc74df362f52/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d", size = 378386 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/b0/5ea97dd2f53e3618560aa1f9674e896e63dff95a9b796879a201bc4c1f00/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a", size = 363440 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/9d/259b6eada6f747cdd60c9a5eb3efab15f6704c182547149926c38e5bd0d5/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5", size = 388816 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/c1/faafc7183712f89f4b7620c3c15979ada13df137d35ef3011ae83e93b005/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d", size = 395058 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/96/d7fa9d2a7b7604a61da201cc0306a355006254942093779d7121c64700ce/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793", size = 448692 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/37/a3146c6eebc65d6d8c96cc5ffdcdb6af2987412c789004213227fbe52467/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba", size = 446462 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/13/6481dfd9ac7de43acdaaa416e3a7da40bc4bb8f5c6ca85e794100aa54596/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea", size = 390460 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/e1/37e36bce65e109543cc4ff8d23206908649023549604fa2e7fbeba5342f7/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032", size = 421609 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/dd/1f1a923d6cd798b8582176aca8a0784676f1a0449fb6f07fce6ac1cdbfb6/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d", size = 565818 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/ec/d8da6df6a1eb3a418944a17b1cb38dd430b9e5a2e972eafd2b06f10c7c46/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25", size = 592627 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/14/c492b9c7d5dd133e13f211ddea6bb9870f99e4f73932f11aa00bc09a9be9/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba", size = 560885 }, +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/b3/52b213298a0ba7097c7ea96bee95e1947aa84cc816d48cebb539770cdf41/rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e", size = 26863, upload-time = "2025-03-26T14:56:01.518Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/e6/c1458bbfb257448fdb2528071f1f4e19e26798ed5ef6d47d7aab0cb69661/rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef", size = 377679, upload-time = "2025-03-26T14:53:06.557Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/26/ea4181ef78f58b2c167548c6a833d7dc22408e5b3b181bda9dda440bb92d/rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97", size = 362571, upload-time = "2025-03-26T14:53:08.439Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/fa/1ec54dd492c64c280a2249a047fc3369e2789dc474eac20445ebfc72934b/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e", size = 388012, upload-time = "2025-03-26T14:53:10.314Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/be/bad8b0e0f7e58ef4973bb75e91c472a7d51da1977ed43b09989264bf065c/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d", size = 394730, upload-time = "2025-03-26T14:53:11.953Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/56/ab417fc90c21826df048fc16e55316ac40876e4b790104ececcbce813d8f/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586", size = 448264, upload-time = "2025-03-26T14:53:13.42Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/75/4c63862d5c05408589196c8440a35a14ea4ae337fa70ded1f03638373f06/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4", size = 446813, upload-time = "2025-03-26T14:53:15.036Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/0c/91cf17dffa9a38835869797a9f041056091ebba6a53963d3641207e3d467/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae", size = 389438, upload-time = "2025-03-26T14:53:17.037Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/b0/60e6c72727c978276e02851819f3986bc40668f115be72c1bc4d922c950f/rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc", size = 420416, upload-time = "2025-03-26T14:53:18.671Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/d7/f46f85b9f863fb59fd3c534b5c874c48bee86b19e93423b9da8784605415/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c", size = 565236, upload-time = "2025-03-26T14:53:20.357Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/d1/1467620ded6dd70afc45ec822cdf8dfe7139537780d1f3905de143deb6fd/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c", size = 592016, upload-time = "2025-03-26T14:53:22.216Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/13/fb1ded2e6adfaa0c0833106c42feb290973f665300f4facd5bf5d7891d9c/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718", size = 560123, upload-time = "2025-03-26T14:53:23.733Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/df/09fc1857ac7cc2eb16465a7199c314cbce7edde53c8ef21d615410d7335b/rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a", size = 222256, upload-time = "2025-03-26T14:53:25.217Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/25/939b40bc4d54bf910e5ee60fb5af99262c92458f4948239e8c06b0b750e7/rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6", size = 234718, upload-time = "2025-03-26T14:53:26.631Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/53/40bcc246a8354530d51a26d2b5b9afd1deacfb0d79e67295cc74df362f52/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d", size = 378386, upload-time = "2025-03-26T14:55:20.381Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/b0/5ea97dd2f53e3618560aa1f9674e896e63dff95a9b796879a201bc4c1f00/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a", size = 363440, upload-time = "2025-03-26T14:55:22.121Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/9d/259b6eada6f747cdd60c9a5eb3efab15f6704c182547149926c38e5bd0d5/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5", size = 388816, upload-time = "2025-03-26T14:55:23.737Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/c1/faafc7183712f89f4b7620c3c15979ada13df137d35ef3011ae83e93b005/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d", size = 395058, upload-time = "2025-03-26T14:55:25.468Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/96/d7fa9d2a7b7604a61da201cc0306a355006254942093779d7121c64700ce/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793", size = 448692, upload-time = "2025-03-26T14:55:27.535Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/37/a3146c6eebc65d6d8c96cc5ffdcdb6af2987412c789004213227fbe52467/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba", size = 446462, upload-time = "2025-03-26T14:55:29.299Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/13/6481dfd9ac7de43acdaaa416e3a7da40bc4bb8f5c6ca85e794100aa54596/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea", size = 390460, upload-time = "2025-03-26T14:55:31.017Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/e1/37e36bce65e109543cc4ff8d23206908649023549604fa2e7fbeba5342f7/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032", size = 421609, upload-time = "2025-03-26T14:55:32.84Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/dd/1f1a923d6cd798b8582176aca8a0784676f1a0449fb6f07fce6ac1cdbfb6/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d", size = 565818, upload-time = "2025-03-26T14:55:34.538Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/ec/d8da6df6a1eb3a418944a17b1cb38dd430b9e5a2e972eafd2b06f10c7c46/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25", size = 592627, upload-time = "2025-03-26T14:55:36.26Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/14/c492b9c7d5dd133e13f211ddea6bb9870f99e4f73932f11aa00bc09a9be9/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba", size = 560885, upload-time = "2025-03-26T14:55:38Z" }, ] [[package]] name = "ruff" version = "0.11.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/61/fb87430f040e4e577e784e325351186976516faef17d6fcd921fe28edfd7/ruff-0.11.2.tar.gz", hash = "sha256:ec47591497d5a1050175bdf4e1a4e6272cddff7da88a2ad595e1e326041d8d94", size = 3857511 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/61/fb87430f040e4e577e784e325351186976516faef17d6fcd921fe28edfd7/ruff-0.11.2.tar.gz", hash = "sha256:ec47591497d5a1050175bdf4e1a4e6272cddff7da88a2ad595e1e326041d8d94", size = 3857511, upload-time = "2025-03-21T13:31:17.419Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/99/102578506f0f5fa29fd7e0df0a273864f79af044757aef73d1cae0afe6ad/ruff-0.11.2-py3-none-linux_armv6l.whl", hash = "sha256:c69e20ea49e973f3afec2c06376eb56045709f0212615c1adb0eda35e8a4e477", size = 10113146 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/ad/5cd4ba58ab602a579997a8494b96f10f316e874d7c435bcc1a92e6da1b12/ruff-0.11.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2c5424cc1c4eb1d8ecabe6d4f1b70470b4f24a0c0171356290b1953ad8f0e272", size = 10867092 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/3e/d3f13619e1d152c7b600a38c1a035e833e794c6625c9a6cea6f63dbf3af4/ruff-0.11.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf20854cc73f42171eedb66f006a43d0a21bfb98a2523a809931cda569552d9", size = 10224082 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/06/f77b3d790d24a93f38e3806216f263974909888fd1e826717c3ec956bbcd/ruff-0.11.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c543bf65d5d27240321604cee0633a70c6c25c9a2f2492efa9f6d4b8e4199bb", size = 10394818 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/7f/78aa431d3ddebfc2418cd95b786642557ba8b3cb578c075239da9ce97ff9/ruff-0.11.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20967168cc21195db5830b9224be0e964cc9c8ecf3b5a9e3ce19876e8d3a96e3", size = 9952251 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/3e/f11186d1ddfaca438c3bbff73c6a2fdb5b60e6450cc466129c694b0ab7a2/ruff-0.11.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:955a9ce63483999d9f0b8f0b4a3ad669e53484232853054cc8b9d51ab4c5de74", size = 11563566 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/6c/6ca91befbc0a6539ee133d9a9ce60b1a354db12c3c5d11cfdbf77140f851/ruff-0.11.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:86b3a27c38b8fce73bcd262b0de32e9a6801b76d52cdb3ae4c914515f0cef608", size = 12208721 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/b0/24516a3b850d55b17c03fc399b681c6a549d06ce665915721dc5d6458a5c/ruff-0.11.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3b66a03b248c9fcd9d64d445bafdf1589326bee6fc5c8e92d7562e58883e30f", size = 11662274 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/65/76be06d28ecb7c6070280cef2bcb20c98fbf99ff60b1c57d2fb9b8771348/ruff-0.11.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0397c2672db015be5aa3d4dac54c69aa012429097ff219392c018e21f5085147", size = 13792284 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/d2/4ceed7147e05852876f3b5f3fdc23f878ce2b7e0b90dd6e698bda3d20787/ruff-0.11.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869bcf3f9abf6457fbe39b5a37333aa4eecc52a3b99c98827ccc371a8e5b6f1b", size = 11327861 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/78/4935ecba13706fd60ebe0e3dc50371f2bdc3d9bc80e68adc32ff93914534/ruff-0.11.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2a2b50ca35457ba785cd8c93ebbe529467594087b527a08d487cf0ee7b3087e9", size = 10276560 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/7f/1b2435c3f5245d410bb5dc80f13ec796454c21fbda12b77d7588d5cf4e29/ruff-0.11.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7c69c74bf53ddcfbc22e6eb2f31211df7f65054bfc1f72288fc71e5f82db3eab", size = 9945091 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/c4/692284c07e6bf2b31d82bb8c32f8840f9d0627d92983edaac991a2b66c0a/ruff-0.11.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6e8fb75e14560f7cf53b15bbc55baf5ecbe373dd5f3aab96ff7aa7777edd7630", size = 10977133 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/cf/8ab81cb7dd7a3b0a3960c2769825038f3adcd75faf46dd6376086df8b128/ruff-0.11.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:842a472d7b4d6f5924e9297aa38149e5dcb1e628773b70e6387ae2c97a63c58f", size = 11378514 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/3a/a647fa4f316482dacf2fd68e8a386327a33d6eabd8eb2f9a0c3d291ec549/ruff-0.11.2-py3-none-win32.whl", hash = "sha256:aca01ccd0eb5eb7156b324cfaa088586f06a86d9e5314b0eb330cb48415097cc", size = 10319835 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/54/3c12d3af58012a5e2cd7ebdbe9983f4834af3f8cbea0e8a8c74fa1e23b2b/ruff-0.11.2-py3-none-win_amd64.whl", hash = "sha256:3170150172a8f994136c0c66f494edf199a0bbea7a409f649e4bc8f4d7084080", size = 11373713 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/d4/dd813703af8a1e2ac33bf3feb27e8a5ad514c9f219df80c64d69807e7f71/ruff-0.11.2-py3-none-win_arm64.whl", hash = "sha256:52933095158ff328f4c77af3d74f0379e34fd52f175144cefc1b192e7ccd32b4", size = 10441990 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/99/102578506f0f5fa29fd7e0df0a273864f79af044757aef73d1cae0afe6ad/ruff-0.11.2-py3-none-linux_armv6l.whl", hash = "sha256:c69e20ea49e973f3afec2c06376eb56045709f0212615c1adb0eda35e8a4e477", size = 10113146, upload-time = "2025-03-21T13:30:26.68Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/ad/5cd4ba58ab602a579997a8494b96f10f316e874d7c435bcc1a92e6da1b12/ruff-0.11.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2c5424cc1c4eb1d8ecabe6d4f1b70470b4f24a0c0171356290b1953ad8f0e272", size = 10867092, upload-time = "2025-03-21T13:30:37.949Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/3e/d3f13619e1d152c7b600a38c1a035e833e794c6625c9a6cea6f63dbf3af4/ruff-0.11.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf20854cc73f42171eedb66f006a43d0a21bfb98a2523a809931cda569552d9", size = 10224082, upload-time = "2025-03-21T13:30:39.962Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/06/f77b3d790d24a93f38e3806216f263974909888fd1e826717c3ec956bbcd/ruff-0.11.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c543bf65d5d27240321604cee0633a70c6c25c9a2f2492efa9f6d4b8e4199bb", size = 10394818, upload-time = "2025-03-21T13:30:42.551Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/7f/78aa431d3ddebfc2418cd95b786642557ba8b3cb578c075239da9ce97ff9/ruff-0.11.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20967168cc21195db5830b9224be0e964cc9c8ecf3b5a9e3ce19876e8d3a96e3", size = 9952251, upload-time = "2025-03-21T13:30:45.196Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/3e/f11186d1ddfaca438c3bbff73c6a2fdb5b60e6450cc466129c694b0ab7a2/ruff-0.11.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:955a9ce63483999d9f0b8f0b4a3ad669e53484232853054cc8b9d51ab4c5de74", size = 11563566, upload-time = "2025-03-21T13:30:47.516Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/6c/6ca91befbc0a6539ee133d9a9ce60b1a354db12c3c5d11cfdbf77140f851/ruff-0.11.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:86b3a27c38b8fce73bcd262b0de32e9a6801b76d52cdb3ae4c914515f0cef608", size = 12208721, upload-time = "2025-03-21T13:30:49.56Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/b0/24516a3b850d55b17c03fc399b681c6a549d06ce665915721dc5d6458a5c/ruff-0.11.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3b66a03b248c9fcd9d64d445bafdf1589326bee6fc5c8e92d7562e58883e30f", size = 11662274, upload-time = "2025-03-21T13:30:52.055Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/65/76be06d28ecb7c6070280cef2bcb20c98fbf99ff60b1c57d2fb9b8771348/ruff-0.11.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0397c2672db015be5aa3d4dac54c69aa012429097ff219392c018e21f5085147", size = 13792284, upload-time = "2025-03-21T13:30:54.24Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/d2/4ceed7147e05852876f3b5f3fdc23f878ce2b7e0b90dd6e698bda3d20787/ruff-0.11.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869bcf3f9abf6457fbe39b5a37333aa4eecc52a3b99c98827ccc371a8e5b6f1b", size = 11327861, upload-time = "2025-03-21T13:30:56.757Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/78/4935ecba13706fd60ebe0e3dc50371f2bdc3d9bc80e68adc32ff93914534/ruff-0.11.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2a2b50ca35457ba785cd8c93ebbe529467594087b527a08d487cf0ee7b3087e9", size = 10276560, upload-time = "2025-03-21T13:30:58.881Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/7f/1b2435c3f5245d410bb5dc80f13ec796454c21fbda12b77d7588d5cf4e29/ruff-0.11.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7c69c74bf53ddcfbc22e6eb2f31211df7f65054bfc1f72288fc71e5f82db3eab", size = 9945091, upload-time = "2025-03-21T13:31:01.45Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/c4/692284c07e6bf2b31d82bb8c32f8840f9d0627d92983edaac991a2b66c0a/ruff-0.11.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6e8fb75e14560f7cf53b15bbc55baf5ecbe373dd5f3aab96ff7aa7777edd7630", size = 10977133, upload-time = "2025-03-21T13:31:04.013Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/cf/8ab81cb7dd7a3b0a3960c2769825038f3adcd75faf46dd6376086df8b128/ruff-0.11.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:842a472d7b4d6f5924e9297aa38149e5dcb1e628773b70e6387ae2c97a63c58f", size = 11378514, upload-time = "2025-03-21T13:31:06.166Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/3a/a647fa4f316482dacf2fd68e8a386327a33d6eabd8eb2f9a0c3d291ec549/ruff-0.11.2-py3-none-win32.whl", hash = "sha256:aca01ccd0eb5eb7156b324cfaa088586f06a86d9e5314b0eb330cb48415097cc", size = 10319835, upload-time = "2025-03-21T13:31:10.7Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/54/3c12d3af58012a5e2cd7ebdbe9983f4834af3f8cbea0e8a8c74fa1e23b2b/ruff-0.11.2-py3-none-win_amd64.whl", hash = "sha256:3170150172a8f994136c0c66f494edf199a0bbea7a409f649e4bc8f4d7084080", size = 11373713, upload-time = "2025-03-21T13:31:13.148Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/d4/dd813703af8a1e2ac33bf3feb27e8a5ad514c9f219df80c64d69807e7f71/ruff-0.11.2-py3-none-win_arm64.whl", hash = "sha256:52933095158ff328f4c77af3d74f0379e34fd52f175144cefc1b192e7ccd32b4", size = 10441990, upload-time = "2025-03-21T13:31:15.206Z" }, ] [[package]] -name = "setproctitle" -version = "1.3.5" +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/4d/6a840c8d2baa07b57329490e7094f90aac177a1d5226bc919046f1106860/setproctitle-1.3.5.tar.gz", hash = "sha256:1e6eaeaf8a734d428a95d8c104643b39af7d247d604f40a7bebcf3960a853c5e", size = 26737 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/4a/9e0243c5df221102fb834a947f5753d9da06ad5f84e36b0e2e93f7865edb/setproctitle-1.3.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1c8dcc250872385f2780a5ea58050b58cbc8b6a7e8444952a5a65c359886c593", size = 17256 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/a1/76ad2ba6f5bd00609238e3d64eeded4598e742a5f25b5cc1a0efdae5f674/setproctitle-1.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ca82fae9eb4800231dd20229f06e8919787135a5581da245b8b05e864f34cc8b", size = 11893 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/3a/75d11fedff5b21ba9a4c5fe3dfa5e596f831d094ef1896713a72e9e38833/setproctitle-1.3.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0424e1d33232322541cb36fb279ea5242203cd6f20de7b4fb2a11973d8e8c2ce", size = 31631 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/12/58220de5600e0ed2e5562297173187d863db49babb03491ffe9c101299bc/setproctitle-1.3.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fec8340ab543144d04a9d805d80a0aad73fdeb54bea6ff94e70d39a676ea4ec0", size = 32975 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/c4/fbb308680d83c1c7aa626950308318c6e6381a8273779163a31741f3c752/setproctitle-1.3.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eab441c89f181271ab749077dcc94045a423e51f2fb0b120a1463ef9820a08d0", size = 30126 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/6e/baaf70bd9a881dd8c12cbccdd7ca0ff291024a37044a8245e942e12e7135/setproctitle-1.3.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c371550a2288901a0dcd84192691ebd3197a43c95f3e0b396ed6d1cedf5c6c", size = 31135 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/dc/d8ab6b1c3d844dc14f596e3cce76604570848f8a67ba6a3812775ed2c015/setproctitle-1.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78288ff5f9c415c56595b2257ad218936dd9fa726b36341b373b31ca958590fe", size = 30874 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/84/62a359b3aa51228bd88f78b44ebb0256a5b96dd2487881c1e984a59b617d/setproctitle-1.3.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f1f13a25fc46731acab518602bb1149bfd8b5fabedf8290a7c0926d61414769d", size = 29893 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/d6/b3c52c03ee41e7f006e1a737e0db1c58d1dc28e258b83548e653d0c34f1c/setproctitle-1.3.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1534d6cd3854d035e40bf4c091984cbdd4d555d7579676d406c53c8f187c006f", size = 32293 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/09/c0ba311879d9c05860503a7e2708ace85913b9a816786402a92c664fe930/setproctitle-1.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62a01c76708daac78b9688ffb95268c57cb57fa90b543043cda01358912fe2db", size = 30247 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/43/cc7155461f0b5a48aebdb87d78239ff3a51ebda0905de478d9fa6ab92d9c/setproctitle-1.3.5-cp311-cp311-win32.whl", hash = "sha256:ea07f29735d839eaed985990a0ec42c8aecefe8050da89fec35533d146a7826d", size = 11476 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/57/6e937ac7aa52db69225f02db2cfdcb66ba1db6fdc65a4ddbdf78e214f72a/setproctitle-1.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:ab3ae11e10d13d514d4a5a15b4f619341142ba3e18da48c40e8614c5a1b5e3c3", size = 12189 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, ] [[package]] -name = "sglang" -version = "0.4.4.post2" +name = "sphinx" +version = "8.2.3" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ - { name = "aiohttp" }, - { name = "ipython" }, - { name = "numpy" }, + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, { name = "requests" }, - { name = "setproctitle" }, - { name = "tqdm" }, + { name = "roman-numerals-py" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/f8/5a8c14fd7de017169f0fdd7a62d023504e3d5f8a1ec7559a70df0901e4f5/sglang-0.4.4.post2.tar.gz", hash = "sha256:ef2ad22e4590cfbaebb93a76d1b79c5a246ff8667dcd93447397d30e5e037dc8", size = 773434 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/73/5e94ae142b32b4dd98124373ea9061e9d9db35b170118b3c24b73e218102/sglang-0.4.4.post2-py3-none-any.whl", hash = "sha256:ab2f84ab9d7308f5aaf52fa420ba0dbe8bb146933617a07563a86e82e704f026", size = 1108191 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, ] [[package]] -name = "six" -version = "1.17.0" +name = "sphinx-rtd-theme" +version = "3.0.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +dependencies = [ + { name = "docutils" }, + { name = "sphinx" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/44/c97faec644d29a5ceddd3020ae2edffa69e7d00054a8c7a6021e82f20335/sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85", size = 7620463, upload-time = "2024-11-13T11:06:04.545Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/77/46e3bac77b82b4df5bb5b61f2de98637724f246b4966cfc34bc5895d852a/sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13", size = 7655561, upload-time = "2024-11-13T11:06:02.094Z" }, ] [[package]] -name = "sniffio" -version = "1.3.1" +name = "sphinxcontrib-applehelp" +version = "2.0.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, ] [[package]] -name = "stack-data" -version = "0.6.3" +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ - { name = "asttokens" }, - { name = "executing" }, - { name = "pure-eval" }, + { name = "sphinx" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "sqids" +version = "0.5.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/12/5fdb4ab1390345220a0c3065e0d3cf7db1d74695d899bb381ac45564ac6b/sqids-0.5.1.tar.gz", hash = "sha256:d69d06b504e5be6fe9523c8bb56104cdd071c39ea96008312f3db1038b1f08be", size = 18138, upload-time = "2024-12-29T22:30:29.076Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/0b/0d732c45f9d2b0d7e5fa216691477c77ecd2b7f7205605cf414a4b46701c/sqids-0.5.1-py3-none-any.whl", hash = "sha256:2e93a35ce25d417f83135fa591c7ea8befbca148a783eb22806e763443e81388", size = 8829, upload-time = "2024-12-29T22:30:26.456Z" }, +] + +[[package]] +name = "sse-starlette" +version = "2.3.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/5f/28f45b1ff14bee871bacafd0a97213f7ec70e389939a80c60c0fb72a9fc9/sse_starlette-2.3.5.tar.gz", hash = "sha256:228357b6e42dcc73a427990e2b4a03c023e2495ecee82e14f07ba15077e334b2", size = 17511, upload-time = "2025-05-12T18:23:52.601Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/48/3e49cf0f64961656402c0023edbc51844fe17afe53ab50e958a6dbbbd499/sse_starlette-2.3.5-py3-none-any.whl", hash = "sha256:251708539a335570f10eaaa21d1848a10c42ee6dc3a9cf37ef42266cdb1c52a8", size = 10233, upload-time = "2025-05-12T18:23:50.722Z" }, ] [[package]] name = "starlette" -version = "0.46.1" +version = "0.46.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" }, ] [[package]] @@ -1337,23 +1290,23 @@ dependencies = [ { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/cf/756fedf6981e82897f2d570dd25fa597eb3f4459068ae0572d7e888cfd6f/tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d", size = 35991 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/cf/756fedf6981e82897f2d570dd25fa597eb3f4459068ae0572d7e888cfd6f/tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d", size = 35991, upload-time = "2025-02-14T06:03:01.003Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/ae/4613a59a2a48e761c5161237fc850eb470b4bb93696db89da51b79a871f1/tiktoken-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f32cc56168eac4851109e9b5d327637f15fd662aa30dd79f964b7c39fbadd26e", size = 1065987 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/86/55d9d1f5b5a7e1164d0f1538a85529b5fcba2b105f92db3622e5d7de6522/tiktoken-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45556bc41241e5294063508caf901bf92ba52d8ef9222023f83d2483a3055348", size = 1009155 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/58/01fb6240df083b7c1916d1dcb024e2b761213c95d576e9f780dfb5625a76/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03935988a91d6d3216e2ec7c645afbb3d870b37bcb67ada1943ec48678e7ee33", size = 1142898 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/73/41591c525680cd460a6becf56c9b17468d3711b1df242c53d2c7b2183d16/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3d80aad8d2c6b9238fc1a5524542087c52b860b10cbf952429ffb714bc1136", size = 1197535 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/7c/1069f25521c8f01a1a182f362e5c8e0337907fae91b368b7da9c3e39b810/tiktoken-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b2a21133be05dc116b1d0372af051cd2c6aa1d2188250c9b553f9fa49301b336", size = 1259548 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/07/c67ad1724b8e14e2b4c8cca04b15da158733ac60136879131db05dda7c30/tiktoken-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:11a20e67fdf58b0e2dea7b8654a288e481bb4fc0289d3ad21291f8d0849915fb", size = 893895 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/ae/4613a59a2a48e761c5161237fc850eb470b4bb93696db89da51b79a871f1/tiktoken-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f32cc56168eac4851109e9b5d327637f15fd662aa30dd79f964b7c39fbadd26e", size = 1065987, upload-time = "2025-02-14T06:02:14.174Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/86/55d9d1f5b5a7e1164d0f1538a85529b5fcba2b105f92db3622e5d7de6522/tiktoken-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45556bc41241e5294063508caf901bf92ba52d8ef9222023f83d2483a3055348", size = 1009155, upload-time = "2025-02-14T06:02:15.384Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/58/01fb6240df083b7c1916d1dcb024e2b761213c95d576e9f780dfb5625a76/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03935988a91d6d3216e2ec7c645afbb3d870b37bcb67ada1943ec48678e7ee33", size = 1142898, upload-time = "2025-02-14T06:02:16.666Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/73/41591c525680cd460a6becf56c9b17468d3711b1df242c53d2c7b2183d16/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3d80aad8d2c6b9238fc1a5524542087c52b860b10cbf952429ffb714bc1136", size = 1197535, upload-time = "2025-02-14T06:02:18.595Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/7c/1069f25521c8f01a1a182f362e5c8e0337907fae91b368b7da9c3e39b810/tiktoken-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b2a21133be05dc116b1d0372af051cd2c6aa1d2188250c9b553f9fa49301b336", size = 1259548, upload-time = "2025-02-14T06:02:20.729Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/07/c67ad1724b8e14e2b4c8cca04b15da158733ac60136879131db05dda7c30/tiktoken-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:11a20e67fdf58b0e2dea7b8654a288e481bb4fc0289d3ad21291f8d0849915fb", size = 893895, upload-time = "2025-02-14T06:02:22.67Z" }, ] [[package]] name = "toml" version = "0.10.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, ] [[package]] @@ -1363,45 +1316,48 @@ source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] [[package]] -name = "traitlets" -version = "5.14.3" +name = "typing-extensions" +version = "4.13.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, ] [[package]] -name = "typing-extensions" -version = "4.13.0" +name = "typing-inspection" +version = "0.4.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/3e/b00a62db91a83fff600de219b6ea9908e6918664899a2d85db222f4fbf19/typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b", size = 106520 } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, ] [[package]] name = "tzdata" version = "2025.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, ] [[package]] name = "urllib3" -version = "2.3.0" +version = "2.4.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, ] [[package]] @@ -1412,55 +1368,16 @@ dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, -] - -[[package]] -name = "wcwidth" -version = "0.2.13" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568, upload-time = "2024-12-15T13:33:30.42Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, -] - -[[package]] -name = "yarl" -version = "1.18.3" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/93/282b5f4898d8e8efaf0790ba6d10e2245d2c9f30e199d1a85cae9356098c/yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069", size = 141555 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/9c/0a49af78df099c283ca3444560f10718fadb8a18dc8b3edf8c7bd9fd7d89/yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193", size = 94351 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/a1/205ab51e148fdcedad189ca8dd587794c6f119882437d04c33c01a75dece/yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889", size = 92286 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/fe/88b690b30f3f59275fb674f5f93ddd4a3ae796c2b62e5bb9ece8a4914b83/yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8", size = 340649 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/eb/3b65499b568e01f36e847cebdc8d7ccb51fff716dbda1ae83c3cbb8ca1c9/yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca", size = 356623 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/46/f559dc184280b745fc76ec6b1954de2c55595f0ec0a7614238b9ebf69618/yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8", size = 354007 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/ba/1865d85212351ad160f19fb99808acf23aab9a0f8ff31c8c9f1b4d671fc9/yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae", size = 344145 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/cb/5c3e975d77755d7b3d5193e92056b19d83752ea2da7ab394e22260a7b824/yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3", size = 336133 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/89/b77d3fd249ab52a5c40859815765d35c91425b6bb82e7427ab2f78f5ff55/yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb", size = 347967 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/bd/f6b7630ba2cc06c319c3235634c582a6ab014d52311e7d7c22f9518189b5/yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e", size = 346397 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/1a/0b4e367d5a72d1f095318344848e93ea70da728118221f84f1bf6c1e39e7/yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59", size = 350206 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/cf/320fff4367341fb77809a2d8d7fe75b5d323a8e1b35710aafe41fdbf327b/yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d", size = 362089 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/cf/aadba261d8b920253204085268bad5e8cdd86b50162fcb1b10c10834885a/yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e", size = 366267 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/58/fb4cadd81acdee6dafe14abeb258f876e4dd410518099ae9a35c88d8097c/yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a", size = 359141 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/7a/4c571597589da4cd5c14ed2a0b17ac56ec9ee7ee615013f74653169e702d/yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1", size = 84402 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/7b/8600250b3d89b625f1121d897062f629883c2f45339623b69b1747ec65fa/yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5", size = 91030 }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315, upload-time = "2024-12-15T13:33:27.467Z" }, ] [[package]] name = "zipfile36" version = "0.1.3" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/5e/22f712e45898d12ad896d54875b736ef90dcfc22590ec118b1fc94fa52a4/zipfile36-0.1.3.tar.gz", hash = "sha256:a78a8dddf4fa114f7fe73df76ffcce7538e23433b7a6a96c1c904023f122aead", size = 61882 } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/5e/22f712e45898d12ad896d54875b736ef90dcfc22590ec118b1fc94fa52a4/zipfile36-0.1.3.tar.gz", hash = "sha256:a78a8dddf4fa114f7fe73df76ffcce7538e23433b7a6a96c1c904023f122aead", size = 61882, upload-time = "2016-10-16T13:09:03.325Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/8a/3b7da0b0bd87d1ef05b74207827c72d348b56a0d6d83242582be18a81e02/zipfile36-0.1.3-py3-none-any.whl", hash = "sha256:f7e48adf627f75cd74cdb50e7d850623b465f4cf5de913809196e8f3aa57dc4b", size = 20841 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/8a/3b7da0b0bd87d1ef05b74207827c72d348b56a0d6d83242582be18a81e02/zipfile36-0.1.3-py3-none-any.whl", hash = "sha256:f7e48adf627f75cd74cdb50e7d850623b465f4cf5de913809196e8f3aa57dc4b", size = 20841, upload-time = "2016-10-16T13:08:59.547Z" }, ]