| name | langchain-agents |
| description | Create and use LangChain agents with createAgent - includes agent loops, ReAct pattern, tool execution, and state management |
| language | js |
langchain-agents (JavaScript/TypeScript)
Overview
Agents combine language models with tools to create systems that can reason about tasks, decide which tools to use, and iteratively work towards solutions. The createAgent() function provides a production-ready agent implementation built on LangGraph.
Key Concepts:
- Agent Loop: The model decides → calls tools → observes results → repeats until done
- ReAct Pattern: Reasoning and Acting - the agent reasons about what to do, then acts by calling tools
- Graph-based Runtime: Agents run on a LangGraph graph with nodes (model, tools, middleware) and edges
When to Use Agents
| Scenario | Use Agent? | Why |
|---|
| Need to call external APIs/databases | ✅ Yes | Agents can dynamically choose which tools to call |
| Multi-step task with decision points | ✅ Yes | Agent loop handles iterative reasoning |
| Simple prompt-response | ❌ No | Use a chat model directly |
| Predetermined workflow | ❌ No | Use LangGraph workflow instead |
| Need tool calling without iteration | ⚠️ Maybe | Consider using model.bindTools() directly |
Decision Tables
Choosing Agent Configuration
| Need | Configuration | Example |
|---|
| Basic agent with tools | createAgent({ model, tools }) | Search, calculator, weather |
| Custom system instructions | Add systemPrompt | Domain-specific behavior |
| Human approval for sensitive operations | Add humanInTheLoopMiddleware | Database writes, emails |
| Persistence across sessions | Add checkpointer | Multi-turn conversations |
| Structured output format | Add responseFormat | Extract contact info, parse forms |
Tool Strategy
| Tool Type | When to Use | Example |
|---|
| Static tools | Tools don't change during execution | Search, weather, calculator |
| Dynamic tools | Tools depend on runtime state | User-specific APIs |
| Built-in tools | Need common functionality | File system, code execution |
| Custom tools | Domain-specific operations | Your business logic |
Code Examples
Basic Agent with Tools
import { createAgent } from "langchain";
import { tool } from "langchain";
const searchTool = tool(
async ({ query }: { query: string }) => {
return `Results for: ${query}`;
},
{
name: "search",
description: "Search for information on the web",
schema: z.object({
query: z.string().describe("The search query"),
}),
}
);
const weatherTool = tool(
async ({ location }: { location: string }) => {
return `Weather in ${location}: Sunny, 72°F`;
},
{
name: "get_weather",
description: "Get current weather for a location",
schema: z.object({
location: z.string().describe("City name"),
}),
}
);
const agent = createAgent({
model: "gpt-4.1",
tools: [searchTool, weatherTool],
});
const result = await agent.invoke({
messages: [
{ role: "user", content: "What's the weather in San Francisco?" }
],
});
console.log(result.messages[result.messages.length - 1].content);
Agent with System Prompt
import { createAgent } from "langchain";
const agent = createAgent({
model: "gpt-4.1",
tools: [searchTool, calculatorTool],
systemPrompt: `You are a helpful research assistant.
Always cite your sources when using the search tool.
Show your work when performing calculations.`,
});
Agent Loop Execution Flow
const agent = createAgent({
model: "gpt-4.1",
tools: [searchTool, weatherTool],
});
const result = await agent.invoke({
messages: [
{
role: "user",
content: "Search for the capital of France, then get its weather"
}
],
});
Streaming Agent Progress
import { createAgent } from "langchain";
const agent = createAgent({
model: "gpt-4.1",
tools: [searchTool],
});
for await (const chunk of await agent.stream(
{ messages: [{ role: "user", content: "Search for LangChain" }] },
{ streamMode: "updates" }
)) {
console.log("Step:", chunk);
}
for await (const chunk of await agent.stream(
{ messages: [{ role: "user", content: "Search for LangChain" }] },
{ streamMode: "messages" }
)) {
const [token, metadata] = chunk;
if (token.content) {
process.stdout.write(token.content);
}
}
Agent with Persistence
import { createAgent } from "langchain";
import { MemorySaver } from "@langchain/langgraph";
const checkpointer = new MemorySaver();
const agent = createAgent({
model: "gpt-4.1",
tools: [searchTool],
checkpointer,
});
const config = { configurable: { thread_id: "user-123" } };
await agent.invoke({
messages: [{ role: "user", content: "My name is Alice" }]
}, config);
await agent.invoke({
messages: [{ role: "user", content: "What's my name?" }]
}, config);
Multiple Tool Calls in Parallel
const agent = createAgent({
model: "gpt-4.1",
tools: [weatherTool, newsToolTool],
});
const result = await agent.invoke({
messages: [{
role: "user",
content: "Get weather for NYC and latest news for SF"
}],
});
Dynamic Tools (Runtime-Dependent)
import { createAgent } from "langchain";
const agent = createAgent({
model: "gpt-4.1",
tools: (state) => {
const userId = state.config?.configurable?.user_id;
return [
getUserSpecificTool(userId),
commonTool,
];
},
});
Error Handling in Agents
import { createAgent, wrapToolCall } from "langchain";
const errorHandler = wrapToolCall({
name: "ErrorHandler",
wrapToolCall: async (toolCall, handler) => {
try {
return await handler(toolCall);
} catch (error) {
return {
...toolCall,
content: `Tool error: ${error.message}`,
};
}
},
});
const agent = createAgent({
model: "gpt-4.1",
tools: [riskyTool],
middleware: [errorHandler],
});
Boundaries
What Agents CAN Configure
✅ Model: Any chat model (OpenAI, Anthropic, Google, etc.)
✅ Tools: Custom tools, built-in tools, dynamic tools
✅ System Prompt: Instructions for agent behavior
✅ Middleware: Human-in-the-loop, error handling, logging
✅ Checkpointer: Memory/persistence across conversations
✅ Response Format: Structured output schemas
✅ Max Iterations: Prevent infinite loops
What Agents CANNOT Configure
❌ Direct Graph Structure: Use LangGraph directly for custom flows
❌ Tool Execution Order: Model decides which tools to call
❌ Interrupt Model Decision: Can only interrupt before tool execution
❌ Multiple Models: One agent = one model (use subagents for multiple)
Gotchas
1. Agent Doesn't Stop (Infinite Loop)
const agent = createAgent({
model: "gpt-4.1",
tools: [searchTool],
});
await agent.invoke({
messages: [{ role: "user", content: "Keep searching until perfect" }]
});
const agent = createAgent({
model: "gpt-4.1",
tools: [searchTool],
maxIterations: 10,
});
2. Tool Not Being Called
const badTool = tool(
async ({ input }: { input: string }) => "result",
{
name: "tool",
description: "Does stuff",
schema: z.object({ input: z.string() }),
}
);
const goodTool = tool(
async ({ query }: { query: string }) => "result",
{
name: "web_search",
description: "Search the web for current information about a topic. Use this when you need recent data that wasn't in your training.",
schema: z.object({
query: z.string().describe("The search query (2-10 words)"),
}),
}
);
3. State Not Persisting
const agent = createAgent({
model: "gpt-4.1",
tools: [searchTool],
});
await agent.invoke({ messages: [{ role: "user", content: "Hi, I'm Bob" }] });
await agent.invoke({ messages: [{ role: "user", content: "What's my name?" }] });
import { MemorySaver } from "@langchain/langgraph";
const agent = createAgent({
model: "gpt-4.1",
tools: [searchTool],
checkpointer: new MemorySaver(),
});
const config = { configurable: { thread_id: "session-1" } };
await agent.invoke({ messages: [{ role: "user", content: "Hi, I'm Bob" }] }, config);
await agent.invoke({ messages: [{ role: "user", content: "What's my name?" }] }, config);
4. Messages vs State Confusion
const result = await agent.invoke({
messages: [{ role: "user", content: "Hello" }]
});
console.log(result.messages);
console.log(result.structuredResponse);
5. Tool Results Must Be Serializable
const badTool = tool(
async () => {
return new Date();
},
{ name: "get_time", description: "Get current time" }
);
const goodTool = tool(
async () => {
return new Date().toISOString();
},
{ name: "get_time", description: "Get current time" }
);
6. Streaming Modes Matter
for await (const chunk of await agent.stream(input, { streamMode: "values" })) {
console.log(chunk.messages);
}
for await (const chunk of await agent.stream(input, { streamMode: "updates" })) {
console.log(chunk);
}
for await (const chunk of await agent.stream(input, { streamMode: "messages" })) {
const [token, metadata] = chunk;
console.log(token.content);
}
Links to Documentation