How to Orchestrate Multi-Call Conversations with an LLM and Twilio Conversation Memory in Python
Time to read:
Have you ever been on the phone with an AI voice agent and gotten frustrated with its lack of memory? Maybe your agent hung up on you, or it got disconnected, forcing you to start a conversation all over again. This kind of interruption can waste time for you and your users, and cause a lot of frustration.
Twilio Conversation Memory is your solution. Conversation Memory allows context to be persisted between calls. This means that if you call the Twilio agent back, it won’t lose the context of what you were talking about when you hung up, and can pick up right where you left off. This can save you a lot of frustration and help you get things done better and faster when you’re talking to an agent.
In this tutorial, you will make a Python FastAPI service that retains caller context, preferences and action history across multiple separate inbound calls.
Prerequisites
To complete this tutorial you will need:
- A free Twilio account with a voice-capable phone number
- Python 3.11 or later
- An OpenAI API key
- ngrok to expose local webhooks to Twilio
- An IDE or text editor such as Visual Studio Code or PyCharm
Building the app
Step 1 - Set up the FastAPI project
To get started, create a new project directory and a virtual environment:
Step 2 - Install required dependencies
Install FastAPI, Uvicorn, the Twilio, OpenAI, httpx, and python-dotenv packages via pip.
The Twilio package will allow your application to interface with Twilio’s services. The python-dotenv package allows you to import your environment variables into your solution using a .env file. You will add those variables in the next step. The OpenAI package will be used to connect your solution to OpenAI, and httpx will be used to make async calls to Twilio’s Conversation Memory REST API.
Step 3 - Create a Twilio Memory Store
For this tutorial, you will need a Conversation Memory Store. Go into your Twilio console and look for Memory Stores. You can use the console search, or look for Data > Conversation Memory > Memory stores.
Memory Stores use machine learning, and you may have to agree to a warning before proceeding. Keep in mind that Conversation Memory is not intended for use with sensitive information. Conversation products are only available on the new Twilio Console, so make sure your account has been migrated. For more information about Conversation Memory, you may want to read the documentation, including the Getting Started Guide.
Once you have found the correct tab, click on Create New Store.
Now follow the steps to set up your memory store.
The console gives you a setup checklist to get you started. Click on Connect Conversation Orchestrator, and give it a friendly name. You write a short description, then can move on to Messaging and Chat Traffic. For the remainder of the items in this checklist, you can select the default values for now.
You don’t have any customer profiles yet, so you can skip the rest of the checklist. However, you will need your memory store ID, which is at the top left of the memory store screen. There should be a convenient button to copy-paste that ID. Keep that ID for the next step. When you connect the Conversation Orchestrator, the console will also show you a configuration ID (prefixed cnv_config_) — copy that value too, as you’ll need it in the next step as well.
Step 4 - Configure environment variables
Now that you have a memory store created, you will need to be able to access that from your application. For this, you will need to get your Memory Store ID and paste that into your secrets file. Create a .env file in the root directory of your project. Add the following values, replacing the placeholders.
Get your API key from your Twilio console, created under Settings > Account Settings > API Keys & Auth Tokens. Because other types of API keys do not have access to the Conversation Memory features, creating a Main API key is required for this tutorial. Keep in mind that the secret key will only be shown once, so be sure you save it. You get your memory store key from the previous step and paste it in here. Your OpenAI API Key is generated from OpenAI’s dashboard. You will also need your Twilio voice-capable phone number, which is in 10DLC format.
Save the file, and move on to the next step, creating your services.
Step 5 - Set up the OpenAI service
This demonstration uses the fiction of an auto repair shop as the agent that you are calling. However, Conversation Memory would be useful in lots of different scenarios, such as tech support, travel, and more. Feel free to adjust the audio prompts as you see fit for your own personal projects.
Create an openai_service.py module to handle interaction with gpt-4o-mini.
Paste the following into your new module:
This code handles your initial connection to OpenAI. Its function is to parse the information from a caller and stream it to the OpenAI API. Notice the system prompt here, which explains the functionality of the agent. It contains some useful instructions for the agent, such as to avoid bullet points and emojis when speaking on the phone.
This code also has an additional call to OpenAI to summarize the call itself. This will parse the conversation into a quick summary that will be stored in Twilio’s Conversation Memory. You can adjust this prompt according to your application’s needs. Keep in mind that if you don’t say anything meaningful, nothing will be stored.
Next, create the webhook for Twilio’s connection.
Step 6 - Build the Twilio webhook and Conversation Memory pipeline
Create another new file called conversation_relay_handler.py. Paste in this code:
conversation_relay_handler is the bridge between Twilio’s WebSocket and the rest of the app. When Twilio opens the socket after a <ConversationRelay> TwiML directive, handle loops reading frames one at a time. FastAPI’s receive_json assembles each frame and parses it as JSON for you. A prompt frame carries the caller’s transcribed speech. It appends the text to an in-memory conversation history and hands the whole history plus the memory context off to openai_service.stream_response, which streams tokens back out through the same socket.
The code also contains interruption handling for your agent. If the agent is interrupted during a conversation, it pops the last message off of the history so the agent realizes the full message was not sent and was incomplete. This will allow the customer to continue talking and handle the interruption in a more human way, without your user missing context.
When the call ends, the finally block hands the full conversation history off to openai_service.summarize_conversation and saves the resulting summary to Twilio via memory.finish_call, so the next call from this caller can pick up right where this one left off.
Step 7 - Process multi-turn historical context
Now you will create one more module to handle the context and memory processing. You’ll call this file conversation_memory_service.py. Paste the following into the file:
This part of the code is what will handle your multi-turn conversation.
The first thing this code does is bring in all your environment variables from .env. It then creates a CallContext using conversation_id, profile_id, and memory_context to store some information about the call. Making an API call to Conversation Summaries, it stores a Customer ID to the Memory Store that you created earlier. It also creates a profile for your caller that stores their phone number. When the call is disconnected, the summary of the call generated by OpenAI will be stored to Twilio.
If the caller calls the number back too soon, the previous call memory may still be active and trying to log. This code accounts for that by checking to see if a call is finished with finish_call. If the old call has closed out, but it recognizes the caller’s profile, the API will retrieve the summary and context of the previous call from the Memory Store. The conversation can then resume on the same topic right where it left off!
The public entry point start_call first calls _create_conversation to create (or, on a 409 conflict, reuse) a Twilio Conversation for the call, then _ensure_profile to find or create a Memory Store profile for the caller’s phone number. _ensure_profile runs a two-step lookup: first _lookup_profile_id normalizes the number, matches it against a canonical profile, and returns a profile ID (or a 404, if no previous caller with that number was found); if none exists, it creates one. If a profile was found or created, _recall posts, asking for up to 20 observations and 5 summaries. The response is logged raw for inspection and then passed to _format_recall, which parses the JSON.
The application tries several plausible field names (text, content, observation, summary, value) via _extract_text and stitches whatever it finds into a bulleted string. That string is what eventually gets prepended to the OpenAI system prompt as “Prior context on this customer,” making the caller’s history part of the model’s instructions before they’ve even spoken.
Step 8 - Finalize your application
To complete your project you will need to create a main.py file to wire up the services that you’ve created. Paste the code below into the file:
This sets up the websocket route to call your conversation_relay_handler, and includes the initial greeting for your user. Feel free to change the greeting according to your needs.
Testing, troubleshooting, or product demonstration
It is now time to test your voice application. Save your files and run the project with:
Once your webhook is running, you will need to expose it to the internet by using ngrok or another tunneling service.
Replace 8000 with whatever port your application is running on if you have a different port shown.
Now ngrok will provide you with a url for utilizing in your Twilio console. Go into your Twilio console and find the Twilio phone number that you prepared. Under the option A Call Comes In, choose Webhook, and fill in your ngrok URL followed by /voice, as shown in the graphic below:
To put your AI to the test, you’ll have to make two phone calls and check the conversation memory.
- Make call #1: Talk to the agent about a car repair issue, including some details such as make and model.
- Hang up and make call #2 from the same phone number.
- Verify the agent greets you and remembers the details of your first call without any prompting.
If your Conversation Memory feature is working properly, you should also see details and a summary of the conversation saved to your Twilio dashboard. Check your Memory Store and it should show you the logged number you called from, as well as a stored conversation under Summaries:
Conclusion
Today you have learned how Twilio Conversation Memory simplifies maintaining state across separate voice calls in Python. This should provide value to any phone AI agent, storing information that keeps conversations feeling more convenient and human.
Do you want to do more with Twilio Conversations? Explore the possibilities by checking out the conversations documentation, where you can find blueprints for Conversational Agents, AI-to-Human handoff, and more.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.