> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ceramic.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Anthropic Tool Use

> Use Ceramic Search as a tool with Claude to build search-powered responses and agents

Claude's tool use lets a model call functions you define. This guide shows how to wire Ceramic Search as a client tool, so Claude can retrieve real-time web results when generating a response.

<Card title="Get your Ceramic API key" icon="key" href="https://platform.ceramic.ai/keys" arrow="true">
  Create a free account to get started.
</Card>

## Generating model responses

When Claude needs to search, it returns a `tool_use` block with `stop_reason: "tool_use"`. You execute the search, append the result as a `tool_result` in a user message, and call the API again for the final answer.

### Set environment variables

```bash theme={null}
export CERAMIC_API_KEY=your_ceramic_api_key
export ANTHROPIC_API_KEY=your_anthropic_api_key
```

### Install dependencies

<CodeGroup>
  ```bash python theme={null}
  pip install anthropic ceramic_ai
  ```

  ```bash typescript theme={null}
  npm install @anthropic-ai/sdk ceramic-ai
  ```
</CodeGroup>

### Full example

<CodeGroup>
  ```python python theme={null}
  import os
  import anthropic
  from ceramic_ai import Ceramic

  claude = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
  ceramic = Ceramic(api_key=os.getenv("CERAMIC_API_KEY"))

  TOOL_DESCRIPTION = (
      "Search the web using Ceramic.\n"
      "Use for accurate current information — news, prices, recent events, documentation, general fact checking.\n"
      "Returns up to 10 ranked results with titles, URLs, and descriptions.\n"
      "Ceramic matches exact keywords — it does not interpret natural language or synonyms automatically.\n"
      "Query rules:\n"
      "- Queries must be 2-8 words\n"
      "- Include specific entities, topics, locations, and dates\n"
      "- Do not include uninformative words such as articles (the, a, an). Avoid prepositions (on, about, in, for, of, at, by, with) unless they are within established phrases or names (United States of America, Into the Wild).\n"
      "- Keep word order meaningful (`house cat` and `cat house` return different results)\n"
      "- Good keyword query examples:\n"
      "    - \"2026 Super Bowl halftime performer\"\n"
      "    - \"climate change effects global warming impact\"\n"
      "    - \"beginner investing strategies stocks bonds basics\"\n"
      "If the search returns no useful results, retry with a more specific keyword query."
  )

  ceramic_search_tool = {
      "name": "ceramic_search",
      "description": TOOL_DESCRIPTION,
      "input_schema": {
          "type": "object",
          "properties": {
              "query": {
                  "type": "string",
                  "description": "keyword search query with 2–8 words",
              }
          },
          "required": ["query"],
      },
  }

  SYSTEM = "You have access to a web search tool. Use it to answer questions with up-to-date information."

  messages = [{"role": "user", "content": "What are the latest California tenant protection laws?"}]

  response = claude.messages.create(
      model="claude-opus-4-6",
      max_tokens=1024,
      system=SYSTEM,
      messages=messages,
      tools=[ceramic_search_tool],
  )

  while response.stop_reason == "tool_use":
      tool_use_blocks = [block for block in response.content if block.type == "tool_use"]

      # Append the full assistant message
      messages.append({"role": "assistant", "content": response.content})

      # Execute each tool call and collect results
      tool_results = []
      for tool_use in tool_use_blocks:
          if tool_use.name == "ceramic_search":
              results = ceramic.search(query=tool_use.input["query"])
              tool_results.append({
                  "type": "tool_result",
                  "tool_use_id": tool_use.id,
                  "content": str(results),
              })

      messages.append({"role": "user", "content": tool_results})

      response = claude.messages.create(
          model="claude-opus-4-6",
          max_tokens=1024,
          system=SYSTEM,
          messages=messages,
          tools=[ceramic_search_tool],
      )

  print(next((block.text for block in response.content if block.type == "text"), ""))
  ```

  ```typescript typescript theme={null}
  import Anthropic from "@anthropic-ai/sdk";
  import { Ceramic } from "ceramic-ai";

  const claude = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
  const ceramic = new Ceramic({ apiKey: process.env.CERAMIC_API_KEY });

  const TOOL_DESCRIPTION =
    "Search the web using Ceramic.\n" +
    "Use for accurate current information — news, prices, recent events, documentation, general fact checking.\n" +
    "Returns up to 10 ranked results with titles, URLs, and descriptions.\n" +
    "Ceramic matches exact keywords — it does not interpret natural language or synonyms automatically.\n" +
    "Query rules:\n" +
    "- Queries must be 2-8 words\n" +
    "- Include specific entities, topics, locations, and dates\n" +
    "- Do not include uninformative words such as articles (the, a, an). Avoid prepositions (on, about, in, for, of, at, by, with) unless they are within established phrases or names (United States of America, Into the Wild).\n" +
    "- Keep word order meaningful (`house cat` and `cat house` return different results)\n" +
    "- Good keyword query examples:\n" +
    '    - "2026 Super Bowl halftime performer"\n' +
    '    - "climate change effects global warming impact"\n' +
    '    - "beginner investing strategies stocks bonds basics"\n' +
    "If the search returns no useful results, retry with a more specific keyword query.";

  const ceramicSearchTool: Anthropic.Tool = {
    name: "ceramic_search",
    description: TOOL_DESCRIPTION,
    input_schema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "keyword search query with 2–8 words",
        },
      },
      required: ["query"],
    },
  };

  const SYSTEM = "You have access to a web search tool. Use it to answer questions with up-to-date information.";

  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: "What are the latest California tenant protection laws?" },
  ];

  let response = await claude.messages.create({
    model: "claude-opus-4-6",
    max_tokens: 1024,
    system: SYSTEM,
    messages,
    tools: [ceramicSearchTool],
  });

  while (response.stop_reason === "tool_use") {
    const toolUseBlocks = response.content.filter(
      (block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
    );

    // Append the full assistant message
    messages.push({ role: "assistant", content: response.content });

    // Execute each tool call and collect results
    const toolResults: Anthropic.ToolResultBlockParam[] = [];
    for (const toolUse of toolUseBlocks) {
      if (toolUse.name === "ceramic_search") {
        const input = toolUse.input as { query: string };
        const results = await ceramic.search({ query: input.query });
        toolResults.push({
          type: "tool_result",
          tool_use_id: toolUse.id,
          content: JSON.stringify(results),
        });
      }
    }

    messages.push({ role: "user", content: toolResults });

    response = await claude.messages.create({
      model: "claude-opus-4-6",
      max_tokens: 1024,
      system: SYSTEM,
      messages,
      tools: [ceramicSearchTool],
    });
  }

  const textBlock = response.content.find(
    (block): block is Anthropic.TextBlock => block.type === "text"
  );
  if (textBlock) console.log(textBlock.text);
  ```
</CodeGroup>

### Run the example

<CodeGroup>
  ```bash python theme={null}
  python anthropic_tool_calling.py
  ```

  ```bash typescript theme={null}
  npx tsx anthropic_tool_calling.ts
  ```
</CodeGroup>

## Building agents

The Tool Runner handles the tool-calling loop automatically. Define `ceramic_search` with the `@beta_tool` decorator (Python) or `betaZodTool` (TypeScript) and the SDK executes it and continues the conversation until Claude returns a final answer.

### Set environment variables

```bash theme={null}
export CERAMIC_API_KEY=your_ceramic_api_key
export ANTHROPIC_API_KEY=your_anthropic_api_key
```

### Install dependencies

<CodeGroup>
  ```bash python theme={null}
  pip install anthropic ceramic_ai
  ```

  ```bash typescript theme={null}
  npm install @anthropic-ai/sdk ceramic-ai zod
  ```
</CodeGroup>

### Full example

<CodeGroup>
  ```python python theme={null}
  import os
  import anthropic
  from anthropic import beta_tool
  from ceramic_ai import Ceramic

  claude = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
  ceramic = Ceramic(api_key=os.getenv("CERAMIC_API_KEY"))

  @beta_tool
  def ceramic_search(query: str) -> str:
      """Search the web using Ceramic.
      Use for accurate current information — news, prices, recent events, documentation, general fact checking.
      Returns up to 10 ranked results with titles, URLs, and descriptions.
      Ceramic matches exact keywords — it does not interpret natural language or synonyms automatically.
      Query rules:
      - Queries must be 2-8 words
      - Include specific entities, topics, locations, and dates
      - Do not include uninformative words such as articles (the, a, an). Avoid prepositions (on, about, in, for, of, at, by, with) unless they are within established phrases or names (United States of America, Into the Wild).
      - Keep word order meaningful (`house cat` and `cat house` return different results)
      - Good keyword query examples:
          - "2026 Super Bowl halftime performer"
          - "climate change effects global warming impact"
          - "beginner investing strategies stocks bonds basics"
      If the search returns no useful results, retry with a more specific keyword query.

      Args:
          query: keyword search query with 2–8 words
      """
      results = ceramic.search(query=query)
      return str(results)

  runner = claude.beta.messages.tool_runner(
      model="claude-opus-4-6",
      max_tokens=1024,
      tools=[ceramic_search],
      messages=[{"role": "user", "content": "What are the latest California tenant protection laws?"}],
  )
  final_message = runner.until_done()
  for block in final_message.content:
      if block.type == "text":
          print(block.text)
  ```

  ```typescript typescript theme={null}
  import Anthropic from "@anthropic-ai/sdk";
  import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
  import { Ceramic } from "ceramic-ai";
  import { z } from "zod";

  const claude = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
  const ceramic = new Ceramic({ apiKey: process.env.CERAMIC_API_KEY });

  const TOOL_DESCRIPTION =
    "Search the web using Ceramic.\n" +
    "Use for accurate current information — news, prices, recent events, documentation, general fact checking.\n" +
    "Returns up to 10 ranked results with titles, URLs, and descriptions.\n" +
    "Ceramic matches exact keywords — it does not interpret natural language or synonyms automatically.\n" +
    "Query rules:\n" +
    "- Queries must be 2-8 words\n" +
    "- Include specific entities, topics, locations, and dates\n" +
    "- Do not include uninformative words such as articles (the, a, an). Avoid prepositions (on, about, in, for, of, at, by, with) unless they are within established phrases or names (United States of America, Into the Wild).\n" +
    "- Keep word order meaningful (`house cat` and `cat house` return different results)\n" +
    "- Good keyword query examples:\n" +
    '    - "2026 Super Bowl halftime performer"\n' +
    '    - "climate change effects global warming impact"\n' +
    '    - "beginner investing strategies stocks bonds basics"\n' +
    "If the search returns no useful results, retry with a more specific keyword query.";

  const ceramicSearchTool = betaZodTool({
    name: "ceramic_search",
    description: TOOL_DESCRIPTION,
    inputSchema: z.object({
      query: z.string().describe("keyword search query with 2–8 words"),
    }),
    run: async ({ query }) => {
      const results = await ceramic.search({ query });
      return JSON.stringify(results);
    },
  });

  const finalMessage = await claude.beta.messages.toolRunner({
    model: "claude-opus-4-6",
    max_tokens: 1024,
    tools: [ceramicSearchTool],
    messages: [{ role: "user", content: "What are the latest California tenant protection laws?" }],
  });

  for (const block of finalMessage.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```
</CodeGroup>

### Run the example

<CodeGroup>
  ```bash python theme={null}
  python tool_runner.py
  ```

  ```bash typescript theme={null}
  npx tsx tool_runner.ts
  ```
</CodeGroup>
