An observable production-ready open-source AI agent framework.
Project description
Tantra
A simple, minimal, observable, production-ready AI agent framework.
Tantra is designed for developers who build AI agents for production. It combines simplicity with powerful features including first-class observability, integrated evaluation, and extensible provider support.
Features
- Minimal by Default - Optional modules for advanced features
- Observable by Design - Every operation produces structured logs with cost tracking
- Testable First - Integrated evaluation framework as easy as writing unit tests
- Type-Safe - Full type hints with Pydantic models
- Extensible - Plugin architecture for custom LLM providers
Installation
pip install tantra
Or install from source:
git clone https://github.com/tantra-run/tantra-py.git
cd tantra
pip install -e ".[dev]"
Quick Start
Simple Agent
import asyncio
from tantra import Agent
async def main():
agent = Agent(
"openai:gpt-4o",
system_prompt="You are a helpful assistant."
)
result = await agent.run("Hello!")
print(result.output)
print(f"Cost: ${result.metadata.estimated_cost:.4f}")
asyncio.run(main())
Agent with Tools
import asyncio
from tantra import Agent, tool, ToolSet
@tool
def get_weather(city: str) -> str:
"""Get the current weather for a city.
Args:
city: The name of the city.
"""
return f"Weather in {city}: Sunny, 72F"
async def main():
agent = Agent(
"openai:gpt-4o",
tools=ToolSet([get_weather]),
system_prompt="You can check weather for users."
)
result = await agent.run("What's the weather in Tokyo?")
print(result.output)
asyncio.run(main())
Evaluation
import asyncio
from tantra import Agent, TestCase, EvaluationSuite, contains, tool_called
async def main():
agent = Agent("openai:gpt-4o-mini")
suite = EvaluationSuite()
suite.add_test(TestCase(
input="What is 2 + 2?",
matchers=[contains("4")]
))
results = await suite.run(agent)
print(f"Passed: {results.passed}/{results.metrics.total_tests}")
asyncio.run(main())
Core Concepts
Agent
The Agent class is the main entry point. It wraps an LLM provider, tools, and memory.
agent = Agent(
provider="openai:gpt-4o", # or a ModelProvider instance
tools=ToolSet([...]), # optional tools
system_prompt="...", # agent behavior
memory=ConversationMemory(), # conversation history
max_iterations=10, # prevent infinite loops
)
Tools
Tools are defined using the @tool decorator. Type hints and docstrings are automatically converted to OpenAI-compatible schemas.
@tool
def calculate(expression: str) -> str:
"""Evaluate a math expression.
Args:
expression: A mathematical expression like "2 + 2".
"""
return str(eval(expression))
Memory
Memory stores conversation history. The built-in ConversationMemory keeps messages in memory.
from tantra import ConversationMemory, WindowedMemory
# Keep all messages
memory = ConversationMemory()
# Keep only last N messages
memory = WindowedMemory(window_size=10)
Observability
Every agent run produces a trace of LogEntry objects with full visibility into:
- Prompts sent to the LLM
- LLM responses
- Tool calls and results
- Token usage and cost
result = await agent.run("Hello!")
for entry in result.trace:
print(f"[{entry.type}] {entry.data}")
print(f"Total cost: ${result.metadata.estimated_cost:.4f}")
Evaluation
The evaluation framework makes testing agents as easy as unit tests:
suite = EvaluationSuite()
suite.add_test(TestCase(
input="What's the capital of France?",
matchers=[
contains("Paris"),
cost_under(0.01),
]
))
results = await suite.run(agent)
Built-in matchers:
contains(text)- Output contains substringnot_contains(text)- Output does not contain substringmatches_regex(pattern)- Output matches regexjson_valid()- Output is valid JSONjson_schema(schema)- Output matches JSON schematool_called(name)- Tool was calledtool_not_called(name)- Tool was not calledcost_under(amount)- Cost below thresholdtokens_under(count)- Tokens below threshold
Custom Providers
Create custom providers by implementing the ModelProvider interface:
from tantra import ModelProvider, Message, ProviderResponse
class MyProvider(ModelProvider):
async def complete(self, messages, tools=None, **kwargs):
# Call your LLM API
return ProviderResponse(content="Hello!")
def count_tokens(self, messages):
return len(str(messages))
@property
def model_name(self):
return "my-model"
@property
def cost_per_1k_input(self):
return 0.001
@property
def cost_per_1k_output(self):
return 0.002
Examples
See the examples/ directory for complete examples:
01_simple_agent.py- Basic agent without tools02_tool_agent.py- Agent with tools03_streaming.py- Streaming execution04_evaluation.py- Testing agents05_custom_provider.py- Custom LLM provider
Development
# Install dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Format code
ruff format .
# Lint
ruff check .
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tantra-0.1.0.tar.gz.
File metadata
- Download URL: tantra-0.1.0.tar.gz
- Upload date:
- Size: 187.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c0308bacf63d90e9c5c249c32462c43f37bcce08a71b5a4ee5f062f7901d16d
|
|
| MD5 |
0bc07fb04fc4843076c6fe9bfc9d93cb
|
|
| BLAKE2b-256 |
b5a023036c405675701dbf3374a6977f236287315f149e9b7be5ed342fb64dbd
|
File details
Details for the file tantra-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tantra-0.1.0-py3-none-any.whl
- Upload date:
- Size: 160.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
44cbfaafdff01687fe1048648911e38923ad45de26c4232cfc75a93612ea90cb
|
|
| MD5 |
234f0d90f92da7d53b42d542e002244a
|
|
| BLAKE2b-256 |
5b48da00320552b4c5e9c5767d761645329ef0c4a8a7695d409c3341c29a7a65
|