abstractcore 2.9.1__py3-none-any.whl → 2.11.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- abstractcore/__init__.py +7 -27
- abstractcore/apps/deepsearch.py +9 -4
- abstractcore/apps/extractor.py +33 -100
- abstractcore/apps/intent.py +19 -0
- abstractcore/apps/judge.py +20 -1
- abstractcore/apps/summarizer.py +20 -1
- abstractcore/architectures/detection.py +34 -1
- abstractcore/architectures/response_postprocessing.py +313 -0
- abstractcore/assets/architecture_formats.json +38 -8
- abstractcore/assets/model_capabilities.json +882 -160
- abstractcore/compression/__init__.py +1 -2
- abstractcore/compression/glyph_processor.py +6 -4
- abstractcore/config/main.py +52 -20
- abstractcore/config/manager.py +390 -12
- abstractcore/config/vision_config.py +5 -5
- abstractcore/core/interface.py +151 -3
- abstractcore/core/session.py +16 -10
- abstractcore/download.py +1 -1
- abstractcore/embeddings/manager.py +20 -6
- abstractcore/endpoint/__init__.py +2 -0
- abstractcore/endpoint/app.py +458 -0
- abstractcore/mcp/client.py +3 -1
- abstractcore/media/__init__.py +52 -17
- abstractcore/media/auto_handler.py +42 -22
- abstractcore/media/base.py +44 -1
- abstractcore/media/capabilities.py +12 -33
- abstractcore/media/enrichment.py +105 -0
- abstractcore/media/handlers/anthropic_handler.py +19 -28
- abstractcore/media/handlers/local_handler.py +124 -70
- abstractcore/media/handlers/openai_handler.py +19 -31
- abstractcore/media/processors/__init__.py +4 -2
- abstractcore/media/processors/audio_processor.py +57 -0
- abstractcore/media/processors/office_processor.py +8 -3
- abstractcore/media/processors/pdf_processor.py +46 -3
- abstractcore/media/processors/text_processor.py +22 -24
- abstractcore/media/processors/video_processor.py +58 -0
- abstractcore/media/types.py +97 -4
- abstractcore/media/utils/image_scaler.py +20 -2
- abstractcore/media/utils/video_frames.py +219 -0
- abstractcore/media/vision_fallback.py +136 -22
- abstractcore/processing/__init__.py +32 -3
- abstractcore/processing/basic_deepsearch.py +15 -10
- abstractcore/processing/basic_intent.py +3 -2
- abstractcore/processing/basic_judge.py +3 -2
- abstractcore/processing/basic_summarizer.py +1 -1
- abstractcore/providers/__init__.py +3 -1
- abstractcore/providers/anthropic_provider.py +95 -8
- abstractcore/providers/base.py +1516 -81
- abstractcore/providers/huggingface_provider.py +546 -69
- abstractcore/providers/lmstudio_provider.py +30 -916
- abstractcore/providers/mlx_provider.py +382 -35
- abstractcore/providers/model_capabilities.py +5 -1
- abstractcore/providers/ollama_provider.py +99 -15
- abstractcore/providers/openai_compatible_provider.py +406 -180
- abstractcore/providers/openai_provider.py +188 -44
- abstractcore/providers/openrouter_provider.py +76 -0
- abstractcore/providers/registry.py +61 -5
- abstractcore/providers/streaming.py +138 -33
- abstractcore/providers/vllm_provider.py +92 -817
- abstractcore/server/app.py +478 -28
- abstractcore/server/audio_endpoints.py +139 -0
- abstractcore/server/vision_endpoints.py +1319 -0
- abstractcore/structured/handler.py +316 -41
- abstractcore/tools/common_tools.py +5501 -2012
- abstractcore/tools/comms_tools.py +1641 -0
- abstractcore/tools/core.py +37 -7
- abstractcore/tools/handler.py +4 -9
- abstractcore/tools/parser.py +49 -2
- abstractcore/tools/tag_rewriter.py +2 -1
- abstractcore/tools/telegram_tdlib.py +407 -0
- abstractcore/tools/telegram_tools.py +261 -0
- abstractcore/utils/cli.py +1085 -72
- abstractcore/utils/structured_logging.py +29 -8
- abstractcore/utils/token_utils.py +2 -0
- abstractcore/utils/truncation.py +29 -0
- abstractcore/utils/version.py +3 -4
- abstractcore/utils/vlm_token_calculator.py +12 -2
- abstractcore-2.11.4.dist-info/METADATA +562 -0
- abstractcore-2.11.4.dist-info/RECORD +133 -0
- {abstractcore-2.9.1.dist-info → abstractcore-2.11.4.dist-info}/WHEEL +1 -1
- {abstractcore-2.9.1.dist-info → abstractcore-2.11.4.dist-info}/entry_points.txt +1 -0
- abstractcore-2.9.1.dist-info/METADATA +0 -1190
- abstractcore-2.9.1.dist-info/RECORD +0 -119
- {abstractcore-2.9.1.dist-info → abstractcore-2.11.4.dist-info}/licenses/LICENSE +0 -0
- {abstractcore-2.9.1.dist-info → abstractcore-2.11.4.dist-info}/top_level.txt +0 -0
abstractcore/config/manager.py
CHANGED
|
@@ -25,6 +25,48 @@ class VisionConfig:
|
|
|
25
25
|
self.fallback_chain = []
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
@dataclass
|
|
29
|
+
class AudioConfig:
|
|
30
|
+
"""Audio configuration settings (input policy + optional fallback)."""
|
|
31
|
+
# Default: do not silently change semantics. Allow native audio when supported,
|
|
32
|
+
# otherwise error unless the caller explicitly requests STT/caption.
|
|
33
|
+
strategy: str = "native_only" # native_only|speech_to_text|caption|auto
|
|
34
|
+
# Optional preferred STT backend (capabilities plugin backend_id).
|
|
35
|
+
stt_backend_id: Optional[str] = None
|
|
36
|
+
stt_language: Optional[str] = None
|
|
37
|
+
# Reserved for future "audio caption" backends.
|
|
38
|
+
caption_provider: Optional[str] = None
|
|
39
|
+
caption_model: Optional[str] = None
|
|
40
|
+
fallback_chain: list = None
|
|
41
|
+
|
|
42
|
+
def __post_init__(self):
|
|
43
|
+
if self.fallback_chain is None:
|
|
44
|
+
self.fallback_chain = []
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class VideoConfig:
|
|
49
|
+
"""Video configuration settings (input policy + optional fallback)."""
|
|
50
|
+
# Default: best-effort usability. Prefer native video when supported; otherwise fall back
|
|
51
|
+
# to sampled frames routed through existing image/vision handling.
|
|
52
|
+
strategy: str = "auto" # native_only|frames_caption|auto
|
|
53
|
+
|
|
54
|
+
# Frame sampling controls for frames-based fallback.
|
|
55
|
+
max_frames: int = 3
|
|
56
|
+
# Native video models typically require more temporal coverage than the fallback path.
|
|
57
|
+
# This default is used when the selected model supports native video input (v0: HF only).
|
|
58
|
+
max_frames_native: int = 8
|
|
59
|
+
frame_format: str = "jpg" # jpg|png
|
|
60
|
+
sampling_strategy: str = "uniform" # uniform|keyframes
|
|
61
|
+
|
|
62
|
+
# Downscale extracted frames (preserve aspect ratio; never upscale). Helps memory + token pressure.
|
|
63
|
+
# Applies to both frames_caption fallback and HF native video ingestion (which uses ffmpeg frames).
|
|
64
|
+
max_frame_side: int = 1024
|
|
65
|
+
|
|
66
|
+
# Maximum video size allowed for processing (bytes). None => use media handler defaults.
|
|
67
|
+
max_video_size_bytes: Optional[int] = None
|
|
68
|
+
|
|
69
|
+
|
|
28
70
|
@dataclass
|
|
29
71
|
class EmbeddingsConfig:
|
|
30
72
|
"""Embeddings configuration settings."""
|
|
@@ -47,6 +89,44 @@ class AppDefaults:
|
|
|
47
89
|
intent_model: Optional[str] = "unsloth/Qwen3-4B-Instruct-2507-GGUF"
|
|
48
90
|
|
|
49
91
|
|
|
92
|
+
@dataclass
|
|
93
|
+
class MaintenanceConfig:
|
|
94
|
+
"""Maintenance agent configuration (triage, stewardship)."""
|
|
95
|
+
|
|
96
|
+
# LLM assist (optional, local-first).
|
|
97
|
+
triage_llm_enabled: bool = False
|
|
98
|
+
triage_llm_base_url: str = "http://localhost:1234"
|
|
99
|
+
triage_llm_model: str = "qwen/qwen3-next-80b"
|
|
100
|
+
triage_llm_temperature: float = 0.2
|
|
101
|
+
triage_llm_max_tokens: int = 800
|
|
102
|
+
triage_llm_timeout_s: float = 30.0
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class EmailConfig:
|
|
107
|
+
"""Email defaults (SMTP outbound + IMAP inbound).
|
|
108
|
+
|
|
109
|
+
These defaults are used by framework-native comms tools and gateway bridges when
|
|
110
|
+
explicit parameters are omitted (env vars still take precedence).
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
# SMTP (outbound)
|
|
114
|
+
smtp_host: str = ""
|
|
115
|
+
smtp_port: int = 587
|
|
116
|
+
smtp_username: str = ""
|
|
117
|
+
smtp_password_env_var: str = "EMAIL_PASSWORD"
|
|
118
|
+
smtp_use_starttls: bool = True
|
|
119
|
+
from_email: Optional[str] = None
|
|
120
|
+
reply_to: Optional[str] = None
|
|
121
|
+
|
|
122
|
+
# IMAP (inbound)
|
|
123
|
+
imap_host: str = ""
|
|
124
|
+
imap_port: int = 993
|
|
125
|
+
imap_username: str = ""
|
|
126
|
+
imap_password_env_var: str = "EMAIL_PASSWORD"
|
|
127
|
+
imap_folder: str = "INBOX"
|
|
128
|
+
|
|
129
|
+
|
|
50
130
|
@dataclass
|
|
51
131
|
class DefaultModels:
|
|
52
132
|
"""Global default model configurations."""
|
|
@@ -61,6 +141,7 @@ class ApiKeysConfig:
|
|
|
61
141
|
"""API keys configuration."""
|
|
62
142
|
openai: Optional[str] = None
|
|
63
143
|
anthropic: Optional[str] = None
|
|
144
|
+
openrouter: Optional[str] = None
|
|
64
145
|
google: Optional[str] = None
|
|
65
146
|
|
|
66
147
|
|
|
@@ -76,7 +157,7 @@ class CacheConfig:
|
|
|
76
157
|
@dataclass
|
|
77
158
|
class LoggingConfig:
|
|
78
159
|
"""Logging configuration settings."""
|
|
79
|
-
console_level: str = "
|
|
160
|
+
console_level: str = "ERROR"
|
|
80
161
|
file_level: str = "DEBUG"
|
|
81
162
|
file_logging_enabled: bool = False
|
|
82
163
|
log_base_dir: Optional[str] = None
|
|
@@ -85,6 +166,12 @@ class LoggingConfig:
|
|
|
85
166
|
file_json: bool = True
|
|
86
167
|
|
|
87
168
|
|
|
169
|
+
@dataclass
|
|
170
|
+
class StreamingConfig:
|
|
171
|
+
"""Streaming configuration settings."""
|
|
172
|
+
cli_stream_default: bool = False
|
|
173
|
+
|
|
174
|
+
|
|
88
175
|
@dataclass
|
|
89
176
|
class TimeoutConfig:
|
|
90
177
|
"""Timeout configuration settings."""
|
|
@@ -106,28 +193,38 @@ class OfflineConfig:
|
|
|
106
193
|
class AbstractCoreConfig:
|
|
107
194
|
"""Main configuration class."""
|
|
108
195
|
vision: VisionConfig
|
|
196
|
+
audio: AudioConfig
|
|
197
|
+
video: VideoConfig
|
|
109
198
|
embeddings: EmbeddingsConfig
|
|
110
199
|
app_defaults: AppDefaults
|
|
111
200
|
default_models: DefaultModels
|
|
112
201
|
api_keys: ApiKeysConfig
|
|
113
202
|
cache: CacheConfig
|
|
114
203
|
logging: LoggingConfig
|
|
204
|
+
streaming: StreamingConfig
|
|
115
205
|
timeouts: TimeoutConfig
|
|
116
206
|
offline: OfflineConfig
|
|
207
|
+
maintenance: MaintenanceConfig
|
|
208
|
+
email: EmailConfig
|
|
117
209
|
|
|
118
210
|
@classmethod
|
|
119
211
|
def default(cls):
|
|
120
212
|
"""Create default configuration."""
|
|
121
213
|
return cls(
|
|
122
214
|
vision=VisionConfig(),
|
|
215
|
+
audio=AudioConfig(),
|
|
216
|
+
video=VideoConfig(),
|
|
123
217
|
embeddings=EmbeddingsConfig(),
|
|
124
218
|
app_defaults=AppDefaults(),
|
|
125
219
|
default_models=DefaultModels(),
|
|
126
220
|
api_keys=ApiKeysConfig(),
|
|
127
221
|
cache=CacheConfig(),
|
|
128
222
|
logging=LoggingConfig(),
|
|
223
|
+
streaming=StreamingConfig(),
|
|
129
224
|
timeouts=TimeoutConfig(),
|
|
130
|
-
offline=OfflineConfig()
|
|
225
|
+
offline=OfflineConfig(),
|
|
226
|
+
maintenance=MaintenanceConfig(),
|
|
227
|
+
email=EmailConfig(),
|
|
131
228
|
)
|
|
132
229
|
|
|
133
230
|
|
|
@@ -157,25 +254,35 @@ class ConfigurationManager:
|
|
|
157
254
|
"""Convert dictionary to config object."""
|
|
158
255
|
# Create config objects from dictionary data
|
|
159
256
|
vision = VisionConfig(**data.get('vision', {}))
|
|
257
|
+
audio = AudioConfig(**data.get('audio', {}))
|
|
258
|
+
video = VideoConfig(**data.get('video', {}))
|
|
160
259
|
embeddings = EmbeddingsConfig(**data.get('embeddings', {}))
|
|
161
260
|
app_defaults = AppDefaults(**data.get('app_defaults', {}))
|
|
162
261
|
default_models = DefaultModels(**data.get('default_models', {}))
|
|
163
262
|
api_keys = ApiKeysConfig(**data.get('api_keys', {}))
|
|
164
263
|
cache = CacheConfig(**data.get('cache', {}))
|
|
165
264
|
logging = LoggingConfig(**data.get('logging', {}))
|
|
265
|
+
streaming = StreamingConfig(**data.get('streaming', {}))
|
|
166
266
|
timeouts = TimeoutConfig(**data.get('timeouts', {}))
|
|
167
267
|
offline = OfflineConfig(**data.get('offline', {}))
|
|
268
|
+
maintenance = MaintenanceConfig(**data.get('maintenance', {}))
|
|
269
|
+
email_cfg = EmailConfig(**data.get('email', {}))
|
|
168
270
|
|
|
169
271
|
return AbstractCoreConfig(
|
|
170
272
|
vision=vision,
|
|
273
|
+
audio=audio,
|
|
274
|
+
video=video,
|
|
171
275
|
embeddings=embeddings,
|
|
172
276
|
app_defaults=app_defaults,
|
|
173
277
|
default_models=default_models,
|
|
174
278
|
api_keys=api_keys,
|
|
175
279
|
cache=cache,
|
|
176
280
|
logging=logging,
|
|
281
|
+
streaming=streaming,
|
|
177
282
|
timeouts=timeouts,
|
|
178
|
-
offline=offline
|
|
283
|
+
offline=offline,
|
|
284
|
+
maintenance=maintenance,
|
|
285
|
+
email=email_cfg,
|
|
179
286
|
)
|
|
180
287
|
|
|
181
288
|
def _save_config(self):
|
|
@@ -185,14 +292,18 @@ class ConfigurationManager:
|
|
|
185
292
|
# Convert config to dictionary
|
|
186
293
|
config_dict = {
|
|
187
294
|
'vision': asdict(self.config.vision),
|
|
295
|
+
'audio': asdict(self.config.audio),
|
|
296
|
+
'video': asdict(self.config.video),
|
|
188
297
|
'embeddings': asdict(self.config.embeddings),
|
|
189
298
|
'app_defaults': asdict(self.config.app_defaults),
|
|
190
299
|
'default_models': asdict(self.config.default_models),
|
|
191
300
|
'api_keys': asdict(self.config.api_keys),
|
|
192
301
|
'cache': asdict(self.config.cache),
|
|
193
302
|
'logging': asdict(self.config.logging),
|
|
303
|
+
'streaming': asdict(self.config.streaming),
|
|
194
304
|
'timeouts': asdict(self.config.timeouts),
|
|
195
|
-
'offline': asdict(self.config.offline)
|
|
305
|
+
'offline': asdict(self.config.offline),
|
|
306
|
+
'maintenance': asdict(self.config.maintenance),
|
|
196
307
|
}
|
|
197
308
|
|
|
198
309
|
with open(self.config_file, 'w') as f:
|
|
@@ -242,6 +353,14 @@ class ConfigurationManager:
|
|
|
242
353
|
"caption_provider": self.config.vision.caption_provider,
|
|
243
354
|
"caption_model": self.config.vision.caption_model
|
|
244
355
|
},
|
|
356
|
+
"video": {
|
|
357
|
+
"strategy": self.config.video.strategy,
|
|
358
|
+
"max_frames": self.config.video.max_frames,
|
|
359
|
+
"max_frames_native": getattr(self.config.video, "max_frames_native", None),
|
|
360
|
+
"frame_format": self.config.video.frame_format,
|
|
361
|
+
"sampling_strategy": getattr(self.config.video, "sampling_strategy", None),
|
|
362
|
+
"max_frame_side": getattr(self.config.video, "max_frame_side", None),
|
|
363
|
+
},
|
|
245
364
|
"app_defaults": {
|
|
246
365
|
"cli": {
|
|
247
366
|
"provider": self.config.app_defaults.cli_provider,
|
|
@@ -276,7 +395,7 @@ class ConfigurationManager:
|
|
|
276
395
|
"model": self.config.embeddings.model
|
|
277
396
|
},
|
|
278
397
|
"streaming": {
|
|
279
|
-
"cli_stream_default":
|
|
398
|
+
"cli_stream_default": self.config.streaming.cli_stream_default
|
|
280
399
|
},
|
|
281
400
|
"logging": {
|
|
282
401
|
"console_level": self.config.logging.console_level,
|
|
@@ -288,11 +407,15 @@ class ConfigurationManager:
|
|
|
288
407
|
"tool_timeout": self.config.timeouts.tool_timeout
|
|
289
408
|
},
|
|
290
409
|
"cache": {
|
|
291
|
-
"default_cache_dir": self.config.cache.default_cache_dir
|
|
410
|
+
"default_cache_dir": self.config.cache.default_cache_dir,
|
|
411
|
+
"huggingface_cache_dir": self.config.cache.huggingface_cache_dir,
|
|
412
|
+
"local_models_cache_dir": self.config.cache.local_models_cache_dir,
|
|
413
|
+
"glyph_cache_dir": self.config.cache.glyph_cache_dir,
|
|
292
414
|
},
|
|
293
415
|
"api_keys": {
|
|
294
416
|
"openai": "✅ Set" if self.config.api_keys.openai else "❌ Not set",
|
|
295
417
|
"anthropic": "✅ Set" if self.config.api_keys.anthropic else "❌ Not set",
|
|
418
|
+
"openrouter": "✅ Set" if self.config.api_keys.openrouter else "❌ Not set",
|
|
296
419
|
"google": "✅ Set" if self.config.api_keys.google else "❌ Not set"
|
|
297
420
|
},
|
|
298
421
|
"offline": {
|
|
@@ -302,6 +425,16 @@ class ConfigurationManager:
|
|
|
302
425
|
}
|
|
303
426
|
}
|
|
304
427
|
|
|
428
|
+
def reset_configuration(self) -> bool:
|
|
429
|
+
"""Reset all configuration to built-in defaults."""
|
|
430
|
+
try:
|
|
431
|
+
self.config = AbstractCoreConfig.default()
|
|
432
|
+
self._provider_config.clear()
|
|
433
|
+
self._save_config()
|
|
434
|
+
return True
|
|
435
|
+
except Exception:
|
|
436
|
+
return False
|
|
437
|
+
|
|
305
438
|
def set_global_default_model(self, provider_model: str) -> bool:
|
|
306
439
|
"""Set global default model in provider/model format."""
|
|
307
440
|
try:
|
|
@@ -319,6 +452,243 @@ class ConfigurationManager:
|
|
|
319
452
|
except Exception:
|
|
320
453
|
return False
|
|
321
454
|
|
|
455
|
+
def set_default_model(self, provider_model: str) -> bool:
|
|
456
|
+
"""Legacy alias for setting the global default model."""
|
|
457
|
+
return self.set_global_default_model(provider_model)
|
|
458
|
+
|
|
459
|
+
def set_global_default_provider(self, provider: str) -> bool:
|
|
460
|
+
"""Set global default provider (legacy)."""
|
|
461
|
+
try:
|
|
462
|
+
provider = str(provider or "").strip()
|
|
463
|
+
if not provider:
|
|
464
|
+
raise ValueError("Provider cannot be empty")
|
|
465
|
+
self.config.default_models.global_provider = provider
|
|
466
|
+
self._save_config()
|
|
467
|
+
return True
|
|
468
|
+
except Exception:
|
|
469
|
+
return False
|
|
470
|
+
|
|
471
|
+
def set_chat_model(self, provider_model: str) -> bool:
|
|
472
|
+
"""Set specialized chat model (provider/model string)."""
|
|
473
|
+
try:
|
|
474
|
+
model = str(provider_model or "").strip()
|
|
475
|
+
if not model:
|
|
476
|
+
raise ValueError("Model cannot be empty")
|
|
477
|
+
self.config.default_models.chat_model = model
|
|
478
|
+
self._save_config()
|
|
479
|
+
return True
|
|
480
|
+
except Exception:
|
|
481
|
+
return False
|
|
482
|
+
|
|
483
|
+
def set_code_model(self, provider_model: str) -> bool:
|
|
484
|
+
"""Set specialized code model (provider/model string)."""
|
|
485
|
+
try:
|
|
486
|
+
model = str(provider_model or "").strip()
|
|
487
|
+
if not model:
|
|
488
|
+
raise ValueError("Model cannot be empty")
|
|
489
|
+
self.config.default_models.code_model = model
|
|
490
|
+
self._save_config()
|
|
491
|
+
return True
|
|
492
|
+
except Exception:
|
|
493
|
+
return False
|
|
494
|
+
|
|
495
|
+
def set_embeddings_model(self, provider_model: str) -> bool:
|
|
496
|
+
"""Set embeddings provider/model from a provider/model string (preferred)."""
|
|
497
|
+
try:
|
|
498
|
+
value = str(provider_model or "").strip()
|
|
499
|
+
if not value:
|
|
500
|
+
raise ValueError("Embeddings model cannot be empty")
|
|
501
|
+
|
|
502
|
+
if "/" in value:
|
|
503
|
+
provider, model = value.split("/", 1)
|
|
504
|
+
self.config.embeddings.provider = provider.strip() or self.config.embeddings.provider
|
|
505
|
+
self.config.embeddings.model = model.strip()
|
|
506
|
+
else:
|
|
507
|
+
self.config.embeddings.model = value
|
|
508
|
+
|
|
509
|
+
self._save_config()
|
|
510
|
+
return True
|
|
511
|
+
except Exception:
|
|
512
|
+
return False
|
|
513
|
+
|
|
514
|
+
def set_embeddings_provider(self, provider: str) -> bool:
|
|
515
|
+
"""Set embeddings provider."""
|
|
516
|
+
try:
|
|
517
|
+
value = str(provider or "").strip()
|
|
518
|
+
if not value:
|
|
519
|
+
raise ValueError("Embeddings provider cannot be empty")
|
|
520
|
+
self.config.embeddings.provider = value
|
|
521
|
+
self._save_config()
|
|
522
|
+
return True
|
|
523
|
+
except Exception:
|
|
524
|
+
return False
|
|
525
|
+
|
|
526
|
+
def set_default_cache_dir(self, path: str) -> bool:
|
|
527
|
+
"""Set default cache directory for AbstractCore."""
|
|
528
|
+
try:
|
|
529
|
+
value = str(path or "").strip()
|
|
530
|
+
if not value:
|
|
531
|
+
raise ValueError("Cache directory cannot be empty")
|
|
532
|
+
self.config.cache.default_cache_dir = value
|
|
533
|
+
self._save_config()
|
|
534
|
+
return True
|
|
535
|
+
except Exception:
|
|
536
|
+
return False
|
|
537
|
+
|
|
538
|
+
def set_huggingface_cache_dir(self, path: str) -> bool:
|
|
539
|
+
"""Set HuggingFace cache directory."""
|
|
540
|
+
try:
|
|
541
|
+
value = str(path or "").strip()
|
|
542
|
+
if not value:
|
|
543
|
+
raise ValueError("HuggingFace cache directory cannot be empty")
|
|
544
|
+
self.config.cache.huggingface_cache_dir = value
|
|
545
|
+
self._save_config()
|
|
546
|
+
return True
|
|
547
|
+
except Exception:
|
|
548
|
+
return False
|
|
549
|
+
|
|
550
|
+
def set_local_models_cache_dir(self, path: str) -> bool:
|
|
551
|
+
"""Set local models cache directory."""
|
|
552
|
+
try:
|
|
553
|
+
value = str(path or "").strip()
|
|
554
|
+
if not value:
|
|
555
|
+
raise ValueError("Local models cache directory cannot be empty")
|
|
556
|
+
self.config.cache.local_models_cache_dir = value
|
|
557
|
+
self._save_config()
|
|
558
|
+
return True
|
|
559
|
+
except Exception:
|
|
560
|
+
return False
|
|
561
|
+
|
|
562
|
+
def set_log_base_dir(self, path: str) -> bool:
|
|
563
|
+
"""Set log base directory."""
|
|
564
|
+
try:
|
|
565
|
+
value = str(path or "").strip()
|
|
566
|
+
if not value:
|
|
567
|
+
raise ValueError("Log base directory cannot be empty")
|
|
568
|
+
self.config.logging.log_base_dir = value
|
|
569
|
+
self._save_config()
|
|
570
|
+
return True
|
|
571
|
+
except Exception:
|
|
572
|
+
return False
|
|
573
|
+
|
|
574
|
+
def set_console_log_level(self, level: str) -> bool:
|
|
575
|
+
"""Set console logging level."""
|
|
576
|
+
try:
|
|
577
|
+
value = str(level or "").strip().upper()
|
|
578
|
+
if not value:
|
|
579
|
+
raise ValueError("Console log level cannot be empty")
|
|
580
|
+
self.config.logging.console_level = value
|
|
581
|
+
self._save_config()
|
|
582
|
+
return True
|
|
583
|
+
except Exception:
|
|
584
|
+
return False
|
|
585
|
+
|
|
586
|
+
def set_file_log_level(self, level: str) -> bool:
|
|
587
|
+
"""Set file logging level."""
|
|
588
|
+
try:
|
|
589
|
+
value = str(level or "").strip().upper()
|
|
590
|
+
if not value:
|
|
591
|
+
raise ValueError("File log level cannot be empty")
|
|
592
|
+
self.config.logging.file_level = value
|
|
593
|
+
self._save_config()
|
|
594
|
+
return True
|
|
595
|
+
except Exception:
|
|
596
|
+
return False
|
|
597
|
+
|
|
598
|
+
def enable_debug_logging(self) -> bool:
|
|
599
|
+
"""Enable debug logging for both console and file."""
|
|
600
|
+
try:
|
|
601
|
+
self.config.logging.console_level = "DEBUG"
|
|
602
|
+
self.config.logging.file_level = "DEBUG"
|
|
603
|
+
self._save_config()
|
|
604
|
+
return True
|
|
605
|
+
except Exception:
|
|
606
|
+
return False
|
|
607
|
+
|
|
608
|
+
def disable_console_logging(self) -> bool:
|
|
609
|
+
"""Disable console logging output."""
|
|
610
|
+
try:
|
|
611
|
+
self.config.logging.console_level = "NONE"
|
|
612
|
+
self._save_config()
|
|
613
|
+
return True
|
|
614
|
+
except Exception:
|
|
615
|
+
return False
|
|
616
|
+
|
|
617
|
+
def enable_file_logging(self) -> bool:
|
|
618
|
+
"""Enable file logging."""
|
|
619
|
+
try:
|
|
620
|
+
self.config.logging.file_logging_enabled = True
|
|
621
|
+
self._save_config()
|
|
622
|
+
return True
|
|
623
|
+
except Exception:
|
|
624
|
+
return False
|
|
625
|
+
|
|
626
|
+
def disable_file_logging(self) -> bool:
|
|
627
|
+
"""Disable file logging."""
|
|
628
|
+
try:
|
|
629
|
+
self.config.logging.file_logging_enabled = False
|
|
630
|
+
self._save_config()
|
|
631
|
+
return True
|
|
632
|
+
except Exception:
|
|
633
|
+
return False
|
|
634
|
+
|
|
635
|
+
def set_streaming_default(self, app_name: str, enabled: bool) -> bool:
|
|
636
|
+
"""Set default streaming behavior for a given app (currently: cli)."""
|
|
637
|
+
try:
|
|
638
|
+
app = str(app_name or "").strip().lower()
|
|
639
|
+
if app != "cli":
|
|
640
|
+
return False
|
|
641
|
+
self.config.streaming.cli_stream_default = bool(enabled)
|
|
642
|
+
self._save_config()
|
|
643
|
+
return True
|
|
644
|
+
except Exception:
|
|
645
|
+
return False
|
|
646
|
+
|
|
647
|
+
def get_streaming_default(self, app_name: str) -> bool:
|
|
648
|
+
"""Get default streaming behavior for a given app (currently: cli)."""
|
|
649
|
+
app = str(app_name or "").strip().lower()
|
|
650
|
+
if app == "cli":
|
|
651
|
+
return bool(self.config.streaming.cli_stream_default)
|
|
652
|
+
return False
|
|
653
|
+
|
|
654
|
+
def enable_cli_streaming(self) -> bool:
|
|
655
|
+
"""Enable streaming by default for the CLI."""
|
|
656
|
+
return self.set_streaming_default("cli", True)
|
|
657
|
+
|
|
658
|
+
def disable_cli_streaming(self) -> bool:
|
|
659
|
+
"""Disable streaming by default for the CLI."""
|
|
660
|
+
return self.set_streaming_default("cli", False)
|
|
661
|
+
|
|
662
|
+
def add_vision_fallback(self, provider: str, model: str) -> bool:
|
|
663
|
+
"""Add a vision fallback provider/model to the chain."""
|
|
664
|
+
try:
|
|
665
|
+
provider_val = str(provider or "").strip()
|
|
666
|
+
model_val = str(model or "").strip()
|
|
667
|
+
if not provider_val or not model_val:
|
|
668
|
+
raise ValueError("Provider and model are required")
|
|
669
|
+
|
|
670
|
+
self.config.vision.fallback_chain.append({"provider": provider_val, "model": model_val})
|
|
671
|
+
# If vision is configured at all, assume two_stage (caption -> text model).
|
|
672
|
+
if not self.config.vision.strategy or self.config.vision.strategy == "disabled":
|
|
673
|
+
self.config.vision.strategy = "two_stage"
|
|
674
|
+
self._save_config()
|
|
675
|
+
return True
|
|
676
|
+
except Exception:
|
|
677
|
+
return False
|
|
678
|
+
|
|
679
|
+
def disable_vision(self) -> bool:
|
|
680
|
+
"""Disable vision fallback for text-only models."""
|
|
681
|
+
try:
|
|
682
|
+
self.config.vision.strategy = "disabled"
|
|
683
|
+
self.config.vision.caption_provider = None
|
|
684
|
+
self.config.vision.caption_model = None
|
|
685
|
+
self.config.vision.fallback_chain = []
|
|
686
|
+
self._save_config()
|
|
687
|
+
return True
|
|
688
|
+
except Exception:
|
|
689
|
+
return False
|
|
690
|
+
|
|
691
|
+
|
|
322
692
|
def set_app_default(self, app_name: str, provider: str, model: str) -> bool:
|
|
323
693
|
"""Set app-specific default provider and model."""
|
|
324
694
|
try:
|
|
@@ -352,6 +722,8 @@ class ConfigurationManager:
|
|
|
352
722
|
self.config.api_keys.openai = key
|
|
353
723
|
elif provider == "anthropic":
|
|
354
724
|
self.config.api_keys.anthropic = key
|
|
725
|
+
elif provider == "openrouter":
|
|
726
|
+
self.config.api_keys.openrouter = key
|
|
355
727
|
elif provider == "google":
|
|
356
728
|
self.config.api_keys.google = key
|
|
357
729
|
else:
|
|
@@ -383,9 +755,12 @@ class ConfigurationManager:
|
|
|
383
755
|
def set_default_timeout(self, timeout: float) -> bool:
|
|
384
756
|
"""Set default HTTP request timeout in seconds."""
|
|
385
757
|
try:
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
758
|
+
# #[WARNING:TIMEOUT]
|
|
759
|
+
# Contract: allow `0` to mean "unlimited" (the provider layer normalizes <=0 to None).
|
|
760
|
+
timeout_f = float(timeout)
|
|
761
|
+
if timeout_f < 0:
|
|
762
|
+
raise ValueError("Timeout must be >= 0 (0 = unlimited)")
|
|
763
|
+
self.config.timeouts.default_timeout = timeout_f
|
|
389
764
|
self._save_config()
|
|
390
765
|
return True
|
|
391
766
|
except Exception:
|
|
@@ -394,9 +769,12 @@ class ConfigurationManager:
|
|
|
394
769
|
def set_tool_timeout(self, timeout: float) -> bool:
|
|
395
770
|
"""Set tool execution timeout in seconds."""
|
|
396
771
|
try:
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
772
|
+
# #[WARNING:TIMEOUT]
|
|
773
|
+
# Contract: allow `0` to mean "unlimited".
|
|
774
|
+
timeout_f = float(timeout)
|
|
775
|
+
if timeout_f < 0:
|
|
776
|
+
raise ValueError("Timeout must be >= 0 (0 = unlimited)")
|
|
777
|
+
self.config.timeouts.tool_timeout = timeout_f
|
|
400
778
|
self._save_config()
|
|
401
779
|
return True
|
|
402
780
|
except Exception:
|
|
@@ -155,9 +155,9 @@ def handle_list_vision(handler: 'VisionFallbackHandler') -> bool:
|
|
|
155
155
|
"gpt-4-turbo-with-vision - GPT-4 Turbo Vision"
|
|
156
156
|
],
|
|
157
157
|
"anthropic": [
|
|
158
|
-
"claude-
|
|
159
|
-
"claude-
|
|
160
|
-
"claude-
|
|
158
|
+
"claude-haiku-4-5 - Claude Haiku 4.5 (vision, cost-effective)",
|
|
159
|
+
"claude-sonnet-4-5 - Claude Sonnet 4.5 (vision)",
|
|
160
|
+
"claude-opus-4-5 - Claude Opus 4.5 (vision)"
|
|
161
161
|
],
|
|
162
162
|
"huggingface": [
|
|
163
163
|
"unsloth/Qwen2.5-VL-7B-Instruct-GGUF - GGUF format",
|
|
@@ -400,7 +400,7 @@ def configure_cloud_provider(handler: 'VisionFallbackHandler') -> bool:
|
|
|
400
400
|
# Suggest models based on provider
|
|
401
401
|
model_suggestions = {
|
|
402
402
|
"openai": ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo-with-vision"],
|
|
403
|
-
"anthropic": ["claude-
|
|
403
|
+
"anthropic": ["claude-haiku-4-5", "claude-sonnet-4-5", "claude-opus-4-5"]
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
print(f"\nSuggested models for {provider}:")
|
|
@@ -488,4 +488,4 @@ def add_vision_arguments(parser: argparse.ArgumentParser):
|
|
|
488
488
|
vision_group.add_argument('--download-vision-model', nargs='?', const=True, metavar='MODEL',
|
|
489
489
|
help='Download vision model for offline use (default: blip-base-caption)')
|
|
490
490
|
vision_group.add_argument('--configure', choices=['vision'],
|
|
491
|
-
help='Interactive configuration mode')
|
|
491
|
+
help='Interactive configuration mode')
|