avenix 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
avenix/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """
2
+ Avenix - Python tracing library for AI requests.
3
+
4
+ Provides decorator-based tracing for AI/LLM requests with beautiful terminal output.
5
+ """
6
+
7
+ from .decorator import trace
8
+ from .tracer import Tracer
9
+
10
+ __version__ = "0.1.0"
11
+ __all__ = ["trace", "Tracer"]
avenix/decorator.py ADDED
@@ -0,0 +1,62 @@
1
+ # Trace decorator implementation
2
+
3
+ import functools
4
+ import time
5
+ from typing import Callable, Any
6
+
7
+
8
+ def trace(func: Callable) -> Callable:
9
+ """
10
+ Decorator that traces AI request function execution.
11
+
12
+ Captures timing, model info, tokens, cost, prompt, and response.
13
+ Displays formatted trace to terminal after execution.
14
+
15
+ Args:
16
+ func: The function to trace (should return AI response object)
17
+
18
+ Returns:
19
+ Wrapped function with identical signature and return type
20
+
21
+ Example:
22
+ @trace
23
+ def call_openai(prompt: str):
24
+ return client.chat.completions.create(
25
+ model="gpt-4",
26
+ messages=[{"role": "user", "content": prompt}]
27
+ )
28
+ """
29
+ from .tracer import Tracer
30
+
31
+ # Create tracer instance once at decoration time
32
+ tracer = Tracer()
33
+
34
+ @functools.wraps(func)
35
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
36
+ # Record start time with high precision
37
+ start_time = time.perf_counter()
38
+
39
+ try:
40
+ # Execute the wrapped function
41
+ result = func(*args, **kwargs)
42
+
43
+ # Calculate latency
44
+ end_time = time.perf_counter()
45
+ latency = end_time - start_time
46
+
47
+ # Capture and display trace
48
+ tracer.capture_trace(
49
+ result=result,
50
+ latency=latency,
51
+ func_name=func.__name__
52
+ )
53
+
54
+ # Return original result unchanged
55
+ return result
56
+
57
+ except Exception:
58
+ # Propagate exceptions without suppression
59
+ # No trace is captured on failure
60
+ raise
61
+
62
+ return wrapper
avenix/extractors.py ADDED
@@ -0,0 +1,81 @@
1
+ # Provider-specific extraction logic for AI responses
2
+
3
+ import logging
4
+ from abc import ABC, abstractmethod
5
+ from typing import Any, Dict
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class ResponseExtractor(ABC):
11
+ """Abstract base class for AI response extractors."""
12
+
13
+ @abstractmethod
14
+ def can_extract(self, result: Any) -> bool:
15
+ """Check if this extractor can handle the given result."""
16
+ ...
17
+
18
+ @abstractmethod
19
+ def extract(self, result: Any) -> Dict[str, Any]:
20
+ """Extract trace data from result."""
21
+ ...
22
+
23
+
24
+ class OpenAIExtractor(ResponseExtractor):
25
+ """Extractor for OpenAI response format."""
26
+
27
+ def can_extract(self, result: Any) -> bool:
28
+ """Check if result matches OpenAI format."""
29
+ usage = getattr(result, 'usage', None)
30
+ choices = getattr(result, 'choices', None)
31
+
32
+ return (
33
+ hasattr(result, 'model') and
34
+ hasattr(usage, 'prompt_tokens') and
35
+ hasattr(usage, 'completion_tokens') and
36
+ isinstance(choices, (list, tuple))
37
+ )
38
+
39
+ def extract(self, result: Any) -> Dict[str, Any]:
40
+ """Extract trace data from OpenAI response."""
41
+ try:
42
+ return {
43
+ 'model': result.model,
44
+ 'input_tokens': result.usage.prompt_tokens,
45
+ 'output_tokens': result.usage.completion_tokens,
46
+ 'prompt': '', # Not available in response
47
+ 'response': result.choices[0].message.content
48
+ }
49
+ except (AttributeError, IndexError) as e:
50
+ logger.warning(f"Failed to extract OpenAI trace data: {e}")
51
+ return {}
52
+
53
+
54
+ class AnthropicExtractor(ResponseExtractor):
55
+ """Extractor for Anthropic response format."""
56
+
57
+ def can_extract(self, result: Any) -> bool:
58
+ """Check if result matches Anthropic format."""
59
+ usage = getattr(result, 'usage', None)
60
+ content = getattr(result, 'content', None)
61
+
62
+ return (
63
+ hasattr(result, 'model') and
64
+ hasattr(usage, 'input_tokens') and
65
+ hasattr(usage, 'output_tokens') and
66
+ isinstance(content, (list, tuple))
67
+ )
68
+
69
+ def extract(self, result: Any) -> Dict[str, Any]:
70
+ """Extract trace data from Anthropic response."""
71
+ try:
72
+ return {
73
+ 'model': result.model,
74
+ 'input_tokens': result.usage.input_tokens,
75
+ 'output_tokens': result.usage.output_tokens,
76
+ 'prompt': '', # Not available in response
77
+ 'response': result.content[0].text if result.content else ''
78
+ }
79
+ except (AttributeError, IndexError) as e:
80
+ logger.warning(f"Failed to extract Anthropic trace data: {e}")
81
+ return {}
avenix/formatter.py ADDED
@@ -0,0 +1,71 @@
1
+ # Terminal output formatting using rich library
2
+
3
+ from rich.panel import Panel
4
+ from rich.text import Text
5
+ from rich.console import RenderableType
6
+
7
+ from .models import TraceModel
8
+
9
+
10
+ class RichFormatter:
11
+ """Formats traces using the rich library for beautiful terminal output."""
12
+
13
+ def format(self, trace: TraceModel) -> RenderableType:
14
+ """
15
+ Format a trace model for terminal display.
16
+
17
+ Creates a structured panel with:
18
+ - Header with emoji and title
19
+ - Metadata section (model, latency, tokens, cost)
20
+ - Prompt section
21
+ - Response section
22
+
23
+ Args:
24
+ trace: The trace model to format
25
+
26
+ Returns:
27
+ Rich renderable object (Panel)
28
+ """
29
+ # Build metadata section
30
+ metadata = Text()
31
+ metadata.append("Model: ", style="bold cyan")
32
+ metadata.append(f"{trace.model}\n")
33
+
34
+ metadata.append("Latency: ", style="bold cyan")
35
+ metadata.append(f"{trace.latency:.2f}s\n")
36
+
37
+ metadata.append("Input: ", style="bold cyan")
38
+ metadata.append(f"{trace.input_tokens} tokens\n")
39
+
40
+ metadata.append("Output: ", style="bold cyan")
41
+ metadata.append(f"{trace.output_tokens} tokens\n")
42
+
43
+ metadata.append("Cost: ", style="bold cyan")
44
+ metadata.append(f"${trace.cost:.4f}\n")
45
+
46
+ # Build complete output
47
+ output = Text()
48
+ output.append(metadata)
49
+ output.append("\n")
50
+
51
+ # Prompt section
52
+ output.append("━" * 50 + "\n", style="dim")
53
+ output.append("Prompt\n", style="bold yellow")
54
+ output.append("━" * 50 + "\n", style="dim")
55
+ output.append(f"{trace.prompt}\n\n")
56
+
57
+ # Response section
58
+ output.append("━" * 50 + "\n", style="dim")
59
+ output.append("Response\n", style="bold green")
60
+ output.append("━" * 50 + "\n", style="dim")
61
+ output.append(f"{trace.response}\n")
62
+
63
+ # Wrap in panel with title
64
+ panel = Panel(
65
+ output,
66
+ title="🚀 Avenix Trace",
67
+ border_style="blue",
68
+ padding=(1, 2)
69
+ )
70
+
71
+ return panel
avenix/logger.py ADDED
@@ -0,0 +1,29 @@
1
+ # Terminal display logger
2
+
3
+ from rich.console import Console, RenderableType
4
+
5
+
6
+ class RichLogger:
7
+ """Logs traces to terminal using rich library."""
8
+
9
+ def __init__(self, console: Console = None):
10
+ """
11
+ Initialize logger with optional custom console.
12
+
13
+ Args:
14
+ console: Custom rich Console instance
15
+ """
16
+ self.console = console or Console()
17
+
18
+ def log(self, renderable: RenderableType) -> None:
19
+ """
20
+ Display a formatted trace to the terminal.
21
+
22
+ Args:
23
+ renderable: Rich renderable object to display
24
+ """
25
+ try:
26
+ self.console.print(renderable)
27
+ except Exception as e:
28
+ # Fallback to basic print if rich rendering fails
29
+ print(f"[Avenix] Failed to render trace: {e}")
avenix/models.py ADDED
@@ -0,0 +1,97 @@
1
+ # Data models for AgentForge tracing
2
+
3
+ from pydantic import BaseModel, Field, field_validator
4
+
5
+
6
+ class TraceModel(BaseModel):
7
+ """
8
+ Data model for a single AI request trace.
9
+
10
+ All fields are validated by Pydantic for type safety.
11
+ """
12
+
13
+ model: str = Field(
14
+ description="Name of the AI model used (e.g., 'gpt-4', 'claude-3')"
15
+ )
16
+
17
+ latency: float = Field(
18
+ ge=0.0,
19
+ description="Request latency in seconds"
20
+ )
21
+
22
+ input_tokens: int = Field(
23
+ ge=0,
24
+ description="Number of tokens in the input/prompt"
25
+ )
26
+
27
+ output_tokens: int = Field(
28
+ ge=0,
29
+ description="Number of tokens in the output/response"
30
+ )
31
+
32
+ cost: float = Field(
33
+ ge=0.0,
34
+ description="Request cost in USD"
35
+ )
36
+
37
+ prompt: str = Field(
38
+ default="",
39
+ description="Input prompt text"
40
+ )
41
+
42
+ response: str = Field(
43
+ default="",
44
+ description="Output response text"
45
+ )
46
+
47
+ @field_validator('latency')
48
+ @classmethod
49
+ def round_latency(cls, v: float) -> float:
50
+ """Round latency to 2 decimal places."""
51
+ return round(v, 2)
52
+
53
+ @field_validator('cost')
54
+ @classmethod
55
+ def round_cost(cls, v: float) -> float:
56
+ """Round cost to 4 decimal places."""
57
+ return round(v, 4)
58
+
59
+
60
+ # Model pricing table (prices per 1K tokens in USD)
61
+ MODEL_PRICING = {
62
+ # OpenAI models
63
+ "gpt-4": {"input": 0.03, "output": 0.06},
64
+ "gpt-4-turbo": {"input": 0.01, "output": 0.03},
65
+ "gpt-3.5-turbo": {"input": 0.0005, "output": 0.0015},
66
+
67
+ # Anthropic models
68
+ "claude-3-opus": {"input": 0.015, "output": 0.075},
69
+ "claude-3-sonnet": {"input": 0.003, "output": 0.015},
70
+ "claude-3-haiku": {"input": 0.00025, "output": 0.00125},
71
+ }
72
+
73
+
74
+ def calculate_cost(model: str, input_tokens: int, output_tokens: int) -> float:
75
+ """
76
+ Calculate cost in USD based on model and token counts.
77
+
78
+ Args:
79
+ model: Name of the AI model (e.g., 'gpt-4', 'claude-3-opus')
80
+ input_tokens: Number of input/prompt tokens
81
+ output_tokens: Number of output/response tokens
82
+
83
+ Returns:
84
+ Cost in USD, rounded to 4 decimal places
85
+ Returns 0.0 for unknown models
86
+
87
+ Formula:
88
+ (input_tokens / 1000) * input_price + (output_tokens / 1000) * output_price
89
+ """
90
+ if model not in MODEL_PRICING:
91
+ return 0.0
92
+
93
+ pricing = MODEL_PRICING[model]
94
+ input_cost = (input_tokens / 1000) * pricing["input"]
95
+ output_cost = (output_tokens / 1000) * pricing["output"]
96
+
97
+ return round(input_cost + output_cost, 4)
avenix/tracer.py ADDED
@@ -0,0 +1,215 @@
1
+ # Core tracer implementation
2
+
3
+ import logging
4
+ from types import SimpleNamespace
5
+ from typing import Any, Optional
6
+
7
+ from pydantic import ValidationError
8
+
9
+ from .models import TraceModel, calculate_cost
10
+
11
+ logger = logging.getLogger(__name__)
12
+ _TRACE_MODEL_CLASS = TraceModel
13
+
14
+
15
+ def _build_fallback_trace(
16
+ model: str = 'unknown',
17
+ latency: float = 0.0,
18
+ input_tokens: int = 0,
19
+ output_tokens: int = 0,
20
+ cost: float = 0.0,
21
+ prompt: str = '',
22
+ response: str = '',
23
+ ):
24
+ """Return a sanitized trace model, falling back to a simple object if needed."""
25
+ sanitized = {
26
+ 'model': model or 'unknown',
27
+ 'latency': max(0.0, latency or 0.0),
28
+ 'input_tokens': max(0, input_tokens or 0),
29
+ 'output_tokens': max(0, output_tokens or 0),
30
+ 'cost': max(0.0, cost or 0.0),
31
+ 'prompt': prompt or '',
32
+ 'response': response or '',
33
+ }
34
+
35
+ try:
36
+ return _TRACE_MODEL_CLASS(**sanitized)
37
+ except Exception:
38
+ return SimpleNamespace(**sanitized)
39
+
40
+
41
+ class Tracer:
42
+ """Core tracing engine for Avenix."""
43
+
44
+ def __init__(self, logger=None, formatter=None):
45
+ """
46
+ Initialize tracer with optional custom logger and formatter.
47
+
48
+ Args:
49
+ logger: Custom logger instance (defaults to RichLogger)
50
+ formatter: Custom formatter instance (defaults to RichFormatter)
51
+ """
52
+ # Lazy import to avoid circular dependencies
53
+ if logger is None:
54
+ from .logger import RichLogger
55
+ self.logger = RichLogger()
56
+ else:
57
+ self.logger = logger
58
+
59
+ if formatter is None:
60
+ from .formatter import RichFormatter
61
+ self.formatter = RichFormatter()
62
+ else:
63
+ self.formatter = formatter
64
+
65
+ def capture_trace(
66
+ self,
67
+ result: Any,
68
+ latency: float,
69
+ func_name: Optional[str] = None
70
+ ) -> None:
71
+ """
72
+ Capture and display a trace from function execution result.
73
+
74
+ Args:
75
+ result: Return value from traced function
76
+ latency: Execution time in seconds
77
+ func_name: Optional name of traced function
78
+ """
79
+ # Extract trace data from result
80
+ extracted = self._extract_trace_data(result)
81
+
82
+ # Calculate cost
83
+ cost = calculate_cost(
84
+ extracted.get('model', 'unknown'),
85
+ extracted.get('input_tokens', 0),
86
+ extracted.get('output_tokens', 0)
87
+ )
88
+
89
+ try:
90
+ # Create validated trace model
91
+ trace = TraceModel(
92
+ model=extracted.get('model', 'unknown'),
93
+ latency=latency,
94
+ input_tokens=extracted.get('input_tokens', 0),
95
+ output_tokens=extracted.get('output_tokens', 0),
96
+ cost=cost,
97
+ prompt=extracted.get('prompt', ''),
98
+ response=extracted.get('response', '')
99
+ )
100
+ except ValidationError as e:
101
+ logger.error(f"Trace validation failed: {e}")
102
+ trace = _build_fallback_trace(
103
+ model='unknown',
104
+ latency=latency,
105
+ response='[Validation failed]',
106
+ )
107
+ except Exception as e:
108
+ logger.error(f"Unexpected error creating trace: {e}")
109
+ trace = _build_fallback_trace(
110
+ model='unknown',
111
+ latency=latency,
112
+ response='[Validation failed]',
113
+ )
114
+
115
+ # Display the trace
116
+ self._display_trace(trace)
117
+
118
+ def create_trace(
119
+ self,
120
+ model: str,
121
+ latency: float,
122
+ input_tokens: int,
123
+ output_tokens: int,
124
+ cost: float,
125
+ prompt: str,
126
+ response: str
127
+ ) -> None:
128
+ """
129
+ Manually create and display a trace with explicit values.
130
+
131
+ Args:
132
+ model: Name of the AI model used
133
+ latency: Request latency in seconds
134
+ input_tokens: Number of input tokens
135
+ output_tokens: Number of output tokens
136
+ cost: Request cost in dollars
137
+ prompt: Input prompt text
138
+ response: Response text from AI
139
+ """
140
+ try:
141
+ trace = TraceModel(
142
+ model=model,
143
+ latency=latency,
144
+ input_tokens=input_tokens,
145
+ output_tokens=output_tokens,
146
+ cost=cost,
147
+ prompt=prompt,
148
+ response=response
149
+ )
150
+ except Exception as e:
151
+ logger.error(f"Trace creation failed: {e}")
152
+ trace = _build_fallback_trace(
153
+ model=model,
154
+ latency=latency,
155
+ input_tokens=input_tokens,
156
+ output_tokens=output_tokens,
157
+ cost=cost,
158
+ prompt=prompt,
159
+ response=response,
160
+ )
161
+
162
+ self._display_trace(trace)
163
+
164
+ def _extract_trace_data(self, result: Any) -> dict:
165
+ """
166
+ Extract trace data using chain of extractors.
167
+
168
+ Returns dict with partial or complete trace data.
169
+ Missing fields will use default values.
170
+ """
171
+ # Lazy import extractors
172
+ from .extractors import OpenAIExtractor, AnthropicExtractor
173
+
174
+ extractors = [
175
+ OpenAIExtractor(),
176
+ AnthropicExtractor(),
177
+ ]
178
+
179
+ try:
180
+ for extractor in extractors:
181
+ if extractor.can_extract(result):
182
+ data = extractor.extract(result)
183
+ if data: # Extractor returned something
184
+ return data
185
+ except Exception as e:
186
+ logger.warning(f"Extraction failed: {e}", exc_info=True)
187
+
188
+ # Fallback to minimal data if no extractor matched
189
+ logger.warning(
190
+ f"No extractor found for result type: {type(result).__name__}"
191
+ )
192
+ return {
193
+ 'model': 'unknown',
194
+ 'input_tokens': 0,
195
+ 'output_tokens': 0,
196
+ 'prompt': '',
197
+ 'response': str(result)[:500] # Fallback to string repr
198
+ }
199
+
200
+ def _display_trace(self, trace: TraceModel) -> None:
201
+ """
202
+ Display a trace to the terminal.
203
+
204
+ Args:
205
+ trace: The trace model to display
206
+ """
207
+ try:
208
+ formatted = self.formatter.format(trace)
209
+ self.logger.log(formatted)
210
+ except Exception as e:
211
+ logger.error(f"Failed to display trace: {e}", exc_info=True)
212
+ # Fallback to basic output
213
+ print(f"[Avenix] Model: {trace.model}, "
214
+ f"Latency: {trace.latency:.2f}s, "
215
+ f"Cost: ${trace.cost:.4f}")
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.4
2
+ Name: avenix
3
+ Version: 0.1.0
4
+ Summary: Avenix is a focused Python tracing library for AI requests with beautiful terminal output
5
+ Author: Avenix Contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/avenix/avenix
8
+ Project-URL: Repository, https://github.com/avenix/avenix
9
+ Project-URL: Documentation, https://github.com/avenix/avenix#readme
10
+ Keywords: ai,tracing,llm,monitoring,openai,anthropic
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: System :: Monitoring
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: pydantic<3.0,>=2.0
23
+ Requires-Dist: rich<14.0,>=13.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
26
+ Requires-Dist: hypothesis<7.0,>=6.0; extra == "dev"
27
+ Requires-Dist: pytest-cov<5.0,>=4.0; extra == "dev"
28
+ Requires-Dist: pytest-mock<4.0,>=3.0; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # Avenix v0.1
32
+
33
+ A Python tracing library for AI/LLM requests with beautiful terminal output.
34
+
35
+ Avenix provides a decorator-based API for tracing AI model requests, automatically capturing execution metrics like timing, token usage, and costs, then displaying them in richly formatted terminal output.
36
+
37
+ ## Overview
38
+
39
+ Avenix simplifies monitoring AI/LLM requests by:
40
+ - **Automatic Capture**: Uses a simple `@trace` decorator to automatically capture request metrics
41
+ - **Beautiful Display**: Renders trace information in a formatted terminal panel with colors and separators
42
+ - **Multi-Provider Support**: Works with OpenAI and Anthropic model responses out of the box
43
+ - **Cost Tracking**: Automatically calculates request costs based on model pricing
44
+ - **Extensible**: Easy to add custom extractors for new AI providers
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ pip install avenix
50
+ ```
51
+
52
+ ## Requirements
53
+
54
+ - Python 3.11+
55
+ - pydantic ^2.0
56
+ - rich ^13.0
57
+
58
+ ## Quick Start
59
+
60
+ ### Using the @trace Decorator
61
+
62
+ The simplest way to use Avenix is with the `@trace` decorator:
63
+
64
+ ```python
65
+ from avenix import trace
66
+ from openai import OpenAI
67
+
68
+ client = OpenAI()
69
+
70
+ @trace
71
+ def get_gpt_response(prompt: str):
72
+ """Call GPT-4 with the given prompt."""
73
+ response = client.chat.completions.create(
74
+ model="gpt-4",
75
+ messages=[{"role": "user", "content": prompt}]
76
+ )
77
+ return response
78
+
79
+ # When you call the function, Avenix will automatically:
80
+ # 1. Measure execution time
81
+ # 2. Extract model, tokens, and response from the result
82
+ # 3. Calculate cost based on token usage
83
+ # 4. Display formatted trace output to terminal
84
+ result = get_gpt_response("What is machine learning?")
85
+ ```
86
+
87
+ ### Manual Trace Creation
88
+
89
+ For more control, you can manually create traces using the `Tracer` API:
90
+
91
+ ```python
92
+ from avenix import Tracer
93
+
94
+ tracer = Tracer()
95
+
96
+ # Later, manually create a trace with explicit values
97
+ tracer.create_trace(
98
+ model="gpt-4",
99
+ latency=2.5,
100
+ input_tokens=150,
101
+ output_tokens=300,
102
+ cost=0.045,
103
+ prompt="What is AI?",
104
+ response="AI is artificial intelligence..."
105
+ )
106
+ ```
107
+
108
+ ## Supported Models
109
+
110
+ Avenix includes built-in support for:
111
+
112
+ ### OpenAI
113
+ - gpt-4
114
+ - gpt-4-turbo
115
+ - gpt-3.5-turbo
116
+
117
+ ### Anthropic
118
+ - claude-3-opus
119
+ - claude-3-sonnet
120
+ - claude-3-haiku
121
+
122
+ ## Features in v0.1
123
+
124
+ ✅ Decorator-based tracing API
125
+ ✅ Automatic timing measurement with perf_counter
126
+ ✅ OpenAI and Anthropic response extraction
127
+ ✅ Model pricing table and cost calculation
128
+ ✅ Beautiful terminal output with rich formatting
129
+ ✅ Error handling and graceful fallbacks
130
+ ✅ Property-based test suite for correctness verification
131
+
132
+ ## Out of Scope for v0.1
133
+
134
+ The following features are planned for future releases:
135
+
136
+ - Custom formatter plugins
137
+ - Database/file persistence of traces
138
+ - Trace filtering and search
139
+ - Performance statistics aggregation
140
+ - Integration with external logging services
141
+ - Support for additional AI providers
142
+ - Rate limiting and quota management
143
+ - Async/await support
144
+
145
+ ## Documentation
146
+
147
+ ### API Reference
148
+
149
+ #### @trace Decorator
150
+
151
+ ```python
152
+ @trace
153
+ def your_function():
154
+ # ... your code that calls an AI model
155
+ return response
156
+ ```
157
+
158
+ The `@trace` decorator:
159
+ - Measures execution time with `time.perf_counter()`
160
+ - Captures the function result
161
+ - Calls the global `Tracer` instance to extract data and display trace
162
+ - Propagates exceptions without suppression
163
+ - Preserves the original function's return value and metadata
164
+
165
+ #### Tracer Class
166
+
167
+ ```python
168
+ from avenix import Tracer
169
+
170
+ tracer = Tracer(logger=None, formatter=None)
171
+ ```
172
+
173
+ Methods:
174
+ - `capture_trace(result, latency, func_name=None)`: Capture and display a trace from function execution
175
+ - `create_trace(model, latency, input_tokens, output_tokens, cost, prompt, response)`: Manually create and display a trace
176
+
177
+ ### TraceModel
178
+
179
+ The `TraceModel` class represents a single trace with validation:
180
+
181
+ Fields:
182
+ - `model` (str): Name of the AI model
183
+ - `latency` (float): Execution time in seconds (rounded to 2 decimals)
184
+ - `input_tokens` (int): Number of input tokens (must be non-negative)
185
+ - `output_tokens` (int): Number of output tokens (must be non-negative)
186
+ - `cost` (float): Request cost in dollars (rounded to 4 decimals, must be non-negative)
187
+ - `prompt` (str): Input prompt text (defaults to empty string)
188
+ - `response` (str): Model response text (defaults to empty string)
189
+
190
+ ## Examples
191
+
192
+ See the `examples/` directory for complete working examples:
193
+
194
+ - `openai_example.py`: Using @trace with OpenAI API
195
+ - `anthropic_example.py`: Using @trace with Anthropic API
196
+ - `manual_trace.py`: Creating traces manually with Tracer API
197
+
198
+ ## Testing
199
+
200
+ Avenix includes a comprehensive test suite with property-based tests:
201
+
202
+ ```bash
203
+ pytest tests/ -v # Run all tests
204
+ pytest tests/ --cov # Run with coverage report
205
+ pytest tests/test_models.py -v # Run specific test file
206
+ ```
207
+
208
+ Test coverage targets:
209
+ - Core modules (decorator, tracer, models, formatter, extractors): >90%
210
+ - Property-based tests for 16 correctness properties
211
+ - Unit tests for all feature components
212
+
213
+ ## Architecture
214
+
215
+ Avenix follows a layered architecture:
216
+
217
+ 1. **Models Layer** (`models.py`): Defines `TraceModel` with Pydantic validation
218
+ 2. **Decorator Layer** (`decorator.py`): Provides `@trace` decorator for wrapping functions
219
+ 3. **Tracer Layer** (`tracer.py`): Core orchestration with optional custom logger/formatter
220
+ 4. **Extraction Layer** (`extractors.py`): Provider-specific response extractors
221
+ 5. **Formatting Layer** (`formatter.py`): Beautiful terminal output with rich library
222
+ 6. **Logging Layer** (`logger.py`): Terminal display with fallback handling
223
+
224
+ ## Error Handling
225
+
226
+ Avenix is designed to be resilient to errors:
227
+
228
+ - **Extraction Failures**: If response format doesn't match known providers, uses sensible defaults
229
+ - **Validation Failures**: If TraceModel validation fails, falls back to defaults
230
+ - **Rendering Failures**: If rich formatting fails, falls back to basic print output
231
+ - **Exception Propagation**: Errors in traced functions propagate normally without suppression
232
+
233
+ ## Contributing
234
+
235
+ This is a v0.1 release. Feedback and contributions are welcome!
236
+
237
+ ## API Reference
238
+
239
+ For detailed API documentation, see [API.md](API.md).
240
+
241
+ ## License
242
+
243
+ MIT License - See LICENSE file for details
244
+
245
+ ## Changelog
246
+
247
+ See CHANGELOG.md for version history and release notes
@@ -0,0 +1,12 @@
1
+ avenix/__init__.py,sha256=As2hvX77otSC2pVaVBynLQVCAMjPr-Qde3663Ex-Bsk,253
2
+ avenix/decorator.py,sha256=O-3vqdNf7q1XP6abcY-roe349BYsepMAWrbo_RVjXXY,1742
3
+ avenix/extractors.py,sha256=j4ckpYEV8FqcbdFNaoyEtzs1kXH9bTru4yex0t-phOQ,2778
4
+ avenix/formatter.py,sha256=5bsRLGFvM1MdwxTQ7a-EWv3lAvGaTJO0wTUTxsG6_Vg,2235
5
+ avenix/logger.py,sha256=lY5uo9Nw3_KBap2_pARCuaWKYRwjlLGNK55z_UW8P30,825
6
+ avenix/models.py,sha256=3IR58kcD7knHFZ7xZ7QeNerLMyIYFaEBLhjfGpD9Mow,2608
7
+ avenix/tracer.py,sha256=SHF7k8WPDsMBDRUAfOhlBP4XW1f4dVRA6zNTYOUgHYE,6766
8
+ avenix-0.1.0.dist-info/licenses/LICENSE,sha256=H3GuE_5sx3TdFZP1Pmsyhk1hARuUaVectoYcPwF9O58,1080
9
+ avenix-0.1.0.dist-info/METADATA,sha256=XtcuXrJiYXNCQSYNYnZlNCzLeO-onCdALNy8y0HyP1Y,7507
10
+ avenix-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
11
+ avenix-0.1.0.dist-info/top_level.txt,sha256=jc9_k43DzwCAngJZNa31azg1h94chC4uM1ZDAwaMhCM,7
12
+ avenix-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 AgentForge Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ avenix