Web search¶
Any catalog model can search the web on demand. Ask for it in the request and Keln runs the search on its side, feeds the results to the model, bills the search, and returns the answer with citations and, when you ask, the sources it saw. It works the same on all three surfaces, with each surface's native shape, so Codex, Claude Code, and the OpenAI and Anthropic SDKs need no extra setup.
Price: $10 per 1,000 searches, on top of tokens. A search is one query, whatever the number of results. Errors, capped attempts, and searches the model did not run are never billed.
Turning it on¶
Search is off unless the request asks for it.
Either of these, they are equivalent:
{ "model": "zai-org/glm-5.3", "messages": [...], "web_search_options": {} }
{ "model": "zai-org/glm-5.3", "messages": [...], "plugins": [{ "id": "web" }] }
OpenAI's hosted tool, unchanged. Codex sends this by default.
{ "model": "zai-org/glm-5.3", "input": "...", "tools": [{ "type": "web_search" }] }
Anthropic's server tool, unchanged. Claude Code's WebSearch sends this.
{ "model": "zai-org/glm-5.3", "messages": [...],
"tools": [{ "type": "web_search_20250305", "name": "web_search", "max_uses": 5 }] }
What comes back¶
The model decides when to search, and may search more than once in a turn. Results reach the model as ranked excerpts, up to 10 per search. What you see:
| Surface | Search activity | Citations | Sources on request |
|---|---|---|---|
| Chat completions | in usage only | message.annotations[], OpenAI's url_citation shape plus content, the excerpt the claim rests on |
web_search_options.include_sources: true → message.web_search_sources[] |
| Responses | a web_search_call item per search, with its lifecycle events and action.query |
url_citation annotations on output_text |
include: ["web_search_call.action.sources"] → action.sources on the item |
| Anthropic | server_tool_use and web_search_tool_result blocks, results included |
citations on the text block |
always, in the result block |
Citations are the links the model wrote. Keln asks the model to cite as markdown links named by the domain, keeps the link visible in the text, and turns each one into an annotation with real character offsets. Keln never adds a citation the model did not make.
usage carries the count on every surface: usage.web_search_requests on chat and Responses,
usage.server_tool_use.web_search_requests on Anthropic. usage.cost includes the fee.
Limits and knobs¶
- Searches per turn: 5 by default, 10 at most. Set
web_search_options.max_useson chat, or the tool'smax_useson Anthropic. Past the cap the model is asked to answer with what it has. - Results per search: 10, fixed.
- Your own tools still work. Search is offered alongside them under
tool_choice: "auto". With"none"or a forced choice, search is not offered and the request is served as before, named on thex-keln-inert-paramsheader. A function tool of yours namedweb_searchtogether with the opt-in is refused with400web_search_name_reserved. - Search results do not persist across turns on chat and Responses, same as OpenAI: the model's answer carries what it learned. On the Anthropic surface the result block you receive is the state, so sending it back restores the results for the next turn.
- Accepted, not applied:
search_context_size,user_locationand domainfiltersare named on thex-keln-inert-paramsheader and otherwise ignored.
Privacy¶
Search queries go to a third-party search engine and nowhere else. Results are relayed, not stored. Keln's logs record the length of a query and the number of results, never the text. Nothing about your account turns search on by default.
Errors¶
| Code | Meaning |
|---|---|
web_search_name_reserved |
the request opts into search and also defines a function tool named web_search |
web_search_unavailable |
search is not enabled on this deployment |
A search that fails on the engine side is not an error to you: the model is told, answers with what it knows, and the search is not billed.