An LLM Alone Is a Stateless Chatbot. Five Plugins Turn It Into an Agent.
Table of Contents
- Agent = LLM + Five Layers of Plugins
- LangChain: A Unified LLM Development Framework
- Hands-on: Writing an Agent Tool and Registering It with the Model
- Promise.all: The Secret to Parallel Multi-Tool Execution
- Summary
I. Agent = LLM + Five Layers of Plugins
A large language model itself is just a probability engine that "predicts the next token"—it has no memory, cannot open a web page, cannot read internal documents, doesn't know today's news, and cannot manipulate files. A chatbot developed directly with an LLM API is essentially a stateless conversational interface.
What an Agent does is equip the LLM with five layers of plugins:
| Problem | Plugin | Function |
|---|---|---|
| LLM doesn't remember last week's conversation | Memory Module | Database/frontend storage/Redis manages memory |
| LLM cannot operate web pages and files | Tool Use Module | Allows the LLM to call external functions and APIs |
| LLM doesn't understand internal company documents | RAG Module | Retrieves internal knowledge bases, injects into Prompt |
| LLM doesn't know the latest news | MCP / Third-party Tool | Connects to external tool protocols to get real-time data |
| LLM cannot automate complex tasks | Skills | Encapsulates multi-step processes into reusable capabilities |
Agent = LLM + Memory + Tool + RAG + MCP + Skills
Claude Code and Codex are Coding Agents; products like Manus are automation task Agents. Their common architecture is: User submits a complex task → LLM plans/reasons → Decides which plugins to call → Executes → Returns the result.
II. LangChain: A Unified LLM Development Framework
LangChain is an LLM development framework that was born even earlier than the OpenAI SDK. Its core advantage is unifying and being compatible with various large models. Regardless of whether the underlying model is DeepSeek, GPT, or another model, the upper-layer code hardly needs to change.
import { ChatOpenAI } from '@langchain/openai';
import 'dotenv/config';
const model = new ChatOpenAI({
modelName: 'deepseek-v4-flash',
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: 'https://api.deepseek.com/v1',
},
});
const response = await model.invoke('What rewards should be set for the Stick King Cup billiards competition?');
console.log(response.content);
LangChain's tech stack is typically paired with:
| Layer | Technology | Responsibility |
|---|---|---|
| Backend | NestJS | Business logic, routing, authentication |
| Single Agent | LangChain | Orchestration of LLM + Tool + Memory |
| Multi-Agent | LangGraph | Collaboration and state flow between multiple Agents |
III. Hands-on: Writing an Agent Tool and Registering It with the Model
In the Agent system, a tool is essentially a function + description + schema constraint. LangChain defines tools using the tool() method from @langchain/core/tools, and uses the zod library for parameter validation.
Defining a Read File Tool
import { tool } from '@langchain/core/tools';
import fs from 'node:fs/promises';
import { z } from 'zod';
const readFileTool = tool(
async ({ filePath }) => {
const content = await fs.readFile(filePath, 'utf-8');
console.log(`[Tool Call] read_file(${filePath}) successfully read ${content.length} bytes`);
return content;
},
{
name: 'read_file',
description: `Use this tool to read file content. Call this tool when the user requests to read a file, view code,
or analyze file content. Input the file path (relative or absolute paths are both acceptable).`,
schema: z.object({
filePath: z.string().describe('The file path to read')
})
}
);
A tool consists of two parts:
| Part | Content | Explanation |
|---|---|---|
| Handler Function | async ({ filePath }) => { ... } |
Asynchronously executes the real task and returns the result |
| Description Object | name + description + schema |
The basis for the LLM to decide when to call and how to pass parameters |
The quality of the description directly determines whether the LLM can select this tool at the right moment—a vague description leads to missed or incorrect calls.
Registering the Tool and Sending a Task
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
const model = new ChatOpenAI({
modelName: 'deepseek-v4-flash',
apiKey: process.env.DEEPSEEK_API_KEY,
temperature: 0,
configuration: { baseURL: 'https://api.deepseek.com/v1' },
});
const tools = [readFileTool];
const modelWithTools = model.bindTools(tools); // Key: Binding tools to the model
const messages = [
new SystemMessage(`
You are a code assistant that can use tools to read files and explain code.
Workflow:
1. When the user requests to read a file, immediately call the read_file tool.
2. Wait for the tool to return the file content.
3. Analyze and explain based on the file content.
`),
new HumanMessage('Please read the code file content and explain it'),
];
let response = await modelWithTools.invoke(messages);
messages.push(response); // Push the LLM's tool_calls response into history
Tool Call Flow
HumanMessage → LLM Reasoning → Recognizes need to call read_file → Generates tool_calls →
→ Runtime executes readFileTool → Result injected into message history → Call LLM again → Final reply
LangChain's message type system clearly distinguishes the roles of different messages:
| Message Type | Role | Purpose |
|---|---|---|
HumanMessage |
user | The user's original question |
SystemMessage |
system | Sets the Agent's behavior rules and tool usage guidelines |
AIMessage |
assistant | The LLM's reply (may contain tool_calls) |
ToolMessage |
tool | The result returned after tool execution |
Agent tasks can be complex and time-consuming—if the user doesn't see feedback for too long, they might leave. Using
console.loginside the tool function to print execution progress is a good habit.
IV. Promise.all: The Secret to Parallel Multi-Tool Execution
In real-world scenarios, an LLM might need to call multiple independent tools simultaneously—checking the weather and fetching tweets don't need to wait for each other. Serial execution makes the total time equal to the sum of each tool's time, while parallel execution is the key to a high-performance Agent.
function getWeather() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ temp: 38, conditions: 'Sunny with Clouds' });
}, 2000);
});
}
function getTweets() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(['I like cake', 'BBQ is good too!']);
}, 500);
});
}
Serial approach — Total time ≈ 2000 + 500 = 2500ms:
const weatherData = await getWeather(); // Wait 2000ms
const tweetsData = await getTweets(); // Wait another 500ms
Parallel approach — Total time ≈ max(2000, 500) = 2000ms:
const [weatherData, tweetsData] = await Promise.all([
getWeather(),
getTweets()
]);
Review of the Three Promise States
Promise is an asynchronous syntax provided by ES6, with three mutually exclusive states:
Pending ──→ Fulfilled (resolve called)
│
└──→ Rejected (reject called)
Once it changes from Pending to Fulfilled or Rejected, the state is locked—it cannot be changed again.
Promise.all([promise array]) — Executes all promises in the array in parallel, waits for all to resolve, and returns an array of results. The order of results matches the order of the promise array. If any one rejects, the entire Promise.all rejects immediately.
In a LangChain Agent, when the LLM's tool_calls return multiple unrelated tool calls, you should use
Promise.allto execute them concurrently, rather than usingfor...ofwith serialawait. This is the first principle of Agent performance optimization.
V. Summary
- Agent = LLM + Plugins: Memory remembers things, Tool does things, RAG searches documents, MCP connects to external tools, Skills encapsulate capabilities. Claude Code is the product of LLM + Tool(fs + cli).
- LangChain unifies the model layer:
ChatOpenAIis compatible with various LLMs,tool()+zoddefines tools,model.bindTools()completes registration. - Two parts of a tool: Handler function (executes the task) + Description object (name / description / schema). The
descriptiondetermines whether the LLM can select it. - Message type system:
HumanMessage/SystemMessage/AIMessage/ToolMessageeach have their own role, constructing the complete call context. - Promise.all parallelism: When multiple tools are independent of each other, execute them concurrently. The total time equals the slowest one, not the sum of all. This is the underlying optimization for Agent performance.
The core of understanding an Agent in one sentence: It is not a smarter LLM, but an LLM armed with Memory, Tool, and RAG.
—— A good Agent is an LLM that can remember, act, and search.