Python SQL and APIs for FDEs: What Actually Works

GT

GigaRAG team

Retrieval13 min read
On this page
GigaRAG article hero showing a laptop with Python code beside a printed three-layer diagram of SQL, Python, and APIs for agent memory orchestration.
GigaRAG article hero showing a laptop with Python code beside a printed three-layer diagram of SQL, Python, and APIs for agent memory orchestration.

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 glanceDetails
Primary stackPython + SQL + external APIs
Best forFDEs building agent memory and RAG
SQL's strengthStructured metadata, filtering, joins
SQL's weaknessSemantic search and unstructured memory
API limitsNo latency or consistency guarantees
Key patternSQL for metadata, API for embeddings

In This Guide

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

FactorSQL DatabasesVector Databases
Structured filteringExcellent — WHERE, JOIN, indexesLimited — metadata filters vary by vendor
Semantic similarityNot native — requires extensions or app codeCore feature — ANN indexes built in
Unstructured memoryPoor fit — schema and types requiredDesigned for embeddings and blobs
Operational maturityHigh — decades of tooling and backupsGrowing — newer, fewer battle-tested patterns
Latency at scalePredictable with proper indexingDepends 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

  1. Define your schema: separate structured metadata (SQL) from embeddings and unstructured memory (vector store or blob).
  2. Write a Python repository layer that wraps SQL queries and returns typed objects, not raw rows.
  3. Add an API client with explicit timeouts, retries, and idempotency keys for every external call.
  4. 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.
  5. Instrument each hop with latency and error metrics so you can see which layer is slow or failing.
  6. Test failure modes: API timeout, partial write, and stale cache — not just the happy path.
Infographic comparing what SQL, APIs, and Python are bad at, with four steps for splitting agent memory by access pattern

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

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.

Six-step guide showing how to define a schema, write a repository layer, add an API client, combine workflows, instrument each hop, and test failure modes when wiring Python, SQL, and APIs for agent memory.
All posts