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>
172 lines
4.8 KiB
Python
172 lines
4.8 KiB
Python
"""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}
|