资讯动态

Docker 部署 DeepSeek 与 Harness:局域网 AI Agent 平台实战

发布时间:2026/10/14 12:10:29 来源:尧图企业网站定制
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 接入自己的应用。这四层之间的数据流是这样的用户请求先到 HarnessHarness 根据会话状态决定要不要调用工具然后把最终的提示词发给 vLLMvLLM 返回结果后 Harness 再决定是否继续调用工具或直接返回给用户。整个过程中模型权重和会话数据都留在局域网内不经过任何外部服务。注意模型权重文件通常有几十 GB下载和挂载的时候要确保磁盘空间充足并且最好放在 SSD 上机械硬盘加载速度会让你等到怀疑人生。2. 部署前的环境准备与关键决策2.1 硬件选型的几个硬指标部署之前先确认你的机器能不能扛得住。DeepSeek 系列有不同的参数规模我这次用的是 32B 的版本量化后大约需要 20GB 左右的显存。如果你用的是 7B 或 14B 的版本显存需求会低很多但效果也会打折扣。下面这张表是我实测下来的参考数据模型规模量化方式显存占用推荐显卡适用场景7BFP16约 15GBRTX 4080个人测试、轻量问答14BINT8约 16GBRTX 4090小团队内部使用32BINT4约 20GBA6000 / 双卡 4090中等规模团队、工具调用70BINT4约 40GBA100 80G高要求场景、复杂推理我这次用的是单张 A600048GB 显存跑 32B 的 INT4 量化版本绰绰有余还能留出空间给 Harness 和系统缓存。如果你只有一张 409024GB 显存跑 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 --runtimedocker sudo systemctl restart docker装完之后用docker run --rm --gpus all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi验证一下如果能正常输出显卡信息说明环境没问题。提示如果你用的是较新的驱动版本比如 545 或 550CUDA 版本也要相应调整。驱动和 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_DEVICES0 - MODEL_NAMEdeepseek-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_ENDPOINThttp://vllm-server:8000/v1 - MODEL_NAMEdeepseek-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.80.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-32breserve_tokens是留给模型回复的 token 数window_size是保留的完整对话轮数。这两个参数需要根据实际使用情况调整如果发现模型经常“失忆”就把window_size调大如果发现回复被截断就把reserve_tokens调大。4.2 工具注册与调用流程Harness 的工具调用机制是基于提示词工程实现的。你需要在配置文件里定义工具的名称、描述、参数 schemaHarness 会把这些信息注入到系统提示词里模型在需要的时候会输出一个特定的格式来调用工具。我注册了三个内部工具一个是查询内部知识库的一个是读取数据库指标的一个是发送通知的。以知识库查询为例配置大概是这样的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 的访问控制每个用户或每个应用分配一个 tokenHarness 根据 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: 30rate_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-seqsvLLM 同时处理的请求数默认 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 平台希望这些经验能帮你少走点弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑