docs(i18n): translate chinese docstrings/comments in backend/api

This commit is contained in:
Dominik Seemann 2026-05-09 10:59:36 +00:00
parent 6439e58eb5
commit b5a8996692
3 changed files with 640 additions and 760 deletions

View File

@ -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上传文件分析生成本体定义
请求方式multipart/form-data
参数
files: 上传的文件PDF/MD/TXT可多个
simulation_requirement: 模拟需求描述必填
project_name: 项目名称可选
additional_context: 额外说明可选
返回
"""Endpoint 1: upload files, analyze them, and generate an ontology definition.
Request format: multipart/form-data.
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": {
@ -156,8 +148,7 @@ 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构建图谱
请求JSON
"""Endpoint 2: build the graph for the given project_id.
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="文本分块中...",
@ -404,30 +376,27 @@ def build_graph():
overlap=chunk_overlap
)
total_chunks = len(chunks)
# 创建图谱
task_manager.update_task(
task_id,
message="创建Zep图谱...",
progress=10
)
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="设置本体定义...",
progress=15
)
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,
@ -476,16 +445,14 @@ def build_graph():
)
builder._wait_for_episodes(episode_uuids, wait_progress_callback)
# 获取图谱数据
task_manager.update_task(
task_id,
message="获取图谱数据...",
progress=95
)
graph_data = builder.get_graph_data(graph_id)
# 更新项目状态
project.status = ProjectStatus.GRAPH_COMPLETED
ProjectManager.save_project(project)
@ -498,8 +465,7 @@ def build_graph():
node_count=node_count,
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({

View File

@ -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():
"""
生成模拟分析报告异步任务
这是一个耗时操作接口会立即返回task_id
使用 GET /api/report/generate/status 查询进度
请求JSON
Generate a simulation analysis report (asynchronous task).
This is a long-running operation. The endpoint returns a task_id immediately;
use GET /api/report/generate/status to poll progress.
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"
}
}
"""
@ -58,8 +59,7 @@ def generate_report():
}), 400
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:
@ -134,15 +132,13 @@ def generate_report():
progress=0,
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:
@ -174,8 +170,7 @@ def generate_report():
except Exception as e:
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():
"""
查询报告生成任务进度
请求JSON
Query the progress of a report generation task.
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():
"""
列出所有报告
Query参数
simulation_id: 按模拟ID过滤可选
limit: 返回数量限制默认50
返回
List all reports.
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格式
返回Markdown文件
Download a report as a Markdown file.
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对话
Report Agent可以在对话中自主调用检索工具来回答问题
请求JSON
Chat with the Report Agent.
The Report Agent can autonomously invoke retrieval tools during the conversation
to answer the user's question.
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)
@ -540,8 +537,7 @@ def chat_with_report_agent():
}), 400
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..."
},
...
],
@ -634,8 +631,7 @@ 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):
"""
检查模拟是否有报告以及报告状态
用于前端判断是否解锁Interview功能
返回
Check whether a simulation has a report, and report its status.
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 的详细执行日志
实时获取报告生成过程中的每一步动作包括
- 报告开始规划开始/完成
- 每个章节的开始工具调用LLM响应完成
- 报告完成或失败
Query参数
from_line: 从第几行开始读取可选默认0用于增量获取
返回
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.
- Per-section start, tool calls, LLM responses, and completion.
- Final report completion or failure.
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 的控制台输出日志
实时获取报告生成过程中的控制台输出INFOWARNING等
这与 agent-log 接口返回的结构化 JSON 日志不同
是纯文本格式的控制台风格日志
Query参数
from_line: 从第几行开始读取可选默认0用于增量获取
返回
Get the Report Agent's console output log.
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 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():
"""
图谱搜索工具接口供调试使用
请求JSON
Graph search tool endpoint (for debugging).
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():
"""
图谱统计工具接口供调试使用
请求JSON
Graph statistics tool endpoint (for debugging).
Request (JSON):
{
"graph_id": "mirofish_xxxx"
}

File diff suppressed because it is too large Load Diff