Search code examples
node.jsnext.jsartificial-intelligencevercel

Next.js AI SDK Generative UI with Together AI and Mixtral 8x7B not working with function calling


I'm trying to make a AI to generate UI components based on the user input such as vercel did in their latest chat example here. Mixtral supports function calling but it seems that it is not working as it should with the render function from vercel AI SDK: import { render } from "ai/rsc";

In the following code, I tried to do as in the documentation and to follow their actual repository from the AI chat example.

import "server-only";

import { Message } from "@/components/message";
import { getMutableAIState, render, createAI } from "ai/rsc";
import OpenAI from "openai";
import { PiSpinnerGap } from "react-icons/pi";
import { z } from "zod";
import { sleep } from "@/lib/utils";

const togetherai = new OpenAI({
  apiKey: process.env.TOGETHER_AI_API_KEY,
  baseURL: "https://api.together.xyz/v1",
});

//export const runtime = "edge";

async function getWeather() {
  return {weather: "30º"}
}

async function submitUserMessage(input: string) {
  "use server";
  const aiState = getMutableAIState();

  // Update AI state with new message.
  aiState.update([
    ...aiState.get(),
    {
      role: "user",
      content: input,
    },
  ]);

  const ui = render({
    provider: togetherai,
    model: "mistralai/Mixtral-8x7B-Instruct-v0.1",
    messages: [
      {
        role: "system",
        content: `You are a helpful assistant that can access external functions when the user asks for. Your name is MistralAI.
          If the user asks for the weather and passes the location, call \`get_weather_info\` to show current weather at that location.`,
      },
      {
        role: "user",
        content: input,
      },
    ],
    text: ({ content, done }) => {
      if (done) {
        aiState.done([
          ...aiState.get(),
          {
            role: "assistant",
            content,
          },
        ]);
      }

      return (
        <div className="flex w-full">
          <Message from="ai">{content}</Message>
        </div>
      );
    },
    initial: (
      <div>
        <PiSpinnerGap className="animate-spin text-muted" size={25} />
      </div>
    ),
    functions: {
      getWeatherInfo: {
        description:
          "Get the information for the weather according to a certain location.",
        parameters: z
          .object({
            location: z
              .string()
              .describe("The location to get the weather from."),
          })
          .required(),
        render: async function* ({ location }) {
          yield (
            <div>
              <PiSpinnerGap className="animate-spin text-muted" size={25} />
            </div>
          );

          //can call from other func to get information from an external api
          const weatherInfo = await getWeather();
          
          await sleep(1000);

          aiState.done([
            ...aiState.get(),
            {
              role: "function",
              name: "getWeatherInfo",
              content: JSON.stringify(weatherInfo),
            },
          ]);

          return (
            <div className="bg-red-500">
              <h1>
                The weather in <span className="font-bold">{location}</span>
              </h1>
              <div>is {weatherInfo.weather}</div>
            </div>
          );
        },
      },
    },
  });

  return {
    id: Date.now(),
    display: ui,
  };
}

// Define the initial state of the AI. It can be any JSON object.
const initialAIState: {
  role: "user" | "assistant" | "system" | "function";
  content: string;
  id?: string;
  name?: string;
}[] = [];

// The initial UI state that the client will keep track of, which contains the message IDs and their UI nodes.
const initialUIState: {
  id: number;
  display: React.ReactNode;
}[] = [];

export const AI = createAI({
  actions: {
    submitUserMessage,
  },
  initialAIState,
  initialUIState,
});

But it is giving me the following error:

Console Error Message

The problem only occurs when the AI actually tries to call the function:

Client chat interaction

Here is the client side

import { AI } from "../action";
import { GenerativeUIChat } from "./fragments/generative-ui-chat";

export default function GenerativeUIPage() {
  return (
    <AI>
      <GenerativeUIChat />
    </AI>
  );
}

Here is the GenerativeUIChat component:

"use client";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
import { PiPaperPlaneTilt, PiRobotThin } from "react-icons/pi";
import { useUIState, useAIState, useActions } from "ai/rsc";
import { useState } from "react";
import { AI } from "@/app/action";
import { Message } from "@/components/message";

export function GenerativeUIChat() {
  const [messages, setMessages] = useUIState<typeof AI>();
  const { submitUserMessage } = useActions<typeof AI>();
  // const [aiState, setAIState] = useAIState<typeof AI>();
  const [input, setInput] = useState<string>('');

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();

    // Add user message to UI state
    setMessages((curr) => [
      ...curr,
      {
        id: Date.now(),
        display: (
          <div className="flex w-full justify-end">
            <Message from="user">{input}</Message>
          </div>
        ),
      },
    ]);

    // Submit and get response message
    const responseMessage = await submitUserMessage(input);
    setMessages((currentMessages) => [...currentMessages, responseMessage]);

    setInput('');
  }

  return (
    <div className="flex flex-col w-full max-w-xl px-4 h-[calc(100vh-4rem)] justify-between items-center mx-auto">
      <div className="flex flex-col w-full max-w-xl max-h-[calc(100%-4.5rem)] pt-6">
        <span className="w-full text-center text-sm text-muted">Mistral</span>
        {messages.length === 0 ? (
          <div className="flex flex-col gap-8 w-full items-center">
            <span className="text-2xl font-semibold text-center">
              Start a conversation with the AI.
            </span>
            <PiRobotThin size={100} />
          </div>
        ) : (
          <div
            className={cn(
              "[&::-webkit-scrollbar]:w-[0.35rem] [&::-webkit-scrollbar-track]:bg-accent [&::-webkit-scrollbar-thumb]:bg-primary [&::-webkit-scrollbar-thumb]:rounded-lg [&::-webkit-scrollbar-thumb:hover]:bg-primary/50",
              "p-2 px-6 pr-3 flex flex-col gap-4 border border-input rounded-lg mb-2 overflow-auto shadow-sm shadow-black/30 transition duration-300 hover:shadow-lg"
            )}
          >
            {
              // View messages in UI state
              messages.map((message) => message.display)
            }
          </div>
        )}
      </div>
      <form className="w-full" onSubmit={handleSubmit}>
        <div className="flex gap-2 w-full py-4">
          <Input
            className="p-2 border border-input rounded shadow-sm bg-background"
            value={input}
            placeholder="Say something..."
            onChange={(event) => {
              setInput(event.target.value);
            }}
          />
          <Button size="icon">
            <PiPaperPlaneTilt size={20} />
          </Button>
        </div>
      </form>
    </div>
  );
}

Any solutions or sugestions are welcome 😁.


Solution

  • I've solved this problem, if anybody have trouble with this you can check out my repository here and see the documentation about Generative UI.

    The main problem that I had was with the streaming, which is now disabled and works fine, you can also use Vercel's Google Gemini Chatbot as a source of information like I did.