Getting Started

Welcome to BrainyFlow! This framework helps you build powerful, modular AI applications using a simple yet expressive abstraction based on nested directed graphs.

1. Installation

First, ensure you have BrainyFlow installed:

pip install brainyflow

For more installation options, see the Installation Guide.

2. Core Concepts

BrainyFlow is built around a minimalist yet powerful abstraction that separates data flow from computation:

  • Node: The fundamental building block that performs a single task with a clear lifecycle (prepexecpost).

  • Flow: Orchestrates nodes in a directed graph, supporting branching, looping, and nesting.

  • Memory: Manages state, separating it into a shared global store and a forkable local store for isolated data flow between nodes.

3. Your First Flow

Let's build a simple Question-Answering flow to demonstrate BrainyFlow's core concepts:

Step 1: Design Your Flow

Our flow will have two nodes:

  1. GetQuestionNode: Captures the user's question

  2. AnswerNode: Generates an answer using an LLM

Step 2: Implement the Nodes

import asyncio
from brainyflow import Node, Flow, Memory
from utils import call_llm  # Your LLM implementation

class GetQuestionNode(Node):
    async def prep(self, memory):
        """Get text input from user."""
        memory.question = input("Enter your question: ")

class AnswerNode(Node):
    async def prep(self, memory):
        """Extract the question from memory."""
        return memory.question

    async def exec(self, question: str | None):
        """Call LLM to generate an answer."""

        prompt = f"Answer the following question: {question}"
        return await call_llm(prompt)

    async def post(self, memory, prep_res: str | None, exec_res: str):
        """Store the answer in memory."""
        memory.answer = exec_res
        print(f"AnswerNode: Stored answer '{exec_res}'")

Review: What was achieved here?

  • GetQuestionNode gets the user's question and writes it to the memory object (global store), then explicitly triggers the default next node.

  • AnswerNode reads the question from the memory object, calls an LLM utility, writes the answer back to the memory object, and triggers the next step (or ends the flow).

Step 3: Connect the Nodes into a Flow

from .nodes import GetQuestionNode, AnswerNode # defined in the previous step
from brainyflow import Flow

def create_qa_flow():
    get_question_node = GetQuestionNode()
    answer_node = AnswerNode()

    # Connect nodes get_question_node → answer_node using the default action
    get_question_node >> answer_node  # >> is Pythonic syntax sugar for .next(node)

    # Create the Flow, specifying the starting node
    return Flow(start=get_question_node)

Review: What was achieved here?

  • We instantiated the nodes and connected them using the default action (>> in Python, .next() in TypeScript).

  • We created a Flow instance, telling it to start execution with getQuestionNode.

Step 4: Run the Flow

import asyncio
from .flow import create_qa_flow # defined in the previous step

async def main():
    memory = {} # Initialize empty memory (which acts as the global store)
    qa_flow = create_qa_flow()

    print("Running QA Flow...")
    # Run the flow, passing the initial global store.
    # The flow modifies the memory object in place.
    # The run method returns the final execution tree (we ignore it here).
    await qa_flow.run(memory)

    # Access the results stored in the global store
    print("\n--- Flow Complete ---")
    print(f"Question: {memory.question}")
    print(f"Answer: {memory.answer}")

if __name__ == '__main__':
    asyncio.run(main())

Review: What was achieved here?

  • We initialized an empty memory object (Python dictionary or TS object) to serve as the global store.

  • qaFlow.run(memory) executed the flow. The Memory instance managed the state internally, reading from and writing to the memory object we passed in.

  • The final question and answer are directly accessible in the original memory object after the flow completes.

4. Key Design Principles

BrainyFlow follows these core design principles:

  1. Separation of Concerns: Data storage (the memory object managing global/local stores) is separate from computation logic (Node classes).

  2. Explicit Data Flow: Data dependencies between steps are clear and traceable through memory access in prep/post and the results passed between prepexecpost.

  3. Composability: Complex systems (Flows) are built from simple, reusable components (Nodes), and Flows themselves can be nested within other Flows.

  4. Minimalism: The framework provides only essential abstractions (Node, Flow, Memory), avoiding vendor-specific implementations or excessive boilerplate.

5. Next Steps

Now that you understand the basics, explore these resources to build sophisticated applications:

If you prefer, jump straight into our example projects:

Last updated