A proof-of-concept demonstrating an agentic search pattern where an LLM agent uses on-demand skills to write correct PostgreSQL queries. The agent loads a PostgreSQL SQL skill before generating queries, ensuring proper syntax (e.g., ILIKE, ::cast, single quotes for strings).
The agent follows a progressive skill-disclosure pattern: it knows skills exist, loads them when needed, executes queries, and formats results as natural language.
sequenceDiagram
autonumber
participant U as User
participant A as Agent (LLM)
participant S as Skill Registry
participant T as execute_sql_query Tool
participant DB as PostgreSQL
Note over A: System prompt lists available skills:<br/>- postgresql-sql
U->>A: "What is name of the session which Alice Johnson is about to give and what is her last signup_date?"
A->>A: Decides SQL context is needed
A->>S: load_skill("postgresql-sql")
S-->>A: Returns skill content:<br/>- Use ILIKE for case-insensitive match<br/>- Use single quotes for strings<br/>- Use ::type for casting
Note over A: Skill content is appended<br/>to the context window
A->>A: Generates query with JOIN
A->>T: execute_sql_query(query)
T->>DB: Runs query
DB-->>T: Result set
T-->>A: Markdown table results
A->>A: Synthesizes answer from results
A-->>U: "Alice Johnson is about to give a session titled 'Agentic Search Patterns', and her last signup date is January 15, 2026."
flowchart TD
A[User asks a question] --> B{Does this need<br/>a SQL query?}
B -->|No| C[Answer directly]
B -->|Yes| D[Call load_skill<br/>postgresql-sql]
D --> E[Skill injected into<br/>agent context]
E --> F[Agent writes<br/>PostgreSQL-specific query<br/>e.g. ILIKE, ::cast, LIMIT]
F --> G[Call execute_sql_query]
G --> H{Query valid?}
H -->|Yes| I[Return results to user]
H -->|No / Error| J[Use skill to rewrite query]
J --> F
C --> K[Done]
I --> K
- Docker (for PostgreSQL container)
- Python 3.10+
- Ollama (for local LLM inference)
- A model capable of tool use (tested with
ministral-3:latest)
docker compose up -dVerify the database is seeded:
docker exec -it pg-agent-poc psql -U agent -d conference -c "SELECT * FROM users;"pip install -r requirements.txtPull the model (tested with ministral-3:latest):
ollama pull ministral-3:latestEnsure Ollama is running:
ollama serveCreate a .env file:
# Ollama / LiteLLM configuration
LITELLM_API_KEY=ollama
LITELLM_API_BASE=http://localhost:11434/v1
# PostgreSQL (matches docker-compose.yml)
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=conference
POSTGRES_USER=agent
POSTGRES_PASSWORD=agentpassNote: When using Ollama, the API key can be any string (e.g.,
ollama). The base URL points to Ollama's OpenAI-compatible endpoint.
.
├── .env # Environment variables
├── docker-compose.yml # PostgreSQL container setup
├── init.sql # Database schema and dummy data
├── skills.py # Module 1: Skill definitions
├── db_tool.py # Module 3: SQL execution tool
└── agent.py # Module 4: Agent wiring and middleware
python agent.pyThe agent will:
- Receive your question
- Load the
postgresql-sqlskill on demand - Generate a correct PostgreSQL query
- Execute it against the database
- Summarize the results in natural language
Below is a real trace using Ollama with ministral-3:latest:
User:
What is name of the session which Alice Johnson is about to give and what is her last signup_date?
Agent (Turn 1 - Load Skill):
Tool Calls:
load_skill(skill_name="postgresql-sql")
Tool Result:
Loaded skill: postgresql-sql
- Use single quotes for string literals
- PostgreSQL uses
ILIKEfor case-insensitive matching- Use
::operator for casting ...
Agent (Turn 2 - Execute Query):
Tool Calls:
execute_sql_query(sql_query="SELECT s.title AS session_title, u.signup_date AS last_signup_date FROM users u JOIN sessions s ON u.name = s.speaker_name WHERE u.name = 'Alice Johnson' ORDER BY u.signup_date DESC LIMIT 1;")
Tool Result:
session_title last_signup_date Agentic Search Patterns 2026-01-15
Agent (Final Answer):
Alice Johnson is about to give a session titled "Agentic Search Patterns", and her last signup date is January 15, 2026.
The agent uses a three-layer context strategy:
| Layer | Content | When It Appears |
|---|---|---|
| Layer 1 | System prompt + skill descriptions | Every request |
| Layer 2 | Full skill content (e.g., PostgreSQL rules) | Only after load_skill is called |
| Layer 3 | Conversation history (questions, tool calls, results) | Grows with each turn |
This keeps the context window small for non-SQL questions while ensuring the agent has detailed syntax rules when it needs them.
The execute_sql_query tool rejects any query that does not start with SELECT, preventing accidental data mutation.
The tool description instructs the LLM to summarize results in natural language rather than outputting raw markdown tables:
"When you receive results, summarize them in natural language. Do not simply output the raw table."
Edit skills.py and append to the SKILLS list:
{
"name": "mysql-sql",
"description": "Write MySQL-compatible SQL queries...",
"content": "# MySQL SQL Skill\n..."
}The SkillMiddleware will automatically make it discoverable without code changes.
Update the model name in agent.py:
llm = ChatOpenAI(
openai_api_base=os.getenv("LITELLM_API_BASE"),
api_key=os.getenv("LITELLM_API_KEY"),
model="ministral-3:latest", # or any Ollama / OpenAI model
temperature=0.2,
)Stop the PostgreSQL container:
docker compose downTo remove the database volume as well:
docker compose down -v- Ollama Compatibility: This POC was tested successfully with
ministral-3:latestvia Ollama's OpenAI-compatible API. Tool use support may vary across models. - Context Persistence: Conversation history is held in memory only. If you need persistence across script runs, add a LangChain checkpointer.
- Read-Only: The SQL tool is restricted to
SELECTstatements for safety.