ainative-python 2.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.
@@ -0,0 +1,483 @@
1
+ """
2
+ ZeroDB Local Environment Inspection Commands
3
+
4
+ Commands for examining local ZeroDB environment state and service health.
5
+ """
6
+
7
+ import click
8
+ import json
9
+ import socket
10
+ import requests
11
+ from datetime import datetime, timedelta
12
+ from typing import Dict, Any, Optional, Tuple
13
+ from rich.console import Console
14
+ from rich.table import Table
15
+ from rich.panel import Panel
16
+
17
+
18
+ console = Console()
19
+
20
+
21
+ # Service configuration
22
+ SERVICES = {
23
+ "postgresql": {
24
+ "name": "PostgreSQL",
25
+ "host": "localhost",
26
+ "port": 5432,
27
+ "health_check": "tcp"
28
+ },
29
+ "qdrant": {
30
+ "name": "Qdrant",
31
+ "host": "localhost",
32
+ "port": 6333,
33
+ "health_check": "http",
34
+ "health_url": "http://localhost:6333/collections"
35
+ },
36
+ "minio": {
37
+ "name": "MinIO",
38
+ "host": "localhost",
39
+ "port": 9000,
40
+ "health_check": "http",
41
+ "health_url": "http://localhost:9000/minio/health/live"
42
+ },
43
+ "redpanda": {
44
+ "name": "RedPanda",
45
+ "host": "localhost",
46
+ "port": 9092,
47
+ "health_check": "tcp"
48
+ },
49
+ "api": {
50
+ "name": "API Server",
51
+ "host": "localhost",
52
+ "port": 8000,
53
+ "health_check": "http",
54
+ "health_url": "http://localhost:8000/health"
55
+ }
56
+ }
57
+
58
+
59
+ def check_tcp_port(host: str, port: int, timeout: float = 2.0) -> Tuple[bool, Optional[str]]:
60
+ """Check if a TCP port is open and accepting connections."""
61
+ try:
62
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
63
+ sock.settimeout(timeout)
64
+ result = sock.connect_ex((host, port))
65
+ sock.close()
66
+
67
+ if result == 0:
68
+ return True, None
69
+ else:
70
+ return False, f"Port {port} not accepting connections"
71
+ except socket.gaierror:
72
+ return False, f"Hostname {host} could not be resolved"
73
+ except socket.timeout:
74
+ return False, f"Connection to {host}:{port} timed out"
75
+ except Exception as e:
76
+ return False, f"Error: {str(e)}"
77
+
78
+
79
+ def check_http_endpoint(url: str, timeout: float = 2.0) -> Tuple[bool, Optional[str], Optional[Dict]]:
80
+ """Check if an HTTP endpoint is responding."""
81
+ try:
82
+ response = requests.get(url, timeout=timeout)
83
+ if response.status_code < 400:
84
+ try:
85
+ data = response.json() if response.text else None
86
+ return True, None, data
87
+ except json.JSONDecodeError:
88
+ return True, None, None
89
+ else:
90
+ return False, f"HTTP {response.status_code}", None
91
+ except requests.exceptions.ConnectionError:
92
+ return False, "Connection refused", None
93
+ except requests.exceptions.Timeout:
94
+ return False, "Request timeout", None
95
+ except Exception as e:
96
+ return False, str(e), None
97
+
98
+
99
+ def check_service_health(service_id: str) -> Dict[str, Any]:
100
+ """Check health status of a service."""
101
+ service = SERVICES.get(service_id)
102
+ if not service:
103
+ return {
104
+ "service": service_id,
105
+ "status": "unknown",
106
+ "error": "Service configuration not found"
107
+ }
108
+
109
+ result = {
110
+ "service": service["name"],
111
+ "host": service["host"],
112
+ "port": service["port"],
113
+ "status": "unknown",
114
+ "error": None,
115
+ "data": None
116
+ }
117
+
118
+ if service["health_check"] == "tcp":
119
+ is_up, error = check_tcp_port(service["host"], service["port"])
120
+ result["status"] = "up" if is_up else "down"
121
+ result["error"] = error
122
+ elif service["health_check"] == "http":
123
+ is_up, error, data = check_http_endpoint(service["health_url"])
124
+ result["status"] = "up" if is_up else "down"
125
+ result["error"] = error
126
+ result["data"] = data
127
+
128
+ return result
129
+
130
+
131
+ def format_status_indicator(status: str) -> str:
132
+ """Format status with colored indicator."""
133
+ if status == "up":
134
+ return "[green]✅ UP[/green]"
135
+ elif status == "down":
136
+ return "[red]❌ DOWN[/red]"
137
+ elif status == "warning":
138
+ return "[yellow]⚠️ WARNING[/yellow]"
139
+ else:
140
+ return "[dim]❓ UNKNOWN[/dim]"
141
+
142
+
143
+ @click.group(name="inspect")
144
+ def inspect_group():
145
+ """Examine local ZeroDB environment state."""
146
+ pass
147
+
148
+
149
+ @inspect_group.command(name="config")
150
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
151
+ def inspect_config(output_json: bool):
152
+ """Show current ZeroDB Local configuration."""
153
+ import os
154
+
155
+ config = {
156
+ "environment": {
157
+ "AINATIVE_API_KEY": "***" if os.getenv("AINATIVE_API_KEY") else "Not set",
158
+ "AINATIVE_API_SECRET": "***" if os.getenv("AINATIVE_API_SECRET") else "Not set",
159
+ "AINATIVE_BASE_URL": os.getenv("AINATIVE_BASE_URL", "Default"),
160
+ "AINATIVE_ORG_ID": os.getenv("AINATIVE_ORG_ID", "Not set")
161
+ },
162
+ "services": {
163
+ "PostgreSQL": f"localhost:{SERVICES['postgresql']['port']}",
164
+ "Qdrant": f"localhost:{SERVICES['qdrant']['port']}",
165
+ "MinIO": f"localhost:{SERVICES['minio']['port']}",
166
+ "RedPanda": f"localhost:{SERVICES['redpanda']['port']}",
167
+ "API Server": f"localhost:{SERVICES['api']['port']}"
168
+ },
169
+ "paths": {
170
+ "config_dir": os.path.expanduser("~/.ainative"),
171
+ "data_dir": os.path.expanduser("~/.ainative/data"),
172
+ "logs_dir": os.path.expanduser("~/.ainative/logs")
173
+ }
174
+ }
175
+
176
+ if output_json:
177
+ click.echo(json.dumps(config, indent=2))
178
+ else:
179
+ console.print(Panel("[bold cyan]🔍 ZeroDB Local Configuration[/bold cyan]"))
180
+ console.print()
181
+
182
+ # Environment
183
+ console.print("[bold]Environment Variables:[/bold]")
184
+ for key, value in config["environment"].items():
185
+ console.print(f" • {key}: {value}")
186
+ console.print()
187
+
188
+ # Services
189
+ console.print("[bold]Service Endpoints:[/bold]")
190
+ for service, endpoint in config["services"].items():
191
+ console.print(f" • {service}: {endpoint}")
192
+ console.print()
193
+
194
+ # Paths
195
+ console.print("[bold]File Paths:[/bold]")
196
+ for path_name, path_value in config["paths"].items():
197
+ console.print(f" • {path_name}: {path_value}")
198
+
199
+
200
+ @inspect_group.command(name="services")
201
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
202
+ def inspect_services(output_json: bool):
203
+ """Show all service health status."""
204
+ results = {}
205
+
206
+ for service_id in SERVICES.keys():
207
+ health = check_service_health(service_id)
208
+ results[service_id] = health
209
+
210
+ if output_json:
211
+ click.echo(json.dumps(results, indent=2))
212
+ else:
213
+ console.print()
214
+ console.print(Panel("[bold cyan]🔍 ZeroDB Local Services[/bold cyan]"))
215
+ console.print()
216
+
217
+ table = Table(show_header=True, header_style="bold")
218
+ table.add_column("Service", style="cyan", width=15)
219
+ table.add_column("Status", width=15)
220
+ table.add_column("Endpoint", style="dim", width=20)
221
+ table.add_column("Details", width=30)
222
+
223
+ for service_id, health in results.items():
224
+ status_display = format_status_indicator(health["status"])
225
+ endpoint = f"{health['host']}:{health['port']}"
226
+ details = health.get("error", "Healthy") or "Healthy"
227
+
228
+ table.add_row(
229
+ health["service"],
230
+ status_display,
231
+ endpoint,
232
+ details
233
+ )
234
+
235
+ console.print(table)
236
+ console.print()
237
+
238
+
239
+ @inspect_group.command(name="db")
240
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
241
+ def inspect_db(output_json: bool):
242
+ """Show PostgreSQL database statistics."""
243
+ # Check PostgreSQL health first
244
+ health = check_service_health("postgresql")
245
+
246
+ if health["status"] != "up":
247
+ if output_json:
248
+ click.echo(json.dumps({"error": "PostgreSQL is not running", "status": "down"}, indent=2))
249
+ else:
250
+ console.print(f"[red]❌ PostgreSQL is not running[/red]")
251
+ console.print(f"Details: {health.get('error', 'Unknown error')}")
252
+ return
253
+
254
+ # Try to get database stats via API
255
+ try:
256
+ api_health = check_service_health("api")
257
+ if api_health["status"] == "up":
258
+ # Try to get stats from API
259
+ response = requests.get("http://localhost:8000/v1/admin/db/stats", timeout=5)
260
+ if response.status_code == 200:
261
+ stats = response.json()
262
+
263
+ if output_json:
264
+ click.echo(json.dumps(stats, indent=2))
265
+ else:
266
+ console.print()
267
+ console.print(Panel("[bold cyan]🗄️ PostgreSQL Database Statistics[/bold cyan]"))
268
+ console.print()
269
+
270
+ table = Table(show_header=True, header_style="bold")
271
+ table.add_column("Metric", style="cyan", width=25)
272
+ table.add_column("Value", width=30)
273
+
274
+ for key, value in stats.items():
275
+ table.add_row(key.replace("_", " ").title(), str(value))
276
+
277
+ console.print(table)
278
+ console.print()
279
+ return
280
+ except Exception as e:
281
+ pass
282
+
283
+ # Fallback: Show basic connection info
284
+ db_info = {
285
+ "status": "up",
286
+ "host": health["host"],
287
+ "port": health["port"],
288
+ "connection": "Accepting connections",
289
+ "note": "Start API server for detailed statistics"
290
+ }
291
+
292
+ if output_json:
293
+ click.echo(json.dumps(db_info, indent=2))
294
+ else:
295
+ console.print()
296
+ console.print(Panel("[bold cyan]🗄️ PostgreSQL Database[/bold cyan]"))
297
+ console.print()
298
+ console.print(f"[green]✅[/green] Status: Running")
299
+ console.print(f"Endpoint: {db_info['host']}:{db_info['port']}")
300
+ console.print(f"Connection: {db_info['connection']}")
301
+ console.print()
302
+ console.print(f"[dim]💡 {db_info['note']}[/dim]")
303
+ console.print()
304
+
305
+
306
+ @inspect_group.command(name="vectors")
307
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
308
+ def inspect_vectors(output_json: bool):
309
+ """Show vector index statistics from Qdrant."""
310
+ # Check Qdrant health
311
+ health = check_service_health("qdrant")
312
+
313
+ if health["status"] != "up":
314
+ if output_json:
315
+ click.echo(json.dumps({"error": "Qdrant is not running", "status": "down"}, indent=2))
316
+ else:
317
+ console.print(f"[red]❌ Qdrant is not running[/red]")
318
+ console.print(f"Details: {health.get('error', 'Unknown error')}")
319
+ return
320
+
321
+ # Try to get collection stats
322
+ try:
323
+ response = requests.get("http://localhost:6333/collections", timeout=5)
324
+ if response.status_code == 200:
325
+ data = response.json()
326
+ collections = data.get("result", {}).get("collections", [])
327
+
328
+ stats = {
329
+ "status": "up",
330
+ "total_collections": len(collections),
331
+ "collections": []
332
+ }
333
+
334
+ # Get details for each collection
335
+ for collection_info in collections:
336
+ collection_name = collection_info.get("name")
337
+ try:
338
+ coll_response = requests.get(f"http://localhost:6333/collections/{collection_name}", timeout=5)
339
+ if coll_response.status_code == 200:
340
+ coll_data = coll_response.json().get("result", {})
341
+ stats["collections"].append({
342
+ "name": collection_name,
343
+ "vectors_count": coll_data.get("vectors_count", 0),
344
+ "points_count": coll_data.get("points_count", 0),
345
+ "indexed_vectors_count": coll_data.get("indexed_vectors_count", 0)
346
+ })
347
+ except:
348
+ pass
349
+
350
+ if output_json:
351
+ click.echo(json.dumps(stats, indent=2))
352
+ else:
353
+ console.print()
354
+ console.print(Panel("[bold cyan]🔍 Qdrant Vector Index Statistics[/bold cyan]"))
355
+ console.print()
356
+ console.print(f"[green]✅[/green] Status: Running")
357
+ console.print(f"Total Collections: {stats['total_collections']}")
358
+ console.print()
359
+
360
+ if stats["collections"]:
361
+ table = Table(show_header=True, header_style="bold")
362
+ table.add_column("Collection", style="cyan")
363
+ table.add_column("Vectors", justify="right")
364
+ table.add_column("Points", justify="right")
365
+ table.add_column("Indexed", justify="right")
366
+
367
+ for coll in stats["collections"]:
368
+ table.add_row(
369
+ coll["name"],
370
+ str(coll["vectors_count"]),
371
+ str(coll["points_count"]),
372
+ str(coll["indexed_vectors_count"])
373
+ )
374
+
375
+ console.print(table)
376
+ else:
377
+ console.print("[dim]No collections found[/dim]")
378
+ console.print()
379
+ return
380
+ except Exception as e:
381
+ pass
382
+
383
+ # Fallback
384
+ vector_info = {
385
+ "status": "up",
386
+ "host": health["host"],
387
+ "port": health["port"],
388
+ "note": "Could not retrieve collection statistics"
389
+ }
390
+
391
+ if output_json:
392
+ click.echo(json.dumps(vector_info, indent=2))
393
+ else:
394
+ console.print()
395
+ console.print(Panel("[bold cyan]🔍 Qdrant Vector Index[/bold cyan]"))
396
+ console.print()
397
+ console.print(f"[green]✅[/green] Status: Running")
398
+ console.print(f"Endpoint: {vector_info['host']}:{vector_info['port']}")
399
+ console.print()
400
+ console.print(f"[yellow]⚠️[/yellow] {vector_info['note']}")
401
+ console.print()
402
+
403
+
404
+ @inspect_group.command(name="sync")
405
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
406
+ def inspect_sync(output_json: bool):
407
+ """Show sync status and last sync time."""
408
+ import os
409
+ from pathlib import Path
410
+
411
+ # Check for sync state file
412
+ sync_state_path = Path.home() / ".ainative" / "sync_state.json"
413
+
414
+ if sync_state_path.exists():
415
+ try:
416
+ with open(sync_state_path, 'r') as f:
417
+ sync_state = json.load(f)
418
+
419
+ last_sync = sync_state.get("last_sync")
420
+ if last_sync:
421
+ last_sync_dt = datetime.fromisoformat(last_sync)
422
+ time_since = datetime.now() - last_sync_dt
423
+
424
+ if time_since < timedelta(minutes=5):
425
+ status = "synced"
426
+ status_text = "Recently synced"
427
+ elif time_since < timedelta(hours=1):
428
+ status = "warning"
429
+ status_text = "Sync recommended"
430
+ else:
431
+ status = "outdated"
432
+ status_text = "Sync needed"
433
+ else:
434
+ status = "never"
435
+ status_text = "Never synced"
436
+ last_sync_dt = None
437
+ except Exception as e:
438
+ status = "error"
439
+ status_text = f"Error reading sync state: {str(e)}"
440
+ last_sync_dt = None
441
+ sync_state = {}
442
+ else:
443
+ status = "never"
444
+ status_text = "Never synced"
445
+ last_sync_dt = None
446
+ sync_state = {}
447
+
448
+ result = {
449
+ "status": status,
450
+ "status_text": status_text,
451
+ "last_sync": str(last_sync_dt) if last_sync_dt else None,
452
+ "sync_state_file": str(sync_state_path),
453
+ "file_exists": sync_state_path.exists(),
454
+ "details": sync_state
455
+ }
456
+
457
+ if output_json:
458
+ click.echo(json.dumps(result, indent=2))
459
+ else:
460
+ console.print()
461
+ console.print(Panel("[bold cyan]🔄 Sync Status[/bold cyan]"))
462
+ console.print()
463
+
464
+ if status == "synced":
465
+ console.print(f"[green]✅ {status_text}[/green]")
466
+ elif status == "warning":
467
+ console.print(f"[yellow]⚠️ {status_text}[/yellow]")
468
+ elif status in ["outdated", "never"]:
469
+ console.print(f"[red]❌ {status_text}[/red]")
470
+ else:
471
+ console.print(f"[dim]❓ {status_text}[/dim]")
472
+
473
+ if last_sync_dt:
474
+ console.print(f"Last Sync: {last_sync_dt.strftime('%Y-%m-%d %H:%M:%S')}")
475
+ console.print(f"Time Since: {str(time_since).split('.')[0]}")
476
+
477
+ console.print(f"State File: {sync_state_path}")
478
+ console.print(f"File Exists: {'Yes' if result['file_exists'] else 'No'}")
479
+ console.print()
480
+
481
+ if status != "synced":
482
+ console.print("[dim]💡 Run 'ainative sync' to synchronize local and remote state[/dim]")
483
+ console.print()
@@ -0,0 +1,119 @@
1
+ """
2
+ Agent Learning CLI Commands
3
+
4
+ Commands for agent learning and feedback.
5
+ """
6
+
7
+ import click
8
+ import json
9
+ from typing import Optional
10
+ from rich.console import Console
11
+ from rich.panel import Panel
12
+ from rich.table import Table
13
+
14
+ from ..client import AINativeClient
15
+
16
+
17
+ console = Console()
18
+
19
+
20
+ def get_client() -> AINativeClient:
21
+ """Get authenticated client."""
22
+ import os
23
+ from ..auth import AuthConfig
24
+
25
+ api_key = os.getenv("AINATIVE_API_KEY")
26
+ if not api_key:
27
+ raise click.ClickException("AINATIVE_API_KEY environment variable not set")
28
+
29
+ return AINativeClient(
30
+ auth_config=AuthConfig(api_key=api_key),
31
+ base_url=os.getenv("AINATIVE_BASE_URL"),
32
+ organization_id=os.getenv("AINATIVE_ORG_ID")
33
+ )
34
+
35
+
36
+ @click.group(name="learn")
37
+ def learning_group():
38
+ """Agent learning and feedback."""
39
+ pass
40
+
41
+
42
+ @learning_group.command(name="feedback")
43
+ @click.option("--agent-id", required=True, help="Agent ID")
44
+ @click.option("--interaction-id", required=True, help="Interaction ID")
45
+ @click.option("--rating", type=int, required=True, help="Rating (1-5)")
46
+ @click.option("--comments", help="Optional feedback comments")
47
+ def submit_feedback(agent_id: str, interaction_id: str, rating: int, comments: Optional[str]):
48
+ """Submit feedback for an agent interaction."""
49
+ try:
50
+ if rating < 1 or rating > 5:
51
+ raise click.ClickException("Rating must be between 1 and 5")
52
+
53
+ client = get_client()
54
+ result = client.agent_learning.submit_feedback(
55
+ agent_id=agent_id,
56
+ interaction_id=interaction_id,
57
+ rating=rating,
58
+ comments=comments
59
+ )
60
+
61
+ console.print(Panel(
62
+ f"[green]✓[/green] Feedback submitted",
63
+ title="Success"
64
+ ))
65
+ console.print(json.dumps(result, indent=2))
66
+
67
+ except Exception as e:
68
+ click.echo(f"Error: {str(e)}", err=True)
69
+
70
+
71
+ @learning_group.command(name="metrics")
72
+ @click.option("--agent-id", required=True, help="Agent ID")
73
+ @click.option("--time-range", default="7d", help="Time range (1d, 7d, 30d, 90d)")
74
+ @click.option("--metrics", help="Specific metrics (comma-separated)")
75
+ def get_metrics(agent_id: str, time_range: str, metrics: Optional[str]):
76
+ """Get agent performance metrics."""
77
+ try:
78
+ client = get_client()
79
+ metric_list = metrics.split(",") if metrics else None
80
+
81
+ result = client.agent_learning.get_performance_metrics(
82
+ agent_id=agent_id,
83
+ metric_types=metric_list,
84
+ time_range=time_range
85
+ )
86
+
87
+ console.print(Panel(
88
+ json.dumps(result, indent=2),
89
+ title=f"Performance Metrics - {agent_id}"
90
+ ))
91
+
92
+ except Exception as e:
93
+ click.echo(f"Error: {str(e)}", err=True)
94
+
95
+
96
+ @learning_group.command(name="compare")
97
+ @click.option("--agents", required=True, help="Agent IDs to compare (comma-separated)")
98
+ @click.option("--metrics", required=True, help="Metrics to compare (comma-separated)")
99
+ @click.option("--time-range", default="7d", help="Time range")
100
+ def compare_agents(agents: str, metrics: str, time_range: str):
101
+ """Compare multiple agents."""
102
+ try:
103
+ client = get_client()
104
+ agent_list = [a.strip() for a in agents.split(",")]
105
+ metric_list = [m.strip() for m in metrics.split(",")]
106
+
107
+ result = client.agent_learning.compare_agents(
108
+ agent_ids=agent_list,
109
+ metrics=metric_list,
110
+ time_range=time_range
111
+ )
112
+
113
+ console.print(Panel(
114
+ json.dumps(result, indent=2),
115
+ title="Agent Comparison"
116
+ ))
117
+
118
+ except Exception as e:
119
+ click.echo(f"Error: {str(e)}", err=True)