Pipeline Validation (neograph check)
neograph check validates your pipelines without executing them. It discovers every Construct in a Python file (or importable module), runs compile() and lint() on each one, and reports problems.
Basic usage
Section titled “Basic usage”neograph check <target> [--config JSON] [--setup MODULE] [--known-vars VARS]Example:
neograph check my_pipeline.pyOutput:
OK ingestion (pipeline)FAIL analysis (analysis_pipeline) compile: Node 'score' in construct 'analysis' declares input=RawText but no upstream produces a compatible value.
2 construct(s) checked, 1 passed, 1 failed.The command accepts a file path or a dotted module name:
neograph check my_pipeline.pyneograph check my_package.pipelinesProviding config for lint
Section titled “Providing config for lint”lint() checks that FromInput and FromConfig DI bindings resolve against a config dict. Without a config, only structural checks run. Two flags supply one:
—config (inline JSON)
Section titled “—config (inline JSON)”neograph check my_pipeline.py --config '{"node_id": "test", "project_root": "/tmp"}'—setup (Python module)
Section titled “—setup (Python module)”For configs that contain non-serializable objects (rate limiters, database pools), point --setup at a Python file. The module can export these functions:
| Export | Returns | Required? |
|---|---|---|
get_check_config() | dict — config values for DI lint | Yes (when --setup is used) |
get_template_resolver() | Callable[[str], str | None] — maps template names to template text | Optional |
get_known_template_vars() | Iterable[str] — extra variable names accepted during placeholder validation | Optional |
get_scripted() | dict[str, Callable] — name → scripted shim for Node.scripted(fn="...") references | Optional |
get_tool_factories() | dict[str, ToolFactory] — name → factory for tools=[Tool("name")] references | Optional |
get_conditions() | dict[str, ConditionFn] — name → condition for Loop(when="name") / Operator(when="name") references | Optional |
get_llm_factory() | Callable[[str], BaseChatModel] | None — tier-to-model factory for LLM-mode nodes | Optional |
get_prompt_compiler() | Callable[[str, Any], list] | None — template-name + input → message list | Optional |
get_template_resolver() enables template placeholder validation during lint. The resolver receives a template name and returns the template text (or None if unknown). When a resolver is available, lint checks that every {placeholder} in prompt templates maps to a known upstream field, DI binding, or declared known variable.
get_known_template_vars() declares variable names that are valid but not discoverable from the pipeline graph alone (e.g., variables injected at runtime by middleware). These are merged with any names passed via --known-vars.
get_scripted() / get_tool_factories() / get_conditions() provide the runtime registrations that compile() requires post-§2 (when string-keyed scripted nodes, tools, or conditions appear in the pipeline). get_llm_factory() and get_prompt_compiler() supply the LLM configuration that compile() and lint() need when any LLM-mode (think/agent/act) node is present.
Example setup module with the runtime hooks:
from pathlib import Pathfrom my_app.resources import make_limiter, make_search_toolfrom my_app.shims import seed_fn, score_fn
TEMPLATE_DIR = Path("prompts/")
def get_check_config(): return { "node_id": "test", "project_root": "/tmp", "rate_limiter": make_limiter(), }
def get_template_resolver(): def resolve(name: str) -> str | None: path = TEMPLATE_DIR / f"{name}.txt" return path.read_text() if path.exists() else None return resolve
def get_known_template_vars(): return ["topic", "json_schema"]
def get_scripted(): return {"seed": seed_fn, "score": score_fn}
def get_tool_factories(): return {"search": make_search_tool}
def get_conditions(): return {"is_approved": lambda d: bool(d.approved)}
def get_llm_factory(): from my_app.llm import build_chat_model return build_chat_model # signature: (tier: str) -> BaseChatModel
def get_prompt_compiler(): from my_app.prompts import compile_prompt return compile_prompt # signature: (template: str, data, **kw) -> list[dict]neograph check my_pipeline.py --setup check_config.py—known-vars (extra template variable names)
Section titled “—known-vars (extra template variable names)”Declares extra template variable names that should be accepted as valid during placeholder validation. Comma-separated, no spaces:
neograph check my_pipeline.py --known-vars topic,json_schemaThese are merged with any variables returned by get_known_template_vars() in the setup module. Use this flag when you need to whitelist a few names without creating a full setup module.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
| 0 | All constructs compiled and linted cleanly |
| 1 | One or more constructs failed compilation or lint |
| 2 | Usage error (file not found, invalid JSON, missing get_check_config) |
CI integration
Section titled “CI integration”Add a check step to your CI pipeline. The non-zero exit code fails the build on any validation error:
name: Pipeline Validationon: [push, pull_request]
jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v6 - run: uv sync - run: uv run neograph check src/my_pipeline.py --config '{"node_id": "test"}'What it runs
Section titled “What it runs”For each Construct found in the target module:
-
compile() — builds the LangGraph
StateGraph, catching type mismatches, missing upstreams, invalid modifiers, and cycle errors. -
lint() — walks every node and checks:
- DI parameters (
FromInput,FromConfig, bundled models) have matching keys in the provided config. - Template placeholders in prompt templates map to known upstream fields, DI bindings, or declared known variables (when a
template_resolveris available via--setup).
See Static Linting for details.
- DI parameters (
Both checks are assembly-time only — no LLM calls, no side effects.
Operator auto-checkpointer
Section titled “Operator auto-checkpointer”Constructs that contain Operator nodes require a checkpointer to compile (LangGraph needs one for the conversational message-list state that Operator uses). When neograph check detects Operator nodes in a construct, it automatically supplies a MemorySaver so compilation succeeds without requiring you to pass a checkpointer.
This is specific to the check command. When calling compile() directly in your own code, you must still pass an explicit checkpointer= for constructs that use Operator.
Documentation © 2025-2026 Constantine Mirin, mirin.pro. Licensed under CC BY-ND 4.0.