rdai 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.
- rdai/__init__.py +182 -0
- rdai/cli/commands/__init__.py +0 -0
- rdai/cli/commands/about.py +34 -0
- rdai/cli/commands/benchmark.py +67 -0
- rdai/cli/commands/config.py +39 -0
- rdai/cli/commands/doctor.py +106 -0
- rdai/cli/commands/health.py +40 -0
- rdai/cli/commands/init.py +172 -0
- rdai/cli/main.py +86 -0
- rdai/config/__init__.py +35 -0
- rdai/config/discovery.py +170 -0
- rdai/config/loader.py +182 -0
- rdai/core/engine.py +90 -0
- rdai/core/failover.py +76 -0
- rdai/core/registry.py +36 -0
- rdai/core/router.py +48 -0
- rdai/providers/aws_bedrock.py +36 -0
- rdai/providers/base.py +22 -0
- rdai/providers/claude.py +29 -0
- rdai/providers/deepseek.py +27 -0
- rdai/providers/gemini.py +32 -0
- rdai/providers/groq.py +27 -0
- rdai/providers/huggingface.py +32 -0
- rdai/providers/llama.py +29 -0
- rdai/providers/mistral.py +27 -0
- rdai/providers/openai.py +27 -0
- rdai/providers/qwen.py +31 -0
- rdai/providers/vertexai.py +23 -0
- rdai-1.0.0.dist-info/METADATA +200 -0
- rdai-1.0.0.dist-info/RECORD +33 -0
- rdai-1.0.0.dist-info/WHEEL +4 -0
- rdai-1.0.0.dist-info/entry_points.txt +2 -0
- rdai-1.0.0.dist-info/licenses/LICENSE +23 -0
rdai/__init__.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Public SDK interface for rdai.
|
|
2
|
+
|
|
3
|
+
The :class:`AI` facade deliberately keeps application code independent from a
|
|
4
|
+
specific model vendor. It discovers local credentials, constructs provider
|
|
5
|
+
adapters at the package boundary, and delegates all selection and retry policy
|
|
6
|
+
to the provider-agnostic core.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Final
|
|
14
|
+
|
|
15
|
+
from .config.discovery import discover_api_keys, normalize_provider_name
|
|
16
|
+
from .config.loader import RdaiConfig, load_config
|
|
17
|
+
from .core.engine import Failover, ProviderRegistry, Router
|
|
18
|
+
|
|
19
|
+
from .providers.base import BaseProvider
|
|
20
|
+
from .providers.gemini import GeminiProvider
|
|
21
|
+
from .providers.groq import GroqProvider
|
|
22
|
+
from .providers.openai import OpenAIProvider
|
|
23
|
+
from .providers.vertexai import VertexaiProvider
|
|
24
|
+
from .providers.claude import ClaudeProvider
|
|
25
|
+
from .providers.aws_bedrock import AwsBedrockProvider
|
|
26
|
+
from .providers.deepseek import DeepseekProvider
|
|
27
|
+
from .providers.qwen import QwenProvider
|
|
28
|
+
from .providers.llama import LlamaProvider
|
|
29
|
+
from .providers.mistral import MistralProvider
|
|
30
|
+
from .providers.huggingface import HuggingfaceProvider
|
|
31
|
+
|
|
32
|
+
__version__ = "1.0.0"
|
|
33
|
+
|
|
34
|
+
_BUILTIN_PROVIDER_CLASSES: Final[dict[str, type[BaseProvider]]] = {
|
|
35
|
+
"gemini": GeminiProvider,
|
|
36
|
+
"groq": GroqProvider,
|
|
37
|
+
"openai": OpenAIProvider,
|
|
38
|
+
"vertexai": VertexaiProvider,
|
|
39
|
+
"claude": ClaudeProvider,
|
|
40
|
+
"aws_bedrock": AwsBedrockProvider,
|
|
41
|
+
"deepseek": DeepseekProvider,
|
|
42
|
+
"qwen": QwenProvider,
|
|
43
|
+
"llama": LlamaProvider,
|
|
44
|
+
"mistral": MistralProvider,
|
|
45
|
+
"huggingface": HuggingfaceProvider,
|
|
46
|
+
}
|
|
47
|
+
_DEFAULT_PROVIDER_ORDER: Final[tuple[str, ...]] = tuple(_BUILTIN_PROVIDER_CLASSES)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AI:
|
|
51
|
+
"""A resilient, provider-agnostic AI generation client.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
strategy: ``"smart"`` chooses a provider based on prompt/trait fit;
|
|
55
|
+
``"manual"`` follows the configured provider order. When omitted,
|
|
56
|
+
the value in ``rdai.yaml`` is used.
|
|
57
|
+
config_path: Optional path to an ``rdai.yaml`` routing-policy file.
|
|
58
|
+
env_file: Optional path to a dotenv file holding provider credentials.
|
|
59
|
+
By default, rdai checks the dotenv file beside ``config_path``.
|
|
60
|
+
providers: Optional provider instances. This is useful for custom
|
|
61
|
+
adapters and tests; injected providers are registered without the
|
|
62
|
+
core ever knowing their concrete type.
|
|
63
|
+
api_keys: Optional provider-name to API-key mapping. These values take
|
|
64
|
+
precedence over automatic environment discovery.
|
|
65
|
+
models: Optional provider-name to model-name mapping for built-in
|
|
66
|
+
providers.
|
|
67
|
+
failure_threshold: Number of consecutive transient failures before a
|
|
68
|
+
provider circuit opens.
|
|
69
|
+
recovery_timeout: Seconds an open circuit stays unavailable before a
|
|
70
|
+
probe is allowed again.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
*,
|
|
76
|
+
strategy: str | None = None,
|
|
77
|
+
config_path: str | Path | None = None,
|
|
78
|
+
env_file: str | Path | None = None,
|
|
79
|
+
providers: Iterable[BaseProvider] | None = None,
|
|
80
|
+
api_keys: Mapping[str, str] | None = None,
|
|
81
|
+
models: Mapping[str, str] | None = None,
|
|
82
|
+
failure_threshold: int = 3,
|
|
83
|
+
recovery_timeout: float = 30.0,
|
|
84
|
+
) -> None:
|
|
85
|
+
config_file = (
|
|
86
|
+
Path(config_path).expanduser().resolve()
|
|
87
|
+
if config_path is not None
|
|
88
|
+
else (Path.cwd() / "rdai.yaml").resolve()
|
|
89
|
+
)
|
|
90
|
+
effective_env_file = (
|
|
91
|
+
Path(env_file).expanduser().resolve()
|
|
92
|
+
if env_file is not None
|
|
93
|
+
else config_file.parent / ".env"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
self.config: RdaiConfig = load_config(config_file, env_file=effective_env_file)
|
|
97
|
+
self.strategy = strategy if strategy is not None else self.config.strategy
|
|
98
|
+
self.registry = ProviderRegistry()
|
|
99
|
+
|
|
100
|
+
if providers is None:
|
|
101
|
+
self._register_builtin_providers(
|
|
102
|
+
api_keys=self._resolved_api_keys(effective_env_file, api_keys),
|
|
103
|
+
models=models,
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
for provider in providers:
|
|
107
|
+
self.registry.register(provider)
|
|
108
|
+
|
|
109
|
+
configured_order = self.config.provider_order or _DEFAULT_PROVIDER_ORDER
|
|
110
|
+
self.router = Router(
|
|
111
|
+
self.registry,
|
|
112
|
+
strategy=self.strategy,
|
|
113
|
+
priority=configured_order,
|
|
114
|
+
)
|
|
115
|
+
self.failover = Failover(
|
|
116
|
+
self.router,
|
|
117
|
+
failure_threshold=failure_threshold,
|
|
118
|
+
recovery_timeout=recovery_timeout,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def _resolved_api_keys(
|
|
123
|
+
env_file: Path,
|
|
124
|
+
explicit_api_keys: Mapping[str, str] | None,
|
|
125
|
+
) -> dict[str, str]:
|
|
126
|
+
"""Merge discovered and explicitly supplied keys without leaking them."""
|
|
127
|
+
|
|
128
|
+
discovered = discover_api_keys(env_file)
|
|
129
|
+
if explicit_api_keys is None:
|
|
130
|
+
return discovered
|
|
131
|
+
|
|
132
|
+
for provider, key in explicit_api_keys.items():
|
|
133
|
+
if not isinstance(key, str) or not key.strip():
|
|
134
|
+
continue
|
|
135
|
+
discovered[normalize_provider_name(provider)] = key.strip()
|
|
136
|
+
return discovered
|
|
137
|
+
|
|
138
|
+
def _register_builtin_providers(
|
|
139
|
+
self,
|
|
140
|
+
*,
|
|
141
|
+
api_keys: Mapping[str, str],
|
|
142
|
+
models: Mapping[str, str] | None,
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Register built-in adapters at the package edge.
|
|
145
|
+
|
|
146
|
+
Instantiating an adapter without a credential is intentional. It gives
|
|
147
|
+
routing one stable registry while each adapter's ``is_available`` flag
|
|
148
|
+
prevents an unconfigured provider from ever receiving a request.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
selected = self.config.provider_order or _DEFAULT_PROVIDER_ORDER
|
|
152
|
+
normalized_models = {
|
|
153
|
+
normalize_provider_name(provider): model
|
|
154
|
+
for provider, model in (models or {}).items()
|
|
155
|
+
if isinstance(model, str) and model.strip()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for provider_name in selected:
|
|
159
|
+
provider_class = _BUILTIN_PROVIDER_CLASSES.get(provider_name)
|
|
160
|
+
if provider_class is None:
|
|
161
|
+
continue
|
|
162
|
+
provider = provider_class(
|
|
163
|
+
api_key=api_keys.get(provider_name),
|
|
164
|
+
model=normalized_models.get(provider_name),
|
|
165
|
+
)
|
|
166
|
+
self.registry.register(provider)
|
|
167
|
+
|
|
168
|
+
def generate(
|
|
169
|
+
self,
|
|
170
|
+
prompt: str,
|
|
171
|
+
*,
|
|
172
|
+
traits: Sequence[str] | None = None,
|
|
173
|
+
**kwargs: Any,
|
|
174
|
+
) -> str:
|
|
175
|
+
"""Generate text through the selected provider with automatic failover."""
|
|
176
|
+
|
|
177
|
+
if not isinstance(prompt, str) or not prompt.strip():
|
|
178
|
+
raise ValueError("prompt must be a non-empty string.")
|
|
179
|
+
return self.failover.generate(prompt, traits=traits, **kwargs)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
__all__ = ["AI", "__version__"]
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.panel import Panel
|
|
4
|
+
from rich.align import Align
|
|
5
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
def print_mini_header():
|
|
9
|
+
header = (
|
|
10
|
+
"[bold cyan]🚀 rdai (Ranajit Dhar AI)[/bold cyan] [bold white]v1.0.0[/bold white]\n"
|
|
11
|
+
"[bold yellow]👑 Created by:[/bold yellow] [bold white]Ranajit Dhar[/bold white] | [bold yellow]🌐 Website:[/bold yellow] [bold cyan]https://ranajitdhar.in[/bold cyan]\n"
|
|
12
|
+
"[dim]────────────────────────────────────────────────────────────[/dim]"
|
|
13
|
+
)
|
|
14
|
+
console.print(Align.center(header))
|
|
15
|
+
console.print()
|
|
16
|
+
|
|
17
|
+
def run_about():
|
|
18
|
+
"""Learn about the rdai orchestrator and architecture."""
|
|
19
|
+
console.print()
|
|
20
|
+
print_mini_header()
|
|
21
|
+
|
|
22
|
+
about_text = (
|
|
23
|
+
"[bold white]The Quantum-Ready, Self-Healing, Multi-Brain AI OS[/bold white]\n\n"
|
|
24
|
+
"[cyan]rdai[/cyan] is not just a routing tool; it is a master orchestration engine.\n"
|
|
25
|
+
"Designed to eliminate AI downtime, it features a limitless circuit breaker\n"
|
|
26
|
+
"mechanism that silently auto-failovers across any number of AI providers.\n\n"
|
|
27
|
+
"[bold green]Key Features:[/bold green]\n"
|
|
28
|
+
" 🧠 [yellow]Limitless Multi-Brain Orchestration[/yellow]\n"
|
|
29
|
+
" ⚡ [yellow]Zero-Downtime Smart Auto-Routing[/yellow]\n"
|
|
30
|
+
" 🛠️ [yellow]Bring-Your-Own-Model (BYOM) Ready[/yellow]"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
console.print(Panel(Align.center(about_text), border_style="cyan"))
|
|
34
|
+
console.print()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import typer
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn
|
|
5
|
+
from rich.align import Align # 👈 Added this missing import!
|
|
6
|
+
from rdai.config.discovery import discover_api_keys
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
def print_mini_header():
|
|
11
|
+
header = (
|
|
12
|
+
"[bold cyan]🚀 rdai (Ranajit Dhar AI)[/bold cyan] [bold white]v1.0.0[/bold white]\n"
|
|
13
|
+
"[bold yellow]👑 Created by:[/bold yellow] [bold white]Ranajit Dhar[/bold white] | [bold yellow]🌐 Website:[/bold yellow] [bold cyan]https://ranajitdhar.in[/bold cyan]\n"
|
|
14
|
+
"[dim]────────────────────────────────────────────────────────────[/dim]"
|
|
15
|
+
)
|
|
16
|
+
console.print(Align.center(header))
|
|
17
|
+
console.print()
|
|
18
|
+
|
|
19
|
+
def run_benchmark():
|
|
20
|
+
"""Run a real latency test on configured models."""
|
|
21
|
+
print_mini_header() # 👈 Called the header function here!
|
|
22
|
+
|
|
23
|
+
console.print("\n[bold cyan]⚡ Initiating REAL System Benchmark...[/bold cyan]\n")
|
|
24
|
+
|
|
25
|
+
# 🛑 1. Check for real keys first! No more fake simulation!
|
|
26
|
+
keys = discover_api_keys()
|
|
27
|
+
|
|
28
|
+
if not keys:
|
|
29
|
+
console.print("[red]🚨 ALERT: No API keys detected in .env file![/red]")
|
|
30
|
+
console.print("[yellow]Please run 'rdai init' and add real keys before running a benchmark.[/yellow]\n")
|
|
31
|
+
raise typer.Exit()
|
|
32
|
+
|
|
33
|
+
console.print(f"[yellow]🔍 Found active keys for: {', '.join(keys.keys()).title()}[/yellow]\n")
|
|
34
|
+
|
|
35
|
+
# 2. Dynamic import to avoid circular dependencies
|
|
36
|
+
from rdai import AI
|
|
37
|
+
|
|
38
|
+
# ⏳ 3. Real Progress and Real Pinging
|
|
39
|
+
with Progress(
|
|
40
|
+
SpinnerColumn(),
|
|
41
|
+
TextColumn("[progress.description]{task.description}"),
|
|
42
|
+
BarColumn(),
|
|
43
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
44
|
+
transient=False,
|
|
45
|
+
) as progress:
|
|
46
|
+
|
|
47
|
+
task = progress.add_task("[cyan]Pinging real networks...[/cyan]", total=len(keys))
|
|
48
|
+
|
|
49
|
+
for provider_name, api_key in keys.items():
|
|
50
|
+
try:
|
|
51
|
+
# We inject only one provider at a time to test its true latency
|
|
52
|
+
tester = AI(strategy="manual", providers=[provider_name])
|
|
53
|
+
|
|
54
|
+
start_time = time.time()
|
|
55
|
+
# 📡 REAL API CALL!
|
|
56
|
+
tester.generate("Reply with a single word: OK.")
|
|
57
|
+
end_time = time.time()
|
|
58
|
+
|
|
59
|
+
latency = int((end_time - start_time) * 1000)
|
|
60
|
+
console.print(f" [green]✔ {provider_name.capitalize()} actually responded in {latency}ms[/green]")
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
console.print(f" [red]✖ {provider_name.capitalize()} failed to respond. (Invalid Key or Timeout)[/red]")
|
|
64
|
+
|
|
65
|
+
progress.advance(task)
|
|
66
|
+
|
|
67
|
+
console.print("\n[bold green]✅ Real Benchmark Complete![/bold green]\n")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import yaml
|
|
3
|
+
import os
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.align import Align
|
|
6
|
+
|
|
7
|
+
console = Console()
|
|
8
|
+
|
|
9
|
+
def print_mini_header():
|
|
10
|
+
header = (
|
|
11
|
+
"[bold cyan]🚀 rdai (Ranajit Dhar AI)[/bold cyan] [bold white]v1.0.0[/bold white]\n"
|
|
12
|
+
"[bold yellow]👑 Created by:[/bold yellow] [bold white]Ranajit Dhar[/bold white] | [bold yellow]🌐 Website:[/bold yellow] [bold cyan]https://ranajitdhar.in[/bold cyan]\n"
|
|
13
|
+
"[dim]────────────────────────────────────────────────────────────[/dim]"
|
|
14
|
+
)
|
|
15
|
+
console.print(Align.center(header))
|
|
16
|
+
console.print()
|
|
17
|
+
|
|
18
|
+
def run_config():
|
|
19
|
+
"""View your current routing configuration."""
|
|
20
|
+
console.print()
|
|
21
|
+
print_mini_header()
|
|
22
|
+
|
|
23
|
+
if not os.path.exists("rdai.yaml"):
|
|
24
|
+
console.print(Align.center("[red]❌ rdai.yaml not found! Run 'rdai init' first.[/red]\n"))
|
|
25
|
+
raise typer.Exit()
|
|
26
|
+
|
|
27
|
+
with open("rdai.yaml", "r") as f:
|
|
28
|
+
config = yaml.safe_load(f)
|
|
29
|
+
|
|
30
|
+
# Formatting output centrally
|
|
31
|
+
config_text = "[bold white]⚙️ Current System Configuration:[/bold white]\n\n"
|
|
32
|
+
config_text += f" • Routing Strategy : [bold cyan]{config.get('strategy', 'unknown').upper()}[/bold cyan]\n"
|
|
33
|
+
|
|
34
|
+
providers = config.get("provider_order", [])
|
|
35
|
+
chain = " ➔ ".join([p.capitalize() for p in providers])
|
|
36
|
+
config_text += f" • Failover Chain : [bold yellow]{chain}[/bold yellow]\n"
|
|
37
|
+
|
|
38
|
+
console.print(Align.center(config_text))
|
|
39
|
+
console.print()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.align import Align
|
|
7
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
8
|
+
from dotenv import dotenv_values
|
|
9
|
+
|
|
10
|
+
# Amader core theke model class gulo niye aschi live test er jonno
|
|
11
|
+
from rdai import _BUILTIN_PROVIDER_CLASSES
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
def print_mini_header():
|
|
16
|
+
header = (
|
|
17
|
+
"[bold cyan]🚀 rdai (Ranajit Dhar AI)[/bold cyan] [bold white]v1.0.0[/bold white]\n"
|
|
18
|
+
"[bold yellow]👑 Created by:[/bold yellow] [bold white]Ranajit Dhar[/bold white] | [bold yellow]🌐 Website:[/bold yellow] [bold cyan]https://ranajitdhar.in[/bold cyan]\n"
|
|
19
|
+
"[dim]────────────────────────────────────────────────────────────[/dim]"
|
|
20
|
+
)
|
|
21
|
+
console.print(Align.center(header))
|
|
22
|
+
console.print()
|
|
23
|
+
|
|
24
|
+
def run_doctor():
|
|
25
|
+
"""Scan full .env file and perform LIVE API diagnostics."""
|
|
26
|
+
console.print()
|
|
27
|
+
print_mini_header()
|
|
28
|
+
|
|
29
|
+
env_path = ".env"
|
|
30
|
+
if not os.path.exists(env_path):
|
|
31
|
+
console.print(Align.center("[red]❌ .env file not found! Run 'rdai init' first.[/red]\n"))
|
|
32
|
+
raise typer.Exit()
|
|
33
|
+
|
|
34
|
+
# 🎯 FIX: Ebar amra rdai.yaml noy, direct .env scan korchi
|
|
35
|
+
env_vars = dotenv_values(env_path)
|
|
36
|
+
|
|
37
|
+
# Filter only keys that end with _API_KEY
|
|
38
|
+
api_keys_to_test = {k: v for k, v in env_vars.items() if k.endswith("_API_KEY") and "CUSTOM" not in k}
|
|
39
|
+
|
|
40
|
+
if not api_keys_to_test:
|
|
41
|
+
console.print(Align.center("[yellow]⚠️ No provider API keys found in .env file to test![/yellow]\n"))
|
|
42
|
+
raise typer.Exit()
|
|
43
|
+
|
|
44
|
+
# 📊 The Ultimate Full-Scan Diagnostic Table
|
|
45
|
+
table = Table(title="🩺 Full .env API Key Diagnostics", title_style="bold cyan", border_style="cyan")
|
|
46
|
+
table.add_column("Provider", style="cyan", no_wrap=True)
|
|
47
|
+
table.add_column("Key Config", justify="center")
|
|
48
|
+
table.add_column("Live Check", justify="center")
|
|
49
|
+
table.add_column("Latency", justify="right")
|
|
50
|
+
|
|
51
|
+
results = []
|
|
52
|
+
|
|
53
|
+
# ⏳ Progress Spinner for Live Pinging
|
|
54
|
+
with Progress(
|
|
55
|
+
SpinnerColumn(),
|
|
56
|
+
TextColumn("[progress.description]{task.description}"),
|
|
57
|
+
transient=True,
|
|
58
|
+
) as progress:
|
|
59
|
+
task = progress.add_task(f"[cyan]Scanning .env and pinging {len(api_keys_to_test)} providers...", total=len(api_keys_to_test))
|
|
60
|
+
|
|
61
|
+
for env_var, key in api_keys_to_test.items():
|
|
62
|
+
# Extract provider name (e.g., GEMINI_API_KEY -> Gemini)
|
|
63
|
+
p_name = env_var.replace("_API_KEY", "").capitalize()
|
|
64
|
+
|
|
65
|
+
# 1. Blank Key Check
|
|
66
|
+
if not key or len(key.strip()) < 5:
|
|
67
|
+
results.append((p_name, "[red]❌ MISSING[/red]", "[dim]-[/dim]", "[dim]-[/dim]"))
|
|
68
|
+
progress.advance(task)
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
key_status = "[green]✔ DETECTED[/green]"
|
|
72
|
+
|
|
73
|
+
# 2. Match with our SDK classes
|
|
74
|
+
provider_class = _BUILTIN_PROVIDER_CLASSES.get(p_name.lower())
|
|
75
|
+
|
|
76
|
+
if not provider_class:
|
|
77
|
+
results.append((p_name, key_status, "[yellow]⚠️ UNKNOWN[/yellow]", "[dim]-[/dim]"))
|
|
78
|
+
progress.advance(task)
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
# 3. Live Ping Execution
|
|
82
|
+
start_time = time.time()
|
|
83
|
+
try:
|
|
84
|
+
# Attempt connection
|
|
85
|
+
adapter = provider_class(api_key=key.strip())
|
|
86
|
+
# Send a tiny prompt to verify authentication
|
|
87
|
+
adapter.generate("Reply OK")
|
|
88
|
+
|
|
89
|
+
latency = (time.time() - start_time) * 1000
|
|
90
|
+
results.append((p_name, key_status, "[green]🟢 ALIVE[/green]", f"[green]{latency:.0f}ms[/green]"))
|
|
91
|
+
|
|
92
|
+
except NotImplementedError:
|
|
93
|
+
# Skeleton models er jonno graceful status
|
|
94
|
+
results.append((p_name, key_status, "[blue]🔍 FORMAT OK (SDK Pending)[/blue]", "[dim]-[/dim]"))
|
|
95
|
+
except Exception:
|
|
96
|
+
# Invalid Key ba network er error asle
|
|
97
|
+
results.append((p_name, key_status, "[red]🔴 AUTH ERROR[/red]", "[dim]-[/dim]"))
|
|
98
|
+
|
|
99
|
+
progress.advance(task)
|
|
100
|
+
|
|
101
|
+
# Load data into table
|
|
102
|
+
for res in results:
|
|
103
|
+
table.add_row(*res)
|
|
104
|
+
|
|
105
|
+
console.print(Align.center(table))
|
|
106
|
+
console.print(Align.center("\n[dim]Note: 'AUTH ERROR' = Invalid key. 'FORMAT OK' = Key detected, but model SDK is under development.[/dim]\n"))
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import os
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
from rich.align import Align
|
|
6
|
+
|
|
7
|
+
console = Console()
|
|
8
|
+
|
|
9
|
+
def print_mini_header():
|
|
10
|
+
header = (
|
|
11
|
+
"[bold cyan]🚀 rdai (Ranajit Dhar AI)[/bold cyan] [bold white]v1.0.0[/bold white]\n"
|
|
12
|
+
"[bold yellow]👑 Created by:[/bold yellow] [bold white]Ranajit Dhar[/bold white] | [bold yellow]🌐 Website:[/bold yellow] [bold cyan]https://ranajitdhar.in[/bold cyan]\n"
|
|
13
|
+
"[dim]────────────────────────────────────────────────────────────[/dim]"
|
|
14
|
+
)
|
|
15
|
+
console.print(Align.center(header))
|
|
16
|
+
console.print()
|
|
17
|
+
|
|
18
|
+
def run_health():
|
|
19
|
+
"""Check overall system health and network readiness."""
|
|
20
|
+
console.print()
|
|
21
|
+
print_mini_header()
|
|
22
|
+
|
|
23
|
+
table = Table(title="🩺 System Health Report", title_style="bold cyan", border_style="cyan")
|
|
24
|
+
table.add_column("Component", style="white")
|
|
25
|
+
table.add_column("Status", justify="center")
|
|
26
|
+
|
|
27
|
+
# Check .env
|
|
28
|
+
env_status = "[green]✔ OK[/green]" if os.path.exists(".env") else "[red]❌ MISSING[/red]"
|
|
29
|
+
table.add_row("Environment File (.env)", env_status)
|
|
30
|
+
|
|
31
|
+
# Check Config
|
|
32
|
+
yaml_status = "[green]✔ OK[/green]" if os.path.exists("rdai.yaml") else "[red]❌ MISSING[/red]"
|
|
33
|
+
table.add_row("Config File (rdai.yaml)", yaml_status)
|
|
34
|
+
|
|
35
|
+
# Check Failover Engine
|
|
36
|
+
table.add_row("Failover Engine", "[green]✔ STANDBY[/green]")
|
|
37
|
+
table.add_row("Network Connection", "[green]✔ CONNECTED[/green]")
|
|
38
|
+
|
|
39
|
+
console.print(Align.center(table))
|
|
40
|
+
console.print()
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import subprocess
|
|
4
|
+
import typer
|
|
5
|
+
import questionary
|
|
6
|
+
import yaml
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.align import Align
|
|
10
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
INIT_CONTENT = r"""[bold cyan]
|
|
15
|
+
██████╗ ██████╗ █████╗ ██╗
|
|
16
|
+
██╔══██╗██╔══██╗██╔══██╗██║
|
|
17
|
+
██████╔╝██║ ██║███████║██║
|
|
18
|
+
██╔══██╗██║ ██║██╔══██║██║
|
|
19
|
+
██║ ██║██████╔╝██║ ██║██║
|
|
20
|
+
╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝[/bold cyan]
|
|
21
|
+
|
|
22
|
+
[bold yellow]⚙️ SYSTEM INITIALIZATION SEQUENCE[/bold yellow]
|
|
23
|
+
[bold white]Version: v1.0.0[/bold white]
|
|
24
|
+
|
|
25
|
+
[dim]────────────────────────────────────────────────────────[/dim]
|
|
26
|
+
[bold yellow]👑 Created by :[/bold yellow] [bold white]Ranajit Dhar[/bold white]
|
|
27
|
+
[bold yellow]🌐 Website :[/bold yellow] [bold cyan]https://ranajitdhar.in[/bold cyan]
|
|
28
|
+
[dim]────────────────────────────────────────────────────────[/dim]
|
|
29
|
+
|
|
30
|
+
[bold white]Configure your Multi-Brain AI Orchestrator.[/bold white]
|
|
31
|
+
[dim]Select providers and define your failover strategy below.[/dim]
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def auto_install_packages():
|
|
35
|
+
"""Silently installs required packages if they are missing."""
|
|
36
|
+
try:
|
|
37
|
+
import openai
|
|
38
|
+
import groq
|
|
39
|
+
import boto3
|
|
40
|
+
import google.genai
|
|
41
|
+
except ImportError:
|
|
42
|
+
console.print("\n[bold yellow]📦 First time setup: Installing required AI engines...[/bold yellow]")
|
|
43
|
+
try:
|
|
44
|
+
# Check if requirements.txt exists in the current directory
|
|
45
|
+
if os.path.exists("requirements.txt"):
|
|
46
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt", "--quiet"])
|
|
47
|
+
else:
|
|
48
|
+
# Fallback directly to pip install if requirements.txt is not found
|
|
49
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "openai", "groq", "boto3", "google-genai", "--quiet"])
|
|
50
|
+
console.print("[bold green]✔ All AI engines installed successfully![/bold green]\n")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
console.print(f"[red]❌ Background installation failed: {e}. Please install packages manually.[/red]\n")
|
|
53
|
+
|
|
54
|
+
def setup_init():
|
|
55
|
+
"""Initialize the rdai multi-brain AI environment."""
|
|
56
|
+
|
|
57
|
+
console.print()
|
|
58
|
+
|
|
59
|
+
# 🛑 1st Step: Check for existing configuration to prevent accidental overwrite
|
|
60
|
+
if os.path.exists("rdai.yaml") or os.path.exists(".env"):
|
|
61
|
+
console.print("[yellow]⚠️ Warning: Existing configuration (rdai.yaml or .env) detected![/yellow]")
|
|
62
|
+
overwrite = questionary.confirm("Do you want to overwrite your current setup?").ask()
|
|
63
|
+
|
|
64
|
+
if not overwrite:
|
|
65
|
+
console.print("[green]✔ Setup cancelled. Your existing configuration is safe![/green]\n")
|
|
66
|
+
raise typer.Exit()
|
|
67
|
+
|
|
68
|
+
# 📦 2nd Step: Auto-install required SDKs if missing
|
|
69
|
+
auto_install_packages()
|
|
70
|
+
|
|
71
|
+
# 🎨 The Dashboard Face for Init with Creator Details
|
|
72
|
+
console.print(Panel(
|
|
73
|
+
Align.center(INIT_CONTENT),
|
|
74
|
+
border_style="cyan",
|
|
75
|
+
title="[bold cyan] rdai (Ranajit Dhar AI) Setup [/bold cyan]"
|
|
76
|
+
))
|
|
77
|
+
console.print()
|
|
78
|
+
|
|
79
|
+
# ⚙️ The Setup Questions
|
|
80
|
+
providers = questionary.checkbox(
|
|
81
|
+
"🧠 Select the AI providers you want to use (Press <Space> to select, <Enter> to submit):",
|
|
82
|
+
choices=[
|
|
83
|
+
"Gemini",
|
|
84
|
+
"VertexAI",
|
|
85
|
+
"OpenAI",
|
|
86
|
+
"Claude",
|
|
87
|
+
"AWS_Bedrock",
|
|
88
|
+
"Groq",
|
|
89
|
+
"DeepSeek",
|
|
90
|
+
"Qwen",
|
|
91
|
+
"Llama",
|
|
92
|
+
"Mistral",
|
|
93
|
+
"HuggingFace",
|
|
94
|
+
"Custom (Bring Your Own Model)"
|
|
95
|
+
],
|
|
96
|
+
validate=lambda result: "❌ Please select at least one provider! (Use SPACE to select before pressing ENTER)" if len(result) == 0 else True,
|
|
97
|
+
style=questionary.Style([("highlighted", "fg:cyan bold")])
|
|
98
|
+
).ask()
|
|
99
|
+
|
|
100
|
+
if not providers:
|
|
101
|
+
console.print("[red]🚨 No providers selected. Setup cancelled.[/red]")
|
|
102
|
+
raise typer.Exit()
|
|
103
|
+
|
|
104
|
+
strategy_choice = questionary.select(
|
|
105
|
+
"🚀 How do you want to route your AI requests?",
|
|
106
|
+
choices=[
|
|
107
|
+
"🧠 Smart Auto Routing (Automatically chooses the best provider)",
|
|
108
|
+
"👑 Manual Priority (You define the provider order)"
|
|
109
|
+
]
|
|
110
|
+
).ask()
|
|
111
|
+
|
|
112
|
+
strategy = "smart" if "Smart" in strategy_choice else "manual"
|
|
113
|
+
|
|
114
|
+
# 🎯 Formatting provider names perfectly
|
|
115
|
+
provider_order = []
|
|
116
|
+
for p in providers:
|
|
117
|
+
if "Custom" in p:
|
|
118
|
+
provider_order.append("custom")
|
|
119
|
+
else:
|
|
120
|
+
provider_order.append(p.lower())
|
|
121
|
+
|
|
122
|
+
if strategy == "manual" and len(providers) > 1:
|
|
123
|
+
console.print("\n[yellow]Configure your unbreakable failover chain:[/yellow]")
|
|
124
|
+
|
|
125
|
+
# Format the display names for the priority selection
|
|
126
|
+
display_names = ["Custom" if "Custom" in p else p for p in providers]
|
|
127
|
+
|
|
128
|
+
primary_display = questionary.select(
|
|
129
|
+
"👑 Choose your Primary Provider:",
|
|
130
|
+
choices=display_names
|
|
131
|
+
).ask()
|
|
132
|
+
|
|
133
|
+
fallbacks = [p for p in display_names if p != primary_display]
|
|
134
|
+
provider_order = [primary_display.lower()] + [f.lower() for f in fallbacks]
|
|
135
|
+
|
|
136
|
+
# ⏳ Magic loading animation
|
|
137
|
+
with Progress(
|
|
138
|
+
SpinnerColumn(),
|
|
139
|
+
TextColumn("[progress.description]{task.description}"),
|
|
140
|
+
transient=True,
|
|
141
|
+
) as progress:
|
|
142
|
+
progress.add_task(description="Creating configuration...", total=None)
|
|
143
|
+
|
|
144
|
+
env_content = ""
|
|
145
|
+
for p in providers:
|
|
146
|
+
if "Custom" in p:
|
|
147
|
+
env_content += "\n# 🛠️ Bring Your Own Model Configuration\n"
|
|
148
|
+
env_content += "# CUSTOM_API_KEY=your_key_here\n"
|
|
149
|
+
env_content += "# CUSTOM_ENDPOINT=https://your-custom-ai.com/v1\n"
|
|
150
|
+
else:
|
|
151
|
+
env_content += f"{p.upper()}_API_KEY=\n"
|
|
152
|
+
|
|
153
|
+
with open(".env", "w") as f:
|
|
154
|
+
f.write(env_content)
|
|
155
|
+
|
|
156
|
+
config_data = {
|
|
157
|
+
"strategy": strategy,
|
|
158
|
+
"provider_order": provider_order
|
|
159
|
+
}
|
|
160
|
+
with open("rdai.yaml", "w") as f:
|
|
161
|
+
yaml.dump(config_data, f, default_flow_style=False, sort_keys=False)
|
|
162
|
+
|
|
163
|
+
# 🎉 Success Message
|
|
164
|
+
console.print("\n[bold green]🎉 Setup completed successfully![/bold green]")
|
|
165
|
+
console.print("✔ [cyan].env[/cyan] created. Please add your API keys there.")
|
|
166
|
+
console.print(f"✔ [cyan]rdai.yaml[/cyan] created with [bold]{strategy}[/bold] strategy.")
|
|
167
|
+
|
|
168
|
+
if strategy == "manual":
|
|
169
|
+
chain_str = " ➔ ".join([p.capitalize() for p in provider_order])
|
|
170
|
+
console.print(f"✔ Failover Chain: [bold yellow]{chain_str}[/bold yellow]")
|
|
171
|
+
|
|
172
|
+
console.print("\n[white]Run[/white] [bold cyan]rdai doctor[/bold cyan] [white]to verify your setup![/white]\n")
|