abstractcore 2.6.6__tar.gz → 2.6.8__tar.gz

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 (138) hide show
  1. {abstractcore-2.6.6 → abstractcore-2.6.8}/PKG-INFO +46 -1
  2. {abstractcore-2.6.6 → abstractcore-2.6.8}/README.md +44 -0
  3. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/assets/model_capabilities.json +14 -5
  4. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/utils/image_scaler.py +1 -8
  5. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/processing/__init__.py +2 -2
  6. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/processing/basic_summarizer.py +79 -10
  7. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/base.py +22 -1
  8. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/server/app.py +51 -2
  9. abstractcore-2.6.8/abstractcore/tools/__init__.py +122 -0
  10. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/tools/common_tools.py +177 -15
  11. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/tools/parser.py +96 -4
  12. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/tools/registry.py +15 -16
  13. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/utils/version.py +1 -1
  14. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/utils/vlm_token_calculator.py +1 -6
  15. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore.egg-info/PKG-INFO +46 -1
  16. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore.egg-info/requires.txt +1 -0
  17. {abstractcore-2.6.6 → abstractcore-2.6.8}/pyproject.toml +1 -0
  18. abstractcore-2.6.6/abstractcore/tools/__init__.py +0 -101
  19. {abstractcore-2.6.6 → abstractcore-2.6.8}/LICENSE +0 -0
  20. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/__init__.py +0 -0
  21. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/apps/__init__.py +0 -0
  22. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/apps/__main__.py +0 -0
  23. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/apps/app_config_utils.py +0 -0
  24. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/apps/deepsearch.py +0 -0
  25. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/apps/extractor.py +0 -0
  26. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/apps/intent.py +0 -0
  27. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/apps/judge.py +0 -0
  28. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/apps/summarizer.py +0 -0
  29. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/architectures/__init__.py +0 -0
  30. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/architectures/detection.py +0 -0
  31. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/architectures/enums.py +0 -0
  32. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/assets/architecture_formats.json +0 -0
  33. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/assets/session_schema.json +0 -0
  34. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/compression/__init__.py +0 -0
  35. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/compression/analytics.py +0 -0
  36. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/compression/cache.py +0 -0
  37. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/compression/config.py +0 -0
  38. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/compression/exceptions.py +0 -0
  39. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/compression/glyph_processor.py +0 -0
  40. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/compression/optimizer.py +0 -0
  41. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/compression/orchestrator.py +0 -0
  42. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/compression/pil_text_renderer.py +0 -0
  43. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/compression/quality.py +0 -0
  44. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/compression/text_formatter.py +0 -0
  45. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/compression/vision_compressor.py +0 -0
  46. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/config/__init__.py +0 -0
  47. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/config/main.py +0 -0
  48. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/config/manager.py +0 -0
  49. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/config/vision_config.py +0 -0
  50. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/core/__init__.py +0 -0
  51. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/core/enums.py +0 -0
  52. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/core/factory.py +0 -0
  53. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/core/interface.py +0 -0
  54. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/core/retry.py +0 -0
  55. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/core/session.py +0 -0
  56. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/core/types.py +0 -0
  57. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/download.py +0 -0
  58. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/embeddings/__init__.py +0 -0
  59. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/embeddings/manager.py +0 -0
  60. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/embeddings/models.py +0 -0
  61. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/events/__init__.py +0 -0
  62. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/exceptions/__init__.py +0 -0
  63. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/__init__.py +0 -0
  64. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/auto_handler.py +0 -0
  65. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/base.py +0 -0
  66. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/capabilities.py +0 -0
  67. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/handlers/__init__.py +0 -0
  68. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/handlers/anthropic_handler.py +0 -0
  69. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/handlers/local_handler.py +0 -0
  70. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/handlers/openai_handler.py +0 -0
  71. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/processors/__init__.py +0 -0
  72. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/processors/direct_pdf_processor.py +0 -0
  73. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/processors/glyph_pdf_processor.py +0 -0
  74. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/processors/image_processor.py +0 -0
  75. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/processors/office_processor.py +0 -0
  76. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/processors/pdf_processor.py +0 -0
  77. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/processors/text_processor.py +0 -0
  78. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/types.py +0 -0
  79. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/utils/__init__.py +0 -0
  80. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/media/vision_fallback.py +0 -0
  81. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/processing/basic_deepsearch.py +0 -0
  82. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/processing/basic_extractor.py +0 -0
  83. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/processing/basic_intent.py +0 -0
  84. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/processing/basic_judge.py +0 -0
  85. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/__init__.py +0 -0
  86. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/anthropic_provider.py +0 -0
  87. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/huggingface_provider.py +0 -0
  88. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/lmstudio_provider.py +0 -0
  89. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/mlx_provider.py +0 -0
  90. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/model_capabilities.py +0 -0
  91. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/ollama_provider.py +0 -0
  92. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/openai_compatible_provider.py +0 -0
  93. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/openai_provider.py +0 -0
  94. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/registry.py +0 -0
  95. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/streaming.py +0 -0
  96. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/providers/vllm_provider.py +0 -0
  97. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/server/__init__.py +0 -0
  98. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/structured/__init__.py +0 -0
  99. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/structured/handler.py +0 -0
  100. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/structured/retry.py +0 -0
  101. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/tools/core.py +0 -0
  102. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/tools/handler.py +0 -0
  103. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/tools/syntax_rewriter.py +0 -0
  104. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/tools/tag_rewriter.py +0 -0
  105. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/utils/__init__.py +0 -0
  106. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/utils/cli.py +0 -0
  107. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/utils/message_preprocessor.py +0 -0
  108. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/utils/self_fixes.py +0 -0
  109. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/utils/structured_logging.py +0 -0
  110. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/utils/token_utils.py +0 -0
  111. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore/utils/trace_export.py +0 -0
  112. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore.egg-info/SOURCES.txt +0 -0
  113. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore.egg-info/dependency_links.txt +0 -0
  114. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore.egg-info/entry_points.txt +0 -0
  115. {abstractcore-2.6.6 → abstractcore-2.6.8}/abstractcore.egg-info/top_level.txt +0 -0
  116. {abstractcore-2.6.6 → abstractcore-2.6.8}/setup.cfg +0 -0
  117. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_agentic_cli_compatibility.py +0 -0
  118. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_basic_session.py +0 -0
  119. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_complete_integration.py +0 -0
  120. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_comprehensive_events.py +0 -0
  121. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_core_components.py +0 -0
  122. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_enhanced_prompt.py +0 -0
  123. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_environment_variable_tool_call_tags.py +0 -0
  124. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_factory.py +0 -0
  125. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_final_accuracy.py +0 -0
  126. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_final_comprehensive.py +0 -0
  127. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_final_graceful_errors.py +0 -0
  128. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_graceful_fallback.py +0 -0
  129. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_import_debug.py +0 -0
  130. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_integrated_functionality.py +0 -0
  131. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_retry_observability.py +0 -0
  132. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_retry_strategy.py +0 -0
  133. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_seed_determinism.py +0 -0
  134. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_seed_temperature_basic.py +0 -0
  135. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_sensory_prompting.py +0 -0
  136. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_text_only_model_experience.py +0 -0
  137. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_unload_memory.py +0 -0
  138. {abstractcore-2.6.6 → abstractcore-2.6.8}/tests/test_user_scenario_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: abstractcore
3
- Version: 2.6.6
3
+ Version: 2.6.8
4
4
  Summary: Unified interface to all LLM providers with essential infrastructure for tool calling, streaming, and model management
5
5
  Author-email: Laurent-Philippe Albou <contact@abstractcore.ai>
6
6
  Maintainer-email: Laurent-Philippe Albou <contact@abstractcore.ai>
@@ -30,6 +30,7 @@ Requires-Dist: pydantic<3.0.0,>=2.0.0
30
30
  Requires-Dist: httpx<1.0.0,>=0.24.0
31
31
  Requires-Dist: tiktoken<1.0.0,>=0.5.0
32
32
  Requires-Dist: requests<3.0.0,>=2.25.0
33
+ Requires-Dist: Pillow<12.0.0,>=10.0.0
33
34
  Provides-Extra: openai
34
35
  Requires-Dist: openai<2.0.0,>=1.0.0; extra == "openai"
35
36
  Provides-Extra: anthropic
@@ -194,6 +195,50 @@ response = llm.generate(
194
195
  print(response.content)
195
196
  ```
196
197
 
198
+ ### Tool Execution Modes
199
+
200
+ AbstractCore supports two tool execution modes:
201
+
202
+ **Mode 1: Passthrough (Default)** - Returns raw tool call tags for downstream processing
203
+
204
+ ```python
205
+ from abstractcore import create_llm
206
+ from abstractcore.tools import tool
207
+
208
+ @tool(name="get_weather", description="Get weather for a city")
209
+ def get_weather(city: str) -> str:
210
+ return f"Weather in {city}: Sunny, 22°C"
211
+
212
+ llm = create_llm("ollama", model="qwen3:4b") # execute_tools=False by default
213
+ response = llm.generate("What's the weather in Paris?", tools=[get_weather])
214
+ # response.content contains raw tool call tags: <|tool_call|>...
215
+ # Downstream runtime (AbstractRuntime, Codex, Claude Code) parses and executes
216
+ ```
217
+
218
+ **Use case**: Agent loops, AbstractRuntime, Codex, Claude Code, custom orchestration
219
+
220
+ **Mode 2: Direct Execution** - AbstractCore executes tools and returns results
221
+
222
+ ```python
223
+ from abstractcore import create_llm
224
+ from abstractcore.tools import tool
225
+ from abstractcore.tools.registry import register_tool
226
+
227
+ @tool(name="get_weather", description="Get weather for a city")
228
+ def get_weather(city: str) -> str:
229
+ return f"Weather in {city}: Sunny, 22°C"
230
+
231
+ register_tool(get_weather) # Required for direct execution
232
+
233
+ llm = create_llm("ollama", model="qwen3:4b", execute_tools=True)
234
+ response = llm.generate("What's the weather in Paris?", tools=[get_weather])
235
+ # response.content contains executed tool results
236
+ ```
237
+
238
+ **Use case**: Simple scripts, single-turn tool use
239
+
240
+ > **Note**: The `@tool` decorator creates metadata but does NOT register globally. Tools are passed explicitly to `generate()`. Use `register_tool()` only when using direct execution mode.
241
+
197
242
  ### Response Object (GenerateResponse)
198
243
 
199
244
  Every LLM generation returns a **GenerateResponse** object with consistent structure across all providers:
@@ -65,6 +65,50 @@ response = llm.generate(
65
65
  print(response.content)
66
66
  ```
67
67
 
68
+ ### Tool Execution Modes
69
+
70
+ AbstractCore supports two tool execution modes:
71
+
72
+ **Mode 1: Passthrough (Default)** - Returns raw tool call tags for downstream processing
73
+
74
+ ```python
75
+ from abstractcore import create_llm
76
+ from abstractcore.tools import tool
77
+
78
+ @tool(name="get_weather", description="Get weather for a city")
79
+ def get_weather(city: str) -> str:
80
+ return f"Weather in {city}: Sunny, 22°C"
81
+
82
+ llm = create_llm("ollama", model="qwen3:4b") # execute_tools=False by default
83
+ response = llm.generate("What's the weather in Paris?", tools=[get_weather])
84
+ # response.content contains raw tool call tags: <|tool_call|>...
85
+ # Downstream runtime (AbstractRuntime, Codex, Claude Code) parses and executes
86
+ ```
87
+
88
+ **Use case**: Agent loops, AbstractRuntime, Codex, Claude Code, custom orchestration
89
+
90
+ **Mode 2: Direct Execution** - AbstractCore executes tools and returns results
91
+
92
+ ```python
93
+ from abstractcore import create_llm
94
+ from abstractcore.tools import tool
95
+ from abstractcore.tools.registry import register_tool
96
+
97
+ @tool(name="get_weather", description="Get weather for a city")
98
+ def get_weather(city: str) -> str:
99
+ return f"Weather in {city}: Sunny, 22°C"
100
+
101
+ register_tool(get_weather) # Required for direct execution
102
+
103
+ llm = create_llm("ollama", model="qwen3:4b", execute_tools=True)
104
+ response = llm.generate("What's the weather in Paris?", tools=[get_weather])
105
+ # response.content contains executed tool results
106
+ ```
107
+
108
+ **Use case**: Simple scripts, single-turn tool use
109
+
110
+ > **Note**: The `@tool` decorator creates metadata but does NOT register globally. Tools are passed explicitly to `generate()`. Use `register_tool()` only when using direct execution mode.
111
+
68
112
  ### Response Object (GenerateResponse)
69
113
 
70
114
  Every LLM generation returns a **GenerateResponse** object with consistent structure across all providers:
@@ -638,17 +638,26 @@
638
638
  "max_tokens": 262144
639
639
  },
640
640
  "qwen3-coder-30b": {
641
- "max_output_tokens": 8192,
641
+ "max_output_tokens": 65536,
642
642
  "tool_support": "native",
643
643
  "structured_output": "native",
644
644
  "parallel_tools": true,
645
645
  "vision_support": false,
646
646
  "audio_support": false,
647
- "notes": "Code-focused model with native tool support via chatml-function-calling format",
648
- "source": "Alibaba official docs",
647
+ "architecture": "mixture_of_experts",
648
+ "total_parameters": "30.5B",
649
+ "active_parameters": "3.3B",
650
+ "experts": 128,
651
+ "experts_activated": 8,
652
+ "notes": "Code-focused MoE model (30.5B total/3.3B active, 128 experts/8 activated). Native tool support via chatml-function-calling format. Supports up to 1M tokens with YaRN extension.",
653
+ "source": "Qwen HuggingFace model card 2025",
649
654
  "canonical_name": "qwen3-coder-30b",
650
- "aliases": [],
651
- "max_tokens": 32768
655
+ "aliases": [
656
+ "Qwen/Qwen3-Coder-30B-A3B-Instruct",
657
+ "qwen3-coder-30b-a3b",
658
+ "qwen3-coder-30b-a3b-instruct"
659
+ ],
660
+ "max_tokens": 262144
652
661
  },
653
662
  "qwen2-vl": {
654
663
  "max_output_tokens": 8192,
@@ -3,20 +3,13 @@ Image scaling utility for AbstractCore media handling.
3
3
 
4
4
  Provides intelligent image scaling based on model-specific requirements
5
5
  and capabilities for vision models.
6
-
7
- Requires: PIL (Pillow) - install with `pip install Pillow`
8
6
  """
9
7
 
10
8
  from typing import Tuple, Optional, Union, Dict, Any
11
9
  from enum import Enum
12
10
  from pathlib import Path
13
11
 
14
- try:
15
- from PIL import Image, ImageOps
16
- except ImportError as e:
17
- raise ImportError(
18
- "PIL (Pillow) is required for image scaling. Install with: pip install Pillow"
19
- ) from e
12
+ from PIL import Image, ImageOps
20
13
 
21
14
  from ..base import MediaProcessingError
22
15
  from ...utils.structured_logging import get_logger
@@ -5,14 +5,14 @@ Basic text processing capabilities built on top of AbstractCore,
5
5
  demonstrating how to leverage the core infrastructure for real-world tasks.
6
6
  """
7
7
 
8
- from .basic_summarizer import BasicSummarizer, SummaryStyle, SummaryLength
8
+ from .basic_summarizer import BasicSummarizer, SummaryStyle, SummaryLength, CompressionMode
9
9
  from .basic_extractor import BasicExtractor
10
10
  from .basic_judge import BasicJudge, JudgmentCriteria, Assessment, create_judge
11
11
  from .basic_deepsearch import BasicDeepSearch, ResearchReport, ResearchFinding, ResearchPlan, ResearchSubTask
12
12
  from .basic_intent import BasicIntentAnalyzer, IntentType, IntentDepth, IntentContext, IdentifiedIntent, IntentAnalysisOutput
13
13
 
14
14
  __all__ = [
15
- 'BasicSummarizer', 'SummaryStyle', 'SummaryLength',
15
+ 'BasicSummarizer', 'SummaryStyle', 'SummaryLength', 'CompressionMode',
16
16
  'BasicExtractor',
17
17
  'BasicJudge', 'JudgmentCriteria', 'Assessment', 'create_judge',
18
18
  'BasicDeepSearch', 'ResearchReport', 'ResearchFinding', 'ResearchPlan', 'ResearchSubTask',
@@ -35,6 +35,42 @@ class SummaryLength(Enum):
35
35
  COMPREHENSIVE = "comprehensive" # Full analysis with context
36
36
 
37
37
 
38
+ class CompressionMode(Enum):
39
+ """Compression aggressiveness for chat history summarization.
40
+
41
+ Controls how aggressively the summarizer compresses conversation history:
42
+ - LIGHT: Keep most information, only remove redundancy
43
+ - STANDARD: Balanced compression, main points and context
44
+ - HEAVY: Aggressive compression, only critical information
45
+ """
46
+ LIGHT = "light"
47
+ STANDARD = "standard"
48
+ HEAVY = "heavy"
49
+
50
+
51
+ # Compression mode-specific instructions for summarization prompts
52
+ COMPRESSION_INSTRUCTIONS = {
53
+ CompressionMode.LIGHT: (
54
+ "Preserve most details from this conversation while removing only redundancy. "
55
+ "Keep: all key decisions and outcomes, important context and background, "
56
+ "specific details/names/numbers/technical terms, all tool calls and results, "
57
+ "error messages and resolutions. Remove only: repetitive greetings, duplicate information."
58
+ ),
59
+ CompressionMode.STANDARD: (
60
+ "Summarize with balanced compression, keeping main points and essential context. "
61
+ "Keep: key decisions and rationale, important outcomes, critical context for ongoing work, "
62
+ "unresolved items and pending tasks. Remove: intermediate reasoning steps, "
63
+ "exploratory tangents, detailed tool outputs (keep only key findings)."
64
+ ),
65
+ CompressionMode.HEAVY: (
66
+ "Extract only the most critical information. Keep ONLY: final decisions made, "
67
+ "critical outcomes (success/failure), essential context to continue work, "
68
+ "blocking issues and hard dependencies. Remove: all exploratory discussion, "
69
+ "all intermediate steps, all detailed outputs, all background explanations."
70
+ ),
71
+ }
72
+
73
+
38
74
  class LLMSummaryOutput(BaseModel):
39
75
  """LLM-generated summary output (without word counts)"""
40
76
  summary: str = Field(description="The main summary text")
@@ -493,7 +529,8 @@ Create a unified summary that represents the entire document effectively."""
493
529
  self,
494
530
  messages: List[dict],
495
531
  preserve_recent: int = 6,
496
- focus: Optional[str] = None
532
+ focus: Optional[str] = None,
533
+ compression_mode: CompressionMode = CompressionMode.STANDARD
497
534
  ) -> SummaryOutput:
498
535
  """
499
536
  Specialized method for chat history summarization following SOTA 2025 practices
@@ -502,6 +539,7 @@ Create a unified summary that represents the entire document effectively."""
502
539
  messages: List of message dicts with 'role' and 'content' keys
503
540
  preserve_recent: Number of recent messages to keep intact (default 6)
504
541
  focus: Optional focus for summarization (e.g., "key decisions", "technical solutions")
542
+ compression_mode: How aggressively to compress (LIGHT, STANDARD, HEAVY)
505
543
 
506
544
  Returns:
507
545
  SummaryOutput: Structured summary optimized for chat history context
@@ -511,36 +549,67 @@ Create a unified summary that represents the entire document effectively."""
511
549
  - Focuses on decisions, solutions, and ongoing topics
512
550
  - Maintains user intent and assistant responses
513
551
  - Optimized for chat continuation rather than standalone summary
552
+
553
+ Compression Modes:
554
+ - LIGHT: Keep most information, only remove redundancy
555
+ - STANDARD: Balanced compression, main points and context
556
+ - HEAVY: Aggressive compression, only critical information
514
557
  """
558
+ # Build focus with compression instructions
559
+ compression_instruction = COMPRESSION_INSTRUCTIONS.get(
560
+ compression_mode,
561
+ COMPRESSION_INSTRUCTIONS[CompressionMode.STANDARD]
562
+ )
563
+
564
+ # Combine user focus with compression instruction
565
+ if focus:
566
+ effective_focus = f"{compression_instruction} Focus especially on: {focus}"
567
+ else:
568
+ effective_focus = compression_instruction
569
+
570
+ # Map compression mode to summary length for appropriate output size
571
+ length_map = {
572
+ CompressionMode.LIGHT: SummaryLength.DETAILED,
573
+ CompressionMode.STANDARD: SummaryLength.STANDARD,
574
+ CompressionMode.HEAVY: SummaryLength.BRIEF,
575
+ }
576
+ target_length = length_map.get(compression_mode, SummaryLength.STANDARD)
577
+
578
+ logger.debug("Chat history summarization with compression mode",
579
+ message_count=len(messages),
580
+ preserve_recent=preserve_recent,
581
+ compression_mode=compression_mode.value,
582
+ target_length=target_length.value)
583
+
515
584
  if len(messages) <= preserve_recent:
516
585
  # If short enough, just summarize normally
517
- logger.debug("Chat history is short, using standard summarization",
518
- message_count=len(messages),
586
+ logger.debug("Chat history is short, using standard summarization",
587
+ message_count=len(messages),
519
588
  preserve_recent=preserve_recent)
520
589
  chat_text = self._format_chat_messages_to_text(messages)
521
590
  return self.summarize(
522
591
  chat_text,
523
- focus=focus or "conversational context and key information",
592
+ focus=effective_focus,
524
593
  style=SummaryStyle.CONVERSATIONAL,
525
- length=SummaryLength.STANDARD
594
+ length=target_length
526
595
  )
527
596
 
528
597
  # Split into older messages (to summarize) and recent messages (to preserve)
529
598
  older_messages = messages[:-preserve_recent]
530
599
  recent_messages = messages[-preserve_recent:]
531
-
532
- logger.debug("Splitting chat history for summarization",
600
+
601
+ logger.debug("Splitting chat history for summarization",
533
602
  total_messages=len(messages),
534
603
  older_messages=len(older_messages),
535
604
  recent_messages=len(recent_messages))
536
605
 
537
- # Summarize older messages with conversational focus
606
+ # Summarize older messages with conversational focus and compression mode
538
607
  older_text = self._format_chat_messages_to_text(older_messages)
539
608
  older_summary = self.summarize(
540
609
  older_text,
541
- focus=focus or "key decisions, solutions, and ongoing context",
610
+ focus=effective_focus,
542
611
  style=SummaryStyle.CONVERSATIONAL,
543
- length=SummaryLength.DETAILED
612
+ length=target_length
544
613
  )
545
614
 
546
615
  # The summary should ONLY contain the older messages summary
@@ -5,6 +5,7 @@ Base provider with integrated telemetry, events, and exception handling.
5
5
  import time
6
6
  import uuid
7
7
  import asyncio
8
+ import warnings
8
9
  from collections import deque
9
10
  from typing import List, Dict, Any, Optional, Union, Iterator, AsyncIterator, Type
10
11
  from abc import ABC, abstractmethod
@@ -60,6 +61,13 @@ class BaseProvider(AbstractCoreInterface, ABC):
60
61
  # execute_tools: True = AbstractCore executes tools (legacy mode)
61
62
  # False = Pass-through mode (default - for API server / agentic CLI)
62
63
  self.execute_tools = kwargs.get('execute_tools', False)
64
+ if self.execute_tools:
65
+ warnings.warn(
66
+ "execute_tools=True is deprecated. Prefer passing tools explicitly to generate() "
67
+ "and executing tool calls in the host/runtime via a ToolExecutor.",
68
+ DeprecationWarning,
69
+ stacklevel=2,
70
+ )
63
71
 
64
72
  # Setup retry manager with optional configuration
65
73
  retry_config = kwargs.get('retry_config', None)
@@ -202,6 +210,12 @@ class BaseProvider(AbstractCoreInterface, ABC):
202
210
  """
203
211
  trace_id = str(uuid.uuid4())
204
212
 
213
+ # If trace retention is disabled, still return a trace_id for correlation
214
+ # without constructing/storing a full trace payload.
215
+ maxlen = getattr(getattr(self, "_traces", None), "maxlen", None)
216
+ if maxlen == 0:
217
+ return trace_id
218
+
205
219
  # Extract generation parameters
206
220
  temperature = kwargs.get('temperature', self.temperature)
207
221
  max_tokens = kwargs.get('max_tokens', self.max_tokens)
@@ -408,6 +422,13 @@ class BaseProvider(AbstractCoreInterface, ABC):
408
422
 
409
423
  # Handle tool execution control
410
424
  should_execute_tools = execute_tools if execute_tools is not None else self.execute_tools
425
+ if should_execute_tools and converted_tools:
426
+ warnings.warn(
427
+ "execute_tools=True is deprecated. Prefer passing tools explicitly to generate() "
428
+ "and executing tool calls in the host/runtime via a ToolExecutor.",
429
+ DeprecationWarning,
430
+ stacklevel=2,
431
+ )
411
432
  if not should_execute_tools and converted_tools:
412
433
  # If tools are provided but execution is disabled,
413
434
  # we still pass them to the provider for generation but won't execute them
@@ -1556,4 +1577,4 @@ Please provide a structured response."""
1556
1577
  # Yield chunks asynchronously
1557
1578
  for chunk in sync_gen:
1558
1579
  yield chunk
1559
- await asyncio.sleep(0) # Yield control to event loop
1580
+ await asyncio.sleep(0) # Yield control to event loop
@@ -1956,6 +1956,39 @@ async def provider_chat_completions(
1956
1956
  _, model = parse_model_string(request.model)
1957
1957
  return await process_chat_completion(provider, model, request, http_request)
1958
1958
 
1959
+
1960
+ def _extract_trace_metadata(http_request: Request) -> Dict[str, Any]:
1961
+ """Extract trace metadata from request headers (schema-safe)."""
1962
+ meta: Dict[str, Any] = {}
1963
+
1964
+ raw = (
1965
+ http_request.headers.get("x-abstractcore-trace-metadata")
1966
+ or http_request.headers.get("x-abstract-trace-metadata")
1967
+ )
1968
+ if raw:
1969
+ try:
1970
+ parsed = json.loads(raw)
1971
+ if isinstance(parsed, dict):
1972
+ meta.update(parsed)
1973
+ except Exception:
1974
+ # Ignore invalid metadata payloads; tracing is best-effort.
1975
+ pass
1976
+
1977
+ header_map = {
1978
+ "actor_id": "x-abstractcore-actor-id",
1979
+ "session_id": "x-abstractcore-session-id",
1980
+ "run_id": "x-abstractcore-run-id",
1981
+ "parent_run_id": "x-abstractcore-parent-run-id",
1982
+ }
1983
+ for key, header in header_map.items():
1984
+ val = http_request.headers.get(header)
1985
+ if val is not None and key not in meta:
1986
+ meta[key] = val
1987
+
1988
+ # Never log or return these directly; they are for internal correlation only.
1989
+ return meta
1990
+
1991
+
1959
1992
  async def process_chat_completion(
1960
1993
  provider: str,
1961
1994
  model: str,
@@ -2019,6 +2052,11 @@ async def process_chat_completion(
2019
2052
  # Create LLM instance
2020
2053
  # Prepare provider-specific kwargs
2021
2054
  provider_kwargs = {}
2055
+ trace_metadata = _extract_trace_metadata(http_request)
2056
+ if trace_metadata:
2057
+ # Enable trace capture (trace_id) without retaining full trace buffers by default.
2058
+ provider_kwargs["enable_tracing"] = True
2059
+ provider_kwargs.setdefault("max_traces", 0)
2022
2060
  if request.base_url:
2023
2061
  provider_kwargs["base_url"] = request.base_url
2024
2062
  logger.info(
@@ -2047,6 +2085,8 @@ async def process_chat_completion(
2047
2085
  "tool_choice": request.tool_choice if request.tools else None,
2048
2086
  "execute_tools": False, # Server mode - don't execute tools
2049
2087
  }
2088
+ if trace_metadata:
2089
+ gen_kwargs["trace_metadata"] = trace_metadata
2050
2090
 
2051
2091
  # Add optional parameters
2052
2092
  if request.stop:
@@ -2081,9 +2121,18 @@ async def process_chat_completion(
2081
2121
  )
2082
2122
  else:
2083
2123
  response = llm.generate(**gen_kwargs)
2084
- return convert_to_openai_response(
2124
+ openai_response = convert_to_openai_response(
2085
2125
  response, provider, model, syntax_rewriter, request_id
2086
2126
  )
2127
+ trace_id = None
2128
+ if hasattr(response, "metadata") and isinstance(getattr(response, "metadata"), dict):
2129
+ trace_id = response.metadata.get("trace_id")
2130
+ if trace_id:
2131
+ return JSONResponse(
2132
+ content=openai_response,
2133
+ headers={"X-AbstractCore-Trace-Id": str(trace_id)},
2134
+ )
2135
+ return openai_response
2087
2136
  finally:
2088
2137
  # Cleanup temporary files (base64 and downloaded images) with delay to avoid race conditions
2089
2138
  import threading
@@ -2408,4 +2457,4 @@ Debug Mode:
2408
2457
  # ============================================================================
2409
2458
 
2410
2459
  if __name__ == "__main__":
2411
- run_server_with_args()
2460
+ run_server_with_args()
@@ -0,0 +1,122 @@
1
+ """
2
+ Universal tool support for AbstractCore.
3
+
4
+ This package provides a unified tool system that works across all models
5
+ and providers, whether they have native tool APIs or require prompting.
6
+
7
+ Tool Execution Modes
8
+ --------------------
9
+
10
+ AbstractCore supports two tool execution modes:
11
+
12
+ **Passthrough Mode (Default)** - execute_tools=False
13
+ The LLM returns raw tool call tags that downstream runtimes
14
+ (AbstractRuntime, Codex, Claude Code) parse and execute.
15
+ Use case: Agent loops, custom orchestration, multi-step workflows.
16
+
17
+ **Direct Execution Mode** - execute_tools=True
18
+ AbstractCore parses and executes tools internally using the
19
+ global registry. Requires register_tool() for each tool.
20
+ Use case: Simple scripts, single-turn tool use.
21
+
22
+ Key Components
23
+ --------------
24
+ - Core types (ToolDefinition, ToolCall, ToolResult)
25
+ - Universal handler for all models
26
+ - Architecture-based parsing and formatting
27
+ - Tool registry for managing available tools
28
+
29
+ Example: Passthrough Mode (Default)
30
+ -----------------------------------
31
+ ```python
32
+ from abstractcore import create_llm
33
+ from abstractcore.tools import tool
34
+
35
+ @tool(name="get_weather", description="Get weather for a city")
36
+ def get_weather(city: str) -> str:
37
+ return f"Weather in {city}: Sunny"
38
+
39
+ llm = create_llm("ollama", model="qwen3:4b")
40
+ response = llm.generate("Weather in Paris?", tools=[get_weather])
41
+ # response.content has tool call tags - parse with your runtime
42
+ ```
43
+
44
+ Example: Direct Execution Mode
45
+ ------------------------------
46
+ ```python
47
+ from abstractcore import create_llm
48
+ from abstractcore.tools import tool, register_tool
49
+
50
+ @tool(name="get_weather", description="Get weather for a city")
51
+ def get_weather(city: str) -> str:
52
+ return f"Weather in {city}: Sunny"
53
+
54
+ register_tool(get_weather) # Required for direct execution
55
+
56
+ llm = create_llm("ollama", model="qwen3:4b", execute_tools=True)
57
+ response = llm.generate("Weather in Paris?", tools=[get_weather])
58
+ # response.content has executed tool results
59
+ ```
60
+
61
+ Note: The @tool decorator creates metadata but does NOT auto-register.
62
+ Tools are passed explicitly to generate(). Use register_tool() only
63
+ when using direct execution mode.
64
+ """
65
+
66
+ # Core types
67
+ from .core import (
68
+ ToolDefinition,
69
+ ToolCall,
70
+ ToolResult,
71
+ ToolCallResponse,
72
+ tool
73
+ )
74
+
75
+ # Handler
76
+ from .handler import (
77
+ UniversalToolHandler,
78
+ create_handler
79
+ )
80
+
81
+ # Parser functions
82
+ from .parser import (
83
+ detect_tool_calls,
84
+ parse_tool_calls,
85
+ format_tool_prompt
86
+ )
87
+
88
+ # Registry
89
+ from .registry import (
90
+ ToolRegistry,
91
+ register_tool,
92
+ get_registry,
93
+ execute_tool,
94
+ execute_tools,
95
+ clear_registry
96
+ )
97
+
98
+ __all__ = [
99
+ # Core types
100
+ "ToolDefinition",
101
+ "ToolCall",
102
+ "ToolResult",
103
+ "ToolCallResponse",
104
+ "tool",
105
+
106
+ # Handler
107
+ "UniversalToolHandler",
108
+ "create_handler",
109
+
110
+ # Parser
111
+ "detect_tool_calls",
112
+ "parse_tool_calls",
113
+ "format_tool_prompt",
114
+
115
+ # Registry
116
+ "ToolRegistry",
117
+ "register_tool",
118
+ "get_registry",
119
+ "execute_tool",
120
+ "execute_tools",
121
+ "clear_registry",
122
+ ]