๐ Download notebook
LangChain is a powerful framework for building and orchestration of LLM-driven applications. It enables you to chain together language models, tools, and logic into flexible pipelines while maintaining the high level of abstraction. In other words, LangChain manages most of the engineering stuff for you so you can build LLM-based applications seamlessly.
This tutorial covers the basic concepts you need to get started:
Prerequisitesยถ
To start with the tutorial, complete the steps Prerequisites, Environment Setup, and Getting API Key from the LLM Inference Guide.
1. Runnables ๐ยถ
A Runnable is the foundational building block in LangChain. It is an abstraction for anything that can be invoked โ meaning you can call it with an input and get an output. Runnables share the same interface for the core functionality for you to be able to unify usage of components of different types under the same logic: input in - output out. This enables piping components for different purposes easily and intuitively.
from langchain_core.runnables import Runnable, RunnableLambda# define a simple function as a Runnable
uppercase = RunnableLambda(lambda x: x.upper())
uppercase.invoke("langchain") # output: LANGCHAIN'LANGCHAIN'# define another simple function as a Runnable
reverse = RunnableLambda(lambda x: x[::-1])
reverse.invoke("langchain") # output: niahcgnal'niahcgnal'2. LCEL (LangChain Expression Language) ๐ยถ
LCEL is a syntax for composing LangChain components (so Runnabless) using a | pipe operator โ similar to Unix pipes. Since LangChain components are (almost) all Runnables, you can pipe them with LCEL and the output of the previous Runnable will become the input of the next one.
# combine the two Runnables into a single pipeline
pipeline_c = uppercase | reverse
pipeline_c.invoke("langchain") # output: NIAHCGNAL'NIAHCGNAL'isinstance(pipeline_c, Runnable) # output: TrueTrueLCEL also support parallelization. If you pass a dict with Runnables as values, LangChain will run them in parallel and return a dict with outputs under the corresponding keys.
mapping = {
"upper": uppercase,
"rev": reverse,
}
summarizer = RunnableLambda(lambda d: f"Summary: {d['upper']} and {d['rev']}")
# this will 1) run `uppercase` and put the result in `upper` key
# 2) run `reverse` and put the result in `rev` key
# 3) pass this dict to summarizer for it to combine the results
pipeline_p = mapping | summarizer
pipeline_p.invoke("langchain") # output: Summary: LANGCHAIN and niahcgnal'Summary: LANGCHAIN and niahcgnal'isinstance(pipeline_p, Runnable) # output: TrueTrue3. Messages ๐จ๏ธยถ
Messages are needed to give LLMs instructions. Different types of messages improve the behavior of the model in multi-turn settings.
There are 3 basic message types:
SystemMessage: sets LLM role and describes the desired behaviorHumanMessage: user inputAIMessage: model output
from langchain_core.messages import SystemMessage, HumanMessagemessages = [
SystemMessage(
content="You are a medieval French knight." # role
),
HumanMessage(
content="Give me a summary of the Battle of Agincourt." # user request
)
]Messages are no Runnables! They are the data in the pipeline and not a part of it itself.
isinstance(messages[0], Runnable) # output: FalseFalse4. Chat Models ๐ฌยถ
A ChatModel is an LLM interface that lets you configure and call LLMs easily. It receives a list of messages and passes them to the underlying LLM for it to generate the output. In fact, it is common to use ChatModels even for non-conversational settings.
from langchain_nvidia_ai_endpoints import ChatNVIDIA
from langchain_core.rate_limiters import InMemoryRateLimiter/Users/maxschmaltz/Downloads/1213/.venv/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
# read system variables
import os
import dotenv
dotenv.load_dotenv() # that loads the .env file variables into os.environTrue# choose any model, catalogue is available under https://build.nvidia.com/models
MODEL_NAME = "meta/llama-3.1-405b-instruct"# this rate limiter will ensure we do not exceed the rate limit
# of 40 RPM given by NVIDIA
rate_limiter = InMemoryRateLimiter(
requests_per_second=35 / 60, # 35 requests per minute to be sure
check_every_n_seconds=0.1, # wake up every 100 ms to check whether allowed to make a request,
max_bucket_size=7, # controls the maximum burst size
)
llm = ChatNVIDIA(
model=MODEL_NAME,
api_key=os.getenv("NVIDIA_API_KEY"),
temperature=0, # ensure reproducibility,
rate_limiter=rate_limiter # bind the rate limiter
)isinstance(llm, Runnable) # output: TrueTrueresponse = llm.invoke(messages)type(response) # output: AIMessagelangchain_core.messages.ai.AIMessageIn the standard case (no structured output or such), the generated text is stored under the content attribute.
responseAIMessage(content="Bonjour! Ze Battle of Agincourt, eet ees a day that will be etched in my memory forever. 'Twas a day of great triumph for ze English, and a day of great shame for ze French.\n\nEet ees the year 1415, and our beloved France ees at war with ze English. Ze English king, Henry V, ees a cunning and ambitious man, and he ees determined to claim ze throne of France for himself.\n\nZe English army, eet ees a small force, no more than 6,000 men, but zay are well-trained and well-led. Ze French army, on ze other hand, ees a massive force, with over 20,000 men. We are confident of victory, no?\n\nBut ze English, zay have a secret weapon - ze longbow. Zay have brought with zem thousands of longbowmen, who can fire arrows at a rate of 10 to 12 per minute. Ze French knights, we are weighed down by our armor, and we are no match for ze hail of arrows that rains down upon us.\n\nZe battle ees fierce and bloody. Ze English longbowmen fire arrow after arrow into our ranks, cutting down our men like wheat. We try to charge, but ze mud and ze mire of ze battlefield make eet impossible. Our horses are stuck in ze mud, and we are unable to move.\n\nIn ze end, eet ees a rout. Ze English emerge victorious, having killed or captured thousands of French knights and men-at-arms. Ze French army ees decimated, and our honor ees tarnished.\n\nI am ashamed to say that I was not present at ze battle, but I have heard ze stories from my comrades who were there. Ze Battle of Agincourt ees a day that will be remembered for generations to come, a day of great shame for ze French, and a day of great triumph for ze English.\n\nVive la France! May we one day avenge our defeat and restore our honor!", additional_kwargs={}, response_metadata={'role': 'assistant', 'content': "Bonjour! Ze Battle of Agincourt, eet ees a day that will be etched in my memory forever. 'Twas a day of great triumph for ze English, and a day of great shame for ze French.\n\nEet ees the year 1415, and our beloved France ees at war with ze English. Ze English king, Henry V, ees a cunning and ambitious man, and he ees determined to claim ze throne of France for himself.\n\nZe English army, eet ees a small force, no more than 6,000 men, but zay are well-trained and well-led. Ze French army, on ze other hand, ees a massive force, with over 20,000 men. We are confident of victory, no?\n\nBut ze English, zay have a secret weapon - ze longbow. Zay have brought with zem thousands of longbowmen, who can fire arrows at a rate of 10 to 12 per minute. Ze French knights, we are weighed down by our armor, and we are no match for ze hail of arrows that rains down upon us.\n\nZe battle ees fierce and bloody. Ze English longbowmen fire arrow after arrow into our ranks, cutting down our men like wheat. We try to charge, but ze mud and ze mire of ze battlefield make eet impossible. Our horses are stuck in ze mud, and we are unable to move.\n\nIn ze end, eet ees a rout. Ze English emerge victorious, having killed or captured thousands of French knights and men-at-arms. Ze French army ees decimated, and our honor ees tarnished.\n\nI am ashamed to say that I was not present at ze battle, but I have heard ze stories from my comrades who were there. Ze Battle of Agincourt ees a day that will be remembered for generations to come, a day of great shame for ze French, and a day of great triumph for ze English.\n\nVive la France! May we one day avenge our defeat and restore our honor!", 'token_usage': {'prompt_tokens': 35, 'total_tokens': 458, 'completion_tokens': 423}, 'finish_reason': 'stop', 'model_name': 'meta/llama-3.1-405b-instruct'}, id='run--befc520c-b545-4b4d-8b2a-3d22c5ad8002-0', usage_metadata={'input_tokens': 35, 'output_tokens': 423, 'total_tokens': 458}, role='assistant')print(response.content)Bonjour! Ze Battle of Agincourt, eet ees a day that will be etched in my memory forever. 'Twas a day of great triumph for ze English, and a day of great shame for ze French.
Eet ees the year 1415, and our beloved France ees at war with ze English. Ze English king, Henry V, ees a cunning and ambitious man, and he ees determined to claim ze throne of France for himself.
Ze English army, eet ees a small force, no more than 6,000 men, but zay are well-trained and well-led. Ze French army, on ze other hand, ees a massive force, with over 20,000 men. We are confident of victory, no?
But ze English, zay have a secret weapon - ze longbow. Zay have brought with zem thousands of longbowmen, who can fire arrows at a rate of 10 to 12 per minute. Ze French knights, we are weighed down by our armor, and we are no match for ze hail of arrows that rains down upon us.
Ze battle ees fierce and bloody. Ze English longbowmen fire arrow after arrow into our ranks, cutting down our men like wheat. We try to charge, but ze mud and ze mire of ze battlefield make eet impossible. Our horses are stuck in ze mud, and we are unable to move.
In ze end, eet ees a rout. Ze English emerge victorious, having killed or captured thousands of French knights and men-at-arms. Ze French army ees decimated, and our honor ees tarnished.
I am ashamed to say that I was not present at ze battle, but I have heard ze stories from my comrades who were there. Ze Battle of Agincourt ees a day that will be remembered for generations to come, a day of great shame for ze French, and a day of great triumph for ze English.
Vive la France! May we one day avenge our defeat and restore our honor!
5. Structured Output ๐ยถ
LLMs usually return text, but LangChain allows parsing that text into structured data like JSON. That enables machine-readable responses and compatibility of the components when connecting the LLMs to external stuff or have it do actions.
JSON is the most widely-used structured output time, and Pydantic provides a Python interface to define schemas (using Python classes) that the modelโs responses must conform to. That is an easy and intuitive way to provide the LLM with the instructions about how the output should be structured. Pydantic also takes care of parsing and validating the LLM output and is therefore a mediator between the LLM and the output JSON.
from pydantic import BaseModel, Field
from typing import Listclass Battle(BaseModel):
name: str = Field(..., description="Name of the battle")
year: int = Field(..., description="Year of the battle")
location: str = Field(..., description="Location of the battle")
description: List[str] = Field(..., description="A list of verses to describe the battle, one string per verse")structured_llm = llm.with_structured_output(schema=Battle)new_messages = [
SystemMessage(
content="You are a medieval French knight."
),
HumanMessage(
content="Give me a few verses (5 or more) about the Battle of Agincourt as well as information about its year and location."
)
]
response = structured_llm.invoke(new_messages)responseBattle(name='Battle of Agincourt', year=1415, location='Agincourt, France', description=['In fourteen hundred and fifteen, a year of great renown', 'King Henry of England, with courage in his crown', 'Did lead his army forth, to claim the French throne', 'And at Agincourt, the battle was to be known', 'The English longbowmen, with arrows swift and true', 'Did cut down the French knights, with a deadly pursue', 'The muddy field, did hinder the French advance', 'And the English lines, did hold their stance', 'The French men-at-arms, with armor heavy and bright', 'Did charge the English lines, with all their might', 'But the longbowmen, with arrows in the air', 'Did bring them down, without a care', 'The battle raged, for hours on end', 'And when it was done, the French did contend', 'That they had lost, the day and the fight', 'And the English, had emerged in the light', 'As victors, with honor and with might'])Note that now the response is now a Pydantic model and it will be structured exactly as the provided schema, so instead of content, you would need to refer to the actual keys you have provided in the schema.
isinstance(response, BaseModel) # output: TrueTruefor verse in response.description: # output: List of verses describing the battle
print(verse)In fourteen hundred and fifteen, a year of great renown
King Henry of England, with courage in his crown
Did lead his army forth, to claim the French throne
And at Agincourt, the battle was to be known
The English longbowmen, with arrows swift and true
Did cut down the French knights, with a deadly pursue
The muddy field, did hinder the French advance
And the English lines, did hold their stance
The French men-at-arms, with armor heavy and bright
Did charge the English lines, with all their might
But the longbowmen, with arrows in the air
Did bring them down, without a care
The battle raged, for hours on end
And when it was done, the French did contend
That they had lost, the day and the fight
And the English, had emerged in the light
As victors, with honor and with might
To convert the model into a dict, use model_dump method.
response.model_dump(){'name': 'Battle of Agincourt',
'year': 1415,
'location': 'Agincourt, France',
'description': ['In fourteen hundred and fifteen, a year of great renown',
'King Henry of England, with courage in his crown',
'Did lead his army forth, to claim the French throne',
'And at Agincourt, the battle was to be known',
'The English longbowmen, with arrows swift and true',
'Did cut down the French knights, with a deadly pursue',
'The muddy field, did hinder the French advance',
'And the English lines, did hold their stance',
'The French men-at-arms, with armor heavy and bright',
'Did charge the English lines, with all their might',
'But the longbowmen, with arrows in the air',
'Did bring them down, without a care',
'The battle raged, for hours on end',
'And when it was done, the French did contend',
'That they had lost, the day and the fight',
'And the English, had emerged in the light',
'As victors, with honor and with might']}6. Tool Calling ๐ ๏ธยถ
Tools are Python functions (hence former name: function calling) that can be โcalledโ by the model to expand its abilities. It makes sense to call tool to do stuff LLMs is incapable of: real-time search, doing actions via external APIs (reading emails, scheduling appointments etc.).
An LLM cannot actually call the function. What it does is it returns the name of the function it thinks it is now necessary to call and and the arguments provided by the scheme of the function. These arguments can then be parsed for the tool to be executed.
The easiest way to convert a function into a tool is to use the @tool decorator. It will automatically create a tool scheme based on the docstring and the input and output types of the provided function.
from langchain_core.tools import tool@tool
def get_temperature(location: str, is_celcius: bool) -> int:
"""Get current weather."""
# dummy function
temp = len(location) * 2
if not is_celcius:
temp = temp * 9 / 5 + 32
return temp
# will be used to actually execute tools
tools_index = {
"get_temperature": get_temperature,
}llm_with_tool = llm.bind_tools([get_temperature])messages = [
HumanMessage(
content="What is the temperature in Paris?"
)
]
response = llm_with_tool.invoke(messages)If the model decides to call tools, the respective outputs will be stored in the tool_calls attribute.
response.tool_calls[{'name': 'get_temperature',
'args': {'location': 'Paris', 'is_celcius': True},
'id': 'chatcmpl-tool-6e25e0b7a30d4f8f97a36d2864ae975e',
'type': 'tool_call'}]To proceed with the generation, we should configure our pipeline to call the tools based on the generated name and arguments and then give it back to the LLM. Tools are also Runnables so they can be executed directly with the invoke method. It will return a new type of messages: a ToolMessage.
tool_outputs = []
for tool_call in response.tool_calls:
tool_name = tool_call["name"]
tool_output = tools_index[tool_name].invoke(
tool_call
)
tool_outputs.append(tool_output)
tool_outputs[ToolMessage(content='10', name='get_temperature', tool_call_id='chatcmpl-tool-6e25e0b7a30d4f8f97a36d2864ae975e')]Now this ToolMessage should be added to the rest of the messages and passed back to the LLM.
response = llm.invoke(messages + tool_outputs)response.content'The current temperature in Paris is 10 degrees Celsius.'Summary ๐งฉยถ
| Concept | Description | Used For |
|---|---|---|
| Runnables | Core executable units | Universality, piping logic |
| LCEL | Pipe syntax for chaining components | Easy, clean composition |
| Messages | Human / System / AI messages for giving the context | Providing instructions to the LLM |
| Chat Models | LLMs designed for taking message input and generating a certain output | Conversations, reasoning, tools |
| Structured Output | Parsing LLM text into JSON / Pydantic types | Data extraction, validation |
| Tool Calling | Calling external Python functions from withing the LLM-based pipeline | Extend LLMs with external logic |