PMFlow/backend/app/api/ai_steward_api/routes_rag_sources.py

152 lines
5.7 KiB
Python

from __future__ import annotations
from .shared import *
from .helpers import _ensure_project_signal_link
from .schemas import SignalSourceListResponse, SignalSourceOnboardRequest, SignalSourceRepairRequest
@router.post("/rag/ask")
async def ask_ai_steward_rag(
request: RAGAskRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
service = AdaptiveRAGService(db, tenant_id=effective_tenant_id)
result = await service.answer_question(
question=request.question,
asked_by=int(current_user.id),
asked_via="pmflow",
project_id=request.project_id,
source_chat_id=request.source_chat_id,
session_id=request.session_id,
force_new_session=bool(request.new_session),
thinking_mode=request.thinking_mode,
action_mode=request.action_mode,
sync_requirement=bool(request.sync_requirement),
)
return success_response(result)
@router.post("/signal-sources/onboard")
@audited_write(caller="user")
async def onboard_signal_source(
request: SignalSourceOnboardRequest,
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
signal = Signal(
tenant_id=effective_tenant_id,
source="PMFlow",
content=f"[PMFlow用户#{int(current_user.id)}]: {request.instruction[:1800]}",
impact_score=0.7,
priority="medium",
processed=False,
strategic_analysis=json.dumps(
{
"source": "pmflow_manual_signal_source_onboard",
"instruction": request.instruction[:2000],
"created_at": now_cn_aware().isoformat(),
},
ensure_ascii=False,
),
tasks_generated=json.dumps([], ensure_ascii=False),
matched_project_id=int(request.project_id),
title=(request.instruction[:80] or "新增信号源"),
signal_type="新需求",
summary=request.instruction[:300],
reasoning=json.dumps(["来自 AI Steward 手动信号源接入请求。"], ensure_ascii=False),
confidence=0.82,
recommended_action="enter_only",
status="待处理",
)
if not request.dry_run:
db.add(signal)
await db.flush()
await _ensure_project_signal_link(
db,
tenant_id=effective_tenant_id,
project_id=int(request.project_id),
signal_id=int(signal.id),
)
await db.commit()
await db.refresh(signal)
signal_id_value = int(signal.id) if not request.dry_run and getattr(signal, "id", None) is not None else None
service = SignalSourceOrchestrationService(db)
result = await service.orchestrate_from_signal(
tenant_id=effective_tenant_id,
project_id=int(request.project_id),
signal_id=signal_id_value,
initiating_text=request.instruction,
requested_via="pmflow",
operator_user_id=int(current_user.id),
dry_run=bool(request.dry_run),
response_mode=request.response_mode,
)
return success_response(
{
"signal_id": signal_id_value,
**result,
}
)
@router.get("/orchestration-runs/{trace_id}")
async def get_orchestration_run(
trace_id: str,
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
row = await AIOrchestrationRunService(db).get_run(
tenant_id=effective_tenant_id,
trace_id=str(trace_id or "").strip(),
)
if row is None:
raise HTTPException(status_code=404, detail="orchestration run not found")
return success_response(AIOrchestrationRunService.serialize_run(row))
@router.get("/projects/{project_id}/signal-sources")
async def list_project_signal_sources(
project_id: int,
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
items = await SignalSourceOrchestrationService(db).list_project_sources(
tenant_id=effective_tenant_id,
project_id=int(project_id),
)
return success_response(SignalSourceListResponse(items=items).model_dump())
@router.post("/signal-sources/repair-dirty")
@audited_write(caller="system")
async def repair_dirty_signal_sources(
request: SignalSourceRepairRequest,
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
try:
rows = await SignalSourceRepairService(db).repair_registries(
tenant_id=effective_tenant_id,
project_id=int(request.project_id),
registry_ids=[int(item) for item in request.registry_ids],
sender=str(request.sender or "PMFlow修复器").strip()[:64] or "PMFlow修复器",
)
except SignalSourceRepairTimeoutError as exc:
raise HTTPException(status_code=504, detail=str(exc)) from exc
except SignalSourceRepairMissingSignalError as exc:
raise HTTPException(status_code=502, detail=str(exc)) from exc
return success_response(
{
"project_id": int(request.project_id),
"items": rows,
}
)