That GPT-4o hallucination that invented a whole `asyncpg` API
The prompt
# Asked GPT-4o for a production-ready asyncpg pool pattern
# with proper lifecycle management and health checksWhat it gave me
import asyncpg
from contextlib import asynccontextmanager
class DatabasePool:
def __init__(self, dsn: str, min_size: int = 10, max_size: int = 50):
self.dsn = dsn
self.min_size = min_size
self.max_size = max_size
self._pool: asyncpg.Pool | None = None
self._health_check_task: asyncio.Task | None = None
async def initialize(self) -> None:
self._pool = await asyncpg.create_pool(
self.dsn,
min_size=self.min_size,
max_size=self.max_size,
command_timeout=60,
# These two parameters DO NOT EXIST in asyncpg 0.29.0
health_check_interval=30,
health_check_query="SELECT 1",
)
self._health_check_task = asyncio.create_task(self._health_monitor())
@asynccontextmanager
async def acquire(self) -> AsyncGenerator[asyncpg.Connection, None]:
if self._pool is None:
raise RuntimeError("Pool not initialized")
async with self._pool.acquire() as conn:
yield conn
async def _health_monitor(self) -> None:
while True:
await asyncio.sleep(30)
# This method DOES NOT EXIST
await self._pool.health_check()
async def close(self) -> None:
if self._health_check_task:
self._health_check_task.cancel()
if self._pool:
await self._pool.close()The error that sent me down the rabbit hole
TypeError: create_pool() got unexpected keyword arguments:
'health_check_interval', 'health_check_query'
AttributeError: 'Pool' object has no attribute 'health_check'What I tried before realizing it was hallucinated
1. Checked asyncpg docs — no health_check_interval, no health_check_query, no Pool.health_check()
2. Searched GitHub issues — zero results for these parameters
3. Dug through source — create_pool signature only accepts: dsn, min_size, max_size, command_timeout, timeout, loop, connection_class, init, setup, server_settings, statement_cache_size, max_queries, max_inactive_connection_lifetime
4. Tested on 0.28.0, 0.29.0, 0.30.0 — same error every version
Where the hallucination likely came from
The model conflated three real things:
| Real thing | What it does |
|------------|--------------|
| max_inactive_connection_lifetime (asyncpg 0.29+) | Closes idle connections after N seconds |
| asyncpg.Pool.acquire() timeout parameter | Fails fast if no connection available |
| asyncpg.Connection.execute("SELECT 1") | Manual health check pattern people actually use |
It then invented a cohesive API around these concepts that looks like it should exist — consistent naming, sensible defaults, proper async context manager usage. That's the dangerous part: it's not random garbage, it's plausible garbage.
The actual working pattern
import asyncpg
import asyncio
from contextlib import asynccontextmanager
class DatabasePool:
def __init__(self, dsn: str, min_size: int = 10, max_size: int = 50):
self.dsn = dsn
self.min_size = min_size
self.max_size = max_size
self._pool: asyncpg.Pool | None = None
async def initialize(self) -> None:
self._pool = await asyncpg.create_pool(
self.dsn,
min_size=self.min_size,
max_size=self.max_size,
command_timeout=60,
max_inactive_connection_lifetime=300, # Real parameter
)
@asynccontextmanager
async def acquire(self) -> AsyncGenerator[asyncpg.Connection, None]:
if self._pool is None:
raise RuntimeError("Pool not initialized")
async with self._pool.acquire(timeout=10) as conn: # Real timeout
yield conn
async def health_check(self) -> bool:
"""Manual health check — call this from your /health endpoint"""
if self._pool is None:
return False
try:
async with self._pool.acquire(timeout=2) as conn:
await conn.execute("SELECT 1")
return True
except Exception:
return False
async def close(self) -> None:
if self._pool:
await self._pool.close()The pattern I'm seeing
Hallucinations cluster around plausible API extensions — methods/parameters that should exist based on naming conventions and common patterns. The model isn't retrieving; it's synthesizing a consistent interface from partial knowledge.
Anyone else hit this with asyncpg or other libraries? Wondering if there's a systematic way to catch these before they waste hours.
asyncpghave any built-in pool introspection methods?