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

# Build your own ChatGPT-like application

> Integrate images via tool calling to bring your chatbot to life

The [quickstart guide](/guides/setup) walks you through building a functional agent. However, we can go further and add more features to it. This guide demonstrates how you can
utilize tool calling to add advanced features to the agent.

Let's assume you want to build an agent that can also integrate images into its responses.

If you have followed the quickstart guide with C1Chat, your backend endpoint may look somewhat like this:

<CodeGroup>
  ```ts app/api/chat/route.ts [expandable] theme={null}
  import { NextRequest, NextResponse } from "next/server";
  import OpenAI from "openai";
  import type { ChatCompletionMessageParam } from "openai/resources.mjs";
  import { transformStream } from "@crayonai/stream";
  import { getMessageStore } from "./messageStore";

  export async function POST(req: NextRequest) {
    const { prompt, threadId, responseId } = (await req.json()) as {
      prompt: ChatCompletionMessageParam & { id: string };
      threadId: string;
      responseId: string;
    };

    const client = new OpenAI({
      baseURL: "https://api.thesys.dev/v1/embed",
      apiKey: process.env.THESYS_API_KEY, // Use the API key you created in the previous step
    });

    const messageStore = getMessageStore(threadId);
    messageStore.addMessage(prompt);

    const llmStream = await client.chat.completions.create({
      model: "c1/anthropic/claude-sonnet-4/v-20251230",
      messages: messageStore.getOpenAICompatibleMessageList(),
      stream: true,
    });

    // Unwrap the OpenAI stream to a C1 stream
    const responseStream = transformStream(
      llmStream,
      (chunk) => {
        return chunk.choices[0].delta.content;
      },
      {
        onEnd: ({ accumulated }) => {
          const message = accumulated.filter((chunk) => chunk).join("");
          messageStore.addMessage({
            id: responseId,
            role: "assistant",
            content: message,
          });
        },
      }
    ) as ReadableStream<string>;

    return new NextResponse(responseStream, {
      headers: {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache, no-transform",
        Connection: "keep-alive",
      },
    });
  }
  ```

  ```js app/api/chat/route.js [expandable] theme={null}
  import { NextResponse } from "next/server";
  import OpenAI from "openai";
  import { transformStream } from "@crayonai/stream";
  import { getMessageStore } from "./messageStore";

  export async function POST(req) {
    const { prompt, threadId, responseId } = await req.json();

    const client = new OpenAI({
      baseURL: "https://api.thesys.dev/v1/embed",
      apiKey: process.env.THESYS_API_KEY, // Use the API key you created in the previous step
    });

    const messageStore = getMessageStore(threadId);
    messageStore.addMessage(prompt);

    const llmStream = await client.chat.completions.create({
      model: "c1/anthropic/claude-sonnet-4/v-20251230",
      messages: messageStore.getOpenAICompatibleMessageList(),
      stream: true,
    });

    // Unwrap the OpenAI stream to a C1 stream
    const responseStream = transformStream(
      llmStream,
      (chunk) => {
        return chunk.choices[0].delta.content;
      },
      {
        onEnd: ({ accumulated }) => {
          const message = accumulated.filter((chunk) => chunk).join("");
          messageStore.addMessage({
            id: responseId,
            role: "assistant",
            content: message,
          });
        },
      }
    );

    return new NextResponse(responseStream, {
      headers: {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache, no-transform",
        Connection: "keep-alive",
      },
    });
  }
  ```
</CodeGroup>

To make the agent integrate images into its responses, you would need to:

* Tell the agent to use images in its responses
* Tell the agent how to get the images to be used in the response

<Steps>
  <Step title="Add a system prompt">
    The first step is to tell the agent to use images in its responses, since C1 does not do this by default. You can do this by adding a [system / developer prompt](https://platform.openai.com/docs/guides/prompt-engineering#messages-and-roles).
    This is also how you can customize the tone and behaviour of the agent.

    ```ts app/api/chat/systemPrompt.ts theme={null}
    export const systemPrompt = `
    You are a helpful and friendly AI assistant. Here are some rules you must follow:

    Rules:
    - Include images in your responses wherever they can make the responses more visually appealing or helpful.
    - The images must be from the 'getImageSrc' tool. Pass the alt text of the image to the 'getImageSrc' tool to get an image src.
    `;
    ```
  </Step>

  <Step title="Use the system prompt">
    If you have followed the quickstart guide, you would have a message history store that persists the conversation state. You can add a system prompt to
    each new thread as follows:

    <CodeGroup>
      ```ts app/api/chat/messageStore.ts {14} theme={null}
      import OpenAI from "openai";
      import { systemPrompt } from "./systemPrompt";

      export type DBMessage = OpenAI.Chat.ChatCompletionMessageParam & {
        id?: string;
      };

      const messagesStore: {
        [threadId: string]: DBMessage[];
      } = {};

      export const getMessageStore = (id: string) => {
        if (!messagesStore[id]) {
          messagesStore[id] = [{ role: "system", content: systemPrompt }];
        }
        const messageList = messagesStore[id];
        return {
          addMessage: (message: DBMessage) => {
            messageList.push(message);
          },
          messageList,
          getOpenAICompatibleMessageList: () => {
            return messageList.map((m) => {
              const message = {
                ...m,
              };
              delete message.id;
              return message;
            });
          },
        };
      };
      ```

      ```js app/api/chat/messageStore.js {7} theme={null}
      import OpenAI from "openai";
      import { systemPrompt } from "./systemPrompt";

      const messagesStore = {};

      export const getMessageStore = (id) => {
        if (!messagesStore[id]) {
          messagesStore[id] = [{ role: "system", content: systemPrompt }];
        }
        const messageList = messagesStore[id];
        return {
          addMessage: (message) => {
            messageList.push(message);
          },
          messageList,
          getOpenAICompatibleMessageList: () => {
            return messageList.map((m) => {
              const message = {
                ...m,
              };
              delete message.id;
              return message;
            });
          },
        };
      };
      ```
    </CodeGroup>
  </Step>

  <Step title="Define the tool">
    Next, add a tool to the agent that it can call to fetch an image url for the response. This example uses the Google Custom Search API to fetch an image url. See
    [google-images](https://www.npmjs.com/package/google-images) package documentation and [Google Custom Search](https://developers.google.com/custom-search/v1/overview) documentation
    for more details.

    <Tip>
      For detailed information on how to use tools, see [Function
      Calling](https://platform.openai.com/docs/guides/function-calling).
    </Tip>

    First, define the tool:

    <CodeGroup>
      ```ts app/api/chat/tools.ts theme={null}
      import type { RunnableToolFunctionWithParse } from "openai/lib/RunnableFunction.mjs";
      import type { RunnableToolFunctionWithoutParse } from "openai/lib/RunnableFunction.mjs";
      import { z } from "zod";
      import { zodToJsonSchema } from "zod-to-json-schema";
      import GoogleImages from "google-images";

      const client = new GoogleImages(
        process.env.GOOGLE_CSE_ID,
        process.env.GOOGLE_API_KEY
      );

      export const tools: (
        | RunnableToolFunctionWithoutParse
        | RunnableToolFunctionWithParse<any>
      )[] = [
        {
          type: "function",
          function: {
            name: "getImageSrc",
            description: "Get the image src for the given alt text",
            parse: JSON.parse,
            parameters: zodToJsonSchema(
              z.object({
                altText: z.string().describe("The alt text of the image"),
              })
            ) as any,
            function: async ({ altText }: { altText: string }) => {
              const results = await client.search(altText, {
                size: "medium",
              });
              return results[0].url;
            },
            strict: true,
          },
        },
      ];
      ```

      ```ts app/api/chat/tools.js theme={null}
      import { z } from "zod";
      import { zodToJsonSchema } from "zod-to-json-schema";
      import GoogleImages from "google-images";

      const client = new GoogleImages(
        process.env.GOOGLE_CSE_ID,
        process.env.GOOGLE_API_KEY
      );

      export const tools = [
        {
          type: "function",
          function: {
            name: "getImageSrc",
            description: "Get the image src for the given alt text",
            parse: JSON.parse,
            parameters: zodToJsonSchema(
              z.object({
                altText: z.string().describe("The alt text of the image"),
              })
            ),
            function: async ({ altText }) => {
              const results = await client.search(altText, {
                size: "medium",
              });
              return results[0].url;
            },
            strict: true,
          },
        },
      ];
      ```
    </CodeGroup>
  </Step>

  <Step title="Pass the tool to the SDK">
    Now you can add the tool to the agent and handle the tool call. The OpenAI SDK provides a `runTools` method for convenient implementation of tool calling.
    Additionally, you can use the `message` event to add the tool call messages along with the assistant response to the message history:

    <CodeGroup>
      ```ts app/api/chat/route.ts {11, 14-16} theme={null}
      // ... other imports
      import { tools } from "./tools";

      export async function POST(req: NextRequest) {
        // ... rest of your endpoint code

        const runToolsResponse = client.beta.chat.completions.runTools({
          model: "c1/anthropic/claude-sonnet-4/v-20251230",
          messages: messageStore.getOpenAICompatibleMessageList(),
          stream: true,
          tools,
        });

        runToolsResponse.on("message", (event) => {
          messageStore.addMessage(event);
        });

        const llmStream = await runToolsResponse;

        // Unwrap the OpenAI stream to a C1 stream
        const responseStream = transformStream(llmStream, (chunk) => {
          return chunk.choices[0].delta.content;
        }) as ReadableStream<string>;

        return new NextResponse(responseStream, {
          headers: {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache, no-transform",
            Connection: "keep-alive",
          },
        });
      }
      ```

      ```js app/api/chat/route.js {11, 14-16} theme={null}
      // ... other imports
      import { tools } from "./tools";

      export async function POST(req) {
        // ... rest of your endpoint code

        const runToolsResponse = client.beta.chat.completions.runTools({
          model: "c1/anthropic/claude-sonnet-4/v-20251230",
          messages: messageStore.getOpenAICompatibleMessageList(),
          stream: true,
          tools,
        });

        runToolsResponse.on("message", (event) => {
          messageStore.addMessage(event);
        });

        const llmStream = await runToolsResponse;

        // Unwrap the OpenAI stream to a C1 stream
        const responseStream = transformStream(llmStream, (chunk) => {
          return chunk.choices[0].delta.content;
        });

        return new NextResponse(responseStream, {
          headers: {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache, no-transform",
            Connection: "keep-alive",
          },
        });
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Test it out!">
    C1 should now integrate images into its responses:

    <Frame>
      <img src="https://mintcdn.com/thesys/3DUVqK628WbZ1Rok/examples/assets/c1-image-integration.jpeg?fit=max&auto=format&n=3DUVqK628WbZ1Rok&q=85&s=974ab69d581a8165336d90dfef4fa8ec" width="5088" height="3620" data-path="examples/assets/c1-image-integration.jpeg" />
    </Frame>
  </Step>
</Steps>
