MiroFish/backend/README.md
666ghj af5c235695 Enhance OASIS simulation capabilities and profile generation
- Updated README.md to include detailed descriptions of new features, including Zep mixed search functionality and detailed persona generation for individual and group entities.
- Implemented a robust mechanism for checking simulation preparation status to avoid redundant profile generation.
- Added support for parallel profile generation, improving efficiency in creating OASIS Agent Profiles.
- Enhanced the simulation configuration generator to adopt a stepwise approach, ensuring better handling of complex configurations.
- Introduced error handling and retry mechanisms for LLM calls, improving the reliability of profile generation.
- Updated simulation management to support new API parameters for controlling profile generation behavior.
2025-12-01 19:40:07 +08:00

1838 lines
52 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# MiroFish Backend
社会舆论模拟系统后端服务基于Flask框架。
## 项目结构
```
backend/
├── app/
│ ├── __init__.py # Flask应用工厂
│ ├── config.py # 配置管理
│ ├── api/ # API路由
│ │ ├── __init__.py # Blueprint注册
│ │ ├── graph.py # Step1: 图谱相关接口
│ │ └── simulation.py # Step2: 模拟相关接口
│ ├── services/ # 业务逻辑层
│ │ ├── __init__.py # 服务模块导出
│ │ ├── ontology_generator.py # 本体生成服务
│ │ ├── graph_builder.py # 图谱构建服务
│ │ ├── text_processor.py # 文本处理服务
│ │ ├── zep_entity_reader.py # Zep实体读取与过滤
│ │ ├── oasis_profile_generator.py # Agent Profile生成器
│ │ ├── simulation_config_generator.py # LLM智能配置生成器核心
│ │ └── simulation_manager.py # 模拟管理器
│ ├── models/ # 数据模型
│ │ ├── task.py # 任务状态管理
│ │ └── project.py # 项目上下文管理
│ └── utils/ # 工具模块
│ ├── file_parser.py # 文件解析
│ ├── llm_client.py # LLM客户端
│ └── logger.py # 日志工具
├── scripts/ # 预设模拟脚本
│ ├── run_twitter_simulation.py # Twitter模拟脚本
│ ├── run_reddit_simulation.py # Reddit模拟脚本
│ └── run_parallel_simulation.py # 双平台并行脚本
├── uploads/ # 上传文件存储
│ ├── projects/ # 项目文件
│ └── simulations/ # 模拟数据(含配置和脚本副本)
├── requirements.txt
└── run.py # 启动入口
```
## 安装
```bash
conda activate MiroFish
cd backend
pip install -r requirements.txt
```
## 配置
在项目根目录 `MiroFish/.env` 中配置:
```bash
# LLM配置统一使用OpenAI格式
LLM_API_KEY=your-llm-api-key
LLM_BASE_URL=https://openrouter.ai/api/v1
LLM_MODEL_NAME=gpt-4o-mini
# Zep配置
ZEP_API_KEY=your-zep-api-key
# OASIS模拟配置可选
OASIS_DEFAULT_MAX_ROUNDS=10
```
## 启动服务
```bash
python run.py
```
服务默认运行在 http://localhost:5001
---
# 系统架构
## 完整工作流程
```
┌─────────────────────────────────────────────────────────────────────────┐
│ Step 1: 图谱构建 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 上传文档 ──→ 生成本体定义 ──→ 构建Zep图谱 ──→ 图谱数据 │
│ (PDF/MD/TXT) (LLM分析) (异步任务) (节点/边) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ Step 2: 实体读取与模拟准备 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 读取图谱节点 ──→ 过滤符合条件实体 ──→ 生成Agent Profile ──→ 生成脚本 │
│ (Zep API) (按Labels筛选) (LLM生成人设) (OASIS启动) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ Step 3: 双平台并行模拟 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Twitter模拟 │ │ Reddit模拟 │ │
│ │ (短平快交互) │ 并行运行 │ (深度话题讨论) │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │
│ ▼ │
│ 同一批智能体,模拟真实社交环境 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
# Step 1: 图谱构建 API
## 核心工作流程
```
1. 上传文件 + 生成本体
POST /api/graph/ontology/generate
→ 返回 project_id
2. 构建图谱
POST /api/graph/build
→ 返回 task_id
3. 查询任务进度
GET /api/graph/task/{task_id}
4. 获取图谱数据
GET /api/graph/data/{graph_id}
```
---
### 接口1生成本体定义
**POST** `/api/graph/ontology/generate`
上传文档,分析生成适合社会模拟的实体和关系类型定义。
**请求form-data**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `files` | File | 是 | PDF/MD/TXT文件可多个 |
| `simulation_requirement` | Text | 是 | 模拟需求描述 |
| `project_name` | Text | 否 | 项目名称 |
| `additional_context` | Text | 否 | 额外说明 |
**响应示例:**
```json
{
"success": true,
"data": {
"project_id": "proj_abc123def456",
"project_name": "武汉大学舆情分析",
"ontology": {
"entity_types": [
{
"name": "Student",
"description": "Students enrolled in educational institutions",
"attributes": [
{"name": "student_id", "type": "text", "description": "Unique identifier"},
{"name": "major", "type": "text", "description": "Field of study"}
]
}
],
"edge_types": [
{
"name": "AFFILIATED_WITH",
"description": "Indicates affiliation between entities",
"source_targets": [
{"source": "Student", "target": "University"}
]
}
]
},
"analysis_summary": "分析说明...",
"files": [{"filename": "报告.pdf", "size": 123456}],
"total_text_length": 20833
}
}
```
---
### 接口2构建图谱
**POST** `/api/graph/build`
根据 `project_id` 构建Zep知识图谱异步任务
**请求JSON**
```json
{
"project_id": "proj_abc123def456",
"graph_name": "图谱名称",
"chunk_size": 500,
"chunk_overlap": 50
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `project_id` | string | 是 | 来自接口1的返回 |
| `graph_name` | string | 否 | 图谱名称 |
| `chunk_size` | int | 否 | 文本块大小默认500 |
| `chunk_overlap` | int | 否 | 块重叠字符默认50 |
**响应:**
```json
{
"success": true,
"data": {
"project_id": "proj_abc123def456",
"task_id": "task_xyz789",
"message": "图谱构建任务已启动"
}
}
```
---
### 任务状态查询
**GET** `/api/graph/task/{task_id}`
```json
{
"success": true,
"data": {
"task_id": "task_xyz789",
"status": "processing",
"progress": 45,
"message": "Zep处理中... 15/30 完成",
"result": null
}
}
```
**状态值:**
- `pending` - 等待中
- `processing` - 处理中
- `completed` - 已完成
- `failed` - 失败
---
### 项目管理接口
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/graph/project/{project_id}` | 获取项目详情 |
| GET | `/api/graph/project/list` | 列出所有项目 |
| DELETE | `/api/graph/project/{project_id}` | 删除项目 |
---
### 图谱数据接口
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/graph/data/{graph_id}` | 获取图谱节点和边 |
| DELETE | `/api/graph/delete/{graph_id}` | 删除Zep图谱 |
---
# Step 2: 实体读取与模拟运行 API
## 核心设计理念
**全程自动化,无需人工设置参数:**
- 脚本是**预设的**,不是动态生成
- 所有模拟参数由**LLM智能生成**
- LLM读取模拟需求+文档+图谱信息,自动设置最佳参数
- **通过API接口启动和监控模拟**,前端可实时展示
## 核心工作流程
```
1. 创建模拟
POST /api/simulation/create
→ 返回 simulation_id
2. 准备模拟环境(异步任务)
POST /api/simulation/prepare
Body: { "simulation_id": "sim_xxxx" }
→ 返回 task_id立即响应
查询进度:
POST /api/simulation/prepare/status
Body: { "task_id": "task_xxxx" }
→ 返回 status, progress, result
3. 开始模拟
POST /api/simulation/start
Body: { "simulation_id": "sim_xxxx", "platform": "parallel" }
→ 在后台启动OASIS模拟进程
→ 返回运行状态
4. 实时监控(前端轮询)
GET /api/simulation/{simulation_id}/run-status/detail
→ 返回当前进度、最近Agent动作
5. 停止模拟(可选)
POST /api/simulation/stop
Body: { "simulation_id": "sim_xxxx" }
```
---
## 实体读取接口
### 获取图谱实体(已过滤)
**GET** `/api/simulation/entities/{graph_id}`
获取图谱中符合预定义实体类型的节点。
**实体过滤逻辑:**
- Zep对符合预定义类型的实体Labels为 `["Entity", "Student"]`
- 对不符合预定义类型的实体Labels仅为 `["Entity"]`
- **筛选规则**只保留Labels中包含除"Entity"和"Node"之外标签的节点
**Query参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `entity_types` | string | 否 | 逗号分隔的实体类型,用于进一步过滤 |
| `enrich` | boolean | 否 | 是否获取相关边信息默认true |
**响应示例:**
```json
{
"success": true,
"data": {
"entities": [
{
"uuid": "node_uuid_123",
"name": "杨景媛",
"labels": ["Entity", "Student"],
"summary": "武汉大学学生,图书馆事件当事人",
"attributes": {
"student_id": "2021001",
"major": "计算机科学"
},
"related_edges": [
{
"direction": "outgoing",
"edge_name": "AFFILIATED_WITH",
"fact": "杨景媛是武汉大学的学生",
"target_node_uuid": "node_uuid_456"
}
],
"related_nodes": [
{
"uuid": "node_uuid_456",
"name": "武汉大学",
"labels": ["Entity", "University"],
"summary": "中国著名高等学府"
}
]
}
],
"entity_types": ["Student", "University", "PublicFigure"],
"total_count": 100,
"filtered_count": 45
}
}
```
---
### 获取单个实体详情
**GET** `/api/simulation/entities/{graph_id}/{entity_uuid}`
获取单个实体的完整信息,包含所有相关边和关联节点。
---
### 按类型获取实体
**GET** `/api/simulation/entities/{graph_id}/by-type/{entity_type}`
获取指定类型如Student、PublicFigure的所有实体。
---
## 模拟管理接口
### 创建模拟
**POST** `/api/simulation/create`
**请求JSON**
```json
{
"project_id": "proj_abc123def456",
"graph_id": "mirofish_xxxx",
"enable_twitter": true,
"enable_reddit": true,
"max_rounds": 10,
"agents_per_round": -1
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `project_id` | string | 是 | 项目ID |
| `graph_id` | string | 否 | 图谱ID不提供则从project获取 |
| `enable_twitter` | boolean | 否 | 启用Twitter模拟默认true |
| `enable_reddit` | boolean | 否 | 启用Reddit模拟默认true |
| `max_rounds` | int | 否 | 最大模拟轮数默认10 |
| `agents_per_round` | int | 否 | 每轮激活智能体数,-1表示全部 |
**响应示例:**
```json
{
"success": true,
"data": {
"simulation_id": "sim_abc123def456",
"config": {
"project_id": "proj_xxxx",
"graph_id": "mirofish_xxxx",
"enable_twitter": true,
"enable_reddit": true,
"max_rounds": 10
},
"status": "created",
"created_at": "2025-12-01T10:00:00"
}
}
```
---
### 准备模拟环境(异步任务)
**POST** `/api/simulation/prepare`
**异步接口**:这是一个耗时操作,接口会立即返回`task_id`,通过`/prepare/status`查询进度。
执行模拟准备流程LLM智能生成所有参数带自动重试机制
1. 从Zep图谱读取并过滤实体
2. 为每个实体生成OASIS Agent Profile带重试
3. LLM智能生成模拟配置带重试
4. 保存配置文件和复制预设脚本
**请求JSON**
```json
{
"simulation_id": "sim_xxxx",
"entity_types": ["Student", "PublicFigure"],
"use_llm_for_profiles": true
}
```
**响应示例:**
```json
{
"success": true,
"data": {
"simulation_id": "sim_xxxx",
"task_id": "task_xxxx",
"status": "preparing",
"message": "准备任务已启动,请通过 /api/simulation/prepare/status 查询进度"
}
}
```
---
### 查询准备进度
**POST** `/api/simulation/prepare/status`
查询准备任务的执行进度。
**请求JSON**
```json
{
"task_id": "task_xxxx"
}
```
**响应示例:**
```json
{
"success": true,
"data": {
"task_id": "task_xxxx",
"task_type": "simulation_prepare",
"status": "processing",
"progress": 45,
"message": "[2/4] 生成Agent人设: 35/93 - 生成 教授张三 的人设...",
"progress_detail": {
"current_stage": "generating_profiles",
"current_stage_name": "生成Agent人设",
"stage_index": 2,
"total_stages": 4,
"stage_progress": 38,
"current_item": 35,
"total_items": 93,
"item_description": "生成 教授张三 的人设..."
},
"result": null,
"error": null,
"metadata": {
"project_id": "proj_xxxx",
"simulation_id": "sim_xxxx"
}
}
}
```
**进度详情字段progress_detail**
| 字段 | 类型 | 说明 |
|------|------|------|
| `current_stage` | string | 当前阶段标识 (reading/generating_profiles/generating_config/copying_scripts) |
| `current_stage_name` | string | 当前阶段中文名称 |
| `stage_index` | int | 当前阶段序号 (1-4) |
| `total_stages` | int | 总阶段数 (4) |
| `stage_progress` | int | 当前阶段内进度 (0-100) |
| `current_item` | int | 当前处理的项目序号 |
| `total_items` | int | 当前阶段总项目数 |
| `item_description` | string | 当前项目描述 |
**阶段说明:**
| 阶段 | 名称 | 权重 | 说明 |
|------|------|------|------|
| 1 | 读取图谱实体 | 0-20% | 从Zep读取并过滤实体 |
| 2 | 生成Agent人设 | 20-70% | 为每个实体生成OASIS Profile |
| 3 | 生成模拟配置 | 70-90% | LLM智能生成模拟参数 |
| 4 | 准备模拟脚本 | 90-100% | 复制预设脚本到模拟目录 |
**状态值status**
- `pending` - 等待中
- `processing` - 处理中
- `completed` - 已完成此时result包含结果
- `failed` - 失败此时error包含错误信息
**完成后的响应:**
```json
{
"success": true,
"data": {
"task_id": "task_xxxx",
"status": "completed",
"progress": 100,
"message": "任务完成",
"result": {
"simulation_id": "sim_xxxx",
"project_id": "proj_xxxx",
"graph_id": "mirofish_xxxx",
"status": "ready",
"entities_count": 93,
"profiles_count": 93,
"entity_types": ["University", "Student", ...],
"config_generated": true,
"error": null
}
}
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `entity_types` | array | 否 | 指定实体类型进行过滤 |
| `use_llm_for_profiles` | boolean | 否 | 是否使用LLM生成人设默认true |
**注意**`simulation_requirement`和`document_text`自动从项目中获取
**响应示例:**
```json
{
"success": true,
"data": {
"simulation_id": "sim_abc123def456",
"status": "ready",
"entities_count": 45,
"profiles_count": 45,
"entity_types": ["Student", "PublicFigure", "University"],
"config_generated": true,
"config_reasoning": "根据武汉大学图书馆事件的特点设置72小时模拟时长...",
"run_instructions": {
"simulation_dir": "/path/to/sim_xxx",
"commands": {...},
"instructions": "..."
}
}
}
```
---
### 获取模拟状态
**GET** `/api/simulation/{simulation_id}`
**响应示例:**
```json
{
"success": true,
"data": {
"simulation_id": "sim_abc123def456",
"status": "ready",
"entities_count": 45,
"profiles_count": 45,
"entity_types": ["Student", "PublicFigure"],
"current_round": 0,
"twitter_status": "not_started",
"reddit_status": "not_started"
}
}
```
---
### 列出所有模拟
**GET** `/api/simulation/list`
| Query参数 | 类型 | 说明 |
|-----------|------|------|
| `project_id` | string | 按项目ID过滤可选 |
---
### 获取Agent Profile
**GET** `/api/simulation/{simulation_id}/profiles`
| Query参数 | 类型 | 说明 |
|-----------|------|------|
| `platform` | string | 平台类型reddit 或 twitter |
**响应示例:**
```json
{
"success": true,
"data": {
"platform": "reddit",
"count": 45,
"profiles": [
{
"user_id": 0,
"user_name": "yangjingyuan_123",
"name": "杨景媛",
"bio": "武汉大学学生,关注教育公平与学生权益",
"persona": "杨景媛是一名积极参与社会讨论的大学生,性格内敛但观点鲜明...",
"karma": 1500,
"age": 22,
"gender": "female",
"mbti": "INFJ",
"country": "China",
"profession": "Student",
"interested_topics": ["Education", "Social Issues"]
}
]
}
}
```
---
### 获取模拟配置
**GET** `/api/simulation/{simulation_id}/config`
获取LLM智能生成的完整配置包含
- `time_config`: 时间配置
- `agent_configs`: 每个Agent的活动配置
- `event_config`: 事件配置
- `generation_reasoning`: LLM的配置推理说明
---
### 下载文件
| 接口 | 说明 |
|------|------|
| GET `/api/simulation/{id}/config/download` | 下载配置文件 |
| GET `/api/simulation/{id}/script/{script_name}/download` | 下载脚本文件 |
**脚本名称:**
- `run_twitter_simulation.py`
- `run_reddit_simulation.py`
- `run_parallel_simulation.py`
---
### 直接生成Profile
**POST** `/api/simulation/generate-profiles`
不创建模拟直接从图谱生成Agent Profile。
```json
{
"graph_id": "mirofish_xxxx",
"entity_types": ["Student", "PublicFigure"],
"use_llm": true,
"platform": "reddit"
}
```
---
## 模拟运行控制接口
### 开始模拟
**POST** `/api/simulation/start`
启动OASIS模拟在后台运行。
**请求JSON**
```json
{
"simulation_id": "sim_xxxx", // 必填
"platform": "parallel" // 可选: twitter / reddit / parallel (默认)
}
```
**响应示例:**
```json
{
"success": true,
"data": {
"simulation_id": "sim_xxxx",
"runner_status": "running",
"process_pid": 12345,
"twitter_running": true,
"reddit_running": true,
"total_rounds": 144,
"total_simulation_hours": 72,
"started_at": "2025-12-01T10:00:00"
}
}
```
---
### 停止模拟
**POST** `/api/simulation/stop`
停止正在运行的模拟。
**请求JSON**
```json
{
"simulation_id": "sim_xxxx" // 必填
}
```
**响应示例:**
```json
{
"success": true,
"data": {
"simulation_id": "sim_xxxx",
"runner_status": "stopped",
"completed_at": "2025-12-01T12:00:00",
"twitter_actions_count": 500,
"reddit_actions_count": 650
}
}
```
---
## 实时状态监控接口
### 获取运行状态(基础)
**GET** `/api/simulation/{simulation_id}/run-status`
获取模拟运行的实时状态,用于前端轮询。
**响应示例:**
```json
{
"success": true,
"data": {
"simulation_id": "sim_xxxx",
"runner_status": "running",
"current_round": 25,
"total_rounds": 144,
"progress_percent": 17.4,
"simulated_hours": 12,
"total_simulation_hours": 72,
"twitter_running": true,
"reddit_running": true,
"twitter_actions_count": 150,
"reddit_actions_count": 200,
"total_actions_count": 350,
"started_at": "2025-12-01T10:00:00",
"updated_at": "2025-12-01T10:30:00"
}
}
```
**运行状态值runner_status**
- `idle` - 未运行
- `starting` - 启动中
- `running` - 运行中
- `paused` - 已暂停
- `stopping` - 停止中
- `stopped` - 已停止
- `completed` - 已完成
- `failed` - 失败
---
### 获取运行状态(详细,含最近动作)
**GET** `/api/simulation/{simulation_id}/run-status/detail`
获取详细运行状态包含最近的Agent动作列表**用于前端实时展示动态**。
**响应示例:**
```json
{
"success": true,
"data": {
"simulation_id": "sim_xxxx",
"runner_status": "running",
"current_round": 25,
"progress_percent": 17.4,
"recent_actions": [
{
"round_num": 25,
"timestamp": "2025-12-01T10:30:00",
"platform": "twitter",
"agent_id": 3,
"agent_name": "Entity Name",
"action_type": "CREATE_POST",
"action_args": {"content": "Post content..."},
"result": null,
"success": true
},
{
"round_num": 25,
"timestamp": "2025-12-01T10:29:55",
"platform": "reddit",
"agent_id": 7,
"agent_name": "Another Entity",
"action_type": "LIKE_POST",
"action_args": {"post_id": 5},
"success": true
}
]
}
}
```
---
### 获取动作历史
**GET** `/api/simulation/{simulation_id}/actions`
获取完整的Agent动作历史记录。
**Query参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| `limit` | int | 返回数量默认100 |
| `offset` | int | 偏移量默认0 |
| `platform` | string | 过滤平台twitter/reddit |
| `agent_id` | int | 过滤Agent ID |
| `round_num` | int | 过滤轮次 |
---
### 获取时间线
**GET** `/api/simulation/{simulation_id}/timeline`
获取按轮次汇总的时间线,用于前端展示进度条和时间线视图。
**Query参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| `start_round` | int | 起始轮次默认0 |
| `end_round` | int | 结束轮次(默认全部) |
**响应示例:**
```json
{
"success": true,
"data": {
"rounds_count": 25,
"timeline": [
{
"round_num": 1,
"twitter_actions": 10,
"reddit_actions": 15,
"total_actions": 25,
"active_agents_count": 8,
"active_agents": [0, 1, 3, 5, 7, 10, 12, 15],
"action_types": {"CREATE_POST": 5, "LIKE_POST": 10, "LLM_ACTION": 10},
"first_action_time": "2025-12-01T10:00:00",
"last_action_time": "2025-12-01T10:05:00"
}
]
}
}
```
---
### 获取Agent统计
**GET** `/api/simulation/{simulation_id}/agent-stats`
获取每个Agent的活跃度统计用于展示排行榜。
**响应示例:**
```json
{
"success": true,
"data": {
"agents_count": 45,
"stats": [
{
"agent_id": 3,
"agent_name": "Active Agent",
"total_actions": 50,
"twitter_actions": 30,
"reddit_actions": 20,
"action_types": {"CREATE_POST": 10, "LIKE_POST": 25, "REPOST": 15},
"first_action_time": "2025-12-01T10:00:00",
"last_action_time": "2025-12-01T12:30:00"
}
]
}
}
```
---
## 数据库查询接口
### 获取帖子
**GET** `/api/simulation/{simulation_id}/posts`
从模拟数据库获取帖子列表。
**Query参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| `platform` | string | 平台类型twitter/reddit默认reddit |
| `limit` | int | 返回数量默认50 |
| `offset` | int | 偏移量 |
---
### 获取评论
**GET** `/api/simulation/{simulation_id}/comments`
从Reddit模拟数据库获取评论列表。
**Query参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| `post_id` | string | 过滤帖子ID可选 |
| `limit` | int | 返回数量默认50 |
| `offset` | int | 偏移量 |
---
# 服务层实现细节
## 1. ZepEntityReaderZep实体读取服务
**文件:** `app/services/zep_entity_reader.py`
### 核心功能
| 方法 | 说明 |
|------|------|
| `get_all_nodes(graph_id)` | 获取图谱所有节点 |
| `get_all_edges(graph_id)` | 获取图谱所有边 |
| `filter_defined_entities(graph_id, ...)` | 筛选符合条件的实体 |
| `get_entity_with_context(graph_id, uuid)` | 获取实体完整上下文 |
| `get_entities_by_type(graph_id, type)` | 按类型获取实体 |
### 数据结构
```python
@dataclass
class EntityNode:
uuid: str # 节点UUID
name: str # 实体名称
labels: List[str] # 标签列表 ["Entity", "Student"]
summary: str # 实体摘要
attributes: Dict[str, Any] # 属性字典
related_edges: List[Dict] # 相关边信息
related_nodes: List[Dict] # 关联节点信息
def get_entity_type(self) -> Optional[str]:
"""获取实体类型排除默认Entity标签"""
@dataclass
class FilteredEntities:
entities: List[EntityNode] # 实体列表
entity_types: Set[str] # 发现的实体类型
total_count: int # 总节点数
filtered_count: int # 过滤后数量
```
### 过滤逻辑示例
```python
# Zep返回的节点Labels示例
# 符合预定义类型: ["Entity", "Student"]
# 不符合预定义类型: ["Entity"]
for node in all_nodes:
labels = node.get("labels", [])
custom_labels = [l for l in labels if l not in ["Entity", "Node"]]
if not custom_labels:
# 只有默认标签,跳过
continue
# 保留符合条件的实体
entity_type = custom_labels[0]
filtered_entities.append(node)
```
---
## 2. OasisProfileGeneratorAgent Profile生成器
**文件:** `app/services/oasis_profile_generator.py`
### 核心功能
| 方法 | 说明 |
|------|------|
| `generate_profile_from_entity(entity, user_id)` | 从实体生成单个Profile带详细人设 |
| `generate_profiles_from_entities(entities, graph_id)` | 批量生成Profile |
| `save_profiles(profiles, path, platform)` | 保存Profile文件 |
| `_search_zep_for_entity(entity_name)` | 调用Zep检索获取额外上下文 |
### 优化特性v2.0
1. **Zep混合搜索功能**:使用多种查询策略获取丰富的实体信息
2. **区分实体类型**:个人实体 vs 群体/机构实体,使用不同的提示词
3. **详细人设生成**生成500字以上的详细人设描述
### Zep混合搜索策略
`_search_zep_for_entity()` 方法采用多种搜索策略获取丰富信息:
**查询策略:**
```python
queries = [
f"总结{entity_name}的全部活动、事件和行为",
f"{entity_name}与其他实体的关系和互动",
f"{entity_name}的背景、历史和重要信息",
f"关于{entity_name}的所有事实和描述",
]
```
**说明:** Zep没有内置的混合搜索接口需要分别搜索edges和nodes。我们使用**并行请求**同时执行两个搜索:
```python
# 并行执行edges和nodes搜索
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
edge_future = executor.submit(search_edges) # scope="edges"
node_future = executor.submit(search_nodes) # scope="nodes"
edge_result = edge_future.result(timeout=30)
node_result = node_future.result(timeout=30)
```
**搜索参数:**
| 搜索类型 | scope | limit | 说明 |
|----------|-------|-------|------|
| 边搜索 | edges | 30 | 获取事实/关系信息 |
| 节点搜索 | nodes | 20 | 获取相关实体摘要 |
**关键参数:**
- 必须传递 `graph_id` 参数否则Zep API会返回400错误
- 使用 `rrf` (Reciprocal Rank Fusion) reranker稳定可靠
- 使用线程池并行执行,提高效率
**返回数据结构:**
```python
{
"facts": [...], # 事实列表来自edges
"node_summaries": [...], # 相关节点摘要来自nodes
"context": "..." # 综合上下文文本
}
```
### LLM生成与JSON修复
为了避免LLM生成的JSON解析失败实现了以下优化
1. **不限制max_tokens**让LLM自由发挥充分利用模型的上下文能力
2. **多次重试机制**最多3次尝试每次降低temperature
3. **截断检测与修复**:检测`finish_reason='length'`自动闭合JSON
4. **完善JSON修复机制**
- `_fix_truncated_json()`: 修复被截断的JSON闭合括号和字符串
- `_try_fix_json()`: 多级修复策略
- 提取JSON部分
- 替换字符串中的换行符
- 移除控制字符
- 从损坏JSON中提取部分信息
5. **字段验证**确保必需字段存在缺失时使用entity_summary填充
**错误处理流程**
```
LLM调用 → 检查截断 → JSON解析 → 修复尝试 → 部分提取 → 规则生成
```
### 并行生成与实时输出
支持并行生成Agent人设提高生成效率
```python
profiles = generator.generate_profiles_from_entities(
entities=filtered.entities,
use_llm=True,
graph_id="mirofish_xxx",
parallel_count=5 # 并行生成数量默认5
)
```
**API参数**
```json
POST /api/simulation/prepare
{
"simulation_id": "sim_xxx",
"parallel_profile_count": 5, // 可选并行生成人设数量默认5
"force_regenerate": false // 可选强制重新生成默认false
}
```
**实时输出**
- 每生成一个人设,立即输出到控制台(完整内容不截断)
- 包含用户名、简介、详细人设、年龄、性别、MBTI等信息
- 方便实时监控生成进度和质量
### 避免重复生成
系统会自动检测已完成的准备工作,避免重复生成:
**检测条件**
1. `state.json` 存在且 `config_generated=true`
2. 必要文件存在:`reddit_profiles.json`, `twitter_profiles.csv`, `simulation_config.json`
**API响应**
```json
// 已准备完成时
{
"success": true,
"data": {
"simulation_id": "sim_xxx",
"status": "ready",
"message": "已有完成的准备工作,无需重复生成",
"already_prepared": true,
"prepare_info": {
"entities_count": 93,
"profiles_count": 93,
"entity_types": ["Student", "Professor", ...],
"existing_files": [...]
}
}
}
```
**强制重新生成**
```json
POST /api/simulation/prepare
{
"simulation_id": "sim_xxx",
"force_regenerate": true // 忽略已有准备,强制重新生成
}
```
### 实体类型分类
```python
# 个人类型实体 - 生成具体人物设定
INDIVIDUAL_ENTITY_TYPES = [
"student", "alumni", "professor", "person", "publicfigure",
"expert", "faculty", "official", "journalist", "activist"
]
# 群体/机构类型实体 - 生成官方账号设定
GROUP_ENTITY_TYPES = [
"university", "governmentagency", "organization", "ngo",
"mediaoutlet", "company", "institution", "group", "community"
]
```
### Profile数据结构
```python
@dataclass
class OasisAgentProfile:
# 基础字段
user_id: int # 用户ID
user_name: str # 用户名
name: str # 显示名称
bio: str # 简介max 150字符
persona: str # 详细人设描述500字以上
# Reddit字段
karma: int = 1000
# Twitter字段
friend_count: int = 100
follower_count: int = 150
statuses_count: int = 500
# 人设详情
age: Optional[int] = None
gender: Optional[str] = None
mbti: Optional[str] = None # INTJ, ENFP等
country: Optional[str] = None
profession: Optional[str] = None
interested_topics: List[str] = []
# 来源信息
source_entity_uuid: Optional[str] = None
source_entity_type: Optional[str] = None
```
### 详细人设生成示例
**个人实体人设结构:**
```markdown
## 一、基本信息
- 姓名/称呼、年龄、职业/身份
- 教育背景、所在地
## 二、人物背景
- 过去的重要经历
- 与事件的关联
- 社会关系网络
## 三、性格特征
- MBTI类型及表现
- 核心性格特点
- 情绪表达方式
## 四、社交媒体行为模式
- 发帖频率和时间
- 内容偏好类型
- 语言风格特点
## 五、立场与观点
- 对核心话题的态度
- 可能被激怒/感动的内容
## 六、独特特征
- 口头禅、个人爱好等
```
### Profile生成策略
**1. LLM生成默认**
使用LLM根据实体信息生成详细人设
```python
prompt = f"""
Entity: {entity_name} ({entity_type})
Summary: {entity_summary}
Context: {related_edges_and_nodes}
Generate a social media user profile with:
- bio (max 150 chars)
- persona (detailed description)
- age, gender, mbti, country
- profession, interested_topics
"""
```
**2. 规则生成Fallback**
根据实体类型使用预定义模板:
| 实体类型 | 生成策略 |
|----------|----------|
| Student/Alumni | 年龄18-30学生身份关注教育话题 |
| PublicFigure/Expert | 年龄35-60专业人士政治经济话题 |
| MediaOutlet | 媒体官方账号,新闻时事话题 |
| University/GovernmentAgency | 机构官方账号,政策公告话题 |
---
## 3. SimulationConfigGenerator模拟配置智能生成器
**文件:** `app/services/simulation_config_generator.py`
### 核心功能
使用LLM分析模拟需求、文档内容、图谱实体信息自动生成最佳的模拟参数配置。
**采用分步生成策略**(避免一次性生成过长内容导致失败):
1. 生成时间配置(轻量级)
2. 生成事件配置和热点话题
3. 分批生成Agent配置**每批5个**,保证生成质量)
4. 生成平台配置
| 方法 | 说明 |
|------|------|
| `generate_config(...)` | 智能生成完整模拟配置(分步) |
| `_generate_time_config(...)` | 生成时间配置 |
| `_generate_event_config(...)` | 生成事件配置 |
| `_generate_agent_configs_batch(...)` | 分批生成Agent配置 |
| `_generate_agent_config_by_rule(...)` | 规则生成LLM失败时 |
### 中国人作息时间配置
系统针对中国用户群体,采用符合北京时间的作息习惯:
| 时段 | 时间范围 | 活跃度系数 | 说明 |
|------|----------|------------|------|
| 深夜 | 0:00-5:59 | 0.05 | 几乎无人活动 |
| 早间 | 6:00-8:59 | 0.4 | 逐渐醒来 |
| 工作 | 9:00-18:59 | 0.7 | 工作时段中等活跃 |
| 高峰 | 19:00-22:59 | 1.5 | 晚间最活跃 |
| 夜间 | 23:00-23:59 | 0.5 | 活跃度下降 |
### LLM智能生成的配置内容
**1. TimeSimulationConfig时间配置**
```python
@dataclass
class TimeSimulationConfig:
total_simulation_hours: int = 72 # 模拟总时长(小时)
minutes_per_round: int = 30 # 每轮代表的时间(分钟)
agents_per_hour_min: int = 5 # 每小时激活Agent数量最小
agents_per_hour_max: int = 20 # 每小时激活Agent数量最大
peak_hours: List[int] = [19,20,21,22] # 高峰时段(晚间)
off_peak_hours: List[int] = [0,1,2,3,4,5] # 低谷时段(凌晨)
peak_activity_multiplier: float = 1.5 # 高峰活跃度乘数
off_peak_activity_multiplier: float = 0.05 # 凌晨活跃度极低
morning_hours: List[int] = [6,7,8] # 早间时段
morning_activity_multiplier: float = 0.4
work_hours: List[int] = [9-18] # 工作时段
work_activity_multiplier: float = 0.7
```
**2. AgentActivityConfig每个Agent的活动配置**
```python
@dataclass
class AgentActivityConfig:
agent_id: int
entity_uuid: str
entity_name: str
entity_type: str
activity_level: float = 0.5 # 整体活跃度 (0.0-1.0)
posts_per_hour: float = 1.0 # 每小时发帖频率
comments_per_hour: float = 2.0 # 每小时评论频率
active_hours: List[int] # 活跃时间段 (0-23)
response_delay_min: int = 5 # 响应延迟最小值(分钟)
response_delay_max: int = 60 # 响应延迟最大值(分钟)
sentiment_bias: float = 0.0 # 情感倾向 (-1到1)
stance: str = "neutral" # 立场 (supportive/opposing/neutral/observer)
influence_weight: float = 1.0 # 影响力权重
```
**3. 不同实体类型的默认参数差异(符合中国人作息)**
| 实体类型 | 活跃度 | 发帖频率 | 活跃时段 | 响应延迟 | 影响力 |
|----------|--------|----------|----------|----------|--------|
| University/GovernmentAgency | 0.2 | 0.1/小时 | 9:00-17:59工作时间 | 60-240分钟 | 3.0 |
| MediaOutlet | 0.5 | 0.8/小时 | 7:00-23:59全天 | 5-30分钟 | 2.5 |
| Professor/Expert | 0.4 | 0.3/小时 | 8:00-21:59工作+晚间) | 15-90分钟 | 2.0 |
| Student | 0.8 | 0.6/小时 | 8-13, 18-23上午+晚间) | 1-15分钟 | 0.8 |
| Alumni | 0.6 | 0.4/小时 | 12-13, 19-23午休+晚间) | 5-30分钟 | 1.0 |
| Person普通人 | 0.7 | 0.5/小时 | 9-13, 18-23白天+晚间) | 2-20分钟 | 1.0 |
**注意**凌晨0-5点所有实体类型都几乎不活动符合中国人作息习惯
---
## 4. SimulationManager模拟管理器
**文件:** `app/services/simulation_manager.py`
### 核心功能
| 方法 | 说明 |
|------|------|
| `create_simulation(project_id, graph_id, ...)` | 创建模拟 |
| `prepare_simulation(simulation_id, ...)` | 准备模拟环境(调用配置生成器) |
| `get_simulation(simulation_id)` | 获取模拟状态 |
| `get_profiles(simulation_id, platform)` | 获取Profile |
| `get_simulation_config(simulation_id)` | 获取模拟配置 |
| `get_run_instructions(simulation_id)` | 获取运行说明 |
### 模拟状态流转
```
created → preparing → ready → running → completed
↓ ↓
failed paused
```
### 生成的文件结构
```
uploads/simulations/sim_xxxx/
├── state.json # 模拟状态
├── simulation_config.json # LLM生成的模拟配置核心文件
├── reddit_profiles.json # Reddit Agent ProfileJSON格式
├── twitter_profiles.csv # Twitter Agent ProfileCSV格式
├── run_reddit_simulation.py # 预设Reddit模拟脚本
├── run_twitter_simulation.py # 预设Twitter模拟脚本
├── run_parallel_simulation.py # 预设双平台并行脚本
├── reddit_simulation.db # Reddit数据库运行后生成
└── twitter_simulation.db # Twitter数据库运行后生成
```
**重要OASIS平台的Profile格式要求不同**
**Twitter CSV格式**符合OASIS官方要求
```csv
user_id,name,username,user_char,description
0,张教授,professor_zhang,"完整人设描述LLM内部使用","简短简介(外部显示)"
```
- `user_id`: 从0开始的顺序ID
- `name`: 真实姓名
- `username`: 系统用户名
- `user_char`: 完整人设bio + persona注入LLM系统提示指导Agent行为
- `description`: 简短简介,显示在用户资料页面
**Reddit JSON格式**
```json
[
{
"realname": "张教授",
"username": "professor_zhang",
"bio": "简短简介",
"persona": "详细人设描述",
"age": 42,
"gender": "男",
"mbti": "INTJ",
"country": "中国",
"profession": "教授",
"interested_topics": ["高等教育", "学术诚信"]
}
]
```
**user_char vs description 区别**
| 字段 | 用途 | 可见性 |
|------|------|--------|
| user_char | LLM系统提示决定Agent如何思考和行动 | 内部使用 |
| description | 用户资料页面的简介 | 其他用户可见 |
### 配置文件示例 (simulation_config.json)
```json
{
"simulation_id": "sim_abc123",
"project_id": "proj_xxx",
"graph_id": "mirofish_xxx",
"simulation_requirement": "分析武汉大学图书馆事件舆论传播",
"time_config": {
"total_simulation_hours": 72,
"minutes_per_round": 30,
"agents_per_hour_min": 5,
"agents_per_hour_max": 15,
"peak_hours": [9, 10, 11, 14, 15, 20, 21, 22],
"off_peak_hours": [0, 1, 2, 3, 4, 5],
"peak_activity_multiplier": 1.5,
"off_peak_activity_multiplier": 0.3
},
"agent_configs": [
{
"agent_id": 0,
"entity_name": "武汉大学",
"entity_type": "University",
"activity_level": 0.15,
"posts_per_hour": 0.08,
"comments_per_hour": 0.02,
"active_hours": [9, 10, 11, 14, 15, 16, 17],
"response_delay_min": 120,
"response_delay_max": 360,
"sentiment_bias": 0.1,
"stance": "neutral",
"influence_weight": 4.0
},
{
"agent_id": 1,
"entity_name": "杨景媛",
"entity_type": "Student",
"activity_level": 0.8,
"posts_per_hour": 0.5,
"comments_per_hour": 2.0,
"active_hours": [7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
"response_delay_min": 1,
"response_delay_max": 15,
"sentiment_bias": -0.3,
"stance": "opposing",
"influence_weight": 1.5
}
],
"event_config": {
"initial_posts": [
{
"poster_agent_id": 1,
"content": "今天在图书馆发生的事情让我非常失望..."
}
],
"hot_topics": ["图书馆事件", "学生权益", "校方回应"],
"narrative_direction": "事件发酵后各方反应的模拟"
},
"generation_reasoning": "根据武汉大学图书馆事件的特点1)涉及学生与校方的冲突设置学生高活跃度、校方低频但高影响力2)事件性质属于短期热点设置72小时模拟时长3)主要当事人杨景媛设置为高活跃度且持opposing立场..."
}
---
## 5. 预设模拟脚本
**目录:** `backend/scripts/`
脚本是**预设的**,不是动态生成。每次准备模拟时,脚本会被复制到模拟目录。
### 脚本说明
| 脚本 | 说明 |
|------|------|
| `run_twitter_simulation.py` | Twitter单平台模拟 |
| `run_reddit_simulation.py` | Reddit单平台模拟 |
| `run_parallel_simulation.py` | 双平台并行模拟(推荐) |
### 脚本工作原理
```python
# 脚本读取配置文件,自动设置所有参数
class TwitterSimulationRunner:
def __init__(self, config_path: str):
self.config = self._load_config() # 读取simulation_config.json
def _get_active_agents_for_round(self, env, current_hour, round_num):
"""根据时间和配置决定本轮激活哪些Agent"""
time_config = self.config.get("time_config", {})
agent_configs = self.config.get("agent_configs", [])
# 1. 检查是否高峰/低谷时段,调整激活数量
# 2. 遍历每个Agent配置检查是否在活跃时间
# 3. 根据activity_level计算激活概率
# 4. 返回本轮应激活的Agent列表
...
async def run(self):
# 1. 创建LLM模型
# 2. 加载Agent图
# 3. 执行初始事件从event_config读取
# 4. 主循环根据配置激活不同Agent
...
```
### 使用方式
```bash
# 进入模拟目录
cd backend/uploads/simulations/sim_xxxx/
# 运行模拟
python run_parallel_simulation.py --config simulation_config.json
# 其他选项
python run_parallel_simulation.py --config simulation_config.json --twitter-only
python run_parallel_simulation.py --config simulation_config.json --reddit-only
```
---
## 6. Profile文件格式说明
**OASIS对两个平台的Profile格式有不同要求**
### Twitter Profile (CSV格式)
```csv
user_id,user_name,name,bio,friend_count,follower_count,statuses_count,created_at
0,user0,User Zero,I am user zero with interests in technology.,100,150,500,2023-01-01
1,user1,User One,Tech enthusiast and coffee lover.,200,250,1000,2023-01-02
```
| 字段 | 类型 | 说明 |
|------|------|------|
| `user_id` | int | 用户ID |
| `user_name` | string | 用户名 |
| `name` | string | 显示名称 |
| `bio` | string | 简介 |
| `friend_count` | int | 关注数 |
| `follower_count` | int | 粉丝数 |
| `statuses_count` | int | 发帖数 |
| `created_at` | string | 创建日期 |
### Reddit Profile (JSON详细格式)
```json
[
{
"realname": "Test User",
"username": "test_user_123",
"bio": "A test user for validation",
"persona": "Test User is an enthusiastic participant in social discussions.",
"age": 25,
"gender": "male",
"mbti": "INTJ",
"country": "China",
"profession": "Student",
"interested_topics": ["Technology", "Education"]
}
]
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `realname` | string | 是 | 真实姓名 |
| `username` | string | 是 | 用户名 |
| `bio` | string | 是 | 简介最大150字符 |
| `persona` | string | 是 | 详细人设描述 |
| `age` | int | 否 | 年龄 |
| `gender` | string | 否 | 性别 |
| `mbti` | string | 否 | MBTI人格类型 |
| `country` | string | 否 | 国家 |
| `profession` | string | 否 | 职业 |
| `interested_topics` | array | 否 | 感兴趣话题列表 |
---
## 7. OASIS平台动作类型
### Twitter可用动作
| 动作 | 说明 |
|------|------|
| `CREATE_POST` | 发布推文 |
| `LIKE_POST` | 点赞推文 |
| `REPOST` | 转发推文 |
| `FOLLOW` | 关注用户 |
| `QUOTE_POST` | 引用转发 |
| `DO_NOTHING` | 不执行动作 |
### Reddit可用动作
| 动作 | 说明 |
|------|------|
| `CREATE_POST` | 发布帖子 |
| `CREATE_COMMENT` | 发表评论 |
| `LIKE_POST` | 点赞帖子 |
| `DISLIKE_POST` | 踩帖子 |
| `LIKE_COMMENT` | 点赞评论 |
| `DISLIKE_COMMENT` | 踩评论 |
| `SEARCH_POSTS` | 搜索帖子 |
| `SEARCH_USER` | 搜索用户 |
| `TREND` | 查看热门 |
| `REFRESH` | 刷新推荐 |
| `FOLLOW` | 关注用户 |
| `MUTE` | 屏蔽用户 |
| `DO_NOTHING` | 不执行动作 |
---
# 实体设计原则
本系统专为社会舆论模拟设计,实体必须是:
**可以是:**
- 具体的个人(有名有姓)
- 注册的公司、组织、机构
- 媒体机构
- 政府部门
- 高校、NGO等
**不可以是:**
- 抽象概念(如"技术"、"创新"
- 情绪、观点、趋势
- 泛指的群体(如"用户"、"消费者"
这是因为后续需要模拟各实体对舆论的反应和传播,抽象概念无法参与这种模拟。
---
# 项目状态流转
```
created → ontology_generated → graph_building → graph_completed
failed
```
---
# 运行模拟
准备完成后,进入模拟数据目录运行预设脚本:
```bash
# 激活conda环境
conda activate MiroFish
# 进入模拟目录
cd backend/uploads/simulations/sim_xxxx/
# 运行单平台模拟
python run_reddit_simulation.py --config simulation_config.json
# 或
python run_twitter_simulation.py --config simulation_config.json
# 运行双平台并行模拟(推荐)
python run_parallel_simulation.py --config simulation_config.json
```
### 脚本参数
| 参数 | 说明 |
|------|------|
| `--config` | 配置文件路径(必填) |
| `--twitter-only` | 只运行Twitter模拟仅parallel脚本 |
| `--reddit-only` | 只运行Reddit模拟仅parallel脚本 |
### 输出文件
模拟运行后会生成:
- `twitter_simulation.db` - Twitter模拟数据库
- `reddit_simulation.db` - Reddit模拟数据库
可使用SQLite工具查看模拟结果帖子、评论、点赞等
---
# API调用重试机制
**文件:** `app/utils/retry.py`
为LLM等外部API调用提供自动重试功能提高系统稳定性。
## 重试策略
- **最大重试次数**3次
- **退避策略**指数退避1s → 2s → 4s
- **最大延迟**30秒
- **随机抖动**:避免请求堆积
## 使用方式
**装饰器方式:**
```python
from app.utils.retry import retry_with_backoff
@retry_with_backoff(max_retries=3)
def call_llm_api():
return client.chat.completions.create(...)
```
**客户端方式:**
```python
from app.utils.retry import RetryableAPIClient
retry_client = RetryableAPIClient(max_retries=3)
result = retry_client.call_with_retry(some_function, arg1, arg2)
```
**批量处理(单项失败不影响其他):**
```python
results, failures = retry_client.call_batch_with_retry(
items=entities,
process_func=generate_profile,
continue_on_failure=True
)
```
## 已应用重试机制的模块
| 模块 | 说明 |
|------|------|
| `OasisProfileGenerator` | LLM生成Agent人设 |
| `SimulationConfigGenerator` | LLM生成模拟配置 |
---
# 依赖说明
```
# Flask框架
flask>=3.0.0
flask-cors>=4.0.0
# Zep Cloud SDK
zep-cloud>=2.0.0
# OpenAI SDKLLM调用
openai>=1.0.0
# PDF处理
PyMuPDF>=1.24.0
# 环境变量
python-dotenv>=1.0.0
# 数据验证
pydantic>=2.0.0
# OASIS社交媒体模拟
oasis-ai>=0.1.0
camel-ai>=0.2.0
```