Ignis Observability: AI Observability Hub
A non-blocking AI observability hub that provides comprehensive monitoring, tracing, and cost tracking for LLM applications. Built on a producer-consumer pattern, it ensures zero impact on your application's response time while capturing detailed metrics to both cloud platforms (LangSmith) and a local database.
Key Features
- Zero-Latency Observability: Fire-and-forget pattern ensures LLM responses reach users instantly
- Dual Persistence: Send traces to LangSmith while maintaining a local SQLite database
- Automatic Cost Tracking: Built-in pricing tables calculate costs per request
- Streaming Support: Full support for streaming responses with TTFT (Time-to-First-Token) metrics
- Universal Decorator: Single
@observedecorator works with both streaming and non-streaming functions - Modern Tech Stack: SQLAlchemy 2.0, asyncio, type-safe with Mapped columns
📋 Table of Contents
- Architecture
- Quick Start
- Configuration
- Usage Examples
- Running Tests
- Database Schema
- Cost Tracking
- API Reference
- Troubleshooting
🏛️ Architecture
Producer-Consumer Pattern
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ Your App │───────▶│ asyncio.Queue │───────▶│ Background │
│ (Producer) │ fast! │ │ │ Worker │
│ │ │ │ │ (Consumer) │
└─────────────┘ └──────────────┘ └────────┬────────┘
│
┌───────────────────────────┴─────────┐
▼ ▼
┌──────────────────┐ ┌──────────────┐
│ LangSmith │ │ SQLite │
│ (Cloud) │ │ (Local) │
└──────────────────┘ └──────────────┘
Tech Stack
| Layer | Technology | Purpose |
|---|---|---|
| Language | Python 3.10+ | Modern async/await support |
| Concurrency | asyncio | Non-blocking I/O operations |
| Database | SQLite + aiosqlite | Lightweight local storage |
| ORM | SQLAlchemy 2.0 | Type-safe database models |
| External Tools | LangSmith SDK | Cloud-based tracing platform |
| Tokenization | tiktoken | Accurate token counting |
| Configuration | pydantic-settings | Type-safe environment config |
Quick Start
1. Installation
# Clone or navigate to the repository
cd c:\Aiden\Obsrv\ignis-observability
# Create and activate virtual environment (if not already done)
python -m venv .venv
.venv\Scripts\Activate.ps1
# Install the package with dependencies
pip install -e .
# For development (includes testing tools)
pip install -e ".[dev]"
2. Get Your API Keys
OpenAI API Key
- Go to https://platform.openai.com/api-keys
- Click "Create new secret key"
- Copy the key (starts with
sk-proj-...)
LangSmith API Key
- Go to https://smith.langchain.com (sign up if needed)
- Navigate to Settings → API Keys
- Click "Create API Key"
- Copy the key (starts with
lsv2_pt_...)
Create a LangSmith Project
- In LangSmith, go to Projects
- Click "New Project"
- Name it (e.g.,
my-llm-apporproduction) - Copy the project name
3. Configure Environment
# Copy the example environment file
Copy-Item .env.example .env
# Edit .env with your favorite editor
notepad .env
Update the following values in .env:
# Required: Your OpenAI API key
OPENAI_API_KEY=sk-proj-YOUR_ACTUAL_KEY_HERE
# Required: Your LangSmith API key
LANGCHAIN_API_KEY=lsv2_pt_YOUR_ACTUAL_KEY_HERE
# Required: Your LangSmith project name
LANGCHAIN_PROJECT=my-llm-app
# Optional: Other settings (defaults usually fine)
LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
LANGCHAIN_TRACING_V2=true
DATABASE_URL=sqlite+aiosqlite:///./obs_hub.db
LOG_LEVEL=info
4. Run Your First Example
# Run the basic example
python examples/01_basic_example.py
You should see output like:
Starting Basic Observability Example
Initializing database...
Database ready
Initializing ObsHub...
ObsHub ready
Starting background worker...
Worker running
Calling LLM...
Response:
Why do database administrators always carry a ladder?
Because they're always climbing up the SQL!
Waiting for observability data to be processed...
Done! Check your LangSmith dashboard and local database.
5. View Your Data
LangSmith Dashboard
- Go to https://smith.langchain.com
- Click on your project (e.g.,
my-llm-app) - See your traced LLM calls with full details!
Local Database
# Query your local data
python
import asyncio
from sqlalchemy import select
from src.ignis_observability.database import DBManager, UnifiedTrace
async def view_traces():
db = DBManager()
await db.init_db()
async with db.session_factory() as session:
result = await session.execute(
select(UnifiedTrace).order_by(UnifiedTrace.timestamp.desc()).limit(5)
)
for trace in result.scalars():
print(f"{trace.name}: {trace.latency_ms:.2f}ms, {trace.prompt_tokens + trace.completion_tokens} tokens, ${trace.total_cost:.6f}")
asyncio.run(view_traces())
Configuration
All configuration is managed through environment variables (loaded from .env file).
Required Variables
| Variable | Description | Example | Where to Get |
|---|---|---|---|
OPENAI_API_KEY |
OpenAI API key for LLM calls | sk-proj-abc123... |
OpenAI Platform |
LANGCHAIN_API_KEY |
LangSmith API key for tracing | lsv2_pt_xyz789... |
LangSmith Settings |
LANGCHAIN_PROJECT |
LangSmith project name | my-production-app |
LangSmith Projects |
Optional Variables
| Variable | Default | Description |
|---|---|---|
LANGCHAIN_ENDPOINT |
https://api.smith.langchain.com |
LangSmith API endpoint |
LANGCHAIN_TRACING_V2 |
true |
Enable/disable LangSmith tracing |
DATABASE_URL |
sqlite+aiosqlite:///./obs_hub.db |
Local database connection string |
LOG_LEVEL |
info |
Logging level (debug/info/warning/error) |
Usage Examples
Basic Usage
Install as a Python package
Syntax for tagged release version deployment with pip
pip install git+https://github.com/Infogain-GenAI/ignis_observability.git@v1.0.0#egg=ignis_observability
Syntax for main branch install as module
pip install git+https://github.com/Infogain-GenAI/ignis_observability.git@main#egg=ignis_observability
Example usage in Python
import asyncio
from dotenv import load_dotenv
from ignis_observability.config import settings
from ignis_observability.database import DBManager
from ignis_observability.engine import ObsHub
from openai import AsyncOpenAI
# Load environment variables
load_dotenv()
async def main():
"""Run a simple example with non-streaming LLM call."""
print("Starting Basic Observability Example\n")
# Step 1: Initialize Database
print("Initializing database...")
db = DBManager(settings.DATABASE_URL)
await db.init_db()
await db.seed_prices()
print("Database ready\n")
# Step 2: Initialize Observability Hub
print("Initializing ObsHub...")
hub = ObsHub(
langsmith_api_key=settings.LANGCHAIN_API_KEY,
db_manager=db,
project_name=settings.LANGCHAIN_PROJECT,
)
print("ObsHub ready\n")
# Step 3: Start background worker
print("Starting background worker...")
worker_task = asyncio.create_task(hub.worker())
print("Worker running\n")
# Step 4: Create OpenAI client
client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY)
# Step 5: Define and decorate your LLM function
@hub.observe(name="get_joke")
async def get_joke(prompt: str):
"""Get a joke from GPT."""
return await client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
)
# Step 6: Call your function - observability happens automatically!
print("Calling LLM...")
response = await get_joke("Tell me a short joke about ai and llm.")
print("Response:")
print(f" {response.choices[0].message.content}\n")
# Step 7: Wait for background processing to complete
print("Waiting for observability data to be processed...")
await asyncio.sleep(2)
print("Done! Check your LangSmith dashboard and local database.\n")
print(f" LangSmith Project: {settings.LANGCHAIN_PROJECT}")
print(f" Database: {settings.DATABASE_URL}")
# Cleanup
worker_task.cancel()
try:
await worker_task
except asyncio.CancelledError:
pass
if __name__ == "__main__":
asyncio.run(main())
You can place the above code in a standalone example file and run it after installing the package.
import asyncio
from dotenv import load_dotenv
from openai import AsyncOpenAI
from ignis_observability.database import DBManager
from ignis_observability.engine import ObsHub
from ignis_observability.config import settings
load_dotenv()
async def main():
# Initialize
db = DBManager(settings.DATABASE_URL)
await db.init_db()
await db.seed_prices()
hub = ObsHub(
langsmith_api_key=settings.LANGCHAIN_API_KEY,
db_manager=db
)
worker_task = asyncio.create_task(hub.worker())
client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY)
# Decorate your function
@hub.observe(name="my_llm_call")
async def ask_llm(question: str):
return await client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": question}],
)
# Use it normally - observability happens automatically!
response = await ask_llm("What is observability?")
print(response.choices[0].message.content)
# Cleanup
await asyncio.sleep(2)
worker_task.cancel()
asyncio.run(main())
Streaming Example
@hub.observe(name="streaming_response")
async def stream_response():
return await client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "Write a poem"}],
stream=True,
)
stream = await stream_response()
async for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
More Examples
See the examples/ directory for complete working examples:
01_basic_example.py- Simple non-streaming call02_streaming_example.py- Streaming with TTFT metrics03_multi_call_example.py- Multiple calls with cost tracking04_complete_example.py- Production-ready setup
Running Tests
# Run all tests
pytest
# Run with coverage
pytest --cov=src --cov-report=html
# Run specific test file
pytest tests/test_database.py
# Run with verbose output
pytest -v
# View coverage report
start htmlcov/index.html
🗄️ Database Schema
llm_traces Table
| Column | Type | Description |
|---|---|---|
id |
String (PK) | Unique trace identifier |
timestamp |
DateTime | When the trace was created |
name |
String | Function/operation name |
model |
String | LLM model used (e.g., gpt-3.5-turbo) |
latency_ms |
Float | Total request latency in milliseconds |
prompt_tokens |
Integer | Number of input tokens |
completion_tokens |
Integer | Number of output tokens |
total_cost |
Float | Calculated cost in USD |
metadata_json |
JSON | Additional metadata (TTFT, streaming, etc.) |
model_prices Table
| Column | Type | Description |
|---|---|---|
model |
String (PK) | Model name |
input_1k_price |
Float | Cost per 1K input tokens (USD) |
output_1k_price |
Float | Cost per 1K output tokens (USD) |
Cost Tracking
The system automatically calculates costs based on:
- Token Counts: Captured from API responses or calculated via tiktoken
- Pricing Table: Local
model_pricestable with current rates - Formula:
cost = (prompt_tokens/1000 * input_price) + (completion_tokens/1000 * output_price)
Current Pricing (2026 estimates)
| Model | Input (per 1K tokens) | Output (per 1K tokens) |
|---|---|---|
| gpt-3.5-turbo | $0.0005 | $0.0015 |
| gpt-4o | $0.0025 | $0.010 |
| gpt-4o-mini | $0.00015 | $0.0006 |
Note: Update prices in database.py seed_prices() method as OpenAI pricing changes.
📚 API Reference
ObsHub
Main orchestration hub for observability.
hub = ObsHub(
langsmith_api_key: str, # Your LangSmith API key
db_manager: DBManager # Database manager instance
)
Methods:
observe(name: str)- Decorator to instrument functionscapture(data: dict)- Manually capture trace dataworker()- Background worker (run as async task)
DBManager
Database operations manager.
db = DBManager(db_url: str = "sqlite+aiosqlite:///./obs_hub.db")
Methods:
init_db()- Initialize database schemaseed_prices()- Seed pricing tablesave_trace(data: dict)- Save trace (simple)calculate_and_save(data: dict)- Calculate cost and save
@observe Decorator
Automatically instruments async functions.
@hub.observe(name="function_name")
async def my_function():
# Your code here
pass
Captured Metrics:
- Latency (wall-clock time)
- TTFT (for streaming)
- Token counts (prompt + completion)
- Cost (calculated automatically)
- Model name
- Custom metadata
🐛 Troubleshooting
Import Errors
Problem: ModuleNotFoundError: No module named 'src'
Solution: Install package in editable mode:
pip install -e .
Package Name with Hyphen
Problem: Python doesn't allow hyphens in package names
Solution: Imports use underscores while directory uses hyphens:
# Directory: src/ignis-observability/
# Import: from src.ignis_observability import ...
Authentication Errors
Problem: AuthenticationError from OpenAI or LangSmith
Solution:
- Check
.envfile has correct API keys - Verify keys are active (not revoked)
- Ensure OpenAI account has credits
- Test keys independently
No Traces in LangSmith
Problem: Traces not appearing in LangSmith dashboard
Solutions:
- Verify
LANGCHAIN_TRACING_V2=truein.env - Check
LANGCHAIN_PROJECTname matches existing project - Wait a few seconds (traces are batched)
- Check console for error messages
- Verify API key has write permissions
Database Errors
Problem: SQLAlchemy or aiosqlite errors
Solutions:
- Delete existing
obs_hub.dbfile - Run
init_db()again - Check file permissions
- Verify
aiosqliteis installed
Streaming Not Working
Problem: Streaming metrics not captured
Solution:
- Ensure you're using
async forto consume the stream completely - The decorator wraps the stream, so all chunks must be consumed
- Check that
stream=Trueis passed to OpenAI API
Contributing
Contributions welcome! Areas for improvement:
- Add more observability providers (LangWatch, Weights & Biases, etc.)
- Implement the sync service (pull data from external tools)
- Add dashboard visualization
- Support for other LLM providers (Anthropic, etc.)
- Distributed tracing support
- Performance optimizations
📄 License
[Your License Here]
Refrences
Built with:
- LangSmith - Cloud-based LLM tracing
- OpenAI - LLM provider
- SQLAlchemy - Database ORM
- tiktoken - Token counting
- PyPI Publish Guide - Step-by-step release and publishing checklist
Release Notes
- Sync pipeline, unified analytics API, security remediation — dev → main merge adding multi-provider sync ingestion, unified analytics endpoints, and DB credential remediation.
Questions? Open an issue or check the examples directory for more details.
Ready to monitor your LLM applications? Start with examples/01_basic_example.py!
Metadata
Release files for ignis-observability 0.3.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ignis_observability-0.3.5.tar.gz | 100.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ignis_observability-0.3.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 174.4 kB
Release files / ignis_observability-0.3.5.tar.gz
| Download URL | ignis_observability-0.3.5.tar.gz |
|---|---|
| Size | 100.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
caee2291c8a5a8e654debaf0b8a03547a3e49d18766a78bf924db87114a57313
|
|
BLAKE2b-256 checksum How to use checksums |
4a369adbf3ad431355242a22b5403e32576171b91f7b6ca3a8d112f11571d5c7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logRelease files / ignis_observability-0.3.5-py3-none-any.whl
| Download URL | ignis_observability-0.3.5-py3-none-any.whl |
|---|---|
| Size | 73.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
254506dacd6d468146b21a5554af1b05434c1aad6c9cf162a83ee1b6cbf92fed
|
|
BLAKE2b-256 checksum How to use checksums |
583c7f700489230f2b56307388b307c80d64d4627ade00e80059c592607154ad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency log