{"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 `。不要把上游密钥当作代理密钥,除非你有意将两者配置为相同值。\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"}}}}