# 品牌药品推荐 - 后端接口分析与方案(已完成实施) > **状态:已实施** (2026-07-29) > 详见 HANDOVER.md 第十六章节。 ## 一、需求理解 ### 核心流程 ``` 用户提问 → RAG检索 → LLM生成回答(SSE流式) → 从sources中提取药名 → 关键词匹配(异步) → 返回匹配的品牌推荐 → 前端展示在回答末尾 ``` ### 举例 - AI回答中"参考原文"区域出现「布洛芬缓释胶囊」→ 匹配关键词"布洛芬" → 推荐"芬必得布洛芬缓释胶囊" - AI回答中「感冒灵颗粒」→ 匹配"感冒灵" → 推荐"999感冒灵颗粒" --- ## 二、匹配逻辑分析 ### 匹配对象:`buildSources` 返回的 sources 列表 当前 `chatStream` 尾部 `tailFlux` 中的 sources 结构(来自 buildSources line 720+): ```json { "drug_id": "xxx", "name": "布洛芬缓释胶囊", // ← 匹配目标 "section": "用法与用量", "category": "化学药品", "source": "2025版药典二部", "excerpt": "..." } ``` ### 匹配规则(按之前的需求) | 规则 | 说明 | |---|---| | 匹配方式 | `sources[i].name` **包含**关键词(忽略大小写) | | 示例 | 标题"布洛芬缓释胶囊"包含关键词"布洛芬" → 命中 | | 同一标题多命中 | 去重展示所有匹配的品牌 | | 无命中 | 不展示推荐区域,不影响正常回答 | | 时机 | AI回答生成后,meta事件发送前(不增加用户可感知延迟) | ### 在 SSE 流中的注入位置 ``` 现有流: ...[token1] [token2] ... [tokenN] → [meta: {sources, intent, cid}] → 完成 改造后: ...[token1] [token2] ... [tokenN] → [brand_recommend: {...}] → [meta: {...}] → 完成 ``` 新增 SSE 事件类型 `brand_recommend`,携带匹配结果。 --- ## 三、需新增的后端内容 ### 3.1 数据库表:`brand_recommend_rules` ```sql CREATE TABLE brand_recommend_rules ( id SERIAL PRIMARY KEY, keyword VARCHAR(100) NOT NULL UNIQUE, -- 匹配关键词(药名) brand_name VARCHAR(200) NOT NULL, -- 推荐品牌药品名称(必填) description VARCHAR(500), -- 推荐品牌药品说明(可选) is_active BOOLEAN NOT NULL DEFAULT TRUE, -- 启用/停用开关 created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); ``` ### 3.2 JPA 实体 + Repository - `BrandRecommendRule.java` — entity - `BrandRecommendRuleRepository.java` — 查询启用的规则 ### 3.3 品牌推荐 Service:`BrandRecommendService` ```java @Service public class BrandRecommendService { // 核心方法:根据 sources 列表返回匹配的品牌推荐 public List match(List> sources) { // 1. 从 sources 提取所有 drug name // 2. 查询所有 is_active=true 的规则 // 3. 对每个 name 做包含匹配(忽略大小写) // 4. 去重,返回匹配结果 } } ``` ### 3.4 管理端 CRUD 接口(AdminKnowledgeController 新增) | 方法 | 路径 | 说明 | |---|---|---| | `GET` | `/api/v1/admin/brand-recommend-rules` | 列表(分页+搜索) | | `POST` | `/api/v1/admin/brand-recommend-rules` | 新增 | | `PUT` | `/api/v1/admin/brand-recommend-rules/{id}` | 修改 | | `DELETE` | `/api/v1/admin/brand-recommend-rules/{id}` | 删除 | | `POST` | `/api/v1/admin/brand-recommend-rules/import` | Excel 导入 | | `GET` | `/api/v1/admin/brand-recommend-rules/export` | Excel 导出 | ### 3.5 ChatController 改造 在 `chatStream` 的 `tailFlux` 中,LLM 流结束后、meta 事件前,插入品牌推荐: ```java Flux> tailFlux = Flux.defer(() -> { List> sources = buildSources(docs); // 新增:品牌推荐匹配 List brandRecs = brandRecommendService.match(sources); // 先发 brand_recommend 事件,再发 meta return Flux.concat( buildBrandRecommendEvent(brandRecs), buildMetaEvent(sources, intent, cid) ); }); ``` --- ## 四、需要确认的问题 ### Q1:匹配范围 需求原文说匹配对象为「参考原文标题」,即 `sources[].name`。但你说"回答了感冒灵颗粒"—这是匹配 AI 回答正文还是 sources? - **方案A(推荐)**:只匹配 `sources[].name`(参考原文标题,已结构化) - **方案B**:同时匹配 AI 回答正文全文(性能开销大,易误匹配) ### Q2:品牌推荐 SSE 事件格式 前端期望的 `brand_recommend` 事件数据格式是什么?建议: ```json { "event": "brand_recommend", "data": { "recommendations": [ {"brand_name": "芬必得布洛芬缓释胶囊", "matched_keyword": "布洛芬", "description": "缓解疼痛"}, {"brand_name": "999感冒灵颗粒", "matched_keyword": "感冒灵", "description": null} ] } } ``` ### Q3:存量历史对话 需求说"用户翻看历史对话时,历史回答中的品牌推荐正常展示,不因后台规则变更而丢失"。这意味着品牌推荐结果需要**持久化存储**到 messages 表中,而不是每次渲染时重新匹配。 是否在 `messages` 表加一个 `brand_recommendations` JSONB 字段,保存当时匹配的品牌推荐结果? ### Q4:品牌推荐表创建 确认是否现在创建 `brand_recommend_rules` 表和相关 Entity/Repository/Service,还是先只做方案分析?