250 lines
8.8 KiB
Go
250 lines
8.8 KiB
Go
|
|
package bot
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/json"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"github.com/google/uuid"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Session is the service-layer representation of a chatbot session.
|
|||
|
|
// Handlers translate this into BotSessionResponse for the wire. Fields
|
|||
|
|
// match plan §3 / §4: lastTurn = last_terminal_ordinal (the highest
|
|||
|
|
// ordinal whose status is not in_flight), never the in-flight ordinal.
|
|||
|
|
type Session struct {
|
|||
|
|
ID uuid.UUID
|
|||
|
|
ClientID string
|
|||
|
|
CreatedBy string
|
|||
|
|
Title string
|
|||
|
|
State json.RawMessage
|
|||
|
|
LastTurn int32
|
|||
|
|
CreatedAt time.Time
|
|||
|
|
UpdatedAt time.Time
|
|||
|
|
Scope SessionScope
|
|||
|
|
RecentTurns []TurnPreview
|
|||
|
|
IsDeleted bool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SessionScope is the persisted (documents, folders) tuple. Returned as
|
|||
|
|
// id arrays per plan §3 — folder expansion to documents lives in the
|
|||
|
|
// M3 FastAPI payload assembly path, not here.
|
|||
|
|
type SessionScope struct {
|
|||
|
|
DocumentIDs []uuid.UUID
|
|||
|
|
FolderIDs []uuid.UUID
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TurnPreview is a single recent-turn row surfaced in the session-list
|
|||
|
|
// response. M2 returns the columns the UI needs for a preview card.
|
|||
|
|
type TurnPreview struct {
|
|||
|
|
Ordinal int32
|
|||
|
|
Prompt string
|
|||
|
|
Completion *string
|
|||
|
|
Status string
|
|||
|
|
CreatedAt time.Time
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ListSessionsInput drives ListSessionsForOwner / ListSessionsForSuperAdmin.
|
|||
|
|
// Limit and Offset are validated up-front by the handler; the service
|
|||
|
|
// re-validates as defense in depth so non-HTTP callers cannot bypass
|
|||
|
|
// the [1, 200] / >= 0 bounds plan §3 mandates.
|
|||
|
|
type ListSessionsInput struct {
|
|||
|
|
Limit int
|
|||
|
|
Offset int
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PatchScopeInput carries the body of PATCH /scope. The service
|
|||
|
|
// deduplicates each array, rejects an id that appears in both add and
|
|||
|
|
// remove sets, and rejects cross-client ids.
|
|||
|
|
type PatchScopeInput struct {
|
|||
|
|
AddDocuments []uuid.UUID
|
|||
|
|
RemoveDocuments []uuid.UUID
|
|||
|
|
AddFolders []uuid.UUID
|
|||
|
|
RemoveFolders []uuid.UUID
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------- M3: FastAPI agent service wire types ----------
|
|||
|
|
//
|
|||
|
|
// Field shapes are pinned by plans/chatbot.stuff/openapi.chatbot.swagger.json
|
|||
|
|
// (the "Aaria Agent Service" v0.1.0 contract). Plan §7 defines the
|
|||
|
|
// request/response mapping; these structs serialize 1:1 to the swagger
|
|||
|
|
// schema with snake_case JSON tags.
|
|||
|
|
|
|||
|
|
// ChatRequest is the body sent to POST /agent/chat. Plan §7 mapping
|
|||
|
|
// table: request_id is "{sessionId}:{ordinal}" stable across retries;
|
|||
|
|
// user_id is the Cognito sub treated as opaque telemetry; session_id is
|
|||
|
|
// bot_sessions.id as a string; conversation_history is the last N
|
|||
|
|
// completed turns × 2 messages; session_state is bot_sessions.state from
|
|||
|
|
// tx1 (nil when the session has no state); scope is the assembled
|
|||
|
|
// document/folder set.
|
|||
|
|
type ChatRequest struct {
|
|||
|
|
RequestID string `json:"request_id"`
|
|||
|
|
UserID string `json:"user_id"`
|
|||
|
|
SessionID string `json:"session_id"`
|
|||
|
|
Message string `json:"message"`
|
|||
|
|
ConversationHistory []ConversationMessage `json:"conversation_history"`
|
|||
|
|
SessionState *SessionState `json:"session_state,omitempty"`
|
|||
|
|
Scope ChatScope `json:"scope"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ChatResponse is the body returned by POST /agent/chat. Plan §7
|
|||
|
|
// mandates that updated_session_state == null preserves the prior state
|
|||
|
|
// and an explicit {} overwrites it; the *json.RawMessage type is the
|
|||
|
|
// standard Go idiom for distinguishing those two cases at unmarshal.
|
|||
|
|
type ChatResponse struct {
|
|||
|
|
RequestID string `json:"request_id"`
|
|||
|
|
Status string `json:"status"`
|
|||
|
|
Answer *Answer `json:"answer,omitempty"`
|
|||
|
|
UpdatedSessionState *json.RawMessage `json:"updated_session_state,omitempty"`
|
|||
|
|
Metadata *Metadata `json:"metadata,omitempty"`
|
|||
|
|
Error *ErrorPayload `json:"error,omitempty"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ChatScope is the assembled document/folder payload sent to FastAPI.
|
|||
|
|
// Both fields are emitted as non-null arrays (the swagger marks them
|
|||
|
|
// required) — empty input produces "[]", not "null".
|
|||
|
|
type ChatScope struct {
|
|||
|
|
DocumentIDs []string `json:"document_ids"`
|
|||
|
|
FolderIDs []string `json:"folder_ids"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ConversationMessage is one entry in conversation_history. Plan §7:
|
|||
|
|
// each completed turn produces a (user, assistant) pair with the turn's
|
|||
|
|
// created_at as the timestamp.
|
|||
|
|
type ConversationMessage struct {
|
|||
|
|
Role string `json:"role"`
|
|||
|
|
Content string `json:"content"`
|
|||
|
|
Timestamp time.Time `json:"timestamp"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Answer is the agent's response payload. v1 plan §3 mandates that
|
|||
|
|
// citations live in answer.text markdown; structured_data and
|
|||
|
|
// follow_up_suggestions are response-only fields (logged or surfaced,
|
|||
|
|
// not persisted).
|
|||
|
|
type Answer struct {
|
|||
|
|
Text string `json:"text"`
|
|||
|
|
ResultType string `json:"result_type"`
|
|||
|
|
StructuredData json.RawMessage `json:"structured_data,omitempty"`
|
|||
|
|
FollowUpSuggestions []string `json:"follow_up_suggestions,omitempty"`
|
|||
|
|
Confidence float64 `json:"confidence"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Metadata is the per-call telemetry from the agent service. The
|
|||
|
|
// authoritative swagger declares Agents as a MAP keyed by agent name
|
|||
|
|
// (NOT an array). SummarizeMetadata sums tokens by iterating map values.
|
|||
|
|
type Metadata struct {
|
|||
|
|
TotalLatencyMs int `json:"total_latency_ms"`
|
|||
|
|
RetryCount int `json:"retry_count"`
|
|||
|
|
Agents map[string]AgentTelemetry `json:"agents"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// AgentTelemetry is one entry in the Metadata.Agents map. Passed is a
|
|||
|
|
// pointer so the field can round-trip null → unset as the swagger allows.
|
|||
|
|
type AgentTelemetry struct {
|
|||
|
|
LatencyMs int `json:"latency_ms"`
|
|||
|
|
InputTokens int `json:"input_tokens"`
|
|||
|
|
OutputTokens int `json:"output_tokens"`
|
|||
|
|
Passed *bool `json:"passed,omitempty"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ErrorPayload is the error-shape returned by FastAPI for a 200 response
|
|||
|
|
// with status:"error" or carried alongside the error sentinel surfaced
|
|||
|
|
// by the client when the upstream fails the application path.
|
|||
|
|
type ErrorPayload struct {
|
|||
|
|
Code string `json:"code"`
|
|||
|
|
Message string `json:"message"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// HealthResponse is the body returned by GET /health. Plan §7 mandates
|
|||
|
|
// status == "ok" plus non-empty service and version.
|
|||
|
|
type HealthResponse struct {
|
|||
|
|
Status string `json:"status"`
|
|||
|
|
Service string `json:"service"`
|
|||
|
|
Version string `json:"version"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SessionState is the agent-side session-state object. The swagger
|
|||
|
|
// defines it as an open object with no enumerated keys; we use a
|
|||
|
|
// json.RawMessage alias so the client passes the bytes through
|
|||
|
|
// untouched and the M4 tx2 path can apply StripCachedResults /
|
|||
|
|
// SessionStateOversize gates without re-decoding. M4 may refine if
|
|||
|
|
// structured access becomes useful.
|
|||
|
|
type SessionState = json.RawMessage
|
|||
|
|
|
|||
|
|
// ChatCallResult is what FastAPIClient.Chat returns on success. The
|
|||
|
|
// service layer reads SentAt/ReceivedAt for the wall-clock fallback when
|
|||
|
|
// metadata is absent (plan §7 mapping table) and AttemptCount when
|
|||
|
|
// persisting bot_turns.error per plan §6.
|
|||
|
|
type ChatCallResult struct {
|
|||
|
|
Response *ChatResponse
|
|||
|
|
SentAt time.Time
|
|||
|
|
ReceivedAt time.Time
|
|||
|
|
AttemptCount int
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// AttemptCounter is implemented by every error returned by
|
|||
|
|
// FastAPIClient.Chat so the service layer can record the attempt count
|
|||
|
|
// in bot_turns.error without unwrapping driver-specific types. Plan §7
|
|||
|
|
// mandates the error path expose AttemptCount alongside the result path.
|
|||
|
|
type AttemptCounter interface {
|
|||
|
|
error
|
|||
|
|
AttemptCount() int
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------- M4: AddTurn types ----------
|
|||
|
|
|
|||
|
|
// AddTurnInput drives Service.AddTurn. Caller specifies Ordinal: the
|
|||
|
|
// handler computes it from session state via prompt-match semantics
|
|||
|
|
// before calling the service; the concurrency test passes Ordinal
|
|||
|
|
// directly. Plan §6 algorithm is by-ordinal.
|
|||
|
|
type AddTurnInput struct {
|
|||
|
|
SessionID uuid.UUID
|
|||
|
|
ClientID string
|
|||
|
|
Actor string
|
|||
|
|
Ordinal int
|
|||
|
|
Prompt string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// AddTurnResult is the per-call outcome the handler converts into
|
|||
|
|
// BotTurnResponse. Status is one of the bot_turns.status enum values.
|
|||
|
|
// Error is the persisted error JSON when status in (errored, abandoned,
|
|||
|
|
// session_deleted), nil otherwise.
|
|||
|
|
type AddTurnResult struct {
|
|||
|
|
Ordinal int32
|
|||
|
|
Status string
|
|||
|
|
Completion *string
|
|||
|
|
LatencyMs int
|
|||
|
|
TokensIn int
|
|||
|
|
TokensOut int
|
|||
|
|
Error []byte
|
|||
|
|
AttemptCount int
|
|||
|
|
CreatedAt time.Time
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// CompleteTurnInput is the argument shape for Service.CompleteBotTurnWithAttempt.
|
|||
|
|
// Plan §11 stale-tx2 test exercises this directly so a stale callback
|
|||
|
|
// (whose AttemptID does not match the persisted row) returns
|
|||
|
|
// ErrTurnSuperseded.
|
|||
|
|
type CompleteTurnInput struct {
|
|||
|
|
SessionID uuid.UUID
|
|||
|
|
Ordinal int
|
|||
|
|
AttemptID uuid.UUID
|
|||
|
|
Completion string
|
|||
|
|
LatencyMs int
|
|||
|
|
TokensIn int
|
|||
|
|
TokensOut int
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Turn is the service-layer representation of one bot_turns row. The
|
|||
|
|
// handler converts this into BotTurnResponse for the wire.
|
|||
|
|
type Turn struct {
|
|||
|
|
Ordinal int32
|
|||
|
|
Prompt string
|
|||
|
|
Completion *string
|
|||
|
|
Status string
|
|||
|
|
LatencyMs *int32
|
|||
|
|
TokensIn *int32
|
|||
|
|
TokensOut *int32
|
|||
|
|
Error []byte
|
|||
|
|
CreatedAt time.Time
|
|||
|
|
}
|