Getting Started with LangChain: Build LLM-Powered Agents Step by Step with Python

Getting Started with LangChain: Build LLM-Powered Agents Step by Step with Python

Learn how to get started with LangChain using Python. This guide covers model invocation, prompt templates, LCEL chains, streaming responses, structured outputs, custom runnables, and agent creation step by step.

What is LangChain

LangChain is an open source framework that helps developers build applications and agents powered by Large Language Models. It provides pre built abstractions for models, prompts, tools, and agents so you do not have to write repetitive glue code every time you interact with an LLM.

With very little code, LangChain allows you to connect to models from OpenAI, Anthropic, Google, and others. It handles common concerns such as prompt formatting, message handling, tool invocation, streaming, and structured outputs.

LangChain is best suited when you want to quickly build intelligent agents and LLM-driven applications. For more advanced orchestration needs such as deterministic workflows, strict latency control, and long running executions, LangGraph is recommended. LangChain agents are internally built on top of LangGraph, so many of those capabilities are already available without extra setup.

LangChain Library

LangChain provides official SDKs for Python and JavaScript or TypeScript. In this blog, we focus on the Python SDK.

Official installation guide: https://docs.langchain.com/oss/python/langchain/install

LangChain Migration from v0.4 to v1.0

LangChain v1 introduced a simplified and cleaner package structure. Older versions exposed a very large API surface that mixed experimental and production-ready features. Version 1 focuses only on core building blocks required for agents and LLM applications.

The langchain package now contains only essential modules such as agents, messages, tools, chat models, and embeddings. Older constructs like legacy chains, retrievers, indexing APIs, and the hub have been moved to langchain-classic, which you can install separately if needed.

This separation makes the framework easier to understand and maintain.

What Problems Does LangChain Solve

Building an LLM-powered application is not just about calling an API. You need to manage prompts, conversation history, structured outputs, tool calls, streaming responses, retries, and orchestration logic.

LangChain solves these problems by offering standard abstractions that work together. Instead of reinventing the same patterns in every project, you compose prompts, models, chains, and agents in a consistent and reusable way.

Prerequisites to Learn LangChain

Before starting with LangChain, you should be familiar with:

  • Python basics
  • Environment variables and API keys
  • REST APIs
  • Basic understanding of Large Language Models
  • Prompt engineering fundamentals

If you want a deeper understanding of LLMs, you can refer to: Understanding LLM and Their Working

Semantic Kernel vs LangChain

We previously covered Semantic Kernel here: What is Semantic kernel

Semantic Kernel is Microsoft’s enterprise-grade orchestration framework focused on structured workflows, planners, and deep Azure integration. It is designed for long-term stability, governance, and enterprise environments.

LangChain, on the other hand, emphasizes flexibility and rapid experimentation. It provides composable building blocks instead of rigid structures, making it ideal for research, startups, and fast-moving teams.

Choose Semantic Kernel for enterprise governance and Azure-first workflows. Choose LangChain for speed, flexibility, and advanced agent reasoning.

What We Will Cover in This Blog

In this guide, we focus on LangChain fundamentals:

  • Creating and invoking a chat model
  • Prompt templates
  • LangChain Expression Language
  • Chains
  • Streaming responses
  • Structured outputs
  • Creating agents with tools

Installing LangChain

We use uv as the Python package manager.

uv add langchain langchain-openai

Create a .env file and add your Azure OpenAI credentials:

AZURE_OPENAI_ENDPOINT=endpoint
AZURE_OPENAI_APIKEY=apikey
AZURE_OPENAI_DEPLOYMENTNAME=deployment_name

Creating the LLM Model

We begin by creating a chat model using Azure OpenAI.

from langchain_openai import AzureChatOpenAI
import os
from langchain_core.language_models import BaseChatModel

Here, we import the AzureChatOpenAI wrapper, the os module to read environment variables, and BaseChatModel which is the common interface LangChain uses for chat models.

llm_model: BaseChatModel = AzureChatOpenAI(
    api_key=os.getenv("AZURE_OPENAI_APIKEY"),
    azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
    azure_deployment="gpt-4.1-mini",
    api_version="2024-12-01-preview",
    temperature=0,
)

This creates a chat model instance. The API key and endpoint authenticate the request. The deployment name selects the Azure model. The API version ensures compatibility. Setting temperature to zero makes the output deterministic.

Invoking a Simple Model

Once the model is created, we can directly send a prompt to it.

from langchain.messages import AIMessage

The AIMessage class represents a message generated by the model.

simple_model_response: AIMessage = llm_model.invoke("Hello")

The invoke method sends a single prompt to the model and waits for a response.

print(simple_model_response.content)
print(simple_model_response.usage_metadata)

The content field contains the generated text, while usage_metadata provides token usage information.

Output:

Hello! How can I assist you today?
{'input_tokens': 8, 'output_tokens': 10, 'total_tokens': 18}

Prompt Templates

Prompt templates allow you to define reusable prompt structures.

from langchain_core.prompts import ChatPromptTemplate
from langchain.messages import SystemMessage

These imports give access to chat-based prompt templates and explicit message types.

prompt_template = ChatPromptTemplate.from_messages(
    [
        SystemMessage("You are a helpful Assistant. Reply to the User Query"),
        ("human", "Here is my Query: {query}"),
    ]
)

Here, we define a prompt with two messages. The system message sets the assistant’s behavior. The human message contains a placeholder that will be filled at runtime. Instead of using classes, you can also define messages using tuples like ("system", "..."), ("human", "..."), or ("ai", "...").

print(prompt_template.format(query="what is your name"))

Formatting the prompt shows the final message sequence sent to the model.

Output:

System: You are a helpful Assistant. Reply to the User Query
Human: Here is my Query: what is your name

LangChain Expression Language (LCEL)

LangChain Expression Language (LCEL) allows you to connect prompts, models, and parsers using the pipe operator. Each component implements the Runnable interface, which means the output of one component automatically becomes the input of the next.

Reference: Langchain Expression Language

Using Chains

Now we combine the prompt, model, and output parser.

from langchain_core.output_parsers import StrOutputParser

The StrOutputParser converts an AIMessage into plain text.

chain = prompt_template | llm_model | StrOutputParser()

This creates a pipeline where the prompt runs first, its output is sent to the model, and the model output is parsed into a string.

response = chain.invoke({"query": "what is your name"})
print(response)

Output:

I’m called ChatGPT. How can I assist you today?

Creating Custom Chains with Runnables

LangChain allows you to create fully custom chains using low level runnable primitives from the langchain_core.runnables module. These primitives give you fine grained control over how data flows between steps while still integrating with LangChain’s execution model.

Using RunnableLambda, you can wrap any Python function and treat it as a runnable component inside a chain. This is useful when you want to add custom logic, transformations, or validations between steps without creating a full LangChain abstraction.

With RunnableParallel, you can execute multiple runnables at the same time and combine their outputs. This enables parallel execution patterns such as running multiple prompts, models, or data retrieval steps concurrently and then merging the results.

By composing runnable primitives, you can build advanced custom chains that go beyond simple prompt to model pipelines while still benefiting from LangChain’s streaming, debugging, and orchestration features.

Streaming Responses

Instead of waiting for the full response, you can stream tokens as they are generated.

for chunk in chain.stream({"query": "what is your name"}):
    print(chunk, end="")

The same chain is executed, but the output is returned incrementally, which is useful for chat UIs.

Output:

I am called ChatGPT. How can I assist you today?

Structured Output

To enforce structured responses, we define a schema using Pydantic.

from pydantic import BaseModel, Field
class User(BaseModel):
    id: str = Field(description="The Id of the User", default="1")
    name: str = Field(description="Name of the User")
    username: str = Field(description="Username of the User")

This model defines the exact shape of the expected output. Field descriptions help the LLM understand what data to generate.

model_structured = llm_model.with_structured_output(schema=User)

Here, we bind the schema to the model so the LLM is instructed to return structured data.

prompt_template = ChatPromptTemplate.from_messages(
    [
        ("system", "You are a helpful Assistant."),
        ("human", "User info: {data}. Convert it into the given schema."),
    ]
)
chain = prompt_template | model_structured
structured_response = chain.invoke(
    {"data": "I am Abhinav Prasad, username abhi_pras, user id 123"}
)
print(structured_response)

Output:

id='123' name='Abhinav Prasad' username='abhi_pras'

The output is now a validated Python object instead of free text.

Structured Output Options

LangChain does not restrict structured output generation to Pydantic alone. While Pydantic models are commonly used because they provide validation and rich error handling, LangChain also supports other schema definitions.

You can define structured outputs using Python dataclasses when you want a lightweight structure without validation overhead. Alternatively, you can use TypedDict when you prefer static typing support without runtime validation. LangChain treats Pydantic models, dataclasses, and TypedDict schemas as valid structured output definitions, allowing you to choose the approach that best fits your application needs.

Creating Tools

Tools allow agents to call Python functions.

from langchain.tools import tool
@tool
def multiple_number(a: int, b: int) -> float:
    return a * b

The @tool decorator registers this function as a callable tool. The function signature and type hints tell the agent what inputs are required and what output to expect.

Creating an Agent

Agents combine a model with tools and reasoning logic.

from langchain.agents import create_agent
agent = create_agent(model=llm_model, tools=[multiple_number])

This creates an agent that can reason and decide when to call the multiplication tool.

agent_response = agent.invoke("Multiply 3 and 4")
for message in agent_response["messages"]:
    print(message.pretty_repr())

Output:

AI: Calling tool multiple_number with a=3 b=4
Tool Result: 12
AI: The result of multiplying 3 and 4 is 12.

The agent reasons about the request, invokes the tool, and uses the result to generate the final response.

Advanced Concepts

LangChain also supports advanced features such as tool runtime control, guardrails, middleware, human in the loop workflows, and execution interrupts.

Official documentation: https://docs.langchain.com/oss/python/langchain/overview

Conclusion

LangChain offers a practical and flexible way to build LLM-powered applications and agents. By composing prompts, models, chains, and tools, you can scale from simple experiments to production-ready systems. If you value flexibility, fast iteration, and powerful agent workflows, LangChain is an excellent starting point.