Skip to content
This repository was archived by the owner on Apr 30, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Overmind SDK

[![CI Checks](https://github.com/overmind-core/overmind-python/actions/workflows/publish.yml/badge.svg)](https://github.com/overmind-core/overmind-python/actions/workflows/publish.yml)
[![PyPI version](https://img.shields.io/pypi/v/overmind-sdk.svg)](https://pypi.org/project/overmind-sdk/)
[![PyPI version](https://img.shields.io/pypi/v/overmind.svg)](https://pypi.org/project/overmind/)

Automatic observability for LLM applications. One call to `init()` instruments your existing OpenAI, Anthropic, Google Gemini, or Agno code — no proxy, no key sharing, no import changes.

Expand All @@ -16,16 +16,16 @@ Automatic observability for LLM applications. One call to `init()` instruments y
## Installation

```bash
pip install overmind-sdk
pip install overmind
```

Install alongside your LLM provider package:

```bash
pip install overmind-sdk openai # OpenAI
pip install overmind-sdk anthropic # Anthropic
pip install overmind-sdk google-genai # Google Gemini
pip install overmind-sdk agno # Agno
pip install overmind openai # OpenAI
pip install overmind anthropic # Anthropic
pip install overmind google-genai # Google Gemini
pip install overmind agno # Agno
```

---
Expand All @@ -41,7 +41,7 @@ Sign up at [console.overmindlab.ai](https://console.overmindlab.ai) — your API
Call `init()` once at application startup, before any LLM calls:

```python
from overmind_sdk import init
from overmind import init

init(
overmind_api_key="ovr_...", # or set OVERMIND_API_KEY env var
Expand Down Expand Up @@ -75,7 +75,7 @@ Traces appear in your [Overmind dashboard](https://console.overmindlab.ai) in re
### OpenAI

```python
from overmind_sdk import init
from overmind import init
from openai import OpenAI

init(service_name="my-service", providers=["openai"])
Expand All @@ -90,7 +90,7 @@ response = client.chat.completions.create(
### Anthropic

```python
from overmind_sdk import init
from overmind import init
import anthropic

init(service_name="my-service", providers=["anthropic"])
Expand All @@ -106,7 +106,7 @@ message = client.messages.create(
### Google Gemini

```python
from overmind_sdk import init
from overmind import init
from google import genai

init(service_name="my-service", providers=["google"])
Expand All @@ -121,7 +121,7 @@ response = client.models.generate_content(
### Agno

```python
from overmind_sdk import init
from overmind import init
from agno.agent import Agent
from agno.models.openai import OpenAIChat

Expand All @@ -136,7 +136,7 @@ agent.print_response("Write a short poem about the sea.")
Pass an empty `providers` list (or omit it) to automatically instrument every supported provider that is installed:

```python
from overmind_sdk import init
from overmind import init

init(service_name="my-service") # auto-detects openai, anthropic, google, agno
```
Expand Down Expand Up @@ -173,7 +173,7 @@ init(service_name="my-service") # auto-detects openai, anthropic, google, agno
Get the OpenTelemetry tracer to create custom spans around any block of code:

```python
from overmind_sdk import init, get_tracer
from overmind import init, get_tracer

init(service_name="my-service")

Expand All @@ -189,7 +189,7 @@ with tracer.start_as_current_span("process-document") as span:
Tag the current trace with user identity. Call this in your request handler or middleware:

```python
from overmind_sdk import set_user
from overmind import set_user

# In a FastAPI middleware:
@app.middleware("http")
Expand All @@ -213,7 +213,7 @@ async def add_user_context(request: Request, call_next):
Add a custom attribute to the current span:

```python
from overmind_sdk import set_tag
from overmind import set_tag

set_tag("feature.flag", "new-checkout-flow")
set_tag("tenant.id", tenant_id)
Expand All @@ -224,7 +224,7 @@ set_tag("tenant.id", tenant_id)
Record an exception on the current span and mark it as an error:

```python
from overmind_sdk import capture_exception
from overmind import capture_exception

try:
result = risky_llm_call()
Expand All @@ -239,7 +239,7 @@ except Exception as e:

```python
import os
from overmind_sdk import init, get_tracer, set_user, set_tag, capture_exception
from overmind import init, get_tracer, set_user, set_tag, capture_exception
from openai import OpenAI

os.environ["OVERMIND_API_KEY"] = "ovr_your_key_here"
Expand Down
47 changes: 28 additions & 19 deletions overmind_sdk/__init__.py → overmind/__init__.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@
"""
Overmind Python Client

A Python client for the Overmind API that provides easy access to AI provider endpoints
with policy enforcement and automatic observability.
A Python client for the Overmind API that provides automatic observability for
LLM applications.
"""

from .client import OvermindClient
from .exceptions import OvermindAPIError, OvermindAuthenticationError, OvermindError

from .tracing import init, get_tracer, set_user, set_tag, capture_exception
from opentelemetry.overmind.prompt import PromptString

from .tracer import observe, SpanType, function, entry_point, workflow, tool

from .exceptions import OvermindAPIError, OvermindAuthenticationError, OvermindError
from .tracing import (
SpanType,
capture_exception,
entry_point,
function,
get_tracer,
init,
observe,
set_tag,
set_user,
start_span,
tool,
workflow,
)

__version__ = "0.1.32"
__version__ = "0.1.39"
__all__ = [
"OvermindClient",
"OvermindError",
"OvermindAPIError",
"OvermindAuthenticationError",
"init",
"get_tracer",
"set_user",
"set_tag",
"capture_exception",
"OvermindError",
"PromptString",
"observe",
"SpanType",
"function",
"capture_exception",
"entry_point",
"workflow",
"function",
"get_tracer",
"init",
"observe",
"set_tag",
"set_user",
"start_span",
"tool",
"workflow",
]
62 changes: 62 additions & 0 deletions overmind/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
Overmind layers client.
"""

from collections.abc import Sequence
from functools import lru_cache

import requests

from .exceptions import OvermindAPIError
from .models import LayerResponse
from .utils.api_settings import get_api_settings


class OvermindLayersClient:
def __init__(
self,
overmind_api_key: str | None = None,
base_url: str | None = None,
traces_base_url: str | None = None,
):
self.overmind_api_key, self.base_url = get_api_settings(overmind_api_key, base_url)
self.session = requests.Session()
self.session.headers.update(
{
"X-API-Token": self.overmind_api_key,
"Content-Type": "application/json",
}
)

def run_layer(
self, input_data: str, policies: Sequence[str | dict], layer_position: str, **kwargs
) -> LayerResponse:
"""
Run a layer of the Overmind API.
"""
payload = {
"input_data": input_data,
"policies": policies,
"layer_position": layer_position,
"kwargs": kwargs,
}

response_data = self.session.request("POST", f"{self.base_url}/api/v1/layers/run", json=payload)

if response_data.status_code != 200:
raise OvermindAPIError(
message=response_data.text,
status_code=response_data.status_code,
response_data=response_data.json(),
)

return LayerResponse(**response_data.json())


@lru_cache
def get_layers_client(
overmind_api_key: str | None = None,
base_url: str | None = None,
traces_base_url: str | None = None,
):
return OvermindLayersClient(overmind_api_key, base_url, traces_base_url)
5 changes: 1 addition & 4 deletions overmind_sdk/exceptions.py → overmind/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@
class OvermindError(Exception):
"""Base exception for all Overmind client errors."""

pass


class OvermindAuthenticationError(OvermindError):
"""Raised when authentication fails."""

pass


class OvermindAPIError(OvermindError):
"""Raised when the API returns an error."""

def __init__(
self, message: str, status_code: int = None, response_data: dict = None
self, message: str, status_code: int | None = None, response_data: dict | None = None
):
super().__init__(message)
self.status_code = status_code
Expand All @@ -29,4 +27,3 @@ def __init__(
class OvermindValidationError(OvermindError):
"""Raised when input validation fails."""

pass
7 changes: 4 additions & 3 deletions overmind_sdk/layers.py → overmind/layers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from overmind_sdk.client import get_layers_client, OvermindLayersClient
from overmind_sdk.models import LayerResponse
from typing import Sequence
from collections.abc import Sequence

from overmind.client import OvermindLayersClient, get_layers_client
from overmind.models import LayerResponse


class GenericOvermindLayer:
Expand Down
67 changes: 67 additions & 0 deletions overmind/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Pydantic models for the Overmind client.
"""

import io
from datetime import datetime
from typing import Any

from pydantic import BaseModel, Field
from rich.console import Console


class ReadableBaseModel(BaseModel):
"""Base model with a readable __repr__ method for better display in Jupyter notebooks."""

def __repr__(self) -> str:
"""
Generate a rich-formatted string representation for the terminal.

This is called by the Python REPL when you inspect an object.
"""
string_buffer = io.StringIO()
console = Console(file=string_buffer, force_terminal=True)
console.print(self)
return string_buffer.getvalue()


class AgentCreateRequest(ReadableBaseModel):
"""Model for creating a new agent."""

agent_id: str = Field(..., description="Unique identifier for the agent")
agent_model: str | None = Field(None, description="The AI model to use (e.g., 'gpt-5-mini')")
agent_description: str | None = Field(None, description="Description of the agent")
stats: dict[str, Any] | None = Field(default={}, description="Agent statistics")
parameters: dict[str, Any] | None = Field(default={}, description="Agent parameters")


class AgentUpdateRequest(ReadableBaseModel):
"""Model for updating an existing agent."""

agent_id: str = Field(..., description="Unique identifier for the agent")
agent_model: str | None = Field(None, description="The AI model to use")
agent_description: str | None = Field(None, description="Description of the agent")
stats: dict[str, Any] | None = Field(None, description="Agent statistics")
parameters: dict[str, Any] | None = Field(None, description="Agent parameters")


class AgentResponse(ReadableBaseModel):
"""Model for agent response data."""

agent_id: str
agent_model: str | None
agent_description: str | None
stats: dict[str, Any] | None
parameters: dict[str, Any] | None
business_id: str
created_at: datetime | None
updated_at: datetime | None


class LayerResponse(BaseModel):
"""Model for invocation response data."""

policy_results: dict[str, Any]
overall_policy_outcome: str
processed_data: str | None
span_context: dict[str, Any]
Loading
Loading