Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,5 @@ Both APIs are fully supported with sync/async variants and streaming support.

- **Core**: `pydantic>=2`, `httpx>=0.27`, `twilio>=9.8.3`
- **Server** (optional): `fastapi`, `uvicorn`, `python-multipart` — install with `pip install tac[server]`
- **Dev**: `pytest`, `ruff`, `mypy`, `openai`, `openai-agents`
- **Dev**: `pytest`, `ruff`, `mypy`
- **Examples**: `openai`, `openai-agents`, `langchain-core`, `langchain-openai`, `boto3`, `strands-agents`, `python-dotenv`
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ For detailed architecture and advanced usage, see [CLAUDE.md](https://github.com

**Examples & Guides:**
- **[Getting Started Guide](https://github.com/twilio/twilio-agent-connect-python/tree/main/getting_started/)** - Setup wizard, examples, and comprehensive documentation
- **[Partner SDK Examples](https://github.com/twilio/twilio-agent-connect-python/tree/main/getting_started/examples/partners/)** - Integration examples for OpenAI, AWS Bedrock Agent, AWS Bedrock AgentCore, and AWS Strands
- **[Partner SDK Examples](https://github.com/twilio/twilio-agent-connect-python/tree/main/getting_started/examples/partners/)** - Integration examples for OpenAI (Chat Completions, Responses API, Agents SDK), LangChain, AWS Bedrock Agent, AWS Bedrock AgentCore, and AWS Strands
- **[ConversationRelay-Only Mode](https://github.com/twilio/twilio-agent-connect-python/blob/main/getting_started/examples/features/relay_only.py)** - Get started with voice using just ConversationRelay
- More examples coming soon

Expand Down
4 changes: 4 additions & 0 deletions getting_started/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ Learn the core pattern for manually extracting and injecting TAC memory into **a
Production-ready examples integrating TAC with partner SDKs:
- **`openai_chat_completions.py`**: OpenAI Chat Completions API with automatic memory injection via `with_tac_memory()`
- **`openai_responses_api.py`**: OpenAI Responses API with automatic memory injection
- **`openai_agents.py`**: OpenAI Agents SDK integration
- **`langchain.py`**: LangChain integration
- **`aws_bedrock_agent.py`**: AWS Bedrock Agent integration
- **`aws_bedrock_agentcore.py`**: AWS Bedrock AgentCore integration
- **`aws_strands.py`**: AWS Strands agents integration
Expand Down Expand Up @@ -85,6 +87,8 @@ walks up from the script's directory, so it'll find
uv run getting_started/examples/overview.py
uv run getting_started/examples/partners/openai_chat_completions.py
uv run getting_started/examples/partners/openai_responses_api.py
uv run getting_started/examples/partners/openai_agents.py
uv run getting_started/examples/partners/langchain.py
uv run getting_started/examples/partners/aws_bedrock_agent.py
uv run getting_started/examples/partners/aws_bedrock_agentcore.py
uv run getting_started/examples/partners/aws_strands.py
Expand Down
2 changes: 1 addition & 1 deletion getting_started/examples/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ TWILIO_WHATSAPP_NUMBER=whatsapp:+1xxxxxxxxxx
# Partner Examples - OpenAI
# ============================================================================

# Required for partners/openai_*.py examples
# Required for partners/openai_*.py and partners/langchain.py examples
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# ============================================================================
Expand Down
130 changes: 130 additions & 0 deletions getting_started/examples/partners/langchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""
Example: Using LangChain with TAC

Demonstrates how to connect Twilio Agent Connect with LangChain
for voice and SMS channels.

Prerequisites:
pip install langchain-core langchain-openai

Environment Variables:
OPENAI_API_KEY - Your OpenAI API key
"""

from dotenv import load_dotenv
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI

from tac import TAC, TACConfig
from tac.adapters import MemoryPromptBuilder
from tac.channels.sms import SMSChannel, SMSChannelConfig
from tac.channels.voice import VoiceChannel, VoiceChannelConfig
from tac.core.logging import get_logger
from tac.models.session import ConversationSession
from tac.models.tac import TACMemoryResponse
from tac.server import TACFastAPIServer

load_dotenv()

logger = get_logger(__name__)

# Initialize TAC with configuration from environment variables
tac = TAC(config=TACConfig.from_env())

# Create channel handlers for Voice and SMS
voice_channel = VoiceChannel(tac, config=VoiceChannelConfig(memory_mode="once"))
sms_channel = SMSChannel(tac, config=SMSChannelConfig(memory_mode="always"))

# Create LangChain LLM
llm = ChatOpenAI(model="gpt-5.4-mini", temperature=0)

# Create prompt template with conversation history support
prompt_template = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a customer service agent speaking with a user over voice or SMS. "
"Keep responses short and conversational — a sentence or two. "
"Do not use markdown, asterisks, bullets, or emojis; your words will be "
"spoken aloud or sent as plain text.",
),
MessagesPlaceholder(variable_name="chat_history"),
("user", "{user_message}"),
]
)

# Create chain using LCEL (pipe operator)
chain = prompt_template | llm

# Store conversation history per conversation
conversation_history: dict[str, list[HumanMessage | AIMessage]] = {}
Comment on lines +60 to +61


async def handle_message_ready(
user_message: str,
context: ConversationSession,
memory_response: TACMemoryResponse | None,
) -> str:
"""
Callback invoked when a message is ready to be processed.

This example uses LangChain with LCEL to process the message
and maintains conversation history per conversation.

Args:
user_message: The customer's message text
context: Session data (conversation_id, channel, profile, etc.)
memory_response: Optional retrieved memories (observations, summaries, communications)

Returns:
Response string to send to the channel
"""
conv_id = context.conversation_id

try:
# Initialize conversation history for new conversations
if conv_id not in conversation_history:
conversation_history[conv_id] = []

# Compose user message with memory context (user message first, then memory)
prompt = MemoryPromptBuilder.compose(
system_prompt=user_message,
memory_response=memory_response,
context=context,
)

# Invoke the chain with conversation history and composed prompt
response = await chain.ainvoke(
{
"chat_history": conversation_history[conv_id],
"user_message": prompt,
}
)
Comment on lines +90 to +103

# Extract text content from the response
llm_response = response.content if hasattr(response, "content") else str(response)

# Update conversation history
conversation_history[conv_id].append(HumanMessage(content=user_message))
conversation_history[conv_id].append(AIMessage(content=llm_response))

return llm_response

except Exception as e:
logger.error("Error processing message", conversation_id=conv_id, error=str(e))
return "Sorry, I encountered an error processing your message."


# Register the message handler callback
tac.on_message_ready(handle_message_ready)

if __name__ == "__main__":
# TACFastAPIServer creates a FastAPI app with all required endpoints:
# - /twiml: Voice call webhook (returns TwiML with ConversationRelay)
# - /ws: WebSocket endpoint for Voice channel
# - /webhook: Conversation webhook for all channels
server = TACFastAPIServer(
tac=tac, voice_channel=voice_channel, messaging_channels=[sms_channel]
)
server.start()
102 changes: 102 additions & 0 deletions getting_started/examples/partners/openai_agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""
Example: Using OpenAI Agents SDK with TAC

Demonstrates how to connect Twilio Agent Connect with the OpenAI Agents SDK
for voice and SMS channels.

Prerequisites:
pip install openai-agents

Environment Variables:
OPENAI_API_KEY - Your OpenAI API key (optional, uses default client if not set)
"""

from agents import Agent, Runner
from dotenv import load_dotenv

from tac import TAC, TACConfig
from tac.adapters import MemoryPromptBuilder
from tac.channels.sms import SMSChannel, SMSChannelConfig
from tac.channels.voice import VoiceChannel, VoiceChannelConfig
from tac.core.logging import get_logger
from tac.models.session import ConversationSession
from tac.models.tac import TACMemoryResponse
from tac.server import TACFastAPIServer

load_dotenv()

logger = get_logger(__name__)

# Initialize TAC with configuration from environment variables
tac = TAC(config=TACConfig.from_env())

# Create channel handlers for Voice and SMS
voice_channel = VoiceChannel(tac, config=VoiceChannelConfig(memory_mode="once"))
sms_channel = SMSChannel(tac, config=SMSChannelConfig(memory_mode="always"))

# Create OpenAI Agent with instructions
agent = Agent(
name="Customer Service Agent",
model="gpt-5.4-mini",
instructions=(
"You are a customer service agent speaking with a user over voice or SMS. "
"Keep responses short and conversational — a sentence or two. "
"Do not use markdown, asterisks, bullets, or emojis; your words will be "
"spoken aloud or sent as plain text."
),
)


async def handle_message_ready(
user_message: str,
context: ConversationSession,
memory_response: TACMemoryResponse | None,
) -> str:
"""
Callback invoked when a message is ready to be processed.

This example uses the OpenAI Agents SDK to run a stateless agent.

Args:
user_message: The customer's message text
context: Session data (conversation_id, channel, profile, etc.)
memory_response: Optional retrieved memories (observations, summaries, communications)

Returns:
Response string to send to the channel
"""
conv_id = context.conversation_id

try:
# Compose user message with memory context (user message first, then memory)
prompt = MemoryPromptBuilder.compose(
system_prompt=user_message,
memory_response=memory_response,
context=context,
)

# Run the agent with the composed prompt
result = await Runner.run(agent, prompt)
Comment on lines +71 to +79

# Extract the agent's response
llm_response = result.final_output or "No response from agent."

return llm_response

except Exception as e:
logger.error("Error processing message", conversation_id=conv_id, error=str(e))
return "Sorry, I encountered an error processing your message."


# Register the message handler callback
tac.on_message_ready(handle_message_ready)

if __name__ == "__main__":
# TACFastAPIServer creates a FastAPI app with all required endpoints:
# - /twiml: Voice call webhook (returns TwiML with ConversationRelay)
# - /ws: WebSocket endpoint for Voice channel
# - /webhook: Conversation webhook for all channels
server = TACFastAPIServer(
tac=tac, voice_channel=voice_channel, messaging_channels=[sms_channel]
)
server.start()
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ examples = [
"boto3>=1.34.0",
"boto3-stubs[bedrock-agent-runtime,bedrock-agentcore]>=1.34.0",
"strands-agents>=1.30.0",
# LangChain examples
"langchain-core>=1.3.0",
"langchain-openai>=1.2.0",
# Common
"python-dotenv>=1.0.0,<2",
"fastapi>=0.115.0,<1",
Expand Down
Loading
Loading