diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..c1801ee --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,38 @@ +name: Deploy Docs to GitHub Pages + +on: + push: + branches: + - docs # Deploy when pushing to docs branch + workflow_dispatch: # Allow manual trigger + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: 'docs' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..e12f213 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,5 @@ +theme: jekyll-theme-cayman +title: Twilio Agent Connect Python SDK +description: Official Python SDK for Twilio Agent Connect + +markdown: kramdown diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..7552d9f --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,4974 @@ + + +# tac.core.tac + + + +## TAC Objects + +```python +class TAC() +``` + +Main Twilio Agent Connect class for processing webhook events with configuration. + +This class accepts configuration and provides methods to process webhook events. + + + +#### \_\_init\_\_ + +```python +def __init__(config: TACConfig | dict[str, Any]) +``` + +Initialize TAC instance with configuration. + +**Arguments**: + +- `config` - TACConfig instance or dictionary with configuration parameters. + + + +#### is\_orchestrator\_enabled + +```python +def is_orchestrator_enabled() -> bool +``` + +True if TAC is configured with Conversation Orchestrator (not relay-only mode). + + + +#### retrieve\_memory + +```python +async def retrieve_memory(conversation_context: ConversationSession, + query: str | None = None) -> TACMemoryResponse +``` + +Retrieve memories from Memory Store with fallback to Conversation Orchestrator. + +Three-tier resolution: +1. Memory API (when conversation_memory_client is configured). +2. Conversation Orchestrator list_communications fallback (when CO is configured). +3. Empty TACMemoryResponse (relay-only mode). + +**Arguments**: + +- `conversation_context` - Session containing conversation and profile information. +- `query` - Optional search query to filter memories. + + +**Returns**: + + Memory response containing conversation history and profile data. + + + +#### process\_cintel\_event + +```python +async def process_cintel_event( + payload: dict[str, Any]) -> OperatorProcessingResult +``` + +Process Conversation Intelligence webhook and create observations/summaries in Memory. + +**Arguments**: + +- `payload` - Webhook payload from Conversation Intelligence service. + + +**Returns**: + + Processing result with created observations and summaries. + + + +#### on\_message\_ready + +```python +def on_message_ready(callback: ( + Callable[[str, ConversationSession, TACMemoryResponse | None], str | None] + | Callable[[str, ConversationSession, TACMemoryResponse | None], + Awaitable[str | None]])) -> None +``` + +Register callback invoked when a message is ready. + +Callback can return a string (TAC auto-sends to channel) or None (manual handling). + +**Example**: + + ```python + async def handle_message( + message: str, context: ConversationSession, memory: TACMemoryResponse | None + ) -> str: + response = await openai_client.responses.create(...) + return response.output_text # TAC routes to appropriate channel + + + tac.on_message_ready(handle_message) + ``` + + +**Arguments**: + +- `callback` - Function with (message, context, memory). Returns str or None. + + + +#### on\_interrupt + +```python +def on_interrupt(callback: (Callable[[ConversationSession, Any], None] + | Callable[[ConversationSession, Any], + Awaitable[None]])) -> None +``` + +Register callback invoked on user interrupt. + +**Example**: + + ```python + def handle_interrupt(context: ConversationSession, interrupt_data: Any): + # Handle user interrupt... + pass + + + tac.on_interrupt(handle_interrupt) + ``` + + +**Arguments**: + +- `callback` - Function to call with (context, interrupt_data). Supports sync and async. + + + +#### on\_conversation\_ended + +```python +def on_conversation_ended(callback: (Callable[[ConversationSession], None] + | Callable[[ConversationSession], + Awaitable[None]])) -> None +``` + +Register callback invoked when conversation ends. + +**Example**: + + ```python + def handle_conversation_ended(context: ConversationSession): + # Clean up conversation... + pass + + + tac.on_conversation_ended(handle_conversation_ended) + ``` + + +**Arguments**: + +- `callback` - Function to call with conversation context. Supports sync and async. + + + +#### register\_partner\_connector + +```python +def register_partner_connector(connector: PartnerConnector, + package_version: str) -> None +``` + +Tag outbound Twilio requests with a partner connector identifier. + +Partner packages built on top of TAC (e.g. ``tac_aws``, ``tac_azure``) +call this from their connector's ``__init__`` to append a suffix to +the User-Agent header of every outbound Twilio request. + +**Arguments**: + +- `connector` - A :class:`PartnerConnector` enum value identifying the + partner package and connector. +- `package_version` - Version string of the partner package (e.g. + ``"0.1.0"``). + + +**Example**: + + ```python + from tac import PartnerConnector + + tac.register_partner_connector(PartnerConnector.AZURE_AGENT_FRAMEWORK, "0.1.0") + ``` + + + +#### trigger\_message\_ready + +```python +async def trigger_message_ready( + user_message: str, + conversation_context: ConversationSession, + memory_response: TACMemoryResponse | None = None) -> str | None +``` + +Trigger the registered message ready callback. + +**Arguments**: + +- `user_message` - User's message text. +- `conversation_context` - Session containing conversation information. +- `memory_response` - Optional memory data to pass to callback. + + +**Returns**: + + Response string if callback returns one (for auto-send), None otherwise. + + +**Raises**: + +- `TypeError` - If callback returns a value that is neither None nor str. + + + +#### trigger\_interrupt + +```python +def trigger_interrupt(conversation_context: ConversationSession, + interrupt_data: Any) -> None +``` + +Trigger the registered interrupt callback. + +**Arguments**: + +- `conversation_context` - Session containing conversation information. +- `interrupt_data` - Interrupt event data from voice channel. + + + +#### trigger\_conversation\_ended + +```python +async def trigger_conversation_ended( + conversation_context: ConversationSession) -> None +``` + +Trigger the registered conversation ended callback. + +**Arguments**: + +- `conversation_context` - Session containing conversation information. + + + +# tac.core.config + +Configuration models for the Twilio Agent Connect. + + + +## ConversationIntelligenceConfig Objects + +```python +class ConversationIntelligenceConfig(BaseModel) +``` + +Configuration for Conversation Intelligence webhook filtering. + +This config specifies which CI configuration and operators to process. +Events that don't match are filtered out. + + + +#### from\_env + +```python +@classmethod +def from_env(cls) -> "ConversationIntelligenceConfig | None" +``` + +Create ConversationIntelligenceConfig from CONVERSATION_INTELLIGENCE_* env vars. + + + +## TwilioMemoryConfig Objects + +```python +class TwilioMemoryConfig(BaseModel) +``` + +Configuration for Twilio Memory Store integration. + +Controls memory retrieval limits, relevance filtering, and profile trait groups. +Memory client is auto-initialized from Conversation Orchestrator configuration. + + + +#### from\_env + +```python +@classmethod +def from_env(cls) -> "TwilioMemoryConfig" +``` + +Create TwilioMemoryConfig from TWILIO_MEMORY_* environment variables. + + + +## TACConfig Objects + +```python +class TACConfig(BaseModel) +``` + +Configuration model for Twilio Agent Connect settings. + + + +#### from\_env + +```python +@classmethod +def from_env(cls) -> "TACConfig" +``` + +Create TACConfig from environment variables. + +Required: +- TWILIO_ACCOUNT_SID: Twilio Account SID +- TWILIO_AUTH_TOKEN: Twilio Auth Token for API authentication +- TWILIO_API_KEY: Twilio API Key SID (starts with SK) +- TWILIO_API_SECRET: Twilio API Secret for API Key authentication +- TWILIO_PHONE_NUMBER: Phone number for voice and SMS channels + +Required for Conversation Orchestrator / Memory / Knowledge: +- TWILIO_CONVERSATION_CONFIGURATION_ID: Conversation Orchestrator configuration ID + (when omitted, TAC runs in ConversationRelay-only mode) + +Optional: +- TWILIO_RCS_SENDER_ID: RCS Sender ID for RCS channel +- TWILIO_WHATSAPP_NUMBER: WhatsApp-enabled phone number + (format: whatsapp:+1234567890) +- TWILIO_KNOWLEDGE_BASE_ID: Knowledge Base ID for RAG search functionality +- TWILIO_LOG_LEVEL: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL). + Default: INFO +- TWILIO_REGION: Twilio region for data residency (e.g., 'au1', 'ie1') +- TWILIO_STUDIO_HANDOFF_FLOW_SID: Studio Flow SID (FWxxx...) for handoff tool + +Memory Configuration: +- TWILIO_MEMORY_PROFILE_TRAIT_GROUPS: Trait groups to include + (comma-separated, e.g., "Contact,Preferences") +- TWILIO_MEMORY_OBSERVATIONS_LIMIT: Max observations in memory retrieval. + Default: 20 +- TWILIO_MEMORY_SUMMARIES_LIMIT: Max summaries in memory retrieval. Default: 5 +- TWILIO_MEMORY_COMMUNICATIONS_LIMIT: Max communications in memory retrieval. + Default: 0 +- TWILIO_MEMORY_RELEVANCE_THRESHOLD: Min relevance score (0.0-1.0). Default: 0.0 + +Conversation Intelligence: +- CONVERSATION_INTELLIGENCE_CONFIGURATION_ID: CI Service configuration ID + for webhook filtering +- CONVERSATION_INTELLIGENCE_OBSERVATION_OPERATOR_SID: Operator SID for + observation extraction +- CONVERSATION_INTELLIGENCE_SUMMARY_OPERATOR_SID: Operator SID for summary + extraction + + + +# tac.core.logging + +Structured logging configuration for the Twilio Agent Connect. + + + +## JSONFormatter Objects + +```python +class JSONFormatter(logging.Formatter) +``` + +JSON formatter for structured logging using only stdlib. + + + +#### \_\_init\_\_ + +```python +def __init__(*args: Any, **kwargs: Any) -> None +``` + +Initialize JSON formatter. + + + +#### format + +```python +def format(record: logging.LogRecord) -> str +``` + +Format log record as JSON. + +**Arguments**: + +- `record` - Log record to format + + +**Returns**: + + JSON-formatted log string + + + +## ConsoleFormatter Objects + +```python +class ConsoleFormatter(logging.Formatter) +``` + +Human-readable console formatter with context support. + + + +#### format + +```python +def format(record: logging.LogRecord) -> str +``` + +Format log record for console output with context. + +**Arguments**: + +- `record` - Log record to format + + +**Returns**: + + Formatted log string + + + +## ContextLogger Objects + +```python +class ContextLogger() +``` + +Logger wrapper that binds context to all log calls. + + + +#### \_\_init\_\_ + +```python +def __init__(logger: logging.Logger, **context: Any) +``` + +Initialize context logger. + +**Arguments**: + +- `logger` - Base logger instance +- `**context` - Context fields to bind to all log calls + + + +#### debug + +```python +def debug(msg: str, **extra: Any) -> None +``` + +Log debug message with context. + +**Arguments**: + +- `msg` - Log message +- `**extra` - Additional fields + + + +#### info + +```python +def info(msg: str, **extra: Any) -> None +``` + +Log info message with context. + +**Arguments**: + +- `msg` - Log message +- `**extra` - Additional fields + + + +#### warning + +```python +def warning(msg: str, **extra: Any) -> None +``` + +Log warning message with context. + +**Arguments**: + +- `msg` - Log message +- `**extra` - Additional fields + + + +#### error + +```python +def error(msg: str, exc_info: bool = False, **extra: Any) -> None +``` + +Log error message with context. + +**Arguments**: + +- `msg` - Log message +- `exc_info` - Include exception traceback +- `**extra` - Additional fields + + + +#### critical + +```python +def critical(msg: str, exc_info: bool = False, **extra: Any) -> None +``` + +Log critical message with context. + +**Arguments**: + +- `msg` - Log message +- `exc_info` - Include exception traceback +- `**extra` - Additional fields + + + +#### bind + +```python +def bind(**context: Any) -> "ContextLogger" +``` + +Create new logger with additional context. + +**Arguments**: + +- `**context` - Additional context fields to bind + + +**Returns**: + + New ContextLogger with merged context + + + +#### isEnabledFor + +```python +def isEnabledFor(level: int) -> bool +``` + +Check if logger is enabled for the given level. + +Note: Method name uses camelCase to match the standard library's logging.Logger API +for drop-in compatibility with code expecting a Logger interface. + +**Arguments**: + +- `level` - Logging level to check + + +**Returns**: + + True if logger is enabled for the level + + + +#### setup\_logging + +```python +def setup_logging(log_level: str = "INFO", + log_format: str = "json") -> logging.Logger +``` + +Configure structured logging for TAC framework. + +**Arguments**: + +- `log_level` - Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) +- `log_format` - Log format - 'json' for structured logs, 'console' for human-readable + + +**Returns**: + + Configured logger instance + + + +#### get\_logger + +```python +def get_logger(name: str, **context: Any) -> ContextLogger +``` + +Get a context-aware logger instance for a specific module. + +**Arguments**: + +- `name` - Logger name (typically __name__ from the calling module) +- `**context` - Initial context to bind (e.g., conversation_id, channel) + + +**Returns**: + + ContextLogger instance with bound context + + + +# tac.channels.base + +Base channel interface for TAC channels. + + + +## BaseChannel Objects + +```python +class BaseChannel(ABC) +``` + +Abstract base class for TAC channels. + +Channels handle protocol-specific webhook processing and response delivery +for different communication channels (SMS, Voice, etc.). + +This class provides common conversation lifecycle management that is shared +across all channel types. + + + +#### \_\_init\_\_ + +```python +def __init__(tac: TAC, + memory_mode: MemoryMode = "never", + dedup_capacity: int = 10000) +``` + +Initialize base channel. + +**Arguments**: + +- `tac` - TAC instance for memory/context operations +- `memory_mode` - Memory retrieval mode. Default is "never". + - "always": Retrieve memory for every message with the query string + - "once": Retrieve memory once at conversation start with empty query and cache it. + Cache is invalidated when conversation becomes INACTIVE. + - "never": Skip memory retrieval +- `dedup_capacity` - Maximum number of idempotency tokens to track for + webhook deduplication. Default 10000. Must be positive. + + + +#### process\_webhook + +```python +@abstractmethod +async def process_webhook(webhook_data: dict[str, Any], + idempotency_token: str | None = None) -> None +``` + +Process incoming webhook event from Twilio. + +This method should: +1. Parse and validate webhook data +2. Handle conversation lifecycle (start, message, end) +3. Trigger memory retrieval via TAC +4. Invoke registered callbacks + +**Arguments**: + +- `webhook_data` - Raw webhook event data from Twilio +- `idempotency_token` - Optional Twilio idempotency token from request headers + + + +#### send\_response + +```python +@abstractmethod +async def send_response(conversation_id: str, + response: str + | AsyncGenerator[str | dict[str, Any], None], + role: str | None = None) -> None +``` + +Send response back through the channel. + +Supports both simple string responses and streaming via async generators. + +**Arguments**: + +- `conversation_id` - Conversation ID to send response to +- `response` - Message content (string) or async generator for streaming +- `role` - Optional message role (e.g., 'assistant', 'user', 'system') + + + +#### get\_channel\_name + +```python +@abstractmethod +def get_channel_name() -> str +``` + +Get the channel name identifier. + +**Returns**: + + Channel name (e.g., 'sms', 'voice') + + + +#### get\_channel\_type\_upper + +```python +def get_channel_type_upper() -> str +``` + +Get uppercase channel type for webhook filtering. + +**Returns**: + + Uppercase channel type (e.g., 'SMS', 'VOICE') + + + +# tac.channels.messaging + +MessagingChannel base class for messaging channels (SMS, RCS, WhatsApp, Chat). + + + +## MessagingChannelConfig Objects + +```python +class MessagingChannelConfig(BaseModel) +``` + +Base configuration for messaging channels (SMS, RCS, WhatsApp, Chat). + +**Attributes**: + +- `dedup_capacity` - Maximum number of idempotency tokens to track. + Default 10000 is suitable for most applications. + Uses Twilio's i-twilio-idempotency-token header for deduplication. +- `memory_mode` - Memory retrieval mode. Default is "never". + - "always": Retrieve memory for every message with the query string + - "once": Retrieve memory once at conversation start with empty query and cache it. + Cache is invalidated when conversation becomes INACTIVE and is fetched + again the next time a message triggers memory retrieval after the + conversation becomes ACTIVE. + - "never": Skip memory retrieval + + + +## MessagingChannel Objects + +```python +class MessagingChannel(BaseChannel) +``` + +Abstract base class for messaging channels (SMS, RCS, WhatsApp, Chat). + +Provides shared webhook processing logic for channels that use +Conversation Orchestrator webhooks with COMMUNICATION_CREATED +and CONVERSATION_UPDATED event types. + +Subclasses must implement: +- is_default_agent_address(): Fast-path check for the channel's default agent address +- get_channel_type_upper(): Return uppercase channel type ("SMS", "RCS", "WHATSAPP", "CHAT") +- get_agent_address(conversation_id): Return the agent's ParticipantAddress for a conversation +- send_response(): Send messages back through the channel +- get_channel_name(): Return lowercase channel name ("sms", "rcs", "whatsapp", "chat") + +Subclass class attributes: +- reconcile_customer_type: If True, reconciliation will also promote a + channel-matching UNKNOWN participant (not owning the agent address) to + CUSTOMER. Set False for channels where the customer is identified + author-driven (e.g. chat). + + + +#### is\_default\_agent\_address + +```python +@abstractmethod +def is_default_agent_address(author_address: str) -> bool +``` + +Fast-path check: is the author address this channel's default agent address? + +For example, config.phone_number for SMS, config.rcs_sender_id for RCS, +config.whatsapp_number for WhatsApp, agent_address for Chat. + +**Arguments**: + +- `author_address` - The address of the message author + + +**Returns**: + + True if the address matches the channel's default agent address + + + +#### get\_channel\_type\_upper + +```python +@abstractmethod +def get_channel_type_upper() -> str +``` + +Return the uppercase channel type for webhook filtering. + +**Returns**: + + Channel type string (e.g., "SMS", "CHAT") + + + +#### get\_agent\_address + +```python +@abstractmethod +def get_agent_address(conversation_id: str) -> ParticipantAddress +``` + +Return the agent-side ParticipantAddress for this conversation. + +Used by `_reconcile_participants` to identify which participant (by +channel + address) represents the agent. May read from session state +(e.g. chat's per-conversation channelId) to build the address. + + + +#### process\_webhook + +```python +async def process_webhook(webhook_data: dict[str, Any], + idempotency_token: str | None = None) -> None +``` + +Process messaging channel webhook event and manage conversation lifecycle. + +Handles: +- COMMUNICATION_CREATED: Process incoming messages from customers +- CONVERSATION_UPDATED: Clean up when conversation is closed + +Note: Conversation tracking uses instance-local memory. In multi-instance +deployments, webhooks may route to a different instance, preventing cleanup. +See CLAUDE.md for horizontal scaling considerations. + +**Arguments**: + +- `webhook_data` - Raw webhook event data from Twilio +- `idempotency_token` - Optional Twilio idempotency token from request headers + + + +# tac.channels.sms + +SMS Channel implementation for TAC. + + + +## SMSChannelConfig Objects + +```python +class SMSChannelConfig(MessagingChannelConfig) +``` + +Configuration for SMS channel. + +Inherits dedup_capacity and memory_mode from MessagingChannelConfig. + + + +## SMSChannel Objects + +```python +class SMSChannel(MessagingChannel) +``` + +SMS Channel for handling SMS-based conversations. + +Inherits shared messaging channel webhook processing from MessagingChannel +and provides SMS-specific message sending and filtering. + + + +#### send\_response + +```python +async def send_response(conversation_id: str, + response: str + | AsyncGenerator[str | dict[str, Any], None], + role: str | None = None) -> None +``` + +Send SMS response using the Conversation Orchestrator Send API. + +Reads the agent and customer participant ids stashed on the session +by inbound reconciliation or outbound initiation. Missing ids are a +misuse — send_response is only expected to be called after an inbound +webhook (COMMUNICATION_CREATED → reconcile) or after +`initiate_outbound_conversation`, both of which populate the session. + +**Arguments**: + +- `conversation_id` - Conversation ID to send response to +- `response` - Message content (must be string for SMS) +- `role` - Optional message role (not used in SMS channel) + + +**Raises**: + +- `TypeError` - If response is not a string +- `RuntimeError` - If the session or participant ids are missing + + + +#### initiate\_outbound\_conversation + +```python +async def initiate_outbound_conversation( + options: InitiateMessagingConversationOptions +) -> InitiateConversationResult +``` + +Initiate an outbound SMS conversation. + +Creates a conversation via Conversation Orchestrator with inline +participants, then sends the initial message via the Actions API. +If an active conversation with the same addresses already exists +(group-by dedup), CO returns 409 and the existing conversation is reused. + + + +# tac.channels.rcs + +RCS Channel implementation for TAC. + + + +## RCSChannelConfig Objects + +```python +class RCSChannelConfig(MessagingChannelConfig) +``` + +Configuration for RCS channel. + +Inherits dedup_capacity and memory_mode from MessagingChannelConfig. + + + +## RCSChannel Objects + +```python +class RCSChannel(MessagingChannel) +``` + +RCS Channel for handling RCS-based conversations. + +Inherits shared messaging channel webhook processing from MessagingChannel +and provides RCS-specific message sending and filtering. + +RCS uses RCS Sender IDs configured in TACConfig (via TWILIO_RCS_SENDER_ID). + + + +#### is\_default\_agent\_address + +```python +def is_default_agent_address(author_address: str) -> bool +``` + +Check if the author address matches the configured RCS sender ID. + + + +#### get\_agent\_address + +```python +def get_agent_address(conversation_id: str) -> ParticipantAddress +``` + +Get the agent's participant address for this conversation. + + + +#### send\_response + +```python +async def send_response(conversation_id: str, + response: str + | AsyncGenerator[str | dict[str, Any], None], + role: str | None = None) -> None +``` + +Send RCS response using the Conversation Orchestrator Send API. + +Reads the agent and customer participant ids stashed on the session +by inbound reconciliation or outbound initiation. Missing ids are a +misuse — send_response is only expected to be called after an inbound +webhook (COMMUNICATION_CREATED → reconcile) or after +`initiate_outbound_conversation`, both of which populate the session. + +**Arguments**: + +- `conversation_id` - Conversation ID to send response to +- `response` - Message content (must be string for RCS) +- `role` - Optional message role (not used in RCS channel) + + +**Raises**: + +- `TypeError` - If response is not a string +- `RuntimeError` - If the session or participant ids are missing + + + +#### initiate\_outbound\_conversation + +```python +async def initiate_outbound_conversation( + options: InitiateMessagingConversationOptions +) -> InitiateConversationResult +``` + +Initiate an outbound RCS conversation. + +Creates a conversation via Conversation Orchestrator with inline +participants, then sends the initial message via the Actions API. +Uses the RCS sender ID from TACConfig as the from address. +If an active conversation with the same addresses already exists +(group-by dedup), CO returns 409 and the existing conversation is reused. + +**Arguments**: + +- `options` - Conversation initiation options (to address and message) + + +**Returns**: + + InitiateConversationResult with conversation_id and session + + +**Raises**: + +- `RuntimeError` - If rcs_sender_id is not configured + + + +# tac.channels.whatsapp + +WhatsApp Channel implementation for TAC. + + + +## WhatsAppChannelConfig Objects + +```python +class WhatsAppChannelConfig(MessagingChannelConfig) +``` + +Configuration for WhatsApp channel. + +Inherits dedup_capacity and memory_mode from MessagingChannelConfig. + + + +## WhatsAppChannel Objects + +```python +class WhatsAppChannel(MessagingChannel) +``` + +WhatsApp Channel for handling WhatsApp-based conversations. + +Inherits shared messaging channel webhook processing from MessagingChannel +and provides WhatsApp-specific message sending and filtering. + +WhatsApp uses WhatsApp sender phone numbers configured in TACConfig +(via TWILIO_WHATSAPP_NUMBER). Address format: whatsapp:+1234567890 + + + +#### is\_default\_agent\_address + +```python +def is_default_agent_address(author_address: str) -> bool +``` + +Check if the author address matches the configured WhatsApp number. + + + +#### get\_agent\_address + +```python +def get_agent_address(conversation_id: str) -> ParticipantAddress +``` + +Get the agent's participant address for this conversation. + + + +#### send\_response + +```python +async def send_response(conversation_id: str, + response: str + | AsyncGenerator[str | dict[str, Any], None], + role: str | None = None) -> None +``` + +Send WhatsApp response using the Conversation Orchestrator Send API. + +Reads the agent and customer participant ids stashed on the session +by inbound reconciliation or outbound initiation. Missing ids are a +misuse — send_response is only expected to be called after an inbound +webhook (COMMUNICATION_CREATED → reconcile) or after +`initiate_outbound_conversation`, both of which populate the session. + +**Arguments**: + +- `conversation_id` - Conversation ID to send response to +- `response` - Message content (must be string for WhatsApp) +- `role` - Optional message role (not used in WhatsApp channel) + + +**Raises**: + +- `TypeError` - If response is not a string +- `RuntimeError` - If the session or participant ids are missing + + + +#### initiate\_outbound\_conversation + +```python +async def initiate_outbound_conversation( + options: InitiateMessagingConversationOptions +) -> InitiateConversationResult +``` + +Initiate an outbound WhatsApp conversation. + +Creates a conversation via Conversation Orchestrator with inline +participants, then sends the initial message via the Actions API. +Uses the WhatsApp number from TACConfig as the from address. +If an active conversation with the same addresses already exists +(group-by dedup), CO returns 409 and the existing conversation is reused. + +**Arguments**: + +- `options` - Conversation initiation options (to address and message) + + +**Returns**: + + InitiateConversationResult with conversation_id and session + + +**Raises**: + +- `RuntimeError` - If whatsapp_number is not configured + + + +# tac.channels.chat + +Chat Channel implementation for TAC. + + + +## ChatChannelConfig Objects + +```python +class ChatChannelConfig(MessagingChannelConfig) +``` + +Configuration for Chat channel. + +**Attributes**: + +- `agent_address` - Chat agent identity string used to identify the bot's messages. + + + +## ChatChannel Objects + +```python +class ChatChannel(MessagingChannel) +``` + +Chat Channel for handling web chat conversations. + +Uses identity-based addressing instead of phone numbers. +Automatically creates AI_AGENT participant if needed (lazy creation) +and manages conversation lifecycle through Conversation Orchestrator webhooks. + + + +#### send\_response + +```python +async def send_response(conversation_id: str, + response: str + | AsyncGenerator[str | dict[str, Any], None], + role: str | None = None) -> None +``` + +Send chat response using the Conversation Orchestrator Send API. + +Reads the agent and customer participant ids stashed on the session +by inbound reconciliation or outbound initiation. Missing ids are a +misuse — send_response is only expected to be called after an inbound +webhook (COMMUNICATION_CREATED → reconcile) or after +`initiate_outbound_conversation`, both of which populate the session. + +**Arguments**: + +- `conversation_id` - Conversation ID to send response to +- `response` - Message content (must be string for Chat) +- `role` - Optional message role (not used in Chat channel) + + +**Raises**: + +- `TypeError` - If response is not a string +- `RuntimeError` - If the session, channel_id, or participant ids are missing + + + +#### initiate\_outbound\_conversation + +```python +async def initiate_outbound_conversation( + options: InitiateChatConversationOptions +) -> InitiateConversationResult +``` + +Initiate an outbound Chat conversation. + +Creates a conversation via Conversation Orchestrator with inline +participants, then sends the initial message via the Actions API. +If an active conversation with the same addresses already exists +(group-by dedup), CO returns 409 and the existing conversation is reused. + + + +# tac.channels.voice.config + +Voice channel configuration. + + + +## VoiceChannelConfig Objects + +```python +class VoiceChannelConfig(BaseModel) +``` + +Configuration for Voice channel. + +**Attributes**: + +- `session_manager` - SessionManager for tracking and canceling in-flight tasks. + Defaults to ThreadSafeSessionManager for automatic task cancellation on + interrupts and new prompts. Set to None only for debugging/testing. +- `memory_mode` - Memory retrieval mode. Default is "never". + - "always": Retrieve memory for every message with the query string + - "once": Retrieve memory once at conversation start with empty query and cache it. + Cache is invalidated when conversation becomes INACTIVE. + - "never": Skip memory retrieval + + + +# tac.channels.voice.channel + + + +## VoiceChannel Objects + +```python +class VoiceChannel(BaseChannel) +``` + +Voice Channel for handling voice-based conversations via WebSocket. + +Key features: +- TwiML generation for incoming calls (see twiml module) +- WebSocket connection management for real-time voice streaming +- Conversation lifecycle management (inherited from BaseChannel) +- Outbound call initiation + +This channel is framework-agnostic and accepts any WebSocket implementation +satisfying WebSocketProtocol. For a batteries-included FastAPI server, use +tac.server.TACFastAPIServer. + + + +#### \_\_init\_\_ + +```python +def __init__(tac: TAC, + config: VoiceChannelConfig | dict[str, Any] | None = None) +``` + +Initialize Voice channel for websocket protocol handling. + +**Arguments**: + +- `tac` - TAC instance for memory/context operations +- `config` - Voice channel configuration (VoiceChannelConfig or dict). + If None, uses default configuration. + + +**Examples**: + + >>> channel = VoiceChannel(tac, config={"memory_mode": "always"}) + >>> channel = VoiceChannel(tac, config=VoiceChannelConfig(session_manager=sm)) + >>> channel = VoiceChannel(tac) # Use defaults + + + +#### handle\_incoming\_call + +```python +async def handle_incoming_call(options: TwiMLOptions | dict[str, Any]) -> str +``` + +Generate TwiML response for incoming voice calls. + +ConversationRelay automatically handles conversation creation and participant +management via the conversation_configuration parameter. + +**Arguments**: + +- `options` - TwiML generation options (TwiMLOptions or dict) containing: + - websocket_url (required): WebSocket URL for ConversationRelay + - custom_parameters (optional): Additional custom parameters + - welcome_greeting (optional): Initial greeting message + - action_url (optional): URL for call completion webhook + + +**Returns**: + + TwiML XML string for call connection + + +**Example**: + + >>> twiml = await voice_channel.handle_incoming_call( + ... options={ + ... "websocket_url": "wss://example.com/ws", + ... "custom_parameters": {"session_id": "sess_123"}, + ... "welcome_greeting": "Hello!", + ... "action_url": "https://example.com/callback", + ... }, + ... ) + + + +#### handle\_conversation\_relay\_callback + +```python +async def handle_conversation_relay_callback( + payload_dict: dict[str, str]) -> None +``` + +Handle ConversationRelay callback webhook from Twilio. + +In relay-only mode, this is a secondary mechanism for cleaning up +conversation state when a call ends (the primary mechanism is websocket +disconnect). In orchestrated mode, conversation lifecycle is managed by +CO webhooks, so this is a no-op. + +**Arguments**: + +- `payload_dict` - Raw form data dict from the webhook request. + + + +#### handle\_websocket + +```python +async def handle_websocket(websocket: WebSocketProtocol) -> None +``` + +Handle voice streaming WebSocket connection lifecycle. + +This method manages the entire websocket connection: +- Accepts the connection +- Processes incoming messages +- Tracks and cancels in-flight tasks (if session_manager provided) +- Cleans up on disconnect + +**Arguments**: + +- `websocket` - Any WebSocket implementation satisfying WebSocketProtocol + + + +#### initiate\_outbound\_conversation + +```python +async def initiate_outbound_conversation( + options: InitiateVoiceConversationOptions +) -> InitiateVoiceConversationResult +``` + +Initiate an outbound voice conversation. + +Places an outbound call with inline TwiML that connects to ConversationRelay. +The conversationConfiguration attribute tells CO to create and manage the +conversation during passive hydration. The session is initialized lazily +on the first prompt when the conversation is discovered by callSid. + +``options.websocket_url`` must be the publicly accessible WebSocket +endpoint (e.g., ``wss://your-domain.ngrok.app/ws``). Unlike inbound calls +where TACServer sets this automatically, outbound calls require it +explicitly since there is no incoming HTTP request to derive the host from. + + + +#### process\_webhook + +```python +async def process_webhook(webhook_data: dict[str, Any], + idempotency_token: str | None = None) -> None +``` + +Process conversation webhooks for cleanup and cache invalidation. + +Voice channel processes CONVERSATION_UPDATED events: +- CLOSED status: Clean up local session state +- INACTIVE status: Invalidate cached memory (memory will be updated by +Conversation Orchestrator) + +Note: Conversation tracking uses instance-local memory. In multi-instance +deployments, webhooks may route to a different instance, preventing cleanup. +See CLAUDE.md for horizontal scaling considerations. + +**Arguments**: + +- `webhook_data` - Raw webhook event data from Twilio +- `idempotency_token` - Optional Twilio idempotency token from request headers + + + +#### send\_response + +```python +async def send_response(conversation_id: str, + response: str + | AsyncGenerator[str | dict[str, Any], None], + role: str | None = None) -> None +``` + +Send voice response through the websocket connection for this conversation. + +Supports both simple string responses and streaming async generators. + +**Arguments**: + +- `conversation_id` - Conversation ID +- `response` - Response text (string) or async generator for streaming +- `role` - Optional message role (not used in this implementation, but kept + for API consistency with BaseChannel interface) + + + +#### get\_websocket + +```python +def get_websocket(conversation_id: str) -> WebSocketProtocol | None +``` + +Get the WebSocket connection for a specific conversation. + +**Arguments**: + +- `conversation_id` - Conversation ID + + +**Returns**: + + WebSocket connection if exists, None otherwise + + + +# tac.channels.voice.twiml + +TwiML generation for voice channel. + + + +#### generate\_twiml + +```python +def generate_twiml(options: TwiMLOptions | dict[str, Any]) -> str +``` + +Generate TwiML XML for ConversationRelay with custom parameters. + +This is a low-level function for generating TwiML with arbitrary custom +parameters. For automatic conversation creation and participant management, +use VoiceChannel.handle_incoming_call() instead. + +**Arguments**: + +- `options` - TwiML generation options (TwiMLOptions model or dict with: + - websocket_url (required): WebSocket URL for ConversationRelay + - custom_parameters (optional): Dict of custom parameters + - welcome_greeting (optional): Initial greeting message + - action_url (optional): URL for call end webhook + - conversation_configuration (optional): Conversation Service SID for + automatic conversation creation + + +**Returns**: + + TwiML XML string ready to return to Twilio + + +**Example**: + + >>> twiml = generate_twiml( + ... { + ... "websocket_url": "wss://example.com/voice", + ... "custom_parameters": { + ... "session_id": "sess_abc123", + ... "user_language": "es", + ... }, + ... "welcome_greeting": "Hello!", + ... "conversation_configuration": "conv_configuration_xxxx", + ... } + ... ) + + + +# tac.channels.websocket\_protocol + +WebSocket protocol abstraction for framework-agnostic channel implementation. + +Defines a Protocol class that any WebSocket implementation (FastAPI, Starlette, +custom, etc.) can satisfy, along with a common disconnect error type. + + + +## WebSocketDisconnectError Objects + +```python +class WebSocketDisconnectError(Exception) +``` + +Raised when a WebSocket connection is unexpectedly closed. + + + +## WebSocketProtocol Objects + +```python +@runtime_checkable +class WebSocketProtocol(Protocol) +``` + +Protocol defining the WebSocket interface used by VoiceChannel. + +Any WebSocket implementation that provides these async methods can be used +with VoiceChannel, including FastAPI WebSocket, raw Starlette WebSocket, +or custom adapters. + + + +# tac.channels.websocket\_manager + +WebSocket connection management for voice channels. + +Provides WebSocket connection tracking for concurrent conversations, +enabling proper response routing in multi-connection scenarios. + + + +## WebSocketManager Objects + +```python +class WebSocketManager() +``` + +Manager for WebSocket connections per conversation. + +Manages the mapping between conversation IDs and their associated WebSocket +connections, enabling proper response routing when multiple calls are active +simultaneously. + +This manager is separate from SessionManager (which handles LLM streaming tasks) +to maintain clean separation of concerns: +- WebSocketManager: Connection routing and lifecycle +- SessionManager: LLM streaming and task cancellation + +Thread safety: No locking needed because each conversation operates on different +dict keys, and Python's dict operations are atomic for simple get/set/delete. + +**Example**: + + >>> ws_manager = WebSocketManager() + >>> ws_manager.add_websocket("conv_123", websocket) + >>> ws = ws_manager.get_websocket("conv_123") + >>> await ws.send_text("Hello") + >>> ws_manager.remove_websocket("conv_123") + + + +#### \_\_init\_\_ + +```python +def __init__() -> None +``` + +Initialize WebSocket manager. + + + +#### add\_websocket + +```python +def add_websocket(conversation_id: str, websocket: WebSocketProtocol) -> None +``` + +Store WebSocket connection for a conversation. + +**Arguments**: + +- `conversation_id` - Unique conversation identifier +- `websocket` - WebSocket connection satisfying WebSocketProtocol + + + +#### get\_websocket + +```python +def get_websocket(conversation_id: str) -> WebSocketProtocol | None +``` + +Retrieve WebSocket connection for a conversation. + +**Arguments**: + +- `conversation_id` - Unique conversation identifier + + +**Returns**: + + WebSocket connection if exists, None otherwise + + + +#### has\_websocket + +```python +def has_websocket(conversation_id: str) -> bool +``` + +Check if WebSocket exists for a conversation. + +**Arguments**: + +- `conversation_id` - Unique conversation identifier + + +**Returns**: + + True if WebSocket connection exists, False otherwise + + + +#### remove\_websocket + +```python +def remove_websocket(conversation_id: str) -> None +``` + +Remove WebSocket connection for a conversation. + +**Arguments**: + +- `conversation_id` - Unique conversation identifier + + + +#### get\_all\_conversation\_ids + +```python +def get_all_conversation_ids() -> list[str] +``` + +Get list of all active conversation IDs. + +**Returns**: + + List of conversation IDs with active WebSocket connections + + + +#### \_\_len\_\_ + +```python +def __len__() -> int +``` + +Get count of active WebSocket connections. + +**Returns**: + + Number of active connections + + + +# tac.models.tac + +TAC unified response models. + + + +## TACCommunicationAuthor Objects + +```python +class TACCommunicationAuthor(BaseModel) +``` + +Unified author model with all fields from both Memory and Conversation Orchestrator APIs. + + + +## TACCommunicationContent Objects + +```python +class TACCommunicationContent(BaseModel) +``` + +Unified content model with all fields from both Memory and Conversation Orchestrator APIs. + + + +## TACCommunication Objects + +```python +class TACCommunication(BaseModel) +``` + +Unified communication model with all fields from both Memory and Conversation Orchestrator APIs. + +Provides complete access to all communication fields regardless of the source. +Fields not available from a particular API will be None. + + + +## TACMemoryResponse Objects + +```python +class TACMemoryResponse() +``` + +Unified response wrapper for TAC.retrieve_memory(). + +Provides a consistent interface for accessing memory data regardless of whether +Memory API is configured or falling back to Conversation Orchestrator Communications API. + +Memory configured: +- observations, summaries, communications all populated +- communications include Memory-specific fields (author id, name, type, profile_id) + +Conversation Orchestrator fallback: +- observations and summaries are empty lists +- communications include Conversation Orchestrator-specific fields + (conversation_id, account_id, etc.) + + + +#### \_\_init\_\_ + +```python +def __init__(data: MemoryRetrievalResponse | list[Communication]) +``` + +Initialize wrapper with either Memory or Conversation Orchestrator data. + +**Arguments**: + +- `data` - Either MemoryRetrievalResponse (Memory) or + list[Communication] (Conversation Orchestrator) + + + +#### observations + +```python +@property +def observations() -> list[ObservationInfo] +``` + +Get observation memories. + +**Returns**: + + List of observations if Memory is configured, + empty list for Conversation Orchestrator fallback + + + +#### summaries + +```python +@property +def summaries() -> list[SummaryInfo] +``` + +Get summary memories. + +**Returns**: + + List of summaries if Memory is configured, + empty list for Conversation Orchestrator fallback + + + +#### communications + +```python +@property +def communications() -> list[TACCommunication] +``` + +Get communications in unified format with all available fields. + +Communications are converted to a common format during initialization that includes +all fields from both Memory and Conversation Orchestrator APIs. +Fields not available from a particular +API will be None. + +**Returns**: + + List of unified communications with all available fields + + + +#### has\_memory\_features + +```python +@property +def has_memory_features() -> bool +``` + +Check if Memory API is configured and providing full features. + +**Returns**: + + True if Memory is configured (observations/summaries available), + False if using Conversation Orchestrator fallback (only communications available) + + + +#### raw\_data + +```python +@property +def raw_data() -> MemoryRetrievalResponse | list[Communication] +``` + +Access raw underlying data for advanced use cases. + +Use this when you need access to all fields from the original API responses, +not just the simplified common fields. + +**Returns**: + + Either MemoryRetrievalResponse or list[Communication] depending on configuration + + + +#### build\_memory\_prompts + +```python +def build_memory_prompts() -> list[str] +``` + +Build all memory prompt sections (observations, summaries, communications) for LLM context. + +**Returns**: + + List of LLM prompt sections. Each element is a complete section + (e.g., observations section, summaries section). Returns empty list + if no memory data is available. + + +**Example**: + + >>> sections = memory_response.build_memory_prompts() + >>> for section in sections: + ... print(section) + ... print() + ## Key Observations + Important notes about the customer from previous interactions: + - Customer prefers email communication + - Previously reported billing issue (resolved) + + ## Past Conversation Summaries + Summaries of previous conversations with this customer: + - Discussed product features and pricing on 2024-01-15 + + + +# tac.models.session + + + +## AuthorInfo Objects + +```python +class AuthorInfo(BaseModel) +``` + +Information about the author of a communication. + + + +## ConversationSession Objects + +```python +class ConversationSession(BaseModel) +``` + +Context information for a conversation session that's passed to callbacks. + +This provides the necessary context for developers to handle memory-ready +events and send responses back through the appropriate channel. + + + +#### build\_profile\_prompt + +```python +def build_profile_prompt(trait_groups: list[str] | None = None) -> str | None +``` + +Build customer profile prompt section for LLM context. + +**Arguments**: + +- `trait_groups` - Optional list of trait group names to include. + If None, no filtering is applied. + + +**Returns**: + + LLM prompt section with profile data, or None if no profile data + is available or no traits match the filter. + + +**Example**: + + >>> section = context.build_profile_prompt(["Contact", "Preferences"]) + >>> print(section) + ## Customer Profile + Information about this customer: + - Contact: {"name": "John Doe", "email": "john@example.com"} + - Preferences: {"language": "en", "timezone": "PST"} + + + +# tac.models.conversation + +Pydantic models for Twilio Conversation Orchestrator API. + + + +## StatusTimeouts Objects + +```python +class StatusTimeouts(BaseModel) +``` + +Timeout settings for channel status transitions. + + + +## CaptureRule Objects + +```python +class CaptureRule(BaseModel) +``` + +Capture rule with from/to addresses and optional metadata. + + + +## ChannelSettings Objects + +```python +class ChannelSettings(BaseModel) +``` + +Configuration settings for a specific channel type. + + + +## StatusCallback Objects + +```python +class StatusCallback(BaseModel) +``` + +Webhook configuration for status callbacks. + + + +## ParticipantAddress Objects + +```python +class ParticipantAddress(BaseModel) +``` + +Communication address for a conversation participant. + + + +## ConversationConfiguration Objects + +```python +class ConversationConfiguration(BaseModel) +``` + +Configuration settings for a conversation response. + + + +## ConversationRequest Objects + +```python +class ConversationRequest(BaseModel) +``` + +Request payload for creating a conversation. + + + +## UpdateConversationRequest Objects + +```python +class UpdateConversationRequest(BaseModel) +``` + +Request payload for updating a conversation. + + + +## ConversationResponse Objects + +```python +class ConversationResponse(BaseModel) +``` + +Response from creating a conversation. + + + +## ParticipantRequest Objects + +```python +class ParticipantRequest(BaseModel) +``` + +Request payload for creating a conversation participant. + + + +## ParticipantResponse Objects + +```python +class ParticipantResponse(BaseModel) +``` + +Response from creating a participant. + + + +## CommunicationParticipant Objects + +```python +class CommunicationParticipant(BaseModel) +``` + +Author or recipient in a communication. + + + +## TranscriptionWord Objects + +```python +class TranscriptionWord(BaseModel) +``` + +Word-level transcription data with timing information. + + + +## Transcription Objects + +```python +class Transcription(BaseModel) +``` + +Transcription metadata for communication content. + + + +## CommunicationContent Objects + +```python +class CommunicationContent(BaseModel) +``` + +Content of a communication (ContentText or ContentTranscription). + + + +## Communication Objects + +```python +class Communication(BaseModel) +``` + +A communication representing a message exchanged in a conversation. + + + +## CommunicationRequest Objects + +```python +class CommunicationRequest(BaseModel) +``` + +Request payload for adding a communication. + + + +## CommunicationsListResponse Objects + +```python +class CommunicationsListResponse(BaseModel) +``` + +Response from list communications endpoint. + + + +## ConversationsListResponse Objects + +```python +class ConversationsListResponse(BaseModel) +``` + +Response from list conversations endpoint. + + + +## ActionParticipantRef Objects + +```python +class ActionParticipantRef(BaseModel) +``` + +Participant reference for the Actions API (`from`/`to` entries). + +Either `participant_id` or `address` must be supplied; `channel` is always required. +When both are provided, Conversation Orchestrator uses `participant_id` and +`channel` disambiguates +which of the participant's addresses to use. + + + +## ActionTextContent Objects + +```python +class ActionTextContent(BaseModel) +``` + +Plain-text content for a SEND_MESSAGE action. + + + +## ActionChannelSettings Objects + +```python +class ActionChannelSettings(BaseModel) +``` + +Channel-specific settings forwarded to the downstream backend. + +Open pass-through: any field not explicitly modeled here (e.g. +`messagingServiceSid`, `statusCallback`, `Attributes`) can be set by callers and +will be forwarded as-is. + + + +## SendMessageActionPayload Objects + +```python +class SendMessageActionPayload(BaseModel) +``` + +Inner payload for a SEND_MESSAGE action. + + + +## SendMessageActionRequest Objects + +```python +class SendMessageActionRequest(BaseModel) +``` + +Request for POST /v2/Conversations/{id}/Actions with type=SEND_MESSAGE. + +Body is discriminated by `type` with the action-specific fields under `payload`. + + + +## ActionResponse Objects + +```python +class ActionResponse(BaseModel) +``` + +Response from POST /v2/Conversations/{id}/Actions (202 Accepted). + + + +# tac.models.memory + + + +## MemoryRetrievalRequest Objects + +```python +class MemoryRetrievalRequest(BaseModel) +``` + +Request payload for retrieving conversation memories. + + + +## MemoryParticipant Objects + +```python +class MemoryParticipant(BaseModel) +``` + +Participant in a Memory communication (author or recipient). + + + +## MemoryCommunicationContent Objects + +```python +class MemoryCommunicationContent(BaseModel) +``` + +Content of a Memory communication. + + + +## MemoryCommunication Objects + +```python +class MemoryCommunication(BaseModel) +``` + +A communication from Memory API (historical conversation data). + + + +## CiOperator Objects + +```python +class CiOperator(BaseModel) +``` + +Information about the Conversational Intelligence operator. + + + +## ObservationInfo Objects + +```python +class ObservationInfo(BaseModel) +``` + +An observation memory from the API response. + + + +## SummaryInfo Objects + +```python +class SummaryInfo(BaseModel) +``` + +A summary memory derived from observations at the end of conversations. + + + +## MemoryRetrievalMeta Objects + +```python +class MemoryRetrievalMeta(BaseModel) +``` + +Metadata about the memory retrieval operation. + + + +## MemoryRetrievalResponse Objects + +```python +class MemoryRetrievalResponse(BaseModel) +``` + +Response from the Memory API /Recall endpoint. + + + +## ProfileResponse Objects + +```python +class ProfileResponse(BaseModel) +``` + +Response from the profile retrieval API. + + + +## ProfileLookupRequest Objects + +```python +class ProfileLookupRequest(BaseModel) +``` + +Request payload for looking up profiles by identifier. + + + +## ProfileLookupResponse Objects + +```python +class ProfileLookupResponse(BaseModel) +``` + +Response from the profile lookup API. + + + +# tac.models.voice + +Pydantic models for Twilio ConversationRelay Voice WebSocket messages. + + + +## CustomParameters Objects + +```python +class CustomParameters(BaseModel) +``` + +Custom parameters for ConversationRelay TwiML. + +Supports well-known TAC parameters plus arbitrary custom fields. +All fields are optional since ConversationRelay handles conversation creation automatically. + + + +## SetupMessage Objects + +```python +class SetupMessage(BaseModel) +``` + +Setup message sent when WebSocket connection is established. + +Contains call metadata from Twilio. + + + +## PromptMessage Objects + +```python +class PromptMessage(BaseModel) +``` + +Prompt message containing user's voice input. + +Sent when user speaks and speech is transcribed. + + + +## InterruptMessage Objects + +```python +class InterruptMessage(BaseModel) +``` + +Interrupt message sent when user interrupts the agent. + +Contains information about what was being said when interrupted. + + + +## TwiMLOptions Objects + +```python +class TwiMLOptions(BaseModel) +``` + +Options for generating ConversationRelay TwiML. + + + +## ConversationRelayCallbackPayload Objects + +```python +class ConversationRelayCallbackPayload(BaseModel) +``` + +Payload received from Twilio ConversationRelay callback webhook. + +Sent via the URL when a call ends or transitions state. +Used in relay-only mode to signal conversation completion. + + + +# tac.models.outbound + +Models for outbound conversation initiation. + + + +## InitiateMessagingConversationOptions Objects + +```python +class InitiateMessagingConversationOptions(BaseModel) +``` + +Shared options for initiating an outbound messaging conversation. + +This base model is used for messaging-style outbound conversations, +including SMS, RCS, WhatsApp, and Chat. Each channel may extend this with +channel-specific requirements (e.g., Chat requires channel_id). + +The sender is always TAC's configured address (``config.phone_number`` +for SMS, ``config.rcs_sender_id`` for RCS, ``config.whatsapp_number`` +for WhatsApp, ``ChatChannelConfig.agent_address`` for Chat). +Multi-sender deployments should use one TAC instance per sender so +inbound webhook routing, memory scoping, and configuration stay in sync. + + + +## InitiateChatConversationOptions Objects + +```python +class InitiateChatConversationOptions(InitiateMessagingConversationOptions) +``` + +Options for initiating an outbound Chat conversation. + +Extends InitiateMessagingConversationOptions with a required channel_id +(Conversations v1 Channel SID) for Chat delivery. + + + +## InitiateConversationResult Objects + +```python +class InitiateConversationResult(BaseModel) +``` + +Result of initiating an outbound messaging conversation. + + + +## InitiateVoiceConversationOptions Objects + +```python +class InitiateVoiceConversationOptions(BaseModel) +``` + +Options for initiating an outbound voice conversation. + +The caller identity is always TAC's configured ``config.phone_number``. +Multi-number deployments should use one TAC instance per line. + + + +## InitiateVoiceConversationResult Objects + +```python +class InitiateVoiceConversationResult(BaseModel) +``` + +Result of initiating an outbound voice conversation. + + + +# tac.models.handoff + + + +## HandoffPayload Objects + +```python +class HandoffPayload(BaseModel) +``` + +Structured payload generated during a handoff. + +Contains conversation context and developer-defined attributes +for routing to the target system (e.g., Flex TaskRouter). + + + +## PendingHandoffData Objects + +```python +class PendingHandoffData(BaseModel) +``` + +ConversationRelay WebSocket ``end`` message carrying a handoff payload. + +``handoff_data`` is a JSON *string* (not a nested object) — ConversationRelay +forwards it verbatim in the POST body to the ```` URL. + + + +# tac.models.intelligence + +Models for Conversation Intelligence webhook events. + + + +## IntelligenceConfiguration Objects + +```python +class IntelligenceConfiguration(BaseModel) +``` + +Intelligence configuration details from the CI service. + + + +## Operator Objects + +```python +class Operator(BaseModel) +``` + +Operator details from the CI service. + + + +## TriggerDetails Objects + +```python +class TriggerDetails(BaseModel) +``` + +Trigger details for the operator execution. + + + +## CommunicationsRange Objects + +```python +class CommunicationsRange(BaseModel) +``` + +Range of communications used in the operator execution. + + + +## Participant Objects + +```python +class Participant(BaseModel) +``` + +Participant in a conversation. + + + +## ExecutionDetails Objects + +```python +class ExecutionDetails(BaseModel) +``` + +Execution context details for the operator result. + + + +## ClassificationResult Objects + +```python +class ClassificationResult(BaseModel) +``` + +Result for Text-Classification output format. + + + +## ExtractionEntity Objects + +```python +class ExtractionEntity(BaseModel) +``` + +An extracted entity from Text-Extraction. + + + +## ExtractionResult Objects + +```python +class ExtractionResult(BaseModel) +``` + +Result for Text-Extraction output format. + + + +## TextGenerationResult Objects + +```python +class TextGenerationResult(BaseModel) +``` + +Result for Text-Generation output format. + + + +## JSONResult Objects + +```python +class JSONResult(BaseModel) +``` + +Result for JSON output format. + + + +## OperatorProcessingResult Objects + +```python +class OperatorProcessingResult(BaseModel) +``` + +Result of processing a Conversation Intelligence webhook event. + + + +## OperatorResult Objects + +```python +class OperatorResult(BaseModel) +``` + +Individual operator result from a CI webhook event. + +This model represents a single operator result within the operatorResults array. + + + +## OperatorResultEvent Objects + +```python +class OperatorResultEvent(BaseModel) +``` + +Operator result event from Conversation Intelligence webhook. + +This model represents the webhook payload received from the CI service. +It contains metadata about the conversation and an array of operator results. + + + +# tac.models.knowledge + +Knowledge models for the Twilio Agent Connect. + + + +## Knowledge Objects + +```python +class Knowledge(BaseModel) +``` + +Represents a Twilio Knowledge resource. + + + +## KnowledgeBase Objects + +```python +class KnowledgeBase(BaseModel) +``` + +Represents a Twilio Knowledge Base resource. + + + +## KnowledgeChunkResult Objects + +```python +class KnowledgeChunkResult(BaseModel) +``` + +Represents a search result chunk from knowledge base search. + + + +# tac.models.pagination + +Pagination models for Twilio API responses. + + + +## PaginationMeta Objects + +```python +class PaginationMeta(BaseModel) +``` + +Pagination metadata for API list responses. + + + +# tac.context.base + + + +## PartnerConnector Objects + +```python +class PartnerConnector(Enum) +``` + +Closed set of partner connectors allowed to identify themselves in the User-Agent. + +Partner packages built on top of TAC (e.g. ``tac_aws``, ``tac_azure``) select +a value from this enum and pass it to :func:`register_partner_connector`. The +enum is intentionally closed so that customers cannot set arbitrary +User-Agent values. Adding a new partner connector requires a release of +core TAC. + +Each value is a ``(package_name, connector_name)`` tuple. The package name +becomes a User-Agent product token and the connector name becomes a +comment, producing e.g. ``tac-azure/0.1.0 (AgentFrameworkConnector)``. + + + +## BaseAPIClient Objects + +```python +class BaseAPIClient() +``` + +Base client for Twilio API interactions with shared HTTP client logic. + + + +#### \_\_init\_\_ + +```python +def __init__(api_key: str, api_secret: str, region: str | None = None) -> None +``` + +Initialize the base API client. + +**Arguments**: + +- `api_key` - Twilio API Key SID for authentication +- `api_secret` - Twilio API Key Secret for authentication +- `region` - Optional Twilio region (e.g., 'au1', 'ie1') + + + +# tac.context.conversation + + + +## ConversationClient Objects + +```python +class ConversationClient(BaseAPIClient) +``` + +Client for interacting with Conversation Orchestrator API. + + + +#### \_\_init\_\_ + +```python +def __init__(api_key: str, + api_secret: str, + configuration_id: str, + region: str | None = None) -> None +``` + +Initialize the Conversation client. + +**Arguments**: + +- `api_key` - Twilio API Key SID for authentication +- `api_secret` - Twilio API Key Secret for authentication +- `configuration_id` - Conversation Configuration ID for API requests +- `region` - Optional Twilio region (e.g., 'au1', 'ie1') + + + +#### list\_conversations + +```python +async def list_conversations( + status: list[Literal["ACTIVE", "INACTIVE", "CLOSED"]] | None = None, + channel_id: str | None = None, + page_size: int | None = None, + page_token: str | None = None) -> list[ConversationResponse] +``` + +List conversations with optional filtering and pagination. + +**Arguments**: + +- `status` - Optional list of statuses to filter conversations + ("ACTIVE", "INACTIVE", "CLOSED") +- `channel_id` - Optional resource ID (call ID, message ID, etc.) to filter conversations +- `page_size` - Maximum number of items to return (1-1000) +- `page_token` - Token for pagination + + +**Returns**: + + List of ConversationResponse objects + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails + + + +#### add\_participant + +```python +async def add_participant( + conversation_id: str, + addresses: list[ParticipantAddress] | None = None, + participant_type: Literal["HUMAN_AGENT", "CUSTOMER", "AI_AGENT", "AGENT", + "UNKNOWN"] + | None = None +) -> ParticipantResponse +``` + +Add a new participant to a conversation. + +Used by `_reconcile_participants` when Conversation Orchestrator's v1-bridge emits only +the customer participant on an inbound SMS/chat — TAC adds itself as +`AI_AGENT` before replying. + +**Arguments**: + +- `conversation_id` - The conversation ID to add participant to +- `addresses` - List of communication addresses for the participant (optional) +- `participant_type` - Type of participant (e.g., "CUSTOMER", "AI_AGENT"). Optional. + + +**Returns**: + + ParticipantResponse object containing the created participant details + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails + + + +#### update\_participant + +```python +async def update_participant( + conversation_id: str, + participant_id: str, + participant_type: Literal["HUMAN_AGENT", "CUSTOMER", "AI_AGENT", + "AGENT"], + addresses: list[ParticipantAddress], + name: str | None = None, + profile_id: str | None = None) -> ParticipantResponse +``` + +Replace an existing participant. + +PUT is a full resource replacement per the Conversation Orchestrator spec — any field +omitted from the body is cleared on the server. Callers must pass the +current `addresses` (and `name` if set) to preserve them; pass a new +`profile_id` to attach a profile during reconciliation. + +**Arguments**: + +- `conversation_id` - Conversation ID containing the participant +- `participant_id` - Participant ID to update +- `participant_type` - New participant type +- `addresses` - Current participant addresses (required to avoid wiping) +- `name` - Current participant display name (optional) +- `profile_id` - Conversation Memory profile ID to attach (optional) + + +**Returns**: + + ParticipantResponse reflecting the updated participant. + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails. + + + +#### create\_conversation + +```python +async def create_conversation( + name: str | None = None, + participants: list[ParticipantRequest] | None = None +) -> ConversationResponse +``` + +Create a new conversation, optionally with inline participants. + +When participants are provided, CO creates them atomically with the +conversation. If an active conversation with the same participant +addresses already exists (respecting the configuration's group-by +rules), CO returns 409 with a pointer to the existing conversation. + +**Arguments**: + +- `name` - Conversation name (optional) +- `participants` - Optional list of participants to create with the conversation + + +**Returns**: + + ConversationResponse object containing the created conversation details + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails + + + +#### create\_or\_reuse\_conversation + +```python +async def create_or_reuse_conversation( + participants: list[ParticipantRequest]) -> tuple[str, bool] +``` + +Create a conversation with inline participants, reusing an existing one on 409. + +On 409 CO returns the existing conversation ID in the +X-Conflicting-Resource-Id response header. + +**Returns**: + + Tuple of (conversation_id, reused) where reused is True if an + existing conversation was found via 409 dedup. + + +**Raises**: + +- `httpx.HTTPStatusError` - If the API returns a non-409 error +- `RuntimeError` - If 409 is returned without X-Conflicting-Resource-Id header + + + +#### update\_conversation + +```python +async def update_conversation(conversation_id: str, + status: Literal["ACTIVE", "INACTIVE", "CLOSED"], + name: str | None = None) -> ConversationResponse +``` + +Update an existing conversation. + +**Arguments**: + +- `conversation_id` - The conversation ID to update +- `status` - Conversation status to update ("ACTIVE", "INACTIVE", "CLOSED") - required +- `name` - Optional conversation name to update + + +**Returns**: + + ConversationResponse object containing the updated conversation details + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails + + + +#### clear\_status\_callbacks + +```python +async def clear_status_callbacks(conversation_id: str) -> None +``` + +Clear statusCallbacks on a conversation's instance configuration. + +This stops the conversation from sending webhook events to TAC, +which is needed during handoff so the receiving system can take over. + +**Arguments**: + +- `conversation_id` - The conversation ID to update + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails + + + +#### create\_communication + +```python +async def create_communication( + conversation_id: str, + communication_request: CommunicationRequest) -> Communication +``` + +Create a new communication for a conversation. + +**Arguments**: + +- `conversation_id` - The conversation ID to create communication for +- `communication_request` - CommunicationRequest object with author, content, and recipients + + +**Returns**: + + Communication object containing the created communication details + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails + + + +#### list\_communications + +```python +async def list_communications( + conversation_id: str, + channel_id: str | None = None, + page_size: int | None = None, + page_token: str | None = None) -> list[Communication] +``` + +List communications for a conversation. + +**Arguments**: + +- `conversation_id` - The conversation ID to list communications for +- `channel_id` - Optional channel ID filter (call ID, message ID, etc.) +- `page_size` - Maximum number of items to return (1-1000) +- `page_token` - Token for pagination + + +**Returns**: + + List of Communication objects + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails + + + +#### create\_action + +```python +async def create_action(conversation_id: str, + request: SendMessageActionRequest) -> ActionResponse +``` + +Create an action via POST /v2/Conversations/{conversationId}/Actions. + +Currently supports SEND_MESSAGE actions. Returns 202 Accepted; the action is +processed asynchronously and its status can be polled via getAction. + +**Arguments**: + +- `conversation_id` - The conversation ID to create the action in +- `request` - SendMessageActionRequest with `from`, `to`, and content + + +**Returns**: + + ActionResponse with id, type, status, and conversationId + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails + + + +#### get\_configuration + +```python +def get_configuration(configuration_id: str) -> ConversationConfiguration +``` + +Retrieve the details for a single configuration. + +**Arguments**: + +- `configuration_id` - The configuration ID to retrieve + + +**Returns**: + + ConversationConfiguration object containing the configuration details + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails +- `ValueError` - If the response schema is invalid + + + +# tac.context.memory + + + +## MemoryClient Objects + +```python +class MemoryClient(BaseAPIClient) +``` + +Client for interacting with Twilio Conversation Memory data plane API. + + + +#### \_\_init\_\_ + +```python +def __init__(store_id: str, + api_key: str, + api_secret: str, + region: str | None = None) -> None +``` + +Initialize the Memory client. + +**Arguments**: + +- `store_id` - Memory store ID (starts with mem_store_). +- `api_key` - API Key for Conversation Memory authentication. +- `api_secret` - API Secret for Conversation Memory authentication. +- `region` - Optional Twilio region (e.g., 'au1', 'ie1') + + + +#### retrieve\_memory + +```python +async def retrieve_memory( + profile_id: str, + conversation_id: str | None = None, + query: str | None = None, + observations_limit: int | None = None, + summaries_limit: int | None = None, + communications_limit: int | None = None, + relevance_threshold: float | None = None) -> MemoryRetrievalResponse +``` + +Retrieve conversation memories with semantic search and configurable limits. + +**Arguments**: + +- `profile_id` - Profile ID (TTID format) +- `conversation_id` - Optional conversation ID (TTID format) +- `query` - Optional semantic search query (1-1024 characters) +- `observations_limit` - Max observations to return (0-100) +- `summaries_limit` - Max summaries to return (0-100) +- `communications_limit` - Max communications to return (0-100) +- `relevance_threshold` - Min relevance score (0.0-1.0) + + +**Returns**: + + MemoryRetrievalResponse with observations, summaries, communications, and metadata. + Returns empty MemoryRetrievalResponse() if API request fails or response cannot be + parsed. + + + +#### get\_profile + +```python +async def get_profile( + profile_id: str, + trait_groups: list[str] | None = None) -> ProfileResponse +``` + +Retrieve a profile by ID with optional trait group selection. + +**Arguments**: + +- `profile_id` - Profile ID using Twilio Type ID (TTID) format +- `trait_groups` - Optional list of trait group names to include in the response + + +**Returns**: + + ProfileResponse containing profile ID, creation timestamp, and traits + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails +- `ValueError` - If the response cannot be parsed + + + +#### lookup\_profile + +```python +async def lookup_profile(id_type: str, value: str) -> ProfileLookupResponse +``` + +Find profiles that contain a specific identifier value. + +Submit an identifier object specifying the idType and value. +The value is normalized using the configured identity resolution settings +(such as phone number formatting) prior to matching. Multiple matches are +returned if more than one profile is associated with the identifier. +Returns canonical profile IDs (the earliest ID if profiles have been merged) +along with the normalized value actually searched. + +**Arguments**: + +- `id_type` - Identifier type as configured in the service's Identity Resolution Settings + (e.g., "phone", "email"). Must be 2-30 characters. +- `value` - Raw value captured for the identifier (e.g., "+13175556789"). + The service normalizes this value according to the configured rules. + + +**Returns**: + + ProfileLookupResponse containing normalized value and list of matching profile IDs + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails +- `ValueError` - If the response cannot be parsed + + + +#### create\_profile + +```python +async def create_profile(traits: dict[str, dict[str, Any]]) -> str +``` + +Create a profile via identity resolution (upsert). + +Conversation Memory runs identity resolution on the submitted traits: if an +identifier match is found the existing canonical profile ID is +returned, otherwise a new profile is minted. The body must contain +at least one trait promoted-to-identifier per the store's identity +resolution settings, else resolution fails with 400. + +The write is queued (202 Accepted) — the canonical profile ID is +returned synchronously in the response body, but downstream traits +may not be fully persisted immediately. + +**Arguments**: + +- `traits` - Trait-group → field → value mapping, e.g. +- ``{"Contact"` - {"phone": "+13175551234"}}`. Max 50 groups × 99 + traits each. + + +**Returns**: + + Canonical profile ID (`mem_profile_…`). + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails. +- `ValueError` - If the response does not contain an `id` field. + + + +#### create\_observation + +```python +async def create_observation(profile_id: str, + content: str, + source: str = "conversation-intelligence", + conversation_ids: list[str] | None = None, + occurred_at: str | None = None) -> dict[str, Any] +``` + +Create a new observation in Conversation Memory. + +**Arguments**: + +- `profile_id` - Profile ID to associate observation with +- `content` - Observation content (the summary text or extracted fact) +- `source` - Source system identifier (default: "conversation-intelligence") +- `conversation_ids` - List of conversation IDs this observation relates to +- `occurred_at` - Optional timestamp when observation occurred (ISO 8601 format) + + +**Returns**: + + Dict with created observation details + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails + + + +#### create\_conversation\_summaries + +```python +async def create_conversation_summaries( + profile_id: str, summaries: list[dict[str, Any]]) -> dict[str, str] +``` + +Create conversation summaries in Conversation Memory. + +**Arguments**: + +- `profile_id` - Profile ID to associate summaries with +- `summaries` - List of summary objects, each containing: + - content (str): The summary text + - conversationId (str): The conversation ID + - occurredAt (str): ISO 8601 timestamp when conversation occurred + - source (str, optional): Source system identifier + + +**Returns**: + + Response dict with message field (e.g., {"message": "Summaries creation accepted"}) + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails + + + +# tac.context.knowledge + + + +## KnowledgeClient Objects + +```python +class KnowledgeClient(BaseAPIClient) +``` + +Client for interacting with Twilio Knowledge Base API. + + + +#### \_\_init\_\_ + +```python +def __init__(api_key: str, api_secret: str, region: str | None = None) -> None +``` + +Initialize the Knowledge client. + +**Arguments**: + +- `api_key` - API Key for Knowledge Base authentication. +- `api_secret` - API Secret for Knowledge Base authentication. +- `region` - Optional Twilio region (e.g., 'au1', 'ie1') + + + +#### get\_knowledge\_base + +```python +async def get_knowledge_base(knowledge_base_id: str) -> KnowledgeBase +``` + +Fetch knowledge base metadata from the Knowledge Base API. + +**Arguments**: + +- `knowledge_base_id` - The knowledge base ID to fetch (format: know_knowledgebase_*) + + +**Returns**: + + KnowledgeBase object with metadata from the API + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails + + + +#### search\_knowledge\_base + +```python +async def search_knowledge_base( + knowledge_base_id: str, + query: str, + top_k: int = 5, + knowledge_ids: list[str] | None = None) -> list[KnowledgeChunkResult] +``` + +Search a knowledge base with the given query. + +**Arguments**: + +- `knowledge_base_id` - The knowledge base ID to search (format: know_knowledgebase_*) +- `query` - The search query string (max 2048 characters) +- `top_k` - Number of knowledge chunks to return (default: 5, max: 20) +- `knowledge_ids` - Optional list of specific knowledge IDs to filter search results + + +**Returns**: + + List of KnowledgeChunkResult objects with content and relevance scores + + +**Raises**: + +- `httpx.HTTPError` - If the API request fails + + + +# tac.tools.base + +Tool representation for the Twilio Agent Connect. + +Inspired by OpenAI's function_schema approach from openai-agents-python (MIT License). +Injection pattern inspired by LangChain's InjectedToolArg system (MIT License). + + + +## InjectedToolArg Objects + +```python +class InjectedToolArg() +``` + +Marker class for tool arguments that are injected at runtime. + +Tool arguments annotated with this class are not included in the tool +schema sent to language models and are instead injected during execution. + +Inspired by LangChain's InjectedToolArg pattern. + +**Example**: + + @function_tool() + def my_tool( +- `user_input` - str, +- `client` - Annotated[MyClient, InjectedToolArg] + ) -> str: + # client is injected, not visible to LLM + return client.process(user_input) + + + +## TACTool Objects + +```python +@dataclass +class TACTool() +``` + +Represents a tool/function that can be used with LLMs. + +Similar to OpenAI's FuncSchema, this captures function metadata +for LLM tool integration. Supports runtime injection of dependencies +that are hidden from the LLM schema. + + + +#### implementation + +```python +@property +def implementation() -> Callable[..., Awaitable[object]] +``` + +Get a clean callable with only non-injected parameters in its signature. + +This property automatically returns the right callable for LLM SDK introspection. +The returned callable has only non-injected parameters in its signature while +automatically handling dependency injection when called. + +Returns an async callable since TAC is async-first. + +**Returns**: + + An async callable with clean signature that can be inspected by any LLM SDK + + +**Example**: + + # Pass to LLM SDK - it will introspect the clean signature + sdk.add_tool(tool.implementation) + + + +#### configure\_injection + +```python +def configure_injection(**kwargs: object) -> "TACTool" +``` + +Configure values to be injected at runtime when the tool is called. + +These values correspond to parameters marked with InjectedToolArg +annotations and will be automatically supplied when the tool executes. + +Validates that provided values match the expected types from the +function signature using Pydantic TypeAdapter for robust validation +of all Python type annotations including generics, Pydantic models, +Literal types, and complex unions. + +**Arguments**: + +- `**kwargs` - Mapping of parameter names to values to inject + + +**Returns**: + + Self for method chaining + + +**Raises**: + +- `TypeError` - If a provided value doesn't match the expected type +- `ValueError` - If an unknown parameter name is provided + + +**Warnings**: + + Do not directly mutate _injected_args. Always use configure_injection() + to ensure proper cache invalidation and type validation. + + +**Example**: + + tool.configure_injection(client=conversation_memory_client, config=tac_config) + + + +#### \_\_call\_\_ + +```python +async def __call__(**kwargs: object) -> object +``` + +Call the tool with the given arguments, automatically injecting +configured dependencies. + +Handles both sync and async implementations transparently. + +**Arguments**: + +- `**kwargs` - Arguments provided by the LLM or caller + + +**Returns**: + + Result from the tool's implementation + + + +#### to\_openai\_format + +```python +def to_openai_format() -> dict[str, object] +``` + +Get tool schema in OpenAI function calling format. + +**Returns**: + + Dictionary in OpenAI function format + + + +#### to\_anthropic\_format + +```python +def to_anthropic_format() -> dict[str, object] +``` + +Get tool schema in Anthropic tool calling format. + +**Returns**: + + Dictionary in Anthropic tool format + + + +#### to\_openai\_agents\_sdk\_tool + +```python +def to_openai_agents_sdk_tool() -> "FunctionTool" +``` + +Convert this tool to an OpenAI Agents SDK ``FunctionTool`` instance. + +Unlike ``to_openai_format`` and ``to_anthropic_format`` (which return +plain dicts consumed by HTTP APIs), the OpenAI Agents SDK dispatches +on tool *class*, so this returns a live ``FunctionTool`` object with +an ``on_invoke`` closure that calls this tool and JSON-encodes the +result. + +Requires the ``openai-agents`` package: + +pip install openai-agents + +**Returns**: + + A ``FunctionTool`` ready to pass to ``Agent(tools=[...])``. + + + +#### to\_json + +```python +def to_json() -> str +``` + +Convert tool to JSON string (OpenAI format by default). + + + +#### function\_tool + +```python +def function_tool( + name: str | None = None, + description: str | None = None +) -> Callable[[Callable[..., object]], TACTool] +``` + +Decorator to create a TAC tool from a function. + +Similar to OpenAI's function_tool decorator approach. + +**Arguments**: + +- `name` - Optional name override (defaults to function name) +- `description` - Optional description override (defaults to docstring) + + +**Returns**: + + Decorator function + + + +#### create\_tool + +```python +def create_tool(name: str, description: str, params_json_schema: dict[str, + object], + implementation: Callable[..., object]) -> TACTool +``` + +Create a TAC tool manually with explicit schema. + +**Arguments**: + +- `name` - The name of the tool/function +- `description` - Description of what the tool does +- `params_json_schema` - JSON Schema for the tool's parameters +- `implementation` - Function that implements the tool's logic + + +**Returns**: + + TACTool instance + + + +# tac.tools.memory + +Memory API tools for the Twilio Agent Connect. + + + +#### retrieve\_profile\_memory + +```python +async def retrieve_profile_memory( + query: str, conversation_memory_client: Annotated[MemoryClient, + InjectedToolArg], + profile_id: Annotated[str, InjectedToolArg]) -> dict[str, Any] +``` + +Search and retrieve relevant memories for the current profile. + +Performs semantic search across the user's conversation history, observations, +and stored traits to find contextually relevant information. + +**Arguments**: + +- `query` - What to search for in the user's memory (e.g., "preferences about food", + "previous complaints", "contact information") + + +**Returns**: + + Dictionary containing relevant memories, traits, and metadata + + + +#### create\_memory\_tool + +```python +def create_memory_tool(conversation_memory_client: MemoryClient, + session: ConversationSession, + *, + name: str | None = None, + description: str | None = None) -> TACTool +``` + +Create memory tool with injected MemoryClient and session context. + +**Arguments**: + +- `conversation_memory_client` - MemoryClient instance for retrieving memories +- `session` - Current session identity with profile and conversation IDs +- `name` - Tool name exposed to the LLM. Defaults to the function name + (``"retrieve_profile_memory"``). +- `description` - Tool description exposed to the LLM. Defaults to the + function's docstring. + + +**Returns**: + + Configured memory tool + + +**Example**: + + >>> tool = create_memory_tool( + ... conversation_memory_client, + ... session, + ... name="recall_customer_history", + ... description="Recall prior preferences and complaints for this customer.", + ... ) + >>> result = await tool(query="user preferences") + + + +# tac.tools.knowledge + +Knowledge API tools for the Twilio Agent Connect. + + + +#### search\_knowledge + +```python +async def search_knowledge( + query: str, knowledge_client: Annotated[KnowledgeClient, + InjectedToolArg], + knowledge_base_id: Annotated[str, InjectedToolArg], + top_k: Annotated[int, InjectedToolArg]) -> list[KnowledgeChunkResult] +``` + +Search the knowledge base with the given query. + +**Arguments**: + +- `query` - The search query string (max 2048 characters) +- `knowledge_client` - KnowledgeClient instance for API calls (injected, not visible to LLM) +- `knowledge_base_id` - Knowledge base ID to search (injected, not visible to LLM) +- `top_k` - Number of chunks to return (injected, not visible to LLM) + + +**Returns**: + + List of KnowledgeChunkResult objects with content, knowledge_id, created_at, and score + + + +#### create\_knowledge\_tool + +```python +async def create_knowledge_tool(knowledge_client: KnowledgeClient, + knowledge_base_id: str, + *, + name: str | None = None, + description: str | None = None, + top_k: int = 5) -> TACTool +``` + +Create a knowledge search tool for the given knowledge base. + +Creates a function tool that searches the specified knowledge using Twilio's +Knowledge Base Search API via KnowledgeClient. The tool uses dependency injection +to hide the knowledge client and knowledge ID from the LLM schema. + +If both ``name`` and ``description`` are provided, uses them directly (no API call). +If either is missing, fetches the knowledge base metadata to derive defaults. + +**Arguments**: + +- `knowledge_client` - KnowledgeClient instance for searching knowledge bases +- `knowledge_base_id` - Knowledge base ID string (e.g., "know_knowledgebase_...") +- `name` - Tool name exposed to the LLM. Defaults to ``search_`` + (fetched from the knowledge base if unset). +- `description` - Tool description exposed to the LLM. Defaults to the knowledge + base's ``description`` field (fetched if unset). +- `top_k` - Number of knowledge chunks to return per query. Defaults to 5. + + +**Returns**: + + A configured TACTool that searches the specified knowledge with injected dependencies + + Example with custom name and description (no API call): + >>> tool = await create_knowledge_tool( + ... knowledge_client=tac.knowledge_client, + ... knowledge_base_id="know_knowledgebase_...", + ... name="search_promotions", + ... description="Search for promotions and discounts", + ... top_k=3, + ... ) + + Example using KB metadata as defaults (fetches KB): + >>> tool = await create_knowledge_tool( + ... knowledge_client=tac.knowledge_client, + ... knowledge_base_id="know_knowledgebase_...", + ... top_k=3, + ... ) + + + +# tac.tools.handoff + +Handoff tool for the Twilio Agent Connect. + + + +#### studio\_executions\_url + +```python +def studio_executions_url(flow_sid: str) -> str +``` + +Build the Twilio Studio Flow Executions URL for a given Flow SID. + +Used for digital (messaging/chat) handoff — POST the handoff payload +to this URL to start a Studio flow execution. + + + +#### studio\_voice\_handoff\_url + +```python +def studio_voice_handoff_url(account_sid: str, flow_sid: str) -> str +``` + +Build the Twilio Studio Flow voice webhook URL for a given Flow SID. + +Used as the ```` URL in TwiML for voice handoff, +so that when ConversationRelay ends the session Twilio triggers the +Studio flow for an incoming call. + + + +#### build\_handoff\_payload + +```python +def build_handoff_payload(session: ConversationSession, memory_store_id: str, + attributes: dict[str, Any]) -> HandoffPayload +``` + +Build a HandoffPayload from session context and attributes. + +Useful for custom handoff tools that want TAC's payload shape without +the Studio-specific delivery in ``post_studio_handoff``. + +**Arguments**: + +- `session` - Current conversation session +- `memory_store_id` - Memory store ID (typically ``tac.conversation_memory_client.store_id``) +- `attributes` - Developer-defined attributes (including reason) + + +**Returns**: + + HandoffPayload with conversation context and attributes + + + +#### post\_studio\_handoff + +```python +async def post_studio_handoff(payload: HandoffPayload, + session: ConversationSession, *, + handoff_url: str, from_address: str, + api_key: str, api_secret: str) -> None +``` + +POST a handoff payload to a Twilio Studio Flow Executions endpoint. + +Emits the Twilio Studio Executions API wire format: form-encoded +``To`` / ``From`` / ``Parameters`` fields with HTTP Basic auth. +``Parameters`` is a JSON string keyed under ``HandoffData`` so Studio +can reference it via ``{{flow.data.HandoffData.*}}``. + +**Arguments**: + +- `payload` - Structured handoff payload +- `session` - Current conversation session (used for ``To`` address) +- `handoff_url` - Studio Flow Executions URL + (``https://studio.twilio.com/v2/Flows/FWxxx/Executions``) +- `from_address` - Twilio phone number used as ``From`` +- `api_key` - Twilio API Key SID (Basic auth username) +- `api_secret` - Twilio API Key Secret (Basic auth password) + + +**Raises**: + +- `httpx.HTTPError` - If the POST request fails + + + +#### create\_studio\_handoff\_tool + +```python +def create_studio_handoff_tool( + tac: "TAC", + session: ConversationSession, + attributes: dict[str, Any] | None = None, + *, + name: str = DEFAULT_HANDOFF_TOOL_NAME, + description: str = DEFAULT_HANDOFF_TOOL_DESCRIPTION) -> TACTool +``` + +Create a handoff tool that delivers in the Twilio Studio Executions API shape. + +The returned tool exposes only ``handoff(reason: str)`` to the LLM. +All other dependencies are injected at runtime. + +On digital channels, the tool POSTs to the Studio Flow Executions +endpoint derived from ``tac.config.studio_handoff_flow_sid`` +(``https://studio.twilio.com/v2/Flows/{flow_sid}/Executions``) using +form-encoded ``To`` / ``From`` / ``Parameters`` fields with HTTP Basic +auth. The Studio flow can access the handoff payload via +``{{flow.data.HandoffData.*}}``. + +For voice channels, the payload is stored on the session and the voice +channel automatically sends the WS ``end`` message with ``handoffData`` +after the LLM's final response is delivered. + +The tool also sets the conversation to INACTIVE and clears status callbacks +to prevent further webhook events from being routed to TAC. + +**Not available in ConversationRelay-only mode.** This tool requires +Conversation Orchestrator for conversation state management (setting +INACTIVE status, clearing callbacks) and Conversation Memory for the +handoff payload's ``storeId``. In relay-only mode, implement a custom +handoff by setting ``session.pending_handoff_data`` directly — the voice +channel will send the WebSocket ``end`` message with your payload, and +your ```` URL handler can route the call accordingly. + +**Arguments**: + +- `tac` - TAC instance for building payload and posting to Studio +- `session` - Current conversation session +- `attributes` - Static attributes to include in the handoff payload + (e.g., ``{"department": "billing", "priority": "high"}``). + The LLM-provided ``reason`` is always added automatically. +- `name` - Tool name exposed to the LLM. Defaults to ``"handoff"``. +- `description` - Tool description exposed to the LLM. Customize when the + default's phrasing doesn't match your product vocabulary + or escalation policy. + + +**Returns**: + + Configured TACTool instance for handoff + + +**Example**: + + >>> handoff_tool = create_studio_handoff_tool( + ... tac, + ... context, + ... attributes={"department": "support"}, + ... name="escalate_to_agent", + ... description="Escalate only for billing disputes over $100.", + ... ) + + +**Raises**: + +- `ValueError` - If ``tac.config.studio_handoff_flow_sid`` is unset, + if Conversation Orchestrator is not configured (relay-only mode), + or if no memory store ID is available. + + + +# tac.adapters.options + +Options for configuring adapter behavior. + + + +## AdapterOptions Objects + +```python +class AdapterOptions(BaseModel) +``` + +Options for configuring how adapters inject memory and profile data. + +**Example**: + + # Default behavior (no options) - inject ALL profile traits + client = with_tac_memory(openai_client, memory_response, context) + + # Default behavior (options but no profile_traits specified) - inject ALL profile traits + options = AdapterOptions() + client = with_tac_memory(openai_client, memory_response, context, options=options) + + # Explicitly exclude all profile traits + options = AdapterOptions(profile_traits=None) + client = with_tac_memory(openai_client, memory_response, context, options=options) + # or + options = AdapterOptions(profile_traits=[]) + client = with_tac_memory(openai_client, memory_response, context, options=options) + + # Specific traits only + options = AdapterOptions(profile_traits=["Contact", "Preferences"]) + client = with_tac_memory(openai_client, memory_response, context, options=options) + + + +#### get\_profile\_traits + +```python +def get_profile_traits() -> list[str] | None +``` + +Get the profile traits to include. + +**Returns**: + + None to include all traits (when field not set), + empty list to exclude all (when explicitly set to None or []), + or list of specific trait group names to include. + + + +# tac.adapters.prompt\_builder + +Memory prompt builder for TAC adapters. + +This module provides a clean class-based API for building LLM prompts from TAC memory +data (observations, summaries, communications) and customer profile information. + +All adapters (OpenAI, Anthropic, Bedrock, LangChain, etc.) should use MemoryPromptBuilder +to ensure consistent memory presentation across different LLM providers. + + + +## MemoryPromptBuilder Objects + +```python +class MemoryPromptBuilder() +``` + +Builds LLM prompts from TAC memory and profile data. + +This class orchestrates prompt building by calling helper methods on +TACMemoryResponse and ConversationSession models, then assembles the +sections into a complete prompt. + +**Example**: + + >>> prompt = MemoryPromptBuilder.build(memory_response, context, options) + >>> if prompt: + ... # Inject into your LLM messages + ... messages.insert(0, {"role": "system", "content": prompt}) + + + +#### build + +```python +@staticmethod +def build(memory_response: TACMemoryResponse | None = None, + context: ConversationSession | None = None, + options: AdapterOptions | None = None) -> str | None +``` + +Build a complete memory prompt from TAC data. + +This is the main entry point. Delegates formatting to model helper methods, +then assembles sections into a complete prompt. + +**Arguments**: + +- `memory_response` - Memory data from TAC.retrieve_memory() +- `context` - Conversation session with profile data +- `options` - Adapter options for trait filtering + + +**Returns**: + + Formatted prompt string ready for LLM injection, or None if + no memory/profile data is available. + + +**Example**: + + >>> prompt = MemoryPromptBuilder.build( + ... memory_response=memory_response, + ... context=context, + ... options=AdapterOptions(profile_traits=["Contact"]), + ... ) + >>> print(prompt) + # Customer Context + You have access to the following information about this customer + from previous interactions: + + ## Customer Profile + Information about this customer: + - Contact: {"name": "John Doe", "email": "john@example.com"} + + ## Key Observations + Important notes about the customer from previous interactions: + - Customer prefers email communication + + + +#### compose + +```python +@staticmethod +def compose(system_prompt: str | None = None, + memory_response: TACMemoryResponse | None = None, + context: ConversationSession | None = None, + options: AdapterOptions | None = None) -> str +``` + +Compose system prompt with memory context. + +Appends memory to system_prompt if available. Always returns a string. + +**Example**: + + >>> prompt = MemoryPromptBuilder.compose( + ... "You are a helpful assistant", memory_response, context + ... ) + + + +# tac.adapters.openai.adapter + +OpenAI adapter for automatic memory injection using wrapper approach. + + + +## TACCompletionsNamespace Objects + +```python +class TACCompletionsNamespace(_BaseCompletionsNamespace) +``` + +Sync wrapper for OpenAI chat.completions namespace with memory injection. + + + +#### create + +```python +def create(*args: Any, messages: list[ChatCompletionMessageParam], + **kwargs: Any) -> Any +``` + +Intercepts create() calls to inject memory automatically. + + + +#### stream + +```python +def stream(*args: Any, messages: list[ChatCompletionMessageParam], + **kwargs: Any) -> Any +``` + +Intercepts stream() calls to inject memory automatically. + + + +## AsyncTACCompletionsNamespace Objects + +```python +class AsyncTACCompletionsNamespace(_BaseCompletionsNamespace) +``` + +Async wrapper for OpenAI chat.completions namespace with memory injection. + + + +#### create + +```python +async def create(*args: Any, messages: list[ChatCompletionMessageParam], + **kwargs: Any) -> Any +``` + +Intercepts async create() calls to inject memory automatically. + + + +#### stream + +```python +def stream(*args: Any, messages: list[ChatCompletionMessageParam], + **kwargs: Any) -> Any +``` + +Intercepts async stream() calls to inject memory automatically. + + + +## TACResponsesNamespace Objects + +```python +class TACResponsesNamespace(_BaseResponsesNamespace) +``` + +Sync wrapper for OpenAI responses namespace with memory injection. + + + +#### create + +```python +def create(*args: Any, instructions: str | None = None, **kwargs: Any) -> Any +``` + +Intercepts create() calls to inject memory automatically. + + + +## AsyncTACResponsesNamespace Objects + +```python +class AsyncTACResponsesNamespace(_BaseResponsesNamespace) +``` + +Async wrapper for OpenAI responses namespace with memory injection. + + + +#### create + +```python +async def create(*args: Any, + instructions: str | None = None, + **kwargs: Any) -> Any +``` + +Intercepts async create() calls to inject memory automatically. + + + +## TACChatNamespace Objects + +```python +class TACChatNamespace(_BaseChatNamespace) +``` + +Sync wrapper for OpenAI chat namespace. + + + +## AsyncTACChatNamespace Objects + +```python +class AsyncTACChatNamespace(_BaseChatNamespace) +``` + +Async wrapper for OpenAI chat namespace. + + + +## \_BaseOpenAIClient Objects + +```python +class _BaseOpenAIClient() +``` + +Base class for OpenAI client wrappers with shared logic. + + + +#### \_\_getattr\_\_ + +```python +def __getattr__(name: str) -> Any +``` + +Proxy all other OpenAI client features (embeddings, images, audio, etc). + + + +## TACOpenAIClient Objects + +```python +class TACOpenAIClient(_BaseOpenAIClient) +``` + +Sync wrapper for OpenAI client that automatically injects TAC memory. + +Does NOT mutate the original client. Safe for global clients and concurrent conversations. + + + +## AsyncTACOpenAIClient Objects + +```python +class AsyncTACOpenAIClient(_BaseOpenAIClient) +``` + +Async wrapper for AsyncOpenAI client that automatically injects TAC memory. + +Does NOT mutate the original client. Safe for global clients and concurrent conversations. + + + +#### with\_tac\_memory + +```python +def with_tac_memory( + openai_client: OpenAI | AsyncOpenAI, + memory_response: TACMemoryResponse | None = None, + context: ConversationSession | None = None, + options: AdapterOptions | None = None +) -> TACOpenAIClient | AsyncTACOpenAIClient +``` + +Wraps an OpenAI or AsyncOpenAI client with automatic Twilio memory injection. + +Does NOT mutate the original client. Returns a new wrapper object that +intercepts chat.completions.create() and stream() calls and injects memory automatically. + +Supports both synchronous and asynchronous clients. + +**Arguments**: + +- `openai_client` - The OpenAI or AsyncOpenAI client instance to wrap +- `memory_response` - Optional memory response from TAC.retrieve_memory() +- `context` - Optional conversation session context with profile data +- `options` - Optional adapter options for controlling memory injection + + +**Returns**: + + Wrapped OpenAI client with memory injection (TACOpenAIClient or AsyncTACOpenAIClient) + + +**Examples**: + + Sync usage: + >>> client = with_tac_memory(openai_client, memory_response, context) + >>> response = client.chat.completions.create( + ... model="gpt-4", messages=[{"role": "user", "content": "Hello"}] + ... ) + + Async usage: + >>> async_client = with_tac_memory(async_openai_client, memory_response, context) + >>> response = await async_client.chat.completions.create( + ... model="gpt-4", messages=[{"role": "user", "content": "Hello"}] + ... ) + + Streaming: + >>> with client.chat.completions.stream( + ... model="gpt-4", messages=[{"role": "user", "content": "Hello"}] + ... ) as stream: + ... for event in stream: + ... print(event.content) + + + +# tac.server.fastapi\_server + +TACFastAPIServer: Batteries-included FastAPI server for TAC channels. + +This module provides FastAPIWebSocketAdapter (bridges FastAPI WebSocket to +WebSocketProtocol) and TACFastAPIServer (creates a FastAPI app with routes for +voice, messaging, and CI webhooks). + +Requires: pip install tac[server] + + + +## FastAPIWebSocketAdapter Objects + +```python +class FastAPIWebSocketAdapter() +``` + +Adapts a FastAPI WebSocket to satisfy WebSocketProtocol. + +Converts FastAPI's WebSocketDisconnect into WebSocketDisconnectError +so that VoiceChannel's framework-agnostic exception handling works. + + + +## TACFastAPIServer Objects + +```python +class TACFastAPIServer() +``` + +Batteries-included FastAPI server for TAC channels. + +Creates (or adopts) a FastAPI app and registers routes for voice, messaging, +and CI webhooks, then starts uvicorn when start() is called. + +Customization: +- Pass your own FastAPI instance via ``app=...`` to control +construction-time settings (title, version, lifespan, docs_url, ...). +TAC routes are registered onto it immediately in ``__init__``. +- Or mutate ``server.app`` after construction: add middleware, +exception handlers, routers, or custom routes — before calling +``start()``. + +**Example**: + + from fastapi import FastAPI + from fastapi.middleware.cors import CORSMiddleware + + app = FastAPI(title="My Service", version="1.2.0") + app.add_middleware(CORSMiddleware, allow_origins=["*"]) + + server = TACFastAPIServer(tac=tac, voice_channel=vc, app=app) + + @server.app.get("/health") + async def health() -> dict: + return {"status": "ok"} + + server.start() + + + +#### start + +```python +def start() -> None +``` + +Start uvicorn serving ``self.app``. + + + +# tac.server.config + +Configuration for TAC server implementations. + + + +## TACServerConfig Objects + +```python +class TACServerConfig(BaseModel) +``` + +Configuration for TAC server implementations. + +Controls host/port binding, public domain for WebSocket URLs, +and customizable webhook paths. + + + +#### from\_env + +```python +@classmethod +def from_env(cls) -> "TACServerConfig" +``` + +Create config from environment variables. + +Environment variables: + TWILIO_VOICE_PUBLIC_DOMAIN: Public domain for WebSocket URLs (required for voice) + TWILIO_SERVER_HOST: Host to bind to (default: 0.0.0.0) + TWILIO_SERVER_PORT: Port to bind to (default: 8000) + + + +# tac.server.signature\_validation + +Webhook signature validation for Twilio webhooks. + +This module provides utilities for validating Twilio webhook signatures +in FastAPI applications. It handles proxy headers (X-Forwarded-Proto, +X-Forwarded-Host) for environments like ngrok. + +Requires: pip install tac[server] + + + +#### validate\_twilio\_webhook + +```python +def validate_twilio_webhook(request: Request, auth_token: str, + body: str | Mapping[str, str]) -> bool +``` + +Validate a Twilio webhook signature. + +Verifies the X-Twilio-Signature header matches the expected signature for the +request URL and body. Handles proxy headers (X-Forwarded-Proto, X-Forwarded-Host) +for environments like ngrok. + +**Arguments**: + +- `request` - FastAPI Request object containing headers and URL info. +- `auth_token` - Twilio Auth Token used for signature validation. +- `body` - Request body - pass str for JSON bodies (SMS webhooks from Conversation Orchestrator, + where signature is computed with empty POST params), or pass a mapping + for form-encoded bodies (Voice webhooks, where params are included). + Accepts dict, FormData, or any Mapping[str, str]. + + +**Returns**: + + True if signature is valid, False otherwise. + + + +#### build\_http\_signature\_dependency + +```python +def build_http_signature_dependency( + auth_token: str) -> Callable[..., Awaitable[None]] +``` + +Build a FastAPI dependency that validates Twilio webhook signatures on HTTP POST routes. + +Usage: + sig_dep = build_http_signature_dependency(auth_token) + + @app.post("/webhook", dependencies=[Depends(sig_dep)]) + async def webhook(request: Request) -> JSONResponse: + ... + + + +#### build\_websocket\_signature\_dependency + +```python +def build_websocket_signature_dependency( + auth_token: str) -> Callable[..., Awaitable[None]] +``` + +Build a FastAPI dependency that validates Twilio signatures on WebSocket upgrade requests. + +Validates the signature before the WebSocket is accepted. +Closes with code 1008 (Policy Violation) on invalid signature. + +Usage: + ws_dep = build_websocket_signature_dependency(auth_token) + + @app.websocket("/ws") + async def ws_endpoint(websocket: WebSocket, _: None = Depends(ws_dep)) -> None: + ... + + + +# tac.intelligence.operator\_result\_processor + +Processor for Conversation Intelligence webhook events. + + + +## OperatorResultProcessor Objects + +```python +class OperatorResultProcessor() +``` + +Processor for Conversation Intelligence webhook events. + +This processor handles incoming CI webhook payloads, validates them, +and creates observations or summaries in Conversation Memory based on the event type. + +Events are filtered by: +- Configuration ID matching the provided config +- Operator SID matching observation or summary operator SID in config + +Example usage: + ```python + from tac.context.memory import MemoryClient + from tac.core.config import ConversationIntelligenceConfig + from tac.intelligence import OperatorResultProcessor + + conversation_memory_client = MemoryClient(...) + config = ConversationIntelligenceConfig( + configuration_id="GA...", + observation_operator_sid="LY...", + summary_operator_sid="LY...", + ) + processor = OperatorResultProcessor(conversation_memory_client, config) + + result = await processor.process_event(webhook_payload) + if result.success: + print(f"Created {result.created_count} {result.event_type}(s)") + elif result.skipped: + print(f"Skipped: {result.skip_reason}") + else: + print(f"Error: {result.error}") + ``` + + + +#### \_\_init\_\_ + +```python +def __init__(conversation_memory_client: MemoryClient, + config: ConversationIntelligenceConfig) -> None +``` + +Initialize the CI event processor. + +**Arguments**: + +- `conversation_memory_client` - MemoryClient instance for creating observations/summaries +- `config` - ConversationIntelligenceConfig for filtering events by configuration + ID and operator SIDs + + + +#### process\_event + +```python +async def process_event(payload: dict[str, Any]) -> OperatorProcessingResult +``` + +Process a CI webhook payload. + +This method: +1. Parses the payload into an OperatorResultEvent (Pydantic validates required fields) +2. Applies filtering logic based on intelligence configuration ID and operator SIDs +3. Iterates over operator_results array +4. For each operator result: extracts profile IDs, generates content +5. Creates observations or summaries in Conversation Memory + +**Arguments**: + +- `payload` - The raw webhook payload dictionary + + +**Returns**: + + OperatorProcessingResult with status and details + + + +# tac.session.base + +Abstract base class for session management. + +Defines the interface that all session manager implementations must follow. + + + +## SessionManager Objects + +```python +class SessionManager(ABC) +``` + +Abstract base class for managing session state with task cancellation support. + +Implementations manage session state and track async tasks for graceful +cancellation. This enables responsive interactions across different channels where: +- New requests can cancel previous incomplete responses +- Sessions are properly cleaned up with task cancellation +- Concurrent sessions are tracked independently + +Example use cases: +- Voice channels: Track streaming tasks and cancel when user interrupts +- Chat channels: Track typing indicators or long-running operations +- Any channel with async operations that need graceful cancellation + +To implement a custom session manager, inherit from this class and implement +all abstract methods. See ThreadSafeSessionManager for a reference implementation. + + + +#### get\_or\_create\_session + +```python +@abstractmethod +def get_or_create_session(session_id: str) -> SessionState +``` + +Get existing session or create a new one. + +**Arguments**: + +- `session_id` - Unique session identifier + + +**Returns**: + + SessionState object for the session + + + +#### has\_session + +```python +@abstractmethod +def has_session(session_id: str) -> bool +``` + +Check if a session exists. + +**Arguments**: + +- `session_id` - Unique session identifier + + +**Returns**: + + True if session exists, False otherwise + + + +#### remove\_session + +```python +@abstractmethod +def remove_session(session_id: str) -> None +``` + +Remove session and clean up resources. + +**Arguments**: + +- `session_id` - Unique session identifier + + + +#### get\_all\_session\_ids + +```python +@abstractmethod +def get_all_session_ids() -> list[str] +``` + +Get all active session IDs. + +**Returns**: + + List of session identifiers + + + +#### \_\_len\_\_ + +```python +@abstractmethod +def __len__() -> int +``` + +Return number of active sessions. + +**Returns**: + + Count of active sessions + + + +# tac.session.state + +Session management utilities for agents + + + +## SessionState Objects + +```python +class SessionState() +``` + +Manages session state for voice conversations with streaming task tracking. + +Tracks the active streaming task to enable cancellation when: +- A new prompt arrives (cancel previous incomplete response) +- An interrupt occurs (user speaks over the agent) +- The session ends (cleanup) + + + +#### cancel\_stream\_task + +```python +async def cancel_stream_task() -> None +``` + +Cancel an in-flight streaming task with timeout protection. + + + +# tac.session.thread\_safe + +Thread-safe session manager implementation. + +Provides concurrent session handling with RLock-based synchronization. + + + +## ThreadSafeSessionManager Objects + +```python +class ThreadSafeSessionManager(SessionManager) +``` + +Thread-safe implementation of SessionManager for concurrent session handling. + +This implementation provides: +- Thread-safe session storage using RLock for concurrent access +- Task lifecycle management with graceful cancellation +- SessionState tracking for each conversation + +Tracks active async tasks per session, enabling cancellation when: +- A new request arrives (cancels previous in-flight task) +- An interrupt occurs (e.g., voice channel user interrupts mid-response) +- The session ends (cleanup with graceful task cancellation) + + + +#### \_\_init\_\_ + +```python +def __init__() -> None +``` + +Initialize thread-safe session manager. + + + +# tac.utils.redaction + +PII redaction utilities for log output. + + + +#### mask\_phone + +```python +def mask_phone(value: str | None) -> str +``` + +Mask a phone number, preserving the first 2 and last 4 characters. + +Returns ``"***"`` for ``None``, empty, or short (< 7 char) inputs. + + + +#### mask\_email + +```python +def mask_email(value: str | None) -> str +``` + +Mask an email address, preserving the first character and full domain. + +Returns ``"***"`` for ``None``, empty, or strings without ``@``. + + + +#### mask\_address + +```python +def mask_address(value: str | None) -> str +``` + +Auto-detect address type and apply the appropriate mask. + +Delegates to :func:`mask_email` if the value contains ``@``, +otherwise to :func:`mask_phone`. + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..299448b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,30 @@ +--- +title: Twilio Agent Connect Python SDK Documentation +--- + +# Twilio Agent Connect Python SDK + +This is a test GitHub Pages deployment for TAC Python SDK documentation. + +## Quick Links + +- [API Reference](api-reference.md) +- [GitHub Repository](https://github.com/twilio/twilio-agent-connect-python) + +## Installation + +```bash +pip install twilio-agent-connect +``` + +## Quick Start + +```python +from tac import TAC, TACConfig + +tac = TAC(config=TACConfig.from_env()) +``` + +--- + +**Note:** This is a test deployment to verify GitHub Pages functionality.