MiroFish/backend/README.md
666ghj 5f159f6d88 Enhance backend functionality with OASIS simulation features
- Updated README.md to include new simulation scripts and configuration details for OASIS, including API retry mechanisms and environment variable settings.
- Added simulation management and configuration generation services to streamline the simulation process across Twitter and Reddit platforms.
- Introduced new API routes for simulation-related operations, including entity retrieval and simulation status management.
- Implemented a robust retry mechanism for external API calls to improve system stability.
- Enhanced task management model to include detailed progress tracking.
- Added logging capabilities for action tracking during simulations.
- Included new scripts for running parallel simulations and testing profile formats.
2025-12-01 15:03:44 +08:00

1591 lines
44 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)` | 批量生成Profile |
| `save_profiles_to_json(profiles, path, platform)` | 保存到JSON文件 |
### Profile数据结构
```python
@dataclass
class OasisAgentProfile:
# 基础字段
user_id: int # 用户ID
user_name: str # 用户名
name: str # 显示名称
bio: str # 简介max 150字符
persona: str # 详细人设描述
# 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
```
### 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分析模拟需求、文档内容、图谱实体信息自动生成最佳的模拟参数配置。
| 方法 | 说明 |
|------|------|
| `generate_config(...)` | 智能生成完整模拟配置 |
| `_build_context(...)` | 构建LLM上下文最大5万字 |
| `_generate_config_with_llm(...)` | 调用LLM生成配置 |
| `_generate_default_config(...)` | 默认配置LLM失败时 |
### 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] # 高峰时段 [9,10,11,14,15,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.3 # 低谷活跃度乘数
```
**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/小时 | 60-240分钟 | 3.0 |
| MediaOutlet | 0.6 | 1.0/小时 | 5-30分钟 | 2.5 |
| PublicFigure/Expert | 0.5 | 0.3/小时 | 10-60分钟 | 2.0 |
| Student/Person | 0.7 | 0.5/小时 | 1-20分钟 | 1.0 |
---
## 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格式字段`user_id,user_name,name,bio,friend_count,follower_count,statuses_count,created_at`
- **Reddit**: 使用JSON格式支持详细人设字段`realname,username,bio,persona,age,gender,mbti,country,profession,interested_topics`
### 配置文件示例 (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
```