如何在 0.27 以上版本的 AFFiNE 中添加 DeepSeek或任意OpenAI兼容服务作为 AI 助手

slintchen
5
2026-09-22

*本文由DeepSeek润色整理并上传,如果不想自己动手,建议将其发送给agent使用。

一份踩了 7 个坑才走通的实战指南。如果你照着官方文档配了 BYOK,却发现 AI 永远回一句“An error occurred, If this issue persists please let us know.”——那这篇就是为你写的。


为什么要写这篇

AFFiNE 从 0.27 开始把自建版的 AI 彻底改成 BYOK(Bring Your Own Key) 模式:服务端不再替你托管模型调用,而是由每个工作区自己填 provider 的 API Key。这个方向很合理,成本可控、数据不出自己的机器。

问题在于:官方文档给的是“理想路径”,而 0.27.x 的自建镜像里埋了七个不显眼的坑,任何一个没绕开,AI 都只会在前端界面上给你一个没有任何信息量的“发生错误”。服务端日志里才会吐出真正的线索:

Error: managed_preset_unavailable
Error: BYOK target is unavailable
Error: invalid BYOK endpoint
WARN Blocked CORS request from origin: http://your-domain:port

下面按“能跑通”的顺序,把完整的配置流程 + 每个坑的原理与解法讲清楚。DeepSeek 只是例子,任何 OpenAI 兼容的服务(LiteLLM、one-api、OpenRouter、本地 vLLM…)套路完全一样。


三个前置概念(30 秒读完)

概念

你需要知道的

BYOK 档案

AFFiNE 里每个工作区可以维护若干条 provider 凭证,叫 profile。列表顺序 = 降级顺序

provider 枚举

AFFiNE 只认 openai​ / anthropic​ / gemini​ / fal​ 四个。DeepSeek 没有专属 provider,要挂在 openai​ 下,靠“自定义端点”指向 DeepSeek

自定义端点

在 0.27.x 上它默认不可用,必须由服务端管理员显式放行——这是第一个大坑


准备工作

  • 一台能跑 Docker 的机器(NAS / VPS / 家用服务器都行),AFFiNE stable​ 镜像 ≥ 0.27.4

  • 一个 DeepSeek API Key:到 platform.deepseek.com 申请,形如 sk-xxxxxxxx​(35 字符)

  • 能 SSH 上服务器(或等价的文件编辑手段)

  • 浏览器能正常打开你的 AFFiNE

本文假定你的 AFFiNE 由 docker-compose.yml​ + config/config.json​ 部署(官方推荐方式)。


第一步:给容器补两个环境变量(最关键)

这是 90% 的人失败的地方。AFFiNE 0.27 的服务端里其实有 两套配置读取器:Nest 那一套会读 ~/.affine/config/config.json​,而处理 AI 路由的 Rust 原生运行时默认根本不看那个文件;更要命的是,它还会默认把自己当成 AFFiNE Cloud,于是对所有自建部署返回:

{ "policy": { "customEndpointMode": "unavailable" } }

结果就是:你在界面上永远找不到“自定义端点”的勾选框,硬调接口也会被 BYOK target is unavailable​ 顶回来。

解决办法是在 affine​ 服务上加两行环境变量:

name: affine
services:
  affine:
    image: ghcr.nio.edu.cn/toeverything/affine:stable # 用你原来的镜像地址即可
    container_name: affine_server
    ports:
      - '3010:3010'
    depends_on:
      redis:
        condition: service_healthy
      postgres:
        condition: service_healthy
      affine_migration:
        condition: service_completed_successfully
    volumes:
      - ./data/storage:/root/.affine/storage
      - ./config:/root/.affine/config
    environment:
      - selfhosted=true
      - DEPLOYMENT_TYPE=selfhosted # ★ 告诉原生运行时:我是自建,不是云端
      - AFFINE_BACKEND_RUNTIME_CONFIG_PATH=/root/.affine/config/config.json # ★ 让原生运行时也读这份配置
      - REDIS_SERVER_HOST=redis
      - DATABASE_URL=postgresql://affine@postgres:5432/affine
    restart: unless-stopped

背景:这几点分别对应官方 issue
#15534(原生运行时默认按 Cloud 处理)
#15506(原生运行时不读 config.json)
#15566(自建部署下 BYOK 自定义端点默认被策略关闭)


第二步:正确书写 config/config.json​

坑: 官方文档给出的样例是嵌套对象:

{ "copilot": { "byok": { "enabled": true } } } // ❌ 在 0.27.4 上会被静默忽略

而这一版实际的 config schema(可以从 https://github.com/toeverything/affine/releases/latest/download/config.schema.json​ 拉下来看)使用的是扁平点号键。写成嵌套不会报错,只是什么都不生效——这是最阴的一个坑。

正确写法:

{
  "$schema": "https://github.com/toeverything/affine/releases/latest/download/config.schema.json",
  "server": {
    "name": "My AFFiNE",
    "externalUrl": "https://affine.example.com"
  },
  "copilot": {
    "enabled": true,
    "byok.enabled": true,
    "byok.allowedProviders": ["openai", "anthropic", "gemini", "fal"],
    "byok.allowCustomEndpoint": true,
    "byok.allowPrivateEndpoint": false
  }
}

两条要点:

  1. ​byok.*​ 必须是扁平点号键(和 providers.openai​ 一个套路)。

  2. ​server.externalUrl​ 必须等于你在浏览器里实际访问的那个域名/端口。AFFiNE 的 CORS 白名单是按它逐字构建的:externalUrl​ 写 https://affine.example.com​,而你用 http://192.168.1.10:3010​ 访问,那么所有 BYOK 相关请求都会被拦掉,服务端日志里只会留下一行很容易被忽略的:

WARN [WorkspaceResolver] Blocked CORS request from origin: http://192.168.1.10:3010

(对应 issue #15552)

  1. ​allowPrivateEndpoint​ 保持 false​,除非你确实要让工作区管理员把请求指向内网地址。

改完重启服务:

docker compose up -d --force-recreate affine
# 或者(配置是挂载进去的,重启即可让进程重读)
docker restart affine_server

验证它真的生效了: 用管理员账号打开 /admin/settings/​ → 左侧 AI BYOK,应当能看到「允许使用自定义 Provider 端点」为开启状态;再用管理员账号访问工作区 设置 → 集成 → AI BYOK​,此时 Add key 对话框里应该出现“使用自定义 API 兼容端点”的选项——它出现,就说明前面两步都对了。


第三步:在工作区里创建 DeepSeek 档案

  1. 打开目标工作区 → Settings → Integrations → AI BYOK → Add key

  2. 填写:

    • Provider:OpenAI​(DeepSeek 兼容 OpenAI 协议,没有专属 provider)

    • 名称:DeepSeek​(随意,好认就行)

    • API Key:你的 sk-xxxxxxxx​

    • Custom endpoint / Base URL:https://api.deepseek.com/v1​

  3. 模型声明填 DeepSeek 的模型 id:

Model ID

建议能力

​deepseek-chat​

文本输入、文本/结构化输出、tool calling

​deepseek-reasoner​

文本输入输出、reasoning

  1. 点 Test key 验证(应当返回 verified),然后 Save。

若使用服务端存储(推荐,多设备可用),凭证存进 AFFiNE 的数据库;本地存储只在该设备的桌面端可用。


第四步:验证

最简单的方式:打开工作区的 Intelligence,问一句

用一句话回答:3 + 3 等于几?

能秒回「3 + 3 等于 6。」就成了 ✅

想看得更“底层”一点,可以抓 GET /api/copilot/chat/<sessionId>/stream-object​,正常时会看到流式片段:

event: message
data: {"type":"text-delta","textDelta":"3"}
data: {"type":"text-delta","textDelta":" +"}
data: {"type":"text-delta","textDelta":" "}

踩坑速查表

你看到的现象

真正的原因

解法

BYOK 里没有“自定义端点”选项;接口报 BYOK target is unavailable​

原生运行时把自己当 Cloud

加 DEPLOYMENT_TYPE=selfhosted​

改了 config.json​ 毫无反应

① 写成了嵌套对象 ② 原生运行时不读该文件

用扁平点号键 byok.*​;加 AFFINE_BACKEND_RUNTIME_CONFIG_PATH​

探测/保存全部失败,日志只有 Blocked CORS request from origin​

​externalUrl​ 与实际访问域名不一致

两者对齐

​invalid BYOK endpoint​

用了 provider_default​ 却还传了 dialect​/url​

provider 默认端点时不要传这两个字段

AI 回 500,日志 managed_preset_unavailable​

工作区里一条 BYOK 档案都没有,模型路由无路可走

按第三步建档案

改完配置没生效

进程没重读配置

​docker restart affine_server​

启动日志有 Invalid json config file SyntaxError​

​config.json​ 语法错了(缺逗号/花括号)

用 jq . config/config.json​ 校验


进阶:把任何 OpenAI 兼容服务接进来

因为 DeepSeek 的接入方式本质是「openai​ provider + 自定义端点」,所以任何 OpenAI 兼容网关都能照抄:

场景

Base URL 填法

DeepSeek 官方

​https://api.deepseek.com/v1​

LiteLLM / one-api 自建网关

​http://你的网关:4000/v1​(内网时记得开 byok.allowPrivateEndpoint​)

OpenRouter

​https://openrouter.ai/api/v1​

本地 vLLM / Ollama(OpenAI 兼容层)

​http://127.0.0.1:8000/v1​ 等

模型 id 换成对方文档里的即可。


附:不用界面,直接用 GraphQL 建档案

CI / 脚本化场景可以在已登录的会话里调用(工作区设置页抓包即可看到 Cookie):

mutation($input: CreateWorkspaceByokProfileInput!) {
  createWorkspaceByokProfile(input: $input) { profileId provider name enabled }
}
{
  "workspaceId": "<你的工作区 id>",
  "provider": "openai",
  "name": "DeepSeek",
  "enabled": true,
  "credential": "sk-xxxxxxxx",
  "definition": {
    "endpoint": {
      "kind": "openai_compatible",
      "dialect": "chat_completions",
      "url": "https://api.deepseek.com/v1"
    },
    "models": [
      { "modelId": "deepseek-chat", "enabled": true,
        "capabilities": [{ "input": ["text"], "output": ["text", "object"],
        "features": ["tool_calling"],
        "attachmentKinds": [], "attachmentSources": [] }] },
      { "modelId": "deepseek-reasoner", "enabled": true,
        "capabilities": [{ "input": ["text"], "output": ["text"],
        "features": ["reasoning"],
        "attachmentKinds": [], "attachmentSources": [] }]
      }
    ]
  }
}

验证端点与密钥(验证通过会返回 connection.kind = verified​):

mutation($input: ProbeWorkspaceByokProfileInput!) {
  probeWorkspaceByokProfile(input: $input) {
    connection { kind errorKind testedAt }
    models { modelId checks { operation status { kind errorKind } } }
  }
}

小结

在 0.27+ 的 AFFiNE 自建版里接 DeepSeek,真正要记住的只有四句话:

  1. ​DEPLOYMENT_TYPE=selfhosted​ —— 不设它,原生运行时永远当你是云端。

  2. ​AFFINE_BACKEND_RUNTIME_CONFIG_PATH​ —— 不设它,原生运行时不读你的 config.json。

  3. ​byok.*​ 用扁平点号键,并且 server.externalUrl​ 与你的访问域名逐字一致。

  4. DeepSeek 没有专属 provider:挂 openai​ + openai_compatible​ 端点 + 声明模型 id。

这四条都对上之后,AI 会稳定工作;哪天网站搬家改了域名,别忘了回来同步 externalUrl​。

祝配置顺利,少踩坑。


文中涉及的 AFFiNE 版本:0.27.4(自建 Docker 部署)。相关官方 issue:15534、​15506、15552、​15566。

动物装饰