发布攻略存放及ai向量搜索流程梳理

用向量的模式,实现ai问答
第一步需要先在es库里增加一列向量,所以我们先去es里改es库,

        //建库 建立映射
        CreateIndexRequest createReq = CreateIndexRequest.of(r -> r.index(INDEX_NAME)
                .settings(s -> s.numberOfShards("1").numberOfReplicas("1"))
                .mappings(m -> m.properties("id", p -> p.long_(l -> l))
                        .properties("title", p -> p.text(t -> t.analyzer("ik_max_word")))
                        .properties("subTitle", p -> p.text(t -> t.analyzer("ik_max_word")))
                        .properties("summary", p -> p.text(t -> t.analyzer("ik_max_word")))
                        .properties("embedding", p -> p.denseVector(
                                d -> d.dims(dimensions)
                                        .index(true)
                                        .similarity(DenseVectorSimilarity.Cosine))
                        ))
        );

库里存的是id,title,subtitle,summary四个字段的数据转换为的向量

 //查询mysql
        List<Strategy> list = remoteStrategyService.list("inner").getData();
        for (Strategy strategy : list) {
            StrategyES strategyES = new StrategyES();
            //同名属性拷贝,strategyES其实相当于strategy的子集了,只是比他少俩属性
            BeanUtils.copyProperties(strategy, strategyES);
            //向量化 转换之后,es库里就有向量embedding这个字段
            StringJoiner joiner = new StringJoiner(" ");
            joiner.add(strategyES.getTitle());
            joiner.add(strategyES.getSubTitle());
            joiner.add(strategyES.getSummary());

            List<Float> embeddingResp = embeddingService.getEmbedding(joiner.toString());
            strategyES.setEmbedding(embeddingResp);

            //同步mysql数据到es
            client.index(i -> i.index(INDEX_NAME).id(strategyES.getId().toString()).document(strategyES));
        }

接下来,到ai流程里,首先先把用户请求向量化

 //把用户问题向量化
        List<Float> embedding = embeddingService.getEmbedding(request.getQuestion());

依旧是拼接请求体(要把前端传回来的请求数据叶转换成向量),请求头,把请求转换成向量,组装去es里找相似的,


        //使用向量方式查询es 
        SearchResponse<StrategyES> resp = client.search(sh -> sh.index("strategy")
                .knn(k -> k.field("embedding")
                        .queryVector(embedding)
                        .k(request.getTopK())), StrategyES.class);

        HitsMetadata<StrategyES> hits = resp.hits();
        TotalHits total = hits.total();
        List<Hit<StrategyES>> hits2 = hits.hits();

        List<StrategyES> strategies = new ArrayList<>();
        for (Hit<StrategyES> strategyES : hits2) {
            strategies.add(strategyES.source());
        }

        //发送请求到deepseek处理
        String answer = askAI(request.getQuestion(), strategies);

        ChatResponse chatResponse = new ChatResponse(answer, strategies);

        return chatResponse;

实现ai的向量搜索

  private String askAI(String question, List<StrategyES> strategies) {
        StringJoiner stringJoiner = new StringJoiner("\n\n");
        for (StrategyES strategy : strategies) {
            String jsonString = JSON.toJSONString(strategy);
            stringJoiner.add(jsonString);

        }
        String strategiesStr = stringJoiner.toString();
        //私库数据
        OpenAIMessage ragMessage = new OpenAIMessage("system", strategiesStr);
        //系统消息
        OpenAIMessage sysMessage = new OpenAIMessage("system", "你是一名资深导游,请结合用户问题和私库数据,并根据互联网信息给出旅游建议。规划处路线图,必坑指南及规划条件。回答小于200字");
        //用户消息
        OpenAIMessage userMessage = new OpenAIMessage("user", question);
        List<OpenAIMessage> messages = new ArrayList<>();
        messages.add(ragMessage);
        messages.add(sysMessage);
        messages.add(userMessage);

        OpenAIChatRequest requestBody = new OpenAIChatRequest();
        requestBody.setMessages(messages);
        requestBody.setModel(model);
        requestBody.setTemperature(temperature);
        Map<String, Object> map = new HashMap<>();
        map.put("type", thinking);
        requestBody.setThinking(map);
        requestBody.setTop_p(top_p);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        List<MediaType> mediaTypeList = new ArrayList<>();
        mediaTypeList.add(MediaType.APPLICATION_JSON);

        headers.setAccept(mediaTypeList);
        headers.setBearerAuth(apiKey);


        //等于header和body
        HttpEntity<OpenAIChatRequest> httpEntity = new HttpEntity<>(requestBody, headers);
        String url = baseUrl + "/chat/completions";
        OpenAIChatResponse response = restTemplate.postForObject(url, httpEntity, OpenAIChatResponse.class);

        if (response == null || response.getChoices() == null ||
                response.getChoices().isEmpty()){
            return "AI没有返回合适的结果";
        }
        OpenAIChatResponse.Choice choice = response.getChoices().get(0);
        if(choice==null||choice.getMessage()==null||choice.getMessage().getContent()==null){
            return "AI没有返回合适的结果";
        }
        return choice.getMessage().getContent();

    }

先做了一个es库初始化,从mysql中查数据,把查到的数据转换成向量数据,一起存到es库中,在这里设定按照余弦方式查找.

发布攻略时,canal一直在监控,生成的消息,告诉redis,放到redis里,然后告诉rabbitmq这个中间件,rabbitmq收到消息会直接消费,拿到canal的message数据,转成向量数据,和message一起存到es库里,当es存完数据,在执行把数据从redis里删掉的操作.同时,这里有一个兜底机制(rabbitmq的超时检查),是一个定时任务的周期性运行.根据定时任务调用strategyserverimpl里的方法检查有没有超时,如果超时定时任务会通过远程调用让rabbitmq重发消息.

ai模块,先把用户消息转换成向量化数据,查出es库数据,拼接请求体请求头,比对,找出最相近的向量数据返回给deepseek,deepseek处理完之后,带着返回值返回给前端,梳理流程是否正确,哪里有偏差

┌─────────────────────────────────────────────────────────────────┐
│ 阶段1:初始化(手动触发,一次性) │
│ MySQL查数据 → 转向量 → 存ES → mapping设定余弦查找 │
└─────────────────────────────────────────────────────────────────┘

▼ 发布攻略
┌─────────────────────────────────────────────────────────────────┐
│ 阶段2:实时同步 + 兜底机制 │
│ │
│ ① Canal监控MySQL binlog → 构造JSON message │
│ ② 写Redis ZSet(暂存) → 发RabbitMQ │
│ │
│ ┌─ 正常路径(立即)────────────────────────────────┐ │
│ │ ③ RabbitMQ直接推送 → Listener.receive() │ │
│ │ ④ JSON→Strategy→BeanUtils.copy→StrategyES │ │
│ │ ⑤ getEmbedding(title+subTitle+summary) │ │
│ │ ⑥ setEmbedding → client.index 写ES │ │
│ │ ⑦ 删Redis ZSet对应消息 │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ ┌─ 兜底路径(周期并行)──────────────────────────────┐ │
│ │ A. 定时任务按cron周期触发 │ │
│ │ B. Feign远程调StrategyServiceImpl │ │
│ │ C. 检查Redis ZSet里score<now-60000的消息 │ │
│ │ D. 超时则补发给RabbitMQ重发 │ │
│ │ E. 没超时则跳过 │ │
│ └────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

▼ 用户提问
┌─────────────────────────────────────────────────────────────────┐
│ 阶段3:AI向量检索问答 │
│ ① getEmbedding(question) 用户问题向量化 │
│ ② client.search(knn) 余弦比对,找最相近向量 │
│ ③ 取hits → List
│ ④ 拼OpenAIChatRequest + HttpHeaders │
│ ⑤ restTemplate → deepseek API │
│ ⑥ choices[0].message.content → answer │
│ ⑦ ChatResponse(answer, strategies) → 前端 │
└─────────────────────────────────────────────────────────────────┘

超时兜底机制
Canal → Redis + RabbitMQ

┌─────────────────┴─────────────────┐
│ │
【正常路径】 【兜底路径】
│ │
RabbitMQ立即推送 定时任务周期跑(独立)
│ │
TravelRabbitListener收到 扫Redis ZSet
│ │
立即处理(不用等超时) 找score<now-60s的消息
│ │
写ES → 删Redis 补发RabbitMQ
│ │
正常完成 消息卡住才介入

posted @ 2026-08-25 21:51  Jennifer_F  阅读(13)  评论(2)    收藏  举报