parishad 0.1.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.
- parishad/__init__.py +70 -0
- parishad/__main__.py +10 -0
- parishad/checker/__init__.py +25 -0
- parishad/checker/deterministic.py +644 -0
- parishad/checker/ensemble.py +496 -0
- parishad/checker/retrieval.py +546 -0
- parishad/cli/__init__.py +6 -0
- parishad/cli/code.py +3254 -0
- parishad/cli/main.py +1158 -0
- parishad/cli/prarambh.py +99 -0
- parishad/cli/sthapana.py +368 -0
- parishad/config/modes.py +139 -0
- parishad/config/pipeline.core.yaml +128 -0
- parishad/config/pipeline.extended.yaml +172 -0
- parishad/config/pipeline.fast.yaml +89 -0
- parishad/config/user_config.py +115 -0
- parishad/data/catalog.py +118 -0
- parishad/data/models.json +108 -0
- parishad/memory/__init__.py +79 -0
- parishad/models/__init__.py +181 -0
- parishad/models/backends/__init__.py +247 -0
- parishad/models/backends/base.py +211 -0
- parishad/models/backends/huggingface.py +318 -0
- parishad/models/backends/llama_cpp.py +239 -0
- parishad/models/backends/mlx_lm.py +141 -0
- parishad/models/backends/ollama.py +253 -0
- parishad/models/backends/openai_api.py +193 -0
- parishad/models/backends/transformers_hf.py +198 -0
- parishad/models/costs.py +385 -0
- parishad/models/downloader.py +1557 -0
- parishad/models/optimizations.py +871 -0
- parishad/models/profiles.py +610 -0
- parishad/models/reliability.py +876 -0
- parishad/models/runner.py +651 -0
- parishad/models/tokenization.py +287 -0
- parishad/orchestrator/__init__.py +24 -0
- parishad/orchestrator/config_loader.py +210 -0
- parishad/orchestrator/engine.py +1113 -0
- parishad/orchestrator/exceptions.py +14 -0
- parishad/roles/__init__.py +71 -0
- parishad/roles/base.py +712 -0
- parishad/roles/dandadhyaksha.py +163 -0
- parishad/roles/darbari.py +246 -0
- parishad/roles/majumdar.py +274 -0
- parishad/roles/pantapradhan.py +150 -0
- parishad/roles/prerak.py +357 -0
- parishad/roles/raja.py +345 -0
- parishad/roles/sacheev.py +203 -0
- parishad/roles/sainik.py +427 -0
- parishad/roles/sar_senapati.py +164 -0
- parishad/roles/vidushak.py +69 -0
- parishad/tools/__init__.py +7 -0
- parishad/tools/base.py +57 -0
- parishad/tools/fs.py +110 -0
- parishad/tools/perception.py +96 -0
- parishad/tools/retrieval.py +74 -0
- parishad/tools/shell.py +103 -0
- parishad/utils/__init__.py +7 -0
- parishad/utils/hardware.py +122 -0
- parishad/utils/logging.py +79 -0
- parishad/utils/scanner.py +164 -0
- parishad/utils/text.py +61 -0
- parishad/utils/tracing.py +133 -0
- parishad-0.1.0.dist-info/METADATA +256 -0
- parishad-0.1.0.dist-info/RECORD +68 -0
- parishad-0.1.0.dist-info/WHEEL +4 -0
- parishad-0.1.0.dist-info/entry_points.txt +2 -0
- parishad-0.1.0.dist-info/licenses/LICENSE +21 -0
parishad/cli/main.py
ADDED
|
@@ -0,0 +1,1158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parishad CLI - Command line interface for the Parishad council.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.syntax import Syntax
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
17
|
+
|
|
18
|
+
from ..orchestrator.engine import Parishad, ParishadEngine, PipelineConfig
|
|
19
|
+
from ..models.runner import ModelConfig
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Setup encoding for Windows
|
|
23
|
+
if sys.platform == "win32":
|
|
24
|
+
try:
|
|
25
|
+
# Try to reconfigure stdout/stderr to use UTF-8
|
|
26
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
27
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
28
|
+
if hasattr(sys.stderr, 'reconfigure'):
|
|
29
|
+
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
|
30
|
+
except Exception:
|
|
31
|
+
# If reconfigure fails, we'll just avoid Unicode characters
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
# Create console with error handling for encoding issues
|
|
35
|
+
try:
|
|
36
|
+
console = Console()
|
|
37
|
+
# Simple test - just create console, don't test Unicode output
|
|
38
|
+
UNICODE_SUPPORTED = sys.platform != "win32" # Disable Unicode on Windows by default
|
|
39
|
+
except Exception:
|
|
40
|
+
UNICODE_SUPPORTED = False
|
|
41
|
+
console = Console()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_config_dir() -> Path:
|
|
45
|
+
"""Get the default configuration directory."""
|
|
46
|
+
# Check for local config first
|
|
47
|
+
local_config = Path("./parishad/config")
|
|
48
|
+
if local_config.exists():
|
|
49
|
+
return local_config
|
|
50
|
+
|
|
51
|
+
# Fall back to package config
|
|
52
|
+
package_dir = Path(__file__).parent.parent
|
|
53
|
+
return package_dir / "config"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_parishad_dir() -> Path:
|
|
57
|
+
"""Get the .parishad directory path (cross-platform)."""
|
|
58
|
+
return Path.home() / ".parishad"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def is_first_run() -> bool:
|
|
62
|
+
"""Check if this is the first time parishad is being run."""
|
|
63
|
+
parishad_dir = get_parishad_dir()
|
|
64
|
+
return not parishad_dir.exists()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def first_run() -> bool:
|
|
68
|
+
"""
|
|
69
|
+
Handle first-time setup permissions.
|
|
70
|
+
|
|
71
|
+
Asks for:
|
|
72
|
+
1. Permission to read files from the system
|
|
73
|
+
2. Permission to create ~/.parishad directory
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
True if setup completed, False if user declined
|
|
77
|
+
"""
|
|
78
|
+
from .code import LOGO
|
|
79
|
+
console.print(LOGO)
|
|
80
|
+
console.print("[dim]पारिषद् में आपका स्वागत है![/dim]")
|
|
81
|
+
console.print()
|
|
82
|
+
|
|
83
|
+
# Permission 1: Read access
|
|
84
|
+
console.print("[yellow]⚠️ Parishad needs permission to read files from your system.[/yellow]")
|
|
85
|
+
console.print("[dim]This allows the coding assistant to understand your codebase[/dim]")
|
|
86
|
+
console.print("[dim]and scan for existing model files to save download time.[/dim]")
|
|
87
|
+
console.print()
|
|
88
|
+
|
|
89
|
+
read_permission = click.confirm(
|
|
90
|
+
"Grant read permission?",
|
|
91
|
+
default=True
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if not read_permission:
|
|
95
|
+
console.print("[red]Read permission denied. Parishad cannot function without this.[/red]")
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
console.print("[green]✓ Read permission granted.[/green]")
|
|
99
|
+
console.print()
|
|
100
|
+
|
|
101
|
+
# Permission 2: Write access to create .parishad directory
|
|
102
|
+
parishad_dir = get_parishad_dir()
|
|
103
|
+
console.print(f"[yellow]⚠️ Parishad needs to create a folder at:[/yellow]")
|
|
104
|
+
console.print(f" [bold]{parishad_dir}[/bold]")
|
|
105
|
+
console.print("[dim]This stores your configuration, history, and cached data.[/dim]")
|
|
106
|
+
console.print()
|
|
107
|
+
|
|
108
|
+
write_permission = click.confirm(
|
|
109
|
+
"Grant write permission to create this folder?",
|
|
110
|
+
default=True
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if not write_permission:
|
|
114
|
+
console.print("[red]Write permission denied. Parishad cannot save settings.[/red]")
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
# Create the directory & Gather Info
|
|
118
|
+
try:
|
|
119
|
+
parishad_dir.mkdir(parents=True, exist_ok=True)
|
|
120
|
+
|
|
121
|
+
# 1. Gather System Info (Silent)
|
|
122
|
+
from ..utils.hardware import get_system_info
|
|
123
|
+
sys_info = get_system_info()
|
|
124
|
+
|
|
125
|
+
# 2. Gather Model Inventory (Deep Silent Scan)
|
|
126
|
+
from ..utils.scanner import ModelScanner
|
|
127
|
+
scanner = ModelScanner()
|
|
128
|
+
# Fast scan (Ollama/HF)
|
|
129
|
+
found_models = scanner.scan_all()
|
|
130
|
+
# Deep scan (Home dir)
|
|
131
|
+
deep_models = scanner.scan_directory(Path.home())
|
|
132
|
+
if deep_models:
|
|
133
|
+
found_models.extend(deep_models)
|
|
134
|
+
|
|
135
|
+
# Convert models to dict for JSON
|
|
136
|
+
inventory = []
|
|
137
|
+
for m in found_models:
|
|
138
|
+
inventory.append({
|
|
139
|
+
"name": m.name,
|
|
140
|
+
"source": m.source,
|
|
141
|
+
"size_gb": m.size_gb,
|
|
142
|
+
"path": m.path
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
# 3. Create Enhanced Config
|
|
146
|
+
config_file = parishad_dir / "config.json"
|
|
147
|
+
initial_config = {
|
|
148
|
+
"version": "0.1.0",
|
|
149
|
+
"first_run_complete": True,
|
|
150
|
+
"permissions": {
|
|
151
|
+
"read": True,
|
|
152
|
+
"write": True
|
|
153
|
+
},
|
|
154
|
+
"system": sys_info.to_dict(),
|
|
155
|
+
"inventory": inventory
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
config_file.write_text(json.dumps(initial_config, indent=2))
|
|
159
|
+
|
|
160
|
+
console.print(f"[green]✓ Created {parishad_dir}[/green]")
|
|
161
|
+
console.print()
|
|
162
|
+
console.print("[bold green]Setup complete! Starting Parishad...[/bold green]")
|
|
163
|
+
console.print()
|
|
164
|
+
return True
|
|
165
|
+
|
|
166
|
+
except PermissionError:
|
|
167
|
+
console.print(f"[red]Error: Cannot create {parishad_dir}. Permission denied by OS.[/red]")
|
|
168
|
+
return False
|
|
169
|
+
except Exception as e:
|
|
170
|
+
console.print(f"[red]Error creating directory: {e}[/red]")
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@click.group(invoke_without_command=True)
|
|
175
|
+
@click.version_option(version="0.1.0", prog_name="parishad")
|
|
176
|
+
@click.pass_context
|
|
177
|
+
def cli(ctx):
|
|
178
|
+
"""
|
|
179
|
+
🏛️ Parishad - एक लोकसभा-शैली LLM प्रणाली विश्वसनीय तर्क के लिए।
|
|
180
|
+
|
|
181
|
+
पारिषद् लोकसभा (Parishad LokSabha) - A structured council of LLMs
|
|
182
|
+
for reliable reasoning with budget tracking and systematic verification.
|
|
183
|
+
|
|
184
|
+
Run 'parishad' without arguments to launch the interactive TUI.
|
|
185
|
+
|
|
186
|
+
Commands:
|
|
187
|
+
(no args) - Launch interactive TUI (with setup wizard on first run)
|
|
188
|
+
run - Execute a single query through the Sabha
|
|
189
|
+
config - View or modify configuration
|
|
190
|
+
sthapana - स्थापना (Setup) - Configure your Parishad Sabha
|
|
191
|
+
"""
|
|
192
|
+
if ctx.invoked_subcommand is None:
|
|
193
|
+
# First run - ask for permissions
|
|
194
|
+
if is_first_run():
|
|
195
|
+
if not first_run():
|
|
196
|
+
# User declined permissions, exit
|
|
197
|
+
sys.exit(0)
|
|
198
|
+
|
|
199
|
+
# Launch unified TUI
|
|
200
|
+
_launch_tui()
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _launch_tui(mode: Optional[str] = None):
|
|
204
|
+
"""
|
|
205
|
+
Launch the unified Parishad TUI with first-time setup detection.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
mode: Optional mode to start with ("fast"/"balanced"/"thorough")
|
|
209
|
+
"""
|
|
210
|
+
from pathlib import Path
|
|
211
|
+
|
|
212
|
+
# Check if first-time setup needed
|
|
213
|
+
config_dir = Path.home() / ".config" / "parishad"
|
|
214
|
+
config_file = config_dir / "config.json"
|
|
215
|
+
|
|
216
|
+
if not config_file.exists():
|
|
217
|
+
# First time - show setup wizard
|
|
218
|
+
console.print("[dim]First time setup detected...[/dim]")
|
|
219
|
+
# For now, go straight to TUI - setup wizard will be added in Phase 2
|
|
220
|
+
|
|
221
|
+
# Launch the TUI
|
|
222
|
+
from .code import run_code_cli
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
run_code_cli(mode=mode)
|
|
226
|
+
except KeyboardInterrupt:
|
|
227
|
+
console.print("\n[yellow]Session ended[/yellow]")
|
|
228
|
+
except Exception as e:
|
|
229
|
+
console.print(f"\n[red]Error:[/red] {e}")
|
|
230
|
+
sys.exit(1)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@cli.command()
|
|
234
|
+
@click.argument("query")
|
|
235
|
+
@click.option(
|
|
236
|
+
"--config", "-c",
|
|
237
|
+
type=click.Choice(["core", "extended", "fast"]),
|
|
238
|
+
default=None,
|
|
239
|
+
help="Pipeline configuration to use (overrides mode-based routing)"
|
|
240
|
+
)
|
|
241
|
+
@click.option(
|
|
242
|
+
"--mode",
|
|
243
|
+
type=click.Choice(["auto", "fast", "balanced", "thorough"]),
|
|
244
|
+
default=None,
|
|
245
|
+
help="Execution mode for adaptive routing (defaults to user config, fallback: balanced)"
|
|
246
|
+
)
|
|
247
|
+
@click.option(
|
|
248
|
+
"--no-retry",
|
|
249
|
+
is_flag=True,
|
|
250
|
+
help="Disable Worker+Checker retry regardless of Checker verdict"
|
|
251
|
+
)
|
|
252
|
+
@click.option(
|
|
253
|
+
"--model-config", "-m",
|
|
254
|
+
type=click.Path(exists=True),
|
|
255
|
+
help="Path to models.yaml configuration (defaults to ~/.parishad/models.yaml)"
|
|
256
|
+
)
|
|
257
|
+
@click.option(
|
|
258
|
+
"--profile",
|
|
259
|
+
type=str,
|
|
260
|
+
default=None,
|
|
261
|
+
help="Model profile to use (defaults to user config, fallback: local_cpu)"
|
|
262
|
+
)
|
|
263
|
+
@click.option(
|
|
264
|
+
"--pipeline-config", "-p",
|
|
265
|
+
type=click.Path(exists=True),
|
|
266
|
+
help="Path to pipeline configuration YAML"
|
|
267
|
+
)
|
|
268
|
+
@click.option(
|
|
269
|
+
"--trace-dir", "-t",
|
|
270
|
+
type=click.Path(),
|
|
271
|
+
help="Directory to save execution traces"
|
|
272
|
+
)
|
|
273
|
+
@click.option(
|
|
274
|
+
"--mock",
|
|
275
|
+
is_flag=True,
|
|
276
|
+
help="Use mock models for testing (returns empty responses)"
|
|
277
|
+
)
|
|
278
|
+
@click.option(
|
|
279
|
+
"--stub",
|
|
280
|
+
is_flag=True,
|
|
281
|
+
help="Use stub models with realistic role-specific responses"
|
|
282
|
+
)
|
|
283
|
+
@click.option(
|
|
284
|
+
"--verbose", "-v",
|
|
285
|
+
is_flag=True,
|
|
286
|
+
help="Show verbose output including role details"
|
|
287
|
+
)
|
|
288
|
+
@click.option(
|
|
289
|
+
"--json-output",
|
|
290
|
+
is_flag=True,
|
|
291
|
+
help="Output results as JSON"
|
|
292
|
+
)
|
|
293
|
+
def run(
|
|
294
|
+
query: str,
|
|
295
|
+
config: Optional[str],
|
|
296
|
+
mode: Optional[str],
|
|
297
|
+
no_retry: bool,
|
|
298
|
+
model_config: Optional[str],
|
|
299
|
+
profile: Optional[str],
|
|
300
|
+
pipeline_config: Optional[str],
|
|
301
|
+
trace_dir: Optional[str],
|
|
302
|
+
mock: bool,
|
|
303
|
+
stub: bool,
|
|
304
|
+
verbose: bool,
|
|
305
|
+
json_output: bool
|
|
306
|
+
):
|
|
307
|
+
"""
|
|
308
|
+
Run a query through the Parishad council.
|
|
309
|
+
|
|
310
|
+
QUERY is the task or question to process.
|
|
311
|
+
|
|
312
|
+
Execution modes control adaptive routing:
|
|
313
|
+
- auto: Let Router decide based on task characteristics
|
|
314
|
+
- fast: Minimal 3-role pipeline for simple queries
|
|
315
|
+
- balanced: Standard 5-role pipeline (default)
|
|
316
|
+
- thorough: Extended pipeline with specialized roles
|
|
317
|
+
|
|
318
|
+
Profile and mode defaults are loaded from ~/.parishad/config.yaml.
|
|
319
|
+
CLI flags override user defaults.
|
|
320
|
+
|
|
321
|
+
Examples:
|
|
322
|
+
|
|
323
|
+
parishad run "Write a Python function to compute fibonacci"
|
|
324
|
+
|
|
325
|
+
parishad run --mode fast "What is 2+2?"
|
|
326
|
+
|
|
327
|
+
parishad run --mode thorough "Explain quantum entanglement"
|
|
328
|
+
|
|
329
|
+
parishad run --config extended --no-retry "Complex task"
|
|
330
|
+
|
|
331
|
+
parishad run --mock "Test query"
|
|
332
|
+
|
|
333
|
+
parishad run --stub "Test with realistic responses"
|
|
334
|
+
"""
|
|
335
|
+
from ..config.user_config import load_user_config
|
|
336
|
+
|
|
337
|
+
# Load user config for defaults
|
|
338
|
+
user_cfg = load_user_config()
|
|
339
|
+
|
|
340
|
+
# Handle stub/mock overrides first
|
|
341
|
+
if stub:
|
|
342
|
+
profile = "stub"
|
|
343
|
+
elif mock:
|
|
344
|
+
profile = "mock"
|
|
345
|
+
elif profile is None:
|
|
346
|
+
profile = user_cfg.default_profile
|
|
347
|
+
|
|
348
|
+
if mode is None:
|
|
349
|
+
mode = user_cfg.default_mode
|
|
350
|
+
|
|
351
|
+
# Resolve config paths
|
|
352
|
+
config_dir = get_config_dir()
|
|
353
|
+
|
|
354
|
+
if not model_config:
|
|
355
|
+
# When using stub/mock, always use package config which has those profiles
|
|
356
|
+
if stub or mock:
|
|
357
|
+
default_model_config = config_dir / "models.yaml"
|
|
358
|
+
if default_model_config.exists():
|
|
359
|
+
model_config = str(default_model_config)
|
|
360
|
+
else:
|
|
361
|
+
# For real models, check user config directory first
|
|
362
|
+
user_model_config = Path.home() / ".parishad" / "models.yaml"
|
|
363
|
+
if user_model_config.exists():
|
|
364
|
+
model_config = str(user_model_config)
|
|
365
|
+
else:
|
|
366
|
+
# Fall back to package config
|
|
367
|
+
default_model_config = config_dir / "models.yaml"
|
|
368
|
+
if default_model_config.exists():
|
|
369
|
+
model_config = str(default_model_config)
|
|
370
|
+
|
|
371
|
+
# Task 4: If user explicitly set --config, use it; otherwise let Router decide
|
|
372
|
+
user_forced_config = config # None if not set, or "core"/"extended"/"fast"
|
|
373
|
+
if not config:
|
|
374
|
+
config = "core" # Default starting point for Router
|
|
375
|
+
|
|
376
|
+
if not pipeline_config:
|
|
377
|
+
if config == "core":
|
|
378
|
+
default_pipeline = config_dir / "pipeline.core.yaml"
|
|
379
|
+
elif config == "fast":
|
|
380
|
+
default_pipeline = config_dir / "pipeline.fast.yaml"
|
|
381
|
+
else:
|
|
382
|
+
default_pipeline = config_dir / "pipeline.extended.yaml"
|
|
383
|
+
if default_pipeline.exists():
|
|
384
|
+
pipeline_config = str(default_pipeline)
|
|
385
|
+
|
|
386
|
+
# Show progress
|
|
387
|
+
if not json_output:
|
|
388
|
+
# Use ASCII-safe title to avoid Windows encoding issues
|
|
389
|
+
title = "Parishad LokSabha" if not UNICODE_SUPPORTED else "🏛️ पारिषद् लोकसभा"
|
|
390
|
+
|
|
391
|
+
# Build subtitle with current settings
|
|
392
|
+
subtitle_parts = []
|
|
393
|
+
if user_forced_config:
|
|
394
|
+
subtitle_parts.append(f"Config: {config}")
|
|
395
|
+
else:
|
|
396
|
+
subtitle_parts.append(f"Mode: {mode}")
|
|
397
|
+
subtitle_parts.append(f"Profile: {profile}")
|
|
398
|
+
if no_retry:
|
|
399
|
+
subtitle_parts.append("no-retry")
|
|
400
|
+
subtitle = " | ".join(subtitle_parts)
|
|
401
|
+
|
|
402
|
+
console.print(Panel(
|
|
403
|
+
f"[bold blue]प्रश्न (Query):[/bold blue] {query}",
|
|
404
|
+
title=title,
|
|
405
|
+
subtitle=subtitle
|
|
406
|
+
))
|
|
407
|
+
|
|
408
|
+
try:
|
|
409
|
+
with Progress(
|
|
410
|
+
SpinnerColumn(),
|
|
411
|
+
TextColumn("[progress.description]{task.description}"),
|
|
412
|
+
console=console,
|
|
413
|
+
disable=json_output
|
|
414
|
+
) as progress:
|
|
415
|
+
task = progress.add_task("लोकसभा विचार-विमर्श जारी... (Sabha deliberating...)", total=None)
|
|
416
|
+
|
|
417
|
+
# Task 4: Initialize with mode and no_retry parameters
|
|
418
|
+
parishad = Parishad(
|
|
419
|
+
config=config,
|
|
420
|
+
model_config_path=model_config,
|
|
421
|
+
profile=profile,
|
|
422
|
+
pipeline_config_path=pipeline_config,
|
|
423
|
+
trace_dir=trace_dir,
|
|
424
|
+
mock=mock,
|
|
425
|
+
stub=stub,
|
|
426
|
+
mode=mode,
|
|
427
|
+
user_forced_config=user_forced_config,
|
|
428
|
+
no_retry=no_retry,
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
trace = parishad.run(query)
|
|
432
|
+
|
|
433
|
+
progress.update(task, description="Complete!")
|
|
434
|
+
|
|
435
|
+
# Output results
|
|
436
|
+
if json_output:
|
|
437
|
+
print(trace.to_json())
|
|
438
|
+
else:
|
|
439
|
+
display_results(trace, verbose)
|
|
440
|
+
|
|
441
|
+
except Exception as e:
|
|
442
|
+
if json_output:
|
|
443
|
+
print(json.dumps({"error": str(e)}))
|
|
444
|
+
else:
|
|
445
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
446
|
+
sys.exit(1)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def display_results(trace, verbose: bool = False):
|
|
450
|
+
"""Display execution results in a nice format."""
|
|
451
|
+
# Final answer panel
|
|
452
|
+
if trace.final_answer:
|
|
453
|
+
answer = trace.final_answer.final_answer
|
|
454
|
+
answer_type = trace.final_answer.answer_type
|
|
455
|
+
confidence = trace.final_answer.confidence
|
|
456
|
+
|
|
457
|
+
# Use ASCII-safe icons
|
|
458
|
+
answer_icon = "Answer" if not UNICODE_SUPPORTED else "📝 Answer"
|
|
459
|
+
code_icon = "Code" if not UNICODE_SUPPORTED else "📝 Code"
|
|
460
|
+
|
|
461
|
+
# Format code nicely
|
|
462
|
+
if answer_type == "code" and trace.final_answer.code_block:
|
|
463
|
+
code = trace.final_answer.code_block
|
|
464
|
+
syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
|
|
465
|
+
console.print(Panel(
|
|
466
|
+
syntax,
|
|
467
|
+
title=f"{code_icon}",
|
|
468
|
+
subtitle=f"Confidence: {confidence:.0%}"
|
|
469
|
+
))
|
|
470
|
+
else:
|
|
471
|
+
console.print(Panel(
|
|
472
|
+
answer,
|
|
473
|
+
title=f"{answer_icon}",
|
|
474
|
+
subtitle=f"Confidence: {confidence:.0%} | Type: {answer_type}"
|
|
475
|
+
))
|
|
476
|
+
|
|
477
|
+
# Show caveats if any
|
|
478
|
+
if trace.final_answer.caveats:
|
|
479
|
+
console.print("\n[yellow]Caveats:[/yellow]")
|
|
480
|
+
for caveat in trace.final_answer.caveats:
|
|
481
|
+
console.print(f" • {caveat}")
|
|
482
|
+
|
|
483
|
+
# Summary table
|
|
484
|
+
console.print()
|
|
485
|
+
table = Table(title="Execution Summary")
|
|
486
|
+
table.add_column("Metric", style="cyan")
|
|
487
|
+
table.add_column("Value", style="green")
|
|
488
|
+
|
|
489
|
+
table.add_row("Query ID", trace.query_id[:8] + "...")
|
|
490
|
+
table.add_row("Config", trace.config)
|
|
491
|
+
table.add_row("Total Tokens", str(trace.total_tokens))
|
|
492
|
+
table.add_row("Total Latency", f"{trace.total_latency_ms}ms")
|
|
493
|
+
table.add_row("Budget Used", f"{trace.budget_initial - trace.budget_remaining}/{trace.budget_initial}")
|
|
494
|
+
table.add_row("Retries", str(trace.retries))
|
|
495
|
+
table.add_row("Success", "✓" if trace.success else "✗")
|
|
496
|
+
|
|
497
|
+
console.print(table)
|
|
498
|
+
|
|
499
|
+
# Verbose role details
|
|
500
|
+
if verbose:
|
|
501
|
+
console.print("\n[bold]Role Execution Details:[/bold]")
|
|
502
|
+
|
|
503
|
+
role_table = Table()
|
|
504
|
+
role_table.add_column("Role", style="cyan")
|
|
505
|
+
role_table.add_column("Slot", style="magenta")
|
|
506
|
+
role_table.add_column("Tokens", style="green")
|
|
507
|
+
role_table.add_column("Latency", style="yellow")
|
|
508
|
+
role_table.add_column("Status", style="blue")
|
|
509
|
+
|
|
510
|
+
for role_output in trace.roles:
|
|
511
|
+
role_table.add_row(
|
|
512
|
+
role_output.role,
|
|
513
|
+
role_output.metadata.slot.value if hasattr(role_output.metadata.slot, 'value') else str(role_output.metadata.slot),
|
|
514
|
+
str(role_output.metadata.tokens_used),
|
|
515
|
+
f"{role_output.metadata.latency_ms}ms",
|
|
516
|
+
role_output.status
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
console.print(role_table)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
@cli.command()
|
|
531
|
+
@click.argument("trace_file", type=click.Path(exists=True))
|
|
532
|
+
def inspect(trace_file: str):
|
|
533
|
+
"""
|
|
534
|
+
Inspect an execution trace file.
|
|
535
|
+
|
|
536
|
+
TRACE_FILE is the path to a trace JSON file.
|
|
537
|
+
"""
|
|
538
|
+
with open(trace_file) as f:
|
|
539
|
+
trace_data = json.load(f)
|
|
540
|
+
|
|
541
|
+
console.print(Panel(
|
|
542
|
+
f"[bold]Query ID:[/bold] {trace_data.get('query_id', 'unknown')}\n"
|
|
543
|
+
f"[bold]Query:[/bold] {trace_data.get('user_query', 'unknown')[:100]}...",
|
|
544
|
+
title="📋 Trace Inspection"
|
|
545
|
+
))
|
|
546
|
+
|
|
547
|
+
# Show role outputs
|
|
548
|
+
roles = trace_data.get("roles", [])
|
|
549
|
+
for role_output in roles:
|
|
550
|
+
role_name = role_output.get("role", "unknown")
|
|
551
|
+
status = role_output.get("status", "unknown")
|
|
552
|
+
output = role_output.get("output", {})
|
|
553
|
+
|
|
554
|
+
console.print(f"\n[bold cyan]== {role_name.upper()} ==[/bold cyan]")
|
|
555
|
+
console.print(f"Status: {status}")
|
|
556
|
+
|
|
557
|
+
# Pretty print output
|
|
558
|
+
if output:
|
|
559
|
+
output_str = json.dumps(output, indent=2)[:1000]
|
|
560
|
+
console.print(Syntax(output_str, "json", theme="monokai"))
|
|
561
|
+
|
|
562
|
+
# Final answer
|
|
563
|
+
final = trace_data.get("final_answer")
|
|
564
|
+
if final:
|
|
565
|
+
console.print("\n[bold green]== FINAL ANSWER ==[/bold green]")
|
|
566
|
+
console.print(final.get("final_answer", "No answer"))
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
@cli.command()
|
|
570
|
+
def init():
|
|
571
|
+
"""
|
|
572
|
+
Initialize Parishad configuration in the current directory.
|
|
573
|
+
|
|
574
|
+
Creates a config/ directory with example configuration files.
|
|
575
|
+
"""
|
|
576
|
+
config_dir = Path("./config")
|
|
577
|
+
config_dir.mkdir(exist_ok=True)
|
|
578
|
+
|
|
579
|
+
# Copy example configs
|
|
580
|
+
package_config = get_config_dir()
|
|
581
|
+
|
|
582
|
+
files_to_copy = [
|
|
583
|
+
"models.example.yaml",
|
|
584
|
+
"pipeline.core.yaml",
|
|
585
|
+
"pipeline.extended.yaml"
|
|
586
|
+
]
|
|
587
|
+
|
|
588
|
+
for filename in files_to_copy:
|
|
589
|
+
src = package_config / filename
|
|
590
|
+
dst = config_dir / filename
|
|
591
|
+
|
|
592
|
+
if src.exists() and not dst.exists():
|
|
593
|
+
dst.write_text(src.read_text())
|
|
594
|
+
console.print(f"[green]Created:[/green] {dst}")
|
|
595
|
+
elif dst.exists():
|
|
596
|
+
console.print(f"[yellow]Skipped (exists):[/yellow] {dst}")
|
|
597
|
+
|
|
598
|
+
console.print("\n[bold]Configuration initialized![/bold]")
|
|
599
|
+
console.print("Edit config/models.example.yaml and rename to models.yaml")
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
@cli.command()
|
|
603
|
+
def info():
|
|
604
|
+
"""
|
|
605
|
+
Show information about Parishad and available configurations.
|
|
606
|
+
"""
|
|
607
|
+
console.print(Panel(
|
|
608
|
+
"[bold]Parishad[/bold] - Cost-aware Council of LLMs\n\n"
|
|
609
|
+
"A local-first system that orchestrates multiple LLMs into a structured\n"
|
|
610
|
+
"council for reliable reasoning, coding, and factual correctness.\n\n"
|
|
611
|
+
"[bold]Configurations:[/bold]\n"
|
|
612
|
+
" • core - 5 roles: Refiner, Planner, Worker, Checker, Judge\n"
|
|
613
|
+
" • extended - 9 roles: Specialized variants of each role\n\n"
|
|
614
|
+
"[bold]Model Slots:[/bold]\n"
|
|
615
|
+
" • small - 2-4B models for Refiner, Checker\n"
|
|
616
|
+
" • mid - 7-13B models for Worker\n"
|
|
617
|
+
" • big - 13-34B models for Planner, Judge",
|
|
618
|
+
title="🏛️ Parishad Info"
|
|
619
|
+
))
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
# =============================================================================
|
|
623
|
+
# Model Management Commands
|
|
624
|
+
# =============================================================================
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
@cli.group()
|
|
628
|
+
def models():
|
|
629
|
+
"""
|
|
630
|
+
Manage LLM models (download, list, remove).
|
|
631
|
+
|
|
632
|
+
Download models from HuggingFace, Ollama, or LM Studio.
|
|
633
|
+
"""
|
|
634
|
+
pass
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
@models.command("list")
|
|
638
|
+
@click.option(
|
|
639
|
+
"--source", "-s",
|
|
640
|
+
type=click.Choice(["all", "huggingface", "ollama", "lmstudio"]),
|
|
641
|
+
default="all",
|
|
642
|
+
help="Filter by source"
|
|
643
|
+
)
|
|
644
|
+
@click.option("--json-output", is_flag=True, help="Output as JSON")
|
|
645
|
+
def list_models(source: str, json_output: bool):
|
|
646
|
+
"""List downloaded models."""
|
|
647
|
+
from ..models.downloader import ModelManager
|
|
648
|
+
|
|
649
|
+
manager = ModelManager()
|
|
650
|
+
|
|
651
|
+
# Scan for any unregistered models
|
|
652
|
+
manager.scan_for_models()
|
|
653
|
+
|
|
654
|
+
source_filter = source if source != "all" else None
|
|
655
|
+
models = manager.list_models(source_filter)
|
|
656
|
+
|
|
657
|
+
if json_output:
|
|
658
|
+
print(json.dumps([m.to_dict() for m in models], indent=2, default=str))
|
|
659
|
+
return
|
|
660
|
+
|
|
661
|
+
if not models:
|
|
662
|
+
console.print("[yellow]No models found.[/yellow]")
|
|
663
|
+
console.print("Use [bold]parishad models download[/bold] to download models.")
|
|
664
|
+
return
|
|
665
|
+
|
|
666
|
+
table = Table(title="Downloaded Models")
|
|
667
|
+
table.add_column("Name", style="cyan")
|
|
668
|
+
table.add_column("Source", style="green")
|
|
669
|
+
table.add_column("Format", style="blue")
|
|
670
|
+
table.add_column("Size", style="yellow")
|
|
671
|
+
table.add_column("Quantization", style="magenta")
|
|
672
|
+
|
|
673
|
+
for model in models:
|
|
674
|
+
table.add_row(
|
|
675
|
+
model.name,
|
|
676
|
+
model.source.value,
|
|
677
|
+
model.format.value,
|
|
678
|
+
model.size_human,
|
|
679
|
+
model.quantization or "-",
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
console.print(table)
|
|
683
|
+
console.print(f"\n[dim]Model directory: {manager.model_dir}[/dim]")
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
@models.command("download")
|
|
687
|
+
@click.argument("model_name")
|
|
688
|
+
@click.option(
|
|
689
|
+
"--source", "-s",
|
|
690
|
+
type=click.Choice(["auto", "huggingface", "ollama", "lmstudio"]),
|
|
691
|
+
default="auto",
|
|
692
|
+
help="Source to download from"
|
|
693
|
+
)
|
|
694
|
+
@click.option(
|
|
695
|
+
"--quantization", "-q",
|
|
696
|
+
help="Preferred quantization (e.g., q4_k_m, q8_0)"
|
|
697
|
+
)
|
|
698
|
+
def download_model(model_name: str, source: str, quantization: Optional[str]):
|
|
699
|
+
"""
|
|
700
|
+
Download a model.
|
|
701
|
+
|
|
702
|
+
MODEL_NAME can be:
|
|
703
|
+
|
|
704
|
+
\b
|
|
705
|
+
- A shortcut: qwen2.5:1.5b, llama3.2:1b, phi3:mini
|
|
706
|
+
- HuggingFace: owner/repo/file.gguf
|
|
707
|
+
- Ollama: llama3.2:1b (requires Ollama installed)
|
|
708
|
+
- LM Studio: path/to/model.gguf (from LM Studio's models dir)
|
|
709
|
+
|
|
710
|
+
\b
|
|
711
|
+
Examples:
|
|
712
|
+
parishad models download qwen2.5:1.5b
|
|
713
|
+
parishad models download llama3.2:1b --source ollama
|
|
714
|
+
parishad models download TheBloke/Llama-2-7B-GGUF/llama-2-7b.Q4_K_M.gguf
|
|
715
|
+
"""
|
|
716
|
+
from ..models.downloader import ModelManager, print_progress
|
|
717
|
+
|
|
718
|
+
manager = ModelManager()
|
|
719
|
+
|
|
720
|
+
# Check sources
|
|
721
|
+
sources = manager.get_available_sources()
|
|
722
|
+
|
|
723
|
+
console.print(f"\n[bold]Downloading:[/bold] {model_name}")
|
|
724
|
+
console.print(f"[dim]Source: {source}[/dim]")
|
|
725
|
+
|
|
726
|
+
if source == "ollama" and not sources["ollama"]:
|
|
727
|
+
console.print("[red]Error:[/red] Ollama is not installed or not running.")
|
|
728
|
+
console.print("Install from: https://ollama.ai")
|
|
729
|
+
sys.exit(1)
|
|
730
|
+
|
|
731
|
+
if source == "lmstudio" and not sources["lmstudio"]:
|
|
732
|
+
console.print("[red]Error:[/red] LM Studio models directory not found.")
|
|
733
|
+
sys.exit(1)
|
|
734
|
+
|
|
735
|
+
try:
|
|
736
|
+
from rich.progress import Progress, BarColumn, DownloadColumn, TransferSpeedColumn, TimeRemainingColumn
|
|
737
|
+
|
|
738
|
+
with Progress(
|
|
739
|
+
"[progress.description]{task.description}",
|
|
740
|
+
BarColumn(),
|
|
741
|
+
"[progress.percentage]{task.percentage:>3.1f}%",
|
|
742
|
+
DownloadColumn(),
|
|
743
|
+
TransferSpeedColumn(),
|
|
744
|
+
TimeRemainingColumn(),
|
|
745
|
+
console=console,
|
|
746
|
+
) as progress:
|
|
747
|
+
task = progress.add_task(f"Downloading {model_name}", total=None)
|
|
748
|
+
|
|
749
|
+
def progress_callback(p):
|
|
750
|
+
"""Update Rich progress bar."""
|
|
751
|
+
if p.total_bytes > 0:
|
|
752
|
+
progress.update(task, total=p.total_bytes, completed=p.downloaded_bytes)
|
|
753
|
+
|
|
754
|
+
model = manager.download(
|
|
755
|
+
model_name,
|
|
756
|
+
source=source,
|
|
757
|
+
quantization=quantization,
|
|
758
|
+
progress_callback=progress_callback,
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
console.print(f"\n[green]✓ Downloaded:[/green] {model.name}")
|
|
762
|
+
console.print(f" [dim]Path: {model.path}[/dim]")
|
|
763
|
+
console.print(f" [dim]Size: {model.size_human}[/dim]")
|
|
764
|
+
|
|
765
|
+
except Exception as e:
|
|
766
|
+
console.print(f"\n[red]Error:[/red] {e}")
|
|
767
|
+
sys.exit(1)
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
@models.command("remove")
|
|
771
|
+
@click.argument("model_name")
|
|
772
|
+
@click.option("--keep-files", is_flag=True, help="Keep model files, only remove from registry")
|
|
773
|
+
def remove_model(model_name: str, keep_files: bool):
|
|
774
|
+
"""Remove a downloaded model."""
|
|
775
|
+
from ..models.downloader import ModelManager
|
|
776
|
+
|
|
777
|
+
manager = ModelManager()
|
|
778
|
+
|
|
779
|
+
model = manager.registry.get(model_name)
|
|
780
|
+
if not model:
|
|
781
|
+
console.print(f"[red]Error:[/red] Model not found: {model_name}")
|
|
782
|
+
console.print("Use [bold]parishad models list[/bold] to see available models.")
|
|
783
|
+
sys.exit(1)
|
|
784
|
+
|
|
785
|
+
# Confirm
|
|
786
|
+
if not keep_files:
|
|
787
|
+
console.print(f"[yellow]Warning:[/yellow] This will delete: {model.path}")
|
|
788
|
+
if not click.confirm("Continue?"):
|
|
789
|
+
console.print("Cancelled.")
|
|
790
|
+
return
|
|
791
|
+
|
|
792
|
+
if manager.remove_model(model_name, delete_files=not keep_files):
|
|
793
|
+
console.print(f"[green]✓ Removed:[/green] {model_name}")
|
|
794
|
+
else:
|
|
795
|
+
console.print(f"[red]Error:[/red] Failed to remove model")
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
@models.command("available")
|
|
799
|
+
def available_models():
|
|
800
|
+
"""Show available model shortcuts for download."""
|
|
801
|
+
from ..models.downloader import ModelManager
|
|
802
|
+
|
|
803
|
+
manager = ModelManager()
|
|
804
|
+
sources = manager.get_available_sources()
|
|
805
|
+
|
|
806
|
+
console.print("\n[bold]Available Model Sources:[/bold]\n")
|
|
807
|
+
|
|
808
|
+
table = Table()
|
|
809
|
+
table.add_column("Source", style="cyan")
|
|
810
|
+
table.add_column("Status", style="green")
|
|
811
|
+
table.add_column("Description")
|
|
812
|
+
|
|
813
|
+
table.add_row(
|
|
814
|
+
"HuggingFace",
|
|
815
|
+
"✓ Available" if sources["huggingface"] else "✗",
|
|
816
|
+
"Download GGUF models from HuggingFace Hub"
|
|
817
|
+
)
|
|
818
|
+
table.add_row(
|
|
819
|
+
"Ollama",
|
|
820
|
+
"✓ Available" if sources["ollama"] else "✗ Not installed",
|
|
821
|
+
"Pull models via Ollama CLI"
|
|
822
|
+
)
|
|
823
|
+
table.add_row(
|
|
824
|
+
"LM Studio",
|
|
825
|
+
"✓ Available" if sources["lmstudio"] else "✗ Not found",
|
|
826
|
+
"Import models from LM Studio"
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
console.print(table)
|
|
830
|
+
|
|
831
|
+
console.print("\n[bold]Popular Model Shortcuts (HuggingFace):[/bold]\n")
|
|
832
|
+
|
|
833
|
+
hf_table = Table()
|
|
834
|
+
hf_table.add_column("Shortcut", style="cyan")
|
|
835
|
+
hf_table.add_column("Repository")
|
|
836
|
+
hf_table.add_column("Size")
|
|
837
|
+
|
|
838
|
+
shortcuts = {
|
|
839
|
+
"qwen2.5:0.5b": ("Qwen/Qwen2.5-0.5B-Instruct-GGUF", "~400MB"),
|
|
840
|
+
"qwen2.5:1.5b": ("Qwen/Qwen2.5-1.5B-Instruct-GGUF", "~1GB"),
|
|
841
|
+
"qwen2.5:3b": ("Qwen/Qwen2.5-3B-Instruct-GGUF", "~2GB"),
|
|
842
|
+
"qwen2.5:7b": ("Qwen/Qwen2.5-7B-Instruct-GGUF", "~5GB"),
|
|
843
|
+
"llama3.2:1b": ("bartowski/Llama-3.2-1B-Instruct-GGUF", "~1GB"),
|
|
844
|
+
"llama3.2:3b": ("bartowski/Llama-3.2-3B-Instruct-GGUF", "~2GB"),
|
|
845
|
+
"phi3:mini": ("microsoft/Phi-3-mini-4k-instruct-gguf", "~2.5GB"),
|
|
846
|
+
"mistral:7b": ("TheBloke/Mistral-7B-Instruct-v0.2-GGUF", "~5GB"),
|
|
847
|
+
"gemma2:2b": ("bartowski/gemma-2-2b-it-GGUF", "~1.5GB"),
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
for shortcut, (repo, size) in shortcuts.items():
|
|
851
|
+
hf_table.add_row(shortcut, repo, size)
|
|
852
|
+
|
|
853
|
+
console.print(hf_table)
|
|
854
|
+
console.print("\n[dim]Usage: parishad models download qwen2.5:1.5b[/dim]")
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
@models.command("wizard")
|
|
858
|
+
def download_wizard():
|
|
859
|
+
"""Interactive model download wizard."""
|
|
860
|
+
from ..models.downloader import ModelManager, interactive_download
|
|
861
|
+
|
|
862
|
+
manager = ModelManager()
|
|
863
|
+
|
|
864
|
+
try:
|
|
865
|
+
model = interactive_download(manager)
|
|
866
|
+
if model:
|
|
867
|
+
console.print(f"\n[green]✓ Model ready:[/green] {model.name}")
|
|
868
|
+
except KeyboardInterrupt:
|
|
869
|
+
console.print("\n[yellow]Cancelled[/yellow]")
|
|
870
|
+
except Exception as e:
|
|
871
|
+
console.print(f"\n[red]Error:[/red] {e}")
|
|
872
|
+
sys.exit(1)
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
# =============================================================================
|
|
876
|
+
# Setup Wizard Commands (prarambh and sthapana)
|
|
877
|
+
# =============================================================================
|
|
878
|
+
|
|
879
|
+
@cli.command("prarambh")
|
|
880
|
+
def prarambh():
|
|
881
|
+
"""
|
|
882
|
+
🚀 Start your Parishad journey - Interactive session.
|
|
883
|
+
|
|
884
|
+
'Prarambh' (प्रारम्भ) means 'beginning' in Sanskrit.
|
|
885
|
+
|
|
886
|
+
This command:
|
|
887
|
+
|
|
888
|
+
\b
|
|
889
|
+
1. Loads existing council config (or runs setup if needed)
|
|
890
|
+
2. Enters interactive mode for queries
|
|
891
|
+
3. Processes questions through the council
|
|
892
|
+
|
|
893
|
+
Example:
|
|
894
|
+
|
|
895
|
+
parishad prarambh
|
|
896
|
+
"""
|
|
897
|
+
from .prarambh import main as run_prarambh
|
|
898
|
+
|
|
899
|
+
try:
|
|
900
|
+
run_prarambh()
|
|
901
|
+
except KeyboardInterrupt:
|
|
902
|
+
console.print("\n[yellow]Session ended[/yellow]")
|
|
903
|
+
except Exception as e:
|
|
904
|
+
console.print(f"\n[red]Error:[/red] {e}")
|
|
905
|
+
sys.exit(1)
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
@cli.command("code")
|
|
909
|
+
@click.option(
|
|
910
|
+
"--backend", "-b",
|
|
911
|
+
default="ollama_native",
|
|
912
|
+
help="Backend to use (ollama_native, openai, ollama, etc.)"
|
|
913
|
+
)
|
|
914
|
+
@click.option(
|
|
915
|
+
"--model", "-m",
|
|
916
|
+
default="llama3.2:3b",
|
|
917
|
+
help="Model ID to use"
|
|
918
|
+
)
|
|
919
|
+
@click.option(
|
|
920
|
+
"--cwd", "-d",
|
|
921
|
+
default=None,
|
|
922
|
+
help="Working directory (default: current)"
|
|
923
|
+
)
|
|
924
|
+
def code(backend: str, model: str, cwd: Optional[str]):
|
|
925
|
+
"""
|
|
926
|
+
🤖 Interactive agentic coding assistant (like Claude Code).
|
|
927
|
+
|
|
928
|
+
Start an interactive chat session where you can:
|
|
929
|
+
|
|
930
|
+
\\b
|
|
931
|
+
- Ask questions about code
|
|
932
|
+
- Read and write files
|
|
933
|
+
- Run shell commands
|
|
934
|
+
- Get help with programming tasks
|
|
935
|
+
|
|
936
|
+
The AI will use tools (file system, shell) to help you.
|
|
937
|
+
|
|
938
|
+
Examples:
|
|
939
|
+
|
|
940
|
+
parishad code
|
|
941
|
+
|
|
942
|
+
parishad code --model llama3.2:3b
|
|
943
|
+
|
|
944
|
+
parishad code --backend openai --model gpt-4o-mini
|
|
945
|
+
"""
|
|
946
|
+
from .code import run_code_cli
|
|
947
|
+
|
|
948
|
+
try:
|
|
949
|
+
run_code_cli(backend=backend, model=model, cwd=cwd)
|
|
950
|
+
except KeyboardInterrupt:
|
|
951
|
+
console.print("\n[yellow]Session ended[/yellow]")
|
|
952
|
+
except Exception as e:
|
|
953
|
+
console.print(f"\n[red]Error:[/red] {e}")
|
|
954
|
+
sys.exit(1)
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
@cli.command("sthapana")
|
|
958
|
+
def sthapana():
|
|
959
|
+
"""
|
|
960
|
+
🔧 Configure your Parishad council - Setup wizard.
|
|
961
|
+
|
|
962
|
+
'Sthapana' (स्थापना) means 'establishment' in Sanskrit.
|
|
963
|
+
|
|
964
|
+
This wizard guides you through:
|
|
965
|
+
|
|
966
|
+
\b
|
|
967
|
+
1. Choose a council configuration (Full/Medium/Minimal)
|
|
968
|
+
2. Select models for each tier (Heavy/Mid/Light)
|
|
969
|
+
3. Download required models
|
|
970
|
+
4. Run health checks
|
|
971
|
+
5. Generate pipeline configuration
|
|
972
|
+
|
|
973
|
+
Example:
|
|
974
|
+
|
|
975
|
+
parishad sthapana
|
|
976
|
+
"""
|
|
977
|
+
from .sthapana import main as run_sthapana
|
|
978
|
+
|
|
979
|
+
try:
|
|
980
|
+
run_sthapana()
|
|
981
|
+
except KeyboardInterrupt:
|
|
982
|
+
console.print("\n[yellow]Setup cancelled[/yellow]")
|
|
983
|
+
except Exception as e:
|
|
984
|
+
console.print(f"\n[red]Error:[/red] {e}")
|
|
985
|
+
sys.exit(1)
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
# =============================================================================
|
|
989
|
+
# Configuration Commands
|
|
990
|
+
# =============================================================================
|
|
991
|
+
|
|
992
|
+
@cli.group("config")
|
|
993
|
+
def config_cmd():
|
|
994
|
+
"""⚙️ Manage Parishad configuration."""
|
|
995
|
+
pass
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
@config_cmd.command("model-dir")
|
|
999
|
+
@click.argument("path", required=False)
|
|
1000
|
+
def set_model_dir_cmd(path: Optional[str]):
|
|
1001
|
+
"""
|
|
1002
|
+
View or set the model storage directory.
|
|
1003
|
+
|
|
1004
|
+
Without arguments, shows the current model directory.
|
|
1005
|
+
With a path argument, sets the model directory.
|
|
1006
|
+
|
|
1007
|
+
Examples:
|
|
1008
|
+
|
|
1009
|
+
\b
|
|
1010
|
+
# View current directory
|
|
1011
|
+
parishad config model-dir
|
|
1012
|
+
|
|
1013
|
+
# Set custom directory (Windows example)
|
|
1014
|
+
parishad config model-dir D:\\AI\\models
|
|
1015
|
+
|
|
1016
|
+
# Set custom directory (macOS/Linux)
|
|
1017
|
+
parishad config model-dir /data/llm-models
|
|
1018
|
+
|
|
1019
|
+
You can also set the PARISHAD_MODELS_DIR environment variable.
|
|
1020
|
+
"""
|
|
1021
|
+
from ..models.downloader import (
|
|
1022
|
+
get_default_model_dir,
|
|
1023
|
+
get_user_configured_model_dir,
|
|
1024
|
+
get_platform_default_model_dir,
|
|
1025
|
+
set_model_dir,
|
|
1026
|
+
PARISHAD_MODELS_DIR_ENV,
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
if path is None:
|
|
1030
|
+
# Show current configuration
|
|
1031
|
+
current_dir = get_default_model_dir()
|
|
1032
|
+
user_dir = get_user_configured_model_dir()
|
|
1033
|
+
platform_default = get_platform_default_model_dir()
|
|
1034
|
+
|
|
1035
|
+
console.print("\n[bold]Model Directory Configuration[/bold]\n")
|
|
1036
|
+
console.print(f" [cyan]Current:[/cyan] {current_dir}")
|
|
1037
|
+
console.print(f" [dim]Platform default:[/dim] {platform_default}")
|
|
1038
|
+
|
|
1039
|
+
if user_dir:
|
|
1040
|
+
console.print(f" [green]Custom (config):[/green] {user_dir}")
|
|
1041
|
+
|
|
1042
|
+
env_val = os.environ.get(PARISHAD_MODELS_DIR_ENV)
|
|
1043
|
+
if env_val:
|
|
1044
|
+
console.print(f" [yellow]From env var:[/yellow] {env_val}")
|
|
1045
|
+
|
|
1046
|
+
console.print(f"\n[dim]To change: parishad config model-dir /your/path[/dim]")
|
|
1047
|
+
console.print(f"[dim]Or set: {PARISHAD_MODELS_DIR_ENV}=/your/path[/dim]\n")
|
|
1048
|
+
else:
|
|
1049
|
+
# Set new directory
|
|
1050
|
+
target_path = Path(path).resolve()
|
|
1051
|
+
|
|
1052
|
+
# Validate path
|
|
1053
|
+
if target_path.exists() and not target_path.is_dir():
|
|
1054
|
+
console.print(f"[red]Error:[/red] {path} exists but is not a directory")
|
|
1055
|
+
sys.exit(1)
|
|
1056
|
+
|
|
1057
|
+
# Create directory if needed
|
|
1058
|
+
try:
|
|
1059
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
1060
|
+
except PermissionError:
|
|
1061
|
+
console.print(f"[red]Error:[/red] Permission denied creating {path}")
|
|
1062
|
+
console.print("[dim]Try running with administrator/sudo privileges[/dim]")
|
|
1063
|
+
sys.exit(1)
|
|
1064
|
+
except Exception as e:
|
|
1065
|
+
console.print(f"[red]Error:[/red] Cannot create directory: {e}")
|
|
1066
|
+
sys.exit(1)
|
|
1067
|
+
|
|
1068
|
+
# Save configuration
|
|
1069
|
+
set_model_dir(target_path)
|
|
1070
|
+
|
|
1071
|
+
console.print(f"\n[green]✓[/green] Model directory set to: [cyan]{target_path}[/cyan]")
|
|
1072
|
+
console.print("[dim]New models will be downloaded to this location.[/dim]\n")
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
@config_cmd.command("show")
|
|
1076
|
+
def show_config():
|
|
1077
|
+
"""Show all Parishad configuration."""
|
|
1078
|
+
from ..models.downloader import get_config_file_path, get_default_model_dir
|
|
1079
|
+
from ..config.user_config import get_user_config_path, load_user_config
|
|
1080
|
+
|
|
1081
|
+
config_file = get_config_file_path()
|
|
1082
|
+
user_config_path = get_user_config_path()
|
|
1083
|
+
|
|
1084
|
+
console.print("\n[bold]Parishad Configuration[/bold]\n")
|
|
1085
|
+
|
|
1086
|
+
# User Config (new)
|
|
1087
|
+
if user_config_path.exists():
|
|
1088
|
+
console.print("[bold cyan]User Config:[/bold cyan]")
|
|
1089
|
+
console.print(f" [dim]Path:[/dim] {user_config_path}")
|
|
1090
|
+
try:
|
|
1091
|
+
user_cfg = load_user_config()
|
|
1092
|
+
console.print(f" [cyan]Default Profile:[/cyan] {user_cfg.default_profile}")
|
|
1093
|
+
console.print(f" [cyan]Default Mode:[/cyan] {user_cfg.default_mode}")
|
|
1094
|
+
console.print(f" [cyan]Model Directory:[/cyan] {user_cfg.model_dir}")
|
|
1095
|
+
except Exception as e:
|
|
1096
|
+
console.print(f" [red]Error loading:[/red] {e}")
|
|
1097
|
+
console.print()
|
|
1098
|
+
else:
|
|
1099
|
+
console.print(f"[yellow]User Config:[/yellow] Not found ({user_config_path})")
|
|
1100
|
+
console.print(" [dim]Run 'parishad sthapana' to create[/dim]\n")
|
|
1101
|
+
|
|
1102
|
+
# Council Config
|
|
1103
|
+
council_config_path = Path.home() / ".parishad" / "council_config.json"
|
|
1104
|
+
if council_config_path.exists():
|
|
1105
|
+
console.print("[bold cyan]Council Config:[/bold cyan]")
|
|
1106
|
+
console.print(f" [dim]Path:[/dim] {council_config_path}")
|
|
1107
|
+
try:
|
|
1108
|
+
with open(council_config_path) as f:
|
|
1109
|
+
council = json.load(f)
|
|
1110
|
+
console.print(f" [cyan]Council:[/cyan] {council['council']['name']}")
|
|
1111
|
+
console.print(f" [cyan]Roles:[/cyan] {council['council']['role_count']}")
|
|
1112
|
+
console.print(f" [cyan]Size:[/cyan] {council['total_size_gb']:.1f} GB")
|
|
1113
|
+
except Exception as e:
|
|
1114
|
+
console.print(f" [red]Error loading:[/red] {e}")
|
|
1115
|
+
console.print()
|
|
1116
|
+
else:
|
|
1117
|
+
console.print(f"[yellow]Council Config:[/yellow] Not found ({council_config_path})\n")
|
|
1118
|
+
|
|
1119
|
+
# Models Config
|
|
1120
|
+
models_config_path = Path.home() / ".parishad" / "models.yaml"
|
|
1121
|
+
if models_config_path.exists():
|
|
1122
|
+
console.print("[bold cyan]Models Config:[/bold cyan]")
|
|
1123
|
+
console.print(f" [dim]Path:[/dim] {models_config_path}")
|
|
1124
|
+
console.print(f" [green]✓[/green] Found")
|
|
1125
|
+
console.print()
|
|
1126
|
+
else:
|
|
1127
|
+
console.print(f"[yellow]Models Config:[/yellow] Not found ({models_config_path})\n")
|
|
1128
|
+
|
|
1129
|
+
# Pipeline Config
|
|
1130
|
+
pipeline_config_path = Path.home() / ".parishad" / "pipeline.yaml"
|
|
1131
|
+
if pipeline_config_path.exists():
|
|
1132
|
+
console.print("[bold cyan]Pipeline Config:[/bold cyan]")
|
|
1133
|
+
console.print(f" [dim]Path:[/dim] {pipeline_config_path}")
|
|
1134
|
+
console.print(f" [green]✓[/green] Found")
|
|
1135
|
+
console.print()
|
|
1136
|
+
else:
|
|
1137
|
+
console.print(f"[yellow]Pipeline Config:[/yellow] Not found ({pipeline_config_path})\n")
|
|
1138
|
+
|
|
1139
|
+
# Model Directory
|
|
1140
|
+
console.print("[bold cyan]Model Storage:[/bold cyan]")
|
|
1141
|
+
console.print(f" [dim]Path:[/dim] {get_default_model_dir()}")
|
|
1142
|
+
|
|
1143
|
+
if config_file.exists():
|
|
1144
|
+
try:
|
|
1145
|
+
with open(config_file) as f:
|
|
1146
|
+
config = json.load(f)
|
|
1147
|
+
console.print(f"\n[bold]Config contents:[/bold]")
|
|
1148
|
+
console.print(json.dumps(config, indent=2))
|
|
1149
|
+
except Exception as e:
|
|
1150
|
+
console.print(f"[yellow]Could not read config: {e}[/yellow]")
|
|
1151
|
+
else:
|
|
1152
|
+
console.print(f"\n[dim]No config file found (using defaults)[/dim]")
|
|
1153
|
+
|
|
1154
|
+
console.print("")
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
if __name__ == "__main__":
|
|
1158
|
+
cli()
|