一、项目概述:AI 的"工具箱"思维

1.1 什么是 big-tool?

“25 的 5 次方是多少”?高手不会凭空算,他会拿起计算器(工具),输入 25^5,然后告诉你结果 9765625。

big-tool 项目模拟的就是这个过程——让 AI 学会"拿起合适的工具"来解决问题。

📝 用户提问
'25的5次方是多少'

🤖 AI 思考
识别为数学问题

🧰 AI 选工具
调用 Math.pow

⚙️ 执行计算
Math.pow(25, 5)

✅ 返回结果
9,765,625

1.2 项目定位

属性 说明
定位 Spring AI Alibaba Graph 框架的工具调用入门示例
核心能力 智能数学计算助手——AI 自动识别数学问题,调用 Java 的 Math 类方法
学习价值 理解 Function Calling(函数调用) 的核心概念
难度 ⭐⭐ 初级(比 chatflow 简单,但引入了"向量检索"新概念)
端口 8080

1.3 核心功能一览

功能阶段 说明 类比人类行为
关键词提取 从用户问题中提炼关键信息 学生读题,圈出重点
向量检索 在"工具知识库"中语义搜索 在工具箱里翻找合适的螺丝刀
智能选工具 自动匹配最恰当的 Math 方法 工匠选择最佳工具
自动执行 调用选中的方法完成计算 动手拧螺丝

二、技术架构:从 HTTP 请求到计算结果的完整旅程

2.1 整体架构:四层协作

AI 服务层

存储层

图计算层(核心)

控制器层

客户端层

关键词检索

提取关键词

理解问题 + 生成答案

浏览器/curl
GET /bigtool/search?query=...

BigToolController
接收请求 + 初始化向量库

START

tools 节点
🔍 工具选择

calculate_agent 节点
🧮 执行计算

END

VectorStore
向量数据库

DashScope 大模型
通义千问

2.2 核心流程详解:一个请求的完整生命周期

让我们跟踪一个真实请求:"25的5次方是多少"

DashScope LLM CalculateAgent (calculate节点) ToolAgent (tools节点) StateGraph VectorStore BigToolController DashScope LLM CalculateAgent (calculate节点) ToolAgent (tools节点) StateGraph VectorStore BigToolController 阶段一:初始化(首次启动) 阶段二:构建执行图 阶段三:执行工具选择节点 阶段四:执行计算节点 用户 GET /bigtool/search?query=25的5次方是多少 1 反射获取 Math.class 所有静态方法 2 转换为 Tool 对象 3 存入向量库(方法描述 → 向量) 4 创建 StateGraph 5 注册 tools 节点 6 注册 calculate_agent 节点 7 连接边:START→tools→calculate→END 8 compiledGraph = stateGraph.compile() 9 invoke(state) 10 发送提示词提取关键词 11 返回:"25 5 次方 power" 12 语义搜索:"25 5 次方 power" 13 返回 Top3 工具 [Math.pow, Math.exp, Math.sqrt] 14 更新状态:hitTool = [...] 15 invoke(state) 16 发送提示词选择工具 17 返回:"使用 Math.pow(25, 5)" 18 反射调用 Math.pow(25, 5) 19 得到结果 9765625 20 更新状态:solution = "9765625" 21 返回最终结果 22 9765625 23 用户

2.3 状态流转:数据如何在节点间传递

初始状态
{input: "25的5次方"}

状态更新
{input: "25 5 次方",
hitTool: [Math.pow, ...]}

状态更新
{solution: "9765625"}

返回结果

START

tools

calculate_agent

END

ToolAgent 工作:
1. 用 LLM 提取关键词
2. 向量库搜索工具
3. 把候选工具写入状态

CalculateAgent 工作:
1. 读取候选工具列表
2. 用 LLM 选择最佳工具
3. 反射执行计算
4. 把结果写入状态


三、核心代码深度解析

3.1 向量库初始化:给 AI 建一个"工具说明书"仓库

为什么要做这件事?
AI 不认识 Java 的 Math.pow 方法。我们需要给每个方法写"说明书",存入向量库。当用户问数学问题时,AI 能通过语义搜索找到最相关的说明书。

反射遍历

生成描述

Embedding 向量化

Math.class

获取所有静态方法

方法 → Tool 对象

Document 文档

VectorStore
向量数据库

核心代码详解

private void initializeVectorStore() {
    List<Tool> allTools = new ArrayList<>();

    // 🔍 反射:像镜子一样照出 Math 类的所有方法
    for (Method method : Math.class.getMethods()) {
        // 只拿静态方法(不需要创建对象就能调用的)
        if (java.lang.reflect.Modifier.isStatic(method.getModifiers())) {
            // 把 Method 对象转换成我们定义的 Tool 对象
            Tool tool = MethodUtils.convertMethodToTool(method);
            if (tool != null) {
                allTools.add(tool);
            }
        }
    }

    // 📚 每个 Tool 变成一份"文档"存入向量库
    allTools.forEach(tool -> documents.add(new Document(
        IdUtil.fastSimpleUUID(),          // 唯一 ID
        tool.getDescription(),            // 📄 文档内容:方法的描述文字
        Map.of(                           // 🏷️ 元数据:额外信息
            Constant.METHOD_NAME, tool.getName(),
            Constant.METHOD_PARAMETER_TYPES, tool.getParameterTypes()
        )
    )));

    // 💾 批量存入向量库(这里会调用 Embedding 模型把文字转成向量)
    vectorStoreService.addDocuments(documents);
}

MethodUtils.convertMethodToTool 做了什么?
它把 Java 方法的元数据翻译成人类能读懂的描述:

Java 方法 生成的 Tool 描述
Math.pow(double a, double b) “返回第一个参数的第二个参数次幂的值”
Math.sqrt(double a) “返回正确舍入的 double 值的正平方根”
Math.sin(double a) “返回角度的三角正弦值”

设计亮点与改进建议

当前实现
✅ 自动反射,零配置
❌ 每次启动都重建
❌ 只支持 Math 类

改进方案

改进1:启动优化
@PostConstruct + 判断非空

改进2:扩展性
配置多个类:Math, StrictMath...

改进3:持久化
向量库落盘,避免重复 Embedding

启动速度提升 10x

支持自定义工具类

节省 API 调用费用


3.2 工具选择节点(ToolAgent):AI 的"读题圈重点"能力

这是整个系统的第一个智能节点。它的任务就像考试时的学生:先读题,圈出关键词,然后去工具箱找合适的工具。

25 5 次方 power

Top 3 结果

用户问题
'25的5次方是多少'

LLM 提取关键词

向量库语义搜索

候选工具列表

完整代码解析

public class ToolAgent implements NodeAction {

    // 📝 给 LLM 的"角色设定"——你是一个关键词提取引擎
    private static final String CLASSIFIER_PROMPT_TEMPLATE = """
        ### Job Description
        You are a text keyword extraction engine. Your task is to analyze 
        user questions and extract the most relevant keywords for 
        searching mathematical tools.

        ### Task
        Extract one or more keywords from this sentence that best describe 
        the mathematical operation needed.

        ### Constraint
        - Return ONLY keywords, separated by spaces
        - Include both Chinese and English terms when applicable
        - Example: "25的5次方" → "25 5 次方 power exponent"
        """;

    @Override
    public Map<String, Object> apply(OverAllState state) throws Exception {
        // 1️⃣ 从状态中获取用户原始输入
        this.inputText = (String) state.value(inputTextKey).orElse(this.inputText);

        // 2️⃣ 调用 LLM 提取关键词(第一次 AI 调用)
        ChatResponse response = chatClient.prompt()
            .system(CLASSIFIER_PROMPT_TEMPLATE)  // 给 AI 设定角色
            .user(inputText)                      // 用户问题
            .call()                               // 执行调用
            .chatResponse();

        String keywords = response.getResult().getOutput().getText();
        // 假设返回:"25 5 次方 power"

        // 3️⃣ 用关键词去向量库搜索(语义匹配,不是精确匹配)
        List<Document> hitTool = vectorStoreService.search(keywords, 3);
        // 返回最相关的 3 个工具:
        // [Math.pow(次方), Math.exp(指数), Math.sqrt(平方根)...]

        // 4️⃣ 更新状态,传递给下一个节点
        Map<String, Object> updatedState = new HashMap<>();
        updatedState.put(Constant.HIT_TOOL, hitTool);   // 🎯 候选工具列表
        updatedState.put(inputTextKey, keywords);        // 📝 提取的关键词

        return updatedState;  // ➡️ 流向 calculate_agent 节点
    }
}

为什么用"两阶段选择"?
直接让用户问题搜向量库效果不好(句子太长,噪音多)。先提炼关键词,就像搜索引擎的"query 理解",能大幅提升检索准确度。

效果差

效果好

直接搜索
'25的5次方是多少'

向量匹配度低

两阶段搜索
先提关键词'25 5 次方 power'

精准匹配 Math.pow


3.3 计算节点(CalculateAgent):AI 的"动手解题"能力

这是第二个智能节点。ToolAgent 给了它一堆候选工具,它要决定用哪个、怎么传参数、最后算出结果。

选 Math.pow
参数: 25, 5

Math.pow(25,5)

输入状态
{input, hitTool}

LLM 分析

反射执行

结果: 9765625

更新状态
{solution}

根据架构推断的工作流程

public class CalculateAgent implements NodeAction {

    @Override
    public Map<String, Object> apply(OverAllState state) throws Exception {
        // 1️⃣ 读取上游传递的数据
        String userQuestion = (String) state.value(Constant.INPUT_KEY).orElse("");
        List<Document> candidateTools = (List<Document>) state.value(Constant.HIT_TOOL).orElse(List.of());

        // 2️⃣ 构建提示词:告诉 AI 有哪些工具可选
        String toolList = candidateTools.stream()
            .map(doc -> doc.getMetadata().get(Constant.METHOD_NAME) + ": " + doc.getText())
            .collect(Collectors.joining("\n"));

        // 3️⃣ 调用 LLM 做"工具选择 + 参数解析"(第二次 AI 调用)
        String prompt = """
            用户问题:%s

            可用工具列表:
            %s

            请分析:
            1. 哪个工具最适合解决这个问题?
            2. 参数应该是什么?
            3. 直接返回工具调用结果。
            """.formatted(userQuestion, toolList);

        // 4️⃣ LLM 返回类似 "使用 Math.pow(25, 5),结果是 9765625"
        //    或者直接返回可解析的 JSON:{"tool":"pow","args":[25,5]}

        // 5️⃣ 解析 LLM 输出,反射调用真实 Java 方法
        //    这里需要安全的反射执行逻辑...

        // 6️⃣ 返回最终结果
        Map<String, Object> result = new HashMap<>();
        result.put(Constant.SOLUTION, calculatedResult);
        return result;
    }
}

3.4 主流程配置:把节点串成流水线

StateGraph 配置

KeyStrategyFactory
状态管理策略

StateGraph 图构建

addNode('tools', toolAgent)

addNode('calculate_agent', calculateAgent)

addEdge(START, 'tools')

addEdge('tools', 'calculate_agent')

addEdge('calculate_agent', END)

compile()
生成 CompiledGraph

invoke(state)
执行

配置代码详解

// 🗝️ 状态策略:定义每个 key 的更新方式
KeyStrategyFactory keyStrategyFactory = new KeyStrategyFactoryBuilder()
    // ReplaceStrategy:新值直接覆盖旧值(适合单次计算场景)
    .addPatternStrategy(Constant.INPUT_KEY, new ReplaceStrategy())
    .addPatternStrategy(Constant.HIT_TOOL, new ReplaceStrategy())
    .addPatternStrategy(Constant.SOLUTION, new ReplaceStrategy())
    .addPatternStrategy(Constant.TOOL_LIST, new ReplaceStrategy())
    .build();

// 🏗️ 构建状态图(StateGraph)
StateGraph stateGraph = new StateGraph("Consumer Service Workflow Demo", keyStrategyFactory)
    // 注册节点:名字 + 异步包装
    .addNode("tools", AsyncNodeAction.node_async(tools))           // 🔍 工具选择
    .addNode("calculate_agent", AsyncNodeAction.node_async(calculateAgent)) // 🧮 计算执行

    // 连接边:定义执行顺序
    .addEdge(StateGraph.START, "tools")           // 起点 → 工具选择
    .addEdge("tools", "calculate_agent")          // 工具选择 → 计算执行
    .addEdge("calculate_agent", StateGraph.END);  // 计算执行 → 终点

// 🔨 编译:生成可执行的图对象
this.compiledGraph = stateGraph.compile();

状态策略科普

策略 作用 适用场景
ReplaceStrategy 新值覆盖旧值 单次计算,不需要历史
AppendStrategy 追加到列表 多轮对话,保留历史
MergeStrategy 合并 Map 复杂状态叠加

四、配置与运行

4.1 最小化配置

server:
  port: 8080

spring:
  application:
    name: spring-ai-alibaba-graph-big-tool-example
  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}  # 🔑 从环境变量读取

为什么不需要配置 model?
Spring AI Alibaba 的自动配置会默认使用 qwen-turboqwen-plus,适合这种简单任务。

4.2 环境变量设置

# Linux/Mac
export AI_DASHSCOPE_API_KEY=sk-your-key

# Windows PowerShell
$env:AI_DASHSCOPE_API_KEY="sk-your-key"

# 验证
echo $AI_DASHSCOPE_API_KEY

五、接口调用与测试

5.1 接口规范

项目 说明
路径 GET /bigtool/search
参数 query - 用户数学问题
返回 纯文本计算结果
Content-Type text/plain

5.2 测试用例集

# 🟢 幂运算(最基本功能)
curl "http://localhost:8080/bigtool/search?query=25的5次方是多少"
# 预期:9765625

# 🟢 平方根
curl "http://localhost:8080/bigtool/search?query=16的平方根是多少"
# 预期:4.0

# 🟡 三角函数(考验关键词提取)
curl "http://localhost:8080/bigtool/search?query=sin(30度)是多少"
# 预期:0.5(注意:Math.sin 接受弧度,这里 AI 需要做转换)

# 🟡 对数运算
curl "http://localhost:8080/bigtool/search?query=log10(100)是多少"
# 预期:2.0

# 🔴 边界测试:非数学问题
curl "http://localhost:8080/bigtool/search?query=今天天气怎么样"
# 预期:可能返回空或报错(项目只注册了 Math 工具)

六、项目结构全景

big-tool 项目结构

服务层

模型层

节点实现

图计算层

控制层

配置层

VectorStoreService.java
向量库操作封装

application.yml
端口 + API Key

BigToolController.java
HTTP入口 + 向量库初始化

pom.xml
5个核心依赖

StateGraph 配置
节点注册 + 边连接

CompiledGraph
编译后的执行图

ToolAgent.java
关键词提取 + 向量检索

CalculateAgent.java
工具选择 + 反射执行

MethodUtils.java
反射转换工具

Tool.java
工具实体类

Constant.java
常量定义


七、扩展与优化路线图

7.1 短期优化(1-2 天)

当前状态

添加 @PostConstruct
避免重复初始化

支持配置化工具类
@Value 注入

添加日志链路追踪
MDC traceId

启动速度 ↑

扩展性 ↑

可观测性 ↑

7.2 中期扩展(1 周)

扩展方向

多工具类支持

自定义工具注解

安全执行沙箱

Math + StrictMath + Integer + ...

@Tool(description='...')
自动扫描注册

参数校验 + 超时控制
防止危险调用

7.3 长期演进(1 月)

big-tool 演进

parallel-stream-node
并行调用多工具

multiagent
多智能体协作

mcp-node
MCP 协议标准化

同时查天气+算数学

分工:A提取关键词
B执行计算

对接外部 MCP 服务
如数据库、搜索引擎


八、常见问题深度解析

Q1:为什么第一次请求很慢?

首次请求慢

原因1:向量库初始化
反射 Math 类所有方法

原因2:Embedding 模型加载
文本 → 向量转换

原因3:LLM 连接建立
HTTP 连接池预热

解决方案:
@PostConstruct 预加载

Q2:向量搜索没找到工具怎么办?

根本原因:关键词提取与工具描述的语义鸿沟。

提取'25 5 次方'

匹配失败

用户说'25的5次方'

向量搜索

可能原因

Embedding 模型
对中文数字敏感度低

工具描述是英文
'returns the value of a^b'

没有同义词扩展
'次方'≠'power'

优化:混合搜索
语义 + 关键词

Q3:AI 选错工具或参数怎么办?

这是 Function Calling 的经典难题。解决方案:

  1. Few-shot 提示:在 prompt 里给 3-5 个正确示例
  2. 强制 JSON 输出:要求 LLM 返回结构化数据 {"tool":"pow","args":[25,5]}
  3. 参数校验:执行前检查参数类型和范围
  4. 重试机制:失败时换下一个候选工具

九、小结

9.1 核心概念图谱

big-tool
核心概念

FunctionCalling

AI选择工具

自动执行

结果返回

向量检索

Embedding
文本→向量

语义相似度

TopK召回

状态图

节点=计算单元

边=数据流向

状态=数据载体

反射执行

Method.invoke

动态调用

安全沙箱

9.2 与 chatflow 的对比

维度 chatflow big-tool
核心能力 多轮对话 + 意图识别分支 工具调用 + 自动执行
流程结构 条件分支(if-else) 线性流水线(两阶段)
AI 调用次数 每轮对话 1 次 单次请求 2 次(提取关键词 + 选择工具)
状态管理 复杂(保留对话历史) 简单(ReplaceStrategy 覆盖)
适用场景 客服、问答 计算、工具执行
学习曲线 ⭐⭐⭐ 中等 ⭐⭐ 较简单

9.3 学完能做什么?

掌握 big-tool

理解 Function Calling 原理

学会向量语义检索

掌握 StateGraph 编排

开发智能助手
自动调用 API

构建企业知识库
语义搜索

设计复杂 AI 工作流
多节点协作


十、部署实操(精简版)

10.1 本地开发(3 分钟)

# 1. 克隆项目
git clone https://github.com/alibaba/spring-ai-alibaba.git
cd spring-ai-alibaba/spring-ai-alibaba-graph-example/big-tool

# 2. 配置环境变量
export AI_DASHSCOPE_API_KEY=sk-your-key

# 3. 编译运行
mvn clean spring-boot:run

# 4. 测试
curl "http://localhost:8080/bigtool/search?query=2的10次方"
# 输出:1024

10.2 Docker 部署(生产推荐)

# Dockerfile
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENV AI_DASHSCOPE_API_KEY=""
ENTRYPOINT ["java", "-jar", "app.jar"]
# 构建 & 运行
docker build -t big-tool .
docker run -d -p 8080:8080 -e AI_DASHSCOPE_API_KEY=sk-xxx big-tool

10.3 Systemd 服务化(Linux 服务器)

# /etc/systemd/system/big-tool.service
[Unit]
Description=Big Tool AI Service
After=network.target

[Service]
Type=simple
User=app
WorkingDirectory=/opt/big-tool
ExecStart=/usr/bin/java -Xms512m -Xmx1g -jar big-tool.jar
Restart=always
Environment="AI_DASHSCOPE_API_KEY=sk-your-key"

[Install]
WantedBy=multi-user.target
sudo systemctl enable big-tool
sudo systemctl start big-tool
sudo systemctl status big-tool

附录:依赖详解

<!-- AI 能力:阿里云 DashScope -->
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>

<!-- Spring AI 自动配置 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
</dependency>

<!-- Graph 编排框架 -->
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-graph-core</artifactId>
</dependency>

<!-- 工具库:UUID 生成等 -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-core</artifactId>
    <version>5.8.38</version>
</dependency>

<!-- HTML 解析(预留扩展) -->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.18.3</version>
</dependency>
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐