Building a Chat Engine with Conversation History in Java

Welcome to the second unit of our course on building a RAG-powered chatbot! In the previous lesson, we built a document processor that forms the retrieval component of our RAG system. Today, we'll focus on the conversational aspect by creating a chat engine that can maintain conversation history.

While our document processor is excellent at finding relevant information, a complete RAG system needs a way to interact with users in a natural, conversational manner. This is where our chat engine comes in. The chat engine is responsible for managing the conversation flow, formatting prompts with relevant context, and maintaining a history of the interaction.

Understanding the Chat Engine

The chat engine we'll build today will:

  1. Manage interactions with the language model
  2. Optionally maintain a history of the conversation for display or logging
  3. Format prompts with relevant context from our document processor
  4. Provide methods to reset the conversation history when needed

By the end of this lesson, you'll have a fully functional chat engine that can be integrated with the document processor we built previously to create a complete RAG system. Let's get started!

Creating the ChatEngine Class Structure

Let's begin by setting up the basic structure of our ChatEngine class in Java. This class will encapsulate all the functionality needed for managing conversations with the language model.

Key points in this initialization:

  1. Chat Model: We initialize chatModel using LangChain4j's OpenAiChatModel to create an instance for generating responses.
  2. System Message: We define strict instructions that guide the AI's behavior, telling it to only answer questions based on the provided context and to politely decline if no relevant context is available.
  3. Conversation History: We initialize an empty list to optionally keep track of the conversation for display or logging purposes. This history is not sent to the model in this implementation.

This structure ensures our chat engine can properly communicate with the language model while optionally maintaining a record of the conversation.

Understanding Prompt Templates in LangChain4j

In this Java implementation, we explicitly construct the message list for each interaction, including both the system message and the user message (which contains the context and question). This mirrors the Python approach of using explicit system and human message templates.

  • The system message sets the assistant's behavior and is always included as the first message.
  • The user message is formatted to include both the context and the question, using a template like:
  • The model receives both messages as input for each query.

By structuring the prompt this way, we clearly separate the instructions (system message) from the user input (user message), and combine them into the exact format the language model expects.

Building the Message Handling System

Now that we have our basic class structure, let's implement the core functionality: sending messages and receiving responses. The sendMessage method will handle this process.

The sendMessage method is the heart of our chat engine. It takes two parameters: userMessage (the question from the user) and context (optional relevant information from our document processor).

  1. Format Messages: We create a list of messages, always including the system message and a user message that contains both the context and the question.
  2. Get Response: We invoke the chat model with our formatted messages using chatModel.chat(messages).
  3. Update History: We append the user's message and the AI's response to the conversation history for display or logging.
  4. Return Result: We return the content of the response to be displayed to the user.

Note: In this implementation, conversation history is not sent to the model. Each response is based only on the current context and question, which is typical for RAG systems.

Implementing Conversation Management

An important aspect of any chat system is the ability to manage the conversation state. Let's implement a method to reset the conversation history:

The resetConversation method clears the conversation history. This is useful for display or logging purposes, and allows users to start fresh when needed.

Testing Our Chat Engine Without Context

Now that we've built our chat engine, let's test it with some examples to see how it works in practice.

First, let's see how the chat engine responds when we don't provide any context:

When you run this code, you'll see output similar to:

As expected, the chat engine follows the instructions in our system message and refuses to answer without context. This is the desired behavior for our RAG system, which should only provide information based on the context it's given.

Testing Our Chat Engine With Context

Now, let's see how the chat engine responds when we provide relevant context:

The output will look something like:

With context provided, the chat engine now gives a detailed and accurate response based on the information available. This demonstrates how our RAG system will use retrieved documents to answer user queries.

Resetting the Conversation

Finally, let's test the reset functionality:

The output might look like this:

After resetting the conversation, the chat engine will have completely cleared its previous context and will now respond based on entirely new information. This reset functionality is important for managing long conversations and allowing users to switch topics cleanly.

Summary and Practice Preview

In this lesson, we've built a powerful chat engine for our RAG chatbot. We've learned how to:

  1. Create a ChatEngine class that manages conversations with a language model
  2. Define system messages to guide the AI's behavior
  3. Optionally maintain conversation history for display or logging
  4. Format prompts with context and questions using templates
  5. Implement methods to send messages and reset conversations
  6. Test our chat engine with various scenarios

Our chat engine complements the document processor we built in the previous lesson. While the document processor handles the retrieval of relevant information, the chat engine manages the conversation flow and presents this information to the user in a natural way. In the next unit, we'll integrate the document processor and chat engine to create a complete RAG system. This integration will allow our chatbot to automatically retrieve relevant context from documents based on user queries, creating a seamless experience where users can ask questions about their documents and receive informed, contextual responses.

Get ready to practice what you've learned and take your RAG chatbot to the next level!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal