abstractcore 2.6.8__py3-none-any.whl → 2.9.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.
- abstractcore/apps/summarizer.py +69 -27
- abstractcore/architectures/detection.py +190 -25
- abstractcore/assets/architecture_formats.json +129 -6
- abstractcore/assets/model_capabilities.json +789 -136
- abstractcore/config/main.py +2 -2
- abstractcore/config/manager.py +3 -1
- abstractcore/events/__init__.py +7 -1
- abstractcore/mcp/__init__.py +30 -0
- abstractcore/mcp/client.py +213 -0
- abstractcore/mcp/factory.py +64 -0
- abstractcore/mcp/naming.py +28 -0
- abstractcore/mcp/stdio_client.py +336 -0
- abstractcore/mcp/tool_source.py +164 -0
- abstractcore/processing/basic_deepsearch.py +1 -1
- abstractcore/processing/basic_summarizer.py +300 -83
- abstractcore/providers/anthropic_provider.py +91 -10
- abstractcore/providers/base.py +537 -16
- abstractcore/providers/huggingface_provider.py +17 -8
- abstractcore/providers/lmstudio_provider.py +170 -25
- abstractcore/providers/mlx_provider.py +13 -10
- abstractcore/providers/ollama_provider.py +42 -26
- abstractcore/providers/openai_compatible_provider.py +87 -22
- abstractcore/providers/openai_provider.py +12 -9
- abstractcore/providers/streaming.py +201 -39
- abstractcore/providers/vllm_provider.py +78 -21
- abstractcore/server/app.py +65 -28
- abstractcore/structured/retry.py +20 -7
- abstractcore/tools/__init__.py +5 -4
- abstractcore/tools/abstractignore.py +166 -0
- abstractcore/tools/arg_canonicalizer.py +61 -0
- abstractcore/tools/common_tools.py +2311 -772
- abstractcore/tools/core.py +109 -13
- abstractcore/tools/handler.py +17 -3
- abstractcore/tools/parser.py +798 -155
- abstractcore/tools/registry.py +107 -2
- abstractcore/tools/syntax_rewriter.py +68 -6
- abstractcore/tools/tag_rewriter.py +186 -1
- abstractcore/utils/jsonish.py +111 -0
- abstractcore/utils/version.py +1 -1
- {abstractcore-2.6.8.dist-info → abstractcore-2.9.0.dist-info}/METADATA +11 -2
- {abstractcore-2.6.8.dist-info → abstractcore-2.9.0.dist-info}/RECORD +45 -36
- {abstractcore-2.6.8.dist-info → abstractcore-2.9.0.dist-info}/WHEEL +0 -0
- {abstractcore-2.6.8.dist-info → abstractcore-2.9.0.dist-info}/entry_points.txt +0 -0
- {abstractcore-2.6.8.dist-info → abstractcore-2.9.0.dist-info}/licenses/LICENSE +0 -0
- {abstractcore-2.6.8.dist-info → abstractcore-2.9.0.dist-info}/top_level.txt +0 -0
abstractcore/tools/core.py
CHANGED
|
@@ -7,6 +7,43 @@ from dataclasses import dataclass, field
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
_MAX_TOOL_DESCRIPTION_CHARS = 200
|
|
11
|
+
_MAX_TOOL_WHEN_TO_USE_CHARS = 240
|
|
12
|
+
_MAX_TOOL_EXAMPLES = 3
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _first_non_empty_line(text: Optional[str]) -> str:
|
|
16
|
+
if not text:
|
|
17
|
+
return ""
|
|
18
|
+
for line in str(text).splitlines():
|
|
19
|
+
stripped = line.strip()
|
|
20
|
+
if stripped:
|
|
21
|
+
return stripped
|
|
22
|
+
return ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _normalize_one_line(text: Optional[str]) -> str:
|
|
26
|
+
"""Collapse whitespace (including newlines) into a single, prompt-friendly line."""
|
|
27
|
+
return " ".join(str(text or "").split()).strip()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _validate_tool_metadata(*, name: str, description: str, when_to_use: Optional[str], examples: List[Dict[str, Any]]) -> None:
|
|
31
|
+
if not description:
|
|
32
|
+
raise ValueError(f"Tool '{name}': description must be a non-empty string")
|
|
33
|
+
if len(description) > _MAX_TOOL_DESCRIPTION_CHARS:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
f"Tool '{name}': description is too long ({len(description)} chars; max {_MAX_TOOL_DESCRIPTION_CHARS}). "
|
|
36
|
+
"Keep it to a single short sentence; put detailed guidance in `when_to_use` or docs."
|
|
37
|
+
)
|
|
38
|
+
if when_to_use is not None and len(when_to_use) > _MAX_TOOL_WHEN_TO_USE_CHARS:
|
|
39
|
+
raise ValueError(
|
|
40
|
+
f"Tool '{name}': when_to_use is too long ({len(when_to_use)} chars; max {_MAX_TOOL_WHEN_TO_USE_CHARS}). "
|
|
41
|
+
"Keep it to a single short sentence."
|
|
42
|
+
)
|
|
43
|
+
if len(examples) > _MAX_TOOL_EXAMPLES:
|
|
44
|
+
raise ValueError(f"Tool '{name}': too many examples ({len(examples)}; max {_MAX_TOOL_EXAMPLES}).")
|
|
45
|
+
|
|
46
|
+
|
|
10
47
|
@dataclass
|
|
11
48
|
class ToolDefinition:
|
|
12
49
|
"""Definition of a tool that can be called by LLM"""
|
|
@@ -20,6 +57,20 @@ class ToolDefinition:
|
|
|
20
57
|
when_to_use: Optional[str] = None
|
|
21
58
|
examples: List[Dict[str, Any]] = field(default_factory=list)
|
|
22
59
|
|
|
60
|
+
def __post_init__(self) -> None:
|
|
61
|
+
# Normalize to a single line for prompt-friendly catalogs.
|
|
62
|
+
self.name = str(self.name or "").strip()
|
|
63
|
+
self.description = _normalize_one_line(self.description)
|
|
64
|
+
self.when_to_use = _normalize_one_line(self.when_to_use) if self.when_to_use else None
|
|
65
|
+
self.tags = list(self.tags) if isinstance(self.tags, list) else []
|
|
66
|
+
self.examples = list(self.examples) if isinstance(self.examples, list) else []
|
|
67
|
+
_validate_tool_metadata(
|
|
68
|
+
name=self.name,
|
|
69
|
+
description=self.description,
|
|
70
|
+
when_to_use=self.when_to_use,
|
|
71
|
+
examples=self.examples,
|
|
72
|
+
)
|
|
73
|
+
|
|
23
74
|
@classmethod
|
|
24
75
|
def from_function(cls, func: Callable) -> 'ToolDefinition':
|
|
25
76
|
"""Create tool definition from a function"""
|
|
@@ -27,7 +78,11 @@ class ToolDefinition:
|
|
|
27
78
|
|
|
28
79
|
# Extract function name and docstring
|
|
29
80
|
name = func.__name__
|
|
30
|
-
description
|
|
81
|
+
# Tool `description` must be short; use the first docstring line (not the whole docstring).
|
|
82
|
+
description = _first_non_empty_line(func.__doc__) or "No description provided"
|
|
83
|
+
description = _normalize_one_line(description)
|
|
84
|
+
if description != "No description provided":
|
|
85
|
+
_validate_tool_metadata(name=name, description=description, when_to_use=None, examples=[])
|
|
31
86
|
|
|
32
87
|
# Extract parameters from function signature
|
|
33
88
|
sig = inspect.signature(func)
|
|
@@ -59,17 +114,35 @@ class ToolDefinition:
|
|
|
59
114
|
|
|
60
115
|
def to_dict(self) -> Dict[str, Any]:
|
|
61
116
|
"""Convert to dictionary format"""
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
117
|
+
# Preserve a human/LLM-friendly field ordering (also improves UX when dumped as JSON).
|
|
118
|
+
result: Dict[str, Any] = {"name": self.name, "description": self.description}
|
|
119
|
+
|
|
120
|
+
if self.when_to_use:
|
|
121
|
+
result["when_to_use"] = self.when_to_use
|
|
122
|
+
|
|
123
|
+
result["parameters"] = self.parameters
|
|
124
|
+
|
|
125
|
+
# Expose required args explicitly for hosts that render ToolDefinitions directly
|
|
126
|
+
# (e.g. AbstractRuntime prompt payloads / debug traces). This is additive metadata:
|
|
127
|
+
# providers still rely on JSON Schema `required` when building native tool payloads.
|
|
128
|
+
try:
|
|
129
|
+
required: List[str] = []
|
|
130
|
+
for param_name, meta in (self.parameters or {}).items():
|
|
131
|
+
if not isinstance(param_name, str) or not param_name.strip():
|
|
132
|
+
continue
|
|
133
|
+
# Convention in this repo: absence of `default` means "required".
|
|
134
|
+
if not isinstance(meta, dict) or "default" not in meta:
|
|
135
|
+
required.append(param_name)
|
|
136
|
+
required.sort()
|
|
137
|
+
if required:
|
|
138
|
+
result["required_args"] = required
|
|
139
|
+
except Exception:
|
|
140
|
+
# Best-effort only; never break tool serialization.
|
|
141
|
+
pass
|
|
67
142
|
|
|
68
143
|
# Include enhanced metadata if available
|
|
69
144
|
if self.tags:
|
|
70
145
|
result["tags"] = self.tags
|
|
71
|
-
if self.when_to_use:
|
|
72
|
-
result["when_to_use"] = self.when_to_use
|
|
73
146
|
if self.examples:
|
|
74
147
|
result["examples"] = self.examples
|
|
75
148
|
|
|
@@ -112,7 +185,8 @@ def tool(
|
|
|
112
185
|
description: Optional[str] = None,
|
|
113
186
|
tags: Optional[List[str]] = None,
|
|
114
187
|
when_to_use: Optional[str] = None,
|
|
115
|
-
examples: Optional[List[Dict[str, Any]]] = None
|
|
188
|
+
examples: Optional[List[Dict[str, Any]]] = None,
|
|
189
|
+
hide_args: Optional[List[str]] = None,
|
|
116
190
|
):
|
|
117
191
|
"""
|
|
118
192
|
Enhanced decorator to convert a function into a tool with rich metadata.
|
|
@@ -144,7 +218,8 @@ def tool(
|
|
|
144
218
|
"""
|
|
145
219
|
def decorator(f):
|
|
146
220
|
tool_name = name or f.__name__
|
|
147
|
-
tool_description = description or f.__doc__ or f"Execute {tool_name}"
|
|
221
|
+
tool_description = description or _first_non_empty_line(f.__doc__) or f"Execute {tool_name}"
|
|
222
|
+
tool_description = _normalize_one_line(tool_description)
|
|
148
223
|
|
|
149
224
|
# Create tool definition from function and customize
|
|
150
225
|
tool_def = ToolDefinition.from_function(f)
|
|
@@ -153,8 +228,29 @@ def tool(
|
|
|
153
228
|
|
|
154
229
|
# Add enhanced metadata
|
|
155
230
|
tool_def.tags = tags or []
|
|
156
|
-
tool_def.when_to_use = when_to_use
|
|
157
|
-
tool_def.examples = examples
|
|
231
|
+
tool_def.when_to_use = _normalize_one_line(when_to_use) if when_to_use else None
|
|
232
|
+
tool_def.examples = list(examples) if isinstance(examples, list) else []
|
|
233
|
+
|
|
234
|
+
# Optionally hide parameters from the exported schema (LLM-facing), while
|
|
235
|
+
# keeping them accepted by the underlying Python callable for backwards
|
|
236
|
+
# compatibility (e.g. legacy callers still passing deprecated kwargs).
|
|
237
|
+
hidden = [str(a).strip() for a in (hide_args or []) if str(a).strip()]
|
|
238
|
+
if hidden:
|
|
239
|
+
for arg in hidden:
|
|
240
|
+
if arg not in tool_def.parameters:
|
|
241
|
+
continue
|
|
242
|
+
# Avoid hiding required args (no default), which would make the
|
|
243
|
+
# tool schema incomplete for tool-call generation.
|
|
244
|
+
if "default" not in tool_def.parameters.get(arg, {}):
|
|
245
|
+
raise ValueError(f"Tool '{tool_def.name}': cannot hide required arg '{arg}'")
|
|
246
|
+
tool_def.parameters.pop(arg, None)
|
|
247
|
+
|
|
248
|
+
_validate_tool_metadata(
|
|
249
|
+
name=tool_def.name,
|
|
250
|
+
description=tool_def.description,
|
|
251
|
+
when_to_use=tool_def.when_to_use,
|
|
252
|
+
examples=tool_def.examples,
|
|
253
|
+
)
|
|
158
254
|
|
|
159
255
|
# Attach tool definition to function for easy access
|
|
160
256
|
f._tool_definition = tool_def
|
|
@@ -167,4 +263,4 @@ def tool(
|
|
|
167
263
|
return decorator
|
|
168
264
|
else:
|
|
169
265
|
# Called without arguments: @tool
|
|
170
|
-
return decorator(func)
|
|
266
|
+
return decorator(func)
|
abstractcore/tools/handler.py
CHANGED
|
@@ -51,7 +51,10 @@ class UniversalToolHandler:
|
|
|
51
51
|
|
|
52
52
|
def format_tools_prompt(
|
|
53
53
|
self,
|
|
54
|
-
tools: List[Union[ToolDefinition, Callable, Dict[str, Any]]]
|
|
54
|
+
tools: List[Union[ToolDefinition, Callable, Dict[str, Any]]],
|
|
55
|
+
*,
|
|
56
|
+
include_tool_list: bool = True,
|
|
57
|
+
include_examples: bool = True,
|
|
55
58
|
) -> str:
|
|
56
59
|
"""
|
|
57
60
|
Format tools into a system prompt for prompted models.
|
|
@@ -71,7 +74,12 @@ class UniversalToolHandler:
|
|
|
71
74
|
return ""
|
|
72
75
|
|
|
73
76
|
# Use architecture-specific formatting
|
|
74
|
-
return format_tool_prompt(
|
|
77
|
+
return format_tool_prompt(
|
|
78
|
+
tool_defs,
|
|
79
|
+
self.model_name,
|
|
80
|
+
include_tool_list=include_tool_list,
|
|
81
|
+
include_examples=include_examples,
|
|
82
|
+
)
|
|
75
83
|
|
|
76
84
|
def prepare_tools_for_native(
|
|
77
85
|
self,
|
|
@@ -243,6 +251,9 @@ class UniversalToolHandler:
|
|
|
243
251
|
logger.warning(f"Failed to parse tool arguments: {tool_call.arguments}")
|
|
244
252
|
tool_call.arguments = {}
|
|
245
253
|
|
|
254
|
+
from .arg_canonicalizer import canonicalize_tool_arguments
|
|
255
|
+
|
|
256
|
+
tool_call.arguments = canonicalize_tool_arguments(tool_call.name, tool_call.arguments)
|
|
246
257
|
tool_calls.append(tool_call)
|
|
247
258
|
|
|
248
259
|
return ToolCallResponse(
|
|
@@ -271,6 +282,9 @@ class UniversalToolHandler:
|
|
|
271
282
|
logger.warning(f"Failed to parse tool arguments: {tool_call.arguments}")
|
|
272
283
|
tool_call.arguments = {}
|
|
273
284
|
|
|
285
|
+
from .arg_canonicalizer import canonicalize_tool_arguments
|
|
286
|
+
|
|
287
|
+
tool_call.arguments = canonicalize_tool_arguments(tool_call.name, tool_call.arguments)
|
|
274
288
|
tool_calls.append(tool_call)
|
|
275
289
|
|
|
276
290
|
return ToolCallResponse(
|
|
@@ -305,4 +319,4 @@ def create_handler(model_name: str) -> UniversalToolHandler:
|
|
|
305
319
|
Returns:
|
|
306
320
|
Configured UniversalToolHandler
|
|
307
321
|
"""
|
|
308
|
-
return UniversalToolHandler(model_name)
|
|
322
|
+
return UniversalToolHandler(model_name)
|