abstractcore 2.6.9__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.
Files changed (46) hide show
  1. abstractcore/apps/summarizer.py +69 -27
  2. abstractcore/architectures/detection.py +190 -25
  3. abstractcore/assets/architecture_formats.json +129 -6
  4. abstractcore/assets/model_capabilities.json +803 -141
  5. abstractcore/config/main.py +2 -2
  6. abstractcore/config/manager.py +3 -1
  7. abstractcore/events/__init__.py +7 -1
  8. abstractcore/mcp/__init__.py +30 -0
  9. abstractcore/mcp/client.py +213 -0
  10. abstractcore/mcp/factory.py +64 -0
  11. abstractcore/mcp/naming.py +28 -0
  12. abstractcore/mcp/stdio_client.py +336 -0
  13. abstractcore/mcp/tool_source.py +164 -0
  14. abstractcore/processing/__init__.py +2 -2
  15. abstractcore/processing/basic_deepsearch.py +1 -1
  16. abstractcore/processing/basic_summarizer.py +379 -93
  17. abstractcore/providers/anthropic_provider.py +91 -10
  18. abstractcore/providers/base.py +540 -16
  19. abstractcore/providers/huggingface_provider.py +17 -8
  20. abstractcore/providers/lmstudio_provider.py +170 -25
  21. abstractcore/providers/mlx_provider.py +13 -10
  22. abstractcore/providers/ollama_provider.py +42 -26
  23. abstractcore/providers/openai_compatible_provider.py +87 -22
  24. abstractcore/providers/openai_provider.py +12 -9
  25. abstractcore/providers/streaming.py +201 -39
  26. abstractcore/providers/vllm_provider.py +78 -21
  27. abstractcore/server/app.py +116 -30
  28. abstractcore/structured/retry.py +20 -7
  29. abstractcore/tools/__init__.py +46 -24
  30. abstractcore/tools/abstractignore.py +166 -0
  31. abstractcore/tools/arg_canonicalizer.py +61 -0
  32. abstractcore/tools/common_tools.py +2443 -742
  33. abstractcore/tools/core.py +109 -13
  34. abstractcore/tools/handler.py +17 -3
  35. abstractcore/tools/parser.py +894 -159
  36. abstractcore/tools/registry.py +122 -18
  37. abstractcore/tools/syntax_rewriter.py +68 -6
  38. abstractcore/tools/tag_rewriter.py +186 -1
  39. abstractcore/utils/jsonish.py +111 -0
  40. abstractcore/utils/version.py +1 -1
  41. {abstractcore-2.6.9.dist-info → abstractcore-2.9.0.dist-info}/METADATA +55 -2
  42. {abstractcore-2.6.9.dist-info → abstractcore-2.9.0.dist-info}/RECORD +46 -37
  43. {abstractcore-2.6.9.dist-info → abstractcore-2.9.0.dist-info}/WHEEL +0 -0
  44. {abstractcore-2.6.9.dist-info → abstractcore-2.9.0.dist-info}/entry_points.txt +0 -0
  45. {abstractcore-2.6.9.dist-info → abstractcore-2.9.0.dist-info}/licenses/LICENSE +0 -0
  46. {abstractcore-2.6.9.dist-info → abstractcore-2.9.0.dist-info}/top_level.txt +0 -0
@@ -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 = func.__doc__ or "No description provided"
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
- result = {
63
- "name": self.name,
64
- "description": self.description,
65
- "parameters": self.parameters
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 or []
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)
@@ -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(tool_defs, self.model_name)
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)