diff --git a/design/call/api.md b/design/call/api.md index 23afe330a2996195d8627747c11c942624150249..aef4a5f450c10b6eb4a587f83ed8a4bfd093fef9 100644 --- a/design/call/api.md +++ b/design/call/api.md @@ -578,78 +578,6 @@ graph TB style Optional fill:#00acc1,color:#fff ``` -## 使用示例 - -### 基本 GET 请求 - -```python -api = await API.instance( - executor=executor, - node=node, - url="https://api.example.com/users", - method=HTTPMethod.GET, - query={"page": 1, "limit": 10} -) - -async for chunk in api.exec(executor, input_data): - print(chunk) -``` - -### POST 请求(JSON) - -```python -api = await API.instance( - executor=executor, - node=node, - url="https://api.example.com/users", - method=HTTPMethod.POST, - content_type=ContentType.JSON, - body={"name": "John", "email": "john@example.com"} -) - -async for chunk in api.exec(executor, input_data): - print(chunk) -``` - -### 带认证的请求 - -```python -# Service Metadata中配置了认证信息 -api = await API.instance( - executor=executor, - node=node_with_service_id, # node中包含serviceId - url="https://api.example.com/protected", - method=HTTPMethod.GET -) - -async for chunk in api.exec(executor, input_data): - print(chunk) -``` - -## 依赖关系 - -```mermaid -graph TB - API[API模块] - - API --> CoreCall[scheduler.call.core.CoreCall] - API --> Schema[api.schema: APIInput/APIOutput] - API --> OIDC[common.oidc.oidc_provider] - API --> Service[services.service.ServiceCenterManager] - API --> Token[services.token.TokenManager] - API --> Enums[schemas.enum_var] - API --> Schemas[schemas.scheduler] - API --> Models[models: LanguageType/NodeInfo] - API --> HTTPX[httpx: HTTP客户端库] - - style API fill:#1976d2,color:#fff - style CoreCall fill:#42a5f5 - style Schema fill:#42a5f5 - style OIDC fill:#42a5f5 - style Service fill:#42a5f5 - style Token fill:#42a5f5 -``` - ## 生命周期 ```mermaid @@ -682,73 +610,3 @@ sequenceDiagram Clean->>Clean: 关闭HTTP客户端 Clean->>Clean: 释放资源 ``` - -## 最佳实践 - -### 1. 超时设置 - -```mermaid -graph LR - Timeout[超时设置] - Timeout --> Short[短超时
30-60秒] - Timeout --> Medium[中超时
60-180秒] - Timeout --> Long[长超时
180-300秒] - - Short --> Fast[快速查询API] - Medium --> Normal[常规业务API] - Long --> Heavy[重计算/大数据API] - - style Timeout fill:#ff9800,color:#fff -``` - -### 2. 错误处理策略 - -```mermaid -graph TB - Strategy[错误处理策略] - - Strategy --> Validate[参数验证] - Strategy --> Retry[重试机制] - Strategy --> Fallback[降级方案] - Strategy --> Logging[日志记录] - - Validate --> V1[初始化时验证必填参数] - Validate --> V2[执行前验证数据格式] - - Retry --> R1[网络异常自动重试] - Retry --> R2[超时适当延长] - - Fallback --> F1[API失败返回默认值] - Fallback --> F2[使用缓存数据] - - Logging --> L1[记录请求参数] - Logging --> L2[记录响应数据] - Logging --> L3[记录错误详情] - - style Strategy fill:#009688,color:#fff -``` - -### 3. 安全考虑 - -```mermaid -graph TB - Security[安全考虑] - - Security --> Auth[认证安全] - Security --> Data[数据安全] - Security --> Network[网络安全] - - Auth --> A1[使用OIDC标准认证] - Auth --> A2[Token安全存储] - Auth --> A3[定期刷新Token] - - Data --> D1[敏感数据加密] - Data --> D2[输入数据验证] - Data --> D3[输出数据过滤] - - Network --> N1[使用HTTPS] - Network --> N2[证书验证] - Network --> N3[超时控制] - - style Security fill:#e91e63,color:#fff -``` diff --git a/design/call/graph.md b/design/call/graph.md new file mode 100644 index 0000000000000000000000000000000000000000..c634f7821517d30b7535f823390edafe073af0ae --- /dev/null +++ b/design/call/graph.md @@ -0,0 +1,870 @@ +# Graph Call 模块设计文档 + +## 模块概述 + +Graph Call 模块是 Scheduler 框架中的图表渲染工具,用于将 SQL 查询得到的结构化数据转换为可视化图表。该模块基于 ECharts 图表库,通过大语言模型智能选择图表类型和样式,自动生成符合用户需求的图表配置。 + +## 核心组件 + +### 1. Graph 类 (graph.py) + +主要的图表生成工具类,继承自 `CoreCall`,实现了完整的图表渲染流程。 + +### 2. Schema 类 (schema.py) + +定义了图表工具的输入输出数据结构: + +- `RenderInput`: 图表渲染的输入参数 +- `RenderOutput`: 图表渲染的输出结果 +- `RenderFormat`: ECharts 图表的完整配置格式 +- `RenderStyleResult`: 大语言模型选择的图表样式结果 +- `RenderAxis`: ECharts 图表的轴配置 + +### 3. 提示词模板 (prompt.py) + +包含中英文双语提示词模板 `GENERATE_STYLE_PROMPT`,用于引导大语言模型根据用户问题选择合适的图表类型和样式。 + +### 4. 图表配置模板 (option.json) + +预定义的 ECharts 基础配置模板,包含 tooltip、legend、dataset、xAxis、yAxis 和 series 等核心配置项。 + +## 类结构图 + +```mermaid +classDiagram + class CoreCall { + <> + +name: str + +description: str + +node: NodeInfo + +enable_filling: bool + +to_user: bool + +info(language) CallInfo + +instance(executor, node) Self + +exec(executor, input_data) AsyncGenerator + #_init(call_vars) DataBase + #_exec(input_data) AsyncGenerator + #_llm(messages, streaming) AsyncGenerator + } + + class DataBase { + <> + +model_json_schema(override, kwargs) dict + } + + class Graph { + +dataset_key: str + +info(language) CallInfo + #_init(call_vars) RenderInput + #_exec(input_data) AsyncGenerator + -_separate_key_value(data) list + -_parse_options(column_num, style) None + -_env: SandboxedEnvironment + -_option_template: dict + } + + class RenderInput { + +question: str + +data: list~dict~ + } + + class RenderOutput { + +output: RenderFormat + } + + class RenderFormat { + +tooltip: dict + +legend: dict + +dataset: dict + +xAxis: RenderAxis + +yAxis: RenderAxis + +series: list~dict~ + } + + class RenderAxis { + +type: str + +axisTick: dict + } + + class RenderStyleResult { + +chart_type: Literal + +additional_style: Literal + +scale_type: Literal + } + + CoreCall <|-- Graph + DataBase <|-- RenderInput + DataBase <|-- RenderOutput + RenderOutput *-- RenderFormat + RenderFormat *-- RenderAxis + Graph ..> RenderInput : uses + Graph ..> RenderOutput : produces + Graph ..> RenderStyleResult : uses +``` + +## 执行流程图 + +### 主流程 + +```mermaid +flowchart TD + Start([开始]) --> Instance[实例化Graph对象] + Instance --> Init[_init 初始化] + Init --> LoadTemplate[加载option.json模板] + LoadTemplate --> CheckTemplate{模板加载成功?} + CheckTemplate -->|否| Error1[抛出CallError] + CheckTemplate -->|是| GetDatasetKey[确定数据源Key] + + GetDatasetKey --> CheckKey{dataset_key是否存在?} + CheckKey -->|否| UseLastStep[使用上一步的dataset] + CheckKey -->|是| ExtractData[提取历史变量数据] + UseLastStep --> ExtractData + + ExtractData --> CreateInput[创建RenderInput] + CreateInput --> SetInput[设置输入数据] + SetInput --> Exec[_exec 执行] + + Exec --> ValidateData[验证数据格式] + ValidateData --> CheckFormat{数据格式正确?} + CheckFormat -->|否| Error2[抛出CallError: 数据格式错误] + CheckFormat -->|是| CheckColumns[检查列数] + + CheckColumns --> CheckColumnNum{列数是否为0?} + CheckColumnNum -->|是| SeparateKV[分离键值对] + CheckColumnNum -->|否| SetProcessed[使用原数据] + SeparateKV --> SetProcessed + + SetProcessed --> FillDataset[填充dataset.source] + FillDataset --> RenderPrompt[渲染样式选择提示词] + RenderPrompt --> CallLLM[调用LLM选择样式] + + CallLLM --> CheckLLM{LLM调用成功?} + CheckLLM -->|否| Error3[抛出CallError: 图表生成失败] + CheckLLM -->|是| ParseResult[解析样式结果] + + ParseResult --> ParseOptions[应用样式到配置] + ParseOptions --> CreateOutput[创建RenderOutput] + CreateOutput --> YieldChunk[生成输出Chunk] + YieldChunk --> End([结束]) + + Error1 --> End + Error2 --> End + Error3 --> End + + style Start fill:#4caf50 + style End fill:#f44336 + style Error1 fill:#ff9800 + style Error2 fill:#ff9800 + style Error3 fill:#ff9800 +``` + +### 样式解析流程 + +```mermaid +flowchart TD + Start([开始样式解析]) --> ParseStyle[获取RenderStyleResult] + ParseStyle --> InitTemplate[初始化series_template] + InitTemplate --> CheckType{检查图表类型} + + CheckType -->|line| SetLine[设置type=line] + CheckType -->|scatter| SetScatter[设置type=scatter] + CheckType -->|pie| SetPie[设置type=pie] + CheckType -->|其他| SetBar[设置type=bar] + + SetPie --> CheckPieStyle{检查饼图样式} + CheckPieStyle -->|ring| SetRing[设置radius为环形] + CheckPieStyle -->|normal| SkipRing[跳过] + SetRing --> SetPieColumn[设置列数为1] + SkipRing --> SetPieColumn + + SetBar --> CheckBarStyle{检查柱状图样式} + CheckBarStyle -->|stacked| SetStacked[设置stack=total] + CheckBarStyle -->|normal| SkipStacked[跳过] + + SetLine --> CheckScale + SetScatter --> CheckScale + SetPieColumn --> CheckScale + SetStacked --> CheckScale + SkipStacked --> CheckScale + + CheckScale{检查坐标比例} -->|log| SetLog[设置yAxis.type=log] + CheckScale -->|linear| SkipLog[跳过] + + SetLog --> FillSeries + SkipLog --> FillSeries + + FillSeries[根据列数填充series数组] --> End([结束]) + + style Start fill:#4caf50 + style End fill:#2196f3 +``` + +## 时序图 + +### 完整调用时序 + +```mermaid +sequenceDiagram + participant Executor as StepExecutor + participant Graph as Graph实例 + participant Template as Jinja2Template + participant LLM as 大语言模型 + participant File as option.json + + Executor->>+Graph: instance(executor, node) + Graph->>Graph: __init__(参数) + Graph->>+Graph: _set_input(executor) + Graph->>+Graph: _init(call_vars) + + Graph->>Graph: 创建SandboxedEnvironment + Graph->>+File: 读取option.json + File-->>-Graph: 返回模板内容 + + alt 模板加载失败 + Graph->>Graph: 抛出CallError + end + + Graph->>Graph: 解析JSON模板 + Graph->>Graph: 保存到_option_template + + alt dataset_key为空 + Graph->>Graph: 使用上一步ID + Graph->>Graph: 拼接路径: step_id/dataset + end + + Graph->>Graph: _extract_history_variables(key, history) + Graph->>Graph: 创建RenderInput对象 + Graph-->>-Graph: 返回RenderInput + Graph->>Graph: 保存input数据 + Graph-->>-Graph: 完成初始化 + Graph-->>-Executor: 返回Graph实例 + + Executor->>+Graph: exec(executor, input_data) + Graph->>+Graph: _exec(input_data) + Graph->>Graph: 验证RenderInput + + alt 数据格式错误 + Graph->>Graph: 抛出CallError + end + + Graph->>Graph: 计算列数 + + alt 列数为0 + Graph->>+Graph: _separate_key_value(data) + Graph-->>-Graph: 返回分离后数据 + Graph->>Graph: 设置列数为1 + end + + Graph->>Graph: 填充_option_template["dataset"]["source"] + + Graph->>+Template: 选择语言模板 + Graph->>Template: render(question) + Template-->>-Graph: 返回提示词 + + Graph->>+LLM: 调用LLM流式生成 + loop 流式接收 + LLM-->>Graph: 返回chunk + Graph->>Graph: 拼接结果 + end + LLM-->>-Graph: 完成生成 + + Graph->>Graph: model_validate_json(result) + Graph->>Graph: 解析为RenderStyleResult + + Graph->>+Graph: _parse_options(column_num, style) + + alt chart_type为line + Graph->>Graph: 设置series_template.type=line + else chart_type为scatter + Graph->>Graph: 设置series_template.type=scatter + else chart_type为pie + Graph->>Graph: 设置series_template.type=pie + Graph->>Graph: 设置column_num=1 + alt additional_style为ring + Graph->>Graph: 设置radius=[40%, 70%] + end + else 其他 + Graph->>Graph: 设置series_template.type=bar + alt additional_style为stacked + Graph->>Graph: 设置stack=total + end + end + + alt scale_type为log + Graph->>Graph: 设置yAxis.type=log + end + + loop 遍历列数 + Graph->>Graph: 添加series_template到series数组 + end + + Graph-->>-Graph: 完成样式配置 + + Graph->>Graph: 创建RenderFormat对象 + Graph->>Graph: 创建RenderOutput对象 + Graph->>Graph: 创建CallOutputChunk + Graph->>Executor: yield chunk + + Graph-->>-Graph: 完成执行 + Graph-->>-Executor: 执行完成 +``` + +## 数据结构 + +### 输入数据结构 + +#### RenderInput + +```json +{ + "question": "查询openEuler各版本的软件数量并绘制柱状图", + "data": [ + { + "openeuler_version": "openEuler-22.03-LTS-SP2", + "软件数量": 5847 + }, + { + "openeuler_version": "openEuler-22.03-LTS-SP3", + "软件数量": 6012 + }, + { + "openeuler_version": "openEuler-22.03-LTS-SP4", + "软件数量": 6123 + } + ] +} +``` + +**字段说明:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| question | string | 是 | 用户原始问题 | +| data | array | 是 | SQL查询结果数据,必须是字典列表格式 | + +### 输出数据结构 + +#### RenderOutput + +```json +{ + "output": { + "tooltip": {}, + "legend": {}, + "dataset": { + "source": [ + { + "openeuler_version": "openEuler-22.03-LTS-SP2", + "软件数量": 5847 + }, + { + "openeuler_version": "openEuler-22.03-LTS-SP3", + "软件数量": 6012 + }, + { + "openeuler_version": "openEuler-22.03-LTS-SP4", + "软件数量": 6123 + } + ] + }, + "xAxis": { + "type": "category", + "axisTick": { + "alignWithLabel": false + } + }, + "yAxis": { + "type": "value", + "axisTick": { + "alignWithLabel": false + } + }, + "series": [ + { + "type": "bar" + } + ] + } +} +``` + +**字段说明:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| output | object | ECharts完整配置对象 | +| output.tooltip | object | 提示框配置 | +| output.legend | object | 图例配置 | +| output.dataset | object | 数据集配置 | +| output.dataset.source | array | 数据源数组 | +| output.xAxis | object | X轴配置 | +| output.yAxis | object | Y轴配置 | +| output.series | array | 系列配置数组 | + +### 中间数据结构 + +#### RenderStyleResult + +LLM选择的图表样式结果: + +```json +{ + "chart_type": "bar", + "additional_style": "stacked", + "scale_type": "linear" +} +``` + +**字段说明:** + +| 字段 | 类型 | 可选值 | 说明 | +|------|------|--------|------| +| chart_type | string | bar, pie, line, scatter | 图表类型 | +| additional_style | string | normal, stacked, ring | 附加样式 | +| scale_type | string | linear, log | 坐标轴比例类型 | + +**样式组合规则:** + +- **柱状图 (bar)**: + - `normal`: 普通柱状图 + - `stacked`: 堆叠柱状图 +- **饼图 (pie)**: + - `normal`: 普通饼图 + - `ring`: 环形饼图 +- **折线图 (line)**: 无附加样式 +- **散点图 (scatter)**: 无附加样式 + +## 配置模板 + +### option.json 模板结构 + +```json +{ + "tooltip": {}, + "legend": {}, + "dataset": { + "source": [] + }, + "xAxis": { + "type": "category", + "axisTick": { + "alignWithLabel": false + } + }, + "yAxis": { + "type": "value", + "axisTick": { + "alignWithLabel": false + } + }, + "series": [] +} +``` + +**配置项说明:** + +| 配置项 | 类型 | 初始值 | 说明 | +|--------|------|--------|------| +| tooltip | object | {} | 提示框组件,空对象表示使用默认配置 | +| legend | object | {} | 图例组件,空对象表示使用默认配置 | +| dataset.source | array | [] | 数据源,运行时填充SQL查询结果 | +| xAxis.type | string | category | X轴类型,类目轴 | +| yAxis.type | string | value | Y轴类型,数值轴,可能被修改为log | +| series | array | [] | 系列列表,运行时根据列数和样式填充 | + +## 提示词模板 + +- **结构化指令**: 使用 XML 标签清晰分隔不同部分 +- **枚举类型说明**: 明确列出所有可用的图表类型和样式选项 +- **示例驱动**: 提供完整的问题-思考-答案示例 +- **思维链引导**: 使用"让我们一步步思考"引导模型推理 +- **格式约束**: 明确要求输出 JSON 格式 +- **多语言支持**: 提供中英文两种语言的独立模板 + +## 核心算法 + +### 1. 数据格式验证 + +系统对输入数据进行严格的格式验证: + +```mermaid +graph TD + A[输入数据] --> B{是否为列表?} + B -->|否| E[格式错误] + B -->|是| C{列表长度>0?} + C -->|否| E + C -->|是| D{第一项是字典?} + D -->|否| E + D -->|是| F[格式正确] + + E --> G[抛出CallError] + F --> H[继续处理] + + style A fill:#2196f3,color:#fff + style E fill:#f44336,color:#fff + style F fill:#4caf50,color:#fff + style G fill:#ff9800 + style H fill:#66bb6a +``` + +**验证规则:** + +- 数据必须是列表类型 +- 列表长度必须大于0 +- 列表的第一个元素必须是字典类型 + +**示例:** + +```python +# 正确格式 +[ + {"version": "22.03-LTS-SP2", "count": 100}, + {"version": "22.03-LTS-SP3", "count": 120} +] + +# 错误格式 +"some string" # 不是列表 +[] # 空列表 +[1, 2, 3] # 不是字典列表 +``` + +### 2. 键值对分离算法 + +当数据只有单列(列数为0)时,执行键值对分离: + +```mermaid +graph LR + A[原始数据] --> B[遍历每个字典] + B --> C[遍历字典的每个键值对] + C --> D[创建新字典] + D --> E["{'type': key, 'value': val}"] + E --> F[添加到结果列表] + F --> G[返回新列表] + + style A fill:#2196f3,color:#fff + style G fill:#4caf50,color:#fff +``` + +**转换示例:** + +输入: + +```json +[ + {"total_packages": 5847} +] +``` + +输出: + +```json +[ + { + "type": "total_packages", + "value": 5847 + } +] +``` + +**算法逻辑:** + +1. 遍历数据列表中的每个字典 +2. 对于每个字典,遍历其键值对 +3. 将每个键值对转换为 `{"type": key, "value": val}` 格式 +4. 将转换后的字典添加到结果列表 +5. 返回新的数据列表 + +### 3. 列数计算 + +列数决定了需要生成多少个 series 配置: + +```mermaid +graph TD + A[获取第一行数据] --> B[计算字典长度] + B --> C[减1得到列数] + C --> D{列数是否为0?} + D -->|是| E[执行键值对分离] + D -->|否| F[使用原列数] + E --> G[设置列数为1] + G --> H[填充series] + F --> H + + style A fill:#2196f3,color:#fff + style H fill:#4caf50,color:#fff +``` + +**计算规则:** + +- 列数 = 数据第一行字典的键数量 - 1 +- 减1是因为通常第一列是类目/标签,不计入数据系列 +- 如果列数为0,表示数据只有一个键值对,需要分离后列数变为1 + +**示例:** + +数据: + +```json +[ + {"version": "v1", "pkg_count": 100, "issue_count": 50} +] +``` + +- 字典长度: 3 +- 列数: 3 - 1 = 2 +- 需要生成2个 series 配置 + +### 4. 样式应用算法 + +根据 LLM 返回的样式结果配置图表: + +```mermaid +graph TD + A[RenderStyleResult] --> B[初始化series_template] + B --> C{chart_type} + + C -->|line| D[type=line] + C -->|scatter| E[type=scatter] + C -->|pie| F[type=pie] + C -->|其他| G[type=bar] + + F --> F1{additional_style} + F1 -->|ring| F2["radius=[40%, 70%]"] + F1 -->|normal| F3[无操作] + F2 --> F4[column_num=1] + F3 --> F4 + + G --> G1{additional_style} + G1 -->|stacked| G2[stack=total] + G1 -->|normal| G3[无操作] + + D --> H + E --> H + F4 --> H + G2 --> H + G3 --> H + + H{scale_type} -->|log| I[yAxis.type=log] + H -->|linear| J[无操作] + + I --> K[循环填充series] + J --> K + K --> L[完成] + + style A fill:#2196f3,color:#fff + style L fill:#4caf50,color:#fff +``` + +## 工作流程 + +### 初始化流程 + +```mermaid +graph TD + A[开始初始化] --> B[创建SandboxedEnvironment] + B --> C[构建option.json路径] + C --> D[异步读取文件] + D --> E{读取成功?} + E -->|否| F[抛出CallError] + E -->|是| G[解析JSON] + G --> H{解析成功?} + H -->|否| F + H -->|是| I[保存到_option_template] + I --> J{dataset_key为空?} + J -->|是| K[获取上一步ID] + J -->|否| L[使用指定key] + K --> M[拼接路径: step_id/dataset] + L --> N[提取历史变量] + M --> N + N --> O[创建RenderInput] + O --> P[返回输入对象] + + style A fill:#4caf50 + style P fill:#2196f3 + style F fill:#f44336 +``` + +### 执行流程 + +```mermaid +graph TD + A[开始执行] --> B[验证输入数据] + B --> C{数据格式正确?} + C -->|否| D[抛出格式错误] + C -->|是| E[计算列数] + E --> F{列数为0?} + F -->|是| G[分离键值对] + F -->|否| H[使用原数据] + G --> I[设置列数为1] + H --> J[填充dataset.source] + I --> J + J --> K[渲染提示词] + K --> L[调用LLM流式接口] + L --> M[拼接响应内容] + M --> N{调用成功?} + N -->|否| O[抛出生成失败错误] + N -->|是| P[解析JSON结果] + P --> Q[验证为RenderStyleResult] + Q --> R[应用样式配置] + R --> S[创建输出对象] + S --> T[生成Chunk] + T --> U[结束] + + style A fill:#4caf50 + style U fill:#2196f3 + style D fill:#f44336 + style O fill:#f44336 +``` + +## 关键特性 + +### 1. 图表类型支持 + +支持的图表类型如下: + +- 柱状图(bar) + - 普通柱状图(normal) + - 堆叠柱状图(stacked) +- 饼图(pie) + - 普通饼图(normal) + - 环形饼图(ring) +- 折线图(line) + - 折线图 +- 散点图(scatter) + - 散点图 + +### 2. 坐标轴比例支持 + +支持的坐标轴比例包括: + +- 线性比例(linear):适用于数据分布较为均匀的情况,配置项无需特殊指定。 +- 对数比例(log):适用于跨度较大、变化量级明显的数据类型,配置项需设置 `yAxis.type=log`。 + +### 3. 数据源自动识别 + +自动识别和选择数据源的方式如下: + +- 如果未指定 `dataset_key`,系统会自动使用上一步输出的 `dataset`。 +- 支持显式指定数据源路径,例如通过"step_id/key"格式指向历史某一步的数据。 +- 默认流程为: + 1. 检查 `dataset_key` 是否存在。 + 2. 存在时使用指定 key 的历史数据。 + 3. 不存在时,获取上一步的 step_id,并拼接出 `step_id/dataset`。 + 4. 从对应位置提取数据传递给图表。 + +### 4. 智能样式选择 + +图表样式的智能选择流程如下: + +1. 用户提出可视化需求(如类型、样式)。 +2. 系统通过 Jinja2 模板渲染用户需求与数据结构,生成提示词。 +3. 大语言模型根据提示词进行推理,返回结构化的样式选择结果(如图表类型、样式、坐标比例)。 +4. 系统根据返回的结果自动生成相应的 ECharts 图表配置。 + +样式选择主要包括: + +- 分析用户问题意图 +- 匹配合适的图表类型 +- 选择可能的附加样式(如 stacked、ring 等) +- 判断使用线性或对数比例 + +## 错误处理 + +### 错误场景 + +```mermaid +graph TB + Errors[错误场景] + + Errors --> E1[模板文件读取失败] + Errors --> E2[JSON解析失败] + Errors --> E3[数据格式错误] + Errors --> E4[LLM调用失败] + Errors --> E5[样式解析失败] + + E1 --> Action1[抛出CallError: 图表模板读取失败] + E2 --> Action2[抛出CallError: 图表模板读取失败] + E3 --> Action3[抛出CallError: 数据格式错误,无法生成图表] + E4 --> Action4[抛出CallError: 图表生成失败] + E5 --> Action5[抛出CallError: 图表生成失败] + + style Errors fill:#f44336,color:#fff + style E1 fill:#ef5350 + style E2 fill:#ef5350 + style E3 fill:#ef5350 + style E4 fill:#ef5350 + style E5 fill:#ef5350 +``` + +**错误信息示例:** + +```json +{ + "message": "数据格式错误,无法生成图表!", + "data": { + "data": "invalid data format" + } +} +``` + +## 配置参数 + +### Graph 类配置参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `dataset_key` | str | "" | 数据源路径,格式为"step_id/key",为空则使用上一步的dataset | +| `name` | str | 必填 | 工具名称 | +| `description` | str | 必填 | 工具描述 | +| `node` | NodeInfo | None | 节点信息 | +| `enable_filling` | bool | False | 是否需要自动参数填充 | +| `to_user` | bool | False | 是否将输出返回给用户 | + +### 多语言支持 + +```mermaid +graph LR + A[语言类型] --> B[中文] + A --> C[英文] + + B --> B1[名称: 图表] + B --> B2[描述: 将SQL查询出的数据转换为图表] + + C --> C1[名称: Graph] + C --> C2[描述: Convert the data queried by SQL into a chart.] + + style A fill:#00bcd4,color:#fff + style B fill:#0097a7,color:#fff + style C fill:#0097a7,color:#fff +``` + +## 状态图 + +```mermaid +stateDiagram-v2 + [*] --> 未初始化 + + 未初始化 --> 初始化中: instance() + 初始化中 --> 初始化失败: 模板读取失败 + 初始化中 --> 已初始化: 初始化成功 + + 已初始化 --> 执行中: exec() + 执行中 --> 验证数据: 接收输入 + 验证数据 --> 验证失败: 格式错误 + 验证数据 --> 数据处理: 格式正确 + + 数据处理 --> 键值分离: 列数为0 + 数据处理 --> 填充数据: 列数>0 + 键值分离 --> 填充数据: 完成分离 + + 填充数据 --> 样式选择: 调用LLM + 样式选择 --> 样式失败: LLM失败 + 样式选择 --> 应用样式: LLM成功 + + 应用样式 --> 生成输出: 配置完成 + 生成输出 --> 执行完成: 输出Chunk + + 初始化失败 --> [*] + 验证失败 --> [*] + 样式失败 --> [*] + 执行完成 --> [*] +``` diff --git a/design/call/slot.md b/design/call/slot.md index 164e19b024b9c28b1f297809e18bbc8c285911f2..768ca0c8c6bab4403dac5b12a59f46e57789c7c6 100644 --- a/design/call/slot.md +++ b/design/call/slot.md @@ -390,13 +390,3 @@ Slot工具包含以下错误处理机制: - JSON Schema格式错误检测 - 验证器初始化失败处理 - 字段验证失败时的错误提取 - -## 依赖关系 - -- `CoreCall`: 基础调用框架 -- `SlotProcessor`: 参数槽处理器,负责Schema验证和数据转换 -- `Jinja2`: 模板引擎,用于动态生成提示词 -- `Pydantic`: 数据验证和序列化 -- `FunctionLLM`: 大语言模型接口和JSON处理 -- `jsonschema`: JSON Schema验证库 -- `CallVars`: 调用变量和上下文信息 diff --git a/design/call/suggest.md b/design/call/suggest.md new file mode 100644 index 0000000000000000000000000000000000000000..940dc24a10159f4b862dad807b7baf563f7e92b1 --- /dev/null +++ b/design/call/suggest.md @@ -0,0 +1,626 @@ +# Suggest 模块设计文档 + +## 1. 模块概述 + +Suggest 模块是一个智能问题推荐系统,用于在对话场景中为用户推荐相关的后续问题。该模块基于用户的历史对话、用户画像和应用流程信息,通过大语言模型生成个性化的问题推荐。 + +### 1.1 核心功能 + +- 基于用户历史对话和用户画像生成个性化问题推荐 +- 支持为应用中的多个 Flow 分别生成推荐问题 +- 支持通过配置指定固定的推荐问题 +- 支持为特定 Flow 生成高亮标识 +- 智能去重避免重复推荐历史问题 + +### 1.2 模块组成 + +模块位于 `apps/scheduler/call/suggest/` 目录下,主要包含以下文件: + +- [suggest.py](../../apps/scheduler/call/suggest/suggest.py) - 问题推荐核心实现 +- [schema.py](../../apps/scheduler/call/suggest/schema.py) - 数据结构定义 +- [prompt.py](../../apps/scheduler/call/suggest/prompt.py) - 提示词模板定义 + +## 2. 数据结构设计 + +### 2.1 核心数据模型 + +```mermaid +classDiagram + class Suggestion { + +bool to_user + +list~SingleFlowSuggestionConfig~ configs + +int num + +UUID conversation_id + +dict~str,dict~ _avaliable_flows + +list~str~ _history_questions + +str _app_id + +str _flow_id + +SandboxedEnvironment _env + +info() CallInfo + +instance() Self + +_init(CallVars) SuggestionInput + +_exec(dict) AsyncGenerator + +_get_history_questions() list~str~ + +_generate_questions_from_llm() SuggestGenResult + +_generate_general_questions() AsyncGenerator + +_generate_questions_for_all_flows() AsyncGenerator + +_process_configs() AsyncGenerator + } + + class SuggestionInput { + +str question + +str user_sub + +list~str~ history_questions + } + + class SuggestionOutput { + +str question + +str flow_name + +str flow_id + +str flow_description + +bool is_highlight + } + + class SingleFlowSuggestionConfig { + +str flow_id + +str question + } + + class SuggestGenResult { + +list~str~ predicted_questions + } + + Suggestion --> SuggestionInput + Suggestion --> SuggestionOutput + Suggestion --> SingleFlowSuggestionConfig + Suggestion --> SuggestGenResult +``` + +### 2.2 数据库相关模型 + +```mermaid +erDiagram + UserTag ||--o{ User : belongs_to + UserTag ||--|| Tag : references + Record ||--|| RecordContent : contains + + UserTag { + bigint id PK + string userSub FK + bigint tag FK + int count + } + + Tag { + bigint id PK + string name + string definition + datetime updatedAt + } + + User { + bigint id PK + string userSub UK + } + + Record { + uuid id PK + uuid conversation_id + string user_sub + string content + dict key + } + + RecordContent { + string question + string answer + dict data + list facts + } +``` + +## 3. 接口设计 + +### 3.1 输入数据结构 + +#### SuggestionInput + +```json +{ + "question": "杭州有哪些著名景点?", + "user_sub": "user_12345", + "history_questions": [ + "简单介绍一下杭州", + "杭州有哪些著名景点?" + ] +} +``` + +**字段说明:** + +- `question` (string): 当前用户提出的问题 +- `user_sub` (string): 用户唯一标识 +- `history_questions` (list[string]): 该对话中的历史问题列表 + +#### SingleFlowSuggestionConfig + +```json +{ + "flow_id": "550e8400-e29b-41d4-a716-446655440000", + "question": "查询杭州的天气情况" +} +``` + +**字段说明:** + +- `flow_id` (string | null): Flow 的唯一标识,为 null 时表示通用问题 +- `question` (string): 固定的推荐问题文本 + +### 3.2 输出数据结构 + +#### SuggestionOutput + +```json +{ + "question": "杭州的天气怎么样?", + "flowName": "景点查询", + "flowId": "550e8400-e29b-41d4-a716-446655440000", + "flowDescription": "查询景点信息和相关内容", + "isHighlight": true +} +``` + +**字段说明:** + +- `question` (string): 推荐的问题文本 +- `flowName` (string | null): 关联的 Flow 名称 +- `flowId` (string | null): 关联的 Flow ID +- `flowDescription` (string | null): Flow 的描述信息 +- `isHighlight` (boolean): 是否为当前 Flow 的推荐,用于前端高亮显示 + +### 3.3 LLM 生成结果结构 + +#### SuggestGenResult + +```json +{ + "predicted_questions": [ + "杭州的天气怎么样?", + "杭州有什么特色美食?", + "西湖周边有哪些酒店推荐?" + ] +} +``` + +**字段说明:** + +- `predicted_questions` (list[string]): LLM 生成的推荐问题列表 + +## 4. 提示词模板设计 + +### 4.1 模板结构 + +- 提示词模板采用 Jinja2 语法编写,支持中文和英文两种语言版本,以适应不同用户需求。 +- 模板整体分为指令区和示例区,引导模型明确生成目标和要求。 + +### 4.2 模板变量 + +- 在模板中,预留了以下变量用于动态渲染: + - `history`:用户过往提出的问题列表,用于帮助模型避免重复推荐。 + - `generated`:已经生成的推荐问题集合,实现进一步去重。 + - `tool`:当前可用工具的信息,包括名称与功能描述,有助于模型理解可用操作范围。 + - `preference`:用户偏好或兴趣领域标签,引导模型生成更贴合用户需求的问题。 + - `target_num`:本次需要推荐的问题数量,指示模型严格生成指定条数的推荐内容。 + +### 4.3 提示词核心设计要点 + +- 明确指示模型:需要基于历史对话、工具信息和用户兴趣生成推荐问题,并且数量、格式等都有明确定义。 +- 重点强调去重:将所有历史问题和已生成问题通过 question_list 逐一罗列,要求模型不得重复推荐这些问题。 +- 问题风格与格式: + 1. 以用户口吻提出,且必须为疑问句或祈使句。 + 2. 内容精炼,每个问题不超过30字(或相应英文长度)。 + 3. 不允许在问题中夹杂与问题内容无关的扩展信息。 +- 通过示例块讲解模板填充方式,包括历史问题、工具信息、目标数量和用户偏好,帮助模型更好理解上下文格式。 +- 模板整体结构规范、要素完整,便于灵活扩展多语言版本和其他业务场景。 + +## 5. 执行流程设计 + +### 5.1 主执行流程 + +```mermaid +flowchart TD + Start([开始]) --> Init[调用 instance 方法创建实例] + Init --> LoadConversation{conversation_id 存在?} + + LoadConversation -->|是| GetHistory[从 RecordManager 获取历史问题] + LoadConversation -->|否| EmptyHistory[设置空历史列表] + + GetHistory --> DecryptRecords[解密 Record 内容] + DecryptRecords --> ExtractQuestions[提取历史问题] + EmptyHistory --> CheckAppId{app_id 存在?} + ExtractQuestions --> CheckAppId + + CheckAppId -->|是| LoadFlows[从 FlowManager 加载 Flow 列表] + CheckAppId -->|否| SkipFlows[跳过 Flow 加载] + + LoadFlows --> InitEnv[初始化 Jinja2 环境] + SkipFlows --> InitEnv + + InitEnv --> CallInit[调用 _init 方法] + CallInit --> GetUserProfile[从 UserTagManager 获取用户画像] + GetUserProfile --> LoadPrompt[加载提示词模板] + + LoadPrompt --> CheckMode{判断执行模式} + + CheckMode -->|configs 存在| ProcessConfigs[处理配置模式] + CheckMode -->|app_id 为空| GeneralMode[通用问题生成模式] + CheckMode -->|app_id 存在| FlowMode[Flow 问题生成模式] + + ProcessConfigs --> Output[输出推荐问题] + GeneralMode --> Output + FlowMode --> Output + + Output --> End([结束]) +``` + +### 5.2 通用问题生成流程 + +```mermaid +flowchart TD + Start([开始通用问题生成]) --> InitVars[初始化变量] + InitVars --> SetTargets["设置目标: pushed_questions=0, attempts=0"] + SetTargets --> CreateSet[创建已生成问题集合] + + CreateSet --> CheckLoop{pushed < target_num
且 attempts < num?} + + CheckLoop -->|否| Return([返回]) + CheckLoop -->|是| IncrementAttempt[attempts += 1] + + IncrementAttempt --> RenderPrompt[渲染提示词模板] + RenderPrompt --> BuildMessages[构建消息列表] + BuildMessages --> CallLLM[调用 _json 方法请求 LLM] + + CallLLM --> ParseResult[解析 SuggestGenResult] + ParseResult --> FilterUnique[过滤已生成的问题] + + FilterUnique --> LoopQuestions{遍历唯一问题} + + LoopQuestions --> CheckLimit{pushed >= target_num?} + CheckLimit -->|是| CheckLoop + CheckLimit -->|否| AddToSet[添加到已生成集合] + + AddToSet --> CreateOutput[创建 CallOutputChunk] + CreateOutput --> YieldOutput[yield SuggestionOutput] + YieldOutput --> IncrementPushed[pushed_questions += 1] + + IncrementPushed --> LoopQuestions + LoopQuestions -->|完成| CheckLoop +``` + +### 5.3 Flow 问题生成流程 + +```mermaid +flowchart TD + Start([开始 Flow 问题生成]) --> GetFlows[获取 _avaliable_flows] + + GetFlows --> LoopFlows{遍历每个 Flow} + + LoopFlows --> PrepareToolInfo[准备工具信息] + PrepareToolInfo --> RenderPrompt[渲染提示词模板] + RenderPrompt --> BuildMessages[构建消息列表] + BuildMessages --> CallLLM[调用 _json 方法请求 LLM] + + CallLLM --> ParseResult[解析 SuggestGenResult] + ParseResult --> RandomSelect[随机选择一个问题] + + RandomSelect --> CheckHighlight{flow_id == _flow_id?} + CheckHighlight -->|是| SetHighlightTrue[is_highlight = True] + CheckHighlight -->|否| SetHighlightFalse[is_highlight = False] + + SetHighlightTrue --> CreateOutput[创建 CallOutputChunk] + SetHighlightFalse --> CreateOutput + + CreateOutput --> YieldOutput[yield SuggestionOutput] + YieldOutput --> LoopFlows + + LoopFlows -->|完成| End([结束]) +``` + +### 5.4 配置处理流程 + +```mermaid +flowchart TD + Start([开始配置处理]) --> LoopConfigs{遍历 configs} + + LoopConfigs --> CheckFlowId{flow_id 为 None?} + + CheckFlowId -->|是| CreateGeneric[创建通用问题输出] + CreateGeneric --> SetGenericFields["设置: flowName=None
flowId=None
isHighlight=False"] + SetGenericFields --> YieldGeneric[yield SuggestionOutput] + YieldGeneric --> LoopConfigs + + CheckFlowId -->|否| ValidateFlowId{flow_id 在
_avaliable_flows 中?} + + ValidateFlowId -->|否| ThrowError[抛出 CallError 异常] + ThrowError --> End([异常结束]) + + ValidateFlowId -->|是| CheckCurrentFlow{flow_id == _flow_id?} + CheckCurrentFlow -->|是| SetHighlightTrue[is_highlight = True] + CheckCurrentFlow -->|否| SetHighlightFalse[is_highlight = False] + + SetHighlightTrue --> GetFlowInfo[从 _avaliable_flows 获取 Flow 信息] + SetHighlightFalse --> GetFlowInfo + + GetFlowInfo --> CreateFlowOutput[创建 Flow 问题输出] + CreateFlowOutput --> YieldFlow[yield SuggestionOutput] + YieldFlow --> LoopConfigs + + LoopConfigs -->|完成| End2([正常结束]) +``` + +## 6. 时序图 + +### 6.1 完整执行时序 + +```mermaid +sequenceDiagram + participant Executor as StepExecutor + participant Suggestion as Suggestion + participant RecordMgr as RecordManager + participant UserTagMgr as UserTagManager + participant FlowMgr as FlowManager + participant Security as Security + participant LLM as LLM Service + + Executor->>Suggestion: instance(executor, node, **kwargs) + activate Suggestion + + alt conversation_id 存在 + Suggestion->>RecordMgr: query_record_by_conversation_id() + activate RecordMgr + RecordMgr-->>Suggestion: 返回 Record 列表 + deactivate RecordMgr + + loop 遍历每条 Record + Suggestion->>Security: decrypt(content, key) + Security-->>Suggestion: 解密后的内容 + Suggestion->>Suggestion: 解析 RecordContent + Suggestion->>Suggestion: 提取 question + end + end + + alt app_id 存在 + Suggestion->>FlowMgr: get_flows_by_app_id(app_id) + activate FlowMgr + FlowMgr-->>Suggestion: 返回 Flow 列表 + deactivate FlowMgr + + Suggestion->>Suggestion: 构建 _avaliable_flows 字典 + end + + Suggestion-->>Executor: 返回 Suggestion 实例 + deactivate Suggestion + + Executor->>Suggestion: _init(call_vars) + activate Suggestion + + Suggestion->>UserTagMgr: get_user_domain_by_user_sub_and_topk(user_sub, 5) + activate UserTagMgr + UserTagMgr-->>Suggestion: 返回用户偏好标签列表 + deactivate UserTagMgr + + Suggestion->>Suggestion: 初始化 Jinja2 环境 + Suggestion-->>Executor: 返回 SuggestionInput + deactivate Suggestion + + Executor->>Suggestion: _exec(input_data) + activate Suggestion + + alt 配置模式 (configs 存在) + loop 遍历每个配置 + alt flow_id 为 None + Suggestion->>Executor: yield 通用问题 + else flow_id 存在 + Suggestion->>Suggestion: 验证 flow_id + Suggestion->>Suggestion: 设置 is_highlight + Suggestion->>Executor: yield Flow 问题 + end + end + else 通用模式 (app_id 为 None) + loop 直到达到目标数量 + Suggestion->>Suggestion: 渲染提示词 + Suggestion->>LLM: _json(messages, schema) + activate LLM + LLM-->>Suggestion: 返回生成的问题 + deactivate LLM + + Suggestion->>Suggestion: 过滤重复问题 + + loop 遍历唯一问题 + Suggestion->>Executor: yield SuggestionOutput + end + end + else Flow 模式 (app_id 存在) + loop 遍历每个 Flow + Suggestion->>Suggestion: 渲染提示词(包含 Flow 信息) + Suggestion->>LLM: _json(messages, schema) + activate LLM + LLM-->>Suggestion: 返回生成的问题 + deactivate LLM + + Suggestion->>Suggestion: 随机选择一个问题 + Suggestion->>Suggestion: 设置 is_highlight + Suggestion->>Executor: yield SuggestionOutput + end + end + + deactivate Suggestion +``` + +### 6.2 LLM 调用时序 + +```mermaid +sequenceDiagram + participant Suggestion as Suggestion + participant Template as Jinja2 Template + participant CoreCall as CoreCall + participant LLM as LLM Service + + Suggestion->>Suggestion: _generate_questions_from_llm() + activate Suggestion + + Suggestion->>Suggestion: 合并历史和已生成问题 + + Suggestion->>Template: render(history, generated, tool, preference, target_num) + activate Template + Template-->>Suggestion: 返回渲染后的提示词 + deactivate Template + + Suggestion->>Suggestion: 构建消息列表 + Note over Suggestion: 包含 system message 和背景对话 + + Suggestion->>CoreCall: _json(messages, schema) + activate CoreCall + + CoreCall->>LLM: 发送请求 + activate LLM + LLM-->>CoreCall: 返回 JSON 响应 + deactivate LLM + + CoreCall-->>Suggestion: 返回解析后的字典 + deactivate CoreCall + + Suggestion->>Suggestion: model_validate(result) + Suggestion-->>Suggestion: 返回 SuggestGenResult + deactivate Suggestion +``` + +## 7. 核心逻辑 + +### 7.1 去重逻辑 + +在通用问题生成模式中,模块通过集合数据结构来实现问题去重。具体做法如下: + +1. 首先初始化一个空的 `generated_questions` 集合,用于存放已经生成过的推荐问题。 +2. 每次调用大模型(LLM)时,会将历史提问和已生成问题一并传入提示词,确保推荐问题不与这些重复。 +3. 大模型返回结果后,需要过滤掉集合中已出现过的问题,只保留新的、未推荐过的问题。 +4. 新产生的问题再加入集合中,持续扩充去重列表。 +5. 以上步骤循环进行,直到达到预定数量或达到最大尝试次数为止。 + +**此逻辑的优势:** + +- 利用集合查找效率高,去重操作快速。 +- 支持多次调用 LLM 的跨批次去重。 +- 将所有历史问题都提供给 LLM,有助于生成更相关、更具有多样性的问题。 + +### 7.2 Flow 匹配与高亮逻辑 + +在流程(Flow)相关的推荐场景,逻辑为: + +- 遍历应用中的所有流程(Flow),为每个 Flow 分别生成专属的推荐问题。 +- 通过对比每个 Flow 的唯一标识(flow_id)与当前实际执行的流程标识(_flow_id),判断该 Flow 是否为当前高亮流程。 +- 若相符,则为该 Flow 生成的推荐问题设置高亮标识,该标识会在前端界面进行突出显示,以便用户快速识别当前重点推荐内容。 + +### 7.3 随机选择逻辑 + +在 Flow 模式下,大模型通常会返回多个可用推荐问题。为提升推荐结果的多样性和用户体验,每次仅随机挑选其中一个问题进行推荐。具体流程为: + +- 从 LLM 返回的问题列表中,采用随机方式选取一个问题推送给用户。 +- 这样可以避免每次都重复展示某一个问题,增强推荐内容的新鲜感和个性化体验。 + +## 8. 配置说明 + +### 8.1 模块配置参数 + +| 参数名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| to_user | bool | true | 是否将推荐问题推送给用户 | +| configs | list[SingleFlowSuggestionConfig] | [] | 固定的问题推荐配置列表 | +| num | int | 3 | 通用模式下推荐问题的总数量 (范围: 1-6) | +| conversation_id | UUID | None | 对话 ID,用于获取历史问题 | + +### 8.2 配置模式示例 + +#### 固定问题配置 + +```json +{ + "configs": [ + { + "flow_id": null, + "question": "这是一个通用推荐问题" + }, + { + "flow_id": "550e8400-e29b-41d4-a716-446655440000", + "question": "查询特定信息" + } + ] +} +``` + +#### 通用生成模式配置 + +```json +{ + "num": 5, + "configs": [] +} +``` + +#### Flow 生成模式配置 + +```json +{ + "app_id": "660e8400-e29b-41d4-a716-446655440000", + "configs": [] +} +``` + +## 9. 数据库查询 + +### 9.1 历史问题查询 + +**查询方法:** `RecordManager.query_record_by_conversation_id()` + +**查询逻辑:** + +1. 根据 user_sub 和 conversation_id 查询 MongoDB +2. 限制返回最近 15 条记录 +3. 解密每条记录的 content 字段 +4. 从 RecordContent 中提取 question 字段 + +**数据流:** + +```text +MongoDB Record → 解密 → RecordContent → question +``` + +### 9.2 用户画像查询 + +**查询方法:** `UserTagManager.get_user_domain_by_user_sub_and_topk()` + +**SQL 查询逻辑:** + +```sql +SELECT ut.*, t.name, t.definition +FROM framework_user_tag ut +JOIN framework_tag t ON ut.tag = t.id +WHERE ut.userSub = ? +ORDER BY ut.count DESC +LIMIT 5 +``` + +**返回数据:** + +- 用户最常涉及的前 5 个领域标签 +- 包含标签名称和访问次数 + +### 9.3 Flow 信息查询 + +**查询方法:** `FlowManager.get_flows_by_app_id()` + +**查询逻辑:** + +1. 根据 app_id 查询该应用下的所有 Flow +2. 提取每个 Flow 的 id、name 和 description +3. 构建字典映射: flow_id → {name, description} diff --git a/design/call/summary.md b/design/call/summary.md index c4cf00207d8b9ccd4fbc17ec333e5a499bf4ad60..9a5c1af8bb6ce2bf34c7cdc1f65950202f460765 100644 --- a/design/call/summary.md +++ b/design/call/summary.md @@ -231,11 +231,3 @@ Summary工具包含以下错误处理机制: 1. **输出格式验证**:检查LLM输出是否为字符串格式 2. **模板渲染错误**:Jinja2模板渲染异常处理 3. **LLM调用异常**:大模型调用失败处理 - -## 依赖关系 - -- `CoreCall`: 基础调用框架 -- `Jinja2`: 模板引擎 -- `Pydantic`: 数据验证和序列化 -- `LLM`: 大语言模型接口 -- `ExecutorBackground`: 执行器背景信息 diff --git a/design/services/appcenter.md b/design/services/appcenter.md new file mode 100644 index 0000000000000000000000000000000000000000..5b49eab97e6c26ba53dac5ff75ff82e25cd91754 --- /dev/null +++ b/design/services/appcenter.md @@ -0,0 +1,962 @@ +# 应用中心服务模块文档 + +## 模块概述 + +应用中心服务模块负责管理系统中的应用生命周期,包括应用的创建、更新、查询、删除、发布、收藏等核心功能。该模块支持两种应用类型:工作流应用(FLOW)和智能体应用(AGENT),并提供了完善的权限控制机制。 + +### 核心职责 + +- **应用管理**:提供应用的CRUD操作,支持工作流和智能体两种应用类型 +- **权限控制**:实现基于所有权和访问控制列表的权限验证机制 +- **应用检索**:支持按关键字、类型、作者、收藏状态等条件筛选应用 +- **使用追踪**:记录和查询用户的应用使用历史 +- **发布管理**:控制应用的发布状态,确保只有调试完成的应用可以发布 + +--- + +## 数据结构 + +### 应用卡片数据 + +用于应用列表展示的卡片信息: + +```json +{ + "appId": "550e8400-e29b-41d4-a716-446655440000", + "appType": "flow", + "icon": "/icons/app-icon.png", + "name": "文档处理助手", + "description": "智能化文档分析与处理工具", + "author": "user123", + "favorited": true, + "published": true +} +``` + +### 应用详细信息 + +工作流应用完整配置: + +```json +{ + "appId": "550e8400-e29b-41d4-a716-446655440000", + "appType": "flow", + "published": true, + "name": "文档处理助手", + "description": "智能化文档分析与处理工具", + "icon": "/icons/app-icon.png", + "links": [ + { + "title": "使用文档", + "url": "https://docs.example.com/guide" + } + ], + "recommendedQuestions": [ + "如何批量处理PDF文档?", + "支持哪些文档格式?" + ], + "dialogRounds": 5, + "permission": { + "visibility": "protected", + "authorizedUsers": ["user456", "user789"] + }, + "workflows": [ + { + "id": "660e8400-e29b-41d4-a716-446655440001", + "name": "PDF提取流程", + "description": "从PDF中提取文本和图像", + "debug": true + } + ], + "mcpService": [] +} +``` + +智能体应用完整配置: + +```json +{ + "appId": "770e8400-e29b-41d4-a716-446655440002", + "appType": "agent", + "published": false, + "name": "代码审查助手", + "description": "自动化代码审查与建议", + "icon": "/icons/code-review.png", + "links": [], + "recommendedQuestions": [], + "dialogRounds": 3, + "permission": { + "visibility": "private", + "authorizedUsers": null + }, + "workflows": [], + "mcpService": [ + { + "id": "880e8400-e29b-41d4-a716-446655440003", + "name": "Git分析服务", + "description": "提供Git仓库分析能力" + } + ] +} +``` + +### 最近使用应用列表 + +```json +{ + "applications": [ + { + "appId": "550e8400-e29b-41d4-a716-446655440000", + "name": "文档处理助手" + }, + { + "appId": "770e8400-e29b-41d4-a716-446655440002", + "name": "代码审查助手" + } + ] +} +``` + +--- + +## API接口定义 + +### 1. 获取应用列表 + +**端点**: `GET /api/app` + +**请求参数**: + +- `createdByMe` (boolean, 可选): 仅显示当前用户创建的应用 +- `favorited` (boolean, 可选): 仅显示收藏的应用 +- `keyword` (string, 可选): 搜索关键字,匹配应用名称、描述或作者 +- `appType` (string, 可选): 应用类型过滤(flow/agent) +- `page` (integer, 必需): 页码,从1开始 + +**请求示例**: + +```http +GET /api/app?keyword=文档&appType=flow&page=1 +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "currentPage": 1, + "totalApps": 42, + "applications": [ + { + "appId": "550e8400-e29b-41d4-a716-446655440000", + "appType": "flow", + "icon": "/icons/app-icon.png", + "name": "文档处理助手", + "description": "智能化文档分析与处理工具", + "author": "user123", + "favorited": true, + "published": true + } + ] + } +} +``` + +### 2. 创建或更新应用 + +**端点**: `POST /api/app` + +**请求体(创建应用)**: + +```json +{ + "appType": "flow", + "name": "新应用", + "description": "这是一个新创建的应用", + "icon": "/icons/new-app.png", + "links": [ + { + "title": "帮助文档", + "url": "https://help.example.com" + } + ], + "recommendedQuestions": ["如何开始?"], + "dialogRounds": 3, + "permission": { + "visibility": "public" + } +} +``` + +**请求体(更新应用)**: + +```json +{ + "appId": "550e8400-e29b-41d4-a716-446655440000", + "appType": "flow", + "name": "更新后的应用名称", + "description": "更新后的描述", + "icon": "/icons/updated-icon.png", + "links": [], + "recommendedQuestions": [], + "dialogRounds": 5, + "permission": { + "visibility": "protected", + "authorizedUsers": ["user456"] + } +} +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "appId": "550e8400-e29b-41d4-a716-446655440000" + } +} +``` + +### 3. 获取应用详情 + +**端点**: `GET /api/app/{appId}` + +**请求示例**: + +```http +GET /api/app/550e8400-e29b-41d4-a716-446655440000 +``` + +**响应示例**: 参见"数据结构"章节中的应用详细信息 + +### 4. 删除应用 + +**端点**: `DELETE /api/app/{appId}` + +**请求示例**: + +```http +DELETE /api/app/550e8400-e29b-41d4-a716-446655440000 +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "appId": "550e8400-e29b-41d4-a716-446655440000" + } +} +``` + +### 5. 发布应用 + +**端点**: `POST /api/app/{appId}` + +**请求示例**: + +```http +POST /api/app/550e8400-e29b-41d4-a716-446655440000 +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "appId": "550e8400-e29b-41d4-a716-446655440000" + } +} +``` + +### 6. 修改收藏状态 + +**端点**: `PUT /api/app/{appId}` + +**请求体**: + +```json +{ + "favorited": true +} +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "appId": "550e8400-e29b-41d4-a716-446655440000", + "favorited": true + } +} +``` + +### 7. 获取最近使用应用 + +**端点**: `GET /api/app/recent` + +**请求参数**: + +- `count` (integer, 可选): 返回数量,范围1-10,默认5 + +**请求示例**: + +```http +GET /api/app/recent?count=3 +``` + +**响应示例**: 参见"数据结构"章节中的最近使用应用列表 + +--- + +## 核心业务流程 + +### 应用创建流程 + +```mermaid +sequenceDiagram + participant Client as 客户端 + participant Router as API路由层 + participant Manager as AppCenterManager + participant DB as PostgreSQL + participant Loader as AppLoader + participant Pool as 资源池 + + Client->>Router: POST /api/app (无appId) + Router->>Router: 验证用户会话和令牌 + Router->>Manager: create_app(user_sub, request) + Manager->>Manager: 生成新的app_id + Manager->>Manager: _process_app_and_save() + Manager->>Pool: 从资源池获取app_loader + Manager->>Loader: read_metadata(app_id) + Note over Loader: 首次创建,返回空或默认值 + Manager->>Manager: _create_metadata() + alt 应用类型为FLOW + Manager->>Manager: _create_flow_metadata() + Note over Manager: 创建工作流元数据
flows为空列表 + else 应用类型为AGENT + Manager->>Manager: _create_agent_metadata() + Manager->>Manager: 验证MCP服务激活状态 + Note over Manager: 创建智能体元数据
mcp_service为已激活列表 + end + Manager->>Loader: save(metadata, app_id) + Note over Loader: 保存元数据到文件系统 + Manager->>DB: 插入App记录 + Manager-->>Router: 返回app_id + Router-->>Client: 返回成功响应 +``` + +### 应用更新流程 + +```mermaid +sequenceDiagram + participant Client as 客户端 + participant Router as API路由层 + participant Manager as AppCenterManager + participant DB as PostgreSQL + participant Loader as AppLoader + + Client->>Router: POST /api/app (含appId) + Router->>Router: 验证用户会话和令牌 + Router->>Manager: update_app(user_sub, app_id, request) + Manager->>Manager: _get_app_data(app_id, user_sub) + Manager->>DB: 查询应用信息 + alt 应用不存在 + DB-->>Manager: 返回空 + Manager-->>Router: 抛出ValueError + Router-->>Client: 400 BAD_REQUEST + else 权限不足 + DB-->>Manager: 返回应用信息 + Manager->>Manager: 检查author != user_sub + Manager-->>Router: 抛出InstancePermissionError + Router-->>Client: 403 FORBIDDEN + else 更改应用类型 + Manager->>Manager: 检查appType变化 + Manager-->>Router: 抛出ValueError + Router-->>Client: 400 BAD_REQUEST + else 正常更新 + DB-->>Manager: 返回应用信息 + Manager->>Manager: _process_app_and_save() + Manager->>Loader: read_metadata(app_id) + Loader-->>Manager: 返回现有元数据 + Manager->>Manager: _create_metadata() + Note over Manager: 保留现有flows/mcp_service
更新其他字段 + Manager->>Loader: save(metadata, app_id) + Manager->>DB: 更新App记录 + Manager-->>Router: 更新完成 + Router-->>Client: 200 OK + end +``` + +### 应用查询流程 + +```mermaid +flowchart TD + Start([客户端请求]) --> Auth[验证用户身份] + Auth --> BuildQuery[构建基础查询] + + BuildQuery --> BaseFilter{构建权限过滤条件} + BaseFilter --> PublicApps[PUBLIC类型应用] + BaseFilter --> PrivateApps[PRIVATE且author=user] + BaseFilter --> ProtectedApps[PROTECTED且在ACL中] + + PublicApps --> UnionQuery[合并查询条件] + PrivateApps --> UnionQuery + ProtectedApps --> UnionQuery + + UnionQuery --> FilterType{应用过滤类型} + FilterType -->|ALL| PublishedFilter[仅已发布应用] + FilterType -->|USER| AuthorFilter[author=user] + FilterType -->|FAVORITE| FavoriteFilter[在收藏列表中] + + PublishedFilter --> AppTypeFilter{应用类型过滤} + AuthorFilter --> AppTypeFilter + FavoriteFilter --> AppTypeFilter + + AppTypeFilter -->|指定类型| TypeQuery[appType=指定值] + AppTypeFilter -->|无指定| SkipType[跳过类型过滤] + + TypeQuery --> KeywordFilter{关键字过滤} + SkipType --> KeywordFilter + + KeywordFilter -->|有关键字| KeywordQuery[name/description/author模糊匹配] + KeywordFilter -->|无关键字| SkipKeyword[跳过关键字过滤] + + KeywordQuery --> Pagination[分页处理] + SkipKeyword --> Pagination + + Pagination --> CountTotal[统计总数] + CountTotal --> QueryPage[查询当前页数据] + QueryPage --> LoadFavorites[加载用户收藏列表] + LoadFavorites --> BuildCards[构建应用卡片] + BuildCards --> End([返回结果]) +``` + +### 应用发布流程 + +```mermaid +sequenceDiagram + participant Client as 客户端 + participant Router as API路由层 + participant Manager as AppCenterManager + participant FlowMgr as FlowManager + participant DB as PostgreSQL + participant Loader as AppLoader + + Client->>Router: POST /api/app/{appId} + Router->>Router: 验证用户会话和令牌 + Router->>Manager: update_app_publish_status(app_id, user_sub) + Manager->>Manager: _get_app_data(app_id, user_sub) + Manager->>DB: 查询应用并验证权限 + + alt 权限验证失败 + DB-->>Manager: 应用不存在或非作者 + Manager-->>Router: 抛出异常 + Router-->>Client: 403 FORBIDDEN + else 权限验证通过 + DB-->>Manager: 返回应用数据 + Manager->>FlowMgr: get_flows_by_app_id(app_id) + FlowMgr->>DB: 查询应用关联的工作流 + FlowMgr-->>Manager: 返回flows列表 + + Manager->>Manager: 遍历检查每个flow的debug状态 + alt 存在未调试的flow + Manager->>Manager: published = False + Note over Manager: 有工作流未完成调试 + else 所有flow均已调试 + Manager->>Manager: published = True + Note over Manager: 可以发布应用 + end + + Manager->>DB: 更新App.isPublished字段 + Manager->>Manager: _process_app_and_save() + Manager->>Loader: 读取并更新元数据 + Manager->>Loader: save(metadata, app_id) + Note over Loader: published字段已更新 + + Manager-->>Router: 返回published状态 + Router-->>Client: 200 OK + end +``` + +### 权限验证流程 + +```mermaid +flowchart TD + Start([权限验证开始]) --> QueryApp[查询应用信息] + QueryApp --> AppExists{应用是否存在} + + AppExists -->|不存在| Error1[抛出ValueError异常] + AppExists -->|存在| CheckAuthor{检查是否为作者} + + CheckAuthor -->|是作者| Granted[授予访问权限] + CheckAuthor -->|非作者| CheckPermType{检查权限类型} + + CheckPermType -->|PUBLIC| Granted + CheckPermType -->|PRIVATE| Denied[拒绝访问] + CheckPermType -->|PROTECTED| QueryACL[查询ACL表] + + QueryACL --> InACL{用户在ACL中} + InACL -->|是| Granted + InACL -->|否| Denied + + Granted --> End1([返回True]) + Denied --> End2([返回False]) + Error1 --> End3([抛出异常]) +``` + +### 收藏功能流程 + +```mermaid +stateDiagram-v2 + [*] --> 验证应用存在 + 验证应用存在 --> 查询收藏记录: 应用存在 + 验证应用存在 --> 抛出异常: 应用不存在 + + 查询收藏记录 --> 添加收藏: favorited=true且无记录 + 查询收藏记录 --> 移除收藏: favorited=false且有记录 + 查询收藏记录 --> 重复操作: 状态一致 + + 添加收藏 --> 创建UserFavorite记录 + 创建UserFavorite记录 --> 提交事务 + + 移除收藏 --> 删除UserFavorite记录 + 删除UserFavorite记录 --> 提交事务 + + 重复操作 --> 抛出ValueError + 提交事务 --> [*] + 抛出异常 --> [*] + 抛出ValueError --> [*] +``` + +--- + +## 核心方法说明 + +### validate_user_app_access + +验证用户是否有权访问指定应用。 + +- **功能描述**: 验证用户对指定应用的访问权限 +- **验证流程**: + - 从数据库查询应用的作者和权限类型 + - 若用户是应用作者,直接授予访问权限 + - 根据权限类型执行相应的验证逻辑 +- **权限判断规则**: + - `PUBLIC`: 所有用户均可访问 + - `PRIVATE`: 仅作者可访问 + - `PROTECTED`: 需检查用户是否在访问控制列表中 +- **异常情况**: 应用不存在时抛出`ValueError`异常 + +### validate_app_belong_to_user + +验证应用是否属于指定用户。 + +- **功能描述**: 确认应用所有权归属 +- **验证逻辑**: 在数据库中查询同时满足应用ID和作者匹配的记录 +- **异常情况**: 找不到匹配记录时抛出`ValueError`异常 +- **使用场景**: 更新、删除等需要所有权验证的操作 + +### fetch_apps + +获取用户可访问的应用列表,并支持多维度筛选。 + +- **功能描述**: 获取并筛选用户可访问的应用列表 +- **权限过滤**: 构建CTE表达式,包含三类应用 + - 公开应用(PUBLIC) + - 用户自己创建的私有应用(PRIVATE) + - 用户在ACL中的受保护应用(PROTECTED) +- **过滤维度**: + - **过滤类型**: 全部(仅已发布)、用户创建、收藏(已发布) + - **应用类型**: 工作流(FLOW)或智能体(AGENT) + - **关键字**: 在应用名称、描述和作者字段中进行模糊匹配 +- **分页处理**: 支持分页查询 +- **结果构建**: 为每个应用构建卡片对象并标注收藏状态 + +### fetch_app_metadata_by_id + +根据应用ID获取完整的元数据信息。 + +- **功能描述**: 获取应用的完整配置信息 +- **执行步骤**: + 1. 在数据库中验证应用是否存在 + 2. 通过应用加载器从文件系统读取元数据 +- **元数据类型**: + - 工作流应用元数据 + - 智能体应用元数据 +- **异常情况**: 应用存在但元数据文件缺失时抛出`ValueError`异常 +- **核心作用**: 获取应用完整配置的核心入口 + +### create_app + +创建新应用的入口方法。 + +- **功能描述**: 创建新应用并初始化配置 +- **执行步骤**: + 1. 生成新的应用ID + 2. 调用内部处理方法完成元数据创建和持久化 +- **应用类型处理**: + - **工作流应用**: 初始化时工作流列表为空 + - **智能体应用**: 验证MCP服务激活状态,仅添加已激活服务 +- **返回结果**: 新生成的应用ID + +### update_app + +更新现有应用的入口方法。 + +- **功能描述**: 更新已存在应用的配置信息 +- **验证检查**: + - 验证用户对应用的所有权 + - 确保只有应用作者可以执行更新操作 + - 检查是否尝试更改应用类型(不允许) +- **更新策略**: + - 保留现有的工作流列表或MCP服务列表 + - 只更新用户提交的基本信息和权限配置 +- **调用流程**: 验证通过后调用内部处理方法更新元数据 + +### update_app_publish_status + +更新应用的发布状态。 + +- **功能描述**: 控制应用的发布状态 +- **执行步骤**: + 1. 验证用户所有权 + 2. 查询应用关联的所有工作流 + 3. 检查每个工作流的调试状态 +- **发布条件**: 所有工作流都已完成调试 +- **更新范围**: 同时更新数据库和元数据文件 +- **返回结果**: 最终的发布状态(有未调试工作流则返回false) + +### modify_favorite_app + +修改用户对应用的收藏状态。 + +- **功能描述**: 添加或取消应用收藏 +- **执行流程**: + 1. 验证应用是否存在 + 2. 查询用户当前对该应用的收藏记录 +- **操作逻辑**: + - **添加收藏**: 请求添加且当前无记录时,创建新收藏记录 + - **取消收藏**: 请求取消且当前有记录时,删除收藏记录 +- **异常情况**: 请求状态与当前状态一致时抛出`ValueError`异常(重复操作) + +### delete_app + +删除指定应用。 + +- **功能描述**: 删除应用及其关联数据 +- **验证步骤**: + 1. 从数据库查询应用信息,验证应用是否存在 + 2. 检查当前用户是否为应用作者 +- **删除范围**: + - 删除数据库中的应用记录 + - 级联删除ACL记录、收藏记录等关联数据 +- **注意事项**: 只删除数据库记录,不删除文件系统中的元数据文件 + +### get_recently_used_apps + +获取用户最近使用的应用列表。 + +- **功能描述**: 查询用户的应用使用历史 +- **查询条件**: + - 在用户应用使用表中查询指定用户的记录 + - 按最后使用时间降序排列 + - 限制返回数量 +- **结果构建**: 为每个应用ID查询应用名称,构建列表项 +- **数据处理**: 已删除的应用会被跳过 +- **使用场景**: 在用户界面展示最近使用历史 + +### update_recent_app + +更新用户的应用使用记录。 + +- **功能描述**: 记录或更新应用使用信息 +- **查询逻辑**: 在用户应用使用表中查找当前用户的所有记录 +- **更新策略**: + - **已有记录**: 更新最后使用时间并将使用次数加一 + - **无记录**: 创建新的使用记录 +- **数据过滤**: 过滤掉全零UUID的应用ID +- **异常情况**: 用户在数据库中不存在时抛出`ValueError`异常 + +### _get_app_data + +内部方法:从数据库获取应用数据并可选择性验证作者身份。 + +- **功能描述**: 获取应用数据并执行权限验证 +- **执行步骤**: 通过应用ID查询数据库中的应用记录 +- **作者检查**: 启用时比对当前用户与应用作者是否一致 +- **异常情况**: + - 查询不到记录时抛出`ValueError`异常 + - 作者不一致时抛出`InstancePermissionError`异常 +- **核心作用**: 其他业务方法执行权限验证的基础工具 + +### _create_metadata + +内部方法:根据应用类型创建相应的元数据对象。 + +- **功能描述**: 创建应用元数据对象 +- **验证步骤**: 验证是否提供了必要的数据源(新建数据或现有数据) +- **通用参数**: 构建参数集合,包括ID、作者、图标、名称、描述、对话轮次和权限配置 +- **类型分派**: + - 工作流应用 → 调用`_create_flow_metadata` + - 智能体应用 → 调用`_create_agent_metadata` +- **异常情况**: 应用类型与元数据类型不匹配时抛出`ValueError`异常 + +### _create_flow_metadata + +内部方法:创建工作流应用的元数据对象。 + +- **功能描述**: 生成工作流应用的完整元数据 +- **基础创建**: 基于通用参数创建工作流元数据 +- **特有字段**: + - 相关链接(links) + - 推荐问题(recommendedQuestions) +- **工作流列表处理**: + - 提供现有元数据时保留现有列表 + - 否则初始化为空列表 +- **发布状态处理**: + - 创建场景:默认为未发布 + - 更新场景:保留现有状态或使用传入参数 + +### _create_agent_metadata + +内部方法:创建智能体应用的元数据对象。 + +- **功能描述**: 生成智能体应用的完整元数据 +- **基础创建**: 基于通用参数创建智能体元数据 +- **MCP服务列表处理**: + - 提供新服务数据时,逐个验证激活状态 + - 仅添加已激活的服务到元数据中 + - 未提供服务数据时初始化为空列表 +- **发布状态**: 使用传入的参数值,创建时默认为未发布 + +### _process_app_and_save + +内部方法:处理应用元数据的创建和持久化。 + +- **功能描述**: 元数据处理和持久化的统一入口 +- **执行步骤**: + 1. 从应用加载器读取现有的应用元数据 + 2. 调用元数据创建方法生成新的元数据对象 + 3. 通过应用加载器将元数据保存到文件系统 +- **核心作用**: 创建、更新、发布等操作的最终执行节点 +- **数据同步**: 确保内存中的元数据对象与文件系统中的持久化数据保持同步 + +--- + +## 数据模型关系 + +```mermaid +erDiagram + App ||--o{ AppACL : "受保护时授权" + App ||--o{ AppMCP : "使用MCP服务" + App ||--o{ UserFavorite : "被用户收藏" + App ||--o{ UserAppUsage : "被用户使用" + User ||--o{ App : "创建" + User ||--o{ AppACL : "被授权" + User ||--o{ UserFavorite : "收藏" + User ||--o{ UserAppUsage : "使用记录" + MCP ||--o{ AppMCP : "被应用使用" + + App { + uuid id PK + string name + string description + string author "FK" + enum appType + datetime updatedAt + string icon + boolean isPublished + enum permission + } + + AppACL { + uuid appId PK "FK" + string userSub "FK" + string action + } + + AppMCP { + uuid appId PK "FK" + string mcpId PK "FK" + datetime createdAt + } + + User { + string userSub PK + datetime lastLogin + boolean isActive + string personalToken + } + + UserFavorite { + int id PK + string userSub "FK" + enum favouriteType + uuid itemId + } + + UserAppUsage { + int id PK + string userSub "FK" + uuid appId "FK" + int usageCount + datetime lastUsed + } + + MCP { + string id PK + string name + string description + } +``` + +--- + +## 异常处理机制 + +### ValueError异常 + +在以下场景中抛出值错误异常: + +- 应用ID无效或应用不存在 +- 尝试更改应用类型 +- 元数据创建时缺少必要参数 +- 应用类型与元数据类型不匹配 +- 收藏操作重复执行 +- 用户不存在于系统中 + +### InstancePermissionError异常 + +在以下场景中抛出实例权限错误异常: + +- 非应用作者尝试更新应用 +- 非应用作者尝试删除应用 +- 非应用作者尝试发布应用 + +### 通用异常 + +其他未预期的异常会被路由层捕获,统一返回500内部服务器错误响应,并记录详细的异常日志用于排查问题。 + +--- + +## 权限模型 + +### 权限类型 + +系统支持三种权限类型: + +1. **PUBLIC(公开)**: 所有用户均可访问和使用该应用 +2. **PRIVATE(私有)**: 仅应用作者本人可以访问和使用 +3. **PROTECTED(受保护)**: 应用作者和授权用户列表中的用户可以访问 + +### 所有权验证 + +只有应用作者可以执行以下操作: + +- 更新应用配置 +- 删除应用 +- 发布应用 +- 修改应用权限设置 + +### 访问权限验证 + +用户访问应用时的权限判断优先级: + +1. 如果用户是应用作者,直接授予访问权限 +2. 如果应用是公开类型,授予访问权限 +3. 如果应用是私有类型,拒绝访问 +4. 如果应用是受保护类型,检查用户是否在ACL表中 + +--- + +## 应用类型差异 + +### 工作流应用(FLOW) + +工作流应用通过可视化的流程编排实现业务逻辑,支持以下特性: + +- 包含多个工作流(workflows),每个工作流是独立的执行单元 +- 配置相关链接(links),提供外部参考资源 +- 设置推荐问题(recommendedQuestions),引导用户使用 +- 发布前必须确保所有工作流都已完成调试 + +### 智能体应用(AGENT) + +智能体应用通过AI模型和外部服务集成实现智能交互,支持以下特性: + +- 集成MCP服务(mcpService),扩展智能体能力 +- 创建或更新时验证MCP服务的激活状态 +- 不包含工作流和推荐问题配置 +- 发布状态独立管理,不依赖子组件状态 + +--- + +## 与其他模块的交互 + +```mermaid +graph LR + AppCenter[应用中心服务] --> FlowMgr[工作流管理器] + AppCenter --> MCPMgr[MCP服务管理器] + AppCenter --> AppLoader[应用加载器] + AppCenter --> PostgreSQL[(PostgreSQL数据库)] + + FlowMgr --> PostgreSQL + MCPMgr --> PostgreSQL + AppLoader --> FileSystem[(文件系统)] + + Router[API路由层] --> AppCenter + Router --> Auth[身份验证模块] + + Auth --> Session[会话验证] + Auth --> Token[令牌验证] + + style AppCenter fill:#e1f5ff + style PostgreSQL fill:#ffe1e1 + style FileSystem fill:#fff4e1 +``` + +### 依赖的外部模块 + +- **FlowManager**: 查询应用关联的工作流信息,用于发布状态判断 +- **MCPServiceManager**: 验证MCP服务的激活状态,用于智能体应用配置 +- **AppLoader**: 从文件系统读取和保存应用元数据 +- **PostgreSQL**: 持久化应用基本信息、权限配置、收藏和使用记录 + +### 被依赖的场景 + +- **对话服务**: 在创建对话时验证用户对应用的访问权限 +- **工作流服务**: 在执行工作流时获取应用配置信息 +- **用户界面**: 展示应用列表、详情和使用历史 + +--- + +## 状态管理 + +### 应用发布状态 + +应用发布状态(isPublished)控制应用是否在应用中心公开展示: + +- **未发布(False)**: 仅在"我的应用"中可见,不出现在应用中心主列表 +- **已发布(True)**: 根据权限配置在应用中心对相应用户可见 + +工作流应用的发布状态由其包含的所有工作流的调试状态决定,所有工作流都必须完成调试才能发布。智能体应用的发布状态可以独立设置。 + +### 应用收藏状态 + +用户可以将任何有访问权限的已发布应用添加到收藏列表。收藏状态记录在UserFavorite表中,与应用本身的状态独立。用户可以通过收藏过滤器快速访问收藏的应用。 + +### 应用使用状态 + +系统自动跟踪用户对应用的使用情况,包括使用次数和最后使用时间。这些数据用于生成最近使用应用列表,帮助用户快速访问常用应用。使用记录的更新通过update_recent_app方法在应用启动时触发。 diff --git a/design/services/blacklist.md b/design/services/blacklist.md new file mode 100644 index 0000000000000000000000000000000000000000..804f39c0e9946a8d63b6700d08f722cb70413cf3 --- /dev/null +++ b/design/services/blacklist.md @@ -0,0 +1,701 @@ +# Blacklist模块设计文档 + +## 概述 + +Blacklist 模块是 openEuler Intelligence 框架中的内容审核与风控模块,负责处理问题黑名单、用户黑名单以及用户举报功能。该模块通过三个核心管理器实现内容过滤、用户信用分管理和举报审核流程,确保系统内容安全和用户行为规范。 + +## 架构设计 + +### 模块结构 + +```text +apps/ +├── models/blacklist.py # 黑名单数据模型 +├── services/blacklist.py # 黑名单服务层 +├── routers/blacklist.py # 黑名单路由层 +└── schemas/ + ├── blacklist.py # 黑名单请求响应数据结构 + └── response_data.py # 通用响应数据结构 +``` + +### 数据模型关系 + +```mermaid +erDiagram + Blacklist ||--|| Record : "关联问答对" + Record ||--|| User : "属于用户" + + Blacklist { + bigint id PK "主键ID" + uuid recordId FK "问答对ID" + text question "黑名单问题" + text answer "固定回答" + boolean isAudited "是否生效" + string reasonType "举报类型" + text reason "举报原因" + datetime updatedAt "更新时间" + } + + Record { + uuid id PK "问答对ID" + string userSub FK "用户标识" + text content "加密内容" + text key "加密密钥" + } + + User { + bigint id PK "用户ID" + string userSub UK "用户标识" + int credit "信用分" + boolean isWhitelisted "白名单标识" + } +``` + +## 核心功能 + +### 1. 问题黑名单管理 (QuestionBlacklistManager) + +#### 功能流程图 + +```mermaid +flowchart TD + A[问题黑名单操作] --> B{操作类型} + + B -->|检测问题| C[check_blacklisted_questions] + B -->|获取列表| D[get_blacklisted_questions] + B -->|修改/删除| E[change_blacklisted_questions] + + C --> C1[查询已审核黑名单] + C1 --> C2{输入问题是否包含黑名单内容} + C2 -->|包含| C3[返回False:拦截] + C2 -->|不包含| C4[返回True:放行] + + D --> D1[根据审核状态查询] + D1 --> D2[应用分页参数] + D2 --> D3[返回黑名单列表] + + E --> E1[根据ID查询黑名单记录] + E1 --> E2{记录是否存在} + E2 -->|不存在| E3[返回False] + E2 -->|存在| E4{是否为删除操作} + E4 -->|是| E5[删除记录] + E4 -->|否| E6[更新问题和答案] + E5 --> E7[提交事务] + E6 --> E7 + E7 --> E8[返回True] + + style C3 fill:#ffebee + style C4 fill:#e8f5e8 + style E3 fill:#ffebee + style E8 fill:#e8f5e8 +``` + +#### 主要方法 + +1. **check_blacklisted_questions(input_question)**: 检测给定问题是否触及黑名单。通过模糊匹配方式查询已审核的黑名单记录,如果输入问题包含黑名单问题内容则返回拦截信号。 + +2. **get_blacklisted_questions(limit, offset, is_audited)**: 分页获取黑名单问题列表。根据审核状态筛选记录,支持获取已生效黑名单或待审核举报内容。 + +3. **change_blacklisted_questions(blacklist_id, question, answer, + is_deletion)**: 修改或删除黑名单记录。管理员可以更新问题和答案内容, + 或彻底删除某条黑名单规则。 + +### 2. 用户黑名单管理 (UserBlacklistManager) + +#### 用户信用分更新流程 + +```mermaid +sequenceDiagram + participant Admin as 管理员 + participant Router as 黑名单路由 + participant Manager as UserBlacklistManager + participant DB as 数据库 + + Admin->>Router: POST /api/blacklist/user + Router->>Manager: change_blacklisted_users(user_sub, credit_diff) + + Manager->>DB: 查询用户信息 + DB-->>Manager: 返回用户数据 + + alt 用户不存在 + Manager-->>Router: 返回False + Router-->>Admin: 返回500错误 + else 用户在白名单 + Manager-->>Router: 返回False + Router-->>Admin: 返回500错误 + else 用户已处于目标状态 + Manager-->>Router: 返回True(无需操作) + Router-->>Admin: 返回成功 + else 正常更新 + Manager->>Manager: 计算新信用分(0-100范围) + Manager->>DB: 更新用户信用分 + DB-->>Manager: 更新成功 + Manager-->>Router: 返回True + Router-->>Admin: 返回成功响应 + end +``` + +#### 核心逻辑 + +1. **信用分机制**: 用户信用分范围为0-100,信用分小于等于0的用户被视为黑名单用户,无法继续使用系统功能。 + +2. **白名单保护**: 被标记为白名单的用户不受信用分限制,任何信用分变更操作对白名单用户无效。 + +3. **状态校验**: 在执行信用分变更前检查用户当前状态,如果用户已处于目标状态(已封禁/已解禁)则直接返回成功,避免重复操作。 + +4. **边界保护**: 信用分更新时自动限制在0-100范围内,防止数值溢出。 + +#### 用户黑名单主要方法 + +1. **get_blacklisted_users(limit, offset)**: 分页获取所有信用分小于等于0的 + 黑名单用户标识列表。 + +2. **check_blacklisted_users(user_sub)**: 检测指定用户是否被拉黑。 + 同时检查信用分和白名单状态,白名单用户即使信用分为0也不会被拦截。 + +3. **change_blacklisted_users(user_sub, credit_diff, credit_limit)**: + 修改用户信用分。传入负值用于封禁用户,传入正值用于解禁用户。 + 系统会自动计算新信用分并确保其在有效范围内。 + +### 3. 举报管理 (AbuseManager) + +#### 举报审核完整流程 + +```mermaid +flowchart TD + A[用户发起举报] --> B[abuse_report: 提交举报] + + B --> B1[验证问答对归属] + B1 --> B2{问答对是否存在且属于该用户} + B2 -->|否| B3[返回失败] + B2 -->|是| B4[解密问答对内容] + + B4 --> B5{该问答对是否已被举报} + B5 -->|是| B6[返回成功:避免重复] + B5 -->|否| B7[创建待审核黑名单记录] + B7 --> B8[isAudited=False] + B8 --> B9[保存举报信息] + + B9 --> C[管理员审核] + + C --> D[get_blacklisted_questions: 获取待审核列表] + D --> D1[查询isAudited=False的记录] + D1 --> D2[展示给管理员] + + D2 --> E[管理员决策] + E --> F{审核结果} + + F -->|批准| G[audit_abuse_report: is_deletion=False] + F -->|拒绝| H[audit_abuse_report: is_deletion=True] + + G --> G1[设置isAudited=True] + G1 --> G2[黑名单生效] + + H --> H1[删除举报记录] + H1 --> H2[举报未通过] + + style B3 fill:#ffebee + style B6 fill:#fff3e0 + style G2 fill:#e8f5e8 + style H2 fill:#e1f5fe +``` + +#### 举报数据流转 + +```mermaid +sequenceDiagram + participant User as 用户 + participant Router as 举报路由 + participant AbuseManager as 举报管理器 + participant Security as 安全模块 + participant DB as 数据库 + + User->>Router: POST /api/blacklist/complaint + Note right of User: record_id, reason_type, reason + + Router->>AbuseManager: change_abuse_report() + AbuseManager->>DB: 查询问答对记录 + + alt 问答对不存在或不属于用户 + DB-->>AbuseManager: 返回None + AbuseManager-->>Router: 返回False + Router-->>User: 500错误:举报记录不合法 + else 问答对存在 + DB-->>AbuseManager: 返回加密的问答对 + AbuseManager->>Security: decrypt(content, key) + Security-->>AbuseManager: 返回明文内容 + + AbuseManager->>DB: 检查是否已举报 + + alt 已存在举报记录 + DB-->>AbuseManager: 返回现有记录 + AbuseManager-->>Router: 返回True(幂等) + Router-->>User: 成功响应 + else 首次举报 + AbuseManager->>DB: 创建新黑名单记录 + Note right of AbuseManager: isAudited=False
question, answer + DB-->>AbuseManager: 保存成功 + AbuseManager-->>Router: 返回True + Router-->>User: 成功响应 + end + end +``` + +#### 举报管理核心逻辑 + +1. **归属验证**: 用户只能举报属于自己的问答对记录,系统通过用户标识和 + 问答对ID双重校验确保合法性。 + +2. **内容解密**: 问答对内容在数据库中以加密形式存储,举报时需要解密 + 获取原始问题和答案文本。 + +3. **幂等保护**: 同一问答对只能被举报一次,重复举报请求返回成功但不 + 创建新记录。 + +4. **两阶段审核**: 举报内容首先进入待审核状态(isAudited=False), + 管理员审核后决定是否生效或删除。 + +5. **审核操作**: 管理员可以批准举报使黑名单生效(isAudited=True), + 或拒绝举报并删除记录。 + +#### 举报管理主要方法 + +1. **change_abuse_report(user_sub, record_id, reason_type, reason)**: + 用户提交举报。验证问答对归属关系,解密内容后创建待审核黑名单记录, + 记录举报类型和原因。 + +2. **audit_abuse_report(record_id, is_deletion)**: 管理员审核举报。 + 批准时将黑名单记录标记为已审核状态使其生效,拒绝时删除举报记录。 + +## API接口设计 + +### 接口列表 + +| 方法 | 路径 | 权限 | 功能 | 描述 | +|------|------|------|------|------| +| GET | `/api/blacklist/user` | 管理员 | 获取黑名单用户 | 分页获取信用分≤0的用户列表 | +| POST | `/api/blacklist/user` | 管理员 | 操作黑名单用户 | 封禁或解禁指定用户 | +| GET | `/api/blacklist/question` | 管理员 | 获取黑名单问题 | 分页获取已生效的黑名单问题 | +| POST | `/api/blacklist/question` | 管理员 | 操作黑名单问题 | 修改或删除黑名单问题 | +| POST | `/api/blacklist/complaint` | 普通用户 | 提交举报 | 用户举报不当问答对 | +| GET | `/api/blacklist/abuse` | 管理员 | 获取待审核举报 | 分页获取待审核的举报记录 | +| POST | `/api/blacklist/abuse` | 管理员 | 审核举报 | 批准或拒绝用户举报 | + +### 请求响应示例 + +#### 1. 获取黑名单用户 + +**请求**: + +```http +GET /api/blacklist/user?page=0 +Authorization: Bearer +X-Personal-Token: +``` + +**响应**: + +```json +{ + "code": 200, + "message": "ok", + "result": { + "user_subs": [ + "user-sub-123", + "user-sub-456", + "user-sub-789" + ] + } +} +``` + +#### 2. 封禁/解禁用户 + +**请求 - 封禁用户**: + +```http +POST /api/blacklist/user +Content-Type: application/json +Authorization: Bearer +X-Personal-Token: + +{ + "user_sub": "user-sub-123", + "is_ban": 1 +} +``` + +**请求 - 解禁用户**: + +```json +{ + "user_sub": "user-sub-123", + "is_ban": 0 +} +``` + +**响应**: + +```json +{ + "code": 200, + "message": "ok", + "result": {} +} +``` + +**错误响应**: + +```json +{ + "code": 500, + "message": "Change user blacklist error.", + "result": {} +} +``` + +#### 3. 获取黑名单问题 + +**请求**: + +```http +GET /api/blacklist/question?page=0 +Authorization: Bearer +X-Personal-Token: +``` + +**响应**: + +```json +{ + "code": 200, + "message": "ok", + "result": { + "question_list": [ + { + "id": 1, + "recordId": "550e8400-e29b-41d4-a716-446655440000", + "question": "如何破解系统密码", + "answer": "很抱歉,我无法回答此类问题。", + "isAudited": true, + "reasonType": "安全风险", + "reason": "涉及系统安全破解", + "updatedAt": "2024-01-15T10:30:00Z" + }, + { + "id": 2, + "recordId": "660e8400-e29b-41d4-a716-446655440001", + "question": "如何获取他人隐私信息", + "answer": "此类问题违反隐私保护政策,无法提供帮助。", + "isAudited": true, + "reasonType": "隐私侵犯", + "reason": "涉及非法获取他人信息", + "updatedAt": "2024-01-16T14:20:00Z" + } + ] + } +} +``` + +#### 4. 修改/删除黑名单问题 + +**请求 - 修改问题**: + +```http +POST /api/blacklist/question +Content-Type: application/json +Authorization: Bearer +X-Personal-Token: + +{ + "id": "1", + "question": "如何破解系统密码或绕过认证", + "answer": "很抱歉,我无法回答任何与系统安全破解相关的问题。", + "is_deletion": 0 +} +``` + +**请求 - 删除问题**: + +```json +{ + "id": "1", + "question": "", + "answer": "", + "is_deletion": 1 +} +``` + +**响应**: + +```json +{ + "code": 200, + "message": "ok", + "result": {} +} +``` + +**错误响应**: + +```json +{ + "code": 500, + "message": "Modify question blacklist error.", + "result": {} +} +``` + +#### 5. 用户提交举报 + +**请求**: + +```http +POST /api/blacklist/complaint +Content-Type: application/json +Authorization: Bearer +X-Personal-Token: + +{ + "record_id": "770e8400-e29b-41d4-a716-446655440002", + "reason_type": "内容不当", + "reason": "回答内容包含误导性信息,可能对用户造成损失" +} +``` + +**响应**: + +```json +{ + "code": 200, + "message": "ok", + "result": {} +} +``` + +**错误响应**: + +```json +{ + "code": 500, + "message": "Report abuse complaint error.", + "result": {} +} +``` + +#### 6. 获取待审核举报 + +**请求**: + +```http +GET /api/blacklist/abuse?page=0 +Authorization: Bearer +X-Personal-Token: +``` + +**响应**: + +```json +{ + "code": 200, + "message": "ok", + "result": { + "question_list": [ + { + "id": 3, + "recordId": "770e8400-e29b-41d4-a716-446655440002", + "question": "openEuler系统如何配置网络", + "answer": "直接修改/etc/network/interfaces文件即可", + "isAudited": false, + "reasonType": "内容不当", + "reason": "回答内容包含误导性信息,可能对用户造成损失", + "updatedAt": "2024-01-17T09:15:00Z" + } + ] + } +} +``` + +#### 7. 审核举报 + +**请求 - 批准举报(黑名单生效)**: + +```http +POST /api/blacklist/abuse +Content-Type: application/json +Authorization: Bearer +X-Personal-Token: + +{ + "id": "770e8400-e29b-41d4-a716-446655440002", + "is_deletion": 0 +} +``` + +**请求 - 拒绝举报(删除记录)**: + +```json +{ + "id": "770e8400-e29b-41d4-a716-446655440002", + "is_deletion": 1 +} +``` + +**响应**: + +```json +{ + "code": 200, + "message": "ok", + "result": {} +} +``` + +**错误响应**: + +```json +{ + "code": 500, + "message": "Audit abuse question error.", + "result": {} +} +``` + +## 数据流转图 + +### 整体架构数据流 + +```mermaid +graph TB + subgraph "客户端层" + A1[管理员客户端] + A2[用户客户端] + end + + subgraph "路由层" + B1[admin_router
管理员路由] + B2[router
普通用户路由] + end + + subgraph "依赖验证" + C1[verify_session
会话验证] + C2[verify_personal_token
令牌验证] + C3[verify_admin
管理员验证] + end + + subgraph "服务层" + D1[QuestionBlacklistManager
问题黑名单管理] + D2[UserBlacklistManager
用户黑名单管理] + D3[AbuseManager
举报管理] + end + + subgraph "数据层" + E1[(Blacklist表
黑名单记录)] + E2[(User表
用户信息)] + E3[(Record表
问答对记录)] + end + + A1 --> B1 + A2 --> B2 + + B1 --> C1 + B1 --> C2 + B1 --> C3 + B2 --> C1 + B2 --> C2 + + B1 --> D1 + B1 --> D2 + B1 --> D3 + B2 --> D3 + + D1 --> E1 + D2 --> E2 + D3 --> E1 + D3 --> E3 + + style A1 fill:#e3f2fd + style A2 fill:#f3e5f5 + style B1 fill:#fff3e0 + style B2 fill:#e8f5e9 + style D1 fill:#fce4ec + style D2 fill:#f1f8e9 + style D3 fill:#e0f2f1 + style E1 fill:#ede7f6 + style E2 fill:#fff9c4 + style E3 fill:#ffebee +``` + +### 问题检测流程 + +```mermaid +sequenceDiagram + participant User as 用户 + participant System as 业务系统 + participant QManager as QuestionBlacklistManager + participant DB as 数据库 + + User->>System: 提交问题 + System->>QManager: check_blacklisted_questions(question) + + QManager->>DB: 模糊查询黑名单 + Note right of QManager: WHERE question ILIKE '%input%'
AND isAudited = True + + alt 匹配到黑名单 + DB-->>QManager: 返回黑名单记录 + QManager->>QManager: 记录日志:问题在黑名单中 + QManager-->>System: 返回False + System-->>User: 拒绝处理并返回提示 + else 未匹配黑名单 + DB-->>QManager: 返回空结果 + QManager-->>System: 返回True + System->>System: 继续业务处理 + System-->>User: 正常响应 + end +``` + +## 安全机制 + +### 1. 权限隔离 + +- **管理员专属接口**: 用户黑名单、问题黑名单和举报审核功能仅对管理员开放,通过`verify_admin`依赖注入确保权限。 +- **用户举报保护**: 用户只能举报属于自己的问答对,系统通过用户标识和问答对ID双重验证防止恶意举报。 + +### 2. 数据安全 + +- **内容加密**: 问答对内容在数据库中加密存储,举报时需解密读取原始内容。 +- **白名单机制**: 白名单用户不受信用分限制,确保关键用户账户不会被误封。 + +### 3. 业务安全 + +- **幂等性保证**: 重复举报、重复封禁/解禁操作具有幂等性,避免重复操作导致数据异常。 +- **状态校验**: 执行操作前检查目标对象是否存在及当前状态,防止无效操作。 +- **边界保护**: 用户信用分限制在0-100范围内,防止数值溢出。 + +### 4. 审核机制 + +- **两阶段审核**: 用户举报不直接生效,需管理员审核后决定是否加入黑名单,防止恶意举报滥用。 +- **模糊匹配检测**: 黑名单问题检测采用模糊匹配(ILIKE),可拦截包含黑名单关键词的变体问题。 + +## 错误处理 + +### 常见错误场景 + +| 错误场景 | HTTP状态码 | 错误信息 | 处理建议 | +|---------|-----------|---------|---------| +| 用户不存在 | 500 | Change user blacklist error. | 检查user_sub是否正确 | +| 黑名单记录不存在 | 500 | Modify question blacklist error. | 检查黑名单ID是否有效 | +| 举报记录不合法 | 500 | Report abuse complaint error. | 确认问答对ID正确且属于当前用户 | +| 待审核问题不存在 | 500 | Audit abuse question error. | 检查record_id是否有效且为待审核状态 | +| 权限不足 | 403 | Forbidden | 确认用户具有管理员权限 | +| 会话过期 | 401 | Unauthorized | 重新登录获取会话令牌 | + +### 错误响应格式 + +```json +{ + "code": 500, + "message": "具体错误信息", + "result": {} +} +``` diff --git a/design/services/mcp_service.md b/design/services/mcp_service.md new file mode 100644 index 0000000000000000000000000000000000000000..265b6c74ebce99beacf197a6e9b400d917b96fae --- /dev/null +++ b/design/services/mcp_service.md @@ -0,0 +1,1407 @@ +# MCP服务管理模块文档 + +## 模块概述 + +MCP服务管理模块负责管理系统中的MCP(Model Context Protocol)服务生命周期, +包括服务的创建、更新、查询、删除、安装、激活等核心功能。该模块支持两种MCP服务类型: +SSE(Server-Sent Events)和STDIO(标准输入输出), +并提供了完善的服务状态管理机制。 + +### 核心职责 + +- **服务管理**:提供MCP服务的CRUD操作,支持SSE和STDIO两种通信类型 +- **权限控制**:实现基于作者身份和管理员权限的访问控制机制 +- **服务检索**:支持按关键字、名称、描述、作者等条件筛选服务 +- **状态追踪**:管理服务的安装、激活状态,确保服务正确运行 +- **工具管理**:维护MCP服务提供的工具列表,支持工具查询和更新 + +--- + +## 数据结构 + +### 服务卡片数据 + +用于服务列表展示的卡片信息: + +```json +{ + "mcpserviceId": "filesystem-service", + "icon": "/static/mcp/filesystem-service.png", + "name": "文件系统服务", + "description": "提供文件读写和目录操作能力", + "author": "admin@example.com", + "isActive": true, + "status": "ready" +} +``` + +### 服务详细信息(查看模式) + +普通用户查看服务详情: + +```json +{ + "serviceId": "filesystem-service", + "icon": "/static/mcp/filesystem-service.png", + "name": "文件系统服务", + "description": "提供文件读写和目录操作能力", + "overview": "这是一个功能强大的文件系统服务,支持常见的文件操作,包括读取、写入、删除和目录管理。", + "status": "ready", + "tools": [ + { + "id": "tool-001", + "mcpId": "filesystem-service", + "name": "read_file", + "description": "读取指定路径的文件内容", + "inputSchema": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "文件路径" + } + }, + "required": ["path"] + } + }, + { + "id": "tool-002", + "mcpId": "filesystem-service", + "name": "write_file", + "description": "写入内容到指定文件", + "inputSchema": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "文件路径" + }, + "content": { + "type": "string", + "description": "文件内容" + } + }, + "required": ["path", "content"] + } + } + ] +} +``` + +### 服务配置信息(编辑模式) + +管理员编辑服务配置: + +```json +{ + "serviceId": "database-connector", + "icon": "/static/mcp/database-connector.png", + "name": "数据库连接器", + "description": "提供数据库查询和操作能力", + "overview": "支持MySQL、PostgreSQL等主流数据库的连接和查询", + "mcpType": "sse", + "data": { + "database-connector": { + "url": "http://localhost:8080/sse", + "env": { + "DB_HOST": "localhost", + "DB_PORT": "5432" + } + } + } +} +``` + +### STDIO类型服务配置 + +```json +{ + "serviceId": "python-runtime", + "name": "Python运行时", + "description": "提供Python代码执行能力", + "overview": "支持Python脚本的安全执行和结果返回", + "mcpType": "stdio", + "data": { + "python-runtime": { + "command": "python", + "args": ["-m", "mcp_server", "--directory", "/path/to/project"], + "env": { + "PYTHONPATH": "/usr/local/lib/python3.9" + } + } + } +} +``` + +### 服务列表响应 + +```json +{ + "code": 200, + "message": "OK", + "result": { + "currentPage": 1, + "services": [ + { + "mcpserviceId": "filesystem-service", + "icon": "/static/mcp/filesystem-service.png", + "name": "文件系统服务", + "description": "提供文件读写和目录操作能力", + "author": "admin@example.com", + "isActive": true, + "status": "ready" + }, + { + "mcpserviceId": "database-connector", + "icon": "/static/mcp/database-connector.png", + "name": "数据库连接器", + "description": "提供数据库查询和操作能力", + "author": "admin@example.com", + "isActive": false, + "status": "init" + } + ] + } +} +``` + +--- + +## API接口定义 + +### 1. 获取MCP服务列表 + +**端点**: `GET /api/mcp` + +**权限**: 需要会话验证和个人令牌验证 + +**请求参数**: + +- `searchType` (SearchType, 可选): 搜索类型,枚举值:ALL/NAME/DESCRIPTION/AUTHOR,默认ALL +- `keyword` (string, 可选): 搜索关键字 +- `page` (integer, 必需): 页码,从1开始,最小值1 +- `isInstall` (boolean, 可选): 过滤已安装/未安装的服务 +- `isActive` (boolean, 可选): 过滤已激活/未激活的服务 + +**请求示例**: + +```http +GET /api/mcp?searchType=NAME&keyword=文件&page=1&isActive=true +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "currentPage": 1, + "services": [ + { + "mcpserviceId": "filesystem-service", + "icon": "/static/mcp/filesystem-service.png", + "name": "文件系统服务", + "description": "提供文件读写和目录操作能力", + "author": "admin@example.com", + "isActive": true, + "status": "ready" + } + ] + } +} +``` + +**错误响应**: + +```json +{ + "code": 500, + "message": "ERROR", + "result": {} +} +``` + +### 2. 创建或更新MCP服务 + +**端点**: `PUT /api/admin/mcp` + +**权限**: 需要管理员权限 + +**请求体(创建服务,无mcp_id)**: + +```json +{ + "name": "新MCP服务", + "description": "服务描述", + "overview": "详细介绍", + "mcp_type": "stdio", + "config": { + "new-service-id": { + "command": "node", + "args": ["server.js", "--directory", "/path/to/project"], + "env": { + "NODE_ENV": "production" + } + } + } +} +``` + +**请求体(更新服务,含mcp_id)**: + +```json +{ + "mcp_id": "existing-service", + "name": "更新的服务名称", + "description": "更新的描述", + "overview": "更新的详细介绍", + "mcp_type": "sse", + "config": { + "existing-service": { + "url": "http://localhost:9000/sse", + "env": { + "API_KEY": "new-key" + } + } + } +} +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "serviceId": "new-service-id", + "name": "新MCP服务" + } +} +``` + +**错误响应(创建失败)**: + +```json +{ + "code": 500, + "message": "MCP服务创建失败: 配置验证失败", + "result": {} +} +``` + +**错误响应(更新失败)**: + +```json +{ + "code": 500, + "message": "更新MCP服务失败: MCP服务未找到或无权限", + "result": {} +} +``` + +### 3. 安装/卸载MCP服务 + +**端点**: `POST /api/admin/mcp/{serviceId}/install` + +**权限**: 需要管理员权限 + +**请求参数**: + +- `serviceId` (string, 路径参数): 服务ID +- `install` (boolean, 查询参数): true表示安装,false表示卸载,默认true + +**请求示例**: + +```http +POST /api/admin/mcp/filesystem-service/install?install=true +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": {} +} +``` + +**错误响应**: + +```json +{ + "code": 500, + "message": "[MCPService] 安装mcp服务失败: MCP服务未找到或无权限", + "result": {} +} +``` + +### 4. 获取MCP服务详情 + +**端点**: `GET /api/admin/mcp/{serviceId}` + +**权限**: 需要管理员权限 + +**请求参数**: + +- `serviceId` (string, 路径参数): 服务ID +- `edit` (boolean, 查询参数): 是否为编辑模式,默认false + +**请求示例(查看模式)**: + +```http +GET /api/admin/mcp/filesystem-service?edit=false +``` + +**响应示例(查看模式)**: 参见"数据结构"章节中的服务详细信息(查看模式) + +**请求示例(编辑模式)**: + +```http +GET /api/admin/mcp/filesystem-service?edit=true +``` + +**响应示例(编辑模式)**: 参见"数据结构"章节中的服务配置信息(编辑模式) + +**错误响应(服务不存在)**: + +```json +{ + "code": 404, + "message": "MCP服务有关信息不存在", + "result": {} +} +``` + +### 5. 删除MCP服务 + +**端点**: `DELETE /api/admin/mcp/{serviceId}` + +**权限**: 需要管理员权限 + +**请求示例**: + +```http +DELETE /api/admin/mcp/filesystem-service +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "serviceId": "filesystem-service" + } +} +``` + +**错误响应**: + +```json +{ + "code": 500, + "message": "ERROR", + "result": {} +} +``` + +### 6. 上传服务图标 + +**端点**: `POST /api/admin/mcp/icon` + +**权限**: 需要管理员权限 + +**请求参数**: + +- `serviceId` (string, 路径参数): 服务ID +- `icon` (UploadFile, 表单数据): 图标文件 + +**请求示例**: + +```http +POST /api/admin/mcp/icon?serviceId=filesystem-service +Content-Type: multipart/form-data + +--boundary +Content-Disposition: form-data; name="icon"; filename="icon.png" +Content-Type: image/png + +[binary data] +--boundary-- +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "serviceId": "filesystem-service", + "url": "/static/mcp/filesystem-service.png" + } +} +``` + +**错误响应(文件大小超限)**: + +```json +{ + "code": 400, + "message": "图标文件为空或超过1MB", + "result": {} +} +``` + +**错误响应(服务不存在)**: + +```json +{ + "code": 403, + "message": "MCP服务未找到: filesystem-service", + "result": {} +} +``` + +### 7. 激活/取消激活MCP服务 + +**端点**: `POST /api/mcp/{mcpId}` + +**权限**: 需要会话验证和个人令牌验证 + +**请求参数**: + +- `mcpId` (string, 路径参数): MCP服务ID + +**请求体(激活)**: + +```json +{ + "active": true, + "mcp_env": { + "CUSTOM_VAR": "custom_value", + "API_ENDPOINT": "https://api.example.com" + } +} +``` + +**请求体(取消激活)**: + +```json +{ + "active": false +} +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "serviceId": "filesystem-service" + } +} +``` + +**错误响应(激活失败)**: + +```json +{ + "code": 500, + "message": "[MCPService] 激活mcp服务失败: MCP服务未准备就绪", + "result": {} +} +``` + +**错误响应(取消激活失败)**: + +```json +{ + "code": 500, + "message": "[MCPService] 取消激活mcp服务失败: MCP服务无进程", + "result": {} +} +``` + +--- + +## 核心业务流程 + +### 服务创建流程 + +```mermaid +sequenceDiagram + participant Client as 客户端 + participant Router as API路由层 + participant Manager as MCPServiceManager + participant DB as PostgreSQL + participant Loader as MCPLoader + participant FileSystem as 文件系统 + + Client->>Router: PUT /api/admin/mcp (无mcp_id) + Router->>Router: 验证管理员权限 + Router->>Manager: create_mcpservice(data, user_sub) + Manager->>Manager: 验证配置类型 + alt mcp_type为SSE + Manager->>Manager: MCPServerSSEConfig.model_validate() + else mcp_type为STDIO + Manager->>Manager: MCPServerStdioConfig.model_validate() + end + Manager->>Manager: clean_name(data.name) + Note over Manager: 清除名称中的特殊字符 + Manager->>Manager: 构建MCPServerConfig对象 + Manager->>DB: 查询是否存在同名服务 + alt 存在同名服务 + DB-->>Manager: 返回已存在服务 + Manager->>Manager: name添加随机后缀 + Note over Manager: name-{6位随机字符} + end + alt config为STDIO类型 + Manager->>Manager: 处理--directory参数 + Note over Manager: 添加或更新项目路径
指向template目录 + end + Manager->>DB: 插入MCPInfo记录 + Manager->>Loader: save_one(mcp_id, config) + Loader->>FileSystem: 保存配置到JSON文件 + Manager->>Loader: update_template_status(mcp_id, INIT) + Loader->>DB: 更新服务状态为INIT + Manager-->>Router: 返回mcp_id + Router-->>Client: 200 OK +``` + +### 服务更新流程 + +```mermaid +sequenceDiagram + participant Client as 客户端 + participant Router as API路由层 + participant Manager as MCPServiceManager + participant DB as PostgreSQL + participant Loader as MCPLoader + participant Pool as MCP连接池 + + Client->>Router: PUT /api/admin/mcp (含mcp_id) + Router->>Router: 验证管理员权限 + Router->>Manager: update_mcpservice(data, user_sub) + + alt mcp_id为空 + Manager-->>Router: 抛出ValueError + Router-->>Client: 500 INTERNAL_SERVER_ERROR + end + + Manager->>DB: 查询服务并验证作者 + alt 服务不存在或作者不匹配 + DB-->>Manager: 返回空或非匹配记录 + Manager-->>Router: 抛出ValueError + Router-->>Client: 500 INTERNAL_SERVER_ERROR + else 验证通过 + DB-->>Manager: 返回服务信息 + Manager->>DB: 查询所有激活该服务的用户 + DB-->>Manager: 返回activated_users列表 + + loop 对每个激活用户 + Manager->>Manager: deactive_mcpservice(user_sub, mcp_id) + Manager->>Pool: stop(mcp_id, user_sub) + Note over Pool: 停止用户的MCP进程 + Manager->>Loader: user_deactive_template(user_sub, mcp_id) + end + + Manager->>Manager: 构建新的MCPServerConfig + Manager->>DB: 更新MCPInfo记录 + Manager->>Loader: save_one(mcp_id, config) + Manager->>Loader: update_template_status(mcp_id, INIT) + Manager-->>Router: 返回mcp_id + Router-->>Client: 200 OK + end +``` + +### 服务安装流程 + +```mermaid +sequenceDiagram + participant Client as 客户端 + participant Router as API路由层 + participant Manager as MCPServiceManager + participant DB as PostgreSQL + participant Loader as MCPLoader + participant Installer as 安装器 + + Client->>Router: POST /api/admin/mcp/{serviceId}/install?install=true + Router->>Router: 验证管理员权限 + Router->>Manager: install_mcpservice(user_sub, service_id, install=true) + Manager->>DB: 查询服务并验证作者权限 + + alt 服务不存在或无权限 + DB-->>Manager: 返回空 + Manager-->>Router: 抛出ValueError + Router-->>Client: 500 INTERNAL_SERVER_ERROR + else install=true + DB-->>Manager: 返回服务信息 + alt 服务已在安装中 + Manager-->>Router: 抛出RuntimeError + Router-->>Client: 500 INTERNAL_SERVER_ERROR + else 可以安装 + Manager->>Loader: get_config(service_id) + Loader-->>Manager: 返回服务配置 + Manager->>Loader: init_one_template(mcp_id, config) + Note over Loader: 启动异步安装任务
创建目录/下载依赖/初始化 + Manager-->>Router: 安装已启动 + Router-->>Client: 200 OK + end + else install=false + DB-->>Manager: 返回服务信息 + alt 服务不在安装中 + Manager-->>Router: 抛出RuntimeError + Router-->>Client: 500 INTERNAL_SERVER_ERROR + else 可以卸载 + Manager->>Loader: cancel_installing_task([service_id]) + Note over Loader: 取消安装任务
清理临时文件 + Manager-->>Router: 卸载完成 + Router-->>Client: 200 OK + end + end +``` + +### 服务激活流程 + +```mermaid +sequenceDiagram + participant Client as 客户端 + participant Router as API路由层 + participant Manager as MCPServiceManager + participant DB as PostgreSQL + participant Loader as MCPLoader + participant Pool as MCP连接池 + + Client->>Router: POST /api/mcp/{mcpId} (active=true) + Router->>Router: 验证用户身份 + Router->>Manager: active_mcpservice(user_sub, mcp_id, mcp_env) + Manager->>DB: 查询MCPInfo记录 + + alt 服务不存在 + DB-->>Manager: 返回空 + Manager-->>Router: 抛出ValueError + Router-->>Client: 500 INTERNAL_SERVER_ERROR + else 服务状态不是READY + DB-->>Manager: 返回服务信息(status!=READY) + Manager-->>Router: 抛出RuntimeError + Router-->>Client: 500 INTERNAL_SERVER_ERROR + else 可以激活 + DB-->>Manager: 返回服务信息(status=READY) + Manager->>DB: 插入或更新MCPActivated记录 + Note over DB: mcpId + userSub为复合键 + Manager->>Loader: user_active_template(user_sub, mcp_id, mcp_env) + Loader->>FileSystem: 创建用户专属配置文件 + Note over FileSystem: 合并用户环境变量
生成用户配置 + Loader->>Pool: 启动用户的MCP进程 + Note over Pool: 根据配置类型启动
SSE或STDIO连接 + Manager-->>Router: 激活完成 + Router-->>Client: 200 OK + end +``` + +### 服务查询流程 + +```mermaid +flowchart TD + Start([客户端请求]) --> Auth[验证用户身份] + Auth --> ParseParams[解析查询参数] + + ParseParams --> BuildQuery[构建基础SQL查询] + BuildQuery --> SearchType{检查搜索类型} + + SearchType -->|ALL| AllFields[name/description/author
模糊匹配] + SearchType -->|NAME| NameField[仅name模糊匹配] + SearchType -->|DESCRIPTION| DescField[仅description模糊匹配] + SearchType -->|AUTHOR| AuthorField[仅author模糊匹配] + + AllFields --> InstallFilter{是否过滤安装状态} + NameField --> InstallFilter + DescField --> InstallFilter + AuthorField --> InstallFilter + + InstallFilter -->|isInstall=true| InstalledQuery[添加子查询:
mcpId在MCPActivated表中] + InstallFilter -->|isInstall=false| ActiveFilter + InstallFilter -->|isInstall=null| ActiveFilter + + InstalledQuery --> ActiveFilter{是否过滤激活状态} + + ActiveFilter -->|isActive=true| ActiveQuery[添加子查询:
mcpId在MCPActivated表中] + ActiveFilter -->|isActive=false| Pagination + ActiveFilter -->|isActive=null| Pagination + + ActiveQuery --> Pagination[应用分页
offset和limit] + + Pagination --> ExecuteQuery[执行数据库查询] + ExecuteQuery --> CheckResult{是否有结果} + + CheckResult -->|无结果| EmptyList[返回空列表] + CheckResult -->|有结果| LoopServices[遍历服务列表] + + LoopServices --> LoadIcon[获取图标路径] + LoadIcon --> CheckActive[检查用户激活状态] + CheckActive --> GetStatus[获取服务安装状态] + GetStatus --> BuildCard[构建服务卡片对象] + BuildCard --> NextService{还有其他服务} + + NextService -->|是| LoopServices + NextService -->|否| ReturnList[返回服务卡片列表] + + EmptyList --> End([返回结果]) + ReturnList --> End +``` + +### 服务删除流程 + +```mermaid +stateDiagram-v2 + [*] --> 验证管理员权限 + 验证管理员权限 --> 调用delete_mcpservice: 权限验证通过 + 验证管理员权限 --> 返回403错误: 权限不足 + + 调用delete_mcpservice --> 删除MCP配置文件 + 删除MCP配置文件 --> 删除模板目录 + 删除模板目录 --> 查询应用关联记录 + + 查询应用关联记录 --> 删除AppMCP记录: 存在关联 + 查询应用关联记录 --> 删除MCPInfo记录: 无关联 + + 删除AppMCP记录 --> 删除MCPInfo记录 + 删除MCPInfo记录 --> 删除MCPActivated记录 + 删除MCPActivated记录 --> 删除MCPTools记录 + 删除MCPTools记录 --> 提交事务 + + 提交事务 --> [*]: 删除成功 + 返回403错误 --> [*] +``` + +--- + +## 核心方法说明 + +### is_active + +判断指定用户是否已激活指定MCP服务。 + +- **功能描述**: 检查用户对MCP服务的激活状态 +- **查询逻辑**: 在MCPActivated表中查找同时匹配mcpId和userSub的记录 +- **返回值**: 存在记录返回True,否则返回False +- **使用场景**: 服务列表展示时标注激活状态,权限验证 + +### get_icon_path + +获取MCP服务图标的访问路径。 + +- **功能描述**: 生成服务图标的URL路径 +- **查找逻辑**: 检查图标存储目录下是否存在以服务ID命名的PNG文件 +- **路径格式**: `/static/mcp/{mcp_id}.png` +- **返回值**: 图标存在返回完整路径,否则返回空字符串 +- **注意事项**: 仅检查PNG格式图标 + +### get_service_status + +获取MCP服务的当前安装状态。 + +- **功能描述**: 查询服务在系统中的安装状态 +- **状态枚举**: INIT(初始化)、INSTALLING(安装中)、READY(就绪)、FAILED(失败) +- **查询逻辑**: 从MCPInfo表中读取status字段 +- **默认值**: 服务不存在时返回FAILED状态 +- **使用场景**: 判断服务是否可以被激活或安装 + +### fetch_mcp_services + +获取符合条件的MCP服务列表,支持多维度筛选和分页。 + +- **功能描述**: 查询并构建服务卡片列表 +- **执行步骤**: + 1. 调用内部搜索方法获取数据库记录 + 2. 遍历每条记录并构建MCPServiceCardItem对象 + 3. 为每个服务加载图标、激活状态和安装状态 +- **过滤维度**: + - **搜索类型**: 全部字段、名称、描述、作者 + - **关键字**: 模糊匹配相应字段 + - **安装状态**: 已安装或未安装 + - **激活状态**: 已激活或未激活 +- **分页支持**: 根据页码计算偏移量并限制返回数量 +- **使用场景**: API接口返回服务列表 + +### get_mcp_service + +获取MCP服务的基本信息记录。 + +- **功能描述**: 从数据库查询服务的基本信息 +- **返回数据**: MCPInfo模型对象,包含id、name、description、author等字段 +- **返回值**: 服务存在返回对象,否则返回None +- **使用场景**: 服务详情查询、权限验证的前置步骤 + +### get_mcp_config + +获取MCP服务的完整配置信息。 + +- **功能描述**: 从配置文件加载服务的详细配置 +- **执行步骤**: 调用MCPLoader的get_config方法读取JSON配置文件 +- **返回数据**: MCPServerConfig对象和图标路径的元组 +- **配置类型**: 根据mcpType字段可能是SSEConfig或StdioConfig +- **使用场景**: 服务编辑、服务安装时获取配置 + +### get_mcp_tools + +获取MCP服务提供的工具列表。 + +- **功能描述**: 查询服务注册的所有工具 +- **查询逻辑**: 从MCPTools表中筛选指定mcpId的所有记录 +- **返回数据**: MCPTools对象列表,包含工具名称、描述、输入模式等信息 +- **使用场景**: 服务详情页面展示工具列表,智能体选择可用工具 + +### _search_mcpservice + +内部方法:根据多个条件构建并执行MCP服务的搜索查询。 + +- **功能描述**: 构建复杂的数据库查询并执行 +- **查询构建**: + 1. 创建基础SELECT语句 + 2. 根据搜索类型添加WHERE条件 + 3. 根据过滤条件添加子查询 + 4. 应用分页限制 +- **搜索类型处理**: + - ALL: 使用OR连接name、description、author三个字段的LIKE条件 + - NAME: 仅匹配name字段 + - DESCRIPTION: 仅匹配description字段 + - AUTHOR: 仅匹配author字段 +- **安装状态过滤**: 通过子查询检查mcpId是否在MCPActivated表中 +- **激活状态过滤**: 同样使用子查询,并额外过滤userSub +- **返回结果**: MCPInfo对象列表或空列表 + +### create_mcpservice + +创建新的MCP服务。 + +- **功能描述**: 创建并初始化新的MCP服务 +- **执行步骤**: + 1. 根据mcp_type验证并构建配置对象 + 2. 清理服务名称中的特殊字符 + 3. 检查是否存在同名服务,如有则添加随机后缀 + 4. 对于STDIO类型服务,处理--directory参数 + 5. 保存配置到文件系统 + 6. 在数据库中插入MCPInfo记录 + 7. 更新服务状态为INIT +- **配置类型**: + - SSE: 需要提供url和可选的env + - STDIO: 需要提供command、args和可选的env +- **项目路径处理**: STDIO类型会自动添加或更新--directory参数指向模板目录 +- **返回值**: 新创建的服务ID + +### update_mcpservice + +更新已存在的MCP服务配置。 + +- **功能描述**: 修改服务的配置信息 +- **验证检查**: + 1. 确保mcp_id不为空 + 2. 验证服务存在且当前用户是作者 +- **执行步骤**: + 1. 查询所有激活该服务的用户 + 2. 依次取消所有用户的激活状态 + 3. 停止所有相关的MCP进程 + 4. 更新配置文件和数据库记录 + 5. 重置服务状态为INIT +- **重要影响**: 更新操作会强制取消所有用户的激活,需要用户重新激活 +- **返回值**: 服务ID + +### delete_mcpservice + +删除MCP服务及其关联数据。 + +- **功能描述**: 完整删除服务及其依赖 +- **执行步骤**: + 1. 调用MCPLoader删除配置文件和模板目录 + 2. 删除AppMCP表中的应用关联记录 + 3. 数据库级联删除MCPInfo、MCPActivated、MCPTools等记录 +- **删除范围**: + - 文件系统:配置JSON文件、模板项目目录、用户配置文件 + - 数据库:服务基本信息、激活记录、工具列表、应用关联 +- **注意事项**: 删除操作不可逆,会影响所有使用该服务的应用 + +### active_mcpservice + +激活用户的MCP服务实例。 + +- **功能描述**: 为指定用户启动MCP服务 +- **执行步骤**: + 1. 查询服务基本信息并验证存在性 + 2. 检查服务状态是否为READY + 3. 在数据库中记录激活关系 + 4. 调用Loader创建用户专属配置 + 5. 启动MCP进程并加入连接池 +- **环境变量处理**: 合并系统配置和用户提供的mcp_env参数 +- **状态要求**: 只有READY状态的服务可以被激活 +- **异常情况**: + - 服务不存在: 抛出ValueError + - 服务未就绪: 抛出RuntimeError + +### deactive_mcpservice + +取消用户的MCP服务激活状态。 + +- **功能描述**: 停止用户的MCP服务实例 +- **执行步骤**: + 1. 从MCP连接池中停止对应进程 + 2. 调用Loader删除用户配置文件 + 3. 数据库中保留激活记录(供安装状态查询) +- **异常处理**: 如果进程不存在,记录警告但不抛出异常 +- **使用场景**: 用户主动取消激活、服务更新时强制取消激活 + +### clean_name + +清理服务名称中的非法字符。 + +- **功能描述**: 移除或替换文件系统不支持的字符 +- **替换规则**: 将 `\ / : * ? " < > |` 等特殊字符替换为下划线 +- **正则表达式**: `r'[\\\/:*?"<>|]'` +- **使用场景**: 创建服务时处理用户输入的名称,确保可以作为文件名使用 + +### save_mcp_icon + +上传并保存MCP服务的图标文件。 + +- **功能描述**: 处理图标上传、验证和存储 +- **执行步骤**: + 1. 检查MIME类型是否在允许列表中 + 2. 使用PIL打开图像并转换为RGB模式 + 3. 调整图像尺寸为64x64像素 + 4. 压缩并保存为PNG格式 +- **验证规则**: + - 文件大小: 不超过1MB + - 格式类型: 必须在ALLOWED_ICON_MIME_TYPES中 +- **存储路径**: `{MCP_ICON_PATH}/{mcp_id}.png` +- **返回值**: 图标的访问URL路径 + +### is_user_actived + +判断用户是否已激活指定MCP服务(与is_active功能相同)。 + +- **功能描述**: 检查用户激活状态 +- **实现方式**: 与is_active方法完全相同的查询逻辑 +- **返回值**: 布尔值表示激活状态 +- **注意**: 此方法为冗余方法,功能与is_active重复 + +### query_mcp_tools + +查询MCP服务的工具列表(与get_mcp_tools功能相同)。 + +- **功能描述**: 获取服务提供的工具信息 +- **实现方式**: 与get_mcp_tools方法完全相同的查询逻辑 +- **返回值**: MCPTools对象列表 +- **注意**: 此方法为冗余方法,功能与get_mcp_tools重复 + +### install_mcpservice + +安装或卸载MCP服务(管理员操作)。 + +- **功能描述**: 控制服务的安装状态 +- **验证步骤**: + 1. 查询服务是否存在 + 2. 验证当前用户是否为作者 +- **安装分支**: + - 检查服务是否已在安装中,避免重复安装 + - 调用Loader的init_one_template方法启动异步安装任务 + - 安装任务包括:下载依赖、初始化环境、更新状态 +- **卸载分支**: + - 检查服务是否在安装中,只能卸载安装中的服务 + - 调用Loader的cancel_installing_task方法取消安装任务 + - 清理已创建的临时文件和目录 +- **异常情况**: + - 服务不存在或无权限: ValueError + - 状态不符合操作要求: RuntimeError + +--- + +## 数据模型关系 + +```mermaid +erDiagram + MCPInfo ||--o{ MCPActivated : "被用户激活" + MCPInfo ||--o{ MCPTools : "提供工具" + MCPInfo ||--o{ AppMCP : "被应用使用" + User ||--o{ MCPInfo : "创建" + User ||--o{ MCPActivated : "激活服务" + App ||--o{ AppMCP : "使用MCP服务" + + MCPInfo { + string id PK + string name + string description + string overview + string author "FK" + enum mcpType + enum status + datetime createdAt + datetime updatedAt + } + + MCPActivated { + string mcpId PK "FK" + string userSub PK "FK" + json env + datetime activatedAt + } + + MCPTools { + string id PK + string mcpId "FK" + string name + string description + json inputSchema + datetime createdAt + } + + AppMCP { + uuid appId PK "FK" + string mcpId PK "FK" + datetime createdAt + } + + User { + string userSub PK + string email + boolean isAdmin + } + + App { + uuid id PK + string name + string author "FK" + enum appType + } +``` + +--- + +## 服务状态机 + +```mermaid +stateDiagram-v2 + [*] --> INIT: 创建服务 + INIT --> INSTALLING: 管理员触发安装 + INSTALLING --> READY: 安装成功 + INSTALLING --> FAILED: 安装失败 + INSTALLING --> INIT: 取消安装 + FAILED --> INSTALLING: 重新安装 + READY --> INIT: 更新服务配置 + INIT --> READY: 直接标记就绪(无需安装) + + note right of INIT + 服务已创建,配置已保存 + 不可被用户激活 + end note + + note right of INSTALLING + 异步安装任务运行中 + 下载依赖、初始化环境 + end note + + note right of READY + 服务就绪,可被用户激活 + 可以启动MCP进程 + end note + + note right of FAILED + 安装失败或运行异常 + 需要管理员排查问题 + end note +``` + +--- + +## 核心业务规则 + +### 权限控制规则 + +#### 管理员权限 + +以下操作需要管理员权限(admin_router) + +- 创建MCP服务 +- 更新MCP服务配置 +- 删除MCP服务 +- 安装/卸载服务 +- 上传服务图标 +- 获取服务详情(含编辑模式) + +#### 作者权限 + +只有服务作者可以执行 + +- 更新服务时,验证author字段匹配当前用户 +- 删除服务时,验证author字段匹配当前用户 +- 安装/卸载时,验证author字段匹配当前用户 + +#### 普通用户权限 + +所有用户可以执行 + +- 查询服务列表 +- 激活/取消激活服务 + +### 服务状态规则 + +#### 安装规则 + +- INIT状态的服务可以触发安装,进入INSTALLING状态 +- INSTALLING状态的服务不能重复安装 +- FAILED状态的服务可以重新安装 +- 安装成功后自动转为READY状态 + +#### 激活规则 + +- 只有READY状态的服务可以被用户激活 +- INIT、INSTALLING、FAILED状态的服务激活会抛出异常 +- 激活时会创建用户专属配置和进程 + +#### 更新规则 + +- 更新服务配置时,必须先取消所有用户的激活状态 +- 更新后服务状态重置为INIT +- 需要重新安装才能使用 + +### 名称唯一性规则 + +#### 同名处理 + +- 创建服务时检查name字段是否重复 +- 如果存在同名服务,自动在名称后添加6位随机字符 +- 格式:`{原名称}-{6位hex}` +- 确保服务名称在系统中的唯一性 + +### 图标管理规则 + +#### 文件限制 + +- 文件大小:不超过1MB +- 文件格式:仅支持ALLOWED_ICON_MIME_TYPES中的MIME类型 +- 图片尺寸:自动调整为64x64像素 +- 存储格式:统一转换为PNG格式 + +#### 存储策略 + +- 文件名:使用服务ID作为文件名 +- 存储路径:`{ICON_PATH}/mcp/{mcp_id}.png` +- 访问路径:`/static/mcp/{mcp_id}.png` +- 覆盖策略:上传新图标会覆盖旧图标 + +--- + +## 配置类型说明 + +### SSE类型配置 + +Server-Sent Events类型的MCP服务通过HTTP连接实现: + +#### SSE配置字段 + +- `url`: 服务端点地址,必需 +- `env`: 环境变量字典,可选 + +#### SSE使用场景 + +- 远程MCP服务 +- 云端服务集成 +- 跨网络的服务调用 + +#### 连接方式 + +- 客户端向指定URL发起HTTP连接 +- 服务端通过SSE协议推送事件 +- 支持断线重连 + +### STDIO类型配置 + +标准输入输出类型的MCP服务通过进程通信实现: + +#### STDIO配置字段 + +- `command`: 启动命令,必需(如python、node) +- `args`: 命令参数列表,必需 +- `env`: 环境变量字典,可选 + +#### STDIO使用场景 + +- 本地MCP服务 +- Python/Node.js脚本服务 +- 需要文件系统访问的服务 + +#### 进程管理 + +- 系统为每个用户启动独立进程 +- 通过stdin/stdout进行通信 +- 进程生命周期由MCP连接池管理 + +#### 目录参数处理 + +- 系统自动处理`--directory`参数 +- 查找args列表中的--directory标志 +- 如果存在,更新下一个参数为模板项目路径 +- 如果不存在,自动追加--directory和路径 + +--- + +## 异常处理机制 + +### ValueError异常 + +在以下场景中抛出值错误异常: + +- MCP服务ID为空(更新操作) +- 服务不存在 +- 服务未找到或无权限(非作者尝试操作) +- 图标格式不支持 +- 激活服务时服务不存在 + +### RuntimeError异常 + +在以下场景中抛出运行时错误异常: + +- 激活服务时服务状态不是READY +- 安装时服务已在安装中 +- 卸载时服务不在安装中状态 + +### HTTPException异常 + +在以下场景中抛出HTTP异常: + +- 上传图标时服务不存在(403 FORBIDDEN) + +### 通用异常 + +其他未预期的异常会被路由层捕获,统一返回500内部服务器错误响应, +并记录详细的异常日志用于排查问题。 +日志格式:`[MCPServiceManager] 操作描述失败: {异常信息}` + +--- + +## 文件系统布局 + +```text +{MCP_PATH}/ +├── template/ # 服务模板目录 +│ ├── {mcp_id}/ # 单个服务目录 +│ │ ├── config.json # 服务配置文件 +│ │ └── project/ # 服务项目文件 +│ │ ├── package.json # 依赖配置(Node.js) +│ │ ├── requirements.txt # 依赖配置(Python) +│ │ └── src/ # 源代码 +│ └── ... +└── user/ # 用户配置目录 + ├── {user_sub}/ # 单个用户目录 + │ ├── {mcp_id}.json # 用户专属配置 + │ └── ... + └── ... + +{ICON_PATH}/ +└── mcp/ # MCP图标目录 + ├── {mcp_id}.png # 服务图标文件 + └── ... +``` + +### 目录说明 + +- **template目录**: 存储服务的模板配置和项目文件 + - config.json: 包含MCPServerConfig的JSON序列化数据 + - project/: 服务的实际项目代码和依赖 + +- **user目录**: 存储用户激活服务时的个性化配置 + - 每个用户一个子目录 + - 配置文件包含合并后的环境变量 + +- **mcp图标目录**: 统一存储所有MCP服务的图标文件 + - 64x64像素的PNG格式 + - 文件名为服务ID + +--- + +## 与其他模块的交互 + +```mermaid +graph TB + Router[API路由层] --> MCPServiceMgr[MCP服务管理器] + Router --> Auth[身份验证模块] + + MCPServiceMgr --> MCPLoader[MCP加载器] + MCPServiceMgr --> PostgreSQL[(PostgreSQL数据库)] + MCPServiceMgr --> MCPPool[MCP连接池] + + MCPLoader --> FileSystem[(文件系统)] + MCPLoader --> Installer[安装器模块] + + MCPPool --> SSEClient[SSE客户端] + MCPPool --> StdioProc[STDIO进程] + + AppCenter[应用中心] --> MCPServiceMgr + AgentService[智能体服务] --> MCPServiceMgr + + Auth --> Session[会话验证] + Auth --> Token[令牌验证] + Auth --> Admin[管理员验证] + + style MCPServiceMgr fill:#e1f5ff + style PostgreSQL fill:#ffe1e1 + style FileSystem fill:#fff4e1 + style MCPPool fill:#e1ffe1 +``` + +### 依赖的外部模块 + +- **MCPLoader**: 管理配置文件的读写、服务模板的初始化和用户配置的生成 +- **MCP连接池**: 管理SSE连接和STDIO进程,提供启动和停止接口 +- **PostgreSQL**: 持久化服务信息、激活记录、工具列表等数据 +- **安装器模块**: 执行异步安装任务,下载依赖并初始化环境 +- **身份验证模块**: 验证用户会话、个人令牌和管理员权限 + +### 被依赖的场景 + +- **应用中心服务**: 智能体应用创建时验证MCP服务激活状态 +- **智能体服务**: 执行智能体对话时调用MCP服务提供的工具 +- **工作流服务**: 工作流节点中可能使用MCP工具 +- **管理界面**: 展示服务列表、管理服务配置 + +--- + +## 工具管理机制 + +### 工具注册流程 + +MCP服务启动后,通过协议自动注册工具到MCPTools表: + +1. MCP进程启动并连接 +2. 服务端发送工具列表声明 +3. 系统解析工具信息并存储 +4. 每个工具包含名称、描述和输入模式 + +### 工具查询接口 + +系统提供两个查询方法(功能相同): + +- `get_mcp_tools(mcp_id)`: 查询指定服务的工具列表 +- `query_mcp_tools(mcp_id)`: 功能同上(冗余方法) + +### 工具数据结构 + +每个工具包含以下字段: + +- **id**: 工具唯一标识 +- **mcpId**: 所属服务ID +- **name**: 工具名称(如read_file) +- **description**: 功能描述 +- **inputSchema**: JSON Schema格式的输入参数定义 + +### 工具生命周期 + +- **创建**: 服务首次启动并注册时创建 +- **更新**: 服务重新启动时可能更新工具列表 +- **删除**: 服务删除时级联删除所有工具记录 diff --git a/design/services/service.md b/design/services/service.md new file mode 100644 index 0000000000000000000000000000000000000000..8de26447a9260a611858c5643d9473753f88d1d2 --- /dev/null +++ b/design/services/service.md @@ -0,0 +1,1443 @@ +# 语义接口中心管理模块文档 + +## 模块概述 + +语义接口中心管理模块负责管理系统中的OpenAPI服务生命周期,包括服务的创建、更新、查询、删除、收藏等核心功能。该模块支持基于OpenAPI规范的服务注册和管理,提供了完善的权限控制和服务检索机制。 + +### 核心职责 + +- **服务管理**:提供OpenAPI服务的CRUD操作,支持上传和解析OpenAPI规范文档 +- **权限控制**:实现基于作者身份和管理员权限的访问控制机制 +- **服务检索**:支持按关键字、名称、描述、作者等条件筛选服务,支持分页查询 +- **收藏管理**:维护用户对服务的收藏状态,支持个性化服务列表 +- **API解析**:解析OpenAPI规范文档,提取服务元数据和API列表 + +--- + +## 数据结构 + +### 服务卡片数据 + +用于服务列表展示的卡片信息: + +```json +{ + "serviceId": "550e8400-e29b-41d4-a716-446655440000", + "icon": "", + "name": "天气查询服务", + "description": "提供实时天气查询和预报功能", + "author": "user@example.com", + "favorited": true +} +``` + +### 服务详细信息(查看模式) + +普通用户查看服务API列表: + +```json +{ + "serviceId": "550e8400-e29b-41d4-a716-446655440000", + "name": "天气查询服务", + "apis": [ + { + "name": "getCurrentWeather", + "path": "GET /weather/current", + "description": "获取当前天气信息" + }, + { + "name": "getForecast", + "path": "GET /weather/forecast", + "description": "获取未来7天天气预报" + }, + { + "name": "getHistoricalData", + "path": "GET /weather/history", + "description": "查询历史天气数据" + } + ] +} +``` + +### 服务配置信息(编辑模式) + +管理员编辑服务配置时获取完整OpenAPI文档: + +```json +{ + "serviceId": "550e8400-e29b-41d4-a716-446655440000", + "name": "天气查询服务", + "data": { + "openapi": "3.1.0", + "info": { + "title": "天气查询服务", + "version": "1.0.0", + "description": "提供实时天气查询和预报功能" + }, + "servers": [ + { + "url": "https://api.weather.example.com/v1" + } + ], + "paths": { + "/weather/current": { + "get": { + "operationId": "getCurrentWeather", + "summary": "获取当前天气信息", + "parameters": [ + { + "name": "city", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "成功返回天气信息" + } + } + } + } + } + } +} +``` + +### 服务列表响应 + +```json +{ + "code": 200, + "message": "OK", + "result": { + "currentPage": 1, + "totalCount": 25, + "services": [ + { + "serviceId": "550e8400-e29b-41d4-a716-446655440000", + "icon": "", + "name": "天气查询服务", + "description": "提供实时天气查询和预报功能", + "author": "user@example.com", + "favorited": true + }, + { + "serviceId": "660e8400-e29b-41d4-a716-446655440001", + "icon": "", + "name": "地图导航服务", + "description": "提供地图搜索和路线规划功能", + "author": "admin@example.com", + "favorited": false + } + ] + } +} +``` + +### 服务创建/更新请求 + +```json +{ + "service_id": "550e8400-e29b-41d4-a716-446655440000", + "data": { + "openapi": "3.1.0", + "info": { + "title": "新服务名称", + "version": "1.0.0", + "description": "服务描述信息" + }, + "servers": [ + { + "url": "https://api.example.com/v1" + } + ], + "paths": { + "/endpoint": { + "get": { + "operationId": "operationName", + "summary": "操作描述" + } + } + } + } +} +``` + +### 收藏状态修改请求 + +```json +{ + "favorited": true +} +``` + +--- + +## API接口定义 + +### 1. 获取服务列表 + +**端点**: `GET /api/service` + +**权限**: 需要会话验证和个人令牌验证 + +**请求参数**: + +- `createdByMe` (boolean, 可选): 筛选我创建的服务,默认false +- `favorited` (boolean, 可选): 筛选我收藏的服务,默认false +- `searchType` (SearchType, 可选): 搜索类型,枚举值:ALL/NAME/DESCRIPTION/AUTHOR,默认ALL +- `keyword` (string, 可选): 搜索关键字 +- `page` (integer, 必需): 页码,从1开始 +- `pageSize` (integer, 可选): 每页数量,默认16 + +**参数约束**: + +- `createdByMe`和`favorited`不能同时为true +- 页码最小值为1 + +**请求示例**: + +```http +GET /api/service?createdByMe=false&favorited=true&searchType=NAME&keyword=天气&page=1&pageSize=10 +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "currentPage": 1, + "totalCount": 3, + "services": [ + { + "serviceId": "550e8400-e29b-41d4-a716-446655440000", + "icon": "", + "name": "天气查询服务", + "description": "提供实时天气查询和预报功能", + "author": "user@example.com", + "favorited": true + } + ] + } +} +``` + +**错误响应(无效参数)**: + +```json +{ + "code": 400, + "message": "INVALID_PARAMETER", + "result": {} +} +``` + +**错误响应(服务器错误)**: + +```json +{ + "code": 500, + "message": "ERROR", + "result": {} +} +``` + +### 2. 创建或更新服务 + +**端点**: `POST /api/admin/service` + +**权限**: 需要管理员权限 + +**请求体(创建服务,无service_id)**: + +```json +{ + "data": { + "openapi": "3.1.0", + "info": { + "title": "新服务", + "version": "1.0.0", + "description": "新服务描述" + }, + "servers": [ + { + "url": "https://api.newservice.com" + } + ], + "paths": { + "/resource": { + "get": { + "operationId": "getResource", + "summary": "获取资源" + } + } + } + } +} +``` + +**请求体(更新服务,含service_id)**: + +```json +{ + "service_id": "550e8400-e29b-41d4-a716-446655440000", + "data": { + "openapi": "3.1.0", + "info": { + "title": "更新后的服务名", + "version": "2.0.0", + "description": "更新后的描述" + }, + "servers": [ + { + "url": "https://api.updatedservice.com" + } + ], + "paths": { + "/resource": { + "get": { + "operationId": "getResource", + "summary": "获取资源" + } + } + } + } +} +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "serviceId": "550e8400-e29b-41d4-a716-446655440000", + "name": "新服务", + "apis": [ + { + "name": "getResource", + "path": "GET /resource", + "description": "获取资源" + } + ] + } +} +``` + +**错误响应(创建失败)**: + +```json +{ + "code": 500, + "message": "OpenAPI解析错误: Invalid schema format", + "result": {} +} +``` + +**错误响应(权限不足)**: + +```json +{ + "code": 403, + "message": "未授权访问", + "result": {} +} +``` + +### 3. 获取服务详情 + +**端点**: `GET /api/service/{serviceId}` + +**权限**: 需要会话验证和个人令牌验证 + +**请求参数**: + +- `serviceId` (UUID, 路径参数): 服务ID +- `edit` (boolean, 查询参数): 是否为编辑模式,默认false + +**请求示例(查看模式)**: + +```http +GET /api/service/550e8400-e29b-41d4-a716-446655440000?edit=false +``` + +**响应示例(查看模式)**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "serviceId": "550e8400-e29b-41d4-a716-446655440000", + "name": "天气查询服务", + "apis": [ + { + "name": "getCurrentWeather", + "path": "GET /weather/current", + "description": "获取当前天气信息" + } + ] + } +} +``` + +**请求示例(编辑模式)**: + +```http +GET /api/service/550e8400-e29b-41d4-a716-446655440000?edit=true +``` + +**响应示例(编辑模式)**: 返回完整OpenAPI文档数据 + +**错误响应(权限不足)**: + +```json +{ + "code": 403, + "message": "未授权访问", + "result": {} +} +``` + +**错误响应(服务器错误)**: + +```json +{ + "code": 500, + "message": "ERROR", + "result": {} +} +``` + +### 4. 删除服务 + +**端点**: `DELETE /api/admin/service/{serviceId}` + +**权限**: 需要管理员权限 + +**请求参数**: + +- `serviceId` (UUID, 路径参数): 服务ID + +**请求示例**: + +```http +DELETE /api/admin/service/550e8400-e29b-41d4-a716-446655440000 +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "serviceId": "550e8400-e29b-41d4-a716-446655440000" + } +} +``` + +**错误响应(权限不足)**: + +```json +{ + "code": 403, + "message": "未授权访问", + "result": {} +} +``` + +**错误响应(服务器错误)**: + +```json +{ + "code": 500, + "message": "ERROR", + "result": {} +} +``` + +### 5. 修改服务收藏状态 + +**端点**: `PUT /api/service/{serviceId}` + +**权限**: 需要会话验证和个人令牌验证 + +**请求参数**: + +- `serviceId` (UUID, 路径参数): 服务ID + +**请求体(收藏)**: + +```json +{ + "favorited": true +} +``` + +**请求体(取消收藏)**: + +```json +{ + "favorited": false +} +``` + +**响应示例**: + +```json +{ + "code": 200, + "message": "OK", + "result": { + "serviceId": "550e8400-e29b-41d4-a716-446655440000", + "favorited": true + } +} +``` + +**错误响应(无效参数)**: + +```json +{ + "code": 400, + "message": "INVALID_PARAMETER", + "result": {} +} +``` + +**错误响应(服务器错误)**: + +```json +{ + "code": 500, + "message": "ERROR", + "result": {} +} +``` + +--- + +## 核心业务流程 + +### 服务创建流程 + +```mermaid +sequenceDiagram + participant Client as 客户端 + participant Router as API路由层 + participant Manager as ServiceCenterManager + participant Validator as OpenAPILoader + participant DB as PostgreSQL + participant Loader as ServiceLoader + participant FileSystem as 文件系统 + + Client->>Router: POST /api/admin/service (无service_id) + Router->>Router: 验证管理员权限 + Router->>Manager: create_service(user_sub, data) + Manager->>Manager: 生成UUID服务ID + Manager->>Validator: load_dict(data) + Note over Validator: 验证OpenAPI规范
提取服务元数据 + alt 验证失败 + Validator-->>Manager: 抛出验证异常 + Manager-->>Router: 返回异常 + Router-->>Client: 500 OpenAPI解析错误 + else 验证通过 + Validator-->>Manager: 返回ReducedOpenAPISpec + Manager->>DB: 查询是否存在同名同描述服务 + alt 存在相同服务 + DB-->>Manager: 返回已存在服务 + Manager-->>Router: 抛出RuntimeError + Router-->>Client: 500 已存在相同服务 + else 不存在 + DB-->>Manager: 返回空 + Manager->>Manager: 构建ServiceMetadata对象 + Note over Manager: 包含id/name/description
author/api/permission + Manager->>Loader: save(service_id, metadata, data) + Loader->>DB: 插入Service记录 + Loader->>FileSystem: 保存OpenAPI文档到YAML + Loader->>DB: 解析并插入NodeInfo记录 + Note over Loader: 为每个API操作创建节点 + Manager-->>Router: 返回service_id + Router->>Manager: get_service_apis(service_id) + Manager->>DB: 查询服务名称和NodeInfo列表 + Manager-->>Router: 返回名称和API列表 + Router-->>Client: 200 OK + 服务详情 + end + end +``` + +### 服务更新流程 + +```mermaid +sequenceDiagram + participant Client as 客户端 + participant Router as API路由层 + participant Manager as ServiceCenterManager + participant Validator as OpenAPILoader + participant DB as PostgreSQL + participant Loader as ServiceLoader + participant FileSystem as 文件系统 + + Client->>Router: POST /api/admin/service (含service_id) + Router->>Router: 验证管理员权限 + Router->>Manager: update_service(user_sub, service_id, data) + + Manager->>DB: 查询Service记录 + alt 服务不存在 + DB-->>Manager: 返回空 + Manager-->>Router: 抛出RuntimeError + Router-->>Client: 500 Service not found + else 非作者用户 + DB-->>Manager: 返回服务(author不匹配) + Manager-->>Router: 抛出RuntimeError + Router-->>Client: 403 未授权访问 + else 验证通过 + DB-->>Manager: 返回服务信息 + Manager->>DB: 更新updatedAt时间戳 + Manager->>Validator: load_dict(data) + Note over Validator: 验证OpenAPI规范 + alt 验证失败 + Validator-->>Manager: 抛出验证异常 + Manager-->>Router: 返回异常 + Router-->>Client: 500 更新服务失败 + else 验证通过 + Validator-->>Manager: 返回ReducedOpenAPISpec + Manager->>Manager: 构建新的ServiceMetadata + Manager->>Loader: save(service_id, metadata, data) + Loader->>DB: 更新Service记录 + Loader->>DB: 删除旧的NodeInfo记录 + Loader->>FileSystem: 更新OpenAPI文档 + Loader->>DB: 插入新的NodeInfo记录 + Manager-->>Router: 返回service_id + Router->>Manager: get_service_apis(service_id) + Manager-->>Router: 返回名称和API列表 + Router-->>Client: 200 OK + 更新后的服务详情 + end + end +``` + +### 服务查询流程 + +```mermaid +flowchart TD + Start([客户端请求]) --> Auth[验证用户身份] + Auth --> ParseParams[解析查询参数] + + ParseParams --> CheckFilter{检查筛选条件} + + CheckFilter -->|createdByMe=true| UserService[调用fetch_user_services] + CheckFilter -->|favorited=true| FavService[调用fetch_favorite_services] + CheckFilter -->|两者都为false| AllService[调用fetch_all_services] + CheckFilter -->|两者都为true| ErrorFilter[返回400错误] + + UserService --> BuildUserQuery[构建用户服务查询] + FavService --> BuildFavQuery[构建收藏服务查询] + AllService --> BuildAllQuery[构建全部服务查询] + + BuildUserQuery --> SearchType{检查搜索类型} + BuildFavQuery --> SearchType + BuildAllQuery --> SearchType + + SearchType -->|ALL| AllFields[name/description/author
模糊匹配] + SearchType -->|NAME| NameField[仅name模糊匹配] + SearchType -->|DESCRIPTION| DescField[仅description模糊匹配] + SearchType -->|AUTHOR| AuthorField[仅author模糊匹配] + + AllFields --> ApplyPaging[应用分页
offset和limit] + NameField --> ApplyPaging + DescField --> ApplyPaging + AuthorField --> ApplyPaging + + ApplyPaging --> OrderBy[按updatedAt降序排序] + OrderBy --> ExecuteQuery[执行数据库查询] + + ExecuteQuery --> GetFavorites[获取用户收藏列表] + GetFavorites --> LoopServices[遍历服务列表] + + LoopServices --> CheckFav[检查是否在收藏列表] + CheckFav --> BuildCard[构建ServiceCardItem] + BuildCard --> NextService{还有其他服务} + + NextService -->|是| LoopServices + NextService -->|否| ReturnResult[返回服务列表和总数] + + ReturnResult --> End([返回200响应]) + ErrorFilter --> End +``` + +### 服务删除流程 + +```mermaid +stateDiagram-v2 + [*] --> 验证管理员权限 + 验证管理员权限 --> 调用delete_service: 权限验证通过 + 验证管理员权限 --> 返回403错误: 权限不足 + + 调用delete_service --> 查询服务记录 + 查询服务记录 --> 验证作者权限 + + 验证作者权限 --> 删除服务文件: 作者匹配 + 验证作者权限 --> 抛出异常: 非作者或不存在 + + 删除服务文件 --> 调用ServiceLoader.delete + 调用ServiceLoader.delete --> 删除文件系统数据 + 删除文件系统数据 --> 删除Service记录 + + 删除Service记录 --> 删除NodeInfo记录 + Note right of 删除NodeInfo记录: 级联删除所有API节点 + + 删除NodeInfo记录 --> 删除ServiceACL记录 + Note right of 删除ServiceACL记录: 删除权限控制列表 + + 删除ServiceACL记录 --> 删除UserFavorite记录 + Note right of 删除UserFavorite记录: 删除所有用户收藏 + + 删除UserFavorite记录 --> 提交事务 + 提交事务 --> [*]: 删除成功 + + 抛出异常 --> 返回403或500错误 + 返回403错误 --> [*] + 返回403或500错误 --> [*] +``` + +### 收藏管理流程 + +```mermaid +sequenceDiagram + participant Client as 客户端 + participant Router as API路由层 + participant Manager as ServiceCenterManager + participant DB as PostgreSQL + + Client->>Router: PUT /api/service/{serviceId} + Router->>Router: 验证用户身份 + Router->>Manager: modify_favorite_service(user_sub, service_id, favorited) + + Manager->>DB: 查询Service是否存在 + alt 服务不存在 + DB-->>Manager: count=0 + Manager-->>Router: 抛出RuntimeError + Router-->>Client: 400 INVALID_PARAMETER + else 服务存在 + DB-->>Manager: count>0 + Manager->>DB: 查询User是否存在 + alt 用户不存在 + DB-->>Manager: count=0 + Manager-->>Router: 抛出RuntimeError + Router-->>Client: 400 INVALID_PARAMETER + else 用户存在 + DB-->>Manager: count>0 + Manager->>DB: 查询UserFavorite记录 + alt favorited=true且未收藏 + DB-->>Manager: 返回空 + Manager->>DB: 插入UserFavorite记录 + Note over DB: itemId=service_id
userSub=user_sub
favouriteType=SERVICE + Manager->>DB: 提交事务 + Manager-->>Router: 返回成功 + Router-->>Client: 200 OK + else favorited=false且已收藏 + DB-->>Manager: 返回收藏记录 + Manager->>DB: 删除UserFavorite记录 + Manager->>DB: 提交事务 + Manager-->>Router: 返回成功 + Router-->>Client: 200 OK + else 状态无变化 + Manager-->>Router: 返回成功 + Router-->>Client: 200 OK + end + end + end +``` + +--- + +## 核心方法说明 + +### fetch_all_services + +获取所有可访问的服务列表,支持搜索和分页。 + +- **功能描述**: 查询系统中所有服务并构建卡片列表 +- **执行步骤**: + 1. 调用内部查询构建方法生成SQL语句 + 2. 根据搜索类型和关键字添加WHERE条件 + 3. 按更新时间降序排序并应用分页 + 4. 执行查询获取Service记录列表 + 5. 获取当前用户的收藏服务ID列表 + 6. 遍历每条记录构建ServiceCardItem对象 + 7. 标注每个服务的收藏状态 +- **搜索维度**: + - **ALL**: 同时匹配name、description、author三个字段 + - **NAME**: 仅匹配服务名称 + - **DESCRIPTION**: 仅匹配描述信息 + - **AUTHOR**: 仅匹配作者标识 +- **分页计算**: offset = (page - 1) * pageSize +- **返回值**: 服务卡片列表和总数的元组 + +### fetch_user_services + +获取指定用户创建的服务列表,支持搜索和分页。 + +- **功能描述**: 查询用户作为作者的所有服务 +- **特殊处理**: + - 当搜索类型为AUTHOR时,验证关键字是否包含在用户标识中 + - 如果不包含,直接返回空列表 + - 自动将关键字替换为完整的user_sub进行精确匹配 +- **执行步骤**: + 1. 构建包含author条件的查询语句 + 2. 根据搜索类型添加额外的过滤条件 + 3. 应用分页和排序 + 4. 获取用户收藏列表并标注收藏状态 +- **使用场景**: "我创建的"服务列表展示 + +### fetch_favorite_services + +获取用户收藏的服务列表,支持搜索和分页。 + +- **功能描述**: 查询用户已收藏的所有服务 +- **执行步骤**: + 1. 首先从UserFavorite表获取用户收藏的服务ID列表 + 2. 构建Service表查询,限制ID在收藏列表中 + 3. 根据搜索类型和关键字添加过滤条件 + 4. 应用分页和排序 + 5. 构建卡片对象时直接标记favorited为True +- **优化逻辑**: 预先获取收藏ID列表,避免每个服务单独查询收藏状态 +- **使用场景**: "我收藏的"服务列表展示 + +### create_service + +创建新的OpenAPI服务并注册到系统中。 + +- **功能描述**: 验证OpenAPI规范并创建服务 +- **执行步骤**: + 1. 生成UUID作为服务唯一标识 + 2. 调用验证器解析并验证OpenAPI文档 + 3. 从文档中提取服务名称、描述、服务器地址 + 4. 在数据库中查询是否存在同名且同描述的服务 + 5. 如果存在则抛出异常,防止重复创建 + 6. 构建ServiceMetadata对象,设置默认权限为PUBLIC + 7. 调用ServiceLoader保存服务元数据和文档 + 8. ServiceLoader解析OpenAPI的paths部分创建NodeInfo记录 +- **验证内容**: + - OpenAPI版本兼容性 + - 必需字段完整性(info、paths) + - Servers配置有效性 + - Schema格式正确性 +- **权限默认值**: 新创建的服务默认为PUBLIC权限 +- **返回值**: 新创建的服务UUID + +### update_service + +更新已存在的OpenAPI服务配置。 + +- **功能描述**: 修改服务的OpenAPI文档和元数据 +- **权限验证**: + 1. 查询Service记录确认服务存在 + 2. 比对author字段与当前用户标识 + 3. 非作者用户抛出权限异常 +- **执行步骤**: + 1. 更新Service表的updatedAt字段为当前时间 + 2. 验证新的OpenAPI文档格式 + 3. 构建新的ServiceMetadata对象 + 4. 调用ServiceLoader更新文件系统和数据库 + 5. 删除旧的NodeInfo记录并插入新的记录 +- **影响范围**: + - Service表基本信息 + - 文件系统中的OpenAPI YAML文档 + - NodeInfo表中的API节点记录 +- **注意事项**: 更新操作不影响已有的收藏关系和权限配置 +- **返回值**: 服务UUID + +### get_service_apis + +获取服务提供的API操作列表。 + +- **功能描述**: 从数据库中查询服务的所有API节点 +- **执行步骤**: + 1. 查询Service表获取服务名称 + 2. 查询NodeInfo表获取所有关联的节点记录 + 3. 遍历节点记录提取name、description和knownParams + 4. 从knownParams中提取method和url构建路径字符串 + 5. 构建ServiceApiData对象列表 +- **路径格式**: "METHOD URL"(如:"GET /weather/current") +- **参数来源**: knownParams字段存储解析后的HTTP方法和URL +- **返回值**: 服务名称和API数据列表的元组 +- **使用场景**: 服务详情页面展示API列表,工作流节点选择API + +### get_service_data + +获取服务的完整OpenAPI文档数据,用于编辑场景。 + +- **功能描述**: 从文件系统读取原始OpenAPI YAML文档 +- **权限验证**: + 1. 查询Service记录并验证存在性 + 2. 验证author字段与当前用户匹配 + 3. 同时满足两个条件才允许访问 +- **执行步骤**: + 1. 构建文件路径:{data_dir}/semantics/service/{service_id}/openapi/api.yaml + 2. 使用异步文件操作打开YAML文件 + 3. 使用yaml.safe_load解析文件内容 + 4. 返回服务名称和解析后的字典数据 +- **文件格式**: YAML格式的OpenAPI 3.x规范文档 +- **异常处理**: 文件不存在或解析失败会抛出异常 +- **使用场景**: 管理员编辑服务配置时获取完整文档 + +### get_service_metadata + +获取服务的元数据信息,包含权限验证逻辑。 + +- **功能描述**: 读取服务的metadata.yaml文件 +- **权限验证逻辑**: + 1. 查询ServiceACL表获取允许访问的用户列表 + 2. 如果当前用户在ACL列表中,可访问PRIVATE服务 + 3. 如果不在ACL列表中,只能访问PUBLIC服务或自己创建的服务 + 4. 使用复杂的OR/AND条件组合实现权限控制 +- **执行步骤**: + 1. 根据权限规则查询Service记录 + 2. 构建文件路径:{data_dir}/semantics/service/{service_id}/metadata.yaml + 3. 读取并解析YAML文件 + 4. 使用ServiceMetadata模型验证并构建对象 +- **元数据内容**: id、name、description、author、api、permission等 +- **使用场景**: 工作流或智能体引用服务时验证权限并获取配置 + +### delete_service + +删除服务及其所有关联数据。 + +- **功能描述**: 完整清理服务的所有痕迹 +- **权限验证**: + 1. 查询Service记录确认存在 + 2. 验证author字段与当前用户匹配 + 3. 同时满足才允许删除 +- **执行步骤**: + 1. 调用ServiceLoader.delete删除文件系统数据 + 2. ServiceLoader删除整个服务目录 + 3. 数据库中删除ServiceACL记录(权限列表) + 4. 数据库中删除UserFavorite记录(所有用户收藏) + 5. 数据库级联删除Service和NodeInfo记录 + 6. 提交事务完成删除 +- **删除范围**: + - 文件系统:{data_dir}/semantics/service/{service_id}/目录 + - 数据库:Service、NodeInfo、ServiceACL、UserFavorite表 +- **不可逆操作**: 删除后无法恢复,需谨慎操作 +- **影响范围**: 所有引用该服务的工作流和智能体需要重新配置 + +### modify_favorite_service + +修改用户对服务的收藏状态。 + +- **功能描述**: 添加或移除用户的服务收藏 +- **验证步骤**: + 1. 使用COUNT查询验证Service记录存在 + 2. 使用COUNT查询验证User记录存在 + 3. 任一不存在则返回失败 +- **执行逻辑**: + - **添加收藏**: 查询UserFavorite不存在且favorited=true时插入新记录 + - **取消收藏**: 查询UserFavorite存在且favorited=false时删除记录 + - **无变化**: 其他情况不执行数据库操作,直接返回成功 +- **复合键**: UserFavorite表使用(itemId, userSub, favouriteType)作为唯一键 +- **类型标识**: favouriteType固定为UserFavoriteType.SERVICE +- **幂等性**: 多次调用相同参数不会产生错误,状态最终一致 +- **使用场景**: 服务列表中的收藏按钮点击事件 + +### _get_favorite_service_ids_by_user + +内部方法:获取用户收藏的所有服务ID列表。 + +- **功能描述**: 从UserFavorite表提取用户收藏的服务UUID +- **查询条件**: + - userSub等于指定用户标识 + - favouriteType等于SERVICE类型 +- **执行步骤**: + 1. 构建查询语句筛选UserFavorite记录 + 2. 执行查询获取所有匹配的记录对象 + 3. 遍历记录提取itemId字段 + 4. 构建UUID列表返回 +- **返回值**: UUID列表,可能为空 +- **使用场景**: 服务列表查询时批量获取收藏状态,避免N+1查询问题 + +### _validate_service_data + +内部方法:验证OpenAPI文档数据的有效性。 + +- **功能描述**: 调用OpenAPILoader解析并验证文档 +- **验证步骤**: + 1. 检查数据字典不为空 + 2. 调用OpenAPILoader.load_dict方法 + 3. 验证OpenAPI规范版本 + 4. 验证必需的info和paths部分 + 5. 提取服务器地址和基本信息 + 6. 构建ReducedOpenAPISpec简化对象 +- **验证规则**: + - 必需包含info.title和info.description + - 必需包含至少一个path定义 + - Servers配置必需有效 + - Schema定义符合JSON Schema规范 +- **返回值**: ReducedOpenAPISpec对象,包含id、description、servers等精简信息 +- **异常抛出**: 验证失败时抛出ValueError或ValidationError +- **使用场景**: 创建和更新服务前的数据验证步骤 + +### _build_service_query + +内部方法:构建服务查询的SQL语句。 + +- **功能描述**: 根据搜索条件生成通用的Service查询 +- **执行步骤**: + 1. 创建基础SELECT语句针对Service表 + 2. 根据keyword和searchType添加WHERE条件 + 3. 使用LIKE操作符进行模糊匹配 + 4. 添加ORDER BY按updatedAt降序排序 + 5. 应用OFFSET和LIMIT实现分页 +- **搜索类型处理**: + - **ALL**: 使用OR连接name、description、author的LIKE条件 + - **NAME**: 仅添加name LIKE条件 + - **DESCRIPTION**: 仅添加description LIKE条件 + - **AUTHOR**: 仅添加author LIKE条件 +- **模糊匹配**: 使用"%{keyword}%"格式进行全文匹配 +- **返回值**: SQLAlchemy Select对象,可直接执行或进一步组合 +- **使用场景**: fetch_all_services内部调用 + +### _build_user_service_query + +内部方法:构建用户创建服务的查询SQL语句。 + +- **功能描述**: 在通用查询基础上添加作者过滤 +- **执行步骤**: + 1. 创建基础SELECT并添加author=user_sub条件 + 2. 如果有keyword,使用AND组合作者条件和搜索条件 + 3. 根据searchType选择搜索字段 + 4. 应用排序和分页 +- **条件组合**: 使用and_和or_组合复杂的WHERE条件 +- **返回值**: SQLAlchemy Select对象 +- **使用场景**: fetch_user_services内部调用 + +### _build_favorite_service_query + +内部方法:构建收藏服务的查询SQL语句。 + +- **功能描述**: 限制查询结果在用户收藏范围内 +- **执行步骤**: + 1. 调用_get_favorite_service_ids_by_user获取收藏ID列表 + 2. 创建基础SELECT并添加id.in_(fav_ids)条件 + 3. 如果有keyword,使用AND组合收藏条件和搜索条件 + 4. 根据searchType选择搜索字段 + 5. 应用排序和分页 +- **特殊处理**: 如果收藏列表为空,查询仍会执行但不会返回结果 +- **返回值**: SQLAlchemy Select对象 +- **使用场景**: fetch_favorite_services内部调用 + +--- + +## 数据模型关系 + +```mermaid +erDiagram + Service ||--o{ NodeInfo : "包含API节点" + Service ||--o{ ServiceACL : "权限控制" + Service ||--o{ UserFavorite : "被用户收藏" + User ||--o{ Service : "创建" + User ||--o{ UserFavorite : "收藏服务" + + Service { + uuid id PK + string name + string description + string author FK + enum permission + datetime createdAt + datetime updatedAt + } + + NodeInfo { + uuid id PK + uuid serviceId FK + string name + string description + json knownParams + string nodeType + datetime createdAt + } + + ServiceACL { + uuid serviceId PK "FK" + string userSub PK "FK" + datetime createdAt + } + + UserFavorite { + uuid itemId PK "FK" + string userSub PK "FK" + enum favouriteType PK + datetime createdAt + } + + User { + string userSub PK + string email + boolean isAdmin + } +``` + +--- + +## 核心业务规则 + +### 权限控制规则 + +#### 管理员权限 + +以下操作需要管理员权限(admin_router) + +- 创建服务 +- 更新服务配置 +- 删除服务 + +#### 作者权限 + +只有服务作者可以执行 + +- 更新服务时,验证author字段匹配当前用户 +- 删除服务时,验证author字段匹配当前用户 +- 编辑模式查看服务时,验证author字段匹配当前用户 + +#### 普通用户权限 + +所有用户可以执行 + +- 查询服务列表(所有服务、我创建的、我收藏的) +- 查看模式获取服务详情和API列表 +- 修改自己的收藏状态 + +#### 服务访问权限 + +服务元数据访问遵循以下规则 + +- **PUBLIC服务**: 所有用户可访问 +- **PRIVATE服务**: 仅作者和ServiceACL列表中的用户可访问 +- **ACL管理**: 通过ServiceACL表维护授权用户列表 + +### 服务唯一性规则 + +#### 重复检测 + +创建服务时检测是否存在相同服务 + +- 同时匹配name和description字段 +- 存在相同服务时抛出异常 +- 防止重复注册相同的OpenAPI服务 + +#### 服务标识 + +- 使用UUID作为服务唯一标识 +- UUID在创建时生成,永不改变 +- 作为文件系统路径和数据库主键 + +### 收藏管理规则 + +#### 收藏唯一性 + +- UserFavorite表使用(itemId, userSub, favouriteType)复合主键 +- 同一用户不能重复收藏同一服务 +- 数据库约束自动保证唯一性 + +#### 收藏状态 + +- favorited=true: 如果未收藏则插入记录 +- favorited=false: 如果已收藏则删除记录 +- 操作具有幂等性,多次执行结果一致 + +#### 级联删除 + +- 服务删除时自动删除所有用户的收藏记录 +- 用户删除时自动删除该用户的所有收藏 + +### 查询筛选规则 + +#### 互斥条件 + +- createdByMe和favorited参数不能同时为true +- 同时为true时返回400错误 +- 两个参数都为false时查询所有服务 + +#### 搜索范围 + +- **ALL**: 在name、description、author三个字段中搜索 +- **NAME**: 仅在服务名称中搜索 +- **DESCRIPTION**: 仅在描述信息中搜索 +- **AUTHOR**: 仅在作者标识中搜索 + +#### 分页规则 + +- 页码从1开始,最小值为1 +- 默认每页16条记录 +- offset计算:(page - 1) * pageSize +- 返回当前页码和总记录数 + +--- + +## 文件系统布局 + +```text +{data_dir}/semantics/service/ +├── {service_id}/ # 单个服务目录 +│ ├── metadata.yaml # 服务元数据 +│ │ ├── id # 服务UUID +│ │ ├── name # 服务名称 +│ │ ├── description # 服务描述 +│ │ ├── author # 作者标识 +│ │ ├── api # API配置 +│ │ │ └── server # 服务器地址 +│ │ └── permission # 权限类型 +│ └── openapi/ # OpenAPI文档目录 +│ └── api.yaml # OpenAPI 3.x规范文档 +└── ... +``` + +### 目录说明 + +- **service目录**: 存储所有注册的服务数据 + - 每个服务一个子目录,使用UUID命名 + - metadata.yaml: ServiceMetadata对象的YAML序列化 + - api.yaml: 完整的OpenAPI规范文档 + +### 文件格式 + +#### metadata.yaml示例 + +```yaml +id: 550e8400-e29b-41d4-a716-446655440000 +name: 天气查询服务 +description: 提供实时天气查询和预报功能 +author: user@example.com +api: + server: + - url: https://api.weather.example.com/v1 +permission: + type: PUBLIC +``` + +#### api.yaml示例 + +```yaml +openapi: 3.1.0 +info: + title: 天气查询服务 + version: 1.0.0 + description: 提供实时天气查询和预报功能 +servers: + - url: https://api.weather.example.com/v1 +paths: + /weather/current: + get: + operationId: getCurrentWeather + summary: 获取当前天气信息 + parameters: + - name: city + in: query + required: true + schema: + type: string + responses: + '200': + description: 成功返回天气信息 +``` + +--- + +## 与其他模块的交互 + +```mermaid +graph TB + Router[API路由层] --> ServiceMgr[ServiceCenterManager] + Router --> Auth[身份验证模块] + + ServiceMgr --> ServiceLoader[ServiceLoader] + ServiceMgr --> OpenAPILoader[OpenAPILoader] + ServiceMgr --> PostgreSQL[(PostgreSQL数据库)] + + ServiceLoader --> FileSystem[(文件系统)] + ServiceLoader --> PostgreSQL + + OpenAPILoader --> Validator[OpenAPI验证器] + + FlowService[工作流服务] --> ServiceMgr + AppCenter[应用中心] --> ServiceMgr + AgentService[智能体服务] --> ServiceMgr + + Auth --> Session[会话验证] + Auth --> Token[令牌验证] + Auth --> Admin[管理员验证] + + style ServiceMgr fill:#e1f5ff + style PostgreSQL fill:#ffe1e1 + style FileSystem fill:#fff4e1 +``` + +### 依赖的外部模块 + +- **ServiceLoader**: 管理服务文件的读写、解析OpenAPI文档并创建NodeInfo记录 +- **OpenAPILoader**: 验证OpenAPI规范文档的合法性,提取服务元数据 +- **PostgreSQL**: 持久化服务信息、API节点、权限控制、收藏记录等数据 +- **身份验证模块**: 验证用户会话、个人令牌和管理员权限 + +### 被依赖的场景 + +- **工作流服务**: 工作流节点引用OpenAPI服务的API操作 +- **应用中心服务**: 智能体应用配置时选择可用的服务和API +- **智能体服务**: 对话执行时调用服务API完成任务 +- **管理界面**: 展示服务列表、管理服务配置和权限 + +--- + +## API节点解析机制 + +### NodeInfo创建流程 + +ServiceLoader在保存服务时解析OpenAPI文档并创建NodeInfo记录: + +1. 遍历OpenAPI的paths部分 +2. 对每个路径的每个HTTP方法创建一个NodeInfo +3. 提取operationId作为节点名称 +4. 提取summary或description作为节点描述 +5. 将HTTP方法和URL存储在knownParams字段 +6. 设置nodeType为对应的操作类型 + +### 节点数据结构 + +每个NodeInfo包含以下字段: + +- **id**: 节点唯一标识UUID +- **serviceId**: 所属服务ID +- **name**: 操作名称(来自operationId) +- **description**: 操作描述 +- **knownParams**: JSON格式存储method和url +- **nodeType**: 节点类型标识 + +### 节点查询接口 + +get_service_apis方法查询并格式化节点信息: + +- 从NodeInfo表查询指定serviceId的所有记录 +- 提取knownParams中的method和url +- 格式化为"METHOD URL"字符串(如"GET /weather/current") +- 构建ServiceApiData对象列表返回 + +### 节点生命周期 + +- **创建**: 服务首次创建或更新时解析生成 +- **更新**: 服务更新时删除旧节点并重新解析创建 +- **删除**: 服务删除时级联删除所有关联节点 + +--- + +## OpenAPI规范支持 + +### 支持的版本 + +- OpenAPI 3.0.x +- OpenAPI 3.1.x + +### 必需字段 + +- **openapi**: 版本号字符串 +- **info.title**: 服务标题(映射为服务name) +- **info.description**: 服务描述 +- **servers**: 至少一个服务器地址 +- **paths**: 至少一个路径定义 + +### 可选字段 + +- **info.version**: 版本号 +- **components**: 可重用的Schema定义 +- **security**: 安全认证配置 +- **tags**: 标签分类 + +### 验证器功能 + +OpenAPILoader执行以下验证: + +- JSON/YAML格式正确性 +- 必需字段完整性检查 +- Schema定义的类型正确性 +- 引用($ref)的有效性 +- 服务器URL格式验证 + +### 简化对象 + +ReducedOpenAPISpec包含提取的核心信息: + +- **id**: 服务标题(info.title) +- **description**: 服务描述 +- **servers**: 服务器地址列表 + +--- + +## 异常处理机制 + +### ValueError异常 + +在以下场景中抛出值错误异常: + +- OpenAPI文档数据为空 +- 文档验证失败(格式错误、缺少必需字段) +- 服务不存在 +- 用户或服务查询失败 + +### RuntimeError异常 + +在以下场景中抛出运行时错误异常: + +- 创建服务时已存在同名同描述服务 +- 更新服务时服务不存在 +- 更新服务时用户不是作者 +- 删除服务时服务不存在或权限不足 +- 收藏操作时服务或用户不存在 + +### InstancePermissionError异常 + +在以下场景中抛出权限错误异常: + +- 更新服务时非作者尝试操作 +- 编辑模式查看服务时非作者尝试访问 +- 删除服务时非作者尝试操作 + +### 通用异常处理 + +路由层捕获所有异常并统一处理: + +- 记录详细的异常日志:`[ServiceCenter] 操作描述失败` +- 返回500内部服务器错误响应 +- 对于特定异常返回403或400状态码 +- 不泄露敏感的内部错误信息 + +--- + +## 服务状态与权限 + +### 权限类型 + +服务支持两种权限类型: + +- **PUBLIC**: 所有用户可访问和查询 +- **PRIVATE**: 仅作者和ACL列表用户可访问 + +### 权限默认值 + +- 新创建的服务默认为PUBLIC权限 +- 可通过更新ServiceMetadata修改权限类型 + +### ACL管理 + +ServiceACL表维护私有服务的授权用户: + +- 复合主键:(serviceId, userSub) +- 作者自动拥有访问权限 +- ACL列表用户可访问PRIVATE服务的元数据 +- 服务删除时级联删除ACL记录