当前位置: 首页 > news >正文

从 Python 到 Node.js:我把两个开源项目揉成一个,在 DeepSeek 上跑出 76% 的 Token 节省率(附完整架构和 35 次真实测试数据)

从 Python 到 Node.js:我把两个开源项目揉成一个,在 DeepSeek 上跑出 76% 的 Token 节省率(附完整架构和 35 次真实测试数据)

先说结果:DeepSeek 真实 API 测试,tokensaver v2.0 代理模式总 Token 节省76.1%,输出节省88.4%,缓存命中时响应 1-3ms。下面讲讲两周的开发过程,包括踩过的坑和关键的 trade-off 决策。


1. 这事是怎么开始的

你调过 DeepSeek 的 API 就知道,一次"解释 Python 装饰器"的问题,裸调返回大概 1024 个 output token。里面有多少是真正有用的信息?我估了一下,可能 30-40% 都在说"好的,让我来帮你详细解释一下"“首先我们需要理解”"综上所述"这种话。

这些就是低信息密度的纯 Token 消耗,一分钱没少花,但没产生价值。

最近 GitHub 上爆了个项目叫 Caveman,一个 19 岁的荷兰小哥写的,52K star。核心思路有多简单?往 System Prompt 里塞一段"像原始人一样说话"的指令——不改模型、不加推理开销,输出直接砍半。

但这个方案有两个明显的问题:一是只支持英文,国产模型(DeepSeek、GLM、Kimi)用英文提示效果打折;二是你得手动把那段 prompt 贴到代码里,换个模型就得改代码重启。

我当时就想:能不能做成一个透明代理层,开发者完全不感知,自动处理所有压缩?


2. 系统架构

2.1 整体数据流

Client (OpenAI SDK) │ POST /v1/chat/completions ▼ ┌─────────────────────────────────────────┐ │ tokensaver Proxy │ │ │ │ Layer 1: SemanticCache │ │ ├─ Tokenize → Jaccard similarity │ │ ├─ Hit → return cached (1-3ms) │ │ └─ Miss → continue │ │ │ │ Layer 2: InputCompressor │ │ ├─ ContentProtector.protect() │ │ ├─ Dedup lines / filter fillers │ │ ├─ Truncate (aggressive mode) │ │ └─ ContentProtector.restore() │ │ │ │ Layer 3: OutputCompressor │ │ ├─ Inject Caveman system prompt │ │ ├─ Forward to upstream LLM │ │ └─ PostProcessor.process() │ │ │ │ Response: + tokensaver stats field │ └─────────────────────────────────────────┘ │ ▼ Upstream LLM (DeepSeek / GLM / Kimi / ...)

2.2 模块职责

模块文件行数核心算法
代理服务器server.js370HTTP pipeline + auto model routing
语义缓存semantic-cache.js230Jaccard similarity + TTL eviction
输入压缩input.js155Line dedup + filler filter + truncation
内容保护content-protector.js95Placeholder substitution pattern
后处理器post-processor.js85Regex-based greeting removal

3. 核心模块实现

3.1 语义缓存:为什么选 Jaccard 而不是 Embedding

tokcut 的 Python 版本使用了 sentence-transformers 的all-MiniLM-L6-v2模型(约 80MB)做语义 embedding,然后用余弦相似度匹配。

Node.js 版本的 trade-off 分析:

// 方案 A: sentence-transformers → 需要 @xenova/transformers (~500MB deps)// 方案 B: Jaccard similarity on tokenized text → 0 额外依赖_jaccardSimilarity(tokensA,tokensB){constsetA=newSet(tokensA),setB=newSet(tokensB);letintersection=0;for(constitemofsetA){if(setB.has(item))intersection++;}returnintersection/(setA.size+setB.size-intersection);}

实测对比(1000 条测试数据):

方案冷启动内存命中率(0.92阈值)
sentence-transformers~3s (模型下载)~200MB91.2%
Jaccard (js-tiktoken)0ms~15MB88.7%

Jaccard 命中率仅低 2.5 个百分点,但启动零延迟、内存占用和部署复杂度大幅降低。对于中小规模部署场景,Jaccard 是更优选择。

3.2 内容保护器的占位符机制

这是整个压缩链路中最容易出 Bug 的模块。核心矛盾:压缩算法不能损坏代码块、URL 和版本号。

// protect → compress → restore 三段式classContentProtector{protect(text){// 1. 多行代码块: ```...```text=text.replace(/```[\s\S]*?```/g,m=>this._addPlaceholder(m));// 2. 行内代码: `...`text=text.replace(/`[^`]+`/g,m=>this._addPlaceholder(m));// 3. URL: https?://...text=text.replace(/https?:\/\/[^\s<>"{}|\\^`\[\]]+/g,m=>this._addPlaceholder(m));// 4. 版本号: \d+\.\d+\.\d+text=text.replace(/\b\d+\.\d+\.\d+\b/g,m=>this._addPlaceholder(m));returntext;}restore(text){// 反向遍历还原,防止占位符嵌套for(leti=this.placeholders.length-1;i>=0;i--){text=text.replace(`__TOKENSAVER_PROTECTED_${i}__`,this.placeholders[i]);}returntext;}}

一个踩过的坑:aggressive 压缩模式会截断文本到 70%,如果截断位置恰好切在占位符中间,restore()就会失败。解决方案是在_aggressiveCompress()中加判断:

if(!text.includes('__TOKENSAVER_PROTECTED_')){constlimit=Math.max(Math.floor(text.length*0.7),1);text=text.substring(0,limit);}

3.3 输入压缩的双模式设计

classInputCompressor{compressText(text){letprotected_=this.protector.protect(text);if(this.mode==='safe'){protected_=this._safeCompress(protected_);// 去重行 + 去填充词}else{protected_=this._aggressiveCompress(protected_);// safe + 截断70%}returnthis.protector.restore(protected_);}compressMessages(messages){// 关键设计:仅压缩 role === 'user' 的消息returnmessages.map(msg=>msg.role==='user'?{...msg,content:this.compressText(msg.content)}:msg);}}

为什么只压 user 消息?因为 system prompt 中的技术约束和 assistant 的历史回答可能包含关键上下文(如错误信息原文),压缩这些内容会导致模型理解偏差。


4. Benchmark 方法

4.1 测试设计

被测模型: deepseek-chat 测试场景: 5 类(代码解释/技术概念/代码审查/最佳实践/问题排查) 测试方案: 7 种(Baseline + tokcut Lite/Full/Ultra + tokensaver CN-Full/Ultra) 总调用次数: 5 × 7 = 35 次

每轮调用设置max_tokens=2048temperature=0.7,请求间间隔 1s 避免限流。Token 计数以 API 返回的usage字段为准。

4.2 测试结果(35次调用汇总)

方案Prompt TokenOutput TokenTotal Token总节省率输出节省率
Baseline1898,0518,240--
tokcut Lite3693,2083,57756.6%60.2%
tokcut Full4242,2782,70267.2%71.7%
tokcut Ultra3941,4191,81378.0%82.4%
tokensaver CN-Full1,0399311,97076.1%88.4%
tokensaver CN-Ultra6195601,17985.7%93.0%

4.3 结果分析

核心发现 1:中文 Prompt 优于英文 Prompt。tokensaver CN-Full 的输出节省率(88.4%)显著高于 tokcut Full(71.7%),差异为 16.7 个百分点。即使 tokensaver 的 System Prompt 开销更大(1039 vs 424 token),总节省率仍领先 8.9 个百分点。

核心发现 2:盈亏平衡分析。tokensaver CN-Full 的额外 Prompt 开销为 1039 - 424 = 615 token,但由此产生额外输出节省 2278 - 931 = 1347 token,净收益 732 token,ROI = 119%。

核心发现 3:Ultra 模式的总节省率高达 85.7%,但输出可读性显著下降。生产环境推荐 CN-Full,在可读性和节省率之间取得平衡。


5. 工程化设计

5.1 代理服务器的动态控制

// 通过 HTTP Header 实现按请求覆盖配置,无需重启服务constoutputCompressEnabled=this._parseBoolHeader(req.headers['x-tokensaver-compress'],this.compressEnabled);constcompressLevel=req.headers['x-tokensaver-level']||this.compressLevel;constcacheEnabled=this._parseBoolHeader(req.headers['x-tokensaver-cache'],this.cache.enabled);

支持 7 个 Header 控制项,覆盖压缩开关、级别、缓存行为、输入压缩等。

5.2 响应中的 Token 统计注入

{"tokensaver":{"cache_hit":false,"input_tokens_before":150,"input_tokens_after":120,"input_tokens_saved":30,"output_tokens":200,"output_tokens_saved":800,"output_compression_applied":true,"prompt_compression_applied":false,"compression_level":"cn-full","elapsed_ms":2546}}

5.3 测试覆盖

$nodetests/run.js# v1 原有测试✅ 通过:17$nodetests/run-v2.js# v2 新增模块测试✅ Content Protector:4/4 ✅ Input Compressor:4/4 ✅ Semantic Cache:4/4 ✅ Post Processor:4/4 ✅ Output Compressor:4/4 ✅ 通过:20总计:37/37 通过

6. 与现有工作的对比

维度Cavemantokcuttokensaver v2
部署方式Claude Code 插件Python 代理Node.js 代理 + CLI
压缩语言英文英文中文 + 英文 + 文言文
语义缓存sentence-transformersJaccard
输入压缩
代理模式
CI/CD
Docker
文件压缩
Benchmark

7. 不足和后面想做的事

几个明显的局限:

  • 还没支持 SSE 流式(streaming),目前只能处理非流式的/v1/chat/completions。流式的问题是每 chunk 都要后处理,得算增量,还没想清楚怎么做。
  • Jaccard 缓存对超长文本的区分度肯定不如 Embedding。如果有人问两篇都很长的文章但主旨不同,Jaccard 的 token 集重合度可能误判。不过短中文本够用了。
  • 只兼容 OpenAI 接口格式,Anthropic 那种不标准的暂时不行。

后面想加的:流式响应、Redis 缓存后端(生产环境需要)、一个简单的 Web 面板看实时统计。


8. 收尾

一周时间,从刷到 B站 上的 Caveman 开始,一路写到这里。最关键的决策有两个:一是用真实 API 跑数据再做技术选型,不凭直觉——要不是跑了 35 次调用,我不会发现中文 prompt 比英文好那么多;二是把工程化补齐,Docker、CI、测试、文档,让一个 demo 变成能真正交付的东西。

如果你也用 LLM API 做开发,试试:

gitclone https://github.com/luckychenxiaowen/tokensaver.gitcdtokensaver&&npminstall&&npmrun serve

然后 base_url 改一行。完事。


项目:github.com/luckychenxiaowen/tokensaver

http://www.jsqmd.com/news/761065/

相关文章:

  • 2026生物医用泡沫箱多维度评测报告:冰袋生产厂家/大号加厚泡沫箱/生物医用泡沫箱/干冰配送/泡沫箱生产厂家/选择指南 - 优质品牌商家
  • 保姆级避坑指南:在Ubuntu 20.04双系统上搞定Nvidia V100驱动与CUDA 11.1(附关闭自动更新关键步骤)
  • 当安装教程遇上ai:用快马打造能听懂问题的pycharm智能配置助手
  • 自托管任务管理工具Questlog:全栈技术解析与实战部署指南
  • UE GAS 实战(六)完美格挡与动画分层融合
  • 华硕笔记本终极优化指南:用G-Helper实现AMD CPU降压调优
  • ESP32-P4开发板评测:7英寸HMI屏与AIoT应用实践
  • 如何用思维导图拆解项目范围
  • 3个致命误区导致国密支付上线失败!PHP工程师必查的国密证书链校验、时间戳RFC3161标准、随机数熵源合规性清单
  • Balena Etcher三步指南:免费开源工具,安全烧录系统镜像到SD卡和U盘
  • Dify对接MES/ERP非结构化日志的智能检索方案(含日志时间序列语义增强模块开源代码)
  • 从传感器开发到Modbus从机:用STM32 HAL库+FreeModbus快速搭建你的工业协议栈
  • Taotoken用量看板如何帮助团队清晰管理AI调用成本
  • OpenUI深度解析:AI驱动界面生成从原理到实战部署
  • 基于飞书与Claude Code的AI Agent自动化工作流构建指南
  • 为什么你的PHP AI校验总被绕过?7个被90%开发者忽略的安全盲区,今天必须修复
  • AI辅助开发:基于快马多模型能力打造你的智能终端,让xshell8具备AI思考力
  • 如何用开源工具让旧Mac重获新生?三步解锁硬件隐藏潜力
  • Docker化Emacs开发环境:跨版本测试与CI/CD集成实践
  • VIOLA框架:小样本视频理解的技术突破与实践
  • ai赋能嵌入式开发:让快马智能助手帮你完成stm32cubemx配置与代码生成
  • 终极Windows Defender控制:开源工具让你完全掌控系统安全
  • 多智能体协作平台AgentWall:从架构设计到工程实践
  • genshin-fps-unlock深度解析:突破《原神》60帧限制的架构实现与实战指南
  • 边缘计算中3D高斯泼溅技术的优化与实现
  • 解密BepInEx:突破性Unity游戏插件框架的实战应用与架构解析
  • OpenAgents智能体开发平台:从核心原理到实战部署
  • camh:轻量级跨平台摄像头框架,嵌入式视觉开发的高性能选择
  • 从APK签名到安装:一次完整的apktool反编译、修改与V1/V2签名实战记录
  • AI智能体记忆管理:基于文件系统的无侵入式记忆整理与提取方案