from __future__ import annotations import asyncio import json from collections.abc import AsyncIterator from typing import Annotated, Any from fastapi import APIRouter, Depends, HTTPException, status from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session from app.api.deps import get_db from app.schemas.common import ErrorResponse from app.schemas.steward import StewardPlanRequest, StewardPlanResponse from app.services.runtime_chat import RuntimeChatService from app.services.steward_intent_agent import StewardIntentAgent from app.services.steward_planner import StewardPlannerService router = APIRouter(prefix="/steward") DbSession = Annotated[Session, Depends(get_db)] @router.post( "/plans", response_model=StewardPlanResponse, summary="生成小财管家任务计划", description="把首页自然语言和附件元信息拆解为可确认、可追踪、可分派的财务任务计划。", responses={ status.HTTP_400_BAD_REQUEST: { "model": ErrorResponse, "description": "请求缺少任务描述,无法生成小财管家计划。", } }, ) def create_steward_plan(payload: StewardPlanRequest, db: DbSession) -> StewardPlanResponse: try: return _build_steward_planner(db).build_plan(payload) except ValueError as exc: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc @router.post( "/plans/stream", summary="流式生成小财管家任务计划", description="以 NDJSON 逐条返回小财管家的过程摘要事件,最后返回完整任务计划。", ) async def stream_steward_plan(payload: StewardPlanRequest, db: DbSession) -> StreamingResponse: return StreamingResponse( _iter_steward_plan_events(payload, _build_steward_planner(db)), media_type="application/x-ndjson", ) async def _iter_steward_plan_events( payload: StewardPlanRequest, planner: StewardPlannerService, ) -> AsyncIterator[str]: try: plan = planner.build_plan(payload) except ValueError as exc: yield _encode_stream_event("error", {"message": str(exc)}) return for event in plan.thinking_events: yield _encode_stream_event("thinking", event.model_dump(mode="json")) await asyncio.sleep(0.18) yield _encode_stream_event("plan", plan.model_dump(mode="json")) def _encode_stream_event(event: str, data: dict[str, Any]) -> str: return json.dumps({"event": event, "data": data}, ensure_ascii=False) + "\n" def _build_steward_planner(db: Session) -> StewardPlannerService: return StewardPlannerService( intent_agent=StewardIntentAgent(RuntimeChatService(db)), )