Prometheus API¶
A Python toolkit that provides two main components:
- StorageClient -- full-featured client for storage operations (sync and async) backed by DeepLynx
- PrometheusAgent -- base class for building A2A-enabled agents with minimal boilerplate
Installation¶
# Basic (StorageClient only)
pip install "git+https://github.com/idaholab/prometheus_core.git#subdirectory=sdk"
# With agent support
pip install "prometheus-api[agent] @ git+https://github.com/idaholab/prometheus_core.git#subdirectory=sdk"
# Editable / development
git clone https://github.com/idaholab/prometheus_core.git
cd prometheus_core/sdk
pip install -e ".[dev]"
Dependencies¶
httpx>=0.24.0pydantic>=2.0.0python-dateutil>=2.8.0python-dotenv>=1.0.0
starlette>=0.27.0uvicorn[standard]>=0.27.0a2a-sdk[http-server]>=0.3.0
pytest,pytest-asyncio,pytest-covblack,mypy,ruff
StorageClient¶
Configuration¶
Create a .env file (see .env.example in the repo):
# Authentication -- choose one method
# Method 1: API key pair (recommended)
DEEPLYNX_KEY=your-api-key
DEEPLYNX_SECRET=your-api-secret
# Method 2: JWT token
# DEEPLYNX_JWT_TOKEN=your-jwt-token
# Required
DEEPLYNX_ORG_ID=123
DEEPLYNX_PROJECT_ID=456
# Optional
DEEPLYNX_DATA_SOURCE_ID=789
DEEPLYNX_BASE_URL=https://deeplynx.inl.gov/api/v1
DEEPLYNX_TIMEOUT=30.0
DEEPLYNX_MAX_RETRIES=3
DEEPLYNX_VERIFY_SSL=true
Load it:
from prometheus_api.storage_client import ConnectionConfig
config = ConnectionConfig.from_env() # reads .env automatically
Or configure manually:
config = ConnectionConfig(
organization_id=123,
project_id=456,
api_key="your-api-key",
api_secret="your-api-secret",
)
Synchronous usage¶
from prometheus_api.storage_client import StorageClient, ItemCreate, ItemUpdate
from pathlib import Path
config = ConnectionConfig.from_env()
with StorageClient(config) as client:
# Items
item = client.items.create(ItemCreate(
name="Experiment A",
properties={"status": "active", "priority": 5}
))
item = client.items.get(item_id=item.id)
client.items.update(item.id, ItemUpdate(name="Experiment A (revised)"))
client.items.delete(item.id)
# Pagination
page = client.items.list(page=1, page_size=100)
for item in client.items.iter_all(page_size=50):
print(item.name)
# Bulk operations
items = client.items.bulk_create([
ItemCreate(name=f"Item {i}") for i in range(100)
])
# Tags
client.items.add_tags(item_id=1, tags=["reviewed"])
client.items.remove_tags(item_id=1, tags=["draft"])
# Files
meta = client.files.upload(Path("data.csv"), description="Results")
client.files.upload_chunked(Path("large.zip"), chunk_size=10*1024*1024)
client.files.download(file_id=meta.id, output_path=Path("out.csv"))
# Relationships
from prometheus_api.storage_client import RelationshipCreate
client.relationships.create(RelationshipCreate(
from_item_id=1, to_item_id=2,
relationship_type="depends_on"
))
rels = client.relationships.get_item_relationships(
item_id=1, direction="outgoing"
)
# Search
results = client.search.query("experiment data")
results = client.search.by_tags(["reviewed"])
results = client.search.advanced_query({
"properties.status": "active"
})
# Timeseries
client.timeseries.upload(Path("sensor.csv"), data_source_id=456)
client.timeseries.query(
data_source_id=456,
sql_query="SELECT * FROM sensor_data WHERE timestamp > '2024-01-01'"
)
Asynchronous usage¶
import asyncio
from prometheus_api.storage_client import AsyncStorageClient, ConnectionConfig, ItemCreate
config = ConnectionConfig.from_env()
async def main():
async with AsyncStorageClient(config) as client:
item = await client.items.create(ItemCreate(name="Test"))
async for item in client.items.iter_all(page_size=50):
print(item.name)
# Concurrent operations
results = await asyncio.gather(
client.search.query("test"),
client.items.list(),
)
asyncio.run(main())
Data models¶
Items
from prometheus_api.storage_client import Item, ItemCreate, ItemUpdate, Tag
ItemCreate(name="...", description="...", properties={}, tags=["..."])
ItemUpdate(name="...", properties={}) # all fields optional
Item(id=1, name="...", properties={}, tags=[Tag(...)], created_at=..., updated_at=...)
Relationships
from prometheus_api.storage_client import Relationship, RelationshipCreate
RelationshipCreate(
from_item_id=1, to_item_id=2,
relationship_type="depends_on", # also: "related_to", "has_data_file"
properties={}
)
Files
from prometheus_api.storage_client import FileMetadata
FileMetadata(id=1, name="data.csv", file_type="csv", size=1024, ...)
Pagination
from prometheus_api.storage_client import Page, PageInfo
page = client.items.list(page=1, page_size=100)
page.items # list of Item
page.page_info # PageInfo with total_count, has_next, total_pages
Error handling¶
from prometheus_api.storage_client import (
StorageClientError, # base
AuthenticationError, # 401
ItemNotFoundError, # 404 (has .item_id)
ValidationError, # 4xx (has .details)
APIError, # other (has .status_code, .response_data)
)
try:
client.items.get(item_id=999)
except ItemNotFoundError as e:
print(f"Item {e.item_id} not found")
except AuthenticationError:
print("Invalid credentials")
PrometheusAgent¶
Base class that handles A2A protocol boilerplate. You override one method -- process_analysis -- and call run().
Minimal example¶
from prometheus_api.agent import PrometheusAgent
class MyAgent(PrometheusAgent):
async def process_analysis(self, params, context, event_queue):
data = params.get("input_data")
return {"status": "success", "result": data}
if __name__ == "__main__":
agent = MyAgent(agent_name="My Agent")
agent.run()
Each agent needs an agent_card.json alongside the script:
{
"name": "My Agent",
"description": "What this agent does",
"version": "1.0.0",
"capabilities": {
"streaming": false,
"pushNotifications": false
},
"skills": [
{
"id": "process_analysis",
"name": "Process Analysis",
"description": "Analyzes input data",
"tags": ["analysis"]
}
],
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"]
}
Configuration¶
# Server
AGENT_PORT=8000
AGENT_HOST=localhost
# Authentication
AGENT_SECRET=your-secret # enables Bearer token auth
AGENT_SECRET_REQUIRED=true
# LiteLLM agent gateway (optional, used for agent registration/routing)
REGISTRY_URL=http://litellm:4000
LITELLM_MASTER_KEY=your-litellm-master-key
# Agent-owned workflow template directory (optional)
AGENT_WORKFLOWS=workflows
# Enterprise proxy
HTTP_PROXY=...
Agent-owned workflow templates¶
PrometheusAgent can publish workflow templates owned by the agent. At startup it loads *.yaml, *.yml, and
*.json files from workflows/ beside agent_card.json, or from AGENT_WORKFLOWS when that environment variable
is set. Loaded workflow definitions are inserted into the served raw agent card so the client-agent can discover
and compile them into the platform workflow catalog.
See Workflow Templates for the full schema, file formats, and ownership model.
Progress updates¶
For long-running tasks, send status updates to the client:
from prometheus_api.agent import (
PrometheusAgent, EventQueue, RequestContext,
TaskState, TaskStatus, TaskStatusUpdateEvent,
)
class LongRunningAgent(PrometheusAgent):
async def process_analysis(self, params, context, event_queue):
# Notify: working
await event_queue.enqueue_event(
TaskStatusUpdateEvent(
taskId=context.task_id,
contextId=context.context_id,
final=False,
status=TaskStatus(state=TaskState.working),
)
)
result = await self.do_heavy_work(params)
return {"status": "success", "result": result}
Using StorageClient inside an agent¶
from prometheus_api.agent import PrometheusAgent
from prometheus_api.storage_client import StorageClient, ConnectionConfig, ItemCreate
class StorageAgent(PrometheusAgent):
def __init__(self):
super().__init__("Storage Agent")
self.storage_config = ConnectionConfig.from_env()
async def process_analysis(self, params, context, event_queue):
with StorageClient(self.storage_config) as client:
item = client.items.create(ItemCreate(
name=params.get("name", "Unknown"),
properties=params
))
return {"status": "success", "item_id": item.id}
Built-in endpoints¶
Every agent automatically exposes:
| Path | Description |
|---|---|
/healthz |
Health check (exempt from auth) |
/.well-known/agent.json |
Agent card (exempt from auth) |
/message |
A2A message endpoint (JSON-RPC 2.0) |
Agent lifecycle¶
When agent.run() is called:
- Loads
agent_card.jsonfrom disk - Loads agent-owned workflow template files and inserts them into the raw agent card
- Creates an executor wrapping
process_analysis() - Sets up a Starlette HTTP server with the A2A SDK
- Adds health endpoint and Bearer token middleware (if
AGENT_SECRETis set) - Registers with LiteLLM (if
REGISTRY_URLis set) - Starts uvicorn
Testing¶
cd sdk
# All tests
pytest tests/
# Unit tests only (no external dependencies)
pytest -m unit tests/
# With coverage
pytest --cov=prometheus_api tests/
Test markers: unit, integration (requires .env), slow.
Package structure¶
prometheus_api/
prometheus_api/
storage_client/
client.py # Synchronous client
async_client.py # Asynchronous client
connection.py # ConnectionConfig
auth.py # Token refresh handlers
exceptions.py # Custom exceptions
models/ # Item, File, Relationship, Pagination, Timeseries
resources/ # Sync resource handlers (items, files, etc.)
async_resources/ # Async resource handlers
agent/
base.py # PrometheusAgent, PrometheusAgentExecutor
middleware.py # BearerTokenMiddleware
examples/ # Working code examples
tests/ # pytest suite