Skip to main content
Install Caesar beside the framework packages your agent already uses:
pip install caesar-search langchain-core

Python tools

from typing import Any

from caesar_search import Caesar
from langchain_core.tools import tool

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


@tool
def caesar_search(query: str, max_results: int = 5) -> list[dict[str, Any]]:
    """Search the web with Caesar and return compact, citable results."""
    response = client.search(query, max_results=max_results, verbosity="compact")
    return [
        {
            "rank": result.rank,
            "title": result.title,
            "url": result.canonical_url,
            "doc_id": result.doc_id,
            "snippet": result.snippet,
        }
        for result in response.results or []
    ]


@tool
def caesar_read(target: str, query: str | None = None, max_chars: int = 12000) -> dict[str, Any]:
    """Read a Caesar doc_id or URL as clean markdown."""
    response = client.read(target, query=query, max_chars=max_chars)
    return {
        "doc_id": response.doc.doc_id,
        "url": response.doc.canonical_url,
        "title": response.doc.title,
        "content": response.content.text if response.content else "",
        "truncated": response.content.truncated if response.content else False,
    }


tools = [caesar_search, caesar_read]
Pass tools into your agent or graph the same way you pass any other framework tool. Keep search and read separate: the first tool finds candidates cheaply, the second spends context only on the document the agent chooses.

TypeScript tools

import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { Caesar } from "caesar-search";

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

export const caesarSearch = tool(
  async ({ query, max_results }) => {
    const response = await caesar.search(query, {
      maxResults: max_results ?? 5,
      verbosity: "compact",
    });

    return (response.results ?? []).map((result) => ({
      rank: result.rank,
      title: result.title,
      url: result.canonical_url,
      doc_id: result.doc_id,
      snippet: result.snippet,
    }));
  },
  {
    name: "caesar_search",
    description: "Search the web with Caesar and return compact, citable results.",
    schema: z.object({
      query: z.string(),
      max_results: z.number().int().min(1).max(50).optional(),
    }),
  },
);

export const caesarRead = tool(
  async ({ target, query, max_chars }) => {
    const response = await caesar.read(target, {
      query,
      maxChars: max_chars ?? 12000,
    });

    return {
      doc_id: response.doc?.doc_id,
      url: response.doc?.canonical_url,
      title: response.doc?.title,
      content: response.content?.text ?? "",
      truncated: response.content?.truncated ?? false,
    };
  },
  {
    name: "caesar_read",
    description: "Read a Caesar doc_id or URL as clean markdown.",
    schema: z.object({
      target: z.string(),
      query: z.string().optional(),
      max_chars: z.number().int().min(1).max(50000).optional(),
    }),
  },
);

export const tools = [caesarSearch, caesarRead];

Feedback

Framework tool loops usually do not call feedback automatically. After the agent uses a result, call the SDK directly with the search_id and doc_id you retained from the search response:
client.feedback("result_helpful", search_id=search_id, doc_id=doc_id)

For agents

  • Use caesar_search first with compact results, then call caesar_read only for the doc_id you actually need.
  • Preserve doc_id and search_id exactly. They are opaque handles, not strings to shorten or reconstruct.
  • Do not merge search and read into one giant tool. Two smaller tools give the agent better control over context cost.
  • If your runtime supports MCP, the remote MCP server exposes the same caesar_search and caesar_read tools without custom code.