Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 4

Crafting Prompts with ChatPromptTemplate

Recommended resources

Welcome back. In our previous session, you successfully made contact with an AI model from your TypeScript project, learning how to initialize ChatOpenAI and use the .invoke() method with static prompts. This was a crucial first step, but hardcoding prompts is like building a car that can only drive to one destination. To create truly useful and interactive applications, we need our prompts to be dynamic.

This lesson bridges that gap. We will explore prompt templates, a fundamental concept in LangChain for creating reusable and flexible prompts. You will learn how to use ChatPromptTemplate to construct structured, multi-part prompts and how to insert dynamic values using template variables. This will unlock the ability to generate prompts based on user input or other data, which is the cornerstone of building any sophisticated LLM-powered application.

The Power of Templating

In the last lesson, every time we invoked the model, we had to write out the full prompt, including system instructions and user questions. This approach has two main problems:

  1. Repetitive: If you want to ask the model to perform the same task (e.g., translation) with different inputs, you have to reconstruct the entire prompt every time.
  2. Brittle: It mixes the application logic (the "how") with the prompt instructions (the "what"). This makes your code harder to read, maintain, and adapt.

Prompt templates solve these problems by providing a standardized and reusable "recipe" for generating prompts. You define the structure once, with placeholders for dynamic parts, and then simply provide the values for those placeholders at runtime.

This diagram illustrates the typical workflow of an LLM chain, where a prompt template is a core component that prepares the user's input for the model.

A high-level view of an LLM chain. The Prompt Template formats the raw input before it's sent to the LLM.

To understand the core idea behind prompt templates, a great starting point is the following article. It explains the concept clearly and highlights why they are so essential.

A Beginner's Guide to Getting Started with Prompt ...

This guide from dev.to provides an excellent conceptual overview of prompt templates in LangChain.

Please read the introduction and the "Understanding Prompt Templates" section. Focus on the explanation of what templates are and the key benefits they provide, such as consistency and flexibility. The "fill-in-the-blank" analogy is particularly helpful.


Creating Your First Chat Prompt Template

As we're working with chat models, we will use the ChatPromptTemplate class. This class is specifically designed to create prompts that consist of a sequence of messages, each with a specific role (like system, user, or ai), perfectly matching the chat model's input format we learned about in the last lesson.

The most common way to create a ChatPromptTemplate is with the static method fromMessages(). This method takes an array where each item defines a single message in the conversation.

The video below gives a concise demonstration of this fromMessages method.

LangChain JS Tutorial #2: Prompt Templates and Chains

In this video, Leon van Zyl demonstrates how to create a more structured prompt using the fromMessages method, which gives you more control than a simple string template.

Watch the segment explaining the fromMessages method from this part. Notice how it accepts an array of message definitions, allowing you to easily set up a conversational structure with both system and human roles.


Hands-On: Building a Translator Template

Let's put this into practice. Open your langchain-project and navigate to the src/index.ts file. We'll replace the code from our last lesson with a new example that uses a prompt template to create a versatile translator.

First, let's import ChatPromptTemplate.

import { ChatPromptTemplate } from "@langchain/core/prompts";

Now, let's define our template. We'll create a system message that sets the AI's role as a translator and uses placeholders for the languages. Then, we'll add a user message with a placeholder for the text to be translated.

Update your src/index.ts to look like this:

// src/index.ts

import dotenv from 'dotenv';
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";

dotenv.config();

// 1. Initialize the model (as before)
const model = new ChatOpenAI({
    modelName: "gpt-4o-mini",
    temperature: 0.7,
});

// 2. Create a prompt template
const promptTemplate = ChatPromptTemplate.fromMessages([
  ["system", "You are an expert translator. Translate the following text from {input_language} to {output_language}."],
  ["user", "{text}"],
]);

Let's break down the fromMessages array:

  • ["system", "..."]: This defines a SystemMessage. The content string contains two placeholders, {input_language} and {output_language}, which we can fill in later.
  • ["user", "..."]: This defines a HumanMessage. It has one placeholder, {text}, for the user's input.

The curly brace syntax {placeholder} is how LangChain identifies variables within the template strings.


From Template to Chain

We now have a model and a prompt template. How do we connect them?

We create a chain. A chain in LangChain is a sequence of components piped together. The simplest chain takes a prompt template, formats it with user input, and then sends the result to a model. We can create this sequence using the .pipe() method. This is a powerful concept from the LangChain Expression Language (LCEL) that we will explore in depth in the next module, but for now, you can think of it as connecting components in a pipeline.

Let's create a chain and invoke it. Add the following main function to your src/index.ts and call it.

// src/index.ts (continued)

async function main() {
  // 3. Create a chain by piping the components together
  const chain = promptTemplate.pipe(model);

  console.log("Creating a chain to translate from English to Pirate...");

  // 4. Invoke the chain with the required variables
  const response = await chain.invoke({
    input_language: "English",
    output_language: "a fun, stereotypical Pirate dialect",
    text: "I love treasure and adventure on the high seas!",
  });

  console.log("Response:");
  console.log(response.content);
}

main();

Here's what's happening in the main function:

  1. promptTemplate.pipe(model) creates a RunnableSequence (our chain). It defines a flow where the output of the promptTemplate is "piped" as the input to the model.
  2. chain.invoke() executes the chain. You pass it an object where the keys exactly match the placeholder names in your template (input_language, output_language, text).
  3. LangChain automatically performs the steps:
    • It takes your input object.
    • It formats the promptTemplate with your values, creating a structured list of messages.
    • It sends these messages to the model.
    • It returns the AIMessage from the model.

Now, run your code from the terminal:

npm start

You should see a fun, pirate-themed translation of your sentence! Feel free to experiment by changing the output_language to "French" or "Klingon" and running it again. You've now built a flexible, reusable component.

The following article provides another complete example, reinforcing the pattern of creating a prompt, piping it to a model, and invoking the resulting chain.

CLI Chatbot with LangChain and OpenAI in Node.js - Robin Wieruch

This article by Robin Wieruch walks through building a simple CLI chatbot, using the same chain-building pattern we just implemented.

In the article, find the section with the src/index.ts code block. Focus on the askModel function defined in this block. Notice how it defines a prompt, a parser, and a model, then combines them with .pipe(). This reinforces the core pattern. Don't worry about the StringOutputParser for now; that's the topic of our next lesson!


Conclusion

Today you've made a significant leap from static prompts to dynamic, reusable prompt templates. This skill is foundational for building any application that needs to respond to varied inputs.

Key Takeaways:

  • Prompt Templates provide a structured and reusable way to generate prompts, separating logic from instructions.
  • ChatPromptTemplate.fromMessages() is the primary tool for creating structured, multi-message prompts for chat models.
  • Placeholders like {variable} allow you to dynamically insert values into your templates at runtime.
  • The .pipe() method is the standard way to connect a prompt template to a model, creating a simple but powerful chain.

We have successfully built a chain that takes variables, formats a prompt, and gets a response from a model. However, the output is still an AIMessage object, and we have to access .content to get the text. What if we wanted the output to be a simple string, a JSON object, or a list of items? In our next lesson, we will introduce Output Parsers, the final piece of our basic chain, which allows us to structure the model's output into a more convenient and predictable format.