Get your Ceramic API key
Create a free account to get started.
Generating model responses
When Claude needs to search, it returns atool_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
export CERAMIC_API_KEY=your_ceramic_api_key
export ANTHROPIC_API_KEY=your_anthropic_api_key
Install dependencies
pip install anthropic ceramic_ai
npm install @anthropic-ai/sdk ceramic-ai
Full example
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"), ""))
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);
Run the example
python anthropic_tool_calling.py
npx tsx anthropic_tool_calling.ts
Building agents
The Tool Runner handles the tool-calling loop automatically. Defineceramic_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
export CERAMIC_API_KEY=your_ceramic_api_key
export ANTHROPIC_API_KEY=your_anthropic_api_key
Install dependencies
pip install anthropic ceramic_ai
npm install @anthropic-ai/sdk ceramic-ai zod
Full example
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)
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);
}
}
Run the example
python tool_runner.py
npx tsx tool_runner.ts

