0Pricing
Spring Boot 4 Complete Guide · 강의

임베딩 및 벡터 저장소 검색

임베딩을 생성하고 벡터 저장소를 조회해 데이터에 대한 의미 기반 검색을 구현합니다.

임베딩 및 벡터 저장소 검색은(는) CoddyKit의 무료 Spring Boot 4 Complete Guide 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Spring Boot 4 Complete Guide 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Spring Boot 4 Complete Guide 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Why Embeddings Power Semantic Search

Keyword search matches literal tokens. Semantic search matches meaning. The bridge between the two is an embedding: a fixed-length vector of floats that captures the semantic content of a piece of text.

  • Texts with similar meaning produce vectors that are close together in vector space.
  • Closeness is measured by cosine similarity or Euclidean distance.
  • A query like "how do I reset my password" can match a document titled "account recovery steps" even with zero shared keywords.

In Spring AI, you generate embeddings with an EmbeddingModel and store/query them with a VectorStore. This lesson wires both together into a Retrieval-Augmented pipeline.

The EmbeddingModel Abstraction

Spring AI exposes EmbeddingModel as a provider-agnostic interface. Add the starter (for example spring-ai-starter-model-openai) and Spring Boot auto-configures a bean you can inject.

  • embed(String) returns a single float[].
  • embed(List<String>) batches multiple texts efficiently.
  • dimensions() tells you the vector size (for example 1536 for text-embedding-3-small).

Configure the model in application.yml so the bean is ready for injection.

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      embedding:
        options:
          model: text-embedding-3-small

Generating Your First Embedding

Inject EmbeddingModel and call embed. The result is a dense vector you can inspect or persist. The richer embedForResponse call also returns token usage metadata.

  • Always log dimensions() once at startup so a model swap that changes vector size is caught early.
  • A vector store column must match this dimension exactly, or inserts fail.
@RestController
class EmbeddingController {

    private final EmbeddingModel embeddingModel;

    EmbeddingController(EmbeddingModel embeddingModel) {
        this.embeddingModel = embeddingModel;
    }

    @GetMapping("/embed")
    Map<String, Object> embed(@RequestParam String text) {
        float[] vector = embeddingModel.embed(text);
        return Map.of(
            "dimensions", vector.length,
            "preview", List.of(vector[0], vector[1], vector[2])
        );
    }
}

Cosine Similarity By Hand

To build intuition, here is the math a vector store does for you. Cosine similarity is the dot product of two vectors divided by the product of their magnitudes. It ranges from -1 (opposite) to 1 (identical direction).

  • A value near 1.0 means the texts are semantically very close.
  • Vector stores usually expose a similarity score derived from this metric.

This standalone program computes cosine similarity for two toy vectors.

public class CosineSimilarity {
    static double cosine(double[] a, double[] b) {
        double dot = 0, normA = 0, normB = 0;
        for (int i = 0; i < a.length; i++) {
            dot += a[i] * b[i];
            normA += a[i] * a[i];
            normB += b[i] * b[i];
        }
        return dot / (Math.sqrt(normA) * Math.sqrt(normB));
    }

    public static void main(String[] args) {
        double[] query = {0.9, 0.1, 0.2};
        double[] docA  = {0.8, 0.2, 0.1};
        double[] docB  = {-0.5, 0.9, -0.4};
        System.out.printf("query vs A: %.4f%n", cosine(query, docA));
        System.out.printf("query vs B: %.4f%n", cosine(query, docB));
    }
}

The VectorStore Abstraction

A VectorStore persists Document objects together with their embeddings and supports similarity queries. Spring AI ships implementations for PGVector, Redis, Chroma, Milvus, Qdrant, and a SimpleVectorStore for tests.

  • add(List<Document>) embeds and stores documents.
  • similaritySearch(SearchRequest) embeds the query and returns the nearest documents.
  • delete(...) removes documents by id or filter.

Critically, add calls the EmbeddingModel for you, so you rarely embed manually when using a store.

Configuring PGVector

For production, PGVector (the Postgres extension) is a common choice because it co-locates vectors with your relational data. Add spring-ai-starter-vector-store-pgvector and configure the schema initialization.

  • index-type: HNSW gives fast approximate nearest-neighbor search.
  • dimensions must equal your embedding model's output size.
  • initialize-schema: true lets Spring create the vector_store table on startup.
spring:
  ai:
    vectorstore:
      pgvector:
        initialize-schema: true
        index-type: HNSW
        distance-type: COSINE_DISTANCE
        dimensions: 1536
  datasource:
    url: jdbc:postgresql://localhost:5432/appdb
    username: app
    password: ${DB_PASSWORD}

Ingesting Documents

Wrap each chunk of source text in a Document. You can attach arbitrary metadata (source, author, tenant id) which becomes filterable at query time. Calling vectorStore.add(...) embeds and persists in one step.

  • Use a stable id when you want to upsert rather than duplicate.
  • Keep chunks small (a few hundred tokens) so retrieved context is focused.
@Service
class IngestionService {

    private final VectorStore vectorStore;

    IngestionService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    void ingest() {
        List<Document> docs = List.of(
            new Document("Spring Boot 4 requires Java 17 or later.",
                Map.of("source", "release-notes", "version", "4.0")),
            new Document("Reset your password from the account recovery page.",
                Map.of("source", "help-center", "topic", "auth"))
        );
        vectorStore.add(docs);
    }
}

Chunking With ETL Readers

Real corpora arrive as PDFs, Markdown, or JSON. Spring AI's ETL pipeline provides DocumentReader sources and DocumentTransformer splitters. The TokenTextSplitter breaks large documents into embedding-sized chunks while preserving metadata.

  • TikaDocumentReader reads PDFs, Word, HTML.
  • TokenTextSplitter splits by token count with configurable overlap.
  • The output feeds straight into vectorStore.add(...).
@Service
class PdfIngestion {

    private final VectorStore vectorStore;

    PdfIngestion(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    void load(Resource pdf) {
        var reader = new TikaDocumentReader(pdf);
        var splitter = new TokenTextSplitter();
        List<Document> chunks = splitter.apply(reader.get());
        vectorStore.add(chunks);
    }
}

Similarity Search With SearchRequest

SearchRequest is a builder that controls retrieval. The key knobs are topK (how many results) and similarityThreshold (drop weak matches below a cutoff).

  • A higher similarityThreshold (for example 0.75) reduces noise but may return fewer hits.
  • topK caps how much context you feed to the LLM, controlling token cost.
@Service
class SearchService {

    private final VectorStore vectorStore;

    SearchService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    List<Document> search(String query) {
        return vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(query)
                .topK(4)
                .similarityThreshold(0.7)
                .build()
        );
    }
}

Metadata Filtering

Semantic relevance alone is not enough for multi-tenant or scoped data. SearchRequest accepts a filter expression evaluated against document metadata, combining vector similarity with structured constraints.

  • Use FilterExpressionBuilder or a portable string expression.
  • The store applies the filter and the similarity ranking together.
  • This is how you enforce tenant isolation: tenantId == 'acme'.
List<Document> scoped = vectorStore.similaritySearch(
    SearchRequest.builder()
        .query("how do I reset my password")
        .topK(3)
        .similarityThreshold(0.7)
        .filterExpression("source == 'help-center' && topic == 'auth'")
        .build()
);

From Retrieval to RAG

Retrieval is the first half of RAG (Retrieval-Augmented Generation). The second half stuffs retrieved chunks into the prompt so the LLM answers grounded in your data. Spring AI's QuestionAnswerAdvisor wires the vector store directly into a ChatClient call.

  • The advisor runs a similarity search per request and injects results as context.
  • You pass the same SearchRequest tuning (topK, threshold) into the advisor.
  • This keeps the answer faithful to your indexed documents and reduces hallucination.
@Service
class RagService {

    private final ChatClient chatClient;

    RagService(ChatClient.Builder builder, VectorStore vectorStore) {
        this.chatClient = builder
            .defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore)
                .searchRequest(SearchRequest.builder().topK(4).similarityThreshold(0.7).build())
                .build())
            .build();
    }

    String ask(String question) {
        return chatClient.prompt().user(question).call().content();
    }
}

Quick Check: Tuning Retrieval

A teammate reports that their RAG endpoint frequently injects irrelevant documents into the prompt, bloating token cost and confusing the model. They want to keep only strongly relevant matches without changing the embedding model. Which single SearchRequest adjustment most directly addresses this?

Recap and Key Takeaways

You built a complete semantic retrieval pipeline in Spring AI:

  • EmbeddingModel turns text into dense vectors; dimensions() must match your store's column size.
  • VectorStore (PGVector, Redis, Qdrant, etc.) stores Documents and embeds them automatically on add().
  • Use the ETL pipeline (TikaDocumentReader + TokenTextSplitter) to chunk real-world files before ingestion.
  • SearchRequest controls retrieval via topK, similarityThreshold, and metadata filterExpression for scoping and tenant isolation.
  • QuestionAnswerAdvisor turns retrieval into full RAG by injecting matched context into a ChatClient prompt.

Tune topK for cost and similarityThreshold for precision to balance grounding against token budget.

자주 묻는 질문

“임베딩 및 벡터 저장소 검색” 강의는 무료인가요?

네 — “임베딩 및 벡터 저장소 검색” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Spring Boot 4 Complete Guide 강의 전체를 잠금 해제할 수 있습니다. Spring Boot 4 Complete Guide 강의에는 총 4개의 강의가 포함되어 있습니다.

“임베딩 및 벡터 저장소 검색”에서 뭘 배우나요?

임베딩을 생성하고 벡터 저장소를 조회해 데이터에 대한 의미 기반 검색을 구현합니다. 브라우저에서 직접 실행하는 실습 코드로 Spring Boot 4 Complete Guide을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Spring Boot 4 Complete Guide을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Spring Boot 4 Complete Guide은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“임베딩 및 벡터 저장소 검색” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Spring Boot 4 Complete Guide 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Spring Boot 4 Complete Guide 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. ChatClient, 프롬프트 및 구조화된 출력
  2. 임베딩 및 벡터 저장소 검색
  3. 검색 증강 생성 파이프라인
  4. 도구 호출 및 에이전트 조언자
← Spring Boot 4 Complete Guide(으)로 돌아가기