feat: 展示 AI 分析过程

This commit is contained in:
cnbugs
2026-07-25 11:47:07 +08:00
parent 8109204843
commit 26ae846501
4 changed files with 389 additions and 95 deletions
+68 -4
View File
@@ -2,6 +2,7 @@
import os
import json
import asyncio
import httpx
from datetime import datetime
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
@@ -10,7 +11,7 @@ from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from typing import Optional
from backend.config import PORT
from backend.config import PORT, AI_API_BASE, AI_MODEL
from backend.diagnosis import (
run_full_diagnosis, build_diagnosis_report, ALL_CHECKS,
check_connectivity,
@@ -18,7 +19,10 @@ from backend.diagnosis import (
check_deployments, check_services, check_pvc, check_resource_usage,
check_network_policies,
)
from backend.ai_agent import analyze_with_ai, chat_with_ai
from backend.ai_agent import (
analyze_with_ai, chat_with_ai,
analyze_with_ai_stream, chat_with_ai_stream,
)
app = FastAPI(title="K8S 智能诊断平台", version="1.0.0")
@@ -196,7 +200,7 @@ async def get_latest():
@app.post("/api/analyze")
async def analyze(req: AnalyzeRequest):
"""AI 分析诊断结果"""
"""AI 分析诊断结果 (非流式,兼容旧接口)"""
global _last_diagnosis, _last_report
if _last_diagnosis is None:
_last_diagnosis = await run_full_diagnosis()
@@ -205,13 +209,73 @@ async def analyze(req: AnalyzeRequest):
return result
@app.get("/api/analyze/stream")
async def analyze_stream(question: str = ""):
"""SSE 流式 AI 分析 - 逐字推送分析内容"""
global _last_diagnosis, _last_report
if _last_diagnosis is None:
_last_diagnosis = await run_full_diagnosis()
_last_report = build_diagnosis_report(_last_diagnosis)
async def event_generator():
process_steps = [
("report", "读取诊断报告", "已加载本次集群诊断上下文"),
("risk", "识别关键风险", "正在梳理严重问题、警告和潜在影响"),
("solution", "生成修复方案", "正在为高优先级问题生成可执行操作"),
("summary", "整理分析结论", "正在组织根因、影响和预防建议"),
]
yield f"data: {json.dumps({'type': 'start', 'model': AI_MODEL}, ensure_ascii=False)}\n\n"
try:
for step, title, detail in process_steps:
yield f"data: {json.dumps({'type': 'process', 'step': step, 'title': title, 'detail': detail, 'status': 'running'}, ensure_ascii=False)}\n\n"
await asyncio.sleep(0)
yield f"data: {json.dumps({'type': 'process', 'step': step, 'title': title, 'detail': detail, 'status': 'done'}, ensure_ascii=False)}\n\n"
async for chunk in analyze_with_ai_stream(_last_report, question=question):
yield f"data: {json.dumps({'type': 'chunk', 'content': chunk}, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n"
except httpx.HTTPStatusError as e:
err = f"AI 接口返回错误: {e.response.status_code} - {str(e)[:300]}"
yield f"data: {json.dumps({'type': 'error', 'message': err}, ensure_ascii=False)}\n\n"
except httpx.ConnectError:
yield f"data: {json.dumps({'type': 'error', 'message': f'无法连接 AI 服务 ({AI_API_BASE}),请检查 .env 中的 AI_API_BASE 配置'}, ensure_ascii=False)}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'message': f'AI 分析失败: {str(e)}'}, ensure_ascii=False)}\n\n"
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
@app.post("/api/chat")
async def chat(req: ChatRequest):
"""AI 多轮对话 (基于诊断上下文追问)"""
"""AI 多轮对话 (非流式,兼容旧接口)"""
result = await chat_with_ai(req.messages, context=_last_report)
return result
@app.post("/api/chat/stream")
async def chat_stream(req: ChatRequest):
"""SSE 流式 AI 对话 - 逐字推送回复"""
async def event_generator():
yield f"data: {json.dumps({'type': 'start'}, ensure_ascii=False)}\n\n"
try:
async for chunk in chat_with_ai_stream(req.messages, context=_last_report):
yield f"data: {json.dumps({'type': 'chunk', 'content': chunk}, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'message': f'AI 对话失败: {str(e)}'}, ensure_ascii=False)}\n\n"
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
@app.get("/api/report")
async def get_report():
"""获取诊断报告文本"""