docs(i18n): translate chinese docstrings/comments in backend/api
This commit is contained in:
parent
6439e58eb5
commit
b5a8996692
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
图谱相关API路由
|
||||
采用项目上下文机制,服务端持久化状态
|
||||
Graph-related API routes.
|
||||
|
||||
Uses a project context mechanism with server-side state persistence.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
|
@ -26,25 +27,22 @@ _graph_data_cache: dict = {} # graph_id -> {"data": ..., "ts": float}
|
|||
_graph_refresh_locks: dict = {} # graph_id -> threading.Lock (one refresh at a time)
|
||||
_GRAPH_CACHE_TTL = 300 # seconds before triggering a background refresh
|
||||
|
||||
# 获取日志器
|
||||
logger = get_logger('mirofish.api')
|
||||
|
||||
|
||||
def allowed_file(filename: str) -> bool:
|
||||
"""检查文件扩展名是否允许"""
|
||||
"""Return True if the file extension is in the allowed list."""
|
||||
if not filename or '.' not in filename:
|
||||
return False
|
||||
ext = os.path.splitext(filename)[1].lower().lstrip('.')
|
||||
return ext in Config.ALLOWED_EXTENSIONS
|
||||
|
||||
|
||||
# ============== 项目管理接口 ==============
|
||||
# ============== Project management endpoints ==============
|
||||
|
||||
@graph_bp.route('/project/<project_id>', methods=['GET'])
|
||||
def get_project(project_id: str):
|
||||
"""
|
||||
获取项目详情
|
||||
"""
|
||||
"""Get project details."""
|
||||
project = ProjectManager.get_project(project_id)
|
||||
|
||||
if not project:
|
||||
|
|
@ -61,9 +59,7 @@ def get_project(project_id: str):
|
|||
|
||||
@graph_bp.route('/project/list', methods=['GET'])
|
||||
def list_projects():
|
||||
"""
|
||||
列出所有项目
|
||||
"""
|
||||
"""List all projects."""
|
||||
limit = request.args.get('limit', 50, type=int)
|
||||
projects = ProjectManager.list_projects(limit=limit)
|
||||
|
||||
|
|
@ -76,9 +72,7 @@ def list_projects():
|
|||
|
||||
@graph_bp.route('/project/<project_id>', methods=['DELETE'])
|
||||
def delete_project(project_id: str):
|
||||
"""
|
||||
删除项目
|
||||
"""
|
||||
"""Delete a project."""
|
||||
success = ProjectManager.delete_project(project_id)
|
||||
|
||||
if not success:
|
||||
|
|
@ -95,9 +89,7 @@ def delete_project(project_id: str):
|
|||
|
||||
@graph_bp.route('/project/<project_id>/reset', methods=['POST'])
|
||||
def reset_project(project_id: str):
|
||||
"""
|
||||
重置项目状态(用于重新构建图谱)
|
||||
"""
|
||||
"""Reset project state (used to rebuild the graph from scratch)."""
|
||||
project = ProjectManager.get_project(project_id)
|
||||
|
||||
if not project:
|
||||
|
|
@ -106,7 +98,8 @@ def reset_project(project_id: str):
|
|||
"error": t("api.error.graph.m004", project_id=project_id)
|
||||
}), 404
|
||||
|
||||
# 重置到本体已生成状态
|
||||
# Roll back to the "ontology generated" state so the next build can resume
|
||||
# from the existing ontology rather than re-running ontology generation.
|
||||
if project.ontology:
|
||||
project.status = ProjectStatus.ONTOLOGY_GENERATED
|
||||
else:
|
||||
|
|
@ -124,22 +117,21 @@ def reset_project(project_id: str):
|
|||
})
|
||||
|
||||
|
||||
# ============== 接口1:上传文件并生成本体 ==============
|
||||
# ============== Endpoint 1: upload files and generate ontology ==============
|
||||
|
||||
@graph_bp.route('/ontology/generate', methods=['POST'])
|
||||
def generate_ontology():
|
||||
"""
|
||||
接口1:上传文件,分析生成本体定义
|
||||
"""Endpoint 1: upload files, analyze them, and generate an ontology definition.
|
||||
|
||||
请求方式:multipart/form-data
|
||||
Request format: multipart/form-data.
|
||||
|
||||
参数:
|
||||
files: 上传的文件(PDF/MD/TXT),可多个
|
||||
simulation_requirement: 模拟需求描述(必填)
|
||||
project_name: 项目名称(可选)
|
||||
additional_context: 额外说明(可选)
|
||||
Args:
|
||||
files: Uploaded files (PDF/MD/TXT); one or more.
|
||||
simulation_requirement: Description of the simulation requirement (required).
|
||||
project_name: Project name (optional).
|
||||
additional_context: Additional context (optional).
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
|
|
@ -157,7 +149,6 @@ def generate_ontology():
|
|||
try:
|
||||
logger.info(t("log.graph_api.m006"))
|
||||
|
||||
# 获取参数
|
||||
simulation_requirement = request.form.get('simulation_requirement', '')
|
||||
project_name = request.form.get('project_name', 'Unnamed Project')
|
||||
additional_context = request.form.get('additional_context', '')
|
||||
|
|
@ -171,7 +162,6 @@ def generate_ontology():
|
|||
"error": t("api.error.graph.m009")
|
||||
}), 400
|
||||
|
||||
# 获取上传的文件
|
||||
uploaded_files = request.files.getlist('files')
|
||||
if not uploaded_files or all(not f.filename for f in uploaded_files):
|
||||
return jsonify({
|
||||
|
|
@ -179,18 +169,17 @@ def generate_ontology():
|
|||
"error": t("api.error.graph.m010")
|
||||
}), 400
|
||||
|
||||
# 创建项目
|
||||
project = ProjectManager.create_project(name=project_name)
|
||||
project.simulation_requirement = simulation_requirement
|
||||
logger.info(t("log.graph_api.m011", project=project.project_id))
|
||||
|
||||
# 保存文件并提取文本
|
||||
# Persist each uploaded file under the project's directory and pull its
|
||||
# text out so the ontology generator has plain text to work with.
|
||||
document_texts = []
|
||||
all_text = ""
|
||||
|
||||
for file in uploaded_files:
|
||||
if file and file.filename and allowed_file(file.filename):
|
||||
# 保存文件到项目目录
|
||||
file_info = ProjectManager.save_file_to_project(
|
||||
project.project_id,
|
||||
file,
|
||||
|
|
@ -201,7 +190,6 @@ def generate_ontology():
|
|||
"size": file_info["size"]
|
||||
})
|
||||
|
||||
# 提取文本
|
||||
text = FileParser.extract_text(file_info["path"])
|
||||
text = TextProcessor.preprocess_text(text)
|
||||
document_texts.append(text)
|
||||
|
|
@ -214,12 +202,10 @@ def generate_ontology():
|
|||
"error": t("api.error.graph.m012")
|
||||
}), 400
|
||||
|
||||
# 保存提取的文本
|
||||
project.total_text_length = len(all_text)
|
||||
ProjectManager.save_extracted_text(project.project_id, all_text)
|
||||
logger.info(t("log.graph_api.m013", len=len(all_text)))
|
||||
|
||||
# 生成本体
|
||||
logger.info(t("log.graph_api.m014"))
|
||||
generator = OntologyGenerator()
|
||||
ontology = generator.generate(
|
||||
|
|
@ -228,7 +214,6 @@ def generate_ontology():
|
|||
additional_context=additional_context if additional_context else None
|
||||
)
|
||||
|
||||
# 保存本体到项目
|
||||
entity_count = len(ontology.get("entity_types", []))
|
||||
edge_count = len(ontology.get("edge_types", []))
|
||||
logger.info(t("log.graph_api.m015", entity_count=entity_count, edge_count=edge_count))
|
||||
|
|
@ -262,35 +247,33 @@ def generate_ontology():
|
|||
}), 500
|
||||
|
||||
|
||||
# ============== 接口2:构建图谱 ==============
|
||||
# ============== Endpoint 2: build graph ==============
|
||||
|
||||
@graph_bp.route('/build', methods=['POST'])
|
||||
def build_graph():
|
||||
"""
|
||||
接口2:根据project_id构建图谱
|
||||
"""Endpoint 2: build the graph for the given project_id.
|
||||
|
||||
请求(JSON):
|
||||
Request (JSON):
|
||||
{
|
||||
"project_id": "proj_xxxx", // 必填,来自接口1
|
||||
"graph_name": "图谱名称", // 可选
|
||||
"chunk_size": 500, // 可选,默认500
|
||||
"chunk_overlap": 50 // 可选,默认50
|
||||
"project_id": "proj_xxxx", // required, from endpoint 1
|
||||
"graph_name": "Graph name", // optional
|
||||
"chunk_size": 500, // optional, default 500
|
||||
"chunk_overlap": 50 // optional, default 50
|
||||
}
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"project_id": "proj_xxxx",
|
||||
"task_id": "task_xxxx",
|
||||
"message": "图谱构建任务已启动"
|
||||
"message": "Graph build task started"
|
||||
}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
logger.info(t("log.graph_api.m017"))
|
||||
|
||||
# 检查配置
|
||||
errors = []
|
||||
if not Config.NEO4J_PASSWORD:
|
||||
errors.append("NEO4J未配置")
|
||||
|
|
@ -301,7 +284,6 @@ def build_graph():
|
|||
"error": "配置错误: " + "; ".join(errors)
|
||||
}), 500
|
||||
|
||||
# 解析请求
|
||||
data = request.get_json() or {}
|
||||
project_id = data.get('project_id')
|
||||
logger.debug(t("log.graph_api.m019", project_id=project_id))
|
||||
|
|
@ -312,7 +294,6 @@ def build_graph():
|
|||
"error": t("api.error.graph.m020")
|
||||
}), 400
|
||||
|
||||
# 获取项目
|
||||
project = ProjectManager.get_project(project_id)
|
||||
if not project:
|
||||
return jsonify({
|
||||
|
|
@ -320,8 +301,8 @@ def build_graph():
|
|||
"error": t("api.error.graph.m021", project_id=project_id)
|
||||
}), 404
|
||||
|
||||
# 检查项目状态
|
||||
force = data.get('force', False) # 强制重新构建
|
||||
# If True, abandon any existing build progress and rebuild from scratch.
|
||||
force = data.get('force', False)
|
||||
|
||||
if project.status == ProjectStatus.CREATED:
|
||||
return jsonify({
|
||||
|
|
@ -336,23 +317,20 @@ def build_graph():
|
|||
"task_id": project.graph_build_task_id
|
||||
}), 400
|
||||
|
||||
# 如果强制重建,重置状态
|
||||
# On a forced rebuild, drop any prior build artifacts so we restart cleanly.
|
||||
if force and project.status in [ProjectStatus.GRAPH_BUILDING, ProjectStatus.FAILED, ProjectStatus.GRAPH_COMPLETED]:
|
||||
project.status = ProjectStatus.ONTOLOGY_GENERATED
|
||||
project.graph_id = None
|
||||
project.graph_build_task_id = None
|
||||
project.error = None
|
||||
|
||||
# 获取配置
|
||||
graph_name = data.get('graph_name', project.name or 'MiroFish Graph')
|
||||
chunk_size = data.get('chunk_size', project.chunk_size or Config.DEFAULT_CHUNK_SIZE)
|
||||
chunk_overlap = data.get('chunk_overlap', project.chunk_overlap or Config.DEFAULT_CHUNK_OVERLAP)
|
||||
|
||||
# 更新项目配置
|
||||
project.chunk_size = chunk_size
|
||||
project.chunk_overlap = chunk_overlap
|
||||
|
||||
# 获取提取的文本
|
||||
text = ProjectManager.get_extracted_text(project_id)
|
||||
if not text:
|
||||
return jsonify({
|
||||
|
|
@ -360,7 +338,6 @@ def build_graph():
|
|||
"error": t("api.error.graph.m024")
|
||||
}), 400
|
||||
|
||||
# 获取本体
|
||||
ontology = project.ontology
|
||||
if not ontology:
|
||||
return jsonify({
|
||||
|
|
@ -368,17 +345,14 @@ def build_graph():
|
|||
"error": t("api.error.graph.m025")
|
||||
}), 400
|
||||
|
||||
# 创建异步任务
|
||||
task_manager = TaskManager()
|
||||
task_id = task_manager.create_task(f"构建图谱: {graph_name}")
|
||||
logger.info(t("log.graph_api.m026", task_id=task_id, project_id=project_id))
|
||||
|
||||
# 更新项目状态
|
||||
project.status = ProjectStatus.GRAPH_BUILDING
|
||||
project.graph_build_task_id = task_id
|
||||
ProjectManager.save_project(project)
|
||||
|
||||
# 启动后台任务
|
||||
def build_task():
|
||||
build_logger = get_logger('mirofish.build')
|
||||
try:
|
||||
|
|
@ -389,10 +363,8 @@ def build_graph():
|
|||
message="初始化图谱构建服务..."
|
||||
)
|
||||
|
||||
# 创建图谱构建服务
|
||||
builder = GraphBuilderService()
|
||||
|
||||
# 分块
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message="文本分块中...",
|
||||
|
|
@ -405,7 +377,6 @@ def build_graph():
|
|||
)
|
||||
total_chunks = len(chunks)
|
||||
|
||||
# 创建图谱
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message="创建Zep图谱...",
|
||||
|
|
@ -413,11 +384,9 @@ def build_graph():
|
|||
)
|
||||
graph_id = builder.create_graph(name=graph_name)
|
||||
|
||||
# 更新项目的graph_id
|
||||
project.graph_id = graph_id
|
||||
ProjectManager.save_project(project)
|
||||
|
||||
# 设置本体
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message="设置本体定义...",
|
||||
|
|
@ -425,9 +394,9 @@ def build_graph():
|
|||
)
|
||||
builder.set_ontology(graph_id, ontology)
|
||||
|
||||
# 添加文本(progress_callback 签名是 (msg, progress_ratio))
|
||||
# Add text. The progress_callback signature is (msg, progress_ratio).
|
||||
def add_progress_callback(msg, progress_ratio):
|
||||
progress = 15 + int(progress_ratio * 40) # 15% - 55%
|
||||
progress = 15 + int(progress_ratio * 40) # maps ratio onto 15%-55%
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message=msg,
|
||||
|
|
@ -460,7 +429,7 @@ def build_graph():
|
|||
skip_chunks=skip_chunks,
|
||||
)
|
||||
|
||||
# 等待Zep处理完成(查询每个episode的processed状态)
|
||||
# Wait for Zep to finish processing (poll each episode's processed flag).
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message="等待Zep处理数据...",
|
||||
|
|
@ -468,7 +437,7 @@ def build_graph():
|
|||
)
|
||||
|
||||
def wait_progress_callback(msg, progress_ratio):
|
||||
progress = 55 + int(progress_ratio * 35) # 55% - 90%
|
||||
progress = 55 + int(progress_ratio * 35) # maps ratio onto 55%-90%
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message=msg,
|
||||
|
|
@ -477,7 +446,6 @@ def build_graph():
|
|||
|
||||
builder._wait_for_episodes(episode_uuids, wait_progress_callback)
|
||||
|
||||
# 获取图谱数据
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
message="获取图谱数据...",
|
||||
|
|
@ -485,7 +453,6 @@ def build_graph():
|
|||
)
|
||||
graph_data = builder.get_graph_data(graph_id)
|
||||
|
||||
# 更新项目状态
|
||||
project.status = ProjectStatus.GRAPH_COMPLETED
|
||||
ProjectManager.save_project(project)
|
||||
|
||||
|
|
@ -499,7 +466,6 @@ def build_graph():
|
|||
edge_count=edge_count,
|
||||
))
|
||||
|
||||
# 完成
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
status=TaskStatus.COMPLETED,
|
||||
|
|
@ -515,7 +481,7 @@ def build_graph():
|
|||
)
|
||||
|
||||
except Exception as e:
|
||||
# 更新项目状态为失败
|
||||
# Mark the project as FAILED so the UI can surface the error.
|
||||
build_logger.error(t("log.graph_api.m029", task_id=task_id, e=str(e)))
|
||||
build_logger.debug(traceback.format_exc())
|
||||
|
||||
|
|
@ -530,7 +496,6 @@ def build_graph():
|
|||
error=traceback.format_exc()
|
||||
)
|
||||
|
||||
# 启动后台线程
|
||||
thread = threading.Thread(target=build_task, daemon=True)
|
||||
thread.start()
|
||||
|
||||
|
|
@ -551,13 +516,11 @@ def build_graph():
|
|||
}), 500
|
||||
|
||||
|
||||
# ============== 任务查询接口 ==============
|
||||
# ============== Task query endpoints ==============
|
||||
|
||||
@graph_bp.route('/task/<task_id>', methods=['GET'])
|
||||
def get_task(task_id: str):
|
||||
"""
|
||||
查询任务状态
|
||||
"""
|
||||
"""Query the status of a task."""
|
||||
task = TaskManager().get_task(task_id)
|
||||
|
||||
if not task:
|
||||
|
|
@ -574,9 +537,7 @@ def get_task(task_id: str):
|
|||
|
||||
@graph_bp.route('/tasks', methods=['GET'])
|
||||
def list_tasks():
|
||||
"""
|
||||
列出所有任务
|
||||
"""
|
||||
"""List all tasks."""
|
||||
tasks = TaskManager().list_tasks()
|
||||
|
||||
return jsonify({
|
||||
|
|
@ -586,7 +547,7 @@ def list_tasks():
|
|||
})
|
||||
|
||||
|
||||
# ============== 图谱数据接口 ==============
|
||||
# ============== Graph data endpoints ==============
|
||||
|
||||
def _refresh_graph_cache(graph_id: str):
|
||||
"""Background thread: fetch graph data from Neo4j and update cache."""
|
||||
|
|
@ -613,11 +574,11 @@ def _refresh_graph_cache(graph_id: str):
|
|||
|
||||
@graph_bp.route('/data/<graph_id>', methods=['GET'])
|
||||
def get_graph_data(graph_id: str):
|
||||
"""
|
||||
获取图谱数据(节点和边)。
|
||||
- 有缓存且未过期:直接返回缓存,不调用 Zep
|
||||
- 有缓存但已过期:立即返回旧缓存,后台异步刷新
|
||||
- 无缓存:后台线程拉取,返回 202 让前端稍后重试
|
||||
"""Return graph data (nodes and edges).
|
||||
|
||||
- Fresh cache: serve from cache without hitting Zep.
|
||||
- Stale cache: return the old cache immediately and refresh in the background.
|
||||
- No cache: kick off a background fetch and return 202 so the frontend retries.
|
||||
"""
|
||||
if not Config.NEO4J_PASSWORD:
|
||||
return jsonify({"success": False, "error": t("api.error.graph.m028")}), 500
|
||||
|
|
@ -645,9 +606,7 @@ def get_graph_data(graph_id: str):
|
|||
|
||||
@graph_bp.route('/delete/<graph_id>', methods=['DELETE'])
|
||||
def delete_graph(graph_id: str):
|
||||
"""
|
||||
删除Zep图谱
|
||||
"""
|
||||
"""Delete a Zep graph."""
|
||||
try:
|
||||
if not Config.NEO4J_PASSWORD:
|
||||
return jsonify({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
Report API路由
|
||||
提供模拟报告生成、获取、对话等接口
|
||||
Report API routes.
|
||||
|
||||
Provides endpoints for generating, retrieving, and chatting about simulation reports.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
|
@ -20,30 +21,30 @@ from ..utils.locale import t, get_locale, set_locale
|
|||
logger = get_logger('mirofish.api.report')
|
||||
|
||||
|
||||
# ============== 报告生成接口 ==============
|
||||
# ============== Report generation endpoints ==============
|
||||
|
||||
@report_bp.route('/generate', methods=['POST'])
|
||||
def generate_report():
|
||||
"""
|
||||
生成模拟分析报告(异步任务)
|
||||
Generate a simulation analysis report (asynchronous task).
|
||||
|
||||
这是一个耗时操作,接口会立即返回task_id,
|
||||
使用 GET /api/report/generate/status 查询进度
|
||||
This is a long-running operation. The endpoint returns a task_id immediately;
|
||||
use GET /api/report/generate/status to poll progress.
|
||||
|
||||
请求(JSON):
|
||||
Request (JSON):
|
||||
{
|
||||
"simulation_id": "sim_xxxx", // 必填,模拟ID
|
||||
"force_regenerate": false // 可选,强制重新生成
|
||||
"simulation_id": "sim_xxxx", // required, simulation ID
|
||||
"force_regenerate": false // optional, force regeneration
|
||||
}
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"simulation_id": "sim_xxxx",
|
||||
"task_id": "task_xxxx",
|
||||
"status": "generating",
|
||||
"message": "报告生成任务已启动"
|
||||
"message": "Report generation task started"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
|
@ -59,7 +60,6 @@ def generate_report():
|
|||
|
||||
force_regenerate = data.get('force_regenerate', False)
|
||||
|
||||
# 获取模拟信息
|
||||
manager = SimulationManager()
|
||||
state = manager.get_simulation(simulation_id)
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ def generate_report():
|
|||
"error": t('api.simulationNotFound', id=simulation_id)
|
||||
}), 404
|
||||
|
||||
# 检查是否已有报告
|
||||
# Skip regeneration if a completed report already exists for this simulation.
|
||||
if not force_regenerate:
|
||||
existing_report = ReportManager.get_report_by_simulation(simulation_id)
|
||||
if existing_report and existing_report.status == ReportStatus.COMPLETED:
|
||||
|
|
@ -84,7 +84,6 @@ def generate_report():
|
|||
}
|
||||
})
|
||||
|
||||
# 获取项目信息
|
||||
project = ProjectManager.get_project(state.project_id)
|
||||
if not project:
|
||||
return jsonify({
|
||||
|
|
@ -106,11 +105,11 @@ def generate_report():
|
|||
"error": t('api.missingSimRequirement')
|
||||
}), 400
|
||||
|
||||
# 提前生成 report_id,以便立即返回给前端
|
||||
# Generate report_id eagerly so the frontend can use it immediately
|
||||
# (before the background task has actually persisted anything).
|
||||
import uuid
|
||||
report_id = f"report_{uuid.uuid4().hex[:12]}"
|
||||
|
||||
# 创建异步任务
|
||||
task_manager = TaskManager()
|
||||
task_id = task_manager.create_task(
|
||||
task_type="report_generate",
|
||||
|
|
@ -124,7 +123,6 @@ def generate_report():
|
|||
# Capture locale before spawning background thread
|
||||
current_locale = get_locale()
|
||||
|
||||
# 定义后台任务
|
||||
def run_generate():
|
||||
set_locale(current_locale)
|
||||
try:
|
||||
|
|
@ -135,14 +133,12 @@ def generate_report():
|
|||
message=t('api.initReportAgent')
|
||||
)
|
||||
|
||||
# 创建Report Agent
|
||||
agent = ReportAgent(
|
||||
graph_id=graph_id,
|
||||
simulation_id=simulation_id,
|
||||
simulation_requirement=simulation_requirement
|
||||
)
|
||||
|
||||
# 进度回调
|
||||
def progress_callback(stage, progress, message):
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
|
|
@ -150,13 +146,13 @@ def generate_report():
|
|||
message=f"[{stage}] {message}"
|
||||
)
|
||||
|
||||
# 生成报告(传入预先生成的 report_id)
|
||||
# Pass in the pre-generated report_id so the persisted report matches
|
||||
# the id we already returned to the frontend.
|
||||
report = agent.generate_report(
|
||||
progress_callback=progress_callback,
|
||||
report_id=report_id
|
||||
)
|
||||
|
||||
# 保存报告
|
||||
ReportManager.save_report(report)
|
||||
|
||||
if report.status == ReportStatus.COMPLETED:
|
||||
|
|
@ -175,7 +171,6 @@ def generate_report():
|
|||
logger.error(t("log.report_api.m001", str=str(e)))
|
||||
task_manager.fail_task(task_id, str(e))
|
||||
|
||||
# 启动后台线程
|
||||
thread = threading.Thread(target=run_generate, daemon=True)
|
||||
thread.start()
|
||||
|
||||
|
|
@ -203,15 +198,15 @@ def generate_report():
|
|||
@report_bp.route('/generate/status', methods=['POST'])
|
||||
def get_generate_status():
|
||||
"""
|
||||
查询报告生成任务进度
|
||||
Query the progress of a report generation task.
|
||||
|
||||
请求(JSON):
|
||||
Request (JSON):
|
||||
{
|
||||
"task_id": "task_xxxx", // 可选,generate返回的task_id
|
||||
"simulation_id": "sim_xxxx" // 可选,模拟ID
|
||||
"task_id": "task_xxxx", // optional, task_id returned by generate
|
||||
"simulation_id": "sim_xxxx" // optional, simulation ID
|
||||
}
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
|
|
@ -228,7 +223,8 @@ def get_generate_status():
|
|||
task_id = data.get('task_id')
|
||||
simulation_id = data.get('simulation_id')
|
||||
|
||||
# 如果提供了simulation_id,先检查是否已有完成的报告
|
||||
# If simulation_id is provided, short-circuit when a completed report already exists
|
||||
# so callers don't have to track a stale task_id after a successful run.
|
||||
if simulation_id:
|
||||
existing_report = ReportManager.get_report_by_simulation(simulation_id)
|
||||
if existing_report and existing_report.status == ReportStatus.COMPLETED:
|
||||
|
|
@ -272,14 +268,14 @@ def get_generate_status():
|
|||
}), 500
|
||||
|
||||
|
||||
# ============== 报告获取接口 ==============
|
||||
# ============== Report retrieval endpoints ==============
|
||||
|
||||
@report_bp.route('/<report_id>', methods=['GET'])
|
||||
def get_report(report_id: str):
|
||||
"""
|
||||
获取报告详情
|
||||
Get report details.
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
|
|
@ -319,9 +315,9 @@ def get_report(report_id: str):
|
|||
@report_bp.route('/by-simulation/<simulation_id>', methods=['GET'])
|
||||
def get_report_by_simulation(simulation_id: str):
|
||||
"""
|
||||
根据模拟ID获取报告
|
||||
Get the report for a given simulation ID.
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
|
|
@ -358,13 +354,13 @@ def get_report_by_simulation(simulation_id: str):
|
|||
@report_bp.route('/list', methods=['GET'])
|
||||
def list_reports():
|
||||
"""
|
||||
列出所有报告
|
||||
List all reports.
|
||||
|
||||
Query参数:
|
||||
simulation_id: 按模拟ID过滤(可选)
|
||||
limit: 返回数量限制(默认50)
|
||||
Query parameters:
|
||||
simulation_id: optional filter by simulation ID.
|
||||
limit: maximum number of reports to return (default 50).
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": [...],
|
||||
|
|
@ -398,9 +394,9 @@ def list_reports():
|
|||
@report_bp.route('/<report_id>/download', methods=['GET'])
|
||||
def download_report(report_id: str):
|
||||
"""
|
||||
下载报告(Markdown格式)
|
||||
Download a report as a Markdown file.
|
||||
|
||||
返回Markdown文件
|
||||
Returns the Markdown file as an attachment.
|
||||
"""
|
||||
try:
|
||||
report = ReportManager.get_report(report_id)
|
||||
|
|
@ -414,7 +410,8 @@ def download_report(report_id: str):
|
|||
md_path = ReportManager._get_report_markdown_path(report_id)
|
||||
|
||||
if not os.path.exists(md_path):
|
||||
# 如果MD文件不存在,生成一个临时文件
|
||||
# MD file is missing on disk; materialize a temp file from the in-memory content
|
||||
# so the download still succeeds for older reports that were never persisted.
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
||||
f.write(report.markdown_content)
|
||||
|
|
@ -443,7 +440,7 @@ def download_report(report_id: str):
|
|||
|
||||
@report_bp.route('/<report_id>', methods=['DELETE'])
|
||||
def delete_report(report_id: str):
|
||||
"""删除报告"""
|
||||
"""Delete a report."""
|
||||
try:
|
||||
success = ReportManager.delete_report(report_id)
|
||||
|
||||
|
|
@ -467,32 +464,33 @@ def delete_report(report_id: str):
|
|||
}), 500
|
||||
|
||||
|
||||
# ============== Report Agent对话接口 ==============
|
||||
# ============== Report Agent chat endpoints ==============
|
||||
|
||||
@report_bp.route('/chat', methods=['POST'])
|
||||
def chat_with_report_agent():
|
||||
"""
|
||||
与Report Agent对话
|
||||
Chat with the Report Agent.
|
||||
|
||||
Report Agent可以在对话中自主调用检索工具来回答问题
|
||||
The Report Agent can autonomously invoke retrieval tools during the conversation
|
||||
to answer the user's question.
|
||||
|
||||
请求(JSON):
|
||||
Request (JSON):
|
||||
{
|
||||
"simulation_id": "sim_xxxx", // 必填,模拟ID
|
||||
"message": "请解释一下舆情走向", // 必填,用户消息
|
||||
"chat_history": [ // 可选,对话历史
|
||||
"simulation_id": "sim_xxxx", // required, simulation ID
|
||||
"message": "Explain the sentiment trend", // required, user message
|
||||
"chat_history": [ // optional, prior turns
|
||||
{"role": "user", "content": "..."},
|
||||
{"role": "assistant", "content": "..."}
|
||||
]
|
||||
}
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"response": "Agent回复...",
|
||||
"tool_calls": [调用的工具列表],
|
||||
"sources": [信息来源]
|
||||
"response": "Agent reply...",
|
||||
"tool_calls": [list of tools invoked],
|
||||
"sources": [information sources]
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
|
@ -515,7 +513,6 @@ def chat_with_report_agent():
|
|||
"error": t('api.requireMessage')
|
||||
}), 400
|
||||
|
||||
# 获取模拟和项目信息
|
||||
manager = SimulationManager()
|
||||
state = manager.get_simulation(simulation_id)
|
||||
|
||||
|
|
@ -541,7 +538,6 @@ def chat_with_report_agent():
|
|||
|
||||
simulation_requirement = project.simulation_requirement or ""
|
||||
|
||||
# 创建Agent并进行对话
|
||||
agent = ReportAgent(
|
||||
graph_id=graph_id,
|
||||
simulation_id=simulation_id,
|
||||
|
|
@ -564,22 +560,22 @@ def chat_with_report_agent():
|
|||
}), 500
|
||||
|
||||
|
||||
# ============== 报告进度与分章节接口 ==============
|
||||
# ============== Report progress and section endpoints ==============
|
||||
|
||||
@report_bp.route('/<report_id>/progress', methods=['GET'])
|
||||
def get_report_progress(report_id: str):
|
||||
"""
|
||||
获取报告生成进度(实时)
|
||||
Get real-time report generation progress.
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"status": "generating",
|
||||
"progress": 45,
|
||||
"message": "正在生成章节: 关键发现",
|
||||
"current_section": "关键发现",
|
||||
"completed_sections": ["执行摘要", "模拟背景"],
|
||||
"message": "Generating section: Key Findings",
|
||||
"current_section": "Key Findings",
|
||||
"completed_sections": ["Executive Summary", "Simulation Background"],
|
||||
"updated_at": "2025-12-09T..."
|
||||
}
|
||||
}
|
||||
|
|
@ -610,11 +606,12 @@ def get_report_progress(report_id: str):
|
|||
@report_bp.route('/<report_id>/sections', methods=['GET'])
|
||||
def get_report_sections(report_id: str):
|
||||
"""
|
||||
获取已生成的章节列表(分章节输出)
|
||||
Get the list of sections generated so far (per-section streaming output).
|
||||
|
||||
前端可以轮询此接口获取已生成的章节内容,无需等待整个报告完成
|
||||
The frontend can poll this endpoint to render sections incrementally,
|
||||
without waiting for the entire report to finish.
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
|
|
@ -623,7 +620,7 @@ def get_report_sections(report_id: str):
|
|||
{
|
||||
"filename": "section_01.md",
|
||||
"section_index": 1,
|
||||
"content": "## 执行摘要\\n\\n..."
|
||||
"content": "## Executive Summary\\n\\n..."
|
||||
},
|
||||
...
|
||||
],
|
||||
|
|
@ -635,7 +632,6 @@ def get_report_sections(report_id: str):
|
|||
try:
|
||||
sections = ReportManager.get_generated_sections(report_id)
|
||||
|
||||
# 获取报告状态
|
||||
report = ReportManager.get_report(report_id)
|
||||
is_complete = report is not None and report.status == ReportStatus.COMPLETED
|
||||
|
||||
|
|
@ -661,14 +657,14 @@ def get_report_sections(report_id: str):
|
|||
@report_bp.route('/<report_id>/section/<int:section_index>', methods=['GET'])
|
||||
def get_single_section(report_id: str, section_index: int):
|
||||
"""
|
||||
获取单个章节内容
|
||||
Get the content of a single section.
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"filename": "section_01.md",
|
||||
"content": "## 执行摘要\\n\\n..."
|
||||
"content": "## Executive Summary\\n\\n..."
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
|
@ -702,16 +698,16 @@ def get_single_section(report_id: str, section_index: int):
|
|||
}), 500
|
||||
|
||||
|
||||
# ============== 报告状态检查接口 ==============
|
||||
# ============== Report status check endpoints ==============
|
||||
|
||||
@report_bp.route('/check/<simulation_id>', methods=['GET'])
|
||||
def check_report_status(simulation_id: str):
|
||||
"""
|
||||
检查模拟是否有报告,以及报告状态
|
||||
Check whether a simulation has a report, and report its status.
|
||||
|
||||
用于前端判断是否解锁Interview功能
|
||||
Used by the frontend to decide whether to unlock the Interview feature.
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
|
|
@ -730,7 +726,7 @@ def check_report_status(simulation_id: str):
|
|||
report_status = report.status.value if report else None
|
||||
report_id = report.report_id if report else None
|
||||
|
||||
# 只有报告完成后才解锁interview
|
||||
# Interview feature is only unlocked once a report has finished generating.
|
||||
interview_unlocked = has_report and report.status == ReportStatus.COMPLETED
|
||||
|
||||
return jsonify({
|
||||
|
|
@ -753,22 +749,22 @@ def check_report_status(simulation_id: str):
|
|||
}), 500
|
||||
|
||||
|
||||
# ============== Agent 日志接口 ==============
|
||||
# ============== Agent log endpoints ==============
|
||||
|
||||
@report_bp.route('/<report_id>/agent-log', methods=['GET'])
|
||||
def get_agent_log(report_id: str):
|
||||
"""
|
||||
获取 Report Agent 的详细执行日志
|
||||
Get the detailed execution log of the Report Agent.
|
||||
|
||||
实时获取报告生成过程中的每一步动作,包括:
|
||||
- 报告开始、规划开始/完成
|
||||
- 每个章节的开始、工具调用、LLM响应、完成
|
||||
- 报告完成或失败
|
||||
Streams every step the agent took while generating the report, including:
|
||||
- Report start, planning start/complete.
|
||||
- Per-section start, tool calls, LLM responses, and completion.
|
||||
- Final report completion or failure.
|
||||
|
||||
Query参数:
|
||||
from_line: 从第几行开始读取(可选,默认0,用于增量获取)
|
||||
Query parameters:
|
||||
from_line: line offset to start reading from (optional, default 0, for incremental polling).
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
|
|
@ -779,7 +775,7 @@ def get_agent_log(report_id: str):
|
|||
"report_id": "report_xxxx",
|
||||
"action": "tool_call",
|
||||
"stage": "generating",
|
||||
"section_title": "执行摘要",
|
||||
"section_title": "Executive Summary",
|
||||
"section_index": 1,
|
||||
"details": {
|
||||
"tool_name": "insight_forge",
|
||||
|
|
@ -817,9 +813,9 @@ def get_agent_log(report_id: str):
|
|||
@report_bp.route('/<report_id>/agent-log/stream', methods=['GET'])
|
||||
def stream_agent_log(report_id: str):
|
||||
"""
|
||||
获取完整的 Agent 日志(一次性获取全部)
|
||||
Get the full Agent log in one shot (no pagination).
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
|
|
@ -848,27 +844,27 @@ def stream_agent_log(report_id: str):
|
|||
}), 500
|
||||
|
||||
|
||||
# ============== 控制台日志接口 ==============
|
||||
# ============== Console log endpoints ==============
|
||||
|
||||
@report_bp.route('/<report_id>/console-log', methods=['GET'])
|
||||
def get_console_log(report_id: str):
|
||||
"""
|
||||
获取 Report Agent 的控制台输出日志
|
||||
Get the Report Agent's console output log.
|
||||
|
||||
实时获取报告生成过程中的控制台输出(INFO、WARNING等),
|
||||
这与 agent-log 接口返回的结构化 JSON 日志不同,
|
||||
是纯文本格式的控制台风格日志。
|
||||
Streams the console output produced during report generation (INFO, WARNING, etc.).
|
||||
Unlike the structured JSON returned by the agent-log endpoint, this is plain-text
|
||||
console-style output.
|
||||
|
||||
Query参数:
|
||||
from_line: 从第几行开始读取(可选,默认0,用于增量获取)
|
||||
Query parameters:
|
||||
from_line: line offset to start reading from (optional, default 0, for incremental polling).
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"logs": [
|
||||
"[19:46:14] INFO: 搜索完成: 找到 15 条相关事实",
|
||||
"[19:46:14] INFO: 图谱搜索: graph_id=xxx, query=...",
|
||||
"[19:46:14] INFO: Search complete: found 15 relevant facts",
|
||||
"[19:46:14] INFO: Graph search: graph_id=xxx, query=...",
|
||||
...
|
||||
],
|
||||
"total_lines": 100,
|
||||
|
|
@ -899,9 +895,9 @@ def get_console_log(report_id: str):
|
|||
@report_bp.route('/<report_id>/console-log/stream', methods=['GET'])
|
||||
def stream_console_log(report_id: str):
|
||||
"""
|
||||
获取完整的控制台日志(一次性获取全部)
|
||||
Get the full console log in one shot (no pagination).
|
||||
|
||||
返回:
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
|
|
@ -930,17 +926,17 @@ def stream_console_log(report_id: str):
|
|||
}), 500
|
||||
|
||||
|
||||
# ============== 工具调用接口(供调试使用)==============
|
||||
# ============== Tool invocation endpoints (for debugging) ==============
|
||||
|
||||
@report_bp.route('/tools/search', methods=['POST'])
|
||||
def search_graph_tool():
|
||||
"""
|
||||
图谱搜索工具接口(供调试使用)
|
||||
Graph search tool endpoint (for debugging).
|
||||
|
||||
请求(JSON):
|
||||
Request (JSON):
|
||||
{
|
||||
"graph_id": "mirofish_xxxx",
|
||||
"query": "搜索查询",
|
||||
"query": "search query",
|
||||
"limit": 10
|
||||
}
|
||||
"""
|
||||
|
|
@ -983,9 +979,9 @@ def search_graph_tool():
|
|||
@report_bp.route('/tools/statistics', methods=['POST'])
|
||||
def get_graph_statistics_tool():
|
||||
"""
|
||||
图谱统计工具接口(供调试使用)
|
||||
Graph statistics tool endpoint (for debugging).
|
||||
|
||||
请求(JSON):
|
||||
Request (JSON):
|
||||
{
|
||||
"graph_id": "mirofish_xxxx"
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue