前言:现在大模型发展真迅猛啊

2022年末,ChatGPT惊艳亮相,带火了生成式文本对话人工智能,如果你还有印象,应该还记得,刚出世的ChatGPT只是一个可以正常流畅对话的AI,知识库和记忆来自训练数据,甚至无法获取当前的时间,不知道现在的美国总统是谁。(你现在关闭联网搜索去问AI这些问题,也很难得到正确答案)。

两年前,大语言模型只能通过文本输入框展开一些流畅但有点胡闹荒唐的对话,而如今,大语言模型已经作为一种通用性AI,利用Agent技术,可以帮我们生成代码,生成PPT,联网搜索和整合信息,支撑起各种AI项目落地开花,今天我们来聊一聊这中间经历了哪些技术更新?Agent,RAG,MCP,这些技术又是什么?做一个AI应用为什么需要这些技术支持。

简单理解LLM

深度学习是机器学习的一个分支,自然语言处理是深度学习的一个方向。通用大语言模型,原本是在自然语言处理任务上表现特别良好的一类模型,主要架构为Transformer,基础特征是参数规模巨大。

LLM我们可以理解成一个巨大的复杂的线性函数和简单非线性函数的集合体(当然没有这么简单,里面还有一些随机性东西),模型参数其实就是内部这些线性函数和非线性函数的参数,比如线性函数 y=wx+b 中的 w,b

参数规模巨大其实可以反映出的模型内部函数的数量巨大,当我们看到一个模型参数量为32B,代表模型内有320亿个参数,也可以推断出这个模型内部大概有百亿个函数聚合在一起。

线性函数和简单非线性函数都属无状态的函数,由这些无状态的函数聚集在一起形成的LLM,自然也可以看作一个无状态的函数,输入一段文本给LLM这个函数,LLM就会给你计算出一段文本,完成文本生成任务。

一个根据输入文本计算输出文本的函数,可以看作是LLM的本质。

RAG:AI私人智能助手

我们已经知道LLM本质就是一个无状态的函数,那为什么在和LLM对话的过程中,LLM能记住我们的历史对话呢?答案很简单,把历史对话一起作为输入传进去,LLM就可以根据我们历史对话进行回答了。

基于这样的智能对话的场景,OpenAI推出了和LLM对话的API规范,将对话消息分为了三个角色类型:systemassistantuser

  • system 代表系统设定,可以理解为LLM本身的prompt,你可以通过这个将LLM设定为一个代码助手或者可爱的猫娘之类的。
  • user 代表用户的提问文本。
  • assistant 则代表LLM的生成文本。

现在我们可以这个API规范实现一个基于LLM的智能对话助手,进行流畅的一问一答。

但随之出现这两个问题:

长对话幻觉问题:众所周知,LLM是有输入长度限制的,超过输入长度的文本无法处理,并且如果我们和LLM之间的对话太长,LLM有时也无法定位到我们需要的上下文信息,往往容易出现幻觉。

外部知识源幻觉问题:在提问到一些外部知识源的问题的时候,LLM仅依赖自己训练数据的知识无法处理,也容易出现幻觉。

针对这些问题,我们想到:

  • 如果对话太长,我们可以主动检索出来很久以前的对话消息,再和我们的问题一起输入给LLM,这样就可以得到更准确更有效的回答。同时有效缩短我们对话上下文的长度,每次只将我们需要的消息传给LLM,而不是原封不动地把所有历史对话一起输入给LLM。
  • 如果涉及外部的知识源,我们能可以将问题相关的数据检索出来,再和问题一起交给LLM,也能有效提高回答的准确性。

这种解决方案就是RAG技术,RAG(Retrieval-Augmented Generation,检索增强生成),现代的RAG可以分成两个组件:检索组件和生成组件。

  • 检索组件可以用关键词搜索引擎(ES),或者文本嵌入模型配合向量数据库,主要作用是将相关的上下文信息检索出来。

  • 增强生成组件则是将检索得到的上下文信息拼接融合用户提问,输入给LLM,得到最后的更准确有效的文本输出。

文本嵌入模型的作用是将文本分割,并编码成向量。向量数据库则用于存储这些编码后的向量。当我们需要检索的时候,则将我们的问题文本编码为向量,再在向量数据库中寻找这个向量的邻近向量,并返回其对应的数据。

RAG技术使得LLM拥有构建私有知识库的能力,让我们的AI智能对话助手拥有检索网页,解析文件等功能。

Agent:将手伸出对话框

需求场景

现在LLM已经能够连接互联网数据和我们对话,也可以根据我们提供的私有知识库变为我们更贴心的助手和伙伴。我们在私有知识库中记录下每周末晚上我们有看电影的习惯。周日晚上,我们再和LLM智能助手交互,LLM热情地回答:“今晚您的安排是观看电影。”,你要求他把电视打开,而LLM却做不到。

LLM没有办法与电视通信,他的手困在了对话框内,让LLM具备调用外部工具的能力成了一个顺其自然的需求。提出一个很简单的需求场景:如何让LLM准确回答“现在几点了?”。这个需求很简单,但对于无状态的LLM来说却几乎不可能。

方案:Function Calling

在2023年6月13日,OpenAI推出了Function calling(现在也叫tools)的方案。

  • 向LLM提供一个函数名列表。
  • 编写精确的prompt描述每个函数的作用。
  • 配置描述函数输入参数的 json schema。

当我们将这些工具信息注册给LLM后,再对LLM提问,LLM会考虑决策是否需要使用到工具,如果是,则返回一个工具调用信息。

调用工具的消息的角色为 tool,附带工具请求ID,函数名,和函数输入参数的 json 字符串。

我们接收到这个 tool 消息之后,需要自己去执行对应的函数并将结果返回给 LLM,这样就让LLM完成了一次调用外部工具的调用。OpenAI自己的Agent框架Swarm就是这么实现的,我自己也复刻了一个:DAWN0ER/swarm4j

LLM拥有调用外部工具的能力时,就成为了我们现在所熟知的Agent,Agent可以根据我们要求的任务智能决策,调用不同的外部函数(工具),获取当前时间,请求外部设备播放视频,完成一系列复杂的功能。

MCP:工具管家

想象一下:当我们需要自己开发一个Agent应用的时候,我们也开始编写自己的定制化工具函数,但写着写着,一个疑惑就自然涌上心头:获取时间这个工具,我为什么要自己重复写一个,能不能有人写好了工具,我只需要注册进自己的Agent就行,这个时候就需要使用MCP了。

Anthropic公司于2024年11月推出了模型上下文协议(Model Context Protocol,简称MCP)。模型上下文其实是一个泛指,我们可以简单理解为LLM能接触到的所有数据源。本地文件,私有知识库,外部关系型数据库,网络服务器,都可以称为模型的上下文。

LLM如何从这些数据源中获取数据呢?在上一节的Agent里面,我们会自己实现一个用于数据交互的外部工具注册给LLM。而在MCP协议中,这些工具将不再直接注册给Agent,而是交给MCP代理。

在官网里是这么说的:

Why MCP?

MCP helps you build agents and complex workflows on top of LLMs. LLMs frequently need to integrate with data and tools, and MCP provides:

  • A growing list of pre-built integrations that your LLM can directly plug into
  • The flexibility to switch between LLM providers and vendors
  • Best practices for securing your data within your infrastructure

MCP 服务端:工具的提供方和执行方

MCP协议定义上下文数据源为MCP服务方,这些数据源可以将自己的工具注册到自己的 MCP 服务端。每一个服务端对应一个客户端,客户端和服务端之间依照MCP的规范通信。如果使用过RPC中间件的话,可以类似成RPC的客户端和服务端,实际上,MCP传输层确实是遵守RPC格式来通信的:

Message Format

MCP uses JSON-RPC 2.0 as its wire format. The transport layer is responsible for converting MCP protocol messages into JSON-RPC format for transmission and converting received JSON-RPC messages back into MCP protocol messages.

MCP 客户端:工具的注册方和请求方

LLM 方为 Host 宿主服务,使用 MCP 的客户端。当我们将一个聊天请求发给 LLM 的时候,每个 MCP 客户端会向 MCP 服务端发送 list_tools() 请求,然后对应的服务端则将返回所有可用的工具的信息,客户端再将这些工具注册给 Agent。

如果Agent 决策需要使用对应的工具,发起工具调用请求,则再由 MCP 客户端代理处理,将请求打包发给对应的服务端,完成工具调用。

MCP定义的工具格式依旧是 OpenAI API 规范的工具格式,但是加上一个注解字段:

{
  name: string;          // Unique identifier for the tool
  description?: string;  // Human-readable description
  inputSchema: {         // JSON Schema for the tool's parameters
    type: "object",
    properties: { ... }  // Tool-specific parameters
  },
  annotations?: {        // Optional hints about tool behavior
    title?: string;      // Human-readable title for the tool
    readOnlyHint?: boolean;    // If true, the tool does not modify its environment
    destructiveHint?: boolean; // If true, the tool may perform destructive updates
    idempotentHint?: boolean;  // If true, repeated calls with same args have no additional effect
    openWorldHint?: boolean;   // If true, tool interacts with external entities
  }
}

不过在这个定义里面,name更像是工具的唯一标识ID,title才是我们原来习惯的工具名。

MCP 的本质是代理 Agent 和外部工具调用之间的通信,也就是代理了模型和上下文之间的通信。

说说优点:

  • 实现了工具的复用:只要MCP服务端实现了工具,不同的模型服务只需要接入了对应的MCP客户端即可使用这些工具。阿里百炼大模型平台就推出了自己的MCP云平台,提供了一系列MCP服务。
  • 模型运行和数据源服务之间隔离:在MCP客户端代理的过程中,可用工具列表由客户端实时向 MCP 服务端请求得到,且工具的实际执行在MCP服务端,服务端之间相互隔离。这使得MCP协议支持动态隔离维护工具的注册和更新,实现了热插拔和热更新。

MCP确实是减轻了不少开发者的负担,将工具的维护和发现代理,提高整个Agent的应用的可扩展性和灵活性。并且由于MCP客户端和服务端之间的通信规范十分统一和清晰,很多客户端实际上采用了配置化的方案,将不同的MCP服务写在一个 json 文件中统一注册配置对应的客户端。

但这个时候还是需要说一下缺点:

  • MCP 传输层实际是 RPC 通信协议,是要走序列化和网路IO的,如果是单体服务,或者数据源就在本机上,使用MCP意义并不大,不如直接本地实现工具后注册到自己的智能体上,调用更快捷。
  • 如果是本地定制化的工具,工具服务依然得自己实现,比如从自己的数据库中拿数据之类,并且如果本地化定制开发依然选择MCP,要么另起一个MCP服务,要么本机开放MCP服务端口,并不算很好的方案。
  • 由于工具都在服务端维护,如果接入的是第三方平台的话,并不能完全依赖稳定性,延长了工具调用链路之后也增加了运营维护的排查成本。

其实这篇博客是我做一个视频的台本,所以配图稀少。

虽然技术的风潮一浪又一浪,但技术思想的核心其实很少有变化,对于出现了什么技术,我更愿意聊一点为什么会出现这些技术,为什么这种解决方案被推崇。技术永远来源于实际问题,从实际问题和环境去考虑这些技术,或许会带来不一样的视角。

希望这篇博客对大家有帮助,MCP并不是什么银弹,不要随便用,如果只写一两个定制工具,还要本地单体服务开放MCP端口给本地MCP客户端,带来的风险更大。