Skip to content

Commit d10844d

Browse files
committed
feat: Add flow_id, self.name for instance names of Flows and Nodes and easy isolated storage for nested flows
This change introduces several improvements to BaseNode and Flow classes: 1. Added instance name tracking with get_instance_name() 2. Simplified UUID format to 8-character hex without hyphens 3. Added flow hierarchy tracking with flow.name and flow.id 4. Improved flow name lookup with explicit naming support These changes enable: - Better debugging with named instances and flows - Simplified flow identification with shorter UUIDs - Flow hierarchy awareness for nested flows - Consistent naming across nodes and flows The improvements include: - Automatic instance name lookup walking up the call stack - Simplified 8-character flow IDs for easier reference - Flow name tracking with explicit naming support - Parent flow tracking for nested flows This is particularly useful for: - Debugging complex flows with named components - Tracking flow execution in logs - Visualizing flow hierarchies - Maintaining backward compatibility feat: Add flow_storage attribute to BaseNode feat: Add instance name tracking to BaseNode for better debugging fix: add base case to prevent infinite recursion in `_propagate_flow_id` fix: prevent infinite recursion in flow propagation with visited set fix: improve instance name lookup by walking up call stack feat: Add rework flow with file processing and LLM integration refactor: enhance debug output in GetOpinion.prep with class name feat: Add name parameter to GetOpinion node initialization feat: Add debug print statement to BaseNode key discovery refactor: improve instance name lookup and GetOpinion initialization feat: Add debug print statement for opinion2_Node name feat: Add flow hierarchy tracking to BaseNode feat: add flow name and id tracking with alias support refactor: Simplify flow UUIDs and improve naming consistency draft: Just a example to check if it works, it needs some cleaning! docs: Add pocketflow.txt documentation feat: Add instance tracking, simplified UUIDs, and flow hierarchy in BaseNode
1 parent b7444fa commit d10844d

File tree

4 files changed

+339
-26
lines changed

4 files changed

+339
-26
lines changed

pocketflow.txt

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
This change introduces several improvements to BaseNode and Flow classes:
2+
3+
1. Added instance name tracking with get_instance_name()
4+
2. Simplified UUID format to 8-character hex without hyphens
5+
3. Added flow hierarchy tracking with flow.name and flow.id
6+
4. Improved flow name lookup with explicit naming support
7+
8+
These changes enable:
9+
- Better debugging with named instances and flows
10+
- Simplified flow identification with shorter UUIDs
11+
- Flow hierarchy awareness for nested flows
12+
- Consistent naming across nodes and flows
13+
14+
The improvements include:
15+
- Automatic instance name lookup walking up the call stack
16+
- Simplified 8-character flow IDs for easier reference
17+
- Flow name tracking with explicit naming support
18+
- Parent flow tracking for nested flows
19+
20+
This is particularly useful for:
21+
- Debugging complex flows with named components
22+
- Tracking flow execution in logs
23+
- Visualizing flow hierarchies
24+
- Maintaining backward compatibility

pocketflow/__init__.py

+128-26
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,35 @@
1-
import asyncio, warnings, copy, time
1+
import asyncio, warnings, copy, time, uuid, sys
22

33
class BaseNode:
4-
def __init__(self): self.params,self.successors={},{}
4+
def __init__(self):
5+
self.params, self.successors = {}, {}
6+
self.flow_id = None
7+
self.flow_storage = {}
8+
self.name = self.get_instance_name() or str(id(self))
9+
self.flow = None # Will be set by Flow._propagate_flow_id
10+
self.parent = None # Will be set by Flow._propagate_flow_id
11+
12+
def get_instance_name(self):
13+
"""Find the variable name this instance is assigned to, if any"""
14+
# First check caller's locals
15+
try:
16+
frame = sys._getframe(1)
17+
while frame:
18+
# Check both locals and globals of each frame
19+
for scope in (frame.f_locals, frame.f_globals):
20+
for key, value in scope.items():
21+
if value is self:
22+
# Skip special names and self references
23+
if not key.startswith('_') and key != 'self':
24+
return key
25+
# Move up the call stack
26+
frame = frame.f_back
27+
except (AttributeError, ValueError):
28+
pass
29+
return None
30+
def _get_id(self):
31+
"""Get unique identifier for this node within its flow"""
32+
return f"{self.flow_id}.{id(self)}" if self.flow_id else str(id(self))
533
def set_params(self,params): self.params=params
634
def add_successor(self,node,action="default"):
735
if action in self.successors: warnings.warn(f"Overwriting successor for action '{action}'")
@@ -11,8 +39,8 @@ def exec(self,prep_res): pass
1139
def post(self,shared,prep_res,exec_res): pass
1240
def _exec(self,prep_res): return self.exec(prep_res)
1341
def _run(self,shared): p=self.prep(shared);e=self._exec(p);return self.post(shared,p,e)
14-
def run(self,shared):
15-
if self.successors: warnings.warn("Node won't run successors. Use Flow.")
42+
def run(self,shared):
43+
if self.successors: warnings.warn("Node won't run successors. Use Flow.")
1644
return self._run(shared)
1745
def __rshift__(self,other): return self.add_successor(other)
1846
def __sub__(self,action):
@@ -37,22 +65,68 @@ class BatchNode(Node):
3765
def _exec(self,items): return [super(BatchNode,self)._exec(i) for i in items]
3866

3967
class Flow(BaseNode):
40-
def __init__(self,start): super().__init__();self.start=start
68+
def __init__(self, start):
69+
super().__init__()
70+
self.start = start
71+
# Generate simpler UUID without hyphens
72+
self.flow_id = uuid.uuid4().hex[:8]
73+
self.id = self.flow_id # Alias for flow_id
74+
# Force name lookup even if flow is created inline
75+
self.name = self.get_instance_name() or f"flow_{self.flow_id}"
76+
self._propagate_flow_id(self.start)
77+
78+
def _get_id(self):
79+
"""Get unique identifier for this flow"""
80+
return self.flow_id
81+
82+
def _propagate_flow_id(self, node, visited=None):
83+
"""Set flow_id and flow hierarchy on all nodes in the flow"""
84+
if visited is None:
85+
visited = set()
86+
87+
if node is None or id(node) in visited: # Base cases
88+
return
89+
90+
visited.add(id(node))
91+
node.flow_id = self.flow_id
92+
node.flow = self
93+
node.parent = self.parent if hasattr(self, 'parent') else None
94+
95+
for successor in node.successors.values():
96+
self._propagate_flow_id(successor, visited)
4197
def get_next_node(self,curr,action):
4298
nxt=curr.successors.get(action or "default")
4399
if not nxt and curr.successors: warnings.warn(f"Flow ends: '{action}' not found in {list(curr.successors)}")
44100
return nxt
45-
def _orch(self,shared,params=None):
46-
curr,p=copy.copy(self.start),(params or {**self.params})
47-
while curr: curr.set_params(p);c=curr._run(shared);curr=copy.copy(self.get_next_node(curr,c))
101+
def _orch(self, shared, params=None):
102+
# Initialize flow storage if needed
103+
if 'storage' not in shared:
104+
shared['storage'] = {}
105+
if self.flow_id not in shared['storage']:
106+
shared['storage'][self.flow_id] = {}
107+
108+
flow_storage = shared['storage'][self.flow_id]
109+
curr, p = copy.copy(self.start), (params or {**self.params})
110+
while curr:
111+
curr.set_params(p)
112+
c = curr._run(flow_storage)
113+
curr = copy.copy(self.get_next_node(curr, c))
48114
def _run(self,shared): pr=self.prep(shared);self._orch(shared);return self.post(shared,pr,None)
49115
def exec(self,prep_res): raise RuntimeError("Flow can't exec.")
50116

51117
class BatchFlow(Flow):
52-
def _run(self,shared):
53-
pr=self.prep(shared) or []
54-
for bp in pr: self._orch(shared,{**self.params,**bp})
55-
return self.post(shared,pr,None)
118+
def _run(self, shared):
119+
# Initialize flow storage if needed
120+
if 'storage' not in shared:
121+
shared['storage'] = {}
122+
if self.flow_id not in shared['storage']:
123+
shared['storage'][self.flow_id] = {}
124+
125+
flow_storage = shared['storage'][self.flow_id]
126+
pr = self.prep(flow_storage) or []
127+
for bp in pr:
128+
self._orch(flow_storage, {**self.params, **bp})
129+
return self.post(flow_storage, pr, None)
56130

57131
class AsyncNode(Node):
58132
def prep(self,shared): raise RuntimeError("Use prep_async.")
@@ -64,14 +138,14 @@ async def prep_async(self,shared): pass
64138
async def exec_async(self,prep_res): pass
65139
async def exec_fallback_async(self,prep_res,exc): raise exc
66140
async def post_async(self,shared,prep_res,exec_res): pass
67-
async def _exec(self,prep_res):
141+
async def _exec(self,prep_res):
68142
for i in range(self.max_retries):
69143
try: return await self.exec_async(prep_res)
70144
except Exception as e:
71145
if i==self.max_retries-1: return await self.exec_fallback_async(prep_res,e)
72146
if self.wait>0: await asyncio.sleep(self.wait)
73-
async def run_async(self,shared):
74-
if self.successors: warnings.warn("Node won't run successors. Use AsyncFlow.")
147+
async def run_async(self,shared):
148+
if self.successors: warnings.warn("Node won't run successors. Use AsyncFlow.")
75149
return await self._run_async(shared)
76150
async def _run_async(self,shared): p=await self.prep_async(shared);e=await self._exec(p);return await self.post_async(shared,p,e)
77151

@@ -82,19 +156,47 @@ class AsyncParallelBatchNode(AsyncNode,BatchNode):
82156
async def _exec(self,items): return await asyncio.gather(*(super(AsyncParallelBatchNode,self)._exec(i) for i in items))
83157

84158
class AsyncFlow(Flow,AsyncNode):
85-
async def _orch_async(self,shared,params=None):
86-
curr,p=copy.copy(self.start),(params or {**self.params})
87-
while curr:curr.set_params(p);c=await curr._run_async(shared) if isinstance(curr,AsyncNode) else curr._run(shared);curr=copy.copy(self.get_next_node(curr,c))
159+
async def _orch_async(self, shared, params=None):
160+
# Initialize flow storage if needed
161+
if 'storage' not in shared:
162+
shared['storage'] = {}
163+
if self.flow_id not in shared['storage']:
164+
shared['storage'][self.flow_id] = {}
165+
166+
flow_storage = shared['storage'][self.flow_id]
167+
curr, p = copy.copy(self.start), (params or {**self.params})
168+
while curr:
169+
curr.set_params(p)
170+
if isinstance(curr, AsyncNode):
171+
c = await curr._run_async(flow_storage)
172+
else:
173+
c = curr._run(flow_storage)
174+
curr = copy.copy(self.get_next_node(curr, c))
88175
async def _run_async(self,shared): p=await self.prep_async(shared);await self._orch_async(shared);return await self.post_async(shared,p,None)
89176

90177
class AsyncBatchFlow(AsyncFlow,BatchFlow):
91-
async def _run_async(self,shared):
92-
pr=await self.prep_async(shared) or []
93-
for bp in pr: await self._orch_async(shared,{**self.params,**bp})
94-
return await self.post_async(shared,pr,None)
178+
async def _run_async(self, shared):
179+
# Initialize flow storage if needed
180+
if 'storage' not in shared:
181+
shared['storage'] = {}
182+
if self.flow_id not in shared['storage']:
183+
shared['storage'][self.flow_id] = {}
184+
185+
flow_storage = shared['storage'][self.flow_id]
186+
pr = await self.prep_async(flow_storage) or []
187+
for bp in pr:
188+
await self._orch_async(flow_storage, {**self.params, **bp})
189+
return await self.post_async(flow_storage, pr, None)
95190

96191
class AsyncParallelBatchFlow(AsyncFlow,BatchFlow):
97-
async def _run_async(self,shared):
98-
pr=await self.prep_async(shared) or []
99-
await asyncio.gather(*(self._orch_async(shared,{**self.params,**bp}) for bp in pr))
100-
return await self.post_async(shared,pr,None)
192+
async def _run_async(self, shared):
193+
# Initialize flow storage if needed
194+
if 'storage' not in shared:
195+
shared['storage'] = {}
196+
if self.flow_id not in shared['storage']:
197+
shared['storage'][self.flow_id] = {}
198+
199+
flow_storage = shared['storage'][self.flow_id]
200+
pr = await self.prep_async(flow_storage) or []
201+
await asyncio.gather(*(self._orch_async(flow_storage, {**self.params, **bp}) for bp in pr))
202+
return await self.post_async(flow_storage, pr, None)

pocketflow/example.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fasdasdasd

0 commit comments

Comments
 (0)