Skip to content
Built by Postindustria. We help teams build agentic production systems.

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.

Terminal window
neograph check <target> [--config JSON] [--setup MODULE] [--known-vars VARS]

Example:

Terminal window
neograph check my_pipeline.py

Output:

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:

Terminal window
neograph check my_pipeline.py
neograph check my_package.pipelines

lint() checks that FromInput and FromConfig DI bindings resolve against a config dict. Without a config, only structural checks run. Two flags supply one:

Terminal window
neograph check my_pipeline.py --config '{"node_id": "test", "project_root": "/tmp"}'

For configs that contain non-serializable objects (rate limiters, database pools), point --setup at a Python file. The module can export these functions:

ExportReturnsRequired?
get_check_config()dict — config values for DI lintYes (when --setup is used)
get_template_resolver()Callable[[str], str | None] — maps template names to template textOptional
get_known_template_vars()Iterable[str] — extra variable names accepted during placeholder validationOptional
get_scripted()dict[str, Callable] — name → scripted shim for Node.scripted(fn="...") referencesOptional
get_tool_factories()dict[str, ToolFactory] — name → factory for tools=[Tool("name")] referencesOptional
get_conditions()dict[str, ConditionFn] — name → condition for Loop(when="name") / Operator(when="name") referencesOptional
get_llm_factory()Callable[[str], BaseChatModel] | None — tier-to-model factory for LLM-mode nodesOptional
get_prompt_compiler()Callable[[str, Any], list] | None — template-name + input → message listOptional

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:

check_config.py
from pathlib import Path
from my_app.resources import make_limiter, make_search_tool
from 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]
Terminal window
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:

Terminal window
neograph check my_pipeline.py --known-vars topic,json_schema

These 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.

CodeMeaning
0All constructs compiled and linted cleanly
1One or more constructs failed compilation or lint
2Usage error (file not found, invalid JSON, missing get_check_config)

Add a check step to your CI pipeline. The non-zero exit code fails the build on any validation error:

.github/workflows/check.yml
name: Pipeline Validation
on: [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"}'

For each Construct found in the target module:

  1. compile() — builds the LangGraph StateGraph, catching type mismatches, missing upstreams, invalid modifiers, and cycle errors.

  2. 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_resolver is available via --setup).

    See Static Linting for details.

Both checks are assembly-time only — no LLM calls, no side effects.

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.