From d096aa2b26fb6090e303318e9912e9bdaa72a390 Mon Sep 17 00:00:00 2001
From: 666ghj <670939375@qq.com>
Date: Tue, 16 Dec 2025 20:58:48 +0800
Subject: [PATCH] Remove outdated README.md and update favicon in index.html
- Deleted the backend README.md file as it was no longer needed.
- Changed the favicon from a SVG to a PNG format for better compatibility.
- Updated the page title in index.html to reflect a more concise branding message.
---
backend/README.md | 2985 --------------------------------------
frontend/index.html | 4 +-
frontend/public/icon.png | Bin 0 -> 30341 bytes
frontend/public/vite.svg | 1 -
4 files changed, 2 insertions(+), 2988 deletions(-)
delete mode 100644 backend/README.md
create mode 100644 frontend/public/icon.png
delete mode 100644 frontend/public/vite.svg
diff --git a/backend/README.md b/backend/README.md
deleted file mode 100644
index 44d437e..0000000
--- a/backend/README.md
+++ /dev/null
@@ -1,2985 +0,0 @@
-# MiroFish Backend - 详细技术文档
-
-## 目录
-
-- [项目简介](#项目简介)
-- [技术架构](#技术架构)
-- [技术栈](#技术栈)
-- [项目结构](#项目结构)
-- [核心功能模块](#核心功能模块)
-- [API接口文档](#api接口文档)
-- [数据模型](#数据模型)
-- [服务层详解](#服务层详解)
-- [工具类](#工具类)
-- [配置说明](#配置说明)
-- [运行指南](#运行指南)
-- [开发指南](#开发指南)
-- [常见问题](#常见问题)
-
----
-
-## 项目简介
-
-**MiroFish Backend** 是一个基于 Flask 的后端服务,用于社交媒体舆论模拟。系统核心功能包括:
-
-1. **知识图谱构建**: 从文档中提取实体和关系,使用 Zep Cloud 构建知识图谱
-2. **本体生成**: 使用 LLM 自动分析文档并生成适合舆论模拟的实体类型和关系类型
-3. **Agent人设生成**: 基于图谱实体,使用 LLM 生成详细的社交媒体用户人设
-4. **模拟配置智能生成**: 使用 LLM 根据需求自动生成模拟参数(时间、活跃度、事件等)
-5. **双平台模拟**: 支持 Twitter 和 Reddit 双平台并行舆论模拟(基于 OASIS 框架)
-6. **图谱记忆动态更新**: 可选功能,将模拟中Agent的活动实时更新到Zep图谱,让图谱"记住"模拟过程
-7. **智能报告生成**: 使用 LangChain + Zep 实现 ReACT 模式的模拟分析报告自动生成
-8. **Report Agent对话**: 报告生成后可与Report Agent对话,自主调用检索工具回答问题
-
----
-
-## 技术架构
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ MiroFish Backend │
-├─────────────────────────────────────────────────────────────┤
-│ Flask Web Framework + CORS │
-│ ┌────────────────┐ ┌──────────────┐ ┌─────────────────┐ │
-│ │ API层 │ │ 服务层 │ │ 模型层 │ │
-│ │ - graph.py │→ │ - 本体生成 │→ │ - Project │ │
-│ │ - simulation │ │ - 图谱构建 │ │ - Task │ │
-│ │ - report.py │ │ - 实体读取 │ │ - Report │ │
-│ └────────────────┘ │ - 人设生成 │ └─────────────────┘ │
-│ │ - 配置生成 │ │
-│ │ - 模拟运行 │ │
-│ │ - 报告生成 │ │
-│ └──────────────┘ │
-├─────────────────────────────────────────────────────────────┤
-│ 外部服务集成 │
-│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
-│ │ Zep Cloud│ │ LLM API │ │ OASIS │ │ 文件系统│ │
-│ │ 知识图谱 │ │ (OpenAI) │ │ 社交模拟│ │ 存储 │ │
-│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
-└─────────────────────────────────────────────────────────────┘
-```
-
-### 核心流程
-
-1. **图谱构建流程**:
- ```
- 上传文档 → 提取文本 → LLM生成本体 → 文本分块 → Zep构建图谱
- ```
-
-2. **模拟准备流程**:
- ```
- 创建模拟 → 读取图谱实体 → LLM生成人设 → LLM生成配置 → 准备完成
- ```
-
-3. **模拟运行流程**:
- ```
- 启动模拟 → 运行OASIS脚本 → 实时监控 → 记录动作 → (可选)更新Zep图谱记忆 → 状态查询
- ```
-
-4. **Interview采访流程**:
- ```
- 模拟完成 → 环境进入等待模式 → 发送Interview命令 → Agent回答 → 获取结果 → (可选)关闭环境
- ```
-
-5. **报告生成流程**:
- ```
- 模拟完成 → 调用Report API → ReACT规划大纲 → 逐章节生成(多次工具调用) → 生成Markdown报告 → 解锁Interview功能
- ```
-
-6. **Report Agent对话流程**:
- ```
- 用户提问 → Agent分析 → 调用Zep检索工具 → 整合信息 → 返回回答
- ```
-
----
-
-## 技术栈
-
-### 核心框架
-- **Flask 3.0+**: Web 框架
-- **Flask-CORS**: 跨域支持
-
-### AI & 知识图谱
-- **Zep Cloud SDK 2.0+**: 知识图谱构建与管理
-- **OpenAI SDK 1.0+**: LLM 调用(支持 OpenAI 兼容接口)
-- **LangChain 0.2+**: Report Agent框架(ReACT模式)
-- **OASIS-AI**: 社交媒体模拟框架
-- **CAMEL-AI**: Agent 行为模拟
-
-### 数据处理
-- **PyMuPDF (fitz)**: PDF 文本提取
-- **Pydantic 2.0+**: 数据验证
-- **Python-dotenv**: 环境变量管理
-
-### 文件处理
-- **Werkzeug 3.0+**: 文件上传处理
-
----
-
-## 项目结构
-
-```
-backend/
-├── run.py # 启动入口
-├── requirements.txt # Python依赖
-├── .env # 环境配置(需创建)
-├── logs/ # 日志文件
-│ └── YYYY-MM-DD.log
-├── uploads/ # 数据存储
-│ ├── projects/ # 项目数据
-│ │ └── proj_xxx/
-│ │ ├── project.json # 项目元数据
-│ │ ├── files/ # 上传的文件
-│ │ └── extracted_text.txt # 提取的文本
-│ ├── reports/ # 报告数据
-│ │ └── report_xxx/
-│ │ ├── report_xxx.json # 报告元数据
-│ │ └── report_xxx.md # Markdown报告
-│ └── simulations/ # 模拟数据
-│ └── sim_xxx/
-│ ├── state.json # 模拟状态
-│ ├── simulation_config.json # 模拟配置
-│ ├── reddit_profiles.json # Reddit人设
-│ ├── twitter_profiles.csv # Twitter人设
-│ ├── run_state.json # 运行状态
-│ ├── simulation.log # 主日志
-│ ├── twitter/ # Twitter数据
-│ │ ├── actions.jsonl
-│ │ └── twitter_simulation.db
-│ └── reddit/ # Reddit数据
-│ ├── actions.jsonl
-│ └── reddit_simulation.db
-├── scripts/ # 模拟运行脚本
-│ ├── run_twitter_simulation.py
-│ ├── run_reddit_simulation.py
-│ ├── run_parallel_simulation.py
-│ └── action_logger.py
-└── app/
- ├── __init__.py # Flask应用工厂
- ├── config.py # 配置管理
- ├── api/ # API路由
- │ ├── __init__.py
- │ ├── graph.py # 图谱相关接口
- │ ├── simulation.py # 模拟相关接口
- │ └── report.py # 报告相关接口
- ├── models/ # 数据模型
- │ ├── __init__.py
- │ ├── project.py # 项目模型
- │ └── task.py # 任务模型
- ├── services/ # 业务服务
- │ ├── __init__.py
- │ ├── ontology_generator.py # 本体生成
- │ ├── graph_builder.py # 图谱构建
- │ ├── text_processor.py # 文本处理
- │ ├── zep_entity_reader.py # 实体读取
- │ ├── zep_tools.py # Zep检索工具服务
- │ ├── oasis_profile_generator.py # 人设生成
- │ ├── simulation_config_generator.py # 配置生成
- │ ├── simulation_manager.py # 模拟管理
- │ ├── simulation_runner.py # 模拟运行
- │ ├── simulation_ipc.py # 模拟IPC通信(Interview功能)
- │ ├── zep_graph_memory_updater.py # 图谱记忆动态更新
- │ └── report_agent.py # 报告生成Agent(ReACT模式)
- └── utils/ # 工具类
- ├── __init__.py
- ├── file_parser.py # 文件解析
- ├── llm_client.py # LLM客户端
- ├── logger.py # 日志配置
- └── retry.py # 重试机制
-```
-
----
-
-## 核心功能模块
-
-### 1. 图谱构建模块
-
-**功能**: 从文档构建知识图谱
-
-**流程**:
-1. 上传文档(PDF/TXT/MD)
-2. 提取文本内容
-3. LLM分析生成本体(实体类型+关系类型)
-4. 文本分块(chunk_size=500, overlap=50)
-5. 调用 Zep API 构建图谱
-6. 等待 Zep 处理完成
-7. 返回图谱ID和统计信息
-
-**核心服务**:
-- `OntologyGenerator`: 本体生成
-- `GraphBuilderService`: 图谱构建
-- `TextProcessor`: 文本处理
-
-### 2. 模拟准备模块
-
-**功能**: 准备舆论模拟所需的所有数据
-
-**流程**:
-1. 创建模拟(指定project_id和graph_id)
-2. 从 Zep 图谱读取并过滤实体
-3. 为每个实体生成 OASIS Agent Profile(支持并行)
-4. 使用 LLM 智能生成模拟配置(时间/活跃度/事件)
-5. 保存配置文件和人设文件
-
-**核心服务**:
-- `ZepEntityReader`: 实体读取与过滤
-- `OasisProfileGenerator`: Agent人设生成
-- `SimulationConfigGenerator`: 模拟配置生成
-- `SimulationManager`: 模拟管理
-
-### 3. 模拟运行模块
-
-**功能**: 运行 Twitter/Reddit 双平台舆论模拟
-
-**流程**:
-1. 检查模拟准备状态
-2. 启动 OASIS 模拟进程(subprocess)
-3. 监控进程运行状态
-4. 解析动作日志(actions.jsonl)
-5. (可选)将Agent活动实时更新到Zep图谱
-6. 实时更新运行状态
-7. 模拟完成后进入等待命令模式
-8. 支持停止/暂停/恢复
-
-**核心服务**:
-- `SimulationRunner`: 模拟运行器
-- `ZepGraphMemoryUpdater`: 图谱记忆动态更新器
-
-### 4. Agent采访(Interview)模块
-
-**功能**: 在模拟完成后对Agent进行采访
-
-**特点**:
-- **模拟状态持久化**: 模拟完成后环境不立即关闭,进入等待命令模式
-- **IPC通信机制**: 通过文件系统在Flask后端和模拟脚本之间通信
-- **单个采访**: 对指定Agent提问并获取回答
-- **批量采访**: 同时对多个Agent提不同问题
-- **全局采访**: 使用相同问题采访所有Agent
-- **采访历史**: 从数据库读取所有Interview记录
-
-**核心服务**:
-- `SimulationIPCClient`: IPC客户端(Flask端使用)
-- `SimulationIPCServer`: IPC服务器(模拟脚本端使用)
-
-### 5. Report Agent模块(报告生成)
-
-**功能**: 模拟完成后自动生成分析报告,支持与用户对话
-
-**特点**:
-- **ReACT模式**: Reasoning + Acting,多轮思考与工具调用
-- **大纲规划**: LLM分析模拟需求,自动规划报告目录结构
-- **分段生成**: 逐章节生成,每章节可多次调用Zep检索工具
-- **Markdown输出**: 生成专业的Markdown格式报告
-- **对话功能**: 报告完成后可与Report Agent对话,自主调用工具回答问题
-
-**工具(MCP封装)**:
-- `search_graph`: 图谱语义搜索
-- `get_graph_statistics`: 获取图谱统计信息
-- `get_entity_summary`: 获取实体关系摘要
-- `get_simulation_context`: 获取模拟上下文
-- `get_entities_by_type`: 按类型获取实体
-
-**核心服务**:
-- `ZepToolsService`: Zep检索工具封装
-- `ReportAgent`: 报告生成Agent(ReACT模式)
-- `ReportManager`: 报告持久化管理
-
-**工作原理**:
-```
-┌─────────────────────────────────────────────────────────────┐
-│ Report Agent (ReACT) │
-├─────────────────────────────────────────────────────────────┤
-│ │
-│ 1. 规划阶段 │
-│ ┌─────────────────────────────────────────────────┐ │
-│ │ LLM分析模拟需求 → 获取图谱上下文 → 生成报告大纲 │ │
-│ └─────────────────────────────────────────────────┘ │
-│ │
-│ 2. 生成阶段 (每章节) │
-│ ┌─────────────────────────────────────────────────┐ │
-│ │ Thought → Action → Observation → ... → Final │ │
-│ │ ↓ ↓ ↓ │ │
-│ │ 分析需求 调用工具 分析结果 生成内容 │ │
-│ └─────────────────────────────────────────────────┘ │
-│ │
-│ 3. 输出阶段 │
-│ ┌─────────────────────────────────────────────────┐ │
-│ │ 组装章节 → 生成Markdown → 保存JSON/MD文件 │ │
-│ └─────────────────────────────────────────────────┘ │
-│ │
-└─────────────────────────────────────────────────────────────┘
-```
-
-**工作原理**:
-```
-Flask后端 模拟脚本
- │ │
- │ 写入命令文件 │
- │ ─────────────────────────→│
- │ │ 轮询命令目录
- │ │ 执行Interview
- │ │ 写入响应文件
- │←───────────────────────── │
- │ 读取响应文件 │
- │ │
-```
-
----
-
-## API接口文档
-
-### 图谱管理接口
-
-#### 1. 生成本体
-
-**接口**: `POST /api/graph/ontology/generate`
-
-**请求类型**: `multipart/form-data`
-
-**请求参数**:
-| 参数 | 类型 | 必填 | 说明 |
-|------|------|------|------|
-| files | File[] | 是 | 上传的文档(PDF/MD/TXT) |
-| simulation_requirement | String | 是 | 模拟需求描述 |
-| project_name | String | 否 | 项目名称 |
-| additional_context | String | 否 | 额外说明 |
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "project_id": "proj_33469c670f56",
- "project_name": "学术不端事件模拟",
- "ontology": {
- "entity_types": [
- {
- "name": "Student",
- "description": "Students involved in the event",
- "attributes": [
- {"name": "full_name", "type": "text", "description": "Student full name"},
- {"name": "major", "type": "text", "description": "Major field"}
- ],
- "examples": ["张三", "李四"]
- },
- {
- "name": "Professor",
- "description": "Faculty members",
- "attributes": [...]
- },
- ...
- {
- "name": "Person",
- "description": "Any individual person not fitting other specific person types",
- "attributes": [...]
- },
- {
- "name": "Organization",
- "description": "Any organization not fitting other specific types",
- "attributes": [...]
- }
- ],
- "edge_types": [
- {
- "name": "STUDIES_AT",
- "description": "Student studies at university",
- "source_targets": [
- {"source": "Student", "target": "University"}
- ],
- "attributes": []
- },
- ...
- ]
- },
- "analysis_summary": "文档涉及学术不端事件...",
- "files": [
- {"filename": "document.pdf", "size": 102400}
- ],
- "total_text_length": 12345
- }
-}
-```
-
-**说明**:
-- 本体设计必须包含10个实体类型,最后2个为兜底类型(`Person`和`Organization`)
-- 实体类型必须是现实中可以发声的主体
-- 属性名不能使用保留字(`name`, `uuid`, `group_id`, `created_at`, `summary`)
-
----
-
-#### 2. 构建图谱
-
-**接口**: `POST /api/graph/build`
-
-**请求类型**: `application/json`
-
-**请求参数**:
-```json
-{
- "project_id": "proj_33469c670f56",
- "graph_name": "学术不端事件图谱",
- "chunk_size": 500,
- "chunk_overlap": 50,
- "force": false
-}
-```
-
-| 参数 | 类型 | 必填 | 默认值 | 说明 |
-|------|------|------|--------|------|
-| project_id | String | 是 | - | 项目ID(来自接口1) |
-| graph_name | String | 否 | 项目名称 | 图谱名称 |
-| chunk_size | Integer | 否 | 500 | 文本块大小 |
-| chunk_overlap | Integer | 否 | 50 | 块重叠大小 |
-| force | Boolean | 否 | false | 强制重新构建 |
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "project_id": "proj_33469c670f56",
- "task_id": "a1b2c3d4-e5f6-...",
- "message": "图谱构建任务已启动,请通过 /task/{task_id} 查询进度"
- }
-}
-```
-
-**异步任务**: 此接口立即返回task_id,实际构建在后台进行
-
----
-
-#### 3. 查询任务状态
-
-**接口**: `GET /api/graph/task/{task_id}`
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "task_id": "a1b2c3d4-e5f6-...",
- "task_type": "graph_build",
- "status": "processing",
- "created_at": "2025-12-02T10:00:00",
- "updated_at": "2025-12-02T10:05:00",
- "progress": 45,
- "message": "Zep处理中... 10/30 完成",
- "result": null,
- "error": null,
- "metadata": {
- "project_id": "proj_33469c670f56"
- }
- }
-}
-```
-
-**状态值**:
-- `pending`: 等待中
-- `processing`: 处理中
-- `completed`: 已完成
-- `failed`: 失败
-
----
-
-#### 4. 获取图谱数据
-
-**接口**: `GET /api/graph/data/{graph_id}`
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "graph_id": "mirofish_abc123",
- "nodes": [
- {
- "uuid": "node-uuid-1",
- "name": "张三",
- "labels": ["Entity", "Student"],
- "summary": "某大学计算机专业学生",
- "attributes": {
- "full_name": "张三",
- "major": "计算机科学"
- }
- },
- ...
- ],
- "edges": [
- {
- "uuid": "edge-uuid-1",
- "name": "STUDIES_AT",
- "fact": "张三就读于某大学",
- "source_node_uuid": "node-uuid-1",
- "target_node_uuid": "node-uuid-2",
- "attributes": {}
- },
- ...
- ],
- "node_count": 50,
- "edge_count": 120
- }
-}
-```
-
----
-
-#### 5. 项目管理接口
-
-**获取项目**: `GET /api/graph/project/{project_id}`
-
-**列出项目**: `GET /api/graph/project/list?limit=50`
-
-**删除项目**: `DELETE /api/graph/project/{project_id}`
-
-**重置项目**: `POST /api/graph/project/{project_id}/reset`
-
----
-
-### 模拟管理接口
-
-#### 1. 创建模拟
-
-**接口**: `POST /api/simulation/create`
-
-**请求参数**:
-```json
-{
- "project_id": "proj_33469c670f56",
- "graph_id": "mirofish_abc123",
- "enable_twitter": true,
- "enable_reddit": true
-}
-```
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "simulation_id": "sim_10b494550540",
- "project_id": "proj_33469c670f56",
- "graph_id": "mirofish_abc123",
- "status": "created",
- "enable_twitter": true,
- "enable_reddit": true,
- "created_at": "2025-12-02T10:00:00"
- }
-}
-```
-
----
-
-#### 2. 准备模拟
-
-**接口**: `POST /api/simulation/prepare`
-
-**请求参数**:
-```json
-{
- "simulation_id": "sim_10b494550540",
- "entity_types": ["Student", "Professor"],
- "use_llm_for_profiles": true,
- "parallel_profile_count": 5,
- "force_regenerate": false
-}
-```
-
-| 参数 | 类型 | 必填 | 默认值 | 说明 |
-|------|------|------|--------|------|
-| simulation_id | String | 是 | - | 模拟ID |
-| entity_types | String[] | 否 | null | 指定实体类型(为空则全部) |
-| use_llm_for_profiles | Boolean | 否 | true | 是否用LLM生成详细人设 |
-| parallel_profile_count | Integer | 否 | 5 | 并行生成人设数量 |
-| force_regenerate | Boolean | 否 | false | 强制重新生成 |
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "simulation_id": "sim_10b494550540",
- "task_id": "task_xyz789",
- "status": "preparing",
- "message": "准备任务已启动",
- "already_prepared": false
- }
-}
-```
-
-**特性**:
-- 自动检测已完成的准备工作,避免重复生成
-- 支持并行生成人设(默认5个并发)
-- 支持强制重新生成
-
----
-
-#### 3. 查询准备进度
-
-**接口**: `POST /api/simulation/prepare/status`
-
-**请求参数**:
-```json
-{
- "task_id": "task_xyz789",
- "simulation_id": "sim_10b494550540"
-}
-```
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "task_id": "task_xyz789",
- "status": "processing",
- "progress": 45,
- "message": "[2/4] 生成Agent配置: 5/15 - 已完成 Student: 张三",
- "progress_detail": {
- "current_stage": "generating_profiles",
- "current_stage_name": "生成Agent人设",
- "stage_index": 2,
- "total_stages": 4,
- "stage_progress": 33,
- "current_item": 5,
- "total_items": 15,
- "item_description": "已完成 Student: 张三"
- },
- "already_prepared": false
- }
-}
-```
-
-**进度阶段**:
-1. `reading`: 读取图谱实体 (0-20%)
-2. `generating_profiles`: 生成Agent人设 (20-70%)
-3. `generating_config`: 生成模拟配置 (70-90%)
-4. `copying_scripts`: 准备模拟脚本 (90-100%)
-
----
-
-#### 4. 启动模拟
-
-**接口**: `POST /api/simulation/start`
-
-**请求参数**:
-```json
-{
- "simulation_id": "sim_10b494550540",
- "platform": "parallel",
- "max_rounds": 100,
- "enable_graph_memory_update": false
-}
-```
-
-| 参数 | 类型 | 必填 | 默认值 | 说明 |
-|------|------|------|--------|------|
-| simulation_id | String | 是 | - | 模拟ID |
-| platform | String | 否 | parallel | 运行平台: twitter/reddit/parallel |
-| max_rounds | Integer | 否 | - | 最大模拟轮数,用于截断过长的模拟 |
-| enable_graph_memory_update | Boolean | 否 | false | 是否将Agent活动动态更新到Zep图谱 |
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "simulation_id": "sim_10b494550540",
- "runner_status": "running",
- "process_pid": 12345,
- "twitter_running": true,
- "reddit_running": true,
- "started_at": "2025-12-02T11:00:00",
- "total_rounds": 100,
- "max_rounds_applied": 100,
- "graph_memory_update_enabled": true,
- "graph_id": "mirofish_abc123"
- }
-}
-```
-
-> **说明**:
-> - `max_rounds_applied` 字段仅在指定了 `max_rounds` 参数时返回
-> - `graph_memory_update_enabled` 和 `graph_id` 字段在启用图谱记忆更新时返回
-
-**图谱记忆更新功能说明**:
-
-启用 `enable_graph_memory_update` 后:
-- 模拟中所有Agent的活动(发帖、评论、点赞、转发等)会实时更新到Zep图谱
-- 每条活动单独发送,确保Zep能正确解析实体和关系
-- 活动会被转换为自然语言描述,例如:`张三: 发布了一条帖子:「...」`
-- Zep会自动从文本中提取实体和关系,丰富图谱知识
-- 需要项目已构建有效的图谱(graph_id)
-
----
-
-#### 5. 停止模拟
-
-**接口**: `POST /api/simulation/stop`
-
-**请求参数**:
-```json
-{
- "simulation_id": "sim_10b494550540"
-}
-```
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "simulation_id": "sim_10b494550540",
- "runner_status": "stopped",
- "completed_at": "2025-12-02T12:00:00"
- }
-}
-```
-
----
-
-### Interview 采访接口
-
-> **注意**: 所有Interview接口的参数都通过请求体(JSON)传递,包括simulation_id。
->
-> **双平台模式说明**: 当不指定`platform`参数时,双平台模拟会同时采访两个平台并返回整合结果。
->
-> **Prompt自动优化**: 系统会自动在用户提供的prompt前添加说明前缀,避免Agent调用工具:
-> ```
-> 原始prompt: "武汉大学发布撤销处分通告后你有什么看法"
-> 优化后: "结合你的人设、所有的过往记忆与行动,不调用任何工具直接用文本回复我:武汉大学发布撤销处分通告后你有什么看法"
-> ```
-
-#### 1. 采访单个Agent
-
-**接口**: `POST /api/simulation/interview`
-
-**请求参数**:
-```json
-{
- "simulation_id": "sim_xxxx",
- "agent_id": 0,
- "prompt": "你对这件事有什么看法?",
- "platform": "reddit",
- "timeout": 60
-}
-```
-
-| 参数 | 类型 | 必填 | 默认值 | 说明 |
-|------|------|------|--------|------|
-| simulation_id | String | 是 | - | 模拟ID |
-| agent_id | Integer | 是 | - | Agent ID |
-| prompt | String | 是 | - | 采访问题 |
-| platform | String | 否 | null | 指定平台(twitter/reddit),不指定则双平台同时采访 |
-| timeout | Integer | 否 | 60 | 超时时间(秒) |
-
-**返回示例(指定单平台)**:
-```json
-{
- "success": true,
- "data": {
- "success": true,
- "agent_id": 0,
- "prompt": "你对这件事有什么看法?",
- "result": {
- "agent_id": 0,
- "response": "我认为这件事反映了...",
- "platform": "reddit",
- "timestamp": "2025-12-08T10:00:00"
- },
- "timestamp": "2025-12-08T10:00:01"
- }
-}
-```
-
-**返回示例(不指定platform,双平台模式)**:
-```json
-{
- "success": true,
- "data": {
- "success": true,
- "agent_id": 0,
- "prompt": "你对这件事有什么看法?",
- "result": {
- "agent_id": 0,
- "prompt": "你对这件事有什么看法?",
- "platforms": {
- "twitter": {
- "agent_id": 0,
- "response": "从Twitter视角来看...",
- "platform": "twitter",
- "timestamp": "2025-12-08T10:00:00"
- },
- "reddit": {
- "agent_id": 0,
- "response": "作为Reddit用户,我认为...",
- "platform": "reddit",
- "timestamp": "2025-12-08T10:00:00"
- }
- }
- },
- "timestamp": "2025-12-08T10:00:01"
- }
-}
-```
-
-**注意**: 此功能需要模拟环境处于运行状态(完成模拟循环后进入等待命令模式)
-
----
-
-#### 2. 批量采访多个Agent
-
-**接口**: `POST /api/simulation/interview/batch`
-
-**请求参数**:
-```json
-{
- "simulation_id": "sim_xxxx",
- "interviews": [
- {"agent_id": 0, "prompt": "你对A有什么看法?", "platform": "twitter"},
- {"agent_id": 1, "prompt": "你对B有什么看法?", "platform": "reddit"},
- {"agent_id": 2, "prompt": "你对C有什么看法?"}
- ],
- "platform": "reddit",
- "timeout": 120
-}
-```
-
-| 参数 | 类型 | 必填 | 默认值 | 说明 |
-|------|------|------|--------|------|
-| simulation_id | String | 是 | - | 模拟ID |
-| interviews | Array | 是 | - | 采访列表,每项包含agent_id、prompt和可选的platform |
-| platform | String | 否 | null | 默认平台(被每项的platform覆盖),不指定则双平台同时采访 |
-| timeout | Integer | 否 | 120 | 超时时间(秒) |
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "success": true,
- "interviews_count": 3,
- "result": {
- "interviews_count": 6,
- "results": {
- "twitter_0": {"agent_id": 0, "response": "...", "platform": "twitter"},
- "reddit_1": {"agent_id": 1, "response": "...", "platform": "reddit"},
- "twitter_2": {"agent_id": 2, "response": "...", "platform": "twitter"},
- "reddit_2": {"agent_id": 2, "response": "...", "platform": "reddit"}
- }
- },
- "timestamp": "2025-12-08T10:00:01"
- }
-}
-```
-
----
-
-#### 3. 全局采访(采访所有Agent)
-
-**接口**: `POST /api/simulation/interview/all`
-
-**请求参数**:
-```json
-{
- "simulation_id": "sim_xxxx",
- "prompt": "你对这件事整体有什么看法?",
- "platform": "reddit",
- "timeout": 180
-}
-```
-
-| 参数 | 类型 | 必填 | 默认值 | 说明 |
-|------|------|------|--------|------|
-| simulation_id | String | 是 | - | 模拟ID |
-| prompt | String | 是 | - | 采访问题(所有Agent使用相同问题) |
-| platform | String | 否 | null | 指定平台(twitter/reddit),不指定则双平台同时采访 |
-| timeout | Integer | 否 | 180 | 超时时间(秒) |
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "success": true,
- "interviews_count": 50,
- "result": {
- "interviews_count": 100,
- "results": {
- "twitter_0": {"agent_id": 0, "response": "...", "platform": "twitter"},
- "reddit_0": {"agent_id": 0, "response": "...", "platform": "reddit"},
- "twitter_1": {"agent_id": 1, "response": "...", "platform": "twitter"},
- "reddit_1": {"agent_id": 1, "response": "...", "platform": "reddit"},
- ...
- }
- },
- "timestamp": "2025-12-08T10:00:01"
- }
-}
-```
-
----
-
-#### 4. 获取Interview历史
-
-**接口**: `POST /api/simulation/interview/history`
-
-**请求参数**:
-```json
-{
- "simulation_id": "sim_xxxx",
- "platform": "reddit",
- "agent_id": 0,
- "limit": 100
-}
-```
-
-| 参数 | 类型 | 必填 | 默认值 | 说明 |
-|------|------|------|--------|------|
-| simulation_id | String | 是 | - | 模拟ID |
-| platform | String | 否 | null | 平台类型(reddit/twitter),不指定则返回两个平台的所有历史 |
-| agent_id | Integer | 否 | - | 只获取该Agent的采访历史 |
-| limit | Integer | 否 | 100 | 返回数量限制 |
-
-**返回示例(不指定platform,返回双平台历史)**:
-```json
-{
- "success": true,
- "data": {
- "count": 10,
- "history": [
- {
- "agent_id": 0,
- "response": "我认为...",
- "prompt": "你对这件事有什么看法?",
- "timestamp": "2025-12-08T10:00:02",
- "platform": "twitter"
- },
- {
- "agent_id": 0,
- "response": "从Reddit角度来看...",
- "prompt": "你对这件事有什么看法?",
- "timestamp": "2025-12-08T10:00:01",
- "platform": "reddit"
- },
- ...
- ]
- }
-}
-```
-
----
-
-#### 5. 获取模拟环境状态
-
-**接口**: `POST /api/simulation/env-status`
-
-**请求参数**:
-```json
-{
- "simulation_id": "sim_xxxx"
-}
-```
-
-| 参数 | 类型 | 必填 | 默认值 | 说明 |
-|------|------|------|--------|------|
-| simulation_id | String | 是 | - | 模拟ID |
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "simulation_id": "sim_xxxx",
- "env_alive": true,
- "twitter_available": true,
- "reddit_available": true,
- "message": "环境正在运行,可以接收Interview命令"
- }
-}
-```
-
----
-
-#### 6. 关闭模拟环境
-
-**接口**: `POST /api/simulation/close-env`
-
-**请求参数**:
-```json
-{
- "simulation_id": "sim_10b494550540",
- "timeout": 30
-}
-```
-
-| 参数 | 类型 | 必填 | 默认值 | 说明 |
-|------|------|------|--------|------|
-| simulation_id | String | 是 | - | 模拟ID |
-| timeout | Integer | 否 | 30 | 超时时间(秒) |
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "success": true,
- "message": "环境关闭命令已发送",
- "result": {"message": "环境即将关闭"},
- "timestamp": "2025-12-08T10:00:01"
- }
-}
-```
-
-**注意**: 此接口与 `/stop` 不同:
-- `/stop`: 强制终止模拟进程
-- `/close-env`: 优雅地关闭环境,让模拟进程正常退出
-
----
-
-### Report 报告接口
-
-> **说明**: 报告生成完成后才能解锁Interview功能。Report Agent使用ReACT模式,可以在对话中自主调用Zep检索工具。
-
-#### 1. 生成报告
-
-**接口**: `POST /api/report/generate`
-
-**请求参数**:
-```json
-{
- "simulation_id": "sim_xxxx",
- "force_regenerate": false
-}
-```
-
-| 参数 | 类型 | 必填 | 默认值 | 说明 |
-|------|------|------|--------|------|
-| simulation_id | String | 是 | - | 模拟ID |
-| force_regenerate | Boolean | 否 | false | 强制重新生成 |
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "simulation_id": "sim_xxxx",
- "task_id": "task_xxxx",
- "status": "generating",
- "message": "报告生成任务已启动",
- "already_generated": false
- }
-}
-```
-
-**如果报告已存在**:
-```json
-{
- "success": true,
- "data": {
- "simulation_id": "sim_xxxx",
- "report_id": "report_xxxx",
- "status": "completed",
- "message": "报告已存在",
- "already_generated": true
- }
-}
-```
-
----
-
-#### 2. 查询生成进度
-
-**接口**: `POST /api/report/generate/status`
-
-**请求参数**:
-```json
-{
- "task_id": "task_xxxx",
- "simulation_id": "sim_xxxx"
-}
-```
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "task_id": "task_xxxx",
- "status": "processing",
- "progress": 45,
- "message": "[generating] 正在生成章节: 关键发现 (3/5)"
- }
-}
-```
-
----
-
-#### 3. 获取报告
-
-**接口**: `GET /api/report/{report_id}`
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "report_id": "report_xxxx",
- "simulation_id": "sim_xxxx",
- "graph_id": "mirofish_xxxx",
- "simulation_requirement": "模拟武汉大学撤销处分后的舆情走向",
- "status": "completed",
- "outline": {
- "title": "武汉大学撤销处分事件舆情分析报告",
- "summary": "基于模拟结果的全面舆情分析",
- "sections": [
- {"title": "执行摘要", "content": "..."},
- {"title": "模拟背景", "content": "..."},
- {"title": "关键发现", "content": "..."},
- {"title": "舆情分析", "content": "..."},
- {"title": "建议与展望", "content": "..."}
- ]
- },
- "markdown_content": "# 武汉大学撤销处分事件舆情分析报告\n\n...",
- "created_at": "2025-12-09T10:00:00",
- "completed_at": "2025-12-09T10:05:00"
- }
-}
-```
-
----
-
-#### 4. 根据模拟ID获取报告
-
-**接口**: `GET /api/report/by-simulation/{simulation_id}`
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {...},
- "has_report": true
-}
-```
-
----
-
-#### 5. 下载报告
-
-**接口**: `GET /api/report/{report_id}/download`
-
-**返回**: Markdown文件下载
-
----
-
-#### 6. 与Report Agent对话
-
-**接口**: `POST /api/report/chat`
-
-**请求参数**:
-```json
-{
- "simulation_id": "sim_xxxx",
- "message": "请详细解释一下舆情的主要趋势",
- "chat_history": [
- {"role": "user", "content": "报告提到了哪些关键人物?"},
- {"role": "assistant", "content": "根据分析,关键人物包括..."}
- ]
-}
-```
-
-| 参数 | 类型 | 必填 | 默认值 | 说明 |
-|------|------|------|--------|------|
-| simulation_id | String | 是 | - | 模拟ID |
-| message | String | 是 | - | 用户消息 |
-| chat_history | Array | 否 | [] | 对话历史(用于上下文) |
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "response": "根据模拟数据分析,舆情的主要趋势表现为...\n\n1. **初期阶段**:...\n2. **发酵阶段**:...\n3. **高峰阶段**:...",
- "tool_calls": [
- {"name": "search_graph", "parameters": {"query": "舆情趋势"}},
- {"name": "get_graph_statistics", "parameters": {}}
- ],
- "sources": []
- }
-}
-```
-
----
-
-#### 7. 检查报告状态
-
-**接口**: `GET /api/report/check/{simulation_id}`
-
-**用途**: 判断是否解锁Interview功能
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "simulation_id": "sim_xxxx",
- "has_report": true,
- "report_status": "completed",
- "report_id": "report_xxxx",
- "interview_unlocked": true
- }
-}
-```
-
----
-
-#### 8. 列出所有报告
-
-**接口**: `GET /api/report/list?simulation_id=sim_xxxx&limit=50`
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": [...],
- "count": 5
-}
-```
-
----
-
-#### 9. 删除报告
-
-**接口**: `DELETE /api/report/{report_id}`
-
----
-
-#### 10. 工具调试接口
-
-**图谱搜索**: `POST /api/report/tools/search`
-```json
-{
- "graph_id": "mirofish_xxxx",
- "query": "舆情走向",
- "limit": 10
-}
-```
-
-**图谱统计**: `POST /api/report/tools/statistics`
-```json
-{
- "graph_id": "mirofish_xxxx"
-}
-```
-
----
-
-#### 6. 获取运行状态
-
-**接口**: `GET /api/simulation/{simulation_id}/run-status`
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- "simulation_id": "sim_10b494550540",
- "runner_status": "running",
- "current_round": 5,
- "total_rounds": 144,
- "progress_percent": 3.5,
- "simulated_hours": 2,
- "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-02T11:00:00",
- "updated_at": "2025-12-02T11:30:00"
- }
-}
-```
-
----
-
-#### 7. 获取详细状态(含最近动作)
-
-**接口**: `GET /api/simulation/{simulation_id}/run-status/detail`
-
-**返回示例**:
-```json
-{
- "success": true,
- "data": {
- ... (基本状态同上) ...,
- "recent_actions": [
- {
- "round_num": 5,
- "timestamp": "2025-12-02T11:30:15",
- "platform": "twitter",
- "agent_id": 3,
- "agent_name": "张三_123",
- "action_type": "CREATE_POST",
- "action_args": {
- "content": "对学术不端事件的看法..."
- },
- "result": "post_id_123",
- "success": true
- },
- ...
- ]
- }
-}
-```
-
----
-
-#### 8. 其他接口
-
-**获取实体列表**: `GET /api/simulation/entities/{graph_id}`
-
-**获取模拟配置**: `GET /api/simulation/{simulation_id}/config`
-
-**获取Agent人设**: `GET /api/simulation/{simulation_id}/profiles?platform=reddit`
-
-**获取动作历史**: `GET /api/simulation/{simulation_id}/actions?limit=100&platform=twitter`
-
-**获取时间线**: `GET /api/simulation/{simulation_id}/timeline?start_round=0&end_round=10`
-
-**获取Agent统计**: `GET /api/simulation/{simulation_id}/agent-stats`
-
-**获取帖子**: `GET /api/simulation/{simulation_id}/posts?platform=reddit&limit=50`
-
-**获取评论**: `GET /api/simulation/{simulation_id}/comments?post_id=123`
-
----
-
-## 数据模型
-
-### 1. Project (项目模型)
-
-**文件**: `app/models/project.py`
-
-**字段**:
-```python
-project_id: str # 项目ID (proj_xxx)
-name: str # 项目名称
-status: ProjectStatus # 状态
-created_at: str # 创建时间
-updated_at: str # 更新时间
-
-# 文件信息
-files: List[Dict] # 上传的文件列表
-total_text_length: int # 文本总长度
-
-# 本体信息
-ontology: Dict # 实体类型和关系类型
-analysis_summary: str # 分析摘要
-
-# 图谱信息
-graph_id: str # Zep图谱ID
-graph_build_task_id: str # 构建任务ID
-
-# 配置
-simulation_requirement: str # 模拟需求
-chunk_size: int # 文本块大小
-chunk_overlap: int # 块重叠大小
-
-# 错误信息
-error: str # 错误描述
-```
-
-**状态枚举**:
-```python
-CREATED = "created" # 已创建
-ONTOLOGY_GENERATED = "ontology_generated" # 本体已生成
-GRAPH_BUILDING = "graph_building" # 图谱构建中
-GRAPH_COMPLETED = "graph_completed" # 图谱已完成
-FAILED = "failed" # 失败
-```
-
----
-
-### 2. Task (任务模型)
-
-**文件**: `app/models/task.py`
-
-**字段**:
-```python
-task_id: str # 任务ID (UUID)
-task_type: str # 任务类型
-status: TaskStatus # 状态
-created_at: datetime # 创建时间
-updated_at: datetime # 更新时间
-progress: int # 进度 (0-100)
-message: str # 状态消息
-result: Dict # 任务结果
-error: str # 错误信息
-metadata: Dict # 元数据
-progress_detail: Dict # 详细进度
-```
-
-**状态枚举**:
-```python
-PENDING = "pending" # 等待中
-PROCESSING = "processing" # 处理中
-COMPLETED = "completed" # 已完成
-FAILED = "failed" # 失败
-```
-
----
-
-### 3. SimulationState (模拟状态)
-
-**文件**: `app/services/simulation_manager.py`
-
-**字段**:
-```python
-simulation_id: str # 模拟ID (sim_xxx)
-project_id: str # 项目ID
-graph_id: str # 图谱ID
-enable_twitter: bool # 启用Twitter
-enable_reddit: bool # 启用Reddit
-status: SimulationStatus # 状态
-entities_count: int # 实体数量
-profiles_count: int # 人设数量
-entity_types: List[str] # 实体类型列表
-config_generated: bool # 配置已生成
-config_reasoning: str # 配置推理说明
-current_round: int # 当前轮次
-twitter_status: str # Twitter状态
-reddit_status: str # Reddit状态
-created_at: str # 创建时间
-updated_at: str # 更新时间
-error: str # 错误信息
-```
-
----
-
-### 4. EntityNode (实体节点)
-
-**文件**: `app/services/zep_entity_reader.py`
-
-**字段**:
-```python
-uuid: str # 实体UUID
-name: str # 实体名称
-labels: List[str] # 标签列表
-summary: str # 摘要
-attributes: Dict # 属性字典
-related_edges: List[Dict] # 相关边信息
-related_nodes: List[Dict] # 关联节点信息
-```
-
----
-
-### 5. OasisAgentProfile (Agent人设)
-
-**文件**: `app/services/oasis_profile_generator.py`
-
-**字段**:
-```python
-user_id: int # 用户ID
-user_name: str # 用户名
-name: str # 真实姓名
-bio: str # 简介 (200字)
-persona: str # 详细人设 (2000字)
-karma: int # Reddit积分
-friend_count: int # Twitter好友数
-follower_count: int # 粉丝数
-statuses_count: int # 发帖数
-age: int # 年龄
-gender: str # 性别 (male/female/other)
-mbti: str # MBTI类型
-country: str # 国家
-profession: str # 职业
-interested_topics: List[str] # 兴趣话题
-source_entity_uuid: str # 来源实体UUID
-source_entity_type: str # 来源实体类型
-created_at: str # 创建时间
-```
-
----
-
-### 6. Report (报告模型)
-
-**文件**: `app/services/report_agent.py`
-
-**字段**:
-```python
-report_id: str # 报告ID (report_xxx)
-simulation_id: str # 模拟ID
-graph_id: str # 图谱ID
-simulation_requirement: str # 模拟需求
-status: ReportStatus # 状态
-outline: ReportOutline # 报告大纲
-markdown_content: str # Markdown内容
-created_at: str # 创建时间
-completed_at: str # 完成时间
-error: str # 错误信息
-```
-
-**状态枚举**:
-```python
-PENDING = "pending" # 等待中
-PLANNING = "planning" # 规划大纲中
-GENERATING = "generating" # 生成内容中
-COMPLETED = "completed" # 已完成
-FAILED = "failed" # 失败
-```
-
-**ReportOutline字段**:
-```python
-title: str # 报告标题
-summary: str # 报告摘要
-sections: List[ReportSection] # 章节列表
-```
-
-**ReportSection字段**:
-```python
-title: str # 章节标题
-content: str # 章节内容
-subsections: List[ReportSection] # 子章节
-```
-
----
-
-### 7. SimulationParameters (模拟参数)
-
-**文件**: `app/services/simulation_config_generator.py`
-
-**字段**:
-```python
-simulation_id: str # 模拟ID
-project_id: str # 项目ID
-graph_id: str # 图谱ID
-simulation_requirement: str # 模拟需求
-
-# 时间配置
-time_config: TimeSimulationConfig
- ├── total_simulation_hours: int # 总时长(小时)
- ├── minutes_per_round: int # 每轮分钟数
- ├── agents_per_hour_min: int # 每小时最少激活Agent数
- ├── agents_per_hour_max: int # 每小时最多激活Agent数
- ├── peak_hours: List[int] # 高峰时段 [19,20,21,22]
- ├── off_peak_hours: List[int] # 低谷时段 [0,1,2,3,4,5]
- ├── morning_hours: List[int] # 早间时段 [6,7,8]
- ├── work_hours: List[int] # 工作时段 [9-18]
- ├── peak_activity_multiplier: float # 高峰活跃度系数 1.5
- ├── off_peak_activity_multiplier: float # 低谷活跃度系数 0.05
- ├── morning_activity_multiplier: float # 早间活跃度系数 0.4
- └── work_activity_multiplier: float # 工作时段活跃度系数 0.7
-
-# Agent配置列表
-agent_configs: List[AgentActivityConfig]
- ├── agent_id: int # Agent ID
- ├── entity_uuid: str # 实体UUID
- ├── entity_name: str # 实体名称
- ├── entity_type: str # 实体类型
- ├── activity_level: float # 活跃度 (0.0-1.0)
- ├── posts_per_hour: float # 每小时发帖数
- ├── comments_per_hour: float # 每小时评论数
- ├── active_hours: List[int] # 活跃时间段
- ├── response_delay_min: int # 最小响应延迟(分钟)
- ├── response_delay_max: int # 最大响应延迟(分钟)
- ├── sentiment_bias: float # 情感倾向 (-1.0到1.0)
- ├── stance: str # 立场 (supportive/opposing/neutral/observer)
- └── influence_weight: float # 影响力权重
-
-# 事件配置
-event_config: EventConfig
- ├── initial_posts: List[Dict] # 初始帖子
- ├── scheduled_events: List[Dict] # 定时事件
- ├── hot_topics: List[str] # 热点话题
- └── narrative_direction: str # 舆论方向
-
-# 平台配置
-twitter_config: PlatformConfig
-reddit_config: PlatformConfig
- ├── platform: str # 平台名称
- ├── recency_weight: float # 时间新鲜度权重
- ├── popularity_weight: float # 热度权重
- ├── relevance_weight: float # 相关性权重
- ├── viral_threshold: int # 病毒传播阈值
- └── echo_chamber_strength: float # 回声室效应强度
-
-# LLM配置
-llm_model: str # LLM模型名称
-llm_base_url: str # LLM API地址
-generated_at: str # 生成时间
-generation_reasoning: str # LLM推理说明
-```
-
----
-
-## 服务层详解
-
-### 1. OntologyGenerator (本体生成器)
-
-**文件**: `app/services/ontology_generator.py`
-
-**功能**: 使用LLM分析文档内容,生成适合舆论模拟的实体类型和关系类型
-
-**核心方法**:
-```python
-def generate(
- document_texts: List[str],
- simulation_requirement: str,
- additional_context: Optional[str] = None
-) -> Dict[str, Any]:
- """
- 生成本体定义
-
- Returns:
- {
- "entity_types": [...], # 10个实体类型(最后2个为Person和Organization)
- "edge_types": [...], # 6-10个关系类型
- "analysis_summary": "..." # 分析摘要
- }
- """
-```
-
-**设计原则**:
-- 必须返回**10个实体类型**,最后2个为兜底类型
-- 实体必须是现实中可以发声的主体(人/组织)
-- 属性名不能使用Zep保留字
-- 关系类型要反映社交媒体互动
-
-**LLM提示词要点**:
-- 系统角色: 知识图谱本体设计专家
-- 任务背景: 社交媒体舆论模拟
-- 输出格式: 严格的JSON结构
-- 实体类型层次: 具体类型(8个) + 兜底类型(2个)
-
----
-
-### 2. GraphBuilderService (图谱构建服务)
-
-**文件**: `app/services/graph_builder.py`
-
-**功能**: 调用Zep API构建知识图谱
-
-**核心方法**:
-```python
-def create_graph(name: str) -> str:
- """创建Zep图谱"""
-
-def set_ontology(graph_id: str, ontology: Dict):
- """设置图谱本体(动态创建Pydantic类)"""
-
-def add_text_batches(
- graph_id: str,
- chunks: List[str],
- batch_size: int = 3,
- progress_callback: Optional[Callable] = None
-) -> List[str]:
- """分批添加文本,返回episode UUIDs"""
-
-def _wait_for_episodes(
- episode_uuids: List[str],
- progress_callback: Optional[Callable] = None,
- timeout: int = 600
-):
- """等待所有episode处理完成"""
-
-def get_graph_data(graph_id: str) -> Dict:
- """获取完整图谱数据(节点和边)"""
-```
-
-**关键技术点**:
-1. **动态类创建**: 根据本体定义动态创建Pydantic类
-2. **批量上传**: 避免一次性提交大量数据
-3. **异步等待**: 轮询episode的`processed`状态
-4. **容错重试**: 所有API调用带重试机制
-
----
-
-### 3. ZepEntityReader (实体读取器)
-
-**文件**: `app/services/zep_entity_reader.py`
-
-**功能**: 从Zep图谱读取并过滤实体
-
-**核心方法**:
-```python
-def get_all_nodes(graph_id: str) -> List[Dict]:
- """获取所有节点(带重试)"""
-
-def get_all_edges(graph_id: str) -> List[Dict]:
- """获取所有边(带重试)"""
-
-def filter_defined_entities(
- graph_id: str,
- defined_entity_types: Optional[List[str]] = None,
- enrich_with_edges: bool = True
-) -> FilteredEntities:
- """
- 筛选符合预定义类型的实体
-
- 筛选逻辑:
- - 只保留Labels中包含除"Entity"和"Node"外的自定义标签的节点
- - 如果指定了entity_types,只保留匹配的类型
- - 可选:获取每个实体的相关边和关联节点
- """
-
-def get_entity_with_context(
- graph_id: str,
- entity_uuid: str
-) -> Optional[EntityNode]:
- """获取单个实体及其完整上下文"""
-```
-
-**容错机制**:
-- 所有Zep API调用带**3次重试**
-- 使用指数退避策略
-- 详细的日志记录
-
----
-
-### 4. OasisProfileGenerator (人设生成器)
-
-**文件**: `app/services/oasis_profile_generator.py`
-
-**功能**: 将图谱实体转换为OASIS Agent Profile
-
-**核心方法**:
-```python
-def generate_profile_from_entity(
- entity: EntityNode,
- user_id: int,
- use_llm: bool = True
-) -> OasisAgentProfile:
- """
- 从实体生成Agent人设
-
- 步骤:
- 1. 构建实体上下文(属性+边+关联节点+Zep检索)
- 2. 使用LLM生成详细人设(2000字persona)
- 3. 返回OasisAgentProfile对象
- """
-
-def generate_profiles_from_entities(
- entities: List[EntityNode],
- use_llm: bool = True,
- progress_callback: Optional[callable] = None,
- graph_id: Optional[str] = None,
- parallel_count: int = 5
-) -> List[OasisAgentProfile]:
- """
- 批量生成人设(支持并行)
-
- 特性:
- - 并行生成(默认5个并发)
- - Zep混合检索增强上下文
- - 区分个人实体和机构实体
- - 容错处理(失败则使用规则生成)
- """
-```
-
-**LLM提示词设计**:
-- **个人实体**: 生成2000字详细人设(基本信息+背景+性格+社交行为+立场观点+个人记忆)
-- **机构实体**: 生成官方账号设定(机构信息+账号定位+发言风格+发布内容+立场态度+机构记忆)
-- **输出格式**: JSON (bio, persona, age, gender, mbti, country, profession, interested_topics)
-
-**容错措施**:
-1. LLM调用失败:最多重试3次
-2. JSON解析失败:尝试修复JSON
-3. 完全失败:使用规则生成基础人设
-
----
-
-### 5. SimulationConfigGenerator (配置生成器)
-
-**文件**: `app/services/simulation_config_generator.py`
-
-**功能**: 使用LLM智能生成模拟配置参数
-
-**核心方法**:
-```python
-def generate_config(
- simulation_id: str,
- project_id: str,
- graph_id: str,
- simulation_requirement: str,
- document_text: str,
- entities: List[EntityNode],
- enable_twitter: bool = True,
- enable_reddit: bool = True,
- progress_callback: Optional[Callable] = None,
-) -> SimulationParameters:
- """
- 智能生成完整模拟配置
-
- 分步生成策略(避免一次性生成过长):
- 1. 生成时间配置(符合中国人作息)
- 2. 生成事件配置(热点话题+初始帖子)
- 3. 分批生成Agent配置(每批15个)
- 4. 生成平台配置
- """
-```
-
-**时间配置特点**:
-- **高峰时段**: 19-22点(活跃度系数1.5)
-- **低谷时段**: 0-5点(活跃度系数0.05)
-- **早间时段**: 6-8点(活跃度系数0.4)
-- **工作时段**: 9-18点(活跃度系数0.7)
-
-**Agent配置规则**:
-- **官方机构**: 活跃度低(0.1-0.3),工作时间活动,响应慢,影响力高(2.5-3.0)
-- **媒体**: 活跃度中(0.4-0.6),全天活动,响应快,影响力高(2.0-2.5)
-- **个人/学生**: 活跃度高(0.6-0.9),晚间活动,响应快,影响力低(0.8-1.2)
-- **专家/教授**: 活跃度中(0.4-0.6),工作+晚间,影响力中高(1.5-2.0)
-
----
-
-### 6. SimulationManager (模拟管理器)
-
-**文件**: `app/services/simulation_manager.py`
-
-**功能**: 管理模拟的完整生命周期
-
-**核心方法**:
-```python
-def create_simulation(
- project_id: str,
- graph_id: str,
- enable_twitter: bool = True,
- enable_reddit: bool = True,
-) -> SimulationState:
- """创建新模拟"""
-
-def prepare_simulation(
- simulation_id: str,
- simulation_requirement: str,
- document_text: str,
- defined_entity_types: Optional[List[str]] = None,
- use_llm_for_profiles: bool = True,
- progress_callback: Optional[callable] = None,
- parallel_profile_count: int = 3
-) -> SimulationState:
- """
- 准备模拟环境(全程自动化)
-
- 步骤:
- 1. 读取并过滤图谱实体
- 2. 并行生成Agent人设(带Zep检索增强)
- 3. LLM智能生成模拟配置
- 4. 保存配置和人设文件
- """
-
-def get_simulation(simulation_id: str) -> Optional[SimulationState]:
- """获取模拟状态"""
-
-def list_simulations(project_id: Optional[str] = None) -> List[SimulationState]:
- """列出所有模拟"""
-```
-
-**数据存储**:
-```
-uploads/simulations/sim_xxx/
-├── state.json # 模拟状态
-├── simulation_config.json # 模拟配置(LLM生成)
-├── reddit_profiles.json # Reddit人设(JSON格式)
-├── twitter_profiles.csv # Twitter人设(CSV格式)
-├── run_state.json # 运行状态
-├── simulation.log # 主日志
-├── twitter/
-│ ├── actions.jsonl # Twitter动作日志
-│ └── twitter_simulation.db # Twitter数据库
-└── reddit/
- ├── actions.jsonl # Reddit动作日志
- └── reddit_simulation.db # Reddit数据库
-```
-
----
-
-### 7. SimulationRunner (模拟运行器)
-
-**文件**: `app/services/simulation_runner.py`
-
-**功能**: 在后台运行OASIS模拟并实时监控
-
-**核心方法**:
-```python
-@classmethod
-def start_simulation(
- cls,
- simulation_id: str,
- platform: str = "parallel"
-) -> SimulationRunState:
- """
- 启动模拟
-
- 步骤:
- 1. 启动模拟进程(subprocess)
- 2. 创建监控线程
- 3. 解析动作日志
- 4. 实时更新状态
- """
-
-@classmethod
-def stop_simulation(cls, simulation_id: str) -> SimulationRunState:
- """
- 停止模拟
-
- 使用进程组终止(确保子进程也被终止)
- """
-
-@classmethod
-def get_run_state(cls, simulation_id: str) -> Optional[SimulationRunState]:
- """获取运行状态"""
-
-@classmethod
-def get_actions(
- cls,
- simulation_id: str,
- limit: int = 100,
- offset: int = 0,
- platform: Optional[str] = None,
- agent_id: Optional[int] = None,
- round_num: Optional[int] = None
-) -> List[AgentAction]:
- """获取动作历史(支持过滤)"""
-
-@classmethod
-def cleanup_all_simulations(cls):
- """清理所有运行中的模拟进程(服务器关闭时调用)"""
-```
-
-**进程管理**:
-- 使用`subprocess.Popen`启动模拟脚本
-- 使用`start_new_session=True`创建新进程组
-- 使用`os.killpg`终止整个进程组
-- 支持优雅关闭(SIGTERM)和强制终止(SIGKILL)
-
-**日志解析**:
-- 实时读取`twitter/actions.jsonl`和`reddit/actions.jsonl`
-- 解析每个Agent的动作记录
-- 更新运行状态和进度
-- 保存最近50个动作用于前端展示
-
----
-
-### 8. ZepGraphMemoryUpdater (图谱记忆更新器)
-
-**文件**: `app/services/zep_graph_memory_updater.py`
-
-**功能**: 将模拟中的Agent活动动态更新到Zep图谱
-
-**核心类**:
-
-```python
-class AgentActivity:
- """Agent活动记录"""
- platform: str # twitter / reddit
- agent_id: int
- agent_name: str
- action_type: str # CREATE_POST, LIKE_POST, etc.
- action_args: Dict
- round_num: int
- timestamp: str
-
- def to_episode_text(self) -> str:
- """
- 将活动转换为自然语言描述(不添加模拟前缀)
-
- 示例输出:
- - "张三: 发布了一条帖子:「官方声明:...」"
- - "李四: 在帖子#5下评论道:「我认为...」"
- - "王五: 引用帖子#3并评论:「同意!」"
- """
-```
-
-```python
-class ZepGraphMemoryUpdater:
- """
- 图谱记忆更新器
-
- 特性:
- - 逐条发送活动到Zep,确保图谱正确解析
- - 后台线程异步处理,不阻塞主模拟流程
- - 带重试的API调用(MAX_RETRIES=3)
- - 自动跳过DO_NOTHING类型的活动
- - 发送间隔控制(SEND_INTERVAL=0.5秒)
- """
-
- def start(self):
- """启动后台工作线程"""
-
- def stop(self):
- """停止并发送剩余活动"""
-
- def add_activity(self, activity: AgentActivity):
- """添加活动到队列"""
-
- def add_activity_from_dict(self, data: Dict, platform: str):
- """从动作日志字典添加活动"""
-
- def get_stats(self) -> Dict:
- """获取统计信息(total_activities, total_sent, failed_count等)"""
-```
-
-```python
-class ZepGraphMemoryManager:
- """
- 管理多个模拟的更新器实例
- """
-
- @classmethod
- def create_updater(cls, simulation_id: str, graph_id: str) -> ZepGraphMemoryUpdater:
- """为模拟创建并启动更新器"""
-
- @classmethod
- def get_updater(cls, simulation_id: str) -> Optional[ZepGraphMemoryUpdater]:
- """获取模拟的更新器"""
-
- @classmethod
- def stop_updater(cls, simulation_id: str):
- """停止并移除模拟的更新器"""
-
- @classmethod
- def stop_all(cls):
- """停止所有更新器(服务器关闭时调用)"""
-```
-
-**活动类型转换**:
-
-| action_type | 转换后的描述 |
-|-------------|-------------|
-| CREATE_POST | 发布了一条帖子:「{content}」 |
-| LIKE_POST | 点赞了帖子#{post_id} |
-| DISLIKE_POST | 踩了帖子#{post_id} |
-| REPOST | 转发了帖子#{post_id} |
-| QUOTE_POST | 引用帖子#{quoted_id}并评论:「{content}」 |
-| FOLLOW | 关注了用户#{user_id} |
-| CREATE_COMMENT | 在帖子#{post_id}下评论道:「{content}」 |
-| LIKE_COMMENT | 点赞了评论#{comment_id} |
-| SEARCH_POSTS | 搜索了「{query}」 |
-| MUTE | 屏蔽了用户#{user_id} |
-
-**使用示例**:
-
-```python
-# 在启动模拟时启用图谱记忆更新
-POST /api/simulation/start
-{
- "simulation_id": "sim_xxx",
- "enable_graph_memory_update": true
-}
-```
-
-启用后,模拟中的活动会被逐条转换为自然语言描述并发送到Zep:
-
-```
-上级: 发布了一条帖子:「官方声明:经复核并结合司法判决,校方决定撤销对肖某某的处分。学校向当事人致以正式歉意...」
-全国顶尖新闻传播学院的大学: 发布了一条帖子:「武汉大学官方发布:学校已决定撤销此前对当事人的处分...」
-全国考生: 引用帖子#5并评论
-教师代表: 在帖子#2下评论道:「此事暴露出高校在程序正义上的问题...」
-```
-
-每条活动单独发送,确保Zep能正确从文本中提取实体(如人名、机构名)和关系,丰富图谱知识。
-
----
-
-### 9. SimulationIPCClient/Server (IPC通信模块)
-
-**文件**: `app/services/simulation_ipc.py`
-
-**功能**: 实现Flask后端与模拟脚本之间的进程间通信
-
-**核心类**:
-
-```python
-class SimulationIPCClient:
- """IPC客户端(Flask端使用)"""
-
- def send_interview(agent_id: int, prompt: str, timeout: float) -> IPCResponse:
- """发送单个Agent采访命令"""
-
- def send_batch_interview(interviews: List[Dict], timeout: float) -> IPCResponse:
- """发送批量采访命令"""
-
- def send_close_env(timeout: float) -> IPCResponse:
- """发送关闭环境命令"""
-
- def check_env_alive() -> bool:
- """检查模拟环境是否存活"""
-```
-
-```python
-class SimulationIPCServer:
- """IPC服务器(模拟脚本端使用)"""
-
- def poll_commands() -> Optional[IPCCommand]:
- """轮询获取待处理命令"""
-
- def send_response(response: IPCResponse):
- """发送响应"""
-```
-
-**命令类型**:
-
-| 命令类型 | 说明 |
-|----------|------|
-| interview | 单个Agent采访 |
-| batch_interview | 批量采访 |
-| close_env | 关闭环境 |
-
-**文件结构**:
-
-```
-uploads/simulations/sim_xxx/
-├── ipc_commands/ # 命令文件目录
-│ └── {command_id}.json # 待处理命令
-├── ipc_responses/ # 响应文件目录
-│ └── {command_id}.json # 命令响应
-└── env_status.json # 环境状态文件
-```
-
-**使用示例**:
-
-```python
-# Flask端发送Interview命令
-from app.services import SimulationRunner
-
-# 单个采访
-result = SimulationRunner.interview_agent(
- simulation_id="sim_xxx",
- agent_id=0,
- prompt="你对这件事有什么看法?"
-)
-
-# 批量采访
-result = SimulationRunner.interview_agents_batch(
- simulation_id="sim_xxx",
- interviews=[
- {"agent_id": 0, "prompt": "问题A"},
- {"agent_id": 1, "prompt": "问题B"}
- ]
-)
-
-# 全局采访
-result = SimulationRunner.interview_all_agents(
- simulation_id="sim_xxx",
- prompt="你认为事件会如何发展?"
-)
-```
-
----
-
-### 10. ZepToolsService (Zep检索工具服务)
-
-**文件**: `app/services/zep_tools.py`
-
-**功能**: 封装多种Zep图谱检索工具,供Report Agent调用
-
-**核心方法**:
-
-```python
-def search_graph(
- graph_id: str,
- query: str,
- limit: int = 10
-) -> SearchResult:
- """
- 图谱语义搜索
-
- 使用混合搜索(语义+BM25)查找相关信息
- 返回: facts列表、edges列表、nodes列表
- """
-
-def get_all_nodes(graph_id: str) -> List[NodeInfo]:
- """获取图谱所有节点"""
-
-def get_all_edges(graph_id: str) -> List[EdgeInfo]:
- """获取图谱所有边"""
-
-def get_node_detail(node_uuid: str) -> Optional[NodeInfo]:
- """获取单个节点详情"""
-
-def get_node_edges(node_uuid: str) -> List[EdgeInfo]:
- """获取节点相关的边"""
-
-def get_entities_by_type(
- graph_id: str,
- entity_type: str
-) -> List[NodeInfo]:
- """按类型获取实体"""
-
-def get_entity_summary(
- graph_id: str,
- entity_name: str
-) -> Dict[str, Any]:
- """获取实体关系摘要"""
-
-def get_graph_statistics(graph_id: str) -> Dict[str, Any]:
- """
- 获取图谱统计信息
-
- 返回:
- - total_nodes: 节点总数
- - total_edges: 边总数
- - entity_types: 实体类型分布
- - relation_types: 关系类型分布
- """
-
-def get_simulation_context(
- graph_id: str,
- simulation_requirement: str,
- limit: int = 30
-) -> Dict[str, Any]:
- """
- 获取模拟相关上下文
-
- 综合搜索与模拟需求相关的所有信息
- """
-```
-
-**容错机制**:
-- 所有API调用带3次重试
-- 指数退避策略
-- 搜索失败返回空结果而非抛出异常
-
----
-
-### 11. ReportAgent (报告生成Agent)
-
-**文件**: `app/services/report_agent.py`
-
-**功能**: 使用ReACT模式生成模拟分析报告
-
-**核心类**:
-
-```python
-class ReportAgent:
- """
- Report Agent - 模拟报告生成Agent
-
- 采用ReACT(Reasoning + Acting)模式:
- 1. 规划阶段:分析模拟需求,规划报告目录结构
- 2. 生成阶段:逐章节生成内容,每章节可多次调用工具获取信息
- 3. 对话阶段:支持与用户对话,自主调用检索工具
- """
-
- # 配置
- MAX_TOOL_CALLS_PER_SECTION = 5 # 每章节最大工具调用次数
- MAX_REFLECTION_ROUNDS = 2 # 最大反思轮数
-```
-
-**核心方法**:
-
-```python
-def plan_outline(
- progress_callback: Optional[Callable] = None
-) -> ReportOutline:
- """
- 规划报告大纲
-
- 步骤:
- 1. 获取模拟上下文(图谱统计、相关事实)
- 2. 使用LLM分析并生成大纲结构
- 3. 返回包含章节列表的大纲对象
- """
-
-def _generate_section_react(
- section: ReportSection,
- outline: ReportOutline,
- previous_sections: List[str],
- progress_callback: Optional[Callable] = None
-) -> str:
- """
- 使用ReACT模式生成单个章节
-
- ReACT循环:
- 1. Thought(思考)- 分析需要什么信息
- 2. Action(行动)- 调用工具获取信息
- 3. Observation(观察)- 分析工具返回结果
- 4. 重复直到信息足够或达到最大次数
- 5. Final Answer(最终回答)- 生成章节内容
- """
-
-def generate_report(
- progress_callback: Optional[Callable] = None
-) -> Report:
- """
- 生成完整报告
-
- 步骤:
- 1. 规划大纲
- 2. 逐章节生成(ReACT模式)
- 3. 组装Markdown报告
- 4. 保存报告文件
- """
-
-def chat(
- message: str,
- chat_history: List[Dict[str, str]] = None
-) -> Dict[str, Any]:
- """
- 与Report Agent对话
-
- 在对话中Agent可以自主调用检索工具来回答问题
-
- Returns:
- {
- "response": "Agent回复",
- "tool_calls": [调用的工具列表],
- "sources": [信息来源]
- }
- """
-```
-
-**工具调用格式**:
-
-Agent使用以下格式调用工具:
-
-```
-
*>EibM)rq0WA&UR^p<;R6KZaVC-Q~rVQ%+{yBe*JL98* zI&KRI*@tIa*&F{
!SV%}NYOL1@rZOnNSdVYc7f9{KtazfF)>7$-U5uR^pQB(} z5~$$mysavJV%++i&ACg?A^?y8y5E%`aT3K`K^R=t7IWfP?s?QZVkdgo(T|Sc`&K)& zWoZ9xL-Ds8g+kvPkpizi6&)ic^Fs6ep&L&;h>##)y-|sNuW{K&T&zusgmA}56BITr z#xq6m1tT(O=1rQ>4}DJ}AX6apq*&S;@R^BcH0QGa^Mj1X=M^^cPB2graz+1R1-cxT za;9yZ(FM%G+;1vd91c3+6tn{)5`h5{3_Lyvprp&T k8b})d+O0#n88lT2~=33ZYdJ;Ax?$7O%Ntt&YDm z* GH+NYcKkE}9tJ?Vy>j{8Foyk%x ziM=%VTp8OYk1n50%CgSyop8v056=8DJMchJep^t8jRkK;$ET6asr>aYoEiYSAQIze zALO#lpM7hVZ^_uzc{j4;s%V4PI9gtFP6hAfzqgfw7nDuErZ8Ri?l|hSq*&G`v*}^H zIB`>>MQ*BcIdf#HBg`nS!KLAl$L|Z3p??D_8ShhU%M0p4M*$l7_*H<{XGnSd>y2wJ zFUF;VAG|8sD0*GiSmQ7nu<&DOXBkF%b8Rg=I5_xut($6A{m9{sQt7l*S5>u^0KGAf zsb%Nn$9VK?>E_1Jw!5vhsa{Z1V7>Xc`@{)blbvd7PaPybe#~o}sAT;jEc~t6;(w{A z3D$Y9U8bFtQFABnbVJjl>pA8j6Lt5Ne?V^1M+ctF^k_(GOkYl~5o}oZ|3W{gg#i!n zK>FgRCfA|YcK2tM|IYOGh8{TMKwRC$I=U{S(-Ox_yU!2kdcdwS^_sd2elkn{B4C*k z=~LF$5hXq=``>}~yFNYTF9BK?|5#yk^rJX- V<)=pa1`bd!+&quUJif{5hmoBHz*zyNz* z@axy#Gl)D^BF8jI_YN}XVO{C_{X1Z(%nAYaaUG!4gtbJxrv*O&e>zMgJ1qC>UKaot zc1mlrqpQ&HND`n;FV0OKk$YKQ+4xHneJTQxf@&!hJP9bLE;^+8xBGj7^E^uf^z1XL z%!eL!xgNT3hX6PZBxG)f8CJrys@t9P@FL@goXcpK1nUZ9RR{pv|3QUbmw&@~APD M5CD`}o zZJcA}M^4%&t2Tf3qKLWt3l!pi4InamjsJZpkrj*k_OxK*!cLnb-hlTe`@t#D3QUO& z-s{Z_HE>}Ebne65Q>j&D=`-6@rCj)EEXY0{I>2kbjmPH~-rdL~A@-U%8$6PQ(;$xL z<|}9LG1+w=nVn5xPmhU!Abr7A#=iB~SAeURKLnHIib~go_(?&kWX?39H^~!a*f7UG zicAsM6=FfF2P*Wi#&+*OHCeRMq?IKzcALs+R)BQv_&yj*0@W_zNl%Cbf(&JYMunmG zhb}nL$y(?iel~dB13a0~tq;jE^~Pk*j0*JxXoL9+p=}MasP*A#IVx)XEM`25c|`Pr z1Z$+_+*u50*S+y9<}z`>+KO{6VfH8mA5L`GQAvUA>-afz(5fF`pwA-oq_~Ve|2>i; z?5PhJZh+Z*5!&068CR<8PPHxel 6Qsv=1RfETkCNIdb0f7Nb+riKF9trk7rTwbYvX!kX zc_)&r_0d0{kbyvfp##oMmR}AI_<_n(aR4Yd@QNK}?@0*{U*-Iv{AW^ofwb$kzqAja zSpeN5SC~S8u7a79Ay*tV<&~B$F*b3#t+~Lu8MIn>x^X$<3Ek(#s_rcT2QQLiYUw0= z(lO-5Ej3H!R|89pGIuUDW}a@q;U$K)HU4|x`;8*(?-XSOR#UO8*!BC{++VDv`6%8` zeFY1hs=nzVDwn?ERM^O@!lv#2)^i14_^8jxV`+Uv$2!^_!pO&=W` &`ZGmNzwpu8CB=u^b^Q$&eRoR4k{o2GbmwTpg~!|5;b(5 |=0vp@_#;fo^4`#(qg&yIfNy>~ z4g1A|E?yjo){4Ea$r#UZIj*Pt2{ZARhZ|X3L4F ;p!dUM9(10#ckw)5 zNBl#gzY%VmHwMtr^9A?sLbv=%b@%_=`{&IC??x#_QUxdTOkZl9WBqQc A9!)StEwu53MGuK1T5>3ztzL37R!Vb;(ri`+=x)kI}0iJjo7W z-cDXniD-1MG4_lFz7_)9ETrp}ooJsGZ}M{OU2nVhz%}E0jkD|@(TvdeuLL|3&`Jdp zJkbt-q0O{&JXHBu(M0xILdodp-%~ dgvPUVjw4;6VYyGXk>g%d&WSE!2 zJVehFAl^_fK$7ih`6&33uOTC|Inb6fVS0Q#vw6VjZPqb>x1YcHGQsxqXwNlOLle*c zsy_wx&&Otb&pkUEo9ex&M~Wj(a{t7C=W+AS8H5ZYqE?4&4~ u ziuBv^0cO8W8H(O^L?ffSm5}#v>& xRWr|{0gR)V95~jDa6PB0V$yOK)B8^ zye$!&!)iPbaHyH`&pAAo6)|Llz9c$akgsqtzD?o0$wqd}T-JDqzAnpu9U)2?*uG~c z(ZO^l{17k=0Oly$#FcN(9E{(1^hoG^9a(CM;IooC|F|nV33KZT27MgeK8a!3n1C05 zTPzKbBE0D#jW#=2D@+ ^&$RQnp@cKu$s z1!HB|&DDatei4Y7B4^fsY)1 xVdzER^)@2fYJL6+ z22lp81aAuLV2i`H$M;8~aiHnG#Ml_n?jmsbg?x5_s2<5Hn18U4ZTqq25z)Y1K>61- zO{vZE!KhMkw6A{md!9H!E}07sWrwaBfYo#Vd+pni;3j-yVrl3G>@UTxlVab8L+dSk zl=)F%IPNSOk3TRqLm>W+ZkJ+NgTCQF-8}S>10?UHfhx7K5Nyy##DiDA7}Yf7Y57Tw zi32;ZjFbFxmeIfjXsR8+zJ8^6a3^P{8KHB0H7oU-@W*o%!#=&uZhRGKNT4@7po}Rz zuh|w9SowEGZ?A1Ix8kM4Ox#?|U}S!lLjQKd{}dV9F&)Y%{}I7hV 33|{!VpL_n&T=7WksxVWckZND@Mku`q$K|4c~K4`Wzo1Qq-I z!PrCeh;>=qmmfxq-kpIxpTFGs-k(=>u0~~kuq>?VepF$upOe{2nU7m>O*kltNg3CV ze)XY(aA!A=Lv!%8>iue9&-!26>*Kq?eH 3?d8g8@j9n9${o&ovU z ;bRtbI&SfcN_hWZR$0W_={mdp7Z0#;yg>ruY^ZzRJ*#58rgyyC0(yXbq5hLg zmz^^0lRhGceP-uji5^?9*hNDEl-svLy7B9vTExNCu=?_+U@4ZM%np7eum9jP10V%5 z)p@KIY(R*??ZN}=94&EnjxyQ{g9fSL^V3BbPnH?XvX(<-aW8B+&idcaW%K$_9eK6j zHp)T(0v4s%Hhil4aN55pPP{=fs3qQ^hSX5=2f!qf4f-(+r}~K}y*T=dj3%?bko{nE z=8F=?^`lZiid=FgjL0$aiD~)bw3lC$AM2@{nna?6 z1}}^ANEO1Uaq@2?(PNVm{7jf_3kMJ#z^ dJ(9N?6$Dg%;0sK?D{yLpH?uT&2| z3w}r!pNNaTN2HYs`8PJFMk}+)P^c1-J)q2SF08yF1#)22W(l%893%nXHUNF0zi7u! z?dO;~41b9s(6p?ux^Y%>kZEW>I8~~gv;tLql|y&qT+L17>T|gn+Yx6+DDsp7T?}4L zO6!z_D(dAXgO %15~x3j&lFjiI?q(l1HVyxI8 7_ zoJfCzP`wufj*K$OwM6(ZarYVmCbG#h7?!>qdYGU8M6~5a^nfKQUH6VimoQf>1S|>c zLo{6fRT2>5d6;$v)x0)=DG|AU`j{d85 0a~&i>ph&P(Wl;_Rx@26y%X|Mha;+437P-r4e2umCWzGKT zApc3;S5>^M{U`|E+l%m{sAp4GE+S0OC%)4H*xrA#Sk@jaX_C;px$OlcrXh(nXCLCY z0_ib~#3Rgb#2IvVIQ-!S1sHC&J}*@WI)kmyjhj1peSpDF$%n;IU;__o@3BT@?+e>5 zNHre3d3JBF%V(ZNpPdp#uvjc8X$;N|-Vkm+)~w+OG`^!FoEaXec1Rn`riuI4zv>dA zaC{b_pn9+V2Vxau8}Lz&g_l7T33KM?vxisu-K%}Y3~YW*FFkr*ns ze29#o)q-yvuEq$0(>*oOkV7J|V)NX8CZ5s=YrS^!bmd{~&97`Z8SEsFe_+Lb?ve$+ zh_Zv{+RO*XpPT*+ +$|BO^Rzs!Z?rTG(|xSu#M&=G 59GDH6L1ZtOSX^&0R!+wuX!UulMnOr4E zcL_LLO2FstpXO40X1~6lsx93%_ 8v0D88Q`pAif0>6;7tB+CX5ssW#!>^z-_Eubjd~xxXuk62b41^i;-4Lkhmw3; zIm1halZN4n<(36e% z{Vbk(p0QworjDpbeAx8*rKV>3b4&NYr8Q+&O2M Q$!v%GU=*%v1TIIh z_U#qyd&Reybf-FDFi@J@>99UFB5?SL__J4&W-AWz|B`C|80?qwI)Bkqh_8posdN%nID}Gyp)!h5B7`8Ap)|}A=n4)sITVRqcG|c@ju(Bu<6oi0C$M>Cg zQzV=GV^4S6e@M`P9JXE?1BzlW`iglPFjFyNrh`8F( VUR`GJp-`QlTQ2vlOAXSPH!609iOFv?i7S|2zI4 z Tr%Oue$#!KDlT0n(SA z^C}9%1w;hlXa2dteum5x!ZtGC8VBRQv3U@Q (c!wzhyGCx9-rU@&uGqG8i=*$e`Rwqb+2a-Jt zcE}=ygJ~<7a=5NqCECG>D`k?`3SqHiAi)a`$boHK7(I)&UjxB6p4HV0S6IG{|RUzarpjiq6TrukP2?(W!4DlBl)pGC)Kfw^33bK znibm|T(*nSD+QRvQY^=8wEeji-P_>WOQ?#A*cRxNU(~f0$cLRTUM|9fBq()abZIQY zJ~zj}BKcYj9sr4D9lCt|0Af%mnsh^;OAZ)4#}!3S@oG#tLjJH&uU*L$#1?|%3eo2T zI4L9AO@@P@Bxlt$TUH7 7mj4)UUuHb`4^?pWX#I2_j6tPa|{w|BF*K5Iz#jJ)CPe=v@8 zVaCO{ZYE&I<4b6)u5RHS@#VmGE7%ts=!{_v2yE_+NOiKoc)gx>uj`mSe68bj)$Uf` z>lI$Dm0r6e*QX9uuKEzzgba!U@5YA&DcG$ce-t06M@C*hn~JI-b>Beq`9a^U3YRb) z2mRdY+!Dwqq{<-q9T;xHQ=qwoCL4#}ZA-D5TRJ=bP_ACtgX#5eG0|rC;Xvt|i<{Xv zke(Jo_{5E$t-kj+U2=V=9{zlr2Ad%~kQ!3Du33Mic=qbLW|ZCAxvr+up23mzPZI-6 z!^?YL|D2dQ#fk3k?@4J0!3FM%J{B;t;al@}Bjj^9IU{H9xrNjvb3b&r d_x(wQj%L{Br4{SB?AE zrf0u2uZ2yR4$TVpPm(ZGrq~jFG9!d F4&t!LbqU SPG;W`wasZP zs4@||s^Xn?E3Gf~cG!E-{6PF<%MSXqgg&F z ;$S0{ZcfAdY0 wKHO~wj_{GXNRRP~&oBx;-(uo)i@ILtU93@bxqQY8yqrC+0w$hn+42A# zOo^)`DyAmOR8+WIq#Qc>>g>z- hr3qFk I=};Ld`HoEZBLUj8gGi zigwE&hVk3ZiqQpuo-BsBlE2mDi-rV4G8Y$`7o7<`yVjBcsa~f23VxQP0KX=opFXln zAV(6T7=y@?DKKzRyKRXqs 7=u2m$7JnpZlRwA~t)qxUL*ar)d>;dGJo>>un@c=6 zNT+K6PcSLLI3R{bokt}Aa!g*oR~0<)K{am?8-_6$tQRD&g(~xf=G~3r0$M3FTWUj) ze+!q$2BjQDzLi4-3B-owtcY@pF3m*ff-(i3;=T~OPn}HSg|6!WvH0C9&U|e$EH9ur zJN@)YT(sMUuf(5VX(|}B0B?L*H=!XLFQ9mC=keoYnVyl<{aa~H@SWeS#WG_bts|q~ z#@tWIgB*tHNntxt0(dw>{P_H9?VSh_T9p@SxJ!&ZDxfN)?FZb+3<5h0A?okDjYE|- z+lr%qe}T)^A0w~8yF^(^PP5Hf8wX@NGfRTi=(zvMe>ZbxQ{)omVVg^oCzs6B@p%kD zFN0aNLSv0$94gEXvo;LDwtfK_Egs&T6=DuXUv2) KK?3XJSMJ-6$r@ySmTKn*=z z2T8>HJVx`HW_Wf0AUeI9dq%(me9#`k?2 J!$goUgQ@4FIbj~GU{fRz15uiq;A?M`=RB$k4{{}R zPSy2N3E)v-& V8f^t#;nUnk4Z2tI6@){8H(sp$!+C?L3xFejr@O z(Zq?EDe$N)0wc(4<>*fgsJdkLL?@~0kB-}tf`WqC-G+d`;f=_u!1c*5dL7@xb0Xla zkHJLMKxX5?Dn`moheE*4+B-&Ol{NDxTkHO1h(6Bn{&K$VY3Aw1xl)gFA-k&xfoj7O zTdXt%ij5ZL_AOMp2Hv_8{KDp z;P-xci54>{0de+yxbx<5hHb$lT{{AgxZ@GO>p2AEC6>I!2V$EEf86$qbBN~e=mRi` zz^7v@#LQAakq@|YEtbwdslWF!&tf|`w{m#uC9d-)?|(e#R$PT;`wZ77KkL3MCaEmK zaL>UrVyqI3#Ov!K@F{V85E-)YWpBrd+@df7rQQSPUx;)%BLiOnadJxOV`tP#xL9_b z_#`Hx5g@anj08hqxbM gGy#Q0p|dr*NIs0Nek(OUr5r9i&dytD|mryEezwu9)>Eu?I<#( zN;|icT`rRCfN_=o0v>n5G#o~%P6IJGhQVrr4l2!edr&(V*Txq#d5NLi;+8u>LNw;B znxx%L999#t$9y^oZKJXe9~roYSL8p4;n?C{d7@5w_Q1gh3 Y>kbVIQ-e66vXqBhXHd0r$xuG9w$qNbGYNegCgZ@!1a+^ =04$`kqyg{yR@Rbu;0e~ l_v=T_;3*TgGUemY72(vT%vtl_ID98AnP0ox1)Hp)w3g^xlpMhu-gvApW8O z7Hw?QX)@&QKDQqEggEZg5#u!q+KQ3rdvT9XM)8}k*-@a;EqC%Q&>U3;4#9vT)ob3k zSabYxkv2MSTaf}WT6Qp5BH1DMH%HO*0n0sPl)Ic6AK`|o4i|)v?CX^ryM;SP>asU0 zQlMSNBYAivH%_0_>5e()TiXI|M(qT4e}IhDAmzZ`-}PQ#@(<>}yk@RwMNtRb^IRMb zwXKg$a8g~yq)$;8KS%R%_w3ifyK8Iyk09+#+QosFvy2l%Ta7L8v}^aDjtn|0KmO7b z>cG7Qo$&s;Kkesx@qg>hCm)|BUvME6sdwT%90Im;DpzF7)r$0~$SiNvbH>K2ClX2O zyPJ>6J_-A#g< n=~_b&aHxc87KLprb!qdnT9e-aOL-Lyv&+@GaxxLi7Z zip2a{Z)VagTz~xTAj7PCGgQm%TvOx4^x6E)_Y?h1|JthlJ_@WLgvH#{tSfLXzuU7b zJM&70c%Fz)mO|X1M@7P^*^TpO%+OkDZxemFr9R^e`9jm*S%CzNsACfG!*}NHYu!+2 zH*NI!RjU2RYp+)Q`={NkeRqOBJDi*T-f=rT*md7K82JdoNFPz_u)J;eqrmEa#)ivB zaNu%JZQbLdcC$m3Mlth~Tt*zjDjINY@A0%3mHS>7Z^`&?JB%9ewLKt! zKP4egT*9M4lQBGC#wvQ*A$nK%P+?U?sp;v{T9}y{q&K F46Bmk zdPz(jR1~oKrZlcY4ZP+qlgE3&a3aK*I?=@^Yivuypd)X_43QH+v8F9f6D)NDGGNQX zyFh^^=AjDqcWZ6oQTFdWl|M=xXDsU;uL kDiH(t2(jr?ie*O%K~(eD#}$ES@=11?9HN9nj|I@T7C#E^8-^X+J|cTp-%wt z8o@;aU<1 zLDSo9Cwfgj+RbpVdSyENAE{`92i WiOmP|h20oe? vm-F!E&*viP%L9Bq!U=uwZqEsG?F0Sr8IOKSf6n!HtI^+xv z?SCJAaVyKMD)ri|ZaJ>S1EeSSQDQ8?3$;h?PycS-`EZ}J`7>v4VE-Pm_}FFM$+GC{ zAD({Vqu&>u( evP%sahUoi*^_!79# zlX~eOwlpnYs SzOoS{Km{9tx8Cz901s34 z?Jc%t`V<>F@G^Z8ITM=?o!!ar5CN;fi_}}nr95!US *TLck6=&RI5Jv zxk|chB9d=wgJ$xrQtd!zb_l^^NeA@rJ1Qb0bi1}_-^Xz`M9dz*&<;Y3;t4g>(uxzc zON}uAmb<_~GTJ$cpR&CBab-J~!ptJdFKk$)cmU6c%$HQuf{4t)ol?ZBB7`8*^L66P zedjSe*xM{f_EubB;36S{%s6bq_Jk=!dLVF(z}kH$;lALz)I~N0v{8Vm=+hjvg1^o# zH%J!o^2>4iKZmI}6C c!MB6OdGX00?`2)2EKP%cYDQbz=}a{v}5`Jt`iyX<(e zOBUwem5D!uQ3U-h$BMtSzskClXDksX(*=H~f+;sSX((TqIV2$*JHPT9mcRy4k+0|P z=KJ0dRWCf?oBy_aI`CgBp6N4q5)YlZ0|u=8PxzqgmxLb-lDVO8(;&Iz2o+tw2!+w0 z?)$C)HqSV^OA2+y=#%*evbY0%78(uYbk-V(xUv71o2#=lVgeCtvLDEO2%UQHUn0h} z2NdZ2OXkX4I#Tbb!KrD_!AU77gO0YG7iinJF!8>hM}x3F?^8~EcsW^y#;*4R 368kpAHirJI6y&?i2g+>fzNnG!hTKlQq&`jdpgy?zt8#lrDz@r6Uz@aa&5y~%k69p~@ I*%yCG$*T3HPOLuK; zd8^U<5O!`qZ-6UmHdQm>V;Qpp{<<41{LKBgbqg&Tc-qlE^I1fB`a)BxY_im6E}3q8 z|HnS(jVolP3S7J;j=z6BRh2xohg?9=r>35eKyAqkLE7F@NYq2N%Lc?iw%uTYb3H+g z-;u)poH>?^S?nX_V7n%AEir8};E!@lRJQC>kus=vA6osdVaHV%dKdvETm`RLMt&IC z=zKiF*^h^Mm5Gz$m-}Ry>sXdKnO$PCr zREu}@U;}f4(d*%0TNhaKjnQWl60v>vI8!6x>X1(&kR;B1(`Gk!1F3Vqj$E2Feol~w zBD1Ae2-u)RGV-YbEDvro)ey4fr6Rgeb(yEbZE^N-EL(6+W&~s!ym_>$pCSdd;Ucqa zIi7l5!;)AylEO74F7|jyW>BHGkFit)4@K(5kL8Fnr!<)-$GNJdN-X0J_@l(nr~_O* zodX%bwoCAQwSj((HKRAj+?)#VG+PYQ662qPleu|E&jKC24N+vH&kzwcvcsXv;$~a% zNH7tVLn_28O!6=}Se7CcTu0tqX9o!Laoy+CP!?qRpFI8aC@4HtUvp>c$@y;VNe`W9 z-zEY%kTqw^;OZf{3Yh75aX&8fjZx4+*@`D Gal8D=Tys|bj z_3W80O`KKojsD8<3 !pj_@DLyjRs7ow$GHIuEPO0hTg_Gfp2h&hqCp!LQR3 z{J0K}4~5Yo`)hhm6Ke co-tFw zkD1suV;erbFYYm*N{3B?4Fxp6ZxEoI?;*!L{Q8&=P)@~+ldIQg(AdXUqde0>PbgK=J;h$?B0mhm{Mmy*g z=DWX }HqmiX zP xX3v)Zz=Aj|f`HoNrFV5Vr$s6&{ar9*HGDLq*_ AYkb>$6p+;Q5D48d+ zmfs4-LnZAXeJc5~qBi%xB|+BSe^Od7ba)ykQch!sKS$qU5%jzeYVU>R{?q{@z^?}! z`>RJ{+ze#&^VoKQX*qQ09}X^AA}V5{Z77V3Hpsz0U&YpAfQneO*D(na<8mPF{6&Pu z-ot|gI6>T^kv99wi G>_Q-x1L8Xn*?-I`p)JgRS$66Zfkq1 JG7Cn`{{dMGO;i8? literal 0 HcmV?d00001 diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file