cortex-llm 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. cortex/__init__.py +73 -0
  2. cortex/__main__.py +83 -0
  3. cortex/config.py +329 -0
  4. cortex/conversation_manager.py +468 -0
  5. cortex/fine_tuning/__init__.py +8 -0
  6. cortex/fine_tuning/dataset.py +332 -0
  7. cortex/fine_tuning/mlx_lora_trainer.py +502 -0
  8. cortex/fine_tuning/trainer.py +957 -0
  9. cortex/fine_tuning/wizard.py +707 -0
  10. cortex/gpu_validator.py +467 -0
  11. cortex/inference_engine.py +727 -0
  12. cortex/metal/__init__.py +275 -0
  13. cortex/metal/gpu_validator.py +177 -0
  14. cortex/metal/memory_pool.py +886 -0
  15. cortex/metal/mlx_accelerator.py +678 -0
  16. cortex/metal/mlx_converter.py +638 -0
  17. cortex/metal/mps_optimizer.py +417 -0
  18. cortex/metal/optimizer.py +665 -0
  19. cortex/metal/performance_profiler.py +364 -0
  20. cortex/model_downloader.py +130 -0
  21. cortex/model_manager.py +2187 -0
  22. cortex/quantization/__init__.py +5 -0
  23. cortex/quantization/dynamic_quantizer.py +736 -0
  24. cortex/template_registry/__init__.py +15 -0
  25. cortex/template_registry/auto_detector.py +144 -0
  26. cortex/template_registry/config_manager.py +234 -0
  27. cortex/template_registry/interactive.py +260 -0
  28. cortex/template_registry/registry.py +347 -0
  29. cortex/template_registry/template_profiles/__init__.py +5 -0
  30. cortex/template_registry/template_profiles/base.py +142 -0
  31. cortex/template_registry/template_profiles/complex/__init__.py +5 -0
  32. cortex/template_registry/template_profiles/complex/reasoning.py +263 -0
  33. cortex/template_registry/template_profiles/standard/__init__.py +9 -0
  34. cortex/template_registry/template_profiles/standard/alpaca.py +73 -0
  35. cortex/template_registry/template_profiles/standard/chatml.py +82 -0
  36. cortex/template_registry/template_profiles/standard/gemma.py +103 -0
  37. cortex/template_registry/template_profiles/standard/llama.py +87 -0
  38. cortex/template_registry/template_profiles/standard/simple.py +65 -0
  39. cortex/ui/__init__.py +120 -0
  40. cortex/ui/cli.py +1685 -0
  41. cortex/ui/markdown_render.py +185 -0
  42. cortex/ui/terminal_app.py +534 -0
  43. cortex_llm-1.0.0.dist-info/METADATA +275 -0
  44. cortex_llm-1.0.0.dist-info/RECORD +48 -0
  45. cortex_llm-1.0.0.dist-info/WHEEL +5 -0
  46. cortex_llm-1.0.0.dist-info/entry_points.txt +2 -0
  47. cortex_llm-1.0.0.dist-info/licenses/LICENSE +21 -0
  48. cortex_llm-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,15 @@
1
+ """Template Registry System for Cortex.
2
+
3
+ This module provides intelligent template management for different model types,
4
+ handling various chat formats and output processing requirements.
5
+ """
6
+
7
+ from cortex.template_registry.registry import TemplateRegistry
8
+ from cortex.template_registry.auto_detector import TemplateDetector
9
+ from cortex.template_registry.template_profiles.base import BaseTemplateProfile
10
+
11
+ __all__ = [
12
+ 'TemplateRegistry',
13
+ 'TemplateDetector',
14
+ 'BaseTemplateProfile'
15
+ ]
@@ -0,0 +1,144 @@
1
+ """Automatic template detection for models."""
2
+
3
+ import logging
4
+ from typing import List, Dict, Any, Optional, Tuple
5
+ from pathlib import Path
6
+
7
+ from cortex.template_registry.template_profiles.base import BaseTemplateProfile, TemplateType
8
+ from cortex.template_registry.template_profiles.standard import (
9
+ ChatMLProfile, LlamaProfile, AlpacaProfile, SimpleProfile, GemmaProfile
10
+ )
11
+ from cortex.template_registry.template_profiles.complex import ReasoningProfile
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class TemplateDetector:
17
+ """Automatically detect the best template for a model."""
18
+
19
+ def __init__(self):
20
+ """Initialize the detector with all available profiles."""
21
+ self.profiles = [
22
+ ReasoningProfile(),
23
+ GemmaProfile(), # Add Gemma profile with high priority
24
+ ChatMLProfile(),
25
+ LlamaProfile(),
26
+ AlpacaProfile(),
27
+ SimpleProfile() # Fallback
28
+ ]
29
+
30
+ def detect_template(
31
+ self,
32
+ model_name: str,
33
+ model_path: Optional[Path] = None,
34
+ tokenizer: Any = None
35
+ ) -> Tuple[BaseTemplateProfile, float]:
36
+ """Detect the best template for a model.
37
+
38
+ Args:
39
+ model_name: Name of the model
40
+ model_path: Optional path to model files
41
+ tokenizer: Optional tokenizer object
42
+
43
+ Returns:
44
+ Tuple of (best_profile, confidence_score)
45
+ """
46
+ candidates = []
47
+
48
+ # Check each profile
49
+ for profile in self.profiles:
50
+ can_handle, confidence = profile.can_handle(model_name, tokenizer)
51
+ if can_handle:
52
+ candidates.append((profile, confidence))
53
+ logger.debug(f"Profile {profile.config.name} can handle {model_name} with confidence {confidence}")
54
+
55
+ # Sort by confidence
56
+ candidates.sort(key=lambda x: x[1], reverse=True)
57
+
58
+ if candidates:
59
+ best_profile, confidence = candidates[0]
60
+ logger.info(f"Selected {best_profile.config.name} template for {model_name} (confidence: {confidence:.2f})")
61
+ return best_profile, confidence
62
+
63
+ # Fallback to simple profile
64
+ logger.warning(f"No specific template found for {model_name}, using simple fallback")
65
+ return SimpleProfile(), 0.1
66
+
67
+ def detect_from_tokenizer_config(self, tokenizer: Any) -> Optional[TemplateType]:
68
+ """Try to detect template type from tokenizer configuration."""
69
+ if not tokenizer:
70
+ return None
71
+
72
+ try:
73
+ # Check for chat_template attribute
74
+ if hasattr(tokenizer, 'chat_template'):
75
+ template_str = str(tokenizer.chat_template)
76
+
77
+ # Check for known patterns
78
+ if '<|im_start|>' in template_str:
79
+ return TemplateType.CHATML
80
+ elif '[INST]' in template_str:
81
+ return TemplateType.LLAMA
82
+ elif '<|channel|>' in template_str:
83
+ return TemplateType.REASONING
84
+ elif '### Instruction' in template_str:
85
+ return TemplateType.ALPACA
86
+ except (AttributeError, TypeError) as e:
87
+ logger.debug(f"Error accessing tokenizer chat_template: {e}")
88
+
89
+ try:
90
+ # Check tokenizer config
91
+ if hasattr(tokenizer, 'tokenizer_config'):
92
+ config = tokenizer.tokenizer_config
93
+ if isinstance(config, dict):
94
+ # Check for model type hints
95
+ model_type = config.get('model_type', '').lower()
96
+ if 'llama' in model_type:
97
+ return TemplateType.LLAMA
98
+ elif 'gpt' in model_type:
99
+ return TemplateType.OPENAI
100
+ except (AttributeError, TypeError, KeyError) as e:
101
+ logger.debug(f"Error accessing tokenizer config: {e}")
102
+
103
+ return None
104
+
105
+ def test_template(
106
+ self,
107
+ profile: BaseTemplateProfile,
108
+ test_prompt: str = "Hello, how are you?"
109
+ ) -> Dict[str, Any]:
110
+ """Test a template profile with a sample prompt.
111
+
112
+ Args:
113
+ profile: Template profile to test
114
+ test_prompt: Test prompt to use
115
+
116
+ Returns:
117
+ Dictionary with test results
118
+ """
119
+ messages = [
120
+ {"role": "user", "content": test_prompt}
121
+ ]
122
+
123
+ formatted = profile.format_messages(messages)
124
+
125
+ # Simulate a response
126
+ sample_response = "I'm doing well, thank you for asking! How can I help you today?"
127
+
128
+ # Add reasoning tokens for reasoning profile
129
+ if profile.config.template_type == TemplateType.REASONING:
130
+ sample_response = (
131
+ "<|channel|>analysis<|message|>The user is greeting me and asking about my state. "
132
+ "I should respond politely.<|end|>"
133
+ f"<|channel|>final<|message|>{sample_response}<|end|>"
134
+ )
135
+
136
+ processed = profile.process_response(sample_response)
137
+
138
+ return {
139
+ 'profile_name': profile.config.name,
140
+ 'formatted_prompt': formatted,
141
+ 'raw_response': sample_response,
142
+ 'processed_response': processed,
143
+ 'template_type': profile.config.template_type.value
144
+ }
@@ -0,0 +1,234 @@
1
+ """Configuration manager for template registry."""
2
+
3
+ import json
4
+ import logging
5
+ from pathlib import Path
6
+ from typing import Dict, Any, Optional, List
7
+ from datetime import datetime
8
+ from dataclasses import dataclass, asdict
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ # Configuration schema for validation
14
+ CONFIG_SCHEMA = {
15
+ "version": str,
16
+ "models": dict,
17
+ "global_settings": dict
18
+ }
19
+
20
+ MODEL_CONFIG_SCHEMA = {
21
+ "detected_type": str,
22
+ "user_preference": str,
23
+ "custom_filters": list,
24
+ "show_reasoning": bool,
25
+ "last_updated": str,
26
+ "confidence": float
27
+ }
28
+
29
+
30
+ @dataclass
31
+ class ModelTemplateConfig:
32
+ """Configuration for a specific model's template."""
33
+ detected_type: str
34
+ user_preference: str
35
+ custom_filters: List[str]
36
+ show_reasoning: bool = False
37
+ last_updated: str = ""
38
+ confidence: float = 0.0
39
+
40
+ def __post_init__(self):
41
+ if not self.last_updated:
42
+ self.last_updated = datetime.now().isoformat()
43
+
44
+
45
+ @dataclass
46
+ class GlobalSettings:
47
+ """Global template settings."""
48
+ auto_detect: bool = True
49
+ prompt_on_unknown: bool = True
50
+ verbose_mode: bool = False
51
+ cache_templates: bool = True
52
+ default_fallback: str = "simple"
53
+
54
+
55
+ class TemplateConfigManager:
56
+ """Manage template configurations and user preferences."""
57
+
58
+ def __init__(self, config_path: Optional[Path] = None):
59
+ """Initialize the configuration manager."""
60
+ self.config_path = config_path or (Path.home() / ".cortex" / "template_config.json")
61
+ self.config_path.parent.mkdir(parents=True, exist_ok=True)
62
+ self.config = self._load_config()
63
+
64
+ def _validate_config_structure(self, config: Dict[str, Any]) -> bool:
65
+ """Validate configuration structure matches expected schema.
66
+
67
+ Args:
68
+ config: Configuration dictionary to validate
69
+
70
+ Returns:
71
+ True if valid, False otherwise
72
+ """
73
+ # Check top-level keys
74
+ for key, expected_type in CONFIG_SCHEMA.items():
75
+ if key not in config:
76
+ logger.warning(f"Missing required config key: {key}")
77
+ return False
78
+ if not isinstance(config[key], expected_type):
79
+ logger.warning(f"Invalid type for {key}: expected {expected_type}, got {type(config[key])}")
80
+ return False
81
+
82
+ # Validate model configs
83
+ if "models" in config:
84
+ for model_name, model_config in config["models"].items():
85
+ if not isinstance(model_config, dict):
86
+ logger.warning(f"Invalid model config for {model_name}")
87
+ return False
88
+ # Validate model config structure
89
+ for key, expected_type in MODEL_CONFIG_SCHEMA.items():
90
+ if key in model_config and not isinstance(model_config[key], expected_type):
91
+ logger.warning(f"Invalid type for {model_name}.{key}")
92
+ return False
93
+
94
+ return True
95
+
96
+ def _load_config(self) -> Dict[str, Any]:
97
+ """Load configuration from file."""
98
+ if self.config_path.exists():
99
+ try:
100
+ with open(self.config_path, 'r') as f:
101
+ config = json.load(f)
102
+
103
+ # Validate loaded configuration
104
+ if self._validate_config_structure(config):
105
+ return config
106
+ else:
107
+ logger.warning("Invalid config structure, using defaults")
108
+ return self._get_default_config()
109
+
110
+ except json.JSONDecodeError as e:
111
+ logger.error(f"Invalid JSON in template config: {e}")
112
+ except IOError as e:
113
+ logger.error(f"Error reading template config file: {e}")
114
+ except PermissionError as e:
115
+ logger.error(f"Permission denied reading template config: {e}")
116
+
117
+ # Return default config
118
+ return self._get_default_config()
119
+
120
+ def _get_default_config(self) -> Dict[str, Any]:
121
+ """Get default configuration."""
122
+ return {
123
+ "version": "1.0",
124
+ "models": {},
125
+ "global_settings": asdict(GlobalSettings())
126
+ }
127
+
128
+ def _save_config(self) -> None:
129
+ """Save configuration to file."""
130
+ try:
131
+ # Ensure parent directory exists
132
+ self.config_path.parent.mkdir(parents=True, exist_ok=True)
133
+
134
+ # Write to temporary file first for atomicity
135
+ temp_path = self.config_path.with_suffix('.tmp')
136
+ with open(temp_path, 'w') as f:
137
+ json.dump(self.config, f, indent=2)
138
+
139
+ # Atomic rename
140
+ temp_path.replace(self.config_path)
141
+ logger.debug(f"Saved template config to {self.config_path}")
142
+
143
+ except IOError as e:
144
+ logger.error(f"IO error saving template config: {e}")
145
+ except PermissionError as e:
146
+ logger.error(f"Permission denied saving template config: {e}")
147
+ except OSError as e:
148
+ logger.error(f"OS error saving template config: {e}")
149
+
150
+ def get_model_config(self, model_name: str) -> Optional[ModelTemplateConfig]:
151
+ """Get configuration for a specific model."""
152
+ if model_name in self.config.get("models", {}):
153
+ data = self.config["models"][model_name]
154
+ return ModelTemplateConfig(
155
+ detected_type=data.get("detected_type", "unknown"),
156
+ user_preference=data.get("user_preference", "auto"),
157
+ custom_filters=data.get("custom_filters", []),
158
+ show_reasoning=data.get("show_reasoning", False),
159
+ last_updated=data.get("last_updated", ""),
160
+ confidence=data.get("confidence", 0.0)
161
+ )
162
+ return None
163
+
164
+ def save_model_config(self, model_name: str, config: ModelTemplateConfig) -> None:
165
+ """Save configuration for a specific model."""
166
+ if "models" not in self.config:
167
+ self.config["models"] = {}
168
+
169
+ self.config["models"][model_name] = {
170
+ "detected_type": config.detected_type,
171
+ "user_preference": config.user_preference,
172
+ "custom_filters": config.custom_filters,
173
+ "show_reasoning": config.show_reasoning,
174
+ "last_updated": datetime.now().isoformat(),
175
+ "confidence": config.confidence
176
+ }
177
+
178
+ self._save_config()
179
+ logger.info(f"Saved template configuration for {model_name}")
180
+
181
+ def get_global_settings(self) -> GlobalSettings:
182
+ """Get global settings."""
183
+ data = self.config.get("global_settings", {})
184
+ return GlobalSettings(
185
+ auto_detect=data.get("auto_detect", True),
186
+ prompt_on_unknown=data.get("prompt_on_unknown", True),
187
+ verbose_mode=data.get("verbose_mode", False),
188
+ cache_templates=data.get("cache_templates", True),
189
+ default_fallback=data.get("default_fallback", "simple")
190
+ )
191
+
192
+ def update_global_settings(self, **kwargs) -> None:
193
+ """Update global settings."""
194
+ settings = self.get_global_settings()
195
+
196
+ for key, value in kwargs.items():
197
+ if hasattr(settings, key):
198
+ setattr(settings, key, value)
199
+
200
+ self.config["global_settings"] = asdict(settings)
201
+ self._save_config()
202
+
203
+ def list_configured_models(self) -> List[str]:
204
+ """List all models with saved configurations."""
205
+ return list(self.config.get("models", {}).keys())
206
+
207
+ def remove_model_config(self, model_name: str) -> bool:
208
+ """Remove configuration for a model."""
209
+ if model_name in self.config.get("models", {}):
210
+ del self.config["models"][model_name]
211
+ self._save_config()
212
+ logger.info(f"Removed template configuration for {model_name}")
213
+ return True
214
+ return False
215
+
216
+ def export_config(self) -> Dict[str, Any]:
217
+ """Export the entire configuration."""
218
+ return self.config.copy()
219
+
220
+ def import_config(self, config: Dict[str, Any]) -> None:
221
+ """Import a configuration.
222
+
223
+ Args:
224
+ config: Configuration dictionary to import
225
+
226
+ Raises:
227
+ ValueError: If configuration is invalid
228
+ """
229
+ if not self._validate_config_structure(config):
230
+ raise ValueError("Invalid configuration structure")
231
+
232
+ self.config = config
233
+ self._save_config()
234
+ logger.info("Imported template configuration")
@@ -0,0 +1,260 @@
1
+ """Interactive template setup for user-friendly configuration."""
2
+
3
+ from typing import Optional, Dict, Any, List
4
+ from rich.console import Console
5
+ from rich.table import Table
6
+ from rich.prompt import Prompt, Confirm
7
+
8
+ from cortex.template_registry.template_profiles.base import BaseTemplateProfile, TemplateType
9
+ from cortex.template_registry.auto_detector import TemplateDetector
10
+ from cortex.template_registry.config_manager import ModelTemplateConfig
11
+
12
+
13
+ class InteractiveTemplateSetup:
14
+ """Interactive template configuration wizard."""
15
+
16
+ def __init__(self, console: Optional[Console] = None):
17
+ """Initialize the interactive setup."""
18
+ self.console = console or Console()
19
+ self.detector = TemplateDetector()
20
+
21
+ def setup_model_template(
22
+ self,
23
+ model_name: str,
24
+ tokenizer: Any = None,
25
+ current_config: Optional[ModelTemplateConfig] = None
26
+ ) -> ModelTemplateConfig:
27
+ """Interactive setup for model template.
28
+
29
+ Args:
30
+ model_name: Name of the model
31
+ tokenizer: Optional tokenizer object
32
+ current_config: Existing configuration if any
33
+
34
+ Returns:
35
+ Updated model template configuration
36
+ """
37
+ self.console.print(f"\n✓ Model loaded: [bold cyan]{model_name}[/bold cyan]")
38
+
39
+ # Detect template
40
+ profile, confidence = self.detector.detect_template(model_name, tokenizer=tokenizer)
41
+
42
+ if confidence < 0.5:
43
+ self.console.print("\n⚠️ [yellow]Template configuration needed for optimal performance[/yellow]")
44
+
45
+ self.console.print(f"\nDetecting template format...")
46
+ self.console.print(f"✓ Found: [green]{profile.config.description}[/green] (confidence: {confidence:.0%})")
47
+
48
+ # Check if this is a reasoning model
49
+ is_reasoning = profile.config.template_type == TemplateType.REASONING
50
+
51
+ if is_reasoning:
52
+ self.console.print("\n[yellow]Note: This model includes internal reasoning/analysis in its output.[/yellow]")
53
+
54
+ # Show options
55
+ self.console.print("\nHow would you like to handle this model's output?\n")
56
+
57
+ options = []
58
+ if is_reasoning:
59
+ options = [
60
+ ("simple", "Simple mode - Hide internal reasoning (recommended)", True),
61
+ ("full", "Full mode - Show all model outputs", False),
62
+ ("custom", "Custom - Configure manually", False),
63
+ ("test", "Test - See examples of each mode", False)
64
+ ]
65
+ else:
66
+ options = [
67
+ ("auto", "Automatic - Use detected template", True),
68
+ ("custom", "Custom - Configure manually", False),
69
+ ("test", "Test - See examples with different templates", False)
70
+ ]
71
+
72
+ # Display options
73
+ for i, (key, desc, recommended) in enumerate(options, 1):
74
+ marker = " [green](recommended)[/green]" if recommended else ""
75
+ self.console.print(f"[{i}] {desc}{marker}")
76
+
77
+ # Get user choice
78
+ choice = Prompt.ask(
79
+ f"\nSelect option (1-{len(options)})",
80
+ default="1",
81
+ choices=[str(i) for i in range(1, len(options) + 1)]
82
+ )
83
+
84
+ selected_key = options[int(choice) - 1][0]
85
+
86
+ # Handle selection
87
+ if selected_key == "test":
88
+ self._show_template_tests(model_name, profile)
89
+ # Recurse to get actual selection
90
+ return self.setup_model_template(model_name, tokenizer, current_config)
91
+
92
+ elif selected_key == "custom":
93
+ return self._custom_setup(model_name, profile)
94
+
95
+ else:
96
+ # Create configuration
97
+ config = ModelTemplateConfig(
98
+ detected_type=profile.config.template_type.value,
99
+ user_preference=selected_key,
100
+ custom_filters=profile.config.custom_filters,
101
+ show_reasoning=(selected_key == "full") if is_reasoning else False,
102
+ confidence=confidence
103
+ )
104
+
105
+ self.console.print(f"\n✓ Template configured: [green]{selected_key} mode[/green]")
106
+ self.console.print("✓ Configuration saved for future use")
107
+ self.console.print("\n[dim]Tip: Use /template to adjust settings anytime[/dim]")
108
+
109
+ return config
110
+
111
+ def _show_template_tests(self, model_name: str, detected_profile: BaseTemplateProfile) -> None:
112
+ """Show examples of different template modes."""
113
+ self.console.print("\n[bold]Testing different template modes:[/bold]\n")
114
+
115
+ test_prompt = "What is 2+2?"
116
+
117
+ # Test different profiles
118
+ profiles_to_test = []
119
+
120
+ if detected_profile.config.template_type == TemplateType.REASONING:
121
+ # Test with and without reasoning
122
+ simple_profile = detected_profile.__class__()
123
+ simple_profile.config.show_reasoning = False
124
+
125
+ full_profile = detected_profile.__class__()
126
+ full_profile.config.show_reasoning = True
127
+
128
+ profiles_to_test = [
129
+ ("Simple Mode", simple_profile),
130
+ ("Full Mode", full_profile)
131
+ ]
132
+ else:
133
+ # Test different template types
134
+ from cortex.template_registry.template_profiles.standard import (
135
+ ChatMLProfile, LlamaProfile, SimpleProfile
136
+ )
137
+
138
+ profiles_to_test = [
139
+ ("Detected", detected_profile),
140
+ ("ChatML", ChatMLProfile()),
141
+ ("Llama", LlamaProfile()),
142
+ ("Simple", SimpleProfile())
143
+ ]
144
+
145
+ for name, profile in profiles_to_test:
146
+ result = self.detector.test_template(profile, test_prompt)
147
+
148
+ self.console.print(f"[bold cyan]{name}:[/bold cyan]")
149
+ self.console.print("─" * 40)
150
+
151
+ # Show formatted prompt
152
+ self.console.print("[dim]Formatted prompt:[/dim]")
153
+ self.console.print(f" {result['formatted_prompt'][:100]}..." if len(result['formatted_prompt']) > 100 else f" {result['formatted_prompt']}")
154
+
155
+ # Show processed response
156
+ self.console.print("[dim]Output:[/dim]")
157
+ self.console.print(f" {result['processed_response']}")
158
+ self.console.print()
159
+
160
+ def _custom_setup(self, model_name: str, detected_profile: BaseTemplateProfile) -> ModelTemplateConfig:
161
+ """Custom template configuration."""
162
+ self.console.print("\n[bold]Custom Template Configuration[/bold]\n")
163
+
164
+ # Select template type
165
+ template_types = [
166
+ ("chatml", "ChatML format"),
167
+ ("llama", "Llama format"),
168
+ ("alpaca", "Alpaca format"),
169
+ ("reasoning", "Reasoning/CoT format"),
170
+ ("simple", "Simple format")
171
+ ]
172
+
173
+ self.console.print("Available template types:")
174
+ for i, (key, desc) in enumerate(template_types, 1):
175
+ self.console.print(f"[{i}] {desc}")
176
+
177
+ choice = Prompt.ask(
178
+ "Select template type",
179
+ default="1",
180
+ choices=[str(i) for i in range(1, len(template_types) + 1)]
181
+ )
182
+
183
+ selected_type = template_types[int(choice) - 1][0]
184
+
185
+ # Configure filters
186
+ custom_filters = []
187
+ if Confirm.ask("Configure custom output filters?", default=False):
188
+ filters_input = Prompt.ask("Enter tokens to filter (comma-separated)")
189
+ custom_filters = [f.strip() for f in filters_input.split(",") if f.strip()]
190
+
191
+ # Show reasoning option
192
+ show_reasoning = False
193
+ if selected_type == "reasoning":
194
+ show_reasoning = Confirm.ask("Show internal reasoning/analysis?", default=False)
195
+
196
+ config = ModelTemplateConfig(
197
+ detected_type=selected_type,
198
+ user_preference="custom",
199
+ custom_filters=custom_filters,
200
+ show_reasoning=show_reasoning,
201
+ confidence=1.0 # User manually configured
202
+ )
203
+
204
+ self.console.print("\n✓ Custom template configured")
205
+ return config
206
+
207
+ def show_current_config(self, model_name: str, config: ModelTemplateConfig) -> None:
208
+ """Display current configuration for a model."""
209
+ table = Table(title=f"Template Configuration for {model_name}")
210
+ table.add_column("Setting", style="cyan")
211
+ table.add_column("Value", style="green")
212
+
213
+ table.add_row("Template Type", config.detected_type)
214
+ table.add_row("User Preference", config.user_preference)
215
+ table.add_row("Show Reasoning", str(config.show_reasoning))
216
+ table.add_row("Custom Filters", ", ".join(config.custom_filters) if config.custom_filters else "None")
217
+ table.add_row("Confidence", f"{config.confidence:.0%}")
218
+ table.add_row("Last Updated", config.last_updated)
219
+
220
+ self.console.print(table)
221
+
222
+ def quick_adjust_template(self, model_name: str, config: ModelTemplateConfig) -> ModelTemplateConfig:
223
+ """Quick adjustment interface for template settings."""
224
+ self.console.print(f"\n[bold]Adjust template for {model_name}[/bold]\n")
225
+
226
+ self.show_current_config(model_name, config)
227
+
228
+ self.console.print("\n[1] Toggle reasoning display")
229
+ self.console.print("[2] Change template type")
230
+ self.console.print("[3] Edit filters")
231
+ self.console.print("[4] Reset to defaults")
232
+ self.console.print("[0] Cancel")
233
+
234
+ choice = Prompt.ask("Select option", choices=["0", "1", "2", "3", "4"])
235
+
236
+ if choice == "1":
237
+ config.show_reasoning = not config.show_reasoning
238
+ self.console.print(f"✓ Reasoning display: [green]{'enabled' if config.show_reasoning else 'disabled'}[/green]")
239
+
240
+ elif choice == "2":
241
+ return self._custom_setup(model_name, None)
242
+
243
+ elif choice == "3":
244
+ filters_input = Prompt.ask("Enter tokens to filter (comma-separated)", default=",".join(config.custom_filters))
245
+ config.custom_filters = [f.strip() for f in filters_input.split(",") if f.strip()]
246
+ self.console.print("✓ Filters updated")
247
+
248
+ elif choice == "4":
249
+ # Reset to detected defaults
250
+ profile, confidence = self.detector.detect_template(model_name)
251
+ config = ModelTemplateConfig(
252
+ detected_type=profile.config.template_type.value,
253
+ user_preference="auto",
254
+ custom_filters=profile.config.custom_filters,
255
+ show_reasoning=False,
256
+ confidence=confidence
257
+ )
258
+ self.console.print("✓ Reset to defaults")
259
+
260
+ return config