Claude + live news
Native tool use with its own shape: input_schema instead of parameters, and tool results returned as user-role content blocks. Strong at deciding when not to call a tool, which matters when the question is not about news at all.
| Vendor | Anthropic, United States |
|---|---|
| Base URL | https://api.anthropic.com/v1 |
| Model id | claude-opus-5 |
| Model credential | ANTHROPIC_API_KEY |
| Tool format | Anthropic-native — input_schema rather than parameters, and tool results returned as user-role blocks. |
| News API key | none required |
| Vendor docs | https://docs.anthropic.com/en/docs/build-with-claude/tool-use |
Base URLs and model ids change often, and several vendors run separate mainland and international endpoints with non-interchangeable keys. Check the vendor docs above before copying. The part that does not change is the tool itself — our API has no key, no version negotiation and no SDK.
Working example
import json, requests
from anthropic import Anthropic
client = Anthropic() # ANTHROPIC_API_KEY
TOOLS = [{
"name": "search_news",
"description": ("Search worldwide news published in the last 30 days. "
"Free, keyless. Use 2-5 keywords, not a question."),
"input_schema": {
"type": "object",
"properties": {
"q": {"type": "string"},
"date": {"type": "string", "enum": ["today", "yesterday", "24h", "7d", "30d"]},
"lang": {"type": "string"},
"size": {"type": "integer"},
},
"required": ["q"],
},
}]
IDENT = {"agent_name": "news-agent", "software": "claude",
"model": "claude-opus-5"}
def call_api(args):
return requests.get("https://freenewsapi.ai/v1/search",
params={**args, **IDENT}, timeout=20).json()
msgs = [{"role": "user", "content": "What happened with grain exports this week?"}]
while True:
r = client.messages.create(model="claude-opus-5", max_tokens=2048,
tools=TOOLS, messages=msgs)
msgs.append({"role": "assistant", "content": r.content})
if r.stop_reason != "tool_use":
print("".join(b.text for b in r.content if b.type == "text"))
break
msgs.append({"role": "user", "content": [
{"type": "tool_result", "tool_use_id": b.id,
"content": json.dumps(call_api(b.input))[:60000]}
for b in r.content if b.type == "tool_use"
]})Worth knowing about Claude
- Tool results go back as a
usermessage containingtool_resultblocks, not as a separate role. - It will issue several tool calls in one turn — for example three countries at once. Collect every result before replying.
Test the tool without the model
Before debugging a tool-calling loop, confirm the data side works. It needs no credentials, so this runs anywhere:
curl "https://freenewsapi.ai/v1/search?q=grain+exports&date=7d&size=5"If that returns results and your loop still fails, the problem is in the model's tool handling, not in the data.
Prompt it properly
The single most common failure is the model phrasing the query as a sentence. Search is
AND across every term, so q=what happened with grain exports matches nothing.
Put this in the system prompt:
Use search_news for anything about current events or recent developments.
Phrase the query as 2-5 keywords that would appear in a headline, never as a question.
Good: "grain export prices". Bad: "what is happening with grain exports".
Scan titles first. Only request full_text for articles you will actually cite.
If a search returns nothing, drop the least essential keyword and try once more.
No results means no coverage in this corpus \u2014 not that the event did not happen.More in system prompts.
Same format
Claude uses the anthropic tool-calling shape. These take the same code with a different base URL and model name: none in this list.