Claude tool use with web search

Give Claude real-time web search with ODEN through tool use: define one tool, handle the tool_use block, and return a synthesized answer with citations.

Use case1 min readUpdated 2026-07-30

Claude can use tools you define. Define one tool that calls ODEN and Claude gains real-time web search returning a synthesized answer plus citations, EU-hosted.

Define the tool#

tools = [{
    "name": "web_search",
    "description": "Search the live web for current information and return a synthesized answer with citations.",
    "input_schema": {
        "type": "object",
        "properties": {
            "query": {"type": "string", "description": "The search query"}
        },
        "required": ["query"],
    },
}]

Handle the tool_use block#

Call Claude; when it returns a tool_use block, call ODEN and send the result back as a tool_result:

import os, requests
from anthropic import Anthropic

client = Anthropic()

def oden_search(query: str) -> str:
    r = requests.post(
        "https://api.oden-api.com/search",
        headers={"Authorization": f"Bearer {os.environ['ODEN_KEY']}"},
        json={"query": query, "include_snippets": True},
        timeout=30,
    )
    r.raise_for_status()
    d = r.json()["results"]
    cites = "\n".join(f"- {c['title']}: {c['url']}" for c in d["citations"])
    return f"{d.get('answer','')}\n\nSources:\n{cites}"

messages = [{"role": "user", "content": "What are the latest EU AI Act deadlines?"}]
resp = client.messages.create(model="claude-sonnet-5", max_tokens=1024, tools=tools, messages=messages)

if resp.stop_reason == "tool_use":
    tool_use = next(b for b in resp.content if b.type == "tool_use")
    result = oden_search(tool_use.input["query"])
    messages.append({"role": "assistant", "content": resp.content})
    messages.append({"role": "user", "content": [{
        "type": "tool_result", "tool_use_id": tool_use.id, "content": result,
    }]})
    final = client.messages.create(model="claude-sonnet-5", max_tokens=1024, tools=tools, messages=messages)
    print("".join(b.text for b in final.content if b.type == "text"))

Notes#

  • Append the assistant's tool_use turn and your tool_result before the follow-up call — Claude needs both to continue.
  • Ask Claude in the system prompt to cite the returned sources inline.
  • ODEN answers in the query's language, which pairs well with Claude's multilingual output.

FAQ#

Does ODEN work with Claude's own web search tool?#

They are alternatives. Claude's built-in web tools are one option; ODEN is a provider-independent, EU-hosted alternative you control and meter yourself.

Can I stream Claude's final answer?#

Yes. Stream the final messages.create call as usual — ODEN's result is already in the message history by then. ODEN itself returns a complete response rather than streaming.

Which Claude models support tool use?#

The current Claude models all support tool use with the tools parameter shown above.

Build it on the free tier
1,000 searches a month, no card required.