abstractcore 2.4.6__tar.gz → 2.4.7__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 (165) hide show
  1. {abstractcore-2.4.6 → abstractcore-2.4.7}/PKG-INFO +50 -4
  2. {abstractcore-2.4.6 → abstractcore-2.4.7}/README.md +40 -0
  3. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/__init__.py +5 -1
  4. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/assets/session_schema.json +1 -1
  5. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/core/session.py +1 -1
  6. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/core/types.py +25 -1
  7. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/providers/anthropic_provider.py +6 -0
  8. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/providers/huggingface_provider.py +21 -9
  9. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/providers/lmstudio_provider.py +11 -3
  10. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/providers/mlx_provider.py +16 -7
  11. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/providers/mock_provider.py +17 -7
  12. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/providers/ollama_provider.py +10 -3
  13. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/providers/openai_provider.py +12 -3
  14. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/utils/version.py +1 -1
  15. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore.egg-info/PKG-INFO +50 -4
  16. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore.egg-info/requires.txt +10 -3
  17. {abstractcore-2.4.6 → abstractcore-2.4.7}/pyproject.toml +11 -3
  18. {abstractcore-2.4.6 → abstractcore-2.4.7}/LICENSE +0 -0
  19. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/apps/__init__.py +0 -0
  20. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/apps/__main__.py +0 -0
  21. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/apps/app_config_utils.py +0 -0
  22. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/apps/extractor.py +0 -0
  23. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/apps/judge.py +0 -0
  24. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/apps/summarizer.py +0 -0
  25. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/architectures/__init__.py +0 -0
  26. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/architectures/detection.py +0 -0
  27. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/architectures/enums.py +0 -0
  28. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/assets/architecture_formats.json +0 -0
  29. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/assets/model_capabilities.json +0 -0
  30. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/cli/__init__.py +0 -0
  31. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/cli/main.py +0 -0
  32. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/cli/vision_config.py +0 -0
  33. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/core/__init__.py +0 -0
  34. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/core/enums.py +0 -0
  35. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/core/factory.py +0 -0
  36. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/core/interface.py +0 -0
  37. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/core/retry.py +0 -0
  38. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/embeddings/__init__.py +0 -0
  39. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/embeddings/manager.py +0 -0
  40. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/embeddings/models.py +0 -0
  41. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/events/__init__.py +0 -0
  42. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/exceptions/__init__.py +0 -0
  43. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/__init__.py +0 -0
  44. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/auto_handler.py +0 -0
  45. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/base.py +0 -0
  46. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/capabilities.py +0 -0
  47. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/handlers/__init__.py +0 -0
  48. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/handlers/anthropic_handler.py +0 -0
  49. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/handlers/local_handler.py +0 -0
  50. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/handlers/openai_handler.py +0 -0
  51. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/processors/__init__.py +0 -0
  52. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/processors/image_processor.py +0 -0
  53. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/processors/office_processor.py +0 -0
  54. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/processors/pdf_processor.py +0 -0
  55. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/processors/text_processor.py +0 -0
  56. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/types.py +0 -0
  57. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/utils/__init__.py +0 -0
  58. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/utils/image_scaler.py +0 -0
  59. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/media/vision_fallback.py +0 -0
  60. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/processing/__init__.py +0 -0
  61. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/processing/basic_extractor.py +0 -0
  62. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/processing/basic_judge.py +0 -0
  63. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/processing/basic_summarizer.py +0 -0
  64. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/providers/__init__.py +0 -0
  65. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/providers/base.py +0 -0
  66. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/providers/registry.py +0 -0
  67. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/providers/streaming.py +0 -0
  68. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/server/__init__.py +0 -0
  69. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/server/app.py +0 -0
  70. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/structured/__init__.py +0 -0
  71. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/structured/handler.py +0 -0
  72. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/structured/retry.py +0 -0
  73. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/tools/__init__.py +0 -0
  74. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/tools/common_tools.py +0 -0
  75. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/tools/core.py +0 -0
  76. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/tools/handler.py +0 -0
  77. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/tools/parser.py +0 -0
  78. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/tools/registry.py +0 -0
  79. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/tools/syntax_rewriter.py +0 -0
  80. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/tools/tag_rewriter.py +0 -0
  81. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/utils/__init__.py +0 -0
  82. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/utils/cli.py +0 -0
  83. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/utils/message_preprocessor.py +0 -0
  84. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/utils/self_fixes.py +0 -0
  85. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/utils/structured_logging.py +0 -0
  86. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore/utils/token_utils.py +0 -0
  87. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore.egg-info/SOURCES.txt +0 -0
  88. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore.egg-info/dependency_links.txt +0 -0
  89. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore.egg-info/entry_points.txt +0 -0
  90. {abstractcore-2.4.6 → abstractcore-2.4.7}/abstractcore.egg-info/top_level.txt +0 -0
  91. {abstractcore-2.4.6 → abstractcore-2.4.7}/setup.cfg +0 -0
  92. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_agentic_cli_compatibility.py +0 -0
  93. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_all_specified_providers.py +0 -0
  94. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_basic_session.py +0 -0
  95. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_basic_summarizer.py +0 -0
  96. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_cli_media.py +0 -0
  97. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_complete_integration.py +0 -0
  98. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_comprehensive_events.py +0 -0
  99. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_consistency.py +0 -0
  100. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_core_components.py +0 -0
  101. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_critical_streaming_tool_fix.py +0 -0
  102. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_debug_server.py +0 -0
  103. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_direct_vs_server.py +0 -0
  104. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_embeddings.py +0 -0
  105. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_embeddings_integration.py +0 -0
  106. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_embeddings_llm_integration.py +0 -0
  107. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_embeddings_matrix_operations.py +0 -0
  108. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_embeddings_no_mock.py +0 -0
  109. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_embeddings_real.py +0 -0
  110. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_embeddings_semantic_validation.py +0 -0
  111. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_embeddings_simple.py +0 -0
  112. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_enhanced_prompt.py +0 -0
  113. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_environment_variable_tool_call_tags.py +0 -0
  114. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_factory.py +0 -0
  115. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_final_accuracy.py +0 -0
  116. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_final_comprehensive.py +0 -0
  117. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_final_graceful_errors.py +0 -0
  118. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_fixed_media.py +0 -0
  119. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_fixed_prompt.py +0 -0
  120. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_graceful_fallback.py +0 -0
  121. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_import_debug.py +0 -0
  122. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_integrated_functionality.py +0 -0
  123. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_lmstudio_context.py +0 -0
  124. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_media_import.py +0 -0
  125. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_media_server.py +0 -0
  126. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_ollama_tool_role_fix.py +0 -0
  127. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_openai_conversion_manual.py +0 -0
  128. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_openai_format_bug.py +0 -0
  129. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_openai_format_conversion.py +0 -0
  130. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_openai_media_integration.py +0 -0
  131. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_progressive_complexity.py +0 -0
  132. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_provider_basic_session.py +0 -0
  133. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_provider_connectivity.py +0 -0
  134. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_provider_simple_generation.py +0 -0
  135. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_provider_streaming.py +0 -0
  136. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_provider_token_translation.py +0 -0
  137. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_provider_tool_detection.py +0 -0
  138. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_providers.py +0 -0
  139. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_providers_comprehensive.py +0 -0
  140. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_providers_simple.py +0 -0
  141. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_real_models_comprehensive.py +0 -0
  142. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_retry_observability.py +0 -0
  143. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_retry_strategy.py +0 -0
  144. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_seed_determinism.py +0 -0
  145. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_seed_temperature_basic.py +0 -0
  146. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_sensory_prompting.py +0 -0
  147. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_server_debug.py +0 -0
  148. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_server_embeddings_real.py +0 -0
  149. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_server_integration.py +0 -0
  150. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_stream_tool_calling.py +0 -0
  151. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_streaming_enhancements.py +0 -0
  152. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_streaming_tag_rewriting.py +0 -0
  153. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_structured_integration.py +0 -0
  154. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_structured_output.py +0 -0
  155. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_syntax_rewriter.py +0 -0
  156. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_text_only_model_experience.py +0 -0
  157. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_tool_calling.py +0 -0
  158. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_tool_execution_separation.py +0 -0
  159. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_unified_streaming.py +0 -0
  160. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_unload_memory.py +0 -0
  161. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_user_scenario_validation.py +0 -0
  162. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_vision_accuracy.py +0 -0
  163. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_vision_comprehensive.py +0 -0
  164. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_vision_fallback_improvement.py +0 -0
  165. {abstractcore-2.4.6 → abstractcore-2.4.7}/tests/test_wrong_model_fallback.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: abstractcore
3
- Version: 2.4.6
3
+ Version: 2.4.7
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>
@@ -29,6 +29,7 @@ License-File: LICENSE
29
29
  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
+ Requires-Dist: requests<3.0.0,>=2.25.0
32
33
  Provides-Extra: openai
33
34
  Requires-Dist: openai<2.0.0,>=1.0.0; extra == "openai"
34
35
  Provides-Extra: anthropic
@@ -46,6 +47,11 @@ Provides-Extra: embeddings
46
47
  Requires-Dist: sentence-transformers<4.0.0,>=2.7.0; extra == "embeddings"
47
48
  Requires-Dist: numpy<2.0.0,>=1.20.0; extra == "embeddings"
48
49
  Provides-Extra: processing
50
+ Provides-Extra: tools
51
+ Requires-Dist: beautifulsoup4<5.0.0,>=4.12.0; extra == "tools"
52
+ Requires-Dist: lxml<6.0.0,>=4.9.0; extra == "tools"
53
+ Requires-Dist: duckduckgo-search<4.0.0,>=3.8.0; extra == "tools"
54
+ Requires-Dist: psutil<6.0.0,>=5.9.0; extra == "tools"
49
55
  Provides-Extra: media
50
56
  Requires-Dist: Pillow<12.0.0,>=10.0.0; extra == "media"
51
57
  Requires-Dist: pymupdf4llm<1.0.0,>=0.0.20; extra == "media"
@@ -60,9 +66,9 @@ Requires-Dist: abstractcore[huggingface]; extra == "heavy-providers"
60
66
  Provides-Extra: all-providers
61
67
  Requires-Dist: abstractcore[anthropic,embeddings,huggingface,lmstudio,mlx,ollama,openai]; extra == "all-providers"
62
68
  Provides-Extra: all
63
- Requires-Dist: abstractcore[anthropic,dev,docs,embeddings,huggingface,lmstudio,media,mlx,ollama,openai,processing,server,test]; extra == "all"
69
+ Requires-Dist: abstractcore[anthropic,dev,docs,embeddings,huggingface,lmstudio,media,mlx,ollama,openai,processing,server,test,tools]; extra == "all"
64
70
  Provides-Extra: lightweight
65
- Requires-Dist: abstractcore[anthropic,embeddings,lmstudio,media,ollama,openai,processing,server]; extra == "lightweight"
71
+ Requires-Dist: abstractcore[anthropic,embeddings,lmstudio,media,ollama,openai,processing,server,tools]; extra == "lightweight"
66
72
  Provides-Extra: dev
67
73
  Requires-Dist: pytest>=7.0.0; extra == "dev"
68
74
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -89,7 +95,7 @@ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
89
95
  Requires-Dist: mkdocstrings[python]>=0.22.0; extra == "docs"
90
96
  Requires-Dist: mkdocs-autorefs>=0.4.0; extra == "docs"
91
97
  Provides-Extra: full-dev
92
- Requires-Dist: abstractcore[all-providers,dev,docs,test]; extra == "full-dev"
98
+ Requires-Dist: abstractcore[all-providers,dev,docs,test,tools]; extra == "full-dev"
93
99
  Dynamic: license-file
94
100
 
95
101
  # AbstractCore
@@ -155,6 +161,45 @@ response = llm.generate(
155
161
  print(response.content)
156
162
  ```
157
163
 
164
+ ### Response Object (GenerateResponse)
165
+
166
+ Every LLM generation returns a **GenerateResponse** object with consistent structure across all providers:
167
+
168
+ ```python
169
+ from abstractcore import create_llm
170
+
171
+ llm = create_llm("openai", model="gpt-4o-mini")
172
+ response = llm.generate("Explain quantum computing in simple terms")
173
+
174
+ # Core response data
175
+ print(f"Content: {response.content}") # Generated text
176
+ print(f"Model: {response.model}") # Model used
177
+ print(f"Finish reason: {response.finish_reason}") # Why generation stopped
178
+
179
+ # Consistent token access across ALL providers (NEW in v2.4.7)
180
+ print(f"Input tokens: {response.input_tokens}") # Always available
181
+ print(f"Output tokens: {response.output_tokens}") # Always available
182
+ print(f"Total tokens: {response.total_tokens}") # Always available
183
+
184
+ # Generation time tracking (NEW in v2.4.7)
185
+ print(f"Generation time: {response.gen_time}ms") # Always available (rounded to 1 decimal)
186
+
187
+ # Advanced access
188
+ print(f"Tool calls: {response.tool_calls}") # Tools executed (if any)
189
+ print(f"Raw usage: {response.usage}") # Provider-specific token data
190
+ print(f"Metadata: {response.metadata}") # Additional context
191
+
192
+ # Comprehensive summary
193
+ print(f"Summary: {response.get_summary()}") # "Model: gpt-4o-mini | Tokens: 117 | Time: 1234.5ms"
194
+ ```
195
+
196
+ **Token Count Sources:**
197
+ - **Provider APIs**: OpenAI, Anthropic, LMStudio (native API token counts)
198
+ - **AbstractCore Calculation**: MLX, HuggingFace, Mock (using `token_utils.py`)
199
+ - **Mixed Sources**: Ollama (combination of provider and calculated tokens)
200
+
201
+ **Backward Compatibility**: Legacy `prompt_tokens` and `completion_tokens` keys remain available in `response.usage` dictionary.
202
+
158
203
  ### Built-in Tools
159
204
 
160
205
  AbstractCore includes a comprehensive set of ready-to-use tools for common tasks:
@@ -271,6 +316,7 @@ response = llm.generate(
271
316
  - **Session Management**: Persistent conversations with metadata, analytics, and complete serialization
272
317
  - **Structured Responses**: Clean, predictable output formats with Pydantic
273
318
  - **Streaming Support**: Real-time token generation for interactive experiences
319
+ - **Consistent Token Terminology**: Unified `input_tokens`, `output_tokens`, `total_tokens` across all providers
274
320
  - **Embeddings**: Built-in support for semantic search and RAG applications
275
321
  - **Universal Server**: Optional OpenAI-compatible API server with `/v1/responses` endpoint
276
322
 
@@ -61,6 +61,45 @@ response = llm.generate(
61
61
  print(response.content)
62
62
  ```
63
63
 
64
+ ### Response Object (GenerateResponse)
65
+
66
+ Every LLM generation returns a **GenerateResponse** object with consistent structure across all providers:
67
+
68
+ ```python
69
+ from abstractcore import create_llm
70
+
71
+ llm = create_llm("openai", model="gpt-4o-mini")
72
+ response = llm.generate("Explain quantum computing in simple terms")
73
+
74
+ # Core response data
75
+ print(f"Content: {response.content}") # Generated text
76
+ print(f"Model: {response.model}") # Model used
77
+ print(f"Finish reason: {response.finish_reason}") # Why generation stopped
78
+
79
+ # Consistent token access across ALL providers (NEW in v2.4.7)
80
+ print(f"Input tokens: {response.input_tokens}") # Always available
81
+ print(f"Output tokens: {response.output_tokens}") # Always available
82
+ print(f"Total tokens: {response.total_tokens}") # Always available
83
+
84
+ # Generation time tracking (NEW in v2.4.7)
85
+ print(f"Generation time: {response.gen_time}ms") # Always available (rounded to 1 decimal)
86
+
87
+ # Advanced access
88
+ print(f"Tool calls: {response.tool_calls}") # Tools executed (if any)
89
+ print(f"Raw usage: {response.usage}") # Provider-specific token data
90
+ print(f"Metadata: {response.metadata}") # Additional context
91
+
92
+ # Comprehensive summary
93
+ print(f"Summary: {response.get_summary()}") # "Model: gpt-4o-mini | Tokens: 117 | Time: 1234.5ms"
94
+ ```
95
+
96
+ **Token Count Sources:**
97
+ - **Provider APIs**: OpenAI, Anthropic, LMStudio (native API token counts)
98
+ - **AbstractCore Calculation**: MLX, HuggingFace, Mock (using `token_utils.py`)
99
+ - **Mixed Sources**: Ollama (combination of provider and calculated tokens)
100
+
101
+ **Backward Compatibility**: Legacy `prompt_tokens` and `completion_tokens` keys remain available in `response.usage` dictionary.
102
+
64
103
  ### Built-in Tools
65
104
 
66
105
  AbstractCore includes a comprehensive set of ready-to-use tools for common tasks:
@@ -177,6 +216,7 @@ response = llm.generate(
177
216
  - **Session Management**: Persistent conversations with metadata, analytics, and complete serialization
178
217
  - **Structured Responses**: Clean, predictable output formats with Pydantic
179
218
  - **Streaming Support**: Real-time token generation for interactive experiences
219
+ - **Consistent Token Terminology**: Unified `input_tokens`, `output_tokens`, `total_tokens` across all providers
180
220
  - **Embeddings**: Built-in support for semantic search and RAG applications
181
221
  - **Universal Server**: Optional OpenAI-compatible API server with `/v1/responses` endpoint
182
222
 
@@ -44,6 +44,9 @@ except ImportError:
44
44
  from .processing import BasicSummarizer, SummaryStyle, SummaryLength, BasicExtractor
45
45
  _has_processing = True
46
46
 
47
+ # Tools module (core functionality)
48
+ from .tools import tool
49
+
47
50
  __all__ = [
48
51
  'create_llm',
49
52
  'BasicSession',
@@ -54,7 +57,8 @@ __all__ = [
54
57
  'MessageRole',
55
58
  'ModelNotFoundError',
56
59
  'ProviderAPIError',
57
- 'AuthenticationError'
60
+ 'AuthenticationError',
61
+ 'tool'
58
62
  ]
59
63
 
60
64
  if _has_embeddings:
@@ -109,7 +109,7 @@
109
109
  "tokens_before": { "type": "integer" },
110
110
  "tokens_after": { "type": "integer" },
111
111
  "compression_ratio": { "type": "number" },
112
- "generation_time_ms": { "type": "number" }
112
+ "gen_time": { "type": "number" }
113
113
  }
114
114
  }
115
115
  },
@@ -760,7 +760,7 @@ class BasicSession:
760
760
  "tokens_before": original_tokens,
761
761
  "tokens_after": self._estimate_tokens_for_summary(summary_result.summary),
762
762
  "compression_ratio": self._calculate_compression_ratio(original_tokens, summary_result.summary),
763
- "generation_time_ms": duration_ms
763
+ "gen_time": duration_ms
764
764
  }
765
765
  }
766
766
 
@@ -91,6 +91,7 @@ class GenerateResponse:
91
91
  usage: Optional[Dict[str, int]] = None
92
92
  tool_calls: Optional[List[Dict[str, Any]]] = None
93
93
  metadata: Optional[Dict[str, Any]] = None
94
+ gen_time: Optional[float] = None # Generation time in milliseconds
94
95
 
95
96
  def has_tool_calls(self) -> bool:
96
97
  """Check if response contains tool calls"""
@@ -109,6 +110,29 @@ class GenerateResponse:
109
110
  parts.append(f"Model: {self.model}")
110
111
  if self.usage:
111
112
  parts.append(f"Tokens: {self.usage.get('total_tokens', 'unknown')}")
113
+ if self.gen_time:
114
+ parts.append(f"Time: {self.gen_time:.1f}ms")
112
115
  if self.tool_calls:
113
116
  parts.append(f"Tools: {len(self.tool_calls)} executed")
114
- return " | ".join(parts)
117
+ return " | ".join(parts)
118
+
119
+ @property
120
+ def input_tokens(self) -> Optional[int]:
121
+ """Get input tokens with consistent terminology (prompt_tokens or input_tokens)."""
122
+ if not self.usage:
123
+ return None
124
+ return self.usage.get('input_tokens') or self.usage.get('prompt_tokens')
125
+
126
+ @property
127
+ def output_tokens(self) -> Optional[int]:
128
+ """Get output tokens with consistent terminology (completion_tokens or output_tokens)."""
129
+ if not self.usage:
130
+ return None
131
+ return self.usage.get('output_tokens') or self.usage.get('completion_tokens')
132
+
133
+ @property
134
+ def total_tokens(self) -> Optional[int]:
135
+ """Get total tokens."""
136
+ if not self.usage:
137
+ return None
138
+ return self.usage.get('total_tokens')
@@ -186,8 +186,14 @@ class AnthropicProvider(BaseProvider):
186
186
  if stream:
187
187
  return self._stream_response(call_params, tools)
188
188
  else:
189
+ # Track generation time
190
+ start_time = time.time()
189
191
  response = self.client.messages.create(**call_params)
192
+ gen_time = round((time.time() - start_time) * 1000, 1)
193
+
190
194
  formatted = self._format_response(response)
195
+ # Add generation time to response
196
+ formatted.gen_time = gen_time
191
197
 
192
198
  # Handle tool execution for Anthropic responses
193
199
  if tools and (formatted.has_tool_calls() or
@@ -863,6 +863,9 @@ class HuggingFaceProvider(BaseProvider):
863
863
  if torch.cuda.is_available():
864
864
  torch.cuda.manual_seed_all(seed)
865
865
 
866
+ # Track generation time
867
+ start_time = time.time()
868
+
866
869
  outputs = self.pipeline(
867
870
  input_text,
868
871
  max_new_tokens=max_new_tokens,
@@ -874,6 +877,8 @@ class HuggingFaceProvider(BaseProvider):
874
877
  truncation=True,
875
878
  return_full_text=False
876
879
  )
880
+
881
+ gen_time = round((time.time() - start_time) * 1000, 1)
877
882
 
878
883
  if outputs and len(outputs) > 0:
879
884
  response_text = outputs[0]['generated_text'].strip()
@@ -885,34 +890,41 @@ class HuggingFaceProvider(BaseProvider):
885
890
  content=response_text,
886
891
  model=self.model,
887
892
  finish_reason="stop",
888
- usage=usage
893
+ usage=usage,
894
+ gen_time=gen_time
889
895
  )
890
896
  else:
891
897
  return GenerateResponse(
892
898
  content="",
893
899
  model=self.model,
894
- finish_reason="stop"
900
+ finish_reason="stop",
901
+ gen_time=gen_time
895
902
  )
896
903
 
897
904
  except Exception as e:
905
+ gen_time = round((time.time() - start_time) * 1000, 1) if 'start_time' in locals() else 0.0
898
906
  return GenerateResponse(
899
907
  content=f"Error: {str(e)}",
900
908
  model=self.model,
901
- finish_reason="error"
909
+ finish_reason="error",
910
+ gen_time=gen_time
902
911
  )
903
912
 
904
913
  def _calculate_usage(self, prompt: str, response: str) -> Dict[str, int]:
905
914
  """Calculate token usage using centralized token utilities."""
906
915
  from ..utils.token_utils import TokenUtils
907
916
 
908
- prompt_tokens = TokenUtils.estimate_tokens(prompt, self.model)
909
- completion_tokens = TokenUtils.estimate_tokens(response, self.model)
910
- total_tokens = prompt_tokens + completion_tokens
917
+ input_tokens = TokenUtils.estimate_tokens(prompt, self.model)
918
+ output_tokens = TokenUtils.estimate_tokens(response, self.model)
919
+ total_tokens = input_tokens + output_tokens
911
920
 
912
921
  return {
913
- "prompt_tokens": prompt_tokens,
914
- "completion_tokens": completion_tokens,
915
- "total_tokens": total_tokens
922
+ "input_tokens": input_tokens,
923
+ "output_tokens": output_tokens,
924
+ "total_tokens": total_tokens,
925
+ # Keep legacy keys for backward compatibility
926
+ "prompt_tokens": input_tokens,
927
+ "completion_tokens": output_tokens
916
928
  }
917
929
 
918
930
  def _stream_generate_transformers(self, input_text: str, max_new_tokens: int,
@@ -4,6 +4,7 @@ LM Studio provider implementation (OpenAI-compatible API).
4
4
 
5
5
  import httpx
6
6
  import json
7
+ import time
7
8
  from typing import List, Dict, Any, Optional, Union, Iterator, Type
8
9
 
9
10
  try:
@@ -225,12 +226,15 @@ class LMStudioProvider(BaseProvider):
225
226
  if not hasattr(self, 'client') or self.client is None:
226
227
  raise ProviderAPIError("HTTP client not initialized")
227
228
 
229
+ # Track generation time
230
+ start_time = time.time()
228
231
  response = self.client.post(
229
232
  f"{self.base_url}/chat/completions",
230
233
  json=payload,
231
234
  headers={"Content-Type": "application/json"}
232
235
  )
233
236
  response.raise_for_status()
237
+ gen_time = round((time.time() - start_time) * 1000, 1)
234
238
 
235
239
  result = response.json()
236
240
 
@@ -252,10 +256,14 @@ class LMStudioProvider(BaseProvider):
252
256
  finish_reason=finish_reason,
253
257
  raw_response=result,
254
258
  usage={
259
+ "input_tokens": usage.get("prompt_tokens", 0),
260
+ "output_tokens": usage.get("completion_tokens", 0),
261
+ "total_tokens": usage.get("total_tokens", 0),
262
+ # Keep legacy keys for backward compatibility
255
263
  "prompt_tokens": usage.get("prompt_tokens", 0),
256
- "completion_tokens": usage.get("completion_tokens", 0),
257
- "total_tokens": usage.get("total_tokens", 0)
258
- }
264
+ "completion_tokens": usage.get("completion_tokens", 0)
265
+ },
266
+ gen_time=gen_time
259
267
  )
260
268
 
261
269
  except AttributeError as e:
@@ -266,6 +266,9 @@ class MLXProvider(BaseProvider):
266
266
  mx.random.seed(seed)
267
267
  self.logger.debug(f"Set MLX random seed to {seed} for deterministic generation")
268
268
 
269
+ # Track generation time
270
+ start_time = time.time()
271
+
269
272
  # Try different MLX API signatures
270
273
  try:
271
274
  # Try new mlx-lm API
@@ -288,6 +291,8 @@ class MLXProvider(BaseProvider):
288
291
  # Fallback to basic response
289
292
  response_text = prompt + " I am an AI assistant powered by MLX on Apple Silicon."
290
293
 
294
+ gen_time = round((time.time() - start_time) * 1000, 1)
295
+
291
296
  # Use the full response as-is - preserve all content including thinking
292
297
  generated = response_text.strip()
293
298
 
@@ -295,21 +300,25 @@ class MLXProvider(BaseProvider):
295
300
  content=generated,
296
301
  model=self.model,
297
302
  finish_reason="stop",
298
- usage=self._calculate_usage(prompt, generated)
303
+ usage=self._calculate_usage(prompt, generated),
304
+ gen_time=gen_time
299
305
  )
300
306
 
301
307
  def _calculate_usage(self, prompt: str, response: str) -> Dict[str, int]:
302
308
  """Calculate token usage using centralized token utilities."""
303
309
  from ..utils.token_utils import TokenUtils
304
310
 
305
- prompt_tokens = TokenUtils.estimate_tokens(prompt, self.model)
306
- completion_tokens = TokenUtils.estimate_tokens(response, self.model)
307
- total_tokens = prompt_tokens + completion_tokens
311
+ input_tokens = TokenUtils.estimate_tokens(prompt, self.model)
312
+ output_tokens = TokenUtils.estimate_tokens(response, self.model)
313
+ total_tokens = input_tokens + output_tokens
308
314
 
309
315
  return {
310
- "prompt_tokens": prompt_tokens,
311
- "completion_tokens": completion_tokens,
312
- "total_tokens": total_tokens
316
+ "input_tokens": input_tokens,
317
+ "output_tokens": output_tokens,
318
+ "total_tokens": total_tokens,
319
+ # Keep legacy keys for backward compatibility
320
+ "prompt_tokens": input_tokens,
321
+ "completion_tokens": output_tokens
313
322
  }
314
323
 
315
324
  def _stream_generate(self, prompt: str, max_tokens: int, temperature: float, top_p: float, tool_call_tags: Optional[str] = None, seed: Optional[int] = None) -> Iterator[GenerateResponse]:
@@ -48,6 +48,12 @@ class MockProvider(BaseProvider):
48
48
 
49
49
  def _single_response(self, prompt: str, response_model: Optional[Type[BaseModel]] = None) -> GenerateResponse:
50
50
  """Generate single mock response"""
51
+ import time
52
+
53
+ # Simulate generation time (10-100ms for mock)
54
+ start_time = time.time()
55
+ time.sleep(0.01 + (len(prompt) % 10) * 0.01) # 10-100ms based on prompt length
56
+ gen_time = round((time.time() - start_time) * 1000, 1)
51
57
 
52
58
  if response_model and PYDANTIC_AVAILABLE:
53
59
  # Generate valid JSON for structured output
@@ -59,21 +65,25 @@ class MockProvider(BaseProvider):
59
65
  content=content,
60
66
  model=self.model,
61
67
  finish_reason="stop",
62
- usage=self._calculate_mock_usage(prompt, content)
68
+ usage=self._calculate_mock_usage(prompt, content),
69
+ gen_time=gen_time
63
70
  )
64
71
 
65
72
  def _calculate_mock_usage(self, prompt: str, response: str) -> Dict[str, int]:
66
73
  """Calculate mock token usage using centralized token utilities."""
67
74
  from ..utils.token_utils import TokenUtils
68
75
 
69
- prompt_tokens = TokenUtils.estimate_tokens(prompt, self.model)
70
- completion_tokens = TokenUtils.estimate_tokens(response, self.model)
71
- total_tokens = prompt_tokens + completion_tokens
76
+ input_tokens = TokenUtils.estimate_tokens(prompt, self.model)
77
+ output_tokens = TokenUtils.estimate_tokens(response, self.model)
78
+ total_tokens = input_tokens + output_tokens
72
79
 
73
80
  return {
74
- "prompt_tokens": prompt_tokens,
75
- "completion_tokens": completion_tokens,
76
- "total_tokens": total_tokens
81
+ "input_tokens": input_tokens,
82
+ "output_tokens": output_tokens,
83
+ "total_tokens": total_tokens,
84
+ # Keep legacy keys for backward compatibility
85
+ "prompt_tokens": input_tokens,
86
+ "completion_tokens": output_tokens
77
87
  }
78
88
 
79
89
  def _stream_response(self, prompt: str) -> Iterator[GenerateResponse]:
@@ -225,11 +225,14 @@ class OllamaProvider(BaseProvider):
225
225
  def _single_generate(self, endpoint: str, payload: Dict[str, Any], tools: Optional[List[Dict[str, Any]]] = None) -> GenerateResponse:
226
226
  """Generate single response"""
227
227
  try:
228
+ # Track generation time
229
+ start_time = time.time()
228
230
  response = self.client.post(
229
231
  f"{self.base_url}{endpoint}",
230
232
  json=payload
231
233
  )
232
234
  response.raise_for_status()
235
+ gen_time = round((time.time() - start_time) * 1000, 1)
233
236
 
234
237
  result = response.json()
235
238
 
@@ -246,10 +249,14 @@ class OllamaProvider(BaseProvider):
246
249
  finish_reason="stop",
247
250
  raw_response=result,
248
251
  usage={
252
+ "input_tokens": result.get("prompt_eval_count", 0),
253
+ "output_tokens": result.get("eval_count", 0),
254
+ "total_tokens": result.get("prompt_eval_count", 0) + result.get("eval_count", 0),
255
+ # Keep legacy keys for backward compatibility
249
256
  "prompt_tokens": result.get("prompt_eval_count", 0),
250
- "completion_tokens": result.get("eval_count", 0),
251
- "total_tokens": result.get("prompt_eval_count", 0) + result.get("eval_count", 0)
252
- }
257
+ "completion_tokens": result.get("eval_count", 0)
258
+ },
259
+ gen_time=gen_time
253
260
  )
254
261
 
255
262
  # Execute tools if enabled and tools are present
@@ -169,8 +169,14 @@ class OpenAIProvider(BaseProvider):
169
169
  if stream:
170
170
  return self._stream_response(call_params, tools)
171
171
  else:
172
+ # Track generation time
173
+ start_time = time.time()
172
174
  response = self.client.chat.completions.create(**call_params)
175
+ gen_time = round((time.time() - start_time) * 1000, 1)
176
+
173
177
  formatted = self._format_response(response)
178
+ # Add generation time to response
179
+ formatted.gen_time = gen_time
174
180
 
175
181
  # Handle tool execution for OpenAI native responses
176
182
  if tools and formatted.has_tool_calls():
@@ -216,13 +222,16 @@ class OpenAIProvider(BaseProvider):
216
222
  "arguments": tc.function.arguments
217
223
  })
218
224
 
219
- # Build usage dict with detailed breakdown
225
+ # Build usage dict with consistent terminology
220
226
  usage = None
221
227
  if hasattr(response, 'usage'):
222
228
  usage = {
229
+ "input_tokens": response.usage.prompt_tokens,
230
+ "output_tokens": response.usage.completion_tokens,
231
+ "total_tokens": response.usage.total_tokens,
232
+ # Keep legacy keys for backward compatibility
223
233
  "prompt_tokens": response.usage.prompt_tokens,
224
- "completion_tokens": response.usage.completion_tokens,
225
- "total_tokens": response.usage.total_tokens
234
+ "completion_tokens": response.usage.completion_tokens
226
235
  }
227
236
 
228
237
  # Add detailed token breakdown for reasoning models
@@ -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.4.6"
14
+ __version__ = "2.4.7"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: abstractcore
3
- Version: 2.4.6
3
+ Version: 2.4.7
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>
@@ -29,6 +29,7 @@ License-File: LICENSE
29
29
  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
+ Requires-Dist: requests<3.0.0,>=2.25.0
32
33
  Provides-Extra: openai
33
34
  Requires-Dist: openai<2.0.0,>=1.0.0; extra == "openai"
34
35
  Provides-Extra: anthropic
@@ -46,6 +47,11 @@ Provides-Extra: embeddings
46
47
  Requires-Dist: sentence-transformers<4.0.0,>=2.7.0; extra == "embeddings"
47
48
  Requires-Dist: numpy<2.0.0,>=1.20.0; extra == "embeddings"
48
49
  Provides-Extra: processing
50
+ Provides-Extra: tools
51
+ Requires-Dist: beautifulsoup4<5.0.0,>=4.12.0; extra == "tools"
52
+ Requires-Dist: lxml<6.0.0,>=4.9.0; extra == "tools"
53
+ Requires-Dist: duckduckgo-search<4.0.0,>=3.8.0; extra == "tools"
54
+ Requires-Dist: psutil<6.0.0,>=5.9.0; extra == "tools"
49
55
  Provides-Extra: media
50
56
  Requires-Dist: Pillow<12.0.0,>=10.0.0; extra == "media"
51
57
  Requires-Dist: pymupdf4llm<1.0.0,>=0.0.20; extra == "media"
@@ -60,9 +66,9 @@ Requires-Dist: abstractcore[huggingface]; extra == "heavy-providers"
60
66
  Provides-Extra: all-providers
61
67
  Requires-Dist: abstractcore[anthropic,embeddings,huggingface,lmstudio,mlx,ollama,openai]; extra == "all-providers"
62
68
  Provides-Extra: all
63
- Requires-Dist: abstractcore[anthropic,dev,docs,embeddings,huggingface,lmstudio,media,mlx,ollama,openai,processing,server,test]; extra == "all"
69
+ Requires-Dist: abstractcore[anthropic,dev,docs,embeddings,huggingface,lmstudio,media,mlx,ollama,openai,processing,server,test,tools]; extra == "all"
64
70
  Provides-Extra: lightweight
65
- Requires-Dist: abstractcore[anthropic,embeddings,lmstudio,media,ollama,openai,processing,server]; extra == "lightweight"
71
+ Requires-Dist: abstractcore[anthropic,embeddings,lmstudio,media,ollama,openai,processing,server,tools]; extra == "lightweight"
66
72
  Provides-Extra: dev
67
73
  Requires-Dist: pytest>=7.0.0; extra == "dev"
68
74
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -89,7 +95,7 @@ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
89
95
  Requires-Dist: mkdocstrings[python]>=0.22.0; extra == "docs"
90
96
  Requires-Dist: mkdocs-autorefs>=0.4.0; extra == "docs"
91
97
  Provides-Extra: full-dev
92
- Requires-Dist: abstractcore[all-providers,dev,docs,test]; extra == "full-dev"
98
+ Requires-Dist: abstractcore[all-providers,dev,docs,test,tools]; extra == "full-dev"
93
99
  Dynamic: license-file
94
100
 
95
101
  # AbstractCore
@@ -155,6 +161,45 @@ response = llm.generate(
155
161
  print(response.content)
156
162
  ```
157
163
 
164
+ ### Response Object (GenerateResponse)
165
+
166
+ Every LLM generation returns a **GenerateResponse** object with consistent structure across all providers:
167
+
168
+ ```python
169
+ from abstractcore import create_llm
170
+
171
+ llm = create_llm("openai", model="gpt-4o-mini")
172
+ response = llm.generate("Explain quantum computing in simple terms")
173
+
174
+ # Core response data
175
+ print(f"Content: {response.content}") # Generated text
176
+ print(f"Model: {response.model}") # Model used
177
+ print(f"Finish reason: {response.finish_reason}") # Why generation stopped
178
+
179
+ # Consistent token access across ALL providers (NEW in v2.4.7)
180
+ print(f"Input tokens: {response.input_tokens}") # Always available
181
+ print(f"Output tokens: {response.output_tokens}") # Always available
182
+ print(f"Total tokens: {response.total_tokens}") # Always available
183
+
184
+ # Generation time tracking (NEW in v2.4.7)
185
+ print(f"Generation time: {response.gen_time}ms") # Always available (rounded to 1 decimal)
186
+
187
+ # Advanced access
188
+ print(f"Tool calls: {response.tool_calls}") # Tools executed (if any)
189
+ print(f"Raw usage: {response.usage}") # Provider-specific token data
190
+ print(f"Metadata: {response.metadata}") # Additional context
191
+
192
+ # Comprehensive summary
193
+ print(f"Summary: {response.get_summary()}") # "Model: gpt-4o-mini | Tokens: 117 | Time: 1234.5ms"
194
+ ```
195
+
196
+ **Token Count Sources:**
197
+ - **Provider APIs**: OpenAI, Anthropic, LMStudio (native API token counts)
198
+ - **AbstractCore Calculation**: MLX, HuggingFace, Mock (using `token_utils.py`)
199
+ - **Mixed Sources**: Ollama (combination of provider and calculated tokens)
200
+
201
+ **Backward Compatibility**: Legacy `prompt_tokens` and `completion_tokens` keys remain available in `response.usage` dictionary.
202
+
158
203
  ### Built-in Tools
159
204
 
160
205
  AbstractCore includes a comprehensive set of ready-to-use tools for common tasks:
@@ -271,6 +316,7 @@ response = llm.generate(
271
316
  - **Session Management**: Persistent conversations with metadata, analytics, and complete serialization
272
317
  - **Structured Responses**: Clean, predictable output formats with Pydantic
273
318
  - **Streaming Support**: Real-time token generation for interactive experiences
319
+ - **Consistent Token Terminology**: Unified `input_tokens`, `output_tokens`, `total_tokens` across all providers
274
320
  - **Embeddings**: Built-in support for semantic search and RAG applications
275
321
  - **Universal Server**: Optional OpenAI-compatible API server with `/v1/responses` endpoint
276
322