How to Integrate Anthropic for pension funds with pgvector for AI agents

By Cyprian AaronsUpdated 2026-04-21
anthropic-for-pension-fundspgvectorai-agents

Anthropic gives you the reasoning layer for agentic workflows. pgvector gives you retrieval over pension documents, policy PDFs, member correspondence, and actuarial notes. Put them together and you get an AI agent that can answer regulated questions with grounded context instead of guessing.

Prerequisites

  • Python 3.10+
  • PostgreSQL 14+ with the pgvector extension installed
  • An Anthropic API key
  • A working embedding model strategy
    • either Anthropic-generated text embeddings if your stack supports them
    • or a separate embedding model feeding vectors into pgvector
  • These Python packages:
    • anthropic
    • psycopg[binary]
    • pgvector
    • python-dotenv

Install them:

pip install anthropic psycopg[binary] pgvector python-dotenv

Make sure your database has the extension enabled:

CREATE EXTENSION IF NOT EXISTS vector;

Integration Steps

1) Set up your database schema

Store chunks of pension-fund content alongside embeddings. Keep metadata close to the vector so you can filter by document type, fund name, or jurisdiction.

import os
import psycopg
from pgvector.psycopg import register_vector

DB_URL = os.environ["DATABASE_URL"]

with psycopg.connect(DB_URL) as conn:
    register_vector(conn)
    with conn.cursor() as cur:
        cur.execute("""
            CREATE TABLE IF NOT EXISTS pension_chunks (
                id BIGSERIAL PRIMARY KEY,
                fund_name TEXT NOT NULL,
                doc_type TEXT NOT NULL,
                chunk_text TEXT NOT NULL,
                embedding VECTOR(1536) NOT NULL,
                created_at TIMESTAMPTZ DEFAULT NOW()
            );
        """)
        cur.execute("""
            CREATE INDEX IF NOT EXISTS pension_chunks_embedding_idx
            ON pension_chunks USING ivfflat (embedding vector_cosine_ops)
            WITH (lists = 100);
        """)
        conn.commit()

Use a vector dimension that matches your embedding model. If you change models later, migrate the column too.

2) Create embeddings and store them in pgvector

If your architecture uses Anthropic for generation and another embedding model for retrieval, keep that separation clean. The important part is that the vectors land in pgvector in a consistent format.

import os
import psycopg
from pgvector.psycopg import register_vector

def fake_embed(text: str) -> list[float]:
    # Replace with your real embedding provider.
    # Must return a fixed-length float list.
    return [0.0] * 1536

chunks = [
    {
        "fund_name": "Northwind Pension Fund",
        "doc_type": "policy",
        "chunk_text": "Members can request partial transfer values after age 55 under specific conditions."
    },
    {
        "fund_name": "Northwind Pension Fund",
        "doc_type": "benefits",
        "chunk_text": "Defined contribution withdrawals are subject to trustee approval and tax rules."
    }
]

with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
    register_vector(conn)
    with conn.cursor() as cur:
        for item in chunks:
            emb = fake_embed(item["chunk_text"])
            cur.execute(
                """
                INSERT INTO pension_chunks (fund_name, doc_type, chunk_text, embedding)
                VALUES (%s, %s, %s, %s)
                """,
                (item["fund_name"], item["doc_type"], item["chunk_text"], emb),
            )
        conn.commit()

In production, replace fake_embed() with your actual embedding call. The rest of the storage path stays the same.

3) Query pgvector for relevant pension context

At runtime, embed the user question, retrieve top matches from pgvector, then pass those chunks into Anthropic as grounded context.

import os
import psycopg
from pgvector.psycopg import register_vector

def embed_query(text: str) -> list[float]:
    return [0.0] * 1536

question = "Can a member request a transfer value after age 55?"

with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
    register_vector(conn)
    with conn.cursor() as cur:
        qvec = embed_query(question)
        cur.execute(
            """
            SELECT fund_name, doc_type, chunk_text
            FROM pension_chunks
            ORDER BY embedding <=> %s
            LIMIT 3
            """,
            (qvec,),
        )
        rows = cur.fetchall()

context = "\n\n".join(
    f"[{fund} | {doc_type}] {text}"
    for fund, doc_type, text in rows
)

print(context)

The <=> operator is the cosine distance operator provided by pgvector. That is the core retrieval primitive you want here.

4) Call Anthropic with retrieved context

Use Anthropic’s Messages API to generate an answer only from retrieved material. For regulated workflows like pensions, keep the prompt tight and explicit.

import os
from anthropic import Anthropic

client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

prompt = f"""
You are assisting with pension fund operations.
Answer only using the provided context.
If the context does not support an answer, say you do not have enough information.

Question: Can a member request a transfer value after age 55?

Context:
{context}
"""

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=300,
    temperature=0,
    messages=[
        {"role": "user", "content": prompt}
    ],
)

print(response.content[0].text)

This is the pattern: retrieve first, generate second. Do not let the model freewheel on policy questions without source text.

5) Wrap it into one agent function

This gives you a production-friendly interface for your app or workflow engine.

import os
import psycopg
from anthropic import Anthropic
from pgvector.psycopg import register_vector

client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

def retrieve_context(question: str) -> str:
    qvec = [0.0] * 1536  # replace with real embedding call

    with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
        register_vector(conn)
        with conn.cursor() as cur:
            cur.execute(
                """
                SELECT fund_name, doc_type, chunk_text
                FROM pension_chunks
                ORDER BY embedding <=> %s
                LIMIT 5
                """,
                (qvec,),
            )
            rows = cur.fetchall()

    return "\n\n".join(f"[{f} | {d}] {t}" for f, d, t in rows)

def answer_pension_question(question: str) -> str:
    context = retrieve_context(question)

    msg = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=400,
        temperature=0,
        messages=[{
            "role": "user",
            "content": f"""
Use only this context to answer.

Question: {question}

Context:
{context}
"""
        }],
    )

    return msg.content[0].text

print(answer_pension_question("What happens if a member requests withdrawal before retirement?"))

Testing the Integration

Run one end-to-end query and verify that retrieval returns relevant chunks before Anthropic answers.

result = answer_pension_question("Can a member request a transfer value after age 55?")
print(result)

Expected output:

Based on the provided policy text, members can request partial transfer values after age 55 under specific conditions. I do not have enough information here to confirm any exceptions beyond that policy language.

If you see an answer that cites unrelated policy details or invents rules not present in your chunks, fix your retrieval quality or tighten the system prompt.

Real-World Use Cases

  • Pension member servicing agents that answer benefit questions from policy docs while staying grounded in trustee-approved content.
  • Internal ops copilots that search scheme rules, then draft compliant responses for caseworkers.
  • Adviser support tools that retrieve plan-specific rules from pgvector and summarize them through Anthropic before escalation.

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides