截至2025年初,只有少数公司提供能够理解和生成文本、图像以及其他媒体(如音频和视频)的大型多模态模型。为了简洁起见,我们统称这些模型为AI模型。最著名的例子是OpenAI创建的GPT模型,其他一些知名模型包括谷歌的Gemini模型、Anthropic开发的Claude Sonnet和Haiku模型,以及Meta的Llama模型。
在很多情况下,这些公司会与其他企业合作,将模型作为云服务提供。例如,OpenAI与微软合作,由微软提供基础设施托管OpenAI的模型,用户可通过API访问。还有像Meta这样的公司,提供模型快照——包含预训练模型权重的大型二进制文件,用户可以将其安装在自有基础设施中。这些基础设施可以是“裸机”,即公司自有的物理服务器,也可以是从其他云服务提供商购买的云基础设施。
模型开发公司通常也会提供面向用户的应用程序。在很多情况下,模型名称和面向用户的应用名称相同或非常相似,容易混淆。例如,Google Gemini应用使用Google Gemini模型,Claude应用使用Anthropic的Claude Sonnet和Haiku模型。OpenAI的命名稍有不同,其面向用户的应用ChatGPT允许用户与GPT-4o和GPT-4o-mini模型交互。
这些应用程序的复杂程度各异。最简单的应用是一种聊天式的网页界面,允许用户直接向模型发送提示并返回响应。而如今,大多数大型公司的应用都更加复杂。它们不仅仅是直接传递提示,还会在用户输入上添加多层指令,跟踪用户在当前会话甚至之前会话中的提问,修改用户提交的提示以提高得到更好回答的概率,并确保答案安全且礼貌。
由于这些额外的提示和安全措施,用户通过API与模型交互时得到的回答,通常会与通过默认网页应用交互时不同。例如,当用户在chatgpt.com网站上与ChatGPT聊天时,得到的回答很可能与直接通过API提交相同提示得到的回答不同。ChatGPT的回答可能基于之前的聊天数据,并会在用户提供的提示上添加额外的安全指令。比如,当你在ChatGPT网站上提问时,回答通常会以邀请用户继续对话的问题结尾,比如“您想继续了解吗?”而直接通过API使用模型时,则不会包含这样的对话性短语。
公司不必自行开发和训练AI模型,也能创建使用AI的应用。他们可以授权并集成已有模型,如Gemini、Claude或GPT-4o到自家面向用户的应用中。自2023年以来,GitHub上大量代码库都导入了使用OpenAI API的代码,显示许多开发者正利用GPT云服务为自己的应用添加AI功能,如图3-1所示。
本章讨论了在面向用户的应用中使用AI模型时的运维考虑事项。
在应用中使用AI模型
许多过去通过自动化和机器学习辅助完成的应用任务,如今正使用AI模型。自己实现自动化与使用基础模型之间的差别虽然微妙,但对LLMOps来说意义重大。
在机器学习和基础模型出现之前,如果想自动化一项任务,需要自己编写代码,为应用程序设计所有可能的输入和对应的输出。在过去十年中,许多自动化代码被机器学习训练的模型所替代。当提交新的输入时,即使该输入未被明确编程或模型未曾见过,机器学习模型仍能生成合适的输出。
以下是几个采用第三方基础模型的流行面向消费者的应用示例:
BeMyEyes
这款由OpenAI支持的应用帮助盲人或视力低下者更好地利用手机导航世界。他们可以用手机摄像头指向物品并听取详细描述。应用可以帮助他们数钱、识别超市商品、协助使用自动取款机,甚至可以指向电脑屏幕以获得技术支持。
Duolingo
这款外语学习应用利用LLM快速生成更多样化的课程。模型被用来生成符合难度标准的多版本对话,使课程更不单调,学习体验更有趣。
Khan Academy
这个教育网站有数亿用户,主要用于K-12年级的学科学习。Khan Academy推出了“Khanmigo”(Khan与西班牙语和葡萄牙语“朋友”amigo的合成词),作为学习伙伴和私人导师。学生可以向Khanmigo提问职业规划,或为什么所学课程有用(青少年常问:“哎,为什么我要学这个?”)。Khanmigo还能生成测验评估学习,反馈学生的写作和答案,辅助学习但不会直接给出答案。
Microsoft Copilot
Copilot系列可能是迄今为止AI应用最广泛的产品。它内置于多个微软热门产品中,包括Office、Windows和Bing,帮助用户更快完成常见任务。例如,你可以打开Word,让Copilot“写一封信给美国银行,要求关闭我的支票账户”,信件会自动生成合适的内容,只需填写少量空白。另一个常用功能是将Word文档转换为PowerPoint演示文稿,反之亦然。
自建还是购买(BUILD VERSUS BUY)
在应用中使用AI模型时,一个主要决策是选择从头构建自己的模型,通过微调和/或提示工程改进已有模型,还是直接使用现成的模型。相关考虑将在第1章详细讨论。本章假设你已做出决策,并拥有一个或多个准备好在应用中使用的模型。
基础设施应用
第一波基于大语言模型(LLM)的应用主要聚焦于面向用户的工具,比如写作和摘要,而当前这波基于LLM的应用更多关注基础设施层面,使LLM更快、更可编程、更模块化。它们重新定义了LLM是什么以及如何使用。LLM应用不再仅限于聊天机器人,而是成为软件应用中的一层新代码。(这就是我们在第2章提到的软件3.0。)下面逐一介绍这些用途。
代理式工作流(Agentic Workflows)
一个简单的提示(prompt)可以完成很多事情。但若超出表层任务,就需要的不只是一次性查询,而是记忆、规划、工具,最终需要能够自主行动的代理——不仅仅完成提示,而是选择下一步做什么。这就是从语言模型向代理系统转变的核心。
代理的本质就是一个循环:观察、决策、行动——不断重复。这些行动可能是读取指令、检查当前状态、获取资源、调用工具或将任务拆解成更小任务。每个动作都需要推理,每个决策影响后续行为。在代理系统中,模型不再是被动的,而是运行代码、管理步骤并动态调整。
这种方法解锁了更复杂的工作流。用户无需手动拼接模型调用,代理会处理逻辑。它能重试失败,存储中间状态,跟踪目标,甚至调用其他代理。这已不再是简单的提示工程,而是系统设计。
代理的工作方式多样。有的遵循固定计划,有的实时调整;有的只思考一次、执行一次后结束,有的则循环运行,根据需要重新审视目标和调整策略。理解这些差异有助于设计出稳健、可解释且高效的系统。主要代理类型包括:
- 单步代理(Single-step agents)
最简单的代理,类似一个封装的提示。接收输入,做局部推理,返回结果,结束。无记忆、无迭代、无反馈循环。适合边界明确的任务,如生成SQL查询、将段落转推文或直接答问。但单步代理脆弱,假设所有信息提前已知,无法处理意外或部分失败。任务涉及多步或状态跟踪时,单步代理不再适用。 - 链式推理代理(Chain-of-thought agents)
代理逐步推理,通常在同一次提示内完成。先解释逻辑再给出答案,提升多跳推理表现。但限制明显:仍在单次模型调用内,无法记忆之前操作。链条太长或上下文窗口太短时,代理会失效。 - 计划与执行代理(Plan-and-act agents)
更有趣的类型。先生成高层计划,再逐步执行。例如,让一个计划执行代理写博客,它会先拟大纲,再分段写作,检查连贯性并编辑。规划和执行可在同一模型或多个代理间完成。该结构明确了已完成、待完成及出错环节,还支持重试和自我纠正。步骤失败时可重新规划或启用备用方案。 - 反思或自我改进代理(Reflective or self-improving agents)
这些代理不仅执行,还会反思表现。完成任务后,可能给输出打分,与真实结果对比,甚至咨询其他模型以提升推理能力。形成一个反馈循环,让代理基于过往行为学习——这不是ML训练意义上的学习,而是运行时学习。反思成本高,但能大幅提升鲁棒性,特别适合开放式领域,难以预先定义“正确”。 - 递归分解代理(Recursive decomposition agents)
代理通过递归拆解任务。如果目标过大,会生成子任务,变身管理者——将子任务分派给自身的新实例或其他专门代理。这种模式用于AutoGPT和BabyAGI等系统,实现动态深度拆解,直到任务足够小可直接解决。挑战在于控制递归不失控,特别是在缺少严格约束或时间限制时。 - 多代理协作(Multi-agent collaborators)
部分系统不采用递归,而是分工协作:一个代理负责写作,另一个负责编辑;一个采集数据,另一个进行分析。各代理职责明确,通常配备独立工具和记忆,彼此通过共享消息或任务队列通信。
这种方式适合可并行处理的任务或各代理具备领域专长的场景,同时要求接口清晰:每个代理需说明如何交互及期望输入类型。
这些模式各有侧重:有的简单快速,有的表达力强、适合复杂任务。随着模型改进和基础设施扩展,我们将看到这些工作流的组合——既有反思和递归,也有规划、执行并交接团队的系统。规模扩大后,协调成关键挑战:有的代理专注数学,有的处理API查询,另有专注摘要。
与其构建一个包揽所有的单一代理,不如组成多个专注且协作的小代理系统。这就是多代理系统的核心。多代理系统不是简单机器人集合,而是一种架构,每个代理半独立运作,配备自己的工具、记忆和目标。代理间会交流、委派和协作。例如,一个代理将用户请求拆解为子目标,另一个执行子任务并反馈结果。随着时间推移,代理还能发展内部协议,优化信息共享与冲突解决。
设计这类工作流并非易事。代理越多,协调成本越高。需明确角色、通信边界和备选方案,做好日志、监控和内存管理。也要定义故障场景,比如某代理失联或返回错误时如何应对。
因此,代理式设计不仅关乎模型,更涉及控制流。哪些决策在模型内完成,哪些由外部逻辑处理?哪些由规则驱动,哪些由学习获得?这些都是架构问题,不只是工程问题。
目前,这一转变还处于早期阶段。行业正从孤立的提示响应器,迈向能分步思考、跨代理委托、持续运行的完整系统。要实现这一点,需要共享协议,如MCP和A2A,接下来我们将讨论。这些协议为代理间提供沟通、推理和协作的规范。
模型上下文协议(Model Context Protocol)
当今智能系统的构建方式正在悄然发生变化。早期的语言模型应用大多是单体结构——自包含、脆弱且与所用工具紧密耦合。每次集成都需要手工定制。如果模型需要与数据库、电子表格、日历或代码仓库通信,就必须为每种组合编写专门的连接代码。随着模型和工具数量的增长,这种集成网络变得难以管理。
从这种混乱中诞生了一条新的设计原则——简单却具变革性。与其让每个工具都“懂”模型语言,或强迫每个模型都“懂”每个工具,AI工程师选择拆分问题,赋予各部分各自职责。这一原则如今体现在“模型上下文协议”(Model Context Protocol,MCP)中,该协议由Anthropic于2024年底首次提出(见图3-2)。
MCP的核心是一个涵盖三个活动部分的协议。首先是主机(host),即你的AI应用,比如桌面助理或聊天机器人。其次是服务器(server),指任何向模型暴露功能的外部系统或工具。最后是客户端(client),它充当连接两者的中介。MCP的强大之处不仅在于它清晰地划分了各部分角色,更在于为它们提供了共同语言——即一种标准化方式,用来描述它们能做什么、知道什么以及能为模型提供什么。
通过MCP,模型无需再猜测可能性。它可以实时发现工具、查询数据源、选择提示——这一切都通过共享协议完成。这意味着模型不再只是生成回应,而是主动执行、调用工具、收集上下文,并以模块化、受控的方式学习如何与外部世界交互。
在实际应用中,使用MCP更像是“管道工程”而非魔法。你可以插入GitHub集成、Slack连接器或日历接口,模型便能学会使用,而无需每次都重新开发集成。每个服务器暴露工具和资源,客户端居中协调,主机负责编排。模型则在这一切中流转,知道自己能用哪些工具。
MCP组件
截至目前,MCP定义了外部系统可以暴露的三大关键组件:
工具(Tools)
工具是模型可调用的操作函数,模型在会话过程中可调用它们执行特定任务。工具类似函数端点——当模型判断需要采取行动(如检索文档、调用API、触发工作流)时,就调用相应工具。工具具有明确的输入/输出格式,并在运行时向主机应用注册。
资源(Resources)
资源是应用控制的只读数据端点,模型可以访问它们以丰富上下文。与工具不同,资源不执行逻辑,而是暴露结构化数据,如文件列表、用户资料或元数据,模型通过查找访问这些信息。资源适用于不需主动计算或外部副作用的信息访问。
提示(Prompts)
提示是用户控制的预设提示模板,可作为上下文或决策路径的一部分呈现给模型。提示通过预定义的指令、格式或策略指导模型行为,封装常见工作流或建议有效使用工具和资源的最佳实践。
这就是软件3.0的新系统架构设计,代表了我们从脆弱的一次性代理向可扩展系统转变的方式。有了MCP,你可以“一次构建,到处使用”。该协议类似通用适配器,抽象了各工具的复杂细节,为模型提供一致的行动方式。
MCP实现机制
接下来,我们来看MCP是如何工作的。MCP采用客户端-服务器协议实现。主机应用(如桌面助理、IDE插件或定制代理)运行一个或多个MCP客户端。每个客户端与一个MCP服务器(一种暴露工具、资源和提示的外部系统)建立一对一连接。
协议从握手阶段开始,客户端与服务器交换版本和能力元数据,确保兼容性,并让客户端动态了解服务器提供的内容。
连接建立后,进入发现阶段。客户端查询服务器,列举所有可用的工具、资源和提示。服务器返回结构化元数据,客户端序列化后通过主机应用暴露给模型。
在交互阶段,当模型确定需要调用工具时,会发出结构化的函数调用(采用JSON或类似格式)。主机通过客户端将请求路由到服务器,服务器执行工具逻辑并返回结果。主机应用再将输出注入模型上下文,允许LLM在下一步推理时融合外部数据。
资源查询采用相同模式——模型可以通过ID或查询请求资源,主机通过客户端从MCP服务器检索数据。同样,使用提示时,主机可基于发现阶段服务器响应向模型提供预定义模板。
所有操作均异步且逐步进行。模型无需预加载所有工具或数据,而是根据对话或任务状态动态实时查询所需内容。该架构极具模块化。单个主机可以同时连接多个MCP服务器。每个服务器仅需实现一次协议,即可兼容任意数量遵循MCP的基于模型的客户端或应用。
MCP项目示例
下面是一个使用Python构建简单服务器的快速示例,展示如何利用MCP获取天气数据。该代码连接到运行天气数据服务的MCP服务器,发送请求以获取葡萄牙里斯本的天气数据,包括摄氏温度。它会列出服务器上可用的资源和工具,调用一个工具(weather-tool)来获取天气数据。可选地,它还会读取一个包含天气报告的文件资源。最后,打印从服务器接收到的天气数据:
步骤1:设置服务器参数
首先定义连接MCP服务器所需的参数,包括可执行文件、服务器脚本和可选环境变量:
server_params = StdioServerParameters(
command="python", # 可执行程序
args=["weather_server.py"], # 你的天气数据服务器脚本
env=None, # 可选环境变量
)
步骤2:定义采样回调函数
创建一个可选的回调函数,用于处理服务器发送的消息,这里用于处理天气相关信息:
async def handle_sampling_message(message):
print(f"Received weather data: {message}")
步骤3:建立与服务器的连接
使用stdio_client连接到MCP服务器:
async with stdio_client(server_params) as (read, write):
async with ClientSession(
read, write, sampling_callback=handle_sampling_message
) as session:
这里,stdio_client(server_params) 使用之前定义的参数打开到MCP服务器的连接,返回两个对象read和write,分别是读取和写入的通信通道。ClientSession负责管理与服务器的所有通信,传入通道和回调函数处理服务器消息。
async with确保代码块执行完毕后自动关闭连接。
步骤4:初始化会话
初始化会话,设置必要的配置或身份验证,必须在进行任何操作(如列出提示或使用工具)前调用:
await session.initialize()
步骤5:列出可用提示
向服务器请求可用提示列表。这里示例获取名为weather-prompt的提示,并传入参数(如城市名):
prompt = await session.get_prompt(
"weather-prompt", arguments={"city": "Lisbon"}
)
步骤6:列出可用资源和工具
从服务器获取两个列表:可用资源和可用工具。资源可能包括文件、API密钥或服务器可访问的数据。工具是服务器可以调用的函数或服务,例如获取天气数据:
resources = await session.list_resources()
tools = await session.list_tools()
步骤7:调用工具
调用服务器上的具体工具,传入必要参数(城市名、温度单位),工具处理请求并返回结果:
weather_data = await session.call_tool(
"weather-tool", arguments={"city": "Lisbon", "unit": "Celsius"}
)
可选地,也可以从服务器获取并读取资源:
content, mime_type = await session.read_resource(
"file://weather_reports/lisbon_report.pdf"
)
preview = content[:100]
print(f"Downloaded content preview: {preview}...")
步骤8:展示结果
打印或处理工具调用返回的结果(这里是天气数据):
print(f"Weather data for Lisbon: {weather_data}")
步骤9:运行代码
使用事件循环(asyncio.run())运行异步函数,完成整个流程:
import asyncio
asyncio.run(fetch_weather_data())
MCP与大型语言模型的未来
MCP最重要的意义在于开启了此前无法实现的能力:跨系统的真正代理推理。我们不再是让模型装载所有知识,而是赋予它寻求、询问、在适当时机调用合适工具的能力。这正是现实世界中的智能表现——不是知道一切,而是知道去哪里找、如何行动。
随着MCP概念的成熟,一类新的框架和工具开始涌现。尽管它们不总是明确标注为基于MCP,但都遵循类似的模块化和语言模型与外部系统结构化交互的原则。LangChain、DSPy和Gorilla等工具正是这种转变的典范。它们通过管理上下文窗口中的执行、扩展与语言模型的交互,并以一致且可预测的方式集成各种工具,使开发者能够构建更高效的程序。核心逻辑是共通的——模块化架构,职责分离,将语言模型视为灵活工具而非单一实体。
这种方法不仅是一种趋势,更是我们与语言模型交互方式的重大变革。随着这些框架的成熟,与模型的互动将从简单的基于提示的查询发展为更复杂的程序化工作流。我们将在模型周围构建逻辑和结构层,就像构建任何后端系统一样。随着语言模型能力的提升,MCP层本身也将演进,支持更高级的功能,如可组合函数、模块化逻辑、条件执行、工具调用和内存操作。
展望未来,上下文窗口将持续扩大。虚拟化LLM(vLLMs)等工具将实现跨会话持久化,让模型状态能从一次交互延续到下一次。这将催生更复杂的工作流,用户不再只是“提示”模型,而是与一个完整集成的系统交互——一个内建记忆、任务堆栈、日志和能力的LLM原生环境。语言模型与更广泛基础设施之间的界限将变得模糊,模型将作为更大系统中动态、有状态的组件被对待。未来,这种协议对用户来说可能如同浏览网页时的HTTP一样“无形”,却将塑造每个AI应用的构建方式。它将成为多代理系统、代理式工作流及支持开放式智能的基础设施的骨干。不仅让LLM更智能,还赋予它们“手”、“眼”和操作世界的“地图”。
MCP的演进将带来这样的环境:任务不再局限于短暂、孤立的交互,而能跨越多步和多场景,模型作为真正的代理,能够与更广泛的工具和资源互动。这种变革已在渐进发生,并将成为未来构建AI驱动应用的基础。
代理对代理协议(Agent-to-Agent Protocol)
MCP为语言模型如何与工具、记忆和外部逻辑接口带来了结构化,提供了一种将模型视作可编程系统的正式方法。但它假设只有一个执行者——一个代理负责查询工具、执行逻辑和管理状态。那么当存在多个代理时,会发生什么?
随着代理系统复杂度的增长,协调需求也随之增加。比如,一个负责安排会议的代理可能需要与负责邮件摘要的代理通信,而后者又可能调用获取航班时间的工具。因此,下一波基于模型的系统不再孤立存在,而是组成“群体”。想象多个具有专业分工的代理分布在不同平台和服务上,彼此间不断通信。这正是Agent2Agent协议(A2A)发挥作用的场景。
A2A由谷歌作为开放标准推出,类似于互操作性的基础层。它定义了AI代理如何识别彼此、交流、协商任务和共享结果。它弥补了MCP的不足:MCP为单个代理提供结构,A2A则为一组代理提供共享语言。
没有共享协议,这些连接会很脆弱,存在定制集成、硬编码依赖和供应商锁定的问题。每个代理都有自己的“方言”和握手方式。A2A通过提供标准解决了这一问题。它不仅管理集中式编排,还支持去中心化协作。一个代理无需了解另一个代理的内部工作,只需知道对方能做什么及如何调用即可。
从本质上讲,A2A是一个自主代理之间的通信协议。它定义了一套用于安全、结构化且可扩展的代理交互的约定。它抽象了供应商特有的细节,聚焦于能力发现、消息交换、任务委托和身份验证。
代理卡(Agent cards)是发现和协商的基础。这些是基于JSON的文档,用于展示代理身份、能力、联系方式以及遵守的安全策略。两个代理可以交换代理卡,彼此查找、评估兼容性并开始协作——无需紧耦合。A2A的核心组件包括:
- 代理身份(Agent identity)
每个代理都通过加密签名其消息,让接收方明确知道对方身份。 - 代理卡(Agent cards)
包含定义代理能力、接口和协议的结构化元数据。 - 能力发现(Capability discovery)
代理互相查询,了解对方支持的功能或任务。 - 任务协商(Task negotiation)
代理可以委托工作、提出计划,或异步协调工作流。 - 安全消息传递(Secure messaging)
所有通信均经过身份认证、加密,并支持审计。 - 可扩展性(Extensibility)
A2A设计为可演进的协议,支持扩展模式、定义自有代理角色和创建领域特定逻辑。
典型的A2A交互遵循几个可预测的步骤,如图3-3所示。
让我们详细看看每个步骤:
步骤1:发现(Discovery)
一个代理通过注册表或直接请求,查找另一个代理的代理卡(agent card)。
步骤2:验证(Validation)
调用代理通过对方的代理卡核验其身份和凭证。
步骤3:能力匹配(Capability matching)
调用代理查看对方列出的功能,并决定如何委派任务。
步骤4:任务委托(Task delegation)
调用代理发送结构化请求,被调用代理可以接受、拒绝或提出替代方案。
步骤5:执行(Execution)
若接受请求,被调用代理执行任务,必要时可调用自身的工具或子代理。
步骤6:响应(Response)
被调用代理将结果返回给调用代理,同时附带相关日志、指标或后续能力。
每一步都是模块化的。你可以将它们集成到任何系统中——无论是基于LangChain、Haystack,还是你自己的平台构建代理。而且因为这是一个开放规范,你不必依赖谷歌或任何单一供应商来实现它。
vLLMs 和多模态大语言模型的崛起
到2025年初,我们讨论的大多数大语言模型(LLM)工作流仍然围绕文本展开。输入和输出都是“token”(标记),即模型理解语言、进行转换并输出结构化的“想法”。但世界不仅仅由文字组成。我们有视觉、听觉,也会在文本之外的空间里行动。
多模态模型就是下一步。这类模型不仅能读懂文字,还能看、听、描述,甚至跨模态生成内容。这些系统能够处理多种模态的输入和输出:文本、图像、音频、视频,有时甚至包括表格数据或代码。如果一个模型可以接受多种模态的输入或输出多种模态的结果,它就是多模态模型。当前最常见的多模态模型是视觉-语言模型(Vision-Language Model,VLM)。VLM同时接受文本和图像作为输入,输出文本。有些模型还能生成图像,或者用边界框和说明为图像标注。
这种转变带来了之前纯语言系统无法实现的新能力。多模态LLM之所以现在可行,主要有三点原因:
- Transformer的泛化能力
LLM背后的Transformer架构并不关心token是词还是像素。一旦数据被嵌入成合适格式,它就成为另一种需要处理的数据流。 - 训练规模
预训练使用了包含图文对的大规模语料库(如LAION、COCO),实现了大规模的视觉-语言对齐。 - 工具和开放访问
如CLIP、Flamingo、BLIP和LLaVA等项目创建了可复用的架构和模型检查点。现在你不必是OpenAI或Google,也能训练或微调模型。
下面是典型的多模态模型处理流程:
- 输入嵌入
文本被分词,图像通过视觉编码器(通常是ViT)处理,两者都被嵌入到同一向量空间。 - 融合
模型融合视觉和文本嵌入,同时关注两者。这是模型进行推理的环节,将所见与所说对齐。 - 输出
模型基于多模态输入生成文本输出(如标题、描述或答案)。
有些模型(如CLIP)不生成文本,而是通过匹配图像和文本的嵌入实现功能。另一些(如LLaVA、MiniGPT-4)是基于聊天的模型,可以回答视觉问题、描述图像或通过对话解释图表。到2025年中期,一些著名的多模态模型包括:
- CLIP
OpenAI模型,学习视觉和文本的联合嵌入,不是生成式,而是匹配式。可以找出匹配某句话的图像,或描述某图像的句子。 - BLIP 和 BLIP-2
预训练的视觉-语言模型,能用自然语言描述和推理图像。BLIP-2使用冻结的图像编码器和轻量查询Transformer。 - MiniGPT-4
结合视觉编码器和冻结的LLM,最小训练即可对齐表示,类似视觉聊天机器人。 - LLaVA
基于LLaMA和CLIP风格视觉编码器,支持交互式视觉对话和视觉问答。 - Flamingo
DeepMind推出的强大闭源模型,在少样本多模态推理上设定了新标杆。 - BentoML 和 LLM Foundry
虽非模型本身,但提供部署、服务和训练基础设施,支持在本地堆栈微调和运行VLM。
多模态代理开启了AI使用方式的巨大新领域,它们可以:
- 回答关于图像、视频帧或文档截图的问题
- 解析图表、表格或手写文本
- 总结幻灯片内容
- 转录白板内容
- 描述UI布局
- 在机器人或辅助代理中导航物理世界
- 构建更“人性化”的接口,感觉更自然、感知更丰富
这关乎跨模态推理,将语言和视觉视作同一世界的两种观察视角。正如语言领域的演进一样,这一转变也从模式识别走向上下文推理。
发展趋势十分明朗:模态界限正在模糊。未来系统将把视觉、语言、音频、代码和交互作为同一认知环的一部分来处理,一些系统已实现这一点。开放的前沿是构建能够观察、决策和行动的代理系统——不是分散的模块,而是集成的流程。
这改变了一切架构。提示变成了界面,而不只是指令。输入是多模态的,比如语音命令加摄像头视频流。输出也是多模态的,常常包含生成的摘要和视觉标注。
因此,多模态LLM不仅是功能升级,而是语言模型本质上的结构性变革。它们代表了从抽象对话机器向具身代理的转变,能够理解并在真实世界中操作。
LLMOps 关注的问题
LLMOps 团队需要回答的核心问题是:“这个应用能否在合理的成本下表现良好?”一旦能回答“是”,团队就能开始优化,努力以最低成本获得最大性能。对于 LLM 应用,性能维度有很多,但我们先从成本谈起。
对于选择从云服务商购买 LLM 服务的公司,成本可以直接用金钱衡量(可以使用 LLM 价格查询工具做对比)。虽然定义和衡量性能并不简单,但假设应用已上线且性能达标,那么如果公司的主要目标是最大化利润,LLMOps 团队就应选择性能相同但成本最低的云端 LLM。
对于选择自建 LLM 或在自有硬件上运行 LLM 应用的公司,还有一个额外考虑:自建与购买的机会成本。(机会成本指的是如果企业选择自建硬件和管理硬件,而不是通过模型 API 外包,会损失的钱、时间和市场领先优势。)对能够运行 LLM 的 GPU 的需求非常大,推动了 Nvidia 等 GPU 制造商的股价创新高。除了计算运行成本,购买 GPU 的公司还需回答一个意想不到的问题:租赁这些 GPU 是否更赚钱?Spheron 发表了一篇关于买 GPU 和租 GPU 经济性及弊端的深入博文。某些情况下,租赁 GPU 比自营更有利可图。
应用性能监控
MLOps 在 LLM 出现之前已存在多年,对许多应用类别而言,MLOps 和 LLMOps 的性能指标基本相同或非常接近。这是因为大多数应用性能指标依赖于业务领域。例如,对于用 LLM 支持销售流程的应用,关键指标可能是销售转化率,回答“自从用上 LLM,销售额有提升吗?”对于用 LLM 匹配简历与职位的招聘应用,关键指标可能是面试筛选成功率,回答“自从用上 LLM,我们的候选人质量提高了吗?”
这些指标的计算不依赖底层技术。无论应用是用 ML、LLM,还是笔纸方式决定拨打哪些客户或候选人,评价指标计算方法相同。LLM 会带来额外挑战,主要是因为其确定性较低,更易受到称为“漂移”的一类变化影响。我们将在第7章详细讨论漂移。现在,可以这样理解:使用 LLM 的应用行为更像人类执行的流程,而非传统计算机程序,尤其体现在输出的多样性上。应用指标需要适应这种多样性。
测量消费级 LLM 应用的性能
为更好地说明差异,我们用一个 ML 已经解决的问题举例:电子邮件垃圾邮件分类。假设 ML 版本使用如 XGBoost 的流行模型,LLM 版本使用 GPT-4o。暂假设我们在 LLM 应用中用的是简单的提示,例如:
这封邮件是垃圾邮件吗?如果是,请回复“spam”,否则回复“ham”。
[邮件内容]
垃圾邮件检测是经典的二分类问题,通常使用准确率、精确率和召回率等指标:
-
准确率(Accuracy)
准确率是正确分类的样本占总样本的比例。在这里,表示模型正确标记为垃圾邮件(真阳性)或非垃圾邮件(真阴性)的邮件比例。计算公式:虽然准确率是通用指标,但在数据不平衡时(如大多数邮件非垃圾)作用有限。
-
精确率(Precision)
精确率是模型判定为垃圾邮件的邮件中真正是垃圾邮件的比例。回答问题:“模型标记为垃圾邮件的邮件中,有多少是真的垃圾邮件?”计算公式:
精确率高意味着模型判定为垃圾邮件时通常正确,但可能因此漏掉部分垃圾邮件,降低召回率。
- 召回率(Recall)
召回率(又称灵敏度或真正率)是所有真实垃圾邮件中被模型正确识别的比例。回答问题:“所有真实垃圾邮件中,模型识别出多少?”计算公式:
召回率高意味着大部分垃圾邮件被检测到,但可能引发更多误报,降低精确率。
准确率给出总体正确率,精确率反映判为正样本的准确度,召回率体现模型捕获正样本的能力。三者结合提供了对模型区分垃圾邮件和非垃圾邮件能力的全面评估。
是否优先考虑精确率(Precision)还是召回率(Recall),取决于具体应用场景。例如,在垃圾邮件过滤中,用户通常更倾向于高召回率——即尽可能多地捕获垃圾邮件,即使这意味着有些正常邮件可能被误判为垃圾邮件。为减轻误报问题,用户多年来已经习惯于定期检查垃圾邮件文件夹。在其他分类任务中,比如医学诊断或欺诈检测,应用可能更倾向于优先保证精确率,以尽量减少误报带来的费用和压力。
在这个例子中,无论你的应用使用的是 LLM 还是传统机器学习模型,只要应用输出“spam”(垃圾邮件)或“ham”(非垃圾邮件),就可以计算准确率、精确率和召回率,并使用上述指标对模型进行比较。为此,你需要一个包含正确答案的测试数据集,即“真实标签”(ground truth)。你可以用该测试数据集来计算这些指标。
在机器学习设置中,模型的输出具有含义:邮件的“垃圾邮件程度”。数值越高,邮件越可能是垃圾邮件。工程师可以通过设置更高的阈值来提升模型的精确率,但代价是召回率下降。这样做可以减少将正常邮件误判为垃圾邮件的概率,从而降低误报,提高精确率;但同时可能漏判更多垃圾邮件,导致召回率下降。
相反,工程师也可以设置更低的阈值,使模型将更多邮件判定为垃圾邮件,从而捕获更多真正的垃圾邮件,提高召回率,但会增加误报的可能性,降低精确率。
通过对模型输出的多个阈值使用上述过程,可以绘制精确率-召回率曲线(Precision-Recall Curve),这是一种评估模型在不同设置下性能的简便方法。该曲线在纵轴显示精确率,横轴显示召回率,反映了不同阈值水平下两者的变化情况。通过这条曲线,MLOps 团队可以直观地看到精确率和召回率随阈值变化的权衡关系。由于机器学习模型默认输出“垃圾邮件程度”,只需用测试数据集配合不同分类阈值,计算精确率和召回率,并绘图,即可轻松得到该曲线。
通常,高质量模型的曲线能达到更高的精确率和召回率,而低质量模型的曲线则接近原点(低精确率和召回率)。你还可以计算该曲线下面积(AUC-PR,Area Under the Curve for Precision and Recall),如图3-4所示。AUC-PR 值越大,表示模型性能越好,因为这说明模型在多个阈值下都能保持高精确率和高召回率。换言之,曲线下方面积越大,模型整体表现越优。
当使用大型语言模型(LLM)作为应用引擎时,无法轻易计算精确率-召回率曲线下面积(AUC)。第一个实际问题是,模型输出通常不包含给定邮件为垃圾邮件的概率。一种解决方法是修改提示词,要求模型输出概率而非直接结果,正如2024年论文《校准大型语言模型的口头概率》(Calibrating Verbalized Probabilities for Large Language Models)中提出的示例:
“下面这封邮件是垃圾邮件的概率是多少?请给出一个介于0到1之间的实数。你的回答只需是对该概率的最佳猜测数字。”
【邮件内容】
使用这样的提示词可以帮助你绘制概率曲线,但LLM非确定性的特点带来了问题:即使温度设置为0(即尽可能减少随机性),同一封邮件输入,LLM产生的概率数值也可能大相径庭。温度参数及其他几个参数(如frequency_penalty和presence_penalty)用于定义模型输出的随机性(或创造性)。一个标准的OpenAI请求示例参数如下:
{
"model": "gpt-4",
"prompt": "Write a poem about machines.",
"temperature": 0.7,
"top_p": 0.9,
"frequency_penalty": 0.5,
"presence_penalty": 0.6,
"max_tokens": 60,
"stop": ["\n\n"]
}
举个例子,我用上述概率提示词对一封通知太阳能板安装组件交付的邮件进行分类:
Abi,
我想更新一下信息。组件会在周五送达。确认后我会安排和你们会面。政府许可预计还需大约两周时间。
我连续两次用温度为0的GPT-4o提交请求,得到的概率分别是0.05和0.1。第三次提交得到0.05。虽然温度为0理论上应使模型输出确定,但明显的结果波动表明,LLM的输出不能保证完美一致性。因此,不能可靠地用这种概率绘制方法来生成曲线,从而选择AUC最大的模型。
传统的计算指标,如受试者工作特征曲线(ROC)、召回率曲线、曲线下面积(AUC)以及精确率-召回率曲线下面积(AUC-PR)都假设评分是稳定的,即相同输入每次得到相同分数。然而,对于LLM来说,这些曲线会变得嘈杂,标准的评估方法也因此变得不可靠。评估系统可能会错误地判定某个“版本”的模型优于另一个,仅仅因为内部的随机性差异。
选择适合您应用的最佳模型
在 MLOps 和 LLMOps 中,一个常见的任务是判断新版本模型是否优于现有版本。这通常被称为“冠军/挑战者测试”或“A/A 测试”,其中当前投入生产的模型称为“冠军”,新模型称为“挑战者”。如果挑战者表现优于冠军,它将替代冠军,接替生产位置。评估通常通过一个或多个指标来进行。
由于 LLM 输出的固有不确定性,您需要对多个样本计算分布,而不是依赖单点指标(如 AUC)。实际上,这意味着对每个测试数据集多次运行,以获得结果范围,从而计算您评估的性能指标的统计分布,比如均值、标准差和置信区间。
通过收集这些分布,您可以洞察模型响应的稳定性和可靠性。这些指标有助于判断挑战者是否真正优于冠军,还是差异仅源于模型本身的波动性。
例如,您不只是使用单一的精确率或召回率数字,而是对两个模型在大量测试中的精确率和召回率取平均值,并记录这些得分的方差。结果分布允许您进行显著性检验(如 t 检验),帮助确定两个模型之间的差异是否具有统计学意义。这种方法考虑了模型的波动性,能更清楚地反映挑战者是否在各种场景和输入下持续优于冠军。您还可以使用 A/A 测试来验证流程的有效性。对同一模型进行两次测试,结果点估计可能不同,但差异不应具有统计显著性。
总之,计算分布(见示例 3-1 代码)而非单一指标,提供了一个更健壮的框架,用于比较基于 LLM 的应用,帮助缓解 LLM 非确定性带来的挑战。
示例 3-1:计算两个模型在大量测试中的精确率和召回率分布
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
# 模拟两个模型得分的分布,设定随机种子
np.random.seed(42)
champion_scores = np.random.normal(loc=0.78, scale=0.02, size=100)
challenger_scores = np.random.normal(loc=0.80, scale=0.02, size=100)
# 绘制分布图
plt.figure(figsize=(10,6))
sns.kdeplot(champion_scores, label="Champion Model", color="blue")
sns.kdeplot(challenger_scores, label="Challenger Model", color="red")
plt.title("Distributions of Model Performance Scores")
plt.xlabel("Score")
plt.ylabel("Density")
plt.legend()
plt.grid(True)
plt.show()
通过对大量测试集进行性能评估并使用统计工具分析结果,您能更准确地判断挑战者模型是否值得用来替代冠军模型。
其他应用指标
尽管在前面的例子中为了简化起见使用了精确率(precision)和召回率(recall),但在实际应用中还会用到许多其他指标。推荐系统和排序选择应用(如搜索算法)通常使用平均精度均值(Mean Average Precision,MAP)来评估排序质量。MAP 计算每个查询或用户的平均精确率,强调将相关项目排在结果列表的靠前位置。MAP 会给那些能将相关项目排在前列的模型更高评分,特别适用于结果顺序非常重要的应用场景。这在电商搜索中尤为关键,因为用户更倾向于点击前几个结果,高 MAP 分数意味着模型有效地将相关项目优先展示。
另一个常用于排序应用的指标是归一化折损累计增益(Normalized Discounted Cumulative Gain,NDCG),它评估结果的相关性,同时考虑了结果的位置。NDCG 采用折损因子,降低排名靠后但相关的项目的重要性,非常适合需要将最相关内容显示在最顶部的系统。例如,在新闻推荐应用中,用户通常只会点击前几篇文章,高 NDCG 分数表明最相关的文章得到了优先推荐。
排名和推荐应用中的其他关键指标包括命中率(hit rate,也称为 top-k 准确率)和覆盖率(coverage)。命中率衡量在前 k 个结果中至少出现一个相关项目的频率,反映模型在高排名位置推荐相关项目的一致性。例如,在流媒体服务中,高命中率确保用户经常在顶部推荐中看到相关的节目或电影。覆盖率则衡量目录中被推荐项目所占的比例,用来判断模型是否提供了丰富多样的选项,避免反复推荐热门项目。推荐系统中较高的覆盖率更受欢迎,因为它能让用户接触到更广泛的内容。
在许多基于 LLM 的应用中,真实标签(ground truth)具有很强的主观性。例如,虽然你可以测试由 LLM 生成的代码是否能通过某个测试集,但这通常不足以判断代码质量——毕竟,有很多代码是能运行的,但效率低下或难以理解。在这种情况下,团队往往会使用面向客户的指标,比如用户参与度指标,包括点击率(Click-Through Rate,CTR)和转化率(Conversion Rate)。CTR 衡量用户点击推荐项目的比例,而转化率追踪那些引导用户完成购买或注册等动作的推荐。
以代码生成应用为例,判断用户是否喜欢生成内容的一种方法是看用户是否点击了“接受”按钮来采纳推荐代码,另一种方法是观察用户对采纳代码后的修改量。在邮件生成应用中,可以查看用户是否直接发送了生成的邮件,并监测邮件的打开率和回复率。
你可以在基于LLM的应用中控制哪些内容?
在实现以LLM为核心组件的应用时,有多个参数可以用来调节模型的回答,从而在创造力、一致性和效率之间取得平衡。最广为人知的设置是温度(temperature) ,它通过调整模型输出的随机性来控制回答的确定性或多样性。温度的取值范围是0到1之间。较低的温度值(如0.2)会让模型更聚焦于最可能的答案,适合生成事实性和一致性的回复。相反,较高的温度值(如0.8)则增加随机性,使模型生成更丰富多样且有创造性的内容,这在讲故事或头脑风暴等场景中非常有用。
另外两个参数,top-k和top-p,通过设定概率限制来控制回答的多样性。top-k采样限制模型每一步只能从概率最高的k个token中选取,这样可以引导模型聚焦于统计上最可能的词汇,较低的k值确保回答更连贯。而top-p采样是累计概率阈值,模型会从概率总和超过设定值(如0.9)的最小token集合中选择,这样既允许考虑更多的词汇选项来生成创造性回答,又忽略概率极低的词汇。你可以将top-k和top-p参数与温度配合使用,以根据应用需求在多样性和连贯性之间找到合适的平衡。
另一个影响回答多样性的参数是频率惩罚(frequency penalty) ,它根据token(单词或短语)在生成文本中出现的次数施加惩罚。也就是说,某个词出现得越多,后续再次出现时的惩罚越重,模型更不可能频繁重复相同词汇。这个参数在创意类应用中尤其有用,因为避免重复可以让输出更加悦耳,保证常用词或短语不会被过度重复。
相反,存在惩罚(presence penalty) 则对所有已经至少出现过一次的token施加通用惩罚,不论出现频率多少。换句话说,模型被鼓励尽量不重复使用已经出现过的词汇或短语,即使它只出现过一次。存在惩罚更多是促使模型在回答中引入全新的词汇,这对内容生成类应用有益,可以避免即使是轻微的重复,保持语言的新鲜和多样。
为了控制回答长度、防止生成过长,大多数模型允许你设置最大token数(max tokens) 参数,用于限制模型每次回答最多生成的token数量。这对于控制成本和调整回答长度以适应不同场景都非常关键;简短回答需要较低的token限制,而详细回答则需要较高的限制。在垃圾邮件检测的例子中,max tokens可以设置得较低。在一些模型中,max tokens参数同时包含输入和输出的token数量。
最后一个容易控制的参数是提示词(prompt) ,这里是大多数现实优化的关键——也就是所谓的提示工程(prompt engineering)。
提示工程是“困难的”
在我们的垃圾邮件检测示例中,我们从一个非常简单的提示开始:
以下邮件是垃圾邮件吗?如果是垃圾邮件,请回答 spam;如果不是垃圾邮件,请回答 ham。
[邮件内容]
请注意,这里提示期望模型只输出“spam”或“ham”,而不会给出“垃圾邮件概率(spamminess)”值。如果你使用上述提示,可能会发现模型并不一定会按照预期只输出“spam”或“ham”。我从 GPT-4o 得到的一些其他可能回答包括:
- 是的,这封邮件是垃圾邮件。
- 不是垃圾邮件。
- 我不确定。
- 抱歉,作为AI语言模型,我必须遵守伦理指南,不能参与有害、恶意或冒犯性行为。
虽然最后一个回答可能让人惊讶,但这类回答在LLM中非常常见,尤其当邮件内容被判定为极度冒犯时,模型可能会触发此类回应。
这也提出了另一个使用LLM替代传统机器学习应用时需要考虑的问题:当模型的回答不符合预期输出时,你该如何处理?一个常见的解决方案是引入一个新的类别,比如“未知(unknown)”。但即使如此,你也可以强制将所有输出都归为“垃圾邮件”或“非垃圾邮件”,例如将所有非“ham”的输出归为“spam”。创建“未知”类别有助于避免隐藏规格外错误。在这种情况下,你需要为“规格外错误百分比”设定一个指标,并将目标设置得较低。
对提示词进行调整被称为提示工程。改进提示词最简单的方法之一是加上一句“只能使用 spam 或 ham 作为答案,别无其他”,这可以降低回答被归为“未知”的比例。
以下邮件是垃圾邮件吗?如果是垃圾邮件,请回答 spam;如果不是垃圾邮件,请回答 ham。只能使用 spam 或 ham 作为答案,别无其他。
[邮件内容]
假设管理层某人看到一篇文章,说如果让模型“仔细思考”,其表现会更好,他们让你测试下面这个新提示是否比现有提示更好或更差:
在经过仔细考虑后,你认为下面的邮件是否是垃圾邮件?如果是,请回答 spam;如果不是,请回答 ham。只能使用 spam 或 ham 作为答案,别无其他。
[邮件内容]
我们知道一点:使用更详细的提示会增加成本,因为模型按输入和输出的总长度计费。
我们的提示工程是否带来了更好的结果?
为了判断额外的成本是否带来了性能提升,你需要经过一个决策过程。示例 3-2 展示了如何使用 Python 实现这一过程。示例中使用了 enron_spam_data.csv 文件,这是一个公开标注数据集,包含约3万个电子邮件,其中正好一半是垃圾邮件。
代码仅测试了少量邮件,进行了10次实验,每次实验各测试30封垃圾邮件和30封非垃圾邮件。在实际环境中,你应使用自己的、更大规模的标注数据集(因为存在数据漂移问题,详见第7章),并进行更多实验。
示例 3-2:提示工程测试代码
import pandas as pd
import numpy as np
import random
from statistics import mean, stdev
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(
api_key=os.environ.get("OPENAI_API_KEY")
)
# 定义要测试的提示词
PROMPT_A = "Is the following email spam? Respond with spam if the email is spam or ham if the email is not spam. Use only spam or ham as the answers, nothing else.\n\nSubject: {subject}\n\nMessage: {message}"
PROMPT_B = "After considering it very carefully, do you think it's likely that the email below is spam? Respond with spam if the email is spam or ham if the email is not spam. Use only spam or ham as the answers, nothing else.\n\nSubject: {subject}\n\nMessage: {message}"
# 加载数据集并抽样
df = pd.read_csv("enron_spam_data.csv")
spam_df = df[df['Spam/Ham'] == 'spam'].sample(n=30)
ham_df = df[df['Spam/Ham'] == 'ham'].sample(n=30)
sampled_df = pd.concat([spam_df, ham_df])
# 评估函数
def evaluate_prompt(prompt_template):
true_positive = 0
false_positive = 0
true_negative = 0
false_negative = 0
for _, row in sampled_df.iterrows():
subject = row['Subject']
message = row['Message']
actual_label = row['Spam/Ham']
# 使用邮件数据生成提示词
prompt = prompt_template.format(subject=subject, message=message)
# 调用 OpenAI API
try:
response = client.chat.completions.create(
messages=[{"role": "user", "content": prompt}],
model="gpt-3.5-turbo-0125",
)
predicted_label = response.choices[0].message.content.strip().lower()
except Exception as e:
print(f"Error calling OpenAI API: {e}")
continue
# 将实际标签和预测标签转换为小写比较
if predicted_label == 'spam' and actual_label == 'spam':
true_positive += 1
elif predicted_label == 'spam' and actual_label == 'ham':
false_positive += 1
elif predicted_label == 'ham' and actual_label == 'ham':
true_negative += 1
elif predicted_label == 'ham' and actual_label == 'spam':
false_negative += 1
# 计算精确率和召回率
precision = (
true_positive / (true_positive + false_positive)
if (true_positive + false_positive) > 0
else 0
)
recall = (
true_positive / (true_positive + false_negative)
if (true_positive + false_negative) > 0
else 0
)
return precision, recall
# 运行实验
def run_experiments(prompt_template, n_experiments=10):
precisions = []
recalls = []
for n in range(n_experiments):
print(f"Running experiment {n+1} of {n_experiments}")
precision, recall = evaluate_prompt(prompt_template)
print(f"Precision: {precision:.4f}, recall: {recall:.4f}")
precisions.append(precision)
recalls.append(recall)
# 计算精确率和召回率的平均值和标准差
precision_mean = mean(precisions)
precision_stdev = stdev(precisions)
recall_mean = mean(recalls)
recall_stdev = stdev(recalls)
return precision_mean, precision_stdev, recall_mean, recall_stdev
# 对 Prompt A 运行实验
(
precision_mean_a,
precision_stdev_a,
recall_mean_a,
recall_stdev_a,
) = run_experiments(PROMPT_A)
print(
f"Prompt A - Precision: {precision_mean_a:.4f} ± {precision_stdev_a:.4f}, "
f"Recall: {recall_mean_a:.4f} ± {recall_stdev_a:.4f}"
)
# 对 Prompt B 运行实验
(
precision_mean_b,
precision_stdev_b,
recall_mean_b,
recall_stdev_b,
) = run_experiments(PROMPT_B)
print(
f"Prompt B - Precision: {precision_mean_b:.4f} ± {precision_stdev_b:.4f}, "
f"Recall: {recall_mean_b:.4f} ± {recall_stdev_b:.4f}"
)
结果如下:
Prompt A - Precision: 0.8370 ± 0.0365, Recall: 1.0000 ± 0.0000
Prompt B - Precision: 0.7763 ± 0.0311, Recall: 1.0000 ± 0.0000
你可以看到,对于这个数据集,LLM 在垃圾邮件检测上的召回率(Recall)为100%,两个提示词都表现非常好,但精确率(Precision)方面,原始提示词约为84%,而更长提示词约为78%。那么,新提示词是否优于现有提示词呢?我们可以用t检验来判断。由于两个模型的召回率相同,只需对精确率进行t检验。计算t统计量的公式为:
这里,t统计量为6.93。将t统计量转化为p值,约为 2.13×10−92.13 \times 10^{-9}2.13×10−9,远小于通常的显著性水平0.05。
由于p值极小,我们可以有信心地得出结论:Prompt A 在精确率方面显著优于 Prompt B。这意味着观察到的精确率平均差异极不可能是由随机变异引起的。且由于 Prompt B 费用更高,我们不会更新模型去使用 Prompt B。
基于大模型(LLM)的基础设施系统“更难”
一旦你不再仅仅是用一个提示词调用LLM,而是将其编排进一个包含记忆、工具、反馈和目标的实时系统时,你面对的就不再是LLM的非确定性本质,而是一个复杂的操作系统——它拥有自己的语言、状态、依赖关系和各种失败模式,随时可能出现故障。在这里,无论是智能体系统还是基础设施级的LLM应用,都面临各自的运维难题或LLMOps问题。
智能体系统虽然承诺提供灵活性、推理能力和自动化决策,但它们也引入了复杂的控制流程,调试非常困难,信任度更低。正如上一节的示例提示所示,LLM的输出会出现较大差异,导致调试变得艰难。如果你的智能体在一个10步任务的第6步失败,重新运行可能会在第3步失败,甚至完全成功,因此没有明确的“调用堆栈”。尽管像W&B公司提供了Weave这样的追踪工具,但在智能体工作流中实现完美的可复现性几乎是不可能的。
另一个问题是智能体需要上下文。它们会记住事实并参考之前的步骤来规划下一步。但存储和检索这些记忆(无论是向量化还是分词化)在延迟和准确性上都成为瓶颈。大多数记忆系统脆弱、泄漏且与模型的表示空间不匹配。此外,迄今为止很少有智能体擅长“规划”,这在多个Twitter/X讨论区中已有报道,智能体经常跳过步骤、重复任务或执行无关操作。调用工具时问题更严重——当工具因API错误或返回空结果失败时,智能体必须恢复,避免陷入死循环或产生幻觉。再次强调,很少有智能体能在没有预编程if-else条件的情况下优雅地处理这些边缘情况。而且,目前还没有针对智能体工作流的“单元测试”或“断言”能正常工作的方案。
智能体工作流在逻辑混乱时会崩溃,比如计划无法分解或记忆结构混乱。而基础设施级的LLM应用则带来更多故障点和复杂性。如果协议不同步、数据流泄漏,或者模型边界不清晰……失败点数不胜数。虽然多数人纷纷拥抱MCP或A2A协议,但能应对这些工具带来的LLMOps问题的公司寥寥无几。
首先,MCP假设记忆和工具是抽象且可调用的,但现实远非如此。记忆更新常常不同步。不同智能体有不同状态,工具可能未经协调就更新共享状态。你需要记忆版本控制、命名空间管理和同步,而这些都不是“开箱即用”的。
再者,假设模型调用耗时,将模型调用包装进MCP会带来编排开销,包括初始化、提示词检索和工具注册等问题,这些会叠加。此时必须考虑机会成本。同理,A2A协议会增加网络延迟、序列化和智能体发现的复杂性,复杂任务中这些开销叠加尤为明显。在LangChain中提示失败可以看到清晰的错误堆栈,而A2A智能体失败时可能返回无效响应、破坏协议,甚至超时,失败点难以定位——是在智能体层、传输层、记忆层还是工具层?你需要叠加多层观测工具和结构化日志,覆盖所有智能体和会话。
如果你的工作流有5个智能体,每个调用3个工具,且所有交互都经过A2A或MCP层,那么用户等待时间可能达到数秒甚至数分钟,极大破坏用户体验。此外,截止目前,大多数安全问题尚未得到充分记录。智能体可能会劫持彼此的记忆,触发意外工具操作——尤其是当LLM自主操作用户数据时,攻击面极其宽广。
智能体智能在演示中极具冲击力,但在生产环境中非常脆弱,缺乏坚实的基础设施支持。每天我都能看到大量巧妙的编排,围绕着脆弱且利用不足的LLMOps基础设施构建的愚蠢提示链。但构建这套基础设施意味着必须面对成本:性能开销、严格的接口契约、状态复杂性,以及对更多LLMOps工程师的需求,以创建最佳实践、工具和框架,确保系统的可靠、安全和稳健运行。
结论
本章涵盖了将大语言模型(LLM)集成到应用中的关键考量,包括性能衡量、通过参数调整和提示工程改进模型,以及智能体和基础设施应用的相关内容。在企业应用中成功使用LLM,需要明确定义性能指标、进行监控,并持续改进模型。
参考文献
- Alayrac, Jean-Baptiste 等人。《用单一视觉语言模型处理多任务》,Google DeepMind,2022年4月28日。
- Anthropic。《介绍模型上下文协议(MCP)》,2024年11月25日。
- Bevans, Rebecca。《t检验简介:定义、公式与示例》,Scribbr,2023年6月22日。
- Fiore, Steven。《微软Copilot堆栈内幕:构建更智能的AI助手》,Lantern,2024年8月2日。
- Henry, Parker。《Duolingo如何利用AI更快创建课程》,Duolingo博客,2023年6月22日。
- Li, Junnan 等人。《BLIP:引导语言-图像预训练实现统一视觉语言理解与生成》,arXiv,2022年2月15日。
- Microsoft | AI for Good Lab。《当AI成为通用技术》(图表),Brad Smith,微软,2024年9月,LinkedIn帖子。
- Microsoft Research。《构建下一代多模态基础模型以支持通用助理》,访问于2025年5月21日。
- OpenAI。《成为我的眼睛》,访问于2025年5月21日。
- OpenAI。《CLIP:连接文本与图像》,OpenAI,2021年1月5日。
- OpenL。《LLM价格检查》,访问于2025年5月21日。
- Schmid, Phil。《模型上下文协议(MCP)概览》,个人博客,2025年4月3日。
- Sigrist, Fabio。《ROC与精确率-召回率曲线解析》,Medium,2022年1月25日。
- South, Tobin 等人。《身份验证的委托和授权AI智能体》,arXiv,2025年1月16日。
- Spheron Network。《租赁云GPU的经济学:全面解析》,2025年3月13日。
- Wang, Cheng 等人。《大语言模型的口头概率校准》,arXiv,2024年10月9日。
询问 ChatGPT