Observed since 21 Oct, 11:41 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
aiobs
Minimal, extensible LLM observability SDK with OpenTelemetry, OpenAI and Gemini support.
aiobs is a lightweight Python library that adds observability to AI/LLM applications. Trace every call, capture inputs/outputs, measure latency, and debug failures—with just 3 lines of code. Built-in support for OpenAI and Google Gemini.
Goal: Make every AI call inspectable, measurable, and debuggable with minimal code changes.
@observeopenai>=1.0)google-genai>=1.0)# Core only
pip install aiobs
# With OpenAI support
pip install aiobs[openai]
# With Gemini support
pip install aiobs[gemini]
# With all providers
pip install aiobs[all]
An API key is required to use aiobs. Get your free API key from:
👉 https://neuralis-in.github.io/shepherd/api-keys
You can also use an OpenAI API key (link: https://platform.openai.com/account/api-keys) (including free trial/credit) to test the OpenAI examples locally.
Once you have your API key, set it as an environment variable:
export AIOBS_API_KEY=aiobs_sk_your_key_here
Or add it to your .env file:
AIOBS_API_KEY=aiobs_sk_your_key_here
Or pass directly:
observer.observe(api_key="aiobs_sk_your_key_here")
from aiobs import observer
observer.observe() # start a session and auto-instrument providers
# ... make your LLM calls (OpenAI, Gemini, etc.) ...
observer.end() # end the session
observer.flush() # write a single JSON file to disk
aiobs installs lightweight hooks into supported SDKs (OpenAI, Gemini, etc.). Whenever an LLM call or an @observe-decorated function runs, aiobs captures:
No servers. No background threads. No lock-in.
Everything stays local unless you export it.
Add labels for filtering in enterprise dashboards:
observer.observe(
labels={
"environment": "production",
"team": "ml-platform",
"project": "recommendation-engine",
}
)
Labels can also be set via environment variables (AIOBS_LABEL_*) and updated dynamically during a session:
# Dynamic label updates
observer.add_label("user_tier", "enterprise")
observer.set_labels({"experiment_id": "exp-42"})
observer.remove_label("experiment_id")
labels = observer.get_labels()
Default output file:
./llm_observability.json. Override with LLM_OBS_OUT=/path/to/file.json.
from aiobs import observer
from openai import OpenAI
observer.observe()
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Hello!"}]
)
observer.end()
observer.flush()
from aiobs import observer
from openai import OpenAI
observer.observe()
client = OpenAI()
response = client.embeddings.create(
model="text-embedding-3-small",
input="Hello world"
)
observer.end()
observer.flush()
from aiobs import observer
from google import genai
observer.observe()
client = genai.Client()
response = client.models.generate_content(
model="gemini-2.0-flash",
contents="Hello!"
)
observer.end()
observer.flush()
@observeTrace any function (sync or async) by decorating it with @observe:
from aiobs import observer, observe
@observe
def research(query: str) -> list:
# your logic here
return results
@observe(name="custom_name")
async def fetch_data(url: str) -> dict:
# async logic here
return data
observer.observe(session_name="my-pipeline")
research("What is an API?")
observer.end()
observer.flush()
| Option | Default | Description |
|---|---|---|
name | function name | Custom display name |
capture_args | True | Capture function arguments |
capture_result | True | Capture return value |
enh_prompt | False | Enable enhanced prompt analysis |
auto_enhance_after | None | Auto-enhance after N traces |
Don't capture sensitive arguments:
@observe(capture_args=False)
def login(username: str, password: str):
...
Skip large return values:
@observe(capture_result=False)
def load_dataset():
...
Mark functions for automatic prompt enhancement analysis:
from aiobs import observer, observe
@observe(enh_prompt=True, auto_enhance_after=10)
def summarize(text: str) -> str:
"""After 10 traces, auto prompt enhancer will run."""
response = client.chat.completions.create(...)
return response.choices[0].message.content
@observe(enh_prompt=True, auto_enhance_after=5)
def analyze(data: dict) -> dict:
"""Different threshold for this function."""
return process(data)
observer.observe()
summarize("Hello world")
analyze({"key": "value"})
observer.end()
observer.flush()
Captured JSON output will include:
enh_prompt_id: Unique identifier for each enhanced prompt traceauto_enhance_after: Configured threshold for auto-enhancementenh_prompt_traces: List of all enh_prompt_id values for easy lookup across multiple JSON filesSimple OpenAI example:
python example/simple-chat-completion/chat.py
Gemini example:
python example/gemini/main.py
Multi-file pipeline example:
python -m example.pipeline.main "Explain vector databases to a backend engineer"
openai or geminichat.completions.create, embeddings.create, or models.generateContentduration_mscapture_args)capture_result){
"sessions": [
{
"id": "sess_abc123",
"name": "production-pipeline",
"started_at": 1733135400.123456,
"ended_at": 1733135402.789012,
"meta": {
"pid": 12345,
"cwd": "/app"
},
"labels": {
"environment": "production"
}
}
],
"events": [
{
"session_id": "sess_abc123",
"provider": "openai",
"api": "chat.completions.create",
"request": {
"model": "gpt-4o-mini",
"messages": [
{"role": "user", "content": "What is observability?"}
]
},
"response": {
"text": "Observability is the ability to understand...",
"model": "gpt-4o-mini",
"usage": {
"prompt_tokens": 12,
"completion_tokens": 45,
"total_tokens": 57
}
},
"started_at": 1733135400.234567,
"ended_at": 1733135401.758912,
"duration_ms": 1524,
"callsite": {
"file": "/app/main.py",
"line": 15,
"function": "main"
}
}
],
"function_events": [
{
"session_id": "sess_abc123",
"provider": "function",
"api": "research",
"name": "research",
"module": "__main__",
"args": ["What is an API?"],
"kwargs": {},
"result": ["result1", "result2"],
"started_at": 1733135400.100,
"ended_at": 1733135400.113,
"duration_ms": 13,
"callsite": {
"file": "/app/main.py",
"line": 8
}
}
],
"generated_at": 1733135402.9,
"version": 1
}
Internally, the SDK structures data with Pydantic models (v2):
aiobs.Session – Session metadata (id, name, labels, timestamps)aiobs.Event – LLM provider call eventaiobs.FunctionEvent – Decorated function trace eventaiobs.ObservedEvent (Event + session_id)aiobs.ObservedFunctionEvent (FunctionEvent + session_id)aiobs.ObservabilityExport (flush payload)These are exported to allow downstream tooling to parse and validate the JSON output and to build integrations.
You can add new provider SDKs by subclassing BaseProvider:
aiobs.BaseProviderOpenAIProvider, GeminiProvider (auto-detected and installed if available)Custom provider skeleton:
from aiobs import BaseProvider, observer
class MyProvider(BaseProvider):
name = "my-provider"
@classmethod
def is_available(cls) -> bool:
try:
import my_sdk # noqa: F401
return True
except Exception:
return False
def install(self, collector):
# monkeypatch or add hooks into your SDK, then
# call collector._record_event({ ... normalized payload ... })
def unpatch():
pass
return unpatch
# Register before observe()
observer.register_provider(MyProvider())
observer.observe()
pip install aiobs[docs]
python -m sphinx -b html docs docs/_build/html
Open docs/_build/html/index.html in your browser.
Docs auto-deploy via GitHub Actions: aiobs-docs
Join the Zulip community for discussions, help, and feature requests: aiobs-zulip-chat
Download counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
opentelemetry-apiEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | opentelemetry-api>=1.20.0 | runtime |
opentelemetry-sdkEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | opentelemetry-sdk>=1.20.0 | runtime |
pydanticEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | pydantic>=2 | runtime |
google-genaiEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | google-genai>=1.0.0; extra == "all" | runtime |
openaiEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | openai<2,>=1.0.0; extra == "all" | runtime |
opentelemetry-instrumentation-google-genaiEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | opentelemetry-instrumentation-google-genai>=0.4b0; extra == "all" | runtime |
opentelemetry-instrumentation-openai-v2Environment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | opentelemetry-instrumentation-openai-v2>=2.0b0; extra == "all" | runtime |
opentelemetry-instrumentation-vertexaiEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | opentelemetry-instrumentation-vertexai>=2.0b0; extra == "all" | runtime |
python-dotenvEnvironment{
"extras": "",
"marker": "extra == \"all\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | python-dotenv>=1.0; extra == "all" | runtime |
furoEnvironment{
"extras": "",
"marker": "extra == \"docs\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | furo>=2024.1.29; extra == "docs" | runtime |
sphinx-copybuttonEnvironment{
"extras": "",
"marker": "extra == \"docs\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | sphinx-copybutton>=0.5; extra == "docs" | runtime |
sphinxEnvironment{
"extras": "",
"marker": "extra == \"docs\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | sphinx>=7; extra == "docs" | runtime |
python-dotenvEnvironment{
"extras": "",
"marker": "extra == \"example\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | python-dotenv>=1.0; extra == "example" | runtime |
google-genaiEnvironment{
"extras": "",
"marker": "extra == \"gemini\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | google-genai>=1.0.0; extra == "gemini" | runtime |
opentelemetry-instrumentation-google-genaiEnvironment{
"extras": "",
"marker": "extra == \"gemini\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | opentelemetry-instrumentation-google-genai>=0.4b0; extra == "gemini" | runtime |
openaiEnvironment{
"extras": "",
"marker": "extra == \"openai\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | openai<2,>=1.0.0; extra == "openai" | runtime |
opentelemetry-instrumentation-openai-v2Environment{
"extras": "",
"marker": "extra == \"openai\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | opentelemetry-instrumentation-openai-v2>=2.0b0; extra == "openai" | runtime |
sqlglotEnvironment{
"extras": "",
"marker": "extra == \"sql\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | sqlglot>=20.0.0; extra == "sql" | runtime |
pytestEnvironment{
"extras": "",
"marker": "extra == \"test\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | pytest>=7; extra == "test" | runtime |
opentelemetry-instrumentation-vertexaiEnvironment{
"extras": "",
"marker": "extra == \"vertexai\"",
"requires_python": ">=3.9",
"evaluation": "NOT_EVALUATED"
} | opentelemetry-instrumentation-vertexai>=2.0b0; extra == "vertexai" | runtime |
bdist_wheel · 83.61 KB · Python py3
sdist · 174.54 KB · Python source
Observed since 21 Oct, 11:41 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:995b5e455ee2ad50d324b3bee5d3103186f7df3cb0371fbeac4290ba2049e6d4