通常,当我们开始尝试使用人工智能技术时,很多人都会采用类似的方法。我们会尝试将某个大型语言模型作为应用程序的核心部分来使用,比如这样:

const response = await llm.chat("Explain Kubernetes");

在最初的一段时间里,整个流程看起来很简单:用户提出问题,模型就会给出答案。这种早期的成功往往会给人一种错觉,让人以为开发人工智能就是简单地发送请求并获取响应而已。

这种简单的思路确实很有吸引力,但实际上并不成立。随着时间的推移,用户会希望助手能够从他们的文档和知识库中查找信息、调用API、获取实时数据,或者触发某些服务或安排会议。

用户还期望这些智能助手能够访问内部系统,并与ERP系统、CRM系统或其他存储重要业务数据的工具进行交互。他们希望助手能够将多个步骤组合起来执行,因为实际的工作流程往往需要将查询、计算以及各种后续操作串联起来,才能形成可靠的流程。

这时,像MCP(模型上下文协议)这样的概念,以及LangChain这样的工具就派上了用场。起初,这些可能只是些时髦的术语,但实际上它们解决了与大型语言模型开发相关的不同问题。

在尝试使用各种人工智能工具后,我发现这些概念确实有助于解决与接口设计、工作流程协调以及系统集成相关的问题。

这篇文章旨在帮助大家了解大型语言模型是如何与各类工具结合使用的,如何协调工作流程,以及它们是如何为真正的AI应用提供支持的。

本文将涵盖以下内容:

  1. 什么是大型语言模型?

  2. 为什么大型语言模型需要工具辅助?

  3. MCP在什么地方发挥作用?

  4. LangChain到底能做什么?

  5. 如何将这些工具整合起来使用?

  6. 在学习过程中我实际开发了什么?

在整篇文章中,我们将探讨大型语言模型到底是什么、它们是如何工作的,实际使用这些工具时具体会遇到哪些情况,MCP的作用及其工作原理,LangChain在整个流程中的位置,以及如何将这些工具有效地组合起来。

为了能够顺利阅读这篇文章,你需要具备对Node.js、API操作以及基本JavaScript概念的基本了解。

什么是大型语言模型?

LLM就是大型语言模型。这类深度神经网络是通过训练海量文本数据而开发出来的,它们的目标就是模拟并生成类似人类的语言。你可能听说过的一些知名例子包括GPT、Claude、Gemini和Llama。

如何从Node.js应用程序中调用大型语言模型

在开始编写代码之前,我们先来了解一下从Node.js应用程序中调用大型语言模型到底意味着什么。调用大型语言模型,其实就是将你的应用程序发送的输入数据传递给人工智能服务提供商提供的API,然后接收对方返回的结果。这一过程与其他外部服务的调用方式并无本质区别。在大多数实际应用中,模型并不是由你的应用程序来托管或训练的。相反,像OpenAI和Groq这样的提供商会负责托管和维护这些模型,而你的应用程序则是通过HTTP API与它们进行交互。

在这个例子中,我们将使用Node.js和Express构建一个简单的API。我们会创建一个`POST /chat`端点,该端点接收用户输入的消息,将其发送到OpenAI API,获取生成的回复结果,然后再将其返回给客户端。在这里,我们的Node.js服务器就充当了用户与大语言模型提供商之间的桥梁。

对于这个例子,你需要从Groq控制台生成一个API密钥。由于Groq提供了免费试用版本,因此这种方式非常适合用来进行实验并理解相关概念。

首先,安装所需的依赖项:
“`bash
npm install express
“`

然后编写代码:
“`javascript
import express from “express”;

const app = express();
app.use(express.json());

app.post(“/chat”, async (req, res) => {
const { message } = req.body;
const response = await fetch(“https://api.groq.com/openai/v1/chat/completions”, {
method: “POST”,
headers: {
“Content-Type”: “application/json”,
Authorization: GROQ_API_KEY,
},
body: JSON.stringify({
model: “llama-3.3-70b-versatile”,
messages: [{ role: “user”, content: message }],
}),
});

const data = await response.json();

if (!response.ok) {
return res.status(response.status).json({ error: data });
}

const reply = data.choices[0].message.content;

res.json({ reply });
});

const PORT = process.env.PORT || 8888;
app.listen(PORT, () => {
console.log(`服务器运行在http://localhost:${PORT}`);
});
“`

启动服务器后,可以使用Postman发送一个`POST`请求到`/chat`端点,请求体内容如下:
“`json
{
“message”: “Explain Kubernetes”
}
“`

示例响应结果如下:
“`json
{
“reply”: “Kubernetes是一个容器编排平台…”
}
“`

后端接收到消息后,会将其转发给相应的模型提供商,获取生成的文本结果,然后再将其返回给客户端。

大语言模型在处理与语言相关的任务时表现得非常出色:它们能够理解语句的含义和用户的意图,生成连贯的文本,从非结构化数据中提取有用的信息,并根据提供的上下文进行基本的推理。这些能力使得它们在摘要生成、文档起草以及对话式问答等领域具有很强的实用性。

然而,也存在一个重要的限制:大语言模型并不自动了解你的私有数据或实时数据,也无法访问这些数据。除非你在运行时主动提供这些信息,否则它们无法访问你的公司数据库、内部API或系统的当前状态。

正因为有这个限制,所以你需要使用安全可靠的机制来将模型与实时系统和数据连接起来——而这正是工具发挥作用的地方。

为什么大语言模型需要工具

想象一下,如果你发出这样的请求:

请检查我的订单情况,如果配送延迟,请提供支持。

仅靠模型本身是无法查询你的订单数据库或在你的系统中创建支持工单的。为此,它必须调用外部函数——例如,getOrderStatus(orderId) API和createSupportTicket(orderId, issue)操作。

这些可被调用的函数就是我们所说的工具:人工智能可以利用这些程序化接口与系统进行交互,并代表用户执行具体的操作。

简单来说,工具就是人工智能模型可以用来与外部系统交互或执行操作的函数。

举个例子,假设我们有一个getOrderStatus(id)函数,它可以返回订单的配送状态。

为了让大语言模型能够使用这个功能,我们需要定义一个工具数组。每个工具都包含以下信息:

  • 类型——目前为“function”

  • 函数名称——函数的标识符

  • 函数描述——帮助大语言模型判断何时应该调用该工具

  • 函数参数——一个JSON格式的描述,说明该工具期望接收哪些输入参数

下面是一个例子:

function getOrderStatus(id) {
  const statuses = ["pending", "success", "cancelled"];
  const status = statuses[Math.floor(Math.random() * statuses.length)];
  return `您的订单状态是${status}.`;
}

const tools = [
  {
    type: "function",
    function: {
      name: "getOrderStatus",
      description: "根据订单ID获取其状态",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "订单ID" },
        },
        required: ["id"],
      },
    },
  },
];

上述工具的定义格式是专为Grok设计的。不同的大语言模型提供商可能会使用不同的格式来定义工具,但基本原理是一样的。

在进行API调用时,我们需要同时传递用户输入的信息以及可用的工具列表。

body: JSON.stringify({
    model: "llama-3.3-70b-versatile",
    messages: [{ role: "user", content: message }],
    tools,
}),

API调用完成后,大语言模型会判断是否需要使用某个工具。如果确实需要调用某个工具,我们的应用程序就会执行相应的函数,并将结果返回给模型。

在这个例子中,我们只处理getOrderStatus这个工具。我们可以通过以下方式判断模型是否请求了该工具的调用:

const toolCall = data.choices[0].message.tool_calls[0];
const { id } = JSON.parse/toolCall.functionarguments);
const toolResult = getOrderStatus(id)

之后,我们可以将结果连同相关的消息信息一起传递给模型。

body: JSON.stringify({
    model: "llama-3.3-70b-versatile",
    messages: [
        { role: "user", content: message },
        assistantMessage,
        { role: "tool", tool_call_id: toolCall.id, content: toolResult },
    ],
    tools,
}),

最后,返回响应:

return res.json({ reply: followUpData.choices[0].message.content });

以下是整个流程的示意图:

用户 -> 大语言模型 -> 工具执行 -> 工具结果 -> 最终响应” height=”887″ loading=”lazy” src=”https://cdn.hashnode.com/uploads/covers/6a1fa5fdc5c3ae375fb38ab2/22d6dc4d-ad5e-4fbb-84f6-71c367565282.png” style=”display:block;margin:0 auto” width=”1774″/></p>
<p>大语言模型会判断是否需要使用某些工具,并生成相应的输入数据,而你的应用程序则会执行相应的功能。</p>
<h2 id=MCP的作用

工具的使用非常简单:你只需定义相应的函数,然后告诉人工智能系统哪些工具可以使用即可。

例如,当所有的工具都内置在应用程序中时,getOrderStatus()这个函数就能正常工作。但随着应用程序规模的扩大,这些工具可能会来自各种不同的地方,比如Slack、GitHub、数据库、内部系统或第三方服务,而每种工具的接口可能都不尽相同。

这时,MCP(模型上下文协议)就能发挥作用了。可以把MCP看作是一种通用语言,它能让人工智能系统以统一的方式与这些外部工具进行交互。

工具决定了人工智能系统能够执行哪些操作,而MCP则规范了人工智能系统如何连接并使用这些工具。

现在让我们扩展之前的聊天API示例,让大语言模型能够使用通过MCP暴露出来的工具。实现这一目标的方法有很多:

  • 自行构建并托管MCP服务器,然后将自己应用程序中的功能作为MCP工具进行暴露

  • 连接到现有的第三方MCP服务器,比如Slack

在这个教程中,我们会选择比较简单的方法,即使用远程的MCP服务器。

npm install express @modelcontextprotocol/sdk zod

现在让我们创建自己的MCP服务器,并将getOrderStatus函数作为MCP工具进行暴露:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";

function getOrderStatus(id) {
  const statuses = ["pending", "success", "cancelled"];
  const status = statuses[Math.floor(Math.random() * statuses.length)];
  return `您的订单状态是${status}.`;
}

function createOrderServer() {
  const server = new McpServer({ name: "order-server", version: "1.0.0" });

  server.registerTool(
    "getOrderStatus",
    {
      description: "根据订单ID获取其状态",
      inputSchema: { id: z.string() },
    },
    async ({ id }) => ({
      content: [{ type: "text", text: getOrderStatus(id) }],
    })
  );

  return server;
}

const app = createMcpExpressApp({ host: "0.0.0.0" });

app.post("/mcp", async (req, res) =&to {
  const server = createOrderServer();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });

  res.on("close", () =&to {
    transport.close();
    server.close();
  });

  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

const PORT = process.env.PORT || 3001;
app.listen(PORT, "0.0.0.0", () =&to {
  console.log(`订单MCP服务器正在运行,地址为http://0.0.0.0:${PORT}/mcp`);
});

当你希望通过MCP来公开自己的应用程序功能时,这种方法非常有用。通常,MCP服务器是独立运行的,由其他MCP客户端访问。现在,任何MCP客户端都可以连接到这个服务器,并自动发现可用的工具。

这一原理同样适用于第三方的MCP服务器。

例如,如果有Slack的MCP服务器可用,我们就可以直接连接它,而无需自己编写集成代码。

在这种情况下,我们的应用程序并不会直接调用Slack的API,而是连接到Slack的MCP服务器,该服务器会按照MCP的标准来提供与Slack相关的工具。

因此,区别在于:

  • 对于我们自己的功能,我们可以构建自己的MCP服务器。

  • 而对于外部系统,如果有现成的MCP服务器可用,我们就可以直接使用它们。

现在,我们可以在向大语言模型发送请求时使用MCP服务器:

body: JSON.stringify({
  model: "llama-3.3-70b-versatile",
  messages: [{ role: "user", content: message }],
  tools: [
    {
      type: "mcp",
      server_label: "OrderServer",
      server_url: `http://0.0.0.0:${PORT}/mcp`,
      server_description: "通过订单ID获取订单状态",
    },
    {
      type: "mcp",
      server_label: "Slack",
      server_url: "https://mcp.slack.com/mcp",
      server_description: "发送和读取Slack消息",
      headers: {
        Authorization: `Bearer ${process.env.SLACK_BOT_TOKEN}`,
      },
    },
  ],
})

我们也可以使用本地的MCP服务器,而不是远程URL。例如,可以通过StdioClientTransport这样的传输机制进行本地连接,从而发现可用的工具,并将它们提供给大语言模型。

如果用户发送如下请求:

{
  "message": "订单123的状态是什么?"
}

大语言模型会判断是否需要使用某个工具,MCP会负责暴露并执行该工具,最终结果会返回给用户。

整个流程如下:

用户 -> 聊天API -> 大语言模型 -> MCP工具 -> 工具结果 -> 回答” height=”887″ loading=”lazy” src=”https://cdn.hashnode.com/uploads/covers/6a1fa5fdc5c3ae375fb38ab2/2db75d86-db9a-477e-b578-92221a490a2a.png” style=”display:block;margin:0 auto” width=”1774″/></p>
<p>这种标准化使得集成代码的可复用性大大提高:团队无需为每个新的连接组件重新编写逻辑代码,只需注册符合MCP标准的工具,让调度系统和模型来负责工具的发现与调用即可。</p>
<h2 id=那么LangChain到底能做什么呢?

起初我认为LangChain只是另一种封装了大语言模型API的工具,但事实上,它更应该被看作是一个用于协调AI工作流程的框架。各种工具可以让大语言模型执行特定的操作,而MCP则规范了这些工具的暴露方式。LangChain帮助整合模型、工具以及应用程序逻辑,从而构建出多步骤的工作流程。

举个例子:

用户:查找航班信息、比较价格、预订酒店、发送确认邮件。

现在,系统可能需要执行以下操作:

  • 检查订单状态

  • 判断是否需要寻求技术支持

  • 创建支持工单

  • 生成最终回复

如果没有相应的协调机制,就需要手动控制每一个步骤。而LangChain能够帮助管理这一整个流程。

要使用LangChain,请安装所需的包:

npm install express langchain @langchain/groq

我们将继续使用之前用过的那些工具函数:

import express from "express";
import { createAgent } from "langchain";
import { ChatGroq } from "@langchain/groq";

const app = express();
app.use(express.json());

const agent = createAgent({
  model: new ChatGroq({
    model: "llama-3.3-70b-versatile",
    apiKey: GROQ_API_KEY,
  }),
  tools: [
    {
      name: "getOrderStatus",
      description:
        "获取订单状态",
      execute: ({ id }) =>
        getOrderStatus(id), // 我们上面已经定义了这个函数
    },
    {
      name: "createSupportTicket",
      description:
        "创建支持工单",
      execute: ({ id }) =>
        createSupportTicket(id), // 假设存在这样一个用于创建支持工单的函数
    },
  ],
});

app.post(
  "/chat",
  async (req, res) => {
    const { message } = req.body;

    const response =
      await agent.invoke({
        messages: [
          {
            role: "user",
            content: message,
          },
        ],
      });

    res.json({
      reply:
        response.messages
          ?.at(-1)
          ?.text,
    });
  }
);

app.listen(3000);

现在,整个流程如下所示:

横向架构图,显示用户 → /chat API → LangChain代理 → OpenAI → 工具 → 工具结果 → 最终回复。

LangChain并不会取代现有的工具或中间件。它位于这些组件的上层,负责协调它们之间的协作关系。

整体架构与运作原理

现代的人工智能应用通常由多个层次组成。大语言模型负责进行推理和文本生成;各种工具则负责执行具体的操作,比如读取数据、调用API或执行某些动作;而中间件则用于规范这些工具的接口和使用方式。LangChain的作用就是协调模型、工具以及工作流程之间的交互。

通过明确划分这些职责,应用程序会变得更易于扩展、维护和优化。

我们的目标不仅仅是生成文本,而是要构建出能够进行推理、检索信息、采取行动,并能可靠地解决用户实际问题的系统。

用户 → 大语言模型 → LangChain → 中间件 → 工具 → 系统与数据

在学习过程中我开发了这些工具

在理解了上述概念之后,我想为自己的项目简化一些开发流程。通过实验我发现,大多数应用程序都会反复使用相同的架构:连接大型语言模型、配置相关工具、管理执行过程以及设计协调机制。

因此,我制作了一个小型开源工具包来简化这些步骤。我们的目标很简单:让用户能够专注于业务逻辑的开发,而无需花费精力去搭建人工智能基础设施。

目前该工具包具备以下功能:

  • 大型语言模型的集成

  • 工具的注册与管理

  • 工具的执行与调度

  • 聊天流程的协调与控制

  • 对LangChain的支持

  • 可扩展的架构设计

相关包:

AI聊天插件:https://www.npmjs.com/package/ai-chat-toolkit-widget

AI聊天服务器:https://www.npmjs.com/package/ai-chat-toolkit-server

GitHub仓库地址:https://github.com/sudheeshshetty/ai-chat-toolkit

若要使用该工具包搭建服务器,请执行以下命令:

npm install express ai-chat-toolkit-server

创建聊天服务器的代码如下:

const aiChat = new AiChatServer({
  path: "/my-chat",
  provider: "groq",
  apiKey: process.env.API_KEY,
  model: process.envMODEL || "llama-3.3-70b-versatile",
  cors: {
    origin: "http://localhost:5174",
  },
  orchestration: "langchain",
  maxToolRounds: 6,
  systemPrompt:
    "你是一个帮助性操作助手,用于演示场景。请简洁回答问题。",
});

添加所需的工具到聊天服务器中:

aiChat.addTools([
  {
    name: "...",
    description: "...",
    inputSchema: { ... },
    handler: async (input) => { /* 在Node环境中执行 */ },
  },
]);

将聊天服务器集成到你的Express应用中:

aiChat.attach(app);

现在,/my-chat路径已经在你的Express服务器中可用,可以直接使用它了。

如果你不想自己构建聊天用户界面,也可以直接使用ai-chat-toolkit-widget插件。

仓库中提供了许多示例代码,你可以快速尝试使用它们。

下面是其中一个示例的截图:

a9079710-be65-472b-881f-350daeeb0f3b

如果你觉得这个工具包有用,欢迎给我打星评价、留下反馈,或在GitHub上贡献代码。我会继续优化开发体验并探索新的功能。感谢阅读——希望这些内容能帮助你更好地理解和使用大型语言模型、相关工具以及LangChain技术。

Comments are closed.