From 57654849b4b0ac600aac067c5a79ee6c7afb53af Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Sun, 3 May 2026 18:36:03 +0300 Subject: [PATCH] fix(demo): install coordinode-embedded from PyPI wheel, drop rustup/Rust build Replace the ~8 min Rust compilation path (rustup + maturin + git source) with a simple pip install from PyPI. coordinode-embedded ships pre-built manylinux wheels so Colab users get the package in ~30 sec with no Rust toolchain required. Also update README timing note. --- README.md | 2 +- demo/notebooks/00_seed_data.ipynb | 145 ++++-------------- .../01_llama_index_property_graph.ipynb | 124 +++------------ demo/notebooks/02_langchain_graph_chain.ipynb | 131 +++------------- demo/notebooks/03_langgraph_agent.ipynb | 121 +++------------ 5 files changed, 96 insertions(+), 427 deletions(-) diff --git a/README.md b/README.md index 949b72e..3713fc4 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ No setup required — runs entirely in-browser using the embedded engine: | 03 · LangGraph agent over graph | [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/structured-world/coordinode-python/blob/main/demo/notebooks/03_langgraph_agent.ipynb) | > Start with **00** to seed the graph — the other notebooks read from it. -> The first cell installs `coordinode-embedded` from source (~8 min); subsequent runs are instant. +> The first cell installs pre-built wheels from PyPI (~30 sec). ## Quick Start diff --git a/demo/notebooks/00_seed_data.ipynb b/demo/notebooks/00_seed_data.ipynb index 23e306c..fffb382 100644 --- a/demo/notebooks/00_seed_data.ipynb +++ b/demo/notebooks/00_seed_data.ipynb @@ -5,37 +5,7 @@ "id": "0", "metadata": {}, "source": [ - "# Seed Demo Data\n", - "\n", - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/structured-world/coordinode-python/blob/main/demo/notebooks/00_seed_data.ipynb)\n", - "\n", - "Populates CoordiNode with a **tech industry knowledge graph**.\n", - "\n", - "> **Note:** Embedded mode writes to `COORDINODE_EMBEDDED_PATH` (default\n", - "> `/content/coordinode-demo.db` in Colab, the OS temp dir locally), so\n", - "> the seeded graph persists across cell reruns and is visible to sibling demo\n", - "> notebooks within the same runtime. Delete the file or set a different path to\n", - "> reset. Connecting to a real CoordiNode server via `COORDINODE_ADDR` is also\n", - "> supported.\n", - "\n", - "**Graph contents:**\n", - "- 10 people (engineers, researchers, founders)\n", - "- 6 companies\n", - "- 8 technologies / research areas\n", - "- ~35 relationships (WORKS_AT, FOUNDED, KNOWS, RESEARCHES, INVENTED, ACQUIRED, USES, \u2026)\n", - "\n", - "All nodes carry a `demo=true` property and a `demo_tag` equal to the `DEMO_TAG` variable\n", - "set in the seed cell. MERGE operations and cleanup are scoped to that tag, so only nodes\n", - "with the matching `demo_tag` are written or removed.\n", - "\n", - "**Environments:**\n", - "- **Google Colab** \u2014 uses `coordinode-embedded` (in-process Rust engine, no server needed). First run compiles from source (~5 min); subsequent runs use the pip cache.\n", - "- **Local / Docker Compose** \u2014 connects to a running CoordiNode server via gRPC.\n", - "\n", - "> **\u26a0\ufe0f Note for real-server use:** All writes and the cleanup step are scoped to `demo_tag`.\n", - "> Collisions can occur if multiple runs reuse the same `demo_tag` value or if `demo_tag` is\n", - "> empty. Run against a fresh/empty database or choose a unique `demo_tag` to avoid affecting\n", - "> unrelated nodes." + "# Seed Demo Data\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/structured-world/coordinode-python/blob/main/demo/notebooks/00_seed_data.ipynb)\n\nPopulates CoordiNode with a **tech industry knowledge graph**.\n\n> **Note:** Embedded mode writes to `COORDINODE_EMBEDDED_PATH` (default\n> `/content/coordinode-demo.db` in Colab, the OS temp dir locally), so\n> the seeded graph persists across cell reruns and is visible to sibling demo\n> notebooks within the same runtime. Delete the file or set a different path to\n> reset. Connecting to a real CoordiNode server via `COORDINODE_ADDR` is also\n> supported.\n\n**Graph contents:**\n- 10 people (engineers, researchers, founders)\n- 6 companies\n- 8 technologies / research areas\n- ~35 relationships (WORKS_AT, FOUNDED, KNOWS, RESEARCHES, INVENTED, ACQUIRED, USES, …)\n\nAll nodes carry a `demo=true` property and a `demo_tag` equal to the `DEMO_TAG` variable\nset in the seed cell. MERGE operations and cleanup are scoped to that tag, so only nodes\nwith the matching `demo_tag` are written or removed.\n\n**Environments:**\n- **Google Colab** — uses `coordinode-embedded` (in-process Rust engine, no server needed). Installs pre-built wheel from PyPI (~30 sec).\n- **Local / Docker Compose** — connects to a running CoordiNode server via gRPC.\n\n> **⚠️ Note for real-server use:** All writes and the cleanup step are scoped to `demo_tag`.\n> Collisions can occur if multiple runs reuse the same `demo_tag` value or if `demo_tag` is\n> empty. Run against a fresh/empty database or choose a unique `demo_tag` to avoid affecting\n> unrelated nodes." ] }, { @@ -57,84 +27,21 @@ "\n", "IN_COLAB = \"google.colab\" in sys.modules\n", "\n", - "# Install coordinode-embedded only when running in Colab AND no gRPC server is configured.\n", - "# If COORDINODE_ADDR is set, a live server is already available \u2014 skip the 5-min Rust build.\n", + "pkgs = [\"coordinode\", \"nest_asyncio\"]\n", "if IN_COLAB and not os.environ.get(\"COORDINODE_ADDR\"):\n", - " # Install Rust toolchain via rustup (https://rustup.rs).\n", - " # Colab's apt packages ship rustc \u22641.75, which cannot build coordinode-embedded\n", - " # (requires Rust \u22651.80 for maturin/pyo3). apt-get is not a viable alternative here.\n", - " # Download the installer to a temp file and execute it explicitly \u2014 this avoids\n", - " # piping remote content directly into a shell while maintaining HTTPS/TLS security\n", - " # through Python's default ssl context (cert-verified, TLS 1.2+).\n", - " # SHA256 pinning of rustup-init is intentionally omitted: rustup.rs does not\n", - " # publish a stable per-release checksum for sh.rustup.rs itself (only for\n", - " # platform-specific rustup-init binaries), and pinning a hash here would break\n", - " # silently on every rustup release. The HTTPS/TLS verification + temp-file\n", - " # execution (not piped to shell) is the rustup team's recommended trust model.\n", - " # No additional env-var gate (e.g. COORDINODE_ENABLE_RUSTUP) is needed:\n", - " # the `IN_COLAB and not COORDINODE_ADDR` check above already ensures this block\n", - " # never runs when a live gRPC server is available, so there is no risk of\n", - " # unintentional execution in local or server environments.\n", - " # Security note: downloading rustup-init via HTTPS with cert verification and\n", - " # executing from a temp file (not piped to shell) is by design \u2014 this is the\n", - " # rustup project's own recommended install method for automated environments.\n", - " # protoc is required by coordinode-raft build (prost-build). Skip if already present (faster reruns),\n", - " # otherwise refresh apt indexes first \u2014 Colab caches can go stale on long-lived runtimes.\n", - " if subprocess.run([\"which\", \"protoc\"], capture_output=True).returncode != 0:\n", - " subprocess.run([\"apt-get\", \"update\", \"-y\", \"-q\"], check=True, timeout=120)\n", - " subprocess.run([\"apt-get\", \"install\", \"-y\", \"-q\", \"protobuf-compiler\"], check=True, timeout=120)\n", - " import ssl as _ssl, tempfile as _tmp, urllib.request as _ur\n", + " pkgs = [\"coordinode-embedded\"] + pkgs\n", "\n", - " _ctx = _ssl.create_default_context()\n", - " with _tmp.NamedTemporaryFile(mode=\"wb\", suffix=\".sh\", delete=False) as _f:\n", - " with _ur.urlopen(\"https://sh.rustup.rs\", context=_ctx, timeout=30) as _r:\n", - " _f.write(_r.read())\n", - " _rustup_path = _f.name\n", - " try:\n", - " subprocess.run([\"/bin/sh\", _rustup_path, \"-y\", \"-q\"], check=True, timeout=300)\n", - " finally:\n", - " os.unlink(_rustup_path)\n", - " # Add cargo to PATH so maturin/pip can find it.\n", - " _cargo_bin = os.path.expanduser(\"~/.cargo/bin\")\n", - " os.environ[\"PATH\"] = f\"{_cargo_bin}{os.pathsep}{os.environ.get('PATH', '')}\"\n", - " subprocess.run([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"maturin\"], check=True, timeout=300)\n", - " subprocess.run(\n", - " [\n", - " sys.executable,\n", - " \"-m\",\n", - " \"pip\",\n", - " \"install\",\n", - " \"-q\",\n", - " \"git+https://github.com/structured-world/coordinode-python.git@50ddc08a89a21fca73be007cb22b57a0054225c3#subdirectory=coordinode-embedded\",\n", - " ],\n", - " check=True,\n", - " timeout=600,\n", - " )\n", - "\n", - "_coordinode_spec = (\n", - " \"git+https://github.com/structured-world/coordinode-python.git@50ddc08a89a21fca73be007cb22b57a0054225c3#subdirectory=coordinode\"\n", - " if IN_COLAB\n", - " else \"coordinode\"\n", - ")\n", "subprocess.run(\n", - " [\n", - " sys.executable,\n", - " \"-m\",\n", - " \"pip\",\n", - " \"install\",\n", - " \"-q\",\n", - " _coordinode_spec,\n", - " \"nest_asyncio\",\n", - " ],\n", + " [sys.executable, \"-m\", \"pip\", \"install\", \"-q\"] + pkgs,\n", " check=True,\n", - " timeout=300,\n", + " timeout=120,\n", ")\n", "\n", "import nest_asyncio\n", "\n", "nest_asyncio.apply()\n", "\n", - "print(\"Ready\")" + "print(\"Ready\")\n" ] }, { @@ -144,8 +51,8 @@ "source": [ "## Connect to CoordiNode\n", "\n", - "- **Colab**: uses `LocalClient(COORDINODE_EMBEDDED_PATH)` \u2014 in-process embedded engine backed by a file under `/content/`, no server required.\n", - "- **Local with server**: set `COORDINODE_ADDR=host:port` to point at a running CoordiNode (no auto-probe \u2014 explicit only).\n", + "- **Colab**: uses `LocalClient(COORDINODE_EMBEDDED_PATH)` — in-process embedded engine backed by a file under `/content/`, no server required.\n", + "- **Local with server**: set `COORDINODE_ADDR=host:port` to point at a running CoordiNode (no auto-probe — explicit only).\n", "- **Local without server**: uses `coordinode-embedded` (file-backed at `COORDINODE_EMBEDDED_PATH`, defaulting to the OS temp dir). Raises `RuntimeError` with install instructions if the package is missing." ] }, @@ -178,7 +85,7 @@ " raise RuntimeError(f\"Health check failed for {COORDINODE_ADDR}\")\n", " print(f\"Connected to {COORDINODE_ADDR}\")\n", "else:\n", - " # No explicit server \u2014 use the embedded in-process engine backed by a file\n", + " # No explicit server — use the embedded in-process engine backed by a file\n", " # so the graph persists across cell reruns and between sibling demo\n", " # notebooks within the same runtime.\n", " try:\n", @@ -202,7 +109,7 @@ "id": "5", "metadata": {}, "source": [ - "## Step 1 \u2014 Clear previous demo data" + "## Step 1 — Clear previous demo data" ] }, { @@ -226,7 +133,7 @@ "print(\"Using DEMO_TAG:\", DEMO_TAG)\n", "# Remove prior demo nodes and any attached relationships in one step to avoid\n", "# duplicate relationship matches during cleanup (undirected MATCH -[r]-() returns\n", - "# each edge twice \u2014 once per endpoint \u2014 causing duplicate-delete errors).\n", + "# each edge twice — once per endpoint — causing duplicate-delete errors).\n", "client.cypher(\n", " \"MATCH (n {demo: true, demo_tag: $tag}) DETACH DELETE n\",\n", " params={\"tag\": DEMO_TAG},\n", @@ -239,7 +146,7 @@ "id": "7", "metadata": {}, "source": [ - "## Step 2 \u2014 Create nodes" + "## Step 2 — Create nodes" ] }, { @@ -249,13 +156,13 @@ "metadata": {}, "outputs": [], "source": [ - "# \u2500\u2500 People \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n", + "# ── People ────────────────────────────────────────────────────────────────\n", "people = [\n", " {\"name\": \"Alice Chen\", \"role\": \"ML Researcher\", \"org\": \"DeepMind\", \"field\": \"Reinforcement Learning\"},\n", " {\"name\": \"Bob Torres\", \"role\": \"Staff Engineer\", \"org\": \"Google\", \"field\": \"Distributed Systems\"},\n", " {\"name\": \"Carol Smith\", \"role\": \"Founder & CEO\", \"org\": \"Synthex\", \"field\": \"NLP\"},\n", " {\"name\": \"David Park\", \"role\": \"Research Scientist\", \"org\": \"OpenAI\", \"field\": \"LLMs\"},\n", - " {\"name\": \"Eva M\u00fcller\", \"role\": \"Systems Architect\", \"org\": \"Synthex\", \"field\": \"Graph Databases\"},\n", + " {\"name\": \"Eva Müller\", \"role\": \"Systems Architect\", \"org\": \"Synthex\", \"field\": \"Graph Databases\"},\n", " {\"name\": \"Frank Liu\", \"role\": \"Principal Engineer\", \"org\": \"Meta\", \"field\": \"Graph ML\"},\n", " {\"name\": \"Grace Okafor\", \"role\": \"PhD Researcher\", \"org\": \"MIT\", \"field\": \"Knowledge Graphs\"},\n", " {\"name\": \"Henry Rossi\", \"role\": \"CTO\", \"org\": \"Synthex\", \"field\": \"Databases\"},\n", @@ -272,7 +179,7 @@ "\n", "print(f\"Created {len(people)} people\")\n", "\n", - "# \u2500\u2500 Companies \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n", + "# ── Companies ─────────────────────────────────────────────────────────────\n", "companies = [\n", " {\"name\": \"Google\", \"industry\": \"Technology\", \"founded\": 1998, \"hq\": \"Mountain View\"},\n", " {\"name\": \"Meta\", \"industry\": \"Technology\", \"founded\": 2004, \"hq\": \"Menlo Park\"},\n", @@ -290,7 +197,7 @@ "\n", "print(f\"Created {len(companies)} companies\")\n", "\n", - "# \u2500\u2500 Technologies \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n", + "# ── Technologies ──────────────────────────────────────────────────────────\n", "technologies = [\n", " {\"name\": \"Transformer\", \"type\": \"Architecture\", \"year\": 2017},\n", " {\"name\": \"Graph Neural Network\", \"type\": \"Algorithm\", \"year\": 2009},\n", @@ -316,7 +223,7 @@ "id": "9", "metadata": {}, "source": [ - "## Step 3 \u2014 Create relationships" + "## Step 3 — Create relationships" ] }, { @@ -332,7 +239,7 @@ " (\"Bob Torres\", \"WORKS_AT\", \"Google\", {}),\n", " (\"Carol Smith\", \"WORKS_AT\", \"Synthex\", {\"since\": 2021}),\n", " (\"David Park\", \"WORKS_AT\", \"OpenAI\", {}),\n", - " (\"Eva M\u00fcller\", \"WORKS_AT\", \"Synthex\", {\"since\": 2022}),\n", + " (\"Eva Müller\", \"WORKS_AT\", \"Synthex\", {\"since\": 2022}),\n", " (\"Frank Liu\", \"WORKS_AT\", \"Meta\", {}),\n", " (\"Grace Okafor\", \"WORKS_AT\", \"MIT\", {}),\n", " (\"Henry Rossi\", \"WORKS_AT\", \"Synthex\", {\"since\": 2021}),\n", @@ -347,7 +254,7 @@ " (\"Carol Smith\", \"KNOWS\", \"Bob Torres\", {}),\n", " (\"Grace Okafor\", \"KNOWS\", \"Alice Chen\", {}),\n", " (\"Frank Liu\", \"KNOWS\", \"James Wright\", {}),\n", - " (\"Eva M\u00fcller\", \"KNOWS\", \"Grace Okafor\", {}),\n", + " (\"Eva Müller\", \"KNOWS\", \"Grace Okafor\", {}),\n", " # RESEARCHES / WORKS_ON\n", " (\"Alice Chen\", \"RESEARCHES\", \"Reinforcement Learning\", {\"since\": 2019}),\n", " (\"David Park\", \"RESEARCHES\", \"LLM\", {\"since\": 2020}),\n", @@ -405,7 +312,7 @@ "id": "11", "metadata": {}, "source": [ - "## Step 4 \u2014 Verify" + "## Step 4 — Verify" ] }, { @@ -450,7 +357,7 @@ " params={\"co\": \"Synthex\", \"tag\": DEMO_TAG},\n", ")\n", "for r in rows:\n", - " print(f\" {r['name']} \u2014 {r['role']}\")\n", + " print(f\" {r['name']} — {r['role']}\")\n", "\n", "print(\"\\n=== What does Synthex use? ===\")\n", "rows = client.cypher(\n", @@ -466,11 +373,11 @@ " params={\"tech\": \"GraphRAG\", \"tag\": DEMO_TAG},\n", ")\n", "for r in rows:\n", - " print(f\" \u2192 {r['dependency']}\")\n", + " print(f\" → {r['dependency']}\")\n", "\n", - "print(\"\\n\u2713 Demo data seeded.\")\n", - "print(\"To query it from notebooks 01\u201303:\")\n", - "print(f\" - Embedded mode: open them with the same COORDINODE_EMBEDDED_PATH (this run: {COORDINODE_EMBEDDED_PATH}) \u2014 they will see this seeded graph.\")\n", + "print(\"\\n✓ Demo data seeded.\")\n", + "print(\"To query it from notebooks 01–03:\")\n", + "print(f\" - Embedded mode: open them with the same COORDINODE_EMBEDDED_PATH (this run: {COORDINODE_EMBEDDED_PATH}) — they will see this seeded graph.\")\n", "print(\" - Server mode: point them at the same running CoordiNode via COORDINODE_ADDR.\")\n", "client.close()" ] @@ -488,4 +395,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/demo/notebooks/01_llama_index_property_graph.ipynb b/demo/notebooks/01_llama_index_property_graph.ipynb index edf7b09..9f30389 100644 --- a/demo/notebooks/01_llama_index_property_graph.ipynb +++ b/demo/notebooks/01_llama_index_property_graph.ipynb @@ -5,24 +5,7 @@ "id": "0", "metadata": {}, "source": [ - "# LlamaIndex + CoordiNode: PropertyGraphIndex\n", - "\n", - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/structured-world/coordinode-python/blob/main/demo/notebooks/01_llama_index_property_graph.ipynb)\n", - "\n", - "Demonstrates `CoordinodePropertyGraphStore` as a backend for LlamaIndex `PropertyGraphIndex`.\n", - "\n", - "**What works right now:**\n", - "- `upsert_nodes` / `upsert_relations` \u2014 idempotent MERGE (safe to call multiple times)\n", - "- `get()` \u2014 look up nodes by ID or properties\n", - "- `get_triplets()` \u2014 all edges (wildcard) or filtered by relation type / entity name\n", - "- `get_rel_map()` \u2014 outgoing relations for a set of nodes (depth=1)\n", - "- `structured_query()` \u2014 arbitrary Cypher pass-through\n", - "- `delete()` \u2014 remove nodes by id or name\n", - "- `get_schema()` \u2014 live text schema of the graph\n", - "\n", - "**Environments:**\n", - "- **Google Colab** \u2014 uses `coordinode-embedded` (in-process Rust engine, no server needed). First run compiles from source (~5 min); subsequent runs use the pip cache.\n", - "- **Local / Docker Compose** \u2014 connects to a running CoordiNode server via gRPC." + "# LlamaIndex + CoordiNode: PropertyGraphIndex\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/structured-world/coordinode-python/blob/main/demo/notebooks/01_llama_index_property_graph.ipynb)\n\nDemonstrates `CoordinodePropertyGraphStore` as a backend for LlamaIndex `PropertyGraphIndex`.\n\n**What works right now:**\n- `upsert_nodes` / `upsert_relations` — idempotent MERGE (safe to call multiple times)\n- `get()` — look up nodes by ID or properties\n- `get_triplets()` — all edges (wildcard) or filtered by relation type / entity name\n- `get_rel_map()` — outgoing relations for a set of nodes (depth=1)\n- `structured_query()` — arbitrary Cypher pass-through\n- `delete()` — remove nodes by id or name\n- `get_schema()` — live text schema of the graph\n\n**Environments:**\n- **Google Colab** — uses `coordinode-embedded` (in-process Rust engine, no server needed). Installs pre-built wheel from PyPI (~30 sec).\n- **Local / Docker Compose** — connects to a running CoordiNode server via gRPC." ] }, { @@ -44,91 +27,26 @@ "\n", "IN_COLAB = \"google.colab\" in sys.modules\n", "\n", - "# Install coordinode-embedded in Colab only (requires Rust build).\n", + "pkgs = [\n", + " \"coordinode\",\n", + " \"llama-index-graph-stores-coordinode\",\n", + " \"llama-index-core\",\n", + " \"nest_asyncio\",\n", + "]\n", "if IN_COLAB and not os.environ.get(\"COORDINODE_ADDR\"):\n", - " # Install Rust toolchain via rustup (https://rustup.rs).\n", - " # Colab's apt packages ship rustc \u22641.75, which cannot build coordinode-embedded\n", - " # (requires Rust \u22651.80 for maturin/pyo3). apt-get is not a viable alternative here.\n", - " # Download the installer to a temp file and execute it explicitly \u2014 this avoids\n", - " # piping remote content directly into a shell while maintaining HTTPS/TLS security\n", - " # through Python's default ssl context (cert-verified, TLS 1.2+).\n", - " # SHA256 pinning of rustup-init is intentionally omitted: rustup.rs does not\n", - " # publish a stable per-release checksum for sh.rustup.rs itself (only for\n", - " # platform-specific rustup-init binaries), and pinning a hash here would break\n", - " # silently on every rustup release. The HTTPS/TLS verification + temp-file\n", - " # execution (not piped to shell) is the rustup team's recommended trust model.\n", - " # No additional env-var gate (e.g. COORDINODE_ENABLE_RUSTUP) is needed:\n", - " # the `IN_COLAB` check above already ensures this block never runs outside\n", - " # Colab sessions, so there is no risk of unintentional execution in local\n", - " # or server environments.\n", - " # Security note: downloading rustup-init via HTTPS with cert verification and\n", - " # executing from a temp file (not piped to shell) is by design \u2014 this is the\n", - " # rustup project's own recommended install method for automated environments.\n", - " # protoc is required by coordinode-raft build (prost-build). Skip if already present (faster reruns),\n", - " # otherwise refresh apt indexes first \u2014 Colab caches can go stale on long-lived runtimes.\n", - " if subprocess.run([\"which\", \"protoc\"], capture_output=True).returncode != 0:\n", - " subprocess.run([\"apt-get\", \"update\", \"-y\", \"-q\"], check=True, timeout=120)\n", - " subprocess.run([\"apt-get\", \"install\", \"-y\", \"-q\", \"protobuf-compiler\"], check=True, timeout=120)\n", - " import ssl as _ssl, tempfile as _tmp, urllib.request as _ur\n", - "\n", - " _ctx = _ssl.create_default_context()\n", - " with _tmp.NamedTemporaryFile(mode=\"wb\", suffix=\".sh\", delete=False) as _f:\n", - " with _ur.urlopen(\"https://sh.rustup.rs\", context=_ctx, timeout=30) as _r:\n", - " _f.write(_r.read())\n", - " _rustup_path = _f.name\n", - " try:\n", - " subprocess.run([\"/bin/sh\", _rustup_path, \"-y\", \"-q\"], check=True, timeout=300)\n", - " finally:\n", - " os.unlink(_rustup_path)\n", - " # Add cargo to PATH so maturin/pip can find it.\n", - " _cargo_bin = os.path.expanduser(\"~/.cargo/bin\")\n", - " os.environ[\"PATH\"] = f\"{_cargo_bin}{os.pathsep}{os.environ.get('PATH', '')}\"\n", - " subprocess.run([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"maturin\"], check=True, timeout=300)\n", - " subprocess.run(\n", - " [\n", - " sys.executable,\n", - " \"-m\",\n", - " \"pip\",\n", - " \"install\",\n", - " \"-q\",\n", - " \"git+https://github.com/structured-world/coordinode-python.git@50ddc08a89a21fca73be007cb22b57a0054225c3#subdirectory=coordinode-embedded\",\n", - " ],\n", - " check=True,\n", - " timeout=600,\n", - " )\n", + " pkgs = [\"coordinode-embedded\"] + pkgs\n", "\n", - "# coordinode-embedded is pinned to a specific git commit because it requires a Rust\n", - "# build (maturin/pyo3) and the embedded engine must match the Python SDK version.\n", - "# coordinode (SDK) is git-pinned in Colab via _coordinode_spec to keep the\n", - "# SDK/proto in sync with this PR; outside Colab it resolves from PyPI.\n", - "# Other packages (LangChain / llama-index / nest_asyncio) are unpinned \u2014 pure\n", - "# Python, release frequently, pip resolves a compatible version.\n", - "_coordinode_spec = (\n", - " \"git+https://github.com/structured-world/coordinode-python.git@50ddc08a89a21fca73be007cb22b57a0054225c3#subdirectory=coordinode\"\n", - " if IN_COLAB\n", - " else \"coordinode\"\n", - ")\n", "subprocess.run(\n", - " [\n", - " sys.executable,\n", - " \"-m\",\n", - " \"pip\",\n", - " \"install\",\n", - " \"-q\",\n", - " _coordinode_spec,\n", - " \"llama-index-graph-stores-coordinode\",\n", - " \"llama-index-core\",\n", - " \"nest_asyncio\",\n", - " ],\n", + " [sys.executable, \"-m\", \"pip\", \"install\", \"-q\"] + pkgs,\n", " check=True,\n", - " timeout=300,\n", + " timeout=120,\n", ")\n", "\n", "import nest_asyncio\n", "\n", "nest_asyncio.apply()\n", "\n", - "print(\"SDK installed\")" + "print(\"SDK installed\")\n" ] }, { @@ -170,7 +88,7 @@ " lines.append(f\" - {r['t']}\")\n", " return \"\\n\".join(lines)\n", "\n", - " # Vector search not available in embedded mode \u2014 requires running CoordiNode server.\n", + " # Vector search not available in embedded mode — requires running CoordiNode server.\n", "\n", " def close(self):\n", " self._lc.close()\n" @@ -214,7 +132,7 @@ " print(f\"Connected to {COORDINODE_ADDR}\")\n", " client = _cc\n", "else:\n", - " # No explicit server \u2014 use the embedded in-process engine backed by a file\n", + " # No explicit server — use the embedded in-process engine backed by a file\n", " # so the graph persists across cell reruns and between sibling demo\n", " # notebooks within the same runtime.\n", " try:\n", @@ -231,7 +149,7 @@ "\n", " _lc = LocalClient(COORDINODE_EMBEDDED_PATH)\n", " # Wrap in _EmbeddedAdapter so CoordinodeGraph/PropertyGraphStore can call\n", - " # get_schema_text() \u2014 LocalClient has .cypher() only.\n", + " # get_schema_text() — LocalClient has .cypher() only.\n", " client = _EmbeddedAdapter(_lc)\n", " print(f\"Using embedded LocalClient at {COORDINODE_EMBEDDED_PATH}\")\n" ] @@ -305,7 +223,7 @@ "id": "11", "metadata": {}, "source": [ - "## 2. get_triplets \u2014 all edges from a node (wildcard)" + "## 2. get_triplets — all edges from a node (wildcard)" ] }, { @@ -326,7 +244,7 @@ "id": "13", "metadata": {}, "source": [ - "## 3. get_rel_map \u2014 relations for a set of nodes" + "## 3. get_rel_map — relations for a set of nodes" ] }, { @@ -348,7 +266,7 @@ "id": "15", "metadata": {}, "source": [ - "## 4. structured_query \u2014 arbitrary Cypher" + "## 4. structured_query — arbitrary Cypher" ] }, { @@ -391,7 +309,7 @@ "id": "19", "metadata": {}, "source": [ - "## 6. Idempotency \u2014 double upsert must not duplicate edges" + "## 6. Idempotency — double upsert must not duplicate edges" ] }, { @@ -401,7 +319,7 @@ "metadata": {}, "outputs": [], "source": [ - "store.upsert_relations(relations) # second call \u2014 should still be exactly 1 edge\n", + "store.upsert_relations(relations) # second call — should still be exactly 1 edge\n", "rows = store.structured_query(\n", " \"MATCH (a {name: $src})-[r:RESEARCHES]->(b {name: $dst}) RETURN count(r) AS cnt\",\n", " param_map={\"src\": f\"Alice-{tag}\", \"dst\": f\"GraphRAG-{tag}\"},\n", @@ -427,7 +345,7 @@ "store.delete(entity_names=[f\"Alice-{tag}\", f\"Bob-{tag}\", f\"GraphRAG-{tag}\"])\n", "print(\"Cleaned up\")\n", "store.close()\n", - "client.close() # injected client \u2014 owned by caller" + "client.close() # injected client — owned by caller" ] } ], @@ -443,4 +361,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/demo/notebooks/02_langchain_graph_chain.ipynb b/demo/notebooks/02_langchain_graph_chain.ipynb index cf23cf1..1f75255 100644 --- a/demo/notebooks/02_langchain_graph_chain.ipynb +++ b/demo/notebooks/02_langchain_graph_chain.ipynb @@ -5,21 +5,7 @@ "id": "0", "metadata": {}, "source": [ - "# LangChain + CoordiNode: Graph Chain\n", - "\n", - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/structured-world/coordinode-python/blob/main/demo/notebooks/02_langchain_graph_chain.ipynb)\n", - "\n", - "Demonstrates `CoordinodeGraph` as a Knowledge Graph backend for LangChain.\n", - "\n", - "**What works right now:**\n", - "- `graph.query()` \u2014 arbitrary Cypher pass-through\n", - "- `graph.schema` / `refresh_schema()` \u2014 live graph schema\n", - "- `add_graph_documents()` \u2014 add Nodes + Relationships from a LangChain `GraphDocument`\n", - "- `GraphCypherQAChain` \u2014 LLM generates Cypher from a natural-language question *(requires `OPENAI_API_KEY`)*\n", - "\n", - "**Environments:**\n", - "- **Google Colab** \u2014 uses `coordinode-embedded` (in-process Rust engine, no server needed). First run compiles from source (~5 min); subsequent runs use the pip cache.\n", - "- **Local / Docker Compose** \u2014 connects to a running CoordiNode server via gRPC." + "# LangChain + CoordiNode: Graph Chain\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/structured-world/coordinode-python/blob/main/demo/notebooks/02_langchain_graph_chain.ipynb)\n\nDemonstrates `CoordinodeGraph` as a Knowledge Graph backend for LangChain.\n\n**What works right now:**\n- `graph.query()` — arbitrary Cypher pass-through\n- `graph.schema` / `refresh_schema()` — live graph schema\n- `add_graph_documents()` — add Nodes + Relationships from a LangChain `GraphDocument`\n- `GraphCypherQAChain` — LLM generates Cypher from a natural-language question *(requires `OPENAI_API_KEY`)*\n\n**Environments:**\n- **Google Colab** — uses `coordinode-embedded` (in-process Rust engine, no server needed). Installs pre-built wheel from PyPI (~30 sec).\n- **Local / Docker Compose** — connects to a running CoordiNode server via gRPC." ] }, { @@ -41,95 +27,28 @@ "\n", "IN_COLAB = \"google.colab\" in sys.modules\n", "\n", - "# Install coordinode-embedded in Colab only (requires Rust build).\n", + "pkgs = [\n", + " \"coordinode\",\n", + " \"langchain-coordinode\",\n", + " \"langchain\",\n", + " \"langchain-community\",\n", + " \"langchain-openai\",\n", + " \"nest_asyncio\",\n", + "]\n", "if IN_COLAB and not os.environ.get(\"COORDINODE_ADDR\"):\n", - " # Install Rust toolchain via rustup (https://rustup.rs).\n", - " # Colab's apt packages ship rustc \u22641.75, which cannot build coordinode-embedded\n", - " # (requires Rust \u22651.80 for maturin/pyo3). apt-get is not a viable alternative here.\n", - " # Download the installer to a temp file and execute it explicitly \u2014 this avoids\n", - " # piping remote content directly into a shell while maintaining HTTPS/TLS security\n", - " # through Python's default ssl context (cert-verified, TLS 1.2+).\n", - " # SHA256 pinning of rustup-init is intentionally omitted: rustup.rs does not\n", - " # publish a stable per-release checksum for sh.rustup.rs itself (only for\n", - " # platform-specific rustup-init binaries), and pinning a hash here would break\n", - " # silently on every rustup release. The HTTPS/TLS verification + temp-file\n", - " # execution (not piped to shell) is the rustup team's recommended trust model.\n", - " # No additional env-var gate (e.g. COORDINODE_ENABLE_RUSTUP) is needed:\n", - " # the `IN_COLAB` check above already ensures this block never runs outside\n", - " # Colab sessions, so there is no risk of unintentional execution in local\n", - " # or server environments.\n", - " # Security note: downloading rustup-init via HTTPS with cert verification and\n", - " # executing from a temp file (not piped to shell) is by design \u2014 this is the\n", - " # rustup project's own recommended install method for automated environments.\n", - " # protoc is required by coordinode-raft build (prost-build). Skip if already present (faster reruns),\n", - " # otherwise refresh apt indexes first \u2014 Colab caches can go stale on long-lived runtimes.\n", - " if subprocess.run([\"which\", \"protoc\"], capture_output=True).returncode != 0:\n", - " subprocess.run([\"apt-get\", \"update\", \"-y\", \"-q\"], check=True, timeout=120)\n", - " subprocess.run([\"apt-get\", \"install\", \"-y\", \"-q\", \"protobuf-compiler\"], check=True, timeout=120)\n", - " import ssl as _ssl, tempfile as _tmp, urllib.request as _ur\n", + " pkgs = [\"coordinode-embedded\"] + pkgs\n", "\n", - " _ctx = _ssl.create_default_context()\n", - " with _tmp.NamedTemporaryFile(mode=\"wb\", suffix=\".sh\", delete=False) as _f:\n", - " with _ur.urlopen(\"https://sh.rustup.rs\", context=_ctx, timeout=30) as _r:\n", - " _f.write(_r.read())\n", - " _rustup_path = _f.name\n", - " try:\n", - " subprocess.run([\"/bin/sh\", _rustup_path, \"-y\", \"-q\"], check=True, timeout=300)\n", - " finally:\n", - " os.unlink(_rustup_path)\n", - " # Add cargo to PATH so maturin/pip can find it.\n", - " _cargo_bin = os.path.expanduser(\"~/.cargo/bin\")\n", - " os.environ[\"PATH\"] = f\"{_cargo_bin}{os.pathsep}{os.environ.get('PATH', '')}\"\n", - " subprocess.run([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"maturin\"], check=True, timeout=300)\n", - " subprocess.run(\n", - " [\n", - " sys.executable,\n", - " \"-m\",\n", - " \"pip\",\n", - " \"install\",\n", - " \"-q\",\n", - " \"git+https://github.com/structured-world/coordinode-python.git@50ddc08a89a21fca73be007cb22b57a0054225c3#subdirectory=coordinode-embedded\",\n", - " ],\n", - " check=True,\n", - " timeout=600,\n", - " )\n", - "\n", - "# coordinode-embedded and langchain-coordinode are pinned to a specific git commit:\n", - "# - coordinode-embedded requires a Rust build (maturin/pyo3); the embedded engine\n", - "# must match the Python SDK version.\n", - "# - langchain-coordinode is pinned to the same commit so CoordinodeGraph(client=...)\n", - "# is available; this parameter is not yet released to PyPI.\n", - "# coordinode (SDK) is git-pinned in Colab via _coordinode_spec to keep the\n", - "# SDK/proto in sync with this PR; outside Colab it resolves from PyPI.\n", - "# Other packages (LangChain / llama-index / nest_asyncio) are unpinned \u2014 pure\n", - "# Python, release frequently, pip resolves a compatible version.\n", - "_coordinode_spec = (\n", - " \"git+https://github.com/structured-world/coordinode-python.git@50ddc08a89a21fca73be007cb22b57a0054225c3#subdirectory=coordinode\"\n", - " if IN_COLAB\n", - " else \"coordinode\"\n", - ")\n", "subprocess.run(\n", - " [\n", - " sys.executable,\n", - " \"-m\",\n", - " \"pip\",\n", - " \"install\",\n", - " \"-q\",\n", - " _coordinode_spec,\n", - " \"langchain\",\n", - " \"git+https://github.com/structured-world/coordinode-python.git@50ddc08a89a21fca73be007cb22b57a0054225c3#subdirectory=langchain-coordinode\",\n", - " \"langchain-community\",\n", - " \"langchain-openai\",\n", - " \"nest_asyncio\",\n", - " ],\n", + " [sys.executable, \"-m\", \"pip\", \"install\", \"-q\"] + pkgs,\n", " check=True,\n", - " timeout=300,\n", + " timeout=120,\n", ")\n", "\n", "import nest_asyncio\n", + "\n", "nest_asyncio.apply()\n", "\n", - "print(\"SDK installed\")" + "print(\"SDK installed\")\n" ] }, { @@ -171,7 +90,7 @@ " lines.append(f\" - {r['t']}\")\n", " return \"\\n\".join(lines)\n", "\n", - " # Vector search not available in embedded mode \u2014 requires running CoordiNode server.\n", + " # Vector search not available in embedded mode — requires running CoordiNode server.\n", "\n", " def close(self):\n", " self._lc.close()\n" @@ -215,7 +134,7 @@ " print(f\"Connected to {COORDINODE_ADDR}\")\n", " client = _cc\n", "else:\n", - " # No explicit server \u2014 use the embedded in-process engine backed by a file\n", + " # No explicit server — use the embedded in-process engine backed by a file\n", " # so the graph persists across cell reruns and between sibling demo\n", " # notebooks within the same runtime.\n", " try:\n", @@ -232,7 +151,7 @@ "\n", " _lc = LocalClient(COORDINODE_EMBEDDED_PATH)\n", " # Wrap in _EmbeddedAdapter so CoordinodeGraph/PropertyGraphStore can call\n", - " # get_schema_text() \u2014 LocalClient has .cypher() only.\n", + " # get_schema_text() — LocalClient has .cypher() only.\n", " client = _EmbeddedAdapter(_lc)\n", " print(f\"Using embedded LocalClient at {COORDINODE_EMBEDDED_PATH}\")\n" ] @@ -304,7 +223,7 @@ "id": "11", "metadata": {}, "source": [ - "## 2. query \u2014 direct Cypher" + "## 2. query — direct Cypher" ] }, { @@ -320,7 +239,7 @@ " \" RETURN s.name AS scientist, type(r) AS relation, f.name AS field\",\n", " params={\"prefix\": f\"Turing-{tag}\"},\n", ")\n", - "print(\"Scientists \u2192 Fields:\")\n", + "print(\"Scientists → Fields:\")\n", "for r in rows:\n", " print(f\" {r['scientist']} --[{r['relation']}]--> {r['field']}\")" ] @@ -330,7 +249,7 @@ "id": "13", "metadata": {}, "source": [ - "## 3. refresh_schema \u2014 structured_schema dict" + "## 3. refresh_schema — structured_schema dict" ] }, { @@ -352,7 +271,7 @@ "source": [ "## 4. Idempotency check\n", "\n", - "`add_graph_documents` uses MERGE internally \u2014 adding the same document twice must not\n", + "`add_graph_documents` uses MERGE internally — adding the same document twice must not\n", "create duplicate edges." ] }, @@ -363,7 +282,7 @@ "metadata": {}, "outputs": [], "source": [ - "graph.add_graph_documents([doc]) # second upsert \u2014 must not create a duplicate edge\n", + "graph.add_graph_documents([doc]) # second upsert — must not create a duplicate edge\n", "cnt = graph.query(\n", " \"MATCH (a {name: $src})-[r:FOUNDED]->(b {name: $dst}) RETURN count(r) AS cnt\",\n", " params={\"src\": f\"Turing-{tag}\", \"dst\": f\"Cryptography-{tag}\"},\n", @@ -376,7 +295,7 @@ "id": "17", "metadata": {}, "source": [ - "## 5. GraphCypherQAChain \u2014 LLM-powered Cypher (optional)\n", + "## 5. GraphCypherQAChain — LLM-powered Cypher (optional)\n", "\n", "> **This section requires `OPENAI_API_KEY`.** Set it in your environment or via\n", "> `os.environ['OPENAI_API_KEY'] = 'sk-...'` before running.\n", @@ -430,7 +349,7 @@ "graph.query(\"MATCH (n) WHERE n.name ENDS WITH $tag DETACH DELETE n\", params={\"tag\": tag})\n", "print(\"Cleaned up\")\n", "graph.close()\n", - "client.close() # injected client \u2014 owned by caller" + "client.close() # injected client — owned by caller" ] } ], @@ -446,4 +365,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/demo/notebooks/03_langgraph_agent.ipynb b/demo/notebooks/03_langgraph_agent.ipynb index 6d70466..345df82 100644 --- a/demo/notebooks/03_langgraph_agent.ipynb +++ b/demo/notebooks/03_langgraph_agent.ipynb @@ -5,22 +5,7 @@ "id": "0", "metadata": {}, "source": [ - "# LangGraph + CoordiNode: Agent with graph memory\n", - "\n", - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/structured-world/coordinode-python/blob/main/demo/notebooks/03_langgraph_agent.ipynb)\n", - "\n", - "Demonstrates a LangGraph agent that uses CoordiNode as persistent **graph memory**:\n", - "- `save_fact` \u2014 store a subject \u2192 relation \u2192 object triple in the graph\n", - "- `query_facts` \u2014 run an arbitrary Cypher query against the graph\n", - "- `find_related` \u2014 traverse the graph from a given entity\n", - "- `list_all_facts` \u2014 dump every fact in the current session\n", - "\n", - "**Works without OpenAI** \u2014 the mock demo section calls tools directly. \n", - "Set `OPENAI_API_KEY` to run the full `gpt-4o-mini` ReAct agent.\n", - "\n", - "**Environments:**\n", - "- **Google Colab** \u2014 uses `coordinode-embedded` (in-process Rust engine, no server needed). First run compiles from source (~5 min); subsequent runs use the pip cache.\n", - "- **Local / Docker Compose** \u2014 set `COORDINODE_ADDR=host:port` to connect to a running CoordiNode server via gRPC; otherwise uses embedded with a file-backed `COORDINODE_EMBEDDED_PATH`." + "# LangGraph + CoordiNode: Agent with graph memory\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/structured-world/coordinode-python/blob/main/demo/notebooks/03_langgraph_agent.ipynb)\n\nDemonstrates a LangGraph agent that uses CoordiNode as persistent **graph memory**:\n- `save_fact` — store a subject → relation → object triple in the graph\n- `query_facts` — run an arbitrary Cypher query against the graph\n- `find_related` — traverse the graph from a given entity\n- `list_all_facts` — dump every fact in the current session\n\n**Works without OpenAI** — the mock demo section calls tools directly. \nSet `OPENAI_API_KEY` to run the full `gpt-4o-mini` ReAct agent.\n\n**Environments:**\n- **Google Colab** — uses `coordinode-embedded` (in-process Rust engine, no server needed). Installs pre-built wheel from PyPI (~30 sec).\n- **Local / Docker Compose** — set `COORDINODE_ADDR=host:port` to connect to a running CoordiNode server via gRPC; otherwise uses embedded with a file-backed `COORDINODE_EMBEDDED_PATH`." ] }, { @@ -41,89 +26,29 @@ "import os, sys, subprocess\n", "\n", "IN_COLAB = \"google.colab\" in sys.modules\n", - "_EMBEDDED_PIP_SPEC = (\n", - " \"git+https://github.com/structured-world/coordinode-python.git\"\n", - " \"@50ddc08a89a21fca73be007cb22b57a0054225c3#subdirectory=coordinode-embedded\"\n", - ")\n", - "_SDK_PIP_SPEC = (\n", - " \"git+https://github.com/structured-world/coordinode-python.git\"\n", - " \"@50ddc08a89a21fca73be007cb22b57a0054225c3#subdirectory=coordinode\"\n", - ")\n", "\n", - "# Install coordinode-embedded in Colab only (requires Rust build).\n", + "pkgs = [\n", + " \"coordinode\",\n", + " \"langchain-coordinode\",\n", + " \"langchain-community\",\n", + " \"langchain-openai\",\n", + " \"langgraph\",\n", + " \"nest_asyncio\",\n", + "]\n", "if IN_COLAB and not os.environ.get(\"COORDINODE_ADDR\"):\n", - " # Install Rust toolchain via rustup (https://rustup.rs).\n", - " # Colab's apt packages ship rustc \u22641.75, which cannot build coordinode-embedded\n", - " # (requires Rust \u22651.80 for maturin/pyo3). apt-get is not a viable alternative here.\n", - " # Download the installer to a temp file and execute it explicitly \u2014 this avoids\n", - " # piping remote content directly into a shell while maintaining HTTPS/TLS security\n", - " # through Python's default ssl context (cert-verified, TLS 1.2+).\n", - " # SHA256 pinning of rustup-init is intentionally omitted: rustup.rs does not\n", - " # publish a stable per-release checksum for sh.rustup.rs itself (only for\n", - " # platform-specific rustup-init binaries), and pinning a hash here would break\n", - " # silently on every rustup release. The HTTPS/TLS verification + temp-file\n", - " # execution (not piped to shell) is the rustup team's recommended trust model.\n", - " # Skip embedded build if COORDINODE_ADDR is set \u2014 user has a gRPC server,\n", - " # no need to spend 5+ minutes building coordinode-embedded from source.\n", - " # The `IN_COLAB` check already guards against local/server environments.\n", - " # Security note: downloading rustup-init via HTTPS with cert verification and\n", - " # executing from a temp file (not piped to shell) is by design \u2014 this is the\n", - " # rustup project's own recommended install method for automated environments.\n", - " # protoc is required by coordinode-raft build (prost-build). Skip if already present (faster reruns),\n", - " # otherwise refresh apt indexes first \u2014 Colab caches can go stale on long-lived runtimes.\n", - " if subprocess.run([\"which\", \"protoc\"], capture_output=True).returncode != 0:\n", - " subprocess.run([\"apt-get\", \"update\", \"-y\", \"-q\"], check=True, timeout=120)\n", - " subprocess.run([\"apt-get\", \"install\", \"-y\", \"-q\", \"protobuf-compiler\"], check=True, timeout=120)\n", - " import ssl as _ssl, tempfile as _tmp, urllib.request as _ur\n", - "\n", - " _ctx = _ssl.create_default_context()\n", - " with _tmp.NamedTemporaryFile(mode=\"wb\", suffix=\".sh\", delete=False) as _f:\n", - " with _ur.urlopen(\"https://sh.rustup.rs\", context=_ctx, timeout=30) as _r:\n", - " _f.write(_r.read())\n", - " _rustup_path = _f.name\n", - " try:\n", - " subprocess.run([\"/bin/sh\", _rustup_path, \"-y\", \"-q\"], check=True, timeout=300)\n", - " finally:\n", - " os.unlink(_rustup_path)\n", - " # Add cargo to PATH so maturin/pip can find it.\n", - " _cargo_bin = os.path.expanduser(\"~/.cargo/bin\")\n", - " os.environ[\"PATH\"] = f\"{_cargo_bin}{os.pathsep}{os.environ.get('PATH', '')}\"\n", - " subprocess.run([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"maturin\"], check=True, timeout=300)\n", - " subprocess.run(\n", - " [\n", - " sys.executable,\n", - " \"-m\",\n", - " \"pip\",\n", - " \"install\",\n", - " \"-q\",\n", - " _EMBEDDED_PIP_SPEC,\n", - " ],\n", - " check=True,\n", - " timeout=600,\n", - " )\n", + " pkgs = [\"coordinode-embedded\"] + pkgs\n", "\n", - "_coordinode_spec = _SDK_PIP_SPEC if IN_COLAB else \"coordinode\"\n", "subprocess.run(\n", - " [\n", - " sys.executable,\n", - " \"-m\",\n", - " \"pip\",\n", - " \"install\",\n", - " \"-q\",\n", - " _coordinode_spec,\n", - " \"langchain-community\",\n", - " \"langchain-openai\",\n", - " \"langgraph\",\n", - " \"nest_asyncio\",\n", - " ],\n", + " [sys.executable, \"-m\", \"pip\", \"install\", \"-q\"] + pkgs,\n", " check=True,\n", - " timeout=300,\n", + " timeout=120,\n", ")\n", "\n", "import nest_asyncio\n", + "\n", "nest_asyncio.apply()\n", "\n", - "print(\"SDK installed\")" + "print(\"SDK installed\")\n" ] }, { @@ -166,7 +91,7 @@ " raise RuntimeError(f\"Health check failed for {COORDINODE_ADDR}\")\n", " print(f\"Connected to {COORDINODE_ADDR}\")\n", "else:\n", - " # No explicit server \u2014 use the embedded in-process engine backed by a file\n", + " # No explicit server — use the embedded in-process engine backed by a file\n", " # so the graph persists across cell reruns and between sibling demo\n", " # notebooks within the same runtime.\n", " try:\n", @@ -231,7 +156,7 @@ "\n", "@tool\n", "def save_fact(subject: str, relation: str, obj: str) -> str:\n", - " \"\"\"Save a fact (subject \u2192 relation \u2192 object) into the knowledge graph.\n", + " \"\"\"Save a fact (subject → relation → object) into the knowledge graph.\n", " Example: save_fact('Alice', 'WORKS_AT', 'Acme Corp')\"\"\"\n", " rel_type = relation.upper().replace(\" \", \"_\")\n", " # Validate rel_type before interpolating into Cypher to prevent injection.\n", @@ -264,7 +189,7 @@ " _LIMIT_AT_END_RE = re.compile(r\"\\bLIMIT\\s+(\\d+)\\s*;?\\s*$\", re.IGNORECASE | re.DOTALL)\n", " def _cap_limit(m):\n", " return f\"LIMIT {min(int(m.group(1)), 20)}\"\n", - " # Reject queries with parameters other than $sess \u2014 only {\"sess\": SESSION} is passed.\n", + " # Reject queries with parameters other than $sess — only {\"sess\": SESSION} is passed.\n", " extra_params = sorted(\n", " {m.group(1) for m in re.finditer(r\"\\$([A-Za-z_][A-Za-z0-9_]*)\", q)} - {\"sess\"}\n", " )\n", @@ -288,7 +213,7 @@ " safe_depth = max(1, min(int(depth), 3))\n", " # Note: session constraint is on both endpoints (n, m). Constraining\n", " # intermediate nodes via path variables (MATCH p=..., WHERE ALL(x IN nodes(p)...))\n", - " # is not yet supported by CoordiNode \u2014 planned for a future release.\n", + " # is not yet supported by CoordiNode — planned for a future release.\n", " # In practice, session isolation holds because all nodes are MERGE'd with\n", " # their session scope, so cross-session paths cannot form.\n", " rows = client.cypher(\n", @@ -324,7 +249,7 @@ "id": "7", "metadata": {}, "source": [ - "## 2. Mock demo \u2014 no LLM required (direct tool calls)\n", + "## 2. Mock demo — no LLM required (direct tool calls)\n", "\n", "Shows the full graph memory workflow by calling the tools directly." ] @@ -370,7 +295,7 @@ "id": "9", "metadata": {}, "source": [ - "## 3. LangGraph StateGraph \u2014 manual workflow\n", + "## 3. LangGraph StateGraph — manual workflow\n", "\n", "Shows how to wire CoordiNode tool calls into a LangGraph state machine without an LLM." ] @@ -429,7 +354,7 @@ "id": "11", "metadata": {}, "source": [ - "## 4. LangGraph ReAct agent (optional \u2014 requires OPENAI_API_KEY)\n", + "## 4. LangGraph ReAct agent (optional — requires OPENAI_API_KEY)\n", "\n", "> Set `OPENAI_API_KEY` in your environment or via\n", "> `os.environ['OPENAI_API_KEY'] = 'sk-...'` before running.\n", @@ -444,7 +369,7 @@ "outputs": [], "source": [ "if not os.environ.get(\"OPENAI_API_KEY\"):\n", - " print(\"OPENAI_API_KEY not set \u2014 skipping LLM agent. See section 2 for the mock demo.\")\n", + " print(\"OPENAI_API_KEY not set — skipping LLM agent. See section 2 for the mock demo.\")\n", "else:\n", " from langchain_openai import ChatOpenAI\n", " from langgraph.prebuilt import create_react_agent\n", @@ -500,4 +425,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file