自托管 Affine 的 AI 配置问题

探讨 Affine 社区普遍反映的自托管版本 AI 自定义功能不完善问题,以及可行的第三方模型配置方案。

从 Affine 官方社区在 2025 年 4 月发布 AI for selfhost #11722 贴文开始,社区对自托管的 AI 功能配置问题的讨论就一直没停过。

作为 Affine 的自托管用户,我做了一些 AI 自定义方面的尝试,虽然无法覆盖所有需求,但起码可以保证按照这样的方式设置能够正常使用。

应该了解

对于自托管的 Affine 自定义 AI 模型,有以下几个必须要了解的情况:

  1. 自定义 AI 模型的功能是从 v0.24.0 版本开始提供的;
  2. Claude、OpenAI、Gemini 三家供应商的模型均需要单独配置;
  3. 如果使用 LiteLLM 之类的代理工具模拟 Claude 模型,需要将 claude-sonnet-4@20250514 中的 @ 符号替换成 -

理想情况

Affine 在后端写死了 AI 模型,所以最理想的使用方法是直接填写各家的 API Key。这个效果自不必说,直接用原厂模型,Affine 已经针对特定的任务关联了使用的模型,严格来说这样的工作质量应该是最好的了。

但众所周知的原因,三家 AI 平台均存在部分国家不可用的情况。有平台不提供服务的原因,也有国家禁止访问的原因。

当然,就算用户位于非服务受限的国家,也可能会因为隐私偏好或价格偏好,希望使用自己喜欢的 AI 服务,比如 Ollama、LM Studio 运行的私有模型,或是 openrouter 这样的 AI 聚合平台。

逐个处理

上面“应该了解”部分的第2条决定了目前无法用一个配置解决所有问题,起码我没有找到这样的解决方案。

假设我们要完全使用第三方模型,那么这里需要分别处理三个平台的配置。

自定义模型配置

在编写这篇文章时,我使用的是 Affine v0.25.1 版本。AI 配置中的自定义模型设置是根据项目 github 仓库代码中写死的模型名称调整过的。

Affine 写死的 AI 模型名

我的实践结论是,不要试图在自定义 JSON 中修改模型名称。因为你修改以后,在客户端中使用时一定是会报错的。

必须保持 JSON 中的名称与 Affine 源码中写死的模型名称一致,也许你唯一需要做的就是把其中 Claude Sonnet 4.5 模型名中的 @ 符号改成 -

既然不允许改变模型的名称,又想使用第三方模型,那么我们现在能做的也许就只有通过 LiteLLM 之类的工具将第三方 AI 模型模拟成这些特定的名称了。

Affine 后台自定义 AI 模型

用 LiteLLM 模拟 OpenAI 和 Claude 的模型

考虑到读者会采用不同的部署方式,所以这里我只提供 LiteLLM 的配置文件(config.yaml)以供参考。查看 LiteLLM 官方文档了解所有可用的 AI Provider 及设置方法。

model_list:
  - model_name: claude-sonnet-4-5-20250929
    litellm_params:
      model: deepseek/deepseek-reasoner
      api_key: os.environ/DEEPSEEK_API_KEY
  - model_name: gpt-5-mini
    litellm_params:
      model: deepseek/deepseek-chat
      api_key: os.environ/DEEPSEEK_API_KEY
  - model_name: gpt-4.1
    litellm_params:
      model: deepseek/deepseek-chat
      api_key: os.environ/DEEPSEEK_API_KEY
  - model_name: gpt-4o-2024-08-06
    litellm_params:
      model: deepseek/deepseek-reasoner
      api_key: os.environ/DEEPSEEK_API_KEY
  
litellm_settings:
  # 自动丢弃不必要的请求参数
  drop_params: True
  # 禁止用量上报
  telemetry: False

假设你使用 Docker 部署 LiteLLM,通过 http://10.10.0.1:4000/v1 就能访问到模型,相应的 Affine AI 配置如下图:

Affine OpenAI 配置

Affine Claude 配置

为什么不用 LiteLLM 代理 Gemini 模型?

答:因为 LiteLLM 接入 Gemini 的时候会报错“API 地址错误”,似乎 LiteLLM 在接入 Gemini 的时候会混淆 Google AI Studio 与 Vertex AI 的 API 地址。

反向代理 Gemini API

其实 Affine 这边用到的 Gemini 模型只有 gemini-2.5-flash 和 gemini-embedding-001,这两个都是非常便宜的模型。如果你的网络能直接访问 Gemini API,建议直接在 Affine 中设置原厂 API 和 Key。

如果你的网络无法直接访问到 Gemini API,可以使用 Caddy Server 或 Cloudflare Worker 创建一个反向代理服务。然后在 Affine 中设置即可:

Affine Gemini 设置

Cloudflare Worker

注意:Cloudflare worker 默认分配的 .workers.dev 域名在一些国家是被屏蔽的,这种情况下只需要给 worker 绑定一个自己的域名即可。

Cloudflre worker 代码

代码如下:

/**
 * Welcome to Cloudflare Workers!
 *
 * This is a transparent proxy worker.
 * It forwards requests to a designated upstream API server.
 *
 */

// ------------------------------------------------------------------
// CONFIGURATION: Change this to your target API endpoint.
const UPSTREAM_HOST = "generativelanguage.googleapis.com";
// ------------------------------------------------------------------


export default {
  async fetch(request, env, ctx) {
    // 1. 创建一个原始请求 URL 的可变副本
    const url = new URL(request.url);

    // 2. 核心步骤:将 URL 的主机名(hostname)替换为我们的目标上游服务器
    url.hostname = UPSTREAM_HOST;

    // 3. 创建一个新的 Request 对象,使用修改后的 URL,
    //    并直接“克隆”原始请求的所有其他属性(方法、请求头、请求体等)。
    //    这是实现“透明代理”最简洁的方式。
    const newRequest = new Request(url, request);
    
    // 4. 将新构建的请求发送到目标服务器,并直接返回它的响应。
    //    fetch() API 会处理所有网络细节。
    console.log(`Proxying request to: ${url.toString()}`);
    return fetch(newRequest);
  },
};

Caddy Server

# 你的自定义域名,Caddy 会自动为其申请和管理 HTTPS 证书
my-ai-proxy.example.com {
    # 将所有指向这个域名的请求,全部反向代理到 Gemini API 的主机
    reverse_proxy https://generativelanguage.googleapis.com {
        # 这是一个非常重要的最佳实践:
        # 将转发给上游服务器的 Host Header 设置为目标服务器的域名。
        # Caddy 的 {upstream_hostport} 占位符会自动完成这件事。
        # 这可以防止一些基于 Host Header 进行路由或验证的目标服务器出现问题。
        header_up Host {upstream_hostport}
    }

    # (可选但推荐) 添加访问日志,方便调试
    log {
        output file /var/log/caddy/ai-proxy-access.log {
            # 设置日志滚动,防止单个日志文件过大
            roll_size 10mb
            roll_keep 5
        }
    }

    # (可选) 启用压缩,可以节省一些带宽
    encode zstd gzip
}

这样一来,就可以在 Affine 中愉快的使用自定义 AI 模型了。

AI Intelligence

Affine AI 文本优化