abstractcore 2.4.6__py3-none-any.whl → 2.4.8__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 CHANGED
@@ -44,6 +44,9 @@ except ImportError:
44
44
  from .processing import BasicSummarizer, SummaryStyle, SummaryLength, BasicExtractor
45
45
  _has_processing = True
46
46
 
47
+ # Tools module (core functionality)
48
+ from .tools import tool
49
+
47
50
  __all__ = [
48
51
  'create_llm',
49
52
  'BasicSession',
@@ -54,7 +57,8 @@ __all__ = [
54
57
  'MessageRole',
55
58
  'ModelNotFoundError',
56
59
  'ProviderAPIError',
57
- 'AuthenticationError'
60
+ 'AuthenticationError',
61
+ 'tool'
58
62
  ]
59
63
 
60
64
  if _has_embeddings:
@@ -109,7 +109,7 @@
109
109
  "tokens_before": { "type": "integer" },
110
110
  "tokens_after": { "type": "integer" },
111
111
  "compression_ratio": { "type": "number" },
112
- "generation_time_ms": { "type": "number" }
112
+ "gen_time": { "type": "number" }
113
113
  }
114
114
  }
115
115
  },
@@ -12,7 +12,7 @@ def create_llm(provider: str, model: Optional[str] = None, **kwargs) -> Abstract
12
12
  Create an LLM provider instance with unified token parameter support.
13
13
 
14
14
  Args:
15
- provider: Provider name (openai, anthropic, ollama, huggingface, mlx, lmstudio, mock)
15
+ provider: Provider name (openai, anthropic, ollama, huggingface, mlx, lmstudio)
16
16
  model: Model name (optional, will use provider default)
17
17
  **kwargs: Additional configuration including token parameters
18
18
 
@@ -760,7 +760,7 @@ class BasicSession:
760
760
  "tokens_before": original_tokens,
761
761
  "tokens_after": self._estimate_tokens_for_summary(summary_result.summary),
762
762
  "compression_ratio": self._calculate_compression_ratio(original_tokens, summary_result.summary),
763
- "generation_time_ms": duration_ms
763
+ "gen_time": duration_ms
764
764
  }
765
765
  }
766
766
 
@@ -91,6 +91,7 @@ class GenerateResponse:
91
91
  usage: Optional[Dict[str, int]] = None
92
92
  tool_calls: Optional[List[Dict[str, Any]]] = None
93
93
  metadata: Optional[Dict[str, Any]] = None
94
+ gen_time: Optional[float] = None # Generation time in milliseconds
94
95
 
95
96
  def has_tool_calls(self) -> bool:
96
97
  """Check if response contains tool calls"""
@@ -109,6 +110,29 @@ class GenerateResponse:
109
110
  parts.append(f"Model: {self.model}")
110
111
  if self.usage:
111
112
  parts.append(f"Tokens: {self.usage.get('total_tokens', 'unknown')}")
113
+ if self.gen_time:
114
+ parts.append(f"Time: {self.gen_time:.1f}ms")
112
115
  if self.tool_calls:
113
116
  parts.append(f"Tools: {len(self.tool_calls)} executed")
114
- return " | ".join(parts)
117
+ return " | ".join(parts)
118
+
119
+ @property
120
+ def input_tokens(self) -> Optional[int]:
121
+ """Get input tokens with consistent terminology (prompt_tokens or input_tokens)."""
122
+ if not self.usage:
123
+ return None
124
+ return self.usage.get('input_tokens') or self.usage.get('prompt_tokens')
125
+
126
+ @property
127
+ def output_tokens(self) -> Optional[int]:
128
+ """Get output tokens with consistent terminology (completion_tokens or output_tokens)."""
129
+ if not self.usage:
130
+ return None
131
+ return self.usage.get('output_tokens') or self.usage.get('completion_tokens')
132
+
133
+ @property
134
+ def total_tokens(self) -> Optional[int]:
135
+ """Get total tokens."""
136
+ if not self.usage:
137
+ return None
138
+ return self.usage.get('total_tokens')
@@ -7,7 +7,6 @@ from .ollama_provider import OllamaProvider
7
7
  from .lmstudio_provider import LMStudioProvider
8
8
  from .huggingface_provider import HuggingFaceProvider
9
9
  from .mlx_provider import MLXProvider
10
- from .mock_provider import MockProvider
11
10
 
12
11
  # Provider registry for centralized provider discovery and management
13
12
  from .registry import (
@@ -32,7 +31,6 @@ __all__ = [
32
31
  'LMStudioProvider',
33
32
  'HuggingFaceProvider',
34
33
  'MLXProvider',
35
- 'MockProvider',
36
34
 
37
35
  # Provider registry
38
36
  'ProviderRegistry',
@@ -186,8 +186,14 @@ class AnthropicProvider(BaseProvider):
186
186
  if stream:
187
187
  return self._stream_response(call_params, tools)
188
188
  else:
189
+ # Track generation time
190
+ start_time = time.time()
189
191
  response = self.client.messages.create(**call_params)
192
+ gen_time = round((time.time() - start_time) * 1000, 1)
193
+
190
194
  formatted = self._format_response(response)
195
+ # Add generation time to response
196
+ formatted.gen_time = gen_time
191
197
 
192
198
  # Handle tool execution for Anthropic responses
193
199
  if tools and (formatted.has_tool_calls() or
@@ -863,6 +863,9 @@ class HuggingFaceProvider(BaseProvider):
863
863
  if torch.cuda.is_available():
864
864
  torch.cuda.manual_seed_all(seed)
865
865
 
866
+ # Track generation time
867
+ start_time = time.time()
868
+
866
869
  outputs = self.pipeline(
867
870
  input_text,
868
871
  max_new_tokens=max_new_tokens,
@@ -874,6 +877,8 @@ class HuggingFaceProvider(BaseProvider):
874
877
  truncation=True,
875
878
  return_full_text=False
876
879
  )
880
+
881
+ gen_time = round((time.time() - start_time) * 1000, 1)
877
882
 
878
883
  if outputs and len(outputs) > 0:
879
884
  response_text = outputs[0]['generated_text'].strip()
@@ -885,34 +890,41 @@ class HuggingFaceProvider(BaseProvider):
885
890
  content=response_text,
886
891
  model=self.model,
887
892
  finish_reason="stop",
888
- usage=usage
893
+ usage=usage,
894
+ gen_time=gen_time
889
895
  )
890
896
  else:
891
897
  return GenerateResponse(
892
898
  content="",
893
899
  model=self.model,
894
- finish_reason="stop"
900
+ finish_reason="stop",
901
+ gen_time=gen_time
895
902
  )
896
903
 
897
904
  except Exception as e:
905
+ gen_time = round((time.time() - start_time) * 1000, 1) if 'start_time' in locals() else 0.0
898
906
  return GenerateResponse(
899
907
  content=f"Error: {str(e)}",
900
908
  model=self.model,
901
- finish_reason="error"
909
+ finish_reason="error",
910
+ gen_time=gen_time
902
911
  )
903
912
 
904
913
  def _calculate_usage(self, prompt: str, response: str) -> Dict[str, int]:
905
914
  """Calculate token usage using centralized token utilities."""
906
915
  from ..utils.token_utils import TokenUtils
907
916
 
908
- prompt_tokens = TokenUtils.estimate_tokens(prompt, self.model)
909
- completion_tokens = TokenUtils.estimate_tokens(response, self.model)
910
- total_tokens = prompt_tokens + completion_tokens
917
+ input_tokens = TokenUtils.estimate_tokens(prompt, self.model)
918
+ output_tokens = TokenUtils.estimate_tokens(response, self.model)
919
+ total_tokens = input_tokens + output_tokens
911
920
 
912
921
  return {
913
- "prompt_tokens": prompt_tokens,
914
- "completion_tokens": completion_tokens,
915
- "total_tokens": total_tokens
922
+ "input_tokens": input_tokens,
923
+ "output_tokens": output_tokens,
924
+ "total_tokens": total_tokens,
925
+ # Keep legacy keys for backward compatibility
926
+ "prompt_tokens": input_tokens,
927
+ "completion_tokens": output_tokens
916
928
  }
917
929
 
918
930
  def _stream_generate_transformers(self, input_text: str, max_new_tokens: int,
@@ -4,6 +4,7 @@ LM Studio provider implementation (OpenAI-compatible API).
4
4
 
5
5
  import httpx
6
6
  import json
7
+ import time
7
8
  from typing import List, Dict, Any, Optional, Union, Iterator, Type
8
9
 
9
10
  try:
@@ -225,12 +226,15 @@ class LMStudioProvider(BaseProvider):
225
226
  if not hasattr(self, 'client') or self.client is None:
226
227
  raise ProviderAPIError("HTTP client not initialized")
227
228
 
229
+ # Track generation time
230
+ start_time = time.time()
228
231
  response = self.client.post(
229
232
  f"{self.base_url}/chat/completions",
230
233
  json=payload,
231
234
  headers={"Content-Type": "application/json"}
232
235
  )
233
236
  response.raise_for_status()
237
+ gen_time = round((time.time() - start_time) * 1000, 1)
234
238
 
235
239
  result = response.json()
236
240
 
@@ -252,10 +256,14 @@ class LMStudioProvider(BaseProvider):
252
256
  finish_reason=finish_reason,
253
257
  raw_response=result,
254
258
  usage={
259
+ "input_tokens": usage.get("prompt_tokens", 0),
260
+ "output_tokens": usage.get("completion_tokens", 0),
261
+ "total_tokens": usage.get("total_tokens", 0),
262
+ # Keep legacy keys for backward compatibility
255
263
  "prompt_tokens": usage.get("prompt_tokens", 0),
256
- "completion_tokens": usage.get("completion_tokens", 0),
257
- "total_tokens": usage.get("total_tokens", 0)
258
- }
264
+ "completion_tokens": usage.get("completion_tokens", 0)
265
+ },
266
+ gen_time=gen_time
259
267
  )
260
268
 
261
269
  except AttributeError as e:
@@ -266,6 +266,9 @@ class MLXProvider(BaseProvider):
266
266
  mx.random.seed(seed)
267
267
  self.logger.debug(f"Set MLX random seed to {seed} for deterministic generation")
268
268
 
269
+ # Track generation time
270
+ start_time = time.time()
271
+
269
272
  # Try different MLX API signatures
270
273
  try:
271
274
  # Try new mlx-lm API
@@ -288,6 +291,8 @@ class MLXProvider(BaseProvider):
288
291
  # Fallback to basic response
289
292
  response_text = prompt + " I am an AI assistant powered by MLX on Apple Silicon."
290
293
 
294
+ gen_time = round((time.time() - start_time) * 1000, 1)
295
+
291
296
  # Use the full response as-is - preserve all content including thinking
292
297
  generated = response_text.strip()
293
298
 
@@ -295,21 +300,25 @@ class MLXProvider(BaseProvider):
295
300
  content=generated,
296
301
  model=self.model,
297
302
  finish_reason="stop",
298
- usage=self._calculate_usage(prompt, generated)
303
+ usage=self._calculate_usage(prompt, generated),
304
+ gen_time=gen_time
299
305
  )
300
306
 
301
307
  def _calculate_usage(self, prompt: str, response: str) -> Dict[str, int]:
302
308
  """Calculate token usage using centralized token utilities."""
303
309
  from ..utils.token_utils import TokenUtils
304
310
 
305
- prompt_tokens = TokenUtils.estimate_tokens(prompt, self.model)
306
- completion_tokens = TokenUtils.estimate_tokens(response, self.model)
307
- total_tokens = prompt_tokens + completion_tokens
311
+ input_tokens = TokenUtils.estimate_tokens(prompt, self.model)
312
+ output_tokens = TokenUtils.estimate_tokens(response, self.model)
313
+ total_tokens = input_tokens + output_tokens
308
314
 
309
315
  return {
310
- "prompt_tokens": prompt_tokens,
311
- "completion_tokens": completion_tokens,
312
- "total_tokens": total_tokens
316
+ "input_tokens": input_tokens,
317
+ "output_tokens": output_tokens,
318
+ "total_tokens": total_tokens,
319
+ # Keep legacy keys for backward compatibility
320
+ "prompt_tokens": input_tokens,
321
+ "completion_tokens": output_tokens
313
322
  }
314
323
 
315
324
  def _stream_generate(self, prompt: str, max_tokens: int, temperature: float, top_p: float, tool_call_tags: Optional[str] = None, seed: Optional[int] = None) -> Iterator[GenerateResponse]:
@@ -225,11 +225,14 @@ class OllamaProvider(BaseProvider):
225
225
  def _single_generate(self, endpoint: str, payload: Dict[str, Any], tools: Optional[List[Dict[str, Any]]] = None) -> GenerateResponse:
226
226
  """Generate single response"""
227
227
  try:
228
+ # Track generation time
229
+ start_time = time.time()
228
230
  response = self.client.post(
229
231
  f"{self.base_url}{endpoint}",
230
232
  json=payload
231
233
  )
232
234
  response.raise_for_status()
235
+ gen_time = round((time.time() - start_time) * 1000, 1)
233
236
 
234
237
  result = response.json()
235
238
 
@@ -246,10 +249,14 @@ class OllamaProvider(BaseProvider):
246
249
  finish_reason="stop",
247
250
  raw_response=result,
248
251
  usage={
252
+ "input_tokens": result.get("prompt_eval_count", 0),
253
+ "output_tokens": result.get("eval_count", 0),
254
+ "total_tokens": result.get("prompt_eval_count", 0) + result.get("eval_count", 0),
255
+ # Keep legacy keys for backward compatibility
249
256
  "prompt_tokens": result.get("prompt_eval_count", 0),
250
- "completion_tokens": result.get("eval_count", 0),
251
- "total_tokens": result.get("prompt_eval_count", 0) + result.get("eval_count", 0)
252
- }
257
+ "completion_tokens": result.get("eval_count", 0)
258
+ },
259
+ gen_time=gen_time
253
260
  )
254
261
 
255
262
  # Execute tools if enabled and tools are present
@@ -169,8 +169,14 @@ class OpenAIProvider(BaseProvider):
169
169
  if stream:
170
170
  return self._stream_response(call_params, tools)
171
171
  else:
172
+ # Track generation time
173
+ start_time = time.time()
172
174
  response = self.client.chat.completions.create(**call_params)
175
+ gen_time = round((time.time() - start_time) * 1000, 1)
176
+
173
177
  formatted = self._format_response(response)
178
+ # Add generation time to response
179
+ formatted.gen_time = gen_time
174
180
 
175
181
  # Handle tool execution for OpenAI native responses
176
182
  if tools and formatted.has_tool_calls():
@@ -216,13 +222,16 @@ class OpenAIProvider(BaseProvider):
216
222
  "arguments": tc.function.arguments
217
223
  })
218
224
 
219
- # Build usage dict with detailed breakdown
225
+ # Build usage dict with consistent terminology
220
226
  usage = None
221
227
  if hasattr(response, 'usage'):
222
228
  usage = {
229
+ "input_tokens": response.usage.prompt_tokens,
230
+ "output_tokens": response.usage.completion_tokens,
231
+ "total_tokens": response.usage.total_tokens,
232
+ # Keep legacy keys for backward compatibility
223
233
  "prompt_tokens": response.usage.prompt_tokens,
224
- "completion_tokens": response.usage.completion_tokens,
225
- "total_tokens": response.usage.total_tokens
234
+ "completion_tokens": response.usage.completion_tokens
226
235
  }
227
236
 
228
237
  # Add detailed token breakdown for reasoning models
@@ -136,19 +136,6 @@ class ProviderRegistry:
136
136
  import_path="..providers.huggingface_provider"
137
137
  ))
138
138
 
139
- # Mock Provider
140
- self.register_provider(ProviderInfo(
141
- name="mock",
142
- display_name="Mock",
143
- provider_class=None,
144
- description="Testing provider for development and unit tests",
145
- default_model="mock-model",
146
- supported_features=["chat", "completion", "embeddings", "prompted_tools", "streaming", "testing"],
147
- authentication_required=False,
148
- local_provider=True,
149
- installation_extras=None,
150
- import_path="..providers.mock_provider"
151
- ))
152
139
 
153
140
  def register_provider(self, provider_info: ProviderInfo):
154
141
  """Register a provider in the registry."""
@@ -182,10 +169,7 @@ class ProviderRegistry:
182
169
  def _load_provider_class(self, provider_info: ProviderInfo):
183
170
  """Dynamically load a provider class."""
184
171
  try:
185
- if provider_info.name == "mock":
186
- from ..providers.mock_provider import MockProvider
187
- return MockProvider
188
- elif provider_info.name == "openai":
172
+ if provider_info.name == "openai":
189
173
  from ..providers.openai_provider import OpenAIProvider
190
174
  return OpenAIProvider
191
175
  elif provider_info.name == "anthropic":
@@ -1101,7 +1101,6 @@ async def list_providers():
1101
1101
  - **LMStudio**: Local model development and testing platform
1102
1102
  - **MLX**: Apple Silicon optimized local inference
1103
1103
  - **HuggingFace**: Access to HuggingFace models (transformers and embeddings)
1104
- - **Mock**: Testing provider for development
1105
1104
 
1106
1105
  **Use Cases:**
1107
1106
  - Discover available providers before making requests
@@ -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.4.6"
14
+ __version__ = "2.4.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: abstractcore
3
- Version: 2.4.6
3
+ Version: 2.4.8
4
4
  Summary: Unified interface to all LLM providers with essential infrastructure for tool calling, streaming, and model management
5
5
  Author-email: Laurent-Philippe Albou <contact@abstractcore.ai>
6
6
  Maintainer-email: Laurent-Philippe Albou <contact@abstractcore.ai>
@@ -29,6 +29,7 @@ License-File: LICENSE
29
29
  Requires-Dist: pydantic<3.0.0,>=2.0.0
30
30
  Requires-Dist: httpx<1.0.0,>=0.24.0
31
31
  Requires-Dist: tiktoken<1.0.0,>=0.5.0
32
+ Requires-Dist: requests<3.0.0,>=2.25.0
32
33
  Provides-Extra: openai
33
34
  Requires-Dist: openai<2.0.0,>=1.0.0; extra == "openai"
34
35
  Provides-Extra: anthropic
@@ -46,6 +47,11 @@ Provides-Extra: embeddings
46
47
  Requires-Dist: sentence-transformers<4.0.0,>=2.7.0; extra == "embeddings"
47
48
  Requires-Dist: numpy<2.0.0,>=1.20.0; extra == "embeddings"
48
49
  Provides-Extra: processing
50
+ Provides-Extra: tools
51
+ Requires-Dist: beautifulsoup4<5.0.0,>=4.12.0; extra == "tools"
52
+ Requires-Dist: lxml<6.0.0,>=4.9.0; extra == "tools"
53
+ Requires-Dist: duckduckgo-search<4.0.0,>=3.8.0; extra == "tools"
54
+ Requires-Dist: psutil<6.0.0,>=5.9.0; extra == "tools"
49
55
  Provides-Extra: media
50
56
  Requires-Dist: Pillow<12.0.0,>=10.0.0; extra == "media"
51
57
  Requires-Dist: pymupdf4llm<1.0.0,>=0.0.20; extra == "media"
@@ -60,9 +66,9 @@ Requires-Dist: abstractcore[huggingface]; extra == "heavy-providers"
60
66
  Provides-Extra: all-providers
61
67
  Requires-Dist: abstractcore[anthropic,embeddings,huggingface,lmstudio,mlx,ollama,openai]; extra == "all-providers"
62
68
  Provides-Extra: all
63
- Requires-Dist: abstractcore[anthropic,dev,docs,embeddings,huggingface,lmstudio,media,mlx,ollama,openai,processing,server,test]; extra == "all"
69
+ Requires-Dist: abstractcore[anthropic,dev,docs,embeddings,huggingface,lmstudio,media,mlx,ollama,openai,processing,server,test,tools]; extra == "all"
64
70
  Provides-Extra: lightweight
65
- Requires-Dist: abstractcore[anthropic,embeddings,lmstudio,media,ollama,openai,processing,server]; extra == "lightweight"
71
+ Requires-Dist: abstractcore[anthropic,embeddings,lmstudio,media,ollama,openai,processing,server,tools]; extra == "lightweight"
66
72
  Provides-Extra: dev
67
73
  Requires-Dist: pytest>=7.0.0; extra == "dev"
68
74
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -89,7 +95,7 @@ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
89
95
  Requires-Dist: mkdocstrings[python]>=0.22.0; extra == "docs"
90
96
  Requires-Dist: mkdocs-autorefs>=0.4.0; extra == "docs"
91
97
  Provides-Extra: full-dev
92
- Requires-Dist: abstractcore[all-providers,dev,docs,test]; extra == "full-dev"
98
+ Requires-Dist: abstractcore[all-providers,dev,docs,test,tools]; extra == "full-dev"
93
99
  Dynamic: license-file
94
100
 
95
101
  # AbstractCore
@@ -155,6 +161,45 @@ response = llm.generate(
155
161
  print(response.content)
156
162
  ```
157
163
 
164
+ ### Response Object (GenerateResponse)
165
+
166
+ Every LLM generation returns a **GenerateResponse** object with consistent structure across all providers:
167
+
168
+ ```python
169
+ from abstractcore import create_llm
170
+
171
+ llm = create_llm("openai", model="gpt-4o-mini")
172
+ response = llm.generate("Explain quantum computing in simple terms")
173
+
174
+ # Core response data
175
+ print(f"Content: {response.content}") # Generated text
176
+ print(f"Model: {response.model}") # Model used
177
+ print(f"Finish reason: {response.finish_reason}") # Why generation stopped
178
+
179
+ # Consistent token access across ALL providers (NEW in v2.4.7)
180
+ print(f"Input tokens: {response.input_tokens}") # Always available
181
+ print(f"Output tokens: {response.output_tokens}") # Always available
182
+ print(f"Total tokens: {response.total_tokens}") # Always available
183
+
184
+ # Generation time tracking (NEW in v2.4.7)
185
+ print(f"Generation time: {response.gen_time}ms") # Always available (rounded to 1 decimal)
186
+
187
+ # Advanced access
188
+ print(f"Tool calls: {response.tool_calls}") # Tools executed (if any)
189
+ print(f"Raw usage: {response.usage}") # Provider-specific token data
190
+ print(f"Metadata: {response.metadata}") # Additional context
191
+
192
+ # Comprehensive summary
193
+ print(f"Summary: {response.get_summary()}") # "Model: gpt-4o-mini | Tokens: 117 | Time: 1234.5ms"
194
+ ```
195
+
196
+ **Token Count Sources:**
197
+ - **Provider APIs**: OpenAI, Anthropic, LMStudio (native API token counts)
198
+ - **AbstractCore Calculation**: MLX, HuggingFace (using `token_utils.py`)
199
+ - **Mixed Sources**: Ollama (combination of provider and calculated tokens)
200
+
201
+ **Backward Compatibility**: Legacy `prompt_tokens` and `completion_tokens` keys remain available in `response.usage` dictionary.
202
+
158
203
  ### Built-in Tools
159
204
 
160
205
  AbstractCore includes a comprehensive set of ready-to-use tools for common tasks:
@@ -271,6 +316,7 @@ response = llm.generate(
271
316
  - **Session Management**: Persistent conversations with metadata, analytics, and complete serialization
272
317
  - **Structured Responses**: Clean, predictable output formats with Pydantic
273
318
  - **Streaming Support**: Real-time token generation for interactive experiences
319
+ - **Consistent Token Terminology**: Unified `input_tokens`, `output_tokens`, `total_tokens` across all providers
274
320
  - **Embeddings**: Built-in support for semantic search and RAG applications
275
321
  - **Universal Server**: Optional OpenAI-compatible API server with `/v1/responses` endpoint
276
322
 
@@ -1,4 +1,4 @@
1
- abstractcore/__init__.py,sha256=7ucTDQXINslx23n-vaFOeFzRAYi76Enqak2S3rjaZLU,1861
1
+ abstractcore/__init__.py,sha256=A7Gbn_C-robdWLLQXjtTyWsoaXDGIblSFmLRV_ni6O8,1934
2
2
  abstractcore/apps/__init__.py,sha256=sgNOv3lYyOWNBC-w6GnRN6aILGCbdkQtNcuQdJz5ghE,31
3
3
  abstractcore/apps/__main__.py,sha256=041daYkoIE1VLEO19IZC5nNIDOQZWNgpB7ogJ3SOlsE,1585
4
4
  abstractcore/apps/app_config_utils.py,sha256=GygjtkianBktRvirHya1KbXuB2r5OUDJM6ilvybQWvk,875
@@ -10,17 +10,17 @@ abstractcore/architectures/detection.py,sha256=Cxap1pL6qOJjyY22Vc4hzbLALTuuBisPT
10
10
  abstractcore/architectures/enums.py,sha256=9vIv2vDBEKhxwzwH9iaSAyf-iVj3p8y9loMeN_mYTJ8,3821
11
11
  abstractcore/assets/architecture_formats.json,sha256=CIf6SaR_IJs1D7Uvd1K3zWngIXJ_yq3DM_IE3wnpCHY,16076
12
12
  abstractcore/assets/model_capabilities.json,sha256=iUkDiljyZUZzPlpYCOFgStXyc6e7dvOfReYQ0HFrX9Q,49703
13
- abstractcore/assets/session_schema.json,sha256=b6HTAWxRVlVhAzA7FqaKpunK1yO6jilBOsD5sQkqJTo,10580
13
+ abstractcore/assets/session_schema.json,sha256=hMCVrq3KSyVExrMGzuykf7bU-z6WyIVuEGU8du9_zUY,10570
14
14
  abstractcore/cli/__init__.py,sha256=rUjLjZSK3wENSw4g_iN43Bc2i5cggcEmj4NPXBMohdc,241
15
15
  abstractcore/cli/main.py,sha256=QD38nnfrInavO452WbkXCI37SVsdIu9VhvjEOojXBGY,31834
16
16
  abstractcore/cli/vision_config.py,sha256=jJzO4zBexh8SqSKp6YKOXdMDSv4AL4Ztl5Xi-5c4KyY,17869
17
17
  abstractcore/core/__init__.py,sha256=2h-86U4QkCQ4gzZ4iRusSTMlkODiUS6tKjZHiEXz6rM,684
18
18
  abstractcore/core/enums.py,sha256=BhkVnHC-X1_377JDmqd-2mnem9GdBLqixWlYzlP_FJU,695
19
- abstractcore/core/factory.py,sha256=UdrNwQAvifvFS3LMjF5KO87m-2n1bJBryTs9pvesYcI,2804
19
+ abstractcore/core/factory.py,sha256=ec7WGW2JKK-dhDplziTAeRkebEUFymtEEZ_bS5qkpqY,2798
20
20
  abstractcore/core/interface.py,sha256=-VAY0nlsTnWN_WghiuMC7iE7xUdZfYOg6KlgrAPi14Y,14086
21
21
  abstractcore/core/retry.py,sha256=wNlUAxfmvdO_uVWb4iqkhTqd7O1oRwXxqvVQaLXQOw0,14538
22
- abstractcore/core/session.py,sha256=cYmQv9m69ivpZXfR-a2xasbBRiP4IZt-9QDuuT6eHKw,36462
23
- abstractcore/core/types.py,sha256=KT9Gf9ei4t0QnWBH72fFa8vR7UZSKI-CJyQjU9ynE8g,3642
22
+ abstractcore/core/session.py,sha256=fdqhnufCWrV022RjQ-Xfb1KFv_s9-GzetSSR-QuXv-Q,36452
23
+ abstractcore/core/types.py,sha256=jj44i07kMjdg9FQ3mA_fK6r_M0Lcgt1RQpy1Ra5w-eI,4578
24
24
  abstractcore/embeddings/__init__.py,sha256=hR3xZyqcRm4c2pq1dIa5lxj_-Bk70Zad802JQN4joWo,637
25
25
  abstractcore/embeddings/manager.py,sha256=uFVbRPHx_R-kVMVA7N7_7EzeUmCJCeN9Dv0EV8Jko24,52245
26
26
  abstractcore/embeddings/models.py,sha256=bsPAzL6gv57AVii8O15PT0kxfwRkOml3f3njJN4UDi4,4874
@@ -47,19 +47,18 @@ abstractcore/processing/__init__.py,sha256=t6hiakQjcZROT4pw9ZFt2q6fF3vf5VpdMKG2E
47
47
  abstractcore/processing/basic_extractor.py,sha256=3x-3BdIHgLvqLnLF6K1-P4qVaLIpAnNIIutaJi7lDQM,49832
48
48
  abstractcore/processing/basic_judge.py,sha256=tKWJrg_tY4vCHzWgXxz0ZjgLXBYYfpMcpG7vl03hJcM,32218
49
49
  abstractcore/processing/basic_summarizer.py,sha256=XHNxMQ_8aLStTeUo6_2JaThlct12Htpz7ORmm0iuJsg,25495
50
- abstractcore/providers/__init__.py,sha256=t8Kp4flH5GvZEC6dx-iYJSPeSxMODa2spXb8MqtlPy4,1282
51
- abstractcore/providers/anthropic_provider.py,sha256=4DsHpfJ5iVnIB6gOL4iZGRjoR0R5kJhKdO5jG06iUmo,21287
50
+ abstractcore/providers/__init__.py,sha256=n-2RMNm3QpKxHw9EOjv8icRMRnfJp5Xg0uSVzHCW3BI,1222
51
+ abstractcore/providers/anthropic_provider.py,sha256=R87Z_DNNdeA4LMSxx84pqo8saKFz38dHCJMBpc-rL70,21552
52
52
  abstractcore/providers/base.py,sha256=YfrqM3c7wLT19vspL7goUO6Bv-z1691ZkCM2wxvQX4s,51501
53
- abstractcore/providers/huggingface_provider.py,sha256=v12JzpZ0Ra6OGD2aWcNdBMLxWytrW3gsSnzrr7F-rnA,48500
54
- abstractcore/providers/lmstudio_provider.py,sha256=NbhJMd3RjZ9nSIfy9lVmGAnxH8eGPz5ogRsN8YQfsl0,20629
55
- abstractcore/providers/mlx_provider.py,sha256=vbuv6lEfAURb6Dvcx7tpjV5woi5oZuZGsqwPBqiZ2EQ,18157
56
- abstractcore/providers/mock_provider.py,sha256=tIjA57Hlwu3vNODOZShNn0tY9HWvz1p4z-HyD_bsvbo,5741
57
- abstractcore/providers/ollama_provider.py,sha256=7p4BcCZ0UJabjw_lHzBqjQvtoEJYOj_NI511QjjWaSc,21361
58
- abstractcore/providers/openai_provider.py,sha256=3cm0TG2gbuoBDkoQmsliH9SBZCwL7hnKuzzDmwU3K4E,22853
59
- abstractcore/providers/registry.py,sha256=c0hxp9RRa-uipGotaAu48fHXc_HGlLcOxC1k763mzhU,16596
53
+ abstractcore/providers/huggingface_provider.py,sha256=pgpeSwpwyNB_5GDyLEz2OSTu9me-GAJzQ116dGtpCvQ,49012
54
+ abstractcore/providers/lmstudio_provider.py,sha256=odT6luVR3POVcq2ZqkINLyLoCAu_YGpLLj3fEddNliU,21021
55
+ abstractcore/providers/mlx_provider.py,sha256=sDgxf_kVJJwxprQWVef9w2CLOu2dLES8D0Vf5tY6PzE,18463
56
+ abstractcore/providers/ollama_provider.py,sha256=1bE80NCj_TQADxRCiu9luyLuI_gZe2EO5pCKoC4VhQM,21740
57
+ abstractcore/providers/openai_provider.py,sha256=1s7JJalyIBOvLB7UAUwXbTc2aYrYSWg7hJjKGnCX1qU,23313
58
+ abstractcore/providers/registry.py,sha256=fKFrN6Z3o5Gi0dfwvXDPtrrJXDjx9oPSfjWjZf-NJBc,15883
60
59
  abstractcore/providers/streaming.py,sha256=VnffBV_CU9SAKzghL154OoFyEdDsiLwUNXPahyU41Bw,31342
61
60
  abstractcore/server/__init__.py,sha256=1DSAz_YhQtnKv7sNi5TMQV8GFujctDOabgvAdilQE0o,249
62
- abstractcore/server/app.py,sha256=2ChRxdpMyGVCjsQPWWpPNtqdtfIIhbpF3uLuTCiF6lo,96629
61
+ abstractcore/server/app.py,sha256=7pG5ZkZqYNnyby4jyvp3_NKl5nNDmZpOhv_-F8Jruy4,96580
63
62
  abstractcore/structured/__init__.py,sha256=VXRQHGcm-iaYnLOBPin2kyhvhhQA0kaGt_pcNDGsE_8,339
64
63
  abstractcore/structured/handler.py,sha256=Vb15smiR81JGDXX2RLkY2Exuj67J7a6C-xwVrZoXp0I,17134
65
64
  abstractcore/structured/retry.py,sha256=BN_PvrWybyU1clMy2cult1-TVxFSMaVqiCPmmXvA5aI,3805
@@ -77,10 +76,10 @@ abstractcore/utils/message_preprocessor.py,sha256=GdHkm6tmrgjm3PwHRSCjIsq1XLkbhy
77
76
  abstractcore/utils/self_fixes.py,sha256=QEDwNTW80iQM4ftfEY3Ghz69F018oKwLM9yeRCYZOvw,5886
78
77
  abstractcore/utils/structured_logging.py,sha256=Vm-HviSa42G9DJCWmaEv4a0QG3NMsADD3ictLOs4En0,19952
79
78
  abstractcore/utils/token_utils.py,sha256=eLwFmJ68p9WMFD_MHLMmeJRW6Oqx_4hKELB8FNQ2Mnk,21097
80
- abstractcore/utils/version.py,sha256=r8Rzbfb6YwcUi9ek9BN0m6rVNtA2oImg_hOzRFnS2CE,605
81
- abstractcore-2.4.6.dist-info/licenses/LICENSE,sha256=PI2v_4HMvd6050uDD_4AY_8PzBnu2asa3RKbdDjowTA,1078
82
- abstractcore-2.4.6.dist-info/METADATA,sha256=agFFlRyASBw8uDKCAmchMdvggXjPydMHTfARd4GQqcU,29749
83
- abstractcore-2.4.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
84
- abstractcore-2.4.6.dist-info/entry_points.txt,sha256=UdVmchBC_Lt3H4Vlkt5js-QDAkVlBbkCu1yCsswk-KE,454
85
- abstractcore-2.4.6.dist-info/top_level.txt,sha256=DiNHBI35SIawW3N9Z-z0y6cQYNbXd32pvBkW0RLfScs,13
86
- abstractcore-2.4.6.dist-info/RECORD,,
79
+ abstractcore/utils/version.py,sha256=4vhW55aGVOlb2i9Zm_ZxoGAPd5qtSm6ZN1WmH15tBYs,605
80
+ abstractcore-2.4.8.dist-info/licenses/LICENSE,sha256=PI2v_4HMvd6050uDD_4AY_8PzBnu2asa3RKbdDjowTA,1078
81
+ abstractcore-2.4.8.dist-info/METADATA,sha256=UfLD0Zi-Mco6B9NkoWuypgHZuUPJeGYyJVJjBBPlpsA,31907
82
+ abstractcore-2.4.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
83
+ abstractcore-2.4.8.dist-info/entry_points.txt,sha256=UdVmchBC_Lt3H4Vlkt5js-QDAkVlBbkCu1yCsswk-KE,454
84
+ abstractcore-2.4.8.dist-info/top_level.txt,sha256=DiNHBI35SIawW3N9Z-z0y6cQYNbXd32pvBkW0RLfScs,13
85
+ abstractcore-2.4.8.dist-info/RECORD,,
@@ -1,157 +0,0 @@
1
- """
2
- Mock provider for testing purposes.
3
- """
4
-
5
- from typing import List, Dict, Any, Optional, Union, Iterator, Type
6
-
7
- try:
8
- from pydantic import BaseModel
9
- PYDANTIC_AVAILABLE = True
10
- except ImportError:
11
- PYDANTIC_AVAILABLE = False
12
- BaseModel = None
13
-
14
- from .base import BaseProvider
15
- from ..core.types import GenerateResponse
16
-
17
-
18
- class MockProvider(BaseProvider):
19
- """Simple mock provider for testing core functionality."""
20
-
21
- def __init__(self, model: str = "mock-model", **kwargs):
22
- super().__init__(model, **kwargs)
23
-
24
- # Handle timeout parameter for mock provider
25
- self._handle_timeout_parameter(kwargs)
26
-
27
- # Mock provider uses prompted strategy for structured output
28
- self.model_capabilities = {"structured_output": "prompted"}
29
-
30
- def generate(self, *args, **kwargs):
31
- """Public generate method that includes telemetry"""
32
- return self.generate_with_telemetry(*args, **kwargs)
33
-
34
- def _generate_internal(self,
35
- prompt: str,
36
- messages: Optional[List[Dict[str, str]]] = None,
37
- system_prompt: Optional[str] = None,
38
- tools: Optional[List[Dict[str, Any]]] = None,
39
- stream: bool = False,
40
- response_model: Optional[Type[BaseModel]] = None,
41
- **kwargs) -> Union[GenerateResponse, Iterator[GenerateResponse]]:
42
- """Mock generation implementation"""
43
-
44
- if stream:
45
- return self._stream_response(prompt)
46
- else:
47
- return self._single_response(prompt, response_model)
48
-
49
- def _single_response(self, prompt: str, response_model: Optional[Type[BaseModel]] = None) -> GenerateResponse:
50
- """Generate single mock response"""
51
-
52
- if response_model and PYDANTIC_AVAILABLE:
53
- # Generate valid JSON for structured output
54
- content = self._generate_mock_json(response_model)
55
- else:
56
- content = f"Mock response to: {prompt}"
57
-
58
- return GenerateResponse(
59
- content=content,
60
- model=self.model,
61
- finish_reason="stop",
62
- usage=self._calculate_mock_usage(prompt, content)
63
- )
64
-
65
- def _calculate_mock_usage(self, prompt: str, response: str) -> Dict[str, int]:
66
- """Calculate mock token usage using centralized token utilities."""
67
- from ..utils.token_utils import TokenUtils
68
-
69
- prompt_tokens = TokenUtils.estimate_tokens(prompt, self.model)
70
- completion_tokens = TokenUtils.estimate_tokens(response, self.model)
71
- total_tokens = prompt_tokens + completion_tokens
72
-
73
- return {
74
- "prompt_tokens": prompt_tokens,
75
- "completion_tokens": completion_tokens,
76
- "total_tokens": total_tokens
77
- }
78
-
79
- def _stream_response(self, prompt: str) -> Iterator[GenerateResponse]:
80
- """Generate streaming mock responses"""
81
- words = f"Mock response to: {prompt}".split()
82
- for i, word in enumerate(words):
83
- yield GenerateResponse(
84
- content=word + (" " if i < len(words) - 1 else ""),
85
- model=self.model,
86
- finish_reason="stop" if i == len(words) - 1 else None
87
- )
88
-
89
- def _generate_mock_json(self, model_class: Type[BaseModel]) -> str:
90
- """Generate valid JSON for Pydantic model"""
91
- import json
92
-
93
- # Create mock data based on field types
94
- mock_data = {}
95
- for field_name, field_info in model_class.model_fields.items():
96
- field_type = field_info.annotation
97
-
98
- # Handle basic types
99
- if field_type == str:
100
- mock_data[field_name] = f"mock_{field_name}"
101
- elif field_type == int:
102
- mock_data[field_name] = 42
103
- elif field_type == float:
104
- mock_data[field_name] = 3.14
105
- elif field_type == bool:
106
- mock_data[field_name] = True
107
- else:
108
- # For complex types, provide reasonable defaults
109
- mock_data[field_name] = f"mock_{field_name}"
110
-
111
- return json.dumps(mock_data)
112
-
113
- def _handle_timeout_parameter(self, kwargs: Dict[str, Any]) -> None:
114
- """
115
- Handle timeout parameter for Mock provider.
116
-
117
- Mock provider simulates responses instantly, so timeout parameters
118
- don't apply. If a non-None timeout is provided, it's accepted but
119
- has no effect on mock generation.
120
-
121
- Args:
122
- kwargs: Initialization kwargs that may contain timeout
123
- """
124
- timeout_value = kwargs.get('timeout')
125
- if timeout_value is not None:
126
- # For mock provider, we accept timeout but it has no effect
127
- # No warning needed since this is for testing
128
- self._timeout = timeout_value
129
- else:
130
- # Keep None value
131
- self._timeout = None
132
-
133
- def _update_http_client_timeout(self) -> None:
134
- """
135
- Mock provider doesn't use HTTP clients.
136
- Timeout changes have no effect on mock responses.
137
- """
138
- # No-op for mock provider - no HTTP clients used
139
- pass
140
-
141
- def get_capabilities(self) -> List[str]:
142
- """Get mock capabilities"""
143
- return ["tools", "streaming", "vision"]
144
-
145
- def validate_config(self) -> bool:
146
- """Validate mock provider configuration"""
147
- return True
148
-
149
- def list_available_models(self, **kwargs) -> List[str]:
150
- """List available mock models for testing."""
151
- return [
152
- "mock-model",
153
- "mock-chat-model",
154
- "mock-streaming-model",
155
- "mock-structured-model",
156
- "mock-vision-model"
157
- ]