5.5 KiB
| layout | title | nav_order |
|---|---|---|
| default | Communication | 4 |
Communication
In Mini LLM Flow, Nodes and Flows communicate with each other in two ways:
- Shared Store – A global data structure (often a Python dict) that every Node can read from and write to.
- Params – Small pieces of metadata or configuration, set on each Node or Flow, typically used to identify items or tweak behavior.
This design avoids complex message-passing or data routing. It also lets you nest Flows easily without having to manage multiple channels.
1. Shared Store
Overview
A shared store is typically a Python dictionary, like:
shared = {"data": {}, "summary": {}, "config": { ... }, ...}
Every Node’s prep(), exec(), and post() methods receive the same shared object. This makes it easy to:
- Read data that another Node loaded, such as a text file or database record.
- Write results for later Nodes to consume.
- Maintain consistent state across the entire Flow.
Example
`` class LoadData(Node): def prep(self, shared): # Suppose we read from disk or an API shared["data"]["my_file.txt"] = "Some text content" return None
def exec(self, shared, prep_res):
# Not doing anything special here
return None
def post(self, shared, prep_res, exec_res):
return "default"
class Summarize(Node): def prep(self, shared): # We can read what LoadData wrote content = shared["data"].get("my_file.txt", "") return content
def exec(self, shared, prep_res):
prompt = f"Summarize: {prep_res}"
summary = call_llm(prompt)
return summary
def post(self, shared, prep_res, exec_res):
shared["summary"]["my_file.txt"] = exec_res
return "default"
``
Here,
LoadDatawrites toshared["data"].Summarizereads from the same location.
No special data-passing code—just the samesharedobject.
Why Not Message Passing?
Message-passing can be great for simple DAGs, but with nested graphs (Flows containing Flows, repeated or cyclic calls), routing messages can become complicated. A shared store keeps the design simpler and easier to debug.
2. Params
Params let you store per-Node or per-Flow configuration that does not need to be in the global store. They are:
- Immutable during a Node’s run cycle (i.e., don’t change mid-run).
- Set via
set_params(). - Cleared or updated each time you call the Flow or Node again.
Common examples:
- File names to process.
- Model hyperparameters for an LLM call.
- API credentials or specialized flags.
Example
``
1) Create a Node that uses params
class SummarizeFile(Node): def prep(self, shared): # Access the node's param filename = self.params["filename"] return shared["data"].get(filename, "")
def exec(self, shared, prep_res):
prompt = f"Summarize: {prep_res}"
return call_llm(prompt)
def post(self, shared, prep_res, exec_res):
filename = self.params["filename"]
shared["summary"][filename] = exec_res
return "default"
2) Set params
node = SummarizeFile() node.set_params({"filename": "doc1.txt"})
3) Run
node.run(shared) ``
Because params are only for that Node, you don’t pollute the global shared with fields that might only matter to one operation.
3. Shared Store vs. Params
-
Shared Store:
- Public, global.
- Great for data results, large content, or anything multiple nodes need.
- Must be carefully structured (like designing a mini schema).
-
Params:
- Local, ephemeral config for a single node or flow execution.
- Perfect for small values such as filenames or numeric IDs.
- Does not persist across different nodes unless specifically copied into
shared.
4. Best Practices
-
Design a Clear
sharedSchema- Decide on keys upfront. Example:
shared["data"]for raw data,shared["summary"]for results, etc.
- Decide on keys upfront. Example:
-
Use Params for Identifiers / Config
- If you need to pass a single ID or filename to a Node, params are usually best.
-
Don’t Overuse the Shared Store
- Keep it tidy. If a piece of data only matters to one Node, consider using
paramsor discarding it after usage.
- Keep it tidy. If a piece of data only matters to one Node, consider using
-
Ensure
sharedIs Accessible- If you switch from an in-memory dict to a database or file-based approach, the Node code can remain the same as long as your
sharedinterface is consistent.
- If you switch from an in-memory dict to a database or file-based approach, the Node code can remain the same as long as your
Putting It All Together
``
Suppose you have a flow:
load_data >> summarize_file my_flow = Flow(start=load_data)
Example usage:
load_data.set_params({"path": "path/to/data/folder"}) # local param for load_data summarize_file.set_params({"filename": "my_text.txt"}) # local param for summarize_file
shared store
shared = { "data": {}, "summary": {} }
my_flow.run(shared)
After run, shared["summary"]["my_text.txt"] might have the LLM summary
``
load_datauses its param ("path") to load some data intoshared["data"].summarize_fileuses its param ("filename") to pick which file fromshared["data"]to summarize.- They share results via
shared["summary"].
That’s the Mini LLM Flow approach to communication:
- A single shared store to handle large data or results for multiple Nodes.
- Per-node params for minimal configuration and identification.
Use these patterns to build powerful, modular LLM pipelines with minimal overhead.