
Python, SQL, and APIs for FDEs: What Actually Works
Python SQL and APIs for FDEs usually collide at the same moment: you've got structured metadata in Postgres, embeddings that need a home, and an external API call that has to happen before you can write anything back. The naive version works at 10 requests per second. It falls apart at 100.
The problem isn't any single layer. It's the seams between them. SQL is great at filtering and joins, lousy at semantic search. APIs are non-deterministic by nature. Python is the glue, but only if you handle pooling, async, and serialization deliberately. Most guides skip the seams because they're written for generic backend developers, not FDEs wiring agent memory and RAG pipelines under latency budgets. GigaRAG handles some of this for you, but you still need to understand what's happening underneath.
| At a glance | Details |
|---|---|
| Primary stack | Python + SQL + external APIs |
| Best for | FDEs building agent memory and RAG |
| SQL's strength | Structured metadata, filtering, joins |
| SQL's weakness | Semantic search and unstructured memory |
| API limits | No latency or consistency guarantees |
| Key pattern | SQL for metadata, API for embeddings |
In This Guide
- What FDEs Actually Need from Python, SQL, and APIs
- SQL vs Vector Databases for Agent Memory
- SQL for FDEs: What It's Good At and What It Can't Do
- Python SQL And APIs For FDEs: A Step-by-Step Guide
- Python as the Orchestration Layer for SQL and APIs
- API Integration Patterns for RAG and Agent Memory
- Common Mistakes When Combining Python SQL and APIs
- A Minimal Reference Architecture for Agent Memory
What FDEs Actually Need from Python, SQL, and APIs
FDEs need Python to orchestrate data flow, SQL to store and query structured facts, and APIs to fetch external context. Three layers that break when treated as one. The mental model that holds up in production: SQL is the source of truth for what you know, APIs are the source of what you can reach, and Python is the glue that decides what to ask for and when.
The three-layer data model for agent memory
Agent memory splits into three layers. Structured facts (user IDs, document metadata, timestamps) belong in SQL. External context (weather, pricing, third-party lookups) comes from APIs at request time. Embeddings and unstructured memory sit in a vector store, not in either. Python sits between all three: it queries SQL for the facts, calls APIs for the context, and assembles both into the prompt or payload the agent actually uses.
Why FDEs hit this problem more than other engineers
Backend developers can often pick one layer and stay there. FDEs can't. You're deployed into a customer environment where the data lives in their Postgres, the enrichment comes from their API, and the agent has to work on their latency budget. That means you're wiring all three layers together under constraints you didn't choose, and the naive approach falls apart fast.
What this guide will and won't cover
This guide covers the patterns that survive contact with production: connection pooling, async orchestration, retry logic, and the storage boundaries that keep SQL from doing a vector store's job. It won't teach you Python syntax or SQL basics. It also won't pretend that any single tool fixes the integration problem. The honest answer is that the integration is the job.
[!note] SQL databases are not designed for semantic similarity search, and external APIs cannot guarantee latency or consistency — architect around these constraints rather than expecting SQL or an ORM to solve them.
SQL vs Vector Databases for Agent Memory
| Factor | SQL Databases | Vector Databases |
|---|---|---|
| Structured filtering | Excellent — WHERE, JOIN, indexes | Limited — metadata filters vary by vendor |
| Semantic similarity | Not native — requires extensions or app code | Core feature — ANN indexes built in |
| Unstructured memory | Poor fit — schema and types required | Designed for embeddings and blobs |
| Operational maturity | High — decades of tooling and backups | Growing — newer, fewer battle-tested patterns |
| Latency at scale | Predictable with proper indexing | Depends on index type and dimensionality |
SQL for FDEs: What It's Good At and What It Can't Do
SQL is a transaction engine with a query language bolted on. That's the whole story. It guarantees consistency, enforces schema, and answers precise questions fast. It does not understand meaning, and it never will.
Structured metadata: the right use case
User IDs, document titles, timestamps, access levels, chunk counts. Anything you can put in a column with a type belongs in SQL. You get ACID guarantees, indexed lookups, and a query planner that's been tuned for fifty years. When an agent needs to know "which documents did this user touch last week," SQL answers in milliseconds.
Joins and filtering for RAG context assembly
The retrieval half of RAG is mostly filtering. You join documents to users, filter by permission and date, then hand the surviving rows to your embedding model. SQL is the right tool for that pre-filter. It narrows the candidate set before any vector math happens, which keeps your embedding calls cheap and your context relevant.
What SQL cannot do: semantic search and unstructured memory
Ask SQL for "documents about pricing changes that feel urgent" and it returns nothing useful. LIKE '%pricing%' misses every synonym, every paraphrase, every implicit reference. Agent memory is messy: half-remembered facts, conflicting statements, context that only matters sometimes. None of that fits in a VARCHAR column. SQL stores what you already know. It can't discover what you almost know.
When to reach for pgvector instead of pure SQL
pgvector adds vector similarity search to Postgres, which covers the gap for small-to-medium embedding workloads. It's not a replacement for a dedicated vector database at scale, but it keeps your stack simple: one database, structured metadata and embeddings side by side. The honest tradeoff is performance. pgvector works fine under a few million vectors. Past that, you'll feel it.
[!tip] For FDEs building agent memory, store the SQL row ID alongside every embedding so you can always trace a vector back to its structured metadata — this single join key saves hours when debugging retrieval quality.
Python SQL And APIs For FDEs: A Step-by-Step Guide
- Define your schema: separate structured metadata (SQL) from embeddings and unstructured memory (vector store or blob).
- Write a Python repository layer that wraps SQL queries and returns typed objects, not raw rows.
- Add an API client with explicit timeouts, retries, and idempotency keys for every external call.
- Combine both in one workflow: fetch metadata from SQL, call the embedding API, then upsert the vector with the SQL row ID as the join key.
- Instrument each hop with latency and error metrics so you can see which layer is slow or failing.
- Test failure modes: API timeout, partial write, and stale cache — not just the happy path.
Python as the Orchestration Layer for SQL and APIs
Python's job is to sit between the database and the outside world without becoming the bottleneck. It coordinates, serializes, and recovers. It doesn't store anything itself.
Connection pooling with SQLAlchemy and psycopg2
Don't open a new connection per request. That's the fastest way to exhaust Postgres and stall your agent under load. Use SQLAlchemy's create_engine with a QueuePool, which holds a fixed number of connections open and hands them out as needed. psycopg2 sits underneath as the driver. The pool size depends on your Postgres max_connections minus headroom for admin tasks. Start with 10 and tune from there.
Async API calls with httpx or aiohttp
External APIs are slow and unpredictable. Blocking on one while your event loop waits is wasted throughput. httpx supports async natively and shares an API shape with requests, which makes it the easier choice. aiohttp is faster for raw throughput but more verbose. Either works. The key is that your API calls run concurrently with other work, not in sequence.
A copy-pasteable pattern: query SQL, enrich via API, write back
Here's the shape that covers most FDE workflows:
import asyncio
import httpx
from sqlalchemy import create_engine, text
from pydantic import BaseModel
engine = create_engine("postgresql+psycopg2://user:pass@localhost/db", pool_size=10)
class EnrichedRow(BaseModel):
id: int
title: str
external_score: float
async def enrich(row_id: int, title: str, client: httpx.AsyncClient) -> EnrichedRow:
resp = await client.post("https://api.example.com/score", json={"title": title})
resp.raise_for_status()
return EnrichedRow(id=row_id, title=title, external_score=resp.json()["score"])
async def main():
with engine.connect() as conn:
rows = conn.execute(text("SELECT id, title FROM documents WHERE scored = false LIMIT 50")).fetchall()
async with httpx.AsyncClient(timeout=10) as client:
enriched = await asyncio.gather(*[enrich(r.id, r.title, client) for r in rows])
with engine.begin() as conn:
for row in enriched:
conn.execute(
text("UPDATE documents SET external_score = :score, scored = true WHERE id = :id"),
{"score": row.external_score, "id": row.id},
)
asyncio.run(main())
This pattern pulls 50 rows, enriches them concurrently, and writes back in one transaction. It's not production-ready as-is. You'll want retry logic, a semaphore to cap concurrency, and error handling for partial failures.
Serialization boundaries: Pydantic models between layers
Pydantic is the contract. SQL returns rows, APIs return JSON, and your agent expects typed objects. Define a model at each boundary and validate on the way through. It catches malformed API responses before they poison your database. The cost is boilerplate, but the alternative is debugging a KeyError at 2am because an API changed its response shape.
API Integration Patterns for RAG and Agent Memory
External APIs are non-deterministic. They time out, rate-limit you, return malformed JSON, and change their response shape without warning. Design for that from the first line of code, not after your agent starts failing in production.
Retry logic and exponential backoff
Retry only on idempotent requests or safe verbs like GET. A POST that creates a resource retried blindly can create duplicates. Use exponential backoff with jitter: wait 1 second, then 2, then 4, capped at 30. Tenacity handles this in a few lines and is worth the dependency.
Rate limiting: what to build yourself vs. what to buy
A simple token bucket in Redis covers most cases. If you're calling one API from one service, build it. If you're coordinating rate limits across multiple services or regions, buy it or use a gateway. The honest answer: most FDEs need the simple version.
Caching API responses for RAG retrieval
Cache deterministic responses aggressively. Embedding lookups, metadata enrichment, and entity resolution rarely change minute to minute. Redis with a TTL of 5 to 15 minutes cuts API spend and latency. Don't cache anything user-specific or time-sensitive.
Handling API latency in agent workflows
Set explicit timeouts on every call. Ten seconds is generous for most enrichment APIs. If a call exceeds it, fail fast and let the agent decide whether to retry, degrade, or skip. Never let an agent hang on a slow API.
Common Mistakes When Combining Python SQL and APIs
Most integration failures aren't exotic. They're the same four mistakes repeated across codebases, and each one costs hours of debugging.
N+1 queries across SQL and API boundaries
You query 50 rows from Postgres, then loop over them calling an enrichment API once per row. That's 51 network round trips instead of 2. Fix it by batching: collect all IDs, make one API call with a list, then join the results back in Python.
Blocking the event loop with synchronous API calls
You write async FastAPI endpoints, then call requests.get() inside them. The event loop stalls, every other request waits, and your throughput collapses. Use httpx.AsyncClient or aiohttp for anything inside an async path. If a library only offers sync calls, run it in a thread pool with asyncio.to_thread().
Treating Postgres as a vector database
Plain Postgres has no native vector similarity search. You can store embeddings as arrays and compute cosine similarity in SQL, but it's slow past a few hundred thousand rows and you lose any index acceleration. Add pgvector, or use a dedicated vector store. Don't pretend a JSONB column solves it.
Ignoring connection pool exhaustion
Every API call that opens a fresh database connection and closes it after one query will exhaust your pool under load. Configure SQLAlchemy's pool size and overflow explicitly, and set pool_pre_ping=True so stale connections get recycled. A pool that's too small fails silently under burst traffic.
A Minimal Reference Architecture for Agent Memory
Here's the stack that works without overbuilding: Postgres for structured metadata, pgvector for embeddings, a Python service layer for orchestration, and external APIs for enrichment. Four components. Nothing else until you need it.
Component diagram in words: Postgres + pgvector + Python + APIs
Postgres holds your facts: user IDs, document metadata, timestamps, session state. pgvector sits in the same database as an extension, storing embeddings in a column with an HNSW index. Python runs between them, handling queries, serialization, and API calls. External APIs fetch context you don't own: enrichment data, model inference, third-party lookups.
The flow is one direction: query Postgres for structured rows, fetch embeddings via pgvector for similarity, call an API for missing context, write the result back. Python is the only layer that touches all three.
Where GigaRAG simplifies the memory layer
Building this yourself means managing schema migrations, index rebuilds, and embedding refresh jobs. GigaRAG handles agent memory storage as a managed layer, so you skip the pgvector maintenance and focus on the orchestration logic. The honest tradeoff: you lose direct control over index tuning and storage layout.
What to build first, what to defer
Build the Postgres schema and pgvector index first. They're the foundation everything else reads from. Add the Python service layer next, with connection pooling and async API calls. Defer caching, retry logic, and any managed memory layer until you've hit real traffic. Premature optimization here costs more than it saves.
For FDEs doing Python SQL and APIs work, this architecture covers 80% of agent memory needs without a single unnecessary component.
Frequently Asked Questions
Can I use SQL for vector similarity search?
Not natively. Some databases offer vector extensions, but they are not a substitute for a purpose-built vector index at scale. Use SQL for metadata and filtering, and a vector store for embeddings.
How do I combine SQL and API calls in one Python workflow?
Wrap SQL access in a repository layer, wrap API calls in a client with retries and timeouts, and join them on a shared ID. Fetch metadata from SQL, call the API for embeddings or enrichment, then write results back keyed by that ID.
What should I not expect from an ORM?
ORMs will not hide all performance problems. They can generate inefficient queries, obscure N+1 issues, and make bulk operations slower than raw SQL. Profile your queries and drop to raw SQL when needed.
Why do external APIs break agent memory pipelines?
APIs introduce network latency, rate limits, and partial failures that SQL transactions do not cover. Without idempotency keys and retry logic, a single timeout can leave your memory store inconsistent.
How should I store unstructured agent memory?
Do not force it into SQL tables. Store unstructured memory as embeddings in a vector store or as blobs in object storage, and keep only the metadata and references in SQL.
What is the biggest mistake FDEs make with Python, SQL, and APIs?
Treating all three as interchangeable persistence layers. Each has a different consistency model, failure mode, and performance profile — design for those differences instead of abstracting them away.
About GigaRAG
GigaRAG is for agent memory and RAG pipeline builders. get this right. Whether you are working through Python SQL and APIs for FDEs or something adjacent, we publish what we have actually tested, including where it falls short.



