abstractcore 2.6.9__py3-none-any.whl → 2.9.1__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.1.dist-info}/METADATA +56 -2
  42. {abstractcore-2.6.9.dist-info → abstractcore-2.9.1.dist-info}/RECORD +46 -37
  43. {abstractcore-2.6.9.dist-info → abstractcore-2.9.1.dist-info}/WHEEL +0 -0
  44. {abstractcore-2.6.9.dist-info → abstractcore-2.9.1.dist-info}/entry_points.txt +0 -0
  45. {abstractcore-2.6.9.dist-info → abstractcore-2.9.1.dist-info}/licenses/LICENSE +0 -0
  46. {abstractcore-2.6.9.dist-info → abstractcore-2.9.1.dist-info}/top_level.txt +0 -0
@@ -100,6 +100,30 @@ class AnthropicProvider(BaseProvider):
100
100
  "role": "assistant",
101
101
  "content": msg["content"]
102
102
  })
103
+ elif role == "tool":
104
+ # Anthropic Messages API represents tool outputs as `tool_result`
105
+ # content blocks inside a USER message (there is no `role="tool"`).
106
+ meta = msg.get("metadata") if isinstance(msg.get("metadata"), dict) else {}
107
+ tool_use_id = meta.get("call_id") or meta.get("tool_use_id") or meta.get("id")
108
+ tool_text = msg.get("content", "")
109
+ tool_text = "" if tool_text is None else str(tool_text)
110
+
111
+ if isinstance(tool_use_id, str) and tool_use_id.strip():
112
+ api_messages.append(
113
+ {
114
+ "role": "user",
115
+ "content": [
116
+ {
117
+ "type": "tool_result",
118
+ "tool_use_id": tool_use_id.strip(),
119
+ "content": tool_text,
120
+ }
121
+ ],
122
+ }
123
+ )
124
+ else:
125
+ # Fallback: preserve as plain user text when no tool_use_id is available.
126
+ api_messages.append({"role": "user", "content": tool_text})
103
127
  else:
104
128
  api_messages.append({
105
129
  "role": "user",
@@ -194,7 +218,9 @@ class AnthropicProvider(BaseProvider):
194
218
  call_params["tool_choice"] = {"type": kwargs.get("tool_choice", "auto")}
195
219
  else:
196
220
  # Add tools as system prompt for prompted models
197
- tool_prompt = self.tool_handler.format_tools_prompt(tools)
221
+ system_text = call_params.get("system") if isinstance(call_params.get("system"), str) else ""
222
+ include_tool_list = "## Tools (session)" not in system_text
223
+ tool_prompt = self.tool_handler.format_tools_prompt(tools, include_tool_list=include_tool_list)
198
224
  if call_params.get("system"):
199
225
  call_params["system"] += f"\n\n{tool_prompt}"
200
226
  else:
@@ -213,6 +239,8 @@ class AnthropicProvider(BaseProvider):
213
239
  formatted = self._format_response(response)
214
240
  # Add generation time to response
215
241
  formatted.gen_time = gen_time
242
+ formatted.metadata = dict(formatted.metadata or {})
243
+ formatted.metadata["_provider_request"] = {"call_params": call_params}
216
244
 
217
245
  # Handle tool execution for Anthropic responses
218
246
  if tools and (formatted.has_tool_calls() or
@@ -232,7 +260,7 @@ class AnthropicProvider(BaseProvider):
232
260
  error_message = format_model_error("Anthropic", self.model, available_models)
233
261
  raise ModelNotFoundError(error_message)
234
262
  else:
235
- raise ProviderAPIError(f"Anthropic API error: {str(e)}")
263
+ raise
236
264
 
237
265
  async def _agenerate_internal(self,
238
266
  prompt: str,
@@ -260,6 +288,30 @@ class AnthropicProvider(BaseProvider):
260
288
  "role": "assistant",
261
289
  "content": msg["content"]
262
290
  })
291
+ elif role == "tool":
292
+ # Anthropic Messages API represents tool outputs as `tool_result`
293
+ # content blocks inside a USER message (there is no `role="tool"`).
294
+ meta = msg.get("metadata") if isinstance(msg.get("metadata"), dict) else {}
295
+ tool_use_id = meta.get("call_id") or meta.get("tool_use_id") or meta.get("id")
296
+ tool_text = msg.get("content", "")
297
+ tool_text = "" if tool_text is None else str(tool_text)
298
+
299
+ if isinstance(tool_use_id, str) and tool_use_id.strip():
300
+ api_messages.append(
301
+ {
302
+ "role": "user",
303
+ "content": [
304
+ {
305
+ "type": "tool_result",
306
+ "tool_use_id": tool_use_id.strip(),
307
+ "content": tool_text,
308
+ }
309
+ ],
310
+ }
311
+ )
312
+ else:
313
+ # Fallback: preserve as plain user text when no tool_use_id is available.
314
+ api_messages.append({"role": "user", "content": tool_text})
263
315
  else:
264
316
  api_messages.append({
265
317
  "role": "user",
@@ -348,7 +400,9 @@ class AnthropicProvider(BaseProvider):
348
400
  elif kwargs.get("tool_choice"):
349
401
  call_params["tool_choice"] = {"type": kwargs.get("tool_choice", "auto")}
350
402
  else:
351
- tool_prompt = self.tool_handler.format_tools_prompt(tools)
403
+ system_text = call_params.get("system") if isinstance(call_params.get("system"), str) else ""
404
+ include_tool_list = "## Tools (session)" not in system_text
405
+ tool_prompt = self.tool_handler.format_tools_prompt(tools, include_tool_list=include_tool_list)
352
406
  if call_params.get("system"):
353
407
  call_params["system"] += f"\n\n{tool_prompt}"
354
408
  else:
@@ -365,6 +419,8 @@ class AnthropicProvider(BaseProvider):
365
419
 
366
420
  formatted = self._format_response(response)
367
421
  formatted.gen_time = gen_time
422
+ formatted.metadata = dict(formatted.metadata or {})
423
+ formatted.metadata["_provider_request"] = {"call_params": call_params}
368
424
 
369
425
  if tools and (formatted.has_tool_calls() or
370
426
  (self.tool_handler.supports_prompted and formatted.content)):
@@ -381,7 +437,7 @@ class AnthropicProvider(BaseProvider):
381
437
  error_message = format_model_error("Anthropic", self.model, available_models)
382
438
  raise ModelNotFoundError(error_message)
383
439
  else:
384
- raise ProviderAPIError(f"Anthropic API error: {str(e)}")
440
+ raise
385
441
 
386
442
  async def _async_stream_response(self, call_params: Dict[str, Any], tools: Optional[List[Dict[str, Any]]] = None) -> AsyncIterator[GenerateResponse]:
387
443
  """Native async streaming with Anthropic's context manager pattern."""
@@ -397,7 +453,7 @@ class AnthropicProvider(BaseProvider):
397
453
  raw_response=chunk
398
454
  )
399
455
  except Exception as e:
400
- raise ProviderAPIError(f"Anthropic streaming error: {str(e)}")
456
+ raise
401
457
 
402
458
  def unload(self) -> None:
403
459
  """Close async client if it was created."""
@@ -414,13 +470,38 @@ class AnthropicProvider(BaseProvider):
414
470
  """Format tools for Anthropic API format"""
415
471
  formatted_tools = []
416
472
  for tool in tools:
417
- # Get parameters and ensure proper JSON schema format
473
+ # Anthropic expects `input_schema` to be a JSON Schema object:
474
+ # https://platform.claude.com/docs/en/agents-and-tools/tool-use/implement-tool-use
475
+ #
476
+ # Our internal tool representation typically uses:
477
+ # tool["parameters"] = { "arg": {"type": "...", "default": ...?}, ... }
478
+ # or, less commonly:
479
+ # tool["parameters"] = {"type":"object","properties":{...},"required":[...]}
418
480
  params = tool.get("parameters", {})
419
- input_schema = {
481
+
482
+ properties: Dict[str, Any] = {}
483
+ required: List[str] = []
484
+
485
+ if isinstance(params, dict) and "properties" in params:
486
+ # Treat as already-schema-like.
487
+ raw_props = params.get("properties") if isinstance(params.get("properties"), dict) else {}
488
+ properties = dict(raw_props)
489
+ raw_required = params.get("required")
490
+ if isinstance(raw_required, list):
491
+ required = [str(x) for x in raw_required if isinstance(x, (str, int))]
492
+ elif isinstance(params, dict):
493
+ # Treat as compact parameter dict; infer required args by absence of `default`.
494
+ properties = dict(params)
495
+ for k, v in params.items():
496
+ if isinstance(v, dict) and "default" not in v:
497
+ required.append(str(k))
498
+
499
+ input_schema: Dict[str, Any] = {
420
500
  "type": "object",
421
- "properties": params.get("properties", params), # Handle both formats
422
- "required": params.get("required", list(params.keys()) if "properties" not in params else [])
501
+ "properties": properties,
423
502
  }
503
+ if required:
504
+ input_schema["required"] = required
424
505
 
425
506
  formatted_tool = {
426
507
  "name": tool.get("name"),
@@ -440,7 +521,7 @@ class AnthropicProvider(BaseProvider):
440
521
  # Handle different content types
441
522
  for content_block in response.content:
442
523
  if content_block.type == "text":
443
- content = content_block.text
524
+ content += content_block.text
444
525
  elif content_block.type == "tool_use":
445
526
  if tool_calls is None:
446
527
  tool_calls = []