Search usually starts simple.
You add a text box, run a keyword query, and call it done.
Then real users show up.
One customer says "international SMS is failing." Another says "texts to Germany are not going through." Someone else writes "messages to +49 numbers are stuck." Those tickets might describe the same underlying issue, but a plain keyword search can easily split them apart.
That is where semantic search becomes useful.
Instead of matching only exact words, semantic search compares meaning. The query and the documents are converted into embeddings, and the app returns the items whose vectors are closest to the query vector.
This example builds that pattern for support tickets using Telnyx AI Inference.
Code:
What the example builds
The project is a small Flask API that indexes support tickets and searches them by meaning.
It includes:
- a bundled
sample_tickets.jsondataset - Telnyx embeddings through
POST /v2/ai/openai/embeddings - an in-memory numpy vector store
- cosine similarity ranking
- endpoints for indexing, searching, adding tickets, viewing stats, and health checks
The flow looks like this:
Support tickets
-> embed subject + body
-> store vectors in memory
-> embed search query
-> compare with cosine similarity
-> return top matching tickets
The point of the sample is not to replace a production search stack in 100 lines of Python. The point is to make the core pattern visible and runnable.
The API shape
The app exposes:
POST /indexto build the embedding indexPOST /searchto search tickets by meaningPOST /ticketsto add a new ticket to the indexGET /tickets/<id>to fetch one ticketGET /statsto inspect the current indexGET /healthto check service status
After the app starts, you build the index:
curl -X POST http://localhost:5000/index
That embeds the bundled support tickets and stores their vectors in memory.
Then you can ask a natural-language question:
curl -X POST http://localhost:5000/search \
-H "Content-Type: application/json" \
-d '{
"query": "I cannot send text messages to international phone numbers",
"top_k": 3
}' | python3 -m json.tool
The response returns the closest ticket matches:
{
"query": "I cannot send text messages to international phone numbers",
"model": "thenlper/gte-large",
"total_indexed": 15,
"results": [
{
"ticket_id": "TKT-1002",
"subject": "International SMS routing issue",
"category": "messaging",
"priority": "high",
"score": 0.9077
}
]
}
That score is a similarity score, not a keyword count. A ticket can rank well even when the wording is different from the query.
How it uses Telnyx embeddings
The app sends text to:
POST /v2/ai/openai/embeddings
The default model is:
EMBEDDING_MODEL=thenlper/gte-large
In the sample, each ticket is represented by its subject and body:
"{subject}. {body}"
Those strings are sent to the embeddings endpoint in batches. The returned vectors are stored in a numpy matrix.
When a search request arrives, the app embeds the query, compares it to every ticket vector, sorts by similarity, and returns the top matches.
That is the core loop behind many retrieval systems:
- embed the documents
- embed the query
- compare vectors
- return the nearest results
Why support tickets are a good example
Support data is messy in a way that makes semantic search feel practical.
Customers do not use your internal taxonomy. They describe symptoms. They mention regions, carriers, products, numbers, error codes, partial context, or business impact.
For example, these might all be related:
- "SMS to France is delayed"
- "messages to EU numbers are not delivering"
- "international text routing problem"
- "DLRs are missing for Germany"
Keyword search can still help, especially for identifiers and exact error codes. But semantic search gives you another layer: it can surface related tickets even when the language changes.
That makes it useful for:
- duplicate ticket detection
- support-agent assist tools
- internal help desk search
- incident triage
- routing similar issues to the same team
- finding historical tickets before opening a new case
Running the example
Clone the repo:
git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/semantic-search-python
Create your environment file:
cp .env.example .env
Add your Telnyx API key:
TELNYX_API_KEY=your_telnyx_api_key
EMBEDDING_MODEL=thenlper/gte-large
HOST=127.0.0.1
PORT=5000
Install dependencies and start the Flask app:
pip install -r requirements.txt
python app.py
Check health:
curl http://localhost:5000/health
Index the sample tickets:
curl -X POST http://localhost:5000/index
Try a few searches:
curl -X POST http://localhost:5000/search \
-H "Content-Type: application/json" \
-d '{"query":"audio quality problems on calls","top_k":3}' | python3 -m json.tool
curl -X POST http://localhost:5000/search \
-H "Content-Type: application/json" \
-d '{"query":"my API key stopped working after I changed it","top_k":3}' | python3 -m json.tool
curl -X POST http://localhost:5000/search \
-H "Content-Type: application/json" \
-d '{"query":"phone number transfer is taking too long","top_k":3}' | python3 -m json.tool
What to change for production
The example uses in-memory storage so the mechanics are easy to inspect. A production version would probably move the vector layer into a dedicated database or search system.
Possible next steps:
- store embeddings in pgvector, Qdrant, Weaviate, Pinecone, Redis, or another vector store
- persist ticket metadata in Postgres
- update vectors incrementally when tickets change
- combine semantic search with filters for category, priority, date range, or customer account
- add authentication to the Flask endpoints
- log search queries and clicked results for relevance tuning
- use similarity thresholds to detect likely duplicates
- add keyword search for exact IDs, phone numbers, or error codes
The important part is the separation of concerns.
Telnyx AI Inference turns text into vectors. Your application decides how to store, rank, filter, and present the results.
That makes semantic search feel less like a black box and more like normal application architecture.