abstractcore 2.5.2__py3-none-any.whl → 2.6.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.
- abstractcore/__init__.py +19 -1
- abstractcore/architectures/detection.py +252 -6
- abstractcore/assets/architecture_formats.json +14 -1
- abstractcore/assets/model_capabilities.json +533 -10
- abstractcore/compression/__init__.py +29 -0
- abstractcore/compression/analytics.py +420 -0
- abstractcore/compression/cache.py +250 -0
- abstractcore/compression/config.py +279 -0
- abstractcore/compression/exceptions.py +30 -0
- abstractcore/compression/glyph_processor.py +381 -0
- abstractcore/compression/optimizer.py +388 -0
- abstractcore/compression/orchestrator.py +380 -0
- abstractcore/compression/pil_text_renderer.py +818 -0
- abstractcore/compression/quality.py +226 -0
- abstractcore/compression/text_formatter.py +666 -0
- abstractcore/compression/vision_compressor.py +371 -0
- abstractcore/config/main.py +64 -0
- abstractcore/config/manager.py +100 -5
- abstractcore/core/retry.py +2 -2
- abstractcore/core/session.py +193 -7
- abstractcore/download.py +253 -0
- abstractcore/embeddings/manager.py +2 -2
- abstractcore/events/__init__.py +113 -2
- abstractcore/exceptions/__init__.py +49 -2
- abstractcore/media/auto_handler.py +312 -18
- abstractcore/media/handlers/local_handler.py +14 -2
- abstractcore/media/handlers/openai_handler.py +62 -3
- abstractcore/media/processors/__init__.py +11 -1
- abstractcore/media/processors/direct_pdf_processor.py +210 -0
- abstractcore/media/processors/glyph_pdf_processor.py +227 -0
- abstractcore/media/processors/image_processor.py +7 -1
- abstractcore/media/processors/office_processor.py +2 -2
- abstractcore/media/processors/text_processor.py +18 -3
- abstractcore/media/types.py +164 -7
- abstractcore/media/utils/image_scaler.py +2 -2
- abstractcore/media/vision_fallback.py +2 -2
- abstractcore/providers/__init__.py +18 -0
- abstractcore/providers/anthropic_provider.py +228 -8
- abstractcore/providers/base.py +378 -11
- abstractcore/providers/huggingface_provider.py +563 -23
- abstractcore/providers/lmstudio_provider.py +284 -4
- abstractcore/providers/mlx_provider.py +27 -2
- abstractcore/providers/model_capabilities.py +352 -0
- abstractcore/providers/ollama_provider.py +282 -6
- abstractcore/providers/openai_provider.py +286 -8
- abstractcore/providers/registry.py +85 -13
- abstractcore/providers/streaming.py +2 -2
- abstractcore/server/app.py +91 -81
- abstractcore/tools/common_tools.py +2 -2
- abstractcore/tools/handler.py +2 -2
- abstractcore/tools/parser.py +2 -2
- abstractcore/tools/registry.py +2 -2
- abstractcore/tools/syntax_rewriter.py +2 -2
- abstractcore/tools/tag_rewriter.py +3 -3
- abstractcore/utils/__init__.py +4 -1
- abstractcore/utils/self_fixes.py +2 -2
- abstractcore/utils/trace_export.py +287 -0
- abstractcore/utils/version.py +1 -1
- abstractcore/utils/vlm_token_calculator.py +655 -0
- {abstractcore-2.5.2.dist-info → abstractcore-2.6.0.dist-info}/METADATA +207 -8
- abstractcore-2.6.0.dist-info/RECORD +108 -0
- abstractcore-2.5.2.dist-info/RECORD +0 -90
- {abstractcore-2.5.2.dist-info → abstractcore-2.6.0.dist-info}/WHEEL +0 -0
- {abstractcore-2.5.2.dist-info → abstractcore-2.6.0.dist-info}/entry_points.txt +0 -0
- {abstractcore-2.5.2.dist-info → abstractcore-2.6.0.dist-info}/licenses/LICENSE +0 -0
- {abstractcore-2.5.2.dist-info → abstractcore-2.6.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trace export utilities for interaction observability.
|
|
3
|
+
|
|
4
|
+
Provides functions to export LLM interaction traces to various formats
|
|
5
|
+
for debugging, analysis, and compliance purposes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from typing import List, Dict, Any, Optional, Union
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def export_traces(
|
|
15
|
+
traces: Union[Dict[str, Any], List[Dict[str, Any]]],
|
|
16
|
+
format: str = 'jsonl',
|
|
17
|
+
file_path: Optional[Union[str, Path]] = None
|
|
18
|
+
) -> str:
|
|
19
|
+
"""
|
|
20
|
+
Export interaction traces to file or return formatted string.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
traces: Single trace dict or list of trace dicts
|
|
24
|
+
format: Output format - 'jsonl', 'json', or 'markdown'
|
|
25
|
+
file_path: Optional file path to write to. If None, returns string.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Formatted trace data as string
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
ValueError: If format is not supported
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
>>> # Export single trace to JSONL
|
|
35
|
+
>>> trace = llm.get_traces(trace_id="...")
|
|
36
|
+
>>> export_traces(trace, format='jsonl', file_path='trace.jsonl')
|
|
37
|
+
|
|
38
|
+
>>> # Export multiple traces to JSON
|
|
39
|
+
>>> traces = llm.get_traces(last_n=10)
|
|
40
|
+
>>> export_traces(traces, format='json', file_path='traces.json')
|
|
41
|
+
|
|
42
|
+
>>> # Get markdown report as string
|
|
43
|
+
>>> report = export_traces(traces, format='markdown')
|
|
44
|
+
>>> print(report)
|
|
45
|
+
"""
|
|
46
|
+
# Normalize to list
|
|
47
|
+
if isinstance(traces, dict):
|
|
48
|
+
traces = [traces]
|
|
49
|
+
|
|
50
|
+
# Validate format
|
|
51
|
+
if format not in ['jsonl', 'json', 'markdown']:
|
|
52
|
+
raise ValueError(f"Unsupported format: {format}. Use 'jsonl', 'json', or 'markdown'")
|
|
53
|
+
|
|
54
|
+
# Format based on type
|
|
55
|
+
if format == 'jsonl':
|
|
56
|
+
content = _format_as_jsonl(traces)
|
|
57
|
+
elif format == 'json':
|
|
58
|
+
content = _format_as_json(traces)
|
|
59
|
+
elif format == 'markdown':
|
|
60
|
+
content = _format_as_markdown(traces)
|
|
61
|
+
|
|
62
|
+
# Write to file if path provided
|
|
63
|
+
if file_path:
|
|
64
|
+
path = Path(file_path)
|
|
65
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
66
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
67
|
+
f.write(content)
|
|
68
|
+
|
|
69
|
+
return content
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _format_as_jsonl(traces: List[Dict[str, Any]]) -> str:
|
|
73
|
+
"""Format traces as JSON Lines (one JSON object per line)."""
|
|
74
|
+
lines = [json.dumps(trace, ensure_ascii=False) for trace in traces]
|
|
75
|
+
return '\n'.join(lines)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _format_as_json(traces: List[Dict[str, Any]]) -> str:
|
|
79
|
+
"""Format traces as pretty-printed JSON array."""
|
|
80
|
+
return json.dumps(traces, indent=2, ensure_ascii=False)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _format_as_markdown(traces: List[Dict[str, Any]]) -> str:
|
|
84
|
+
"""Format traces as human-readable markdown report."""
|
|
85
|
+
lines = ["# LLM Interaction Trace Report", ""]
|
|
86
|
+
lines.append(f"**Generated:** {datetime.now().isoformat()}")
|
|
87
|
+
lines.append(f"**Total Interactions:** {len(traces)}")
|
|
88
|
+
lines.append("")
|
|
89
|
+
|
|
90
|
+
for i, trace in enumerate(traces, 1):
|
|
91
|
+
lines.append(f"## Interaction {i}: {trace.get('trace_id', 'unknown')}")
|
|
92
|
+
lines.append("")
|
|
93
|
+
|
|
94
|
+
# Metadata section
|
|
95
|
+
lines.append("### Metadata")
|
|
96
|
+
lines.append(f"- **Timestamp:** {trace.get('timestamp', 'N/A')}")
|
|
97
|
+
lines.append(f"- **Provider:** {trace.get('provider', 'N/A')}")
|
|
98
|
+
lines.append(f"- **Model:** {trace.get('model', 'N/A')}")
|
|
99
|
+
|
|
100
|
+
# Custom metadata
|
|
101
|
+
metadata = trace.get('metadata', {})
|
|
102
|
+
if metadata:
|
|
103
|
+
for key, value in metadata.items():
|
|
104
|
+
lines.append(f"- **{key.replace('_', ' ').title()}:** {value}")
|
|
105
|
+
lines.append("")
|
|
106
|
+
|
|
107
|
+
# Input section
|
|
108
|
+
lines.append("### Input")
|
|
109
|
+
|
|
110
|
+
system_prompt = trace.get('system_prompt')
|
|
111
|
+
if system_prompt:
|
|
112
|
+
lines.append(f"**System Prompt:**")
|
|
113
|
+
lines.append(f"```")
|
|
114
|
+
lines.append(system_prompt)
|
|
115
|
+
lines.append(f"```")
|
|
116
|
+
lines.append("")
|
|
117
|
+
|
|
118
|
+
prompt = trace.get('prompt', '')
|
|
119
|
+
lines.append(f"**User Prompt:**")
|
|
120
|
+
lines.append(f"```")
|
|
121
|
+
lines.append(prompt)
|
|
122
|
+
lines.append(f"```")
|
|
123
|
+
lines.append("")
|
|
124
|
+
|
|
125
|
+
# Parameters
|
|
126
|
+
parameters = trace.get('parameters', {})
|
|
127
|
+
if parameters:
|
|
128
|
+
lines.append("**Parameters:**")
|
|
129
|
+
for key, value in parameters.items():
|
|
130
|
+
if value is not None:
|
|
131
|
+
lines.append(f"- `{key}`: {value}")
|
|
132
|
+
lines.append("")
|
|
133
|
+
|
|
134
|
+
# Tools
|
|
135
|
+
tools = trace.get('tools')
|
|
136
|
+
if tools:
|
|
137
|
+
lines.append(f"**Tools Available:** {len(tools)} tools")
|
|
138
|
+
lines.append("")
|
|
139
|
+
|
|
140
|
+
# Response section
|
|
141
|
+
response = trace.get('response', {})
|
|
142
|
+
lines.append("### Response")
|
|
143
|
+
|
|
144
|
+
content = response.get('content', '')
|
|
145
|
+
if content:
|
|
146
|
+
lines.append(f"**Content:**")
|
|
147
|
+
lines.append(f"```")
|
|
148
|
+
lines.append(content)
|
|
149
|
+
lines.append(f"```")
|
|
150
|
+
lines.append("")
|
|
151
|
+
|
|
152
|
+
# Tool calls
|
|
153
|
+
tool_calls = response.get('tool_calls')
|
|
154
|
+
if tool_calls:
|
|
155
|
+
lines.append(f"**Tool Calls:** {len(tool_calls)}")
|
|
156
|
+
for call in tool_calls:
|
|
157
|
+
lines.append(f"- `{call.get('name', 'unknown')}({call.get('arguments', {})})`")
|
|
158
|
+
lines.append("")
|
|
159
|
+
|
|
160
|
+
# Usage metrics
|
|
161
|
+
usage = response.get('usage', {})
|
|
162
|
+
if usage:
|
|
163
|
+
lines.append("**Usage:**")
|
|
164
|
+
input_tokens = usage.get('input_tokens') or usage.get('prompt_tokens', 0)
|
|
165
|
+
output_tokens = usage.get('output_tokens') or usage.get('completion_tokens', 0)
|
|
166
|
+
total_tokens = usage.get('total_tokens', input_tokens + output_tokens)
|
|
167
|
+
|
|
168
|
+
lines.append(f"- Input tokens: {input_tokens}")
|
|
169
|
+
lines.append(f"- Output tokens: {output_tokens}")
|
|
170
|
+
lines.append(f"- Total tokens: {total_tokens}")
|
|
171
|
+
|
|
172
|
+
visual_tokens = usage.get('visual_tokens')
|
|
173
|
+
if visual_tokens:
|
|
174
|
+
lines.append(f"- Visual tokens: {visual_tokens}")
|
|
175
|
+
lines.append("")
|
|
176
|
+
|
|
177
|
+
# Performance
|
|
178
|
+
gen_time = response.get('generation_time_ms')
|
|
179
|
+
finish_reason = response.get('finish_reason')
|
|
180
|
+
if gen_time or finish_reason:
|
|
181
|
+
lines.append("**Performance:**")
|
|
182
|
+
if gen_time:
|
|
183
|
+
lines.append(f"- Generation time: {gen_time:.2f}ms")
|
|
184
|
+
if finish_reason:
|
|
185
|
+
lines.append(f"- Finish reason: {finish_reason}")
|
|
186
|
+
lines.append("")
|
|
187
|
+
|
|
188
|
+
lines.append("---")
|
|
189
|
+
lines.append("")
|
|
190
|
+
|
|
191
|
+
return '\n'.join(lines)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def summarize_traces(traces: Union[Dict[str, Any], List[Dict[str, Any]]]) -> Dict[str, Any]:
|
|
195
|
+
"""
|
|
196
|
+
Generate summary statistics for traces.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
traces: Single trace dict or list of trace dicts
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Dictionary with summary statistics:
|
|
203
|
+
- total_interactions: Number of traces
|
|
204
|
+
- total_tokens: Sum of all tokens used
|
|
205
|
+
- total_time_ms: Sum of generation times
|
|
206
|
+
- avg_tokens_per_interaction: Average tokens
|
|
207
|
+
- avg_time_ms: Average generation time
|
|
208
|
+
- providers: Set of providers used
|
|
209
|
+
- models: Set of models used
|
|
210
|
+
- date_range: First and last timestamps
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
>>> traces = session.get_interaction_history()
|
|
214
|
+
>>> summary = summarize_traces(traces)
|
|
215
|
+
>>> print(f"Total interactions: {summary['total_interactions']}")
|
|
216
|
+
>>> print(f"Total tokens: {summary['total_tokens']}")
|
|
217
|
+
>>> print(f"Average time: {summary['avg_time_ms']:.2f}ms")
|
|
218
|
+
"""
|
|
219
|
+
# Normalize to list
|
|
220
|
+
if isinstance(traces, dict):
|
|
221
|
+
traces = [traces]
|
|
222
|
+
|
|
223
|
+
if not traces:
|
|
224
|
+
return {
|
|
225
|
+
'total_interactions': 0,
|
|
226
|
+
'total_tokens': 0,
|
|
227
|
+
'total_time_ms': 0,
|
|
228
|
+
'avg_tokens_per_interaction': 0,
|
|
229
|
+
'avg_time_ms': 0,
|
|
230
|
+
'providers': set(),
|
|
231
|
+
'models': set(),
|
|
232
|
+
'date_range': None
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
total_tokens = 0
|
|
236
|
+
total_time_ms = 0
|
|
237
|
+
providers = set()
|
|
238
|
+
models = set()
|
|
239
|
+
timestamps = []
|
|
240
|
+
|
|
241
|
+
for trace in traces:
|
|
242
|
+
# Extract usage
|
|
243
|
+
response = trace.get('response', {})
|
|
244
|
+
usage = response.get('usage', {})
|
|
245
|
+
if usage:
|
|
246
|
+
total_tokens += usage.get('total_tokens', 0)
|
|
247
|
+
|
|
248
|
+
# Extract timing
|
|
249
|
+
gen_time = response.get('generation_time_ms')
|
|
250
|
+
if gen_time:
|
|
251
|
+
total_time_ms += gen_time
|
|
252
|
+
|
|
253
|
+
# Extract metadata
|
|
254
|
+
provider = trace.get('provider')
|
|
255
|
+
if provider:
|
|
256
|
+
providers.add(provider)
|
|
257
|
+
|
|
258
|
+
model = trace.get('model')
|
|
259
|
+
if model:
|
|
260
|
+
models.add(model)
|
|
261
|
+
|
|
262
|
+
timestamp = trace.get('timestamp')
|
|
263
|
+
if timestamp:
|
|
264
|
+
timestamps.append(timestamp)
|
|
265
|
+
|
|
266
|
+
num_traces = len(traces)
|
|
267
|
+
avg_tokens = total_tokens / num_traces if num_traces > 0 else 0
|
|
268
|
+
avg_time = total_time_ms / num_traces if num_traces > 0 else 0
|
|
269
|
+
|
|
270
|
+
date_range = None
|
|
271
|
+
if timestamps:
|
|
272
|
+
timestamps_sorted = sorted(timestamps)
|
|
273
|
+
date_range = {
|
|
274
|
+
'first': timestamps_sorted[0],
|
|
275
|
+
'last': timestamps_sorted[-1]
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
'total_interactions': num_traces,
|
|
280
|
+
'total_tokens': total_tokens,
|
|
281
|
+
'total_time_ms': total_time_ms,
|
|
282
|
+
'avg_tokens_per_interaction': avg_tokens,
|
|
283
|
+
'avg_time_ms': avg_time,
|
|
284
|
+
'providers': list(providers),
|
|
285
|
+
'models': list(models),
|
|
286
|
+
'date_range': date_range
|
|
287
|
+
}
|
abstractcore/utils/version.py
CHANGED
|
@@ -11,4 +11,4 @@ including when the package is installed from PyPI where pyproject.toml is not av
|
|
|
11
11
|
|
|
12
12
|
# Package version - update this when releasing new versions
|
|
13
13
|
# This must be manually synchronized with the version in pyproject.toml
|
|
14
|
-
__version__ = "2.
|
|
14
|
+
__version__ = "2.6.0"
|