Generative AIBuilding with foundation modelsTools & frameworks

LangChain basics

23 minutes read

We all know how Large Language Models (LLMs) like ChatGPT have revolutionized our world today. However, working with them effectively requires more than just sending prompts and receiving responses. Building complex workflows like agents and RAG requires a framework to make the process smooth. That's where LangChain comes in. It serves as a bridge, providing a structured way to interact with LLMs, manage conversations, integrate with data sources, and much more.

Let's review what LangChain is, explore its key components, and show you how to build LLM-powered applications.

What is LangChain and why?

Interacting with Large Language Models (LLMs) requires more than simple API calls. LangChain is an open-source framework that helps developers work with LLMs more effectively. It creates a standard way for applications to communicate with different models, providing a consistent interface across providers. These interfaces offer an abstraction layer that makes model interactions simpler, no matter which provider you choose.

LangChain makes development more straightforward and scalable by handling tasks like prompt formatting, API calls, and integration with various data sources. Here's why you might want to use LangChain:

  • LangChain lets you structure your AI applications with reusable components instead of hard-coding prompts and responses.

  • It supports multi-step reasoning, stateful memory, and decision-making agents, which make AI applications more dynamic.

  • It works well with external data sources like databases, APIs, and vector stores, allowing applications to retrieve relevant knowledge.

  • You're not limited to one model. LangChain works with OpenAI, Anthropic, Hugging Face, Cohere, and more. This avoids vendor lock-in.

At its core, LangChain focuses on chaining different AI-related functions together to create more complex structures. We'll explore how this works in the next sections.

LLM interfaces

LangChain has packages for different model providers (like langchain-openai for OpenAI models) that act as an intermediary between your code and the model API. Instead of manually handling requests and responses, these packages:

  • Standardize API interactions — you use a similar interface for different models.

  • Simplify request formatting — making it easy to pass inputs like temperature, max tokens, and authentication.

  • Optimize response handling — making output processing easier.

Let's look at how AI interactions appear with and without LangChain:

try:
    response = requests.post(
        "https://api.groq.com/openai/v1/chat/completions",
        headers={"Authorization": f"Bearer {api_key}"},
        json={
            "model": "llama-3.3-70b-versatile",
            "messages": [
                {
                    "role": "user",
                    "content": "What is LangChain",
                }
            ]
        }
    )
    response.raise_for_status()
    chat_completion = response.json()
    print(chat_completion["choices"][0]["message"]["content"])
except Exception as e:
    print(f"An error occurred: {e}")
llm = ChatGroq(model="llama-3.3-70b-versatile", temperature=0.5, max_retries=2)

messages = [
    ("system", "You are an AI assistant that provides concise answers."),
    ("human", "What is LangChain?"),
]

response = llm.invoke(messages)
print(response.content)

As you can see, LangChain makes it straightforward to interact with AI models. LangChain supports numerous integrations that you can use to interact with various model providers.

Full code
# install the required packages:
# pip install langchain-groq langchain-core python-dotenv

from langchain_core.prompts import PromptTemplate
from langchain_groq import ChatGroq
import dotenv

dotenv.load_dotenv()

llm = ChatGroq(model="llama-3.3-70b-versatile", temperature=0.0, max_retries=2)

template = PromptTemplate.from_template("Explain {concept} in simple terms. Your output should be concise and informative.")

prompt = template.invoke({"concept": "black holes"})
response = llm.invoke(prompt)
print(response.content)

Messages

In chat models, messages are a structured conversation history. A message has at least two parts:

  • role — the sender (e.g., user, assistant, system, or tool)

  • content — the actual message or payload.

When calling models, you pass a list of messages, for example:

messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "What’s 2+2?"},
    {"role": "assistant", "content": "4"},
    {"role": "user", "content": "Now square it."}
]

When the model sees these messages, it generates the next assistant message. LangChain allows you to easily create these sequences of messages and pass them to a chat model. Here are the various message types:

  • SystemMessage — the system prompt, defining some guidelines for the model to follow.

  • HumanMessage — represents the user's prompt and interactions.

  • AIMessage — the response from the model. In some cases, it might also include tool calls for models that support function calling.

  • ToolMessage — outputs from tool calls.

Here's an example using various messages:

from langchain_core.messages import HumanMessage, AIMessage, SystemMessage

system_prompt = SystemMessage("You are a helpful assistant.")
user_input = HumanMessage("What is 2+2?")

messages = [system_prompt, user_input]

final_response = llm.invoke(messages)
print(type(final_response)) # <class 'langchain_core.messages.AIMessage'>

This will enable models to "remember" context and handle multi-turn chats naturally. With messages, you can easily construct chat history, ensuring your model has the right context to provide the right answers. Here's how you might create chat history:

from langchain_core.messages import SystemMessage, HumanMessage

# Start conversation with a system prompt
messages = [SystemMessage(content="You are a helpful assistant specializing in Python programming.")]

# Simulate multi-turn conversation
while True:
    user_input = input("Enter your query here: ")
    if user_input.lower() == "exit":
        break

    # Add user message
    messages.append(HumanMessage(content=user_input))

    # Get model response (returns an AIMessage)
    response = model.invoke(messages)

    # Append model's reply to history
    messages.append(response)

    print(f"Assistant: {response.content}")

The chat will grow message by message, allowing the model to respond with the right context.

Working with data

LangChain goes beyond prompt management by integrating packages that help you ingest, process, and query data. This capability is particularly useful when you need to include additional data, such as proprietary information, in your LLM-powered applications. You can achieve this through document loaders, text splitters, data stores, and retrievers.

Document loaders make data ingestion from files or other sources simpler. They convert raw data into a format that LangChain can process. LangChain supports many data sources for loading data. For example, if you want to load planetary information from a text file, you can use the TextLoader class from the langchain_community package:

# Install the required package:
# pip install langchain-community
from langchain_community.document_loaders import TextLoader

# Load planetary data from a text file
loader = TextLoader("planets/mars.txt")
documents = loader.load()

print("Loaded documents:", documents)

After loading the data, you can use text splitters to break large documents into smaller, more manageable chunks. This step is essential when working with lengthy texts to ensure you send only relevant context to LLMs and stay within token limits. LangChain offers several ways to split documents. The table below summarizes these methods:

Method

Description

Splitter

Based on length — can be token or character-based.

Split documents into chunks of a specified size.

Based on text structure

Text is split into units based on its paragraph, sentence, or word structure.

Based on the document's structure

Useful for documents with an inherent structure, such as JSON or Markdown.

Using CharacterTextSplitter
# Install the required package:
# pip install langchain-text-splitters tiktoken

from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter
import tiktoken

# Use the document loader to load the content of planets.txt
loader = TextLoader("planets/earth.txt")
documents = loader.load()
# Assume the file contains a single document
planets_text = documents[0].page_content

# Create a text splitter using the TikToken encoder.
# This configuration sets a chunk size of 50 tokens with no overlap.
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base", chunk_size=50, chunk_overlap=0
)

# Split the document text into chunks
texts = text_splitter.split_text(planets_text)
print(texts[0]) 

The next step involves using an embedding model to transform the raw text into vectors that capture its semantics. Here, again, LangChain integrates with various providers from OpenAI, Google, Cohere, and others.

Next, you store the split chunks with their vector embeddings in a vector database for similarity search. While we'll explore vector search and embeddings in more detail later, integrations with data stores simplify working with various providers. You only need to provide an embedding model and the document to embed:

# Install the required packages:
# pip install langchain-openai python-dotenv

import os
from langchain_openai import OpenAIEmbeddings
import dotenv

dotenv.load_dotenv()

# Initialize OpenAI embeddings.
embeddings_model = OpenAIEmbeddings(
    model="text-embedding-ada-002",
    base_url="<base_url>", # in case you are using an LLM proxy
    api_key=os.getenv("OPENAI_API_KEY")
)

LangChain handles generating embeddings using the specified model and storing them in the database. Our example below uses Chromadb, which is free, open source, and runs locally:

# Install the required packages.
# pip install langchain-chroma

from langchain_chroma import Chroma

# Create a Chroma vector store from the loaded documents and embeddings.
db = Chroma(
    collection_name="hyper-collection",
    embedding_function=embeddings_model
)

# add documents
db.add_documents(documents=documents)

Finally, you can perform similarity searches and display the contents of the most relevant document:

# Perform a similarity search on the vector store with a sample query.
query = "What makes Saturn unique?"
docs = db.similarity_search(query)

# Print the content of the most similar document.
print(docs[0].page_content)

The vector store can serve as a retriever to query a data store and fetch relevant data in response to natural language queries. Here's an example:

retriever = db.as_retriever(search_kwargs={"k": 5})
retriever.invoke("Which planet is the largest?")

Other capabilities

LangChain offers many more capabilities to streamline your workflow when developing LLM-powered applications. For instance, when building apps that require the LLM to do tool/function calling to perform arbitrary processing logic, LangChain simplifies defining tool schemas and orchestrating how models call them. This feature is especially useful for agents needing to perform various actions, from simple calculations to interactions with complex external systems.

With structured output, you can ensure LLM responses follow a predefined format, such as JSON, making it easier to parse, validate, and integrate the generated data into applications (for example, when working with tools). Structured output is accomplished using schema validators like Pydantic models, TypedDicts, JSON Schema specifications, or Python data classes.

You can even build agents — equip LLMs with tools and instructions — to intelligently perform various tasks autonomously. Agentic workflows need different components, tools, memory, prompts, and other elements to work effectively. LangChain makes building such complex workflows more streamlined. For example, here's how you can create a simple Reason and Act (ReAct) agent with a tool to convert an integer to binary:

# install packages and set your OpenAI API key in the environment: export OPENAI_API_KEY="sk-..." 
# pip install --pre -U langchain
# pip install -qU "langchain-openai"

from langchain.agents import create_agent
def to_binary(n: int) -> str:
    "Convert a given integer to binary"
    return bin(n)

agent = create_agent(
    model="openai:gpt-4o-mini",
    tools=[to_binary],
    prompt="You are a helpful assistant that can help return the binary of integers.",
)
print(agent.invoke({"messages":[{"role":"user","content":"binary of 42"}]}))

Conclusion

LangChain offers a comprehensive suite of capabilities and packages to streamline the development of LLM-powered applications. You can use various interfaces to connect to different LLMs, which helps you avoid vendor lock-in. Through prompt templates, you can effectively combine user input, context, and prompts to ensure LLMs respond efficiently. Integration packages enable you to work with multiple data sources to enhance LLM responses with current or proprietary information. If you need to orchestrate more advanced workflows, needing structured output and tool calling, particularly when creating agents, LangChain makes it effortless.

Using LangChain eliminates the need to handle API calls manually, reducing the risk of errors and improving performance.

2 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo