5.5 KiB
| layout | title | parent | nav_order |
|---|---|---|---|
| default | Communication | Core Abstraction | 3 |
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.