> ## 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.

# OpenAI Tool Use

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

OpenAI’s tool calling lets a model invoke functions you define. This guide shows how to implement a Ceramic Search function the model can use to 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

Ceramic Search works with both the Chat Completions API and the Responses API. Select the one that matches your project.

### Set environment variables

```bash theme={null}
export CERAMIC_API_KEY=your_ceramic_api_key
export OPENAI_API_KEY=your_openai_api_key
```

### Install dependencies

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

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

### Full example

<Tabs>
  <Tab title="Chat Completions API">
    When the model decides a search is needed, it returns a `tool_calls` message. You execute the search, return the results, and call the model again to get the final answer.

    <CodeGroup>
      ```python python theme={null}
      import os
      import json
      from openai import OpenAI
      from ceramic_ai import Ceramic

      openai = OpenAI(api_key=os.getenv("OPENAI_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 = {
          "type": "function",
          "function": {
              "name": "ceramic_search",
              "description": TOOL_DESCRIPTION,
              "parameters": {
                  "type": "object",
                  "properties": {
                      "query": {
                          "type": "string",
                          "description": "keyword search query with 2–8 words",
                      }
                  },
                  "required": ["query"],
              },
          },
      }

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

      # First call — model may decide to search
      response = openai.chat.completions.create(
          model="gpt-5.4",
          messages=messages,
          tools=[ceramic_search_tool],
          tool_choice="auto",
      )

      message = response.choices[0].message

      if message.tool_calls:
          messages.append(message)

          for tool_call in message.tool_calls:
              args = json.loads(tool_call.function.arguments)
              results = ceramic.search(query=args["query"])
              messages.append(
                  {
                      "role": "tool",
                      "tool_call_id": tool_call.id,
                      "content": str(results),
                  }
              )

          # Second call — model generates the final answer
          final_response = openai.chat.completions.create(
              model="gpt-5.4",
              messages=messages,
          )
          print(final_response.choices[0].message.content)
      else:
          print(message.content)
      ```

      ```typescript typescript theme={null}
      import OpenAI from "openai";
      import { Ceramic } from "ceramic-ai";

      const openai = new OpenAI({ apiKey: process.env.OPENAI_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: OpenAI.ChatCompletionTool = {
        type: "function",
        function: {
          name: "ceramic_search",
          description: TOOL_DESCRIPTION,
          parameters: {
            type: "object",
            properties: {
              query: {
                type: "string",
                description: "keyword search query with 2–8 words",
              },
            },
            required: ["query"],
          },
        },
      };

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

      // First call — model may decide to search
      const response = await openai.chat.completions.create({
        model: "gpt-5.4",
        messages,
        tools: [ceramicSearchTool],
        tool_choice: "auto",
      });

      const message = response.choices[0].message;

      if (message.tool_calls) {
        messages.push(message);

        for (const toolCall of message.tool_calls) {
          const args = JSON.parse(toolCall.function.arguments);
          const results = await ceramic.search({ query: args.query });
          messages.push({
            role: "tool",
            tool_call_id: toolCall.id,
            content: JSON.stringify(results),
          });
        }

        // Second call — model generates the final answer
        const finalResponse = await openai.chat.completions.create({
          model: "gpt-5.4",
          messages,
        });
        console.log(finalResponse.choices[0].message.content);
      } else {
        console.log(message.content);
      }
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Responses API">
    Maintain a running `input_list` starting with the user message. After the first call, append all output items to the list. When you find a `function_call` item, execute the search and append a `function_call_output` before calling the API again for the final answer.

    <CodeGroup>
      ```python python theme={null}
      import os
      import json
      from openai import OpenAI
      from ceramic_ai import Ceramic

      openai = OpenAI(api_key=os.getenv("OPENAI_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."
      )

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

      user_input = "What are the latest California tenant protection laws?"

      input_list = [{"role": "user", "content": user_input}]

      while True:
          response = openai.responses.create(
              model="gpt-5.4",
              input=input_list,
              tools=TOOLS,
          )
          input_list += response.output

          tool_calls = [item for item in response.output if item.type == "function_call"]

          if not tool_calls:
              print(response.output_text)
              break

          for item in tool_calls:
              if item.name == "ceramic_search":
                  query = json.loads(item.arguments)["query"]
                  results = ceramic.search(query=query)
                  input_list.append({
                      "type": "function_call_output",
                      "call_id": item.call_id,
                      "output": str(results),
                  })
      ```

      ```typescript typescript theme={null}
      import OpenAI from "openai";
      import { Ceramic } from "ceramic-ai";

      const openai = new OpenAI({ apiKey: process.env.OPENAI_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 tools: OpenAI.Responses.Tool[] = [
          {
              type: "function",
              name: "ceramic_search",
              description: TOOL_DESCRIPTION,
              strict: true,
              parameters: {
                  type: "object",
                  properties: {
                      query: {
                          type: "string",
                          description: "keyword search query with 2–8 words",
                      },
                  },
                  required: ["query"],
                  additionalProperties: false,
              },
          },
      ];

      const userInput = "What are the latest California tenant protection laws?";

      const inputList: OpenAI.Responses.ResponseInputParam[] = [
          { role: "user", content: userInput },
      ];

      while (true) {
          const response = await openai.responses.create({
              model: "gpt-5.4",
              input: inputList,
              tools,
          });
          inputList.push(...response.output);

          const toolCalls = response.output.filter((item) => item.type === "function_call");

          if (toolCalls.length === 0) {
              console.log(response.output_text);
              break;
          }

          for (const item of toolCalls) {
              if (item.type === "function_call" && item.name === "ceramic_search") {
                  const args = JSON.parse(item.arguments);
                  const results = await ceramic.search({ query: args.query });
                  inputList.push({
                      type: "function_call_output",
                      call_id: item.call_id,
                      output: JSON.stringify(results),
                  });
              }
          }
      }
      ```
    </CodeGroup>
  </Tab>
</Tabs>

### Run the example

Save the code to a file, then run it from your terminal:

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

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

## Building agents

In the Agents SDK, wrap Ceramic Search as a `function_tool` and attach it to your agent. The SDK handles the tool-calling loop automatically.

### Set environment variables

```bash theme={null}
export CERAMIC_API_KEY=your_ceramic_api_key
export OPENAI_API_KEY=your_openai_api_key
```

### Install dependencies

<CodeGroup>
  ```bash python theme={null}
  pip install openai-agents ceramic_ai
  ```

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

### Full example

<CodeGroup>
  ```python python theme={null}
  import os
  from agents import Agent, Runner, function_tool
  from ceramic_ai import Ceramic

  ceramic = Ceramic(api_key=os.getenv("CERAMIC_API_KEY"))

  @function_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)

  agent = Agent(
      name="Research assistant",
      instructions="You have access to a web search tool. Use it to answer questions with up-to-date information.",
      tools=[ceramic_search],
  )

  result = Runner.run_sync(agent, "What are the latest California tenant protection laws?")
  print(result.final_output)
  ```

  ```typescript typescript theme={null}
  import { Agent, run, tool } from "@openai/agents";
  import { Ceramic } from "ceramic-ai";
  import { z } from "zod";

  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 = tool({
    name: "ceramic_search",
    description: TOOL_DESCRIPTION,
    parameters: z.object({
      query: z.string().describe("keyword search query with 2–8 words"),
    }),
    async execute({ query }) {
      const results = await ceramic.search({ query });
      return JSON.stringify(results);
    },
  });

  const agent = new Agent({
    name: "Research assistant",
    instructions:
      "You have access to a web search tool. Use it to answer questions with up-to-date information.",
    tools: [ceramicSearchTool],
  });

  const result = await run(agent, "What are the latest California tenant protection laws?");
  console.log(result.finalOutput);
  ```
</CodeGroup>

### Run the example

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

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