如果你从 2023 年就开始关注大语言模型,大概率会有类似的经历:最初觉得它只是个“更聪明的自动补全”,或者“能陪你聊天的搜索引擎”;后来看到 GPT-4 写代码、写文章、做数学题,又觉得它要取代程序员;再后来做真实项目,发现单靠模型本身根本落不了地,要把文档切分、向量检索、工具调用、流程编排全都接起来,模型才能稳定输出结果。
这个认知转变正是当前 LLM 应用开发的核心命题。LLM 真正要扮演的角色,不是“内容生成器”,而是应用架构里的“推理引擎”和“控制中枢”。我早期对 LLM 角色的判断,就是把重点放在模型本身能生成什么,忽略了模型在真实业务系统里到底处于什么位置。等到亲手做 RAG、Agent、MCP 集成时才发现,决定一个 LLM 应用能不能上线、能不能稳定运行、成本能不能接受的,从来不是模型有多强,而是外部系统怎么设计。
这篇文章会复盘这个认知偏差:LLM 在应用中的真实角色是什么,RAG、Agent、MCP、编排框架分别解决什么问题,以及从零搭建一个最小 LLM 应用时,环境、代码、验证和排错应该怎么做。读完你至少能明白,为什么“LLM 应用开发”变成了一个工程问题,而不是提示词问题。
1. 我为什么看错了 LLM:从“聊天机器人”到“推理引擎”
对 LLM 角色的早期判断,很容易被 Demo 带偏。OpenAI 发布 GPT-3.5 时,大家看到的是它写邮件、写小作文、回答常识问题,所以第一反应是:这是一个自然语言生成工具,是 Chatbot,是下一代搜索入口。
但真实业务里的问题完全不是这样。企业想做的不是“聊天”,而是“让系统自动完成某件事”:比如根据工单内容自动分类并调用修复脚本;比如从财报里抽取数据后自动生成分析报告;比如在客服对话中实时检索知识库并给出带出处的回复。这些任务的核心不是“生成一段文字”,而是“在恰当的时候调用恰当的逻辑”。
于是 LLM 的真实角色浮出水面:它负责把自然语言请求转化为结构化的决策,其他系统负责执行。一句话概括——LLM 是大脑,不是手脚。
早期认知和实际角色的差异可以这样对比:
| 维度 | 早期认知 | 实际角色 |
|---|---|---|
| 核心能力 | 文本生成 | 语义理解、决策规划、工具调度 |
| 调用方式 | 单次 prompt 得到文本 | 多轮调用,与检索、工具、状态机配合 |
| 评价标准 | 句子是否通顺 | 任务是否完成、成本是否可控、结果是否可验证 |
| 工程重心 | 提示词 | RAG、Agent、MCP、精度、缓存、评测 |
| 失败表现 | 输出变差 | 检索不到、工具调用失败、流程卡死、幻觉无引用 |
从这个角度看,真正重要的不是“模型能生成多好的文字”,而是“模型能不能可靠地驱动外层系统完成一个闭环任务”。这也解释了为什么 2024 年开始,RAG、Agent、MCP、编排框架这些关键词逐渐取代“prompt”成为 LLM 应用开发的核心话题。
如果你正在做 LLM 相关项目,最应该读这篇文章的原因就在这里:先搞清模型在系统里的位置,再决定模型选型、数据方案和框架选择,否则很容易在模型效果上投入大量精力,最后发现系统根本跑不通。
2. 三种真实角色:生成器、理解器与决策器
把 LLM 放进业务系统,一般会承担三种角色。很多团队以为“只要用了 LLM 就是 AI 应用”,实际上不同角色对架构、数据、评测的要求完全不同。
2.1 内容生成器
这是最基础的角色,也是大多数人熟悉的能力。输入一段描述,模型输出文本、代码、文案或对话内容。典型的应用包括:自动生成周报、生成 SQL、写营销文案、写代码注释。
在这个角色里,衡量标准很简单:生成内容是否符合预期。工程上要处理的是输出格式、长度控制、敏感词过滤、内容审核。没有复杂的链路,单次调用即可完成。
2.2 语义理解器
这个角色不生成内容,而是对输入做结构化处理。典型场景是:意图分类、实体抽取、文本向量化、情感分析、关键词提取。
这类任务在传统 NLP 里需要训练单独的模型,现在用 LLM 可以直接完成,而且泛化能力强很多。但它和“生成器”最大的区别是:你需要对输出做严格的格式约束,比如让模型输出 JSON,再用代码解析,而不是让模型“随便说说”。工程上要注意 prompt 里明确输出格式,同时做好解析失败的重试机制。
2.3 决策推理器
这是最复杂也最接近“Agent”的角色。模型不再是“回答一个问题”,而是“根据目标做规划,决定下一步调用什么工具,并汇总结果”。比如:用户说“帮我查一下订单物流,如果延迟就发一封解释邮件”,系统里的 LLM 要先判断要调用订单查询工具,再判断物流状态是否异常,再决定是否触发邮件工具,最后生成邮件内容。
这个角色里,LLM 的输出不再是最终答案,而是“下一步动作”。它可能输出一个结构化指令:{"tool": "query_order", "args": {"order_id": "12345"}},然后由外层系统执行。这个转变极其关键——当你把 LLM 的输出从“最终文本”改成“动作指令”时,LLM 就从生成器变成了推理引擎。
大多数生产项目会同时用到这三种角色。架构设计时必须区分清楚:哪个环节用 LLM 做生成,哪个环节用 LLM 做理解,哪个环节用 LLM 做决策。如果混在一起,会导致 prompt 越来越复杂,效果越来越不稳定,最后无从排错。
3. RAG:为什么私有知识接入不是“把文档塞进 Prompt”
RAG(Retrieval-Augmented Generation,检索增强生成)是 LLM 应用里最常见的工程模式。它的目的是让模型能利用模型权重之外的私有知识和最新信息。
3.1 为什么需要 RAG
很多团队刚接触 LLM 时都会提一个问题:直接把公司文档贴到 prompt 里不就行了吗?早期确实有人这么做,但很快遇到三个问题:
- 上下文长度有限,文档一多就超限;
- 把大量无关内容塞进 prompt,会稀释注意力,生成质量下降;
- 每次调用都发送全量文档,成本指数级上升,延迟也高。
RAG 的思路是:不把所有资料塞进 prompt,而是先检索出和问题最相关的片段,再把这些片段作为上下文交给模型。整个过程是:文档切分 → 向量化 → 存储 → 检索 → 重排 → 拼装 prompt → 生成。
3.2 最简 RAG 流程代码示意
下面是最小实现的伪代码,重点演示流程,具体的向量库和模型接口以你使用的框架版本为准:
# 最简 RAG 流程示意(伪代码,API 以实际框架为准) def build_index(documents, embedding_fn): chunks = [] for doc in documents: # 按固定长度切分,建议保留 20% 重叠,避免语义断裂 chunks.extend(split_text(doc, chunk_size=512, overlap=100)) vectors = [embedding_fn(chunk) for chunk in chunks] return chunks, vectors def query_rag(question, chunks, vectors, embedding_fn, llm_fn): # 1. 问题向量化 q_vec = embedding_fn(question) # 2. 向量相似度检索,取 top_k hits = search_similar(q_vec, vectors, top_k=5) # 3. 拼接检索片段作为上下文 context = "\n\n".join([chunks[i] for i in hits]) # 4. LLM 基于上下文生成答案 prompt = f"""请基于以下资料回答问题。 如果资料中没有相关信息,请明确说明“未找到相关资料”,不要编造。 资料: {context} 问题: {question} """ return llm_fn(prompt)这个流程里真正的难点有三个:切分策略、检索质量、引用溯源。
切分策略直接影响检索效果。按固定字符切分实现简单,但容易把一句话切开;按段落和章节切分更语义化,但实现复杂。实际项目里通常先用 Markdown 标题或文档结构切分,再配合固定窗口补一块重叠区域。
检索质量决定了 RAG 效果的底线。向量检索对语义相近的内容有效,但对“关键词完全不同的同义表达”退化严重,所以很多生产级系统会加一层重排(Rerank),用交叉编码器对检索结果再做一轮精排。
引用溯源是 RAG 应用上线前必须做的。模型生成的答案必须能对应到具体文档片段,否则用户无法判断结果是否可靠。这里特别要提醒:如果你的向量化 API 没有配置好,比如 embedding 服务地址或 Key 缺失,整个 RAG 链路会直接失败,这也是热搜词里“LLM文本向量API未配置”的典型问题,排查时先看配置中心里 embedding 服务相关配置是否完整。
3.3 小结
RAG 解决的核心问题是“让模型知道它不知道的事”,但它的工程难点在检索和数据处理,不在模型本身。如果检索质量差,换再强的模型也没用。
4. Agent:当 LLM 从“回答问题”变成“完成任务”
Agent 是 LLM 角色转变最明显的场景。如果说 RAG 还是在“生成答案”,Agent 则是在“执行任务”。两者的差异,可以从一个真实需求体会:
- RAG 模式的问题:“我们公司的年假制度是什么?”
- Agent 模式的问题:“帮我查一下小王今年还有几天年假,如果不足 5 天,提醒他尽快安排休假。”
第二个问题无法靠检索直接回答,它需要调用人事系统接口查询小王剩余年假,再比较是否少于 5 天,然后生成一个提醒消息发送出去。这个流程里,LLM 承担的是“规划 + 决策 + 生成”的组合工作。
4.1 工具调用是 Agent 的基石
Agent 能执行任务的前提,是 LLM 具备工具调用(Function Calling / Tool Use)能力。模型输出不再是“纯文本”,而是“应该调用哪个工具、参数是什么”。下面是一个工具定义的通用 JSON Schema 示例:
{ "name": "query_annual_leave", "description": "查询指定员工的年假剩余天数", "parameters": { "type": "object", "properties": { "employee_id": { "type": "string", "description": "员工 ID" } }, "required": ["employee_id"] } }配合这个工具定义,Agent 的调用循环大致如下:
# Agent 工具调用循环(伪代码,API 以实际框架为准) tools = [query_annual_leave_schema] messages = [{"role": "user", "content": "查一下小王还有几天年假"}] while True: response = llm_chat(messages, tools=tools) if response.tool_calls: # 模型决定调用工具,则执行工具 for call in response.tool_calls: result = execute_tool(call.name, call.arguments) messages.append({ "role": "tool", "tool_call_id": call.id, "content": result }) # 把工具结果回传给模型,让它继续决策 continue else: # 模型直接输出最终答案 print(response.content) break这个循环逻辑本身很简单,但生产环境的 Agent 比这个复杂得多。比如:工具执行超时怎么办?工具返回格式不符合模型预期怎么办?多步调用后上下文太长怎么办?某个工具连续失败,要不要重新规划?这些都需要外层编排系统兜底。
4.2 Agent 的边界
从实操经验看,Agent 适合用在“工具边界清晰、步骤可预期、失败成本可控”的场景。比如内部工单处理、订单查询、代码仓库操作、网页抓取等。相反,如果任务本身没有明确工具支撑,或者模型需要自由发挥的地方太多,Agent 很容易陷入死循环或产生不可控行为。这也是为什么很多项目一开始只做 RAG,跑通后才逐步引入 Agent。
5. MCP 与编排框架:为什么单次调用撑不起复杂应用
如果你做过 Agent 项目,一定遇到过同一个烦恼:每个工具都要单独写对接逻辑,模型服务、向量库、外部 API、内部系统之间耦合严重。这种问题不是模型能力能解决的,而是架构问题。MCP(Model Context Protocol)就是为了解决工具接入标准化而出现的。
5.1 MCP 解决什么问题
在 MCP 出现之前,给 LLM 接一个网页抓取工具,你需要自己实现:工具定义、HTTP 请求封装、超时重试、错误格式化、权限校验。第二个工具要再写一套。工具一多,维护成本迅速膨胀。
MCP 的思路是统一协议。模型服务通过 MCP Client 连接 MCP Server,每个 Server 暴露一组标准化的工具或数据源。客户端负责发现工具、调用工具、接收结果,服务端负责具体执行。这样一来,LLM 应用与外部系统的连接被抽象成一整套标准协议,换工具、加工具都不需要改核心链路。
一个很常见的落地场景就是热搜词里的“实现 MCP Client 与 LLM 连接,实现抓取网页内容功能”。你只需要让工具层暴露一个fetch_url的 MCP Server,LLM 在需要网页内容时自动调用它,整个流程就可以闭环。
5.2 为什么需要编排框架
单次调用和复杂应用之间,还隔着一层“流程控制”。编排框架要解决的是:任务状态怎么保存、多步调用怎么路由、失败怎么重试、上下文怎么管理、权限怎么校验。
| 直接调用 LLM | 引入 MCP + 编排框架 |
|---|---|
| 无状态,每次调用互相独立 | 有任务状态,支持多步流程 |
| 工具逻辑散落在业务代码里 | 工具通过标准协议接入,可复用 |
| 失败处理靠业务代码自己写 | 框架统一处理超时、重试、回退 |
| 上下文管理靠手工拼 prompt | 框架自动管理消息历史与窗口 |
| 难以追踪一次任务的完整链路 | 有调用日志、追踪和评测入口 |
这就是“LLM 应用为什么需要编排框架”的根本原因:当 LLM 从“单次问答”变成“业务闭环”,你需要的是像传统后端那样,把状态、异常、日志、权限管好,而不是每次调用都从零开始拼接。
需要注意,编排框架不等于某个特定产品。你可以选择成熟的框架,也可以用状态机自己写一套轻量编排。关键是明确一点:复杂 LLM 应用的工程量,更多在模型之外。
6. 从零落地一个最小 LLM 应用:推荐架构与前置条件
前面讲了不少概念,这一节给出可操作的最小方案。如果你正准备做第一个 LLM 应用,建议从“单轮 RAG + 一个工具调用”开始,不要一上来就搭复杂 Agent。
6.1 环境与前置条件
搭建最小 LLM 应用,至少需要以下组件:
- 模型服务:在线 API,或本地推理服务。本地推理需要 GPU,模型精度建议用 BF16 或 FP16。
- 向量数据库:用于存储文档向量和执行相似度检索。可选方案有 Milvus、Qdrant、Chroma,也可以直接使用云服务。
- embedding 服务:把文本转为向量,独立于 LLM 服务的 API。
- 编排框架:管理多步调用和工具调度。
- 一个可测试的业务场景:例如“根据内部知识库回答员工政策问题”或“抓取网页并总结”。
版本方面不写死具体数字,以你选型时各框架最新稳定版为准。重点是先把链路跑通,再优化效果。
6.2 最小架构
整个应用的调用关系可以用一句话描述:用户请求先进入编排层,编排层判断是否需要检索,再调用 LLM 推理,如果模型决定调用工具,则通过 MCP/工具层执行并回填结果,最后汇总输出。
拆成模块是:
- 编排层:接收请求,维护消息状态,调度各组件。
- 检索层:连接向量库,对用户问题做 embedding 检索,返回相关文档片段。
- 推理层:调用 LLM,支持普通对话、工具调用、结构化输出。
- 工具层:通过 HTTP 或 MCP 暴露外部能力,比如查询订单、抓取网页、调用内部 API。
- 数据层:管理文档切分、向量化、索引更新。
6.3 完整示例:最小 RAG + 工具调用应用
下面这段代码把 RAG 和工具调用整合到一个简单流程里,使用通用伪代码风格,方便你迁移到不同语言和框架:
# 最小 LLM 应用:RAG + 工具调用示意(伪代码) class LLMApp: def __init__(self, llm_fn, embedding_fn, vector_store, tools): self.llm = llm_fn self.embedding = embedding_fn self.store = vector_store self.tools = tools def retrieve(self, question, top_k=3): q_vec = self.embedding(question) return self.store.search(q_vec, top_k) def run(self, user_message): messages = [{"role": "user", "content": user_message}] # 第一轮:先让模型判断是否需要检索或调用工具 resp = self.llm(messages, tools=self.tools) if resp.need_search: contexts = self.retrieve(user_message) messages.append({ "role": "user", "content": f"补充资料:{contexts}" }) while True: resp = self.llm(messages, tools=self.tools) if resp.tool_calls: for call in resp.tool_calls: result = self.tools[call.name](**call.arguments) messages.append({ "role": "tool", "tool_call_id": call.id, "content": result }) continue return resp.content这段代码的价值不在功能完整,而在让你看到 LLM 应用的基本骨架:LLM 负责判断、检索负责知识、工具负责执行、消息列表负责记忆。所有真实项目都是在这个骨架上不断增加细节。
7. 推理精度与部署成本:FP16、BF16、FP32 怎么选
做到本地部署时,一定会遇到模型精度问题。热搜词里“LLM 大模型之精度问题(FP16、FP32、BF16)详解与实践”被反复搜索,说明这是很多开发者踩过坑的地方。
7.1 为什么精度问题重要
模型权重占用的显存,直接决定你能不能跑起来。以 7B 模型为例,FP32 权重约 28GB,FP16 约 14GB,BF16 也是 14GB,4bit 量化后约 4GB。显存不够,模型只能降精度或换小模型;精度选择不当,模型效果可能明显下降,甚至训练时直接数值溢出。
7.2 三种常见精度对比
| 精度 | 占用显存 | 数值范围 | 稳定性 | 典型用途 |
|---|---|---|---|---|
| FP32 | 最大 | 大 | 最稳定 | 基准测试、少量算子 |
| FP16 | 中 | 较小,容易溢出 | 一般 | 老显卡推理、部分训练场景 |
| BF16 | 中 | 与 FP32 一致 | 稳定 | 大模型训练和推理的主流选择 |
FP16 的问题在于数值范围小。训练时梯度很小,容易低于 FP16 能表示的最小值,导致下溢出变成 0;反过来,数值太大又可能上溢出变成 inf。BF16 牺牲了尾数精度,但保留了与 FP32 相同的指数范围,所以对大模型训练更友好。
7.3 实践建议
本地推理时,如果不是特殊算子兼容性问题,优先选择 BF16。如果你的 GPU 架构较老,对 BF16 支持不好,只能用 FP16,则要注意 loss 不稳定或输出异常的问题,此时可以考虑混合精度训练或在关键计算环节用 FP32 做累加。
部署大模型时,建议先用 FP16 或 BF16 跑通功能,验证效果后再决定是否量化到 8bit 或 4bit 以降低显存和成本。生产环境里,效果、显存、延迟三者不可兼得,模型路由是最实用的折中:简单问题用小模型,复杂问题用大模型。
当前主流推理框架一般都支持精度参数配置,比如在加载模型时指定torch_dtype=torch.bfloat16,或者通过启动参数指定量化方式。具体参数项以你选型的框架文档为准,重点是先确认硬件支持,再定精度。
8. 常见问题与排查思路
LLM 应用开发和传统后端最大的区别是:问题不一定稳定复现。同一个 prompt 这次成功,下次可能失败。下面整理了几个高频问题,按排查顺序排列。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 生成的答案与资料无关 | RAG 检索到的片段质量差 | 打印检索命中结果,人工检查相关性 | 优化切分策略,增加重排,调整 top_k |
| 提示“未找到相关资料”但资料库确实有内容 | 向量化服务配置错误或 embedding API 不可用 | 检查配置中心的向量化 API 地址和 Key | 修复配置,单独调用 embedding 接口验证 |
| Agent 调用工具失败 | 工具超时、参数格式不符合预期 | 查看工具服务日志,检查模型输出参数 | 统一工具异常返回格式,增加超时和重试 |
| 本地推理显存不足 | 模型精度过高或上下文太长 | 查看 GPU 显存占用,检查加载精度 | 切换 BF16/FP16,或缩短上下文,必要时量化 |
| 同一段代码跑了两次结果不同 | 温度参数高,prompt 或上下文不一致 | 对比两次调用参数,检查上下文拼接 | 固定温度,做 prompt 版本管理,加评测集 |
| 本地部署的模型和线上模型效果差异大 | 精度、推理框架、采样参数不同 | 对比模型版本和推理配置 | 统一精度和采样参数,以推理框架文档为准 |
| 多步骤流程卡在某一步不结束 | Agent 死循环或工具持续失败 | 查看编排框架的调用日志和状态 | 设置最大调用轮数,失败回退,增加人工介入 |
| ComfyUI 和 LLM 是否必须在同一台电脑 | 这是部署架构问题,不是必须同机 | 评估网络延迟和数据传输量 | 可以分机部署,注意密钥管理和接口鉴权 |
这里的核心经验是:LLM 应用出问题时,不要只盯着模型,先检查上下文、工具结果和配置。多数失败发生在模型之外。
9. 最佳实践与工程建议
从“跑通 Demo”到“上线生产”,中间隔着大量工程细节。下面这几条建议来自实际项目中最容易踩的坑,按重要性排序。
9.1 认知:LLM 是推理引擎,不是知识库
模型参数里固化的知识是静态的、过时的,而且幻觉问题无法根治。私有知识必须走 RAG,动态信息必须走工具调用,复杂决策才交给模型规划。这个认知能帮你少走很多弯路。
9.2 数据:把 RAG 当数据工程来做
RAG 的成败在数据质量。上线前要梳理文档结构,设计切分策略,建立索引更新机制。文档更新后向量索引如何同步、删除文档如何处理、版本如何回溯,这些都要提前规划。
9.3 工程:记录每一次调用,建立评测集
LLM 应用的调试非常依赖日志。每次调用要记录输入、输出、检索命中、工具调用、耗时和成本。同时准备一个固定的评测集,每次改 prompt、换模型、调参数,都跑一遍评测集,防止效果回退。没有评测集的 LLM 项目,优化等于碰运气。
9.4 成本:缓存 + 路由 + 批量
相同问题的答案可以缓存,减少重复调用;简单问题路由到小模型,复杂问题才用大模型;非实时场景用批量处理,降低 API 调用开销。
9.5 安全:最小权限 + 工具白名单 + 审计
LLM 应用特别要注意安全边界。外部工具调用要白名单化,内部系统接口要鉴权,prompt 内容要防注入,敏感数据要脱敏,所有调用要可审计。涉及生产环境变更时,坚持最小权限原则,先在测试环境验证。
9.6 团队:LLM 应用开发是后端工程
不要迷信“写几个 prompt 就是 AI 应用”。一个合格的生产级 LLM 应用,需要后端、数据、运维协同。团队里最好有人懂检索、有人懂模型部署、有人懂业务系统集成,而不是只靠一个人调 prompt。
10. 总结与后续学习方向
回到开头的问题:关于 LLM 将扮演的角色,最初的判断错在哪里?错在把 LLM 当成了产品本身,而实际上它是新一代应用架构里的推理基础设施。真正值得投入精力的方向,是模型之外的工程系统:RAG 的检索质量、Agent 的工具调度、MCP 的标准化接入、编排框架的状态管理、低精度推理的稳定性。
如果你刚接触 LLM 应用开发,建议按这个顺序动手实践:
- 先完成一个最小 RAG:文档切分、向量检索、Prompt 生成,跑通后打印检索结果,理解检索与生成的关系。
- 再加一个工具调用:让模型通过 Function Calling 查询某个接口,完成“提问 → 调用 → 汇总”的闭环。
- 然后引入 MCP:把工具改造成标准协议接入,理解客户端与服务端如何连接。
- 最后考虑编排框架:把多步任务、异常处理、日志追踪纳入统一管理。
网上也能找到不少不错的资料方向,比如 Andrej Karpathy 提出的 “LLM wiki” 范式,强调用工程化手段组织和管理 LLM 知识边界;但更重要的还是动手把链路跑通,自己感受一次“模型答错 → 排查 → 发现是检索问题”的过程。
不要指望一个模型解决所有问题,也不要觉得提示词能解决所有问题。LLM 应用开发是一场数据、模型、工程三者的合力游戏,把模型放在正确的位置上,它才会成为真正的引擎。