Skip to main content

Write Python. Get a production LLM graph. Declarative LLM graph compiler that compiles @node-decorated functions into LangGraph.

Project description

NeoGraph

Write Python. Get a production graph.

pip install neograph

Define your LLM pipeline as Python functions. The framework infers the topology, validates types at assembly time, and compiles to LangGraph with checkpointing, observability, and tool orchestration. No DSL. No YAML. No add_node / add_edge.

A function is a node. A parameter name is an edge. An if is a branch.


Functions are nodes

from neograph import node, construct_from_module, compile, run

@node(output=Claims, prompt='rw/decompose', model='reason')
def decompose(topic: RawText) -> Claims: ...

@node(output=Classified, prompt='rw/classify', model='fast')
def classify(decompose: Claims) -> Classified: ...

@node(output=Report)
def report(classify: Classified) -> Report:
    return Report(summary=f"{len(classify.items)} claims processed")

pipeline = construct_from_module(sys.modules[__name__])
graph = compile(pipeline)
result = run(graph, input={'node_id': 'doc-001'})

classify(decompose: Claims) — the parameter name IS the dependency. Rename a function, downstream breaks at import time. Fan-in is just more parameters: def report(claims, scores, verified).

Mode is inferred. prompt= + model= means LLM call. Neither means the function body runs.

if is a branch

from neograph import ForwardConstruct, Node, compile

class Analysis(ForwardConstruct):
    check   = Node(output=CheckResult, prompt='check', model='fast')
    deep    = Node(output=Result, prompt='deep-analysis', model='reason')
    shallow = Node(output=Result, prompt='quick-scan', model='fast')

    def forward(self, topic):
        checked = self.check(topic)
        if checked.confidence > 0.8:
            return self.shallow(checked)
        else:
            return self.deep(checked)

graph = compile(Analysis())

The if compiles to a conditional edge. for compiles to fan-out. Python is the graph language. Your type checker sees everything. Your debugger works.

Everything else is a keyword

# Fan-out over a collection
@node(output=MatchResult, map_over='clusters.groups', map_key='label')
def verify(cluster: ClusterGroup) -> MatchResult: ...

# N-way ensemble with merge
@node(output=Claims, prompt='decompose', model='reason',
      ensemble_n=3, merge_fn='merge_claims')
def decompose() -> Claims: ...

# Human-in-the-loop interrupt
@node(output=ValidationResult,
      interrupt_when=lambda s: {'issues': s.validate.issues} if not s.validate.passed else None)
def validate(claims: Claims) -> ValidationResult: ...

# Non-node parameters: runtime input, config, constants
@node(output=Report)
def summarize(
    claims: Claims,                        # upstream node
    topic: FromInput[str],                 # from run(input={...})
    rate_limiter: FromConfig[RateLimiter], # from config
    max_items: int = 10,                   # constant
) -> Report: ...

Catches mistakes before you run

ConstructError: Node 'verify' declares input=ClusterGroup but no upstream
  produces a compatible value.
  upstream producers:
    • node 'cluster': Clusters
  hint: did you forget to fan out? try .map(lambda s: s.cluster.groups, key='...')
  at my_pipeline.py:42

Types are validated at assembly time — when you define the pipeline, not when you execute it.

Scales to real systems

Organize by module. Each pipeline is a Python module. Import nodes across modules. construct_from_module finds them all.

Isolate with sub-constructs. Typed I/O boundaries for sub-pipelines: Construct("enrich", input=Claims, output=ScoredClaims, nodes=[...]).

Observe everything. Structured logs on every node. Pass trace providers and shared resources via FromConfig[T].

Test at every level. node.run_isolated() for unit tests. compile() + run() for integration. forward() direct-call for debugging.

LLMs can build the graph too

For runtime construction — an LLM emitting a pipeline via tool calls, a config system defining workflows — use the programmatic API with the | pipe syntax:

from neograph import Node, Construct, Oracle, Each, compile, run

decompose = Node("decompose", mode="produce", output=Claims,
                 prompt="rw/decompose", model="reason") | Oracle(n=3, merge_fn="merge")
verify = Node("verify", mode="gather", output=MatchResult,
              prompt="verify", model="fast") | Each(over="decompose.items", key="label")

pipeline = Construct("dynamic", nodes=[decompose, verify])
graph = compile(pipeline)

Three surfaces — @node, ForwardConstruct, Node | Modifier — one compiler.

Examples

See examples/ for runnable pipelines and examples/vs_langgraph/ for side-by-side comparisons.

License

Code: MIT

Documentation content © 2025-2026 Constantine Mirin, mirin.pro. Licensed under CC BY-ND 4.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

neograph-0.1.0.tar.gz (45.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

neograph-0.1.0-py3-none-any.whl (51.6 kB view details)

Uploaded Python 3

File details

Details for the file neograph-0.1.0.tar.gz.

File metadata

  • Download URL: neograph-0.1.0.tar.gz
  • Upload date:
  • Size: 45.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for neograph-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5704e840bea68342fbf2b7ecfef9da568937233a1d3bfaec4c6a4111f53ef817
MD5 2c5652ff407eb420fe4be62b493e1a80
BLAKE2b-256 1d223abfb720ba469ccec923c498a71442cf1d72f858315d3374c5a5625a7dbe

See more details on using hashes here.

Provenance

The following attestation bundles were made for neograph-0.1.0.tar.gz:

Publisher: publish.yml on KonstantinMirin/neograph

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file neograph-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: neograph-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 51.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for neograph-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c09b9bcd65674f0793ff266311264e2fe69e562dea04da3e288c4fba11699313
MD5 eb6152ef44ae278f2774623ffe4a99ff
BLAKE2b-256 845b29893c55303f87fb60cf14cc85ff8368a0b769e804460792bc2bd5818214

See more details on using hashes here.

Provenance

The following attestation bundles were made for neograph-0.1.0-py3-none-any.whl:

Publisher: publish.yml on KonstantinMirin/neograph

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page