Skip to main content
Any tool-calling runtime can use Caesar with two functions: caesar_search and caesar_read. The same split appears in the MCP server and AI SDK tools.

TypeScript dispatcher

npm install caesar-search
import { Caesar } from "caesar-search";

const caesar = new Caesar(); // reads CAESAR_API_KEY; anonymous works without a key

export const caesarTools = [
  {
    name: "caesar_search",
    description: "Search the web with Caesar and return ranked results with doc_id handles.",
    input_schema: {
      type: "object",
      properties: {
        query: { type: "string" },
        max_results: { type: "integer", minimum: 1, maximum: 50 },
        response_format: { type: "string", enum: ["compact", "standard", "full"] },
      },
      required: ["query"],
    },
  },
  {
    name: "caesar_read",
    description: "Read a Caesar doc_id or URL as clean markdown.",
    input_schema: {
      type: "object",
      properties: {
        target: { type: "string" },
        query: { type: "string" },
        max_chars: { type: "integer", minimum: 1, maximum: 50000 },
        start_char: { type: "integer", minimum: 0 },
      },
      required: ["target"],
    },
  },
];

export async function runCaesarToolCall(call: {
  name: string;
  input: Record<string, unknown>;
}) {
  if (call.name === "caesar_search") {
    const query = String(call.input.query);
    const maxResults = Number(call.input.max_results ?? 8);
    const responseFormat = String(call.input.response_format ?? "compact");

    return caesar.search(query, {
      maxResults,
      verbosity: responseFormat as "compact" | "standard" | "full",
    });
  }

  if (call.name === "caesar_read") {
    const target = String(call.input.target);
    return caesar.read(target, {
      query: call.input.query ? String(call.input.query) : undefined,
      maxChars: Number(call.input.max_chars ?? 12000),
      startChar: Number(call.input.start_char ?? 0),
    });
  }

  throw new Error(`unknown tool: ${call.name}`);
}
Adapt the schema wrapper (input_schema, parameters, or similar) to your model runtime, but keep the inner field names snake_case. Those names match the API and reduce translation errors.

Python dispatcher

pip install caesar-search
from typing import Any

from caesar_search import Caesar

client = Caesar()  # reads CAESAR_API_KEY; anonymous works without a key


def run_caesar_tool_call(name: str, arguments: dict[str, Any]) -> Any:
    if name == "caesar_search":
        return client.search(
            str(arguments["query"]),
            max_results=int(arguments.get("max_results", 8)),
            verbosity=str(arguments.get("response_format", "compact")),
        ).model_dump()

    if name == "caesar_read":
        return client.read(
            str(arguments["target"]),
            query=arguments.get("query"),
            max_chars=int(arguments.get("max_chars", 12000)),
            start_char=int(arguments.get("start_char", 0)),
        ).model_dump()

    raise ValueError(f"unknown tool: {name}")

Feedback

Tool-calling loops often stop after the model produces an answer. If you can observe which result the agent used, close the loop:
await caesar.feedback("result_helpful", {
  searchId: search_id,
  docId: doc_id,
});

For agents

  • Use response_format: "compact" for initial searches. Request standard or full only when you need passages or capture provenance.
  • Pass doc_id from caesar_search into caesar_read. It is more stable than reusing the URL.
  • Preserve search_id until the task is done so feedback can be attributed to the right ranked list.
  • Do not expose API keys as tool arguments. Configure CAESAR_API_KEY in the runtime environment or use anonymous access.