Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/chenyueqi2024_token-proxy.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
36 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "chenyueqi2024/token-proxy", "files": {"README.md": {"ff77074708ccd11c8a3fbce9a27d116569f8a2b1": {"t": 1786067834.6196332, "n": 20761, "h": "25d9d0b62b2f1a6c", "c": "# token-proxy · 让长对话更轻、更省、更好用 🚀\n\n> 一个本地优先的 DeepSeek 聊天代理:为长期项目、反复追问和越来越长的上下文而生。\n\n当一段对话从“问一个问题”变成“共同推进一个项目”,历史消息会不断堆积:输入越来越长、等待越来越久、费用也越来越难控制。`token-proxy` 希望把这件事变得简单——它在你的电脑或内网中运行,替你安全地连接 DeepSeek;短对话直接转发,长对话则可以按需压缩较早内容,同时保留当前问题、最近对话、代码、JSON、工具调用和关键数据。\n\n- ✨ **给普通用户**:打开本地聊天页,填一次代理密钥,就能像普通聊天软件一样提问;复杂参数默认隐藏。\n- 🛡️ **给重视可靠性的人**:压缩失败、超时或验证不通过时自动回退原文,不让优化功能中断正常对话。\n- 🔐 **给团队与开发者**:DeepSeek 密钥只保存在服务端 `.env`;客户端只使用独立代理密钥。\n- 🔌 **给现有工作流**:保留标准 `POST /v1/chat/completions`、SSE 流式响应和未知字段转发,不绑定额外客户端 SDK。\n\n无论你只是想更轻松地使用 DeepSeek,还是想为团队搭建一层可控、可观察、可逐步优化的本地入口,`token-proxy` 都可以从“直接转发”开始,按需要再开启本地 Token 压缩。💡\n\n`token-proxy` 通过标准 HTTP JSON 接口调用 `deepseek-chat` 或 `deepseek-reasoner`;客户端可以使用聊天页面、`httpx`、curl 或 PowerShell 请求本服务的 `POST /v1/chat/completions`。整个使用过程不需要安装或调用其他厂商的 SDK 和服务。\n\n## 0. 从 GitHub 获取项目(第一次安装必读)\n\n**本节作用:** 把项目完整放到你的电脑上,为后续安装依赖、创建配置和启动服务准备一个正确的工作目录。\n\n如果你正在 GitHub 网页阅读本 README,先把项目下载到电脑。任选一种方式:\n\n### 方式 A:使用 Git(推荐)\n\n**本节作用:** 使用 Git 克隆仓库,今后可以方便地获取项目更新或参与贡献。\n\n打开 PowerShell,执行:\n\n```powershell\ncd D:\\tokens\ngit clone https://github.com/ChenYueqi2024/token-proxy.git\ncd .\\token-proxy\n```\n\n### 方式 B:不安装 Git,下载 ZIP\n\n**本节作用:** 不熟悉 Git 时,用浏览器下载压缩包也能完成本地使用。\n\n1. 在 GitHub 仓库页面点击绿色 `Code` 按钮;\n2. 点击 `Download ZIP`;\n3. 下载完成后右键 ZIP,选择“全部解压缩”;\n4. 打开解压后的 `token-proxy` 文件夹;\n5. 在文件夹空白处按住 `Shift` 并右键,选择“在此处打开 PowerShell 窗口”。\n\n后文所有命令都应在项目根目录执行,也就是能看到 `README.md`、`pyproject.toml` 和 `app` 文件夹的目录。\n\n## 1. 功能与限制\n\n**本节作用:** 先了解项目能做什么、不会做什么,避免把不支持的图片、文件或其他接口当作压缩目标。\n\n目前支持:\n\n- 普通 JSON 和 `stream=true` SSE 流式响应;\n- `system`、`developer`、`user`、`assistant`、`tool` 消息;\n- `tools`、`tool_choice`、`response_format` 及未知请求字段透明转发;\n- 独立的代理密钥,客户端密钥不会发送给上游;\n- LLMLingua-2 逐消息压缩、关键片段保护、验证、超时和 LRU 缓存;\n- `/healthz`、`/readyz`、`/metrics`;\n- 本地运行和 Docker 部署。\n\n目前不压缩图片、音频、文件、工具调用、JSON、主要内容为代码的消息、当前用户问题和最近若干轮对话。项目暂不实现 `/v1/responses`、Realtime、Batch、多租户计费或管理后台。\n\n## 2. 环境要求\n\n**本节作用:** 检查电脑和账号是否具备运行代理的基本条件,避免安装到一半才发现缺少 Python 或 DeepSeek 密钥。\n\n- Python 3.11 或更高版本;\n- 一个 DeepSeek API Key;\n- 如需压缩,需要足够的磁盘、内存并安装 PyTorch、Transformers、LLMLingua;\n- 或使用 Docker / Docker Compose。\n\n以下命令都应在 `token-proxy` 目录中执行。\n\n### 2.1 第一次使用:获取 DeepSeek API Key 🔑\n\n**本节作用:** 创建代理在服务端调用 DeepSeek 所需的密钥,并学会和本地代理密钥区分开来。\n\n`token-proxy` 需要 DeepSeek API Key 才能代表你向 DeepSeek 发送请求。它和网页聊天账号密码不同,也不要把它填写到聊天页面的“代理密钥”输入框中。\n\n按以下步骤获取:\n\n1. 打开 [DeepSeek 开放平台](https://platform.deepseek.com/),按页面提示注册或登录账号。\n2. 登录后打开官方 [API Keys 页面](https://platform.deepseek.com/api_keys)。\n3. 点击页面上的“创建 API Key”或类似按钮;名称可以填写 `token-proxy`,方便以后识别。\n4. 创建后复制生成的以 `sk-` 开头的密钥,并立即保存在密码管理器等安全位置。部分平台密钥只会完整显示一次;看不到时请新建一把密钥,不要猜测或尝试恢复旧密钥。\n5. 如平台提示需要余额、套餐或账户验证,请按 DeepSeek 开放平台页面的说明完成。API 调用与网页聊天服务的额度规则可能不同。\n\n官方参考文档:[DeepSeek API 文档](https://api-docs.deepseek.com/)。\n\n> ⚠️ 把 API Key 当作密码对待:不要发到聊天记录、截图、Issue、GitHub、代码文件或 `.env.example`。如果意外泄露,请立即在 DeepSeek 平台撤销该密钥并创建新的。\n\n接下来在本项目的 `.env` 中填写它:\n\n```dotenv\nUPSTREAM_API_KEY=sk-你刚刚创建的DeepSeek密钥\n```\n\n然后为本地聊天用户设置一个不同的代理密钥:\n\n```dotenv\nPROXY_API_KEY=proxy_请设置一个独立且足够长的本地密钥\n```\n\n两者的区别如下:\n\n| 密钥 | 写在什么位置 | 谁使用它 | 是否应发给聊天用户 |\n| --- | --- | --- | --- |\n| `UPSTREAM_API_KEY` | 服务器 `.env` | 只有代理服务,用来调用 DeepSeek | **不可以** |\n| `PROXY_API_KEY` | 服务器 `.env` 与本地聊天页 | 聊天页面、脚本或受信客户端,用来访问本代理 | 仅可安全地发给受信使用者 |\n\n如果你只是在自己的电脑上使用,两个密钥也必须保持不同。这样即使代理密钥被误用,也不会直接暴露 DeepSeek 账户密钥。\n\n## 3. 本地安装\n\n**本节作用:** 在电脑上创建独立运行环境并安装项目,避免影响系统已有的 Python 软件。\n\n### 3.1 创建虚拟环境\n\n**本节作用:** 为 token-proxy 单独准备 Python 和依赖目录,便于后续升级、排错和删除。\n\nWindows PowerShell:\n\n```powershell\npython -m venv .venv\n.\\.venv\\Scripts\\Activate.ps1\npython -m pip install --upgrade pip\n```\n\nLinux/macOS:\n\n```bash\npython3 -m venv .venv\nsource .venv/bin/activate\npython -m pip install --upgrade pip\n```\n\n### 3.2 安装依赖\n\n**本节作用:** 安装启动代理所需的基础库;压缩模型依赖可在需要时再单独安装。\n\n仅使用透明代理和自动回退:\n\n```bash\npip install -e .\n```\n\n开发和测试:\n\n```bash\npip install -e \".[dev]\"\n```\n\n启用本地压缩器:\n\n```bash\npip install -e \".[compression]\"\n```\n\n压缩依赖包含 PyTorch,安装体积可能较大。不安装不会影响代理转发,`FAIL_OPEN=true` 时服务会直接使用原文。\n\n## 4. 配置\n\n**本节作用:** 将 DeepSeek 地址、服务端密钥、代理密钥和运行参数保存在本机 `.env`,不写入代码。\n\n复制配置模板:\n\n```powershell\nCopy-Item .env.example .env\n```\n\nLinux/macOS 使用:\n\n```bash\ncp .env.example .env\n```\n\n编辑 `.env`:\n\n```dotenv\nUPSTREAM_BASE_URL=https://api.deepseek.com/v1\nUPSTREAM_API_KEY=sk-DeepSeek密钥\nPROXY_API_KEY=proxy_请设置一个独立且足够长的密钥\n\nCOMPRESSION_ENABLED=false\nCOMPRESSOR_MODEL=\nCOMPRESSOR_DEVICE=cpu\nMINIMUM_INPUT_TOKENS=4000\nMINIMUM_MESSAGE_TOKENS=300\nTARGET_COMPRESSION_RATIO=0.45\nKEEP_RECENT_TURNS=2\nMAX_COMPRESSION_LATENCY_MS=5000\nFAIL_OPEN=true\n\nREQUEST_TIMEOUT_SECONDS=120\nMAX_REQUEST_BODY_MB=10\nLOG_PROMPT_CONTENT=false\n```\n\n配置解释:\n\n| 配置项 | 默认值 | 说明 |\n| --- | ---: | --- |\n| `UPSTREAM_BASE_URL` | DeepSeek API | DeepSeek 的 `/v1` 基础地址,不要写到 `/chat/completions` |\n| `UPSTREAM_API_KEY` | 空 | 发送给 DeepSeek 的 API Key |\n| `PROXY_API_KEY` | 空 | 客户端访问本代理时使用的独立密钥 |\n| `COMPRESSION_ENABLED` | `false` | 是否尝试压缩;首次安装建议保持关闭 |\n| `COMPRESSOR_MODEL` | 空 | LLMLingua-2 兼容模型名或本地模型目录;空值表示压缩器不可用 |\n| `COMPRESSOR_DEVICE` | `cpu` | 压缩模型运行设备;普通电脑保持 `cpu`,有已配置 CUDA 的 NVIDIA 显卡时才改为 `cuda` |\n| `MINIMUM_INPUT_TOKENS` | `4000` | 总输入低于此估算值时不压缩 |\n| `MINIMUM_MESSAGE_TOKENS` | `300` | 单条旧消息低于此估算值时不压缩 |\n| `TARGET_COMPRESSION_RATIO` | `0.45` | 传给压缩器的目标比例 |\n| `KEEP_RECENT_TURNS` | `2` | 原样保留最近几组用户—助手对话 |\n| `MAX_COMPRESSION_LATENCY_MS` | `5000` | 单次请求允许的最大压缩耗时 |\n| `FAIL_OPEN` | `true` | 压缩器异常时允许使用原文继续服务 |\n| `REQUEST_TIMEOUT_SECONDS` | `120` | 上游 HTTP 请求超时 |\n| `MAX_REQUEST_BODY_MB` | `10` | 请求体大小上限 |\n| `LOG_PROMPT_CONTENT` | `false` | 是否记录提示词;当前建议保持关闭 |\n\n首次使用建议先将 `COMPRESSION_ENABLED=false`,确认代理链路正常后再安装和配置模型。模型名称没有写死,具体模型必须与本机资源和已安装的 LLMLingua 版本兼容。\n\n## 5. 启动服务\n\n**本节作用:** 启动本地 HTTP 代理,使聊天页面、健康检查和 DeepSeek 转发接口可以访问。\n\n开发模式:\n\n```bash\nuvicorn app.main:app --reload --host 127.0.0.1 --port 8080\n```\n\n本机或内网服务模式:\n\n```bash\nuvicorn app.main:app --host 0.0.0.0 --port 8080\n```\n\n启动后 API 基础地址为:\n\n```text\nhttp://localhost:8080/v1\n```\n\n### 普通用户:打开聊天页面\n\n**本节作用:** 不写代码、不填写 JSON,直接在浏览器中使用 DeepSeek 聊天。\n\n如果你只想提问和获得回答,不需要填写 JSON 或理解接口参数。打开:\n\n```text\nhttp://127.0.0.1:8080/\n```\n\n首次使用只需填写管理员提供的 `proxy_` 代理密钥,选择“普通聊天(推荐)”,输入问题并点击“发送”。页面只把代理密钥发送给本机代理;DeepSeek API Key 始终只保存在服务器 `.env` 中。聊天页面可选择在当前浏览器记住代理密钥,公用电脑请不要勾选该选项。\n\n## 5.1 浏览器交互接口文档:`/docs`(高级功能)\n\n**本节作用:** 为需要测试接口、查看 JSON 请求和排查响应的人提供可点击的 API 调试页面。\n\n服务启动后,在浏览器打开:\n\n```text\nhttp://127.0.0.1:8080/docs\n```\n\n这是代理自带的交互式接口页面。它只在你的电脑上运行,不是 DeepSeek 官网,也不是聊天网页;它的作用是让你填写一次 API 请求并立即查看结果。请不要在浏览器地址栏直接打开 `/v1` 或 `/v1/chat/completions`:聊天接口只接受 `POST` 请求,地址栏只能发送 `GET` 请求,所以会显示 `Not Found` 或无法调用。\n\n### 页面上有哪些接口\n\n**本节作用:** 快速判断每个接口是用来检查服务、查看状态还是发送聊天请求。\n\n打开页面后,每一条彩色横栏都是一个接口。单击横栏可以展开或收起详情:\n\n| 页面项目 | 用途 | 初次使用是否需要代理密钥 |\n| --- | --- | --- |\n| `GET /healthz` | 确认程序是否正在运行 | 不需要 |\n| `GET /readyz` | 确认 DeepSeek 配置和压缩器状态 | 不需要 |\n| `GET /metrics` | 查看供监控软件读取的计数器文本 | 不需要 |\n| `POST /v1/chat/completions` | 向 DeepSeek 发送聊天请求 | 需要 |\n\n`GET` 表示读取信息,不会发送聊天内容;`POST` 表示提交聊天请求。第一次建议先调用 `/healthz`,看到状态码 `200` 和 `{\"status\":\"ok\"}` 后,再调用聊天接口。\n\n### 各个按钮的作用\n\n**本节作用:** 了解交互文档中的按钮含义,避免误以为填写内容后已经自动发送请求。\n\n展开任意接口后,Swagger 页面通常会显示这些按钮:\n\n| 按钮 | 作用 | 什么时候使用 |\n| --- | --- | --- |\n| `Try it out` | 解锁输入框,使你能够填写参数或 JSON | 每次准备手动测试接口时先点击 |\n| `Execute` | 按当前填写的内容发送一次请求 | 确认密钥和 JSON 无误后点击 |\n| `Cancel` | 放弃本次编辑,恢复为未编辑状态 | 填错内容但尚未发送时点击 |\n| `Clear` | 清除上一次执行结果 | 想重新查看或避免页面保留响应内容时点击 |\n| `Schema` | 展开字段类型和说明,不会发请求 | 不理解某个字段时查看 |\n\n执行完成后,页面下方会显示:\n\n- `Request URL`:实际调用的本地地址;\n- `Server response`:状态码和返回内容;\n- `Response body`:DeepSeek 的 JSON 回答;\n- `Response headers`:响应头,其中 `X-Compression-Applied` 表示是否应用了压缩。\n\n此页面没有全局 `Authorize` 按钮。请在聊天接口展开后的 `Parameters` 区域,直接填写 `authorization` 输入框。\n\n### 在网页中发送第一条聊天消息\n\n**本节作用:** 用最小示例验证代理密钥、DeepSeek 配置和聊天转发链路是否都正常。\n\n1. 打开 `http://127.0.0.1:8080/docs`。\n2. 展开 `POST /v1/chat/completions`。\n3. 点击 `Try it out`。\n4. 在 `authorization` 输入框填写下面的格式。`Bearer` 后有一个空格;填写的是代理密钥,不是 DeepSeek API Key。\n\n ```text\n Bearer proxy_你的代理密钥\n ```\n\n5. 在 `Request body` 文本框粘贴以下 JSON。初次测试保持 `stream` 为 `false`,这样结果最容易阅读。\n\n ```json\n {\n \"model\": \"deepseek-chat\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"你好,请用一句话介绍自己。\"\n }\n ],\n \"stream\": false\n }\n ```\n\n6. 点击 `Execute`,等待 `Server response` 显示 `200`。\n7. 在 `Response body` 中找到以下路径,它就是模型的文字回答:\n\n ```text\n choices → 第 1 项 → message → content\n ```\n\n不要把 DeepSeek API Key 粘贴到 `authorization` 输入框,也不要把它发到聊天内容中。该密钥只保存在代理服务器的 `.env` 文件中。\n\n### 聊天接口常用选项\n\n**本节作用:** 理解最常用字段,只在确实需要时调整模型、随机性或输出长度。\n\n`Request body` 必须是合法 JSON:字段名和字符串要用英文双引号,最后一项后面不能多逗号。下面是常用字段:\n\n| 字段 | 必填 | 作用 | 新手建议 |\n| --- | --- | --- | --- |\n| `model` | 是 | 使用的 DeepSeek 模型 | 普通对话填写 `deepseek-chat`;需要更强推理时使用 `deepseek-reasoner` |\n| `messages` | 是 | 对话记录数组,顺序会影响回答 | 第一条从 `user` 开始即可 |\n| `messages[].role` | 是 | 消息身份 | 初次只用 `user`;规则可使用 `system` |\n| `messages[].content` | 是 | 该条消息的文字 | 写入你的问题或上下文 |\n| `stream` | 否 | 是否逐段返回回答 | 初次填写 `false`;熟悉后可改为 `true` |\n| `temperature` | 否 | 输出随机性 | 不填即可;需要稳定回答可设为 `0.2` |\n| `max_tokens` | 否 | 最多生成多少 token | 不填即可;想限制答案长度时再填写 |\n| `tools` | 否 | 工具定义 | 初学阶段不要填写 |\n| `response_format` | 否 | 结构化输出要求 | 初学阶段不要填写 |\n\n`messages` 可保存多轮对话,例如:\n\n```json\n{\n \"model\": \"deepseek-chat\",\n \"messages\": [\n {\"role\": \"user\", \"content\": \"北京是中国的首都吗?\"},\n {\"role\": \"assistant\", \"content\": \"是,北京是中国的首都。\"},\n {\"role\": \"user\", \"content\": \"它大约有多少人口?\"}\n ],\n \"stream\": false\n}\n```\n\n### 怎样理解常见结果\n\n**本节作用:** 根据 HTTP 状态码快速判断是请求格式、代理密钥、请求大小还是上游连接的问题。\n\n| 状态码 | 含义 | 处理方法 |\n| ---: | --- | --- |\n| `200` | 请求成功 | 在 `Response body` 阅读回答 |\n| `400` | JSON 格式不正确或请求体不是对象 | 检查引号、逗号和花括号 |\n| `401` | 没有代理密钥或代理密钥错误 | 检查 `authorization` 是否以 `Bearer ` 开头 |\n| `413` | 请求内容超过大小限制 | 缩短内容,或调整 `.env` 的 `MAX_REQUEST_BODY_MB` |\n| `422` | 某个输入不符合接口要求 | 阅读页面显示的字段错误信息 |\n| `502` | 代理无法连接 DeepSeek | 检查网络、`UPSTREAM_BASE_URL` 和 `.env` 中的 DeepSeek 密钥 |\n\n当 `stream` 是 `true` 时,浏览器页面会显示一连串 SSE 数据块,而不是一段整理好的文字;这是正常现象。日常使用流式输出更适合用本 README 的 `httpx` 示例或你自己的客户端程序。\n\n## 6. 客户端调用\n\n**本节作用:** 将本地代理接入命令行、Python 程序或其他现有工具,而不依赖特定厂商 SDK。\n\n### 6.1 curl 普通响应\n\n**本节作用:** 用一条命令快速发送普通聊天请求,适合验证接口或编写简单脚本。\n\n```bash\ncurl http://localhost:8080/v1/chat/completions \\\n -H \"Authorization: Bearer proxy_请替换\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"model\":\"deepseek-chat\",\"messages\":[{\"role\":\"user\",\"content\":\"你好\"}]}'\n```\n\nWindows PowerShell:\n\n```powershell\n$headers = @{ Authorization = \"Bearer proxy_请替换\" }\n$body = @{\n model = \"deepseek-chat\"\n messages = @(@{ role = \"user\"; content = \"你好\" })\n} | ConvertTo-Json -Depth 10\nInvoke-RestMethod http://localhost:8080/v1/chat/completions `\n -Method Post -Headers $headers -ContentType \"application/json\" -Body $body\n```\n\n### 6.2 使用 httpx 调用 DeepSeek\n\n**本节作用:** 在 Python 程序中通过通用 HTTP 库调用本地代理,并读取模型回答。\n\n项目本身已经依赖 `httpx`。如果在独立的客户端环境中调用,请先安装:\n\n```bash\npip install httpx\n```\n\n```python\nimport httpx\n\nresponse = httpx.post(\n \"http://localhost:8080/v1/chat/completions\",\n headers={\n \"Authorization\": \"Bearer proxy_请替换\",\n \"Content-Type\": \"application/json\",\n },\n json={\n \"model\": \"deepseek-chat\",\n \"messages\": [\n {\"role\": \"user\", \"content\": \"你好,请介绍一下你自己\"}\n ],\n },\n timeout=120,\n)\n\nresponse.raise_for_status()\ndata = response.json()\nprint(data[\"choices\"][0][\"message\"][\"content\"])\n```\n\n请求头中填写的是 `PROXY_API_KEY`,不是 DeepSeek 密钥。代理会在服务端换成 `UPSTREAM_API_KEY`。\n\n### 6.3 使用 httpx 接收流式响应\n\n**本节作用:** 让程序边接收边显示回答,适合需要实时输出的命令行或应用界面。\n\n```python\nimport json\nimport httpx\n\nwith httpx.stream(\n \"POST\",\n \"http://localhost:8080/v1/chat/completions\",\n headers={\"Authorization\": \"Bearer proxy_请替换\"},\n json={\n \"model\": \"deepseek-chat\",\n \"messages\": [{\"role\": \"user\", \"content\": \"写一段简短的故事\"}],\n \"stream\": True,\n },\n timeout=120,\n) as response:\n response.raise_for_status()\n for line in response.iter_lines():\n if not line.startswith(\"data: \"):\n continue\n payload = line[6:]\n if payload == \"[DONE]\":\n break\n chunk = json.loads(payload)\n content = chunk[\"choices\"][0][\"delta\"].get(\"content\")\n if content:\n print(content, end=\"\", flush=True)\n```\n\n流式数据由代理按原始字节转发。首个 SSE 数据块发出后,代理不会重新请求上游,以免重复生成和计费。\n\n## 7. 压缩行为\n\n**本节作用:** 说明代理如何在不破坏关键上下文的前提下压缩长对话,以及何时会安全跳过压缩。\n\n代理只尝试压缩较早且足够长的纯文本消息。以下内容始终原样保留:\n\n- `system` 和 `developer`;\n- 当前用户消息及最近 `KEEP_RECENT_TURNS` 轮;\n- `tool` 消息、工具参数、工具结果和含 `tool_calls` 的助手消息;\n- 非字符串 `content`;\n- 纯 JSON 或主要内容为代码的消息。\n\n压缩前会保护代码块、行内代码、URL、邮箱、日期时间、数字、UUID、ID 和引号内文本。压缩结果必须保留全部占位符且实际减少 token,否则该消息使用原文。请求级压缩超时或没有获得有效收益时,整个请求安全透传。\n\n响应头会提供统计:\n\n```text\nX-Proxy-Request-Id\nX-Compression-Applied\nX-Compression-Status\nX-Original-Input-Tokens\nX-Forwarded-Input-Tokens\nX-Compression-Ratio\nX-Compression-Latency-Ms\nX-Compression-Strategy\n```\n\n### 7.1 在简化聊天页开启或关闭压缩\n\n**本节作用:** 控制当前浏览器会话是否允许尝试压缩,而不影响服务器配置或其他使用者。\n\n普通用户不需要在每次请求中填写 token、比例等参数。打开聊天页后,右上角有一个压缩按钮:\n\n- 显示“压缩:已关闭”时,点击一次即可让当前浏览器会话的后续请求尝试压缩;\n- 显示“压缩:已开启”时,点击一次即可关闭;\n- 显示“压缩不可用 · 查看配置”时,说明本地压缩模型尚未安装或无法加载。点击该按钮会打开本地安装指南:`http://127.0.0.1:8080/guide/compression`。\n\n这个按钮只决定本次浏览器会话是否允许压缩,不会改写服务器 `.env`,也不会影响其他使用者。服务器仍会自动跳过短对话、当前问题、最近轮次、代码、JSON 和工具消息。\n\n### 7.2 从零开启本地压缩:Windows 新手完整流程\n\n**本节作用:** 从安装本地模型到验证实际 token 减少,完整配置压缩功能,适合第一次接触模型部署的用户。\n\n本节可以独立完成,不需要先理解 token、Python 或模型原理。压缩是可选功能:不启用时代理和聊天页仍可正常使用 DeepSeek;启用后只会尝试缩短较早、较长的纯文本历史消息。\n\n服务已经启动时,也可以在浏览器打开带按钮的本地指南:\n\n```text\nhttp://127.0.0.1:8080/guide/compression\n```\n\n#### 第 0 步:确认项目目录与 Python 环境\n\n新手推荐 Python 3.11 或 3.12。项目的压缩依赖固定使用 `llmlingua==0.2.2`,并限制 `transformers` 在 5.0 以下,避免新版本 API 变化导致教程失效。\n\n以下示例假设项目在 `D:\\tokens\\token-proxy`。如果你克隆或解压到了其他位置,只替换第一行引号内的目录即可。它会自动尝试项目内 `.venv` 和项目父目录中的 `.venv`:\n\n```powershell\nSet-Location -LiteralPath \"D:\\tokens\\token-proxy\"\n$pythonCandidates = @(\n (Join-Path $PWD \".venv\\Scripts\\python.exe\"),\n (Join-Path $PWD \"..\\.venv\\Scripts\\python.exe\")\n)\n$python = $pythonCandidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1\nif (-not $python) {\n throw \"没有找到虚拟环境。请先按 README 第 3.1 节创建 .venv,然后重新执行本段命令。\"\n}\n& $python --version\n```\n\n最后一行应显示 Python 版本。若项目根目录还没有 `.env`,先创建它:\n\n```powershell\nif (-not (Test-Path -LiteralPath .env)) { Copy-Item .env.example .env }\n```\n\n#### 第 1 步:安装压缩依赖\n\n在同一个 PowerShell 窗口执行:\n\n```powershell\n& $python -m pip install --upgrade pip\n& $python -m pip install -e \".[compression]\"\n& $python -c \"import llmlingua, torch, transformers; print('LLMLingua:', llmlingua.__version__); print('PyTorch:', torch.__version__); print('Transformers:', transformers.__version__)\"\n```\n\n这一步会安装 PyTorch、Transformers 和 LLMLingua,下载量较大。命令最后应打印三个版本号。若 PyTorch 安装失败,请优先使用 Python 3.11 或 3.12 重新创建虚拟环境;不要直接删除当前项目文件。\n\n#### 第 2 步:选择模型并编辑 `.env`\n\n用记事本打开 `.env`:\n\n```powershell\nnotepad .env\n```\n\n普通电脑优先选择 CPU,填写以下内容并保存。不要改动 `UPSTREAM_API_KEY` 和 `PROXY_API_KEY`:\n\n```dotenv\nCOMPRESSION_ENABLED=true\nCOMPRESSOR_MODEL=microsoft/llmlingua-2-xlm-roberta-large-meetingbank\nCOMPRESSOR_DEVICE=cpu\n\nMINIMUM_INPUT_TOKENS=4000\nMINIMUM_MESSAGE_TOKENS=300\nTARGET_COMPRESSION_RATIO=0.45\nKEEP_RECENT_TURNS=2\nMAX_COMPRESSION_LATENCY_MS=5000\nFAIL_OPEN=true\n```\n\n模型选择:\n\n| 适合场景 | `COMPRESSOR_MODEL` | 说明 |\n| --- | --- | --- |\n| 推荐、效果优先 | `microsoft/llmlingua-2-xlm-roberta-large-meetingbank` | 多语言大型模型,首次下载和 CPU 加载较慢;[模型主页](https://huggingface.co/microsoft/llmlingua-2-xlm-roberta-large-meetingbank) |\n| 内存较小、速度优先 | `microsoft/llmlingua-2-bert-base-multilingual-cased-meetingbank` | 较小的多语言模型,压缩效果可能较弱;[模型主页](https://huggingface.co/microsoft/llmlingua-2-bert-base-multilingual-cased-meetingbank) |\n\n`COMPRESSOR_DEVICE=cpu` 是普通 Windows 电脑的安全默认值。只有已经安装并确认可用 CUDA 的 NVIDIA 显卡用户才改成 `cuda`。如果模型已经完整下载到本地,例如 `D:\\models\\llmlingua`,也可以写成:\n\n```dotenv\nCOMPRESSOR_MODEL=\"D:\\models\\llmlingua\"\n```\n\n本地目录必须是完整模型快照,而不只是一个 `.bin` 或 `.safetensors` 文件。路径有空格时保留英文双引号。\n\n`TARGET_COMPRESSION_RATIO=0.45` 是目标保留比例,不保证每次都恰好节省 55%。代理会在保护关键内容和验证结果后,只有确实安全且有收益时才应用压缩。`FAIL_OPEN=true` 表示模型下载、加载、超时或验证失败时安全发送原文;新手请保持 `true`。设为 `false` 时,技术性压缩失败会返回 HTTP 503,而不会发送原文。\n\n#### 第 3 步:处理模型下载网络问题(仅在需要时)\n\n模型首次加载会从 Hugging Face 下载。如果你的网络需要本地代理,请在**启动代理的同一个 PowerShell 窗口**先设置代理地址;以下端口只是示例,请替换为自己的:\n\n```powershell\n$env:HTTP_PROXY = \"http://127.0.0.1:7897\"\n$env:HTTPS_PROXY = \"http://127.0.0.1:7897\"\n```\n\n下载失败后,修复网络或模型路径,再重新启动代理。压缩器会在进程内缓存加载结果,因此不重启可能继续显示旧的失败状态。\n\n#### 第 4 步:启动代理并等待模型首次加载\n\n如果代理已在另一个窗口运行,先回到该窗口按 `Ctrl + C` 停止它。然后在当前窗口执行:\n\n```powershell\n& $python -m uvicorn app.main:app --host 127.0.0.1 --port 8080\n```\n\n看到类似 `Uvicorn running on http://127.0.0.1:8080` 即表示 Web 服务已启动。模型不会在这一步立刻加载;首次访问状态接口、聊天页或合格的压缩请求时才会下载和加载。第一次访问可能持续数分钟,请不要关闭该窗口。\n\n在另一个 PowerShell 窗口确认状态:\n\n```powershell\nInvoke-WebRequest http://127.0.0.1:8080/readyz -TimeoutSec 300 | Select-Object -ExpandProperty Content\n```\n\n| 返回中的 `compressor` 值 | 含义与下一步 |\n| --- | --- |\n| `ready` | 模型已成功加载,可以继续下一步测试。 |\n| `disabled` | `.env` 中仍是 `COMPRESSION_ENABLED=false`;修改后重启代理。 |\n| `degraded` | 代理会正常聊天并回退原文,但模型未能加载;检查模型名、网络、CPU/CUDA 选择和服务窗口日志。 |\n| `unavailable` | 开启了严格模式 `FAIL_OPEN=false` 且模型不可用;新手请改回 `true` 后重启。 |\n\n若提示 8080 端口已被占用,请不要直接结束未知程序。先运行下面命令,确认 `CommandLine` 包含 `uvicorn` 和 `app.main:app` 后再停止对应进程:\n\n```powershell\n$processId = (Get-NetTCPConnection -LocalPort 8080 -State Listen).OwningProcess\nGet-CimInstance Win32_Process -Filter \"ProcessId=$processId\" | Select-Object ProcessId, Name, CommandLine\n```\n\n#### 第 5 步:零 API 费用的本地压缩自检\n\n这个项目附带了自检脚本。它会使用你在 `.env` 中配置的模型、设备和真实压缩管线压缩内置测试文本,**不会访问 DeepSeek,也不会消耗 API 额度**:\n\n```powershell\n& $python scripts\\compression_smoke_test.py\n```\n\n成功时应看到“通过:本地模型加载和项目压缩管线均正常”,以及压缩前后估算 token、压缩比例和耗时。失败时,脚本会显示可执行的原因,例如模型未配置、模型无法加载或压缩结果未达到安全要求。\n\n#### 第 6 步:进行一次真实代理压缩验收(会消耗少量 DeepSeek 额度)\n\n本地自检通过后,保持代理在运行状态,在项目目录执行:\n\n```powershell\npowershell -ExecutionPolicy Bypass -File .\\scripts\\proxy_compression_test.ps1\n```\n\n脚本会从 `.env` 读取代理密钥但不会打印它,构造足够长的历史消息,并向本机代理发送一次 `deepseek-chat` 请求。成功验收应显示:\n\n```text\nCompressionApplied : true\nCompressionStatus : applied\nForwardedInputTokens 小于 OriginalInputTokens\n```\n\n它会消耗一次 DeepSeek 请求额度。若不想产生费用,可跳过本步,只执行第 5 步。\n\n#### 第 7 步:在聊天页中使用开关\n\n打开 `http://127.0.0.1:8080/`,右上角应显示“压缩:已关闭”。点击后变为“压缩:已开启”。此开关只影响当前浏览器会话:\n\n- 开启会向代理发送 `X-Proxy-Compression: enabled`,允许本次请求尝试压缩;\n- 关闭会发送 `X-Proxy-Compression: disabled`,强制本次请求使用原文;\n- 即使开启,短对话、当前问题、最近两轮、代码、JSON、工具调用和非文本内容仍会安全透传;\n- API 客户端不发送这个请求头时,服务器会按 `.env` 的全局压缩配置决定是否尝试压缩。\n\n#### 常见状态与排障\n\n`X-Compression-Status` 始终说明本次是否压缩;压缩前后 token、比例和延迟头只会在 `X-Compression-Applied: true` 时出现。这里的 token 是项目本地估算值,不等于 DeepSeek 最终计费 token。\n\n| 状态 | 含义 | 处理方式 |\n| --- | --- | --- |\n| `applied` | 已实际压缩 | 查看前后 token 是否减少。 |\n| `below_input_threshold` | 总历史低于 4000 估算 token | 正常现象;继续长对话或使用第 6 步脚本。 |\n| `no_compressible_messages` | 没有足够早、足够长的纯文本消息 | 保留更多历史;默认当前问题和最近两轮不会压缩。 |\n| `non_text_content` | 请求含图片、音频、文件等非文本内容 | 当前版本会安全透传整次请求。 |\n| `compressor_unavailable` | 模型未安装或加载失败 | 查看 `/readyz`、模型名、网络和 `COMPRESSOR_DEVICE`。 |\n| `compression_timeout` | CPU 压缩超过时间限制 | 可将 `MAX_COMPRESSION_LATENCY_MS` 临时改为 `30000` 或 `60000`,重启后重试。 |\n| `compression_not_effective` | 压缩收益不足 | 正常安全回退;可以换模型或保留原文。 |\n| `result_validation_failed` | 压缩结果未通过安全检查 | 正常安全回退;不要为了强制压缩而关闭验证。 |\n\n首次配置遇到问题时,请记录 `/readyz` 返回内容、服务窗口中的错误信息、Python 版本和第 5 步脚本输出;不要复制 `.env`、API Key、代理密钥或真实对话内容到 Issue 中。\n\n## 8. 健康检查和监控\n\n**本节作用:** 确认进程是否存活、模型是否可用,并为监控系统提供基础指标。\n\n```bash\ncurl http://localhost:8080/healthz\ncurl http://localhost:8080/readyz\ncurl http://localhost:8080/metrics\n```\n\n- `/healthz` 返回 `{\"status\":\"ok\"}` 表示进程存活;\n- `/readyz` 显示代理配置和压缩器状态;\n- `/metrics` 返回 Prometheus 文本指标。\n\n例如未配置压缩模型但允许回退时:\n\n```json\n{\"proxy\":\"ready\",\"compressor\":\"degraded\",\"fallback\":\"original\"}\n```\n\n## 9. Docker 部署\n\n**本节作用:** 使用容器在另一台电脑、服务器或稳定环境中部署代理,减少本机 Python 环境差异。\n\n不包含大型压缩依赖的轻量构建:\n\n```bash\ndocker compose build\ndocker compose up -d\n```\n\n包含压缩依赖:\n\n```powershell\n$env:INSTALL_COMPRESSION = \"true\"\ndocker compose build\ndocker compose up -d\n```\n\nLinux/macOS:\n\n```bash\nINSTALL_COMPRESSION=true docker compose build\ndocker compose up -d\n```\n\n查看状态与日志:\n\n```bash\ndocker compose ps\ndocker compose logs -f token-proxy\n```\n\n停止服务:\n\n```bash\ndocker compose down\n```\n\n如 `COMPRESSOR_MODEL` 指向宿主机本地目录,需要在 `docker-compose.yml` 中将模型目录挂载进容器,并把配置改成容器内路径。\n\n## 10. 测试\n\n**本节作用:** 在修改代码、升级依赖或准备发布前,验证核心代理和压缩保护逻辑没有回归。\n\n安装开发依赖后运行:\n\n```bash\npytest -q\n```\n\n执行语法检查:\n\n```bash\npython -m compileall -q app tests\n```\n\n测试覆盖 token 估算、最近轮次选择、保护片段恢复、压缩失败回退、请求透传和 API 基本调用。真实上游测试会产生费用,因此默认测试不会访问 DeepSeek。\n\n## 11. 常见问题\n\n**本节作用:** 将常见报错和现象对应到可执行的排查方式,减少反复搜索和猜测。\n\n### `/readyz` 显示 `compressor: degraded`\n\n**本节作用:** 判断压缩模型未就绪时,代理是否仍能安全使用原文继续聊天。\n\n确认已安装 `.[compression]`、设置了 `COMPRESSOR_MODEL`,且模型能在当前用户权限和网络条件下加载。若只需要透明代理,这是预期状态。\n\n### 返回 401\n\n**本节作用:** 排查代理密钥缺失、格式错误或与 DeepSeek 密钥混用的问题。\n\n请求头必须是 `Authorization: Bearer <PROXY_API_KEY>`。不要把上游密钥当作代理密钥,除非你有意将两者配置为相同值。\n\n### DeepSeek 返回 401 或 429\n\n**本节作用:** 区分代理自身的认证问题与 DeepSeek 的认证、余额或限流问题。\n\n代理会保留 DeepSeek 状态码和错误正文。检查 `UPSTREAM_API_KEY`、DeepSeek 账户余额和限流响应头。\n\n### 长对话没有压缩\n\n**本节作用:** 检查压缩阈值、消息类型、模型状态和浏览器开关,判断未压缩是否正常。\n\n检查总 token 和单条消息是否达到阈值,是否存在足够早的纯文本轮次,以及内容是否主要为代码、JSON 或工具消息。查看 `X-Compression-Applied` 响应头和 `/readyz`。\n\n### 公开部署是否安全\n\n**本节作用:** 了解本项目适用的本机/内网边界,以及公开服务前还需补充的安全措施。\n\n本版本面向本机或受信内网。公开部署前还需要 TLS、限流、防暴力破解、密钥轮换、CORS 策略、日志脱敏和多租户隔离。\n\n## 12. 开源协作\n\n**本节作用:** 说明如何安全地报告问题、提交改进和参与项目长期维护。\n\n项目以 [MIT License](LICENSE) 发布。欢迎提交 Issue 和 Pull Request;贡献流程见 [CONTRIBUTING.md](CONTRIBUTING.md),安全问题请先阅读 [SECURITY.md](SECURITY.md)。\n"}}}}