1. 为什么要在局域网里自建 AI Agent 平台
把大模型能力搬进局域网,这件事在两年前还属于"折腾党"的玩具,现在已经变成很多小团队和个人的刚需。原因很直接:公网 API 按 token 计费,长期跑 Agent 任务(尤其是带工具调用、多轮反思的那种)成本会失控;而数据一旦离开内网,很多场景根本不允许。所以"本地推理 + 局域网共享"这套组合,正在成为个人开发者和小团队的标准姿势。
DeepSeek 系列模型这两年在开源社区热度很高,尤其是它的推理能力和工具调用表现,配合合适的量化版本,在消费级显卡甚至纯 CPU 上都能跑起来。但问题在于:模型跑起来只是第一步,真正难的是把它包装成一个可被局域网内多台设备访问、支持多轮对话、能挂载工具、还能管理会话的 Agent 平台。这就是标题里"Harness"这个词的价值所在——它不是模型本身,而是套在模型外面的那层"马具",负责把裸模型变成可用的服务。
我这次做的事情,就是用 Docker 把 DeepSeek 模型和一层 Agent 编排框架打包起来,部署在一台内网机器上,让局域网里的笔记本、平板、甚至手机浏览器都能直接访问。整个过程踩了不少坑,从镜像体积、GPU 透传、端口规划到会话持久化,每一步都有讲究。这篇文章我会把完整的思路、选型理由、实操步骤和踩坑记录都摊开讲,适合以下人群:
- 手里有一台带独显的机器(或一台性能尚可的服务器),想把它变成团队内部的 AI 入口;
- 已经会用 Docker 跑一些基础服务,但对 GPU 容器、模型服务编排不太熟;
- 想给团队搭一个"数据不出内网"的 Agent 实验环境,用于内部工具调用、文档问答等场景。
需要提前说明的是,本文不涉及任何网络穿透、外部访问相关的内容,所有讨论都严格限定在局域网内部的部署与访问。这也是自建方案最核心的价值——闭环、可控、可审计。
2. 拆解需求:一个"局域网 AI Agent 平台"到底要包含哪些部件
很多人一上来就docker run一个模型镜像,跑通了就以为完事了,结果发现局域网里其他机器根本连不上,或者连上了但没法多轮对话、没法挂工具。这是因为"能推理"和"能当平台用"之间差了好几个组件。我先把这套系统的组成拆清楚,后面部署才不会乱。
2.1 模型推理层:DeepSeek 用什么形态跑
DeepSeek 官方放出的模型有多个尺寸和量化版本,选择哪个直接决定了你的硬件门槛和响应速度。我的经验是分三档来看:
| 硬件条件 | 推荐模型形态 | 量化方式 | 典型场景 |
|---|---|---|---|
| 单卡 24G 显存 | 中等尺寸指令模型 | 4bit 量化 | 团队日常问答、工具调用 |
| 单卡 12G 显存 | 较小尺寸指令模型 | 4bit 量化 | 个人使用、轻量 Agent |
| 纯 CPU / 核显 | 小尺寸模型 | GGUF Q4 | 低频调用、演示验证 |
这里有个关键判断:Agent 场景比纯聊天更吃显存。因为 Agent 要维护系统提示词、工具定义、历史对话、工具返回结果,上下文长度往往是普通聊天的 3 到 5 倍。所以如果你按"聊天够用"的标准选模型,跑 Agent 时大概率会爆显存或者疯狂降速。我的建议是留出至少 30% 的显存余量给上下文。
推理引擎方面,主流选择是 vLLM 和 Ollama 两条路线。vLLM 吞吐高、并发强,适合多人同时用,但对显存要求更硬;Ollama 部署简单、量化支持好、CPU 也能跑,适合快速起步。我这次选的是 Ollama 作为推理后端,原因是它和 Docker 的配合最省心,模型管理也直观,后面要换模型只需要ollama pull一下。
2.2 Agent 编排层:Harness 到底管什么
"Harness"这个词在 AI 工程里指的是把模型包装成 Agent 的那层框架。它要负责的事情比想象中多:
- 会话管理:每个用户的对话历史要隔离存储,不能串台;
- 工具注册与调用:把外部函数(查数据库、读文件、调内部接口)暴露给模型,并解析模型的调用意图;
- 提示词模板:系统提示、角色设定、few-shot 示例的统一管理;
- 流式输出:把模型的 token 流实时推给前端,而不是等全部生成完;
- 错误重试与降级:模型调用失败、工具超时时的兜底逻辑。
市面上能承担这个角色的框架不少,有偏轻量的,也有偏企业级的。选型时我主要看三点:是否支持 OpenAI 兼容接口(这样能无缝对接 Ollama)、是否容易容器化、社区是否活跃。最终我用的是一套支持 OpenAI 协议、自带 Web UI 和工具插件机制的框架,具体名字这里不点名,重点是它的架构思路——前端、编排、推理三层解耦,每层都能独立替换。
2.3 访问层:局域网内怎么让所有设备都能用
这一层最容易被忽略。模型和编排都跑在服务器上,但用户是在自己的笔记本、平板上访问的。要解决几个问题:
- 端口暴露:容器端口要映射到宿主机,且宿主机防火墙要放行局域网网段;
- 访问地址:不能用
localhost,要用宿主机的局域网 IP,否则其他设备访问不到; - 跨设备兼容:Web UI 要能适配不同屏幕,最好不依赖特定客户端;
- 并发承载:多人同时访问时,推理层要能排队而不是直接崩。
我见过太多人卡在"本机访问正常、别的机器打不开"这一步,其实九成是端口绑定到了127.0.0.1而不是0.0.0.0。这个细节后面会专门讲。
3. 部署前的环境盘点与关键决策
动手之前,先把环境和决策理清楚,能省掉后面大量的返工。这一节我把自己实际做的盘点和取舍过程完整写出来,你可以对照自己的情况调整。
3.1 硬件与系统的最低门槛
先说结论:能跑起来的最低配置是一台 4 核 8G 内存、带 20G 以上空闲磁盘的机器,纯 CPU 也能跑小模型,但响应速度会让你怀疑人生。推荐配置是 8 核 16G 内存 + 一张 12G 以上显存的显卡,这样体验才谈得上"可用"。
系统层面,Linux 是首选,Ubuntu 22.04 或更新版本最省事,因为 NVIDIA 驱动和容器工具链的生态最成熟。Windows 也能做,但要走 WSL2,多一层转发,GPU 透传的坑会更多。我这次用的是 Ubuntu 服务器版,无桌面环境,所有操作走 SSH。
磁盘规划要单独说一句:模型文件动辄几个 G 到几十个 G,加上 Docker 镜像和容器层,很容易把系统盘撑爆。我的做法是把 Docker 的数据目录和模型存储目录都挂到一块独立的大容量盘上,具体操作后面有。
3.2 推理后端选型:为什么是 Ollama 而不是别的
在 vLLM、Ollama、以及各种自建推理服务之间,我最终选 Ollama,理由如下:
- 模型管理简单:一条命令拉取、一条命令切换,不用手动下载权重、配置 tokenizer;
- 量化开箱即用:官方库里的模型大多已经做好量化,省去自己转换的麻烦;
- Docker 镜像成熟:官方镜像支持 GPU 透传,文档清晰;
- OpenAI 兼容接口:自带
/v1/chat/completions端点,编排层可以直接对接。
vLLM 的优势在并发和吞吐,如果你要支撑几十人同时高频调用,它更合适。但代价是配置复杂、显存占用更激进、模型格式要求更严。对于小团队和个人,Ollama 的性价比明显更高。这个取舍没有绝对对错,取决于你的并发规模。
3.3 网络与端口规划:提前画好一张表
端口冲突是部署时最常见的低级错误。我习惯在动手前先画一张端口分配表,把每个服务的端口、用途、是否对外都列清楚:
| 服务 | 容器内端口 | 宿主机端口 | 是否局域网可见 | 用途 |
|---|---|---|---|---|
| 推理后端 | 11434 | 11434 | 否(仅内部) | 模型推理 API |
| Agent 编排 | 3000 | 3000 | 是 | Web UI 与 API |
| 会话存储 | 6379 | 不映射 | 否 | 缓存与队列 |
核心原则:推理后端不对局域网暴露,只让编排层访问它。这样外部只能通过编排层间接调用模型,安全边界清晰,也方便做统一的鉴权和限流。很多人图省事把推理端口也开出去,结果内网里谁都能直接刷模型,既没审计也容易被打满。
4. 从零到跑通:Docker 部署的完整实操链路
这一节是全文的核心,我会按实际操作顺序,把每一步的命令、参数含义、以及为什么这么做都讲清楚。你可以直接照着敲,但建议先理解每一步的意图,出问题时才知道从哪查。
4.1 基础环境准备:驱动、容器工具链、目录规划
第一步是把 GPU 驱动和容器工具链装好。如果你只用 CPU,可以跳过驱动部分,但容器运行时还是要装。
# 确认显卡和驱动状态,有输出说明驱动正常 nvidia-smi # 安装容器工具链(以 Ubuntu 为例) sudo apt-get update sudo apt-get install -y docker.io docker-compose-plugin # 安装 NVIDIA 容器运行时,让容器能用上 GPU sudo apt-get install -y nvidia-container-toolkit sudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart docker装完后一定要验证容器能不能看到 GPU:
docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi如果这条命令能打印出显卡信息,说明 GPU 透传链路通了。这一步不过,后面所有 GPU 相关的配置都是白搭。
接下来规划目录。我的习惯是在大容量盘上建一个统一的工作目录,把模型、配置、数据都放进去:
sudo mkdir -p /data/ai-platform/{models,config,data} sudo chown -R $USER:$USER /data/ai-platform然后把 Docker 的数据目录也迁过去,避免系统盘被镜像撑爆。修改/etc/docker/daemon.json:
{ "data-root": "/data/docker", "runtimes": { "nvidia": { "path": "nvidia-container-runtime", "runtimeArgs": [] } } }改完重启 Docker 服务。注意>docker run -d \ --name ollama \ --gpus all \ --restart unless-stopped \ -p 127.0.0.1:11434:11434 \ -v /data/ai-platform/models:/root/.ollama \ ollama/ollama:latest
逐条解释这些参数为什么这么写:
--gpus all:把宿主机所有 GPU 透传给容器,Ollama 会自动检测并使用;--restart unless-stopped:机器重启后容器自动拉起,省得每次手动启动;-p 127.0.0.1:11434:11434:注意这里绑的是 127.0.0.1 而不是 0.0.0.0,意味着只有宿主机本机能访问推理端口,局域网其他设备访问不到。这正是我们要的安全边界;-v .../models:/root/.ollama:把模型文件持久化到宿主机,容器删了模型还在,不用重新下载。
容器起来后,进容器拉模型:
docker exec -it ollama ollama pull deepseek-r1:7b模型尺寸根据你的显存选。拉完后验证一下:
curl http://127.0.0.1:11434/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-r1:7b", "messages": [{"role": "user", "content": "你好"}] }'能返回内容就说明推理层通了。这里有个实测经验:首次调用会明显慢,因为模型要从磁盘加载进显存,第二次开始才是正常速度。别以为第一次卡住就是配置错了。
4.3 部署 Agent 编排层:让模型变成"平台"
推理层只是裸模型,编排层才是把它变成平台的关键。我用的是支持 OpenAI 兼容接口的编排框架,通过环境变量指向 Ollama 的地址。
docker run -d \ --name agent-harness \ --restart unless-stopped \ -p 0.0.0.0:3000:3000 \ -e OPENAI_API_BASE=http://host.docker.internal:11434/v1 \ -e OPENAI_API_KEY=dummy \ -e DEFAULT_MODEL=deepseek-r1:7b \ -v /data/ai-platform/config:/app/config \ -v /data/ai-platform/data:/app/data \ your-agent-image:latest几个关键点:
-p 0.0.0.0:3000:3000:编排层要对外,所以绑0.0.0.0,局域网设备才能访问;OPENAI_API_BASE指向host.docker.internal:这是容器访问宿主机的特殊域名。在 Linux 上需要额外加--add-host=host.docker.internal:host-gateway,否则解析不了。这是 Linux 和 Mac 的一个差异,很多人在这里卡住;OPENAI_API_KEY=dummy:Ollama 不校验 key,但很多框架强制要求这个字段非空,随便填一个即可;- 两个
-v挂载:配置和会话数据持久化,容器重建不丢数据。
完整的 Linux 版本命令要加上 host 映射:
docker run -d \ --name agent-harness \ --add-host=host.docker.internal:host-gateway \ --restart unless-stopped \ -p 0.0.0.0:3000:3000 \ -e OPENAI_API_BASE=http://host.docker.internal:11434/v1 \ -e OPENAI_API_KEY=dummy \ -e DEFAULT_MODEL=deepseek-r1:7b \ -v /data/ai-platform/config:/app/config \ -v /data/ai-platform/data:/app/data \ your-agent-image:latest4.4 用 Compose 把两层编排起来
手动docker run适合调试,长期运行建议用 Compose 管理,好处是依赖关系、网络、重启策略都能声明式配置。
version: "3.8" services: ollama: image: ollama/ollama:latest container_name: ollama restart: unless-stopped ports: - "127.0.0.1:11434:11434" volumes: - /data/ai-platform/models:/root/.ollama deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] agent-harness: image: your-agent-image:latest container_name: agent-harness restart: unless-stopped depends_on: - ollama ports: - "0.0.0.0:3000:3000" environment: - OPENAI_API_BASE=http://ollama:11434/v1 - OPENAI_API_KEY=dummy - DEFAULT_MODEL=deepseek-r1:7b volumes: - /data/ai-platform/config:/app/config - /data/ai-platform/data:/app/data注意这里有个优化:用 Compose 时两个容器在同一个自定义网络里,编排层可以直接用服务名ollama访问推理层,不需要host.docker.internal那套。这比手动 run 更干净,也避免了 host 映射的兼容性问题。这是我强烈推荐用 Compose 的原因之一。
启动:
docker compose up -d docker compose logs -f看日志确认两个容器都正常起来,编排层能连上推理层。
5. 局域网访问打通与多人共用的调优
容器跑起来只是"本机能用",要变成"局域网平台",还有几道关要过。这一节讲访问打通和多人场景下的调优。
5.1 从 localhost 到局域网 IP:访问不通的排查顺序
局域网其他设备访问http://宿主机IP:3000打不开时,按这个顺序排查,基本能定位到问题:
- 确认端口绑定:
docker ps看端口映射,如果是127.0.0.1:3000->3000,那外部永远访问不到,必须改成0.0.0.0; - 确认宿主机防火墙:
sudo ufw status,如果开了防火墙,要放行 3000 端口,且限定局域网网段,比如sudo ufw allow from 192.168.1.0/24 to any port 3000; - 确认宿主机 IP:
ip addr找到局域网网卡的 IP,别用127.0.0.1; - 从另一台设备测试连通性:
ping 宿主机IP通了再curl http://宿主机IP:3000,逐步缩小范围。
我遇到最多的情况是第 1 条和第 2 条。尤其是用docker run时手滑写成-p 3000:3000(等价于绑所有网卡)还是-p 127.0.0.1:3000:3000(只绑本机),差别巨大,一定要看清楚。
5.2 多人同时用:并发、显存与排队策略
一个人用和五个人用,体验完全是两回事。核心瓶颈在推理层,因为 GPU 是串行处理的。几个调优方向:
- 限制单次上下文长度:Agent 场景上下文容易膨胀,给编排层设一个上限,比如 8K token,超了就截断或摘要,避免单个请求吃满显存;
- 设置并发上限:Ollama 可以通过环境变量控制并行请求数,超过就排队,防止显存溢出导致整个服务崩掉;
- 会话隔离:确保每个用户的会话独立存储,编排层的会话管理要按用户 ID 分区;
- 冷启动优化:设置模型常驻显存,避免每次请求都重新加载。Ollama 有
OLLAMA_KEEP_ALIVE参数控制模型在显存里的驻留时间,设长一点能显著降低首次响应延迟。
实测下来,单张 12G 显卡跑 7B 量化模型,支撑 3 到 5 人低频使用是没问题的,但如果是高频工具调用,建议上更大显存或做请求队列。
5.3 会话持久化:容器重启不丢对话
容器一重启,内存里的会话就没了,用户会骂人。解决办法是把会话数据落到宿主机卷上。大多数编排框架支持配置存储后端,可以是文件、SQLite、或者 Redis。我的做法是:
- 轻量场景:直接用文件存储,挂载到宿主机目录,简单可靠;
- 多人场景:上 Redis,支持并发读写和过期策略。
Redis 也可以容器化,加进 Compose:
session-store: image: redis:7-alpine container_name: session-store restart: unless-stopped volumes: - /data/ai-platform/data/redis:/data然后在编排层配置里把存储后端指向redis://session-store:6379。注意 Redis 不需要对外映射端口,只在内部网络里被编排层访问即可。
6. 踩坑实录:那些文档里不会写的细节
这一节是我这次部署过程中真实踩到的坑,以及排查过程。这些内容官方文档基本不会提,但每一个都能让你卡上半天。
6.1 GPU 透传失败:容器里 nvidia-smi 报错的三种原因
第一次跑 Ollama 容器时,日志里一直报找不到 GPU,回退到 CPU 模式,慢得离谱。排查下来有三种可能:
- 容器工具链没配好:
nvidia-ctk runtime configure没执行,或者执行后没重启 Docker; - daemon.json 里 runtime 配置冲突:如果同时装了旧版 nvidia-docker,配置可能打架,需要清理;
- 驱动版本和 CUDA 镜像不匹配:宿主机驱动太旧,容器里的 CUDA 版本要求更高,导致初始化失败。
排查方法就是那条docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi,从最基础的镜像开始测,逐层排除。别一上来就在 Ollama 容器里查,干扰因素太多。
6.2 容器间通信:host.docker.internal 在 Linux 上不生效
前面提过,Linux 上host.docker.internal默认不解析,需要--add-host手动加。我第一次没加,编排层一直连不上推理层,日志报 DNS 解析失败。后来改用 Compose 自定义网络,直接用服务名通信,问题彻底消失。这也是我推荐 Compose 而非手动 run 的核心原因之一——网络这块省心太多。
6.3 模型加载慢与显存不足的连锁反应
有次拉了个偏大的模型,启动后第一次请求等了快两分钟才返回,期间编排层超时报错。原因是模型加载进显存的时间超过了编排层的默认超时。解决办法有两个:一是调大编排层的请求超时;二是提前"预热",部署完先手动发一个请求把模型加载进显存,再开放给用户。我现在的习惯是部署脚本里加一步预热请求,避免用户第一次访问就吃闭门羹。
6.4 端口冲突与防火墙的隐蔽问题
有一次 3000 端口死活访问不了,docker ps显示映射正常,宿主机curl localhost:3000也通,但局域网其他机器就是不行。最后发现是宿主机上另一套服务占用了 3000,Docker 的端口映射其实没真正生效,只是本机回环走了别的路径。换端口后立刻正常。教训是:部署前用ss -tlnp | grep 3000确认端口真的空闲,别只看 Docker 的输出。
7. 后续可扩展的方向与个人体会
平台跑通之后,能做的事情还有很多。比如给编排层挂载内部工具——查内部文档库、读共享目录里的文件、调用内部数据接口,这些都能通过工具注册机制接进去,让 Agent 真正"能干活"而不只是聊天。再比如做多模型路由,简单问题走小模型、复杂问题走大模型,兼顾成本和效果。还有访问审计,把每次调用记录下来,方便回溯。
我个人在实际操作中的体会是:这套系统的难点从来不在模型本身,而在"把模型变成服务"的那层工程。模型拉下来就能跑,但要让局域网里所有人都能用、用得稳、数据不丢,靠的是容器编排、网络规划、存储持久化这些"传统"工程能力。所以如果你本身有后端或运维基础,上手会非常快;如果只熟悉调模型,建议先把 Docker 网络和卷挂载这两块补一补,能少走很多弯路。
最后分享一个小技巧:部署完成后,写一个简单的健康检查脚本,定时探测编排层和推理层的接口,一旦异常就发通知。这套平台一旦成为团队日常依赖,稳定性就变得很重要,早发现早处理,比事后救火强得多。