From a1ff42f93917ee166bcff5a1da9dd75e9c074a6b Mon Sep 17 00:00:00 2001 From: z30057876 Date: Thu, 7 Aug 2025 17:02:42 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=EF=BC=8C=E5=AE=8C=E5=96=84Task=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/models/record.py | 4 +- apps/models/task.py | 46 +++++- apps/scheduler/call/choice/choice.py | 75 +++++---- .../call/choice/condition_handler.py | 156 ++++++++++-------- apps/scheduler/call/choice/schema.py | 4 +- apps/scheduler/call/mcp/mcp.py | 14 +- apps/scheduler/call/suggest/suggest.py | 3 +- apps/scheduler/executor/agent.py | 6 +- apps/scheduler/executor/flow.py | 5 +- apps/scheduler/mcp/host.py | 7 +- apps/scheduler/mcp/select.py | 26 ++- apps/scheduler/mcp_agent/plan.py | 4 +- apps/scheduler/pool/loader/app.py | 10 +- apps/scheduler/pool/pool.py | 7 +- apps/scheduler/scheduler/scheduler.py | 16 +- apps/schemas/agent.py | 2 +- apps/schemas/response_data.py | 2 +- apps/schemas/scheduler.py | 7 +- .../euler_copilot/configs/deepinsight/.env | 40 +++++ deploy/chart/euler_copilot/configs/rag/.env | 2 +- .../deepinsight-web-config.yaml | 10 ++ .../deepinsight-web/deepinsight-web.yaml | 80 +++++++++ .../deepinsight/deepinsight-config.yaml | 21 +++ .../templates/deepinsight/deepinsight.yaml | 102 ++++++++++++ .../templates/web/web-config.yaml | 1 + .../install_eulercopilot.sh | 63 ++++++- 26 files changed, 546 insertions(+), 167 deletions(-) create mode 100644 deploy/chart/euler_copilot/configs/deepinsight/.env create mode 100644 deploy/chart/euler_copilot/templates/deepinsight-web/deepinsight-web-config.yaml create mode 100644 deploy/chart/euler_copilot/templates/deepinsight-web/deepinsight-web.yaml create mode 100644 deploy/chart/euler_copilot/templates/deepinsight/deepinsight-config.yaml create mode 100644 deploy/chart/euler_copilot/templates/deepinsight/deepinsight.yaml diff --git a/apps/models/record.py b/apps/models/record.py index 008678f9..9a53c44b 100644 --- a/apps/models/record.py +++ b/apps/models/record.py @@ -22,8 +22,8 @@ class Record(Base): UUID(as_uuid=True), ForeignKey("framework_conversation.id"), nullable=False, ) """对话ID""" - taskId: Mapped[uuid.UUID] = mapped_column( # noqa: N815 - UUID(as_uuid=True), ForeignKey("framework_task.id"), nullable=False, + taskId: Mapped[uuid.UUID | None] = mapped_column( # noqa: N815 + UUID(as_uuid=True), ForeignKey("framework_task.id"), nullable=True, ) """任务ID""" content: Mapped[str] = mapped_column(Text, nullable=False) diff --git a/apps/models/task.py b/apps/models/task.py index 9c1ecbc6..2b48f335 100644 --- a/apps/models/task.py +++ b/apps/models/task.py @@ -2,8 +2,8 @@ import uuid -from sqlalchemy import Column, ForeignKey, String, Text -from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy import ForeignKey, String +from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import Mapped, mapped_column from apps.models.base import Base @@ -14,9 +14,14 @@ class Task(Base): __tablename__ = "framework_task" userSub: Mapped[str] = mapped_column(String(255), ForeignKey("framework_user.sub")) # noqa: N815 + """用户ID""" + conversationId: Mapped[uuid.UUID] = mapped_column( # noqa: N815 + UUID(as_uuid=True), ForeignKey("framework_conversation.id"), nullable=False, + ) + checkpointId: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("framework_executor_checkpoint.id")) # noqa: N815 + """对话ID""" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default_factory=uuid.uuid4) - - + """任务ID""" class ExecutorCheckpoint(Base): @@ -24,8 +29,31 @@ class ExecutorCheckpoint(Base): __tablename__ = "framework_executor_checkpoint" - id: Mapped[str] = mapped_column(String(36), primary_key=True) - task_id: Mapped[str] = mapped_column(String(36), ForeignKey("framework_task.id")) - flow_id: Mapped[str] = mapped_column(String(36), ForeignKey("framework_flow.id")) - flow_name: Mapped[str] = mapped_column(String(255)) - step_id: Mapped[str] = mapped_column(String(36)) \ No newline at end of file + taskId: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("framework_task.id"), nullable=False) # noqa: N815 + """任务ID""" + executorId: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False) # noqa: N815 + """执行器ID(例如工作流ID)""" + executorName: Mapped[str] = mapped_column(String(255), nullable=False) # noqa: N815 + """执行器名称(例如工作流名称)""" + stepId: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False) # noqa: N815 + """步骤ID""" + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default_factory=uuid.uuid4) + """检查点ID""" + + + +class ExecutorHistory(Base): + """执行器历史""" + + __tablename__ = "framework_executor_history" + + taskId: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("framework_task.id"), nullable=False) # noqa: N815 + """任务ID""" + executorId: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False) # noqa: N815 + """执行器ID(例如工作流ID)""" + executorName: Mapped[str] = mapped_column(String(255)) # noqa: N815 + """执行器名称(例如工作流名称)""" + stepId: Mapped[str] = mapped_column(String(36)) # noqa: N815 + """步骤ID""" + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default_factory=uuid.uuid4) + """执行器历史ID""" diff --git a/apps/scheduler/call/choice/choice.py b/apps/scheduler/call/choice/choice.py index c78cd4ab..f294c9cb 100644 --- a/apps/scheduler/call/choice/choice.py +++ b/apps/scheduler/call/choice/choice.py @@ -42,7 +42,7 @@ class Choice(CoreCall, input_model=ChoiceInput, output_model=ChoiceOutput): """返回Call的名称和描述""" return CallInfo(name="选择器", description="使用大模型或使用程序做出判断") - async def _prepare_message(self, call_vars: CallVars) -> list[dict[str, Any]]: + async def _prepare_message(self, call_vars: CallVars) -> list[ChoiceBranch]: # noqa: C901, PLR0912, PLR0915 """替换choices中的系统变量""" valid_choices = [] @@ -50,8 +50,8 @@ class Choice(CoreCall, input_model=ChoiceInput, output_model=ChoiceOutput): try: # 验证逻辑运算符 if choice.logic not in [Logic.AND, Logic.OR]: - msg = f"无效的逻辑运算符: {choice.logic}" - logger.warning(f"[Choice] 分支 {choice.branch_id} 条件处理失败: {msg}") + msg = f"[Choice] 分支 {choice.branch_id} 条件处理失败:无效的逻辑运算符:{choice.logic}" + logger.warning(msg) continue valid_conditions = [] @@ -60,62 +60,74 @@ class Choice(CoreCall, input_model=ChoiceInput, output_model=ChoiceOutput): # 处理左值 if condition.left.step_id is not None: condition.left.value = self._extract_history_variables( - condition.left.step_id+'/'+condition.left.value, call_vars.history) + f"{condition.left.step_id}/{condition.left.value}", call_vars.history) # 检查历史变量是否成功提取 if condition.left.value is None: - msg = f"步骤 {condition.left.step_id} 的历史变量不存在" - logger.warning(f"[Choice] 分支 {choice.branch_id} 条件处理失败: {msg}") + msg = (f"[Choice] 分支 {choice.branch_id} 条件处理失败:" + f"步骤 {condition.left.step_id} 的历史变量不存在") + logger.warning(msg) continue - if not ConditionHandler.check_value_type( - condition.left.value, condition.left.type): - msg = f"左值类型不匹配: {condition.left.value} 应为 {condition.left.type.value}" - logger.warning(f"[Choice] 分支 {choice.branch_id} 条件处理失败: {msg}") + if not ConditionHandler.check_value_type(condition.left, condition.left.type): + msg = (f"[Choice] 分支 {choice.branch_id} 条件处理失败:" + f"左值类型不匹配:{condition.left.value}" + f"应为 {condition.left.type.value if condition.left.type else 'None'}") + logger.warning(msg) continue else: - msg = "左侧变量缺少step_id" - logger.warning(f"[Choice] 分支 {choice.branch_id} 条件处理失败: {msg}") + msg = f"[Choice] 分支 {choice.branch_id} 条件处理失败:左侧变量缺少step_id" + logger.warning(msg) continue # 处理右值 if condition.right.step_id is not None: condition.right.value = self._extract_history_variables( - condition.right.step_id+'/'+condition.right.value, call_vars.history) + f"{condition.right.step_id}/{condition.right.value}", call_vars.history, + ) # 检查历史变量是否成功提取 if condition.right.value is None: - msg = f"步骤 {condition.right.step_id} 的历史变量不存在" - logger.warning(f"[Choice] 分支 {choice.branch_id} 条件处理失败: {msg}") + msg = (f"[Choice] 分支 {choice.branch_id} 条件处理失败:" + f"步骤 {condition.right.step_id} 的历史变量不存在") + logger.warning(msg) continue if not ConditionHandler.check_value_type( - condition.right.value, condition.right.type): - msg = f"右值类型不匹配: {condition.right.value} 应为 {condition.right.type.value}" - logger.warning(f"[Choice] 分支 {choice.branch_id} 条件处理失败: {msg}") + condition.right, condition.right.type, + ): + msg = (f"[Choice] 分支 {choice.branch_id} 条件处理失败:" + f"右值类型不匹配:{condition.right.value}" + f"应为 {condition.right.type.value if condition.right.type else 'None'}") + logger.warning(msg) continue else: # 如果右值没有step_id,尝试从call_vars中获取 right_value_type = await ConditionHandler.get_value_type_from_operate( - condition.operate) + condition.operate, + ) if right_value_type is None: - msg = f"不支持的运算符: {condition.operate}" - logger.warning(f"[Choice] 分支 {choice.branch_id} 条件处理失败: {msg}") + msg = f"[Choice] 分支 {choice.branch_id} 条件处理失败:不支持的运算符:{condition.operate}" + logger.warning(msg) continue if condition.right.type != right_value_type: - msg = f"右值类型不匹配: {condition.right.value} 应为 {right_value_type.value}" - logger.warning(f"[Choice] 分支 {choice.branch_id} 条件处理失败: {msg}") + msg = (f"[Choice] 分支 {choice.branch_id} 条件处理失败:" + f"右值类型不匹配:{condition.right.value} 应为 {right_value_type.value}") + logger.warning(msg) continue if right_value_type == Type.STRING: condition.right.value = str(condition.right.value) else: condition.right.value = ast.literal_eval(condition.right.value) if not ConditionHandler.check_value_type( - condition.right.value, condition.right.type): - msg = f"右值类型不匹配: {condition.right.value} 应为 {condition.right.type.value}" - logger.warning(f"[Choice] 分支 {choice.branch_id} 条件处理失败: {msg}") + condition.right, condition.right.type, + ): + msg = (f"[Choice] 分支 {choice.branch_id} 条件处理失败:" + f"右值类型不匹配:{condition.right.value}" + f"应为 {condition.right.type.value if condition.right.type else 'None'}") + logger.warning(msg) continue valid_conditions.append(condition) # 如果所有条件都无效,抛出异常 if not valid_conditions and not choice.is_default: - msg = "分支没有有效条件" - logger.warning(f"[Choice] 分支 {choice.branch_id} 条件处理失败: {msg}") + msg = f"[Choice] 分支 {choice.branch_id} 条件处理失败:没有有效条件" + logger.warning(msg) continue # 更新有效条件 @@ -123,7 +135,8 @@ class Choice(CoreCall, input_model=ChoiceInput, output_model=ChoiceOutput): valid_choices.append(choice) except ValueError as e: - logger.warning("分支 %s 处理失败: %s,已跳过", choice.branch_id, str(e)) + msg = f"[Choice] 分支 {choice.branch_id} 处理失败:{e!s},已跳过" + logger.warning(msg) continue return valid_choices @@ -135,7 +148,7 @@ class Choice(CoreCall, input_model=ChoiceInput, output_model=ChoiceOutput): ) async def _exec( - self, input_data: dict[str, Any] + self, input_data: dict[str, Any], ) -> AsyncGenerator[CallOutputChunk, None]: """执行Choice工具""" # 解析输入数据 @@ -147,4 +160,4 @@ class Choice(CoreCall, input_model=ChoiceInput, output_model=ChoiceOutput): content=ChoiceOutput(branch_id=branch_id).model_dump(exclude_none=True, by_alias=True), ) except Exception as e: - raise CallError(message=f"选择工具调用失败:{e!s}", data={}) from e \ No newline at end of file + 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 index 3ee04825..3c1354c9 100644 --- a/apps/scheduler/call/choice/condition_handler.py +++ b/apps/scheduler/call/choice/condition_handler.py @@ -2,6 +2,7 @@ """处理条件分支的工具""" import logging +import re from pydantic import BaseModel @@ -25,9 +26,11 @@ logger = logging.getLogger(__name__) class ConditionHandler(BaseModel): """条件分支处理器""" + @staticmethod - async def get_value_type_from_operate(operate: NumberOperate | StringOperate | ListOperate | - BoolOperate | DictOperate) -> Type: + async def get_value_type_from_operate( # noqa: PLR0911 + operate: NumberOperate | StringOperate | ListOperate | BoolOperate | DictOperate | None, + ) -> Type | None: """获取右值的类型""" if isinstance(operate, NumberOperate): return Type.NUMBER @@ -56,7 +59,7 @@ class ConditionHandler(BaseModel): return None @staticmethod - def check_value_type(value: Value, expected_type: Type) -> bool: + def check_value_type(value: Value, expected_type: Type | None) -> bool: """检查值的类型是否符合预期""" if expected_type == Type.STRING and isinstance(value.value, str): return True @@ -66,9 +69,7 @@ class ConditionHandler(BaseModel): return True if expected_type == Type.DICT and isinstance(value.value, dict): return True - if expected_type == Type.BOOL and isinstance(value.value, bool): - return True - return False + return bool(expected_type == Type.BOOL and isinstance(value.value, bool)) @staticmethod def handler(choices: list[ChoiceBranch]) -> str: @@ -82,7 +83,8 @@ class ConditionHandler(BaseModel): if result is not None: results.append(result) if not results: - logger.warning(f"[Choice] 分支 {block_judgement.branch_id} 条件处理失败: 没有有效的条件") + err = f"[Choice] 分支 {block_judgement.branch_id} 条件处理失败: 没有有效的条件" + logger.warning(err) continue if block_judgement.logic == Logic.AND: final_result = all(results) @@ -108,27 +110,27 @@ class ConditionHandler(BaseModel): left = condition.left operate = condition.operate right = condition.right - value_type = condition.type + value_type = condition.left.type - result = None - if value_type == Type.STRING: + result = False + if value_type == Type.STRING and isinstance(operate, StringOperate): result = ConditionHandler._judge_string_condition(left, operate, right) - elif value_type == Type.NUMBER: + elif value_type == Type.NUMBER and isinstance(operate, NumberOperate): result = ConditionHandler._judge_number_condition(left, operate, right) - elif value_type == Type.BOOL: + elif value_type == Type.BOOL and isinstance(operate, BoolOperate): result = ConditionHandler._judge_bool_condition(left, operate, right) - elif value_type == Type.LIST: + elif value_type == Type.LIST and isinstance(operate, ListOperate): result = ConditionHandler._judge_list_condition(left, operate, right) - elif value_type == Type.DICT: + elif value_type == Type.DICT and isinstance(operate, DictOperate): result = ConditionHandler._judge_dict_condition(left, operate, right) else: - msg = f"不支持的数据类型: {value_type}" - logger.error(f"[Choice] 条件处理失败: {msg}") - return None + msg = f"[Choice] 条件处理失败: 不支持的数据类型: {value_type}" + logger.error(msg) + return False return result @staticmethod - def _judge_string_condition(left: Value, operate: StringOperate, right: Value) -> bool: + def _judge_string_condition(left: Value, operate: StringOperate, right: Value) -> bool: # noqa: C901, PLR0911, PLR0912 """ 判断字符串类型的条件。 @@ -145,33 +147,37 @@ class ConditionHandler(BaseModel): if not isinstance(left_value, str): msg = f"左值必须是字符串类型 ({left_value})" logger.warning(msg) - return None + return False right_value = right.value + if not isinstance(right_value, str): + msg = f"右值必须是字符串类型 ({right_value})" + logger.warning(msg) + return False + if operate == StringOperate.EQUAL: return left_value == right_value - elif operate == StringOperate.NOT_EQUAL: + if operate == StringOperate.NOT_EQUAL: return left_value != right_value - elif operate == StringOperate.CONTAINS: + if operate == StringOperate.CONTAINS: return right_value in left_value - elif operate == StringOperate.NOT_CONTAINS: + if operate == StringOperate.NOT_CONTAINS: return right_value not in left_value - elif operate == StringOperate.STARTS_WITH: + if operate == StringOperate.STARTS_WITH: return left_value.startswith(right_value) - elif operate == StringOperate.ENDS_WITH: + if operate == StringOperate.ENDS_WITH: return left_value.endswith(right_value) - elif operate == StringOperate.REGEX_MATCH: - import re + if operate == StringOperate.REGEX_MATCH: return bool(re.match(right_value, left_value)) - elif operate == StringOperate.LENGTH_EQUAL: + if operate == StringOperate.LENGTH_EQUAL: return len(left_value) == right_value - elif operate == StringOperate.LENGTH_GREATER_THAN: - return len(left_value) > right_value - elif operate == StringOperate.LENGTH_GREATER_THAN_OR_EQUAL: - return len(left_value) >= right_value - elif operate == StringOperate.LENGTH_LESS_THAN: - return len(left_value) < right_value - elif operate == StringOperate.LENGTH_LESS_THAN_OR_EQUAL: - return len(left_value) <= right_value + if operate == StringOperate.LENGTH_GREATER_THAN: + return len(left_value) > len(right_value) + if operate == StringOperate.LENGTH_GREATER_THAN_OR_EQUAL: + return len(left_value) >= len(right_value) + if operate == StringOperate.LENGTH_LESS_THAN: + return len(left_value) < len(right_value) + if operate == StringOperate.LENGTH_LESS_THAN_OR_EQUAL: + return len(left_value) <= len(right_value) return False @staticmethod @@ -192,19 +198,24 @@ class ConditionHandler(BaseModel): if not isinstance(left_value, (int, float)): msg = f"左值必须是数字类型 ({left_value})" logger.warning(msg) - return None + return False right_value = right.value + if not isinstance(right_value, (int, float)): + msg = f"右值必须是数字类型 ({right_value})" + logger.warning(msg) + return False + if operate == NumberOperate.EQUAL: return left_value == right_value - elif operate == NumberOperate.NOT_EQUAL: + if operate == NumberOperate.NOT_EQUAL: return left_value != right_value - elif operate == NumberOperate.GREATER_THAN: + if operate == NumberOperate.GREATER_THAN: return left_value > right_value - elif operate == NumberOperate.LESS_THAN: # noqa: PLR2004 + if operate == NumberOperate.LESS_THAN: return left_value < right_value - elif operate == NumberOperate.GREATER_THAN_OR_EQUAL: + if operate == NumberOperate.GREATER_THAN_OR_EQUAL: return left_value >= right_value - elif operate == NumberOperate.LESS_THAN_OR_EQUAL: + if operate == NumberOperate.LESS_THAN_OR_EQUAL: return left_value <= right_value return False @@ -226,20 +237,21 @@ class ConditionHandler(BaseModel): if not isinstance(left_value, bool): msg = "左值必须是布尔类型" logger.warning(msg) - return None + return False right_value = right.value + if not isinstance(right_value, bool): + msg = "右值必须是布尔类型" + logger.warning(msg) + return False + if operate == BoolOperate.EQUAL: return left_value == right_value - elif operate == BoolOperate.NOT_EQUAL: + if operate == BoolOperate.NOT_EQUAL: return left_value != right_value - elif operate == BoolOperate.IS_EMPTY: - return not left_value - elif operate == BoolOperate.NOT_EMPTY: - return left_value return False @staticmethod - def _judge_list_condition(left: Value, operate: ListOperate, right: Value): + def _judge_list_condition(left: Value, operate: ListOperate, right: Value) -> bool: # noqa: C901, PLR0911 """ 判断列表类型的条件。 @@ -256,30 +268,35 @@ class ConditionHandler(BaseModel): if not isinstance(left_value, list): msg = f"左值必须是列表类型 ({left_value})" logger.warning(msg) - return None + return False right_value = right.value + if not isinstance(right_value, list): + msg = f"右值必须是列表类型 ({right_value})" + logger.warning(msg) + return False + if operate == ListOperate.EQUAL: return left_value == right_value - elif operate == ListOperate.NOT_EQUAL: + if operate == ListOperate.NOT_EQUAL: return left_value != right_value - elif operate == ListOperate.CONTAINS: + if operate == ListOperate.CONTAINS: return right_value in left_value - elif operate == ListOperate.NOT_CONTAINS: + if operate == ListOperate.NOT_CONTAINS: return right_value not in left_value - elif operate == ListOperate.LENGTH_EQUAL: + if operate == ListOperate.LENGTH_EQUAL: return len(left_value) == right_value - elif operate == ListOperate.LENGTH_GREATER_THAN: - return len(left_value) > right_value - elif operate == ListOperate.LENGTH_GREATER_THAN_OR_EQUAL: - return len(left_value) >= right_value - elif operate == ListOperate.LENGTH_LESS_THAN: - return len(left_value) < right_value - elif operate == ListOperate.LENGTH_LESS_THAN_OR_EQUAL: - return len(left_value) <= right_value + if operate == ListOperate.LENGTH_GREATER_THAN: + return len(left_value) > len(right_value) + if operate == ListOperate.LENGTH_GREATER_THAN_OR_EQUAL: + return len(left_value) >= len(right_value) + if operate == ListOperate.LENGTH_LESS_THAN: + return len(left_value) < len(right_value) + if operate == ListOperate.LENGTH_LESS_THAN_OR_EQUAL: + return len(left_value) <= len(right_value) return False @staticmethod - def _judge_dict_condition(left: Value, operate: DictOperate, right: Value): + def _judge_dict_condition(left: Value, operate: DictOperate, right: Value) -> bool: # noqa: PLR0911 """ 判断字典类型的条件。 @@ -296,14 +313,19 @@ class ConditionHandler(BaseModel): if not isinstance(left_value, dict): msg = f"左值必须是字典类型 ({left_value})" logger.warning(msg) - return None + return False right_value = right.value + if not isinstance(right_value, dict): + msg = f"右值必须是字典类型 ({right_value})" + logger.warning(msg) + return False + if operate == DictOperate.EQUAL: return left_value == right_value - elif operate == DictOperate.NOT_EQUAL: + if operate == DictOperate.NOT_EQUAL: return left_value != right_value - elif operate == DictOperate.CONTAINS_KEY: + if operate == DictOperate.CONTAINS_KEY: return right_value in left_value - elif operate == DictOperate.NOT_CONTAINS_KEY: + if operate == DictOperate.NOT_CONTAINS_KEY: return right_value not in left_value - return False \ No newline at end of file + return False diff --git a/apps/scheduler/call/choice/schema.py b/apps/scheduler/call/choice/schema.py index 1be23761..95532270 100644 --- a/apps/scheduler/call/choice/schema.py +++ b/apps/scheduler/call/choice/schema.py @@ -3,7 +3,7 @@ import uuid from enum import Enum -from pydantic import BaseModel, Field +from pydantic import Field from apps.scheduler.call.core import DataBase from apps.schemas.parameters import ( @@ -60,4 +60,4 @@ class ChoiceInput(DataBase): class ChoiceOutput(DataBase): """Choice Call的输出""" - branch_id: str = Field(description="分支ID", default="") \ No newline at end of file + branch_id: str = Field(description="分支ID", default="") diff --git a/apps/scheduler/call/mcp/mcp.py b/apps/scheduler/call/mcp/mcp.py index 6b4b7958..55c0d7fa 100644 --- a/apps/scheduler/call/mcp/mcp.py +++ b/apps/scheduler/call/mcp/mcp.py @@ -58,9 +58,9 @@ class MCP(CoreCall, input_model=MCPInput, output_model=MCPOutput): # 获取工具列表 avaliable_tools = {} for tool in self._tool_list: - if tool.mcp_id not in avaliable_tools: - avaliable_tools[tool.mcp_id] = [] - avaliable_tools[tool.mcp_id].append(tool.name) + if tool.mcpId not in avaliable_tools: + avaliable_tools[tool.mcpId] = [] + avaliable_tools[tool.mcpId].append(tool.toolName) return MCPInput(avaliable_tools=avaliable_tools, max_steps=self.max_steps) @@ -120,7 +120,7 @@ class MCP(CoreCall, input_model=MCPInput, output_model=MCPOutput): # 提示开始调用 yield self._create_output( - f"[MCP] 正在调用工具 {tool.name}...\n\n", + f"[MCP] 正在调用工具 {tool.toolName}...\n\n", MCPMessageType.TOOL_BEGIN, ) @@ -128,14 +128,14 @@ class MCP(CoreCall, input_model=MCPInput, output_model=MCPOutput): try: result = await self._host.call_tool(tool, plan_item) except Exception as e: - err = f"[MCP] 工具 {tool.name} 调用失败: {e!s}" + err = f"[MCP] 工具 {tool.toolName} 调用失败: {e!s}" logger.exception(err) raise CallError(err, data={}) from e # 提示调用完成 - logger.info("[MCP] 工具 %s 调用完成, 结果: %s", tool.name, result) + logger.info("[MCP] 工具 %s 调用完成, 结果: %s", tool.toolName, result) yield self._create_output( - f"[MCP] 工具 {tool.name} 调用完成\n\n", + f"[MCP] 工具 {tool.toolName} 调用完成\n\n", MCPMessageType.TOOL_END, data={ "data": result, diff --git a/apps/scheduler/call/suggest/suggest.py b/apps/scheduler/call/suggest/suggest.py index 432c6468..7b0b2d99 100644 --- a/apps/scheduler/call/suggest/suggest.py +++ b/apps/scheduler/call/suggest/suggest.py @@ -23,6 +23,7 @@ from apps.schemas.scheduler import ( CallOutputChunk, CallVars, ) +from apps.services.appcenter import AppCenterManager from apps.services.record import RecordManager from apps.services.user_tag import UserTagManager @@ -83,8 +84,6 @@ class Suggestion(CoreCall, input_model=SuggestionInput, output_model=SuggestionO async def _init(self, call_vars: CallVars) -> SuggestionInput: """初始化""" - from apps.services.appcenter import AppCenterManager - self._history_questions = await self._get_history_questions( call_vars.ids.user_sub, self.conversation_id, diff --git a/apps/scheduler/executor/agent.py b/apps/scheduler/executor/agent.py index 4a88ecb6..421e2db2 100644 --- a/apps/scheduler/executor/agent.py +++ b/apps/scheduler/executor/agent.py @@ -3,18 +3,16 @@ import logging import uuid -from typing import Any from pydantic import Field -from apps.llm.patterns.rewrite import QuestionRewrite from apps.llm.reasoning import ReasoningLLM from apps.scheduler.executor.base import BaseExecutor from apps.scheduler.mcp_agent.host import MCPHost from apps.scheduler.mcp_agent.plan import MCPPlanner from apps.scheduler.mcp_agent.select import FINAL_TOOL_ID, MCPSelector from apps.scheduler.pool.mcp.client import MCPClient -from apps.schemas.enum_var import EventType, FlowStatus, SpecialCallType, StepStatus +from apps.schemas.enum_var import EventType, FlowStatus, StepStatus from apps.schemas.mcp import ( ErrorType, GoalEvaluationResult, @@ -26,7 +24,7 @@ from apps.schemas.mcp import ( ToolRisk, ) from apps.schemas.message import param -from apps.schemas.task import ExecutorState, FlowStepHistory, StepQueueItem +from apps.schemas.task import FlowStepHistory from apps.services.appcenter import AppCenterManager from apps.services.mcp_service import MCPServiceManager from apps.services.task import TaskManager diff --git a/apps/scheduler/executor/flow.py b/apps/scheduler/executor/flow.py index 08b34ecc..286c4df0 100644 --- a/apps/scheduler/executor/flow.py +++ b/apps/scheduler/executor/flow.py @@ -47,10 +47,7 @@ class FlowExecutor(BaseExecutor): flow_id: str = Field(description="Flow ID") question: str = Field(description="用户输入") post_body_app: RequestDataApp = Field(description="请求体中的app信息") - current_step: StepQueueItem | None = Field( - description="当前执行的步骤", - default=None, - ) + current_step: StepQueueItem = Field(description="当前执行的步骤") async def load_state(self) -> None: diff --git a/apps/scheduler/mcp/host.py b/apps/scheduler/mcp/host.py index 443fd29d..a4fc8b89 100644 --- a/apps/scheduler/mcp/host.py +++ b/apps/scheduler/mcp/host.py @@ -3,6 +3,7 @@ import json import logging +import uuid from typing import Any from jinja2 import BaseLoader @@ -14,7 +15,7 @@ from apps.models.mcp import MCPTools from apps.scheduler.mcp.prompt import MEMORY_TEMPLATE from apps.scheduler.pool.mcp.client import MCPClient from apps.scheduler.pool.mcp.pool import MCPPool -from apps.schemas.enum_var import StepStatus +from apps.schemas.enum_var import FlowStatus, StepStatus from apps.schemas.mcp import MCPPlanItem from apps.schemas.task import FlowStepHistory from apps.services.mcp_service import MCPServiceManager @@ -26,7 +27,7 @@ logger = logging.getLogger(__name__) class MCPHost: """MCP宿主服务""" - def __init__(self, user_sub: str, task_id: str, runtime_id: str, runtime_name: str) -> None: + def __init__(self, user_sub: str, task_id: uuid.UUID, runtime_id: uuid.UUID, runtime_name: str) -> None: """初始化MCP宿主""" self._user_sub = user_sub self._task_id = task_id @@ -101,7 +102,7 @@ class MCPHost: task_id=self._task_id, flow_id=self._runtime_id, flow_name=self._runtime_name, - flow_status=StepStatus.RUNNING, + flow_status=FlowStatus.RUNNING, step_id=tool.id, step_name=tool.toolName, # description是规划的实际内容 diff --git a/apps/scheduler/mcp/select.py b/apps/scheduler/mcp/select.py index 2ba38e96..e0ae86e1 100644 --- a/apps/scheduler/mcp/select.py +++ b/apps/scheduler/mcp/select.py @@ -3,10 +3,14 @@ import logging +from sqlalchemy import select + +from apps.common.postgres import postgres from apps.llm.embedding import Embedding from apps.llm.function import FunctionLLM from apps.llm.reasoning import ReasoningLLM from apps.models.mcp import MCPTools +from apps.models.vectors import MCPToolVector from apps.schemas.mcp import MCPSelectResult from apps.services.mcp_service import MCPServiceManager @@ -21,14 +25,6 @@ class MCPSelector: self.input_tokens = 0 self.output_tokens = 0 - @staticmethod - def _assemble_sql(mcp_list: list[str]) -> str: - """组装SQL""" - sql = "(" - for mcp_id in mcp_list: - sql += f"'{mcp_id}', " - return sql.rstrip(", ") + ")" - async def _call_reasoning(self, prompt: str) -> str: """调用大模型进行推理""" @@ -69,19 +65,19 @@ class MCPSelector: @staticmethod async def select_top_tool(query: str, mcp_list: list[str], top_n: int = 10) -> list[MCPTools]: """选择最合适的工具""" - tool_vector = await LanceDB().get_table("mcp_tool") query_embedding = await Embedding.get_embedding([query]) - tool_vecs = await (await tool_vector.search( - query=query_embedding, - vector_column_name="embedding", - )).where(f"mcp_id IN {MCPSelector._assemble_sql(mcp_list)}").limit(top_n).to_list() + async with postgres.session() as session: + tool_vecs = await session.scalars( + select(MCPToolVector).where(MCPToolVector.mcpId.in_(mcp_list)) + .order_by(MCPToolVector.embedding.cosine_distance(query_embedding)).limit(top_n), + ) # 拿到工具 llm_tool_list = [] for tool_vec in tool_vecs: - logger.info("[MCPHelper] 查询MCP Tool名称和描述: %s", tool_vec["mcp_id"]) - tool_data = await MCPServiceManager.get_mcp_tools(tool_vec["mcp_id"]) + logger.info("[MCPHelper] 查询MCP Tool名称和描述: %s", tool_vec.mcpId) + tool_data = await MCPServiceManager.get_mcp_tools(tool_vec.mcpId) llm_tool_list.extend(tool_data) return llm_tool_list diff --git a/apps/scheduler/mcp_agent/plan.py b/apps/scheduler/mcp_agent/plan.py index 474a7010..f2d5665a 100644 --- a/apps/scheduler/mcp_agent/plan.py +++ b/apps/scheduler/mcp_agent/plan.py @@ -31,6 +31,7 @@ _env = SandboxedEnvironment( class MCPPlanner: """MCP 用户目标拆解与规划""" + @staticmethod async def get_resoning_result(prompt: str, resoning_llm: ReasoningLLM = ReasoningLLM()) -> str: """获取推理结果""" @@ -66,11 +67,12 @@ class MCPPlanner: @staticmethod async def evaluate_goal( + goal: str, tool_list: list[MCPTool], resoning_llm: ReasoningLLM = ReasoningLLM()) -> GoalEvaluationResult: """评估用户目标的可行性""" # 获取推理结果 - result = await MCPPlanner._get_reasoning_evaluation(tool_list, resoning_llm) + result = await MCPPlanner._get_reasoning_evaluation(goal, tool_list, resoning_llm) # 解析为结构化数据 evaluation = await MCPPlanner._parse_evaluation_result(result) diff --git a/apps/scheduler/pool/loader/app.py b/apps/scheduler/pool/loader/app.py index 30503a0c..16cc858d 100644 --- a/apps/scheduler/pool/loader/app.py +++ b/apps/scheduler/pool/loader/app.py @@ -28,7 +28,8 @@ BASE_PATH = Path(config.deploy.data_dir) / "semantics" / "app" class AppLoader: """应用加载器""" - async def load(self, app_id: uuid.UUID, hashes: dict[str, str]) -> None: # noqa: C901 + @staticmethod + async def load(app_id: uuid.UUID, hashes: dict[str, str]) -> None: # noqa: C901 """ 从文件系统中加载应用 @@ -85,10 +86,11 @@ class AppLoader: err = "[AppLoader] Agent应用元数据验证失败" logger.exception(err) raise RuntimeError(err) from e - await self._update_db(metadata) + await AppLoader._update_db(metadata) - async def save(self, metadata: AppMetadata | AgentAppMetadata, app_id: uuid.UUID) -> None: + @staticmethod + async def save(metadata: AppMetadata | AgentAppMetadata, app_id: uuid.UUID) -> None: """ 保存应用 @@ -104,7 +106,7 @@ class AppLoader: # 重新载入 file_checker = FileChecker() await file_checker.diff_one(app_path) - await self.load(app_id, file_checker.hashes[f"app/{app_id}"]) + await AppLoader.load(app_id, file_checker.hashes[f"app/{app_id}"]) @staticmethod diff --git a/apps/scheduler/pool/pool.py b/apps/scheduler/pool/pool.py index 01b6ab07..0504c322 100644 --- a/apps/scheduler/pool/pool.py +++ b/apps/scheduler/pool/pool.py @@ -108,19 +108,18 @@ class Pool: # 加载App logger.info("[Pool] 载入App") changed_app, deleted_app = await checker.diff(MetadataType.APP) - app_loader = AppLoader() # 批量删除App for app in changed_app: - await app_loader.delete(app, is_reload=True) + await AppLoader.delete(app, is_reload=True) for app in deleted_app: - await app_loader.delete(app) + await AppLoader.delete(app) # 批量加载App for app in changed_app: hash_key = Path("app/" + str(app)).as_posix() if hash_key in checker.hashes: - await app_loader.load(app, checker.hashes[hash_key]) + await AppLoader.load(app, checker.hashes[hash_key]) # 载入MCP logger.info("[Pool] 载入MCP") diff --git a/apps/scheduler/scheduler/scheduler.py b/apps/scheduler/scheduler/scheduler.py index 0c58a410..1dd9b08c 100644 --- a/apps/scheduler/scheduler/scheduler.py +++ b/apps/scheduler/scheduler/scheduler.py @@ -207,9 +207,19 @@ class Scheduler: if not app_metadata: logger.error("[Scheduler] 未找到Agent应用") return - llm = await LLMManager.get_llm_by_id( - self.task.ids.user_sub, app_metadata.llm_id, - ) + if app_metadata.llm_id == "empty": + llm = LLM( + _id="empty", + user_sub=self.task.ids.user_sub, + openai_base_url=Config().get_config().llm.endpoint, + openai_api_key=Config().get_config().llm.key, + model_name=Config().get_config().llm.model, + max_tokens=Config().get_config().llm.max_tokens, + ) + else: + llm = await LLMManager.get_llm_by_id( + self.task.ids.user_sub, app_metadata.llm_id, + ) if not llm: logger.error("[Scheduler] 获取大模型失败") await self.queue.close() diff --git a/apps/schemas/agent.py b/apps/schemas/agent.py index 2c6c5f23..1ffc56ca 100644 --- a/apps/schemas/agent.py +++ b/apps/schemas/agent.py @@ -17,6 +17,6 @@ class AgentAppMetadata(MetadataBase): app_type: AppType = Field(default=AppType.AGENT, description="应用类型", frozen=True) published: bool = Field(description="是否发布", default=False) history_len: int = Field(description="对话轮次", default=3, le=10) - mcp_service: list[str] = Field(default=[], alias="mcpService", description="MCP服务id列表") + mcp_service: list[str] = Field(default=[], description="MCP服务id列表") permission: Permission | None = Field(description="应用权限配置", default=None) version: str = Field(description="元数据版本") diff --git a/apps/schemas/response_data.py b/apps/schemas/response_data.py index 3373fda3..4d74a3d5 100644 --- a/apps/schemas/response_data.py +++ b/apps/schemas/response_data.py @@ -629,7 +629,7 @@ class OperateAndBindType(BaseModel): """操作和绑定类型数据结构""" operate: NumberOperate | StringOperate | ListOperate | BoolOperate | DictOperate = Field(description="操作类型") - bind_type: Type = Field(description="绑定类型") + bind_type: Type | None = Field(description="绑定类型") class GetOperaRsp(ResponseData): diff --git a/apps/schemas/scheduler.py b/apps/schemas/scheduler.py index 2eb4cb84..476ae0c6 100644 --- a/apps/schemas/scheduler.py +++ b/apps/schemas/scheduler.py @@ -1,6 +1,7 @@ # Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """插件、工作流、步骤相关数据结构定义""" +import uuid from typing import Any from pydantic import BaseModel, Field @@ -19,10 +20,10 @@ class CallInfo(BaseModel): class CallIds(BaseModel): """Call的ID,来自于Task""" - task_id: str = Field(description="任务ID") - flow_id: str = Field(description="Flow ID") + task_id: uuid.UUID = Field(description="任务ID") + flow_id: uuid.UUID = Field(description="Flow ID") session_id: str = Field(description="当前用户的Session ID") - app_id: str = Field(description="当前应用的ID") + app_id: uuid.UUID = Field(description="当前应用的ID") user_sub: str = Field(description="当前用户的用户ID") diff --git a/deploy/chart/euler_copilot/configs/deepinsight/.env b/deploy/chart/euler_copilot/configs/deepinsight/.env new file mode 100644 index 00000000..c51f1cc1 --- /dev/null +++ b/deploy/chart/euler_copilot/configs/deepinsight/.env @@ -0,0 +1,40 @@ +# .env.example +# 大模型配置 +# 支持列表见camel官方文档:https://docs.camel-ai.org/key_modules/models#direct-integrations +MODEL_PLATFORM=deepseek +# 支持列表见camel官方文档:https://docs.camel-ai.org/key_modules/models#direct-integrations +MODEL_TYPE=deepseek-chat +MODEL_API_KEY=sk-882ee9907bdf4bf4afee6994dea5697b +DEEPSEEK_API_KEY=sk-882ee9907bdf4bf4afee6994dea5697b +# 数据库配置,默认使用sqlite,支持postgresql和sqlite +DB_TYPE=sqlite + +# opengauss +DATABASE_TYPE=opengauss +DATABASE_HOST=opengauss-db.{{ .Release.Namespace }}.svc.cluster.local +DATABASE_PORT=5432 +DATABASE_USER=postgres +DATABASE_PASSWORD=${gauss-password} +DATABASE_DB=postgres + +# MongoDB +MONGODB_USER=euler_copilot +MONGODB_PASSWORD=${mongo-password} +MONGODB_HOST=mongo-db.{{ .Release.Namespace }}.svc.cluster.local +MONGODB_PORT=27017 +MONGODB_DATABASE=euler_copilot + +# 是否需要认证鉴权: deepInsight集成到openeuler intelligence依赖认证健全,如果单用户部署则不需要,默认用户为admin +REQUIRE_AUTHENTICATION=false + +# 默认监听地址和端口 +HOST=0.0.0.0 +PORT=9380 +API_PREFIX=/deepinsight + +# 日志配置 +LOG_DIR= +LOG_FILENAME= +LOG_LEVEL= +LOG_MAX_BYTES= +LOG_BACKUP_COUNT= diff --git a/deploy/chart/euler_copilot/configs/rag/.env b/deploy/chart/euler_copilot/configs/rag/.env index 6457ee1d..cc06decf 100644 --- a/deploy/chart/euler_copilot/configs/rag/.env +++ b/deploy/chart/euler_copilot/configs/rag/.env @@ -45,7 +45,7 @@ HALF_KEY3=${halfKey3} #LLM config MODEL_NAME={{ .Values.models.answer.name }} -OPENAI_API_BASE={{ .Values.models.answer.endpoint }}/v1 +OPENAI_API_BASE={{ .Values.models.answer.endpoint }} OPENAI_API_KEY={{ default "" .Values.models.answer.key }} MAX_TOKENS={{ default 2048 .Values.models.answer.maxTokens }} diff --git a/deploy/chart/euler_copilot/templates/deepinsight-web/deepinsight-web-config.yaml b/deploy/chart/euler_copilot/templates/deepinsight-web/deepinsight-web-config.yaml new file mode 100644 index 00000000..5564366b --- /dev/null +++ b/deploy/chart/euler_copilot/templates/deepinsight-web/deepinsight-web-config.yaml @@ -0,0 +1,10 @@ +{{- if .Values.euler_copilot.deepinsight_web.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: deepinsight-web-config + namespace: {{ .Release.Namespace }} +data: + .env: |- + DEEPINSIGHT_BACEND_URL=http://deepinsight-service.{{ .Release.Namespace }}.svc.cluster.local:9380 +{{- end -}} \ No newline at end of file diff --git a/deploy/chart/euler_copilot/templates/deepinsight-web/deepinsight-web.yaml b/deploy/chart/euler_copilot/templates/deepinsight-web/deepinsight-web.yaml new file mode 100644 index 00000000..d8393a0d --- /dev/null +++ b/deploy/chart/euler_copilot/templates/deepinsight-web/deepinsight-web.yaml @@ -0,0 +1,80 @@ +{{- if .Values.euler_copilot.deepinsight_web.enabled -}} +--- +apiVersion: v1 +kind: Service +metadata: + name: deepinsight-web-service + namespace: {{ .Release.Namespace }} +spec: + type: {{ default "ClusterIP" .Values.euler_copilot.deepinsight_web.service.type }} + selector: + app: deepinsight-web + ports: + - port: 9222 + targetPort: 9222 + nodePort: {{ default nil .Values.euler_copilot.deepinsight_web.service.nodePort }} + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deepinsight-web-deploy + namespace: {{ .Release.Namespace }} + labels: + app: deepinsight-web +spec: + replicas: {{ default 1 .Values.globals.replicaCount }} + selector: + matchLabels: + app: deepinsight-web + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/deepinsight-web/deepinsight-web-config.yaml") . | sha256sum }} + labels: + app: deepinsight-web + spec: + automountServiceAccountToken: false + containers: + - name: deepinsight-web + image: {{ .Values.euler_copilot.deepinsight_web.image | default (printf "%s/neocopilot/deepinsight-web:0.9.6-%s" (.Values.globals.imageRegistry | default "hub.oepkgs.net") (.Values.globals.arch | default "x86")) }} + imagePullPolicy: {{ default "IfNotPresent" .Values.globals.imagePullPolicy }} + ports: + - containerPort: 9222 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: 9222 + scheme: HTTP + failureThreshold: 5 + initialDelaySeconds: 60 + periodSeconds: 90 + env: + - name: TZ + value: "Asia/Shanghai" + volumeMounts: + - mountPath: /config + name: deepinsight-web-config-volume + - mountPath: /var/lib/nginx/tmp + name: deepinsight-web-tmp + - mountPath: /opt/.env + name: deepinsight-web-env-volume + subPath: .env + resources: + requests: + cpu: 0.05 + memory: 64Mi + limits: + {{ toYaml .Values.euler_copilot.deepinsight_web.resourceLimits | nindent 14 }} + volumes: + - name: deepinsight-web-config-volume + emptyDir: + medium: Memory + - name: deepinsight-web-env-volume + configMap: + name: deepinsight-web-config + - name: deepinsight-web-tmp + emptyDir: + medium: Memory +{{- end -}} \ No newline at end of file diff --git a/deploy/chart/euler_copilot/templates/deepinsight/deepinsight-config.yaml b/deploy/chart/euler_copilot/templates/deepinsight/deepinsight-config.yaml new file mode 100644 index 00000000..ff28ba9e --- /dev/null +++ b/deploy/chart/euler_copilot/templates/deepinsight/deepinsight-config.yaml @@ -0,0 +1,21 @@ +{{- if .Values.euler_copilot.deepinsight.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: deepinsight-config + namespace: {{ .Release.Namespace }} +data: + .env: |- +{{ tpl (.Files.Get "configs/deepinsight/.env") . | indent 4 }} + copy-config.yaml: |- + copy: + - from: /config/.env + to: /config-rw/.env + 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/deepinsight/deepinsight.yaml b/deploy/chart/euler_copilot/templates/deepinsight/deepinsight.yaml new file mode 100644 index 00000000..8f987af2 --- /dev/null +++ b/deploy/chart/euler_copilot/templates/deepinsight/deepinsight.yaml @@ -0,0 +1,102 @@ +{{- if .Values.euler_copilot.deepinsight.enabled -}} +--- +apiVersion: v1 +kind: Service +metadata: + name: deepinsight-service + namespace: {{ .Release.Namespace }} +spec: + type: {{ default "ClusterIP" .Values.euler_copilot.deepinsight.service.type }} + selector: + app: deepinsight + ports: + - name: deepinsight + port: 9380 + targetPort: 9380 + nodePort: {{ default nil .Values.euler_copilot.deepinsight.service.nodePort }} + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deepinsight-deploy + namespace: {{ .Release.Namespace }} + labels: + app: deepinsight +spec: + replicas: {{ default 1 .Values.globals.replicaCount }} + selector: + matchLabels: + app: deepinsight + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/deepinsight/deepinsight-config.yaml") . | sha256sum }} + labels: + app: deepinsight + spec: + automountServiceAccountToken: false + containers: + - name: deepinsight + image: {{ .Values.euler_copilot.deepinsight.image | default (printf "%s/neocopilot/deepinsight_backend:0.9.6-%s" (.Values.globals.imageRegistry | default "hub.oepkgs.net") (.Values.globals.arch | default "x86")) }} + imagePullPolicy: {{ default "IfNotPresent" .Values.globals.imagePullPolicy }} + ports: + - containerPort: 9380 + protocol: TCP + # livenessProbe: + # httpGet: + # path: /health_check + # port: 9380 + # scheme: HTTP + # failureThreshold: 5 + # initialDelaySeconds: 60 + # periodSeconds: 90 + env: + - name: TZ + value: "Asia/Shanghai" + volumeMounts: + - mountPath: /app/backend/config + name: deepinsight-shared + resources: + requests: + cpu: 0.25 + memory: 512Mi + limits: + {{ toYaml .Values.euler_copilot.deepinsight.resourceLimits | nindent 14 }} + initContainers: + - name: deepinsight-copy-secret + image: {{ .Values.euler_copilot.secretInject.image | default (printf "%s/neocopilot/secret_inject:dev-%s" (.Values.globals.imageRegistry | default "hub.oepkgs.net") (.Values.globals.arch | default "x86")) }} + imagePullPolicy: {{ default "IfNotPresent" .Values.globals.imagePullPolicy }} + command: + - python3 + - ./main.py + - --config + - config.yaml + - --copy + volumeMounts: + - mountPath: /config/.env + name: deepinsight-config-vl + subPath: .env + - mountPath: /app/config.yaml + name: deepinsight-config-vl + subPath: copy-config.yaml + - mountPath: /config-rw + name: deepinsight-shared + - mountPath: /db-secrets + name: database-secret + - mountPath: /system-secrets + name: system-secret + volumes: + - name: deepinsight-config-vl + configMap: + name: deepinsight-config + - name: database-secret + secret: + secretName: euler-copilot-database + - name: system-secret + secret: + secretName: euler-copilot-system + - name: deepinsight-shared + emptyDir: + medium: Memory +{{- 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 1793023e..77c79a85 100644 --- a/deploy/chart/euler_copilot/templates/web/web-config.yaml +++ b/deploy/chart/euler_copilot/templates/web/web-config.yaml @@ -8,4 +8,5 @@ data: .env: |- RAG_WEB_URL=http://rag-web-service.{{ .Release.Namespace }}.svc.cluster.local:9888 FRAMEWORK_URL=http://framework-service.{{ .Release.Namespace }}.svc.cluster.local:8002 + DEEPINSIGHT_WEB_URL=http://deepinsight-web-service.{{ .Release.Namespace }}.svc.cluster.local:9222 {{- end -}} \ No newline at end of file diff --git a/deploy/scripts/8-install-EulerCopilot/install_eulercopilot.sh b/deploy/scripts/8-install-EulerCopilot/install_eulercopilot.sh index 412db706..b7961ebe 100755 --- a/deploy/scripts/8-install-EulerCopilot/install_eulercopilot.sh +++ b/deploy/scripts/8-install-EulerCopilot/install_eulercopilot.sh @@ -15,6 +15,8 @@ PLUGINS_DIR="/var/lib/eulercopilot" # 全局变量声明 client_id="" client_secret="" +eulercopilot_address="" +authhub_address="" SCRIPT_PATH="$( cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 @@ -26,6 +28,53 @@ DEPLOY_DIR="$( dirname "$(dirname "$(dirname "$canonical_path")")" )" +# 显示帮助信息 +show_help() { + echo -e "${GREEN}用法: $0 [选项]" + echo -e "选项:" + echo -e " --help 显示此帮助信息" + echo -e " --eulercopilot_address 指定EulerCopilot前端访问URL" + echo -e " --authhub_address 指定Authhub前端访问URL" + echo -e "" + echo -e "示例:" + echo -e " $0 --eulercopilot_address http://myhost:30080 --authhub_address http://myhost:30081${NC}" + exit 0 +} + +# 解析命令行参数 +parse_arguments() { + while [[ $# -gt 0 ]]; do + case "$1" in + --help) + show_help + ;; + --eulercopilot_address) + if [ -n "$2" ]; then + eulercopilot_address="$2" + shift + else + echo -e "${RED}错误: --eulercopilot_address 需要提供一个值${NC}" >&2 + exit 1 + fi + ;; + --authhub_address) + if [ -n "$2" ]; then + authhub_address="$2" + shift + else + echo -e "${RED}错误: --authhub_address 需要提供一个值${NC}" >&2 + exit 1 + fi + ;; + *) + echo -e "${RED}未知选项: $1${NC}" >&2 + show_help + exit 1 + ;; + esac + shift + done +} # 安装成功信息显示函数 @@ -106,6 +155,14 @@ get_network_ip() { } get_address_input() { + # 如果命令行参数已经提供了地址,则直接使用,不进行交互式输入 + if [ -n "$eulercopilot_address" ] && [ -n "$authhub_address" ]; then + echo -e "${GREEN}使用命令行参数配置:" + echo "EulerCopilot地址: $eulercopilot_address" + echo "Authhub地址: $authhub_address" + return + fi + # 从环境变量读取或使用默认值 eulercopilot_address=${EULERCOPILOT_ADDRESS:-"http://127.0.0.1:30080"} authhub_address=${AUTHHUB_ADDRESS:-"http://127.0.0.1:30081"} @@ -126,8 +183,6 @@ get_address_input() { echo "Authhub地址: $authhub_address" } - - get_client_info_auto() { # 获取用户输入地址 get_address_input @@ -376,6 +431,8 @@ check_pods_status() { # 修改main函数 main() { + parse_arguments "$@" + pre_install_checks local arch host @@ -420,4 +477,4 @@ main() { fi } -main "$@" +main "$@" \ No newline at end of file -- Gitee