1. 内网部署 DeepSeek 的真实困境与破局思路
很多企业想把大模型用起来,第一反应是调云端 API,但真到落地阶段,安全部门一句"数据不能出内网"就把方案打回去了。尤其是金融、医疗、制造业的研发团队,代码、工单、客户资料这些内容一旦离开内网,合规上根本过不去。我接触过几个团队,他们的诉求非常一致:模型要能跑在自己的服务器上,接口要统一,最好还能像调 OpenAI 那样一行代码切换模型。
这就是"企业专属 DeepSeek 智能中台"要解决的问题。核心思路其实不复杂:用 Ollama 在内网服务器上把 DeepSeek 跑起来,再用 OneAPI 做一层统一网关,把 Ollama 的本地模型、以及未来可能接入的其他模型,全部包装成标准的 OpenAI 兼容接口。这样上层业务系统只需要认一个 Base URL 和一个 Key,底层换模型、加模型都不用改业务代码。
为什么选 DeepSeek?因为它在中文推理、代码生成上的表现确实能打,而且开源权重可以本地部署,32B 量化版本在单张 24G 显存的卡上就能跑起来,对中小企业来说门槛可控。为什么选 Ollama?它把模型加载、显存管理、API 暴露这些脏活都封装好了,一条ollama serve就能对外提供 HTTP 接口。为什么还要 OneAPI?因为 Ollama 原生接口虽然能用,但格式和 OpenAI 不完全一致,而且多模型、多 Key、用量统计这些企业级需求它不管,OneAPI 正好补上这块。
整个链路是这样的:业务系统 → OneAPI(统一网关,端口 3000)→ Ollama(本地推理,端口 11434)→ DeepSeek 模型。数据全程在内网流转,不出网卡。下面我把这套中台的搭建过程拆成可复制的步骤,包括离线环境下的模型导入、OneAPI 的渠道配置、以及连通性验证。
2. 前置准备:Ollama 离线安装与 DeepSeek 模型导入
内网服务器最大的坑就是装不了东西。Ollama 官方的一键脚本curl -fsSL https://ollama.com/install.sh | sh在内网直接歇菜,因为脚本本身要联网下载二进制包。所以必须走离线路线:在有网的机器上下载好安装包和模型文件,再通过 U 盘或跳板机传进内网。
先说 Ollama 的离线安装。根据服务器架构(一般是 linux-amd64),在有网机器上执行:
curl -L https://ollama.com/download/ollama-linux-amd64.tgz -o ollama-linux-amd64.tgz下载完通过 scp 或堡垒机传到内网服务器,比如放到/tmp目录。然后解压安装:
sudo tar -C /usr -xzf /tmp/ollama-linux-amd64.tgz解压后/usr/bin/ollama就有了,直接执行ollama serve能跑起来。但为了让它开机自启、方便管理,建议注册成 systemd 服务。先创建专用用户:
sudo useradd -r -s /bin/false -U -m -d /usr/share/ollama ollama sudo usermod -a -G ollama $(whoami)然后在/etc/systemd/system/ollama.service写入:
[Unit] Description=Ollama Service After=network-online.target [Service] ExecStart=/usr/bin/ollama serve User=ollama Group=ollama Restart=always RestartSec=3 Environment="PATH=$PATH" Environment="OLLAMA_HOST=0.0.0.0:11434" [Install] WantedBy=default.target注意这里加了OLLAMA_HOST=0.0.0.0:11434,否则 Ollama 默认只监听 127.0.0.1,OneAPI 容器访问不到。改完执行:
sudo systemctl daemon-reload sudo systemctl enable --now ollama接下来是模型导入,这是内网部署最绕的一步。内网没法ollama pull,得先在本地机器上把模型拉下来,找到模型文件,再传进去。本地执行:
ollama pull deepseek-r1:32b ollama show deepseek-r1:32b --modelfile输出里会有一行FROM C:\Users\xxx\.ollama\models\blobs\sha256-96c415...,这个路径就是模型权重文件。同时把 Modelfile 导出:
ollama show deepseek-r1:32b --modelfile > Modelfile把权重文件和 Modelfile 一起传到内网服务器同一目录,比如/opt/models/deepseek。进去后重命名权重文件,方便识别:
mv sha256-96c415656d377afbff962f6cdb2394ab092ccbcbaab4b82525bc4ca800fe8a49 deepseek-r1_32b.gguf然后编辑 Modelfile,把FROM那行改成相对路径:
FROM ./deepseek-r1_32b.gguf最后导入:
ollama create deepseek-r1:32b -f Modelfile ollama list看到deepseek-r1:32b出现在列表里,模型这步就成了。这里有个细节:Modelfile 里的 TEMPLATE 和 PARAMETER 不要动,那是 DeepSeek 的对话模板,改了会导致输出格式错乱。
3. OneAPI 网关部署与渠道配置可复制片段
模型跑起来只是第一步,要让业务系统方便调用,还得有 OneAPI 这层网关。OneAPI 是开源的 LLM API 管理与分发系统,能把 Ollama、OpenAI、Claude 等各种后端统一成 OpenAI 格式。内网部署同样用 Docker,先写docker-compose.yaml:
services: db: image: mysql:8.1.0 container_name: mysql restart: always environment: MYSQL_ROOT_PASSWORD: mysql-password volumes: - ./data:/var/lib/mysql one-api: image: justsong/one-api container_name: one-api restart: always ports: - "3000:3000" depends_on: - db environment: - SQL_DSN=root:mysql-password@tcp(db:3306)/one_api - TZ=Asia/Shanghai - TIKTOKEN_CACHE_DIR=/TIKTOKEN_CACHE_DIR volumes: - ./data:/data - ./TIKTOKEN_CACHE_DIR:/TIKTOKEN_CACHE_DIR networks: default: name: one-api这里有个内网必踩的坑:OneAPI 依赖 tiktoken 做 token 计数,而 tiktoken 启动时会去openaipublic.blob.core.windows.net下载编码文件,内网直接超时。报错长这样:
[FATAL] failed to get gpt-3.5-turbo token encoder: dial tcp ... i/o timeout, if you are using in offline environment, please set TIKTOKEN_CACHE_DIR to use exsited files解决办法是手动把编码文件放进去。tiktoken 认的是 URL 的 SHA-1 作为文件名,先算哈希:
echo -n "https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken" | sha1sum | head -c 40输出是9b5ad71b2ce5302211f9c61530b329a4922fc6a4。在有网机器上下载cl100k_base.tiktoken,重命名为这个哈希值,放进./TIKTOKEN_CACHE_DIR目录。如果日志里还报别的 encoder 缺失,重复这个操作,直到启动不再报错。
启动服务:
docker compose up -d docker compose logs -f one-api看到监听 3000 端口就 OK。浏览器访问http://内网IP:3000,默认账号root,密码123456,首次登录务必改掉。
接下来配置 Ollama 渠道。进后台「渠道」→「添加新的渠道」,关键字段这样填:
| 字段 | 值 | 说明 |
|---|---|---|
| 类型 | Ollama | 下拉选择 |
| 名称 | deepseek-local | 自定义 |
| 分组 | default | 默认分组 |
| 模型 | deepseek-r1:32b | 与 ollama list 一致 |
| 代理 | 留空 | 内网直连 |
| 密钥 | 任意填 | Ollama 不校验 |
最容易卡住的是「代理」地址。OneAPI 跑在容器里,容器内的localhost指向容器自己,不是宿主机。所以不能填http://localhost:11434。两种解法:一是让 OneAPI 容器用 host 网络模式,network_mode: host,然后填http://127.0.0.1:11434;二是保持 bridge 网络,填http://host.docker.internal:11434,但 Linux 下需要在 compose 里加extra_hosts: - "host.docker.internal:host-gateway"。我一般用第一种,省事。
渠道建好后,去「令牌」页面新建一个令牌,这就是业务系统要用的统一 Key。到这里,网关层就通了。
4. 内网调用验证与连通性排查动作
配置完不验证等于没配。验证要分两层:先确认 Ollama 本身能出结果,再确认通过 OneAPI 能出结果。
第一层,直接在服务器上打 Ollama 原生接口:
curl http://127.0.0.1:11434/api/generate -d '{ "model": "deepseek-r1:32b", "prompt": "用一句话解释什么是向量数据库", "stream": false }'如果返回 JSON 里有response字段且内容是中文,说明模型加载正常。如果卡住不动,多半是显存不够,去看nvidia-smi或者journalctl -u ollama -f的日志。
第二层,走 OneAPI 的 OpenAI 兼容接口。假设 OneAPI 在内网 IP192.168.1.100,令牌是sk-xxxxxxxx:
curl http://192.168.1.100:3000/v1/chat/completions \ -H "Authorization: Bearer sk-xxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-r1:32b", "messages": [{"role": "user", "content": "写一个 Python 快速排序"}], "stream": false }'返回结构里choices[0].message.content就是模型输出。这一步通了,说明整条链路打通:请求进 OneAPI,OneAPI 转发给 Ollama,Ollama 推理后原路返回。
Python 业务侧调用更简单,用 openai 官方 SDK 改个 base_url 就行:
from openai import OpenAI client = OpenAI( base_url="http://192.168.1.100:3000/v1", api_key="sk-xxxxxxxx" ) resp = client.chat.completions.create( model="deepseek-r1:32b", messages=[{"role": "user", "content": "帮我审查这段 SQL 有没有注入风险"}] ) print(resp.choices[0].message.content)这样业务代码完全不用感知底层是 Ollama 还是别的,未来要加模型,只在 OneAPI 后台加渠道,业务侧改个 model 名即可。
连通性排查我整理了一个顺序:先在服务器上curl 127.0.0.1:11434确认 Ollama 活着;再curl 127.0.0.1:3000确认 OneAPI 活着;然后从另一台内网机器curl 192.168.1.100:3000确认端口没被防火墙拦;最后带上 Key 打完整请求。哪一步断,问题就锁定在哪一层。
5. 常见报错排查:401、local proxy failed 与 reading choices
内网搭这套东西,报错基本集中在几个固定位置。我把踩过的坑按现象列出来,对照着查能省不少时间。
401 Unauthorized。这个最常见,八成是 Key 的问题。先确认请求头里Authorization: Bearer sk-xxx格式对不对,Bearer 后面有空格。再确认令牌在 OneAPI 后台是不是被禁用或额度耗尽。还有一种情况是令牌绑定的分组和渠道分组不匹配,比如令牌是 default 分组,渠道却建在 vip 分组,请求就会 401。去「令牌」页面看分组设置,和渠道保持一致。
local proxy failed / dial tcp 127.0.0.1:11434 connection refused。这是 OneAPI 连不上 Ollama 的典型报错。原因就一个:容器网络隔离。OneAPI 容器里的 127.0.0.1 不是宿主机。解决方式前面提过,要么把 OneAPI 改成 host 网络,要么用host.docker.internal并配extra_hosts。改完记得docker compose down && docker compose up -d重建容器,光 restart 不生效。
reading choices: unexpected end of JSON input。这个报错说明 OneAPI 收到了 Ollama 的响应,但解析失败。常见原因是 Ollama 返回了非标准格式,或者模型名对不上。先确认渠道里填的模型名和ollama list输出完全一致,大小写、冒号都不能差。再确认 Modelfile 导入时没改坏 TEMPLATE,模板错了会导致输出结构异常。如果还不行,去 OneAPI 日志里看原始响应:
docker compose logs -f one-api | grep -i "choices"OAuth / 登录相关报错。如果你用的是 OneAPI 的 OAuth 登录(比如接企业 SSO),内网环境下回调地址配错会一直跳转失败。检查后台「系统设置」里的服务器地址,必须填内网可访问的地址,不能填 localhost。另外首次部署如果没改默认密码,有些版本会强制要求改密,按提示操作即可。
模型加载超时或 OOM。这不是 OneAPI 的问题,是 Ollama 侧显存不够。32B 模型量化后大概需要 20G 左右显存,如果卡是 16G 就会加载失败。换 14B 或 7B 版本,或者用ollama ps看当前加载状态。日志里出现CUDA out of memory就是显存爆了。
排查这类问题有个通用心法:先分层定位,再逐层缩小。Ollama 层、网络层、OneAPI 层、业务层,每层单独验证,不要一上来就怀疑最上层。
6. 从本地中台到统一接入:长期编码场景的延伸
内网中台搭好之后,日常的模型调用就统一走 OneAPI 这一个入口了。但实际用起来你会发现,团队里不同角色的需求差异挺大:有人只是偶尔问问问题,有人要长期跑 Agent 做代码审查,还有人需要把模型接进 IDE 做补全。这时候光有一个内网网关还不够,得考虑怎么把内网模型和外部能力做统一调度。
我的做法是保留内网 OneAPI 作为主入口,同时用 TaoToken 这类统一接入层来管理多来源的模型调用。它的价值在于把不同后端(本地 Ollama、云端模型)的 Key 和 Base URL 收敛成一套配置,业务侧不用为每个来源写一套适配代码。对于需要长期跑编码任务的场景,可以直接用 Coding Plan 把模型调用额度管起来,避免某个 Agent 把额度跑爆。
具体接入时,Base URL 填https://taotoken.net/api,Key 在控制台的 API Keys 页面生成,Model ID 按你实际要用的模型填。这三件套配好,无论是 Cline、Codex 还是自己写的脚本,都能用同一套凭证。如果你还在选模型阶段,可以先去模型对话页面实测一下不同模型在代码任务上的表现,再决定长期用哪个。
需要说明的是,内网中台和统一接入层不是替代关系。内网中台解决的是"数据不出内网"的合规底线,统一接入层解决的是"多模型调度和额度管理"的效率问题。两者配合,才能既守住安全,又不牺牲开发体验。对于刚开始搭中台的团队,建议先把 Ollama + OneAPI 这条链路跑通,验证模型效果和性能,再考虑接入层的扩展。