abstractcore 2.5.3__py3-none-any.whl → 2.6.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 -1
- abstractcore/architectures/detection.py +2 -2
- abstractcore/config/__init__.py +24 -1
- abstractcore/config/manager.py +47 -0
- abstractcore/core/retry.py +2 -2
- abstractcore/core/session.py +132 -1
- abstractcore/download.py +253 -0
- abstractcore/embeddings/manager.py +2 -2
- abstractcore/events/__init__.py +112 -1
- abstractcore/exceptions/__init__.py +49 -2
- abstractcore/media/processors/office_processor.py +2 -2
- abstractcore/media/utils/image_scaler.py +2 -2
- abstractcore/media/vision_fallback.py +2 -2
- abstractcore/providers/anthropic_provider.py +200 -6
- abstractcore/providers/base.py +100 -5
- abstractcore/providers/lmstudio_provider.py +254 -4
- abstractcore/providers/ollama_provider.py +253 -4
- abstractcore/providers/openai_provider.py +258 -6
- abstractcore/providers/registry.py +9 -1
- abstractcore/providers/streaming.py +2 -2
- 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/self_fixes.py +2 -2
- abstractcore/utils/version.py +1 -1
- {abstractcore-2.5.3.dist-info → abstractcore-2.6.2.dist-info}/METADATA +162 -4
- {abstractcore-2.5.3.dist-info → abstractcore-2.6.2.dist-info}/RECORD +34 -33
- {abstractcore-2.5.3.dist-info → abstractcore-2.6.2.dist-info}/WHEEL +0 -0
- {abstractcore-2.5.3.dist-info → abstractcore-2.6.2.dist-info}/entry_points.txt +0 -0
- {abstractcore-2.5.3.dist-info → abstractcore-2.6.2.dist-info}/licenses/LICENSE +0 -0
- {abstractcore-2.5.3.dist-info → abstractcore-2.6.2.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,
|
|
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
|
-
#
|
|
46
|
-
self.
|
|
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(
|
|
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
|
|
|
@@ -354,6 +354,8 @@ class ProviderRegistry:
|
|
|
354
354
|
|
|
355
355
|
This is used by the factory to create provider instances.
|
|
356
356
|
"""
|
|
357
|
+
from ..config import get_provider_config
|
|
358
|
+
|
|
357
359
|
provider_info = self.get_provider_info(provider_name)
|
|
358
360
|
if not provider_info:
|
|
359
361
|
available_providers = ", ".join(self.list_provider_names())
|
|
@@ -362,8 +364,14 @@ class ProviderRegistry:
|
|
|
362
364
|
provider_class = self.get_provider_class(provider_name)
|
|
363
365
|
model = model or provider_info.default_model
|
|
364
366
|
|
|
367
|
+
# Get runtime config for this provider
|
|
368
|
+
runtime_config = get_provider_config(provider_name)
|
|
369
|
+
|
|
370
|
+
# Merge: runtime_config < kwargs (user kwargs take precedence)
|
|
371
|
+
merged_kwargs = {**runtime_config, **kwargs}
|
|
372
|
+
|
|
365
373
|
try:
|
|
366
|
-
return provider_class(model=model, **
|
|
374
|
+
return provider_class(model=model, **merged_kwargs)
|
|
367
375
|
except ImportError as e:
|
|
368
376
|
# Re-raise import errors with helpful message
|
|
369
377
|
if provider_info.installation_extras:
|
|
@@ -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 =
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class ToolDetectionState(Enum):
|
|
@@ -12,7 +12,6 @@ import subprocess
|
|
|
12
12
|
import requests
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import Optional, Dict, Any, Union
|
|
15
|
-
import logging
|
|
16
15
|
import platform
|
|
17
16
|
import re
|
|
18
17
|
import time
|
|
@@ -43,8 +42,9 @@ except ImportError:
|
|
|
43
42
|
|
|
44
43
|
# Import our enhanced tool decorator
|
|
45
44
|
from abstractcore.tools.core import tool
|
|
45
|
+
from abstractcore.utils.structured_logging import get_logger
|
|
46
46
|
|
|
47
|
-
logger =
|
|
47
|
+
logger = get_logger(__name__)
|
|
48
48
|
|
|
49
49
|
# File Operations
|
|
50
50
|
@tool(
|
abstractcore/tools/handler.py
CHANGED
|
@@ -6,14 +6,14 @@ across all models, whether they have native tool APIs or require prompting.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
|
-
import logging
|
|
10
9
|
from typing import List, Dict, Any, Optional, Union, Callable
|
|
11
10
|
|
|
12
11
|
from ..architectures import detect_architecture, get_model_capabilities, get_architecture_format
|
|
13
12
|
from .core import ToolDefinition, ToolCall, ToolCallResponse, ToolResult
|
|
14
13
|
from .parser import detect_tool_calls, parse_tool_calls, format_tool_prompt
|
|
14
|
+
from ..utils.structured_logging import get_logger
|
|
15
15
|
|
|
16
|
-
logger =
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class UniversalToolHandler:
|
abstractcore/tools/parser.py
CHANGED
|
@@ -7,14 +7,14 @@ responses based on their architecture.
|
|
|
7
7
|
|
|
8
8
|
import re
|
|
9
9
|
import json
|
|
10
|
-
import logging
|
|
11
10
|
from typing import List, Optional, Dict, Any
|
|
12
11
|
from enum import Enum
|
|
13
12
|
|
|
14
13
|
from .core import ToolCall, ToolDefinition
|
|
15
14
|
from ..architectures import detect_architecture, get_architecture_format
|
|
15
|
+
from ..utils.structured_logging import get_logger
|
|
16
16
|
|
|
17
|
-
logger =
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class ToolFormat(Enum):
|
abstractcore/tools/registry.py
CHANGED
|
@@ -5,15 +5,15 @@ This module provides a centralized registry for managing available tools
|
|
|
5
5
|
and executing them safely.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import logging
|
|
9
8
|
import time
|
|
10
9
|
from typing import Dict, List, Any, Callable, Optional, Union
|
|
11
10
|
from functools import wraps
|
|
12
11
|
|
|
13
12
|
from .core import ToolDefinition, ToolCall, ToolResult
|
|
14
13
|
from ..events import EventType, emit_global, create_tool_event
|
|
14
|
+
from ..utils.structured_logging import get_logger
|
|
15
15
|
|
|
16
|
-
logger =
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class ToolRegistry:
|
|
@@ -8,15 +8,15 @@ Supports multiple target formats including OpenAI, Codex, and custom agent forma
|
|
|
8
8
|
import re
|
|
9
9
|
import json
|
|
10
10
|
import uuid
|
|
11
|
-
import logging
|
|
12
11
|
from typing import List, Dict, Any, Optional, Union
|
|
13
12
|
from dataclasses import dataclass
|
|
14
13
|
from enum import Enum
|
|
15
14
|
|
|
16
15
|
from .core import ToolCall
|
|
17
16
|
from .parser import parse_tool_calls
|
|
17
|
+
from ..utils.structured_logging import get_logger
|
|
18
18
|
|
|
19
|
-
logger =
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class SyntaxFormat(Enum):
|
|
@@ -9,6 +9,9 @@ import re
|
|
|
9
9
|
import json
|
|
10
10
|
from typing import Dict, Any, Optional, Tuple, List
|
|
11
11
|
from dataclasses import dataclass
|
|
12
|
+
from ..utils.structured_logging import get_logger
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
@dataclass
|
|
@@ -161,9 +164,6 @@ class ToolCallTagRewriter:
|
|
|
161
164
|
Returns:
|
|
162
165
|
Text with rewritten tool call tags
|
|
163
166
|
"""
|
|
164
|
-
import logging
|
|
165
|
-
logger = logging.getLogger(__name__)
|
|
166
|
-
|
|
167
167
|
logger.debug(f"rewrite_text called with text: {text[:100] if text else None}")
|
|
168
168
|
logger.debug(f"Target output tags: start='{self._output_start_tag}', end='{self._output_end_tag}'")
|
|
169
169
|
|
abstractcore/utils/self_fixes.py
CHANGED
|
@@ -8,9 +8,9 @@ before giving up on parsing.
|
|
|
8
8
|
import json
|
|
9
9
|
import re
|
|
10
10
|
from typing import Optional
|
|
11
|
-
import
|
|
11
|
+
from .structured_logging import get_logger
|
|
12
12
|
|
|
13
|
-
logger =
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def fix_json(text: str) -> Optional[str]:
|
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.2"
|