深入浅出大模型开发:从多轮推理到 Agent 智能体与工具调用(Tool Calling)实战

深入浅出大模型开发:从多轮推理到 Agent 智能体与工具调用(Tool Calling)实战

随着人工智能技术的演进,大语言模型(LLM)的应用模式正发生着根本性的转变------从最初单纯的"问答对话箱",演变为能够自主规划、调用工具并执行复杂任务的 Agent(智能体)

本文将从基础的概念辨析出发,结合主流的开发工作流,通过完整的代码示例,深度剖析如何使用 Node.js、dotenv 以及 OpenAI SDK,构建支持多轮对话、深度推理以及工具调用(Tool Calling)的 AI 应用。


一、 核心概念解析

在进入代码实战前,必须清晰理解以下四个支撑现代 AI 应用的核心概念:

1. LLM(大语言模型)

大语言模型是 AI 应用的"大脑"。它具备强大的文本理解、逻辑推理与内容生成能力。然而,LLM 的核心职责仅限于推理生成,它本身是一个闭环系统,无法感知实时互联网,也无法直接操作外部世界。

2. Tool(工具)

由于 LLM 无法直接对接外部世界,工具(Tool) 补齐了这一短板。工具可以是网络爬虫、数据库查询接口或本地的计算函数。没有工具的 AI 只有"空推理",而一旦接入工具,LLM 就能通过工具调用打破时空限制,完成自动化、闭环的任务。

3. Reasoning(推理)

指大模型在生成最终答案前的规划和思维过程。引入推理机制不仅能显著提升复杂问题的准确率,还能输出思维链(Chain of Thought),方便开发者与用户了解 AI 的解题思路并进行介入。

  • 多轮对话列表 (messages):承载上下文的记忆载体。
  • 推理强度 (reasoning_effort):控制模型思考深度的参数。
  • 推理内容 (reasoning_content) :模型内部的思考逻辑,与流式输出的最终内容(content)分离。

4. Agent(智能体)

Agent 是当前 AI 落地、企业降本增效的核心形态。它不仅能够回答问题,还能根据目标自主读取文件、检索网络、编写代码并操作浏览器或操作系统。

Agent 的能力上限公式

Agent 能力=底座大模型(大脑)+赋能工具(Tool)+输入上下文(信息)\text{Agent 能力} = \text{底座大模型(大脑)} + \text{赋能工具(Tool)} + \text{输入上下文(信息)}Agent 能力=底座大模型(大脑)+赋能工具(Tool)+输入上下文(信息)


二、 运行环境与项目初始化

在开发基于 LLM 的应用时,规范的环境变量管理与依赖注入是项目落地的第一步。

1. 依赖安装

首先,在本地创建一个空白目录,完成 npm 初始化并使用 pnpm 安装 OpenAI 官方 SDK 与环境变量管理库 dotenv

bash 复制代码
npm init -y
pnpm i openai dotenv

2. 环境变量配置 (.env)

为了避免将敏感的 API 密钥硬编码在代码中,在项目根目录下创建 .env 文件,配置大模型供应商的密钥、基础路径及目标模型:

复制代码
DEEPSEEK_API_KEY=sk-your-actual-api-key
DEEPSEEK_API_BASE_URL=https://api.deepseek.com
DEEPSEEK_MODEL=deepseek-v4-flash

三、 基础模块封装

项目结构采用 ES Modules (.mjs) 实现模块化解耦,确保逻辑清晰。

1. 客户端实例化 (client.mjs)

该模块负责读取环境变量并初始化大模型客户端,作为单例供其他业务模块复用。

javascript 复制代码
import { OpenAI } from 'openai';
import dotenv from 'dotenv';

// 加载 .env 文件中的环境变量到 process.env 中
dotenv.config();

// 实例化 LLM 客户端对象
const client = new OpenAI({
    apiKey: process.env.DEEPSEEK_API_KEY,
    baseURL: process.env.DEEPSEEK_API_BASE_URL,
});

export default client; // 默认导出客户端实例

2. 文本生成服务封装 (completion.mjs)

对通用的文本生成接口进行抽象封装,降低上层业务的调用成本。

javascript 复制代码
import client from './client.mjs';

/**
 * 封装基础的文本生成请求
 * @param {string} prompt - 用户的输入提示词
 * @returns {Promise<string>} 大模型生成的文本内容
 */
export async function getCompletion(prompt) {
  const response = await client.chat.completions.create({
    model: process.env.DEEPSEEK_MODEL, // 动态读取环境变量中的模型名称
    messages: [
      { role: 'user', content: prompt } // 构造单轮对话结构
    ]
  });
  // 解析并返回标准响应路径下的文本内容
  return response.choices[0].message.content;
}

/**
 * 图像生成留空接口,便于后续多模态功能扩展
 */
export async function genImage(prompt) {
  // 待实现
}

四、 深度推理与多轮对话细节剖析 (main.mjs)

在传统 Web 开发中,服务器是无状态的;在大模型开发中同样如此,API 无法自动记住历史对话。main.mjs 展示了如何通过状态显式传递高阶模型参数来实现高智商的连续对话。

javascript 复制代码
import client from './client.mjs'

const main = async () => {
    const result = await client.chat.completions.create({
        model: 'deepseek-v4-pro',   // 使用支持深度推理的高阶模型
        reasoning_effort: 'high',  // 开启深度推理模式,设置为高强度思考
        messages: [  
            { 
                role: 'system', 
                content: '你是一个足球领域的专家,请尽量帮我回答与足球相关的问题'
            },
            {
                role: 'user',
                content: 'c罗是哪个国家的足球运动员?'
            },
            {
                role: 'assistant',
                content: 'c罗是葡萄牙的足球运动员'
            },
            {
                role: 'user',
                content: '内马尔呢?'
            }
        ]
    });
    console.log('思考过程:');
    console.log(result.choices[0].message.reasoning_content);
    console.log('\n最终答案:');
    console.log(result.choices[0].message.content);
}
main();

核心代码细节深度拆解

1. 数组结构与历史上下文的滚动机制

大模型的连续对话本质上是通过维护一个持久化的消息队列(messages 数组)实现的。每次发起新提问时,都必须将历史上下文完整发送给模型。

  • 角色(Role)的分类与职责
    • system:系统级注入,拥有最高话语权。它在底层会引导模型的注意力机制(Attention),让模型在后续生成中时刻保持"足球专家"的设定。
    • user:用户输入的原始文本。
    • assistant:大模型曾经 给出的回答。在多轮对话中,开发者必须把大模型之前返回的文本包装成 assistant 角色重新发送。
  • 上下文消除指代 :当模型看到最后一个 user 的提问 "内马尔呢?" 时,其底层的 Attention 矩阵会逆向扫描整个 messages 数组。它发现前文在讨论"C罗是哪个国家的足球运动员",从而在数学概率上精确推断出此时的"呢"代表"是哪个国家的足球运动员",完美消除了自然语言中的指代模糊。

2. 推理能力控制参数 reasoning_effort

  • 思维深度控制 :与传统的快思考模型(直接输出答案)不同,像 deepseek-v4-pro 这类推理模型在接收到 prompt 后,内部会先启动强化学习训练出的推理链(Reasoning Chain)。
  • reasoning_effort: 'high':该参数显式要求模型分配更多的计算 Token(思维 Token)用于内部逻辑推演、自我纠错和路径规划。
  • 数据结构的分离 :模型返回的响应体中,reasoning_content 是模型"在幕后拔河"的草稿纸(例如:"用户问内马尔,前文问的是国籍,内马尔是巴西的,我需要回答巴西...")。而 content 才是精炼后展现给用户的最终结果。这两者在底层是完全结构化分离的。

五、 工具调用(Tool Calling)闭环演练 (index.mjs)

当用户询问实时数据(如特定股票价格)时,大模型由于训练数据的滞后性无法直接回答。此时,需要向模型开放工具声明。

需要特别强调的是:大模型本身并不会帮你执行 JavaScript 函数 。它的本质是扮演一个**"决策官",决定要不要用工具,以及生成调用工具所需的结构化参数包**。

1. 基础框架实现

以下代码演示了如何向模型声明工具,并获取模型的首次决策响应:

javascript 复制代码
import client from "./client.mjs";

// 1. 声明可用的工具列表 (告诉 LLM 哪些任务可以外包)
const tools = [
    {
        type: "function", // 规定格式:工具类型为函数

        function: {
            name: "get_closing_price",
            // 核心要点:description 必须极其详细具体,LLM 正是基于此处的自然语言描述来判断何时使用该工具
            description: "获取指定股票的收盘价",
            parameters: {
                type: "object",
                properties: {
                  name: {
                    type: "string",
                    description: "股票名称" // 用于自然语言处理(NLP)的参数抽取依据
                  }  
                },
                required: ["name"] // 声明必填参数
            }
        }
    }
];

// 2. 本地具体的业务函数实现(模拟外部 API)
function get_closing_price(name) {
    if (name === '青岛啤酒') return "67.92";
    if (name === '贵州茅台') return "1488.21";
    return "未找到股票";
}

// 3. 封装带有工具声明的发送函数
const send_message = async (messages) => {
    return await client.chat.completions.create({
        model: 'deepseek-v4-flash',
        messages,
        tools,             // 将工具配置数组传递给模型
        tool_choice: 'auto' // 'auto' 允许模型自主决定是否调用工具
    })
}

const main = async () => {
    let messages = [
        { role: 'user', content: "青岛啤酒的收盘价是多少?"}
    ];

    const response = await send_message(messages);
    const message = response.choices[0].message;
    console.log(message);
}
main();

2. 工具调用协议的底层机制

自然语言路由与实体抽取

当执行上述代码,用户询问"青岛啤酒的收盘价是多少?"时,大模型在处理时会经历以下逻辑推演:

  1. 匹配与推理 :模型扫描发送过去的 tools 列表,发现 get_closing_price 的描述("获取指定股票的收盘价")与用户意图高度匹配。
  2. 实体抽取 :模型根据 properties.name.description 知道它需要抽取一个"股票名称"。于是它从输入语句中精准切词,提取出 "青岛啤酒",并将其组装。

首次请求后的响应体(Message Object)细节

index.mjs 中,运行 main() 后,控制台打印的 message 并不是字符串,而是一个结构化对象:

javascript 复制代码
{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_abc123", 
      "type": "function",
      "function": {
        "name": "get_closing_price",
        "arguments": "{\"name\":\"青岛啤酒\"}"
      }
    }
  ]
}
  • content: null:此时内容为空,大模型没有直接回答价格,因为它需要依赖外部数据。
  • tool_calls 数组 :这就是大模型下达的**"调机指令"**。它包含了唯一标识符 id(用于后续结果对齐)、要求执行的本地函数名 name,以及提炼好的参数 arguments(注意:它是一个 JSON 字符串,本地必须用 JSON.parse() 解析)。

3. 实现自动化任务的闭环逻辑

为了让程序完整运行并输出"人话"给用户,开发者必须在本地代码中解析模型的决策指令,执行本地函数,并将结果反馈给模型进行第二次调度。

以下是完整的闭环代码实现:

javascript 复制代码
const main = async () => {
    let messages = [
        { role: 'user', content: "青岛啤酒的收盘价是多少?"}
    ];

    // 第一次请求:告诉模型问题,并给出工具箱
    const response = await send_message(messages);
    const message = response.choices[0].message;

    // 检查大模型是否触发了工具调用决策
    if (message.tool_calls) {
        // 核心步骤 1:必须把模型的这个决策结果(包含 tool_calls 的 message)塞进历史消息队列
        messages.push(message);

        // 核心步骤 2:遍历模型要求调用的每一个工具指令
        for (const toolCall of message.tool_calls) {
            if (toolCall.function.name === "get_closing_price") {
                // 解析模型生成的 JSON 字符串参数
                const args = JSON.parse(toolCall.function.arguments);
                
                // 执行本地真正的 JavaScript 业务函数,获取真实数据
                const localResult = get_closing_price(args.name); // 返回 "67.92"

                // 核心步骤 3:将本地函数执行的结果,以 role: 'tool' 的身份塞进消息流
                // 注意:必须携带 tool_call_id,让模型知道这个结果是对应哪一次下发的指令
                messages.push({
                    role: "tool",
                    tool_call_id: toolCall.id,
                    name: toolCall.function.name,
                    content: localResult // 传递真实世界的数据结果
                });
            }
        }

        // 核心步骤 4:第二次请求。此时的 messages 包含了 [原问题, 模型工具指令, 工具执行结果]
        const finalResponse = await client.chat.completions.create({
            model: 'deepseek-v4-flash',
            messages: messages // 将更新后的上下文完整发送
        });

        // 打印大模型结合真实数据后整合输出的最终答案
        console.log(finalResponse.choices[0].message.content); 
        // 终端将输出类似:"青岛啤酒当前的收盘价是 67.92 元。"

六、 总结

通过上述两个实战案例,我们可以看出现代大模型开发的核心范式:

  1. main.mjs 强调的是记忆(Context)与深度思考(Reasoning):利用数组的不断追加维护长短期记忆,配合推理模型突破复杂逻辑的瓶颈。
  2. index.mjs 展示的是 Agent 智能体的最小化基石:利用标准的 Tool Calling 协议,让大模型扮演决策中枢(分发指令),而让本地代码提供执行力。两者有机结合,才形成了当下能够真正落地并降本增效的智能体解决方案。