Caveman 本地代理与多提供商接入全解:127.0.0.1:8787 上的字节安全流量网关
Caveman 的本地代理(local proxy)是一个只绑定在回环地址上的开发者工具:它在 127.0.0.1:8787 上暴露 Anthropic、OpenAI、Gemini、Bedrock 等提供商兼容的 HTTP 路由,对符合条件的请求执行本地变换(如上下文压缩),再把请求转发到真实提供商端点并记录本地用量。读完本文,你将掌握如何用 caveman start 启动代理、把 Agent 的 base URL 指过去、理解其请求路径与七种运行模式、配置各提供商挂载与凭据来源,并理解其 loopback 强制与 SSRF 出站防护的实现细节。
定位:单操作者开发工具,而非多用户网关
本地代理的本质是 base-URL-swap 反向代理:把任意 Agent 的 provider base URL 指向 http://127.0.0.1:8787,其 LLM 流量就会经过 Caveman,无需修改任何代码。启动方式是:
caveman start
默认监听地址为 127.0.0.1:8787。需要明确它的边界:
- 它是单操作者(single-operator)开发者工具,不是多用户网络网关;
- BYOK(自带密钥):零云依赖,凭据来自请求本身或本地环境变量;
- record 模式永远是字节透传;任何变换环节出现问题,原始字节原样转发;
- 每笔花费记录到本地
~/.caveman/caveman.db,且所有节省数字只标记为inferred(推断值),从不标记为verified。
上述定位在 proxy/README.md 中有明确声明,运行时代码也印证了这一点:入口文件 proxy/cmd/caveman-proxy/main.go 的包注释说明该二进制"loads caveman.yaml, opens the local ~/.caveman/ spend store, and serves the byte-safe lifecycle on 127.0.0.1:8787 with zero cloud dependencies. Every savings figure it records is inferred"。
请求路径:从 Agent 到提供商的完整链路
本地代理的请求处理遵循一条固定序列(源自 docs/technical/proxy-and-providers.md):
Agent SDK ──provider-compatible request──▶ 本地代理
本地代理 ──inspect / transform eligible context──▶ Engine
Engine ──original or compact request data──▶ 本地代理
本地代理 ──forward request with provider credential──▶ Provider
Provider ──response + usage──▶ 本地代理 ──▶ Agent SDK
从源码结构看,proxy/internal/gateway/server.go 的包注释把这条生命周期概括为 match → authenticate → inspect → byte-safe transform → upstream → meter(匹配 → 认证 → 检查 → 字节安全变换 → 上游 → 计量)。它与托管网关共用同一形态,区别仅在于把多租户控制面替换为三个注入的"缝"(seams):
| 注入缝 | 接口 | 职责 |
|---|---|---|
| Authenticator | server.go | 接受请求并返回 RuntimeMode 与 optimizer 策略 |
| CredentialResolver | server.go | 解析上游 provider 密钥(BYOK 环境变量或请求透传) |
| TelemetrySink | server.go | 记录一条真实的每请求花费行(本地 SQLite) |
凭据处理原则与文档一致:提供商凭据优先从入站请求中保留(inbound auth header);只有当集成方没有随请求发送凭据时,才回退到本地环境变量的命名密钥。这一点在 proxy/internal/config/config.go 的 Credential 方法中可见——每个 provider 的凭据对象都带 AuthFallbackEnv 字段,回退仅在入站缺失时生效。
路由表:各提供商挂载点全览
代理在 127.0.0.1:8787 上按提供商划分挂载前缀。以下路由表完整继承自原文档,使用哪个提供商就把对应前缀配进 Agent 的 base URL。
Anthropic
/anthropic/v1/messages
/anthropic/v1/messages/count_tokens
/v1/messages
OpenAI
/openai/v1/chat/completions
/openai/v1/responses
/openai/v1/embeddings
/v1/chat/completions
/v1/responses
/v1/embeddings
Google Gemini
/gemini/v1beta/models/{model}:generateContent
/gemini/v1beta/models/{model}:streamGenerateContent
/gemini/v1beta/models/{model}:countTokens
当 profile 配置使用裸路径时,等价的 bare Gemini 路径也会被接受(/v1beta/models/... 形式),源码中 proxy/internal/gateway/gemini_bare_route_test.go 专门覆盖该行为的测试。
Amazon Bedrock
/bedrock/model/{model}/invoke
/bedrock/model/{model}/invoke-with-response-stream
/bedrock/model/{model}/converse
/bedrock/model/{model}/converse-stream
可选的 Mantle 兼容路由(Anthropic Messages 兼容形态):
/bedrock/anthropic/v1/messages
从 proxy/README.md 可知,Bedrock Runtime 在 standalone 模式是一等公民:默认走 Bedrock Runtime lane;/bedrock/anthropic 的 Mantle 路由独立存在,且默认关闭,只有显式设置 CAVE_BEDROCK_MANTLE_ENABLED 才启用。Bedrock 凭据支持两条路径——低摩擦的 bearer 密钥或完整 IAM 密钥对:
AWS_REGION=us-east-1 AWS_BEARER_TOKEN_BEDROCK=… caveman-proxy
# 或:
AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… caveman-proxy
AWS_SESSION_TOKEN 对临时 IAM 凭据有效。凭据优先级为:显式入站凭据 → Bedrock bearer token → 完整 IAM 对;只有半套 IAM(缺 access key 或 secret)会失败关闭(fail closed),绝不会被当作可用凭据。这一点可以在 proxy/internal/config/config.go 的 Credential 方法中验证:accessKey == "" || secretKey == "" 时返回空凭据,"A partial IAM pair is never useful and must not fall through as an apparently valid credential"。区域解析优先级为 providers.bedrock.region(caveman.yaml)→ CAVE_BEDROCK_REGION → AWS_REGION → AWS_DEFAULT_REGION → 默认 us-east-1,与 config.go 中 BedrockRegion() 的实现完全一致。
Azure OpenAI 与 Vertex AI
- Azure 在其 base URL 配置后挂载在
/azure/...下; - Vertex 挂载在
/vertex/v1/projects/...下,支持由适配器实现的公有 Google 与 Anthropic 发布者(publisher)路由形态。
两者均为显式 opt-in,因为端点和身份配置属于安装环境特有。Vertex 的适配器实现位于 proxy/providers/vertex/vertex.go 与 routing.go。
OpenAI 兼容提供商
命名兼容挂载使用如下形式:
/compat/{name}/...
每个挂载在 caveman.yaml 的 compat 段声明 base_url 和存放凭据的环境变量名:
compat:
myllm:
base_url: https://llm.example.com/v1
api_key_env: MY_LLM_API_KEY
从源码看,config.go 的 validateCompat 在启动时强制校验:名称合法、base_url 必填且格式合法(由 openaicompat.ValidateName / ValidateBaseURL 检查);CompatCredential 则说明"空的 api_key_env 有意表示该命名上游不发 Authorization 头"。要特别注意文档的限定:兼容指 HTTP 形态(HTTP shape)兼容,不保证支持某个提供商的所有扩展特性。
运行模式:七种模式与失败关闭语义
原文档定义了七种模式,行为语义如下:
| 模式 | 请求行为 |
|---|---|
record |
模型可见字节原样转发(永远透传) |
compress |
对符合条件的 Engine 变换施加,带恢复(CCR)能力 |
pixel |
允许配置过的 text-to-image 上下文传输 |
recommend |
只产生本地推荐,不激活任何变换 |
shadow |
评估符合条件的变更但不实际提供 |
canary |
对选定流量应用配置的实验性行为 |
active |
应用已启用的 optimizer 行为 |
关键规则:未知模式一律退化为 record。标准本地 CLI 工作流只暴露 record、compress、pixel 三种;其余模式服务于受控评估路径。
源码印证了这一"fail closed"设计。proxy/internal/config/config.go 中定义了已知模式白名单,且未知值在加载时即被归一化为 record:
var knownModes = map[string]bool{"record": true, "recommend": true, "shadow": true,
"canary": true, "active": true, "compress": true, "pixel": true}
// withDefaults 中:
if !knownModes[c.Mode] {
c.Mode = "record"
}
注释明确说明原因:"anything else fails closed to record so an unrecognized config can never silently enable transforms"——未识别的配置永远不可能悄悄打开变换。此外 proxy/cmd/caveman-proxy/main.go 的 initializeNativePersistence 展示了运行期的第二道保险:当本地恢复存储(CCR)不可用或会话关联密钥缺失时,代理会显式降级为 record 透传并禁用 native runtime,而不是带着不可恢复的压缩状态继续运行。
流式处理:变换前置、流式保持
代理保留提供商的流式协议与状态码行为。时序上的关键约束是:请求侧变换在上游分发之前完成,流式响应保持流式(streaming response stays streaming)。
从源码结构看,RequestRecord 字段中的 Stream、TTFBMS(time-to-first-byte)以及 RequestHashComplete 字段都围绕流式场景设计:流式传输失败时可能只捕获到请求体前缀,这类行会保留空哈希并标记 RequestHashComplete=false,保证审计诚实性。
凭据管理:密钥不入库、不落地
原文档的凭据章节给出两条硬规则,源码逐条对应:
- API 密钥永远不写进 YAML。
caveman.yaml只承载模式、监听地址、optimizer 开关与各 provider base URL;API key 在请求时从环境读取。config.go 顶部的包注释即声明此约定。各提供商的命名环境变量映射为:
| Provider | 环境变量 |
|---|---|
| Anthropic | ANTHROPIC_API_KEY |
| OpenAI | OPENAI_API_KEY |
| Gemini | GEMINI_API_KEY |
| Azure OpenAI | AZURE_OPENAI_API_KEY |
| OpenAI 兼容 | OPENAI_COMPAT_API_KEY(或 compat 段自定义 api_key_env) |
| Bedrock | AWS_BEARER_TOKEN_BEDROCK,或 AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY(+ 可选 AWS_SESSION_TOKEN) |
- 从不记录 Authorization 头。日志经过 redaction 层:main.go 中 slog handler 使用
redact.SlogReplaceAttr对日志属性做脱敏,本地遥测只存用量与有界元数据(如 RequestRecord 中的 token 数、哈希、状态码),不存原始密钥。
端点安全:loopback 强制 + SSRF 出站防护
代理有两道安全边界,均已在源码中确认:
入站:拒绝非 loopback 监听地址
config.go 的 validateListen 是启动期硬校验:
func validateListen(listen string) error {
host, port, err := net.SplitHostPort(strings.TrimSpace(listen))
if err != nil || port == "" {
return fmt.Errorf("listen address %q must be loopback host:port", listen)
}
if strings.EqualFold(host, "localhost") {
return nil
}
ip := net.ParseIP(host)
if ip == nil || !ip.IsLoopback() {
return fmt.Errorf("listen address %q is not loopback; standalone proxy has no inbound authentication", listen)
}
return nil
}
原因是直白的:这是一个无入站认证的 BYOK 代理,绑定空地址、通配符或任何非回环地址,都会把配置好的所有 provider 凭据暴露到网络上。
出站:SSRF 防护与白名单逃生门
shared/platform/ssrf/ssrf.go 实现了双向 SSRF 防护(预检 URL 校验 + DialContext 拨号期校验,后者用于防 DNS rebinding)。其封禁策略分层:
- 永远封禁(任何模式):link-local(含云元数据地址
169.254.169.254)、unique-local IPv6、组播、文档/测试段、CGNAT、Teredo/6to4,以及::ffff:0:0/96这类 IPv4-mapped IPv6 绕过形态; - 条件封禁:loopback(
127.0.0.0/8、::1/128)与 RFC1918 私网段(10/8、172.16/12、192.168/16)——self-hosted 模式下可通过显式白名单放回。
对本地自托管模型(Ollama、LM Studio、测试 stub 等)的接入,通过 CAVE_SSRF_ALLOWLIST 精确加入主机名或 IP 即可;其中 localhost 作为白名单项会同时覆盖 127.0.0.0/8 与 ::1。proxy/internal/standalone/standalone.go 中的 StandaloneHTTPClient 用 ssrf.SelfHostedConfig 守护每一次上游拨号,保证该逃生门真实生效;standalone_test.go 用 t.Setenv("CAVE_SSRF_ALLOWLIST", "127.0.0.1") 验证了放行路径。
在允许接入本地模型端点之前,建议先阅读 Security and privacy。
配置加载与运行环境要点
caveman start 背后的二进制 caveman-proxy 的启动流程值得开发者了解,因为它决定了排查问题的切入点:
- 解析 home 目录(默认
~/.caveman,可用CAVEMAN_HOME覆盖),创建目录; - 从
CAVEMAN_CONFIG(默认~/.caveman/caveman.yaml)加载配置——文件缺失不是错误,得到默认配置(record 模式、127.0.0.1:8787),即"裸caveman start是一次合法的 record-only 会话"(config.go 的Load函数注释); - 打开本地花费存储(
CAVEMAN_DB,默认~/.caveman/caveman.db)与 CCR 恢复存储(默认~/.caveman/ccr.db); - 按模式装配 Compressor:仅当
mode为compress或pixel且恢复存储可用时才接入真实引擎压缩器;record+CAVEMAN_OBSERVE_ESTIMATE时接入只读估计器; - 绑定监听器、写运行状态(runstate,供后续 wrap 校验复用的代理)、启动 HTTP 服务(ReadHeaderTimeout 5s、ReadTimeout 30s、MaxHeaderBytes 1MB)。
健康检查与指标端点同样值得在排障时使用(server.go 的 Handler):
GET /health/live → {"ok":true,"service":"caveman-proxy","billing":"byok","adapters":N}
GET /health/ready
GET /metrics → cave_proxy_inflight_requests <N>
此外日志会落盘到 ~/.caveman/proxy.log(超过 16MB 轮转一次为 proxy.log.1),这对排查"上游失败但客户端只有笼统报错"的场景很有价值。
定价与用量:诚实的数字
- Provider catalog 提供带日期的公开 list price;未知 provider 或模型的定价解析为零并打上
unpriced标记,而不是猜测成本——main.go 中stats路径的注释同样强调"The summary's basis is alwaysinferred; the figures are never re-projected"; - provider 报告的 token 数与 Engine 自己的估计值始终分开记录(
RequestRecord中TokenUsageBasis独立于Basis:前者取值provider_complete/provider_partial/provider_malformed/unavailable,后者是节省数字的来源依据,standalone 恒为inferred); - 展示的 provider 成本是 list-price 小计,不是 provider 账单。详细口径见 Accounting and evidence。
故障排查清单
原文档给出的排查条目,结合源码可以定位得更精确:
- 404:通常是 Agent 用错了 provider 挂载前缀或裸路由。对照本文路由表核对 base URL;
- 认证失败:分别检查入站请求头与 provider 凭据来源(环境变量),检查过程中不要打印密钥值;
- 自定义 base URL 被拒:通常需要精确的
CAVE_SSRF_ALLOWLIST条目(主机名或 IP); - 上下文"意外"未变化:这在 record 模式下是正常行为;compress 模式下,变换未过 parse、size、policy 或 recovery 任一闸门时同样会回退为原样字节转发——byte-safe 契约就是"任何变换问题都向前兼容";
- 行为对比:在 record 模式重放同一请求,比较 provider 请求与响应的类别(class),而非带密钥的原始日志;本地还提供
CAVE_CAPTURE_DIR捕获目录用于受控的本地观测(见 server.go 中capture字段注释:它"never affects what is sent, recorded, or claimed")。
小结
Caveman 本地代理把"给 Agent 省钱"做成了一个可本地验证的字节安全网关:caveman start 一条命令起服务,路由表覆盖主流提供商的兼容形态,七种模式从纯记录到主动优化逐级放开且全部失败关闭,凭据永远走环境变量或入站透传,监听强制 loopback、出站强制 SSRF 防护,所有节省数字只标注 inferred。对需要理解实现细节的读者,建议按 proxy/README.md → proxy/cmd/caveman-proxy/main.go(启动装配)→ proxy/internal/config/config.go(配置与凭据解析)→ proxy/internal/gateway/server.go(请求生命周期)→ proxy/providers/(各提供商字节安全适配器)的顺序阅读,每一环都有对应的 *_test.go 可供验证。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00