1 line
17 KiB
JSON
1 line
17 KiB
JSON
{"repo": "cdatasoftware/langgraph-customer-health-agent", "files": {"run.py": {"d62d697c6cacc1971dff3f3241934ccd3b4eb5e9": {"t": 1785674055.9762993, "n": 15892, "h": "a228e4cb16a18f1f", "c": "\"\"\"Interactive runner for the LangGraph Customer Health Agent.\n\nRun with: python run.py\n\"\"\"\nimport os\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n# ---------------------------------------------------------------------------\n# Auto-install rich if missing\n# ---------------------------------------------------------------------------\ntry:\n from rich.console import Console\n from rich.panel import Panel\n from rich.prompt import Prompt, Confirm\n from rich.table import Table\n from rich.text import Text\nexcept ImportError:\n print(\"Installing rich library...\")\n subprocess.check_call(\n [sys.executable, \"-m\", \"pip\", \"install\", \"rich\", \"-q\"],\n )\n from rich.console import Console\n from rich.panel import Panel\n from rich.prompt import Prompt, Confirm\n from rich.table import Table\n from rich.text import Text\n\nconsole = Console()\n\n# ---------------------------------------------------------------------------\n# Paths\n# ---------------------------------------------------------------------------\nROOT = Path(__file__).resolve().parent\nENV_FILE = ROOT / \".env\"\nSRC_DIR = ROOT / \"src\"\nCACHE_FILE = Path.home() / \".cache\" / \"langgraph-health\" / \"schema.json\"\n\n# ---------------------------------------------------------------------------\n# LLM provider configs\n# ---------------------------------------------------------------------------\nPROVIDERS = {\n \"1\": {\n \"name\": \"OpenAI\",\n \"provider\": \"openai\",\n \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4-turbo\"],\n \"default_model\": \"gpt-4o\",\n \"key_var\": \"OPENAI_API_KEY\",\n \"key_hint\": \"sk-proj-...\",\n \"extra_vars\": {},\n },\n \"2\": {\n \"name\": \"Google Gemini\",\n \"provider\": \"google\",\n \"models\": [\"gemini-2.0-flash\", \"gemini-1.5-pro\", \"gemini-1.5-flash\"],\n \"default_model\": \"gemini-2.0-flash\",\n \"key_var\": \"GOOGLE_API_KEY\",\n \"key_hint\": \"AIza...\",\n \"extra_vars\": {},\n },\n \"3\": {\n \"name\": \"DeepSeek\",\n \"provider\": \"openai\",\n \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"],\n \"default_model\": \"deepseek-chat\",\n \"key_var\": \"OPENAI_API_KEY\",\n \"key_hint\": \"sk-...\",\n \"extra_vars\": {\"OPENAI_API_BASE\": \"https://api.deepseek.com\"},\n },\n}\n\n# ---------------------------------------------------------------------------\n# Sample data for suggestions\n# ---------------------------------------------------------------------------\nSAMPLE_ACCOUNTS = [\n \"Premium Auto Group Europe\",\n \"Nordic Energy Solutions\",\n \"Pacific Rim Logistics\",\n]\n\nSAMPLE_QUERIES = [\n \"Show me the top 10 customers by annual revenue\",\n \"Which industries have the most high-priority open tickets?\",\n \"How many open opportunities do we have and what is the total value?\",\n]\n\n\n# ---------------------------------------------------------------------------\n# Helpers\n# ---------------------------------------------------------------------------\ndef _load_env_file():\n \"\"\"Parse .env without python-dotenv dependency.\"\"\"\n if not ENV_FILE.exists():\n return {}\n env = {}\n for line in ENV_FILE.read_text(encoding=\"utf-8\").splitlines():\n line = line.strip()\n if not line or line.startswith(\"#\"):\n continue\n if \"=\" in line:\n key, _, value = line.partition(\"=\")\n env[key.strip()] = value.strip()\n return env\n\n\ndef _run_main(*args):\n \"\"\"Run src/main.py with the given arguments.\"\"\"\n cmd = [sys.executable, str(SRC_DIR / \"main.py\")] + list(args)\n console.print(f\"\\n[dim]> {' '.join(cmd)}[/dim]\\n\")\n console.rule(style=\"dim\")\n result = subprocess.run(cmd, cwd=str(ROOT))\n console.rule(style=\"dim\")\n return result.returncode\n\n\n# ---------------------------------------------------------------------------\n# Banner & menu\n# ---------------------------------------------------------------------------\ndef show_banner():\n console.print()\n console.print(\n Panel(\n \"[bold white]LangGraph Customer Health Agent[/bold white]\\n\"\n \"[dim]Autonomous data discovery and analysis powered by \"\n \"CData Connect AI[/dim]\",\n border_style=\"blue\",\n padding=(1, 2),\n )\n )\n console.print()\n\n\ndef show_menu():\n table = Table(show_header=False, box=None, padding=(0, 2))\n table.add_column(style=\"bold cyan\", width=4)\n table.add_column()\n table.add_row(\"1.\", \"Setup wizard [dim](configure credentials & LLM provider)[/dim]\")\n table.add_row(\"2.\", \"Run health analysis [dim](for a specific account)[/dim]\")\n table.add_row(\"3.\", \"Run open-ended query [dim](ask anything about your data)[/dim]\")\n table.add_row(\"4.\", \"Refresh schema cache\")\n table.add_row(\"5.\", \"Check setup [dim](verify credentials & MCP connection)[/dim]\")\n table.add_row(\"0.\", \"Exit\")\n console.print(table)\n console.print()\n\n\n# ---------------------------------------------------------------------------\n# 1. Setup wizard\n# ---------------------------------------------------------------------------\ndef setup_wizard():\n console.print(\"\\n[bold blue]--- Setup Wizard ---[/bold blue]\\n\")\n\n if ENV_FILE.exists():\n if not Confirm.ask(\n \"[yellow].env already exists.[/yellow] Reconfigure?\", default=False\n ):\n return\n\n # ---- CData credentials ----\n console.print(\"[bold]Step 1: CData Connect AI Credentials[/bold]\")\n console.print(\n \"[dim]Sign up or log in at https://cloud.cdata.com/\\n\"\n \"Create a PAT under Settings > Access Tokens[/dim]\\n\"\n )\n email = Prompt.ask(\" CData email\")\n pat = Prompt.ask(\" CData PAT (Personal Access Token)\")\n\n # ---- Optional catalog ----\n catalog = \"\"\n console.print()\n if Confirm.ask(\n \"Force a specific catalog? [dim](recommended for demos to skip discovery)[/dim]\",\n default=False,\n ):\n catalog = Prompt.ask(\" Catalog name\")\n\n # ---- LLM provider ----\n console.print(\"\\n[bold]Step 2: LLM Provider[/bold]\\n\")\n table = Table(show_header=True, box=None, padding=(0, 2))\n table.add_column(\"#\", style=\"bold cyan\", width=3)\n table.add_column(\"Provider\")\n table.add_column(\"Default Model\", style=\"dim\")\n for key, cfg in PROVIDERS.items():\n table.add_row(key, cfg[\"name\"], cfg[\"default_model\"])\n console.print(table)\n console.print(\n \"\\n[dim]To add more providers (Anthropic, Ollama, Grok), edit .env after setup.\\n\"\n \"Set LLM_PROVIDER and LLM_MODEL -- see README > Multi-Provider LLM Support.[/dim]\\n\"\n )\n\n choice = Prompt.ask(\" Select provider\", choices=[\"1\", \"2\", \"3\"], default=\"1\")\n provider_cfg = PROVIDERS[choice]\n\n # ---- Model selection ----\n models = provider_cfg[\"models\"]\n console.print(f\"\\n Available models for {provider_cfg['name']}:\")\n for i, m in enumerate(models, 1):\n suffix = \" [dim](default)[/dim]\" if m == provider_cfg[\"default_model\"] else \"\"\n console.print(f\" [cyan]{i}.[/cyan] {m}{suffix}\")\n console.print()\n model_choice = Prompt.ask(\n \" Select model\",\n choices=[str(i) for i in range(1, len(models) + 1)],\n default=\"1\",\n )\n model = models[int(model_choice) - 1]\n\n # ---- API key ----\n console.print()\n api_key = Prompt.ask(\n f\" {provider_cfg['name']} API key [dim]({provider_cfg['key_hint']})[/dim]\",\n password=True,\n )\n\n # ---- Build .env ----\n lines = [\n \"# CData Connect AI\",\n f\"CDATA_EMAIL={email}\",\n f\"CDATA_PAT={pat}\",\n \"\",\n \"# LLM Provider\",\n f\"LLM_PROVIDER={provider_cfg['provider']}\",\n f\"LLM_MODEL={model}\",\n f\"{provider_cfg['key_var']}={api_key}\",\n ]\n\n for var, val in provider_cfg.get(\"extra_vars\", {}).items():\n lines.append(f\"{var}={val}\")\n\n if catalog:\n lines.extend([\"\", f\"# Demo catalog (skip catalog discovery)\", f\"CDATA_CATALOG={catalog}\"])\n\n lines.extend([\n \"\",\n \"# Optional (uncomment to customize)\",\n \"# SCHEMA_CACHE_TTL=86400\",\n \"# LOG_LEVEL=INFO\",\n \"# MAX_ITERATIONS=25\",\n ])\n\n ENV_FILE.write_text(\"\\n\".join(lines) + \"\\n\", encoding=\"utf-8\")\n console.print(\"\\n[bold green]Setup complete![/bold green] Credentials saved to .env\")\n console.print(\"[dim]Run option 5 (Check setup) to verify the connection.[/dim]\")\n\n\n# ---------------------------------------------------------------------------\n# 2. Run health analysis\n# ---------------------------------------------------------------------------\ndef run_health_analysis():\n console.print(\"\\n[bold blue]--- Health Analysis ---[/bold blue]\\n\")\n console.print(\n \"Examples from the demo dataset [dim](type any account name from your data source)[/dim]:\"\n )\n for i, name in enumerate(SAMPLE_ACCOUNTS, 1):\n console.print(f\" [cyan]{i}.[/cyan] {name}\")\n console.print()\n\n account = Prompt.ask(\"Enter account name [dim](or pick 1-3 for examples)[/dim]\")\n if account in (\"1\", \"2\", \"3\"):\n account = SAMPLE_ACCOUNTS[int(account) - 1]\n\n verbose = Confirm.ask(\"Verbose output?\", default=False)\n\n args = [\"--account\", account]\n if verbose:\n args.append(\"--verbose\")\n\n _run_main(*args)\n\n\n# ---------------------------------------------------------------------------\n# 3. Run open-ended query\n# ---------------------------------------------------------------------------\ndef run_open_query():\n console.print(\"\\n[bold blue]--- Open-Ended Query ---[/bold blue]\\n\")\n console.print(\"Example queries [dim](type any question about your data)[/dim]:\")\n for i, q in enumerate(SAMPLE_QUERIES, 1):\n console.print(f\" [cyan]{i}.[/cyan] {q}\")\n console.print()\n\n query = Prompt.ask(\"Enter your question [dim](or pick 1-3 for examples)[/dim]\")\n if query in (\"1\", \"2\", \"3\"):\n query = SAMPLE_QUERIES[int(query) - 1]\n\n verbose = Confirm.ask(\"Verbose output?\", default=False)\n\n args = [query]\n if verbose:\n args.append(\"--verbose\")\n\n _run_main(*args)\n\n\n# ---------------------------------------------------------------------------\n# 4. Refresh schema cache\n# ---------------------------------------------------------------------------\ndef refresh_cache():\n console.print()\n if CACHE_FILE.exists():\n CACHE_FILE.unlink()\n console.print(\"[green]Schema cache cleared.[/green] Next run will re-discover schemas.\")\n else:\n console.print(\"[yellow]No cache file found.[/yellow] Nothing to clear.\")\n\n\n# ---------------------------------------------------------------------------\n# 5. Check setup\n# ---------------------------------------------------------------------------\ndef check_setup():\n console.print(\"\\n[bold blue]--- Setup Check ---[/bold blue]\\n\")\n\n # ---- .env file ----\n if not ENV_FILE.exists():\n console.print(\"[red]No .env file found.[/red] Run the setup wizard first (option 1).\")\n return\n\n console.print(\"[green]OK[/green] .env file found\")\n\n env = _load_env_file()\n\n # ---- Required CData vars ----\n missing = [v for v in (\"CDATA_EMAIL\", \"CDATA_PAT\") if not env.get(v)]\n if missing:\n console.print(f\"[red]MISSING[/red] Required variables: {', '.join(missing)}\")\n return\n console.print(\"[green]OK[/green] CData credentials configured\")\n\n # ---- LLM config ----\n provider = env.get(\"LLM_PROVIDER\", \"openai\")\n model = env.get(\"LLM_MODEL\", \"gpt-4o\")\n console.print(f\"[green]OK[/green] LLM provider: {provider} ({model})\")\n\n # ---- API key ----\n key_var = \"GOOGLE_API_KEY\" if provider == \"google\" else \"OPENAI_API_KEY\"\n if env.get(key_var):\n masked = env[key_var][:8] + \"...\" + env[key_var][-4:] if len(env[key_var]) > 12 else \"***\"\n console.print(f\"[green]OK[/green] {key_var} configured ({masked})\")\n else:\n console.print(f\"[yellow]WARN[/yellow] {key_var} not found in .env\")\n\n # ---- Optional catalog ----\n if env.get(\"CDATA_CATALOG\"):\n console.print(f\"[green]OK[/green] CDATA_CATALOG: {env['CDATA_CATALOG']}\")\n else:\n console.print(\"[dim]--[/dim] CDATA_CATALOG not set (agent will discover catalogs)\")\n\n # ---- MCP connection test ----\n console.print(\"\\n[dim]Testing MCP connection...[/dim]\")\n try:\n import base64\n import json\n import urllib.request\n import urllib.error\n\n creds = f\"{env['CDATA_EMAIL']}:{env['CDATA_PAT']}\"\n encoded = base64.b64encode(creds.encode()).decode()\n\n payload = json.dumps({\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"initialize\",\n \"params\": {\n \"protocolVersion\": \"2024-11-05\",\n \"capabilities\": {},\n \"clientInfo\": {\"name\": \"health-agent-check\", \"version\": \"1.0\"},\n },\n }).encode()\n\n req = urllib.request.Request(\n \"https://mcp.cloud.cdata.com/mcp\",\n data=payload,\n headers={\n \"Authorization\": f\"Basic {encoded}\",\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json, text/event-stream\",\n },\n method=\"POST\",\n )\n\n start = time.time()\n with urllib.request.urlopen(req, timeout=15) as resp:\n elapsed = time.time() - start\n if resp.status == 200:\n console.print(\n f\"[bold green]OK[/bold green] MCP connection successful ({elapsed:.1f}s)\"\n )\n else:\n console.print(f\"[red]FAIL[/red] MCP returned status {resp.status}\")\n except urllib.error.HTTPError as e:\n console.print(f\"[red]FAIL[/red] MCP error: HTTP {e.code}\")\n except Exception as e:\n console.print(f\"[red]FAIL[/red] MCP connection failed: {e}\")\n\n # ---- Schema cache ----\n if CACHE_FILE.exists():\n age_h = (time.time() - CACHE_FILE.stat().st_mtime) / 3600\n console.print(f\"[green]OK[/green] Schema cache exists (age: {age_h:.1f}h)\")\n else:\n console.print(\"[dim]--[/dim] No schema cache (will be created on first run)\")\n\n # ---- Dependencies check ----\n console.print()\n deps_ok = True\n for pkg, imp in [(\"langgraph\", \"langgraph\"), (\"langchain-core\", \"langchain_core\"),\n (\"requests\", \"requests\"), (\"jinja2\", \"jinja2\")]:\n try:\n __import__(imp)\n console.print(f\"[green]OK[/green] {pkg}\")\n except ImportError:\n console.print(f\"[red]MISSING[/red] {pkg} [dim](pip install -r requirements.txt)[/dim]\")\n deps_ok = False\n\n if deps_ok:\n console.print(\"\\n[bold green]All checks passed![/bold green] Ready to run.\")\n else:\n console.print(\n \"\\n[yellow]Some dependencies missing.[/yellow] \"\n \"Run: [bold]pip install -r requirements.txt[/bold]\"\n )\n\n\n# ---------------------------------------------------------------------------\n# Main loop\n# ---------------------------------------------------------------------------\ndef main():\n show_banner()\n\n try:\n while True:\n show_menu()\n choice = Prompt.ask(\n \"Select option\",\n choices=[\"0\", \"1\", \"2\", \"3\", \"4\", \"5\"],\n default=\"0\",\n )\n\n if choice == \"0\":\n console.print(\"\\n[dim]Session closed.[/dim]\\n\")\n break\n elif choice == \"1\":\n setup_wizard()\n elif choice == \"2\":\n run_health_analysis()\n elif choice == \"3\":\n run_open_query()\n elif choice == \"4\":\n refresh_cache()\n elif choice == \"5\":\n check_setup()\n\n console.print()\n except KeyboardInterrupt:\n console.print(\"\\n\\n[dim]Session closed.[/dim]\\n\")\n sys.exit(0)\n\n\nif __name__ == \"__main__\":\n main()\n"}}}} |