惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
aimingoo的专栏
aimingoo的专栏
F
Fortinet All Blogs
Blog — PlanetScale
Blog — PlanetScale
GbyAI
GbyAI
MongoDB | Blog
MongoDB | Blog
月光博客
月光博客
The Cloudflare Blog
量子位
T
Tailwind CSS Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog
MyScale Blog
MyScale Blog
T
The Blog of Author Tim Ferriss
The GitHub Blog
The GitHub Blog
G
Google Developers Blog
D
DataBreaches.Net
V
Visual Studio Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
U
Unit 42
博客园 - 聂微东
有赞技术团队
有赞技术团队
A
About on SuperTechFans

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
pgvector + Ollama Setup
Pedro Santos · 2026-05-07 · via DEV Community

RAG Without the Chatbot: pgvector + Ollama for Operational Data

Most RAG tutorials start with "upload a PDF and ask questions about it." That's fine for document search. But I needed RAG for something different: diagnosing failures in a distributed system by searching through historical saga events.

No PDFs. No chatbot. Just a Kafka consumer that vectorizes every saga event into pgvector and an agent that searches similar past incidents to diagnose new failures.

This series covers how I built it. The stack is Ollama for local embeddings, pgvector on PostgreSQL for storage, and LangChain4j to tie it together.

Why RAG (and Not Just Logs)

My saga orchestrator processes orders across 5 microservices. When a saga fails, the event carries a full history: which services ran, what status each returned, what error messages were generated. This data lives in Kafka and MongoDB.

I could search logs. But logs are text. Searching "payment failed" gives you exact matches. It doesn't find incidents where the payment was blocked for a different reason but with a similar pattern: same customer type, similar amount, same time of day.

RAG with vector search finds similar incidents, not exact matches. You convert the failure description to a vector (a list of numbers). You store it alongside thousands of past incidents. When a new failure arrives, you search for the closest vectors. The results are incidents that look like the current one, even if the words are different.

The Stack

Component Tool Role
Embedding model Ollama + nomic-embed-text Converts text to 768-dimensional vectors
Vector store pgvector on PostgreSQL Stores and searches vectors
Application LangChain4j Connects embedding model to vector store

I chose this stack because it runs locally with no cloud dependencies. Ollama is free. pgvector is a PostgreSQL extension, so it uses the same database infrastructure I already have. No separate vector database to manage.

Setting Up pgvector

pgvector is a PostgreSQL extension. The easiest way to run it is with the official Docker image:

# docker-compose.yml
vectors-db:
  image: pgvector/pgvector:pg16
  environment:
    POSTGRES_DB: vectors-db
    POSTGRES_USER: postgres
    POSTGRES_PASSWORD: postgres
  ports:
    - "5435:5432"
  volumes:
    - ./ai-saga-agent/src/main/resources/init-vectors.sql:/docker-entrypoint-initdb.d/init.sql

Enter fullscreen mode Exit fullscreen mode

The init script enables the extension:

CREATE EXTENSION IF NOT EXISTS vector;

Enter fullscreen mode Exit fullscreen mode

That's it. PostgreSQL now supports vector columns and similarity search.

Setting Up Ollama

Install Ollama and pull the embedding model:

brew install ollama
ollama pull nomic-embed-text    # 274MB, 768-dimensional output
ollama serve                    # API at http://localhost:11434

Enter fullscreen mode Exit fullscreen mode

nomic-embed-text is a good default for production. It's small (274MB vs 4GB+ for chat models), fast (single-digit milliseconds per embedding), and the quality is solid for operational data like log messages and event histories.

LangChain4j Configuration

Three beans connect everything:

@Configuration
public class EmbeddingConfig {

    @Value("${ai.ollama.base-url}")
    private String ollamaUrl;

    @Bean
    public EmbeddingModel embeddingModel() {
        return OllamaEmbeddingModel.builder()
            .baseUrl(ollamaUrl)
            .modelName("nomic-embed-text")
            .build();
    }

    @Bean
    public EmbeddingStore<TextSegment> embeddingStore(DataSource dataSource) {
        return PgVectorEmbeddingStore.datasourceBuilder()
            .datasource(dataSource)
            .table("saga_history_embeddings")
            .dimension(768)       // nomic-embed-text output size
            .createTable(true)    // auto-creates if not exists
            .build();
    }

    @Bean
    public EmbeddingStoreIngestor ingestor(
            EmbeddingModel model, EmbeddingStore<TextSegment> store) {
        return EmbeddingStoreIngestor.builder()
            .embeddingModel(model)
            .embeddingStore(store)
            .build();
    }
}

Enter fullscreen mode Exit fullscreen mode

The EmbeddingModel connects to Ollama. The EmbeddingStore connects to pgvector. The EmbeddingStoreIngestor is a convenience that handles the embed-and-store pipeline.

The dimension(768) parameter must match the output size of your embedding model. nomic-embed-text produces 768-dimensional vectors. If you switch models, update this number.

createTable(true) auto-creates the saga_history_embeddings table with the right schema on first run. In production, you'd manage this with migrations.

How Embedding Works

When you embed a piece of text, the model converts it to a list of 768 numbers. Similar texts produce similar numbers. "Payment failed due to insufficient funds" and "Card declined for low balance" will have vectors that are close together in 768-dimensional space, even though they share few words.

In code:

// Text in
String text = "PAYMENT_SERVICE [ROLLBACK]: Fail to realize payment: " +
              "New customer limit exceeded: R$450.00 > R$500.00";

// Vector out (768 floats)
Embedding embedding = embeddingModel.embed(text).content();

// Store with metadata
var segment = TextSegment.from(text, new Metadata()
    .put("orderId", "abc123")
    .put("status", "FAIL")
    .put("profileKey", "new:high-value"));

embeddingStore.add(embedding, segment);

Enter fullscreen mode Exit fullscreen mode

The Metadata lets you filter results later. You could search only for incidents with status=FAIL or profileKey=new:high-value.

Searching for Similar Incidents

To find past incidents similar to a new failure:

String newFailure = "PAYMENT_SERVICE [ROLLBACK]: Transaction blocked by fraud prevention";

var queryEmbedding = embeddingModel.embed(newFailure).content();
var results = embeddingStore.search(
    EmbeddingSearchRequest.builder()
        .queryEmbedding(queryEmbedding)
        .maxResults(3)
        .minScore(0.75)
        .build());

for (var match : results.matches()) {
    System.out.println("Score: " + match.score());
    System.out.println("Text: " + match.embedded().text());
}

Enter fullscreen mode Exit fullscreen mode

minScore(0.75) filters out weak matches. I found that scores below 0.7 tend to be noise in my data. Scores above 0.85 are usually the same type of failure.

maxResults(3) keeps the RAG context manageable. The LLM performs better with 3 highly relevant examples than 10 mediocre ones.

What's Next

The embedding and search infrastructure is ready. In the next post, I'll show how I feed it with real-time Kafka events: every saga completion gets vectorized, building up a knowledge base that improves the agent's diagnoses over time.

The repo: github.com/pedrop3/saga-orchestration