API Reference
March 7, 2025 · View on GitHub
This document provides a comprehensive reference for Hapax's public API.
Core API
Decorators
@ops
Creates a type-safe operation from a function.
from hapax import ops
@ops(
name: Optional[str] = None,
description: Optional[str] = None,
tags: Optional[List[str]] = None,
metadata: Optional[Dict[str, Any]] = None,
openlit_config: Optional[Dict[str, Any]] = None
)
def my_operation(input_data: InputType) -> OutputType:
# Implementation
return output
Parameters:
name: Optional name of the operation (defaults to function name)description: Optional descriptiontags: Optional list of tags for categorizationmetadata: Optional dictionary of metadataopenlit_config: Optional OpenLIT configuration
Returns:
- An
Operationinstance
@graph
Creates a graph from a function that returns a composition of operations.
from hapax import graph
@graph(
name: Optional[str] = None,
description: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None
)
def my_graph(input_data: InputType) -> OutputType:
return op1 >> op2 >> op3
Parameters:
name: Optional name of the graph (defaults to function name)description: Optional descriptionmetadata: Optional dictionary of metadata
Returns:
- A
Graphinstance
@eval
Adds evaluation capabilities to a function.
from hapax import eval
@eval(
evals: List[str] = ["all"],
threshold: float = 0.5,
metadata: Dict[str, Any] = {},
openlit_config: Dict[str, Any] = {},
cache_results: bool = True,
use_openlit: bool = False,
openlit_provider: Optional[str] = None
)
def generate_content(input_data: InputType) -> str:
# Implementation
return output
Parameters:
evals: List of evaluators to usethreshold: Threshold score for evaluation (0.0 to 1.0)metadata: Additional metadata for evaluationopenlit_config: OpenLIT configurationcache_results: Whether to cache evaluation resultsuse_openlit: Whether to use OpenLIT evaluatorsopenlit_provider: Provider for OpenLIT evaluations
Returns:
- The decorated function with evaluation capabilities
Classes
Operation
Represents a type-safe operation in a graph.
from hapax import Operation
# Usually created via @ops decorator
operation = Operation(
func: Callable[[T], U],
config: OpConfig
)
Methods:
__call__(input_data: T) -> U: Execute the operationcompose(other: Operation[U, V]) -> Operation[T, V]: Compose with another operation__rshift__(other: Operation[U, V]) -> Operation[T, V]: Operator version of compose
Graph
Represents a computational graph.
from hapax import Graph
# Create a graph
graph = Graph(
name: str,
description: Optional[str] = None
)
# Fluent interface
graph.then(operation)
graph.branch(op1, op2, op3)
graph.merge(merger_func)
graph.condition(predicate, true_branch, false_branch)
graph.loop(operation, condition, max_iterations)
Methods:
execute(input_data: Any) -> Any: Execute the graphvalidate() -> None: Validate the graphvisualize(filename: Optional[str] = None) -> None: Generate a visualizationthen(operation: Operation) -> Graph: Add a sequential operationbranch(*operations: Operation) -> Graph: Add parallel operationsmerge(merger: Callable[[List[Any]], Any]) -> Graph: Merge branch resultscondition(predicate: Callable[[Any], bool], true_branch: Operation, false_branch: Operation) -> Graph: Add conditional logicloop(operation: Operation, condition: Callable[[Any], bool], max_iterations: Optional[int] = None) -> Graph: Add a loopwith_gpu_monitoring(enabled: bool = True, sample_rate_seconds: int = 1, custom_config: Optional[Dict[str, Any]] = None) -> Graph: Enable GPU monitoringwith_evaluation(eval_type: str = "all", threshold: float = 0.5, provider: str = "openai", fail_on_evaluation: bool = False, model: Optional[str] = None, api_key: Optional[str] = None, custom_config: Optional[Dict[str, Any]] = None) -> Graph: Add evaluation
OpConfig
Configuration for an operation.
from hapax import OpConfig
config = OpConfig(
name: str,
description: Optional[str] = None,
tags: List[str] = [],
metadata: Dict[str, Any] = {},
openlit_config: Optional[Dict[str, Any]] = None
)
Flow Control
Branch
Creates parallel execution branches.
from hapax.core.flow import Branch
branch = Branch(name: str)
branch.add(*operations: Operation)
Merge
Merges results from parallel branches.
from hapax.core.flow import Merge
merge = Merge(
name: str,
merger: Callable[[List[Any]], Any]
)
Condition
Adds conditional logic to a graph.
from hapax.core.flow import Condition
condition = Condition(
name: str,
predicate: Callable[[Any], bool],
true_branch: Operation,
false_branch: Operation
)
Loop
Repeats an operation until a condition is met.
from hapax.core.flow import Loop
loop = Loop(
name: str,
operation: Operation,
condition: Callable[[Any], bool],
max_iterations: Optional[int] = None
)
Monitoring & Evaluations API
OpenLIT Configuration
from hapax import set_openlit_config, get_openlit_config
# Set global OpenLIT configuration
set_openlit_config({
"otlp_endpoint": "http://localhost:4318",
"environment": "development",
"application_name": "my_app",
"trace_content": True,
"disable_metrics": False
})
# Get current configuration
current_config = get_openlit_config()
GPU Monitoring
from hapax import enable_gpu_monitoring, get_gpu_metrics
# Enable GPU monitoring
enable_gpu_monitoring(
otlp_endpoint: Optional[str] = None,
sample_rate_seconds: int = 1,
application_name: Optional[str] = None,
environment: Optional[str] = None,
custom_config: Optional[Dict[str, Any]] = None
)
# Get current GPU metrics
metrics = get_gpu_metrics()
Evaluators
from hapax import (
OpenLITEvaluator,
HallucinationEvaluator,
BiasEvaluator,
ToxicityEvaluator,
AllEvaluator
)
# Create an evaluator
evaluator = HallucinationEvaluator(
provider: str = "openai",
threshold: float = 0.5,
collect_metrics: bool = True,
model: Optional[str] = None,
api_key: Optional[str] = None,
base_url: Optional[str] = None
)
# Evaluate text
result = evaluator.evaluate(
text: str,
contexts: List[str],
prompt: Optional[str] = None
)
OpenLIT and Observability Architecture
Hapax provides comprehensive observability through deep integration with OpenLIT (Open Lineage In Tracing), which is built on top of the OpenTelemetry standard. This allows for detailed monitoring, tracing, and metrics collection at all levels of the execution stack.
Observability Integration
Hapax's observability architecture has three key components:
- Automatic Instrumentation: All operations and graphs are automatically traced
- Hierarchical Spans: Traces capture the full execution tree with proper parent-child relationships
- Rich Context: Input/output data, types, and errors are captured in spans
┌─────────────────────────────────────────────────────┐
│ Hapax Application │
└───────────────────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ OpenLIT Instrumentation │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ Trace Data │ │ Metrics │ │ Logs │ │
│ └──────┬──────┘ └──────┬──────┘ └────┬─────┘ │
└─────────┼─────────────────┼─────────────────┼───────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────┐
│ OpenTelemetry Protocol │
└───────────────────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ OpenTelemetry Collector/Backend │
│ (Jaeger, Zipkin, Prometheus, Grafana, etc.) │
└─────────────────────────────────────────────────────┘
Automatic Tracing Implementation
Hapax creates OpenTelemetry spans at multiple levels:
- Graph-Level Spans: Each graph execution creates a root span
- Node-Level Spans: Each operation creates a child span
- Branch/Merge Spans: Flow control operations create their own spans
Implementation in Hapax's core:
# Inside Graph.execute method
with self._tracer.start_as_current_span(
name=f"graph.{self.name}",
attributes={
"graph.name": self.name,
"graph.num_operations": len(self.operations),
"graph.num_edges": len(self.edges)
}
) as graph_span:
# Execute operations with child spans
for node_name in nx.topological_sort(self._graph):
with self._tracer.start_as_current_span(
name=f"node.{node_name}",
attributes={"node.type": "operation"}
) as node_span:
# Operation execution...
OpenLIT Configuration
Hapax provides a global OpenLIT configuration that applies to all operations:
import openlit
from hapax import set_openlit_config
# Initialize OpenLIT
openlit.init(
otlp_endpoint="http://localhost:4318",
environment="development",
application_name="my_app",
trace_content=True
)
# Configure Hapax to use OpenLIT
set_openlit_config({
"trace_content": True, # Trace operation inputs/outputs
"disable_metrics": False, # Enable metrics collection
"log_level": "INFO" # Set logging level
})
This configuration can also be set at the operation level through the @ops decorator:
@ops(
name="process_text",
openlit_config={
"trace_content": True,
"disable_metrics": False
}
)
def process_text(text: str) -> str:
return text.upper()
What Gets Traced
By default, Hapax traces the following:
-
Operation Execution:
- Start and end times
- Success/failure status
- Error details if applicable
-
Typed Data (when
trace_content=True):- Input values and types
- Output values and types
- Type validation results
-
Graph Structure:
- Operation dependencies
- Branch/merge relationships
- Execution order
-
Performance Metrics:
- Execution time
- Memory usage (when enabled)
- GPU utilization (when GPU monitoring is enabled)
Practical Example
Here's a complete example showing how to create a graph with OpenLIT monitoring:
import os
from typing import List, Dict
import openlit
from hapax import ops, Graph, set_openlit_config
# Initialize OpenLIT
openlit.init(
otlp_endpoint=os.getenv("OPENLIT_ENDPOINT", "http://localhost:4318"),
environment="development",
application_name="text_processor"
)
# Configure global OpenLIT settings for Hapax
set_openlit_config({
"trace_content": True,
"disable_metrics": False
})
# Define operations - automatically traced
@ops(name="tokenize", tags=["preprocessing"])
def tokenize(text: str) -> List[str]:
return text.split()
@ops(name="count_words", tags=["analysis"])
def count_words(tokens: List[str]) -> Dict[str, int]:
from collections import Counter
return dict(Counter(tokens))
# Build pipeline with automatic tracing
pipeline = (
Graph("text_analysis")
.then(tokenize) # Each operation will have its own span
.then(count_words)
)
# Execute pipeline - traced from start to finish
result = pipeline.execute("Hello world! Hello Hapax!")
When this code executes, it generates spans for:
- The
text_analysisgraph execution - The
tokenizeoperation - The
count_wordsoperation - Each with proper parent-child relationships and timing data
Integration with External Monitoring Systems
The traces and metrics can be consumed by any OpenTelemetry-compatible system:
-
Tracing Systems:
- Jaeger
- Zipkin
- Datadog APM
- New Relic
-
Metrics Systems:
- Prometheus
- Grafana
- InfluxDB
- Datadog Metrics
-
Logging Systems:
- ELK Stack
- Loki
- Cloud Logging (AWS CloudWatch, GCP Logging)
Advanced Configuration
For more advanced control over OpenLIT and tracing:
# Per-operation custom trace content
@ops(
name="process_sensitive_data",
openlit_config={
"trace_content": False, # Don't trace sensitive data
"trace_metadata": True, # Still trace metadata
"sampling_rate": 0.1 # Only trace 10% of executions
}
)
def process_sensitive_data(data: Dict[str, Any]) -> Dict[str, Any]:
# Process sensitive data
return processed
# Custom span attributes
@ops(
name="important_operation",
openlit_config={
"span_attributes": {
"service.version": "1.2.3",
"priority": "high"
}
}
)
def important_operation(data: Any) -> Any:
# Important processing
return result
Exceptions
EvaluationError
Raised when evaluations fail.
try:
result = function_with_eval(input_data)
except EvaluationError as e:
print(f"Failed evaluations: {e.failed_evals}")
print(f"Scores: {e.scores}")
GraphValidationError
Raised when graph validation fails.
try:
graph.validate()
except GraphValidationError as e:
print(f"Validation error: {e}")
GraphExecutionError
Raised when graph execution fails.
try:
result = graph.execute(input_data)
except GraphExecutionError as e:
print(f"Failed at node: {e.node_name}")
print(f"Node errors: {e.node_errors}")
print(f"Partial results: {e.partial_results}")
BranchError
Raised when errors occur in parallel branches.
try:
result = branch_operation(input_data)
except BranchError as e:
print(f"Branch errors: {e.branch_errors}")
print(f"Partial results: {e.partial_results}")
Utility Functions
Register Custom Evaluators
from hapax.core.decorators import register_evaluator
class MyEvaluator:
def __init__(self, **config):
self.config = config
def evaluate(self, text: str) -> float:
# Return score between 0 and 1
return 0.5
register_evaluator("my_evaluator", MyEvaluator)