abstractcore 2.5.2__py3-none-any.whl → 2.5.3__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.
Files changed (50) hide show
  1. abstractcore/__init__.py +12 -0
  2. abstractcore/architectures/detection.py +250 -4
  3. abstractcore/assets/architecture_formats.json +14 -1
  4. abstractcore/assets/model_capabilities.json +533 -10
  5. abstractcore/compression/__init__.py +29 -0
  6. abstractcore/compression/analytics.py +420 -0
  7. abstractcore/compression/cache.py +250 -0
  8. abstractcore/compression/config.py +279 -0
  9. abstractcore/compression/exceptions.py +30 -0
  10. abstractcore/compression/glyph_processor.py +381 -0
  11. abstractcore/compression/optimizer.py +388 -0
  12. abstractcore/compression/orchestrator.py +380 -0
  13. abstractcore/compression/pil_text_renderer.py +818 -0
  14. abstractcore/compression/quality.py +226 -0
  15. abstractcore/compression/text_formatter.py +666 -0
  16. abstractcore/compression/vision_compressor.py +371 -0
  17. abstractcore/config/main.py +64 -0
  18. abstractcore/config/manager.py +100 -5
  19. abstractcore/core/session.py +61 -6
  20. abstractcore/events/__init__.py +1 -1
  21. abstractcore/media/auto_handler.py +312 -18
  22. abstractcore/media/handlers/local_handler.py +14 -2
  23. abstractcore/media/handlers/openai_handler.py +62 -3
  24. abstractcore/media/processors/__init__.py +11 -1
  25. abstractcore/media/processors/direct_pdf_processor.py +210 -0
  26. abstractcore/media/processors/glyph_pdf_processor.py +227 -0
  27. abstractcore/media/processors/image_processor.py +7 -1
  28. abstractcore/media/processors/text_processor.py +18 -3
  29. abstractcore/media/types.py +164 -7
  30. abstractcore/providers/__init__.py +18 -0
  31. abstractcore/providers/anthropic_provider.py +28 -2
  32. abstractcore/providers/base.py +278 -6
  33. abstractcore/providers/huggingface_provider.py +563 -23
  34. abstractcore/providers/lmstudio_provider.py +38 -2
  35. abstractcore/providers/mlx_provider.py +27 -2
  36. abstractcore/providers/model_capabilities.py +352 -0
  37. abstractcore/providers/ollama_provider.py +38 -4
  38. abstractcore/providers/openai_provider.py +28 -2
  39. abstractcore/providers/registry.py +85 -13
  40. abstractcore/server/app.py +91 -81
  41. abstractcore/utils/__init__.py +4 -1
  42. abstractcore/utils/trace_export.py +287 -0
  43. abstractcore/utils/version.py +1 -1
  44. abstractcore/utils/vlm_token_calculator.py +655 -0
  45. {abstractcore-2.5.2.dist-info → abstractcore-2.5.3.dist-info}/METADATA +107 -6
  46. {abstractcore-2.5.2.dist-info → abstractcore-2.5.3.dist-info}/RECORD +50 -33
  47. {abstractcore-2.5.2.dist-info → abstractcore-2.5.3.dist-info}/WHEEL +0 -0
  48. {abstractcore-2.5.2.dist-info → abstractcore-2.5.3.dist-info}/entry_points.txt +0 -0
  49. {abstractcore-2.5.2.dist-info → abstractcore-2.5.3.dist-info}/licenses/LICENSE +0 -0
  50. {abstractcore-2.5.2.dist-info → abstractcore-2.5.3.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
+ }
@@ -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.5.2"
14
+ __version__ = "2.5.3"