Skip to content

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hypergraph

Composable AI workflows that don't suck.

Hypergraph is an ergonomic workflow engine for building AI agents and data pipelines. It provides simple, composable primitives that make complex orchestration feel natural.

Why Hypergraph?

LangGraph:

// 150+ lines of boilerplate
const StateAnnotation = Annotation.Root({...});
async function nodeA(state) {...}
async function nodeB(state) {...}
// ... more ceremony ...
const graph = new StateGraph(StateAnnotation)
  .addNode("a", nodeA)
  .addNode("b", nodeB)
  .addEdge(START, "a")
  .addEdge("a", "b")
  .compile();

Hypergraph:

// 30 lines, crystal clear
const workflow = graph("my-workflow")
  .add(
    node("a", processA),
    node("b", processB)
  )
  .connect("a", "b")
  .setEntry("a")
  .setExit("b");

The difference? Hypergraph optimizes for readability and developer experience without sacrificing power.


Installation

npm install hypergraph
# or
bun add hypergraph

Quick Start

import { graph, node } from "hypergraph";

const square = (x: number) => x ** 2;
const addTen = (x: number) => x + 10;

const workflow = graph<number>("math")
  .add(
    node("square", square),
    node("addTen", addTen)
  )
  .connect("square", "addTen")
  .setEntry("square")
  .setExit("addTen");

const result = await workflow.run(5); // 35

Core Primitives

1. node() - Basic Operations

Transform data with simple functions:

import { graph, node } from "hypergraph";

type State = { value: number };

const workflow = graph<State>("example")
  .add(
    node("double", (s) => ({ ...s, value: s.value * 2 })),
    node("log", (s) => {
      console.log(s.value);
      return s;
    })
  )
  .connect("double", "log")
  .setEntry("double")
  .setExit("log");

await workflow.run({ value: 10 }); // Logs: 20

2. ai() - LLM Operations

Call language models without boilerplate:

import { ChatOpenAI } from "@langchain/openai";
import { graph, ai } from "hypergraph";

const model = new ChatOpenAI({ model: "gpt-4o" });

type State = {
  topic: string;
  joke?: string;
  tweet?: string;
};

const workflow = graph<State>("joke-writer")
  .add(
    ai("writeJoke", {
      model,
      prompt: (s) => `Write a joke about: ${s.topic}`,
      update: (s, joke) => ({ ...s, joke })
    }),
    ai("writeTweet", {
      model,
      prompt: (s) => `Turn this into a tweet: ${s.joke}`,
      update: (s, tweet) => ({ ...s, tweet })
    })
  )
  .connect("writeJoke", "writeTweet")
  .setEntry("writeJoke")
  .setExit("writeTweet");

const result = await workflow.run({ topic: "programming" });
console.log(result.tweet);

3. parallel() - Concurrent Execution

Run multiple operations simultaneously:

import { graph, ai, parallel } from "hypergraph";

type State = {
  review: string;
  sentiment?: object;
  keyIssues?: string[];
  competitors?: string[];
};

const workflow = graph<State>("review-analyzer")
  .add(
    parallel(
      "analysis",
      [
        ai("sentiment", {
          model,
          prompt: (s) => `Analyze sentiment: ${s.review}`,
          parse: "json",
          update: (s, sentiment) => ({ ...s, sentiment })
        }),
        ai("issues", {
          model,
          prompt: (s) => `Extract issues: ${s.review}`,
          parse: "json",
          update: (s, keyIssues) => ({ ...s, keyIssues })
        }),
        ai("competitors", {
          model,
          prompt: (s) => `Find competitors: ${s.review}`,
          parse: "json",
          update: (s, competitors) => ({ ...s, competitors })
        })
      ],
      // Reducer: merge all results
      (results) => results.reduce((acc, curr) => ({ ...acc, ...curr }), {})
    )
  )
  .setEntry("analysis")
  .setExit("analysis");

// Runs 3 AI calls in parallel, ~3x faster than sequential
const result = await workflow.run({ review: "..." });

Performance: Sequential would take ~6 seconds, parallel takes ~2 seconds.


4. chain() - Sequential Grouping

Group sequential operations into logical units:

import { graph, node, chain } from "hypergraph";

const workflow = graph("pipeline")
  .add(
    node("start", validateInput),
    chain("processing", [
      node("normalize", normalizeData),
      node("enrich", enrichWithAPI),
      node("transform", transformFormat)
    ]),
    node("save", saveToDatabase)
  )
  .connect("start", "processing")
  .connect("processing", "save")
  .setEntry("start")
  .setExit("save");

Why? Clean logical grouping without cluttering your graph with intermediate connections.


5. Conditional Routing

Dynamic branching based on state:

import { graph, node } from "hypergraph";

type State = { 
  action: string;
  result?: string;
};

const workflow = graph<State>("agent")
  .add(
    node("decide", (s) => ({ ...s, action: "search" })),
    node("search", (s) => ({ ...s, result: "search results" })),
    node("calculate", (s) => ({ ...s, result: "42" })),
    node("done", (s) => s)
  )
  .connect(
    "decide", 
    ["search", "calculate", "done"],
    (s) => s.action // Returns which node to route to
  )
  .connect("search", "done")
  .connect("calculate", "done")
  .setEntry("decide")
  .setExit("done");

await workflow.run({ action: "search" }); // Routes to search node

Real-World Example: Product Review Analysis

Process customer reviews with parallel AI analysis:

import { ChatOpenAI } from "@langchain/openai";
import { graph, ai, parallel } from "hypergraph";

const model = new ChatOpenAI({ model: "gpt-4o-mini" });

type ReviewState = {
  review: string;
  sentiment?: { score: number; label: string };
  issues?: string[];
  competitors?: string[];
  insights?: string[];
  summary?: string;
};

const analyzer = graph<ReviewState>("review-analyzer")
  .add(
    // Stage 1: Parallel analysis (4 AI calls simultaneously)
    parallel(
      "analyze",
      [
        ai("sentiment", {
          model,
          prompt: (s) => `Analyze sentiment of: ${s.review}. Return JSON.`,
          parse: "json",
          update: (s, sentiment) => ({ ...s, sentiment })
        }),
        ai("issues", {
          model,
          prompt: (s) => `Extract issues from: ${s.review}. Return JSON array.`,
          parse: "json",
          update: (s, issues) => ({ ...s, issues })
        }),
        ai("competitors", {
          model,
          prompt: (s) => `Find competitors in: ${s.review}. Return JSON array.`,
          parse: "json",
          update: (s, competitors) => ({ ...s, competitors })
        }),
        ai("insights", {
          model,
          prompt: (s) => `Generate insights for: ${s.review}. Return JSON array.`,
          parse: "json",
          update: (s, insights) => ({ ...s, insights })
        })
      ],
      (results) => results.reduce((acc, curr) => ({ ...acc, ...curr }), {})
    ),
    
    // Stage 2: Generate executive summary
    ai("summarize", {
      model,
      prompt: (s) => `
        Create executive summary:
        Sentiment: ${s.sentiment?.label}
        Issues: ${s.issues?.join(", ")}
        Competitors: ${s.competitors?.join(", ")}
        Insights: ${s.insights?.join("; ")}
      `,
      update: (s, summary) => ({ ...s, summary })
    })
  )
  .connect("analyze", "summarize")
  .setEntry("analyze")
  .setExit("summarize");

const result = await analyzer.run({
  review: "Great product but battery life is poor. Competitors do better..."
});

console.log(result.summary);

Output:

Sentiment: { score: 0.2, label: "neutral" }
Issues: ["Poor battery life", "Price too high"]
Competitors: ["Dell XPS", "MacBook Pro"]
Insights: ["Improve battery optimization", "Review pricing strategy"]
Summary: "Mixed review citing battery concerns. Competitors mentioned..."

Performance: 4 parallel AI calls + 1 sequential = ~3 seconds total (vs ~10 seconds sequential)


Composition: Mixing Primitives

The real power is combining primitives:

const workflow = graph("complex-pipeline")
  .add(
    node("validate", validateInput),
    
    parallel("processing", [
      chain("data-flow", [
        node("fetch", fetchData),
        node("transform", transformData)
      ]),
      chain("ai-flow", [
        ai("analyze", {...}),
        ai("summarize", {...})
      ])
    ]),
    
    node("merge", mergeResults),
    
    ai("final", {
      model,
      prompt: (s) => `Generate report: ${JSON.stringify(s)}`,
      update: (s, report) => ({ ...s, report })
    })
  )
  .connect("validate", "processing")
  .connect("processing", "merge")
  .connect("merge", "final")
  .setEntry("validate")
  .setExit("final");

Parallel chains running simultaneously, then merged and passed to a final AI step. This would be 100+ lines in LangGraph.


Comparison: Hypergraph vs LangGraph

Feature Hypergraph LangGraph
Lines of code ~30 lines ~150+ lines
Parallel execution parallel([...]) (explicit) Multiple addEdge(START, ...) (implicit)
AI abstraction ai() helper Manual model.invoke() every time
JSON parsing parse: "json" built-in Manual parsing with try/catch
Sequential grouping chain([...]) N/A (wire manually)
Conditional routing .connect(src, [dest], router) addConditionalEdges() with maps
Type safety TypeScript inference Zod schemas required
Readability Self-documenting Requires mental graph building

Design Philosophy

1. Simple things should be simple

Writing a basic workflow shouldn't require 50 lines of boilerplate.

2. Complex things should be possible

Parallel execution, conditional routing, and composition should feel natural.

3. Explicit over implicit

parallel() explicitly shows concurrent execution. No guessing from edge patterns.

4. Progressive complexity

Start simple with node(), add ai() when needed, compose with parallel() and chain() as complexity grows.

5. Developer experience matters

The API should feel like writing normal code, not fighting a framework.


Roadmap

  • Streaming support for AI nodes
  • Built-in observability/tracing
  • Retry logic and error handling
  • Persistent state management
  • Visual graph debugger
  • More LLM provider integrations

Contributing

Hypergraph is in early development. Contributions, issues, and feedback are welcome!


License

MIT


Credits

Built because LangGraph made simple things too complex. Inspired by great API design from libraries like Express, Lodash, and D3.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages