Building Intelligent Chatbots with LangChain and OpenAI: A Complete Guide
In the age of AI, chatbots have evolved from simple rule-based systems to sophisticated conversational agents powered by Large Language Models (LLMs). With OpenAI’s GPT models and the LangChain framework, building chatbots that can handle nuanced conversations, remember context, and output structured responses has never been easier. In this blog, we’ll explore step-by-step how to leverage LangChain and OpenAI to create advanced chatbots.
1. Introduction to OpenAI Chat Completion
OpenAI’s ChatCompletion API allows developers to interact with LLMs using a conversational paradigm. A chat consists of messages with roles: system, user, and assistant.
System: Provides instructions to the AI about its behavior.
User: Represents input from a human user.
Assistant: Represents the AI’s responses.
Here’s an example of creating a sarcastic chatbot:
import os
from dotenv import load_dotenv
import openai
load_dotenv()
openai.api_key = os.getenv('OPENAI_API_KEY')
client = openai.OpenAI()
completion = client.chat.completions.create(
model='gpt-4',
messages=[
{'role':'system', 'content':'You are Marv, a chatbot that reluctantly answers questions with sarcastic responses.'},
{'role':'user', 'content':'I’ve recently adopted a dog. Could you suggest some dog names?'}
]
)
print(completion.choices[0].message.content)
Explanation:
The system message instructs the chatbot to be sarcastic, while the user message is the query. The response is stored in completion.choices[0].message.content.
2. Controlling Responses with Temperature, Max Tokens, and Seed
OpenAI models provide parameters to influence response behavior:
completion = client.chat.completions.create(
model='gpt-4',
messages=[{'role':'user', 'content':'Explain briefly what a black hole is.'}],
max_tokens=250,
temperature=0,
seed=365
)
print(completion.choices[0].message.content)
Reasoning:
Setting temperature=0 ensures informative, deterministic responses suitable for educational or factual chatbots. Limiting max_tokens prevents unnecessarily long outputs.
3. Streaming Responses
Sometimes, you want the chatbot to stream responses in real-time instead of waiting for the full answer:
completion = client.chat.completions.create(
model='gpt-4',
messages=[{'role':'user', 'content':'Explain briefly what a black hole is.'}],
max_tokens=250,
temperature=0,
stream=True
)
for chunk in completion:
print(chunk.choices[0].delta.content, end="")
Explanation:
Each ChatCompletionChunk is a partial response. Streaming improves user experience in chat applications.
4. LangChain: ChatOpenAI
LangChain simplifies working with OpenAI models. The ChatOpenAI class wraps GPT models for easier integration:
from langchain_openai.chat_models import ChatOpenAI
chat = ChatOpenAI(model_name='gpt-4', model_kwargs={'seed':365}, temperature=0, max_tokens=100)
response = chat.invoke("I've recently adopted a dog. Could you suggest some dog names?")
print(response.content)
Reasoning:
LangChain provides an abstraction over OpenAI APIs, enabling chaining, memory, and prompt management.
5. System and Human Messages in LangChain
LangChain defines structured messages with roles:
from langchain_core.messages import SystemMessage, HumanMessage
message_s = SystemMessage(content="You are Marv, a sarcastic chatbot.")
message_h = HumanMessage(content="Can you suggest dog names?")
response = chat.invoke([message_s, message_h])
print(response.content)
Explanation:
This is similar to OpenAI’s roles but wrapped in LangChain objects, improving code readability and maintainability.
6. Prompt Templates
Prompt templates allow dynamic construction of prompts:
from langchain_core.prompts import PromptTemplate
TEMPLATE = '''
System:
{description}
Human:
I've recently adopted a {pet}. Could you suggest some {pet} names?
'''
prompt_template = PromptTemplate.from_template(TEMPLATE)
prompt_value = prompt_template.invoke({'description':'Sarcastic bot', 'pet':'dog'})
print(prompt_value.text)
Reasoning:
Templates separate logic from content, making prompts reusable for multiple scenarios.
from langchain_community.chat_message_histories import ChatMessageHistory
history = ChatMessageHistory()
history.add_user_message("Give me a fun fact.")
history.add_ai_message("Longest place name is 85 letters long.")
print(history.messages)
Reasoning:
Maintaining history improves multi-turn conversation consistency.
from langchain.memory import ConversationBufferMemory
chat_memory = ConversationBufferMemory(memory_key='message_log', chat_memory=history, return_messages=True)
chain = LLMChain(llm=chat, prompt=chat_template, memory=chat_memory)
response = chain.invoke({'question':'Can you give another interesting fact?'})
print(response['text'])
Reasoning:
Buffer memory stores past messages for coherent follow-ups.
12. Conversation Buffer Window Memory
This variant remembers only the last k messages, controlling memory size:
from langchain.memory import ConversationBufferWindowMemory
chat_memory = ConversationBufferWindowMemory(memory_key='message_log', chat_memory=history, return_messages=True, k=2)
13. Conversation Summary Memory
Summarizes past conversation to reduce token usage:
from langchain.memory import ConversationSummaryMemory
chat_memory = ConversationSummaryMemory(llm=chat, memory_key='message_log', return_messages=False)
14. Combined Memory
Combines buffer and summary memories for efficient and contextual responses:
from langchain.memory import CombinedMemory
chat_combined_memory = CombinedMemory(memories=[chat_buffer_memory, chat_summary_memory])
15. Output Parsers
LangChain provides structured output parsers:
String Parser:
from langchain_core.output_parsers import StrOutputParser
str_parser = StrOutputParser()
parsed = str_parser.invoke(response)
Comma-Separated List Parser:
from langchain_core.output_parsers import CommaSeparatedListOutputParser
list_parser = CommaSeparatedListOutputParser()
parsed_list = list_parser.invoke(response)
Datetime Parser:
from langchain.output_parsers import DatetimeOutputParser
date_parser = DatetimeOutputParser()
parsed_date = date_parser.invoke(response)
Reasoning:
Output parsers convert unstructured text into structured, machine-readable formats.
16. End-to-End Chatbot Example: Using Best Practices
In this section, we’ll build a complete chatbot using LangChain and OpenAI. This bot will:
Use a system message to define behavior.
Accept human messages dynamically.
Use Prompt Templates for structured questions.
Remember conversation context using memory.
Use an LLMChain for execution.
Output responses in a structured format using an output parser.
Step 1: Import Required Modules
%load_ext dotenv
%dotenv
import os
from langchain_openai.chat_models import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder
from langchain.chains.llm import LLMChain
from langchain.memory import ConversationBufferMemory
from langchain_core.output_parsers import CommaSeparatedListOutputParser
Step 2: Define System and Human Messages
The system message instructs the bot to be helpful, witty, and sarcastic:
system_message = SystemMessage(
content="You are Marv, a chatbot that answers questions wittily and sarcastically."
)
human_template = HumanMessagePromptTemplate.from_template(
"I've recently adopted a {pet}. Can you suggest some {pet} names?"
)
Why this step is necessary:
System messages define the chatbot’s personality, while Human messages capture dynamic user input.
Why:
Memory ensures that the follow-up question is answered contextually, maintaining continuity.
Conclusion
LangChain combined with OpenAI GPT models empowers developers to build context-aware, memory-enabled, and style-controlled chatbots. From basic chat completions to complex memory and output parsing, LangChain provides a robust framework for efficient and maintainable AI applications.
By mastering templates, LLM chains, memory management, and output parsing, you can create chatbots that are not only interactive but also intelligent, engaging, and reliable.
In the previous two parts, we built a strong foundation of LangGraph fundamentals—nodes, edges, message states, conditional routing, reducers, summarization loops, and graph orchestration.
In Part-1 of this LangGraph Blog Series, we understood the foundation of LangGraph — Graph structure, Nodes, Edges, Conditional Routing, State system, and Graph Execution.
Now in Part-2, we upgrade our knowledge and turn LangGraph into a real conversation system.
Modern AI workflows need more than just a prompt and a model call. Real applications require memory, state transitions, branching logic, routing decisions, and orchestration of multiple AI models. This is where LangGraph enters the scene.