0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

簡単なRAGを作成して動かしてみる(Windows + Eclipse + Java + Spring AI + PostgreSQL/pgvector + Google Gemini API)

0
Posted at

お試しで簡単な RAG を作ってみたので、その時の手順を残します。LLMとして、ローカルLLMではなくGoogle Gemini API経由のGeminiモデルを使用しました。

1. 環境

  • Windows 10
  • Eclipse(Pleiades 2026-06 (4.40.0) )
  • Java 21
  • Spring AI 2.0.0
  • PostgreSQL 18.6 + pgvector
  • Google Gemini API

2. 構成

今回作成した簡単な RAG の構成は以下となります。

Windows
│
├─ Eclipse
│   └─ Java 21
│       └─ Spring Boot
│           └─ Spring AI
│               │
│               ├── Gemini API
│               │      ├── Chat
│               │      └── Embedding
│               │
│               └── PostgreSQL + pgvector
│
└─ PDF
    ├─ postgresql-18.0-release-notes.pdf
    ├─ postgresql-18.1-release-notes.pdf
    ├─ postgresql-18.2-release-notes.pdf
    ├─ postgresql-18.3-release-notes.pdf
    ├─ postgresql-18.4-release-notes.pdf
    └─ postgresql-18.6-release-notes.pdf

3. 完成形の概要

http://localhost:8080/rag?question=PostgreSQL%2018.0から18.6への主な変更点は?
へアクセスし、Gemini から回答を得ること。
(%20は半角スペースです。)

今回動作させる RAG の内容は以下となります。

(1) PostgreSQLのPDFを読み込む
    リリースノートや公式マニュアル
    ↓
(2) 文章を小さく分割する
    検索しやすい単位(チャンク)に分ける
    ↓
(3) Geminiで文章を数値化する
    Embedding:文章の意味をベクトルで表現する
    ↓
(4) PostgreSQLに保存する
    pgvectorで文章の意味が近いものを検索できるようにする
    ↓
(5) 質問すると、関連資料を検索する
    質問に関係する文章を最大5件取得する
    ↓
(6) Geminiが回答を作る
    検索した資料を根拠に、質問への回答を生成する

4. Eclipse(Pleades All in One) のインストール

Java + Spring AI の開発環境として、Eclipse(Pleades All in One)をインストールします。

詳しいインストール手順は「WindowsにEclipse(Pleiades All in One)をインストールする手順」を参照してください。

5. PostgreSQL及びpgvectorのインストール

PostgreSQL 18.6 及び pgvector v0.8.6 をインストールします。

詳しい手順は「PostgreSQL及びpgvectorのインストール手順」を参照してください。

6. Gemini APIキーを取得、環境変数に登録

6.1. Gemini APIキーを取得

「Google AI Studio」のページから GeminiAPIキー を取得します。

「Google AI Studio」のページを開き、左下の「Get API key(鍵マーク)」をクリックします。
6-1.png


右上の「APIキーを作成」をクリックします。

6-2.png


「インポートしたプロジェクトを選択」から「プロジェクト作成」を選択します。

6-3.png


任意のプロジェクト名を入力し(ここでは「testGemini」)、「プロジェクトを作成」をクリックします。

6-4.png


「キーを作成」をクリックします。

6-5.png


「鍵をコピー」をクリックし、クリップボードに鍵をコピーし、テキスト等に張り付けて保管します。

6-6.png


6.2. Gemini APIキーを環境変数に登録

取得した Gemini APIキー を環境変数に登録します。

「Windowsボタン + R」で「ファイル名を指定して実行」を開き、sysdm.cplを入力し「OK」をクリックします。

6-7.png


「詳細設定」タブをクリックします。

6-8.png


「環境変数」をクリックします。

6-9.png


ユーザ環境変数の「新規」をクリックします。

6-10.png


「変数名」にGEMINI_API_KEY、「変数値」に先ほどコピーした GeminiのAPIキー を入力し、「OK」をクリックします。

6-11.png


ユーザ環境変数に「GEMINI_API_KEY」が追加されたことを確認し、「OK」をクリックします。

6-12.png


7. Eclipse で Spring AI のプロジェクトを作成

Eclipse を起動し、「ファイル」-「新規」-「Mavenプロジェクト」を選択します。

7-1.png


「シンプルなプロジェクトの作成」をチェックし、「次へ」をクリックします。

7-2.png


グループIdに「com.example」、アーティファクトIdに「postgres-rag」を入力し、「完了」をクリックします。

7-3.png


「postgres-rag」のプロジェクトが作成されていることを確認します。

7-4.png


「postgres-rag」配下の「pom.xml」を開き、以下の「pom.xml」の内容ですべて上書きします。

7-5.png

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="
           http://maven.apache.org/POM/4.0.0
           https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.0.8</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>postgres-rag</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>21</java.version>
        <spring-ai.version>2.0.0</spring-ai.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- PostgreSQL -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Spring AI Google GenAI -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-google-genai</artifactId>
        </dependency>

        <!-- Spring AI Google GenAI Embedding -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-google-genai-embedding</artifactId>
        </dependency>

        <!-- Spring AI PGVector -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
        </dependency>

        <!-- Spring AI PDF -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-pdf-document-reader</artifactId>
        </dependency>

        <!-- Spring AI Tika -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-tika-document-reader</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

        </plugins>
    </build>

</project>

「postgres-rag」プロジェクトを右クリックし、「Maven」-「プロジェクトの更新」を選択します。

7-6.png


「OK」をクリックします。

7-7.png


Javaのバージョンが「21」で、Maven 依存関係にjarファイルが作成されていることを確認します。

7-8.png


8. 全体のクラス構成

ここから簡単な RAG を作成していきます。最終的な全体のパッケージは以下となります。

postgres-rag
  ├─src/main/java
  │   └─ com.example.postgresrag
  │      │
  │      ├─ controller
  │      │   ├─ EmbeddingTestController.java
  │      │   ├─ GeminiTestController.java
  │      │   ├─ PdfIngestController.java
  │      │   └─ RagController.java
  │      │
  │      ├─ service
  │      │   ├─ PdfIngestService.java
  │      │   └─ RagService.java
  │      │
  │      └─ RagApplication.java
  │
  ├src/main/resources
      └─ application.yml

まずは以下のパッケージを作成します。

  • com.example.postgresrag
  • com.example.postgresrag.controller
  • com.example.postgresrag.service

「com.example.postgresrag」パッケージの配下に、以下の「RagApplication.java」を作成します。

RagApplication.java
package com.example.postgresrag;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RagApplication {

    public static void main(String[] args) {
        SpringApplication.run(RagApplication.class, args);
    }
}

次に「src/main/resources」配下に、以下の「application.yml」を作成します。

application.yml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/postgres_rag
    username: postgres
    password: postgres

  ai:
    google:
      genai:
        api-key: ${GEMINI_API_KEY}

        chat:
          model: gemini-3.5-flash-lite

        embedding:
          api-key: ${GEMINI_API_KEY}
          text:
            model: gemini-embedding-001
            dimensions: 768

    vectorstore:
      pgvector:
        initialize-schema: true
        index-type: HNSW
        distance-type: COSINE_DISTANCE
        dimensions: 768

9. GeminiTestController.java で Gemini Chat をテスト

まずは Gemini Chat をテストするために、以下の「GeminiTestController.java」を作成します。

GeminiTestController
package com.example.postgresrag.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GeminiTestController {

    private final ChatClient chatClient;

    public GeminiTestController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @GetMapping("/test")
    public String test() {

        return chatClient
                .prompt()
                .user("PostgreSQLとは何ですか?")
                .call()
                .content();
    }
}

Spring Boot を起動します。

「RagApplication.java」を右クリックし、「実行」-「実行の構成」を選択します。

8-1.png


「Spring Boot アプリケーション」配下に

  • 名前:RagApplication
  • プロジェクト:postgres-rag
  • メイン型:com.example.postgresrag.RagApplication
    を作成し、「実行」をクリックします。

8-2.png


起動するとコンソールログの最終行に以下が表示されると起動成功です。

Started RagApplication in ~~~

起動後に、ブラウザから以下のURLでアクセスします。

http://localhost:8080/test

以下のように「Postgresとは~~」が表示されると成功です。

8-3.png

10. EmbeddingTestController.java で Embedding をテスト

Gemini Embedding のテストをします。

文章
 ↓
Gemini Embedding
 ↓
ベクトル

の動作を確認します。

パッケージ「com.example.postgresrag.controller」配下に、以下の「EmbeddingTestController.java」を作成します。

EmbeddingTestController.java
package com.example.postgresrag.controller;

import java.util.List;

import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EmbeddingTestController {

    private final EmbeddingModel embeddingModel;

    public EmbeddingTestController(
            EmbeddingModel embeddingModel) {
        this.embeddingModel = embeddingModel;
    }

    @GetMapping("/embedding")
    public List<Double> embedding() {

        float[] vector = embeddingModel.embed(
                "PostgreSQLのshared_buffersについて"
        );

        return toList(vector);
    }

    private List<Double> toList(float[] vector) {

        return java.util.stream.IntStream
                .range(0, vector.length)
                .mapToObj(i -> (double) vector[i])
                .toList();
    }
}

Spring Boot を起動し、起動後に、ブラウザから以下のURLでアクセスします。

http://localhost:8080/embedding

以下のように大量のベクトル値が返ってくれば成功です。

10-1.png

11. PDFファイルの格納

ローカルディスク上にPDFファイルを配置するフォルダを作成し、PDFファイルを配置します。

ここでは、以下のURLのページを Chromeブラウザで開き、「印刷」-「PDFで保存」で、PDF化したファイルを使用しました。

URL ファイル名(PDF)
https://www.postgresql.org/docs/release/18.0/ postgresql-18.0-release-notes.pdf
https://www.postgresql.org/docs/release/18.1/ postgresql-18.1-release-notes.pdf
https://www.postgresql.org/docs/release/18.2/ postgresql-18.2-release-notes.pdf
https://www.postgresql.org/docs/release/18.3/ postgresql-18.3-release-notes.pdf
https://www.postgresql.org/docs/release/18.4/ postgresql-18.4-release-notes.pdf
https://www.postgresql.org/docs/release/18.6/ postgresql-18.6-release-notes.pdf

ファイルの配置は以下の通りに配置しました。

C:\apl\postgres-rag\documents
  ├─ postgresql-18.0-release-notes.pdf
  ├─ postgresql-18.1-release-notes.pdf
  ├─ postgresql-18.2-release-notes.pdf
  ├─ postgresql-18.3-release-notes.pdf
  ├─ postgresql-18.4-release-notes.pdf
  └─ postgresql-18.6-release-notes.pdf

12. PDF登録APIの作成

12.1. PdfIngestService.javaの作成

パッケージ「com.example.postgresrag.service」配下に、以下の「PdfIngestService.java」を作成します。

PdfIngestService.java
package com.example.postgresrag.service;

import java.io.File;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.ai.document.Document;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Service;

@Service
public class PdfIngestService {

	private final VectorStore vectorStore;

	public PdfIngestService(VectorStore vectorStore) {
		this.vectorStore = vectorStore;
	}

	
	public int ingest(String filePath) {

		PagePdfDocumentReader reader = new PagePdfDocumentReader(new FileSystemResource(filePath));

		List<Document> documents = reader.read();

		// ファイル名を取得PDFのmetadataを付与
		String fileName = new File(filePath).getName();

		// ファイル名からバージョンを取得
        String version = getVersion(fileName);
        
		// PDFのmetadataを設定
		for (int i = 0; i < documents.size(); i++) {

			Document document = documents.get(i);

			// ファイルパス
			document.getMetadata().put("source", filePath);

			// ファイル名
			document.getMetadata().put("filename", fileName);

			// ファイル種別
			document.getMetadata().put("fileType", "pdf");

			// PDFのページ番号
			document.getMetadata().put("page", i + 1);
			// PostgreSQLバージョン
            document.getMetadata().put("version", version);

		}

		TokenTextSplitter splitter = TokenTextSplitter.builder().build();

		List<Document> chunks = splitter.apply(documents);

		System.out.println("documents = " + documents.size());
		System.out.println("chunks = " + chunks.size());

		vectorStore.add(chunks);

		return chunks.size();
	}
	
	private String getVersion(String fileName) {

		Pattern pattern = Pattern.compile("postgresql-(\\d+\\.\\d+)-release-notes\\.pdf");
		Matcher matcher = pattern.matcher(fileName);

		String version = "";
		if (matcher.matches()) {
			version = matcher.group(1);
		}
		return version;
	}
}

12.2. PdfIngestController.javaの作成

パッケージ「com.example.postgresrag.controller」配下に、以下の「PdfIngestController.java」を作成します。

PdfIngestController.java
package com.example.postgresrag.controller;

import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.example.postgresrag.service.PdfIngestService;

@RestController
public class PdfIngestController {

    private final PdfIngestService pdfIngestService;

    public PdfIngestController(
            PdfIngestService pdfIngestService) {

        this.pdfIngestService = pdfIngestService;
    }

    @GetMapping("/ingest")
    public Map<String, Object> ingest(
            @RequestParam String path) {

        int count =
                pdfIngestService.ingest(path);

        return Map.of(
                "status", "OK",
                "chunks", count
        );
    }
}

12.3. PDFの登録

Spring Bootを再起動し、ブラウザから以下のURLでPDFを登録します。
(URLの%3Aは:、%5Cは\を表しています。)
一度に実行するとGemini APIキーの無料枠の利用制限にひっかかるため、1ファイルずつ時間をあけて実行します。

http://localhost:8080/ingest?path=C%3A%5Capl%5Cpostgres-rag%5Cdocuments%5Cpostgresql-18.0-release-notes.pdf
http://localhost:8080/ingest?path=C%3A%5Capl%5Cpostgres-rag%5Cdocuments%5Cpostgresql-18.1-release-notes.pdf
http://localhost:8080/ingest?path=C%3A%5Capl%5Cpostgres-rag%5Cdocuments%5Cpostgresql-18.2-release-notes.pdf
http://localhost:8080/ingest?path=C%3A%5Capl%5Cpostgres-rag%5Cdocuments%5Cpostgresql-18.3-release-notes.pdf
http://localhost:8080/ingest?path=C%3A%5Capl%5Cpostgres-rag%5Cdocuments%5Cpostgresql-18.4-release-notes.pdf
http://localhost:8080/ingest?path=C%3A%5Capl%5Cpostgres-rag%5Cdocuments%5Cpostgresql-18.6-release-notes.pdf

それぞれの登録で、以下のような画面が表示されると成功です。

12-1.png

13. 質問APIの作成

13.1. RagService.javaの作成

パッケージ「com.example.postgresrag.service」配下に、以下の「RagService.java」を作成します。

RagService.java
/**
 * 検索と回答を担当するService
 */
package com.example.postgresrag.service;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;

@Service
public class RagService {

	private final VectorStore vectorStore;
	private final ChatClient chatClient;

	public RagService(
			VectorStore vectorStore,
			ChatClient.Builder chatClientBuilder) {

		this.vectorStore = vectorStore;
		this.chatClient = chatClientBuilder.build();
	}

	public String ask(String question) {

		List<Document> documents = vectorStore.similaritySearch(
				SearchRequest.builder()
						.query(question)
						.topK(5)
						.build());

		if (documents == null || documents.isEmpty()) {
			return "関連する資料が見つかりませんでした。";
		}

		// 検索結果に出典情報を付けてGeminiへ渡す
		String context = documents.stream()
				.map(doc -> {

					String filename = String.valueOf(doc.getMetadata().get("filename"));

					String version = String.valueOf(doc.getMetadata().get("version"));

					String page = String.valueOf(doc.getMetadata().get("page"));

					return """
							【出典】
							ファイル名: %s
							PostgreSQLバージョン: %s
							ページ: %s

							【本文】
							%s
							""".formatted(
							filename,
							version,
							page,
							doc.getText());
				})
				.collect(Collectors.joining("\n\n---\n\n"));

		String prompt = """
				あなたはPostgreSQLの専門家です。

				以下の資料だけを根拠に質問に回答してください。

				資料に書かれていないことを推測して回答しないでください。

				資料から判断できない場合は、
				「資料からは判断できません」
				と回答してください。

				【回答ルール】
				- 回答の根拠となった資料の出典を必ず記載してください。
				- 出典は「ファイル名 p.ページ」の形式で記載してください。
				- PostgreSQLのバージョンも分かる場合は記載してください。
				- 複数の資料を根拠にした場合は、それぞれの出典を記載してください。
				- 資料にない情報を補足するために、一般知識や推測を使用しないでください。

				【資料】
				%s

				【質問】
				%s
				""".formatted(context, question);

		return chatClient
				.prompt()
				.user(prompt)
				.call()
				.content();
	}
}

13.2. RagController.javaの作成

パッケージ「com.example.postgresrag.controller」配下に、以下の「RagController.java」を作成します。

RagController.java
package com.example.postgresrag.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.example.postgresrag.service.RagService;

@RestController
public class RagController {

    private final RagService ragService;

    public RagController(RagService ragService) {
        this.ragService = ragService;
    }

    @GetMapping("/rag")
    public String ask(
            @RequestParam String question) {

        return ragService.ask(question);
    }
}

14. RAGの実行

Spring Bootを再起動し、
http://localhost:8080/rag?question=PostgreSQL%2018.0から18.6への主な変更点は?
へアクセスします。
(%20は半角スペースです。)

13-1.png

後書き

今回は無料枠でのお試し実行のため、REST APIでの1件処理のみで作成していますが、実際は有料枠を利用して、一括登録したり、画面のインターフェースを作ったりすると面白そうです。
また、今回はベクトル検索のみの検索でしたが、ベクトル検索とキーワード検索を組み合わせるほうが実用的のようです。


以上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?