hanzo 0.3.23__py3-none-any.whl → 0.3.24__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/base_agent.py +4 -5
- hanzo/batch_orchestrator.py +11 -11
- hanzo/cli.py +7 -5
- hanzo/commands/chat.py +57 -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/repl.py +52 -32
- 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-0.3.24.dist-info/METADATA +276 -0
- {hanzo-0.3.23.dist-info → hanzo-0.3.24.dist-info}/RECORD +18 -17
- hanzo-0.3.23.dist-info/METADATA +0 -137
- {hanzo-0.3.23.dist-info → hanzo-0.3.24.dist-info}/WHEEL +0 -0
- {hanzo-0.3.23.dist-info → hanzo-0.3.24.dist-info}/entry_points.txt +0 -0
hanzo/commands/router.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Router command for starting Hanzo router proxy."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from ..utils.output import console
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group(name="router")
|
|
15
|
+
def router_group():
|
|
16
|
+
"""Manage Hanzo router (LLM proxy)."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@router_group.command(name="start")
|
|
21
|
+
@click.option("--port", "-p", default=4000, help="Port to run router on")
|
|
22
|
+
@click.option("--config", "-c", help="Config file path")
|
|
23
|
+
@click.option("--detach", "-d", is_flag=True, help="Run in background")
|
|
24
|
+
@click.pass_context
|
|
25
|
+
def start_router(ctx, port: int, config: Optional[str], detach: bool):
|
|
26
|
+
"""Start the Hanzo router proxy server."""
|
|
27
|
+
# Find router directory
|
|
28
|
+
router_paths = [
|
|
29
|
+
Path.home() / "work" / "hanzo" / "router",
|
|
30
|
+
Path.home() / "hanzo" / "router",
|
|
31
|
+
Path.cwd().parent / "router",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
router_dir = None
|
|
35
|
+
for path in router_paths:
|
|
36
|
+
if path.exists() and (path / "litellm" / "proxy" / "proxy_server.py").exists():
|
|
37
|
+
router_dir = path
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
if not router_dir:
|
|
41
|
+
console.print("[red]Error:[/red] Hanzo router not found")
|
|
42
|
+
console.print("\nPlease clone the router:")
|
|
43
|
+
console.print(" git clone https://github.com/hanzoai/router.git ~/work/hanzo/router")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
console.print(f"[green]✓[/green] Found router at {router_dir}")
|
|
47
|
+
|
|
48
|
+
# Prepare environment
|
|
49
|
+
env = os.environ.copy()
|
|
50
|
+
env["PYTHONPATH"] = str(router_dir) + ":" + env.get("PYTHONPATH", "")
|
|
51
|
+
|
|
52
|
+
# Build command
|
|
53
|
+
cmd = [
|
|
54
|
+
sys.executable,
|
|
55
|
+
"-m", "litellm.proxy.proxy_server",
|
|
56
|
+
"--port", str(port),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
if config:
|
|
60
|
+
# Use provided config
|
|
61
|
+
config_path = Path(config)
|
|
62
|
+
if not config_path.exists():
|
|
63
|
+
console.print(f"[red]Error:[/red] Config file not found: {config}")
|
|
64
|
+
return
|
|
65
|
+
cmd.extend(["--config", str(config_path)])
|
|
66
|
+
else:
|
|
67
|
+
# Check for default config
|
|
68
|
+
default_config = router_dir / "config.yaml"
|
|
69
|
+
if default_config.exists():
|
|
70
|
+
cmd.extend(["--config", str(default_config)])
|
|
71
|
+
console.print(f"[dim]Using config: {default_config}[/dim]")
|
|
72
|
+
|
|
73
|
+
console.print(f"\n[bold cyan]Starting Hanzo Router on port {port}[/bold cyan]")
|
|
74
|
+
console.print(f"API endpoint: http://localhost:{port}/v1")
|
|
75
|
+
console.print("\nPress Ctrl+C to stop\n")
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
# Change to router directory and run
|
|
79
|
+
os.chdir(router_dir)
|
|
80
|
+
|
|
81
|
+
if detach:
|
|
82
|
+
# Run in background
|
|
83
|
+
process = subprocess.Popen(
|
|
84
|
+
cmd,
|
|
85
|
+
env=env,
|
|
86
|
+
stdout=subprocess.DEVNULL,
|
|
87
|
+
stderr=subprocess.DEVNULL,
|
|
88
|
+
start_new_session=True
|
|
89
|
+
)
|
|
90
|
+
console.print(f"[green]✓[/green] Router started in background (PID: {process.pid})")
|
|
91
|
+
console.print(f"Check status: curl http://localhost:{port}/health")
|
|
92
|
+
else:
|
|
93
|
+
# Run in foreground
|
|
94
|
+
subprocess.run(cmd, env=env)
|
|
95
|
+
except KeyboardInterrupt:
|
|
96
|
+
console.print("\n[yellow]Router stopped[/yellow]")
|
|
97
|
+
except Exception as e:
|
|
98
|
+
console.print(f"[red]Error starting router: {e}[/red]")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@router_group.command(name="stop")
|
|
102
|
+
@click.option("--port", "-p", default=4000, help="Port router is running on")
|
|
103
|
+
def stop_router(port: int):
|
|
104
|
+
"""Stop the router."""
|
|
105
|
+
import signal
|
|
106
|
+
|
|
107
|
+
import psutil
|
|
108
|
+
|
|
109
|
+
found = False
|
|
110
|
+
for proc in psutil.process_iter(['pid', 'cmdline']):
|
|
111
|
+
try:
|
|
112
|
+
cmdline = proc.info['cmdline']
|
|
113
|
+
if cmdline and 'proxy_server' in ' '.join(cmdline) and str(port) in ' '.join(cmdline):
|
|
114
|
+
console.print(f"[yellow]Stopping router (PID: {proc.info['pid']})[/yellow]")
|
|
115
|
+
proc.send_signal(signal.SIGTERM)
|
|
116
|
+
proc.wait(timeout=5)
|
|
117
|
+
found = True
|
|
118
|
+
console.print("[green]✓[/green] Router stopped")
|
|
119
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.TimeoutExpired):
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
if not found:
|
|
123
|
+
console.print(f"[yellow]No router found on port {port}[/yellow]")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@router_group.command(name="status")
|
|
127
|
+
@click.option("--port", "-p", default=4000, help="Port to check")
|
|
128
|
+
def router_status(port: int):
|
|
129
|
+
"""Check router status."""
|
|
130
|
+
import httpx
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
response = httpx.get(f"http://localhost:{port}/health", timeout=2.0)
|
|
134
|
+
if response.status_code == 200:
|
|
135
|
+
console.print(f"[green]✓[/green] Router is running on port {port}")
|
|
136
|
+
|
|
137
|
+
# Try to get models
|
|
138
|
+
try:
|
|
139
|
+
models_response = httpx.get(f"http://localhost:{port}/models", timeout=2.0)
|
|
140
|
+
if models_response.status_code == 200:
|
|
141
|
+
data = models_response.json()
|
|
142
|
+
if "data" in data:
|
|
143
|
+
console.print(f"Available models: {len(data['data'])}")
|
|
144
|
+
except Exception:
|
|
145
|
+
pass
|
|
146
|
+
else:
|
|
147
|
+
console.print(f"[yellow]Router responding but unhealthy (status: {response.status_code})[/yellow]")
|
|
148
|
+
except httpx.ConnectError:
|
|
149
|
+
console.print(f"[red]Router not running on port {port}[/red]")
|
|
150
|
+
console.print("\nStart with: hanzo router start")
|
|
151
|
+
except Exception as e:
|
|
152
|
+
console.print(f"[red]Error checking router: {e}[/red]")
|
hanzo/dev.py
CHANGED
hanzo/fallback_handler.py
CHANGED
hanzo/interactive/repl.py
CHANGED
|
@@ -35,8 +35,11 @@ class HanzoREPL:
|
|
|
35
35
|
# Don't print welcome message here since it's already printed in cli.py
|
|
36
36
|
|
|
37
37
|
# Set up command completer
|
|
38
|
+
cli_commands = ["chat", "ask", "agent", "node", "mcp", "network",
|
|
39
|
+
"auth", "config", "tools", "miner", "serve", "net",
|
|
40
|
+
"dev", "router"]
|
|
38
41
|
completer = WordCompleter(
|
|
39
|
-
list(self.commands.keys()) +
|
|
42
|
+
list(self.commands.keys()) + cli_commands,
|
|
40
43
|
ignore_case=True,
|
|
41
44
|
)
|
|
42
45
|
|
|
@@ -58,8 +61,12 @@ class HanzoREPL:
|
|
|
58
61
|
# Execute command
|
|
59
62
|
if cmd in self.commands:
|
|
60
63
|
await self.commands[cmd](args)
|
|
61
|
-
|
|
64
|
+
elif cmd in ["chat", "ask", "agent", "node", "mcp", "network", "auth", "config", "tools", "miner", "serve", "net", "dev", "router"]:
|
|
65
|
+
# Execute known CLI commands
|
|
62
66
|
await self.execute_command(cmd, args)
|
|
67
|
+
else:
|
|
68
|
+
# Treat as chat message if not a known command
|
|
69
|
+
await self.chat_with_ai(command)
|
|
63
70
|
|
|
64
71
|
except KeyboardInterrupt:
|
|
65
72
|
continue
|
|
@@ -83,7 +90,7 @@ class HanzoREPL:
|
|
|
83
90
|
All Hanzo CLI commands are available:
|
|
84
91
|
- `chat <message>` - Chat with AI
|
|
85
92
|
- `agent start` - Start an agent
|
|
86
|
-
- `
|
|
93
|
+
- `node status` - Check node status
|
|
87
94
|
- `mcp tools` - List MCP tools
|
|
88
95
|
- `network agents` - List network agents
|
|
89
96
|
|
|
@@ -91,7 +98,7 @@ All Hanzo CLI commands are available:
|
|
|
91
98
|
```
|
|
92
99
|
hanzo> chat How do I create a Python web server?
|
|
93
100
|
hanzo> agent list
|
|
94
|
-
hanzo>
|
|
101
|
+
hanzo> node start --models llama-3.2-3b
|
|
95
102
|
hanzo> mcp run read_file --arg path=README.md
|
|
96
103
|
```
|
|
97
104
|
|
|
@@ -114,53 +121,60 @@ hanzo> mcp run read_file --arg path=README.md
|
|
|
114
121
|
async def show_status(self, args: str = ""):
|
|
115
122
|
"""Show system status."""
|
|
116
123
|
status = {
|
|
117
|
-
"
|
|
124
|
+
"node": await self.check_node_status(),
|
|
118
125
|
"agents": await self.count_agents(),
|
|
119
126
|
"auth": self.check_auth_status(),
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
self.console.print("[cyan]System Status:[/cyan]")
|
|
123
|
-
self.console.print(f"
|
|
130
|
+
self.console.print(f" Node: {status['node']}")
|
|
124
131
|
self.console.print(f" Agents: {status['agents']}")
|
|
125
132
|
self.console.print(f" Auth: {status['auth']}")
|
|
126
133
|
|
|
127
134
|
async def execute_command(self, cmd: str, args: str):
|
|
128
135
|
"""Execute a CLI command."""
|
|
129
|
-
|
|
136
|
+
import os
|
|
130
137
|
import sys
|
|
131
|
-
|
|
132
|
-
import
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
import shutil
|
|
139
|
+
import subprocess
|
|
140
|
+
|
|
141
|
+
# Find hanzo executable
|
|
142
|
+
hanzo_cmd = shutil.which("hanzo")
|
|
143
|
+
if not hanzo_cmd:
|
|
144
|
+
# Try using Python module directly
|
|
145
|
+
hanzo_cmd = sys.executable
|
|
146
|
+
argv = [hanzo_cmd, "-m", "hanzo", cmd]
|
|
147
|
+
else:
|
|
148
|
+
argv = [hanzo_cmd, cmd]
|
|
149
|
+
|
|
138
150
|
if args:
|
|
139
151
|
import shlex
|
|
140
|
-
|
|
141
152
|
argv.extend(shlex.split(args))
|
|
142
153
|
|
|
143
|
-
#
|
|
154
|
+
# Execute as subprocess to avoid context issues
|
|
144
155
|
try:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
+
result = subprocess.run(
|
|
157
|
+
argv,
|
|
158
|
+
capture_output=True,
|
|
159
|
+
text=True,
|
|
160
|
+
timeout=30,
|
|
161
|
+
env=os.environ.copy() # Pass environment variables
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if result.stdout:
|
|
165
|
+
self.console.print(result.stdout.rstrip())
|
|
166
|
+
if result.stderr and result.returncode != 0:
|
|
167
|
+
self.console.print(f"[red]{result.stderr.rstrip()}[/red]")
|
|
168
|
+
|
|
169
|
+
except subprocess.TimeoutExpired:
|
|
170
|
+
self.console.print("[red]Command timed out[/red]")
|
|
171
|
+
except FileNotFoundError:
|
|
172
|
+
self.console.print("[red]Command not found. Make sure 'hanzo' is installed.[/red]")
|
|
156
173
|
except Exception as e:
|
|
157
174
|
self.console.print(f"[red]Command error: {e}[/red]")
|
|
158
|
-
finally:
|
|
159
|
-
# Restore argv
|
|
160
|
-
sys.argv = orig_argv
|
|
161
175
|
|
|
162
|
-
async def
|
|
163
|
-
"""Check if
|
|
176
|
+
async def check_node_status(self) -> str:
|
|
177
|
+
"""Check if node is running."""
|
|
164
178
|
try:
|
|
165
179
|
import httpx
|
|
166
180
|
|
|
@@ -185,3 +199,9 @@ hanzo> mcp run read_file --arg path=README.md
|
|
|
185
199
|
return "authenticated (saved)"
|
|
186
200
|
else:
|
|
187
201
|
return "not authenticated"
|
|
202
|
+
|
|
203
|
+
async def chat_with_ai(self, message: str):
|
|
204
|
+
"""Chat with AI when user types natural language."""
|
|
205
|
+
# For natural language input, try to use it as a chat message
|
|
206
|
+
# Default to cloud mode to avoid needing local server
|
|
207
|
+
await self.execute_command("ask", f"--cloud {message}")
|
hanzo/mcp_server.py
CHANGED
|
@@ -6,11 +6,16 @@ import click
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def main():
|
|
9
|
-
"""Start the Hanzo MCP server.
|
|
9
|
+
"""Start the Hanzo MCP server.
|
|
10
|
+
|
|
11
|
+
This wrapper defers to hanzo_mcp.cli:main so that the CLI can parse
|
|
12
|
+
transport flags and configure logging BEFORE importing any heavy modules,
|
|
13
|
+
preventing stdio protocol corruption.
|
|
14
|
+
"""
|
|
10
15
|
try:
|
|
11
|
-
from hanzo_mcp.
|
|
16
|
+
from hanzo_mcp.cli import main as cli_main
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
cli_main()
|
|
14
19
|
except ImportError:
|
|
15
20
|
click.echo(
|
|
16
21
|
"Error: hanzo-mcp is not installed. Please run: pip install hanzo[mcp] or pip install hanzo[all]",
|
hanzo/memory_manager.py
CHANGED
hanzo/model_registry.py
CHANGED
|
@@ -6,9 +6,9 @@ eliminating duplication and ensuring consistency across the codebase.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from dataclasses import dataclass, field
|
|
10
|
-
from typing import Dict, List, Optional, Set
|
|
11
9
|
from enum import Enum
|
|
10
|
+
from typing import Set, Dict, List, Optional
|
|
11
|
+
from dataclasses import field, dataclass
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class ModelProvider(Enum):
|
hanzo/rate_limiter.py
CHANGED
hanzo/streaming.py
CHANGED
|
@@ -273,7 +273,7 @@ async def stream_with_fallback(message: str, console: Console = None) -> Optiona
|
|
|
273
273
|
# Try Ollama streaming
|
|
274
274
|
try:
|
|
275
275
|
return await handler.stream_ollama(message)
|
|
276
|
-
except:
|
|
276
|
+
except Exception:
|
|
277
277
|
pass
|
|
278
278
|
|
|
279
279
|
# Fallback to non-streaming with simulated effect
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hanzo
|
|
3
|
+
Version: 0.3.24
|
|
4
|
+
Summary: Hanzo AI - Complete AI Infrastructure Platform with CLI, Router, MCP, and Agent Runtime
|
|
5
|
+
Project-URL: Homepage, https://hanzo.ai
|
|
6
|
+
Project-URL: Repository, https://github.com/hanzoai/python-sdk
|
|
7
|
+
Project-URL: Documentation, https://docs.hanzo.ai/cli
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/hanzoai/python-sdk/issues
|
|
9
|
+
Author-email: Hanzo AI <dev@hanzo.ai>
|
|
10
|
+
Keywords: agents,ai,cli,hanzo,llm,local-ai,mcp,private-ai
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: anthropic>=0.25.0
|
|
24
|
+
Requires-Dist: click>=8.1.0
|
|
25
|
+
Requires-Dist: httpx>=0.23.0
|
|
26
|
+
Requires-Dist: openai>=1.0.0
|
|
27
|
+
Requires-Dist: prompt-toolkit>=3.0.0
|
|
28
|
+
Requires-Dist: pydantic>=2.0.0
|
|
29
|
+
Requires-Dist: pyyaml>=6.0
|
|
30
|
+
Requires-Dist: rich>=13.0.0
|
|
31
|
+
Requires-Dist: typer>=0.9.0
|
|
32
|
+
Provides-Extra: agents
|
|
33
|
+
Requires-Dist: hanzo-agents>=0.1.0; extra == 'agents'
|
|
34
|
+
Requires-Dist: hanzo-network>=0.1.3; extra == 'agents'
|
|
35
|
+
Provides-Extra: ai
|
|
36
|
+
Requires-Dist: hanzoai>=1.0.0; extra == 'ai'
|
|
37
|
+
Provides-Extra: all
|
|
38
|
+
Requires-Dist: hanzo-aci>=0.2.8; extra == 'all'
|
|
39
|
+
Requires-Dist: hanzo-agents>=0.1.0; extra == 'all'
|
|
40
|
+
Requires-Dist: hanzo-mcp>=0.7.0; extra == 'all'
|
|
41
|
+
Requires-Dist: hanzo-memory>=1.0.0; extra == 'all'
|
|
42
|
+
Requires-Dist: hanzo-network>=0.1.3; extra == 'all'
|
|
43
|
+
Requires-Dist: hanzo-repl>=0.1.0; extra == 'all'
|
|
44
|
+
Requires-Dist: hanzoai>=1.0.0; extra == 'all'
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: hanzo-aci>=0.2.8; extra == 'dev'
|
|
47
|
+
Provides-Extra: mcp
|
|
48
|
+
Requires-Dist: hanzo-mcp>=0.7.0; extra == 'mcp'
|
|
49
|
+
Provides-Extra: repl
|
|
50
|
+
Requires-Dist: hanzo-repl>=0.1.0; extra == 'repl'
|
|
51
|
+
Provides-Extra: router
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# Hanzo CLI and Orchestration Tools
|
|
55
|
+
|
|
56
|
+
[](https://pypi.org/project/hanzo/)
|
|
57
|
+
[](https://pypi.org/project/hanzo/)
|
|
58
|
+
|
|
59
|
+
Core CLI and orchestration tools for the Hanzo AI platform.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install hanzo
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Features
|
|
68
|
+
|
|
69
|
+
- **Interactive Chat**: Chat with AI models through CLI
|
|
70
|
+
- **Node Management**: Run local AI inference nodes
|
|
71
|
+
- **Router Control**: Manage LLM proxy router
|
|
72
|
+
- **REPL Interface**: Interactive Python REPL with AI
|
|
73
|
+
- **Batch Orchestration**: Orchestrate multiple AI tasks
|
|
74
|
+
- **Memory Management**: Persistent conversation memory
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
### CLI Commands
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Interactive chat
|
|
82
|
+
hanzo chat
|
|
83
|
+
|
|
84
|
+
# Use specific model
|
|
85
|
+
hanzo chat --model gpt-4
|
|
86
|
+
|
|
87
|
+
# Use router (local proxy)
|
|
88
|
+
hanzo chat --router
|
|
89
|
+
|
|
90
|
+
# Use cloud API
|
|
91
|
+
hanzo chat --cloud
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Node Management
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Start local node
|
|
98
|
+
hanzo node start
|
|
99
|
+
|
|
100
|
+
# Check status
|
|
101
|
+
hanzo node status
|
|
102
|
+
|
|
103
|
+
# List available models
|
|
104
|
+
hanzo node models
|
|
105
|
+
|
|
106
|
+
# Load specific model
|
|
107
|
+
hanzo node load llama2:7b
|
|
108
|
+
|
|
109
|
+
# Stop node
|
|
110
|
+
hanzo node stop
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Router Management
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Start router proxy
|
|
117
|
+
hanzo router start
|
|
118
|
+
|
|
119
|
+
# Check router status
|
|
120
|
+
hanzo router status
|
|
121
|
+
|
|
122
|
+
# List available models
|
|
123
|
+
hanzo router models
|
|
124
|
+
|
|
125
|
+
# View configuration
|
|
126
|
+
hanzo router config
|
|
127
|
+
|
|
128
|
+
# Stop router
|
|
129
|
+
hanzo router stop
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Interactive REPL
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Start REPL
|
|
136
|
+
hanzo repl
|
|
137
|
+
|
|
138
|
+
# In REPL:
|
|
139
|
+
> /help # Show help
|
|
140
|
+
> /models # List models
|
|
141
|
+
> /model gpt-4 # Switch model
|
|
142
|
+
> /clear # Clear context
|
|
143
|
+
> What is Python? # Ask questions
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Python API
|
|
147
|
+
|
|
148
|
+
### Batch Orchestration
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from hanzo.batch_orchestrator import BatchOrchestrator
|
|
152
|
+
|
|
153
|
+
orchestrator = BatchOrchestrator()
|
|
154
|
+
results = await orchestrator.run_batch([
|
|
155
|
+
"Summarize quantum computing",
|
|
156
|
+
"Explain machine learning",
|
|
157
|
+
"Define artificial intelligence"
|
|
158
|
+
])
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Memory Management
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from hanzo.memory_manager import MemoryManager
|
|
165
|
+
|
|
166
|
+
memory = MemoryManager()
|
|
167
|
+
memory.add_to_context("user", "What is Python?")
|
|
168
|
+
memory.add_to_context("assistant", "Python is...")
|
|
169
|
+
context = memory.get_context()
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Fallback Handling
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from hanzo.fallback_handler import FallbackHandler
|
|
176
|
+
|
|
177
|
+
handler = FallbackHandler()
|
|
178
|
+
result = await handler.handle_with_fallback(
|
|
179
|
+
primary_fn=api_call,
|
|
180
|
+
fallback_fn=local_inference
|
|
181
|
+
)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Configuration
|
|
185
|
+
|
|
186
|
+
### Environment Variables
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# API settings
|
|
190
|
+
HANZO_API_KEY=your-api-key
|
|
191
|
+
HANZO_BASE_URL=https://api.hanzo.ai
|
|
192
|
+
|
|
193
|
+
# Router settings
|
|
194
|
+
HANZO_ROUTER_URL=http://localhost:4000/v1
|
|
195
|
+
|
|
196
|
+
# Node settings
|
|
197
|
+
HANZO_NODE_URL=http://localhost:8000/v1
|
|
198
|
+
HANZO_NODE_WORKERS=4
|
|
199
|
+
|
|
200
|
+
# Model preferences
|
|
201
|
+
HANZO_DEFAULT_MODEL=gpt-4
|
|
202
|
+
HANZO_FALLBACK_MODEL=llama2:7b
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Configuration File
|
|
206
|
+
|
|
207
|
+
Create `~/.hanzo/config.yaml`:
|
|
208
|
+
|
|
209
|
+
```yaml
|
|
210
|
+
api:
|
|
211
|
+
key: your-api-key
|
|
212
|
+
base_url: https://api.hanzo.ai
|
|
213
|
+
|
|
214
|
+
router:
|
|
215
|
+
url: http://localhost:4000/v1
|
|
216
|
+
auto_start: true
|
|
217
|
+
|
|
218
|
+
node:
|
|
219
|
+
url: http://localhost:8000/v1
|
|
220
|
+
workers: 4
|
|
221
|
+
models:
|
|
222
|
+
- llama2:7b
|
|
223
|
+
- mistral:7b
|
|
224
|
+
|
|
225
|
+
models:
|
|
226
|
+
default: gpt-4
|
|
227
|
+
fallback: llama2:7b
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Architecture
|
|
231
|
+
|
|
232
|
+
### Components
|
|
233
|
+
|
|
234
|
+
- **CLI**: Command-line interface (`cli.py`)
|
|
235
|
+
- **Chat**: Interactive chat interface (`commands/chat.py`)
|
|
236
|
+
- **Node**: Local AI node management (`commands/node.py`)
|
|
237
|
+
- **Router**: LLM proxy management (`commands/router.py`)
|
|
238
|
+
- **REPL**: Interactive Python REPL (`interactive/repl.py`)
|
|
239
|
+
- **Orchestrator**: Batch task orchestration (`batch_orchestrator.py`)
|
|
240
|
+
- **Memory**: Conversation memory (`memory_manager.py`)
|
|
241
|
+
- **Fallback**: Resilient API handling (`fallback_handler.py`)
|
|
242
|
+
|
|
243
|
+
### Port Allocation
|
|
244
|
+
|
|
245
|
+
- **4000**: Router (LLM proxy)
|
|
246
|
+
- **8000**: Node (local AI)
|
|
247
|
+
- **9550-9553**: Desktop app integration
|
|
248
|
+
|
|
249
|
+
## Development
|
|
250
|
+
|
|
251
|
+
### Setup
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
cd pkg/hanzo
|
|
255
|
+
uv sync --all-extras
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Testing
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Run tests
|
|
262
|
+
pytest tests/
|
|
263
|
+
|
|
264
|
+
# With coverage
|
|
265
|
+
pytest tests/ --cov=hanzo
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Building
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
uv build
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
Apache License 2.0
|