用 SpringAIAlibab 让高频问题实现毫秒级响应

在上一篇文章《用 Spring AI Alibaba 打造智能查询增强引擎》中,我们实现了查询增强 Hook,让 RAG

系统能听懂"弦外之音"。但增强后的查询依然要走完整的检索链路:向量召回、重排序、LLM

生成......如果用户反复问同一个问题,这套流程每次都要重跑,不仅慢,还烧钱。

今天我们来实现语义缓存(Semantic Cache),让高频问题实现毫秒级响应。

传统 Redis 缓存用的是精确匹配

  • 用户问:"iPhone 15 Pro Max 多少钱?"
  • 缓存 Key:iPhone 15 Pro Max 多少钱?
  • 用户问:"iPhone 15 Pro Max 价格是多少?" → 缓存 miss,明明意思一样

传统缓存无法理解语义相似性

语义缓存的核心思想:

  1. 将用户查询向量化
  2. 在向量数据库中检索相似的历史查询
  3. 如果相似度超过阈值,直接返回历史答案

效果对比:

  • 精确缓存:命中率低,只能覆盖完全相同的问法
  • 语义缓存:命中率高,能覆盖"价格""多少钱""售价""报价"等所有同义表达

在 Spring AI Alibaba 的 Agent 框架中,我们在 BEFORE_MODEL 位置插入语义缓存 Hook:

  • BEFORE_MODEL 在模型调用之前执行
  • 如果命中缓存,可以通过 JumpTo.end 跳过后续所有节点(包括 RAG、LLM 生成)
  • 性能提升最明显

首先定义缓存服务:

java 复制代码
/**
 * 语义缓 Service
 */
@Service
public class SemanticCacheService {

    @Autowired
    @Qualifier("redisVectorStore")
    private VectorStore redisVectorStore;

    @Autowired
    private JedisPooled jedisPooled;

    // 阈值:相似度
    private static final double SIMILARITY_THRESHOLD = 0.90;

    // 缓存有效期
    private static final long CACHE_TTL_SECONDS = 60 * 10;

    /**
     * 尝试命中缓存
     * @return 命中则返回答案,未命中返回 null
     */
    public String getIfPresent(String userQuestion) {

        // 1. 搜索最相似的 1 个问题
        List<Document> results = redisVectorStore.similaritySearch(
                SearchRequest.builder().similarityThreshold(SIMILARITY_THRESHOLD)
                        .topK(1)
                        .query(userQuestion)
                        .build()
        );
        if (results.isEmpty()) {
            return null;
        }

        Document doc = results.getFirst();
        return doc.getMetadata().get("answer").toString();

    }

    /**
     * 存入缓存
     */
    public void put(String userQuestion, String llmAnswer) {
        // 过滤无效回答 (可选)
        if (llmAnswer == null || llmAnswer.trim().isBlank()) {
            return;
        }

        String docId = UUID.randomUUID().toString();
        String redisKey = "embedding:" + docId;

        Document doc = new Document(
                docId,
                userQuestion,
                Map.of("answer", llmAnswer) 
        );

        // 1. 写入向量索引
        redisVectorStore.add(List.of(doc));

        // 2. 设置过期时间 (关键步骤)
        try {
            jedisPooled.expire(redisKey, CACHE_TTL_SECONDS);
        } catch (Exception e) {
            System.err.println("⚠️ 设置缓存 TTL 失败: " + e.getMessage());
        }
    }
}

下面是 Hook 的核心代码,这里存在两个bug:

java 复制代码
/**
 * 语义缓存Hook
 **/
@Slf4j
@RequiredArgsConstructor
@HookPositions({HookPosition.BEFORE_MODEL})
public class SemanticCacheHook extends MessagesModelHook {

    private final SemanticCacheService semanticCacheService;

    public static final String CACHE_HIT_KEY = "cache_hit";

    @Override
    public String getName() {
        return "semantic_cache_check";
    }

    @Override
    public List<JumpTo> canJumpTo() {
        return List.of(JumpTo.end);
    }

    @Override
    public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
        
        previousMessages = previousMessages.stream().distinct().toList();
        String queryToSearch = previousMessages.stream()
                .filter(msg -> msg instanceof UserMessage)
                .map(msg -> ((UserMessage) msg).getText())
                .reduce((first, second) -> second)
                .orElse("");

        if (queryToSearch.isBlank()) {
            return new AgentCommand(previousMessages); // 无查询,继续流程
        }

        // 执行向量搜索 (Redis)
        String cache = semanticCacheService.getIfPresent(queryToSearch);
        if (cache != null && !cache.isBlank()) {
            // 元数据中增加内容结果,并结束流程
            config.metadata().ifPresent(meta -> meta.put(CACHE_HIT_KEY, cache));
            return new AgentCommand(JumpTo.end, previousMessages);
        }

        return new AgentCommand(null, previousMessages);

    }
}

缓存命中后直接返回,但缓存未命中时,需要在正常流程结束后写入缓存。参考前文介绍。

效果对比:

场景 无缓存 精确缓存 语义缓存
"iPhone 15 多少钱?" 全流程 命中 命中
"iPhone 15 价格是多少?" 全流程 miss 命中 ✅
"15 Pro Max 售价" 全流程 miss 命中 ✅
响应时间 2-5 秒 < 100ms < 100ms
Token 成本 每次都有 仅首次 仅首次

语义缓存命中检测的阈值建议在 0.85~0.92 之间微调。

语义缓存是 RAG 系统性能优化的关键一环。通过 Spring AI Alibaba 的 MessagesModelHook,我们优雅地在模型调用前插入了缓存检查逻辑,实现了相似问题命中缓存时跳过整个 RAG 链路的效果,这大幅减少了 LLM 调用次数。

下一篇文章介绍Token自适应压缩。

相关推荐
小王师傅6611 小时前
英语学习记
java·学习
Ender(弹射回家版)11 小时前
超越聊天机器人:GVIM 2.0 如何将人工智能转变为真正的科学实验室合作伙伴
人工智能
罗小罗同学11 小时前
安德森癌症中心发表SCOPE IO模型,实现了对罕见肿瘤帕博利珠单抗疗效的精准预测,在患者早期即可预判药效
人工智能·算法·医学图像处理·医学ai
wuhanzhanhui11 小时前
从精密齿轮到智慧流体:2026武汉动力传动展览会,重构未来工业动力
大数据·人工智能
万事可爱^11 小时前
Claude 新发布的 Opus 5,系统提示语删了 80%,半价还能逼近 Fable 5
android·服务器·数据库·人工智能·claude
民乐团扒谱机11 小时前
【微科普】一文搞懂AI对话平台公式复制差异:DeepSeek一键粘贴PPT可编辑,豆包复制仅得到LaTeX源码底层原理
人工智能·ai·powerpoint·ai写作·deepseek
cooldream200911 小时前
AI 时代,Git 应该怎么学
人工智能·git
小保CPP11 小时前
OpenCV C++计算多边形的最大内切圆
c++·人工智能·opencv·计算机视觉
像风一样自由202011 小时前
2.时序异常检测入门到实战:评估的陷阱:point-adjust 如何把烂模型刷成 SOTA
人工智能·时序异常检测·时间序列模型
第七页独白12 小时前
AI 赋能 APQP 项目管控!全星研发项目管理系统为制造企业创造多重核心价值
人工智能