从 if-else 到智能体:一文读懂 Agent 的概念、架构与实践

0 阅读13分钟

为什么要聊智能体?

周一早晨,程序员小王收到了老板发来的消息:

老板:"小王啊,公司要开发一个智能客服系统,能自动回答用户问题,还能查订单、处理退款。下周能搞定吗?"

小王盯着屏幕,脑子里第一反应是:

传统方案:写一堆 if-else?
问题是:
1. 用户问法千奇百怪,关键词根本列举不完
2. 新问题天天出现,规则会越堆越乱
3. 一旦超出预设流程,系统就只能转人工

也正是在这个时候,他想起最近总被提到的一个词:Agent(智能体)

这篇文章不会一上来就丢一堆抽象概念,而是从一个更实际的问题出发:

  • 智能体到底是什么?
  • 它和传统软件到底有什么本质区别?
  • 一个能工作的智能体,最少需要哪些组件?
  • 什么时候值得用 Agent,什么时候其实不该硬上?
  • 最后,怎么动手写出自己的第一个智能体?

如果你也刚开始接触 Agent,这篇就是给你的第一站。


第一章:什么是智能体?

1.1 一个够用的定义

先给一个适合入门的版本:

Agent = 感知 + 决策 + 行动
flowchart LR
    A["感知\nPerception"] --> B["决策\nDecision"]
    B --> C["行动\nAction"]
    C --> D["环境反馈"]
    D -.-> A

拆开来看:

  • 感知(Perception):理解输入,知道发生了什么
  • 决策(Decision):判断接下来该怎么做
  • 行动(Action):把决定真正执行出来

用一个生活化的例子来理解:

场景:你是一名餐厅服务员

顾客说:"给我来一份宫保鸡丁"

感知:理解顾客在点菜
决策:判断菜品是否可下单、是否需要推荐搭配
行动:把菜下给厨房,并告知预计等待时间

这其实就是一个最小版本的智能体。

1.2 现代 LLM Agent 是什么?

如果放到今天的大模型语境里,Agent 可以理解成:

以 LLM 为决策核心,能够结合记忆、工具和任务流程,自主完成目标的系统。

这里有两个细节很重要:

  1. 智能体不等于聊天机器人如果它只能回复文本、不能调用工具、也不能根据目标调整策略,那它更像一个对话接口,而不是完整 Agent。
  2. 智能体也不等于“什么都能自动完成” Agent 的关键不是“无所不能”,而是它会根据上下文决定下一步怎么做,而不是完全依赖写死的分支逻辑。

所以更准确一点说:

现代 Agent = LLM + Memory + Tools + Workflow

前面的“感知 + 决策 + 行动”是概念骨架,这里的 “LLM + Memory + Tools + Workflow” 是工程落地时更常见的实现形态。


第二章:智能体和传统程序,到底差在哪?

很多人第一次接触 Agent 时都会问:

"不就是在 if-else 外面套了个大模型吗?"

这个问题很关键。因为只有搞清楚它和传统程序的区别,后面学工具、记忆、规划才不会变成“学一堆术语”。

2.1 控制流的本质区别

传统程序的核心是:

输入 -> 匹配规则 -> 命中分支 -> 输出结果

智能体的核心更像:

输入 -> 理解意图 -> 生成策略 -> 调用工具/执行动作 -> 根据结果继续调整

两者最大的差异,不是“用了没用大模型”,而是:

  • 传统程序的控制流主要由开发者预先写死
  • 智能体的控制流更多由模型在运行时动态决定
flowchart TB
    subgraph T["传统程序"]
        T1["用户输入"] --> T2["规则匹配"]
        T2 --> T3["if-else 分支"]
        T3 --> T4["输出结果"]
    end

    subgraph A["Agent"]
        A1["用户输入"] --> A2["LLM 理解意图"]
        A2 --> A3["动态决策"]
        A3 --> A4["调用工具 / 执行动作"]
        A4 --> A5["观察结果"]
        A5 --> A3
        A3 --> A6["最终回答"]
    end

2.2 看一段代码就明白了

传统客服机器人

function getResponse(userInput: string): string {
  const input = userInput.toLowerCase();

  if (input.includes("价格")) {
    return "我们的产品价格为 99 元";
  } else if (input.includes("退款")) {
    return "请提供订单号,我们将为您处理退款";
  } else if (input.includes("配送")) {
    return "配送时间为 3-5 个工作日";
  }

  return "抱歉,我无法理解您的问题,请转人工客服。";
}

LLM 驱动的 Agent

async function agentResponse(userInput: string, memory: Memory): Promise<string> {
  const messages = [
    { role: "system", content: "你是专业的客服助手" },
    ...memory.getRecent(10),
    { role: "user", content: userInput }
  ];

  const decision = await llm.think(messages);

  if (needsToolCall(decision)) {
    const toolResult = await executeTool(decision.tool, decision.input);
    messages.push({ role: "assistant", content: decision });
    messages.push({ role: "user", content: `工具执行结果:${toolResult}` });
    return await llm.think(messages);
  }

  return decision;
}

从这两段代码里,你会看到一个非常本质的变化:

  • 在传统程序里,是你告诉系统该怎么分支
  • 在智能体里,是系统自己判断下一步该做什么

2.3 一句话总结这场区别

传统程序擅长处理“规则稳定、流程固定”的问题; 智能体更擅长处理“表达开放、路径不固定、需要动态决策”的问题。

这句话你现在先记住,后面判断“什么时候该用 Agent”时会非常有用。


第三章:一个完整智能体,通常有哪些组件?

当小王继续往下研究,他发现一个真正可用的智能体,通常不只是一个 LLM 调用,而是由几块核心能力共同组成。

Agent 架构图转存失败,建议直接上传图片文件

           Agent
             |
    ---------------------
    |    |      |      |
   LLM Memory  Tools Planner
flowchart TD
    U["用户目标"] --> G["Agent"]
    G --> L["LLM\n负责理解与决策"]
    G --> M["Memory\n负责上下文与偏好"]
    G --> T["Tools\n负责调用外部能力"]
    G --> P["Planner\n负责拆解复杂任务"]
    T --> E["外部世界 / API / 数据库"]

3.1 LLM:智能体的大脑

LLM 是整个 Agent 的决策中心,主要负责:

  • 理解用户意图
  • 做出下一步判断
  • 生成回答、计划或动作指令
const messages = [
  { role: "system", content: "你是一个智能助手" },
  { role: "user", content: "帮我查询北京明天的天气" }
];

const response = await openai.chat.completions.create({
  model: "gpt-4",
  messages
});

这里要注意一点:

LLM 很重要,但 LLM 本身不等于完整 Agent。

它像大脑,但大脑要想真的解决问题,还需要记忆、手脚和任务拆解能力。

3.2 Memory:让它不再像“七秒鱼”

如果智能体记不住你刚刚说过什么,每次都像重新认识你,那体验会非常糟糕。

记忆一般分成两类:

类型作用类比常见实现
短期记忆保存当前对话上下文正在进行中的聊天记录消息列表
长期记忆跨会话保存用户偏好、事实和知识你对某个人长期的了解数据库 / 向量库
interface Message {
  role: "user" | "assistant" | "system";
  content: string;
  timestamp: Date;
}

class Memory {
  private history: Message[] = [];

  add(message: Message): void {
    this.history.push(message);
  }

  getRecent(n: number): Message[] {
    return this.history.slice(-n);
  }
}

3.3 Tools:让智能体真的“能做事”

纯 LLM 最大的限制是:它主要处理文本,本身不能直接访问外部世界。

这时就需要工具:

  • 搜索工具:查实时信息
  • 数据库工具:查订单、查库存
  • 计算工具:执行公式和数值运算
  • API 工具:调用业务系统
  • 通知工具:发消息、发邮件、发工单
interface Tool {
  name: string;
  description: string;
  execute: (input: string) => Promise<string>;
}

class SearchTool implements Tool {
  name = "search";
  description = "网页搜索引擎,用于查询实时信息";

  async execute(query: string): Promise<string> {
    const results = await searchAPI(query);
    return results.map((r, i) => `[${i + 1}] ${r.title}\n${r.snippet}`).join("\n\n");
  }
}

如果说 LLM 是大脑,那工具就像手脚。 没有工具,智能体往往只能“说得像会做事”,但不一定真的能完成任务。

3.4 Planner:处理复杂任务时的任务拆解器

不是所有 Agent 都需要 Planner。 简单问答型 Agent 可能完全不需要它。

但一旦任务变复杂,比如:

  • 规划一次旅行
  • 调研一个行业
  • 生成多步骤业务流程
  • 调用多个系统协作完成目标

就需要先拆解任务,再逐步执行。

interface PlanStep {
  number: number;
  description: string;
  dependencies: number[];
  status: "pending" | "completed" | "failed";
  result?: string;
}

const plan: PlanStep[] = [
  {
    number: 1,
    description: "搜索目的地景点推荐",
    dependencies: [],
    status: "pending"
  },
  {
    number: 2,
    description: "查询机票价格",
    dependencies: [],
    status: "pending"
  },
  {
    number: 3,
    description: "搜索酒店信息",
    dependencies: [],
    status: "pending"
  },
  {
    number: 4,
    description: "生成完整旅行计划",
    dependencies: [1, 2, 3],
    status: "pending"
  }
];

到这里,我们可以把完整的 Agent 理解成这样:

LLM 负责决策
Memory 负责记住上下文
Tools 负责和外部世界交互
Planner 负责拆复杂任务

第四章:什么时候应该用 Agent,什么时候不要硬上?

这是很多入门文章都会跳过的一点,但我觉得它恰恰最重要。

因为“能做”不等于“值得做”。

4.1 适合用 Agent 的场景

如果你的问题有下面这些特征,Agent 往往会比较合适:

  • 用户表达开放 同一个需求可能有很多种说法,关键词规则不好写。
  • 任务路径不固定 系统要根据上下文动态决定下一步。
  • 需要工具调用 比如搜索、查数据库、发消息、执行 API。
  • 需要多轮上下文 系统必须记住前面的对话和状态。
  • 问题规模变化大 有些是简单问答,有些是复杂任务拆解。

典型例子:

  • 智能客服
  • 办公助手
  • 调研助手
  • 编程助手
  • 工作流自动化助手

4.2 不适合强行上 Agent 的场景

如果你的需求是下面这种,传统软件可能反而更好:

  • 流程非常固定 比如表单校验、固定审批流、状态机控制。
  • 结果必须完全确定 比如财务结算、权限判断、计费规则。
  • 性能和成本极敏感 高频、简单、重复任务没必要每次都调 LLM。
  • 问题本身没有开放性 明明一个 SQL 或 if-else 就能稳定解决,就不需要上 Agent。

你可以把它理解成一个简单判断:

规则稳定 -> 优先传统程序
决策开放 -> 更适合 Agent
flowchart LR
    S["问题类型判断"] --> Q1{"规则是否稳定?"}
    Q1 -- "是" --> R1["优先传统程序"]
    Q1 -- "否" --> Q2{"是否需要动态决策、工具、上下文?"}
    Q2 -- "是" --> R2["更适合 Agent"]
    Q2 -- "否" --> R3["先用简单自动化或脚本"]

4.3 最容易踩的坑

很多团队一开始做 Agent,最常见的问题不是“模型不够强”,而是:

  • 把本来适合规则引擎的问题强行做成 Agent
  • 以为只接一个 LLM 就算 Agent
  • 没有工具,导致智能体只能“空谈”
  • 没有记忆,导致每轮都像第一次见面
  • 没有边界控制,导致结果时好时坏

所以更成熟的做法通常是:

用传统程序兜住确定性部分, 用 Agent 处理那些开放、灵活、需要动态决策的部分。


第五章:智能体是怎么一步步发展到今天的?

智能体并不是突然冒出来的,它的演进其实很清晰。

5.1 规则驱动时代

在 LLM 出现之前,很多所谓“智能系统”本质上是:

  • 专家系统
  • 基于流程图的机器人
  • 推荐系统
  • 搜索系统

它们能解决问题,但核心仍然是规则、特征和预定义流程。

5.2 LLM 出现之后,局面变了

2022 年以后,大模型让系统第一次具备了更强的:

  • 语义理解能力
  • 泛化能力
  • 零样本和少样本能力
  • 多任务处理能力

这带来了一个重要变化:

系统不再只能执行我们写死的流程, 而是可以在运行时“推理出下一步该干什么”。

5.3 为什么后来又会出现 ReAct、Tool、Memory、MCP?

因为大家很快发现:只有 LLM 还不够。

  • 只会回答,不会行动 所以要接 Tools
  • 只看当前输入,记不住上下文 所以要接 Memory
  • 复杂任务容易乱 所以要接 Planner
  • 工具生态太碎 所以开始需要更统一的协议,比如 Function CallingMCP

也就是说,今天你看到的 Agent 框架,并不是一开始就长这样,而是一步一步“被问题逼出来”的。


第六章:动手实践,写一个最小可用的 SimpleAgent

理论看到这里,已经足够开始写代码了。

这一节我们不追求一步到位做复杂框架,而是先写一个最小版本的 SimpleAgent,把核心骨架搭起来。

flowchart LR
    I["用户输入"] --> S["system prompt"]
    S --> H["history"]
    H --> L["HelloAgentsLLM"]
    L --> O["模型输出"]
    O --> R["写入 history"]
    R --> F["返回结果"]

6.1 项目结构

agent/
├── src/
│   ├── core/
│   │   ├── llm.ts
│   │   ├── message.ts
│   │   └── agent.ts
│   └── agents/
│       └── simple.ts
├── package.json
├── tsconfig.json
└── .env

6.2 环境变量配置

在项目根目录创建 .env 文件:

LLM_API_KEY="your_api_key_here"
LLM_MODEL_ID="gpt-3.5-turbo"
LLM_BASE_URL="https://api.openai.com/v1/"
LLM_TIMEOUT=120
变量名必填说明
LLM_API_KEYLLM 服务的 API 密钥
LLM_MODEL_ID模型名称
LLM_BASE_URLAPI 地址
LLM_TIMEOUT请求超时时间,默认 60 秒

常见配置示例:

智谱 AI(GLM)

LLM_API_KEY="your_zhipu_api_key"
LLM_MODEL_ID="glm-4-flash"
LLM_BASE_URL="https://open.bigmodel.cn/api/paas/v4/"

OpenAI

LLM_API_KEY="sk-xxxxxxxxxxxxxxxx"
LLM_MODEL_ID="gpt-3.5-turbo"
LLM_BASE_URL="https://api.openai.com/v1/"

DeepSeek

LLM_API_KEY="your_deepseek_api_key"
LLM_MODEL_ID="deepseek-chat"
LLM_BASE_URL="https://api.deepseek.com/v1/"

本地模型(Ollama)

LLM_API_KEY="ollama"
LLM_MODEL_ID="llama3"
LLM_BASE_URL="http://localhost:11434/v1/"

代码中加载环境变量:

import "dotenv/config";

const apiKey = process.env.LLM_API_KEY;
const model = process.env.LLM_MODEL_ID;
const baseUrl = process.env.LLM_BASE_URL;

⚠️ 注意:.env 不要提交到 Git 仓库。

6.3 核心类型:Message

export type MessageRole = "user" | "assistant" | "system" | "tool";

export interface Message {
  role: MessageRole;
  content: string;
  timestamp?: Date;
  metadata?: Record<string, any>;
}

export function createMessage(role: MessageRole, content: string): Message {
  return {
    role,
    content,
    timestamp: new Date()
  };
}

Message 是整个 Agent 系统的基础,因为后面无论是对话历史、记忆系统,还是工具调用,都会围绕它展开。

6.4 LLM 客户端封装

import OpenAI from "openai";
import type { ChatCompletionMessageParam } from "openai/resources/chat";

export class HelloAgentsLLM {
  private model: string;
  private client: OpenAI;

  constructor(options?: {
    model?: string;
    apiKey?: string;
    baseUrl?: string;
  }) {
    this.model = options?.model || process.env.LLM_MODEL_ID || "gpt-3.5-turbo";

    this.client = new OpenAI({
      apiKey: options?.apiKey || process.env.LLM_API_KEY,
      baseURL: options?.baseUrl || process.env.LLM_BASE_URL
    });
  }

  async think(
    messages: ChatCompletionMessageParam[],
    temperature: number = 0.7
  ): Promise<string> {
    const response = await this.client.chat.completions.create({
      model: this.model,
      messages,
      temperature
    });

    return response.choices[0]?.message?.content || "";
  }
}

这一层的意义是:把具体模型服务的差异统一封装起来,后续 Agent 不需要关心是 OpenAI、智谱还是本地模型。

6.5 Agent 基类

import { Message, createMessage, toChatMessages } from "./message";
import { HelloAgentsLLM } from "./llm";

export interface AgentConfig {
  name: string;
  systemPrompt?: string;
  maxHistoryLength?: number;
}

export abstract class Agent {
  protected llm: HelloAgentsLLM;
  protected config: AgentConfig;
  protected history: Message[] = [];

  constructor(llm: HelloAgentsLLM, config: AgentConfig) {
    this.llm = llm;
    this.config = {
      maxHistoryLength: 100,
      ...config
    };
  }

  abstract run(input: string): Promise<string>;

  protected addToHistory(message: Message): void {
    this.history.push(message);
    if (this.history.length > this.config.maxHistoryLength!) {
      this.history = this.history.slice(-this.config.maxHistoryLength!);
    }
  }

  getHistory(): Message[] {
    return [...this.history];
  }

  resetHistory(): void {
    this.history = [];
  }
}

基类的意义在于:先把骨架统一下来。 后面的 ReActAgent、ReflectionAgent、Plan-and-SolveAgent,本质上都会继续长在这个骨架上。

6.6 实现第一个 SimpleAgent

import { Agent, AgentConfig } from "../core/agent";
import { HelloAgentsLLM } from "../core/llm";
import { createMessage, toChatMessages } from "../core/message";

export class SimpleAgent extends Agent {
  constructor(llm: HelloAgentsLLM, config: AgentConfig) {
    super(llm, config);
  }

  async run(input: string): Promise<string> {
    const messages = toChatMessages([
      createMessage("system", this.config.systemPrompt || "你是一个有帮助的AI助手。"),
      ...this.history,
      createMessage("user", input)
    ]);

    const response = await this.llm.think(messages);

    this.addToHistory(createMessage("user", input));
    this.addToHistory(createMessage("assistant", response));

    return response;
  }
}

这个版本还很简单,但已经具备了一个最小 Agent 的核心特征:

  • 有统一输入输出
  • 有历史上下文
  • 有系统提示
  • 能通过 LLM 生成响应

6.7 运行示例

import "dotenv/config";
import { HelloAgentsLLM } from "./core/llm";
import { SimpleAgent } from "./agents/simple";

async function main() {
  const llm = new HelloAgentsLLM();

  const agent = new SimpleAgent(llm, {
    name: "AI助手",
    systemPrompt: "你是一个友好的AI助手,请用简洁清晰的方式回答问题。"
  });

  const questions = [
    "你好,请介绍一下自己",
    "我刚才问了你什么?",
    "请解释什么是智能体"
  ];

  for (const question of questions) {
    console.log(`用户:${question}`);
    const response = await agent.run(question);
    console.log(`助手:${response}\n`);
  }
}

main().catch(console.error);

6.8 这个 SimpleAgent 有什么局限?

也正因为它简单,所以它还做不到很多更高级的事情:

  • 不会主动调用工具
  • 不会拆解复杂任务
  • 不会反思和纠错
  • 长期记忆能力还没有建立

但这正是它的价值所在:

我们不是一开始就把 Agent 做复杂, 而是先搭一个最小骨架,再一层层加能力。


第七章:这篇文章最重要的,不只是“定义”

如果你读到这里,我更希望你带走的不是一句“智能体是感知 + 决策 + 行动”,而是下面这几个更关键的判断:

7.1 智能体的核心,不是“会聊天”

而是:

  • 能理解目标
  • 能动态决策
  • 能结合上下文
  • 能调用外部能力

7.2 智能体也不是银弹

它不是用来替代所有传统软件的。真正好的系统,通常是:

  • 确定性部分交给传统程序
  • 开放性部分交给 Agent

7.3 Agent 学习路径,应该是渐进式的

建议按这个顺序学习:

  1. 先理解 Agent 的概念和边界
  2. 再实现一个最小 SimpleAgent
  3. 然后给它加工具
  4. 再给它加记忆
  5. 再进入 ReAct、Reflection、Plan-and-Solve 等范式

这也是为什么这篇文章是整个系列的第一篇。


总结

今天我们主要解决了 5 个问题:

  1. 什么是智能体? 它是一个能够感知、决策并采取行动的系统。
  2. 它和传统软件有什么区别? 传统程序依赖预设规则,智能体更强调运行时动态决策。
  3. 一个完整 Agent 通常有哪些组成? LLM、Memory、Tools、Planner 是最常见的四块能力。
  4. 什么时候应该用 Agent? 当任务开放、路径不固定、需要上下文和工具调用时,Agent 更有优势。
  5. 怎么开始动手? 最好的方式不是直接上复杂框架,而是先实现一个最小可用的 SimpleAgent

如果用一句话结束这篇文章,那就是:

Agent 不是一个“更会聊天的机器人”,而是一种把 LLM、记忆、工具和任务执行组织起来的新软件形态。


下篇预告

下一篇我们会进入一个真正让 Agent “动起来”的经典范式:ReActAgent

也就是让智能体学会下面这个循环:

Thought -> Action -> Observation -> 再思考
flowchart LR
    T["Thought"] --> A["Action"]
    A --> O["Observation"]
    O --> T

到那时,Agent 就不再只是“回答问题”,而是开始真正具备“边想边做”的能力。

我们会重点讲:

  • 为什么需要 ReAct?
  • Thought-Action-Observation 是怎么工作的?
  • 如何用 TypeScript 实现一个可运行的 ReActAgent?
  • 工具调用是怎么接进来的?

参考资料