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

从缓存到知识库:用 RAG 给 OCR 系统加上“长期记忆”

当“精确匹配”遇到瓶颈,“语义检索”让缓存更有价值

一、背景

在从零搭建智能体检报告解析系统:OCR + 本地大模型实践-CSDN博客中,我们搭建了一套“OCR + 本地大模型”的体检报告解析系统。虽然项目可以跑起来,但是本着精益求精的精神我发现还有几个优化点,1是可以增加缓存提高速度和降低成本,2是增加rag检索增强语义检索,后续还能在次基础上进行数据分析。以下为正文:

百度OCR每日有1000的免费调用额度,1000次听起来不少,但在实际业务场景中:

  • 同一份报告可能被多次提交(HR重复上传、候选人重新发送)

  • 不同候选人可能提交同一家医院的同类报告

  • 测试和调试阶段也会消耗调用次数

如果1000次用完了怎么办?

于是产生了这个需求——给OCR系统加上缓存

text

核心需求清单: 1. 上传文件计算MD5,已处理过直接返回缓存结果 2. 缓存命中后,用Tesseract快速验证人员信息是否一致(防止缓存“张冠李戴”) 3. 百度OCR每日1000条调用限制,超出自动降级到Tesseract 4. 缓存、限流都有独立开关,方便调试和应急切换 5. 每个缓存条目做成可检索的知识库,方便后续查询分析

二、刚性MD5精确缓存

最简单的缓存思路:文件内容不变,结果就不变

计算上传文件的MD5值作为Key,OCR结果作为Value存入Redis:

text

┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 上传文件 │ ──▶ │ 计算 MD5 │ ──▶ │ 查询 Redis │ └──────────────┘ └──────────────┘ └──────┬───────┘ │ ┌─────────────┴─────────────┐ │ │ ▼ ▼ ┌───────────┐ ┌───────────┐ │ 命中缓存 │ │ 未命中 │ │ 直接返回 │ │ 走百度OCR │ └───────────┘ └───────────┘

为什么还要加“人员信息比对”?

缓存最大的风险是“张冠李戴”——文件相同,缓存结果也相同,这没问题。但万一Redis里的缓存数据被污染了(比如之前识别错了),后续所有命中都会返回错误结果。

所以加了缓存校验逻辑:命中缓存后,用Tesseract(免费离线OCR)快速提取一下当前文件的人员姓名和体检日期,与缓存中存储的比对。匹配则返回,不匹配则重新走百度OCR,并更新缓存。

java

// 缓存命中后,快速校验 PatientInfo cachedInfo = cacheEntry.getPatientInfo(); PatientInfo quickInfo = tesseract.extractPatientInfo(fileBytes); if (match(cachedInfo, quickInfo)) { return cacheEntry; // 校验通过,放心返回 } else { evictCache(md5); // 校验失败,清除缓存,重新识别 return executeBaiduOcr(fileBytes); }

每日限流 + 自动降级

每天1000次的配额,用Redis做原子计数:

yaml

ocr: rate-limit: enabled: true daily-limit: 1000

配额用完或百度OCR不可用时,自动降级到Tesseract(虽然准确率稍低,但够用):

java

if (!rateLimit.tryAcquire()) { log.warn("今日配额已用完,降级到Tesseract"); return fallbackToTesseract(fileBytes); }

三、第二层:RAG语义缓存

MD5缓存有个硬伤——太“刚”了

场景MD5缓存效果
完全相同的文件✅ 命中完美复用
内容一样但格式不同(PDF → Word)❌ MD5变了缓存失效,重新OCR
稍有差异的报告❌ 完全无关缓存失效

如果能把“缓存”升级为“相似报告检索”,就能解决这个问题。RAG(检索增强生成)正好做这件事。

RAG缓存的核心思路

  1. 每次OCR完成后,把结果摘要(姓名、年龄、体检日期、指标异常情况)向量化

  2. 存入Dify知识库

  3. 新报告来的时候,先用Tesseract快速提取摘要,在知识库中做相似度检索

  4. 相似度超过阈值(如0.85)则直接复用,否则走百度OCR

text

┌─────────────────────────────────────────────────────────────────────────────┐ │ MD5缓存查不到 或 格式变了 │ │ ↓ │ │ Tesseract快速提取摘要(姓名+年龄+体检日期+指标概要) │ │ ↓ │ │ 在RAG知识库中语义检索(向量相似度) │ │ ↓ │ │ 相似度 > 0.85? │ │ ├── 是 → 复用缓存的OCR结果(即使格式不同,内容相似) │ │ └── 否 → 走百度OCR → 新结果存入RAG知识库 │ └─────────────────────────────────────────────────────────────────────────────┘

具体实现

每个缓存条目存储

json

{ "id": "uuid", "md5": "abc123...", "patient_info": {"name": "邓飞", "age": 52, "exam_date": "2026-04-10"}, "indicators": [{"name": "甘油三酯", "value": 4.82, "status": "abnormal"}], "summary": "姓名:邓飞,年龄:52岁,异常指标:甘油三酯偏高、血糖偏高", "tags": ["高血糖", "高血脂", "心电图异常"], "engine": "baidu" }

检索摘要的生成:把人员信息和指标状态组合成一段自然语言:

text

姓名:邓飞,性别:男,年龄:52岁,体检日期:2026-04-10 异常指标:甘油三酯:4.82、血糖:7.20 正常指标:白细胞:6.19、血红蛋白:152、血小板:241

这段文本用Embedding模型(如nomic-embed-text)向量化后存入知识库。新报告来时,生成同样的摘要格式做检索,语义相似度高的就能命中。

MD5缓存 vs RAG缓存:分工协作

场景MD5缓存RAG缓存
完全相同文件✅ 1ms返回
内容相似但格式不同✅ 可命中
人员相同但体检日期不同❌ 不命中(语义不同)
查询"之前血糖偏高的报告"✅ 语义检索

它们不是互斥的,是互补的——MD5做精确匹配,RAG做语义相似,两层都查不到才走百度OCR。

四、关键代码片段

4.1 缓存优先级策略

java

public OcrResult extract(MultipartFile file) { byte[] bytes = file.getBytes(); String md5 = Md5Util.calculate(bytes); // 1. 精确缓存(MD5) CacheEntry cached = exactCache.get(md5); if (cached != null && validation.pass(cached, bytes)) { return OcrResult.exactCache(cached); } // 2. 快速提取摘要,查RAG缓存 String summary = tesseract.extractSummary(bytes); RagHitResult ragHit = ragCache.search(summary); if (ragHit.isHit()) { return OcrResult.ragCache(ragHit.getEntry()); } // 3. 检查配额 if (!rateLimit.tryAcquire()) { return fallbackToTesseract(bytes); } // 4. 执行百度OCR OcrResult result = baiduOcr.extract(bytes); // 5. 存入两层缓存 exactCache.put(md5, result); ragCache.save(result); return result; }

4.2 每日限流实现(Redis原子计数)

java

public boolean tryAcquire() { String key = "ocr:ratelimit:" + LocalDate.now(); RAtomicLong counter = redissonClient.getAtomicLong(key); if (counter.get() == 0) { // 首次使用,设置次日凌晨过期 counter.expire(getSecondsUntilMidnight(), TimeUnit.SECONDS); } if (counter.get() >= 1000) { return false; // 配额已用完 } counter.incrementAndGet(); return true; }

五、开关控制

所有功能都有独立开关,方便调试和应急:

yaml

ocr: cache: enabled: true # 精确缓存开关 expire-days: 30 rag-cache: enabled: true # RAG缓存开关 similarity-threshold: 0.85 rate-limit: enabled: true # 每日限流开关 daily-limit: 1000 validation: enabled: true # 缓存校验开关

调试场景

  • 测试新逻辑时,关掉所有缓存ocr.cache.enabled: falseocr.rag-cache.enabled: false

  • 压测时,关掉限流ocr.rate-limit.enabled: false

  • 怀疑缓存数据有问题时,关掉校验ocr.validation.enabled: false

六、效果与收益

场景优化前优化后
相同文件重复上传消耗OCR额度缓存命中,1ms返回
格式变化但内容相同重新OCRRAG命中,复用结果
1000次额度用完花钱(bushi)自动降级到Tesseract
查询历史报告翻日志知识库语义检索

核心收益

  • 个人百度OCR账号的1000次/天额度变得够用(缓存命中率70%+)

  • 响应速度从30-60秒降到1-5ms(缓存命中时)

  • 积累了一个可检索的体检报告知识库

  • 为后续的“相似报告推荐”、“异常趋势分析”打下基础

七、总结

RAG这个知识库还可以做更多事情

  • 输入“最近一个月有多少份高血糖报告?”→ 语义检索 + 统计

  • 输入“有没有和XX情况类似的候选人?”→ 相似案例推荐

  • 输入“甘油三酯异常的报告都是哪些医院的?”→ 分类分析

从缓存到知识库,从精确匹配到语义理解,这套方案让“省钱”变成了“积累”。

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

相关文章:

  • Jmeter 压测保姆级入门教程
  • LinkSwift:五分钟搞定九大网盘直链下载,告别龟速烦恼
  • HDMI显示异常问题(MPO接口拔插问题)
  • LinkSwift深度解析:开源网盘直链提取工具的技术架构与实战指南
  • 浏览器控制台模拟请求
  • 【IDEA文件模板高阶实战指南】:20年JetBrains深度用户揭秘97%开发者不知道的5大模板黑科技
  • 重磅:chatgpt对低价、黑Pro进行大清算!正价用户也被殃及。
  • 玉虎装饰家装工装双轨并行,一站式承接全品类装修需求
  • 树莓派GPU内存分配误区解析:gpu_mem参数的正确使用指南
  • KMS智能激活脚本:3分钟免费激活Windows和Office的终极解决方案
  • 2026年,四川省成都市透明胶带厂商名声究竟如何?背后真相大揭秘!
  • VMware vSphere 8.0与Windows Server 2022 Hyper-V功能对齐全景图:18项关键能力逐行比对(含API/SDN/TPM支持)
  • 告别Windows激活烦恼:智能脚本让系统授权变得简单
  • 广州机电安装公司推荐?广州机电安装价格!
  • 063、MCP 协议基础:Model Context Protocol 的架构与 CodeX 的集成方式
  • Windows和Office激活终极指南:KMS智能激活脚本完整教程
  • 信安毕设最全选题怎么做
  • Chatbox终极指南:3步轻松搭建你的AI桌面助手
  • 宽压、精控、多调光:TP8525 大功率LED恒流驱动芯片应用场景深度解析
  • 如何快速将XAPK转换为APK:一站式解决Android应用安装兼容性问题
  • KMS_VL_ALL_AIO:3分钟搞定Windows和Office激活的终极方案
  • 一种另类的最小生成树(MST)算法:Boruvka算法
  • 【Netty源码解读和权威指南】第69篇:Netty与gRPC——高性能RPC框架的底层网络秘密
  • 【控制工程全栈】2026年自控阀门技术架构解析与源头工厂选型指南(附总线诊断Python源码)
  • okbiye AI 写作数据分析:甩掉 SPSS 与 Python,自动生成可直接复用的 docx 实证报告
  • Destiny 2 Solo Enabler:3步实现单人游戏,告别匹配烦恼
  • 如何快速下载Fansly内容:终极Fansly下载器使用指南
  • 变系数Camassa-Holm方程小色散渐近解:类孤子与类尖峰形态分析
  • USB打印机/加密狗/工业采集卡在VMware中无法识别?一线运维团队压箱底的8步黄金复位流程
  • 视频号资源下载难题如何破解?3个核心功能带你轻松获取网络素材