Showing preview only (2,958K chars total). Download the full file or copy to clipboard to get everything.
Repository: The-Pocket/PocketFlow-Tutorial-Codebase-Knowledge
Branch: main
Commit: c8a8ca17180c
Files: 203
Total size: 2.8 MB
Directory structure:
gitextract_ufv5rlmg/
├── .clinerules
├── .cursorrules
├── .dockerignore
├── .gitignore
├── .windsurfrules
├── Dockerfile
├── LICENSE
├── README.md
├── docs/
│ ├── AutoGen Core/
│ │ ├── 01_agent.md
│ │ ├── 02_messaging_system__topic___subscription_.md
│ │ ├── 03_agentruntime.md
│ │ ├── 04_tool.md
│ │ ├── 05_chatcompletionclient.md
│ │ ├── 06_chatcompletioncontext.md
│ │ ├── 07_memory.md
│ │ ├── 08_component.md
│ │ └── index.md
│ ├── Browser Use/
│ │ ├── 01_agent.md
│ │ ├── 02_system_prompt.md
│ │ ├── 03_browsercontext.md
│ │ ├── 04_dom_representation.md
│ │ ├── 05_action_controller___registry.md
│ │ ├── 06_message_manager.md
│ │ ├── 07_data_structures__views_.md
│ │ ├── 08_telemetry_service.md
│ │ └── index.md
│ ├── Celery/
│ │ ├── 01_celery_app.md
│ │ ├── 02_configuration.md
│ │ ├── 03_task.md
│ │ ├── 04_broker_connection__amqp_.md
│ │ ├── 05_worker.md
│ │ ├── 06_result_backend.md
│ │ ├── 07_beat__scheduler_.md
│ │ ├── 08_canvas__signatures___primitives_.md
│ │ ├── 09_events.md
│ │ ├── 10_bootsteps.md
│ │ └── index.md
│ ├── Click/
│ │ ├── 01_command___group.md
│ │ ├── 02_decorators.md
│ │ ├── 03_parameter__option___argument_.md
│ │ ├── 04_paramtype.md
│ │ ├── 05_context.md
│ │ ├── 06_term_ui__terminal_user_interface_.md
│ │ ├── 07_click_exceptions.md
│ │ └── index.md
│ ├── Codex/
│ │ ├── 01_terminal_ui__ink_components_.md
│ │ ├── 02_input_handling__textbuffer_editor_.md
│ │ ├── 03_agent_loop.md
│ │ ├── 04_approval_policy___security.md
│ │ ├── 05_response___tool_call_handling.md
│ │ ├── 06_command_execution___sandboxing.md
│ │ ├── 07_configuration_management.md
│ │ ├── 08_single_pass_mode.md
│ │ └── index.md
│ ├── Crawl4AI/
│ │ ├── 01_asynccrawlerstrategy.md
│ │ ├── 02_asyncwebcrawler.md
│ │ ├── 03_crawlerrunconfig.md
│ │ ├── 04_contentscrapingstrategy.md
│ │ ├── 05_relevantcontentfilter.md
│ │ ├── 06_extractionstrategy.md
│ │ ├── 07_crawlresult.md
│ │ ├── 08_deepcrawlstrategy.md
│ │ ├── 09_cachecontext___cachemode.md
│ │ ├── 10_basedispatcher.md
│ │ └── index.md
│ ├── CrewAI/
│ │ ├── 01_crew.md
│ │ ├── 02_agent.md
│ │ ├── 03_task.md
│ │ ├── 04_tool.md
│ │ ├── 05_process.md
│ │ ├── 06_llm.md
│ │ ├── 07_memory.md
│ │ ├── 08_knowledge.md
│ │ └── index.md
│ ├── DSPy/
│ │ ├── 01_module___program.md
│ │ ├── 02_signature.md
│ │ ├── 03_example.md
│ │ ├── 04_predict.md
│ │ ├── 05_lm__language_model_client_.md
│ │ ├── 06_rm__retrieval_model_client_.md
│ │ ├── 07_evaluate.md
│ │ ├── 08_teleprompter___optimizer.md
│ │ ├── 09_adapter.md
│ │ ├── 10_settings.md
│ │ └── index.md
│ ├── FastAPI/
│ │ ├── 01_fastapi_application___routing.md
│ │ ├── 02_path_operations___parameter_declaration.md
│ │ ├── 03_data_validation___serialization__pydantic_.md
│ │ ├── 04_openapi___automatic_docs.md
│ │ ├── 05_dependency_injection.md
│ │ ├── 06_error_handling.md
│ │ ├── 07_security_utilities.md
│ │ ├── 08_background_tasks.md
│ │ └── index.md
│ ├── Flask/
│ │ ├── 01_application_object___flask__.md
│ │ ├── 02_routing_system.md
│ │ ├── 03_request_and_response_objects.md
│ │ ├── 04_templating__jinja2_integration_.md
│ │ ├── 05_context_globals___current_app____request____session____g__.md
│ │ ├── 06_configuration___config__.md
│ │ ├── 07_application_and_request_contexts.md
│ │ ├── 08_blueprints.md
│ │ └── index.md
│ ├── Google A2A/
│ │ ├── 01_agent_card.md
│ │ ├── 02_task.md
│ │ ├── 03_a2a_protocol___core_types.md
│ │ ├── 04_a2a_server_implementation.md
│ │ ├── 05_a2a_client_implementation.md
│ │ ├── 06_task_handling_logic__server_side_.md
│ │ ├── 07_streaming_communication__sse_.md
│ │ ├── 08_multi_agent_orchestration__host_agent_.md
│ │ ├── 09_demo_ui_application___service.md
│ │ └── index.md
│ ├── LangGraph/
│ │ ├── 01_graph___stategraph.md
│ │ ├── 02_nodes___pregelnode__.md
│ │ ├── 03_channels.md
│ │ ├── 04_control_flow_primitives___branch____send____interrupt__.md
│ │ ├── 05_pregel_execution_engine.md
│ │ ├── 06_checkpointer___basecheckpointsaver__.md
│ │ └── index.md
│ ├── LevelDB/
│ │ ├── 01_table___sstable___tablecache.md
│ │ ├── 02_memtable.md
│ │ ├── 03_write_ahead_log__wal____logwriter_logreader.md
│ │ ├── 04_dbimpl.md
│ │ ├── 05_writebatch.md
│ │ ├── 06_version___versionset.md
│ │ ├── 07_iterator.md
│ │ ├── 08_compaction.md
│ │ ├── 09_internalkey___dbformat.md
│ │ └── index.md
│ ├── MCP Python SDK/
│ │ ├── 01_cli___mcp__command_.md
│ │ ├── 02_fastmcp_server___fastmcp__.md
│ │ ├── 03_fastmcp_resources___resource____resourcemanager__.md
│ │ ├── 04_fastmcp_tools___tool____toolmanager__.md
│ │ ├── 05_fastmcp_prompts___prompt____promptmanager__.md
│ │ ├── 06_fastmcp_context___context__.md
│ │ ├── 07_mcp_protocol_types.md
│ │ ├── 08_client_server_sessions___clientsession____serversession__.md
│ │ ├── 09_communication_transports__stdio__sse__websocket__memory_.md
│ │ └── index.md
│ ├── NumPy Core/
│ │ ├── 01_ndarray__n_dimensional_array_.md
│ │ ├── 02_dtype__data_type_object_.md
│ │ ├── 03_ufunc__universal_function_.md
│ │ ├── 04_numeric_types___numerictypes__.md
│ │ ├── 05_array_printing___arrayprint__.md
│ │ ├── 06_multiarray_module.md
│ │ ├── 07_umath_module.md
│ │ ├── 08___array_function___protocol___overrides___overrides__.md
│ │ └── index.md
│ ├── OpenManus/
│ │ ├── 01_llm.md
│ │ ├── 02_message___memory.md
│ │ ├── 03_baseagent.md
│ │ ├── 04_tool___toolcollection.md
│ │ ├── 05_baseflow.md
│ │ ├── 06_schema.md
│ │ ├── 07_configuration__config_.md
│ │ ├── 08_dockersandbox.md
│ │ ├── 09_mcp__model_context_protocol_.md
│ │ └── index.md
│ ├── PocketFlow/
│ │ ├── 01_shared_state___shared__dictionary__.md
│ │ ├── 02_node___basenode____node____asyncnode___.md
│ │ ├── 03_actions___transitions_.md
│ │ ├── 04_flow___flow____asyncflow___.md
│ │ ├── 05_asynchronous_processing___asyncnode____asyncflow___.md
│ │ ├── 06_batch_processing___batchnode____batchflow____asyncparallelbatchnode___.md
│ │ ├── 07_a2a__agent_to_agent__communication_framework_.md
│ │ └── index.md
│ ├── Pydantic Core/
│ │ ├── 01_basemodel.md
│ │ ├── 02_fields__fieldinfo___field_function_.md
│ │ ├── 03_configuration__configdict___configwrapper_.md
│ │ ├── 04_custom_logic__decorators___annotated_helpers_.md
│ │ ├── 05_core_schema___validation_serialization.md
│ │ ├── 06_typeadapter.md
│ │ └── index.md
│ ├── Requests/
│ │ ├── 01_functional_api.md
│ │ ├── 02_request___response_models.md
│ │ ├── 03_session.md
│ │ ├── 04_cookie_jar.md
│ │ ├── 05_authentication_handlers.md
│ │ ├── 06_exception_hierarchy.md
│ │ ├── 07_transport_adapters.md
│ │ ├── 08_hook_system.md
│ │ └── index.md
│ ├── SmolaAgents/
│ │ ├── 01_multistepagent.md
│ │ ├── 02_model_interface.md
│ │ ├── 03_tool.md
│ │ ├── 04_agentmemory.md
│ │ ├── 05_prompttemplates.md
│ │ ├── 06_pythonexecutor.md
│ │ ├── 07_agenttype.md
│ │ ├── 08_agentlogger___monitor.md
│ │ └── index.md
│ ├── _config.yml
│ ├── design.md
│ └── index.md
├── flow.py
├── main.py
├── nodes.py
├── requirements.txt
└── utils/
├── __init__.py
├── call_llm.py
├── crawl_github_files.py
└── crawl_local_files.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .clinerules
================================================
---
layout: default
title: "Agentic Coding"
---
# Agentic Coding: Humans Design, Agents code!
> If you are an AI agents involved in building LLM Systems, read this guide **VERY, VERY** carefully! This is the most important chapter in the entire document. Throughout development, you should always (1) start with a small and simple solution, (2) design at a high level (`docs/design.md`) before implementation, and (3) frequently ask humans for feedback and clarification.
{: .warning }
## Agentic Coding Steps
Agentic Coding should be a collaboration between Human System Design and Agent Implementation:
| Steps | Human | AI | Comment |
|:-----------------------|:----------:|:---------:|:------------------------------------------------------------------------|
| 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. |
1. **Requirements**: Clarify the requirements for your project, and evaluate whether an AI system is a good fit.
- Understand AI systems' strengths and limitations:
- **Good for**: Routine tasks requiring common sense (filling forms, replying to emails)
- **Good for**: Creative tasks with well-defined inputs (building slides, writing SQL)
- **Not good for**: Ambiguous problems requiring complex decision-making (business strategy, startup planning)
- **Keep It User-Centric:** Explain the "problem" from the user's perspective rather than just listing features.
- **Balance complexity vs. impact**: Aim to deliver the highest value features with minimal complexity early.
2. **Flow Design**: Outline at a high level, describe how your AI system orchestrates nodes.
- Identify applicable design patterns (e.g., [Map Reduce](./design_pattern/mapreduce.md), [Agent](./design_pattern/agent.md), [RAG](./design_pattern/rag.md)).
- For each node in the flow, start with a high-level one-line description of what it does.
- If using **Map Reduce**, specify how to map (what to split) and how to reduce (how to combine).
- If using **Agent**, specify what are the inputs (context) and what are the possible actions.
- If using **RAG**, specify what to embed, noting that there's usually both offline (indexing) and online (retrieval) workflows.
- Outline the flow and draw it in a mermaid diagram. For example:
```mermaid
flowchart LR
start[Start] --> batch[Batch]
batch --> check[Check]
check -->|OK| process
check -->|Error| fix[Fix]
fix --> check
subgraph process[Process]
step1[Step 1] --> step2[Step 2]
end
process --> endNode[End]
```
- > **If Humans can't specify the flow, AI Agents can't automate it!** Before building an LLM system, thoroughly understand the problem and potential solution by manually solving example inputs to develop intuition.
{: .best-practice }
3. **Utilities**: Based on the Flow Design, identify and implement necessary utility functions.
- Think of your AI system as the brain. It needs a body—these *external utility functions*—to interact with the real world:
<div align="center"><img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/utility.png?raw=true" width="400"/></div>
- Reading inputs (e.g., retrieving Slack messages, reading emails)
- Writing outputs (e.g., generating reports, sending emails)
- Using external tools (e.g., calling LLMs, searching the web)
- **NOTE**: *LLM-based tasks* (e.g., summarizing text, analyzing sentiment) are **NOT** utility functions; rather, they are *core functions* internal in the AI system.
- For each utility function, implement it and write a simple test.
- Document their input/output, as well as why they are necessary. For example:
- `name`: `get_embedding` (`utils/get_embedding.py`)
- `input`: `str`
- `output`: a vector of 3072 floats
- `necessity`: Used by the second node to embed text
- Example utility implementation:
```python
# utils/call_llm.py
from openai import OpenAI
def call_llm(prompt):
client = OpenAI(api_key="YOUR_API_KEY_HERE")
r = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
)
return r.choices[0].message.content
if __name__ == "__main__":
prompt = "What is the meaning of life?"
print(call_llm(prompt))
```
- > **Sometimes, design Utilies 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 }
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](./core_abstraction/communication.md), so start with a shared store design:
- 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.
- Example shared store design:
```python
shared = {
"user": {
"id": "user123",
"context": { # Another nested dict
"weather": {"temp": 72, "condition": "sunny"},
"location": "San Francisco"
}
},
"results": {} # Empty dict to store outputs
}
```
- For each [Node](./core_abstraction/node.md), 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
- `post`: Write "embedding" to the shared store
5. **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.
- Add logging throughout the code to facilitate debugging.
7. **Optimization**:
- **Use Intuition**: For a quick initial evaluation, human intuition is often a good start.
- **Redesign Flow (Back to Step 3)**: Consider breaking down tasks further, introducing agentic decisions, or better managing input contexts.
- If your flow design is already solid, move on to micro-optimizations:
- **Prompt Engineering**: Use clear, specific instructions with examples to reduce ambiguity.
- **In-Context Learning**: Provide robust examples for tasks that are difficult to specify with instructions alone.
- > **You'll likely iterate a lot!** Expect to repeat Steps 3–6 hundreds of times.
>
> <div align="center"><img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/success.png?raw=true" width="400"/></div>
{: .best-practice }
8. **Reliability**
- **Node Retries**: Add checks in the node `exec` to ensure outputs meet requirements, and consider increasing `max_retries` and `wait` times.
- **Logging and Visualization**: Maintain logs of all attempts and visualize node results for easier debugging.
- **Self-Evaluation**: Add a separate node (powered by an LLM) to review outputs when results are uncertain.
## Example LLM Project File Structure
```
my_project/
├── main.py
├── nodes.py
├── flow.py
├── utils/
│ ├── __init__.py
│ ├── call_llm.py
│ └── search_web.py
├── requirements.txt
└── docs/
└── design.md
```
- **`docs/design.md`**: Contains project documentation for each step above. This should be *high-level* and *no-code*.
- **`utils/`**: Contains all utility functions.
- It's recommended to dedicate one Python file to each API call, for example `call_llm.py` or `search_web.py`.
- Each file should also include a `main()` function to try that API call
- **`nodes.py`**: Contains all the node definitions.
```python
# nodes.py
from pocketflow import Node
from utils.call_llm import call_llm
class GetQuestionNode(Node):
def exec(self, _):
# Get question directly from user input
user_question = input("Enter your question: ")
return user_question
def post(self, shared, prep_res, exec_res):
# Store the user's question
shared["question"] = exec_res
return "default" # Go to the next node
class AnswerNode(Node):
def prep(self, shared):
# Read question from shared
return shared["question"]
def exec(self, question):
# Call LLM to get the answer
return call_llm(question)
def post(self, shared, prep_res, exec_res):
# Store the answer in shared
shared["answer"] = exec_res
```
- **`flow.py`**: Implements functions that create flows by importing node definitions and connecting them.
```python
# flow.py
from pocketflow import Flow
from nodes import GetQuestionNode, AnswerNode
def create_qa_flow():
"""Create and return a question-answering flow."""
# Create nodes
get_question_node = GetQuestionNode()
answer_node = AnswerNode()
# Connect nodes in sequence
get_question_node >> answer_node
# Create flow starting with input node
return Flow(start=get_question_node)
```
- **`main.py`**: Serves as the project's entry point.
```python
# main.py
from flow import create_qa_flow
# Example main function
# Please replace this with your own main function
def main():
shared = {
"question": None, # Will be populated by GetQuestionNode from user input
"answer": None # Will be populated by AnswerNode
}
# Create the flow and run it
qa_flow = create_qa_flow()
qa_flow.run(shared)
print(f"Question: {shared['question']}")
print(f"Answer: {shared['answer']}")
if __name__ == "__main__":
main()
```
================================================
File: docs/index.md
================================================
---
layout: default
title: "Home"
nav_order: 1
---
# Pocket Flow
A [100-line](https://github.com/the-pocket/PocketFlow/blob/main/pocketflow/__init__.py) minimalist LLM framework for *Agents, Task Decomposition, RAG, etc*.
- **Lightweight**: Just the core graph abstraction in 100 lines. ZERO dependencies, and vendor lock-in.
- **Expressive**: Everything you love from larger frameworks—([Multi-](./design_pattern/multi_agent.html))[Agents](./design_pattern/agent.html), [Workflow](./design_pattern/workflow.html), [RAG](./design_pattern/rag.html), and more.
- **Agentic-Coding**: Intuitive enough for AI agents to help humans build complex LLM applications.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/meme.jpg?raw=true" width="400"/>
</div>
## Core Abstraction
We model the LLM workflow as a **Graph + Shared Store**:
- [Node](./core_abstraction/node.md) handles simple (LLM) tasks.
- [Flow](./core_abstraction/flow.md) connects nodes through **Actions** (labeled edges).
- [Shared Store](./core_abstraction/communication.md) enables communication between nodes within flows.
- [Batch](./core_abstraction/batch.md) nodes/flows allow for data-intensive tasks.
- [Async](./core_abstraction/async.md) nodes/flows allow waiting for asynchronous tasks.
- [(Advanced) Parallel](./core_abstraction/parallel.md) nodes/flows handle I/O-bound tasks.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/abstraction.png" width="500"/>
</div>
## Design Pattern
From there, it’s easy to implement popular design patterns:
- [Agent](./design_pattern/agent.md) autonomously makes decisions.
- [Workflow](./design_pattern/workflow.md) chains multiple tasks into pipelines.
- [RAG](./design_pattern/rag.md) integrates data retrieval with generation.
- [Map Reduce](./design_pattern/mapreduce.md) splits data tasks into Map and Reduce steps.
- [Structured Output](./design_pattern/structure.md) formats outputs consistently.
- [(Advanced) Multi-Agents](./design_pattern/multi_agent.md) coordinate multiple agents.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/design.png" width="500"/>
</div>
## Utility Function
We **do not** provide built-in utilities. Instead, we offer *examples*—please *implement your own*:
- [LLM Wrapper](./utility_function/llm.md)
- [Viz and Debug](./utility_function/viz.md)
- [Web Search](./utility_function/websearch.md)
- [Chunking](./utility_function/chunking.md)
- [Embedding](./utility_function/embedding.md)
- [Vector Databases](./utility_function/vector.md)
- [Text-to-Speech](./utility_function/text_to_speech.md)
**Why not built-in?**: I believe it's a *bad practice* for vendor-specific APIs in a general framework:
- *API Volatility*: Frequent changes lead to heavy maintenance for hardcoded APIs.
- *Flexibility*: You may want to switch vendors, use fine-tuned models, or run them locally.
- *Optimizations*: Prompt caching, batching, and streaming are easier without vendor lock-in.
## Ready to build your Apps?
Check out [Agentic Coding Guidance](./guide.md), the fastest way to develop LLM projects with Pocket Flow!
================================================
File: docs/core_abstraction/async.md
================================================
---
layout: default
title: "(Advanced) Async"
parent: "Core Abstraction"
nav_order: 5
---
# (Advanced) Async
**Async** Nodes implement `prep_async()`, `exec_async()`, `exec_fallback_async()`, and/or `post_async()`. This is useful for:
1. **prep_async()**: For *fetching/reading data (files, APIs, DB)* in an I/O-friendly way.
2. **exec_async()**: Typically used for async LLM calls.
3. **post_async()**: For *awaiting user feedback*, *coordinating across multi-agents* or any additional async steps after `exec_async()`.
**Note**: `AsyncNode` must be wrapped in `AsyncFlow`. `AsyncFlow` can also include regular (sync) nodes.
### Example
```python
class SummarizeThenVerify(AsyncNode):
async def prep_async(self, shared):
# Example: read a file asynchronously
doc_text = await read_file_async(shared["doc_path"])
return doc_text
async def exec_async(self, prep_res):
# Example: async LLM call
summary = await call_llm_async(f"Summarize: {prep_res}")
return summary
async def post_async(self, shared, prep_res, exec_res):
# Example: wait for user feedback
decision = await gather_user_feedback(exec_res)
if decision == "approve":
shared["summary"] = exec_res
return "approve"
return "deny"
summarize_node = SummarizeThenVerify()
final_node = Finalize()
# Define transitions
summarize_node - "approve" >> final_node
summarize_node - "deny" >> summarize_node # retry
flow = AsyncFlow(start=summarize_node)
async def main():
shared = {"doc_path": "document.txt"}
await flow.run_async(shared)
print("Final Summary:", shared.get("summary"))
asyncio.run(main())
```
================================================
File: docs/core_abstraction/batch.md
================================================
---
layout: default
title: "Batch"
parent: "Core Abstraction"
nav_order: 4
---
# Batch
**Batch** makes it easier to handle large inputs in one Node or **rerun** a Flow multiple times. Example use cases:
- **Chunk-based** processing (e.g., splitting large texts).
- **Iterative** processing over lists of input items (e.g., user queries, files, URLs).
## 1. BatchNode
A **BatchNode** extends `Node` but changes `prep()` and `exec()`:
- **`prep(shared)`**: returns an **iterable** (e.g., list, generator).
- **`exec(item)`**: called **once** per item in that iterable.
- **`post(shared, prep_res, exec_res_list)`**: after all items are processed, receives a **list** of results (`exec_res_list`) and returns an **Action**.
### Example: Summarize a Large File
```python
class MapSummaries(BatchNode):
def prep(self, shared):
# Suppose we have a big file; chunk it
content = shared["data"]
chunk_size = 10000
chunks = [content[i:i+chunk_size] for i in range(0, len(content), chunk_size)]
return chunks
def exec(self, chunk):
prompt = f"Summarize this chunk in 10 words: {chunk}"
summary = call_llm(prompt)
return summary
def post(self, shared, prep_res, exec_res_list):
combined = "\n".join(exec_res_list)
shared["summary"] = combined
return "default"
map_summaries = MapSummaries()
flow = Flow(start=map_summaries)
flow.run(shared)
```
---
## 2. BatchFlow
A **BatchFlow** runs a **Flow** multiple times, each time with different `params`. Think of it as a loop that replays the Flow for each parameter set.
### Example: Summarize Many Files
```python
class SummarizeAllFiles(BatchFlow):
def prep(self, shared):
# Return a list of param dicts (one per file)
filenames = list(shared["data"].keys()) # e.g., ["file1.txt", "file2.txt", ...]
return [{"filename": fn} for fn in filenames]
# Suppose we have a per-file Flow (e.g., load_file >> summarize >> reduce):
summarize_file = SummarizeFile(start=load_file)
# Wrap that flow into a BatchFlow:
summarize_all_files = SummarizeAllFiles(start=summarize_file)
summarize_all_files.run(shared)
```
### Under the Hood
1. `prep(shared)` returns a list of param dicts—e.g., `[{filename: "file1.txt"}, {filename: "file2.txt"}, ...]`.
2. The **BatchFlow** loops through each dict. For each one:
- It merges the dict with the BatchFlow’s own `params`.
- It calls `flow.run(shared)` using the merged result.
3. This means the sub-Flow is run **repeatedly**, once for every param dict.
---
## 3. Nested or Multi-Level Batches
You can nest a **BatchFlow** in another **BatchFlow**. For instance:
- **Outer** batch: returns a list of diretory param dicts (e.g., `{"directory": "/pathA"}`, `{"directory": "/pathB"}`, ...).
- **Inner** batch: returning a list of per-file param dicts.
At each level, **BatchFlow** merges its own param dict with the parent’s. By the time you reach the **innermost** node, the final `params` is the merged result of **all** parents in the chain. This way, a nested structure can keep track of the entire context (e.g., directory + file name) at once.
```python
class FileBatchFlow(BatchFlow):
def prep(self, shared):
directory = self.params["directory"]
# e.g., files = ["file1.txt", "file2.txt", ...]
files = [f for f in os.listdir(directory) if f.endswith(".txt")]
return [{"filename": f} for f in files]
class DirectoryBatchFlow(BatchFlow):
def prep(self, shared):
directories = [ "/path/to/dirA", "/path/to/dirB"]
return [{"directory": d} for d in directories]
# MapSummaries have params like {"directory": "/path/to/dirA", "filename": "file1.txt"}
inner_flow = FileBatchFlow(start=MapSummaries())
outer_flow = DirectoryBatchFlow(start=inner_flow)
```
================================================
File: docs/core_abstraction/communication.md
================================================
---
layout: default
title: "Communication"
parent: "Core Abstraction"
nav_order: 3
---
# Communication
Nodes and Flows **communicate** in 2 ways:
1. **Shared Store (for almost all the cases)**
- A global data structure (often an in-mem dict) that all nodes can read ( `prep()`) and write (`post()`).
- Great for data results, large content, or anything multiple nodes need.
- You shall design the data structure and populate it ahead.
- > **Separation of Concerns:** Use `Shared Store` for almost all cases to separate *Data Schema* from *Compute Logic*! This approach is both flexible and easy to manage, resulting in more maintainable code. `Params` is more a syntax sugar for [Batch](./batch.md).
{: .best-practice }
2. **Params (only for [Batch](./batch.md))**
- Each node has a local, ephemeral `params` dict passed in by the **parent Flow**, used as an identifier for tasks. Parameter keys and values shall be **immutable**.
- Good for identifiers like filenames or numeric IDs, in Batch mode.
If you know memory management, think of the **Shared Store** like a **heap** (shared by all function calls), and **Params** like a **stack** (assigned by the caller).
---
## 1. Shared Store
### Overview
A shared store is typically an in-mem dictionary, like:
```python
shared = {"data": {}, "summary": {}, "config": {...}, ...}
```
It can also contain local file handlers, DB connections, or a combination for persistence. We recommend deciding the data structure or DB schema first based on your app requirements.
### Example
```python
class LoadData(Node):
def post(self, shared, prep_res, exec_res):
# We write data to shared store
shared["data"] = "Some text content"
return None
class Summarize(Node):
def prep(self, shared):
# We read data from shared store
return shared["data"]
def exec(self, prep_res):
# Call LLM to summarize
prompt = f"Summarize: {prep_res}"
summary = call_llm(prompt)
return summary
def post(self, shared, prep_res, exec_res):
# We write summary to shared store
shared["summary"] = exec_res
return "default"
load_data = LoadData()
summarize = Summarize()
load_data >> summarize
flow = Flow(start=load_data)
shared = {}
flow.run(shared)
```
Here:
- `LoadData` writes to `shared["data"]`.
- `Summarize` reads from `shared["data"]`, summarizes, and writes to `shared["summary"]`.
---
## 2. Params
**Params** let you store *per-Node* or *per-Flow* config that doesn't need to live in the shared store. They are:
- **Immutable** during a Node's run cycle (i.e., they don't change mid-`prep->exec->post`).
- **Set** via `set_params()`.
- **Cleared** and updated each time a parent Flow calls it.
> Only set the uppermost Flow params because others will be overwritten by the parent Flow.
>
> If you need to set child node params, see [Batch](./batch.md).
{: .warning }
Typically, **Params** are identifiers (e.g., file name, page number). Use them to fetch the task you assigned or write to a specific part of the shared store.
### Example
```python
# 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, 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()
# 3) Set Node params directly (for testing)
node.set_params({"filename": "doc1.txt"})
node.run(shared)
# 4) Create Flow
flow = Flow(start=node)
# 5) Set Flow params (overwrites node params)
flow.set_params({"filename": "doc2.txt"})
flow.run(shared) # The node summarizes doc2, not doc1
```
================================================
File: docs/core_abstraction/flow.md
================================================
---
layout: default
title: "Flow"
parent: "Core Abstraction"
nav_order: 2
---
# Flow
A **Flow** orchestrates a graph of Nodes. You can chain Nodes in a sequence or create branching depending on the **Actions** returned from each Node's `post()`.
## 1. Action-based Transitions
Each Node's `post()` returns an **Action** string. By default, if `post()` doesn't return anything, we treat that as `"default"`.
You define transitions with the syntax:
1. **Basic default transition**: `node_a >> node_b`
This means if `node_a.post()` returns `"default"`, go to `node_b`.
(Equivalent to `node_a - "default" >> node_b`)
2. **Named action transition**: `node_a - "action_name" >> node_b`
This means if `node_a.post()` returns `"action_name"`, go to `node_b`.
It's possible to create loops, branching, or multi-step flows.
## 2. Creating a Flow
A **Flow** begins with a **start** node. You call `Flow(start=some_node)` to specify the entry point. When you call `flow.run(shared)`, it executes the start node, looks at its returned Action from `post()`, follows the transition, and continues until there's no next node.
### Example: Simple Sequence
Here's a minimal flow of two nodes in a chain:
```python
node_a >> node_b
flow = Flow(start=node_a)
flow.run(shared)
```
- When you run the flow, it executes `node_a`.
- Suppose `node_a.post()` returns `"default"`.
- The flow then sees `"default"` Action is linked to `node_b` and runs `node_b`.
- `node_b.post()` returns `"default"` but we didn't define `node_b >> something_else`. So the flow ends there.
### Example: Branching & Looping
Here's a simple expense approval flow that demonstrates branching and looping. The `ReviewExpense` node can return three possible Actions:
- `"approved"`: expense is approved, move to payment processing
- `"needs_revision"`: expense needs changes, send back for revision
- `"rejected"`: expense is denied, finish the process
We can wire them like this:
```python
# Define the flow connections
review - "approved" >> payment # If approved, process payment
review - "needs_revision" >> revise # If needs changes, go to revision
review - "rejected" >> finish # If rejected, finish the process
revise >> review # After revision, go back for another review
payment >> finish # After payment, finish the process
flow = Flow(start=review)
```
Let's see how it flows:
1. If `review.post()` returns `"approved"`, the expense moves to the `payment` node
2. If `review.post()` returns `"needs_revision"`, it goes to the `revise` node, which then loops back to `review`
3. If `review.post()` returns `"rejected"`, it moves to the `finish` node and stops
```mermaid
flowchart TD
review[Review Expense] -->|approved| payment[Process Payment]
review -->|needs_revision| revise[Revise Report]
review -->|rejected| finish[Finish Process]
revise --> review
payment --> finish
```
### Running Individual Nodes vs. Running a Flow
- `node.run(shared)`: Just runs that node alone (calls `prep->exec->post()`), returns an Action.
- `flow.run(shared)`: Executes from the start node, follows Actions to the next node, and so on until the flow can't continue.
> `node.run(shared)` **does not** proceed to the successor.
> This is mainly for debugging or testing a single node.
>
> Always use `flow.run(...)` in production to ensure the full pipeline runs correctly.
{: .warning }
## 3. Nested Flows
A **Flow** can act like a Node, which enables powerful composition patterns. This means you can:
1. Use a Flow as a Node within another Flow's transitions.
2. Combine multiple smaller Flows into a larger Flow for reuse.
3. Node `params` will be a merging of **all** parents' `params`.
### Flow's Node Methods
A **Flow** is also a **Node**, so it will run `prep()` and `post()`. However:
- It **won't** run `exec()`, as its main logic is to orchestrate its nodes.
- `post()` always receives `None` for `exec_res` and should instead get the flow execution results from the shared store.
### Basic Flow Nesting
Here's how to connect a flow to another node:
```python
# Create a sub-flow
node_a >> node_b
subflow = Flow(start=node_a)
# Connect it to another node
subflow >> node_c
# Create the parent flow
parent_flow = Flow(start=subflow)
```
When `parent_flow.run()` executes:
1. It starts `subflow`
2. `subflow` runs through its nodes (`node_a->node_b`)
3. After `subflow` completes, execution continues to `node_c`
### Example: Order Processing Pipeline
Here's a practical example that breaks down order processing into nested flows:
```python
# Payment processing sub-flow
validate_payment >> process_payment >> payment_confirmation
payment_flow = Flow(start=validate_payment)
# Inventory sub-flow
check_stock >> reserve_items >> update_inventory
inventory_flow = Flow(start=check_stock)
# Shipping sub-flow
create_label >> assign_carrier >> schedule_pickup
shipping_flow = Flow(start=create_label)
# Connect the flows into a main order pipeline
payment_flow >> inventory_flow >> shipping_flow
# Create the master flow
order_pipeline = Flow(start=payment_flow)
# Run the entire pipeline
order_pipeline.run(shared_data)
```
This creates a clean separation of concerns while maintaining a clear execution path:
```mermaid
flowchart LR
subgraph order_pipeline[Order Pipeline]
subgraph paymentFlow["Payment Flow"]
A[Validate Payment] --> B[Process Payment] --> C[Payment Confirmation]
end
subgraph inventoryFlow["Inventory Flow"]
D[Check Stock] --> E[Reserve Items] --> F[Update Inventory]
end
subgraph shippingFlow["Shipping Flow"]
G[Create Label] --> H[Assign Carrier] --> I[Schedule Pickup]
end
paymentFlow --> inventoryFlow
inventoryFlow --> shippingFlow
end
```
================================================
File: docs/core_abstraction/node.md
================================================
---
layout: default
title: "Node"
parent: "Core Abstraction"
nav_order: 1
---
# Node
A **Node** is the smallest building block. Each Node has 3 steps `prep->exec->post`:
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/node.png?raw=true" width="400"/>
</div>
1. `prep(shared)`
- **Read and preprocess data** from `shared` store.
- Examples: *query DB, read files, or serialize data into a string*.
- Return `prep_res`, which is used by `exec()` and `post()`.
2. `exec(prep_res)`
- **Execute compute logic**, with optional retries and error handling (below).
- Examples: *(mostly) LLM calls, remote APIs, tool use*.
- ⚠️ This shall be only for compute and **NOT** access `shared`.
- ⚠️ If retries enabled, ensure idempotent implementation.
- Return `exec_res`, which is passed to `post()`.
3. `post(shared, prep_res, exec_res)`
- **Postprocess and write data** back to `shared`.
- Examples: *update DB, change states, log results*.
- **Decide the next action** by returning a *string* (`action = "default"` if *None*).
> **Why 3 steps?** To enforce the principle of *separation of concerns*. The data storage and data processing are operated separately.
>
> All steps are *optional*. E.g., you can only implement `prep` and `post` if you just need to process data.
{: .note }
### Fault Tolerance & Retries
You can **retry** `exec()` if it raises an exception via two parameters when define the Node:
- `max_retries` (int): Max times to run `exec()`. The default is `1` (**no** retry).
- `wait` (int): The time to wait (in **seconds**) before next retry. By default, `wait=0` (no waiting).
`wait` is helpful when you encounter rate-limits or quota errors from your LLM provider and need to back off.
```python
my_node = SummarizeFile(max_retries=3, wait=10)
```
When an exception occurs in `exec()`, the Node automatically retries until:
- It either succeeds, or
- The Node has retried `max_retries - 1` times already and fails on the last attempt.
You can get the current retry times (0-based) from `self.cur_retry`.
```python
class RetryNode(Node):
def exec(self, prep_res):
print(f"Retry {self.cur_retry} times")
raise Exception("Failed")
```
### Graceful Fallback
To **gracefully handle** the exception (after all retries) rather than raising it, override:
```python
def exec_fallback(self, prep_res, exc):
raise exc
```
By default, it just re-raises exception. But you can return a fallback result instead, which becomes the `exec_res` passed to `post()`.
### Example: Summarize file
```python
class SummarizeFile(Node):
def prep(self, shared):
return shared["data"]
def exec(self, prep_res):
if not prep_res:
return "Empty file content"
prompt = f"Summarize this text in 10 words: {prep_res}"
summary = call_llm(prompt) # might fail
return summary
def exec_fallback(self, prep_res, exc):
# Provide a simple fallback instead of crashing
return "There was an error processing your request."
def post(self, shared, prep_res, exec_res):
shared["summary"] = exec_res
# Return "default" by not returning
summarize_node = SummarizeFile(max_retries=3)
# node.run() calls prep->exec->post
# If exec() fails, it retries up to 3 times before calling exec_fallback()
action_result = summarize_node.run(shared)
print("Action returned:", action_result) # "default"
print("Summary stored:", shared["summary"])
```
================================================
File: docs/core_abstraction/parallel.md
================================================
---
layout: default
title: "(Advanced) Parallel"
parent: "Core Abstraction"
nav_order: 6
---
# (Advanced) Parallel
**Parallel** Nodes and Flows let you run multiple **Async** Nodes and Flows **concurrently**—for example, summarizing multiple texts at once. This can improve performance by overlapping I/O and compute.
> Because of Python’s GIL, parallel nodes and flows can’t truly parallelize CPU-bound tasks (e.g., heavy numerical computations). However, they excel at overlapping I/O-bound work—like LLM calls, database queries, API requests, or file I/O.
{: .warning }
> - **Ensure Tasks Are Independent**: If each item depends on the output of a previous item, **do not** parallelize.
>
> - **Beware of Rate Limits**: Parallel calls can **quickly** trigger rate limits on LLM services. You may need a **throttling** mechanism (e.g., semaphores or sleep intervals).
>
> - **Consider Single-Node Batch APIs**: Some LLMs offer a **batch inference** API where you can send multiple prompts in a single call. This is more complex to implement but can be more efficient than launching many parallel requests and mitigates rate limits.
{: .best-practice }
## AsyncParallelBatchNode
Like **AsyncBatchNode**, but run `exec_async()` in **parallel**:
```python
class ParallelSummaries(AsyncParallelBatchNode):
async def prep_async(self, shared):
# e.g., multiple texts
return shared["texts"]
async def exec_async(self, text):
prompt = f"Summarize: {text}"
return await call_llm_async(prompt)
async def post_async(self, shared, prep_res, exec_res_list):
shared["summary"] = "\n\n".join(exec_res_list)
return "default"
node = ParallelSummaries()
flow = AsyncFlow(start=node)
```
## AsyncParallelBatchFlow
Parallel version of **BatchFlow**. Each iteration of the sub-flow runs **concurrently** using different parameters:
```python
class SummarizeMultipleFiles(AsyncParallelBatchFlow):
async def prep_async(self, shared):
return [{"filename": f} for f in shared["files"]]
sub_flow = AsyncFlow(start=LoadAndSummarizeFile())
parallel_flow = SummarizeMultipleFiles(start=sub_flow)
await parallel_flow.run_async(shared)
```
================================================
File: docs/design_pattern/agent.md
================================================
---
layout: default
title: "Agent"
parent: "Design Pattern"
nav_order: 1
---
# Agent
Agent is a powerful design pattern in which nodes can take dynamic actions based on the context.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/agent.png?raw=true" width="350"/>
</div>
## Implement Agent with Graph
1. **Context and Action:** Implement nodes that supply context and perform actions.
2. **Branching:** Use branching to connect each action node to an agent node. Use action to allow the agent to direct the [flow](../core_abstraction/flow.md) between nodes—and potentially loop back for multi-step.
3. **Agent Node:** Provide a prompt to decide action—for example:
```python
f"""
### CONTEXT
Task: {task_description}
Previous Actions: {previous_actions}
Current State: {current_state}
### ACTION SPACE
[1] search
Description: Use web search to get results
Parameters:
- query (str): What to search for
[2] answer
Description: Conclude based on the results
Parameters:
- result (str): Final answer to provide
### NEXT ACTION
Decide the next action based on the current context and available action space.
Return your response in the following format:
```yaml
thinking: |
<your step-by-step reasoning process>
action: <action_name>
parameters:
<parameter_name>: <parameter_value>
```"""
```
The core of building **high-performance** and **reliable** agents boils down to:
1. **Context Management:** Provide *relevant, minimal context.* For example, rather than including an entire chat history, retrieve the most relevant via [RAG](./rag.md). Even with larger context windows, LLMs still fall victim to ["lost in the middle"](https://arxiv.org/abs/2307.03172), overlooking mid-prompt content.
2. **Action Space:** Provide *a well-structured and unambiguous* set of actions—avoiding overlap like separate `read_databases` or `read_csvs`. Instead, import CSVs into the database.
## Example Good Action Design
- **Incremental:** Feed content in manageable chunks (500 lines or 1 page) instead of all at once.
- **Overview-zoom-in:** First provide high-level structure (table of contents, summary), then allow drilling into details (raw texts).
- **Parameterized/Programmable:** Instead of fixed actions, enable parameterized (columns to select) or programmable (SQL queries) actions, for example, to read CSV files.
- **Backtracking:** Let the agent undo the last step instead of restarting entirely, preserving progress when encountering errors or dead ends.
## Example: Search Agent
This agent:
1. Decides whether to search or answer
2. If searches, loops back to decide if more search needed
3. Answers when enough context gathered
```python
class DecideAction(Node):
def prep(self, shared):
context = shared.get("context", "No previous search")
query = shared["query"]
return query, context
def exec(self, inputs):
query, context = inputs
prompt = f"""
Given input: {query}
Previous search results: {context}
Should I: 1) Search web for more info 2) Answer with current knowledge
Output in yaml:
```yaml
action: search/answer
reason: why this action
search_term: search phrase if action is search
```"""
resp = call_llm(prompt)
yaml_str = resp.split("```yaml")[1].split("```")[0].strip()
result = yaml.safe_load(yaml_str)
assert isinstance(result, dict)
assert "action" in result
assert "reason" in result
assert result["action"] in ["search", "answer"]
if result["action"] == "search":
assert "search_term" in result
return result
def post(self, shared, prep_res, exec_res):
if exec_res["action"] == "search":
shared["search_term"] = exec_res["search_term"]
return exec_res["action"]
class SearchWeb(Node):
def prep(self, shared):
return shared["search_term"]
def exec(self, search_term):
return search_web(search_term)
def post(self, shared, prep_res, exec_res):
prev_searches = shared.get("context", [])
shared["context"] = prev_searches + [
{"term": shared["search_term"], "result": exec_res}
]
return "decide"
class DirectAnswer(Node):
def prep(self, shared):
return shared["query"], shared.get("context", "")
def exec(self, inputs):
query, context = inputs
return call_llm(f"Context: {context}\nAnswer: {query}")
def post(self, shared, prep_res, exec_res):
print(f"Answer: {exec_res}")
shared["answer"] = exec_res
# Connect nodes
decide = DecideAction()
search = SearchWeb()
answer = DirectAnswer()
decide - "search" >> search
decide - "answer" >> answer
search - "decide" >> decide # Loop back
flow = Flow(start=decide)
flow.run({"query": "Who won the Nobel Prize in Physics 2024?"})
```
================================================
File: docs/design_pattern/mapreduce.md
================================================
---
layout: default
title: "Map Reduce"
parent: "Design Pattern"
nav_order: 4
---
# Map Reduce
MapReduce is a design pattern suitable when you have either:
- Large input data (e.g., multiple files to process), or
- Large output data (e.g., multiple forms to fill)
and there is a logical way to break the task into smaller, ideally independent parts.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/mapreduce.png?raw=true" width="400"/>
</div>
You first break down the task using [BatchNode](../core_abstraction/batch.md) in the map phase, followed by aggregation in the reduce phase.
### Example: Document Summarization
```python
class SummarizeAllFiles(BatchNode):
def prep(self, shared):
files_dict = shared["files"] # e.g. 10 files
return list(files_dict.items()) # [("file1.txt", "aaa..."), ("file2.txt", "bbb..."), ...]
def exec(self, one_file):
filename, file_content = one_file
summary_text = call_llm(f"Summarize the following file:\n{file_content}")
return (filename, summary_text)
def post(self, shared, prep_res, exec_res_list):
shared["file_summaries"] = dict(exec_res_list)
class CombineSummaries(Node):
def prep(self, shared):
return shared["file_summaries"]
def exec(self, file_summaries):
# format as: "File1: summary\nFile2: summary...\n"
text_list = []
for fname, summ in file_summaries.items():
text_list.append(f"{fname} summary:\n{summ}\n")
big_text = "\n---\n".join(text_list)
return call_llm(f"Combine these file summaries into one final summary:\n{big_text}")
def post(self, shared, prep_res, final_summary):
shared["all_files_summary"] = final_summary
batch_node = SummarizeAllFiles()
combine_node = CombineSummaries()
batch_node >> combine_node
flow = Flow(start=batch_node)
shared = {
"files": {
"file1.txt": "Alice was beginning to get very tired of sitting by her sister...",
"file2.txt": "Some other interesting text ...",
# ...
}
}
flow.run(shared)
print("Individual Summaries:", shared["file_summaries"])
print("\nFinal Summary:\n", shared["all_files_summary"])
```
================================================
File: docs/design_pattern/rag.md
================================================
---
layout: default
title: "RAG"
parent: "Design Pattern"
nav_order: 3
---
# RAG (Retrieval Augmented Generation)
For certain LLM tasks like answering questions, providing relevant context is essential. One common architecture is a **two-stage** RAG pipeline:
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/rag.png?raw=true" width="400"/>
</div>
1. **Offline stage**: Preprocess and index documents ("building the index").
2. **Online stage**: Given a question, generate answers by retrieving the most relevant context.
---
## Stage 1: Offline Indexing
We create three Nodes:
1. `ChunkDocs` – [chunks](../utility_function/chunking.md) raw text.
2. `EmbedDocs` – [embeds](../utility_function/embedding.md) each chunk.
3. `StoreIndex` – stores embeddings into a [vector database](../utility_function/vector.md).
```python
class ChunkDocs(BatchNode):
def prep(self, shared):
# A list of file paths in shared["files"]. We process each file.
return shared["files"]
def exec(self, filepath):
# read file content. In real usage, do error handling.
with open(filepath, "r", encoding="utf-8") as f:
text = f.read()
# chunk by 100 chars each
chunks = []
size = 100
for i in range(0, len(text), size):
chunks.append(text[i : i + size])
return chunks
def post(self, shared, prep_res, exec_res_list):
# exec_res_list is a list of chunk-lists, one per file.
# flatten them all into a single list of chunks.
all_chunks = []
for chunk_list in exec_res_list:
all_chunks.extend(chunk_list)
shared["all_chunks"] = all_chunks
class EmbedDocs(BatchNode):
def prep(self, shared):
return shared["all_chunks"]
def exec(self, chunk):
return get_embedding(chunk)
def post(self, shared, prep_res, exec_res_list):
# Store the list of embeddings.
shared["all_embeds"] = exec_res_list
print(f"Total embeddings: {len(exec_res_list)}")
class StoreIndex(Node):
def prep(self, shared):
# We'll read all embeds from shared.
return shared["all_embeds"]
def exec(self, all_embeds):
# Create a vector index (faiss or other DB in real usage).
index = create_index(all_embeds)
return index
def post(self, shared, prep_res, index):
shared["index"] = index
# Wire them in sequence
chunk_node = ChunkDocs()
embed_node = EmbedDocs()
store_node = StoreIndex()
chunk_node >> embed_node >> store_node
OfflineFlow = Flow(start=chunk_node)
```
Usage example:
```python
shared = {
"files": ["doc1.txt", "doc2.txt"], # any text files
}
OfflineFlow.run(shared)
```
---
## Stage 2: Online Query & Answer
We have 3 nodes:
1. `EmbedQuery` – embeds the user’s question.
2. `RetrieveDocs` – retrieves top chunk from the index.
3. `GenerateAnswer` – calls the LLM with the question + chunk to produce the final answer.
```python
class EmbedQuery(Node):
def prep(self, shared):
return shared["question"]
def exec(self, question):
return get_embedding(question)
def post(self, shared, prep_res, q_emb):
shared["q_emb"] = q_emb
class RetrieveDocs(Node):
def prep(self, shared):
# We'll need the query embedding, plus the offline index/chunks
return shared["q_emb"], shared["index"], shared["all_chunks"]
def exec(self, inputs):
q_emb, index, chunks = inputs
I, D = search_index(index, q_emb, top_k=1)
best_id = I[0][0]
relevant_chunk = chunks[best_id]
return relevant_chunk
def post(self, shared, prep_res, relevant_chunk):
shared["retrieved_chunk"] = relevant_chunk
print("Retrieved chunk:", relevant_chunk[:60], "...")
class GenerateAnswer(Node):
def prep(self, shared):
return shared["question"], shared["retrieved_chunk"]
def exec(self, inputs):
question, chunk = inputs
prompt = f"Question: {question}\nContext: {chunk}\nAnswer:"
return call_llm(prompt)
def post(self, shared, prep_res, answer):
shared["answer"] = answer
print("Answer:", answer)
embed_qnode = EmbedQuery()
retrieve_node = RetrieveDocs()
generate_node = GenerateAnswer()
embed_qnode >> retrieve_node >> generate_node
OnlineFlow = Flow(start=embed_qnode)
```
Usage example:
```python
# Suppose we already ran OfflineFlow and have:
# shared["all_chunks"], shared["index"], etc.
shared["question"] = "Why do people like cats?"
OnlineFlow.run(shared)
# final answer in shared["answer"]
```
================================================
File: docs/design_pattern/structure.md
================================================
---
layout: default
title: "Structured Output"
parent: "Design Pattern"
nav_order: 5
---
# Structured Output
In many use cases, you may want the LLM to output a specific structure, such as a list or a dictionary with predefined keys.
There are several approaches to achieve a structured output:
- **Prompting** the LLM to strictly return a defined structure.
- Using LLMs that natively support **schema enforcement**.
- **Post-processing** the LLM's response to extract structured content.
In practice, **Prompting** is simple and reliable for modern LLMs.
### Example Use Cases
- Extracting Key Information
```yaml
product:
name: Widget Pro
price: 199.99
description: |
A high-quality widget designed for professionals.
Recommended for advanced users.
```
- Summarizing Documents into Bullet Points
```yaml
summary:
- This product is easy to use.
- It is cost-effective.
- Suitable for all skill levels.
```
- Generating Configuration Files
```yaml
server:
host: 127.0.0.1
port: 8080
ssl: true
```
## Prompt Engineering
When prompting the LLM to produce **structured** output:
1. **Wrap** the structure in code fences (e.g., `yaml`).
2. **Validate** that all required fields exist (and let `Node` handles retry).
### Example Text Summarization
```python
class SummarizeNode(Node):
def exec(self, prep_res):
# Suppose `prep_res` is the text to summarize.
prompt = f"""
Please summarize the following text as YAML, with exactly 3 bullet points
{prep_res}
Now, output:
```yaml
summary:
- bullet 1
- bullet 2
- bullet 3
```"""
response = call_llm(prompt)
yaml_str = response.split("```yaml")[1].split("```")[0].strip()
import yaml
structured_result = yaml.safe_load(yaml_str)
assert "summary" in structured_result
assert isinstance(structured_result["summary"], list)
return structured_result
```
> Besides using `assert` statements, another popular way to validate schemas is [Pydantic](https://github.com/pydantic/pydantic)
{: .note }
### Why YAML instead of JSON?
Current LLMs struggle with escaping. YAML is easier with strings since they don't always need quotes.
**In JSON**
```json
{
"dialogue": "Alice said: \"Hello Bob.\\nHow are you?\\nI am good.\""
}
```
- Every double quote inside the string must be escaped with `\"`.
- Each newline in the dialogue must be represented as `\n`.
**In YAML**
```yaml
dialogue: |
Alice said: "Hello Bob.
How are you?
I am good."
```
- No need to escape interior quotes—just place the entire text under a block literal (`|`).
- Newlines are naturally preserved without needing `\n`.
================================================
File: docs/design_pattern/workflow.md
================================================
---
layout: default
title: "Workflow"
parent: "Design Pattern"
nav_order: 2
---
# Workflow
Many real-world tasks are too complex for one LLM call. The solution is to **Task Decomposition**: decompose them into a [chain](../core_abstraction/flow.md) of multiple Nodes.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/workflow.png?raw=true" width="400"/>
</div>
> - You don't want to make each task **too coarse**, because it may be *too complex for one LLM call*.
> - You don't want to make each task **too granular**, because then *the LLM call doesn't have enough context* and results are *not consistent across nodes*.
>
> You usually need multiple *iterations* to find the *sweet spot*. If the task has too many *edge cases*, consider using [Agents](./agent.md).
{: .best-practice }
### Example: Article Writing
```python
class GenerateOutline(Node):
def prep(self, shared): return shared["topic"]
def exec(self, topic): return call_llm(f"Create a detailed outline for an article about {topic}")
def post(self, shared, prep_res, exec_res): shared["outline"] = exec_res
class WriteSection(Node):
def prep(self, shared): return shared["outline"]
def exec(self, outline): return call_llm(f"Write content based on this outline: {outline}")
def post(self, shared, prep_res, exec_res): shared["draft"] = exec_res
class ReviewAndRefine(Node):
def prep(self, shared): return shared["draft"]
def exec(self, draft): return call_llm(f"Review and improve this draft: {draft}")
def post(self, shared, prep_res, exec_res): shared["final_article"] = exec_res
# Connect nodes
outline = GenerateOutline()
write = WriteSection()
review = ReviewAndRefine()
outline >> write >> review
# Create and run flow
writing_flow = Flow(start=outline)
shared = {"topic": "AI Safety"}
writing_flow.run(shared)
```
For *dynamic cases*, consider using [Agents](./agent.md).
================================================
File: docs/utility_function/llm.md
================================================
---
layout: default
title: "LLM Wrapper"
parent: "Utility Function"
nav_order: 1
---
# LLM Wrappers
Check out libraries like [litellm](https://github.com/BerriAI/litellm).
Here, we provide some minimal example implementations:
1. OpenAI
```python
def call_llm(prompt):
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY_HERE")
r = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
)
return r.choices[0].message.content
# Example usage
call_llm("How are you?")
```
> Store the API key in an environment variable like OPENAI_API_KEY for security.
{: .best-practice }
2. Claude (Anthropic)
```python
def call_llm(prompt):
from anthropic import Anthropic
client = Anthropic(api_key="YOUR_API_KEY_HERE")
response = client.messages.create(
model="claude-2",
messages=[{"role": "user", "content": prompt}],
max_tokens=100
)
return response.content
```
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")
response = genai.generate_text(
model="models/text-bison-001",
prompt=prompt
)
return response.result
```
4. Azure (Azure OpenAI)
```python
def call_llm(prompt):
from openai import AzureOpenAI
client = AzureOpenAI(
azure_endpoint="https://<YOUR_RESOURCE_NAME>.openai.azure.com/",
api_key="YOUR_API_KEY_HERE",
api_version="2023-05-15"
)
r = client.chat.completions.create(
model="<YOUR_DEPLOYMENT_NAME>",
messages=[{"role": "user", "content": prompt}]
)
return r.choices[0].message.content
```
5. Ollama (Local LLM)
```python
def call_llm(prompt):
from ollama import chat
response = chat(
model="llama2",
messages=[{"role": "user", "content": prompt}]
)
return response.message.content
```
## Improvements
Feel free to enhance your `call_llm` function as needed. Here are examples:
- Handle chat history:
```python
def call_llm(messages):
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY_HERE")
r = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
return r.choices[0].message.content
```
- Add in-memory caching
```python
from functools import lru_cache
@lru_cache(maxsize=1000)
def call_llm(prompt):
# Your implementation here
pass
```
> ⚠️ Caching conflicts with Node retries, as retries yield the same result.
>
> To address this, you could use cached results only if not retried.
{: .warning }
```python
from functools import lru_cache
@lru_cache(maxsize=1000)
def cached_call(prompt):
pass
def call_llm(prompt, use_cache):
if use_cache:
return cached_call(prompt)
# Call the underlying function directly
return cached_call.__wrapped__(prompt)
class SummarizeNode(Node):
def exec(self, text):
return call_llm(f"Summarize: {text}", self.cur_retry==0)
```
- Enable logging:
```python
def call_llm(prompt):
import logging
logging.info(f"Prompt: {prompt}")
response = ... # Your implementation here
logging.info(f"Response: {response}")
return response
```
================================================
FILE: .cursorrules
================================================
---
layout: default
title: "Agentic Coding"
---
# Agentic Coding: Humans Design, Agents code!
> If you are an AI agents involved in building LLM Systems, read this guide **VERY, VERY** carefully! This is the most important chapter in the entire document. Throughout development, you should always (1) start with a small and simple solution, (2) design at a high level (`docs/design.md`) before implementation, and (3) frequently ask humans for feedback and clarification.
{: .warning }
## Agentic Coding Steps
Agentic Coding should be a collaboration between Human System Design and Agent Implementation:
| Steps | Human | AI | Comment |
|:-----------------------|:----------:|:---------:|:------------------------------------------------------------------------|
| 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. |
1. **Requirements**: Clarify the requirements for your project, and evaluate whether an AI system is a good fit.
- Understand AI systems' strengths and limitations:
- **Good for**: Routine tasks requiring common sense (filling forms, replying to emails)
- **Good for**: Creative tasks with well-defined inputs (building slides, writing SQL)
- **Not good for**: Ambiguous problems requiring complex decision-making (business strategy, startup planning)
- **Keep It User-Centric:** Explain the "problem" from the user's perspective rather than just listing features.
- **Balance complexity vs. impact**: Aim to deliver the highest value features with minimal complexity early.
2. **Flow Design**: Outline at a high level, describe how your AI system orchestrates nodes.
- Identify applicable design patterns (e.g., [Map Reduce](./design_pattern/mapreduce.md), [Agent](./design_pattern/agent.md), [RAG](./design_pattern/rag.md)).
- For each node in the flow, start with a high-level one-line description of what it does.
- If using **Map Reduce**, specify how to map (what to split) and how to reduce (how to combine).
- If using **Agent**, specify what are the inputs (context) and what are the possible actions.
- If using **RAG**, specify what to embed, noting that there's usually both offline (indexing) and online (retrieval) workflows.
- Outline the flow and draw it in a mermaid diagram. For example:
```mermaid
flowchart LR
start[Start] --> batch[Batch]
batch --> check[Check]
check -->|OK| process
check -->|Error| fix[Fix]
fix --> check
subgraph process[Process]
step1[Step 1] --> step2[Step 2]
end
process --> endNode[End]
```
- > **If Humans can't specify the flow, AI Agents can't automate it!** Before building an LLM system, thoroughly understand the problem and potential solution by manually solving example inputs to develop intuition.
{: .best-practice }
3. **Utilities**: Based on the Flow Design, identify and implement necessary utility functions.
- Think of your AI system as the brain. It needs a body—these *external utility functions*—to interact with the real world:
<div align="center"><img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/utility.png?raw=true" width="400"/></div>
- Reading inputs (e.g., retrieving Slack messages, reading emails)
- Writing outputs (e.g., generating reports, sending emails)
- Using external tools (e.g., calling LLMs, searching the web)
- **NOTE**: *LLM-based tasks* (e.g., summarizing text, analyzing sentiment) are **NOT** utility functions; rather, they are *core functions* internal in the AI system.
- For each utility function, implement it and write a simple test.
- Document their input/output, as well as why they are necessary. For example:
- `name`: `get_embedding` (`utils/get_embedding.py`)
- `input`: `str`
- `output`: a vector of 3072 floats
- `necessity`: Used by the second node to embed text
- Example utility implementation:
```python
# utils/call_llm.py
from openai import OpenAI
def call_llm(prompt):
client = OpenAI(api_key="YOUR_API_KEY_HERE")
r = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
)
return r.choices[0].message.content
if __name__ == "__main__":
prompt = "What is the meaning of life?"
print(call_llm(prompt))
```
- > **Sometimes, design Utilies 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 }
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](./core_abstraction/communication.md), so start with a shared store design:
- 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.
- Example shared store design:
```python
shared = {
"user": {
"id": "user123",
"context": { # Another nested dict
"weather": {"temp": 72, "condition": "sunny"},
"location": "San Francisco"
}
},
"results": {} # Empty dict to store outputs
}
```
- For each [Node](./core_abstraction/node.md), 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
- `post`: Write "embedding" to the shared store
5. **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.
- Add logging throughout the code to facilitate debugging.
7. **Optimization**:
- **Use Intuition**: For a quick initial evaluation, human intuition is often a good start.
- **Redesign Flow (Back to Step 3)**: Consider breaking down tasks further, introducing agentic decisions, or better managing input contexts.
- If your flow design is already solid, move on to micro-optimizations:
- **Prompt Engineering**: Use clear, specific instructions with examples to reduce ambiguity.
- **In-Context Learning**: Provide robust examples for tasks that are difficult to specify with instructions alone.
- > **You'll likely iterate a lot!** Expect to repeat Steps 3–6 hundreds of times.
>
> <div align="center"><img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/success.png?raw=true" width="400"/></div>
{: .best-practice }
8. **Reliability**
- **Node Retries**: Add checks in the node `exec` to ensure outputs meet requirements, and consider increasing `max_retries` and `wait` times.
- **Logging and Visualization**: Maintain logs of all attempts and visualize node results for easier debugging.
- **Self-Evaluation**: Add a separate node (powered by an LLM) to review outputs when results are uncertain.
## Example LLM Project File Structure
```
my_project/
├── main.py
├── nodes.py
├── flow.py
├── utils/
│ ├── __init__.py
│ ├── call_llm.py
│ └── search_web.py
├── requirements.txt
└── docs/
└── design.md
```
- **`docs/design.md`**: Contains project documentation for each step above. This should be *high-level* and *no-code*.
- **`utils/`**: Contains all utility functions.
- It's recommended to dedicate one Python file to each API call, for example `call_llm.py` or `search_web.py`.
- Each file should also include a `main()` function to try that API call
- **`nodes.py`**: Contains all the node definitions.
```python
# nodes.py
from pocketflow import Node
from utils.call_llm import call_llm
class GetQuestionNode(Node):
def exec(self, _):
# Get question directly from user input
user_question = input("Enter your question: ")
return user_question
def post(self, shared, prep_res, exec_res):
# Store the user's question
shared["question"] = exec_res
return "default" # Go to the next node
class AnswerNode(Node):
def prep(self, shared):
# Read question from shared
return shared["question"]
def exec(self, question):
# Call LLM to get the answer
return call_llm(question)
def post(self, shared, prep_res, exec_res):
# Store the answer in shared
shared["answer"] = exec_res
```
- **`flow.py`**: Implements functions that create flows by importing node definitions and connecting them.
```python
# flow.py
from pocketflow import Flow
from nodes import GetQuestionNode, AnswerNode
def create_qa_flow():
"""Create and return a question-answering flow."""
# Create nodes
get_question_node = GetQuestionNode()
answer_node = AnswerNode()
# Connect nodes in sequence
get_question_node >> answer_node
# Create flow starting with input node
return Flow(start=get_question_node)
```
- **`main.py`**: Serves as the project's entry point.
```python
# main.py
from flow import create_qa_flow
# Example main function
# Please replace this with your own main function
def main():
shared = {
"question": None, # Will be populated by GetQuestionNode from user input
"answer": None # Will be populated by AnswerNode
}
# Create the flow and run it
qa_flow = create_qa_flow()
qa_flow.run(shared)
print(f"Question: {shared['question']}")
print(f"Answer: {shared['answer']}")
if __name__ == "__main__":
main()
```
================================================
File: docs/index.md
================================================
---
layout: default
title: "Home"
nav_order: 1
---
# Pocket Flow
A [100-line](https://github.com/the-pocket/PocketFlow/blob/main/pocketflow/__init__.py) minimalist LLM framework for *Agents, Task Decomposition, RAG, etc*.
- **Lightweight**: Just the core graph abstraction in 100 lines. ZERO dependencies, and vendor lock-in.
- **Expressive**: Everything you love from larger frameworks—([Multi-](./design_pattern/multi_agent.html))[Agents](./design_pattern/agent.html), [Workflow](./design_pattern/workflow.html), [RAG](./design_pattern/rag.html), and more.
- **Agentic-Coding**: Intuitive enough for AI agents to help humans build complex LLM applications.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/meme.jpg?raw=true" width="400"/>
</div>
## Core Abstraction
We model the LLM workflow as a **Graph + Shared Store**:
- [Node](./core_abstraction/node.md) handles simple (LLM) tasks.
- [Flow](./core_abstraction/flow.md) connects nodes through **Actions** (labeled edges).
- [Shared Store](./core_abstraction/communication.md) enables communication between nodes within flows.
- [Batch](./core_abstraction/batch.md) nodes/flows allow for data-intensive tasks.
- [Async](./core_abstraction/async.md) nodes/flows allow waiting for asynchronous tasks.
- [(Advanced) Parallel](./core_abstraction/parallel.md) nodes/flows handle I/O-bound tasks.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/abstraction.png" width="500"/>
</div>
## Design Pattern
From there, it’s easy to implement popular design patterns:
- [Agent](./design_pattern/agent.md) autonomously makes decisions.
- [Workflow](./design_pattern/workflow.md) chains multiple tasks into pipelines.
- [RAG](./design_pattern/rag.md) integrates data retrieval with generation.
- [Map Reduce](./design_pattern/mapreduce.md) splits data tasks into Map and Reduce steps.
- [Structured Output](./design_pattern/structure.md) formats outputs consistently.
- [(Advanced) Multi-Agents](./design_pattern/multi_agent.md) coordinate multiple agents.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/design.png" width="500"/>
</div>
## Utility Function
We **do not** provide built-in utilities. Instead, we offer *examples*—please *implement your own*:
- [LLM Wrapper](./utility_function/llm.md)
- [Viz and Debug](./utility_function/viz.md)
- [Web Search](./utility_function/websearch.md)
- [Chunking](./utility_function/chunking.md)
- [Embedding](./utility_function/embedding.md)
- [Vector Databases](./utility_function/vector.md)
- [Text-to-Speech](./utility_function/text_to_speech.md)
**Why not built-in?**: I believe it's a *bad practice* for vendor-specific APIs in a general framework:
- *API Volatility*: Frequent changes lead to heavy maintenance for hardcoded APIs.
- *Flexibility*: You may want to switch vendors, use fine-tuned models, or run them locally.
- *Optimizations*: Prompt caching, batching, and streaming are easier without vendor lock-in.
## Ready to build your Apps?
Check out [Agentic Coding Guidance](./guide.md), the fastest way to develop LLM projects with Pocket Flow!
================================================
File: docs/core_abstraction/async.md
================================================
---
layout: default
title: "(Advanced) Async"
parent: "Core Abstraction"
nav_order: 5
---
# (Advanced) Async
**Async** Nodes implement `prep_async()`, `exec_async()`, `exec_fallback_async()`, and/or `post_async()`. This is useful for:
1. **prep_async()**: For *fetching/reading data (files, APIs, DB)* in an I/O-friendly way.
2. **exec_async()**: Typically used for async LLM calls.
3. **post_async()**: For *awaiting user feedback*, *coordinating across multi-agents* or any additional async steps after `exec_async()`.
**Note**: `AsyncNode` must be wrapped in `AsyncFlow`. `AsyncFlow` can also include regular (sync) nodes.
### Example
```python
class SummarizeThenVerify(AsyncNode):
async def prep_async(self, shared):
# Example: read a file asynchronously
doc_text = await read_file_async(shared["doc_path"])
return doc_text
async def exec_async(self, prep_res):
# Example: async LLM call
summary = await call_llm_async(f"Summarize: {prep_res}")
return summary
async def post_async(self, shared, prep_res, exec_res):
# Example: wait for user feedback
decision = await gather_user_feedback(exec_res)
if decision == "approve":
shared["summary"] = exec_res
return "approve"
return "deny"
summarize_node = SummarizeThenVerify()
final_node = Finalize()
# Define transitions
summarize_node - "approve" >> final_node
summarize_node - "deny" >> summarize_node # retry
flow = AsyncFlow(start=summarize_node)
async def main():
shared = {"doc_path": "document.txt"}
await flow.run_async(shared)
print("Final Summary:", shared.get("summary"))
asyncio.run(main())
```
================================================
File: docs/core_abstraction/batch.md
================================================
---
layout: default
title: "Batch"
parent: "Core Abstraction"
nav_order: 4
---
# Batch
**Batch** makes it easier to handle large inputs in one Node or **rerun** a Flow multiple times. Example use cases:
- **Chunk-based** processing (e.g., splitting large texts).
- **Iterative** processing over lists of input items (e.g., user queries, files, URLs).
## 1. BatchNode
A **BatchNode** extends `Node` but changes `prep()` and `exec()`:
- **`prep(shared)`**: returns an **iterable** (e.g., list, generator).
- **`exec(item)`**: called **once** per item in that iterable.
- **`post(shared, prep_res, exec_res_list)`**: after all items are processed, receives a **list** of results (`exec_res_list`) and returns an **Action**.
### Example: Summarize a Large File
```python
class MapSummaries(BatchNode):
def prep(self, shared):
# Suppose we have a big file; chunk it
content = shared["data"]
chunk_size = 10000
chunks = [content[i:i+chunk_size] for i in range(0, len(content), chunk_size)]
return chunks
def exec(self, chunk):
prompt = f"Summarize this chunk in 10 words: {chunk}"
summary = call_llm(prompt)
return summary
def post(self, shared, prep_res, exec_res_list):
combined = "\n".join(exec_res_list)
shared["summary"] = combined
return "default"
map_summaries = MapSummaries()
flow = Flow(start=map_summaries)
flow.run(shared)
```
---
## 2. BatchFlow
A **BatchFlow** runs a **Flow** multiple times, each time with different `params`. Think of it as a loop that replays the Flow for each parameter set.
### Example: Summarize Many Files
```python
class SummarizeAllFiles(BatchFlow):
def prep(self, shared):
# Return a list of param dicts (one per file)
filenames = list(shared["data"].keys()) # e.g., ["file1.txt", "file2.txt", ...]
return [{"filename": fn} for fn in filenames]
# Suppose we have a per-file Flow (e.g., load_file >> summarize >> reduce):
summarize_file = SummarizeFile(start=load_file)
# Wrap that flow into a BatchFlow:
summarize_all_files = SummarizeAllFiles(start=summarize_file)
summarize_all_files.run(shared)
```
### Under the Hood
1. `prep(shared)` returns a list of param dicts—e.g., `[{filename: "file1.txt"}, {filename: "file2.txt"}, ...]`.
2. The **BatchFlow** loops through each dict. For each one:
- It merges the dict with the BatchFlow’s own `params`.
- It calls `flow.run(shared)` using the merged result.
3. This means the sub-Flow is run **repeatedly**, once for every param dict.
---
## 3. Nested or Multi-Level Batches
You can nest a **BatchFlow** in another **BatchFlow**. For instance:
- **Outer** batch: returns a list of diretory param dicts (e.g., `{"directory": "/pathA"}`, `{"directory": "/pathB"}`, ...).
- **Inner** batch: returning a list of per-file param dicts.
At each level, **BatchFlow** merges its own param dict with the parent’s. By the time you reach the **innermost** node, the final `params` is the merged result of **all** parents in the chain. This way, a nested structure can keep track of the entire context (e.g., directory + file name) at once.
```python
class FileBatchFlow(BatchFlow):
def prep(self, shared):
directory = self.params["directory"]
# e.g., files = ["file1.txt", "file2.txt", ...]
files = [f for f in os.listdir(directory) if f.endswith(".txt")]
return [{"filename": f} for f in files]
class DirectoryBatchFlow(BatchFlow):
def prep(self, shared):
directories = [ "/path/to/dirA", "/path/to/dirB"]
return [{"directory": d} for d in directories]
# MapSummaries have params like {"directory": "/path/to/dirA", "filename": "file1.txt"}
inner_flow = FileBatchFlow(start=MapSummaries())
outer_flow = DirectoryBatchFlow(start=inner_flow)
```
================================================
File: docs/core_abstraction/communication.md
================================================
---
layout: default
title: "Communication"
parent: "Core Abstraction"
nav_order: 3
---
# Communication
Nodes and Flows **communicate** in 2 ways:
1. **Shared Store (for almost all the cases)**
- A global data structure (often an in-mem dict) that all nodes can read ( `prep()`) and write (`post()`).
- Great for data results, large content, or anything multiple nodes need.
- You shall design the data structure and populate it ahead.
- > **Separation of Concerns:** Use `Shared Store` for almost all cases to separate *Data Schema* from *Compute Logic*! This approach is both flexible and easy to manage, resulting in more maintainable code. `Params` is more a syntax sugar for [Batch](./batch.md).
{: .best-practice }
2. **Params (only for [Batch](./batch.md))**
- Each node has a local, ephemeral `params` dict passed in by the **parent Flow**, used as an identifier for tasks. Parameter keys and values shall be **immutable**.
- Good for identifiers like filenames or numeric IDs, in Batch mode.
If you know memory management, think of the **Shared Store** like a **heap** (shared by all function calls), and **Params** like a **stack** (assigned by the caller).
---
## 1. Shared Store
### Overview
A shared store is typically an in-mem dictionary, like:
```python
shared = {"data": {}, "summary": {}, "config": {...}, ...}
```
It can also contain local file handlers, DB connections, or a combination for persistence. We recommend deciding the data structure or DB schema first based on your app requirements.
### Example
```python
class LoadData(Node):
def post(self, shared, prep_res, exec_res):
# We write data to shared store
shared["data"] = "Some text content"
return None
class Summarize(Node):
def prep(self, shared):
# We read data from shared store
return shared["data"]
def exec(self, prep_res):
# Call LLM to summarize
prompt = f"Summarize: {prep_res}"
summary = call_llm(prompt)
return summary
def post(self, shared, prep_res, exec_res):
# We write summary to shared store
shared["summary"] = exec_res
return "default"
load_data = LoadData()
summarize = Summarize()
load_data >> summarize
flow = Flow(start=load_data)
shared = {}
flow.run(shared)
```
Here:
- `LoadData` writes to `shared["data"]`.
- `Summarize` reads from `shared["data"]`, summarizes, and writes to `shared["summary"]`.
---
## 2. Params
**Params** let you store *per-Node* or *per-Flow* config that doesn't need to live in the shared store. They are:
- **Immutable** during a Node's run cycle (i.e., they don't change mid-`prep->exec->post`).
- **Set** via `set_params()`.
- **Cleared** and updated each time a parent Flow calls it.
> Only set the uppermost Flow params because others will be overwritten by the parent Flow.
>
> If you need to set child node params, see [Batch](./batch.md).
{: .warning }
Typically, **Params** are identifiers (e.g., file name, page number). Use them to fetch the task you assigned or write to a specific part of the shared store.
### Example
```python
# 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, 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()
# 3) Set Node params directly (for testing)
node.set_params({"filename": "doc1.txt"})
node.run(shared)
# 4) Create Flow
flow = Flow(start=node)
# 5) Set Flow params (overwrites node params)
flow.set_params({"filename": "doc2.txt"})
flow.run(shared) # The node summarizes doc2, not doc1
```
================================================
File: docs/core_abstraction/flow.md
================================================
---
layout: default
title: "Flow"
parent: "Core Abstraction"
nav_order: 2
---
# Flow
A **Flow** orchestrates a graph of Nodes. You can chain Nodes in a sequence or create branching depending on the **Actions** returned from each Node's `post()`.
## 1. Action-based Transitions
Each Node's `post()` returns an **Action** string. By default, if `post()` doesn't return anything, we treat that as `"default"`.
You define transitions with the syntax:
1. **Basic default transition**: `node_a >> node_b`
This means if `node_a.post()` returns `"default"`, go to `node_b`.
(Equivalent to `node_a - "default" >> node_b`)
2. **Named action transition**: `node_a - "action_name" >> node_b`
This means if `node_a.post()` returns `"action_name"`, go to `node_b`.
It's possible to create loops, branching, or multi-step flows.
## 2. Creating a Flow
A **Flow** begins with a **start** node. You call `Flow(start=some_node)` to specify the entry point. When you call `flow.run(shared)`, it executes the start node, looks at its returned Action from `post()`, follows the transition, and continues until there's no next node.
### Example: Simple Sequence
Here's a minimal flow of two nodes in a chain:
```python
node_a >> node_b
flow = Flow(start=node_a)
flow.run(shared)
```
- When you run the flow, it executes `node_a`.
- Suppose `node_a.post()` returns `"default"`.
- The flow then sees `"default"` Action is linked to `node_b` and runs `node_b`.
- `node_b.post()` returns `"default"` but we didn't define `node_b >> something_else`. So the flow ends there.
### Example: Branching & Looping
Here's a simple expense approval flow that demonstrates branching and looping. The `ReviewExpense` node can return three possible Actions:
- `"approved"`: expense is approved, move to payment processing
- `"needs_revision"`: expense needs changes, send back for revision
- `"rejected"`: expense is denied, finish the process
We can wire them like this:
```python
# Define the flow connections
review - "approved" >> payment # If approved, process payment
review - "needs_revision" >> revise # If needs changes, go to revision
review - "rejected" >> finish # If rejected, finish the process
revise >> review # After revision, go back for another review
payment >> finish # After payment, finish the process
flow = Flow(start=review)
```
Let's see how it flows:
1. If `review.post()` returns `"approved"`, the expense moves to the `payment` node
2. If `review.post()` returns `"needs_revision"`, it goes to the `revise` node, which then loops back to `review`
3. If `review.post()` returns `"rejected"`, it moves to the `finish` node and stops
```mermaid
flowchart TD
review[Review Expense] -->|approved| payment[Process Payment]
review -->|needs_revision| revise[Revise Report]
review -->|rejected| finish[Finish Process]
revise --> review
payment --> finish
```
### Running Individual Nodes vs. Running a Flow
- `node.run(shared)`: Just runs that node alone (calls `prep->exec->post()`), returns an Action.
- `flow.run(shared)`: Executes from the start node, follows Actions to the next node, and so on until the flow can't continue.
> `node.run(shared)` **does not** proceed to the successor.
> This is mainly for debugging or testing a single node.
>
> Always use `flow.run(...)` in production to ensure the full pipeline runs correctly.
{: .warning }
## 3. Nested Flows
A **Flow** can act like a Node, which enables powerful composition patterns. This means you can:
1. Use a Flow as a Node within another Flow's transitions.
2. Combine multiple smaller Flows into a larger Flow for reuse.
3. Node `params` will be a merging of **all** parents' `params`.
### Flow's Node Methods
A **Flow** is also a **Node**, so it will run `prep()` and `post()`. However:
- It **won't** run `exec()`, as its main logic is to orchestrate its nodes.
- `post()` always receives `None` for `exec_res` and should instead get the flow execution results from the shared store.
### Basic Flow Nesting
Here's how to connect a flow to another node:
```python
# Create a sub-flow
node_a >> node_b
subflow = Flow(start=node_a)
# Connect it to another node
subflow >> node_c
# Create the parent flow
parent_flow = Flow(start=subflow)
```
When `parent_flow.run()` executes:
1. It starts `subflow`
2. `subflow` runs through its nodes (`node_a->node_b`)
3. After `subflow` completes, execution continues to `node_c`
### Example: Order Processing Pipeline
Here's a practical example that breaks down order processing into nested flows:
```python
# Payment processing sub-flow
validate_payment >> process_payment >> payment_confirmation
payment_flow = Flow(start=validate_payment)
# Inventory sub-flow
check_stock >> reserve_items >> update_inventory
inventory_flow = Flow(start=check_stock)
# Shipping sub-flow
create_label >> assign_carrier >> schedule_pickup
shipping_flow = Flow(start=create_label)
# Connect the flows into a main order pipeline
payment_flow >> inventory_flow >> shipping_flow
# Create the master flow
order_pipeline = Flow(start=payment_flow)
# Run the entire pipeline
order_pipeline.run(shared_data)
```
This creates a clean separation of concerns while maintaining a clear execution path:
```mermaid
flowchart LR
subgraph order_pipeline[Order Pipeline]
subgraph paymentFlow["Payment Flow"]
A[Validate Payment] --> B[Process Payment] --> C[Payment Confirmation]
end
subgraph inventoryFlow["Inventory Flow"]
D[Check Stock] --> E[Reserve Items] --> F[Update Inventory]
end
subgraph shippingFlow["Shipping Flow"]
G[Create Label] --> H[Assign Carrier] --> I[Schedule Pickup]
end
paymentFlow --> inventoryFlow
inventoryFlow --> shippingFlow
end
```
================================================
File: docs/core_abstraction/node.md
================================================
---
layout: default
title: "Node"
parent: "Core Abstraction"
nav_order: 1
---
# Node
A **Node** is the smallest building block. Each Node has 3 steps `prep->exec->post`:
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/node.png?raw=true" width="400"/>
</div>
1. `prep(shared)`
- **Read and preprocess data** from `shared` store.
- Examples: *query DB, read files, or serialize data into a string*.
- Return `prep_res`, which is used by `exec()` and `post()`.
2. `exec(prep_res)`
- **Execute compute logic**, with optional retries and error handling (below).
- Examples: *(mostly) LLM calls, remote APIs, tool use*.
- ⚠️ This shall be only for compute and **NOT** access `shared`.
- ⚠️ If retries enabled, ensure idempotent implementation.
- Return `exec_res`, which is passed to `post()`.
3. `post(shared, prep_res, exec_res)`
- **Postprocess and write data** back to `shared`.
- Examples: *update DB, change states, log results*.
- **Decide the next action** by returning a *string* (`action = "default"` if *None*).
> **Why 3 steps?** To enforce the principle of *separation of concerns*. The data storage and data processing are operated separately.
>
> All steps are *optional*. E.g., you can only implement `prep` and `post` if you just need to process data.
{: .note }
### Fault Tolerance & Retries
You can **retry** `exec()` if it raises an exception via two parameters when define the Node:
- `max_retries` (int): Max times to run `exec()`. The default is `1` (**no** retry).
- `wait` (int): The time to wait (in **seconds**) before next retry. By default, `wait=0` (no waiting).
`wait` is helpful when you encounter rate-limits or quota errors from your LLM provider and need to back off.
```python
my_node = SummarizeFile(max_retries=3, wait=10)
```
When an exception occurs in `exec()`, the Node automatically retries until:
- It either succeeds, or
- The Node has retried `max_retries - 1` times already and fails on the last attempt.
You can get the current retry times (0-based) from `self.cur_retry`.
```python
class RetryNode(Node):
def exec(self, prep_res):
print(f"Retry {self.cur_retry} times")
raise Exception("Failed")
```
### Graceful Fallback
To **gracefully handle** the exception (after all retries) rather than raising it, override:
```python
def exec_fallback(self, prep_res, exc):
raise exc
```
By default, it just re-raises exception. But you can return a fallback result instead, which becomes the `exec_res` passed to `post()`.
### Example: Summarize file
```python
class SummarizeFile(Node):
def prep(self, shared):
return shared["data"]
def exec(self, prep_res):
if not prep_res:
return "Empty file content"
prompt = f"Summarize this text in 10 words: {prep_res}"
summary = call_llm(prompt) # might fail
return summary
def exec_fallback(self, prep_res, exc):
# Provide a simple fallback instead of crashing
return "There was an error processing your request."
def post(self, shared, prep_res, exec_res):
shared["summary"] = exec_res
# Return "default" by not returning
summarize_node = SummarizeFile(max_retries=3)
# node.run() calls prep->exec->post
# If exec() fails, it retries up to 3 times before calling exec_fallback()
action_result = summarize_node.run(shared)
print("Action returned:", action_result) # "default"
print("Summary stored:", shared["summary"])
```
================================================
File: docs/core_abstraction/parallel.md
================================================
---
layout: default
title: "(Advanced) Parallel"
parent: "Core Abstraction"
nav_order: 6
---
# (Advanced) Parallel
**Parallel** Nodes and Flows let you run multiple **Async** Nodes and Flows **concurrently**—for example, summarizing multiple texts at once. This can improve performance by overlapping I/O and compute.
> Because of Python’s GIL, parallel nodes and flows can’t truly parallelize CPU-bound tasks (e.g., heavy numerical computations). However, they excel at overlapping I/O-bound work—like LLM calls, database queries, API requests, or file I/O.
{: .warning }
> - **Ensure Tasks Are Independent**: If each item depends on the output of a previous item, **do not** parallelize.
>
> - **Beware of Rate Limits**: Parallel calls can **quickly** trigger rate limits on LLM services. You may need a **throttling** mechanism (e.g., semaphores or sleep intervals).
>
> - **Consider Single-Node Batch APIs**: Some LLMs offer a **batch inference** API where you can send multiple prompts in a single call. This is more complex to implement but can be more efficient than launching many parallel requests and mitigates rate limits.
{: .best-practice }
## AsyncParallelBatchNode
Like **AsyncBatchNode**, but run `exec_async()` in **parallel**:
```python
class ParallelSummaries(AsyncParallelBatchNode):
async def prep_async(self, shared):
# e.g., multiple texts
return shared["texts"]
async def exec_async(self, text):
prompt = f"Summarize: {text}"
return await call_llm_async(prompt)
async def post_async(self, shared, prep_res, exec_res_list):
shared["summary"] = "\n\n".join(exec_res_list)
return "default"
node = ParallelSummaries()
flow = AsyncFlow(start=node)
```
## AsyncParallelBatchFlow
Parallel version of **BatchFlow**. Each iteration of the sub-flow runs **concurrently** using different parameters:
```python
class SummarizeMultipleFiles(AsyncParallelBatchFlow):
async def prep_async(self, shared):
return [{"filename": f} for f in shared["files"]]
sub_flow = AsyncFlow(start=LoadAndSummarizeFile())
parallel_flow = SummarizeMultipleFiles(start=sub_flow)
await parallel_flow.run_async(shared)
```
================================================
File: docs/design_pattern/agent.md
================================================
---
layout: default
title: "Agent"
parent: "Design Pattern"
nav_order: 1
---
# Agent
Agent is a powerful design pattern in which nodes can take dynamic actions based on the context.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/agent.png?raw=true" width="350"/>
</div>
## Implement Agent with Graph
1. **Context and Action:** Implement nodes that supply context and perform actions.
2. **Branching:** Use branching to connect each action node to an agent node. Use action to allow the agent to direct the [flow](../core_abstraction/flow.md) between nodes—and potentially loop back for multi-step.
3. **Agent Node:** Provide a prompt to decide action—for example:
```python
f"""
### CONTEXT
Task: {task_description}
Previous Actions: {previous_actions}
Current State: {current_state}
### ACTION SPACE
[1] search
Description: Use web search to get results
Parameters:
- query (str): What to search for
[2] answer
Description: Conclude based on the results
Parameters:
- result (str): Final answer to provide
### NEXT ACTION
Decide the next action based on the current context and available action space.
Return your response in the following format:
```yaml
thinking: |
<your step-by-step reasoning process>
action: <action_name>
parameters:
<parameter_name>: <parameter_value>
```"""
```
The core of building **high-performance** and **reliable** agents boils down to:
1. **Context Management:** Provide *relevant, minimal context.* For example, rather than including an entire chat history, retrieve the most relevant via [RAG](./rag.md). Even with larger context windows, LLMs still fall victim to ["lost in the middle"](https://arxiv.org/abs/2307.03172), overlooking mid-prompt content.
2. **Action Space:** Provide *a well-structured and unambiguous* set of actions—avoiding overlap like separate `read_databases` or `read_csvs`. Instead, import CSVs into the database.
## Example Good Action Design
- **Incremental:** Feed content in manageable chunks (500 lines or 1 page) instead of all at once.
- **Overview-zoom-in:** First provide high-level structure (table of contents, summary), then allow drilling into details (raw texts).
- **Parameterized/Programmable:** Instead of fixed actions, enable parameterized (columns to select) or programmable (SQL queries) actions, for example, to read CSV files.
- **Backtracking:** Let the agent undo the last step instead of restarting entirely, preserving progress when encountering errors or dead ends.
## Example: Search Agent
This agent:
1. Decides whether to search or answer
2. If searches, loops back to decide if more search needed
3. Answers when enough context gathered
```python
class DecideAction(Node):
def prep(self, shared):
context = shared.get("context", "No previous search")
query = shared["query"]
return query, context
def exec(self, inputs):
query, context = inputs
prompt = f"""
Given input: {query}
Previous search results: {context}
Should I: 1) Search web for more info 2) Answer with current knowledge
Output in yaml:
```yaml
action: search/answer
reason: why this action
search_term: search phrase if action is search
```"""
resp = call_llm(prompt)
yaml_str = resp.split("```yaml")[1].split("```")[0].strip()
result = yaml.safe_load(yaml_str)
assert isinstance(result, dict)
assert "action" in result
assert "reason" in result
assert result["action"] in ["search", "answer"]
if result["action"] == "search":
assert "search_term" in result
return result
def post(self, shared, prep_res, exec_res):
if exec_res["action"] == "search":
shared["search_term"] = exec_res["search_term"]
return exec_res["action"]
class SearchWeb(Node):
def prep(self, shared):
return shared["search_term"]
def exec(self, search_term):
return search_web(search_term)
def post(self, shared, prep_res, exec_res):
prev_searches = shared.get("context", [])
shared["context"] = prev_searches + [
{"term": shared["search_term"], "result": exec_res}
]
return "decide"
class DirectAnswer(Node):
def prep(self, shared):
return shared["query"], shared.get("context", "")
def exec(self, inputs):
query, context = inputs
return call_llm(f"Context: {context}\nAnswer: {query}")
def post(self, shared, prep_res, exec_res):
print(f"Answer: {exec_res}")
shared["answer"] = exec_res
# Connect nodes
decide = DecideAction()
search = SearchWeb()
answer = DirectAnswer()
decide - "search" >> search
decide - "answer" >> answer
search - "decide" >> decide # Loop back
flow = Flow(start=decide)
flow.run({"query": "Who won the Nobel Prize in Physics 2024?"})
```
================================================
File: docs/design_pattern/mapreduce.md
================================================
---
layout: default
title: "Map Reduce"
parent: "Design Pattern"
nav_order: 4
---
# Map Reduce
MapReduce is a design pattern suitable when you have either:
- Large input data (e.g., multiple files to process), or
- Large output data (e.g., multiple forms to fill)
and there is a logical way to break the task into smaller, ideally independent parts.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/mapreduce.png?raw=true" width="400"/>
</div>
You first break down the task using [BatchNode](../core_abstraction/batch.md) in the map phase, followed by aggregation in the reduce phase.
### Example: Document Summarization
```python
class SummarizeAllFiles(BatchNode):
def prep(self, shared):
files_dict = shared["files"] # e.g. 10 files
return list(files_dict.items()) # [("file1.txt", "aaa..."), ("file2.txt", "bbb..."), ...]
def exec(self, one_file):
filename, file_content = one_file
summary_text = call_llm(f"Summarize the following file:\n{file_content}")
return (filename, summary_text)
def post(self, shared, prep_res, exec_res_list):
shared["file_summaries"] = dict(exec_res_list)
class CombineSummaries(Node):
def prep(self, shared):
return shared["file_summaries"]
def exec(self, file_summaries):
# format as: "File1: summary\nFile2: summary...\n"
text_list = []
for fname, summ in file_summaries.items():
text_list.append(f"{fname} summary:\n{summ}\n")
big_text = "\n---\n".join(text_list)
return call_llm(f"Combine these file summaries into one final summary:\n{big_text}")
def post(self, shared, prep_res, final_summary):
shared["all_files_summary"] = final_summary
batch_node = SummarizeAllFiles()
combine_node = CombineSummaries()
batch_node >> combine_node
flow = Flow(start=batch_node)
shared = {
"files": {
"file1.txt": "Alice was beginning to get very tired of sitting by her sister...",
"file2.txt": "Some other interesting text ...",
# ...
}
}
flow.run(shared)
print("Individual Summaries:", shared["file_summaries"])
print("\nFinal Summary:\n", shared["all_files_summary"])
```
================================================
File: docs/design_pattern/rag.md
================================================
---
layout: default
title: "RAG"
parent: "Design Pattern"
nav_order: 3
---
# RAG (Retrieval Augmented Generation)
For certain LLM tasks like answering questions, providing relevant context is essential. One common architecture is a **two-stage** RAG pipeline:
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/rag.png?raw=true" width="400"/>
</div>
1. **Offline stage**: Preprocess and index documents ("building the index").
2. **Online stage**: Given a question, generate answers by retrieving the most relevant context.
---
## Stage 1: Offline Indexing
We create three Nodes:
1. `ChunkDocs` – [chunks](../utility_function/chunking.md) raw text.
2. `EmbedDocs` – [embeds](../utility_function/embedding.md) each chunk.
3. `StoreIndex` – stores embeddings into a [vector database](../utility_function/vector.md).
```python
class ChunkDocs(BatchNode):
def prep(self, shared):
# A list of file paths in shared["files"]. We process each file.
return shared["files"]
def exec(self, filepath):
# read file content. In real usage, do error handling.
with open(filepath, "r", encoding="utf-8") as f:
text = f.read()
# chunk by 100 chars each
chunks = []
size = 100
for i in range(0, len(text), size):
chunks.append(text[i : i + size])
return chunks
def post(self, shared, prep_res, exec_res_list):
# exec_res_list is a list of chunk-lists, one per file.
# flatten them all into a single list of chunks.
all_chunks = []
for chunk_list in exec_res_list:
all_chunks.extend(chunk_list)
shared["all_chunks"] = all_chunks
class EmbedDocs(BatchNode):
def prep(self, shared):
return shared["all_chunks"]
def exec(self, chunk):
return get_embedding(chunk)
def post(self, shared, prep_res, exec_res_list):
# Store the list of embeddings.
shared["all_embeds"] = exec_res_list
print(f"Total embeddings: {len(exec_res_list)}")
class StoreIndex(Node):
def prep(self, shared):
# We'll read all embeds from shared.
return shared["all_embeds"]
def exec(self, all_embeds):
# Create a vector index (faiss or other DB in real usage).
index = create_index(all_embeds)
return index
def post(self, shared, prep_res, index):
shared["index"] = index
# Wire them in sequence
chunk_node = ChunkDocs()
embed_node = EmbedDocs()
store_node = StoreIndex()
chunk_node >> embed_node >> store_node
OfflineFlow = Flow(start=chunk_node)
```
Usage example:
```python
shared = {
"files": ["doc1.txt", "doc2.txt"], # any text files
}
OfflineFlow.run(shared)
```
---
## Stage 2: Online Query & Answer
We have 3 nodes:
1. `EmbedQuery` – embeds the user’s question.
2. `RetrieveDocs` – retrieves top chunk from the index.
3. `GenerateAnswer` – calls the LLM with the question + chunk to produce the final answer.
```python
class EmbedQuery(Node):
def prep(self, shared):
return shared["question"]
def exec(self, question):
return get_embedding(question)
def post(self, shared, prep_res, q_emb):
shared["q_emb"] = q_emb
class RetrieveDocs(Node):
def prep(self, shared):
# We'll need the query embedding, plus the offline index/chunks
return shared["q_emb"], shared["index"], shared["all_chunks"]
def exec(self, inputs):
q_emb, index, chunks = inputs
I, D = search_index(index, q_emb, top_k=1)
best_id = I[0][0]
relevant_chunk = chunks[best_id]
return relevant_chunk
def post(self, shared, prep_res, relevant_chunk):
shared["retrieved_chunk"] = relevant_chunk
print("Retrieved chunk:", relevant_chunk[:60], "...")
class GenerateAnswer(Node):
def prep(self, shared):
return shared["question"], shared["retrieved_chunk"]
def exec(self, inputs):
question, chunk = inputs
prompt = f"Question: {question}\nContext: {chunk}\nAnswer:"
return call_llm(prompt)
def post(self, shared, prep_res, answer):
shared["answer"] = answer
print("Answer:", answer)
embed_qnode = EmbedQuery()
retrieve_node = RetrieveDocs()
generate_node = GenerateAnswer()
embed_qnode >> retrieve_node >> generate_node
OnlineFlow = Flow(start=embed_qnode)
```
Usage example:
```python
# Suppose we already ran OfflineFlow and have:
# shared["all_chunks"], shared["index"], etc.
shared["question"] = "Why do people like cats?"
OnlineFlow.run(shared)
# final answer in shared["answer"]
```
================================================
File: docs/design_pattern/structure.md
================================================
---
layout: default
title: "Structured Output"
parent: "Design Pattern"
nav_order: 5
---
# Structured Output
In many use cases, you may want the LLM to output a specific structure, such as a list or a dictionary with predefined keys.
There are several approaches to achieve a structured output:
- **Prompting** the LLM to strictly return a defined structure.
- Using LLMs that natively support **schema enforcement**.
- **Post-processing** the LLM's response to extract structured content.
In practice, **Prompting** is simple and reliable for modern LLMs.
### Example Use Cases
- Extracting Key Information
```yaml
product:
name: Widget Pro
price: 199.99
description: |
A high-quality widget designed for professionals.
Recommended for advanced users.
```
- Summarizing Documents into Bullet Points
```yaml
summary:
- This product is easy to use.
- It is cost-effective.
- Suitable for all skill levels.
```
- Generating Configuration Files
```yaml
server:
host: 127.0.0.1
port: 8080
ssl: true
```
## Prompt Engineering
When prompting the LLM to produce **structured** output:
1. **Wrap** the structure in code fences (e.g., `yaml`).
2. **Validate** that all required fields exist (and let `Node` handles retry).
### Example Text Summarization
```python
class SummarizeNode(Node):
def exec(self, prep_res):
# Suppose `prep_res` is the text to summarize.
prompt = f"""
Please summarize the following text as YAML, with exactly 3 bullet points
{prep_res}
Now, output:
```yaml
summary:
- bullet 1
- bullet 2
- bullet 3
```"""
response = call_llm(prompt)
yaml_str = response.split("```yaml")[1].split("```")[0].strip()
import yaml
structured_result = yaml.safe_load(yaml_str)
assert "summary" in structured_result
assert isinstance(structured_result["summary"], list)
return structured_result
```
> Besides using `assert` statements, another popular way to validate schemas is [Pydantic](https://github.com/pydantic/pydantic)
{: .note }
### Why YAML instead of JSON?
Current LLMs struggle with escaping. YAML is easier with strings since they don't always need quotes.
**In JSON**
```json
{
"dialogue": "Alice said: \"Hello Bob.\\nHow are you?\\nI am good.\""
}
```
- Every double quote inside the string must be escaped with `\"`.
- Each newline in the dialogue must be represented as `\n`.
**In YAML**
```yaml
dialogue: |
Alice said: "Hello Bob.
How are you?
I am good."
```
- No need to escape interior quotes—just place the entire text under a block literal (`|`).
- Newlines are naturally preserved without needing `\n`.
================================================
File: docs/design_pattern/workflow.md
================================================
---
layout: default
title: "Workflow"
parent: "Design Pattern"
nav_order: 2
---
# Workflow
Many real-world tasks are too complex for one LLM call. The solution is to **Task Decomposition**: decompose them into a [chain](../core_abstraction/flow.md) of multiple Nodes.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/workflow.png?raw=true" width="400"/>
</div>
> - You don't want to make each task **too coarse**, because it may be *too complex for one LLM call*.
> - You don't want to make each task **too granular**, because then *the LLM call doesn't have enough context* and results are *not consistent across nodes*.
>
> You usually need multiple *iterations* to find the *sweet spot*. If the task has too many *edge cases*, consider using [Agents](./agent.md).
{: .best-practice }
### Example: Article Writing
```python
class GenerateOutline(Node):
def prep(self, shared): return shared["topic"]
def exec(self, topic): return call_llm(f"Create a detailed outline for an article about {topic}")
def post(self, shared, prep_res, exec_res): shared["outline"] = exec_res
class WriteSection(Node):
def prep(self, shared): return shared["outline"]
def exec(self, outline): return call_llm(f"Write content based on this outline: {outline}")
def post(self, shared, prep_res, exec_res): shared["draft"] = exec_res
class ReviewAndRefine(Node):
def prep(self, shared): return shared["draft"]
def exec(self, draft): return call_llm(f"Review and improve this draft: {draft}")
def post(self, shared, prep_res, exec_res): shared["final_article"] = exec_res
# Connect nodes
outline = GenerateOutline()
write = WriteSection()
review = ReviewAndRefine()
outline >> write >> review
# Create and run flow
writing_flow = Flow(start=outline)
shared = {"topic": "AI Safety"}
writing_flow.run(shared)
```
For *dynamic cases*, consider using [Agents](./agent.md).
================================================
File: docs/utility_function/llm.md
================================================
---
layout: default
title: "LLM Wrapper"
parent: "Utility Function"
nav_order: 1
---
# LLM Wrappers
Check out libraries like [litellm](https://github.com/BerriAI/litellm).
Here, we provide some minimal example implementations:
1. OpenAI
```python
def call_llm(prompt):
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY_HERE")
r = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
)
return r.choices[0].message.content
# Example usage
call_llm("How are you?")
```
> Store the API key in an environment variable like OPENAI_API_KEY for security.
{: .best-practice }
2. Claude (Anthropic)
```python
def call_llm(prompt):
from anthropic import Anthropic
client = Anthropic(api_key="YOUR_API_KEY_HERE")
response = client.messages.create(
model="claude-2",
messages=[{"role": "user", "content": prompt}],
max_tokens=100
)
return response.content
```
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")
response = genai.generate_text(
model="models/text-bison-001",
prompt=prompt
)
return response.result
```
4. Azure (Azure OpenAI)
```python
def call_llm(prompt):
from openai import AzureOpenAI
client = AzureOpenAI(
azure_endpoint="https://<YOUR_RESOURCE_NAME>.openai.azure.com/",
api_key="YOUR_API_KEY_HERE",
api_version="2023-05-15"
)
r = client.chat.completions.create(
model="<YOUR_DEPLOYMENT_NAME>",
messages=[{"role": "user", "content": prompt}]
)
return r.choices[0].message.content
```
5. Ollama (Local LLM)
```python
def call_llm(prompt):
from ollama import chat
response = chat(
model="llama2",
messages=[{"role": "user", "content": prompt}]
)
return response.message.content
```
## Improvements
Feel free to enhance your `call_llm` function as needed. Here are examples:
- Handle chat history:
```python
def call_llm(messages):
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY_HERE")
r = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
return r.choices[0].message.content
```
- Add in-memory caching
```python
from functools import lru_cache
@lru_cache(maxsize=1000)
def call_llm(prompt):
# Your implementation here
pass
```
> ⚠️ Caching conflicts with Node retries, as retries yield the same result.
>
> To address this, you could use cached results only if not retried.
{: .warning }
```python
from functools import lru_cache
@lru_cache(maxsize=1000)
def cached_call(prompt):
pass
def call_llm(prompt, use_cache):
if use_cache:
return cached_call(prompt)
# Call the underlying function directly
return cached_call.__wrapped__(prompt)
class SummarizeNode(Node):
def exec(self, text):
return call_llm(f"Summarize: {text}", self.cur_retry==0)
```
- Enable logging:
```python
def call_llm(prompt):
import logging
logging.info(f"Prompt: {prompt}")
response = ... # Your implementation here
logging.info(f"Response: {response}")
return response
```
================================================
FILE: .dockerignore
================================================
# Byte-compiled / cache files
__pycache__/
*.py[cod]
*.pyo
*.pyd
# Virtual environments
venv/
env/
.venv/
.env/
# Distribution / packaging
*.egg-info/
build/
dist/
# Git and other VCS
.git/
.gitignore
# Editor files
*.swp
*.swo
*.bak
*.tmp
.DS_Store
.idea/
.vscode/
# Secrets (if you’re using .env for API keys etc.)
.env
================================================
FILE: .gitignore
================================================
# Dependencies
node_modules/
vendor/
.pnp/
.pnp.js
# Build outputs
dist/
build/
out/
*.pyc
__pycache__/
# Environment files
.env
.env.local
.env.*.local
.env.development
.env.test
.env.production
# Python virtual environments
.venv/
venv/
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# IDE - JetBrains
.idea/
*.iml
*.iws
*.ipr
# IDE - Eclipse
.project
.classpath
.settings/
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Operating System
.DS_Store
Thumbs.db
*.swp
*.swo
# Testing
coverage/
.nyc_output/
# Temporary files
*.tmp
*.temp
.cache/
# Compiled files
*.com
*.class
*.dll
*.exe
*.o
*.so
# Package files
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
# Database
*.sqlite
*.sqlite3
*.db
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# LLM cache
llm_cache.json
# Output files
output/
# uv manage
pyproject.toml
uv.lock
docs/*.pdf
docs/design-cn.md
================================================
FILE: .windsurfrules
================================================
---
layout: default
title: "Agentic Coding"
---
# Agentic Coding: Humans Design, Agents code!
> If you are an AI agents involved in building LLM Systems, read this guide **VERY, VERY** carefully! This is the most important chapter in the entire document. Throughout development, you should always (1) start with a small and simple solution, (2) design at a high level (`docs/design.md`) before implementation, and (3) frequently ask humans for feedback and clarification.
{: .warning }
## Agentic Coding Steps
Agentic Coding should be a collaboration between Human System Design and Agent Implementation:
| Steps | Human | AI | Comment |
|:-----------------------|:----------:|:---------:|:------------------------------------------------------------------------|
| 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. |
1. **Requirements**: Clarify the requirements for your project, and evaluate whether an AI system is a good fit.
- Understand AI systems' strengths and limitations:
- **Good for**: Routine tasks requiring common sense (filling forms, replying to emails)
- **Good for**: Creative tasks with well-defined inputs (building slides, writing SQL)
- **Not good for**: Ambiguous problems requiring complex decision-making (business strategy, startup planning)
- **Keep It User-Centric:** Explain the "problem" from the user's perspective rather than just listing features.
- **Balance complexity vs. impact**: Aim to deliver the highest value features with minimal complexity early.
2. **Flow Design**: Outline at a high level, describe how your AI system orchestrates nodes.
- Identify applicable design patterns (e.g., [Map Reduce](./design_pattern/mapreduce.md), [Agent](./design_pattern/agent.md), [RAG](./design_pattern/rag.md)).
- For each node in the flow, start with a high-level one-line description of what it does.
- If using **Map Reduce**, specify how to map (what to split) and how to reduce (how to combine).
- If using **Agent**, specify what are the inputs (context) and what are the possible actions.
- If using **RAG**, specify what to embed, noting that there's usually both offline (indexing) and online (retrieval) workflows.
- Outline the flow and draw it in a mermaid diagram. For example:
```mermaid
flowchart LR
start[Start] --> batch[Batch]
batch --> check[Check]
check -->|OK| process
check -->|Error| fix[Fix]
fix --> check
subgraph process[Process]
step1[Step 1] --> step2[Step 2]
end
process --> endNode[End]
```
- > **If Humans can't specify the flow, AI Agents can't automate it!** Before building an LLM system, thoroughly understand the problem and potential solution by manually solving example inputs to develop intuition.
{: .best-practice }
3. **Utilities**: Based on the Flow Design, identify and implement necessary utility functions.
- Think of your AI system as the brain. It needs a body—these *external utility functions*—to interact with the real world:
<div align="center"><img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/utility.png?raw=true" width="400"/></div>
- Reading inputs (e.g., retrieving Slack messages, reading emails)
- Writing outputs (e.g., generating reports, sending emails)
- Using external tools (e.g., calling LLMs, searching the web)
- **NOTE**: *LLM-based tasks* (e.g., summarizing text, analyzing sentiment) are **NOT** utility functions; rather, they are *core functions* internal in the AI system.
- For each utility function, implement it and write a simple test.
- Document their input/output, as well as why they are necessary. For example:
- `name`: `get_embedding` (`utils/get_embedding.py`)
- `input`: `str`
- `output`: a vector of 3072 floats
- `necessity`: Used by the second node to embed text
- Example utility implementation:
```python
# utils/call_llm.py
from openai import OpenAI
def call_llm(prompt):
client = OpenAI(api_key="YOUR_API_KEY_HERE")
r = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
)
return r.choices[0].message.content
if __name__ == "__main__":
prompt = "What is the meaning of life?"
print(call_llm(prompt))
```
- > **Sometimes, design Utilies 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 }
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](./core_abstraction/communication.md), so start with a shared store design:
- 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.
- Example shared store design:
```python
shared = {
"user": {
"id": "user123",
"context": { # Another nested dict
"weather": {"temp": 72, "condition": "sunny"},
"location": "San Francisco"
}
},
"results": {} # Empty dict to store outputs
}
```
- For each [Node](./core_abstraction/node.md), 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
- `post`: Write "embedding" to the shared store
5. **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.
- Add logging throughout the code to facilitate debugging.
7. **Optimization**:
- **Use Intuition**: For a quick initial evaluation, human intuition is often a good start.
- **Redesign Flow (Back to Step 3)**: Consider breaking down tasks further, introducing agentic decisions, or better managing input contexts.
- If your flow design is already solid, move on to micro-optimizations:
- **Prompt Engineering**: Use clear, specific instructions with examples to reduce ambiguity.
- **In-Context Learning**: Provide robust examples for tasks that are difficult to specify with instructions alone.
- > **You'll likely iterate a lot!** Expect to repeat Steps 3–6 hundreds of times.
>
> <div align="center"><img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/success.png?raw=true" width="400"/></div>
{: .best-practice }
8. **Reliability**
- **Node Retries**: Add checks in the node `exec` to ensure outputs meet requirements, and consider increasing `max_retries` and `wait` times.
- **Logging and Visualization**: Maintain logs of all attempts and visualize node results for easier debugging.
- **Self-Evaluation**: Add a separate node (powered by an LLM) to review outputs when results are uncertain.
## Example LLM Project File Structure
```
my_project/
├── main.py
├── nodes.py
├── flow.py
├── utils/
│ ├── __init__.py
│ ├── call_llm.py
│ └── search_web.py
├── requirements.txt
└── docs/
└── design.md
```
- **`docs/design.md`**: Contains project documentation for each step above. This should be *high-level* and *no-code*.
- **`utils/`**: Contains all utility functions.
- It's recommended to dedicate one Python file to each API call, for example `call_llm.py` or `search_web.py`.
- Each file should also include a `main()` function to try that API call
- **`nodes.py`**: Contains all the node definitions.
```python
# nodes.py
from pocketflow import Node
from utils.call_llm import call_llm
class GetQuestionNode(Node):
def exec(self, _):
# Get question directly from user input
user_question = input("Enter your question: ")
return user_question
def post(self, shared, prep_res, exec_res):
# Store the user's question
shared["question"] = exec_res
return "default" # Go to the next node
class AnswerNode(Node):
def prep(self, shared):
# Read question from shared
return shared["question"]
def exec(self, question):
# Call LLM to get the answer
return call_llm(question)
def post(self, shared, prep_res, exec_res):
# Store the answer in shared
shared["answer"] = exec_res
```
- **`flow.py`**: Implements functions that create flows by importing node definitions and connecting them.
```python
# flow.py
from pocketflow import Flow
from nodes import GetQuestionNode, AnswerNode
def create_qa_flow():
"""Create and return a question-answering flow."""
# Create nodes
get_question_node = GetQuestionNode()
answer_node = AnswerNode()
# Connect nodes in sequence
get_question_node >> answer_node
# Create flow starting with input node
return Flow(start=get_question_node)
```
- **`main.py`**: Serves as the project's entry point.
```python
# main.py
from flow import create_qa_flow
# Example main function
# Please replace this with your own main function
def main():
shared = {
"question": None, # Will be populated by GetQuestionNode from user input
"answer": None # Will be populated by AnswerNode
}
# Create the flow and run it
qa_flow = create_qa_flow()
qa_flow.run(shared)
print(f"Question: {shared['question']}")
print(f"Answer: {shared['answer']}")
if __name__ == "__main__":
main()
```
================================================
File: docs/index.md
================================================
---
layout: default
title: "Home"
nav_order: 1
---
# Pocket Flow
A [100-line](https://github.com/the-pocket/PocketFlow/blob/main/pocketflow/__init__.py) minimalist LLM framework for *Agents, Task Decomposition, RAG, etc*.
- **Lightweight**: Just the core graph abstraction in 100 lines. ZERO dependencies, and vendor lock-in.
- **Expressive**: Everything you love from larger frameworks—([Multi-](./design_pattern/multi_agent.html))[Agents](./design_pattern/agent.html), [Workflow](./design_pattern/workflow.html), [RAG](./design_pattern/rag.html), and more.
- **Agentic-Coding**: Intuitive enough for AI agents to help humans build complex LLM applications.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/meme.jpg?raw=true" width="400"/>
</div>
## Core Abstraction
We model the LLM workflow as a **Graph + Shared Store**:
- [Node](./core_abstraction/node.md) handles simple (LLM) tasks.
- [Flow](./core_abstraction/flow.md) connects nodes through **Actions** (labeled edges).
- [Shared Store](./core_abstraction/communication.md) enables communication between nodes within flows.
- [Batch](./core_abstraction/batch.md) nodes/flows allow for data-intensive tasks.
- [Async](./core_abstraction/async.md) nodes/flows allow waiting for asynchronous tasks.
- [(Advanced) Parallel](./core_abstraction/parallel.md) nodes/flows handle I/O-bound tasks.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/abstraction.png" width="500"/>
</div>
## Design Pattern
From there, it’s easy to implement popular design patterns:
- [Agent](./design_pattern/agent.md) autonomously makes decisions.
- [Workflow](./design_pattern/workflow.md) chains multiple tasks into pipelines.
- [RAG](./design_pattern/rag.md) integrates data retrieval with generation.
- [Map Reduce](./design_pattern/mapreduce.md) splits data tasks into Map and Reduce steps.
- [Structured Output](./design_pattern/structure.md) formats outputs consistently.
- [(Advanced) Multi-Agents](./design_pattern/multi_agent.md) coordinate multiple agents.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/design.png" width="500"/>
</div>
## Utility Function
We **do not** provide built-in utilities. Instead, we offer *examples*—please *implement your own*:
- [LLM Wrapper](./utility_function/llm.md)
- [Viz and Debug](./utility_function/viz.md)
- [Web Search](./utility_function/websearch.md)
- [Chunking](./utility_function/chunking.md)
- [Embedding](./utility_function/embedding.md)
- [Vector Databases](./utility_function/vector.md)
- [Text-to-Speech](./utility_function/text_to_speech.md)
**Why not built-in?**: I believe it's a *bad practice* for vendor-specific APIs in a general framework:
- *API Volatility*: Frequent changes lead to heavy maintenance for hardcoded APIs.
- *Flexibility*: You may want to switch vendors, use fine-tuned models, or run them locally.
- *Optimizations*: Prompt caching, batching, and streaming are easier without vendor lock-in.
## Ready to build your Apps?
Check out [Agentic Coding Guidance](./guide.md), the fastest way to develop LLM projects with Pocket Flow!
================================================
File: docs/core_abstraction/async.md
================================================
---
layout: default
title: "(Advanced) Async"
parent: "Core Abstraction"
nav_order: 5
---
# (Advanced) Async
**Async** Nodes implement `prep_async()`, `exec_async()`, `exec_fallback_async()`, and/or `post_async()`. This is useful for:
1. **prep_async()**: For *fetching/reading data (files, APIs, DB)* in an I/O-friendly way.
2. **exec_async()**: Typically used for async LLM calls.
3. **post_async()**: For *awaiting user feedback*, *coordinating across multi-agents* or any additional async steps after `exec_async()`.
**Note**: `AsyncNode` must be wrapped in `AsyncFlow`. `AsyncFlow` can also include regular (sync) nodes.
### Example
```python
class SummarizeThenVerify(AsyncNode):
async def prep_async(self, shared):
# Example: read a file asynchronously
doc_text = await read_file_async(shared["doc_path"])
return doc_text
async def exec_async(self, prep_res):
# Example: async LLM call
summary = await call_llm_async(f"Summarize: {prep_res}")
return summary
async def post_async(self, shared, prep_res, exec_res):
# Example: wait for user feedback
decision = await gather_user_feedback(exec_res)
if decision == "approve":
shared["summary"] = exec_res
return "approve"
return "deny"
summarize_node = SummarizeThenVerify()
final_node = Finalize()
# Define transitions
summarize_node - "approve" >> final_node
summarize_node - "deny" >> summarize_node # retry
flow = AsyncFlow(start=summarize_node)
async def main():
shared = {"doc_path": "document.txt"}
await flow.run_async(shared)
print("Final Summary:", shared.get("summary"))
asyncio.run(main())
```
================================================
File: docs/core_abstraction/batch.md
================================================
---
layout: default
title: "Batch"
parent: "Core Abstraction"
nav_order: 4
---
# Batch
**Batch** makes it easier to handle large inputs in one Node or **rerun** a Flow multiple times. Example use cases:
- **Chunk-based** processing (e.g., splitting large texts).
- **Iterative** processing over lists of input items (e.g., user queries, files, URLs).
## 1. BatchNode
A **BatchNode** extends `Node` but changes `prep()` and `exec()`:
- **`prep(shared)`**: returns an **iterable** (e.g., list, generator).
- **`exec(item)`**: called **once** per item in that iterable.
- **`post(shared, prep_res, exec_res_list)`**: after all items are processed, receives a **list** of results (`exec_res_list`) and returns an **Action**.
### Example: Summarize a Large File
```python
class MapSummaries(BatchNode):
def prep(self, shared):
# Suppose we have a big file; chunk it
content = shared["data"]
chunk_size = 10000
chunks = [content[i:i+chunk_size] for i in range(0, len(content), chunk_size)]
return chunks
def exec(self, chunk):
prompt = f"Summarize this chunk in 10 words: {chunk}"
summary = call_llm(prompt)
return summary
def post(self, shared, prep_res, exec_res_list):
combined = "\n".join(exec_res_list)
shared["summary"] = combined
return "default"
map_summaries = MapSummaries()
flow = Flow(start=map_summaries)
flow.run(shared)
```
---
## 2. BatchFlow
A **BatchFlow** runs a **Flow** multiple times, each time with different `params`. Think of it as a loop that replays the Flow for each parameter set.
### Example: Summarize Many Files
```python
class SummarizeAllFiles(BatchFlow):
def prep(self, shared):
# Return a list of param dicts (one per file)
filenames = list(shared["data"].keys()) # e.g., ["file1.txt", "file2.txt", ...]
return [{"filename": fn} for fn in filenames]
# Suppose we have a per-file Flow (e.g., load_file >> summarize >> reduce):
summarize_file = SummarizeFile(start=load_file)
# Wrap that flow into a BatchFlow:
summarize_all_files = SummarizeAllFiles(start=summarize_file)
summarize_all_files.run(shared)
```
### Under the Hood
1. `prep(shared)` returns a list of param dicts—e.g., `[{filename: "file1.txt"}, {filename: "file2.txt"}, ...]`.
2. The **BatchFlow** loops through each dict. For each one:
- It merges the dict with the BatchFlow’s own `params`.
- It calls `flow.run(shared)` using the merged result.
3. This means the sub-Flow is run **repeatedly**, once for every param dict.
---
## 3. Nested or Multi-Level Batches
You can nest a **BatchFlow** in another **BatchFlow**. For instance:
- **Outer** batch: returns a list of diretory param dicts (e.g., `{"directory": "/pathA"}`, `{"directory": "/pathB"}`, ...).
- **Inner** batch: returning a list of per-file param dicts.
At each level, **BatchFlow** merges its own param dict with the parent’s. By the time you reach the **innermost** node, the final `params` is the merged result of **all** parents in the chain. This way, a nested structure can keep track of the entire context (e.g., directory + file name) at once.
```python
class FileBatchFlow(BatchFlow):
def prep(self, shared):
directory = self.params["directory"]
# e.g., files = ["file1.txt", "file2.txt", ...]
files = [f for f in os.listdir(directory) if f.endswith(".txt")]
return [{"filename": f} for f in files]
class DirectoryBatchFlow(BatchFlow):
def prep(self, shared):
directories = [ "/path/to/dirA", "/path/to/dirB"]
return [{"directory": d} for d in directories]
# MapSummaries have params like {"directory": "/path/to/dirA", "filename": "file1.txt"}
inner_flow = FileBatchFlow(start=MapSummaries())
outer_flow = DirectoryBatchFlow(start=inner_flow)
```
================================================
File: docs/core_abstraction/communication.md
================================================
---
layout: default
title: "Communication"
parent: "Core Abstraction"
nav_order: 3
---
# Communication
Nodes and Flows **communicate** in 2 ways:
1. **Shared Store (for almost all the cases)**
- A global data structure (often an in-mem dict) that all nodes can read ( `prep()`) and write (`post()`).
- Great for data results, large content, or anything multiple nodes need.
- You shall design the data structure and populate it ahead.
- > **Separation of Concerns:** Use `Shared Store` for almost all cases to separate *Data Schema* from *Compute Logic*! This approach is both flexible and easy to manage, resulting in more maintainable code. `Params` is more a syntax sugar for [Batch](./batch.md).
{: .best-practice }
2. **Params (only for [Batch](./batch.md))**
- Each node has a local, ephemeral `params` dict passed in by the **parent Flow**, used as an identifier for tasks. Parameter keys and values shall be **immutable**.
- Good for identifiers like filenames or numeric IDs, in Batch mode.
If you know memory management, think of the **Shared Store** like a **heap** (shared by all function calls), and **Params** like a **stack** (assigned by the caller).
---
## 1. Shared Store
### Overview
A shared store is typically an in-mem dictionary, like:
```python
shared = {"data": {}, "summary": {}, "config": {...}, ...}
```
It can also contain local file handlers, DB connections, or a combination for persistence. We recommend deciding the data structure or DB schema first based on your app requirements.
### Example
```python
class LoadData(Node):
def post(self, shared, prep_res, exec_res):
# We write data to shared store
shared["data"] = "Some text content"
return None
class Summarize(Node):
def prep(self, shared):
# We read data from shared store
return shared["data"]
def exec(self, prep_res):
# Call LLM to summarize
prompt = f"Summarize: {prep_res}"
summary = call_llm(prompt)
return summary
def post(self, shared, prep_res, exec_res):
# We write summary to shared store
shared["summary"] = exec_res
return "default"
load_data = LoadData()
summarize = Summarize()
load_data >> summarize
flow = Flow(start=load_data)
shared = {}
flow.run(shared)
```
Here:
- `LoadData` writes to `shared["data"]`.
- `Summarize` reads from `shared["data"]`, summarizes, and writes to `shared["summary"]`.
---
## 2. Params
**Params** let you store *per-Node* or *per-Flow* config that doesn't need to live in the shared store. They are:
- **Immutable** during a Node's run cycle (i.e., they don't change mid-`prep->exec->post`).
- **Set** via `set_params()`.
- **Cleared** and updated each time a parent Flow calls it.
> Only set the uppermost Flow params because others will be overwritten by the parent Flow.
>
> If you need to set child node params, see [Batch](./batch.md).
{: .warning }
Typically, **Params** are identifiers (e.g., file name, page number). Use them to fetch the task you assigned or write to a specific part of the shared store.
### Example
```python
# 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, 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()
# 3) Set Node params directly (for testing)
node.set_params({"filename": "doc1.txt"})
node.run(shared)
# 4) Create Flow
flow = Flow(start=node)
# 5) Set Flow params (overwrites node params)
flow.set_params({"filename": "doc2.txt"})
flow.run(shared) # The node summarizes doc2, not doc1
```
================================================
File: docs/core_abstraction/flow.md
================================================
---
layout: default
title: "Flow"
parent: "Core Abstraction"
nav_order: 2
---
# Flow
A **Flow** orchestrates a graph of Nodes. You can chain Nodes in a sequence or create branching depending on the **Actions** returned from each Node's `post()`.
## 1. Action-based Transitions
Each Node's `post()` returns an **Action** string. By default, if `post()` doesn't return anything, we treat that as `"default"`.
You define transitions with the syntax:
1. **Basic default transition**: `node_a >> node_b`
This means if `node_a.post()` returns `"default"`, go to `node_b`.
(Equivalent to `node_a - "default" >> node_b`)
2. **Named action transition**: `node_a - "action_name" >> node_b`
This means if `node_a.post()` returns `"action_name"`, go to `node_b`.
It's possible to create loops, branching, or multi-step flows.
## 2. Creating a Flow
A **Flow** begins with a **start** node. You call `Flow(start=some_node)` to specify the entry point. When you call `flow.run(shared)`, it executes the start node, looks at its returned Action from `post()`, follows the transition, and continues until there's no next node.
### Example: Simple Sequence
Here's a minimal flow of two nodes in a chain:
```python
node_a >> node_b
flow = Flow(start=node_a)
flow.run(shared)
```
- When you run the flow, it executes `node_a`.
- Suppose `node_a.post()` returns `"default"`.
- The flow then sees `"default"` Action is linked to `node_b` and runs `node_b`.
- `node_b.post()` returns `"default"` but we didn't define `node_b >> something_else`. So the flow ends there.
### Example: Branching & Looping
Here's a simple expense approval flow that demonstrates branching and looping. The `ReviewExpense` node can return three possible Actions:
- `"approved"`: expense is approved, move to payment processing
- `"needs_revision"`: expense needs changes, send back for revision
- `"rejected"`: expense is denied, finish the process
We can wire them like this:
```python
# Define the flow connections
review - "approved" >> payment # If approved, process payment
review - "needs_revision" >> revise # If needs changes, go to revision
review - "rejected" >> finish # If rejected, finish the process
revise >> review # After revision, go back for another review
payment >> finish # After payment, finish the process
flow = Flow(start=review)
```
Let's see how it flows:
1. If `review.post()` returns `"approved"`, the expense moves to the `payment` node
2. If `review.post()` returns `"needs_revision"`, it goes to the `revise` node, which then loops back to `review`
3. If `review.post()` returns `"rejected"`, it moves to the `finish` node and stops
```mermaid
flowchart TD
review[Review Expense] -->|approved| payment[Process Payment]
review -->|needs_revision| revise[Revise Report]
review -->|rejected| finish[Finish Process]
revise --> review
payment --> finish
```
### Running Individual Nodes vs. Running a Flow
- `node.run(shared)`: Just runs that node alone (calls `prep->exec->post()`), returns an Action.
- `flow.run(shared)`: Executes from the start node, follows Actions to the next node, and so on until the flow can't continue.
> `node.run(shared)` **does not** proceed to the successor.
> This is mainly for debugging or testing a single node.
>
> Always use `flow.run(...)` in production to ensure the full pipeline runs correctly.
{: .warning }
## 3. Nested Flows
A **Flow** can act like a Node, which enables powerful composition patterns. This means you can:
1. Use a Flow as a Node within another Flow's transitions.
2. Combine multiple smaller Flows into a larger Flow for reuse.
3. Node `params` will be a merging of **all** parents' `params`.
### Flow's Node Methods
A **Flow** is also a **Node**, so it will run `prep()` and `post()`. However:
- It **won't** run `exec()`, as its main logic is to orchestrate its nodes.
- `post()` always receives `None` for `exec_res` and should instead get the flow execution results from the shared store.
### Basic Flow Nesting
Here's how to connect a flow to another node:
```python
# Create a sub-flow
node_a >> node_b
subflow = Flow(start=node_a)
# Connect it to another node
subflow >> node_c
# Create the parent flow
parent_flow = Flow(start=subflow)
```
When `parent_flow.run()` executes:
1. It starts `subflow`
2. `subflow` runs through its nodes (`node_a->node_b`)
3. After `subflow` completes, execution continues to `node_c`
### Example: Order Processing Pipeline
Here's a practical example that breaks down order processing into nested flows:
```python
# Payment processing sub-flow
validate_payment >> process_payment >> payment_confirmation
payment_flow = Flow(start=validate_payment)
# Inventory sub-flow
check_stock >> reserve_items >> update_inventory
inventory_flow = Flow(start=check_stock)
# Shipping sub-flow
create_label >> assign_carrier >> schedule_pickup
shipping_flow = Flow(start=create_label)
# Connect the flows into a main order pipeline
payment_flow >> inventory_flow >> shipping_flow
# Create the master flow
order_pipeline = Flow(start=payment_flow)
# Run the entire pipeline
order_pipeline.run(shared_data)
```
This creates a clean separation of concerns while maintaining a clear execution path:
```mermaid
flowchart LR
subgraph order_pipeline[Order Pipeline]
subgraph paymentFlow["Payment Flow"]
A[Validate Payment] --> B[Process Payment] --> C[Payment Confirmation]
end
subgraph inventoryFlow["Inventory Flow"]
D[Check Stock] --> E[Reserve Items] --> F[Update Inventory]
end
subgraph shippingFlow["Shipping Flow"]
G[Create Label] --> H[Assign Carrier] --> I[Schedule Pickup]
end
paymentFlow --> inventoryFlow
inventoryFlow --> shippingFlow
end
```
================================================
File: docs/core_abstraction/node.md
================================================
---
layout: default
title: "Node"
parent: "Core Abstraction"
nav_order: 1
---
# Node
A **Node** is the smallest building block. Each Node has 3 steps `prep->exec->post`:
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/node.png?raw=true" width="400"/>
</div>
1. `prep(shared)`
- **Read and preprocess data** from `shared` store.
- Examples: *query DB, read files, or serialize data into a string*.
- Return `prep_res`, which is used by `exec()` and `post()`.
2. `exec(prep_res)`
- **Execute compute logic**, with optional retries and error handling (below).
- Examples: *(mostly) LLM calls, remote APIs, tool use*.
- ⚠️ This shall be only for compute and **NOT** access `shared`.
- ⚠️ If retries enabled, ensure idempotent implementation.
- Return `exec_res`, which is passed to `post()`.
3. `post(shared, prep_res, exec_res)`
- **Postprocess and write data** back to `shared`.
- Examples: *update DB, change states, log results*.
- **Decide the next action** by returning a *string* (`action = "default"` if *None*).
> **Why 3 steps?** To enforce the principle of *separation of concerns*. The data storage and data processing are operated separately.
>
> All steps are *optional*. E.g., you can only implement `prep` and `post` if you just need to process data.
{: .note }
### Fault Tolerance & Retries
You can **retry** `exec()` if it raises an exception via two parameters when define the Node:
- `max_retries` (int): Max times to run `exec()`. The default is `1` (**no** retry).
- `wait` (int): The time to wait (in **seconds**) before next retry. By default, `wait=0` (no waiting).
`wait` is helpful when you encounter rate-limits or quota errors from your LLM provider and need to back off.
```python
my_node = SummarizeFile(max_retries=3, wait=10)
```
When an exception occurs in `exec()`, the Node automatically retries until:
- It either succeeds, or
- The Node has retried `max_retries - 1` times already and fails on the last attempt.
You can get the current retry times (0-based) from `self.cur_retry`.
```python
class RetryNode(Node):
def exec(self, prep_res):
print(f"Retry {self.cur_retry} times")
raise Exception("Failed")
```
### Graceful Fallback
To **gracefully handle** the exception (after all retries) rather than raising it, override:
```python
def exec_fallback(self, prep_res, exc):
raise exc
```
By default, it just re-raises exception. But you can return a fallback result instead, which becomes the `exec_res` passed to `post()`.
### Example: Summarize file
```python
class SummarizeFile(Node):
def prep(self, shared):
return shared["data"]
def exec(self, prep_res):
if not prep_res:
return "Empty file content"
prompt = f"Summarize this text in 10 words: {prep_res}"
summary = call_llm(prompt) # might fail
return summary
def exec_fallback(self, prep_res, exc):
# Provide a simple fallback instead of crashing
return "There was an error processing your request."
def post(self, shared, prep_res, exec_res):
shared["summary"] = exec_res
# Return "default" by not returning
summarize_node = SummarizeFile(max_retries=3)
# node.run() calls prep->exec->post
# If exec() fails, it retries up to 3 times before calling exec_fallback()
action_result = summarize_node.run(shared)
print("Action returned:", action_result) # "default"
print("Summary stored:", shared["summary"])
```
================================================
File: docs/core_abstraction/parallel.md
================================================
---
layout: default
title: "(Advanced) Parallel"
parent: "Core Abstraction"
nav_order: 6
---
# (Advanced) Parallel
**Parallel** Nodes and Flows let you run multiple **Async** Nodes and Flows **concurrently**—for example, summarizing multiple texts at once. This can improve performance by overlapping I/O and compute.
> Because of Python’s GIL, parallel nodes and flows can’t truly parallelize CPU-bound tasks (e.g., heavy numerical computations). However, they excel at overlapping I/O-bound work—like LLM calls, database queries, API requests, or file I/O.
{: .warning }
> - **Ensure Tasks Are Independent**: If each item depends on the output of a previous item, **do not** parallelize.
>
> - **Beware of Rate Limits**: Parallel calls can **quickly** trigger rate limits on LLM services. You may need a **throttling** mechanism (e.g., semaphores or sleep intervals).
>
> - **Consider Single-Node Batch APIs**: Some LLMs offer a **batch inference** API where you can send multiple prompts in a single call. This is more complex to implement but can be more efficient than launching many parallel requests and mitigates rate limits.
{: .best-practice }
## AsyncParallelBatchNode
Like **AsyncBatchNode**, but run `exec_async()` in **parallel**:
```python
class ParallelSummaries(AsyncParallelBatchNode):
async def prep_async(self, shared):
# e.g., multiple texts
return shared["texts"]
async def exec_async(self, text):
prompt = f"Summarize: {text}"
return await call_llm_async(prompt)
async def post_async(self, shared, prep_res, exec_res_list):
shared["summary"] = "\n\n".join(exec_res_list)
return "default"
node = ParallelSummaries()
flow = AsyncFlow(start=node)
```
## AsyncParallelBatchFlow
Parallel version of **BatchFlow**. Each iteration of the sub-flow runs **concurrently** using different parameters:
```python
class SummarizeMultipleFiles(AsyncParallelBatchFlow):
async def prep_async(self, shared):
return [{"filename": f} for f in shared["files"]]
sub_flow = AsyncFlow(start=LoadAndSummarizeFile())
parallel_flow = SummarizeMultipleFiles(start=sub_flow)
await parallel_flow.run_async(shared)
```
================================================
File: docs/design_pattern/agent.md
================================================
---
layout: default
title: "Agent"
parent: "Design Pattern"
nav_order: 1
---
# Agent
Agent is a powerful design pattern in which nodes can take dynamic actions based on the context.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/agent.png?raw=true" width="350"/>
</div>
## Implement Agent with Graph
1. **Context and Action:** Implement nodes that supply context and perform actions.
2. **Branching:** Use branching to connect each action node to an agent node. Use action to allow the agent to direct the [flow](../core_abstraction/flow.md) between nodes—and potentially loop back for multi-step.
3. **Agent Node:** Provide a prompt to decide action—for example:
```python
f"""
### CONTEXT
Task: {task_description}
Previous Actions: {previous_actions}
Current State: {current_state}
### ACTION SPACE
[1] search
Description: Use web search to get results
Parameters:
- query (str): What to search for
[2] answer
Description: Conclude based on the results
Parameters:
- result (str): Final answer to provide
### NEXT ACTION
Decide the next action based on the current context and available action space.
Return your response in the following format:
```yaml
thinking: |
<your step-by-step reasoning process>
action: <action_name>
parameters:
<parameter_name>: <parameter_value>
```"""
```
The core of building **high-performance** and **reliable** agents boils down to:
1. **Context Management:** Provide *relevant, minimal context.* For example, rather than including an entire chat history, retrieve the most relevant via [RAG](./rag.md). Even with larger context windows, LLMs still fall victim to ["lost in the middle"](https://arxiv.org/abs/2307.03172), overlooking mid-prompt content.
2. **Action Space:** Provide *a well-structured and unambiguous* set of actions—avoiding overlap like separate `read_databases` or `read_csvs`. Instead, import CSVs into the database.
## Example Good Action Design
- **Incremental:** Feed content in manageable chunks (500 lines or 1 page) instead of all at once.
- **Overview-zoom-in:** First provide high-level structure (table of contents, summary), then allow drilling into details (raw texts).
- **Parameterized/Programmable:** Instead of fixed actions, enable parameterized (columns to select) or programmable (SQL queries) actions, for example, to read CSV files.
- **Backtracking:** Let the agent undo the last step instead of restarting entirely, preserving progress when encountering errors or dead ends.
## Example: Search Agent
This agent:
1. Decides whether to search or answer
2. If searches, loops back to decide if more search needed
3. Answers when enough context gathered
```python
class DecideAction(Node):
def prep(self, shared):
context = shared.get("context", "No previous search")
query = shared["query"]
return query, context
def exec(self, inputs):
query, context = inputs
prompt = f"""
Given input: {query}
Previous search results: {context}
Should I: 1) Search web for more info 2) Answer with current knowledge
Output in yaml:
```yaml
action: search/answer
reason: why this action
search_term: search phrase if action is search
```"""
resp = call_llm(prompt)
yaml_str = resp.split("```yaml")[1].split("```")[0].strip()
result = yaml.safe_load(yaml_str)
assert isinstance(result, dict)
assert "action" in result
assert "reason" in result
assert result["action"] in ["search", "answer"]
if result["action"] == "search":
assert "search_term" in result
return result
def post(self, shared, prep_res, exec_res):
if exec_res["action"] == "search":
shared["search_term"] = exec_res["search_term"]
return exec_res["action"]
class SearchWeb(Node):
def prep(self, shared):
return shared["search_term"]
def exec(self, search_term):
return search_web(search_term)
def post(self, shared, prep_res, exec_res):
prev_searches = shared.get("context", [])
shared["context"] = prev_searches + [
{"term": shared["search_term"], "result": exec_res}
]
return "decide"
class DirectAnswer(Node):
def prep(self, shared):
return shared["query"], shared.get("context", "")
def exec(self, inputs):
query, context = inputs
return call_llm(f"Context: {context}\nAnswer: {query}")
def post(self, shared, prep_res, exec_res):
print(f"Answer: {exec_res}")
shared["answer"] = exec_res
# Connect nodes
decide = DecideAction()
search = SearchWeb()
answer = DirectAnswer()
decide - "search" >> search
decide - "answer" >> answer
search - "decide" >> decide # Loop back
flow = Flow(start=decide)
flow.run({"query": "Who won the Nobel Prize in Physics 2024?"})
```
================================================
File: docs/design_pattern/mapreduce.md
================================================
---
layout: default
title: "Map Reduce"
parent: "Design Pattern"
nav_order: 4
---
# Map Reduce
MapReduce is a design pattern suitable when you have either:
- Large input data (e.g., multiple files to process), or
- Large output data (e.g., multiple forms to fill)
and there is a logical way to break the task into smaller, ideally independent parts.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/mapreduce.png?raw=true" width="400"/>
</div>
You first break down the task using [BatchNode](../core_abstraction/batch.md) in the map phase, followed by aggregation in the reduce phase.
### Example: Document Summarization
```python
class SummarizeAllFiles(BatchNode):
def prep(self, shared):
files_dict = shared["files"] # e.g. 10 files
return list(files_dict.items()) # [("file1.txt", "aaa..."), ("file2.txt", "bbb..."), ...]
def exec(self, one_file):
filename, file_content = one_file
summary_text = call_llm(f"Summarize the following file:\n{file_content}")
return (filename, summary_text)
def post(self, shared, prep_res, exec_res_list):
shared["file_summaries"] = dict(exec_res_list)
class CombineSummaries(Node):
def prep(self, shared):
return shared["file_summaries"]
def exec(self, file_summaries):
# format as: "File1: summary\nFile2: summary...\n"
text_list = []
for fname, summ in file_summaries.items():
text_list.append(f"{fname} summary:\n{summ}\n")
big_text = "\n---\n".join(text_list)
return call_llm(f"Combine these file summaries into one final summary:\n{big_text}")
def post(self, shared, prep_res, final_summary):
shared["all_files_summary"] = final_summary
batch_node = SummarizeAllFiles()
combine_node = CombineSummaries()
batch_node >> combine_node
flow = Flow(start=batch_node)
shared = {
"files": {
"file1.txt": "Alice was beginning to get very tired of sitting by her sister...",
"file2.txt": "Some other interesting text ...",
# ...
}
}
flow.run(shared)
print("Individual Summaries:", shared["file_summaries"])
print("\nFinal Summary:\n", shared["all_files_summary"])
```
================================================
File: docs/design_pattern/rag.md
================================================
---
layout: default
title: "RAG"
parent: "Design Pattern"
nav_order: 3
---
# RAG (Retrieval Augmented Generation)
For certain LLM tasks like answering questions, providing relevant context is essential. One common architecture is a **two-stage** RAG pipeline:
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/rag.png?raw=true" width="400"/>
</div>
1. **Offline stage**: Preprocess and index documents ("building the index").
2. **Online stage**: Given a question, generate answers by retrieving the most relevant context.
---
## Stage 1: Offline Indexing
We create three Nodes:
1. `ChunkDocs` – [chunks](../utility_function/chunking.md) raw text.
2. `EmbedDocs` – [embeds](../utility_function/embedding.md) each chunk.
3. `StoreIndex` – stores embeddings into a [vector database](../utility_function/vector.md).
```python
class ChunkDocs(BatchNode):
def prep(self, shared):
# A list of file paths in shared["files"]. We process each file.
return shared["files"]
def exec(self, filepath):
# read file content. In real usage, do error handling.
with open(filepath, "r", encoding="utf-8") as f:
text = f.read()
# chunk by 100 chars each
chunks = []
size = 100
for i in range(0, len(text), size):
chunks.append(text[i : i + size])
return chunks
def post(self, shared, prep_res, exec_res_list):
# exec_res_list is a list of chunk-lists, one per file.
# flatten them all into a single list of chunks.
all_chunks = []
for chunk_list in exec_res_list:
all_chunks.extend(chunk_list)
shared["all_chunks"] = all_chunks
class EmbedDocs(BatchNode):
def prep(self, shared):
return shared["all_chunks"]
def exec(self, chunk):
return get_embedding(chunk)
def post(self, shared, prep_res, exec_res_list):
# Store the list of embeddings.
shared["all_embeds"] = exec_res_list
print(f"Total embeddings: {len(exec_res_list)}")
class StoreIndex(Node):
def prep(self, shared):
# We'll read all embeds from shared.
return shared["all_embeds"]
def exec(self, all_embeds):
# Create a vector index (faiss or other DB in real usage).
index = create_index(all_embeds)
return index
def post(self, shared, prep_res, index):
shared["index"] = index
# Wire them in sequence
chunk_node = ChunkDocs()
embed_node = EmbedDocs()
store_node = StoreIndex()
chunk_node >> embed_node >> store_node
OfflineFlow = Flow(start=chunk_node)
```
Usage example:
```python
shared = {
"files": ["doc1.txt", "doc2.txt"], # any text files
}
OfflineFlow.run(shared)
```
---
## Stage 2: Online Query & Answer
We have 3 nodes:
1. `EmbedQuery` – embeds the user’s question.
2. `RetrieveDocs` – retrieves top chunk from the index.
3. `GenerateAnswer` – calls the LLM with the question + chunk to produce the final answer.
```python
class EmbedQuery(Node):
def prep(self, shared):
return shared["question"]
def exec(self, question):
return get_embedding(question)
def post(self, shared, prep_res, q_emb):
shared["q_emb"] = q_emb
class RetrieveDocs(Node):
def prep(self, shared):
# We'll need the query embedding, plus the offline index/chunks
return shared["q_emb"], shared["index"], shared["all_chunks"]
def exec(self, inputs):
q_emb, index, chunks = inputs
I, D = search_index(index, q_emb, top_k=1)
best_id = I[0][0]
relevant_chunk = chunks[best_id]
return relevant_chunk
def post(self, shared, prep_res, relevant_chunk):
shared["retrieved_chunk"] = relevant_chunk
print("Retrieved chunk:", relevant_chunk[:60], "...")
class GenerateAnswer(Node):
def prep(self, shared):
return shared["question"], shared["retrieved_chunk"]
def exec(self, inputs):
question, chunk = inputs
prompt = f"Question: {question}\nContext: {chunk}\nAnswer:"
return call_llm(prompt)
def post(self, shared, prep_res, answer):
shared["answer"] = answer
print("Answer:", answer)
embed_qnode = EmbedQuery()
retrieve_node = RetrieveDocs()
generate_node = GenerateAnswer()
embed_qnode >> retrieve_node >> generate_node
OnlineFlow = Flow(start=embed_qnode)
```
Usage example:
```python
# Suppose we already ran OfflineFlow and have:
# shared["all_chunks"], shared["index"], etc.
shared["question"] = "Why do people like cats?"
OnlineFlow.run(shared)
# final answer in shared["answer"]
```
================================================
File: docs/design_pattern/structure.md
================================================
---
layout: default
title: "Structured Output"
parent: "Design Pattern"
nav_order: 5
---
# Structured Output
In many use cases, you may want the LLM to output a specific structure, such as a list or a dictionary with predefined keys.
There are several approaches to achieve a structured output:
- **Prompting** the LLM to strictly return a defined structure.
- Using LLMs that natively support **schema enforcement**.
- **Post-processing** the LLM's response to extract structured content.
In practice, **Prompting** is simple and reliable for modern LLMs.
### Example Use Cases
- Extracting Key Information
```yaml
product:
name: Widget Pro
price: 199.99
description: |
A high-quality widget designed for professionals.
Recommended for advanced users.
```
- Summarizing Documents into Bullet Points
```yaml
summary:
- This product is easy to use.
- It is cost-effective.
- Suitable for all skill levels.
```
- Generating Configuration Files
```yaml
server:
host: 127.0.0.1
port: 8080
ssl: true
```
## Prompt Engineering
When prompting the LLM to produce **structured** output:
1. **Wrap** the structure in code fences (e.g., `yaml`).
2. **Validate** that all required fields exist (and let `Node` handles retry).
### Example Text Summarization
```python
class SummarizeNode(Node):
def exec(self, prep_res):
# Suppose `prep_res` is the text to summarize.
prompt = f"""
Please summarize the following text as YAML, with exactly 3 bullet points
{prep_res}
Now, output:
```yaml
summary:
- bullet 1
- bullet 2
- bullet 3
```"""
response = call_llm(prompt)
yaml_str = response.split("```yaml")[1].split("```")[0].strip()
import yaml
structured_result = yaml.safe_load(yaml_str)
assert "summary" in structured_result
assert isinstance(structured_result["summary"], list)
return structured_result
```
> Besides using `assert` statements, another popular way to validate schemas is [Pydantic](https://github.com/pydantic/pydantic)
{: .note }
### Why YAML instead of JSON?
Current LLMs struggle with escaping. YAML is easier with strings since they don't always need quotes.
**In JSON**
```json
{
"dialogue": "Alice said: \"Hello Bob.\\nHow are you?\\nI am good.\""
}
```
- Every double quote inside the string must be escaped with `\"`.
- Each newline in the dialogue must be represented as `\n`.
**In YAML**
```yaml
dialogue: |
Alice said: "Hello Bob.
How are you?
I am good."
```
- No need to escape interior quotes—just place the entire text under a block literal (`|`).
- Newlines are naturally preserved without needing `\n`.
================================================
File: docs/design_pattern/workflow.md
================================================
---
layout: default
title: "Workflow"
parent: "Design Pattern"
nav_order: 2
---
# Workflow
Many real-world tasks are too complex for one LLM call. The solution is to **Task Decomposition**: decompose them into a [chain](../core_abstraction/flow.md) of multiple Nodes.
<div align="center">
<img src="https://github.com/the-pocket/PocketFlow/raw/main/assets/workflow.png?raw=true" width="400"/>
</div>
> - You don't want to make each task **too coarse**, because it may be *too complex for one LLM call*.
> - You don't want to make each task **too granular**, because then *the LLM call doesn't have enough context* and results are *not consistent across nodes*.
>
> You usually need multiple *iterations* to find the *sweet spot*. If the task has too many *edge cases*, consider using [Agents](./agent.md).
{: .best-practice }
### Example: Article Writing
```python
class GenerateOutline(Node):
def prep(self, shared): return shared["topic"]
def exec(self, topic): return call_llm(f"Create a detailed outline for an article about {topic}")
def post(self, shared, prep_res, exec_res): shared["outline"] = exec_res
class WriteSection(Node):
def prep(self, shared): return shared["outline"]
def exec(self, outline): return call_llm(f"Write content based on this outline: {outline}")
def post(self, shared, prep_res, exec_res): shared["draft"] = exec_res
class ReviewAndRefine(Node):
def prep(self, shared): return shared["draft"]
def exec(self, draft): return call_llm(f"Review and improve this draft: {draft}")
def post(self, shared, prep_res, exec_res): shared["final_article"] = exec_res
# Connect nodes
outline = GenerateOutline()
write = WriteSection()
review = ReviewAndRefine()
outline >> write >> review
# Create and run flow
writing_flow = Flow(start=outline)
shared = {"topic": "AI Safety"}
writing_flow.run(shared)
```
For *dynamic cases*, consider using [Agents](./agent.md).
================================================
File: docs/utility_function/llm.md
================================================
---
layout: default
title: "LLM Wrapper"
parent: "Utility Function"
nav_order: 1
---
# LLM Wrappers
Check out libraries like [litellm](https://github.com/BerriAI/litellm).
Here, we provide some minimal example implementations:
1. OpenAI
```python
def call_llm(prompt):
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY_HERE")
r = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
)
return r.choices[0].message.content
# Example usage
call_llm("How are you?")
```
> Store the API key in an environment variable like OPENAI_API_KEY for security.
{: .best-practice }
2. Claude (Anthropic)
```python
def call_llm(prompt):
from anthropic import Anthropic
client = Anthropic(api_key="YOUR_API_KEY_HERE")
response = client.messages.create(
model="claude-2",
messages=[{"role": "user", "content": prompt}],
max_tokens=100
)
return response.content
```
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")
response = genai.generate_text(
model="models/text-bison-001",
prompt=prompt
)
return response.result
```
4. Azure (Azure OpenAI)
```python
def call_llm(prompt):
from openai import AzureOpenAI
client = AzureOpenAI(
azure_endpoint="https://<YOUR_RESOURCE_NAME>.openai.azure.com/",
api_key="YOUR_API_KEY_HERE",
api_version="2023-05-15"
)
r = client.chat.completions.create(
model="<YOUR_DEPLOYMENT_NAME>",
messages=[{"role": "user", "content": prompt}]
)
return r.choices[0].message.content
```
5. Ollama (Local LLM)
```python
def call_llm(prompt):
from ollama import chat
response = chat(
model="llama2",
messages=[{"role": "user", "content": prompt}]
)
return response.message.content
```
## Improvements
Feel free to enhance your `call_llm` function as needed. Here are examples:
- Handle chat history:
```python
def call_llm(messages):
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY_HERE")
r = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
return r.choices[0].message.content
```
- Add in-memory caching
```python
from functools import lru_cache
@lru_cache(maxsize=1000)
def call_llm(prompt):
# Your implementation here
pass
```
> ⚠️ Caching conflicts with Node retries, as retries yield the same result.
>
> To address this, you could use cached results only if not retried.
{: .warning }
```python
from functools import lru_cache
@lru_cache(maxsize=1000)
def cached_call(prompt):
pass
def call_llm(prompt, use_cache):
if use_cache:
return cached_call(prompt)
# Call the underlying function directly
return cached_call.__wrapped__(prompt)
class SummarizeNode(Node):
def exec(self, text):
return call_llm(f"Summarize: {text}", self.cur_retry==0)
```
- Enable logging:
```python
def call_llm(prompt):
import logging
logging.info(f"Prompt: {prompt}")
response = ... # Your implementation here
logging.info(f"Response: {response}")
return response
```
================================================
FILE: Dockerfile
================================================
FROM python:3.10-slim
# update packages, install git and remove cache
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENTRYPOINT ["python", "main.py"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2025 Zachary Huang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<h1 align="center">Turns Codebase into Easy Tutorial with AI</h1>

<a href="https://discord.gg/hUHHE9Sa6T">
<img src="https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat">
</a>
> *Ever stared at a new codebase written by others feeling completely lost? This tutorial shows you how to build an AI agent that analyzes GitHub repositories and creates beginner-friendly tutorials explaining exactly how the code works.*
<p align="center">
<img
src="./assets/banner.png" width="800"
/>
</p>
This is a tutorial project of [Pocket Flow](https://github.com/The-Pocket/PocketFlow), a 100-line LLM framework. It crawls GitHub repositories and builds a knowledge base from the code. It analyzes entire codebases to identify core abstractions and how they interact, and transforms complex code into beginner-friendly tutorials with clear visualizations.
- Check out the [YouTube Development Tutorial](https://youtu.be/AFY67zOpbSo) for more!
- Check out the [Substack Post Tutorial](https://zacharyhuang.substack.com/p/ai-codebase-knowledge-builder-full) for more!
**🔸 🎉 Reached Hacker News Front Page** (April 2025) with >900 up‑votes: [Discussion »](https://news.ycombinator.com/item?id=43739456)
**🔸 🎊 Online Service Now Live!** (May 2025) Try our new online version at [https://code2tutorial.com/](https://code2tutorial.com/) – just paste a GitHub link, no installation needed!
## ⭐ Example Results for Popular GitHub Repositories!
<p align="center">
<img
src="./assets/example.png" width="600"
/>
</p>
🤯 All these tutorials are generated **entirely by AI** by crawling the GitHub repo!
- [AutoGen Core](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/AutoGen%20Core) - Build AI teams that talk, think, and solve problems together like coworkers!
- [Browser Use](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/Browser%20Use) - Let AI surf the web for you, clicking buttons and filling forms like a digital assistant!
- [Celery](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/Celery) - Supercharge your app with background tasks that run while you sleep!
- [Click](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/Click) - Turn Python functions into slick command-line tools with just a decorator!
- [Codex](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/Codex) - Turn plain English into working code with this AI terminal wizard!
- [Crawl4AI](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/Crawl4AI) - Train your AI to extract exactly what matters from any website!
- [CrewAI](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/CrewAI) - Assemble a dream team of AI specialists to tackle impossible problems!
- [DSPy](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/DSPy) - Build LLM apps like Lego blocks that optimize themselves!
- [FastAPI](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/FastAPI) - Create APIs at lightning speed with automatic docs that clients will love!
- [Flask](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/Flask) - Craft web apps with minimal code that scales from prototype to production!
- [Google A2A](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/Google%20A2A) - The universal language that lets AI agents collaborate across borders!
- [LangGraph](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/LangGraph) - Design AI agents as flowcharts where each step remembers what happened before!
- [LevelDB](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/LevelDB) - Store data at warp speed with Google's engine that powers blockchains!
- [MCP Python SDK](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/MCP%20Python%20SDK) - Build powerful apps that communicate through an elegant protocol without sweating the details!
- [NumPy Core](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/NumPy%20Core) - Master the engine behind data science that makes Python as fast as C!
- [OpenManus](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/OpenManus) - Build AI agents with digital brains that think, learn, and use tools just like humans do!
- [PocketFlow](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/PocketFlow) - 100-line LLM framework. Let Agents build Agents!
- [Pydantic Core](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/Pydantic%20Core) - Validate data at rocket speed with just Python type hints!
- [Requests](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/Requests) - Talk to the internet in Python with code so simple it feels like cheating!
- [SmolaAgents](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/SmolaAgents) - Build tiny AI agents that punch way above their weight class!
- Showcase Your AI-Generated Tutorials in [Discussions](https://github.com/The-Pocket/PocketFlow-Tutorial-Codebase-Knowledge/discussions)!
## 🚀 Getting Started
1. Clone this repository
```bash
git clone https://github.com/The-Pocket/PocketFlow-Tutorial-Codebase-Knowledge
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
4. Set up LLM in [`utils/call_llm.py`](./utils/call_llm.py) by providing credentials. To do so, you can put the values in a `.env` file. By default, you can use the AI Studio key with this client for Gemini Pro 2.5 by setting the `GEMINI_API_KEY` environment variable. If you want to use another LLM, you can set the `LLM_PROVIDER` environment variable (e.g. `XAI`), and then set the model, url, and API key (e.g. `XAI_MODEL`, `XAI_URL`,`XAI_API_KEY`). If using Ollama, the url is `http://localhost:11434/` and the API key can be omitted.
You can use your own models. We highly recommend the latest models with thinking capabilities (Claude 3.7 with thinking, O1). You can verify that it is correctly set up by running:
```bash
python utils/call_llm.py
```
5. Generate a complete codebase tutorial by running the main script:
```bash
# Analyze a GitHub repository
python main.py --repo https://github.com/username/repo --include "*.py" "*.js" --exclude "tests/*" --max-size 50000
# Or, analyze a local directory
python main.py --dir /path/to/your/codebase --include "*.py" --exclude "*test*"
# Or, generate a tutorial in Chinese
python main.py --repo https://github.com/username/repo --language "Chinese"
```
- `--repo` or `--dir` - Specify either a GitHub repo URL or a local directory path (required, mutually exclusive)
- `-n, --name` - Project name (optional, derived from URL/directory if omitted)
- `-t, --token` - GitHub token (or set GITHUB_TOKEN environment variable)
- `-o, --output` - Output directory (default: ./output)
- `-i, --include` - Files to include (e.g., "`*.py`" "`*.js`")
- `-e, --exclude` - Files to exclude (e.g., "`tests/*`" "`docs/*`")
- `-s, --max-size` - Maximum file size in bytes (default: 100KB)
- `--language` - Language for the generated tutorial (default: "english")
- `--max-abstractions` - Maximum number of abstractions to identify (default: 10)
- `--no-cache` - Disable LLM response caching (default: caching enabled)
The application will crawl the repository, analyze the codebase structure, generate tutorial content in the specified language, and save the output in the specified directory (default: ./output).
<details>
<summary> 🐳 <b>Running with Docker</b> </summary>
To run this project in a Docker container, you'll need to pass your API keys as environment variables.
1. Build the Docker image
```bash
docker build -t pocketflow-app .
```
2. Run the container
You'll need to provide your `GEMINI_API_KEY` for the LLM to function. If you're analyzing private GitHub repositories or want to avoid rate limits, also provide your `GITHUB_TOKEN`.
Mount a local directory to `/app/output` inside the container to access the generated tutorials on your host machine.
**Example for analyzing a public GitHub repository:**
```bash
docker run -it --rm \
-e GEMINI_API_KEY="YOUR_GEMINI_API_KEY_HERE" \
-v "$(pwd)/output_tutorials":/app/output \
pocketflow-app --repo https://github.com/username/repo
```
**Example for analyzing a local directory:**
```bash
docker run -it --rm \
-e GEMINI_API_KEY="YOUR_GEMINI_API_KEY_HERE" \
-v "/path/to/your/local_codebase":/app/code_to_analyze \
-v "$(pwd)/output_tutorials":/app/output \
pocketflow-app --dir /app/code_to_analyze
```
</details>
## 💡 Development Tutorial
- I built using [**Agentic Coding**](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to), the fastest development paradigm, where humans simply [design](docs/design.md) and agents [code](flow.py).
- The secret weapon is [Pocket Flow](https://github.com/The-Pocket/PocketFlow), a 100-line LLM framework that lets Agents (e.g., Cursor AI) build for you
- Check out the Step-by-step YouTube development tutorial:
<br>
<div align="center">
<a href="https://youtu.be/AFY67zOpbSo" target="_blank">
<img src="./assets/youtube_thumbnail.png" width="500" alt="Pocket Flow Codebase Tutorial" style="cursor: pointer;">
</a>
</div>
<br>
================================================
FILE: docs/AutoGen Core/01_agent.md
================================================
---
layout: default
title: "Agent"
parent: "AutoGen Core"
nav_order: 1
---
# Chapter 1: Agent - The Workers of AutoGen
Welcome to the AutoGen Core tutorial! We're excited to guide you through building powerful applications with autonomous agents.
## Motivation: Why Do We Need Agents?
Imagine you want to build an automated system to write blog posts. You might need one part of the system to research a topic and another part to write the actual post based on the research. How do you represent these different "workers" and make them talk to each other?
This is where the concept of an **Agent** comes in. In AutoGen Core, an `Agent` is the fundamental building block representing an actor or worker in your system. Think of it like an employee in an office.
## Key Concepts: Understanding Agents
Let's break down what makes an Agent:
1. **It's a Worker:** An Agent is designed to *do* things. This could be running calculations, calling a Large Language Model (LLM) like ChatGPT, using a tool (like a search engine), or managing a piece of data.
2. **It Has an Identity (`AgentId`):** Just like every employee has a name and a job title, every Agent needs a unique identity. This identity, called `AgentId`, has two parts:
* `type`: What kind of role does the agent have? (e.g., "researcher", "writer", "coder"). This helps organize agents.
* `key`: A unique name for this specific agent instance (e.g., "researcher-01", "amy-the-writer").
```python
# From: _agent_id.py
class AgentId:
def __init__(self, type: str, key: str) -> None:
# ... (validation checks omitted for brevity)
self._type = type
self._key = key
@property
def type(self) -> str:
return self._type
@property
def key(self) -> str:
return self._key
def __str__(self) -> str:
# Creates an id like "researcher/amy-the-writer"
return f"{self._type}/{self._key}"
```
This `AgentId` acts like the agent's address, allowing other agents (or the system) to send messages specifically to it.
3. **It Has Metadata (`AgentMetadata`):** Besides its core identity, an agent often has descriptive information.
* `type`: Same as in `AgentId`.
* `key`: Same as in `AgentId`.
* `description`: A human-readable explanation of what the agent does (e.g., "Researches topics using web search").
```python
# From: _agent_metadata.py
from typing import TypedDict
class AgentMetadata(TypedDict):
type: str
key: str
description: str
```
This metadata helps understand the agent's purpose within the system.
4. **It Communicates via Messages:** Agents don't work in isolation. They collaborate by sending and receiving messages. The primary way an agent receives work is through its `on_message` method. Think of this like the agent's inbox.
```python
# From: _agent.py (Simplified Agent Protocol)
from typing import Any, Mapping, Protocol
# ... other imports
class Agent(Protocol):
@property
def id(self) -> AgentId: ... # The agent's unique ID
async def on_message(self, message: Any, ctx: MessageContext) -> Any:
"""Handles an incoming message."""
# Agent's logic to process the message goes here
...
```
When an agent receives a message, `on_message` is called. The `message` contains the data or task, and `ctx` (MessageContext) provides extra information about the message (like who sent it). We'll cover `MessageContext` more later.
5. **It Can Remember Things (State):** Sometimes, an agent needs to remember information between tasks, like keeping notes on research progress. Agents can optionally implement `save_state` and `load_state` methods to store and retrieve their internal memory.
```python
# From: _agent.py (Simplified Agent Protocol)
class Agent(Protocol):
# ... other methods
async def save_state(self) -> Mapping[str, Any]:
"""Save the agent's internal memory."""
# Return a dictionary representing the state
...
async def load_state(self, state: Mapping[str, Any]) -> None:
"""Load the agent's internal memory."""
# Restore state from the dictionary
...
```
We'll explore state and memory in more detail in [Chapter 7: Memory](07_memory.md).
6. **Different Agent Types:** AutoGen Core provides base classes to make creating agents easier:
* `BaseAgent`: The fundamental class most agents inherit from. It provides common setup.
* `ClosureAgent`: A very quick way to create simple agents using just a function (like hiring a temp worker for a specific task defined on the spot).
* `RoutedAgent`: An agent that can automatically direct different types of messages to different internal handler methods (like a smart receptionist).
## Use Case Example: Researcher and Writer
Let's revisit our blog post example. We want a `Researcher` agent and a `Writer` agent.
**Goal:**
1. Tell the `Researcher` a topic (e.g., "AutoGen Agents").
2. The `Researcher` finds some facts (we'll keep it simple and just make them up for now).
3. The `Researcher` sends these facts to the `Writer`.
4. The `Writer` receives the facts and drafts a short post.
**Simplified Implementation Idea (using `ClosureAgent` for brevity):**
First, let's define the messages they might exchange:
```python
from dataclasses import dataclass
@dataclass
class ResearchTopic:
topic: str
@dataclass
class ResearchFacts:
topic: str
facts: list[str]
@dataclass
class DraftPost:
topic: str
draft: str
```
These are simple Python classes to hold the data being passed around.
Now, let's imagine defining the `Researcher` using a `ClosureAgent`. This agent will listen for `ResearchTopic` messages.
```python
# Simplified concept - requires AgentRuntime (Chapter 3) to actually run
async def researcher_logic(agent_context, message: ResearchTopic, msg_context):
print(f"Researcher received topic: {message.topic}")
# In a real scenario, this would involve searching, calling an LLM, etc.
# For now, we just make up facts.
facts = [f"Fact 1 about {message.topic}", f"Fact 2 about {message.topic}"]
print(f"Researcher found facts: {facts}")
# Find the Writer agent's ID (we assume we know it)
writer_id = AgentId(type="writer", key="blog_writer_1")
# Send the facts to the Writer
await agent_context.send_message(
message=ResearchFacts(topic=message.topic, facts=facts),
recipient=writer_id,
)
print("Researcher sent facts to Writer.")
# This agent doesn't return a direct reply
return None
```
This `researcher_logic` function defines *what* the researcher does when it gets a `ResearchTopic` message. It processes the topic, creates `ResearchFacts`, and uses `agent_context.send_message` to send them to the `writer` agent.
Similarly, the `Writer` agent would have its own logic:
```python
# Simplified concept - requires AgentRuntime (Chapter 3) to actually run
async def writer_logic(agent_context, message: ResearchFacts, msg_context):
print(f"Writer received facts for topic: {message.topic}")
# In a real scenario, this would involve LLM prompting
draft = f"Blog Post about {message.topic}:\n"
for fact in message.facts:
draft += f"- {fact}\n"
print(f"Writer drafted post:\n{draft}")
# Perhaps save the draft or send it somewhere else
# For now, we just print it. We don't send another message.
return None # Or maybe return a confirmation/result
```
This `writer_logic` function defines how the writer reacts to receiving `ResearchFacts`.
**Important:** To actually *run* these agents and make them communicate, we need the `AgentRuntime` (covered in [Chapter 3: AgentRuntime](03_agentruntime.md)) and the `Messaging System` (covered in [Chapter 2: Messaging System](02_messaging_system__topic___subscription_.md)). For now, focus on the *idea* that Agents are distinct workers defined by their logic (`on_message`) and identified by their `AgentId`.
## Under the Hood: How an Agent Gets a Message
While the full message delivery involves the `Messaging System` and `AgentRuntime`, let's look at the agent's role when it receives a message.
**Conceptual Flow:**
```mermaid
sequenceDiagram
participant Sender as Sender Agent
participant Runtime as AgentRuntime
participant Recipient as Recipient Agent
Sender->>+Runtime: send_message(message, recipient_id)
Runtime->>+Recipient: Locate agent by recipient_id
Runtime->>+Recipient: on_message(message, context)
Recipient->>Recipient: Process message using internal logic
alt Response Needed
Recipient->>-Runtime: Return response value
Runtime->>-Sender: Deliver response value
else No Response
Recipient->>-Runtime: Return None (or no return)
end
```
1. Some other agent (Sender) or the system decides to send a message to our agent (Recipient).
2. It tells the `AgentRuntime` (the manager): "Deliver this `message` to the agent with `recipient_id`".
3. The `AgentRuntime` finds the correct `Recipient` agent instance.
4. The `AgentRuntime` calls the `Recipient.on_message(message, context)` method.
5. The agent's internal logic inside `on_message` (or methods called by it, like in `RoutedAgent`) runs to process the message.
6. If the message requires a direct response (like an RPC call), the agent returns a value from `on_message`. If not (like a general notification or event), it might return `None`.
**Code Glimpse:**
The core definition is the `Agent` Protocol (`_agent.py`). It's like an interface or a contract – any class wanting to be an Agent *must* provide these methods.
```python
# From: _agent.py - The Agent blueprint (Protocol)
@runtime_checkable
class Agent(Protocol):
@property
def metadata(self) -> AgentMetadata: ...
@property
def id(self) -> AgentId: ...
async def on_message(self, message: Any, ctx: MessageContext) -> Any: ...
async def save_state(self) -> Mapping[str, Any]: ...
async def load_state(self, state: Mapping[str, Any]) -> None: ...
async def close(self) -> None: ...
```
Most agents you create will inherit from `BaseAgent` (`_base_agent.py`). It provides some standard setup:
```python
# From: _base_agent.py (Simplified)
class BaseAgent(ABC, Agent):
def __init__(self, description: str) -> None:
# Gets runtime & id from a special context when created by the runtime
# Raises error if you try to create it directly!
self._runtime: AgentRuntime = AgentInstantiationContext.current_runtime()
self._id: AgentId = AgentInstantiationContext.current_agent_id()
self._description = description
# ...
# This is the final version called by the runtime
@final
async def on_message(self, message: Any, ctx: MessageContext) -> Any:
# It calls the implementation method you need to write
return await self.on_message_impl(message, ctx)
# You MUST implement this in your subclass
@abstractmethod
async def on_message_impl(self, message: Any, ctx: MessageContext) -> Any: ...
# Helper to send messages easily
async def send_message(self, message: Any, recipient: AgentId, ...) -> Any:
# It just asks the runtime to do the actual sending
return await self._runtime.send_message(
message, sender=self.id, recipient=recipient, ...
)
# ... other methods like publish_message, save_state, load_state
```
Notice how `BaseAgent` handles getting its `id` and `runtime` during creation and provides a convenient `send_message` method that uses the runtime. When inheriting from `BaseAgent`, you primarily focus on implementing the `on_message_impl` method to define your agent's unique behavior.
## Next Steps
You now understand the core concept of an `Agent` in AutoGen Core! It's the fundamental worker unit with an identity, the ability to process messages, and optionally maintain state.
In the next chapters, we'll explore:
* [Chapter 2: Messaging System](02_messaging_system__topic___subscription_.md): How messages actually travel between agents.
* [Chapter 3: AgentRuntime](03_agentruntime.md): The manager responsible for creating, running, and connecting agents.
Let's continue building your understanding!
---
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)
================================================
FILE: docs/AutoGen Core/02_messaging_system__topic___subscription_.md
================================================
---
layout: default
title: "Messaging System"
parent: "AutoGen Core"
nav_order: 2
---
# Chapter 2: Messaging System (Topic & Subscription)
In [Chapter 1: Agent](01_agent.md), we learned about Agents as individual workers. But how do they coordinate when one agent doesn't know exactly *who* needs the information it produces? Imagine our Researcher finds some facts. Maybe the Writer needs them, but maybe a Fact-Checker agent or a Summary agent also needs them later. How can the Researcher just announce "Here are the facts!" without needing a specific mailing list?
This is where the **Messaging System**, specifically **Topics** and **Subscriptions**, comes in. It allows agents to broadcast messages to anyone interested, like posting on a company announcement board.
## Motivation: Broadcasting Information
Let's refine our blog post example:
1. The `Researcher` agent finds facts about "AutoGen Agents".
2. Instead of sending *directly* to the `Writer`, the `Researcher` **publishes** these facts to a general "research-results" **Topic**.
3. The `Writer` agent has previously told the system it's **subscribed** to the "research-results" Topic.
4. The system sees the new message on the Topic and delivers it to the `Writer` (and any other subscribers).
This way, the `Researcher` doesn't need to know who the `Writer` is, or even if a `Writer` exists! It just broadcasts the results. If we later add a `FactChecker` agent that also needs the results, it simply subscribes to the same Topic.
## Key Concepts: Topics and Subscriptions
Let's break down the components of this broadcasting system:
1. **Topic (`TopicId`): The Announcement Board**
* A `TopicId` represents a specific channel or category for messages. Think of it like the name of an announcement board (e.g., "Project Updates", "General Announcements").
* It has two main parts:
* `type`: What *kind* of event or information is this? (e.g., "research.completed", "user.request"). This helps categorize messages.
* `source`: *Where* or *why* did this event originate? Often, this relates to the specific task
gitextract_ufv5rlmg/
├── .clinerules
├── .cursorrules
├── .dockerignore
├── .gitignore
├── .windsurfrules
├── Dockerfile
├── LICENSE
├── README.md
├── docs/
│ ├── AutoGen Core/
│ │ ├── 01_agent.md
│ │ ├── 02_messaging_system__topic___subscription_.md
│ │ ├── 03_agentruntime.md
│ │ ├── 04_tool.md
│ │ ├── 05_chatcompletionclient.md
│ │ ├── 06_chatcompletioncontext.md
│ │ ├── 07_memory.md
│ │ ├── 08_component.md
│ │ └── index.md
│ ├── Browser Use/
│ │ ├── 01_agent.md
│ │ ├── 02_system_prompt.md
│ │ ├── 03_browsercontext.md
│ │ ├── 04_dom_representation.md
│ │ ├── 05_action_controller___registry.md
│ │ ├── 06_message_manager.md
│ │ ├── 07_data_structures__views_.md
│ │ ├── 08_telemetry_service.md
│ │ └── index.md
│ ├── Celery/
│ │ ├── 01_celery_app.md
│ │ ├── 02_configuration.md
│ │ ├── 03_task.md
│ │ ├── 04_broker_connection__amqp_.md
│ │ ├── 05_worker.md
│ │ ├── 06_result_backend.md
│ │ ├── 07_beat__scheduler_.md
│ │ ├── 08_canvas__signatures___primitives_.md
│ │ ├── 09_events.md
│ │ ├── 10_bootsteps.md
│ │ └── index.md
│ ├── Click/
│ │ ├── 01_command___group.md
│ │ ├── 02_decorators.md
│ │ ├── 03_parameter__option___argument_.md
│ │ ├── 04_paramtype.md
│ │ ├── 05_context.md
│ │ ├── 06_term_ui__terminal_user_interface_.md
│ │ ├── 07_click_exceptions.md
│ │ └── index.md
│ ├── Codex/
│ │ ├── 01_terminal_ui__ink_components_.md
│ │ ├── 02_input_handling__textbuffer_editor_.md
│ │ ├── 03_agent_loop.md
│ │ ├── 04_approval_policy___security.md
│ │ ├── 05_response___tool_call_handling.md
│ │ ├── 06_command_execution___sandboxing.md
│ │ ├── 07_configuration_management.md
│ │ ├── 08_single_pass_mode.md
│ │ └── index.md
│ ├── Crawl4AI/
│ │ ├── 01_asynccrawlerstrategy.md
│ │ ├── 02_asyncwebcrawler.md
│ │ ├── 03_crawlerrunconfig.md
│ │ ├── 04_contentscrapingstrategy.md
│ │ ├── 05_relevantcontentfilter.md
│ │ ├── 06_extractionstrategy.md
│ │ ├── 07_crawlresult.md
│ │ ├── 08_deepcrawlstrategy.md
│ │ ├── 09_cachecontext___cachemode.md
│ │ ├── 10_basedispatcher.md
│ │ └── index.md
│ ├── CrewAI/
│ │ ├── 01_crew.md
│ │ ├── 02_agent.md
│ │ ├── 03_task.md
│ │ ├── 04_tool.md
│ │ ├── 05_process.md
│ │ ├── 06_llm.md
│ │ ├── 07_memory.md
│ │ ├── 08_knowledge.md
│ │ └── index.md
│ ├── DSPy/
│ │ ├── 01_module___program.md
│ │ ├── 02_signature.md
│ │ ├── 03_example.md
│ │ ├── 04_predict.md
│ │ ├── 05_lm__language_model_client_.md
│ │ ├── 06_rm__retrieval_model_client_.md
│ │ ├── 07_evaluate.md
│ │ ├── 08_teleprompter___optimizer.md
│ │ ├── 09_adapter.md
│ │ ├── 10_settings.md
│ │ └── index.md
│ ├── FastAPI/
│ │ ├── 01_fastapi_application___routing.md
│ │ ├── 02_path_operations___parameter_declaration.md
│ │ ├── 03_data_validation___serialization__pydantic_.md
│ │ ├── 04_openapi___automatic_docs.md
│ │ ├── 05_dependency_injection.md
│ │ ├── 06_error_handling.md
│ │ ├── 07_security_utilities.md
│ │ ├── 08_background_tasks.md
│ │ └── index.md
│ ├── Flask/
│ │ ├── 01_application_object___flask__.md
│ │ ├── 02_routing_system.md
│ │ ├── 03_request_and_response_objects.md
│ │ ├── 04_templating__jinja2_integration_.md
│ │ ├── 05_context_globals___current_app____request____session____g__.md
│ │ ├── 06_configuration___config__.md
│ │ ├── 07_application_and_request_contexts.md
│ │ ├── 08_blueprints.md
│ │ └── index.md
│ ├── Google A2A/
│ │ ├── 01_agent_card.md
│ │ ├── 02_task.md
│ │ ├── 03_a2a_protocol___core_types.md
│ │ ├── 04_a2a_server_implementation.md
│ │ ├── 05_a2a_client_implementation.md
│ │ ├── 06_task_handling_logic__server_side_.md
│ │ ├── 07_streaming_communication__sse_.md
│ │ ├── 08_multi_agent_orchestration__host_agent_.md
│ │ ├── 09_demo_ui_application___service.md
│ │ └── index.md
│ ├── LangGraph/
│ │ ├── 01_graph___stategraph.md
│ │ ├── 02_nodes___pregelnode__.md
│ │ ├── 03_channels.md
│ │ ├── 04_control_flow_primitives___branch____send____interrupt__.md
│ │ ├── 05_pregel_execution_engine.md
│ │ ├── 06_checkpointer___basecheckpointsaver__.md
│ │ └── index.md
│ ├── LevelDB/
│ │ ├── 01_table___sstable___tablecache.md
│ │ ├── 02_memtable.md
│ │ ├── 03_write_ahead_log__wal____logwriter_logreader.md
│ │ ├── 04_dbimpl.md
│ │ ├── 05_writebatch.md
│ │ ├── 06_version___versionset.md
│ │ ├── 07_iterator.md
│ │ ├── 08_compaction.md
│ │ ├── 09_internalkey___dbformat.md
│ │ └── index.md
│ ├── MCP Python SDK/
│ │ ├── 01_cli___mcp__command_.md
│ │ ├── 02_fastmcp_server___fastmcp__.md
│ │ ├── 03_fastmcp_resources___resource____resourcemanager__.md
│ │ ├── 04_fastmcp_tools___tool____toolmanager__.md
│ │ ├── 05_fastmcp_prompts___prompt____promptmanager__.md
│ │ ├── 06_fastmcp_context___context__.md
│ │ ├── 07_mcp_protocol_types.md
│ │ ├── 08_client_server_sessions___clientsession____serversession__.md
│ │ ├── 09_communication_transports__stdio__sse__websocket__memory_.md
│ │ └── index.md
│ ├── NumPy Core/
│ │ ├── 01_ndarray__n_dimensional_array_.md
│ │ ├── 02_dtype__data_type_object_.md
│ │ ├── 03_ufunc__universal_function_.md
│ │ ├── 04_numeric_types___numerictypes__.md
│ │ ├── 05_array_printing___arrayprint__.md
│ │ ├── 06_multiarray_module.md
│ │ ├── 07_umath_module.md
│ │ ├── 08___array_function___protocol___overrides___overrides__.md
│ │ └── index.md
│ ├── OpenManus/
│ │ ├── 01_llm.md
│ │ ├── 02_message___memory.md
│ │ ├── 03_baseagent.md
│ │ ├── 04_tool___toolcollection.md
│ │ ├── 05_baseflow.md
│ │ ├── 06_schema.md
│ │ ├── 07_configuration__config_.md
│ │ ├── 08_dockersandbox.md
│ │ ├── 09_mcp__model_context_protocol_.md
│ │ └── index.md
│ ├── PocketFlow/
│ │ ├── 01_shared_state___shared__dictionary__.md
│ │ ├── 02_node___basenode____node____asyncnode___.md
│ │ ├── 03_actions___transitions_.md
│ │ ├── 04_flow___flow____asyncflow___.md
│ │ ├── 05_asynchronous_processing___asyncnode____asyncflow___.md
│ │ ├── 06_batch_processing___batchnode____batchflow____asyncparallelbatchnode___.md
│ │ ├── 07_a2a__agent_to_agent__communication_framework_.md
│ │ └── index.md
│ ├── Pydantic Core/
│ │ ├── 01_basemodel.md
│ │ ├── 02_fields__fieldinfo___field_function_.md
│ │ ├── 03_configuration__configdict___configwrapper_.md
│ │ ├── 04_custom_logic__decorators___annotated_helpers_.md
│ │ ├── 05_core_schema___validation_serialization.md
│ │ ├── 06_typeadapter.md
│ │ └── index.md
│ ├── Requests/
│ │ ├── 01_functional_api.md
│ │ ├── 02_request___response_models.md
│ │ ├── 03_session.md
│ │ ├── 04_cookie_jar.md
│ │ ├── 05_authentication_handlers.md
│ │ ├── 06_exception_hierarchy.md
│ │ ├── 07_transport_adapters.md
│ │ ├── 08_hook_system.md
│ │ └── index.md
│ ├── SmolaAgents/
│ │ ├── 01_multistepagent.md
│ │ ├── 02_model_interface.md
│ │ ├── 03_tool.md
│ │ ├── 04_agentmemory.md
│ │ ├── 05_prompttemplates.md
│ │ ├── 06_pythonexecutor.md
│ │ ├── 07_agenttype.md
│ │ ├── 08_agentlogger___monitor.md
│ │ └── index.md
│ ├── _config.yml
│ ├── design.md
│ └── index.md
├── flow.py
├── main.py
├── nodes.py
├── requirements.txt
└── utils/
├── __init__.py
├── call_llm.py
├── crawl_github_files.py
└── crawl_local_files.py
SYMBOL INDEX (35 symbols across 6 files)
FILE: flow.py
function create_tutorial_flow (line 12) | def create_tutorial_flow():
FILE: main.py
function main (line 39) | def main():
FILE: nodes.py
function get_content_for_indices (line 11) | def get_content_for_indices(files_data, indices):
class FetchRepo (line 22) | class FetchRepo(Node):
method prep (line 23) | def prep(self, shared):
method exec (line 51) | def exec(self, prep_res):
method post (line 80) | def post(self, shared, prep_res, exec_res):
class IdentifyAbstractions (line 84) | class IdentifyAbstractions(Node):
method prep (line 85) | def prep(self, shared):
method exec (line 118) | def exec(self, prep_res):
method post (line 234) | def post(self, shared, prep_res, exec_res):
class AnalyzeRelationships (line 240) | class AnalyzeRelationships(Node):
method prep (line 241) | def prep(self, shared):
method exec (line 289) | def exec(self, prep_res):
method post (line 404) | def post(self, shared, prep_res, exec_res):
class OrderChapters (line 410) | class OrderChapters(Node):
method prep (line 411) | def prep(self, shared):
method exec (line 454) | def exec(self, prep_res):
method post (line 532) | def post(self, shared, prep_res, exec_res):
class WriteChapters (line 537) | class WriteChapters(BatchNode):
method prep (line 538) | def prep(self, shared):
method exec (line 630) | def exec(self, item):
method post (line 745) | def post(self, shared, prep_res, exec_res_list):
class CombineTutorial (line 753) | class CombineTutorial(Node):
method prep (line 754) | def prep(self, shared):
method exec (line 854) | def exec(self, prep_res):
method post (line 878) | def post(self, shared, prep_res, exec_res):
FILE: utils/call_llm.py
function load_cache (line 29) | def load_cache():
function save_cache (line 38) | def save_cache(cache):
function get_llm_provider (line 46) | def get_llm_provider():
function _call_llm_provider (line 54) | def _call_llm_provider(prompt: str) -> str:
function call_llm (line 128) | def call_llm(prompt: str, use_cache: bool = True) -> str:
function _call_llm_gemini (line 161) | def _call_llm_gemini(prompt: str) -> str:
FILE: utils/crawl_github_files.py
function crawl_github_files (line 11) | def crawl_github_files(
FILE: utils/crawl_local_files.py
function crawl_local_files (line 6) | def crawl_local_files(
Condensed preview — 203 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,010K chars).
[
{
"path": ".clinerules",
"chars": 57244,
"preview": "---\nlayout: default\ntitle: \"Agentic Coding\"\n---\n\n# Agentic Coding: Humans Design, Agents code!\n\n> If you are an AI agent"
},
{
"path": ".cursorrules",
"chars": 57244,
"preview": "---\nlayout: default\ntitle: \"Agentic Coding\"\n---\n\n# Agentic Coding: Humans Design, Agents code!\n\n> If you are an AI agent"
},
{
"path": ".dockerignore",
"chars": 327,
"preview": "# Byte-compiled / cache files\n__pycache__/\n*.py[cod]\n*.pyo\n*.pyd\n\n# Virtual environments\nvenv/\nenv/\n.venv/\n.env/\n\n# Dist"
},
{
"path": ".gitignore",
"chars": 1032,
"preview": "# Dependencies\nnode_modules/\nvendor/\n.pnp/\n.pnp.js\n\n# Build outputs\ndist/\nbuild/\nout/\n*.pyc\n__pycache__/\n\n# Environment "
},
{
"path": ".windsurfrules",
"chars": 57244,
"preview": "---\nlayout: default\ntitle: \"Agentic Coding\"\n---\n\n# Agentic Coding: Humans Design, Agents code!\n\n> If you are an AI agent"
},
{
"path": "Dockerfile",
"chars": 281,
"preview": "FROM python:3.10-slim\n\n# update packages, install git and remove cache\nRUN apt-get update && apt-get install -y git && r"
},
{
"path": "LICENSE",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2025 Zachary Huang\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "README.md",
"chars": 9560,
"preview": "<h1 align=\"center\">Turns Codebase into Easy Tutorial with AI</h1>\n\n\"\nparent: \"Browser Use\"\nnav_order: 7\n---\n\n# Chapter 7: Data Structure"
},
{
"path": "docs/Browser Use/08_telemetry_service.md",
"chars": 14274,
"preview": "---\nlayout: default\ntitle: \"Telemetry Service\"\nparent: \"Browser Use\"\nnav_order: 8\n---\n\n# Chapter 8: Telemetry Service - "
},
{
"path": "docs/Browser Use/index.md",
"chars": 1877,
"preview": "---\nlayout: default\ntitle: \"Browser Use\"\nnav_order: 4\nhas_children: true\n---\n\n# Tutorial: Browser Use\n\n> This tutorial i"
},
{
"path": "docs/Celery/01_celery_app.md",
"chars": 13896,
"preview": "---\nlayout: default\ntitle: \"Celery App\"\nparent: \"Celery\"\nnav_order: 1\n---\n\n# Chapter 1: The Celery App - Your Task Headq"
},
{
"path": "docs/Celery/02_configuration.md",
"chars": 12693,
"preview": "---\nlayout: default\ntitle: \"Configuration\"\nparent: \"Celery\"\nnav_order: 2\n---\n\n# Chapter 2: Configuration - Telling Celer"
},
{
"path": "docs/Celery/03_task.md",
"chars": 14390,
"preview": "---\nlayout: default\ntitle: \"Task\"\nparent: \"Celery\"\nnav_order: 3\n---\n\n# Chapter 3: Task - The Job Description\n\nIn [Chapte"
},
{
"path": "docs/Celery/04_broker_connection__amqp_.md",
"chars": 10893,
"preview": "---\nlayout: default\ntitle: \"Broker Connection (AMQP)\"\nparent: \"Celery\"\nnav_order: 4\n---\n\n# Chapter 4: Broker Connection "
},
{
"path": "docs/Celery/05_worker.md",
"chars": 14663,
"preview": "---\nlayout: default\ntitle: \"Worker\"\nparent: \"Celery\"\nnav_order: 5\n---\n\n# Chapter 5: Worker - The Task Doer\n\nIn [Chapter "
},
{
"path": "docs/Celery/06_result_backend.md",
"chars": 17212,
"preview": "---\nlayout: default\ntitle: \"Result Backend\"\nparent: \"Celery\"\nnav_order: 6\n---\n\n# Chapter 6: Result Backend - Checking Yo"
},
{
"path": "docs/Celery/07_beat__scheduler_.md",
"chars": 16406,
"preview": "---\nlayout: default\ntitle: \"Beat (Scheduler)\"\nparent: \"Celery\"\nnav_order: 7\n---\n\n# Chapter 7: Beat (Scheduler) - Celery'"
},
{
"path": "docs/Celery/08_canvas__signatures___primitives_.md",
"chars": 18115,
"preview": "---\nlayout: default\ntitle: \"Canvas (Signatures & Primitives)\"\nparent: \"Celery\"\nnav_order: 8\n---\n\n# Chapter 8: Canvas (Si"
},
{
"path": "docs/Celery/09_events.md",
"chars": 16064,
"preview": "---\nlayout: default\ntitle: \"Events\"\nparent: \"Celery\"\nnav_order: 9\n---\n\n# Chapter 9: Events - Listening to Celery's Heart"
},
{
"path": "docs/Celery/10_bootsteps.md",
"chars": 13689,
"preview": "---\nlayout: default\ntitle: \"Bootsteps\"\nparent: \"Celery\"\nnav_order: 10\n---\n\n# Chapter 10: Bootsteps - How Celery Workers "
},
{
"path": "docs/Celery/index.md",
"chars": 1764,
"preview": "---\nlayout: default\ntitle: \"Celery\"\nnav_order: 5\nhas_children: true\n---\n\n# Tutorial: Celery\n\n> This tutorial is AI-gener"
},
{
"path": "docs/Click/01_command___group.md",
"chars": 9204,
"preview": "---\nlayout: default\ntitle: \"Command & Group\"\nparent: \"Click\"\nnav_order: 1\n---\n\n# Chapter 1: Commands and Groups: The Bui"
},
{
"path": "docs/Click/02_decorators.md",
"chars": 12638,
"preview": "---\nlayout: default\ntitle: \"Decorators\"\nparent: \"Click\"\nnav_order: 2\n---\n\n# Chapter 2: Decorators: Magic Wands for Your "
},
{
"path": "docs/Click/03_parameter__option___argument_.md",
"chars": 13473,
"preview": "---\nlayout: default\ntitle: \"Parameter (Option & Argument)\"\nparent: \"Click\"\nnav_order: 3\n---\n\n# Chapter 3: Parameter (Opt"
},
{
"path": "docs/Click/04_paramtype.md",
"chars": 12291,
"preview": "---\nlayout: default\ntitle: \"ParamType\"\nparent: \"Click\"\nnav_order: 4\n---\n\n# Chapter 4: ParamType - Checking and Convertin"
},
{
"path": "docs/Click/05_context.md",
"chars": 14136,
"preview": "---\nlayout: default\ntitle: \"Context\"\nparent: \"Click\"\nnav_order: 5\n---\n\n# Chapter 5: Context - The Command's Nervous Syst"
},
{
"path": "docs/Click/06_term_ui__terminal_user_interface_.md",
"chars": 12547,
"preview": "---\nlayout: default\ntitle: \"Term UI (Terminal User Interface)\"\nparent: \"Click\"\nnav_order: 6\n---\n\n# Chapter 6: Term UI (T"
},
{
"path": "docs/Click/07_click_exceptions.md",
"chars": 13892,
"preview": "---\nlayout: default\ntitle: \"Click Exceptions\"\nparent: \"Click\"\nnav_order: 7\n---\n\n# Chapter 7: Click Exceptions - Handling"
},
{
"path": "docs/Click/index.md",
"chars": 1437,
"preview": "---\nlayout: default\ntitle: \"Click\"\nnav_order: 6\nhas_children: true\n---\n\n# Tutorial: Click\n\n> This tutorial is AI-generat"
},
{
"path": "docs/Codex/01_terminal_ui__ink_components_.md",
"chars": 15553,
"preview": "---\nlayout: default\ntitle: \"Terminal UI (Ink Components)\"\nparent: \"Codex\"\nnav_order: 1\n---\n\n# Chapter 1: Terminal UI (In"
},
{
"path": "docs/Codex/02_input_handling__textbuffer_editor_.md",
"chars": 18406,
"preview": "---\nlayout: default\ntitle: \"Input Handling (TextBuffer/Editor)\"\nparent: \"Codex\"\nnav_order: 2\n---\n\n# Chapter 2: Input Han"
},
{
"path": "docs/Codex/03_agent_loop.md",
"chars": 18156,
"preview": "---\nlayout: default\ntitle: \"Agent Loop\"\nparent: \"Codex\"\nnav_order: 3\n---\n\n# Chapter 3: Agent Loop\n\nIn the [previous chap"
},
{
"path": "docs/Codex/04_approval_policy___security.md",
"chars": 13912,
"preview": "---\nlayout: default\ntitle: \"Approval Policy & Security\"\nparent: \"Codex\"\nnav_order: 4\n---\n\n# Chapter 4: Approval Policy &"
},
{
"path": "docs/Codex/05_response___tool_call_handling.md",
"chars": 17270,
"preview": "---\nlayout: default\ntitle: \"Response & Tool Call Handling\"\nparent: \"Codex\"\nnav_order: 5\n---\n\n# Chapter 5: Response & Too"
},
{
"path": "docs/Codex/06_command_execution___sandboxing.md",
"chars": 18173,
"preview": "---\nlayout: default\ntitle: \"Command Execution & Sandboxing\"\nparent: \"Codex\"\nnav_order: 6\n---\n\n# Chapter 6: Command Execu"
},
{
"path": "docs/Codex/07_configuration_management.md",
"chars": 14361,
"preview": "---\nlayout: default\ntitle: \"Configuration Management\"\nparent: \"Codex\"\nnav_order: 7\n---\n\n# Chapter 7: Configuration Manag"
},
{
"path": "docs/Codex/08_single_pass_mode.md",
"chars": 17593,
"preview": "---\nlayout: default\ntitle: \"Single-Pass Mode\"\nparent: \"Codex\"\nnav_order: 8\n---\n\n# Chapter 8: Single-Pass Mode\n\nIn the [p"
},
{
"path": "docs/Codex/index.md",
"chars": 1531,
"preview": "---\nlayout: default\ntitle: \"Codex\"\nnav_order: 5\nhas_children: true\n---\n\n# Tutorial: Codex\n\n> This tutorial is AI-generat"
},
{
"path": "docs/Crawl4AI/01_asynccrawlerstrategy.md",
"chars": 12089,
"preview": "---\nlayout: default\ntitle: \"AsyncCrawlerStrategy\"\nparent: \"Crawl4AI\"\nnav_order: 1\n---\n\n# Chapter 1: How We Fetch Webpage"
},
{
"path": "docs/Crawl4AI/02_asyncwebcrawler.md",
"chars": 17965,
"preview": "---\nlayout: default\ntitle: \"AsyncWebCrawler\"\nparent: \"Crawl4AI\"\nnav_order: 2\n---\n\n# Chapter 2: Meet the General Manager "
},
{
"path": "docs/Crawl4AI/03_crawlerrunconfig.md",
"chars": 13863,
"preview": "---\nlayout: default\ntitle: \"CrawlerRunConfig\"\nparent: \"Crawl4AI\"\nnav_order: 3\n---\n\n# Chapter 3: Giving Instructions - Cr"
},
{
"path": "docs/Crawl4AI/04_contentscrapingstrategy.md",
"chars": 16190,
"preview": "---\nlayout: default\ntitle: \"ContentScrapingStrategy\"\nparent: \"Crawl4AI\"\nnav_order: 4\n---\n\n# Chapter 4: Cleaning Up the M"
},
{
"path": "docs/Crawl4AI/05_relevantcontentfilter.md",
"chars": 21808,
"preview": "---\nlayout: default\ntitle: \"RelevantContentFilter\"\nparent: \"Crawl4AI\"\nnav_order: 5\n---\n\n# Chapter 5: Focusing on What Ma"
},
{
"path": "docs/Crawl4AI/06_extractionstrategy.md",
"chars": 23699,
"preview": "---\nlayout: default\ntitle: \"ExtractionStrategy\"\nparent: \"Crawl4AI\"\nnav_order: 6\n---\n\n# Chapter 6: Getting Specific Data "
},
{
"path": "docs/Crawl4AI/07_crawlresult.md",
"chars": 16989,
"preview": "---\nlayout: default\ntitle: \"CrawlResult\"\nparent: \"Crawl4AI\"\nnav_order: 7\n---\n\n# Chapter 7: Understanding the Results - C"
},
{
"path": "docs/Crawl4AI/08_deepcrawlstrategy.md",
"chars": 20730,
"preview": "---\nlayout: default\ntitle: \"DeepCrawlStrategy\"\nparent: \"Crawl4AI\"\nnav_order: 8\n---\n\n# Chapter 8: Exploring Websites - De"
},
{
"path": "docs/Crawl4AI/09_cachecontext___cachemode.md",
"chars": 17312,
"preview": "---\nlayout: default\ntitle: \"CacheContext & CacheMode\"\nparent: \"Crawl4AI\"\nnav_order: 9\n---\n\n# Chapter 9: Smart Fetching w"
},
{
"path": "docs/Crawl4AI/10_basedispatcher.md",
"chars": 19503,
"preview": "---\nlayout: default\ntitle: \"BaseDispatcher\"\nparent: \"Crawl4AI\"\nnav_order: 10\n---\n\n# Chapter 10: Orchestrating the Crawl "
},
{
"path": "docs/Crawl4AI/index.md",
"chars": 1863,
"preview": "---\nlayout: default\ntitle: \"Crawl4AI\"\nnav_order: 7\nhas_children: true\n---\n\n# Tutorial: Crawl4AI\n\n> This tutorial is AI-g"
},
{
"path": "docs/CrewAI/01_crew.md",
"chars": 10932,
"preview": "---\nlayout: default\ntitle: \"Crew\"\nparent: \"CrewAI\"\nnav_order: 1\n---\n\n# Chapter 1: Crew - Your AI Team Manager\n\nWelcome t"
},
{
"path": "docs/CrewAI/02_agent.md",
"chars": 11238,
"preview": "---\nlayout: default\ntitle: \"Agent\"\nparent: \"CrewAI\"\nnav_order: 2\n---\n\n# Chapter 2: Agent - Your Specialized AI Worker\n\nI"
},
{
"path": "docs/CrewAI/03_task.md",
"chars": 15164,
"preview": "---\nlayout: default\ntitle: \"Task\"\nparent: \"CrewAI\"\nnav_order: 3\n---\n\n# Chapter 3: Task - Defining the Work\n\nIn [Chapter "
},
{
"path": "docs/CrewAI/04_tool.md",
"chars": 16581,
"preview": "---\nlayout: default\ntitle: \"Tool\"\nparent: \"CrewAI\"\nnav_order: 4\n---\n\n# Chapter 4: Tool - Equipping Your Agents\n\nIn [Chap"
},
{
"path": "docs/CrewAI/05_process.md",
"chars": 16018,
"preview": "---\nlayout: default\ntitle: \"Process\"\nparent: \"CrewAI\"\nnav_order: 5\n---\n\n# Chapter 5: Process - Orchestrating the Workflo"
},
{
"path": "docs/CrewAI/06_llm.md",
"chars": 18171,
"preview": "---\nlayout: default\ntitle: \"LLM\"\nparent: \"CrewAI\"\nnav_order: 6\n---\n\n# Chapter 6: LLM - The Agent's Brain\n\nIn the [previo"
},
{
"path": "docs/CrewAI/07_memory.md",
"chars": 13785,
"preview": "---\nlayout: default\ntitle: \"Memory\"\nparent: \"CrewAI\"\nnav_order: 7\n---\n\n# Chapter 7: Memory - Giving Your Crew Recall\n\nIn"
},
{
"path": "docs/CrewAI/08_knowledge.md",
"chars": 16728,
"preview": "---\nlayout: default\ntitle: \"Knowledge\"\nparent: \"CrewAI\"\nnav_order: 8\n---\n\n# Chapter 8: Knowledge - Providing External In"
},
{
"path": "docs/CrewAI/index.md",
"chars": 1327,
"preview": "---\nlayout: default\ntitle: \"CrewAI\"\nnav_order: 8\nhas_children: true\n---\n\n# Tutorial: CrewAI\n\n> This tutorial is AI-gener"
},
{
"path": "docs/DSPy/01_module___program.md",
"chars": 13058,
"preview": "---\nlayout: default\ntitle: \"Module & Program\"\nparent: \"DSPy\"\nnav_order: 1\n---\n\n# Chapter 1: Modules and Programs: Buildi"
},
{
"path": "docs/DSPy/02_signature.md",
"chars": 10330,
"preview": "---\nlayout: default\ntitle: \"Signature\"\nparent: \"DSPy\"\nnav_order: 2\n---\n\n# Chapter 2: Signatures - Defining the Task\n\nIn "
},
{
"path": "docs/DSPy/03_example.md",
"chars": 11543,
"preview": "---\nlayout: default\ntitle: \"Example\"\nparent: \"DSPy\"\nnav_order: 3\n---\n\n# Chapter 3: Example - Your Data Points\n\nIn [Chapt"
},
{
"path": "docs/DSPy/04_predict.md",
"chars": 12788,
"preview": "---\nlayout: default\ntitle: \"Predict\"\nparent: \"DSPy\"\nnav_order: 4\n---\n\n# Chapter 4: Predict - The Basic LM Caller\n\nIn [Ch"
},
{
"path": "docs/DSPy/05_lm__language_model_client_.md",
"chars": 16587,
"preview": "---\nlayout: default\ntitle: \"LM (Language Model Client)\"\nparent: \"DSPy\"\nnav_order: 5\n---\n\n# Chapter 5: LM (Language Model"
},
{
"path": "docs/DSPy/06_rm__retrieval_model_client_.md",
"chars": 18879,
"preview": "---\nlayout: default\ntitle: \"RM (Retrieval Model Client)\"\nparent: \"DSPy\"\nnav_order: 6\n---\n\n# Chapter 6: RM (Retrieval Mod"
},
{
"path": "docs/DSPy/07_evaluate.md",
"chars": 17190,
"preview": "---\nlayout: default\ntitle: \"Evaluate\"\nparent: \"DSPy\"\nnav_order: 7\n---\n\n# Chapter 7: Evaluate - Grading Your Program\n\nIn "
},
{
"path": "docs/DSPy/08_teleprompter___optimizer.md",
"chars": 17159,
"preview": "---\nlayout: default\ntitle: \"Teleprompter & Optimizer\"\nparent: \"DSPy\"\nnav_order: 8\n---\n\n# Chapter 8: Teleprompter / Optim"
},
{
"path": "docs/DSPy/09_adapter.md",
"chars": 17329,
"preview": "---\nlayout: default\ntitle: \"Adapter\"\nparent: \"DSPy\"\nnav_order: 9\n---\n\n# Chapter 9: Adapter - The Universal Translator\n\nW"
},
{
"path": "docs/DSPy/10_settings.md",
"chars": 16940,
"preview": "---\nlayout: default\ntitle: \"Settings\"\nparent: \"DSPy\"\nnav_order: 10\n---\n\n# Chapter 10: Settings - Your Program's Control "
},
{
"path": "docs/DSPy/index.md",
"chars": 1726,
"preview": "---\nlayout: default\ntitle: \"DSPy\"\nnav_order: 9\nhas_children: true\n---\n\n# Tutorial: DSPy\n\n> This tutorial is AI-generated"
},
{
"path": "docs/FastAPI/01_fastapi_application___routing.md",
"chars": 12650,
"preview": "---\nlayout: default\ntitle: \"FastAPI Application & Routing\"\nparent: \"FastAPI\"\nnav_order: 1\n---\n\n# Chapter 1: FastAPI Appl"
},
{
"path": "docs/FastAPI/02_path_operations___parameter_declaration.md",
"chars": 20921,
"preview": "---\nlayout: default\ntitle: \"Path Operations & Parameter Declaration\"\nparent: \"FastAPI\"\nnav_order: 2\n---\n\n# Chapter 2: Pa"
},
{
"path": "docs/FastAPI/03_data_validation___serialization__pydantic_.md",
"chars": 20271,
"preview": "---\nlayout: default\ntitle: \"Data Validation & Serialization (Pydantic)\"\nparent: \"FastAPI\"\nnav_order: 3\n---\n\n# Chapter 3:"
},
{
"path": "docs/FastAPI/04_openapi___automatic_docs.md",
"chars": 16646,
"preview": "---\nlayout: default\ntitle: \"OpenAPI & Automatic Docs\"\nparent: \"FastAPI\"\nnav_order: 4\n---\n\n# Chapter 4: OpenAPI & Automat"
},
{
"path": "docs/FastAPI/05_dependency_injection.md",
"chars": 19851,
"preview": "---\nlayout: default\ntitle: \"Dependency Injection\"\nparent: \"FastAPI\"\nnav_order: 5\n---\n\n# Chapter 5: Dependency Injection\n"
},
{
"path": "docs/FastAPI/06_error_handling.md",
"chars": 16626,
"preview": "---\nlayout: default\ntitle: \"Error Handling\"\nparent: \"FastAPI\"\nnav_order: 6\n---\n\n# Chapter 6: Error Handling\n\nWelcome bac"
},
{
"path": "docs/FastAPI/07_security_utilities.md",
"chars": 22607,
"preview": "---\nlayout: default\ntitle: \"Security Utilities\"\nparent: \"FastAPI\"\nnav_order: 7\n---\n\n# Chapter 7: Security Utilities\n\nHi "
},
{
"path": "docs/FastAPI/08_background_tasks.md",
"chars": 11500,
"preview": "---\nlayout: default\ntitle: \"Background Tasks\"\nparent: \"FastAPI\"\nnav_order: 8\n---\n\n# Chapter 8: Background Tasks\n\nWelcome"
},
{
"path": "docs/FastAPI/index.md",
"chars": 1563,
"preview": "---\nlayout: default\ntitle: \"FastAPI\"\nnav_order: 10\nhas_children: true\n---\n\n# Tutorial: FastAPI\n\n> This tutorial is AI-ge"
},
{
"path": "docs/Flask/01_application_object___flask__.md",
"chars": 8965,
"preview": "---\nlayout: default\ntitle: \"Application Object (Flask)\"\nparent: \"Flask\"\nnav_order: 1\n---\n\n# Chapter 1: Application Objec"
},
{
"path": "docs/Flask/02_routing_system.md",
"chars": 11079,
"preview": "---\nlayout: default\ntitle: \"Routing System\"\nparent: \"Flask\"\nnav_order: 2\n---\n\n# Chapter 2: Routing System\n\nWelcome back!"
},
{
"path": "docs/Flask/03_request_and_response_objects.md",
"chars": 14977,
"preview": "---\nlayout: default\ntitle: \"Request and Response Objects\"\nparent: \"Flask\"\nnav_order: 3\n---\n\n# Chapter 3: Request and Res"
},
{
"path": "docs/Flask/04_templating__jinja2_integration_.md",
"chars": 15749,
"preview": "---\nlayout: default\ntitle: \"Templating (Jinja2 Integration)\"\nparent: \"Flask\"\nnav_order: 4\n---\n\n# Chapter 4: Templating ("
},
{
"path": "docs/Flask/05_context_globals___current_app____request____session____g__.md",
"chars": 19475,
"preview": "---\nlayout: default\ntitle: \"Context Globals\"\nparent: \"Flask\"\nnav_order: 5\n---\n\n# Chapter 5: Context Globals (`current_ap"
},
{
"path": "docs/Flask/06_configuration___config__.md",
"chars": 18341,
"preview": "---\nlayout: default\ntitle: \"Configuration (config)\"\nparent: \"Flask\"\nnav_order: 6\n---\n\n# Chapter 6: Configuration (`Confi"
},
{
"path": "docs/Flask/07_application_and_request_contexts.md",
"chars": 15069,
"preview": "---\nlayout: default\ntitle: \"Application and Request Contexts\"\nparent: \"Flask\"\nnav_order: 7\n---\n\n# Chapter 7: Application"
},
{
"path": "docs/Flask/08_blueprints.md",
"chars": 16343,
"preview": "---\nlayout: default\ntitle: \"Blueprints\"\nparent: \"Flask\"\nnav_order: 8\n---\n\n# Chapter 8: Blueprints\n\nWelcome back! In [Cha"
},
{
"path": "docs/Flask/index.md",
"chars": 1352,
"preview": "---\nlayout: default\ntitle: \"Flask\"\nnav_order: 11\nhas_children: true\n---\n\n# Tutorial: Flask\n\n> This tutorial is AI-genera"
},
{
"path": "docs/Google A2A/01_agent_card.md",
"chars": 8136,
"preview": "---\nlayout: default\ntitle: \"Agent Card\"\nparent: \"Google A2A\"\nnav_order: 1\n---\n\n# Chapter 1: Agent Card - The AI's Busine"
},
{
"path": "docs/Google A2A/02_task.md",
"chars": 11601,
"preview": "---\nlayout: default\ntitle: \"Task\"\nparent: \"Google A2A\"\nnav_order: 2\n---\n\n# Chapter 2: Task - The AI's Work Order\n\nIn the"
},
{
"path": "docs/Google A2A/03_a2a_protocol___core_types.md",
"chars": 10732,
"preview": "---\nlayout: default\ntitle: \"A2A Protocol & Core Types\"\nparent: \"Google A2A\"\nnav_order: 3\n---\n\n# Chapter 3: A2A Protocol "
},
{
"path": "docs/Google A2A/04_a2a_server_implementation.md",
"chars": 14010,
"preview": "---\nlayout: default\ntitle: \"A2A Server Implementation\"\nparent: \"Google A2A\"\nnav_order: 4\n---\n\n# Chapter 4: A2A Server Im"
},
{
"path": "docs/Google A2A/05_a2a_client_implementation.md",
"chars": 14239,
"preview": "---\nlayout: default\ntitle: \"A2A Client Implementation\"\nparent: \"Google A2A\"\nnav_order: 5\n---\n\n# Chapter 5: A2A Client Im"
},
{
"path": "docs/Google A2A/06_task_handling_logic__server_side_.md",
"chars": 16806,
"preview": "---\nlayout: default\ntitle: \"Task Handling Logic (Server-side)\"\nparent: \"Google A2A\"\nnav_order: 6\n---\n\n# Chapter 6: Task "
},
{
"path": "docs/Google A2A/07_streaming_communication__sse_.md",
"chars": 19181,
"preview": "---\nlayout: default\ntitle: \"Streaming Communication (SSE)\"\nparent: \"Google A2A\"\nnav_order: 7\n---\n\n# Chapter 7: Streaming"
},
{
"path": "docs/Google A2A/08_multi_agent_orchestration__host_agent_.md",
"chars": 14569,
"preview": "---\nlayout: default\ntitle: \"Multi-Agent Orchestration (Host Agent)\"\nparent: \"Google A2A\"\nnav_order: 8\n---\n\n# Chapter 8: "
},
{
"path": "docs/Google A2A/09_demo_ui_application___service.md",
"chars": 15505,
"preview": "---\nlayout: default\ntitle: \"Demo UI Application & Service\"\nparent: \"Google A2A\"\nnav_order: 9\n---\n\n# Chapter 9: Demo UI A"
},
{
"path": "docs/Google A2A/index.md",
"chars": 1603,
"preview": "---\nlayout: default\ntitle: \"Google A2A\"\nnav_order: 12\nhas_children: true\n---\n\n# Tutorial: Google A2A\n\n> This tutorial is"
},
{
"path": "docs/LangGraph/01_graph___stategraph.md",
"chars": 14650,
"preview": "---\nlayout: default\ntitle: \"Graph & StateGraph\"\nparent: \"LangGraph\"\nnav_order: 1\n---\n\n# Chapter 1: Graph / StateGraph - "
},
{
"path": "docs/LangGraph/02_nodes___pregelnode__.md",
"chars": 12496,
"preview": "---\nlayout: default\ntitle: \"Nodes (PregelNode)\"\nparent: \"LangGraph\"\nnav_order: 2\n---\n\n# Chapter 2: Nodes (`PregelNode`) "
},
{
"path": "docs/LangGraph/03_channels.md",
"chars": 20891,
"preview": "---\nlayout: default\ntitle: \"Channels\"\nparent: \"LangGraph\"\nnav_order: 3\n---\n\n# Chapter 3: Channels - The Communication Sy"
},
{
"path": "docs/LangGraph/04_control_flow_primitives___branch____send____interrupt__.md",
"chars": 27488,
"preview": "---\nlayout: default\ntitle: \"Control Flow Primitives\"\nparent: \"LangGraph\"\nnav_order: 4\n---\n\n# Chapter 4: Control Flow Pri"
},
{
"path": "docs/LangGraph/05_pregel_execution_engine.md",
"chars": 17190,
"preview": "---\nlayout: default\ntitle: \"Pregel Execution Engine\"\nparent: \"LangGraph\"\nnav_order: 5\n---\n\n# Chapter 5: Pregel Execution"
},
{
"path": "docs/LangGraph/06_checkpointer___basecheckpointsaver__.md",
"chars": 20548,
"preview": "---\nlayout: default\ntitle: \"Checkpointer (BaseCheckpointSaver)\"\nparent: \"LangGraph\"\nnav_order: 6\n---\n\n# Chapter 6: Check"
},
{
"path": "docs/LangGraph/index.md",
"chars": 1379,
"preview": "---\nlayout: default\ntitle: \"LangGraph\"\nnav_order: 13\nhas_children: true\n---\n\n# Tutorial: LangGraph\n\n> This tutorial is A"
},
{
"path": "docs/LevelDB/01_table___sstable___tablecache.md",
"chars": 14313,
"preview": "---\nlayout: default\ntitle: \"Table, SSTable & TableCache\"\nparent: \"LevelDB\"\nnav_order: 1\n---\n\n# Chapter 1: Table / SSTabl"
},
{
"path": "docs/LevelDB/02_memtable.md",
"chars": 13850,
"preview": "---\nlayout: default\ntitle: \"MemTable\"\nparent: \"LevelDB\"\nnav_order: 2\n---\n\n# Chapter 2: MemTable\n\nIn [Chapter 1: Table / "
},
{
"path": "docs/LevelDB/03_write_ahead_log__wal____logwriter_logreader.md",
"chars": 17946,
"preview": "---\nlayout: default\ntitle: \"Write-Ahead Log (WAL)\"\nparent: \"LevelDB\"\nnav_order: 3\n---\n\n# Chapter 3: Write-Ahead Log (WAL"
},
{
"path": "docs/LevelDB/04_dbimpl.md",
"chars": 23384,
"preview": "---\nlayout: default\ntitle: \"DBImpl\"\nparent: \"LevelDB\"\nnav_order: 4\n---\n\n# Chapter 4: DBImpl - The Database General Manag"
},
{
"path": "docs/LevelDB/05_writebatch.md",
"chars": 12839,
"preview": "---\nlayout: default\ntitle: \"WriteBatch\"\nparent: \"LevelDB\"\nnav_order: 5\n---\n\n# Chapter 5: WriteBatch - Grouping Changes T"
},
{
"path": "docs/LevelDB/06_version___versionset.md",
"chars": 16009,
"preview": "---\nlayout: default\ntitle: \"Version & VersionSet\"\nparent: \"LevelDB\"\nnav_order: 6\n---\n\n# Chapter 6: Version & VersionSet "
},
{
"path": "docs/LevelDB/07_iterator.md",
"chars": 18894,
"preview": "---\nlayout: default\ntitle: \"Iterator\"\nparent: \"LevelDB\"\nnav_order: 7\n---\n\n# Chapter 7: Iterator - Your Guide Through the"
},
{
"path": "docs/LevelDB/08_compaction.md",
"chars": 17578,
"preview": "---\nlayout: default\ntitle: \"Compaction\"\nparent: \"LevelDB\"\nnav_order: 8\n---\n\n# Chapter 8: Compaction - Keeping the Librar"
},
{
"path": "docs/LevelDB/09_internalkey___dbformat.md",
"chars": 16153,
"preview": "---\nlayout: default\ntitle: \"InternalKey & DBFormat\"\nparent: \"LevelDB\"\nnav_order: 9\n---\n\n# Chapter 9: InternalKey & DBFor"
},
{
"path": "docs/LevelDB/index.md",
"chars": 1820,
"preview": "---\nlayout: default\ntitle: \"LevelDB\"\nnav_order: 14\nhas_children: true\n---\n\n# Tutorial: LevelDB\n\n> This tutorial is AI-ge"
},
{
"path": "docs/MCP Python SDK/01_cli___mcp__command_.md",
"chars": 12050,
"preview": "---\nlayout: default\ntitle: \"CLI (mcp command)\"\nparent: \"MCP Python SDK\"\nnav_order: 1\n---\n\n# Chapter 1: Your Control Pane"
},
{
"path": "docs/MCP Python SDK/02_fastmcp_server___fastmcp__.md",
"chars": 15034,
"preview": "---\nlayout: default\ntitle: \"FastMCP Server (FastMCP)\"\nparent: \"MCP Python SDK\"\nnav_order: 2\n---\n\n# Chapter 2: Easier Ser"
},
{
"path": "docs/MCP Python SDK/03_fastmcp_resources___resource____resourcemanager__.md",
"chars": 15936,
"preview": "---\nlayout: default\ntitle: \"FastMCP Resources (Resource, ResourceManager)\"\nparent: \"MCP Python SDK\"\nnav_order: 3\n---\n\n# "
},
{
"path": "docs/MCP Python SDK/04_fastmcp_tools___tool____toolmanager__.md",
"chars": 14014,
"preview": "---\nlayout: default\ntitle: \"FastMCP Tools (Tool, ToolManager)\"\nparent: \"MCP Python SDK\"\nnav_order: 4\n---\n\n# Chapter 4: F"
},
{
"path": "docs/MCP Python SDK/05_fastmcp_prompts___prompt____promptmanager__.md",
"chars": 17372,
"preview": "---\nlayout: default\ntitle: \"FastMCP Prompts (Prompt, PromptManager)\"\nparent: \"MCP Python SDK\"\nnav_order: 5\n---\n\n# Chapte"
},
{
"path": "docs/MCP Python SDK/06_fastmcp_context___context__.md",
"chars": 17518,
"preview": "---\nlayout: default\ntitle: \"FastMCP Context (Context)\"\nparent: \"MCP Python SDK\"\nnav_order: 6\n---\n\n# Chapter 6: Talking B"
},
{
"path": "docs/MCP Python SDK/07_mcp_protocol_types.md",
"chars": 14949,
"preview": "---\nlayout: default\ntitle: \"MCP Protocol Types\"\nparent: \"MCP Python SDK\"\nnav_order: 7\n---\n\n# Chapter 7: MCP Protocol Typ"
},
{
"path": "docs/MCP Python SDK/08_client_server_sessions___clientsession____serversession__.md",
"chars": 19144,
"preview": "---\nlayout: default\ntitle: \"Client/Server Sessions (ClientSession, ServerSession)\"\nparent: \"MCP Python SDK\"\nnav_order: 8"
},
{
"path": "docs/MCP Python SDK/09_communication_transports__stdio__sse__websocket__memory_.md",
"chars": 19119,
"preview": "---\nlayout: default\ntitle: \"Communication Transports\"\nparent: \"MCP Python SDK\"\nnav_order: 9\n---\n\n# Chapter 9: Communicat"
},
{
"path": "docs/MCP Python SDK/index.md",
"chars": 1851,
"preview": "---\nlayout: default\ntitle: \"MCP Python SDK\"\nnav_order: 15\nhas_children: true\n---\n\n# Tutorial: MCP Python SDK\n\n> This tut"
},
{
"path": "docs/NumPy Core/01_ndarray__n_dimensional_array_.md",
"chars": 9493,
"preview": "---\nlayout: default\ntitle: \"ndarray (N-dimensional array)\"\nparent: \"NumPy Core\"\nnav_order: 1\n---\n\n# Chapter 1: ndarray ("
},
{
"path": "docs/NumPy Core/02_dtype__data_type_object_.md",
"chars": 12688,
"preview": "---\nlayout: default\ntitle: \"dtype (data type object)\"\nparent: \"NumPy Core\"\nnav_order: 2\n---\n\n# Chapter 2: dtype (Data Ty"
},
{
"path": "docs/NumPy Core/03_ufunc__universal_function_.md",
"chars": 12196,
"preview": "---\nlayout: default\ntitle: \"ufunc (universal function)\"\nparent: \"NumPy Core\"\nnav_order: 3\n---\n\n# Chapter 3: ufunc (Unive"
},
{
"path": "docs/NumPy Core/04_numeric_types___numerictypes__.md",
"chars": 13028,
"preview": "---\nlayout: default\ntitle: \"Numeric Types (numerictypes)\"\nparent: \"NumPy Core\"\nnav_order: 4\n---\n\n# Chapter 4: Numeric Ty"
},
{
"path": "docs/NumPy Core/05_array_printing___arrayprint__.md",
"chars": 15034,
"preview": "---\nlayout: default\ntitle: \"Array Printing (arrayprint)\"\nparent: \"NumPy Core\"\nnav_order: 5\n---\n\n# Chapter 5: Array Print"
},
{
"path": "docs/NumPy Core/06_multiarray_module.md",
"chars": 10918,
"preview": "---\nlayout: default\ntitle: \"Multiarray Module\"\nparent: \"NumPy Core\"\nnav_order: 6\n---\n\n# Chapter 6: multiarray Module\n\nWe"
},
{
"path": "docs/NumPy Core/07_umath_module.md",
"chars": 11004,
"preview": "---\nlayout: default\ntitle: \"Umath Module\"\nparent: \"NumPy Core\"\nnav_order: 7\n---\n\n# Chapter 7: umath Module\n\nWelcome to C"
},
{
"path": "docs/NumPy Core/08___array_function___protocol___overrides___overrides__.md",
"chars": 13574,
"preview": "---\nlayout: default\ntitle: \"__array_function__ Protocol (overrides)\"\nparent: \"NumPy Core\"\nnav_order: 8\n---\n\n# Chapter 8:"
},
{
"path": "docs/NumPy Core/index.md",
"chars": 1723,
"preview": "---\nlayout: default\ntitle: \"NumPy Core\"\nnav_order: 16\nhas_children: true\n---\n\n# Tutorial: NumPy Core\n\n> This tutorial is"
},
{
"path": "docs/OpenManus/01_llm.md",
"chars": 11911,
"preview": "---\nlayout: default\ntitle: \"LLM\"\nparent: \"OpenManus\"\nnav_order: 1\n---\n\n# Chapter 1: The LLM - Your Agent's Brainpower\n\nW"
},
{
"path": "docs/OpenManus/02_message___memory.md",
"chars": 13787,
"preview": "---\nlayout: default\ntitle: \"Message & Memory\"\nparent: \"OpenManus\"\nnav_order: 2\n---\n\n# Chapter 2: Message / Memory - Reme"
},
{
"path": "docs/OpenManus/03_baseagent.md",
"chars": 14103,
"preview": "---\nlayout: default\ntitle: \"BaseAgent\"\nparent: \"OpenManus\"\nnav_order: 3\n---\n\n# Chapter 3: BaseAgent - The Agent Blueprin"
},
{
"path": "docs/OpenManus/04_tool___toolcollection.md",
"chars": 15791,
"preview": "---\nlayout: default\ntitle: \"Tool & ToolCollection\"\nparent: \"OpenManus\"\nnav_order: 4\n---\n\n# Chapter 4: Tool / ToolCollect"
},
{
"path": "docs/OpenManus/05_baseflow.md",
"chars": 19244,
"preview": "---\nlayout: default\ntitle: \"BaseFlow\"\nparent: \"OpenManus\"\nnav_order: 5\n---\n\n# Chapter 5: BaseFlow - Managing Multi-Step "
},
{
"path": "docs/OpenManus/06_schema.md",
"chars": 13056,
"preview": "---\nlayout: default\ntitle: \"Schema\"\nparent: \"OpenManus\"\nnav_order: 6\n---\n\n# Chapter 6: Schema - The Official Data Forms\n"
},
{
"path": "docs/OpenManus/07_configuration__config_.md",
"chars": 17299,
"preview": "---\nlayout: default\ntitle: \"Configuration (config)\"\nparent: \"OpenManus\"\nnav_order: 7\n---\n\n# Chapter 7: Configuration (Co"
},
{
"path": "docs/OpenManus/08_dockersandbox.md",
"chars": 18737,
"preview": "---\nlayout: default\ntitle: \"DockerSandbox\"\nparent: \"OpenManus\"\nnav_order: 8\n---\n\n# Chapter 8: DockerSandbox - A Safe Pla"
},
{
"path": "docs/OpenManus/09_mcp__model_context_protocol_.md",
"chars": 21655,
"preview": "---\nlayout: default\ntitle: \"MCP (Model Context Protocol)\"\nparent: \"OpenManus\"\nnav_order: 9\n---\n\n# Chapter 9: MCP (Model "
},
{
"path": "docs/OpenManus/index.md",
"chars": 1854,
"preview": "---\nlayout: default\ntitle: \"OpenManus\"\nnav_order: 17\nhas_children: true\n---\n\n# Tutorial: OpenManus\n\n> This tutorial is A"
},
{
"path": "docs/PocketFlow/01_shared_state___shared__dictionary__.md",
"chars": 14804,
"preview": "---\nlayout: default\ntitle: \"Shared State (Shared Dictionary)\"\nparent: \"PocketFlow\"\nnav_order: 1\n---\n\n# Chapter 1: Shared"
},
{
"path": "docs/PocketFlow/02_node___basenode____node____asyncnode___.md",
"chars": 14787,
"preview": "---\nlayout: default\ntitle: \"Node (BaseNode, Node, AsyncNode)\"\nparent: \"PocketFlow\"\nnav_order: 2\n---\n\n# Chapter 2: Node ("
},
{
"path": "docs/PocketFlow/03_actions___transitions_.md",
"chars": 17362,
"preview": "---\nlayout: default\ntitle: \"Actions / Transitions\"\nparent: \"PocketFlow\"\nnav_order: 3\n---\n\n# Chapter 3: Actions / Transit"
},
{
"path": "docs/PocketFlow/04_flow___flow____asyncflow___.md",
"chars": 17187,
"preview": "---\nlayout: default\ntitle: \"Flow (Flow, AsyncFlow)\"\nparent: \"PocketFlow\"\nnav_order: 4\n---\n\n# Chapter 4: Flow (`Flow`, `A"
},
{
"path": "docs/PocketFlow/05_asynchronous_processing___asyncnode____asyncflow___.md",
"chars": 13777,
"preview": "---\nlayout: default\ntitle: \"Asynchronous Processing (AsyncNode, AsyncFlow)\"\nparent: \"PocketFlow\"\nnav_order: 5\n---\n\n# Cha"
},
{
"path": "docs/PocketFlow/06_batch_processing___batchnode____batchflow____asyncparallelbatchnode___.md",
"chars": 18456,
"preview": "---\nlayout: default\ntitle: \"Batch Processing (BatchNode, BatchFlow, AsyncParallelBatchNode)\"\nparent: \"PocketFlow\"\nnav_or"
},
{
"path": "docs/PocketFlow/07_a2a__agent_to_agent__communication_framework_.md",
"chars": 20919,
"preview": "---\nlayout: default\ntitle: \"A2A (Agent-to-Agent) Communication Framework\"\nparent: \"PocketFlow\"\nnav_order: 7\n---\n\n# Chapt"
},
{
"path": "docs/PocketFlow/index.md",
"chars": 2244,
"preview": "---\nlayout: default\ntitle: \"PocketFlow\"\nnav_order: 18\nhas_children: true\n---\n\n# Tutorial: PocketFlow\n\n> This tutorial is"
},
{
"path": "docs/Pydantic Core/01_basemodel.md",
"chars": 11090,
"preview": "---\nlayout: default\ntitle: \"BaseModel\"\nparent: \"Pydantic Core\"\nnav_order: 1\n---\n\n# Chapter 1: BaseModel - Your Data Blue"
},
{
"path": "docs/Pydantic Core/02_fields__fieldinfo___field_function_.md",
"chars": 15829,
"preview": "---\nlayout: default\ntitle: \"Fields (FieldInfo & Field function)\"\nparent: \"Pydantic Core\"\nnav_order: 2\n---\n\n# Chapter 2: "
},
{
"path": "docs/Pydantic Core/03_configuration__configdict___configwrapper_.md",
"chars": 18856,
"preview": "---\nlayout: default\ntitle: \"Configuration (ConfigDict & ConfigWrapper)\"\nparent: \"Pydantic Core\"\nnav_order: 3\n---\n\n# Chap"
},
{
"path": "docs/Pydantic Core/04_custom_logic__decorators___annotated_helpers_.md",
"chars": 25361,
"preview": "---\nlayout: default\ntitle: \"Custom Logic (Decorators & Annotated Helpers)\"\nparent: \"Pydantic Core\"\nnav_order: 4\n---\n\n# C"
},
{
"path": "docs/Pydantic Core/05_core_schema___validation_serialization.md",
"chars": 14442,
"preview": "---\nlayout: default\ntitle: \"Core Schema & Validation/Serialization\"\nparent: \"Pydantic Core\"\nnav_order: 5\n---\n\n# Chapter "
},
{
"path": "docs/Pydantic Core/06_typeadapter.md",
"chars": 17059,
"preview": "---\nlayout: default\ntitle: \"TypeAdapter\"\nparent: \"Pydantic Core\"\nnav_order: 6\n---\n\n# Chapter 6: TypeAdapter - Your Unive"
},
{
"path": "docs/Pydantic Core/index.md",
"chars": 1552,
"preview": "---\nlayout: default\ntitle: \"Pydantic Core\"\nnav_order: 18\nhas_children: true\n---\n\n# Tutorial: Pydantic Core\n\n> This tutor"
},
{
"path": "docs/Requests/01_functional_api.md",
"chars": 10017,
"preview": "---\nlayout: default\ntitle: \"Functional API\"\nparent: \"Requests\"\nnav_order: 1\n---\n\n# Chapter 1: The Simplest Way - The Fun"
},
{
"path": "docs/Requests/02_request___response_models.md",
"chars": 13340,
"preview": "---\nlayout: default\ntitle: \"Request & Response Models\"\nparent: \"Requests\"\nnav_order: 2\n---\n\n# Chapter 2: What Happens Wh"
},
{
"path": "docs/Requests/03_session.md",
"chars": 17906,
"preview": "---\nlayout: default\ntitle: \"Session\"\nparent: \"Requests\"\nnav_order: 3\n---\n\n# Chapter 3: Remembering Things - The Session "
},
{
"path": "docs/Requests/04_cookie_jar.md",
"chars": 16202,
"preview": "---\nlayout: default\ntitle: \"Cookie Jar\"\nparent: \"Requests\"\nnav_order: 4\n---\n\n# Chapter 4: The Cookie Jar - Remembering W"
},
{
"path": "docs/Requests/05_authentication_handlers.md",
"chars": 19613,
"preview": "---\nlayout: default\ntitle: \"Authentication Handlers\"\nparent: \"Requests\"\nnav_order: 5\n---\n\n# Chapter 5: Authentication Ha"
},
{
"path": "docs/Requests/06_exception_hierarchy.md",
"chars": 19084,
"preview": "---\nlayout: default\ntitle: \"Exception Hierarchy\"\nparent: \"Requests\"\nnav_order: 6\n---\n\n# Chapter 6: When Things Go Wrong "
},
{
"path": "docs/Requests/07_transport_adapters.md",
"chars": 21977,
"preview": "---\nlayout: default\ntitle: \"Transport Adapters\"\nparent: \"Requests\"\nnav_order: 7\n---\n\n# Chapter 7: Transport Adapters - C"
},
{
"path": "docs/Requests/08_hook_system.md",
"chars": 18205,
"preview": "---\nlayout: default\ntitle: \"Hook System\"\nparent: \"Requests\"\nnav_order: 8\n---\n\n# Chapter 8: The Hook System - Setting Up "
},
{
"path": "docs/Requests/index.md",
"chars": 1438,
"preview": "---\nlayout: default\ntitle: \"Requests\"\nnav_order: 19\nhas_children: true\n---\n\n# Tutorial: Requests\n\n> This tutorial is AI-"
},
{
"path": "docs/SmolaAgents/01_multistepagent.md",
"chars": 16036,
"preview": "---\nlayout: default\ntitle: \"MultiStepAgent\"\nparent: \"SmolaAgents\"\nnav_order: 1\n---\n\n# Chapter 1: The MultiStepAgent - Yo"
},
{
"path": "docs/SmolaAgents/02_model_interface.md",
"chars": 15540,
"preview": "---\nlayout: default\ntitle: \"Model Interface\"\nparent: \"SmolaAgents\"\nnav_order: 2\n---\n\n# Chapter 2: Model Interface - Your"
},
{
"path": "docs/SmolaAgents/03_tool.md",
"chars": 15095,
"preview": "---\nlayout: default\ntitle: \"Tool\"\nparent: \"SmolaAgents\"\nnav_order: 3\n---\n\n# Chapter 3: Tool - Giving Your Agent Superpow"
},
{
"path": "docs/SmolaAgents/04_agentmemory.md",
"chars": 18321,
"preview": "---\nlayout: default\ntitle: \"AgentMemory\"\nparent: \"SmolaAgents\"\nnav_order: 4\n---\n\n# Chapter 4: AgentMemory - The Agent's "
},
{
"path": "docs/SmolaAgents/05_prompttemplates.md",
"chars": 14774,
"preview": "---\nlayout: default\ntitle: \"PromptTemplates\"\nparent: \"SmolaAgents\"\nnav_order: 5\n---\n\n# Chapter 5: PromptTemplates - Craf"
},
{
"path": "docs/SmolaAgents/06_pythonexecutor.md",
"chars": 20760,
"preview": "---\nlayout: default\ntitle: \"PythonExecutor\"\nparent: \"SmolaAgents\"\nnav_order: 6\n---\n\n# Chapter 6: PythonExecutor - Runnin"
},
{
"path": "docs/SmolaAgents/07_agenttype.md",
"chars": 19017,
"preview": "---\nlayout: default\ntitle: \"AgentType\"\nparent: \"SmolaAgents\"\nnav_order: 7\n---\n\n# Chapter 7: AgentType - Handling More Th"
},
{
"path": "docs/SmolaAgents/08_agentlogger___monitor.md",
"chars": 18690,
"preview": "---\nlayout: default\ntitle: \"AgentLogger & Monitor\"\nparent: \"SmolaAgents\"\nnav_order: 8\n---\n\n# Chapter 8: AgentLogger & Mo"
},
{
"path": "docs/SmolaAgents/index.md",
"chars": 1835,
"preview": "---\nlayout: default\ntitle: \"SmolaAgents\"\nnav_order: 20\nhas_children: true\n---\n\n# Tutorial: SmolaAgents\n\n> This tutorial "
},
{
"path": "docs/_config.yml",
"chars": 947,
"preview": "# Basic site settings\ntitle: Pocket Flow\n\n# Theme settings\nremote_theme: just-the-docs/just-the-docs\n\n# Navigation\nnav_s"
},
{
"path": "docs/design.md",
"chars": 16067,
"preview": "---\nlayout: default\ntitle: \"System Design\"\nnav_order: 2\n---\n\n# System Design: Codebase Knowledge Builder\n\n> Please DON'T"
},
{
"path": "docs/index.md",
"chars": 3123,
"preview": "---\nlayout: default\ntitle: \"Home\"\nnav_order: 1\n---\n\n# Turns Codebase into Easy Tutorial - Pocket Flow\n\nEver stared at a "
},
{
"path": "flow.py",
"chars": 1074,
"preview": "from pocketflow import Flow\n# Import all node classes from nodes.py\nfrom nodes import (\n FetchRepo,\n IdentifyAbstr"
},
{
"path": "main.py",
"chars": 4528,
"preview": "import dotenv\nimport os\nimport argparse\n# Import the function that creates the flow\nfrom flow import create_tutorial_flo"
},
{
"path": "nodes.py",
"chars": 41523,
"preview": "import os\nimport re\nimport yaml\nfrom pocketflow import Node, BatchNode\nfrom utils.crawl_github_files import crawl_github"
},
{
"path": "requirements.txt",
"chars": 154,
"preview": "pocketflow>=0.0.1\npyyaml>=6.0\nrequests>=2.28.0\ngitpython>=3.1.0\ngoogle-cloud-aiplatform>=1.25.0\ngoogle-genai>=1.9.0\npyth"
},
{
"path": "utils/__init__.py",
"chars": 0,
"preview": ""
}
]
// ... and 3 more files (download for full content)
About this extraction
This page contains the full source code of the The-Pocket/PocketFlow-Tutorial-Codebase-Knowledge GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 203 files (2.8 MB), approximately 740.6k tokens, and a symbol index with 35 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.