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 allowlist a few names without creating a full setup module.

Every lint issue carries a severity, and the severity decides the exit code:

  • ERROR blocks. The construct fails the check and the command exits 1.
  • WARN advises. The command prints the issue and leaves the exit code alone.

A compile failure always blocks, whatever the lint result. The reference table in Lint issue kinds gives the severity of each kind.

This split lets a new check land as a warning first. Consumers see the finding and their build stays green until the check is promoted to ERROR.

CodeMeaning
0Every construct compiled, and no construct reported an ERROR lint issue. WARN issues may still print.
1At least one construct failed to compile, or reported an ERROR lint issue
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 a compile failure or an ERROR lint issue:

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