Skip to content
3 min read · 638 words

Schemas — schemas.py

Pydantic models define the HTTP contract: what FastAPI accepts (request schemas) and what it serializes back (response schemas with from_attributes = True).

Purpose

backend/app/schemas.py is the single source of request/response shapes. Request schemas reject malformed bodies with 422 before handlers run; response schemas (declared via response_model=) filter ORM objects down to the public contract.

Conventions

  • Response schemas set class Config: from_attributes = True so FastAPI can serialize SQLAlchemy objects directly.
  • Optionality is expressed as field: T | None = None; defaults mirror the ORM defaults (language="English", tone="Professional", top_k=3).
  • Naming: XCreate/XRequest for inputs, XResponse for outputs.

Schema catalog

Agents & chat

SchemaDirectionFields
AgentCreaterequestname, type, description?, system_prompt, language="English", tone="Professional", enabled_tools=""
AgentResponseresponseall of the above + id, created_at
ChatRequestrequestmessage: str
ChatResponseresponseagent_id, user_message, assistant_response, run_id, latency_ms
MessageResponseresponseid, agent_id, role, content, created_at
AgentRunResponseresponseid, agent_id, input_text, output_text?, latency_ms?, status, error_message?, created_at

Email & tasks

SchemaDirectionFields
EmailAnalyzeRequestrequestemail_text: str
EmailAnalysisResponse(declared, unused as response_model)summary, intent, priority: Literal["low","medium","high","urgent"], deadline?, action_items: list[str], suggested_reply
TaskResponseresponseid, agent_id, title, description?, priority, status, due_date?, source_type, created_at
TaskStatusUpdaterequeststatus: str (validated in the handler against open/in_progress/completed)

EmailAnalysisResponse allows "urgent", but the analysis prompt instructs the model to use only low | medium | high — and the endpoint returns the analysis as a raw dict anyway (no response_model), so this Literal is never enforced in practice. Logged in Known Issues.

Tool calls

ToolCallResponseid, agent_id, agent_run_id?, tool_name, tool_input?, tool_output?, success: str, error_message?, latency_ms?, created_at, model_name?, estimated_tokens?, estimated_cost?. Mirrors the ToolCall model exactly, string-typed success included.

Documents & RAG

SchemaDirectionFields
DocumentResponseresponseid, agent_id, filename, file_type, status, chunks_count, created_at
DocumentChunkResponseresponseid, document_id, agent_id, chunk_text, chunk_index, created_at
DocumentSearchRequestrequestquery: str, top_k: int = 3
DocumentSearchResultnestedchunk_text, score?, document_id, filename, chunk_index
DocumentSearchResponseresponsequery, results: list[DocumentSearchResult]
RAGAnswerRequestrequestquestion: str, top_k: int = 3
RAGSourcenestedfilename, document_id, chunk_index, score?
RetrievalEvaluationnestedhas_answer: bool, confidence: float, reason: str
RAGAnswerResponseresponsequestion, answer, sources: list[RAGSource], retrieval_evaluation?

Workflows

SchemaDirectionFields
MultiAgentEmailWorkflowRequestrequestemail_text: str, create_tasks: bool = True
WorkflowReviewResponsenestedapproved: bool, quality_score: float, issues: list[str], recommendation: str
MultiAgentEmailWorkflowResponseresponseemail_text, analysis: dict, tasks_created: list[TaskResponse], suggested_reply, review: WorkflowReviewResponse
WorkflowRunResponseresponseid, agent_id, workflow_name, input_text, final_output?, status, quality_score?, error_message?, created_at
WorkflowStepResponseresponseid, workflow_run_id, step_name, input_data?, output_data?, status, latency_ms?, error_message?, created_at

Note analysis: dict — the analysis JSON is passed through untyped, so its shape is guaranteed only by the prompt contract, not by Pydantic.

Dashboard & stats

AgentDashboardResponse — the aggregated metrics contract the frontend dashboard renders: task counts (total/open/in_progress/completed), tool-call counts (total/successful/failed), workflow counts, average_latency_ms?, estimated_total_tokens, estimated_total_cost: str, average_quality_score?. See Observability.

AgentStatsResponse — declared for the stats endpoint but inconsistent with what the handler returns (and agent_name is typed int); that endpoint is broken practice code — see Known Issues.

Validation examples

bash
# Missing required field → 422 with field pointer
curl -X POST http://localhost:8001/agents/ -H "Content-Type: application/json" -d '{"name": "X"}'
json
{
  "detail": [
    {"type": "missing", "loc": ["body", "type"], "msg": "Field required"},
    {"type": "missing", "loc": ["body", "system_prompt"], "msg": "Field required"}
  ]
}

The frontend proxy flattens this array shape into a single message for the UI — see API Proxy.

Future improvements

  • Enforce EmailAnalysisResponse as the analysis response_model (and align the Literal with the prompt).
  • Replace analysis: dict with the typed schema.
  • Add Field constraints (max_length on names, ge=1 on top_k).