跪拜 Guibai
← Back to the summary

An LLM Alone Is a Stateless Chatbot. Five Plugins Turn It Into an Agent.

Table of Contents

  1. Agent = LLM + Five Layers of Plugins
  2. LangChain: A Unified LLM Development Framework
  3. Hands-on: Writing an Agent Tool and Registering It with the Model
  4. Promise.all: The Secret to Parallel Multi-Tool Execution
  5. 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

aa0d76fb6a792a3a09ab047ac0c28381.png

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.log inside 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.all to execute them concurrently, rather than using for...of with serial await. This is the first principle of Agent performance optimization.


V. Summary

  1. 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).
  2. LangChain unifies the model layer: ChatOpenAI is compatible with various LLMs, tool() + zod defines tools, model.bindTools() completes registration.
  3. Two parts of a tool: Handler function (executes the task) + Description object (name / description / schema). The description determines whether the LLM can select it.
  4. Message type system: HumanMessage / SystemMessage / AIMessage / ToolMessage each have their own role, constructing the complete call context.
  5. 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.