# PrismCat [English](./README.md) | [简体中文](./README_CN.md) ![GitHub Release](https://img.shields.io/github/v/release/paopaoandlingyia/PrismCat) ![License](https://img.shields.io/github/license/paopaoandlingyia/PrismCat) ![Docker Image](https://img.shields.io/badge/image-ghcr.io%2Fpaopaoandlingyia%2Fprismcat-blue) > **你永远不知道 SDK 在你的 Prompt 里偷偷塞了多少东西 —— 直到你用了 PrismCat。** PrismCat 是一个**自托管的 LLM API 透明代理与调试控制台**。 只需改一行 `base_url`,即可完整记录你的应用与 OpenAI / Claude / Gemini / Ollama 等任意 LLM API 之间的所有通信 —— 包括流式响应 (SSE)。 PrismCat 日志控制台:所有上游的每一条请求,含状态码、token 用量和延迟 ## 这些场景你熟悉吗? | | PrismCat | |---|---| | "为什么 Token 消耗这么大?我的 Prompt 明明很短啊" | 看到 SDK / 框架偷偷注入的 system prompt 和 few-shot 示例 | | "Agent 跑着跑着就失控了,不知道它中间干了什么" | 每次调用都已经记下来了,几天后照样能回溯完整行为链路 | | "流式输出有时候会卡住或截断" | 完整记录每一个 SSE chunk,分得清是模型、网关还是客户端的问题 | | "Function Calling 返回的 JSON 总是格式错误" | 抓到模型返回的原始文本,在 Playground 里改 Prompt 重发 | | "多人共用一个 API Key,谁的请求出了问题?" | 用 `X-PrismCat-Tag` 给请求打标签,按用户或项目筛选 | | "我用 Ollama 跑本地模型,想看看实际通信" | 加一个上游指向 `http://localhost:11434` —— 它就是个通用 HTTP 代理 | --- ## 30 秒上手 ### 1. 启动 前往 [Releases](https://github.com/paopaoandlingyia/PrismCat/releases) 下载对应系统的压缩包。 | 平台 | 启动方式 | |------|---------| | **Windows** | 双击 `prismcat.exe`,自动隐藏至系统托盘 | | **macOS** | 解压 `prismcat-*-macos-universal.zip`,把 `PrismCat.app` 拖进「应用程序」后打开,常驻顶部菜单栏 | | **Linux / macOS 命令行** | 终端执行 `./prismcat` | | **Docker** | 参见 [Docker 部署](#docker-部署) | > **macOS 首次打开**:应用暂未经过 Apple 公证,第一次需要在「应用程序」里右键 `PrismCat.app` →「打开」,或在终端执行 `xattr -dr com.apple.quarantine /Applications/PrismCat.app`。数据保存在 `~/Library/Application Support/PrismCat`。 打开浏览器访问 **`http://localhost:8711`** 进入控制面板。 ### 2. 添加上游 在 Settings 页面添加一个上游,例如: | 名称 | 目标地址 | |------|---------| | `openai` | `https://api.openai.com` | PrismCat 会自动生成一个代理地址:**`http://openai.localhost:8711`** ### 3. 改一行代码,开始抓包 ```python from openai import OpenAI client = OpenAI( base_url="http://openai.localhost:8711/v1", # ← 只改这里 api_key="sk-..." ) # 其余代码和平时完全一样 response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "Hello!"}], ) ``` 回到控制面板,你已经能看到完整的请求和响应了。就这么简单。 --- ## 它是怎么工作的? PrismCat 使用**子域名路由**实现透明代理。当你在 Settings 里添加一个名为 `openai` 的上游后: ``` 你的应用 PrismCat OpenAI │ │ │ │ openai.localhost:8711 │ api.openai.com │ │ ─────────────────────────>│ ────────────────────────────>│ │ │ 记录请求 ✓ │ │<─────────────────────────│<────────────────────────────│ │ │ 记录响应 ✓ │ ``` **为什么用子域名?** 因为它是真正的"透明代理"——你的请求路径(如 `/v1/chat/completions`)完全不需要改动。无论你用哪种 SDK、哪种语言,只要它支持自定义 `base_url`,直接把域名指向 PrismCat 就行了。甚至可以串联多级代理(App → PrismCat → 中转站 → OpenAI),每一环都无感接入。 > **💡 关于 `*.localhost`**:现代浏览器和大多数操作系统都会将 `*.localhost` 自动解析为 `127.0.0.1`,无需配置 hosts 文件。如果你的环境不支持,可以参考 [备选:路径路由模式](#备选路径路由模式) 或手动添加 hosts 条目。 --- ## 核心特性 ### 完整的流量观测 - 记录完整的请求头、请求体、响应头、响应体,支持关键字搜索与高亮 - **SSE 流式响应**完整捕获,支持查看原始流或合并后的完整文本 - JSON 自动格式化美化,大段 Base64(如内嵌图片)智能折叠并支持一键预览,告别刷屏 - 任意请求一键复制为可执行的 **cURL 命令** ![Image Preview](assets/image_preview.png) ### 一键重放 (Playground) 看到一条失败的请求?点击 **Replay**,在浏览器里直接修改 Prompt、参数,一键重发,秒级定位问题。不用重新跑你的 Python/Node 脚本。 ### Trace 关联 & 用量追踪 自动将相关请求关联为 Trace 链路,并从响应中提取 Token 用量。内置 OpenAI、Anthropic、Gemini 提取规则,也支持自定义。 ### 目标预设 一个稳定的代理入口,背后挂多组完整目标,在 Settings 里一键切换,客户端不用动。 - 每组预设把目标 URL、超时、出站代理、规则绑定捆在一起,不会出现"新地址配旧 Key" - 适合生产/预发、直连/代理线路、地域节点、租户凭据,以及真实服务与 mock/replay 之间切换 - 切换只影响新请求;正在进行的流式请求会用开始时的那组目标走完 ### 参数覆盖(需手动启用) 无需改动业务代码即可改写外发请求——对 JSON Body 执行 set / remove / default / append / prepend,还可 set 或 remove 请求头。每条规则按 method / path / JSON 内容匹配命中,日志详情页可看到原始请求 vs. 最终请求的逐字段 diff。 > **🔒 严格 opt-in,PrismCat 不会自作主张。** 默认就是透明转发——必须 (1) 手动打开总开关、(2) 定义规则、(3) 把规则绑定到具体上游,三步全做完请求才会被改写。任何一步没配置,请求都按字节原样透传。 ### 隐私与安全 - **纯本地部署**,数据存在本地 SQLite + 文件系统,不经过任何第三方服务器 - 自动对 `Authorization`、`api-key` 等敏感头部脱敏 ### 日志标签 请求时加一个 Header `X-PrismCat-Tag: my-tag`,即可在 UI 里按标签筛选。多人 / 多项目共用一个代理时特别有用。 ### 极简部署 单个二进制文件,没有任何外部依赖。Windows 支持系统托盘静默运行,Docker 原生支持。 ### 常驻运行,随时复盘 按 7×24 小时静默运行的黑匣子设计 —— 不需要"出了 bug 才想起来开抓包"。 - 自动清理过期日志、大 body 分离存储,连续跑几个月也不会撑爆数据库 - 几天后照样能回看某个自主 Agent 到底发了什么、模型到底回了什么 --- ## 和其他工具有什么不同? | | PrismCat | mitmproxy | Langfuse / Helicone | |---|---------|-----------|---------------------| | 部署方式 | 单二进制 / Docker | 本地安装 + 证书配置 | SaaS 或自建复杂后端 | | 针对 LLM 优化 | ✅ JSON 美化、Base64 折叠、SSE 合并 | ❌ 通用 HTTP 抓包 | ✅ 但偏向生产监控 | | 一键重放 | ✅ 内置 Playground | ❌ | 部分支持 | | 接入方式 | 改 `base_url` | 全局代理 / 证书 | 侵入 SDK 代码 | | 数据归属 | 完全本地 | 完全本地 | 依赖外部服务 | | 流式响应回看 | ✅ 原始流 + 合并视图 | 体验差 | 部分支持 | | 长期运行 | ✅ 自动清理、静默常驻 | 临时调试工具 | ✅ 但依赖外部基础设施 | --- ## Docker 部署 ### Docker Compose 创建 `docker-compose.yml`: ```yaml services: prismcat: image: ghcr.io/paopaoandlingyia/prismcat:latest container_name: prismcat ports: - "8711:8711" environment: # 控制台访问 Host。本地用 localhost;服务器部署时填你的域名或 IP。 - PRISMCAT_UI_HOSTS=localhost,127.0.0.1 # 子域名路由的基础域名。裸 IP 部署请开启路径路由,而不是把 IP 填到这里。 - PRISMCAT_PROXY_DOMAINS=localhost # 裸 IP / 无泛域名部署时:把 PRISMCAT_UI_HOSTS 改成你的 IP,并开启路径路由。 # - PRISMCAT_UI_HOSTS=你的IP # - PRISMCAT_ENABLE_PATH_ROUTING=true # 公网部署建议设置控制台密码;也可留空后首次访问 UI 时设置 - PRISMCAT_UI_PASSWORD=your_strong_password - PRISMCAT_RETENTION_DAYS=30 volumes: - ./data:/app/data restart: always ``` ```bash docker compose up -d ``` ### Docker Run ```bash docker run -d --name prismcat \ -p 8711:8711 \ -e PRISMCAT_UI_HOSTS=localhost,127.0.0.1 \ -e PRISMCAT_PROXY_DOMAINS=localhost \ -e PRISMCAT_UI_PASSWORD=your_strong_password \ -e PRISMCAT_RETENTION_DAYS=30 \ -v ./data:/app/data \ --restart always \ ghcr.io/paopaoandlingyia/prismcat:latest ``` --- ## 备选:路径路由模式 如果你的环境无法正确解析 `*.localhost`,或者你是用裸 IP 部署、没有泛域名,可以在 Settings 中开启 **路径路由模式**,通过路径前缀代替子域名: ```python # 路径路由模式 —— 无需子域名解析 client = OpenAI( base_url="http://localhost:8711/_proxy/openai/v1", # 服务器上可替换为 http://你的IP:8711/_proxy/openai/v1 api_key="sk-..." ) ``` 也可以通过配置文件或环境变量开启: ```yaml # config.yaml server: enable_path_routing: true path_routing_prefix: "/_proxy" ``` ```bash # 或通过环境变量 PRISMCAT_ENABLE_PATH_ROUTING=true ``` > **注意**:路径路由模式下,请求路径会被添加前缀(如 `/_proxy/openai/...`),某些 SDK 的路径拼接逻辑可能需要额外注意。子域名模式不存在这个问题。 --- ## 生产部署 (Nginx + 泛域名) 公网部署推荐使用泛域名解析(如 `*.prismcat.example.com`)配合 Nginx: ```nginx server { listen 80; server_name prismcat.example.com *.prismcat.example.com; location / { proxy_pass http://127.0.0.1:8711; proxy_set_header Host $host; # 必须:透传 Host 用于子域名路由 # SSE / 流式响应必须配置 proxy_http_version 1.1; proxy_set_header Connection ""; proxy_buffering off; client_max_body_size 50M; } } ``` 然后在 PrismCat 的 `proxy_domains` 中添加 `prismcat.example.com`。控制台可通过 `prismcat.example.com` 访问,上游 `openai` 可通过 `openai.prismcat.example.com` 访问。 --- ## 配置参考 配置文件位于 `data/config.yaml`,首次启动自动创建。大多数选项可通过 UI 的 Settings 页面修改。
完整配置示例 ```yaml server: port: 8711 ui_password: "" # 控制台密码;留空时首次访问 UI 设置 proxy_domains: # 子域名路由的基础域名 - localhost logging: max_request_body: 5242880 # 请求内容最多保存 5MB max_response_body: 33554432 # 响应内容最多保存 32MB sensitive_headers: # 自动脱敏的 Header - Authorization - api-key - x-api-key detach_body_over_bytes: 2097152 # 超过 2MB 的内容按需加载 body_preview_bytes: 524288 # 内联可读预览;长期高频运行可适当调小 early_request_body_snapshot: false logging_rules: # 首次启动时从内置 ai_models.json 生成;之后完全由用户维护。 model_path_templates_initialized: false model_path_templates: [] storage: retention_days: 30 # 日志保留天数,0 = 永久 upstreams: openai: target: "https://api.openai.com" timeout: 300 # 整个上游请求和响应过程的总超时;新配置默认 5 分钟 response_header_timeout: 60 # 高级:等待状态码/响应头;0 = 禁用,且始终受总超时约束 response_body_first_byte_timeout: 30 # 高级:收到响应头后等待首个响应体字节,适用于所有响应 response_body_idle_timeout: 15 # 高级:响应体开始后连续无新数据的最长时间,每次收到数据重新计时 sse_keepalive: # 可选;默认关闭,心跳不会延长总超时 mode: off # off、after_headers,或 full(包含等待响应头阶段) interval: 15 # 上游静默多久后向下游发送 SSE 注释 outbound_proxy: "env" # env、direct,或 http://127.0.0.1:7890 这样的代理 URL logging_path_filter: # all、allowlist 或 denylist;Ant 与 RE2 正则可混用 mode: allowlist rules: - matcher: ant pattern: "/v1/responses" - matcher: regex pattern: "^/v1/chat/completions$" gemini: target: "https://generativelanguage.googleapis.com" timeout: 300 response_header_timeout: 0 response_body_first_byte_timeout: 0 response_body_idle_timeout: 0 # 0 = 禁用对应阶段超时 outbound_proxy: "http://127.0.0.1:7890" # 可选:同一稳定入口下的多个完整目标预设。 # target 与 targets 互斥;旧的单目标格式仍然兼容。 codex: active_target: primary targets: primary: url: "https://api.openai.com" timeout: 300 outbound_proxy: "env" request_overrides: enabled: false rule_names: [] backup: url: "https://api.example.com" timeout: 300 outbound_proxy: "direct" request_overrides: enabled: false rule_names: [] `sse_keepalive.mode` 的 `after_headers` 仅在确认上游返回成功且未压缩的 `text/event-stream` 后注入 `: ping` 注释。`full` 还会根据 `Accept: text/event-stream`、`alt=sse` 或 JSON 顶层 `stream: true` 覆盖响应头前的等待;第一枚心跳发出后 HTTP 200 已提交,之后的上游 401、429 或 5xx 只能记录到 PrismCat 日志,下游表现为流中断。现有配置中明确写入的 `timeout` 不会随默认值变化而自动迁移。 # 请求参数覆盖(默认关闭) # 支持的操作:JSON Body set / remove / default / append / prepend;Header set / remove request_overrides: enabled: false max_body_bytes: 1048576 upstreams: {} rules: [] # Token 用量提取(默认关闭) # 内置 OpenAI、Anthropic、Gemini 规则,也可自定义 paths usage_extraction: enabled: false upstreams: {} rules: [] # 完整内置规则见 config.example.yaml ```
--- ## 常见问题
Q: openai.localhost 无法访问? 大多数现代系统会自动将 `*.localhost` 解析为 `127.0.0.1`。如果不行: 1. 手动在 hosts 文件中添加 `127.0.0.1 openai.localhost` 2. 或者开启 [路径路由模式](#备选路径路由模式) 作为替代 3. 或者使用自己的泛域名(参见 [Nginx 部署](#生产部署-nginx--泛域名))
Q: Streaming 感觉卡住了? 如果你在反向代理(如 Nginx)后面使用 PrismCat,务必确认: - `proxy_buffering off;` - `proxy_http_version 1.1;` Nginx 默认会缓冲整个响应再转发,这会导致流式输出看起来像是"卡住"了。
Q: 支持哪些 LLM 服务? PrismCat 是通用 HTTP 代理,与具体的 LLM 服务无关。只要是走 HTTP/HTTPS 的 API 都能用,包括但不限于: - OpenAI / Azure OpenAI - Anthropic Claude - Google Gemini - Ollama / LM Studio(本地模型) - 各类中转站 / API 聚合服务
Q: 会影响请求速度吗? PrismCat 使用异步日志写入,代理本身的延迟通常在 1ms 以内。日志记录不会阻塞请求的转发和响应。
--- ## 社区 - [LinuxDo 讨论帖](https://linux.do/t/topic/1623556/33) --- ## 支持 PrismCat 如果 PrismCat 帮你节省了调试 LLM 应用的时间,也欢迎请我喝杯咖啡: [在爱发电支持 PrismCat](https://afdian.com/a/etgpao) --- ## License [MIT License](LICENSE)