首页
/ Caveman 本地代理与多提供商接入全解:127.0.0.1:8787 上的字节安全流量网关

Caveman 本地代理与多提供商接入全解:127.0.0.1:8787 上的字节安全流量网关

2026-09-04 21:42:52作者:房伟宁

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.goCredential 方法中可见——每个 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.goCredential 方法中验证: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_REGIONAWS_REGIONAWS_DEFAULT_REGION → 默认 us-east-1,与 config.goBedrockRegion() 的实现完全一致。

Azure OpenAI 与 Vertex AI

  • Azure 在其 base URL 配置后挂载在 /azure/... 下;
  • Vertex 挂载在 /vertex/v1/projects/... 下,支持由适配器实现的公有 Google 与 Anthropic 发布者(publisher)路由形态。

两者均为显式 opt-in,因为端点和身份配置属于安装环境特有。Vertex 的适配器实现位于 proxy/providers/vertex/vertex.gorouting.go

OpenAI 兼容提供商

命名兼容挂载使用如下形式:

/compat/{name}/...

每个挂载在 caveman.yamlcompat 段声明 base_url 和存放凭据的环境变量名:

compat:
  myllm:
    base_url: https://llm.example.com/v1
    api_key_env: MY_LLM_API_KEY

从源码看,config.govalidateCompat 在启动时强制校验:名称合法、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.goinitializeNativePersistence 展示了运行期的第二道保险:当本地恢复存储(CCR)不可用或会话关联密钥缺失时,代理会显式降级为 record 透传并禁用 native runtime,而不是带着不可恢复的压缩状态继续运行。

流式处理:变换前置、流式保持

代理保留提供商的流式协议与状态码行为。时序上的关键约束是:请求侧变换在上游分发之前完成,流式响应保持流式(streaming response stays streaming)。

从源码结构看,RequestRecord 字段中的 StreamTTFBMS(time-to-first-byte)以及 RequestHashComplete 字段都围绕流式场景设计:流式传输失败时可能只捕获到请求体前缀,这类行会保留空哈希并标记 RequestHashComplete=false,保证审计诚实性。

凭据管理:密钥不入库、不落地

原文档的凭据章节给出两条硬规则,源码逐条对应:

  1. API 密钥永远不写进 YAMLcaveman.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
  1. 从不记录 Authorization 头。日志经过 redaction 层:main.go 中 slog handler 使用 redact.SlogReplaceAttr 对日志属性做脱敏,本地遥测只存用量与有界元数据(如 RequestRecord 中的 token 数、哈希、状态码),不存原始密钥。

端点安全:loopback 强制 + SSRF 出站防护

代理有两道安全边界,均已在源码中确认:

入站:拒绝非 loopback 监听地址

config.govalidateListen 是启动期硬校验:

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/8172.16/12192.168/16)——self-hosted 模式下可通过显式白名单放回。

对本地自托管模型(Ollama、LM Studio、测试 stub 等)的接入,通过 CAVE_SSRF_ALLOWLIST 精确加入主机名或 IP 即可;其中 localhost 作为白名单项会同时覆盖 127.0.0.0/8::1proxy/internal/standalone/standalone.go 中的 StandaloneHTTPClientssrf.SelfHostedConfig 守护每一次上游拨号,保证该逃生门真实生效;standalone_test.got.Setenv("CAVE_SSRF_ALLOWLIST", "127.0.0.1") 验证了放行路径。

在允许接入本地模型端点之前,建议先阅读 Security and privacy

配置加载与运行环境要点

caveman start 背后的二进制 caveman-proxy 的启动流程值得开发者了解,因为它决定了排查问题的切入点:

  1. 解析 home 目录(默认 ~/.caveman,可用 CAVEMAN_HOME 覆盖),创建目录;
  2. CAVEMAN_CONFIG(默认 ~/.caveman/caveman.yaml)加载配置——文件缺失不是错误,得到默认配置(record 模式、127.0.0.1:8787),即"裸 caveman start 是一次合法的 record-only 会话"(config.goLoad 函数注释);
  3. 打开本地花费存储(CAVEMAN_DB,默认 ~/.caveman/caveman.db)与 CCR 恢复存储(默认 ~/.caveman/ccr.db);
  4. 按模式装配 Compressor:仅当 modecompresspixel 且恢复存储可用时才接入真实引擎压缩器;record + CAVEMAN_OBSERVE_ESTIMATE 时接入只读估计器;
  5. 绑定监听器、写运行状态(runstate,供后续 wrap 校验复用的代理)、启动 HTTP 服务(ReadHeaderTimeout 5s、ReadTimeout 30s、MaxHeaderBytes 1MB)。

健康检查与指标端点同样值得在排障时使用(server.goHandler):

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.gostats 路径的注释同样强调"The summary's basis is always inferred; the figures are never re-projected";
  • provider 报告的 token 数与 Engine 自己的估计值始终分开记录RequestRecordTokenUsageBasis 独立于 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.gocapture 字段注释:它"never affects what is sent, recorded, or claimed")。

小结

Caveman 本地代理把"给 Agent 省钱"做成了一个可本地验证的字节安全网关:caveman start 一条命令起服务,路由表覆盖主流提供商的兼容形态,七种模式从纯记录到主动优化逐级放开且全部失败关闭,凭据永远走环境变量或入站透传,监听强制 loopback、出站强制 SSRF 防护,所有节省数字只标注 inferred。对需要理解实现细节的读者,建议按 proxy/README.mdproxy/cmd/caveman-proxy/main.go(启动装配)→ proxy/internal/config/config.go(配置与凭据解析)→ proxy/internal/gateway/server.go(请求生命周期)→ proxy/providers/(各提供商字节安全适配器)的顺序阅读,每一环都有对应的 *_test.go 可供验证。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384