How live web search works, what it sends, and how retrieved text is contained.
Breeze can search the live web, and the sources come back cited. Search runs through Tavily, falling back to a keyless provider when Tavily is unavailable.
This is the only feature that leaves your machine
Everything else in Breeze talks to localhost. Only the search query the model
wrote and the pages it reads cross the boundary -- never your transcript.
The Web search toggle in the chat settings popover starts on, and the composer remembers it along with your other switches.
Leaving it on is cheap, because the flag does not decide anything on its own. A short acquire pass decides per message whether the web is actually needed, and the answer model is picked from what that pass did -- so "hi" is still answered by the small local model. See model selection.
A turn runs in two phases: acquire, then answer.
A short, non-streaming call on the tool-capable model whose only job is to decide whether the web is needed, and to run the tools if it is. It never writes the answer, so nothing from this pass reaches you.
Tool calls run in parallel. Each returns Evidence -- a title, a URL and
text -- rather than raw JSON.
One streaming call. The results arrive as a numbered evidence block inside the
user turn, not as tool-role messages, and the model cites them as [1],
[2].
Why the evidence block is the interesting part
Putting results in the user turn instead of a tool message is what lets any model answer from them -- including the generative-UI model, which cannot call tools at all. That single change is why search and widgets now compose.
| Tool | What it does |
|---|---|
web_search | Tavily, falling back to a keyless DuckDuckGo HTML search when Tavily errors or runs out of credits. |
fetch_url | Reads one page, when you name a URL or a result needs its full text. |
Both are defined in backend/tools.py in OpenAI function-calling format.
Credit exhaustion arrives as an ordinary exception from the SDK -- and so does a bad key, a network blip or a timeout. All of them take the same fallback, so a dry Tavily quota degrades your answer rather than breaking your turn. If the fallback also fails, the model answers from its own knowledge.
Anything fetched from the open web is attacker-controlled: a page can contain
text written specifically to be read by a model. Breeze treats it as data, in
layers, and every retrieved byte passes the same choke point (backend/evidence.py)
regardless of which tool fetched it.
Tavily is optional. Without a key, web_search falls straight through to the
keyless fallback.
TAVILY_API_KEY=your-tavily-keyWith Langfuse on, the acquire pass runs under tools.acquire and the answer
under chat.stream_responses, so the decision and the answer are separate spans
in one trace. See Langfuse.