Files
planet/backend/app/api/v1/ai.py
rayd1o 5bf5c73ca0
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
release: bump version to 0.66.0
2026-05-26 03:41:47 +08:00

465 lines
15 KiB
Python

from uuid import uuid4
from fastapi import APIRouter, Depends, HTTPException, Request, Response
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.logging import get_logger
from app.core.security import get_current_user
from app.db.session import get_db
from app.models.user import User
from app.schemas.ai import (
AIProviderStatusResponse,
AlertBriefRequest,
AlertBriefResponse,
BGPBriefRequest,
BGPBriefRecordResponse,
BGPBriefRecordSummary,
PlaygroundMessageActionResponse,
PlaygroundMessageCreateRequest,
PlaygroundMessageEditRequest,
PlaygroundMessageResendRequest,
PlaygroundMessageStopRequest,
PlaygroundSessionResponse,
PlaygroundSessionUpsertRequest,
PlaygroundThreadResponse,
SituationalAlertBriefRequest,
SituationalAlertBriefResponse,
SituationalAnalysisRequest,
SituationalAnalysisResponse,
)
from app.services.alert_ai_brief import build_alert_brief_request
from app.services.ai_client import AIProviderClient, get_ai_provider_client
from app.services.bgp_ai_brief import build_bgp_brief_request
from app.services.bgp_ai_brief_store import (
get_bgp_brief_record,
get_latest_bgp_brief_record,
list_bgp_brief_records,
save_bgp_brief_record,
)
from app.services.playground_session_store import (
get_playground_session,
upsert_playground_session,
)
from app.services.playground_chat_service import (
create_turn,
edit_user_message,
get_thread,
resend_turn,
stop_message,
)
from app.services.situational_alert_ai_brief import build_situational_alert_brief_request
from app.services.business_logs import emit_business_log, exception_context
router = APIRouter()
logger = get_logger(__name__, service="api")
@router.get("/provider/status", response_model=AIProviderStatusResponse)
async def get_ai_provider_status(
request: Request,
response: Response,
current_user: User = Depends(get_current_user),
provider_client: AIProviderClient = Depends(get_ai_provider_client),
):
request_id = request.headers.get("X-Request-ID") or str(uuid4())
response.headers["X-Request-ID"] = request_id
return await provider_client.get_status(request_id=request_id)
@router.post("/situational-awareness/analyze", response_model=SituationalAnalysisResponse)
async def analyze_situational_awareness(
payload: SituationalAnalysisRequest,
request: Request,
response: Response,
current_user: User = Depends(get_current_user),
provider_client: AIProviderClient = Depends(get_ai_provider_client),
):
request_id = request.headers.get("X-Request-ID") or str(uuid4())
response.headers["X-Request-ID"] = request_id
return await provider_client.analyze(payload, request_id=request_id)
@router.get("/playground/thread", response_model=PlaygroundThreadResponse | None)
async def get_playground_thread(
session_key: str = "default",
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
return await get_thread(
db,
user_id=current_user.id,
session_key=session_key,
)
@router.get("/playground/session", response_model=PlaygroundSessionResponse | None)
async def get_saved_playground_session(
session_key: str = "default",
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
return await get_playground_session(
db,
user_id=current_user.id,
session_key=session_key,
)
@router.put("/playground/session", response_model=PlaygroundSessionResponse)
async def save_playground_session(
payload: PlaygroundSessionUpsertRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
return await upsert_playground_session(
db,
user_id=current_user.id,
payload=payload,
)
@router.post("/playground/messages", response_model=PlaygroundMessageActionResponse)
async def create_playground_message(
payload: PlaygroundMessageCreateRequest,
current_user: User = Depends(get_current_user),
provider_client: AIProviderClient = Depends(get_ai_provider_client),
db: AsyncSession = Depends(get_db),
):
await emit_business_log(
logger,
event="ai.playground.message.create",
message="Playground message creation requested",
category="ai",
service="api",
module=__name__,
user_id=current_user.id,
context={"session_key": payload.session_key, "preset": payload.selected_preset_key},
)
return await create_turn(
db,
user_id=current_user.id,
payload=payload,
provider_client=provider_client,
)
@router.post("/playground/messages/stop", response_model=PlaygroundMessageActionResponse)
async def stop_playground_message(
payload: PlaygroundMessageStopRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
await emit_business_log(
logger,
event="ai.playground.message.stop",
message="Playground message stop requested",
category="ai",
service="api",
module=__name__,
user_id=current_user.id,
context={"session_key": payload.session_key, "message_id": payload.message_id},
)
return await stop_message(
db,
user_id=current_user.id,
payload=payload,
)
@router.post("/playground/messages/resend", response_model=PlaygroundMessageActionResponse)
async def resend_playground_message(
payload: PlaygroundMessageResendRequest,
current_user: User = Depends(get_current_user),
provider_client: AIProviderClient = Depends(get_ai_provider_client),
db: AsyncSession = Depends(get_db),
):
await emit_business_log(
logger,
event="ai.playground.message.resend",
message="Playground message resend requested",
category="ai",
service="api",
module=__name__,
user_id=current_user.id,
context={"session_key": payload.session_key, "user_message_id": payload.user_message_id},
)
return await resend_turn(
db,
user_id=current_user.id,
payload=payload,
provider_client=provider_client,
)
@router.post("/playground/messages/edit", response_model=PlaygroundMessageActionResponse)
async def edit_playground_message(
payload: PlaygroundMessageEditRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
return await edit_user_message(
db,
user_id=current_user.id,
payload=payload,
)
@router.get("/bgp/briefs", response_model=list[BGPBriefRecordSummary])
async def list_saved_bgp_briefs(
current_user: User = Depends(get_current_user),
):
return list_bgp_brief_records()
@router.get("/bgp/briefs/latest", response_model=BGPBriefRecordResponse | None)
async def get_latest_saved_bgp_brief(
current_user: User = Depends(get_current_user),
):
return get_latest_bgp_brief_record()
@router.get("/bgp/briefs/{brief_id}", response_model=BGPBriefRecordResponse)
async def get_saved_bgp_brief(
brief_id: str,
current_user: User = Depends(get_current_user),
):
record = get_bgp_brief_record(brief_id)
if record is None:
raise HTTPException(status_code=404, detail="BGP brief not found")
return record
@router.post("/bgp/brief", response_model=BGPBriefRecordResponse)
async def analyze_bgp_brief(
payload: BGPBriefRequest,
request: Request,
response: Response,
current_user: User = Depends(get_current_user),
provider_client: AIProviderClient = Depends(get_ai_provider_client),
db: AsyncSession = Depends(get_db),
):
request_id = request.headers.get("X-Request-ID") or str(uuid4())
response.headers["X-Request-ID"] = request_id
brief_request, facts, context = await build_bgp_brief_request(
db,
incident_limit=payload.incident_limit,
anomaly_limit=payload.anomaly_limit,
collector_limit=payload.collector_limit,
)
await emit_business_log(
logger,
event="ai.brief.bgp.facts_collected",
message="BGP brief facts collected",
category="ai",
service="api",
module=__name__,
request_id=request_id,
user_id=current_user.id,
context={
"incident_limit": payload.incident_limit,
"anomaly_limit": payload.anomaly_limit,
"collector_limit": payload.collector_limit,
"fact_count": len(facts or []),
},
)
brief_request.preferred_model = payload.preferred_model
brief_request.thinking = payload.thinking
await emit_business_log(
logger,
event="ai.brief.bgp.start",
message="BGP brief AI analysis started",
category="ai",
service="api",
module=__name__,
request_id=request_id,
user_id=current_user.id,
context={"preferred_model": payload.preferred_model},
)
try:
analysis = await provider_client.analyze(brief_request, request_id=request_id)
record = save_bgp_brief_record(
analysis,
request_id=request_id,
facts=facts,
context=context,
)
await emit_business_log(
logger,
event="ai.brief.bgp.completed",
message="BGP brief AI analysis saved",
category="ai",
service="api",
module=__name__,
request_id=request_id,
user_id=current_user.id,
context={"provider": analysis.provider, "model": analysis.model, "brief_id": record.id},
)
return record
except Exception as exc:
await emit_business_log(
logger,
event="ai.brief.bgp.failed",
message="BGP brief AI analysis failed",
category="ai",
level="error",
service="api",
module=__name__,
request_id=request_id,
user_id=current_user.id,
context=exception_context(exc, {"preferred_model": payload.preferred_model}),
)
raise
@router.post("/alerts/brief", response_model=AlertBriefResponse)
async def analyze_alert_brief(
payload: AlertBriefRequest,
request: Request,
response: Response,
current_user: User = Depends(get_current_user),
provider_client: AIProviderClient = Depends(get_ai_provider_client),
db: AsyncSession = Depends(get_db),
):
request_id = request.headers.get("X-Request-ID") or str(uuid4())
response.headers["X-Request-ID"] = request_id
brief_request, facts, context = await build_alert_brief_request(
db,
alert_limit=payload.alert_limit,
)
await emit_business_log(
logger,
event="ai.brief.alerts.facts_collected",
message="Alert brief facts collected",
category="ai",
service="api",
module=__name__,
request_id=request_id,
user_id=current_user.id,
context={"alert_limit": payload.alert_limit, "fact_count": len(facts or [])},
)
brief_request.preferred_model = payload.preferred_model
brief_request.thinking = payload.thinking
await emit_business_log(
logger,
event="ai.brief.alerts.start",
message="Alert brief AI analysis started",
category="ai",
service="api",
module=__name__,
request_id=request_id,
user_id=current_user.id,
context={"preferred_model": payload.preferred_model},
)
try:
analysis = await provider_client.analyze(brief_request, request_id=request_id)
await emit_business_log(
logger,
event="ai.brief.alerts.completed",
message="Alert brief AI analysis completed",
category="ai",
service="api",
module=__name__,
request_id=request_id,
user_id=current_user.id,
context={"provider": analysis.provider, "model": analysis.model},
)
return AlertBriefResponse(
**analysis.model_dump(),
title=brief_request.title,
objective=brief_request.objective,
facts=facts,
context=context,
)
except Exception as exc:
await emit_business_log(
logger,
event="ai.brief.alerts.failed",
message="Alert brief AI analysis failed",
category="ai",
level="error",
service="api",
module=__name__,
request_id=request_id,
user_id=current_user.id,
context=exception_context(exc, {"preferred_model": payload.preferred_model}),
)
raise
@router.post("/situational-alerts/brief", response_model=SituationalAlertBriefResponse)
async def analyze_situational_alert_brief(
payload: SituationalAlertBriefRequest,
request: Request,
response: Response,
current_user: User = Depends(get_current_user),
provider_client: AIProviderClient = Depends(get_ai_provider_client),
db: AsyncSession = Depends(get_db),
):
request_id = request.headers.get("X-Request-ID") or str(uuid4())
response.headers["X-Request-ID"] = request_id
brief_request, facts, context = await build_situational_alert_brief_request(db)
await emit_business_log(
logger,
event="ai.brief.situational_alerts.facts_collected",
message="Situational alert brief facts collected",
category="ai",
service="api",
module=__name__,
request_id=request_id,
user_id=current_user.id,
context={"fact_count": len(facts or [])},
)
brief_request.preferred_model = payload.preferred_model
brief_request.thinking = payload.thinking
await emit_business_log(
logger,
event="ai.brief.situational_alerts.start",
message="Situational alert brief AI analysis started",
category="ai",
service="api",
module=__name__,
request_id=request_id,
user_id=current_user.id,
context={"preferred_model": payload.preferred_model},
)
try:
analysis = await provider_client.analyze(brief_request, request_id=request_id)
await emit_business_log(
logger,
event="ai.brief.situational_alerts.completed",
message="Situational alert brief AI analysis completed",
category="ai",
service="api",
module=__name__,
request_id=request_id,
user_id=current_user.id,
context={"provider": analysis.provider, "model": analysis.model},
)
return SituationalAlertBriefResponse(
**analysis.model_dump(),
title=brief_request.title,
objective=brief_request.objective,
facts=facts,
context=context,
)
except Exception as exc:
await emit_business_log(
logger,
event="ai.brief.situational_alerts.failed",
message="Situational alert brief AI analysis failed",
category="ai",
level="error",
service="api",
module=__name__,
request_id=request_id,
user_id=current_user.id,
context=exception_context(exc, {"preferred_model": payload.preferred_model}),
)
raise