1. 项目概述Savor一个双协议LLM代理网关如果你正在使用像OpenClaw、Claude Code这类客户端来调用大模型API或者你的应用集成了OpenAI/Anthropic的SDK那么你很可能遇到过几个头疼的问题工具调用陷入死循环疯狂消耗你的Token和预算某个客户端行为异常瞬间打爆你的API调用限额或者只是想简单看看今天到底花了多少钱却找不到一个直观的监控面板。这些问题正是Savor这个项目要帮你解决的。Savor是一个用TypeScript编写的轻量级代理网关它就像一个智能的“中间人”部署在你的客户端比如OpenClaw和上游的大模型API服务比如DeepSeek、Qwen、Claude等之间。它的核心价值在于在几乎无感地转发所有请求和响应的同时为你提供了一系列生产环境必需的管控和观测能力。最吸引人的是它同时兼容OpenAI的Chat Completions API和Anthropic的Messages API两套协议这意味着你用一个服务就能同时管理对接这两类模型生态的应用配置独立互不干扰。我最初接触它是因为需要管理团队内部多个成员对多个模型API的调用既要保证大家能顺畅使用又得控制成本和防止滥用。自己写监控和限流脚本太麻烦而Savor开箱即用的看板、灵活的限流规则以及那个非常实用的“循环保护”功能直接解决了我的核心痛点。经过一段时间的部署和使用它已经成了我们内部AI应用架构里一个不可或缺的基础组件。2. 核心功能与设计思路拆解2.1 双协议支持为何要同时兼容OpenAI和Anthropic当前开源大模型生态中API协议逐渐形成了两大主流以GPT系列为代表的OpenAI Chat Completions API和以Claude系列为代表的Anthropic Messages API。许多优秀的客户端如OpenClaw和框架如LangChain都优先或同时支持这两套协议。如果你的业务同时涉及两类模型维护两个独立的代理网关无疑是冗余的。Savor的设计聪明之处在于它在同一个服务端口上根据请求的路径和头部信息自动识别并路由到对应的上游。例如发送到/v1/chat/completions的请求会被识别为OpenAI协议转发到config.js里配置的upstream地址而符合Anthropic格式的请求则会被路由到anthropicUpstream。底层实现上它维护了两套独立的配置上下文Config Context包括上游地址、API Key替换规则、模型覆盖等确保两种协议的流量处理完全隔离不会互相影响。这种设计让你可以用一套代码、一个进程来统一管理所有流量极大简化了运维复杂度。2.2 透明代理与核心增值功能Savor的基础角色是一个“透明代理”。这意味着对于合规的请求它除了添加一些用于监控和控制的HTTP头如X-Savor-Request-ID不会修改请求和响应的主体内容。你的客户端应用几乎感知不到它的存在就像直接连接上游API一样。它的核心价值体现在一系列增值功能上这些功能都是围绕“降本、增效、控风险”这三个目标设计的循环保护 (Loop Guard)这是针对“工具调用”Function Calling场景的“救命”功能。当AI模型在连续多轮对话中反复调用同一个或一组工具且没有产生实质进展时就可能陷入死循环。Savor会监测连续的“工具调用”请求当在短时间内可配置如10秒达到阈值默认3次时会自动熔断返回一个友好的错误信息而不是让请求无休止地继续下去白白浪费Token。这个功能的实现依赖于对请求体messages数组中最后一条消息角色 (role) 和内容 (content) 的分析。智能限流 (Rate Limit)基于客户端IP进行限流。你可以配置每分钟/每小时的最大请求数。超出限制的客户端会被暂时锁定。更精细的是它还支持“永久锁定”和“定时解锁”策略。例如你可以将某个测试用的IP永久锁定或者让违规IP锁定1小时后自动解锁。所有被锁定的客户端状态都可以通过Web看板或专用的/rate-limit/statusAPI查看和管理。上下文截断 (Context Truncation)大模型按Token收费而长上下文消耗的Token是成本大头。有时我们只需要模型关注最近几轮对话。Savor可以配置为只保留最近N轮对话以用户消息为轮次起点自动丢弃更早的历史。这在多轮对话后进行话题切换时特别有用可以显著节省Token。实时成本监控 (Real-time Dashboard)这是Savor的“眼睛”。它提供了一个简洁的Web看板实时展示总请求数、成功/失败率、Token消耗区分输入和输出、估算成本需在配置中设置单价以及最近7天的趋势图。所有请求的详细日志包括原始消息、响应内容、耗时、Token数等都可以在日志页面查询。这对于团队协作和成本核算至关重要。2.3 安全与隐私考量除了功能Savor在安全和隐私方面也做了不少考虑API Key保护客户端配置中可以使用一个虚拟的API Key如sk-xxx。Savor在转发请求时会用配置文件中真实的API Key替换掉这个虚拟Key。这样真实的Key永远不会离开你的服务器降低了在客户端或网络传输中泄露的风险。隐私信息过滤 (Content Filter)可以配置正则表达式规则在请求发送给上游API之前自动过滤掉消息中的手机号、身份证号、邮箱等敏感信息替换为占位符如[PHONE]。这在一定程度上防止了敏感数据被意外发送给第三方API。CORS与安全头内置了CORS跨域资源共享白名单配置并集成了Helmet中间件来设置一系列安全的HTTP头帮助减轻一些常见的Web漏洞风险。3. 部署与配置实操详解Savor提供了多种部署方式从最简单的直接运行到生产级Docker部署。这里我以最推荐、也最省心的Docker Compose部署为例带你走一遍完整流程。这种方式隔离性好依赖干净非常适合生产环境。3.1 环境准备与文件获取首先确保你的服务器或本地开发机已经安装了Docker和Docker Compose。然后创建一个专属目录并获取配置文件。# 1. 创建项目目录并进入 mkdir -p ~/apps/savor cd ~/apps/savor # 2. 下载必需的配置文件 # 使用curl从GitHub仓库直接拉取最新的docker-compose.yml和config.js curl -sSL -o docker-compose.yml https://raw.githubusercontent.com/chziyue/savor/main/docker-compose.yml curl -sSL -o config.js https://raw.githubusercontent.com/chziyue/savor/main/config.js # 3. 可选如果你想使用最新的开发版代码可以克隆整个仓库 # git clone https://github.com/chziyue/savor.git . # 但通常直接使用官方镜像和配置文件即可。执行完上述命令后你的savor目录下应该有两个文件docker-compose.yml和config.js。docker-compose.yml定义了服务而config.js就是核心配置文件。3.2 核心配置文件解析与修改接下来是关键的配置环节。用你熟悉的文本编辑器如vim,nano, 或VSCode打开config.js。它的结构很清晰我们主要关注几个必须修改的项。// config.js - 关键配置项详解 module.exports { // OpenAI 协议配置区 // 上游API地址指向你实际使用的OpenAI兼容服务 // 例如DeepSeek, Qwen, OpenAI官方等 upstream: https://api.deepseek.com/v1, // 必须修改 // 是否在转发时自动在upstream地址后追加 /v1。如果上游地址已包含/v1则设为false。 upstreamAppendV1: false, // 根据你的上游地址调整 // 模型覆盖强制将所有请求的模型字段替换为指定值 // 例如所有请求都使用 qwen-max 模型无视客户端请求 modelOverride: { enabled: false, // 设为true启用 model: qwen-max }, // API Key替换用这里配置的Key替换客户端请求中的Key // 保护真实Key不暴露给客户端 apiKeyOverride: { enabled: true, // 强烈建议在生产环境启用 apiKey: sk-your-real-openai-api-key-here // 替换为你的真实Key }, // Anthropic 协议配置区 // 上游API地址指向你实际使用的Anthropic兼容服务 anthropicUpstream: https://api.anthropic.com, // 必须修改 // 是否追加 /v1。Anthropic官方API不需要但有些代理服务可能需要。 anthropicUpstreamAppendV1: false, // Anthropic模型覆盖 anthropicModelOverride: { enabled: false, model: claude-3-5-sonnet-20241022 }, // Anthropic API Key替换 anthropicApiKeyOverride: { enabled: true, apiKey: sk-ant-your-real-anthropic-api-key-here // 替换为你的真实Key }, // 服务基础配置 port: 3456, // HTTP服务端口 host: 0.0.0.0, // 监听所有网络接口 // 功能配置 // 循环保护配置 loopGuard: { enabled: true, threshold: 3, // 连续工具调用次数阈值 windowMs: 10000 // 时间窗口毫秒10秒内触发3次则熔断 }, // 限流配置 rateLimit: { enabled: true, windowMs: 60000, // 限流窗口1分钟 maxRequests: 100, // 每个客户端IP每分钟最大请求数 lockDurationMs: 3600000 // 触发限流后锁定1小时-1为永久锁定 }, // 上下文截断配置 contextTruncation: { enabled: false, // 默认关闭通过命令或按需开启 keepRounds: 5 // 如果启用默认保留最近5轮对话 }, // 更多配置如 contentFilter, commands, dashboard 等... };实操要点与避坑指南upstream和anthropicUpstream这是最容易出错的地方。你必须将其修改为你实际购买或使用的API服务地址。例如如果你用的是DeepSeek就填https://api.deepseek.com/v1如果是OpenAI官方就填https://api.openai.com/v1。务必确认地址末尾是否需要/v1并相应设置upstreamAppendV1。apiKeyOverride强烈建议在生产环境启用此功能。这样你在所有客户端OpenClaw, Claude Code等里都可以统一填写一个虚拟Key如sk-xxx而真实Key安全地保存在服务器配置文件中。即使客户端配置泄露攻击者也无法直接使用你的API额度。Docker环境下的文件路径如果你用Docker运行config.js文件是通过卷映射 (volumes) 挂载到容器内的。在docker-compose.yml中通常映射为./config.js:/app/config.js。这意味着你在宿主机即你的服务器上修改config.js容器内会同步生效。修改后无需重启容器Savor支持配置热重载除端口等少数配置外。网络与防火墙确保你的服务器防火墙开放了配置的端口默认3456。如果你在云服务器上部署还需要在安全组规则中放行该端口的入站流量。3.3 启动服务与验证配置文件修改保存后就可以启动服务了。# 在 ~/apps/savor 目录下执行 docker compose up -d-d参数表示在后台运行。执行后Docker会拉取chziyue/savor:latest镜像如果本地没有并创建容器在后台运行。验证服务是否正常运行# 查看容器状态 docker compose ps # 应该看到 savor 服务状态为 “Up” # 查看实时日志 docker compose logs -f # 如果一切正常你会看到类似下面的输出没有报错 # savor-1 | [Savor] Server is running on http://0.0.0.0:3456 # savor-1 | [Savor] Dashboard available at http://0.0.0.0:3456/现在打开浏览器访问http://你的服务器IP:3456。你应该能看到Savor的监控看板。如果能看到页面说明服务部署成功。3.4 客户端配置以OpenClaw和Claude Code为例服务跑起来了接下来就是让客户端连接它。这里以最常用的两个客户端为例。OpenClaw 配置OpenClaw的配置文件通常位于~/.openclaw/openclaw.jsonLinux/macOS或%APPDATA%\.openclaw\openclaw.jsonWindows。{ models: { providers: { // 你可以为你使用的服务起一个名字比如 “my-savor-proxy” my-savor-proxy: { baseUrl: http://你的服务器IP:3456/v1, // 注意结尾必须有 /v1 apiKey: sk-xxx // 这里填写虚拟Key与config.js中的apiKeyOverride.enabledtrue配合使用 } }, // 将某个模型映射到你的代理服务 modelMapping: { gpt-4: my-savor-proxy:gpt-4, // 格式为 “提供商名:模型名” qwen-max: my-savor-proxy:qwen-max } } }注意OpenAI协议代理地址 (baseUrl) 必须以/v1结尾因为OpenAI的API路径是/v1/chat/completions。Savor内部会根据这个路径将请求识别为OpenAI协议。Claude Code 配置Claude Code的配置文件通常位于~/.claude/settings.json。{ apiConfiguration: { baseURL: http://你的服务器IP:3456, // 注意这里结尾没有 /v1 apiKey: sk-ant-xxx // 虚拟Key } }注意Anthropic协议代理地址 (baseURL)不能以/v1结尾。Claude Code发送的请求路径本身就是/v1/messagesSavor会将其识别并转发到anthropicUpstream。配置完成后重启你的客户端然后尝试发送一条消息。如果配置正确消息会通过Savor代理转发到上游API并且你可以在Savor的监控看板上看到这次请求的日志和统计信息更新。4. 高级功能与实战技巧4.1 命令系统主动控制上下文与翻译Savor内置了一个非常实用的命令系统。在对话中输入以反斜杠\开头的特定命令可以触发特殊行为而命令本身不会被发送给大模型。翻译命令 (\翻译或\translate)作用立即截断所有历史上下文只将当前这条命令后的内容发送给大模型。同时模型会看到“翻译”这个指令。使用场景当你在一段很长的对话之后突然需要翻译一段独立的文本时。使用这个命令可以避免将之前所有的对话历史可能成千上万个Token都发送给模型极大节省成本。示例用户: 之前我们讨论了人工智能的哲学基础...很长的一段历史 用户: \翻译 The quick brown fox jumps over the lazy dog.Savor会只发送[{role: user, content: 翻译 The quick brown fox jumps over the lazy dog.}]给上游API。上下文裁切命令 (\N)作用精确控制保留多少轮历史对话。N是一个数字表示保留最近N轮完整对话一轮包含一个用户消息和对应的AI回复。工作机制Savor以用户消息为轮次的起点进行计数。\0表示清空历史\1表示保留上一轮用户问AI答\2表示保留上两轮以此类推。使用场景\0开启一个全新的、不依赖任何历史的话题。\1基于AI的上一个回答进行追问或修正。\5在进行一个复杂的多步骤任务如编写代码、分析长文档时保留足够的上下文以保证连贯性同时定期清理更早的、已不相关的部分以节省Token。示例假设已有10轮对话 用户: \2 请只根据我们最近两次讨论的内容总结一下要点。Savor会截断上下文只保留最后两轮完整的对话第9轮和第10轮然后将它们连同当前消息第11轮一起发送。实操心得命令系统是精细化管理成本和上下文的神器。我经常在长时间编码对话后使用\0来开始一个无关的新问题或者在多轮调试后使用\2来让模型聚焦于最近的问题。这比在客户端手动清空聊天记录更加灵活和精准。4.2 配置热重载无需重启的动态调整Savor支持配置热重载这是生产环境非常友好的特性。修改config.js后大部分配置项会在1秒内自动生效无需重启服务。支持热重载的配置项包括upstream,anthropicUpstream切换上游API地址。modelOverride,apiKeyOverride动态更换模型或API Key。loopGuard,rateLimit调整循环保护阈值或限流规则。contentFilter更新隐私过滤规则。contextTruncation,commands调整上下文保留轮数或命令前缀。查看热重载日志当你保存config.js后查看Savor的日志docker compose logs -f会看到类似[ConfigWatcher] config.js 文件已变化和[ConfigWatcher] 配置已热更新 [...]的信息表明新配置已加载。仍需重启的配置项port,host改变服务监听端口或地址。https修改HTTPS证书配置。features启用或禁用核心功能模块如监控看板。logDir更改日志目录。技巧你可以利用热重载功能实现“蓝绿部署”式的API Key轮换。准备一个新的API Key更新到config.js的apiKeyOverride.apiKey中并保存。Savor会立即开始使用新Key而正在处理的请求不会中断。这对于无缝更换过期或泄露的Key非常有用。4.3 使用HTTPS保障通信安全如果你的Savor服务暴露在公网或者在内网中也需要加密通信启用HTTPS是必要的。Savor支持通过配置启用HTTPS并同时监听HTTP和HTTPS端口。方案一使用自签名证书适合内网或测试# 在 savor 项目目录下创建 certs 文件夹 mkdir -p certs cd certs # 生成自签名证书有效期约100年 openssl req -x509 -nodes -days 36500 -newkey rsa:2048 \ -keyout key.pem -out cert.pem \ -subj /CCN/STBeijing/LBeijing/OMyOrg/CNsavor.local \ -addext subjectAltNameDNS:savor.local,IP:192.168.1.100 # 将 192.168.1.100 替换为你服务器的实际IP或域名然后修改config.jshttps: { enabled: true, port: 3457, // HTTPS端口与HTTP(3456)不同 keyPath: ./certs/key.pem, certPath: ./certs/cert.pem }重启Savor服务后即可通过https://你的服务器IP:3457访问。注意客户端如OpenClaw也需要将baseUrl改为https://...。由于是自签名证书客户端可能需要配置跳过证书验证不推荐生产环境。方案二使用自有域名证书适合生产环境如果你有域名可以从Let‘s Encrypt等机构获取免费的SSL证书通常包含fullchain.pem和privkey.pem。将这两个文件放入certs/目录并修改config.js中的keyPath和certPath指向它们即可。4.4 通过API管理限流客户端当有客户端触发限流被锁定后除了在Web看板上操作你还可以通过Savor提供的RESTful API进行管理便于集成到自动化运维脚本中。# 假设Savor运行在本地3456端口 # 1. 查看所有客户端限流状态 curl -s http://localhost:3456/rate-limit/status | jq . # 返回示例 # { # 192.168.1.101: { locked: false, requestCount: 42, firstRequestTime: 1712345678901 }, # 192.168.1.102: { locked: true, requestCount: 150, firstRequestTime: 1712345678000, lockExpiresAt: 1712349278000 } # } # 2. 查看特定IP的状态 curl -s http://localhost:3456/rate-limit/status?clientId192.168.1.102 | jq . # 3. 解锁特定IP curl -X POST http://localhost:3456/rate-limit/reset?clientId192.168.1.102 # 4. 解锁所有被锁定的IP慎用 curl -X POST http://localhost:3456/rate-limit/reset?alltrue应用场景你可以写一个简单的监控脚本定期检查rate-limit/statusAPI如果发现某个重要的服务IP被误锁可能因为突发流量就自动调用reset接口解锁保证业务连续性。5. 常见问题排查与优化建议在实际部署和使用Savor的过程中你可能会遇到一些问题。下面是我总结的一些常见情况及解决方法。5.1 客户端连接失败或超时现象可能原因排查步骤与解决方案OpenClaw/Claude Code 提示“连接失败”或“网络错误”1. Savor服务未启动。2. 防火墙/安全组未开放端口。3. 客户端配置的baseUrl错误。1. 运行docker compose ps和docker compose logs检查Savor容器状态和日志。2. 在服务器上运行curl http://localhost:3456/测试本地访问。如果失败服务有问题。3. 从客户端网络环境用telnet 服务器IP 3456测试端口连通性。如果不通检查防火墙和安全组。4. 仔细核对客户端配置中的baseUrl的IP、端口和路径OpenAI协议必须有/v1Anthropic协议不能有。连接成功但请求长时间无响应或超时1. Savor到上游API的网络不通。2. 上游API服务本身故障或响应慢。3. Savor配置的上游地址 (upstream) 错误。1. 查看Savor日志 (docker compose logs -f)看是否有转发请求和接收响应的记录。如果日志停在转发请求处很可能是网络问题。2. 在Savor服务器上直接curl你配置的upstream地址带上正确的API Key和请求体测试上游API是否可用。3. 检查config.js中的upstream和anthropicUpstream地址是否正确特别是/v1后缀。返回“Invalid API Key”错误1.apiKeyOverride未启用且客户端使用了错误的虚拟Key。2.apiKeyOverride已启用但配置的真实Key无效或过期。3. 上游API服务商要求的Key格式不同。1. 确认config.js中apiKeyOverride.enabled为true并且apiKey字段填写了正确、有效的真实Key。2. 暂时关闭apiKeyOverride在客户端直接使用真实Key测试以排除Key本身的问题。3. 有些国内API服务商可能使用Bearer之外的前缀需要查看其文档。Savor默认使用Bearer认证头。5.2 监控看板无数据或数据显示异常现象可能原因排查步骤与解决方案看板能打开但所有统计数据都是0日志页面为空。1. 没有流量经过Savor。2. 数据库文件权限问题Docker部署常见。3. 数据库文件损坏。1. 确保客户端已正确配置并正在通过Savor发送请求。发送一条测试消息查看日志页面是否有新记录。2. 对于Docker部署检查docker-compose.yml中数据库文件通常是./data:/app/data的卷映射。确保宿主机上的./data目录存在且Docker进程有读写权限。3. 可以尝试停止容器删除./data目录下的savor.db文件然后重启容器。Savor会自动创建新的数据库历史数据会丢失。Token估算成本与实际账单相差较大。Savor的Token估算是基于启发式规则如中文汉字、英文单词、数字、标点的不同权重系数的近似计算并非官方Tokenizer的精确结果。这是预期行为。Savor的Token估算主要用于相对成本监控和趋势分析不能作为精确计费的依据。你可以在config.js的tokenEstimation部分微调系数使其更接近你所用模型的实际消耗比例。但最终成本请以API服务商的后台账单为准。日志页面显示请求成功但客户端收到错误。Savor代理转发成功但上游API返回了业务错误如上下文超长、模型不支持等。查看Savor日志中该次请求的详细信息特别是“上游响应”部分。错误信息通常包含在上游API返回的JSON体中。Savor会将这些错误信息原样返回给客户端。5.3 性能与稳定性优化建议资源监控Savor本身是Node.js应用资源消耗不高。但在高并发下需要关注内存和CPU。可以使用docker stats或服务器监控工具观察。如果请求量巨大考虑增加Node.js内存限制通过Docker的-e NODE_OPTIONS--max-old-space-size4096环境变量或部署多个实例配合负载均衡。日志管理Savor的日志默认输出到控制台和文件。生产环境建议配置logLevel如设为info减少debug日志并定期清理旧的日志文件。可以通过配置logDir将日志挂载到独立的存储卷。数据库维护Savor使用SQLite存储请求日志和统计信息。长时间运行后数据库可能增大。Savor内置了定时任务会自动清理8天前的数据。你也可以手动在服务器上执行sqlite3 ./data/savor.db VACUUM;来压缩数据库文件回收空间。高可用考虑对于关键业务可以考虑部署多个Savor实例在前端用Nginx或HAProxy做负载均衡和故障转移。注意由于限流状态存储在内存中多实例间的限流状态不会共享。如果需要全局限流可能需要外接Redis等共享存储这需要修改Savor源码。5.4 利用Docker Compose进行健康检查与自动重启生产环境部署我们可以在docker-compose.yml中增加健康检查配置让Docker自动监控服务状态。# 在 savor 服务的定义中添加 healthcheck services: savor: image: chziyue/savor:latest # ... 其他配置 ... healthcheck: test: [CMD, wget, --no-verbose, --tries1, --spider, http://localhost:3456/health] # 假设Savor有/health端点 # 或者用 curl: test: [CMD, curl, -f, http://localhost:3456/] interval: 30s timeout: 10s retries: 3 start_period: 40s restart: unless-stopped # 定义重启策略同时修改Savor的config.js确保features中的stats和webDashboard为true默认就是这样根路径/才能访问健康检查才有端点可探测。通过docker compose ps可以查看容器的健康状态。最后Savor这个项目解决的是LLM应用在调用层的一个通用痛点——管控与观测。它轻量、专注并且通过双协议支持覆盖了主流的使用场景。从我自己的使用体验来看它的配置热重载、命令系统和清晰的监控看板是最大的亮点真正做到了“开箱即用用了就离不开”。如果你也在管理多个AI应用或团队成员的API调用不妨试试把它加入到你的技术栈中它很可能就是那个你一直在找的、省心又省钱的“中间层”。