1020 lines
30 KiB
Python
1020 lines
30 KiB
Python
"""
|
|
Report API routes
|
|
Cung cấp các API cho việc generate báo cáo simulation, lấy báo cáo, và chat
|
|
"""
|
|
|
|
import os
|
|
import traceback
|
|
import threading
|
|
from flask import request, jsonify, send_file
|
|
|
|
from . import report_bp
|
|
from ..config import Config
|
|
from ..services.report_agent import ReportAgent, ReportManager, ReportStatus
|
|
from ..services.simulation_manager import SimulationManager
|
|
from ..models.project import ProjectManager
|
|
from ..models.task import TaskManager, TaskStatus
|
|
from ..utils.logger import get_logger
|
|
|
|
logger = get_logger('mirofish.api.report')
|
|
|
|
|
|
# ============== Report generation API ==============
|
|
|
|
@report_bp.route('/generate', methods=['POST'])
|
|
def generate_report():
|
|
"""
|
|
Generate báo cáo phân tích simulation (async task)
|
|
|
|
Đây là thao tác tốn thời gian, API sẽ trả về task_id ngay lập tức,
|
|
dùng GET /api/report/generate/status để kiểm tra progress
|
|
|
|
Request (JSON):
|
|
{
|
|
"simulation_id": "sim_xxxx", // bắt buộc
|
|
"force_regenerate": false // optional, force regenerate
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"simulation_id": "sim_xxxx",
|
|
"task_id": "task_xxxx",
|
|
"status": "generating",
|
|
"message": "Report generation task started"
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
data = request.get_json() or {}
|
|
|
|
simulation_id = data.get('simulation_id')
|
|
if not simulation_id:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": "Please provide simulation_id"
|
|
}), 400
|
|
|
|
force_regenerate = data.get('force_regenerate', False)
|
|
|
|
# Lấy thông tin simulation
|
|
manager = SimulationManager()
|
|
state = manager.get_simulation(simulation_id)
|
|
|
|
if not state:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": f"Simulation does not exist: {simulation_id}"
|
|
}), 404
|
|
|
|
# Kiểm tra đã có report chưa
|
|
if not force_regenerate:
|
|
existing_report = ReportManager.get_report_by_simulation(simulation_id)
|
|
if existing_report and existing_report.status == ReportStatus.COMPLETED:
|
|
return jsonify({
|
|
"success": True,
|
|
"data": {
|
|
"simulation_id": simulation_id,
|
|
"report_id": existing_report.report_id,
|
|
"status": "completed",
|
|
"message": "Report already exists",
|
|
"already_generated": True
|
|
}
|
|
})
|
|
|
|
# Lấy thông tin project
|
|
project = ProjectManager.get_project(state.project_id)
|
|
if not project:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": f"Project does not exist: {state.project_id}"
|
|
}), 404
|
|
|
|
graph_id = state.graph_id or project.graph_id
|
|
if not graph_id:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": "Missing graph_id, please ensure the graph has been built"
|
|
}), 400
|
|
|
|
simulation_requirement = project.simulation_requirement
|
|
if not simulation_requirement:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": "Missing simulation requirement description"
|
|
}), 400
|
|
|
|
# Tạo report_id trước để có thể trả về ngay cho frontend
|
|
import uuid
|
|
report_id = f"report_{uuid.uuid4().hex[:12]}"
|
|
|
|
# Tạo async task
|
|
task_manager = TaskManager()
|
|
task_id = task_manager.create_task(
|
|
task_type="report_generate",
|
|
metadata={
|
|
"simulation_id": simulation_id,
|
|
"graph_id": graph_id,
|
|
"report_id": report_id
|
|
}
|
|
)
|
|
|
|
# Định nghĩa background task
|
|
def run_generate():
|
|
try:
|
|
task_manager.update_task(
|
|
task_id,
|
|
status=TaskStatus.PROCESSING,
|
|
progress=0,
|
|
message="Initializing Report Agent..."
|
|
)
|
|
|
|
# Tạo Report Agent
|
|
agent = ReportAgent(
|
|
graph_id=graph_id,
|
|
simulation_id=simulation_id,
|
|
project_id=state.project_id,
|
|
simulation_requirement=simulation_requirement
|
|
)
|
|
|
|
# Callback cập nhật progress
|
|
def progress_callback(stage, progress, message):
|
|
task_manager.update_task(
|
|
task_id,
|
|
progress=progress,
|
|
message=f"[{stage}] {message}"
|
|
)
|
|
|
|
# Generate report
|
|
report = agent.generate_report(
|
|
progress_callback=progress_callback,
|
|
report_id=report_id
|
|
)
|
|
|
|
# Lưu report
|
|
ReportManager.save_report(report)
|
|
|
|
if report.status == ReportStatus.COMPLETED:
|
|
task_manager.complete_task(
|
|
task_id,
|
|
result={
|
|
"report_id": report.report_id,
|
|
"simulation_id": simulation_id,
|
|
"status": "completed"
|
|
}
|
|
)
|
|
else:
|
|
task_manager.fail_task(task_id, report.error or "Report generation failed")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Report generation failed: {str(e)}")
|
|
task_manager.fail_task(task_id, str(e))
|
|
|
|
# Chạy background thread
|
|
thread = threading.Thread(target=run_generate, daemon=True)
|
|
thread.start()
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": {
|
|
"simulation_id": simulation_id,
|
|
"report_id": report_id,
|
|
"task_id": task_id,
|
|
"status": "generating",
|
|
"message": "Report generation task started. Use /api/report/generate/status to check progress",
|
|
"already_generated": False
|
|
}
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to start report generation task: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
@report_bp.route('/generate/status', methods=['POST'])
|
|
def get_generate_status():
|
|
"""
|
|
Truy vấn progress của task generate report
|
|
|
|
Request (JSON):
|
|
{
|
|
"task_id": "task_xxxx", // optional, task_id trả về từ generate
|
|
"simulation_id": "sim_xxxx" // optional
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"task_id": "task_xxxx",
|
|
"status": "processing|completed|failed",
|
|
"progress": 45,
|
|
"message": "..."
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
data = request.get_json() or {}
|
|
|
|
task_id = data.get('task_id')
|
|
simulation_id = data.get('simulation_id')
|
|
|
|
# Nếu có simulation_id, kiểm tra xem report đã hoàn thành chưa
|
|
if simulation_id:
|
|
existing_report = ReportManager.get_report_by_simulation(simulation_id)
|
|
if existing_report and existing_report.status == ReportStatus.COMPLETED:
|
|
return jsonify({
|
|
"success": True,
|
|
"data": {
|
|
"simulation_id": simulation_id,
|
|
"report_id": existing_report.report_id,
|
|
"status": "completed",
|
|
"progress": 100,
|
|
"message": "Report generated",
|
|
"already_completed": True
|
|
}
|
|
})
|
|
|
|
if not task_id:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": "Please provide task_id or simulation_id"
|
|
}), 400
|
|
|
|
task_manager = TaskManager()
|
|
task = task_manager.get_task(task_id)
|
|
|
|
if not task:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": f"Task does not exist: {task_id}"
|
|
}), 404
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": task.to_dict()
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to query task status: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e)
|
|
}), 500
|
|
|
|
|
|
# ============== Report retrieval APIs ==============
|
|
|
|
@report_bp.route('/<report_id>', methods=['GET'])
|
|
def get_report(report_id: str):
|
|
"""
|
|
Lấy chi tiết report
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"report_id": "report_xxxx",
|
|
"simulation_id": "sim_xxxx",
|
|
"status": "completed",
|
|
"outline": {...},
|
|
"markdown_content": "...",
|
|
"created_at": "...",
|
|
"completed_at": "..."
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
report = ReportManager.get_report(report_id)
|
|
|
|
if not report:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": f"Report does not exist: {report_id}"
|
|
}), 404
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": report.to_dict()
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to retrieve report: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
@report_bp.route('/by-simulation/<simulation_id>', methods=['GET'])
|
|
def get_report_by_simulation(simulation_id: str):
|
|
"""
|
|
Lấy report theo simulation_id
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"report_id": "report_xxxx",
|
|
...
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
report = ReportManager.get_report_by_simulation(simulation_id)
|
|
|
|
if not report:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": f"No report found for this simulation: {simulation_id}",
|
|
"has_report": False
|
|
}), 404
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": report.to_dict(),
|
|
"has_report": True
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to retrieve report: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
@report_bp.route('/list', methods=['GET'])
|
|
def list_reports():
|
|
"""
|
|
Liệt kê tất cả reports
|
|
|
|
Query parameters:
|
|
simulation_id: Filter theo simulation_id (optional)
|
|
limit: Giới hạn số lượng trả về (default 50)
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": [...],
|
|
"count": 10
|
|
}
|
|
"""
|
|
try:
|
|
simulation_id = request.args.get('simulation_id')
|
|
limit = request.args.get('limit', 50, type=int)
|
|
|
|
reports = ReportManager.list_reports(
|
|
simulation_id=simulation_id,
|
|
limit=limit
|
|
)
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": [r.to_dict() for r in reports],
|
|
"count": len(reports)
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to list reports: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
@report_bp.route('/<report_id>/download', methods=['GET'])
|
|
def download_report(report_id: str):
|
|
"""
|
|
Download report (Markdown format)
|
|
|
|
Trả về file Markdown
|
|
"""
|
|
try:
|
|
report = ReportManager.get_report(report_id)
|
|
|
|
if not report:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": f"Report does not exist: {report_id}"
|
|
}), 404
|
|
|
|
md_path = ReportManager._get_report_markdown_path(report_id)
|
|
|
|
if not os.path.exists(md_path):
|
|
# Nếu file MD không tồn tại, tạo file tạm
|
|
import tempfile
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
|
f.write(report.markdown_content)
|
|
temp_path = f.name
|
|
|
|
return send_file(
|
|
temp_path,
|
|
as_attachment=True,
|
|
download_name=f"{report_id}.md"
|
|
)
|
|
|
|
return send_file(
|
|
md_path,
|
|
as_attachment=True,
|
|
download_name=f"{report_id}.md"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to download report: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
@report_bp.route('/<report_id>', methods=['DELETE'])
|
|
def delete_report(report_id: str):
|
|
"""Xóa report"""
|
|
try:
|
|
success = ReportManager.delete_report(report_id)
|
|
|
|
if not success:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": f"Report does not exist: {report_id}"
|
|
}), 404
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"message": f"Report deleted: {report_id}"
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to delete report: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
# ============== Report Agent chat API ==============
|
|
|
|
@report_bp.route('/chat', methods=['POST'])
|
|
def chat_with_report_agent():
|
|
"""
|
|
Chat với Report Agent
|
|
|
|
Report Agent có thể tự động gọi các retrieval tool trong quá trình trả lời
|
|
|
|
Request (JSON):
|
|
{
|
|
"simulation_id": "sim_xxxx", // bắt buộc
|
|
"message": "Please explain the public opinion trend", // bắt buộc
|
|
"chat_history": [ // optional
|
|
{"role": "user", "content": "..."},
|
|
{"role": "assistant", "content": "..."}
|
|
]
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"response": "Agent reply...",
|
|
"tool_calls": [list of called tools],
|
|
"sources": [information sources]
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
data = request.get_json() or {}
|
|
|
|
simulation_id = data.get('simulation_id')
|
|
message = data.get('message')
|
|
chat_history = data.get('chat_history', [])
|
|
|
|
if not simulation_id:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": "Please provide simulation_id"
|
|
}), 400
|
|
|
|
if not message:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": "Please provide message"
|
|
}), 400
|
|
|
|
# Lấy thông tin simulation và project
|
|
manager = SimulationManager()
|
|
state = manager.get_simulation(simulation_id)
|
|
|
|
if not state:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": f"Simulation does not exist: {simulation_id}"
|
|
}), 404
|
|
|
|
project = ProjectManager.get_project(state.project_id)
|
|
if not project:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": f"Project does not exist: {state.project_id}"
|
|
}), 404
|
|
|
|
graph_id = state.graph_id or project.graph_id
|
|
if not graph_id:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": "Missing graph_id"
|
|
}), 400
|
|
|
|
simulation_requirement = project.simulation_requirement or ""
|
|
|
|
# Tạo Agent và chat
|
|
agent = ReportAgent(
|
|
graph_id=graph_id,
|
|
simulation_id=simulation_id,
|
|
project_id=state.project_id,
|
|
simulation_requirement=simulation_requirement
|
|
)
|
|
|
|
result = agent.chat(message=message, chat_history=chat_history)
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": result
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Chat failed: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
# ============== Report progress & sections APIs ==============
|
|
|
|
@report_bp.route('/<report_id>/progress', methods=['GET'])
|
|
def get_report_progress(report_id: str):
|
|
"""
|
|
Lấy progress generate report (real-time)
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"status": "generating",
|
|
"progress": 45,
|
|
"message": "Generating section: Key Findings",
|
|
"current_section": "Key Findings",
|
|
"completed_sections": ["Executive Summary", "Simulation Background"],
|
|
"updated_at": "2025-12-09T..."
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
progress = ReportManager.get_progress(report_id)
|
|
|
|
if not progress:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": f"Report does not exist or progress unavailable: {report_id}"
|
|
}), 404
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": progress
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to retrieve report progress: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
@report_bp.route('/<report_id>/sections', methods=['GET'])
|
|
def get_report_sections(report_id: str):
|
|
"""
|
|
Lấy danh sách các section đã generate (output theo từng section)
|
|
|
|
Frontend có thể poll API này để lấy section đã generate
|
|
mà không cần chờ toàn bộ report hoàn thành
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"report_id": "report_xxxx",
|
|
"sections": [
|
|
{
|
|
"filename": "section_01.md",
|
|
"section_index": 1,
|
|
"content": "## Executive Summary\\n\\n..."
|
|
}
|
|
],
|
|
"total_sections": 3,
|
|
"is_complete": false
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
sections = ReportManager.get_generated_sections(report_id)
|
|
|
|
# Lấy trạng thái report
|
|
report = ReportManager.get_report(report_id)
|
|
is_complete = report is not None and report.status == ReportStatus.COMPLETED
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": {
|
|
"report_id": report_id,
|
|
"sections": sections,
|
|
"total_sections": len(sections),
|
|
"is_complete": is_complete
|
|
}
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to retrieve section list: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
@report_bp.route('/<report_id>/section/<int:section_index>', methods=['GET'])
|
|
def get_single_section(report_id: str, section_index: int):
|
|
"""
|
|
Lấy nội dung một section
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"filename": "section_01.md",
|
|
"content": "## Executive Summary\\n\\n..."
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
section_path = ReportManager._get_section_path(report_id, section_index)
|
|
|
|
if not os.path.exists(section_path):
|
|
return jsonify({
|
|
"success": False,
|
|
"error": f"Section does not exist: section_{section_index:02d}.md"
|
|
}), 404
|
|
|
|
with open(section_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": {
|
|
"filename": f"section_{section_index:02d}.md",
|
|
"section_index": section_index,
|
|
"content": content
|
|
}
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to retrieve section content: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
# ============== 报告状态检查接口 ==============
|
|
|
|
@report_bp.route('/check/<simulation_id>', methods=['GET'])
|
|
def check_report_status(simulation_id: str):
|
|
"""
|
|
检查模拟是否有报告,以及报告状态
|
|
|
|
用于前端判断是否解锁Interview功能
|
|
|
|
返回:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"simulation_id": "sim_xxxx",
|
|
"has_report": true,
|
|
"report_status": "completed",
|
|
"report_id": "report_xxxx",
|
|
"interview_unlocked": true
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
report = ReportManager.get_report_by_simulation(simulation_id)
|
|
|
|
has_report = report is not None
|
|
report_status = report.status.value if report else None
|
|
report_id = report.report_id if report else None
|
|
|
|
# 只有报告完成后才解锁interview
|
|
interview_unlocked = has_report and report.status == ReportStatus.COMPLETED
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": {
|
|
"simulation_id": simulation_id,
|
|
"has_report": has_report,
|
|
"report_status": report_status,
|
|
"report_id": report_id,
|
|
"interview_unlocked": interview_unlocked
|
|
}
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"检查报告状态失败: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
# ============== API nhật ký Agent ==============
|
|
|
|
@report_bp.route('/<report_id>/agent-log', methods=['GET'])
|
|
def get_agent_log(report_id: str):
|
|
"""
|
|
Lấy nhật ký thực thi chi tiết của Report Agent
|
|
|
|
Lấy theo thời gian thực từng bước trong quá trình tạo báo cáo, bao gồm:
|
|
- Bắt đầu báo cáo, bắt đầu / hoàn thành planning
|
|
- Bắt đầu từng section, tool call, LLM response, hoàn thành
|
|
- Báo cáo hoàn thành hoặc thất bại
|
|
|
|
Query parameters:
|
|
from_line: đọc từ dòng nào (optional, mặc định 0, dùng cho incremental fetch)
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"logs": [
|
|
{
|
|
"timestamp": "2025-12-13T...",
|
|
"elapsed_seconds": 12.5,
|
|
"report_id": "report_xxxx",
|
|
"action": "tool_call",
|
|
"stage": "generating",
|
|
"section_title": "Executive Summary",
|
|
"section_index": 1,
|
|
"details": {
|
|
"tool_name": "insight_forge",
|
|
"parameters": {...},
|
|
...
|
|
}
|
|
},
|
|
...
|
|
],
|
|
"total_lines": 25,
|
|
"from_line": 0,
|
|
"has_more": false
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
from_line = request.args.get('from_line', 0, type=int)
|
|
|
|
log_data = ReportManager.get_agent_log(report_id, from_line=from_line)
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": log_data
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get Agent log: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
@report_bp.route('/<report_id>/agent-log/stream', methods=['GET'])
|
|
def stream_agent_log(report_id: str):
|
|
"""
|
|
Lấy toàn bộ Agent logs (fetch toàn bộ một lần)
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"logs": [...],
|
|
"count": 25
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
logs = ReportManager.get_agent_log_stream(report_id)
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": {
|
|
"logs": logs,
|
|
"count": len(logs)
|
|
}
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get Agent log: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
# ============== API nhật ký Console ==============
|
|
|
|
@report_bp.route('/<report_id>/console-log', methods=['GET'])
|
|
def get_console_log(report_id: str):
|
|
"""
|
|
Lấy console output logs của Report Agent
|
|
|
|
Lấy theo thời gian thực các console output trong quá trình tạo báo cáo
|
|
(INFO, WARNING, ...).
|
|
|
|
Khác với API agent-log trả về structured JSON logs,
|
|
API này trả về console-style log dạng text thuần.
|
|
|
|
Query parameters:
|
|
from_line: đọc từ dòng nào (optional, mặc định 0, dùng cho incremental fetch)
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"logs": [
|
|
"[19:46:14] INFO: Search completed: found 15 relevant facts",
|
|
"[19:46:14] INFO: Graph search: graph_id=xxx, query=...",
|
|
...
|
|
],
|
|
"total_lines": 100,
|
|
"from_line": 0,
|
|
"has_more": false
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
from_line = request.args.get('from_line', 0, type=int)
|
|
|
|
log_data = ReportManager.get_console_log(report_id, from_line=from_line)
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": log_data
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get console log: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
@report_bp.route('/<report_id>/console-log/stream', methods=['GET'])
|
|
def stream_console_log(report_id: str):
|
|
"""
|
|
Lấy toàn bộ console logs (fetch toàn bộ một lần)
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"logs": [...],
|
|
"count": 100
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
logs = ReportManager.get_console_log_stream(report_id)
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": {
|
|
"logs": logs,
|
|
"count": len(logs)
|
|
}
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get console log: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
# ============== API gọi Tool (dùng cho debugging) ==============
|
|
|
|
@report_bp.route('/tools/search', methods=['POST'])
|
|
def search_graph_tool():
|
|
"""
|
|
API công cụ graph search (dùng cho debugging)
|
|
|
|
Request (JSON):
|
|
{
|
|
"graph_id": "mirofish_xxxx",
|
|
"query": "search query",
|
|
"limit": 10
|
|
}
|
|
"""
|
|
try:
|
|
data = request.get_json() or {}
|
|
|
|
graph_id = data.get('graph_id')
|
|
query = data.get('query')
|
|
limit = data.get('limit', 10)
|
|
|
|
if not graph_id or not query:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": "Please provide graph_id and query"
|
|
}), 400
|
|
|
|
from ..services.zep_tools import ZepToolsService
|
|
|
|
tools = ZepToolsService()
|
|
result = tools.search_graph(
|
|
graph_id=graph_id,
|
|
query=query,
|
|
limit=limit
|
|
)
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": result.to_dict()
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Graph search failed: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|
|
|
|
|
|
@report_bp.route('/tools/statistics', methods=['POST'])
|
|
def get_graph_statistics_tool():
|
|
"""
|
|
API công cụ thống kê graph (dùng cho debugging)
|
|
|
|
Request (JSON):
|
|
{
|
|
"graph_id": "mirofish_xxxx"
|
|
}
|
|
"""
|
|
try:
|
|
data = request.get_json() or {}
|
|
|
|
graph_id = data.get('graph_id')
|
|
|
|
if not graph_id:
|
|
return jsonify({
|
|
"success": False,
|
|
"error": "Please provide graph_id"
|
|
}), 400
|
|
|
|
from ..services.zep_tools import ZepToolsService
|
|
|
|
tools = ZepToolsService()
|
|
result = tools.get_graph_statistics(graph_id)
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"data": result
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get graph statistics: {str(e)}")
|
|
return jsonify({
|
|
"success": False,
|
|
"error": str(e),
|
|
"traceback": traceback.format_exc()
|
|
}), 500
|