1、核心设计思路
Text-to-SQL 的本质是让大模型根据数据库表结构(元数据) 和用户自然语言问题 生成 SQL。当数据库表较多时,把所有表结构都塞进 Prompt 会超出上下文限制,所以需要用 RAG 做"表选择"------先根据用户问题从向量库中检索相关的表结构,再交给 LLM 生成 SQL。
2、使用框架和工具
- 技术栈:Ollama + Qwen3.5:4b (作为聊天模型)、nomic-embed-text:latest (作为嵌入模型)、Qdrant(作为向量数据库),并集成了 RAG 架构来管理数据库表元数据。
- 大模型:ollama/qwen3.5:4b 负责自然语言转 SQL、问答
- 向量嵌入:ollama/nomic-embed-text:latest 文本向量化
- 向量库:Qdrant 存储表结构元数据向量,做检索增强
- 框架:Spring AI 2.0(统一封装 Ollama、Qdrant、Embedding)

3、数据库表
电商订单系统为例,包含用户、商品、订单、订单明细等表:
-- ----------------------------
-- Table structure for carts
-- ----------------------------
DROP TABLE IF EXISTS `carts`;
CREATE TABLE `carts` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NULL DEFAULT NULL COMMENT '用户ID',
`product_id` int NULL DEFAULT NULL COMMENT '商品ID',
`quantity` int NOT NULL COMMENT '数量',
`selected` tinyint(1) NULL DEFAULT 1 COMMENT '是否选中',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_user_product`(`user_id` ASC, `product_id` ASC) USING BTREE,
INDEX `product_id`(`product_id` ASC) USING BTREE,
CONSTRAINT `carts_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `carts_ibfk_2` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `carts_chk_1` CHECK (`quantity` > 0)
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '购物车表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of carts
-- ----------------------------
INSERT INTO `carts` VALUES (1, 1, 3, 1, 1, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `carts` VALUES (2, 1, 5, 2, 1, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `carts` VALUES (3, 2, 4, 1, 0, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `carts` VALUES (4, 3, 1, 1, 1, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `carts` VALUES (5, 4, 2, 2, 1, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
-- ----------------------------
-- Table structure for categories
-- ----------------------------
DROP TABLE IF EXISTS `categories`;
CREATE TABLE `categories` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '分类名称',
`parent_id` int NULL DEFAULT NULL COMMENT '父分类ID',
`level` int NULL DEFAULT 1 COMMENT '分类层级',
`sort_order` int NULL DEFAULT 0 COMMENT '排序序号',
`status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '启用' COMMENT '状态',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `parent_id`(`parent_id` ASC) USING BTREE,
CONSTRAINT `categories_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `categories` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `categories_chk_1` CHECK (`level` between 1 and 3),
CONSTRAINT `categories_chk_2` CHECK (`status` in (_utf8mb4'启用',_utf8mb4'禁用'))
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品分类表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of categories
-- ----------------------------
INSERT INTO `categories` VALUES (1, '电子产品', NULL, 1, 1, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (2, '服装鞋帽', NULL, 1, 2, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (3, '食品饮料', NULL, 1, 3, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (4, '手机', 1, 2, 1, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (5, '电脑', 1, 2, 2, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (6, '男装', 2, 2, 1, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (7, '女装', 2, 2, 2, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (8, '智能手机', 4, 3, 1, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `categories` VALUES (9, '笔记本电脑', 5, 3, 1, '启用', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
-- ----------------------------
-- Table structure for order_items
-- ----------------------------
DROP TABLE IF EXISTS `order_items`;
CREATE TABLE `order_items` (
`id` int NOT NULL AUTO_INCREMENT,
`order_id` int NULL DEFAULT NULL COMMENT '订单ID',
`product_id` int NULL DEFAULT NULL COMMENT '商品ID',
`product_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品名称',
`product_price` decimal(10, 2) NOT NULL COMMENT '商品单价',
`quantity` int NOT NULL COMMENT '购买数量',
`total_price` decimal(12, 2) NOT NULL COMMENT '小计金额',
`discount` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '单项优惠金额',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `order_id`(`order_id` ASC) USING BTREE,
INDEX `product_id`(`product_id` ASC) USING BTREE,
CONSTRAINT `order_items_ibfk_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `order_items_ibfk_2` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `order_items_chk_1` CHECK (`product_price` >= 0),
CONSTRAINT `order_items_chk_2` CHECK (`quantity` > 0),
CONSTRAINT `order_items_chk_3` CHECK (`total_price` >= 0),
CONSTRAINT `order_items_chk_4` CHECK (`discount` >= 0)
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单明细表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of order_items
-- ----------------------------
INSERT INTO `order_items` VALUES (1, 1, 1, 'iPhone 15 Pro Max', 9999.00, 1, 9999.00, 100.00, '2026-07-18 22:26:42');
INSERT INTO `order_items` VALUES (2, 2, 2, '小米14 Ultra', 5999.00, 1, 5999.00, 0.00, '2026-07-18 22:26:42');
INSERT INTO `order_items` VALUES (3, 3, 5, '男士羽绒服', 899.00, 2, 1798.00, 0.00, '2026-07-18 22:26:42');
INSERT INTO `order_items` VALUES (4, 3, 6, '女士羊绒大衣', 2599.00, 1, 2599.00, 200.00, '2026-07-18 22:26:42');
INSERT INTO `order_items` VALUES (5, 4, 5, '男士羽绒服', 899.00, 1, 899.00, 50.00, '2026-07-18 22:26:42');
INSERT INTO `order_items` VALUES (6, 5, 6, '女士羊绒大衣', 2599.00, 1, 2599.00, 0.00, '2026-07-18 22:26:42');
INSERT INTO `order_items` VALUES (7, 6, 2, '小米14 Ultra', 5999.00, 1, 5999.00, 100.00, '2026-07-18 22:26:42');
-- ----------------------------
-- Table structure for orders
-- ----------------------------
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` int NOT NULL AUTO_INCREMENT,
`order_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '订单编号',
`user_id` int NULL DEFAULT NULL COMMENT '用户ID',
`total_amount` decimal(12, 2) NOT NULL COMMENT '订单总金额',
`discount_amount` decimal(12, 2) NULL DEFAULT 0.00 COMMENT '优惠金额',
`pay_amount` decimal(12, 2) NOT NULL COMMENT '实付金额',
`freight` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '运费',
`pay_method` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '在线支付' COMMENT '支付方式',
`order_status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '待付款' COMMENT '订单状态',
`payment_time` timestamp NULL DEFAULT NULL COMMENT '支付时间',
`delivery_time` timestamp NULL DEFAULT NULL COMMENT '发货时间',
`receive_time` timestamp NULL DEFAULT NULL COMMENT '收货时间',
`complete_time` timestamp NULL DEFAULT NULL COMMENT '完成时间',
`cancel_time` timestamp NULL DEFAULT NULL COMMENT '取消时间',
`shipping_address` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '收货地址',
`receiver_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '收货人姓名',
`receiver_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '收货人电话',
`remark` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '备注',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `order_no`(`order_no` ASC) USING BTREE,
INDEX `user_id`(`user_id` ASC) USING BTREE,
CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `orders_chk_1` CHECK (`total_amount` >= 0),
CONSTRAINT `orders_chk_2` CHECK (`discount_amount` >= 0),
CONSTRAINT `orders_chk_3` CHECK (`pay_amount` >= 0),
CONSTRAINT `orders_chk_4` CHECK (`freight` >= 0),
CONSTRAINT `orders_chk_5` CHECK (`pay_method` in (_utf8mb4'在线支付',_utf8mb4'货到付款',_utf8mb4'银行转账')),
CONSTRAINT `orders_chk_6` CHECK (`order_status` in (_utf8mb4'待付款',_utf8mb4'已付款',_utf8mb4'已发货',_utf8mb4'已收货',_utf8mb4'已完成',_utf8mb4'已取消',_utf8mb4'退货中'))
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单主表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of orders
-- ----------------------------
INSERT INTO `orders` VALUES (1, 'ORD2024120001', 1, 9999.00, 100.00, 9899.00, 0.00, '在线支付', '已完成', '2024-12-01 10:30:00', '2024-12-02 14:00:00', '2024-12-05 16:20:00', NULL, NULL, '北京市朝阳区XX路1号', '张伟', '13800001001', NULL, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `orders` VALUES (2, 'ORD2024120002', 2, 5999.00, 0.00, 5999.00, 20.00, '在线支付', '已发货', '2024-12-10 14:20:00', '2024-12-11 09:30:00', NULL, NULL, NULL, '上海市浦东新区XX路2号', '李明', '13800001002', NULL, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `orders` VALUES (3, 'ORD2024120003', 3, 3498.00, 200.00, 3298.00, 0.00, '在线支付', '已收货', '2024-12-15 08:45:00', '2024-12-16 16:00:00', '2024-12-18 10:00:00', NULL, NULL, '广州市天河区XX路3号', '王芳', '13800001003', NULL, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `orders` VALUES (4, 'ORD2024120004', 1, 899.00, 50.00, 849.00, 15.00, '货到付款', '已付款', '2024-12-18 11:00:00', NULL, NULL, NULL, NULL, '北京市海淀区XX路4号', '张伟', '13800001001', NULL, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `orders` VALUES (5, 'ORD2024120005', 4, 2599.00, 0.00, 2599.00, 0.00, '在线支付', '待付款', NULL, NULL, NULL, NULL, NULL, '深圳市南山区XX路5号', '陈静', '13800001004', NULL, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `orders` VALUES (6, 'ORD2024120006', 5, 5999.00, 100.00, 5899.00, 30.00, '在线支付', '已完成', '2024-12-05 09:15:00', '2024-12-06 10:00:00', '2024-12-08 14:30:00', NULL, NULL, '杭州市西湖区XX路6号', '刘俊', '13800001005', NULL, '2026-07-18 22:26:42', '2026-07-18 22:26:42');
-- ----------------------------
-- Table structure for products
-- ----------------------------
DROP TABLE IF EXISTS `products`;
CREATE TABLE `products` (
`id` int NOT NULL AUTO_INCREMENT,
`category_id` int NULL DEFAULT NULL COMMENT '分类ID',
`name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品名称',
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '商品描述',
`price` decimal(10, 2) NOT NULL COMMENT '商品价格',
`stock` int NOT NULL DEFAULT 0 COMMENT '库存数量',
`sales_volume` int NULL DEFAULT 0 COMMENT '销量',
`rating` decimal(3, 2) NULL DEFAULT 0.00 COMMENT '评分',
`brand` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '品牌',
`supplier` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '供应商',
`images` json NULL COMMENT '商品图片JSON数组',
`status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '上架' COMMENT '商品状态',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `category_id`(`category_id` ASC) USING BTREE,
CONSTRAINT `products_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `products_chk_1` CHECK (`price` >= 0),
CONSTRAINT `products_chk_2` CHECK (`stock` >= 0),
CONSTRAINT `products_chk_3` CHECK (`sales_volume` >= 0),
CONSTRAINT `products_chk_4` CHECK (`rating` between 0 and 5),
CONSTRAINT `products_chk_5` CHECK (`status` in (_utf8mb4'上架',_utf8mb4'下架',_utf8mb4'售罄'))
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品信息表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of products
-- ----------------------------
INSERT INTO `products` VALUES (1, 8, 'iPhone 15 Pro Max', '苹果最新旗舰手机,A17芯片,钛金属边框', 9999.00, 50, 120, 4.80, 'Apple', '苹果中国', NULL, '上架', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `products` VALUES (2, 8, '小米14 Ultra', '徕卡光学镜头,骁龙8 Gen3', 5999.00, 80, 200, 4.60, 'Xiaomi', '小米科技', NULL, '上架', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `products` VALUES (3, 9, 'MacBook Pro 16寸', 'M3 Max芯片,36GB内存,1TB SSD', 24999.00, 20, 45, 4.90, 'Apple', '苹果中国', NULL, '上架', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `products` VALUES (4, 9, '联想 ThinkPad X1', '轻薄商务本,i7处理器,32GB内存', 15999.00, 30, 78, 4.50, 'Lenovo', '联想集团', NULL, '上架', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `products` VALUES (5, 6, '男士羽绒服', '90%白鹅绒,防风防水,-30℃保暖', 899.00, 100, 350, 4.30, '波司登', '波司登服饰', NULL, '上架', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `products` VALUES (6, 7, '女士羊绒大衣', '100%山羊绒,双面呢,经典款', 2599.00, 60, 180, 4.70, '鄂尔多斯', '鄂尔多斯集团', NULL, '上架', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
-- ----------------------------
-- Table structure for reviews
-- ----------------------------
DROP TABLE IF EXISTS `reviews`;
CREATE TABLE `reviews` (
`id` int NOT NULL AUTO_INCREMENT,
`product_id` int NULL DEFAULT NULL COMMENT '商品ID',
`user_id` int NULL DEFAULT NULL COMMENT '用户ID',
`order_id` int NULL DEFAULT NULL COMMENT '订单ID',
`order_item_id` int NULL DEFAULT NULL COMMENT '订单明细ID',
`rating` int NOT NULL COMMENT '评分',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '评价内容',
`images` json NULL COMMENT '评价图片JSON数组',
`is_anonymous` tinyint(1) NULL DEFAULT 0 COMMENT '是否匿名',
`status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '审核中' COMMENT '审核状态',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `product_id`(`product_id` ASC) USING BTREE,
INDEX `user_id`(`user_id` ASC) USING BTREE,
INDEX `order_id`(`order_id` ASC) USING BTREE,
CONSTRAINT `reviews_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `reviews_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `reviews_ibfk_3` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `reviews_chk_1` CHECK (`rating` between 1 and 5),
CONSTRAINT `reviews_chk_2` CHECK (`status` in (_utf8mb4'审核中',_utf8mb4'已通过',_utf8mb4'已拒绝'))
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品评价表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of reviews
-- ----------------------------
INSERT INTO `reviews` VALUES (1, 1, 1, 1, 1, 5, '手机非常好,拍照效果惊艳!', NULL, 0, '已通过', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `reviews` VALUES (2, 2, 5, 6, 7, 4, '性能强劲,就是发热有点严重', NULL, 0, '已通过', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `reviews` VALUES (3, 5, 3, 3, 3, 5, '羽绒服很暖和,质量很好', NULL, 0, '已通过', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
-- ----------------------------
-- Table structure for spring_ai_chat_memory
-- ----------------------------
DROP TABLE IF EXISTS `spring_ai_chat_memory`;
CREATE TABLE `spring_ai_chat_memory` (
`conversation_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`type` enum('USER','ASSISTANT','SYSTEM','TOOL') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`timestamp` timestamp NOT NULL,
`sequence_id` bigint NOT NULL,
INDEX `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX`(`conversation_id` ASC, `timestamp` ASC) USING BTREE,
INDEX `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_SEQUENCE_ID_IDX`(`conversation_id` ASC, `sequence_id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of spring_ai_chat_memory
-- ----------------------------
-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '电子邮箱',
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '手机号码',
`real_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '真实姓名',
`gender` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '性别',
`age` int NULL DEFAULT NULL COMMENT '年龄',
`level` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '普通会员' COMMENT '会员等级',
`register_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
`last_login_time` timestamp NULL DEFAULT NULL COMMENT '最后登录时间',
`status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '正常' COMMENT '账户状态',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username`(`username` ASC) USING BTREE,
UNIQUE INDEX `email`(`email` ASC) USING BTREE,
CONSTRAINT `users_chk_1` CHECK (`gender` in (_utf8mb4'男',_utf8mb4'女',_utf8mb4'保密')),
CONSTRAINT `users_chk_2` CHECK ((`age` >= 0) and (`age` <= 150)),
CONSTRAINT `users_chk_3` CHECK (`level` in (_utf8mb4'普通会员',_utf8mb4'银牌会员',_utf8mb4'金牌会员',_utf8mb4'钻石会员')),
CONSTRAINT `users_chk_4` CHECK (`status` in (_utf8mb4'正常',_utf8mb4'冻结',_utf8mb4'注销'))
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES (1, 'zhangwei', 'zhangwei@email.com', '13800001001', '张伟', '男', 28, '金牌会员', '2024-01-15 10:00:00', '2024-12-20 14:30:00', '正常', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `users` VALUES (2, 'liming', 'liming@email.com', '13800001002', '李明', '男', 35, '钻石会员', '2023-06-20 09:00:00', '2024-12-21 09:15:00', '正常', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `users` VALUES (3, 'wangfang', 'wangfang@email.com', '13800001003', '王芳', '女', 26, '银牌会员', '2024-03-10 16:20:00', '2024-12-19 20:00:00', '正常', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `users` VALUES (4, 'chenjing', 'chenjing@email.com', '13800001004', '陈静', '女', 32, '普通会员', '2024-08-05 11:00:00', '2024-12-18 11:30:00', '正常', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
INSERT INTO `users` VALUES (5, 'liujun', 'liujun@email.com', '13800001005', '刘俊', '男', 41, '金牌会员', '2023-11-01 08:30:00', '2024-12-20 16:45:00', '正常', '2026-07-18 22:26:42', '2026-07-18 22:26:42');
4、代码
Maven 依赖 (pom.xml)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<!--AI start-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-starter-model-chat-memory-repository-jdbc -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
<!--AI end-->
<!--向量存储star-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-qdrant</artifactId>
</dependency>
<!-- 用于读取 PDF、Word 等各类文档 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
<!--向量存储end-->
<!-- Spring Boot JDBC Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
<!--工具-->
<!-- Source: https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>
配置文件 (application.yml)
spring:
application:
name: text-sql-demo
ai:
ollama:
base-url: http://127.0.0.1:11434
chat:
model: qwen3.5:4b # 模型
temperature: 0.1 # 低温度让 SQL 生成更确定、更准确。
top-p: 0.7 # 概率
think: true # 是否思考
embedding:
model: nomic-embed-text:latest # embedding 模型
chat:
memory:
repository:
jdbc:
initialize-schema: always
vectorstore:
qdrant:
host: 127.0.0.1 # 服务器地址
port: 6334 # gRPC 端口
collection-name: rag_knowledge # 集合名称
initialize-schema: true # 自动创建集合
datasource:
url: jdbc:mysql://localhost:3306/text_sql?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&createDatabaseIfNotExist=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 15
minimum-idle: 5
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
servlet:
encoding:
charset: UTF-8
关键参数说明:
-
initialize-schema: true:让 Spring AI 自动创建 Qdrant 集合,注意:这在 2.0 版本中默认是关闭的,需要显式开启
temperature: 0.1:低温度让 SQL 生成更确定、更准确
数据库元数据抽取器
从数据库中抽取表结构信息,后续会存入向量库供 RAG 检索。
package com.ybw.service;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSON;
import com.ybw.entity.ColumnInfo;
import com.ybw.entity.ForeignKeyInfo;
import com.ybw.entity.TableMetadata;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
@Slf4j
public class EnhancedDatabaseMetadataService {
private final JdbcClient jdbcClient;
private final VectorStore vectorStore;
//数据来源
private final String DATA_SOURCE_KEY = "data_source";
private final String DATA_SOURCE_VALUE = "mysql_text_sql";
/**
* 提取所有表的结构信息,每个表生成一个 Document 用于向量化存储
*/
public List<Document> extractEnhancedTableMetadata() {
try {
//1、MySQL: 获取所有表名
List<String> tables = jdbcClient.sql(
"SELECT table_name FROM information_schema.tables " +
"WHERE table_schema = DATABASE() AND table_type = 'BASE TABLE'")
.query(String.class)
.list();
//2、过滤掉qdrant已存在的表
Set<String> existingTableNames = getQdrantExistingTableNames();
List<String> newTables = tables.stream()
.filter(t -> !existingTableNames.contains(t))
.toList();
log.info("Found {} tables in database, {} new ({} already in Qdrant)",
tables.size(), newTables.size(), tables.size() - newTables.size());
//3、只对新增的表构建Document
return buildDocument(newTables);
} catch (Exception e) {
log.error("Error extracting table metadata", e);
}
return new ArrayList<>();
}
/**
* 查询 Qdrant 中已索引的表名集合
*/
private Set<String> getQdrantExistingTableNames() {
try {
// 使用通用查询检索所有表元数据文档,topK 设置较大值确保覆盖全部表
List<Document> existingDocs = vectorStore.similaritySearch(
SearchRequest.builder()
.query("表名 列信息 字段")
.topK(10000)
.similarityThreshold(0.0)
.filterExpression(new FilterExpressionBuilder().eq(DATA_SOURCE_KEY, DATA_SOURCE_VALUE).build())
.build()
);
Set<String> tableNames = existingDocs.stream()
.map(doc -> (String) doc.getMetadata().get("table_name"))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
log.info("Qdrant already has {} indexed tables: {}", tableNames.size(), tableNames);
return tableNames;
} catch (Exception e) {
log.warn("Failed to query Qdrant for existing tables, will index all", e);
return Set.of();
}
}
/**
* 构建Document
*
* @param tables 表名列表
* @methodName: buildDocument
* @return: java.util.List<org.springframework.ai.document.Document>
* @author: ybw
* @date: 2026/7/19
**/
private List<Document> buildDocument(List<String> tables) {
//1、如果没有表,则返回空列表
if (CollectionUtil.isEmpty(tables)) {
return new ArrayList<>();
}
List<Document> documents = new ArrayList<>();
tables.forEach(tableName -> {
try {
//2、获取表结构信息
TableMetadata tableMeta = getTableMetadata(tableName);
log.info("Indexed table: {},metadata: {} )",
tableName, JSON.toJSONString(tableMeta));
//2.1 序列化为 JSON 存入 metadata
String metadataJson = JSON.toJSONString(tableMeta);
//2.2 构建用于向量检索的自然语言描述
String content = buildTableDescription(tableName, tableMeta.tableComment(), tableMeta.columns(), tableMeta.foreignKeys());
//3、 构建 Document 并存入 documents
Map<String, Object> metadata = Map.of("metadata", metadataJson, "table_name", tableName, DATA_SOURCE_KEY, DATA_SOURCE_VALUE);
Document doc = new Document(content, metadata);
documents.add(doc);
} catch (Exception e) {
log.error("Error processing table: {}", tableName, e);
}
});
return documents;
}
/**
* 获取表结构信息
*
* @param tableName 表名
* @methodName: getTableMetadata
* @return: com.ybw.entity.TableMetadata
* @author: ybw
* @date: 2026/7/19
**/
public TableMetadata getTableMetadata(String tableName) {
//1、 MySQL: 获取表注释
String tableComment = jdbcClient.sql(
"SELECT table_comment FROM information_schema.tables " +
"WHERE table_schema = DATABASE() AND table_name = ?")
.param(tableName)
.query(String.class)
.optional()
.orElse("");
//2、获取列信息(MySQL 版本)
List<ColumnInfo> columns = jdbcClient.sql(
"""
SELECT
column_name,
data_type,
is_nullable,
column_default,
column_comment
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = ?
ORDER BY ordinal_position
""")
.param(tableName)
.query((rs, rowNum) -> new ColumnInfo(
rs.getString("column_name"),
rs.getString("data_type"),
rs.getString("is_nullable"),
rs.getString("column_default"),
rs.getString("column_comment")
))
.list();
//3、获取外键关系(MySQL 版本)
List<ForeignKeyInfo> foreignKeys = jdbcClient.sql(
"""
SELECT
kcu.column_name,
kcu.referenced_table_name,
kcu.referenced_column_name
FROM information_schema.key_column_usage kcu
WHERE kcu.table_schema = DATABASE()
AND kcu.table_name = ?
AND kcu.referenced_table_name IS NOT NULL
""")
.param(tableName)
.query((rs, rowNum) -> new ForeignKeyInfo(
rs.getString("column_name"),
rs.getString("referenced_table_name"),
rs.getString("referenced_column_name")
))
.list();
//4、构建 TableMetadata 并创建 Document
return new TableMetadata(
tableName, tableComment, columns, foreignKeys);
}
/**
* 构建表的自然语言描述,用于向量相似度检索
*/
private String buildTableDescription(String tableName, String tableComment,
List<ColumnInfo> columns, List<ForeignKeyInfo> foreignKeys) {
StringBuilder sb = new StringBuilder();
sb.append("表名: ").append(tableName);
if (tableComment != null && !tableComment.isEmpty()) {
sb.append(" (").append(tableComment).append(")");
}
sb.append("\n");
sb.append("列信息:\n");
for (ColumnInfo col : columns) {
sb.append(" - ").append(col.name())
.append(": ").append(col.type());
if ("NO".equals(col.nullable())) {
sb.append(" NOT NULL");
}
if (col.defaultValue() != null) {
sb.append(" 默认值=").append(col.defaultValue());
}
if (col.comment() != null && !col.comment().isEmpty()) {
sb.append(" -- ").append(col.comment());
}
sb.append("\n");
}
if (!foreignKeys.isEmpty()) {
sb.append("外键关系:\n");
for (ForeignKeyInfo fk : foreignKeys) {
sb.append(" - ").append(fk.columnName())
.append(" -> ").append(fk.referencedTable())
.append(".").append(fk.referencedColumn())
.append("\n");
}
}
return sb.toString();
}
}
RAG 元数据索引器
将数据库表结构向量化后存入 Qdrant,供后续检索。
package com.ybw.config;
import cn.hutool.core.collection.CollectionUtil;
import com.ybw.service.EnhancedDatabaseMetadataService;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
@RequiredArgsConstructor
public class MetadataIndexer {
private final VectorStore vectorStore;
private final EnhancedDatabaseMetadataService metadataService;
/**
* 启动时,将表结构索引到向量库
*
* @methodName: init
* @return: void
* @author: ybw
* @date: 2026/7/19
**/
@PostConstruct
public void init() {
indexMetadata();
}
/**
* 启动时或手动调用,将表结构索引到向量库
*/
public void indexMetadata() {
//1、获取表结构信息
List<Document> documents = metadataService.extractEnhancedTableMetadata();
if (CollectionUtil.isEmpty(documents)) {
log.info("没有新表需要保存到向量数据库");
return;
}
//2、将表结构信息保存到向量库
vectorStore.add(documents);
log.info("Indexed {} tables to Qdrant", documents.size());
}
}
数据库元数据 Advisor(核心 RAG 逻辑)
Advisor 在每次请求前拦截,从 Qdrant 检索相关表结构,注入到 Prompt 中。
package com.ybw.advisor;
import org.jspecify.annotations.NonNull;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.content.Content;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.Ordered;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class DatabaseMetadataAdvisor implements BaseAdvisor {
private static final String SYSTEM_TEMPLATE = """
你是一个 SQL 专家。请根据下面的数据库表结构,生成可执行的 SQL 语句来回答用户问题。
只生成 SELECT 查询,不要执行 DDL/DML 操作。
===可用的表结构===
{table_schemas}
""";
private final VectorStore vectorStore;
public DatabaseMetadataAdvisor(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
@Override
public ChatClientRequest before(@NonNull ChatClientRequest request, @NonNull AdvisorChain chain) {
//1、从用户消息中提取查询文本
String userQuery = extractUserQuery(request);
//2、从 Qdrant 检索相关表结构(topK=3)
List<Document> relevantTables = vectorStore.similaritySearch(
SearchRequest.builder()
.query(userQuery)
.topK(3)
.similarityThreshold(0.5)
.build()
);
//3、构建表结构信息
String tableSchemas = relevantTables.stream()
.map(doc -> doc.getMetadata().get("metadata").toString())
.collect(Collectors.joining("\n\n"));
//4、渲染系统提示
String systemText = new PromptTemplate(SYSTEM_TEMPLATE)
.render(Map.of("table_schemas", tableSchemas));
//5、更新请求
return request.mutate()
.prompt(request.prompt().augmentSystemMessage(systemText))
.build();
}
/**
* 从用户消息中提取查询文本
*
* @param request 请求
* @methodName: extractUserQuery
* @return: java.lang.String
* @author: ybw
* @date: 2026/7/19
**/
private String extractUserQuery(ChatClientRequest request) {
return request.prompt().getInstructions().stream()
.filter(msg -> msg instanceof UserMessage)
.map(Content::getText)
.findFirst()
.orElse("");
}
@Override
public ChatClientResponse after(@NonNull ChatClientResponse response, @NonNull AdvisorChain chain) {
return response;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
SQL 执行工具(Tool Calling)
让 LLM 可以调用工具执行生成的 SQL 并返回结果。Spring AI 2.0 使用 @Tool 注解定义工具。
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 运行 SQL 查询工具
*
* @author ybw
* @version V1.0
* @className RunSqlQueryTool
* @date 2026/7/19
**/
@Component
@RequiredArgsConstructor
@Slf4j
public class RunSqlQueryTool {
private final JdbcClient jdbcClient;
@Tool(description = "执行 SQL 查询并返回结果,以 CSV 格式输出")
public String runQuery(RunSqlQueryRequest request) {
try {
//1、执行sql查询
String sql = request.query();
log.info("sql: {}", sql);
List<Map<String, Object>> rows = jdbcClient.sql(sql)
.query()
.listOfRows();
if (rows.isEmpty()) {
return "查询结果为空";
}
//2、简单格式化为 CSV
StringBuilder sb = new StringBuilder();
//2.1 表头
sb.append(String.join(",", rows.getFirst().keySet())).append("\n");
//2.2 数据行
rows.forEach(row -> {
sb.append(row.values().stream()
.map(String::valueOf)
.collect(Collectors.joining(",")))
.append("\n");
});
return sb.toString();
} catch (Exception e) {
return "SQL 执行错误: " + e.getMessage();
}
}
public record RunSqlQueryRequest(String query) {
}
}
ChatClient 配置
package com.ybw.config;
import com.ybw.advisor.DatabaseMetadataAdvisor;
import com.ybw.util.RunSqlQueryTool;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository;
import org.springframework.ai.ollama.api.OllamaChatOptions;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* 配置 ChatClient
*
* @author ybw
* @version V1.0
* @className ChatClientConfig
* @date 2026/6/29
**/
@Configuration
public class ChatClientConfig {
/**
* 工具大模型:绑定工具
*
* @param builder 构建 ChatClient
* @param vectorStore 向量库
* @param toolService 工具服务
* @methodName: toolChatClient
* @return: org.springframework.ai.chat.client.ChatClient
* @author: ybw
* @date: 2026/7/8
**/
@Bean("toolChatClient")
ChatClient toolChatClient(ChatClient.Builder builder, VectorStore vectorStore, RunSqlQueryTool toolService) {
OllamaChatOptions.Builder ollamaChatOptions = OllamaChatOptions.builder()
.model("qwen3.5:4b")
.disableThinking()
.temperature(0.4);
return builder
.defaultSystem("你是 SQL 专家")
//绑定工具
.defaultTools(toolService)
.defaultAdvisors(new DatabaseMetadataAdvisor(vectorStore))
.defaultOptions(ollamaChatOptions)
.build();
}
}
测试
package com.ybw.service;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author ybw
* @version V1.0
* @className TextToSqlTest
* @date 2026/7/19
**/
@SpringBootTest
@Slf4j
public class TextToSqlTest {
@Resource(name = "toolChatClient")
private ChatClient chatClient;
/**
* 查询所有用户订单数量
*/
@Test
public void testTextToSql() {
String question = "查询所有用户订单数量";
String result = chatClient.prompt()
.user(question)
.call()
.content();
log.info("result: {}", result);
}
/**
* 查询订单总金额超过5000的订单
*/
@Test
public void testTextToSql2() {
String question = "查询订单总金额超过5000的订单";
String result = chatClient.prompt()
.user(question)
.call()
.content();
log.info("result: {}", result);
}
/**
* 查看张伟的所有订单
*/
@Test
public void testTextToSql3() {
String question = "查看张伟的所有订单";
String result = chatClient.prompt()
.user(question)
.call()
.content();
log.info("result: {}", result);
}
/**
* 统计每个商品分类下的商品数量
*/
@Test
public void testTextToSql4() {
String question = "统计每个商品分类下的商品数量";
String result = chatClient.prompt()
.user(question)
.call()
.content();
log.info("result: {}", result);
}
}
5、补充
阿里相关框架
Spring AI Alibaba中的Data Agent。
- 基于 Spring AI Alibaba 的自然语言转 SQL 项目,让您可以直接使用自然语言查询数据库,无需编写复杂的 SQL。