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.
Files changed (66) hide show
  1. abstractcore/__init__.py +19 -1
  2. abstractcore/architectures/detection.py +252 -6
  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/retry.py +2 -2
  20. abstractcore/core/session.py +193 -7
  21. abstractcore/download.py +253 -0
  22. abstractcore/embeddings/manager.py +2 -2
  23. abstractcore/events/__init__.py +113 -2
  24. abstractcore/exceptions/__init__.py +49 -2
  25. abstractcore/media/auto_handler.py +312 -18
  26. abstractcore/media/handlers/local_handler.py +14 -2
  27. abstractcore/media/handlers/openai_handler.py +62 -3
  28. abstractcore/media/processors/__init__.py +11 -1
  29. abstractcore/media/processors/direct_pdf_processor.py +210 -0
  30. abstractcore/media/processors/glyph_pdf_processor.py +227 -0
  31. abstractcore/media/processors/image_processor.py +7 -1
  32. abstractcore/media/processors/office_processor.py +2 -2
  33. abstractcore/media/processors/text_processor.py +18 -3
  34. abstractcore/media/types.py +164 -7
  35. abstractcore/media/utils/image_scaler.py +2 -2
  36. abstractcore/media/vision_fallback.py +2 -2
  37. abstractcore/providers/__init__.py +18 -0
  38. abstractcore/providers/anthropic_provider.py +228 -8
  39. abstractcore/providers/base.py +378 -11
  40. abstractcore/providers/huggingface_provider.py +563 -23
  41. abstractcore/providers/lmstudio_provider.py +284 -4
  42. abstractcore/providers/mlx_provider.py +27 -2
  43. abstractcore/providers/model_capabilities.py +352 -0
  44. abstractcore/providers/ollama_provider.py +282 -6
  45. abstractcore/providers/openai_provider.py +286 -8
  46. abstractcore/providers/registry.py +85 -13
  47. abstractcore/providers/streaming.py +2 -2
  48. abstractcore/server/app.py +91 -81
  49. abstractcore/tools/common_tools.py +2 -2
  50. abstractcore/tools/handler.py +2 -2
  51. abstractcore/tools/parser.py +2 -2
  52. abstractcore/tools/registry.py +2 -2
  53. abstractcore/tools/syntax_rewriter.py +2 -2
  54. abstractcore/tools/tag_rewriter.py +3 -3
  55. abstractcore/utils/__init__.py +4 -1
  56. abstractcore/utils/self_fixes.py +2 -2
  57. abstractcore/utils/trace_export.py +287 -0
  58. abstractcore/utils/version.py +1 -1
  59. abstractcore/utils/vlm_token_calculator.py +655 -0
  60. {abstractcore-2.5.2.dist-info → abstractcore-2.6.0.dist-info}/METADATA +207 -8
  61. abstractcore-2.6.0.dist-info/RECORD +108 -0
  62. abstractcore-2.5.2.dist-info/RECORD +0 -90
  63. {abstractcore-2.5.2.dist-info → abstractcore-2.6.0.dist-info}/WHEEL +0 -0
  64. {abstractcore-2.5.2.dist-info → abstractcore-2.6.0.dist-info}/entry_points.txt +0 -0
  65. {abstractcore-2.5.2.dist-info → abstractcore-2.6.0.dist-info}/licenses/LICENSE +0 -0
  66. {abstractcore-2.5.2.dist-info → abstractcore-2.6.0.dist-info}/top_level.txt +0 -0
@@ -5,7 +5,7 @@ OpenAI provider implementation.
5
5
  import os
6
6
  import json
7
7
  import time
8
- from typing import List, Dict, Any, Optional, Union, Iterator, Type
8
+ from typing import List, Dict, Any, Optional, Union, Iterator, AsyncIterator, Type
9
9
 
10
10
  try:
11
11
  from pydantic import BaseModel
@@ -16,7 +16,7 @@ except ImportError:
16
16
  from .base import BaseProvider
17
17
  from ..core.types import GenerateResponse
18
18
  from ..media import MediaHandler
19
- from ..exceptions import AuthenticationError, ProviderAPIError, ModelNotFoundError, format_model_error
19
+ from ..exceptions import AuthenticationError, ProviderAPIError, ModelNotFoundError, format_model_error, format_auth_error
20
20
  from ..tools import UniversalToolHandler, execute_tools
21
21
  from ..events import EventType
22
22
 
@@ -30,7 +30,8 @@ except ImportError:
30
30
  class OpenAIProvider(BaseProvider):
31
31
  """OpenAI API provider with full integration"""
32
32
 
33
- def __init__(self, model: str = "gpt-3.5-turbo", api_key: Optional[str] = None, **kwargs):
33
+ def __init__(self, model: str = "gpt-3.5-turbo", api_key: Optional[str] = None,
34
+ base_url: Optional[str] = None, **kwargs):
34
35
  super().__init__(model, **kwargs)
35
36
  self.provider = "openai"
36
37
 
@@ -42,8 +43,15 @@ class OpenAIProvider(BaseProvider):
42
43
  if not self.api_key:
43
44
  raise ValueError("OpenAI API key required. Set OPENAI_API_KEY environment variable.")
44
45
 
45
- # Initialize client with timeout
46
- self.client = openai.OpenAI(api_key=self.api_key, timeout=self._timeout)
46
+ # Get base URL from param or environment
47
+ self.base_url = base_url or os.getenv("OPENAI_BASE_URL")
48
+
49
+ # Initialize client with timeout and optional base_url
50
+ client_kwargs = {"api_key": self.api_key, "timeout": self._timeout}
51
+ if self.base_url:
52
+ client_kwargs["base_url"] = self.base_url
53
+ self.client = openai.OpenAI(**client_kwargs)
54
+ self._async_client = None # Lazy-loaded async client
47
55
 
48
56
  # Initialize tool handler
49
57
  self.tool_handler = UniversalToolHandler(model)
@@ -60,6 +68,16 @@ class OpenAIProvider(BaseProvider):
60
68
  """Public generate method that includes telemetry"""
61
69
  return self.generate_with_telemetry(*args, **kwargs)
62
70
 
71
+ @property
72
+ def async_client(self):
73
+ """Lazy-load AsyncOpenAI client for native async operations."""
74
+ if self._async_client is None:
75
+ client_kwargs = {"api_key": self.api_key, "timeout": self._timeout}
76
+ if self.base_url:
77
+ client_kwargs["base_url"] = self.base_url
78
+ self._async_client = openai.AsyncOpenAI(**client_kwargs)
79
+ return self._async_client
80
+
63
81
  def _generate_internal(self,
64
82
  prompt: str,
65
83
  messages: Optional[List[Dict[str, str]]] = None,
@@ -188,6 +206,228 @@ class OpenAIProvider(BaseProvider):
188
206
  # Model validation is done at initialization, so this is likely an API error
189
207
  raise ProviderAPIError(f"OpenAI API error: {str(e)}")
190
208
 
209
+ async def _agenerate_internal(self,
210
+ prompt: str,
211
+ messages: Optional[List[Dict[str, str]]] = None,
212
+ system_prompt: Optional[str] = None,
213
+ tools: Optional[List[Dict[str, Any]]] = None,
214
+ media: Optional[List['MediaContent']] = None,
215
+ stream: bool = False,
216
+ response_model: Optional[Type[BaseModel]] = None,
217
+ **kwargs) -> Union[GenerateResponse, AsyncIterator[GenerateResponse]]:
218
+ """Native async implementation using AsyncOpenAI - 3-10x faster for batch operations."""
219
+
220
+ # Build messages array (same logic as sync)
221
+ api_messages = []
222
+
223
+ # Add system message if provided
224
+ if system_prompt:
225
+ api_messages.append({"role": "system", "content": system_prompt})
226
+
227
+ # Add conversation history
228
+ if messages:
229
+ for msg in messages:
230
+ # Skip system messages as they're handled separately
231
+ if msg.get("role") != "system":
232
+ api_messages.append({
233
+ "role": msg["role"],
234
+ "content": msg["content"]
235
+ })
236
+
237
+ # Add current prompt as user message
238
+ if prompt and prompt not in [msg.get("content") for msg in (messages or [])]:
239
+ # Handle multimodal message with media content
240
+ if media:
241
+ try:
242
+ from ..media.handlers import OpenAIMediaHandler
243
+ media_handler = OpenAIMediaHandler(self.model_capabilities)
244
+
245
+ # Create multimodal message combining text and media
246
+ multimodal_message = media_handler.create_multimodal_message(prompt, media)
247
+ api_messages.append(multimodal_message)
248
+ except ImportError:
249
+ self.logger.warning("Media processing not available. Install with: pip install abstractcore[media]")
250
+ api_messages.append({"role": "user", "content": prompt})
251
+ except Exception as e:
252
+ self.logger.warning(f"Failed to process media content: {e}")
253
+ api_messages.append({"role": "user", "content": prompt})
254
+ else:
255
+ api_messages.append({"role": "user", "content": prompt})
256
+
257
+ # Prepare API call parameters using unified system (same logic as sync)
258
+ generation_kwargs = self._prepare_generation_kwargs(**kwargs)
259
+ max_output_tokens = self._get_provider_max_tokens_param(generation_kwargs)
260
+
261
+ call_params = {
262
+ "model": self.model,
263
+ "messages": api_messages,
264
+ "stream": stream
265
+ }
266
+
267
+ # Add parameters that are supported by this model
268
+ if not self._is_reasoning_model():
269
+ # Reasoning models (o1, gpt-5) don't support many parameters
270
+ call_params["temperature"] = kwargs.get("temperature", self.temperature)
271
+ call_params["top_p"] = kwargs.get("top_p", self.top_p)
272
+ call_params["frequency_penalty"] = kwargs.get("frequency_penalty", self.frequency_penalty)
273
+ call_params["presence_penalty"] = kwargs.get("presence_penalty", self.presence_penalty)
274
+
275
+ # Add seed if provided (OpenAI supports seed for deterministic outputs)
276
+ seed_value = kwargs.get("seed", self.seed)
277
+ if seed_value is not None:
278
+ call_params["seed"] = seed_value
279
+
280
+ # Handle different token parameter names for different model families
281
+ if self._uses_max_completion_tokens():
282
+ call_params["max_completion_tokens"] = max_output_tokens
283
+ else:
284
+ call_params["max_tokens"] = max_output_tokens
285
+
286
+ # Add tools if provided (convert to native format)
287
+ if tools:
288
+ # Convert tools to native format for OpenAI API
289
+ if self.tool_handler.supports_native:
290
+ call_params["tools"] = self.tool_handler.prepare_tools_for_native(tools)
291
+ call_params["tool_choice"] = kwargs.get("tool_choice", "auto")
292
+ else:
293
+ # Fallback to manual formatting
294
+ call_params["tools"] = self._format_tools_for_openai(tools)
295
+ call_params["tool_choice"] = kwargs.get("tool_choice", "auto")
296
+
297
+ # Add structured output support (OpenAI native)
298
+ if response_model and PYDANTIC_AVAILABLE:
299
+ if self._supports_structured_output():
300
+ json_schema = response_model.model_json_schema()
301
+
302
+ # OpenAI requires additionalProperties: false for strict mode
303
+ self._ensure_strict_schema(json_schema)
304
+
305
+ call_params["response_format"] = {
306
+ "type": "json_schema",
307
+ "json_schema": {
308
+ "name": response_model.__name__,
309
+ "strict": True,
310
+ "schema": json_schema
311
+ }
312
+ }
313
+
314
+ # Make async API call with proper exception handling
315
+ try:
316
+ if stream:
317
+ return self._async_stream_response(call_params, tools)
318
+ else:
319
+ # Track generation time
320
+ start_time = time.time()
321
+ response = await self.async_client.chat.completions.create(**call_params)
322
+ gen_time = round((time.time() - start_time) * 1000, 1)
323
+
324
+ formatted = self._format_response(response)
325
+ # Add generation time to response
326
+ formatted.gen_time = gen_time
327
+
328
+ # Handle tool execution for OpenAI native responses
329
+ if tools and formatted.has_tool_calls():
330
+ formatted = self._handle_tool_execution(formatted, tools)
331
+
332
+ return formatted
333
+ except Exception as e:
334
+ # Model validation is done at initialization, so this is likely an API error
335
+ raise ProviderAPIError(f"OpenAI API error: {str(e)}")
336
+
337
+ async def _async_stream_response(self, call_params: Dict[str, Any], tools: Optional[List[Dict[str, Any]]] = None) -> AsyncIterator[GenerateResponse]:
338
+ """Native async streaming responses from OpenAI."""
339
+ try:
340
+ stream = await self.async_client.chat.completions.create(**call_params)
341
+ except Exception as e:
342
+ # Model validation is done at initialization, so this is likely an API error
343
+ raise ProviderAPIError(f"OpenAI API error: {str(e)}")
344
+
345
+ # For streaming with tools, we need to collect the complete response
346
+ collected_content = ""
347
+ collected_tool_calls = {} # Use dict to merge streaming chunks by tool call ID
348
+ final_response = None
349
+
350
+ async for chunk in stream:
351
+ choice = chunk.choices[0] if chunk.choices else None
352
+ if not choice:
353
+ continue
354
+
355
+ delta = choice.delta
356
+ content = getattr(delta, 'content', None) or ""
357
+ collected_content += content
358
+
359
+ # Handle tool calls in streaming - merge incomplete chunks
360
+ if hasattr(delta, 'tool_calls') and delta.tool_calls:
361
+ for tc in delta.tool_calls:
362
+ tc_id = getattr(tc, 'id', None) or getattr(tc, 'index', 0)
363
+
364
+ # Initialize or get existing tool call
365
+ if tc_id not in collected_tool_calls:
366
+ collected_tool_calls[tc_id] = {
367
+ "id": getattr(tc, 'id', None),
368
+ "type": getattr(tc, 'type', 'function'),
369
+ "name": None,
370
+ "arguments": ""
371
+ }
372
+
373
+ # Update with new data from this chunk
374
+ if hasattr(tc, 'function'):
375
+ if hasattr(tc.function, 'name') and tc.function.name:
376
+ collected_tool_calls[tc_id]["name"] = tc.function.name
377
+ if hasattr(tc.function, 'arguments') and tc.function.arguments:
378
+ collected_tool_calls[tc_id]["arguments"] += tc.function.arguments
379
+
380
+ # Create chunk response
381
+ chunk_response = GenerateResponse(
382
+ content=content,
383
+ raw_response=chunk,
384
+ model=chunk.model,
385
+ finish_reason=choice.finish_reason,
386
+ tool_calls=None # Don't include incomplete tool calls in chunks
387
+ )
388
+
389
+ # If this is the final chunk and we have tools, handle tool execution
390
+ if choice.finish_reason and tools and collected_tool_calls:
391
+ # Convert dict to list and filter out incomplete tool calls
392
+ complete_tool_calls = []
393
+ for tc in collected_tool_calls.values():
394
+ if tc["name"] and tc["arguments"] is not None: # Include tool calls with empty args
395
+ complete_tool_calls.append(tc)
396
+
397
+ # Create complete response for tool processing
398
+ complete_response = GenerateResponse(
399
+ content=collected_content,
400
+ raw_response=chunk,
401
+ model=chunk.model,
402
+ finish_reason=choice.finish_reason,
403
+ tool_calls=complete_tool_calls if complete_tool_calls else None
404
+ )
405
+
406
+ # Handle tool execution
407
+ final_response = self._handle_tool_execution(complete_response, tools)
408
+
409
+ # If tools were executed, yield the tool results as final chunk
410
+ if final_response.content != collected_content:
411
+ tool_results_content = final_response.content[len(collected_content):]
412
+ yield GenerateResponse(
413
+ content=tool_results_content,
414
+ raw_response=chunk,
415
+ model=chunk.model,
416
+ finish_reason=choice.finish_reason,
417
+ tool_calls=None
418
+ )
419
+ else:
420
+ # No tools executed but response was processed - yield final response content
421
+ yield GenerateResponse(
422
+ content=final_response.content,
423
+ raw_response=chunk,
424
+ model=chunk.model,
425
+ finish_reason=choice.finish_reason,
426
+ tool_calls=complete_tool_calls if complete_tool_calls else None
427
+ )
428
+ else:
429
+ yield chunk_response
430
+
191
431
  def _format_tools_for_openai(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
192
432
  """Format tools for OpenAI API format"""
193
433
  formatted_tools = []
@@ -391,6 +631,18 @@ class OpenAIProvider(BaseProvider):
391
631
  return False
392
632
  return True
393
633
 
634
+ def unload(self) -> None:
635
+ """Close async client if it was created."""
636
+ if self._async_client is not None:
637
+ import asyncio
638
+ try:
639
+ loop = asyncio.get_running_loop()
640
+ loop.create_task(self._async_client.close())
641
+ except RuntimeError:
642
+ # No running loop, close synchronously
643
+ import asyncio
644
+ asyncio.run(self._async_client.close())
645
+
394
646
  def _validate_model_exists(self):
395
647
  """Preflight check to validate model exists before any generation"""
396
648
  try:
@@ -410,7 +662,7 @@ class OpenAIProvider(BaseProvider):
410
662
  # For other errors (like API failures), handle gracefully
411
663
  error_str = str(e).lower()
412
664
  if 'api_key' in error_str or 'authentication' in error_str:
413
- raise AuthenticationError(f"OpenAI authentication failed: {str(e)}")
665
+ raise AuthenticationError(format_auth_error("openai", str(e)))
414
666
  # For other API errors during preflight, continue (model might work)
415
667
  # This allows for cases where models.list() fails but generation works
416
668
 
@@ -511,9 +763,21 @@ class OpenAIProvider(BaseProvider):
511
763
 
512
764
  @classmethod
513
765
  def list_available_models(cls, **kwargs) -> List[str]:
514
- """List available models from OpenAI API."""
766
+ """
767
+ List available models from OpenAI API.
768
+
769
+ Args:
770
+ **kwargs: Optional parameters including:
771
+ - api_key: OpenAI API key
772
+ - input_capabilities: List of ModelInputCapability enums to filter by input capability
773
+ - output_capabilities: List of ModelOutputCapability enums to filter by output capability
774
+
775
+ Returns:
776
+ List of model names, optionally filtered by capabilities
777
+ """
515
778
  try:
516
779
  import openai
780
+ from .model_capabilities import filter_models_by_capabilities
517
781
 
518
782
  # Get API key from kwargs or environment
519
783
  api_key = kwargs.get('api_key') or os.getenv("OPENAI_API_KEY")
@@ -542,7 +806,21 @@ class OpenAIProvider(BaseProvider):
542
806
  ]):
543
807
  chat_models.append(model_id)
544
808
 
545
- return sorted(chat_models, reverse=True) # Latest models first
809
+ chat_models = sorted(chat_models, reverse=True) # Latest models first
810
+
811
+ # Apply new capability filtering if provided
812
+ input_capabilities = kwargs.get('input_capabilities')
813
+ output_capabilities = kwargs.get('output_capabilities')
814
+
815
+ if input_capabilities or output_capabilities:
816
+ chat_models = filter_models_by_capabilities(
817
+ chat_models,
818
+ input_capabilities=input_capabilities,
819
+ output_capabilities=output_capabilities
820
+ )
821
+
822
+
823
+ return chat_models
546
824
 
547
825
  except Exception:
548
826
  return []
@@ -202,10 +202,14 @@ class ProviderRegistry:
202
202
 
203
203
  Args:
204
204
  provider_name: Name of the provider
205
- **kwargs: Provider-specific parameters (e.g., api_key, base_url)
205
+ **kwargs: Provider-specific parameters including:
206
+ - api_key: API key for authentication (if required)
207
+ - base_url: Base URL for API endpoint (if applicable)
208
+ - input_capabilities: List of ModelInputCapability enums to filter by input capability
209
+ - output_capabilities: List of ModelOutputCapability enums to filter by output capability
206
210
 
207
211
  Returns:
208
- List of available model names
212
+ List of available model names, optionally filtered by capabilities
209
213
  """
210
214
  try:
211
215
  provider_class = self.get_provider_class(provider_name)
@@ -285,13 +289,64 @@ class ProviderRegistry:
285
289
  for provider_name in self.list_provider_names()
286
290
  ]
287
291
 
288
- def get_providers_with_models(self) -> List[Dict[str, Any]]:
289
- """Get only providers that have available models."""
290
- all_providers = self.get_all_providers_status()
291
- return [
292
- provider for provider in all_providers
293
- if provider.get("status") == "available" and provider.get("model_count", 0) > 0
294
- ]
292
+ def get_providers_with_models(self, include_models: bool = True) -> List[Dict[str, Any]]:
293
+ """
294
+ Get only providers that have available models.
295
+
296
+ Args:
297
+ include_models: If True, include actual model lists (slower).
298
+ If False, return metadata only (much faster). Default: True.
299
+ """
300
+ if include_models:
301
+ # Original behavior - get full status including model lists
302
+ all_providers = self.get_all_providers_status()
303
+ return [
304
+ provider for provider in all_providers
305
+ if provider.get("status") == "available" and provider.get("model_count", 0) > 0
306
+ ]
307
+ else:
308
+ # Fast path - get all provider metadata without model enumeration
309
+ # Note: We return all providers since we can't quickly determine which have models
310
+ return self.get_providers_metadata_only()
311
+
312
+ def get_providers_metadata_only(self) -> List[Dict[str, Any]]:
313
+ """
314
+ Get provider metadata without enumerating models (fast path).
315
+
316
+ This method returns provider information without making API calls
317
+ or scanning for models, making it extremely fast for UI discovery.
318
+ """
319
+ providers_metadata = []
320
+
321
+ for provider_name in self.list_provider_names():
322
+ provider_info = self.get_provider_info(provider_name)
323
+ if not provider_info:
324
+ continue
325
+
326
+ # Basic availability check without model enumeration
327
+ try:
328
+ provider_class = self.get_provider_class(provider_name)
329
+ status = "available" # Assume available if class can be imported
330
+ except Exception:
331
+ status = "error"
332
+
333
+ metadata = {
334
+ "name": provider_info.name,
335
+ "display_name": provider_info.display_name,
336
+ "type": provider_info.provider_type,
337
+ "model_count": "unknown", # Don't enumerate models
338
+ "status": status,
339
+ "description": provider_info.description,
340
+ "local_provider": provider_info.local_provider,
341
+ "authentication_required": provider_info.authentication_required,
342
+ "supported_features": provider_info.supported_features,
343
+ "installation_extras": provider_info.installation_extras,
344
+ "models": [] # Empty list for fast response
345
+ }
346
+
347
+ providers_metadata.append(metadata)
348
+
349
+ return providers_metadata
295
350
 
296
351
  def create_provider_instance(self, provider_name: str, model: Optional[str] = None, **kwargs):
297
352
  """
@@ -348,7 +403,7 @@ def is_provider_available(provider_name: str) -> bool:
348
403
  return get_provider_registry().is_provider_available(provider_name)
349
404
 
350
405
 
351
- def get_all_providers_with_models() -> List[Dict[str, Any]]:
406
+ def get_all_providers_with_models(include_models: bool = True) -> List[Dict[str, Any]]:
352
407
  """
353
408
  Get comprehensive information about all providers with available models.
354
409
 
@@ -356,14 +411,18 @@ def get_all_providers_with_models() -> List[Dict[str, Any]]:
356
411
  for provider discovery and information. It replaces the manual provider
357
412
  lists in factory.py and server/app.py.
358
413
 
414
+ Args:
415
+ include_models: If True, include actual model lists (slower).
416
+ If False, return metadata only (much faster). Default: True.
417
+
359
418
  Returns:
360
419
  List of provider dictionaries with comprehensive metadata including:
361
420
  - name, display_name, type, description
362
421
  - model_count, status, supported_features
363
422
  - local_provider, authentication_required
364
- - installation_extras, sample models
423
+ - installation_extras, sample models (if include_models=True)
365
424
  """
366
- return get_provider_registry().get_providers_with_models()
425
+ return get_provider_registry().get_providers_with_models(include_models=include_models)
367
426
 
368
427
 
369
428
  def get_all_providers_status() -> List[Dict[str, Any]]:
@@ -386,5 +445,18 @@ def create_provider(provider_name: str, model: Optional[str] = None, **kwargs):
386
445
 
387
446
 
388
447
  def get_available_models_for_provider(provider_name: str, **kwargs) -> List[str]:
389
- """Get available models for a specific provider."""
448
+ """
449
+ Get available models for a specific provider.
450
+
451
+ Args:
452
+ provider_name: Name of the provider
453
+ **kwargs: Provider-specific parameters including:
454
+ - api_key: API key for authentication (if required)
455
+ - base_url: Base URL for API endpoint (if applicable)
456
+ - input_capabilities: List of ModelInputCapability enums to filter by input capability
457
+ - output_capabilities: List of ModelOutputCapability enums to filter by output capability
458
+
459
+ Returns:
460
+ List of available model names, optionally filtered by capabilities
461
+ """
390
462
  return get_provider_registry().get_available_models(provider_name, **kwargs)
@@ -7,15 +7,15 @@ while maintaining real-time streaming performance, with proper tag rewriting sup
7
7
 
8
8
  import json
9
9
  import re
10
- import logging
11
10
  import uuid
12
11
  from typing import List, Dict, Any, Optional, Iterator, Tuple
13
12
  from enum import Enum
14
13
 
15
14
  from ..core.types import GenerateResponse
16
15
  from ..tools.core import ToolCall
16
+ from ..utils.structured_logging import get_logger
17
17
 
18
- logger = logging.getLogger(__name__)
18
+ logger = get_logger(__name__)
19
19
 
20
20
 
21
21
  class ToolDetectionState(Enum):