☰
Docker 部署 DeepSeek 与 Harness:局域网 AI Agent 平台实战
2026/10/14 12:10:25 网站建设 项目流程

1. 为什么要在局域网里自建 AI Agent 平台

1.1 从“调用云端 API”到“把模型搬进机房”的转变

过去一年,我身边不少做企业内部工具的朋友都经历过同一个阶段:一开始大家用云端大模型 API 做原型,跑得挺顺,但一旦要接入内部知识库、客户数据、生产日志,问题就来了。数据出域这件事,在很多团队里是红线,尤其是做工业软件、医疗信息化、金融后台的团队,根本不可能把原始数据往外部服务上送。于是“把模型部署到局域网内”就成了一个绕不开的需求。

DeepSeek 系列模型开源之后,本地化部署的门槛被大幅拉低。但真正动手的人会发现,光把模型跑起来只是第一步。你要的是一个能对话、能调用工具、能记住上下文、能接入内部系统的 AI Agent 平台,而不是一个只会吐字的命令行窗口。这就是 Harness 这类框架存在的意义——它把模型、工具调用、会话管理、接口层打包成一个可以对外提供服务的整体。

我这次做的事情,就是用 Docker 把 DeepSeek 模型和 Harness 框架组合起来,部署在一台局域网内的服务器上,让团队里任何一台电脑都能通过浏览器或接口访问。整个过程不需要公网,不需要复杂的环境配置,一台有显卡的机器加一份 docker-compose 文件就能跑起来。

1.2 这套方案适合谁,不适合谁

先说适合的场景。如果你所在的团队有以下特征,这套方案基本可以直接抄作业:第一,有内部数据不能外传的硬性要求;第二,团队规模在几人到几十人之间,不需要做大规模并发;第三,有一台带独立显卡的服务器或工作站,显存至少 24GB 起步;第四,希望有一个统一的入口来管理对话、工具和权限。

不适合的情况也要说清楚。如果你只是个人玩玩,想快速体验 DeepSeek 的对话能力,那直接跑一个 Ollama 加个前端就够了,没必要上 Harness 这套。如果你的并发需求是几百人同时在线,那单卡方案撑不住,需要考虑多卡推理或者模型量化后的分布式部署。另外,如果你对工具调用的定制化要求极高,Harness 的默认工具集可能不够用,需要自己写插件,这部分我会在后面的章节里展开。

1.3 整体架构长什么样

在动手之前,先把架构讲清楚,后面每一步操作你才知道自己在干什么。整套系统分成四层:

  • 模型推理层:负责加载 DeepSeek 模型权重,提供推理能力。我选用的是 vLLM 作为推理引擎,原因是它对 DeepSeek 系列的支持比较成熟,而且自带 OpenAI 兼容的接口,省去了自己写适配层的麻烦。
  • Agent 框架层:Harness 运行在这一层,负责会话管理、工具注册、提示词编排、上下文裁剪。它通过 HTTP 调用推理层的接口,对外暴露自己的 API 和 Web 界面。
  • 容器编排层:Docker 和 docker-compose 负责把上面两层打包成容器,管理网络、卷挂载、环境变量和启动顺序。
  • 访问层:局域网内的用户通过浏览器访问 Harness 的 Web 界面,或者通过 API 接入自己的应用。

这四层之间的数据流是这样的:用户请求先到 Harness,Harness 根据会话状态决定要不要调用工具,然后把最终的提示词发给 vLLM,vLLM 返回结果后 Harness 再决定是否继续调用工具或直接返回给用户。整个过程中,模型权重和会话数据都留在局域网内,不经过任何外部服务。

注意:模型权重文件通常有几十 GB,下载和挂载的时候要确保磁盘空间充足,并且最好放在 SSD 上,机械硬盘加载速度会让你等到怀疑人生。

2. 部署前的环境准备与关键决策

2.1 硬件选型的几个硬指标

部署之前,先确认你的机器能不能扛得住。DeepSeek 系列有不同的参数规模,我这次用的是 32B 的版本,量化后大约需要 20GB 左右的显存。如果你用的是 7B 或 14B 的版本,显存需求会低很多,但效果也会打折扣。下面这张表是我实测下来的参考数据:

模型规模量化方式显存占用推荐显卡适用场景
7BFP16约 15GBRTX 4080个人测试、轻量问答
14BINT8约 16GBRTX 4090小团队内部使用
32BINT4约 20GBA6000 / 双卡 4090中等规模团队、工具调用
70BINT4约 40GBA100 80G高要求场景、复杂推理

我这次用的是单张 A6000,48GB 显存,跑 32B 的 INT4 量化版本绰绰有余,还能留出空间给 Harness 和系统缓存。如果你只有一张 4090,24GB 显存,跑 14B 的 INT8 版本是比较稳妥的选择,32B 的 INT4 也能跑,但上下文长度要控制一下,不然容易爆显存。

CPU 和内存方面,建议至少 16 核 CPU 和 64GB 内存。模型加载的时候会先把权重读到内存再传到显存,内存不够会导致加载失败。磁盘方面,模型文件加上 Docker 镜像和日志,预留 200GB 空间比较保险。

2.2 软件环境的版本锁定

Docker 和 NVIDIA 驱动的版本兼容性是个老生常谈的坑。我这次用的组合是:Ubuntu 22.04 LTS、NVIDIA 驱动 535 系列、CUDA 12.1、Docker 24.0 以上、docker-compose v2。这个组合在我这边跑了三个月,没有出现过驱动崩溃或容器无法访问显卡的问题。

安装 Docker 的时候,不要用系统自带的版本,那个通常太旧。用官方脚本安装最新稳定版:

curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $USER

装完之后记得重新登录,让用户组权限生效。然后安装 NVIDIA Container Toolkit,这是让 Docker 容器能访问显卡的关键:

distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/libnvidia-container/gpgkey | sudo apt-key add - curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list sudo apt-get update sudo apt-get install -y nvidia-container-toolkit sudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart docker

装完之后用docker run --rm --gpus all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi验证一下,如果能正常输出显卡信息,说明环境没问题。

提示:如果你用的是较新的驱动版本,比如 545 或 550,CUDA 版本也要相应调整。驱动和 CUDA 的对应关系可以去 NVIDIA 官方文档查,不要凭感觉选。

2.3 模型文件的获取与存放

DeepSeek 的模型权重可以从官方渠道获取,我这里不展开具体来源,只说存放方式。我习惯把模型文件放在/data/models/deepseek-32b这样的路径下,然后在 docker-compose 里通过卷挂载到容器内。这样做的好处是模型文件独立于容器,容器重建的时候不需要重新下载。

模型目录的结构大概是这样的:

/data/models/deepseek-32b/ ├── config.json ├── tokenizer.json ├── tokenizer_config.json ├── model-00001-of-00008.safetensors ├── model-00002-of-00008.safetensors └── ...

如果你用的是量化版本,可能还会有额外的量化配置文件。确保所有文件都下载完整,缺一个文件都会导致加载失败。我一般会用sha256sum校验一下,虽然麻烦,但比加载到一半报错要省时间。

3. 用 Docker Compose 编排整套服务

3.1 服务拆分与网络设计

整套系统我拆成了两个服务:vllm-server和harness。它们跑在同一个自定义 bridge 网络里,通过服务名互相访问。这样做的好处是隔离性好,不会和宿主机上其他服务抢端口,而且容器之间的通信不走宿主机网络,延迟更低。

docker-compose 文件的结构大致如下:

version: '3.8' services: vllm-server: image: vllm/vllm-openai:latest runtime: nvidia environment: - NVIDIA_VISIBLE_DEVICES=0 - MODEL_NAME=deepseek-32b volumes: - /data/models/deepseek-32b:/models/deepseek-32b command: > --model /models/deepseek-32b --tensor-parallel-size 1 --max-model-len 8192 --gpu-memory-utilization 0.85 --served-model-name deepseek-32b ports: - "8000:8000" networks: - ai-net deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] harness: image: harness/agent:latest environment: - MODEL_ENDPOINT=http://vllm-server:8000/v1 - MODEL_NAME=deepseek-32b - SESSION_STORE=/data/sessions volumes: - /data/harness/sessions:/data/sessions - /data/harness/config:/app/config ports: - "3000:3000" depends_on: - vllm-server networks: - ai-net networks: ai-net: driver: bridge

这里有几个关键点需要解释。tensor-parallel-size设置为 1,因为我是单卡。如果你有多张卡,可以设置为卡的数量,vLLM 会自动做张量并行。max-model-len设置为 8192,这是上下文长度,设置得越大显存占用越高,需要根据你的显存情况调整。gpu-memory-utilization设置为 0.85,意思是让 vLLM 使用 85% 的显存,留出 15% 给系统和其他进程,这个比例我试过 0.9 和 0.8,0.85 是比较稳的平衡点。

3.2 环境变量的配置细节

Harness 的配置主要通过环境变量传入。除了上面提到的MODEL_ENDPOINT和MODEL_NAME,还有几个比较重要的:

  • MAX_TOKENS:单次回复的最大 token 数,默认 2048,我一般设置成 4096,方便处理长回复。
  • TEMPERATURE:温度参数,控制回复的随机性。内部工具场景我建议设置成 0.3 左右,太高的温度会让工具调用变得不稳定。
  • TOOL_TIMEOUT:工具调用的超时时间,默认 30 秒,如果你的工具涉及外部接口调用,可以适当调大。
  • SESSION_TTL:会话过期时间,默认 24 小时,根据团队使用习惯调整。

这些参数我建议先在配置文件里写好,然后通过卷挂载进去,而不是全部写在 docker-compose 里。这样修改的时候不需要重建容器,重启一下服务就行。

3.3 启动顺序与健康检查

depends_on只能保证容器启动顺序,不能保证服务就绪。vLLM 加载模型需要几分钟时间,如果 Harness 在 vLLM 还没就绪的时候就发请求,会直接报错。所以我在 Harness 的启动脚本里加了一个等待逻辑:

#!/bin/bash until curl -s http://vllm-server:8000/v1/models | grep -q "deepseek-32b"; do echo "Waiting for vLLM server..." sleep 5 done echo "vLLM server is ready, starting Harness..." exec /app/start.sh

这个脚本会每 5 秒检查一次 vLLM 的模型列表接口,直到模型加载完成才启动 Harness。虽然简单,但能避免很多“服务启动失败”的假象。

注意:vLLM 首次加载模型的时候会做一次编译优化,时间可能比后续启动长很多。我这边 32B 模型首次加载花了将近 8 分钟,后面再启动就只要 2 分钟左右。不要因为第一次启动慢就以为卡死了。

4. Harness 的核心配置与工具接入

4.1 会话管理与上下文裁剪策略

Harness 的会话管理是我比较看重的功能。它把每个用户的对话历史存在本地,支持多轮对话和上下文继承。但上下文长度是有限的,32B 模型我设置的是 8192 token,如果对话轮次多了,历史消息会占满上下文,导致模型“忘记”前面的内容。

Harness 提供了几种上下文裁剪策略,我实测下来比较有效的是“滑动窗口加摘要”的组合。具体做法是:保留最近 N 轮完整对话,更早的对话用模型生成一段摘要,把摘要作为系统消息插入到上下文开头。这样既保留了关键信息,又控制了 token 消耗。

配置方式是在 Harness 的配置文件里设置:

context: strategy: sliding_window_with_summary max_tokens: 8192 reserve_tokens: 2048 window_size: 10 summary_model: deepseek-32b

reserve_tokens是留给模型回复的 token 数,window_size是保留的完整对话轮数。这两个参数需要根据实际使用情况调整,如果发现模型经常“失忆”,就把window_size调大;如果发现回复被截断,就把reserve_tokens调大。

4.2 工具注册与调用流程

Harness 的工具调用机制是基于提示词工程实现的。你需要在配置文件里定义工具的名称、描述、参数 schema,Harness 会把这些信息注入到系统提示词里,模型在需要的时候会输出一个特定的格式来调用工具。

我注册了三个内部工具:一个是查询内部知识库的,一个是读取数据库指标的,一个是发送通知的。以知识库查询为例,配置大概是这样的:

tools: - name: query_knowledge_base description: 查询内部知识库,输入关键词返回相关文档片段 parameters: type: object properties: query: type: string description: 查询关键词 top_k: type: integer description: 返回结果数量,默认 5 required: - query endpoint: http://internal-kb:8080/search method: POST timeout: 10

模型在对话中如果判断需要查询知识库,会输出类似{"tool": "query_knowledge_base", "parameters": {"query": "部署文档", "top_k": 3}}的内容,Harness 解析后调用对应的接口,把结果再喂回给模型,模型基于结果生成最终回复。

这个流程听起来简单,但实际调试的时候有几个坑。第一,工具描述要写得足够清晰,模型才能正确判断什么时候该调用。第二,参数 schema 要严格,模型有时候会输出不符合 schema 的参数,Harness 需要做校验和重试。第三,工具返回的结果要控制长度,太长的结果会挤占上下文。

4.3 权限控制与访问隔离

局域网内部使用虽然不像公网那样需要严防死守,但基本的权限控制还是要做的。Harness 支持基于 token 的访问控制,每个用户或每个应用分配一个 token,Harness 根据 token 识别用户身份,记录会话归属。

我在配置文件里开启了 token 认证:

auth: enabled: true type: bearer_token tokens: - name: team-a token: sk-team-a-xxxxxxxx rate_limit: 60 - name: team-b token: sk-team-b-xxxxxxxx rate_limit: 30

rate_limit是每分钟的请求数限制,防止某个团队把资源占满。这个限制我设置得比较宽松,因为内部使用并发不高,主要是防止误操作导致的请求风暴。

另外,Harness 的 Web 界面也支持简单的登录认证,可以设置用户名密码,避免局域网内任何人都能访问。虽然局域网相对安全,但多一层防护总归是好的。

5. 实操过程中的问题排查与经验总结

5.1 模型加载失败的几种典型情况

模型加载失败是我遇到最多的问题,表现是 vLLM 容器启动后不断重启,日志里报各种错误。下面这张表是我整理出来的常见原因和解决方法:

错误现象可能原因解决方法
CUDA out of memory显存不足降低gpu-memory-utilization,或换更小的量化版本
File not found模型路径挂载错误检查卷挂载路径,确保容器内路径和 command 里一致
Unsupported model模型格式不兼容确认 vLLM 版本支持该模型架构,必要时升级镜像
Tokenizer errortokenizer 文件缺失检查模型目录是否包含完整的 tokenizer 文件
NCCL error多卡通信问题单卡时设置tensor-parallel-size 1,多卡时检查 NCCL 配置

我印象最深的一次是模型加载到 90% 的时候报显存不足,日志显示是 KV cache 分配失败。后来把max-model-len从 16384 降到 8192,问题就解决了。KV cache 的大小和上下文长度是平方关系,上下文翻倍,KV cache 占用会翻四倍,这个账一定要提前算清楚。

5.2 Harness 连接 vLLM 超时的排查思路

Harness 启动后如果连不上 vLLM,表现是 Web 界面能打开,但发消息一直转圈,最后报超时。排查的时候按这个顺序来:

第一步,在 Harness 容器里curl http://vllm-server:8000/v1/models,看能不能通。如果不通,说明网络有问题,检查两个容器是不是在同一个 network 里。

第二步,如果网络通但返回很慢,说明 vLLM 还在加载模型,等几分钟再试。

第三步,如果 vLLM 返回正常但 Harness 还是超时,检查 Harness 的MODEL_ENDPOINT配置,确保没有多写或少写/v1路径。

第四步,如果以上都正常,检查 Harness 的日志,看是不是工具调用卡住了。我有一次是因为知识库接口响应太慢,导致整个请求超时,后来把TOOL_TIMEOUT从 10 秒调到 30 秒就好了。

提示:排查网络问题的时候,docker exec -it harness bash进入容器,用curl和ping是最直接的方法。不要在外面猜,进去测一下比什么都快。

5.3 性能调优的几个实用参数

跑起来之后,如果觉得响应速度不够快,可以调这几个参数:

  • --max-num-seqs:vLLM 同时处理的请求数,默认 256,单卡场景可以降到 64,减少显存碎片。
  • --max-num-batched-tokens:批处理的最大 token 数,默认 4096,调大能提高吞吐,但会增加延迟。
  • --enable-prefix-caching:开启前缀缓存,多轮对话场景下能显著减少重复计算,我实测能降低 30% 左右的延迟。

Harness 这边,MAX_TOKENS不要设置得太大,4096 对于大多数场景足够了。设置得太大,模型生成的时间会线性增加,用户体验反而不好。

5.4 日常维护与监控建议

系统跑起来之后,日常维护主要是看日志和监控资源。我习惯用docker stats看容器的 CPU、内存、显存占用,用docker logs -f看实时日志。如果发现显存占用持续上涨,可能是会话数据没有及时清理,检查一下SESSION_TTL配置。

另外,建议给模型文件和会话数据做定期备份。模型文件虽然不常变,但重新下载很耗时;会话数据虽然可以重建,但丢失了会影响用户体验。我一般每周备份一次会话数据,模型文件只在更新版本的时候备份。

最后分享一个我踩过的坑:Docker 的日志文件默认不限制大小,跑久了会把磁盘占满。在 docker-compose 里加上日志轮转配置:

logging: driver: json-file options: max-size: "100m" max-file: "3"

这样每个容器最多保留 300MB 日志,不会把磁盘撑爆。这个配置我建议所有容器都加上,尤其是 vLLM 这种日志量大的服务。

这套方案我在团队内部跑了三个月,日均处理几百次对话请求,稳定性没问题。中间遇到过两次显存泄漏,都是因为会话数据没清理干净,调整SESSION_TTL之后就再没出现过。如果你也在考虑局域网内部署 AI Agent 平台,希望这些经验能帮你少走点弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询