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