no key · no human
HomeNews API for AI agentsAnthropic tool use definition

Anthropic tool use

The same tool in Claude's format. The only difference from OpenAI's is input_schema instead of parameters.

Tool definitions

python
TOOLS = [
    {
        "name": "search_news",
        "description": (
            "Search worldwide news articles published in the last 30 days. Returns title, "
            "description, publisher, country, language, publication time and URL. Use it "
            "whenever the question touches current events or anything that may have changed "
            "recently. The service is free and needs no API key."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "q": {
                    "type": "string",
                    "description": (
                        "Search phrase. Every word must appear in the article, so use 2-5 "
                        "meaningful keywords, not a natural-language question."
                    ),
                },
                "country": {"type": "string", "description": "ISO 3166-1 alpha-2 codes, comma separated, uppercase."},
                "lang":    {"type": "string", "description": "ISO 639-1 codes, comma separated, lowercase."},
                "date":    {"type": "string", "enum": ["today", "yesterday", "24h", "48h", "7d", "30d"]},
                "sort":    {"type": "string", "enum": ["date", "relevance", "crawled"]},
                "size":    {"type": "integer", "description": "1-100. Use 5-10 to answer, 50-100 to build a dataset."},
                "full_text": {
                    "type": "boolean",
                    "description": "Include article bodies. Roughly 900 tokens each \u2014 scan first, then fetch.",
                },
            },
            "required": ["q"],
        },
    },
    {
        "name": "get_article",
        "description": "Fetch the full text of one article by its exact URL from a search_news result.",
        "input_schema": {
            "type": "object",
            "properties": {"url": {"type": "string"},
            "required": ["url"],
        },
    },
    {
        "name": "news_stats",
        "description": (
            "Count matching articles by country, language, domain zone, publisher and day. "
            "Use it to measure coverage without spending tokens on article text."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "q":    {"type": "string"},
                "date": {"type": "string", "enum": ["today", "yesterday", "24h", "48h", "7d", "30d"]},
                "top":  {"type": "integer"},
            },
            "required": ["q"],
        },
    },
]

Conversation loop

python
import json, requests
from anthropic import Anthropic

BASE = "https://freenewsapi.ai"
client = Anthropic()

ENDPOINT = {"search_news": "/v1/search", "get_article": "/v1/article", "news_stats": "/v1/stats"}


def run_tool(name, args):
    r = requests.get(BASE + ENDPOINT[name], params=args, timeout=30)
    if r.status_code == 429:
        return {"error": "rate limited, wait 2 seconds and retry"}
    if r.status_code >= 500:
        return {"error": "news service unavailable, do not retry immediately"}
    if r.status_code >= 400:
        return {"error": r.json().get("detail", "bad request"), "retry": False}
    return r.json()


def ask(question, system=""):
    msgs = [{"role": "user", "content": question}]
    while True:
        resp = client.messages.create(
            model="claude-opus-5", max_tokens=4096,
            system=system, tools=TOOLS, messages=msgs,
        )
        msgs.append({"role": "assistant", "content": resp.content})

        if resp.stop_reason != "tool_use":
            return "".join(b.text for b in resp.content if b.type == "text")

        results = []
        for b in resp.content:
            if b.type == "tool_use":
                out = run_tool(b.name, b.input)
                results.append({
                    "type": "tool_result",
                    "tool_use_id": b.id,
                    "content": json.dumps(out)[:60000],
                })
        msgs.append({"role": "user", "content": results})


print(ask("Summarise what Ukrainian outlets reported today about the energy grid. "
          "Cite each claim with its source URL."))

Claude will happily issue several tool_use blocks in one turn — for example searching three countries at once. The loop above handles that by collecting every result before replying, which is the whole point: one round trip instead of three.

How this differs from the OpenAI shape

Three differences, and all three cause bugs when code is ported between the two.

  • input_schema, not parameters. The JSON Schema inside is identical; only the wrapper key changes.
  • Tool results are user-role content. There is no tool role. Results go back as a user message containing tool_result blocks, each carrying the tool_use_id it answers.
  • The assistant turn is a list of blocks. One turn can hold text and several tool_use blocks at once, so you cannot assume a single call and you must append the whole content list back into the conversation, not just the text.

Knowing when not to call

The failure mode that costs most in practice is not a missed call, it is a needless one: an agent that searches the news for questions about arithmetic, about the user's own code, or about stable facts. Every such call burns a round trip and several thousand tokens on an answer the model already had.

Give it an explicit boundary in the system prompt rather than hoping. The prompts page has one written for exactly this, including the harder half — the cases where the model should search but does not, because it does not realise its knowledge is stale.

Handling a refusal to stop

A loop like the one above runs until stop_reason is not tool_use. In rare cases a model will keep searching with reworded queries and never converge. Cap the iterations — five is generous — and on the last turn strip the tools from the request so the model has no choice but to answer from what it already gathered.

Next

Add a system prompt so the model knows when to reach for the tool, and read token costs before you let it set full_text on its own. For other model families — Qwen, DeepSeek, MiniMax, Gemini and the rest — see connect any model.