- name
- langchain-framework
- description
- Guidance for building LLM-powered agents with LangChain, LangGraph, or Deep Agents. Use when building agents with any of these frameworks, implementing tool-calling agents, stateful graph workflows, or multi-step orchestration. Read this skill before writing any LangChain/LangGraph agent code.
- compatibility
- Python ≥ 3.10, uv for install, API keys in .env. langchain >= 1.0,<2.0; langgraph >= 1.0,<2.0; langsmith >= 0.3.0; deepagents latest.
- allowed-tools
- Read
- metadata
- {"author":"NeMo Helix Team <nemo-helix@nvidia.com>"}
<!-- SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
# Building Agents with LangChain / LangGraph
> NVIDIA-authored guidance summarizing the public LangChain, LangGraph, and Deep Agents
> documentation (<https://docs.langchain.com>), which is published by LangChain, Inc. under the
> MIT license. API names and short usage examples are drawn from those docs; the framework
> selection guidance, comparisons, and structure are original.
LangChain, LangGraph, and Deep Agents are **layered** frameworks — each builds on the one below:
```
┌─────────────────────────────────────────┐
│ Deep Agents │ ← batteries included: planning, memory, files
├─────────────────────────────────────────┤
│ LangGraph │ ← orchestration: graphs, loops, state
├─────────────────────────────────────────┤
│ LangChain │ ← foundation: models, tools, chains
└─────────────────────────────────────────┘
```
## Pick Your Framework
| Question | Yes → | No → |
|----------|-------|------|
| Need sub-task planning, file management, persistent memory, or on-demand skills? | **Deep Agents** | ↓ |
| Need loops, branching, human-in-the-loop, or custom state? | **LangGraph** | ↓ |
| Single-purpose agent with fixed tools? (+ middleware for HITL, retry, PII, etc.) | **LangChain** (`create_agent`) | ↓ |
| Pure model call or chain with no agent loop? | **LangChain** (LCEL) | — |
## Installation
```bash
uv add langchain langchain-core langsmith
# Orchestration — only add if you use langgraph.* imports directly or Deep Agents:
uv add langgraph # StateGraph, checkpointers, etc.
uv add deepagents # Deep Agents (includes LangGraph)
# Model provider — pick yours:
uv add langchain-anthropic # Claude (direct) — installed in this environment
uv add langchain-openai # GPT or any OpenAI-compatible endpoint — NOT pre-installed; add explicitly
```
> **Note:** `langchain-openai` is **not installed by default** in this environment. Code examples below that use `ChatOpenAI` require `uv add langchain-openai` first. All code examples work with `langchain-anthropic` out of the box.
Set your API key in `.env`:
```bash
ANTHROPIC_API_KEY=<your-key> # if using langchain-anthropic directly
# or, for any OpenAI-compatible endpoint (e.g. a custom inference server):
OPENAI_API_KEY=<your-key>
LANGSMITH_API_KEY=<your-key> # optional, for tracing
```
## Hello World
### LangChain — LCEL chain (pure model call, no agent loop)
```python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from pydantic import BaseModel
import os
# Option A: Anthropic directly (langchain-anthropic installed by default)
from langchain_anthropic import ChatAnthropic
model = ChatAnthropic(model="claude-sonnet-4-5")
# Option B: OpenAI-compatible endpoint — requires: uv add langchain-openai
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="<model-name>",
base_url="<your-endpoint-url>",
api_key=os.environ["YOUR_API_KEY"],
)
# Option 1: plain text output
chain = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
("human", "{input}"),
]) | model | StrOutputParser()
print(chain.invoke({"input": "Say hello"}))
# Option 2: structured output
class Reply(BaseModel):
message: str
structured_chain = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
("human", "{input}"),
]) | model.with_structured_output(Reply)
print(structured_chain.invoke({"input": "Say hello"}))
```
### LangChain — `create_agent` (single-purpose agent with fixed tools)
```python
from langchain.agents import create_agent
from langchain_core.tools import tool
from datetime import date
@tool
def get_current_date() -> str:
"""Get today's date."""
return date.today().isoformat()
agent = create_agent(
model="anthropic:claude-sonnet-4-5", # or a model instance (see above)
tools=[get_current_date],
system_prompt="You are a helpful assistant.",
)
result = agent.invoke({"messages": [{"role": "user", "content": "What is today's date?"}]})
print(result["messages"][-1].content)
```
**Middleware** — `create_agent` supports a `middleware` list for cross-cutting concerns:
| Middleware | Purpose |
|---|---|
| `HumanInTheLoopMiddleware` | Pause for human approval before tool calls |
| `TodoListMiddleware` | Track sub-tasks with a built-in todo list |
| `ModelRetryMiddleware` | Retry on model errors |
| `ToolRetryMiddleware` | Retry on tool call errors |
| `ModelCallLimitMiddleware` | Cap total LLM calls per run |
| `SummarizationMiddleware` | Auto-compress context when it grows too long |
| `PIIMiddleware` | Strip PII from model inputs/outputs |
```python
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware, TodoListMiddleware
agent = create_agent(
model="anthropic:claude-sonnet-4-5",
tools=[...],
middleware=[HumanInTheLoopMiddleware(), TodoListMiddleware()],
)
```
**Per-invocation typed config (`context_schema`)** — pass typed context (user IDs, flags) to nodes without global state:
```python
from langchain.agents import create_agent
from pydantic import BaseModel
class UserContext(BaseModel):
user_id: str
locale: str = "en"
agent = create_agent(
model="anthropic:claude-sonnet-4-5",
tools=[...],
context_schema=UserContext,
)
result = agent.invoke(
{"messages": [...]},
config={"configurable": {"context": UserContext(user_id="u123")}}
)
```
See `references/langchain-middleware.md` for full middleware reference.
### LangGraph — `StateGraph` (custom state, loops, branching)
```python
from langgraph.graph import StateGraph, START, END
from langchain_core.tools import tool
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
from datetime import date
import os
# Requires: uv add langchain-openai (or swap for ChatAnthropic)
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="<model-name>",
base_url="<your-endpoint-url>",
api_key=os.environ["YOUR_API_KEY"],
)
@tool
def get_current_date() -> str:
"""Get today's date."""
return date.today().isoformat()
class State(TypedDict):
messages: Annotated[list, add_messages]
model_with_tools = model.bind_tools([get_current_date])
def call_model(state: State) -> State:
return {"messages": [model_with_tools.invoke(state["messages"])]}
graph = StateGraph(State)
graph.add_node("agent", call_model)
graph.add_edge(START, "agent")
graph.add_edge("agent", END)
agent = graph.compile()
result = agent.invoke({"messages": [{"role": "user", "content": "What is today's date?"}]})
print(result["messages"][-1].content)
```
### Deep Agents — `create_deep_agent` (planning, files, memory, subagents)
```python
from deepagents import create_deep_agent
from langchain_core.tools import tool
from langgraph.checkpoint.memory import MemorySaver
from datetime import date
@tool
def get_current_date() -> str:
"""Get today's date."""
return date.today().isoformat()
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-5",
tools=[get_current_date],
system_prompt="You are a helpful assistant.",
checkpointer=MemorySaver(), # required for deep agents
)
# thread_id is always required for deep agents
config = {"configurable": {"thread_id": "session-1"}}
result = agent.invoke({"messages": [{"role": "user", "content": "What is today's date?"}]}, config=config)
print(result["messages"][-1].content)
```
## Core Concepts
### Tools
Tools are Python functions decorated with `@tool`. The docstring is the LLM's description of when and how to call the tool. Pass tools to `create_agent`, `create_deep_agent`, or bind them to a LangGraph model with `bind_tools`.
```python
from langchain_core.tools import tool
@tool
def search_files(pattern: str) -> list[str]:
"""Search for files matching the given glob pattern."""
import glob
return glob.glob(pattern, recursive=True)
agent = create_agent(model="anthropic:claude-sonnet-4-5", tools=[search_files])
```
For stateful or resource-sharing tools, use class-based tools or `StructuredTool` for complex input schemas.
---
### System Prompt
Pass `system_prompt=` to `create_agent` or `create_deep_agent` to set the agent's role and constraints:
```python
agent = create_agent(
model="anthropic:claude-sonnet-4-5",
tools=[...],
system_prompt="You are a precise code reviewer. Always cite line numbers.",
)
```
For LCEL chains, set the system message in `ChatPromptTemplate`:
```python
chain = ChatPromptTemplate.from_messages([
("system", "You are a precise code reviewer. Always cite line numbers."),
("human", "{input}"),
]) | model | StrOutputParser()
```
For LangGraph, inject a `SystemMessage` at the start of the state or inside a node.
---
### Agent Types
Choose based on workflow complexity:
| Question | Answer |
|---|---|
| Need planning, memory, file management, or on-demand skills? | `create_deep_agent` |
| Need custom state, loops, branching, or human-in-the-loop? | `StateGraph` (LangGraph) |
| Single-purpose agent with fixed tools? | `create_agent` |
| Pure model call or chain? | LCEL (`ChatPromptTemplate | model | parser`) |
```python
# create_agent — fixed tools, middleware support
agent = create_agent(model="anthropic:claude-sonnet-4-5", tools=[...])
# StateGraph — custom state and control flow
graph = StateGraph(State)
graph.add_node("agent", call_model)
agent = graph.compile()
# create_deep_agent — planning, memory, subagents, skills
agent = create_deep_agent(model="...", tools=[...], checkpointer=MemorySaver())
```
---
### Structured Output
When a method needs to return a specific format, use `.with_structured_output()` rather than describing the format in the system prompt. Structured output is enforced by the model API — it is more reliable than prompt instructions and gives downstream code a typed, predictable value.
```python
from pydantic import BaseModel
class Analysis(BaseModel):
sentiment: str
confidence: float
topics: list[str]
structured_chain = ChatPromptTemplate.from_messages([
("system", "Analyze the text."),
("human", "{input}"),
]) | model.with_structured_output(Analysis)
result = structured_chain.invoke({"input": "Great product!"})
print(result.sentiment) # validated Analysis instance
```
Bind the schema in `__init__` so every call on that model instance returns the typed output:
```python
class MyAgent:
def __init__(self):
self.llm = ChatNVIDIA(model="meta/llama-3.1-70b-instruct").with_structured_output(MySchema)
```
---
### Dynamic Prompts
Use template variables in `ChatPromptTemplate` to inject runtime values:
```python
chain = ChatPromptTemplate.from_messages([
("system", "You are a {role}. Focus on {domain}."),
("human", "{input}"),
]) | model | StrOutputParser()
result = chain.invoke({"role": "code reviewer", "domain": "security", "input": "..."})
```
For `create_agent`, pass per-invocation typed config via `context_schema`:
```python
class RunContext(BaseModel):
language: str = "Python"
strict_mode: bool = False
agent = create_agent(model="...", tools=[...], context_schema=RunContext)
result = agent.invoke({...}, config={"configurable": {"context": RunContext(language="Go")}})
```
---
### Skills
Deep Agents supports skills — callable modules the agent can invoke on demand:
```python
from deepagents import create_deep_agent, Skill
View on GitHub