chore: 清理旧版 teams 需求文档
移除已归档的 teams 目录下的需求分析文档 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,137 +0,0 @@
|
|||||||
# AI-Core 文档解析服务 API 对接文档
|
|
||||||
|
|
||||||
## 服务地址
|
|
||||||
|
|
||||||
```
|
|
||||||
localhost:50051
|
|
||||||
```
|
|
||||||
|
|
||||||
## VLM 配置(可选)
|
|
||||||
|
|
||||||
VLM 用于提升图片文件的解析效果。如果不配置 VLM,则使用默认的 MarkItDown 解析。
|
|
||||||
|
|
||||||
### 方式一:环境变量
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 设置环境变量
|
|
||||||
export VLM_API_KEY="your-api-key"
|
|
||||||
export VLM_PROVIDER="openai" # openai / anthropic / qwen
|
|
||||||
export VLM_MODEL="gpt-4o"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方式二:配置文件
|
|
||||||
|
|
||||||
在 `ai-core/config.yaml` 中配置:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
vlm:
|
|
||||||
enabled: true
|
|
||||||
provider: "openai" # openai / anthropic / qwen
|
|
||||||
model: "gpt-4o" # 模型名称
|
|
||||||
api_key: "sk-xxx" # API Key
|
|
||||||
base_url: "" # 自定义 API 地址(可选)
|
|
||||||
prompt: "" # 自定义提示词(可选)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 支持的 VLM 提供商
|
|
||||||
|
|
||||||
| 提供商 | 示例模型 |
|
|
||||||
|--------|----------|
|
|
||||||
| openai | gpt-4o, gpt-4o-mini |
|
|
||||||
| anthropic | claude-3-opus, claude-3-sonnet |
|
|
||||||
| qwen | qwen-vl-max, qwen2-vl-72b |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## gRPC API 定义
|
|
||||||
|
|
||||||
### 1. ParseDocument - 解析文档
|
|
||||||
|
|
||||||
**请求 (ParseRequest)**
|
|
||||||
```protobuf
|
|
||||||
message ParseRequest {
|
|
||||||
string file_url = 1; // 文件 URL(必填)
|
|
||||||
string file_name = 2; // 文件名,带扩展名(必填)
|
|
||||||
string file_type = 3; // 文件类型(可选,自动检测)
|
|
||||||
string parser_engine = 4; // 解析引擎(可选)
|
|
||||||
map<string, string> engine_overrides = 5; // 引擎配置
|
|
||||||
|
|
||||||
// VLM 配置(可选,优先级高于全局配置)
|
|
||||||
VLMConfig vlm_config = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message VLMConfig {
|
|
||||||
bool enabled = 1;
|
|
||||||
string provider = 2;
|
|
||||||
string model = 3;
|
|
||||||
string api_key = 4;
|
|
||||||
string base_url = 5;
|
|
||||||
string prompt = 6;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应 (ParseResponse)**
|
|
||||||
```protobuf
|
|
||||||
message ParseResponse {
|
|
||||||
bool success = 1;
|
|
||||||
string content = 2; // Markdown 内容
|
|
||||||
string message = 3;
|
|
||||||
int32 content_length = 4;
|
|
||||||
string file_type = 5;
|
|
||||||
string parser_engine = 6;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Golang 对接示例
|
|
||||||
|
|
||||||
### 基础调用(无 VLM 配置时使用 MarkItDown)
|
|
||||||
|
|
||||||
```go
|
|
||||||
req := &pb.ParseRequest{
|
|
||||||
FileUrl: "https://example.com/document.pdf",
|
|
||||||
FileName: "document.pdf",
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, client.ParseDocument(ctx, req)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 带 VLM 配置调用
|
|
||||||
|
|
||||||
```go
|
|
||||||
req := &pb.ParseRequest{
|
|
||||||
FileUrl: "https://example.com/image.png",
|
|
||||||
FileName: "image.png",
|
|
||||||
VlmConfig: &pb.VLMConfig{
|
|
||||||
Enabled: Provider: "open true,
|
|
||||||
ai",
|
|
||||||
Model: "gpt-4o",
|
|
||||||
ApiKey: "sk-xxx",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.ParseDocument(ctx, req)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 解析逻辑
|
|
||||||
|
|
||||||
1. **图片文件** (jpg, png, webp 等)
|
|
||||||
- 如果配置了 VLM → 使用 VLM 解析
|
|
||||||
- 如果没有配置 VLM → 使用 MarkItDown 解析
|
|
||||||
|
|
||||||
2. **PDF/DOCX/PPTX 等文档**
|
|
||||||
- 使用 MarkItDown 解析
|
|
||||||
|
|
||||||
3. **VLM 优先级**
|
|
||||||
- gRPC 请求中的 vlm_config > 全局配置(config.yaml/环境变量)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **文件 URL**: 必须是可直接访问的 URL
|
|
||||||
2. **文件名**: 必须带扩展名(如 `.pdf`, `.png`)
|
|
||||||
3. **返回内容**: Markdown 格式文本
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# AI 服务需求 TODO
|
|
||||||
|
|
||||||
## 2026年3月
|
|
||||||
|
|
||||||
### 2026-03-09
|
|
||||||
|
|
||||||
- [ ] **AI-Core 文档解析服务对接**
|
|
||||||
- 服务:ai-core (gRPC, 端口 50051)
|
|
||||||
- 功能:将文档(PDF/DOCX/PPTX/图片等)转换为 Markdown
|
|
||||||
- 对接方式:gRPC 调用
|
|
||||||
- 详细需求:[ai-core-api.md](./ai-core-api.md)
|
|
||||||
|
|
||||||
- [ ] **VLM 调用支持**
|
|
||||||
- 支持 OpenAI GPT-4o、Anthropic Claude、阿里 Qwen VL
|
|
||||||
- 通过 vlm_config 配置启用
|
|
||||||
- 适用场景:图片文件(jpg, png 等)自动使用 VLM 解析
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
> 需求完成后请完成者打 ✔
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# API 接口文档
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
### Database 相关
|
|
||||||
|
|
||||||
- [检查数据库连接并获取表结构](database-check.md)
|
|
||||||
- [创建数据库配置](database-create.md)
|
|
||||||
- [获取数据库列表](database-list.md)
|
|
||||||
- [获取子表列表](subtable-list.md)
|
|
||||||
|
|
||||||
### Neo4j 相关
|
|
||||||
|
|
||||||
- [Neo4j 连接测试](neo4j-check.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
> 接口如有更新,请同步更新此文档
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
# 检查数据库连接并获取表结构
|
|
||||||
|
|
||||||
## 接口地址
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /database/check
|
|
||||||
```
|
|
||||||
|
|
||||||
## 请求参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| db_type | string | 是 | 数据库类型:`mysql`、`postgres`、`neo4j` |
|
|
||||||
| host | string | 是 | 数据库主机 |
|
|
||||||
| port | int | 是 | 数据库端口 |
|
|
||||||
| username | string | 是 | 用户名 |
|
|
||||||
| password | string | 否 | 密码 |
|
|
||||||
| database | string | 是 | 数据库名 |
|
|
||||||
| charset | string | 否 | 字符集,默认 `utf8mb4` |
|
|
||||||
| ssl_mode | string | 否 | SSL 模式 |
|
|
||||||
| database_id | string | 否 | 已存在的数据库ID,用于恢复字段映射 |
|
|
||||||
| uri | string | 否 | Neo4j 连接地址(如 bolt://localhost:7687),Neo4j 类型必填 |
|
|
||||||
|
|
||||||
## 请求示例(MySQL/PostgreSQL)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"db_type": "mysql",
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 3306,
|
|
||||||
"username": "root",
|
|
||||||
"password": "root",
|
|
||||||
"database": "students",
|
|
||||||
"charset": "utf8mb4",
|
|
||||||
"database_id": "xxx-xxx-xxx"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 请求示例(Neo4j)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"db_type": "neo4j",
|
|
||||||
"uri": "bolt://localhost:7687",
|
|
||||||
"username": "neo4j",
|
|
||||||
"password": "password",
|
|
||||||
"database": "neo4j"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 返回参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| success | bool | 是否连接成功 |
|
|
||||||
| message | string | 消息 |
|
|
||||||
| database | string | 数据库名 |
|
|
||||||
| tables | array | 表结构列表(MySQL/PostgreSQL) |
|
|
||||||
| graphs | object | 图谱数据(Neo4j) |
|
|
||||||
|
|
||||||
### tables[] 详情(关系型数据库)
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| table_name | string | 表名 |
|
|
||||||
| table_comment | string | 表注释 |
|
|
||||||
| ddl | string | 建表 DDL(带 COMMENT 的映射后 DDL) |
|
|
||||||
| columns | array | 列信息列表 |
|
|
||||||
|
|
||||||
### columns[] 详情
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| column_name | string | 列名 |
|
|
||||||
| data_type | string | 数据类型 |
|
|
||||||
| column_type | string | 完整列类型 |
|
|
||||||
| is_nullable | string | 是否可空(YES/NO) |
|
|
||||||
| default_value | string | 默认值 |
|
|
||||||
| column_key | string | 主键标识(PRI/MUL/UNI) |
|
|
||||||
| extra | string | 额外信息(如 auto_increment) |
|
|
||||||
| column_comment | string | 列注释 |
|
|
||||||
| mapped_name | string | 字段中文映射名(已保存的映射) |
|
|
||||||
|
|
||||||
### graphs 详情(Neo4j)
|
|
||||||
|
|
||||||
| 参数 |类型| 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| labels | array | 标签列表 |
|
|
||||||
| relationshipTypes | array | 关系类型列表 |
|
|
||||||
| nodes | array | 节点属性定义 |
|
|
||||||
| relationships | array | 关系属性定义 |
|
|
||||||
|
|
||||||
### graphs.labels[]
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| name | string | 标签名称 |
|
|
||||||
| count | int | 节点数量 |
|
|
||||||
|
|
||||||
### graphs.relationshipTypes[]
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| name | string | 关系类型名称 |
|
|
||||||
| count | int | 关系数量 |
|
|
||||||
|
|
||||||
### graphs.nodes[]
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| label | string | 节点标签名 |
|
|
||||||
| properties | array | 属性列表 |
|
|
||||||
|
|
||||||
### graphs.relationships[]
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| type | string | 关系类型名 |
|
|
||||||
| startLabel | string | 起始节点标签 |
|
|
||||||
| endLabel | string | 目标节点标签 |
|
|
||||||
| properties | array | 属性列表 |
|
|
||||||
|
|
||||||
## 返回示例(MySQL/PostgreSQL)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "connection successful",
|
|
||||||
"database": "students",
|
|
||||||
"tables": [
|
|
||||||
{
|
|
||||||
"table_name": "users",
|
|
||||||
"table_comment": "用户表",
|
|
||||||
"ddl": "CREATE TABLE `users` (\n `id` int(10) unsigned NOT NULL COMMENT '用户ID'\n ...\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"column_name": "id",
|
|
||||||
"data_type": "int",
|
|
||||||
"column_type": "int(10) unsigned",
|
|
||||||
"is_nullable": "NO",
|
|
||||||
"default_value": "",
|
|
||||||
"column_key": "PRI",
|
|
||||||
"extra": "auto_increment",
|
|
||||||
"column_comment": "",
|
|
||||||
"mapped_name": "用户ID"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 返回示例(Neo4j)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "connection successful",
|
|
||||||
"database": "neo4j",
|
|
||||||
"graphs": {
|
|
||||||
"labels": [
|
|
||||||
{"name": "User", "count": 100},
|
|
||||||
{"name": "Order", "count": 50}
|
|
||||||
],
|
|
||||||
"relationshipTypes": [
|
|
||||||
{"name": "KNOWS", "count": 30},
|
|
||||||
{"name": "OWNS", "count": 20}
|
|
||||||
],
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"label": "User",
|
|
||||||
"properties": [
|
|
||||||
{"name": "id", "type": "string"},
|
|
||||||
{"name": "name", "type": "string"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"relationships": [
|
|
||||||
{
|
|
||||||
"type": "KNOWS",
|
|
||||||
"startLabel": "User",
|
|
||||||
"endLabel": "User",
|
|
||||||
"properties": [
|
|
||||||
{"name": "since", "type": "date"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用场景
|
|
||||||
|
|
||||||
1. **关系型数据库**:
|
|
||||||
- 首次连接:不传 `database_id`,获取实时表结构
|
|
||||||
- 恢复映射:传入 `database_id`,返回已保存的 `mapped_name` 和 `ddl`
|
|
||||||
|
|
||||||
2. **Neo4j 图数据库**:
|
|
||||||
- 连接 Neo4j 并获取图谱概览数据(标签、关系类型、属性定义)
|
|
||||||
- 用于前端图可视化展示
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
# 创建数据库配置
|
|
||||||
|
|
||||||
## 接口地址
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /database/add
|
|
||||||
```
|
|
||||||
|
|
||||||
## 请求参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| name | string | 是 | 数据库名称 |
|
|
||||||
| description | string | 否 | 描述 |
|
|
||||||
| db_type | string | 是 | 数据库类型 |
|
|
||||||
| host | string | 是 | 主机 |
|
|
||||||
| port | int | 是 | 端口 |
|
|
||||||
| username | string | 是 | 用户名 |
|
|
||||||
| password | string | 否 | 密码 |
|
|
||||||
| database | string | 是 | 数据库名 |
|
|
||||||
| charset | string | 否 | 字符集 |
|
|
||||||
| ssl_mode | string | 否 | SSL 模式 |
|
|
||||||
| sub_tables | array | 否 | 子表配置列表 |
|
|
||||||
|
|
||||||
### sub_tables[] 详情
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| parent_table | string | 是 | 原始表名 |
|
|
||||||
| sub_table_name | string | 是 | 子表别名 |
|
|
||||||
| sub_table_comment | string | 否 | 子表注释 |
|
|
||||||
| mapping_type | string | 否 | 映射类型 |
|
|
||||||
| relation_field | string | 否 | 关联字段 |
|
|
||||||
| relation_type | string | 否 | 关联类型 |
|
|
||||||
| fields | array | 否 | 字段映射列表 |
|
|
||||||
|
|
||||||
### fields[] 详情
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| column_name | string | 是 | 列名 |
|
|
||||||
| mapped_name | string | 是 | 中文映射名 |
|
|
||||||
|
|
||||||
## 请求示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "学生数据库",
|
|
||||||
"description": "用于存储学生信息",
|
|
||||||
"db_type": "mysql",
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 3306,
|
|
||||||
"username": "root",
|
|
||||||
"password": "root",
|
|
||||||
"database": "students",
|
|
||||||
"charset": "utf8mb4",
|
|
||||||
"sub_tables": [
|
|
||||||
{
|
|
||||||
"parent_table": "users",
|
|
||||||
"sub_table_name": "用户表",
|
|
||||||
"sub_table_comment": "用户信息",
|
|
||||||
"fields": [
|
|
||||||
{"column_name": "id", "mapped_name": "用户ID"},
|
|
||||||
{"column_name": "name", "mapped_name": "用户名"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 返回参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| id | string | 数据库记录ID |
|
|
||||||
| name | string | 数据库名称 |
|
|
||||||
| db_type | string | 数据库类型 |
|
|
||||||
| host | string | 主机 |
|
|
||||||
| port | int | 端口 |
|
|
||||||
| ... | ... | 其他字段 |
|
|
||||||
|
|
||||||
## 返回示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "xxx-xxx-xxx",
|
|
||||||
"name": "学生数据库",
|
|
||||||
"description": "用于存储学生信息",
|
|
||||||
"db_type": "mysql",
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 3306,
|
|
||||||
"username": "root",
|
|
||||||
"password": "root",
|
|
||||||
"database": "students",
|
|
||||||
"table_count": 1,
|
|
||||||
"charset": "utf8mb4",
|
|
||||||
"created_at": "2026-03-06T15:00:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 说明
|
|
||||||
|
|
||||||
- 创建时会自动连接数据库获取表结构 DDL
|
|
||||||
- 如果传入了 `fields`(字段映射),会自动生成带 COMMENT 的新 DDL 并存储
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# 获取数据库列表
|
|
||||||
|
|
||||||
## 接口地址
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /database/list
|
|
||||||
```
|
|
||||||
|
|
||||||
## 请求参数
|
|
||||||
|
|
||||||
无
|
|
||||||
|
|
||||||
## 返回参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| list | array | 数据库列表 |
|
|
||||||
|
|
||||||
### list[] 详情
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| id | string | 数据库ID |
|
|
||||||
| name | string | 数据库名称 |
|
|
||||||
| description | string | 描述 |
|
|
||||||
| db_type | string | 数据库类型 |
|
|
||||||
| host | string | 主机 |
|
|
||||||
| port | int | 端口 |
|
|
||||||
| database | string | 数据库名 |
|
|
||||||
| table_count | int | 子表数量 |
|
|
||||||
| created_at | string | 创建时间 |
|
|
||||||
|
|
||||||
## 返回示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"id": "xxx-xxx",
|
|
||||||
"name": "学生数据库",
|
|
||||||
"description": "用于存储学生信息",
|
|
||||||
"db_type": "mysql",
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 3306,
|
|
||||||
"database": "students",
|
|
||||||
"table_count": 5,
|
|
||||||
"created_at": "2026-03-06T15:00:00Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,268 +0,0 @@
|
|||||||
# 知识库 API
|
|
||||||
|
|
||||||
## 基础信息
|
|
||||||
|
|
||||||
| 项目 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| 基础URL | `http://localhost:8082` |
|
|
||||||
|
|
||||||
## 接口列表
|
|
||||||
|
|
||||||
### 1. 创建知识库
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/knowledge/create
|
|
||||||
Content-Type: application/json
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| name | String | 是 | 知识库名称 |
|
|
||||||
| description | String | 否 | 知识库描述 |
|
|
||||||
| llm_model_id | String | 是 | LLM 模型 ID |
|
|
||||||
| embedding_model_id | String | 是 | Embedding 模型 ID |
|
|
||||||
| parsing_config | Object | 是 | 解析配置 |
|
|
||||||
| - engine | String | 是 | 解析引擎:markitdown / docling |
|
|
||||||
| - docling_url | String | 条件 | Docling URL(engine=docling 时必填) |
|
|
||||||
| - enable_pdf | Boolean | 否 | 是否启用 PDF 解析 |
|
|
||||||
| - pandoc | Boolean | 否 | 是否启用 Pandoc |
|
|
||||||
| storage_config | Object | 否 | 存储配置,不传则使用全局配置 |
|
|
||||||
| - type | String | 否 | 存储模式:local / minio |
|
|
||||||
| - endpoint | String | 否 | MinIO endpoint |
|
|
||||||
| - bucket | String | 否 | MinIO bucket |
|
|
||||||
| - access_key | String | 否 | MinIO access key |
|
|
||||||
| - secret_key | String | 否 | MinIO secret key |
|
|
||||||
| - use_ssl | Boolean | 否 | MinIO 是否使用 SSL |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"id": "kb_xxx",
|
|
||||||
"message": "Knowledge base created successfully"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 获取知识库列表
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/knowledge/list
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "kb_001",
|
|
||||||
"name": "产品文档知识库",
|
|
||||||
"description": "用于存储产品手册",
|
|
||||||
"llm_model_id": "model_001",
|
|
||||||
"embedding_model_id": "model_002",
|
|
||||||
"status": "active",
|
|
||||||
"document_count": 15,
|
|
||||||
"chunk_count": 156,
|
|
||||||
"created_at": "2024-01-15T10:30:00Z",
|
|
||||||
"updated_at": "2024-01-15T10:30:00Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 获取知识库详情
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/knowledge/:id
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"id": "kb_001",
|
|
||||||
"name": "产品文档知识库",
|
|
||||||
"description": "用于存储产品手册",
|
|
||||||
"llm_model_id": "model_001",
|
|
||||||
"embedding_model_id": "model_002",
|
|
||||||
"parsing_config": {
|
|
||||||
"engine": "markitdown",
|
|
||||||
"enable_pdf": true,
|
|
||||||
"pandoc": true
|
|
||||||
},
|
|
||||||
"status": "active",
|
|
||||||
"document_count": 15,
|
|
||||||
"chunk_count": 156,
|
|
||||||
"created_at": "2024-01-15T10:30:00Z",
|
|
||||||
"updated_at": "2024-01-15T10:30:00Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. 删除知识库
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
DELETE /api/knowledge/:id
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Knowledge base deleted"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. 获取知识库下的文档列表
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/knowledge/:id/documents
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| status | String | 否 | 过滤状态:all / parsed / parsing / failed |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "doc_001",
|
|
||||||
"knowledge_base_id": "kb_001",
|
|
||||||
"name": "产品手册_v2.0.pdf",
|
|
||||||
"file_key": "abc123.pdf",
|
|
||||||
"file_url": "http://localhost:8082/files/abc123.pdf",
|
|
||||||
"file_size": 2516582,
|
|
||||||
"status": "parsed",
|
|
||||||
"chunk_count": 156,
|
|
||||||
"uploaded_at": "2024-01-15T10:30:00Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. 上传文档到知识库
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/knowledge/:id/documents
|
|
||||||
Content-Type: multipart/form-data
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| file | File | 是 | 要上传的文件 |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"id": "doc_001",
|
|
||||||
"url": "http://localhost:8082/files/abc123.pdf",
|
|
||||||
"document": {
|
|
||||||
"id": "doc_001",
|
|
||||||
"knowledge_base_id": "kb_001",
|
|
||||||
"name": "产品手册_v2.0.pdf",
|
|
||||||
"file_size": 2516582,
|
|
||||||
"status": "parsing",
|
|
||||||
"chunk_count": 0,
|
|
||||||
"uploaded_at": "2024-01-15T10:30:00Z"
|
|
||||||
},
|
|
||||||
"message": "Document uploaded"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. 删除知识库文档
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
DELETE /api/knowledge/:id/documents/:doc_id
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Document deleted"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 8. 重新解析文档
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/knowledge/:id/documents/:doc_id/reparse
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Document reparse started"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 9. 获取文档预览内容
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/knowledge/:id/documents/:doc_id/preview
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| page | Number | 否 | 页码(默认 1) |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"total_pages": 3,
|
|
||||||
"current_page": 1,
|
|
||||||
"content": "第一章 产品介绍..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
# Model Settings 接口文档
|
|
||||||
|
|
||||||
## 接口列表
|
|
||||||
|
|
||||||
### 1. 获取模型列表
|
|
||||||
|
|
||||||
**接口地址:** `GET /model/list`
|
|
||||||
|
|
||||||
**返回参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"id": "xxx-xxx-xxx",
|
|
||||||
"name": "OpenAI",
|
|
||||||
"model_type": "chat",
|
|
||||||
"provider": "OpenAI",
|
|
||||||
"model": "gpt-4o",
|
|
||||||
"api_key": "sk-xxx",
|
|
||||||
"base_url": "https://api.openai.com",
|
|
||||||
"api_endpoint": "/v1/chat/completions",
|
|
||||||
"status": "active",
|
|
||||||
"created_at": "2024-01-01T00:00:00Z",
|
|
||||||
"updated_at": "2024-01-01T00:00:00Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 获取模型详情
|
|
||||||
|
|
||||||
**接口地址:** `GET /model/:id`
|
|
||||||
|
|
||||||
**返回参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "xxx-xxx-xxx",
|
|
||||||
"name": "OpenAI",
|
|
||||||
"model_type": "chat",
|
|
||||||
"provider": "OpenAI",
|
|
||||||
"model": "gpt-4o",
|
|
||||||
"api_key": "sk-xxx",
|
|
||||||
"base_url": "https://api.openai.com",
|
|
||||||
"api_endpoint": "/v1/chat/completions",
|
|
||||||
"status": "active",
|
|
||||||
"created_at": "2024-01-01T00:00:00Z",
|
|
||||||
"updated_at": "2024-01-01T00:00:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 创建模型
|
|
||||||
|
|
||||||
**接口地址:** `POST /model/add`
|
|
||||||
|
|
||||||
**请求参数:**
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| name | string | 是 | 模型名称 |
|
|
||||||
| model_type | string | 是 | 模型类型:chat/embedding/rerank/vlm |
|
|
||||||
| provider | string | 是 | 提供商:OpenAI/Ollama |
|
|
||||||
| model | string | 是 | 模型标识,如 gpt-4o |
|
|
||||||
| api_key | string | 是 | API 密钥 |
|
|
||||||
| base_url | string | 是 | 基础 URL |
|
|
||||||
| api_endpoint | string | 否 | API 端点路径 |
|
|
||||||
|
|
||||||
**请求示例:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "OpenAI",
|
|
||||||
"model_type": "chat",
|
|
||||||
"provider": "OpenAI",
|
|
||||||
"model": "gpt-4o",
|
|
||||||
"api_key": "sk-xxx",
|
|
||||||
"base_url": "https://api.openai.com",
|
|
||||||
"api_endpoint": "/v1/chat/completions"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "xxx-xxx-xxx",
|
|
||||||
"name": "OpenAI",
|
|
||||||
"model_type": "chat",
|
|
||||||
"provider": "OpenAI",
|
|
||||||
"model": "gpt-4o",
|
|
||||||
"api_key": "sk-xxx",
|
|
||||||
"base_url": "https://api.openai.com",
|
|
||||||
"api_endpoint": "/v1/chat/completions",
|
|
||||||
"status": "active",
|
|
||||||
"created_at": "2024-01-01T00:00:00Z",
|
|
||||||
"updated_at": "2024-01-01T00:00:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. 更新模型
|
|
||||||
|
|
||||||
**接口地址:** `PUT /model/:id`
|
|
||||||
|
|
||||||
**请求参数:**
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| name | string | 否 | 模型名称 |
|
|
||||||
| model_type | string | 否 | 模型类型:chat/embedding/rerank/vlm |
|
|
||||||
| provider | string | 否 | 提供商:OpenAI/Ollama |
|
|
||||||
| model | string | 否 | 模型标识 |
|
|
||||||
| api_key | string | 否 | API 密钥 |
|
|
||||||
| base_url | string | 否 | 基础 URL |
|
|
||||||
| api_endpoint | string | 否 | API 端点路径 |
|
|
||||||
| status | string | 否 | 状态:active/inactive |
|
|
||||||
|
|
||||||
**请求示例:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "OpenAI Updated",
|
|
||||||
"model_type": "chat",
|
|
||||||
"provider": "OpenAI",
|
|
||||||
"model": "gpt-4o",
|
|
||||||
"api_key": "sk-xxx",
|
|
||||||
"base_url": "https://api.openai.com",
|
|
||||||
"api_endpoint": "/v1/chat/completions",
|
|
||||||
"status": "active"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. 删除模型
|
|
||||||
|
|
||||||
**接口地址:** `DELETE /model/:id`
|
|
||||||
|
|
||||||
**返回参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. 测试连接
|
|
||||||
|
|
||||||
**接口地址:** `POST /model/test`
|
|
||||||
|
|
||||||
**请求参数:**
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| provider | string | 是 | 提供商:OpenAI/Ollama |
|
|
||||||
| model | string | 是 | 模型标识 |
|
|
||||||
| api_key | string | 是 | API 密钥 |
|
|
||||||
| base_url | string | 是 | 基础 URL |
|
|
||||||
| api_endpoint | string | 否 | API 端点路径 |
|
|
||||||
|
|
||||||
**请求示例:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"provider": "OpenAI",
|
|
||||||
"model": "gpt-4o",
|
|
||||||
"api_key": "sk-xxx",
|
|
||||||
"base_url": "https://api.openai.com",
|
|
||||||
"api_endpoint": "/v1/chat/completions"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Connection successful"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
或失败时:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"message": "HTTP 401: Unauthorized"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 数据结构
|
|
||||||
|
|
||||||
### ModelInfo 模型信息
|
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| id | string | 主键 UUID |
|
|
||||||
| name | string | 模型名称 |
|
|
||||||
| model_type | string | 模型类型:chat/embedding/rerank/vlm |
|
|
||||||
| provider | string | 提供商:OpenAI/Ollama |
|
|
||||||
| model | string | 模型标识 |
|
|
||||||
| api_key | string | API 密钥 |
|
|
||||||
| base_url | string | 基础 URL |
|
|
||||||
| api_endpoint | string | API 端点路径 |
|
|
||||||
| status | string | 状态:active/inactive |
|
|
||||||
| created_at | datetime | 创建时间 |
|
|
||||||
| updated_at | datetime | 更新时间 |
|
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
# Neo4j 连接测试
|
|
||||||
|
|
||||||
## 接口地址
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /neo4j/check
|
|
||||||
```
|
|
||||||
|
|
||||||
## 请求参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| host | string | 是 | Neo4j 主机 |
|
|
||||||
| port | int | 是 | Neo4j 端口(默认 7687) |
|
|
||||||
| username | string | 是 | 用户名(默认 neo4j) |
|
|
||||||
| password | string | 是 | 密码 |
|
|
||||||
| database | string | 否 | 数据库名(默认 neo4j) |
|
|
||||||
| name | string | 否 | 数据库名称,用于保存记录 |
|
|
||||||
| uri | string | 否 | Neo4j 连接地址(bolt://host:port) |
|
|
||||||
| description | string | 否 | 数据库描述 |
|
|
||||||
|
|
||||||
## 请求示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 7687,
|
|
||||||
"username": "neo4j",
|
|
||||||
"password": "password",
|
|
||||||
"database": "neo4j",
|
|
||||||
"name": "My Neo4j Database"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 返回参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| success | bool | 是否连接成功 |
|
|
||||||
| message | string | 消息 |
|
|
||||||
| version | string | Neo4j 版本 |
|
|
||||||
| databases | array | 数据库列表 |
|
|
||||||
| databaseId | string | 数据库记录 ID(新增) |
|
|
||||||
| name | string | 数据库名称(新增) |
|
|
||||||
| description | string | 数据库描述(新增) |
|
|
||||||
|
|
||||||
## 返回示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "connection successful",
|
|
||||||
"version": "5.14.0",
|
|
||||||
"databases": ["neo4j", "system"],
|
|
||||||
"databaseId": "abc-123-def",
|
|
||||||
"name": "Neo4j-neo4j",
|
|
||||||
"description": "Neo4j neo4j@localhost:7687"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **说明**:连接成功时,后端会自动检查数据库记录是否存在,不存在则创建并返回 `databaseId`、`name` 和 `description`。前端可使用这些信息进行后续保存图谱操作。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Neo4j 获取图谱概览数据(核心接口)
|
|
||||||
|
|
||||||
获取所有标签(Labels)和关系类型(Relationship Types)的统计信息,用于前端图谱可视化。
|
|
||||||
|
|
||||||
## 接口地址
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /neo4j/graphs
|
|
||||||
```
|
|
||||||
|
|
||||||
## 请求参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| uri | string | 是 | Neo4j 连接地址,如 bolt://localhost:7687 |
|
|
||||||
| username | string | 是 | 用户名 |
|
|
||||||
| password | string | 是 | 密码 |
|
|
||||||
| database | string | 否 | 数据库名(默认 neo4j) |
|
|
||||||
|
|
||||||
## 请求示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"uri": "bolt://localhost:7687",
|
|
||||||
"username": "neo4j",
|
|
||||||
"password": "password",
|
|
||||||
"database": "neo4j"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 返回参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| success | bool | 是否成功 |
|
|
||||||
| graphs | object | 图谱数据 |
|
|
||||||
|
|
||||||
### graphs 对象
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|
|
|
||||||
| labels------|------| | array | 标签列表 |
|
|
||||||
| relationshipTypes | array | 关系类型列表 |
|
|
||||||
|
|
||||||
### labels 数组项
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| name | string | 标签名称 |
|
|
||||||
| count | int | 该标签的节点数量 |
|
|
||||||
|
|
||||||
### relationshipTypes 数组项
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| name | string | 关系类型名称 |
|
|
||||||
| count | int | 该关系的数量 |
|
|
||||||
|
|
||||||
## 返回示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"graphs": {
|
|
||||||
"labels": [
|
|
||||||
{"name": "User", "count": 1523},
|
|
||||||
{"name": "Order", "count": 856},
|
|
||||||
{"name": "Product", "count": 2341},
|
|
||||||
{"name": "Category", "count": 45},
|
|
||||||
{"name": "Review", "count": 5678}
|
|
||||||
],
|
|
||||||
"relationshipTypes": [
|
|
||||||
{"name": "KNOWS", "count": 2341},
|
|
||||||
{"name": "BOUGHT", "count": 5678},
|
|
||||||
{"name": "BELONGS_TO", "count": 2341},
|
|
||||||
{"name": "HAS_REVIEW", "count": 5678},
|
|
||||||
{"name": "LOCATED_IN", "count": 1523}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 前端使用说明
|
|
||||||
|
|
||||||
前端使用 ECharts 力导向图谱展示:
|
|
||||||
- `labels` 生成图谱节点,count 决定节点大小
|
|
||||||
- `relationshipTypes` 生成图谱边
|
|
||||||
- 建议返回至少 5-10 个关系类型以便生成丰富图谱
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Neo4j 获取节点详情
|
|
||||||
|
|
||||||
## 接口地址
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /neo4j/nodes
|
|
||||||
```
|
|
||||||
|
|
||||||
## 请求参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| uri | string | 是 | Neo4j 连接地址,如 bolt://localhost:7687 |
|
|
||||||
| username | string | 是 | 用户名 |
|
|
||||||
| password | string | 是 | 密码 |
|
|
||||||
| database | string | 否 | 数据库名 |
|
|
||||||
| label | string | 是 | 节点标签名 |
|
|
||||||
| limit | int | 否 | 返回数量限制,默认 10 |
|
|
||||||
|
|
||||||
## 请求示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"uri": "bolt://localhost:7687",
|
|
||||||
"username": "neo4j",
|
|
||||||
"password": "password",
|
|
||||||
"label": "User",
|
|
||||||
"limit": 10
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 返回参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| success | bool | 是否成功 |
|
|
||||||
| message | string | 消息 |
|
|
||||||
| nodes | array | 节点数据列表 |
|
|
||||||
|
|
||||||
## 返回示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "success",
|
|
||||||
"nodes": [
|
|
||||||
{"id": "1", "name": "张三", "email": "zhangsan@example.com"},
|
|
||||||
{"id": "2", "name": "李四", "email": "lisi@example.com"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Neo4j 获取关系详情
|
|
||||||
|
|
||||||
## 接口地址
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /neo4j/relationships
|
|
||||||
```
|
|
||||||
|
|
||||||
## 请求参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| uri | string | 是 | Neo4j 连接地址 |
|
|
||||||
| username | string | 是 | 用户名 |
|
|
||||||
| password | string | 是 | 密码 |
|
|
||||||
| database | string | 否 | 数据库名 |
|
|
||||||
| relationship_type | string | 是 | 关系类型名 |
|
|
||||||
| limit | int | 否 | 返回数量限制,默认 10 |
|
|
||||||
|
|
||||||
## 请求示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"uri": "bolt://localhost:7687",
|
|
||||||
"username": "neo4j",
|
|
||||||
"password": "password",
|
|
||||||
"relationship_type": "KNOWS",
|
|
||||||
"limit": 10
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 返回参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| success | bool | 是否成功 |
|
|
||||||
| message | string | 消息 |
|
|
||||||
| relationships | array | 关系数据列表 |
|
|
||||||
|
|
||||||
## 返回示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "success",
|
|
||||||
"relationships": [
|
|
||||||
{
|
|
||||||
"startId": "1",
|
|
||||||
"endId": "2",
|
|
||||||
"startLabels": ["User"],
|
|
||||||
"endLabels": ["User"],
|
|
||||||
"since": "2020-01-01"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# Neo4j 图谱保存接口需求
|
|
||||||
|
|
||||||
## 需求说明
|
|
||||||
|
|
||||||
前端需要保存 Neo4j 图谱的连接信息,以便后续快速加载和查看。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 接口地址
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /database/graph/save
|
|
||||||
```
|
|
||||||
|
|
||||||
## 请求参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| databaseId | string | 是 | 数据库 ID |
|
|
||||||
| databaseName | string | 是 | 数据库名称 |
|
|
||||||
| uri | string | 是 | Neo4j 连接地址,如 bolt://localhost:7687 |
|
|
||||||
| username | string | 是 | 用户名 |
|
|
||||||
| labels | array | 是 | 标签列表 |
|
|
||||||
| relationshipTypes | array | 是 | 关系类型列表 |
|
|
||||||
| selectedLabel | string | 否 | 当前选中的标签 |
|
|
||||||
|
|
||||||
## 请求示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"databaseId": "123",
|
|
||||||
"databaseName": "neo4j",
|
|
||||||
"uri": "bolt://10.10.10.189:7687",
|
|
||||||
"username": "neo4j",
|
|
||||||
"labels": ["User", "Order", "Product"],
|
|
||||||
"relationshipTypes": ["KNOWS", "BOUGHT", "BELONGS_TO"],
|
|
||||||
"selectedLabel": "User"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 返回参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| success | bool | 是否成功 |
|
|
||||||
| message | string | 消息 |
|
|
||||||
|
|
||||||
## 返回示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "保存成功"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 前端调用示例
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
fetch('/database/graph/save', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
databaseId: '123',
|
|
||||||
databaseName: 'neo4j',
|
|
||||||
uri: 'bolt://10.10.10.189:7687',
|
|
||||||
username: 'neo4j',
|
|
||||||
labels: ['User', 'Order', 'Product'],
|
|
||||||
relationshipTypes: ['KNOWS', 'BOUGHT', 'BELONGS_TO'],
|
|
||||||
selectedLabel: 'User',
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
```
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# 获取子表列表
|
|
||||||
|
|
||||||
## 接口地址
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /sub-table/database/:database_id
|
|
||||||
```
|
|
||||||
|
|
||||||
## 路径参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| database_id | string | 是 | 数据库ID |
|
|
||||||
|
|
||||||
## 返回参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| list | array | 子表列表 |
|
|
||||||
|
|
||||||
### list[] 详情
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| id | string | 子表ID |
|
|
||||||
| database_id | string | 关联的数据库ID |
|
|
||||||
| parent_table | string | 原始表名 |
|
|
||||||
| sub_table_name | string | 子表别名 |
|
|
||||||
| sub_table_comment | string | 子表注释 |
|
|
||||||
| mapping_type | string | 映射类型 |
|
|
||||||
| relation_field | string | 关联字段 |
|
|
||||||
| relation_type | string | 关联类型 |
|
|
||||||
| fields | array | 字段映射列表 |
|
|
||||||
| ddl | string | 建表 DDL(带 COMMENT) |
|
|
||||||
| created_at | string | 创建时间 |
|
|
||||||
|
|
||||||
### fields[] 详情
|
|
||||||
|
|
||||||
| 参数 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| column_name | string | 列名 |
|
|
||||||
| mapped_name | string | 中文映射名 |
|
|
||||||
|
|
||||||
## 返回示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"id": "xxx-xxx",
|
|
||||||
"database_id": "database-xxx",
|
|
||||||
"parent_table": "users",
|
|
||||||
"sub_table_name": "用户表",
|
|
||||||
"sub_table_comment": "用户信息",
|
|
||||||
"mapping_type": "horizontal",
|
|
||||||
"relation_field": "id",
|
|
||||||
"relation_type": "one_to_many",
|
|
||||||
"fields": [
|
|
||||||
{"column_name": "id", "mapped_name": "用户ID"},
|
|
||||||
{"column_name": "name", "mapped_name": "用户名"}
|
|
||||||
],
|
|
||||||
"ddl": "CREATE TABLE `users` (\n `id` int(10) unsigned NOT NULL COMMENT '用户ID'\n ...\n)",
|
|
||||||
"created_at": "2026-03-06T15:00:00Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用场景
|
|
||||||
|
|
||||||
用于恢复映射状态:
|
|
||||||
1. 用户点击已存在的数据库的 "Map Tables" 按钮
|
|
||||||
2. 调用此接口获取已保存的子表信息
|
|
||||||
3. 根据 `parent_table` 勾选已选择的表
|
|
||||||
4. 根据 `fields` 恢复字段映射
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
# 文件上传 API
|
|
||||||
|
|
||||||
## 基础信息
|
|
||||||
|
|
||||||
| 项目 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| 基础URL | `http://localhost:8082` |
|
|
||||||
| 上传模式 | local / minio(配置决定) |
|
|
||||||
|
|
||||||
## 配置说明
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# config.yaml
|
|
||||||
upload_mode: "local" # 上传模式:local 或 minio
|
|
||||||
upload_local_path: "resource/files" # 本地存储路径
|
|
||||||
server_base_url: "http://localhost:8082" # 服务器基础URL
|
|
||||||
|
|
||||||
# MinIO 配置(upload_mode 为 minio 时需要)
|
|
||||||
minio_endpoint: "localhost:9000"
|
|
||||||
minio_access_key: "your-access-key"
|
|
||||||
minio_secret_key: "your-secret-key"
|
|
||||||
minio_bucket: "x-agents"
|
|
||||||
minio_use_ssl: false
|
|
||||||
```
|
|
||||||
|
|
||||||
## 接口列表
|
|
||||||
|
|
||||||
### 1. 上传文件
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/file_upload
|
|
||||||
Content-Type: multipart/form-data
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| file | File | 是 | 要上传的文件 |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"url": "http://localhost:8082/files/abc123.pdf",
|
|
||||||
"fileKey": "abc123",
|
|
||||||
"message": "Upload successful"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**错误响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"message": "File too large (max 100MB)"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 删除文件
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
DELETE /api/file_upload/:filename
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| filename | String | 是 | 文件名(不含路径) |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "File deleted"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 访问文件(仅本地模式)
|
|
||||||
|
|
||||||
文件上传后,本地模式下可通过以下 URL 直接访问:
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /files/{filename}
|
|
||||||
```
|
|
||||||
|
|
||||||
例如:`http://localhost:8082/files/abc123.pdf`
|
|
||||||
|
|
||||||
> 注意:MinIO 模式返回的是预签名 URL,有效期 24 小时。
|
|
||||||
160
teams/chat_history_plan.md
Normal file
160
teams/chat_history_plan.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# 对话内容记录实现计划
|
||||||
|
|
||||||
|
> 创建时间:2026-03-13 11:30
|
||||||
|
> 计划周期:2026-03-13 ~ 2026-03-15
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
在 Chat 页面创建新对话时,持久化记录对话内容,支持历史会话查看和刷新页面不丢失。
|
||||||
|
|
||||||
|
## 一、数据库设计
|
||||||
|
|
||||||
|
### 1. 会话表 (chat_sessions)
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS chat_sessions (
|
||||||
|
id VARCHAR(191) PRIMARY KEY,
|
||||||
|
user_id VARCHAR(191) NOT NULL,
|
||||||
|
agent_id VARCHAR(191) NOT NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
model_id VARCHAR(191),
|
||||||
|
status VARCHAR(20) DEFAULT 'active', -- active/archived
|
||||||
|
created_at DATETIME(3),
|
||||||
|
updated_at DATETIME(3),
|
||||||
|
INDEX idx_sessions_user (user_id),
|
||||||
|
INDEX idx_sessions_agent (agent_id),
|
||||||
|
INDEX idx_sessions_updated (updated_at DESC)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 消息表 (chat_messages)
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS chat_messages (
|
||||||
|
id VARCHAR(191) PRIMARY KEY,
|
||||||
|
session_id VARCHAR(191) NOT NULL,
|
||||||
|
role VARCHAR(20) NOT NULL, -- user/assistant/system
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
tokens_used INT DEFAULT 0,
|
||||||
|
duration_ms INT DEFAULT 0,
|
||||||
|
metadata TEXT, -- JSON格式存储额外信息
|
||||||
|
created_at DATETIME(3),
|
||||||
|
INDEX idx_messages_session (session_id),
|
||||||
|
INDEX idx_messages_created (created_at)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 二、后端接口设计
|
||||||
|
|
||||||
|
### 1. 会话管理
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| POST | /chat/sessions | 创建新会话 |
|
||||||
|
| GET | /chat/sessions | 获取用户会话列表 |
|
||||||
|
| GET | /chat/sessions/:id | 获取会话详情(含消息) |
|
||||||
|
| PUT | /chat/sessions/:id | 更新会话(如标题) |
|
||||||
|
| DELETE | /chat/sessions/:id | 删除会话 |
|
||||||
|
|
||||||
|
### 2. 消息管理
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | /chat/sessions/:id/messages | 获取会话消息历史 |
|
||||||
|
| POST | /chat/messages | 记录单条消息(可选) |
|
||||||
|
|
||||||
|
## 三、实现步骤
|
||||||
|
|
||||||
|
### 阶段一:后端 (小尧、小陈)
|
||||||
|
- [ ] 1. 创建 chat_sessions 和 chat_messages 表的 GORM 模型
|
||||||
|
- [ ] 2. 添加数据库表初始化代码到 main.go
|
||||||
|
- [ ] 3. 实现 SessionRepository (会话 CRUD)
|
||||||
|
- [ ] 4. 实现 MessageRepository (消息 CRUD)
|
||||||
|
- [ ] 5. 创建 ChatSessionHandler 和 ChatMessageHandler
|
||||||
|
- [ ] 6. 注册路由并测试接口
|
||||||
|
|
||||||
|
### 阶段二:Python 服务 (小泽、小明)
|
||||||
|
- [ ] 1. 修改 api/routes.py 接入后端会话服务
|
||||||
|
- [ ] 2. 每次对话结束后调用后端保存消息
|
||||||
|
- [ ] 3. 支持历史消息加载(从后端获取)
|
||||||
|
- [ ] 4. 异常处理和重试机制
|
||||||
|
|
||||||
|
### 阶段三:前端 (小荣、狗蛋)
|
||||||
|
- [ ] 1. 创建 useChatSession composable
|
||||||
|
- [ ] 2. 新建对话时调用后端创建会话
|
||||||
|
- [ ] 3. 发送消息时持久化到后端
|
||||||
|
- [ ] 4. 加载历史会话和消息
|
||||||
|
- [ ] 5. 会话列表展示和切换
|
||||||
|
|
||||||
|
## 四、详细时间计划
|
||||||
|
|
||||||
|
### 阶段一:后端开发 (2026-03-13 ~ 2026-03-13)
|
||||||
|
| 日期 | 任务 | 负责人 |
|
||||||
|
|------|------|--------|
|
||||||
|
| 03-13 下午 | 创建 chat_sessions 和 chat_messages 表的 GORM 模型 | 小尧 |
|
||||||
|
| 03-13 下午 | 添加数据库表初始化代码到 main.go | 小尧 |
|
||||||
|
| 03-13 下午 | 实现 SessionRepository (会话 CRUD) | 小尧 |
|
||||||
|
| 03-13 下午 | 实现 MessageRepository (消息 CRUD) | 小陈 |
|
||||||
|
| 03-13 下午 | 创建 ChatSessionHandler 和 ChatMessageHandler | 小陈 |
|
||||||
|
| 03-13 下午 | 注册路由并测试接口 | 小陈 |
|
||||||
|
|
||||||
|
### 阶段二:Python 服务接入 (2026-03-14)
|
||||||
|
| 日期 | 任务 | 负责人 |
|
||||||
|
|------|------|--------|
|
||||||
|
| 03-14 上午 | 修改 api/routes.py 接入后端会话服务 | 小泽 |
|
||||||
|
| 03-14 上午 | 每次对话结束后调用后端保存消息 | 小泽 |
|
||||||
|
| 03-14 下午 | 支持历史消息加载(从后端获取) | 小明 |
|
||||||
|
| 03-14 下午 | 异常处理和重试机制 | 小明 |
|
||||||
|
|
||||||
|
### 阶段三:前端对接 (2026-03-14 ~ 2026-03-15)
|
||||||
|
| 日期 | 任务 | 负责人 |
|
||||||
|
|------|------|--------|
|
||||||
|
| 03-14 下午 | 创建 useChatSession composable | 小荣 |
|
||||||
|
| 03-14 下午 | 新建对话时调用后端创建会话 | 小荣 |
|
||||||
|
| 03-15 上午 | 发送消息时持久化到后端 | 小荣 |
|
||||||
|
| 03-15 上午 | 加载历史会话和消息 | 狗蛋 |
|
||||||
|
| 03-15 下午 | 会话列表展示和切换 | 狗蛋 |
|
||||||
|
|
||||||
|
## 五、技术细节
|
||||||
|
|
||||||
|
### 前端消息流程
|
||||||
|
```
|
||||||
|
1. 用户点击"新建对话"
|
||||||
|
→ 调用 POST /chat/sessions 创建会话
|
||||||
|
→ 保存 sessionId 到状态
|
||||||
|
|
||||||
|
2. 用户发送消息
|
||||||
|
→ 前端发送消息到 Python 服务
|
||||||
|
→ Python 服务返回响应后
|
||||||
|
→ 前端调用 POST /chat/messages 保存用户消息
|
||||||
|
→ 前端调用 POST /chat/messages 保存 AI 响应
|
||||||
|
|
||||||
|
3. 加载历史会话
|
||||||
|
→ 调用 GET /chat/sessions 获取会话列表
|
||||||
|
→ 选择会话后调用 GET /chat/sessions/:id/messages 获取消息
|
||||||
|
```
|
||||||
|
|
||||||
|
### 消息结构
|
||||||
|
```typescript
|
||||||
|
interface ChatMessage {
|
||||||
|
id: string
|
||||||
|
session_id: string
|
||||||
|
role: 'user' | 'assistant' | 'system'
|
||||||
|
content: string
|
||||||
|
tokens_used?: number
|
||||||
|
duration_ms?: number
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 五、分工汇总
|
||||||
|
|
||||||
|
| 人员 | 任务 | 阶段 |
|
||||||
|
|------|------|------|
|
||||||
|
| 小尧 | 后端 - 会话/消息模型和 Repository | 阶段一 |
|
||||||
|
| 小陈 | 后端 - Handler 和路由注册 | 阶段一 |
|
||||||
|
| 小泽 | Python - 接入后端会话服务 | 阶段二 |
|
||||||
|
| 小明 | Python - 历史消息加载 | 阶段二 |
|
||||||
|
| 小荣 | 前端 - 会话管理和消息持久化 | 阶段三 |
|
||||||
|
| 狗蛋 | 前端 - UI 对接和测试 | 阶段三 |
|
||||||
|
|
||||||
|
## 六、待确认问题
|
||||||
|
1. 是否需要支持消息撤回/编辑?
|
||||||
|
2. 会话标题是否需要 AI 自动生成还是用户手动输入?
|
||||||
|
3. 是否需要消息搜索功能?
|
||||||
@@ -1,874 +0,0 @@
|
|||||||
# 多智能体群聊系统实现计划
|
|
||||||
|
|
||||||
## 项目概述
|
|
||||||
|
|
||||||
实现类似"一人公司"的多智能体群聊系统,支持多个 Agent 在群聊中讨论问题,类似于人类的团队会议。
|
|
||||||
|
|
||||||
### 核心特性
|
|
||||||
- **三阶段流程**: 观点提出 → 讨论完善 → CEO 总结决策
|
|
||||||
- **智能轮数**: AI 判断讨论是否充分,自动决定轮数
|
|
||||||
- **用户插话**: 用户可以随时打断发表意见
|
|
||||||
- **角色扮演**: 可配置不同角色的 Agent(CEO、CTO、Designer 等)
|
|
||||||
- **复用架构**: 复用现有的 LLM、ToolRegistry、SessionManager
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 一、系统架构
|
|
||||||
|
|
||||||
### 1.1 整体架构图
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ Agent Group Chat System │
|
|
||||||
│ ┌─────────────────────────────────────────────────────────────┐│
|
|
||||||
│ │ GroupChatManager ││
|
|
||||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││
|
|
||||||
│ │ │ Presenter │ │ Discusser │ │ Summarizer │ ││
|
|
||||||
│ │ │ Controller │ │ Controller │ │ Controller │ ││
|
|
||||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ ││
|
|
||||||
│ │ │ │ │ ││
|
|
||||||
│ │ └────────────────┴────────────────┘ ││
|
|
||||||
│ │ │ ││
|
|
||||||
│ │ SmartRoundController ││
|
|
||||||
│ └─────────────────────────────────────────────────────────────┘│
|
|
||||||
│ │ │
|
|
||||||
│ ┌──────────────────┼──────────────────┐ │
|
|
||||||
│ ▼ ▼ ▼ │
|
|
||||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
||||||
│ │ CEO Agent │ │ CTO Agent │ │Designer Agent│ │
|
|
||||||
│ │ Participant │ │ Participant │ │ Participant │ │
|
|
||||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
||||||
│ │ │ │ │
|
|
||||||
│ └──────────────────┴──────────────────┘ │
|
|
||||||
│ │ │
|
|
||||||
│ ▼ │
|
|
||||||
│ ┌─────────────┐ │
|
|
||||||
│ │GroupContext │ │
|
|
||||||
│ │(共享上下文) │ │
|
|
||||||
│ └─────────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 核心组件
|
|
||||||
|
|
||||||
| 组件 | 职责 | 文件位置 |
|
|
||||||
|------|------|----------|
|
|
||||||
| **GroupChatManager** | 群聊管理器,控制整体流程 | `group_chat/manager.py` |
|
|
||||||
| **Participant** | 参与群聊的 Agent 实例 | `group_chat/participant.py` |
|
|
||||||
| **StageController** | 阶段控制器,管理各阶段 | `group_chat/stages/controller.py` |
|
|
||||||
| **PresenterStage** | 观点提出阶段 | `group_chat/stages/presenter.py` |
|
|
||||||
| **DiscusserStage** | 讨论完善阶段 | `group_chat/stages/discusser.py` |
|
|
||||||
| **SummarizerStage** | 总结决策阶段 | `group_chat/stages/summarizer.py` |
|
|
||||||
| **SmartRoundController** | 智能轮数控制 | `group_chat/round_controller.py` |
|
|
||||||
| **GroupContext** | 共享讨论上下文 | `group_chat/context.py` |
|
|
||||||
| **GroupMessage** | 群聊消息 | `group_chat/message.py` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 二、数据结构设计
|
|
||||||
|
|
||||||
### 2.1 消息定义
|
|
||||||
|
|
||||||
```python
|
|
||||||
# group_chat/message.py
|
|
||||||
from typing import Optional
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
from datetime import datetime
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class MessageStage(str, Enum):
|
|
||||||
"""消息所属阶段"""
|
|
||||||
PRESENT = "present" # 观点提出
|
|
||||||
DISCUSS = "discuss" # 讨论完善
|
|
||||||
SUMMARIZE = "summarize" # 总结决策
|
|
||||||
|
|
||||||
|
|
||||||
class GroupMessage(BaseModel):
|
|
||||||
"""群聊消息"""
|
|
||||||
id: str = Field(..., description="消息唯一标识")
|
|
||||||
agent_id: str = Field(..., description="Agent ID")
|
|
||||||
agent_name: str = Field(..., description="Agent 名称")
|
|
||||||
agent_role: str = Field(..., description="Agent 角色")
|
|
||||||
content: str = Field(..., description="消息内容")
|
|
||||||
timestamp: datetime = Field(default_factory=datetime.now)
|
|
||||||
stage: MessageStage = Field(..., description="所属阶段")
|
|
||||||
round: int = Field(default=1, description="轮数")
|
|
||||||
replying_to: Optional[str] = Field(default=None, description="回复的消息ID")
|
|
||||||
|
|
||||||
|
|
||||||
class UserInterruption(BaseModel):
|
|
||||||
"""用户插话"""
|
|
||||||
id: str
|
|
||||||
content: str
|
|
||||||
timestamp: datetime = Field(default_factory=datetime.now)
|
|
||||||
stage: MessageStage
|
|
||||||
round: int
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 共享上下文
|
|
||||||
|
|
||||||
```python
|
|
||||||
# group_chat/context.py
|
|
||||||
from typing import Optional
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
from .message import GroupMessage, UserInterruption
|
|
||||||
|
|
||||||
|
|
||||||
class GroupContext(BaseModel):
|
|
||||||
"""群聊共享上下文"""
|
|
||||||
topic: str = Field(..., description="讨论主题")
|
|
||||||
stage: str = Field(default="present", description="当前阶段")
|
|
||||||
round: int = Field(default=1, description="当前轮数")
|
|
||||||
messages: list[GroupMessage] = Field(default_factory=list)
|
|
||||||
user_interruptions: list[UserInterruption] = Field(default_factory=list)
|
|
||||||
final_decision: Optional[str] = Field(default=None, description="最终决策")
|
|
||||||
status: str = Field(default="running", description="running/completed/failed")
|
|
||||||
|
|
||||||
|
|
||||||
class GroupState(BaseModel):
|
|
||||||
"""群聊状态"""
|
|
||||||
context: GroupContext
|
|
||||||
participants: dict[str, dict] # {agent_id: config}
|
|
||||||
config: dict # 群聊配置
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 三、核心实现
|
|
||||||
|
|
||||||
### 3.1 Agent 角色定义
|
|
||||||
|
|
||||||
```python
|
|
||||||
# group_chat/roles.py
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
# 默认角色配置
|
|
||||||
DEFAULT_ROLES = {
|
|
||||||
"ceo": {
|
|
||||||
"id": "ceo",
|
|
||||||
"name": "CEO",
|
|
||||||
"role": "首席执行官",
|
|
||||||
"system_prompt": """你是一家公司的 CEO,负责制定公司战略方向和重大决策。
|
|
||||||
你的特点是:高瞻远瞩、战略思维、注重长远利益。
|
|
||||||
在讨论中,你应该:
|
|
||||||
- 从公司整体战略角度分析问题
|
|
||||||
- 权衡短期和长期利益
|
|
||||||
- 做最终决策并承担责任
|
|
||||||
- 善于总结和归纳各方观点""",
|
|
||||||
"stages": ["present", "summarize"],
|
|
||||||
"is_final_decider": True,
|
|
||||||
"priority": 1
|
|
||||||
},
|
|
||||||
"cto": {
|
|
||||||
"id": "cto",
|
|
||||||
"name": "CTO",
|
|
||||||
"role": "首席技术官",
|
|
||||||
"system_prompt": """你是一家公司的 CTO,负责技术决策和技术团队管理。
|
|
||||||
你的特点是:技术深厚、注重可行性、关注技术趋势。
|
|
||||||
在讨论中,你应该:
|
|
||||||
- 从技术角度分析方案的可行性和风险
|
|
||||||
- 提供技术实现建议
|
|
||||||
- 评估技术选型
|
|
||||||
- 关注系统的可扩展性和稳定性""",
|
|
||||||
"stages": ["present", "discuss"],
|
|
||||||
"is_final_decider": False,
|
|
||||||
"priority": 2
|
|
||||||
},
|
|
||||||
"designer": {
|
|
||||||
"id": "designer",
|
|
||||||
"name": "Designer",
|
|
||||||
"role": "产品设计师",
|
|
||||||
"system_prompt": """你是公司的产品设计师,负责用户体验和界面设计。
|
|
||||||
你的特点是:创意丰富、注重用户体验、审美在线。
|
|
||||||
在讨论中,你应该:
|
|
||||||
- 从用户角度分析问题
|
|
||||||
- 提出用户体验优化建议
|
|
||||||
- 关注产品细节
|
|
||||||
- 平衡美观和实用性""",
|
|
||||||
"stages": ["present", "discuss"],
|
|
||||||
"is_final_decider": False,
|
|
||||||
"priority": 3
|
|
||||||
},
|
|
||||||
"analyst": {
|
|
||||||
"id": "analyst",
|
|
||||||
"name": "Analyst",
|
|
||||||
"role": "数据分析师",
|
|
||||||
"system_prompt": """你是公司的数据分析师,负责数据分析和决策支持。
|
|
||||||
你的特点是:数据敏感、逻辑严谨、注重证据。
|
|
||||||
在讨论中,你应该:
|
|
||||||
- 用数据支撑观点
|
|
||||||
- 提供数据分析结果
|
|
||||||
- 指出数据驱动的机会和风险
|
|
||||||
- 关注关键指标""",
|
|
||||||
"stages": ["present", "discuss"],
|
|
||||||
"is_final_decider": False,
|
|
||||||
"priority": 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 Participant(参与者 Agent)
|
|
||||||
|
|
||||||
```python
|
|
||||||
# group_chat/participant.py
|
|
||||||
import uuid
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Optional
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
|
||||||
from langchain_core.messages import HumanMessage, SystemMessage
|
|
||||||
|
|
||||||
from .message import GroupMessage, MessageStage
|
|
||||||
from .context import GroupContext
|
|
||||||
|
|
||||||
|
|
||||||
class Participant:
|
|
||||||
"""参与群聊的 Agent"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
llm: BaseChatModel,
|
|
||||||
config: dict,
|
|
||||||
stage: str = "present"
|
|
||||||
):
|
|
||||||
self.llm = llm
|
|
||||||
self.config = config
|
|
||||||
self.id = config["id"]
|
|
||||||
self.name = config["name"]
|
|
||||||
self.role = config["role"]
|
|
||||||
self.system_prompt = config["system_prompt"]
|
|
||||||
self.stages = config.get("stages", ["present", "discuss", "summarize"])
|
|
||||||
self.is_final_decider = config.get("is_final_decider", False)
|
|
||||||
|
|
||||||
async def generate_message(
|
|
||||||
self,
|
|
||||||
context: GroupContext,
|
|
||||||
stage: MessageStage,
|
|
||||||
round_num: int,
|
|
||||||
replying_to: Optional[str] = None
|
|
||||||
) -> GroupMessage:
|
|
||||||
"""生成消息"""
|
|
||||||
|
|
||||||
# 构建 prompt
|
|
||||||
prompt = self._build_prompt(context, stage, round_num)
|
|
||||||
|
|
||||||
# 调用 LLM
|
|
||||||
response = await self.llm.ainvoke([
|
|
||||||
SystemMessage(content=prompt),
|
|
||||||
HumanMessage(content=f"请就「{context.topic}」发表你的观点。")
|
|
||||||
])
|
|
||||||
|
|
||||||
content = response.content if hasattr(response, 'content') else str(response)
|
|
||||||
|
|
||||||
# 创建消息
|
|
||||||
message = GroupMessage(
|
|
||||||
id=str(uuid.uuid4()),
|
|
||||||
agent_id=self.id,
|
|
||||||
agent_name=self.name,
|
|
||||||
agent_role=self.role,
|
|
||||||
content=content,
|
|
||||||
timestamp=datetime.now(),
|
|
||||||
stage=stage,
|
|
||||||
round=round_num,
|
|
||||||
replying_to=replying_to
|
|
||||||
)
|
|
||||||
|
|
||||||
return message
|
|
||||||
|
|
||||||
def _build_prompt(self, context: GroupContext, stage: MessageStage, round_num: int) -> str:
|
|
||||||
"""构建 prompt"""
|
|
||||||
|
|
||||||
base_prompt = self.system_prompt
|
|
||||||
|
|
||||||
# 添加上下文
|
|
||||||
context_info = f"""
|
|
||||||
## 当前讨论
|
|
||||||
主题:{context.topic}
|
|
||||||
阶段:{stage.value}
|
|
||||||
轮数:{round_num}
|
|
||||||
|
|
||||||
## 历史消息
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 添加历史消息(限制数量)
|
|
||||||
recent_messages = context.messages[-10:] if context.messages else []
|
|
||||||
for msg in recent_messages:
|
|
||||||
context_info += f"\n【{msg.agent_name}】{msg.content}\n"
|
|
||||||
|
|
||||||
# 添加用户插话
|
|
||||||
if context.user_interruptions:
|
|
||||||
context_info += "\n## 用户插话\n"
|
|
||||||
for interruption in context.user_interruptions[-3:]:
|
|
||||||
context_info += f"\n【用户】{interruption.content}\n"
|
|
||||||
|
|
||||||
# 阶段特定的指令
|
|
||||||
stage_instruction = {
|
|
||||||
"present": "请简洁地提出你的观点和建议。",
|
|
||||||
"discuss": "请回应其他人的观点,进行讨论和完善。",
|
|
||||||
"summarize": "请总结各方观点,给出最终决策建议。"
|
|
||||||
}
|
|
||||||
|
|
||||||
full_prompt = f"{base_prompt}\n\n{context_info}\n\n{stage_instruction.get(stage.value, '')}"
|
|
||||||
|
|
||||||
return full_prompt
|
|
||||||
|
|
||||||
def can_participate(self, stage: MessageStage) -> bool:
|
|
||||||
"""判断是否可以参与当前阶段"""
|
|
||||||
return stage.value in self.stages
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 智能轮数控制器
|
|
||||||
|
|
||||||
```python
|
|
||||||
# group_chat/round_controller.py
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
|
||||||
from langchain_core.messages import SystemMessage, HumanMessage
|
|
||||||
|
|
||||||
from .context import GroupContext
|
|
||||||
from .message import MessageStage
|
|
||||||
|
|
||||||
|
|
||||||
class SmartRoundController:
|
|
||||||
"""智能轮数控制器"""
|
|
||||||
|
|
||||||
def __init__(self, llm: BaseChatModel, max_rounds: int = 3):
|
|
||||||
self.llm = llm
|
|
||||||
self.max_rounds = max_rounds
|
|
||||||
|
|
||||||
async def should_continue(
|
|
||||||
self,
|
|
||||||
context: GroupContext,
|
|
||||||
stage: MessageStage
|
|
||||||
) -> tuple[bool, str]:
|
|
||||||
"""
|
|
||||||
判断是否继续下一轮
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(是否继续, 原因)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 达到最大轮数
|
|
||||||
if context.round >= self.max_rounds:
|
|
||||||
return False, "max_rounds_reached"
|
|
||||||
|
|
||||||
# 构建判断 prompt
|
|
||||||
prompt = self._build_judge_prompt(context, stage)
|
|
||||||
|
|
||||||
# 调用 LLM 判断
|
|
||||||
response = await self.llm.ainvoke([
|
|
||||||
SystemMessage(content=prompt),
|
|
||||||
HumanMessage(content="请判断讨论是否已经充分,是否需要更多轮数。")
|
|
||||||
])
|
|
||||||
|
|
||||||
content = response.content.lower() if hasattr(response, 'content') else str(response)
|
|
||||||
|
|
||||||
# 解析判断结果
|
|
||||||
if "充分" in content or "足够" in content or "不需要" in content:
|
|
||||||
return False, "discussion_sufficient"
|
|
||||||
elif "不充分" in content or "不够" in content or "需要" in content:
|
|
||||||
return True, "need_more_discussion"
|
|
||||||
|
|
||||||
# 默认继续
|
|
||||||
return True, "default_continue"
|
|
||||||
|
|
||||||
def _build_judge_prompt(self, context: GroupContext, stage: MessageStage) -> str:
|
|
||||||
"""构建判断 prompt"""
|
|
||||||
|
|
||||||
messages_summary = "\n".join([
|
|
||||||
f"【{msg.agent_name}】{msg.content[:200]}..."
|
|
||||||
for msg in context.messages[-5:]
|
|
||||||
])
|
|
||||||
|
|
||||||
return f"""你是一个讨论质量评估专家。请判断当前的讨论是否已经充分。
|
|
||||||
|
|
||||||
## 讨论信息
|
|
||||||
主题:{context.topic}
|
|
||||||
当前阶段:{stage.value}
|
|
||||||
当前轮数:{context.round}
|
|
||||||
最大轮数:{self.max_rounds}
|
|
||||||
|
|
||||||
## 最近的讨论内容
|
|
||||||
{messages_summary}
|
|
||||||
|
|
||||||
## 判断标准
|
|
||||||
- 各方观点是否已经充分表达?
|
|
||||||
- 是否有建设性的讨论和回应?
|
|
||||||
- 是否已经形成明确的结论或方向?
|
|
||||||
|
|
||||||
## 请输出
|
|
||||||
如果讨论已经充分,请输出"充分",并简要说明原因。
|
|
||||||
如果还需要更多讨论,请输出"不充分",并说明需要讨论哪些方面。
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.4 群聊管理器
|
|
||||||
|
|
||||||
```python
|
|
||||||
# group_chat/manager.py
|
|
||||||
import uuid
|
|
||||||
from typing import Optional
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
|
||||||
|
|
||||||
from .context import GroupContext
|
|
||||||
from .message import GroupMessage, MessageStage, UserInterruption
|
|
||||||
from .participant import Participant
|
|
||||||
from .roles import DEFAULT_ROLES
|
|
||||||
from .round_controller import SmartRoundController
|
|
||||||
|
|
||||||
|
|
||||||
class GroupChatManager:
|
|
||||||
"""群聊管理器"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
llm: BaseChatModel,
|
|
||||||
roles: dict = None,
|
|
||||||
max_rounds: int = 3,
|
|
||||||
enable_user_interrupt: bool = True
|
|
||||||
):
|
|
||||||
self.llm = llm
|
|
||||||
self.roles = roles or DEFAULT_ROLES
|
|
||||||
self.max_rounds = max_rounds
|
|
||||||
self.enable_user_interrupt = enable_user_interrupt
|
|
||||||
|
|
||||||
# 初始化组件
|
|
||||||
self.round_controller = SmartRoundController(llm, max_rounds)
|
|
||||||
|
|
||||||
# 运行时状态
|
|
||||||
self.context: Optional[GroupContext] = None
|
|
||||||
self.participants: dict[str, Participant] = {}
|
|
||||||
|
|
||||||
async def start_chat(self, topic: str) -> dict:
|
|
||||||
"""开始群聊"""
|
|
||||||
|
|
||||||
# 创建上下文
|
|
||||||
self.context = GroupContext(
|
|
||||||
topic=topic,
|
|
||||||
stage="present",
|
|
||||||
round=1,
|
|
||||||
messages=[],
|
|
||||||
user_interruptions=[],
|
|
||||||
status="running"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 创建参与者
|
|
||||||
self.participants = {
|
|
||||||
role_id: Participant(self.llm, config)
|
|
||||||
for role_id, config in self.roles.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
# 按优先级排序参与者
|
|
||||||
sorted_participants = sorted(
|
|
||||||
self.participants.values(),
|
|
||||||
key=lambda p: p.config.get("priority", 999)
|
|
||||||
)
|
|
||||||
|
|
||||||
# 开始第一阶段
|
|
||||||
result = await self._run_stage(MessageStage.PRESENT, sorted_participants)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
async def _run_stage(
|
|
||||||
self,
|
|
||||||
stage: MessageStage,
|
|
||||||
participants: list[Participant]
|
|
||||||
) -> dict:
|
|
||||||
"""运行指定阶段"""
|
|
||||||
|
|
||||||
# 更新上下文
|
|
||||||
self.context.stage = stage.value
|
|
||||||
|
|
||||||
# 按阶段获取参与者
|
|
||||||
stage_participants = [p for p in participants if p.can_participate(stage)]
|
|
||||||
|
|
||||||
# 执行多轮
|
|
||||||
for round_num in range(1, self.max_rounds + 1):
|
|
||||||
self.context.round = round_num
|
|
||||||
|
|
||||||
# 当前阶段参与者发言
|
|
||||||
messages = await self._run_round(stage, stage_participants, round_num)
|
|
||||||
|
|
||||||
# 智能判断是否继续
|
|
||||||
should_continue, reason = await self.round_controller.should_continue(
|
|
||||||
self.context, stage
|
|
||||||
)
|
|
||||||
|
|
||||||
if not should_continue:
|
|
||||||
break
|
|
||||||
|
|
||||||
# 阶段完成后的处理
|
|
||||||
if stage == MessageStage.PRESENT:
|
|
||||||
# 进入讨论阶段
|
|
||||||
if any(p.can_participate(MessageStage.DISCUSS) for p in participants):
|
|
||||||
return await self._run_stage(MessageStage.DISCUSS, participants)
|
|
||||||
|
|
||||||
elif stage == MessageStage.DISCUSS:
|
|
||||||
# 进入总结阶段
|
|
||||||
summarizer = next(
|
|
||||||
(p for p in participants if p.is_final_decider),
|
|
||||||
participants[0]
|
|
||||||
)
|
|
||||||
return await self._run_stage(MessageStage.SUMMARIZE, [summarizer])
|
|
||||||
|
|
||||||
elif stage == MessageStage.SUMMARIZE:
|
|
||||||
# 完成
|
|
||||||
self.context.status = "completed"
|
|
||||||
|
|
||||||
return self._build_result()
|
|
||||||
|
|
||||||
async def _run_round(
|
|
||||||
self,
|
|
||||||
stage: MessageStage,
|
|
||||||
participants: list[Participant],
|
|
||||||
round_num: int
|
|
||||||
) -> list[GroupMessage]:
|
|
||||||
"""运行一轮发言"""
|
|
||||||
|
|
||||||
messages = []
|
|
||||||
|
|
||||||
for participant in participants:
|
|
||||||
# 获取前一轮的最新消息(用于回复)
|
|
||||||
replying_to = None
|
|
||||||
if self.context.messages:
|
|
||||||
last_message = self.context.messages[-1]
|
|
||||||
if last_message.agent_id != participant.id:
|
|
||||||
replying_to = last_message.id
|
|
||||||
|
|
||||||
# 生成消息
|
|
||||||
message = await participant.generate_message(
|
|
||||||
context=self.context,
|
|
||||||
stage=stage,
|
|
||||||
round_num=round_num,
|
|
||||||
replying_to=replying_to
|
|
||||||
)
|
|
||||||
|
|
||||||
# 保存消息
|
|
||||||
self.context.messages.append(message)
|
|
||||||
messages.append(message)
|
|
||||||
|
|
||||||
return messages
|
|
||||||
|
|
||||||
async def add_interruption(self, content: str) -> dict:
|
|
||||||
"""添加用户插话"""
|
|
||||||
|
|
||||||
if not self.enable_user_interrupt:
|
|
||||||
return {"error": "用户插话已禁用"}
|
|
||||||
|
|
||||||
interruption = UserInterruption(
|
|
||||||
id=str(uuid.uuid4()),
|
|
||||||
content=content,
|
|
||||||
timestamp=datetime.now(),
|
|
||||||
stage=MessageStage(self.context.stage),
|
|
||||||
round=self.context.round
|
|
||||||
)
|
|
||||||
|
|
||||||
self.context.user_interruptions.append(interruption)
|
|
||||||
|
|
||||||
return {"success": True, "interruption_id": interruption.id}
|
|
||||||
|
|
||||||
def get_messages(self) -> list[GroupMessage]:
|
|
||||||
"""获取所有消息"""
|
|
||||||
return self.context.messages if self.context else []
|
|
||||||
|
|
||||||
def get_result(self) -> dict:
|
|
||||||
"""获取结果"""
|
|
||||||
return self._build_result()
|
|
||||||
|
|
||||||
def _build_result(self) -> dict:
|
|
||||||
"""构建结果"""
|
|
||||||
return {
|
|
||||||
"topic": self.context.topic,
|
|
||||||
"status": self.context.status,
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"agent_name": m.agent_name,
|
|
||||||
"agent_role": m.agent_role,
|
|
||||||
"content": m.content,
|
|
||||||
"stage": m.stage.value,
|
|
||||||
"round": m.round
|
|
||||||
}
|
|
||||||
for m in self.context.messages
|
|
||||||
],
|
|
||||||
"final_decision": self.context.final_decision,
|
|
||||||
"user_interruptions": [
|
|
||||||
{"content": i.content, "timestamp": i.timestamp.isoformat()}
|
|
||||||
for i in self.context.user_interruptions
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 四、阶段实现
|
|
||||||
|
|
||||||
### 4.1 Presenter Stage(观点提出)
|
|
||||||
|
|
||||||
```python
|
|
||||||
# group_chat/stages/presenter.py
|
|
||||||
from typing import list
|
|
||||||
from ..participant import Participant
|
|
||||||
from ..message import GroupMessage
|
|
||||||
|
|
||||||
|
|
||||||
class PresenterStage:
|
|
||||||
"""观点提出阶段"""
|
|
||||||
|
|
||||||
async def execute(
|
|
||||||
self,
|
|
||||||
participants: list[Participant],
|
|
||||||
context
|
|
||||||
) -> list[GroupMessage]:
|
|
||||||
"""执行观点提出"""
|
|
||||||
|
|
||||||
messages = []
|
|
||||||
|
|
||||||
# 按优先级顺序发言
|
|
||||||
for participant in participants:
|
|
||||||
if not participant.can_participate("present"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
message = await participant.generate_message(
|
|
||||||
context=context,
|
|
||||||
stage="present",
|
|
||||||
round=context.round
|
|
||||||
)
|
|
||||||
|
|
||||||
messages.append(message)
|
|
||||||
context.messages.append(message)
|
|
||||||
|
|
||||||
return messages
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 Discusser Stage(讨论完善)
|
|
||||||
|
|
||||||
```python
|
|
||||||
# group_chat/stages/discusser.py
|
|
||||||
from ..participant import Participant
|
|
||||||
from ..message import GroupMessage
|
|
||||||
|
|
||||||
|
|
||||||
class DiscusserStage:
|
|
||||||
"""讨论完善阶段"""
|
|
||||||
|
|
||||||
async def execute(
|
|
||||||
self,
|
|
||||||
participants: list[Participant],
|
|
||||||
context
|
|
||||||
) -> list[GroupMessage]:
|
|
||||||
"""执行讨论完善"""
|
|
||||||
|
|
||||||
messages = []
|
|
||||||
|
|
||||||
# 获取上一轮的消息
|
|
||||||
last_round_messages = [
|
|
||||||
m for m in context.messages
|
|
||||||
if m.stage == "present" and m.round == context.round - 1
|
|
||||||
]
|
|
||||||
|
|
||||||
for participant in participants:
|
|
||||||
if not participant.can_participate("discuss"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 选择要回复的消息
|
|
||||||
replying_to = None
|
|
||||||
for msg in reversed(last_round_messages):
|
|
||||||
if msg.agent_id != participant.id:
|
|
||||||
replying_to = msg.id
|
|
||||||
break
|
|
||||||
|
|
||||||
message = await participant.generate_message(
|
|
||||||
context=context,
|
|
||||||
stage="discuss",
|
|
||||||
round=context.round,
|
|
||||||
replying_to=replying_to
|
|
||||||
)
|
|
||||||
|
|
||||||
messages.append(message)
|
|
||||||
context.messages.append(message)
|
|
||||||
|
|
||||||
return messages
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3 Summarizer Stage(总结决策)
|
|
||||||
|
|
||||||
```python
|
|
||||||
# group_chat/stages/summarizer.py
|
|
||||||
from ..participant import Participant
|
|
||||||
from ..message import GroupMessage
|
|
||||||
|
|
||||||
|
|
||||||
class SummarizerStage:
|
|
||||||
"""总结决策阶段"""
|
|
||||||
|
|
||||||
async def execute(
|
|
||||||
self,
|
|
||||||
participants: list[Participant],
|
|
||||||
context
|
|
||||||
) -> GroupMessage:
|
|
||||||
"""执行总结决策"""
|
|
||||||
|
|
||||||
# CEO 总结
|
|
||||||
summarizer = next(
|
|
||||||
(p for p in participants if p.is_final_decider),
|
|
||||||
participants[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
message = await summarizer.generate_message(
|
|
||||||
context=context,
|
|
||||||
stage="summarize",
|
|
||||||
round=context.round
|
|
||||||
)
|
|
||||||
|
|
||||||
# 保存最终决策
|
|
||||||
context.final_decision = message.content
|
|
||||||
context.messages.append(message)
|
|
||||||
|
|
||||||
return message
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 五、与现有系统集成
|
|
||||||
|
|
||||||
### 5.1 复用现有组件
|
|
||||||
|
|
||||||
```python
|
|
||||||
# group_chat/integration.py
|
|
||||||
from typing import Optional
|
|
||||||
from app.llm.factory import LLMFactory
|
|
||||||
from app.agent.tools.registry import ToolRegistry
|
|
||||||
from app.agent.memory.session import SessionManager
|
|
||||||
|
|
||||||
from .manager import GroupChatManager
|
|
||||||
|
|
||||||
|
|
||||||
class GroupChatSystem:
|
|
||||||
"""群聊系统 - 集成现有组件"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
llm_provider: str = "openai",
|
|
||||||
openai_api_key: Optional[str] = None,
|
|
||||||
anthropic_api_key: Optional[str] = None,
|
|
||||||
roles: dict = None,
|
|
||||||
max_rounds: int = 3,
|
|
||||||
enable_user_interrupt: bool = True
|
|
||||||
):
|
|
||||||
# 初始化 LLM Factory
|
|
||||||
self.llm_factory = LLMFactory(
|
|
||||||
provider=llm_provider,
|
|
||||||
openai_api_key=openai_api_key,
|
|
||||||
anthropic_api_key=anthropic_api_key
|
|
||||||
)
|
|
||||||
|
|
||||||
# 初始化 Tool Registry
|
|
||||||
self.tool_registry = ToolRegistry()
|
|
||||||
|
|
||||||
# 初始化 Session Manager
|
|
||||||
self.session_manager = SessionManager()
|
|
||||||
|
|
||||||
# 配置
|
|
||||||
self.roles = roles
|
|
||||||
self.max_rounds = max_rounds
|
|
||||||
self.enable_user_interrupt = enable_user_interrupt
|
|
||||||
|
|
||||||
async def start_group_chat(
|
|
||||||
self,
|
|
||||||
topic: str,
|
|
||||||
session_id: str = None
|
|
||||||
) -> dict:
|
|
||||||
"""开始群聊"""
|
|
||||||
|
|
||||||
# 获取 LLM
|
|
||||||
llm = self.llm_factory.get_llm()
|
|
||||||
|
|
||||||
# 创建群聊管理器
|
|
||||||
manager = GroupChatManager(
|
|
||||||
llm=llm,
|
|
||||||
roles=self.roles,
|
|
||||||
max_rounds=self.max_rounds,
|
|
||||||
enable_user_interrupt=self.enable_user_interrupt
|
|
||||||
)
|
|
||||||
|
|
||||||
# 开始群聊
|
|
||||||
result = await manager.start_chat(topic)
|
|
||||||
|
|
||||||
# 保存到 session
|
|
||||||
if session_id:
|
|
||||||
self.session_manager.add_message(session_id, "user", topic)
|
|
||||||
self.session_manager.add_message(
|
|
||||||
session_id,
|
|
||||||
"assistant",
|
|
||||||
result.get("final_decision", str(result))
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
async def add_message(
|
|
||||||
self,
|
|
||||||
message: str,
|
|
||||||
session_id: str
|
|
||||||
) -> dict:
|
|
||||||
"""添加用户消息(插话)"""
|
|
||||||
|
|
||||||
# 获取 session 中的 manager
|
|
||||||
# ... 实现获取逻辑
|
|
||||||
|
|
||||||
return manager.add_interruption(message)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 六、文件结构
|
|
||||||
|
|
||||||
```
|
|
||||||
agent/app/agent/multi/
|
|
||||||
├── group_chat/
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── roles.py # Agent 角色定义
|
|
||||||
│ ├── message.py # 消息类型
|
|
||||||
│ ├── context.py # 共享上下文
|
|
||||||
│ ├── participant.py # 参与者 Agent
|
|
||||||
│ ├── manager.py # 群聊管理器
|
|
||||||
│ ├── round_controller.py # 智能轮数控制器
|
|
||||||
│ ├── stages/
|
|
||||||
│ │ ├── __init__.py
|
|
||||||
│ │ ├── controller.py # 阶段控制器
|
|
||||||
│ │ ├── presenter.py # 观点提出阶段
|
|
||||||
│ │ ├── discusser.py # 讨论完善阶段
|
|
||||||
│ │ └── summarizer.py # 总结决策阶段
|
|
||||||
│ └── integration.py # 与现有系统集成
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 七、实现顺序
|
|
||||||
|
|
||||||
1. **Phase 1: 基础架构**
|
|
||||||
- 定义数据类型 (message.py, context.py)
|
|
||||||
- 创建角色配置 (roles.py)
|
|
||||||
|
|
||||||
2. **Phase 2: 核心组件**
|
|
||||||
- 实现 Participant (participant.py)
|
|
||||||
- 实现 SmartRoundController (round_controller.py)
|
|
||||||
|
|
||||||
3. **Phase 3: 阶段实现**
|
|
||||||
- 实现 PresenterStage
|
|
||||||
- 实现 DiscusserStage
|
|
||||||
- 实现 SummarizerStage
|
|
||||||
|
|
||||||
4. **Phase 4: 集成**
|
|
||||||
- 实现 GroupChatManager
|
|
||||||
- 与现有系统集成
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 八、测试计划
|
|
||||||
|
|
||||||
1. **单元测试**: 测试各 Participant 的消息生成
|
|
||||||
2. **集成测试**: 测试完整的群聊流程
|
|
||||||
3. **轮数控制测试**: 测试智能轮数判断
|
|
||||||
4. **用户插话测试**: 测试插话机制
|
|
||||||
5. **端到端测试**: 模拟真实群聊场景
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
# Notes: 多智能体群聊系统研究
|
|
||||||
|
|
||||||
## 核心概念
|
|
||||||
|
|
||||||
### 群聊系统 vs 之前的 Supervisor 系统
|
|
||||||
|
|
||||||
| 特性 | Supervisor 系统 | 群聊系统 |
|
|
||||||
|------|----------------|----------|
|
|
||||||
| 流程 | 线性:规划 → 执行 → 汇总 | 多阶段循环 |
|
|
||||||
| Agent 关系 | 层级:Supervisor 管理 Workers | 平等协作 |
|
|
||||||
| 通信方式 | 单向:任务分发 | 多向:互相讨论 |
|
|
||||||
| 决策方式 | Supervisor 决定 | CEO 最终决策 |
|
|
||||||
| 用户参与 | 旁观 | 可插话 |
|
|
||||||
|
|
||||||
### 设计模式
|
|
||||||
|
|
||||||
#### 1. 流水线模式
|
|
||||||
```
|
|
||||||
Stage 1 (Presenter) → Stage 2 (Discusser) → Stage 3 (Summarizer)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. 消息传递
|
|
||||||
- 每个 Stage 维护一个消息队列
|
|
||||||
- Agent 的输出成为下一个 Agent 的输入
|
|
||||||
- 使用 Shared Context 存储共享状态
|
|
||||||
|
|
||||||
#### 3. 智能轮数
|
|
||||||
```python
|
|
||||||
class SmartRoundController:
|
|
||||||
def should_continue(self, stage, round_num, messages):
|
|
||||||
# 使用 LLM 判断是否继续
|
|
||||||
prompt = f"""
|
|
||||||
当前阶段: {stage}
|
|
||||||
当前轮数: {round_num}
|
|
||||||
讨论内容: {messages}
|
|
||||||
|
|
||||||
讨论是否已经充分?是否需要更多轮数?
|
|
||||||
"""
|
|
||||||
return llm.judge(prompt)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 复用现有架构
|
|
||||||
|
|
||||||
### 可复用的组件
|
|
||||||
1. **LLM Factory** - 语言模型
|
|
||||||
2. **Tool Registry** - 工具注册
|
|
||||||
3. **Session Manager** - 会话管理
|
|
||||||
4. **Agent Executor** - Agent 执行逻辑(部分)
|
|
||||||
|
|
||||||
### 需要新增的组件
|
|
||||||
1. **GroupChatManager** - 群聊管理器
|
|
||||||
2. **Participant** - 参与者 Agent
|
|
||||||
3. **Stage Controller** - 阶段控制器
|
|
||||||
4. **SmartRoundController** - 智能轮数控制器
|
|
||||||
|
|
||||||
## 关键数据结构
|
|
||||||
|
|
||||||
### GroupMessage
|
|
||||||
```python
|
|
||||||
class GroupMessage(BaseModel):
|
|
||||||
id: str
|
|
||||||
agent_id: str
|
|
||||||
agent_name: str
|
|
||||||
content: str
|
|
||||||
timestamp: datetime
|
|
||||||
stage: str # presenter/discusser/summarizer
|
|
||||||
round: int
|
|
||||||
replying_to: Optional[str] # 回复的消息 ID
|
|
||||||
```
|
|
||||||
|
|
||||||
### GroupContext
|
|
||||||
```python
|
|
||||||
class GroupContext(BaseModel):
|
|
||||||
topic: str # 讨论主题
|
|
||||||
stage: str # 当前阶段
|
|
||||||
round: int # 当前轮数
|
|
||||||
messages: list[GroupMessage] # 所有消息
|
|
||||||
user_interruptions: list[str] # 用户插话
|
|
||||||
final_decision: Optional[str] # 最终决策
|
|
||||||
```
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Task Plan: 多智能体群聊系统实现计划
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
实现类似"一人公司"的多智能体群聊系统,支持头脑风暴、任务协作、角色扮演等多模式讨论。
|
|
||||||
|
|
||||||
## Phases
|
|
||||||
- [x] Phase 1: 基础架构设计和核心组件规划
|
|
||||||
- [ ] Phase 2: 群聊管理器 (GroupChatManager) 实现
|
|
||||||
- [ ] Phase 3: 参与 Agent (Participant) 实现
|
|
||||||
- [ ] Phase 4: 三个阶段实现 (Presenter/Discusser/Summarizer)
|
|
||||||
- [ ] Phase 5: 智能轮数控制实现
|
|
||||||
- [ ] Phase 6: 用户插话机制实现
|
|
||||||
- [ ] Phase 7: 与现有系统集成和 API 接口
|
|
||||||
|
|
||||||
## Key Questions
|
|
||||||
1. 如何复用现有的 Supervisor + Workers 架构?
|
|
||||||
2. 如何实现智能轮数控制?
|
|
||||||
3. 如何处理用户插话?
|
|
||||||
|
|
||||||
## Decisions Made
|
|
||||||
- 架构:任务流水线模式(观点提出 → 讨论完善 → 总结决策)
|
|
||||||
- 决策机制:CEO Agent 最终决策
|
|
||||||
- 轮数控制:AI 智能判断
|
|
||||||
- 用户参与:插话模式
|
|
||||||
- 复用策略:复用现有 LLM、ToolRegistry、SessionManager
|
|
||||||
|
|
||||||
## Status
|
|
||||||
**Currently in Phase 1** - 系统架构设计和核心组件规划已完成
|
|
||||||
|
|
||||||
## 实现计划文件
|
|
||||||
- `group_chat_implementation_plan.md` - 详细实现计划
|
|
||||||
- `group_chat_notes.md` - 研究笔记
|
|
||||||
@@ -1,709 +0,0 @@
|
|||||||
# 多智能体联动系统实现计划
|
|
||||||
|
|
||||||
## 项目概述
|
|
||||||
|
|
||||||
基于 LangGraph 实现类似 OpenClaw 的多智能体协作系统,采用 Supervisor + Workers 层级架构。
|
|
||||||
|
|
||||||
### 核心特性
|
|
||||||
- **任务规划**: Supervisor 分析任务并生成执行计划
|
|
||||||
- **动态分发**: LLM 自主决策调用哪个 Worker
|
|
||||||
- **并行执行**: 支持多个 Worker 同时处理任务
|
|
||||||
- **结果汇总**: Supervisor 汇总所有 Worker 结果
|
|
||||||
- **迭代优化**: 支持 Review 机制和迭代重试
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 一、系统架构
|
|
||||||
|
|
||||||
### 1.1 整体架构图
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ MultiAgentSystem │
|
|
||||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
|
||||||
│ │ Supervisor Agent │ │
|
|
||||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
||||||
│ │ │ Planner │ │ Dispatcher │ │ Aggregator │ │ │
|
|
||||||
│ │ │ (任务规划) │ │ (任务分发) │ │ (结果汇总) │ │ │
|
|
||||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
||||||
│ └───────────────────────────────────────────────────────────┘ │
|
|
||||||
│ │ │
|
|
||||||
│ ┌──────────────────┼──────────────────┐ │
|
|
||||||
│ ▼ ▼ ▼ │
|
|
||||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
||||||
│ │ Research │ │ Coder │ │ Review │ │
|
|
||||||
│ │ Worker │ │ Worker │ │ Worker │ │
|
|
||||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
||||||
│ │ │ │ │
|
|
||||||
│ └──────────────────┴──────────────────┘ │
|
|
||||||
│ │ │
|
|
||||||
│ ▼ │
|
|
||||||
│ ┌─────────────┐ │
|
|
||||||
│ │ Shared State │ │
|
|
||||||
│ │ (共享状态) │ │
|
|
||||||
│ └─────────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 核心组件
|
|
||||||
|
|
||||||
| 组件 | 职责 | 文件位置 |
|
|
||||||
|------|------|----------|
|
|
||||||
| **SupervisorAgent** | 任务分析、规划、分发、汇总 | `agent/multi/supervisor.py` |
|
|
||||||
| **BaseWorker** | Worker 基类,定义执行接口 | `agent/multi/workers/base.py` |
|
|
||||||
| **ResearchWorker** | 信息搜索和调研 | `agent/multi/workers/research.py` |
|
|
||||||
| **CoderWorker** | 代码编写和修改 | `agent/multi/workers/coder.py` |
|
|
||||||
| **ReviewWorker** | 结果检查和评审 | `agent/multi/workers/review.py` |
|
|
||||||
| **SharedState** | 跨 Agent 共享状态 | `agent/multi/state.py` |
|
|
||||||
| **TaskQueue** | 任务队列管理 | `agent/multi/queue.py` |
|
|
||||||
| **MultiAgentGraph** | LangGraph 流程编排 | `agent/multi/graph.py` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 二、数据结构设计
|
|
||||||
|
|
||||||
### 2.1 Agent State 定义
|
|
||||||
|
|
||||||
```python
|
|
||||||
# agent/multi/types.py
|
|
||||||
from typing import TypedDict, Annotated, Optional
|
|
||||||
from operator import add
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class TaskItem(BaseModel):
|
|
||||||
"""单个任务项"""
|
|
||||||
id: str
|
|
||||||
description: str
|
|
||||||
assigned_agent: str # research / coder / review
|
|
||||||
status: str # pending / running / completed / failed
|
|
||||||
result: Optional[dict] = None
|
|
||||||
error: Optional[str] = None
|
|
||||||
retry_count: int = 0
|
|
||||||
|
|
||||||
|
|
||||||
class AgentState(TypedDict):
|
|
||||||
"""贯穿整个图的 Agent 状态"""
|
|
||||||
# 用户输入
|
|
||||||
original_task: str # 原始任务描述
|
|
||||||
|
|
||||||
# 任务规划
|
|
||||||
task_plan: list[TaskItem] # 分解后的任务列表
|
|
||||||
current_task_index: int # 当前执行的任务索引
|
|
||||||
|
|
||||||
# 执行结果
|
|
||||||
results: dict # {task_id: result}
|
|
||||||
|
|
||||||
# 流程控制
|
|
||||||
iteration: int # 当前迭代次数
|
|
||||||
next_node: str # 下一个节点名称
|
|
||||||
|
|
||||||
# 共享上下文
|
|
||||||
shared_context: dict # Agent 间共享的数据
|
|
||||||
|
|
||||||
# 最终输出
|
|
||||||
final_output: str
|
|
||||||
status: str # running / completed / failed
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 Supervisor 输出结构
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Supervisor 的结构化输出
|
|
||||||
class SupervisorDecision(BaseModel):
|
|
||||||
"""Supervisor 的决策"""
|
|
||||||
analysis: str # 任务分析
|
|
||||||
task_plan: list[TaskItem] # 任务计划
|
|
||||||
need_aggregation: bool # 是否需要汇总
|
|
||||||
next_worker: str # 下一个执行的 Worker
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 三、核心实现
|
|
||||||
|
|
||||||
### 3.1 Supervisor Agent
|
|
||||||
|
|
||||||
```python
|
|
||||||
# agent/multi/supervisor.py
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
|
||||||
from langchain_core.output_parsers import PydanticOutputParser
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing import Type
|
|
||||||
|
|
||||||
from .types import AgentState, TaskItem, SupervisorDecision
|
|
||||||
from .prompts import SUPERVISOR_SYSTEM_PROMPT
|
|
||||||
|
|
||||||
|
|
||||||
class SupervisorAgent:
|
|
||||||
"""Supervisor Agent - 负责任务规划和分发"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
llm: BaseChatModel,
|
|
||||||
max_iterations: int = 3
|
|
||||||
):
|
|
||||||
self.llm = llm
|
|
||||||
self.max_iterations = max_iterations
|
|
||||||
self.output_parser = PydanticOutputParser(pydantic_object=SupervisorDecision)
|
|
||||||
|
|
||||||
def create_node(self):
|
|
||||||
"""创建 Supervisor 节点"""
|
|
||||||
return self._supervisor_node
|
|
||||||
|
|
||||||
async def _supervisor_node(self, state: AgentState) -> dict:
|
|
||||||
"""Supervisor 节点逻辑"""
|
|
||||||
# 首次调用:分析任务并生成计划
|
|
||||||
if not state.get("task_plan"):
|
|
||||||
decision = await self._plan_tasks(state["original_task"])
|
|
||||||
return {
|
|
||||||
"task_plan": decision.task_plan,
|
|
||||||
"next_node": decision.next_worker,
|
|
||||||
"current_task_index": 0,
|
|
||||||
"shared_context": {"task_analysis": decision.analysis}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 检查是否需要继续
|
|
||||||
current_task = state["task_plan"][state["current_task_index"]]
|
|
||||||
|
|
||||||
if current_task["status"] == "completed":
|
|
||||||
# 当前任务完成,检查是否还有更多任务
|
|
||||||
if state["current_task_index"] + 1 < len(state["task_plan"]):
|
|
||||||
next_index = state["current_task_index"] + 1
|
|
||||||
next_task = state["task_plan"][next_index]
|
|
||||||
return {
|
|
||||||
"current_task_index": next_index,
|
|
||||||
"next_node": next_task["assigned_agent"]
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# 所有任务完成,进入汇总
|
|
||||||
return {"next_node": "aggregate"}
|
|
||||||
|
|
||||||
elif current_task["status"] == "failed":
|
|
||||||
# 任务失败,检查是否超过最大重试
|
|
||||||
if current_task["retry_count"] >= self.max_iterations:
|
|
||||||
return {"next_node": "aggregate", "status": "failed"}
|
|
||||||
else:
|
|
||||||
# 重试
|
|
||||||
return {"next_node": current_task["assigned_agent"]}
|
|
||||||
|
|
||||||
return {"next_node": state.get("next_node", "aggregate")}
|
|
||||||
|
|
||||||
async def _plan_tasks(self, task: str) -> SupervisorDecision:
|
|
||||||
"""调用 LLM 生成任务计划"""
|
|
||||||
prompt = SUPERVISOR_SYSTEM_PROMPT.format(task=task)
|
|
||||||
|
|
||||||
response = await self.llm.ainvoke([
|
|
||||||
{"role": "system", "content": prompt},
|
|
||||||
{"role": "user", "content": "请分析任务并制定执行计划。"}
|
|
||||||
])
|
|
||||||
|
|
||||||
# 解析 LLM 输出为结构化决策
|
|
||||||
# ... (实现解析逻辑)
|
|
||||||
return decision
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 Worker 基类
|
|
||||||
|
|
||||||
```python
|
|
||||||
# agent/multi/workers/base.py
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from typing import Any
|
|
||||||
from langchain_core.language_models import BaseChatModel
|
|
||||||
|
|
||||||
from ..types import AgentState
|
|
||||||
|
|
||||||
|
|
||||||
class BaseWorker(ABC):
|
|
||||||
"""Worker Agent 基类"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
llm: BaseChatModel,
|
|
||||||
name: str,
|
|
||||||
system_prompt: str,
|
|
||||||
tools: list = None
|
|
||||||
):
|
|
||||||
self.llm = llm
|
|
||||||
self.name = name
|
|
||||||
self.system_prompt = system_prompt
|
|
||||||
self.tools = tools or []
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def execute(self, task: TaskItem, context: dict) -> dict:
|
|
||||||
"""执行任务"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_node(self):
|
|
||||||
"""创建 LangGraph 节点"""
|
|
||||||
async def node(state: AgentState) -> dict:
|
|
||||||
task = state["task_plan"][state["current_task_index"]]
|
|
||||||
result = await self.execute(task, state.get("shared_context", {}))
|
|
||||||
|
|
||||||
# 更新状态
|
|
||||||
return {
|
|
||||||
"results": {task.id: result},
|
|
||||||
"task_plan": self._update_task_status(
|
|
||||||
state["task_plan"],
|
|
||||||
task.id,
|
|
||||||
"completed" if result.get("success") else "failed"
|
|
||||||
),
|
|
||||||
"shared_context": {**state.get("shared_context", {}), **result.get("context", {})}
|
|
||||||
}
|
|
||||||
|
|
||||||
return node
|
|
||||||
|
|
||||||
def _update_task_status(self, tasks: list, task_id: str, status: str) -> list:
|
|
||||||
"""更新任务状态"""
|
|
||||||
return [
|
|
||||||
{**task, "status": status} if task["id"] == task_id else task
|
|
||||||
for task in tasks
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 任务队列(可选:支持并行执行)
|
|
||||||
|
|
||||||
```python
|
|
||||||
# agent/multi/queue.py
|
|
||||||
import asyncio
|
|
||||||
from typing import Any, Callable
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class TaskStatus(Enum):
|
|
||||||
PENDING = "pending"
|
|
||||||
RUNNING = "running"
|
|
||||||
COMPLETED = "completed"
|
|
||||||
FAILED = "failed"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class QueuedTask:
|
|
||||||
id: str
|
|
||||||
agent_name: str
|
|
||||||
task_data: Any
|
|
||||||
status: TaskStatus = TaskStatus.PENDING
|
|
||||||
result: Any = None
|
|
||||||
error: str = None
|
|
||||||
|
|
||||||
|
|
||||||
class TaskQueue:
|
|
||||||
"""任务队列 - 支持并行执行多个 Worker"""
|
|
||||||
|
|
||||||
def __init__(self, max_concurrent: int = 3):
|
|
||||||
self.max_concurrent = max_concurrent
|
|
||||||
self.queue: asyncio.Queue = asyncio.Queue()
|
|
||||||
self.results: dict = {}
|
|
||||||
self._running = 0
|
|
||||||
|
|
||||||
async def add_task(self, task: QueuedTask):
|
|
||||||
"""添加任务到队列"""
|
|
||||||
await self.queue.put(task)
|
|
||||||
|
|
||||||
async def execute_all(self, worker_factory: Callable):
|
|
||||||
"""执行所有任务"""
|
|
||||||
async def worker():
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
task = self.queue.get_nowait()
|
|
||||||
except asyncio.QueueEmpty:
|
|
||||||
break
|
|
||||||
|
|
||||||
self._running += 1
|
|
||||||
task.status = TaskStatus.Running
|
|
||||||
|
|
||||||
try:
|
|
||||||
worker_instance = worker_factory(task.agent_name)
|
|
||||||
task.result = await worker_instance.execute(task.task_data)
|
|
||||||
task.status = TaskStatus.COMPLETED
|
|
||||||
except Exception as e:
|
|
||||||
task.status = TaskStatus.FAILED
|
|
||||||
task.error = str(e)
|
|
||||||
finally:
|
|
||||||
self._running -= 1
|
|
||||||
self.results[task.id] = task
|
|
||||||
|
|
||||||
# 启动多个 worker 协程
|
|
||||||
workers = [asyncio.create_task(worker()) for _ in range(self.max_concurrent)]
|
|
||||||
await asyncio.gather(*workers)
|
|
||||||
|
|
||||||
return self.results
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.4 LangGraph 流程编排
|
|
||||||
|
|
||||||
```python
|
|
||||||
# agent/multi/graph.py
|
|
||||||
from langgraph.graph import StateGraph, END
|
|
||||||
from langgraph.prebuilt import ToolNode
|
|
||||||
|
|
||||||
from .types import AgentState
|
|
||||||
from .supervisor import SupervisorAgent
|
|
||||||
from .workers.research import ResearchWorker
|
|
||||||
from .workers.coder import CoderWorker
|
|
||||||
from .workers.review import ReviewWorker
|
|
||||||
from .aggregator import ResultAggregator
|
|
||||||
|
|
||||||
|
|
||||||
def create_multi_agent_graph(
|
|
||||||
llm,
|
|
||||||
tool_registry,
|
|
||||||
max_iterations: int = 3
|
|
||||||
) -> StateGraph:
|
|
||||||
"""创建多 Agent 流程图"""
|
|
||||||
|
|
||||||
# 初始化组件
|
|
||||||
supervisor = SupervisorAgent(llm, max_iterations)
|
|
||||||
research_worker = ResearchWorker(llm, tool_registry)
|
|
||||||
coder_worker = CoderWorker(llm, tool_registry)
|
|
||||||
review_worker = ReviewWorker(llm, tool_registry)
|
|
||||||
aggregator = ResultAggregator(llm)
|
|
||||||
|
|
||||||
# 创建图
|
|
||||||
graph = StateGraph(AgentState)
|
|
||||||
|
|
||||||
# 添加节点
|
|
||||||
graph.add_node("supervisor", supervisor.create_node())
|
|
||||||
graph.add_node("research", research_worker.create_node())
|
|
||||||
graph.add_node("coder", coder_worker.create_node())
|
|
||||||
graph.add_node("review", review_worker.create_node())
|
|
||||||
graph.add_node("aggregate", aggregator.create_node())
|
|
||||||
|
|
||||||
# 设置入口
|
|
||||||
graph.set_entry_point("supervisor")
|
|
||||||
|
|
||||||
# 添加边
|
|
||||||
graph.add_edge("supervisor", "research")
|
|
||||||
graph.add_edge("research", "review")
|
|
||||||
graph.add_edge("coder", "review")
|
|
||||||
|
|
||||||
# 条件边:从 review 回到 supervisor
|
|
||||||
def should_continue(state: AgentState) -> str:
|
|
||||||
if state.get("status") == "failed":
|
|
||||||
return "aggregate"
|
|
||||||
if state.get("iteration", 0) >= max_iterations:
|
|
||||||
return "aggregate"
|
|
||||||
if state.get("current_task_index", 0) >= len(state.get("task_plan", [])):
|
|
||||||
return "aggregate"
|
|
||||||
return "supervisor"
|
|
||||||
|
|
||||||
graph.add_conditional_edges(
|
|
||||||
"review",
|
|
||||||
should_continue,
|
|
||||||
{
|
|
||||||
"supervisor": "supervisor",
|
|
||||||
"aggregate": "aggregate"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 结束节点
|
|
||||||
graph.add_edge("aggregate", END)
|
|
||||||
|
|
||||||
return graph.compile()
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 四、Prompt 设计
|
|
||||||
|
|
||||||
### 4.1 Supervisor System Prompt
|
|
||||||
|
|
||||||
```python
|
|
||||||
# agent/multi/prompts.py
|
|
||||||
|
|
||||||
SUPERVISOR_SYSTEM_PROMPT = """你是一个任务规划专家(Supervisor)。你的职责是将复杂任务分解为可执行的子任务,并分配给合适的执行 Agent。
|
|
||||||
|
|
||||||
## 可用的 Worker Agent
|
|
||||||
- **research**: 信息搜索和调研
|
|
||||||
- **coder**: 代码编写、修改和调试
|
|
||||||
- **review**: 结果检查、质量评审
|
|
||||||
|
|
||||||
## 任务
|
|
||||||
{task}
|
|
||||||
|
|
||||||
## 请按以下步骤执行
|
|
||||||
|
|
||||||
### 步骤 1: 任务分析
|
|
||||||
分析任务的性质,确定需要哪些步骤来完成。
|
|
||||||
|
|
||||||
### 步骤 2: 任务分解
|
|
||||||
将任务分解为独立的子任务。每个子任务应该:
|
|
||||||
- 描述清晰
|
|
||||||
- 可以由单个 Agent 完成
|
|
||||||
- 有明确的完成标准
|
|
||||||
|
|
||||||
### 步骤 3: 分配 Agent
|
|
||||||
为每个子任务选择最合适的执行 Agent。
|
|
||||||
|
|
||||||
### 步骤 4: 确定执行顺序
|
|
||||||
如果有依赖关系,确定正确的执行顺序。
|
|
||||||
|
|
||||||
## 输出格式
|
|
||||||
请以 JSON 格式输出你的决策:
|
|
||||||
```json
|
|
||||||
{{
|
|
||||||
"analysis": "任务分析...",
|
|
||||||
"task_plan": [
|
|
||||||
{{
|
|
||||||
"id": "task_1",
|
|
||||||
"description": "子任务描述",
|
|
||||||
"assigned_agent": "research"
|
|
||||||
}},
|
|
||||||
{{
|
|
||||||
"id": "task_2",
|
|
||||||
"description": "子任务描述",
|
|
||||||
"assigned_agent": "coder"
|
|
||||||
}}
|
|
||||||
],
|
|
||||||
"need_aggregation": true,
|
|
||||||
"next_worker": "research"
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意
|
|
||||||
- 如果任务很简单,可以只分配给一个 Agent
|
|
||||||
- 如果任务需要迭代优化,确保有 review 环节
|
|
||||||
- 考虑任务之间的依赖关系
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 Review Worker Prompt
|
|
||||||
|
|
||||||
```python
|
|
||||||
REVIEW_SYSTEM_PROMPT = """你是一个代码和结果评审专家(Reviewer)。你的职责是检查任务执行结果是否符合要求。
|
|
||||||
|
|
||||||
## 任务描述
|
|
||||||
{task_description}
|
|
||||||
|
|
||||||
## 执行结果
|
|
||||||
{execution_result}
|
|
||||||
|
|
||||||
## 检查标准
|
|
||||||
1. 结果是否完整解决了原始任务?
|
|
||||||
2. 输出格式是否正确?
|
|
||||||
3. 是否存在明显的错误或遗漏?
|
|
||||||
4. 代码是否有潜在问题?
|
|
||||||
|
|
||||||
## 请输出评审结果
|
|
||||||
```json
|
|
||||||
{{
|
|
||||||
"passed": true/false,
|
|
||||||
"issues": [
|
|
||||||
{{"severity": "high/medium/low", "description": "问题描述"}}
|
|
||||||
],
|
|
||||||
"suggestions": ["改进建议"]
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 五、迭代控制
|
|
||||||
|
|
||||||
### 5.1 迭代逻辑
|
|
||||||
|
|
||||||
```python
|
|
||||||
# agent/multi/iteration.py
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
class IterationController:
|
|
||||||
"""迭代控制器"""
|
|
||||||
|
|
||||||
def __init__(self, max_iterations: int = 3):
|
|
||||||
self.max_iterations = max_iterations
|
|
||||||
|
|
||||||
def should_continue(
|
|
||||||
self,
|
|
||||||
iteration: int,
|
|
||||||
task_status: str,
|
|
||||||
review_result: dict
|
|
||||||
) -> tuple[bool, str]:
|
|
||||||
"""
|
|
||||||
判断是否继续迭代
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(是否继续, 原因)
|
|
||||||
"""
|
|
||||||
# 超过最大迭代次数
|
|
||||||
if iteration >= self.max_iterations:
|
|
||||||
return False, "max_iterations_reached"
|
|
||||||
|
|
||||||
# 任务成功完成
|
|
||||||
if task_status == "completed" and review_result.get("passed"):
|
|
||||||
return False, "task_completed"
|
|
||||||
|
|
||||||
# 任务失败且不可重试
|
|
||||||
if task_status == "failed" and not review_result.get("retryable"):
|
|
||||||
return False, "task_failed"
|
|
||||||
|
|
||||||
# 需要重试
|
|
||||||
if review_result.get("issues") and review_result.get("passed") is False:
|
|
||||||
return True, "needs_retry"
|
|
||||||
|
|
||||||
return True, "continue"
|
|
||||||
|
|
||||||
def get_next_action(
|
|
||||||
self,
|
|
||||||
review_result: dict,
|
|
||||||
current_worker: str
|
|
||||||
) -> str:
|
|
||||||
"""确定下一步动作"""
|
|
||||||
if review_result.get("passed"):
|
|
||||||
return "supervisor" # 返回 Supervisor
|
|
||||||
|
|
||||||
# 根据问题类型决定下一步
|
|
||||||
issues = review_result.get("issues", [])
|
|
||||||
high_severity = any(i.get("severity") == "high" for i in issues)
|
|
||||||
|
|
||||||
if high_severity:
|
|
||||||
# 严重问题,重新执行相同任务
|
|
||||||
return current_worker
|
|
||||||
else:
|
|
||||||
# 轻微问题,可以继续
|
|
||||||
return "supervisor"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 六、与现有系统集成
|
|
||||||
|
|
||||||
### 6.1 复用现有组件
|
|
||||||
|
|
||||||
```python
|
|
||||||
# agent/multi/integration.py
|
|
||||||
from app.agent.core.agent import AgentManager
|
|
||||||
from app.agent.tools.registry import ToolRegistry
|
|
||||||
from app.agent.memory.session import SessionManager
|
|
||||||
from app.llm.factory import LLMFactory
|
|
||||||
|
|
||||||
|
|
||||||
class MultiAgentSystem:
|
|
||||||
"""多智能体系统 - 集成现有组件"""
|
|
||||||
|
|
||||||
def __init__(self, config: dict):
|
|
||||||
# 复用现有 LLM Factory
|
|
||||||
self.llm_factory = LLMFactory(
|
|
||||||
provider=config.get("llm_provider", "openai"),
|
|
||||||
openai_api_key=config.get("openai_api_key"),
|
|
||||||
anthropic_api_key=config.get("anthropic_api_key")
|
|
||||||
)
|
|
||||||
|
|
||||||
# 复用现有 Tool Registry
|
|
||||||
self.tool_registry = ToolRegistry()
|
|
||||||
self._register_default_tools()
|
|
||||||
|
|
||||||
# 复用现有 Session Manager
|
|
||||||
self.session_manager = SessionManager()
|
|
||||||
|
|
||||||
# 配置
|
|
||||||
self.max_iterations = config.get("max_iterations", 3)
|
|
||||||
|
|
||||||
def _register_default_tools(self):
|
|
||||||
"""注册默认工具"""
|
|
||||||
# 从现有 Agent 复制工具注册逻辑
|
|
||||||
from app.agent.tools.impl import search, calculator
|
|
||||||
self.tool_registry.register(
|
|
||||||
name="search",
|
|
||||||
func=search.search_web,
|
|
||||||
description="Search the web",
|
|
||||||
security_level="safe"
|
|
||||||
)
|
|
||||||
# ... 其他工具
|
|
||||||
|
|
||||||
async def execute(self, task: str, session_id: str = None) -> dict:
|
|
||||||
"""执行多 Agent 任务"""
|
|
||||||
# 创建 LangGraph
|
|
||||||
from .graph import create_multi_agent_graph
|
|
||||||
|
|
||||||
llm = self.llm_factory.get_llm()
|
|
||||||
graph = create_multi_agent_graph(
|
|
||||||
llm=llm,
|
|
||||||
tool_registry=self.tool_registry,
|
|
||||||
max_iterations=self.max_iterations
|
|
||||||
)
|
|
||||||
|
|
||||||
# 初始化状态
|
|
||||||
initial_state = {
|
|
||||||
"original_task": task,
|
|
||||||
"task_plan": [],
|
|
||||||
"current_task_index": 0,
|
|
||||||
"results": {},
|
|
||||||
"iteration": 0,
|
|
||||||
"next_node": "supervisor",
|
|
||||||
"shared_context": {},
|
|
||||||
"final_output": "",
|
|
||||||
"status": "running"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 执行
|
|
||||||
result = await graph.ainvoke(initial_state)
|
|
||||||
|
|
||||||
# 保存到 session
|
|
||||||
if session_id:
|
|
||||||
self.session_manager.add_message(session_id, "user", task)
|
|
||||||
self.session_manager.add_message(session_id, "assistant", result["final_output"])
|
|
||||||
|
|
||||||
return result
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 七、文件结构
|
|
||||||
|
|
||||||
```
|
|
||||||
agent/
|
|
||||||
├── __init__.py
|
|
||||||
├── multi/
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── types.py # 数据类型定义
|
|
||||||
│ ├── prompts.py # Prompt 模板
|
|
||||||
│ ├── supervisor.py # Supervisor Agent
|
|
||||||
│ ├── graph.py # LangGraph 流程图
|
|
||||||
│ ├── iteration.py # 迭代控制
|
|
||||||
│ ├── integration.py # 与现有系统集成
|
|
||||||
│ ├── queue.py # 任务队列(可选)
|
|
||||||
│ └── workers/
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── base.py # Worker 基类
|
|
||||||
│ ├── research.py # Research Worker
|
|
||||||
│ ├── coder.py # Coder Worker
|
|
||||||
│ └── review.py # Review Worker
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 八、实现顺序
|
|
||||||
|
|
||||||
1. **Phase 1: 基础架构**
|
|
||||||
- 定义数据类型 (types.py)
|
|
||||||
- 创建 Prompt 模板 (prompts.py)
|
|
||||||
|
|
||||||
2. **Phase 2: Supervisor**
|
|
||||||
- 实现 SupervisorAgent
|
|
||||||
- 实现任务规划和分发逻辑
|
|
||||||
|
|
||||||
3. **Phase 3: Workers**
|
|
||||||
- 实现 BaseWorker
|
|
||||||
- 实现 ResearchWorker
|
|
||||||
- 实现 CoderWorker
|
|
||||||
- 实现 ReviewWorker
|
|
||||||
|
|
||||||
4. **Phase 4: 流程编排**
|
|
||||||
- 实现 LangGraph 流程图
|
|
||||||
- 添加条件边和迭代控制
|
|
||||||
|
|
||||||
5. **Phase 5: 集成**
|
|
||||||
- 与现有 Agent 系统集成
|
|
||||||
- 添加 API 接口
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 九、测试计划
|
|
||||||
|
|
||||||
1. **单元测试**: 测试各 Worker 的执行逻辑
|
|
||||||
2. **集成测试**: 测试完整的 Supervisor + Workers 流程
|
|
||||||
3. **迭代测试**: 测试重试和迭代逻辑
|
|
||||||
4. **端到端测试**: 模拟真实任务执行
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
# Notes: LangGraph 多智能体研究
|
|
||||||
|
|
||||||
## 核心概念
|
|
||||||
|
|
||||||
### LangGraph 基础
|
|
||||||
- **StateGraph**: 有向无环图(DAG),节点是 Agent/函数,边是流转逻辑
|
|
||||||
- **State**: 贯穿整个图流动的状态对象
|
|
||||||
- **Node**: 执行单元(可以是 Agent、函数、条件判断)
|
|
||||||
- **Edge**: 连接节点的边,支持条件边(conditional edges)
|
|
||||||
|
|
||||||
### Supervisor + Workers 模式参考
|
|
||||||
|
|
||||||
#### 1. LangChain 官方 Supervisor 示例
|
|
||||||
```python
|
|
||||||
from langgraph.prebuilt import create_react_agent
|
|
||||||
from langgraph.graph import StateGraph, END
|
|
||||||
|
|
||||||
# 定义 Workers
|
|
||||||
research_agent = create_react_agent(llm, tools=[search])
|
|
||||||
coder_agent = create_react_agent(llm, tools=[write_file])
|
|
||||||
|
|
||||||
# 定义 Supervisor 节点
|
|
||||||
def supervisor_node(state):
|
|
||||||
# LLM 决定下一步调用哪个 Agent
|
|
||||||
response = llm.with_structured_output(SupervisorOutput).invoke(
|
|
||||||
[SystemMessage(content=SUPERVISOR_PROMPT)] + state["messages"]
|
|
||||||
)
|
|
||||||
return {"next": response.next_agent}
|
|
||||||
|
|
||||||
# 构建图
|
|
||||||
graph = StateGraph(AgentState)
|
|
||||||
graph.add_node("supervisor", supervisor_node)
|
|
||||||
graph.add_node("research", research_agent)
|
|
||||||
graph.add_node("code", coder_agent)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. 状态定义
|
|
||||||
```python
|
|
||||||
from typing import TypedDict, Annotated
|
|
||||||
import operator
|
|
||||||
|
|
||||||
class AgentState(TypedDict):
|
|
||||||
messages: Annotated[list, operator.add]
|
|
||||||
task: str
|
|
||||||
plan: list
|
|
||||||
results: dict
|
|
||||||
iteration: int
|
|
||||||
next: str # 控制下一步流向
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. 条件边实现
|
|
||||||
```python
|
|
||||||
def should_continue(state):
|
|
||||||
if state["iteration"] >= MAX_ITERATIONS:
|
|
||||||
return "end"
|
|
||||||
if state.get("task_complete"):
|
|
||||||
return "end"
|
|
||||||
return "continue"
|
|
||||||
|
|
||||||
graph.add_conditional_edges(
|
|
||||||
"review",
|
|
||||||
should_continue,
|
|
||||||
{
|
|
||||||
"continue": "supervisor",
|
|
||||||
"end": END
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 设计决策
|
|
||||||
|
|
||||||
### 架构优势
|
|
||||||
1. **清晰的分层**: Supervisor 负责任务规划,Workers 负责执行
|
|
||||||
2. **可扩展**: 容易添加新的 Worker 类型
|
|
||||||
3. **可控**: 迭代次数全局配置
|
|
||||||
4. **灵活**: 支持条件分支和循环
|
|
||||||
|
|
||||||
### 需要解决的问题
|
|
||||||
1. **Supervisor 如何做规划**: 需要设计 prompt 让 LLM 生成任务列表
|
|
||||||
2. **任务队列**: 需要支持并行分发多个 Worker
|
|
||||||
3. **共享上下文**: 需要设计数据结构在 Agent 间共享状态
|
|
||||||
4. **Review 机制**: 需要定义检查标准和重试逻辑
|
|
||||||
|
|
||||||
## 关键 Prompt 设计
|
|
||||||
|
|
||||||
### Supervisor System Prompt
|
|
||||||
```
|
|
||||||
你是一个任务规划专家(Supervisor)。用户的任务是:{task}
|
|
||||||
|
|
||||||
请按以下步骤执行:
|
|
||||||
1. 分析任务需求和约束
|
|
||||||
2. 将任务分解为可执行的子任务
|
|
||||||
3. 为每个子任务选择合适的执行 Agent:
|
|
||||||
- research: 信息搜索和调研
|
|
||||||
- coder: 代码编写和修改
|
|
||||||
- review: 结果检查和评审
|
|
||||||
4. 确定执行顺序和依赖关系
|
|
||||||
|
|
||||||
当前任务进度:{progress}
|
|
||||||
共享上下文:{context}
|
|
||||||
|
|
||||||
请输出你的决策,格式如下:
|
|
||||||
- 需要执行的子任务列表
|
|
||||||
- 每个任务的执行 Agent
|
|
||||||
- 任务执行顺序
|
|
||||||
- 是否需要汇总结果
|
|
||||||
```
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Task Plan: 多智能体联动系统实现计划
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
基于 LangGraph 实现类似 OpenClaw 的多智能体联动系统,支持任务规划、动态分发、结果汇总和迭代优化。
|
|
||||||
|
|
||||||
## Phases
|
|
||||||
- [x] Phase 1: 系统架构设计和核心组件规划
|
|
||||||
- [ ] Phase 2: Supervisor Agent 实现
|
|
||||||
- [ ] Phase 3: Worker Agent 实现
|
|
||||||
- [ ] Phase 4: 任务队列和共享上下文实现
|
|
||||||
- [ ] Phase 5: State Machine 流程控制实现
|
|
||||||
- [ ] Phase 6: 迭代控制和 Review 机制实现
|
|
||||||
- [ ] Phase 7: 与现有 Agent 系统集成
|
|
||||||
|
|
||||||
## Key Questions
|
|
||||||
1. 如何用 LangGraph 实现 Supervisor + Workers 架构?
|
|
||||||
2. 如何设计任务队列支持并行执行?
|
|
||||||
3. 如何实现共享上下文在 Agent 间传递?
|
|
||||||
4. 如何控制迭代次数和流程分支?
|
|
||||||
|
|
||||||
## Decisions Made
|
|
||||||
- 架构:Supervisor + Workers 层级模式
|
|
||||||
- 协作方式:LLM 自主决策任务分配
|
|
||||||
- 通信:共享内存(Shared Context)
|
|
||||||
- 迭代控制:全局最大迭代次数配置
|
|
||||||
- Workers 定义:复用现有 tool_registry
|
|
||||||
|
|
||||||
## Status
|
|
||||||
**Currently in Phase 1** - 系统架构设计和核心组件规划已完成
|
|
||||||
|
|
||||||
## 实现计划文件
|
|
||||||
- `implementation_plan.md` - 详细的实现计划
|
|
||||||
- `notes.md` - LangGraph 研究笔记
|
|
||||||
218
teams/openclaw-memory-research.md
Normal file
218
teams/openclaw-memory-research.md
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
# OpenCLAW 记忆模块调研报告
|
||||||
|
|
||||||
|
## 调研目标
|
||||||
|
调研 OpenCLAW 的记忆模块(Memory Module)功能,了解其架构设计、核心功能和实现原理。
|
||||||
|
|
||||||
|
## 一、OpenCLAW 简介
|
||||||
|
|
||||||
|
OpenCLAW 是一个开源的个人 AI Agent 网关平台(GitHub: https://github.com/openclaw/openclaw),旨在让 AI 成为能实际操作用户设备、拥有持久化记忆并能主动发起任务的私人助理。
|
||||||
|
|
||||||
|
## 二、记忆模块架构设计
|
||||||
|
|
||||||
|
### 2.1 三层记忆架构
|
||||||
|
|
||||||
|
OpenCLAW 采用经典的三层类继承架构:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ 顶层:导览员(检索层) │
|
||||||
|
│ 负责处理用户查询,快速找到最相关的记忆 │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ 中间层:翻译官(嵌入层) │
|
||||||
|
│ 把文字转化为机器能理解的数学向量 │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ 底层:书架管理员(存储层) │
|
||||||
|
│ 负责文本块的入库、上架和盘点 │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 三层记忆类型
|
||||||
|
|
||||||
|
| 记忆类型 | 描述 | 存储方式 |
|
||||||
|
|---------|------|---------|
|
||||||
|
| **短期记忆 (Short-term Context)** | 当前会话的上下文信息 | 存于 Context Window |
|
||||||
|
| **语义记忆 (Semantic Memory)** | 长期稳定的知识 | SQLite + 向量索引 |
|
||||||
|
| **情景记忆 (Episodic Memory)** | 具体事件和经历 | memory/YYYY-MM-DD.md |
|
||||||
|
|
||||||
|
## 三、核心功能特性
|
||||||
|
|
||||||
|
### 3.1 混合搜索(Hybrid Search)
|
||||||
|
|
||||||
|
OpenCLAW 实现了 **BM25 + 向量语义** 的混合搜索:
|
||||||
|
|
||||||
|
- **BM25 全文检索**:基于 SQLite FTS5 引擎,擅长精确关键词匹配
|
||||||
|
- **向量语义检索**:通过嵌入模型将文本转化为高维向量,基于余弦相似度捕捉语义关联
|
||||||
|
|
||||||
|
**权重配置**:默认 7:3(语义 70%,精确 30%)
|
||||||
|
|
||||||
|
### 3.2 时间衰减机制
|
||||||
|
|
||||||
|
引入**指数时间衰减模型**,模拟人类记忆的遗忘曲线:
|
||||||
|
|
||||||
|
```
|
||||||
|
衰减后得分 = 原始得分 × e^(-λ × 天数)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 半衰期默认 30 天
|
||||||
|
- 30 天后检索权重衰减为原来的一半
|
||||||
|
- 60 天后只剩四分之一
|
||||||
|
|
||||||
|
**常青记忆(Evergreen Memory)**:
|
||||||
|
- `MEMORY.md`(根级记忆文件)不受时间衰减影响
|
||||||
|
- `memory/patterns.md` 等主题归纳文件也不衰减
|
||||||
|
- 按日期命名的记录(如 `memory/2024-01-15.md`)正常应用衰减
|
||||||
|
|
||||||
|
### 3.3 MMR 重排算法
|
||||||
|
|
||||||
|
使用 **MMR(Maximal Marginal Relevance,最大边际相关性)** 算法进行结果重排,避免信息冗余:
|
||||||
|
|
||||||
|
```
|
||||||
|
MMR(d) = λ × 相关性(d) - (1-λ) × max(与已选结果的相似度)
|
||||||
|
```
|
||||||
|
|
||||||
|
采用 **Jaccard 集合相似度** 替代余弦相似度,计算更轻量。
|
||||||
|
|
||||||
|
### 3.4 多提供商嵌入支持
|
||||||
|
|
||||||
|
| 提供商 | 默认模型 | 特点 |
|
||||||
|
|-------|---------|------|
|
||||||
|
| OpenAI | text-embedding-3-small | 业界主流,质量稳定 |
|
||||||
|
| Gemini | gemini-embedding-001 | 支持 API Key 自动轮换 |
|
||||||
|
| Voyage | voyage-4-large | 高维度,擅长长文本 |
|
||||||
|
| Mistral | mistral-embed | 多语言能力出色 |
|
||||||
|
| 本地 | embeddinggemma-300m (GGUF) | 完全离线,数据不出本机 |
|
||||||
|
|
||||||
|
**四级降级链条**:确保任何环境下都能提供服务
|
||||||
|
|
||||||
|
### 3.5 嵌入缓存与索引重建
|
||||||
|
|
||||||
|
- **Embedding 缓存**:以 `provider + model + 文本内容哈希` 为主键,同一段文本只需计算一次嵌入
|
||||||
|
- **原子化重建**:采用"双数据库原子交换"策略,索引重建期间用户无感知
|
||||||
|
|
||||||
|
### 3.6 实时同步机制
|
||||||
|
|
||||||
|
三条并行的同步通道:
|
||||||
|
|
||||||
|
1. **文件监控(chokidar)**:实时监听 `MEMORY.md` 和 `memory/` 目录变化,通过防抖机制避免重复触发
|
||||||
|
2. **会话增量同步**:维护 delta 追踪器,只在增量达到阈值时触发更新
|
||||||
|
3. **定时轮询**:兜底机制,确保不会遗漏
|
||||||
|
|
||||||
|
### 3.7 中文检索支持
|
||||||
|
|
||||||
|
采用**字级别 + bigram 策略**:
|
||||||
|
- 单字保证召回率
|
||||||
|
- bigram 捕捉常见双字词组合
|
||||||
|
- 内置约 80 个中文停用词过滤
|
||||||
|
|
||||||
|
## 四、数据流架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||||
|
│ 用户输入 │────▶│ 混合搜索 │────▶│ MMR重排 │
|
||||||
|
│ (查询) │ │ BM25+向量 │ │ 去冗余 │
|
||||||
|
└──────────────┘ └──────────────┘ └──────────────┘
|
||||||
|
│
|
||||||
|
┌───────────────────────────┘
|
||||||
|
▼
|
||||||
|
┌──────────────┐
|
||||||
|
│ 时间衰减 │
|
||||||
|
│ +常青记忆 │
|
||||||
|
└──────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────┐
|
||||||
|
│ 返回结果 │
|
||||||
|
│ 给 Agent │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 五、存储结构
|
||||||
|
|
||||||
|
```
|
||||||
|
workspace/
|
||||||
|
├── MEMORY.md # 常青记忆(根级)
|
||||||
|
├── memory/
|
||||||
|
│ ├── patterns.md # 主题归纳(常青)
|
||||||
|
│ ├── 2024-01-15.md # 按日期的情景记忆
|
||||||
|
│ └── ...
|
||||||
|
└── .claude/
|
||||||
|
└── memory/ # SQLite 向量索引
|
||||||
|
└── index.db
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、总结与启发
|
||||||
|
|
||||||
|
### 设计哲学
|
||||||
|
|
||||||
|
| 原则 | 体现 |
|
||||||
|
|------|------|
|
||||||
|
| **混合优于单一** | BM25 和向量搜索各有盲区,融合才能互补 |
|
||||||
|
| **韧性优于性能** | 四级降级链条确保任何环境下都能提供服务 |
|
||||||
|
| **仿生优于机械** | 时间衰减与常青记忆模拟人类认知的自然规律 |
|
||||||
|
| **务实优于完美** | Jaccard 替代余弦相似度,bigram 替代专业分词 |
|
||||||
|
|
||||||
|
### 可借鉴点
|
||||||
|
|
||||||
|
1. **三层记忆架构**:短期/语义/情景记忆分离
|
||||||
|
2. **混合搜索**:BM25 + 向量检索结合
|
||||||
|
3. **时间衰减**:模拟人类遗忘曲线
|
||||||
|
4. **常青记忆**:区分核心知识和时效信息
|
||||||
|
5. **多级降级**:确保服务高可用
|
||||||
|
6. **实时同步**:文件监控 + 增量同步 + 定时轮询
|
||||||
|
|
||||||
|
## 七、参考资料
|
||||||
|
|
||||||
|
- OpenCLAW 官方 GitHub: https://github.com/openclaw/openclaw
|
||||||
|
- OpenCLAW 记忆系统深度解析: https://zhuanlan.zhihu.com/p/2010072734986702963
|
||||||
|
- OpenCLAW Design Patterns: https://kenhuangus.substack.com/p/the-openclaw-design-patternspart
|
||||||
|
- OpenCLAW 记忆系统架构: https://zhuanlan.zhihu.com/p/2005943466006438841
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、可行性评估(algorithm-dev)
|
||||||
|
|
||||||
|
### 8.1 与现有系统兼容性分析
|
||||||
|
|
||||||
|
| 维度 | 现有 X-Agents | OpenCLAW | 兼容性 |
|
||||||
|
|------|--------------|----------|--------|
|
||||||
|
| 存储格式 | Markdown 文件 | Markdown 文件 | ✅ 高度兼容 |
|
||||||
|
| 短期记忆 | SessionMemory(内存) | sessions/ 目录 | ✅ 可对齐 |
|
||||||
|
| 长期记忆 | AgentMemory(文件) | MEMORY.md | ✅ 可对齐 |
|
||||||
|
| 检索机制 | 无 | BM25+向量混合 | ❌ 需新增 |
|
||||||
|
| 嵌入服务 | 无 | 多种 provider | ❌ 需新增 |
|
||||||
|
| 分块策略 | 无 | 400 token 滑动窗口 | ❌ 需新增 |
|
||||||
|
|
||||||
|
### 8.2 工程落地技术难点
|
||||||
|
|
||||||
|
1. **嵌入服务集成** - 需要选择嵌入提供者(本地 ONNX vs 远程 API)
|
||||||
|
2. **向量数据库** - 需要集成 Milvus/Chroma/FAISS 等
|
||||||
|
3. **混合检索加权融合** - 需要实现 70/30 加权融合算法
|
||||||
|
4. **分块策略** - 需要实现带重叠的滑动窗口分块
|
||||||
|
5. **预压缩冲刷** - 需要监控 token 数量并触发自动保存
|
||||||
|
|
||||||
|
### 8.3 可行性结论
|
||||||
|
|
||||||
|
**✅ 可行性:高**
|
||||||
|
|
||||||
|
### 建议方案
|
||||||
|
|
||||||
|
#### 方案A(推荐)- 集成 memsearch 库
|
||||||
|
|
||||||
|
- Zilliz 提取并开源的 Python 库,完全复现 OpenCLAW 记忆架构
|
||||||
|
- 支持多种嵌入提供者(推荐本地 ONNX,无需 API key)
|
||||||
|
- 支持 Milvus Lite(单用户)/Milvus Server(团队)/Zilliz Cloud(生产)
|
||||||
|
- 开箱即用,集成成本低
|
||||||
|
|
||||||
|
#### 方案B - 自研轻量级实现
|
||||||
|
|
||||||
|
- 保持现有 Markdown 文件存储
|
||||||
|
- 简化检索:仅用关键词搜索或简单向量相似度
|
||||||
|
- 开发周期更长,但可控性更高
|
||||||
|
|
||||||
|
**推荐采用 memsearch 库**,可以快速获得成熟的 OpenCLAW 记忆能力,同时保持 Markdown 文件的可读性和版本可控性。
|
||||||
|
|
||||||
|
### 8.4 待确认事项
|
||||||
|
|
||||||
|
1. 是否需要新增向量数据库服务?(Milvus Lite vs 自建)
|
||||||
|
2. 嵌入模型选择本地 ONNX 还是远程 API?
|
||||||
|
3. 是否需要支持多用户协作场景?
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
# 后端需求 - 表结构返回 columns 数据
|
|
||||||
|
|
||||||
## 问题描述
|
|
||||||
|
|
||||||
前端在 Edit Mapping 页面需要展示表的列信息(字段名、类型、COMMENT等),但前端自行解析 DDL 存在困难。
|
|
||||||
|
|
||||||
## 需求
|
|
||||||
|
|
||||||
后端在获取表结构列表时,需要同时返回:
|
|
||||||
|
|
||||||
1. **DDL 语句**(已有的需求,继续保留)
|
|
||||||
2. **结构化的 columns 数据**(新增)
|
|
||||||
|
|
||||||
### 返回数据结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"tables": [
|
|
||||||
{
|
|
||||||
"table_name": "exam_scores",
|
|
||||||
"table_comment": "考试成绩表",
|
|
||||||
"ddl": "CREATE TABLE `exam_scores` (...)",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"column_name": "id",
|
|
||||||
"data_type": "int",
|
|
||||||
"column_type": "int(10) unsigned",
|
|
||||||
"is_nullable": "NO",
|
|
||||||
"default_value": null,
|
|
||||||
"column_key": "PRI",
|
|
||||||
"extra": "auto_increment",
|
|
||||||
"column_comment": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"column_name": "student_id",
|
|
||||||
"data_type": "int",
|
|
||||||
"column_type": "int(10) unsigned",
|
|
||||||
"is_nullable": "NO",
|
|
||||||
"default_value": null,
|
|
||||||
"column_key": "",
|
|
||||||
"extra": "",
|
|
||||||
"column_comment": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"column_name": "subject",
|
|
||||||
"data_type": "varchar",
|
|
||||||
"column_type": "varchar(50)",
|
|
||||||
"is_nullable": "NO",
|
|
||||||
"default_value": null,
|
|
||||||
"column_key": "",
|
|
||||||
"extra": "",
|
|
||||||
"column_comment": "科目"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"column_name": "score",
|
|
||||||
"data_type": "double",
|
|
||||||
"column_type": "double",
|
|
||||||
"is_nullable": "YES",
|
|
||||||
"default_value": null,
|
|
||||||
"column_key": "",
|
|
||||||
"extra": "",
|
|
||||||
"column_comment": "分数"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段说明
|
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| column_name | string | 列名 |
|
|
||||||
| data_type | string | 数据类型(如 int, varchar, double) |
|
|
||||||
| column_type | string | 完整列类型(如 int(10) unsigned) |
|
|
||||||
| is_nullable | string | 是否可空(YES/NO) |
|
|
||||||
| default_value | string | 默认值 |
|
|
||||||
| column_key | string | 主键标识(PRI/MUL/UNI) |
|
|
||||||
| extra | string | 额外信息(如 auto_increment) |
|
|
||||||
| column_comment | string | 列注释 |
|
|
||||||
|
|
||||||
## 影响范围
|
|
||||||
|
|
||||||
- 文件:`server/internal/service/database_service.go`
|
|
||||||
- 函数:`getMySQLTables`, `getPostgresTables`
|
|
||||||
- 数据模型:`server/internal/model/sub_table_info.go` 的 `ColumnInfo` 结构体
|
|
||||||
|
|
||||||
## 优先级
|
|
||||||
|
|
||||||
高 - 前端 Edit Mapping 页面字段映射功能依赖此数据
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
# 后端需求 - DDL 编辑功能
|
|
||||||
|
|
||||||
## 问题描述
|
|
||||||
|
|
||||||
前端 Database 页面的 Table Mapping 功能已从"字段映射"改为"DDL 编辑"模式。后端需要支持保存和读取 DDL 数据。
|
|
||||||
|
|
||||||
## 需求
|
|
||||||
|
|
||||||
### 1. 保存 DDL
|
|
||||||
|
|
||||||
前端保存时发送 `ddl` 字段而非 `fields` 字段。
|
|
||||||
|
|
||||||
请求结构:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "数据库名",
|
|
||||||
"sub_tables": [
|
|
||||||
{
|
|
||||||
"parent_table": "users",
|
|
||||||
"sub_table_name": "用户表",
|
|
||||||
"sub_table_comment": "用户表",
|
|
||||||
"ddl": "CREATE TABLE users (...)"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 后端处理
|
|
||||||
|
|
||||||
- `CreateSubTableRequest` 已有 `DDL string` 字段(已添加)
|
|
||||||
- `UpdateSubTableRequest` 已有 `DDL string` 字段(已添加)
|
|
||||||
- `UpdateDatabaseRequest` 已有 `SubTables []CreateSubTableRequest` 字段(已添加)
|
|
||||||
|
|
||||||
### 3. Service 层修改
|
|
||||||
|
|
||||||
**sub_table_service.go**:
|
|
||||||
|
|
||||||
1. `Create` 函数 - 添加 DDL 字段赋值:
|
|
||||||
```go
|
|
||||||
info := &model.SubTableInfo{
|
|
||||||
// ... 现有字段
|
|
||||||
DDL: req.DDL,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. `Update` 函数 - 添加 DDL 更新逻辑:
|
|
||||||
```go
|
|
||||||
// 更新 DDL
|
|
||||||
info.DDL = req.DDL
|
|
||||||
```
|
|
||||||
|
|
||||||
**database_service.go 或 handler**:
|
|
||||||
|
|
||||||
在 `Update` 方法中处理 `SubTables` 字段:
|
|
||||||
- 当前端传入 `sub_tables` 时,需要创建或更新对应的子表记录(包括 DDL)
|
|
||||||
- 遍历 `sub_tables`,调用 `SubTableService.Create` 或 `SubTableService.Update`
|
|
||||||
|
|
||||||
## 影响范围
|
|
||||||
|
|
||||||
- `server/internal/service/sub_table_service.go` - Create/Update 方法
|
|
||||||
- `server/internal/service/database_service.go` 或 handler - Update 方法处理 SubTables
|
|
||||||
|
|
||||||
## 状态
|
|
||||||
|
|
||||||
- [x] 前端修改完成
|
|
||||||
- [x] 后端修改已完成
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
# 后端需求 - 字段映射保存与读取
|
|
||||||
|
|
||||||
## 问题描述
|
|
||||||
|
|
||||||
前端 Edit Mapping 页面中,用户输入的字段中文映射名(mapped_name)在保存后,第二次打开时丢失了。
|
|
||||||
|
|
||||||
## 原因分析
|
|
||||||
|
|
||||||
1. **保存时**:前端只保存了表级别信息,没有保存字段的中文映射
|
|
||||||
2. **加载时**:前端每次都从 `/database/check` 重新获取表结构,没有读取已保存的映射数据
|
|
||||||
|
|
||||||
## 需求
|
|
||||||
|
|
||||||
### 1. 保存字段映射
|
|
||||||
|
|
||||||
前端保存时需要传递每个字段的中文映射名,后端需要存储这些数据。
|
|
||||||
|
|
||||||
请求结构:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "数据库名",
|
|
||||||
"sub_tables": [
|
|
||||||
{
|
|
||||||
"parent_table": "users",
|
|
||||||
"sub_table_name": "用户表",
|
|
||||||
"sub_table_comment": "用户表",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"column_name": "id",
|
|
||||||
"mapped_name": "编号"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"column_name": "username",
|
|
||||||
"mapped_name": "用户名"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 返回字段映射
|
|
||||||
|
|
||||||
后端在返回表结构时,需要同时返回已保存的字段映射信息。
|
|
||||||
|
|
||||||
返回结构:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"tables": [
|
|
||||||
{
|
|
||||||
"table_name": "users",
|
|
||||||
"table_comment": "用户表",
|
|
||||||
"ddl": "...",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"column_name": "id",
|
|
||||||
"data_type": "int",
|
|
||||||
"column_type": "int(10)",
|
|
||||||
"column_comment": "",
|
|
||||||
"mapped_name": "编号"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"column_name": "username",
|
|
||||||
"data_type": "varchar",
|
|
||||||
"column_type": "varchar(50)",
|
|
||||||
"column_comment": "用户名",
|
|
||||||
"mapped_name": "用户名"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 数据存储
|
|
||||||
|
|
||||||
- 可以在 `sub_table_info` 表中增加 `fields` JSON 字段存储字段映射
|
|
||||||
- 或者创建新的关联表 `sub_table_fields`
|
|
||||||
|
|
||||||
## 影响范围
|
|
||||||
|
|
||||||
- `server/internal/service/database_service.go` - Create/Update 方法
|
|
||||||
- `server/internal/model/` - 数据模型修改
|
|
||||||
- 子表映射的数据存储结构
|
|
||||||
|
|
||||||
## 优先级
|
|
||||||
|
|
||||||
高 - 用户输入的映射数据丢失影响使用体验
|
|
||||||
@@ -1,375 +0,0 @@
|
|||||||
# 知识库创建 API
|
|
||||||
|
|
||||||
## 基础信息
|
|
||||||
|
|
||||||
| 项目 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| 基础URL | `http://localhost:8082` |
|
|
||||||
| 前端页面 | Knowledge Base 创建弹窗 |
|
|
||||||
|
|
||||||
## 接口列表
|
|
||||||
|
|
||||||
### 1. 创建知识库
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/knowledge/create
|
|
||||||
Content-Type: application/json
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| name | String | 是 | 知识库名称 |
|
|
||||||
| description | String | 否 | 知识库描述 |
|
|
||||||
| llm_model_id | String | 是 | LLM 模型 ID(来自 model 表) |
|
|
||||||
| embedding_model_id | String | 是 | Embedding 模型 ID(来自 model 表) |
|
|
||||||
| parsing_config | Object | 是 | 解析配置 |
|
|
||||||
| - engine | String | 是 | 解析引擎:markitdown / docling |
|
|
||||||
| - docling_url | String | 条件必填 | Docling 服务 URL(engine=docling 时必填) |
|
|
||||||
| - enable_pdf | Boolean | 否 | 是否启用 PDF 解析(默认 true) |
|
|
||||||
| - pandoc | Boolean | 否 | 是否启用 Pandoc(默认 true) |
|
|
||||||
| storage_config | Object | 否 | 存储配置(默认 local) |
|
|
||||||
| - type | String | 是 | 存储类型:local / minio / s3 |
|
|
||||||
| - endpoint | String | 否 | MinIO Endpoint(如 minio:9000) |
|
|
||||||
| - access_key_id | String | 否 | MinIO Access Key ID |
|
|
||||||
| - secret_access_key | String | 否 | MinIO Secret Access Key |
|
|
||||||
| - bucket | String | 否 | MinIO Bucket 名称 |
|
|
||||||
|
|
||||||
**请求示例**
|
|
||||||
|
|
||||||
本地存储:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "产品文档知识库",
|
|
||||||
"description": "用于存储产品手册和文档",
|
|
||||||
"llm_model_id": "model_001",
|
|
||||||
"embedding_model_id": "model_002",
|
|
||||||
"parsing_config": {
|
|
||||||
"engine": "markitdown",
|
|
||||||
"enable_pdf": true,
|
|
||||||
"pandoc": true
|
|
||||||
},
|
|
||||||
"storage_config": {
|
|
||||||
"type": "local"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
使用 Docling + MinIO:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "产品文档知识库",
|
|
||||||
"description": "用于存储产品手册和文档",
|
|
||||||
"llm_model_id": "model_001",
|
|
||||||
"embedding_model_id": "model_002",
|
|
||||||
"parsing_config": {
|
|
||||||
"engine": "docling",
|
|
||||||
"docling_url": "http://localhost:8501",
|
|
||||||
"enable_pdf": true,
|
|
||||||
"pandoc": true
|
|
||||||
},
|
|
||||||
"storage_config": {
|
|
||||||
"type": "minio",
|
|
||||||
"endpoint": "localhost:9000",
|
|
||||||
"access_key_id": "minioadmin",
|
|
||||||
"secret_access_key": "minioadmin",
|
|
||||||
"bucket": "x-agents"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**成功响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"id": "kb_abc123",
|
|
||||||
"message": "Knowledge base created successfully"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**错误响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"message": "LLM model not found"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 获取知识库列表
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/knowledge/list
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "kb_001",
|
|
||||||
"name": "产品文档知识库",
|
|
||||||
"description": "用于存储产品手册",
|
|
||||||
"llm_model_id": "model_001",
|
|
||||||
"embedding_model_id": "model_002",
|
|
||||||
"status": "active",
|
|
||||||
"document_count": 15,
|
|
||||||
"chunk_count": 156,
|
|
||||||
"created_at": "2024-01-15T10:30:00Z",
|
|
||||||
"updated_at": "2024-01-15T10:30:00Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 获取知识库详情
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/knowledge/:id
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| id | String | 是 | 知识库 ID |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"id": "kb_001",
|
|
||||||
"name": "产品文档知识库",
|
|
||||||
"description": "用于存储产品手册",
|
|
||||||
"llm_model_id": "model_001",
|
|
||||||
"embedding_model_id": "model_002",
|
|
||||||
"parsing_config": {
|
|
||||||
"engine": "markitdown",
|
|
||||||
"enable_pdf": true,
|
|
||||||
"pandoc": true
|
|
||||||
},
|
|
||||||
"status": "active",
|
|
||||||
"document_count": 15,
|
|
||||||
"chunk_count": 156,
|
|
||||||
"created_at": "2024-01-15T10:30:00Z",
|
|
||||||
"updated_at": "2024-01-15T10:30:00Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. 删除知识库
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
DELETE /api/knowledge/:id
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| id | String | 是 | 知识库 ID |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Knowledge base deleted"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. 获取知识库下的文档列表
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/knowledge/:id/documents
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| id | String | 是 | 知识库 ID |
|
|
||||||
|
|
||||||
**查询参数**
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| status | String | 否 | 过滤状态:all / parsed / parsing / failed |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "doc_001",
|
|
||||||
"knowledge_base_id": "kb_001",
|
|
||||||
"name": "产品手册_v2.0.pdf",
|
|
||||||
"file_key": "abc123.pdf",
|
|
||||||
"file_url": "http://localhost:8082/files/abc123.pdf",
|
|
||||||
"file_size": 2516582,
|
|
||||||
"status": "parsed",
|
|
||||||
"chunk_count": 156,
|
|
||||||
"uploaded_at": "2024-01-15T10:30:00Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. 上传文档到知识库
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/knowledge/:id/documents
|
|
||||||
Content-Type: multipart/form-data
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| id | String | 是 | 知识库 ID |
|
|
||||||
| file | File | 是 | 要上传的文件 |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"dataid": "doc_001",
|
|
||||||
": {
|
|
||||||
" "name": "产品手册_v2.0.pdf",
|
|
||||||
"status": "parsing"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. 删除知识库文档
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
DELETE /api/knowledge/:id/documents/:doc_id
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| id | String | 是 | 知识库 ID |
|
|
||||||
| doc_id | String | 是 | 文档 ID |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Document deleted"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 8. 重新解析文档
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/knowledge/:id/documents/:doc_id/reparse
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| id | String | 是 | 知识库 ID |
|
|
||||||
| doc_id | String | 是 | 文档 ID |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Document reparse started"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 9. 获取文档预览内容
|
|
||||||
|
|
||||||
**请求**
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/knowledge/:id/documents/:doc_id/preview
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| id | String | 是 | 知识库 ID |
|
|
||||||
| doc_id | String | 是 | 文档 ID |
|
|
||||||
| page | Number | 否 | 页码(默认 1) |
|
|
||||||
|
|
||||||
**响应**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"total_pages": 3,
|
|
||||||
"current_page": 1,
|
|
||||||
"content": "第一章 产品介绍\n\n欢迎使用我们的产品手册..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 数据库表设计(参考)
|
|
||||||
|
|
||||||
### knowledge_base 表
|
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| id | String | 主键 |
|
|
||||||
| name | String | 知识库名称 |
|
|
||||||
| description | Text | 描述 |
|
|
||||||
| llm_model_id | String | LLM 模型 ID |
|
|
||||||
| embedding_model_id | String | Embedding 模型 ID |
|
|
||||||
| parsing_config | JSON | 解析配置 |
|
|
||||||
| storage_config | JSON | 存储配置(包含 type, endpoint, access_key_id, secret_access_key, bucket) |
|
|
||||||
| status | String | 状态:active / inactive |
|
|
||||||
| document_count | Integer | 文档数量 |
|
|
||||||
| chunk_count | Integer | 切片数量 |
|
|
||||||
| created_at | Timestamp | 创建时间 |
|
|
||||||
| updated_at | Timestamp | 更新时间 |
|
|
||||||
|
|
||||||
### knowledge_document 表
|
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| id | String | 主键 |
|
|
||||||
| knowledge_base_id | String | 知识库 ID |
|
|
||||||
| name | String | 文档名称 |
|
|
||||||
| file_key | String | 文件存储 key |
|
|
||||||
| file_url | String | 文件访问 URL(本地路径或 MinIO 预签名 URL) |
|
|
||||||
| file_size | BigInteger | 文件大小 |
|
|
||||||
| status | String | 状态:parsing / parsed / failed |
|
|
||||||
| chunk_count | Integer | 切片数量 |
|
|
||||||
| uploaded_at | Timestamp | 上传时间 |
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# 后端需求 - 保存和恢复映射状态
|
|
||||||
|
|
||||||
## 问题描述
|
|
||||||
|
|
||||||
用户第一次选择表并设置字段映射后,第二次点击 "Map Tables" 按钮进入界面时,之前选择的表和设置的字段映射都丢失了。
|
|
||||||
|
|
||||||
## 需求
|
|
||||||
|
|
||||||
前端打开已存在的数据库映射时,需要恢复以下状态:
|
|
||||||
|
|
||||||
### 1. 已选择的表列表
|
|
||||||
|
|
||||||
后端需要在数据库记录中保存用户选择了哪些表(不仅仅是子表信息),或者在查询时返回该数据库关联的所有子表。
|
|
||||||
|
|
||||||
### 2. 字段映射
|
|
||||||
|
|
||||||
每个子表保存的字段映射(mapped_name)需要在前端重新加载时显示。
|
|
||||||
|
|
||||||
## 期望的行为
|
|
||||||
|
|
||||||
1. 用户点击已存在的数据库的 "Map Tables" 按钮
|
|
||||||
2. 前端获取实时表结构
|
|
||||||
3. 同时加载该数据库已保存的子表信息(包括选择的表和字段映射)
|
|
||||||
4. 前端合并数据,显示:
|
|
||||||
- 已选择的表(勾选状态)
|
|
||||||
- 每个字段之前设置的 mapped_name
|
|
||||||
|
|
||||||
## 技术实现建议
|
|
||||||
|
|
||||||
在数据库表中增加或利用已有字段:
|
|
||||||
|
|
||||||
- `sub_table_info` 表已包含 `Fields` JSON 字段存储字段映射
|
|
||||||
- 需要在创建/更新数据库时保存选择的表列表
|
|
||||||
- 或者在查询时返回该数据库下所有已创建的子表
|
|
||||||
|
|
||||||
## 影响范围
|
|
||||||
|
|
||||||
- 数据库创建/更新接口
|
|
||||||
- 子表映射查询接口
|
|
||||||
|
|
||||||
## 优先级
|
|
||||||
|
|
||||||
高 - 影响用户体验,第二次进入无法看到之前的工作成果
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
# Model Settings 需求文档
|
|
||||||
|
|
||||||
## 需求概述
|
|
||||||
|
|
||||||
Model Settings 页面用于管理 AI 模型配置,支持添加、编辑、删除和测试模型连接。
|
|
||||||
|
|
||||||
## 功能列表
|
|
||||||
|
|
||||||
### 1. 模型列表展示
|
|
||||||
|
|
||||||
展示已配置的模型列表,包含以下字段:
|
|
||||||
- Model Name(模型名称)
|
|
||||||
- Model Type(模型类型):Chat / Embedding / Rerank / VLM
|
|
||||||
- API Endpoint(API 端点)
|
|
||||||
- Status(状态):Active / Inactive
|
|
||||||
|
|
||||||
### 2. 添加新模型
|
|
||||||
|
|
||||||
点击 "Add New Model" 按钮,弹出表单包含以下字段:
|
|
||||||
- Model Name(必填)
|
|
||||||
- Model Type(必选):Chat / Embedding / Rerank / VLM
|
|
||||||
- Provider(必选):OpenAI / Ollama
|
|
||||||
- Model(必填):模型名称,如 gpt-4o
|
|
||||||
- API Key(必填)
|
|
||||||
- Base URL(必填)
|
|
||||||
- API Endpoint(可选)
|
|
||||||
|
|
||||||
### 3. 测试连接
|
|
||||||
|
|
||||||
在添加模型表单中提供 "Test Connection" 按钮,用于验证模型连接是否可用。
|
|
||||||
|
|
||||||
### 4. 编辑模型
|
|
||||||
|
|
||||||
点击编辑按钮,弹出编辑表单,可修改模型信息。
|
|
||||||
|
|
||||||
### 5. 删除模型
|
|
||||||
|
|
||||||
点击删除按钮,确认后删除模型记录。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 后端接口需求
|
|
||||||
|
|
||||||
### 1. 获取模型列表
|
|
||||||
|
|
||||||
**接口地址:** `GET /api/models` 或 `GET /model/list`
|
|
||||||
|
|
||||||
**返回参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"id": "1",
|
|
||||||
"name": "OpenAI",
|
|
||||||
"model_type": "chat",
|
|
||||||
"provider": "OpenAI",
|
|
||||||
"model": "gpt-4o",
|
|
||||||
"api_key": "sk-xxx",
|
|
||||||
"base_url": "https://api.openai.com",
|
|
||||||
"api_endpoint": "/v1/chat/completions",
|
|
||||||
"status": "active",
|
|
||||||
"created_at": "2024-01-01T00:00:00Z",
|
|
||||||
"updated_at": "2024-01-01T00:00:00Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 添加模型
|
|
||||||
|
|
||||||
**接口地址:** `POST /api/models` 或 `POST /model/add`
|
|
||||||
|
|
||||||
**请求参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "OpenAI",
|
|
||||||
"model_type": "chat",
|
|
||||||
"provider": "OpenAI",
|
|
||||||
"model": "gpt-4o",
|
|
||||||
"api_key": "sk-xxx",
|
|
||||||
"base_url": "https://api.openai.com",
|
|
||||||
"api_endpoint": "/v1/chat/completions"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "1",
|
|
||||||
"name": "OpenAI",
|
|
||||||
"model_type": "chat",
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 更新模型
|
|
||||||
|
|
||||||
**接口地址:** `PUT /api/models/{id}` 或 `PUT /model/{id}`
|
|
||||||
|
|
||||||
**请求参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "OpenAI Updated",
|
|
||||||
"model_type": "chat",
|
|
||||||
"provider": "OpenAI",
|
|
||||||
"model": "gpt-4o",
|
|
||||||
"api_key": "sk-xxx",
|
|
||||||
"base_url": "https://api.openai.com",
|
|
||||||
"api_endpoint": "/v1/chat/completions",
|
|
||||||
"status": "active"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 删除模型
|
|
||||||
|
|
||||||
**接口地址:** `DELETE /api/models/{id}` 或 `DELETE /model/{id}`
|
|
||||||
|
|
||||||
**返回参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 测试连接
|
|
||||||
|
|
||||||
**接口地址:** `POST /api/models/test` 或 `POST /model/test`
|
|
||||||
|
|
||||||
**请求参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"provider": "OpenAI",
|
|
||||||
"model": "gpt-4o",
|
|
||||||
"api_key": "sk-xxx",
|
|
||||||
"base_url": "https://api.openai.com",
|
|
||||||
"api_endpoint": "/v1/chat/completions"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Connection successful"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 数据结构
|
|
||||||
|
|
||||||
### Model 表结构(参考)
|
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| id | string | 主键 UUID |
|
|
||||||
| name | string | 模型名称 |
|
|
||||||
| model_type | string | 模型类型:chat/embedding/rerank/vlm |
|
|
||||||
| provider | string | 提供商:OpenAI/Ollama |
|
|
||||||
| model | string | 模型标识 |
|
|
||||||
| api_key | string | API 密钥(加密存储) |
|
|
||||||
| base_url | string | 基础 URL |
|
|
||||||
| api_endpoint | string | API 端点路径 |
|
|
||||||
| status | string | 状态:active/inactive |
|
|
||||||
| created_at | datetime | 创建时间 |
|
|
||||||
| updated_at | datetime | 更新时间 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 前端组件状态
|
|
||||||
|
|
||||||
### 表单数据 (newModelForm)
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
name: string,
|
|
||||||
apiKey: string,
|
|
||||||
apiEndpoint: string,
|
|
||||||
baseUrl: string,
|
|
||||||
provider: string,
|
|
||||||
model: string,
|
|
||||||
modelType: string
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 模型类型选项 (modelTypeOptions)
|
|
||||||
- Chat
|
|
||||||
- Embedding
|
|
||||||
- Rerank
|
|
||||||
- VLM
|
|
||||||
|
|
||||||
### 提供商选项 (providerOptions)
|
|
||||||
- OpenAI
|
|
||||||
- Ollama
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
# Neo4j 接口后端需求
|
|
||||||
|
|
||||||
## 需求说明
|
|
||||||
|
|
||||||
前端 Neo4j 图谱功能已完成,后端接口需要匹配前端调用。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 新增 `/neo4j/graphs` 接口
|
|
||||||
|
|
||||||
### 接口地址
|
|
||||||
```
|
|
||||||
POST /neo4j/graphs
|
|
||||||
```
|
|
||||||
|
|
||||||
### 请求参数
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"uri": "bolt://localhost:7687",
|
|
||||||
"username": "neo4j",
|
|
||||||
"password": "password",
|
|
||||||
"database": "neo4j"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| uri | string | 是 | Neo4j 连接地址,如 bolt://localhost:7687 |
|
|
||||||
| username | string | 是 | 用户名 |
|
|
||||||
| password | string | 是 | 密码 |
|
|
||||||
| database | string | 否 | 数据库名(默认 neo4j) |
|
|
||||||
|
|
||||||
### 返回参数
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"graphs": {
|
|
||||||
"labels": [
|
|
||||||
{"name": "User", "count": 1523},
|
|
||||||
{"name": "Order", "count": 856}
|
|
||||||
],
|
|
||||||
"relationshipTypes": [
|
|
||||||
{"name": "KNOWS", "count": 2341},
|
|
||||||
{"name": "BOUGHT", "count": 5678}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 修改路由路径
|
|
||||||
|
|
||||||
### 当前状态
|
|
||||||
- `/database/neo4j/nodes` → 需要改为 → `/neo4j/nodes`
|
|
||||||
- `/database/neo4j/relationships` → 需要改为 → `/neo4j/relationships`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
后端需要修改以下内容:
|
|
||||||
|
|
||||||
1. **新增** `/neo4j/graphs` 接口
|
|
||||||
2. **修改** `/database/neo4j/nodes` → `/neo4j/nodes`
|
|
||||||
3. **修改** `/database/neo4j/relationships` → `/neo4j/relationships`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 附:前端 API 调用示例
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 获取图谱概览
|
|
||||||
fetch('/neo4j/graphs', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
uri: 'bolt://10.10.10.189:7687',
|
|
||||||
username: 'neo4j',
|
|
||||||
password: 'neo4j',
|
|
||||||
database: 'neo4j'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取节点详情
|
|
||||||
fetch('/neo4j/nodes', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
uri: 'bolt://10.10.10.189:7687',
|
|
||||||
username: 'neo4j',
|
|
||||||
password: 'neo4j',
|
|
||||||
label: 'User',
|
|
||||||
limit: 10
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取关系详情
|
|
||||||
fetch('/neo4j/relationships', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
uri: 'bolt://10.10.10.189:7687',
|
|
||||||
username: 'neo4j',
|
|
||||||
password: 'neo4j',
|
|
||||||
relationship_type: 'KNOWS',
|
|
||||||
limit: 10
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
# Neo4j 连接成功后返回数据库 ID
|
|
||||||
|
|
||||||
## 需求说明
|
|
||||||
|
|
||||||
当前端 Connect 测试 Neo4j 连接成功后,后端需要返回数据库的 ID,以便前端保存图谱配置。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 问题
|
|
||||||
|
|
||||||
当前 `/neo4j/check` 接口返回:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "connection successful",
|
|
||||||
"version": "5.14.0",
|
|
||||||
"databases": ["neo4j", "system"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**没有返回 `databaseId`**,导致后续保存图谱时缺少 `databaseId`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 需求内容
|
|
||||||
|
|
||||||
修改 `/neo4j/check` 接口,在连接成功时:
|
|
||||||
|
|
||||||
1. **检查数据库是否已存在** - 根据 URI(bolt://host:port)、username、database 查询
|
|
||||||
2. **如果存在** - 返回已有的 `databaseId`
|
|
||||||
3. **如果不存在** - 自动创建一条数据库记录,并返回新的 `databaseId`
|
|
||||||
|
|
||||||
### 返回格式
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "connection successful",
|
|
||||||
"version": "5.14.0",
|
|
||||||
"databases": ["neo4j", "system"],
|
|
||||||
"databaseId": "xxx-xxx-xxx",
|
|
||||||
"name": "Neo4j-neo4j",
|
|
||||||
"description": "Neo4j neo4j@10.10.10.189:7687"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| success | bool | 是否成功 |
|
|
||||||
| message | string | 消息 |
|
|
||||||
| version | string | Neo4j 版本 |
|
|
||||||
| databases | array | 数据库列表 |
|
|
||||||
| databaseId | string | 数据库记录 ID |
|
|
||||||
| name | string | 数据库名称 |
|
|
||||||
| description | string | 数据库描述 |
|
|
||||||
|
|
||||||
### 请求参数
|
|
||||||
|
|
||||||
当前 `/neo4j/check` 请求:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"db_type": "Neo4j",
|
|
||||||
"host": "10.10.10.189",
|
|
||||||
"port": 7687,
|
|
||||||
"username": "neo4j",
|
|
||||||
"password": "neo4j",
|
|
||||||
"database": "neo4j"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
后端需要增加 `name` 字段用于数据库名称(可选):
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "My Neo4j Database"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 涉及文件
|
|
||||||
|
|
||||||
- `server/internal/service/neo4j_service.go`
|
|
||||||
- 函数:`Check()` - 第 81-128 行
|
|
||||||
- 函数:`ensureNeo4jDatabase()` - 第 131-175 行(已有代码但可能有问题)
|
|
||||||
|
|
||||||
- `server/internal/model/neo4j_info.go`
|
|
||||||
- 结构体:`Neo4jCheckResponse` - 需要确保 `databaseId` 字段正确返回
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 前端使用
|
|
||||||
|
|
||||||
前端代码已实现兼容处理:
|
|
||||||
```javascript
|
|
||||||
const dbId = result.databaseId || result.id || result.database_id || ''
|
|
||||||
```
|
|
||||||
|
|
||||||
所以后端返回 `databaseId`、`id` 或 `database_id` 都可以被正确识别。
|
|
||||||
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
# 后端需求 - Neo4j 图谱数据获取(完善版)
|
|
||||||
|
|
||||||
## 需求描述
|
|
||||||
|
|
||||||
Neo4j 连接成功后,需要获取图谱数据供前端可视化展示。前端使用 ECharts 力导向图谱展示科幻风格效果。
|
|
||||||
|
|
||||||
## Neo4j 图谱核心概念
|
|
||||||
|
|
||||||
Neo4j 是图数据库,与关系型数据库概念不同:
|
|
||||||
- **Node(节点)** - 类似于表,但不需要固定结构
|
|
||||||
- **Label(标签)** - 类似于表的类型名(如 User, Order)
|
|
||||||
- **Relationship(关系)** - 节点之间的边
|
|
||||||
- **Relationship Type(关系类型)** - 关系的类型(如 KNOWS, OWNS)
|
|
||||||
|
|
||||||
## 后端需要提供的接口
|
|
||||||
|
|
||||||
### 1. 获取图谱概览数据(核心接口)
|
|
||||||
|
|
||||||
返回所有 Label(标签)和 Relationship Type(关系类型)的统计信息。这是前端图谱可视化的核心数据来源。
|
|
||||||
|
|
||||||
**接口地址:** `POST /database/check` (复用现有接口,在 db_type 为 Neo4j 时返回图谱数据)
|
|
||||||
|
|
||||||
**请求参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"db_type": "Neo4j",
|
|
||||||
"uri": "bolt://localhost:7687",
|
|
||||||
"username": "neo4j",
|
|
||||||
"password": "password",
|
|
||||||
"database": "neo4j"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"graphs": {
|
|
||||||
"labels": [
|
|
||||||
{"name": "User", "count": 1523},
|
|
||||||
{"name": "Order", "count": 856},
|
|
||||||
{"name": "Product", "count": 2341},
|
|
||||||
{"name": "Category", "count": 45},
|
|
||||||
{"name": "Review", "count": 5678}
|
|
||||||
],
|
|
||||||
"relationshipTypes": [
|
|
||||||
{"name": "KNOWS", "count": 2341},
|
|
||||||
{"name": "BOUGHT", "count": 5678},
|
|
||||||
{"name": "BELONGS_TO", "count": 2341},
|
|
||||||
{"name": "HAS_REVIEW", "count": 5678},
|
|
||||||
{"name": "LOCATED_IN", "count": 1523}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 获取节点详情(可选,用于点击显示)
|
|
||||||
|
|
||||||
点击某个 Label 节点时,获取该类型节点的样本数据用于详情展示。
|
|
||||||
|
|
||||||
**接口地址:** `POST /database/neo4j/nodes`
|
|
||||||
|
|
||||||
**请求参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"uri": "bolt://localhost:7687",
|
|
||||||
"username": "neo4j",
|
|
||||||
"password": "password",
|
|
||||||
"database": "neo4j",
|
|
||||||
"label": "User",
|
|
||||||
"limit": 5
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"nodes": [
|
|
||||||
{"id": "1", "name": "张三", "email": "zhangsan@example.com", "created_at": "2024-01-01"},
|
|
||||||
{"id": "2", "name": "李四", "email": "lisi@example.com", "created_at": "2024-01-02"}
|
|
||||||
],
|
|
||||||
"properties": [
|
|
||||||
{"name": "id", "type": "string"},
|
|
||||||
{"name": "name", "type": "string"},
|
|
||||||
{"name": "email", "type": "string"},
|
|
||||||
{"name": "created_at", "type": "datetime"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 获取关系详情(可选)
|
|
||||||
|
|
||||||
获取两个节点之间的关系数据。
|
|
||||||
|
|
||||||
**接口地址:** `POST /database/neo4j/relationships`
|
|
||||||
|
|
||||||
**请求参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"uri": "bolt://localhost:7687",
|
|
||||||
"username": "neo4j",
|
|
||||||
"password": "password",
|
|
||||||
"database": "neo4j",
|
|
||||||
"relationshipType": "KNOWS",
|
|
||||||
"limit": 10
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回参数:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"relationships": [
|
|
||||||
{
|
|
||||||
"id": "rel-1",
|
|
||||||
"source": "1",
|
|
||||||
"target": "2",
|
|
||||||
"properties": {"since": "2020-01-01"}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 数据结构说明
|
|
||||||
|
|
||||||
### graphs.labels[] - 标签列表(前端图谱节点)
|
|
||||||
| 字段 | 类型 | 说明 | 用途 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| name | string | 标签名称(如 User, Order) | 作为图谱节点显示 |
|
|
||||||
| count | int | 该标签的节点数量 | 计算节点大小 symbolSize |
|
|
||||||
|
|
||||||
### graphs.relationshipTypes[] - 关系类型列表(前端图谱边)
|
|
||||||
| 字段 | 类型 | 说明 | 用途 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| name | string | 关系类型(如 KNOWS, OWNS) | 作为图谱边的标签 |
|
|
||||||
| count | int | 该关系的数量 | 可能影响边的粗细 |
|
|
||||||
|
|
||||||
### nodes[] - 节点详情
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| id | string | 节点唯一标识 |
|
|
||||||
| (其他) | any | 节点的其他属性 |
|
|
||||||
|
|
||||||
### relationships[] - 关系详情
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| id | string | 关系唯一标识 |
|
|
||||||
| source | string | 起始节点ID |
|
|
||||||
| target | string | 目标节点ID |
|
|
||||||
| properties | object | 关系属性 |
|
|
||||||
|
|
||||||
## 前端图谱展示逻辑
|
|
||||||
|
|
||||||
前端使用 ECharts 力导向图谱(force-directed graph),展示方式如下:
|
|
||||||
|
|
||||||
1. **节点生成**:
|
|
||||||
- 根据 `graphs.labels` 数组生成节点
|
|
||||||
- `name` 作为节点显示名称
|
|
||||||
- `count` 决定节点大小(symbolSize = log2(count+1) * 12)
|
|
||||||
- 节点颜色按索引分配科幻配色(紫、蓝、绿、橙、粉、青)
|
|
||||||
- 节点带发光效果(shadowBlur: 20)
|
|
||||||
|
|
||||||
2. **边生成**:
|
|
||||||
- 根据 `graphs.relationshipTypes` 生成边
|
|
||||||
- 边 label 显示关系类型名称
|
|
||||||
- 曲线连接(curveness: 0.2)
|
|
||||||
- 带箭头
|
|
||||||
|
|
||||||
3. **交互效果**:
|
|
||||||
- 弹簧物理效果(force layout)
|
|
||||||
- 节点可拖拽
|
|
||||||
- 滚轮缩放
|
|
||||||
- hover 相邻节点高亮
|
|
||||||
|
|
||||||
## 优先级
|
|
||||||
|
|
||||||
高 - Neo4j 可视化的核心数据
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. Neo4j 连接使用官方 Go 驱动:`github.com/neo4j/neo4j-go-driver`
|
|
||||||
2. 注意处理连接超时和认证失败的情况
|
|
||||||
3. 大数据量时需要限制返回数量(limit 参数)
|
|
||||||
4. **建议返回足够多的关系类型**(建议至少 5-10 个),以便前端生成丰富的图谱连接
|
|
||||||
5. 如果关系类型少于节点数,可以创建额外连接让图谱更美观
|
|
||||||
|
|
||||||
## 前端缓存策略
|
|
||||||
|
|
||||||
### 方案设计
|
|
||||||
- **首次加载**:获取数据并缓存
|
|
||||||
- **第二次展示**:直接使用缓存,秒开
|
|
||||||
- **刷新按钮**:用户手动点击刷新获取最新数据
|
|
||||||
|
|
||||||
### 实现说明
|
|
||||||
前端会缓存图谱数据,第二次进入时直接展示缓存数据,提升用户体验。同时提供"刷新"按钮供用户手动刷新。
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# 后端需求 - 支持 Neo4j 图数据库
|
|
||||||
|
|
||||||
## 需求描述
|
|
||||||
|
|
||||||
添加 Neo4j 图数据库类型支持。
|
|
||||||
|
|
||||||
## Neo4j 连接参数
|
|
||||||
|
|
||||||
Neo4j 连接需要以下参数:
|
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 | 默认值 |
|
|
||||||
|------|------|------|------|--------|
|
|
||||||
| uri | string | 是 | 连接地址 | bolt://localhost:7687 |
|
|
||||||
| username | string | 是 | 用户名 | neo4j |
|
|
||||||
| password | string | 是 | 密码 | - |
|
|
||||||
| database | string | 否 | 数据库名 | neo4j(默认数据库) |
|
|
||||||
|
|
||||||
### 连接示例
|
|
||||||
- `bolt://localhost:7687`
|
|
||||||
- `neo4j://localhost:7687`
|
|
||||||
- `bolt://192.168.1.100:7687`
|
|
||||||
|
|
||||||
## 需要修改的地方
|
|
||||||
|
|
||||||
### 1. 数据库类型列表
|
|
||||||
在前端和后端添加 "Neo4j" 选项
|
|
||||||
|
|
||||||
### 2. 连接表单
|
|
||||||
Neo4j 只需要 3-4 个字段:
|
|
||||||
- URI(连接地址)
|
|
||||||
- Username(用户名)
|
|
||||||
- Password(密码)
|
|
||||||
- Database(数据库名,可选)
|
|
||||||
|
|
||||||
### 3. 数据库服务
|
|
||||||
- `server/internal/service/database_service.go`
|
|
||||||
- 新增 `connectNeo4j` 方法
|
|
||||||
- 新增 `getNeo4jTables` 方法
|
|
||||||
|
|
||||||
## 优先级
|
|
||||||
|
|
||||||
中 - 扩展数据库类型支持
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# 后端需求 - 编辑数据库时正确处理 sub_tables
|
|
||||||
|
|
||||||
## 问题描述
|
|
||||||
|
|
||||||
用户点击 Action 修改数据库,进入 Map Tables 页面后:
|
|
||||||
1. 初始显示 2 个已选中的表
|
|
||||||
2. 用户取消选中 1 个表,只保留 1 个
|
|
||||||
3. 点击 Save Mapping 保存
|
|
||||||
4. 再次点击 Map Tables 查看时,仍然显示 2 个表,而不是修改后的 1 个
|
|
||||||
|
|
||||||
## 原因
|
|
||||||
|
|
||||||
可能的问题:
|
|
||||||
1. 编辑时没有正确从数据库加载已保存的 sub_tables 数据
|
|
||||||
2. Save Mapping 时没有正确更新 sub_tables(可能只是创建新的,没有删除已取消的)
|
|
||||||
|
|
||||||
## 需求
|
|
||||||
|
|
||||||
### 1. 加载数据库时返回 sub_tables 数据
|
|
||||||
|
|
||||||
在获取数据库详情时,需要返回该数据库已保存的子表映射信息(包括 parent_table 等),以便前端正确显示已选中的表。
|
|
||||||
|
|
||||||
### 2. 保存时正确处理子表
|
|
||||||
|
|
||||||
- 新增的子表:创建新记录
|
|
||||||
- 保留的子表:更新记录
|
|
||||||
- 取消的子表:删除对应记录
|
|
||||||
|
|
||||||
或者使用更简单的方案:
|
|
||||||
- 保存时删除该数据库所有的旧 sub_tables
|
|
||||||
- 重新创建新的 sub_tables 记录
|
|
||||||
|
|
||||||
## 状态
|
|
||||||
|
|
||||||
- [ ] 后端修改待实现
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# 后端需求 - 编辑数据库时更新 table_count
|
|
||||||
|
|
||||||
## 问题描述
|
|
||||||
|
|
||||||
用户点击 Action 修改数据库,从 2 个子表修改为 1 个子表并保存后,数据库列表中的 Tables 列没有更新为新的数量。
|
|
||||||
|
|
||||||
## 原因
|
|
||||||
|
|
||||||
编辑数据库并保存 sub_tables 时,后端没有更新 `table_count` 字段。
|
|
||||||
|
|
||||||
## 需求
|
|
||||||
|
|
||||||
在 UpdateDatabaseRequest 处理 `SubTables` 字段时,需要同步更新数据库记录的 `table_count` 字段为当前 sub_tables 的数量。
|
|
||||||
|
|
||||||
### 修改位置
|
|
||||||
|
|
||||||
- `server/internal/service/database_service.go` 或 handler
|
|
||||||
- 在 Update 方法中处理 SubTables 时更新 table_count
|
|
||||||
|
|
||||||
## 状态
|
|
||||||
|
|
||||||
- [x] 后端修改已完成
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# 后端需求 - 保存映射时更新 table_count
|
|
||||||
|
|
||||||
## 问题描述
|
|
||||||
|
|
||||||
用户在 Database 列表页面看到 Table Mapping 选中 2 个表并保存后,表格的 Tables 列仍然显示 1,没有更新为实际选中的表数量。
|
|
||||||
|
|
||||||
## 原因
|
|
||||||
|
|
||||||
保存 Table Mapping 时,后端没有更新数据库的 `table_count` 字段。
|
|
||||||
|
|
||||||
## 需求
|
|
||||||
|
|
||||||
在保存子表映射时,需要同时更新数据库的 `table_count` 字段为实际保存的子表数量。
|
|
||||||
|
|
||||||
### 修改位置
|
|
||||||
|
|
||||||
- `server/internal/service/database_service.go` 或 handler
|
|
||||||
- 在处理 `SubTables` 保存逻辑后,更新 `database_info` 表的 `table_count` 字段
|
|
||||||
|
|
||||||
### 逻辑
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 保存 sub_tables 后,更新 table_count
|
|
||||||
tableCount := len(subTables)
|
|
||||||
// 更新数据库记录的 table_count 字段
|
|
||||||
```
|
|
||||||
|
|
||||||
## 状态
|
|
||||||
|
|
||||||
- [x] 后端修改已完成
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# Web 前端需求 TODO
|
|
||||||
|
|
||||||
## 2026年3月
|
|
||||||
|
|
||||||
### 2026-03-06
|
|
||||||
|
|
||||||
- [x] **DDL 获取功能** - 后端需在获取表结构时返回 DDL 语句 ✔
|
|
||||||
- 相关文件:`server/internal/service/database_service.go`
|
|
||||||
- 函数:`getMySQLTables`, `getPostgresTables`
|
|
||||||
- 详细需求:[ddl-fetch.md](./ddl-fetch.md)
|
|
||||||
|
|
||||||
- [x] **返回结构化 columns 数据** - 后端需返回完整的列信息(column_name, data_type, column_type, is_nullable, default_value, column_key, extra, column_comment)✔
|
|
||||||
- 相关文件:`server/internal/service/database_service.go`
|
|
||||||
- 函数:`getMySQLTables`, `getPostgresTables`
|
|
||||||
- 详细需求:[columns-api.md](./columns-api.md)
|
|
||||||
|
|
||||||
- [x] **保存和读取字段映射** - 后端需支持保存/读取字段的中文映射名(mapped_name) ✔
|
|
||||||
- 相关文件:`server/internal/service/database_service.go`, `server/internal/model/`
|
|
||||||
- 详细需求:[field-mapping.md](./field-mapping.md)
|
|
||||||
|
|
||||||
- [x] **保存和恢复映射状态** - 第二次进入 Map Tables 时需恢复之前选择的表和字段映射 ✔
|
|
||||||
- 相关文件:`server/internal/service/database_service.go`, `server/internal/model/`
|
|
||||||
- 详细需求:[mapping-state.md](./mapping-state.md)
|
|
||||||
|
|
||||||
- [x] **Neo4j 图谱数据获取** - 前端已完成 ECharts 科幻风格图谱,后端需提供图谱数据接口 ✔
|
|
||||||
- 前端:使用 ECharts force-directed graph,力导向弹簧效果,可拖拽,hover 高亮
|
|
||||||
- 详细需求:[neo4j-graphs.md](./neo4j-graphs.md), [neo4j-support.md](./neo4j-support.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- [x] **Neo4j 接口路由修改** - 后端已完成 ✔
|
|
||||||
- 新增 `/neo4j/graphs` 接口
|
|
||||||
- 修改 `/database/neo4j/nodes` → `/neo4j/nodes`
|
|
||||||
- 修改 `/database/neo4j/relationships` → `/neo4j/relationships`
|
|
||||||
- 详细需求:[neo4j-api-requirement.md](./neo4j-api-requirement.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2026-03-07
|
|
||||||
|
|
||||||
- [x] **Neo4j 图谱保存接口** - 后端已完成 ✔
|
|
||||||
- 接口地址:`POST /database/graph/save`
|
|
||||||
- 详细需求:[neo4j-graph-save.md](./neo4j-graph-save.md)
|
|
||||||
|
|
||||||
- [x] **Neo4j 连接成功后返回 databaseId** - 后端已完成 ✔
|
|
||||||
- 问题:Connect 测试连接成功后没有保存数据库记录,导致后续保存图谱时缺少 databaseId
|
|
||||||
- 解决方案:/neo4j/check 成功时检查数据库是否已存在,不存在则自动创建并返回 databaseId
|
|
||||||
- 详细需求:[neo4j-check-return-id.md](./neo4j-check-return-id.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
> 需求完成后请完成者打 ✔
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# Web 前端需求 TODO
|
|
||||||
|
|
||||||
## 2026年3月
|
|
||||||
|
|
||||||
### 2026-03-08
|
|
||||||
|
|
||||||
- [x] **知识库(Knowledge Base)API** - 后端已完成 ✔
|
|
||||||
- 创建知识库、获取列表、获取详情、删除
|
|
||||||
- 上传文档、删除文档、重新解析
|
|
||||||
- 获取文档预览内容
|
|
||||||
- 详细需求:[knowledge-base-api.md](./knowledge-base-api.md)
|
|
||||||
|
|
||||||
- [x] **编辑时正确处理 sub_tables** - 后端已完成 ✔
|
|
||||||
- 问题:取消选中 1 个表后保存,再次进入仍显示 2 个表
|
|
||||||
- 详细需求:[sub-tables-edit.md](./sub-tables-edit.md)
|
|
||||||
|
|
||||||
- [x] **知识库存储配置 (MinIO/S3)** - 后端已完成 ✔
|
|
||||||
- 前端已完成:添加 storage_config 参数传递
|
|
||||||
- 后端已完成:KnowledgeBase 模型添加 storage_config 字段
|
|
||||||
- 上传文件时使用知识库的 storage_config,而非全局配置
|
|
||||||
- 详细需求:[knowledge-base-api.md](./knowledge-base-api.md)
|
|
||||||
|
|
||||||
- [x] **文档列表返回 file_url** - 后端已完成 ✔
|
|
||||||
- 问题:重新进入知识库后 PDF 无法预览
|
|
||||||
- 已确认:API 返回的 file_url 字段有值
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2026-03-07
|
|
||||||
|
|
||||||
- [x] **DDL 编辑功能** - 后端已完成 ✔
|
|
||||||
- 前端只发送 ddl 字段,不再发送 fields 字段
|
|
||||||
- 详细需求:[ddl-edit.md](./ddl-edit.md)
|
|
||||||
|
|
||||||
- [x] **保存映射时更新 table_count** - 后端已完成 ✔
|
|
||||||
- 问题:用户保存 2 个子表后,Tables 列仍显示 1
|
|
||||||
- 详细需求:[table-count-update.md](./table-count-update.md)
|
|
||||||
|
|
||||||
- [x] **编辑数据库时更新 table_count** - 后端已完成 ✔
|
|
||||||
- 问题:用户从 2 个子表修改为 1 个后,Tables 列没有更新
|
|
||||||
- 详细需求:[table-count-update-edit.md](./table-count-update-edit.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
> 需求完成后请完成者打 ✔
|
|
||||||
Reference in New Issue
Block a user