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
35 changes: 21 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ pip install overmind

## Quick Start

### Use default Overmind agent
### Use Overmind Proxy

Get your free Overmind API key at [overmind.evallab.dev](https://overmind.evallab.dev)

Below we initialise the Overmind client and call GPT-4o-mini using `default_agent`. This will run our `reject_prompt_injection` and `reject_irrelevant_answer` policies.
Below we initialise the Overmind client and call GPT-5-mini with `anonymize_pii` and `reject_irrelevant_answer` policies. This will prevent PII data leakage and ensure only relevant answers are produced.
```python
import os
from overmind.client import OvermindClient
Expand All @@ -40,10 +40,12 @@ overmind = OvermindClient()


# Use existing OpenAI client methods
response = overmind.openai.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Tell me a joke about LLMs"}],
agent_id="default_agent"
response = overmind.openai.responses.create(
model='gpt-5-mini',
input="Should I switch my mortgage now or wait for a year to have a lower interest rate?",
# Overmind built-in policies
input_policies=['anonymize_pii'],
output_policies=['reject_irrelevant_answer'],
)

response.summary()
Expand Down Expand Up @@ -74,29 +76,34 @@ output_llm_judge_criteria = {
}
}

messages = [
input_messages = [
{
"role": "user",
"content": "Hi my name is Jon, account number 20194812. Should I switch my mortgage now or wait for a year to have a lower interest rate?"
}
]

# Use existing OpenAI client methods but now you can pass your policies
response = overmind.openai.chat.completions.create(
model='gpt-4o-mini',
messages=messages,
result = overmind.openai.responses.create(
model='gpt-5-mini',
input=input_messages,
input_policies=[input_pii_policy],
output_policies=[output_llm_judge_criteria]
)

response.summary()
result.summary()
```
### Use Overmind Layers

For more complex use cases you can choose Overmind Layers - an API to call standalone policies without relying on us to call LLMs.

This use case is best demonstrated in our [LangGraph integration tutorial](https://github.com/overmind-core/overmind-python/blob/main/docs/Overmind%20Layers%20%26%20LangGraph%20tutorial.ipynb), although the Layers can be used with any framework.

## Further usage

There is a more detailed [tutorial notebook](https://github.com/overmind-core/overmind-python/blob/main/docs/overmind_tutorial.ipynb) available.
There are a more detailed tutorials available for [Overmind Proxy](https://github.com/overmind-core/overmind-python/blob/main/docs/Overmind%20Proxy%20tutorial.ipynb) and [Overmind Layers & LangGraph integration](https://github.com/overmind-core/overmind-python/blob/main/docs/Overmind%20Layers%20%26%20LangGraph%20tutorial.ipynb).

We are not storing your API keys and you are solely responsible for managing them and the associated costs.

On ours side we run policy executions for free as this is an alpha stage product. We may impose usage limits and scale our services up and down from time to time.
On our side we run policy executions for free as this is an alpha stage product. We may impose usage limits and scale our services up and down from time to time.

We appreciate any feedback, collaboration or other suggestions. You can reach out at [support@evallab.dev](mailto:support@evallab.dev)
1,181 changes: 590 additions & 591 deletions docs/Overmind Proxy tutorial.ipynb

Large diffs are not rendered by default.

17 changes: 8 additions & 9 deletions overmind/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
import requests

from .exceptions import OvermindAPIError, OvermindAuthenticationError, OvermindError
from .models import InvocationResponse, LayerResponse
from .models import LayerResponse
from .agents import AgentsClient
from .policies import PoliciesClient
from .invocations import InvocationsClient
from .utils.api_settings import get_api_settings
from .utils.serializers import serialize
from .models import ProxyRunResponse

# Mapping of common environment variables to provider parameter names
COMMON_ENV_VARS = {
Expand Down Expand Up @@ -61,7 +62,7 @@ def __call__(self, *args, **kwargs):
# Invoke the provider through the Overmind API
return self.client.invoke(
client_path=client_path,
client_call_params=kwargs,
client_call_params=serialize(kwargs),
input_policies=input_policies,
output_policies=output_policies,
agent_id=agent_id,
Expand All @@ -76,7 +77,6 @@ class OvermindClient:
- Dynamic provider access (e.g., client.openai.chat.completions.create)
- Agent management via client.agents.{methods}
- Policy management via client.policies.{methods}
- Invocation management via client.invocations.{methods}
"""

def __init__(
Expand Down Expand Up @@ -123,7 +123,6 @@ def __init__(
# Initialize sub-clients
self.agents = AgentsClient(self)
self.policies = PoliciesClient(self)
self.invocations = InvocationsClient(self)

# Cache for provider proxies
self._provider_proxies = {}
Expand Down Expand Up @@ -192,7 +191,7 @@ def invoke(
client_init_params: Optional[Dict[str, Any]] = None,
input_policies: Optional[List[str]] = None,
output_policies: Optional[List[str]] = None,
) -> InvocationResponse:
) -> ProxyRunResponse:
"""
Invoke an AI provider through the Overmind API.

Expand All @@ -205,7 +204,7 @@ def invoke(
output_policies: Output policies to apply

Returns:
InvocationResponse object
ProxyRunResponse object
"""
# Use provided client_init_params or fall back to stored provider_parameters
init_params = client_init_params or self.provider_parameters
Expand All @@ -219,10 +218,10 @@ def invoke(
}

response_data = self._make_request(
"POST", f"invocations/invoke/{client_path}", data=payload
"POST", f"proxy/run/{client_path}", data=payload
)

return InvocationResponse(**response_data)
return ProxyRunResponse(**response_data)


class OvermindLayersClient:
Expand Down
96 changes: 0 additions & 96 deletions overmind/formatters.py

This file was deleted.

34 changes: 0 additions & 34 deletions overmind/invocations.py

This file was deleted.

21 changes: 6 additions & 15 deletions overmind/langchain/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,10 @@
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
import os
import json
from langgraph.graph import StateGraph
import logging
import sys
from typing import Optional
from overmind.utils.api_settings import get_api_settings


def pydantic_serializer(obj):
"""A JSON serializer for objects with a .model_dump() method."""
if hasattr(obj, "model_dump"):
return obj.model_dump()
raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")


def serialize(obj):
return json.dumps(obj, default=pydantic_serializer)
from overmind.utils.serializers import serialize


class OvermindObservabilityCallback(BaseCallbackHandler):
Expand Down Expand Up @@ -113,6 +99,11 @@ def on_chain_end(self, outputs, *, run_id, **kwargs):
self.run_spans[run_id].set_attribute(
"policy_results", serialize(outputs.pop("policy_results"))
)
# can't add links to the span that has been started and there is no clean way to pass
# this span to the backend at the layer run time (since it happening in a downstream node)
self.run_spans[run_id].set_attribute(
"span_context", serialize(outputs.pop("span_context"))
)

self.run_spans[run_id].set_attribute("outputs", serialize(outputs))
self.run_spans[run_id].set_status(trace.Status(trace.StatusCode.OK))
Expand Down
52 changes: 14 additions & 38 deletions overmind/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import Any, Dict, List, Optional
import pprint
from pydantic import BaseModel, Field, field_validator, model_validator
from .formatters import summarise_invocation
from .utils.formatters import summarize_proxy_run
from rich.console import Console
from rich.pretty import Pretty
import io
Expand Down Expand Up @@ -137,47 +137,23 @@ class PolicyResponse(ReadableBaseModel):
is_built_in: Optional[bool] = False


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

invocation_id: str
agent_id: str
raw_input: str
processed_input: Optional[str]
raw_output: Optional[str]
processed_output: Optional[str]
invocation_results: Dict[str, Any]
policy_results: Dict[str, Any]
llm_client_response: Optional[Dict[str, Any]]
business_id: str
created_at: Optional[datetime]
updated_at: Optional[datetime]
overall_policy_outcome: str
processed_data: str
span_context: Dict[str, Any]

def summary(self) -> None:
summarise_invocation(self)

class ProxyRunResponse(ReadableBaseModel):
"""Model for proxy run response data."""

class InvokeRequest(ReadableBaseModel):
"""Model for invoke request payload."""
llm_client_response: Dict[str, Any]
input_layer_results: Dict[str, Any]
output_layer_results: Dict[str, Any]
processed_output: Any
processed_input: Any

agent_id: str = Field(default="default_agent", description="Agent ID to use")
client_call_params: Dict[str, Any] = Field(
..., description="Parameters for the client call"
)
client_init_params: Optional[Dict[str, Any]] = Field(
default={}, description="Parameters for client initialization"
)
input_policies: Optional[List[str]] = Field(
default=None, description="Input policies to apply"
)
output_policies: Optional[List[str]] = Field(
default=None, description="Output policies to apply"
)


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

policy_results: Dict[str, Any]
overall_policy_outcome: str
processed_data: str
def summary(self) -> None:
summarize_proxy_run(self)
Loading