基于RAG+SimpleVectorStore的Tool Calling预筛选方案

一、方案核心思路

通过RAG(检索增强生成) 机制,将所有工具的元数据(名称、描述、适用场景、参数等)存入SimpleVectorStore向量库;用户提问后,先将问题转为向量,与向量库中的工具向量做相似度匹配,筛选出TopN最相关的工具,再将这些工具传给大模型做Tool Calling,从而减少上下文占用,提升调用准确性。

二、具体实现步骤
1. 定义工具元数据结构

为每个工具设计结构化的元数据(需包含向量检索所需的核心特征),示例:

@Data
public class ToolMetadata {
    /** 工具唯一标识 */
    private String toolId;
    /** 工具名称(与Spring AI的Function名称一致) */
    private String toolName;
    /** 工具描述(核心:适用场景+功能) */
    private String toolDesc;
    /** 适用问题关键词(如“干部简历”“考核结果”) */
    private String keywords;
    /** 工具参数说明(简化版,避免冗余) */
    private String params;
}

关键toolDesckeywords需简洁且覆盖工具核心能力,作为向量检索的文本依据。

2. 初始化SimpleVectorStore(工具向量库)

将所有工具的元数据转为Document对象(Spring AI的文档模型),存入SimpleVectorStore

(1)配置Embedding模型(用于工具元数据向量化)

沿用之前的Embedding配置(如阿里云text-embedding-v2),确保工具描述和用户问题使用同一Embedding模型:

@Configuration
public class EmbeddingConfig {
    @Bean
    public EmbeddingModel embeddingModel() {
        return new OpenAiEmbeddingModel(
            new OpenAiApi("https://dashscope.aliyuncs.com/compatible-mode/v1", "sk-xxx"),
            OpenAiEmbeddingModel.Options.builder().withModel("text-embedding-v2").build()
        );
    }

    @Bean
    public SimpleVectorStore toolVectorStore(EmbeddingModel embeddingModel) {
        return new SimpleVectorStore(embeddingModel);
    }
}
(2)加载工具元数据到向量库

编写初始化类,将所有工具的元数据转为Document并入库(可通过配置文件/数据库加载工具列表):

@Component
@Slf4j
public class ToolVectorStoreInitializer implements CommandLineRunner {

    @Autowired
    private SimpleVectorStore toolVectorStore;
    @Autowired
    private EmbeddingModel embeddingModel;

    // 模拟工具列表(实际可从配置/数据库读取)
    private List<ToolMetadata> getToolList() {
        return Arrays.asList(
            ToolMetadata.builder()
                .toolId("1")
                .toolName("getCadreResume")
                .toolDesc("查询干部的基本简历信息,包括学历、工作经历、任职情况")
                .keywords("干部简历、基本信息、学历、工作经历")
                .params("cadreId:干部ID(必填)")
                .build(),
            ToolMetadata.builder()
                .toolId("2")
                .toolName("getCadreAssessment")
                .toolDesc("查询干部的年度考核结果、民主测评情况")
                .keywords("干部考核、测评结果、年度考核")
                .params("cadreId:干部ID(必填),year:考核年份(选填)")
                .build()
            // 更多工具...
        );
    }

    @Override
    public void run(String... args) throws Exception {
        List<ToolMetadata> toolList = getToolList();
        List<Document> toolDocs = toolList.stream().map(tool -> {
            // 将工具元数据转为Document(content为检索文本,metadata存工具属性)
            String content = tool.getToolDesc() + " " + tool.getKeywords();
            Map<String, Object> metadata = new HashMap<>();
            metadata.put("toolId", tool.getToolId());
            metadata.put("toolName", tool.getToolName());
            metadata.put("params", tool.getParams());
            return new Document(content, metadata);
        }).collect(Collectors.toList());

        // 存入SimpleVectorStore
        toolVectorStore.add(toolDocs);
        log.info("工具向量库初始化完成,共加载{}个工具", toolList.size());
    }
}
3. 实现工具筛选服务

编写服务类,接收用户问题,检索向量库并返回最相关的工具列表:

@Service
public class ToolRetrievalService {

    @Autowired
    private SimpleVectorStore toolVectorStore;

    /**
     * 根据用户问题筛选TopN相关工具
     * @param userQuestion 用户问题
     * @param topN 返回数量(如Top3)
     * @return 筛选后的工具元数据列表
     */
    public List<ToolMetadata> retrieveRelevantTools(String userQuestion, int topN) {
        // 构建检索请求(设置相似度阈值,过滤低相关工具)
        SearchRequest request = SearchRequest.query(userQuestion)
            .withTopK(topN)
            .withSimilarityThreshold(0.7f); // 相似度≥0.7才保留

        // 从向量库检索相关Document
        List<Document> retrievedDocs = toolVectorStore.similaritySearch(request);

        // 转换为ToolMetadata返回
        return retrievedDocs.stream().map(doc -> {
            Map<String, Object> metadata = doc.getMetadata();
            return ToolMetadata.builder()
                .toolId(metadata.get("toolId").toString())
                .toolName(metadata.get("toolName").toString())
                .toolDesc(doc.getContent())
                .params(metadata.get("params").toString())
                .build();
        }).collect(Collectors.toList());
    }
}
4. 集成Tool Calling流程

在对话服务中,先调用工具筛选服务,再将筛选后的工具传给大模型做Tool Calling:

(1)定义Spring AI的Function工具(与元数据中的工具对应)
@Component
public class CadreTools {

    @Tool("getCadreResume")
    @Description("查询干部的基本简历信息")
    public String getCadreResume(@Parameter(description = "干部ID") String cadreId) {
        // 实际业务逻辑:调用接口/数据库查询简历
        return "干部ID:" + cadreId + ",简历信息:本科,10年工作经验,现任XX部门主任";
    }

    @Tool("getCadreAssessment")
    @Description("查询干部的年度考核结果")
    public String getCadreAssessment(
        @Parameter(description = "干部ID") String cadreId,
        @Parameter(description = "考核年份") String year) {
        // 实际业务逻辑:查询考核结果
        return "干部ID:" + cadreId + "," + year + "年考核结果:优秀";
    }

    // 更多工具方法...
}
(2)对话服务中集成筛选+Tool Calling
@Service
public class CadreChatService {

    @Autowired
    private OpenAiChatModel chatModel;
    @Autowired
    private ToolRetrievalService toolRetrievalService;
    @Autowired
    private CadreTools cadreTools;

    public String chat(String userQuestion) {
        // 步骤1:检索最相关的Top3工具
        List<ToolMetadata> relevantTools = toolRetrievalService.retrieveRelevantTools(userQuestion, 3);

        // 步骤2:将筛选后的工具转为Spring AI的ToolSpec(仅传递相关工具)
        List<ToolSpec> toolSpecs = relevantTools.stream()
            .map(tool -> {
                // 根据toolName匹配CadreTools中的方法(可通过反射/映射实现)
                Method toolMethod = Arrays.stream(CadreTools.class.getMethods())
                    .filter(m -> m.getAnnotation(Tool.class).value().equals(tool.getToolName()))
                    .findFirst().orElse(null);
                return toolMethod != null ? ToolSpec.builder(toolMethod, cadreTools).build() : null;
            })
            .filter(Objects::nonNull)
            .collect(Collectors.toList());

        // 步骤3:构建对话请求,仅传入筛选后的工具
        ChatResponse response = chatModel.call(
            new ChatRequest(
                PromptTemplate.create(userQuestion).createMessage(),
                ToolCallRequest.builder().toolSpecs(toolSpecs).build()
            )
        );

        return response.getResult().getOutput().getContent();
    }
}
三、优化与注意事项
  1. 相似度阈值调优:根据实际测试调整similarityThreshold(如0.6~0.8),平衡召回率和精准度;
  2. 工具元数据优化toolDesc需避免冗余,聚焦“问题场景+功能”(如“查询干部简历”而非“该工具用于通过干部ID查询其学历、工作经历等简历信息”);
  3. 缓存机制:对高频问题的工具筛选结果缓存(如Redis),减少重复向量检索;
  4. 动态更新工具库:若工具新增/修改,需同步更新SimpleVectorStore(可通过定时任务/事件触发);
  5. 多轮对话适配:若多轮对话中工具调用需上下文,可将历史筛选结果纳入检索参考。
四、方案优势
  • 减少上下文占用:仅传递用户问题相关的工具,避免所有工具描述挤占token;
  • 提升调用准确性:通过向量匹配筛选最相关工具,降低大模型选错工具的概率;
  • 轻量易实现:基于SimpleVectorStore无需额外依赖,适合中小规模工具集(若工具超100个,可替换为Milvus/PGVector)。
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐