偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

用 LangChain4j+Ollama 打造 Text-to-SQL AI Agent,數(shù)據(jù)庫想問就問

人工智能
如今用 Java 在本地折騰大語言模型其實特簡單。輕量級模型加上用熟悉的語言寫的結(jié)構(gòu)清晰的代碼,讓這些想法快速原型化又快又好玩!

想象一下:老板拿著 Excel 表問你 “咱們家最暢銷的三款產(chǎn)品是啥?”,你卻對著復(fù)雜的 SQL 查詢語句抓頭發(fā) —— 這種尷尬場面,以后有 AI 幫忙就再也不會出現(xiàn)啦!

其實很多職場人(甚至有些開發(fā))都搞不定 SQL,更別說非技術(shù)出身的管理者了。要是能直接用大白話問數(shù)據(jù)庫問題,比如 “誰買的東西最多?”“哪個產(chǎn)品最賺錢?”,那工作效率不得直接起飛?

先給大家看個實際效果:

問題:咱們最暢銷的三款產(chǎn)品是啥?

AI 給出的答案:根據(jù)數(shù)據(jù)庫數(shù)據(jù),top3 暢銷產(chǎn)品是:

筆記本電腦 —— 賣了 3 臺

鼠標(biāo) —— 賣了 2 臺

鍵盤 —— 賣了 2 臺

這三款產(chǎn)品的銷量都在 2 臺以上,是目前的銷量冠軍。

不過先說明哈:這方法不算 “最優(yōu)解”,主要是帶大家玩一玩,感受下 LLM(大語言模型)的能力。畢竟先摸清 AI 的 “脾氣”,才能解鎖更多騷操作嘛~(要是真要上生產(chǎn)環(huán)境,還是得用 MCP 或者 Langchain4j 的 @Tool 工具調(diào)用功能,專業(yè)的事得靠專業(yè)工具!)

1.核心思路:讓 AI 當(dāng)你的 “SQL 翻譯官”

整個流程其實超簡單,就像 “傳話游戲”,只不過中間多了個 AI 幫忙:

你用大白話提問題(比如 “最暢銷的產(chǎn)品是啥?”)

給 Ollama 模型(一款本地能跑的 AI)看數(shù)據(jù)庫的 “結(jié)構(gòu)說明書”,讓它生成對應(yīng)的 SQL 查詢語句

用這個 SQL 去數(shù)據(jù)庫里撈數(shù)據(jù)

再把撈到的數(shù)據(jù)和你的問題一起丟給 AI,讓它給你一個人話版答案

全程沒什么 “黑科技”,就是靠 AI 幫你搞定 “把大白話轉(zhuǎn)成 SQL” 和 “把數(shù)據(jù)轉(zhuǎn)成人話” 這兩步,咱們純屬邊玩邊學(xué)~

2.準(zhǔn)備工作:先搭好 “工具箱”

在寫代碼之前,得先把需要的工具準(zhǔn)備好,就像做飯前要先買菜一樣:

  • 本地起個 PostgreSQL 數(shù)據(jù)庫(用 Docker 超方便)

復(fù)制下面這行命令,在終端里敲一下,就能自動下載并啟動數(shù)據(jù)庫,連安裝步驟都省了:

docker run --name tts-db \
 -e POSTGRES_USER=tts-user \
 -e POSTGRES_PASSWORD=tts-pass \
 -e POSTGRES_DB=tts-db \
 -p 5432:5432 -d postgres

給大家劃重點:

數(shù)據(jù)庫名:tts-db

用戶名:tts-user

密碼:tts-pass

記好這三個信息,后面要用到!

  • 裝 Ollama(本地跑 AI 模型的工具)

直接去官網(wǎng)下載:https://ollama.com/download,跟裝微信一樣簡單。

裝完后,在終端里敲一句

ollama run llama3.2:1b

就能啟動目前最小的 Ollama 模型(才 1.3GB)。雖然這模型不算 “聰明”,但勝在跑得快,練手剛好合適~ 后面想換厲害的模型,直接換個命令就行(官網(wǎng)有各種免費模型可選)。

  • 建個 Java 項目(用 Gradle 舉例)

先建個普通的 Java Gradle/Maven 項目,然后在build.gradle里加三個依賴(相當(dāng)于給項目裝 “插件”):

implementation 'org.postgresql:postgresql:42.7.7'
implementation 'dev.langchain4j:langchain4j:1.0.1'
implementation 'dev.langchain4j:langchain4j-ollama:1.0.1-beta6'

3.動手實操:一步步搭起 “AI 查數(shù)據(jù)庫” 功能

準(zhǔn)備工作做完,就到最有意思的環(huán)節(jié)了!咱們一步步來,別怕,代碼都給你寫好了~

第一步:給數(shù)據(jù)庫填點 “測試數(shù)據(jù)”

先建個init.sql文件(放在src/main/resources文件夾下),里面寫好建表和插數(shù)據(jù)的 SQL。我讓 ChatGPT 幫我編了個簡化的電商數(shù)據(jù)庫,大家直接用就行:

-- 先刪舊表,避免重復(fù)
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS customers;
-- 顧客表
CREATE TABLE customers (
    id INT PRIMARY KEY,
    name VARCHAR(100),  -- 姓名
    email VARCHAR(100), -- 郵箱
    city VARCHAR(50)    -- 城市
);
-- 產(chǎn)品表
CREATE TABLE products (
    id INT PRIMARY KEY,
    name VARCHAR(100),  -- 產(chǎn)品名
    price DECIMAL(8,2)  -- 價格
);
-- 訂單表(記錄誰買了啥)
CREATE TABLE orders (
    id INT PRIMARY KEY,
    customer_id INT,    -- 關(guān)聯(lián)顧客表
    product_id INT,     -- 關(guān)聯(lián)產(chǎn)品表
    quantity INT,       -- 購買數(shù)量
    order_date DATE,    -- 下單日期
    FOREIGN KEY (customer_id) REFERENCES customers(id),
    FOREIGN KEY (product_id) REFERENCES products(id)
);
-- 插點測試顧客
INSERT INTO customers VALUES
(1, '張三', 'zhangsan@email.com', '北京'),
(2, '李四', 'lisi@email.com', '上海'),
(3, '王五', 'wangwu@email.com', '廣州'),
(4, '趙六', 'zhaoliu@email.com', '深圳'),
(5, '錢七', 'qianqi@email.com', '杭州');
-- 插點測試產(chǎn)品
INSERT INTO products VALUES
(1, '筆記本電腦', 9999.99),
(2, '鼠標(biāo)', 99.99),
(3, '鍵盤', 299.99),
(4, '顯示器', 1999.99),
(5, '耳機', 799.99);
-- 插點測試訂單
INSERT INTO orders VALUES
(1, 1, 1, 1, '2024-01-15'),  -- 張三買1臺筆記本
(2, 2, 2, 2, '2024-01-16'),  -- 李四買2個鼠標(biāo)
(3, 1, 3, 1, '2024-01-17'),  -- 張三買1個鍵盤
(4, 3, 1, 1, '2024-01-18'),  -- 王五買1臺筆記本
(5, 4, 4, 1, '2024-01-19'),  -- 趙六買1個顯示器
(6, 2, 5, 1, '2024-01-20'),  -- 李四買1個耳機
(7, 5, 2, 3, '2024-01-21'),  -- 錢七買3個鼠標(biāo)
(8, 1, 4, 1, '2024-01-22'),  -- 張三買1個顯示器
(9, 3, 3, 1, '2024-01-23'),  -- 王五買1個鍵盤
(10, 4, 2, 1, '2024-01-24'); -- 趙六買1個鼠標(biāo)

有了這些數(shù)據(jù),就能問 “誰買的鼠標(biāo)最多?”“筆記本賣了多少臺” 這類問題啦~

第二步:寫個 “數(shù)據(jù)庫管理器”(幫你連數(shù)據(jù)庫 + 初始化數(shù)據(jù))

建個DatabaseManager類,負責(zé)兩件事:連數(shù)據(jù)庫,以及執(zhí)行上面的init.sql文件初始化數(shù)據(jù)。代碼里都加了注釋,看不懂的地方看注釋就行:

package com.tsvetkov.db;


import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;


public class DatabaseManager {
    // 數(shù)據(jù)庫連接信息(就是之前讓你記的那三個?。?    private static final String URL = "jdbc:postgresql://localhost:5432/tts-db";
    private static final String USER = "tts-user";
    private static final String PASSWORD = "tts-pass";


    // 連數(shù)據(jù)庫并初始化表和數(shù)據(jù)
    public static Connection getInitialConnection() throws SQLException, IOException {
        // 1. 連數(shù)據(jù)庫
        Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
        System.out.println("成功連到PostgreSQL啦!");


        // 2. 讀init.sql文件,執(zhí)行里面的SQL
        return new String(Files.readAllBytes(Paths.get("src/main/resources/init.sql")));
    }

第三步:初始化 AI 助手(讓 Ollama 模型跑起來)

建個AIUtils類,負責(zé)啟動 Ollama 模型,相當(dāng)于給你找個 “AI 小助手”:

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.ollama.OllamaChatModel;
import dev.langchain4j.service.AiServices;
public class AIUtils {
    // 生成AI助手(能幫你轉(zhuǎn)SQL、寫答案)
    public static AIAssistant getAIAssistant() {
        return AiServices.builder(AIAssistant.class)
                .chatModel(initOllamaChatModel()) // 連Ollama模型
                .build();
    }
    // 啟動Ollama的llama3.2:1b模型
    private static ChatModel initOllamaChatModel() {
        return OllamaChatModel.builder()
                .baseUrl("http://localhost:11434") // Ollama默認地址
                .modelName("llama3.2:1b") // 用的模型名
                .build();
    }
}

第四步:定義 AI 助手的 “能力”(讓它知道該干啥)

建個AIAssistant接口,用注解告訴 AI:“你要幫我把大白話轉(zhuǎn)成 SQL”“你要幫我把數(shù)據(jù)轉(zhuǎn)成人話”。這就像給 AI 發(fā) “任務(wù)清單”:

import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
public interface AIAssistant {
    @UserMessage("""
      You are a senior SQL engineer. Given the database schema and user question below, write a syntactically correct and schema-valid SQL SELECT query.
      Database Schema (Use only columns and tables listed here)
      {{schemaDescription}}
      Rules:
      Only valid syntax queries - meaning it must start with SELECT
      Only use tables and columns from the schema above — do not guess
      Only use SELECT statements (no INSERT, UPDATE, DELETE)
      Use explicit JOINs, not subqueries unless necessary
      Add LIMIT 100 to large result sets if not specified in the question
      Use aggregate functions (COUNT, SUM, etc.) only if the question requires it
      Return only the SQL query, no explanation, no comments
      The query must be valid SQL and executable without syntax errors
      User Question
      {{question}}
    """)
    String getQuery(@V("question") String question,  @V("schema") String schemaDescription);
}

第五步:寫個 “數(shù)據(jù)庫結(jié)構(gòu)分析器”(給 AI 看 “說明書”)

AI 得知道數(shù)據(jù)庫里有哪些表、每個表有哪些字段,才能寫出正確的 SQL。所以建個SchemaAnalyzer類,把數(shù)據(jù)庫結(jié)構(gòu)整理成 AI 能看懂的格式(還會加幾條測試數(shù)據(jù)當(dāng)例子):

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
// 存字段信息(字段名+字段類型)
record ColumnInfo(String name, String type) {}
public class SchemaAnalyzer {
    // 每個表只取3條數(shù)據(jù)當(dāng)例子,太多了AI看不過來
    private static final int EXAMPLES_LIMIT = 3;
    // 生成數(shù)據(jù)庫結(jié)構(gòu)描述(給AI看的“說明書”)
    public static String getSchemaDescription(Connection connection) throws SQLException {
        if (connection == null) {
            throw new IllegalArgumentException("數(shù)據(jù)庫沒連上,別瞎傳!");
        }
        StringBuilder schemaDesc = new StringBuilder();
        // 1. 先獲取所有表名
        List<String> tables = getTables(connection);
        // 2. 逐個表整理結(jié)構(gòu)和示例數(shù)據(jù)
        for (String table : tables) {
            schemaDesc.append("表名:").append(table).append("\n");
            schemaDesc.append(getTableDetail(connection, table));
        }
        return schemaDesc.toString();
    }
    // 整理單個表的結(jié)構(gòu)(字段名+類型)和示例數(shù)據(jù)
    private static String getTableDetail(Connection connection, String table) throws SQLException {
        StringBuilder detail = new StringBuilder();
        // 獲取表的所有字段
        List<ColumnInfo> columns = getColumns(connection, table);
        for (ColumnInfo col : columns) {
            detail.append("  - ").append(col.name()).append("(類型:").append(col.type()).append(")\n");
        }
        // 獲取表的示例數(shù)據(jù)
        List<List<String>> sampleRows = getSampleRows(connection, table, columns);
        // 把示例數(shù)據(jù)寫成表格格式,AI看得更清楚
        detail.append("示例數(shù)據(jù):\n");
        detail.append("  | ");
        for (ColumnInfo col : columns) {
            detail.append(col.name()).append(" | ");
        }
        detail.append("\n");
        for (List<String> row : sampleRows) {
            detail.append("  | ");
            for (String value : row) {
                detail.append(value).append(" | ");
            }
            detail.append("\n");
        }
        return detail.toString();
    }
    // 獲取數(shù)據(jù)庫里的所有表名
    private static List<String> getTables(Connection connection) throws SQLException {
        List<String> tables = new ArrayList<>();
        try (ResultSet rs = connection.getMetaData().getTables(null, "public", "%", new String[]{"TABLE"})) {
            while (rs.next()) {
                tables.add(rs.getString("TABLE_NAME"));
            }
        }
        return tables;
    }
    // 獲取單個表的所有字段
    private static List<ColumnInfo> getColumns(Connection connection, String table) throws SQLException {
        List<ColumnInfo> columns = new ArrayList<>();
        try (ResultSet rs = connection.getMetaData().getColumns(null, "public", table, "%")) {
            while (rs.next()) {
                columns.add(new ColumnInfo(
                        rs.getString("COLUMN_NAME"),  // 字段名
                        rs.getString("TYPE_NAME")     // 字段類型
                ));
            }
        }
        return columns;
}        
   private static List<List<String>> getSampleRows(Connection connection, String table, List<ColumnInfo> columns) throws SQLException {
        var rows = new ArrayList<List<String>>();
        try (ResultSet rs = connection.createStatement().executeQuery("SELECT * FROM \"" + table + "\" LIMIT " + EXAMPLES_LIMIT)) {
            while (rs.next()) {
                var row = new ArrayList<String>();
                for (ColumnInfo col : columns) {
                    row.add(rs.getString(col.name()));
                }
                rows.add(row);
            }
        }
        return rows;
    }
 }

咱們的操作邏輯其實特簡單:先搞清楚數(shù)據(jù)庫里有哪些表,每張表有啥字段、字段是啥類型,然后按照剛才說的格式生成描述,再從每張表里挑幾條數(shù)據(jù)當(dāng)例子 —— 齊活!

舉個例子,要是調(diào)用 AIAssistant 的 getQuery 方法,問 “產(chǎn)品目錄里最貴的是啥?”,AI 可能會返回這樣的 SQL:

SELECT T1.price FROM products AS T1 INNER JOIN orders AS T2 ON T1.id= T2.product_id GROUP BY T1.price ORDER BY SUM(T1.price) DESC LIMIT 1
執(zhí)行數(shù)據(jù)庫查詢

下一步就是執(zhí)行這個查詢,把結(jié)果撈出來。咱先簡單校驗下 SQL 語法,不用搞太復(fù)雜的操作。給 DatabaseManager 類加個 validateQuery 方法就行:

public static void validateQuery(String sql) {
    // 基礎(chǔ)校驗——只允許SELECT語句
    String upperSql = sql.toUpperCase().trim();
    if(!upperSql.startsWith("SELECT") ||
            upperSql.contains("DROP") ||  // 不準(zhǔn)刪表
            upperSql.contains("DELETE") || // 不準(zhǔn)刪數(shù)據(jù)
            upperSql.contains("INSERT") || // 不準(zhǔn)插數(shù)據(jù)
            upperSql.contains("UPDATE") || // 不準(zhǔn)改數(shù)據(jù)
            upperSql.contains("ALTER") ||  // 不準(zhǔn)改表結(jié)構(gòu)
            upperSql.contains("CREATE")) { // 不準(zhǔn)建表
        throw new RuntimeException("SQL有問題,不能執(zhí)行:\n"+sql);
    }
}

校驗通過后,就可以執(zhí)行查詢,然后把結(jié)果整理一下。

比如問 “能列出所有產(chǎn)品和它們的價格不?”

模型可能會返回這樣的查詢:

SELECT p.name, p.price FROM products p ORDER BY p.price LIMIT 100

我希望結(jié)果能長成這樣:

name       | price
Mouse      | 25.99
Keyboard   | 75.99
Headphones | 149.99
Monitor    | 299.99
Laptop     | 999.99

咱們來實現(xiàn)個方法干這事兒。這個方法接收模型生成的 SQL 查詢和數(shù)據(jù)庫連接作為參數(shù),就叫 getFormattedResultsFromQuery:

private static String getFormattedResultsFromQuery(Connection conn, String sql) throws SQLException {
    DatabaseManager.validateQuery(sql);
    var resultsDescription = new StringBuilder();
    try (Statement stmt = conn.createStatement();
         ResultSet rs = stmt.executeQuery(sql)) {
        int columnCount = rs.getMetaData().getColumnCount();
        // 先拼表頭
        for (int i = 1; i <= columnCount; i++) {
            resultsDescription.append(rs.getMetaData().getColumnName(i));
            if (i < columnCount) resultsDescription.append(" | ");
        }
        resultsDescription.append("\n");
        // 再拼數(shù)據(jù)行
        while (rs.next()) {
            for (int i = 1; i <= columnCount; i++) {
                resultsDescription.append(rs.getString(i));
                if (i < columnCount) resultsDescription.append(" | ");
            }
            resultsDescription.append("\n");
        }
    }
    return resultsDescription.toString();
}
用 SQL 查詢結(jié)果生成最終答案

現(xiàn)在可以給模型定義個模板,把數(shù)據(jù)庫結(jié)構(gòu)、查詢結(jié)果和用戶的問題一起傳過去 —— 這樣模型應(yīng)該就能給出靠譜的答案了。給 AIAssistant 類加個這玩意兒:

@UserMessage("""
      You are a data analyst. Based on the database schema, the user’s question, the SQL query and the SQL query results, generate a clear, concise, human-readable answer.
      Focus on answering the user's question directly using the data provided — do not describe the SQL or repeat the table structure. Give a straight answer.
      ### Database Schema
      {{schema}}
      ### User Question
      {{question}}
      ### SQL Query
      {{ query }}
      ### SQL Query Results
      {{results}}
      ### Answer
    """)
    String explainAnswer(@V("question") String question, @V("schema") String schema, @V("results") String results);

搞定啦!咱來捋一捋:

  1. 用戶提個問題
  2. 生成數(shù)據(jù)庫結(jié)構(gòu)描述
  3. 把問題和結(jié)構(gòu)描述傳給模型
  4. 模型生成能回答用戶問題的 SQL 查詢
  5. 校驗查詢
  6. 執(zhí)行查詢
  7. 整理結(jié)果
  8. 把結(jié)果、數(shù)據(jù)庫結(jié)構(gòu)和用戶問題傳給模型
  9. 得到人話版答案

咱們把這些零件組裝到一個簡單的 main 方法里:

public static void main(String[] args) throws SQLException, IOException {
        System.out.println("Starting our text-to-sql application...");
        AIAssistant aiAssistant = AIUtils.getAIAssistant();
        try (Connection conn = DatabaseManager.getInitialConnection()) {
            String schemaDescription = SchemaAnalyzer.getSchemaDescription(conn);
            String question = "What are our top 3 most ordered products?";
            String query = aiAssistant.getQuery(question, schemaDescription);
            String formattedQueryResults = getFormattedResultsFromQuery(conn, query);
            String humanAnswer = aiAssistant.explainAnswer(question, schemaDescription, query, formattedQueryResults);
            System.out.println(humanAnswer);
        }
}

最后,運行這個例子,會得到類似這樣的結(jié)果:

啟動咱們的文本轉(zhuǎn)SQL應(yīng)用...
成功連到PostgreSQL啦!
數(shù)據(jù)庫表和數(shù)據(jù)都初始化好咯~
張三總共下了3單。

得說明一下,用我這個例子里的模型,結(jié)果不一定總能完美無缺。不過用來瞎折騰折騰夠夠的了,而且試試不同的模型模板,看啥樣能讓模型給出更好的答案,還挺有意思的。要是用更厲害的模型,幾乎每次都能得到不錯的結(jié)果。

你可能還會發(fā)現(xiàn),模型生成的查詢經(jīng)常有問題,所以整個重試邏輯是個不錯的練習(xí)。玩得開心就好!

總結(jié)

如今用 Java 在本地折騰大語言模型其實特簡單。輕量級模型加上用熟悉的語言寫的結(jié)構(gòu)清晰的代碼,讓這些想法快速原型化又快又好玩!就像文章開頭說的,這個方法不算優(yōu)化,也不適合實際生產(chǎn)應(yīng)用,但它基于 AI 代理的理念,簡化展示了底層的工作原理。

如果需要直接用Text-To-SQL的框架請移步我們開源的基于Spring AI實現(xiàn)的Java的Text-To-SQL框架SuperSQL

責(zé)任編輯:武曉燕 來源: HELLO程序員
相關(guān)推薦

2025-05-07 01:01:00

JavaQuarkusAI

2024-08-01 13:12:57

2025-03-31 00:44:00

JavaAI開發(fā)

2025-10-24 10:58:24

智能體大語言模型LLM

2025-10-10 09:03:18

2025-04-22 03:00:00

模型SpringAI

2017-07-28 15:12:28

Neo4j圖數(shù)據(jù)庫

2025-05-22 02:00:00

AI人工智能前端

2024-08-02 08:00:00

2025-03-11 00:25:00

組件接口工具

2010-06-30 08:27:45

SQL Server數(shù)

2025-05-07 08:14:58

2023-06-06 14:16:06

ChatGPTAI

2024-12-13 08:32:28

向量數(shù)據(jù)庫云原生LangChain

2010-05-05 11:17:55

Oracle數(shù)據(jù)庫

2025-01-14 10:29:34

2010-05-14 14:12:58

MySQL數(shù)據(jù)庫優(yōu)化

2016-03-17 21:35:48

2023-10-12 08:59:52

Docker AI生成式 AI

2011-08-29 15:40:00

SQL Server獲取TEXT字段的內(nèi)容DATALENGTH
點贊
收藏

51CTO技術(shù)棧公眾號