Live2d Test Env

【论术】一文搞懂agent

本文尝试以一个node文件使读者理解agent的运行流程,不追求工程化,先搞懂运行逻辑

某种意义来说,Agent 并不是教会大模型做什么,而是要求大模型根据我们的意图来做什么。

在一个 Agent 流程中,大模型作为“大脑”存在。我们的目的是通过给定约束、给定资源、提供工具,给大模型装上“手脚”。大模型负责判断是否应该调用我们给定的工具,从而识别用户意图,并按照我们的期望逐步完成任务。

Agent 是一个以大模型为决策核心的运行系统。
它通过约束、上下文和工具,让大模型在可控范围内理解任务、选择动作、观察结果,并逐步完成目标。

一个完整的 Agent 包含四个步骤:

  1. 感知
  2. 决策
  3. 行动
  4. 反馈

前置工作

为了实现上述四个步骤,并达成“理解 Agent 流程”的目的,需要先进行一些准备工作。

1. 配置大模型 Key

大模型 Key 是程序与大模型对话的凭证。

推荐使用阿里百炼:

https://bailian.console.aliyun.com/cn-beijing?spm=5176.2020520153.resourceCenter.1.c887336aaut1bJ&tab=model#/api-key

注册后去“模型广场”创建 Key,即可使用。

2. 编程语言选择:Node

语言只是实现目的的工具。

本文使用 Node 来实现最小 Agent 流程。

3. 提示词准备

提示词可以简单理解为系统提示词和用户提示词。

消息结构可以理解为:

Array<{ role: 'system' | 'user' | 'assistant'; content: string }>

system

system 是系统规则,用来告诉模型应该怎么工作:

  • 你是谁
  • 你能做什么
  • 你必须怎么做
  • 你不能怎么做

它的范围通常是当前对话,在任务开始前预定义。

system 通常分为四个方面:

  1. 定义身份
  2. 定义工作方式
  3. 定义输出格式
  4. 注入可用资源
systemPrompt = 给大模型的岗位说明书 + 工作流程 + 约束规则 + 工具清单

它的作用是:

把通用大模型临时约束成当前这个 Agent 的“大脑”,让它在指定角色、规则、工具和输出格式下进行决策。

user

user 是用户输入,代表人类每次提出的问题或任务。

它通常包含三个方面:

  1. 表达用户意图
  2. 提供本次任务上下文,也就是想干什么
  3. 驱动 Agent 进入决策循环

可以这样理解:

userPrompt 是触发 Agent 运行的具体目标。

也可以更完整地说:

userPrompt 是用户在当前轮次提出的具体任务输入,用来告诉 Agent 本次要解决什么问题,并驱动大模型基于系统规则进行决策和行动。

两者的区别是:

system 决定角色和规则
user 决定当前任务

为什么要花这么多笔墨描述提示词?

因为提示词是与大模型交互,并促使大模型做出正确、合理意图的核心变量。

4. tools 准备:定义工具

这一步是为了给大模型装上“手脚”。

tools 是程序中真实可执行的函数;toolDescriptions 是写给大模型看的工具说明。

大模型只能根据 toolDescriptions 判断要不要调用工具,真正执行的是程序里的 tools

5. 输出协议准备

这一步是为了约定大模型和程序之间的通信格式。

如果要调用工具,输出:

{"action":"工具名","args":{}}

如果任务完成,输出:

{"final":"最终回答"}

这样可以让大模型的“意图”变成程序可以稳定解析的结构。

6. 主循环

程序根据大模型输出,决定是调用工具,还是输出最终结果。

主循环会不断执行:

感知 -> 决策 -> 行动 -> 反馈

直到输出最终结果。

前置工作负责把 Agent 装配起来;
四步骤负责让 Agent 运转起来。
messages 是 Agent 的上下文容器。
systemPrompt、userPrompt、模型上一次输出、工具执行结果,都会被放进 messages。
每一轮大模型决策,都是基于当前 messages 进行的。

一:感知

简单点说,感知层就是:

对大模型的数据输入,以及将数据整理成大模型可以理解的格式。

在程序中,感知层主要体现为 messages 的构造和更新,包括:

  • systemPrompt
  • userPrompt
  • 后续工具反馈

二:决策

简单点说,决策层就是:

让大模型根据上下文和条件,自己判断下一步应该做什么。

决策层本身不执行动作,只负责产生下一步意图。

比如:

{"action":"get_current_time","args":{}}

或者:

{"final":"当前北京时间是 xxx"}

三:行动

简单点说,行动层就是:

程序根据模型的意图,执行某些动作。

注意:是程序执行,不是模型执行。

模型只是给出决策意图,具体动作由程序执行。

这里描述的是“程序执行模型意图”这个动作,并不包含执行结果。

程序负责把大模型的意图变成真实执行。

四:反馈

简单点说,反馈层就是:

程序将上一步的执行结果反馈给模型,由模型基于结果决定下一步做什么。

反馈层本质上会变成下一轮的感知输入。

上面四个步骤并不是单向的,而是循环往复的。

这样就构成了一个 Agent 的真实流程。

阶段总结

我们可以尝试举一个现实生活中的例子来强化理解:

作为一个推拿馆的医生,
有一天接到了一个患者,他告诉医生腰不舒服。
医生让患者躺下,手指触摸患者腰部,并询问是不是这里不舒服。
患者回答是或者不是。
循环往复,直到触摸到某处,患者确认是这里不适。
随后医生根据患者描述对患处进行治疗,或推拿,或针灸。
患者状态好转并致谢,随后离开。

这个例子可以很好地映射 Agent 的整体流程:

1. 医生上岗前
   = Agent 装配阶段

   医生知道自己是推拿馆医生;
   医生知道自己会问诊、触诊、推拿、针灸;
   医生知道哪些情况能处理,哪些情况不能乱治。

   对应 Agent:
   systemPrompt:规定身份、职责、边界、输出方式
   tools:可执行的动作能力
   toolDescriptions:告诉大模型有哪些工具可以用
   约束规则:限制大模型必须在可控范围内决策

2. 患者进店说“腰不舒服”
   = 第一轮感知

   医生接收到患者的初始描述。

   对应 Agent:
   userPrompt = “我腰不舒服”
   程序把用户输入整理进 messages,交给大模型。

3. 医生判断信息还不够,不能直接治疗
   = 第一轮决策

   医生根据患者描述判断:
   现在还不知道具体痛点,需要进一步检查。

   对应 Agent:
   大模型根据 messages 判断:
   当前不能直接 final,需要先调用检查类工具。

4. 医生让患者躺下,并触摸某个腰部位置
   = 第一轮行动

   医生执行真实动作:触诊、按压、询问。

   对应 Agent:
   大模型输出 action;
   程序根据 action 调用工具,例如:
   touch_waist_area / ask_patient。

5. 患者回答“不是这里”或“是这里”
   = 第一轮反馈

   医生获得患者对本次检查动作的反应。

   对应 Agent:
   工具返回 observation;
   程序把 observation 放回 messages。

6. 医生根据患者反馈继续换位置检查
   = 第二轮感知 + 第二轮决策

   上一轮患者反馈,变成医生下一轮判断的新依据。

   对应 Agent:
   observation 重新进入上下文,
   成为下一轮大模型输入的一部分。
   大模型基于新的 messages 再次判断下一步。

7. 患者确认“是这里不舒服”
   = 关键信息被定位

   医生通过多轮检查确认了问题位置。

   对应 Agent:
   大模型获得足够上下文,
   判断不需要继续检查,可以进入处理阶段。

8. 医生选择推拿或针灸
   = 新一轮决策

   医生根据痛点、症状、患者反馈,选择治疗方式。

   对应 Agent:
   大模型根据当前上下文选择下一步 action,
   例如 massage / acupuncture。

9. 医生执行治疗
   = 新一轮行动

   医生真正进行推拿或针灸。

   对应 Agent:
   程序执行对应工具,
   得到治疗后的结果。

10. 患者状态好转并致谢离开
    = 最终反馈 + 任务完成

    患者反馈状态好转,医生确认本次处理完成。

    对应 Agent:
    工具结果或用户反馈进入上下文;
    大模型判断任务已完成;
    输出 final;
    Agent 循环结束。

从始至终,大模型负责输出意图或最终结果;真正的动作执行,由程序完成。

以下是实现的最小化可以实现的agent文件,以获取当前天气为例
主文件:

// agent.js
const { existsSync, readFileSync } = require('node:fs');

function print(title, content = '') {
    console.log(`\n=== ${title} ===`);
    if (content) {
        console.log(content);
    }
}

// 0. 装配:读取 .env。这里不用 dotenv,只用 Node 原生 fs。
function loadEnv(filePath = '.env') {
    if (!existsSync(filePath)) {
        return;
    }

    const lines = readFileSync(filePath, 'utf8').split(/\r?\n/);

    for (const line of lines) {
        const text = line.trim();
        if (!text || text.startsWith('#')) {
            continue;
        }

        const index = text.indexOf('=');
        if (index === -1) {
            continue;
        }

        const key = text.slice(0, index).trim();
        const value = text.slice(index + 1).trim().replace(/^['"]|['"]$/g, '');

        if (key && process.env[key] === undefined) {
            process.env[key] = value;
        }
    }
}

loadEnv();

const apiKey = process.env.BAI_LIAN_KEY;
const model = process.env.BAI_LIAN_MODEL || 'deepseek-v4-pro';
const endpoint = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions';

// 1. 装配:定义工具。工具是真正由程序执行的动作。
const tools = {
    get_current_time() {
        return new Date().toLocaleString('zh-CN', {
            timeZone: 'Asia/Shanghai',
            hour12: false
        });
    }
};

// 2. 装配:用自然语言告诉模型有哪些工具。
const toolDescriptions = `
可用工具:
- get_current_time:获取当前北京时间,无参数。
`;

// 3. 装配:定义系统提示词和输出协议。
const systemPrompt = `
你是一个最小化 Agent。

你必须遵守下面的输出协议:
1. 如果需要调用工具,只输出 JSON:{"action":"工具名","args":{}}
2. 如果可以给出最终答案,只输出 JSON:{"final":"最终答案"}
3. 不要输出 JSON 以外的内容。

${toolDescriptions}
`;

// 感知:读取用户任务,并把它放进 messages。
function getUserTask() {
    return process.argv.slice(2).join(' ') || '现在北京时间是多少?请调用工具后回答。';
}

// 决策:调用大模型,让它根据 messages 判断下一步。
async function askModel(messages) {
    const response = await fetch(endpoint, {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${apiKey}`,
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            model,
            messages,
            temperature: 0.2
        })
    });

    if (!response.ok) {
        const detail = await response.text();
        throw new Error(`模型请求失败:${response.status} ${detail}`);
    }

    const data = await response.json();
    return data.choices?.[0]?.message?.content || '';
}

// 决策:把模型输出的 JSON 字符串解析成程序可读对象。
function parseDecision(text) {
    try {
        return JSON.parse(text);
    } catch {
        const matched = text.match(/\{[\s\S]*\}/);
        if (!matched) {
            throw new Error(`模型没有按协议返回 JSON:${text}`);
        }
        return JSON.parse(matched[0]);
    }
}

// 行动:根据模型的 action,执行对应工具。
function runTool(action, args = {}) {
    const tool = tools[action];
    if (!tool) {
        throw new Error(`未知工具:${action}`);
    }

    return tool(args);
}

async function main() {
    print('装配阶段', '读取配置、定义工具、准备系统提示词。');

    if (!apiKey) {
        throw new Error('缺少 BAI_LIAN_KEY,请先在 .env 中配置。');
    }

    const userTask = getUserTask();

    print('感知', `用户任务:${userTask}`);

    const messages = [
        { role: 'system', content: systemPrompt },
        { role: 'user', content: userTask }
    ];

    for (let round = 1; round <= 4; round += 1) {
        print('决策', `第 ${round} 轮,把 messages 发给大模型。`);
        const modelOutput = await askModel(messages);
        console.log(modelOutput);

        const decision = parseDecision(modelOutput);

        if (decision.final) {
            print('最终结果', decision.final);
            return;
        }

        if (!decision.action) {
            throw new Error(`模型输出中没有 action 或 final:${modelOutput}`);
        }

        print('行动', `执行工具:${decision.action}`);
        const result = runTool(decision.action, decision.args);
        console.log(result);

        print('反馈', '把工具执行结果放回 messages,进入下一轮。');
        messages.push({ role: 'assistant', content: modelOutput });
        messages.push({
            role: 'user',
            content: `工具 ${decision.action} 的执行结果是:${result}。请基于这个结果继续。`
        });
    }

    throw new Error('达到最大循环次数,仍然没有得到最终结果。');
}

main().catch((error) => {
    console.error('\n[Error]', error.message);
    process.exit(1);
});

.env文件(key在百炼平台获取)

BAI_LIAN_KEY=YOUR_KEY
BAI_LIAN_MODEL=deepseek-v4-pro

以上。

posted @ 2026-06-07 17:57  致爱丽丝  阅读(24)  评论(0)    收藏  举报