智能体的自定义工具

1.自定义仓库管理系统中的仓库信息查询工具:

java 复制代码
@Data
public class WmsInventoryQuery {

    //商品名称
    
    @ToolParam(required = true, description = "商品名称(必填)")
    private String productName;

    //商品编码
    
    @ToolParam(required = false, description = "商品编码")
    private String productCode;

    //货主名称
    
    @ToolParam(required = true, description = "货主名称(必填)")
    private String ownerName;

    //货主编码
    
    @ToolParam(required = false, description = "货主编码")
    private String ownerCode;

    //仓库名称
    
    @ToolParam(required = true, description = "仓库名称(必填)")
    private String warehouseName;

    //仓库编码
    
    @ToolParam(required = false, description = "仓库编码")
    private String warehouseCode;

    //储位编码
    
    @ToolParam(required = false, description = "储位编码")
    private String locationCode;
}

写一个查询仓库信息的工具类:

java 复制代码
@Component
public class WmsInventoryTools {

    @Resource
    private IWmsInventoryService wmsInventoryService;

    @Resource
    private IWmsCargoOwnersService wmsCargoOwnersService;

    @Resource
    private IWmsWarehousesService wmsWarehousesService;

    @Resource
    private IWmsProductsService wmsProductsService;

    /**
     * 查询库存
     */

    @Tool(description = "根据条件查询库存信息")
    public List<WmsInventory> queryInventory(
            @ToolParam(description = "库存查询条件") WmsInventoryQuery query,
            @ToolParam(description = "库存查询页码") Integer pageNo) {

        if (pageNo == null || pageNo <= 0) {
            pageNo = 1;
        }
        WmsInventory wmsInventory = new WmsInventory();
        BeanUtils.copyProperties(query, wmsInventory);
        IPage<WmsInventory> wmsInventoryIPage = wmsInventoryService.queryList(wmsInventory, pageNo, 2);
        return wmsInventoryIPage.getRecords();
    }

    /**
     * 查询货主信息
     */
    @Tool(description = "查询货主信息")
    public List<WmsCargoOwners> queryOwnerInfo() {
        return wmsCargoOwnersService.getBaseMapper().selectList(null);
    }

    /**
     * 查询仓库信息
     */
    @Tool(description = "查询仓库信息")
    public List<WmsWarehouses> queryWarehouses() {
        return wmsWarehousesService.getBaseMapper().selectList(null);
    }

    /**
     * 查询商品信息
     */
    @Tool(description = "查询商品信息")
    public List<WmsProducts> queryProducts() {
        return wmsProductsService.getBaseMapper().selectList(null);
    }


}

@ToolParam有两种用法,一种是在方法参数上直接标注,另一种是在VO/DTO类上直接标注,本文采用第二种,required参数值必须是true才能生效。

controller类建立聊天窗口:

java 复制代码
@RestController
@Slf4j
@RequestMapping("/ai")
public class ChatController {

    @Resource(name = "chatClientOpenAi")
    private ChatClient chatClient;

    @Resource
    private OpenAiEmbeddingModel openAiEmbeddingModel;

    @Resource
    private IAiragAppService airagAppService;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private ISysBaseAPI sysBaseApi;

    @Resource
    private VectorStore pgVectorStore;

    @Resource
    private WmsInventoryTools wmsInventoryTools;


    // 辅助方法:将float数组格式化为字符串
    private String arrayToString(float[] array) {
        if (array == null) {
            return "null";
        }
        if (array.length == 0) {
            return "[]";
        }

        StringBuilder sb = new StringBuilder();
        sb.append("[");

        // 限制显示长度,避免输出过长的向量
        // 只显示前10个元素
        int limit = Math.min(array.length, 10);
        for (int i = 0; i < limit; i++) {
            sb.append(String.format("%.6f", array[i]));
            if (i < limit - 1) {
                sb.append(", ");
            }
        }

        if (array.length > limit) {
            sb.append(", ... (").append(array.length).append(" dimensions)");
        }

        sb.append("]");
        return sb.toString();
    }

    private String getUsername(HttpServletRequest httpRequest) {
        try {
            TokenUtils.getTokenByRequest();
            String token;
            if (null != httpRequest) {
                token = TokenUtils.getTokenByRequest(httpRequest);
            } else {
                token = TokenUtils.getTokenByRequest();
            }
            if (TokenUtils.verifyToken(token, sysBaseApi, redisUtil)) {
                return JwtUtil.getUsername(token);
            }
        } catch (Exception e) {
            return null;
        }
        return null;
    }

    @RequestMapping(value = "/chat", produces = "text/html;charset=UTF-8")
    public Flux<String> chat(String prompt) {
        Flux<String> content = chatClient.prompt(prompt).stream().content();
        log.debug(String.valueOf(content));
        return content;
    }

    private QuestionAnswerAdvisor getQuestionAnswerAdvisor(List<String> knowIds) {
        StringBuilder expression = new StringBuilder();
        Iterator<String> iterator = knowIds.iterator();
        //通过下边迭代器拼接字符串 最终拼接字符串为:knowledgeId == 'xx' || knowledgeId == 'yy || ...'
        while (iterator.hasNext()) {
            String knowId = iterator.next();
            expression.append("knowledgeId == '").append(knowId).append("'");
            if (iterator.hasNext()) {
                expression.append(" || ");
            }
        }

        return new QuestionAnswerAdvisor(
                // 向量库
                pgVectorStore,
                // 向量检索的请求参数
                SearchRequest.builder()
                        // 相似度阈值
                        .similarityThreshold(0.5d)
                        .filterExpression(expression.toString())
                        // 返回的文档片段数量
                        .topK(2)
                        .build()
        );

    }

    @RequestMapping(value = "/chat/send")
    public SseEmitter send(@RequestBody ChatSendParams chatSendParams, HttpServletRequest httpRequest) throws IOException {
        AssertUtils.assertNotEmpty("参数异常", chatSendParams);
        String userMessage = chatSendParams.getContent();
        AssertUtils.assertNotEmpty("至少发送一条消息", userMessage);
        // 获取会话信息
        String conversationId = chatSendParams.getConversationId();
        String topicId = oConvertUtils.getString(chatSendParams.getTopicId(), UUIDGenerator.generate());
        chatSendParams.setTopicId(topicId);
        String username = getUsername(httpRequest);
        chatSendParams.setUsername(username);
        // 每次会话都生成一个新的,用来缓存emitter
        String requestId = UUIDGenerator.generate();

        SseEmitter emitter = new SseEmitter(-0L);
        AiragApp app = null;
        List<String> knowIds = null;
        if (oConvertUtils.isNotEmpty(chatSendParams.getAppId())) {
            app = airagAppService.getById(chatSendParams.getAppId());
            knowIds = app.getKnowIds();
        }
//        QuestionAnswerAdvisor questionAnswerAdvisor = getQuestionAnswerAdvisor(knowIds);


        // 发送初始思考提示消息
        try {
            // 发送 "> " 消息
            sendMessage(emitter, conversationId, topicId, requestId, "> ", "MESSAGE");

            // 发送 "\n> " 消息
            sendMessage(emitter, conversationId, topicId, requestId, "\n> ", "MESSAGE");

        } catch (IOException e) {
            emitter.completeWithError(e);
            return emitter;
        }
        String json = JSONObject.toJSONString(chatSendParams);
        Flux<String> flux = null;
        if (app == null) {
            //没有挂应用
            flux = chatClient
                    .prompt(userMessage)
                    .advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, json))
                    .stream()
                    .content();

        } else {
            if (knowIds != null && !knowIds.isEmpty()) {
                QuestionAnswerAdvisor questionAnswerAdvisor = getQuestionAnswerAdvisor(knowIds);
                flux = chatClient
                        .prompt(userMessage)
                        .user(userMessage)
                        // 添加系统提示词
                        .system(app.getPrompt())
                        .advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, json))
                        .advisors(questionAnswerAdvisor)
                        .tools(wmsInventoryTools)
                        .stream()
                        .content();
            } else {
                flux = chatClient
                        .prompt(userMessage)
                        .user(userMessage)
                        // 添加系统提示词
                        .tools(wmsInventoryTools)
                        .system(app.getPrompt())
                        .advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, json))
                        .stream()
                        .content();
            }
        }
        /**
         * 是否正在思考
         */
        AtomicBoolean isThinking = new AtomicBoolean(false);
        try {
            sendMessage(emitter, conversationId, topicId, requestId, ">", "MESSAGE");
        } catch (IOException e) {
            emitter.completeWithError(e);
        }
//        sendMessage(emitter, conversationId, topicId, requestId, "\\n", "MESSAGE");
        flux.subscribe(
                data -> {
                    try {
                        // 兼容推理模型
                        if ("<think>".equals(data)) {
                            isThinking.set(true);
                            data = "> ";
                        }
                        if ("</think>".equals(data)) {
                            isThinking.set(false);
                            data = "\n\n";
                        }
                        if (isThinking.get()) {
                            if (null != data && data.contains("\n")) {
                                data = "\n> ";
                            }
                        }

                        sendMessage(emitter, conversationId, topicId, requestId, data, "MESSAGE");


                    } catch (IOException e) {
                        emitter.completeWithError(e);//将错误信息发送给客户端,并结束SSE连接。
                    }
                },
                error -> {
                    try {
                        // 错误响应
                        sendMessage(emitter, conversationId, topicId, requestId, error.getMessage(), "ERROR");
                        // 发送结束消息
                        sendEndMessage(emitter, conversationId, topicId, requestId);
                    } catch (IOException ioException) {
                        emitter.completeWithError(ioException);
                    }
                },
                () -> {
                    try {
                        // 发送结束消息两次
                        sendEndMessage(emitter, conversationId, topicId, requestId);
                        sendEndMessage(emitter, conversationId, topicId, requestId);
                    } catch (IOException e) {
                        emitter.completeWithError(e);
                    }
                }
        );


        return emitter;
    }


    // 辅助方法:向客户端发送消息
    private void sendMessage(SseEmitter emitter, String conversationId, String topicId,
                             String requestId, String message, String event) throws IOException {
        Map<String, Object> response = new HashMap<>();
        response.put("conversationId", conversationId);
        response.put("topicId", topicId);
        response.put("requestId", requestId);
        response.put("event", event);

        Map<String, Object> messageData = new HashMap<>();
        messageData.put("message", message);
        response.put("data", messageData);

        String jsonData = JSONObject.toJSONString(response);
        emitter.send(SseEmitter.event().data(jsonData));
    }

    // 辅助方法:发送结束消息
    private void sendEndMessage(SseEmitter emitter, String conversationId, String topicId,
                                String requestId) throws IOException {
        Map<String, Object> endResponse = new HashMap<>();
        endResponse.put("event", "MESSAGE_END");
        endResponse.put("flowId", null);
        endResponse.put("requestId", requestId);
        endResponse.put("conversationId", conversationId);
        endResponse.put("topicId", topicId);
        endResponse.put("data", null);

        String endJson = JSONObject.toJSONString(endResponse);
        emitter.send(SseEmitter.event().data(endJson));
    }
}

QuestionAnswerAdvisor 是 Spring AI 的内置 Advisor,它在每次 LLM 调用前自动执行以下流程:

将用户问题向量化,RAG 检索(调用 LLM 前自动触发)

|---------------------|----------------------|---------------------------------|
| 参数 | 值 | 作用 |
| pgVectorStore | PostgreSQL 向量库 | 存储知识文档的向量和元数据 |
| similarityThreshold | 0.5 | 只有相似度 ≥ 0.5 的文档才会被召回,过滤低质量结果 |
| filterExpression | knowledgeId == 'xxx' | 限定只检索指定的知识库,避免跨库污染 |
| topK | 2 | 每次最多返回 2 个最相关的文档片段,控制 Prompt 长度 |

在调用大模型链里面的作用:

ChatClient
.prompt(userMessage) ← 用户输入信息
.system(app.getPrompt()) ← 系统提示词
.advisors(questionAnswerAdvisor) ← RAG 检索(调用 LLM 前自动触发)
.tools(wmsInventoryTools) ← 工具注册
.advisors(memory) ← 会话记忆(自动,ChatConfiguration 注入)
.stream()

相关推荐
老王以为1 小时前
单仓库下的四十模块 —— React Monorepo 工程架构拆解
前端·react native·react.js
原创小甜甜1 小时前
OOM 排查复盘:Hutool 序列化 Request 导致 Java Heap Space
java·开发语言·python
lichenyang4531 小时前
鸿蒙路由研读:为什么公司项目用 HMRouterMgr 而不用原生 Navigation
前端
列星随旋1 小时前
矩阵快速幂
java·算法·矩阵
闪电悠米1 小时前
黑马点评-分布式锁-02_simple_redis_lock_setnx
java·数据库·spring boot·redis·分布式·缓存·wpf
gf13211111 小时前
【精确查找python脚本是否在运行】
linux·前端·python
Sunny Boy 0011 小时前
linux环境编译Pro*C 源文件(.pc文件)
linux·c语言·oracle
萨小耶1 小时前
[Java学习日记10】聊聊checked exception和runtime exception
java·开发语言·学习
超梦dasgg1 小时前
IDEA(IntelliJ IDEA)超详细基础使用教程
java·ide·intellij-idea