Spaces:
Running
Running
| title: ChemGraph Loop | |
| emoji: π§ͺ | |
| colorFrom: green | |
| colorTo: yellow | |
| sdk: docker | |
| app_port: 7860 | |
| pinned: false | |
| license: apache-2.0 | |
| short_description: Real agentic chemistry workflow β resolve, simulate, verify | |
| # ChemGraph Loop | |
| A guarded, **natural-language API around the real ChemGraph agent** β Argonne | |
| Leadership Computing Facility's agentic computational-chemistry framework | |
| (LangGraph + ASE), running the workflow-correctness hardening by **Lu Zhang**. | |
| Backs the live demo at **sciencesloop.com/agent** (frontend = | |
| `lu-zhang-site/src/components/ChemGraphDemo.astro`). | |
| - **GitHub (source):** `github.com/rockyzl/chemgraph-loop` (private) | |
| - **Deploy target:** HF Space `rockyaaos/chemgraph-loop` (Docker, free CPU-basic) | |
| - **Upstream ChemGraph:** https://github.com/argonne-lcf/ChemGraph (Apache-2.0) | |
| ## How it works (request lifecycle) | |
| 1. **LLM intent router** (`llm_parse_query`, PRIMARY) β one `gpt-4o-mini` JSON | |
| call reads the plain-language question and returns `{molecule, task, | |
| calculator}`, resolving the molecule by ANY name/synonym/formula | |
| ("dihydrogen monoxide"/"H2O"/"NH3"/"CO2"/"EtOH"/"benzol" β canonical). It | |
| refuses to match derivatives to a parent (dimethoxybenzene β benzene) and | |
| emits `nearest`/`note` for out-of-scope molecules. `keyword_fallback` | |
| (token match, no regex) runs ONLY if the model call fails. This is | |
| intentionally agentic, not a rule engine. | |
| 2. **Route by physics cost:** | |
| - `energy` / `dipole` = single-point (~12β25 s) β run the **real ChemGraph | |
| agent LIVE** on a canonical, guard-matched query built from the intent. | |
| - `ir` / `thermo` = finite-difference Hessian (too slow for free CPU) β | |
| served from **precomputed real agent runs** in `precomputed/` | |
| (`cached:true`, shown as "precomputed real run"). | |
| 3. **Extract** (`cg_extract.extract`) β unified rich payload: molecule/formula/ | |
| smiles, `structure.atoms` (parsed from molecule.xyz, drives the 3D viewer), | |
| `steps[]` (tool calls + folded detail), task-specific `result` | |
| (energy / dipole vector+magnitude / ir.modes / thermo H,S,G), `verified`. | |
| 4. **Clarification node** β out-of-scope molecule or an unavailable heavy task | |
| returns `{clarify:true, message, suggestions:[{label,query}]}` (HTTP 200), | |
| NOT an error. The frontend renders it as a conversational bubble + chips. | |
| ## Endpoints | |
| ``` | |
| GET / β health: molecules, tasks, live_tasks, ir_molecules, thermo_molecules | |
| POST /run { "query": "IR spectrum of water" } # natural-language (primary) | |
| POST /run { "molecule": "water", "calculator": "emt" } # legacy, energy only | |
| ``` | |
| `/run` returns one of: a rich result payload Β· a `clarify` payload Β· an error | |
| (`429` rate limit Β· `504` timeout Β· `500` agent error). | |
| ## Coverage & reliability (IMPORTANT) | |
| - **energy / dipole:** LIVE for all 10 allow-listed molecules (water, methane, | |
| ammonia, methanol, ethanol, carbon dioxide, benzene, acetic acid, | |
| formaldehyde, hydrogen peroxide). | |
| - **thermochemistry:** precomputed for **all 10** (thermo uses plain | |
| `Vibrations`; works for large molecules). | |
| - **IR / vibrations:** precomputed for **β€4-atom molecules only** β water, | |
| ammonia, carbon dioxide, formaldehyde, hydrogen peroxide. The ASE `Infrared` | |
| intensity step returns **empty modes for β₯5-atom molecules** (methane, | |
| methanol, ethanol, acetic acid, benzene all fail β confirmed). Do NOT add | |
| β₯5-atom IR; the clarification node covers them. | |
| - Guard-matched queries only (single-point / dipole / ir / thermo). "geometry | |
| optimization" phrasing makes the agent loop β never expose it. | |
| - Display-layer IR filter: keep modes with `cm1 β₯ 100` & `intensity > 1e-3` | |
| (drops spurious near-zero / translation-rotation modes). | |
| ## Files | |
| | File | Role | | |
| |---|---| | |
| | `app.py` | FastAPI: intent router, routing, clarification node, guards (CORS, rate limit, timeout) | | |
| | `cg_extract.py` | shared payload extractor (structure/steps/results) β used by app + precompute | | |
| | `precompute.py` | offline heavy-task generator: `python precompute.py <molecule...>` | | |
| | `precomputed/<slug>__<task>.json` | bundled real agent runs for ir/thermo | | |
| | `Dockerfile` | CPU torch β `pip install .[calculators]` β copies wrappers LAST (fast layer-cached rebuilds) | | |
| | `src/` | vendored copy of Lu's hardened ChemGraph fork | | |
| ## Deploy / operate | |
| The HF Space builds from its own git; push files with `huggingface_hub` | |
| (`~/.local` env at `/tmp/hfenv/bin/python` on Lu's box) β `upload_file` / | |
| `upload_folder` / `delete_file` each trigger a rebuild (~2β4 min, layer-cached | |
| since wrappers copy late). `OPENAI_API_KEY` is a Space **secret**. | |
| ```python | |
| from huggingface_hub import HfApi | |
| HfApi().upload_file(path_or_fileobj="app.py", path_in_repo="app.py", | |
| repo_id="rockyaaos/chemgraph-loop", repo_type="space", | |
| commit_message="...") | |
| ``` | |
| **Add a spectrum/thermo molecule:** run `precompute.py <name>`, validate the IR | |
| modes are non-empty & physical (`cm1β₯100`), `upload_file` the JSON, let the Space | |
| rebuild (its `_avail(task)` picks it up automatically). Only β€4-atom molecules | |
| get valid IR. | |
| CORS allows `sciencesloop.com`, `*.vercel.app`, and `localhost:4321`. | |