ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

隔离内网AI Agent工程化实战:模型推理与LangGraph编排

隔离内网AI Agent工程化实战:模型推理与LangGraph编排 隔离内网下做 AI Agent和你在公网上调 API 跑 demo 完全不是一回事。这些年我在企业环境里折腾过不少 Agent 项目从最早拿开源模型搭私有化对话服务到后来用 LangGraph 做带工具调用的复杂任务编排最深的体会是网络边界一旦封死很多公网教程里的方案直接作废真正决定项目成败的反而是模型分发、依赖镜像、推理引擎这些“基建”问题。这篇就把我踩过的坑、验证过的方案整理出来给同样在隔离环境里做 AI Agent 工程化的朋友一条可复现的路径。先说清楚这篇文章覆盖什么适合已经跑通过 LangChain 或 Spring AI 这类框架、但还没在隔离内网里完整落地的团队也适合正在做技术选型、需要对比推理引擎和编排方案的架构师。内容会涉及模型私有化部署、Agent 编排框架选型、内网工具链适配、并发性能优化以及可观测性和安全加固这几个硬骨头。每个环节我都会给出具体的版本、参数和配置你照着改就能用。1. 隔离内网部署 AI Agent 的整体架构设计1.1 隔离环境带来的核心约束隔离内网这个前提不是简单一句“没外网”就能概括的它牵一发动全身。我在项目启动前的需求评审里通常会先把约束拆成四类来盘每一类都会直接影响架构决策。第一类是模型获取。公网环境你可以直接调用云端大模型 API几行代码就能拿到结果但隔离内网里模型权重本身就是一个资产你需要通过审批流程把权重文件拷贝进内网而且一旦模型要更新整个分发流程要重走一遍。这意味着模型选型必须慎重不能频繁换。第二类是依赖管理。Agent 项目通常涉及 Python 包、Node 包、系统级依赖甚至 CUDA 驱动公网环境下一个 pip install 就能解决的事在内网里必须自建私有镜像仓库而且初始的包同步必须在隔离之前完成。很多团队前期没意识到这一点开发到一半发现缺一个依赖包整个进度卡住。第三类是工具生态。Agent 最核心的价值是调用工具但公网教程里默认的搜索工具、代码解释器、公开 API 在内网里都不能直接用你需要用内网已有的接口来替代。这个替代不是简单换 URL 就行还有协议、鉴权、数据格式的适配。第四类是数据闭环。隔离内网通常伴随着数据合规要求数据不能出域这意味着所有 Agent 的输入输出、模型推理、日志追踪都必须在内部闭环。也正因为这样反而倒逼我们把 RAG、微调、评估这些环节都做扎实了效果往往比公网随手调 API 更稳定。1.2 架构分层建议基于上述约束我习惯把隔离内网下的 Agent 系统拆成五层接入层、编排层、模型层、工具层、基础设施层。接入层负责对外提供统一入口通常是一个内部网关或服务接收用户请求、做鉴权和限流把请求转给编排层。编排层是 Agent 的核心负责理解任务、规划步骤、调用工具、组织上下文这一层我们用 LangGraph 来落地。模型层本身是一个独立的推理服务集群通过 OpenAI 兼容协议暴露给编排层调用。工具层则是一组预先注册好的内部服务比如查数据库、调内部 API、读知识库文档等等。基础设施层则是兜底包括日志、监控、向量库、Redis、MySQL 等底座。这五层之间用明确的 API 契约连接每一层都独立部署、独立扩容。我特别想强调一点模型层和编排层必须解耦。不要图省事把模型嵌入到 Agent 进程里跑否则并发一上来你根本没法分开扩出了问题也说不清是 Agent 逻辑的问题还是模型推理的问题。2. 模型推理引擎选型与部署实战2.1 开源模型选型的基本原则隔离内网里做 Agent模型的选型会比对话机器人更挑剔。Agent 任务的核心逻辑是“推理 工具调用”模型不仅要能听懂人话还要能从上下文里提取出准确的函数参数、判断调用哪个工具、处理工具返回结果。所以我在选型时会重点看两个能力Instruction Following 和 Function Calling 的稳定性。当前开源模型里Qwen 系列和 LLaMA 系列的综合表现是经过验证的。Qwen 系列的中文能力和工具调用能力都比较均衡尤其是 Qwen2.5-14B/32B 在 Function Calling 的评测里表现稳定适合做 Agent 的主模型。如果你对代码类任务有强需求可以考虑加入 CodeLlama 或 Qwen-Coder 作为专用模型。参数量的选择要看你手里的 GPU 资源。我的经验是32B 及以上适合做复杂任务编排的主模型7B/14B 适合做实体抽取、意图分类这类子任务。不要指望一个 7B 模型能撑起复杂的多跳推理 Agent它在工具选择上会频繁出错调试成本反而更高。2.2 推理引擎对比与部署配置模型选好了接下来就是推理引擎。我在内网环境实测过三套主流的开源推理方案vLLM、TGIText Generation Inference、llama.cpp。用一张表来对比最直观特性vLLMTGIllama.cpp高并发吞吐强PagedAttention 优化显存强HuggingFace 官方优化弱侧重单机轻量量化支持GPTQ/AWQ/FP8 等GPTQ/AWQGGUF 量化OpenAI 兼容 API原生支持原生支持需额外适配层GPU 利用率高高中低部署复杂度中中低适用场景生产级高并发 Service生产级高并发 Service资源受限、快速验证我在生产环境主力用 vLLM原因很直接它对高并发的支持最好PagedAttention 技术能把显存利用率提上一个台阶同样的 A100 上跑 14B 模型vLLM 的吞吐比普通 HuggingFace 推理高出接近一倍。部署 vLLM 的配置我给一个可参考的启动参数python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen2.5-14B-Instruct \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.92 \ --max-model-len 32768 \ --dtype bfloat16 \ --port 8000 \ --api-key internal-secret-key这里有几个参数值得解释一下。--tensor-parallel-size表示张量并行度如果单卡显存不够放整个模型就需要在多卡之间切分模型14B 的模型在 bfloat16 精度下大约需要 28GB 显存两张 24GB 的卡就建议设成 2。--gpu-memory-utilization控制在 0.9 左右比较稳妥留出一点余量给推理过程中的临时显存开销。--max-model-len决定了上下文窗口Agent 任务里会塞工具定义、历史消息、中间结果我建议至少 32K有条件可以上 64K。2.3 量化方案与显存估算隔离内网环境里GPU 资源往往不像公网云那么充裕量化几乎是必选项。我常用的量化方案有两种GPTQ 和 AWQ两者的效果在主流任务上非常接近但 AWQ 在低比特下的精度损失通常更小一些。以 Qwen2.5-14B 为例原始 bfloat16 权重大约 28GB4-bit 量化后可以压到 9GB 以内这意味着原来必须用 A100 才能跑起来的模型现在一张 16GB 的 L4 或者 24GB 的 4090 就能带得动。实际部署时还要算上 KV Cache 的显存开销公式大致是显存需求 ≈ 模型权重大小 KV Cache 大小 推理临时开销。KV Cache 这一块经常被忽略。Agent 的对话往往很长工具调用的中间结果也都塞进上下文里所以 KV Cache 会迅速膨胀。算一笔账假设 batch size 是 8上下文长度 32K模型层数 40 层KV Cache 大概需要 8 × 4096 × 32K × 2 × 40 × 2 bytes算下来要接近 40GB。所以做容量规划时千万别只看模型权重KV Cache 才是压垮显存的最后一根稻草。量化部署的实操上我踩过一个具体的坑vLLM 加载 GPTQ 量化模型时如果没指定正确的--quantization参数模型会被当成原始权重加载显存直接爆掉。解决方案是在启动参数里显式加上--quantization gptq或者--quantization awq并且确认权重目录下有对应的量化配置文件。3. Agent 编排框架与工具链落地3.1 为什么选 LangGraph 作为编排层Agent 编排层是整个系统的“大脑”市面上可选的无非 LangChain、LangGraph、Spring AI还有一个比较小众的 Rust 生态方案。我的选择很明确编排层用 LangGraphSpring AI 留给 Java 技术栈的团队Rust 方案现阶段只适合极客尝鲜。LangChain 的问题是它更像一个工具集合而不是一个状态机复杂的多步 Agent 流程写起来会陷入无尽的回调嵌套。LangGraph 本质上是 LangChain 团队推出的图编排框架它把 Agent 流程建模成一张有向图节点是“执行的动作”边是“状态转移的条件”这样复杂流程的可读性和可控性都大大提升。对于隔离内网的生产项目来说LangGraph 还有个好处是它是纯 Python 库可以完全离线部署。你只需要把 LangGraph 及其依赖通过私有 pip 源同步进内网所有编排逻辑都发生在本地进程内不需要任何外部服务。3.2 用 LangGraph 实现带工具调用的 Agent下面这段代码是我在项目里实际用过的结构做了一个简单的“查库并回答问题”的 Agent。核心思路是先判断是否需要调用工具调用完工具把结果写回状态再让模型继续推理。from typing import TypedDict, Annotated from langgraph.graph import StateGraph, END from langgraph.prebuilt import ToolNode from langchain_openai import ChatOpenAI import json class AgentState(TypedDict): messages: list need_tool: bool tool_output: str # 工具查内部订单库 def query_orders(order_id: str) - str: # 实际实现走内网 API返回 JSON 字符串 return json.dumps({order_id: order_id, status: shipped}) tools [query_orders] # 用 OpenAI 兼容协议接入 vLLM 推理服务 llm ChatOpenAI( base_urlhttp://llm-service:8000/v1, api_keyinternal-secret-key, modelQwen2.5-14B-Instruct, temperature0.1, ) llm_with_tools llm.bind_tools(tools) def decide_action(state: AgentState): 让模型决定是直接回答还是调用工具 response llm_with_tools.invoke(state[messages]) if response.tool_calls: return {messages: [response], need_tool: True} return {messages: [response], need_tool: False} def execute_tool(state: AgentState): 执行工具并把结果回填给模型 tool_node ToolNode(tools) result tool_node.invoke(state) return {messages: result, tool_output: str(result), need_tool: False} graph StateGraph(AgentState) graph.add_node(decide, decide_action) graph.add_node(tool, execute_tool) graph.add_edge(decide, tool, conditionlambda s: s[need_tool]) graph.add_edge(decide, END, conditionlambda s: not s[need_tool]) graph.add_edge(tool, decide) app graph.compile()这段代码的核心在decision节点模型通过bind_tools知道了有哪些工具可用并在推理时决定是否调用。tool节点执行完工具后结果以消息形式回填给decide节点形成“思考-行动-观察”的循环直到模型不再触发工具调用为止。我额外强调一个细节工具描述写得好不好直接决定 Agent 的准确率。比如同样是查订单工具如果你只写“query_orders”模型经常搞不清楚该传什么参数但如果你写清楚“当用户询问订单状态、物流信息时调用此工具参数 order_id 是用户提供的订单号”模型的工具命中率会从 70% 提升到 95% 以上。3.3 工具层的内网适配与 RAG 知识库工具层是隔离内网和公网环境差异最大的地方。公网教程里随手就用的 Search API、天气 API在内网里统统不存在你需要面向内部服务重写。我在工程里涉及的典型工具包括内部订单系统查询接口、MySQL 数据库查询、企业知识库文档检索、定时任务触发器等。这些工具本身都有现成的内部系统Agent 要做的就是把工具请求翻译成内部 API 调用。每个工具接入时都要做三件事定义 JSON Schema、封装鉴权逻辑、配置超时与失败策略。JSON Schema 是关键LangGraph 和 vLLM 的 Function Calling 都依赖它来约束模型输出格式。鉴权则要对应内部系统的认证机制可能是 token、签名或者内部证书。超时上我的经验是默认设 10 秒Agent 的容错重试策略用 max_retries2。RAG 这块单独说一下。隔离内网的 Agent 不能依赖模型内部知识来回答内部业务问题必须做外挂知识库。向量化服务的嵌入模型我们用BAAI/bge-m3它对中文的支持很好而且尺寸不大在 CPU 上都能勉强跑。向量数据库可选 Milvus 或 pgvector如果团队已经有 PostgreSQL直接用 pgvector 最省事少维护一个组件。整体 RAG 流程不需要模型参与纯工具层实现回到编排层再交给 LLM 结合检索结果生成最终回答。4. 并发优化与可靠性保障4.1 并发瓶颈到底在哪在做内网 Agent 项目的时候我经常被问“能扛多少并发”这个问题其实要拆开看。Agent 请求的耗时和普通接口完全不同一次完整任务往往需要模型多次推理先理解任务、再决定工具、再分析工具结果中间可能还要多轮往返。所以并发设计不能只盯着模型推理引擎要把“计算并发”和“编排并发”分开分析。模型推理服务的并发受到显存和算力的双重限制这决定了同一时间能同时推理几个请求。编排层则是 CPU 密集的 Python 进程负责处理请求状态流转、工具调用、上下文拼接它的瓶颈更多在线程效率和 IO 等待上。我讲一个实际场景的估算方法。假设部署两台 A100 80G 的节点跑 14B 量化模型单请求平均生成长度按 800 token 算vLLM 实测单节点吞吐大约是 800-1200 token/s。如果一个 Agent 任务平均需要 4 轮模型推理每轮生成 800 token那单个请求总生成 token 数是 3200。单节点可以撑起的并发 Agent 任务数大约是吞吐量除以总 token 数也就是 800 到 1200 除以 3200大约 0.25 到 0.375 个任务/秒。两台节点合计就是每分钟 30 到 45 个 Agent 任务。这个估算很有参考价值能直接指导你对业务方提真实的容量承诺。4.2 请求排队、限流与熔断Agent 请求比普通接口慢得多如果不做排队和限流一旦流量陡增整个系统会像饭店来了 100 桌客人但只有一个厨师一样全线崩溃。我的方案是三层保护。第一层是入口限流。在接入层网关做令牌桶限流按用户维度限制每秒请求数比如每个用户 1 QPS这样能挡住绝大多数异常流量。第二层是模型服务排队。vLLM 本身支持请求排队但队列不能无限长要在编排层设一个最大等待时间超过 60 秒直接返回“系统繁忙请稍后再试”避免用户无限等待。第三层是工具调用熔断。当某个内部服务响应超时率达到阈值时自动熔断该工具的调用让 Agent 走“抱歉该功能暂时不可用”的兜底话术。我在生产环境里还踩过一个坑Python 编排层如果直接用普通同步代码调用模型服务一次请求的时长可能上百秒线程全部占住后续请求全部卡死。所以编排层必须用异步 IOLangGraph 本身支持异步执行把节点函数改成 async 之后并发能力能提升一个数量级。4.3 长任务异步化是必经之路Agent 里经常有那种需要跑几分钟甚至更长的任务比如“分析上个月所有订单数据并生成报告”。这种任务不能靠同步请求硬扛否则客户端等超时不说整个连接资源也被占死。我的做法是把任务拆成两类交互型短任务和批处理型长任务。交互型短任务走同步接口50 秒内返回批处理型长任务通过消息队列提交立刻返回一个 task_id后台由独立的 Worker 处理完成后把结果写到存储中客户端通过 task_id 查询进度和结果。这个设计在隔离内网里尤其重要因为内网用户不多但单次任务的复杂度往往很高一个长任务可能触发数十次工具调用消耗的资源是普通对话任务的几十倍。没有异步化系统根本没有余量接待其他用户。5. 可观测性与问题排查实录5.1 日志追踪与链路观测Agent 系统的排错难度比普通 Web 服务高一个量级因为同一个用户请求会引发模型多次推理、多次工具调用任何一个环节出错最终表现的都只是“回答不对”。没有好的链路追踪你只能在一个个日志文件里翻到疯掉。我推荐用 OpenTelemetry 作为追踪底座配合 Langfuse 这类面向 LLM 的观测平台。Langfuse 支持私有化部署可以把所有推理记录、工具调用、 token 用量完整记录下来还能直接在 UI 里看到每一步的 prompt 和 response。部署层面就是 docker compose 起一套服务然后给 LangChain/LangGraph 的请求打上 trace 标记。接完之后你会发现一个巨大的好处生产环境里用户说“回答错误”你能利用 Langfuse 的 trace 直接定位到模型是在哪一轮推理出了问题是因为上下文被截断、工具参数解析错误、还是模型幻觉排查效率提升几个数量级。5.2 常见问题速查表以下是我在项目验收和灰度过程中真实遇到过的问题整理成速查表遇到类似现象可以直接照着查。问题现象根因解决方案模型回答喜欢编造工具结果模型上下文里工具返回为空或被截断检查工具输出长度超过 max_model_len 的部分做摘要或截断工具调用参数总是格式错误Function Calling Schema 定义不严谨重写 JSON Schema补充参数字段描述和格式约束并发一高就 OOMKV Cache 估算不足降低 max-model-len 或减小 batch size或者加显存上下文一长就变笨超出模型有效处理长度对历史消息做滑动窗口裁剪关键信息摘要化长时间运行后响应变慢请求队列堆积排查慢工具调用给每个工具设置独立超时同样的输入不同时间回答差异大模型温度设置过高Agent 场景 temperature 建议 0.1~0.35.3 Agent 幻觉问题的排查实录我单独说一个头疼的问题Agent 在工具调用之后经常会“脑补”出工具没有返回的内容。比如工具返回的 JSON 里只有订单状态但模型的最终回答里却把客户地址、商品名称全编出来了。这种幻觉在隔离内网里特别危险因为内部数据都有保密属性回答一旦出错轻则误导业务重则违反数据规范。排查的思路要分成三步走。第一步看 Langfuse 里工具的原始返回确认返回内容本身有没有被截断第二步看这轮 prompt 的指令是不是明确要求“只能基于工具返回内容回答”如果 prompt 里没有这层约束模型天然倾向于自由发挥第三步如果前两步都没问题那就是模型能力问题7B/14B 在这种场景下更明显只能升级更大模型。我最后加了一行专门的 system prompt 才压住这个问题“You are an assistant that only answers based on the provided tool results. Do not make up information that is not present in the tool output.” 加完之后幻觉比例从大约 15% 降到了 4% 左右。6. 安全加固与权限控制6.1 数据隔离与多租户设计隔离内网不代表系统内部的用户都可以互相看数据。我接手过的一个项目里多个部门共用一个 Agent 网关模型层是无状态的但工具层涉及的数据权限完全是按部门隔离的。如果没有在编排层就做好租户隔离一个部门的员工就能通过 Agent 问到另一个部门的订单数据。我的做法是在请求进来时从网关解析用户身份生成一个租户上下文在 Agent 调用工具的节点里把它透传下去。内部工具的调用链路上租户上下文会作为强制参数传给业务系统做数据权限校验。这块必须在工具封装时提前设计好后期再补非常痛苦。6.2 Prompt 注入与工具权限最小化Agent 有一个公网内网都躲不开的安全问题Prompt 注入。用户可能通过对话内容诱导模型调用某些工具或者从文档里读取隐藏指令。在隔离内网里这意味着潜在的信息泄露必须做防御。我的实际策略是三层。第一层对模型输入做内容过滤检测并标记可疑的指令片段尤其是“忽略之前的指令”“不要遵守系统约束”这类典型注入模式。第二层所有的工具调用在编排层再校验一次参数不让模型直接决定一切比如删除类操作必须二次确认。第三层工具权限最小化Agent 用的服务账号只授予只读权限绝不使用个人账号或管理员权限这样即使被恶意利用破坏面也有限。另外一个容易被忽略的点Agent 生成的代码或脚本要放在沙箱里执行不要让 Agent 直接操作宿主机的 shell。即使是内网环境这一点也不能省我在项目里是直接给代码执行工具单独挂了一个容器环境与主服务完全隔离。7. 一点沉淀隔离内网下做 AI Agent本质上是在“有限的资源 有限的生态 更高的一致性要求”这三重约束下做工程取舍。模型可以不用最强但推理服务必须稳定工具无法照搬公网但内部接口的适配只要做好效果往往更好效率不能用公网 API 的“拿来主义”但建好私有镜像和模型分发机制后反而显得更踏实。我个人的体会是这类项目能不能成百分之三十取决于模型选型百分之七十取决于工程基建的细腻程度。先把模型分发、依赖镜像、推理服务、可观测性和安全策略这五件事做扎实Agent 本身的业务逻辑反而不是最难的部分。按照这条路径推进你至少可以少踩一半的坑。
返回列表