ai newsDi., 3. Nov.
EilmeldungClaude Message Threads Are Fast Until Your Agent Waits 5 Minutes

Building a Travel AI Agent With Agno and Qdrant (Phidata's Successor)

If you are following a Phidata tutorial from 2024, the first thing you will hit is that the package does not install the way the article says. Phidata became Agno in 2025, the imports moved, and half the travel-agent examples floating around still use the old names. This is the stack I actually run

dev.toDienstag 18:26 · 03.11.5 Min. Lesezeit
Bild: dev.to

If you are following a Phidata tutorial from 2024, the first thing you will hit is that the package does not install the way the article says. Phidata became Agno in 2025, the imports moved, and half the travel-agent examples floating around still use the old names.

This is the stack I actually run in 2026: Agno for the agent loop, Qdrant for retrieval, and an MCP server for live inventory. The parts that matter are the boundaries between them, not the framework.

The mistake I made twice: putting live prices in the vector store. Embeddings are snapshots. A price embedded last Tuesday is a wrong price with confident retrieval. Prices belong in a tool call, not in a document.

Qdrant holds the parts of travel that change slowly: neighborhood guides, cancellation policy language, visa notes, accessibility details, your own post-trip writeups. Those are the facts a model cannot know and should not guess.

from agno.vectordb.qdrant import Qdrant from agno.knowledge.embedder.openai import OpenAIEmbedder vector_db = Qdrant ( collection = " travel-notes ", url = " http://localhost:6333 ", embedder = OpenAIEmbedder ( id = " text-embedding-3-small " ), ) Enter fullscreen mode Exit fullscreen mode Chunking is where retrieval quality is won. I split by section, not by token count, and I keep the heading in every chunk. A 400-token chunk that starts mid-sentence retrieves badly, and no reranker fixes a chunk with no subject.

Two metadata fields carry most of the value: city and doc_type ( guide, policy, trip_report ). Filtering on those before vector search cut irrelevant retrievals by roughly a third in my test set. Qdrant's payload filters are cheap; use them before you reach for a bigger embedding model.

from agno.agent import Agent from agno.models.openai import OpenAIChat agent = Agent ( model = OpenAIChat ( id = " gpt-5 " ), tools = [ search_hotels, hotel_detail, search_notes ], instructions = [ " Answer with facts from tools. Never invent a hotel name or price. ", " If a required field is missing, ask one question and stop. ", " Quote prices with currency and the date they were checked. ", ], show_tool_calls = True, markdown = True, ) Enter fullscreen mode Exit fullscreen mode Three instruction lines, and the third one does more work than it looks like. "Quote the date they were checked" makes stale data visible, which is the failure users forgive and silent staleness is the one they do not.

show_tool_calls=True is not for the user. It is for me, during development, so I can see which tool produced a number before I trust the answer.

Agno calls tools; the tools decide truth. My travel tools come from an MCP server rather than a hand-written client, for one reason: the schema and the error shapes stay on the server.

{ "mcpServers": { "rollinggo-hotel": { "type": "http", "url": "https://mcp.rollinggo.ai/mcp", "headers": { "Authorization": "Bearer YOUR_API_KEY" } } } } Enter fullscreen mode Exit fullscreen mode That gives the agent three capabilities: searchHotels for a destination, date range, star rating, and tag filters; getHotelDetail for room types, rates, and cancellation rules; getHotelSearchTags for the vocabulary of filters, so it stops inventing tags like "rooftop pool".

The supply side is 2M+ properties across 200+ countries with 110,000 direct contracts, and the vendor reports 2,000+ developers and agents connected. I care about the part I can check: the response contains a hotel ID, a currency, a nightly rate, and a booking URL, and those four fields agree with each other.

What I gave up by using a hosted server instead of my own adapter: control over retries, and the ability to cache aggressively. What I got: a schema I did not have to version, and error messages a model can act on.

The subtle bug in this stack: Qdrant says "the hotel is a 10-minute walk from the station" and the tool says the hotel is closed. Two sources, one contradiction, and the model picks whichever reads better.

I handle it with precedence in the prompt and a check in code:

def merge_facts ( note_hit, hotel ): facts = { " name ": hotel [ " name " ], " price ": hotel [ " price " ], " currency ": hotel [ " currency " ]} notes = [ n for n in note_hit if n [ " metadata " ][ " hotel_id " ] == hotel [ " hotelId " ]] if notes: facts [ " walk_note " ] = notes [ 0 ][ " text " ] return facts Enter fullscreen mode Exit fullscreen mode Only attach prose from the vector store when it is keyed to the same hotel ID that came back from the tool. Fuzzy matching by name is how you get a lovely paragraph about a different property.

The retrieval number is judged by hand on 30 queries per run, which is tedious and the only way I have found to catch chunking regressions before users do.

Embedding drift. Change the embedding model and you reindex everything. Pin the model name in config, and version the collection name so a reindex can run beside the old one.

Qdrant is another service. For a weekend project, run it in Docker; for anything real, budget for backups and a collection schema you will not regret.

Framework churn. Agno has moved fast since the rename. Pin versions, and keep your tools as plain functions so a framework change costs you the wrapper, not the logic.

Prices are still moving objects. Everything in the inventory layer is a snapshot of a moment. The agent should say when.

That order is not glamorous, and it is the reason this version survived contact with users when the previous one did not. The tool schemas, install steps, and response examples are in the RollingGo Hotel MCP repository if you want to skip the adapter work and start at step three.

Quelle: dev.to (dev.to). Original öffnen