feat(ai-session): add FlowPilot AI-powered troubleshooting sessions
Implements Phase 1 of the FlowPilot-First pivot — the core AI session experience where engineers describe a problem and FlowPilot guides them through structured diagnosis with selectable options, free-text escape hatches, and auto-generated documentation on resolution. Backend: AISession + AISessionStep models, FlowPilot Engine (LLM orchestration with structured JSON output), Flow Matching Engine v1 (semantic + keyword + recency scoring), 8 API endpoints with auth, rate limiting, and AI quota enforcement. Frontend: Intake screen, conversational session view with sidebar, step cards with options/actions/resolution suggestions, resolve/escalate modals, documentation view with rating, session history integration, and /pilot route with sidebar navigation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
171
backend/app/schemas/ai_session.py
Normal file
171
backend/app/schemas/ai_session.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""Pydantic schemas for FlowPilot AI sessions."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Any
|
||||
from uuid import UUID
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
# ── Intake ──
|
||||
|
||||
class AISessionCreateRequest(BaseModel):
|
||||
"""Start a new FlowPilot session."""
|
||||
intake_type: str = Field(
|
||||
"free_text",
|
||||
pattern="^(free_text|psa_ticket|screenshot|log_paste|combined)$",
|
||||
)
|
||||
intake_content: dict[str, Any] = Field(
|
||||
...,
|
||||
description=(
|
||||
"Intake payload. Shape depends on intake_type: "
|
||||
"{text: str} for free_text, "
|
||||
"{text?: str, image_urls?: list[str]} for screenshot, "
|
||||
"{text?: str, log_content?: str} for log_paste, "
|
||||
"{ticket_id: str, psa_connection_id: str} for psa_ticket, "
|
||||
"any combination for combined."
|
||||
),
|
||||
)
|
||||
psa_ticket_id: Optional[str] = None
|
||||
psa_connection_id: Optional[UUID] = None
|
||||
|
||||
|
||||
class AISessionCreateResponse(BaseModel):
|
||||
"""Response after starting a session — includes the first FlowPilot step."""
|
||||
session_id: UUID
|
||||
status: str
|
||||
confidence_tier: str
|
||||
problem_summary: str | None = None
|
||||
problem_domain: str | None = None
|
||||
matched_flow_id: UUID | None = None
|
||||
matched_flow_name: str | None = None
|
||||
match_score: float | None = None
|
||||
first_step: AISessionStepResponse
|
||||
|
||||
|
||||
# ── Step interaction ──
|
||||
|
||||
class StepOptionSchema(BaseModel):
|
||||
"""A selectable option presented to the engineer."""
|
||||
label: str
|
||||
value: str
|
||||
followup_hint: str | None = None
|
||||
|
||||
|
||||
class AISessionStepResponse(BaseModel):
|
||||
"""A FlowPilot step rendered in the session UI."""
|
||||
step_id: UUID
|
||||
step_order: int
|
||||
step_type: str
|
||||
content: dict[str, Any]
|
||||
context_message: str | None = None
|
||||
options: list[StepOptionSchema] = []
|
||||
allow_free_text: bool = True
|
||||
allow_skip: bool = True
|
||||
confidence_tier: str
|
||||
confidence_score: float
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class StepResponseRequest(BaseModel):
|
||||
"""Engineer's response to a FlowPilot step."""
|
||||
selected_option: str | None = None
|
||||
free_text_input: str | None = None
|
||||
was_skipped: bool = False
|
||||
action_result: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class StepResponseResponse(BaseModel):
|
||||
"""FlowPilot's next step after processing the engineer's response."""
|
||||
session_id: UUID
|
||||
status: str
|
||||
confidence_tier: str
|
||||
confidence_score: float
|
||||
next_step: AISessionStepResponse | None = None
|
||||
resolution_suggested: bool = False
|
||||
resolution_summary: str | None = None
|
||||
|
||||
|
||||
# ── Resolution / Escalation ──
|
||||
|
||||
class ResolveSessionRequest(BaseModel):
|
||||
"""Close a session as resolved."""
|
||||
resolution_summary: str = Field(..., min_length=5, max_length=2000)
|
||||
resolution_action: str | None = None
|
||||
session_rating: int | None = Field(None, ge=1, le=5)
|
||||
session_feedback: str | None = None
|
||||
|
||||
|
||||
class EscalateSessionRequest(BaseModel):
|
||||
"""Escalate a session to another engineer."""
|
||||
escalation_reason: str = Field(..., min_length=5, max_length=2000)
|
||||
escalated_to_id: UUID | None = None
|
||||
|
||||
|
||||
class DocumentationStep(BaseModel):
|
||||
"""A step in the documentation trail."""
|
||||
step_number: int
|
||||
step_type: str
|
||||
description: str
|
||||
engineer_response: str | None = None
|
||||
outcome: str | None = None
|
||||
|
||||
|
||||
class SessionDocumentation(BaseModel):
|
||||
"""Auto-generated session documentation."""
|
||||
problem_summary: str
|
||||
problem_domain: str | None = None
|
||||
intake_summary: str
|
||||
diagnostic_steps: list[DocumentationStep]
|
||||
resolution_summary: str | None = None
|
||||
escalation_reason: str | None = None
|
||||
total_steps: int
|
||||
duration_display: str | None = None
|
||||
generated_at: datetime
|
||||
|
||||
|
||||
class SessionCloseResponse(BaseModel):
|
||||
"""Response after resolving or escalating."""
|
||||
session_id: UUID
|
||||
status: str
|
||||
documentation: SessionDocumentation
|
||||
|
||||
|
||||
class RateSessionRequest(BaseModel):
|
||||
"""Submit post-session rating."""
|
||||
rating: int = Field(..., ge=1, le=5)
|
||||
feedback: str | None = None
|
||||
|
||||
|
||||
# ── List / Detail ──
|
||||
|
||||
class AISessionSummary(BaseModel):
|
||||
"""Compact session for list views."""
|
||||
id: UUID
|
||||
status: str
|
||||
intake_type: str
|
||||
problem_summary: str | None = None
|
||||
problem_domain: str | None = None
|
||||
confidence_tier: str
|
||||
step_count: int
|
||||
session_rating: int | None = None
|
||||
created_at: datetime
|
||||
resolved_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class AISessionDetail(AISessionSummary):
|
||||
"""Full session detail with steps."""
|
||||
intake_content: dict[str, Any]
|
||||
matched_flow_id: UUID | None = None
|
||||
match_score: float | None = None
|
||||
resolution_summary: str | None = None
|
||||
resolution_action: str | None = None
|
||||
escalation_reason: str | None = None
|
||||
session_feedback: str | None = None
|
||||
steps: list[AISessionStepResponse] = []
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
Reference in New Issue
Block a user