Docs
ChatwatsonxAI

ChatwatsonxAI

Use different IBM's text generation models by following these instructions.

Installation

Install peer dependencies:

npm install @ibm-cloud/watsonx-ai

Prerequisites

Before setting up the component, you'll need to obtain the necessary environment variables from the IBM WatsonX AI platform. Follow these steps:

  1. Create an IBM Account and API Key
  • Sign up for an IBM WatsonX AI account
  • Navigate to the API Key section
  • Click "Create API Key" to generate your credentials API Key Generation
  1. Set Up Your Project
  • Create a new project if you don't have one
  • Access the project's "Manage" section to find your Project ID
  • Important: Enable "WatsonMachineLearning" under /Manage/Services & integrations Project ID Location
  1. Get Service URL
  • Navigate to the dashboard
  • Locate and copy your service URL Service URL Location

Once you have these credentials, you can proceed with the environment variable setup.

Add Environment Variables

.env
WATSONX_AI_AUTH_TYPE=iam
WATSONX_AI_APIKEY="YOUR_SAMPLE_API_KEY"
WATSONX_AI_PROJECT_ID="YOUR_SAMPLE_PROJECT_ID"
WATSONX_AI_SERVICE_URL="YOUR_SAMPLE_SERVICE_URL"
/* You can get one from - https://www.ibm.com/products/watsonx-ai */

Copy the code

Add the following code to your utils/chatWatsonxAi.ts file:

chatWatsonxAi.ts
import { WatsonXAI } from "@ibm-cloud/watsonx-ai";
import { Stream } from "@ibm-cloud/watsonx-ai/dist/lib/common";
import WatsonxAiMlVml_v1, {
  ObjectStreamed,
  TextChatStreamResponse,
} from "@ibm-cloud/watsonx-ai/dist/watsonx-ai-ml/vml_v1";
import { UserOptions } from "ibm-cloud-sdk-core";
import fs from "fs";
 
interface WatsonxAIConfig extends UserOptions {
  modelId: string;
  projectId: string;
}
 
interface ChatResponse {
  output: string;
}
 
interface WatsonxAICallOptions {
  prompt: string;
  stream?: boolean;
  context?: string;
  outputFormat?: string;
}
 
interface WatsonxAICallWithChatOptions extends WatsonxAICallOptions {
  chatHistory?: Record<"assistant" | "user", string>[];
  image?: { path: string; type: "local" | "remote" }[];
  systemInstruction?: string;
  options?: Omit<
    WatsonxAiMlVml_v1.TextChatParams,
    "modelId" | "messages" | "projectId"
  >;
}
 
export class ChatWatsonxAI {
  private client: WatsonXAI;
  private modelId: string;
  private projectId: string;
 
  constructor(config: WatsonxAIConfig) {
    const { modelId, projectId, serviceUrl, version } = config;
 
    this.client = WatsonXAI.newInstance({
      version: version,
      serviceUrl,
      ...config,
    });
 
    this.modelId = modelId;
    this.projectId = projectId;
  }
 
  async chat(
    options: WatsonxAICallOptions
  ): Promise<
    | ChatResponse
    | Stream<
        WatsonxAiMlVml_v1.ObjectStreamed<WatsonxAiMlVml_v1.TextGenResponse>
      >
  > {
    const { prompt, context, stream, outputFormat } = options;
 
    const content = await this.buildMessage(prompt, context, outputFormat);
 
    if (stream) {
      const response = await this.client.generateTextStream({
        modelId: this.modelId,
        projectId: this.projectId,
        input: content,
        returnObject: true,
      });
      return response;
    }
 
    const response = await this.client.generateText({
      modelId: this.modelId,
      projectId: this.projectId,
      input: content,
    });
 
    return {
      output:
        response.result?.results?.[0]?.generated_text?.replace(
          /^```json\n|\n```$/g,
          ""
        ) ?? "",
    };
  }
 
  async chatWithHistory(
    options: WatsonxAICallWithChatOptions
  ): Promise<ChatResponse | Stream<ObjectStreamed<TextChatStreamResponse>>> {
    const {
      prompt,
      context,
      stream,
      outputFormat,
      image,
      chatHistory,
      systemInstruction,
    } = options;
 
    let userMessages: { role: string; content: string }[] = [];
 
    if (systemInstruction) {
      userMessages.push({ role: "system", content: systemInstruction });
    }
 
    if (chatHistory && chatHistory.length > 0) {
      chatHistory.forEach((chat) => {
        userMessages.push({ role: "user", content: chat.user });
        userMessages.push({ role: "assistant", content: chat.assistant });
      });
    }
 
    const content = await this.buildMessage(
      prompt,
      context,
      outputFormat,
      image
    );
    userMessages.push({
      role: "user",
      content: image && image.length > 0 ? JSON.parse(content) : content,
    });
 
    if (stream) {
      const response = await this.client.textChatStream({
        modelId: this.modelId,
        projectId: this.projectId,
        returnObject: true,
        messages: userMessages,
        ...options.options,
      });
      return response;
    }
 
    const response = await this.client.textChat({
      modelId: this.modelId,
      projectId: this.projectId,
      messages: userMessages,
      ...options.options,
    });
 
    return {
      output:
        response.result?.choices?.[0]?.message?.content?.replace(
          /^```json\n|\n```$/g,
          ""
        ) ?? "",
    };
  }
 
  private async buildMessage(
    prompt: string,
    context?: string,
    outputFormat?: string,
    file?: { path: string; type: "local" | "remote" }[]
  ): Promise<string> {
    let content = prompt;
 
    if (context) {
      content += `\ncontext:\n${context}`;
    }
 
    if (outputFormat) {
      content += `\noutput format:\n${outputFormat}`;
    }
 
    if (file && file.length > 0) {
      const filePromises = file.map(async (f) => ({
        type: "image_url",
        image_url: {
          url: await this.encodeImage(f.path, f.type),
        },
      }));
 
      const resolvedFiles = await Promise.all(filePromises);
 
      const finalContent = [{ type: "text", text: content }, ...resolvedFiles];
      return JSON.stringify(finalContent);
    }
 
    return content;
  }
 
  private async encodeImage(
    path: string,
    type: "local" | "remote"
  ): Promise<string> {
    if (type === "remote") {
      const response = await fetch(path);
      const buffer = Buffer.from(await response.arrayBuffer()).toString(
        "base64"
      );
      return `data:image/jpeg;base64,${buffer}`;
    }
 
    return `data:image/jpeg;base64,${fs.readFileSync(path).toString("base64")}`;
  }
}
 
 

Usage

Initialize client

Model Selection Guide

IBM's Granite models offer various capabilities for different use cases. Before initializing the client, you'll need to select the appropriate model for your needs.

Available Models and Use Cases

ModelBest For
ibm/granite-13b-instruct-v2General-purpose AI (chatbots, summaries, reasoning)
ibm/granite-20b-code-instructCode generation and AI-assisted programming
ibm/granite-3-2b-instructLightweight applications requiring low latency
ibm/granite-3-8b-instructBalanced performance and resource efficiency

For a complete list of IBM's models, visit the IBM Data Platform Documentation.

Getting Model Information

To obtain the required version and modelId:

  1. Navigate to the Dashboard and select "Explore Foundational Models" Foundational Models Navigation

  2. Select your desired model and open it in the prompt lab

  3. Click "View Code" in the top right corner to find:

  • modelId (e.g., ibm/granite-13b-instruct-v2)
  • version (e.g., 2023-05-29) Model Details

Initialize the WatsonxAIChat client.

import { ChatWatsonxAI} from "@/utils/chatWatsonxAi";
 
const watsonxChat = new ChatWatsonxAI({
  modelId: "ibm/granite-13b-instruct-v2",
  projectId: process.env.WATSONX_AI_PROJECT_ID!,
  serviceUrl: process.env.WATSONX_AI_SERVICE_URL,
  version: "2023-05-29"
});
 

Simple Chat

Sample chat with a simple single prompt.

Models like granite-13b-instruct-v2 are best to use for single prompt chats.

const data = await watsonxChat.chat({
  prompt: "What is YCombinator? Respond in one liner!",
});
 
console.log(data);
 
/**
{
  "output": "YCombinator is a seed money startup accelerator program."
}
**/
 

Multi Conversational chat

Sample chats involving addition of chat history.

Models like granite-3-2b-instruct supports multi-conversational chats.

 
const watsonxChat = new ChatWatsonxAI({
  modelId: "ibm/granite-3-2b-instruct",
  projectId: process.env.WATSONX_AI_PROJECT_ID!,
  serviceUrl: process.env.WATSONX_AI_SERVICE_URL,
  version: "2023-05-29",
 
});
 
const dataWithHistory = await watsonxChat.chatWithHistory({
  prompt: "Who I am?",
  chatHistory: [
    { user: "Hello", assistant: "Hi there! How can I help you today?" },
    {
      user: "My name is John Doe, and I am an AI developer!",
      assistant: "Sure, I will remember that!",
    },
  ],
});
 
console.log(dataWithHistory);
 
/**
{
  "output": "You are John Doe, an AI developer. How can I assist you further today?"
}
**/

Defining Output Format

Example of defining output format. Though it's not 100% accurate, LLM can hallucinate sometime.

const dataWithFormat = await watsonxChat.chatWithHistory({
  prompt: "Who I am?",
  chatHistory: [
    { user: "Hello", assistant: "Hi there! How can I help you today?" },
    {
      user: "My name is John Doe, and I am an AI developer!",
      assistant: "Sure, I will remember that!",
    },
  ],
  outputFormat: `{name: "", occupation: ""}`,
});
 
console.log(dataWithFormat);
 
/**
{
  "output": "{\"name\": \"John Doe\", \"occupation\": \"AI developer\"}"
}
**/

Defining Context and other optional parameters

const dataWithContext = await watsonxChat.chatWithHistory({
  prompt: "Who I am?",
  chatHistory: [
    { user: "Hello", assistant: "Hi there! How can I help you today?" },
    {
      user: "My name is John Doe, and I am an AI developer!",
      assistant: "Sure, I will remember that!",
    },
  ],
  context:
    "John Doe is an AI developer, specialized in chatbot development and RAG research.",
  systemInstruction: "You're a helpful assistant and your name is AgentGenesis!",
  outputFormat: `{name: "", occupation: "", speciality: "", your_name : ""}`,
  options: {
    maxTokens: 100,
    temperature: 0.7,
  },
});
 
console.log(dataWithContext);
 
/**
{
  "output": "{\n    \"name\": \"John Doe\",\n    \"occupation\": \"AI developer\",\n    \"speciality\": \"chatbot development and RAG research\",\n    \"your_name\": \"AgentGenesis\"\n}"
}
**/
 

Multimodal Chat

WatsonxAIChat supports multimodal inputs, allowing you to combine text with image files.

For multimodal chat interactions, we use vision-capable models like meta-llama/llama-3-2-11b-vision-instruct which can process both text and images

Example: Using Public URLs and Local paths

The following examples demonstrate how to send file using public URLs and Local paths.

 
const watsonxChat = new ChatWatsonxAI({
  modelId: "meta-llama/llama-3-2-11b-vision-instruct",
  projectId: process.env.WATSONX_AI_PROJECT_ID!,
  serviceUrl: process.env.WATSONX_AI_SERVICE_URL,
  version: "2023-05-29"
});
 
const dataWithRemoteImage = await watsonxChat.chatWithHistory({
  prompt: "what the image is depicting...?",
  image: [
    {
      path: "https://2.img-dpreview.com/files/p/E~C1000x0S4000x4000T1200x1200~articles/3925134721/0266554465.jpeg",
      type: "remote",
    },
  ],
  options: {
    temperature: 0.6,
  },
});
 
 
const dataWithLocalImage = await watsonxChat.chatWithHistory({
  prompt: "what the image is depicting...?",
  image: [
    {
      path: "./public/sample_image.jpg",
      type: "local",
    },
  ],
  options: {
    temperature: 0.6,
  },
});

Streaming text

Sample example of creating and handling stream responses. You can stream in both the chat and chatWithHistory methods.

const dataWithStreaming = await watsonxChat.chatWithHistory({
  prompt: "Who I am?",
  chatHistory: [
    { user: "Hello", assistant: "Hi there! How can I help you today?" },
    {
      user: "My name is John Doe, and I am an AI developer!",
      assistant: "Sure, I will remember that!",
    },
  ],
  context: "John Doe is an AI developer, specialized in chatbot development and RAG research.",
  systemInstruction: "You're a helpful assistant and your name is AgentGenesis!",
  options: {
    temperature: 0.6,
    maxTokens: 100,
  },
  stream: true,
});
 
// @ts-ignore
for await (const chunk of dataWithStreaming) {
  console.log(chunk.data?.choices[0]?.delta?.content ?? "");
}
 
/**
You are John Doe, an AI developer specialized in chatbot development and RAG research. How can I assist you further, John?
**/
 
const singleDataWithStreaming = await watsonxChat.chat({
  prompt: "Who I am?",
  context: "John Doe is an AI developer, specialized in chatbot development and RAG research.",
  outputFormat: `{name: "", occupation: "", speciality: "", your_name : ""}`,
  stream: true,
});
 
// @ts-ignore
for await (const chunk of singleDataWithStreaming) {
  console.log(chunk.data.results[0].generated_text);
}
 
 
 
 

Props

chat

PropTypeDescriptionDefault
promptstringPrompt provided by user.""
contextstring?Additional context user wants to provide.""
outputFormatstring?Particular format in which the user wants their output.""
streamboolean?Returns streamed text if truefalse

chatWithHistory

PropTypeDescriptionDefault
promptstringPrompt provided by user.""
contextstring?Additional context user wants to provide.""
outputFormatstring?Particular format in which the user wants their output.""
chatHistoryoptionalConversational history.""
systemInstructionstring?Any specific instruction to the system user wants to feed.""
imageoptionalImage path[]
optionsoptional?Additional args as per IBM docs.

Credits

This component is built on top of Watsonx AI's Node SDK