diff --git a/apps/common/config.py b/apps/common/config.py index 1ba28c599db2f04f7c945fc52eeb7ad8271e3ae1..2cf0ecf2e1e51603396c8f2bf06ee3b9c13ddda4 100644 --- a/apps/common/config.py +++ b/apps/common/config.py @@ -2,30 +2,31 @@ """配置文件处理模块""" import os -from copy import deepcopy from pathlib import Path +from typing import Self import toml +from pydantic import ConfigDict -from apps.common.singleton import SingletonMeta from apps.schemas.config import ConfigModel -class Config(metaclass=SingletonMeta): +class Config(ConfigModel): """配置文件读取和使用Class""" - _config: ConfigModel + model_config = ConfigDict(frozen=True) - def __init__(self) -> None: + @classmethod + def init_config(cls) -> Self: """读取配置文件;当PROD环境变量设置时,配置文件将在读取后删除""" config_file = os.getenv("CONFIG") if config_file is None: config_file = Path(__file__).parents[2] / "config" / "config.toml" - self._config = ConfigModel.model_validate(toml.load(config_file)) + config = cls.model_validate(toml.load(config_file)) if os.getenv("PROD"): Path(config_file).unlink() - def get_config(self) -> ConfigModel: - """获取配置文件内容""" - return deepcopy(self._config) + return config + +config = Config.init_config() diff --git a/apps/common/cryptohub.py b/apps/common/cryptohub.py index e98ea2b81f58ea5019d6f42f18d90fb6ea8f41fc..ce81a4e833705621e5a1f75303e0d83f21ab6594 100644 --- a/apps/common/cryptohub.py +++ b/apps/common/cryptohub.py @@ -3,7 +3,7 @@ import hashlib -from apps.common.security import Security +from .security import Security class CryptoHub: diff --git a/apps/common/lance.py b/apps/common/lance.py index 2ead17625661ac27a9a49a749c37b9eb8cab9a79..e54871fd5a1d59b1ef5d329d8ce7fed4073a65e3 100644 --- a/apps/common/lance.py +++ b/apps/common/lance.py @@ -4,8 +4,6 @@ import lancedb from lancedb.index import HnswSq -from apps.common.config import Config -from apps.common.singleton import SingletonMeta from apps.models.vector import ( CallPoolVector, FlowPoolVector, @@ -14,6 +12,9 @@ from apps.models.vector import ( ) from apps.schemas.mcp import MCPToolVector, MCPVector +from .config import config +from .singleton import SingletonMeta + class LanceDB(metaclass=SingletonMeta): """LanceDB向量化存储""" @@ -27,7 +28,7 @@ class LanceDB(metaclass=SingletonMeta): :return: 无 """ self._engine = await lancedb.connect_async( - Config().get_config().deploy.data_dir.rstrip("/") + "/vectors", + config.deploy.data_dir.rstrip("/") + "/vectors", ) # 创建表 @@ -72,7 +73,7 @@ class LanceDB(metaclass=SingletonMeta): :rtype: lancedb.AsyncTable """ self._engine = await lancedb.connect_async( - Config().get_config().deploy.data_dir.rstrip("/") + "/vectors", + config.deploy.data_dir.rstrip("/") + "/vectors", ) return await self._engine.open_table(table_name) diff --git a/apps/common/minio.py b/apps/common/minio.py index b5187dbb5b63674879c53def5a125cdd60348544..48c6f50c4b77c130461d247fbf11f5e117389bc7 100644 --- a/apps/common/minio.py +++ b/apps/common/minio.py @@ -5,17 +5,17 @@ from typing import Any import minio -from apps.common.config import Config +from .config import config class MinioClient: """MinIO客户端""" client = minio.Minio( - endpoint=Config().get_config().minio.endpoint, - access_key=Config().get_config().minio.access_key, - secret_key=Config().get_config().minio.secret_key, - secure=Config().get_config().minio.secure, + endpoint=config.minio.endpoint, + access_key=config.minio.access_key, + secret_key=config.minio.secret_key, + secure=config.minio.secure, ) @classmethod diff --git a/apps/common/mongo.py b/apps/common/mongo.py index 2f05d4e76c62e86180692e81cd522dcd0ec35b34..96c2a3fafe7cb2d6025e15963b6fc9772a9f7ee0 100644 --- a/apps/common/mongo.py +++ b/apps/common/mongo.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from pymongo.asynchronous.client_session import AsyncClientSession from pymongo.asynchronous.collection import AsyncCollection -from apps.common.config import Config +from .config import config logger = logging.getLogger(__name__) @@ -22,7 +22,7 @@ class MongoDB: from pymongo import AsyncMongoClient 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", + f"mongodb://{urllib.parse.quote_plus(config.mongodb.user)}:{urllib.parse.quote_plus(config.mongodb.password)}@{config.mongodb.host}:{config.mongodb.port}/?directConnection=true&replicaSet=rs0", ) @@ -34,7 +34,7 @@ class MongoDB: :return: 集合对象 :rtype: AsyncCollection """ - return self._client[Config().get_config().mongodb.database][collection_name] + return self._client[config.mongodb.database][collection_name] async def clear_collection(self, collection_name: str) -> None: @@ -44,7 +44,7 @@ class MongoDB: :param str collection_name: 集合名称 :return: 无 """ - await self._client[Config().get_config().mongodb.database][collection_name].delete_many({}) + await self._client[config.mongodb.database][collection_name].delete_many({}) def get_session(self) -> "AsyncClientSession": diff --git a/apps/common/oidc.py b/apps/common/oidc.py index f1d01e8ffe31487d46fad322f915e1cb29e82ea3..7fbd3c54a4feb289fec2f3644092842d131f7055 100644 --- a/apps/common/oidc.py +++ b/apps/common/oidc.py @@ -5,12 +5,13 @@ import logging from datetime import UTC, datetime, timedelta from typing import Any -from apps.common.config import Config -from apps.common.mongo import MongoDB -from apps.common.oidc_provider.authhub import AuthhubOIDCProvider -from apps.common.oidc_provider.openeuler import OpenEulerOIDCProvider from apps.constants import OIDC_ACCESS_TOKEN_EXPIRE_TIME, OIDC_REFRESH_TOKEN_EXPIRE_TIME +from .config import config +from .mongo import MongoDB +from .oidc_provider.authhub import AuthhubOIDCProvider +from .oidc_provider.openeuler import OpenEulerOIDCProvider + logger = logging.getLogger(__name__) @@ -19,12 +20,12 @@ class OIDCProvider: def __init__(self) -> None: """初始化OIDC Provider""" - if Config().get_config().login.provider == "openeuler": + if config.login.provider == "openeuler": self.provider = OpenEulerOIDCProvider() - elif Config().get_config().login.provider == "authhub": + elif config.login.provider == "authhub": self.provider = AuthhubOIDCProvider() else: - err = f"[OIDC] 未知OIDC提供商: {Config().get_config().login.provider}" + err = f"[OIDC] 未知OIDC提供商: {config.login.provider}" logger.error(err) raise NotImplementedError(err) diff --git a/apps/common/oidc_provider/authhub.py b/apps/common/oidc_provider/authhub.py index f0f462ecb8997b0505a22bd8f6a0c6ec9a2e3895..43f75530c5680addf5cd9b71d41eb6108b9449b7 100644 --- a/apps/common/oidc_provider/authhub.py +++ b/apps/common/oidc_provider/authhub.py @@ -7,10 +7,11 @@ from typing import Any import httpx from fastapi import status -from apps.common.config import Config -from apps.common.oidc_provider.base import OIDCProviderBase +from apps.common.config import config from apps.schemas.config import OIDCConfig +from .base import OIDCProviderBase + logger = logging.getLogger(__name__) @@ -20,7 +21,7 @@ class AuthhubOIDCProvider(OIDCProviderBase): @classmethod def _get_login_config(cls) -> OIDCConfig: """获取并验证登录配置""" - login_config = Config().get_config().login.settings + login_config = config.login.settings if not isinstance(login_config, OIDCConfig): err = "Authhub OIDC配置错误" raise TypeError(err) diff --git a/apps/common/oidc_provider/openeuler.py b/apps/common/oidc_provider/openeuler.py index 5fec2f51a1efe1c0a7eeffe4b921cf981d14a896..e86a6ad3d3dd6feba74b750b4ef1dcdc30875c44 100644 --- a/apps/common/oidc_provider/openeuler.py +++ b/apps/common/oidc_provider/openeuler.py @@ -7,10 +7,11 @@ from typing import Any import httpx from fastapi import status -from apps.common.config import Config -from apps.common.oidc_provider.base import OIDCProviderBase +from apps.common.config import config from apps.schemas.config import OIDCConfig +from .base import OIDCProviderBase + logger = logging.getLogger(__name__) @@ -20,7 +21,7 @@ class OpenEulerOIDCProvider(OIDCProviderBase): @classmethod def _get_login_config(cls) -> OIDCConfig: """获取并验证登录配置""" - login_config = Config().get_config().login.settings + login_config = config.login.settings if not isinstance(login_config, OIDCConfig): err = "OpenEuler OIDC配置错误" raise TypeError(err) diff --git a/apps/common/postgres.py b/apps/common/postgres.py index 7669220694dcf78e4a736ead5dfdabfd58b9a23a..90b1837a9e00e9f612a0d950f17fc08919d03842 100644 --- a/apps/common/postgres.py +++ b/apps/common/postgres.py @@ -1,11 +1,49 @@ """Postgres连接器""" -from sqlalchemy import Column -from sqlalchemy.dialects.postgresql import JSONB +import logging +import urllib.parse +from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine -class PGSQL: +from apps.models import ( + Base, +) + +from .config import config + +logger = logging.getLogger(__name__) + + +class Postgres: """Postgres连接器""" - def __init__(self, db_url: str): - pass + async def init(self) -> None: + """初始化Postgres连接器""" + logger.info("[Postgres] 初始化Postgres连接器") + self._engine = create_async_engine( + f"postgresql+asyncpg://{urllib.parse.quote_plus(config.postgres.user)}:" + f"{urllib.parse.quote_plus(config.postgres.password)}@{config.postgres.host}:" + f"{config.postgres.port}/{config.postgres.database}", + ) + self._session = async_sessionmaker(self._engine, expire_on_commit=False) + + logger.info("[Postgres] 创建表") + async with self._engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + @asynccontextmanager + async def session(self) -> AsyncGenerator[AsyncSession, None]: + """获取会话""" + async with self._session() as session: + try: + yield session + except Exception: + logger.exception("[Postgres] 会话错误") + await session.rollback() + raise + finally: + await session.close() + +postgres = Postgres() diff --git a/apps/common/security.py b/apps/common/security.py index 7bae582e69b8e835df84033a416f53c91c90b73e..fe3dd379bbd9338d3fbc21da8e52fc9a1f1668f8 100644 --- a/apps/common/security.py +++ b/apps/common/security.py @@ -9,7 +9,7 @@ import secrets from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from apps.common.config import Config +from .config import config class Security: @@ -23,7 +23,7 @@ class Security: :param plaintext: 待加密的字符串 :return: 加密后的字符串和存放工作密钥的dict """ - half_key1 = Config().get_config().security.half_key1 + half_key1 = config.security.half_key1 if half_key1 is None: err = "配置文件中未设置HALF_KEY1" raise ValueError(err) @@ -66,13 +66,13 @@ class Security: @staticmethod def _get_root_key(half_key1: str) -> bytes: - half_key2 = Config().get_config().security.half_key2 + half_key2 = config.security.half_key2 if half_key2 is None: err = "配置文件中未设置HALF_KEY2" raise ValueError(err) key = (half_key1 + half_key2).encode("utf-8") - half_key3 = Config().get_config().security.half_key3.encode("utf-8") + half_key3 = config.security.half_key3.encode("utf-8") hash_key = hashlib.pbkdf2_hmac("sha256", key, half_key3, 10000) return binascii.hexlify(hash_key)[13:45] diff --git a/apps/common/wordscheck.py b/apps/common/wordscheck.py index efc31190a214174192a2c27485d7807c8b29e2e5..795166494eb8f09a72213e857317a0e9a3b95def 100644 --- a/apps/common/wordscheck.py +++ b/apps/common/wordscheck.py @@ -4,8 +4,8 @@ import logging from pathlib import Path -from apps.common.config import Config -from apps.common.singleton import SingletonMeta +from .config import config +from .singleton import SingletonMeta logger = logging.getLogger(__name__) @@ -20,8 +20,8 @@ class WordsCheck(metaclass=SingletonMeta): def _init_words_list(self) -> None: """同步初始化敏感词列表""" - if not self._initialized and Config().get_config().check.enable: - with Path(Config().get_config().check.words_list).open(encoding="utf-8") as f: + if not self._initialized and config.check.enable: + with Path(config.check.words_list).open(encoding="utf-8") as f: self._words_list = f.read().splitlines() self._initialized = True @@ -40,7 +40,7 @@ class WordsCheck(metaclass=SingletonMeta): 异常-1,拦截0,正常1 """ - if Config().get_config().check.enable: + if config.check.enable: return await self._check_wordlist(message) # 不设置检查类型,默认不拦截 return 1 diff --git a/apps/constants.py b/apps/constants.py index 58158b33c3aa4ea5aa3595f5642bf6444e7d76cb..60d9f20f592abfc5b8aab4de7bf94e22c1fa53b7 100644 --- a/apps/constants.py +++ b/apps/constants.py @@ -6,7 +6,7 @@ Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. from anyio import Path -from apps.common.config import Config +from .common.config import config # 新对话默认标题 NEW_CHAT = "新对话" @@ -48,7 +48,7 @@ ALLOWED_ICON_MIME_TYPES = [ "image/tiff", ] # MCP路径 -MCP_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" / "mcp" +MCP_PATH = Path(config.deploy.data_dir) / "semantics" / "mcp" # 项目路径 PROJ_PATH = Path(__file__).parent.parent # 图标存储 diff --git a/apps/llm/embedding.py b/apps/llm/embedding.py index 28ab86b49a96d19cc6e2db83930d5aff48f82260..89c3cf48e457cd3bd3618e5229f4afd99dc94632 100644 --- a/apps/llm/embedding.py +++ b/apps/llm/embedding.py @@ -2,7 +2,7 @@ import httpx -from apps.common.config import Config +from apps.common.config import config class Embedding: @@ -19,18 +19,18 @@ class Embedding: @classmethod async def _get_openai_embedding(cls, text: list[str]) -> list[list[float]]: """访问OpenAI兼容的Embedding API,获得向量化数据""" - api = Config().get_config().embedding.endpoint + "/embeddings" + api = config.embedding.endpoint + "/embeddings" data = { "input": text, - "model": Config().get_config().embedding.model, + "model": config.embedding.model, "encoding_format": "float", } headers = { "Content-Type": "application/json", } - if Config().get_config().embedding.api_key: - headers["Authorization"] = f"Bearer {Config().get_config().embedding.api_key}" + if config.embedding.api_key: + headers["Authorization"] = f"Bearer {config.embedding.api_key}" async with httpx.AsyncClient() as client: response = await client.post( @@ -45,12 +45,12 @@ class Embedding: @classmethod async def _get_tei_embedding(cls, text: list[str]) -> list[list[float]]: """访问TEI兼容的Embedding API,获得向量化数据""" - api = Config().get_config().embedding.endpoint + "/embed" + api = config.embedding.endpoint + "/embed" headers = { "Content-Type": "application/json", } - if Config().get_config().embedding.api_key: - headers["Authorization"] = f"Bearer {Config().get_config().embedding.api_key}" + if config.embedding.api_key: + headers["Authorization"] = f"Bearer {config.embedding.api_key}" async with httpx.AsyncClient() as client: result = [] @@ -75,10 +75,10 @@ class Embedding: :param text: 待向量化文本(多条文本组成List) :return: 文本对应的向量(顺序与text一致,也为List) """ - if Config().get_config().embedding.type == "openai": + if config.embedding.type == "openai": return await cls._get_openai_embedding(text) - if Config().get_config().embedding.type == "mindie": + if config.embedding.type == "mindie": return await cls._get_tei_embedding(text) - err = f"不支持的Embedding API类型: {Config().get_config().embedding.type}" + err = f"不支持的Embedding API类型: {config.embedding.type}" raise ValueError(err) diff --git a/apps/llm/function.py b/apps/llm/function.py index 1f995fe7ba187cead03aa6fc62a4cbce1ec05a65..10cd92571f6911d843efc63661af8ad42484821b 100644 --- a/apps/llm/function.py +++ b/apps/llm/function.py @@ -11,9 +11,10 @@ from jinja2 import BaseLoader from jinja2.sandbox import SandboxedEnvironment from jsonschema import Draft7Validator -from apps.common.config import Config +from apps.common.config import config from apps.constants import JSON_GEN_MAX_TRIAL, REASONING_END_TOKEN -from apps.llm.prompt import JSON_GEN_BASIC + +from .prompt import JSON_GEN_BASIC logger = logging.getLogger(__name__) @@ -33,7 +34,7 @@ class FunctionLLM: - structured_output """ # 暂存config;这里可以替代为从其他位置获取 - self._config = Config().get_config().function_call + self._config = config.function_call if not self._config.model: err_msg = "[FunctionCall] 未设置FuntionCall所用模型!" logger.error(err_msg) @@ -258,7 +259,7 @@ class JsonGenerator: async def _assemble_message(self) -> str: """组装消息""" # 检查类型 - function_call = Config().get_config().function_call.backend == "function_call" + function_call = config.function_call.backend == "function_call" # 渲染模板 template = self._env.from_string(JSON_GEN_BASIC) diff --git a/apps/llm/patterns/executor.py b/apps/llm/patterns/executor.py index f872fd2ac8d691b4079756a56a6107d6b6556585..07ad5b2d345c4af600b22efc68080833386b2898 100644 --- a/apps/llm/patterns/executor.py +++ b/apps/llm/patterns/executor.py @@ -3,10 +3,11 @@ from typing import TYPE_CHECKING, Any -from apps.llm.patterns.core import CorePattern from apps.llm.reasoning import ReasoningLLM from apps.llm.snippet import convert_context_to_prompt, facts_to_prompt +from .core import CorePattern + if TYPE_CHECKING: from apps.schemas.scheduler import ExecutorBackground diff --git a/apps/llm/patterns/facts.py b/apps/llm/patterns/facts.py index 0b0381ff40a0e6632fd204c9efcf834a13c4711f..d8dbc4c1e77e9a26b0c9c2886faac399d775b5b2 100644 --- a/apps/llm/patterns/facts.py +++ b/apps/llm/patterns/facts.py @@ -6,10 +6,11 @@ import logging from pydantic import BaseModel, Field from apps.llm.function import JsonGenerator -from apps.llm.patterns.core import CorePattern from apps.llm.reasoning import ReasoningLLM from apps.llm.snippet import convert_context_to_prompt +from .core import CorePattern + logger = logging.getLogger(__name__) diff --git a/apps/llm/patterns/rewoo.py b/apps/llm/patterns/rewoo.py index ef78d92667d30fbd3b26d55ba4d87961181f3d48..140d942a9ea6b8d97abe90715cc6de12159ac666 100644 --- a/apps/llm/patterns/rewoo.py +++ b/apps/llm/patterns/rewoo.py @@ -1,9 +1,10 @@ # Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """规划生成命令行""" -from apps.llm.patterns.core import CorePattern from apps.llm.reasoning import ReasoningLLM +from .core import CorePattern + class InitPlan(CorePattern): """规划生成命令行""" diff --git a/apps/llm/patterns/rewrite.py b/apps/llm/patterns/rewrite.py index d72475fa98aa4d233b1163cbf387067ec123e570..25773ca7ba4dad690633e1cc44121cb0641b4f74 100644 --- a/apps/llm/patterns/rewrite.py +++ b/apps/llm/patterns/rewrite.py @@ -6,9 +6,10 @@ import logging from pydantic import BaseModel, Field from apps.llm.function import JsonGenerator -from apps.llm.patterns.core import CorePattern from apps.llm.reasoning import ReasoningLLM +from .core import CorePattern + logger = logging.getLogger(__name__) diff --git a/apps/llm/patterns/select.py b/apps/llm/patterns/select.py index a6c496bdd0ef79631bbdc41935e717ca3e3668ea..a0b950fc85370d2c56c1f55f5170e1ce460ffdc4 100644 --- a/apps/llm/patterns/select.py +++ b/apps/llm/patterns/select.py @@ -8,10 +8,11 @@ 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.reasoning import ReasoningLLM from apps.llm.snippet import choices_to_prompt +from .core import CorePattern + logger = logging.getLogger(__name__) diff --git a/apps/llm/reasoning.py b/apps/llm/reasoning.py index fdb36fc05adf38920bcce0d962b6aafc21e44b71..b765988c60a6444a074d3b3e13237fb49ddbfec8 100644 --- a/apps/llm/reasoning.py +++ b/apps/llm/reasoning.py @@ -8,11 +8,12 @@ from dataclasses import dataclass from openai import AsyncOpenAI from openai.types.chat import ChatCompletionChunk -from apps.common.config import Config +from apps.common.config import config from apps.constants import REASONING_BEGIN_TOKEN, REASONING_END_TOKEN -from apps.llm.token import TokenCalculator from apps.schemas.config import LLMConfig +from .token import TokenCalculator + logger = logging.getLogger(__name__) @@ -96,7 +97,7 @@ class ReasoningLLM: def __init__(self, llm_config: LLMConfig | None = None) -> None: """判断配置文件里用了哪种大模型;初始化大模型客户端""" if not llm_config: - self._config: LLMConfig = Config().get_config().llm + self._config: LLMConfig = config.llm self._init_client() else: self._config: LLMConfig = llm_config diff --git a/apps/main.py b/apps/main.py index c4ca2bfb116db11624f4e4fbbe4e876a86e5f1eb..43be981e94c37e4f605807f11280dd9149ea9e28 100644 --- a/apps/main.py +++ b/apps/main.py @@ -15,11 +15,12 @@ from fastapi.middleware.cors import CORSMiddleware from rich.console import Console from rich.logging import RichHandler -from apps.common.config import Config -from apps.common.lance import LanceDB -from apps.common.wordscheck import WordsCheck -from apps.llm.token import TokenCalculator -from apps.routers import ( +from .common.config import config +from .common.lance import LanceDB +from .common.postgres import postgres +from .common.wordscheck import WordsCheck +from .llm.token import TokenCalculator +from .routers import ( api_key, appcenter, auth, @@ -37,14 +38,14 @@ from apps.routers import ( service, user, ) -from apps.scheduler.pool.pool import Pool +from .scheduler.pool.pool import Pool # 定义FastAPI app app = FastAPI(redoc_url=None) # 定义FastAPI全局中间件 app.add_middleware( CORSMiddleware, - allow_origins=[Config().get_config().fastapi.domain], + allow_origins=[config.fastapi.domain], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -84,6 +85,7 @@ logging.basicConfig( async def init_resources() -> None: """初始化必要资源""" WordsCheck() + await postgres.init() await LanceDB().init() await Pool.init() TokenCalculator() diff --git a/apps/models/__init__.py b/apps/models/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9b0215ed4b820af0d31aadc01f50e16b90d32277 100644 --- a/apps/models/__init__.py +++ b/apps/models/__init__.py @@ -0,0 +1,5 @@ +"""SQLAlchemy 数据库表结构""" + +from .base import Base + +__all__ = ["Base"] diff --git a/apps/models/base.py b/apps/models/base.py new file mode 100644 index 0000000000000000000000000000000000000000..fd90ec0eebc1fc80681621237b842039a5c3b7dd --- /dev/null +++ b/apps/models/base.py @@ -0,0 +1,5 @@ +"""SQLAlchemy模型基类""" + +from sqlalchemy.orm import declarative_base + +Base = declarative_base() diff --git a/apps/models/vectors.py b/apps/models/vectors.py new file mode 100644 index 0000000000000000000000000000000000000000..6ce20f5515e7baa2c7a80c4eae3f60a05eecf8f4 --- /dev/null +++ b/apps/models/vectors.py @@ -0,0 +1,44 @@ +"""向量表""" + +from pgvector.sqlalchemy import Vector +from sqlalchemy import Integer, String +from sqlalchemy.orm import Mapped, mapped_column + +from .base import Base + + +class FlowPoolVector(Base): + """Flow向量数据""" + + __tablename__ = "flow_vector" + id: Mapped[int] = mapped_column(Integer, primary_key=True) + flow_id: Mapped[str] = mapped_column(String(32), index=True, unique=True) + app_id: Mapped[str] = mapped_column(String(32)) + embedding: Mapped[Vector] = mapped_column(Vector(1024)) + + +class ServicePoolVector(Base): + """Service向量数据""" + + __tablename__ = "service_vector" + id: Mapped[int] = mapped_column(Integer, primary_key=True) + service_id: Mapped[str] = mapped_column(String(32), index=True, unique=True) + embedding: Mapped[Vector] = mapped_column(Vector(1024)) + + +class CallPoolVector(Base): + """Call向量数据""" + + __tablename__ = "call_vector" + id: Mapped[int] = mapped_column(Integer, primary_key=True) + call_id: Mapped[str] = mapped_column(String(32), index=True, unique=True) + embedding: Mapped[Vector] = mapped_column(Vector(1024)) + + +class NodePoolVector(Base): + """Node向量数据""" + + __tablename__ = "node_vector" + id: Mapped[int] = mapped_column(Integer, primary_key=True) + service_id: Mapped[str] = mapped_column(String(32)) + embedding: Mapped[Vector] = mapped_column(Vector(1024)) diff --git a/apps/routers/knowledge.py b/apps/routers/knowledge.py index ef85efc4db2aa4d5f6dd7c143c8ec2a654d8e40f..c157994b46824de5824a729a12661602b069acc1 100644 --- a/apps/routers/knowledge.py +++ b/apps/routers/knowledge.py @@ -33,7 +33,7 @@ router = APIRouter( 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_id: Annotated[str, Query(alias="kbId")] = "", kb_name: Annotated[str, Query(alias="kbName")] = "", ) -> JSONResponse: """获取当前用户的知识库ID""" diff --git a/apps/scheduler/call/api/api.py b/apps/scheduler/call/api/api.py index 15420c54b9ab41bc21b8aea904323e58225740a6..54e06cb5b9b41f45059158c4fdba5bf2132edef8 100644 --- a/apps/scheduler/call/api/api.py +++ b/apps/scheduler/call/api/api.py @@ -13,7 +13,6 @@ from pydantic import Field from pydantic.json_schema import SkipJsonSchema from apps.common.oidc import oidc_provider -from apps.scheduler.call.api.schema import APIInput, APIOutput from apps.scheduler.call.core import CoreCall from apps.schemas.enum_var import CallOutputType, ContentType, HTTPMethod from apps.schemas.scheduler import ( @@ -25,6 +24,8 @@ from apps.schemas.scheduler import ( from apps.services.service import ServiceCenterManager from apps.services.token import TokenManager +from .schema import APIInput, APIOutput + logger = logging.getLogger(__name__) SUCCESS_HTTP_CODES = [ status.HTTP_200_OK, diff --git a/apps/scheduler/call/choice/choice.py b/apps/scheduler/call/choice/choice.py index a5edf21afb2eeb308dd909a40696500751c9a086..807c9d6981626630f38f5e42b9f3ef8b6863ffef 100644 --- a/apps/scheduler/call/choice/choice.py +++ b/apps/scheduler/call/choice/choice.py @@ -1,19 +1,71 @@ # Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """使用大模型或使用程序做出判断""" -from enum import Enum +import logging +from collections.abc import AsyncGenerator +from typing import Any -from apps.scheduler.call.choice.schema import ChoiceInput, ChoiceOutput -from apps.scheduler.call.core import CoreCall +from pydantic import Field +from apps.scheduler.call.core import CoreCall +from apps.schemas.enum_var import CallOutputType +from apps.schemas.scheduler import ( + CallError, + CallInfo, + CallOutputChunk, + CallVars, +) -class Operator(str, Enum): - """Choice工具支持的运算符""" +from .condition_handler import ConditionHandler +from .schema import ( + ChoiceBranch, + ChoiceInput, + ChoiceOutput, +) - pass +logger = logging.getLogger(__name__) class Choice(CoreCall, input_model=ChoiceInput, output_model=ChoiceOutput): """Choice工具""" - pass + to_user: bool = Field(default=False) + choices: list[ChoiceBranch] = Field(description="分支", default=[]) + + @classmethod + def info(cls) -> CallInfo: + """返回Call的名称和描述""" + return CallInfo(name="Choice", description="使用大模型或使用程序做出判断") + + async def _prepare_message(self, call_vars: CallVars) -> list[dict[str, Any]]: + """替换choices中的系统变量""" + for choice in self.choices: + for condition in choice.conditions: + split = condition.left.step_id.split(".") + step_id = split[0] + value = "/".join(split[1:]) + if step_id and value: + path = f"{step_id}/{value}" + condition.left.value = self._extract_history_variables(path, call_vars.history) + return self.choices + + async def _init(self, call_vars: CallVars) -> ChoiceInput: + """初始化Choice工具""" + return ChoiceInput( + choices=await self._prepare_message(call_vars), + ) + + async def _exec(self, input_data: dict[str, Any]) -> AsyncGenerator[CallOutputChunk, None]: + """执行Choice工具""" + # 解析输入数据 + data = ChoiceInput(**input_data) + ret: CallOutputChunk=CallOutputChunk( + type=CallOutputType.DATA, + content=None, + ) + condition_handler = ConditionHandler() + try: + ret.content = condition_handler.handler(data.choices) + yield ret + except Exception as e: + raise CallError(message=f"选择工具调用失败:{e!s}", data={}) from e diff --git a/apps/scheduler/call/choice/condition_handler.py b/apps/scheduler/call/choice/condition_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..35ff541cf62e7860e133008eb35ee792e9fda878 --- /dev/null +++ b/apps/scheduler/call/choice/condition_handler.py @@ -0,0 +1,176 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. +"""处理条件分支的工具""" + +import logging + +from pydantic import BaseModel + +from .schema import ChoiceBranch, ChoiceOutput, Condition, Logic, Operator, Type, Value + +logger = logging.getLogger(__name__) + + +class ConditionHandler(BaseModel): + """条件分支处理器""" + + def handler(self, choices: list[ChoiceBranch]) -> ChoiceOutput: + """处理条件""" + default_branch = [c for c in choices if c.is_default] + + for block_judgement in choices: + results = [] + if block_judgement.is_default: + continue + for condition in block_judgement.conditions: + result = self._judge_condition(condition) + results.append(result) + if block_judgement.logic == Logic.AND: + final_result = all(results) + elif block_judgement.logic == Logic.OR: + final_result = any(results) + + if final_result: + return { + "branch_id": block_judgement.branch_id, + "message": f"选择分支:{block_judgement.branch_id}", + } + + # 如果没有匹配的分支,选择默认分支 + if default_branch: + return { + "branch_id": default_branch[0].branch_id, + "message": f"选择默认分支:{default_branch[0].branch_id}", + } + return { + "branch_id": "", + "message": "没有匹配的分支,且没有默认分支", + } + + def _judge_condition(self, condition: Condition) -> bool: + """ + 判断条件是否成立。 + + Args: + condition (Condition): 'left', 'operator', 'right', 'type' + + Returns: + bool + + """ + left = condition.left + operator = condition.operator + right = condition.right + value_type = condition.type + + result = None + if value_type == Type.STRING: + result = self._judge_string_condition(left, operator, right) + elif value_type == Type.INT: + result = self._judge_int_condition(left, operator, right) + elif value_type == Type.BOOL: + result = self._judge_bool_condition(left, operator, right) + else: + logger.error("不支持的数据类型: %s", value_type) + msg = f"不支持的数据类型: {value_type}" + raise ValueError(msg) + return result + + def _judge_string_condition(self, left: Value, operator: Operator, right: Value) -> bool: + """ + 判断字符串类型的条件。 + + Args: + left (Value): 左值,包含 'value' 键。 + operator (Operator): 操作符 + right (Value): 右值,包含 'value' 键。 + + Returns: + bool + + """ + left_value = left.value + if not isinstance(left_value, str): + logger.error("左值不是字符串类型: %s", left_value) + msg = "左值必须是字符串类型" + raise TypeError(msg) + right_value = right.value + result = False + if operator == Operator.EQUAL: + result = left_value == right_value + elif operator == Operator.NEQUAL: + result = left_value != right_value + elif operator == Operator.GREAT: + result = len(left_value) > len(right_value) + elif operator == Operator.GREAT_EQUALS: + result = len(left_value) >= len(right_value) + elif operator == Operator.LESS: + result = len(left_value) < len(right_value) + elif operator == Operator.LESS_EQUALS: + result = len(left_value) <= len(right_value) + elif operator == Operator.CONTAINS: + result = right_value in left_value + elif operator == Operator.NOT_CONTAINS: + result = right_value not in left_value + return result + + def _judge_int_condition(self, left: Value, operator: Operator, right: Value) -> bool: # noqa: PLR0911 + """ + 判断整数类型的条件。 + + Args: + left (Value): 左值,包含 'value' 键。 + operator (Operator): 操作符 + right (Value): 右值,包含 'value' 键。 + + Returns: + bool + + """ + left_value = left.value + if not isinstance(left_value, int): + logger.error("左值不是整数类型: %s", left_value) + msg = "左值必须是整数类型" + raise TypeError(msg) + right_value = right.value + if operator == Operator.EQUAL: + return left_value == right_value + if operator == Operator.NEQUAL: + return left_value != right_value + if operator == Operator.GREAT: + return left_value > right_value + if operator == Operator.GREAT_EQUALS: + return left_value >= right_value + if operator == Operator.LESS: + return left_value < right_value + if operator == Operator.LESS_EQUALS: + return left_value <= right_value + return False + + def _judge_bool_condition(self, left: Value, operator: Operator, right: Value) -> bool: + """ + 判断布尔类型的条件。 + + Args: + left (Value): 左值,包含 'value' 键。 + operator (Operator): 操作符 + right (Value): 右值,包含 'value' 键。 + + Returns: + bool + + """ + left_value = left.value + if not isinstance(left_value, bool): + logger.error("左值不是布尔类型: %s", left_value) + msg = "左值必须是布尔类型" + raise TypeError(msg) + right_value = right.value + if operator == Operator.EQUAL: + return left_value == right_value + if operator == Operator.NEQUAL: + return left_value != right_value + if operator == Operator.IS_EMPTY: + return left_value == "" + if operator == Operator.NOT_EMPTY: + return left_value != "" + return False diff --git a/apps/scheduler/call/choice/schema.py b/apps/scheduler/call/choice/schema.py index 60b62d09fd66adbf32295f44ec86398a537f38d5..2d1ba2b24661d7ed589eabb606fd3fd344b919ef 100644 --- a/apps/scheduler/call/choice/schema.py +++ b/apps/scheduler/call/choice/schema.py @@ -1,12 +1,74 @@ # Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """Choice Call的输入和输出""" -from apps.scheduler.call.core import DataBase +from enum import Enum +from pydantic import BaseModel, Field -class ChoiceInput(DataBase): + +class Operator(str, Enum): + """Choice Call支持的运算符""" + + EQUAL = "equal" + NEQUAL = "not_equal" + GREAT = "great" + GREAT_EQUALS = "great_equals" + LESS = "less" + LESS_EQUALS = "less_equals" + # string + CONTAINS = "contains" + NOT_CONTAINS = "not_contains" + # bool + IS_EMPTY = "is_empty" + NOT_EMPTY = "not_empty" + + +class Logic(str, Enum): + """Choice 工具支持的逻辑运算符""" + + AND = "and" + OR = "or" + + +class Type(str, Enum): + """Choice 工具支持的类型""" + + STRING = "string" + INT = "int" + BOOL = "bool" + + +class Value(BaseModel): + """值的结构""" + + step_id: str = Field(description="步骤id", default="") + value: str | int | bool = Field(description="值", default=None) + + +class Condition(BaseModel): + """单个条件""" + + type: Type = Field(description="值的类型", default=Type.STRING) + left: Value = Field(description="左值") + right: Value = Field(description="右值") + operator: Operator = Field(description="运算符") + id: int = Field(description="条件ID") + + +class ChoiceBranch(BaseModel): + """子分支""" + + branch_id: str = Field(description="分支ID", default="") + logic: Logic = Field(description="逻辑运算符", default=Logic.AND) + conditions: list[Condition] = Field(description="条件列表", default=[]) + is_default: bool = Field(description="是否为默认分支", default=False) + + +class ChoiceInput(BaseModel): """Choice Call的输入""" + choices: list[ChoiceBranch] = Field(description="分支", default=[]) + -class ChoiceOutput(DataBase): +class ChoiceOutput(BaseModel): """Choice Call的输出""" diff --git a/apps/scheduler/call/convert/convert.py b/apps/scheduler/call/convert/convert.py index 27980bd8ad46aaaa7741d6a6919661aac580de64..30392b0e5819505fce7a99f580800245c93b51ca 100644 --- a/apps/scheduler/call/convert/convert.py +++ b/apps/scheduler/call/convert/convert.py @@ -3,15 +3,13 @@ from collections.abc import AsyncGenerator from datetime import datetime -from typing import Any import pytz from jinja2 import BaseLoader from jinja2.sandbox import SandboxedEnvironment from pydantic import Field -from apps.scheduler.call.convert.schema import ConvertInput, ConvertOutput -from apps.scheduler.call.core import CallOutputChunk, CoreCall +from apps.scheduler.call.core import CoreCall from apps.schemas.enum_var import CallOutputType from apps.schemas.scheduler import ( CallInfo, @@ -19,6 +17,8 @@ from apps.schemas.scheduler import ( CallVars, ) +from .schema import ConvertInput, ConvertOutput + class Convert(CoreCall, input_model=ConvertInput, output_model=ConvertOutput): """Convert 工具,用于对生成的文字信息和原始数据进行格式化""" diff --git a/apps/scheduler/call/empty.py b/apps/scheduler/call/empty.py index 5865bc7e804491a7d6aa41fa251dc8c5d9c77dc6..3c743f3bd6231b38b5f4a554e5a97270a31e9ac8 100644 --- a/apps/scheduler/call/empty.py +++ b/apps/scheduler/call/empty.py @@ -4,10 +4,11 @@ from collections.abc import AsyncGenerator from typing import Any -from apps.scheduler.call.core import CoreCall, DataBase from apps.schemas.enum_var import CallOutputType from apps.schemas.scheduler import CallInfo, CallOutputChunk, CallVars +from .core import CoreCall, DataBase + class Empty(CoreCall, input_model=DataBase, output_model=DataBase): """空Call""" diff --git a/apps/scheduler/call/facts/facts.py b/apps/scheduler/call/facts/facts.py index f8aebcd748d92de4109553d0f68fecac43363f58..72490cba582255e1f0f05de9de45c1fad46ef6d6 100644 --- a/apps/scheduler/call/facts/facts.py +++ b/apps/scheduler/call/facts/facts.py @@ -9,17 +9,18 @@ from jinja2.sandbox import SandboxedEnvironment from pydantic import Field from apps.scheduler.call.core import CoreCall -from apps.scheduler.call.facts.prompt import DOMAIN_PROMPT, FACTS_PROMPT -from apps.scheduler.call.facts.schema import ( +from apps.schemas.enum_var import CallOutputType +from apps.schemas.pool import NodePool +from apps.schemas.scheduler import CallInfo, CallOutputChunk, CallVars +from apps.services.user_domain import UserDomainManager + +from .prompt import DOMAIN_PROMPT, FACTS_PROMPT +from .schema import ( DomainGen, FactsGen, FactsInput, FactsOutput, ) -from apps.schemas.enum_var import CallOutputType -from apps.schemas.pool import NodePool -from apps.schemas.scheduler import CallInfo, CallOutputChunk, CallVars -from apps.services.user_domain import UserDomainManager if TYPE_CHECKING: from apps.scheduler.executor.step import StepExecutor diff --git a/apps/scheduler/call/graph/graph.py b/apps/scheduler/call/graph/graph.py index c2728f17913fcd0e8343168f2b508dbe6006fd6e..a309b07948188af31b051303f4f0b763c3089b6a 100644 --- a/apps/scheduler/call/graph/graph.py +++ b/apps/scheduler/call/graph/graph.py @@ -9,8 +9,6 @@ from anyio import Path from pydantic import Field from apps.scheduler.call.core import CoreCall -from apps.scheduler.call.graph.schema import RenderFormat, RenderInput, RenderOutput -from apps.scheduler.call.graph.style import RenderStyle from apps.schemas.enum_var import CallOutputType from apps.schemas.scheduler import ( CallError, @@ -19,6 +17,9 @@ from apps.schemas.scheduler import ( CallVars, ) +from .schema import RenderFormat, RenderInput, RenderOutput +from .style import RenderStyle + class Graph(CoreCall, input_model=RenderInput, output_model=RenderOutput): """Render Call,用于将SQL Tool查询出的数据转换为图表""" diff --git a/apps/scheduler/call/llm/llm.py b/apps/scheduler/call/llm/llm.py index 6a679dce98af6164211edd16b2fa38714d899f31..f16cffc6fc59e8cb007bbd82a5dc95ea6ea65242 100644 --- a/apps/scheduler/call/llm/llm.py +++ b/apps/scheduler/call/llm/llm.py @@ -13,8 +13,6 @@ from pydantic import Field from apps.llm.reasoning import ReasoningLLM from apps.scheduler.call.core import CoreCall -from apps.scheduler.call.llm.prompt import LLM_CONTEXT_PROMPT, LLM_DEFAULT_PROMPT -from apps.scheduler.call.llm.schema import LLMInput, LLMOutput from apps.schemas.enum_var import CallOutputType from apps.schemas.scheduler import ( CallError, @@ -23,6 +21,9 @@ from apps.schemas.scheduler import ( CallVars, ) +from .prompt import LLM_CONTEXT_PROMPT, LLM_DEFAULT_PROMPT +from .schema import LLMInput, LLMOutput + logger = logging.getLogger(__name__) diff --git a/apps/scheduler/call/mcp/mcp.py b/apps/scheduler/call/mcp/mcp.py index ec25c10c7a50a73a6b9de6af1dacab4580f81d3b..e84b56cb3ea4640a6703f3b53de36fa15d2dcc10 100644 --- a/apps/scheduler/call/mcp/mcp.py +++ b/apps/scheduler/call/mcp/mcp.py @@ -9,12 +9,6 @@ from typing import Any from pydantic import Field 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 from apps.schemas.enum_var import CallOutputType from apps.schemas.mcp import MCPPlanItem @@ -24,6 +18,13 @@ from apps.schemas.scheduler import ( CallVars, ) +from .schema import ( + MCPInput, + MCPMessage, + MCPMessageType, + MCPOutput, +) + logger = logging.getLogger(__name__) @@ -95,7 +96,7 @@ class MCP(CoreCall, input_model=MCPInput, output_model=MCPOutput): # 输出计划 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" + plan_str += f"[+] {plan_item.content}; {plan_item.tool}[{plan_item.instruction}]\n\n" yield self._create_output( f"[MCP] 计划生成完成:\n\n{plan_str}\n\n\n\n", diff --git a/apps/scheduler/call/rag/rag.py b/apps/scheduler/call/rag/rag.py index e27327d8ad4d01387eeeb4f1644c056d32bc0dbe..01fa566d9ac256bbd1225778e076115f9e08773b 100644 --- a/apps/scheduler/call/rag/rag.py +++ b/apps/scheduler/call/rag/rag.py @@ -9,10 +9,9 @@ import httpx from fastapi import status from pydantic import Field -from apps.common.config import Config +from apps.common.config import config from apps.llm.patterns.rewrite import QuestionRewrite from apps.scheduler.call.core import CoreCall -from apps.scheduler.call.rag.schema import RAGInput, RAGOutput, SearchMethod from apps.schemas.enum_var import CallOutputType from apps.schemas.scheduler import ( CallError, @@ -21,6 +20,8 @@ from apps.schemas.scheduler import ( CallVars, ) +from .schema import RAGInput, RAGOutput, SearchMethod + logger = logging.getLogger(__name__) @@ -67,7 +68,7 @@ class RAG(CoreCall, input_model=RAGInput, output_model=RAGOutput): 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/search" + url = config.rag.rag_service.rstrip("/") + "/chunk/search" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {data.session_id}", diff --git a/apps/scheduler/call/search/search.py b/apps/scheduler/call/search/search.py index 73d21d7b9956a19b6eaaf23467b4286d7fbb3d79..31ae9d1458eacc7645501e5a00719fee2d922465 100644 --- a/apps/scheduler/call/search/search.py +++ b/apps/scheduler/call/search/search.py @@ -4,7 +4,6 @@ from collections.abc import AsyncGenerator from typing import Any from apps.scheduler.call.core import CoreCall -from apps.scheduler.call.search.schema import SearchInput, SearchOutput from apps.schemas.scheduler import ( CallError, CallInfo, @@ -12,6 +11,8 @@ from apps.schemas.scheduler import ( CallVars, ) +from .schema import SearchInput, SearchOutput + class Search(CoreCall, input_model=SearchInput, output_model=SearchOutput): """搜索工具""" diff --git a/apps/scheduler/call/slot/slot.py b/apps/scheduler/call/slot/slot.py index 4f8e1010cc0bd88f22e050778bce236d7d3515e0..5f13a672bd6a94e94ecfc4fe8663629fb05cc684 100644 --- a/apps/scheduler/call/slot/slot.py +++ b/apps/scheduler/call/slot/slot.py @@ -12,13 +12,14 @@ from pydantic import Field 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.prompt import SLOT_GEN_PROMPT -from apps.scheduler.call.slot.schema import SlotInput, SlotOutput from apps.scheduler.slot.slot import Slot as SlotProcessor from apps.schemas.enum_var import CallOutputType from apps.schemas.pool import NodePool from apps.schemas.scheduler import CallInfo, CallOutputChunk, CallVars +from .prompt import SLOT_GEN_PROMPT +from .schema import SlotInput, SlotOutput + if TYPE_CHECKING: from apps.scheduler.executor.step import StepExecutor diff --git a/apps/scheduler/call/sql/sql.py b/apps/scheduler/call/sql/sql.py index 3e24301de508e06adf5cfdbf24b3d8ca37c0cc27..67bccd6f7c7c2029a5e9d7f713da31b2cc1e4973 100644 --- a/apps/scheduler/call/sql/sql.py +++ b/apps/scheduler/call/sql/sql.py @@ -9,9 +9,8 @@ import httpx from fastapi import status from pydantic import Field -from apps.common.config import Config +from apps.common.config import config from apps.scheduler.call.core import CoreCall -from apps.scheduler.call.sql.schema import SQLInput, SQLOutput from apps.schemas.enum_var import CallOutputType from apps.schemas.scheduler import ( CallError, @@ -20,6 +19,8 @@ from apps.schemas.scheduler import ( CallVars, ) +from .schema import SQLInput, SQLOutput + logger = logging.getLogger(__name__) @@ -64,7 +65,7 @@ class SQL(CoreCall, input_model=SQLInput, output_model=SQLOutput): try: async with httpx.AsyncClient() as client: response = await client.post( - Config().get_config().extra.sql_url + "/database/sql", + config.extra.sql_url + "/database/sql", headers=headers, json=post_data, timeout=60.0, @@ -94,7 +95,7 @@ class SQL(CoreCall, input_model=SQLInput, output_model=SQLOutput): try: async with httpx.AsyncClient() as client: response = await client.post( - Config().get_config().extra.sql_url + "/sql/execute", + config.extra.sql_url + "/sql/execute", headers=headers, json={ "database_id": sql_dict["database_id"], diff --git a/apps/scheduler/call/suggest/suggest.py b/apps/scheduler/call/suggest/suggest.py index 1788fa0f4a8ede3af38264c9bb4a82628018086b..b7d981df297d5adee29862f90a55fa0ed80d3125 100644 --- a/apps/scheduler/call/suggest/suggest.py +++ b/apps/scheduler/call/suggest/suggest.py @@ -13,13 +13,6 @@ from pydantic.json_schema import SkipJsonSchema from apps.common.security import Security from apps.llm.function import FunctionLLM from apps.scheduler.call.core import CoreCall -from apps.scheduler.call.suggest.prompt import SUGGEST_PROMPT -from apps.scheduler.call.suggest.schema import ( - SingleFlowSuggestionConfig, - SuggestGenResult, - SuggestionInput, - SuggestionOutput, -) from apps.schemas.enum_var import CallOutputType from apps.schemas.pool import NodePool from apps.schemas.record import RecordContent @@ -32,6 +25,14 @@ from apps.schemas.scheduler import ( from apps.services.record import RecordManager from apps.services.user_domain import UserDomainManager +from .prompt import SUGGEST_PROMPT +from .schema import ( + SingleFlowSuggestionConfig, + SuggestGenResult, + SuggestionInput, + SuggestionOutput, +) + if TYPE_CHECKING: from apps.scheduler.executor.step import StepExecutor diff --git a/apps/scheduler/call/summary/summary.py b/apps/scheduler/call/summary/summary.py index b605204e179246f915d561c0884fd277712faf33..1dd99b819a89db6081edea327ce85e80756e4f72 100644 --- a/apps/scheduler/call/summary/summary.py +++ b/apps/scheduler/call/summary/summary.py @@ -8,7 +8,6 @@ from pydantic import Field from apps.llm.patterns.executor import ExecutorSummary from apps.scheduler.call.core import CoreCall, DataBase -from apps.scheduler.call.summary.schema import SummaryOutput from apps.schemas.enum_var import CallOutputType from apps.schemas.pool import NodePool from apps.schemas.scheduler import ( @@ -18,6 +17,8 @@ from apps.schemas.scheduler import ( ExecutorBackground, ) +from .schema import SummaryOutput + if TYPE_CHECKING: from apps.scheduler.executor.step import StepExecutor diff --git a/apps/scheduler/executor/flow.py b/apps/scheduler/executor/flow.py index a70d0d7073c50ec2290c3fd4c797bbb3efcd4501..533b759cfb6a42e989d4316ad2a8e51772344ff7 100644 --- a/apps/scheduler/executor/flow.py +++ b/apps/scheduler/executor/flow.py @@ -9,14 +9,15 @@ from datetime import UTC, datetime from pydantic import Field from apps.scheduler.call.llm.prompt import LLM_ERROR_PROMPT -from apps.scheduler.executor.base import BaseExecutor -from apps.scheduler.executor.step import StepExecutor from apps.schemas.enum_var import EventType, SpecialCallType, StepStatus from apps.schemas.flow import Flow, Step from apps.schemas.request_data import RequestDataApp from apps.schemas.task import ExecutorState, StepQueueItem from apps.services.task import TaskManager +from .base import BaseExecutor +from .step import StepExecutor + logger = logging.getLogger(__name__) # 开始前的固定步骤 FIXED_STEPS_BEFORE_START = [ @@ -117,6 +118,15 @@ class FlowExecutor(BaseExecutor): # 如果当前步骤为结束,则直接返回 if self.task.state.step_id == "end" or not self.task.state.step_id: # type: ignore[arg-type] return [] + if self.task.state.step_name == "Choice": + # 如果是choice节点,获取分支ID + branch_id = self.task.context[-1]["output_data"].get("branch_id", None) + if branch_id: + self.task.state.step_id = self.task.state.step_id + "." + branch_id + logger.info("[FlowExecutor] 分支ID:%s", branch_id) + else: + logger.warning("[FlowExecutor] 没有找到分支ID,返回空列表") + return [] next_steps = await self._find_next_id(self.task.state.step_id) # type: ignore[arg-type] # 如果step没有任何出边,直接跳到end diff --git a/apps/scheduler/executor/qa.py b/apps/scheduler/executor/qa.py index ccbc4b0cf3e8aa9036b73c6aee8d0132fbacd315..e3d75e9d2b33f399dadff5886fbb03850be62799 100644 --- a/apps/scheduler/executor/qa.py +++ b/apps/scheduler/executor/qa.py @@ -1,5 +1,5 @@ """用于执行智能问答的Executor""" -from apps.scheduler.executor.base import BaseExecutor +from .base import BaseExecutor class QAExecutor(BaseExecutor): diff --git a/apps/scheduler/executor/step.py b/apps/scheduler/executor/step.py index 6b3451fa9ccd92b8f9b2d497f3983a64ff3a6981..ef05c3696f113b0d0b19400295563983a2e880c2 100644 --- a/apps/scheduler/executor/step.py +++ b/apps/scheduler/executor/step.py @@ -16,7 +16,6 @@ from apps.scheduler.call.facts.facts import FactsCall from apps.scheduler.call.slot.schema import SlotOutput from apps.scheduler.call.slot.slot import Slot from apps.scheduler.call.summary.summary import Summary -from apps.scheduler.executor.base import BaseExecutor from apps.scheduler.pool.pool import Pool from apps.schemas.enum_var import ( EventType, @@ -28,6 +27,8 @@ from apps.schemas.scheduler import CallError, CallOutputChunk from apps.schemas.task import FlowStepHistory, StepQueueItem from apps.services.node import NodeManager +from .base import BaseExecutor + logger = logging.getLogger(__name__) diff --git a/apps/scheduler/mcp/__init__.py b/apps/scheduler/mcp/__init__.py index 12f5cb68c12e4d19d830a8155eaeb0851fce897d..89cb4e735c62e982b3373e9f5621c30130c587ca 100644 --- a/apps/scheduler/mcp/__init__.py +++ b/apps/scheduler/mcp/__init__.py @@ -1,8 +1,8 @@ # Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """Scheduler MCP 模块""" -from apps.scheduler.mcp.host import MCPHost -from apps.scheduler.mcp.plan import MCPPlanner -from apps.scheduler.mcp.select import MCPSelector +from .host import MCPHost +from .plan import MCPPlanner +from .select import MCPSelector __all__ = ["MCPHost", "MCPPlanner", "MCPSelector"] diff --git a/apps/scheduler/mcp/host.py b/apps/scheduler/mcp/host.py index b1a56683a8cb48c0b6625e67200fd12b3a48fb9e..78aa7bc3ee869e8710e1fb02a2d9fb438d04be34 100644 --- a/apps/scheduler/mcp/host.py +++ b/apps/scheduler/mcp/host.py @@ -108,7 +108,7 @@ class MCPHost: step_id=tool.name, step_name=tool.name, # description是规划的实际内容 - step_description=plan_item.plan, + step_description=plan_item.content, status=StepStatus.SUCCESS, input_data=input_data, output_data=output_data, diff --git a/apps/scheduler/mcp/plan.py b/apps/scheduler/mcp/plan.py index cd4f5975eea3f023a92626966081c2d1eb33bdb7..50224567ed9e4f0081d7a55f324f76fd7894ff6b 100644 --- a/apps/scheduler/mcp/plan.py +++ b/apps/scheduler/mcp/plan.py @@ -6,9 +6,10 @@ from jinja2.sandbox import SandboxedEnvironment from apps.llm.function import JsonGenerator from apps.llm.reasoning import ReasoningLLM -from apps.scheduler.mcp.prompt import CREATE_PLAN, FINAL_ANSWER from apps.schemas.mcp import MCPPlan, MCPTool +from .prompt import CREATE_PLAN, FINAL_ANSWER + class MCPPlanner: """MCP 用户目标拆解与规划""" diff --git a/apps/scheduler/mcp/prompt.py b/apps/scheduler/mcp/prompt.py index d6d8243ba053f6ce99647a6ac7690e44171818c5..b322fb0883e8ed935243389cb86066845a549631 100644 --- a/apps/scheduler/mcp/prompt.py +++ b/apps/scheduler/mcp/prompt.py @@ -197,7 +197,7 @@ EVALUATE_PLAN = dedent(r""" ```json { "evaluation": "评估结果", - "plan": [ + "plans": [ { "content": "改进后的计划内容", "tool": "工具ID", diff --git a/apps/scheduler/mcp/select.py b/apps/scheduler/mcp/select.py index 2ff5034471c5e9c38f166c6187b76dfb4596f734..e3b5af84e6f947747e2975f85b757ed17d1c219a 100644 --- a/apps/scheduler/mcp/select.py +++ b/apps/scheduler/mcp/select.py @@ -11,15 +11,14 @@ from apps.common.mongo import MongoDB from apps.llm.embedding import Embedding from apps.llm.function import FunctionLLM from apps.llm.reasoning import ReasoningLLM -from apps.scheduler.mcp.prompt import ( - MCP_SELECT, -) from apps.schemas.mcp import ( MCPCollection, MCPSelectResult, MCPTool, ) +from .prompt import MCP_SELECT + logger = logging.getLogger(__name__) diff --git a/apps/scheduler/mcp_agent/agent/base.py b/apps/scheduler/mcp_agent/agent/base.py index eccb58a9ce8466f67ac76040a53469edac455109..69b06bc3ac26c3318d9e0212febf9cff7d8467ef 100644 --- a/apps/scheduler/mcp_agent/agent/base.py +++ b/apps/scheduler/mcp_agent/agent/base.py @@ -6,10 +6,10 @@ from contextlib import asynccontextmanager from pydantic import BaseModel, Field, model_validator from apps.common.queue import MessageQueue -from apps.schemas.enum_var import AgentState -from apps.schemas.task import Task from apps.llm.reasoning import ReasoningLLM from apps.scheduler.mcp_agent.schema import Memory, Message, Role +from apps.schemas.enum_var import AgentState +from apps.schemas.task import Task from apps.services.activity import Activity logger = logging.getLogger(__name__) diff --git a/apps/scheduler/mcp_agent/agent/mcp.py b/apps/scheduler/mcp_agent/agent/mcp.py index 378da368aca02d0d628352fcc4816b98b2921d01..f309226321cd008ffaa0fc42f6878b76e5ea916f 100644 --- a/apps/scheduler/mcp_agent/agent/mcp.py +++ b/apps/scheduler/mcp_agent/agent/mcp.py @@ -4,9 +4,10 @@ import logging from pydantic import Field from apps.scheduler.mcp.host import MCPHost -from apps.scheduler.mcp_agent.agent.toolcall import ToolCallAgent from apps.scheduler.mcp_agent.tool import Terminate, ToolCollection +from .toolcall import ToolCallAgent + logger = logging.getLogger(__name__) diff --git a/apps/scheduler/mcp_agent/agent/react.py b/apps/scheduler/mcp_agent/agent/react.py index b56efd8b195eb36c4d5718711cc5f07b5a49812f..15dcaac3276f8f3c0468c800c4f68323eb50038e 100644 --- a/apps/scheduler/mcp_agent/agent/react.py +++ b/apps/scheduler/mcp_agent/agent/react.py @@ -2,10 +2,11 @@ from abc import ABC, abstractmethod from pydantic import Field -from apps.schemas.enum_var import AgentState from apps.llm.reasoning import ReasoningLLM -from apps.scheduler.mcp_agent.agent.base import BaseAgent from apps.scheduler.mcp_agent.schema import Memory +from apps.schemas.enum_var import AgentState + +from .base import BaseAgent class ReActAgent(BaseAgent, ABC): diff --git a/apps/scheduler/mcp_agent/agent/toolcall.py b/apps/scheduler/mcp_agent/agent/toolcall.py index 1e22099ce1d2e2f2f54a3bc018511acf887a91a1..dc00ebef3b39f34e29434febddba8e5606178410 100644 --- a/apps/scheduler/mcp_agent/agent/toolcall.py +++ b/apps/scheduler/mcp_agent/agent/toolcall.py @@ -5,12 +5,12 @@ from typing import Any, Optional from pydantic import Field -from apps.schemas.enum_var import AgentState from apps.llm.function import JsonGenerator from apps.llm.patterns import Select from apps.scheduler.mcp_agent.agent.react import ReActAgent from apps.scheduler.mcp_agent.schema import Function, Message, ToolCall from apps.scheduler.mcp_agent.tool import Terminate, ToolCollection +from apps.schemas.enum_var import AgentState logger = logging.getLogger(__name__) diff --git a/apps/scheduler/pool/check.py b/apps/scheduler/pool/check.py index 3a76f6c65a50a0b5364d8fc3498717a9c06e346a..b3cf1852b2d01982d4932296b49ec56cfc6853d2 100644 --- a/apps/scheduler/pool/check.py +++ b/apps/scheduler/pool/check.py @@ -6,7 +6,7 @@ from hashlib import sha256 from anyio import Path -from apps.common.config import Config +from apps.common.config import config from apps.common.mongo import MongoDB from apps.schemas.enum_var import MetadataType @@ -19,7 +19,7 @@ class FileChecker: def __init__(self) -> None: """初始化文件检查器""" self.hashes = {} - self._dir_path = Path(Config().get_config().deploy.data_dir) / "semantics" + self._dir_path = Path(config.deploy.data_dir) / "semantics" async def check_one(self, path: Path) -> dict[str, str]: """检查单个App/Service文件是否有变动""" @@ -44,7 +44,7 @@ class FileChecker: async def diff_one(self, path: Path, previous_hashes: dict[str, str] | None = None) -> bool: """检查文件是否发生变化""" self._resource_path = path - semantics_path = Path(Config().get_config().deploy.data_dir) / "semantics" + semantics_path = Path(config.deploy.data_dir) / "semantics" path_diff = self._resource_path.relative_to(semantics_path) self.hashes[path_diff.as_posix()] = await self.check_one(path) return self.hashes[path_diff.as_posix()] != previous_hashes @@ -54,10 +54,10 @@ class FileChecker: """生成更新列表和删除列表""" if check_type == MetadataType.APP: collection = MongoDB().get_collection("app") - self._dir_path = Path(Config().get_config().deploy.data_dir) / "semantics" / "app" + self._dir_path = Path(config.deploy.data_dir) / "semantics" / "app" elif check_type == MetadataType.SERVICE: collection = MongoDB().get_collection("service") - self._dir_path = Path(Config().get_config().deploy.data_dir) / "semantics" / "service" + self._dir_path = Path(config.deploy.data_dir) / "semantics" / "service" changed_list = [] deleted_list = [] diff --git a/apps/scheduler/pool/loader/__init__.py b/apps/scheduler/pool/loader/__init__.py index 4f229a1320062479b989ffd0c87437bec3ca9863..c78ef6c4cd93ab9915d0d13cf8db8ae16a0bb3f3 100644 --- a/apps/scheduler/pool/loader/__init__.py +++ b/apps/scheduler/pool/loader/__init__.py @@ -1,11 +1,11 @@ # 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 +from .app import AppLoader +from .call import CallLoader +from .flow import FlowLoader +from .mcp import MCPLoader +from .service import ServiceLoader __all__ = [ "AppLoader", diff --git a/apps/scheduler/pool/loader/app.py b/apps/scheduler/pool/loader/app.py index a85395bfc7723af9d009a1945fe4617cf390b938..50d571e92c79671963470b4f9f5aa6d2ff841b4e 100644 --- a/apps/scheduler/pool/loader/app.py +++ b/apps/scheduler/pool/loader/app.py @@ -7,18 +7,19 @@ import shutil from anyio import Path from fastapi.encoders import jsonable_encoder -from apps.common.config import Config +from apps.common.config import config +from apps.common.mongo import MongoDB +from apps.scheduler.pool.check import FileChecker from apps.schemas.agent import AgentAppMetadata from apps.schemas.enum_var import AppType from apps.schemas.flow import AppFlow, AppMetadata, MetadataType, Permission from apps.schemas.pool import AppPool -from apps.common.mongo import MongoDB -from apps.scheduler.pool.check import FileChecker -from apps.scheduler.pool.loader.flow import FlowLoader -from apps.scheduler.pool.loader.metadata import MetadataLoader + +from .flow import FlowLoader +from .metadata import MetadataLoader logger = logging.getLogger(__name__) -BASE_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" / "app" +BASE_PATH = Path(config.deploy.data_dir) / "semantics" / "app" class AppLoader: diff --git a/apps/scheduler/pool/loader/call.py b/apps/scheduler/pool/loader/call.py index 2834487860a5f8cad1c33723ac5add4976160b57..fb1b36af40798d2214e6c6a1ac3b06c45e16ad21 100644 --- a/apps/scheduler/pool/loader/call.py +++ b/apps/scheduler/pool/loader/call.py @@ -9,17 +9,17 @@ from hashlib import shake_128 from pathlib import Path import apps.scheduler.call as system_call -from apps.common.config import Config +from apps.common.config import config +from apps.common.lance import LanceDB +from apps.common.mongo import MongoDB from apps.common.singleton import SingletonMeta +from apps.llm.embedding import Embedding +from apps.models.vector import CallPoolVector from apps.schemas.enum_var import CallType from apps.schemas.pool import CallPool, NodePool -from apps.models.vector import CallPoolVector -from apps.llm.embedding import Embedding -from apps.common.lance import LanceDB -from apps.common.mongo import MongoDB logger = logging.getLogger(__name__) -BASE_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" / "call" +BASE_PATH = Path(config.deploy.data_dir) / "semantics" / "call" class CallLoader(metaclass=SingletonMeta): diff --git a/apps/scheduler/pool/loader/flow.py b/apps/scheduler/pool/loader/flow.py index 57344d40a5c1aba0454a7f00fffcfbf5a93ee221..1f7e13ab51e65d07ed1953aabc17cc6b286806f2 100644 --- a/apps/scheduler/pool/loader/flow.py +++ b/apps/scheduler/pool/loader/flow.py @@ -1,7 +1,6 @@ # Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """Flow加载器""" -import asyncio import logging from hashlib import sha256 from typing import Any @@ -10,19 +9,20 @@ import aiofiles import yaml from anyio import Path -from apps.common.config import Config +from apps.common.config import config +from apps.common.mongo import MongoDB +from apps.llm.embedding import Embedding +from apps.scheduler.util import yaml_enum_presenter, yaml_str_presenter from apps.schemas.enum_var import EdgeType from apps.schemas.flow import AppFlow, Flow from apps.schemas.pool import AppPool from apps.models.vector import FlowPoolVector -from apps.llm.embedding import Embedding from apps.services.node import NodeManager from apps.common.lance import LanceDB -from apps.common.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" +BASE_PATH = Path(config.deploy.data_dir) / "semantics" / "app" + class FlowLoader: """工作流加载器""" @@ -190,7 +190,7 @@ class FlowLoader: logger.warning("[FlowLoader] 工作流文件不存在或不是文件:%s", flow_path) return True - async def _update_db(self, app_id: str, metadata: AppFlow) -> None: # noqa: C901 + async def _update_db(self, app_id: str, metadata: AppFlow) -> None: """更新数据库""" try: app_collection = MongoDB().get_collection("app") diff --git a/apps/scheduler/pool/loader/metadata.py b/apps/scheduler/pool/loader/metadata.py index c5c38cd95ae9c4b686c7709544efe8c9595793ad..002fa45ae4ef4d508b3b3803fa88a29861e095de 100644 --- a/apps/scheduler/pool/loader/metadata.py +++ b/apps/scheduler/pool/loader/metadata.py @@ -8,17 +8,17 @@ import yaml from anyio import Path from fastapi.encoders import jsonable_encoder -from apps.common.config import Config +from apps.common.config import config +from apps.scheduler.util import yaml_str_presenter from apps.schemas.agent import AgentAppMetadata from apps.schemas.enum_var import AppType, MetadataType from apps.schemas.flow import ( AppMetadata, ServiceMetadata, ) -from apps.scheduler.util import yaml_str_presenter logger = logging.getLogger(__name__) -BASE_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" +BASE_PATH = Path(config.deploy.data_dir) / "semantics" class MetadataLoader: diff --git a/apps/scheduler/pool/loader/service.py b/apps/scheduler/pool/loader/service.py index 2b9060461fc0f3baaece19e88c71776449e9752a..2e640e50ce040d76e86fbcdabb1d32e3e04f2686 100644 --- a/apps/scheduler/pool/loader/service.py +++ b/apps/scheduler/pool/loader/service.py @@ -8,19 +8,20 @@ import shutil from anyio import Path from fastapi.encoders import jsonable_encoder -from apps.common.config import Config -from apps.schemas.flow import Permission, ServiceMetadata -from apps.schemas.pool import NodePool, ServicePool -from apps.models.vector import NodePoolVector, ServicePoolVector -from apps.llm.embedding import Embedding +from apps.common.config import config from apps.common.lance import LanceDB from apps.common.mongo import MongoDB +from apps.llm.embedding import Embedding +from apps.models.vector import NodePoolVector, ServicePoolVector from apps.scheduler.pool.check import FileChecker -from apps.scheduler.pool.loader.metadata import MetadataLoader, MetadataType -from apps.scheduler.pool.loader.openapi import OpenAPILoader +from apps.schemas.flow import Permission, ServiceMetadata +from apps.schemas.pool import NodePool, ServicePool + +from .metadata import MetadataLoader, MetadataType +from .openapi import OpenAPILoader logger = logging.getLogger(__name__) -BASE_PATH = Path(Config().get_config().deploy.data_dir) / "semantics" / "service" +BASE_PATH = Path(config.deploy.data_dir) / "semantics" / "service" class ServiceLoader: diff --git a/apps/scheduler/pool/pool.py b/apps/scheduler/pool/pool.py index 7710d24dc102fe02c06d8cbc14f126bd088db2d4..94e6b5eb6058effc1af2035807c5903bbb0dea76 100644 --- a/apps/scheduler/pool/pool.py +++ b/apps/scheduler/pool/pool.py @@ -7,19 +7,20 @@ from typing import Any from anyio import Path -from apps.common.config import Config +from apps.common.config import config from apps.common.mongo import MongoDB -from apps.scheduler.pool.check import FileChecker -from apps.scheduler.pool.loader import ( +from apps.schemas.enum_var import MetadataType +from apps.schemas.flow import Flow +from apps.schemas.pool import AppFlow, CallPool + +from .check import FileChecker +from .loader import ( AppLoader, CallLoader, FlowLoader, MCPLoader, ServiceLoader, ) -from apps.schemas.enum_var import MetadataType -from apps.schemas.flow import Flow -from apps.schemas.pool import AppFlow, CallPool logger = logging.getLogger(__name__) @@ -42,7 +43,7 @@ class Pool: :return: 无 """ - root_dir = Config().get_config().deploy.data_dir.rstrip("/") + "/semantics/" + root_dir = config.deploy.data_dir.rstrip("/") + "/semantics/" 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) diff --git a/apps/scheduler/scheduler/message.py b/apps/scheduler/scheduler/message.py index 3012d75e1e63feb0d1aa5f2eea6962e46f33f24c..9b4fccadf46e0a488537eace1c9442f5f1075564 100644 --- a/apps/scheduler/scheduler/message.py +++ b/apps/scheduler/scheduler/message.py @@ -5,7 +5,7 @@ import logging from datetime import UTC, datetime from textwrap import dedent -from apps.common.config import Config +from apps.common.config import config from apps.common.queue import MessageQueue from apps.schemas.collection import LLM, Document from apps.schemas.enum_var import EventType @@ -31,14 +31,14 @@ async def push_init_message( # 组装feature if is_flow: feature = InitContentFeature( - maxTokens=Config().get_config().llm.max_tokens or 0, + maxTokens=config.llm.max_tokens or 0, contextNum=context_num, enableFeedback=False, enableRegenerate=False, ) else: feature = InitContentFeature( - maxTokens=Config().get_config().llm.max_tokens or 0, + maxTokens=config.llm.max_tokens or 0, contextNum=context_num, enableFeedback=True, enableRegenerate=True, diff --git a/apps/scheduler/scheduler/scheduler.py b/apps/scheduler/scheduler/scheduler.py index 39244160cc77f1b32c3bc12f87ddb88d9340e158..82281b7f1e80bcacec0b058ce3678204689450e3 100644 --- a/apps/scheduler/scheduler/scheduler.py +++ b/apps/scheduler/scheduler/scheduler.py @@ -4,7 +4,7 @@ import logging from datetime import UTC, datetime -from apps.common.config import Config +from apps.common.config import config from apps.common.mongo import MongoDB from apps.common.queue import MessageQueue from apps.scheduler.executor.agent import MCPAgentExecutor @@ -60,10 +60,10 @@ class Scheduler: 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, + openai_base_url=config.llm.endpoint, + openai_api_key=config.llm.key, + model_name=config.llm.model, + max_tokens=config.llm.max_tokens, ) else: llm = await LLMManager.get_llm_by_id(self.task.ids.user_sub, llm_id) diff --git a/apps/scheduler/slot/parser/__init__.py b/apps/scheduler/slot/parser/__init__.py index 81ae4413f3b2855b282d38fd770059b29370dfb2..7d4a577cf41fb782ab4fb779ccbaf0469f2ca9dd 100644 --- a/apps/scheduler/slot/parser/__init__.py +++ b/apps/scheduler/slot/parser/__init__.py @@ -1,10 +1,10 @@ # 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 -from apps.scheduler.slot.parser.default import SlotDefaultParser -from apps.scheduler.slot.parser.timestamp import SlotTimestampParser +from .const import SlotConstParser +from .date import SlotDateParser +from .default import SlotDefaultParser +from .timestamp import SlotTimestampParser __all__ = [ "SlotConstParser", diff --git a/apps/scheduler/slot/slot.py b/apps/scheduler/slot/slot.py index 4caab4d2dc945ec5ac14b7769770f94c39980a9f..1b372e9edf5f97aa498655588a5b584aa3da9d9a 100644 --- a/apps/scheduler/slot/slot.py +++ b/apps/scheduler/slot/slot.py @@ -12,13 +12,13 @@ from jsonschema.exceptions import ValidationError from jsonschema.protocols import Validator from jsonschema.validators import extend -from apps.scheduler.slot.parser import ( +from .parser import ( SlotConstParser, SlotDateParser, SlotDefaultParser, SlotTimestampParser, ) -from apps.scheduler.slot.util import escape_path, patch_json +from .util import escape_path, patch_json logger = logging.getLogger(__name__) diff --git a/apps/schemas/agent.py b/apps/schemas/agent.py index b52f5e1c3315fb873acddb2bcc4e27937498d561..2c6c5f23454f5970db66849954299beea6c7b55c 100644 --- a/apps/schemas/agent.py +++ b/apps/schemas/agent.py @@ -3,11 +3,11 @@ from pydantic import Field -from apps.schemas.enum_var import ( +from .enum_var import ( AppType, MetadataType, ) -from apps.schemas.flow import MetadataBase, Permission +from .flow import MetadataBase, Permission class AgentAppMetadata(MetadataBase): diff --git a/apps/schemas/api_key.py b/apps/schemas/api_key.py index f486a125c99af77ebca9a42867214af1e714f84b..9f0083c33267b372078dfbf7503ac3a9ee3aab31 100644 --- a/apps/schemas/api_key.py +++ b/apps/schemas/api_key.py @@ -3,7 +3,7 @@ from pydantic import BaseModel -from apps.schemas.response_data import ResponseData +from .response_data import ResponseData class _GetAuthKeyMsg(BaseModel): diff --git a/apps/schemas/appcenter.py b/apps/schemas/appcenter.py index a89f39df18083d90b988cc764c0e88f90500d1f3..42bb1d482d9abf55f22c352c0dab78662ff0f9cf 100644 --- a/apps/schemas/appcenter.py +++ b/apps/schemas/appcenter.py @@ -3,7 +3,7 @@ from pydantic import BaseModel, Field -from apps.schemas.enum_var import AppType, PermissionType +from .enum_var import AppType, PermissionType class AppCenterCardItem(BaseModel): diff --git a/apps/schemas/collection.py b/apps/schemas/collection.py index 1d6aa097da435e611a794f3a7d93fa2da3f37861..ae2b452fadcc662e99c0eadd2d9a26724fe906ff 100644 --- a/apps/schemas/collection.py +++ b/apps/schemas/collection.py @@ -6,7 +6,7 @@ from datetime import UTC, datetime from pydantic import BaseModel, Field -from apps.common.config import Config +from apps.common.config import config from apps.constants import NEW_CHAT from apps.templates.generate_llm_operator_config import llm_provider_dict @@ -73,10 +73,10 @@ class LLM(BaseModel): 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) + openai_base_url: str = Field(default=config.llm.endpoint) + openai_api_key: str = Field(default=config.llm.key) + model_name: str = Field(default=config.llm.model) + max_tokens: int | None = Field(default=config.llm.max_tokens) created_at: float = Field(default_factory=lambda: round(datetime.now(tz=UTC).timestamp(), 3)) @@ -84,7 +84,7 @@ class LLMItem(BaseModel): """大模型信息""" llm_id: str = Field(default="empty") - model_name: str = Field(default=Config().get_config().llm.model) + model_name: str = Field(default=config.llm.model) icon: str = Field(default=llm_provider_dict["ollama"]["icon"]) diff --git a/apps/schemas/config.py b/apps/schemas/config.py index b88a81f1afb018663713a3bf4c0b9b62436e59d4..eb0ad66bf8db103a5c74523c31b09720eeb446a7 100644 --- a/apps/schemas/config.py +++ b/apps/schemas/config.py @@ -77,6 +77,16 @@ class MongoDBConfig(BaseModel): database: str = Field(description="MongoDB数据库名") +class PostgresConfig(BaseModel): + """Postgres配置""" + + host: str = Field(description="Postgres主机名") + port: int = Field(description="Postgres端口号", default=5432) + user: str = Field(description="Postgres用户名") + password: str = Field(description="Postgres密码") + database: str = Field(description="Postgres数据库名") + + class LLMConfig(BaseModel): """LLM配置""" @@ -130,6 +140,7 @@ class ConfigModel(BaseModel): fastapi: FastAPIConfig minio: MinioConfig mongodb: MongoDBConfig + postgres: PostgresConfig llm: LLMConfig function_call: FunctionCallConfig security: SecurityConfig diff --git a/apps/schemas/enum_var.py b/apps/schemas/enum_var.py index 9a20ba84d4805bcd502d9d4ca0f9ba3c49a7bb4c..9f171f7bc96d1c8e66e21437bf420d388149de1f 100644 --- a/apps/schemas/enum_var.py +++ b/apps/schemas/enum_var.py @@ -34,7 +34,7 @@ class EventType(str, Enum): """事件类型""" HEARTBEAT = "heartbeat" - INIT = "init", + INIT = "init" TEXT_ADD = "text.add" GRAPH = "graph" DOCUMENT_ADD = "document.add" diff --git a/apps/schemas/flow.py b/apps/schemas/flow.py index 2646d04390099fd92988d78c00d1b1471780d6a9..542f7407417c67428208307344f85a7e70e208d9 100644 --- a/apps/schemas/flow.py +++ b/apps/schemas/flow.py @@ -5,14 +5,14 @@ from typing import Any from pydantic import BaseModel, Field -from apps.schemas.appcenter import AppLink -from apps.schemas.enum_var import ( +from .appcenter import AppLink +from .enum_var import ( AppType, EdgeType, MetadataType, PermissionType, ) -from apps.schemas.flow_topology import PositionItem +from .flow_topology import PositionItem class Edge(BaseModel): diff --git a/apps/schemas/flow_topology.py b/apps/schemas/flow_topology.py index d0ab666a9902ec7e0d81bb922f973d655f29eaf2..17f4baba2b083bfb689b71e75996fe8cb7e6dc61 100644 --- a/apps/schemas/flow_topology.py +++ b/apps/schemas/flow_topology.py @@ -5,7 +5,7 @@ from typing import Any from pydantic import BaseModel, Field -from apps.schemas.enum_var import EdgeType +from .enum_var import EdgeType class NodeMetaDataItem(BaseModel): diff --git a/apps/schemas/mcp.py b/apps/schemas/mcp.py index 4d80cfc5d5c92bf98cbf305c00753950fbfe0244..44021b0ed5f5f6c372ac0a472bb3ac28ff5bbddb 100644 --- a/apps/schemas/mcp.py +++ b/apps/schemas/mcp.py @@ -118,7 +118,7 @@ class MCPToolSelectResult(BaseModel): class MCPPlanItem(BaseModel): """MCP 计划""" - plan: str = Field(description="计划内容") + content: str = Field(description="计划内容") tool: str = Field(description="工具名称") instruction: str = Field(description="工具指令") diff --git a/apps/schemas/message.py b/apps/schemas/message.py index 0d9ea7ce76767d7374bdb7fef5273b4cba2ccc0c..180a5f46dfc2cd2d51925f840ce704ab5861a651 100644 --- a/apps/schemas/message.py +++ b/apps/schemas/message.py @@ -5,8 +5,8 @@ from typing import Any from pydantic import BaseModel, Field -from apps.schemas.enum_var import EventType, StepStatus -from apps.schemas.record import RecordMetadata +from .enum_var import EventType, StepStatus +from .record import RecordMetadata class HeartbeatData(BaseModel): diff --git a/apps/schemas/node.py b/apps/schemas/node.py index 6c4823db5fa765c287a4c8e370272ab434c96450..2803bc909950c4b418a4f74a750aa782c361e8fd 100644 --- a/apps/schemas/node.py +++ b/apps/schemas/node.py @@ -5,7 +5,7 @@ from typing import Any from pydantic import BaseModel, Field -from apps.schemas.pool import NodePool +from .pool import NodePool class APINodeInput(BaseModel): diff --git a/apps/schemas/pool.py b/apps/schemas/pool.py index 27e16b370ec83acc11e1f435ac1da296fe2a9560..0e9cef22d165e7f0fd7c29cdfd2a401293dc73e8 100644 --- a/apps/schemas/pool.py +++ b/apps/schemas/pool.py @@ -6,9 +6,9 @@ from typing import Any from pydantic import BaseModel, Field -from apps.schemas.appcenter import AppLink -from apps.schemas.enum_var import AppType, CallType, PermissionType -from apps.schemas.flow import AppFlow, Permission +from .appcenter import AppLink +from .enum_var import AppType, CallType, PermissionType +from .flow import AppFlow, Permission class BaseData(BaseModel): diff --git a/apps/schemas/record.py b/apps/schemas/record.py index 505699c4e8c1039a0019d00d0b7491af20137105..d6117d8c18997d056404e2f4958cc0e82bccfaf3 100644 --- a/apps/schemas/record.py +++ b/apps/schemas/record.py @@ -7,10 +7,10 @@ from typing import Any, Literal from pydantic import BaseModel, Field -from apps.schemas.collection import ( +from .collection import ( Document, ) -from apps.schemas.enum_var import CommentType, StepStatus +from .enum_var import CommentType, StepStatus class RecordDocument(Document): diff --git a/apps/schemas/request_data.py b/apps/schemas/request_data.py index 2305dd93dfe33969691dcc42928e578e7f1c4207..e5bd09cfe27256357e370e9dd00aaa1ef4795df2 100644 --- a/apps/schemas/request_data.py +++ b/apps/schemas/request_data.py @@ -5,11 +5,12 @@ from typing import Any from pydantic import BaseModel, Field -from apps.common.config import Config -from apps.schemas.appcenter import AppData -from apps.schemas.enum_var import CommentType -from apps.schemas.flow_topology import FlowItem -from apps.schemas.mcp import MCPType +from apps.common.config import config + +from .appcenter import AppData +from .enum_var import CommentType +from .flow_topology import FlowItem +from .mcp import MCPType class RequestDataApp(BaseModel): @@ -32,7 +33,7 @@ class MockRequestData(BaseModel): class RequestDataFeatures(BaseModel): """POST /api/chat的features字段数据""" - max_tokens: int | None = Field(default=Config().get_config().llm.max_tokens, description="最大生成token数") + max_tokens: int | None = Field(default=config.llm.max_tokens, description="最大生成token数") context_num: int = Field(default=5, description="上下文消息数量", le=10, ge=0) diff --git a/apps/schemas/response_data.py b/apps/schemas/response_data.py index b2a1872918638b153e1e94ff4db85ef00cfc0502..5ee70a83d696efba1b50cedcf07e4753d1ec1f1a 100644 --- a/apps/schemas/response_data.py +++ b/apps/schemas/response_data.py @@ -5,19 +5,20 @@ from typing import Any from pydantic import BaseModel, Field -from apps.schemas.appcenter import AppCenterCardItem, AppData -from apps.schemas.collection import Blacklist, Document -from apps.schemas.enum_var import DocumentStatus -from apps.schemas.flow_topology import ( +from apps.templates.generate_llm_operator_config import llm_provider_dict + +from .appcenter import AppCenterCardItem, AppData +from .collection import Blacklist, Document +from .enum_var import DocumentStatus +from .flow_topology import ( FlowItem, NodeMetaDataItem, NodeServiceItem, PositionItem, ) -from apps.schemas.mcp import MCPInstallStatus, MCPTool, MCPType -from apps.schemas.record import RecordData -from apps.schemas.user import UserInfo -from apps.templates.generate_llm_operator_config import llm_provider_dict +from .mcp import MCPInstallStatus, MCPTool, MCPType +from .record import RecordData +from .user import UserInfo class ResponseData(BaseModel): diff --git a/apps/schemas/scheduler.py b/apps/schemas/scheduler.py index 38fd94ad4db6cff189e79f4abaec3448c97d45ec..2eb4cb84a83f9f71a30b85ffa4578826558c013c 100644 --- a/apps/schemas/scheduler.py +++ b/apps/schemas/scheduler.py @@ -5,8 +5,8 @@ from typing import Any from pydantic import BaseModel, Field -from apps.schemas.enum_var import CallOutputType -from apps.schemas.task import FlowStepHistory +from .enum_var import CallOutputType +from .task import FlowStepHistory class CallInfo(BaseModel): diff --git a/apps/schemas/task.py b/apps/schemas/task.py index 9d40a5d20edb33f36145acaa991a05e489c7f88d..2b43cd9b2f404fbb238535515ff95b0bbdad5619 100644 --- a/apps/schemas/task.py +++ b/apps/schemas/task.py @@ -7,8 +7,8 @@ from typing import Any from pydantic import BaseModel, Field -from apps.schemas.enum_var import StepStatus -from apps.schemas.flow import Step +from .enum_var import StepStatus +from .flow import Step class FlowStepHistory(BaseModel): diff --git a/apps/scripts/delete_user.py b/apps/scripts/delete_user.py index c3594a80824b3d45ec2f11fb36e20ae9c164eea5..7cc85a5406cae2b4c32906c9447b1a05c2a628f1 100644 --- a/apps/scripts/delete_user.py +++ b/apps/scripts/delete_user.py @@ -6,12 +6,12 @@ from datetime import UTC, datetime, timedelta import asyncer +from apps.common.mongo import MongoDB from apps.schemas.collection import Audit from apps.services.audit_log import AuditLogManager -from apps.services.user import UserManager -from apps.services.session import SessionManager -from apps.common.mongo import MongoDB from apps.services.knowledge_base import KnowledgeBaseService +from apps.services.session import SessionManager +from apps.services.user import UserManager logger = logging.getLogger(__name__) diff --git a/apps/services/appcenter.py b/apps/services/appcenter.py index e256ab55ab13bf8c5b9e40cb36a3c616a1d39596..85a5505ff31b1a0ede6501204d12ba3989dd568e 100644 --- a/apps/services/appcenter.py +++ b/apps/services/appcenter.py @@ -17,8 +17,9 @@ from apps.schemas.enum_var import AppFilterType, AppType, PermissionType from apps.schemas.flow import AppMetadata, MetadataType, Permission from apps.schemas.pool import AppPool from apps.schemas.response_data import RecentAppList, RecentAppListItem -from apps.services.flow import FlowManager -from apps.services.mcp_service import MCPServiceManager + +from .flow import FlowManager +from .mcp_service import MCPServiceManager logger = logging.getLogger(__name__) diff --git a/apps/services/conversation.py b/apps/services/conversation.py index 4bcade45c757ea2b3dca6c58863b2852af98c15b..9e4f97454213feca8be1f1bd125a79a9298c762e 100644 --- a/apps/services/conversation.py +++ b/apps/services/conversation.py @@ -6,14 +6,15 @@ import uuid from datetime import UTC, datetime from typing import Any -from apps.common.config import Config +from apps.common.config import config from apps.common.mongo import MongoDB from apps.schemas.collection import Conversation, KnowledgeBaseItem, LLMItem -from apps.services.knowledge import KnowledgeBaseManager -from apps.services.llm import LLMManager -from apps.services.task import TaskManager from apps.templates.generate_llm_operator_config import llm_provider_dict +from .knowledge import KnowledgeBaseManager +from .llm import LLMManager +from .task import TaskManager + logger = logging.getLogger(__name__) @@ -46,7 +47,7 @@ class ConversationManager: if llm_id == "empty": llm_item = LLMItem( llm_id="empty", - model_name=Config().get_config().llm.model, + model_name=config.llm.model, icon=llm_provider_dict["ollama"]["icon"], ) else: diff --git a/apps/services/document.py b/apps/services/document.py index be5393603e94aae2e08bb7a892d48d754c77b5bc..f4b4733c70939cc8ea46d05d262a90491d37a7df 100644 --- a/apps/services/document.py +++ b/apps/services/document.py @@ -15,8 +15,9 @@ from apps.schemas.collection import ( Document, ) from apps.schemas.record import RecordDocument, RecordGroup, RecordGroupDocument -from apps.services.knowledge_base import KnowledgeBaseService -from apps.services.session import SessionManager + +from .knowledge_base import KnowledgeBaseService +from .session import SessionManager logger = logging.getLogger(__name__) diff --git a/apps/services/flow.py b/apps/services/flow.py index 9275fc60c75199e8e17ebcba8f164083190b7211..e9e9415e44a38fee41483b67d7d0fd781ddc45f6 100644 --- a/apps/services/flow.py +++ b/apps/services/flow.py @@ -19,7 +19,8 @@ from apps.schemas.flow_topology import ( NodeServiceItem, PositionItem, ) -from apps.services.node import NodeManager + +from .node import NodeManager logger = logging.getLogger(__name__) diff --git a/apps/services/flow_validate.py b/apps/services/flow_validate.py index 78e8d340e955115a51e24ac8fc9081fd8ff00e7e..a4185c82938bb99bbf37ec4659f98fad0ca4f86d 100644 --- a/apps/services/flow_validate.py +++ b/apps/services/flow_validate.py @@ -3,11 +3,16 @@ import collections import logging +from typing import TYPE_CHECKING from apps.exceptions import FlowBranchValidationError, FlowEdgeValidationError, FlowNodeValidationError +from apps.scheduler.pool.pool import Pool from apps.schemas.enum_var import NodeType from apps.schemas.flow_topology import EdgeItem, FlowItem, NodeItem +if TYPE_CHECKING: + from pydantic import BaseModel + logger = logging.getLogger(__name__) @@ -36,18 +41,16 @@ class FlowService: node_branch_map = {} branch_illegal_chars = "." for node in flow_item.nodes: - from apps.scheduler.pool.pool import Pool - from pydantic import BaseModel - if node.node_id != 'start' and node.node_id != 'end' and node.node_id != 'Empty': + if node.node_id not in {"start", "end", "Empty"}: try: call_class: type[BaseModel] = await Pool().get_call(node.call_id) if not call_class: - node.node_id = 'Empty' - node.description = '【对应的api工具被删除!节点不可用!请联系相关人员!】\n\n'+node.description - except Exception as e: - node.node_id = 'Empty' - node.description = '【对应的api工具被删除!节点不可用!请联系相关人员!】\n\n'+node.description - logger.error(f"[FlowService] 获取步骤的call_id失败{node.call_id}由于:{e}") + node.node_id = "Empty" + node.description = "【对应的api工具被删除!节点不可用!请联系相关人员!】\n\n"+node.description + except Exception: + node.node_id = "Empty" + node.description = "【对应的api工具被删除!节点不可用!请联系相关人员!】\n\n"+node.description + logger.exception("[FlowService] 获取步骤的call_id失败%s", node.call_id) node_branch_map[node.step_id] = set() if node.call_id == NodeType.CHOICE.value: node.parameters = node.parameters["input_parameters"] diff --git a/apps/services/knowledge.py b/apps/services/knowledge.py index 377d65f0752ead59eee36002b71a60dd46eee486..876321dc70b460c708e641e081dc1db6d9690063 100644 --- a/apps/services/knowledge.py +++ b/apps/services/knowledge.py @@ -7,12 +7,13 @@ from typing import Any import httpx from fastapi import status -from apps.common.config import Config +from apps.common.config import config from apps.common.mongo import MongoDB from apps.schemas.collection import KnowledgeBaseItem from apps.schemas.response_data import KnowledgeBaseItem as KnowledgeBaseItemResponse from apps.schemas.response_data import TeamKnowledgeBaseItem -from apps.services.session import SessionManager + +from .session import SessionManager logger = logging.getLogger(__name__) @@ -55,7 +56,7 @@ class KnowledgeBaseManager: :return: 知识库列表 """ session_id = await SessionManager.get_session_by_user_sub(user_sub) - url = Config().get_config().rag.rag_service.rstrip("/")+"/kb" + url = config.rag.rag_service.rstrip("/")+"/kb" headers = { "Authorization": f"Bearer {session_id}", "Content-Type": "application/json", diff --git a/apps/services/knowledge_base.py b/apps/services/knowledge_base.py index 81979867634372045e01194a2be7c0dfa4f4a98e..bd99c67fe51099d64ffe1bc65ef3add232cf1e8b 100644 --- a/apps/services/knowledge_base.py +++ b/apps/services/knowledge_base.py @@ -6,7 +6,7 @@ import logging import httpx from fastapi import status -from apps.common.config import Config +from apps.common.config import config from apps.schemas.collection import Document from apps.schemas.rag_data import ( RAGFileParseReq, @@ -15,7 +15,7 @@ from apps.schemas.rag_data import ( ) logger = logging.getLogger(__name__) -rag_host = Config().get_config().rag.rag_service +rag_host = config.rag.rag_service _RAG_DOC_PARSE_URI = rag_host.rstrip("/") + "/doc/temporary/parser" _RAG_DOC_STATUS_URI = rag_host.rstrip("/") + "/doc/temporary/status" _RAG_DOC_DELETE_URI = rag_host.rstrip("/") + "/doc/temporary/delete" diff --git a/apps/services/llm.py b/apps/services/llm.py index 1ce87bdfa9952b184c072368329add0a4e96a9f5..d28e601b8e78e910de197a40f94075308b98db2c 100644 --- a/apps/services/llm.py +++ b/apps/services/llm.py @@ -3,7 +3,7 @@ import logging -from apps.common.config import Config +from apps.common.config import config from apps.common.mongo import MongoDB from apps.schemas.collection import LLM, LLMItem from apps.schemas.request_data import ( @@ -89,10 +89,10 @@ class LLMManager: llm_item = LLMProviderInfo( 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, + openaiBaseUrl=config.llm.endpoint, + openaiApiKey=config.llm.key, + modelName=config.llm.model, + maxTokens=config.llm.max_tokens, isEditable=False, ) llm_list = [llm_item] @@ -173,7 +173,7 @@ class LLMManager: {"$set": {"llm": { "llm_id": "empty", "icon": llm_provider_dict["ollama"]["icon"], - "model_name": Config().get_config().llm.model, + "model_name": config.llm.model, }}}, ) @@ -210,7 +210,7 @@ class LLMManager: else: llm_dict = { "llm_id": "empty", - "model_name": Config().get_config().llm.model, + "model_name": config.llm.model, "icon": llm_provider_dict["ollama"]["icon"], } conv_dict = await conv_collection.find_one({"_id": conversation_id, "user_sub": user_sub}) diff --git a/apps/services/rag.py b/apps/services/rag.py index a0bded22f109145bea449d88765f199ec7e8d137..875ce42d33becfdb7330100f41da3182ef47c4d1 100644 --- a/apps/services/rag.py +++ b/apps/services/rag.py @@ -8,7 +8,7 @@ from collections.abc import AsyncGenerator import httpx from fastapi import status -from apps.common.config import Config +from apps.common.config import config from apps.llm.patterns.rewrite import QuestionRewrite from apps.llm.reasoning import ReasoningLLM from apps.llm.token import TokenCalculator @@ -75,7 +75,7 @@ class RAG: ) -> 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" + url = config.rag.rag_service.rstrip("/") + "/chunk/search" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {session_id}", diff --git a/apps/services/service.py b/apps/services/service.py index 487cd1e408f59af4c1c5322ea8725309e773373f..deea90f0fd91728e407d519450c63f7af888b8e9 100644 --- a/apps/services/service.py +++ b/apps/services/service.py @@ -8,7 +8,7 @@ from typing import Any import yaml from anyio import Path -from apps.common.config import Config +from apps.common.config import config from apps.common.mongo import MongoDB from apps.exceptions import InstancePermissionError, ServiceIDError from apps.scheduler.openapi import ReducedOpenAPISpec @@ -237,7 +237,7 @@ class ServiceCenterManager: msg = "Permission denied" raise InstancePermissionError(msg) service_path = ( - Path(Config().get_config().deploy.data_dir) / "semantics" / "service" / service_id / "openapi" / "api.yaml" + Path(config.deploy.data_dir) / "semantics" / "service" / service_id / "openapi" / "api.yaml" ) async with await service_path.open() as f: service_data = yaml.safe_load(await f.read()) @@ -267,7 +267,7 @@ class ServiceCenterManager: raise ServiceIDError(msg) metadata_path = ( - Path(Config().get_config().deploy.data_dir) / "semantics" / "service" / service_id / "metadata.yaml" + Path(config.deploy.data_dir) / "semantics" / "service" / service_id / "metadata.yaml" ) async with await metadata_path.open() as f: metadata_data = yaml.safe_load(await f.read()) diff --git a/apps/services/session.py b/apps/services/session.py index 904ba26dc842bcb90c6027f35af25084164bcf44..13dd425a7965329cfbf434982bf8ac2694a59611 100644 --- a/apps/services/session.py +++ b/apps/services/session.py @@ -5,13 +5,14 @@ import logging import secrets from datetime import UTC, datetime, timedelta -from apps.common.config import Config +from apps.common.config import config from apps.common.mongo import MongoDB from apps.constants import SESSION_TTL from apps.exceptions import LoginSettingsError from apps.schemas.config import FixedUserConfig from apps.schemas.session import Session -from apps.services.blacklist import UserBlacklistManager + +from .blacklist import UserBlacklistManager logger = logging.getLogger(__name__) @@ -32,8 +33,8 @@ class SessionManager: ip=ip, expired_at=datetime.now(UTC) + timedelta(minutes=SESSION_TTL), ) - if Config().get_config().login.provider == "disable": - login_settings = Config().get_config().login.settings + if config.login.provider == "disable": + login_settings = config.login.settings if not isinstance(login_settings, FixedUserConfig): err = "固定用户配置错误!" raise LoginSettingsError(err) diff --git a/apps/services/task.py b/apps/services/task.py index 1e672be690a6f17896ab01bc7149aa353987ecf7..98926f506614b205b20ab2f281eb0814a67e7508 100644 --- a/apps/services/task.py +++ b/apps/services/task.py @@ -14,7 +14,8 @@ from apps.schemas.task import ( TaskRuntime, TaskTokens, ) -from apps.services.record import RecordManager + +from .record import RecordManager logger = logging.getLogger(__name__) diff --git a/apps/services/token.py b/apps/services/token.py index 8c83d3aa2710fd978f62ebaf59b81fa5b2ed4975..8a41c0ad4861d4c5b359a28a3c3c80cbcb8203c9 100644 --- a/apps/services/token.py +++ b/apps/services/token.py @@ -7,12 +7,13 @@ from datetime import UTC, datetime, timedelta import httpx from fastapi import status -from apps.common.config import Config +from apps.common.config import config from apps.common.mongo import MongoDB from apps.common.oidc import oidc_provider from apps.constants import OIDC_ACCESS_TOKEN_EXPIRE_TIME from apps.schemas.config import OIDCConfig -from apps.services.session import SessionManager + +from .session import SessionManager logger = logging.getLogger(__name__) @@ -52,7 +53,7 @@ class TokenManager: @staticmethod def _get_login_config() -> OIDCConfig: """获取并验证登录配置""" - login_config = Config().get_config().login.settings + login_config = config.login.settings if not isinstance(login_config, OIDCConfig): err = "Authhub OIDC配置错误" raise TypeError(err) diff --git a/apps/services/user.py b/apps/services/user.py index 2721d3773bd43e2a8fda5b371d6336fcf0b9b7f3..8a893d09ab693246397ac7ac11fada8742fbee89 100644 --- a/apps/services/user.py +++ b/apps/services/user.py @@ -6,7 +6,8 @@ from datetime import UTC, datetime from apps.common.mongo import MongoDB from apps.schemas.collection import User -from apps.services.conversation import ConversationManager + +from .conversation import ConversationManager logger = logging.getLogger(__name__) diff --git a/deploy/chart/authhub/configs/backend/copy-config.yml b/deploy/chart/authhub/configs/backend/copy-config.yml deleted file mode 100644 index 25f33b9b245d27a9689c09b9db1ec11357857902..0000000000000000000000000000000000000000 --- a/deploy/chart/authhub/configs/backend/copy-config.yml +++ /dev/null @@ -1,19 +0,0 @@ -copy: - - from: /config/aops-config.yml - to: /config-rw/aops-config.yml - mode: - uid: 0 - gid: 0 - mode: "0o650" - secrets: - - /db-secrets - - /authhub-secrets - - from: /config/conf.d/authhub.yml - to: /config-rw/conf.d/authhub.yml - mode: - uid: 0 - gid: 0 - mode: "0o650" - secrets: - - /db-secrets - - /authhub-secrets diff --git a/deploy/chart/authhub/templates/NOTES.txt b/deploy/chart/authhub/templates/NOTES.txt index af7fd5871fbd07fdce3af1b476ac83d3b3946d13..2589763fe3986b0ab74926792e9153c0d9d4ec34 100644 --- a/deploy/chart/authhub/templates/NOTES.txt +++ b/deploy/chart/authhub/templates/NOTES.txt @@ -1,5 +1,5 @@ 感谢您使用openEuler Intelligence! -当前为0.9.5版本。 +当前为0.9.6版本。 当前Chart的功能为:AuthHub统一登录系统部署。 说明: diff --git a/deploy/chart/authhub/templates/backend/authhub-backend-config.yaml b/deploy/chart/authhub/templates/backend/authhub-backend-config.yaml index 0164dcbb936e9b4224f4bdc3f5f4d1099d519473..0509245f79fb87ddb18be97a27ba610e9c51d431 100644 --- a/deploy/chart/authhub/templates/backend/authhub-backend-config.yaml +++ b/deploy/chart/authhub/templates/backend/authhub-backend-config.yaml @@ -10,5 +10,23 @@ data: authhub.yml: |- {{ tpl (.Files.Get "configs/backend/authhub.yml") . | indent 4 }} copy-config.yml: |- -{{ tpl (.Files.Get "configs/backend/copy-config.yml") . | indent 4 }} + copy: + - from: /config/aops-config.yml + to: /config-rw/aops-config.yml + mode: + uid: 0 + gid: 0 + mode: "0o650" + secrets: + - /db-secrets + - /authhub-secrets + - from: /config/conf.d/authhub.yml + to: /config-rw/conf.d/authhub.yml + mode: + uid: 0 + gid: 0 + mode: "0o650" + secrets: + - /db-secrets + - /authhub-secrets {{- end -}} diff --git a/deploy/chart/authhub/templates/web/authhub-web.yaml b/deploy/chart/authhub/templates/web/authhub-web.yaml index 0523b6dfcc0b12b491178b7178fddf7aed31d580..91bec2d9bf6e6a4a692d3381d45e9f8b852a3107 100644 --- a/deploy/chart/authhub/templates/web/authhub-web.yaml +++ b/deploy/chart/authhub/templates/web/authhub-web.yaml @@ -6,32 +6,13 @@ metadata: name: authhub-web-service namespace: {{ .Release.Namespace }} spec: - type: {{ default "ClusterIP" .Values.authhub.web.service.type }} + type: {{ default "NodePort" .Values.authhub.web.service.type }} selector: app: authhub-web ports: - port: 8000 targetPort: 8000 - nodePort: {{ default nil .Values.authhub.web.service.nodePort }} - ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: authhub-web-ingress - namespace: {{ .Release.Namespace }} -spec: - rules: - - host: {{ default "authhub.eulercopilot.local" .Values.domain.authhub }} - http: - paths: - - path: {{ default "/" .Values.authhub.web.ingress.prefix }} - pathType: Prefix - backend: - service: - name: authhub-web-service - port: - number: 8000 + nodePort: {{ default 30081 .Values.authhub.web.service.nodePort }} --- apiVersion: apps/v1 diff --git a/deploy/chart/databases/templates/NOTES.txt b/deploy/chart/databases/templates/NOTES.txt index 8ea1bb0b4a7d6d04d14439a811b5e59a211b0dc7..c3d1f59454b90bd0261ce8f4bd393f3760dc6785 100644 --- a/deploy/chart/databases/templates/NOTES.txt +++ b/deploy/chart/databases/templates/NOTES.txt @@ -1,3 +1,3 @@ 感谢您使用openEuler Intelligence! -当前为0.9.5版本。 +当前为0.9.6版本。 当前Chart的功能为:数据库部署。 diff --git a/deploy/chart/databases/templates/minio/minio.yaml b/deploy/chart/databases/templates/minio/minio.yaml index 354a0400caaa73f17c08de3d4afdaa9acc30f7d2..185883b624962e1da4c3be9b4df413cf6ba3f1e7 100644 --- a/deploy/chart/databases/templates/minio/minio.yaml +++ b/deploy/chart/databases/templates/minio/minio.yaml @@ -10,14 +10,9 @@ spec: selector: app: minio ports: - - name: minio-data - port: 9000 + - port: 9000 targetPort: 9000 nodePort: {{ default nil .Values.databases.minio.service.dataNodePort }} - - name: minio-console - port: 9001 - targetPort: 9001 - nodePort: {{ default nil .Values.databases.minio.service.consoleNodePort }} --- apiVersion: apps/v1 @@ -48,11 +43,7 @@ spec: - "--console-address" - ":9001" ports: - - name: minio-data - containerPort: 9000 - protocol: TCP - - name: minio-console - containerPort: 9001 + - containerPort: 9000 protocol: TCP livenessProbe: httpGet: diff --git a/deploy/chart/euler_copilot/configs/framework/config.toml b/deploy/chart/euler_copilot/configs/framework/config.toml index 603d7f88ecc7ce639727cc8f467aa09e88e27226..929cc6a564d9b876e98ac2b6ca2a46234bd1c441 100644 --- a/deploy/chart/euler_copilot/configs/framework/config.toml +++ b/deploy/chart/euler_copilot/configs/framework/config.toml @@ -55,7 +55,7 @@ backend = '{{ default "ollama" .Values.models.functionCall.backend }}' endpoint = '{{ default .Values.models.answer.endpoint .Values.models.functionCall.endpoint }}' model = '{{ default .Values.models.answer.name .Values.models.functionCall.name }}' api_key = '{{ default .Values.models.answer.key .Values.models.functionCall.key }}' -max_tokens = '{{ default .Values.models.answer.maxTokens .Values.models.functionCall.maxTokens }}' +max_tokens = {{ default .Values.models.answer.maxTokens .Values.models.functionCall.maxTokens }} temperature = {{ default 0.7 .Values.models.functionCall.temperature }} [check] diff --git a/deploy/chart/euler_copilot/configs/framework/copy-config.yaml b/deploy/chart/euler_copilot/configs/framework/copy-config.yaml deleted file mode 100644 index e06158dff642bbffa3f26cb271f450b0fa1b6d2c..0000000000000000000000000000000000000000 --- a/deploy/chart/euler_copilot/configs/framework/copy-config.yaml +++ /dev/null @@ -1,10 +0,0 @@ -copy: - - from: /config/config.toml - to: /config-rw/config.toml - mode: - uid: 0 - gid: 0 - mode: "0o650" - secrets: - - /db-secrets - - /system-secrets diff --git a/deploy/chart/euler_copilot/configs/rag-web/.env b/deploy/chart/euler_copilot/configs/rag-web/.env deleted file mode 100644 index 77fedc8dc7c07ca36e1e56a60aadaf8e414935b3..0000000000000000000000000000000000000000 --- a/deploy/chart/euler_copilot/configs/rag-web/.env +++ /dev/null @@ -1,3 +0,0 @@ -PROD=enabled -SERVER_NAME={{ default "witchaind.eulercopilot.local" .Values.domain.witchaind }} -DATA_CHAIN_BACEND_URL=http://rag-service.{{ .Release.Namespace }}.svc.cluster.local:9988 diff --git a/deploy/chart/euler_copilot/configs/rag/copy-config.yaml b/deploy/chart/euler_copilot/configs/rag/copy-config.yaml deleted file mode 100644 index 01abd3090db084b09df57aec2477420673c04c35..0000000000000000000000000000000000000000 --- a/deploy/chart/euler_copilot/configs/rag/copy-config.yaml +++ /dev/null @@ -1,19 +0,0 @@ -copy: - - from: /config/.env - to: /config-rw/.env - mode: - uid: 1001 - gid: 1001 - mode: "0o650" - secrets: - - /db-secrets - - /system-secrets - - from: /config/.env-sql - to: /config-rw/.env-sql - mode: - uid: 1001 - gid: 1001 - mode: "0o650" - secrets: - - /db-secrets - - /system-secrets diff --git a/deploy/chart/euler_copilot/configs/web/.env b/deploy/chart/euler_copilot/configs/web/.env deleted file mode 100644 index 6a480d4a3d5f57271250e9d535f993b8f12b9d72..0000000000000000000000000000000000000000 --- a/deploy/chart/euler_copilot/configs/web/.env +++ /dev/null @@ -1,3 +0,0 @@ -PROD=enabled -SERVER_NAME={{ default "www.eulercopilot.local" .Values.domain.euler_copilot }} -FRAMEWORK_URL=http://framework-service.{{ .Release.Namespace }}.svc.cluster.local:8002 \ No newline at end of file diff --git a/deploy/chart/euler_copilot/templates/NOTES.txt b/deploy/chart/euler_copilot/templates/NOTES.txt index 65265e06c12b4ebb23b058ee18fd4230a46ac2bb..ec7debb6b984f3e6a2aa2b3128301cd763bce5e6 100644 --- a/deploy/chart/euler_copilot/templates/NOTES.txt +++ b/deploy/chart/euler_copilot/templates/NOTES.txt @@ -1,5 +1,5 @@ 感谢您使用openEuler Intelligence! -当前为0.9.5版本。 +当前为0.9.6版本。 当前Chart的功能为:openEuler Intelligence核心组件部署。 更多项目动态和分享会议,请关注openEuler sig-intelligence:https://www.openeuler.org/en/sig/sig-detail/?name=sig-intelligence diff --git a/deploy/chart/euler_copilot/templates/cornjob.yaml b/deploy/chart/euler_copilot/templates/cornjob.yaml deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/deploy/chart/euler_copilot/templates/framework/framework-config.yaml b/deploy/chart/euler_copilot/templates/framework/framework-config.yaml index 7fff831b0d991b0fbb175eb8ea44086d265ec43a..328803d4f537c5285baf0e8190d9a8a7d11f0237 100644 --- a/deploy/chart/euler_copilot/templates/framework/framework-config.yaml +++ b/deploy/chart/euler_copilot/templates/framework/framework-config.yaml @@ -8,5 +8,14 @@ data: config.toml: |- {{ tpl (.Files.Get "configs/framework/config.toml") . | indent 4 }} copy-config.yaml: |- -{{ tpl (.Files.Get "configs/framework/copy-config.yaml") . | indent 4 }} + copy: + - from: /config/config.toml + to: /config-rw/config.toml + mode: + uid: 0 + gid: 0 + mode: "0o650" + secrets: + - /db-secrets + - /system-secrets {{- end -}} \ No newline at end of file diff --git a/deploy/chart/euler_copilot/templates/framework/framework-storage.yaml b/deploy/chart/euler_copilot/templates/framework/framework-storage.yaml index 78119e43f04d901eda1f32dde9b3b9bf7ba5b439..b6b97ee83ae623e93af7bdd3c22d46097996df56 100644 --- a/deploy/chart/euler_copilot/templates/framework/framework-storage.yaml +++ b/deploy/chart/euler_copilot/templates/framework/framework-storage.yaml @@ -8,11 +8,11 @@ metadata: spec: storageClassName: {{ default "local-path" .Values.globals.storageClass }} capacity: - storage: {{ default "5Gi" .Values.storage.frameworkSemantics.size }} + storage: {{ default "10Gi" .Values.storage.frameworkSemantics.size }} accessModes: - ReadWriteOnce hostPath: - path: {{ default "/home/eulercopilot/semantics" .Values.storage.frameworkSemantics.path }} + path: {{ default "/var/lib/eulercopilot" .Values.storage.frameworkSemantics.path }} --- apiVersion: v1 diff --git a/deploy/chart/euler_copilot/templates/rag-web/rag-web-config.yaml b/deploy/chart/euler_copilot/templates/rag-web/rag-web-config.yaml index ebd01c6de15b728f949ca93ebe6ef04a179c0e68..75cf602658fb0deaebc615334a414ef974d8ddf1 100644 --- a/deploy/chart/euler_copilot/templates/rag-web/rag-web-config.yaml +++ b/deploy/chart/euler_copilot/templates/rag-web/rag-web-config.yaml @@ -6,5 +6,6 @@ metadata: namespace: {{ .Release.Namespace }} data: .env: |- -{{ tpl (.Files.Get "configs/rag-web/.env") . | indent 4 }} + PROD=enabled + DATA_CHAIN_BACEND_URL=http://rag-service.{{ .Release.Namespace }}.svc.cluster.local:9988 {{- end -}} diff --git a/deploy/chart/euler_copilot/templates/rag-web/rag-web.yaml b/deploy/chart/euler_copilot/templates/rag-web/rag-web.yaml index 8b3defa84f8ca088909929aebf35decefee8a528..fd9daf5379327bef3f0a470785bebf3ab3835a08 100644 --- a/deploy/chart/euler_copilot/templates/rag-web/rag-web.yaml +++ b/deploy/chart/euler_copilot/templates/rag-web/rag-web.yaml @@ -14,25 +14,6 @@ spec: targetPort: 9888 nodePort: {{ default nil .Values.euler_copilot.rag_web.service.nodePort }} ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: rag-web-ingress - namespace: {{ .Release.Namespace }} -spec: - rules: - - host: {{ default "www.eulercopilot.local" .Values.domain.euler_copilot }} - http: - paths: - - path: {{ default "/witchaind" .Values.euler_copilot.rag_web.ingress.prefix }} - pathType: Prefix - backend: - service: - name: rag-web-service - port: - number: 9888 - --- apiVersion: apps/v1 kind: Deployment @@ -77,7 +58,7 @@ spec: name: rag-web-config-volume - mountPath: /var/lib/nginx/tmp name: rag-web-tmp - - mountPath: /home/eulercopilot/.env + - mountPath: /opt/.env name: rag-web-env-volume subPath: .env resources: diff --git a/deploy/chart/euler_copilot/templates/rag/rag-config.yaml b/deploy/chart/euler_copilot/templates/rag/rag-config.yaml index 25bf01df19cb68910d2fb8704d6ba43517892a38..7ffc922d10808077cf1667d58d0dbb3c19c4fa96 100644 --- a/deploy/chart/euler_copilot/templates/rag/rag-config.yaml +++ b/deploy/chart/euler_copilot/templates/rag/rag-config.yaml @@ -10,5 +10,23 @@ data: .env-sql: |- {{ tpl (.Files.Get "configs/rag/.env-sql") . | indent 4}} copy-config.yaml: |- -{{ tpl (.Files.Get "configs/rag/copy-config.yaml") . | indent 4}} + copy: + - from: /config/.env + to: /config-rw/.env + mode: + uid: 1001 + gid: 1001 + mode: "0o650" + secrets: + - /db-secrets + - /system-secrets + - from: /config/.env-sql + to: /config-rw/.env-sql + mode: + uid: 1001 + gid: 1001 + mode: "0o650" + secrets: + - /db-secrets + - /system-secrets {{- end -}} \ No newline at end of file diff --git a/deploy/chart/euler_copilot/templates/web/web-config.yaml b/deploy/chart/euler_copilot/templates/web/web-config.yaml index ee47374afb3e0b14047d33d5b2719d582442f1ca..1793023e13f014cac11ac2939106f14688620c8b 100644 --- a/deploy/chart/euler_copilot/templates/web/web-config.yaml +++ b/deploy/chart/euler_copilot/templates/web/web-config.yaml @@ -6,5 +6,6 @@ metadata: namespace: {{ .Release.Namespace }} data: .env: |- -{{ tpl (.Files.Get "configs/web/.env") . | indent 4 }} + RAG_WEB_URL=http://rag-web-service.{{ .Release.Namespace }}.svc.cluster.local:9888 + FRAMEWORK_URL=http://framework-service.{{ .Release.Namespace }}.svc.cluster.local:8002 {{- end -}} \ No newline at end of file diff --git a/deploy/chart/euler_copilot/templates/web/web.yaml b/deploy/chart/euler_copilot/templates/web/web.yaml index adc214b3dbc0ec571daa634b5540c31789fb03e7..c20c045dd686f0c1607467f5c680821afe7e7994 100644 --- a/deploy/chart/euler_copilot/templates/web/web.yaml +++ b/deploy/chart/euler_copilot/templates/web/web.yaml @@ -6,32 +6,13 @@ metadata: name: web-service namespace: {{ .Release.Namespace }} spec: - type: {{ default "ClusterIP" .Values.euler_copilot.web.service.type }} + type: {{ default "NodePort" .Values.euler_copilot.web.service.type }} selector: app: web ports: - port: 8080 targetPort: 8080 - nodePort: {{ default nil .Values.euler_copilot.web.service.nodePort }} - ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: web-ingress - namespace: {{ .Release.Namespace }} -spec: - rules: - - host: {{ default "www.eulercopilot.local" .Values.domain.euler_copilot }} - http: - paths: - - path: {{ default "/" .Values.euler_copilot.web.ingress.prefix }} - pathType: Prefix - backend: - service: - name: web-service - port: - number: 8080 + nodePort: {{ default 30080 .Values.euler_copilot.web.service.nodePort }} --- apiVersion: apps/v1 @@ -76,7 +57,7 @@ spec: name: web-config-volume - mountPath: /var/lib/nginx/tmp name: web-tmp - - mountPath: /home/eulercopilot/.env + - mountPath: /opt/.env name: web-env-volume subPath: .env - mountPath: /usr/share/nginx/html/static diff --git a/deploy/chart/euler_copilot/values.yaml b/deploy/chart/euler_copilot/values.yaml index dbc61a83fbfa6a8a6abc64dcc7221d129f3cfbf0..edf15b99d9ce58c0fc654f23de2a2507731072e5 100644 --- a/deploy/chart/euler_copilot/values.yaml +++ b/deploy/chart/euler_copilot/values.yaml @@ -115,13 +115,9 @@ euler_copilot: # Service设置 service: # Service类型,例如NodePort - type: + type: NodePort # 当类型为NodePort时,填写主机的端口号 - nodePort: - # Ingress设置 - ingress: - # URI前缀,默认为/ - prefix: + nodePort: 30080 rag_web: # [必填] 是否部署RAG Web前端用户界面 @@ -139,10 +135,6 @@ euler_copilot: type: # 当类型为NodePort时,填写主机的端口号 nodePort: - # Ingress设置 - ingress: - # URI前缀;默认为/ - prefix: rag: # [必填] 是否部署RAG后端服务 diff --git a/deploy/scripts/8-install-EulerCopilot/install_eulercopilot.sh b/deploy/scripts/8-install-EulerCopilot/install_eulercopilot.sh index 6458d7c8127c2d1f3ca62cb934e6db0b25031794..1ff60f727757ff517e1fb2f3a9fb183b977bcabe 100755 --- a/deploy/scripts/8-install-EulerCopilot/install_eulercopilot.sh +++ b/deploy/scripts/8-install-EulerCopilot/install_eulercopilot.sh @@ -253,7 +253,7 @@ modify_yaml() { # 如果不需要保留模型配置,则添加模型相关的参数 if [[ "$preserve_models" != [Yy]* ]]; then set_args+=( - "--set" "models.answer.endpoint=http://$host:11434" + "--set" "models.answer.endpoint=http://$host:11434/v1" "--set" "models.answer.key=sk-123456" "--set" "models.answer.name=deepseek-llm-7b-chat:latest" "--set" "models.functionCall.backend=ollama" diff --git a/deploy/scripts/9-other-script/import_images.sh b/deploy/scripts/9-other-script/import_images.sh index ee801c42a7078a49c6bf70cfb59f0ca0fccd71f7..e5b97706a7045166bd4856bd87f4589913d1a208 100755 --- a/deploy/scripts/9-other-script/import_images.sh +++ b/deploy/scripts/9-other-script/import_images.sh @@ -84,7 +84,7 @@ detect_architecture() { delete_image() { local image=$1 echo -e "${YELLOW}正在删除镜像: $image${NC}" - if sudo ctr -n=k8s.io images rm "$image"; then + if sudo k3s ctr -n=k8s.io images rm "$image"; then echo -e "${GREEN} 删除成功${NC}" else echo -e "${RED} 删除失败${NC}" @@ -109,7 +109,7 @@ import_single_image() { exit 1 fi - if sudo ctr -n=k8s.io images import "$file"; then + if sudo k3s ctr -n=k8s.io images import "$file"; then echo -e "${GREEN} 导入成功${NC}" else echo -e "${RED} 导入失败${NC}" @@ -139,7 +139,7 @@ batch_import_images() { echo -e "\n${BLUE}正在导入 $tar_file...${NC}" - if sudo ctr -n=k8s.io images import "$tar_file"; then + if sudo k3s ctr -n=k8s.io images import "$tar_file"; then ((success_count++)) echo -e "${GREEN} 导入成功${NC}" else @@ -189,7 +189,7 @@ check_images() { echo -e "\n${MAGENTA}开始镜像完整性检查:${NC}" for image in "${expected_images[@]}"; do - if sudo ctr -n=k8s.io images ls | grep -q "$image"; then + if sudo k3s ctr -n=k8s.io images ls | grep -q "$image"; then echo -e "${GREEN}[存在] $image${NC}" else echo -e "${RED}[缺失] $image${NC}" diff --git "a/documents/user-guide/\351\203\250\347\275\262\346\214\207\345\215\227/pictures/\346\250\241\345\236\213\351\205\215\347\275\256\344\275\215\347\275\256.png" "b/documents/user-guide/\351\203\250\347\275\262\346\214\207\345\215\227/pictures/\346\250\241\345\236\213\351\205\215\347\275\256\344\275\215\347\275\256.png" new file mode 100644 index 0000000000000000000000000000000000000000..3f7f852e37c89e8ef2a5930cb7f3322267bb81fe Binary files /dev/null and "b/documents/user-guide/\351\203\250\347\275\262\346\214\207\345\215\227/pictures/\346\250\241\345\236\213\351\205\215\347\275\256\344\275\215\347\275\256.png" differ diff --git "a/documents/user-guide/\351\203\250\347\275\262\346\214\207\345\215\227/\346\227\240\347\275\221\347\273\234\347\216\257\345\242\203\344\270\213\351\203\250\347\275\262\346\214\207\345\215\227.md" "b/documents/user-guide/\351\203\250\347\275\262\346\214\207\345\215\227/\346\227\240\347\275\221\347\273\234\347\216\257\345\242\203\344\270\213\351\203\250\347\275\262\346\214\207\345\215\227.md" index ebd75b3c06e66a5e86d05c2d8568cac1223cfa31..58fda3ef2b0042a11efd19b548dbc5be3fbc5d0b 100644 --- "a/documents/user-guide/\351\203\250\347\275\262\346\214\207\345\215\227/\346\227\240\347\275\221\347\273\234\347\216\257\345\242\203\344\270\213\351\203\250\347\275\262\346\214\207\345\215\227.md" +++ "b/documents/user-guide/\351\203\250\347\275\262\346\214\207\345\215\227/\346\227\240\347\275\221\347\273\234\347\216\257\345\242\203\344\270\213\351\203\250\347\275\262\346\214\207\345\215\227.md" @@ -2,7 +2,7 @@ **版本信息** 当前版本:v0.9.6 -更新日期:2025年6月16日 +更新日期:2025年7月1日 ## 产品概述 @@ -227,18 +227,28 @@ sudo ./deploy.sh **运维命令**: ```bash -# 查看组件日志 -kubectl logs $pod_name -n euler-copilot - # 查看服务状态 kubectl get pod -n euler-copilot +# 查看组件日志 +kubectl logs $pod_name -n euler-copilot + # 大模型配置修改 cd /home/euler-copilot-framework/deploy/chart/euler-copilot vim values.yaml - -# 更新服务 helm upgrade euler-copilot -n euler-copilot . + +# 集群事件检查 +kubectl get events -n euler-copilot + +# 镜像查看 +k3s crictl images + +# 镜像卸载 +k3s crictl rmi $image_id + +# 镜像导入 +k3s ctr image import $image_tar ``` ## 验证安装 @@ -262,12 +272,13 @@ helm upgrade euler-copilot -n euler-copilot . 2. **配置资产库** - 点击对话 + - 点击模型 + ![点击模型配置配置](./pictures/模型配置位置.png) - 点击知识库 ![点击资产库配置](./pictures/资产库配置位置.png) - 选择资产库 ![选择资产库](./pictures/选择资产库.png) - --- ## 附录 @@ -412,7 +423,7 @@ curl -k -X POST "http://localhost:9988/kb/get_answer" \ -d '{"question": "您的问题", "kb_sn": "default_test", "fetch_source": true}' ``` -#### 3. 解决 `helm upgrade` 错误 +#### 3. 解决 helm upgrade 错误 ```text Error: UPGRADE FAILED: Kubernetes cluster unreachable @@ -470,54 +481,69 @@ curl -k -X POST http://localhost:11434/v1/embeddings \ #### 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. **集群事件检查**: +#### 9. 资源不足时,所有pod状态pending? ```bash - kubectl get events -n euler-copilot + kubectl top nodes + df -h # 确保磁盘空间 >30% ``` -2. **镜像状态验证**: + 参考该链接挂载空间较大的磁盘[How to move k3s data to another location](https://mrkandreev.name/snippets/how_to_move_k3s_data_to_another_location/) - ```bash - k3s crictl images - ``` +#### 10. 无法插网线的离线环境安装k3s后启动失败? -3. **日志审查**: +k3s无法找到route和ipv6_route,报错:"no default routes found in '/proc/net/route' or '\proc/net\ipv6_route'",无法使用k3s,可以通过创建创建虚拟网络接口配置解决 - ```bash - kubectl logs -n euler-copilot - ``` +**解决办法:** -4. **资源评估**: +```bash +# 注意:服务器器显示时间需要和当前网络时间保持一致 +mkdir -p /etc/systemd/system/k3s.service.d/ +cat <30% - ``` +#### 11. x86架构且CPU型号E系列的服务器知识库文件解析至71%失败? -5. **版本兼容性检查**: +opengauss数据库的指令集加速特性默认开启,该特性约束为docker镜像的opgs的x86需要支持AVX512指令集,arm需要支持neon指令集。由于x86架构且CPU型号E系列的服务器是低功耗x86计算,仅支持AVX2指令集,不支持AVX512,导致数据插入出现报错。 - ```bash - k3s -v # 需 >= v1.30.2 - ``` +**解决办法:** +由于不加指令集性能会受限,x86环境下如果环境不支持指令集加速,使用l2距离构建索引并查询,需要单独构建rag镜像,不能确保准确率是否会下降,建议更换支持AVX512的CPU型号进行部署。 + +```bash +# 查看是否支持avx512: +cat /proc/cpuinfo| grep avx512 +lscpu | grep -i avx +``` -6. **配置验证**: +#### 12. 工作流理解上下文耗费时间很久? - ```bash - cd euler-copilot-framework/deploy/chart/euler_copilot - vim values.yaml - helm upgrade euler-copilot -n euler-copilot. - ``` +大模型使用带reasoning的模型,例如deepseek-r1-distill-qwen32B + +**解决办法:** + +更换无思考过程的模型,不支持使用Deepseek R1系列和Qwen3系列 diff --git "a/documents/user-guide/\351\203\250\347\275\262\346\214\207\345\215\227/\347\275\221\347\273\234\347\216\257\345\242\203\344\270\213\351\203\250\347\275\262\346\214\207\345\215\227.md" "b/documents/user-guide/\351\203\250\347\275\262\346\214\207\345\215\227/\347\275\221\347\273\234\347\216\257\345\242\203\344\270\213\351\203\250\347\275\262\346\214\207\345\215\227.md" index e2548da1e552ec28f6adf6856b08a84fac87b0ae..98c4cb586fea9fdf49f368f34e0b956ef53247b4 100644 --- "a/documents/user-guide/\351\203\250\347\275\262\346\214\207\345\215\227/\347\275\221\347\273\234\347\216\257\345\242\203\344\270\213\351\203\250\347\275\262\346\214\207\345\215\227.md" +++ "b/documents/user-guide/\351\203\250\347\275\262\346\214\207\345\215\227/\347\275\221\347\273\234\347\216\257\345\242\203\344\270\213\351\203\250\347\275\262\346\214\207\345\215\227.md" @@ -2,7 +2,7 @@ **版本信息** 当前版本:v0.9.6 -更新日期:2025年6月16日 +更新日期:2025年7月1日 ## 产品概述 @@ -186,16 +186,28 @@ sudo ./deploy.sh **运维命令**: ```bash -# 查看组件日志 -kubectl logs $pod_name$ -n euler-copilot - # 查看服务状态 kubectl get pod -n euler-copilot +# 查看组件日志 +kubectl logs $pod_name -n euler-copilot + # 大模型配置修改 cd /home/euler-copilot-framework/deploy/chart/euler-copilot vim values.yaml helm upgrade euler-copilot -n euler-copilot . + +# 集群事件检查 +kubectl get events -n euler-copilot + +# 镜像查看 +k3s crictl images + +# 镜像卸载 +k3s crictl rmi $image_id + +# 镜像导入 +k3s ctr image import $image_tar ``` ## 验证安装 @@ -219,6 +231,8 @@ helm upgrade euler-copilot -n euler-copilot . 2. **配置资产库** - 点击对话 + - 点击模型 + ![点击模型配置配置](./pictures/模型配置位置.png) - 点击知识库 ![点击资产库配置](./pictures/资产库配置位置.png) - 选择资产库 @@ -337,9 +351,9 @@ helm upgrade euler-copilot -n euler-copilot . 参考:[昇腾镜像仓库](https://www.hiascend.com/developer/ascendhub) | [NPU部署案例](./NPU推理服务器部署指南.md) -## FAQ +### FAQ -### 1. 解决 Hugging Face 连接错误 +#### 1. 解决 Hugging Face 连接错误 ```bash urllib3.exceptions.NewConnectionError: : Failed to establish a new connection: [Errno 101] Network is unreachable @@ -358,7 +372,7 @@ urllib3.exceptions.NewConnectionError: > /root/.bashrc source /root/.bashrc ``` -### 4. 查看 Pod 日志失败 +#### 4. 查看 Pod 日志失败 **解决方案**: @@ -387,7 +401,7 @@ source /root/.bashrc cat /etc/systemd/system/k3s.service.env # 确认 no_proxy 包含本机 IP ``` -### 5. GPU 环境流式回复问题 +#### 5. GPU 环境流式回复问题 **解决方案**: @@ -396,7 +410,7 @@ pip install pydantic==1.10.13 # 请求中将 "stream": true 改为 false ``` -### 6. sglang 模型部署 +#### 6. sglang 模型部署 ```bash conda create --prefix=/root/py310 python==3.10.14 @@ -414,7 +428,7 @@ python -m sglang.launch_server \ --tp 8 ``` -### 7. 获取 Embedding +#### 7. 获取 Embedding ```bash curl -k -X POST http://localhost:11434/v1/embeddings \ @@ -422,56 +436,71 @@ curl -k -X POST http://localhost:11434/v1/embeddings \ -d '{"input": "The food was delicious...", "model": "bge-m3", "encoding_format": "float"}' ``` -### 8. 生成证书 +#### 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. **集群事件检查**: +#### 9. 资源不足时,所有pod状态pending? ```bash - kubectl get events -n euler-copilot + kubectl top nodes + df -h # 确保磁盘空间 >30% ``` -2. **镜像状态验证**: + 参考该链接挂载空间较大的磁盘[How to move k3s data to another location](https://mrkandreev.name/snippets/how_to_move_k3s_data_to_another_location/) - ```bash - k3s crictl images - ``` +#### 10. 无法插网线的离线环境安装k3s后启动失败? -3. **日志审查**: +k3s无法找到route和ipv6_route,报错:"no default routes found in '/proc/net/route' or '\proc/net\ipv6_route'",无法使用k3s,可以通过创建创建虚拟网络接口配置解决 - ```bash - kubectl logs -n euler-copilot - ``` +**解决办法:** -4. **资源评估**: +```bash +# 注意:服务器器显示时间需要和当前网络时间保持一致 +mkdir -p /etc/systemd/system/k3s.service.d/ +cat <30% - ``` +#### 11. x86架构且CPU型号E系列的服务器知识库文件解析至71%失败? -5. **版本兼容性检查**: +opengauss数据库的指令集加速特性默认开启,该特性约束为docker镜像的opgs的x86需要支持AVX512指令集,arm需要支持neon指令集。由于x86架构且CPU型号E系列的服务器是低功耗x86计算,仅支持AVX2指令集,不支持AVX512,导致数据插入出现报错。 - ```bash - k3s -v # 需 >= v1.30.2 - ``` +**解决办法:** +由于不加指令集性能会受限,x86环境下如果环境不支持指令集加速,使用l2距离构建索引并查询,需要单独构建rag镜像,不能确保准确率是否会下降,建议更换支持AVX512的CPU型号进行部署。 -6. **配置验证**: +```bash +# 查看是否支持avx512: +cat /proc/cpuinfo| grep avx512 +lscpu | grep -i avx +``` - ```bash - cd /home/euler-copilot-framework/deploy/chart/euler_copilot - vim values.yaml - helm upgrade euler-copilot -n euler-copilot . - ``` +#### 12. 工作流理解上下文耗费时间很久? + +大模型使用带reasoning的模型,例如deepseek-r1-distill-qwen32B + +**解决办法:** + +更换无思考过程的模型,不支持使用Deepseek R1系列和Qwen3系列