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记录