我在很长一段时间里,对 LLM 的判断可能是错的。最早接触大语言模型时,我把它误当成“更聪明的搜索框”:输入问题,返回一段看起来像人写的答案,最多再结合上下文做点润色。真正开始做应用之后才发现,这个定位完全支撑不起现实需求。你让 LLM 做知识库问答,它会把错误信息说得理直气壮;你让它按固定格式输出,它偶尔会多出几个莫名其妙的字段;你希望它去调用外部工具,它根本没有手。更关键的是,当 Agent、RAG、MCP 这类词开始频繁出现在项目里时,我才意识到 LLM 在真实系统里扮演的从来不是“最终答案生成器”,而更像一个调度枢纽、一个决策内核、一个能把工具、知识库和后续动作串起来的中间层。
这轮认知更新,是过去大半年里对我工作方式影响最大的一件事。这篇文章不想复述概念,我想按实际做项目的顺序,把 LLM 角色变化的起因、落地步骤、参数选择、部署边界和排查思路完整拆一遍。尤其是最近讨论比较多的 LLM wiki 范式、Agent 编排、Spring AI 这类框架组合,我会把它们放回真实工程场景里,说清楚它们到底解决了什么问题,哪些地方容易被高估,哪些坑我确实踩过。
1. 我之前的理解,为什么会错
1.1 我最初把 LLM 当成“答案生成器”
早期做产品原型时,我的思路很单一:把用户问题拼进 prompt,调一次 LLM,拿到文本后直接展示。那种模式在聊天机器人、文章总结、翻译工具里确实能跑通。只要 prompt 写得好,输出质量就够看。
但这个思路放在复杂业务里会出现明显断层。最典型的场景是私有知识库问答。我一开始以为只需要把相关文档片段塞进上下文,让 LLM“读”完再回答。可问题很快暴露:文档一多,上下文塞不下;即使能塞下,模型也会把相似但无关的内容当成依据;如果用户问到表格里的数据,模型经常自己“推算出”一个看起来合理但完全不存在的数字。
那时候我得出一个错误结论:LLM 还不够强,所以要等更强的模型。后来才发现,问题不在模型强弱,而在角色定位。我把它当成一个必须自己知道所有答案的“全知者”,但现实里它不仅不知道,还会自信地编。正确做法应该是让它当一个“会查资料、会调用工具、会按规则输出的工作人员”,而不是让它硬背所有知识。
1.2 现实情况:LLM 更像调度器,而不是知识容器
真正改变我理解的是一个很朴素的项目经验。当时要做一套客服工单分类系统,最初我让 LLM 直接读工单全文,然后输出分类标签。效果不稳定,标签偶尔重叠,偶尔漏掉,格式也不统一。
后来我换了一个思路:先让 LLM 看工单摘要,再从预设的分类列表里选一个,最后只输出一个 JSON 对象,里面包含分类编号和置信度。这个改动看起来只是输出格式变了,实际上是角色变了。LLM 不再负责“理解所有内容并给出最终答案”,而是负责“理解当前情况,在受限选项里做决策”。准确率立刻稳定很多,后续接 RAG、接工具调用也顺理成章。
这也是我后来理解 Karpathy 提出的 LLM wiki 范式时最有共鸣的地方。LLM wiki 的核心不是让模型把海量知识装进参数里,而是把知识拆解成结构化的文档、卡片或条目,再由 LLM 去检索、组织、调度。知识放在外部系统里,LLM 负责定位和组合。这样一来,模型的“记忆负担”大幅下降,幻觉的空间也会被压缩,因为每一次回答都能落到一个可追溯的条目上。
所以,我错在把 LLM 当成了知识容器。实际上它更接近操作系统的调度器:接收目标,拆解步骤,决定该查什么、该调什么、该生成什么,最后把各模块的结果拼装成用户需要的样子。
2. 真实项目里 LLM 扮演的五个角色
2.1 五种角色的典型技术组合
当我重新盘点做过和接触过的项目后,发现 LLM 在真实系统里基本能归成五类角色。不同角色对模型能力、参数配置、外部依赖的要求都不一样。
| 角色 | 解决什么问题 | 典型技术组合 | 判断标准 |
|---|---|---|---|
| 文本生成器 | 摘要、翻译、营销文案、报告起草 | prompt + 基础模型 | 输出可读、无明显事实错误、风格稳定 |
| 知识路由 | 私有知识库问答、资料检索 | RAG + 向量库 + 嵌入模型 | 召回内容准确、回答有引用来源 |
| 工具调度器 | 查询数据、调用接口、执行操作 | Function Calling / MCP + Agent | 工具名正确、参数完整、执行结果可回填 |
| 代码助手 | 代码补全、脚本生成、报错解释 | 代码模型 + IDE 插件 | 代码可运行、注释合理、不破坏上下文 |
| 内容组织者 | 资料整理、文档建联、知识卡片生成 | LLM wiki / 结构化输出 | 条目可定位、链接可跳转、结构一致 |
先说文本生成器。这是最容易上手,也是定义最窄的角色。判断它做得好不好,不看模型参数大不大,而看输出能不能直接用。我一般会把输出要求写得极其具体,包括字数、语气、是否允许 Markdown、是否需要列表,否则后续处理会很痛苦。
知识路由是目前最常见的企业应用方向。技术上绕不开 RAG:先把文档切块,做嵌入,存进向量库;用户提问时先检索相似内容,再带着检索结果让 LLM 作答。这里最容易忽略的不是模型,而是嵌入模型和向量库的配置。很多项目报错“文本向量 API 未配置”,就是因为只配了对话模型,没配 embedding 模型,或者向量库索引没有建。
工具调度器是 Agent 的核心。LLM 根据用户意图决定调用哪个工具、传入哪些参数,再把工具返回的结果整理成最终回答。现在很多框架支持 MCP(Model Context Protocol),本质就是让 LLM 和外部工具之间有一套标准化接口。这个角色的难点不在“让 LLM 说人话”,而在“让 LLM 准确表达动作”。
代码助手和内容组织者,我放在一起说。代码助手我已经不指望它能直接写出一整系统,但它很适合做单函数生成、报错解释、重构建议。内容组织者是我最近很看好的方向,典型做法就是把零散笔记交给 LLM 提炼成结构化条目,再按主题建索引,形成个人 wiki。这类项目不需要 GPU 集群,甚至用 API 就够了,但对输出稳定性要求很高。
2.2 角色不同,参数和验收标准也要变
很多新手拿到一个 LLM 项目,第一件事就是调温度(temperature),但角色不同,参数的优先级完全不同。
只做文本生成时,温度可以适当高一点,比如 0.7 到 0.9,让句子更有变化。但做工具调度时,温度必须降得很低,我一般会控制在 0 到 0.2。因为工具调度不需要创造性,需要稳定和可复现。如果温度太高,同一个用户问题可能第一次调用“查询订单”,第二次调用“删除订单”,这在业务里是不可接受的。
max_tokens 也要注意。如果只把 LLM 当调度器,输出本来就很短,像一段 JSON 或一个工具名,那就没必要把 max_tokens 设成 4096。设得太大反而可能让模型输出冗余内容,增加解析失败的概率。做 RAG 时,还要额外关注上下文长度和召回数量,不是召回越多越好,召回太多反而会把无关信息喂给模型。
验收标准也要换。判断一个对话模型好不好,可以看回答是否通顺;但判断一个工具调度模型好不好,要看输出能否被程序解析、工具名是否正确、参数是否完整、失败后能不能重试。换句话说,文本生成的验收标准是“人看着舒服”,工具调度的验收标准是“程序能跑通”。
3. 从“问答接口”到“工具路由器”的改造步骤
3.1 最小可运行流程
想把 LLM 从文本生成器改造成工具路由器,不需要一上来就搭全套 Agent 框架。我建议先跑一个最小流程,验证三件事:模型支持不稳定输出、程序能解析输出、解析后的动作能执行。
下面是一个示意流程。假设用户要求抓取一个网页并总结成要点,我们希望 LLM 不要直接假装抓到了网页,而是先返回一个“抓取网页”的动作。
# 示意代码,依赖以实际项目为准 messages = [ {"role": "system", "content": "你是任务调度器。请从工具列表中选择合适的工具,并返回 JSON。"}, {"role": "user", "content": "把 https://example.com 的内容抓取下来,总结成三条要点"} ] resp = llm_client.chat(messages) action = json.loads(resp.content) # action 可能是这样的结构: # {"tool": "fetch_url", "params": {"url": "https://example.com"}, "next": "summarize"} print(action)这段代码的重点不是具体调用哪个 SDK,而是让 LLM 输出一个“动作描述”,不是输出最终答案。拿到动作之后,程序再去执行真正的网页抓取。抓取完成,再把网页文本塞回给 LLM,让它做总结。这样 LLM 就不需要假装知道网页内容,只需要完成“决策抓取动作”和“总结已有文本”这两件事。
如果你用的模型支持 Function Calling,那更好,可以直接按官方协议声明工具。如果不支持,就在 system prompt 里给出工具列表、参数说明和 JSON 样例。只要模型的输出足够稳定,这种“提示词约束”的方式也能跑通。但要注意,如果模型没有经过工具调用训练,即使提示词写得再细,也可能输出非法 JSON。遇到这种情况,先换一个支持函数调用的模型,比反复调 prompt 更有效。
3.2 单条任务怎么验证
我把首次接入工具调用的验证拆成了四步,每步都有明确的成功标准。
第一步,验证基础对话。给模型发一个最简单的消息,确认 API Key、模型名称、网络连通性都正常。这一步如果失败,后面所有工作都白搭。
第二步,验证 JSON 输出。让模型生成一段固定结构的 JSON,比如包含“tool”和“params”两个字段。成功标准是json.loads能直接解析,不需要人工修正。
第三步,验证工具执行。从模型返回的 JSON 中取出工具名和参数,在真实环境里调用一次。比如调用抓取网页接口,确认能拿到 HTTP 响应和正文内容。
第四步,验证结果回填。把工具执行结果传回给模型,让模型基于真实结果生成最终答案。这一步能有效降低幻觉,因为模型不再需要“猜”网页内容。
这四步全部跑通后,才算完成了一个最基础的工具路由闭环。我通常会把这个闭环写成一个函数,后续所有任务都走同一个入口。
3.3 为什么不建议直接上并发
很多人在演示环境里跑通一次,马上就想上并发。我的建议是别急。工具路由和普通文本生成的差异在于:普通文本生成失败,最多就是回答烂一点;工具路由失败,可能真的会误调接口、误传参数、误删数据。
先跑单条任务,观察完整链路的时间消耗。然后再加一条带特殊字符的输入,确认 JSON 解析不会崩。再试试工具返回异常时,模型能不能感知到。比如网页抓取返回 404,模型应该告诉用户“页面不存在”,而不是假装抓取成功。这一步不做,并发越高,错误越规模化。
3.4 接入 MCP 和 RAG 的顺序
如果项目需要更复杂的工具生态,可以考虑 MCP。它把工具描述和调用方式标准化,让不同的 LLM 应用可以共用同一套工具服务。我的接入顺序一般是:先手工定义工具函数,跑通路由闭环;再把这套工具改成 MCP Server;最后再让应用通过 MCP Client 连接。这样一旦出问题,你能判断是业务逻辑问题,还是协议实现问题。
RAG 的接入顺序则可以放在工具路由之后。先用 LLM 判断用户问题是否需要查资料,如果需要,再调用检索工具,把召回的文本交给 LLM 生成答案。把 RAG 看成一个“外部工具”,会让架构清晰很多,也不会每轮对话都触发向量检索,节省成本和时间。
4. 精度、部署与硬件边界
4.1 精度选择:fp16、fp32、bf16 怎么理解
本地跑模型时,绕不开精度问题。很多资料里看到 fp16、fp32、bf16,容易被绕晕。简单说,它们代表模型参数保存时的数值类型,不同精度会直接影响显存占用、计算速度和生成质量。
fp32 是单精度,信息保留最完整,但占用最大。fp16 是半精度,占用少一半,速度通常更快,但在某些数值范围上容易丢失细节。bf16 也用半精度,但指数范围和 fp32 更接近,所以在大模型场景里更常用,尤其适合训练和推理。很多人说 bf16 比 fp16 更稳,原因就在这里。
不过,具体使用哪种精度,要看你的硬件驱动和框架支不支持。有些显卡对 fp16 支持很好,对 bf16 支持一般。我的建议是先查两份资料:一是模型卡说明,二是推理框架的文档,不要只看网上结论。低精度不是“有损压缩”的绝对坏事,很多时候生成质量差别很小,但显存占用差距很大。如果显存不够,优先尝试 8 位或 4 位量化,而不是一直和精度死磕。
4.2 ComfyUI 与 LLM 要不要放同一台电脑
这是搜索里出现频率很高的问题,来自一个很实际的使用场景:一个人既要跑 ComfyUI 做图像生成,又要跑 LLM 做文本推理,但机器只有一张显卡,不知道该怎么分配。
答案很直接:不一定非要同一台电脑。ComfyUI 核心是图像生成模型,LLM 核心是文本生成模型,两者资源需求完全不同。如果你的显卡只有 8G 显存,同时在本地跑一个较大的图像模型和一个较大的 LLM,大概率会内存溢出或卡死。有两种常见解法。
第一种,错开任务。同一台机器,图像任务和文本任务不要同时跑。跑 ComfyUI 时,LLM 走远端 API;跑 LLM 时,先关掉 ComfyUI。这种方式适合学习和小规模使用。
第二种,分工。ComfyUI 放在有 NVIDIA 显卡的 Windows 或 Linux 机器,LLM 放在另一台 Mac 或云服务器。通过 API 调用连接,这样两边可以同时工作,互不干扰。需要注意的是,这种方案需要自己管理网络调用、API 地址和并发,不是零成本。
如果你的机器配置很高,比如 24G 以上显存,当然可以在同一台机器上跑 ComfyUI 和一个小参数 LLM。但无论如何,先看显存占满会怎样,再看任务类型。图像生成经常吃满显存,文本推理瞬时内存也高,两者叠加极不稳定。
4.3 Mac 本地推理引擎怎么选
Mac 用户想跑 LLM,通常会问选哪个推理引擎。这个问题不能一概而论,但判断标准是固定的:模型格式兼容性、是否支持 Metal、量化支持、上下文长度处理能力。
很多 Mac 本地推理工具本质是调 llama.cpp 或其分支,支持 GGUF 格式模型。只要把模型转成 GGUF 或用别人转好的 GGUF 文件,大多数都能跑。选择引擎时,我建议先看官方文档里是否明确提到自家芯片支持列表。苹果芯片的内存带宽很高,跑 LLM 有天然优势,但不能只看总内存。一个 32G 统一内存的 Mac,能跑的模型规模大约是 14B 到 34B 的量化版,具体要看模型结构、量化位数和上下文长度。
不要光看“推荐某某引擎”的结论。正确的做法是拿你计划部署的模型,分别用两个引擎跑同一条推理请求,对比三样东西:首 token 延迟、生成速度和内存占用。文本推理没有绝对的最优引擎,只有最适合你模型格式和硬件条件的引擎。
4.4 什么时候果断用 API
本地部署最大的优点是数据不出本机,离线也能用,长期不一定比 API 贵。但它也有明显代价:硬件维护、模型更新、依赖兼容、并发规划都要自己管。
如果只是做应用开发、拼业务原型,或者团队只有一两个人,不建议前期就投入大量精力做本地部署。先用成熟的模型 API 把流程跑通,成本更可控,速度也更快。等到你确认了模型类型、推理频率和业务指标,再评估是否要本地部署。很多团队一上来就想“私有化部署一个开源模型”,最后卡在显存不够、推理太慢、精度对不齐这些问题上,反而拖慢进度。
5. 新范式下最容易踩的坑和排查顺序
5.1 按现象排查
工具调用类和 RAG 类项目,常见问题是有共性的。我总结成一张排查表。
| 现象 | 优先排查方向 | 常见原因 |
|---|---|---|
| LLM 返回内容无法解析 | 先看输出格式 | 提示词没有给出明确的 JSON 示例,或模型不支持函数调用 |
| 工具调用总是选错 | 检查工具描述和参数命名 | 工具描述太模糊,或两个工具功能太接近 |
| 工具执行成功但答案不对 | 检查结果回填 | 只把工具结果放在 response 里,没有作为上下文传给模型 |
| RAG 召回内容为空 | 检查向量 API 和索引 | 嵌入模型未配置,或文档没有切块 |
| RAG 召回了但不相关 | 检查切块大小和检索 top_k | 切块太大语义被稀释,或 top_k 设置过多 |
| Agent 反复调用同一工具 | 检查终止条件 | 缺少最大轮数限制,或模型没有得到最终结果判断依据 |
| 本地推理内存不够 | 检查量化参数和上下文长度 | 模型位数太高,或上下文设得太大 |
5.2 Agent 与编排框架的边界
现在很多人一提到 LLM 应用,就想到 Agent。但 Agent 不是银弹。它适合的是需要多步决策、需要工具调用、需要把外部系统结果回传的任务。而简单的“问题转答案”场景,直接调用模型反而更稳定。
编排框架如 Spring AI、LangChain 这类工具,解决的核心问题有三件:屏蔽不同模型 API 的差异、管理多步任务的上下文、提供 RAG 和 Agent 的常用组件。项目里要不要上框架,取决于你要自己控制多少细节。如果你只是调用文本生成接口,框架是多余的。如果要把 Spring AI、MCP、RAG、Agent、Skill 组合到同一个业务系统里,框架能少写很多胶水代码,但也意味着你需要理解它的抽象方式、配置项和版本兼容。
我见过最多的问题,不是框架不会用,而是把框架当黑盒。项目跑起来后出现“输入了问题,但 Agent 没有任何动作”,排查半天才发现是某个配置项没打开,比如工具列表没注册,或者向量 API 的地址写错。所以,使用任何编排框架,第一件事是搞清楚它的关键配置项在哪里,尤其是模型地址、工具列表、向量库连接和超时时间。
5.3 从单条任务到批量任务的四道关
单条任务跑通后,不要直接写 for 循环批量跑。批量任务和单条任务是不同量级的问题。
第一关是输入归一化。批量数据里可能混入空值、超长文本、错误编码。在进入 LLM 之前,先做清洗和格式校验。否则某一条异常输入可能导致整批任务失败。
第二关是输出命名。每条任务结果要能对应回输入。建议在请求里带上业务 ID,并把业务 ID 放入日志和输出文件命名。不要只按时间戳命名,不然出错了根本不知道是哪条数据。
第三关是失败重试。LLM 接口可能因为限流、网络抖动、模型服务过载而报错。批量任务要有重试策略,比如重试 3 次,每次间隔递增。还要注意幂等性,同一个任务重试多次,不应该是重复执行多次工具操作,否则可能产生副作用。
第四关是并发控制。并发不是越大越好。开太大,接口会被限流,本地推理会内存不足。先测一条请求的延迟和资源消耗,再决定并发数。建议从小并发开始,观察稳定后逐步上调。不要一上来就开 32、64 并发,那几乎一定会翻车。
6. 最后一点反思
6.1 不再问“模型能生成什么”,而是问“模型能不能稳定执行动作”
这几天我在写新项目时,已经完全换了一套提问方式。以前我会问:“这个模型能写出高质量文章吗?”现在我更关心:“给它一个明确的动作列表,它能不能稳定输出对应的 JSON?如果第一次输出不对,重试一次能不能纠正?”
这个转变让 LLM 的定位落到了实处。它不再是产品里最显眼的“大脑”,而是整个流程中的一个处理节点。它负责理解、决策和调度,但真正执行动作的是业务代码、外部工具、数据库和知识库。你需要把它当做一个能力边界清晰、输出可能不稳定的组件来设计。所有关键路径都要有校验、重试和兜底。
如果你问我未来 LLM 会扮演什么角色,我现在的答案是:它会越来越像系统里的“统一交互层”。用户不需要记住工具叫什么,不需要自己拼参数,只要描述目标,LLM 负责把它翻译成动作序列,再交给后端执行。至于执行结果,再由 LLM 整理回人类可读的样子。这个模式对文本、代码、图像、音频都成立。
6.2 给新手的项目启动顺序
如果你正准备把一个 LLM 想法变成项目,我建议按照下面的顺序走,能少踩很多坑。
第一,先定义 LLM 的角色。它在这个项目里是文本生成器、知识路由、工具调度器,还是内容组织者?角色定义不清楚,后面所有参数和架构都会乱。
第二,先跑最小闭环。用最简单的本地脚本,调一次模型,拿到结果,完成一个判断或动作。不要一开始就接 Multi-Agent,不要同时配三个框架。
第三,再补外部依赖。需要知识库就接 RAG,需要工具就接 Function Calling 或 MCP,需要长流程再加编排框架。每加一个依赖,都要单独验证。
第四,最后再讨论部署和精度。本地还是 API、fp16 还是量化、要不要两台电脑,都放到业务逻辑基本稳定之后再看。过早优化部署,只会分散注意力。
我在这个过程中最大的教训就是:不要用“模型能力”来掩盖“架构问题”。很多时候,任务做不好不是因为模型太笨,而是因为把不该给模型的任务交给了它。LLM 最擅长的是理解意图、做取舍、组织语言和决策,而不是替你把所有业务逻辑都记住。把这个边界想清楚,项目推进会稳很多。
踩过几次坑之后,我最大的变化是:不再纠结“这个模型聪明不聪明”,而是先问“这个任务到底应该由谁负责”。LLM 的作用,是让越来越多的事情变得不再需要人肉翻译和手工调度。但在那之前,我们得先把它放对位置。