abstractcore 2.9.1__py3-none-any.whl → 2.11.2__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 +7 -27
- abstractcore/apps/extractor.py +33 -100
- abstractcore/apps/intent.py +19 -0
- abstractcore/apps/judge.py +20 -1
- abstractcore/apps/summarizer.py +20 -1
- abstractcore/architectures/detection.py +34 -1
- abstractcore/architectures/response_postprocessing.py +313 -0
- abstractcore/assets/architecture_formats.json +38 -8
- abstractcore/assets/model_capabilities.json +781 -160
- abstractcore/compression/__init__.py +1 -2
- abstractcore/compression/glyph_processor.py +6 -4
- abstractcore/config/main.py +31 -19
- abstractcore/config/manager.py +389 -11
- abstractcore/config/vision_config.py +5 -5
- abstractcore/core/interface.py +151 -3
- abstractcore/core/session.py +16 -10
- abstractcore/download.py +1 -1
- abstractcore/embeddings/manager.py +20 -6
- abstractcore/endpoint/__init__.py +2 -0
- abstractcore/endpoint/app.py +458 -0
- abstractcore/mcp/client.py +3 -1
- abstractcore/media/__init__.py +52 -17
- abstractcore/media/auto_handler.py +42 -22
- abstractcore/media/base.py +44 -1
- abstractcore/media/capabilities.py +12 -33
- abstractcore/media/enrichment.py +105 -0
- abstractcore/media/handlers/anthropic_handler.py +19 -28
- abstractcore/media/handlers/local_handler.py +124 -70
- abstractcore/media/handlers/openai_handler.py +19 -31
- abstractcore/media/processors/__init__.py +4 -2
- abstractcore/media/processors/audio_processor.py +57 -0
- abstractcore/media/processors/office_processor.py +8 -3
- abstractcore/media/processors/pdf_processor.py +46 -3
- abstractcore/media/processors/text_processor.py +22 -24
- abstractcore/media/processors/video_processor.py +58 -0
- abstractcore/media/types.py +97 -4
- abstractcore/media/utils/image_scaler.py +20 -2
- abstractcore/media/utils/video_frames.py +219 -0
- abstractcore/media/vision_fallback.py +136 -22
- abstractcore/processing/__init__.py +32 -3
- abstractcore/processing/basic_deepsearch.py +15 -10
- abstractcore/processing/basic_intent.py +3 -2
- abstractcore/processing/basic_judge.py +3 -2
- abstractcore/processing/basic_summarizer.py +1 -1
- abstractcore/providers/__init__.py +3 -1
- abstractcore/providers/anthropic_provider.py +95 -8
- abstractcore/providers/base.py +1516 -81
- abstractcore/providers/huggingface_provider.py +546 -69
- abstractcore/providers/lmstudio_provider.py +35 -923
- abstractcore/providers/mlx_provider.py +382 -35
- abstractcore/providers/model_capabilities.py +5 -1
- abstractcore/providers/ollama_provider.py +99 -15
- abstractcore/providers/openai_compatible_provider.py +406 -180
- abstractcore/providers/openai_provider.py +188 -44
- abstractcore/providers/openrouter_provider.py +76 -0
- abstractcore/providers/registry.py +61 -5
- abstractcore/providers/streaming.py +138 -33
- abstractcore/providers/vllm_provider.py +92 -817
- abstractcore/server/app.py +461 -13
- abstractcore/server/audio_endpoints.py +139 -0
- abstractcore/server/vision_endpoints.py +1319 -0
- abstractcore/structured/handler.py +316 -41
- abstractcore/tools/common_tools.py +5501 -2012
- abstractcore/tools/comms_tools.py +1641 -0
- abstractcore/tools/core.py +37 -7
- abstractcore/tools/handler.py +4 -9
- abstractcore/tools/parser.py +49 -2
- abstractcore/tools/tag_rewriter.py +2 -1
- abstractcore/tools/telegram_tdlib.py +407 -0
- abstractcore/tools/telegram_tools.py +261 -0
- abstractcore/utils/cli.py +1085 -72
- abstractcore/utils/token_utils.py +2 -0
- abstractcore/utils/truncation.py +29 -0
- abstractcore/utils/version.py +3 -4
- abstractcore/utils/vlm_token_calculator.py +12 -2
- abstractcore-2.11.2.dist-info/METADATA +562 -0
- abstractcore-2.11.2.dist-info/RECORD +133 -0
- {abstractcore-2.9.1.dist-info → abstractcore-2.11.2.dist-info}/WHEEL +1 -1
- {abstractcore-2.9.1.dist-info → abstractcore-2.11.2.dist-info}/entry_points.txt +1 -0
- abstractcore-2.9.1.dist-info/METADATA +0 -1190
- abstractcore-2.9.1.dist-info/RECORD +0 -119
- {abstractcore-2.9.1.dist-info → abstractcore-2.11.2.dist-info}/licenses/LICENSE +0 -0
- {abstractcore-2.9.1.dist-info → abstractcore-2.11.2.dist-info}/top_level.txt +0 -0
|
@@ -51,7 +51,7 @@ class OllamaProvider(BaseProvider):
|
|
|
51
51
|
)
|
|
52
52
|
return self._async_client
|
|
53
53
|
|
|
54
|
-
def
|
|
54
|
+
def unload_model(self, model_name: str) -> None:
|
|
55
55
|
"""
|
|
56
56
|
Unload the model from Ollama server memory.
|
|
57
57
|
|
|
@@ -59,9 +59,11 @@ class OllamaProvider(BaseProvider):
|
|
|
59
59
|
from the Ollama server, freeing server-side memory.
|
|
60
60
|
"""
|
|
61
61
|
try:
|
|
62
|
+
target_model = model_name.strip() if isinstance(model_name, str) and model_name.strip() else self.model
|
|
63
|
+
|
|
62
64
|
# Send a minimal generate request with keep_alive=0 to unload
|
|
63
65
|
payload = {
|
|
64
|
-
"model":
|
|
66
|
+
"model": target_model,
|
|
65
67
|
"prompt": "", # Minimal prompt
|
|
66
68
|
"stream": False,
|
|
67
69
|
"keep_alive": 0 # Immediately unload after this request
|
|
@@ -97,6 +99,47 @@ class OllamaProvider(BaseProvider):
|
|
|
97
99
|
"""Public generate method that includes telemetry"""
|
|
98
100
|
return self.generate_with_telemetry(*args, **kwargs)
|
|
99
101
|
|
|
102
|
+
def _apply_provider_thinking_kwargs(
|
|
103
|
+
self,
|
|
104
|
+
*,
|
|
105
|
+
enabled: Optional[bool],
|
|
106
|
+
level: Optional[str],
|
|
107
|
+
kwargs: Dict[str, Any],
|
|
108
|
+
) -> tuple[Dict[str, Any], bool]:
|
|
109
|
+
# Ollama exposes "thinking" via the request field `think`.
|
|
110
|
+
# Most models accept a boolean; GPT-OSS uses "low|medium|high" and cannot fully disable traces.
|
|
111
|
+
if enabled is None and level is None:
|
|
112
|
+
return kwargs, False
|
|
113
|
+
|
|
114
|
+
new_kwargs = dict(kwargs)
|
|
115
|
+
|
|
116
|
+
reasoning_levels = self._model_reasoning_levels()
|
|
117
|
+
wants_levels = bool(reasoning_levels)
|
|
118
|
+
|
|
119
|
+
if wants_levels:
|
|
120
|
+
# Prefer explicit level; otherwise map off->low, on->medium.
|
|
121
|
+
if level is not None:
|
|
122
|
+
new_kwargs["think"] = level
|
|
123
|
+
return new_kwargs, True
|
|
124
|
+
if enabled is False:
|
|
125
|
+
new_kwargs["think"] = "low"
|
|
126
|
+
return new_kwargs, True
|
|
127
|
+
if enabled is True:
|
|
128
|
+
new_kwargs["think"] = "medium"
|
|
129
|
+
return new_kwargs, True
|
|
130
|
+
return kwargs, False
|
|
131
|
+
|
|
132
|
+
# Boolean mode.
|
|
133
|
+
if level is not None:
|
|
134
|
+
# Best-effort fallback.
|
|
135
|
+
new_kwargs["think"] = True
|
|
136
|
+
return new_kwargs, True
|
|
137
|
+
if enabled is not None:
|
|
138
|
+
new_kwargs["think"] = bool(enabled)
|
|
139
|
+
return new_kwargs, True
|
|
140
|
+
|
|
141
|
+
return kwargs, False
|
|
142
|
+
|
|
100
143
|
def _convert_messages_for_ollama(self, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
101
144
|
"""Convert OpenAI messages to Ollama-compatible format
|
|
102
145
|
|
|
@@ -167,16 +210,21 @@ class OllamaProvider(BaseProvider):
|
|
|
167
210
|
"model": self.model,
|
|
168
211
|
"stream": stream,
|
|
169
212
|
"options": {
|
|
170
|
-
"temperature":
|
|
213
|
+
"temperature": generation_kwargs.get("temperature", self.temperature),
|
|
171
214
|
"num_predict": max_output_tokens, # Ollama uses num_predict for max output tokens
|
|
172
215
|
}
|
|
173
216
|
}
|
|
174
217
|
|
|
175
218
|
# Add seed if provided (Ollama supports seed for deterministic outputs)
|
|
176
|
-
seed_value =
|
|
219
|
+
seed_value = generation_kwargs.get("seed")
|
|
177
220
|
if seed_value is not None:
|
|
178
221
|
payload["options"]["seed"] = seed_value
|
|
179
222
|
|
|
223
|
+
# Unified thinking/reasoning control (Ollama-native).
|
|
224
|
+
think_value = generation_kwargs.get("think")
|
|
225
|
+
if think_value is not None:
|
|
226
|
+
payload["think"] = think_value
|
|
227
|
+
|
|
180
228
|
# Add structured output support (Ollama native JSON schema)
|
|
181
229
|
# Ollama accepts the full JSON schema in the "format" parameter
|
|
182
230
|
# This provides server-side guaranteed schema compliance
|
|
@@ -204,6 +252,7 @@ class OllamaProvider(BaseProvider):
|
|
|
204
252
|
payload["messages"].extend(converted_messages)
|
|
205
253
|
|
|
206
254
|
# Handle media content regardless of prompt (media can be used with messages too)
|
|
255
|
+
media_enrichment = None
|
|
207
256
|
if media:
|
|
208
257
|
# Get the text to combine with media
|
|
209
258
|
user_message_text = prompt.strip() if prompt else ""
|
|
@@ -213,6 +262,7 @@ class OllamaProvider(BaseProvider):
|
|
|
213
262
|
|
|
214
263
|
# Create multimodal message combining text and media
|
|
215
264
|
multimodal_message = media_handler.create_multimodal_message(user_message_text, media)
|
|
265
|
+
media_enrichment = getattr(media_handler, "media_enrichment", None)
|
|
216
266
|
|
|
217
267
|
# For local providers, we might get a string (embedded text) or dict (structured)
|
|
218
268
|
if isinstance(multimodal_message, str):
|
|
@@ -223,7 +273,7 @@ class OllamaProvider(BaseProvider):
|
|
|
223
273
|
else:
|
|
224
274
|
payload["messages"].append(multimodal_message)
|
|
225
275
|
except ImportError:
|
|
226
|
-
self.logger.warning("Media processing not available. Install with: pip install abstractcore[media]")
|
|
276
|
+
self.logger.warning("Media processing not available. Install with: pip install \"abstractcore[media]\"")
|
|
227
277
|
if user_message_text:
|
|
228
278
|
payload["messages"].append({
|
|
229
279
|
"role": "user",
|
|
@@ -257,7 +307,12 @@ class OllamaProvider(BaseProvider):
|
|
|
257
307
|
if stream:
|
|
258
308
|
return self._stream_generate(endpoint, payload, tools, kwargs.get('tool_call_tags'))
|
|
259
309
|
else:
|
|
260
|
-
|
|
310
|
+
response = self._single_generate(endpoint, payload, tools, media_metadata)
|
|
311
|
+
if media_enrichment:
|
|
312
|
+
from ..media.enrichment import merge_enrichment_metadata
|
|
313
|
+
|
|
314
|
+
response.metadata = merge_enrichment_metadata(response.metadata, media_enrichment)
|
|
315
|
+
return response
|
|
261
316
|
|
|
262
317
|
def _single_generate(self, endpoint: str, payload: Dict[str, Any], tools: Optional[List[Dict[str, Any]]] = None, media_metadata: Optional[List[Dict[str, Any]]] = None) -> GenerateResponse:
|
|
263
318
|
"""Generate single response"""
|
|
@@ -303,6 +358,20 @@ class OllamaProvider(BaseProvider):
|
|
|
303
358
|
"url": f"{self.base_url}{endpoint}",
|
|
304
359
|
"payload": payload,
|
|
305
360
|
}
|
|
361
|
+
|
|
362
|
+
# Capture Ollama thinking output (if present) into canonical metadata["reasoning"].
|
|
363
|
+
thinking_text = None
|
|
364
|
+
try:
|
|
365
|
+
if endpoint == "/api/chat":
|
|
366
|
+
msg = result.get("message") if isinstance(result, dict) else None
|
|
367
|
+
msg = msg if isinstance(msg, dict) else {}
|
|
368
|
+
thinking_text = msg.get("thinking") or msg.get("reasoning")
|
|
369
|
+
else:
|
|
370
|
+
thinking_text = result.get("thinking") or result.get("reasoning")
|
|
371
|
+
except Exception:
|
|
372
|
+
thinking_text = None
|
|
373
|
+
if isinstance(thinking_text, str) and thinking_text.strip():
|
|
374
|
+
generate_response.metadata.setdefault("reasoning", thinking_text.strip())
|
|
306
375
|
|
|
307
376
|
# Attach media metadata if available
|
|
308
377
|
if media_metadata:
|
|
@@ -370,17 +439,31 @@ class OllamaProvider(BaseProvider):
|
|
|
370
439
|
rewritten_content, buffer = rewriter.rewrite_streaming_chunk(content, buffer)
|
|
371
440
|
content = rewritten_content
|
|
372
441
|
|
|
442
|
+
metadata: Dict[str, Any] = {
|
|
443
|
+
"_provider_request": {
|
|
444
|
+
"url": f"{self.base_url}{endpoint}",
|
|
445
|
+
"payload": payload,
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
# Capture incremental thinking output when present.
|
|
449
|
+
try:
|
|
450
|
+
if endpoint == "/api/chat":
|
|
451
|
+
msg = chunk.get("message") if isinstance(chunk, dict) else None
|
|
452
|
+
msg = msg if isinstance(msg, dict) else {}
|
|
453
|
+
thinking_text = msg.get("thinking") or msg.get("reasoning")
|
|
454
|
+
else:
|
|
455
|
+
thinking_text = chunk.get("thinking") or chunk.get("reasoning")
|
|
456
|
+
except Exception:
|
|
457
|
+
thinking_text = None
|
|
458
|
+
if isinstance(thinking_text, str) and thinking_text.strip():
|
|
459
|
+
metadata.setdefault("reasoning", thinking_text.strip())
|
|
460
|
+
|
|
373
461
|
chunk_response = GenerateResponse(
|
|
374
462
|
content=content,
|
|
375
463
|
model=self.model,
|
|
376
464
|
finish_reason="stop" if done else None,
|
|
377
465
|
raw_response=chunk,
|
|
378
|
-
metadata=
|
|
379
|
-
"_provider_request": {
|
|
380
|
-
"url": f"{self.base_url}{endpoint}",
|
|
381
|
-
"payload": payload,
|
|
382
|
-
}
|
|
383
|
-
},
|
|
466
|
+
metadata=metadata,
|
|
384
467
|
)
|
|
385
468
|
|
|
386
469
|
yield chunk_response
|
|
@@ -449,12 +532,12 @@ class OllamaProvider(BaseProvider):
|
|
|
449
532
|
"model": self.model,
|
|
450
533
|
"stream": stream,
|
|
451
534
|
"options": {
|
|
452
|
-
"temperature":
|
|
535
|
+
"temperature": generation_kwargs.get("temperature", self.temperature),
|
|
453
536
|
"num_predict": max_output_tokens,
|
|
454
537
|
}
|
|
455
538
|
}
|
|
456
539
|
|
|
457
|
-
seed_value =
|
|
540
|
+
seed_value = generation_kwargs.get("seed")
|
|
458
541
|
if seed_value is not None:
|
|
459
542
|
payload["options"]["seed"] = seed_value
|
|
460
543
|
|
|
@@ -685,7 +768,8 @@ class OllamaProvider(BaseProvider):
|
|
|
685
768
|
finish_reason=response.finish_reason,
|
|
686
769
|
raw_response=response.raw_response,
|
|
687
770
|
usage=response.usage,
|
|
688
|
-
tool_calls=tool_call_response.tool_calls
|
|
771
|
+
tool_calls=tool_call_response.tool_calls,
|
|
772
|
+
metadata=response.metadata,
|
|
689
773
|
)
|
|
690
774
|
|
|
691
775
|
def get_capabilities(self) -> List[str]:
|