找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1107

积分

0

好友

159

主题
发表于 5 天前 | 查看: 7| 回复: 0

面对上百个API工具时,如何让AI智能体准确选择并调用合适的工具,是一个在构建复杂智能应用时常遇到的挑战。传统的Function Calling方式是将所有工具的schema一次性全部塞进提示词,这在工具数量庞大时不仅会导致Token消耗剧增,还会让大模型陷入“选择困难”,影响决策速度和准确率。

本文将介绍一个基于Spring AI Alibaba Graph实现的解决方案,通过 “向量检索粗筛 + 精确调用” 的两阶段策略,优雅地解决大规模工具调度问题。我们将以Java Math类的100+个静态方法为例,构建一个智能数学助手,并深入解析其核心架构与实现细节。

一、传统方案的困境与破局思路

假设我们需要构建一个支持各种数学运算的智能助手。Java Math类提供了超过100个静态方法,涵盖三角函数、对数、幂运算等。

传统做法

  • 将所有方法的描述(schema)放入LLM的提示词中。
  • 每次用户提问(如“计算8的平方根”),LLM都需要从这100多个选项中理解并选择sqrt()方法。

这带来三个明显问题:

  1. 成本高昂:每次调用都需传输大量工具描述,Token消耗巨大。
  2. 速度缓慢:LLM需要处理和理解所有工具信息才能做出决策。
  3. 准确率下降:选项过多容易导致混淆,例如sqrtcbrtpow都与“开方”相关,模型可能选错。

破局思路:模仿人类解决问题的过程。

  1. 理解问题:先提取“计算平方根”这个核心意图。
  2. 快速筛选:在知识库中快速匹配出与“平方根”相关的几个方法(如sqrtcbrtpow)。
  3. 精确选择:从这几个高度相关的方法中,精准选择sqrt并执行。

这正是下文所述两阶段策略的核心。

二、核心架构:两阶段策略详解

整个工作流设计为两个核心阶段,通过图编排进行串联,流程清晰高效。

两阶段工具调度工作流

阶段一:ToolAgent (智能粗筛 - 向量检索)

此节点负责从海量工具中快速筛选出最相关的少数几个。其核心代码如下:

public class ToolAgent implements NodeAction {
    private ChatClient chatClient;
    private VectorStoreService vectorStoreService;
    private String inputTextKey;
    private static final String CLASSIFIER_PROMPT_TEMPLATE = “””
            ### Job Description
            You are a text keyword extraction engine that can analyze the questions
            passed in by users and extract the main keywords of this sentence.
            ### Task
            You need to extract one or more keywords from this sentence,
            without missing the main body of the user description
            ### Constraint
            Multiple keywords returned, separated by spaces
            “””;

    @Override
    public Map<String, Object> apply(OverAllState state) throws Exception {
        // 1. 获取用户输入
        String inputText = (String) state.value(inputTextKey).orElseThrow();
        // 2. 调用LLM提取问题关键词
        ChatResponse response = chatClient.prompt()
            .system(CLASSIFIER_PROMPT_TEMPLATE)
            .user(inputText) // 例如:“计算 8 的平方根”
            .call()
            .chatResponse();
        String keywords = response.getResult().getOutput().getText(); // 提取结果:“8 平方根 计算”
        // 3. 基于关键词进行向量相似度检索,只返回最相关的Top-K个工具(例如K=3)
        List<Document> hitTools = vectorStoreService.search(keywords, 3);
        // 4. 更新状态,传递给下一节点
        Map<String, Object> updatedState = new HashMap<>();
        updatedState.put(Constant.HIT_TOOL, hitTools);
        updatedState.put(inputTextKey, keywords);
        return updatedState;
    }
}

关键点

  • 意图理解:利用大模型提取用户问题的核心关键词。
  • 向量检索:基于关键词的嵌入向量,在工具库中进行语义相似度搜索。
  • Top-K筛选:仅保留相似度最高的K个(如3个)工具,极大减少后续LLM的决策负担。

向量库的初始化是性能的基础。系统启动时,会将所有工具的高质量描述转换为向量:

private void initializeVectorStore() {
    List<Tool> allTools = new ArrayList<>();
    // 遍历 Math 类的所有静态方法
    for (Method method : Math.class.getMethods()) {
        if (Modifier.isStatic(method.getModifiers())) {
            // 从官方文档抓取方法的Javadoc作为高质量描述
            Tool tool = MethodUtils.convertMethodToTool(method);
            allTools.add(tool);
        }
    }
    // 为每个工具创建Document对象并存入向量库(自动完成Embedding)
    allTools.forEach(tool -> {
        Document doc = new Document(
            UUID.randomUUID().toString(),
            tool.getDescription(), // 例如:“sqrt: Returns the square root of a double value...”
            Map.of(
                “method_name”, tool.getName(),
                “parameter_types”, tool.getParameterTypes()
            )
        );
        documents.add(doc);
    });
    // 批量添加到向量数据库
    vectorStoreService.addDocuments(documents);
}

当用户查询“计算平方根”时,向量检索内部工作大致如下:

查询向量:embedding(“平方根 计算”) = [0.15, 0.87, 0.44, ...]
相似度计算:
  sqrt 描述: “Returns the square root...” → 相似度 0.92
  cbrt 描述: “Returns the cube root...”  → 相似度 0.75
  pow 描述: “Returns the value of...”   → 相似度 0.68
  sin 描述: “Returns the sine...”       → 相似度 0.23
返回 Top-3: [sqrt, cbrt, pow]
阶段二:CalculateAgent (精确调用)

该节点接收筛选出的少数工具,交由LLM进行精确的Function Calling。

public class CalculateAgent implements NodeAction {
    private ChatClient chatClient;
    private String inputTextKey;

    @Override
    public Map<String, Object> apply(OverAllState state) throws Exception {
        // 1. 获取上一节点筛选出的工具列表
        List<Document> hitTools = (List<Document>) state.value(Constant.HIT_TOOL).orElseThrow();
        // 2. 动态构建ToolCallback列表
        List<ToolCallback> toolCallbacks = new ArrayList<>();
        for (Document doc : hitTools) {
            // 通过反射根据元数据找到对应的Method
            Method toolMethod = ReflectionUtils.findMethod(
                Math.class,
                doc.getMetadata().get(“method_name”).toString(),
                (Class<?>[]) doc.getMetadata().get(“parameter_types”)
            );
            // 构建工具定义
            DefaultToolDefinition toolDef = DefaultToolDefinition.builder()
                .name(ToolUtils.getToolName(toolMethod))
                .description(ToolUtils.getToolDescription(toolMethod))
                .inputSchema(JsonSchemaGenerator.generateForMethodInput(toolMethod))
                .build();
            // 创建可执行的回调
            MethodToolCallback callback = MethodToolCallback.builder()
                .toolDefinition(toolDef)
                .toolMethod(toolMethod)
                .build();
            toolCallbacks.add(callback);
        }
        // 3. 调用LLM,仅传入筛选后的工具(如3个)
        String inputText = (String) state.value(inputTextKey).orElse(“”);
        ChatResponse response = chatClient.prompt()
            .system(“Please use the tools to complete the task”)
            .user(inputText) // “计算 8 的平方根”
            .toolCallbacks(toolCallbacks) // 关键:只注册3个工具!
            .call()
            .chatResponse();
        // 4. 返回最终结果
        Map<String, Object> updatedState = new HashMap<>();
        updatedState.put(Constant.SOLUTION, response.getResult().getOutput().getText());
        return updatedState;
    }
}

优势

  • 按需注册:LLM只需处理少量高度相关的工具,决策速度快,准确率高。
  • 动态反射:通过元数据动态定位和调用方法,无需硬编码,扩展性强。

三、图编排与集成

使用Spring AI Alibaba Graph将上述两个节点优雅地串联起来,形成一个完整的工作流。

@RestController
@RequestMapping(“bigtool”)
public class BigToolController {
    private final VectorStoreService vectorStoreService;
    private CompiledGraph compiledGraph;

    public BigToolController(VectorStoreService vectorStoreService, ChatModel chatModel)
            throws GraphStateException {
        this.vectorStoreService = vectorStoreService;
        // 初始化向量数据库
        this.initializeVectorStore();
        // 创建ChatClient
        ChatClient chatClient = ChatClient.builder(chatModel)
            .defaultAdvisors(new SimpleLoggerAdvisor())
            .build();
        // 定义状态管理策略
        KeyStrategyFactory keyStrategyFactory = new KeyStrategyFactoryBuilder()
            .addPatternStrategy(Constant.INPUT_KEY, new ReplaceStrategy())
            .addPatternStrategy(Constant.HIT_TOOL, new ReplaceStrategy())
            .addPatternStrategy(Constant.SOLUTION, new ReplaceStrategy())
            .addPatternStrategy(Constant.TOOL_LIST, new ReplaceStrategy())
            .build();
        // 创建两个节点
        ToolAgent toolAgent = new ToolAgent(chatClient, Constant.INPUT_KEY, vectorStoreService);
        CalculateAgent calculateAgent = new CalculateAgent(chatClient, Constant.INPUT_KEY);
        // 构建并编译执行图
        StateGraph stateGraph = new StateGraph(“Big Tool Workflow”, keyStrategyFactory)
            .addNode(“tools”, AsyncNodeAction.node_async(toolAgent))
            .addNode(“calculate_agent”, AsyncNodeAction.node_async(calculateAgent))
            .addEdge(START, “tools”)
            .addEdge(“tools”, “calculate_agent”)
            .addEdge(“calculate_agent”, END);
        this.compiledGraph = stateGraph.compile();
    }

    @GetMapping(“/search”)
    public String search(@RequestParam String query) {
        Map<String, Object> initialState = Map.of(
            Constant.INPUT_KEY, query,
            Constant.TOOL_LIST, documents
        );
        Optional<OverAllState> result = compiledGraph.call(initialState);
        return result.get().value(“solution”).get().toString();
    }
}

四、实战效果演示

启动应用后,可以通过API进行测试:

  1. 基础数学运算

    curl “http://localhost:18080/bigtool/search?query=计算8的平方根”

    执行流程

    用户输入 -> ToolAgent提取关键词“平方根 计算” -> 向量检索匹配[sqrt, cbrt, pow] -> CalculateAgent调用sqrt(8) -> 返回结果 2.8284271247461903
  2. 三角函数计算

    curl “http://localhost:18080/bigtool/search?query=sin(30度)的值是多少”

    注意:向量检索的Top-K值设置至关重要。若K值太小(如K=3),可能无法召回sin函数(因为sin描述与“30度”的语义匹配度可能不够高)。此时需调整K值(例如设为5)或优化工具描述。这是一个典型的召回率问题,后续可通过混合检索策略优化。

  3. 比较运算

    curl “http://localhost:18080/bigtool/search?query=5和8哪个更大”

    执行流程

    关键词“比较 大小” -> 匹配[max, min, abs] -> 调用max(5, 8) -> 返回结果 8

五、性能与优势分析

通过基准测试对比传统方式与两阶段策略:

性能对比图

  • Token消耗:大幅降低95%以上,因仅需传输少量工具描述。
  • 响应速度:显著提升,LLM决策路径变短。
  • 准确率:不降反升,因为LLM面对的选项高度相关,干扰信息少。

六、关键技术实现细节

1. 高质量工具描述生成

项目启动时自动从Oracle官方Java文档抓取方法的Javadoc,确保描述准确、丰富,这对向量检索的效果至关重要。

// 示例:抓取Math类方法文档
ConcurrentHashMap<String, String> methodDocs = fetchFromOfficialJavadoc(“https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html”);
2. 向量检索服务

基于Spring AI提供的抽象,可以轻松切换底层向量数据库。

@Service
public class VectorStoreService {
    private final VectorStore vectorStore; // 可注入SimpleVectorStore、Milvus等实现

    public List<Document> search(String query, int topK) {
        return vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(query)
                .topK(topK)
                .build()
        );
    }
}
  • 中小规模:使用Spring AI内置的SimpleVectorStore(工具数<1万)。
  • 大规模:可替换为MilvusElasticsearchWeaviate等专业向量数据库。
3. 清晰的状态键设计

通过定义清晰的状态键,实现节点间解耦的数据传递。

public class Constant {
    public static final String INPUT_KEY = “input”; // 用户输入
    public static final String HIT_TOOL = “hit_tool”; // 命中的工具
    public static final String SOLUTION = “solution”; // 最终结果
}

七、架构扩展与应用场景

此架构具有高度通用性,不仅限于数学工具:

  1. 企业级API网关:管理成百上千个内部微服务API。员工用自然语言描述需求,系统自动匹配并调用对应API。
  2. 智能客服知识库:将常见问题解答(FAQ)和标准话术封装为工具,根据客户问题精准匹配回复策略。
  3. 代码生成助手:对接大型代码库或模板库,根据开发者意图生成对应的代码片段。

八、实践总结与最佳实践

在开发过程中,我们总结了以下关键点:

  1. 工具描述质量是核心:丰富、准确、包含多语言关键词的描述能极大提升向量检索召回率。
  2. Top-K值需权衡:K值过小(如1)易导致召回失败,过大(如10)则引入噪声。K=3或5通常是较好的平衡点。
  3. 考虑混合检索:对于关键场景,可结合向量检索(语义)和关键词匹配(字面)的结果,再进行重排序,以提高稳定性。
  4. 完善可观测性:记录关键词提取、检索结果、LLM选择等各环节日志,便于问题排查和效果优化。

九、结语

从“如何让AI从100个工具中智能选择”的疑问出发,到实现一个完整的、基于向量检索的两阶段调度系统,这一实践深刻揭示了处理复杂AI应用问题的有效路径:分层处理、语义优先、平衡性能与精度。利用Spring AI Alibaba Graph这样的框架,可以让我们更专注于业务逻辑而非底层编排,大幅提升开发效率。希望本文的实战方案能为你在构建大规模AI智能体应用时提供有价值的参考。




上一篇:MySQL数据库设计实战:空值字段选择NULL还是默认值的性能对比与最佳实践
下一篇:LandingAI ADE Python SDK实战:基于JSON Schema从非结构化文档提取结构化数据
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 13:13 , Processed in 0.132620 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表