This tutorial focuses on demonstrating the interaction between a chat application and TuneStudio APIs to provide functionalities beyond simple text messaging. By enabling functions such as web searching, fetching news, online shopping information retrieval, URL summarization, and image generation, developers can create more robust and interactive user experiences.

Tools and Libraries

For this implementation, we’ll be using several tools and libraries:

  • NextJs: To manage the frontend and backend of the application.
  • TuneStudio API: To stream the chat operations and incorporate various functionalities.
  • Serper API: To fetch search, news articles and other information.
  • Stability API: To generate images based on the user’s requests.

Setting Up the Environment

To integrate TuneStudio APIs, you will need several keys which can be obtained by registering on the respective platforms:

Store these keys in your .env file as shown:

.env
TUNE_KEY=<your_tune_key>
SERPER_KEY=<your_serper_key>
STABILITY_API_KEY=<your_stability_api_key>
STUDIO_MODEL=rohan/tune-gpt4

Backend Implementation Using TuneStudio APIs

Start by creating a file named route.ts inside app/prompt directory. This file will handle the API requests and responses.

app/route.ts
import constants from "../constants";
import { TuneAIStream } from "../utils";
import { StreamingTextResponse } from "ai";

export async function POST(req: Request) {
  const { messages } = await req.json();
  const tuneResp = await TuneAIStream({
    messages: messages,
    model: process.env.STUDIO_MODEL || "",
    stream: false,
    tools: constants.tools,
    temperature: 0.5,
    max_tokens: 600,
  });
  console.log("Tune Response", tuneResp);

  return new StreamingTextResponse(tuneResp);
}

We will be defining some tools that our model can use, for that create a file named constants.ts inside the app directory.

app/constants/index.ts
const constants = {
  tools: [
    {
      type: "function",
      function: {
        name: "search_web",
        description: "Search the web using a query.",
        parameters: {
          type: "object",
          properties: {
            query: {
              type: "string",
              description: "The search query to use for searching the web.",
            },
          },
          required: ["query"],
        },
      },
    },
    {
      type: "function",
      function: {
        name: "get_news",
        description: "Retrieve news articles from the web.",
        parameters: {
          type: "object",
          properties: {
            query: {
              type: "string",
              description:
                "The search query to use for retrieving news articles.",
            },
          },
          required: ["query"],
        },
      },
    },
    {
      type: "function",
      function: {
        name: "shop_online",
        description: "Find products for sale online.",
        parameters: {
          type: "object",
          properties: {
            query: {
              type: "string",
              description:
                "The search query to use for finding products online.",
            },
          },
          required: ["query"],
        },
      },
    },
    {
      type: "function",
      function: {
        name: "summarize_given_url",
        description: "Summarize the content of a given URL.",
        parameters: {
          type: "object",
          properties: {
            url: {
              type: "string",
              description: "The URL to summarize the content of.",
            },
          },
          required: ["url"],
        },
      },
    },
    {
      type: "function",
      function: {
        name: "generate_image_from_text",
        description: "Generate an image from a given text.",
        parameters: {
          type: "object",
          properties: {
            text: {
              type: "string",
              description: "The text to generate an image from.",
            },
          },
          required: ["text"],
        },
      },
    },
  ],
};

export default constants;

The backbone of our application is the function that streams responses based on various operations, defined in the TuneAIStream module.

The TuneAIStream function handles the response from the TuneStudio API based on the messages it receives:

app/utils/index.ts
const response = await fetch(`https://proxy.tune.app/chat/completions`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: process.env.TUNE_KEY || "",
  },
  body: JSON.stringify({
    messages: messages,
    model: model,
    stream: stream,
    stop,
    temperature,
    max_tokens: max_tokens,
    tools,
  }),
});

The API is used to initiate different AI functionalities like web searching (search_web), fetching news (get_news), or summarizing content (summarize_given_url), mostly dependent on the user’s requests parsed from the chat messages.

Frontend Interaction

Create a file named page.tsx inside the app directory to handle the frontend interaction.

You can find complete code for the frontend interaction here

Frontend interaction involves sending messages to the backend API and displaying the responses in the chat interface. The user can input text messages, and the application will process them through the TuneStudio APIs to provide the desired functionalities. We have a handleSearch function that sends the user’s message to the backend API and receives the response. The response is then displayed in the chat interface.

  • The handleSearch function exemplifies the interaction with the backend. It prepares a POST request with user messages and sends it to our endpoint which then communicates with TuneStudio API.
  • The function parses and handles streaming responses effectively by updating the UI in real-time as chunks of data are received from the TuneStudio API.
app/page.tsx
  const handleSearch = async () => {
    try {
      const searchValue = search.trim();
      setSearch("");
      if (loading) return;
      setLoading(true);
      abortController.current = new AbortController();
      abortController.current.signal.addEventListener("abort", () => {
        setLoading(false);
        setAnswer("");
      });
      const url = `/prompt`;

      const response = await fetch(url, {
        method: "POST",
        signal: abortController.current?.signal,
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          messages: chats?.slice(0, chats.length - 1).map((chat) => {
            return {
              role: chat.isSender ? "user" : "assistant",
              content: chat.msg?.includes("data:image/png;base64")
                ? "Image Generated"
                : chat.msg,
            };
          }),
        }),
      });
      if (!response.ok) {
        alert("Something went wrong, please try again later.");
        setLoading(false);
        return;
      }
      if (!response?.headers?.get("content-type")) {
        const text = await response.text();
        setAnswer(text);
        setLoading(false);
        setTimeout(() => {
          setAnswer("");
        }, 10000);
        return;
      }

      const reader = response?.body?.getReader();
      let decoder = new TextDecoder();

      let message = "";
      while (true && reader) {
        const chunkReader = await reader.read();

        const { done, value } = chunkReader;
        if (done) {
          break;
        }

        const text = decoder.decode(value);
        const lines = text.trim().split("\n\n\n\n\n\n");

        for (const line of lines) {
          console.log("eventData ->", { line });

          const eventData = safeParse(line);
          if (eventData?.sources) continue;
          if (eventData.error) {
            // if eventData has error
            console.log("error ->", eventData.error);
            setLoading(false);
            // show error
            alert(eventData.error);
            return;
          }
          if (eventData?.type === "text" || eventData?.type === "image") {
            setLoadingTxt("");
            console.log("data ->", eventData.data);
            message = message + eventData.data;
          } else if (eventData?.type !== "bye") {
            setLoadingTxt(eventData.data);
          } else if (eventData?.type === "bye") {
            message = eventData.data;
          } else {
            setLoadingTxt("");
            setLoading(false);
          }

          setAnswer(message);
        }
      }

      setAnswer(message);

      setTimeout(() => {
        setAnswer("");
        setLoading(false);
      }, 700);
    } catch (error) {
      console.log(error);
      setAnswer("");
      setLoading(false);
    }
  };

Conclusion

Innovative use of TuneStudio APIs in a chat application setup allows the creation of a vibrant, interactive user experience that goes beyond traditional chat mechanisms. By carefully harnessing AI capabilities, developers can deliver a dynamic environment where information retrieval becomes part of the conversation, enriching user engagement and providing instant value from within the chat interface. This tutorial has outlined setting up such functionalities using TuneStudio’s powerful tools, paving the way for more comprehensive and interactive applications.

You can find the complete code for this tutorial here