How to Integrate Anthropic for pension funds with pgvector for RAG
Combining Anthropic with pgvector gives you a clean RAG stack for pension-fund workflows: retrieve the right policy, investment memo, or member communication from your private corpus, then have the model answer with context instead of guessing. For regulated environments, that matters because you want grounded responses, traceable retrieval, and a database you already control.
Prerequisites
- •Python 3.10+
- •PostgreSQL 14+ with the
pgvectorextension installed - •An Anthropic API key
- •A working PostgreSQL user with permission to create tables and extensions
- •These Python packages:
- •
anthropic - •
psycopg[binary] - •
pgvector - •
python-dotenv
- •
Install them:
pip install anthropic psycopg[binary] pgvector python-dotenv
Make sure PostgreSQL has pgvector enabled:
CREATE EXTENSION IF NOT EXISTS vector;
Integration Steps
- •Set up your environment and database connection.
import os
from dotenv import load_dotenv
import psycopg
load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL")
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
conn = psycopg.connect(DATABASE_URL)
conn.autocommit = True
with conn.cursor() as cur:
cur.execute("CREATE EXTENSION IF NOT EXISTS vector;")
- •Create a table for pension-fund documents and embeddings.
Use the same embedding dimension as your chosen model. For Anthropic-based RAG systems, embeddings are usually generated by a separate embedding model, then passed to Anthropic for generation. The retrieval layer stays in pgvector.
with conn.cursor() as cur:
cur.execute("""
CREATE TABLE IF NOT EXISTS pension_docs (
id BIGSERIAL PRIMARY KEY,
source ტექST NOT NULL,
content TEXT NOT NULL,
embedding VECTOR(1536)
);
""")
cur.execute("""
CREATE INDEX IF NOT EXISTS pension_docs_embedding_idx
ON pension_docs USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
""")
- •Generate embeddings for your pension documents and store them in pgvector.
Here I’m using a placeholder embedding function so the pattern is clear. In production, swap this for your embedding provider of choice and keep the rest of the pipeline unchanged.
from typing import List
import hashlib
def fake_embedding(text: str, dims: int = 1536) -> List[float]:
# Replace with a real embedding model call.
h = hashlib.sha256(text.encode()).digest()
values = [(b / 255.0) for b in h]
return (values * (dims // len(values) + 1))[:dims]
docs = [
("policy_2024.pdf", "The fund must review liquidity risk quarterly."),
("member_faq.md", "Members can request benefit statements annually."),
]
with conn.cursor() as cur:
for source, content in docs:
emb = fake_embedding(content)
cur.execute(
"INSERT INTO pension_docs (source, content, embedding) VALUES (%s, %s, %s)",
(source, content, emb),
)
- •Retrieve the top-k relevant chunks from pgvector and send them to Anthropic.
This is where Anthropic’s Messages API comes in. You retrieve context from Postgres first, then pass it into client.messages.create(...).
import anthropic
client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
def search_similar(query: str, k: int = 3):
q_emb = fake_embedding(query)
with conn.cursor() as cur:
cur.execute(
"""
SELECT source, content
FROM pension_docs
ORDER BY embedding <=> %s::vector
LIMIT %s;
""",
(q_emb, k),
)
return cur.fetchall()
query = "What is the fund's liquidity review requirement?"
matches = search_similar(query)
context_block = "\n\n".join(
f"Source: {source}\nContent: {content}"
for source, content in matches
)
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=300,
temperature=0,
messages=[
{
"role": "user",
"content": f"""Answer using only this context:
{context_block}
Question: {query}"""
}
],
)
print(response.content[0].text)
- •Wrap retrieval + generation into a reusable RAG function.
This is the shape you want in an agent system: one function for retrieval, one function for generation, one boundary between data access and model calls.
def answer_pension_question(question: str) -> str:
matches = search_similar(question, k=4)
if not matches:
return "No relevant internal documents found."
context_block = "\n\n".join(
f"[{source}] {content}" for source, content in matches
)
resp = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=250,
temperature=0,
messages=[
{
"role": "user",
"content": (
"You are answering questions for a pension-fund operations team.\n"
"Use only the provided context.\n\n"
f"Context:\n{context_block}\n\n"
f"Question: {question}"
),
}
],
)
return resp.content[0].text
print(answer_pension_question("How often do we review liquidity risk?"))
Testing the Integration
Run a simple end-to-end query and confirm that retrieval returns matching rows before Anthropic generates the final response.
test_question = "How often must liquidity risk be reviewed?"
rows = search_similar(test_question, k=2)
print("Retrieved:")
for row in rows:
print(row)
print("\nAnswer:")
print(answer_pension_question(test_question))
Expected output:
Retrieved:
('policy_2024.pdf', 'The fund must review liquidity risk quarterly.')
Answer:
The fund must review liquidity risk quarterly.
If you get an empty retrieval result or irrelevant context, fix embeddings first. If retrieval looks good but answers drift, tighten the prompt and keep temperature=0.
Real-World Use Cases
- •Pension administrator copilot that answers policy questions from internal procedures and investment committee notes.
- •Member-services assistant that drafts benefit explanations using approved handbook language stored in Postgres.
- •Compliance workflow agent that retrieves prior decisions, then generates audit-ready summaries with citations to source documents.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
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