
1. 项目缘起为什么要在隔离内网里折腾 AI Agent第一次接到“在内网环境里跑 AI Agent”这个需求时我脑子里蹦出来的第一个念头是这不是自己给自己找麻烦吗。外网环境里各种大模型 API 随手就能调工具链成熟、文档齐全、社区活跃遇到问题搜一下基本都有答案。但内网环境完全是另一套玩法——没有公网出口不能随便装依赖所有外部请求都要经过审批连拉个镜像都得走内部制品库。这个项目的背景其实很典型一家做企业级软件的公司内部有一套完整的业务系统包括工单系统、知识库、代码仓库、CI/CD 流水线等。他们希望在这些系统之上搭一个 AI Agent能够自动处理一些重复性的运维和开发辅助工作比如根据工单描述自动检索知识库、根据代码变更自动生成审查意见、根据告警信息自动关联历史故障记录。听起来很美好但问题是——这些系统全部部署在隔离内网里没有任何一台机器能直接访问外部大模型服务。所以核心矛盾就来了AI Agent 的能力高度依赖大模型而大模型服务通常在外网内网又出不去。这个矛盾不解决后面所有事情都无从谈起。我见过不少团队在这个环节卡了很久要么是安全部门不批要么是网络部门不给开策略要么是运维部门嫌维护成本高。最后项目不了了之大家继续用人工方式处理那些重复劳动。我接手这个项目之后第一件事不是急着选型或写代码而是先把整个约束条件理清楚。隔离内网意味着什么意味着你不能假设任何外部服务可达不能假设任何外部依赖能自动下载不能假设任何配置能从网上复制粘贴。所有东西都必须自包含、可审计、可复现。这听起来是限制但换个角度想它其实逼着你把架构做得更干净、更可控。这个项目适合谁参考我觉得有三类人。第一类是在金融、政务、军工等强合规行业做 AI 落地的工程师你们面对的内网限制可能比我还严格。第二类是在企业内网做平台建设的同学你们可能不需要完全隔离但审批机制和网络策略同样让人头疼。第三类是对 AI Agent 架构感兴趣但还没实际落地过的开发者你们可以从这个项目里看到真实场景下的取舍和妥协而不是 demo 里那种理想化流程。接下来我会从整体设计、核心细节、实操过程、问题排查几个维度把这个项目的完整思路和落地经验拆开讲。所有内容都基于真实实践能复现的我会给出具体步骤不能复现的我会说明原因和替代方案。2. 整体架构设计在约束条件下做取舍2.1 核心矛盾拆解与方案选型隔离内网下做 AI Agent最核心的矛盾就一个模型能力从哪里来。这个问题不解决后面全是空谈。我梳理了一下大概有三条路可走。第一条路是内网自部署模型。把开源模型下载下来在内网服务器上跑推理服务。优点是数据不出内网合规性最好审批最容易过。缺点是硬件成本高推理质量取决于模型规模和量化程度而且维护成本不低。我实测下来7B 到 14B 级别的模型在内网做辅助性任务够用但复杂推理和长上下文场景就比较吃力。第二条路是通过审批通道调用外部模型。在内网和外部模型服务之间建一条受控的、可审计的通道所有请求都经过审批和日志记录。优点是模型能力强不用自己维护推理基础设施。缺点是审批流程长网络策略复杂而且每次调用都要考虑数据脱敏和合规审查。这条路适合对模型能力要求高、且安全部门愿意配合的场景。第三条路是混合方案。简单任务走内网小模型复杂任务走审批通道调外部大模型。优点是兼顾成本和能力缺点是架构复杂度高需要一套路由和降级机制。我最终选的是混合方案但做了简化内网部署一个中等规模的模型作为默认推理引擎处理知识库检索、工单分类、代码摘要这类任务对于需要复杂推理的场景走审批通道调用外部模型但调用前必须经过数据脱敏和审批队列。这个方案的好处是大部分日常任务不依赖外部通道系统可用性有保障少数复杂任务才走审批审批压力可控。提示选型时不要一上来就追求最强模型。先梳理清楚你的任务类型把 80% 的日常任务用内网模型覆盖掉剩下 20% 的复杂任务再考虑外部通道。这样架构更稳审批也更容易过。2.2 组件分层与职责划分整个系统我分成了四层每层职责清晰层与层之间通过明确定义的接口通信。接入层负责接收来自各个业务系统的请求。工单系统通过 webhook 推送新工单代码仓库通过 hook 推送代码变更事件监控系统通过告警通道推送告警信息。接入层做统一的协议转换和请求校验把不同来源的请求转成内部统一的消息格式。编排层是核心负责 Agent 的决策和工具调用。这一层包含几个关键模块意图识别模块判断用户请求属于哪类任务规划模块把复杂任务拆成子步骤工具调用模块根据子步骤选择合适的工具并执行结果聚合模块把多个工具的输出合并成最终结果。编排层不直接调用模型而是通过模型网关来调用。模型网关层负责模型调用的路由和降级。它维护一个模型列表包括内网模型和外部模型通道。根据任务类型、当前负载、审批状态等因素决定把请求路由到哪个模型。如果内网模型置信度低于阈值自动升级到外部通道但升级前会触发数据脱敏和审批流程。工具层封装了所有可被 Agent 调用的能力。知识库检索工具、代码分析工具、工单查询工具、告警关联工具等。每个工具都有明确的输入输出定义和权限控制。工具层不直接访问业务系统而是通过内部 API 网关这样权限和审计都集中在网关层处理。这个分层的好处是每一层都可以独立替换或升级。比如内网模型从 7B 换成 14B只需要改模型网关的配置新增一个工具只需要在工具层注册编排层通过工具发现机制自动识别。2.3 审批机制的设计与实现审批机制是这个项目里最容易被低估的部分。很多人觉得审批就是个流程走个形式就行。但在隔离内网场景下审批机制直接决定了系统能不能用、好不好用。我的设计思路是审批不是拦路虎而是安全阀。审批的目的不是阻止调用而是确保每次外部调用都有记录、有依据、可追溯。所以审批流程要尽量自动化减少人工干预。具体实现上我做了三级审批策略。第一级是自动审批针对那些已经预定义好的、数据脱敏后的标准请求比如“查询公开知识库条目”这类不涉及敏感信息的操作系统自动放行只记录日志。第二级是规则审批针对包含特定关键词或特定数据类型的请求系统根据预设规则自动判断是否放行比如请求中包含“客户”字段但已经过脱敏处理规则引擎判断符合要求后自动通过。第三级是人工审批针对规则无法判断的请求推送到审批队列由值班人员人工审核。这套机制的关键在于规则引擎的维护。规则不是一成不变的需要根据实际运行情况不断调整。我建议每周 review 一次审批日志看看哪些请求被人工审批了能不能抽象成规则自动处理。这样审批效率会越来越高人工介入越来越少。注意审批日志一定要保留完整的请求上下文包括原始请求、脱敏后的请求、审批决策、审批人、审批时间。这些日志不仅是合规要求也是后续优化规则的重要依据。3. 核心细节解析模型部署与工具封装3.1 内网模型部署的实操要点内网部署模型第一步是选模型。我试过好几个开源模型最后选了一个 13B 参数量的中文优化模型。选它的理由有三个一是中文理解能力够用二是量化后显存占用可控三是社区活跃、文档齐全遇到问题好排查。部署方式上我用的是容器化部署。把模型和推理框架打包成一个镜像推到内部制品库然后在 GPU 服务器上拉起来。这样做的好处是环境隔离、版本可控、迁移方便。镜像里包含了模型权重、推理框架、依赖库和启动脚本换一台机器只需要拉镜像、改配置、启动不需要重新装环境。推理框架我选的是 vLLM原因是它的吞吐量比原生 transformers 高不少而且支持连续批处理多个请求可以并行处理。配置上我开了张量并行把模型切到两张 GPU 上这样单卡显存压力小推理速度也更快。启动参数这块有几个关键点。--max-model-len控制最大上下文长度我设的是 8192再长显存就不够了。--gpu-memory-utilization控制显存利用率我设的是 0.9留一点余量给系统。--dtype设的是 half用半精度推理速度和显存都更优。这些参数需要根据实际硬件调整没有万能值。python -m vllm.entrypoints.openai.api_server \ --model /models/your-model-path \ --tensor-parallel-size 2 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --dtype half \ --port 8000模型服务起来之后我封装了一个内部 API 网关所有对模型的调用都走网关。网关做几件事请求鉴权、速率限制、日志记录、失败重试。这样做的好处是模型服务本身不需要处理这些横切关注点专注推理就行。实操心得内网模型部署最容易踩的坑是显存不够。建议先算清楚模型权重要占多少显存KV Cache 要占多少然后留 20% 余量。如果显存紧张优先考虑量化而不是减小上下文长度因为上下文长度直接影响 Agent 的任务处理能力。3.2 工具封装与 MCP 协议适配工具封装这块我参考了 MCP 协议的设计思路。MCP 的核心思想是把工具的能力描述标准化让 Agent 能够自动发现和调用工具。虽然内网环境不一定能直接用 MCP 的官方实现但它的设计理念完全可以借鉴。每个工具我定义了三部分工具描述、输入参数 schema、输出格式。工具描述用自然语言写清楚这个工具能做什么、什么时候用、有什么限制。输入参数 schema 用 JSON Schema 定义明确每个参数的类型、是否必填、取值范围。输出格式统一成 JSON包含状态码、结果数据和错误信息。举个例子知识库检索工具的定义大概是这样的工具描述是“根据关键词检索内部知识库返回最相关的文档片段”输入参数包括 query字符串必填、top_k整数可选默认 5、threshold浮点数可选默认 0.7输出格式是包含文档 ID、标题、片段内容、相关度分数的列表。工具注册这块我用了一个注册中心。每个工具启动时向注册中心注册自己的元信息编排层通过注册中心发现可用工具。这样做的好处是工具可以动态增减不需要重启编排层。注册中心还维护工具的健康状态如果某个工具连续失败自动从可用列表中摘除避免 Agent 反复调用一个坏掉的工具。工具调用的权限控制也很重要。不是所有 Agent 都能调用所有工具。我在注册中心里加了权限标签每个工具标记允许调用的 Agent 角色。编排层在调用工具前先检查权限没有权限的直接拒绝并记录审计日志。3.3 Skills 机制的设计与落地Skills 这个概念最近很火我理解它本质上是一种可复用的能力封装。一个 Skill 可以是一个工具也可以是一组工具的编排流程还可以是一段提示词模板。它的价值在于把常见的任务模式固化下来让 Agent 不用每次都从头规划。我在项目里设计了一套轻量级的 Skills 机制。每个 Skill 是一个 YAML 文件定义了 Skill 的名称、描述、触发条件、执行步骤和输出格式。触发条件可以是关键词匹配也可以是意图分类的结果。执行步骤可以调用一个或多个工具也可以调用子 Skill。举个例子我定义了一个“工单自动分类”的 Skill。触发条件是“新工单创建”事件。执行步骤是第一步调用工单查询工具获取工单详情第二步调用知识库检索工具查找相似工单第三步调用模型对工单进行分类第四步调用工单更新工具写回分类结果。输出格式是分类标签和置信度。这套机制的好处是新任务来了不需要改代码写个 YAML 文件就行。而且 Skill 可以版本化管理出问题了回滚到上一个版本。我实测下来大部分日常任务都能用 Skill 覆盖只有少数复杂任务需要 Agent 动态规划。提示Skills 的粒度要控制好。太细了一个任务要调十几个 Skill编排开销大太粗了复用性差稍微变个场景就不适用。我的经验是一个 Skill 对应一个完整的业务动作比如“分类工单”“生成代码审查意见”“关联告警”这样粒度比较合适。4. 实操过程从零搭建一个可用的 Agent4.1 环境准备与依赖管理内网环境准备是最耗时的一步因为所有依赖都要手动处理。我的做法是先在外网环境把整个项目跑通然后把所有依赖打包再迁移到内网。外网阶段我用 pip 的pip download命令把所有 Python 依赖下载到本地目录。注意要指定平台和 Python 版本否则下载的包可能不兼容。命令大概是这样的pip download -r requirements.txt -d ./packages \ --platform manylinux2014_x86_64 \ --python-version 310 \ --only-binary:all:下载完之后把 packages 目录拷贝到内网用pip install --no-index --find-links./packages -r requirements.txt安装。这样就不需要访问外部 PyPI 源了。系统依赖这块比如 CUDA 驱动、cuDNN 这些需要提前在内网服务器上装好。我建议在项目开始前就确认好 GPU 服务器的驱动版本和 CUDA 版本然后选择兼容的推理框架版本。版本不匹配是内网部署最常见的坑之一。容器镜像的迁移也是类似思路。在外网构建好镜像用docker save导出成 tar 文件拷贝到内网再用docker load导入。如果内网有制品库直接把镜像推到制品库更方便。4.2 模型服务启动与验证模型服务启动后第一件事是验证服务是否正常。我用 curl 发一个简单的请求看看能不能正常返回。curl http://localhost:8000/v1/completions \ -H Content-Type: application/json \ -d { model: your-model, prompt: 你好, max_tokens: 50 }如果返回正常说明模型服务没问题。如果报错常见原因有几个显存不够、模型路径不对、端口被占用、依赖版本不匹配。排查的时候先看日志日志里一般会写清楚错误原因。验证通过之后我会跑一个简单的基准测试测一下推理延迟和吞吐量。用 vLLM 自带的 benchmark 脚本就行或者自己写个简单的并发请求脚本。这个数据很重要后面做容量规划和性能优化都要参考。import time import requests from concurrent.futures import ThreadPoolExecutor def send_request(prompt): start time.time() resp requests.post( http://localhost:8000/v1/completions, json{model: your-model, prompt: prompt, max_tokens: 100} ) return time.time() - start prompts [测试请求] * 20 with ThreadPoolExecutor(max_workers10) as executor: latencies list(executor.map(send_request, prompts)) print(f平均延迟: {sum(latencies)/len(latencies):.3f}s) print(f最大延迟: {max(latencies):.3f}s)4.3 Agent 编排逻辑实现编排逻辑是整个系统的核心我用了状态机的方式来管理 Agent 的执行流程。每个任务从“接收”状态开始经过“意图识别”“任务规划”“工具调用”“结果聚合”几个状态最后到“完成”或“失败”状态。意图识别这块我用的是模型分类加规则匹配的混合方式。先用模型对请求做初步分类如果置信度高于阈值直接采用如果低于阈值走规则匹配根据关键词和上下文判断。这样兼顾了准确率和响应速度。任务规划这块简单任务直接用 Skill 执行复杂任务才走动态规划。动态规划的逻辑是先把任务拆成子目标然后为每个子目标选择合适的工具最后按依赖关系排序执行。规划过程中如果发现某个子目标没有可用工具就标记为“需要人工介入”把任务挂起。工具调用这块我加了重试和降级机制。每个工具调用最多重试两次如果还是失败尝试降级方案。比如知识库检索工具失败降级到用模型直接回答代码分析工具失败降级到返回原始代码让模型分析。降级方案不一定完美但至少能保证任务不中断。结果聚合这块我把多个工具的输出合并成一个结构化的结果。如果工具输出之间有冲突用模型做仲裁选择置信度更高的结果。最终结果会附带每个步骤的执行日志方便排查问题。4.4 审批通道对接与数据脱敏审批通道的对接是内网项目的特色环节。我的实现方式是在模型网关层加一个审批拦截器当请求需要走外部通道时先经过脱敏模块处理然后推送到审批队列。脱敏模块的规则是可配置的。我定义了几类敏感信息人名、手机号、身份证号、客户编号、内部项目代号。每类信息有对应的脱敏策略比如人名替换成“某某”手机号中间四位打码客户编号替换成哈希值。脱敏规则用正则表达式实现配置在 YAML 文件里方便调整。sensitive_patterns: - name: phone pattern: 1[3-9]\\d{9} replacement: 1XXXXXXXXXX - name: id_card pattern: \\d{17}[\\dXx] replacement: XXXXXXXXXXXXXXXXX - name: customer_id pattern: CUST-\\d{6} replacement: CUST-XXXXXX审批队列我用的是内部的消息队列审批人员通过一个简单的 Web 界面处理审批请求。界面上显示脱敏后的请求内容、请求来源、请求时间、建议审批意见。审批人员可以选择通过、拒绝或转人工处理。审批结果通过回调接口通知模型网关网关根据审批结果决定是否继续调用外部模型。注意脱敏模块一定要在审批之前执行确保审批人员看到的是脱敏后的内容。同时脱敏日志要单独存储不能和原始请求日志混在一起避免脱敏后的数据被反向还原。5. 常见问题与排查技巧实录5.1 模型服务类问题速查内网模型服务的问题主要集中在显存、性能和稳定性三个方面。我整理了一个速查表覆盖了最常见的几类问题。问题现象可能原因排查方法解决方案服务启动失败报 CUDA out of memory显存不足检查模型大小和 GPU 显存量化模型、减小 max-model-len、增加 GPU推理速度慢延迟高批处理未开启、GPU 利用率低查看 GPU 利用率和请求队列开启连续批处理、调整并发数服务运行一段时间后崩溃显存泄漏、请求堆积查看日志和显存监控限制最大并发、定期重启、升级推理框架返回结果乱码或截断编码问题、max-tokens 设置过小检查请求参数和模型配置统一 UTF-8 编码、增大 max-tokens模型加载失败报版本不兼容推理框架与模型格式不匹配检查框架版本和模型格式统一版本、转换模型格式显存问题是最常见的。我踩过一次坑模型加载时显示显存够用但跑了一段时间后 OOM。后来发现是 KV Cache 没有限制长上下文请求多了之后显存被吃满。解决办法是设置--max-num-seqs限制并发请求数同时监控 KV Cache 的使用率。5.2 工具调用类问题排查工具调用的问题通常比较隐蔽因为 Agent 可能会“静默失败”——工具报错了但 Agent 没有正确处理继续往下走最后给出一个看似合理但实际错误的结果。我的排查思路是先看日志再看链路最后看数据。日志里记录了每次工具调用的输入输出和耗时先确认工具是否被调用了、调用是否成功。如果日志显示调用成功但结果不对就看链路确认工具返回的数据在后续步骤中是否被正确处理。如果链路也没问题就看数据确认工具访问的数据源本身是否正确。常见的问题包括工具超时、工具返回格式不符合预期、工具权限不足、工具依赖的服务不可用。针对这些问题我在编排层加了几个保护机制。工具调用设置超时时间超时后自动重试或降级工具返回结果做 schema 校验不符合格式的直接标记为失败工具权限在调用前检查没权限的直接拒绝并记录工具依赖的服务做健康检查不健康的工具从可用列表中摘除。实操心得工具调用的日志一定要记录完整的输入输出不要只记录成功或失败。很多时候问题出在“工具返回了结果但结果不是 Agent 期望的格式”这种问题只看成功/失败日志是排查不出来的。5.3 审批流程类问题与优化审批流程的问题主要是效率和体验。我遇到过几种典型情况审批队列积压、审批规则误判、审批日志缺失。审批队列积压通常是因为规则覆盖不够太多请求走了人工审批。解决办法是定期分析审批日志把高频的人工审批请求抽象成规则。我一般每周做一次分析看看过去一周哪些请求被人工审批了能不能自动化。坚持做了两个月之后人工审批量下降了 70% 左右。审批规则误判是指规则把本该放行的请求拦下来了或者把该拦的请求放行了。前者影响效率后者有安全风险。我的做法是给规则加一个“观察模式”新规则先不实际拦截只记录如果按这个规则执行会是什么结果。观察一段时间后确认规则准确率达标再正式启用。审批日志缺失通常是因为日志记录不完整或存储时间不够。我的要求是审批日志至少保留 180 天包含完整的请求上下文、脱敏结果、审批决策和审批人信息。日志存储用独立的存储卷不和业务数据混在一起避免被误删。5.4 性能优化与容量规划性能优化这块我主要做了三件事模型推理优化、工具调用优化、编排逻辑优化。模型推理优化方面除了前面提到的连续批处理和量化我还用了前缀缓存。很多请求的前缀是相同的比如系统提示词和工具描述这部分可以缓存起来不用每次重新计算。vLLM 支持自动前缀缓存开启后推理速度有明显提升。工具调用优化方面我把一些耗时的工具调用改成了异步。比如知识库检索原来是同步等待结果改成异步之后Agent 可以同时发起多个检索请求最后统一收集结果。这样整体耗时从串行的 N 倍变成了并行的 1 倍左右。编排逻辑优化方面我减少了不必要的模型调用。原来每个步骤都要调模型做决策后来发现很多步骤的决策是固定的可以直接用规则判断不需要调模型。这样既减少了模型负载也降低了延迟。容量规划方面我根据基准测试的数据估算了一下。单张 GPU 卡大概能支撑每秒 5 到 10 个并发请求具体取决于请求长度和模型大小。按照日常峰值 50 并发估算至少需要 5 到 10 张卡。实际部署时我留了 50% 余量用了 8 张卡目前运行稳定。6. 一些踩坑之后的个人体会这个项目做下来最大的体会是内网环境下的 AI Agent难点不在 AI而在工程。模型选型、推理优化这些当然重要但真正决定项目能不能落地的是审批机制、工具封装、日志审计这些“脏活累活”。我见过太多团队在模型上花了很多精力最后卡在审批流程上项目推不动。另一个体会是不要追求一步到位。我一开始想做一个全自动的 Agent所有任务都自动处理不需要人工介入。后来发现不现实审批要人工、异常要人工、规则维护要人工。接受“人机协同”的现实之后架构反而更简单了落地也更快了。先跑通核心流程再逐步自动化这个节奏比较稳。还有一个细节值得分享日志的粒度要足够细但不要细到无法维护。我一开始把每个模型调用的完整 prompt 和 response 都记下来结果日志量爆炸存储成本很高排查的时候也很难找到关键信息。后来改成只记录关键字段请求 ID、任务类型、模型名称、输入摘要、输出摘要、耗时、状态。这样日志量可控排查也够用。最后说一个关于 Skills 的观察。Skills 机制确实能提升复用性但前提是 Skill 的抽象层次要对。我一开始把 Skill 做得太细一个 Skill 只做一件事结果一个任务要串十几个 Skill编排逻辑复杂得没法维护。后来把 Skill 的粒度调粗一个 Skill 对应一个完整的业务动作编排逻辑就清晰多了。这个粒度怎么定我的经验是如果一个 Skill 的 YAML 文件超过 50 行或者执行步骤超过 5 步就考虑拆如果两个 Skill 经常一起出现就考虑合。