Files

155 lines
4.4 KiB
Python

from dataclasses import dataclass
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
class AgentRequestMetadata(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
locale: str | None = None
client_version: str | None = None
ui_entry: str | None = None
class AgentRequest(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
agent_type: str
message: str
session_id: str
idempotency_key: str
metadata: AgentRequestMetadata = Field(default_factory=AgentRequestMetadata)
@field_validator("message")
@classmethod
def message_must_not_be_blank(cls, value: str) -> str:
if not value.strip():
raise ValueError("message must not be blank")
return value
@field_validator("idempotency_key")
@classmethod
def idempotency_key_must_be_valid(cls, value: str) -> str:
if not 16 <= len(value) <= 128 or not value.replace("-", "").replace("_", "").isalnum():
raise ValueError("idempotency_key must be 16-128 alphanumeric characters")
return value
class RequestContext(BaseModel):
model_config = ConfigDict(frozen=True)
user_id: str
trace_id: str
roles: tuple[str, ...] = ()
customer_ids: tuple[str, ...] = ()
data_scope: str = "self"
portal: str = "api"
clarification_round: int = Field(default=0, ge=0, le=10)
permissions: tuple[str, ...] = ()
permission_scopes: dict[str, str] = Field(default_factory=dict)
class AgentDefinition(BaseModel):
model_config = ConfigDict(frozen=True)
agent_type: str
version: str
allowed_tools: tuple[str, ...] = ()
allowed_roles: tuple[str, ...] = ()
allowed_portals: tuple[str, ...] = ()
supported_intents: tuple[str, ...] = ("general",)
intent_descriptions: dict[str, str] = Field(default_factory=dict)
@model_validator(mode="after")
def validate_intent_descriptions(self) -> "AgentDefinition":
unknown = set(self.intent_descriptions).difference(self.supported_intents)
if unknown:
raise ValueError(f"intent descriptions contain unsupported intents: {sorted(unknown)}")
return self
class ResolvedAgentConfig(BaseModel):
model_config = ConfigDict(frozen=True)
config_version: str
prompt_version: str
model_endpoint: str
allowed_tools: tuple[str, ...] = ()
timeout_seconds: int = Field(default=60, gt=0)
release_id: int | None = None
allowed_tools_by_intent: dict[str, tuple[str, ...]] = Field(default_factory=dict)
negative_rules: tuple[tuple[str, str], ...] = ()
class RecalledMemory(BaseModel):
model_config = ConfigDict(frozen=True)
memory_uuid: str
customer_id: str
content: str
class SourceReference(BaseModel):
model_config = ConfigDict(frozen=True)
source_type: Literal["knowledge", "memory", "relationship", "tool"]
source_id: str
title: str | None = None
score: float | None = Field(default=None, ge=0, le=1)
class ToolCallRecord(BaseModel):
model_config = ConfigDict(frozen=True)
tool_name: str
status: Literal["succeeded", "failed", "denied"]
input_summary: dict[str, Any] = Field(default_factory=dict)
output_summary: dict[str, Any] = Field(default_factory=dict)
class IntentResult(BaseModel):
model_config = ConfigDict(frozen=True)
intent: str
confidence: float = Field(ge=0, le=1)
needs_clarification: bool = False
class CoreResult(BaseModel):
model_config = ConfigDict(frozen=True)
text: str
intent: IntentResult | None = None
source_references: tuple[SourceReference, ...] = ()
tool_calls: tuple[ToolCallRecord, ...] = ()
transfer_required: bool = False
transfer_reason: str | None = None
class AgentResult(BaseModel):
model_config = ConfigDict(frozen=True)
run_id: str
result: CoreResult
usage: dict[str, int] = Field(default_factory=dict)
class RunProgressEvent(BaseModel):
model_config = ConfigDict(frozen=True)
event_type: Literal["start", "tools", "delta", "replace", "done", "error"]
run_id: str
payload: dict[str, Any] = Field(default_factory=dict)
@dataclass(frozen=True)
class DomainEvent:
event_id: str
event_type: str
aggregate_type: str
aggregate_id: str
trace_id: str
payload: dict[str, Any]
occurred_at: datetime