brand-recommend-analysis.md 5.3 KB

# 品牌药品推荐 - 后端接口分析与方案(已完成实施)

状态:已实施 (2026-07-29) 详见 HANDOVER.md 第十六章节。

一、需求理解

核心流程

用户提问 → RAG检索 → LLM生成回答(SSE流式) → 从sources中提取药名 → 
关键词匹配(异步) → 返回匹配的品牌推荐 → 前端展示在回答末尾

举例

  • AI回答中"参考原文"区域出现「布洛芬缓释胶囊」→ 匹配关键词"布洛芬" → 推荐"芬必得布洛芬缓释胶囊"
  • AI回答中「感冒灵颗粒」→ 匹配"感冒灵" → 推荐"999感冒灵颗粒"

二、匹配逻辑分析

匹配对象:buildSources 返回的 sources 列表

当前 chatStream 尾部 tailFlux 中的 sources 结构(来自 buildSources line 720+):

{
  "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

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

@Service
public class BrandRecommendService {
    // 核心方法:根据 sources 列表返回匹配的品牌推荐
    public List<BrandMatchResult> match(List<Map<String, Object>> 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 改造

chatStreamtailFlux 中,LLM 流结束后、meta 事件前,插入品牌推荐:

Flux<ServerSentEvent<String>> tailFlux = Flux.defer(() -> {
    List<Map<String, Object>> sources = buildSources(docs);
    // 新增:品牌推荐匹配
    List<BrandMatchResult> 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 事件数据格式是什么?建议:

{
  "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,还是先只做方案分析?