abstractcore 2.4.9__tar.gz → 2.5.0__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.
- {abstractcore-2.4.9 → abstractcore-2.5.0}/PKG-INFO +5 -1
- {abstractcore-2.4.9 → abstractcore-2.5.0}/README.md +4 -0
- abstractcore-2.5.0/abstractcore/config/__init__.py +10 -0
- {abstractcore-2.4.9/abstractcore/cli → abstractcore-2.5.0/abstractcore/config}/main.py +11 -0
- abstractcore-2.5.0/abstractcore/config/manager.py +344 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/utils/version.py +1 -1
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore.egg-info/PKG-INFO +5 -1
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore.egg-info/SOURCES.txt +4 -3
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore.egg-info/entry_points.txt +2 -2
- {abstractcore-2.4.9 → abstractcore-2.5.0}/pyproject.toml +3 -3
- abstractcore-2.4.9/abstractcore/cli/__init__.py +0 -9
- {abstractcore-2.4.9 → abstractcore-2.5.0}/LICENSE +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/apps/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/apps/__main__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/apps/app_config_utils.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/apps/extractor.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/apps/judge.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/apps/summarizer.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/architectures/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/architectures/detection.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/architectures/enums.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/assets/architecture_formats.json +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/assets/model_capabilities.json +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/assets/session_schema.json +0 -0
- {abstractcore-2.4.9/abstractcore/cli → abstractcore-2.5.0/abstractcore/config}/vision_config.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/core/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/core/enums.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/core/factory.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/core/interface.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/core/retry.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/core/session.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/core/types.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/embeddings/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/embeddings/manager.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/embeddings/models.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/events/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/exceptions/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/auto_handler.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/base.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/capabilities.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/handlers/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/handlers/anthropic_handler.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/handlers/local_handler.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/handlers/openai_handler.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/processors/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/processors/image_processor.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/processors/office_processor.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/processors/pdf_processor.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/processors/text_processor.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/types.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/utils/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/utils/image_scaler.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/media/vision_fallback.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/processing/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/processing/basic_extractor.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/processing/basic_judge.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/processing/basic_summarizer.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/providers/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/providers/anthropic_provider.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/providers/base.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/providers/huggingface_provider.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/providers/lmstudio_provider.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/providers/mlx_provider.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/providers/ollama_provider.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/providers/openai_provider.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/providers/registry.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/providers/streaming.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/server/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/server/app.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/structured/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/structured/handler.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/structured/retry.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/tools/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/tools/common_tools.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/tools/core.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/tools/handler.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/tools/parser.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/tools/registry.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/tools/syntax_rewriter.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/tools/tag_rewriter.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/utils/__init__.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/utils/cli.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/utils/message_preprocessor.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/utils/self_fixes.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/utils/structured_logging.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore/utils/token_utils.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore.egg-info/dependency_links.txt +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore.egg-info/requires.txt +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/abstractcore.egg-info/top_level.txt +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/setup.cfg +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_agentic_cli_compatibility.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_all_specified_providers.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_basic_session.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_basic_summarizer.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_cli_media.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_complete_integration.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_comprehensive_events.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_consistency.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_core_components.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_critical_streaming_tool_fix.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_debug_server.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_direct_vs_server.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_embeddings.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_embeddings_integration.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_embeddings_llm_integration.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_embeddings_matrix_operations.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_embeddings_no_mock.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_embeddings_real.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_embeddings_semantic_validation.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_embeddings_simple.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_enhanced_prompt.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_environment_variable_tool_call_tags.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_factory.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_final_accuracy.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_final_comprehensive.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_final_graceful_errors.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_fixed_media.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_fixed_prompt.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_graceful_fallback.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_import_debug.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_integrated_functionality.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_lmstudio_context.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_media_import.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_media_server.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_ollama_tool_role_fix.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_openai_conversion_manual.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_openai_format_bug.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_openai_format_conversion.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_openai_media_integration.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_progressive_complexity.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_provider_basic_session.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_provider_connectivity.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_provider_simple_generation.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_provider_streaming.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_provider_token_translation.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_provider_tool_detection.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_providers.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_providers_comprehensive.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_providers_simple.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_real_models_comprehensive.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_retry_observability.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_retry_strategy.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_seed_determinism.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_seed_temperature_basic.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_sensory_prompting.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_server_debug.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_server_embeddings_real.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_server_integration.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_stream_tool_calling.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_streaming_enhancements.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_streaming_tag_rewriting.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_structured_integration.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_structured_output.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_syntax_rewriter.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_text_only_model_experience.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_tool_calling.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_tool_execution_separation.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_unified_streaming.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_unload_memory.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_user_scenario_validation.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_vision_accuracy.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_vision_comprehensive.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/tests/test_vision_fallback_improvement.py +0 -0
- {abstractcore-2.4.9 → abstractcore-2.5.0}/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.
|
|
3
|
+
Version: 2.5.0
|
|
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>
|
|
@@ -551,6 +551,10 @@ abstractcore --set-console-log-level NONE # Disable console logging
|
|
|
551
551
|
abstractcore --enable-file-logging # Save logs to files
|
|
552
552
|
abstractcore --enable-debug-logging # Full debug mode
|
|
553
553
|
|
|
554
|
+
# Configure vision for image analysis with text-only models
|
|
555
|
+
abstractcore --set-vision-provider ollama qwen2.5vl:7b
|
|
556
|
+
abstractcore --set-vision-provider lmstudio qwen/qwen3-vl-4b
|
|
557
|
+
|
|
554
558
|
# Set API keys as needed
|
|
555
559
|
abstractcore --set-api-key openai sk-your-key-here
|
|
556
560
|
abstractcore --set-api-key anthropic your-anthropic-key
|
|
@@ -451,6 +451,10 @@ abstractcore --set-console-log-level NONE # Disable console logging
|
|
|
451
451
|
abstractcore --enable-file-logging # Save logs to files
|
|
452
452
|
abstractcore --enable-debug-logging # Full debug mode
|
|
453
453
|
|
|
454
|
+
# Configure vision for image analysis with text-only models
|
|
455
|
+
abstractcore --set-vision-provider ollama qwen2.5vl:7b
|
|
456
|
+
abstractcore --set-vision-provider lmstudio qwen/qwen3-vl-4b
|
|
457
|
+
|
|
454
458
|
# Set API keys as needed
|
|
455
459
|
abstractcore --set-api-key openai sk-your-key-here
|
|
456
460
|
abstractcore --set-api-key anthropic your-anthropic-key
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AbstractCore Configuration Module
|
|
3
|
+
|
|
4
|
+
Provides configuration management and command-line interface for AbstractCore.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .vision_config import handle_vision_commands, add_vision_arguments
|
|
8
|
+
from .manager import get_config_manager
|
|
9
|
+
|
|
10
|
+
__all__ = ['handle_vision_commands', 'add_vision_arguments', 'get_config_manager']
|
|
@@ -264,6 +264,11 @@ def add_arguments(parser: argparse.ArgumentParser):
|
|
|
264
264
|
|
|
265
265
|
def print_status():
|
|
266
266
|
"""Print comprehensive configuration status with improved readability."""
|
|
267
|
+
if not CONFIG_AVAILABLE or get_config_manager is None:
|
|
268
|
+
print("❌ Configuration system not available")
|
|
269
|
+
print("💡 The AbstractCore configuration module is missing")
|
|
270
|
+
return
|
|
271
|
+
|
|
267
272
|
config_manager = get_config_manager()
|
|
268
273
|
status = config_manager.get_status()
|
|
269
274
|
|
|
@@ -494,6 +499,12 @@ def interactive_configure():
|
|
|
494
499
|
|
|
495
500
|
def handle_commands(args) -> bool:
|
|
496
501
|
"""Handle AbstractCore configuration commands."""
|
|
502
|
+
if not CONFIG_AVAILABLE or get_config_manager is None:
|
|
503
|
+
print("❌ Error: Configuration system not available")
|
|
504
|
+
print("💡 The AbstractCore configuration module is missing or not properly installed")
|
|
505
|
+
print("💡 Please reinstall AbstractCore or check your installation")
|
|
506
|
+
return True
|
|
507
|
+
|
|
497
508
|
config_manager = get_config_manager()
|
|
498
509
|
handled = False
|
|
499
510
|
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AbstractCore Configuration Manager
|
|
3
|
+
|
|
4
|
+
Provides centralized configuration management for AbstractCore.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, Any, Optional, Tuple
|
|
11
|
+
from dataclasses import dataclass, asdict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class VisionConfig:
|
|
16
|
+
"""Vision configuration settings."""
|
|
17
|
+
strategy: str = "disabled"
|
|
18
|
+
caption_provider: Optional[str] = None
|
|
19
|
+
caption_model: Optional[str] = None
|
|
20
|
+
fallback_chain: list = None
|
|
21
|
+
local_models_path: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
def __post_init__(self):
|
|
24
|
+
if self.fallback_chain is None:
|
|
25
|
+
self.fallback_chain = []
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class EmbeddingsConfig:
|
|
30
|
+
"""Embeddings configuration settings."""
|
|
31
|
+
provider: Optional[str] = "huggingface"
|
|
32
|
+
model: Optional[str] = "all-minilm-l6-v2"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class AppDefaults:
|
|
37
|
+
"""Per-application default configurations."""
|
|
38
|
+
cli_provider: Optional[str] = "huggingface"
|
|
39
|
+
cli_model: Optional[str] = "unsloth/Qwen3-4B-Instruct-2507-GGUF"
|
|
40
|
+
summarizer_provider: Optional[str] = "huggingface"
|
|
41
|
+
summarizer_model: Optional[str] = "unsloth/Qwen3-4B-Instruct-2507-GGUF"
|
|
42
|
+
extractor_provider: Optional[str] = "huggingface"
|
|
43
|
+
extractor_model: Optional[str] = "unsloth/Qwen3-4B-Instruct-2507-GGUF"
|
|
44
|
+
judge_provider: Optional[str] = "huggingface"
|
|
45
|
+
judge_model: Optional[str] = "unsloth/Qwen3-4B-Instruct-2507-GGUF"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class DefaultModels:
|
|
50
|
+
"""Global default model configurations."""
|
|
51
|
+
global_provider: Optional[str] = None
|
|
52
|
+
global_model: Optional[str] = None
|
|
53
|
+
chat_model: Optional[str] = None
|
|
54
|
+
code_model: Optional[str] = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class ApiKeysConfig:
|
|
59
|
+
"""API keys configuration."""
|
|
60
|
+
openai: Optional[str] = None
|
|
61
|
+
anthropic: Optional[str] = None
|
|
62
|
+
google: Optional[str] = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class CacheConfig:
|
|
67
|
+
"""Cache configuration settings."""
|
|
68
|
+
default_cache_dir: str = "~/.cache/abstractcore"
|
|
69
|
+
huggingface_cache_dir: str = "~/.cache/huggingface"
|
|
70
|
+
local_models_cache_dir: str = "~/.abstractcore/models"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class LoggingConfig:
|
|
75
|
+
"""Logging configuration settings."""
|
|
76
|
+
console_level: str = "WARNING"
|
|
77
|
+
file_level: str = "DEBUG"
|
|
78
|
+
file_logging_enabled: bool = False
|
|
79
|
+
log_base_dir: Optional[str] = None
|
|
80
|
+
verbatim_enabled: bool = True
|
|
81
|
+
console_json: bool = False
|
|
82
|
+
file_json: bool = True
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class AbstractCoreConfig:
|
|
87
|
+
"""Main configuration class."""
|
|
88
|
+
vision: VisionConfig
|
|
89
|
+
embeddings: EmbeddingsConfig
|
|
90
|
+
app_defaults: AppDefaults
|
|
91
|
+
default_models: DefaultModels
|
|
92
|
+
api_keys: ApiKeysConfig
|
|
93
|
+
cache: CacheConfig
|
|
94
|
+
logging: LoggingConfig
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def default(cls):
|
|
98
|
+
"""Create default configuration."""
|
|
99
|
+
return cls(
|
|
100
|
+
vision=VisionConfig(),
|
|
101
|
+
embeddings=EmbeddingsConfig(),
|
|
102
|
+
app_defaults=AppDefaults(),
|
|
103
|
+
default_models=DefaultModels(),
|
|
104
|
+
api_keys=ApiKeysConfig(),
|
|
105
|
+
cache=CacheConfig(),
|
|
106
|
+
logging=LoggingConfig()
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ConfigurationManager:
|
|
111
|
+
"""Manages AbstractCore configuration."""
|
|
112
|
+
|
|
113
|
+
def __init__(self):
|
|
114
|
+
self.config_dir = Path.home() / ".abstractcore" / "config"
|
|
115
|
+
self.config_file = self.config_dir / "abstractcore.json"
|
|
116
|
+
self.config = self._load_config()
|
|
117
|
+
|
|
118
|
+
def _load_config(self) -> AbstractCoreConfig:
|
|
119
|
+
"""Load configuration from file or create default."""
|
|
120
|
+
if self.config_file.exists():
|
|
121
|
+
try:
|
|
122
|
+
with open(self.config_file, 'r') as f:
|
|
123
|
+
data = json.load(f)
|
|
124
|
+
return self._dict_to_config(data)
|
|
125
|
+
except Exception:
|
|
126
|
+
# If loading fails, return default config
|
|
127
|
+
return AbstractCoreConfig.default()
|
|
128
|
+
else:
|
|
129
|
+
return AbstractCoreConfig.default()
|
|
130
|
+
|
|
131
|
+
def _dict_to_config(self, data: Dict[str, Any]) -> AbstractCoreConfig:
|
|
132
|
+
"""Convert dictionary to config object."""
|
|
133
|
+
# Create config objects from dictionary data
|
|
134
|
+
vision = VisionConfig(**data.get('vision', {}))
|
|
135
|
+
embeddings = EmbeddingsConfig(**data.get('embeddings', {}))
|
|
136
|
+
app_defaults = AppDefaults(**data.get('app_defaults', {}))
|
|
137
|
+
default_models = DefaultModels(**data.get('default_models', {}))
|
|
138
|
+
api_keys = ApiKeysConfig(**data.get('api_keys', {}))
|
|
139
|
+
cache = CacheConfig(**data.get('cache', {}))
|
|
140
|
+
logging = LoggingConfig(**data.get('logging', {}))
|
|
141
|
+
|
|
142
|
+
return AbstractCoreConfig(
|
|
143
|
+
vision=vision,
|
|
144
|
+
embeddings=embeddings,
|
|
145
|
+
app_defaults=app_defaults,
|
|
146
|
+
default_models=default_models,
|
|
147
|
+
api_keys=api_keys,
|
|
148
|
+
cache=cache,
|
|
149
|
+
logging=logging
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def _save_config(self):
|
|
153
|
+
"""Save configuration to file."""
|
|
154
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
155
|
+
|
|
156
|
+
# Convert config to dictionary
|
|
157
|
+
config_dict = {
|
|
158
|
+
'vision': asdict(self.config.vision),
|
|
159
|
+
'embeddings': asdict(self.config.embeddings),
|
|
160
|
+
'app_defaults': asdict(self.config.app_defaults),
|
|
161
|
+
'default_models': asdict(self.config.default_models),
|
|
162
|
+
'api_keys': asdict(self.config.api_keys),
|
|
163
|
+
'cache': asdict(self.config.cache),
|
|
164
|
+
'logging': asdict(self.config.logging)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
with open(self.config_file, 'w') as f:
|
|
168
|
+
json.dump(config_dict, f, indent=2)
|
|
169
|
+
|
|
170
|
+
def set_vision_provider(self, provider: str, model: str) -> bool:
|
|
171
|
+
"""Set vision provider and model."""
|
|
172
|
+
try:
|
|
173
|
+
self.config.vision.strategy = "two_stage"
|
|
174
|
+
self.config.vision.caption_provider = provider
|
|
175
|
+
self.config.vision.caption_model = model
|
|
176
|
+
self._save_config()
|
|
177
|
+
return True
|
|
178
|
+
except Exception:
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
def set_vision_caption(self, model: str) -> bool:
|
|
182
|
+
"""Set vision caption model (deprecated)."""
|
|
183
|
+
# Auto-detect provider from model name
|
|
184
|
+
provider = self._detect_provider_from_model(model)
|
|
185
|
+
if provider:
|
|
186
|
+
return self.set_vision_provider(provider, model)
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
def _detect_provider_from_model(self, model: str) -> Optional[str]:
|
|
190
|
+
"""Detect provider from model name."""
|
|
191
|
+
model_lower = model.lower()
|
|
192
|
+
|
|
193
|
+
if any(x in model_lower for x in ['qwen2.5vl', 'llama3.2-vision', 'llava']):
|
|
194
|
+
return "ollama"
|
|
195
|
+
elif any(x in model_lower for x in ['gpt-4', 'gpt-4o']):
|
|
196
|
+
return "openai"
|
|
197
|
+
elif any(x in model_lower for x in ['claude-3']):
|
|
198
|
+
return "anthropic"
|
|
199
|
+
elif '/' in model:
|
|
200
|
+
return "lmstudio"
|
|
201
|
+
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
def get_status(self) -> Dict[str, Any]:
|
|
205
|
+
"""Get configuration status."""
|
|
206
|
+
return {
|
|
207
|
+
"config_file": str(self.config_file),
|
|
208
|
+
"vision": {
|
|
209
|
+
"strategy": self.config.vision.strategy,
|
|
210
|
+
"status": "✅ Ready" if self.config.vision.caption_provider else "❌ Not configured",
|
|
211
|
+
"caption_provider": self.config.vision.caption_provider,
|
|
212
|
+
"caption_model": self.config.vision.caption_model
|
|
213
|
+
},
|
|
214
|
+
"app_defaults": {
|
|
215
|
+
"cli": {
|
|
216
|
+
"provider": self.config.app_defaults.cli_provider,
|
|
217
|
+
"model": self.config.app_defaults.cli_model
|
|
218
|
+
},
|
|
219
|
+
"summarizer": {
|
|
220
|
+
"provider": self.config.app_defaults.summarizer_provider,
|
|
221
|
+
"model": self.config.app_defaults.summarizer_model
|
|
222
|
+
},
|
|
223
|
+
"extractor": {
|
|
224
|
+
"provider": self.config.app_defaults.extractor_provider,
|
|
225
|
+
"model": self.config.app_defaults.extractor_model
|
|
226
|
+
},
|
|
227
|
+
"judge": {
|
|
228
|
+
"provider": self.config.app_defaults.judge_provider,
|
|
229
|
+
"model": self.config.app_defaults.judge_model
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
"global_defaults": {
|
|
233
|
+
"provider": self.config.default_models.global_provider,
|
|
234
|
+
"model": self.config.default_models.global_model,
|
|
235
|
+
"chat_model": self.config.default_models.chat_model,
|
|
236
|
+
"code_model": self.config.default_models.code_model
|
|
237
|
+
},
|
|
238
|
+
"embeddings": {
|
|
239
|
+
"status": "✅ Ready",
|
|
240
|
+
"provider": self.config.embeddings.provider,
|
|
241
|
+
"model": self.config.embeddings.model
|
|
242
|
+
},
|
|
243
|
+
"streaming": {
|
|
244
|
+
"cli_stream_default": False # Default value
|
|
245
|
+
},
|
|
246
|
+
"logging": {
|
|
247
|
+
"console_level": self.config.logging.console_level,
|
|
248
|
+
"file_level": self.config.logging.file_level,
|
|
249
|
+
"file_logging_enabled": self.config.logging.file_logging_enabled
|
|
250
|
+
},
|
|
251
|
+
"cache": {
|
|
252
|
+
"default_cache_dir": self.config.cache.default_cache_dir
|
|
253
|
+
},
|
|
254
|
+
"api_keys": {
|
|
255
|
+
"openai": "✅ Set" if self.config.api_keys.openai else "❌ Not set",
|
|
256
|
+
"anthropic": "✅ Set" if self.config.api_keys.anthropic else "❌ Not set",
|
|
257
|
+
"google": "✅ Set" if self.config.api_keys.google else "❌ Not set"
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
def set_global_default_model(self, provider_model: str) -> bool:
|
|
262
|
+
"""Set global default model in provider/model format."""
|
|
263
|
+
try:
|
|
264
|
+
if '/' in provider_model:
|
|
265
|
+
provider, model = provider_model.split('/', 1)
|
|
266
|
+
else:
|
|
267
|
+
# Assume it's just a model name, use default provider
|
|
268
|
+
provider = "ollama"
|
|
269
|
+
model = provider_model
|
|
270
|
+
|
|
271
|
+
self.config.default_models.global_provider = provider
|
|
272
|
+
self.config.default_models.global_model = model
|
|
273
|
+
self._save_config()
|
|
274
|
+
return True
|
|
275
|
+
except Exception:
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
def set_app_default(self, app_name: str, provider: str, model: str) -> bool:
|
|
279
|
+
"""Set app-specific default provider and model."""
|
|
280
|
+
try:
|
|
281
|
+
if app_name == "cli":
|
|
282
|
+
self.config.app_defaults.cli_provider = provider
|
|
283
|
+
self.config.app_defaults.cli_model = model
|
|
284
|
+
elif app_name == "summarizer":
|
|
285
|
+
self.config.app_defaults.summarizer_provider = provider
|
|
286
|
+
self.config.app_defaults.summarizer_model = model
|
|
287
|
+
elif app_name == "extractor":
|
|
288
|
+
self.config.app_defaults.extractor_provider = provider
|
|
289
|
+
self.config.app_defaults.extractor_model = model
|
|
290
|
+
elif app_name == "judge":
|
|
291
|
+
self.config.app_defaults.judge_provider = provider
|
|
292
|
+
self.config.app_defaults.judge_model = model
|
|
293
|
+
else:
|
|
294
|
+
raise ValueError(f"Unknown app: {app_name}")
|
|
295
|
+
|
|
296
|
+
self._save_config()
|
|
297
|
+
return True
|
|
298
|
+
except Exception:
|
|
299
|
+
return False
|
|
300
|
+
|
|
301
|
+
def set_api_key(self, provider: str, key: str) -> bool:
|
|
302
|
+
"""Set API key for a provider."""
|
|
303
|
+
try:
|
|
304
|
+
if provider == "openai":
|
|
305
|
+
self.config.api_keys.openai = key
|
|
306
|
+
elif provider == "anthropic":
|
|
307
|
+
self.config.api_keys.anthropic = key
|
|
308
|
+
elif provider == "google":
|
|
309
|
+
self.config.api_keys.google = key
|
|
310
|
+
else:
|
|
311
|
+
return False
|
|
312
|
+
|
|
313
|
+
self._save_config()
|
|
314
|
+
return True
|
|
315
|
+
except Exception:
|
|
316
|
+
return False
|
|
317
|
+
|
|
318
|
+
def get_app_default(self, app_name: str) -> Tuple[str, str]:
|
|
319
|
+
"""Get default provider and model for an app."""
|
|
320
|
+
app_defaults = self.config.app_defaults
|
|
321
|
+
|
|
322
|
+
if app_name == "cli":
|
|
323
|
+
return app_defaults.cli_provider, app_defaults.cli_model
|
|
324
|
+
elif app_name == "summarizer":
|
|
325
|
+
return app_defaults.summarizer_provider, app_defaults.summarizer_model
|
|
326
|
+
elif app_name == "extractor":
|
|
327
|
+
return app_defaults.extractor_provider, app_defaults.extractor_model
|
|
328
|
+
elif app_name == "judge":
|
|
329
|
+
return app_defaults.judge_provider, app_defaults.judge_model
|
|
330
|
+
else:
|
|
331
|
+
# Return default fallback
|
|
332
|
+
return "huggingface", "unsloth/Qwen3-4B-Instruct-2507-GGUF"
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# Global instance
|
|
336
|
+
_config_manager = None
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def get_config_manager() -> ConfigurationManager:
|
|
340
|
+
"""Get the global configuration manager instance."""
|
|
341
|
+
global _config_manager
|
|
342
|
+
if _config_manager is None:
|
|
343
|
+
_config_manager = ConfigurationManager()
|
|
344
|
+
return _config_manager
|
|
@@ -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.
|
|
14
|
+
__version__ = "2.5.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: abstractcore
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
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>
|
|
@@ -551,6 +551,10 @@ abstractcore --set-console-log-level NONE # Disable console logging
|
|
|
551
551
|
abstractcore --enable-file-logging # Save logs to files
|
|
552
552
|
abstractcore --enable-debug-logging # Full debug mode
|
|
553
553
|
|
|
554
|
+
# Configure vision for image analysis with text-only models
|
|
555
|
+
abstractcore --set-vision-provider ollama qwen2.5vl:7b
|
|
556
|
+
abstractcore --set-vision-provider lmstudio qwen/qwen3-vl-4b
|
|
557
|
+
|
|
554
558
|
# Set API keys as needed
|
|
555
559
|
abstractcore --set-api-key openai sk-your-key-here
|
|
556
560
|
abstractcore --set-api-key anthropic your-anthropic-key
|
|
@@ -20,9 +20,10 @@ abstractcore/architectures/enums.py
|
|
|
20
20
|
abstractcore/assets/architecture_formats.json
|
|
21
21
|
abstractcore/assets/model_capabilities.json
|
|
22
22
|
abstractcore/assets/session_schema.json
|
|
23
|
-
abstractcore/
|
|
24
|
-
abstractcore/
|
|
25
|
-
abstractcore/
|
|
23
|
+
abstractcore/config/__init__.py
|
|
24
|
+
abstractcore/config/main.py
|
|
25
|
+
abstractcore/config/manager.py
|
|
26
|
+
abstractcore/config/vision_config.py
|
|
26
27
|
abstractcore/core/__init__.py
|
|
27
28
|
abstractcore/core/enums.py
|
|
28
29
|
abstractcore/core/factory.py
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[console_scripts]
|
|
2
|
-
abstractcore = abstractcore.
|
|
2
|
+
abstractcore = abstractcore.config.main:main
|
|
3
3
|
abstractcore-chat = abstractcore.utils.cli:main
|
|
4
|
-
abstractcore-config = abstractcore.
|
|
4
|
+
abstractcore-config = abstractcore.config.main:main
|
|
5
5
|
abstractcore-extractor = abstractcore.apps.extractor:main
|
|
6
6
|
abstractcore-judge = abstractcore.apps.judge:main
|
|
7
7
|
abstractcore-summarizer = abstractcore.apps.summarizer:main
|
|
@@ -61,8 +61,8 @@ Changelog = "https://github.com/lpalbou/AbstractCore/blob/main/CHANGELOG.md"
|
|
|
61
61
|
|
|
62
62
|
[project.scripts]
|
|
63
63
|
# Configuration CLI (manage AbstractCore settings, API keys, models)
|
|
64
|
-
abstractcore = "abstractcore.
|
|
65
|
-
abstractcore-config = "abstractcore.
|
|
64
|
+
abstractcore = "abstractcore.config.main:main"
|
|
65
|
+
abstractcore-config = "abstractcore.config.main:main"
|
|
66
66
|
|
|
67
67
|
# Interactive Chat CLI (REPL for LLM interaction)
|
|
68
68
|
abstractcore-chat = "abstractcore.utils.cli:main"
|
|
@@ -216,7 +216,7 @@ packages = [
|
|
|
216
216
|
"abstractcore.media.processors",
|
|
217
217
|
"abstractcore.media.handlers",
|
|
218
218
|
"abstractcore.media.utils",
|
|
219
|
-
"abstractcore.
|
|
219
|
+
"abstractcore.config"
|
|
220
220
|
]
|
|
221
221
|
|
|
222
222
|
[tool.setuptools.dynamic]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractcore-2.4.9/abstractcore/cli → abstractcore-2.5.0/abstractcore/config}/vision_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|