java 实现pgvector+bge 文本相似筛查+重排

简述

实现一个高精度、低延迟的文本相似性检索系统,包含两个阶段:

  1. 召回(Recall):使用向量数据库 pgvector 快速筛选出 top-k 相似文档。
  2. 重排(Rerank):使用 BGE 模型对召回结果进行语义重排序,提升最终结果的相关性。
为什么选择 Java 而非 Python?

系统集成

与 Spring Boot、微服务、企业级系统无缝集成,无需跨语言调用。

部署运维

单 JAR 包部署,无 Python 环境依赖(如 conda、pip、CUDA 配置)。

性能与内存

JVM GC 优化成熟,适合长时间运行服务;模型可缓存复用。

工程化

更强的类型安全、模块化、依赖管理(Maven/Gradle),适合团队协作。

生产稳定性

在金融、电商等场景广泛使用,故障率低,监控体系完善。

整体架构

+------------------+     +-------------------+     +------------------+
|   用户查询 Query   | --> |  EmbeddingService | --> | pgvector 查询 Top-K |
+------------------+     +-------------------+     +------------------+
                                                      |
                                                      v
                                              +------------------+
                                              | RerankerService  | --> 重排序结果
                                              +------------------+

在构建基于向量搜索的文本相似度系统时,pgvector 作为 PostgreSQL 的开源扩展,提供高效的向量存储和相似度计算能力(如 L2 距离、内积或余弦相似度),适用于大规模数据检索。BGE(BAAI General Embedding)模型是一种高性能的嵌入生成模型,支持多语言文本向量化,常用于语义搜索和推荐场景。传统实现中,BGE 模型通常依赖 Python 的 Hugging Face Transformers 库加载,但本分析聚焦于纯 Java 环境实现,避免 Python 依赖。具体目标包括:使用 BGE 生成文本嵌入,进行初始相似度筛选(retrieval),并通过重排(reranking)优化结果排序,以提升检索精度。

挑战在于 BGE 是基于 Transformer 的模型,原生不支持 Java,但可以通过 ONNX(Open Neural Network Exchange)格式转换并利用 ONNX Runtime 的 Java 绑定实现本地推理。这确保了跨平台兼容性和性能优化,同时结合 JDBC 或 Spring AI 与 pgvector 集成,实现端到端流程。

纯 Java 实现 pgvector + BGE 的相似筛选与重排完全可行,通过 ONNX 桥接 ML 模型和 JDBC/Spring AI 集成数据库,适用于企业级应用,避免 Python 依赖带来的部署复杂性

向量存储与相似度筛选:集成 pgvector

pgvector 扩展 PostgreSQL,支持向量类型(vector)和索引(如 HNSW 用于近似最近邻搜索)。在 Java 中,通过 JDBC 或 Spring AI 抽象层操作数据库,实现嵌入插入和相似度查询。

筛选阶段使用 HNSW 索引实现高效近似搜索,适用于海量数据(百万级向量),召回率 >95% 时延迟 <50ms。

重排机制:优化检索结果

初始筛选(retrieval)后,重排通过更精细的相似度计算(如跨编码器模型)对 top-K 结果重新排序,提升精度。BGE 本身支持 reranking,但需额外模型或算法。

实现

创建表结构
CREATE TABLE triage_feedback (
                                 id SERIAL PRIMARY KEY,
                                 title TEXT NOT NULL,
                                 description TEXT,
                                 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

select * from triage_feedback

CREATE TABLE embedding (
                           id SERIAL PRIMARY KEY,
                           triage_feedback_id INTEGER NOT NULL,
                           chunk_text TEXT NOT NULL,                    -- 原始文本片段
                           embedding_vector VECTOR(1536),               -- 假设使用 OpenAI 的 text-embedding-ada-002 (1536维)
                           search_vector TSVECTOR,                      -- 用于中文全文搜索
                           created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    -- 外键约束
                           CONSTRAINT fk_triage_feedback
                               FOREIGN KEY (triage_feedback_id)
                                   REFERENCES triage_feedback(id)
                                   ON DELETE CASCADE
);

-- 创建索引以提高搜索性能
CREATE INDEX idx_embedding_vector ON embedding USING IVFFLAT (embedding_vector vector_cosine_ops);
CREATE INDEX idx_embedding_search_vector ON embedding USING GIN (search_vector);
相关的jpa代码
Embedding.java
package com.triage.mspbotai.entity;

import jakarta.persistence.*;
import java.time.ZonedDateTime;

import lombok.*;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.Formula;

@Entity
@Table(name = "embedding")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Embedding {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "triage_feedback_id", nullable = false)
    private Integer triageFeedbackId;

    @Column(name = "chunk_text", nullable = false)
    private String chunkText;

    @Column(name = "embedding_vector", columnDefinition = "vector")
    @ColumnTransformer(
        read = "embedding_vector::float4[]",
        write = "?::vector"
    )
    private float[] embeddingVector;

    @Formula("to_tsvector('english', coalesce(content, ''))")
    private String searchVector;

    @Column(name = "created_at")
    private ZonedDateTime createdAt;

    @Column(name = "content")
    private String content;

}
TriageFeedback.java
package com.triage.mspbotai.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import jakarta.persistence.*;
import java.time.ZonedDateTime;

@Entity
@Table(name = "triage_feedback")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class TriageFeedback {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @Column(nullable = false)
    private String title;
    
    private String description;
    
    @Column(name = "created_at")
    private ZonedDateTime createdAt;

}
TriageFeedbackRepository.java
package com.triage.mspbotai.repository;

import com.triage.mspbotai.entity.TriageFeedback;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface TriageFeedbackRepository extends JpaRepository<TriageFeedback, Integer> {

    @Query(value = """
    SELECT id, triage_feedback_id, chunk_text, created_at, content,
               1 - (embedding_vector <=> CAST(?1 AS vector)) AS score
        FROM embedding
        ORDER BY embedding_vector <=> CAST(?1 AS vector)
        LIMIT ?2
    """, nativeQuery = true)
    List<Object[]> findSimilarEmbeddings(float[] embedding, int limit);
}

因为jpa不支持直接操作tsvector 结构,我们需要使用触发器来修改这个字段

-- 创建触发器函数
CREATE OR REPLACE FUNCTION update_search_vector()
RETURNS TRIGGER AS $$
BEGIN
    NEW.search_vector := to_tsvector('english', COALESCE(NEW.content, ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;



-- 创建触发器
CREATE TRIGGER tsvector_update
    BEFORE INSERT OR UPDATE ON embedding
    FOR EACH ROW EXECUTE FUNCTION update_search_vector();

-- 创建 GIN 索引(提升查询性能)
CREATE INDEX idx_search_vector ON embedding USING GIN(search_vector);
bge 模型调用代码

pom.xml 使用依赖

<!-- DJL for BGE models -->
		<dependency>
			<groupId>ai.djl</groupId>
			<artifactId>api</artifactId>
			<version>0.27.0</version>
		</dependency>
		<dependency>
			<groupId>ai.djl.pytorch</groupId>
			<artifactId>pytorch-engine</artifactId>
			<version>0.27.0</version>
		</dependency>

		<dependency>
			<groupId>ai.djl.onnxruntime</groupId>
			<artifactId>onnxruntime-engine</artifactId>
			<version>0.25.0</version> <!-- 请使用最新版本 -->
		</dependency>

		<dependency>
			<groupId>ai.djl.huggingface</groupId>
			<artifactId>tokenizers</artifactId>
			<version>0.25.0</version> <!-- 与 DJL 版本匹配 -->
		</dependency>

EmbeddingService.java

package com.triage.mspbotai.service;

import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer;
import ai.djl.inference.Predictor;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDList;
import ai.djl.ndarray.NDManager;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.translate.TranslateException;
import ai.djl.translate.Translator;
import ai.djl.translate.TranslatorContext;
import com.triage.mspbotai.entity.TriageFeedback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@Service
public class EmbeddingService {

    private static final Logger logger = LoggerFactory.getLogger(EmbeddingService.class);
    private Predictor<String, float[]> predictor;

    public EmbeddingService() {
        logger.info("EmbeddingService constructor called");
    }

    @PostConstruct
    public void init() {
        try {
            Path tokenizerPath = Paths.get("/opt/maxkb/model/bge-large-en-v1.5");
            Path modelPath = Paths.get("/opt/maxkb/model/bge-large-en-v1.5/onnx/model.onnx");
            if (!Files.exists(tokenizerPath) || !Files.exists(modelPath)) {
                throw new IOException("Model or tokenizer path does not exist: " + tokenizerPath + " or " + modelPath);
            }

            HuggingFaceTokenizer tokenizer = HuggingFaceTokenizer.builder()
                    .optTokenizerPath(tokenizerPath)
                    .optPadding(true)
                    .optTruncation(true)
                    .optMaxLength(4096)
                    .build();

            Criteria<String, float[]> criteria = Criteria.builder()
                    .setTypes(String.class, float[].class)
                    .optEngine("OnnxRuntime")
                    .optModelPath(modelPath)
                    .optTranslator(new BgeEmbeddingTranslator(tokenizer))
                    .build();

            ZooModel<String, float[]> model = criteria.loadModel();
            this.predictor = model.newPredictor();
            logger.info("EmbeddingService initialized successfully");
        } catch (Exception e) {
            logger.error("Failed to initialize EmbeddingService", e);
            throw new RuntimeException("EmbeddingService initialization failed", e);
        }
    }

    public float[] generateEmbedding(String text) {
        
        try {
            return predictor.predict(text);
        } catch (TranslateException e) {
            logger.error("Failed to generate embedding for text: {}", text, e);
            throw new RuntimeException("Embedding generation failed", e);
        }
    }

    public float[] generateEmbedding(TriageFeedback feedback) throws Exception {
        String combined = feedback.getTitle() + " " + (feedback.getDescription() != null ? feedback.getDescription() : "");
        return generateEmbedding(combined);
    }

    private static class BgeEmbeddingTranslator implements Translator<String, float[]> {
        private final HuggingFaceTokenizer tokenizer;
        private final boolean normalize;

        public BgeEmbeddingTranslator(HuggingFaceTokenizer tokenizer) {
            this.tokenizer = tokenizer;
            this.normalize = true;
        }

        @Override
        public NDList processInput(TranslatorContext ctx, String input) throws Exception {
            // Tokenize the input text
            var encoding = tokenizer.encode(input);
            NDManager manager = ctx.getNDManager();

            // Create input tensors with shape [1, sequence_length]
            long[] inputIdsArray = encoding.getIds();
            long[] attentionMaskArray = encoding.getAttentionMask();
            long[] tokenTypeIdsArray = encoding.getTypeIds();

            // Convert to NDArray with shape [sequence_length]
            NDArray inputIds = manager.create(inputIdsArray);
            NDArray attentionMask = manager.create(attentionMaskArray);
            NDArray tokenTypeIds = manager.create(tokenTypeIdsArray);


            return new NDList(inputIds, attentionMask, tokenTypeIds);
        }

        @Override
        public float[] processOutput(TranslatorContext ctx, NDList output) throws Exception {
            NDArray lastHiddenState = output.get(0);

            // Step 1: 确保是 [seq_len, hidden_size] 或 [1, seq_len, hidden_size]
            if (lastHiddenState.getShape().dimension() == 3) {
                // 如果是 [1, seq_len, hidden_size],去掉 batch 维度 -> [seq_len, hidden_size]
                if (lastHiddenState.size(0) == 1) {
                    lastHiddenState = lastHiddenState.squeeze(0); // shape: [seq_len, hidden_size]
                }
            }
            // 现在 shape 应该是 [seq_len, hidden_size]

            long seqLen = lastHiddenState.size(0);
            long hiddenSize = lastHiddenState.size(1);

            logger.debug("last_hidden_state shape: [{} x {}]", seqLen, hiddenSize);

            // Step 2: Mean Pooling over sequence length (axis=0)
            NDArray sentenceEmbedding = lastHiddenState.mean(new int[]{0}); // shape: [hidden_size]

            // Step 3: L2 Normalization (BGE 模型要求)
            if (normalize) {
                sentenceEmbedding = sentenceEmbedding.div(sentenceEmbedding.norm());
            }

            // Step 4: 转为 float[],长度 = hidden_size (如 1024)
            return sentenceEmbedding.toFloatArray();
        }
    }
}
RerankerService.java
package com.triage.mspbotai.service;

import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer;
import ai.djl.inference.Predictor;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDList;
import ai.djl.ndarray.NDManager;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.translate.TranslateException;
import ai.djl.translate.Translator;
import ai.djl.translate.TranslatorContext;
import com.triage.mspbotai.entity.TriageFeedback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class RerankerService {

    private static final Logger logger = LoggerFactory.getLogger(RerankerService.class);

    private Predictor<RerankerInput, Float> predictor;
    private HuggingFaceTokenizer tokenizer;

    @PostConstruct
    public void init() {
        try {
            Path modelPath = Paths.get("/opt/maxkb/model/bge-reranker-large/onnx/model.onnx");
            Path tokenizerPath = Paths.get("/opt/maxkb/model/bge-reranker-large");

            // 1. 检查模型和 tokenizer 路径是否存在
            if (!Files.exists(modelPath)) {
                throw new IOException("Reranker model path does not exist: " + modelPath);
            }
            if (!Files.exists(tokenizerPath)) {
                throw new IOException("Tokenizer path does not exist: " + tokenizerPath);
            }

            // 2. 显式加载 HuggingFace Tokenizer
            this.tokenizer = HuggingFaceTokenizer.builder()
                    .optTokenizerPath(tokenizerPath)
                    .optPadding(true)
                    .optTruncation(true)
                    .optMaxLength(512)
                    .build();

            // 3. 构建 Criteria,使用自定义 Translator
            Criteria<RerankerInput, Float> criteria = Criteria.builder()
                    .setTypes(RerankerInput.class, Float.class)
                    .optEngine("OnnxRuntime")
                    .optModelPath(modelPath)
                    .optTranslator(new RerankerTranslator(tokenizer))
                    .build();

            // 4. 加载模型
            ZooModel<RerankerInput, Float> model = criteria.loadModel();
            this.predictor = model.newPredictor();

            logger.info("Reranker service initialized successfully with model: {}", modelPath);

        } catch (Exception e) {
            logger.error("Failed to initialize Reranker service", e);
            throw new RuntimeException("Reranker initialization failed", e);
        }
    }

    public List<ScoredFeedback> rerank(String query, List<TriageFeedback> candidates) throws TranslateException {
        return candidates.stream()
                .map(cand -> {
                    try {
                        String doc = cand.getTitle() + " " + (cand.getDescription() != null ? cand.getDescription() : "");
                        Float score = predictor.predict(new RerankerInput(query, doc));
                        return new ScoredFeedback(cand, score);
                    } catch (Exception e) {
                        return new ScoredFeedback(cand, 0.0f);
                    }
                })
                .sorted((a, b) -> Float.compare(b.score, a.score))
                .limit(5)
                .collect(Collectors.toList());
    }

    public static class RerankerInput {
        private String query;
        private String document;

        public RerankerInput(String query, String document) {
            this.query = query;
            this.document = document;
        }

        public String getQuery() {
            return query;
        }

        public String getDocument() {
            return document;
        }
    }

    public static class ScoredFeedback {
        private TriageFeedback feedback;
        private float score;

        public ScoredFeedback(TriageFeedback feedback, float score) {
            this.feedback = feedback;
            this.score = score;
        }

        public TriageFeedback getFeedback() {
            return feedback;
        }

        public float getScore() {
            return score;
        }
    }

    private static class RerankerTranslator implements Translator<RerankerInput, Float> {
        private final HuggingFaceTokenizer tokenizer;

        public RerankerTranslator(HuggingFaceTokenizer tokenizer) {
            this.tokenizer = tokenizer;
        }

        @Override
        public NDList processInput(TranslatorContext ctx, RerankerInput input) throws Exception {
            // Combine query and document for tokenization
            String text = input.getQuery() + " " + input.getDocument();

            // Tokenize the input text
            var encoding = tokenizer.encode(text);

            NDManager manager = ctx.getNDManager();

            // Convert tokens to NDArray
            long[] inputIdsArray = encoding.getIds();
            NDArray inputIds = manager.create(inputIdsArray);
            inputIds.setName("input_ids");

            // Create attention mask
            long[] attentionMaskArray = encoding.getAttentionMask();
            NDArray attentionMask = manager.create(attentionMaskArray);
            attentionMask.setName("attention_mask");

            // Return NDList with input_ids and attention_mask
            return new NDList(inputIds, attentionMask);
        }

        @Override
        public Float processOutput(TranslatorContext ctx, NDList output) throws Exception {
            // The bge-reranker-large model typically outputs a single score
            NDArray scoreArray = output.get(0);
            // Apply sigmoid to convert logits to probability (0-1 range)
            float score = (float) (1.0 / (1.0 + Math.exp(-scoreArray.getFloat())));
            return score;
        }
    }
}

TriagePipelineService.java
package com.triage.mspbotai.service;

import com.huaban.analysis.jieba.JiebaSegmenter;
import com.huaban.analysis.jieba.SegToken;
import com.triage.mspbotai.entity.Embedding;
import com.triage.mspbotai.entity.TriageFeedback;
import com.triage.mspbotai.repository.EmbeddingRepository;
import com.triage.mspbotai.repository.TriageFeedbackRepository;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class TriagePipelineService {

    @Autowired
    private EmbeddingService embeddingService;

    @Autowired
    private TriageFeedbackRepository feedbackRepository;

    @Autowired
    private EmbeddingRepository embeddingRepository;

    @Autowired
    private RerankerService rerankerService;

    @Transactional
    public void insertFeedbackTriageTicket(String title, String description) {
        // Step 1: Create and save new feedback
        TriageFeedback newFeedback = new TriageFeedback();
        newFeedback.setTitle(title);
        newFeedback.setDescription(description);
        newFeedback.setCreatedAt(ZonedDateTime.now());
        newFeedback = feedbackRepository.save(newFeedback);

        // Step 2: Generate and save embedding
        String combined = newFeedback.getTitle() + " " + (newFeedback.getDescription() != null ? newFeedback.getDescription() : "");
        float[] embedding = embeddingService.generateEmbedding(combined);
        Embedding embeddingEntity = new Embedding();
        embeddingEntity.setTriageFeedbackId(newFeedback.getId());
        embeddingEntity.setChunkText(title + " " + (description != null ? description : ""));
        embeddingEntity.setEmbeddingVector(embedding);
        embeddingEntity.setCreatedAt(ZonedDateTime.now());

        // Step 3: Generate search vector
        embeddingEntity.setContent(combined);
        embeddingRepository.save(embeddingEntity);


    }


    @Transactional
    public List<RerankerService.ScoredFeedback> search(String title, String description) throws Exception {
        TriageFeedback newFeedback = new TriageFeedback();
        newFeedback.setTitle(title);
        newFeedback.setDescription(description);
        float[] embedding = embeddingService.generateEmbedding(newFeedback);

        // Step 3: Vector search for top-20 similar feedbacks
        List<Object[]> similarFeedbacks = feedbackRepository.findSimilarEmbeddings(embedding, 20);
        // id, triage_feedback_id, chunk_text, created_at, content, embedding_vector
        List<TriageFeedback> candidates = similarFeedbacks.stream().map(result -> {
            TriageFeedback feedback = new TriageFeedback();
            feedback.setId((Integer) result[1]);
            feedback.setTitle(StringUtils.substring((String) result[2], 0,20));
            feedback.setDescription((String) result[2]);
            return feedback;
        }).collect(Collectors.toList());

        List<RerankerService.ScoredFeedback> reranked =  new ArrayList<>();
        String query = newFeedback.getTitle() + " " + (newFeedback.getDescription() != null ? newFeedback.getDescription() : "");

        if (!candidates.isEmpty()) {
            // Step 4: Rerank candidates
            reranked = rerankerService.rerank(query, candidates);
        }

        // Step 5: return reranked
        return reranked;
    }

}
需要重点说明下
1. 使用 DJL + ONNX Runtime 而非 PyTorch

为什么重要? 这是整个服务的技术选型核心。

  • 说明:本服务使用 Deep Java Library (DJL) 的 OnnxRuntime 引擎加载 bge-large-en-v1.5 模型的 ONNX 格式。
  • 优势
    • 避免 PyTorch 原生库下载:不依赖庞大的 libtorch.so,避免在生产环境因网络问题下载失败。
    • 轻量、高性能:ONNX Runtime 是专为推理优化的引擎,启动快、内存占用低。
    • 跨平台:无需为不同 OS/架构编译 PyTorch。
  • 模型需提前从 Hugging Face 转换为 ONNX 格式(使用 transformers.onnx 工具)。
HuggingFaceTokenizer 的使用与配置

为什么重要? 分词是生成正确 embedding 的第一步。

  • 说明:使用 DJL 的 HuggingFaceTokenizer 组件进行文本预处理,确保与 BGE 模型训练时的分词方式一致。
  • 关键配置
    • .optPadding(true):自动填充到最大长度,满足 ONNX 模型对固定输入 shape 的要求。
    • .optTruncation(true):超长文本自动截断。
    • .optMaxLength(512):BGE 模型推荐的最大序列长度,必须与 ONNX 模型导出时的配置一致
processInput 中的张量维度处理(关键!)

为什么重要? 这是模型推理能否成功的关键。

  • 说明:ONNX 模型期望输入为 [batch_size, sequence_length] 的 2D 张量。
processOutput 中的 Mean Pooling 与 L2 归一化

为什么重要? 这是生成可用 sentence embedding 的后处理步骤。

  • 说明
    • Mean Pooling:BGE 模型输出 last_hidden_state[seq_len, hidden_size] 的矩阵。通过对 sequence_length 维度(axis=0)求平均,得到 [hidden_size] 的句子向量。
    • L2 Normalization:BGE 模型要求输出向量为单位向量(模长为1),这样才能使用 cosine 相似度进行高效检索。sentenceEmbedding.div(sentenceEmbedding.norm()) 实现了这一点。

最后写一点spring托管相比python的优点

predictor 的线程安全性
  • 说明:DJL 的 Predictor 是线程安全的。
  • 需说明EmbeddingService 是一个单例(@Service),其 predictor 成员变量可以被多个 HTTP 请求线程并发调用,无需额外的同步机制。这使得服务可以高效处理高并发的 embedding 请求。

测试

测试feedback 向量化保存
curl --location 'http://127.0.0.1:9000/postFeedback' \
--header 'Content-Type: application/json' \
--data-raw '[
    {
        "title": "My problem isn'\''t listed or I have feedback",
        "description": "If you couldn'\''t find your problem listed under a category or you would like to submit feedback for the support portal\r\n\r\nDetails\r\n-----------------------------------------\r\nRequesting an IT Service: Service Requests are for things that aren'\''t broken but IT need to help with, like changing mailbox access or setting up a computer. Request an IT Service here\r\nWould you like to provide feedback or report a different problem?: Different problem\r\nBriefly describe the problem that is occurring: Requesting admin access to be able to install triconvey \r\n",
        "billing_codes": "Remote Urgent Support"
    },
    {
        "title": "User offboarding",
        "description": "Immediate offboarding of \nPeter Garrone",
        "billing_codes": "Remote Urgent Support"
    },
    ...
]'

测试命中
curl --location 'http://127.0.0.1:9000/test_hit' \
--header 'Content-Type: application/json' \
--data '{
    "title": "New Support Ticket R",
    "description": "User offboarding Immediate offboarding of \nPeter Garrone"
}'

返回中score是重拍的得分

{
            "feedback": {
                "id": 94,
                "title": "User offboarding Imm",
                "description": "User offboarding Immediate offboarding of \nPeter Garrone",
                "createdAt": null
            },
            "score": 0.43528542
        },

总结:用到的技术

DJL + ONNX Runtime 选型

解释为何不使用 Python/PyTorch,突出 Java 部署优势。

HuggingFaceTokenizer 配置

确保分词行为与模型训练一致,是准确性的基础。

processInput 的 2D 张量创建

关键修复点,否则模型无法运行。

Mean Pooling + L2 Normalize

解释如何从模型输出得到最终的 sentence embedding。

模型路径与部署

明确运维要求,确保环境一致性。

@PostConstruct 失败策略

说明“宁可不启动,也不返回错误结果”的设计哲学。

predictor 线程安全

证明服务可以支持高并发,是性能保障。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CHEN_RUI_2200

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值