Concepts

How it fits together

Three separately installable packages with a strict, one-way dependency graph. Each one has a single job, and none of them blur into the next.

The dependency graph

PyWrapAI-Monitor
imports →
PyWrapAI-Graph
imports →
PyWrapAI

PyWrapAI imports nothing from the other two. Adding a dependency in the other direction would force every user of the simple LLM-calling library to install the full stack.

What each library owns

The Engine

PyWrapAI

Knows: LLM providers, tokens, cost, caching, retries, tool wire format, embeddings.

Doesn't know: Conversation structure, history, system prompts, databases, RAG.

The Assembler

PyWrapAI-Graph

Knows: Message order, prompt files, history injection, tool execution, RAG, the agent loop.

Doesn't know: Databases, user identities, session IDs, analytics.

The Observer

PyWrapAI-Monitor

Knows: User identities, session IDs, databases, analytics queries.

Doesn't know: LLM provider details, API keys, token pricing calculations.

Runtime message flow

Every call assembles the same shape, regardless of which layers are involved:

SYSTEM loaded from a versioned prompt file — position 0, always
HISTORY prior turns, chronological — omitted entirely for a fresh conversation
RAG (optional) retrieved context injected into the current message text, not a separate message
CURRENT the message being answered

Only the original user text is ever persisted to history — injected RAG context is dropped before saving, so follow-up turns always retrieve fresh context instead of stale chunks.

Infrastructure — one file, then three backends

Start

SQLite

Zero external infrastructure. One file. Strong enough to demo end-to-end and run a real single-server deployment.

→
Scale

PostgreSQL / MySQL

Built in, not planned. Same BaseStore interface, connection pooling, portable SQL — swap the connection string, nothing else changes.

Design philosophy

One library, one job

No feature ever belongs to two libraries. The boundaries never blur.

No magic

Every behavior is explicit and visible in the calling code.

Lazy imports

Provider SDKs load only when used. Importing PyWrapAI with none installed never fails.

Portable SQL

ON CONFLICT ... DO UPDATE SET everywhere — a connection-string change moves SQLite to Postgres.

Production first

Built for thousands of concurrent users — pooling, async writes, isolated errors from day one.

Structured logging

A logging.getLogger per library — pywrapai.llm, .agent, .monitor — wired into your own log pipeline.