diff --git a/.cursor/rules/core_abstraction/node.mdc b/.cursor/rules/core_abstraction/node.mdc index 235915c..5aa8c9f 100644 --- a/.cursor/rules/core_abstraction/node.mdc +++ b/.cursor/rules/core_abstraction/node.mdc @@ -19,6 +19,7 @@ A **Node** is the smallest building block. Each Node has 3 steps `prep->exec->po - Examples: *(mostly) LLM calls, remote APIs, tool use*. - ⚠️ This shall be only for compute and **NOT** access `shared`. - ⚠️ If retries enabled, ensure idempotent implementation. + - ⚠️ Defer exception handling to the Node's built-in retry mechanism. - Return `exec_res`, which is passed to `post()`. 3. `post(shared, prep_res, exec_res)` diff --git a/.cursor/rules/guide_for_pocketflow.mdc b/.cursor/rules/guide_for_pocketflow.mdc index 809e1d7..e91137a 100644 --- a/.cursor/rules/guide_for_pocketflow.mdc +++ b/.cursor/rules/guide_for_pocketflow.mdc @@ -27,10 +27,11 @@ Agentic Coding should be a collaboration between Human System Design and Agent I | 1. Requirements | ★★★ High | ★☆☆ Low | Humans understand the requirements and context. | | 2. Flow | ★★☆ Medium | ★★☆ Medium | Humans specify the high-level design, and the AI fills in the details. | | 3. Utilities | ★★☆ Medium | ★★☆ Medium | Humans provide available external APIs and integrations, and the AI helps with implementation. | -| 4. Node | ★☆☆ Low | ★★★ High | The AI helps design the node types and data handling based on the flow. | -| 5. Implementation | ★☆☆ Low | ★★★ High | The AI implements the flow based on the design. | -| 6. Optimization | ★★☆ Medium | ★★☆ Medium | Humans evaluate the results, and the AI helps optimize. | -| 7. Reliability | ★☆☆ Low | ★★★ High | The AI writes test cases and addresses corner cases. | +| 4. Data | ★☆☆ Low | ★★★ High | AI designs the data schema, and humans verify. | +| 5. Node | ★☆☆ Low | ★★★ High | The AI helps design the node based on the flow. | +| 6. Implementation | ★☆☆ Low | ★★★ High | The AI implements the flow based on the design. | +| 7. Optimization | ★★☆ Medium | ★★☆ Medium | Humans evaluate the results, and the AI helps optimize. | +| 8. Reliability | ★☆☆ Low | ★★★ High | The AI writes test cases and addresses corner cases. | 1. **Requirements**: Clarify the requirements for your project, and evaluate whether an AI system is a good fit. - Understand AI systems' strengths and limitations: @@ -97,9 +98,11 @@ Agentic Coding should be a collaboration between Human System Design and Agent I ``` - > **Sometimes, design Utilities before Flow:** For example, for an LLM project to automate a legacy system, the bottleneck will likely be the available interface to that system. Start by designing the hardest utilities for interfacing, and then build the flow around them. {: .best-practice } + - > **Avoid Exception Handling in Utilities**: If a utility function is called from a Node's `exec()` method, avoid using `try...except` blocks within the utility. Let the Node's built-in retry mechanism handle failures. + {: .warning } -4. **Node Design**: Plan how each node will read and write data, and use utility functions. - - One core design principle for PocketFlow is to use a [shared store], so start with a shared store design: +4. **Data Design**: Design the shared store that nodes will use to communicate. + - One core design principle for PocketFlow is to use a well-designed [shared store]—a data contract that all nodes agree upon to retrieve and store data. - For simple systems, use an in-memory dictionary. - For more complex systems or when persistence is required, use a database. - **Don't Repeat Yourself**: Use in-memory references or foreign keys. @@ -116,16 +119,18 @@ Agentic Coding should be a collaboration between Human System Design and Agent I "results": {} # Empty dict to store outputs } ``` + +5. **Node Design**: Plan how each node will read and write data, and use utility functions. - For each [Node], describe its type, how it reads and writes data, and which utility function it uses. Keep it specific but high-level without codes. For example: - `type`: Regular (or Batch, or Async) - `prep`: Read "text" from the shared store - - `exec`: Call the embedding utility function + - `exec`: Call the embedding utility function. **Avoid exception handling here**; let the Node's retry mechanism manage failures. - `post`: Write "embedding" to the shared store -5. **Implementation**: Implement the initial nodes and flows based on the design. +6. **Implementation**: Implement the initial nodes and flows based on the design. - 🎉 If you've reached this step, humans have finished the design. Now *Agentic Coding* begins! - **"Keep it simple, stupid!"** Avoid complex features and full-scale type checking. - - **FAIL FAST**! Avoid `try` logic so you can quickly identify any weak points in the system. + - **FAIL FAST**! Leverage the built-in [Node] retry and fallback mechanisms to handle failures gracefully. This helps you quickly identify weak points in the system. - Add logging throughout the code to facilitate debugging. 7. **Optimization**: diff --git a/.cursor/rules/utility_function/llm.mdc b/.cursor/rules/utility_function/llm.mdc index 5a1ee20..4de420c 100644 --- a/.cursor/rules/utility_function/llm.mdc +++ b/.cursor/rules/utility_function/llm.mdc @@ -43,13 +43,13 @@ Here, we provide some minimal example implementations: 3. Google (Generative AI Studio / PaLM API) ```python def call_llm(prompt): - import google.generativeai as genai - genai.configure(api_key="YOUR_API_KEY_HERE") - r = genai.generate_text( - model="models/text-bison-001", - prompt=prompt - ) - return r.result + from google import genai + client = genai.Client(api_key='GEMINI_API_KEY') + response = client.models.generate_content( + model='gemini-2.0-flash-001', + contents=prompt + ) + return response.text ``` 4. Azure (Azure OpenAI) diff --git a/.cursor/rules/utility_function/viz.mdc b/.cursor/rules/utility_function/viz.mdc index 560099b..4c99907 100644 --- a/.cursor/rules/utility_function/viz.mdc +++ b/.cursor/rules/utility_function/viz.mdc @@ -1,9 +1,4 @@ --- -description: -globs: -alwaysApply: false ---- ---- description: Guidelines for using PocketFlow, Utility Function, Viz and Debug globs: alwaysApply: false @@ -92,146 +87,9 @@ graph LR end ``` -## 2. Interactive D3.js Visualization +For visualization based on d3.js, check out [the cookbook](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-visualization). -For more complex flows, a static diagram may not be sufficient. We provide a D3.js-based interactive visualization that allows for dragging nodes, showing group boundaries for flows, and connecting flows at their boundaries. - -### Converting Flow to JSON - -First, we convert the PocketFlow graph to JSON format suitable for D3.js: - -```python -def flow_to_json(start): - """Convert a flow to JSON format suitable for D3.js visualization. - - This function walks through the flow graph and builds a structure with: - - nodes: All non-Flow nodes with their group memberships - - links: Connections between nodes within the same group - - group_links: Connections between different groups (for inter-flow connections) - - flows: Flow information for group labeling - """ - nodes = [] - links = [] - group_links = [] # For connections between groups (Flow to Flow) - ids = {} - node_types = {} - flow_nodes = {} # Keep track of flow nodes - ctr = 1 - - # Implementation details... - - # Post-processing: Generate group links based on node connections between different groups - node_groups = {n["id"]: n["group"] for n in nodes} - filtered_links = [] - - # Filter out direct node-to-node connections between different groups - for link in links: - source_id = link["source"] - target_id = link["target"] - source_group = node_groups.get(source_id, 0) - target_group = node_groups.get(target_id, 0) - - if source_group != target_group and source_group > 0 and target_group > 0: - # Create group-to-group links instead of node-to-node links across groups - if not any(gl["source"] == source_group and gl["target"] == target_group - for gl in group_links): - group_links.append({ - "source": source_group, - "target": target_group, - "action": link["action"] - }) - # Skip adding this link to filtered_links - we don't want direct node connections across groups - else: - # Keep links within the same group - filtered_links.append(link) - - return { - "nodes": nodes, - "links": filtered_links, - "group_links": group_links, - "flows": {str(k): v.__class__.__name__ for k, v in flow_nodes.items()}, - } -``` - -### Creating the Visualization - -Then, we generate an HTML file with D3.js visualization: - -```python -def create_d3_visualization(json_data, output_dir="./viz", filename="flow_viz"): - """Create a D3.js visualization from JSON data.""" - # Create output directory - os.makedirs(output_dir, exist_ok=True) - - # Save JSON data to file - json_path = os.path.join(output_dir, f"{filename}.json") - with open(json_path, "w") as f: - json.dump(json_data, f, indent=2) - - # Generate HTML with D3.js visualization - # ...HTML template with D3.js code... - - # Key features implemented in the visualization: - # 1. Nodes can be dragged to reorganize the layout - # 2. Flows are shown as dashed rectangles (groups) - # 3. Inter-group connections shown as dashed lines connecting at group boundaries - # 4. Edge labels show transition actions - - # Write HTML to file - html_path = os.path.join(output_dir, f"{filename}.html") - with open(html_path, "w") as f: - f.write(html_content) - - print(f"Visualization created at {html_path}") - return html_path -``` - -### Convenience Function - -A convenience function to visualize flows: - -```python -def visualize_flow(flow, flow_name): - """Helper function to visualize a flow with both mermaid and D3.js""" - print(f"\n--- {flow_name} Mermaid Diagram ---") - print(build_mermaid(start=flow)) - - print(f"\n--- {flow_name} D3.js Visualization ---") - json_data = flow_to_json(flow) - create_d3_visualization( - json_data, filename=f"{flow_name.lower().replace(' ', '_')}" - ) -``` - -### Usage Example - -```python -from visualize import visualize_flow - -# Create a complex flow with nested subflows -# ...flow definition... - -# Generate visualization -visualize_flow(data_science_flow, "Data Science Flow") -``` - -### Customizing the Visualization - -You can customize the visualization by adjusting the force simulation parameters: - -```javascript -const simulation = d3.forceSimulation(data.nodes) - // Controls the distance between connected nodes - .force("link", d3.forceLink(data.links).id(d => d.id).distance(100)) - // Controls how nodes repel each other - lower values bring nodes closer - .force("charge", d3.forceManyBody().strength(-30)) - // Centers the entire graph in the SVG - .force("center", d3.forceCenter(width / 2, height / 2)) - // Prevents nodes from overlapping - acts like a minimum distance - .force("collide", d3.forceCollide().radius(50)); -``` - -## 3. Call Stack Debugging +## 2. Call Stack Debugging It would be useful to print the Node call stacks for debugging. This can be achieved by inspecting the runtime call stack: @@ -279,4 +137,6 @@ data_science_flow = DataScienceFlow(start=data_prep_node) data_science_flow.run({}) ``` -The output would be: `Call stack: ['EvaluateModelNode', 'ModelFlow', 'DataScienceFlow']` \ No newline at end of file +The output would be: `Call stack: ['EvaluateModelNode', 'ModelFlow', 'DataScienceFlow']` + +For a more complete implementation, check out [the cookbook](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-tracing). \ No newline at end of file