feat(api): enhance error handling and add structured hints for agent operations
This commit is contained in:
@@ -5,7 +5,8 @@ from __future__ import annotations
|
||||
from typing import Literal
|
||||
from uuid import UUID
|
||||
|
||||
from sqlmodel import Field, SQLModel
|
||||
from pydantic import ConfigDict, Field
|
||||
from sqlmodel import SQLModel
|
||||
|
||||
from app.schemas.common import NonEmptyStr
|
||||
|
||||
@@ -23,72 +24,255 @@ def _user_reply_tags() -> list[str]:
|
||||
class GatewayLeadMessageRequest(SQLModel):
|
||||
"""Request payload for sending a message to a board lead agent."""
|
||||
|
||||
kind: Literal["question", "handoff"] = "question"
|
||||
correlation_id: str | None = None
|
||||
content: NonEmptyStr
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"x-llm-intent": "lead_direct_message",
|
||||
"x-when-to-use": [
|
||||
"A board has an urgent tactical request that needs direct lead routing",
|
||||
"You need a specific lead response before delegating work",
|
||||
],
|
||||
"x-when-not-to-use": [
|
||||
"Broadcasting to many leads (use broadcast request)",
|
||||
"Requesting end-user decisions (use ask-user request)",
|
||||
],
|
||||
"x-required-actor": "main_agent",
|
||||
"x-response-shape": "GatewayLeadMessageResponse",
|
||||
},
|
||||
)
|
||||
|
||||
kind: Literal["question", "handoff"] = Field(
|
||||
default="question",
|
||||
description="Routing mode for lead messages.",
|
||||
examples=["question", "handoff"],
|
||||
)
|
||||
correlation_id: str | None = Field(
|
||||
default=None,
|
||||
description="Optional correlation token shared across upstream and downstream systems.",
|
||||
examples=["lead-msg-1234"],
|
||||
)
|
||||
content: NonEmptyStr = Field(
|
||||
description="Human-readable body sent to lead agents.",
|
||||
examples=["Please triage the highest-priority blocker on board X."],
|
||||
)
|
||||
|
||||
# How the lead should reply (defaults are interpreted by templates).
|
||||
reply_tags: list[str] = Field(default_factory=_lead_reply_tags)
|
||||
reply_source: str | None = "lead_to_gateway_main"
|
||||
reply_tags: list[str] = Field(
|
||||
default_factory=_lead_reply_tags,
|
||||
description="Tags required by reply templates when the lead responds.",
|
||||
examples=[["gateway_main", "lead_reply"]],
|
||||
)
|
||||
reply_source: str | None = Field(
|
||||
default="lead_to_gateway_main",
|
||||
description="Reply destination key for the orchestrator.",
|
||||
examples=["lead_to_gateway_main"],
|
||||
)
|
||||
|
||||
|
||||
class GatewayLeadMessageResponse(SQLModel):
|
||||
"""Response payload for a lead-message dispatch attempt."""
|
||||
|
||||
ok: bool = True
|
||||
board_id: UUID
|
||||
lead_agent_id: UUID | None = None
|
||||
lead_agent_name: str | None = None
|
||||
lead_created: bool = False
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"x-llm-intent": "lead_direct_message_result",
|
||||
"x-when-to-use": [
|
||||
"Confirm lead routing outcome for a direct message request.",
|
||||
],
|
||||
"x-when-not-to-use": [
|
||||
"Broadcast outcomes (use GatewayLeadBroadcastResponse)",
|
||||
],
|
||||
"x-required-actor": "gateway_main",
|
||||
"x-interpretation": "Use to confirm handoff path and recipient lead context.",
|
||||
},
|
||||
)
|
||||
|
||||
ok: bool = Field(default=True, description="Whether dispatch was accepted.")
|
||||
board_id: UUID = Field(description="Board receiving the message.")
|
||||
lead_agent_id: UUID | None = Field(
|
||||
default=None,
|
||||
description="Resolved lead agent id when present.",
|
||||
)
|
||||
lead_agent_name: str | None = Field(
|
||||
default=None,
|
||||
description="Resolved lead agent display name.",
|
||||
)
|
||||
lead_created: bool = Field(
|
||||
default=False,
|
||||
description="Whether a lead fallback actor was created during routing.",
|
||||
)
|
||||
|
||||
|
||||
class GatewayLeadBroadcastRequest(SQLModel):
|
||||
"""Request payload for broadcasting a message to multiple board leads."""
|
||||
|
||||
kind: Literal["question", "handoff"] = "question"
|
||||
correlation_id: str | None = None
|
||||
content: NonEmptyStr
|
||||
board_ids: list[UUID] | None = None
|
||||
reply_tags: list[str] = Field(default_factory=_lead_reply_tags)
|
||||
reply_source: str | None = "lead_to_gateway_main"
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"x-llm-intent": "lead_broadcast_message",
|
||||
"x-when-to-use": [
|
||||
"Multiple board leads need the same message",
|
||||
"Coordinating cross-board operational alerts",
|
||||
],
|
||||
"x-when-not-to-use": [
|
||||
"Single lead response required (use direct message)",
|
||||
"Personalized board-level instruction from agent context",
|
||||
],
|
||||
"x-required-actor": "main_agent",
|
||||
"x-response-shape": "GatewayLeadBroadcastResponse",
|
||||
},
|
||||
)
|
||||
|
||||
kind: Literal["question", "handoff"] = Field(
|
||||
default="question",
|
||||
description="Broadcast intent. `question` asks for responses; `handoff` requests transfer.",
|
||||
examples=["question", "handoff"],
|
||||
)
|
||||
correlation_id: str | None = Field(
|
||||
default=None,
|
||||
description="Optional correlation token shared with downstream handlers.",
|
||||
examples=["broadcast-2026-02-14"],
|
||||
)
|
||||
content: NonEmptyStr = Field(
|
||||
description="Message content distributed to selected board leads.",
|
||||
examples=["Board-wide incident: prioritize risk triage on task set 14."],
|
||||
)
|
||||
board_ids: list[UUID] | None = Field(
|
||||
default=None,
|
||||
description="Optional explicit list of board IDs; omit for lead-scoped defaults.",
|
||||
examples=[[ "11111111-1111-1111-1111-111111111111" ]],
|
||||
)
|
||||
reply_tags: list[str] = Field(
|
||||
default_factory=_lead_reply_tags,
|
||||
description="Tags required by reply templates when each lead responds.",
|
||||
examples=[["gateway_main", "lead_reply"]],
|
||||
)
|
||||
reply_source: str | None = Field(
|
||||
default="lead_to_gateway_main",
|
||||
description="Reply destination key for broadcast responses.",
|
||||
examples=["lead_to_gateway_main"],
|
||||
)
|
||||
|
||||
|
||||
class GatewayLeadBroadcastBoardResult(SQLModel):
|
||||
"""Per-board result entry for a lead broadcast operation."""
|
||||
|
||||
board_id: UUID
|
||||
lead_agent_id: UUID | None = None
|
||||
lead_agent_name: str | None = None
|
||||
ok: bool = False
|
||||
error: str | None = None
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"x-llm-intent": "lead_broadcast_status",
|
||||
"x-when-to-use": [
|
||||
"Reading per-board outcomes for retries/follow-up workflows",
|
||||
],
|
||||
"x-when-not-to-use": ["Global summary checks should use parent broadcast response"],
|
||||
"x-interpretation": "Use this result object as a transport status for one board.",
|
||||
},
|
||||
)
|
||||
|
||||
board_id: UUID = Field(description="Target board id for this result.")
|
||||
lead_agent_id: UUID | None = Field(
|
||||
default=None,
|
||||
description="Resolved lead agent id for the target board.",
|
||||
)
|
||||
lead_agent_name: str | None = Field(
|
||||
default=None,
|
||||
description="Resolved lead agent display name.",
|
||||
)
|
||||
ok: bool = Field(default=False, description="Whether this board delivery succeeded.")
|
||||
error: str | None = Field(
|
||||
default=None,
|
||||
description="Failure reason if this board failed.",
|
||||
)
|
||||
|
||||
|
||||
class GatewayLeadBroadcastResponse(SQLModel):
|
||||
"""Aggregate response for a lead broadcast operation."""
|
||||
|
||||
ok: bool = True
|
||||
sent: int = 0
|
||||
failed: int = 0
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"x-llm-intent": "lead_broadcast_summary",
|
||||
"x-when-to-use": [
|
||||
"Inspect final counters after attempting a multi-board send.",
|
||||
],
|
||||
"x-when-not-to-use": [
|
||||
"Single-board directed lead message (use GatewayLeadMessageResponse)",
|
||||
],
|
||||
"x-required-actor": "lead_agent_or_router",
|
||||
"x-interpretation": "Use sent/failed counters before considering retry logic.",
|
||||
"x-response-shape": "List of GatewayLeadBroadcastBoardResult",
|
||||
},
|
||||
)
|
||||
|
||||
ok: bool = Field(default=True, description="Whether broadcast execution succeeded.")
|
||||
sent: int = Field(default=0, description="Number of boards successfully messaged.")
|
||||
failed: int = Field(default=0, description="Number of boards that failed messaging.")
|
||||
results: list[GatewayLeadBroadcastBoardResult] = Field(default_factory=list)
|
||||
|
||||
|
||||
class GatewayMainAskUserRequest(SQLModel):
|
||||
"""Request payload for asking the end user via a main gateway agent."""
|
||||
|
||||
correlation_id: str | None = None
|
||||
content: NonEmptyStr
|
||||
preferred_channel: str | None = None
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"x-llm-intent": "human_escalation_request",
|
||||
"x-when-to-use": [
|
||||
"Blocking decision requires explicit user input",
|
||||
"Task flow requires preference confirmation or permission",
|
||||
],
|
||||
"x-required-actor": "lead_agent",
|
||||
"x-response-shape": "GatewayMainAskUserResponse",
|
||||
},
|
||||
)
|
||||
|
||||
correlation_id: str | None = Field(
|
||||
default=None,
|
||||
description="Optional correlation token for tracing request/response flow.",
|
||||
examples=["ask-user-001"],
|
||||
)
|
||||
content: NonEmptyStr = Field(
|
||||
description="Prompt that should be asked to the human.",
|
||||
examples=["Can we proceed with the proposed vendor budget increase?"],
|
||||
)
|
||||
preferred_channel: str | None = Field(
|
||||
default=None,
|
||||
description="Optional preferred messaging channel.",
|
||||
examples=["chat", "email"],
|
||||
)
|
||||
|
||||
# How the main agent should reply back into Mission Control
|
||||
# (defaults interpreted by templates).
|
||||
reply_tags: list[str] = Field(default_factory=_user_reply_tags)
|
||||
reply_source: str | None = "user_via_gateway_main"
|
||||
reply_tags: list[str] = Field(
|
||||
default_factory=_user_reply_tags,
|
||||
description="Tags required for routing the user response.",
|
||||
examples=[["gateway_main", "user_reply"]],
|
||||
)
|
||||
reply_source: str | None = Field(
|
||||
default="user_via_gateway_main",
|
||||
description="Reply destination key for user confirmation loops.",
|
||||
examples=["user_via_gateway_main"],
|
||||
)
|
||||
|
||||
|
||||
class GatewayMainAskUserResponse(SQLModel):
|
||||
"""Response payload for user-question dispatch via gateway main agent."""
|
||||
|
||||
ok: bool = True
|
||||
board_id: UUID
|
||||
main_agent_id: UUID | None = None
|
||||
main_agent_name: str | None = None
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"x-llm-intent": "human_escalation_result",
|
||||
"x-when-to-use": [
|
||||
"Track completion and main-agent handoff after human escalation request.",
|
||||
],
|
||||
"x-when-not-to-use": [
|
||||
"Regular lead routing outcomes (use lead message/broadcast responses)",
|
||||
],
|
||||
"x-required-actor": "lead_agent",
|
||||
"x-interpretation": "Track whether ask was accepted and which main agent handled it.",
|
||||
},
|
||||
)
|
||||
|
||||
ok: bool = Field(default=True, description="Whether ask-user dispatch was accepted.")
|
||||
board_id: UUID = Field(description="Board context used for the request.")
|
||||
main_agent_id: UUID | None = Field(
|
||||
default=None,
|
||||
description="Resolved main agent id handling the ask.",
|
||||
)
|
||||
main_agent_name: str | None = Field(
|
||||
default=None,
|
||||
description="Resolved main agent display name.",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user