1. 企业级 LLM 落地,为什么“能跑通 Demo”和“能上生产”是两回事
企业级 LLM 这个系列写到第九篇,我越来越确信一件事:真正卡住团队的从来不是模型本身,而是模型外面那一圈工程。你可能已经用几行代码调通了某个大模型的接口,也搭过一个能问答的 RAG 小样,但当这套东西要接进公司真实业务、要面对成百上千的并发、要保证数据不出内网、要能追溯每一次回答的来源时,之前那套玩具式的写法会瞬间崩塌。
这篇要聊的,就是把 LLM 从“个人玩具”变成“企业级基础设施”的完整思路。核心关键词绕不开几个:LLM 网关、RAG 与 GraphRAG、LLM Wiki 知识库、企业级 Agent 平台、本地化部署与 ONNX 推理。适合谁看?如果你是把大模型往公司系统里塞的工程师、架构师,或者是正在做企业知识库、智能问答、数据 Agent 的产品负责人,这篇基本能帮你把坑提前踩一遍。
我先给一个判断:企业级 LLM 的本质,是一个受控的、可观测的、可替换的推理与编排系统。模型只是其中一个零件,而且是最容易被替换的那个零件。今天用 A 家的模型,明天可能因为成本或合规换成 B 家,甚至换成自己微调的开源模型。如果你的系统架构把模型焊死在业务逻辑里,那每次换模型都是一次重构灾难。所以整篇文章的主线,就是围绕“如何让模型可替换、让知识可管理、让调用可治理”这三件事展开。
2. 企业级 LLM 的整体架构设计与选型逻辑
2.1 为什么必须要有 LLM 网关这一层
很多人第一次听到“LLM 网关”会觉得是多此一举——我直接调模型 API 不就行了?我试过在早期项目里省掉这一层,结果很快就吃到苦头。当时业务代码里散落着十几处直接调用模型的逻辑,每处的超时设置、重试策略、密钥管理都不一样。后来要做成本统计,发现根本没法统一采集 token 消耗;要做限流,发现得改十几个地方;要换模型供应商,发现改动面大到不敢动。
LLM 网关要解决的就是这些横切关注点。它本质上是一个反向代理加策略中心,所有对模型的请求都先经过它。它负责的事情包括:统一鉴权与密钥管理、请求路由与模型选择、限流与配额、重试与降级、token 计量与成本核算、请求日志与审计、敏感内容过滤。把这些能力收敛到一层,业务代码就只需要关心“我要问什么”,而不用关心“怎么问、问谁、问多少次”。
选型上,自研一个轻量网关并不难,核心就是一层 HTTP 代理加中间件。但如果团队没有精力维护,也可以考虑成熟的开源网关方案,重点看它是否支持多供应商适配、是否支持流式响应、是否支持按 key 或按租户做配额。这里有个经验:网关一定要支持流式(streaming)透传,否则前端打字机效果会失效,用户体验直接掉一个档次。
2.2 模型选型的三个维度:能力、成本、可控性
企业选模型不能只看榜单。Open LLM Leaderboard 这类公开榜单有参考价值,但它测的是通用能力,跟你的业务场景往往对不上。我一般从三个维度评估:
第一是能力匹配度。你的任务是中文长文本理解、代码生成、还是结构化抽取?不同模型擅长的方向差别很大。最靠谱的办法是拿你自己的真实业务数据做一个小规模评测集,几百条就够,跑一遍看准确率和幻觉率。
第二是成本结构。这里要算清楚输入 token 和输出 token 的单价差异,以及缓存命中能省多少。很多团队只盯着单价,忽略了输出 token 通常比输入贵好几倍,而 Agent 类应用输出往往很长,成本会失控。
第三是可控性。数据能不能出内网?模型能不能私有化部署?推理延迟能不能接受?如果业务涉及敏感数据,那基本只能走本地部署路线,这时候 ONNX 这类推理格式就派上用场了——它能把模型导出成跨框架的中间表示,配合推理引擎做量化加速,在自有硬件上跑出可接受的吞吐。
2.3 知识层:从朴素 RAG 到 GraphRAG 与 LLM Wiki
企业知识库是 LLM 落地最刚需的场景,但朴素 RAG 的问题很快会暴露:它只会做向量相似度检索,把最像的几段文本拼给模型,缺乏对实体关系的理解。当用户问“A 公司的供应商里,哪些同时给 B 项目供过货”这种需要多跳推理的问题时,向量检索基本抓瞎。
这就是 GraphRAG 和 LLM Wiki 思路的价值所在。GraphRAG 在向量检索之外构建了知识图谱,把实体和关系显式建模,检索时能沿着关系边做多跳遍历。而 LLM Wiki 这类项目(比如 Karpathy 提出的那套思路)强调的是让模型自己维护一个结构化的知识页面,把零散信息沉淀成可读、可链接的条目,本质上是用 LLM 做知识的组织者而不只是检索器。
我的实践建议是:不要一上来就上图谱。先用朴素 RAG 跑通闭环,把文档切分、embedding、召回、重排这条链路调稳,等发现多跳和关系类问题确实成为瓶颈了,再引入图谱层。否则你会陷入图谱构建的泥潭,schema 设计、实体消歧、关系抽取每一个都是大工程。
3. 核心细节解析:Token 机制、RAG 链路与 Agent 编排
3.1 把 Token 的 QKV 讲成人话
热词里有个很有意思的说法:token 的三个点 key 是“我是谁”、query 是“我在找什么”、value 是“我能提供什么”。这个类比其实相当到位,我用它给非算法背景的同事解释过很多次注意力机制。
你可以把每个 token 想象成公司里的一个员工。Query是这个员工当前的需求——“我现在要找能帮我完成这份报表的人”。Key是每个员工的自我介绍标签——“我擅长数据分析”“我擅长文案”。注意力机制做的事,就是拿当前员工的 Query 去和所有人的 Key 做匹配,匹配度高的员工,他的Value(实际能提供的帮助)就被更多地采纳进来。所以 QKV 本质是一套“按需检索并加权汇总信息”的机制,跟我们在 RAG 里做的检索在哲学上是一回事,只不过一个发生在模型内部,一个发生在模型外部。
理解这一点很重要,因为它解释了为什么长上下文不等于好效果。上下文越长,Key 越多,注意力越容易被稀释,真正相关的信息反而被淹没。这也是为什么 RAG 这种“先筛后喂”的策略在企业场景里依然必要——与其把整本手册塞进去,不如精准检索出相关几段。
3.2 RAG 链路的五个关键环节与踩坑点
一条完整的 RAG 链路,我习惯拆成五步:文档解析、切分、向量化、检索召回、重排与生成。每一步都有坑。
文档解析阶段,PDF 是最大的敌人。表格、双栏、扫描件,处理不好后面全废。我的经验是优先用能保留版面结构的解析器,实在不行对关键文档做人工校对。
切分阶段,固定长度切分是最省事但最粗暴的做法。更好的策略是按语义边界切,比如按标题层级、按段落。切分粒度要匹配你的问题粒度——问题通常聚焦在一个小点上,那 chunk 就不宜太大,否则噪声太多。
向量化阶段,embedding 模型的选择要和你的语言、领域匹配。中文场景下有些通用模型表现一般,需要实测。另外要注意 embedding 的维度直接影响存储和检索成本。
检索召回阶段,纯向量检索对关键词不敏感。用户搜一个精确的产品型号,向量可能召回一堆语义相近但型号不对的内容。所以实践中我一般用混合检索:向量检索加关键词检索(BM25),两路结果融合。
重排与生成阶段,重排模型(reranker)能显著提升 top-k 的精度,虽然增加一点延迟但通常值得。生成阶段则要在 prompt 里明确要求“只依据给定资料回答,资料中没有就说不知道”,这是抑制幻觉最有效的一招。
3.3 Agent 编排:让模型学会用工具
企业级 Agent 平台的核心,是让 LLM 能够调用外部工具、访问外部数据、执行多步任务。热词里提到的 “LLM powered autonomous agents” 讲的就是这个方向。一个 Agent 的基本循环是:观察当前状态、思考下一步、选择工具、执行、观察结果、继续循环,直到任务完成。
这里的关键设计点是工具的定义。工具描述要清晰,参数 schema 要严格,否则模型会乱调。我踩过的坑是工具太多导致模型选择困难,后来把工具按场景分组,每组只暴露相关工具,准确率明显提升。
另一个重点是循环的终止条件。Agent 很容易陷入死循环,反复调用同一个工具。必须设置最大步数、超时,以及检测重复动作的机制。生产环境里,Agent 的每一步都应该被记录,方便事后复盘它为什么做了这个决策。
4. 实操过程:从零搭一套可用的企业级 LLM 服务
4.1 环境与依赖准备
假设我们要搭一套本地化的企业知识问答服务,包含网关、RAG、Agent 三层。基础环境我一般这样配:
# 基础运行时 python 3.11+ # 向量数据库,轻量场景用这个就够 pip install chromadb # 推理与模型 pip install onnxruntime transformers # Web 服务 pip install fastapi uvicorn # 检索增强 pip install rank_bm25 sentence-transformers如果你的模型走本地 ONNX 推理,需要先把模型导出。以常见的开源模型为例,导出时要注意做动态量化,否则显存吃不住:
from transformers import AutoTokenizer from optimum.onnxruntime import ORTModelForCausalLM model_id = "your-local-model-path" tokenizer = AutoTokenizer.from_pretrained(model_id) ort_model = ORTModelForCausalLM.from_pretrained(model_id, export=True) ort_model.save_pretrained("./onnx_model") tokenizer.save_pretrained("./onnx_model")导出后建议跑一个基准测试,记录首 token 延迟和每秒生成 token 数,这两个指标直接决定用户体验。
4.2 网关层的核心实现
网关我用 FastAPI 写一个最小可用版本,核心是统一入口加策略中间件:
from fastapi import FastAPI, Request from fastapi.responses import StreamingResponse import time, hashlib app = FastAPI() # 简单的内存配额表,生产环境换成 Redis quota = {} @app.post("/v1/chat") async def chat(request: Request): body = await request.json() tenant = request.headers.get("X-Tenant-Id", "default") # 配额检查 used = quota.get(tenant, 0) if used > 100000: return {"error": "quota exceeded"} # 路由:根据模型名选择后端 model = body.get("model", "default") backend = route_model(model) # 透传流式响应 async def stream(): async for chunk in backend.stream(body): quota[tenant] = quota.get(tenant, 0) + 1 yield chunk return StreamingResponse(stream(), media_type="text/event-stream") def route_model(model: str): # 这里根据模型名返回对应的后端客户端 ...这段代码看着简单,但几个设计点很关键。配额按租户维度统计,方便做多部门成本分摊。流式透传保证前端体验。路由函数独立,换模型只改这一处。生产环境还要加上请求日志、失败重试、熔断降级,但骨架就是这个样子。
4.3 RAG 检索链路的落地
检索部分我推荐混合检索,代码大致如下:
from rank_bm25 import BM25Okapi import chromadb class HybridRetriever: def __init__(self, docs): self.docs = docs # 关键词检索 tokenized = [d.split() for d in docs] self.bm25 = BM25Okapi(tokenized) # 向量检索 self.client = chromadb.Client() self.collection = self.client.create_collection("kb") self.collection.add( documents=docs, ids=[str(i) for i in range(len(docs))] ) def search(self, query, top_k=5): # 关键词路 kw_scores = self.bm25.get_scores(query.split()) kw_top = sorted(range(len(kw_scores)), key=lambda i: kw_scores[i], reverse=True)[:top_k] # 向量路 vec_res = self.collection.query(query_texts=[query], n_results=top_k) vec_top = [int(i) for i in vec_res["ids"][0]] # 融合去重 merged = list(dict.fromkeys(kw_top + vec_top)) return [self.docs[i] for i in merged[:top_k]]融合策略这里用的是最简单的并集去重,更精细的做法是引入 RRF(倒数排名融合),给两路结果按排名加权。实测下来 RRF 在多数场景比简单并集更稳,尤其是当两路结果差异较大时。
4.4 Agent 工具调用的实现要点
Agent 部分,工具注册和调用循环是核心:
import json TOOLS = { "search_kb": { "desc": "在企业知识库中检索资料", "params": {"query": "检索关键词"}, "fn": lambda query: retriever.search(query) }, "query_db": { "desc": "查询业务数据库", "params": {"sql": "查询语句"}, "fn": lambda sql: run_sql(sql) } } def agent_loop(user_input, max_steps=6): messages = [{"role": "user", "content": user_input}] for step in range(max_steps): # 让模型决定下一步 resp = call_llm(messages, tools=TOOLS) if resp.get("tool_call"): name = resp["tool_call"]["name"] args = resp["tool_call"]["args"] result = TOOLS[name]["fn"](**args) messages.append({"role": "tool", "content": str(result)}) else: return resp["content"] return "任务步数超限,请简化问题"这里max_steps是保命参数,一定要设。工具描述要写得让模型一看就懂什么时候用,参数名要语义化。我见过因为参数名写成q导致模型频繁传错的案例,改成query后就好了。
5. 常见问题与排查技巧实录
5.1 模型返回格式错误怎么排查
热词里有一条 “llm request failed: provider rejected the request schema or tool payload”,这是非常典型的工具调用格式问题。模型返回的 JSON 不符合你定义的 schema,网关或供应商直接拒了。
排查思路分三步。第一,打印原始返回,看模型到底吐了什么,很多时候是多了 markdown 代码块标记或者尾部多了逗号。第二,检查你的 schema 是否过于严格,比如要求必填字段但模型有时省略。第三,在 prompt 里明确给出格式示例,few-shot 对格式稳定性提升非常明显。我的经验是,工具调用的 schema 校验要宽松解析、严格使用——解析时尽量容错,但真正执行前必须校验关键字段。
5.2 检索召回不准的定位方法
召回不准通常有三种表现:该召回的没召回、召回了不相关的、召回了但排序靠后。对应三种排查方向。
该召回没召回,先看切分是不是把关键信息切断了,再看 embedding 模型是否适配你的领域。召回了不相关的,多半是 chunk 太大导致噪声多,或者缺少重排。排序靠后,就是重排模型该上场了。
我一般会建一个小的评测集,几十条“问题-期望文档”对,每次调整参数就跑一遍,看召回率变化。没有评测集的调优就是盲调,改了半天不知道是变好还是变坏。
5.3 成本失控的常见原因
成本突然飙升,八成是这几个原因:Agent 循环步数没限制导致反复调用、上下文没有做裁剪导致每次请求都带一大堆历史、缓存没做导致重复问题重复计费、输出长度没约束导致模型啰嗦。
对策很直接:给 Agent 设最大步数,给上下文设 token 上限并做滑动窗口,对高频重复问题做语义缓存,在 prompt 里明确要求简洁回答。我实测过,光是把历史上下文从全量改成最近若干轮,成本就能降一大截。
| 问题现象 | 可能原因 | 排查动作 |
|---|---|---|
| 工具调用被拒 | schema 不匹配 | 打印原始返回,放宽解析 |
| 召回缺失 | 切分或 embedding 问题 | 检查 chunk 边界,换 embedding 实测 |
| 成本飙升 | 循环或上下文失控 | 限制步数,裁剪历史,加缓存 |
| 响应变慢 | 模型过大或并发过高 | 量化模型,加网关限流 |
| 回答幻觉 | 缺少约束 prompt | 强制“无依据不回答” |
5.4 几个我踩过的坑
第一个坑是过早引入图谱。我在一个项目里花了两周搭 GraphRAG,结果发现业务问题其实用朴素 RAG 加混合检索就能解决八成,图谱带来的收益远不及维护成本。教训是先简单后复杂。
第二个坑是忽略流式响应。早期版本等模型全部生成完才返回,用户以为卡死了。改成流式后,同样的响应时间,体感快了好几倍。
第三个坑是密钥硬编码。这个不用多说,一旦代码进了仓库就是事故。所有密钥必须走配置中心或环境变量,网关层统一管理。
第四个坑是没有做降级。主模型服务挂了,整个系统就瘫了。后来加了备用模型路由,主服务超时就自动切备用,可用性提升明显。
6. 企业级 LLM 的治理与长期演进
6.1 可观测性:没有日志就没有优化
企业级系统和玩具最大的区别之一,就是一切皆可观测。每一次 LLM 调用都应该记录:请求方、模型、输入输出 token 数、延迟、是否命中缓存、检索召回了哪些文档、Agent 走了几步。这些数据是后续优化的唯一依据。
我一般会在网关层统一埋点,把日志打到结构化存储里,然后做几个核心看板:调用量趋势、成本趋势、P95 延迟、错误率、缓存命中率。有了这些,任何异常都能第一时间发现。
6.2 知识库的持续维护
知识库不是建完就完事的。文档会更新,业务会变化,模型会迭代。我建议建立一套知识入库的流程:文档变更触发重新切分和向量化、定期做检索质量抽检、对高频未命中问题做专项补充。LLM Wiki 那种让模型辅助整理知识的思路在这里很有用——让模型定期把零散问答沉淀成结构化条目,知识库会越用越厚。
6.3 多模型与多租户的演进方向
系统跑起来之后,演进方向通常是两个:一是支持多模型并存,不同任务路由到不同模型,比如简单分类用小模型、复杂推理用大模型,成本能省不少;二是支持多租户,不同部门共享基础设施但数据隔离、配额独立。这两件事都依赖前面说的网关层,所以网关这层投资是值得的。
我在实际项目里的体会是,企业级 LLM 的难点从来不在模型,而在模型之外的那套工程体系。把网关、检索、编排、治理这几层搭稳,模型换成谁都无所谓,系统照样跑。反过来,如果只盯着模型效果,忽略了这些基础设施,那 Demo 再惊艳也上不了生产。最后分享一个小技巧:每次要引入一个新组件之前,先问自己“不用它,用现有能力能不能解决八成问题”,能的话就先别加,等真的成为瓶颈了再说。这套克制,能帮你省下大量维护成本。