项目实训(十三)|中医智能诊疗系统 热门搜索与文言文翻译功能实现
从用户行为洞察到古籍知识普惠
一、背景与需求
在中医知识库查询模块的开发过程中,两个看似独立的用户需求逐渐浮现:
1. 热门搜索:用户需要“被引导”
知识库中存储了大量的食材、穴位、方剂信息,但新用户往往不知道从何查起。传统的做法是在搜索框下方固定几个热门词(如“气虚体质”、“菊花”),但这些词是硬编码的,无法反映真实的用户搜索趋势,也无法根据季节或热点动态变化。
2. 文言文翻译:用户需要“被理解”
知识库中大量内容来自中医古籍(如《本草纲目》《伤寒论》),这些文献以文言文写成,对普通用户存在阅读障碍。用户看到一段古籍原文后,如果无法理解其含义,知识库的“知识传递”价值就大打折扣。
针对这两个痛点,我分别设计了动态热门搜索和文言文翻译两个功能。
二、热门搜索:从静态数据到动态洞察
2.1 设计思路
热门搜索的核心是记录用户的真实搜索行为,并从中提取高频词。技术上需要解决三个问题:
- 记录什么:用户每次通过主搜索框发起的查询
- 怎么存储:统计每个搜索词的出现次数
- 怎么展示:按频率降序返回 Top N 个词
2.2 技术实现
在backend/api/knowledge.py中,我使用内存中的Counter来统计搜索词,并设置每 24 小时自动清空的机制,确保热门词反映近期趋势而非历史累积。
fromcollectionsimportCounterimporttime# 全局计数器_search_counter=Counter()_last_cleanup=time.time()defrecord_search(q:str):"""记录用户搜索词"""ifqandlen(q.strip())>=2:# 过滤过短的搜索词_search_counter.update([q.strip()])@router.get("/hot_searches")asyncdefget_hot_searches(limit:int=6):global_last_cleanup# 每 24 小时自动清空,避免数据膨胀iftime.time()-_last_cleanup>86400:_search_counter.clear()_last_cleanup=time.time()hot=[wordforword,_in_search_counter.most_common(limit)]return{"code":200,"data":hot}在/search接口中,每次全库搜索时调用record_search(q):
@router.get("/search")asyncdefsearch(q:str=Query(...,min_length=1),...):# 记录搜索词(仅全库搜索,分类搜索不记录)record_search(q)# ... 原有搜索逻辑前端在页面加载时调用/knowledge/hot_searches接口,将返回的动态数据替换原有的静态列表:
constloadHotSearches=async()=>{constres=awaitrequest.get('/knowledge/hot_searches')if(res.code===200&&res.data.length){hotSearches.value=res.data}}2.3 技术思考
为什么用内存而不是 Redis?
项目当前处于开发演示阶段,使用内存Counter足以满足需求,且无需额外依赖。但在生产环境中,内存存储存在两个局限:一是后端重启会丢失数据;二是多实例部署时无法共享计数。因此,我在代码中预留了迁移至 Redis 的接口设计,后续只需替换存储层即可。
为什么 24 小时清空?
热门搜索的本质是反映当下的用户兴趣。如果不定期清空,早期的高频词会长期占据榜单,无法反映近期热点(如季节变化带来的搜索词变化)。24 小时的窗口既保证了数据的时效性,又不会因频繁重置导致榜单波动过大。
与静态数据的对比
| 对比项 | 静态热门搜索 | 动态热门搜索 |
|---|---|---|
| 数据来源 | 硬编码 | 用户真实搜索行为 |
| 时效性 | 固定不变 | 每 24 小时更新 |
| 维护成本 | 需手动修改代码 | 自动统计,零维护 |
| 用户引导效果 | 一般 | 更好(反映真实热点) |
三、文言文翻译:从原文到现代汉语
3.1 设计思路
古籍翻译功能的目标是:当用户查看卡片详情时,如果原文是文言文,可以点击按钮一键翻译为现代汉语。
技术上需要解决三个核心问题:
- 翻译质量:中医古籍术语专业,需要大模型具备中医领域知识
- 响应速度:用户点击后等待时间不宜过长
- 成本控制:避免相同内容重复调用大模型
3.2 技术实现
后端翻译接口
在backend/api/knowledge.py中新增翻译接口,调用项目已有的 LLM 服务:
classTranslateRequest(BaseModel):text:str# 翻译缓存_translation_cache={}@router.post("/translate")asyncdeftranslate_text(req:TranslateRequest):text=req.text.strip()ifnottextorlen(text)<5:return{"code":200,"translated":""}# 生成缓存键(使用内容的 MD5 哈希)cache_key=hashlib.md5(text.encode('utf-8')).hexdigest()# 检查缓存ifcache_keyin_translation_cache:return{"code":200,"translated":_translation_cache[cache_key]}llm=get_llm()prompt=f"""你是一位中医古籍翻译专家。请将以下文言文或中医专业内容翻译成通俗易懂的现代汉语,要求: - 保留原意,不添加额外信息 - 语言流畅,适合普通人阅读 - 直接输出翻译结果,不要有任何解释或标记 原文:{text[:1500]}翻译:"""try:result=awaitllm.generate(prompt,temperature=0.3)translated=result.strip()# 存入缓存_translation_cache[cache_key]=translated# 控制缓存大小iflen(_translation_cache)>1000:for_inrange(100):_translation_cache.popitem(last=False)return{"code":200,"translated":translated}exceptExceptionase:print(f"翻译失败:{e}")return{"code":500,"message":"翻译服务暂时不可用"}前端交互设计
在知识卡片详情弹窗中,原文和译文采用左右并列布局:
┌─────────────────────────────────────────────────────────┐ │ 📜 原文 │ 🌿 现代汉语译文 │ │ 【定位】在胸部,横平第1肋间隙... │ 【定位】在胸部,与 │ │ 【解剖】当胸大肌、胸小肌处... │ 第1肋间隙水平相同...│ │ 【主治】①咳嗽、气喘... │ 【主治】①咳嗽、... │ │ │ │ │ │ [点击翻译] 按钮 │ └─────────────────────────────────────────────────────────┘未翻译时右侧显示“点击翻译”按钮;点击后调用后端接口,译文显示在右侧区域,方便用户对照阅读。
<divclass="detail-compare"><!-- 原文栏(始终显示) --><divclass="detail-original"><divclass="detail-label">📜 原文</div><divclass="detail-text">{{ currentDetail?.content }}</div></div><!-- 译文栏(点击后展开) --><divv-if="showTranslation"class="detail-translated"><divclass="detail-label">🌿 现代汉语译文</div><divv-if="translating">翻译中...</div><divv-else-if="translatedText"class="detail-text">{{ translatedText }}</div></div><divv-else><button@click="translateDetail">点击翻译</button></div></div>3.3 技术思考
为什么用大模型而不是规则翻译?
中医古籍的翻译涉及大量专业术语(如“归经”、“性味”、“君臣佐使”),这些词汇在不同的语境下可能有不同的含义。基于规则的翻译(如词典替换)无法处理这些复杂的语义变化,而大模型具备上下文理解能力,能够根据语境选择恰当的现代汉语表达。
Prompt 设计的关键
翻译功能的 prompt 设计经历了多次迭代。最初我让模型“翻译成现代汉语”,但结果时好时坏——有时译文过于口语化丢失了专业信息,有时又过于书面化不够通俗。最终我在 prompt 中增加了三条明确要求:
- 保留原意,不添加额外信息——防止模型“发挥”
- 语言流畅,适合普通人阅读——明确目标受众
- 直接输出翻译结果——避免模型输出“好的,翻译如下”等冗余内容
缓存策略的考量
同一段古籍原文可能被多个用户查看,如果不加缓存,每次点击都会调用大模型,既增加成本又降低响应速度。我用内容的 MD5 哈希作为缓存键,并设置了 1000 条的上限(防止内存溢出)。当缓存超过上限时,删除最早的 100 条(利用 Python 3.7+ 字典保持插入顺序的特性)。
前后端分离的交互设计
翻译采用“点击触发”而非“自动翻译”的设计,既节省了大模型调用成本,也让用户在自己需要时才触发翻译,避免不必要的等待。
四、总结与思考
4.1 两个功能的共同点
热门搜索和文言文翻译看似无关,但都体现了同一个设计理念:让技术服务于用户的真实需求。
- 热门搜索降低用户的启动成本——不知道搜什么时,看别人搜什么
- 文言文翻译降低用户的理解成本——看不懂古籍时,一键翻译
4.2 技术选型的权衡
| 功能 | 技术选型 | 权衡考虑 |
|---|---|---|
| 热门搜索 | 内存 Counter | 开发简单,满足演示需求;生产环境可迁移至 Redis |
| 文言文翻译 | 大模型 + 缓存 | 质量高但成本可控;缓存避免重复调用 |
4.3 未来优化方向
热门搜索:
- 接入 Redis 实现多实例共享
- 增加时间权重(近期搜索权重更高)
- 按分类维度统计(“食材类热门”、“穴位类热门”)
文言文翻译:
- 支持流式输出(SSE),逐字显示译文
- 增加术语解释(点击术语弹出释义)
- 支持多语言(英文、日文)翻译
这两个功能的实现,让我更加深刻地体会到:好的产品不是堆砌功能,而是在用户需要的时候,提供恰到好处的帮助。