hanzo 0.3.23__py3-none-any.whl → 0.3.25__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.
Potentially problematic release.
This version of hanzo might be problematic. Click here for more details.
- hanzo/__init__.py +2 -2
- hanzo/base_agent.py +4 -5
- hanzo/batch_orchestrator.py +11 -11
- hanzo/cli.py +20 -10
- hanzo/commands/auth.py +206 -266
- hanzo/commands/auth_broken.py +377 -0
- hanzo/commands/chat.py +60 -16
- hanzo/commands/{cluster.py → node.py} +128 -128
- hanzo/commands/router.py +152 -0
- hanzo/dev.py +1 -1
- hanzo/fallback_handler.py +1 -1
- hanzo/interactive/enhanced_repl.py +513 -0
- hanzo/interactive/repl.py +54 -34
- hanzo/mcp_server.py +8 -3
- hanzo/memory_manager.py +1 -1
- hanzo/model_registry.py +2 -2
- hanzo/rate_limiter.py +1 -1
- hanzo/streaming.py +1 -1
- hanzo/ui/__init__.py +13 -0
- hanzo/ui/inline_startup.py +136 -0
- hanzo/ui/startup.py +350 -0
- hanzo-0.3.25.dist-info/METADATA +276 -0
- hanzo-0.3.25.dist-info/RECORD +43 -0
- hanzo-0.3.23.dist-info/METADATA +0 -137
- hanzo-0.3.23.dist-info/RECORD +0 -37
- {hanzo-0.3.23.dist-info → hanzo-0.3.25.dist-info}/WHEEL +0 -0
- {hanzo-0.3.23.dist-info → hanzo-0.3.25.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""Authentication commands."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from rich.prompt import Prompt
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
from rich import box
|
|
14
|
+
|
|
15
|
+
from ..utils.output import console
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AuthManager:
|
|
19
|
+
"""Manage Hanzo authentication."""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.config_dir = Path.home() / ".hanzo"
|
|
23
|
+
self.auth_file = self.config_dir / "auth.json"
|
|
24
|
+
|
|
25
|
+
def load_auth(self) -> dict:
|
|
26
|
+
"""Load authentication data."""
|
|
27
|
+
if self.auth_file.exists():
|
|
28
|
+
try:
|
|
29
|
+
return json.loads(self.auth_file.read_text())
|
|
30
|
+
except:
|
|
31
|
+
pass
|
|
32
|
+
return {}
|
|
33
|
+
|
|
34
|
+
def save_auth(self, auth: dict):
|
|
35
|
+
"""Save authentication data."""
|
|
36
|
+
self.config_dir.mkdir(exist_ok=True)
|
|
37
|
+
self.auth_file.write_text(json.dumps(auth, indent=2))
|
|
38
|
+
|
|
39
|
+
def is_authenticated(self) -> bool:
|
|
40
|
+
"""Check if authenticated."""
|
|
41
|
+
if os.getenv("HANZO_API_KEY"):
|
|
42
|
+
return True
|
|
43
|
+
auth = self.load_auth()
|
|
44
|
+
return bool(auth.get("api_key") or auth.get("logged_in"))
|
|
45
|
+
|
|
46
|
+
def get_api_key(self) -> Optional[str]:
|
|
47
|
+
"""Get API key."""
|
|
48
|
+
if os.getenv("HANZO_API_KEY"):
|
|
49
|
+
return os.getenv("HANZO_API_KEY")
|
|
50
|
+
auth = self.load_auth()
|
|
51
|
+
return auth.get("api_key")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@click.group(name="auth")
|
|
55
|
+
def auth_group():
|
|
56
|
+
"""Manage Hanzo authentication."""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@auth_group.command()
|
|
61
|
+
@click.option("--email", "-e", help="Email address")
|
|
62
|
+
@click.option("--password", "-p", help="Password (not recommended, use prompt)")
|
|
63
|
+
@click.option("--api-key", "-k", help="API key for direct authentication")
|
|
64
|
+
@click.option("--sso", is_flag=True, help="Use SSO authentication")
|
|
65
|
+
@click.pass_context
|
|
66
|
+
async def login(ctx, email: str, password: str, api_key: str, sso: bool):
|
|
67
|
+
"""Login to Hanzo AI."""
|
|
68
|
+
try:
|
|
69
|
+
from hanzoai.auth import HanzoAuth
|
|
70
|
+
except ImportError:
|
|
71
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
72
|
+
console.print("Install with: pip install hanzo")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
auth = HanzoAuth()
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
if api_key:
|
|
79
|
+
# Direct API key authentication
|
|
80
|
+
console.print("Authenticating with API key...")
|
|
81
|
+
result = await auth.login_with_api_key(api_key)
|
|
82
|
+
elif sso:
|
|
83
|
+
# SSO authentication via browser
|
|
84
|
+
console.print("Opening browser for SSO login...")
|
|
85
|
+
console.print("If browser doesn't open, visit: https://iam.hanzo.ai/login")
|
|
86
|
+
result = await auth.login_with_sso()
|
|
87
|
+
else:
|
|
88
|
+
# Email/password authentication
|
|
89
|
+
if not email:
|
|
90
|
+
email = Prompt.ask("Email")
|
|
91
|
+
if not password:
|
|
92
|
+
password = Prompt.ask("Password", password=True)
|
|
93
|
+
|
|
94
|
+
console.print("Authenticating...")
|
|
95
|
+
result = await auth.login(email, password)
|
|
96
|
+
|
|
97
|
+
# Save credentials
|
|
98
|
+
config_dir = Path.home() / ".hanzo"
|
|
99
|
+
config_dir.mkdir(exist_ok=True)
|
|
100
|
+
|
|
101
|
+
config_file = config_dir / "auth.json"
|
|
102
|
+
await auth.save_credentials(config_file)
|
|
103
|
+
|
|
104
|
+
# Also set environment variable if API key is available
|
|
105
|
+
if api_key or result.get("api_key"):
|
|
106
|
+
key = api_key or result.get("api_key")
|
|
107
|
+
console.print(f"\n[dim]To use in environment:[/dim]")
|
|
108
|
+
console.print(f"export HANZO_API_KEY={key}")
|
|
109
|
+
|
|
110
|
+
console.print(f"[green]✓[/green] Logged in as {result.get('email', 'user')}")
|
|
111
|
+
|
|
112
|
+
# Check organization
|
|
113
|
+
if org := result.get("organization"):
|
|
114
|
+
console.print(f" Organization: {org}")
|
|
115
|
+
|
|
116
|
+
# Check permissions
|
|
117
|
+
if permissions := result.get("permissions"):
|
|
118
|
+
console.print(f" Permissions: {', '.join(permissions)}")
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
console.print(f"[red]Login failed: {e}[/red]")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@auth_group.command()
|
|
125
|
+
@click.pass_context
|
|
126
|
+
async def logout(ctx):
|
|
127
|
+
"""Logout from Hanzo AI."""
|
|
128
|
+
auth_mgr = AuthManager()
|
|
129
|
+
|
|
130
|
+
if not auth_mgr.is_authenticated():
|
|
131
|
+
console.print("[yellow]Not logged in[/yellow]")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
# Try using hanzoai if available
|
|
136
|
+
try:
|
|
137
|
+
from hanzoai.auth import HanzoAuth
|
|
138
|
+
hanzo_auth = HanzoAuth()
|
|
139
|
+
await hanzo_auth.logout()
|
|
140
|
+
except ImportError:
|
|
141
|
+
pass # hanzoai not installed, just clear local auth
|
|
142
|
+
|
|
143
|
+
# Clear local auth
|
|
144
|
+
auth_mgr.save_auth({})
|
|
145
|
+
|
|
146
|
+
console.print("[green]✓[/green] Logged out successfully")
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
console.print(f"[red]Logout failed: {e}[/red]")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@auth_group.command()
|
|
153
|
+
@click.pass_context
|
|
154
|
+
async def status(ctx):
|
|
155
|
+
"""Show authentication status."""
|
|
156
|
+
auth_mgr = AuthManager()
|
|
157
|
+
|
|
158
|
+
# Create status table
|
|
159
|
+
table = Table(title="Authentication Status", box=box.ROUNDED)
|
|
160
|
+
table.add_column("Property", style="cyan")
|
|
161
|
+
table.add_column("Value", style="white")
|
|
162
|
+
|
|
163
|
+
if auth_mgr.is_authenticated():
|
|
164
|
+
auth = auth_mgr.load_auth()
|
|
165
|
+
|
|
166
|
+
table.add_row("Status", "✅ Authenticated")
|
|
167
|
+
|
|
168
|
+
# Show auth method
|
|
169
|
+
if os.getenv("HANZO_API_KEY"):
|
|
170
|
+
table.add_row("Method", "Environment Variable")
|
|
171
|
+
api_key = os.getenv("HANZO_API_KEY")
|
|
172
|
+
table.add_row("API Key", f"{api_key[:8]}...{api_key[-4:]}")
|
|
173
|
+
elif auth.get("api_key"):
|
|
174
|
+
table.add_row("Method", "API Key")
|
|
175
|
+
table.add_row("API Key", f"{auth['api_key'][:8]}...")
|
|
176
|
+
elif auth.get("email"):
|
|
177
|
+
console.print(f" Email: {email}")
|
|
178
|
+
if org := creds.get("organization"):
|
|
179
|
+
console.print(f" Organization: {org}")
|
|
180
|
+
|
|
181
|
+
# Verify credentials are still valid
|
|
182
|
+
with console.status("Verifying credentials..."):
|
|
183
|
+
try:
|
|
184
|
+
user_info = await auth.get_user_info()
|
|
185
|
+
console.print("[green]✓[/green] Credentials are valid")
|
|
186
|
+
|
|
187
|
+
# Show usage stats if available
|
|
188
|
+
if usage := user_info.get("usage"):
|
|
189
|
+
console.print("\n[cyan]Usage:[/cyan]")
|
|
190
|
+
console.print(f" API calls: {usage.get('api_calls', 0)}")
|
|
191
|
+
console.print(f" Tokens: {usage.get('tokens', 0)}")
|
|
192
|
+
if quota := usage.get("quota"):
|
|
193
|
+
console.print(
|
|
194
|
+
f" Quota: {usage.get('tokens', 0)} / {quota}"
|
|
195
|
+
)
|
|
196
|
+
except Exception:
|
|
197
|
+
console.print("[yellow]![/yellow] Credentials may be expired")
|
|
198
|
+
console.print("Run 'hanzo auth login' to refresh")
|
|
199
|
+
|
|
200
|
+
except Exception as e:
|
|
201
|
+
console.print(f"[red]Error reading credentials: {e}[/red]")
|
|
202
|
+
else:
|
|
203
|
+
console.print("[yellow]![/yellow] Not logged in")
|
|
204
|
+
console.print("Run 'hanzo auth login' to authenticate")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@auth_group.command()
|
|
208
|
+
@click.option("--name", "-n", required=True, help="API key name")
|
|
209
|
+
@click.option(
|
|
210
|
+
"--permissions", "-p", multiple=True, help="Permissions (e.g., read, write, admin)"
|
|
211
|
+
)
|
|
212
|
+
@click.option("--expires", "-e", help="Expiration (e.g., 30d, 1y, never)")
|
|
213
|
+
@click.pass_context
|
|
214
|
+
async def create_key(ctx, name: str, permissions: tuple, expires: str):
|
|
215
|
+
"""Create a new API key."""
|
|
216
|
+
try:
|
|
217
|
+
from hanzoai.auth import HanzoAuth
|
|
218
|
+
except ImportError:
|
|
219
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
auth = HanzoAuth()
|
|
223
|
+
|
|
224
|
+
# Ensure authenticated
|
|
225
|
+
if not await auth.is_authenticated():
|
|
226
|
+
console.print("[red]Error:[/red] Not authenticated")
|
|
227
|
+
console.print("Run 'hanzo auth login' first")
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
with console.status(f"Creating API key '{name}'..."):
|
|
231
|
+
try:
|
|
232
|
+
result = await auth.create_api_key(
|
|
233
|
+
name=name,
|
|
234
|
+
permissions=list(permissions) if permissions else None,
|
|
235
|
+
expires=expires,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
key = result.get("key")
|
|
239
|
+
console.print(f"[green]✓[/green] Created API key: {name}")
|
|
240
|
+
console.print(
|
|
241
|
+
f"\n[yellow]Save this key - it won't be shown again:[/yellow]"
|
|
242
|
+
)
|
|
243
|
+
console.print(f"{key}")
|
|
244
|
+
console.print(f"\nTo use:")
|
|
245
|
+
console.print(f"export HANZO_API_KEY={key}")
|
|
246
|
+
|
|
247
|
+
except Exception as e:
|
|
248
|
+
console.print(f"[red]Failed to create key: {e}[/red]")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@auth_group.command()
|
|
252
|
+
@click.pass_context
|
|
253
|
+
async def list_keys(ctx):
|
|
254
|
+
"""List your API keys."""
|
|
255
|
+
try:
|
|
256
|
+
from hanzoai.auth import HanzoAuth
|
|
257
|
+
except ImportError:
|
|
258
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
auth = HanzoAuth()
|
|
262
|
+
|
|
263
|
+
# Ensure authenticated
|
|
264
|
+
if not await auth.is_authenticated():
|
|
265
|
+
console.print("[red]Error:[/red] Not authenticated")
|
|
266
|
+
console.print("Run 'hanzo auth login' first")
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
with console.status("Loading API keys..."):
|
|
270
|
+
try:
|
|
271
|
+
keys = await auth.list_api_keys()
|
|
272
|
+
|
|
273
|
+
if keys:
|
|
274
|
+
from rich.table import Table
|
|
275
|
+
|
|
276
|
+
table = Table(title="API Keys")
|
|
277
|
+
table.add_column("Name", style="cyan")
|
|
278
|
+
table.add_column("Created", style="green")
|
|
279
|
+
table.add_column("Last Used", style="yellow")
|
|
280
|
+
table.add_column("Permissions", style="blue")
|
|
281
|
+
table.add_column("Status", style="magenta")
|
|
282
|
+
|
|
283
|
+
for key in keys:
|
|
284
|
+
table.add_row(
|
|
285
|
+
key.get("name", "unknown"),
|
|
286
|
+
key.get("created_at", "unknown"),
|
|
287
|
+
key.get("last_used", "never"),
|
|
288
|
+
", ".join(key.get("permissions", [])),
|
|
289
|
+
key.get("status", "active"),
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
console.print(table)
|
|
293
|
+
else:
|
|
294
|
+
console.print("[yellow]No API keys found[/yellow]")
|
|
295
|
+
console.print("Create one with: hanzo auth create-key")
|
|
296
|
+
|
|
297
|
+
except Exception as e:
|
|
298
|
+
console.print(f"[red]Failed to list keys: {e}[/red]")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@auth_group.command()
|
|
302
|
+
@click.argument("name")
|
|
303
|
+
@click.pass_context
|
|
304
|
+
async def revoke_key(ctx, name: str):
|
|
305
|
+
"""Revoke an API key."""
|
|
306
|
+
try:
|
|
307
|
+
from hanzoai.auth import HanzoAuth
|
|
308
|
+
except ImportError:
|
|
309
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
auth = HanzoAuth()
|
|
313
|
+
|
|
314
|
+
# Ensure authenticated
|
|
315
|
+
if not await auth.is_authenticated():
|
|
316
|
+
console.print("[red]Error:[/red] Not authenticated")
|
|
317
|
+
console.print("Run 'hanzo auth login' first")
|
|
318
|
+
return
|
|
319
|
+
|
|
320
|
+
if click.confirm(f"Revoke API key '{name}'?"):
|
|
321
|
+
with console.status(f"Revoking key '{name}'..."):
|
|
322
|
+
try:
|
|
323
|
+
await auth.revoke_api_key(name)
|
|
324
|
+
console.print(f"[green]✓[/green] Revoked API key: {name}")
|
|
325
|
+
except Exception as e:
|
|
326
|
+
console.print(f"[red]Failed to revoke key: {e}[/red]")
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@auth_group.command()
|
|
330
|
+
@click.pass_context
|
|
331
|
+
async def whoami(ctx):
|
|
332
|
+
"""Show current user information."""
|
|
333
|
+
try:
|
|
334
|
+
from hanzoai.auth import HanzoAuth
|
|
335
|
+
except ImportError:
|
|
336
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
auth = HanzoAuth()
|
|
340
|
+
|
|
341
|
+
# Check if authenticated
|
|
342
|
+
if not await auth.is_authenticated():
|
|
343
|
+
console.print("[yellow]Not authenticated[/yellow]")
|
|
344
|
+
|
|
345
|
+
# Check if API key is set
|
|
346
|
+
if os.environ.get("HANZO_API_KEY"):
|
|
347
|
+
console.print("HANZO_API_KEY is set but may be invalid")
|
|
348
|
+
else:
|
|
349
|
+
console.print("Run 'hanzo auth login' to authenticate")
|
|
350
|
+
return
|
|
351
|
+
|
|
352
|
+
with console.status("Loading user information..."):
|
|
353
|
+
try:
|
|
354
|
+
user = await auth.get_user_info()
|
|
355
|
+
|
|
356
|
+
console.print(f"[cyan]User Information:[/cyan]")
|
|
357
|
+
console.print(f" ID: {user.get('id', 'unknown')}")
|
|
358
|
+
console.print(f" Email: {user.get('email', 'unknown')}")
|
|
359
|
+
console.print(f" Name: {user.get('name', 'unknown')}")
|
|
360
|
+
|
|
361
|
+
if org := user.get("organization"):
|
|
362
|
+
console.print(f"\n[cyan]Organization:[/cyan]")
|
|
363
|
+
console.print(f" Name: {org.get('name', 'unknown')}")
|
|
364
|
+
console.print(f" Role: {org.get('role', 'member')}")
|
|
365
|
+
|
|
366
|
+
if teams := user.get("teams"):
|
|
367
|
+
console.print(f"\n[cyan]Teams:[/cyan]")
|
|
368
|
+
for team in teams:
|
|
369
|
+
console.print(f" • {team}")
|
|
370
|
+
|
|
371
|
+
if perms := user.get("permissions"):
|
|
372
|
+
console.print(f"\n[cyan]Permissions:[/cyan]")
|
|
373
|
+
for perm in sorted(perms):
|
|
374
|
+
console.print(f" • {perm}")
|
|
375
|
+
|
|
376
|
+
except Exception as e:
|
|
377
|
+
console.print(f"[red]Error: {e}[/red]")
|
hanzo/commands/chat.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"""Chat command for interactive AI conversations."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import os
|
|
4
5
|
from typing import Optional
|
|
5
6
|
|
|
6
7
|
import click
|
|
7
8
|
import httpx
|
|
8
9
|
from rich.markdown import Markdown
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich import box
|
|
9
12
|
|
|
10
13
|
from ..utils.output import console
|
|
11
14
|
|
|
@@ -88,20 +91,32 @@ async def ask_once(
|
|
|
88
91
|
|
|
89
92
|
try:
|
|
90
93
|
if local:
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
# Try router first, then fall back to local node
|
|
95
|
+
base_urls = [
|
|
96
|
+
"http://localhost:4000", # Hanzo router default port
|
|
97
|
+
"http://localhost:8000", # Local node port
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
base_url = None
|
|
101
|
+
for url in base_urls:
|
|
102
|
+
try:
|
|
103
|
+
async with httpx.AsyncClient() as client:
|
|
104
|
+
await client.get(f"{url}/health", timeout=1.0)
|
|
105
|
+
base_url = url
|
|
106
|
+
break
|
|
107
|
+
except (httpx.ConnectError, httpx.TimeoutException):
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
if not base_url:
|
|
99
111
|
console.print(
|
|
100
|
-
"[yellow]
|
|
112
|
+
"[yellow]No local AI server running.[/yellow]\n"
|
|
113
|
+
"Start one of:\n"
|
|
114
|
+
" • Hanzo router: hanzo router start\n"
|
|
115
|
+
" • Local node: hanzo serve"
|
|
101
116
|
)
|
|
102
117
|
return
|
|
103
118
|
|
|
104
|
-
# Make request to local
|
|
119
|
+
# Make request to local node
|
|
105
120
|
async with httpx.AsyncClient() as client:
|
|
106
121
|
response = await client.post(
|
|
107
122
|
f"{base_url}/v1/chat/completions",
|
|
@@ -113,16 +128,31 @@ async def ask_once(
|
|
|
113
128
|
else:
|
|
114
129
|
# Use cloud API
|
|
115
130
|
try:
|
|
116
|
-
|
|
131
|
+
# Try different import paths
|
|
132
|
+
try:
|
|
133
|
+
from hanzoai import completion
|
|
134
|
+
except ImportError:
|
|
135
|
+
try:
|
|
136
|
+
from pkg.hanzoai import completion
|
|
137
|
+
except ImportError:
|
|
138
|
+
# Fallback to using litellm directly
|
|
139
|
+
import litellm
|
|
140
|
+
def completion(**kwargs):
|
|
141
|
+
import os
|
|
142
|
+
api_key = os.getenv("HANZO_API_KEY")
|
|
143
|
+
if api_key:
|
|
144
|
+
kwargs["api_key"] = api_key
|
|
145
|
+
kwargs["api_base"] = "https://api.hanzo.ai/v1"
|
|
146
|
+
return litellm.completion(**kwargs)
|
|
117
147
|
|
|
118
148
|
result = completion(
|
|
119
149
|
model=f"anthropic/{model}" if "claude" in model else model,
|
|
120
150
|
messages=messages,
|
|
121
151
|
)
|
|
122
152
|
content = result.choices[0].message.content
|
|
123
|
-
except ImportError:
|
|
124
|
-
console.print("[red]Error:[/red]
|
|
125
|
-
console.print("Install with: pip install
|
|
153
|
+
except ImportError as e:
|
|
154
|
+
console.print(f"[red]Error:[/red] Missing dependencies: {e}")
|
|
155
|
+
console.print("Install with: pip install litellm")
|
|
126
156
|
return
|
|
127
157
|
|
|
128
158
|
# Display response
|
|
@@ -166,7 +196,7 @@ async def interactive_chat(ctx, model: str, local: bool, system: Optional[str]):
|
|
|
166
196
|
console.print("AI: ", end="")
|
|
167
197
|
with console.status(""):
|
|
168
198
|
if local:
|
|
169
|
-
# Use local
|
|
199
|
+
# Use local node
|
|
170
200
|
async with httpx.AsyncClient() as client:
|
|
171
201
|
response = await client.post(
|
|
172
202
|
"http://localhost:8000/v1/chat/completions",
|
|
@@ -181,7 +211,21 @@ async def interactive_chat(ctx, model: str, local: bool, system: Optional[str]):
|
|
|
181
211
|
content = result["choices"][0]["message"]["content"]
|
|
182
212
|
else:
|
|
183
213
|
# Use cloud API
|
|
184
|
-
|
|
214
|
+
try:
|
|
215
|
+
from hanzoai import completion
|
|
216
|
+
except ImportError:
|
|
217
|
+
try:
|
|
218
|
+
from pkg.hanzoai import completion
|
|
219
|
+
except ImportError:
|
|
220
|
+
# Fallback to using litellm directly
|
|
221
|
+
import litellm
|
|
222
|
+
def completion(**kwargs):
|
|
223
|
+
import os
|
|
224
|
+
api_key = os.getenv("HANZO_API_KEY")
|
|
225
|
+
if api_key:
|
|
226
|
+
kwargs["api_key"] = api_key
|
|
227
|
+
kwargs["api_base"] = "https://api.hanzo.ai/v1"
|
|
228
|
+
return litellm.completion(**kwargs)
|
|
185
229
|
|
|
186
230
|
result = completion(
|
|
187
231
|
model=f"anthropic/{model}" if "claude" in model else model,
|