First Tool

Defining the Tool

Create your first tool. This will be called getProducts.

app/actions.tsx
// REST OF CODE

  const result = await streamUI({
    model: openai("gpt-3.5-turbo"),
    messages: [...history.get(), { role: "user", content: input }],
    text: ({ content, done }) => {
      if (done) {
        history.done((messages: ServerMessage[]) => [
          ...messages,
          { role: "assistant", content },
        ]);
      }

      return <div>{content}</div>;
    },
    tools: { 
      getProducts: { 
        description: "Get list of products for the company the user asks for.", 
        parameters: z.object({
          company: z.object({
            name: z.string(),
          }),
        }),
        generate: async function* ({ company }) {
          yield (
            <div className="animate-pulse p-4 bg-neutral-50 rounded-md">
              Loading {company.name[0].toUpperCase()}
              {company.name.slice(1)} products...
            </div>
          );
          const productsGeneration = await generateObject({
            model: openai("gpt-3.5-turbo"),
            schema: z.object({
              products: z.array(z.string()).min(3),
            }),
            prompt: `Generate realistic products for ${company.name} the user has requested. Use specific model names.`,
          });
          history.done((messages: ServerMessage[]) => [
            ...messages,
            {
              role: "assistant",
              content: `Showing products (products: ${productsGeneration.object.products.map((p) => p).join(", ")})`,
            },
          ]);
          return (
            <ul>
              {productsGeneration.object.products.map((p) => (
                <li key={p}>{p}</li>
              ))}
            </ul>
          );
        },
      },
    }
  });

// REST OF CODE

Note: We are using a generator function here (function*), which allows you to pause its execution and return a value, then resume from where it left off on the next call. This is useful for handling data streams, as you can fetch and return data from an asynchronous source like an API, then resume the function to fetch the next chunk when needed. By yielding values one at a time, generator functions enable efficient processing of streaming data without blocking the main thread.

Run the dev server and head over to http://localhost:3000 and ask the bot for Apple Products.

npm run dev

You should see a list of Apple Products generated. Awesome, but that's just text! Let's create a component to display them.

Move Schemas

To make it easier to share types throughout our application, let's move our product schema to a new file. Create a file at lib/schemas/products.ts and paste the following code:

lib/schemas/products.ts
import { z } from "zod";

export const productSchema = z.object({
  name: z.string(),
  description: z.string(),
  price: z.number(),
  type: z.enum(["one-off", "subscription"]),
});

export const productsSchema = z.object({
  products: z.array(productSchema).min(5),
});

export type Product = z.infer<typeof productSchema>;

Note: we are also adding a few more properties to generate a description, price, and type.

Generate a Products Component

We can use v0 to generate a component to display your "products". Let's use this carousel.

Install it with the following command and name it ProductCarousel.

npx v0 add VFdYPYy7iUy -n ProductCarousel

Update the component to map through the products and display the product details.

components/product-carousel.tsx
/**
 * This code was generated by v0 by Vercel.
 * @see https://v0.dev/t/VFdYPYy7iUy
 * Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app
**/
import { Card } from "@/components/ui/card";
import {
  CarouselItem,
  CarouselContent,
  CarouselPrevious,
  CarouselNext,
  Carousel,
} from "@/components/ui/carousel";
import { Product } from "@/lib/schemas/products"; 

export function ProductCarousel({
  products, 
}: {
  products: Product[];
}) {
  return (
    <Carousel className="w-full max-w-2xl">
      <CarouselContent>
        {products.map((p) => (
          <CarouselItem key={p.name}>
            <Card className="flex flex-col items-center gap-4 p-6">
              <div className="text-center">
                <h3 className="text-xl font-semibold">{p.name}</h3>
                <p className="text-gray-500 dark:text-gray-400">
                  {p.description}
                </p>
              </div>
            </Card>
          </CarouselItem>
        ))}
      </CarouselContent>
      <CarouselPrevious />
      <CarouselNext />
    </Carousel>
  );
}

Go back to your Server Action and update the products schema to the one defined in the previous step. Return the newly generated ProductCarousel component, passing in the generated products as a prop.

app/actions.tsx
// REST OF CODE
    tools: {
      getProducts: {
        description:
          "Get list of products for the company the user asks for. Only use when the user asks for products. If the user doesn't specify a company, be sure to ask.",
        parameters: z.object({
          company: z.object({
            name: z.string(),
          }),
        }),
        generate: async function* ({ company }) {
          yield (
            <div className="animate-pulse p-4 bg-neutral-50 rounded-md">
              Loading {company.name[0].toUpperCase()}
              {company.name.slice(1)} products...
            </div>
          );
          const productsGeneration = await generateObject({
            model: openai("gpt-3.5-turbo"),
            schema: productsSchema, 
            prompt: `Generate realistic products for ${company.name} the user has requested. Use specific model names.`,
          });
          history.done((messages: ServerMessage[]) => [
            ...messages,
            {
              role: "assistant",
              content: `Showing products (products: ${productsGeneration.object.products.map((p) => p.name).join(", ")})`, 
            },
          ]);
          return <ProductCarousel products={productsGeneration.object.products} />; 
        },
      },
    }
// REST OF CODE

Remember to import productsSchema!

Head back to your browser and ask for Apple Products again.