[
  {
    "path": ".cursor/rules/core_abstraction/async.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Core Abstraction, (Advanced) Async\nglobs: \nalwaysApply: false\n---\n# (Advanced) Async\n\n**Async** Nodes implement `prep_async()`, `exec_async()`, `exec_fallback_async()`, and/or `post_async()`. This is useful for:\n\n1. **prep_async()**: For *fetching/reading data (files, APIs, DB)* in an I/O-friendly way.\n2. **exec_async()**: Typically used for async LLM calls.\n3. **post_async()**: For *awaiting user feedback*, *coordinating across multi-agents* or any additional async steps after `exec_async()`.\n\n**Note**: `AsyncNode` must be wrapped in `AsyncFlow`. `AsyncFlow` can also include regular (sync) nodes.\n\n### Example\n\n```python\nclass SummarizeThenVerify(AsyncNode):\n    async def prep_async(self, shared):\n        # Example: read a file asynchronously\n        doc_text = await read_file_async(shared[\"doc_path\"])\n        return doc_text\n\n    async def exec_async(self, prep_res):\n        # Example: async LLM call\n        summary = await call_llm_async(f\"Summarize: {prep_res}\")\n        return summary\n\n    async def post_async(self, shared, prep_res, exec_res):\n        # Example: wait for user feedback\n        decision = await gather_user_feedback(exec_res)\n        if decision == \"approve\":\n            shared[\"summary\"] = exec_res\n            return \"approve\"\n        return \"deny\"\n\nsummarize_node = SummarizeThenVerify()\nfinal_node = Finalize()\n\n# Define transitions\nsummarize_node - \"approve\" >> final_node\nsummarize_node - \"deny\"    >> summarize_node  # retry\n\nflow = AsyncFlow(start=summarize_node)\n\nasync def main():\n    shared = {\"doc_path\": \"document.txt\"}\n    await flow.run_async(shared)\n    print(\"Final Summary:\", shared.get(\"summary\"))\n\nasyncio.run(main())\n```"
  },
  {
    "path": ".cursor/rules/core_abstraction/batch.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Core Abstraction, Batch\nglobs: \nalwaysApply: false\n---\n# Batch\n\n**Batch** makes it easier to handle large inputs in one Node or **rerun** a Flow multiple times. Example use cases:\n- **Chunk-based** processing (e.g., splitting large texts).\n- **Iterative** processing over lists of input items (e.g., user queries, files, URLs).\n\n## 1. BatchNode\n\nA **BatchNode** extends `Node` but changes `prep()` and `exec()`:\n\n- **`prep(shared)`**: returns an **iterable** (e.g., list, generator).\n- **`exec(item)`**: called **once** per item in that iterable.\n- **`post(shared, prep_res, exec_res_list)`**: after all items are processed, receives a **list** of results (`exec_res_list`) and returns an **Action**.\n\n\n### Example: Summarize a Large File\n\n```python\nclass MapSummaries(BatchNode):\n    def prep(self, shared):\n        # Suppose we have a big file; chunk it\n        content = shared[\"data\"]\n        chunk_size = 10000\n        chunks = [content[i:i+chunk_size] for i in range(0, len(content), chunk_size)]\n        return chunks\n\n    def exec(self, chunk):\n        prompt = f\"Summarize this chunk in 10 words: {chunk}\"\n        summary = call_llm(prompt)\n        return summary\n\n    def post(self, shared, prep_res, exec_res_list):\n        combined = \"\\n\".join(exec_res_list)\n        shared[\"summary\"] = combined\n        return \"default\"\n\nmap_summaries = MapSummaries()\nflow = Flow(start=map_summaries)\nflow.run(shared)\n```\n\n---\n\n## 2. BatchFlow\n\nA **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.\n\n### Key Differences from BatchNode\n\n**Important**: Unlike BatchNode, which processes items and modifies the shared store:\n\n1. BatchFlow returns **parameters to pass to the child Flow**, not data to process\n2. These parameters are accessed in child nodes via `self.params`, not from the shared store\n3. Each child Flow runs independently with a different set of parameters\n4. Child nodes can be regular Nodes, not BatchNodes (the batching happens at the Flow level)\n\n### Example: Summarize Many Files\n\n```python\nclass SummarizeAllFiles(BatchFlow):\n    def prep(self, shared):\n        # IMPORTANT: Return a list of param dictionaries (not data for processing)\n        filenames = list(shared[\"data\"].keys())  # e.g., [\"file1.txt\", \"file2.txt\", ...]\n        return [{\"filename\": fn} for fn in filenames]\n\n# Child node that accesses filename from params, not shared store\nclass LoadFile(Node):\n    def prep(self, shared):\n        # Access filename from params (not from shared)\n        filename = self.params[\"filename\"]  # Important! Use self.params, not shared\n        return filename\n        \n    def exec(self, filename):\n        with open(filename, 'r') as f:\n            return f.read()\n            \n    def post(self, shared, prep_res, exec_res):\n        # Store file content in shared\n        shared[\"current_file_content\"] = exec_res\n        return \"default\"\n\n# Summarize node that works on the currently loaded file\nclass Summarize(Node):\n    def prep(self, shared):\n        return shared[\"current_file_content\"]\n        \n    def exec(self, content):\n        prompt = f\"Summarize this file in 50 words: {content}\"\n        return call_llm(prompt)\n        \n    def post(self, shared, prep_res, exec_res):\n        # Store summary in shared, indexed by current filename\n        filename = self.params[\"filename\"]  # Again, using params\n        if \"summaries\" not in shared:\n            shared[\"summaries\"] = {}\n        shared[\"summaries\"][filename] = exec_res\n        return \"default\"\n\n# Create a per-file flow\nload_file = LoadFile()\nsummarize = Summarize()\nload_file >> summarize\nsummarize_file = Flow(start=load_file)\n\n# Wrap in a BatchFlow to process all files\nsummarize_all_files = SummarizeAllFiles(start=summarize_file)\nsummarize_all_files.run(shared)\n```\n\n### Under the Hood\n1. `prep(shared)` in the BatchFlow returns a list of param dicts—e.g., `[{\"filename\": \"file1.txt\"}, {\"filename\": \"file2.txt\"}, ...]`.\n2. The **BatchFlow** loops through each dict. For each one:\n   - It merges the dict with the BatchFlow's own `params` (if any): `{**batch_flow.params, **dict_from_prep}`\n   - It calls `flow.run(shared)` using the merged parameters\n   - **IMPORTANT**: These parameters are passed to the child Flow's nodes via `self.params`, NOT via the shared store\n3. This means the sub-Flow is run **repeatedly**, once for every param dict, with each node in the flow accessing the parameters via `self.params`.\n\n---\n\n## 3. Nested or Multi-Level Batches\n\nYou can nest a **BatchFlow** in another **BatchFlow**. For instance:\n- **Outer** batch: returns a list of directory param dicts (e.g., `{\"directory\": \"/pathA\"}`, `{\"directory\": \"/pathB\"}`, ...).\n- **Inner** batch: returning a list of per-file param dicts.\n\nAt 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.\n\n```python\n\nclass FileBatchFlow(BatchFlow):\n    def prep(self, shared):\n        # Access directory from params (set by parent)\n        directory = self.params[\"directory\"]\n        # e.g., files = [\"file1.txt\", \"file2.txt\", ...]\n        files = [f for f in os.listdir(directory) if f.endswith(\".txt\")]\n        return [{\"filename\": f} for f in files]\n\nclass DirectoryBatchFlow(BatchFlow):\n    def prep(self, shared):\n        directories = [ \"/path/to/dirA\", \"/path/to/dirB\"]\n        return [{\"directory\": d} for d in directories]\n\n# The actual processing node\nclass ProcessFile(Node):\n    def prep(self, shared):\n        # Access both directory and filename from params\n        directory = self.params[\"directory\"]  # From outer batch\n        filename = self.params[\"filename\"]    # From inner batch\n        full_path = os.path.join(directory, filename)\n        return full_path\n        \n    def exec(self, full_path):\n        # Process the file...\n        return f\"Processed {full_path}\"\n        \n    def post(self, shared, prep_res, exec_res):\n        # Store results, perhaps indexed by path\n        if \"results\" not in shared:\n            shared[\"results\"] = {}\n        shared[\"results\"][prep_res] = exec_res\n        return \"default\"\n\n# Set up the nested batch structure\nprocess_node = ProcessFile()\ninner_flow = FileBatchFlow(start=process_node)\nouter_flow = DirectoryBatchFlow(start=inner_flow)\n\n# Run it\nouter_flow.run(shared)\n```\n"
  },
  {
    "path": ".cursor/rules/core_abstraction/communication.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Core Abstraction, Communication\nglobs: \nalwaysApply: false\n---\n# Communication\n\nNodes and Flows **communicate** in 2 ways:\n\n1. **Shared Store (for almost all the cases)** \n\n   - A global data structure (often an in-mem dict) that all nodes can read ( `prep()`) and write (`post()`).  \n   - Great for data results, large content, or anything multiple nodes need.\n   - You shall design the data structure and populate it ahead.\n     \n   - > **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](mdc:./batch.md).\n     {: .best-practice }\n\n2. **Params (only for [Batch](mdc:./batch.md))** \n   - 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**.\n   - Good for identifiers like filenames or numeric IDs, in Batch mode.\n\nIf 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).\n\n---\n\n## 1. Shared Store\n\n### Overview\n\nA shared store is typically an in-mem dictionary, like:\n```python\nshared = {\"data\": {}, \"summary\": {}, \"config\": {...}, ...}\n```\n\nIt 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.\n\n### Example\n\n```python\nclass LoadData(Node):\n    def post(self, shared, prep_res, exec_res):\n        # We write data to shared store\n        shared[\"data\"] = \"Some text content\"\n        return None\n\nclass Summarize(Node):\n    def prep(self, shared):\n        # We read data from shared store\n        return shared[\"data\"]\n\n    def exec(self, prep_res):\n        # Call LLM to summarize\n        prompt = f\"Summarize: {prep_res}\"\n        summary = call_llm(prompt)\n        return summary\n\n    def post(self, shared, prep_res, exec_res):\n        # We write summary to shared store\n        shared[\"summary\"] = exec_res\n        return \"default\"\n\nload_data = LoadData()\nsummarize = Summarize()\nload_data >> summarize\nflow = Flow(start=load_data)\n\nshared = {}\nflow.run(shared)\n```\n\nHere:\n- `LoadData` writes to `shared[\"data\"]`.\n- `Summarize` reads from `shared[\"data\"]`, summarizes, and writes to `shared[\"summary\"]`.\n\n---\n\n## 2. Params\n\n**Params** let you store *per-Node* or *per-Flow* config that doesn't need to live in the shared store. They are:\n- **Immutable** during a Node's run cycle (i.e., they don't change mid-`prep->exec->post`).\n- **Set** via `set_params()`.\n- **Cleared** and updated each time a parent Flow calls it.\n\n> Only set the uppermost Flow params because others will be overwritten by the parent Flow. \n> \n> If you need to set child node params, see [Batch](mdc:./batch.md).\n{: .warning }\n\nTypically, **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.\n\n### Example\n\n```python\n# 1) Create a Node that uses params\nclass SummarizeFile(Node):\n    def prep(self, shared):\n        # Access the node's param\n        filename = self.params[\"filename\"]\n        return shared[\"data\"].get(filename, \"\")\n\n    def exec(self, prep_res):\n        prompt = f\"Summarize: {prep_res}\"\n        return call_llm(prompt)\n\n    def post(self, shared, prep_res, exec_res):\n        filename = self.params[\"filename\"]\n        shared[\"summary\"][filename] = exec_res\n        return \"default\"\n\n# 2) Set params\nnode = SummarizeFile()\n\n# 3) Set Node params directly (for testing)\nnode.set_params({\"filename\": \"doc1.txt\"})\nnode.run(shared)\n\n# 4) Create Flow\nflow = Flow(start=node)\n\n# 5) Set Flow params (overwrites node params)\nflow.set_params({\"filename\": \"doc2.txt\"})\nflow.run(shared)  # The node summarizes doc2, not doc1\n```\n"
  },
  {
    "path": ".cursor/rules/core_abstraction/flow.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Core Abstraction, Flow\nglobs: \nalwaysApply: false\n---\n# Flow\n\nA **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()`.\n\n## 1. Action-based Transitions\n\nEach Node's `post()` returns an **Action** string. By default, if `post()` doesn't return anything, we treat that as `\"default\"`.\n\nYou define transitions with the syntax:\n\n1. **Basic default transition**: `node_a >> node_b`\n  This means if `node_a.post()` returns `\"default\"`, go to `node_b`. \n  (Equivalent to `node_a - \"default\" >> node_b`)\n\n2. **Named action transition**: `node_a - \"action_name\" >> node_b`\n  This means if `node_a.post()` returns `\"action_name\"`, go to `node_b`.\n\nIt's possible to create loops, branching, or multi-step flows.\n\n## 2. Creating a Flow\n\nA **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.\n\n### Example: Simple Sequence\n\nHere's a minimal flow of two nodes in a chain:\n\n```python\nnode_a >> node_b\nflow = Flow(start=node_a)\nflow.run(shared)\n```\n\n- When you run the flow, it executes `node_a`.  \n- Suppose `node_a.post()` returns `\"default\"`.  \n- The flow then sees `\"default\"` Action is linked to `node_b` and runs `node_b`.  \n- `node_b.post()` returns `\"default\"` but we didn't define `node_b >> something_else`. So the flow ends there.\n\n### Example: Branching & Looping\n\nHere's a simple expense approval flow that demonstrates branching and looping. The `ReviewExpense` node can return three possible Actions:\n\n- `\"approved\"`: expense is approved, move to payment processing\n- `\"needs_revision\"`: expense needs changes, send back for revision \n- `\"rejected\"`: expense is denied, finish the process\n\nWe can wire them like this:\n\n```python\n# Define the flow connections\nreview - \"approved\" >> payment        # If approved, process payment\nreview - \"needs_revision\" >> revise   # If needs changes, go to revision\nreview - \"rejected\" >> finish         # If rejected, finish the process\n\nrevise >> review   # After revision, go back for another review\npayment >> finish  # After payment, finish the process\n\nflow = Flow(start=review)\n```\n\nLet's see how it flows:\n\n1. If `review.post()` returns `\"approved\"`, the expense moves to the `payment` node\n2. If `review.post()` returns `\"needs_revision\"`, it goes to the `revise` node, which then loops back to `review`\n3. If `review.post()` returns `\"rejected\"`, it moves to the `finish` node and stops\n\n```mermaid\nflowchart TD\n    review[Review Expense] -->|approved| payment[Process Payment]\n    review -->|needs_revision| revise[Revise Report]\n    review -->|rejected| finish[Finish Process]\n\n    revise --> review\n    payment --> finish\n```\n\n### Running Individual Nodes vs. Running a Flow\n\n- `node.run(shared)`: Just runs that node alone (calls `prep->exec->post()`), returns an Action. \n- `flow.run(shared)`: Executes from the start node, follows Actions to the next node, and so on until the flow can't continue.\n\n> `node.run(shared)` **does not** proceed to the successor.\n> This is mainly for debugging or testing a single node.\n> \n> Always use `flow.run(...)` in production to ensure the full pipeline runs correctly.\n{: .warning }\n\n## 3. Nested Flows\n\nA **Flow** can act like a Node, which enables powerful composition patterns. This means you can:\n\n1. Use a Flow as a Node within another Flow's transitions.  \n2. Combine multiple smaller Flows into a larger Flow for reuse.  \n3. Node `params` will be a merging of **all** parents' `params`.\n\n### Flow's Node Methods\n\nA **Flow** is also a **Node**, so it will run `prep()` and `post()`. However:\n\n- It **won't** run `exec()`, as its main logic is to orchestrate its nodes.\n- `post()` always receives `None` for `exec_res` and should instead get the flow execution results from the shared store.\n\n### Basic Flow Nesting\n\nHere's how to connect a flow to another node:\n\n```python\n# Create a sub-flow\nnode_a >> node_b\nsubflow = Flow(start=node_a)\n\n# Connect it to another node\nsubflow >> node_c\n\n# Create the parent flow\nparent_flow = Flow(start=subflow)\n```\n\nWhen `parent_flow.run()` executes:\n1. It starts `subflow`\n2. `subflow` runs through its nodes (`node_a->node_b`)\n3. After `subflow` completes, execution continues to `node_c`\n\n### Example: Order Processing Pipeline\n\nHere's a practical example that breaks down order processing into nested flows:\n\n```python\n# Payment processing sub-flow\nvalidate_payment >> process_payment >> payment_confirmation\npayment_flow = Flow(start=validate_payment)\n\n# Inventory sub-flow\ncheck_stock >> reserve_items >> update_inventory\ninventory_flow = Flow(start=check_stock)\n\n# Shipping sub-flow\ncreate_label >> assign_carrier >> schedule_pickup\nshipping_flow = Flow(start=create_label)\n\n# Connect the flows into a main order pipeline\npayment_flow >> inventory_flow >> shipping_flow\n\n# Create the master flow\norder_pipeline = Flow(start=payment_flow)\n\n# Run the entire pipeline\norder_pipeline.run(shared_data)\n```\n\nThis creates a clean separation of concerns while maintaining a clear execution path:\n\n```mermaid\nflowchart LR\n    subgraph order_pipeline[Order Pipeline]\n        subgraph paymentFlow[\"Payment Flow\"]\n            A[Validate Payment] --> B[Process Payment] --> C[Payment Confirmation]\n        end\n\n        subgraph inventoryFlow[\"Inventory Flow\"]\n            D[Check Stock] --> E[Reserve Items] --> F[Update Inventory]\n        end\n\n        subgraph shippingFlow[\"Shipping Flow\"]\n            G[Create Label] --> H[Assign Carrier] --> I[Schedule Pickup]\n        end\n\n        paymentFlow --> inventoryFlow\n        inventoryFlow --> shippingFlow\n    end\n```"
  },
  {
    "path": ".cursor/rules/core_abstraction/node.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Core Abstraction, Node\nglobs: \nalwaysApply: false\n---\n# Node\n\nA **Node** is the smallest building block. Each Node has 3 steps `prep->exec->post`:\n\n\n\n1. `prep(shared)`\n   - **Read and preprocess data** from `shared` store. \n   - Examples: *query DB, read files, or serialize data into a string*.\n   - Return `prep_res`, which is used by `exec()` and `post()`.\n\n2. `exec(prep_res)`\n   - **Execute compute logic**, with optional retries and error handling (below).\n   - Examples: *(mostly) LLM calls, remote APIs, tool use*.\n   - ⚠️ This shall be only for compute and **NOT** access `shared`.\n   - ⚠️ If retries enabled, ensure idempotent implementation.\n   - ⚠️ Defer exception handling to the Node's built-in retry mechanism.\n   - Return `exec_res`, which is passed to `post()`.\n\n3. `post(shared, prep_res, exec_res)`\n   - **Postprocess and write data** back to `shared`.\n   - Examples: *update DB, change states, log results*.\n   - **Decide the next action** by returning a *string* (`action = \"default\"` if *None*).\n\n> **Why 3 steps?** To enforce the principle of *separation of concerns*. The data storage and data processing are operated separately.\n>\n> All steps are *optional*. E.g., you can only implement `prep` and `post` if you just need to process data.\n{: .note }\n\n### Fault Tolerance & Retries\n\nYou can **retry** `exec()` if it raises an exception via two parameters when define the Node:\n\n- `max_retries` (int): Max times to run `exec()`. The default is `1` (**no** retry).\n- `wait` (int): The time to wait (in **seconds**) before next retry. By default, `wait=0` (no waiting). \n`wait` is helpful when you encounter rate-limits or quota errors from your LLM provider and need to back off.\n\n```python \nmy_node = SummarizeFile(max_retries=3, wait=10)\n```\n\nWhen an exception occurs in `exec()`, the Node automatically retries until:\n\n- It either succeeds, or\n- The Node has retried `max_retries - 1` times already and fails on the last attempt.\n\nYou can get the current retry times (0-based) from `self.cur_retry`.\n\n```python \nclass RetryNode(Node):\n    def exec(self, prep_res):\n        print(f\"Retry {self.cur_retry} times\")\n        raise Exception(\"Failed\")\n```\n\n### Graceful Fallback\n\nTo **gracefully handle** the exception (after all retries) rather than raising it, override:\n\n```python \ndef exec_fallback(self, prep_res, exc):\n    raise exc\n```\n\nBy default, it just re-raises exception. But you can return a fallback result instead, which becomes the `exec_res` passed to `post()`.\n\n### Example: Summarize file\n\n```python \nclass SummarizeFile(Node):\n    def prep(self, shared):\n        return shared[\"data\"]\n\n    def exec(self, prep_res):\n        if not prep_res:\n            return \"Empty file content\"\n        prompt = f\"Summarize this text in 10 words: {prep_res}\"\n        summary = call_llm(prompt)  # might fail\n        return summary\n\n    def exec_fallback(self, prep_res, exc):\n        # Provide a simple fallback instead of crashing\n        return \"There was an error processing your request.\"\n\n    def post(self, shared, prep_res, exec_res):\n        shared[\"summary\"] = exec_res\n        # Return \"default\" by not returning\n\nsummarize_node = SummarizeFile(max_retries=3)\n\n# node.run() calls prep->exec->post\n# If exec() fails, it retries up to 3 times before calling exec_fallback()\naction_result = summarize_node.run(shared)\n\nprint(\"Action returned:\", action_result)  # \"default\"\nprint(\"Summary stored:\", shared[\"summary\"])\n```"
  },
  {
    "path": ".cursor/rules/core_abstraction/parallel.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Core Abstraction, (Advanced) Parallel\nglobs: \nalwaysApply: false\n---\n# (Advanced) Parallel\n\n**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. \n\n> 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.\n{: .warning }\n\n> - **Ensure Tasks Are Independent**: If each item depends on the output of a previous item, **do not** parallelize.\n> \n> - **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).\n> \n> - **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.\n{: .best-practice }\n\n## AsyncParallelBatchNode\n\nLike **AsyncBatchNode**, but run `exec_async()` in **parallel**:\n\n```python\nclass ParallelSummaries(AsyncParallelBatchNode):\n    async def prep_async(self, shared):\n        # e.g., multiple texts\n        return shared[\"texts\"]\n\n    async def exec_async(self, text):\n        prompt = f\"Summarize: {text}\"\n        return await call_llm_async(prompt)\n\n    async def post_async(self, shared, prep_res, exec_res_list):\n        shared[\"summary\"] = \"\\n\\n\".join(exec_res_list)\n        return \"default\"\n\nnode = ParallelSummaries()\nflow = AsyncFlow(start=node)\n```\n\n## AsyncParallelBatchFlow\n\nParallel version of **BatchFlow**. Each iteration of the sub-flow runs **concurrently** using different parameters:\n\n```python\nclass SummarizeMultipleFiles(AsyncParallelBatchFlow):\n    async def prep_async(self, shared):\n        return [{\"filename\": f} for f in shared[\"files\"]]\n\nsub_flow = AsyncFlow(start=LoadAndSummarizeFile())\nparallel_flow = SummarizeMultipleFiles(start=sub_flow)\nawait parallel_flow.run_async(shared)\n```"
  },
  {
    "path": ".cursor/rules/design_pattern/agent.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Design Pattern, Agent\nglobs: \nalwaysApply: false\n---\n# Agent\n\nAgent is a powerful design pattern in which nodes can take dynamic actions based on the context.\n\n\n\n## Implement Agent with Graph\n\n1. **Context and Action:** Implement nodes that supply context and perform actions.  \n2. **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.\n3. **Agent Node:** Provide a prompt to decide action—for example:\n\n```python\nf\"\"\"\n### CONTEXT\nTask: {task_description}\nPrevious Actions: {previous_actions}\nCurrent State: {current_state}\n\n### ACTION SPACE\n[1] search\n  Description: Use web search to get results\n  Parameters:\n    - query (str): What to search for\n\n[2] answer\n  Description: Conclude based on the results\n  Parameters:\n    - result (str): Final answer to provide\n\n### NEXT ACTION\nDecide the next action based on the current context and available action space.\nReturn your response in the following format:\n\n```yaml\nthinking: |\n    \naction: \nparameters:\n    : \n```\"\"\"\n```\n\nThe core of building **high-performance** and **reliable** agents boils down to:\n\n1. **Context Management:** Provide *relevant, minimal context.* For example, rather than including an entire chat history, retrieve the most relevant via [RAG](mdc:./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.\n\n2. **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.\n\n## Example Good Action Design\n\n- **Incremental:** Feed content in manageable chunks (500 lines or 1 page) instead of all at once.\n\n- **Overview-zoom-in:** First provide high-level structure (table of contents, summary), then allow drilling into details (raw texts).\n\n- **Parameterized/Programmable:** Instead of fixed actions, enable parameterized (columns to select) or programmable (SQL queries) actions, for example, to read CSV files.\n\n- **Backtracking:** Let the agent undo the last step instead of restarting entirely, preserving progress when encountering errors or dead ends.\n\n## Example: Search Agent\n\nThis agent:\n1. Decides whether to search or answer\n2. If searches, loops back to decide if more search needed\n3. Answers when enough context gathered\n\n```python\nclass DecideAction(Node):\n    def prep(self, shared):\n        context = shared.get(\"context\", \"No previous search\")\n        query = shared[\"query\"]\n        return query, context\n        \n    def exec(self, inputs):\n        query, context = inputs\n        prompt = f\"\"\"\nGiven input: {query}\nPrevious search results: {context}\nShould I: 1) Search web for more info 2) Answer with current knowledge\nOutput in yaml:\n```yaml\naction: search/answer\nreason: why this action\nsearch_term: search phrase if action is search\n```\"\"\"\n        resp = call_llm(prompt)\n        yaml_str = resp.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        result = yaml.safe_load(yaml_str)\n        \n        assert isinstance(result, dict)\n        assert \"action\" in result\n        assert \"reason\" in result\n        assert result[\"action\"] in [\"search\", \"answer\"]\n        if result[\"action\"] == \"search\":\n            assert \"search_term\" in result\n        \n        return result\n\n    def post(self, shared, prep_res, exec_res):\n        if exec_res[\"action\"] == \"search\":\n            shared[\"search_term\"] = exec_res[\"search_term\"]\n        return exec_res[\"action\"]\n\nclass SearchWeb(Node):\n    def prep(self, shared):\n        return shared[\"search_term\"]\n        \n    def exec(self, search_term):\n        return search_web(search_term)\n    \n    def post(self, shared, prep_res, exec_res):\n        prev_searches = shared.get(\"context\", [])\n        shared[\"context\"] = prev_searches + [\n            {\"term\": shared[\"search_term\"], \"result\": exec_res}\n        ]\n        return \"decide\"\n        \nclass DirectAnswer(Node):\n    def prep(self, shared):\n        return shared[\"query\"], shared.get(\"context\", \"\")\n        \n    def exec(self, inputs):\n        query, context = inputs\n        return call_llm(f\"Context: {context}\\nAnswer: {query}\")\n\n    def post(self, shared, prep_res, exec_res):\n       print(f\"Answer: {exec_res}\")\n       shared[\"answer\"] = exec_res\n\n# Connect nodes\ndecide = DecideAction()\nsearch = SearchWeb()\nanswer = DirectAnswer()\n\ndecide - \"search\" >> search\ndecide - \"answer\" >> answer\nsearch - \"decide\" >> decide  # Loop back\n\nflow = Flow(start=decide)\nflow.run({\"query\": \"Who won the Nobel Prize in Physics 2024?\"})\n```\n"
  },
  {
    "path": ".cursor/rules/design_pattern/mapreduce.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Design Pattern, Map Reduce\nglobs: \nalwaysApply: false\n---\n# Map Reduce\n\nMapReduce is a design pattern suitable when you have either:\n- Large input data (e.g., multiple files to process), or\n- Large output data (e.g., multiple forms to fill)\n\nand there is a logical way to break the task into smaller, ideally independent parts. \n\n\n\nYou first break down the task using [BatchNode](../core_abstraction/batch.md) in the map phase, followed by aggregation in the reduce phase.\n\n### Example: Document Summarization\n\n```python\nclass SummarizeAllFiles(BatchNode):\n    def prep(self, shared):\n        files_dict = shared[\"files\"]  # e.g. 10 files\n        return list(files_dict.items())  # [(\"file1.txt\", \"aaa...\"), (\"file2.txt\", \"bbb...\"), ...]\n\n    def exec(self, one_file):\n        filename, file_content = one_file\n        summary_text = call_llm(f\"Summarize the following file:\\n{file_content}\")\n        return (filename, summary_text)\n\n    def post(self, shared, prep_res, exec_res_list):\n        shared[\"file_summaries\"] = dict(exec_res_list)\n\nclass CombineSummaries(Node):\n    def prep(self, shared):\n        return shared[\"file_summaries\"]\n\n    def exec(self, file_summaries):\n        # format as: \"File1: summary\\nFile2: summary...\\n\"\n        text_list = []\n        for fname, summ in file_summaries.items():\n            text_list.append(f\"{fname} summary:\\n{summ}\\n\")\n        big_text = \"\\n---\\n\".join(text_list)\n\n        return call_llm(f\"Combine these file summaries into one final summary:\\n{big_text}\")\n\n    def post(self, shared, prep_res, final_summary):\n        shared[\"all_files_summary\"] = final_summary\n\nbatch_node = SummarizeAllFiles()\ncombine_node = CombineSummaries()\nbatch_node >> combine_node\n\nflow = Flow(start=batch_node)\n\nshared = {\n    \"files\": {\n        \"file1.txt\": \"Alice was beginning to get very tired of sitting by her sister...\",\n        \"file2.txt\": \"Some other interesting text ...\",\n        # ...\n    }\n}\nflow.run(shared)\nprint(\"Individual Summaries:\", shared[\"file_summaries\"])\nprint(\"\\nFinal Summary:\\n\", shared[\"all_files_summary\"])\n```\n\n> **Performance Tip**: The example above works sequentially. You can speed up the map phase by running it in parallel. See [(Advanced) Parallel](../core_abstraction/parallel.md) for more details.\n{: .note }"
  },
  {
    "path": ".cursor/rules/design_pattern/multi_agent.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Design Pattern, (Advanced) Multi-Agents\nglobs: \nalwaysApply: false\n---\n# (Advanced) Multi-Agents\n\nMultiple [Agents](mdc:./flow.md) can work together by handling subtasks and communicating the progress. \nCommunication between agents is typically implemented using message queues in shared storage.\n\n> Most of time, you don't need Multi-Agents. Start with a simple solution first.\n{: .best-practice }\n\n### Example Agent Communication: Message Queue\n\nHere's a simple example showing how to implement agent communication using `asyncio.Queue`. \nThe agent listens for messages, processes them, and continues listening:\n\n```python\nclass AgentNode(AsyncNode):\n    async def prep_async(self, _):\n        message_queue = self.params[\"messages\"]\n        message = await message_queue.get()\n        print(f\"Agent received: {message}\")\n        return message\n\n# Create node and flow\nagent = AgentNode()\nagent >> agent  # connect to self\nflow = AsyncFlow(start=agent)\n\n# Create heartbeat sender\nasync def send_system_messages(message_queue):\n    counter = 0\n    messages = [\n        \"System status: all systems operational\",\n        \"Memory usage: normal\",\n        \"Network connectivity: stable\",\n        \"Processing load: optimal\"\n    ]\n    \n    while True:\n        message = f\"{messages[counter % len(messages)]} | timestamp_{counter}\"\n        await message_queue.put(message)\n        counter += 1\n        await asyncio.sleep(1)\n\nasync def main():\n    message_queue = asyncio.Queue()\n    shared = {}\n    flow.set_params({\"messages\": message_queue})\n    \n    # Run both coroutines\n    await asyncio.gather(\n        flow.run_async(shared),\n        send_system_messages(message_queue)\n    )\n    \nasyncio.run(main())\n```\n\nThe output:\n\n```\nAgent received: System status: all systems operational | timestamp_0\nAgent received: Memory usage: normal | timestamp_1\nAgent received: Network connectivity: stable | timestamp_2\nAgent received: Processing load: optimal | timestamp_3\n```\n\n### Interactive Multi-Agent Example: Taboo Game\n\nHere's a more complex example where two agents play the word-guessing game Taboo. \nOne agent provides hints while avoiding forbidden words, and another agent tries to guess the target word:\n\n```python\nclass AsyncHinter(AsyncNode):\n    async def prep_async(self, shared):\n        guess = await shared[\"hinter_queue\"].get()\n        if guess == \"GAME_OVER\":\n            return None\n        return shared[\"target_word\"], shared[\"forbidden_words\"], shared.get(\"past_guesses\", [])\n\n    async def exec_async(self, inputs):\n        if inputs is None:\n            return None\n        target, forbidden, past_guesses = inputs\n        prompt = f\"Generate hint for '{target}'\\nForbidden words: {forbidden}\"\n        if past_guesses:\n            prompt += f\"\\nPrevious wrong guesses: {past_guesses}\\nMake hint more specific.\"\n        prompt += \"\\nUse at most 5 words.\"\n        \n        hint = call_llm(prompt)\n        print(f\"\\nHinter: Here's your hint - {hint}\")\n        return hint\n\n    async def post_async(self, shared, prep_res, exec_res):\n        if exec_res is None:\n            return \"end\"\n        await shared[\"guesser_queue\"].put(exec_res)\n        return \"continue\"\n\nclass AsyncGuesser(AsyncNode):\n    async def prep_async(self, shared):\n        hint = await shared[\"guesser_queue\"].get()\n        return hint, shared.get(\"past_guesses\", [])\n\n    async def exec_async(self, inputs):\n        hint, past_guesses = inputs\n        prompt = f\"Given hint: {hint}, past wrong guesses: {past_guesses}, make a new guess. Directly reply a single word:\"\n        guess = call_llm(prompt)\n        print(f\"Guesser: I guess it's - {guess}\")\n        return guess\n\n    async def post_async(self, shared, prep_res, exec_res):\n        if exec_res.lower() == shared[\"target_word\"].lower():\n            print(\"Game Over - Correct guess!\")\n            await shared[\"hinter_queue\"].put(\"GAME_OVER\")\n            return \"end\"\n            \n        if \"past_guesses\" not in shared:\n            shared[\"past_guesses\"] = []\n        shared[\"past_guesses\"].append(exec_res)\n        \n        await shared[\"hinter_queue\"].put(exec_res)\n        return \"continue\"\n\nasync def main():\n    # Set up game\n    shared = {\n        \"target_word\": \"nostalgia\",\n        \"forbidden_words\": [\"memory\", \"past\", \"remember\", \"feeling\", \"longing\"],\n        \"hinter_queue\": asyncio.Queue(),\n        \"guesser_queue\": asyncio.Queue()\n    }\n    \n    print(\"Game starting!\")\n    print(f\"Target word: {shared['target_word']}\")\n    print(f\"Forbidden words: {shared['forbidden_words']}\")\n\n    # Initialize by sending empty guess to hinter\n    await shared[\"hinter_queue\"].put(\"\")\n\n    # Create nodes and flows\n    hinter = AsyncHinter()\n    guesser = AsyncGuesser()\n\n    # Set up flows\n    hinter_flow = AsyncFlow(start=hinter)\n    guesser_flow = AsyncFlow(start=guesser)\n\n    # Connect nodes to themselves\n    hinter - \"continue\" >> hinter\n    guesser - \"continue\" >> guesser\n\n    # Run both agents concurrently\n    await asyncio.gather(\n        hinter_flow.run_async(shared),\n        guesser_flow.run_async(shared)\n    )\n\nasyncio.run(main())\n```\n\nThe Output:\n\n```\nGame starting!\nTarget word: nostalgia\nForbidden words: ['memory', 'past', 'remember', 'feeling', 'longing']\n\nHinter: Here's your hint - Thinking of childhood summer days\nGuesser: I guess it's - popsicle\n\nHinter: Here's your hint - When childhood cartoons make you emotional\nGuesser: I guess it's - nostalgic\n\nHinter: Here's your hint - When old songs move you\nGuesser: I guess it's - memories\n\nHinter: Here's your hint - That warm emotion about childhood\nGuesser: I guess it's - nostalgia\nGame Over - Correct guess!\n```"
  },
  {
    "path": ".cursor/rules/design_pattern/rag.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Design Pattern, RAG\nglobs: \nalwaysApply: false\n---\n# RAG (Retrieval Augmented Generation)\n\nFor certain LLM tasks like answering questions, providing relevant context is essential. One common architecture is a **two-stage** RAG pipeline:\n\n\n\n1. **Offline stage**: Preprocess and index documents (\"building the index\").\n2. **Online stage**: Given a question, generate answers by retrieving the most relevant context.\n\n---\n## Stage 1: Offline Indexing\n\nWe create three Nodes:\n1. `ChunkDocs` – [chunks](../utility_function/chunking.md) raw text.\n2. `EmbedDocs` – [embeds](../utility_function/embedding.md) each chunk.\n3. `StoreIndex` – stores embeddings into a [vector database](../utility_function/vector.md).\n\n```python\nclass ChunkDocs(BatchNode):\n    def prep(self, shared):\n        # A list of file paths in shared[\"files\"]. We process each file.\n        return shared[\"files\"]\n\n    def exec(self, filepath):\n        # read file content. In real usage, do error handling.\n        with open(filepath, \"r\", encoding=\"utf-8\") as f:\n            text = f.read()\n        # chunk by 100 chars each\n        chunks = []\n        size = 100\n        for i in range(0, len(text), size):\n            chunks.append(text[i : i + size])\n        return chunks\n    \n    def post(self, shared, prep_res, exec_res_list):\n        # exec_res_list is a list of chunk-lists, one per file.\n        # flatten them all into a single list of chunks.\n        all_chunks = []\n        for chunk_list in exec_res_list:\n            all_chunks.extend(chunk_list)\n        shared[\"all_chunks\"] = all_chunks\n\nclass EmbedDocs(BatchNode):\n    def prep(self, shared):\n        return shared[\"all_chunks\"]\n\n    def exec(self, chunk):\n        return get_embedding(chunk)\n\n    def post(self, shared, prep_res, exec_res_list):\n        # Store the list of embeddings.\n        shared[\"all_embeds\"] = exec_res_list\n        print(f\"Total embeddings: {len(exec_res_list)}\")\n\nclass StoreIndex(Node):\n    def prep(self, shared):\n        # We'll read all embeds from shared.\n        return shared[\"all_embeds\"]\n\n    def exec(self, all_embeds):\n        # Create a vector index (faiss or other DB in real usage).\n        index = create_index(all_embeds)\n        return index\n\n    def post(self, shared, prep_res, index):\n        shared[\"index\"] = index\n\n# Wire them in sequence\nchunk_node = ChunkDocs()\nembed_node = EmbedDocs()\nstore_node = StoreIndex()\n\nchunk_node >> embed_node >> store_node\n\nOfflineFlow = Flow(start=chunk_node)\n```\n\nUsage example:\n\n```python\nshared = {\n    \"files\": [\"doc1.txt\", \"doc2.txt\"],  # any text files\n}\nOfflineFlow.run(shared)\n```\n\n---\n## Stage 2: Online Query & Answer\n\nWe have 3 nodes:\n1. `EmbedQuery` – embeds the user’s question.\n2. `RetrieveDocs` – retrieves top chunk from the index.\n3. `GenerateAnswer` – calls the LLM with the question + chunk to produce the final answer.\n\n```python\nclass EmbedQuery(Node):\n    def prep(self, shared):\n        return shared[\"question\"]\n\n    def exec(self, question):\n        return get_embedding(question)\n\n    def post(self, shared, prep_res, q_emb):\n        shared[\"q_emb\"] = q_emb\n\nclass RetrieveDocs(Node):\n    def prep(self, shared):\n        # We'll need the query embedding, plus the offline index/chunks\n        return shared[\"q_emb\"], shared[\"index\"], shared[\"all_chunks\"]\n\n    def exec(self, inputs):\n        q_emb, index, chunks = inputs\n        I, D = search_index(index, q_emb, top_k=1)\n        best_id = I[0][0]\n        relevant_chunk = chunks[best_id]\n        return relevant_chunk\n\n    def post(self, shared, prep_res, relevant_chunk):\n        shared[\"retrieved_chunk\"] = relevant_chunk\n        print(\"Retrieved chunk:\", relevant_chunk[:60], \"...\")\n\nclass GenerateAnswer(Node):\n    def prep(self, shared):\n        return shared[\"question\"], shared[\"retrieved_chunk\"]\n\n    def exec(self, inputs):\n        question, chunk = inputs\n        prompt = f\"Question: {question}\\nContext: {chunk}\\nAnswer:\"\n        return call_llm(prompt)\n\n    def post(self, shared, prep_res, answer):\n        shared[\"answer\"] = answer\n        print(\"Answer:\", answer)\n\nembed_qnode = EmbedQuery()\nretrieve_node = RetrieveDocs()\ngenerate_node = GenerateAnswer()\n\nembed_qnode >> retrieve_node >> generate_node\nOnlineFlow = Flow(start=embed_qnode)\n```\n\nUsage example:\n\n```python\n# Suppose we already ran OfflineFlow and have:\n# shared[\"all_chunks\"], shared[\"index\"], etc.\nshared[\"question\"] = \"Why do people like cats?\"\n\nOnlineFlow.run(shared)\n# final answer in shared[\"answer\"]\n```"
  },
  {
    "path": ".cursor/rules/design_pattern/structure.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Design Pattern, Structured Output\nglobs: \nalwaysApply: false\n---\n# Structured Output\n\nIn many use cases, you may want the LLM to output a specific structure, such as a list or a dictionary with predefined keys.\n\nThere are several approaches to achieve a structured output:\n- **Prompting** the LLM to strictly return a defined structure.\n- Using LLMs that natively support **schema enforcement**.\n- **Post-processing** the LLM's response to extract structured content.\n\nIn practice, **Prompting** is simple and reliable for modern LLMs.\n\n### Example Use Cases\n\n- Extracting Key Information \n\n```yaml\nproduct:\n  name: Widget Pro\n  price: 199.99\n  description: |\n    A high-quality widget designed for professionals.\n    Recommended for advanced users.\n```\n\n- Summarizing Documents into Bullet Points\n\n```yaml\nsummary:\n  - This product is easy to use.\n  - It is cost-effective.\n  - Suitable for all skill levels.\n```\n\n- Generating Configuration Files\n\n```yaml\nserver:\n  host: 127.0.0.1\n  port: 8080\n  ssl: true\n```\n\n## Prompt Engineering\n\nWhen prompting the LLM to produce **structured** output:\n1. **Wrap** the structure in code fences (e.g., `yaml`).\n2. **Validate** that all required fields exist (and let `Node` handles retry).\n\n### Example Text Summarization\n\n```python\nclass SummarizeNode(Node):\n    def exec(self, prep_res):\n        # Suppose `prep_res` is the text to summarize.\n        prompt = f\"\"\"\nPlease summarize the following text as YAML, with exactly 3 bullet points\n\n{prep_res}\n\nNow, output:\n```yaml\nsummary:\n  - bullet 1\n  - bullet 2\n  - bullet 3\n```\"\"\"\n        response = call_llm(prompt)\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n\n        import yaml\n        structured_result = yaml.safe_load(yaml_str)\n\n        assert \"summary\" in structured_result\n        assert isinstance(structured_result[\"summary\"], list)\n\n        return structured_result\n```\n\n> Besides using `assert` statements, another popular way to validate schemas is [Pydantic](https://github.com/pydantic/pydantic)\n{: .note }\n\n### Why YAML instead of JSON?\n\nCurrent LLMs struggle with escaping. YAML is easier with strings since they don't always need quotes.\n\n**In JSON**  \n\n```json\n{\n  \"dialogue\": \"Alice said: \\\"Hello Bob.\\\\nHow are you?\\\\nI am good.\\\"\"\n}\n```\n\n- Every double quote inside the string must be escaped with `\\\"`.\n- Each newline in the dialogue must be represented as `\\n`.\n\n**In YAML**  \n\n```yaml\ndialogue: |\n  Alice said: \"Hello Bob.\n  How are you?\n  I am good.\"\n```\n\n- No need to escape interior quotes—just place the entire text under a block literal (`|`).\n- Newlines are naturally preserved without needing `\\n`."
  },
  {
    "path": ".cursor/rules/design_pattern/workflow.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Design Pattern, Workflow\nglobs: \nalwaysApply: false\n---\n# Workflow\n\nMany 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.\n\n\n\n> - You don't want to make each task **too coarse**, because it may be *too complex for one LLM call*.\n> - 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*.\n> \n> You usually need multiple *iterations* to find the *sweet spot*. If the task has too many *edge cases*, consider using [Agents](mdc:./agent.md).\n{: .best-practice }\n\n### Example: Article Writing\n\n```python\nclass GenerateOutline(Node):\n    def prep(self, shared): return shared[\"topic\"]\n    def exec(self, topic): return call_llm(f\"Create a detailed outline for an article about {topic}\")\n    def post(self, shared, prep_res, exec_res): shared[\"outline\"] = exec_res\n\nclass WriteSection(Node):\n    def prep(self, shared): return shared[\"outline\"]\n    def exec(self, outline): return call_llm(f\"Write content based on this outline: {outline}\")\n    def post(self, shared, prep_res, exec_res): shared[\"draft\"] = exec_res\n\nclass ReviewAndRefine(Node):\n    def prep(self, shared): return shared[\"draft\"]\n    def exec(self, draft): return call_llm(f\"Review and improve this draft: {draft}\")\n    def post(self, shared, prep_res, exec_res): shared[\"final_article\"] = exec_res\n\n# Connect nodes\noutline = GenerateOutline()\nwrite = WriteSection()\nreview = ReviewAndRefine()\n\noutline >> write >> review\n\n# Create and run flow\nwriting_flow = Flow(start=outline)\nshared = {\"topic\": \"AI Safety\"}\nwriting_flow.run(shared)\n```\n\nFor *dynamic cases*, consider using [Agents](mdc:./agent.md)."
  },
  {
    "path": ".cursor/rules/guide_for_pocketflow.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Agentic Coding\nglobs: **/*.py\nalwaysApply: true\n---\n# DOCUMENTATION FIRST POLICY\n\n**CRITICAL INSTRUCTION**: When implementing a Pocket Flow app:\n\n1. **ALWAYS REQUEST MDC FILES FIRST** - Before writing any code, request and review all relevant MDC documentation files. This doc provides an explaination of the documents.\n2. **UNDERSTAND THE FRAMEWORK** - Gain comprehensive understanding of the Pocket Flow framework from documentation\n3. **AVOID ASSUMPTION-DRIVEN DEVELOPMENT** - Do not base your implementation on assumptions or guesswork. Even if the human didn't explicitly mention pocket flow in their request, if the code you are editing is using pocket flow, you should request relevant docs to help you understand best practice as well before editing.\n\n**VERIFICATION**: Begin each implementation with a brief summary of the documentation you've reviewed to inform your approach.\n\n# Agentic Coding: Humans Design, Agents code!\n\n> If you are an AI agent 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.\n{: .warning }\n\n## Agentic Coding Steps\n\nAgentic Coding should be a collaboration between Human System Design and Agent Implementation:\n\n| Steps                  | Human      | AI        | Comment                                                                 |\n|:-----------------------|:----------:|:---------:|:------------------------------------------------------------------------|\n| 1. Requirements | ★★★ High  | ★☆☆ Low   | Humans understand the requirements and context.                    |\n| 2. Flow          | ★★☆ Medium | ★★☆ Medium |  Humans specify the high-level design, and the AI fills in the details. |\n| 3. Utilities   | ★★☆ Medium | ★★☆ Medium | Humans provide available external APIs and integrations, and the AI helps with implementation. |\n| 4. Data          | ★☆☆ Low    | ★★★ High   | AI designs the data schema, and humans verify.                            |\n| 5. Node          | ★☆☆ Low   | ★★★ High  | The AI helps design the node based on the flow.          |\n| 6. Implementation      | ★☆☆ Low   | ★★★ High  |  The AI implements the flow based on the design. |\n| 7. Optimization        | ★★☆ Medium | ★★☆ Medium | Humans evaluate the results, and the AI helps optimize. |\n| 8. Reliability         | ★☆☆ Low   | ★★★ High  |  The AI writes test cases and addresses corner cases.     |\n\n1. **Requirements**: Clarify the requirements for your project, and evaluate whether an AI system is a good fit. \n    - Understand AI systems' strengths and limitations:\n      - **Good for**: Routine tasks requiring common sense (filling forms, replying to emails)\n      - **Good for**: Creative tasks with well-defined inputs (building slides, writing SQL)\n      - **Not good for**: Ambiguous problems requiring complex decision-making (business strategy, startup planning)\n    - **Keep It User-Centric:** Explain the \"problem\" from the user's perspective rather than just listing features.\n    - **Balance complexity vs. impact**: Aim to deliver the highest value features with minimal complexity early.\n\n2. **Flow Design**: Outline at a high level, describe how your AI system orchestrates nodes.\n    - Identify applicable design patterns (e.g., [Map Reduce], [Agent], [RAG]).\n      - For each node in the flow, start with a high-level one-line description of what it does.\n      - If using **Map Reduce**, specify how to map (what to split) and how to reduce (how to combine).\n      - If using **Agent**, specify what are the inputs (context) and what are the possible actions.\n      - If using **RAG**, specify what to embed, noting that there's usually both offline (indexing) and online (retrieval) workflows.\n    - Outline the flow and draw it in a mermaid diagram. For example:\n      ```mermaid\n      flowchart LR\n          start[Start] --> batch[Batch]\n          batch --> check[Check]\n          check -->|OK| process\n          check -->|Error| fix[Fix]\n          fix --> check\n          \n          subgraph process[Process]\n            step1[Step 1] --> step2[Step 2]\n          end\n          \n          process --> endNode[End]\n      ```\n    - > **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.  \n      {: .best-practice }\n\n3. **Utilities**: Based on the Flow Design, identify and implement necessary utility functions.\n    - Think of your AI system as the brain. It needs a body—these *external utility functions*—to interact with the real world:\n        \n\n        - Reading inputs (e.g., retrieving Slack messages, reading emails)\n        - Writing outputs (e.g., generating reports, sending emails)\n        - Using external tools (e.g., calling LLMs, searching the web)\n        - **NOTE**: *LLM-based tasks* (e.g., summarizing text, analyzing sentiment) are **NOT** utility functions; rather, they are *core functions* internal in the AI system.\n    - For each utility function, implement it and write a simple test.\n    - Document their input/output, as well as why they are necessary. For example:\n      - `name`: `get_embedding` (`utils/get_embedding.py`)\n      - `input`: `str`\n      - `output`: a vector of 3072 floats\n      - `necessity`: Used by the second node to embed text\n    - Example utility implementation:\n      ```python\n      # utils/call_llm.py\n      from openai import OpenAI\n\n      def call_llm(prompt):    \n          client = OpenAI(api_key=\"YOUR_API_KEY_HERE\")\n          r = client.chat.completions.create(\n              model=\"gpt-4o\",\n              messages=[{\"role\": \"user\", \"content\": prompt}]\n          )\n          return r.choices[0].message.content\n          \n      if __name__ == \"__main__\":\n          prompt = \"What is the meaning of life?\"\n          print(call_llm(prompt))\n      ```\n    - > **Sometimes, design Utilities before Flow:**  For example, for an LLM project to automate a legacy system, the bottleneck will likely be the available interface to that system. Start by designing the hardest utilities for interfacing, and then build the flow around them.\n      {: .best-practice }\n    - > **Avoid Exception Handling in Utilities**: If a utility function is called from a Node's `exec()` method, avoid using `try...except` blocks within the utility. Let the Node's built-in retry mechanism handle failures.\n      {: .warning }\n\n4. **Data Design**: Design the shared store that nodes will use to communicate.\n   - One core design principle for PocketFlow is to use a well-designed [shared store]—a data contract that all nodes agree upon to retrieve and store data.\n      - For simple systems, use an in-memory dictionary.\n      - For more complex systems or when persistence is required, use a database.\n      - **Don't Repeat Yourself**: Use in-memory references or foreign keys.\n      - Example shared store design:\n        ```python\n        shared = {\n            \"user\": {\n                \"id\": \"user123\",\n                \"context\": {                # Another nested dict\n                    \"weather\": {\"temp\": 72, \"condition\": \"sunny\"},\n                    \"location\": \"San Francisco\"\n                }\n            },\n            \"results\": {}                   # Empty dict to store outputs\n        }\n        ```\n\n5. **Node Design**: Plan how each node will read and write data, and use utility functions.\n   - For each [Node], describe its type, how it reads and writes data, and which utility function it uses. Keep it specific but high-level without codes. For example:\n     - `type`: Regular (or Batch, or Async)\n     - `prep`: Read \"text\" from the shared store\n     - `exec`: Call the embedding utility function. **Avoid exception handling here**; let the Node's retry mechanism manage failures.\n     - `post`: Write \"embedding\" to the shared store\n\n6. **Implementation**: Implement the initial nodes and flows based on the design.\n   - 🎉 If you've reached this step, humans have finished the design. Now *Agentic Coding* begins!\n   - **\"Keep it simple, stupid!\"** Avoid complex features and full-scale type checking.\n   - **FAIL FAST**! Leverage the built-in [Node] retry and fallback mechanisms to handle failures gracefully. This helps you quickly identify weak points in the system.\n   - Add logging throughout the code to facilitate debugging.\n\n7. **Optimization**:\n   - **Use Intuition**: For a quick initial evaluation, human intuition is often a good start.\n   - **Redesign Flow (Back to Step 3)**: Consider breaking down tasks further, introducing agentic decisions, or better managing input contexts.\n   - If your flow design is already solid, move on to micro-optimizations:\n     - **Prompt Engineering**: Use clear, specific instructions with examples to reduce ambiguity.\n     - **In-Context Learning**: Provide robust examples for tasks that are difficult to specify with instructions alone.\n\n   - > **You'll likely iterate a lot!** Expect to repeat Steps 3–6 hundreds of times.\n     >\n     > \n     {: .best-practice }\n\n8. **Reliability**  \n   - **Node Retries**: Add checks in the node `exec` to ensure outputs meet requirements, and consider increasing `max_retries` and `wait` times.\n   - **Logging and Visualization**: Maintain logs of all attempts and visualize node results for easier debugging.\n   - **Self-Evaluation**: Add a separate node (powered by an LLM) to review outputs when results are uncertain.\n\n## Example LLM Project File Structure\n\n```\nmy_project/\n├── main.py\n├── nodes.py\n├── flow.py\n├── utils/\n│   ├── __init__.py\n│   ├── call_llm.py\n│   └── search_web.py\n├── requirements.txt\n└── docs/\n    └── design.md\n```\n\n- **`docs/design.md`**: Contains project documentation for each step above. This should be *high-level* and *no-code*.\n- **`utils/`**: Contains all utility functions.\n  - It's recommended to dedicate one Python file to each API call, for example `call_llm.py` or `search_web.py`.\n  - Each file should also include a `main()` function to try that API call\n- **`nodes.py`**: Contains all the node definitions.\n  ```python\n  # nodes.py\n  from pocketflow import Node\n  from utils.call_llm import call_llm\n\n  class GetQuestionNode(Node):\n      def exec(self, _):\n          # Get question directly from user input\n          user_question = input(\"Enter your question: \")\n          return user_question\n      \n      def post(self, shared, prep_res, exec_res):\n          # Store the user's question\n          shared[\"question\"] = exec_res\n          return \"default\"  # Go to the next node\n\n  class AnswerNode(Node):\n      def prep(self, shared):\n          # Read question from shared\n          return shared[\"question\"]\n      \n      def exec(self, question):\n          # Call LLM to get the answer\n          return call_llm(question)\n      \n      def post(self, shared, prep_res, exec_res):\n          # Store the answer in shared\n          shared[\"answer\"] = exec_res\n  ```\n- **`flow.py`**: Implements functions that create flows by importing node definitions and connecting them.\n  ```python\n  # flow.py\n  from pocketflow import Flow\n  from nodes import GetQuestionNode, AnswerNode\n\n  def create_qa_flow():\n      \"\"\"Create and return a question-answering flow.\"\"\"\n      # Create nodes\n      get_question_node = GetQuestionNode()\n      answer_node = AnswerNode()\n      \n      # Connect nodes in sequence\n      get_question_node >> answer_node\n      \n      # Create flow starting with input node\n      return Flow(start=get_question_node)\n  ```\n- **`main.py`**: Serves as the project's entry point.\n  ```python\n  # main.py\n  from flow import create_qa_flow\n\n  # Example main function\n  # Please replace this with your own main function\n  def main():\n      shared = {\n          \"question\": None,  # Will be populated by GetQuestionNode from user input\n          \"answer\": None     # Will be populated by AnswerNode\n      }\n\n      # Create the flow and run it\n      qa_flow = create_qa_flow()\n      qa_flow.run(shared)\n      print(f\"Question: {shared['question']}\")\n      print(f\"Answer: {shared['answer']}\")\n\n  if __name__ == \"__main__\":\n      main()\n  ```\n\n\n# Pocket Flow\n\nA [100-line](https://github.com/the-pocket/PocketFlow/blob/main/pocketflow/__init__.py) minimalist LLM framework for *Agents, Task Decomposition, RAG, etc*.\n\n- **Lightweight**: Just the core graph abstraction in 100 lines. ZERO dependencies, and vendor lock-in.\n- **Expressive**: Everything you love from larger frameworks—([Multi-])[Agents], [Workflow], [RAG], and more.  \n- **Agentic-Coding**: Intuitive enough for AI agents to help humans build complex LLM applications.\n\n\n\n\n## Core Abstraction\n\nWe model the LLM workflow as a **Graph + Shared Store**:\n\n- [Node] handles simple (LLM) tasks.\n- [Flow] connects nodes through **Actions** (labeled edges).\n- [Shared Store] enables communication between nodes within flows.\n- [Batch] nodes/flows allow for data-intensive tasks.\n- [Async] nodes/flows allow waiting for asynchronous tasks.\n- [(Advanced) Parallel] nodes/flows handle I/O-bound tasks.\n\n\n\n## Design Pattern\n\nFrom there, it’s easy to implement popular design patterns:\n\n- [Agent] autonomously makes decisions.\n- [Workflow] chains multiple tasks into pipelines.\n- [RAG] integrates data retrieval with generation.\n- [Map Reduce] splits data tasks into Map and Reduce steps.\n- [Structured Output] formats outputs consistently.\n- [(Advanced) Multi-Agents] coordinate multiple agents.\n\n\n\n## Utility Function\n\nWe **do not** provide built-in utilities. Instead, we offer *examples*—please *implement your own*:\n\n- [LLM Wrapper]\n- [Viz and Debug]\n- [Web Search]\n- [Chunking]\n- [Embedding]\n- [Vector Databases]\n- [Text-to-Speech]\n\n**Why not built-in?**: I believe it's a *bad practice* for vendor-specific APIs in a general framework:\n- *API Volatility*: Frequent changes lead to heavy maintenance for hardcoded APIs.\n- *Flexibility*: You may want to switch vendors, use fine-tuned models, or run them locally.\n- *Optimizations*: Prompt caching, batching, and streaming are easier without vendor lock-in.\n\n## Ready to build your Apps? \n\nCheck out [Agentic Coding Guidance], the fastest way to develop LLM projects with Pocket Flow!\n"
  },
  {
    "path": ".cursor/rules/utility_function/chunking.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Utility Function, Text Chunking\nglobs: \nalwaysApply: false\n---\n# Text Chunking\n\nWe recommend some implementations of commonly used text chunking approaches.\n\n\n> Text Chunking is more a micro optimization, compared to the Flow Design.\n> \n> It's recommended to start with the Naive Chunking and optimize later.\n{: .best-practice }\n\n---\n\n## Example Python Code Samples\n\n### 1. Naive (Fixed-Size) Chunking\nSplits text by a fixed number of words, ignoring sentence or semantic boundaries.\n\n```python\ndef fixed_size_chunk(text, chunk_size=100):\n    chunks = []\n    for i in range(0, len(text), chunk_size):\n        chunks.append(text[i : i + chunk_size])\n    return chunks\n```\n\nHowever, sentences are often cut awkwardly, losing coherence.\n\n### 2. Sentence-Based Chunking\n\n```python\nimport nltk\n\ndef sentence_based_chunk(text, max_sentences=2):\n    sentences = nltk.sent_tokenize(text)\n    chunks = []\n    for i in range(0, len(sentences), max_sentences):\n        chunks.append(\" \".join(sentences[i : i + max_sentences]))\n    return chunks\n```\n\nHowever, might not handle very long sentences or paragraphs well.\n\n### 3. Other Chunking\n\n- **Paragraph-Based**: Split text by paragraphs (e.g., newlines). Large paragraphs can create big chunks.\n- **Semantic**: Use embeddings or topic modeling to chunk by semantic boundaries.\n- **Agentic**: Use an LLM to decide chunk boundaries based on context or meaning."
  },
  {
    "path": ".cursor/rules/utility_function/embedding.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Utility Function, Embedding\nglobs: \nalwaysApply: false\n---\n# Embedding\n\nBelow you will find an overview table of various text embedding APIs, along with example Python code.\n\n>  Embedding is more a micro optimization, compared to the Flow Design.\n> \n> It's recommended to start with the most convenient one and optimize later.\n{: .best-practice }\n\n\n| **API** | **Free Tier** | **Pricing Model** | **Docs** |\n| --- | --- | --- | --- |\n| **OpenAI** | ~$5 credit | ~$0.0001/1K tokens | [OpenAI Embeddings](https://platform.openai.com/docs/api-reference/embeddings) |\n| **Azure OpenAI** | $200 credit | Same as OpenAI (~$0.0001/1K tokens) | [Azure OpenAI Embeddings](https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource?tabs=portal) |\n| **Google Vertex AI** | $300 credit | ~$0.025 / million chars | [Vertex AI Embeddings](https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings/get-text-embeddings) |\n| **AWS Bedrock** | No free tier, but AWS credits may apply | ~$0.00002/1K tokens (Titan V2) | [Amazon Bedrock](https://docs.aws.amazon.com/bedrock/) |\n| **Cohere** | Limited free tier | ~$0.0001/1K tokens | [Cohere Embeddings](https://docs.cohere.com/docs/cohere-embed) |\n| **Hugging Face** | ~$0.10 free compute monthly | Pay per second of compute | [HF Inference API](https://huggingface.co/docs/api-inference) |\n| **Jina** | 1M tokens free | Pay per token after | [Jina Embeddings](https://jina.ai/embeddings/) |\n\n## Example Python Code\n\n### 1. OpenAI\n```python\nfrom openai import OpenAI\n\nclient = OpenAI(api_key=\"YOUR_API_KEY\")\nresponse = client.embeddings.create(\n    model=\"text-embedding-ada-002\",\n    input=text\n)\n    \n# Extract the embedding vector from the response\nembedding = response.data[0].embedding\nembedding = np.array(embedding, dtype=np.float32)\nprint(embedding)\n```\n\n### 2. Azure OpenAI\n```python\nimport openai\n\nopenai.api_type = \"azure\"\nopenai.api_base = \"https://YOUR_RESOURCE_NAME.openai.azure.com\"\nopenai.api_version = \"2023-03-15-preview\"\nopenai.api_key = \"YOUR_AZURE_API_KEY\"\n\nresp = openai.Embedding.create(engine=\"ada-embedding\", input=\"Hello world\")\nvec = resp[\"data\"][0][\"embedding\"]\nprint(vec)\n```\n\n### 3. Google Vertex AI\n```python\nfrom vertexai.preview.language_models import TextEmbeddingModel\nimport vertexai\n\nvertexai.init(project=\"YOUR_GCP_PROJECT_ID\", location=\"us-central1\")\nmodel = TextEmbeddingModel.from_pretrained(\"textembedding-gecko@001\")\n\nemb = model.get_embeddings([\"Hello world\"])\nprint(emb[0])\n```\n\n### 4. AWS Bedrock\n```python\nimport boto3, json\n\nclient = boto3.client(\"bedrock-runtime\", region_name=\"us-east-1\")\nbody = {\"inputText\": \"Hello world\"}\nresp = client.invoke_model(modelId=\"amazon.titan-embed-text-v2:0\", contentType=\"application/json\", body=json.dumps(body))\nresp_body = json.loads(resp[\"body\"].read())\nvec = resp_body[\"embedding\"]\nprint(vec)\n```\n\n### 5. Cohere\n```python\nimport cohere\n\nco = cohere.Client(\"YOUR_API_KEY\")\nresp = co.embed(texts=[\"Hello world\"])\nvec = resp.embeddings[0]\nprint(vec)\n```\n\n### 6. Hugging Face\n```python\nimport requests\n\nAPI_URL = \"https://api-inference.huggingface.co/models/sentence-transformers/all-MiniLM-L6-v2\"\nHEADERS = {\"Authorization\": \"Bearer YOUR_HF_TOKEN\"}\n\nres = requests.post(API_URL, headers=HEADERS, json={\"inputs\": \"Hello world\"})\nvec = res.json()[0]\nprint(vec)\n```\n\n### 7. Jina\n```python\nimport requests\n\nurl = \"https://api.jina.ai/v2/embed\"\nheaders = {\"Authorization\": \"Bearer YOUR_JINA_TOKEN\"}\npayload = {\"data\": [\"Hello world\"], \"model\": \"jina-embeddings-v3\"}\nres = requests.post(url, headers=headers, json=payload)\nvec = res.json()[\"data\"][0][\"embedding\"]\nprint(vec)\n```\n\n"
  },
  {
    "path": ".cursor/rules/utility_function/llm.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Utility Function, LLM Wrapper\nglobs: \nalwaysApply: false\n---\n# LLM Wrappers\n\nCheck out libraries like [litellm](https://github.com/BerriAI/litellm). \nHere, we provide some minimal example implementations:\n\n1. OpenAI\n    ```python\n    def call_llm(prompt):\n        from openai import OpenAI\n        client = OpenAI(api_key=\"YOUR_API_KEY_HERE\")\n        r = client.chat.completions.create(\n            model=\"gpt-4o\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return r.choices[0].message.content\n\n    # Example usage\n    call_llm(\"How are you?\")\n    ```\n    > Store the API key in an environment variable like OPENAI_API_KEY for security.\n    {: .best-practice }\n\n2. Claude (Anthropic)\n    ```python\n    def call_llm(prompt):\n        from anthropic import Anthropic\n        client = Anthropic(api_key=\"YOUR_API_KEY_HERE\")\n        r = client.messages.create(\n            model=\"claude-3-7-sonnet-20250219\",\n            max_tokens=3000,\n            messages=[\n                {\"role\": \"user\", \"content\": prompt}\n            ]\n        )\n        return r.content[0].text\n    ```\n\n3. Google (Generative AI Studio / PaLM API)\n    ```python\n    def call_llm(prompt):\n    from google import genai\n    client = genai.Client(api_key='GEMINI_API_KEY')\n        response = client.models.generate_content(\n        model='gemini-2.0-flash-001',\n        contents=prompt\n    )\n    return response.text\n    ```\n\n4. Azure (Azure OpenAI)\n    ```python\n    def call_llm(prompt):\n        from openai import AzureOpenAI\n        client = AzureOpenAI(\n            azure_endpoint=\"https://.openai.azure.com/\",\n            api_key=\"YOUR_API_KEY_HERE\",\n            api_version=\"2023-05-15\"\n        )\n        r = client.chat.completions.create(\n            model=\"\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return r.choices[0].message.content\n    ```\n\n5. Ollama (Local LLM)\n    ```python\n    def call_llm(prompt):\n        from ollama import chat\n        response = chat(\n            model=\"llama2\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return response.message.content\n    ```\n    \n6. DeepSeek\n    ```python\n    def call_llm(prompt):\n        from openai import OpenAI\n        client = OpenAI(api_key=\"YOUR_DEEPSEEK_API_KEY\", base_url=\"https://api.deepseek.com\")\n        r = client.chat.completions.create(\n            model=\"deepseek-chat\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return r.choices[0].message.content\n    ```\n\n\n## Improvements\nFeel free to enhance your `call_llm` function as needed. Here are examples:\n\n- Handle chat history:\n\n```python\ndef call_llm(messages):\n    from openai import OpenAI\n    client = OpenAI(api_key=\"YOUR_API_KEY_HERE\")\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=messages\n    )\n    return r.choices[0].message.content\n```\n\n- Add in-memory caching \n\n```python\nfrom functools import lru_cache\n\n@lru_cache(maxsize=1000)\ndef call_llm(prompt):\n    # Your implementation here\n    pass\n```\n\n> ⚠️ Caching conflicts with Node retries, as retries yield the same result.\n>\n> To address this, you could use cached results only if not retried.\n{: .warning }\n\n\n```python\nfrom functools import lru_cache\n\n@lru_cache(maxsize=1000)\ndef cached_call(prompt):\n    pass\n\ndef call_llm(prompt, use_cache):\n    if use_cache:\n        return cached_call(prompt)\n    # Call the underlying function directly\n    return cached_call.__wrapped__(prompt)\n\nclass SummarizeNode(Node):\n    def exec(self, text):\n        return call_llm(f\"Summarize: {text}\", self.cur_retry==0)\n```\n\n- Enable logging:\n\n```python\ndef call_llm(prompt):\n    import logging\n    logging.info(f\"Prompt: {prompt}\")\n    response = ... # Your implementation here\n    logging.info(f\"Response: {response}\")\n    return response\n```\n\n"
  },
  {
    "path": ".cursor/rules/utility_function/text_to_speech.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Utility Function, Text-to-Speech\nglobs: \nalwaysApply: false\n---\n# Text-to-Speech\n\n| **Service**          | **Free Tier**         | **Pricing Model**                                            | **Docs**                                                            |\n|----------------------|-----------------------|--------------------------------------------------------------|---------------------------------------------------------------------|\n| **Amazon Polly**     | 5M std + 1M neural   | ~$4 /M (std), ~$16 /M (neural) after free tier               | [Polly Docs](https://aws.amazon.com/polly/)                         |\n| **Google Cloud TTS** | 4M std + 1M WaveNet  | ~$4 /M (std), ~$16 /M (WaveNet) pay-as-you-go                | [Cloud TTS Docs](https://cloud.google.com/text-to-speech)           |\n| **Azure TTS**        | 500K neural ongoing  | ~$15 /M (neural), discount at higher volumes                 | [Azure TTS Docs](https://azure.microsoft.com/products/cognitive-services/text-to-speech/) |\n| **IBM Watson TTS**   | 10K chars Lite plan  | ~$0.02 /1K (i.e. ~$20 /M). Enterprise options available       | [IBM Watson Docs](https://www.ibm.com/cloud/watson-text-to-speech)   |\n| **ElevenLabs**       | 10K chars monthly    | From ~$5/mo (30K chars) up to $330/mo (2M chars). Enterprise  | [ElevenLabs Docs](https://elevenlabs.io)                            |\n\n## Example Python Code\n\n### Amazon Polly\n```python\nimport boto3\n\npolly = boto3.client(\"polly\", region_name=\"us-east-1\",\n                     aws_access_key_id=\"YOUR_AWS_ACCESS_KEY_ID\",\n                     aws_secret_access_key=\"YOUR_AWS_SECRET_ACCESS_KEY\")\n\nresp = polly.synthesize_speech(\n    Text=\"Hello from Polly!\",\n    OutputFormat=\"mp3\",\n    VoiceId=\"Joanna\"\n)\n\nwith open(\"polly.mp3\", \"wb\") as f:\n    f.write(resp[\"AudioStream\"].read())\n```\n\n### Google Cloud TTS\n```python\nfrom google.cloud import texttospeech\n\nclient = texttospeech.TextToSpeechClient()\ninput_text = texttospeech.SynthesisInput(text=\"Hello from Google Cloud TTS!\")\nvoice = texttospeech.VoiceSelectionParams(language_code=\"en-US\")\naudio_cfg = texttospeech.AudioConfig(audio_encoding=texttospeech.AudioEncoding.MP3)\n\nresp = client.synthesize_speech(input=input_text, voice=voice, audio_config=audio_cfg)\n\nwith open(\"gcloud_tts.mp3\", \"wb\") as f:\n    f.write(resp.audio_content)\n```\n\n### Azure TTS\n```python\nimport azure.cognitiveservices.speech as speechsdk\n\nspeech_config = speechsdk.SpeechConfig(\n    subscription=\"AZURE_KEY\", region=\"AZURE_REGION\")\naudio_cfg = speechsdk.audio.AudioConfig(filename=\"azure_tts.wav\")\n\nsynthesizer = speechsdk.SpeechSynthesizer(\n    speech_config=speech_config,\n    audio_config=audio_cfg\n)\n\nsynthesizer.speak_text_async(\"Hello from Azure TTS!\").get()\n```\n\n### IBM Watson TTS\n```python\nfrom ibm_watson import TextToSpeechV1\nfrom ibm_cloud_sdk_core.authenticators import IAMAuthenticator\n\nauth = IAMAuthenticator(\"IBM_API_KEY\")\nservice = TextToSpeechV1(authenticator=auth)\nservice.set_service_url(\"IBM_SERVICE_URL\")\n\nresp = service.synthesize(\n    \"Hello from IBM Watson!\",\n    voice=\"en-US_AllisonV3Voice\",\n    accept=\"audio/mp3\"\n).get_result()\n\nwith open(\"ibm_tts.mp3\", \"wb\") as f:\n    f.write(resp.content)\n```\n\n### ElevenLabs\n```python\nimport requests\n\napi_key = \"ELEVENLABS_KEY\"\nvoice_id = \"ELEVENLABS_VOICE\"\nurl = f\"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}\"\nheaders = {\"xi-api-key\": api_key, \"Content-Type\": \"application/json\"}\n\njson_data = {\n    \"text\": \"Hello from ElevenLabs!\",\n    \"voice_settings\": {\"stability\": 0.75, \"similarity_boost\": 0.75}\n}\n\nresp = requests.post(url, headers=headers, json=json_data)\n\nwith open(\"elevenlabs.mp3\", \"wb\") as f:\n    f.write(resp.content)\n```\n\n"
  },
  {
    "path": ".cursor/rules/utility_function/vector.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Utility Function, Vector Databases\nglobs: \nalwaysApply: false\n---\n# Vector Databases\n\n\nBelow is a  table of the popular vector search solutions:\n\n| **Tool** | **Free Tier** | **Pricing Model** | **Docs** |\n| --- | --- | --- | --- |\n| **FAISS** | N/A, self-host | Open-source | [Faiss.ai](https://faiss.ai) |\n| **Pinecone** | 2GB free | From $25/mo | [pinecone.io](https://pinecone.io) |\n| **Qdrant** | 1GB free cloud | Pay-as-you-go | [qdrant.tech](https://qdrant.tech) |\n| **Weaviate** | 14-day sandbox | From $25/mo | [weaviate.io](https://weaviate.io) |\n| **Milvus** | 5GB free cloud | PAYG or $99/mo dedicated | [milvus.io](https://milvus.io) |\n| **Chroma** | N/A, self-host | Free (Apache 2.0) | [trychroma.com](https://trychroma.com) |\n| **Redis** | 30MB free | From $5/mo | [redis.io](https://redis.io) |\n\n---\n## Example Python Code\n\nBelow are basic usage snippets for each tool.\n\n### FAISS\n```python\nimport faiss\nimport numpy as np\n\n# Dimensionality of embeddings\nd = 128\n\n# Create a flat L2 index\nindex = faiss.IndexFlatL2(d)\n\n# Random vectors\ndata = np.random.random((1000, d)).astype('float32')\nindex.add(data)\n\n# Query\nquery = np.random.random((1, d)).astype('float32')\nD, I = index.search(query, k=5)\n\nprint(\"Distances:\", D)\nprint(\"Neighbors:\", I)\n```\n\n### Pinecone\n```python\nimport pinecone\n\npinecone.init(api_key=\"YOUR_API_KEY\", environment=\"YOUR_ENV\")\n\nindex_name = \"my-index\"\n\n# Create the index if it doesn't exist\nif index_name not in pinecone.list_indexes():\n    pinecone.create_index(name=index_name, dimension=128)\n\n# Connect\nindex = pinecone.Index(index_name)\n\n# Upsert\nvectors = [\n    (\"id1\", [0.1]*128),\n    (\"id2\", [0.2]*128)\n]\nindex.upsert(vectors)\n\n# Query\nresponse = index.query([[0.15]*128], top_k=3)\nprint(response)\n```\n\n### Qdrant\n```python\nimport qdrant_client\nfrom qdrant_client.models import Distance, VectorParams, PointStruct\n\nclient = qdrant_client.QdrantClient(\n    url=\"https://YOUR-QDRANT-CLOUD-ENDPOINT\",\n    api_key=\"YOUR_API_KEY\"\n)\n\ncollection = \"my_collection\"\nclient.recreate_collection(\n    collection_name=collection,\n    vectors_config=VectorParams(size=128, distance=Distance.COSINE)\n)\n\npoints = [\n    PointStruct(id=1, vector=[0.1]*128, payload={\"type\": \"doc1\"}),\n    PointStruct(id=2, vector=[0.2]*128, payload={\"type\": \"doc2\"}),\n]\n\nclient.upsert(collection_name=collection, points=points)\n\nresults = client.search(\n    collection_name=collection,\n    query_vector=[0.15]*128,\n    limit=2\n)\nprint(results)\n```\n\n### Weaviate\n```python\nimport weaviate\n\nclient = weaviate.Client(\"https://YOUR-WEAVIATE-CLOUD-ENDPOINT\")\n\nschema = {\n    \"classes\": [\n        {\n            \"class\": \"Article\",\n            \"vectorizer\": \"none\"\n        }\n    ]\n}\nclient.schema.create(schema)\n\nobj = {\n    \"title\": \"Hello World\",\n    \"content\": \"Weaviate vector search\"\n}\nclient.data_object.create(obj, \"Article\", vector=[0.1]*128)\n\nresp = (\n    client.query\n    .get(\"Article\", [\"title\", \"content\"])\n    .with_near_vector({\"vector\": [0.15]*128})\n    .with_limit(3)\n    .do()\n)\nprint(resp)\n```\n\n### Milvus\n```python\nfrom pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection\nimport numpy as np\n\nconnections.connect(alias=\"default\", host=\"localhost\", port=\"19530\")\n\nfields = [\n    FieldSchema(name=\"id\", dtype=DataType.INT64, is_primary=True),\n    FieldSchema(name=\"embedding\", dtype=DataType.FLOAT_VECTOR, dim=128)\n]\nschema = CollectionSchema(fields)\ncollection = Collection(\"MyCollection\", schema)\n\nemb = np.random.rand(10, 128).astype('float32')\nids = list(range(10))\ncollection.insert([ids, emb])\n\nindex_params = {\n    \"index_type\": \"IVF_FLAT\",\n    \"params\": {\"nlist\": 128},\n    \"metric_type\": \"L2\"\n}\ncollection.create_index(\"embedding\", index_params)\ncollection.load()\n\nquery_emb = np.random.rand(1, 128).astype('float32')\nresults = collection.search(query_emb, \"embedding\", param={\"nprobe\": 10}, limit=3)\nprint(results)\n```\n\n### Chroma\n```python\nimport chromadb\nfrom chromadb.config import Settings\n\nclient = chromadb.Client(Settings(\n    chroma_db_impl=\"duckdb+parquet\",\n    persist_directory=\"./chroma_data\"\n))\n\ncoll = client.create_collection(\"my_collection\")\n\nvectors = [[0.1, 0.2, 0.3], [0.2, 0.2, 0.2]]\nmetas = [{\"doc\": \"text1\"}, {\"doc\": \"text2\"}]\nids = [\"id1\", \"id2\"]\ncoll.add(embeddings=vectors, metadatas=metas, ids=ids)\n\nres = coll.query(query_embeddings=[[0.15, 0.25, 0.3]], n_results=2)\nprint(res)\n```\n\n### Redis\n```python\nimport redis\nimport struct\n\nr = redis.Redis(host=\"localhost\", port=6379)\n\n# Create index\nr.execute_command(\n    \"FT.CREATE\", \"my_idx\", \"ON\", \"HASH\",\n    \"SCHEMA\", \"embedding\", \"VECTOR\", \"FLAT\", \"6\",\n    \"TYPE\", \"FLOAT32\", \"DIM\", \"128\",\n    \"DISTANCE_METRIC\", \"L2\"\n)\n\n# Insert\nvec = struct.pack('128f', *[0.1]*128)\nr.hset(\"doc1\", mapping={\"embedding\": vec})\n\n# Search\nqvec = struct.pack('128f', *[0.15]*128)\nq = \"*=>[KNN 3 @embedding $BLOB AS dist]\"\nres = r.ft(\"my_idx\").search(q, query_params={\"BLOB\": qvec})\nprint(res.docs)\n```\n\n"
  },
  {
    "path": ".cursor/rules/utility_function/viz.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Utility Function, Viz and Debug\nglobs: \nalwaysApply: false\n---\n# Visualization and Debugging\n\nSimilar to LLM wrappers, we **don't** provide built-in visualization and debugging. Here, we recommend some *minimal* (and incomplete) implementations These examples can serve as a starting point for your own tooling.\n\n## 1. Visualization with Mermaid\n\nThis code recursively traverses the nested graph, assigns unique IDs to each node, and treats Flow nodes as subgraphs to generate Mermaid syntax for a hierarchical visualization.\n\n{% raw %}\n```python\ndef build_mermaid(start):\n    ids, visited, lines = {}, set(), [\"graph LR\"]\n    ctr = 1\n    def get_id(n):\n        nonlocal ctr\n        return ids[n] if n in ids else (ids.setdefault(n, f\"N{ctr}\"), (ctr := ctr + 1))[0]\n    def link(a, b):\n        lines.append(f\"    {a} --> {b}\")\n    def walk(node, parent=None):\n        if node in visited:\n            return parent and link(parent, get_id(node))\n        visited.add(node)\n        if isinstance(node, Flow):\n            node.start_node and parent and link(parent, get_id(node.start_node))\n            lines.append(f\"\\n    subgraph sub_flow_{get_id(node)}[{type(node).__name__}]\")\n            node.start_node and walk(node.start_node)\n            for nxt in node.successors.values():\n                node.start_node and walk(nxt, get_id(node.start_node)) or (parent and link(parent, get_id(nxt))) or walk(nxt)\n            lines.append(\"    end\\n\")\n        else:\n            lines.append(f\"    {(nid := get_id(node))}['{type(node).__name__}']\")\n            parent and link(parent, nid)\n            [walk(nxt, nid) for nxt in node.successors.values()]\n    walk(start)\n    return \"\\n\".join(lines)\n```\n{% endraw %}\n\n\nFor example, suppose we have a complex Flow for data science:\n\n```python\nclass DataPrepBatchNode(BatchNode):\n    def prep(self,shared): return []\nclass ValidateDataNode(Node): pass\nclass FeatureExtractionNode(Node): pass\nclass TrainModelNode(Node): pass\nclass EvaluateModelNode(Node): pass\nclass ModelFlow(Flow): pass\nclass DataScienceFlow(Flow):pass\n\nfeature_node = FeatureExtractionNode()\ntrain_node = TrainModelNode()\nevaluate_node = EvaluateModelNode()\nfeature_node >> train_node >> evaluate_node\nmodel_flow = ModelFlow(start=feature_node)\ndata_prep_node = DataPrepBatchNode()\nvalidate_node = ValidateDataNode()\ndata_prep_node >> validate_node >> model_flow\ndata_science_flow = DataScienceFlow(start=data_prep_node)\nresult = build_mermaid(start=data_science_flow)\n```\n\nThe code generates a Mermaid diagram:\n\n```mermaid\ngraph LR\n    subgraph sub_flow_N1[DataScienceFlow]\n    N2['DataPrepBatchNode']\n    N3['ValidateDataNode']\n    N2 --> N3\n    N3 --> N4\n\n    subgraph sub_flow_N5[ModelFlow]\n    N4['FeatureExtractionNode']\n    N6['TrainModelNode']\n    N4 --> N6\n    N7['EvaluateModelNode']\n    N6 --> N7\n    end\n\n    end\n```\n\nFor visualization based on d3.js, check out [the cookbook](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-visualization).\n\n## 2. Call Stack Debugging\n\nIt would be useful to print the Node call stacks for debugging. This can be achieved by inspecting the runtime call stack:\n\n```python\nimport inspect\n\ndef get_node_call_stack():\n    stack = inspect.stack()\n    node_names = []\n    seen_ids = set()\n    for frame_info in stack[1:]:\n        local_vars = frame_info.frame.f_locals\n        if 'self' in local_vars:\n            caller_self = local_vars['self']\n            if isinstance(caller_self, BaseNode) and id(caller_self) not in seen_ids:\n                seen_ids.add(id(caller_self))\n                node_names.append(type(caller_self).__name__)\n    return node_names\n```\n\nFor example, suppose we have a complex Flow for data science:\n\n```python\nclass DataPrepBatchNode(BatchNode): \n    def prep(self, shared): return []\nclass ValidateDataNode(Node): pass\nclass FeatureExtractionNode(Node): pass\nclass TrainModelNode(Node): pass\nclass EvaluateModelNode(Node): \n    def prep(self, shared):\n        stack = get_node_call_stack()\n        print(\"Call stack:\", stack)\nclass ModelFlow(Flow): pass\nclass DataScienceFlow(Flow):pass\n\nfeature_node = FeatureExtractionNode()\ntrain_node = TrainModelNode()\nevaluate_node = EvaluateModelNode()\nfeature_node >> train_node >> evaluate_node\nmodel_flow = ModelFlow(start=feature_node)\ndata_prep_node = DataPrepBatchNode()\nvalidate_node = ValidateDataNode()\ndata_prep_node >> validate_node >> model_flow\ndata_science_flow = DataScienceFlow(start=data_prep_node)\ndata_science_flow.run({})\n```\n\nThe output would be: `Call stack: ['EvaluateModelNode', 'ModelFlow', 'DataScienceFlow']`\n\nFor a more complete implementation, check out [the cookbook](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-tracing)."
  },
  {
    "path": ".cursor/rules/utility_function/websearch.mdc",
    "content": "---\ndescription: Guidelines for using PocketFlow, Utility Function, Web Search\nglobs: \nalwaysApply: false\n---\n# Web Search\n\nWe recommend some implementations of commonly used web search tools.\n\n| **API**                         | **Free Tier**                                | **Pricing Model**                                              | **Docs**                                                  |\n|---------------------------------|-----------------------------------------------|-----------------------------------------------------------------|------------------------------------------------------------------------|\n| **Google Custom Search JSON API** | 100 queries/day free       | $5 per 1000 queries.           | [Link](https://developers.google.com/custom-search/v1/overview)        |\n| **Bing Web Search API**         | 1,000 queries/month               | $15–$25 per 1,000 queries. | [Link](https://azure.microsoft.com/en-us/services/cognitive-services/bing-web-search-api/) |\n| **DuckDuckGo Instant Answer**   | Completely free (Instant Answers only, **no URLs**) | No paid plans; usage unlimited, but data is limited             | [Link](https://duckduckgo.com/api)                                     |\n| **Brave Search API**         | 2,000 queries/month free | $3 per 1k queries for Base, $5 per 1k for Pro | [Link](https://brave.com/search/api/)                                  |\n| **SerpApi**              | 100 searches/month free            | Start at $75/month for 5,000 searches| [Link](https://serpapi.com/)                                             |\n| **RapidAPI**           | Many  options    | Many  options             | [Link](https://rapidapi.com/search?term=search&sortBy=ByRelevance)      |\n\n## Example Python Code\n\n### 1. Google Custom Search JSON API\n```python\nimport requests\n\nAPI_KEY = \"YOUR_API_KEY\"\nCX_ID = \"YOUR_CX_ID\"\nquery = \"example\"\n\nurl = \"https://www.googleapis.com/customsearch/v1\"\nparams = {\n    \"key\": API_KEY,\n    \"cx\": CX_ID,\n    \"q\": query\n}\n\nresponse = requests.get(url, params=params)\nresults = response.json()\nprint(results)\n```\n\n### 2. Bing Web Search API\n```python\nimport requests\n\nSUBSCRIPTION_KEY = \"YOUR_BING_API_KEY\"\nquery = \"example\"\n\nurl = \"https://api.bing.microsoft.com/v7.0/search\"\nheaders = {\"Ocp-Apim-Subscription-Key\": SUBSCRIPTION_KEY}\nparams = {\"q\": query}\n\nresponse = requests.get(url, headers=headers, params=params)\nresults = response.json()\nprint(results)\n```\n\n### 3. DuckDuckGo Instant Answer\n```python\nimport requests\n\nquery = \"example\"\nurl = \"https://api.duckduckgo.com/\"\nparams = {\n    \"q\": query,\n    \"format\": \"json\"\n}\n\nresponse = requests.get(url, params=params)\nresults = response.json()\nprint(results)\n```\n\n### 4. Brave Search API\n```python\nimport requests\n\nSUBSCRIPTION_TOKEN = \"YOUR_BRAVE_API_TOKEN\"\nquery = \"example\"\n\nurl = \"https://api.search.brave.com/res/v1/web/search\"\nheaders = {\n    \"X-Subscription-Token\": SUBSCRIPTION_TOKEN\n}\nparams = {\n    \"q\": query\n}\n\nresponse = requests.get(url, headers=headers, params=params)\nresults = response.json()\nprint(results)\n```\n\n### 5. SerpApi\n```python\nimport requests\n\nAPI_KEY = \"YOUR_SERPAPI_KEY\"\nquery = \"example\"\n\nurl = \"https://serpapi.com/search\"\nparams = {\n    \"engine\": \"google\",\n    \"q\": query,\n    \"api_key\": API_KEY\n}\n\nresponse = requests.get(url, params=params)\nresults = response.json()\nprint(results)\n```\n\n\n"
  },
  {
    "path": ".cursorrules",
    "content": "---\nlayout: default\ntitle: \"Agentic Coding\"\n---\n\n# Agentic Coding: Humans Design, Agents code!\n\n> If you are an AI agent 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.\n{: .warning }\n\n## Agentic Coding Steps\n\nAgentic Coding should be a collaboration between Human System Design and Agent Implementation:\n\n| Steps                  | Human      | AI        | Comment                                                                 |\n|:-----------------------|:----------:|:---------:|:------------------------------------------------------------------------|\n| 1. Requirements | ★★★ High  | ★☆☆ Low   | Humans understand the requirements and context.                    |\n| 2. Flow          | ★★☆ Medium | ★★☆ Medium |  Humans specify the high-level design, and the AI fills in the details. |\n| 3. Utilities   | ★★☆ Medium | ★★☆ Medium | Humans provide available external APIs and integrations, and the AI helps with implementation. |\n| 4. Data          | ★☆☆ Low    | ★★★ High   | AI designs the data schema, and humans verify.                            |\n| 5. Node          | ★☆☆ Low   | ★★★ High  | The AI helps design the node based on the flow.          |\n| 6. Implementation      | ★☆☆ Low   | ★★★ High  |  The AI implements the flow based on the design. |\n| 7. Optimization        | ★★☆ Medium | ★★☆ Medium | Humans evaluate the results, and the AI helps optimize. |\n| 8. Reliability         | ★☆☆ Low   | ★★★ High  |  The AI writes test cases and addresses corner cases.     |\n\n1. **Requirements**: Clarify the requirements for your project, and evaluate whether an AI system is a good fit. \n    - Understand AI systems' strengths and limitations:\n      - **Good for**: Routine tasks requiring common sense (filling forms, replying to emails)\n      - **Good for**: Creative tasks with well-defined inputs (building slides, writing SQL)\n      - **Not good for**: Ambiguous problems requiring complex decision-making (business strategy, startup planning)\n    - **Keep It User-Centric:** Explain the \"problem\" from the user's perspective rather than just listing features.\n    - **Balance complexity vs. impact**: Aim to deliver the highest value features with minimal complexity early.\n\n2. **Flow Design**: Outline at a high level, describe how your AI system orchestrates nodes.\n    - Identify applicable design patterns (e.g., [Map Reduce](./design_pattern/mapreduce.md), [Agent](./design_pattern/agent.md), [RAG](./design_pattern/rag.md)).\n      - For each node in the flow, start with a high-level one-line description of what it does.\n      - If using **Map Reduce**, specify how to map (what to split) and how to reduce (how to combine).\n      - If using **Agent**, specify what are the inputs (context) and what are the possible actions.\n      - If using **RAG**, specify what to embed, noting that there's usually both offline (indexing) and online (retrieval) workflows.\n    - Outline the flow and draw it in a mermaid diagram. For example:\n      ```mermaid\n      flowchart LR\n          start[Start] --> batch[Batch]\n          batch --> check[Check]\n          check -->|OK| process\n          check -->|Error| fix[Fix]\n          fix --> check\n          \n          subgraph process[Process]\n            step1[Step 1] --> step2[Step 2]\n          end\n          \n          process --> endNode[End]\n      ```\n    - > **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.  \n      {: .best-practice }\n\n3. **Utilities**: Based on the Flow Design, identify and implement necessary utility functions.\n    - Think of your AI system as the brain. It needs a body—these *external utility functions*—to interact with the real world:\n        <div align=\"center\"><img src=\"https://github.com/the-pocket/.github/raw/main/assets/utility.png?raw=true\" width=\"400\"/></div>\n\n        - Reading inputs (e.g., retrieving Slack messages, reading emails)\n        - Writing outputs (e.g., generating reports, sending emails)\n        - Using external tools (e.g., calling LLMs, searching the web)\n        - **NOTE**: *LLM-based tasks* (e.g., summarizing text, analyzing sentiment) are **NOT** utility functions; rather, they are *core functions* internal in the AI system.\n    - For each utility function, implement it and write a simple test.\n    - Document their input/output, as well as why they are necessary. For example:\n      - `name`: `get_embedding` (`utils/get_embedding.py`)\n      - `input`: `str`\n      - `output`: a vector of 3072 floats\n      - `necessity`: Used by the second node to embed text\n    - Example utility implementation:\n      ```python\n      # utils/call_llm.py\n      from openai import OpenAI\n\n      def call_llm(prompt):    \n          client = OpenAI(api_key=\"YOUR_API_KEY_HERE\")\n          r = client.chat.completions.create(\n              model=\"gpt-4o\",\n              messages=[{\"role\": \"user\", \"content\": prompt}]\n          )\n          return r.choices[0].message.content\n          \n      if __name__ == \"__main__\":\n          prompt = \"What is the meaning of life?\"\n          print(call_llm(prompt))\n      ```\n    - > **Sometimes, design Utilities before Flow:**  For example, for an LLM project to automate a legacy system, the bottleneck will likely be the available interface to that system. Start by designing the hardest utilities for interfacing, and then build the flow around them.\n      {: .best-practice }\n    - > **Avoid Exception Handling in Utilities**: If a utility function is called from a Node's `exec()` method, avoid using `try...except` blocks within the utility. Let the Node's built-in retry mechanism handle failures.\n      {: .warning }\n\n4. **Data Design**: Design the shared store that nodes will use to communicate.\n   - One core design principle for PocketFlow is to use a well-designed [shared store](./core_abstraction/communication.md)—a data contract that all nodes agree upon to retrieve and store data.\n      - For simple systems, use an in-memory dictionary.\n      - For more complex systems or when persistence is required, use a database.\n      - **Don't Repeat Yourself**: Use in-memory references or foreign keys.\n      - Example shared store design:\n        ```python\n        shared = {\n            \"user\": {\n                \"id\": \"user123\",\n                \"context\": {                # Another nested dict\n                    \"weather\": {\"temp\": 72, \"condition\": \"sunny\"},\n                    \"location\": \"San Francisco\"\n                }\n            },\n            \"results\": {}                   # Empty dict to store outputs\n        }\n        ```\n\n5. **Node Design**: Plan how each node will read and write data, and use utility functions.\n   - 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:\n     - `type`: Regular (or Batch, or Async)\n     - `prep`: Read \"text\" from the shared store\n     - `exec`: Call the embedding utility function. **Avoid exception handling here**; let the Node's retry mechanism manage failures.\n     - `post`: Write \"embedding\" to the shared store\n\n6. **Implementation**: Implement the initial nodes and flows based on the design.\n   - 🎉 If you've reached this step, humans have finished the design. Now *Agentic Coding* begins!\n   - **\"Keep it simple, stupid!\"** Avoid complex features and full-scale type checking.\n   - **FAIL FAST**! Leverage the built-in [Node](./core_abstraction/node.md) retry and fallback mechanisms to handle failures gracefully. This helps you quickly identify weak points in the system.\n   - Add logging throughout the code to facilitate debugging.\n\n7. **Optimization**:\n   - **Use Intuition**: For a quick initial evaluation, human intuition is often a good start.\n   - **Redesign Flow (Back to Step 3)**: Consider breaking down tasks further, introducing agentic decisions, or better managing input contexts.\n   - If your flow design is already solid, move on to micro-optimizations:\n     - **Prompt Engineering**: Use clear, specific instructions with examples to reduce ambiguity.\n     - **In-Context Learning**: Provide robust examples for tasks that are difficult to specify with instructions alone.\n\n   - > **You'll likely iterate a lot!** Expect to repeat Steps 3–6 hundreds of times.\n     >\n     > <div align=\"center\"><img src=\"https://github.com/the-pocket/.github/raw/main/assets/success.png?raw=true\" width=\"400\"/></div>\n     {: .best-practice }\n\n8. **Reliability**  \n   - **Node Retries**: Add checks in the node `exec` to ensure outputs meet requirements, and consider increasing `max_retries` and `wait` times.\n   - **Logging and Visualization**: Maintain logs of all attempts and visualize node results for easier debugging.\n   - **Self-Evaluation**: Add a separate node (powered by an LLM) to review outputs when results are uncertain.\n\n## Example LLM Project File Structure\n\n```\nmy_project/\n├── main.py\n├── nodes.py\n├── flow.py\n├── utils/\n│   ├── __init__.py\n│   ├── call_llm.py\n│   └── search_web.py\n├── requirements.txt\n└── docs/\n    └── design.md\n```\n\n- **`requirements.txt`**: Lists the Python dependencies for the project.\n  ```\n  PyYAML\n  pocketflow\n  ```\n\n- **`docs/design.md`**: Contains project documentation for each step above. This should be *high-level* and *no-code*.\n  ~~~\n  # Design Doc: Your Project Name\n\n  > Please DON'T remove notes for AI\n\n  ## Requirements\n\n  > Notes for AI: Keep it simple and clear.\n  > If the requirements are abstract, write concrete user stories\n\n\n  ## Flow Design\n\n  > Notes for AI:\n  > 1. Consider the design patterns of agent, map-reduce, rag, and workflow. Apply them if they fit.\n  > 2. Present a concise, high-level description of the workflow.\n\n  ### Applicable Design Pattern:\n\n  1. Map the file summary into chunks, then reduce these chunks into a final summary.\n  2. Agentic file finder\n    - *Context*: The entire summary of the file\n    - *Action*: Find the file\n\n  ### Flow high-level Design:\n\n  1. **First Node**: This node is for ...\n  2. **Second Node**: This node is for ...\n  3. **Third Node**: This node is for ...\n\n  ```mermaid\n  flowchart TD\n      firstNode[First Node] --> secondNode[Second Node]\n      secondNode --> thirdNode[Third Node]\n  ```\n  ## Utility Functions\n\n  > Notes for AI:\n  > 1. Understand the utility function definition thoroughly by reviewing the doc.\n  > 2. Include only the necessary utility functions, based on nodes in the flow.\n\n  1. **Call LLM** (`utils/call_llm.py`)\n    - *Input*: prompt (str)\n    - *Output*: response (str)\n    - Generally used by most nodes for LLM tasks\n\n  2. **Embedding** (`utils/get_embedding.py`)\n    - *Input*: str\n    - *Output*: a vector of 3072 floats\n    - Used by the second node to embed text\n\n  ## Node Design\n\n  ### Shared Store\n\n  > Notes for AI: Try to minimize data redundancy\n\n  The shared store structure is organized as follows:\n\n  ```python\n  shared = {\n      \"key\": \"value\"\n  }\n  ```\n\n  ### Node Steps\n\n  > Notes for AI: Carefully decide whether to use Batch/Async Node/Flow.\n\n  1. First Node\n    - *Purpose*: Provide a short explanation of the node’s function\n    - *Type*: Decide between Regular, Batch, or Async\n    - *Steps*:\n      - *prep*: Read \"key\" from the shared store\n      - *exec*: Call the utility function\n      - *post*: Write \"key\" to the shared store\n\n  2. Second Node\n    ...\n  ~~~\n\n\n- **`utils/`**: Contains all utility functions.\n  - It's recommended to dedicate one Python file to each API call, for example `call_llm.py` or `search_web.py`.\n  - Each file should also include a `main()` function to try that API call\n  ```python\n  from google import genai\n  import os\n\n  def call_llm(prompt: str) -> str:\n      client = genai.Client(\n          api_key=os.getenv(\"GEMINI_API_KEY\", \"\"),\n      )\n      model = os.getenv(\"GEMINI_MODEL\", \"gemini-2.5-flash\")\n      response = client.models.generate_content(model=model, contents=[prompt])\n      return response.text\n\n  if __name__ == \"__main__\":\n      test_prompt = \"Hello, how are you?\"\n\n      # First call - should hit the API\n      print(\"Making call...\")\n      response1 = call_llm(test_prompt, use_cache=False)\n      print(f\"Response: {response1}\")\n  ```\n\n- **`nodes.py`**: Contains all the node definitions.\n  ```python\n  # nodes.py\n  from pocketflow import Node\n  from utils.call_llm import call_llm\n\n  class GetQuestionNode(Node):\n      def exec(self, _):\n          # Get question directly from user input\n          user_question = input(\"Enter your question: \")\n          return user_question\n      \n      def post(self, shared, prep_res, exec_res):\n          # Store the user's question\n          shared[\"question\"] = exec_res\n          return \"default\"  # Go to the next node\n\n  class AnswerNode(Node):\n      def prep(self, shared):\n          # Read question from shared\n          return shared[\"question\"]\n      \n      def exec(self, question):\n          # Call LLM to get the answer\n          return call_llm(question)\n      \n      def post(self, shared, prep_res, exec_res):\n          # Store the answer in shared\n          shared[\"answer\"] = exec_res\n  ```\n- **`flow.py`**: Implements functions that create flows by importing node definitions and connecting them.\n  ```python\n  # flow.py\n  from pocketflow import Flow\n  from nodes import GetQuestionNode, AnswerNode\n\n  def create_qa_flow():\n      \"\"\"Create and return a question-answering flow.\"\"\"\n      # Create nodes\n      get_question_node = GetQuestionNode()\n      answer_node = AnswerNode()\n      \n      # Connect nodes in sequence\n      get_question_node >> answer_node\n      \n      # Create flow starting with input node\n      return Flow(start=get_question_node)\n  ```\n- **`main.py`**: Serves as the project's entry point.\n  ```python\n  # main.py\n  from flow import create_qa_flow\n\n  # Example main function\n  # Please replace this with your own main function\n  def main():\n      shared = {\n          \"question\": None,  # Will be populated by GetQuestionNode from user input\n          \"answer\": None     # Will be populated by AnswerNode\n      }\n\n      # Create the flow and run it\n      qa_flow = create_qa_flow()\n      qa_flow.run(shared)\n      print(f\"Question: {shared['question']}\")\n      print(f\"Answer: {shared['answer']}\")\n\n  if __name__ == \"__main__\":\n      main()\n  ```\n\n================================================\nFile: docs/index.md\n================================================\n---\nlayout: default\ntitle: \"Home\"\nnav_order: 1\n---\n\n# Pocket Flow\n\nA [100-line](https://github.com/the-pocket/PocketFlow/blob/main/pocketflow/__init__.py) minimalist LLM framework for *Agents, Task Decomposition, RAG, etc*.\n\n- **Lightweight**: Just the core graph abstraction in 100 lines. ZERO dependencies, and vendor lock-in.\n- **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.  \n- **Agentic-Coding**: Intuitive enough for AI agents to help humans build complex LLM applications.\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/meme.jpg?raw=true\" width=\"400\"/>\n</div>\n\n## Core Abstraction\n\nWe model the LLM workflow as a **Graph + Shared Store**:\n\n- [Node](./core_abstraction/node.md) handles simple (LLM) tasks.\n- [Flow](./core_abstraction/flow.md) connects nodes through **Actions** (labeled edges).\n- [Shared Store](./core_abstraction/communication.md) enables communication between nodes within flows.\n- [Batch](./core_abstraction/batch.md) nodes/flows allow for data-intensive tasks.\n- [Async](./core_abstraction/async.md) nodes/flows allow waiting for asynchronous tasks.\n- [(Advanced) Parallel](./core_abstraction/parallel.md) nodes/flows handle I/O-bound tasks.\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/abstraction.png\" width=\"500\"/>\n</div>\n\n## Design Pattern\n\nFrom there, it’s easy to implement popular design patterns:\n\n- [Agent](./design_pattern/agent.md) autonomously makes decisions.\n- [Workflow](./design_pattern/workflow.md) chains multiple tasks into pipelines.\n- [RAG](./design_pattern/rag.md) integrates data retrieval with generation.\n- [Map Reduce](./design_pattern/mapreduce.md) splits data tasks into Map and Reduce steps.\n- [Structured Output](./design_pattern/structure.md) formats outputs consistently.\n- [(Advanced) Multi-Agents](./design_pattern/multi_agent.md) coordinate multiple agents.\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/design.png\" width=\"500\"/>\n</div>\n\n## Utility Function\n\nWe **do not** provide built-in utilities. Instead, we offer *examples*—please *implement your own*:\n\n- [LLM Wrapper](./utility_function/llm.md)\n- [Viz and Debug](./utility_function/viz.md)\n- [Web Search](./utility_function/websearch.md)\n- [Chunking](./utility_function/chunking.md)\n- [Embedding](./utility_function/embedding.md)\n- [Vector Databases](./utility_function/vector.md)\n- [Text-to-Speech](./utility_function/text_to_speech.md)\n\n**Why not built-in?**: I believe it's a *bad practice* for vendor-specific APIs in a general framework:\n- *API Volatility*: Frequent changes lead to heavy maintenance for hardcoded APIs.\n- *Flexibility*: You may want to switch vendors, use fine-tuned models, or run them locally.\n- *Optimizations*: Prompt caching, batching, and streaming are easier without vendor lock-in.\n\n## Ready to build your Apps? \n\nCheck out [Agentic Coding Guidance](./guide.md), the fastest way to develop LLM projects with Pocket Flow!\n\n================================================\nFile: docs/core_abstraction/async.md\n================================================\n---\nlayout: default\ntitle: \"(Advanced) Async\"\nparent: \"Core Abstraction\"\nnav_order: 5\n---\n\n# (Advanced) Async\n\n**Async** Nodes implement `prep_async()`, `exec_async()`, `exec_fallback_async()`, and/or `post_async()`. This is useful for:\n\n1. **prep_async()**: For *fetching/reading data (files, APIs, DB)* in an I/O-friendly way.\n2. **exec_async()**: Typically used for async LLM calls.\n3. **post_async()**: For *awaiting user feedback*, *coordinating across multi-agents* or any additional async steps after `exec_async()`.\n\n**Note**: `AsyncNode` must be wrapped in `AsyncFlow`. `AsyncFlow` can also include regular (sync) nodes.\n\n### Example\n\n```python\nclass SummarizeThenVerify(AsyncNode):\n    async def prep_async(self, shared):\n        # Example: read a file asynchronously\n        doc_text = await read_file_async(shared[\"doc_path\"])\n        return doc_text\n\n    async def exec_async(self, prep_res):\n        # Example: async LLM call\n        summary = await call_llm_async(f\"Summarize: {prep_res}\")\n        return summary\n\n    async def post_async(self, shared, prep_res, exec_res):\n        # Example: wait for user feedback\n        decision = await gather_user_feedback(exec_res)\n        if decision == \"approve\":\n            shared[\"summary\"] = exec_res\n            return \"approve\"\n        return \"deny\"\n\nsummarize_node = SummarizeThenVerify()\nfinal_node = Finalize()\n\n# Define transitions\nsummarize_node - \"approve\" >> final_node\nsummarize_node - \"deny\"    >> summarize_node  # retry\n\nflow = AsyncFlow(start=summarize_node)\n\nasync def main():\n    shared = {\"doc_path\": \"document.txt\"}\n    await flow.run_async(shared)\n    print(\"Final Summary:\", shared.get(\"summary\"))\n\nasyncio.run(main())\n```\n\n================================================\nFile: docs/core_abstraction/batch.md\n================================================\n---\nlayout: default\ntitle: \"Batch\"\nparent: \"Core Abstraction\"\nnav_order: 4\n---\n\n# Batch\n\n**Batch** makes it easier to handle large inputs in one Node or **rerun** a Flow multiple times. Example use cases:\n- **Chunk-based** processing (e.g., splitting large texts).\n- **Iterative** processing over lists of input items (e.g., user queries, files, URLs).\n\n## 1. BatchNode\n\nA **BatchNode** extends `Node` but changes `prep()` and `exec()`:\n\n- **`prep(shared)`**: returns an **iterable** (e.g., list, generator).\n- **`exec(item)`**: called **once** per item in that iterable.\n- **`post(shared, prep_res, exec_res_list)`**: after all items are processed, receives a **list** of results (`exec_res_list`) and returns an **Action**.\n\n\n### Example: Summarize a Large File\n\n```python\nclass MapSummaries(BatchNode):\n    def prep(self, shared):\n        # Suppose we have a big file; chunk it\n        content = shared[\"data\"]\n        chunk_size = 10000\n        chunks = [content[i:i+chunk_size] for i in range(0, len(content), chunk_size)]\n        return chunks\n\n    def exec(self, chunk):\n        prompt = f\"Summarize this chunk in 10 words: {chunk}\"\n        summary = call_llm(prompt)\n        return summary\n\n    def post(self, shared, prep_res, exec_res_list):\n        combined = \"\\n\".join(exec_res_list)\n        shared[\"summary\"] = combined\n        return \"default\"\n\nmap_summaries = MapSummaries()\nflow = Flow(start=map_summaries)\nflow.run(shared)\n```\n\n---\n\n## 2. BatchFlow\n\nA **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.\n\n### Example: Summarize Many Files\n\n```python\nclass SummarizeAllFiles(BatchFlow):\n    def prep(self, shared):\n        # Return a list of param dicts (one per file)\n        filenames = list(shared[\"data\"].keys())  # e.g., [\"file1.txt\", \"file2.txt\", ...]\n        return [{\"filename\": fn} for fn in filenames]\n\n# Suppose we have a per-file Flow (e.g., load_file >> summarize >> reduce):\nsummarize_file = SummarizeFile(start=load_file)\n\n# Wrap that flow into a BatchFlow:\nsummarize_all_files = SummarizeAllFiles(start=summarize_file)\nsummarize_all_files.run(shared)\n```\n\n### Under the Hood\n1. `prep(shared)` returns a list of param dicts—e.g., `[{filename: \"file1.txt\"}, {filename: \"file2.txt\"}, ...]`.\n2. The **BatchFlow** loops through each dict. For each one:\n   - It merges the dict with the BatchFlow’s own `params`.\n   - It calls `flow.run(shared)` using the merged result.\n3. This means the sub-Flow is run **repeatedly**, once for every param dict.\n\n---\n\n## 3. Nested or Multi-Level Batches\n\nYou can nest a **BatchFlow** in another **BatchFlow**. For instance:\n- **Outer** batch: returns a list of diretory param dicts (e.g., `{\"directory\": \"/pathA\"}`, `{\"directory\": \"/pathB\"}`, ...).\n- **Inner** batch: returning a list of per-file param dicts.\n\nAt 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.\n\n```python\n\nclass FileBatchFlow(BatchFlow):\n    def prep(self, shared):\n        directory = self.params[\"directory\"]\n        # e.g., files = [\"file1.txt\", \"file2.txt\", ...]\n        files = [f for f in os.listdir(directory) if f.endswith(\".txt\")]\n        return [{\"filename\": f} for f in files]\n\nclass DirectoryBatchFlow(BatchFlow):\n    def prep(self, shared):\n        directories = [ \"/path/to/dirA\", \"/path/to/dirB\"]\n        return [{\"directory\": d} for d in directories]\n\n# MapSummaries have params like {\"directory\": \"/path/to/dirA\", \"filename\": \"file1.txt\"}\ninner_flow = FileBatchFlow(start=MapSummaries())\nouter_flow = DirectoryBatchFlow(start=inner_flow)\n```\n\n================================================\nFile: docs/core_abstraction/communication.md\n================================================\n---\nlayout: default\ntitle: \"Communication\"\nparent: \"Core Abstraction\"\nnav_order: 3\n---\n\n# Communication\n\nNodes and Flows **communicate** in 2 ways:\n\n1. **Shared Store (for almost all the cases)** \n\n   - A global data structure (often an in-mem dict) that all nodes can read ( `prep()`) and write (`post()`).  \n   - Great for data results, large content, or anything multiple nodes need.\n   - You shall design the data structure and populate it ahead.\n     \n   - > **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).\n     {: .best-practice }\n\n2. **Params (only for [Batch](./batch.md))** \n   - 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**.\n   - Good for identifiers like filenames or numeric IDs, in Batch mode.\n\nIf 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).\n\n---\n\n## 1. Shared Store\n\n### Overview\n\nA shared store is typically an in-mem dictionary, like:\n```python\nshared = {\"data\": {}, \"summary\": {}, \"config\": {...}, ...}\n```\n\nIt 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.\n\n### Example\n\n```python\nclass LoadData(Node):\n    def post(self, shared, prep_res, exec_res):\n        # We write data to shared store\n        shared[\"data\"] = \"Some text content\"\n        return None\n\nclass Summarize(Node):\n    def prep(self, shared):\n        # We read data from shared store\n        return shared[\"data\"]\n\n    def exec(self, prep_res):\n        # Call LLM to summarize\n        prompt = f\"Summarize: {prep_res}\"\n        summary = call_llm(prompt)\n        return summary\n\n    def post(self, shared, prep_res, exec_res):\n        # We write summary to shared store\n        shared[\"summary\"] = exec_res\n        return \"default\"\n\nload_data = LoadData()\nsummarize = Summarize()\nload_data >> summarize\nflow = Flow(start=load_data)\n\nshared = {}\nflow.run(shared)\n```\n\nHere:\n- `LoadData` writes to `shared[\"data\"]`.\n- `Summarize` reads from `shared[\"data\"]`, summarizes, and writes to `shared[\"summary\"]`.\n\n---\n\n## 2. Params\n\n**Params** let you store *per-Node* or *per-Flow* config that doesn't need to live in the shared store. They are:\n- **Immutable** during a Node's run cycle (i.e., they don't change mid-`prep->exec->post`).\n- **Set** via `set_params()`.\n- **Cleared** and updated each time a parent Flow calls it.\n\n> Only set the uppermost Flow params because others will be overwritten by the parent Flow. \n> \n> If you need to set child node params, see [Batch](./batch.md).\n{: .warning }\n\nTypically, **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.\n\n### Example\n\n```python\n# 1) Create a Node that uses params\nclass SummarizeFile(Node):\n    def prep(self, shared):\n        # Access the node's param\n        filename = self.params[\"filename\"]\n        return shared[\"data\"].get(filename, \"\")\n\n    def exec(self, prep_res):\n        prompt = f\"Summarize: {prep_res}\"\n        return call_llm(prompt)\n\n    def post(self, shared, prep_res, exec_res):\n        filename = self.params[\"filename\"]\n        shared[\"summary\"][filename] = exec_res\n        return \"default\"\n\n# 2) Set params\nnode = SummarizeFile()\n\n# 3) Set Node params directly (for testing)\nnode.set_params({\"filename\": \"doc1.txt\"})\nnode.run(shared)\n\n# 4) Create Flow\nflow = Flow(start=node)\n\n# 5) Set Flow params (overwrites node params)\nflow.set_params({\"filename\": \"doc2.txt\"})\nflow.run(shared)  # The node summarizes doc2, not doc1\n```\n\n================================================\nFile: docs/core_abstraction/flow.md\n================================================\n---\nlayout: default\ntitle: \"Flow\"\nparent: \"Core Abstraction\"\nnav_order: 2\n---\n\n# Flow\n\nA **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()`.\n\n## 1. Action-based Transitions\n\nEach Node's `post()` returns an **Action** string. By default, if `post()` doesn't return anything, we treat that as `\"default\"`.\n\nYou define transitions with the syntax:\n\n1. **Basic default transition**: `node_a >> node_b`\n  This means if `node_a.post()` returns `\"default\"`, go to `node_b`. \n  (Equivalent to `node_a - \"default\" >> node_b`)\n\n2. **Named action transition**: `node_a - \"action_name\" >> node_b`\n  This means if `node_a.post()` returns `\"action_name\"`, go to `node_b`.\n\nIt's possible to create loops, branching, or multi-step flows.\n\n## 2. Creating a Flow\n\nA **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.\n\n### Example: Simple Sequence\n\nHere's a minimal flow of two nodes in a chain:\n\n```python\nnode_a >> node_b\nflow = Flow(start=node_a)\nflow.run(shared)\n```\n\n- When you run the flow, it executes `node_a`.  \n- Suppose `node_a.post()` returns `\"default\"`.  \n- The flow then sees `\"default\"` Action is linked to `node_b` and runs `node_b`.  \n- `node_b.post()` returns `\"default\"` but we didn't define `node_b >> something_else`. So the flow ends there.\n\n### Example: Branching & Looping\n\nHere's a simple expense approval flow that demonstrates branching and looping. The `ReviewExpense` node can return three possible Actions:\n\n- `\"approved\"`: expense is approved, move to payment processing\n- `\"needs_revision\"`: expense needs changes, send back for revision \n- `\"rejected\"`: expense is denied, finish the process\n\nWe can wire them like this:\n\n```python\n# Define the flow connections\nreview - \"approved\" >> payment        # If approved, process payment\nreview - \"needs_revision\" >> revise   # If needs changes, go to revision\nreview - \"rejected\" >> finish         # If rejected, finish the process\n\nrevise >> review   # After revision, go back for another review\npayment >> finish  # After payment, finish the process\n\nflow = Flow(start=review)\n```\n\nLet's see how it flows:\n\n1. If `review.post()` returns `\"approved\"`, the expense moves to the `payment` node\n2. If `review.post()` returns `\"needs_revision\"`, it goes to the `revise` node, which then loops back to `review`\n3. If `review.post()` returns `\"rejected\"`, it moves to the `finish` node and stops\n\n```mermaid\nflowchart TD\n    review[Review Expense] -->|approved| payment[Process Payment]\n    review -->|needs_revision| revise[Revise Report]\n    review -->|rejected| finish[Finish Process]\n\n    revise --> review\n    payment --> finish\n```\n\n### Running Individual Nodes vs. Running a Flow\n\n- `node.run(shared)`: Just runs that node alone (calls `prep->exec->post()`), returns an Action. \n- `flow.run(shared)`: Executes from the start node, follows Actions to the next node, and so on until the flow can't continue.\n\n> `node.run(shared)` **does not** proceed to the successor.\n> This is mainly for debugging or testing a single node.\n> \n> Always use `flow.run(...)` in production to ensure the full pipeline runs correctly.\n{: .warning }\n\n## 3. Nested Flows\n\nA **Flow** can act like a Node, which enables powerful composition patterns. This means you can:\n\n1. Use a Flow as a Node within another Flow's transitions.  \n2. Combine multiple smaller Flows into a larger Flow for reuse.  \n3. Node `params` will be a merging of **all** parents' `params`.\n\n### Flow's Node Methods\n\nA **Flow** is also a **Node**, so it will run `prep()` and `post()`. However:\n\n- It **won't** run `exec()`, as its main logic is to orchestrate its nodes.\n- `post()` always receives `None` for `exec_res` and should instead get the flow execution results from the shared store.\n\n### Basic Flow Nesting\n\nHere's how to connect a flow to another node:\n\n```python\n# Create a sub-flow\nnode_a >> node_b\nsubflow = Flow(start=node_a)\n\n# Connect it to another node\nsubflow >> node_c\n\n# Create the parent flow\nparent_flow = Flow(start=subflow)\n```\n\nWhen `parent_flow.run()` executes:\n1. It starts `subflow`\n2. `subflow` runs through its nodes (`node_a->node_b`)\n3. After `subflow` completes, execution continues to `node_c`\n\n### Example: Order Processing Pipeline\n\nHere's a practical example that breaks down order processing into nested flows:\n\n```python\n# Payment processing sub-flow\nvalidate_payment >> process_payment >> payment_confirmation\npayment_flow = Flow(start=validate_payment)\n\n# Inventory sub-flow\ncheck_stock >> reserve_items >> update_inventory\ninventory_flow = Flow(start=check_stock)\n\n# Shipping sub-flow\ncreate_label >> assign_carrier >> schedule_pickup\nshipping_flow = Flow(start=create_label)\n\n# Connect the flows into a main order pipeline\npayment_flow >> inventory_flow >> shipping_flow\n\n# Create the master flow\norder_pipeline = Flow(start=payment_flow)\n\n# Run the entire pipeline\norder_pipeline.run(shared_data)\n```\n\nThis creates a clean separation of concerns while maintaining a clear execution path:\n\n```mermaid\nflowchart LR\n    subgraph order_pipeline[Order Pipeline]\n        subgraph paymentFlow[\"Payment Flow\"]\n            A[Validate Payment] --> B[Process Payment] --> C[Payment Confirmation]\n        end\n\n        subgraph inventoryFlow[\"Inventory Flow\"]\n            D[Check Stock] --> E[Reserve Items] --> F[Update Inventory]\n        end\n\n        subgraph shippingFlow[\"Shipping Flow\"]\n            G[Create Label] --> H[Assign Carrier] --> I[Schedule Pickup]\n        end\n\n        paymentFlow --> inventoryFlow\n        inventoryFlow --> shippingFlow\n    end\n```\n\n================================================\nFile: docs/core_abstraction/node.md\n================================================\n---\nlayout: default\ntitle: \"Node\"\nparent: \"Core Abstraction\"\nnav_order: 1\n---\n\n# Node\n\nA **Node** is the smallest building block. Each Node has 3 steps `prep->exec->post`:\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/node.png?raw=true\" width=\"400\"/>\n</div>\n\n1. `prep(shared)`\n   - **Read and preprocess data** from `shared` store. \n   - Examples: *query DB, read files, or serialize data into a string*.\n   - Return `prep_res`, which is used by `exec()` and `post()`.\n\n2. `exec(prep_res)`\n   - **Execute compute logic**, with optional retries and error handling (below).\n   - Examples: *(mostly) LLM calls, remote APIs, tool use*.\n   - ⚠️ This shall be only for compute and **NOT** access `shared`.\n   - ⚠️ If retries enabled, ensure idempotent implementation.\n   - ⚠️ Defer exception handling to the Node's built-in retry mechanism.\n   - Return `exec_res`, which is passed to `post()`.\n\n3. `post(shared, prep_res, exec_res)`\n   - **Postprocess and write data** back to `shared`.\n   - Examples: *update DB, change states, log results*.\n   - **Decide the next action** by returning a *string* (`action = \"default\"` if *None*).\n\n> **Why 3 steps?** To enforce the principle of *separation of concerns*. The data storage and data processing are operated separately.\n>\n> All steps are *optional*. E.g., you can only implement `prep` and `post` if you just need to process data.\n{: .note }\n\n### Fault Tolerance & Retries\n\nYou can **retry** `exec()` if it raises an exception via two parameters when define the Node:\n\n- `max_retries` (int): Max times to run `exec()`. The default is `1` (**no** retry).\n- `wait` (int): The time to wait (in **seconds**) before next retry. By default, `wait=0` (no waiting). \n`wait` is helpful when you encounter rate-limits or quota errors from your LLM provider and need to back off.\n\n```python \nmy_node = SummarizeFile(max_retries=3, wait=10)\n```\n\nWhen an exception occurs in `exec()`, the Node automatically retries until:\n\n- It either succeeds, or\n- The Node has retried `max_retries - 1` times already and fails on the last attempt.\n\nYou can get the current retry times (0-based) from `self.cur_retry`.\n\n```python \nclass RetryNode(Node):\n    def exec(self, prep_res):\n        print(f\"Retry {self.cur_retry} times\")\n        raise Exception(\"Failed\")\n```\n\n### Graceful Fallback\n\nTo **gracefully handle** the exception (after all retries) rather than raising it, override:\n\n```python \ndef exec_fallback(self, prep_res, exc):\n    raise exc\n```\n\nBy default, it just re-raises exception. But you can return a fallback result instead, which becomes the `exec_res` passed to `post()`.\n\n### Example: Summarize file\n\n```python \nclass SummarizeFile(Node):\n    def prep(self, shared):\n        return shared[\"data\"]\n\n    def exec(self, prep_res):\n        if not prep_res:\n            return \"Empty file content\"\n        prompt = f\"Summarize this text in 10 words: {prep_res}\"\n        summary = call_llm(prompt)  # might fail\n        return summary\n\n    def exec_fallback(self, prep_res, exc):\n        # Provide a simple fallback instead of crashing\n        return \"There was an error processing your request.\"\n\n    def post(self, shared, prep_res, exec_res):\n        shared[\"summary\"] = exec_res\n        # Return \"default\" by not returning\n\nsummarize_node = SummarizeFile(max_retries=3)\n\n# node.run() calls prep->exec->post\n# If exec() fails, it retries up to 3 times before calling exec_fallback()\naction_result = summarize_node.run(shared)\n\nprint(\"Action returned:\", action_result)  # \"default\"\nprint(\"Summary stored:\", shared[\"summary\"])\n```\n\n================================================\nFile: docs/core_abstraction/parallel.md\n================================================\n---\nlayout: default\ntitle: \"(Advanced) Parallel\"\nparent: \"Core Abstraction\"\nnav_order: 6\n---\n\n# (Advanced) Parallel\n\n**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. \n\n> 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.\n{: .warning }\n\n> - **Ensure Tasks Are Independent**: If each item depends on the output of a previous item, **do not** parallelize.\n> \n> - **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).\n> \n> - **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.\n{: .best-practice }\n\n## AsyncParallelBatchNode\n\nLike **AsyncBatchNode**, but run `exec_async()` in **parallel**:\n\n```python\nclass ParallelSummaries(AsyncParallelBatchNode):\n    async def prep_async(self, shared):\n        # e.g., multiple texts\n        return shared[\"texts\"]\n\n    async def exec_async(self, text):\n        prompt = f\"Summarize: {text}\"\n        return await call_llm_async(prompt)\n\n    async def post_async(self, shared, prep_res, exec_res_list):\n        shared[\"summary\"] = \"\\n\\n\".join(exec_res_list)\n        return \"default\"\n\nnode = ParallelSummaries()\nflow = AsyncFlow(start=node)\n```\n\n## AsyncParallelBatchFlow\n\nParallel version of **BatchFlow**. Each iteration of the sub-flow runs **concurrently** using different parameters:\n\n```python\nclass SummarizeMultipleFiles(AsyncParallelBatchFlow):\n    async def prep_async(self, shared):\n        return [{\"filename\": f} for f in shared[\"files\"]]\n\nsub_flow = AsyncFlow(start=LoadAndSummarizeFile())\nparallel_flow = SummarizeMultipleFiles(start=sub_flow)\nawait parallel_flow.run_async(shared)\n```\n\n================================================\nFile: docs/design_pattern/agent.md\n================================================\n---\nlayout: default\ntitle: \"Agent\"\nparent: \"Design Pattern\"\nnav_order: 1\n---\n\n# Agent\n\nAgent is a powerful design pattern in which nodes can take dynamic actions based on the context.\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/agent.png?raw=true\" width=\"350\"/>\n</div>\n\n## Implement Agent with Graph\n\n1. **Context and Action:** Implement nodes that supply context and perform actions.  \n2. **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.\n3. **Agent Node:** Provide a prompt to decide action—for example:\n\n```python\nf\"\"\"\n### CONTEXT\nTask: {task_description}\nPrevious Actions: {previous_actions}\nCurrent State: {current_state}\n\n### ACTION SPACE\n[1] search\n  Description: Use web search to get results\n  Parameters:\n    - query (str): What to search for\n\n[2] answer\n  Description: Conclude based on the results\n  Parameters:\n    - result (str): Final answer to provide\n\n### NEXT ACTION\nDecide the next action based on the current context and available action space.\nReturn your response in the following format:\n\n```yaml\nthinking: |\n    <your step-by-step reasoning process>\naction: <action_name>\nparameters:\n    <parameter_name>: <parameter_value>\n```\"\"\"\n```\n\nThe core of building **high-performance** and **reliable** agents boils down to:\n\n1. **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.\n\n2. **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.\n\n## Example Good Action Design\n\n- **Incremental:** Feed content in manageable chunks (500 lines or 1 page) instead of all at once.\n\n- **Overview-zoom-in:** First provide high-level structure (table of contents, summary), then allow drilling into details (raw texts).\n\n- **Parameterized/Programmable:** Instead of fixed actions, enable parameterized (columns to select) or programmable (SQL queries) actions, for example, to read CSV files.\n\n- **Backtracking:** Let the agent undo the last step instead of restarting entirely, preserving progress when encountering errors or dead ends.\n\n## Example: Search Agent\n\nThis agent:\n1. Decides whether to search or answer\n2. If searches, loops back to decide if more search needed\n3. Answers when enough context gathered\n\n```python\nclass DecideAction(Node):\n    def prep(self, shared):\n        context = shared.get(\"context\", \"No previous search\")\n        query = shared[\"query\"]\n        return query, context\n        \n    def exec(self, inputs):\n        query, context = inputs\n        prompt = f\"\"\"\nGiven input: {query}\nPrevious search results: {context}\nShould I: 1) Search web for more info 2) Answer with current knowledge\nOutput in yaml:\n```yaml\naction: search/answer\nreason: why this action\nsearch_term: search phrase if action is search\n```\"\"\"\n        resp = call_llm(prompt)\n        yaml_str = resp.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        result = yaml.safe_load(yaml_str)\n        \n        assert isinstance(result, dict)\n        assert \"action\" in result\n        assert \"reason\" in result\n        assert result[\"action\"] in [\"search\", \"answer\"]\n        if result[\"action\"] == \"search\":\n            assert \"search_term\" in result\n        \n        return result\n\n    def post(self, shared, prep_res, exec_res):\n        if exec_res[\"action\"] == \"search\":\n            shared[\"search_term\"] = exec_res[\"search_term\"]\n        return exec_res[\"action\"]\n\nclass SearchWeb(Node):\n    def prep(self, shared):\n        return shared[\"search_term\"]\n        \n    def exec(self, search_term):\n        return search_web(search_term)\n    \n    def post(self, shared, prep_res, exec_res):\n        prev_searches = shared.get(\"context\", [])\n        shared[\"context\"] = prev_searches + [\n            {\"term\": shared[\"search_term\"], \"result\": exec_res}\n        ]\n        return \"decide\"\n        \nclass DirectAnswer(Node):\n    def prep(self, shared):\n        return shared[\"query\"], shared.get(\"context\", \"\")\n        \n    def exec(self, inputs):\n        query, context = inputs\n        return call_llm(f\"Context: {context}\\nAnswer: {query}\")\n\n    def post(self, shared, prep_res, exec_res):\n       print(f\"Answer: {exec_res}\")\n       shared[\"answer\"] = exec_res\n\n# Connect nodes\ndecide = DecideAction()\nsearch = SearchWeb()\nanswer = DirectAnswer()\n\ndecide - \"search\" >> search\ndecide - \"answer\" >> answer\nsearch - \"decide\" >> decide  # Loop back\n\nflow = Flow(start=decide)\nflow.run({\"query\": \"Who won the Nobel Prize in Physics 2024?\"})\n```\n\n================================================\nFile: docs/design_pattern/mapreduce.md\n================================================\n---\nlayout: default\ntitle: \"Map Reduce\"\nparent: \"Design Pattern\"\nnav_order: 4\n---\n\n# Map Reduce\n\nMapReduce is a design pattern suitable when you have either:\n- Large input data (e.g., multiple files to process), or\n- Large output data (e.g., multiple forms to fill)\n\nand there is a logical way to break the task into smaller, ideally independent parts. \n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/mapreduce.png?raw=true\" width=\"400\"/>\n</div>\n\nYou first break down the task using [BatchNode](../core_abstraction/batch.md) in the map phase, followed by aggregation in the reduce phase.\n\n### Example: Document Summarization\n\n```python\nclass SummarizeAllFiles(BatchNode):\n    def prep(self, shared):\n        files_dict = shared[\"files\"]  # e.g. 10 files\n        return list(files_dict.items())  # [(\"file1.txt\", \"aaa...\"), (\"file2.txt\", \"bbb...\"), ...]\n\n    def exec(self, one_file):\n        filename, file_content = one_file\n        summary_text = call_llm(f\"Summarize the following file:\\n{file_content}\")\n        return (filename, summary_text)\n\n    def post(self, shared, prep_res, exec_res_list):\n        shared[\"file_summaries\"] = dict(exec_res_list)\n\nclass CombineSummaries(Node):\n    def prep(self, shared):\n        return shared[\"file_summaries\"]\n\n    def exec(self, file_summaries):\n        # format as: \"File1: summary\\nFile2: summary...\\n\"\n        text_list = []\n        for fname, summ in file_summaries.items():\n            text_list.append(f\"{fname} summary:\\n{summ}\\n\")\n        big_text = \"\\n---\\n\".join(text_list)\n\n        return call_llm(f\"Combine these file summaries into one final summary:\\n{big_text}\")\n\n    def post(self, shared, prep_res, final_summary):\n        shared[\"all_files_summary\"] = final_summary\n\nbatch_node = SummarizeAllFiles()\ncombine_node = CombineSummaries()\nbatch_node >> combine_node\n\nflow = Flow(start=batch_node)\n\nshared = {\n    \"files\": {\n        \"file1.txt\": \"Alice was beginning to get very tired of sitting by her sister...\",\n        \"file2.txt\": \"Some other interesting text ...\",\n        # ...\n    }\n}\nflow.run(shared)\nprint(\"Individual Summaries:\", shared[\"file_summaries\"])\nprint(\"\\nFinal Summary:\\n\", shared[\"all_files_summary\"])\n```\n\n================================================\nFile: docs/design_pattern/rag.md\n================================================\n---\nlayout: default\ntitle: \"RAG\"\nparent: \"Design Pattern\"\nnav_order: 3\n---\n\n# RAG (Retrieval Augmented Generation)\n\nFor certain LLM tasks like answering questions, providing relevant context is essential. One common architecture is a **two-stage** RAG pipeline:\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/rag.png?raw=true\" width=\"400\"/>\n</div>\n\n1. **Offline stage**: Preprocess and index documents (\"building the index\").\n2. **Online stage**: Given a question, generate answers by retrieving the most relevant context.\n\n---\n## Stage 1: Offline Indexing\n\nWe create three Nodes:\n1. `ChunkDocs` – [chunks](../utility_function/chunking.md) raw text.\n2. `EmbedDocs` – [embeds](../utility_function/embedding.md) each chunk.\n3. `StoreIndex` – stores embeddings into a [vector database](../utility_function/vector.md).\n\n```python\nclass ChunkDocs(BatchNode):\n    def prep(self, shared):\n        # A list of file paths in shared[\"files\"]. We process each file.\n        return shared[\"files\"]\n\n    def exec(self, filepath):\n        # read file content. In real usage, do error handling.\n        with open(filepath, \"r\", encoding=\"utf-8\") as f:\n            text = f.read()\n        # chunk by 100 chars each\n        chunks = []\n        size = 100\n        for i in range(0, len(text), size):\n            chunks.append(text[i : i + size])\n        return chunks\n    \n    def post(self, shared, prep_res, exec_res_list):\n        # exec_res_list is a list of chunk-lists, one per file.\n        # flatten them all into a single list of chunks.\n        all_chunks = []\n        for chunk_list in exec_res_list:\n            all_chunks.extend(chunk_list)\n        shared[\"all_chunks\"] = all_chunks\n\nclass EmbedDocs(BatchNode):\n    def prep(self, shared):\n        return shared[\"all_chunks\"]\n\n    def exec(self, chunk):\n        return get_embedding(chunk)\n\n    def post(self, shared, prep_res, exec_res_list):\n        # Store the list of embeddings.\n        shared[\"all_embeds\"] = exec_res_list\n        print(f\"Total embeddings: {len(exec_res_list)}\")\n\nclass StoreIndex(Node):\n    def prep(self, shared):\n        # We'll read all embeds from shared.\n        return shared[\"all_embeds\"]\n\n    def exec(self, all_embeds):\n        # Create a vector index (faiss or other DB in real usage).\n        index = create_index(all_embeds)\n        return index\n\n    def post(self, shared, prep_res, index):\n        shared[\"index\"] = index\n\n# Wire them in sequence\nchunk_node = ChunkDocs()\nembed_node = EmbedDocs()\nstore_node = StoreIndex()\n\nchunk_node >> embed_node >> store_node\n\nOfflineFlow = Flow(start=chunk_node)\n```\n\nUsage example:\n\n```python\nshared = {\n    \"files\": [\"doc1.txt\", \"doc2.txt\"],  # any text files\n}\nOfflineFlow.run(shared)\n```\n\n---\n## Stage 2: Online Query & Answer\n\nWe have 3 nodes:\n1. `EmbedQuery` – embeds the user’s question.\n2. `RetrieveDocs` – retrieves top chunk from the index.\n3. `GenerateAnswer` – calls the LLM with the question + chunk to produce the final answer.\n\n```python\nclass EmbedQuery(Node):\n    def prep(self, shared):\n        return shared[\"question\"]\n\n    def exec(self, question):\n        return get_embedding(question)\n\n    def post(self, shared, prep_res, q_emb):\n        shared[\"q_emb\"] = q_emb\n\nclass RetrieveDocs(Node):\n    def prep(self, shared):\n        # We'll need the query embedding, plus the offline index/chunks\n        return shared[\"q_emb\"], shared[\"index\"], shared[\"all_chunks\"]\n\n    def exec(self, inputs):\n        q_emb, index, chunks = inputs\n        I, D = search_index(index, q_emb, top_k=1)\n        best_id = I[0][0]\n        relevant_chunk = chunks[best_id]\n        return relevant_chunk\n\n    def post(self, shared, prep_res, relevant_chunk):\n        shared[\"retrieved_chunk\"] = relevant_chunk\n        print(\"Retrieved chunk:\", relevant_chunk[:60], \"...\")\n\nclass GenerateAnswer(Node):\n    def prep(self, shared):\n        return shared[\"question\"], shared[\"retrieved_chunk\"]\n\n    def exec(self, inputs):\n        question, chunk = inputs\n        prompt = f\"Question: {question}\\nContext: {chunk}\\nAnswer:\"\n        return call_llm(prompt)\n\n    def post(self, shared, prep_res, answer):\n        shared[\"answer\"] = answer\n        print(\"Answer:\", answer)\n\nembed_qnode = EmbedQuery()\nretrieve_node = RetrieveDocs()\ngenerate_node = GenerateAnswer()\n\nembed_qnode >> retrieve_node >> generate_node\nOnlineFlow = Flow(start=embed_qnode)\n```\n\nUsage example:\n\n```python\n# Suppose we already ran OfflineFlow and have:\n# shared[\"all_chunks\"], shared[\"index\"], etc.\nshared[\"question\"] = \"Why do people like cats?\"\n\nOnlineFlow.run(shared)\n# final answer in shared[\"answer\"]\n```\n\n================================================\nFile: docs/design_pattern/structure.md\n================================================\n---\nlayout: default\ntitle: \"Structured Output\"\nparent: \"Design Pattern\"\nnav_order: 5\n---\n\n# Structured Output\n\nIn many use cases, you may want the LLM to output a specific structure, such as a list or a dictionary with predefined keys.\n\nThere are several approaches to achieve a structured output:\n- **Prompting** the LLM to strictly return a defined structure.\n- Using LLMs that natively support **schema enforcement**.\n- **Post-processing** the LLM's response to extract structured content.\n\nIn practice, **Prompting** is simple and reliable for modern LLMs.\n\n### Example Use Cases\n\n- Extracting Key Information \n\n```yaml\nproduct:\n  name: Widget Pro\n  price: 199.99\n  description: |\n    A high-quality widget designed for professionals.\n    Recommended for advanced users.\n```\n\n- Summarizing Documents into Bullet Points\n\n```yaml\nsummary:\n  - This product is easy to use.\n  - It is cost-effective.\n  - Suitable for all skill levels.\n```\n\n- Generating Configuration Files\n\n```yaml\nserver:\n  host: 127.0.0.1\n  port: 8080\n  ssl: true\n```\n\n## Prompt Engineering\n\nWhen prompting the LLM to produce **structured** output:\n1. **Wrap** the structure in code fences (e.g., `yaml`).\n2. **Validate** that all required fields exist (and let `Node` handles retry).\n\n### Example Text Summarization\n\n```python\nclass SummarizeNode(Node):\n    def exec(self, prep_res):\n        # Suppose `prep_res` is the text to summarize.\n        prompt = f\"\"\"\nPlease summarize the following text as YAML, with exactly 3 bullet points\n\n{prep_res}\n\nNow, output:\n```yaml\nsummary:\n  - bullet 1\n  - bullet 2\n  - bullet 3\n```\"\"\"\n        response = call_llm(prompt)\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n\n        import yaml\n        structured_result = yaml.safe_load(yaml_str)\n\n        assert \"summary\" in structured_result\n        assert isinstance(structured_result[\"summary\"], list)\n\n        return structured_result\n```\n\n> Besides using `assert` statements, another popular way to validate schemas is [Pydantic](https://github.com/pydantic/pydantic)\n{: .note }\n\n### Why YAML instead of JSON?\n\nCurrent LLMs struggle with escaping. YAML is easier with strings since they don't always need quotes.\n\n**In JSON**  \n\n```json\n{\n  \"dialogue\": \"Alice said: \\\"Hello Bob.\\\\nHow are you?\\\\nI am good.\\\"\"\n}\n```\n\n- Every double quote inside the string must be escaped with `\\\"`.\n- Each newline in the dialogue must be represented as `\\n`.\n\n**In YAML**  \n\n```yaml\ndialogue: |\n  Alice said: \"Hello Bob.\n  How are you?\n  I am good.\"\n```\n\n- No need to escape interior quotes—just place the entire text under a block literal (`|`).\n- Newlines are naturally preserved without needing `\\n`.\n\n================================================\nFile: docs/design_pattern/workflow.md\n================================================\n---\nlayout: default\ntitle: \"Workflow\"\nparent: \"Design Pattern\"\nnav_order: 2\n---\n\n# Workflow\n\nMany 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.\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/workflow.png?raw=true\" width=\"400\"/>\n</div>\n\n> - You don't want to make each task **too coarse**, because it may be *too complex for one LLM call*.\n> - 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*.\n> \n> You usually need multiple *iterations* to find the *sweet spot*. If the task has too many *edge cases*, consider using [Agents](./agent.md).\n{: .best-practice }\n\n### Example: Article Writing\n\n```python\nclass GenerateOutline(Node):\n    def prep(self, shared): return shared[\"topic\"]\n    def exec(self, topic): return call_llm(f\"Create a detailed outline for an article about {topic}\")\n    def post(self, shared, prep_res, exec_res): shared[\"outline\"] = exec_res\n\nclass WriteSection(Node):\n    def prep(self, shared): return shared[\"outline\"]\n    def exec(self, outline): return call_llm(f\"Write content based on this outline: {outline}\")\n    def post(self, shared, prep_res, exec_res): shared[\"draft\"] = exec_res\n\nclass ReviewAndRefine(Node):\n    def prep(self, shared): return shared[\"draft\"]\n    def exec(self, draft): return call_llm(f\"Review and improve this draft: {draft}\")\n    def post(self, shared, prep_res, exec_res): shared[\"final_article\"] = exec_res\n\n# Connect nodes\noutline = GenerateOutline()\nwrite = WriteSection()\nreview = ReviewAndRefine()\n\noutline >> write >> review\n\n# Create and run flow\nwriting_flow = Flow(start=outline)\nshared = {\"topic\": \"AI Safety\"}\nwriting_flow.run(shared)\n```\n\nFor *dynamic cases*, consider using [Agents](./agent.md).\n\n================================================\nFile: docs/utility_function/llm.md\n================================================\n---\nlayout: default\ntitle: \"LLM Wrapper\"\nparent: \"Utility Function\"\nnav_order: 1\n---\n\n# LLM Wrappers\n\nCheck out libraries like [litellm](https://github.com/BerriAI/litellm). \nHere, we provide some minimal example implementations:\n\n1. OpenAI\n    ```python\n    def call_llm(prompt):\n        from openai import OpenAI\n        client = OpenAI(api_key=\"YOUR_API_KEY_HERE\")\n        r = client.chat.completions.create(\n            model=\"gpt-4o\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return r.choices[0].message.content\n\n    # Example usage\n    call_llm(\"How are you?\")\n    ```\n    > Store the API key in an environment variable like OPENAI_API_KEY for security.\n    {: .best-practice }\n\n2. Claude (Anthropic)\n    ```python\n    def call_llm(prompt):\n        from anthropic import Anthropic\n        client = Anthropic(api_key=\"YOUR_API_KEY_HERE\")\n        r = client.messages.create(\n            model=\"claude-sonnet-4-0\",\n            messages=[\n                {\"role\": \"user\", \"content\": prompt}\n            ]\n        )\n        return r.content[0].text\n    ```\n\n3. Google (Generative AI Studio / PaLM API)\n    ```python\n    def call_llm(prompt):\n    from google import genai\n    client = genai.Client(api_key='GEMINI_API_KEY')\n        response = client.models.generate_content(\n        model='gemini-2.5-pro',\n        contents=prompt\n    )\n    return response.text\n    ```\n\n4. Azure (Azure OpenAI)\n    ```python\n    def call_llm(prompt):\n        from openai import AzureOpenAI\n        client = AzureOpenAI(\n            azure_endpoint=\"https://<YOUR_RESOURCE_NAME>.openai.azure.com/\",\n            api_key=\"YOUR_API_KEY_HERE\",\n            api_version=\"2023-05-15\"\n        )\n        r = client.chat.completions.create(\n            model=\"<YOUR_DEPLOYMENT_NAME>\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return r.choices[0].message.content\n    ```\n\n5. Ollama (Local LLM)\n    ```python\n    def call_llm(prompt):\n        from ollama import chat\n        response = chat(\n            model=\"llama2\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return response.message.content\n    ```\n\n## Improvements\nFeel free to enhance your `call_llm` function as needed. Here are examples:\n\n- Handle chat history:\n\n```python\ndef call_llm(messages):\n    from openai import OpenAI\n    client = OpenAI(api_key=\"YOUR_API_KEY_HERE\")\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=messages\n    )\n    return r.choices[0].message.content\n```\n\n- Add in-memory caching \n\n```python\nfrom functools import lru_cache\n\n@lru_cache(maxsize=1000)\ndef call_llm(prompt):\n    # Your implementation here\n    pass\n```\n\n> ⚠️ Caching conflicts with Node retries, as retries yield the same result.\n>\n> To address this, you could use cached results only if not retried.\n{: .warning }\n\n\n```python\nfrom functools import lru_cache\n\n@lru_cache(maxsize=1000)\ndef cached_call(prompt):\n    pass\n\ndef call_llm(prompt, use_cache):\n    if use_cache:\n        return cached_call(prompt)\n    # Call the underlying function directly\n    return cached_call.__wrapped__(prompt)\n\nclass SummarizeNode(Node):\n    def exec(self, text):\n        return call_llm(f\"Summarize: {text}\", self.cur_retry==0)\n```\n\n- Enable logging:\n\n```python\ndef call_llm(prompt):\n    import logging\n    logging.info(f\"Prompt: {prompt}\")\n    response = ... # Your implementation here\n    logging.info(f\"Response: {response}\")\n    return response\n```"
  },
  {
    "path": ".gitignore",
    "content": "# OS generated files\n.DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nehthumbs.db\nThumbs.db\n\n\n# IDE specific files\n.idea/\n.vscode/\n*.swp\n*.swo\n*~\n\n# Node\nnode_modules/\nnpm-debug.log\nyarn-debug.log\nyarn-error.log\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# Python\n__pycache__/\n*.py[cod]\n*$py.class\n*.so\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\nvenv/\nENV/\n\n# Logs and databases\n*.log\n*.sql\n*.sqlite\n\n# Build output\ndist/\nbuild/\nout/\n\n# Coverage reports\ncoverage/\n.coverage\n.coverage.*\nhtmlcov/\n\n# Misc\n*.bak\n*.tmp\n*.temp\n\n\ntest.ipynb\n.pytest_cache/\ncookbook/pocketflow-multi-agent/.python-version\n\n\n# local\nuv.lock\n.python-version\npyproject.toml\nusage.md\ncookbook/pocketflow-minimal-example/viz/flow_visualization.html\ncookbook/pocketflow-minimal-example/viz/flow_visualization.json\n.claude/\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 Zachary Huang\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-line minimalist LLM framework\" width=\"600\"/>\n</div>\n\n<!-- For translation, replace English with [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md), and remove the link for the target language. -->\n\nEnglish | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow is a [100-line](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) minimalist LLM framework\n\n- **Lightweight**: Just 100 lines. Zero bloat, zero dependencies, zero vendor lock-in.\n  \n- **Expressive**: Everything you love—([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agents](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), and more.\n\n- **[Agentic Coding](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: Let AI Agents (e.g., Cursor AI) build Agents—10x productivity boost!\n\nGet started with Pocket Flow:\n- To install, ```pip install pocketflow```or just copy the [source code](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) (only 100 lines).\n- To learn more, check out the [video tutorial](https://youtu.be/0Zr3NwcvpA0) and [documentation](https://the-pocket.github.io/PocketFlow/)\n- 🎉 Join our [Discord](https://discord.gg/hUHHE9Sa6T) to connect with other developers building with Pocket Flow!\n- 🎉 Pocket Flow now has [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP), [Go](https://github.com/The-Pocket/PocketFlow-Go), [Rust](https://github.com/The-Pocket/PocketFlow-Rust) and [PHP](https://github.com/The-Pocket/PocketFlow-PHP) versions!\n\n## Why Pocket Flow?\n\nCurrent LLM frameworks are bloated... You only need 100 lines for LLM Framework!\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **Abstraction**          | **App-Specific Wrappers**                                      | **Vendor-Specific Wrappers**                                    | **Lines**       | **Size**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agent, Chain               | Many <br><sup><sub>(e.g., QA, Summarization)</sub></sup>              | Many <br><sup><sub>(e.g., OpenAI, Pinecone, etc.)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agent, Chain            | Many <br><sup><sub>(e.g., FileReadTool, SerperDevTool)</sub></sup>         | Many <br><sup><sub>(e.g., OpenAI, Anthropic, Pinecone, etc.)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agent                      | Some <br><sup><sub>(e.g., CodeAgent, VisitWebTool)</sub></sup>         | Some <br><sup><sub>(e.g., DuckDuckGo, Hugging Face, etc.)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agent, Graph           | Some <br><sup><sub>(e.g., Semantic Search)</sub></sup>                     | Some <br><sup><sub>(e.g., PostgresStore, SqliteSaver, etc.) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agent                | Some <br><sup><sub>(e.g., Tool Agent, Chat Agent)</sub></sup>              | Many <sup><sub>[Optional]<br> (e.g., OpenAI, Pinecone, etc.)</sub></sup>        | 7K <br><sup><sub>(core-only)</sub></sup>    | +26MB <br><sup><sub>(core-only)</sub></sup>          |\n| **PocketFlow** | **Graph**                    | **None**                                                 | **None**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## How does Pocket Flow work?\n\nThe [100 lines](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) capture the core abstraction of LLM frameworks: Graph!\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nFrom there, it's easy to implement popular design patterns like ([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agents](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), etc.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ Below are basic tutorials:\n\n<div align=\"center\">\n  \n|  Name  | Difficulty    |  Description  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <sup>*Dummy*</sup>  | A basic chat bot with conversation history |\n| [Structured Output](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <sup>*Dummy*</sup> | Extracting structured data from resumes by prompting |\n| [Workflow](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <sup>*Dummy*</sup> | A writing workflow that outlines, writes content, and applies styling |\n| [Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <sup>*Dummy*</sup>  | A research agent that can search the web and answer questions |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <sup>*Dummy*</sup> | A simple Retrieval-augmented Generation process |\n| [Batch](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <sup>*Dummy*</sup> | A batch processor that translates markdown into multiple languages |\n| [Streaming](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <sup>*Dummy*</sup> | A real-time LLM streaming demo with user interrupt capability |\n| [Chat Guardrail](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <sup>*Dummy*</sup> | A travel advisor chatbot that only processes travel-related queries |\n| [Majority Vote](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ☆☆☆ <sup>*Dummy*</sup> | Improve reasoning accuracy by aggregating multiple solution attempts |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ☆☆☆ <sup>*Dummy*</sup>  | Batch resume qualification using map-reduce pattern |\n| [CLI HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-cli-hitl) | ☆☆☆ <sup>*Dummy*</sup>  | A command-line joke generator with human-in-the-loop feedback |\n| [Multi-Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <sup>*Beginner*</sup> | A Taboo word game for async communication between 2 agents |\n| [Supervisor](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <sup>*Beginner*</sup> | Research agent is getting unreliable... Let's build a supervision process|\n| [Parallel](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) |  ★☆☆ <sup>*Beginner*</sup> | A parallel execution demo that shows 3x speedup |\n| [Parallel Flow](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <sup>*Beginner*</sup> | A parallel image processing showing 8x speedup |\n| [Thinking](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) |  ★☆☆ <sup>*Beginner*</sup> | Solve complex reasoning problems through Chain-of-Thought |\n| [Memory](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) |  ★☆☆ <sup>*Beginner*</sup> | A chat bot with short-term and long-term memory |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) |  ★☆☆ <sup>*Beginner*</sup>  | Convert natural language to SQL queries with an auto-debug loop |\n| [Code Generator](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-code-generator) | ★☆☆ <sup>*Beginner*</sup> | Generate test cases, implement solutions, and iteratively improve code |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) |  ★☆☆ <sup>*Beginner*</sup> |  Agent using Model Context Protocol for numerical operations |\n| [Agent Skills](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent-skills) |  ★☆☆ <sup>*Beginner*</sup> | Route requests to reusable markdown skills and apply them in an agent flow |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) |  ★☆☆ <sup>*Beginner*</sup> | Agent wrapped with A2A protocol for inter-agent communication |\n| [Streamlit FSM](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-streamlit-fsm) | ★☆☆ <sup>*Beginner*</sup> | Streamlit app with finite state machine for HITL image generation |\n| [FastAPI WebSocket](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-fastapi-websocket) | ★☆☆ <sup>*Beginner*</sup> | Real-time chat interface with streaming LLM responses via WebSocket |\n| [FastAPI Background](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-fastapi-background) | ★☆☆ <sup>*Beginner*</sup> | FastAPI app with background jobs and real-time progress via SSE |\n| [Voice Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-voice-chat) | ★☆☆ <sup>*Beginner*</sup> | An interactive voice chat application with VAD, STT, LLM, and TTS. |\n\n</div>\n\n👀 Want to see other tutorials for dummies? [Create an issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## How to Use Pocket Flow?\n\n🚀 Through **Agentic Coding**—the fastest LLM App development paradigm-where *humans design* and *agents code*!\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ Below are examples of more complex LLM Apps:\n\n<div align=\"center\">\n  \n|  App Name     |  Difficulty    | Topics  | Human Design | Agent Code |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Website Chatbot](https://github.com/The-Pocket/PocketFlow-Tutorial-Website-Chatbot) <br> <sup><sub>Turn your website into a 24/7 customer support genius</sup></sub> | ★★☆ <br> *Medium* | [Agent](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) <br> [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) | [Design Doc](https://github.com/The-Pocket/PocketFlow-Tutorial-Website-Chatbot/blob/main/docs/design.md) | [Flow Code](https://github.com/The-Pocket/PocketFlow-Tutorial-Website-Chatbot/blob/main/flow.py)\n| [Danganronpa Simulator](https://github.com/The-Pocket/PocketFlow-Tutorial-Danganronpa-Simulator) <br> <sup><sub>Forget the Turing test. Danganronpa, the ultimate AI experiment!</sup></sub> | ★★★ <br> *Advanced*   | [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) <br> [Agent](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [Design Doc](https://github.com/The-Pocket/PocketFlow-Tutorial-Danganronpa-Simulator/blob/main/docs/design.md) | [Flow Code](https://github.com/The-Pocket/PocketFlow-Tutorial-Danganronpa-Simulator/blob/main/flow.py)\n| [Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>Life's too short to stare at others' code in confusion</sup></sub> |  ★★☆ <br> *Medium* | [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [Design Doc](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Flow Code](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [Build Cursor with Cursor](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>We'll reach the singularity soon ...</sup></sub> | ★★★ <br> *Advanced*   | [Agent](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [Design Doc](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Flow Code](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [Ask AI Paul Graham](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>Ask AI Paul Graham, in case you don't get in</sup></sub> | ★★☆ <br> *Medium*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [Design Doc](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Flow Code](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Youtube Summarizer](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> Explain YouTube Videos to you like you're 5 </sup></sub> | ★☆☆ <br> *Beginner*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [Design Doc](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Flow Code](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [Cold Opener Generator](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> Instant icebreakers that turn cold leads hot </sup></sub> | ★☆☆ <br> *Beginner*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Web Search](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [Design Doc](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Flow Code](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n\n</div>\n\n- Want to learn **Agentic Coding**?\n\n  - Check out [my YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1) for video tutorial on how some apps above are made!\n\n  - Want to build your own LLM App? Read this [post](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)! Start with [this template](https://github.com/The-Pocket/PocketFlow-Template-Python)!\n\n\n"
  },
  {
    "path": "cookbook/README.md",
    "content": "# Pocket Flow Cookbook\n\n\n<div align=\"center\">\n  \n|  Name  | Difficulty    |  Description  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *Dummy*   | A basic chat bot with conversation history |\n| [Structured Output](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *Dummy* | Extracting structured data from resumes by prompting |\n| [Workflow](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *Dummy*   | A writing workflow that outlines, writes content, and applies styling |\n| [Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Dummy*   | A research agent that can search the web and answer questions |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *Dummy*   | A simple Retrieval-augmented Generation process |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ☆☆☆ <br> *Dummy* | A resume qualification processor using map-reduce pattern for batch evaluation |\n| [Streaming](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Dummy*   | A real-time LLM streaming demo with user interrupt capability |\n| [Chat Guardrail](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *Dummy*  | A travel advisor chatbot that only processes travel-related queries |\n| [Multi-Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *Beginner* | A Taboo word game for asynchronous communication between two agents |\n| [Supervisor](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Beginner* | Research agent is getting unreliable... Let's build a supervision process|\n| [Parallel](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Beginner*   | A parallel execution demo that shows 3x speedup |\n| [Parallel Flow](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *Beginner*   | A parallel image processing demo showing 8x speedup with multiple filters |\n| [Thinking](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Beginner*   | Solve complex reasoning problems through Chain-of-Thought |\n| [Memory](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Beginner* | A chat bot with short-term and long-term memory |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *Beginner* |  Agent using Model Context Protocol for numerical operations |\n| [Tracing](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-tracing) | ★☆☆ <br> *Beginner* |  Trace and visualize the execution of your flow |\n\n</div>\n\n👀 Want to see other tutorials? [Create an issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n"
  },
  {
    "path": "cookbook/data/PaulGrahamEssaysLarge/addiction.txt",
    "content": "July 2010What hard liquor, cigarettes, heroin, and crack have in common is\nthat they're all more concentrated forms of less addictive predecessors.\nMost if not all the things we describe as addictive are.  And the\nscary thing is, the process that created them is accelerating.We wouldn't want to stop it.  It's the same process that cures\ndiseases: technological progress.  Technological progress means\nmaking things do more of what we want.  When the thing we want is\nsomething we want to want, we consider technological progress good.\nIf some new technique makes solar cells x% more efficient, that\nseems strictly better.  When progress concentrates something we\ndon't want to want—when it transforms opium into heroin—it seems\nbad.  But it's the same process at work.\n[1]No one doubts this process is accelerating, which means increasing\nnumbers of things we like will be transformed into things we like\ntoo much.\n[2]As far as I know there's no word for something we like too much.\nThe closest is the colloquial sense of \"addictive.\" That usage has\nbecome increasingly common during my lifetime.  And it's clear why:\nthere are an increasing number of things we need it for.  At the\nextreme end of the spectrum are crack and meth.  Food has been\ntransformed by a combination of factory farming and innovations in\nfood processing into something with way more immediate bang for the\nbuck, and you can see the results in any town in America.  Checkers\nand solitaire have been replaced by World of Warcraft and FarmVille.\nTV has become much more engaging, and even so it can't compete with Facebook.The world is more addictive than it was 40 years ago.   And unless\nthe forms of technological progress that produced these things are\nsubject to different laws than technological progress in general,\nthe world will get more addictive in the next 40 years than it did\nin the last 40.The next 40 years will bring us some wonderful things.  I don't\nmean to imply they're all to be avoided.  Alcohol is a dangerous\ndrug, but I'd rather live in a world with wine than one without.\nMost people can coexist with alcohol; but you have to be careful.\nMore things we like will mean more things we have to be careful\nabout.Most people won't, unfortunately.  Which means that as the world\nbecomes more addictive, the two senses in which one can live a\nnormal life will be driven ever further apart.  One sense of \"normal\"\nis statistically normal: what everyone else does.  The other is the\nsense we mean when we talk about the normal operating range of a\npiece of machinery: what works best.These two senses are already quite far apart.  Already someone\ntrying to live well would seem eccentrically abstemious in most of\nthe US.  That phenomenon is only going to become more pronounced.\nYou can probably take it as a rule of thumb from now on that if\npeople don't think you're weird, you're living badly.Societies eventually develop antibodies to addictive new things.\nI've seen that happen with cigarettes.  When cigarettes first\nappeared, they spread the way an infectious disease spreads through\na previously isolated population.  Smoking rapidly became a\n(statistically) normal thing.  There were ashtrays everywhere.  We\nhad ashtrays in our house when I was a kid, even though neither of\nmy parents smoked.  You had to for guests.As knowledge spread about the dangers of smoking, customs changed.\nIn the last 20 years, smoking has been transformed from something\nthat seemed totally normal into a rather seedy habit: from something\nmovie stars did in publicity shots to something small huddles of\naddicts do outside the doors of office buildings.  A lot of the\nchange was due to legislation, of course, but the legislation\ncouldn't have happened if customs hadn't already changed.It took a while though—on the order of 100 years.  And unless the\nrate at which social antibodies evolve can increase to match the\naccelerating rate at which technological progress throws off new\naddictions, we'll be increasingly unable to rely on customs to\nprotect us.\n[3]\nUnless we want to be canaries in the coal mine\nof each new addiction—the people whose sad example becomes a\nlesson to future generations—we'll have to figure out for ourselves\nwhat to avoid and how.  It will actually become a reasonable strategy\n(or a more reasonable strategy) to suspect \neverything new.In fact, even that won't be enough.  We'll have to worry not just\nabout new things, but also about existing things becoming more\naddictive.  That's what bit me.  I've avoided most addictions, but\nthe Internet got me because it became addictive while I was using\nit.\n[4]Most people I know have problems with Internet addiction.  We're\nall trying to figure out our own customs for getting free of it.\nThat's why I don't have an iPhone, for example; the last thing I\nwant is for the Internet to follow me out into the world.\n[5]\nMy latest trick is taking long hikes.  I used to think running was a\nbetter form of exercise than hiking because it took less time.  Now\nthe slowness of hiking seems an advantage, because the longer I\nspend on the trail, the longer I have to think without interruption.Sounds pretty eccentric, doesn't it?  It always will when you're\ntrying to solve problems where there are no customs yet to guide\nyou.  Maybe I can't plead Occam's razor; maybe I'm simply eccentric.\nBut if I'm right about the acceleration of addictiveness, then this\nkind of lonely squirming to avoid it will increasingly be the fate\nof anyone who wants to get things done.  We'll increasingly be\ndefined by what we say no to.\nNotes[1]\nCould you restrict technological progress to areas where you\nwanted it?  Only in a limited way, without becoming a police state.\nAnd even then your restrictions would have undesirable side effects.\n\"Good\" and \"bad\" technological progress aren't sharply differentiated,\nso you'd find you couldn't slow the latter without also slowing the\nformer.  And in any case, as Prohibition and the \"war on drugs\"\nshow, bans often do more harm than good.[2]\nTechnology has always been accelerating.  By Paleolithic\nstandards, technology evolved at a blistering pace in the Neolithic\nperiod.[3]\nUnless we mass produce social customs.  I suspect the recent\nresurgence of evangelical Christianity in the US is partly a reaction\nto drugs.  In desperation people reach for the sledgehammer; if\ntheir kids won't listen to them, maybe they'll listen to God.  But\nthat solution has broader consequences than just getting kids to\nsay no to drugs.  You end up saying no to \nscience as well.\nI worry we may be heading for a future in which only a few people\nplot their own itinerary through no-land, while everyone else books\na package tour.  Or worse still, has one booked for them by the\ngovernment.[4]\nPeople commonly use the word \"procrastination\" to describe\nwhat they do on the Internet.  It seems to me too mild to describe\nwhat's happening as merely not-doing-work.  We don't call it\nprocrastination when someone gets drunk instead of working.[5]\nSeveral people have told me they like the iPad because it\nlets them bring the Internet into situations where a laptop would\nbe too conspicuous.  In other words, it's a hip flask.  (This is\ntrue of the iPhone too, of course, but this advantage isn't as\nobvious because it reads as a phone, and everyone's used to those.)Thanks to Sam Altman, Patrick Collison, Jessica Livingston, and\nRobert Morris for reading drafts of this."
  },
  {
    "path": "cookbook/data/PaulGrahamEssaysLarge/aord.txt",
    "content": "October 2015When I talk to a startup that's been operating for more than 8 or\n9 months, the first thing I want to know is almost always the same.\nAssuming their expenses remain constant and their revenue growth\nis what it has been over the last several months, do they make it to\nprofitability on the money they have left?  Or to put it more\ndramatically, by default do they live or die?The startling thing is how often the founders themselves don't know.\nHalf the founders I talk to don't know whether they're default alive\nor default dead.If you're among that number, Trevor Blackwell has made a handy\ncalculator you can use to find out.The reason I want to know first whether a startup is default alive\nor default dead is that the rest of the conversation depends on the\nanswer.  If the company is default alive, we can talk about ambitious\nnew things they could do.  If it's default dead, we probably need\nto talk about how to save it.  We know the current trajectory ends\nbadly.  How can they get off that trajectory?Why do so few founders know whether they're default alive or default\ndead?  Mainly, I think, because they're not used to asking that.\nIt's not a question that makes sense to ask early on, any more than\nit makes sense to ask a 3 year old how he plans to support\nhimself.  But as the company grows older, the question switches from\nmeaningless to critical.  That kind of switch often takes people\nby surprise.I propose the following solution: instead of starting to ask too\nlate whether you're default alive or default dead, start asking too\nearly.  It's hard to say precisely when the question switches\npolarity.  But it's probably not that dangerous to start worrying\ntoo early that you're default dead, whereas it's very dangerous to\nstart worrying too late.The reason is a phenomenon I wrote about earlier: the\nfatal pinch.\nThe fatal pinch is default dead + slow growth + not enough\ntime to fix it.  And the way founders end up in it is by not realizing\nthat's where they're headed.There is another reason founders don't ask themselves whether they're\ndefault alive or default dead: they assume it will be easy to raise\nmore money.  But that assumption is often false, and worse still, the\nmore you depend on it, the falser it becomes.Maybe it will help to separate facts from hopes. Instead of thinking\nof the future with vague optimism, explicitly separate the components.\nSay \"We're default dead, but we're counting on investors to save\nus.\" Maybe as you say that, it will set off the same alarms in your\nhead that it does in mine.  And if you set off the alarms sufficiently\nearly, you may be able to avoid the fatal pinch.It would be safe to be default dead if you could count on investors\nsaving you.  As a rule their interest is a function of\ngrowth.  If you have steep revenue growth, say over 5x a year, you\ncan start to count on investors being interested even if you're not\nprofitable.\n[1]\nBut investors are so fickle that you can never\ndo more than start to count on them.  Sometimes something about your\nbusiness will spook investors even if your growth is great.  So no\nmatter how good your growth is, you can never safely treat fundraising\nas more than a plan A. You should always have a plan B as well: you\nshould know (as in write down) precisely what you'll need to do to\nsurvive if you can't raise more money, and precisely when you'll \nhave to switch to plan B if plan A isn't working.In any case, growing fast versus operating cheaply is far from the\nsharp dichotomy many founders assume it to be.  In practice there\nis surprisingly little connection between how much a startup spends\nand how fast it grows.  When a startup grows fast, it's usually\nbecause the product hits a nerve, in the sense of hitting some big\nneed straight on.  When a startup spends a lot, it's usually because\nthe product is expensive to develop or sell, or simply because\nthey're wasteful.If you're paying attention, you'll be asking at this point not just\nhow to avoid the fatal pinch, but how to avoid being default dead.\nThat one is easy: don't hire too fast.  Hiring too fast is by far\nthe biggest killer of startups that raise money.\n[2]Founders tell themselves they need to hire in order to grow.  But\nmost err on the side of overestimating this need rather than\nunderestimating it.  Why?  Partly because there's so much work to\ndo.  Naive founders think that if they can just hire enough\npeople, it will all get done.  Partly because successful startups have\nlots of employees, so it seems like that's what one does in order\nto be successful.  In fact the large staffs of successful startups\nare probably more the effect of growth than the cause.  And\npartly because when founders have slow growth they don't want to\nface what is usually the real reason: the product is not appealing\nenough.Plus founders who've just raised money are often encouraged to\noverhire by the VCs who funded them.  Kill-or-cure strategies are\noptimal for VCs because they're protected by the portfolio effect.\nVCs want to blow you up, in one sense of the phrase or the other.\nBut as a founder your incentives are different.  You want above all\nto survive.\n[3]Here's a common way startups die.  They make something moderately\nappealing and have decent initial growth. They raise their first\nround fairly easily, because the founders seem smart and the idea\nsounds plausible. But because the product is only moderately\nappealing, growth is ok but not great.  The founders convince\nthemselves that hiring a bunch of people is the way to boost growth.\nTheir investors agree.  But (because the product is only moderately\nappealing) the growth never comes.  Now they're rapidly running out\nof runway.  They hope further investment will save them. But because\nthey have high expenses and slow growth, they're now unappealing\nto investors. They're unable to raise more, and the company dies.What the company should have done is address the fundamental problem:\nthat the product is only moderately appealing.  Hiring people is\nrarely the way to fix that.  More often than not it makes it harder.\nAt this early stage, the product needs to evolve more than to be\n\"built out,\" and that's usually easier with fewer people.\n[4]Asking whether you're default alive or default dead may save you\nfrom this.  Maybe the alarm bells it sets off will counteract the\nforces that push you to overhire.  Instead you'll be compelled to\nseek growth in other ways. For example, by doing\nthings that don't scale, or by redesigning the product in the\nway only founders can.\nAnd for many if not most startups, these paths to growth will be\nthe ones that actually work.Airbnb waited 4 months after raising money at the end of Y Combinator\nbefore they hired their first employee.  In the meantime the founders\nwere terribly overworked.  But they were overworked evolving Airbnb\ninto the astonishingly successful organism it is now.Notes[1]\nSteep usage growth will also interest investors.  Revenue\nwill ultimately be a constant multiple of usage, so x% usage growth\npredicts x% revenue growth.  But in practice investors discount\nmerely predicted revenue, so if you're measuring usage you need a\nhigher growth rate to impress investors.[2]\nStartups that don't raise money are saved from hiring too\nfast because they can't afford to. But that doesn't mean you should\navoid raising money in order to avoid this problem, any more than\nthat total abstinence is the only way to avoid becoming an alcoholic.[3]\nI would not be surprised if VCs' tendency to push founders\nto overhire is not even in their own interest.  They don't know how\nmany of the companies that get killed by overspending might have\ndone well if they'd survived.  My guess is a significant number.[4]\nAfter reading a draft, Sam Altman wrote:\"I think you should make the hiring point more strongly.  I think\nit's roughly correct to say that YC's most successful companies\nhave never been the fastest to hire, and one of the marks of a great\nfounder is being able to resist this urge.\"Paul Buchheit adds:\"A related problem that I see a lot is premature scaling—founders\ntake a small business that isn't really working (bad unit economics,\ntypically) and then scale it up because they want impressive growth\nnumbers. This is similar to over-hiring in that it makes the business\nmuch harder to fix once it's big, plus they are bleeding cash really\nfast.\"\nThanks to Sam Altman, Paul Buchheit, Joe Gebbia, Jessica Livingston,\nand Geoff Ralston for reading drafts of this."
  },
  {
    "path": "cookbook/data/PaulGrahamEssaysLarge/apple.txt",
    "content": "\n\nWant to start a startup?  Get funded by\nY Combinator.\n\n\n\n\nNovember 2009I don't think Apple realizes how badly the App Store approval process\nis broken.  Or rather, I don't think they realize how much it matters\nthat it's broken.The way Apple runs the App Store has harmed their reputation with\nprogrammers more than anything else they've ever done. \nTheir reputation with programmers used to be great.\nIt used to be the most common complaint you heard\nabout Apple was that their fans admired them too uncritically.\nThe App Store has changed that.  Now a lot of programmers\nhave started to see Apple as evil.How much of the goodwill Apple once had with programmers have they\nlost over the App Store?  A third?  Half?  And that's just so far.\nThe App Store is an ongoing karma leak.* * *How did Apple get into this mess?  Their fundamental problem is\nthat they don't understand software.They treat iPhone apps the way they treat the music they sell through\niTunes.  Apple is the channel; they own the user; if you want to\nreach users, you do it on their terms. The record labels agreed,\nreluctantly.  But this model doesn't work for software.  It doesn't\nwork for an intermediary to own the user.  The software business\nlearned that in the early 1980s, when companies like VisiCorp showed\nthat although the words \"software\" and \"publisher\" fit together,\nthe underlying concepts don't.  Software isn't like music or books.\nIt's too complicated for a third party to act as an intermediary\nbetween developer and user.   And yet that's what Apple is trying\nto be with the App Store: a software publisher.  And a particularly\noverreaching one at that, with fussy tastes and a rigidly enforced\nhouse style.If software publishing didn't work in 1980, it works even less now\nthat software development has evolved from a small number of big\nreleases to a constant stream of small ones.  But Apple doesn't\nunderstand that either.  Their model of product development derives\nfrom hardware.  They work on something till they think it's finished,\nthen they release it.  You have to do that with hardware, but because\nsoftware is so easy to change, its design can benefit from evolution.\nThe standard way to develop applications now is to launch fast and\niterate.  Which means it's a disaster to have long, random delays\neach time you release a new version.Apparently Apple's attitude is that developers should be more careful\nwhen they submit a new version to the App Store.  They would say\nthat.  But powerful as they are, they're not powerful enough to\nturn back the evolution of technology.  Programmers don't use\nlaunch-fast-and-iterate out of laziness.  They use it because it\nyields the best results.  By obstructing that process, Apple is\nmaking them do bad work, and programmers hate that as much as Apple\nwould.How would Apple like it if when they discovered a serious bug in\nOS X, instead of releasing a software update immediately, they had\nto submit their code to an intermediary who sat on it for a month\nand then rejected it because it contained an icon they didn't like?By breaking software development, Apple gets the opposite of what\nthey intended: the version of an app currently available in the App\nStore tends to be an old and buggy one.  One developer told me:\n\n  As a result of their process, the App Store is full of half-baked\n  applications. I make a new version almost every day that I release\n  to beta users. The version on the App Store feels old and crappy.\n  I'm sure that a lot of developers feel this way: One emotion is\n  \"I'm not really proud about what's in the App Store\", and it's\n  combined with the emotion \"Really, it's Apple's fault.\"\n\nAnother wrote:\n\n  I believe that they think their approval process helps users by\n  ensuring quality.  In reality, bugs like ours get through all the\n  time and then it can take 4-8 weeks to get that bug fix approved,\n  leaving users to think that iPhone apps sometimes just don't work.\n  Worse for Apple, these apps work just fine on other platforms\n  that have immediate approval processes.\n\nActually I suppose Apple has a third misconception: that all the\ncomplaints about App Store approvals are not a serious problem.\nThey must hear developers complaining.  But partners and suppliers\nare always complaining.  It would be a bad sign if they weren't;\nit would mean you were being too easy on them.  Meanwhile the iPhone\nis selling better than ever.  So why do they need to fix anything?They get away with maltreating developers, in the short term, because\nthey make such great hardware.  I just bought a new 27\" iMac a\ncouple days ago.  It's fabulous.  The screen's too shiny, and the\ndisk is surprisingly loud, but it's so beautiful that you can't\nmake yourself care.So I bought it, but I bought it, for the first time, with misgivings.\nI felt the way I'd feel buying something made in a country with a\nbad human rights record.  That was new.  In the past when I bought\nthings from Apple it was an unalloyed pleasure.  Oh boy!  They make\nsuch great stuff.  This time it felt like a Faustian bargain.  They\nmake such great stuff, but they're such assholes.  Do I really want\nto support this company?* * *Should Apple care what people like me think?  What difference does\nit make if they alienate a small minority of their users?There are a couple reasons they should care.  One is that these\nusers are the people they want as employees.  If your company seems\nevil, the best programmers won't work for you.  That hurt Microsoft\na lot starting in the 90s.  Programmers started to feel sheepish\nabout working there.  It seemed like selling out.  When people from\nMicrosoft were talking to other programmers and they mentioned where\nthey worked, there were a lot of self-deprecating jokes about having\ngone over to the dark side.  But the real problem for Microsoft\nwasn't the embarrassment of the people they hired.  It was the\npeople they never got.  And you know who got them?  Google and\nApple.  If Microsoft was the Empire, they were the Rebel Alliance.\nAnd it's largely because they got more of the best people that\nGoogle and Apple are doing so much better than Microsoft today.Why are programmers so fussy about their employers' morals?  Partly\nbecause they can afford to be.  The best programmers can work\nwherever they want.  They don't have to work for a company they\nhave qualms about.But the other reason programmers are fussy, I think, is that evil\nbegets stupidity.  An organization that wins by exercising power\nstarts to lose the ability to win by doing better work.  And it's\nnot fun for a smart person to work in a place where the best ideas\naren't the ones that win.  I think the reason Google embraced \"Don't\nbe evil\" so eagerly was not so much to impress the outside world\nas to inoculate themselves against arrogance.\n[1]That has worked for Google so far.  They've become more\nbureaucratic, but otherwise they seem to have held true to their\noriginal principles. With Apple that seems less the case.  When you\nlook at the famous \n1984 ad \nnow, it's easier to imagine Apple as the\ndictator on the screen than the woman with the hammer.\n[2]\nIn fact, if you read the dictator's speech it sounds uncannily like a\nprophecy of the App Store.\n\n  We have triumphed over the unprincipled dissemination of facts.We have created, for the first time in all history, a garden of\n  pure ideology, where each worker may bloom secure from the pests\n  of contradictory and confusing truths.\n\nThe other reason Apple should care what programmers think of them\nis that when you sell a platform, developers make or break you.  If\nanyone should know this, Apple should.  VisiCalc made the Apple II.And programmers build applications for the platforms they use.  Most\napplications—most startups, probably—grow out of personal projects.\nApple itself did.  Apple made microcomputers because that's what\nSteve Wozniak wanted for himself.  He couldn't have afforded a\nminicomputer. \n[3]\n Microsoft likewise started out making interpreters\nfor little microcomputers because\nBill Gates and Paul Allen were interested in using them.  It's a\nrare startup that doesn't build something the founders use.The main reason there are so many iPhone apps is that so many programmers\nhave iPhones.  They may know, because they read it in an article,\nthat Blackberry has such and such market share.  But in practice\nit's as if RIM didn't exist. If they're going to build something,\nthey want to be able to use it themselves, and that means building\nan iPhone app.So programmers continue to develop iPhone apps, even though Apple\ncontinues to maltreat them.  They're like someone stuck in an abusive\nrelationship.  They're so attracted to the iPhone that they can't\nleave.  But they're looking for a way out.  One wrote:\n\n  While I did enjoy developing for the iPhone, the control they\n  place on the App Store does not give me the drive to develop\n  applications as I would like. In fact I don't intend to make any\n  more iPhone applications unless absolutely necessary.\n[4]\n\nCan anything break this cycle?  No device I've seen so far could.\nPalm and RIM haven't a hope.  The only credible contender is Android.\nBut Android is an orphan; Google doesn't really care about it, not\nthe way Apple cares about the iPhone.  Apple cares about the iPhone\nthe way Google cares about search.* * *Is the future of handheld devices one locked down by Apple?  It's\na worrying prospect.  It would be a bummer to have another grim\nmonoculture like we had in the 1990s.  In 1995, writing software\nfor end users was effectively identical with writing Windows\napplications.  Our horror at that prospect was the single biggest\nthing that drove us to start building web apps.At least we know now what it would take to break Apple's lock.\nYou'd have to get iPhones out of programmers' hands.  If programmers\nused some other device for mobile web access, they'd start to develop\napps for that instead.How could you make a device programmers liked better than the iPhone?\nIt's unlikely you could make something better designed.  Apple\nleaves no room there.  So this alternative device probably couldn't\nwin on general appeal.  It would have to win by virtue of some\nappeal it had to programmers specifically.One way to appeal to programmers is with software.  If you\ncould think of an application programmers had to have, but that\nwould be impossible in the circumscribed world of the iPhone, \nyou could presumably get them to switch.That would definitely happen if programmers started to use handhelds\nas development machines—if handhelds displaced laptops the\nway laptops displaced desktops.  You need more control of a development\nmachine than Apple will let you have over an iPhone.Could anyone make a device that you'd carry around in your pocket\nlike a phone, and yet would also work as a development machine?\nIt's hard to imagine what it would look like.  But I've learned\nnever to say never about technology.  A phone-sized device that\nwould work as a development machine is no more miraculous by present\nstandards than the iPhone itself would have seemed by the standards\nof 1995.My current development machine is a MacBook Air, which I use with\nan external monitor and keyboard in my office, and by itself when\ntraveling.  If there was a version half the size I'd prefer it.\nThat still wouldn't be small enough to carry around everywhere like\na phone, but we're within a factor of 4 or so.  Surely that gap is\nbridgeable.  In fact, let's make it an\nRFS. Wanted: \nWoman with hammer.Notes[1]\nWhen Google adopted \"Don't be evil,\" they were still so small\nthat no one would have expected them to be, yet.\n[2]\nThe dictator in the 1984 ad isn't Microsoft, incidentally;\nit's IBM.  IBM seemed a lot more frightening in those days, but\nthey were friendlier to developers than Apple is now.[3]\nHe couldn't even afford a monitor.  That's why the Apple\nI used a TV as a monitor.[4]\nSeveral people I talked to mentioned how much they liked the\niPhone SDK.  The problem is not Apple's products but their policies.\nFortunately policies are software; Apple can change them instantly\nif they want to.  Handy that, isn't it?Thanks to Sam Altman, Trevor Blackwell, Ross Boucher, \nJames Bracy, Gabor Cselle,\nPatrick Collison, Jason Freedman, John Gruber, Joe Hewitt, Jessica Livingston,\nRobert Morris, Teng Siong Ong, Nikhil Pandit, Savraj Singh, and Jared Tame for reading drafts of this."
  },
  {
    "path": "cookbook/data/PaulGrahamEssaysLarge/avg.txt",
    "content": "\n\nWant to start a startup?  Get funded by\nY Combinator.\n\n\n\n\nApril 2001, rev. April 2003(This article is derived from a talk given at the 2001 Franz\nDeveloper Symposium.)\nIn the summer of 1995, my friend Robert Morris and I\nstarted a startup called \nViaweb.  \nOur plan was to write\nsoftware that would let end users build online stores.\nWhat was novel about this software, at the time, was\nthat it ran on our server, using ordinary Web pages\nas the interface.A lot of people could have been having this idea at the\nsame time, of course, but as far as I know, Viaweb was\nthe first Web-based application.  It seemed such\na novel idea to us that we named the company after it:\nViaweb, because our software worked via the Web,\ninstead of running on your desktop computer.Another unusual thing about this software was that it\nwas written primarily in a programming language called\nLisp. It was one of the first big end-user\napplications to be written in Lisp, which up till then\nhad been used mostly in universities and research labs. [1]The Secret WeaponEric Raymond has written an essay called \"How to Become a Hacker,\"\nand in it, among other things, he tells would-be hackers what\nlanguages they should learn.  He suggests starting with Python and\nJava, because they are easy to learn.  The serious hacker will also\nwant to learn C, in order to hack Unix, and Perl for system\nadministration and cgi scripts.  Finally, the truly serious hacker\nshould consider learning Lisp:\n\n  Lisp is worth learning for the profound enlightenment experience\n  you will have when you finally get it; that experience will make\n  you a better programmer for the rest of your days, even if you\n  never actually use Lisp itself a lot.\n\nThis is the same argument you tend to hear for learning Latin.  It\nwon't get you a job, except perhaps as a classics professor, but\nit will improve your mind, and make you a better writer in languages\nyou do want to use, like English.But wait a minute.  This metaphor doesn't stretch that far.  The\nreason Latin won't get you a job is that no one speaks it.  If you\nwrite in Latin, no one can understand you.  But Lisp is a computer\nlanguage, and computers speak whatever language you, the programmer,\ntell them to.So if Lisp makes you a better programmer, like he says, why wouldn't\nyou want to use it? If a painter were offered a brush that would\nmake him a better painter, it seems to me that he would want to\nuse it in all his paintings, wouldn't he? I'm not trying to make\nfun of Eric Raymond here.  On the whole, his advice is good.  What\nhe says about Lisp is pretty much the conventional wisdom.  But\nthere is a contradiction in the conventional wisdom:  Lisp will\nmake you a better programmer, and yet you won't use it.Why not?  Programming languages are just tools, after all.  If Lisp\nreally does yield better programs, you should use it.  And if it\ndoesn't, then who needs it?This is not just a theoretical question.  Software is a very\ncompetitive business, prone to natural monopolies.  A company that\ngets software written faster and better will, all other things\nbeing equal, put its competitors out of business.  And when you're\nstarting a startup, you feel this very keenly.  Startups tend to\nbe an all or nothing proposition.  You either get rich, or you get\nnothing.  In a startup, if you bet on the wrong technology, your\ncompetitors will crush you.Robert and I both knew Lisp well, and we couldn't see any reason\nnot to trust our instincts and go with Lisp.  We knew that everyone\nelse was writing their software in C++ or Perl.  But we also knew\nthat that didn't mean anything.  If you chose technology that way,\nyou'd be running Windows.  When you choose technology, you have to\nignore what other people are doing, and consider only what will\nwork the best.This is especially true in a startup.  In a big company, you can\ndo what all the other big companies are doing.  But a startup can't\ndo what all the other startups do.  I don't think a lot of people\nrealize this, even in startups.The average big company grows at about ten percent a year.  So if\nyou're running a big company and you do everything the way the\naverage big company does it, you can expect to do as well as the\naverage big company-- that is, to grow about ten percent a year.The same thing will happen if you're running a startup, of course.\nIf you do everything the way the average startup does it, you should\nexpect average performance.  The problem here is, average performance\nmeans that you'll go out of business.  The survival rate for startups\nis way less than fifty percent.  So if you're running a startup,\nyou had better be doing something odd.  If not, you're in trouble.Back in 1995, we knew something that I don't think our competitors\nunderstood, and few understand even now:  when you're writing\nsoftware that only has to run on your own servers, you can use\nany language you want.  When you're writing desktop software,\nthere's a strong bias toward writing applications in the same\nlanguage as the operating system.  Ten years ago, writing applications\nmeant writing applications in C.  But with Web-based software,\nespecially when you have the source code of both the language and\nthe operating system, you can use whatever language you want.This new freedom is a double-edged sword, however.  Now that you\ncan use any language, you have to think about which one to use.\nCompanies that try to pretend nothing has changed risk finding that\ntheir competitors do not.If you can use any language, which do you use?  We chose Lisp.\nFor one thing, it was obvious that rapid development would be\nimportant in this market.  We were all starting from scratch, so\na company that could get new features done before its competitors\nwould have a big advantage.  We knew Lisp was a really good language\nfor writing software quickly, and server-based applications magnify\nthe effect of rapid development, because you can release software\nthe minute it's done.If other companies didn't want to use Lisp, so much the better.\nIt might give us a technological edge, and we needed all the help\nwe could get.  When we started Viaweb, we had no experience in\nbusiness.  We didn't know anything about marketing, or hiring\npeople, or raising money, or getting customers.  Neither of us had\never even had what you would call a real job.  The only thing we\nwere good at was writing software.  We hoped that would save us.\nAny advantage we could get in the software department, we would\ntake.So you could say that using Lisp was an experiment.  Our hypothesis\nwas that if we wrote our software in Lisp, we'd be able to get\nfeatures done faster than our competitors, and also to do things\nin our software that they couldn't do.  And because Lisp was so\nhigh-level, we wouldn't need a big development team, so our costs\nwould be lower.  If this were so, we could offer a better product\nfor less money, and still make a profit.  We would end up getting\nall the users, and our competitors would get none, and eventually\ngo out of business.  That was what we hoped would happen, anyway.What were the results of this experiment?  Somewhat surprisingly,\nit worked.  We eventually had many competitors, on the order of\ntwenty to thirty of them, but none of their software could compete\nwith ours.  We had a wysiwyg online store builder that ran on the\nserver and yet felt like a desktop application.  Our competitors\nhad cgi scripts.  And we were always far ahead of them in features.\nSometimes, in desperation, competitors would try to introduce\nfeatures that we didn't have.  But with Lisp our development cycle\nwas so fast that we could sometimes duplicate a new feature within\na day or two of a competitor announcing it in a press release.  By\nthe time journalists covering the press release got round to calling\nus, we would have the new feature too.It must have seemed to our competitors that we had some kind of\nsecret weapon-- that we were decoding their Enigma traffic or\nsomething.  In fact we did have a secret weapon, but it was simpler\nthan they realized.  No one was leaking news of their features to\nus.   We were just able to develop software faster than anyone\nthought possible.When I was about nine I happened to get hold of a copy of The Day\nof the Jackal, by Frederick Forsyth.  The main character is an\nassassin who is hired to kill the president of France.  The assassin\nhas to get past the police to get up to an apartment that overlooks\nthe president's route.  He walks right by them, dressed up as an\nold man on crutches, and they never suspect him.Our secret weapon was similar.  We wrote our software in a weird\nAI language, with a bizarre syntax full of parentheses.  For years\nit had annoyed me to hear Lisp described that way.  But now it\nworked to our advantage.  In business, there is nothing more valuable\nthan a technical advantage your competitors don't understand.  In\nbusiness, as in war, surprise is worth as much as force.And so, I'm a little embarrassed to say, I never said anything\npublicly about Lisp while we were working on Viaweb.  We never\nmentioned it to the press, and if you searched for Lisp on our Web\nsite, all you'd find were the titles of two books in my bio.  This\nwas no accident.  A startup should give its competitors as little\ninformation as possible.  If they didn't know what language our\nsoftware was written in, or didn't care, I wanted to keep it that\nway.[2]The people who understood our technology best were the customers.\nThey didn't care what language Viaweb was written in either, but\nthey noticed that it worked really well.  It let them build great\nlooking online stores literally in minutes.  And so, by word of\nmouth mostly, we got more and more users.  By the end of 1996 we\nhad about 70 stores online.  At the end of 1997 we had 500.  Six\nmonths later, when Yahoo bought us, we had 1070 users.  Today, as\nYahoo Store, this software continues to dominate its market.  It's\none of the more profitable pieces of Yahoo, and the stores built\nwith it are the foundation of Yahoo Shopping.  I left Yahoo in\n1999, so I don't know exactly how many users they have now, but\nthe last I heard there were about 20,000.\nThe Blub ParadoxWhat's so great about Lisp?  And if Lisp is so great, why doesn't\neveryone use it?  These sound like rhetorical questions, but actually\nthey have straightforward answers.  Lisp is so great not because\nof some magic quality visible only to devotees, but because it is\nsimply the most powerful language available.  And the reason everyone\ndoesn't use it is that programming languages are not merely\ntechnologies, but habits of mind as well, and nothing changes\nslower.  Of course, both these answers need explaining.I'll begin with a shockingly controversial statement:  programming\nlanguages vary in power.Few would dispute, at least, that high level languages are more\npowerful than machine language.  Most programmers today would agree\nthat you do not, ordinarily, want to program in machine language.\nInstead, you should program in a high-level language, and have a\ncompiler translate it into machine language for you.  This idea is\neven built into the hardware now: since the 1980s, instruction sets\nhave been designed for compilers rather than human programmers.Everyone knows it's a mistake to write your whole program by hand\nin machine language.  What's less often understood is that there\nis a more general principle here: that if you have a choice of\nseveral languages, it is, all other things being equal, a mistake\nto program in anything but the most powerful one. [3]There are many exceptions to this rule.  If you're writing a program\nthat has to work very closely with a program written in a certain\nlanguage, it might be a good idea to write the new program in the\nsame language.  If you're writing a program that only has to do\nsomething very simple, like number crunching or bit manipulation,\nyou may as well use a less abstract language, especially since it\nmay be slightly faster.  And if you're writing a short, throwaway\nprogram, you may be better off just using whatever language has\nthe best library functions for the task.  But in general, for\napplication software, you want to be using the most powerful\n(reasonably efficient) language you can get, and using anything\nelse is a mistake, of exactly the same kind, though possibly in a\nlesser degree, as programming in machine language.You can see that machine language is very low level.  But, at least\nas a kind of social convention, high-level languages are often all\ntreated as equivalent.  They're not.  Technically the term \"high-level\nlanguage\" doesn't mean anything very definite.  There's no dividing\nline with machine languages on one side and all the high-level\nlanguages on the other.  Languages fall along a continuum [4] of\nabstractness, from the most powerful all the way down to machine\nlanguages, which themselves vary in power.Consider Cobol.  Cobol is a high-level language, in the sense that\nit gets compiled into machine language.  Would anyone seriously\nargue that Cobol is equivalent in power to, say, Python?  It's\nprobably closer to machine language than Python.Or how about Perl 4?  Between Perl 4 and Perl 5, lexical closures\ngot added to the language.  Most Perl hackers would agree that Perl\n5 is more powerful than Perl 4.  But once you've admitted that,\nyou've admitted that one high level language can be more powerful\nthan another.  And it follows inexorably that, except in special\ncases, you ought to use the most powerful you can get.This idea is rarely followed to its conclusion, though.  After a\ncertain age, programmers rarely switch languages voluntarily.\nWhatever language people happen to be used to, they tend to consider\njust good enough.Programmers get very attached to their favorite languages, and I\ndon't want to hurt anyone's feelings, so to explain this point I'm\ngoing to use a hypothetical language called Blub.  Blub falls right\nin the middle of the abstractness continuum.  It is not the most\npowerful language, but it is more powerful than Cobol or machine\nlanguage.And in fact, our hypothetical Blub programmer wouldn't use either\nof them.  Of course he wouldn't program in machine language.  That's\nwhat compilers are for.  And as for Cobol, he doesn't know how\nanyone can get anything done with it.  It doesn't even have x (Blub\nfeature of your choice).As long as our hypothetical Blub programmer is looking down the\npower continuum, he knows he's looking down.  Languages less powerful\nthan Blub are obviously less powerful, because they're missing some\nfeature he's used to.  But when our hypothetical Blub programmer\nlooks in the other direction, up the power continuum, he doesn't\nrealize he's looking up.  What he sees are merely weird languages.\nHe probably considers them about equivalent in power to Blub, but\nwith all this other hairy stuff thrown in as well.  Blub is good\nenough for him, because he thinks in Blub.When we switch to the point of view of a programmer using any of\nthe languages higher up the power continuum, however, we find that\nhe in turn looks down upon Blub.  How can you get anything done in\nBlub? It doesn't even have y.By induction, the only programmers in a position to see all the\ndifferences in power between the various languages are those who\nunderstand the most powerful one.  (This is probably what Eric\nRaymond meant about Lisp making you a better programmer.) You can't\ntrust the opinions of the others, because of the Blub paradox:\nthey're satisfied with whatever language they happen to use, because\nit dictates the way they think about programs.I know this from my own experience, as a high school kid writing\nprograms in Basic.  That language didn't even support recursion.\nIt's hard to imagine writing programs without using recursion, but\nI didn't miss it at the time.  I thought in Basic.  And I was a\nwhiz at it.  Master of all I surveyed.The five languages that Eric Raymond recommends to hackers fall at\nvarious points on the power continuum.  Where they fall relative\nto one another is a sensitive topic.  What I will say is that I\nthink Lisp is at the top.  And to support this claim I'll tell you\nabout one of the things I find missing when I look at the other\nfour languages.  How can you get anything done in them, I think,\nwithout macros? [5]Many languages have something called a macro.  But Lisp macros are\nunique.  And believe it or not, what they do is related to the\nparentheses.  The designers of Lisp didn't put all those parentheses\nin the language just to be different.  To the Blub programmer, Lisp\ncode looks weird.  But those parentheses are there for a reason.\nThey are the outward evidence of a fundamental difference between\nLisp and other languages.Lisp code is made out of Lisp data objects.  And not in the trivial\nsense that the source files contain characters, and strings are\none of the data types supported by the language.  Lisp code, after\nit's read by the parser, is made of data structures that you can\ntraverse.If you understand how compilers work, what's really going on is\nnot so much that Lisp has a strange syntax as that Lisp has no\nsyntax.  You write programs in the parse trees that get generated\nwithin the compiler when other languages are parsed.  But these\nparse trees are fully accessible to your programs.  You can write\nprograms that manipulate them.  In Lisp, these programs are called\nmacros.  They are programs that write programs.Programs that write programs?  When would you ever want to do that?\nNot very often, if you think in Cobol.  All the time, if you think\nin Lisp.  It would be convenient here if I could give an example\nof a powerful macro, and say there! how about that?  But if I did,\nit would just look like gibberish to someone who didn't know Lisp;\nthere isn't room here to explain everything you'd need to know to\nunderstand what it meant.  In \nAnsi Common Lisp I tried to move\nthings along as fast as I could, and even so I didn't get to macros\nuntil page 160.But I think I can give a kind of argument that might be convincing.\nThe source code of the Viaweb editor was probably about 20-25%\nmacros.  Macros are harder to write than ordinary Lisp functions,\nand it's considered to be bad style to use them when they're not\nnecessary.  So every macro in that code is there because it has to\nbe.  What that means is that at least 20-25% of the code in this\nprogram is doing things that you can't easily do in any other\nlanguage.  However skeptical the Blub programmer might be about my\nclaims for the mysterious powers of Lisp, this ought to make him\ncurious.  We weren't writing this code for our own amusement.  We\nwere a tiny startup, programming as hard as we could in order to\nput technical barriers between us and our competitors.A suspicious person might begin to wonder if there was some\ncorrelation here.  A big chunk of our code was doing things that\nare very hard to do in other languages.  The resulting software\ndid things our competitors' software couldn't do.  Maybe there was\nsome kind of connection.  I encourage you to follow that thread.\nThere may be more to that old man hobbling along on his crutches\nthan meets the eye.Aikido for StartupsBut I don't expect to convince anyone \n(over 25) \nto go out and learn\nLisp.  The purpose of this article is not to change anyone's mind,\nbut to reassure people already interested in using Lisp-- people\nwho know that Lisp is a powerful language, but worry because it\nisn't widely used.  In a competitive situation, that's an advantage.\nLisp's power is multiplied by the fact that your competitors don't\nget it.If you think of using Lisp in a startup, you shouldn't worry that\nit isn't widely understood.  You should hope that it stays that\nway. And it's likely to.  It's the nature of programming languages\nto make most people satisfied with whatever they currently use.\nComputer hardware changes so much faster than personal habits that\nprogramming practice is usually ten to twenty years behind the\nprocessor.  At places like MIT they were writing programs in\nhigh-level languages in the early 1960s, but many companies continued\nto write code in machine language well into the 1980s.  I bet a\nlot of people continued to write machine language until the processor,\nlike a bartender eager to close up and go home, finally kicked them\nout by switching to a risc instruction set.Ordinarily technology changes fast.  But programming languages are\ndifferent: programming languages are not just technology, but what\nprogrammers think in.  They're half technology and half religion.[6]\nAnd so the median language, meaning whatever language the median\nprogrammer uses, moves as slow as an iceberg.  Garbage collection,\nintroduced by Lisp in about 1960, is now widely considered to be\na good thing.  Runtime typing, ditto, is growing in popularity.\nLexical closures, introduced by Lisp in the early 1970s, are now,\njust barely, on the radar screen.  Macros, introduced by Lisp in the\nmid 1960s, are still terra incognita.Obviously, the median language has enormous momentum.  I'm not\nproposing that you can fight this powerful force.  What I'm proposing\nis exactly the opposite: that, like a practitioner of Aikido, you\ncan use it against your opponents.If you work for a big company, this may not be easy.  You will have\na hard time convincing the pointy-haired boss to let you build\nthings in Lisp, when he has just read in the paper that some other\nlanguage is poised, like Ada was twenty years ago, to take over\nthe world.  But if you work for a startup that doesn't have\npointy-haired bosses yet, you can, like we did, turn the Blub\nparadox to your advantage:  you can use technology that your\ncompetitors, glued immovably to the median language, will never be\nable to match.If you ever do find yourself working for a startup, here's a handy\ntip for evaluating competitors.  Read their job listings.  Everything\nelse on their site may be stock photos or the prose equivalent,\nbut the job listings have to be specific about what they want, or\nthey'll get the wrong candidates.During the years we worked on Viaweb I read a lot of job descriptions.\nA new competitor seemed to emerge out of the woodwork every month\nor so.  The first thing I would do, after checking to see if they\nhad a live online demo, was look at their job listings.  After a\ncouple years of this I could tell which companies to worry about\nand which not to.  The more of an IT flavor the job descriptions\nhad, the less dangerous the company was.  The safest kind were the\nones that wanted Oracle experience.  You never had to worry about\nthose.  You were also safe if they said they wanted C++ or Java\ndevelopers.  If they wanted Perl or Python programmers, that would\nbe a bit frightening-- that's starting to sound like a company\nwhere the technical side, at least, is run by real hackers.  If I\nhad ever seen a job posting looking for Lisp hackers, I would have\nbeen really worried.\nNotes[1] Viaweb at first had two parts: the editor, written in Lisp,\nwhich people used to build their sites, and the ordering system,\nwritten in C, which handled orders.  The first version was mostly\nLisp, because the ordering system was small.  Later we added two\nmore modules, an image generator written in C, and a back-office\nmanager written mostly in Perl.In January 2003, Yahoo released a new version of the editor \nwritten in C++ and Perl.  It's hard to say whether the program is no\nlonger written in Lisp, though, because to translate this program\ninto C++ they literally had to write a Lisp interpreter: the source\nfiles of all the page-generating templates are still, as far as I\nknow,  Lisp code.  (See Greenspun's Tenth Rule.)[2] Robert Morris says that I didn't need to be secretive, because\neven if our competitors had known we were using Lisp, they wouldn't\nhave understood why:  \"If they were that smart they'd already be\nprogramming in Lisp.\"[3] All languages are equally powerful in the sense of being Turing\nequivalent, but that's not the sense of the word programmers care\nabout. (No one wants to program a Turing machine.)  The kind of\npower programmers care about may not be formally definable, but\none way to explain it would be to say that it refers to features\nyou could only get in the less powerful language by writing an\ninterpreter for the more powerful language in it. If language A\nhas an operator for removing spaces from strings and language B\ndoesn't, that probably doesn't make A more powerful, because you\ncan probably write a subroutine to do it in B.  But if A supports,\nsay, recursion, and B doesn't, that's not likely to be something\nyou can fix by writing library functions.[4] Note to nerds: or possibly a lattice, narrowing toward the top;\nit's not the shape that matters here but the idea that there is at\nleast a partial order.[5] It is a bit misleading to treat macros as a separate feature.\nIn practice their usefulness is greatly enhanced by other Lisp\nfeatures like lexical closures and rest parameters.[6] As a result, comparisons of programming languages either take\nthe form of religious wars or undergraduate textbooks so determinedly\nneutral that they're really works of anthropology.  People who\nvalue their peace, or want tenure, avoid the topic.  But the question\nis only half a religious one; there is something there worth\nstudying, especially if you want to design new languages."
  },
  {
    "path": "cookbook/data/PaulGrahamEssaysLarge/before.txt",
    "content": "\n\nWant to start a startup?  Get funded by\nY Combinator.\n\n\n\n\nOctober 2014(This essay is derived from a guest lecture in Sam Altman's startup class at\nStanford.  It's intended for college students, but much of it is\napplicable to potential founders at other ages.)One of the advantages of having kids is that when you have to give\nadvice, you can ask yourself \"what would I tell my own kids?\"  My\nkids are little, but I can imagine what I'd tell them about startups\nif they were in college, and that's what I'm going to tell you.Startups are very counterintuitive.  I'm not sure why.  Maybe it's\njust because knowledge about them hasn't permeated our culture yet.\nBut whatever the reason, starting a startup is a task where you\ncan't always trust your instincts.It's like skiing in that way.  When you first try skiing and you\nwant to slow down, your instinct is to lean back.  But if you lean\nback on skis you fly down the hill out of control.  So part of\nlearning to ski is learning to suppress that impulse.  Eventually\nyou get new habits, but at first it takes a conscious effort.  At\nfirst there's a list of things you're trying to remember as you\nstart down the hill.Startups are as unnatural as skiing, so there's a similar list for\nstartups. Here I'm going to give you the first part of it — the things\nto remember if you want to prepare yourself to start a startup.\nCounterintuitiveThe first item on it is the fact I already mentioned: that startups\nare so weird that if you trust your instincts, you'll make a lot\nof mistakes.  If you know nothing more than this, you may at least\npause before making them.When I was running Y Combinator I used to joke that our function\nwas to tell founders things they would ignore.  It's really true.\nBatch after batch, the YC partners warn founders about mistakes\nthey're about to make, and the founders ignore them, and then come\nback a year later and say \"I wish we'd listened.\"Why do the founders ignore the partners' advice?  Well, that's the\nthing about counterintuitive ideas: they contradict your intuitions.\nThey seem wrong.  So of course your first impulse is to disregard\nthem.  And in fact my joking description is not merely the curse\nof Y Combinator but part of its raison d'etre. If founders' instincts\nalready gave them the right answers, they wouldn't need us.  You\nonly need other people to give you advice that surprises you. That's\nwhy there are a lot of ski instructors and not many running\ninstructors.\n[1]You can, however, trust your instincts about people.  And in fact\none of the most common mistakes young founders make is not to\ndo that enough.  They get involved with people who seem impressive,\nbut about whom they feel some misgivings personally.  Later when\nthings blow up they say \"I knew there was something off about him,\nbut I ignored it because he seemed so impressive.\"If you're thinking about getting involved with someone — as a\ncofounder, an employee, an investor, or an acquirer — and you\nhave misgivings about them, trust your gut.  If someone seems\nslippery, or bogus, or a jerk, don't ignore it.This is one case where it pays to be self-indulgent. Work with\npeople you genuinely like, and you've known long enough to be sure.\nExpertiseThe second counterintuitive point is that it's not that important\nto know a lot about startups.  The way to succeed in a startup is\nnot to be an expert on startups, but to be an expert on your users\nand the problem you're solving for them.\nMark Zuckerberg didn't succeed because he was an expert on startups.\nHe succeeded despite being a complete noob at startups, because he\nunderstood his users really well.If you don't know anything about, say, how to raise an angel round,\ndon't feel bad on that account.  That sort of thing you can learn\nwhen you need to, and forget after you've done it.In fact, I worry it's not merely unnecessary to learn in great\ndetail about the mechanics of startups, but possibly somewhat\ndangerous.  If I met an undergrad who knew all about convertible\nnotes and employee agreements and (God forbid) class FF stock, I\nwouldn't think \"here is someone who is way ahead of their peers.\"\nIt would set off alarms.  Because another of the characteristic\nmistakes of young founders is to go through the motions of starting\na startup.  They make up some plausible-sounding idea, raise money\nat a good valuation, rent a cool office, hire a bunch of people.\nFrom the outside that seems like what startups do.  But the next\nstep after rent a cool office and hire a bunch of people is: gradually\nrealize how completely fucked they are, because while imitating all\nthe outward forms of a startup they have neglected the one thing\nthat's actually essential: making something people want.\nGameWe saw this happen so often that we made up a name for it: playing\nhouse.  Eventually I realized why it was happening.  The reason\nyoung founders go through the motions of starting a startup is\nbecause that's what they've been trained to do for their whole lives\nup to that point.  Think about what you have to do to get into\ncollege, for example.  Extracurricular activities, check.  Even in\ncollege classes most of the work is as artificial as running laps.I'm not attacking the educational system for being this way. There\nwill always be a certain amount of fakeness in the work you do when\nyou're being taught something, and if you measure their performance\nit's inevitable that people will exploit the difference to the point\nwhere much of what you're measuring is artifacts of the fakeness.I confess I did it myself in college. I found that in a lot of\nclasses there might only be 20 or 30 ideas that were the right shape\nto make good exam questions.  The way I studied for exams in these\nclasses was not (except incidentally) to master the material taught\nin the class, but to make a list of potential exam questions and\nwork out the answers in advance. When I walked into the final, the\nmain thing I'd be feeling was curiosity about which of my questions\nwould turn up on the exam.  It was like a game.It's not surprising that after being trained for their whole lives\nto play such games, young founders' first impulse on starting a\nstartup is to try to figure out the tricks for winning at this new\ngame. Since fundraising appears to be the measure of success for\nstartups (another classic noob mistake), they always want to know what the\ntricks are for convincing investors.  We tell them the best way to\nconvince investors is to make a startup\nthat's actually doing well, meaning growing fast, and then simply\ntell investors so.  Then they want to know what the tricks are for\ngrowing fast.  And we have to tell them the best way to do that is\nsimply to make something people want.So many of the conversations YC partners have with young founders\nbegin with the founder asking \"How do we...\" and the partner replying\n\"Just...\"Why do the founders always make things so complicated?  The reason,\nI realized, is that they're looking for the trick.So this is the third counterintuitive thing to remember about\nstartups: starting a startup is where gaming the system stops\nworking.  Gaming the system may continue to work if you go to work\nfor a big company. Depending on how broken the company is, you can\nsucceed by sucking up to the right people, giving the impression\nof productivity, and so on. \n[2]\nBut that doesn't work with startups.\nThere is no boss to trick, only users, and all users care about is\nwhether your product does what they want. Startups are as impersonal\nas physics.  You have to make something people want, and you prosper\nonly to the extent you do.The dangerous thing is, faking does work to some degree on investors.\nIf you're super good at sounding like you know what you're talking\nabout, you can fool investors for at least one and perhaps even two\nrounds of funding.  But it's not in your interest to.  The company\nis ultimately doomed.  All you're doing is wasting your own time\nriding it down.So stop looking for the trick. There are tricks in startups, as\nthere are in any domain, but they are an order of magnitude less\nimportant than solving the real problem. A founder who knows nothing\nabout fundraising but has made something users love will have an\neasier time raising money than one who knows every trick in the\nbook but has a flat usage graph. And more importantly, the founder\nwho has made something users love is the one who will go on to\nsucceed after raising the money.Though in a sense it's bad news in that you're deprived of one of\nyour most powerful weapons, I think it's exciting that gaming the\nsystem stops working when you start a startup.  It's exciting that\nthere even exist parts of the world where you win by doing good\nwork.  Imagine how depressing the world would be if it were all\nlike school and big companies, where you either have to spend a lot\nof time on bullshit things or lose to people who do.\n[3]\nI would\nhave been delighted if I'd realized in college that there were parts\nof the real world where gaming the system mattered less than others,\nand a few where it hardly mattered at all.  But there are, and this\nvariation is one of the most important things to consider when\nyou're thinking about your future.  How do you win in each type of\nwork, and what would you like to win by doing?\n[4]\nAll-ConsumingThat brings us to our fourth counterintuitive point: startups are\nall-consuming.  If you start a startup, it will take over your life\nto a degree you cannot imagine.  And if your startup succeeds, it\nwill take over your life for a long time: for several years at the\nvery least, maybe for a decade, maybe for the rest of your working\nlife.  So there is a real opportunity cost here.Larry Page may seem to have an enviable life, but there are aspects\nof it that are unenviable.  Basically at 25 he started running as\nfast as he could and it must seem to him that he hasn't stopped to\ncatch his breath since.  Every day new shit happens in the Google\nempire that only the CEO can deal with, and he, as CEO, has to deal\nwith it.  If he goes on vacation for even a week, a whole week's\nbacklog of shit accumulates.  And he has to bear this uncomplainingly,\npartly because as the company's daddy he can never show fear or\nweakness, and partly because billionaires get less than zero sympathy\nif they talk about having difficult lives.  Which has the strange\nside effect that the difficulty of being a successful startup founder\nis concealed from almost everyone except those who've done it.Y Combinator has now funded several companies that can be called\nbig successes, and in every single case the founders say the same\nthing.  It never gets any easier.  The nature of the problems change.\nYou're worrying about construction delays at your London office\ninstead of the broken air conditioner in your studio apartment.\nBut the total volume of worry never decreases; if anything it\nincreases.Starting a successful startup is similar to having kids in that\nit's like a button you push that changes your life irrevocably.\nAnd while it's truly wonderful having kids, there are a lot of\nthings that are easier to do before you have them than after.  Many\nof which will make you a better parent when you do have kids. And\nsince you can delay pushing the button for a while, most people in\nrich countries do.Yet when it comes to startups, a lot of people seem to think they're\nsupposed to start them while they're still in college.  Are you\ncrazy?  And what are the universities thinking?  They go out of\ntheir way to ensure their students are well supplied with contraceptives,\nand yet they're setting up entrepreneurship programs and startup\nincubators left and right.To be fair, the universities have their hand forced here.  A lot\nof incoming students are interested in startups.  Universities are,\nat least de facto, expected to prepare them for their careers.  So\nstudents who want to start startups hope universities can teach\nthem about startups.  And whether universities can do this or not,\nthere's some pressure to claim they can, lest they lose applicants\nto other universities that do.Can universities teach students about startups?  Yes and no.  They\ncan teach students about startups, but as I explained before, this\nis not what you need to know.  What you need to learn about are the\nneeds of your own users, and you can't do that until you actually\nstart the company.\n[5]\nSo starting a startup is intrinsically\nsomething you can only really learn by doing it.  And it's impossible\nto do that in college, for the reason I just explained: startups\ntake over your life.  You can't start a startup for real as a\nstudent, because if you start a startup for real you're not a student\nanymore. You may be nominally a student for a bit, but you won't even\nbe that for long.\n[6]Given this dichotomy, which of the two paths should you take?  Be\na real student and not start a startup, or start a real startup and\nnot be a student?  I can answer that one for you. Do not start a\nstartup in college.  How to start a startup is just a subset of a\nbigger problem you're trying to solve: how to have a good life.\nAnd though starting a startup can be part of a good life for a lot\nof ambitious people, age 20 is not the optimal time to do it.\nStarting a startup is like a brutally fast depth-first search.  Most\npeople should still be searching breadth-first at 20.You can do things in your early 20s that you can't do as well before\nor after, like plunge deeply into projects on a whim and travel\nsuper cheaply with no sense of a deadline.  For unambitious people,\nthis sort of thing is the dreaded \"failure to launch,\" but for the\nambitious ones it can be an incomparably valuable sort of exploration.\nIf you start a startup at 20 and you're sufficiently successful,\nyou'll never get to do it.\n[7]Mark Zuckerberg will never get to bum around a foreign country.  He\ncan do other things most people can't, like charter jets to fly him\nto foreign countries. But success has taken a lot of the serendipity\nout of his life. Facebook is running him as much as he's running\nFacebook. And while it can be very cool to be in the grip of a\nproject you consider your life's work, there are advantages to\nserendipity too, especially early in life.  Among other things it\ngives you more options to choose your life's work from.There's not even a tradeoff here. You're not sacrificing anything\nif you forgo starting a startup at 20, because you're more likely\nto succeed if you wait.  In the unlikely case that you're 20 and\none of your side projects takes off like Facebook did, you'll face\na choice of running with it or not, and it may be reasonable to run\nwith it.  But the usual way startups take off is for the founders\nto make them take off, and it's gratuitously\nstupid to do that at 20.\nTryShould you do it at any age?  I realize I've made startups sound\npretty hard.  If I haven't, let me try again: starting a startup\nis really hard.  What if it's too hard?  How can you tell if you're\nup to this challenge?The answer is the fifth counterintuitive point: you can't tell. Your\nlife so far may have given you some idea what your prospects might\nbe if you tried to become a mathematician, or a professional football\nplayer.  But unless you've had a very strange life you haven't done\nmuch that was like being a startup founder.\nStarting a startup will change you a lot.  So what you're trying\nto estimate is not just what you are, but what you could grow into,\nand who can do that?For the past 9 years it was my job to predict whether people would\nhave what it took to start successful startups.  It was easy to\ntell how smart they were, and most people reading this will be over\nthat threshold.  The hard part was predicting how tough and ambitious they would become.  There\nmay be no one who has more experience at trying to predict that,\nso I can tell you how much an expert can know about it, and the\nanswer is: not much.  I learned to keep a completely open mind about\nwhich of the startups in each batch would turn out to be the stars.The founders sometimes think they know. Some arrive feeling sure\nthey will ace Y Combinator just as they've aced every one of the (few,\nartificial, easy) tests they've faced in life so far.  Others arrive\nwondering how they got in, and hoping YC doesn't discover whatever\nmistake caused it to accept them.  But there is little correlation\nbetween founders' initial attitudes and how well their companies\ndo.I've read that the same is true in the military — that the\nswaggering recruits are no more likely to turn out to be really\ntough than the quiet ones. And probably for the same reason: that\nthe tests involved are so different from the ones in their previous\nlives.If you're absolutely terrified of starting a startup, you probably\nshouldn't do it.  But if you're merely unsure whether you're up to\nit, the only way to find out is to try.  Just not now.\nIdeasSo if you want to start a startup one day, what should you do in\ncollege?  There are only two things you need initially: an idea and\ncofounders.  And the m.o. for getting both is the same.  Which leads\nto our sixth and last counterintuitive point: that the way to get\nstartup ideas is not to try to think of startup ideas.I've written a whole essay on this,\nso I won't repeat it all here.  But the short version is that if\nyou make a conscious effort to think of startup ideas, the ideas\nyou come up with will not merely be bad, but bad and plausible-sounding,\nmeaning you'll waste a lot of time on them before realizing they're\nbad.The way to come up with good startup ideas is to take a step back.\nInstead of making a conscious effort to think of startup ideas,\nturn your mind into the type that startup ideas form in without any\nconscious effort.  In fact, so unconsciously that you don't even\nrealize at first that they're startup ideas.This is not only possible, it's how Apple, Yahoo, Google, and\nFacebook all got started.  None of these companies were even meant\nto be companies at first.  They were all just side projects.  The\nbest startups almost have to start as side projects, because great\nideas tend to be such outliers that your conscious mind would reject\nthem as ideas for companies.Ok, so how do you turn your mind into the type that startup ideas\nform in unconsciously?  (1) Learn a lot about things that matter,\nthen (2) work on problems that interest you (3) with people you\nlike and respect.  The third part, incidentally, is how you get\ncofounders at the same time as the idea.The first time I wrote that paragraph, instead of \"learn a lot about\nthings that matter,\" I wrote \"become good at some technology.\" But\nthat prescription, though sufficient, is too narrow.  What was\nspecial about Brian Chesky and Joe Gebbia was not that they were\nexperts in technology.  They were good at design, and perhaps even\nmore importantly, they were good at organizing groups and making\nprojects happen.  So you don't have to work on technology per se,\nso long as you work on problems demanding enough to stretch you.What kind of problems are those?  That is very hard to answer in\nthe general case.  History is full of examples of young people who\nwere working on important problems that no\none else at the time thought were important, and in particular\nthat their parents didn't think were important.  On the other hand,\nhistory is even fuller of examples of parents who thought their\nkids were wasting their time and who were right.  So how do you\nknow when you're working on real stuff?\n[8]I know how I know.  Real problems are interesting, and I am\nself-indulgent in the sense that I always want to work on interesting\nthings, even if no one else cares about them (in fact, especially\nif no one else cares about them), and find it very hard to make\nmyself work on boring things, even if they're supposed to be\nimportant.My life is full of case after case where I worked on something just\nbecause it seemed interesting, and it turned out later to be useful\nin some worldly way.  Y\nCombinator itself was something I only did because it seemed\ninteresting. So I seem to have some sort of internal compass that\nhelps me out.  But I don't know what other people have in their\nheads. Maybe if I think more about this I can come up with heuristics\nfor recognizing genuinely interesting problems, but for the moment\nthe best I can offer is the hopelessly question-begging advice that\nif you have a taste for genuinely interesting problems, indulging\nit energetically is the best way to prepare yourself for a startup.\nAnd indeed, probably also the best way to live.\n[9]But although I can't explain in the general case what counts as an\ninteresting problem, I can tell you about a large subset of them.\nIf you think of technology as something that's spreading like a\nsort of fractal stain, every moving point on the edge represents\nan interesting problem.  So one guaranteed way to turn your mind\ninto the type that has good startup ideas is to get yourself to the\nleading edge of some technology — to cause yourself, as Paul\nBuchheit put it, to \"live in the future.\" When you reach that point,\nideas that will seem to other people uncannily prescient will seem\nobvious to you.  You may not realize they're startup ideas, but\nyou'll know they're something that ought to exist.For example, back at Harvard in the mid 90s a fellow grad student\nof my friends Robert and Trevor wrote his own voice over IP software.\nHe didn't mean it to be a startup, and he never tried to turn it\ninto one.  He just wanted to talk to his girlfriend in Taiwan without\npaying for long distance calls, and since he was an expert on\nnetworks it seemed obvious to him that the way to do it was turn\nthe sound into packets and ship it over the Internet. He never did\nany more with his software than talk to his girlfriend, but this\nis exactly the way the best startups get started.So strangely enough the optimal thing to do in college if you want\nto be a successful startup founder is not some sort of new, vocational\nversion of college focused on \"entrepreneurship.\" It's the classic\nversion of college as education for its own sake. If you want to\nstart a startup after college, what you should do in college is\nlearn powerful things.  And if you have genuine intellectual\ncuriosity, that's what you'll naturally tend to do if you just\nfollow your own inclinations.\n[10]The component of entrepreneurship that really matters is domain\nexpertise.  The way to become Larry Page was to become an expert\non search. And the way to become an expert on search was to be\ndriven by genuine curiosity, not some ulterior motive.At its best, starting a startup is merely an ulterior motive for\ncuriosity.  And you'll do it best if you introduce the ulterior\nmotive toward the end of the process.So here is the ultimate advice for young would-be startup founders,\nboiled down to two words: just learn.\nNotes[1]\nSome founders listen more than others, and this tends to be a\npredictor of success. One of the things I\nremember about the Airbnbs during YC is how intently they listened.[2]\nIn fact, this is one of the reasons startups are possible.  If\nbig companies weren't plagued by internal inefficiencies, they'd\nbe proportionately more effective, leaving less room for startups.[3]\nIn a startup you have to spend a lot of time on schleps, but this sort of work is merely\nunglamorous, not bogus.[4]\nWhat should you do if your true calling is gaming the system?\nManagement consulting.[5]\nThe company may not be incorporated, but if you start to get\nsignificant numbers of users, you've started it, whether you realize\nit yet or not.[6]\nIt shouldn't be that surprising that colleges can't teach\nstudents how to be good startup founders, because they can't teach\nthem how to be good employees either.The way universities \"teach\" students how to be employees is to\nhand off the task to companies via internship programs.  But you\ncouldn't do the equivalent thing for startups, because by definition\nif the students did well they would never come back.[7]\nCharles Darwin was 22 when he received an invitation to travel\naboard the HMS Beagle as a naturalist.  It was only because he was\notherwise unoccupied, to a degree that alarmed his family, that he\ncould accept it. And yet if he hadn't we probably would not know\nhis name.[8]\nParents can sometimes be especially conservative in this\ndepartment.  There are some whose definition of important problems\nincludes only those on the critical path to med school.[9]\nI did manage to think of a heuristic for detecting whether you\nhave a taste for interesting ideas: whether you find known boring\nideas intolerable.  Could you endure studying literary theory, or\nworking in middle management at a large company?[10]\nIn fact, if your goal is to start a startup, you can stick\neven more closely to the ideal of a liberal education than past\ngenerations have. Back when students focused mainly on getting a\njob after college, they thought at least a little about how the\ncourses they took might look to an employer.  And perhaps even\nworse, they might shy away from taking a difficult class lest they\nget a low grade, which would harm their all-important GPA.  Good\nnews: users don't care what your GPA\nwas.  And I've never heard of investors caring either.  Y Combinator\ncertainly never asks what classes you took in college or what grades\nyou got in them.\nThanks to Sam Altman, Paul Buchheit, John Collison, Patrick\nCollison, Jessica Livingston, Robert Morris, Geoff Ralston, and\nFred Wilson for reading drafts of this."
  },
  {
    "path": "cookbook/pocketflow-a2a/README.md",
    "content": "# PocketFlow Agent with A2A Protocol\n\nThis project demonstrates how to take an existing agent built with the PocketFlow library and make it accessible to other agents using the **Agent-to-Agent (A2A) communication protocol**.\n\nThis implementation is based  on the tutorial: [A2A Protocol Simply Explained: Here are 3 key differences to MCP!](https://zacharyhuang.substack.com/p/a2a-protocol-simply-explained-here)\n\n## How it Works: A2A Integration\n\nThis project combines two main parts:\n\n1.  **PocketFlow Agent Logic:** The original agent code ([`nodes.py`](nodes.py), [`utils.py`](utils.py), [`flow.py`](flow.py)) defines the internal workflow (Decide -> Search -> Answer). This code is taken directly from the [PocketFlow Agent Tutorial](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent).\n2.  **A2A Server Wrapper:** Code from the [google/A2A samples repository](https://github.com/google/A2A/tree/main/samples/python) (`common/` directory) provides the necessary infrastructure to host the agent as an A2A-compliant server. *Note: Minor modifications were made to the common server/client code to add detailed logging for educational purposes.*\n3.  **The Bridge ([`task_manager.py`](task_manager.py)):** A custom `PocketFlowTaskManager` class acts as the bridge. It receives A2A requests (like `tasks/send`), extracts the user query, runs the PocketFlow `agent_flow`, takes the final result from the flow's shared state, and packages it back into an A2A `Task` object with the answer as an `Artifact`.\n\nThis demonstrates how a non-A2A agent framework can be exposed over the A2A protocol by implementing a specific `TaskManager`.\n\n## Simplified Interaction Sequence\n\n```mermaid\nsequenceDiagram\n    participant Client as \"Client ([minimal_a2a_client.py](a2a_client.py))\"\n    participant Server as \"Server (localhost:10003)\"\n    \n    Note over Client: User enters question\n    Client->>+Server: POST / (JSON-RPC Request: tasks/send)\n    Note over Server: Processes request internally (runs PocketFlow)\n    Server-->>-Client: HTTP 200 OK (JSON-RPC Response: result=Task)\n    Note over Client: Displays final answer\n```\n\n## Getting Started\n\n### Prerequisites\n\n*   Python 3.10+ (due to type hinting used in the A2A `common` code)\n*   An OpenAI API Key\n\n### Installation\n\n\n1.  Install dependencies:\n    ```bash\n    pip install -r requirements.txt\n    ```\n\n2. Set your OpenAI API key as an environment variable:\n\n    ```bash\n    export OPENAI_API_KEY=\"your-api-key-here\"\n    ```\n\n    Let's do a quick check to make sure your API key is working properly:\n\n    ```bash\n    python utils.py\n    ```\n3. Run the server from this directory:\n\n    ```bash\n    python a2a_server.py --port 10003\n    ```\n\n    You should see logs indicating the server has started on `http://localhost:10003`.\n\n\n4.  Run the Client in a *separate terminal* \n\n    ```bash\n    python a2a_client.py --agent-url http://localhost:10003\n    ```\n\n5.  Follow the instructions in the client terminal to ask questions. Type `:q` or `quit` to exit the client.\n\n## Example Interaction Logs\n\n**(Server Log - showing internal PocketFlow steps)**\n\n```\n2025-04-12 17:20:40,893 - __main__ - INFO - Starting PocketFlow A2A server on http://localhost:10003\nINFO:     Started server process [677223]\nINFO:     Waiting for application startup.\nINFO:     Application startup complete.\nINFO:     Uvicorn running on http://localhost:10003 (Press CTRL+C to quit)\n2025-04-12 17:20:57,647 - A2AServer - INFO - <- Received Request (ID: d3f3fb93350d47d9a94ca12bb62b656b):\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"d3f3fb93350d47d9a94ca12bb62b656b\",\n  \"method\": \"tasks/send\",\n  \"params\": {\n    \"id\": \"46c3ce7b941a4fff9b8e3b644d6db5f4\",\n    \"sessionId\": \"f3e12b8424c44241be881cd4bb8a269f\",\n    \"message\": {\n      \"role\": \"user\",\n      \"parts\": [\n        {\n          \"type\": \"text\",\n          \"text\": \"Who won the Nobel Prize in Physics 2024?\"\n        }\n      ]\n    },\n    \"acceptedOutputModes\": [\n      \"text\",\n      \"text/plain\"\n    ]\n  }\n}\n2025-04-12 17:20:57,647 - task_manager - INFO - Received task send request: 46c3ce7b941a4fff9b8e3b644d6db5f4\n2025-04-12 17:20:57,647 - common.server.task_manager - INFO - Upserting task 46c3ce7b941a4fff9b8e3b644d6db5f4\n2025-04-12 17:20:57,647 - task_manager - INFO - Running PocketFlow for task 46c3ce7b941a4fff9b8e3b644d6db5f4...\n🤔 Agent deciding what to do next...\n2025-04-12 17:20:59,213 - httpx - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions \"HTTP/1.1 200 OK\"\n🔍 Agent decided to search for: 2024 Nobel Prize in Physics winner\n🌐 Searching the web for: 2024 Nobel Prize in Physics winner\n2025-04-12 17:20:59,974 - primp - INFO - response: https://lite.duckduckgo.com/lite/ 200\n📚 Found information, analyzing results...\n🤔 Agent deciding what to do next...\n2025-04-12 17:21:01,619 - httpx - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions \"HTTP/1.1 200 OK\"\n💡 Agent decided to answer the question\n✍️ Crafting final answer...\n2025-04-12 17:21:03,833 - httpx - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions \"HTTP/1.1 200 OK\"\n✅ Answer generated successfully\n2025-04-12 17:21:03,834 - task_manager - INFO - PocketFlow completed for task 46c3ce7b941a4fff9b8e3b644d6db5f4\n2025-04-12 17:21:03,834 - A2AServer - INFO - -> Response (ID: d3f3fb93350d47d9a94ca12bb62b656b):\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"d3f3fb93350d47d9a94ca12bb62b656b\",\n  \"result\": {\n    \"id\": \"46c3ce7b941a4fff9b8e3b644d6db5f4\",\n    \"sessionId\": \"f3e12b8424c44241be881cd4bb8a269f\",\n    \"status\": {\n      \"state\": \"completed\",\n      \"timestamp\": \"2025-04-12T17:21:03.834542\"\n    },\n    \"artifacts\": [\n      {\n        \"parts\": [\n          {\n            \"type\": \"text\",\n            \"text\": \"The 2024 Nobel Prize in Physics was awarded to John J. Hopfield and Geoffrey Hinton for their foundational discoveries and inventions that have significantly advanced the field of machine learning through the use of artificial neural networks. Their pioneering work has been crucial in the development and implementation of algorithms that enable machines to learn and process information in a manner that mimics human cognitive functions. This advancement in artificial intelligence technology has had a profound impact on numerous industries, facilitating innovations across various applications, from image and speech recognition to self-driving cars.\"\n          }\n        ],\n        \"index\": 0\n      }\n    ],\n    \"history\": []\n  }\n}\n```\n\n**(Client Log - showing request/response)**\n\n```\nConnecting to agent at: http://localhost:10003\nUsing Session ID: f3e12b8424c44241be881cd4bb8a269f\n\nEnter your question (:q or quit to exit) > Who won the Nobel Prize in Physics 2024?\nSending task 46c3ce7b941a4fff9b8e3b644d6db5f4...\n2025-04-12 17:20:57,643 - A2AClient - INFO - -> Sending Request (ID: d3f3fb93350d47d9a94ca12bb62b656b, Method: tasks/send):\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"d3f3fb93350d47d9a94ca12bb62b656b\",\n  \"method\": \"tasks/send\",\n  \"params\": {\n    \"id\": \"46c3ce7b941a4fff9b8e3b644d6db5f4\",\n    \"sessionId\": \"f3e12b8424c44241be881cd4bb8a269f\",\n    \"message\": {\n      \"role\": \"user\",\n      \"parts\": [\n        {\n          \"type\": \"text\",\n          \"text\": \"Who won the Nobel Prize in Physics 2024?\"\n        }\n      ]\n    },\n    \"acceptedOutputModes\": [\n      \"text\",\n      \"text/plain\"\n    ]\n  }\n}\n2025-04-12 17:21:03,835 - httpx - INFO - HTTP Request: POST http://localhost:10003 \"HTTP/1.1 200 OK\"\n2025-04-12 17:21:03,836 - A2AClient - INFO - <- Received HTTP Status 200 for Request (ID: d3f3fb93350d47d9a94ca12bb62b656b)\n2025-04-12 17:21:03,836 - A2AClient - INFO - <- Received Success Response (ID: d3f3fb93350d47d9a94ca12bb62b656b):\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"d3f3fb93350d47d9a94ca12bb62b656b\",\n  \"result\": {\n    \"id\": \"46c3ce7b941a4fff9b8e3b644d6db5f4\",\n    \"sessionId\": \"f3e12b8424c44241be881cd4bb8a269f\",\n    \"status\": {\n      \"state\": \"completed\",\n      \"timestamp\": \"2025-04-12T17:21:03.834542\"\n    },\n    \"artifacts\": [\n      {\n        \"parts\": [\n          {\n            \"type\": \"text\",\n            \"text\": \"The 2024 Nobel Prize in Physics was awarded to John J. Hopfield and Geoffrey Hinton for their foundational discoveries and inventions that have significantly advanced the field of machine learning through the use of artificial neural networks. Their pioneering work has been crucial in the development and implementation of algorithms that enable machines to learn and process information in a manner that mimics human cognitive functions. This advancement in artificial intelligence technology has had a profound impact on numerous industries, facilitating innovations across various applications, from image and speech recognition to self-driving cars.\"\n          }\n        ],\n        \"index\": 0\n      }\n    ],\n    \"history\": []\n  }\n}\nTask 46c3ce7b941a4fff9b8e3b644d6db5f4 finished with state: completed\n\nAgent Response:\nThe 2024 Nobel Prize in Physics was awarded to John J. Hopfield and Geoffrey Hinton for their foundational discoveries and inventions that have significantly advanced the field of machine learning through the use of artificial neural networks. Their pioneering work has been crucial in the development and implementation of algorithms that enable machines to learn and process information in a manner that mimics human cognitive functions. This advancement in artificial intelligence technology has had a profound impact on numerous industries, facilitating innovations across various applications, from image and speech recognition to self-driving cars.\n```\n\n## Key A2A Integration Points\n\nTo make the PocketFlow agent A2A-compatible, the following were essential:\n\n1.  **A2A Server ([`common/server/server.py`](common/server/server.py)):** An ASGI application (using Starlette/Uvicorn) that listens for HTTP POST requests, parses JSON-RPC, and routes requests based on the `method` field.\n2.  **A2A Data Types ([`common/types.py`](common/types.py)):** Pydantic models defining the structure of A2A messages, tasks, artifacts, errors, and the agent card, ensuring compliance with the `a2a.json` specification.\n3.  **Task Manager ([`task_manager.py`](task_manager.py)):** A custom class (`PocketFlowTaskManager`) inheriting from the common `InMemoryTaskManager`. Its primary role is implementing the `on_send_task` method (and potentially others like `on_send_task_subscribe` if streaming were supported). This method:\n    *   Receives the validated A2A `SendTaskRequest`.\n    *   Extracts the user's query (`TextPart`) from the request's `message`.\n    *   Initializes the PocketFlow `shared_data` dictionary.\n    *   Creates and runs the PocketFlow `agent_flow`.\n    *   Retrieves the final answer from the `shared_data` dictionary *after* the flow completes.\n    *   Updates the task's state (e.g., to `COMPLETED` or `FAILED`) in the `InMemoryTaskManager`'s store.\n    *   Packages the final answer into an A2A `Artifact` containing a `TextPart`.\n    *   Constructs the final A2A `Task` object for the response.\n4.  **Agent Card ([`a2a_server.py`](a2a_server.py)):** A Pydantic model (`AgentCard`) defining the agent's metadata (name, description, URL, capabilities, skills) served at `/.well-known/agent.json`.\n5.  **Server Entry Point ([`a2a_server.py`](a2a_server.py)):** A script that initializes the `AgentCard`, the `PocketFlowTaskManager`, and the `A2AServer`, then starts the Uvicorn server process.\n"
  },
  {
    "path": "cookbook/pocketflow-a2a/a2a_client.py",
    "content": "import asyncio\nimport asyncclick as click # Using asyncclick for async main\nfrom uuid import uuid4\nimport json # For potentially inspecting raw errors\nimport anyio\nimport functools\nimport logging\n\n# Import from the common directory placed alongside this script\nfrom common.client import A2AClient\nfrom common.types import (\n    TaskState,\n    A2AClientError,\n    TextPart, # Used to construct the message\n    JSONRPCResponse # Potentially useful for error checking\n)\n\n# --- Configure logging ---\n# Set level to INFO to see client requests and responses\n# Set level to DEBUG to see raw response bodies and SSE data lines\nlogging.basicConfig(\n    level=logging.INFO,\n    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n)\n# Optionally silence overly verbose libraries\n# logging.getLogger(\"httpx\").setLevel(logging.WARNING)\n# logging.getLogger(\"httpcore\").setLevel(logging.WARNING)\n\n# --- ANSI Colors (Optional but helpful) ---\nC_RED = \"\\x1b[31m\"\nC_GREEN = \"\\x1b[32m\"\nC_YELLOW = \"\\x1b[33m\"\nC_BLUE = \"\\x1b[34m\"\nC_MAGENTA = \"\\x1b[35m\"\nC_CYAN = \"\\x1b[36m\"\nC_WHITE = \"\\x1b[37m\"\nC_GRAY = \"\\x1b[90m\"\nC_BRIGHT_MAGENTA = \"\\x1b[95m\"\nC_RESET = \"\\x1b[0m\"\nC_BOLD = \"\\x1b[1m\"\n\ndef colorize(color, text):\n    return f\"{color}{text}{C_RESET}\"\n\n@click.command()\n@click.option(\n    \"--agent-url\",\n    default=\"http://localhost:10003\", # Default to the port used in server __main__\n    help=\"URL of the PocketFlow A2A agent server.\",\n)\nasync def cli(agent_url: str):\n    \"\"\"Minimal CLI client to interact with an A2A agent.\"\"\"\n\n    print(colorize(C_BRIGHT_MAGENTA, f\"Connecting to agent at: {agent_url}\"))\n\n    # Instantiate the client - only URL is needed if not fetching card first\n    # Note: The PocketFlow wrapper doesn't expose much via the AgentCard,\n    # so we skip fetching it for this minimal client.\n    client = A2AClient(url=agent_url)\n\n    sessionId = uuid4().hex # Generate a new session ID for this run\n    print(colorize(C_GRAY, f\"Using Session ID: {sessionId}\"))\n\n    while True:\n        taskId = uuid4().hex # Generate a new task ID for each interaction\n        try:\n            # Use functools.partial to prepare the prompt function call\n            prompt_func = functools.partial(\n                click.prompt,\n                colorize(C_CYAN, \"\\nEnter your question (:q or quit to exit)\"),\n                prompt_suffix=\" > \",\n                type=str\n            )\n            # Run the synchronous prompt function in a worker thread\n            prompt = await anyio.to_thread.run_sync(prompt_func)\n        except (EOFError, RuntimeError, KeyboardInterrupt):\n            # Catch potential errors during input or if stdin closes\n            print(colorize(C_RED, \"\\nInput closed or interrupted. Exiting.\"))\n            break\n\n        if prompt.lower() in [\":q\", \"quit\"]:\n            print(colorize(C_YELLOW, \"Exiting client.\"))\n            break\n\n        # --- Construct A2A Request Payload ---\n        payload = {\n            \"id\": taskId,\n            \"sessionId\": sessionId,\n            \"message\": {\n                \"role\": \"user\",\n                \"parts\": [\n                    {\n                        \"type\": \"text\", # Explicitly match TextPart structure\n                        \"text\": prompt,\n                    }\n                ],\n            },\n            \"acceptedOutputModes\": [\"text\", \"text/plain\"], # What the client wants back\n            # historyLength could be added if needed\n        }\n\n        print(colorize(C_GRAY, f\"Sending task {taskId}...\"))\n\n        try:\n            # --- Send Task (Non-Streaming) ---\n            response = await client.send_task(payload)\n\n            # --- Process Response ---\n            if response.error:\n                print(colorize(C_RED, f\"Error from agent (Code: {response.error.code}): {response.error.message}\"))\n                if response.error.data:\n                    print(colorize(C_GRAY, f\"Error Data: {response.error.data}\"))\n            elif response.result:\n                task_result = response.result\n                print(colorize(C_GREEN, f\"Task {task_result.id} finished with state: {task_result.status.state}\"))\n\n                final_answer = \"Agent did not provide a final artifact.\"\n                # Extract answer from artifacts (as implemented in PocketFlowTaskManager)\n                if task_result.artifacts:\n                    try:\n                        # Find the first text part in the first artifact\n                        first_artifact = task_result.artifacts[0]\n                        first_text_part = next(\n                            (p for p in first_artifact.parts if isinstance(p, TextPart)),\n                            None\n                        )\n                        if first_text_part:\n                            final_answer = first_text_part.text\n                        else:\n                             final_answer = f\"(Non-text artifact received: {first_artifact.parts})\"\n                    except (IndexError, AttributeError, TypeError) as e:\n                        final_answer = f\"(Error parsing artifact: {e})\"\n                elif task_result.status.message and task_result.status.message.parts:\n                     # Fallback to status message if no artifact\n                     try:\n                        first_text_part = next(\n                             (p for p in task_result.status.message.parts if isinstance(p, TextPart)),\n                             None\n                         )\n                        if first_text_part:\n                            final_answer = f\"(Final status message: {first_text_part.text})\"\n\n                     except (AttributeError, TypeError) as e:\n                         final_answer = f\"(Error parsing status message: {e})\"\n\n\n                print(colorize(C_BOLD + C_WHITE, f\"\\nAgent Response:\\n{final_answer}\"))\n\n            else:\n                # Should not happen if error is None\n                print(colorize(C_YELLOW, \"Received response with no result and no error.\"))\n\n        except A2AClientError as e:\n            print(colorize(C_RED, f\"\\nClient Error: {e}\"))\n        except Exception as e:\n            print(colorize(C_RED, f\"\\nAn unexpected error occurred: {e}\"))\n\nif __name__ == \"__main__\":\n    asyncio.run(cli())"
  },
  {
    "path": "cookbook/pocketflow-a2a/a2a_server.py",
    "content": "import click\nimport logging\nimport os\n\n# Import from the common code you copied\nfrom common.server import A2AServer\nfrom common.types import AgentCard, AgentCapabilities, AgentSkill, MissingAPIKeyError\n\n# Import your custom TaskManager (which now imports from your original files)\nfrom task_manager import PocketFlowTaskManager\n\n# --- Configure logging ---\n# Set level to INFO to see server start, requests, responses\n# Set level to DEBUG to see raw response bodies from client\nlogging.basicConfig(\n    level=logging.INFO,\n    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n)\n# Optionally silence overly verbose libraries\n# logging.getLogger(\"httpx\").setLevel(logging.WARNING)\n# logging.getLogger(\"httpcore\").setLevel(logging.WARNING)\n# logging.getLogger(\"uvicorn.access\").setLevel(logging.WARNING)\n\nlogger = logging.getLogger(__name__)\n\n@click.command()\n@click.option(\"--host\", \"host\", default=\"localhost\")\n@click.option(\"--port\", \"port\", default=10003) # Use a different port from other agents\ndef main(host, port):\n    \"\"\"Starts the PocketFlow A2A Agent server.\"\"\"\n    try:\n        # Check for necessary API keys (add others if needed)\n        if not os.getenv(\"OPENAI_API_KEY\"):\n            raise MissingAPIKeyError(\"OPENAI_API_KEY environment variable not set.\")\n\n        # --- Define the Agent Card ---\n        capabilities = AgentCapabilities(\n            streaming=False, # This simple implementation is synchronous\n            pushNotifications=False,\n            stateTransitionHistory=False # PocketFlow state isn't exposed via A2A history\n        )\n        skill = AgentSkill(\n            id=\"web_research_qa\",\n            name=\"Web Research and Answering\",\n            description=\"Answers questions using web search results when necessary.\",\n            tags=[\"research\", \"qa\", \"web search\"],\n            examples=[\n                \"Who won the Nobel Prize in Physics 2024?\",\n                \"What is quantum computing?\",\n                \"Summarize the latest news about AI.\",\n            ],\n            # Input/Output modes defined in the TaskManager\n            inputModes=PocketFlowTaskManager.SUPPORTED_CONTENT_TYPES,\n            outputModes=PocketFlowTaskManager.SUPPORTED_CONTENT_TYPES,\n        )\n        agent_card = AgentCard(\n            name=\"PocketFlow Research Agent (A2A Wrapped)\",\n            description=\"A simple research agent based on PocketFlow, made accessible via A2A.\",\n            url=f\"http://{host}:{port}/\", # The endpoint A2A clients will use\n            version=\"0.1.0-a2a\",\n            capabilities=capabilities,\n            skills=[skill],\n            # Assuming no specific provider or auth for this example\n            provider=None,\n            authentication=None,\n            defaultInputModes=PocketFlowTaskManager.SUPPORTED_CONTENT_TYPES,\n            defaultOutputModes=PocketFlowTaskManager.SUPPORTED_CONTENT_TYPES,\n        )\n\n        # --- Initialize and Start Server ---\n        task_manager = PocketFlowTaskManager() # Instantiate your custom manager\n        server = A2AServer(\n            agent_card=agent_card,\n            task_manager=task_manager,\n            host=host,\n            port=port,\n        )\n\n        logger.info(f\"Starting PocketFlow A2A server on http://{host}:{port}\")\n        server.start()\n\n    except MissingAPIKeyError as e:\n        logger.error(f\"Configuration Error: {e}\")\n        exit(1)\n    except Exception as e:\n        logger.error(f\"An error occurred during server startup: {e}\", exc_info=True)\n        exit(1)\n\n\nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "cookbook/pocketflow-a2a/common/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-a2a/common/client/__init__.py",
    "content": "from .client import A2AClient\nfrom .card_resolver import A2ACardResolver\n\n__all__ = [\"A2AClient\", \"A2ACardResolver\"]\n"
  },
  {
    "path": "cookbook/pocketflow-a2a/common/client/card_resolver.py",
    "content": "import httpx\nfrom common.types import (\n    AgentCard,\n    A2AClientJSONError,\n)\nimport json\n\n\nclass A2ACardResolver:\n    def __init__(self, base_url, agent_card_path=\"/.well-known/agent.json\"):\n        self.base_url = base_url.rstrip(\"/\")\n        self.agent_card_path = agent_card_path.lstrip(\"/\")\n\n    def get_agent_card(self) -> AgentCard:\n        with httpx.Client() as client:\n            response = client.get(self.base_url + \"/\" + self.agent_card_path)\n            response.raise_for_status()\n            try:\n                return AgentCard(**response.json())\n            except json.JSONDecodeError as e:\n                raise A2AClientJSONError(str(e)) from e\n"
  },
  {
    "path": "cookbook/pocketflow-a2a/common/client/client.py",
    "content": "import httpx\nfrom httpx_sse import connect_sse\nfrom typing import Any, AsyncIterable\nfrom common.types import (\n    AgentCard,\n    GetTaskRequest,\n    SendTaskRequest,\n    SendTaskResponse,\n    JSONRPCRequest,\n    JSONRPCResponse,\n    JSONRPCError,\n    GetTaskResponse,\n    CancelTaskResponse,\n    CancelTaskRequest,\n    SetTaskPushNotificationRequest,\n    SetTaskPushNotificationResponse,\n    GetTaskPushNotificationRequest,\n    GetTaskPushNotificationResponse,\n    A2AClientHTTPError,\n    A2AClientJSONError,\n    SendTaskStreamingRequest,\n    SendTaskStreamingResponse,\n    Task,\n    TaskPushNotificationConfig,\n    TaskStatusUpdateEvent,\n    TaskArtifactUpdateEvent,\n)\nimport json\nimport logging\n\n# Configure a logger specific to the client\nlogger = logging.getLogger(\"A2AClient\")\n\nclass A2AClientError(Exception):\n    \"\"\"Base class for A2A client errors\"\"\"\n    def __init__(self, message):\n        super().__init__(message)\n\nclass RpcError(Exception):\n    code: int\n    data: Any = None\n    def __init__(self, code: int, message: str, data: Any = None):\n        super().__init__(message)\n        self.name = \"RpcError\"\n        self.code = code\n        self.data = data\n\nclass A2AClient:\n    def __init__(self, agent_card: AgentCard = None, url: str = None):\n        if agent_card:\n            self.url = agent_card.url.rstrip(\"/\")\n        elif url:\n            self.url = url.rstrip(\"/\")\n        else:\n            raise ValueError(\"Must provide either agent_card or url\")\n        self.fetchImpl = httpx.AsyncClient(timeout=None)\n\n    def _generateRequestId(self):\n        import time\n        return int(time.time() * 1000)\n\n    async def _send_request(self, request: JSONRPCRequest) -> dict[str, Any]:\n        req_id = request.id\n        req_method = request.method\n        req_dump = request.model_dump(exclude_none=True)\n\n        logger.info(f\"-> Sending Request (ID: {req_id}, Method: {req_method}):\\n{json.dumps(req_dump, indent=2)}\")\n\n        try:\n            response = await self.fetchImpl.post(\n                self.url, json=req_dump, timeout=60.0\n            )\n            logger.info(f\"<- Received HTTP Status {response.status_code} for Request (ID: {req_id})\")\n            response_text = await response.aread()\n            logger.debug(f\"Raw Response Body (ID: {req_id}):\\n{response_text.decode('utf-8', errors='replace')}\")\n\n            response.raise_for_status()\n\n            try:\n                json_response = json.loads(response_text)\n            except json.JSONDecodeError as e:\n                logger.error(f\"Failed to decode JSON response (ID: {req_id}): {e}\")\n                raise A2AClientJSONError(f\"Failed to decode JSON: {e}\") from e\n\n            if \"error\" in json_response and json_response[\"error\"] is not None:\n                rpc_error = json_response[\"error\"]\n                logger.warning(f\"<- Received JSON-RPC Error (ID: {req_id}): Code={rpc_error.get('code')}, Msg='{rpc_error.get('message')}'\")\n                raise RpcError(rpc_error.get(\"code\", -32000), rpc_error.get(\"message\", \"Unknown RPC Error\"), rpc_error.get(\"data\"))\n\n            logger.info(f\"<- Received Success Response (ID: {req_id}):\\n{json.dumps(json_response, indent=2)}\")\n            return json_response\n\n        except httpx.HTTPStatusError as e:\n            logger.error(f\"HTTP Error for Request (ID: {req_id}): {e.response.status_code} - {e.request.url}\")\n            error_body = await e.response.aread()\n            raise A2AClientHTTPError(e.response.status_code, f\"{e}. Body: {error_body.decode('utf-8', errors='replace')}\") from e\n        except httpx.RequestError as e:\n            logger.error(f\"Request Error for (ID: {req_id}): {e}\")\n            raise A2AClientError(f\"Network or request error: {e}\") from e\n        except RpcError:\n             raise\n        except Exception as e:\n             logger.error(f\"Unexpected error during request (ID: {req_id}): {e}\", exc_info=True)\n             raise A2AClientError(f\"Unexpected error: {e}\") from e\n\n    async def send_task(self, payload: dict[str, Any]) -> SendTaskResponse:\n        request = SendTaskRequest(params=payload)\n        response_dict = await self._send_request(request)\n        return SendTaskResponse(**response_dict)\n\n    async def send_task_streaming(\n        self, payload: dict[str, Any]\n    ) -> AsyncIterable[SendTaskStreamingResponse]:\n        request = SendTaskStreamingRequest(params=payload)\n        req_id = request.id\n        req_dump = request.model_dump(exclude_none=True)\n\n        logger.info(f\"-> Sending Streaming Request (ID: {req_id}, Method: {request.method}):\\n{json.dumps(req_dump, indent=2)}\")\n\n        try:\n            async with self.fetchImpl.stream(\"POST\", self.url, json=req_dump, timeout=None) as response:\n                 logger.info(f\"<- Received HTTP Status {response.status_code} for Streaming Request (ID: {req_id})\")\n                 response.raise_for_status()\n\n                 buffer = \"\"\n                 async for line in response.aiter_lines():\n                     if not line:\n                         if buffer.startswith(\"data:\"):\n                             data_str = buffer[len(\"data:\"):].strip()\n                             logger.debug(f\"Received SSE Data Line (ID: {req_id}): {data_str}\")\n                             try:\n                                 sse_data_dict = json.loads(data_str)\n                                 yield SendTaskStreamingResponse(**sse_data_dict)\n                             except json.JSONDecodeError as e:\n                                 logger.error(f\"Failed to decode SSE JSON (ID: {req_id}): {e}. Data: '{data_str}'\")\n                             except Exception as e:\n                                 logger.error(f\"Error processing SSE data (ID: {req_id}): {e}. Data: '{data_str}'\", exc_info=True)\n                         elif buffer:\n                             logger.debug(f\"Received non-data SSE line (ID: {req_id}): {buffer}\")\n                         buffer = \"\"\n                     else:\n                         buffer += line + \"\\n\"\n\n                 if buffer:\n                     logger.warning(f\"SSE stream ended with partial data in buffer (ID: {req_id}): {buffer}\")\n\n                 logger.info(f\"SSE Stream ended for request ID: {req_id}\")\n\n        except httpx.HTTPStatusError as e:\n            logger.error(f\"HTTP Error during streaming connection (ID: {req_id}): {e.response.status_code} - {e.request.url}\")\n            error_body = await e.response.aread()\n            raise A2AClientHTTPError(e.response.status_code, f\"{e}. Body: {error_body.decode('utf-8', errors='replace')}\") from e\n        except httpx.RequestError as e:\n             logger.error(f\"Request Error during streaming (ID: {req_id}): {e}\")\n             raise A2AClientError(f\"Network or request error during streaming: {e}\") from e\n        except Exception as e:\n            logger.error(f\"Unexpected error during streaming (ID: {req_id}): {e}\", exc_info=True)\n            raise A2AClientError(f\"Unexpected streaming error: {e}\") from e\n\n    async def get_task(self, payload: dict[str, Any]) -> GetTaskResponse:\n        request = GetTaskRequest(params=payload)\n        response_dict = await self._send_request(request)\n        return GetTaskResponse(**response_dict)\n\n    async def cancel_task(self, payload: dict[str, Any]) -> CancelTaskResponse:\n        request = CancelTaskRequest(params=payload)\n        response_dict = await self._send_request(request)\n        return CancelTaskResponse(**response_dict)\n\n    async def set_task_callback(\n        self, payload: dict[str, Any]\n    ) -> SetTaskPushNotificationResponse:\n        request = SetTaskPushNotificationRequest(params=payload)\n        response_dict = await self._send_request(request)\n        return SetTaskPushNotificationResponse(**response_dict)\n\n    async def get_task_callback(\n        self, payload: dict[str, Any]\n    ) -> GetTaskPushNotificationResponse:\n        request = GetTaskPushNotificationRequest(params=payload)\n        response_dict = await self._send_request(request)\n        return GetTaskPushNotificationResponse(**response_dict)\n"
  },
  {
    "path": "cookbook/pocketflow-a2a/common/server/__init__.py",
    "content": "from .server import A2AServer\nfrom .task_manager import TaskManager, InMemoryTaskManager\n\n__all__ = [\"A2AServer\", \"TaskManager\", \"InMemoryTaskManager\"]\n"
  },
  {
    "path": "cookbook/pocketflow-a2a/common/server/server.py",
    "content": "from starlette.applications import Starlette\nfrom starlette.responses import JSONResponse\nfrom sse_starlette.sse import EventSourceResponse\nfrom starlette.requests import Request\nfrom common.types import (\n    A2ARequest,\n    JSONRPCResponse,\n    InvalidRequestError,\n    JSONParseError,\n    GetTaskRequest,\n    CancelTaskRequest,\n    SendTaskRequest,\n    SetTaskPushNotificationRequest,\n    GetTaskPushNotificationRequest,\n    InternalError,\n    AgentCard,\n    TaskResubscriptionRequest,\n    SendTaskStreamingRequest,\n    Message,\n)\nfrom pydantic import ValidationError\nimport json\nfrom typing import AsyncIterable, Any\nfrom common.server.task_manager import TaskManager\n\nimport logging\n\n# Configure a logger specific to the server\nlogger = logging.getLogger(\"A2AServer\")\n\n\nclass A2AServer:\n    def __init__(\n        self,\n        host=\"0.0.0.0\",\n        port=5000,\n        endpoint=\"/\",\n        agent_card: AgentCard = None,\n        task_manager: TaskManager = None,\n    ):\n        self.host = host\n        self.port = port\n        self.endpoint = endpoint\n        self.task_manager = task_manager\n        self.agent_card = agent_card\n        self.app = Starlette()\n        self.app.add_route(self.endpoint, self._process_request, methods=[\"POST\"])\n        self.app.add_route(\n            \"/.well-known/agent.json\", self._get_agent_card, methods=[\"GET\"]\n        )\n\n    def start(self):\n        if self.agent_card is None:\n            raise ValueError(\"agent_card is not defined\")\n\n        if self.task_manager is None:\n            raise ValueError(\"request_handler is not defined\")\n\n        import uvicorn\n\n        # Basic logging config moved to __main__.py for application-level control\n        uvicorn.run(self.app, host=self.host, port=self.port)\n\n    def _get_agent_card(self, request: Request) -> JSONResponse:\n        logger.info(\"Serving Agent Card request\")\n        return JSONResponse(self.agent_card.model_dump(exclude_none=True))\n\n    async def _process_request(self, request: Request):\n        request_id_for_log = \"N/A\"  # Default if parsing fails early\n        raw_body = b\"\"\n        try:\n            # Log raw body first\n            raw_body = await request.body()\n            body = json.loads(raw_body)  # Attempt parsing\n            request_id_for_log = body.get(\"id\", \"N/A\")  # Get ID if possible\n            logger.info(f\"<- Received Request (ID: {request_id_for_log}):\\n{json.dumps(body, indent=2)}\")\n\n            json_rpc_request = A2ARequest.validate_python(body)\n\n            # Route based on method (same as before)\n            if isinstance(json_rpc_request, GetTaskRequest):\n                result = await self.task_manager.on_get_task(json_rpc_request)\n            elif isinstance(json_rpc_request, SendTaskRequest):\n                result = await self.task_manager.on_send_task(json_rpc_request)\n            elif isinstance(json_rpc_request, SendTaskStreamingRequest):\n                result = await self.task_manager.on_send_task_subscribe(\n                    json_rpc_request\n                )\n            elif isinstance(json_rpc_request, CancelTaskRequest):\n                result = await self.task_manager.on_cancel_task(json_rpc_request)\n            elif isinstance(json_rpc_request, SetTaskPushNotificationRequest):\n                result = await self.task_manager.on_set_task_push_notification(json_rpc_request)\n            elif isinstance(json_rpc_request, GetTaskPushNotificationRequest):\n                result = await self.task_manager.on_get_task_push_notification(json_rpc_request)\n            elif isinstance(json_rpc_request, TaskResubscriptionRequest):\n                result = await self.task_manager.on_resubscribe_to_task(\n                    json_rpc_request\n                )\n            else:\n                logger.warning(f\"Unexpected request type: {type(json_rpc_request)}\")\n                raise ValueError(f\"Unexpected request type: {type(request)}\")\n\n            return self._create_response(result)  # Pass result to response creation\n\n        except json.decoder.JSONDecodeError as e:\n            logger.error(f\"JSON Parse Error for Request body: <<<{raw_body.decode('utf-8', errors='replace')}>>>\\nError: {e}\")\n            return self._handle_exception(e, request_id_for_log)  # Pass ID if known\n        except ValidationError as e:\n             logger.error(f\"Request Validation Error (ID: {request_id_for_log}): {e.json()}\")\n             return self._handle_exception(e, request_id_for_log)\n        except Exception as e:\n             logger.error(f\"Unhandled Exception processing request (ID: {request_id_for_log}): {e}\", exc_info=True)\n             return self._handle_exception(e, request_id_for_log)  # Pass ID if known\n\n    def _handle_exception(self, e: Exception, req_id=None) -> JSONResponse:  # Accept req_id\n        if isinstance(e, json.decoder.JSONDecodeError):\n            json_rpc_error = JSONParseError()\n        elif isinstance(e, ValidationError):\n            json_rpc_error = InvalidRequestError(data=json.loads(e.json()))\n        else:\n            # Log the full exception details\n            logger.error(f\"Internal Server Error (ReqID: {req_id}): {e}\", exc_info=True)\n            json_rpc_error = InternalError(message=f\"Internal Server Error: {type(e).__name__}\")\n\n        response = JSONRPCResponse(id=req_id, error=json_rpc_error)\n        response_dump = response.model_dump(exclude_none=True)\n        logger.info(f\"-> Sending Error Response (ReqID: {req_id}):\\n{json.dumps(response_dump, indent=2)}\")\n        # A2A errors are still sent with HTTP 200\n        return JSONResponse(response_dump, status_code=200)\n\n    def _create_response(self, result: Any) -> JSONResponse | EventSourceResponse:\n        if isinstance(result, AsyncIterable):\n            # Streaming response\n            async def event_generator(result_stream) -> AsyncIterable[dict[str, str]]:\n                stream_request_id = None  # Capture ID from the first event if possible\n                try:\n                    async for item in result_stream:\n                        # Log each streamed item\n                        response_json = item.model_dump_json(exclude_none=True)\n                        stream_request_id = item.id  # Update ID\n                        logger.info(f\"-> Sending SSE Event (ID: {stream_request_id}):\\n{json.dumps(json.loads(response_json), indent=2)}\")\n                        yield {\"data\": response_json}\n                    logger.info(f\"SSE Stream ended for request ID: {stream_request_id}\")\n                except Exception as e:\n                    logger.error(f\"Error during SSE generation (ReqID: {stream_request_id}): {e}\", exc_info=True)\n                    # Optionally yield an error event if the protocol allows/requires it\n                    # error_payload = JSONRPCResponse(id=stream_request_id, error=InternalError(message=f\"SSE Error: {e}\"))\n                    # yield {\"data\": error_payload.model_dump_json(exclude_none=True)}\n\n            logger.info(\"Starting SSE stream...\")  # Log stream start\n            return EventSourceResponse(event_generator(result))\n        elif isinstance(result, JSONRPCResponse):\n            # Standard JSON response\n            response_dump = result.model_dump(exclude_none=True)\n            log_id = result.id if result.id is not None else \"N/A (Notification?)\"\n            log_prefix = \"->\"\n            log_type = \"Response\"\n            if result.error:\n                 log_prefix = \"-> Sending Error\"\n                 log_type = \"Error Response\"\n\n            logger.info(f\"{log_prefix} {log_type} (ID: {log_id}):\\n{json.dumps(response_dump, indent=2)}\")\n            return JSONResponse(response_dump)\n        else:\n            # This should ideally not happen if task manager returns correctly\n            logger.error(f\"Task manager returned unexpected type: {type(result)}\")\n            err_resp = JSONRPCResponse(id=None, error=InternalError(message=\"Invalid internal response type\"))\n            return JSONResponse(err_resp.model_dump(exclude_none=True), status_code=500)\n"
  },
  {
    "path": "cookbook/pocketflow-a2a/common/server/task_manager.py",
    "content": "from abc import ABC, abstractmethod\nfrom typing import Union, AsyncIterable, List\nfrom common.types import Task\nfrom common.types import (\n    JSONRPCResponse,\n    TaskIdParams,\n    TaskQueryParams,\n    GetTaskRequest,\n    TaskNotFoundError,\n    SendTaskRequest,\n    CancelTaskRequest,\n    TaskNotCancelableError,\n    SetTaskPushNotificationRequest,\n    GetTaskPushNotificationRequest,\n    GetTaskResponse,\n    CancelTaskResponse,\n    SendTaskResponse,\n    SetTaskPushNotificationResponse,\n    GetTaskPushNotificationResponse,\n    PushNotificationNotSupportedError,\n    TaskSendParams,\n    TaskStatus,\n    TaskState,\n    TaskResubscriptionRequest,\n    SendTaskStreamingRequest,\n    SendTaskStreamingResponse,\n    Artifact,\n    PushNotificationConfig,\n    TaskStatusUpdateEvent,\n    JSONRPCError,\n    TaskPushNotificationConfig,\n    InternalError,\n)\nfrom common.server.utils import new_not_implemented_error\nimport asyncio\nimport logging\n\nlogger = logging.getLogger(__name__)\n\nclass TaskManager(ABC):\n    @abstractmethod\n    async def on_get_task(self, request: GetTaskRequest) -> GetTaskResponse:\n        pass\n\n    @abstractmethod\n    async def on_cancel_task(self, request: CancelTaskRequest) -> CancelTaskResponse:\n        pass\n\n    @abstractmethod\n    async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse:\n        pass\n\n    @abstractmethod\n    async def on_send_task_subscribe(\n        self, request: SendTaskStreamingRequest\n    ) -> Union[AsyncIterable[SendTaskStreamingResponse], JSONRPCResponse]:\n        pass\n\n    @abstractmethod\n    async def on_set_task_push_notification(\n        self, request: SetTaskPushNotificationRequest\n    ) -> SetTaskPushNotificationResponse:\n        pass\n\n    @abstractmethod\n    async def on_get_task_push_notification(\n        self, request: GetTaskPushNotificationRequest\n    ) -> GetTaskPushNotificationResponse:\n        pass\n\n    @abstractmethod\n    async def on_resubscribe_to_task(\n        self, request: TaskResubscriptionRequest\n    ) -> Union[AsyncIterable[SendTaskResponse], JSONRPCResponse]:\n        pass\n\n\nclass InMemoryTaskManager(TaskManager):\n    def __init__(self):\n        self.tasks: dict[str, Task] = {}\n        self.push_notification_infos: dict[str, PushNotificationConfig] = {}\n        self.lock = asyncio.Lock()\n        self.task_sse_subscribers: dict[str, List[asyncio.Queue]] = {}\n        self.subscriber_lock = asyncio.Lock()\n\n    async def on_get_task(self, request: GetTaskRequest) -> GetTaskResponse:\n        logger.info(f\"Getting task {request.params.id}\")\n        task_query_params: TaskQueryParams = request.params\n\n        async with self.lock:\n            task = self.tasks.get(task_query_params.id)\n            if task is None:\n                return GetTaskResponse(id=request.id, error=TaskNotFoundError())\n\n            task_result = self.append_task_history(\n                task, task_query_params.historyLength\n            )\n\n        return GetTaskResponse(id=request.id, result=task_result)\n\n    async def on_cancel_task(self, request: CancelTaskRequest) -> CancelTaskResponse:\n        logger.info(f\"Cancelling task {request.params.id}\")\n        task_id_params: TaskIdParams = request.params\n\n        async with self.lock:\n            task = self.tasks.get(task_id_params.id)\n            if task is None:\n                return CancelTaskResponse(id=request.id, error=TaskNotFoundError())\n\n        return CancelTaskResponse(id=request.id, error=TaskNotCancelableError())\n\n    @abstractmethod\n    async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse:\n        pass\n\n    @abstractmethod\n    async def on_send_task_subscribe(\n        self, request: SendTaskStreamingRequest\n    ) -> Union[AsyncIterable[SendTaskStreamingResponse], JSONRPCResponse]:\n        pass\n\n    async def set_push_notification_info(self, task_id: str, notification_config: PushNotificationConfig):\n        async with self.lock:\n            task = self.tasks.get(task_id)\n            if task is None:\n                raise ValueError(f\"Task not found for {task_id}\")\n\n            self.push_notification_infos[task_id] = notification_config\n\n        return\n    \n    async def get_push_notification_info(self, task_id: str) -> PushNotificationConfig:\n        async with self.lock:\n            task = self.tasks.get(task_id)\n            if task is None:\n                raise ValueError(f\"Task not found for {task_id}\")\n\n            return self.push_notification_infos[task_id]\n            \n        return\n    \n    async def has_push_notification_info(self, task_id: str) -> bool:\n        async with self.lock:\n            return task_id in self.push_notification_infos\n            \n\n    async def on_set_task_push_notification(\n        self, request: SetTaskPushNotificationRequest\n    ) -> SetTaskPushNotificationResponse:\n        logger.info(f\"Setting task push notification {request.params.id}\")\n        task_notification_params: TaskPushNotificationConfig = request.params\n\n        try:\n            await self.set_push_notification_info(task_notification_params.id, task_notification_params.pushNotificationConfig)\n        except Exception as e:\n            logger.error(f\"Error while setting push notification info: {e}\")\n            return JSONRPCResponse(\n                id=request.id,\n                error=InternalError(\n                    message=\"An error occurred while setting push notification info\"\n                ),\n            )\n            \n        return SetTaskPushNotificationResponse(id=request.id, result=task_notification_params)\n\n    async def on_get_task_push_notification(\n        self, request: GetTaskPushNotificationRequest\n    ) -> GetTaskPushNotificationResponse:\n        logger.info(f\"Getting task push notification {request.params.id}\")\n        task_params: TaskIdParams = request.params\n\n        try:\n            notification_info = await self.get_push_notification_info(task_params.id)\n        except Exception as e:\n            logger.error(f\"Error while getting push notification info: {e}\")\n            return GetTaskPushNotificationResponse(\n                id=request.id,\n                error=InternalError(\n                    message=\"An error occurred while getting push notification info\"\n                ),\n            )\n        \n        return GetTaskPushNotificationResponse(id=request.id, result=TaskPushNotificationConfig(id=task_params.id, pushNotificationConfig=notification_info))\n\n    async def upsert_task(self, task_send_params: TaskSendParams) -> Task:\n        logger.info(f\"Upserting task {task_send_params.id}\")\n        async with self.lock:\n            task = self.tasks.get(task_send_params.id)\n            if task is None:\n                task = Task(\n                    id=task_send_params.id,\n                    sessionId = task_send_params.sessionId,\n                    messages=[task_send_params.message],\n                    status=TaskStatus(state=TaskState.SUBMITTED),\n                    history=[task_send_params.message],\n                )\n                self.tasks[task_send_params.id] = task\n            else:\n                task.history.append(task_send_params.message)\n\n            return task\n\n    async def on_resubscribe_to_task(\n        self, request: TaskResubscriptionRequest\n    ) -> Union[AsyncIterable[SendTaskStreamingResponse], JSONRPCResponse]:\n        return new_not_implemented_error(request.id)\n\n    async def update_store(\n        self, task_id: str, status: TaskStatus, artifacts: list[Artifact]\n    ) -> Task:\n        async with self.lock:\n            try:\n                task = self.tasks[task_id]\n            except KeyError:\n                logger.error(f\"Task {task_id} not found for updating the task\")\n                raise ValueError(f\"Task {task_id} not found\")\n\n            task.status = status\n\n            if status.message is not None:\n                task.history.append(status.message)\n\n            if artifacts is not None:\n                if task.artifacts is None:\n                    task.artifacts = []\n                task.artifacts.extend(artifacts)\n\n            return task\n\n    def append_task_history(self, task: Task, historyLength: int | None):\n        new_task = task.model_copy()\n        if historyLength is not None and historyLength > 0:\n            new_task.history = new_task.history[-historyLength:]\n        else:\n            new_task.history = []\n\n        return new_task        \n\n    async def setup_sse_consumer(self, task_id: str, is_resubscribe: bool = False):\n        async with self.subscriber_lock:\n            if task_id not in self.task_sse_subscribers:\n                if is_resubscribe:\n                    raise ValueError(\"Task not found for resubscription\")\n                else:\n                    self.task_sse_subscribers[task_id] = []\n\n            sse_event_queue = asyncio.Queue(maxsize=0) # <=0 is unlimited\n            self.task_sse_subscribers[task_id].append(sse_event_queue)\n            return sse_event_queue\n\n    async def enqueue_events_for_sse(self, task_id, task_update_event):\n        async with self.subscriber_lock:\n            if task_id not in self.task_sse_subscribers:\n                return\n\n            current_subscribers = self.task_sse_subscribers[task_id]\n            for subscriber in current_subscribers:\n                await subscriber.put(task_update_event)\n\n    async def dequeue_events_for_sse(\n        self, request_id, task_id, sse_event_queue: asyncio.Queue\n    ) -> AsyncIterable[SendTaskStreamingResponse] | JSONRPCResponse:\n        try:\n            while True:                \n                event = await sse_event_queue.get()\n                if isinstance(event, JSONRPCError):\n                    yield SendTaskStreamingResponse(id=request_id, error=event)\n                    break\n                                                \n                yield SendTaskStreamingResponse(id=request_id, result=event)\n                if isinstance(event, TaskStatusUpdateEvent) and event.final:\n                    break\n        finally:\n            async with self.subscriber_lock:\n                if task_id in self.task_sse_subscribers:\n                    self.task_sse_subscribers[task_id].remove(sse_event_queue)\n\n"
  },
  {
    "path": "cookbook/pocketflow-a2a/common/server/utils.py",
    "content": "from common.types import (\n    JSONRPCResponse,\n    ContentTypeNotSupportedError,\n    UnsupportedOperationError,\n)\nfrom typing import List\n\n\ndef are_modalities_compatible(\n    server_output_modes: List[str], client_output_modes: List[str]\n):\n    \"\"\"Modalities are compatible if they are both non-empty\n    and there is at least one common element.\"\"\"\n    if client_output_modes is None or len(client_output_modes) == 0:\n        return True\n\n    if server_output_modes is None or len(server_output_modes) == 0:\n        return True\n\n    return any(x in server_output_modes for x in client_output_modes)\n\n\ndef new_incompatible_types_error(request_id):\n    return JSONRPCResponse(id=request_id, error=ContentTypeNotSupportedError())\n\n\ndef new_not_implemented_error(request_id):\n    return JSONRPCResponse(id=request_id, error=UnsupportedOperationError())\n"
  },
  {
    "path": "cookbook/pocketflow-a2a/common/types.py",
    "content": "from typing import Union, Any\nfrom pydantic import BaseModel, Field, TypeAdapter\nfrom typing import Literal, List, Annotated, Optional\nfrom datetime import datetime\nfrom pydantic import model_validator, ConfigDict, field_serializer\nfrom uuid import uuid4\nfrom enum import Enum\nfrom typing_extensions import Self\n\n\nclass TaskState(str, Enum):\n    SUBMITTED = \"submitted\"\n    WORKING = \"working\"\n    INPUT_REQUIRED = \"input-required\"\n    COMPLETED = \"completed\"\n    CANCELED = \"canceled\"\n    FAILED = \"failed\"\n    UNKNOWN = \"unknown\"\n\n\nclass TextPart(BaseModel):\n    type: Literal[\"text\"] = \"text\"\n    text: str\n    metadata: dict[str, Any] | None = None\n\n\nclass FileContent(BaseModel):\n    name: str | None = None\n    mimeType: str | None = None\n    bytes: str | None = None\n    uri: str | None = None\n\n    @model_validator(mode=\"after\")\n    def check_content(self) -> Self:\n        if not (self.bytes or self.uri):\n            raise ValueError(\"Either 'bytes' or 'uri' must be present in the file data\")\n        if self.bytes and self.uri:\n            raise ValueError(\n                \"Only one of 'bytes' or 'uri' can be present in the file data\"\n            )\n        return self\n\n\nclass FilePart(BaseModel):\n    type: Literal[\"file\"] = \"file\"\n    file: FileContent\n    metadata: dict[str, Any] | None = None\n\n\nclass DataPart(BaseModel):\n    type: Literal[\"data\"] = \"data\"\n    data: dict[str, Any]\n    metadata: dict[str, Any] | None = None\n\n\nPart = Annotated[Union[TextPart, FilePart, DataPart], Field(discriminator=\"type\")]\n\n\nclass Message(BaseModel):\n    role: Literal[\"user\", \"agent\"]\n    parts: List[Part]\n    metadata: dict[str, Any] | None = None\n\n\nclass TaskStatus(BaseModel):\n    state: TaskState\n    message: Message | None = None\n    timestamp: datetime = Field(default_factory=datetime.now)\n\n    @field_serializer(\"timestamp\")\n    def serialize_dt(self, dt: datetime, _info):\n        return dt.isoformat()\n\n\nclass Artifact(BaseModel):\n    name: str | None = None\n    description: str | None = None\n    parts: List[Part]\n    metadata: dict[str, Any] | None = None\n    index: int = 0\n    append: bool | None = None\n    lastChunk: bool | None = None\n\n\nclass Task(BaseModel):\n    id: str\n    sessionId: str | None = None\n    status: TaskStatus\n    artifacts: List[Artifact] | None = None\n    history: List[Message] | None = None\n    metadata: dict[str, Any] | None = None\n\n\nclass TaskStatusUpdateEvent(BaseModel):\n    id: str\n    status: TaskStatus\n    final: bool = False\n    metadata: dict[str, Any] | None = None\n\n\nclass TaskArtifactUpdateEvent(BaseModel):\n    id: str\n    artifact: Artifact    \n    metadata: dict[str, Any] | None = None\n\n\nclass AuthenticationInfo(BaseModel):\n    model_config = ConfigDict(extra=\"allow\")\n\n    schemes: List[str]\n    credentials: str | None = None\n\n\nclass PushNotificationConfig(BaseModel):\n    url: str\n    token: str | None = None\n    authentication: AuthenticationInfo | None = None\n\n\nclass TaskIdParams(BaseModel):\n    id: str\n    metadata: dict[str, Any] | None = None\n\n\nclass TaskQueryParams(TaskIdParams):\n    historyLength: int | None = None\n\n\nclass TaskSendParams(BaseModel):\n    id: str\n    sessionId: str = Field(default_factory=lambda: uuid4().hex)\n    message: Message\n    acceptedOutputModes: Optional[List[str]] = None\n    pushNotification: PushNotificationConfig | None = None\n    historyLength: int | None = None\n    metadata: dict[str, Any] | None = None\n\n\nclass TaskPushNotificationConfig(BaseModel):\n    id: str\n    pushNotificationConfig: PushNotificationConfig\n\n\n## RPC Messages\n\n\nclass JSONRPCMessage(BaseModel):\n    jsonrpc: Literal[\"2.0\"] = \"2.0\"\n    id: int | str | None = Field(default_factory=lambda: uuid4().hex)\n\n\nclass JSONRPCRequest(JSONRPCMessage):\n    method: str\n    params: dict[str, Any] | None = None\n\n\nclass JSONRPCError(BaseModel):\n    code: int\n    message: str\n    data: Any | None = None\n\n\nclass JSONRPCResponse(JSONRPCMessage):\n    result: Any | None = None\n    error: JSONRPCError | None = None\n\n\nclass SendTaskRequest(JSONRPCRequest):\n    method: Literal[\"tasks/send\"] = \"tasks/send\"\n    params: TaskSendParams\n\n\nclass SendTaskResponse(JSONRPCResponse):\n    result: Task | None = None\n\n\nclass SendTaskStreamingRequest(JSONRPCRequest):\n    method: Literal[\"tasks/sendSubscribe\"] = \"tasks/sendSubscribe\"\n    params: TaskSendParams\n\n\nclass SendTaskStreamingResponse(JSONRPCResponse):\n    result: TaskStatusUpdateEvent | TaskArtifactUpdateEvent | None = None\n\n\nclass GetTaskRequest(JSONRPCRequest):\n    method: Literal[\"tasks/get\"] = \"tasks/get\"\n    params: TaskQueryParams\n\n\nclass GetTaskResponse(JSONRPCResponse):\n    result: Task | None = None\n\n\nclass CancelTaskRequest(JSONRPCRequest):\n    method: Literal[\"tasks/cancel\",] = \"tasks/cancel\"\n    params: TaskIdParams\n\n\nclass CancelTaskResponse(JSONRPCResponse):\n    result: Task | None = None\n\n\nclass SetTaskPushNotificationRequest(JSONRPCRequest):\n    method: Literal[\"tasks/pushNotification/set\",] = \"tasks/pushNotification/set\"\n    params: TaskPushNotificationConfig\n\n\nclass SetTaskPushNotificationResponse(JSONRPCResponse):\n    result: TaskPushNotificationConfig | None = None\n\n\nclass GetTaskPushNotificationRequest(JSONRPCRequest):\n    method: Literal[\"tasks/pushNotification/get\",] = \"tasks/pushNotification/get\"\n    params: TaskIdParams\n\n\nclass GetTaskPushNotificationResponse(JSONRPCResponse):\n    result: TaskPushNotificationConfig | None = None\n\n\nclass TaskResubscriptionRequest(JSONRPCRequest):\n    method: Literal[\"tasks/resubscribe\",] = \"tasks/resubscribe\"\n    params: TaskIdParams\n\n\nA2ARequest = TypeAdapter(\n    Annotated[\n        Union[\n            SendTaskRequest,\n            GetTaskRequest,\n            CancelTaskRequest,\n            SetTaskPushNotificationRequest,\n            GetTaskPushNotificationRequest,\n            TaskResubscriptionRequest,\n            SendTaskStreamingRequest,\n        ],\n        Field(discriminator=\"method\"),\n    ]\n)\n\n## Error types\n\n\nclass JSONParseError(JSONRPCError):\n    code: int = -32700\n    message: str = \"Invalid JSON payload\"\n    data: Any | None = None\n\n\nclass InvalidRequestError(JSONRPCError):\n    code: int = -32600\n    message: str = \"Request payload validation error\"\n    data: Any | None = None\n\n\nclass MethodNotFoundError(JSONRPCError):\n    code: int = -32601\n    message: str = \"Method not found\"\n    data: None = None\n\n\nclass InvalidParamsError(JSONRPCError):\n    code: int = -32602\n    message: str = \"Invalid parameters\"\n    data: Any | None = None\n\n\nclass InternalError(JSONRPCError):\n    code: int = -32603\n    message: str = \"Internal error\"\n    data: Any | None = None\n\n\nclass TaskNotFoundError(JSONRPCError):\n    code: int = -32001\n    message: str = \"Task not found\"\n    data: None = None\n\n\nclass TaskNotCancelableError(JSONRPCError):\n    code: int = -32002\n    message: str = \"Task cannot be canceled\"\n    data: None = None\n\n\nclass PushNotificationNotSupportedError(JSONRPCError):\n    code: int = -32003\n    message: str = \"Push Notification is not supported\"\n    data: None = None\n\n\nclass UnsupportedOperationError(JSONRPCError):\n    code: int = -32004\n    message: str = \"This operation is not supported\"\n    data: None = None\n\n\nclass ContentTypeNotSupportedError(JSONRPCError):\n    code: int = -32005\n    message: str = \"Incompatible content types\"\n    data: None = None\n\n\nclass AgentProvider(BaseModel):\n    organization: str\n    url: str | None = None\n\n\nclass AgentCapabilities(BaseModel):\n    streaming: bool = False\n    pushNotifications: bool = False\n    stateTransitionHistory: bool = False\n\n\nclass AgentAuthentication(BaseModel):\n    schemes: List[str]\n    credentials: str | None = None\n\n\nclass AgentSkill(BaseModel):\n    id: str\n    name: str\n    description: str | None = None\n    tags: List[str] | None = None\n    examples: List[str] | None = None\n    inputModes: List[str] | None = None\n    outputModes: List[str] | None = None\n\n\nclass AgentCard(BaseModel):\n    name: str\n    description: str | None = None\n    url: str\n    provider: AgentProvider | None = None\n    version: str\n    documentationUrl: str | None = None\n    capabilities: AgentCapabilities\n    authentication: AgentAuthentication | None = None\n    defaultInputModes: List[str] = [\"text\"]\n    defaultOutputModes: List[str] = [\"text\"]\n    skills: List[AgentSkill]\n\n\nclass A2AClientError(Exception):\n    pass\n\n\nclass A2AClientHTTPError(A2AClientError):\n    def __init__(self, status_code: int, message: str):\n        self.status_code = status_code\n        self.message = message\n        super().__init__(f\"HTTP Error {status_code}: {message}\")\n\n\nclass A2AClientJSONError(A2AClientError):\n    def __init__(self, message: str):\n        self.message = message\n        super().__init__(f\"JSON Error: {message}\")\n\n\nclass MissingAPIKeyError(Exception):\n    \"\"\"Exception for missing API key.\"\"\"\n\n    pass"
  },
  {
    "path": "cookbook/pocketflow-a2a/common/utils/in_memory_cache.py",
    "content": "\"\"\"In Memory Cache utility.\"\"\"\n\nimport threading\nimport time\nfrom typing import Any, Dict, Optional\n\n\nclass InMemoryCache:\n    \"\"\"A thread-safe Singleton class to manage cache data.\n\n    Ensures only one instance of the cache exists across the application.\n    \"\"\"\n\n    _instance: Optional[\"InMemoryCache\"] = None\n    _lock: threading.Lock = threading.Lock()\n    _initialized: bool = False\n\n    def __new__(cls):\n        \"\"\"Override __new__ to control instance creation (Singleton pattern).\n\n        Uses a lock to ensure thread safety during the first instantiation.\n\n        Returns:\n            The singleton instance of InMemoryCache.\n        \"\"\"\n        if cls._instance is None:\n            with cls._lock:\n                if cls._instance is None:\n                    cls._instance = super().__new__(cls)\n        return cls._instance\n\n    def __init__(self):\n        \"\"\"Initialize the cache storage.\n\n        Uses a flag (_initialized) to ensure this logic runs only on the very first\n        creation of the singleton instance.\n        \"\"\"\n        if not self._initialized:\n            with self._lock:\n                if not self._initialized:\n                    # print(\"Initializing SessionCache storage\")\n                    self._cache_data: Dict[str, Dict[str, Any]] = {}\n                    self._ttl: Dict[str, float] = {}\n                    self._data_lock: threading.Lock = threading.Lock()\n                    self._initialized = True\n\n    def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:\n        \"\"\"Set a key-value pair.\n\n        Args:\n            key: The key for the data.\n            value: The data to store.\n            ttl: Time to live in seconds. If None, data will not expire.\n        \"\"\"\n        with self._data_lock:\n            self._cache_data[key] = value\n\n            if ttl is not None:\n                self._ttl[key] = time.time() + ttl\n            else:\n                if key in self._ttl:\n                    del self._ttl[key]\n\n    def get(self, key: str, default: Any = None) -> Any:\n        \"\"\"Get the value associated with a key.\n\n        Args:\n            key: The key for the data within the session.\n            default: The value to return if the session or key is not found.\n\n        Returns:\n            The cached value, or the default value if not found.\n        \"\"\"\n        with self._data_lock:\n            if key in self._ttl and time.time() > self._ttl[key]:\n                del self._cache_data[key]\n                del self._ttl[key]\n                return default\n            return self._cache_data.get(key, default)\n\n    def delete(self, key: str) -> None:\n        \"\"\"Delete a specific key-value pair from a cache.\n\n        Args:\n            key: The key to delete.\n\n        Returns:\n            True if the key was found and deleted, False otherwise.\n        \"\"\"\n\n        with self._data_lock:\n            if key in self._cache_data:\n                del self._cache_data[key]\n                if key in self._ttl:\n                    del self._ttl[key]\n                return True\n            return False\n\n    def clear(self) -> bool:\n        \"\"\"Remove all data.\n\n        Returns:\n            True if the data was cleared, False otherwise.\n        \"\"\"\n        with self._data_lock:\n            self._cache_data.clear()\n            self._ttl.clear()\n            return True\n        return False\n"
  },
  {
    "path": "cookbook/pocketflow-a2a/common/utils/push_notification_auth.py",
    "content": "from jwcrypto import jwk\nimport uuid\nfrom starlette.responses import JSONResponse\nfrom starlette.requests import Request\nfrom typing import Any\n\nimport jwt\nimport time\nimport json\nimport hashlib\nimport httpx\nimport logging\n\nfrom jwt import PyJWK, PyJWKClient\n\nlogger = logging.getLogger(__name__)\nAUTH_HEADER_PREFIX = 'Bearer '\n\nclass PushNotificationAuth:\n    def _calculate_request_body_sha256(self, data: dict[str, Any]):\n        \"\"\"Calculates the SHA256 hash of a request body.\n\n        This logic needs to be same for both the agent who signs the payload and the client verifier.\n        \"\"\"\n        body_str = json.dumps(\n            data,\n            ensure_ascii=False,\n            allow_nan=False,\n            indent=None,\n            separators=(\",\", \":\"),\n        )\n        return hashlib.sha256(body_str.encode()).hexdigest()\n\nclass PushNotificationSenderAuth(PushNotificationAuth):\n    def __init__(self):\n        self.public_keys = []\n        self.private_key_jwk: PyJWK = None\n\n    @staticmethod\n    async def verify_push_notification_url(url: str) -> bool:\n        async with httpx.AsyncClient(timeout=10) as client:\n            try:\n                validation_token = str(uuid.uuid4())\n                response = await client.get(\n                    url,\n                    params={\"validationToken\": validation_token}\n                )\n                response.raise_for_status()\n                is_verified = response.text == validation_token\n\n                logger.info(f\"Verified push-notification URL: {url} => {is_verified}\")            \n                return is_verified                \n            except Exception as e:\n                logger.warning(f\"Error during sending push-notification for URL {url}: {e}\")\n\n        return False\n\n    def generate_jwk(self):\n        key = jwk.JWK.generate(kty='RSA', size=2048, kid=str(uuid.uuid4()), use=\"sig\")\n        self.public_keys.append(key.export_public(as_dict=True))\n        self.private_key_jwk = PyJWK.from_json(key.export_private())\n    \n    def handle_jwks_endpoint(self, _request: Request):\n        \"\"\"Allow clients to fetch public keys.\n        \"\"\"\n        return JSONResponse({\n            \"keys\": self.public_keys\n        })\n    \n    def _generate_jwt(self, data: dict[str, Any]):\n        \"\"\"JWT is generated by signing both the request payload SHA digest and time of token generation.\n\n        Payload is signed with private key and it ensures the integrity of payload for client.\n        Including iat prevents from replay attack.\n        \"\"\"\n        \n        iat = int(time.time())\n\n        return jwt.encode(\n            {\"iat\": iat, \"request_body_sha256\": self._calculate_request_body_sha256(data)},\n            key=self.private_key_jwk,\n            headers={\"kid\": self.private_key_jwk.key_id},\n            algorithm=\"RS256\"\n        )\n\n    async def send_push_notification(self, url: str, data: dict[str, Any]):\n        jwt_token = self._generate_jwt(data)\n        headers = {'Authorization': f\"Bearer {jwt_token}\"}\n        async with httpx.AsyncClient(timeout=10) as client: \n            try:\n                response = await client.post(\n                    url,\n                    json=data,\n                    headers=headers\n                )\n                response.raise_for_status()\n                logger.info(f\"Push-notification sent for URL: {url}\")                            \n            except Exception as e:\n                logger.warning(f\"Error during sending push-notification for URL {url}: {e}\")\n\nclass PushNotificationReceiverAuth(PushNotificationAuth):\n    def __init__(self):\n        self.public_keys_jwks = []\n        self.jwks_client = None\n\n    async def load_jwks(self, jwks_url: str):\n        self.jwks_client = PyJWKClient(jwks_url)\n    \n    async def verify_push_notification(self, request: Request) -> bool:\n        auth_header = request.headers.get(\"Authorization\")\n        if not auth_header or not auth_header.startswith(AUTH_HEADER_PREFIX):\n            print(\"Invalid authorization header\")\n            return False\n        \n        token = auth_header[len(AUTH_HEADER_PREFIX):]\n        signing_key = self.jwks_client.get_signing_key_from_jwt(token)\n\n        decode_token = jwt.decode(\n            token,\n            signing_key,\n            options={\"require\": [\"iat\", \"request_body_sha256\"]},\n            algorithms=[\"RS256\"],\n        )\n\n        actual_body_sha256 = self._calculate_request_body_sha256(await request.json())\n        if actual_body_sha256 != decode_token[\"request_body_sha256\"]:\n            # Payload signature does not match the digest in signed token.\n            raise ValueError(\"Invalid request body\")\n        \n        if time.time() - decode_token[\"iat\"] > 60 * 5:\n            # Do not allow push-notifications older than 5 minutes.\n            # This is to prevent replay attack.\n            raise ValueError(\"Token is expired\")\n        \n        return True\n"
  },
  {
    "path": "cookbook/pocketflow-a2a/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import DecideAction, SearchWeb, AnswerQuestion\n\ndef create_agent_flow():\n    \"\"\"\n    Create and connect the nodes to form a complete agent flow.\n    \n    The flow works like this:\n    1. DecideAction node decides whether to search or answer\n    2. If search, go to SearchWeb node\n    3. If answer, go to AnswerQuestion node\n    4. After SearchWeb completes, go back to DecideAction\n    \n    Returns:\n        Flow: A complete research agent flow\n    \"\"\"\n    # Create instances of each node\n    decide = DecideAction()\n    search = SearchWeb()\n    answer = AnswerQuestion()\n    \n    # Connect the nodes\n    # If DecideAction returns \"search\", go to SearchWeb\n    decide - \"search\" >> search\n    \n    # If DecideAction returns \"answer\", go to AnswerQuestion\n    decide - \"answer\" >> answer\n    \n    # After SearchWeb completes and returns \"decide\", go back to DecideAction\n    search - \"decide\" >> decide\n    \n    # Create and return the flow, starting with the DecideAction node\n    return Flow(start=decide) "
  },
  {
    "path": "cookbook/pocketflow-a2a/main.py",
    "content": "import sys\nfrom flow import create_agent_flow\n\ndef main():\n    \"\"\"Simple function to process a question.\"\"\"\n    # Default question\n    default_question = \"Who won the Nobel Prize in Physics 2024?\"\n    \n    # Get question from command line if provided with --\n    question = default_question\n    for arg in sys.argv[1:]:\n        if arg.startswith(\"--\"):\n            question = arg[2:]\n            break\n    \n    # Create the agent flow\n    agent_flow = create_agent_flow()\n    \n    # Process the question\n    shared = {\"question\": question}\n    print(f\"🤔 Processing question: {question}\")\n    agent_flow.run(shared)\n    print(\"\\n🎯 Final Answer:\")\n    print(shared.get(\"answer\", \"No answer found\"))\n\nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "cookbook/pocketflow-a2a/nodes.py",
    "content": "from pocketflow import Node\nfrom utils import call_llm, search_web\nimport yaml\n\nclass DecideAction(Node):\n    def prep(self, shared):\n        \"\"\"Prepare the context and question for the decision-making process.\"\"\"\n        # Get the current context (default to \"No previous search\" if none exists)\n        context = shared.get(\"context\", \"No previous search\")\n        # Get the question from the shared store\n        question = shared[\"question\"]\n        # Return both for the exec step\n        return question, context\n        \n    def exec(self, inputs):\n        \"\"\"Call the LLM to decide whether to search or answer.\"\"\"\n        question, context = inputs\n        \n        print(f\"🤔 Agent deciding what to do next...\")\n        \n        # Create a prompt to help the LLM decide what to do next with proper yaml formatting\n        prompt = f\"\"\"\n### CONTEXT\nYou are a research assistant that can search the web.\nQuestion: {question}\nPrevious Research: {context}\n\n### ACTION SPACE\n[1] search\n  Description: Look up more information on the web\n  Parameters:\n    - query (str): What to search for\n\n[2] answer\n  Description: Answer the question with current knowledge\n  Parameters:\n    - answer (str): Final answer to the question\n\n## NEXT ACTION\nDecide the next action based on the context and available actions.\nReturn your response in this format:\n\n```yaml\nthinking: |\n    <your step-by-step reasoning process>\naction: search OR answer\nreason: <why you chose this action>\nanswer: <if action is answer>\nsearch_query: <specific search query if action is search>\n```\nIMPORTANT: Make sure to:\n1. Use proper indentation (4 spaces) for all multi-line fields\n2. Use the | character for multi-line text fields\n3. Keep single-line fields without the | character\n\"\"\"\n        \n        # Call the LLM to make a decision\n        response = call_llm(prompt)\n        \n        # Parse the response to get the decision\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        decision = yaml.safe_load(yaml_str)\n        \n        return decision\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Save the decision and determine the next step in the flow.\"\"\"\n        # If LLM decided to search, save the search query\n        if exec_res[\"action\"] == \"search\":\n            shared[\"search_query\"] = exec_res[\"search_query\"]\n            print(f\"🔍 Agent decided to search for: {exec_res['search_query']}\")\n        else:\n            shared[\"context\"] = exec_res[\"answer\"] #save the context if LLM gives the answer without searching.\n            print(f\"💡 Agent decided to answer the question\")\n        \n        # Return the action to determine the next node in the flow\n        return exec_res[\"action\"]\n\nclass SearchWeb(Node):\n    def prep(self, shared):\n        \"\"\"Get the search query from the shared store.\"\"\"\n        return shared[\"search_query\"]\n        \n    def exec(self, search_query):\n        \"\"\"Search the web for the given query.\"\"\"\n        # Call the search utility function\n        print(f\"🌐 Searching the web for: {search_query}\")\n        results = search_web(search_query)\n        return results\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Save the search results and go back to the decision node.\"\"\"\n        # Add the search results to the context in the shared store\n        previous = shared.get(\"context\", \"\")\n        shared[\"context\"] = previous + \"\\n\\nSEARCH: \" + shared[\"search_query\"] + \"\\nRESULTS: \" + exec_res\n        \n        print(f\"📚 Found information, analyzing results...\")\n        \n        # Always go back to the decision node after searching\n        return \"decide\"\n\nclass AnswerQuestion(Node):\n    def prep(self, shared):\n        \"\"\"Get the question and context for answering.\"\"\"\n        return shared[\"question\"], shared.get(\"context\", \"\")\n        \n    def exec(self, inputs):\n        \"\"\"Call the LLM to generate a final answer.\"\"\"\n        question, context = inputs\n        \n        print(f\"✍️ Crafting final answer...\")\n        \n        # Create a prompt for the LLM to answer the question\n        prompt = f\"\"\"\n### CONTEXT\nBased on the following information, answer the question.\nQuestion: {question}\nResearch: {context}\n\n## YOUR ANSWER:\nProvide a comprehensive answer using the research results.\n\"\"\"\n        # Call the LLM to generate an answer\n        answer = call_llm(prompt)\n        return answer\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Save the final answer and complete the flow.\"\"\"\n        # Save the answer in the shared store\n        shared[\"answer\"] = exec_res\n        \n        print(f\"✅ Answer generated successfully\")\n        \n        # We're done - no need to continue the flow\n        return \"done\" \n"
  },
  {
    "path": "cookbook/pocketflow-a2a/requirements.txt",
    "content": "# For PocketFlow Agent Logic\npocketflow>=0.0.1\nopenai>=1.0.0\nduckduckgo-search>=7.5.2\npyyaml>=5.1\n\n# For A2A Server Infrastructure (from common)\nstarlette>=0.37.2,<0.38.0\nuvicorn[standard]>=0.29.0,<0.30.0\nsse-starlette>=1.8.2,<2.0.0\npydantic>=2.0.0,<3.0.0\nhttpx>=0.27.0,<0.28.0\nanyio>=3.0.0,<5.0.0 # Dependency of starlette/httpx\n\n# For running __main__.py\nclick>=8.0.0,<9.0.0\n\n# For A2A Client\nhttpx>=0.27.0,<0.28.0\nhttpx-sse>=0.4.0\nasyncclick>=8.1.8 # Or just 'click' if you prefer asyncio.run\npydantic>=2.0.0,<3.0.0 # For common.types"
  },
  {
    "path": "cookbook/pocketflow-a2a/task_manager.py",
    "content": "# FILE: pocketflow_a2a_agent/task_manager.py\nimport logging\nfrom typing import AsyncIterable, Union\nimport asyncio\n\n# Import from the common code you copied\nfrom common.server.task_manager import InMemoryTaskManager\nfrom common.types import (\n    JSONRPCResponse, SendTaskRequest, SendTaskResponse,\n    SendTaskStreamingRequest, SendTaskStreamingResponse, Task, TaskSendParams,\n    TaskState, TaskStatus, TextPart, Artifact, UnsupportedOperationError,\n    InternalError, InvalidParamsError, \n    Message\n)\nimport common.server.utils as server_utils\n\n# Import directly from your original PocketFlow files\nfrom flow import create_agent_flow\n\nlogger = logging.getLogger(__name__)\n\nclass PocketFlowTaskManager(InMemoryTaskManager):\n    \"\"\" TaskManager implementation that runs the PocketFlow agent. \"\"\"\n\n    SUPPORTED_CONTENT_TYPES = [\"text\", \"text/plain\"] # Define what the agent accepts/outputs\n\n    async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse:\n        \"\"\"Handles non-streaming task requests.\"\"\"\n        logger.info(f\"Received task send request: {request.params.id}\")\n\n        # Validate output modes\n        if not server_utils.are_modalities_compatible(\n            request.params.acceptedOutputModes, self.SUPPORTED_CONTENT_TYPES\n        ):\n            logger.warning(\n                \"Unsupported output mode. Received %s, Support %s\",\n                request.params.acceptedOutputModes, self.SUPPORTED_CONTENT_TYPES\n            )\n            return SendTaskResponse(id=request.id, error=server_utils.new_incompatible_types_error(request.id).error)\n\n        # Upsert the task in the store (initial state: submitted)\n        # We create the task first so its state can be tracked, even if the sync execution fails\n        await self.upsert_task(request.params)\n        # Update state to working before running\n        await self.update_store(request.params.id, TaskStatus(state=TaskState.WORKING), [])\n\n\n        # --- Run the PocketFlow logic ---\n        task_params: TaskSendParams = request.params\n        query = self._get_user_query(task_params)\n        if query is None:\n            fail_status = TaskStatus(state=TaskState.FAILED, message=Message(role=\"agent\", parts=[TextPart(text=\"No text query found\")]))\n            await self.update_store(task_params.id, fail_status, [])\n            return SendTaskResponse(id=request.id, error=InvalidParamsError(message=\"No text query found in message parts\"))\n\n        shared_data = {\"question\": query}\n        agent_flow = create_agent_flow() # Create the flow instance\n\n        try:\n            # Run the synchronous PocketFlow\n            # In a real async server, you might run this in a separate thread/process\n            # executor to avoid blocking the event loop. For simplicity here, we run it directly.\n            # Consider adding a timeout if flows can hang.\n            logger.info(f\"Running PocketFlow for task {task_params.id}...\")\n            agent_flow.run(shared_data) # Run the flow, modifying shared_data in place\n            logger.info(f\"PocketFlow completed for task {task_params.id}\")\n            # Access the original shared_data dictionary, which was modified by the flow\n            answer_text = shared_data.get(\"answer\", \"Agent did not produce a final answer text.\")\n\n            # --- Package result into A2A Task ---\n            final_task_status = TaskStatus(state=TaskState.COMPLETED)\n            # Package the answer as an artifact\n            final_artifact = Artifact(parts=[TextPart(text=answer_text)])\n\n            # Update the task in the store with final status and artifact\n            final_task = await self.update_store(\n                task_params.id, final_task_status, [final_artifact]\n            )\n\n            # Prepare and return the A2A response\n            task_result = self.append_task_history(final_task, task_params.historyLength)\n            return SendTaskResponse(id=request.id, result=task_result)\n\n        except Exception as e:\n            logger.error(f\"Error executing PocketFlow for task {task_params.id}: {e}\", exc_info=True)\n            # Update task state to FAILED\n            fail_status = TaskStatus(\n                state=TaskState.FAILED,\n                message=Message(role=\"agent\", parts=[TextPart(text=f\"Agent execution failed: {e}\")])\n            )\n            await self.update_store(task_params.id, fail_status, [])\n            return SendTaskResponse(id=request.id, error=InternalError(message=f\"Agent error: {e}\"))\n\n    async def on_send_task_subscribe(\n        self, request: SendTaskStreamingRequest\n    ) -> Union[AsyncIterable[SendTaskStreamingResponse], JSONRPCResponse]:\n        \"\"\"Handles streaming requests - Not implemented for this synchronous agent.\"\"\"\n        logger.warning(f\"Streaming requested for task {request.params.id}, but not supported by this PocketFlow agent implementation.\")\n        # Return an error indicating streaming is not supported\n        return JSONRPCResponse(id=request.id, error=UnsupportedOperationError(message=\"Streaming not supported by this agent\"))\n\n    def _get_user_query(self, task_send_params: TaskSendParams) -> str | None:\n        \"\"\"Extracts the first text part from the user message.\"\"\"\n        if not task_send_params.message or not task_send_params.message.parts:\n            logger.warning(f\"No message parts found for task {task_send_params.id}\")\n            return None\n        for part in task_send_params.message.parts:\n            # Ensure part is treated as a dictionary if it came from JSON\n            part_dict = part if isinstance(part, dict) else part.model_dump()\n            if part_dict.get(\"type\") == \"text\" and \"text\" in part_dict:\n                 return part_dict[\"text\"]\n        logger.warning(f\"No text part found in message for task {task_send_params.id}\")\n        return None # No text part found"
  },
  {
    "path": "cookbook/pocketflow-a2a/utils.py",
    "content": "from openai import OpenAI\nimport os\nfrom duckduckgo_search import DDGS\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n\ndef search_web(query):\n    results = DDGS().text(query, max_results=5)\n    # Convert results to a string\n    results_str = \"\\n\\n\".join([f\"Title: {r['title']}\\nURL: {r['href']}\\nSnippet: {r['body']}\" for r in results])\n    return results_str\n    \nif __name__ == \"__main__\":\n    print(\"## Testing call_llm\")\n    prompt = \"In a few words, what is the meaning of life?\"\n    print(f\"## Prompt: {prompt}\")\n    response = call_llm(prompt)\n    print(f\"## Response: {response}\")\n\n    print(\"## Testing search_web\")\n    query = \"Who won the Nobel Prize in Physics 2024?\"\n    print(f\"## Query: {query}\")\n    results = search_web(query)\n    print(f\"## Results: {results}\")"
  },
  {
    "path": "cookbook/pocketflow-agent/README.md",
    "content": "# Research Agent\n\nThis project demonstrates a simple yet powerful LLM-powered research agent. This implementation is based directly on the tutorial: [LLM Agents are simply Graph — Tutorial For Dummies](https://zacharyhuang.substack.com/p/llm-agent-internal-as-a-graph-tutorial).\n\n👉 Run the tutorial in your browser: [Try Google Colab Notebook](\nhttps://colab.research.google.com/github/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-agent/demo.ipynb)\n\n## Features\n\n- Performs web searches to gather information\n- Makes decisions about when to search vs. when to answer\n- Generates comprehensive answers based on research findings\n\n## Getting Started\n\n1. Install the packages you need with this simple command:\n```bash\npip install -r requirements.txt\n```\n\n2. Let's get your OpenAI API key ready:\n\n```bash\nexport OPENAI_API_KEY=\"your-api-key-here\"\n```\n\n3. Let's do a quick check to make sure your API key is working properly:\n\n```bash\npython utils.py\n```\n\nThis will test both the LLM call and web search features. If you see responses, you're good to go!\n\n4. Try out the agent with the default question (about Nobel Prize winners):\n\n```bash\npython main.py\n```\n\n5. Got a burning question? Ask anything you want by using the `--` prefix:\n\n```bash\npython main.py --\"What is quantum computing?\"\n```\n\n## How It Works?\n\nThe magic happens through a simple but powerful graph structure with three main parts:\n\n```mermaid\ngraph TD\n    A[DecideAction] -->|\"search\"| B[SearchWeb]\n    A -->|\"answer\"| C[AnswerQuestion]\n    B -->|\"decide\"| A\n```\n\nHere's what each part does:\n1. **DecideAction**: The brain that figures out whether to search or answer\n2. **SearchWeb**: The researcher that goes out and finds information\n3. **AnswerQuestion**: The writer that crafts the final answer\n\nHere's what's in each file:\n- [`main.py`](./main.py): The starting point - runs the whole show!\n- [`flow.py`](./flow.py): Connects everything together into a smart agent\n- [`nodes.py`](./nodes.py): The building blocks that make decisions and take actions\n- [`utils.py`](./utils.py): Helper functions for talking to the LLM and searching the web\n"
  },
  {
    "path": "cookbook/pocketflow-agent/demo.ipynb",
    "content": "{\n  \"cells\": [\n    {\n      \"cell_type\": \"code\",\n      \"execution_count\": 1,\n      \"metadata\": {\n        \"vscode\": {\n          \"languageId\": \"plaintext\"\n        },\n        \"id\": \"8MeqVASIxKBH\"\n      },\n      \"outputs\": [],\n      \"source\": [\n        \"! pip install pocketflow>=0.0.1\\n\",\n        \"! pip install aiohttp>=3.8.0\\n\",\n        \"! pip install openai>=1.0.0\\n\",\n        \"! pip install duckduckgo-search>=7.5.2\"\n      ]\n    },\n    {\n      \"cell_type\": \"code\",\n      \"execution_count\": 2,\n      \"metadata\": {\n        \"vscode\": {\n          \"languageId\": \"plaintext\"\n        },\n        \"colab\": {\n          \"base_uri\": \"https://localhost:8080/\"\n        },\n        \"id\": \"wUp_sNU1xKBI\",\n        \"outputId\": \"a647f919-b253-48c8-c132-5eef582e29c7\"\n      },\n      \"outputs\": [\n        {\n          \"output_type\": \"stream\",\n          \"name\": \"stdout\",\n          \"text\": [\n            \"## Testing call_llm\\n\",\n            \"## Prompt: In a few words, what is the meaning of life?\\n\",\n            \"## Response: The meaning of life is a deeply personal and philosophical question. For many, it involves seeking happiness, forming relationships, pursuing knowledge, or finding purpose and fulfillment. It's a journey that varies for each individual.\\n\",\n            \"## Testing search_web\\n\",\n            \"## Query: Who won the Nobel Prize in Physics 2024?\\n\",\n            \"## Results: Title: Press release: The Nobel Prize in Physics 2024 - NobelPrize.org\\n\",\n            \"URL: https://www.nobelprize.org/prizes/physics/2024/press-release/\\n\",\n            \"Snippet: The Nobel Prize in Physics 2024 was awarded jointly to John J. Hopfield and Geoffrey Hinton \\\"for foundational discoveries and inventions that enable machine learning with artificial neural networks\\\"\\n\",\n            \"\\n\",\n            \"Title: Pioneers in artificial intelligence win the Nobel Prize in physics\\n\",\n            \"URL: https://apnews.com/article/nobel-prize-physics-fc0567de3f2ca45f81a7359a017cd542\\n\",\n            \"Snippet: Two pioneers of artificial intelligence have won the Nobel Prize in physics. John Hopfield and Geoffrey Hinton were awarded the prize Tuesday for discoveries and inventions that formed the building blocks of machine learning.\\n\",\n            \"\\n\",\n            \"Title: Nobel Prize 2024: All the Winners | TIME\\n\",\n            \"URL: https://time.com/7065011/nobel-prize-2024-winners/\\n\",\n            \"Snippet: The 2024 Nobel Prize announcements began on Oct. 7, recognizing groundbreaking contributions to humanity. The first prize, in the category of physiology or medicine, went to a pair of American ...\\n\",\n            \"\\n\",\n            \"Title: Nobel physics prize 2024 won by AI pioneers John Hopfield and Geoffrey ...\\n\",\n            \"URL: https://www.reuters.com/science/hopfield-hinton-win-2024-nobel-prize-physics-2024-10-08/\\n\",\n            \"Snippet: John Hopfield and Geoffrey Hinton won for discoveries that paved the way for the AI boom.\\n\",\n            \"\\n\",\n            \"Title: Nobel Prize in physics 2024 awarded for work on artificial intelligence ...\\n\",\n            \"URL: https://www.cnn.com/2024/10/08/science/nobel-prize-physics-hopfield-hinton-machine-learning-intl/index.html\\n\",\n            \"Snippet: The 2024 Nobel Prize in physics has been awarded to John Hopfield and Geoffrey Hinton for their fundamental discoveries in machine learning, which paved the way for how artificial intelligence is ...\\n\"\n          ]\n        }\n      ],\n      \"source\": [\n        \"# utils.py\\n\",\n        \"from openai import OpenAI\\n\",\n        \"import os\\n\",\n        \"from duckduckgo_search import DDGS\\n\",\n        \"\\n\",\n        \"def call_llm(prompt):\\n\",\n        \"    client = OpenAI(api_key=\\\"your-api-key\\\")\\n\",\n        \"    r = client.chat.completions.create(\\n\",\n        \"        model=\\\"gpt-4o\\\",\\n\",\n        \"        messages=[{\\\"role\\\": \\\"user\\\", \\\"content\\\": prompt}]\\n\",\n        \"    )\\n\",\n        \"    return r.choices[0].message.content\\n\",\n        \"\\n\",\n        \"def search_web(query):\\n\",\n        \"    results = DDGS().text(query, max_results=5)\\n\",\n        \"    # Convert results to a string\\n\",\n        \"    results_str = \\\"\\\\n\\\\n\\\".join([f\\\"Title: {r['title']}\\\\nURL: {r['href']}\\\\nSnippet: {r['body']}\\\" for r in results])\\n\",\n        \"    return results_str\\n\",\n        \"\\n\",\n        \"print(\\\"## Testing call_llm\\\")\\n\",\n        \"prompt = \\\"In a few words, what is the meaning of life?\\\"\\n\",\n        \"print(f\\\"## Prompt: {prompt}\\\")\\n\",\n        \"response = call_llm(prompt)\\n\",\n        \"print(f\\\"## Response: {response}\\\")\\n\",\n        \"\\n\",\n        \"print(\\\"## Testing search_web\\\")\\n\",\n        \"query = \\\"Who won the Nobel Prize in Physics 2024?\\\"\\n\",\n        \"print(f\\\"## Query: {query}\\\")\\n\",\n        \"results = search_web(query)\\n\",\n        \"print(f\\\"## Results: {results}\\\")\"\n      ]\n    },\n    {\n      \"cell_type\": \"code\",\n      \"execution_count\": 3,\n      \"metadata\": {\n        \"vscode\": {\n          \"languageId\": \"plaintext\"\n        },\n        \"id\": \"T0ETd4C2xKBI\"\n      },\n      \"outputs\": [],\n      \"source\": [\n        \"# nodes.py\\n\",\n        \"from pocketflow import Node\\n\",\n        \"import yaml\\n\",\n        \"\\n\",\n        \"class DecideAction(Node):\\n\",\n        \"    def prep(self, shared):\\n\",\n        \"        \\\"\\\"\\\"Prepare the context and question for the decision-making process.\\\"\\\"\\\"\\n\",\n        \"        # Get the current context (default to \\\"No previous search\\\" if none exists)\\n\",\n        \"        context = shared.get(\\\"context\\\", \\\"No previous search\\\")\\n\",\n        \"        # Get the question from the shared store\\n\",\n        \"        question = shared[\\\"question\\\"]\\n\",\n        \"        # Return both for the exec step\\n\",\n        \"        return question, context\\n\",\n        \"\\n\",\n        \"    def exec(self, inputs):\\n\",\n        \"        \\\"\\\"\\\"Call the LLM to decide whether to search or answer.\\\"\\\"\\\"\\n\",\n        \"        question, context = inputs\\n\",\n        \"\\n\",\n        \"        print(f\\\"🤔 Agent deciding what to do next...\\\")\\n\",\n        \"\\n\",\n        \"        # Create a prompt to help the LLM decide what to do next with proper yaml formatting\\n\",\n        \"        prompt = f\\\"\\\"\\\"\\n\",\n        \"### CONTEXT\\n\",\n        \"You are a research assistant that can search the web.\\n\",\n        \"Question: {question}\\n\",\n        \"Previous Research: {context}\\n\",\n        \"\\n\",\n        \"### ACTION SPACE\\n\",\n        \"[1] search\\n\",\n        \"  Description: Look up more information on the web\\n\",\n        \"  Parameters:\\n\",\n        \"    - query (str): What to search for\\n\",\n        \"\\n\",\n        \"[2] answer\\n\",\n        \"  Description: Answer the question with current knowledge\\n\",\n        \"  Parameters:\\n\",\n        \"    - answer (str): Final answer to the question\\n\",\n        \"\\n\",\n        \"## NEXT ACTION\\n\",\n        \"Decide the next action based on the context and available actions.\\n\",\n        \"Return your response in this format:\\n\",\n        \"\\n\",\n        \"```yaml\\n\",\n        \"thinking: |\\n\",\n        \"    <your step-by-step reasoning process>\\n\",\n        \"action: search OR answer\\n\",\n        \"reason: <why you chose this action>\\n\",\n        \"answer: <if action is answer>\\n\",\n        \"search_query: <specific search query if action is search>\\n\",\n        \"```\\n\",\n        \"IMPORTANT: Make sure to:\\n\",\n        \"1. Use proper indentation (4 spaces) for all multi-line fields\\n\",\n        \"2. Use the | character for multi-line text fields\\n\",\n        \"3. Keep single-line fields without the | character\\n\",\n        \"\\\"\\\"\\\"\\n\",\n        \"\\n\",\n        \"        # Call the LLM to make a decision\\n\",\n        \"        response = call_llm(prompt)\\n\",\n        \"\\n\",\n        \"        # Parse the response to get the decision\\n\",\n        \"        yaml_str = response.split(\\\"```yaml\\\")[1].split(\\\"```\\\")[0].strip()\\n\",\n        \"        decision = yaml.safe_load(yaml_str)\\n\",\n        \"\\n\",\n        \"        return decision\\n\",\n        \"\\n\",\n        \"    def post(self, shared, prep_res, exec_res):\\n\",\n        \"        \\\"\\\"\\\"Save the decision and determine the next step in the flow.\\\"\\\"\\\"\\n\",\n        \"        # If LLM decided to search, save the search query\\n\",\n        \"        if exec_res[\\\"action\\\"] == \\\"search\\\":\\n\",\n        \"            shared[\\\"search_query\\\"] = exec_res[\\\"search_query\\\"]\\n\",\n        \"            print(f\\\"🔍 Agent decided to search for: {exec_res['search_query']}\\\")\\n\",\n        \"        else:\\n\",\n        \"            shared[\\\"context\\\"] = exec_res[\\\"answer\\\"] #save the context if LLM gives the answer without searching.\\n\",\n        \"            print(f\\\"💡 Agent decided to answer the question\\\")\\n\",\n        \"\\n\",\n        \"        # Return the action to determine the next node in the flow\\n\",\n        \"        return exec_res[\\\"action\\\"]\\n\",\n        \"\\n\",\n        \"class SearchWeb(Node):\\n\",\n        \"    def prep(self, shared):\\n\",\n        \"        \\\"\\\"\\\"Get the search query from the shared store.\\\"\\\"\\\"\\n\",\n        \"        return shared[\\\"search_query\\\"]\\n\",\n        \"\\n\",\n        \"    def exec(self, search_query):\\n\",\n        \"        \\\"\\\"\\\"Search the web for the given query.\\\"\\\"\\\"\\n\",\n        \"        # Call the search utility function\\n\",\n        \"        print(f\\\"🌐 Searching the web for: {search_query}\\\")\\n\",\n        \"        results = search_web(search_query)\\n\",\n        \"        return results\\n\",\n        \"\\n\",\n        \"    def post(self, shared, prep_res, exec_res):\\n\",\n        \"        \\\"\\\"\\\"Save the search results and go back to the decision node.\\\"\\\"\\\"\\n\",\n        \"        # Add the search results to the context in the shared store\\n\",\n        \"        previous = shared.get(\\\"context\\\", \\\"\\\")\\n\",\n        \"        shared[\\\"context\\\"] = previous + \\\"\\\\n\\\\nSEARCH: \\\" + shared[\\\"search_query\\\"] + \\\"\\\\nRESULTS: \\\" + exec_res\\n\",\n        \"\\n\",\n        \"        print(f\\\"📚 Found information, analyzing results...\\\")\\n\",\n        \"\\n\",\n        \"        # Always go back to the decision node after searching\\n\",\n        \"        return \\\"decide\\\"\\n\",\n        \"\\n\",\n        \"class AnswerQuestion(Node):\\n\",\n        \"    def prep(self, shared):\\n\",\n        \"        \\\"\\\"\\\"Get the question and context for answering.\\\"\\\"\\\"\\n\",\n        \"        return shared[\\\"question\\\"], shared.get(\\\"context\\\", \\\"\\\")\\n\",\n        \"\\n\",\n        \"    def exec(self, inputs):\\n\",\n        \"        \\\"\\\"\\\"Call the LLM to generate a final answer.\\\"\\\"\\\"\\n\",\n        \"        question, context = inputs\\n\",\n        \"\\n\",\n        \"        print(f\\\"✍️ Crafting final answer...\\\")\\n\",\n        \"\\n\",\n        \"        # Create a prompt for the LLM to answer the question\\n\",\n        \"        prompt = f\\\"\\\"\\\"\\n\",\n        \"### CONTEXT\\n\",\n        \"Based on the following information, answer the question.\\n\",\n        \"Question: {question}\\n\",\n        \"Research: {context}\\n\",\n        \"\\n\",\n        \"## YOUR ANSWER:\\n\",\n        \"Provide a comprehensive answer using the research results.\\n\",\n        \"\\\"\\\"\\\"\\n\",\n        \"        # Call the LLM to generate an answer\\n\",\n        \"        answer = call_llm(prompt)\\n\",\n        \"        return answer\\n\",\n        \"\\n\",\n        \"    def post(self, shared, prep_res, exec_res):\\n\",\n        \"        \\\"\\\"\\\"Save the final answer and complete the flow.\\\"\\\"\\\"\\n\",\n        \"        # Save the answer in the shared store\\n\",\n        \"        shared[\\\"answer\\\"] = exec_res\\n\",\n        \"\\n\",\n        \"        print(f\\\"✅ Answer generated successfully\\\")\\n\",\n        \"\\n\",\n        \"        # We're done - no need to continue the flow\\n\",\n        \"        return \\\"done\\\"\"\n      ]\n    },\n    {\n      \"cell_type\": \"code\",\n      \"execution_count\": 4,\n      \"metadata\": {\n        \"vscode\": {\n          \"languageId\": \"plaintext\"\n        },\n        \"id\": \"0B4jCAmXxKBI\"\n      },\n      \"outputs\": [],\n      \"source\": [\n        \"# flow.py\\n\",\n        \"from pocketflow import Flow\\n\",\n        \"\\n\",\n        \"def create_agent_flow():\\n\",\n        \"    \\\"\\\"\\\"\\n\",\n        \"    Create and connect the nodes to form a complete agent flow.\\n\",\n        \"\\n\",\n        \"    The flow works like this:\\n\",\n        \"    1. DecideAction node decides whether to search or answer\\n\",\n        \"    2. If search, go to SearchWeb node\\n\",\n        \"    3. If answer, go to AnswerQuestion node\\n\",\n        \"    4. After SearchWeb completes, go back to DecideAction\\n\",\n        \"\\n\",\n        \"    Returns:\\n\",\n        \"        Flow: A complete research agent flow\\n\",\n        \"    \\\"\\\"\\\"\\n\",\n        \"    # Create instances of each node\\n\",\n        \"    decide = DecideAction()\\n\",\n        \"    search = SearchWeb()\\n\",\n        \"    answer = AnswerQuestion()\\n\",\n        \"\\n\",\n        \"    # Connect the nodes\\n\",\n        \"    # If DecideAction returns \\\"search\\\", go to SearchWeb\\n\",\n        \"    decide - \\\"search\\\" >> search\\n\",\n        \"\\n\",\n        \"    # If DecideAction returns \\\"answer\\\", go to AnswerQuestion\\n\",\n        \"    decide - \\\"answer\\\" >> answer\\n\",\n        \"\\n\",\n        \"    # After SearchWeb completes and returns \\\"decide\\\", go back to DecideAction\\n\",\n        \"    search - \\\"decide\\\" >> decide\\n\",\n        \"\\n\",\n        \"    # Create and return the flow, starting with the DecideAction node\\n\",\n        \"    return Flow(start=decide)\"\n      ]\n    },\n    {\n      \"cell_type\": \"code\",\n      \"execution_count\": 5,\n      \"metadata\": {\n        \"vscode\": {\n          \"languageId\": \"plaintext\"\n        },\n        \"colab\": {\n          \"base_uri\": \"https://localhost:8080/\"\n        },\n        \"id\": \"bIwsNEDCxKBI\",\n        \"outputId\": \"e6c02020-6fae-4377-8f0a-01d2580dd659\"\n      },\n      \"outputs\": [\n        {\n          \"output_type\": \"stream\",\n          \"name\": \"stdout\",\n          \"text\": [\n            \"🤔 Processing question: Who won the Nobel Prize in Physics 2024?\\n\",\n            \"🤔 Agent deciding what to do next...\\n\",\n            \"🔍 Agent decided to search for: 2024 Nobel Prize in Physics winner\\n\",\n            \"🌐 Searching the web for: 2024 Nobel Prize in Physics winner\\n\",\n            \"📚 Found information, analyzing results...\\n\",\n            \"🤔 Agent deciding what to do next...\\n\",\n            \"💡 Agent decided to answer the question\\n\",\n            \"✍️ Crafting final answer...\\n\",\n            \"✅ Answer generated successfully\\n\",\n            \"\\n\",\n            \"🎯 Final Answer:\\n\",\n            \"John J. Hopfield and Geoffrey Hinton won the 2024 Nobel Prize in Physics. They were awarded this prestigious recognition for their foundational discoveries and inventions that have significantly advanced the field of machine learning by enabling the use of artificial neural networks. These contributions have had a profound impact on the development and application of machine learning technologies.\\n\"\n          ]\n        }\n      ],\n      \"source\": [\n        \"# main.py\\n\",\n        \"import sys\\n\",\n        \"\\n\",\n        \"def main():\\n\",\n        \"    \\\"\\\"\\\"Simple function to process a question.\\\"\\\"\\\"\\n\",\n        \"    # Default question\\n\",\n        \"    default_question = \\\"Who won the Nobel Prize in Physics 2024?\\\"\\n\",\n        \"\\n\",\n        \"    # Get question from command line if provided with --\\n\",\n        \"    question = default_question\\n\",\n        \"    for arg in sys.argv[1:]:\\n\",\n        \"        if arg.startswith(\\\"--\\\"):\\n\",\n        \"            question = arg[2:]\\n\",\n        \"            break\\n\",\n        \"\\n\",\n        \"    # Create the agent flow\\n\",\n        \"    agent_flow = create_agent_flow()\\n\",\n        \"\\n\",\n        \"    # Process the question\\n\",\n        \"    shared = {\\\"question\\\": question}\\n\",\n        \"    print(f\\\"🤔 Processing question: {question}\\\")\\n\",\n        \"    agent_flow.run(shared)\\n\",\n        \"    print(\\\"\\\\n🎯 Final Answer:\\\")\\n\",\n        \"    print(shared.get(\\\"answer\\\", \\\"No answer found\\\"))\\n\",\n        \"\\n\",\n        \"main()\"\n      ]\n    }\n  ],\n  \"metadata\": {\n    \"language_info\": {\n      \"name\": \"python\"\n    },\n    \"colab\": {\n      \"provenance\": []\n    },\n    \"kernelspec\": {\n      \"name\": \"python3\",\n      \"display_name\": \"Python 3\"\n    }\n  },\n  \"nbformat\": 4,\n  \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "cookbook/pocketflow-agent/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import DecideAction, SearchWeb, AnswerQuestion\n\ndef create_agent_flow():\n    \"\"\"\n    Create and connect the nodes to form a complete agent flow.\n    \n    The flow works like this:\n    1. DecideAction node decides whether to search or answer\n    2. If search, go to SearchWeb node\n    3. If answer, go to AnswerQuestion node\n    4. After SearchWeb completes, go back to DecideAction\n    \n    Returns:\n        Flow: A complete research agent flow\n    \"\"\"\n    # Create instances of each node\n    decide = DecideAction()\n    search = SearchWeb()\n    answer = AnswerQuestion()\n    \n    # Connect the nodes\n    # If DecideAction returns \"search\", go to SearchWeb\n    decide - \"search\" >> search\n    \n    # If DecideAction returns \"answer\", go to AnswerQuestion\n    decide - \"answer\" >> answer\n    \n    # After SearchWeb completes and returns \"decide\", go back to DecideAction\n    search - \"decide\" >> decide\n    \n    # Create and return the flow, starting with the DecideAction node\n    return Flow(start=decide) "
  },
  {
    "path": "cookbook/pocketflow-agent/main.py",
    "content": "import sys\nfrom flow import create_agent_flow\n\ndef main():\n    \"\"\"Simple function to process a question.\"\"\"\n    # Default question\n    default_question = \"Who won the Nobel Prize in Physics 2024?\"\n    \n    # Get question from command line if provided with --\n    question = default_question\n    for arg in sys.argv[1:]:\n        if arg.startswith(\"--\"):\n            question = arg[2:]\n            break\n    \n    # Create the agent flow\n    agent_flow = create_agent_flow()\n    \n    # Process the question\n    shared = {\"question\": question}\n    print(f\"🤔 Processing question: {question}\")\n    agent_flow.run(shared)\n    print(\"\\n🎯 Final Answer:\")\n    print(shared.get(\"answer\", \"No answer found\"))\n\nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "cookbook/pocketflow-agent/nodes.py",
    "content": "from pocketflow import Node\nfrom utils import call_llm, search_web_duckduckgo\nimport yaml\nimport re\n\nclass DecideAction(Node):\n    def prep(self, shared):\n        \"\"\"Prepare the context and question for the decision-making process.\"\"\"\n        # Get the current context (default to \"No previous search\" if none exists)\n        context = shared.get(\"context\", \"No previous search\")\n        # Get the question from the shared store\n        question = shared[\"question\"]\n        # Return both for the exec step\n        return question, context\n        \n    def exec(self, inputs):\n        \"\"\"Call the LLM to decide whether to search or answer.\"\"\"\n        question, context = inputs\n        \n        print(f\"🤔 Agent deciding what to do next...\")\n        \n        # Create a prompt to help the LLM decide what to do next with proper yaml formatting\n        prompt = f\"\"\"\n### CONTEXT\nYou are a research assistant that can search the web.\nQuestion: {question}\nPrevious Research: {context}\n\n### ACTION SPACE\n[1] search\n  Description: Look up more information on the web\n  Parameters:\n    - query (str): What to search for\n\n[2] answer\n  Description: Answer the question with current knowledge\n  Parameters:\n    - answer (str): Final answer to the question\n\n## NEXT ACTION\nDecide the next action based on the context and available actions.\nReturn your response in this format:\n\n```yaml\nthinking: |\n    <your step-by-step reasoning process>\naction: search OR answer\nreason: |\n    <why you chose this action - always use block scalar>\nanswer: |\n    <if action is answer - always use block scalar, leave empty if searching>\nsearch_query: <specific search query if action is search (plain string)>\n```\nIMPORTANT: Make sure to:\n1. ALWAYS use the | block scalar for thinking, reason and answer so colons or quotes inside the text do not break YAML.\n2. Use proper indentation (4 spaces) for all multi-line fields under |.\n3. Keep search_query as a single line string without the | character.\n\"\"\"\n        \n        # Call the LLM to make a decision\n        response = call_llm(prompt)\n        \n        # Parse the response to get the decision\n        def extract_yaml_block(text):\n            \"\"\"Extract YAML from a fenced code block, or fall back to the whole text.\"\"\"\n            match = re.search(r\"```yaml(.*?)```\", text, re.DOTALL | re.IGNORECASE)\n            if match:\n                return match.group(1).strip()\n            return text.strip()\n\n        def parse_yaml_safely(block):\n            \"\"\"Parse YAML, retrying with block scalars if colon characters caused issues.\"\"\"\n            try:\n                return yaml.safe_load(block)\n            except yaml.YAMLError:\n                fixed_lines = []\n                for line in block.splitlines():\n                    if re.match(r\"^(thinking|reason|answer|search_query):\", line) and \"|\" not in line:\n                        key, _, val = line.partition(\":\")\n                        fixed_lines.append(f\"{key}: |\")\n                        val = val.strip()\n                        if val:\n                            fixed_lines.append(f\"  {val}\")\n                    else:\n                        fixed_lines.append(line)\n                fixed_block = \"\\n\".join(fixed_lines)\n                try:\n                    return yaml.safe_load(fixed_block)\n                except yaml.YAMLError as exc:\n                    raise ValueError(f\"Unable to parse LLM YAML response:\\n{block}\") from exc\n\n        yaml_str = extract_yaml_block(response)\n        decision = parse_yaml_safely(yaml_str)\n        \n        return decision\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Save the decision and determine the next step in the flow.\"\"\"\n        # If LLM decided to search, save the search query\n        if exec_res[\"action\"] == \"search\":\n            shared[\"search_query\"] = exec_res[\"search_query\"]\n            print(f\"🔍 Agent decided to search for: {exec_res['search_query']}\")\n        else:\n            shared[\"context\"] = exec_res[\"answer\"] #save the context if LLM gives the answer without searching.\n            print(f\"💡 Agent decided to answer the question\")\n        \n        # Return the action to determine the next node in the flow\n        return exec_res[\"action\"]\n\nclass SearchWeb(Node):\n    def prep(self, shared):\n        \"\"\"Get the search query from the shared store.\"\"\"\n        return shared[\"search_query\"]\n        \n    def exec(self, search_query):\n        \"\"\"Search the web for the given query.\"\"\"\n        # Call the search utility function\n        print(f\"🌐 Searching the web for: {search_query}\")\n        results = search_web_duckduckgo(search_query)\n        return results\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Save the search results and go back to the decision node.\"\"\"\n        # Add the search results to the context in the shared store\n        previous = shared.get(\"context\", \"\")\n        shared[\"context\"] = previous + \"\\n\\nSEARCH: \" + shared[\"search_query\"] + \"\\nRESULTS: \" + exec_res\n        \n        print(f\"📚 Found information, analyzing results...\")\n        \n        # Always go back to the decision node after searching\n        return \"decide\"\n\nclass AnswerQuestion(Node):\n    def prep(self, shared):\n        \"\"\"Get the question and context for answering.\"\"\"\n        return shared[\"question\"], shared.get(\"context\", \"\")\n        \n    def exec(self, inputs):\n        \"\"\"Call the LLM to generate a final answer.\"\"\"\n        question, context = inputs\n        \n        print(f\"✍️ Crafting final answer...\")\n        \n        # Create a prompt for the LLM to answer the question\n        prompt = f\"\"\"\n### CONTEXT\nBased on the following information, answer the question.\nQuestion: {question}\nResearch: {context}\n\n## YOUR ANSWER:\nProvide a comprehensive answer using the research results.\n\"\"\"\n        # Call the LLM to generate an answer\n        answer = call_llm(prompt)\n        return answer\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Save the final answer and complete the flow.\"\"\"\n        # Save the answer in the shared store\n        shared[\"answer\"] = exec_res\n        \n        print(f\"✅ Answer generated successfully\")\n        \n        # We're done - no need to continue the flow\n        return \"done\" \n"
  },
  {
    "path": "cookbook/pocketflow-agent/requirements.txt",
    "content": "pocketflow>=0.0.1\nddgs>=7.5.2     # For web search\naiohttp>=3.8.0               # For HTTP requests\nopenai>=1.0.0                # For LLM calls \nrequests>=2.25.1             # For HTTP requests\nPyYAML>=6.0.2                # For YAML parsing"
  },
  {
    "path": "cookbook/pocketflow-agent/utils.py",
    "content": "from openai import OpenAI\nimport os\nfrom ddgs import DDGS\nimport requests\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n\ndef search_web_duckduckgo(query):\n    results = DDGS().text(query, max_results=5)\n    # Convert results to a string\n    results_str = \"\\n\\n\".join([f\"Title: {r['title']}\\nURL: {r['href']}\\nSnippet: {r['body']}\" for r in results])\n    return results_str\n\ndef search_web_brave(query):\n\n    url = f\"https://api.search.brave.com/res/v1/web/search?q={query}\"\n    api_key = \"your brave search api key\"\n\n    headers = {\n        \"accept\": \"application/json\",\n        \"Accept-Encoding\": \"gzip\",\n        \"x-subscription-token\": api_key\n    }\n\n    response = requests.get(url, headers=headers)\n\n    if response.status_code == 200:\n        data = response.json()\n        results = data['web']['results']\n        results_str = \"\\n\\n\".join([f\"Title: {r['title']}\\nURL: {r['url']}\\nDescription: {r['description']}\" for r in results])     \n    else:\n        print(f\"Request failed with status code: {response.status_code}\")\n    return results_str\n    \nif __name__ == \"__main__\":\n    print(\"## Testing call_llm\")\n    prompt = \"In a few words, what is the meaning of life?\"\n    print(f\"## Prompt: {prompt}\")\n    response = call_llm(prompt)\n    print(f\"## Response: {response}\")\n\n    print(\"## Testing search_web\")\n    query = \"Who won the Nobel Prize in Physics 2024?\"\n    print(f\"## Query: {query}\")\n    results = search_web_duckduckgo(query)\n    print(f\"## Results: {results}\")"
  },
  {
    "path": "cookbook/pocketflow-agent-skills/README.md",
    "content": "# Agent Skills with PocketFlow\n\nThis cookbook shows a lightweight pattern for using **Agent Skills** inside a PocketFlow graph.\n\nAgent Skills are just reusable instruction files (Markdown) that you can route to at runtime.\n\n## What this demo does\n\n- keeps skills as local markdown files (`./skills/*.md`)\n- chooses a skill based on the user request\n- injects the chosen skill into the final LLM prompt\n\n## Flow\n\n```mermaid\ngraph TD\n    A[SelectSkill] --> B[ApplySkill]\n```\n\n1. **SelectSkill** picks a skill file (e.g. executive brief vs checklist writer)\n2. **ApplySkill** reads that skill and executes the task with the LLM\n\n## Run\n\n```bash\npip install -r requirements.txt\nexport OPENAI_API_KEY=\"your-key\"\npython main.py --\"Summarize this launch plan for a VP audience\"\n```\n\nTry another task:\n\n```bash\npython main.py --\"Turn this into an implementation checklist\"\n```\n\n## Files\n\n- `main.py` — CLI entry\n- `flow.py` — graph wiring\n- `nodes.py` — skill selection + execution nodes\n- `utils.py` — load skills + LLM helper\n- `skills/*.md` — reusable Agent Skills\n"
  },
  {
    "path": "cookbook/pocketflow-agent-skills/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import SelectSkill, ApplySkill\n\n\ndef create_flow():\n    select_skill = SelectSkill()\n    apply_skill = ApplySkill()\n\n    select_skill >> apply_skill\n\n    return Flow(start=select_skill)\n"
  },
  {
    "path": "cookbook/pocketflow-agent-skills/main.py",
    "content": "import sys\nfrom flow import create_flow\n\n\ndef parse_task(default_task: str) -> str:\n    for arg in sys.argv[1:]:\n        if arg.startswith(\"--\"):\n            return arg[2:]\n    return default_task\n\n\ndef main():\n    task = parse_task(\"Summarize this launch plan for a VP audience\")\n\n    shared = {\n        \"task\": task,\n        \"skills_dir\": \"skills\",\n    }\n\n    flow = create_flow()\n\n    print(f\"🧩 Task: {task}\")\n    flow.run(shared)\n\n    print(\"\\n=== Skill Used ===\")\n    print(shared.get(\"selected_skill\", \"(none)\"))\n\n    print(\"\\n=== Output ===\")\n    print(shared.get(\"result\", \"(no result)\"))\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "cookbook/pocketflow-agent-skills/nodes.py",
    "content": "from pocketflow import Node\nfrom utils import call_llm, load_skills\n\n\nclass SelectSkill(Node):\n    def prep(self, shared):\n        return {\n            \"task\": shared[\"task\"],\n            \"skills\": load_skills(shared[\"skills_dir\"]),\n        }\n\n    def exec(self, prep_res):\n        task = prep_res[\"task\"].lower()\n        skills = prep_res[\"skills\"]\n\n        # Tiny deterministic router for demo purposes.\n        if \"checklist\" in task or \"steps\" in task:\n            preferred = \"checklist_writer\"\n        else:\n            preferred = \"executive_brief\"\n\n        if preferred in skills:\n            return preferred, skills[preferred]\n\n        # fallback: first available skill\n        name, content = next(iter(skills.items()))\n        return name, content\n\n    def post(self, shared, prep_res, exec_res):\n        skill_name, skill_content = exec_res\n        shared[\"selected_skill\"] = skill_name\n        shared[\"selected_skill_content\"] = skill_content\n        return \"default\"\n\n\nclass ApplySkill(Node):\n    def prep(self, shared):\n        return {\n            \"task\": shared[\"task\"],\n            \"skill_name\": shared[\"selected_skill\"],\n            \"skill_content\": shared[\"selected_skill_content\"],\n        }\n\n    def exec(self, prep_res):\n        prompt = f\"\"\"\nYou are running an Agent Skill.\n\nSkill name: {prep_res['skill_name']}\n\nSkill instructions:\n---\n{prep_res['skill_content']}\n---\n\nUser task:\n{prep_res['task']}\n\nFollow the skill instructions exactly and return the final result only.\n\"\"\".strip()\n        return call_llm(prompt)\n\n    def post(self, shared, prep_res, exec_res):\n        shared[\"result\"] = exec_res\n        return \"default\"\n"
  },
  {
    "path": "cookbook/pocketflow-agent-skills/requirements.txt",
    "content": "pocketflow>=0.0.1\nopenai>=1.0.0\n"
  },
  {
    "path": "cookbook/pocketflow-agent-skills/skills/checklist_writer.md",
    "content": "# Checklist Writer Skill\n\nConvert requests into clear, actionable checklists.\n\n## Rules\n- Use numbered steps.\n- Keep each step short and verifiable.\n- Highlight dependencies and blockers.\n- End with a \"Definition of Done\" section.\n"
  },
  {
    "path": "cookbook/pocketflow-agent-skills/skills/executive_brief.md",
    "content": "# Executive Brief Skill\n\nYou are writing for senior leaders.\n\n## Rules\n- Keep it concise and decision-oriented.\n- Start with 3 bullet point summary.\n- Include risks and recommended next action.\n- Avoid implementation-level details unless critical.\n"
  },
  {
    "path": "cookbook/pocketflow-agent-skills/utils.py",
    "content": "from pathlib import Path\nimport os\nfrom openai import OpenAI\n\n\ndef load_skills(skills_dir: str) -> dict[str, str]:\n    skills = {}\n    for md_file in sorted(Path(skills_dir).glob(\"*.md\")):\n        skills[md_file.stem] = md_file.read_text(encoding=\"utf-8\")\n\n    if not skills:\n        raise ValueError(f\"No skill files found in {skills_dir}\")\n    return skills\n\n\ndef call_llm(prompt: str) -> str:\n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    response = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}],\n    )\n    return response.choices[0].message.content\n"
  },
  {
    "path": "cookbook/pocketflow-async-basic/README.md",
    "content": "# PocketFlow Async Basic Example\n\nThis example demonstrates async operations using a simple Recipe Finder that:\n1. Fetches recipes from an API (async HTTP)\n2. Processes them with an LLM (async LLM)\n3. Waits for user confirmation (async input)\n\n## What this Example Does\n\nWhen you run the example:\n1. You enter an ingredient (e.g., \"chicken\")\n2. It searches for recipes (async API call)\n3. It suggests a recipe (async LLM call)\n4. You approve or reject the suggestion\n5. If rejected, it tries again with a different recipe\n\n## How it Works\n\n1. **FetchRecipes (AsyncNode)**\n   ```python\n   async def prep_async(self, shared):\n       ingredient = input(\"Enter ingredient: \")\n       return ingredient\n\n   async def exec_async(self, ingredient):\n       # Async API call\n       recipes = await fetch_recipes(ingredient)\n       return recipes\n   ```\n\n2. **SuggestRecipe (AsyncNode)**\n   ```python\n   async def exec_async(self, recipes):\n       # Async LLM call\n       suggestion = await call_llm_async(\n           f\"Choose best recipe from: {recipes}\"\n       )\n       return suggestion\n   ```\n\n3. **GetApproval (AsyncNode)**\n   ```python\n   async def post_async(self, shared, prep_res, suggestion):\n       # Async user input\n       answer = await get_user_input(\n           f\"Accept {suggestion}? (y/n): \"\n       )\n       return \"accept\" if answer == \"y\" else \"retry\"\n   ```\n\n## Running the Example\n\n```bash\npip install -r requirements.txt\npython main.py\n```\n\n## Sample Interaction\n\n```\nEnter ingredient: chicken\nFetching recipes...\nFound 3 recipes.\n\nSuggesting best recipe...\nHow about: Grilled Chicken with Herbs\n\nAccept this recipe? (y/n): n\nSuggesting another recipe...\nHow about: Chicken Stir Fry\n\nAccept this recipe? (y/n): y\nGreat choice! Here's your recipe...\n```\n\n## Key Concepts\n\n1. **Async Operations**: Using `async/await` for:\n   - API calls (non-blocking I/O)\n   - LLM calls (potentially slow)\n   - User input (waiting for response)\n\n2. **AsyncNode Methods**:\n   - `prep_async`: Setup and data gathering\n   - `exec_async`: Main async processing\n   - `post_async`: Post-processing and decisions\n\n3. **Flow Control**:\n   - Actions (\"accept\"/\"retry\") control flow\n   - Retry loop for rejected suggestions "
  },
  {
    "path": "cookbook/pocketflow-async-basic/flow.py",
    "content": "\"\"\"AsyncFlow implementation for recipe finder.\"\"\"\n\nfrom pocketflow import AsyncFlow, Node\nfrom nodes import FetchRecipes, SuggestRecipe, GetApproval\n\nclass NoOp(Node):\n    \"\"\"Node that does nothing, used to properly end the flow.\"\"\"\n    pass\n\ndef create_flow():\n    \"\"\"Create and connect nodes into a flow.\"\"\"\n    \n    # Create nodes\n    fetch = FetchRecipes()\n    suggest = SuggestRecipe()\n    approve = GetApproval()\n    end = NoOp()\n    \n    # Connect nodes\n    fetch - \"suggest\" >> suggest\n    suggest - \"approve\" >> approve\n    approve - \"retry\" >> suggest  # Loop back for another suggestion\n    approve - \"accept\" >> end     # Properly end the flow\n    \n    # Create flow starting with fetch\n    flow = AsyncFlow(start=fetch)\n    return flow "
  },
  {
    "path": "cookbook/pocketflow-async-basic/main.py",
    "content": "import asyncio\nfrom flow import create_flow\n\nasync def main():\n    \"\"\"Run the recipe finder flow.\"\"\"\n    # Create flow\n    flow = create_flow()\n    \n    # Create shared store\n    shared = {}\n    \n    # Run flow\n    print(\"\\nWelcome to Recipe Finder!\")\n    print(\"------------------------\")\n    await flow.run_async(shared)\n    print(\"\\nThanks for using Recipe Finder!\")\n\nif __name__ == \"__main__\":\n    # Run the async main function\n    asyncio.run(main()) "
  },
  {
    "path": "cookbook/pocketflow-async-basic/nodes.py",
    "content": "from pocketflow import AsyncNode\nfrom utils import fetch_recipes, call_llm_async, get_user_input\n\nclass FetchRecipes(AsyncNode):\n    \"\"\"AsyncNode that fetches recipes.\"\"\"\n    \n    async def prep_async(self, shared):\n        \"\"\"Get ingredient from user.\"\"\"\n        ingredient = await get_user_input(\"Enter ingredient: \")\n        return ingredient\n    \n    async def exec_async(self, ingredient):\n        \"\"\"Fetch recipes asynchronously.\"\"\"\n        recipes = await fetch_recipes(ingredient)\n        return recipes\n    \n    async def post_async(self, shared, prep_res, recipes):\n        \"\"\"Store recipes and continue.\"\"\"\n        shared[\"recipes\"] = recipes\n        shared[\"ingredient\"] = prep_res\n        return \"suggest\"\n\nclass SuggestRecipe(AsyncNode):\n    \"\"\"AsyncNode that suggests a recipe using LLM.\"\"\"\n    \n    async def prep_async(self, shared):\n        \"\"\"Get recipes from shared store.\"\"\"\n        return shared[\"recipes\"]\n    \n    async def exec_async(self, recipes):\n        \"\"\"Get suggestion from LLM.\"\"\"\n        suggestion = await call_llm_async(\n            f\"Choose best recipe from: {', '.join(recipes)}\"\n        )\n        return suggestion\n    \n    async def post_async(self, shared, prep_res, suggestion):\n        \"\"\"Store suggestion and continue.\"\"\"\n        shared[\"suggestion\"] = suggestion\n        return \"approve\"\n\nclass GetApproval(AsyncNode):\n    \"\"\"AsyncNode that gets user approval.\"\"\"\n    \n    async def prep_async(self, shared):\n        \"\"\"Get current suggestion.\"\"\"\n        return shared[\"suggestion\"]\n    \n    async def exec_async(self, suggestion):\n        \"\"\"Ask for user approval.\"\"\"\n        answer = await get_user_input(f\"\\nAccept this recipe? (y/n): \")\n        return answer\n    \n    async def post_async(self, shared, prep_res, answer):\n        \"\"\"Handle user's decision.\"\"\"\n        if answer == \"y\":\n            print(\"\\nGreat choice! Here's your recipe...\")\n            print(f\"Recipe: {shared['suggestion']}\")\n            print(f\"Ingredient: {shared['ingredient']}\")\n            return \"accept\"\n        else:\n            print(\"\\nLet's try another recipe...\")\n            return \"retry\" "
  },
  {
    "path": "cookbook/pocketflow-async-basic/requirements.txt",
    "content": "pocketflow\naiohttp>=3.8.0  # For async HTTP requests\nopenai>=1.0.0   # For async LLM calls "
  },
  {
    "path": "cookbook/pocketflow-async-basic/utils.py",
    "content": "import asyncio\nimport aiohttp\nfrom openai import AsyncOpenAI\n\nasync def fetch_recipes(ingredient):\n    \"\"\"Fetch recipes from an API asynchronously.\"\"\"\n    print(f\"Fetching recipes for {ingredient}...\")\n    \n    # Simulate API call with delay\n    await asyncio.sleep(1)\n    \n    # Mock recipes (in real app, would fetch from API)\n    recipes = [\n        f\"{ingredient} Stir Fry\",\n        f\"Grilled {ingredient} with Herbs\",\n        f\"Baked {ingredient} with Vegetables\"\n    ]\n    \n    print(f\"Found {len(recipes)} recipes.\")\n    \n    return recipes\n\nasync def call_llm_async(prompt):\n    \"\"\"Make async LLM call.\"\"\"\n    print(\"\\nSuggesting best recipe...\")\n    \n    # Simulate LLM call with delay\n    await asyncio.sleep(1)\n    \n    # Mock LLM response (in real app, would call OpenAI)\n    recipes = prompt.split(\": \")[1].split(\", \")\n    suggestion = recipes[1]  # Always suggest second recipe\n    \n    print(f\"How about: {suggestion}\")\n    return suggestion\n\nasync def get_user_input(prompt):\n    \"\"\"Get user input asynchronously.\"\"\"\n    # Create event loop to handle async input\n    loop = asyncio.get_event_loop()\n    \n    # Get input in a non-blocking way\n    answer = await loop.run_in_executor(None, input, prompt)\n\n    return answer.lower() "
  },
  {
    "path": "cookbook/pocketflow-batch/README.md",
    "content": "# Batch Translation Process\n\nThis project demonstrates a batch processing implementation that enables LLMs to translate documents into multiple languages simultaneously. It's designed to efficiently handle the translation of markdown files while preserving formatting.\n\n## Features\n\n- Translates markdown content into multiple languages in parallel\n- Saves translated files to specified output directory\n\n## Getting Started\n\n1. Install the required packages:\n```bash\npip install -r requirements.txt\n```\n\n2. Set up your API key:\n```bash\nexport ANTHROPIC_API_KEY=\"your-api-key-here\"\n```\n\n3. Run the translation process:\n```bash\npython main.py\n```\n\n## How It Works\n\nThe implementation uses a `TranslateTextNode` that processes batches of translation requests:\n\n```mermaid\nflowchart LR\n    batch[TranslateTextNode]\n```\n\nThe `TranslateTextNode`:\n1. Prepares batches for multiple language translations\n2. Executes translations in parallel using the model\n3. Saves the translated content to individual files\n4. Maintains the original markdown structure\n\nThis approach demonstrates how PocketFlow can efficiently process multiple related tasks in parallel.\n\n## Example Output\n\nWhen you run the translation process, you'll see output similar to this:\n\n```\nTranslated Chinese text\nTranslated Spanish text\nTranslated Japanese text\nTranslated German text\nTranslated Russian text\nTranslated Portuguese text\nTranslated French text\nTranslated Korean text\nSaved translation to translations/README_CHINESE.md\nSaved translation to translations/README_SPANISH.md\nSaved translation to translations/README_JAPANESE.md\nSaved translation to translations/README_GERMAN.md\nSaved translation to translations/README_RUSSIAN.md\nSaved translation to translations/README_PORTUGUESE.md\nSaved translation to translations/README_FRENCH.md\nSaved translation to translations/README_KOREAN.md\n\n=== Translation Complete ===\nTranslations saved to: translations\n============================\n```\n\n## Files\n\n- [`main.py`](./main.py): Implementation of the batch translation node\n- [`utils.py`](./utils.py): Simple wrapper for calling the Anthropic model\n- [`requirements.txt`](./requirements.txt): Project dependencies\n\nThe translations are saved to the `translations` directory, with each file named according to the target language."
  },
  {
    "path": "cookbook/pocketflow-batch/main.py",
    "content": "import os\nimport time\nfrom pocketflow import BatchNode, Flow\nfrom utils import call_llm\n\nclass TranslateTextNode(BatchNode):\n    def prep(self, shared):\n        text = shared.get(\"text\", \"(No text provided)\")\n        languages = shared.get(\"languages\", [\"Chinese\", \"Spanish\", \"Japanese\", \"German\", \n                              \"Russian\", \"Portuguese\", \"French\", \"Korean\"])\n        \n        # Create batches for each language translation\n        return [(text, lang) for lang in languages]\n\n    def exec(self, data_tuple):\n        text, language = data_tuple\n        \n        prompt = f\"\"\"\nPlease translate the following markdown file into {language}. \nBut keep the original markdown format, links and code blocks.\nDirectly return the translated text, without any other text or comments.\n\nOriginal: \n{text}\n\nTranslated:\"\"\"\n        \n        result = call_llm(prompt)\n        print(f\"Translated {language} text\")\n        return {\"language\": language, \"translation\": result}\n\n    def post(self, shared, prep_res, exec_res_list):\n        # Create output directory if it doesn't exist\n        output_dir = shared.get(\"output_dir\", \"translations\")\n        os.makedirs(output_dir, exist_ok=True)\n        \n        # Write each translation to a file\n        for result in exec_res_list:\n            language, translation = result[\"language\"], result[\"translation\"]\n            \n            # Write to file\n            filename = os.path.join(output_dir, f\"README_{language.upper()}.md\")\n            with open(filename, \"w\", encoding=\"utf-8\") as f:\n                f.write(translation)\n            \n            print(f\"Saved translation to {filename}\")\n\nif __name__ == \"__main__\":\n    # read the text from ../../README.md\n    with open(\"../../README.md\", \"r\") as f:\n        text = f.read()\n    \n    # Default settings\n    shared = {\n        \"text\": text,\n        \"languages\": [\"Chinese\", \"Spanish\", \"Japanese\", \"German\", \"Russian\", \"Portuguese\", \"French\", \"Korean\"],\n        \"output_dir\": \"translations\"\n    }\n\n    # --- Time Measurement Start ---\n    print(f\"Starting sequential translation into {len(shared['languages'])} languages...\")\n    start_time = time.perf_counter()\n\n    # Run the translation flow\n    translate_node = TranslateTextNode(max_retries=3)\n    flow = Flow(start=translate_node)\n    flow.run(shared)\n\n    # --- Time Measurement End ---\n    end_time = time.perf_counter()\n    duration = end_time - start_time\n\n    print(f\"\\nTotal sequential translation time: {duration:.4f} seconds\") # Print duration\n    print(\"\\n=== Translation Complete ===\")\n    print(f\"Translations saved to: {shared['output_dir']}\")\n    print(\"============================\")"
  },
  {
    "path": "cookbook/pocketflow-batch/requirements.txt",
    "content": "pocketflow>=0.0.1\nanthropic>=0.15.0\npyyaml>=6.0"
  },
  {
    "path": "cookbook/pocketflow-batch/translations/README_CHINESE.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-line minimalist LLM framework\" width=\"600\"/>\n</div>\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | 中文 | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow 是一个[100行代码](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)的极简主义LLM框架\n\n- **轻量级**：仅100行代码。零臃肿，零依赖，零供应商锁定。\n  \n- **表达力强**：包含你喜爱的一切—([多-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[智能体](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html)，[工作流](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html)，[RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html)等等。\n\n- **[智能体编码](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**：让AI智能体（例如Cursor AI）构建智能体—生产力提升10倍！\n\nPocket Flow入门：\n- 安装方式，```pip install pocketflow```或者直接复制[源代码](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)（仅100行）。\n- 了解更多，查看[文档](https://the-pocket.github.io/PocketFlow/)。了解动机，阅读[故事](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just)。\n- 有问题？查看这个[AI助手](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant)，或[创建issue！](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 加入我们的[Discord](https://discord.gg/hUHHE9Sa6T)，与其他使用Pocket Flow构建应用的开发者交流！\n- 🎉 Pocket Flow最初是Python版本，但我们现在有[Typescript](https://github.com/The-Pocket/PocketFlow-Typescript)，[Java](https://github.com/The-Pocket/PocketFlow-Java)，[C++](https://github.com/The-Pocket/PocketFlow-CPP)和[Go](https://github.com/The-Pocket/PocketFlow-Go)版本！\n\n## 为什么选择Pocket Flow？\n\n当前的LLM框架过于臃肿... 你只需要100行代码就能构建LLM框架！\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **抽象**          | **应用特定包装器**                                      | **供应商特定包装器**                                    | **代码行数**       | **大小**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agent, Chain               | 很多 <br><sup><sub>(例如，QA, 摘要)</sub></sup>              | 很多 <br><sup><sub>(例如，OpenAI, Pinecone等)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agent, Chain            | 很多 <br><sup><sub>(例如，FileReadTool, SerperDevTool)</sub></sup>         | 很多 <br><sup><sub>(例如，OpenAI, Anthropic, Pinecone等)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agent                      | 一些 <br><sup><sub>(例如，CodeAgent, VisitWebTool)</sub></sup>         | 一些 <br><sup><sub>(例如，DuckDuckGo, Hugging Face等)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agent, Graph           | 一些 <br><sup><sub>(例如，语义搜索)</sub></sup>                     | 一些 <br><sup><sub>(例如，PostgresStore, SqliteSaver等) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agent                | 一些 <br><sup><sub>(例如，Tool Agent, Chat Agent)</sub></sup>              | 很多 <sup><sub>[可选]<br> (例如，OpenAI, Pinecone等)</sub></sup>        | 7K <br><sup><sub>(仅核心)</sub></sup>    | +26MB <br><sup><sub>(仅核心)</sub></sup>          |\n| **PocketFlow** | **Graph**                    | **无**                                                 | **无**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Pocket Flow如何工作？\n\n这[100行代码](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)捕捉了LLM框架的核心抽象：图（Graph）！\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\n从这里开始，很容易实现流行的设计模式，如([多-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[智能体](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html)，[工作流](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html)，[RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html)等。\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ 以下是基础教程：\n\n<div align=\"center\">\n  \n|  名称  | 难度    |  描述  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [聊天](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *入门*   | 带有对话历史的基础聊天机器人 |\n| [结构化输出](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *入门* | 通过提示从简历中提取结构化数据 |\n| [工作流](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *入门*   | 一个写作工作流，包括大纲编写、内容创作和样式应用 |\n| [智能体](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *入门*   | 一个可以搜索网络并回答问题的研究智能体 |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *入门*   | 一个简单的检索增强生成过程 |\n| [批处理](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *入门* | 一个将markdown内容翻译成多种语言的批处理器 |\n| [流式处理](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *入门*   | 具有用户中断功能的实时LLM流式演示 |\n| [聊天护栏](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *入门*  | 一个仅处理旅行相关查询的旅行顾问聊天机器人 |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *初级* | 使用map-reduce模式进行批量评估的简历资格处理器 |\n| [多智能体](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *初级* | 两个智能体之间异步通信的禁忌词游戏 |\n| [监督者](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *初级* | 研究智能体变得不可靠...让我们构建一个监督流程|\n| [并行](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *初级*   | 展示3倍加速的并行执行演示 |\n| [并行流](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *初级*   | 展示使用多个过滤器实现8倍加速的并行图像处理演示 |\n| [多数投票](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *初级* | 通过聚合多次解决方案尝试来提高推理准确性 |\n| [思考](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *初级*   | 通过思维链解决复杂推理问题 |\n| [记忆](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *初级* | 具有短期和长期记忆的聊天机器人 |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *初级* | 使用自动调试循环将自然语言转换为SQL查询 |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *初级* |  使用模型上下文协议进行数值运算的智能体 |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *初级* | 使用智能体到智能体协议包装的智能体，用于智能体间通信 |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *初级* | 具有SSE更新的人工审核循环的最小Web服务 |\n\n</div>\n\n👀 想看其他入门教程？[创建一个issue！](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## 如何使用Pocket Flow？\n\n🚀 通过**智能体编码**—最快的LLM应用开发范式—*人类设计*，*智能体编码*！\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ 以下是更复杂LLM应用的示例：\n\n<div align=\"center\">\n  \n|  应用名称     |  难度    | 主题  | 人类设计 | 智能体代码 |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [用Cursor构建Cursor](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>我们很快将达到奇点...</sup></sub> | ★★★ <br> *高级*   | [智能体](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [设计文档](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Flow代码](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [代码库知识构建器](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>生命太短暂，不应该困惑地盯着他人的代码</sup></sub> |  ★★☆ <br> *中级* | [工作流](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [设计文档](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Flow代码](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [询问AI Paul Graham](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>询问AI Paul Graham，以防你没被录取</sup></sub> | ★★☆ <br> *中级*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [设计文档](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Flow代码](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Youtube摘要器](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> 像你5岁一样向你解释YouTube视频 </sup></sub> | ★☆☆ <br> *初级*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [设计文档](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Flow代码](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [冷启动生成器](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> 将冷门线索转变为热门的即时破冰工具 </sup></sub> | ★☆☆ <br> *初级*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Web搜索](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [设计文档](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Flow代码](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- 想学习**智能体编码**？\n\n  - 查看[我的YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1)获取关于如何制作上述应用的视频教程！\n\n  - 想构建自己的LLM应用？阅读这篇[文章](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)！从[这个模板](https://github.com/The-Pocket/PocketFlow-Template-Python)开始！"
  },
  {
    "path": "cookbook/pocketflow-batch/translations/README_FRENCH.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – framework LLM minimaliste en 100 lignes\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | Français | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow est un framework LLM minimaliste en [100 lignes](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)\n\n- **Léger** : Seulement 100 lignes. Zéro superflu, zéro dépendance, zéro verrouillage fournisseur.\n  \n- **Expressif** : Tout ce que vous aimez — ([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agents](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), et plus encore.\n\n- **[Programmation Agentique](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)** : Laissez les Agents IA (par exemple, Cursor AI) créer des Agents — augmentez votre productivité par 10 !\n\nCommencer avec Pocket Flow :\n- Pour installer, ```pip install pocketflow``` ou copiez simplement le [code source](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) (seulement 100 lignes).\n- Pour en savoir plus, consultez la [documentation](https://the-pocket.github.io/PocketFlow/). Pour comprendre la motivation, lisez l'[histoire](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just).\n- Des questions ? Consultez cet [Assistant IA](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant), ou [créez une issue !](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 Rejoignez notre [Discord](https://discord.gg/hUHHE9Sa6T) pour vous connecter avec d'autres développeurs utilisant Pocket Flow !\n- 🎉 Pocket Flow est initialement en Python, mais nous avons maintenant des versions en [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP) et [Go](https://github.com/The-Pocket/PocketFlow-Go) !\n\n## Pourquoi Pocket Flow ?\n\nLes frameworks LLM actuels sont surchargés... Vous n'avez besoin que de 100 lignes pour un framework LLM !\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **Abstraction**          | **Wrappers spécifiques aux applications**                                      | **Wrappers spécifiques aux fournisseurs**                                    | **Lignes**       | **Taille**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agent, Chain               | Nombreux <br><sup><sub>(ex., QA, Résumé)</sub></sup>              | Nombreux <br><sup><sub>(ex., OpenAI, Pinecone, etc.)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agent, Chain            | Nombreux <br><sup><sub>(ex., FileReadTool, SerperDevTool)</sub></sup>         | Nombreux <br><sup><sub>(ex., OpenAI, Anthropic, Pinecone, etc.)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agent                      | Quelques <br><sup><sub>(ex., CodeAgent, VisitWebTool)</sub></sup>         | Quelques <br><sup><sub>(ex., DuckDuckGo, Hugging Face, etc.)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agent, Graph           | Quelques <br><sup><sub>(ex., Recherche Sémantique)</sub></sup>                     | Quelques <br><sup><sub>(ex., PostgresStore, SqliteSaver, etc.) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agent                | Quelques <br><sup><sub>(ex., Tool Agent, Chat Agent)</sub></sup>              | Nombreux <sup><sub>[Optionnel]<br> (ex., OpenAI, Pinecone, etc.)</sub></sup>        | 7K <br><sup><sub>(core-only)</sub></sup>    | +26MB <br><sup><sub>(core-only)</sub></sup>          |\n| **PocketFlow** | **Graph**                    | **Aucun**                                                 | **Aucun**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Comment fonctionne Pocket Flow ?\n\nLes [100 lignes](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) capturent l'abstraction fondamentale des frameworks LLM : le Graph !\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nDe là, il est facile d'implémenter des modèles de conception populaires comme ([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agents](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), etc.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ Voici des tutoriels de base :\n\n<div align=\"center\">\n  \n|  Nom  | Difficulté    |  Description  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *Débutant*   | Un chatbot basique avec historique de conversation |\n| [Sortie Structurée](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *Débutant* | Extraction de données structurées à partir de CV par prompt |\n| [Workflow](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *Débutant*   | Un workflow d'écriture qui planifie, rédige du contenu et applique un style |\n| [Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Débutant*   | Un agent de recherche qui peut chercher sur le web et répondre aux questions |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *Débutant*   | Un processus simple de génération augmentée par récupération |\n| [Batch](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *Débutant* | Un processeur par lots qui traduit du contenu markdown en plusieurs langues |\n| [Streaming](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Débutant*   | Une démo de streaming LLM en temps réel avec capacité d'interruption utilisateur |\n| [Garde-fou de Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *Débutant*  | Un chatbot conseiller de voyage qui ne traite que les requêtes liées au voyage |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *Intermédiaire* | Un processeur de qualification de CV utilisant le modèle map-reduce pour l'évaluation par lots |\n| [Multi-Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *Intermédiaire* | Un jeu de Tabou pour la communication asynchrone entre deux agents |\n| [Superviseur](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Intermédiaire* | L'agent de recherche devient peu fiable... Construisons un processus de supervision |\n| [Parallèle](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Intermédiaire*   | Une démo d'exécution parallèle montrant une accélération de 3x |\n| [Flux Parallèle](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *Intermédiaire*   | Une démo de traitement d'image parallèle montrant une accélération de 8x avec plusieurs filtres |\n| [Vote Majoritaire](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *Intermédiaire* | Améliorer la précision du raisonnement en agrégeant plusieurs tentatives de solution |\n| [Réflexion](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Intermédiaire*   | Résoudre des problèmes de raisonnement complexes grâce à la Chaîne de Pensée |\n| [Mémoire](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Intermédiaire* | Un chatbot avec mémoire à court et long terme |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *Intermédiaire* | Convertir le langage naturel en requêtes SQL avec une boucle d'auto-débogage |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *Intermédiaire* |  Agent utilisant le Protocole de Contexte de Modèle pour les opérations numériques |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *Intermédiaire* | Agent encapsulé avec le protocole Agent-to-Agent pour la communication inter-agent |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *Intermédiaire* | Un service web minimal pour une boucle de révision humaine avec mises à jour SSE |\n\n</div>\n\n👀 Vous voulez voir d'autres tutoriels pour débutants ? [Créez une issue !](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## Comment utiliser Pocket Flow ?\n\n🚀 Par la **Programmation Agentique** — le paradigme de développement d'applications LLM le plus rapide — où *les humains conçoivent* et *les agents programment* !\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ Voici des exemples d'applications LLM plus complexes :\n\n<div align=\"center\">\n  \n|  Nom de l'application     |  Difficulté    | Sujets  | Conception Humaine | Code Agent |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Construire Cursor avec Cursor](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>Nous atteindrons bientôt la singularité ...</sup></sub> | ★★★ <br> *Avancé*   | [Agent](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [Document de conception](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Code Flow](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [Constructeur de Connaissances de Base de Code](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>La vie est trop courte pour rester perplexe devant le code des autres</sup></sub> |  ★★☆ <br> *Moyen* | [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [Document de conception](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Code Flow](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [Interroger l'IA Paul Graham](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>Interrogez l'IA Paul Graham, au cas où vous ne seriez pas accepté</sup></sub> | ★★☆ <br> *Moyen*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [Document de conception](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Code Flow](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Résumeur Youtube](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> Vous explique les vidéos YouTube comme si vous aviez 5 ans </sup></sub> | ★☆☆ <br> *Intermédiaire*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [Document de conception](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Code Flow](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [Générateur d'Accroche pour Email](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> Des brise-glaces instantanés qui transforment les prospects froids en prospects chauds </sup></sub> | ★☆☆ <br> *Intermédiaire*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Recherche Web](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [Document de conception](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Code Flow](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- Vous voulez apprendre la **Programmation Agentique** ?\n\n  - Consultez [ma chaîne YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1) pour des tutoriels vidéo sur la façon dont certaines applications ci-dessus sont créées !\n\n  - Vous voulez créer votre propre application LLM ? Lisez cet [article](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to) ! Commencez avec [ce modèle](https://github.com/The-Pocket/PocketFlow-Template-Python) !"
  },
  {
    "path": "cookbook/pocketflow-batch/translations/README_GERMAN.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-Zeilen minimalistisches LLM-Framework\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | Deutsch | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![Lizenz: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow ist ein [100-zeiliges](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) minimalistisches LLM-Framework\n\n- **Leichtgewichtig**: Nur 100 Zeilen. Kein Ballast, keine Abhängigkeiten, keine Anbieterbindung.\n  \n- **Ausdrucksstark**: Alles, was Sie lieben—([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agenten](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), und mehr.\n\n- **[Agenten-basiertes Programmieren](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: Lassen Sie KI-Agenten (z.B. Cursor AI) Agenten bauen—10-fache Produktivitätssteigerung!\n\nErste Schritte mit Pocket Flow:\n- Zur Installation, ```pip install pocketflow```oder kopieren Sie einfach den [Quellcode](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) (nur 100 Zeilen).\n- Um mehr zu erfahren, schauen Sie in die [Dokumentation](https://the-pocket.github.io/PocketFlow/). Um die Motivation zu verstehen, lesen Sie die [Geschichte](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just).\n- Haben Sie Fragen? Schauen Sie sich diesen [KI-Assistenten](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant) an, oder [erstellen Sie ein Issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 Treten Sie unserem [Discord](https://discord.gg/hUHHE9Sa6T) bei, um sich mit anderen Entwicklern zu vernetzen, die mit Pocket Flow arbeiten!\n- 🎉 Pocket Flow ist ursprünglich in Python geschrieben, aber wir haben jetzt auch Versionen für [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP) und [Go](https://github.com/The-Pocket/PocketFlow-Go)!\n\n## Warum Pocket Flow?\n\nAktuelle LLM-Frameworks sind aufgebläht... Sie brauchen nur 100 Zeilen für ein LLM-Framework!\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **Abstraktion**          | **App-spezifische Wrapper**                                      | **Anbieter-spezifische Wrapper**                                    | **Zeilen**       | **Größe**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agent, Chain               | Viele <br><sup><sub>(z.B. QA, Zusammenfassung)</sub></sup>              | Viele <br><sup><sub>(z.B. OpenAI, Pinecone, etc.)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agent, Chain            | Viele <br><sup><sub>(z.B. FileReadTool, SerperDevTool)</sub></sup>         | Viele <br><sup><sub>(z.B. OpenAI, Anthropic, Pinecone, etc.)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agent                      | Einige <br><sup><sub>(z.B. CodeAgent, VisitWebTool)</sub></sup>         | Einige <br><sup><sub>(z.B. DuckDuckGo, Hugging Face, etc.)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agent, Graph           | Einige <br><sup><sub>(z.B. Semantic Search)</sub></sup>                     | Einige <br><sup><sub>(z.B. PostgresStore, SqliteSaver, etc.) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agent                | Einige <br><sup><sub>(z.B. Tool Agent, Chat Agent)</sub></sup>              | Viele <sup><sub>[Optional]<br> (z.B. OpenAI, Pinecone, etc.)</sub></sup>        | 7K <br><sup><sub>(nur Kern)</sub></sup>    | +26MB <br><sup><sub>(nur Kern)</sub></sup>          |\n| **PocketFlow** | **Graph**                    | **Keine**                                                 | **Keine**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Wie funktioniert Pocket Flow?\n\nDie [100 Zeilen](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) erfassen die Kernabstraktion von LLM-Frameworks: Graph!\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nVon dort aus ist es einfach, beliebte Designmuster wie ([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agenten](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), etc. zu implementieren.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ Hier sind grundlegende Tutorials:\n\n<div align=\"center\">\n  \n|  Name  | Schwierigkeit    |  Beschreibung  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *Anfänger*   | Ein einfacher Chatbot mit Gesprächsverlauf |\n| [Strukturierte Ausgabe](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *Anfänger* | Extraktion strukturierter Daten aus Lebensläufen durch Prompting |\n| [Workflow](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *Anfänger*   | Ein Schreib-Workflow, der gliedert, Inhalte schreibt und Formatierungen anwendet |\n| [Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Anfänger*   | Ein Recherche-Agent, der im Web suchen und Fragen beantworten kann |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *Anfänger*   | Ein einfacher Abrufsaugmentierter Generierungsprozess |\n| [Batch](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *Anfänger* | Ein Batch-Prozessor, der Markdown-Inhalte in mehrere Sprachen übersetzt |\n| [Streaming](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Anfänger*   | Eine Echtzeit-LLM-Streaming-Demo mit Benutzer-Unterbrechungsfunktion |\n| [Chat-Leitplanke](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *Anfänger*  | Ein Reiseberater-Chatbot, der nur reisebezogene Anfragen verarbeitet |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *Einsteiger* | Ein Lebenslauf-Qualifikationsprozessor, der das Map-Reduce-Muster für Batch-Auswertungen verwendet |\n| [Multi-Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *Einsteiger* | Ein Tabu-Wortspiel für asynchrone Kommunikation zwischen zwei Agenten |\n| [Supervisor](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Einsteiger* | Forschungsagent wird unzuverlässig... Bauen wir einen Überwachungsprozess auf|\n| [Parallel](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Einsteiger*   | Eine parallele Ausführungsdemo, die 3-fache Beschleunigung zeigt |\n| [Paralleler Flow](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *Einsteiger*   | Eine parallele Bildverarbeitungsdemo, die 8-fache Beschleunigung mit mehreren Filtern zeigt |\n| [Mehrheitswahl](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *Einsteiger* | Verbesserte Schlussfolgerungsgenauigkeit durch Aggregation mehrerer Lösungsversuche |\n| [Denken](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Einsteiger*   | Lösen komplexer Schlussfolgerungsprobleme durch Chain-of-Thought |\n| [Gedächtnis](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Einsteiger* | Ein Chatbot mit Kurz- und Langzeitgedächtnis |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *Einsteiger* | Konvertierung natürlicher Sprache in SQL-Abfragen mit Auto-Debug-Schleife |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *Einsteiger* |  Agent mit Model Context Protocol für numerische Operationen |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *Einsteiger* | Agent mit Agent-to-Agent-Protokoll für Inter-Agenten-Kommunikation |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *Einsteiger* | Ein minimaler Webdienst für eine menschliche Überprüfungsschleife mit SSE-Updates |\n\n</div>\n\n👀 Möchten Sie andere Tutorials für Anfänger sehen? [Erstellen Sie ein Issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## Wie verwendet man Pocket Flow?\n\n🚀 Durch **Agenten-basiertes Programmieren**—das schnellste LLM-App-Entwicklungsparadigma, bei dem *Menschen entwerfen* und *Agenten programmieren*!\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ Hier sind Beispiele für komplexere LLM-Apps:\n\n<div align=\"center\">\n  \n|  App-Name     |  Schwierigkeit    | Themen  | Menschlicher Entwurf | Agent-Code |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Cursor mit Cursor bauen](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>Wir werden bald die Singularität erreichen ...</sup></sub> | ★★★ <br> *Fortgeschritten*   | [Agent](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [Design-Dokument](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Flow-Code](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [Codebase-Wissensgenerator](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>Das Leben ist zu kurz, um ratlos fremden Code anzustarren</sup></sub> |  ★★☆ <br> *Mittel* | [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [Design-Dokument](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Flow-Code](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [Frage KI Paul Graham](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>Frage KI Paul Graham, falls du nicht reinkommst</sup></sub> | ★★☆ <br> *Mittel*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [Design-Dokument](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Flow-Code](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Youtube-Zusammenfasser](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> Erklärt YouTube-Videos so, als wärst du 5 </sup></sub> | ★☆☆ <br> *Einsteiger*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [Design-Dokument](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Flow-Code](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [Cold-Opener-Generator](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> Sofortige Eisbrecher, die kalte Leads heiß machen </sup></sub> | ★☆☆ <br> *Einsteiger*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Web-Suche](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [Design-Dokument](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Flow-Code](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- Möchten Sie **Agenten-basiertes Programmieren** lernen?\n\n  - Schauen Sie sich [meinen YouTube-Kanal](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1) für Video-Tutorials an, wie einige der oben genannten Apps erstellt wurden!\n\n  - Möchten Sie Ihre eigene LLM-App erstellen? Lesen Sie diesen [Beitrag](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)! Beginnen Sie mit [dieser Vorlage](https://github.com/The-Pocket/PocketFlow-Template-Python)!"
  },
  {
    "path": "cookbook/pocketflow-batch/translations/README_JAPANESE.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100行のミニマリストLLMフレームワーク\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\nEnglish | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flowは[たった100行](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)のミニマリストLLMフレームワークです\n\n- **軽量**: わずか100行。余分なものなし、依存関係なし、ベンダーロックインなし。\n  \n- **表現力豊か**: あなたが愛するすべてのもの—([マルチ](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[エージェント](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html)、[ワークフロー](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html)、[RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html)など。\n\n- **[エージェンティックコーディング](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: AIエージェント（例：Cursor AI）にエージェントを構築させる—生産性が10倍向上！\n\nPocket Flowを始めるには：\n- インストールするには、```pip install pocketflow```または[ソースコード](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)（わずか100行）をコピーするだけです。\n- 詳細については、[ドキュメント](https://the-pocket.github.io/PocketFlow/)をご覧ください。開発の動機については、[ストーリー](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just)をお読みください。\n- 質問がありますか？この[AIアシスタント](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant)をチェックするか、[問題を作成してください！](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 [Discord](https://discord.gg/hUHHE9Sa6T)に参加して、Pocket Flowで開発している他の開発者とつながりましょう！\n- 🎉 Pocket Flowは最初はPythonですが、現在は[Typescript](https://github.com/The-Pocket/PocketFlow-Typescript)、[Java](https://github.com/The-Pocket/PocketFlow-Java)、[C++](https://github.com/The-Pocket/PocketFlow-CPP)、[Go](https://github.com/The-Pocket/PocketFlow-Go)バージョンもあります！\n\n## なぜPocket Flow？\n\n現在のLLMフレームワークは膨大すぎます... LLMフレームワークには100行だけで十分です！\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **抽象化**          | **アプリ固有のラッパー**                                      | **ベンダー固有のラッパー**                                    | **行数**       | **サイズ**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | エージェント、チェーン               | 多数 <br><sup><sub>(例：QA、要約)</sub></sup>              | 多数 <br><sup><sub>(例：OpenAI、Pineconeなど)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | エージェント、チェーン            | 多数 <br><sup><sub>(例：FileReadTool、SerperDevTool)</sub></sup>         | 多数 <br><sup><sub>(例：OpenAI、Anthropic、Pineconeなど)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | エージェント                      | 一部 <br><sup><sub>(例：CodeAgent、VisitWebTool)</sub></sup>         | 一部 <br><sup><sub>(例：DuckDuckGo、Hugging Faceなど)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | エージェント、グラフ           | 一部 <br><sup><sub>(例：セマンティック検索)</sub></sup>                     | 一部 <br><sup><sub>(例：PostgresStore、SqliteSaverなど) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | エージェント                | 一部 <br><sup><sub>(例：ツールエージェント、チャットエージェント)</sub></sup>              | 多数 <sup><sub>[オプション]<br> (例：OpenAI、Pineconeなど)</sub></sup>        | 7K <br><sup><sub>(コアのみ)</sub></sup>    | +26MB <br><sup><sub>(コアのみ)</sub></sup>          |\n| **PocketFlow** | **グラフ**                    | **なし**                                                 | **なし**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Pocket Flowはどのように機能するのか？\n\n[100行](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)がLLMフレームワークの中核的抽象化を捉えています：グラフ！\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nそこから、([マルチ](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[エージェント](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html)、[ワークフロー](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html)、[RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html)などの人気のあるデザインパターンを簡単に実装できます。\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ 以下は基本的なチュートリアルです：\n\n<div align=\"center\">\n  \n|  名前  | 難易度    |  説明  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [チャット](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *超簡単*   | 会話履歴を持つ基本的なチャットボット |\n| [構造化出力](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *超簡単* | プロンプトを使って履歴書から構造化データを抽出する |\n| [ワークフロー](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *超簡単*   | アウトライン作成、コンテンツ作成、スタイル適用を行うライティングワークフロー |\n| [エージェント](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *超簡単*   | ウェブを検索して質問に答えることができる調査エージェント |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *超簡単*   | シンプルな検索拡張生成プロセス |\n| [バッチ処理](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *超簡単* | マークダウンコンテンツを複数の言語に翻訳するバッチプロセッサ |\n| [ストリーミング](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *超簡単*   | ユーザー割り込み機能を備えたリアルタイムLLMストリーミングデモ |\n| [チャットガードレール](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *超簡単*  | 旅行関連のクエリのみを処理する旅行アドバイザーチャットボット |\n| [マップリデュース](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *初級* | マップリデュースパターンを使用したバッチ評価の履歴書資格処理プログラム |\n| [マルチエージェント](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *初級* | 2つのエージェント間の非同期通信のためのタブーワードゲーム |\n| [スーパーバイザー](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *初級* | 調査エージェントが信頼性を失っています... 監視プロセスを構築しましょう |\n| [並列処理](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *初級*   | 3倍の高速化を示す並列実行デモ |\n| [並列フロー](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *初級*   | 複数のフィルターによる8倍の高速化を示す並列画像処理デモ |\n| [多数決](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *初級* | 複数の解決策を集約して推論の精度を向上させる |\n| [思考](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *初級*   | 思考の連鎖を通じて複雑な推論問題を解決する |\n| [メモリ](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *初級* | 短期記憶と長期記憶を持つチャットボット |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *初級* | 自動デバッグループを備えた自然言語からSQLクエリへの変換 |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *初級* | 数値演算のためのモデルコンテキストプロトコルを使用するエージェント |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *初級* | エージェント間通信のためのエージェント間プロトコルでラップされたエージェント |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *初級* | SSE更新を備えた人間レビューループのためのミニマルなウェブサービス |\n\n</div>\n\n👀 他の超初心者向けチュートリアルを見たいですか？[問題を作成してください！](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## Pocket Flowの使い方\n\n🚀 **エージェンティックコーディング**を通じて—*人間が設計し*、*エージェントがコーディングする*最速のLLMアプリ開発パラダイム！\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"イメージ代替テキスト\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ 以下はより複雑なLLMアプリの例です：\n\n<div align=\"center\">\n  \n|  アプリ名     |  難易度    | トピック  | 人間の設計 | エージェントのコード |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [CursorでCursorを構築する](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>もうすぐシンギュラリティに達します...</sup></sub> | ★★★ <br> *上級*   | [エージェント](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [設計ドキュメント](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [フローコード](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [コードベース知識ビルダー](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>他人のコードを混乱して見つめるほど人生は短くない</sup></sub> |  ★★☆ <br> *中級* | [ワークフロー](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [設計ドキュメント](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [フローコード](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [AI Paul Grahamに質問する](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>採用されない場合に備えて、AI Paul Grahamに質問しましょう</sup></sub> | ★★☆ <br> *中級*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [マップリデュース](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [設計ドキュメント](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [フローコード](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Youtubeサマライザー](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> 5歳児にもわかるようにYouTube動画を説明 </sup></sub> | ★☆☆ <br> *初級*   | [マップリデュース](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [設計ドキュメント](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [フローコード](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [コールドオープナージェネレーター](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> 冷たいリードを熱くする即席アイスブレイカー </sup></sub> | ★☆☆ <br> *初級*   | [マップリデュース](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [ウェブ検索](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [設計ドキュメント](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [フローコード](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- **エージェンティックコーディング**を学びたいですか？\n\n  - 上記のアプリの作り方に関するビデオチュートリアルについては、[私のYouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1)をチェックしてください！\n\n  - 自分のLLMアプリを構築したいですか？この[投稿](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)を読んでください！[このテンプレート](https://github.com/The-Pocket/PocketFlow-Template-Python)から始めましょう！"
  },
  {
    "path": "cookbook/pocketflow-batch/translations/README_KOREAN.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-line minimalist LLM framework\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | 한국어\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow는 [100줄](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)의 미니멀리스트 LLM 프레임워크입니다\n\n- **경량화**: 단 100줄. 불필요한 요소 없음, 의존성 없음, 벤더 종속성 없음.\n  \n- **표현력**: 여러분이 좋아하는 모든 것—([멀티-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[에이전트](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [워크플로우](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) 등.\n\n- **[에이전트 코딩](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: AI 에이전트(예: Cursor AI)가 에이전트를 구축하도록 하세요—생산성 10배 향상!\n\nPocket Flow 시작하기:\n- 설치하려면 ```pip install pocketflow```나 [소스 코드](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)(단 100줄)를 복사하세요.\n- 더 알아보려면 [문서](https://the-pocket.github.io/PocketFlow/)를 확인하세요. 개발 동기에 대해 알고 싶다면 [이야기](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just)를 읽어보세요.\n- 질문이 있으신가요? [AI 어시스턴트](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant)를 확인하거나, [이슈를 생성하세요!](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 Pocket Flow로 개발하는 다른 개발자들과 소통하려면 [Discord](https://discord.gg/hUHHE9Sa6T)에 가입하세요!\n- 🎉 Pocket Flow는 처음에 Python으로 개발되었지만, 이제 [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP) 및 [Go](https://github.com/The-Pocket/PocketFlow-Go) 버전도 있습니다!\n\n## 왜 Pocket Flow인가?\n\n현재 LLM 프레임워크들은 너무 비대합니다... LLM 프레임워크는 단 100줄이면 충분합니다!\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **추상화**          | **앱 특화 래퍼**                                      | **벤더 특화 래퍼**                                    | **코드 줄**       | **크기**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agent, Chain               | 많음 <br><sup><sub>(예: QA, 요약)</sub></sup>              | 많음 <br><sup><sub>(예: OpenAI, Pinecone 등)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agent, Chain            | 많음 <br><sup><sub>(예: FileReadTool, SerperDevTool)</sub></sup>         | 많음 <br><sup><sub>(예: OpenAI, Anthropic, Pinecone 등)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agent                      | 일부 <br><sup><sub>(예: CodeAgent, VisitWebTool)</sub></sup>         | 일부 <br><sup><sub>(예: DuckDuckGo, Hugging Face 등)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agent, Graph           | 일부 <br><sup><sub>(예: Semantic Search)</sub></sup>                     | 일부 <br><sup><sub>(예: PostgresStore, SqliteSaver 등) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agent                | 일부 <br><sup><sub>(예: Tool Agent, Chat Agent)</sub></sup>              | 많음 <sup><sub>[선택적]<br> (예: OpenAI, Pinecone 등)</sub></sup>        | 7K <br><sup><sub>(핵심만)</sub></sup>    | +26MB <br><sup><sub>(핵심만)</sub></sup>          |\n| **PocketFlow** | **Graph**                    | **없음**                                                 | **없음**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Pocket Flow는 어떻게 작동하나요?\n\n[100줄](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)의 코드는 LLM 프레임워크의 핵심 추상화인 그래프를 구현합니다!\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\n이를 기반으로 ([멀티-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[에이전트](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [워크플로우](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) 등의 인기 있는 디자인 패턴을 쉽게 구현할 수 있습니다.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ 아래는 기본 튜토리얼입니다:\n\n<div align=\"center\">\n  \n|  이름  | 난이도    |  설명  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [채팅](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *초보*   | 대화 기록을 가진 기본 채팅봇 |\n| [구조화된 출력](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *초보* | 프롬프트를 통해 이력서에서 구조화된 데이터 추출 |\n| [워크플로우](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *초보*   | 개요 작성, 내용 작성, 스타일 적용이 포함된 작성 워크플로우 |\n| [에이전트](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *초보*   | 웹을 검색하고 질문에 답할 수 있는 연구 에이전트 |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *초보*   | 간단한 검색 증강 생성 프로세스 |\n| [배치](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *초보* | 마크다운 콘텐츠를 여러 언어로 번역하는 배치 프로세서 |\n| [스트리밍](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *초보*   | 사용자 중단 기능이 있는 실시간 LLM 스트리밍 데모 |\n| [채팅 가드레일](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *초보*  | 여행 관련 쿼리만 처리하는 여행 상담 채팅봇 |\n| [맵-리듀스](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *초급* | 배치 평가를 위한 맵-리듀스 패턴을 사용하는 이력서 자격 처리기 |\n| [멀티-에이전트](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *초급* | 두 에이전트 간의 비동기 통신을 위한 금지어 게임 |\n| [감독자](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *초급* | 연구 에이전트가 불안정할 때... 감독 프로세스를 구축해 봅시다 |\n| [병렬](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *초급*   | 3배 속도 향상을 보여주는 병렬 실행 데모 |\n| [병렬 플로우](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *초급*   | 여러 필터를 사용한 8배 속도 향상을 보여주는 병렬 이미지 처리 데모 |\n| [다수결 투표](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *초급* | 여러 솔루션 시도를 집계하여 추론 정확도 향상 |\n| [사고](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *초급*   | Chain-of-Thought를 통한 복잡한 추론 문제 해결 |\n| [메모리](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *초급* | 단기 및 장기 메모리가 있는 채팅봇 |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *초급* | 자동 디버그 루프가 있는 자연어에서 SQL 쿼리로 변환 |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *초급* | 수치 연산을 위한 모델 컨텍스트 프로토콜을 사용하는 에이전트 |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *초급* | 에이전트 간 통신을 위한 Agent-to-Agent 프로토콜로 래핑된 에이전트 |\n| [웹 HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *초급* | SSE 업데이트가 있는 인간 검토 루프를 위한 최소한의 웹 서비스 |\n\n</div>\n\n👀 더 많은 초보자용 튜토리얼을 보고 싶으신가요? [이슈를 생성하세요!](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## Pocket Flow를 어떻게 사용하나요?\n\n🚀 **에이전트 코딩**을 통해—가장 빠른 LLM 앱 개발 패러다임으로, *인간이 설계*하고 *에이전트가 코딩*합니다!\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ 아래는 더 복잡한 LLM 앱의 예시입니다:\n\n<div align=\"center\">\n  \n|  앱 이름     |  난이도    | 주제  | 인간 설계 | 에이전트 코드 |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Cursor로 Cursor 만들기](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>곧 기술적 특이점에 도달할 것입니다...</sup></sub> | ★★★ <br> *고급*   | [에이전트](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [설계 문서](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [플로우 코드](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [코드베이스 지식 빌더](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>인생은 다른 사람의 코드를 혼란스럽게 바라볼 만큼 길지 않습니다</sup></sub> |  ★★☆ <br> *중급* | [워크플로우](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [설계 문서](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [플로우 코드](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [AI Paul Graham에게 물어보기](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>합격하지 못한 경우를 대비해 AI Paul Graham에게 물어보세요</sup></sub> | ★★☆ <br> *중급*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [맵 리듀스](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [설계 문서](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [플로우 코드](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [유튜브 요약기](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> 5살 아이에게 설명하듯 YouTube 동영상 설명 </sup></sub> | ★☆☆ <br> *초급*   | [맵 리듀스](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [설계 문서](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [플로우 코드](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [콜드 오프너 생성기](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> 차가운 잠재 고객을 뜨겁게 만드는 즉각적인 아이스브레이커 </sup></sub> | ★☆☆ <br> *초급*   | [맵 리듀스](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [웹 검색](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [설계 문서](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [플로우 코드](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- **에이전트 코딩**을 배우고 싶으신가요?\n\n  - 위에 소개된 앱들이 어떻게 만들어졌는지 비디오 튜토리얼을 보려면 [제 YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1)를 확인하세요!\n\n  - 자신만의 LLM 앱을 만들고 싶으신가요? 이 [포스트](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)를 읽어보세요! [이 템플릿](https://github.com/The-Pocket/PocketFlow-Template-Python)으로 시작하세요!"
  },
  {
    "path": "cookbook/pocketflow-batch/translations/README_PORTUGUESE.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-line minimalist LLM framework\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | Português | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow é um framework minimalista para LLM com [apenas 100 linhas](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)\n\n- **Leve**: Apenas 100 linhas. Zero inchaço, zero dependências, zero aprisionamento a fornecedores.\n  \n- **Expressivo**: Tudo o que você adora—([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agentes](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Fluxo de Trabalho](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), e mais.\n\n- **[Codificação Agêntica](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: Deixe que Agentes de IA (ex: Cursor AI) construam Agentes—aumento de produtividade de 10x!\n\nComece com o Pocket Flow:\n- Para instalar, ```pip install pocketflow``` ou apenas copie o [código-fonte](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) (apenas 100 linhas).\n- Para saber mais, consulte a [documentação](https://the-pocket.github.io/PocketFlow/). Para entender a motivação, leia a [história](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just).\n- Tem perguntas? Consulte este [Assistente de IA](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant), ou [crie uma issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 Junte-se ao nosso [Discord](https://discord.gg/hUHHE9Sa6T) para se conectar com outros desenvolvedores construindo com o Pocket Flow!\n- 🎉 O Pocket Flow é inicialmente em Python, mas agora temos versões em [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP) e [Go](https://github.com/The-Pocket/PocketFlow-Go)!\n\n## Por que Pocket Flow?\n\nOs frameworks LLM atuais são pesados... Você só precisa de 100 linhas para um Framework LLM!\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **Abstração**          | **Wrappers Específicos para Aplicações**                                      | **Wrappers Específicos para Fornecedores**                                    | **Linhas**       | **Tamanho**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agente, Cadeia               | Muitos <br><sup><sub>(ex: QA, Summarization)</sub></sup>              | Muitos <br><sup><sub>(ex: OpenAI, Pinecone, etc.)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agente, Cadeia            | Muitos <br><sup><sub>(ex: FileReadTool, SerperDevTool)</sub></sup>         | Muitos <br><sup><sub>(ex: OpenAI, Anthropic, Pinecone, etc.)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agente                      | Alguns <br><sup><sub>(ex: CodeAgent, VisitWebTool)</sub></sup>         | Alguns <br><sup><sub>(ex: DuckDuckGo, Hugging Face, etc.)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agente, Grafo           | Alguns <br><sup><sub>(ex: Semantic Search)</sub></sup>                     | Alguns <br><sup><sub>(ex: PostgresStore, SqliteSaver, etc.) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agente                | Alguns <br><sup><sub>(ex: Tool Agent, Chat Agent)</sub></sup>              | Muitos <sup><sub>[Opcional]<br> (ex: OpenAI, Pinecone, etc.)</sub></sup>        | 7K <br><sup><sub>(somente core)</sub></sup>    | +26MB <br><sup><sub>(somente core)</sub></sup>          |\n| **PocketFlow** | **Grafo**                    | **Nenhum**                                                 | **Nenhum**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Como funciona o Pocket Flow?\n\nAs [100 linhas](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) capturam a abstração central dos frameworks LLM: o Grafo!\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nA partir daí, é fácil implementar padrões de design populares como ([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agentes](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Fluxo de Trabalho](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), etc.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ Abaixo estão tutoriais básicos:\n\n<div align=\"center\">\n  \n|  Nome  | Dificuldade    |  Descrição  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *Básico*   | Um chatbot básico com histórico de conversação |\n| [Saída Estruturada](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *Básico* | Extraindo dados estruturados de currículos por prompt |\n| [Fluxo de Trabalho](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *Básico*   | Um fluxo de escrita que esboça, escreve conteúdo e aplica estilo |\n| [Agente](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Básico*   | Um agente de pesquisa que pode buscar na web e responder perguntas |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *Básico*   | Um processo simples de Geração Aumentada por Recuperação |\n| [Processamento em Lote](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *Básico* | Um processador em lote que traduz conteúdo markdown para vários idiomas |\n| [Streaming](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Básico*   | Uma demonstração de streaming LLM em tempo real com capacidade de interrupção pelo usuário |\n| [Guardrail de Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *Básico*  | Um chatbot de consultoria de viagens que processa apenas consultas relacionadas a viagens |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *Iniciante* | Um processador de qualificação de currículos usando o padrão map-reduce para avaliação em lote |\n| [Multi-Agente](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *Iniciante* | Um jogo de Tabu para comunicação assíncrona entre dois agentes |\n| [Supervisor](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Iniciante* | O agente de pesquisa está ficando pouco confiável... Vamos criar um processo de supervisão |\n| [Paralelo](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Iniciante*   | Uma demonstração de execução paralela que mostra um aumento de velocidade de 3x |\n| [Fluxo Paralelo](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *Iniciante*   | Uma demonstração de processamento de imagem paralelo mostrando um aumento de velocidade de 8x com múltiplos filtros |\n| [Voto Majoritário](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *Iniciante* | Melhore a precisão de raciocínio agregando múltiplas tentativas de solução |\n| [Pensamento](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Iniciante*   | Resolva problemas complexos de raciocínio através de Cadeia-de-Pensamento |\n| [Memória](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Iniciante* | Um chatbot com memória de curto e longo prazo |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *Iniciante* | Converta linguagem natural para consultas SQL com um loop de autodepuração |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *Iniciante* | Agente usando o Protocolo de Contexto de Modelo para operações numéricas |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *Iniciante* | Agente envolvido com o protocolo Agente-para-Agente para comunicação entre agentes |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *Iniciante* | Um serviço web mínimo para um loop de revisão humana com atualizações SSE |\n\n</div>\n\n👀 Quer ver outros tutoriais para iniciantes? [Crie uma issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## Como usar o Pocket Flow?\n\n🚀 Através da **Codificação Agêntica**—o paradigma mais rápido de desenvolvimento de aplicativos LLM—onde *humanos projetam* e *agentes codificam*!\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ Abaixo estão exemplos de aplicativos LLM mais complexos:\n\n<div align=\"center\">\n  \n|  Nome do Aplicativo     |  Dificuldade    | Tópicos  | Design Humano | Código do Agente |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Construir Cursor com Cursor](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>Logo chegaremos à singularidade ...</sup></sub> | ★★★ <br> *Avançado*   | [Agente](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [Doc de Design](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Código de Fluxo](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [Construtor de Conhecimento de Base de Código](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>A vida é curta demais para ficar olhando o código dos outros em confusão</sup></sub> |  ★★☆ <br> *Médio* | [Fluxo de Trabalho](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [Doc de Design](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Código de Fluxo](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [Pergunte à IA Paul Graham](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>Pergunte à IA Paul Graham, caso você não consiga entrar</sup></sub> | ★★☆ <br> *Médio*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [Doc de Design](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Código de Fluxo](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Resumidor de Youtube](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> Explica vídeos do YouTube para você como se você tivesse 5 anos </sup></sub> | ★☆☆ <br> *Iniciante*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [Doc de Design](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Código de Fluxo](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [Gerador de Abertura a Frio](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> Quebra-gelos instantâneos que transformam leads frios em quentes </sup></sub> | ★☆☆ <br> *Iniciante*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Busca Web](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [Doc de Design](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Código de Fluxo](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- Quer aprender **Codificação Agêntica**?\n\n  - Confira [meu YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1) para tutoriais em vídeo sobre como alguns dos aplicativos acima são feitos!\n\n  - Quer construir seu próprio aplicativo LLM? Leia este [post](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)! Comece com [este modelo](https://github.com/The-Pocket/PocketFlow-Template-Python)!"
  },
  {
    "path": "cookbook/pocketflow-batch/translations/README_RUSSIAN.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-line minimalist LLM framework\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | Русский| [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow — это минималистичный фреймворк для LLM всего в [100 строк](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)\n\n- **Легкий**: Всего 100 строк. Никакого лишнего веса, никаких зависимостей, никакой привязки к вендорам.\n  \n- **Выразительный**: Всё, что вы любите — ([Мульти-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Агенты](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Рабочие процессы](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) и многое другое.\n\n- **[Агентское кодирование](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: Позвольте ИИ-агентам (например, Cursor AI) создавать других агентов — повысьте продуктивность в 10 раз!\n\nНачало работы с Pocket Flow:\n- Для установки, ```pip install pocketflow``` или просто скопируйте [исходный код](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) (всего 100 строк).\n- Чтобы узнать больше, ознакомьтесь с [документацией](https://the-pocket.github.io/PocketFlow/). Чтобы понять мотивацию, прочитайте [историю](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just).\n- Есть вопросы? Спросите этого [ИИ-ассистента](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant) или [создайте issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 Присоединяйтесь к нашему [Discord](https://discord.gg/hUHHE9Sa6T), чтобы общаться с другими разработчиками, использующими Pocket Flow!\n- 🎉 Pocket Flow изначально написан на Python, но теперь у нас есть версии на [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP) и [Go](https://github.com/The-Pocket/PocketFlow-Go)!\n\n## Почему Pocket Flow?\n\nСовременные фреймворки для LLM слишком громоздкие... Для фреймворка LLM достаточно всего 100 строк!\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **Абстракция**          | **Обертки для конкретных приложений**                                      | **Обертки для конкретных вендоров**                                    | **Строк**       | **Размер**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agent, Chain               | Много <br><sup><sub>(напр., QA, Суммаризация)</sub></sup>              | Много <br><sup><sub>(напр., OpenAI, Pinecone и т.д.)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agent, Chain            | Много <br><sup><sub>(напр., FileReadTool, SerperDevTool)</sub></sup>         | Много <br><sup><sub>(напр., OpenAI, Anthropic, Pinecone и т.д.)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agent                      | Несколько <br><sup><sub>(напр., CodeAgent, VisitWebTool)</sub></sup>         | Несколько <br><sup><sub>(напр., DuckDuckGo, Hugging Face и т.д.)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agent, Graph           | Несколько <br><sup><sub>(напр., Semantic Search)</sub></sup>                     | Несколько <br><sup><sub>(напр., PostgresStore, SqliteSaver и т.д.) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agent                | Несколько <br><sup><sub>(напр., Tool Agent, Chat Agent)</sub></sup>              | Много <sup><sub>[Опционально]<br> (напр., OpenAI, Pinecone и т.д.)</sub></sup>        | 7K <br><sup><sub>(только ядро)</sub></sup>    | +26MB <br><sup><sub>(только ядро)</sub></sup>          |\n| **PocketFlow** | **Graph**                    | **Нет**                                                 | **Нет**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Как работает Pocket Flow?\n\n[100 строк](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) охватывают ключевую абстракцию фреймворков LLM: Граф!\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nОтсюда легко реализовать популярные шаблоны проектирования, такие как ([Мульти-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Агенты](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Рабочие процессы](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) и другие.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ Ниже приведены базовые руководства:\n\n<div align=\"center\">\n  \n|  Название  | Сложность    |  Описание  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [Чат](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *Простейший*   | Базовый чат-бот с историей разговора |\n| [Структурированный вывод](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *Простейший* | Извлечение структурированных данных из резюме с помощью промптов |\n| [Рабочий процесс](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *Простейший*   | Процесс написания, который создает план, пишет контент и применяет стили |\n| [Агент](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Простейший*   | Исследовательский агент, который может искать в интернете и отвечать на вопросы |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *Простейший*   | Простой процесс генерации с извлечением информации |\n| [Пакетная обработка](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *Простейший* | Пакетный процессор, который переводит markdown-контент на несколько языков |\n| [Потоковая передача](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Простейший*   | Демонстрация потоковой передачи LLM в реальном времени с возможностью прерывания пользователем |\n| [Ограничение чата](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *Простейший*  | Чат-бот туристического консультанта, обрабатывающий только запросы, связанные с путешествиями |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *Начальный* | Процессор квалификации резюме, использующий паттерн map-reduce для пакетной оценки |\n| [Мульти-агент](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *Начальный* | Игра Табу для асинхронного общения между двумя агентами |\n| [Супервизор](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Начальный* | Исследовательский агент становится ненадежным... Давайте создадим процесс надзора|\n| [Параллельное выполнение](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Начальный*   | Демонстрация параллельного выполнения, показывающая 3-кратное ускорение |\n| [Параллельный поток](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *Начальный*   | Демонстрация параллельной обработки изображений, показывающая 8-кратное ускорение с несколькими фильтрами |\n| [Голосование большинством](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *Начальный* | Повышение точности рассуждений путем агрегации нескольких попыток решения |\n| [Мышление](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Начальный*   | Решение сложных задач рассуждения с помощью цепочки размышлений |\n| [Память](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Начальный* | Чат-бот с кратковременной и долговременной памятью |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *Начальный* | Преобразование естественного языка в SQL-запросы с автоматическим циклом отладки |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *Начальный* | Агент, использующий протокол контекста модели для числовых операций |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *Начальный* | Агент, обернутый протоколом агент-к-агенту для межагентного взаимодействия |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *Начальный* | Минимальный веб-сервис для цикла проверки человеком с обновлениями SSE |\n\n</div>\n\n👀 Хотите увидеть другие руководства для начинающих? [Создайте issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## Как использовать Pocket Flow?\n\n🚀 Через **Агентское кодирование** — самую быструю парадигму разработки LLM-приложений, где *люди проектируют*, а *агенты кодируют*!\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ Ниже приведены примеры более сложных LLM-приложений:\n\n<div align=\"center\">\n  \n|  Название приложения     |  Сложность    | Темы  | Дизайн от человека | Код от агента |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Создание Cursor с помощью Cursor](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>Скоро достигнем сингулярности ...</sup></sub> | ★★★ <br> *Продвинутый*   | [Агент](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [Документация по дизайну](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Код потока](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [Конструктор знаний о кодовой базе](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>Жизнь слишком коротка, чтобы в растерянности смотреть на чужой код</sup></sub> |  ★★☆ <br> *Средний* | [Рабочий процесс](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [Документация по дизайну](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Код потока](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [Спроси ИИ Пола Грэма](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>Спроси ИИ Пола Грэма, если тебя не приняли</sup></sub> | ★★☆ <br> *Средний*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [Документация по дизайну](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Код потока](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Суммаризатор YouTube](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> Объясняет YouTube-видео как для 5-летнего </sup></sub> | ★☆☆ <br> *Начальный*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [Документация по дизайну](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Код потока](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [Генератор холодных открытий](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> Мгновенные ледоколы, превращающие холодных лидов в горячих </sup></sub> | ★☆☆ <br> *Начальный*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Веб-поиск](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [Документация по дизайну](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Код потока](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- Хотите изучить **Агентское кодирование**?\n\n  - Посмотрите [мой YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1) для видеоуроков о том, как создаются некоторые из вышеперечисленных приложений!\n\n  - Хотите создать свое собственное LLM-приложение? Прочитайте эту [статью](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)! Начните с [этого шаблона](https://github.com/The-Pocket/PocketFlow-Template-Python)!"
  },
  {
    "path": "cookbook/pocketflow-batch/translations/README_SPANISH.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-line minimalist LLM framework\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | Español | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow es un framework minimalista de LLM de [100 líneas](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)\n\n- **Ligero**: Solo 100 líneas. Cero hinchazón, cero dependencias, cero vinculación a proveedores.\n  \n- **Expresivo**: Todo lo que amas—([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agentes](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Flujo de Trabajo](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), y más.\n\n- **[Programación mediante Agentes](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: Permite que los Agentes de IA (por ejemplo, Cursor AI) construyan Agentes—¡multiplicando la productividad por 10!\n\nComienza con Pocket Flow:\n- Para instalar, ```pip install pocketflow``` o simplemente copia el [código fuente](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) (solo 100 líneas).\n- Para aprender más, consulta la [documentación](https://the-pocket.github.io/PocketFlow/). Para conocer la motivación, lee la [historia](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just).\n- ¿Tienes preguntas? Consulta este [Asistente de IA](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant), o [¡crea un issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 ¡Únete a nuestro [Discord](https://discord.gg/hUHHE9Sa6T) para conectar con otros desarrolladores construyendo con Pocket Flow!\n- 🎉 Pocket Flow inicialmente está en Python, ¡pero ahora tenemos versiones en [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP) y [Go](https://github.com/The-Pocket/PocketFlow-Go)!\n\n## ¿Por qué Pocket Flow?\n\nLos frameworks actuales de LLM están sobrecargados... ¡Solo necesitas 100 líneas para un framework de LLM!\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **Abstracción**          | **Envolturas Específicas de Aplicación**                                      | **Envolturas Específicas de Proveedor**                                    | **Líneas**       | **Tamaño**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agente, Cadena               | Muchas <br><sup><sub>(p.ej., QA, Resumen)</sub></sup>              | Muchas <br><sup><sub>(p.ej., OpenAI, Pinecone, etc.)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agente, Cadena            | Muchas <br><sup><sub>(p.ej., FileReadTool, SerperDevTool)</sub></sup>         | Muchas <br><sup><sub>(p.ej., OpenAI, Anthropic, Pinecone, etc.)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agente                      | Algunas <br><sup><sub>(p.ej., CodeAgent, VisitWebTool)</sub></sup>         | Algunas <br><sup><sub>(p.ej., DuckDuckGo, Hugging Face, etc.)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agente, Grafo           | Algunas <br><sup><sub>(p.ej., Búsqueda Semántica)</sub></sup>                     | Algunas <br><sup><sub>(p.ej., PostgresStore, SqliteSaver, etc.) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agente                | Algunas <br><sup><sub>(p.ej., Tool Agent, Chat Agent)</sub></sup>              | Muchas <sup><sub>[Opcional]<br> (p.ej., OpenAI, Pinecone, etc.)</sub></sup>        | 7K <br><sup><sub>(solo-núcleo)</sub></sup>    | +26MB <br><sup><sub>(solo-núcleo)</sub></sup>          |\n| **PocketFlow** | **Grafo**                    | **Ninguna**                                                 | **Ninguna**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## ¿Cómo funciona Pocket Flow?\n\nLas [100 líneas](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) capturan la abstracción principal de los frameworks de LLM: ¡el Grafo!\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nA partir de ahí, es fácil implementar patrones de diseño populares como ([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agentes](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Flujo de Trabajo](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), etc.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ A continuación se presentan tutoriales básicos:\n\n<div align=\"center\">\n  \n|  Nombre  | Dificultad    |  Descripción  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *Principiante*   | Un chatbot básico con historial de conversación |\n| [Salida Estructurada](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *Principiante* | Extracción de datos estructurados de currículums mediante prompts |\n| [Flujo de Trabajo](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *Principiante*   | Un flujo de escritura que esquematiza, escribe contenido y aplica estilo |\n| [Agente](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Principiante*   | Un agente de investigación que puede buscar en la web y responder preguntas |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *Principiante*   | Un simple proceso de Generación aumentada por Recuperación |\n| [Procesamiento por Lotes](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *Principiante* | Un procesador por lotes que traduce contenido markdown a múltiples idiomas |\n| [Streaming](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Principiante*   | Una demostración de streaming LLM en tiempo real con capacidad de interrupción del usuario |\n| [Protección de Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *Principiante*  | Un chatbot asesor de viajes que solo procesa consultas relacionadas con viajes |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *Inicial* | Un procesador de calificación de currículums que utiliza el patrón map-reduce para evaluación por lotes |\n| [Multi-Agente](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *Inicial* | Un juego de palabras Tabú para comunicación asíncrona entre dos agentes |\n| [Supervisor](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Inicial* | El agente de investigación se vuelve poco fiable... Construyamos un proceso de supervisión|\n| [Paralelo](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Inicial*   | Una demostración de ejecución paralela que muestra una aceleración de 3x |\n| [Flujo Paralelo](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *Inicial*   | Una demostración de procesamiento de imágenes en paralelo que muestra una aceleración de 8x con múltiples filtros |\n| [Voto por Mayoría](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *Inicial* | Mejora de la precisión del razonamiento mediante la agregación de múltiples intentos de solución |\n| [Pensamiento](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Inicial*   | Resolver problemas de razonamiento complejos a través de Cadena de Pensamiento |\n| [Memoria](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Inicial* | Un chatbot con memoria a corto y largo plazo |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *Inicial* | Convertir lenguaje natural a consultas SQL con un bucle de auto-depuración |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *Inicial* | Agente que utiliza el Protocolo de Contexto de Modelo para operaciones numéricas |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *Inicial* | Agente envuelto con protocolo Agente-a-Agente para comunicación entre agentes |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *Inicial* | Un servicio web mínimo para un bucle de revisión humana con actualizaciones SSE |\n\n</div>\n\n👀 ¿Quieres ver otros tutoriales para principiantes? [¡Crea un issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## ¿Cómo usar Pocket Flow?\n\n🚀 A través de la **Programación mediante Agentes**—el paradigma de desarrollo de aplicaciones LLM más rápido- donde *los humanos diseñan* y *los agentes programan*!\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ A continuación hay ejemplos de aplicaciones LLM más complejas:\n\n<div align=\"center\">\n  \n|  Nombre de la App     |  Dificultad    | Temas  | Diseño Humano | Código del Agente |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Construir Cursor con Cursor](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>Pronto alcanzaremos la singularidad ...</sup></sub> | ★★★ <br> *Avanzado*   | [Agente](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [Doc de Diseño](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Código de Flujo](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [Constructor de Conocimiento de Código Base](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>La vida es demasiado corta para mirar el código de otros con confusión</sup></sub> |  ★★☆ <br> *Medio* | [Flujo de Trabajo](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [Doc de Diseño](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Código de Flujo](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [Pregunta a AI Paul Graham](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>Pregunta a AI Paul Graham, en caso de que no entres</sup></sub> | ★★☆ <br> *Medio*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [Doc de Diseño](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Código de Flujo](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Resumidor de Youtube](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> Explica videos de YouTube como si tuvieras 5 años </sup></sub> | ★☆☆ <br> *Principiante*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [Doc de Diseño](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Código de Flujo](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [Generador de Introducción para Email Frío](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> Rompehielos instantáneos que convierten leads fríos en calientes </sup></sub> | ★☆☆ <br> *Principiante*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Búsqueda Web](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [Doc de Diseño](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Código de Flujo](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- ¿Quieres aprender **Programación mediante Agentes**?\n\n  - ¡Consulta [mi YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1) para ver tutoriales en video sobre cómo se crearon algunas de las aplicaciones anteriores!\n\n  - ¿Quieres construir tu propia aplicación LLM? ¡Lee este [post](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)! ¡Comienza con [esta plantilla](https://github.com/The-Pocket/PocketFlow-Template-Python)!"
  },
  {
    "path": "cookbook/pocketflow-batch/utils.py",
    "content": "from anthropic import Anthropic\nimport os\n\ndef call_llm(prompt):\n    client = Anthropic(api_key=os.environ.get(\"ANTHROPIC_API_KEY\", \"your-api-key\"))\n    response = client.messages.create(\n        model=\"claude-3-7-sonnet-20250219\",\n        max_tokens=20000,\n        thinking={\n            \"type\": \"enabled\",\n            \"budget_tokens\": 16000\n        },\n        messages=[\n            {\"role\": \"user\", \"content\": prompt}\n        ]\n    )\n    return response.content[1].text\n\nif __name__ == \"__main__\":\n    print(\"## Testing call_llm\")\n    prompt = \"In a few words, what is the meaning of life?\"\n    print(f\"## Prompt: {prompt}\")\n    response = call_llm(prompt)\n    print(f\"## Response: {response}\")"
  },
  {
    "path": "cookbook/pocketflow-batch-flow/README.md",
    "content": "# PocketFlow BatchFlow Example\n\nThis example demonstrates the BatchFlow concept in PocketFlow by implementing an image processor that applies different filters to multiple images.\n\n## What this Example Demonstrates\n\n- How to use BatchFlow to run a Flow multiple times with different parameters\n- Key concepts of BatchFlow:\n  1. Creating a base Flow for single-item processing\n  2. Using BatchFlow to process multiple items with different parameters\n  3. Managing parameters across multiple Flow executions\n\n## Project Structure\n```\npocketflow-batch-flow/\n├── README.md\n├── requirements.txt\n├── images/\n│   ├── cat.jpg        # Sample image 1\n│   ├── dog.jpg        # Sample image 2\n│   └── bird.jpg       # Sample image 3\n├── main.py            # Entry point\n├── flow.py            # Flow and BatchFlow definitions\n└── nodes.py           # Node implementations for image processing\n```\n\n## How it Works\n\nThe example processes multiple images with different filters:\n\n1. **Base Flow**: Processes a single image\n   - Load image\n   - Apply filter (grayscale, blur, or sepia)\n   - Save processed image\n\n2. **BatchFlow**: Processes multiple image-filter combinations\n   - Takes a list of parameters (image + filter combinations)\n   - Runs the base Flow for each parameter set\n   - Organizes output in a structured way\n\n## Installation\n\n```bash\npip install -r requirements.txt\n```\n\n## Usage\n\n```bash\npython main.py\n```\n\n## Sample Output\n\n```\nProcessing images with filters...\n\nProcessing cat.jpg with grayscale filter...\nProcessing cat.jpg with blur filter...\nProcessing dog.jpg with sepia filter...\n...\n\nAll images processed successfully!\nCheck the 'output' directory for results.\n```\n\n## Key Concepts Illustrated\n\n1. **Parameter Management**: Shows how BatchFlow manages different parameter sets\n2. **Flow Reuse**: Demonstrates running the same Flow multiple times\n3. **Batch Processing**: Shows how to process multiple items efficiently\n4. **Real-world Application**: Provides a practical example of batch processing "
  },
  {
    "path": "cookbook/pocketflow-batch-flow/flow.py",
    "content": "from pocketflow import Flow, BatchFlow\nfrom nodes import LoadImage, ApplyFilter, SaveImage\n\ndef create_base_flow():\n    \"\"\"Create the base Flow for processing a single image.\"\"\"\n    # Create nodes\n    load = LoadImage()\n    filter_node = ApplyFilter()\n    save = SaveImage()\n    \n    # Connect nodes\n    load - \"apply_filter\" >> filter_node\n    filter_node - \"save\" >> save\n    \n    # Create and return flow\n    return Flow(start=load)\n\nclass ImageBatchFlow(BatchFlow):\n    \"\"\"BatchFlow for processing multiple images with different filters.\"\"\"\n    \n    def prep(self, shared):\n        \"\"\"Generate parameters for each image-filter combination.\"\"\"\n        # List of images to process\n        images = [\"cat.jpg\", \"dog.jpg\", \"bird.jpg\"]\n        \n        # List of filters to apply\n        filters = [\"grayscale\", \"blur\", \"sepia\"]\n        \n        # Generate all combinations\n        params = []\n        for img in images:\n            for f in filters:\n                params.append({\n                    \"input\": img,\n                    \"filter\": f\n                })\n        \n        return params\n\ndef create_flow():\n    \"\"\"Create the complete batch processing flow.\"\"\"\n    # Create base flow for single image processing\n    base_flow = create_base_flow()\n    \n    # Wrap in BatchFlow for multiple images\n    batch_flow = ImageBatchFlow(start=base_flow)\n    \n    return batch_flow "
  },
  {
    "path": "cookbook/pocketflow-batch-flow/main.py",
    "content": "import os\nfrom PIL import Image\nimport numpy as np\nfrom flow import create_flow\n\ndef main():\n    # Create and run flow\n    print(\"Processing images with filters...\")\n    \n    flow = create_flow()\n    flow.run({}) \n    \n    print(\"\\nAll images processed successfully!\")\n    print(\"Check the 'output' directory for results.\")\n\nif __name__ == \"__main__\":\n    main() "
  },
  {
    "path": "cookbook/pocketflow-batch-flow/nodes.py",
    "content": "\"\"\"Node implementations for image processing.\"\"\"\n\nimport os\nfrom PIL import Image, ImageEnhance, ImageFilter\nfrom pocketflow import Node\n\nclass LoadImage(Node):\n    \"\"\"Node that loads an image file.\"\"\"\n    \n    def prep(self, shared):\n        \"\"\"Get image path from parameters.\"\"\"\n        return os.path.join(\"images\", self.params[\"input\"])\n    \n    def exec(self, image_path):\n        \"\"\"Load the image using PIL.\"\"\"\n        return Image.open(image_path)\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store the image in shared store.\"\"\"\n        shared[\"image\"] = exec_res\n        return \"apply_filter\"\n\nclass ApplyFilter(Node):\n    \"\"\"Node that applies a filter to an image.\"\"\"\n    \n    def prep(self, shared):\n        \"\"\"Get image and filter type.\"\"\"\n        return shared[\"image\"], self.params[\"filter\"]\n    \n    def exec(self, inputs):\n        \"\"\"Apply the specified filter.\"\"\"\n        image, filter_type = inputs\n        \n        if filter_type == \"grayscale\":\n            return image.convert(\"L\")\n        elif filter_type == \"blur\":\n            return image.filter(ImageFilter.BLUR)\n        elif filter_type == \"sepia\":\n            # Sepia implementation\n            enhancer = ImageEnhance.Color(image)\n            grayscale = enhancer.enhance(0.3)\n            colorize = ImageEnhance.Brightness(grayscale)\n            return colorize.enhance(1.2)\n        else:\n            raise ValueError(f\"Unknown filter: {filter_type}\")\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store the filtered image.\"\"\"\n        shared[\"filtered_image\"] = exec_res\n        return \"save\"\n\nclass SaveImage(Node):\n    \"\"\"Node that saves the processed image.\"\"\"\n    \n    def prep(self, shared):\n        \"\"\"Get filtered image and prepare output path.\"\"\"\n        # Create output directory if it doesn't exist\n        os.makedirs(\"output\", exist_ok=True)\n        \n        # Generate output filename\n        input_name = os.path.splitext(self.params[\"input\"])[0]\n        filter_name = self.params[\"filter\"]\n        output_path = os.path.join(\"output\", f\"{input_name}_{filter_name}.jpg\")\n        \n        return shared[\"filtered_image\"], output_path\n    \n    def exec(self, inputs):\n        \"\"\"Save the image to file.\"\"\"\n        image, output_path = inputs\n        image.save(output_path, \"JPEG\")\n        return output_path\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Print success message.\"\"\"\n        print(f\"Saved filtered image to: {exec_res}\")\n        return \"default\" "
  },
  {
    "path": "cookbook/pocketflow-batch-flow/requirements.txt",
    "content": "pocketflow\nPillow>=10.0.0"
  },
  {
    "path": "cookbook/pocketflow-batch-node/README.md",
    "content": "# PocketFlow BatchNode Example\n\nThis example demonstrates the BatchNode concept in PocketFlow by implementing a CSV processor that handles large files by processing them in chunks.\n\n## What this Example Demonstrates\n\n- How to use BatchNode to process large inputs in chunks\n- The three key methods of BatchNode:\n  1. `prep`: Splits input into chunks\n  2. `exec`: Processes each chunk independently\n  3. `post`: Combines results from all chunks\n\n## Project Structure\n```\npocketflow-batch-node/\n├── README.md\n├── requirements.txt\n├── data/\n│   └── sales.csv      # Sample large CSV file\n├── main.py            # Entry point\n├── flow.py            # Flow definition\n└── nodes.py           # BatchNode implementation\n```\n\n## How it Works\n\nThe example processes a large CSV file containing sales data:\n\n1. **Chunking (prep)**: The CSV file is read and split into chunks of N rows\n2. **Processing (exec)**: Each chunk is processed to calculate:\n   - Total sales\n   - Average sale value\n   - Number of transactions\n3. **Combining (post)**: Results from all chunks are aggregated into final statistics\n\n## Installation\n\n```bash\npip install -r requirements.txt\n```\n\n## Usage\n\n```bash\npython main.py\n```\n\n## Sample Output\n\n```\nProcessing sales.csv in chunks...\n\nFinal Statistics:\n- Total Sales: $1,234,567.89\n- Average Sale: $123.45\n- Total Transactions: 10,000\n```\n\n## Key Concepts Illustrated\n\n1. **Chunk-based Processing**: Shows how BatchNode handles large inputs by breaking them into manageable pieces\n2. **Independent Processing**: Demonstrates how each chunk is processed separately\n3. **Result Aggregation**: Shows how individual results are combined into a final output "
  },
  {
    "path": "cookbook/pocketflow-batch-node/data/sales.csv",
    "content": "date,amount,product\n2024-01-01,114.9,B\n2024-01-02,95.85,A\n2024-01-03,119.43,C\n2024-01-04,145.69,A\n2024-01-05,92.98,B\n2024-01-06,92.98,C\n2024-01-07,147.38,B\n2024-01-08,123.02,A\n2024-01-09,85.92,A\n2024-01-10,116.28,B\n2024-01-11,86.1,B\n2024-01-12,86.03,C\n2024-01-13,107.26,B\n2024-01-14,42.6,B\n2024-01-15,48.25,A\n2024-01-16,83.13,A\n2024-01-17,69.62,B\n2024-01-18,109.43,B\n2024-01-19,72.76,A\n2024-01-20,57.63,C\n2024-01-21,143.97,C\n2024-01-22,93.23,C\n2024-01-23,102.03,C\n2024-01-24,57.26,A\n2024-01-25,83.67,B\n2024-01-26,103.33,C\n2024-01-27,65.47,A\n2024-01-28,111.27,B\n2024-01-29,81.98,C\n2024-01-30,91.25,B\n2024-01-31,81.95,C\n2024-02-01,155.57,B\n2024-02-02,99.6,C\n2024-02-03,68.27,C\n2024-02-04,124.68,B\n2024-02-05,63.37,B\n2024-02-06,106.27,B\n2024-02-07,41.21,A\n2024-02-08,60.15,A\n2024-02-09,105.91,B\n2024-02-10,122.15,A\n2024-02-11,105.14,C\n2024-02-12,96.53,B\n2024-02-13,90.97,A\n2024-02-14,55.64,B\n2024-02-15,78.4,C\n2024-02-16,86.18,C\n2024-02-17,131.71,A\n2024-02-18,110.31,C\n2024-02-19,47.11,B\n2024-02-20,109.72,A\n2024-02-21,88.45,B\n2024-02-22,79.69,B\n2024-02-23,118.35,B\n2024-02-24,130.93,C\n2024-02-25,127.94,B\n2024-02-26,74.82,C\n2024-02-27,90.72,B\n2024-02-28,109.94,C\n2024-02-29,129.27,C\n2024-03-01,85.62,C\n2024-03-02,94.43,B\n2024-03-03,66.81,B\n2024-03-04,64.11,C\n2024-03-05,124.38,B\n2024-03-06,140.69,C\n2024-03-07,97.84,B\n2024-03-08,130.11,C\n2024-03-09,110.85,C\n2024-03-10,80.65,A\n2024-03-11,110.84,B\n2024-03-12,146.14,B\n2024-03-13,98.93,A\n2024-03-14,146.94,C\n2024-03-15,21.41,C\n2024-03-16,124.66,A\n2024-03-17,102.61,A\n2024-03-18,91.03,C\n2024-03-19,102.75,B\n2024-03-20,40.37,A\n2024-03-21,93.41,A\n2024-03-22,110.71,A\n2024-03-23,144.34,A\n2024-03-24,84.45,A\n2024-03-25,75.75,A\n2024-03-26,84.95,C\n2024-03-27,127.46,C\n2024-03-28,109.86,A\n2024-03-29,84.11,C\n2024-03-30,115.4,B\n2024-03-31,102.91,B\n2024-04-01,129.06,A\n2024-04-02,78.94,B\n2024-04-03,90.17,B\n2024-04-04,88.24,C\n2024-04-05,56.09,C\n2024-04-06,108.88,C\n2024-04-07,107.83,C\n2024-04-08,100.15,B\n2024-04-09,92.96,A\n2024-04-10,57.54,C\n2024-04-11,87.38,B\n2024-04-12,89.72,C\n2024-04-13,75.93,B\n2024-04-14,95.16,C\n2024-04-15,112.12,A\n2024-04-16,156.59,C\n2024-04-17,105.24,A\n2024-04-18,107.73,C\n2024-04-19,97.77,C\n2024-04-20,42.44,C\n2024-04-21,99.2,B\n2024-04-22,101.81,B\n2024-04-23,173.9,A\n2024-04-24,94.23,A\n2024-04-25,109.05,B\n2024-04-26,98.96,A\n2024-04-27,64.94,A\n2024-04-28,134.28,B\n2024-04-29,122.56,B\n2024-04-30,123.73,B\n2024-05-01,72.72,B\n2024-05-02,142.08,C\n2024-05-03,57.94,B\n2024-05-04,117.61,A\n2024-05-05,165.71,C\n2024-05-06,70.28,C\n2024-05-07,83.01,A\n2024-05-08,102.99,C\n2024-05-09,84.9,A\n2024-05-10,53.48,C\n2024-05-11,102.06,C\n2024-05-12,68.13,A\n2024-05-13,114.21,A\n2024-05-14,72.42,A\n2024-05-15,146.5,A\n2024-05-16,76.5,B\n2024-05-17,90.34,B\n2024-05-18,124.41,A\n2024-05-19,63.07,B\n2024-05-20,106.82,A\n2024-05-21,139.21,A\n2024-05-22,51.78,C\n2024-05-23,105.54,A\n2024-05-24,107.8,A\n2024-05-25,123.45,A\n2024-05-26,62.89,A\n2024-05-27,60.39,C\n2024-05-28,115.66,A\n2024-05-29,108.91,C\n2024-05-30,107.51,A\n2024-05-31,110.39,C\n2024-06-01,79.6,B\n2024-06-02,106.97,C\n2024-06-03,108.79,C\n2024-06-04,78.57,A\n2024-06-05,155.97,C\n2024-06-06,114.21,C\n2024-06-07,64.26,A\n2024-06-08,119.7,B\n2024-06-09,70.76,B\n2024-06-10,123.61,B\n2024-06-11,134.76,C\n2024-06-12,75.38,A\n2024-06-13,128.9,B\n2024-06-14,112.38,B\n2024-06-15,124.66,B\n2024-06-16,156.9,B\n2024-06-17,92.64,A\n2024-06-18,77.39,B\n2024-06-19,73.31,C\n2024-06-20,75.53,C\n2024-06-21,97.69,A\n2024-06-22,110.23,B\n2024-06-23,108.3,B\n2024-06-24,124.82,C\n2024-06-25,100.39,A\n2024-06-26,143.61,C\n2024-06-27,92.06,A\n2024-06-28,181.61,A\n2024-06-29,118.77,A\n2024-06-30,74.29,C\n2024-07-01,67.87,A\n2024-07-02,114.47,C\n2024-07-03,93.3,C\n2024-07-04,121.42,B\n2024-07-05,114.2,C\n2024-07-06,97.82,C\n2024-07-07,74.6,B\n2024-07-08,54.55,B\n2024-07-09,86.6,B\n2024-07-10,125.69,A\n2024-07-11,106.42,A\n2024-07-12,62.63,B\n2024-07-13,105.2,A\n2024-07-14,111.56,B\n2024-07-15,73.48,A\n2024-07-16,104.61,C\n2024-07-17,101.75,A\n2024-07-18,65.71,A\n2024-07-19,110.73,C\n2024-07-20,116.82,B\n2024-07-21,132.49,A\n2024-07-22,131.61,A\n2024-07-23,58.67,C\n2024-07-24,71.87,C\n2024-07-25,115.45,C\n2024-07-26,115.41,C\n2024-07-27,115.45,B\n2024-07-28,215.58,A\n2024-07-29,117.13,A\n2024-07-30,134.07,A\n2024-07-31,128.62,A\n2024-08-01,119.54,B\n2024-08-02,90.54,A\n2024-08-03,122.77,B\n2024-08-04,76.82,B\n2024-08-05,92.9,C\n2024-08-06,85.44,C\n2024-08-07,102.46,A\n2024-08-08,169.44,B\n2024-08-09,43.98,C\n2024-08-10,120.59,B\n2024-08-11,51.62,C\n2024-08-12,85.84,B\n2024-08-13,132.67,A\n2024-08-14,101.93,A\n2024-08-15,67.67,B\n2024-08-16,78.54,C\n2024-08-17,120.39,C\n2024-08-18,78.09,A\n2024-08-19,106.49,A\n2024-08-20,101.37,A\n2024-08-21,80.45,C\n2024-08-22,164.32,C\n2024-08-23,119.02,A\n2024-08-24,39.25,B\n2024-08-25,105.59,C\n2024-08-26,80.15,A\n2024-08-27,125.57,B\n2024-08-28,76.22,C\n2024-08-29,96.56,B\n2024-08-30,115.15,B\n2024-08-31,125.97,C\n2024-09-01,63.99,A\n2024-09-02,89.96,B\n2024-09-03,85.75,A\n2024-09-04,80.4,C\n2024-09-05,152.96,B\n2024-09-06,112.15,B\n2024-09-07,62.17,B\n2024-09-08,127.54,B\n2024-09-09,163.66,B\n2024-09-10,130.97,C\n2024-09-11,54.42,B\n2024-09-12,85.47,C\n2024-09-13,138.01,A\n2024-09-14,78.77,C\n2024-09-15,113.31,B\n2024-09-16,123.24,A\n2024-09-17,72.19,C\n2024-09-18,98.21,C\n2024-09-19,2.76,C\n2024-09-20,69.27,B\n2024-09-21,92.42,B\n2024-09-22,62.57,C\n2024-09-23,148.97,C\n2024-09-24,57.1,A\n2024-09-25,86.8,B\n2024-09-26,103.92,B\n2024-09-27,143.24,A\n2024-09-28,56.92,C\n2024-09-29,134.89,C\n2024-09-30,100.31,A\n2024-10-01,70.55,C\n2024-10-02,113.86,C\n2024-10-03,105.97,A\n2024-10-04,81.99,A\n2024-10-05,102.09,A\n2024-10-06,88.44,C\n2024-10-07,103.41,A\n2024-10-08,119.86,C\n2024-10-09,147.58,B\n2024-10-10,62.87,B\n2024-10-11,163.99,A\n2024-10-12,41.44,C\n2024-10-13,95.45,C\n2024-10-14,117.65,C\n2024-10-15,108.43,A\n2024-10-16,81.32,C\n2024-10-17,93.76,C\n2024-10-18,85.21,C\n2024-10-19,82.32,B\n2024-10-20,125.49,C\n2024-10-21,110.71,B\n2024-10-22,79.21,C\n2024-10-23,126.99,C\n2024-10-24,109.22,C\n2024-10-25,124.39,B\n2024-10-26,118.89,C\n2024-10-27,75.13,A\n2024-10-28,83.19,C\n2024-10-29,122.42,B\n2024-10-30,118.31,B\n2024-10-31,99.37,A\n2024-11-01,103.52,A\n2024-11-02,138.33,A\n2024-11-03,82.25,B\n2024-11-04,116.41,C\n2024-11-05,93.93,C\n2024-11-06,93.47,A\n2024-11-07,132.96,A\n2024-11-08,124.76,C\n2024-11-09,124.41,A\n2024-11-10,139.16,C\n2024-11-11,100.63,C\n2024-11-12,120.46,C\n2024-11-13,90.69,C\n2024-11-14,109.72,B\n2024-11-15,96.1,C\n2024-11-16,102.91,A\n2024-11-17,117.85,A\n2024-11-18,75.45,A\n2024-11-19,162.77,A\n2024-11-20,69.82,B\n2024-11-21,63.57,C\n2024-11-22,134.74,C\n2024-11-23,123.75,A\n2024-11-24,118.72,A\n2024-11-25,118.85,A\n2024-11-26,99.63,C\n2024-11-27,73.08,B\n2024-11-28,102.27,B\n2024-11-29,79.69,B\n2024-11-30,129.25,B\n2024-12-01,95.59,A\n2024-12-02,75.24,C\n2024-12-03,90.36,A\n2024-12-04,112.39,B\n2024-12-05,83.09,A\n2024-12-06,75.33,B\n2024-12-07,107.31,C\n2024-12-08,107.35,B\n2024-12-09,84.79,A\n2024-12-10,85.87,C\n2024-12-11,106.96,B\n2024-12-12,56.56,B\n2024-12-13,57.78,C\n2024-12-14,78.45,A\n2024-12-15,93.6,C\n2024-12-16,109.33,A\n2024-12-17,144.26,B\n2024-12-18,125.73,B\n2024-12-19,95.2,C\n2024-12-20,99.43,B\n2024-12-21,69.92,C\n2024-12-22,99.44,B\n2024-12-23,91.34,B\n2024-12-24,109.68,A\n2024-12-25,75.18,C\n2024-12-26,115.58,C\n2024-12-27,145.98,B\n2024-12-28,96.74,A\n2024-12-29,112.05,A\n2024-12-30,120.7,C\n2024-12-31,87.96,C\n2025-01-01,106.72,B\n2025-01-02,100.38,C\n2025-01-03,102.93,B\n2025-01-04,76.81,A\n2025-01-05,100.74,C\n2025-01-06,114.94,A\n2025-01-07,143.53,A\n2025-01-08,128.78,B\n2025-01-09,164.6,A\n2025-01-10,76.98,B\n2025-01-11,126.17,B\n2025-01-12,105.5,A\n2025-01-13,165.69,A\n2025-01-14,75.75,B\n2025-01-15,74.81,B\n2025-01-16,82.02,A\n2025-01-17,36.28,B\n2025-01-18,84.23,B\n2025-01-19,77.23,C\n2025-01-20,104.51,C\n2025-01-21,110.25,C\n2025-01-22,156.29,C\n2025-01-23,128.51,C\n2025-01-24,82.69,B\n2025-01-25,73.05,B\n2025-01-26,114.76,C\n2025-01-27,60.39,A\n2025-01-28,154.94,C\n2025-01-29,135.38,A\n2025-01-30,85.92,B\n2025-01-31,48.61,A\n2025-02-01,140.62,B\n2025-02-02,96.56,A\n2025-02-03,137.13,B\n2025-02-04,52.17,B\n2025-02-05,82.02,C\n2025-02-06,100.16,B\n2025-02-07,101.41,C\n2025-02-08,86.5,C\n2025-02-09,118.69,C\n2025-02-10,67.97,A\n2025-02-11,95.73,A\n2025-02-12,103.61,B\n2025-02-13,115.43,C\n2025-02-14,121.35,C\n2025-02-15,66.26,B\n2025-02-16,53.98,C\n2025-02-17,138.33,B\n2025-02-18,109.97,C\n2025-02-19,77.55,A\n2025-02-20,146.53,B\n2025-02-21,103.47,B\n2025-02-22,135.38,A\n2025-02-23,102.03,B\n2025-02-24,161.82,A\n2025-02-25,152.66,C\n2025-02-26,92.53,A\n2025-02-27,129.15,A\n2025-02-28,119.36,B\n2025-03-01,141.06,C\n2025-03-02,71.05,A\n2025-03-03,120.58,C\n2025-03-04,131.75,A\n2025-03-05,47.24,B\n2025-03-06,64.5,A\n2025-03-07,38.82,A\n2025-03-08,91.92,C\n2025-03-09,121.53,A\n2025-03-10,145.07,B\n2025-03-11,102.22,B\n2025-03-12,148.86,A\n2025-03-13,58.6,A\n2025-03-14,48.9,C\n2025-03-15,98.33,C\n2025-03-16,111.52,B\n2025-03-17,99.02,C\n2025-03-18,37.98,A\n2025-03-19,97.33,B\n2025-03-20,60.87,C\n2025-03-21,120.09,B\n2025-03-22,111.0,A\n2025-03-23,71.8,C\n2025-03-24,84.58,A\n2025-03-25,68.22,A\n2025-03-26,98.12,B\n2025-03-27,128.65,B\n2025-03-28,70.43,B\n2025-03-29,115.12,C\n2025-03-30,84.09,C\n2025-03-31,76.21,B\n2025-04-01,96.79,C\n2025-04-02,68.94,C\n2025-04-03,83.39,B\n2025-04-04,64.06,A\n2025-04-05,158.94,C\n2025-04-06,101.06,C\n2025-04-07,79.01,A\n2025-04-08,106.42,A\n2025-04-09,96.63,A\n2025-04-10,93.37,B\n2025-04-11,118.43,A\n2025-04-12,122.73,B\n2025-04-13,84.08,A\n2025-04-14,82.73,A\n2025-04-15,91.75,B\n2025-04-16,30.94,B\n2025-04-17,54.54,A\n2025-04-18,141.01,A\n2025-04-19,149.35,A\n2025-04-20,92.53,C\n2025-04-21,117.3,A\n2025-04-22,109.34,B\n2025-04-23,192.37,C\n2025-04-24,133.59,B\n2025-04-25,96.16,C\n2025-04-26,71.33,C\n2025-04-27,51.81,B\n2025-04-28,106.1,C\n2025-04-29,77.31,B\n2025-04-30,57.33,A\n2025-05-01,80.6,C\n2025-05-02,67.55,B\n2025-05-03,150.61,C\n2025-05-04,126.45,B\n2025-05-05,99.76,A\n2025-05-06,144.4,A\n2025-05-07,102.32,C\n2025-05-08,74.16,C\n2025-05-09,145.69,B\n2025-05-10,116.17,B\n2025-05-11,68.88,C\n2025-05-12,94.29,B\n2025-05-13,73.73,B\n2025-05-14,58.52,B\n2025-05-15,127.79,C\n2025-05-16,157.28,A\n2025-05-17,58.04,A\n2025-05-18,116.89,B\n2025-05-19,80.48,C\n2025-05-20,85.39,B\n2025-05-21,82.23,A\n2025-05-22,74.08,B\n2025-05-23,101.46,A\n2025-05-24,75.07,C\n2025-05-25,108.11,B\n2025-05-26,98.49,C\n2025-05-27,92.83,B\n2025-05-28,72.77,B\n2025-05-29,82.7,B\n2025-05-30,122.66,B\n2025-05-31,115.03,B\n2025-06-01,70.67,C\n2025-06-02,102.98,C\n2025-06-03,122.54,A\n2025-06-04,49.92,B\n2025-06-05,116.3,A\n2025-06-06,80.12,C\n2025-06-07,117.12,A\n2025-06-08,77.1,C\n2025-06-09,45.85,B\n2025-06-10,51.17,A\n2025-06-11,101.44,A\n2025-06-12,107.79,A\n2025-06-13,72.87,A\n2025-06-14,119.16,B\n2025-06-15,50.15,C\n2025-06-16,98.02,C\n2025-06-17,63.67,B\n2025-06-18,80.44,C\n2025-06-19,101.42,A\n2025-06-20,74.19,B\n2025-06-21,88.46,C\n2025-06-22,130.19,B\n2025-06-23,82.69,B\n2025-06-24,125.07,C\n2025-06-25,66.11,C\n2025-06-26,115.89,B\n2025-06-27,143.25,B\n2025-06-28,25.85,C\n2025-06-29,76.09,B\n2025-06-30,117.31,C\n2025-07-01,93.91,B\n2025-07-02,111.13,B\n2025-07-03,81.88,B\n2025-07-04,102.6,C\n2025-07-05,95.33,A\n2025-07-06,135.03,C\n2025-07-07,107.63,B\n2025-07-08,110.13,A\n2025-07-09,87.64,A\n2025-07-10,85.37,B\n2025-07-11,87.02,C\n2025-07-12,111.83,A\n2025-07-13,87.37,A\n2025-07-14,108.69,C\n2025-07-15,162.26,C\n2025-07-16,126.13,A\n2025-07-17,90.22,C\n2025-07-18,136.04,A\n2025-07-19,87.76,B\n2025-07-20,38.86,C\n2025-07-21,69.76,A\n2025-07-22,43.88,C\n2025-07-23,89.45,C\n2025-07-24,100.55,A\n2025-07-25,150.29,B\n2025-07-26,109.81,A\n2025-07-27,93.43,C\n2025-07-28,124.88,A\n2025-07-29,33.67,C\n2025-07-30,107.07,A\n2025-07-31,123.13,B\n2025-08-01,55.64,B\n2025-08-02,134.31,B\n2025-08-03,110.15,B\n2025-08-04,87.54,A\n2025-08-05,118.98,B\n2025-08-06,168.12,A\n2025-08-07,105.46,C\n2025-08-08,107.45,C\n2025-08-09,86.22,C\n2025-08-10,74.5,B\n2025-08-11,124.91,B\n2025-08-12,74.32,B\n2025-08-13,102.15,C\n2025-08-14,85.67,C\n2025-08-15,114.37,C\n2025-08-16,110.01,C\n2025-08-17,131.13,A\n2025-08-18,84.7,A\n2025-08-19,91.9,B\n2025-08-20,70.64,B\n2025-08-21,86.67,C\n2025-08-22,111.32,A\n2025-08-23,122.71,A\n2025-08-24,72.34,B\n2025-08-25,126.09,A\n2025-08-26,140.67,B\n2025-08-27,112.4,C\n2025-08-28,156.3,C\n2025-08-29,76.79,B\n2025-08-30,62.66,C\n2025-08-31,46.64,C\n2025-09-01,144.88,A\n2025-09-02,119.63,A\n2025-09-03,98.33,A\n2025-09-04,108.4,A\n2025-09-05,66.24,A\n2025-09-06,173.37,B\n2025-09-07,103.88,B\n2025-09-08,103.28,A\n2025-09-09,121.77,C\n2025-09-10,114.43,C\n2025-09-11,106.72,B\n2025-09-12,76.29,B\n2025-09-13,114.14,B\n2025-09-14,156.46,C\n2025-09-15,140.36,B\n2025-09-16,147.8,C\n2025-09-17,84.66,B\n2025-09-18,70.31,C\n2025-09-19,96.23,B\n2025-09-20,101.67,B\n2025-09-21,132.83,C\n2025-09-22,49.23,B\n2025-09-23,145.89,A\n2025-09-24,95.26,C\n2025-09-25,87.19,B\n2025-09-26,69.64,C\n2025-09-27,50.35,B\n2025-09-28,124.7,A\n2025-09-29,102.2,B\n2025-09-30,61.3,B\n2025-10-01,61.15,A\n2025-10-02,89.93,C\n2025-10-03,150.07,B\n2025-10-04,92.21,B\n2025-10-05,54.91,A\n2025-10-06,92.63,C\n2025-10-07,91.82,C\n2025-10-08,19.09,A\n2025-10-09,98.37,A\n2025-10-10,93.07,B\n2025-10-11,120.89,A\n2025-10-12,155.47,A\n2025-10-13,133.8,A\n2025-10-14,91.93,C\n2025-10-15,66.8,A\n2025-10-16,177.2,B\n2025-10-17,101.78,B\n2025-10-18,100.42,B\n2025-10-19,99.28,A\n2025-10-20,105.94,C\n2025-10-21,95.67,A\n2025-10-22,82.79,A\n2025-10-23,83.59,B\n2025-10-24,99.02,A\n2025-10-25,83.7,C\n2025-10-26,78.61,B\n2025-10-27,103.19,C\n2025-10-28,92.35,C\n2025-10-29,145.12,C\n2025-10-30,20.47,A\n2025-10-31,132.75,A\n2025-11-01,137.38,A\n2025-11-02,37.8,A\n2025-11-03,89.72,B\n2025-11-04,88.86,A\n2025-11-05,57.77,B\n2025-11-06,76.67,C\n2025-11-07,66.68,B\n2025-11-08,152.57,A\n2025-11-09,128.07,B\n2025-11-10,138.15,A\n2025-11-11,121.65,A\n2025-11-12,66.13,B\n2025-11-13,84.26,B\n2025-11-14,114.68,C\n2025-11-15,63.34,C\n2025-11-16,121.39,C\n2025-11-17,92.79,C\n2025-11-18,88.76,C\n2025-11-19,121.33,B\n2025-11-20,113.33,C\n2025-11-21,89.17,B\n2025-11-22,134.78,C\n2025-11-23,67.57,A\n2025-11-24,118.48,C\n2025-11-25,117.79,A\n2025-11-26,90.71,B\n2025-11-27,109.78,C\n2025-11-28,62.47,B\n2025-11-29,127.72,C\n2025-11-30,94.45,B\n2025-12-01,84.32,C\n2025-12-02,131.47,C\n2025-12-03,78.87,A\n2025-12-04,57.75,B\n2025-12-05,53.3,B\n2025-12-06,118.18,B\n2025-12-07,61.59,B\n2025-12-08,152.64,B\n2025-12-09,37.54,A\n2025-12-10,150.89,C\n2025-12-11,106.33,A\n2025-12-12,97.1,B\n2025-12-13,83.65,B\n2025-12-14,111.97,C\n2025-12-15,98.87,A\n2025-12-16,133.1,C\n2025-12-17,103.43,C\n2025-12-18,104.51,A\n2025-12-19,89.09,C\n2025-12-20,98.29,A\n2025-12-21,109.23,A\n2025-12-22,48.69,B\n2025-12-23,59.55,C\n2025-12-24,122.3,B\n2025-12-25,105.13,C\n2025-12-26,94.48,C\n2025-12-27,100.55,B\n2025-12-28,110.43,B\n2025-12-29,83.81,B\n2025-12-30,76.65,A\n2025-12-31,105.88,C\n2026-01-01,70.65,A\n2026-01-02,112.25,C\n2026-01-03,48.92,C\n2026-01-04,130.87,A\n2026-01-05,114.18,A\n2026-01-06,107.68,C\n2026-01-07,129.48,B\n2026-01-08,149.96,B\n2026-01-09,130.43,B\n2026-01-10,44.77,B\n2026-01-11,61.61,C\n2026-01-12,81.26,B\n2026-01-13,100.78,B\n2026-01-14,115.53,B\n2026-01-15,78.23,C\n2026-01-16,105.6,A\n2026-01-17,77.34,B\n2026-01-18,81.65,C\n2026-01-19,57.8,C\n2026-01-20,72.3,B\n2026-01-21,59.45,A\n2026-01-22,70.72,A\n2026-01-23,131.61,A\n2026-01-24,71.52,C\n2026-01-25,178.97,B\n2026-01-26,114.8,B\n2026-01-27,105.55,C\n2026-01-28,74.25,B\n2026-01-29,121.01,B\n2026-01-30,82.73,A\n2026-01-31,103.66,B\n2026-02-01,176.8,B\n2026-02-02,97.12,C\n2026-02-03,134.48,C\n2026-02-04,78.9,A\n2026-02-05,98.95,C\n2026-02-06,153.12,A\n2026-02-07,81.19,A\n2026-02-08,154.37,B\n2026-02-09,121.23,A\n2026-02-10,83.13,B\n2026-02-11,118.97,A\n2026-02-12,129.18,C\n2026-02-13,118.65,C\n2026-02-14,52.89,C\n2026-02-15,78.19,A\n2026-02-16,92.57,A\n2026-02-17,97.77,B\n2026-02-18,118.62,B\n2026-02-19,105.33,B\n2026-02-20,59.94,B\n2026-02-21,111.41,A\n2026-02-22,118.32,A\n2026-02-23,116.79,B\n2026-02-24,132.42,A\n2026-02-25,125.02,A\n2026-02-26,113.78,A\n2026-02-27,97.9,A\n2026-02-28,50.17,B\n2026-03-01,112.89,B\n2026-03-02,106.23,A\n2026-03-03,108.15,C\n2026-03-04,61.7,B\n2026-03-05,67.57,B\n2026-03-06,131.59,C\n2026-03-07,98.81,B\n2026-03-08,120.45,C\n2026-03-09,100.85,C\n2026-03-10,100.89,A\n2026-03-11,128.15,B\n2026-03-12,84.52,C\n2026-03-13,102.88,A\n2026-03-14,86.13,B\n2026-03-15,86.97,A\n2026-03-16,90.72,A\n2026-03-17,106.66,C\n2026-03-18,85.64,B\n2026-03-19,137.67,A\n2026-03-20,73.16,C\n2026-03-21,94.39,C\n2026-03-22,86.81,A\n2026-03-23,143.41,A\n2026-03-24,105.9,C\n2026-03-25,130.96,A\n2026-03-26,55.43,A\n2026-03-27,108.01,B\n2026-03-28,126.69,C\n2026-03-29,102.47,A\n2026-03-30,131.96,C\n2026-03-31,84.48,B\n2026-04-01,142.28,C\n2026-04-02,168.97,C\n2026-04-03,89.11,C\n2026-04-04,86.63,B\n2026-04-05,143.6,B\n2026-04-06,147.39,C\n2026-04-07,84.31,C\n2026-04-08,87.39,B\n2026-04-09,91.55,A\n2026-04-10,59.67,B\n2026-04-11,72.44,C\n2026-04-12,69.88,A\n2026-04-13,76.97,A\n2026-04-14,98.96,A\n2026-04-15,107.03,B\n2026-04-16,146.52,B\n2026-04-17,70.05,B\n2026-04-18,129.53,B\n2026-04-19,93.58,C\n2026-04-20,98.52,A\n2026-04-21,120.24,C\n2026-04-22,66.32,B\n2026-04-23,111.47,B\n2026-04-24,104.99,C\n2026-04-25,114.77,A\n2026-04-26,108.68,A\n2026-04-27,173.66,B\n2026-04-28,80.87,B\n2026-04-29,84.07,B\n2026-04-30,81.31,B\n2026-05-01,83.34,A\n2026-05-02,80.88,C\n2026-05-03,135.67,A\n2026-05-04,142.62,C\n2026-05-05,82.88,B\n2026-05-06,75.03,B\n2026-05-07,114.14,C\n2026-05-08,83.43,B\n2026-05-09,118.99,A\n2026-05-10,106.09,B\n2026-05-11,54.53,B\n2026-05-12,146.43,B\n2026-05-13,153.88,B\n2026-05-14,81.62,A\n2026-05-15,88.37,C\n2026-05-16,108.58,B\n2026-05-17,110.03,B\n2026-05-18,119.76,A\n2026-05-19,160.31,A\n2026-05-20,94.69,A\n2026-05-21,76.05,B\n2026-05-22,58.62,C\n2026-05-23,78.07,A\n2026-05-24,99.01,A\n2026-05-25,153.84,A\n2026-05-26,84.47,C\n2026-05-27,106.71,B\n2026-05-28,99.51,B\n2026-05-29,135.65,B\n2026-05-30,175.81,A\n2026-05-31,84.07,B\n2026-06-01,85.32,A\n2026-06-02,131.32,C\n2026-06-03,120.46,C\n2026-06-04,155.4,B\n2026-06-05,117.52,A\n2026-06-06,89.22,A\n2026-06-07,117.72,B\n2026-06-08,133.26,C\n2026-06-09,124.61,C\n2026-06-10,115.22,C\n2026-06-11,132.0,C\n2026-06-12,135.08,C\n2026-06-13,141.46,B\n2026-06-14,119.46,B\n2026-06-15,94.99,B\n2026-06-16,104.4,C\n2026-06-17,136.2,C\n2026-06-18,75.49,B\n2026-06-19,111.06,B\n2026-06-20,88.2,C\n2026-06-21,100.86,A\n2026-06-22,138.35,B\n2026-06-23,105.73,C\n2026-06-24,101.39,C\n2026-06-25,59.2,B\n2026-06-26,122.39,A\n2026-06-27,119.36,C\n2026-06-28,164.9,B\n2026-06-29,90.77,C\n2026-06-30,106.57,C\n2026-07-01,107.48,C\n2026-07-02,147.32,C\n2026-07-03,97.14,A\n2026-07-04,108.37,B\n2026-07-05,118.24,B\n2026-07-06,105.6,B\n2026-07-07,86.61,B\n2026-07-08,105.82,B\n2026-07-09,132.21,A\n2026-07-10,69.2,C\n2026-07-11,103.99,C\n2026-07-12,79.0,B\n2026-07-13,135.85,C\n2026-07-14,54.3,C\n2026-07-15,83.23,C\n2026-07-16,111.32,B\n2026-07-17,146.97,B\n2026-07-18,98.03,A\n2026-07-19,83.34,B\n2026-07-20,156.43,A\n2026-07-21,56.56,B\n2026-07-22,34.04,C\n2026-07-23,113.2,A\n2026-07-24,84.94,A\n2026-07-25,69.36,A\n2026-07-26,121.25,B\n2026-07-27,107.31,A\n2026-07-28,83.08,B\n2026-07-29,61.59,A\n2026-07-30,126.17,B\n2026-07-31,119.51,A\n2026-08-01,97.02,C\n2026-08-02,155.4,C\n2026-08-03,67.9,A\n2026-08-04,54.23,B\n2026-08-05,79.24,B\n2026-08-06,98.63,B\n2026-08-07,107.3,C\n2026-08-08,92.76,C\n2026-08-09,110.56,C\n2026-08-10,62.45,B\n2026-08-11,143.31,B\n2026-08-12,97.54,B\n2026-08-13,133.52,A\n2026-08-14,110.28,A\n2026-08-15,113.7,A\n2026-08-16,117.09,C\n2026-08-17,113.43,C\n2026-08-18,119.28,B\n2026-08-19,139.87,A\n2026-08-20,105.9,B\n2026-08-21,121.27,A\n2026-08-22,97.31,A\n2026-08-23,143.2,B\n2026-08-24,79.71,B\n2026-08-25,154.03,A\n2026-08-26,98.8,A\n2026-08-27,57.08,C\n2026-08-28,103.84,B\n2026-08-29,79.57,B\n2026-08-30,125.22,B\n2026-08-31,80.42,B\n2026-09-01,86.61,A\n2026-09-02,43.31,C\n2026-09-03,86.43,B\n2026-09-04,27.28,A\n2026-09-05,52.48,A\n2026-09-06,122.81,A\n2026-09-07,123.57,A\n2026-09-08,112.76,A\n2026-09-09,70.99,B\n2026-09-10,98.57,B\n2026-09-11,99.89,C\n2026-09-12,65.25,B\n2026-09-13,145.1,A\n2026-09-14,126.32,C\n2026-09-15,93.37,A\n2026-09-16,100.81,A\n2026-09-17,106.25,B\n2026-09-18,38.75,A\n2026-09-19,92.58,B\n2026-09-20,79.54,A\n2026-09-21,69.95,C\n2026-09-22,91.57,A\n2026-09-23,153.93,B\n2026-09-24,119.23,A\n2026-09-25,82.86,B\n2026-09-26,117.18,B\n2026-09-27,141.98,C\n2026-09-28,127.74,B\n2026-09-29,101.79,C\n2026-09-30,80.59,C\n2026-10-01,120.95,A\n2026-10-02,111.8,C\n2026-10-03,126.86,B\n2026-10-04,119.06,B\n2026-10-05,131.49,C\n2026-10-06,83.94,A\n2026-10-07,139.52,C\n2026-10-08,105.93,C\n2026-10-09,162.26,B\n2026-10-10,79.32,A\n2026-10-11,152.08,A\n2026-10-12,105.94,A\n2026-10-13,80.46,A\n2026-10-14,85.48,B\n2026-10-15,90.39,A\n2026-10-16,112.72,A\n2026-10-17,115.69,A\n2026-10-18,82.79,B\n2026-10-19,99.27,B\n2026-10-20,164.27,A\n2026-10-21,151.83,B\n2026-10-22,113.09,C\n2026-10-23,101.14,A\n2026-10-24,103.6,B\n2026-10-25,118.41,A\n2026-10-26,69.32,A\n2026-10-27,92.28,B\n2026-10-28,49.94,C\n2026-10-29,111.98,A\n2026-10-30,119.42,C\n2026-10-31,85.5,B\n2026-11-01,147.22,A\n2026-11-02,63.23,B\n2026-11-03,56.07,B\n2026-11-04,106.73,B\n2026-11-05,131.41,B\n2026-11-06,150.52,A\n2026-11-07,86.23,C\n2026-11-08,132.36,C\n2026-11-09,98.84,A\n2026-11-10,94.82,A\n2026-11-11,126.51,C\n2026-11-12,119.57,C\n2026-11-13,52.71,A\n2026-11-14,144.3,B\n2026-11-15,141.4,A\n2026-11-16,81.23,C\n2026-11-17,111.87,B\n2026-11-18,114.82,C\n2026-11-19,107.82,B\n2026-11-20,83.49,C\n2026-11-21,79.85,B\n2026-11-22,99.23,A\n2026-11-23,135.18,A\n2026-11-24,116.31,B\n2026-11-25,88.88,B\n2026-11-26,123.15,A\n2026-11-27,14.54,A\n2026-11-28,134.46,B\n2026-11-29,47.81,B\n2026-11-30,89.13,A\n2026-12-01,66.41,C\n2026-12-02,61.16,A\n2026-12-03,134.82,B\n2026-12-04,85.97,B\n2026-12-05,110.4,C\n2026-12-06,98.59,A\n2026-12-07,114.31,B\n2026-12-08,102.3,A\n2026-12-09,61.51,B\n2026-12-10,129.89,C\n2026-12-11,85.19,A\n2026-12-12,53.3,A\n2026-12-13,87.16,C\n2026-12-14,145.02,B\n2026-12-15,125.51,B\n2026-12-16,89.54,A\n2026-12-17,89.52,C\n2026-12-18,90.35,A\n2026-12-19,162.3,A\n2026-12-20,111.46,B\n2026-12-21,112.9,B\n2026-12-22,130.91,B\n2026-12-23,107.16,B\n2026-12-24,92.23,B\n2026-12-25,94.11,C\n2026-12-26,97.85,A\n2026-12-27,98.88,C\n2026-12-28,121.83,A\n2026-12-29,101.56,B\n2026-12-30,121.98,C\n2026-12-31,97.58,A\n2027-01-01,102.36,B\n2027-01-02,40.05,B\n2027-01-03,127.49,C\n2027-01-04,110.39,C\n2027-01-05,129.94,C\n2027-01-06,13.11,B\n2027-01-07,162.65,A\n2027-01-08,95.81,A\n2027-01-09,133.25,C\n2027-01-10,68.8,A\n2027-01-11,118.38,B\n2027-01-12,68.4,A\n2027-01-13,81.29,B\n2027-01-14,157.42,C\n2027-01-15,94.28,A\n2027-01-16,106.52,B\n2027-01-17,126.1,C\n2027-01-18,114.87,C\n2027-01-19,104.51,C\n2027-01-20,110.95,C\n2027-01-21,172.1,A\n2027-01-22,98.27,B\n2027-01-23,106.03,A\n2027-01-24,131.52,B\n2027-01-25,133.17,C\n2027-01-26,135.61,A\n2027-01-27,119.16,C\n2027-01-28,65.71,B\n2027-01-29,149.0,C\n2027-01-30,65.61,A\n2027-01-31,109.08,A\n2027-02-01,77.37,B\n2027-02-02,98.08,B\n2027-02-03,109.86,A\n2027-02-04,109.64,A\n2027-02-05,112.66,A\n2027-02-06,148.41,A\n2027-02-07,113.61,B\n2027-02-08,92.68,A\n2027-02-09,128.92,B\n2027-02-10,135.68,A\n2027-02-11,63.17,A\n2027-02-12,117.92,C\n2027-02-13,121.04,C\n2027-02-14,91.07,A\n2027-02-15,141.27,B\n2027-02-16,95.5,A\n2027-02-17,103.77,C\n2027-02-18,94.81,A\n2027-02-19,100.47,C\n2027-02-20,67.11,A\n2027-02-21,56.8,A\n2027-02-22,147.84,B\n2027-02-23,74.59,C\n2027-02-24,70.26,C\n2027-02-25,35.4,C\n2027-02-26,80.83,B\n2027-02-27,60.31,A\n2027-02-28,149.26,B\n2027-03-01,130.29,A\n2027-03-02,79.36,B\n2027-03-03,167.57,B\n2027-03-04,129.45,C\n2027-03-05,90.26,B\n2027-03-06,25.02,A\n2027-03-07,168.73,B\n2027-03-08,58.31,B\n2027-03-09,50.64,A\n2027-03-10,130.68,C\n2027-03-11,173.19,A\n2027-03-12,141.53,A\n2027-03-13,116.92,A\n2027-03-14,117.84,B\n2027-03-15,125.6,B\n2027-03-16,122.77,A\n2027-03-17,108.44,A\n2027-03-18,103.13,C\n2027-03-19,98.12,B\n2027-03-20,77.38,B\n2027-03-21,91.58,B\n2027-03-22,49.21,B\n2027-03-23,97.05,A\n2027-03-24,70.34,A\n2027-03-25,66.89,C\n2027-03-26,105.4,B\n2027-03-27,141.76,B\n2027-03-28,127.55,A\n2027-03-29,52.88,C\n2027-03-30,70.31,A\n2027-03-31,128.22,B\n2027-04-01,70.53,B\n2027-04-02,93.26,C\n2027-04-03,116.5,A\n2027-04-04,70.95,B\n2027-04-05,103.16,B\n2027-04-06,59.98,C\n2027-04-07,81.96,B\n2027-04-08,109.59,B\n2027-04-09,52.21,A\n2027-04-10,113.21,C\n2027-04-11,99.41,C\n2027-04-12,116.57,C\n2027-04-13,106.72,A\n2027-04-14,140.92,C\n2027-04-15,103.76,B\n2027-04-16,87.12,C\n2027-04-17,103.67,B\n2027-04-18,116.3,C\n2027-04-19,101.47,C\n2027-04-20,101.22,A\n2027-04-21,78.94,C\n2027-04-22,80.11,C\n2027-04-23,57.92,A\n2027-04-24,152.49,B\n2027-04-25,62.68,C\n2027-04-26,79.21,C\n2027-04-27,78.45,B\n2027-04-28,126.85,A\n2027-04-29,91.15,C\n2027-04-30,137.43,A\n2027-05-01,79.8,B\n2027-05-02,108.37,B\n2027-05-03,74.94,A\n2027-05-04,164.35,B\n2027-05-05,64.37,B\n2027-05-06,109.29,C\n2027-05-07,119.01,A\n2027-05-08,112.41,C\n2027-05-09,94.44,A\n2027-05-10,96.11,C\n2027-05-11,101.31,B\n2027-05-12,95.59,A\n2027-05-13,128.92,A\n2027-05-14,166.32,A\n2027-05-15,83.28,A\n2027-05-16,58.91,B\n2027-05-17,97.35,B\n2027-05-18,177.39,B\n2027-05-19,75.89,C\n2027-05-20,149.17,B\n2027-05-21,150.33,C\n2027-05-22,83.39,C\n2027-05-23,117.07,B\n2027-05-24,148.85,B\n2027-05-25,88.63,B\n2027-05-26,93.89,C\n2027-05-27,82.55,A\n2027-05-28,69.56,B\n2027-05-29,80.52,C\n2027-05-30,63.28,C\n2027-05-31,101.02,B\n2027-06-01,76.9,C\n2027-06-02,107.01,B\n2027-06-03,53.32,C\n2027-06-04,109.93,A\n2027-06-05,125.01,C\n2027-06-06,40.19,B\n2027-06-07,111.22,A\n2027-06-08,136.83,B\n2027-06-09,63.71,C\n2027-06-10,150.18,A\n2027-06-11,112.57,A\n2027-06-12,78.85,C\n2027-06-13,98.33,A\n2027-06-14,116.75,A\n2027-06-15,102.28,A\n2027-06-16,116.16,B\n2027-06-17,72.38,B\n2027-06-18,105.08,A\n2027-06-19,57.59,C\n2027-06-20,96.66,C\n2027-06-21,72.88,B\n2027-06-22,77.93,C\n2027-06-23,137.08,A\n2027-06-24,132.74,C\n2027-06-25,118.27,B\n2027-06-26,67.23,B\n2027-06-27,90.51,A\n2027-06-28,136.39,B\n2027-06-29,104.25,B\n2027-06-30,169.58,B\n2027-07-01,111.8,C\n2027-07-02,105.76,B\n2027-07-03,90.73,C\n2027-07-04,104.01,B\n2027-07-05,95.43,B\n2027-07-06,121.24,C\n2027-07-07,128.7,C\n2027-07-08,76.42,A\n2027-07-09,60.06,A\n2027-07-10,44.91,C\n2027-07-11,115.24,B\n2027-07-12,66.9,A\n2027-07-13,35.41,C\n2027-07-14,111.66,C\n2027-07-15,174.79,B\n2027-07-16,99.82,C\n2027-07-17,125.15,A\n2027-07-18,102.45,B\n2027-07-19,97.03,C\n2027-07-20,127.57,C\n2027-07-21,91.29,B\n2027-07-22,108.02,B\n2027-07-23,109.65,B\n2027-07-24,79.96,A\n2027-07-25,129.76,A\n2027-07-26,94.75,C\n2027-07-27,77.33,C\n2027-07-28,116.1,B\n2027-07-29,73.05,C\n2027-07-30,100.85,C\n2027-07-31,99.73,B\n2027-08-01,132.58,A\n2027-08-02,114.24,A\n2027-08-03,99.25,A\n2027-08-04,124.53,C\n2027-08-05,141.71,B\n2027-08-06,116.73,A\n2027-08-07,100.31,C\n2027-08-08,60.64,B\n2027-08-09,68.05,A\n2027-08-10,90.84,A\n2027-08-11,81.71,B\n2027-08-12,94.39,C\n2027-08-13,101.7,C\n2027-08-14,115.89,A\n2027-08-15,97.89,B\n2027-08-16,114.6,C\n2027-08-17,101.93,B\n2027-08-18,40.74,B\n2027-08-19,71.82,A\n2027-08-20,95.68,B\n2027-08-21,63.71,B\n2027-08-22,118.0,B\n2027-08-23,145.92,C\n2027-08-24,136.56,A\n2027-08-25,93.6,A\n2027-08-26,144.72,B\n2027-08-27,104.46,B\n2027-08-28,89.89,C\n2027-08-29,81.6,A\n2027-08-30,90.93,C\n2027-08-31,88.35,A\n2027-09-01,105.11,C\n2027-09-02,104.82,B\n2027-09-03,100.09,C\n2027-09-04,113.11,B\n2027-09-05,135.72,A\n2027-09-06,128.49,A\n2027-09-07,55.45,B\n2027-09-08,23.38,A\n2027-09-09,128.03,C\n2027-09-10,58.99,B\n2027-09-11,93.26,A\n2027-09-12,64.9,C\n2027-09-13,45.94,C\n2027-09-14,116.24,A\n2027-09-15,122.77,A\n2027-09-16,82.7,C\n2027-09-17,22.27,B\n2027-09-18,83.61,A\n2027-09-19,111.75,A\n2027-09-20,55.63,B\n2027-09-21,105.5,B\n2027-09-22,99.54,A\n2027-09-23,117.38,B\n2027-09-24,103.59,C\n2027-09-25,70.81,C\n2027-09-26,135.9,C\n2027-09-27,95.24,A\n2027-09-28,99.18,B\n2027-09-29,72.0,C\n2027-09-30,86.7,B\n2027-10-01,73.46,C\n2027-10-02,94.81,B\n2027-10-03,151.35,C\n2027-10-04,58.84,C\n2027-10-05,51.59,C\n2027-10-06,144.14,C\n2027-10-07,93.72,C\n2027-10-08,79.93,C\n2027-10-09,131.2,C\n2027-10-10,81.83,C\n2027-10-11,154.78,C\n2027-10-12,120.34,B\n2027-10-13,85.36,B\n2027-10-14,164.72,A\n2027-10-15,81.83,C\n2027-10-16,122.26,B\n2027-10-17,108.98,B\n2027-10-18,139.05,B\n2027-10-19,146.85,B\n2027-10-20,100.96,B\n2027-10-21,77.4,B\n2027-10-22,113.8,A\n2027-10-23,79.67,B\n2027-10-24,160.4,A\n2027-10-25,104.1,A\n2027-10-26,89.04,C\n2027-10-27,105.54,C\n2027-10-28,59.59,C\n2027-10-29,70.85,B\n2027-10-30,136.01,C\n2027-10-31,80.29,B\n2027-11-01,68.59,B\n2027-11-02,116.1,C\n2027-11-03,135.57,C\n2027-11-04,121.57,A\n2027-11-05,129.88,A\n2027-11-06,77.3,B\n2027-11-07,57.35,B\n2027-11-08,145.04,C\n2027-11-09,90.32,C\n2027-11-10,92.48,A\n2027-11-11,139.85,C\n2027-11-12,116.69,B\n2027-11-13,113.68,C\n2027-11-14,164.95,A\n2027-11-15,80.69,B\n2027-11-16,127.84,A\n2027-11-17,101.71,A\n2027-11-18,108.06,B\n2027-11-19,145.85,A\n2027-11-20,115.24,C\n2027-11-21,116.15,C\n2027-11-22,132.18,A\n2027-11-23,89.05,C\n2027-11-24,74.82,A\n2027-11-25,68.66,C\n2027-11-26,41.01,A\n2027-11-27,161.69,C\n2027-11-28,66.9,B\n2027-11-29,93.36,B\n2027-11-30,91.7,C\n2027-12-01,109.22,A\n2027-12-02,124.47,C\n2027-12-03,125.81,A\n2027-12-04,82.51,C\n2027-12-05,94.99,C\n2027-12-06,108.48,B\n2027-12-07,92.54,A\n2027-12-08,148.22,A\n2027-12-09,114.73,A\n2027-12-10,122.05,A\n2027-12-11,119.89,A\n2027-12-12,135.2,B\n2027-12-13,105.43,C\n2027-12-14,61.1,A\n2027-12-15,111.99,A\n2027-12-16,80.46,C\n2027-12-17,84.14,B\n2027-12-18,117.59,B\n2027-12-19,137.15,B\n2027-12-20,100.64,C\n2027-12-21,109.26,C\n2027-12-22,151.07,B\n2027-12-23,107.22,B\n2027-12-24,178.05,C\n2027-12-25,116.97,B\n2027-12-26,47.18,A\n2027-12-27,122.6,C\n2027-12-28,111.43,B\n2027-12-29,138.69,A\n2027-12-30,120.2,C\n2027-12-31,95.85,C\n2028-01-01,63.27,A\n2028-01-02,93.73,C\n2028-01-03,74.48,B\n2028-01-04,82.58,C\n2028-01-05,117.66,B\n2028-01-06,150.1,A\n2028-01-07,111.84,A\n2028-01-08,64.12,C\n2028-01-09,113.34,A\n2028-01-10,135.9,A\n2028-01-11,81.71,C\n2028-01-12,95.98,B\n2028-01-13,100.44,C\n2028-01-14,76.45,C\n2028-01-15,119.45,B\n2028-01-16,96.37,B\n2028-01-17,112.59,B\n2028-01-18,73.38,C\n2028-01-19,86.88,B\n2028-01-20,121.67,A\n2028-01-21,88.82,B\n2028-01-22,151.81,A\n2028-01-23,88.01,B\n2028-01-24,106.74,A\n2028-01-25,127.98,A\n2028-01-26,57.45,A\n2028-01-27,47.18,A\n2028-01-28,54.23,A\n2028-01-29,137.88,B\n2028-01-30,83.44,C\n2028-01-31,176.75,C\n2028-02-01,83.07,C\n2028-02-02,105.54,C\n2028-02-03,146.26,C\n2028-02-04,160.18,A\n2028-02-05,161.85,B\n2028-02-06,136.25,A\n2028-02-07,130.72,B\n2028-02-08,117.78,B\n2028-02-09,123.35,B\n2028-02-10,83.46,B\n2028-02-11,75.45,C\n2028-02-12,99.9,B\n2028-02-13,94.89,B\n2028-02-14,86.4,C\n2028-02-15,120.89,A\n2028-02-16,128.66,A\n2028-02-17,102.65,A\n2028-02-18,144.33,A\n2028-02-19,65.75,B\n2028-02-20,94.19,C\n2028-02-21,78.5,C\n2028-02-22,44.0,A\n2028-02-23,97.52,C\n2028-02-24,96.35,B\n2028-02-25,145.4,A\n2028-02-26,118.92,B\n2028-02-27,69.27,B\n2028-02-28,155.62,B\n2028-02-29,136.63,B\n2028-03-01,117.46,B\n2028-03-02,93.21,A\n2028-03-03,71.22,B\n2028-03-04,88.83,A\n2028-03-05,132.66,C\n2028-03-06,156.54,B\n2028-03-07,146.3,B\n2028-03-08,85.33,C\n2028-03-09,66.41,A\n2028-03-10,104.23,B\n2028-03-11,46.95,C\n2028-03-12,109.7,C\n2028-03-13,95.57,B\n2028-03-14,86.02,A\n2028-03-15,52.16,A\n2028-03-16,115.41,B\n2028-03-17,84.02,C\n2028-03-18,64.9,B\n2028-03-19,13.83,A\n2028-03-20,99.17,C\n2028-03-21,153.17,B\n2028-03-22,149.84,C\n2028-03-23,86.29,A\n2028-03-24,81.93,A\n2028-03-25,114.06,A\n2028-03-26,70.05,B\n2028-03-27,109.05,C\n2028-03-28,122.98,B\n2028-03-29,136.81,B\n2028-03-30,97.0,B\n2028-03-31,93.89,B\n2028-04-01,73.66,C\n2028-04-02,75.19,C\n2028-04-03,93.21,B\n2028-04-04,111.02,B\n2028-04-05,127.41,A\n2028-04-06,75.9,A\n2028-04-07,144.78,C\n2028-04-08,91.87,A\n2028-04-09,99.36,C\n2028-04-10,77.58,C\n2028-04-11,27.27,C\n2028-04-12,126.52,A\n2028-04-13,122.11,A\n2028-04-14,91.56,C\n2028-04-15,102.01,C\n2028-04-16,115.48,B\n2028-04-17,53.12,C\n2028-04-18,84.13,C\n2028-04-19,123.83,B\n2028-04-20,62.37,C\n2028-04-21,108.81,C\n2028-04-22,59.3,C\n2028-04-23,113.99,B\n2028-04-24,98.93,A\n2028-04-25,51.55,C\n2028-04-26,134.94,B\n2028-04-27,77.96,C\n2028-04-28,75.69,B\n2028-04-29,106.02,C\n2028-04-30,134.46,B\n2028-05-01,69.53,A\n2028-05-02,101.85,A\n2028-05-03,112.86,A\n2028-05-04,120.79,C\n2028-05-05,105.29,B\n2028-05-06,88.99,C\n2028-05-07,75.17,C\n2028-05-08,102.58,B\n2028-05-09,67.84,C\n2028-05-10,12.36,B\n2028-05-11,113.1,B\n2028-05-12,127.12,A\n2028-05-13,29.11,A\n2028-05-14,69.71,A\n2028-05-15,118.57,B\n2028-05-16,161.72,A\n2028-05-17,100.62,B\n2028-05-18,78.16,C\n2028-05-19,94.51,A\n2028-05-20,141.25,B\n2028-05-21,80.62,B\n2028-05-22,76.02,B\n2028-05-23,85.52,B\n2028-05-24,71.4,A\n2028-05-25,103.68,A\n2028-05-26,148.74,A\n2028-05-27,109.69,B\n2028-05-28,92.43,C\n2028-05-29,91.25,B\n2028-05-30,53.1,C\n2028-05-31,126.49,A\n2028-06-01,97.66,B\n2028-06-02,94.59,B\n2028-06-03,195.79,A\n2028-06-04,108.96,B\n2028-06-05,77.45,C\n2028-06-06,87.21,C\n2028-06-07,134.45,C\n2028-06-08,103.4,B\n2028-06-09,56.85,B\n2028-06-10,127.58,C\n2028-06-11,79.96,B\n2028-06-12,156.2,C\n2028-06-13,132.4,A\n2028-06-14,86.58,C\n2028-06-15,138.43,A\n2028-06-16,102.04,A\n2028-06-17,125.58,A\n2028-06-18,114.54,C\n2028-06-19,74.61,C\n2028-06-20,80.69,B\n2028-06-21,130.9,B\n2028-06-22,89.96,A\n2028-06-23,87.89,C\n2028-06-24,71.35,B\n2028-06-25,112.71,B\n2028-06-26,161.88,B\n2028-06-27,67.97,C\n2028-06-28,100.73,B\n2028-06-29,142.37,A\n2028-06-30,97.61,C\n2028-07-01,113.57,B\n2028-07-02,68.13,B\n2028-07-03,112.85,C\n2028-07-04,94.39,B\n2028-07-05,129.57,B\n2028-07-06,135.62,A\n2028-07-07,177.69,C\n2028-07-08,117.39,B\n2028-07-09,109.77,C\n2028-07-10,105.83,C\n2028-07-11,89.41,A\n2028-07-12,110.15,A\n2028-07-13,91.14,C\n2028-07-14,105.05,A\n2028-07-15,139.53,A\n2028-07-16,69.8,B\n2028-07-17,134.2,A\n2028-07-18,139.51,A\n2028-07-19,96.46,B\n2028-07-20,36.34,B\n2028-07-21,81.77,C\n2028-07-22,138.91,A\n2028-07-23,99.31,B\n2028-07-24,70.02,A\n2028-07-25,84.86,C\n2028-07-26,125.22,A\n2028-07-27,116.4,A\n2028-07-28,92.83,A\n2028-07-29,89.0,B\n2028-07-30,88.25,B\n2028-07-31,72.33,C\n2028-08-01,148.46,A\n2028-08-02,90.33,C\n2028-08-03,136.51,C\n2028-08-04,145.64,B\n2028-08-05,129.95,A\n2028-08-06,87.05,A\n2028-08-07,112.11,C\n2028-08-08,99.27,A\n2028-08-09,72.89,B\n2028-08-10,109.73,C\n2028-08-11,64.63,C\n2028-08-12,135.63,B\n2028-08-13,86.06,B\n2028-08-14,106.03,B\n2028-08-15,108.5,B\n2028-08-16,92.23,B\n2028-08-17,117.6,B\n2028-08-18,85.75,C\n2028-08-19,126.14,A\n2028-08-20,59.62,A\n2028-08-21,103.79,C\n2028-08-22,158.17,A\n2028-08-23,69.99,A\n2028-08-24,79.67,C\n2028-08-25,115.42,A\n2028-08-26,105.39,A\n2028-08-27,110.52,C\n2028-08-28,114.68,A\n2028-08-29,119.04,B\n2028-08-30,133.29,C\n2028-08-31,112.29,A\n2028-09-01,92.76,A\n2028-09-02,120.18,B\n2028-09-03,157.0,C\n2028-09-04,96.02,C\n2028-09-05,70.76,A\n2028-09-06,133.21,B\n2028-09-07,96.39,B\n2028-09-08,34.82,B\n2028-09-09,125.42,B\n2028-09-10,83.94,B\n2028-09-11,97.28,B\n2028-09-12,109.96,C\n2028-09-13,105.71,A\n2028-09-14,121.28,A\n2028-09-15,86.94,B\n2028-09-16,115.39,C\n2028-09-17,92.21,C\n2028-09-18,122.16,B\n2028-09-19,118.46,A\n2028-09-20,71.94,B\n2028-09-21,132.58,C\n2028-09-22,83.92,B\n2028-09-23,124.24,A\n2028-09-24,111.02,A\n2028-09-25,155.15,C\n2028-09-26,93.3,B\n2028-09-27,89.52,B\n2028-09-28,99.42,C\n2028-09-29,90.9,B\n2028-09-30,124.0,C\n2028-10-01,51.51,B\n2028-10-02,68.39,A\n2028-10-03,67.97,C\n2028-10-04,128.51,B\n2028-10-05,151.32,B\n2028-10-06,96.87,B\n2028-10-07,94.94,C\n2028-10-08,102.1,A\n2028-10-09,134.86,B\n2028-10-10,72.18,C\n2028-10-11,107.15,B\n2028-10-12,129.26,A\n2028-10-13,115.03,A\n2028-10-14,105.69,A\n2028-10-15,130.03,C\n2028-10-16,18.9,B\n2028-10-17,120.34,C\n2028-10-18,80.38,B\n2028-10-19,45.08,C\n2028-10-20,115.34,B\n2028-10-21,141.21,C\n2028-10-22,95.88,B\n2028-10-23,128.59,C\n2028-10-24,148.37,A\n2028-10-25,139.45,C\n2028-10-26,149.2,A\n2028-10-27,122.26,A\n2028-10-28,102.26,B\n2028-10-29,51.94,A\n2028-10-30,92.62,A\n2028-10-31,74.7,B\n2028-11-01,165.13,C\n2028-11-02,94.72,C\n2028-11-03,103.7,A\n2028-11-04,116.54,C\n2028-11-05,101.31,A\n2028-11-06,150.85,C\n2028-11-07,81.32,C\n2028-11-08,105.84,C\n2028-11-09,77.73,A\n2028-11-10,60.4,B\n2028-11-11,81.65,C\n2028-11-12,98.89,C\n2028-11-13,87.12,C\n2028-11-14,79.23,A\n2028-11-15,57.81,B\n2028-11-16,97.51,A\n2028-11-17,54.86,C\n2028-11-18,122.8,A\n2028-11-19,102.47,C\n2028-11-20,56.27,C\n2028-11-21,90.72,A\n2028-11-22,77.44,C\n2028-11-23,109.58,C\n2028-11-24,140.21,B\n2028-11-25,43.74,A\n2028-11-26,103.45,A\n2028-11-27,95.2,C\n2028-11-28,120.14,C\n2028-11-29,106.4,A\n2028-11-30,77.44,A\n2028-12-01,90.43,A\n2028-12-02,76.12,C\n2028-12-03,132.28,B\n2028-12-04,100.64,B\n2028-12-05,157.04,C\n2028-12-06,98.18,C\n2028-12-07,78.75,C\n2028-12-08,54.59,A\n2028-12-09,45.91,C\n2028-12-10,52.48,A\n2028-12-11,108.01,B\n2028-12-12,115.26,C\n2028-12-13,52.56,C\n2028-12-14,126.85,A\n2028-12-15,85.51,A\n2028-12-16,104.4,A\n2028-12-17,148.37,B\n2028-12-18,126.91,B\n2028-12-19,91.94,C\n2028-12-20,73.26,C\n2028-12-21,35.45,A\n2028-12-22,78.43,B\n2028-12-23,93.67,C\n2028-12-24,70.38,A\n2028-12-25,96.06,A\n2028-12-26,102.31,B\n2028-12-27,93.25,B\n2028-12-28,80.5,C\n2028-12-29,105.06,C\n2028-12-30,113.26,A\n2028-12-31,67.29,A\n2029-01-01,142.33,A\n2029-01-02,97.04,C\n2029-01-03,100.57,B\n2029-01-04,121.25,C\n2029-01-05,107.0,B\n2029-01-06,128.59,C\n2029-01-07,108.61,C\n2029-01-08,81.63,B\n2029-01-09,110.85,C\n2029-01-10,65.69,C\n2029-01-11,103.26,A\n2029-01-12,99.0,B\n2029-01-13,93.76,B\n2029-01-14,96.14,A\n2029-01-15,43.54,B\n2029-01-16,83.54,A\n2029-01-17,102.79,B\n2029-01-18,104.8,B\n2029-01-19,69.17,B\n2029-01-20,137.97,B\n2029-01-21,74.01,A\n2029-01-22,129.08,A\n2029-01-23,112.82,B\n2029-01-24,80.61,C\n2029-01-25,153.26,A\n2029-01-26,64.19,B\n2029-01-27,127.57,A\n2029-01-28,130.02,C\n2029-01-29,79.88,C\n2029-01-30,141.77,C\n2029-01-31,92.5,B\n2029-02-01,108.66,B\n2029-02-02,107.81,C\n2029-02-03,95.97,A\n2029-02-04,124.32,A\n2029-02-05,123.8,C\n2029-02-06,47.54,B\n2029-02-07,139.13,A\n2029-02-08,50.13,A\n2029-02-09,130.98,A\n2029-02-10,133.8,B\n2029-02-11,67.27,C\n2029-02-12,87.68,B\n2029-02-13,66.83,A\n2029-02-14,93.55,C\n2029-02-15,90.76,B\n2029-02-16,123.39,C\n2029-02-17,139.31,A\n2029-02-18,141.87,B\n2029-02-19,83.13,C\n2029-02-20,93.72,B\n2029-02-21,49.5,A\n2029-02-22,75.82,C\n2029-02-23,128.95,A\n2029-02-24,148.47,C\n2029-02-25,62.97,A\n2029-02-26,82.23,A\n2029-02-27,99.21,B\n2029-02-28,108.4,A\n2029-03-01,75.71,B\n2029-03-02,112.72,A\n2029-03-03,85.78,A\n2029-03-04,99.57,A\n2029-03-05,116.39,C\n2029-03-06,100.19,B\n2029-03-07,86.91,C\n2029-03-08,96.71,B\n2029-03-09,97.35,A\n2029-03-10,88.9,C\n2029-03-11,92.24,B\n2029-03-12,147.96,A\n2029-03-13,116.83,B\n2029-03-14,91.14,B\n2029-03-15,120.91,A\n2029-03-16,89.99,C\n2029-03-17,135.19,A\n2029-03-18,111.09,C\n2029-03-19,96.78,B\n2029-03-20,113.43,B\n2029-03-21,52.87,B\n2029-03-22,66.2,C\n2029-03-23,64.18,A\n2029-03-24,104.29,C\n2029-03-25,151.98,B\n2029-03-26,166.94,B\n2029-03-27,119.14,A\n2029-03-28,115.03,C\n2029-03-29,45.97,B\n2029-03-30,83.72,C\n2029-03-31,76.37,C\n2029-04-01,81.37,A\n2029-04-02,94.96,A\n2029-04-03,85.84,B\n2029-04-04,40.62,A\n2029-04-05,122.44,C\n2029-04-06,67.82,A\n2029-04-07,107.18,C\n2029-04-08,162.22,B\n2029-04-09,72.42,C\n2029-04-10,24.09,A\n2029-04-11,91.42,A\n2029-04-12,133.03,B\n2029-04-13,158.75,A\n2029-04-14,63.11,B\n2029-04-15,114.9,B\n2029-04-16,86.04,A\n2029-04-17,96.82,C\n2029-04-18,179.33,B\n2029-04-19,54.91,C\n2029-04-20,107.61,A\n2029-04-21,114.03,C\n2029-04-22,132.57,C\n2029-04-23,102.94,C\n2029-04-24,109.24,C\n2029-04-25,88.25,A\n2029-04-26,108.07,A\n2029-04-27,89.7,C\n2029-04-28,118.65,A\n2029-04-29,88.91,A\n2029-04-30,111.31,B\n2029-05-01,99.12,A\n2029-05-02,133.78,C\n2029-05-03,98.46,A\n2029-05-04,46.81,A\n2029-05-05,137.86,A\n2029-05-06,72.83,A\n2029-05-07,80.39,B\n2029-05-08,82.13,C\n2029-05-09,141.23,C\n2029-05-10,35.93,A\n2029-05-11,194.13,A\n2029-05-12,131.68,B\n2029-05-13,106.7,A\n2029-05-14,98.35,B\n2029-05-15,108.57,C\n2029-05-16,115.63,C\n2029-05-17,119.36,B\n2029-05-18,116.67,C\n2029-05-19,102.69,A\n2029-05-20,94.08,A\n2029-05-21,95.46,B\n2029-05-22,94.15,C\n2029-05-23,134.01,C\n2029-05-24,117.81,B\n2029-05-25,11.79,B\n2029-05-26,119.68,C\n2029-05-27,105.84,B\n2029-05-28,99.44,A\n2029-05-29,88.34,C\n2029-05-30,133.72,B\n2029-05-31,128.43,B\n2029-06-01,76.81,B\n2029-06-02,112.21,C\n2029-06-03,70.85,A\n2029-06-04,58.61,A\n2029-06-05,81.2,C\n2029-06-06,125.87,B\n2029-06-07,128.59,C\n2029-06-08,115.39,C\n2029-06-09,121.75,B\n2029-06-10,115.49,C\n2029-06-11,80.76,A\n2029-06-12,112.96,C\n2029-06-13,124.01,B\n2029-06-14,122.63,C\n2029-06-15,135.67,A\n2029-06-16,121.25,C\n2029-06-17,110.54,C\n2029-06-18,132.1,C\n2029-06-19,99.2,C\n2029-06-20,73.54,B\n2029-06-21,95.11,A\n2029-06-22,77.65,A\n2029-06-23,79.74,A\n2029-06-24,95.66,C\n2029-06-25,76.23,A\n2029-06-26,90.76,B\n2029-06-27,43.19,C\n2029-06-28,106.4,C\n2029-06-29,100.04,A\n2029-06-30,75.49,C\n2029-07-01,119.78,C\n2029-07-02,128.13,A\n2029-07-03,51.77,B\n2029-07-04,77.12,B\n2029-07-05,76.93,A\n2029-07-06,71.8,B\n2029-07-07,124.88,B\n2029-07-08,94.19,B\n2029-07-09,92.06,C\n2029-07-10,39.88,C\n2029-07-11,119.06,C\n2029-07-12,62.82,B\n2029-07-13,101.8,C\n2029-07-14,108.32,B\n2029-07-15,140.82,A\n2029-07-16,60.74,B\n2029-07-17,9.41,C\n2029-07-18,105.52,B\n2029-07-19,154.02,C\n2029-07-20,137.17,C\n2029-07-21,106.29,A\n2029-07-22,85.25,A\n2029-07-23,124.21,C\n2029-07-24,70.79,B\n2029-07-25,114.29,A\n2029-07-26,115.16,A\n2029-07-27,131.81,C\n2029-07-28,182.79,B\n2029-07-29,111.77,A\n2029-07-30,84.73,A\n2029-07-31,99.23,B\n2029-08-01,46.93,A\n2029-08-02,79.16,B\n2029-08-03,87.72,C\n2029-08-04,84.28,A\n2029-08-05,104.57,C\n2029-08-06,75.33,B\n2029-08-07,133.63,C\n2029-08-08,100.01,C\n2029-08-09,99.72,C\n2029-08-10,90.16,B\n2029-08-11,104.66,B\n2029-08-12,124.75,B\n2029-08-13,73.99,B\n2029-08-14,80.26,B\n2029-08-15,90.89,A\n2029-08-16,59.62,A\n2029-08-17,75.42,A\n2029-08-18,85.71,C\n2029-08-19,126.23,C\n2029-08-20,107.88,B\n2029-08-21,105.81,B\n2029-08-22,125.53,A\n2029-08-23,95.88,B\n2029-08-24,111.71,C\n2029-08-25,96.9,B\n2029-08-26,107.96,A\n2029-08-27,82.52,B\n2029-08-28,26.84,C\n2029-08-29,95.97,A\n2029-08-30,142.68,B\n2029-08-31,127.79,C\n2029-09-01,128.96,A\n2029-09-02,137.08,A\n2029-09-03,102.66,C\n2029-09-04,105.92,B\n2029-09-05,81.47,B\n2029-09-06,90.52,B\n2029-09-07,118.47,A\n2029-09-08,136.12,A\n2029-09-09,95.82,B\n2029-09-10,86.49,A\n2029-09-11,100.02,A\n2029-09-12,118.04,C\n2029-09-13,56.68,B\n2029-09-14,31.11,B\n2029-09-15,83.48,B\n2029-09-16,63.38,A\n2029-09-17,84.76,A\n2029-09-18,95.57,A\n2029-09-19,86.4,B\n2029-09-20,143.57,A\n2029-09-21,109.8,B\n2029-09-22,109.01,A\n2029-09-23,118.67,B\n2029-09-24,65.84,C\n2029-09-25,131.17,A\n2029-09-26,97.73,B\n2029-09-27,120.11,C\n2029-09-28,67.84,C\n2029-09-29,53.39,A\n2029-09-30,124.54,B\n2029-10-01,111.29,A\n2029-10-02,72.94,C\n2029-10-03,73.91,A\n2029-10-04,133.76,B\n2029-10-05,64.32,C\n2029-10-06,149.28,A\n2029-10-07,72.98,A\n2029-10-08,119.15,A\n2029-10-09,90.14,A\n2029-10-10,118.1,A\n2029-10-11,83.68,C\n2029-10-12,95.12,C\n2029-10-13,101.23,B\n2029-10-14,69.93,A\n2029-10-15,122.22,B\n2029-10-16,84.6,C\n2029-10-17,93.14,A\n2029-10-18,70.17,B\n2029-10-19,23.13,A\n2029-10-20,94.27,A\n2029-10-21,172.38,B\n2029-10-22,123.54,C\n2029-10-23,99.42,B\n2029-10-24,92.11,B\n2029-10-25,100.67,B\n2029-10-26,116.41,A\n2029-10-27,64.58,C\n2029-10-28,133.43,C\n2029-10-29,121.46,A\n2029-10-30,121.55,C\n2029-10-31,113.15,B\n2029-11-01,100.59,C\n2029-11-02,120.19,A\n2029-11-03,117.75,C\n2029-11-04,89.38,A\n2029-11-05,82.79,B\n2029-11-06,103.06,A\n2029-11-07,146.47,C\n2029-11-08,62.83,B\n2029-11-09,55.97,C\n2029-11-10,104.94,A\n2029-11-11,101.53,B\n2029-11-12,105.2,A\n2029-11-13,107.32,C\n2029-11-14,93.3,A\n2029-11-15,144.7,C\n2029-11-16,51.97,C\n2029-11-17,75.15,B\n2029-11-18,96.9,B\n2029-11-19,50.7,A\n2029-11-20,94.72,C\n2029-11-21,149.84,C\n2029-11-22,100.63,B\n2029-11-23,106.92,B\n2029-11-24,62.2,B\n2029-11-25,81.51,B\n2029-11-26,88.74,A\n2029-11-27,90.47,A\n2029-11-28,138.45,A\n2029-11-29,116.73,A\n2029-11-30,66.66,B\n2029-12-01,107.4,A\n2029-12-02,114.95,A\n2029-12-03,134.2,A\n2029-12-04,147.42,B\n2029-12-05,69.55,C\n2029-12-06,75.67,B\n2029-12-07,62.27,C\n2029-12-08,92.98,C\n2029-12-09,113.99,A\n2029-12-10,129.62,A\n2029-12-11,97.72,A\n2029-12-12,90.4,B\n2029-12-13,104.55,B\n2029-12-14,74.95,A\n2029-12-15,162.69,B\n2029-12-16,51.77,B\n2029-12-17,105.54,C\n2029-12-18,160.71,B\n2029-12-19,100.2,C\n2029-12-20,94.3,A\n2029-12-21,89.28,B\n2029-12-22,94.59,C\n2029-12-23,141.19,B\n2029-12-24,33.64,C\n2029-12-25,146.0,C\n2029-12-26,57.28,B\n2029-12-27,92.0,A\n2029-12-28,87.12,C\n2029-12-29,117.66,A\n2029-12-30,52.06,B\n2029-12-31,113.87,B\n2030-01-01,160.73,A\n2030-01-02,59.1,B\n2030-01-03,105.69,C\n2030-01-04,80.14,A\n2030-01-05,112.78,B\n2030-01-06,100.57,B\n2030-01-07,80.76,A\n2030-01-08,114.64,C\n2030-01-09,154.13,A\n2030-01-10,94.27,B\n2030-01-11,121.59,B\n2030-01-12,61.2,A\n2030-01-13,71.31,C\n2030-01-14,114.17,C\n2030-01-15,144.52,C\n2030-01-16,110.67,A\n2030-01-17,90.61,C\n2030-01-18,99.98,C\n2030-01-19,62.49,C\n2030-01-20,118.14,C\n2030-01-21,126.47,B\n2030-01-22,86.44,A\n2030-01-23,85.9,A\n2030-01-24,107.98,C\n2030-01-25,86.9,A\n2030-01-26,98.02,A\n2030-01-27,162.99,B\n2030-01-28,92.59,A\n2030-01-29,89.25,B\n2030-01-30,80.57,A\n2030-01-31,122.33,B\n2030-02-01,94.56,B\n2030-02-02,80.52,A\n2030-02-03,139.64,B\n2030-02-04,142.59,B\n2030-02-05,81.99,A\n2030-02-06,44.0,B\n2030-02-07,130.23,B\n2030-02-08,79.46,B\n2030-02-09,123.72,B\n2030-02-10,40.9,B\n2030-02-11,126.78,C\n2030-02-12,63.66,B\n2030-02-13,121.92,B\n2030-02-14,100.43,B\n2030-02-15,71.38,B\n2030-02-16,87.79,A\n2030-02-17,120.59,C\n2030-02-18,103.18,A\n2030-02-19,117.53,C\n2030-02-20,159.29,B\n2030-02-21,53.07,B\n2030-02-22,148.52,C\n2030-02-23,103.13,C\n2030-02-24,73.04,A\n2030-02-25,60.09,C\n2030-02-26,94.33,B\n2030-02-27,127.65,A\n2030-02-28,96.17,C\n2030-03-01,145.33,C\n2030-03-02,56.46,A\n2030-03-03,99.64,C\n2030-03-04,62.43,B\n2030-03-05,110.91,A\n2030-03-06,126.61,C\n2030-03-07,87.38,B\n2030-03-08,21.87,C\n2030-03-09,105.97,A\n2030-03-10,113.1,C\n2030-03-11,112.13,C\n2030-03-12,137.07,C\n2030-03-13,67.87,B\n2030-03-14,120.41,B\n2030-03-15,135.78,C\n2030-03-16,46.64,C\n2030-03-17,109.59,A\n2030-03-18,84.87,C\n2030-03-19,97.55,A\n2030-03-20,110.43,B\n2030-03-21,85.38,C\n2030-03-22,79.73,A\n2030-03-23,101.02,B\n2030-03-24,67.38,A\n2030-03-25,67.43,B\n2030-03-26,120.38,C\n2030-03-27,65.54,A\n2030-03-28,119.99,C\n2030-03-29,113.88,A\n2030-03-30,48.23,B\n2030-03-31,79.67,A\n2030-04-01,135.82,A\n2030-04-02,70.57,C\n2030-04-03,86.07,C\n2030-04-04,113.86,C\n2030-04-05,123.5,B\n2030-04-06,92.45,C\n2030-04-07,82.07,A\n2030-04-08,142.67,B\n2030-04-09,152.17,C\n2030-04-10,129.37,A\n2030-04-11,102.56,A\n2030-04-12,75.75,B\n2030-04-13,75.09,A\n2030-04-14,115.68,C\n2030-04-15,112.55,A\n2030-04-16,142.05,C\n2030-04-17,119.51,C\n2030-04-18,54.91,B\n2030-04-19,131.56,A\n2030-04-20,70.06,B\n2030-04-21,88.48,B\n2030-04-22,107.51,B\n2030-04-23,159.87,C\n2030-04-24,193.3,C\n2030-04-25,118.2,C\n2030-04-26,94.5,C\n2030-04-27,116.04,A\n2030-04-28,126.63,A\n2030-04-29,90.38,C\n2030-04-30,153.86,C\n2030-05-01,106.9,A\n2030-05-02,114.93,B\n2030-05-03,119.98,B\n2030-05-04,112.65,C\n2030-05-05,125.17,B\n2030-05-06,81.48,C\n2030-05-07,83.25,C\n2030-05-08,67.0,C\n2030-05-09,113.19,A\n2030-05-10,123.37,B\n2030-05-11,113.73,A\n2030-05-12,150.23,B\n2030-05-13,99.83,A\n2030-05-14,120.06,A\n2030-05-15,67.25,C\n2030-05-16,88.39,C\n2030-05-17,120.87,A\n2030-05-18,125.47,B\n2030-05-19,91.18,B\n2030-05-20,97.85,C\n2030-05-21,54.46,C\n2030-05-22,89.29,B\n2030-05-23,126.71,B\n2030-05-24,117.26,B\n2030-05-25,115.02,C\n2030-05-26,101.49,B\n2030-05-27,100.21,C\n2030-05-28,80.19,A\n2030-05-29,120.96,A\n2030-05-30,112.63,A\n2030-05-31,114.76,B\n2030-06-01,84.22,C\n2030-06-02,35.4,B\n2030-06-03,132.91,C\n2030-06-04,85.63,B\n2030-06-05,74.12,A\n2030-06-06,120.8,C\n2030-06-07,88.24,B\n2030-06-08,131.8,C\n2030-06-09,118.51,C\n2030-06-10,120.51,B\n2030-06-11,59.02,B\n2030-06-12,136.36,C\n2030-06-13,107.84,C\n2030-06-14,88.92,A\n2030-06-15,104.3,C\n2030-06-16,46.71,A\n2030-06-17,112.26,A\n2030-06-18,69.12,A\n2030-06-19,59.42,C\n2030-06-20,54.33,A\n2030-06-21,133.38,C\n2030-06-22,81.12,C\n2030-06-23,146.01,B\n2030-06-24,83.93,C\n2030-06-25,48.78,C\n2030-06-26,66.5,A\n2030-06-27,137.07,C\n2030-06-28,95.32,C\n2030-06-29,83.55,B\n2030-06-30,104.8,B\n2030-07-01,115.05,A\n2030-07-02,133.52,A\n2030-07-03,143.45,B\n2030-07-04,89.21,B\n2030-07-05,60.22,C\n2030-07-06,87.6,A\n2030-07-07,107.81,A\n2030-07-08,71.09,C\n2030-07-09,71.29,C\n2030-07-10,110.31,A\n2030-07-11,98.54,B\n2030-07-12,100.98,B\n2030-07-13,77.25,C\n2030-07-14,93.09,A\n2030-07-15,72.27,C\n2030-07-16,126.71,B\n2030-07-17,131.06,B\n2030-07-18,44.61,C\n2030-07-19,72.11,B\n2030-07-20,55.1,A\n2030-07-21,80.5,C\n2030-07-22,97.5,C\n2030-07-23,56.51,C\n2030-07-24,72.34,B\n2030-07-25,69.88,A\n2030-07-26,106.22,C\n2030-07-27,102.08,C\n2030-07-28,78.35,B\n2030-07-29,105.3,A\n2030-07-30,83.6,C\n2030-07-31,91.85,B\n2030-08-01,150.2,C\n2030-08-02,140.21,B\n2030-08-03,61.01,A\n2030-08-04,124.89,B\n2030-08-05,124.34,C\n2030-08-06,65.55,C\n2030-08-07,124.56,B\n2030-08-08,146.14,A\n2030-08-09,66.32,A\n2030-08-10,72.47,C\n2030-08-11,130.53,C\n2030-08-12,108.14,B\n2030-08-13,116.54,B\n2030-08-14,110.22,B\n2030-08-15,111.72,A\n2030-08-16,60.21,A\n2030-08-17,131.42,C\n2030-08-18,135.09,C\n2030-08-19,93.12,C\n2030-08-20,98.7,B\n2030-08-21,54.07,A\n2030-08-22,115.43,C\n2030-08-23,117.16,C\n2030-08-24,98.13,C\n2030-08-25,133.73,C\n2030-08-26,89.98,C\n2030-08-27,116.94,C\n2030-08-28,69.41,A\n2030-08-29,99.29,C\n2030-08-30,94.77,A\n2030-08-31,106.76,B\n2030-09-01,88.91,A\n2030-09-02,96.06,B\n2030-09-03,124.78,A\n2030-09-04,86.9,A\n2030-09-05,51.8,C\n2030-09-06,152.49,A\n2030-09-07,141.44,A\n2030-09-08,61.23,C\n2030-09-09,120.69,B\n2030-09-10,84.91,C\n2030-09-11,107.9,C\n2030-09-12,108.83,C\n2030-09-13,92.97,B\n2030-09-14,76.49,A\n2030-09-15,79.28,A\n2030-09-16,72.51,C\n2030-09-17,75.05,C\n2030-09-18,97.98,A\n2030-09-19,78.53,A\n2030-09-20,120.46,A\n2030-09-21,144.62,B\n2030-09-22,82.6,B\n2030-09-23,107.18,C\n2030-09-24,114.99,B\n2030-09-25,114.16,C\n2030-09-26,102.28,A\n2030-09-27,122.28,A\n2030-09-28,114.47,C\n2030-09-29,62.87,A\n2030-09-30,126.07,C\n2030-10-01,126.62,C\n2030-10-02,77.1,C\n2030-10-03,101.14,B\n2030-10-04,120.5,C\n2030-10-05,93.72,C\n2030-10-06,132.19,C\n2030-10-07,170.92,B\n2030-10-08,76.42,A\n2030-10-09,58.57,B\n2030-10-10,109.11,C\n2030-10-11,121.65,B\n2030-10-12,93.07,A\n2030-10-13,143.6,A\n2030-10-14,59.84,C\n2030-10-15,120.79,A\n2030-10-16,81.82,B\n2030-10-17,151.58,B\n2030-10-18,159.78,A\n2030-10-19,77.0,C\n2030-10-20,83.51,C\n2030-10-21,125.79,B\n2030-10-22,88.39,A\n2030-10-23,98.64,B\n2030-10-24,100.76,B\n2030-10-25,42.41,B\n2030-10-26,99.58,C\n2030-10-27,79.31,A\n2030-10-28,85.21,A\n2030-10-29,143.31,C\n2030-10-30,62.3,B\n2030-10-31,124.4,B\n2030-11-01,91.63,C\n2030-11-02,91.61,A\n2030-11-03,123.71,C\n2030-11-04,110.2,A\n2030-11-05,117.12,A\n2030-11-06,129.05,A\n2030-11-07,90.06,B\n2030-11-08,81.63,B\n2030-11-09,67.45,C\n2030-11-10,75.24,C\n2030-11-11,188.47,B\n2030-11-12,137.34,C\n2030-11-13,59.47,B\n2030-11-14,60.33,B\n2030-11-15,114.46,A\n2030-11-16,116.42,A\n2030-11-17,116.47,B\n2030-11-18,92.35,A\n2030-11-19,96.24,B\n2030-11-20,109.84,A\n2030-11-21,102.58,C\n2030-11-22,33.42,C\n2030-11-23,93.11,B\n2030-11-24,74.46,B\n2030-11-25,105.26,A\n2030-11-26,189.56,A\n2030-11-27,111.02,B\n2030-11-28,90.59,C\n2030-11-29,127.65,C\n2030-11-30,114.48,B\n2030-12-01,112.6,C\n2030-12-02,118.21,B\n2030-12-03,161.7,A\n2030-12-04,66.07,C\n2030-12-05,114.21,C\n2030-12-06,72.21,A\n2030-12-07,116.67,A\n2030-12-08,72.44,C\n2030-12-09,87.48,C\n2030-12-10,91.15,B\n2030-12-11,129.27,B\n2030-12-12,127.55,C\n2030-12-13,62.63,B\n2030-12-14,101.65,A\n2030-12-15,78.71,B\n2030-12-16,62.24,B\n2030-12-17,93.49,C\n2030-12-18,90.75,B\n2030-12-19,172.8,C\n2030-12-20,112.99,C\n2030-12-21,58.67,C\n2030-12-22,83.06,A\n2030-12-23,130.64,B\n2030-12-24,162.65,B\n2030-12-25,52.42,B\n2030-12-26,43.6,C\n2030-12-27,156.13,A\n2030-12-28,111.69,C\n2030-12-29,73.95,B\n2030-12-30,116.04,C\n2030-12-31,20.93,C\n2031-01-01,100.1,B\n2031-01-02,109.83,C\n2031-01-03,127.73,C\n2031-01-04,69.58,A\n2031-01-05,102.57,C\n2031-01-06,72.24,A\n2031-01-07,107.66,B\n2031-01-08,73.14,A\n2031-01-09,87.76,C\n2031-01-10,70.13,B\n2031-01-11,119.53,C\n2031-01-12,125.75,C\n2031-01-13,92.96,A\n2031-01-14,101.15,C\n2031-01-15,56.54,A\n2031-01-16,91.01,B\n2031-01-17,98.49,B\n2031-01-18,178.62,C\n2031-01-19,66.64,A\n2031-01-20,143.93,C\n2031-01-21,129.91,C\n2031-01-22,108.42,C\n2031-01-23,152.76,C\n2031-01-24,111.16,A\n2031-01-25,111.67,C\n2031-01-26,98.41,B\n2031-01-27,136.71,A\n2031-01-28,132.89,C\n2031-01-29,81.23,A\n2031-01-30,60.42,C\n2031-01-31,93.91,B\n2031-02-01,76.0,B\n2031-02-02,98.09,B\n2031-02-03,137.12,A\n2031-02-04,86.28,B\n2031-02-05,98.72,C\n2031-02-06,101.74,C\n2031-02-07,125.45,C\n2031-02-08,32.59,A\n2031-02-09,81.8,A\n2031-02-10,106.34,B\n2031-02-11,136.0,A\n2031-02-12,85.24,C\n2031-02-13,43.7,C\n2031-02-14,118.59,C\n2031-02-15,80.94,A\n2031-02-16,64.31,C\n2031-02-17,81.27,B\n2031-02-18,94.46,A\n2031-02-19,81.86,C\n2031-02-20,33.83,C\n2031-02-21,126.91,A\n2031-02-22,138.25,A\n2031-02-23,119.55,B\n2031-02-24,65.87,A\n2031-02-25,93.96,C\n2031-02-26,99.8,B\n2031-02-27,117.96,C\n2031-02-28,120.05,C\n2031-03-01,77.97,C\n2031-03-02,102.46,A\n2031-03-03,113.72,A\n2031-03-04,143.67,C\n2031-03-05,121.14,A\n2031-03-06,123.67,C\n2031-03-07,102.51,A\n2031-03-08,142.31,A\n2031-03-09,112.29,A\n2031-03-10,74.17,B\n2031-03-11,142.1,A\n2031-03-12,120.94,B\n2031-03-13,86.71,B\n2031-03-14,85.62,B\n2031-03-15,108.9,C\n2031-03-16,113.9,A\n2031-03-17,105.92,C\n2031-03-18,109.34,B\n2031-03-19,151.0,A\n2031-03-20,132.15,C\n2031-03-21,105.72,B\n2031-03-22,128.24,C\n2031-03-23,69.02,C\n2031-03-24,111.94,A\n2031-03-25,154.28,A\n2031-03-26,93.46,C\n2031-03-27,74.57,A\n2031-03-28,80.44,A\n2031-03-29,67.31,C\n2031-03-30,76.46,A\n2031-03-31,88.88,A\n2031-04-01,57.83,B\n2031-04-02,100.47,A\n2031-04-03,127.07,B\n2031-04-04,72.77,C\n2031-04-05,145.58,B\n2031-04-06,115.32,C\n2031-04-07,130.92,A\n2031-04-08,80.26,A\n2031-04-09,125.64,B\n2031-04-10,67.24,C\n2031-04-11,126.71,A\n2031-04-12,105.16,B\n2031-04-13,116.6,B\n2031-04-14,64.73,B\n2031-04-15,73.15,A\n2031-04-16,117.92,C\n2031-04-17,71.55,A\n2031-04-18,113.9,A\n2031-04-19,58.99,C\n2031-04-20,125.45,B\n2031-04-21,63.02,A\n2031-04-22,116.57,B\n2031-04-23,118.77,B\n2031-04-24,79.1,C\n2031-04-25,117.46,C\n2031-04-26,107.81,C\n2031-04-27,83.83,B\n2031-04-28,69.74,B\n2031-04-29,41.12,A\n2031-04-30,110.49,B\n2031-05-01,53.06,C\n2031-05-02,102.86,C\n2031-05-03,92.1,C\n2031-05-04,120.37,C\n2031-05-05,90.94,B\n2031-05-06,90.12,C\n2031-05-07,121.96,A\n2031-05-08,110.05,B\n2031-05-09,109.48,C\n2031-05-10,114.08,A\n2031-05-11,53.93,C\n2031-05-12,122.7,B\n2031-05-13,118.37,B\n2031-05-14,69.5,A\n2031-05-15,92.68,B\n2031-05-16,98.82,A\n2031-05-17,95.97,C\n2031-05-18,110.02,B\n2031-05-19,142.94,C\n2031-05-20,132.45,C\n2031-05-21,60.63,B\n2031-05-22,118.66,C\n2031-05-23,139.87,A\n2031-05-24,111.6,A\n2031-05-25,132.73,A\n2031-05-26,160.37,B\n2031-05-27,130.71,C\n2031-05-28,107.48,C\n2031-05-29,131.35,C\n2031-05-30,104.35,C\n2031-05-31,100.72,A\n2031-06-01,89.46,B\n2031-06-02,146.9,A\n2031-06-03,75.45,A\n2031-06-04,145.97,B\n2031-06-05,115.0,B\n2031-06-06,58.02,A\n2031-06-07,111.03,C\n2031-06-08,37.0,B\n2031-06-09,118.77,B\n2031-06-10,126.56,A\n2031-06-11,82.23,C\n2031-06-12,103.71,B\n2031-06-13,158.62,A\n2031-06-14,84.83,B\n2031-06-15,68.23,C\n2031-06-16,144.45,A\n2031-06-17,158.88,A\n2031-06-18,100.11,C\n2031-06-19,130.34,A\n2031-06-20,140.24,C\n2031-06-21,77.73,A\n2031-06-22,85.44,C\n2031-06-23,136.93,B\n2031-06-24,150.55,B\n2031-06-25,116.89,B\n2031-06-26,73.61,A\n2031-06-27,159.61,C\n2031-06-28,84.07,C\n2031-06-29,89.95,B\n2031-06-30,110.27,B\n2031-07-01,146.62,B\n2031-07-02,125.62,C\n2031-07-03,112.45,B\n2031-07-04,113.9,A\n2031-07-05,101.31,B\n2031-07-06,116.74,B\n2031-07-07,24.11,C\n2031-07-08,91.1,A\n2031-07-09,107.24,C\n2031-07-10,65.47,A\n2031-07-11,111.59,A\n2031-07-12,93.87,B\n2031-07-13,152.66,B\n2031-07-14,147.19,C\n2031-07-15,86.03,C\n2031-07-16,95.47,C\n2031-07-17,97.78,A\n2031-07-18,86.45,C\n2031-07-19,105.85,A\n2031-07-20,77.25,A\n2031-07-21,66.08,A\n2031-07-22,118.68,A\n2031-07-23,118.89,A\n2031-07-24,75.87,B\n2031-07-25,126.86,A\n2031-07-26,81.05,A\n2031-07-27,107.59,B\n2031-07-28,124.61,B\n2031-07-29,98.99,B\n2031-07-30,113.64,C\n2031-07-31,84.51,C\n2031-08-01,94.11,A\n2031-08-02,93.82,A\n2031-08-03,77.6,A\n2031-08-04,94.7,A\n2031-08-05,53.54,B\n2031-08-06,85.27,C\n2031-08-07,91.45,B\n2031-08-08,92.31,C\n2031-08-09,92.76,B\n2031-08-10,98.15,A\n2031-08-11,114.38,C\n2031-08-12,126.24,A\n2031-08-13,80.51,C\n2031-08-14,63.9,C\n2031-08-15,68.74,A\n2031-08-16,85.38,A\n2031-08-17,89.44,A\n2031-08-18,76.9,B\n2031-08-19,61.12,C\n2031-08-20,86.32,C\n2031-08-21,105.44,C\n2031-08-22,117.85,A\n2031-08-23,83.28,C\n2031-08-24,87.61,C\n2031-08-25,72.2,A\n2031-08-26,99.06,A\n2031-08-27,74.55,C\n2031-08-28,117.19,B\n2031-08-29,46.42,A\n2031-08-30,89.21,B\n2031-08-31,109.03,B\n2031-09-01,105.52,B\n2031-09-02,180.79,B\n2031-09-03,110.49,A\n2031-09-04,69.88,C\n2031-09-05,97.14,C\n2031-09-06,46.71,A\n2031-09-07,97.58,A\n2031-09-08,75.01,B\n2031-09-09,127.46,B\n2031-09-10,83.51,A\n2031-09-11,96.5,C\n2031-09-12,80.93,B\n2031-09-13,152.17,A\n2031-09-14,90.36,A\n2031-09-15,154.98,A\n2031-09-16,124.42,B\n2031-09-17,114.46,A\n2031-09-18,111.06,C\n2031-09-19,111.81,A\n2031-09-20,42.17,B\n2031-09-21,91.63,C\n2031-09-22,125.35,C\n2031-09-23,98.53,A\n2031-09-24,57.84,A\n2031-09-25,98.43,C\n2031-09-26,151.12,A\n2031-09-27,137.41,B\n2031-09-28,98.14,C\n2031-09-29,128.54,A\n2031-09-30,89.0,C\n2031-10-01,48.88,A\n2031-10-02,72.27,A\n2031-10-03,146.89,B\n2031-10-04,91.78,A\n2031-10-05,92.7,A\n2031-10-06,91.0,A\n2031-10-07,157.12,B\n2031-10-08,148.78,A\n2031-10-09,166.56,A\n2031-10-10,95.23,A\n2031-10-11,108.87,C\n2031-10-12,54.5,B\n2031-10-13,144.31,C\n2031-10-14,64.97,B\n2031-10-15,106.5,B\n2031-10-16,67.08,C\n2031-10-17,82.33,B\n2031-10-18,74.88,B\n2031-10-19,81.76,C\n2031-10-20,83.83,A\n2031-10-21,83.55,B\n2031-10-22,125.0,C\n2031-10-23,66.85,B\n2031-10-24,106.62,C\n2031-10-25,136.54,A\n2031-10-26,84.6,B\n2031-10-27,78.46,A\n2031-10-28,93.08,A\n2031-10-29,135.39,C\n2031-10-30,105.82,B\n2031-10-31,84.06,B\n2031-11-01,114.52,B\n2031-11-02,66.93,B\n2031-11-03,120.44,B\n2031-11-04,112.26,C\n2031-11-05,90.77,B\n2031-11-06,74.84,A\n2031-11-07,73.4,A\n2031-11-08,116.04,B\n2031-11-09,136.87,C\n2031-11-10,80.88,C\n2031-11-11,113.75,B\n2031-11-12,37.39,A\n2031-11-13,82.46,B\n2031-11-14,99.07,B\n2031-11-15,72.71,B\n2031-11-16,71.9,C\n2031-11-17,79.97,C\n2031-11-18,108.77,A\n2031-11-19,94.38,A\n2031-11-20,32.85,B\n2031-11-21,36.38,A\n2031-11-22,81.79,B\n2031-11-23,113.73,A\n2031-11-24,17.57,B\n2031-11-25,85.01,C\n2031-11-26,84.21,B\n2031-11-27,141.65,C\n2031-11-28,88.45,C\n2031-11-29,111.49,A\n2031-11-30,104.24,A\n2031-12-01,36.08,C\n2031-12-02,123.05,B\n2031-12-03,106.46,B\n2031-12-04,115.25,A\n2031-12-05,217.79,B\n2031-12-06,37.48,B\n2031-12-07,151.74,B\n2031-12-08,91.38,C\n2031-12-09,108.62,C\n2031-12-10,98.63,B\n2031-12-11,87.27,A\n2031-12-12,82.91,B\n2031-12-13,109.89,A\n2031-12-14,54.48,C\n2031-12-15,122.52,B\n2031-12-16,87.51,B\n2031-12-17,66.1,A\n2031-12-18,86.5,A\n2031-12-19,137.71,B\n2031-12-20,83.94,B\n2031-12-21,110.75,A\n2031-12-22,78.07,C\n2031-12-23,122.74,A\n2031-12-24,120.57,B\n2031-12-25,155.46,C\n2031-12-26,94.73,C\n2031-12-27,120.06,C\n2031-12-28,102.94,C\n2031-12-29,138.88,B\n2031-12-30,78.44,C\n2031-12-31,122.35,A\n2032-01-01,94.16,A\n2032-01-02,98.07,A\n2032-01-03,102.75,B\n2032-01-04,107.57,C\n2032-01-05,96.51,A\n2032-01-06,106.44,A\n2032-01-07,147.34,A\n2032-01-08,129.56,C\n2032-01-09,126.07,A\n2032-01-10,86.33,C\n2032-01-11,73.32,C\n2032-01-12,128.66,C\n2032-01-13,126.28,B\n2032-01-14,144.18,B\n2032-01-15,81.88,C\n2032-01-16,93.11,C\n2032-01-17,50.82,B\n2032-01-18,88.23,B\n2032-01-19,129.9,C\n2032-01-20,86.01,B\n2032-01-21,119.21,A\n2032-01-22,94.28,C\n2032-01-23,109.72,C\n2032-01-24,65.57,B\n2032-01-25,102.56,A\n2032-01-26,10.27,B\n2032-01-27,94.39,A\n2032-01-28,51.09,A\n2032-01-29,136.18,C\n2032-01-30,123.32,A\n2032-01-31,114.0,C\n2032-02-01,145.63,A\n2032-02-02,71.53,C\n2032-02-03,152.42,A\n2032-02-04,127.97,B\n2032-02-05,92.9,A\n2032-02-06,134.07,B\n2032-02-07,66.81,A\n2032-02-08,75.26,A\n2032-02-09,81.74,C\n2032-02-10,84.13,A\n2032-02-11,68.3,A\n2032-02-12,136.69,A\n2032-02-13,92.23,B\n2032-02-14,110.58,C\n2032-02-15,82.89,C\n2032-02-16,45.38,A\n2032-02-17,108.1,A\n2032-02-18,42.63,B\n2032-02-19,97.94,A\n2032-02-20,58.95,C\n2032-02-21,159.62,C\n2032-02-22,127.34,B\n2032-02-23,103.17,B\n2032-02-24,137.91,C\n2032-02-25,74.61,B\n2032-02-26,116.3,A\n2032-02-27,105.99,A\n2032-02-28,107.92,B\n2032-02-29,138.17,C\n2032-03-01,121.97,B\n2032-03-02,108.66,C\n2032-03-03,50.35,B\n2032-03-04,71.2,B\n2032-03-05,96.32,B\n2032-03-06,102.8,A\n2032-03-07,66.09,A\n2032-03-08,172.35,C\n2032-03-09,145.49,B\n2032-03-10,118.06,A\n2032-03-11,102.16,B\n2032-03-12,93.63,C\n2032-03-13,71.44,A\n2032-03-14,102.32,C\n2032-03-15,107.73,A\n2032-03-16,62.75,B\n2032-03-17,110.03,C\n2032-03-18,95.34,A\n2032-03-19,42.77,A\n2032-03-20,74.19,B\n2032-03-21,87.59,C\n2032-03-22,156.63,C\n2032-03-23,116.7,C\n2032-03-24,59.94,B\n2032-03-25,114.58,A\n2032-03-26,53.58,A\n2032-03-27,132.48,C\n2032-03-28,85.87,A\n2032-03-29,97.19,C\n2032-03-30,139.77,B\n2032-03-31,61.39,B\n2032-04-01,58.09,B\n2032-04-02,82.49,C\n2032-04-03,131.15,A\n2032-04-04,54.42,B\n2032-04-05,15.04,C\n2032-04-06,86.47,C\n2032-04-07,116.55,B\n2032-04-08,136.01,B\n2032-04-09,86.11,B\n2032-04-10,87.66,A\n2032-04-11,134.62,A\n2032-04-12,43.91,C\n2032-04-13,88.34,B\n2032-04-14,105.71,B\n2032-04-15,113.48,A\n2032-04-16,84.71,B\n2032-04-17,101.03,C\n2032-04-18,25.35,B\n2032-04-19,80.25,A\n2032-04-20,113.61,C\n2032-04-21,70.53,C\n2032-04-22,101.77,B\n2032-04-23,113.41,C\n2032-04-24,89.72,B\n2032-04-25,105.11,B\n2032-04-26,71.12,A\n2032-04-27,93.8,C\n2032-04-28,118.31,B\n2032-04-29,104.71,B\n2032-04-30,82.4,B\n2032-05-01,106.73,C\n2032-05-02,121.44,C\n2032-05-03,38.5,C\n2032-05-04,134.77,B\n2032-05-05,89.91,C\n2032-05-06,112.76,B\n2032-05-07,135.92,C\n2032-05-08,58.85,C\n2032-05-09,78.72,B\n2032-05-10,91.34,C\n2032-05-11,76.49,B\n2032-05-12,152.05,C\n2032-05-13,74.3,C\n2032-05-14,83.33,C\n2032-05-15,106.13,C\n2032-05-16,63.94,A\n2032-05-17,88.13,B\n2032-05-18,109.52,A\n2032-05-19,90.01,C\n2032-05-20,97.2,C\n2032-05-21,84.12,B\n2032-05-22,54.57,B\n2032-05-23,109.65,B\n2032-05-24,152.65,B\n2032-05-25,100.55,C\n2032-05-26,106.76,B\n2032-05-27,120.78,A\n2032-05-28,61.92,B\n2032-05-29,151.08,B\n2032-05-30,106.07,A\n2032-05-31,148.96,A\n2032-06-01,78.01,A\n2032-06-02,154.54,A\n2032-06-03,123.25,B\n2032-06-04,116.59,A\n2032-06-05,107.02,C\n2032-06-06,92.54,B\n2032-06-07,136.02,C\n2032-06-08,104.21,B\n2032-06-09,40.99,B\n2032-06-10,66.48,C\n2032-06-11,94.42,A\n2032-06-12,109.3,A\n2032-06-13,98.3,C\n2032-06-14,136.57,A\n2032-06-15,41.47,B\n2032-06-16,104.31,B\n2032-06-17,45.46,B\n2032-06-18,122.79,A\n2032-06-19,97.17,A\n2032-06-20,112.59,C\n2032-06-21,74.08,B\n2032-06-22,138.38,C\n2032-06-23,131.25,A\n2032-06-24,117.51,C\n2032-06-25,96.11,A\n2032-06-26,117.4,B\n2032-06-27,78.79,B\n2032-06-28,125.67,A\n2032-06-29,149.48,A\n2032-06-30,132.12,A\n2032-07-01,78.11,B\n2032-07-02,110.84,A\n2032-07-03,61.21,B\n2032-07-04,117.17,C\n2032-07-05,113.52,B\n2032-07-06,43.91,C\n2032-07-07,65.15,B\n2032-07-08,91.51,B\n2032-07-09,90.97,A\n2032-07-10,63.72,B\n2032-07-11,111.67,A\n2032-07-12,107.54,A\n2032-07-13,94.17,A\n2032-07-14,77.33,B\n2032-07-15,131.46,A\n2032-07-16,149.66,C\n2032-07-17,85.49,C\n2032-07-18,81.63,B\n2032-07-19,114.74,A\n2032-07-20,89.26,C\n2032-07-21,95.82,B\n2032-07-22,122.19,B\n2032-07-23,42.72,B\n2032-07-24,139.55,B\n2032-07-25,102.18,B\n2032-07-26,87.66,A\n2032-07-27,97.32,A\n2032-07-28,98.87,A\n2032-07-29,48.06,A\n2032-07-30,144.85,B\n2032-07-31,101.24,B\n2032-08-01,113.29,C\n2032-08-02,128.55,C\n2032-08-03,69.37,A\n2032-08-04,114.2,B\n2032-08-05,91.97,C\n2032-08-06,125.4,C\n2032-08-07,36.18,C\n2032-08-08,97.03,C\n2032-08-09,81.92,A\n2032-08-10,112.97,B\n2032-08-11,114.1,A\n2032-08-12,78.77,C\n2032-08-13,78.63,C\n2032-08-14,96.68,B\n2032-08-15,73.1,B\n2032-08-16,125.26,A\n2032-08-17,88.92,B\n2032-08-18,12.79,A\n2032-08-19,88.76,A\n2032-08-20,68.84,C\n2032-08-21,51.06,A\n2032-08-22,62.88,C\n2032-08-23,103.28,A\n2032-08-24,139.86,A\n2032-08-25,109.4,B\n2032-08-26,81.8,B\n2032-08-27,113.68,C\n2032-08-28,86.23,B\n2032-08-29,79.16,A\n2032-08-30,65.37,C\n2032-08-31,47.45,C\n2032-09-01,88.3,C\n2032-09-02,104.74,A\n2032-09-03,97.1,B\n2032-09-04,87.52,C\n2032-09-05,71.63,A\n2032-09-06,118.25,B\n2032-09-07,60.49,B\n2032-09-08,123.28,C\n2032-09-09,69.93,A\n2032-09-10,77.43,B\n2032-09-11,56.0,A\n2032-09-12,84.96,B\n2032-09-13,129.26,C\n2032-09-14,115.47,C\n2032-09-15,129.35,B\n2032-09-16,115.66,A\n2032-09-17,66.89,B\n2032-09-18,90.08,A\n2032-09-19,76.59,B\n2032-09-20,139.92,C\n2032-09-21,64.1,A\n2032-09-22,126.81,C\n2032-09-23,126.79,A\n2032-09-24,154.89,B\n2032-09-25,87.71,C\n2032-09-26,121.37,C\n2032-09-27,168.45,A\n2032-09-28,81.47,A\n2032-09-29,53.95,A\n2032-09-30,43.6,C\n2032-10-01,121.38,B\n2032-10-02,43.51,A\n2032-10-03,88.83,B\n2032-10-04,113.12,A\n2032-10-05,105.56,B\n2032-10-06,112.76,A\n2032-10-07,106.67,B\n2032-10-08,138.37,C\n2032-10-09,71.43,A\n2032-10-10,79.69,A\n2032-10-11,76.82,C\n2032-10-12,124.91,A\n2032-10-13,127.01,A\n2032-10-14,113.54,C\n2032-10-15,135.5,C\n2032-10-16,64.66,B\n2032-10-17,150.02,C\n2032-10-18,145.7,C\n2032-10-19,122.07,A\n2032-10-20,153.45,C\n2032-10-21,50.3,A\n2032-10-22,84.27,C\n2032-10-23,77.94,C\n2032-10-24,121.64,B\n2032-10-25,68.5,C\n2032-10-26,122.72,C\n2032-10-27,141.12,A\n2032-10-28,120.86,A\n2032-10-29,108.51,B\n2032-10-30,70.36,C\n2032-10-31,74.59,A\n2032-11-01,137.49,C\n2032-11-02,123.38,B\n2032-11-03,98.83,A\n2032-11-04,87.46,A\n2032-11-05,39.23,B\n2032-11-06,67.87,C\n2032-11-07,157.34,A\n2032-11-08,61.59,A\n2032-11-09,105.68,B\n2032-11-10,130.22,B\n2032-11-11,62.35,B\n2032-11-12,105.54,C\n2032-11-13,128.14,C\n2032-11-14,100.37,C\n2032-11-15,186.05,B\n2032-11-16,49.94,A\n2032-11-17,131.76,C\n2032-11-18,94.82,B\n2032-11-19,123.16,C\n2032-11-20,113.24,A\n2032-11-21,78.01,A\n2032-11-22,106.87,B\n2032-11-23,44.26,A\n2032-11-24,118.1,B\n2032-11-25,108.94,B\n2032-11-26,119.16,B\n2032-11-27,131.74,B\n2032-11-28,111.03,A\n2032-11-29,104.44,A\n2032-11-30,73.57,A\n2032-12-01,78.63,A\n2032-12-02,135.6,B\n2032-12-03,143.09,B\n2032-12-04,92.86,A\n2032-12-05,101.38,C\n2032-12-06,72.86,C\n2032-12-07,135.18,C\n2032-12-08,119.96,A\n2032-12-09,158.24,B\n2032-12-10,73.65,B\n2032-12-11,88.66,C\n2032-12-12,106.94,C\n2032-12-13,119.39,B\n2032-12-14,93.53,C\n2032-12-15,73.81,C\n2032-12-16,126.44,B\n2032-12-17,121.63,C\n2032-12-18,72.51,C\n2032-12-19,140.66,A\n2032-12-20,135.11,C\n2032-12-21,104.03,A\n2032-12-22,102.39,B\n2032-12-23,116.62,A\n2032-12-24,74.15,B\n2032-12-25,100.9,A\n2032-12-26,35.43,A\n2032-12-27,126.29,A\n2032-12-28,53.16,C\n2032-12-29,145.1,C\n2032-12-30,90.1,C\n2032-12-31,93.65,C\n2033-01-01,81.17,C\n2033-01-02,91.36,A\n2033-01-03,142.56,C\n2033-01-04,25.37,B\n2033-01-05,138.31,A\n2033-01-06,110.14,A\n2033-01-07,63.79,B\n2033-01-08,67.74,C\n2033-01-09,150.29,B\n2033-01-10,71.63,A\n2033-01-11,65.4,A\n2033-01-12,134.12,A\n2033-01-13,110.16,B\n2033-01-14,71.86,C\n2033-01-15,106.5,A\n2033-01-16,69.23,C\n2033-01-17,133.02,A\n2033-01-18,131.84,C\n2033-01-19,115.96,B\n2033-01-20,110.85,A\n2033-01-21,152.84,A\n2033-01-22,99.99,A\n2033-01-23,64.54,B\n2033-01-24,113.48,B\n2033-01-25,162.58,C\n2033-01-26,69.56,B\n2033-01-27,89.16,A\n2033-01-28,112.49,C\n2033-01-29,98.38,B\n2033-01-30,70.53,C\n2033-01-31,133.66,C\n2033-02-01,169.6,C\n2033-02-02,105.88,A\n2033-02-03,72.88,A\n2033-02-04,53.53,B\n2033-02-05,107.74,A\n2033-02-06,133.12,C\n2033-02-07,114.26,C\n2033-02-08,99.93,A\n2033-02-09,82.32,C\n2033-02-10,67.24,A\n2033-02-11,125.04,C\n2033-02-12,127.41,B\n2033-02-13,53.63,B\n2033-02-14,147.7,A\n2033-02-15,117.22,A\n2033-02-16,141.99,A\n2033-02-17,59.74,A\n2033-02-18,59.03,A\n2033-02-19,95.53,C\n2033-02-20,115.08,B\n2033-02-21,153.89,C\n2033-02-22,121.18,B\n2033-02-23,92.72,C\n2033-02-24,69.21,C\n2033-02-25,136.9,C\n2033-02-26,71.06,A\n2033-02-27,148.85,B\n2033-02-28,91.47,A\n2033-03-01,147.72,A\n2033-03-02,120.37,C\n2033-03-03,95.89,B\n2033-03-04,84.42,B\n2033-03-05,89.77,B\n2033-03-06,112.85,B\n2033-03-07,102.31,A\n2033-03-08,82.19,A\n2033-03-09,95.03,B\n2033-03-10,102.36,A\n2033-03-11,36.14,B\n2033-03-12,113.75,A\n2033-03-13,70.61,C\n2033-03-14,65.4,C\n2033-03-15,49.38,B\n2033-03-16,46.18,B\n2033-03-17,59.34,A\n2033-03-18,78.73,B\n2033-03-19,158.6,B\n2033-03-20,84.22,A\n2033-03-21,105.33,B\n2033-03-22,112.01,B\n2033-03-23,103.93,C\n2033-03-24,97.68,C\n2033-03-25,64.14,C\n2033-03-26,143.53,B\n2033-03-27,154.22,A\n2033-03-28,49.52,C\n2033-03-29,69.28,B\n2033-03-30,91.61,C\n2033-03-31,71.06,A\n2033-04-01,115.18,A\n2033-04-02,78.15,A\n2033-04-03,164.95,A\n2033-04-04,135.72,A\n2033-04-05,106.38,A\n2033-04-06,130.81,B\n2033-04-07,133.18,C\n2033-04-08,83.08,A\n2033-04-09,75.51,C\n2033-04-10,102.34,C\n2033-04-11,125.85,B\n2033-04-12,104.17,A\n2033-04-13,52.69,A\n2033-04-14,75.92,B\n2033-04-15,97.78,B\n2033-04-16,97.73,B\n2033-04-17,159.18,B\n2033-04-18,58.42,B\n2033-04-19,115.17,C\n2033-04-20,144.67,B\n2033-04-21,168.14,A\n2033-04-22,87.87,A\n2033-04-23,114.74,B\n2033-04-24,117.09,B\n2033-04-25,105.86,B\n2033-04-26,97.03,B\n2033-04-27,113.08,B\n2033-04-28,24.03,A\n2033-04-29,120.46,C\n2033-04-30,103.79,B\n2033-05-01,93.33,B\n2033-05-02,161.41,B\n2033-05-03,79.75,A\n2033-05-04,87.91,A\n2033-05-05,159.75,C\n2033-05-06,75.04,C\n2033-05-07,83.5,C\n2033-05-08,95.59,C\n2033-05-09,125.22,A\n2033-05-10,106.23,C\n2033-05-11,61.22,B\n2033-05-12,84.03,A\n2033-05-13,81.78,A\n2033-05-14,97.68,A\n2033-05-15,112.78,B\n2033-05-16,112.55,A\n2033-05-17,46.72,B\n2033-05-18,131.92,C\n2033-05-19,107.58,A\n2033-05-20,141.54,B\n2033-05-21,113.33,A\n2033-05-22,133.04,C\n2033-05-23,114.0,A\n2033-05-24,140.39,C\n2033-05-25,115.67,A\n2033-05-26,97.06,B\n2033-05-27,168.17,C\n2033-05-28,126.67,A\n2033-05-29,117.21,C\n2033-05-30,61.71,C\n2033-05-31,61.35,B\n2033-06-01,108.78,B\n2033-06-02,104.37,B\n2033-06-03,81.58,B\n2033-06-04,104.23,C\n2033-06-05,147.66,C\n2033-06-06,120.86,A\n2033-06-07,65.77,A\n2033-06-08,96.65,A\n2033-06-09,75.85,B\n2033-06-10,88.37,C\n2033-06-11,87.59,C\n2033-06-12,85.67,A\n2033-06-13,62.35,A\n2033-06-14,96.17,C\n2033-06-15,83.18,B\n2033-06-16,12.12,A\n2033-06-17,161.59,A\n2033-06-18,132.66,C\n2033-06-19,88.71,C\n2033-06-20,100.56,A\n2033-06-21,64.85,A\n2033-06-22,150.87,B\n2033-06-23,156.92,B\n2033-06-24,104.7,B\n2033-06-25,130.71,B\n2033-06-26,105.26,A\n2033-06-27,59.9,A\n2033-06-28,87.65,A\n2033-06-29,103.96,B\n2033-06-30,86.36,A\n2033-07-01,93.44,C\n2033-07-02,97.26,B\n2033-07-03,97.57,C\n2033-07-04,101.57,B\n2033-07-05,68.18,A\n2033-07-06,78.49,B\n2033-07-07,61.46,C\n2033-07-08,129.33,B\n2033-07-09,36.83,C\n2033-07-10,134.61,A\n2033-07-11,141.59,C\n2033-07-12,90.93,A\n2033-07-13,21.91,C\n2033-07-14,89.16,A\n2033-07-15,98.07,C\n2033-07-16,69.68,A\n2033-07-17,84.54,A\n2033-07-18,145.91,A\n2033-07-19,119.95,C\n2033-07-20,72.26,C\n2033-07-21,52.07,A\n2033-07-22,90.19,B\n2033-07-23,93.6,A\n2033-07-24,114.89,B\n2033-07-25,83.94,A\n2033-07-26,115.34,C\n2033-07-27,158.05,B\n2033-07-28,124.47,C\n2033-07-29,98.56,B\n2033-07-30,94.51,B\n2033-07-31,89.3,B\n2033-08-01,135.42,B\n2033-08-02,81.18,C\n2033-08-03,101.36,B\n2033-08-04,101.54,A\n2033-08-05,84.95,A\n2033-08-06,58.83,B\n2033-08-07,109.69,C\n2033-08-08,98.17,C\n2033-08-09,115.01,B\n2033-08-10,83.99,C\n2033-08-11,136.62,C\n2033-08-12,73.7,C\n2033-08-13,151.36,B\n2033-08-14,47.57,A\n2033-08-15,113.04,A\n2033-08-16,114.26,A\n2033-08-17,76.13,A\n2033-08-18,112.73,A\n2033-08-19,138.39,C\n2033-08-20,33.5,C\n2033-08-21,115.08,C\n2033-08-22,25.2,B\n2033-08-23,71.03,C\n2033-08-24,148.87,A\n2033-08-25,82.55,C\n2033-08-26,64.78,A\n2033-08-27,94.06,C\n2033-08-28,162.04,A\n2033-08-29,45.05,C\n2033-08-30,68.46,A\n2033-08-31,144.92,A\n2033-09-01,155.73,B\n2033-09-02,96.9,C\n2033-09-03,62.83,A\n2033-09-04,162.88,C\n2033-09-05,147.83,A\n2033-09-06,120.37,B\n2033-09-07,75.63,B\n2033-09-08,98.53,B\n2033-09-09,95.19,B\n2033-09-10,109.93,A\n2033-09-11,143.53,C\n2033-09-12,126.38,B\n2033-09-13,67.68,B\n2033-09-14,141.29,B\n2033-09-15,109.39,C\n2033-09-16,120.61,A\n2033-09-17,144.0,A\n2033-09-18,66.65,A\n2033-09-19,98.93,A\n2033-09-20,84.06,C\n2033-09-21,52.96,C\n2033-09-22,110.4,C\n2033-09-23,175.35,B\n2033-09-24,44.8,C\n2033-09-25,99.03,A\n2033-09-26,119.22,C\n2033-09-27,103.69,B\n2033-09-28,96.61,A\n2033-09-29,61.02,C\n2033-09-30,106.98,A\n2033-10-01,77.3,A\n2033-10-02,34.3,C\n2033-10-03,135.84,C\n2033-10-04,128.75,B\n2033-10-05,101.55,C\n2033-10-06,106.87,C\n2033-10-07,132.23,C\n2033-10-08,106.73,A\n2033-10-09,127.12,B\n2033-10-10,91.08,A\n2033-10-11,139.36,C\n2033-10-12,109.62,B\n2033-10-13,105.82,C\n2033-10-14,61.9,A\n2033-10-15,108.61,A\n2033-10-16,75.04,C\n2033-10-17,80.85,B\n2033-10-18,75.54,A\n2033-10-19,68.01,C\n2033-10-20,163.74,C\n2033-10-21,139.94,C\n2033-10-22,157.61,B\n2033-10-23,63.31,C\n2033-10-24,102.14,C\n2033-10-25,61.19,A\n2033-10-26,79.13,C\n2033-10-27,72.46,B\n2033-10-28,137.19,C\n2033-10-29,88.11,B\n2033-10-30,132.05,C\n2033-10-31,118.12,C\n2033-11-01,169.11,C\n2033-11-02,55.62,B\n2033-11-03,137.78,A\n2033-11-04,134.39,B\n2033-11-05,70.8,A\n2033-11-06,130.21,B\n2033-11-07,110.08,C\n2033-11-08,93.18,C\n2033-11-09,75.28,A\n2033-11-10,78.43,A\n2033-11-11,161.49,B\n2033-11-12,100.02,B\n2033-11-13,123.46,A\n2033-11-14,76.27,A\n2033-11-15,76.6,B\n2033-11-16,132.46,A\n2033-11-17,59.51,A\n2033-11-18,87.54,A\n2033-11-19,101.02,A\n2033-11-20,103.34,C\n2033-11-21,94.63,C\n2033-11-22,111.87,A\n2033-11-23,120.79,B\n2033-11-24,122.04,C\n2033-11-25,70.43,B\n2033-11-26,91.48,B\n2033-11-27,135.84,C\n2033-11-28,126.84,B\n2033-11-29,58.81,B\n2033-11-30,110.54,A\n2033-12-01,40.65,A\n2033-12-02,101.41,A\n2033-12-03,156.93,C\n2033-12-04,71.56,A\n2033-12-05,74.97,B\n2033-12-06,122.92,B\n2033-12-07,53.72,B\n2033-12-08,81.02,B\n2033-12-09,117.89,B\n2033-12-10,80.97,C\n2033-12-11,92.92,B\n2033-12-12,122.33,C\n2033-12-13,112.65,B\n2033-12-14,108.0,C\n2033-12-15,89.83,C\n2033-12-16,111.1,B\n2033-12-17,96.29,B\n2033-12-18,113.73,B\n2033-12-19,85.09,B\n2033-12-20,94.52,A\n2033-12-21,70.3,A\n2033-12-22,98.95,C\n2033-12-23,75.67,C\n2033-12-24,66.58,A\n2033-12-25,107.76,C\n2033-12-26,106.38,B\n2033-12-27,123.49,C\n2033-12-28,87.07,C\n2033-12-29,112.24,C\n2033-12-30,116.52,A\n2033-12-31,113.34,A\n2034-01-01,75.23,C\n2034-01-02,104.48,B\n2034-01-03,110.92,C\n2034-01-04,100.22,A\n2034-01-05,37.41,B\n2034-01-06,104.81,C\n2034-01-07,166.38,A\n2034-01-08,89.2,B\n2034-01-09,78.16,C\n2034-01-10,111.2,A\n2034-01-11,109.37,B\n2034-01-12,135.78,C\n2034-01-13,62.26,B\n2034-01-14,121.94,B\n2034-01-15,117.71,B\n2034-01-16,95.74,A\n2034-01-17,110.16,C\n2034-01-18,130.88,B\n2034-01-19,126.17,B\n2034-01-20,65.71,B\n2034-01-21,124.6,A\n2034-01-22,98.06,B\n2034-01-23,102.19,A\n2034-01-24,102.2,A\n2034-01-25,95.49,B\n2034-01-26,135.75,B\n2034-01-27,97.49,A\n2034-01-28,66.41,A\n2034-01-29,88.19,B\n2034-01-30,105.36,A\n2034-01-31,136.88,A\n2034-02-01,118.09,B\n2034-02-02,74.48,B\n2034-02-03,174.56,C\n2034-02-04,107.94,B\n2034-02-05,138.26,B\n2034-02-06,95.45,B\n2034-02-07,116.0,C\n2034-02-08,125.18,A\n2034-02-09,136.55,B\n2034-02-10,131.72,B\n2034-02-11,126.17,C\n2034-02-12,90.55,C\n2034-02-13,82.85,C\n2034-02-14,109.98,A\n2034-02-15,127.99,C\n2034-02-16,93.32,A\n2034-02-17,131.96,B\n2034-02-18,143.58,B\n2034-02-19,72.51,B\n2034-02-20,74.89,C\n2034-02-21,95.79,B\n2034-02-22,109.23,C\n2034-02-23,84.26,A\n2034-02-24,140.57,B\n2034-02-25,112.72,A\n2034-02-26,101.18,C\n2034-02-27,56.92,C\n2034-02-28,60.53,B\n2034-03-01,108.43,B\n2034-03-02,36.02,C\n2034-03-03,130.38,B\n2034-03-04,95.26,B\n2034-03-05,197.29,A\n2034-03-06,169.24,C\n2034-03-07,94.56,A\n2034-03-08,96.81,B\n2034-03-09,129.87,A\n2034-03-10,151.1,C\n2034-03-11,50.86,A\n2034-03-12,46.41,A\n2034-03-13,81.34,B\n2034-03-14,117.48,C\n2034-03-15,114.91,B\n2034-03-16,132.09,B\n2034-03-17,64.01,C\n2034-03-18,30.52,A\n2034-03-19,125.74,B\n2034-03-20,75.15,A\n2034-03-21,39.45,C\n2034-03-22,104.35,B\n2034-03-23,123.8,A\n2034-03-24,96.34,A\n2034-03-25,86.26,B\n2034-03-26,95.22,C\n2034-03-27,88.88,A\n2034-03-28,59.56,C\n2034-03-29,78.56,B\n2034-03-30,115.64,A\n2034-03-31,109.65,A\n2034-04-01,105.04,A\n2034-04-02,70.15,C\n2034-04-03,94.55,A\n2034-04-04,34.69,A\n2034-04-05,105.35,B\n2034-04-06,142.87,A\n2034-04-07,55.78,C\n2034-04-08,82.42,B\n2034-04-09,109.87,B\n2034-04-10,137.59,A\n2034-04-11,86.55,B\n2034-04-12,79.23,A\n2034-04-13,40.93,B\n2034-04-14,103.1,C\n2034-04-15,161.26,A\n2034-04-16,108.32,C\n2034-04-17,99.33,A\n2034-04-18,109.66,C\n2034-04-19,99.67,C\n2034-04-20,75.61,B\n2034-04-21,124.7,A\n2034-04-22,106.77,B\n2034-04-23,100.53,C\n2034-04-24,70.74,A\n2034-04-25,139.11,C\n2034-04-26,119.94,C\n2034-04-27,83.41,C\n2034-04-28,130.05,C\n2034-04-29,85.07,B\n2034-04-30,76.54,C\n2034-05-01,77.21,B\n2034-05-02,46.87,C\n2034-05-03,114.15,A\n2034-05-04,45.07,C\n2034-05-05,63.15,C\n2034-05-06,37.73,A\n2034-05-07,97.42,A\n2034-05-08,95.49,C\n2034-05-09,90.2,C\n2034-05-10,68.72,B\n2034-05-11,64.83,A\n2034-05-12,113.93,A\n2034-05-13,83.48,B\n2034-05-14,109.49,C\n2034-05-15,73.44,C\n2034-05-16,105.43,A\n2034-05-17,139.1,B\n2034-05-18,117.58,C\n2034-05-19,87.63,A\n2034-05-20,107.72,C\n2034-05-21,92.78,C\n2034-05-22,100.24,C\n2034-05-23,90.23,A\n2034-05-24,95.07,B\n2034-05-25,106.36,B\n2034-05-26,70.2,C\n2034-05-27,69.52,B\n2034-05-28,58.34,C\n2034-05-29,51.99,A\n2034-05-30,63.48,A\n2034-05-31,104.78,A\n2034-06-01,73.22,C\n2034-06-02,115.41,B\n2034-06-03,101.04,A\n2034-06-04,40.94,C\n2034-06-05,119.65,B\n2034-06-06,124.32,B\n2034-06-07,70.18,B\n2034-06-08,112.61,C\n2034-06-09,74.31,C\n2034-06-10,102.93,C\n2034-06-11,62.07,A\n2034-06-12,46.21,C\n2034-06-13,88.52,A\n2034-06-14,96.29,A\n2034-06-15,88.07,A\n2034-06-16,64.48,C\n2034-06-17,88.0,A\n2034-06-18,108.08,C\n2034-06-19,98.65,C\n2034-06-20,105.48,A\n2034-06-21,79.9,A\n2034-06-22,135.15,A\n2034-06-23,64.71,C\n2034-06-24,114.63,C\n2034-06-25,135.46,A\n2034-06-26,114.43,A\n2034-06-27,93.92,B\n2034-06-28,84.15,A\n2034-06-29,131.08,B\n2034-06-30,57.31,B\n2034-07-01,70.9,C\n2034-07-02,88.07,C\n2034-07-03,137.13,B\n2034-07-04,111.39,B\n2034-07-05,70.96,A\n2034-07-06,90.69,B\n2034-07-07,81.57,A\n2034-07-08,78.71,C\n2034-07-09,129.37,A\n2034-07-10,58.85,C\n2034-07-11,148.26,B\n2034-07-12,124.85,B\n2034-07-13,132.73,B\n2034-07-14,86.11,A\n2034-07-15,95.5,C\n2034-07-16,57.01,C\n2034-07-17,84.26,C\n2034-07-18,114.81,C\n2034-07-19,138.88,C\n2034-07-20,66.79,A\n2034-07-21,89.03,C\n2034-07-22,95.46,B\n2034-07-23,58.13,C\n2034-07-24,117.74,B\n2034-07-25,78.28,A\n2034-07-26,94.14,A\n2034-07-27,86.44,B\n2034-07-28,76.32,C\n2034-07-29,99.79,A\n2034-07-30,93.53,A\n2034-07-31,88.84,A\n2034-08-01,78.15,C\n2034-08-02,109.59,B\n2034-08-03,149.61,B\n2034-08-04,104.2,C\n2034-08-05,129.88,A\n2034-08-06,58.29,C\n2034-08-07,104.73,C\n2034-08-08,67.08,B\n2034-08-09,55.69,C\n2034-08-10,77.82,C\n2034-08-11,135.76,A\n2034-08-12,128.5,A\n2034-08-13,107.29,B\n2034-08-14,54.02,A\n2034-08-15,94.11,C\n2034-08-16,109.05,C\n2034-08-17,105.27,C\n2034-08-18,44.69,B\n2034-08-19,73.29,A\n2034-08-20,98.08,C\n2034-08-21,116.09,A\n2034-08-22,34.12,A\n2034-08-23,95.14,A\n2034-08-24,104.29,A\n2034-08-25,125.49,B\n2034-08-26,120.79,B\n2034-08-27,117.48,B\n2034-08-28,76.0,C\n2034-08-29,42.76,C\n2034-08-30,88.21,C\n2034-08-31,130.05,B\n2034-09-01,141.8,A\n2034-09-02,121.32,B\n2034-09-03,112.88,C\n2034-09-04,111.39,C\n2034-09-05,83.32,A\n2034-09-06,96.1,B\n2034-09-07,150.07,A\n2034-09-08,71.72,B\n2034-09-09,148.44,C\n2034-09-10,90.34,A\n2034-09-11,139.76,A\n2034-09-12,57.8,B\n2034-09-13,117.58,B\n2034-09-14,77.9,B\n2034-09-15,57.54,C\n2034-09-16,106.56,B\n2034-09-17,130.28,C\n2034-09-18,74.65,B\n2034-09-19,55.03,B\n2034-09-20,97.24,C\n2034-09-21,97.38,B\n2034-09-22,116.45,C\n2034-09-23,128.49,C\n2034-09-24,98.22,B\n2034-09-25,155.67,B\n2034-09-26,106.73,A\n2034-09-27,89.61,B\n2034-09-28,51.65,A\n2034-09-29,92.12,B\n2034-09-30,89.91,C\n2034-10-01,128.81,C\n2034-10-02,113.87,A\n2034-10-03,64.62,B\n2034-10-04,105.17,A\n2034-10-05,120.15,B\n2034-10-06,145.84,B\n2034-10-07,126.63,C\n2034-10-08,122.83,A\n2034-10-09,101.8,C\n2034-10-10,111.65,C\n2034-10-11,137.49,C\n2034-10-12,60.02,A\n2034-10-13,89.52,B\n2034-10-14,76.82,B\n2034-10-15,111.37,A\n2034-10-16,136.91,B\n2034-10-17,82.09,A\n2034-10-18,28.29,A\n2034-10-19,87.63,B\n2034-10-20,127.4,C\n2034-10-21,116.13,A\n2034-10-22,112.85,A\n2034-10-23,91.6,A\n2034-10-24,61.63,A\n2034-10-25,115.46,B\n2034-10-26,74.97,B\n2034-10-27,165.52,A\n2034-10-28,117.12,C\n2034-10-29,82.58,B\n2034-10-30,81.66,C\n2034-10-31,97.21,C\n2034-11-01,92.81,A\n2034-11-02,133.51,B\n2034-11-03,105.59,A\n2034-11-04,78.05,A\n2034-11-05,156.71,C\n2034-11-06,101.48,C\n2034-11-07,123.07,C\n2034-11-08,81.75,A\n2034-11-09,110.89,A\n2034-11-10,109.33,A\n2034-11-11,43.31,A\n2034-11-12,160.46,A\n2034-11-13,138.72,A\n2034-11-14,88.08,A\n2034-11-15,66.93,A\n2034-11-16,110.43,B\n2034-11-17,102.6,B\n2034-11-18,110.67,B\n2034-11-19,105.75,B\n2034-11-20,115.19,A\n2034-11-21,143.42,A\n2034-11-22,117.04,C\n2034-11-23,68.51,C\n2034-11-24,140.88,B\n2034-11-25,149.22,C\n2034-11-26,194.56,B\n2034-11-27,66.3,A\n2034-11-28,107.29,A\n2034-11-29,37.54,C\n2034-11-30,116.59,A\n2034-12-01,83.55,A\n2034-12-02,157.7,B\n2034-12-03,76.76,C\n2034-12-04,49.32,A\n2034-12-05,85.86,C\n2034-12-06,40.74,A\n2034-12-07,122.53,B\n2034-12-08,38.05,A\n2034-12-09,100.85,C\n2034-12-10,37.67,C\n2034-12-11,90.39,B\n2034-12-12,149.3,A\n2034-12-13,110.82,B\n2034-12-14,74.1,C\n2034-12-15,99.06,B\n2034-12-16,100.54,A\n2034-12-17,114.18,A\n2034-12-18,58.99,B\n2034-12-19,117.78,C\n2034-12-20,18.87,C\n2034-12-21,81.1,B\n2034-12-22,85.35,A\n2034-12-23,119.0,A\n2034-12-24,100.68,A\n2034-12-25,57.83,A\n2034-12-26,70.45,C\n2034-12-27,105.96,A\n2034-12-28,97.63,B\n2034-12-29,82.63,B\n2034-12-30,85.57,B\n2034-12-31,120.89,B\n2035-01-01,84.47,A\n2035-01-02,97.36,C\n2035-01-03,165.1,A\n2035-01-04,57.29,C\n2035-01-05,44.44,A\n2035-01-06,123.51,C\n2035-01-07,79.64,B\n2035-01-08,90.45,B\n2035-01-09,76.57,C\n2035-01-10,92.21,C\n2035-01-11,147.96,C\n2035-01-12,124.06,B\n2035-01-13,125.87,B\n2035-01-14,57.51,A\n2035-01-15,65.47,C\n2035-01-16,14.83,C\n2035-01-17,75.72,C\n2035-01-18,128.7,B\n2035-01-19,115.51,C\n2035-01-20,93.35,C\n2035-01-21,99.33,B\n2035-01-22,79.33,C\n2035-01-23,106.14,A\n2035-01-24,122.98,B\n2035-01-25,51.52,B\n2035-01-26,108.99,B\n2035-01-27,127.97,A\n2035-01-28,87.55,B\n2035-01-29,121.02,B\n2035-01-30,184.44,B\n2035-01-31,96.87,B\n2035-02-01,111.8,A\n2035-02-02,77.86,C\n2035-02-03,105.44,C\n2035-02-04,113.81,B\n2035-02-05,105.85,A\n2035-02-06,69.41,B\n2035-02-07,106.19,C\n2035-02-08,143.87,C\n2035-02-09,113.46,A\n2035-02-10,128.86,C\n2035-02-11,92.89,B\n2035-02-12,122.5,A\n2035-02-13,108.38,C\n2035-02-14,71.47,B\n2035-02-15,109.56,A\n2035-02-16,70.25,B\n2035-02-17,81.01,C\n2035-02-18,105.68,B\n2035-02-19,108.92,A\n2035-02-20,64.32,B\n2035-02-21,131.62,C\n2035-02-22,117.16,A\n2035-02-23,78.86,A\n2035-02-24,53.87,C\n2035-02-25,95.65,B\n2035-02-26,86.62,B\n2035-02-27,103.39,C\n2035-02-28,89.11,A\n2035-03-01,39.78,C\n2035-03-02,88.35,A\n2035-03-03,90.83,A\n2035-03-04,119.01,B\n2035-03-05,79.05,C\n2035-03-06,150.36,A\n2035-03-07,42.65,B\n2035-03-08,139.34,C\n2035-03-09,93.27,C\n2035-03-10,125.79,A\n2035-03-11,102.39,A\n2035-03-12,134.19,A\n2035-03-13,88.38,C\n2035-03-14,67.04,A\n2035-03-15,142.62,C\n2035-03-16,96.6,B\n2035-03-17,106.65,C\n2035-03-18,137.04,A\n2035-03-19,69.04,C\n2035-03-20,117.86,A\n2035-03-21,82.3,B\n2035-03-22,96.03,A\n2035-03-23,102.17,A\n2035-03-24,54.99,A\n2035-03-25,76.63,A\n2035-03-26,140.53,A\n2035-03-27,126.59,A\n2035-03-28,98.12,B\n2035-03-29,107.29,B\n2035-03-30,91.17,C\n2035-03-31,140.7,A\n2035-04-01,93.99,C\n2035-04-02,106.96,B\n2035-04-03,134.38,B\n2035-04-04,76.68,A\n2035-04-05,146.76,A\n2035-04-06,54.09,C\n2035-04-07,148.89,B\n2035-04-08,88.82,C\n2035-04-09,92.72,C\n2035-04-10,43.3,C\n2035-04-11,86.36,A\n2035-04-12,68.81,B\n2035-04-13,81.77,C\n2035-04-14,95.88,B\n2035-04-15,128.73,B\n2035-04-16,106.23,C\n2035-04-17,109.23,C\n2035-04-18,67.13,A\n2035-04-19,73.58,B\n2035-04-20,153.96,C\n2035-04-21,77.94,C\n2035-04-22,149.69,B\n2035-04-23,92.71,C\n2035-04-24,100.07,B\n2035-04-25,132.11,C\n2035-04-26,138.16,B\n2035-04-27,96.68,C\n2035-04-28,88.35,C\n2035-04-29,98.32,A\n2035-04-30,110.14,B\n2035-05-01,126.74,A\n2035-05-02,100.13,C\n2035-05-03,118.57,B\n2035-05-04,149.15,B\n2035-05-05,111.44,C\n2035-05-06,109.35,C\n2035-05-07,54.43,C\n2035-05-08,21.08,C\n2035-05-09,111.65,B\n2035-05-10,97.16,A\n2035-05-11,84.56,B\n2035-05-12,107.31,B\n2035-05-13,98.19,B\n2035-05-14,133.83,B\n2035-05-15,102.58,C\n2035-05-16,77.59,A\n2035-05-17,129.7,B\n2035-05-18,93.49,B\n2035-05-19,115.8,A\n2035-05-20,85.68,C\n2035-05-21,80.51,B\n2035-05-22,102.53,C\n2035-05-23,100.38,B\n2035-05-24,114.39,B\n2035-05-25,91.94,A\n2035-05-26,44.91,A\n2035-05-27,87.02,C\n2035-05-28,123.81,B\n2035-05-29,91.08,C\n2035-05-30,92.61,A\n2035-05-31,129.59,A\n2035-06-01,78.64,A\n2035-06-02,53.72,A\n2035-06-03,95.18,A\n2035-06-04,85.47,C\n2035-06-05,71.01,C\n2035-06-06,94.74,A\n2035-06-07,114.75,B\n2035-06-08,99.72,B\n2035-06-09,109.21,B\n2035-06-10,154.4,B\n2035-06-11,89.71,B\n2035-06-12,108.35,A\n2035-06-13,84.72,C\n2035-06-14,74.61,A\n2035-06-15,82.45,B\n2035-06-16,85.64,B\n2035-06-17,110.78,C\n2035-06-18,170.47,A\n2035-06-19,78.52,C\n2035-06-20,87.5,B\n2035-06-21,98.11,B\n2035-06-22,111.87,C\n2035-06-23,107.91,B\n2035-06-24,138.52,A\n2035-06-25,27.21,B\n2035-06-26,28.39,B\n2035-06-27,85.12,A\n2035-06-28,132.92,B\n2035-06-29,53.03,C\n2035-06-30,9.77,C\n2035-07-01,117.14,B\n2035-07-02,107.31,A\n2035-07-03,67.29,A\n2035-07-04,70.43,C\n2035-07-05,70.91,B\n2035-07-06,103.32,A\n2035-07-07,81.21,A\n2035-07-08,114.82,C\n2035-07-09,14.36,C\n2035-07-10,74.29,A\n2035-07-11,66.22,C\n2035-07-12,92.31,A\n2035-07-13,83.86,B\n2035-07-14,123.94,C\n2035-07-15,44.22,C\n2035-07-16,101.36,B\n2035-07-17,116.75,A\n2035-07-18,155.7,A\n2035-07-19,85.55,C\n2035-07-20,109.73,A\n2035-07-21,45.88,A\n2035-07-22,28.24,B\n2035-07-23,125.34,B\n2035-07-24,99.83,B\n2035-07-25,135.36,C\n2035-07-26,58.8,B\n2035-07-27,115.6,B\n2035-07-28,156.06,A\n2035-07-29,112.59,B\n2035-07-30,108.3,C\n2035-07-31,100.26,B\n2035-08-01,52.87,C\n2035-08-02,140.67,A\n2035-08-03,81.67,A\n2035-08-04,115.13,B\n2035-08-05,53.11,A\n2035-08-06,64.62,B\n2035-08-07,143.05,A\n2035-08-08,89.58,C\n2035-08-09,143.6,A\n2035-08-10,97.65,B\n2035-08-11,44.74,B\n2035-08-12,126.76,B\n2035-08-13,82.3,C\n2035-08-14,120.96,A\n2035-08-15,100.76,B\n2035-08-16,53.71,C\n2035-08-17,104.93,C\n2035-08-18,165.89,C\n2035-08-19,114.79,A\n2035-08-20,98.27,A\n2035-08-21,132.26,A\n2035-08-22,106.32,C\n2035-08-23,106.29,A\n2035-08-24,67.25,B\n2035-08-25,107.02,C\n2035-08-26,131.06,A\n2035-08-27,74.92,C\n2035-08-28,142.73,C\n2035-08-29,97.25,A\n2035-08-30,73.37,C\n2035-08-31,129.93,A\n2035-09-01,91.4,A\n2035-09-02,122.79,A\n2035-09-03,91.25,C\n2035-09-04,98.21,C\n2035-09-05,114.81,A\n2035-09-06,108.73,C\n2035-09-07,40.13,A\n2035-09-08,66.36,B\n2035-09-09,127.88,C\n2035-09-10,96.04,C\n2035-09-11,84.29,B\n2035-09-12,59.24,B\n2035-09-13,67.61,B\n2035-09-14,39.35,A\n2035-09-15,96.96,A\n2035-09-16,82.05,B\n2035-09-17,71.0,A\n2035-09-18,97.96,C\n2035-09-19,110.92,B\n2035-09-20,162.8,C\n2035-09-21,54.64,A\n2035-09-22,134.78,B\n2035-09-23,129.19,A\n2035-09-24,76.63,C\n2035-09-25,106.1,A\n2035-09-26,86.27,C\n2035-09-27,105.57,A\n2035-09-28,145.27,C\n2035-09-29,118.17,A\n2035-09-30,115.26,B\n2035-10-01,87.09,B\n2035-10-02,55.48,A\n2035-10-03,93.75,C\n2035-10-04,136.2,A\n2035-10-05,54.18,B\n2035-10-06,68.35,C\n2035-10-07,103.79,A\n2035-10-08,70.46,A\n2035-10-09,76.22,C\n2035-10-10,87.29,A\n2035-10-11,59.03,B\n2035-10-12,70.78,B\n2035-10-13,121.1,C\n2035-10-14,60.78,A\n2035-10-15,52.06,A\n2035-10-16,81.1,B\n2035-10-17,121.96,C\n2035-10-18,94.09,C\n2035-10-19,86.06,C\n2035-10-20,101.33,B\n2035-10-21,47.63,B\n2035-10-22,114.84,C\n2035-10-23,111.5,C\n2035-10-24,87.72,A\n2035-10-25,130.18,A\n2035-10-26,66.18,C\n2035-10-27,133.84,B\n2035-10-28,104.63,B\n2035-10-29,89.9,A\n2035-10-30,99.4,C\n2035-10-31,96.78,B\n2035-11-01,136.61,B\n2035-11-02,67.7,B\n2035-11-03,77.12,B\n2035-11-04,131.09,A\n2035-11-05,82.13,B\n2035-11-06,127.02,B\n2035-11-07,150.98,C\n2035-11-08,87.95,C\n2035-11-09,120.44,A\n2035-11-10,65.39,A\n2035-11-11,122.64,A\n2035-11-12,105.98,A\n2035-11-13,114.19,B\n2035-11-14,80.63,C\n2035-11-15,50.17,B\n2035-11-16,115.1,B\n2035-11-17,38.1,B\n2035-11-18,90.48,C\n2035-11-19,128.24,A\n2035-11-20,105.76,A\n2035-11-21,158.45,C\n2035-11-22,130.75,C\n2035-11-23,80.38,A\n2035-11-24,100.82,B\n2035-11-25,98.58,A\n2035-11-26,52.53,C\n2035-11-27,71.79,A\n2035-11-28,83.18,A\n2035-11-29,38.91,A\n2035-11-30,101.42,C\n2035-12-01,92.53,C\n2035-12-02,49.81,A\n2035-12-03,112.13,A\n2035-12-04,121.48,B\n2035-12-05,106.81,B\n2035-12-06,90.28,A\n2035-12-07,117.74,C\n2035-12-08,120.41,B\n2035-12-09,109.22,A\n2035-12-10,75.26,B\n2035-12-11,108.3,B\n2035-12-12,135.86,B\n2035-12-13,82.1,C\n2035-12-14,162.94,A\n2035-12-15,84.23,C\n2035-12-16,100.97,C\n2035-12-17,79.83,B\n2035-12-18,106.7,A\n2035-12-19,47.73,B\n2035-12-20,83.62,C\n2035-12-21,67.24,C\n2035-12-22,104.06,A\n2035-12-23,129.83,C\n2035-12-24,119.38,C\n2035-12-25,28.85,A\n2035-12-26,161.26,B\n2035-12-27,108.86,A\n2035-12-28,76.71,C\n2035-12-29,104.71,A\n2035-12-30,105.29,B\n2035-12-31,79.99,C\n2036-01-01,105.94,B\n2036-01-02,81.78,B\n2036-01-03,87.87,A\n2036-01-04,79.41,B\n2036-01-05,57.02,A\n2036-01-06,104.38,A\n2036-01-07,117.56,B\n2036-01-08,115.45,A\n2036-01-09,124.6,A\n2036-01-10,109.73,B\n2036-01-11,84.02,C\n2036-01-12,125.19,A\n2036-01-13,59.27,A\n2036-01-14,72.98,B\n2036-01-15,71.07,A\n2036-01-16,132.4,B\n2036-01-17,128.28,C\n2036-01-18,114.62,C\n2036-01-19,132.16,B\n2036-01-20,73.62,C\n2036-01-21,110.46,B\n2036-01-22,96.24,A\n2036-01-23,125.41,C\n2036-01-24,96.54,A\n2036-01-25,88.45,A\n2036-01-26,116.23,B\n2036-01-27,126.29,C\n2036-01-28,79.95,C\n2036-01-29,98.17,A\n2036-01-30,68.25,A\n2036-01-31,25.06,B\n2036-02-01,106.01,C\n2036-02-02,118.27,B\n2036-02-03,107.86,B\n2036-02-04,41.51,B\n2036-02-05,89.47,C\n2036-02-06,33.45,B\n2036-02-07,141.09,C\n2036-02-08,67.42,B\n2036-02-09,137.66,A\n2036-02-10,128.38,B\n2036-02-11,72.98,B\n2036-02-12,66.02,C\n2036-02-13,110.51,A\n2036-02-14,74.67,A\n2036-02-15,103.07,C\n2036-02-16,85.52,C\n2036-02-17,46.57,A\n2036-02-18,33.09,C\n2036-02-19,106.8,A\n2036-02-20,83.15,B\n2036-02-21,83.41,B\n2036-02-22,120.88,A\n2036-02-23,135.2,A\n2036-02-24,91.82,B\n2036-02-25,89.78,B\n2036-02-26,115.0,A\n2036-02-27,59.29,C\n2036-02-28,106.69,C\n2036-02-29,116.56,A\n2036-03-01,120.41,C\n2036-03-02,41.9,B\n2036-03-03,77.16,B\n2036-03-04,73.65,C\n2036-03-05,103.0,A\n2036-03-06,114.42,B\n2036-03-07,144.9,B\n2036-03-08,85.15,B\n2036-03-09,85.07,C\n2036-03-10,134.49,A\n2036-03-11,108.9,C\n2036-03-12,138.03,A\n2036-03-13,108.66,A\n2036-03-14,65.29,C\n2036-03-15,60.01,B\n2036-03-16,53.45,A\n2036-03-17,119.86,C\n2036-03-18,135.66,B\n2036-03-19,155.48,A\n2036-03-20,125.29,A\n2036-03-21,134.89,B\n2036-03-22,82.6,A\n2036-03-23,65.31,C\n2036-03-24,125.5,A\n2036-03-25,112.53,A\n2036-03-26,103.22,B\n2036-03-27,111.62,C\n2036-03-28,109.7,B\n2036-03-29,138.51,C\n2036-03-30,138.85,B\n2036-03-31,116.33,A\n2036-04-01,125.37,C\n2036-04-02,115.42,C\n2036-04-03,86.16,A\n2036-04-04,122.7,B\n2036-04-05,58.17,C\n2036-04-06,131.62,A\n2036-04-07,64.16,A\n2036-04-08,108.57,C\n2036-04-09,74.65,C\n2036-04-10,116.6,C\n2036-04-11,98.98,A\n2036-04-12,52.16,B\n2036-04-13,112.62,B\n2036-04-14,82.66,C\n2036-04-15,126.43,A\n2036-04-16,100.04,A\n2036-04-17,111.86,C\n2036-04-18,71.27,A\n2036-04-19,82.32,A\n2036-04-20,86.24,A\n2036-04-21,125.7,C\n2036-04-22,150.32,C\n2036-04-23,75.84,C\n2036-04-24,132.31,B\n2036-04-25,34.91,B\n2036-04-26,64.17,C\n2036-04-27,71.24,A\n2036-04-28,59.42,A\n2036-04-29,52.49,C\n2036-04-30,112.39,A\n2036-05-01,93.58,A\n2036-05-02,111.38,B\n2036-05-03,120.71,A\n2036-05-04,107.97,A\n2036-05-05,119.73,C\n2036-05-06,97.91,B\n2036-05-07,64.39,B\n2036-05-08,131.73,A\n2036-05-09,82.33,C\n2036-05-10,71.93,B\n2036-05-11,94.66,B\n2036-05-12,72.62,A\n2036-05-13,87.48,B\n2036-05-14,110.32,C\n2036-05-15,110.45,C\n2036-05-16,73.07,B\n2036-05-17,85.37,C\n2036-05-18,132.23,C\n2036-05-19,114.89,A\n2036-05-20,162.25,B\n2036-05-21,135.4,B\n2036-05-22,134.33,A\n2036-05-23,58.36,A\n2036-05-24,122.92,A\n2036-05-25,98.41,A\n2036-05-26,83.47,C\n2036-05-27,105.04,C\n2036-05-28,128.36,A\n2036-05-29,115.02,B\n2036-05-30,73.65,B\n2036-05-31,122.27,B\n2036-06-01,80.87,A\n2036-06-02,106.5,C\n2036-06-03,90.9,C\n2036-06-04,93.43,C\n2036-06-05,122.99,C\n2036-06-06,68.79,B\n2036-06-07,158.36,B\n2036-06-08,45.37,C\n2036-06-09,82.07,A\n2036-06-10,111.11,C\n2036-06-11,117.53,C\n2036-06-12,96.99,A\n2036-06-13,98.28,C\n2036-06-14,112.32,B\n2036-06-15,93.07,A\n2036-06-16,141.8,A\n2036-06-17,125.18,C\n2036-06-18,126.18,B\n2036-06-19,71.02,B\n2036-06-20,97.62,B\n2036-06-21,90.99,C\n2036-06-22,69.83,C\n2036-06-23,110.8,B\n2036-06-24,83.87,C\n2036-06-25,119.66,B\n2036-06-26,67.89,C\n2036-06-27,151.57,A\n2036-06-28,73.11,A\n2036-06-29,106.58,C\n2036-06-30,123.76,C\n2036-07-01,82.88,B\n2036-07-02,125.57,A\n2036-07-03,63.12,B\n2036-07-04,143.56,C\n2036-07-05,100.42,B\n2036-07-06,82.11,B\n2036-07-07,91.02,C\n2036-07-08,124.27,A\n2036-07-09,82.47,B\n2036-07-10,77.52,C\n2036-07-11,93.35,B\n2036-07-12,64.69,C\n2036-07-13,154.62,C\n2036-07-14,83.01,C\n2036-07-15,124.95,A\n2036-07-16,106.28,A\n2036-07-17,70.46,C\n2036-07-18,99.82,A\n2036-07-19,123.52,B\n2036-07-20,94.35,A\n2036-07-21,135.79,A\n2036-07-22,130.65,B\n2036-07-23,127.57,B\n2036-07-24,86.21,B\n2036-07-25,67.6,C\n2036-07-26,81.59,B\n2036-07-27,105.89,B\n2036-07-28,116.37,B\n2036-07-29,34.85,C\n2036-07-30,99.76,A\n2036-07-31,104.74,C\n2036-08-01,91.67,A\n2036-08-02,138.74,B\n2036-08-03,112.65,C\n2036-08-04,139.31,C\n2036-08-05,93.52,A\n2036-08-06,128.12,C\n2036-08-07,149.05,C\n2036-08-08,139.35,A\n2036-08-09,67.24,C\n2036-08-10,92.39,C\n2036-08-11,89.65,B\n2036-08-12,114.68,B\n2036-08-13,120.59,B\n2036-08-14,74.83,B\n2036-08-15,102.04,B\n2036-08-16,39.4,C\n2036-08-17,143.26,C\n2036-08-18,84.63,B\n2036-08-19,101.68,A\n2036-08-20,90.71,B\n2036-08-21,134.38,C\n2036-08-22,14.87,A\n2036-08-23,99.5,B\n2036-08-24,96.83,A\n2036-08-25,86.39,B\n2036-08-26,151.79,C\n2036-08-27,73.38,B\n2036-08-28,123.15,A\n2036-08-29,110.69,C\n2036-08-30,137.85,B\n2036-08-31,79.98,B\n2036-09-01,78.01,B\n2036-09-02,132.88,B\n2036-09-03,112.76,C\n2036-09-04,53.65,C\n2036-09-05,83.82,B\n2036-09-06,111.59,C\n2036-09-07,125.13,A\n2036-09-08,79.62,C\n2036-09-09,12.57,A\n2036-09-10,115.7,A\n2036-09-11,63.39,C\n2036-09-12,39.48,B\n2036-09-13,15.03,C\n2036-09-14,124.42,A\n2036-09-15,103.86,A\n2036-09-16,93.23,C\n2036-09-17,141.67,A\n2036-09-18,119.12,C\n2036-09-19,148.49,C\n2036-09-20,58.22,B\n2036-09-21,98.96,A\n2036-09-22,51.5,C\n2036-09-23,93.12,A\n2036-09-24,92.54,C\n2036-09-25,118.8,C\n2036-09-26,101.37,B\n2036-09-27,118.7,C\n2036-09-28,121.1,B\n2036-09-29,164.65,A\n2036-09-30,90.54,B\n2036-10-01,71.77,B\n2036-10-02,107.38,A\n2036-10-03,75.93,B\n2036-10-04,144.46,B\n2036-10-05,91.5,C\n2036-10-06,83.03,B\n2036-10-07,85.86,C\n2036-10-08,121.55,B\n2036-10-09,95.27,A\n2036-10-10,80.75,A\n2036-10-11,149.96,B\n2036-10-12,74.31,C\n2036-10-13,101.07,B\n2036-10-14,119.57,C\n2036-10-15,51.06,B\n2036-10-16,114.41,A\n2036-10-17,68.35,A\n2036-10-18,124.68,C\n2036-10-19,71.12,B\n2036-10-20,120.78,C\n2036-10-21,133.17,A\n2036-10-22,125.84,C\n2036-10-23,107.54,B\n2036-10-24,93.06,C\n2036-10-25,82.71,B\n2036-10-26,144.13,B\n2036-10-27,79.02,A\n2036-10-28,68.89,B\n2036-10-29,119.96,B\n2036-10-30,76.12,B\n2036-10-31,71.5,C\n2036-11-01,56.69,A\n2036-11-02,85.51,C\n2036-11-03,97.52,C\n2036-11-04,107.29,A\n2036-11-05,128.54,C\n2036-11-06,115.81,C\n2036-11-07,94.27,B\n2036-11-08,94.05,C\n2036-11-09,115.3,A\n2036-11-10,138.18,C\n2036-11-11,103.79,B\n2036-11-12,85.93,C\n2036-11-13,70.21,A\n2036-11-14,56.64,B\n2036-11-15,119.99,C\n2036-11-16,117.34,A\n2036-11-17,117.32,A\n2036-11-18,96.14,C\n2036-11-19,142.21,C\n2036-11-20,85.48,B\n2036-11-21,128.66,C\n2036-11-22,98.84,B\n2036-11-23,81.75,C\n2036-11-24,117.78,A\n2036-11-25,112.29,A\n2036-11-26,110.71,A\n2036-11-27,102.44,A\n2036-11-28,98.86,B\n2036-11-29,171.65,A\n2036-11-30,135.53,B\n2036-12-01,121.68,B\n2036-12-02,103.35,B\n2036-12-03,130.21,C\n2036-12-04,115.54,B\n2036-12-05,100.79,C\n2036-12-06,90.89,B\n2036-12-07,97.57,C\n2036-12-08,4.89,C\n2036-12-09,106.32,A\n2036-12-10,85.35,B\n2036-12-11,147.78,C\n2036-12-12,100.58,A\n2036-12-13,64.33,B\n2036-12-14,98.5,A\n2036-12-15,68.62,C\n2036-12-16,114.2,A\n2036-12-17,108.73,A\n2036-12-18,128.74,B\n2036-12-19,76.27,C\n2036-12-20,101.02,B\n2036-12-21,59.05,B\n2036-12-22,121.04,A\n2036-12-23,115.57,C\n2036-12-24,114.87,B\n2036-12-25,100.05,C\n2036-12-26,112.44,B\n2036-12-27,148.13,A\n2036-12-28,91.45,B\n2036-12-29,104.16,B\n2036-12-30,67.42,B\n2036-12-31,73.95,C\n2037-01-01,102.93,A\n2037-01-02,133.12,C\n2037-01-03,92.31,B\n2037-01-04,92.92,A\n2037-01-05,87.79,C\n2037-01-06,83.96,B\n2037-01-07,108.85,A\n2037-01-08,55.08,A\n2037-01-09,138.43,B\n2037-01-10,80.04,A\n2037-01-11,121.66,A\n2037-01-12,61.9,C\n2037-01-13,120.07,A\n2037-01-14,120.16,C\n2037-01-15,111.53,C\n2037-01-16,70.17,C\n2037-01-17,107.49,C\n2037-01-18,64.51,A\n2037-01-19,92.42,A\n2037-01-20,130.88,C\n2037-01-21,123.9,A\n2037-01-22,100.46,C\n2037-01-23,70.76,C\n2037-01-24,119.37,C\n2037-01-25,100.58,B\n2037-01-26,30.39,A\n2037-01-27,123.48,A\n2037-01-28,99.27,C\n2037-01-29,136.07,A\n2037-01-30,67.14,C\n2037-01-31,4.7,A\n2037-02-01,108.37,C\n2037-02-02,108.75,C\n2037-02-03,49.28,B\n2037-02-04,70.65,C\n2037-02-05,182.66,B\n2037-02-06,90.83,A\n2037-02-07,121.93,A\n2037-02-08,80.48,B\n2037-02-09,37.29,A\n2037-02-10,94.89,B\n2037-02-11,92.03,A\n2037-02-12,88.0,B\n2037-02-13,96.56,B\n2037-02-14,107.93,C\n2037-02-15,95.5,A\n2037-02-16,62.08,C\n2037-02-17,114.59,B\n2037-02-18,41.52,C\n2037-02-19,38.49,B\n2037-02-20,122.18,A\n2037-02-21,115.21,A\n2037-02-22,98.28,A\n2037-02-23,58.14,B\n2037-02-24,78.76,C\n2037-02-25,92.12,A\n2037-02-26,52.12,B\n2037-02-27,77.83,C\n2037-02-28,119.57,B\n2037-03-01,142.82,B\n2037-03-02,87.42,A\n2037-03-03,122.69,B\n2037-03-04,42.58,B\n2037-03-05,119.78,C\n2037-03-06,75.88,A\n2037-03-07,109.22,B\n2037-03-08,92.57,B\n2037-03-09,139.61,C\n2037-03-10,73.77,B\n2037-03-11,118.83,B\n2037-03-12,91.61,B\n2037-03-13,95.25,B\n2037-03-14,94.48,B\n2037-03-15,74.63,C\n2037-03-16,99.58,B\n2037-03-17,98.12,B\n2037-03-18,107.24,B\n2037-03-19,102.54,B\n2037-03-20,123.94,A\n2037-03-21,127.71,A\n2037-03-22,68.48,A\n2037-03-23,126.06,A\n2037-03-24,64.41,C\n2037-03-25,117.64,A\n2037-03-26,118.59,C\n2037-03-27,42.66,A\n2037-03-28,102.79,C\n2037-03-29,85.95,A\n2037-03-30,77.24,B\n2037-03-31,140.53,A\n2037-04-01,80.39,A\n2037-04-02,75.81,A\n2037-04-03,102.27,B\n2037-04-04,120.57,C\n2037-04-05,95.75,C\n2037-04-06,134.99,B\n2037-04-07,115.93,A\n2037-04-08,34.65,A\n2037-04-09,84.12,A\n2037-04-10,83.14,C\n2037-04-11,114.38,C\n2037-04-12,21.26,B\n2037-04-13,176.47,B\n2037-04-14,115.93,C\n2037-04-15,104.47,A\n2037-04-16,135.45,A\n2037-04-17,170.31,A\n2037-04-18,126.12,A\n2037-04-19,128.46,B\n2037-04-20,103.92,B\n2037-04-21,95.75,C\n2037-04-22,85.73,B\n2037-04-23,75.22,A\n2037-04-24,79.39,C\n2037-04-25,59.48,B\n2037-04-26,97.72,B\n2037-04-27,49.46,B\n2037-04-28,105.73,B\n2037-04-29,98.91,B\n2037-04-30,104.41,A\n2037-05-01,84.34,A\n2037-05-02,192.31,A\n2037-05-03,140.8,B\n2037-05-04,62.66,C\n2037-05-05,106.99,A\n2037-05-06,81.76,A\n2037-05-07,69.57,B\n2037-05-08,59.8,C\n2037-05-09,112.97,A\n2037-05-10,79.56,A\n2037-05-11,129.03,C\n2037-05-12,129.81,B\n2037-05-13,141.22,C\n2037-05-14,74.43,B\n2037-05-15,114.26,A\n2037-05-16,118.97,C\n2037-05-17,85.77,C\n2037-05-18,76.85,A\n2037-05-19,148.29,A\n2037-05-20,106.82,B\n2037-05-21,119.08,A\n2037-05-22,75.34,A\n2037-05-23,63.42,C\n2037-05-24,82.92,B\n2037-05-25,96.28,A\n2037-05-26,68.07,C\n2037-05-27,99.7,A\n2037-05-28,49.34,A\n2037-05-29,66.4,A\n2037-05-30,120.13,A\n2037-05-31,118.11,B\n2037-06-01,88.24,C\n2037-06-02,69.47,C\n2037-06-03,69.18,A\n2037-06-04,88.8,C\n2037-06-05,119.34,A\n2037-06-06,127.85,C\n2037-06-07,85.1,B\n2037-06-08,65.4,B\n2037-06-09,108.04,C\n2037-06-10,75.29,A\n2037-06-11,84.45,A\n2037-06-12,139.76,A\n2037-06-13,124.81,A\n2037-06-14,104.74,B\n2037-06-15,61.84,A\n2037-06-16,106.34,A\n2037-06-17,131.88,A\n2037-06-18,88.73,A\n2037-06-19,140.53,B\n2037-06-20,138.78,C\n2037-06-21,149.91,C\n2037-06-22,109.16,A\n2037-06-23,117.62,A\n2037-06-24,142.43,C\n2037-06-25,113.42,B\n2037-06-26,84.96,B\n2037-06-27,86.54,B\n2037-06-28,107.16,B\n2037-06-29,61.28,B\n2037-06-30,95.7,B\n2037-07-01,132.9,B\n2037-07-02,34.26,B\n2037-07-03,146.03,B\n2037-07-04,133.67,A\n2037-07-05,86.21,B\n2037-07-06,116.3,A\n2037-07-07,100.09,C\n2037-07-08,136.9,A\n2037-07-09,129.48,C\n2037-07-10,58.0,A\n2037-07-11,117.62,C\n2037-07-12,93.83,A\n2037-07-13,102.48,A\n2037-07-14,78.15,C\n2037-07-15,152.44,B\n2037-07-16,131.2,B\n2037-07-17,77.81,B\n2037-07-18,59.96,B\n2037-07-19,88.43,B\n2037-07-20,113.04,A\n2037-07-21,107.21,B\n2037-07-22,137.59,A\n2037-07-23,123.7,C\n2037-07-24,129.09,B\n2037-07-25,109.81,B\n2037-07-26,58.32,B\n2037-07-27,94.13,B\n2037-07-28,131.8,A\n2037-07-29,98.45,A\n2037-07-30,115.09,A\n2037-07-31,90.44,C\n2037-08-01,69.11,A\n2037-08-02,96.3,B\n2037-08-03,109.27,C\n2037-08-04,80.33,C\n2037-08-05,92.93,B\n2037-08-06,71.82,B\n2037-08-07,133.58,B\n2037-08-08,91.17,B\n2037-08-09,143.58,A\n2037-08-10,145.86,C\n2037-08-11,97.57,B\n2037-08-12,69.75,A\n2037-08-13,37.42,B\n2037-08-14,55.84,A\n2037-08-15,58.78,B\n2037-08-16,141.35,B\n2037-08-17,103.47,B\n2037-08-18,111.69,A\n2037-08-19,33.39,B\n2037-08-20,64.06,A\n2037-08-21,126.61,A\n2037-08-22,108.6,C\n2037-08-23,95.58,A\n2037-08-24,116.95,A\n2037-08-25,149.07,A\n2037-08-26,93.37,A\n2037-08-27,102.08,A\n2037-08-28,105.78,C\n2037-08-29,171.76,B\n2037-08-30,37.02,C\n2037-08-31,120.5,C\n2037-09-01,96.56,B\n2037-09-02,117.0,A\n2037-09-03,80.28,B\n2037-09-04,98.53,A\n2037-09-05,121.34,B\n2037-09-06,193.39,C\n2037-09-07,124.24,A\n2037-09-08,74.56,C\n2037-09-09,87.29,A\n2037-09-10,86.4,B\n2037-09-11,46.13,B\n2037-09-12,90.1,B\n2037-09-13,121.98,B\n2037-09-14,61.77,B\n2037-09-15,131.45,C\n2037-09-16,114.63,C\n2037-09-17,77.97,A\n2037-09-18,95.75,C\n2037-09-19,147.95,A\n2037-09-20,122.01,A\n2037-09-21,100.26,C\n2037-09-22,92.87,C\n2037-09-23,102.3,A\n2037-09-24,84.65,C\n2037-09-25,36.86,C\n2037-09-26,161.27,C\n2037-09-27,104.66,B\n2037-09-28,111.94,B\n2037-09-29,98.26,A\n2037-09-30,77.95,C\n2037-10-01,94.42,A\n2037-10-02,52.02,C\n2037-10-03,168.31,B\n2037-10-04,62.96,C\n2037-10-05,69.04,C\n2037-10-06,86.79,C\n2037-10-07,138.64,B\n2037-10-08,100.44,B\n2037-10-09,62.92,B\n2037-10-10,137.63,A\n2037-10-11,121.93,C\n2037-10-12,116.15,B\n2037-10-13,121.47,A\n2037-10-14,171.83,B\n2037-10-15,165.55,C\n2037-10-16,79.4,A\n2037-10-17,148.72,B\n2037-10-18,111.5,B\n2037-10-19,114.97,A\n2037-10-20,49.23,A\n2037-10-21,94.89,A\n2037-10-22,75.62,A\n2037-10-23,124.84,A\n2037-10-24,132.13,B\n2037-10-25,37.77,A\n2037-10-26,99.34,A\n2037-10-27,133.66,C\n2037-10-28,94.4,B\n2037-10-29,121.87,C\n2037-10-30,110.53,B\n2037-10-31,110.25,B\n2037-11-01,91.86,B\n2037-11-02,63.5,A\n2037-11-03,66.35,C\n2037-11-04,121.28,B\n2037-11-05,100.58,B\n2037-11-06,95.84,B\n2037-11-07,87.28,C\n2037-11-08,90.84,B\n2037-11-09,131.41,C\n2037-11-10,89.94,C\n2037-11-11,59.77,A\n2037-11-12,122.31,B\n2037-11-13,93.85,B\n2037-11-14,100.94,C\n2037-11-15,148.23,C\n2037-11-16,99.38,A\n2037-11-17,108.68,B\n2037-11-18,64.63,C\n2037-11-19,93.41,B\n2037-11-20,118.29,B\n2037-11-21,53.34,B\n2037-11-22,106.03,C\n2037-11-23,103.02,B\n2037-11-24,101.24,C\n2037-11-25,131.11,A\n2037-11-26,110.03,A\n2037-11-27,144.34,C\n2037-11-28,70.19,B\n2037-11-29,104.08,A\n2037-11-30,102.53,A\n2037-12-01,67.92,C\n2037-12-02,128.69,C\n2037-12-03,169.74,C\n2037-12-04,99.49,C\n2037-12-05,115.15,C\n2037-12-06,102.54,C\n2037-12-07,78.59,C\n2037-12-08,45.38,B\n2037-12-09,78.83,B\n2037-12-10,58.24,B\n2037-12-11,151.12,B\n2037-12-12,96.55,A\n2037-12-13,89.62,B\n2037-12-14,126.67,A\n2037-12-15,66.35,A\n2037-12-16,76.99,A\n2037-12-17,102.04,C\n2037-12-18,130.86,A\n2037-12-19,115.05,A\n2037-12-20,107.45,A\n2037-12-21,102.34,C\n2037-12-22,112.01,B\n2037-12-23,106.05,B\n2037-12-24,69.1,A\n2037-12-25,101.38,A\n2037-12-26,67.79,B\n2037-12-27,102.43,B\n2037-12-28,85.58,B\n2037-12-29,176.18,B\n2037-12-30,98.8,A\n2037-12-31,57.87,A\n2038-01-01,95.97,A\n2038-01-02,115.58,B\n2038-01-03,150.2,C\n2038-01-04,82.32,B\n2038-01-05,52.53,B\n2038-01-06,124.51,C\n2038-01-07,58.58,C\n2038-01-08,86.92,B\n2038-01-09,92.27,C\n2038-01-10,97.91,C\n2038-01-11,120.67,C\n2038-01-12,79.49,B\n2038-01-13,91.79,B\n2038-01-14,99.57,C\n2038-01-15,104.49,A\n2038-01-16,102.93,C\n2038-01-17,113.08,C\n2038-01-18,125.32,B\n2038-01-19,111.78,A\n2038-01-20,113.61,C\n2038-01-21,52.53,A\n2038-01-22,101.51,B\n2038-01-23,114.39,A\n2038-01-24,121.87,B\n2038-01-25,79.23,C\n2038-01-26,80.23,B\n2038-01-27,82.78,A\n2038-01-28,116.09,A\n2038-01-29,131.02,A\n2038-01-30,87.78,C\n2038-01-31,127.24,A\n2038-02-01,103.7,A\n2038-02-02,96.27,A\n2038-02-03,93.66,B\n2038-02-04,122.4,A\n2038-02-05,109.77,B\n2038-02-06,168.27,A\n2038-02-07,74.43,B\n2038-02-08,115.54,B\n2038-02-09,84.12,C\n2038-02-10,73.92,C\n2038-02-11,99.67,B\n2038-02-12,104.12,B\n2038-02-13,187.55,C\n2038-02-14,94.48,B\n2038-02-15,66.09,C\n2038-02-16,108.19,C\n2038-02-17,37.76,A\n2038-02-18,97.24,A\n2038-02-19,100.48,C\n2038-02-20,123.84,C\n2038-02-21,129.21,B\n2038-02-22,84.89,C\n2038-02-23,95.48,C\n2038-02-24,77.02,A\n2038-02-25,66.16,C\n2038-02-26,109.3,B\n2038-02-27,61.79,C\n2038-02-28,87.56,A\n2038-03-01,70.14,A\n2038-03-02,116.49,A\n2038-03-03,154.04,A\n2038-03-04,60.95,C\n2038-03-05,44.41,B\n2038-03-06,96.25,B\n2038-03-07,100.47,B\n2038-03-08,106.56,C\n2038-03-09,86.61,C\n2038-03-10,93.98,C\n2038-03-11,111.03,B\n2038-03-12,131.21,C\n2038-03-13,41.35,C\n2038-03-14,88.93,A\n2038-03-15,106.41,A\n2038-03-16,72.46,B\n2038-03-17,151.94,A\n2038-03-18,80.94,A\n2038-03-19,98.76,A\n2038-03-20,116.68,C\n2038-03-21,82.26,A\n2038-03-22,44.6,A\n2038-03-23,87.14,A\n2038-03-24,130.88,C\n2038-03-25,89.89,B\n2038-03-26,74.61,C\n2038-03-27,127.77,C\n2038-03-28,90.08,A\n2038-03-29,84.7,B\n2038-03-30,104.48,A\n2038-03-31,123.12,A\n2038-04-01,171.45,B\n2038-04-02,124.82,B\n2038-04-03,134.8,C\n2038-04-04,84.82,B\n2038-04-05,69.22,C\n2038-04-06,101.87,C\n2038-04-07,103.22,A\n2038-04-08,67.64,A\n2038-04-09,94.74,A\n2038-04-10,111.98,B\n2038-04-11,110.81,B\n2038-04-12,115.13,C\n2038-04-13,108.73,C\n2038-04-14,84.79,C\n2038-04-15,84.58,B\n2038-04-16,64.03,C\n2038-04-17,153.54,C\n2038-04-18,100.4,B\n2038-04-19,93.25,A\n2038-04-20,72.14,B\n2038-04-21,188.07,C\n2038-04-22,67.8,A\n2038-04-23,87.13,B\n2038-04-24,112.48,A\n2038-04-25,118.01,A\n2038-04-26,128.74,C\n2038-04-27,154.23,B\n2038-04-28,115.18,C\n2038-04-29,68.32,B\n2038-04-30,92.29,B\n2038-05-01,109.23,B\n2038-05-02,162.05,A\n2038-05-03,58.07,A\n2038-05-04,136.08,C\n2038-05-05,105.4,B\n2038-05-06,125.13,B\n2038-05-07,96.94,A\n2038-05-08,74.73,B\n2038-05-09,122.53,C\n2038-05-10,119.63,B\n2038-05-11,129.16,B\n2038-05-12,99.6,A\n2038-05-13,82.01,B\n2038-05-14,53.24,C\n2038-05-15,124.58,C\n2038-05-16,112.72,C\n2038-05-17,71.03,C\n2038-05-18,83.84,A\n2038-05-19,86.0,B\n2038-05-20,55.51,A\n2038-05-21,75.47,C\n2038-05-22,65.99,B\n2038-05-23,87.63,A\n2038-05-24,77.84,B\n2038-05-25,51.19,C\n2038-05-26,160.68,C\n2038-05-27,137.58,B\n2038-05-28,76.63,B\n2038-05-29,108.17,C\n2038-05-30,112.52,A\n2038-05-31,135.73,C\n2038-06-01,75.44,B\n2038-06-02,132.76,A\n2038-06-03,83.56,C\n2038-06-04,59.92,B\n2038-06-05,121.72,B\n2038-06-06,138.72,B\n2038-06-07,177.64,A\n2038-06-08,64.01,A\n2038-06-09,120.97,A\n2038-06-10,87.71,A\n2038-06-11,123.5,B\n2038-06-12,55.89,B\n2038-06-13,62.12,A\n2038-06-14,76.72,A\n2038-06-15,102.37,B\n2038-06-16,80.77,C\n2038-06-17,119.77,C\n2038-06-18,130.81,B\n2038-06-19,117.99,A\n2038-06-20,148.56,A\n2038-06-21,112.47,B\n2038-06-22,63.49,C\n2038-06-23,140.9,B\n2038-06-24,81.85,B\n2038-06-25,127.31,B\n2038-06-26,147.21,A\n2038-06-27,113.7,A\n2038-06-28,55.12,A\n2038-06-29,72.45,C\n2038-06-30,139.68,B\n2038-07-01,94.93,B\n2038-07-02,102.39,C\n2038-07-03,122.76,A\n2038-07-04,120.89,B\n2038-07-05,108.36,C\n2038-07-06,93.74,A\n2038-07-07,90.62,A\n2038-07-08,125.74,B\n2038-07-09,116.61,A\n2038-07-10,92.28,C\n2038-07-11,132.46,A\n2038-07-12,96.13,B\n2038-07-13,74.45,C\n2038-07-14,77.23,A\n2038-07-15,70.64,A\n2038-07-16,91.83,A\n2038-07-17,137.01,C\n2038-07-18,75.85,A\n2038-07-19,105.65,B\n2038-07-20,80.3,C\n2038-07-21,92.19,C\n2038-07-22,54.7,A\n2038-07-23,93.38,C\n2038-07-24,94.19,B\n2038-07-25,116.21,C\n2038-07-26,106.7,B\n2038-07-27,64.05,A\n2038-07-28,132.53,C\n2038-07-29,106.31,B\n2038-07-30,105.95,A\n2038-07-31,99.72,B\n2038-08-01,84.47,C\n2038-08-02,106.53,C\n2038-08-03,114.27,B\n2038-08-04,47.59,A\n2038-08-05,83.84,A\n2038-08-06,92.35,A\n2038-08-07,107.69,A\n2038-08-08,135.8,B\n2038-08-09,76.85,C\n2038-08-10,85.06,B\n2038-08-11,84.62,C\n2038-08-12,75.95,C\n2038-08-13,89.58,B\n2038-08-14,123.12,B\n2038-08-15,68.38,A\n2038-08-16,112.83,C\n2038-08-17,48.94,A\n2038-08-18,83.16,A\n2038-08-19,90.12,B\n2038-08-20,150.25,A\n2038-08-21,91.76,A\n2038-08-22,33.96,A\n2038-08-23,40.03,B\n2038-08-24,101.02,A\n2038-08-25,118.41,A\n2038-08-26,111.26,B\n2038-08-27,105.03,B\n2038-08-28,134.87,B\n2038-08-29,83.81,C\n2038-08-30,126.85,B\n2038-08-31,51.04,C\n2038-09-01,72.85,B\n2038-09-02,45.79,B\n2038-09-03,91.75,B\n2038-09-04,98.61,C\n2038-09-05,103.42,A\n2038-09-06,101.86,B\n2038-09-07,72.17,C\n2038-09-08,94.29,B\n2038-09-09,55.08,A\n2038-09-10,140.9,C\n2038-09-11,90.89,B\n2038-09-12,85.33,B\n2038-09-13,147.67,B\n2038-09-14,62.73,A\n2038-09-15,66.23,C\n2038-09-16,67.11,B\n2038-09-17,97.96,B\n2038-09-18,112.95,C\n2038-09-19,122.19,A\n2038-09-20,177.16,A\n2038-09-21,32.52,A\n2038-09-22,118.26,A\n2038-09-23,83.29,A\n2038-09-24,82.41,B\n2038-09-25,81.41,B\n2038-09-26,72.74,B\n2038-09-27,150.54,C\n2038-09-28,125.27,A\n2038-09-29,100.7,C\n2038-09-30,140.53,A\n2038-10-01,54.62,A\n2038-10-02,60.08,C\n2038-10-03,34.66,B\n2038-10-04,79.02,C\n2038-10-05,104.85,B\n2038-10-06,103.09,A\n2038-10-07,104.48,B\n2038-10-08,57.67,A\n2038-10-09,124.09,A\n2038-10-10,107.19,B\n2038-10-11,121.99,A\n2038-10-12,43.22,B\n2038-10-13,109.34,C\n2038-10-14,64.37,A\n2038-10-15,88.08,A\n2038-10-16,116.03,C\n2038-10-17,102.73,A\n2038-10-18,74.45,C\n2038-10-19,100.19,C\n2038-10-20,54.05,A\n2038-10-21,134.51,C\n2038-10-22,93.84,C\n2038-10-23,133.56,C\n2038-10-24,84.2,A\n2038-10-25,84.29,C\n2038-10-26,77.07,B\n2038-10-27,126.29,B\n2038-10-28,78.72,C\n2038-10-29,119.34,B\n2038-10-30,88.53,A\n2038-10-31,40.72,C\n2038-11-01,82.89,A\n2038-11-02,64.63,B\n2038-11-03,101.02,B\n2038-11-04,64.29,B\n2038-11-05,127.78,B\n2038-11-06,97.27,A\n2038-11-07,94.67,C\n2038-11-08,119.68,A\n2038-11-09,128.92,C\n2038-11-10,99.4,B\n2038-11-11,76.86,B\n2038-11-12,46.78,B\n2038-11-13,79.42,B\n2038-11-14,81.48,A\n2038-11-15,103.21,C\n2038-11-16,89.65,C\n2038-11-17,120.64,B\n2038-11-18,81.61,A\n2038-11-19,98.29,B\n2038-11-20,135.52,C\n2038-11-21,110.25,C\n2038-11-22,98.2,A\n2038-11-23,81.94,C\n2038-11-24,81.0,A\n2038-11-25,74.48,B\n2038-11-26,83.77,B\n2038-11-27,122.67,A\n2038-11-28,93.34,B\n2038-11-29,59.51,A\n2038-11-30,153.05,B\n2038-12-01,109.6,B\n2038-12-02,57.79,C\n2038-12-03,86.74,C\n2038-12-04,103.33,A\n2038-12-05,151.14,B\n2038-12-06,66.76,B\n2038-12-07,129.28,B\n2038-12-08,141.98,C\n2038-12-09,81.14,A\n2038-12-10,91.67,A\n2038-12-11,68.91,A\n2038-12-12,93.9,B\n2038-12-13,98.9,A\n2038-12-14,119.79,C\n2038-12-15,132.71,C\n2038-12-16,77.56,A\n2038-12-17,40.98,B\n2038-12-18,99.36,C\n2038-12-19,108.81,C\n2038-12-20,107.48,B\n2038-12-21,90.2,B\n2038-12-22,103.74,A\n2038-12-23,145.46,C\n2038-12-24,111.45,C\n2038-12-25,88.8,A\n2038-12-26,84.99,A\n2038-12-27,94.9,C\n2038-12-28,149.1,C\n2038-12-29,94.5,B\n2038-12-30,99.41,B\n2038-12-31,85.53,C\n2039-01-01,132.97,C\n2039-01-02,158.8,B\n2039-01-03,113.52,A\n2039-01-04,102.48,A\n2039-01-05,101.1,C\n2039-01-06,28.47,C\n2039-01-07,122.35,B\n2039-01-08,104.02,C\n2039-01-09,112.85,A\n2039-01-10,78.66,B\n2039-01-11,88.47,B\n2039-01-12,87.67,A\n2039-01-13,130.65,A\n2039-01-14,88.64,B\n2039-01-15,162.48,C\n2039-01-16,112.93,A\n2039-01-17,61.73,B\n2039-01-18,96.14,C\n2039-01-19,80.45,C\n2039-01-20,34.17,B\n2039-01-21,147.98,A\n2039-01-22,121.09,C\n2039-01-23,116.0,A\n2039-01-24,104.96,B\n2039-01-25,58.54,A\n2039-01-26,85.49,C\n2039-01-27,125.25,B\n2039-01-28,68.46,C\n2039-01-29,24.05,C\n2039-01-30,93.19,B\n2039-01-31,95.75,B\n2039-02-01,104.07,A\n2039-02-02,121.86,B\n2039-02-03,119.13,A\n2039-02-04,107.76,B\n2039-02-05,135.87,C\n2039-02-06,54.43,A\n2039-02-07,115.14,B\n2039-02-08,118.8,A\n2039-02-09,62.81,A\n2039-02-10,54.91,A\n2039-02-11,77.62,C\n2039-02-12,80.15,B\n2039-02-13,122.6,B\n2039-02-14,81.56,A\n2039-02-15,74.31,B\n2039-02-16,81.65,C\n2039-02-17,133.56,A\n2039-02-18,100.39,C\n2039-02-19,94.62,C\n2039-02-20,96.7,B\n2039-02-21,138.76,B\n2039-02-22,77.52,A\n2039-02-23,139.47,C\n2039-02-24,106.31,B\n2039-02-25,95.19,A\n2039-02-26,175.03,A\n2039-02-27,68.33,B\n2039-02-28,89.39,C\n2039-03-01,63.03,A\n2039-03-02,128.57,B\n2039-03-03,150.68,A\n2039-03-04,150.1,B\n2039-03-05,136.72,A\n2039-03-06,94.07,C\n2039-03-07,89.04,A\n2039-03-08,49.86,B\n2039-03-09,161.08,B\n2039-03-10,114.07,B\n2039-03-11,85.24,C\n2039-03-12,139.84,B\n2039-03-13,70.82,B\n2039-03-14,112.2,C\n2039-03-15,94.82,B\n2039-03-16,119.16,B\n2039-03-17,98.09,C\n2039-03-18,56.47,A\n2039-03-19,102.27,B\n2039-03-20,131.47,A\n2039-03-21,99.12,C\n2039-03-22,123.25,B\n2039-03-23,68.63,B\n2039-03-24,99.21,C\n2039-03-25,81.26,B\n2039-03-26,122.41,A\n2039-03-27,100.94,A\n2039-03-28,120.7,C\n2039-03-29,132.63,B\n2039-03-30,120.75,B\n2039-03-31,108.19,C\n2039-04-01,70.05,A\n2039-04-02,99.92,A\n2039-04-03,84.01,C\n2039-04-04,89.68,B\n2039-04-05,88.76,A\n2039-04-06,70.6,B\n2039-04-07,41.28,A\n2039-04-08,79.96,B\n2039-04-09,105.8,A\n2039-04-10,59.55,B\n2039-04-11,100.47,C\n2039-04-12,157.01,A\n2039-04-13,130.69,C\n2039-04-14,149.31,A\n2039-04-15,111.73,A\n2039-04-16,135.03,A\n2039-04-17,78.28,B\n2039-04-18,80.67,A\n2039-04-19,17.83,C\n2039-04-20,135.58,B\n2039-04-21,146.21,B\n2039-04-22,83.4,A\n2039-04-23,92.38,B\n2039-04-24,77.38,A\n2039-04-25,111.48,C\n2039-04-26,133.75,B\n2039-04-27,47.72,B\n2039-04-28,112.28,C\n2039-04-29,91.7,C\n2039-04-30,103.4,A\n2039-05-01,113.77,B\n2039-05-02,94.35,C\n2039-05-03,96.89,B\n2039-05-04,93.58,C\n2039-05-05,129.11,B\n2039-05-06,112.56,C\n2039-05-07,106.62,B\n2039-05-08,61.87,A\n2039-05-09,88.64,B\n2039-05-10,126.96,A\n2039-05-11,101.13,A\n2039-05-12,119.79,A\n2039-05-13,153.0,B\n2039-05-14,131.16,B\n2039-05-15,102.79,A\n2039-05-16,84.67,C\n2039-05-17,23.39,B\n2039-05-18,94.95,B\n2039-05-19,90.22,B\n2039-05-20,111.71,B\n2039-05-21,95.38,A\n2039-05-22,90.39,C\n2039-05-23,46.62,C\n2039-05-24,71.8,C\n2039-05-25,114.3,C\n2039-05-26,151.48,A\n2039-05-27,97.07,B\n2039-05-28,109.83,B\n2039-05-29,128.57,A\n2039-05-30,75.67,B\n2039-05-31,111.0,A\n2039-06-01,77.79,C\n2039-06-02,125.94,A\n2039-06-03,104.54,B\n2039-06-04,108.95,B\n2039-06-05,76.52,B\n2039-06-06,121.21,C\n2039-06-07,162.54,A\n2039-06-08,69.76,A\n2039-06-09,89.81,B\n2039-06-10,106.95,A\n2039-06-11,123.59,B\n2039-06-12,83.21,A\n2039-06-13,15.01,C\n2039-06-14,140.78,C\n2039-06-15,122.82,A\n2039-06-16,47.71,C\n2039-06-17,81.77,C\n2039-06-18,147.05,C\n2039-06-19,143.43,B\n2039-06-20,128.16,A\n2039-06-21,94.72,A\n2039-06-22,102.49,B\n2039-06-23,96.07,B\n2039-06-24,133.85,C\n2039-06-25,73.72,A\n2039-06-26,75.83,B\n2039-06-27,130.31,C\n2039-06-28,98.67,A\n2039-06-29,141.35,C\n2039-06-30,79.85,A\n2039-07-01,98.36,C\n2039-07-02,72.33,A\n2039-07-03,100.22,A\n2039-07-04,55.64,A\n2039-07-05,85.37,A\n2039-07-06,56.91,C\n2039-07-07,120.82,B\n2039-07-08,121.5,A\n2039-07-09,48.09,A\n2039-07-10,140.02,A\n2039-07-11,174.92,A\n2039-07-12,101.38,C\n2039-07-13,80.77,B\n2039-07-14,184.73,B\n2039-07-15,60.43,B\n2039-07-16,100.6,C\n2039-07-17,105.39,B\n2039-07-18,140.35,C\n2039-07-19,110.79,C\n2039-07-20,85.04,A\n2039-07-21,71.46,C\n2039-07-22,89.75,B\n2039-07-23,69.79,B\n2039-07-24,101.43,B\n2039-07-25,83.07,A\n2039-07-26,55.18,A\n2039-07-27,137.65,A\n2039-07-28,85.07,C\n2039-07-29,61.96,B\n2039-07-30,94.95,C\n2039-07-31,102.05,C\n2039-08-01,47.61,B\n2039-08-02,109.95,A\n2039-08-03,68.59,B\n2039-08-04,151.33,C\n2039-08-05,115.72,C\n2039-08-06,88.0,A\n2039-08-07,77.64,C\n2039-08-08,96.85,C\n2039-08-09,118.38,B\n2039-08-10,99.24,A\n2039-08-11,93.49,B\n2039-08-12,101.75,B\n2039-08-13,81.88,B\n2039-08-14,89.7,C\n2039-08-15,37.43,C\n2039-08-16,129.21,A\n2039-08-17,108.96,C\n2039-08-18,76.8,A\n2039-08-19,156.96,B\n2039-08-20,102.61,B\n2039-08-21,68.92,C\n2039-08-22,125.7,C\n2039-08-23,111.94,A\n2039-08-24,111.84,C\n2039-08-25,87.23,C\n2039-08-26,113.51,C\n2039-08-27,92.15,B\n2039-08-28,116.68,C\n2039-08-29,42.85,C\n2039-08-30,86.98,B\n2039-08-31,151.66,B\n2039-09-01,76.99,B\n2039-09-02,81.88,B\n2039-09-03,86.56,C\n2039-09-04,63.56,C\n2039-09-05,33.75,C\n2039-09-06,93.08,A\n2039-09-07,104.08,B\n2039-09-08,98.42,C\n2039-09-09,95.65,C\n2039-09-10,101.29,C\n2039-09-11,130.69,B\n2039-09-12,94.29,B\n2039-09-13,82.78,B\n2039-09-14,91.57,C\n2039-09-15,117.38,B\n2039-09-16,142.36,C\n2039-09-17,81.16,A\n2039-09-18,102.97,B\n2039-09-19,110.15,B\n2039-09-20,70.22,C\n2039-09-21,100.61,B\n2039-09-22,56.03,B\n2039-09-23,46.93,C\n2039-09-24,111.0,C\n2039-09-25,88.48,C\n2039-09-26,106.07,A\n2039-09-27,99.46,C\n2039-09-28,70.71,B\n2039-09-29,98.64,A\n2039-09-30,63.01,C\n2039-10-01,141.73,A\n2039-10-02,59.32,A\n2039-10-03,94.13,B\n2039-10-04,68.73,A\n2039-10-05,117.11,C\n2039-10-06,90.06,A\n2039-10-07,84.44,A\n2039-10-08,125.6,B\n2039-10-09,100.88,B\n2039-10-10,115.5,C\n2039-10-11,70.56,B\n2039-10-12,57.86,C\n2039-10-13,103.22,C\n2039-10-14,90.43,B\n2039-10-15,118.79,A\n2039-10-16,97.6,B\n2039-10-17,106.15,A\n2039-10-18,51.3,A\n2039-10-19,156.65,C\n2039-10-20,95.07,C\n2039-10-21,120.93,B\n2039-10-22,74.92,A\n2039-10-23,109.24,B\n2039-10-24,60.91,C\n2039-10-25,113.56,A\n2039-10-26,89.32,B\n2039-10-27,100.88,B\n2039-10-28,120.94,C\n2039-10-29,63.02,C\n2039-10-30,79.47,C\n2039-10-31,113.72,C\n2039-11-01,162.95,B\n2039-11-02,95.86,C\n2039-11-03,117.73,C\n2039-11-04,96.79,B\n2039-11-05,158.27,B\n2039-11-06,47.58,A\n2039-11-07,102.84,B\n2039-11-08,51.23,A\n2039-11-09,24.8,C\n2039-11-10,108.92,A\n2039-11-11,104.18,A\n2039-11-12,91.45,C\n2039-11-13,96.81,C\n2039-11-14,192.95,C\n2039-11-15,92.25,A\n2039-11-16,115.3,B\n2039-11-17,80.52,B\n2039-11-18,108.86,B\n2039-11-19,92.85,C\n2039-11-20,87.68,C\n2039-11-21,154.57,B\n2039-11-22,135.32,C\n2039-11-23,104.1,A\n2039-11-24,30.87,A\n2039-11-25,89.17,C\n2039-11-26,181.09,B\n2039-11-27,93.08,B\n2039-11-28,133.1,A\n2039-11-29,143.26,C\n2039-11-30,106.57,B\n2039-12-01,74.75,B\n2039-12-02,26.94,C\n2039-12-03,120.52,C\n2039-12-04,75.56,B\n2039-12-05,105.28,C\n2039-12-06,62.51,C\n2039-12-07,92.04,C\n2039-12-08,101.01,B\n2039-12-09,108.06,A\n2039-12-10,66.56,C\n2039-12-11,135.91,B\n2039-12-12,82.09,A\n2039-12-13,49.77,B\n2039-12-14,96.73,B\n2039-12-15,101.03,B\n2039-12-16,104.82,A\n2039-12-17,140.86,B\n2039-12-18,92.91,A\n2039-12-19,96.41,C\n2039-12-20,66.9,A\n2039-12-21,124.62,B\n2039-12-22,105.53,A\n2039-12-23,87.4,B\n2039-12-24,42.86,C\n2039-12-25,143.33,A\n2039-12-26,63.17,C\n2039-12-27,82.26,A\n2039-12-28,110.75,B\n2039-12-29,64.1,A\n2039-12-30,55.19,C\n2039-12-31,36.28,A\n2040-01-01,121.63,C\n2040-01-02,83.64,C\n2040-01-03,187.82,B\n2040-01-04,70.17,A\n2040-01-05,72.36,C\n2040-01-06,83.04,B\n2040-01-07,107.29,B\n2040-01-08,85.89,B\n2040-01-09,118.37,C\n2040-01-10,70.64,A\n2040-01-11,58.55,C\n2040-01-12,46.05,B\n2040-01-13,33.29,C\n2040-01-14,84.46,C\n2040-01-15,90.97,B\n2040-01-16,81.06,A\n2040-01-17,137.3,B\n2040-01-18,136.84,B\n2040-01-19,89.96,C\n2040-01-20,46.18,B\n2040-01-21,83.0,C\n2040-01-22,82.01,B\n2040-01-23,80.29,B\n2040-01-24,79.07,A\n2040-01-25,80.4,B\n2040-01-26,88.4,A\n2040-01-27,80.93,B\n2040-01-28,106.25,B\n2040-01-29,110.81,B\n2040-01-30,72.68,A\n2040-01-31,138.13,C\n2040-02-01,70.6,A\n2040-02-02,51.47,B\n2040-02-03,111.42,C\n2040-02-04,75.48,B\n2040-02-05,109.64,A\n2040-02-06,27.89,A\n2040-02-07,141.77,C\n2040-02-08,120.17,A\n2040-02-09,54.99,B\n2040-02-10,55.64,C\n2040-02-11,150.17,B\n2040-02-12,77.88,C\n2040-02-13,74.97,C\n2040-02-14,32.02,B\n2040-02-15,89.48,A\n2040-02-16,93.17,B\n2040-02-17,121.46,A\n2040-02-18,58.03,A\n2040-02-19,54.39,B\n2040-02-20,113.53,C\n2040-02-21,130.2,B\n2040-02-22,52.87,C\n2040-02-23,132.45,A\n2040-02-24,101.13,C\n2040-02-25,132.75,B\n2040-02-26,97.89,C\n2040-02-27,24.33,A\n2040-02-28,95.44,A\n2040-02-29,131.8,C\n2040-03-01,73.19,A\n2040-03-02,88.36,A\n2040-03-03,94.72,C\n2040-03-04,13.01,A\n2040-03-05,150.14,C\n2040-03-06,72.96,C\n2040-03-07,115.17,A\n2040-03-08,97.4,C\n2040-03-09,137.73,A\n2040-03-10,112.18,A\n2040-03-11,92.95,A\n2040-03-12,73.19,B\n2040-03-13,123.94,A\n2040-03-14,43.8,C\n2040-03-15,119.38,C\n2040-03-16,67.03,C\n2040-03-17,137.57,B\n2040-03-18,104.84,B\n2040-03-19,119.32,C\n2040-03-20,102.46,C\n2040-03-21,47.43,B\n2040-03-22,74.18,C\n2040-03-23,120.17,A\n2040-03-24,105.0,A\n2040-03-25,135.43,C\n2040-03-26,112.1,A\n2040-03-27,71.0,C\n2040-03-28,76.12,B\n2040-03-29,103.09,C\n2040-03-30,30.58,B\n2040-03-31,150.2,A\n2040-04-01,113.25,A\n2040-04-02,136.81,A\n2040-04-03,52.58,C\n2040-04-04,137.34,B\n2040-04-05,166.94,B\n2040-04-06,60.37,A\n2040-04-07,123.14,A\n2040-04-08,100.03,B\n2040-04-09,53.67,C\n2040-04-10,114.79,A\n2040-04-11,125.01,C\n2040-04-12,81.97,A\n2040-04-13,134.98,A\n2040-04-14,50.0,C\n2040-04-15,55.16,C\n2040-04-16,104.1,C\n2040-04-17,100.76,C\n2040-04-18,78.5,B\n2040-04-19,124.29,B\n2040-04-20,115.93,A\n2040-04-21,125.74,A\n2040-04-22,96.31,A\n2040-04-23,118.08,B\n2040-04-24,86.63,C\n2040-04-25,161.61,A\n2040-04-26,75.62,B\n2040-04-27,163.16,A\n2040-04-28,101.05,B\n2040-04-29,58.73,C\n2040-04-30,141.04,B\n2040-05-01,103.78,C\n2040-05-02,125.53,A\n2040-05-03,136.72,C\n2040-05-04,110.14,B\n2040-05-05,82.23,A\n2040-05-06,129.27,A\n2040-05-07,100.02,B\n2040-05-08,120.58,C\n2040-05-09,35.39,B\n2040-05-10,113.41,C\n2040-05-11,80.84,C\n2040-05-12,149.6,A\n2040-05-13,118.69,C\n2040-05-14,107.2,A\n2040-05-15,114.59,B\n2040-05-16,70.05,B\n2040-05-17,148.7,A\n2040-05-18,159.74,A\n2040-05-19,111.79,C\n2040-05-20,147.71,A\n2040-05-21,82.93,B\n2040-05-22,76.09,A\n2040-05-23,101.23,A\n2040-05-24,113.05,A\n2040-05-25,88.18,C\n2040-05-26,116.13,A\n2040-05-27,109.19,C\n2040-05-28,70.05,C\n2040-05-29,115.56,A\n2040-05-30,125.91,C\n2040-05-31,105.14,A\n2040-06-01,134.58,B\n2040-06-02,63.48,B\n2040-06-03,114.04,C\n2040-06-04,64.89,B\n2040-06-05,66.58,A\n2040-06-06,81.07,C\n2040-06-07,71.74,A\n2040-06-08,83.56,C\n2040-06-09,93.58,C\n2040-06-10,125.11,C\n2040-06-11,90.37,C\n2040-06-12,52.43,B\n2040-06-13,134.2,B\n2040-06-14,74.89,B\n2040-06-15,98.24,C\n2040-06-16,113.4,B\n2040-06-17,105.99,C\n2040-06-18,132.83,B\n2040-06-19,114.37,C\n2040-06-20,74.16,B\n2040-06-21,165.36,B\n2040-06-22,105.83,B\n2040-06-23,95.58,C\n2040-06-24,128.92,C\n2040-06-25,107.48,B\n2040-06-26,111.98,C\n2040-06-27,174.77,A\n2040-06-28,91.75,B\n2040-06-29,90.47,B\n2040-06-30,103.5,A\n2040-07-01,132.15,C\n2040-07-02,54.8,C\n2040-07-03,41.03,C\n2040-07-04,82.39,B\n2040-07-05,50.98,B\n2040-07-06,87.26,B\n2040-07-07,115.07,B\n2040-07-08,73.28,B\n2040-07-09,57.2,C\n2040-07-10,102.08,A\n2040-07-11,125.88,C\n2040-07-12,49.15,C\n2040-07-13,124.61,B\n2040-07-14,124.52,A\n2040-07-15,128.62,A\n2040-07-16,136.55,C\n2040-07-17,166.06,A\n2040-07-18,89.32,C\n2040-07-19,103.29,C\n2040-07-20,87.51,C\n2040-07-21,100.06,C\n2040-07-22,145.99,B\n2040-07-23,59.65,A\n2040-07-24,74.17,B\n2040-07-25,141.32,B\n2040-07-26,130.6,B\n2040-07-27,89.11,C\n2040-07-28,125.63,C\n2040-07-29,110.24,B\n2040-07-30,145.01,B\n2040-07-31,102.24,A\n2040-08-01,130.77,B\n2040-08-02,179.26,C\n2040-08-03,117.94,B\n2040-08-04,101.48,A\n2040-08-05,121.67,A\n2040-08-06,89.04,B\n2040-08-07,171.64,A\n2040-08-08,105.4,A\n2040-08-09,91.81,C\n2040-08-10,74.93,A\n2040-08-11,132.92,A\n2040-08-12,126.02,B\n2040-08-13,85.08,C\n2040-08-14,117.29,A\n2040-08-15,54.74,B\n2040-08-16,105.55,B\n2040-08-17,113.58,B\n2040-08-18,151.22,B\n2040-08-19,76.88,B\n2040-08-20,61.65,B\n2040-08-21,73.14,A\n2040-08-22,59.9,C\n2040-08-23,140.1,C\n2040-08-24,146.46,B\n2040-08-25,109.61,B\n2040-08-26,136.64,C\n2040-08-27,104.02,A\n2040-08-28,97.15,B\n2040-08-29,90.01,C\n2040-08-30,90.69,C\n2040-08-31,53.57,B\n2040-09-01,107.93,C\n2040-09-02,98.79,A\n2040-09-03,122.36,C\n2040-09-04,102.81,B\n2040-09-05,95.12,B\n2040-09-06,73.86,C\n2040-09-07,102.16,C\n2040-09-08,122.15,B\n2040-09-09,65.7,A\n2040-09-10,102.62,C\n2040-09-11,106.99,A\n2040-09-12,78.46,A\n2040-09-13,82.36,B\n2040-09-14,106.92,B\n2040-09-15,66.0,B\n2040-09-16,110.5,A\n2040-09-17,73.14,B\n2040-09-18,131.89,B\n2040-09-19,170.13,A\n2040-09-20,74.18,A\n2040-09-21,116.03,A\n2040-09-22,78.2,A\n2040-09-23,131.23,C\n2040-09-24,62.04,B\n2040-09-25,114.42,C\n2040-09-26,73.81,A\n2040-09-27,93.79,A\n2040-09-28,69.07,A\n2040-09-29,124.67,A\n2040-09-30,74.89,A\n2040-10-01,116.56,B\n2040-10-02,89.68,C\n2040-10-03,96.57,C\n2040-10-04,104.43,B\n2040-10-05,77.15,B\n2040-10-06,106.31,B\n2040-10-07,145.91,C\n2040-10-08,76.66,C\n2040-10-09,135.15,A\n2040-10-10,144.94,C\n2040-10-11,75.52,C\n2040-10-12,56.35,B\n2040-10-13,109.06,C\n2040-10-14,83.58,A\n2040-10-15,156.06,C\n2040-10-16,101.04,B\n2040-10-17,75.93,B\n2040-10-18,65.59,A\n2040-10-19,132.7,A\n2040-10-20,82.86,C\n2040-10-21,101.31,A\n2040-10-22,128.67,A\n2040-10-23,85.47,B\n2040-10-24,98.62,B\n2040-10-25,99.5,C\n2040-10-26,120.5,C\n2040-10-27,117.72,C\n2040-10-28,93.4,C\n2040-10-29,101.91,B\n2040-10-30,88.85,C\n2040-10-31,55.67,C\n2040-11-01,107.73,A\n2040-11-02,156.77,C\n2040-11-03,73.49,A\n2040-11-04,76.81,B\n2040-11-05,73.88,C\n2040-11-06,121.74,A\n2040-11-07,113.45,C\n2040-11-08,95.24,A\n2040-11-09,72.46,C\n2040-11-10,102.77,B\n2040-11-11,53.06,B\n2040-11-12,104.3,A\n2040-11-13,126.15,A\n2040-11-14,62.46,B\n2040-11-15,54.02,C\n2040-11-16,147.38,A\n2040-11-17,81.65,B\n2040-11-18,134.08,A\n2040-11-19,95.38,B\n2040-11-20,95.14,B\n2040-11-21,113.15,A\n2040-11-22,73.48,A\n2040-11-23,103.97,B\n2040-11-24,88.56,C\n2040-11-25,60.39,B\n2040-11-26,110.12,C\n2040-11-27,92.1,C\n2040-11-28,95.29,C\n2040-11-29,63.7,A\n2040-11-30,147.74,B\n2040-12-01,126.3,B\n2040-12-02,128.92,C\n2040-12-03,90.84,C\n2040-12-04,48.12,C\n2040-12-05,158.59,B\n2040-12-06,98.17,C\n2040-12-07,77.54,B\n2040-12-08,112.5,B\n2040-12-09,135.01,A\n2040-12-10,99.31,C\n2040-12-11,138.36,C\n2040-12-12,132.49,C\n2040-12-13,67.71,A\n2040-12-14,79.79,C\n2040-12-15,128.73,B\n2040-12-16,18.62,C\n2040-12-17,95.52,B\n2040-12-18,57.08,B\n2040-12-19,85.7,B\n2040-12-20,91.04,B\n2040-12-21,89.25,C\n2040-12-22,83.43,A\n2040-12-23,67.09,A\n2040-12-24,168.76,B\n2040-12-25,156.02,C\n2040-12-26,65.14,A\n2040-12-27,147.46,C\n2040-12-28,88.02,A\n2040-12-29,103.88,B\n2040-12-30,98.1,B\n2040-12-31,80.21,B\n2041-01-01,129.52,A\n2041-01-02,103.78,C\n2041-01-03,85.7,B\n2041-01-04,60.86,C\n2041-01-05,73.19,C\n2041-01-06,50.76,B\n2041-01-07,100.37,A\n2041-01-08,64.1,B\n2041-01-09,92.03,B\n2041-01-10,141.1,A\n2041-01-11,135.98,C\n2041-01-12,99.03,A\n2041-01-13,97.39,A\n2041-01-14,156.4,B\n2041-01-15,115.62,A\n2041-01-16,90.17,A\n2041-01-17,57.22,A\n2041-01-18,111.82,C\n2041-01-19,124.88,C\n2041-01-20,81.05,B\n2041-01-21,108.74,B\n2041-01-22,89.09,C\n2041-01-23,94.48,A\n2041-01-24,25.37,B\n2041-01-25,93.35,B\n2041-01-26,129.08,A\n2041-01-27,81.06,A\n2041-01-28,101.58,A\n2041-01-29,77.32,A\n2041-01-30,110.81,A\n2041-01-31,93.5,C\n2041-02-01,71.82,A\n2041-02-02,139.98,B\n2041-02-03,127.87,C\n2041-02-04,81.85,B\n2041-02-05,114.93,A\n2041-02-06,116.12,B\n2041-02-07,119.27,A\n2041-02-08,49.17,B\n2041-02-09,130.2,B\n2041-02-10,82.91,A\n2041-02-11,89.27,B\n2041-02-12,179.14,C\n2041-02-13,109.06,B\n2041-02-14,88.41,C\n2041-02-15,33.84,C\n2041-02-16,141.69,B\n2041-02-17,59.64,C\n2041-02-18,69.9,C\n2041-02-19,90.06,B\n2041-02-20,142.49,A\n2041-02-21,123.2,A\n2041-02-22,95.26,C\n2041-02-23,109.25,C\n2041-02-24,81.99,A\n2041-02-25,104.19,C\n2041-02-26,102.2,A\n2041-02-27,129.06,B\n2041-02-28,129.02,C\n2041-03-01,114.82,B\n2041-03-02,89.13,A\n2041-03-03,122.25,A\n2041-03-04,97.24,B\n2041-03-05,67.54,C\n2041-03-06,87.9,B\n2041-03-07,102.54,B\n2041-03-08,127.31,C\n2041-03-09,94.49,A\n2041-03-10,63.72,B\n2041-03-11,101.7,B\n2041-03-12,103.4,A\n2041-03-13,105.58,B\n2041-03-14,127.6,A\n2041-03-15,130.57,C\n2041-03-16,82.17,A\n2041-03-17,81.52,A\n2041-03-18,68.79,B\n2041-03-19,120.96,A\n2041-03-20,143.46,A\n2041-03-21,90.55,B\n2041-03-22,92.76,A\n2041-03-23,144.0,B\n2041-03-24,115.17,C\n2041-03-25,113.09,A\n2041-03-26,64.27,C\n2041-03-27,120.36,A\n2041-03-28,72.13,A\n2041-03-29,65.98,C\n2041-03-30,89.6,C\n2041-03-31,82.43,B\n2041-04-01,93.35,B\n2041-04-02,56.77,C\n2041-04-03,128.76,A\n2041-04-04,133.83,B\n2041-04-05,41.58,A\n2041-04-06,129.35,B\n2041-04-07,75.68,B\n2041-04-08,125.71,B\n2041-04-09,97.95,C\n2041-04-10,100.86,A\n2041-04-11,94.45,C\n2041-04-12,136.36,B\n2041-04-13,62.81,C\n2041-04-14,118.24,C\n2041-04-15,133.04,C\n2041-04-16,121.49,A\n2041-04-17,169.74,C\n2041-04-18,84.13,C\n2041-04-19,128.72,C\n2041-04-20,96.82,C\n2041-04-21,137.0,C\n2041-04-22,99.77,C\n2041-04-23,88.36,C\n2041-04-24,139.18,B\n2041-04-25,85.25,C\n2041-04-26,57.62,C\n2041-04-27,84.49,A\n2041-04-28,118.57,B\n2041-04-29,141.76,C\n2041-04-30,103.33,A\n2041-05-01,139.55,C\n2041-05-02,59.25,C\n2041-05-03,86.7,C\n2041-05-04,91.34,C\n2041-05-05,96.56,C\n2041-05-06,102.52,A\n2041-05-07,120.73,C\n2041-05-08,86.66,A\n2041-05-09,104.17,C\n2041-05-10,98.65,A\n2041-05-11,72.22,B\n2041-05-12,96.59,C\n2041-05-13,95.69,B\n2041-05-14,95.05,B\n2041-05-15,100.95,B\n2041-05-16,102.27,C\n2041-05-17,87.03,B\n2041-05-18,138.18,A\n2041-05-19,64.92,C\n2041-05-20,78.37,B\n2041-05-21,183.4,C\n2041-05-22,68.86,B\n2041-05-23,59.39,A\n2041-05-24,122.03,C\n2041-05-25,37.69,C\n2041-05-26,92.94,B\n2041-05-27,164.37,A\n2041-05-28,53.65,C\n2041-05-29,40.54,B\n2041-05-30,55.02,A\n2041-05-31,126.95,A\n2041-06-01,133.74,A\n2041-06-02,84.62,A\n2041-06-03,120.5,C\n2041-06-04,141.71,C\n2041-06-05,153.62,A\n2041-06-06,92.61,B\n2041-06-07,30.96,B\n2041-06-08,75.37,A\n2041-06-09,87.2,A\n2041-06-10,92.23,B\n2041-06-11,94.36,A\n2041-06-12,133.57,A\n2041-06-13,138.23,C\n2041-06-14,94.85,C\n2041-06-15,132.95,A\n2041-06-16,110.73,A\n2041-06-17,101.94,C\n2041-06-18,127.2,A\n2041-06-19,102.17,C\n2041-06-20,161.73,C\n2041-06-21,56.85,B\n2041-06-22,84.54,C\n2041-06-23,96.2,B\n2041-06-24,113.18,B\n2041-06-25,138.04,C\n2041-06-26,99.27,A\n2041-06-27,57.33,C\n2041-06-28,125.28,B\n2041-06-29,122.65,C\n2041-06-30,41.74,C\n2041-07-01,99.89,B\n2041-07-02,52.3,C\n2041-07-03,80.06,C\n2041-07-04,72.92,B\n2041-07-05,124.28,A\n2041-07-06,103.5,B\n2041-07-07,61.43,B\n2041-07-08,126.4,C\n2041-07-09,56.99,B\n2041-07-10,109.9,C\n2041-07-11,75.58,C\n2041-07-12,89.03,C\n2041-07-13,115.13,B\n2041-07-14,115.64,B\n2041-07-15,140.11,C\n2041-07-16,97.53,A\n2041-07-17,109.19,A\n2041-07-18,86.87,B\n2041-07-19,105.46,C\n2041-07-20,113.18,C\n2041-07-21,97.05,B\n2041-07-22,45.89,A\n2041-07-23,109.94,B\n2041-07-24,110.94,B\n2041-07-25,79.59,C\n2041-07-26,61.47,B\n2041-07-27,144.28,A\n2041-07-28,114.74,B\n2041-07-29,93.13,C\n2041-07-30,65.89,A\n2041-07-31,75.35,C\n2041-08-01,139.08,B\n2041-08-02,104.13,B\n2041-08-03,83.48,B\n2041-08-04,115.29,C\n2041-08-05,75.57,B\n2041-08-06,145.03,A\n2041-08-07,140.58,A\n2041-08-08,86.0,C\n2041-08-09,81.14,C\n2041-08-10,21.46,C\n2041-08-11,55.55,B\n2041-08-12,59.82,A\n2041-08-13,104.06,A\n2041-08-14,90.21,A\n2041-08-15,59.64,C\n2041-08-16,91.57,C\n2041-08-17,115.16,B\n2041-08-18,115.84,C\n2041-08-19,43.01,A\n2041-08-20,96.15,B\n2041-08-21,58.5,C\n2041-08-22,127.05,C\n2041-08-23,123.52,A\n2041-08-24,73.06,B\n2041-08-25,77.02,C\n2041-08-26,86.16,C\n2041-08-27,32.42,B\n2041-08-28,110.08,B\n2041-08-29,91.81,C\n2041-08-30,129.11,C\n2041-08-31,67.05,A\n2041-09-01,81.43,B\n2041-09-02,54.8,C\n2041-09-03,69.87,C\n2041-09-04,64.54,C\n2041-09-05,129.88,C\n2041-09-06,79.05,A\n2041-09-07,103.73,B\n2041-09-08,88.58,C\n2041-09-09,61.92,B\n2041-09-10,94.95,B\n2041-09-11,117.97,C\n2041-09-12,63.77,C\n2041-09-13,124.27,C\n2041-09-14,74.51,A\n2041-09-15,85.51,C\n2041-09-16,115.92,B\n2041-09-17,80.1,B\n2041-09-18,101.13,A\n2041-09-19,90.56,C\n2041-09-20,103.88,C\n2041-09-21,28.58,A\n2041-09-22,45.11,A\n2041-09-23,118.48,C\n2041-09-24,131.12,C\n2041-09-25,72.69,B\n2041-09-26,92.68,C\n2041-09-27,108.84,C\n2041-09-28,120.8,B\n2041-09-29,39.17,B\n2041-09-30,73.42,B\n2041-10-01,90.02,B\n2041-10-02,121.09,A\n2041-10-03,174.95,B\n2041-10-04,93.16,A\n2041-10-05,57.79,B\n2041-10-06,112.07,B\n2041-10-07,139.63,B\n2041-10-08,103.24,C\n2041-10-09,72.8,A\n2041-10-10,128.74,B\n2041-10-11,119.91,C\n2041-10-12,69.22,A\n2041-10-13,111.44,A\n2041-10-14,129.92,C\n2041-10-15,135.46,A\n2041-10-16,98.01,C\n2041-10-17,89.26,B\n2041-10-18,136.82,C\n2041-10-19,63.7,A\n2041-10-20,113.16,A\n2041-10-21,58.9,B\n2041-10-22,131.96,C\n2041-10-23,92.26,B\n2041-10-24,138.06,C\n2041-10-25,114.74,C\n2041-10-26,63.3,C\n2041-10-27,142.74,B\n2041-10-28,146.54,B\n2041-10-29,126.47,A\n2041-10-30,77.15,B\n2041-10-31,90.82,C\n2041-11-01,104.36,C\n2041-11-02,78.89,C\n2041-11-03,150.77,B\n2041-11-04,104.33,A\n2041-11-05,18.9,B\n2041-11-06,110.18,A\n2041-11-07,89.56,B\n2041-11-08,97.75,C\n2041-11-09,88.94,A\n2041-11-10,124.21,B\n2041-11-11,133.59,C\n2041-11-12,108.49,A\n2041-11-13,89.82,A\n2041-11-14,98.76,C\n2041-11-15,100.59,C\n2041-11-16,75.93,B\n2041-11-17,117.37,B\n2041-11-18,113.8,C\n2041-11-19,146.21,B\n2041-11-20,137.66,C\n2041-11-21,79.43,C\n2041-11-22,124.61,B\n2041-11-23,71.47,B\n2041-11-24,120.88,A\n2041-11-25,84.5,A\n2041-11-26,69.3,B\n2041-11-27,108.4,B\n2041-11-28,19.76,B\n2041-11-29,47.47,A\n2041-11-30,94.68,B\n2041-12-01,102.47,A\n2041-12-02,88.92,A\n2041-12-03,52.62,A\n2041-12-04,75.46,A\n2041-12-05,111.74,C\n2041-12-06,117.45,B\n2041-12-07,99.51,B\n2041-12-08,134.71,C\n2041-12-09,111.92,B\n2041-12-10,77.58,A\n2041-12-11,91.58,C\n2041-12-12,57.91,A\n2041-12-13,96.76,C\n2041-12-14,133.36,A\n2041-12-15,120.56,B\n2041-12-16,142.06,B\n2041-12-17,90.91,B\n2041-12-18,140.38,C\n2041-12-19,94.2,B\n2041-12-20,144.15,C\n2041-12-21,99.34,A\n2041-12-22,121.22,B\n2041-12-23,144.25,A\n2041-12-24,94.2,B\n2041-12-25,106.55,A\n2041-12-26,138.67,C\n2041-12-27,96.01,B\n2041-12-28,60.37,C\n2041-12-29,164.29,C\n2041-12-30,85.91,A\n2041-12-31,104.76,B\n2042-01-01,134.68,C\n2042-01-02,81.75,A\n2042-01-03,69.76,A\n2042-01-04,107.61,A\n2042-01-05,46.21,B\n2042-01-06,91.36,B\n2042-01-07,107.35,A\n2042-01-08,91.9,B\n2042-01-09,53.7,A\n2042-01-10,147.83,B\n2042-01-11,103.11,B\n2042-01-12,142.93,A\n2042-01-13,92.2,A\n2042-01-14,73.6,B\n2042-01-15,108.08,A\n2042-01-16,93.53,A\n2042-01-17,64.67,A\n2042-01-18,144.09,A\n2042-01-19,81.33,B\n2042-01-20,44.79,C\n2042-01-21,139.84,B\n2042-01-22,84.87,A\n2042-01-23,81.77,A\n2042-01-24,47.04,B\n2042-01-25,152.23,A\n2042-01-26,61.32,B\n2042-01-27,101.75,C\n2042-01-28,127.38,A\n2042-01-29,112.65,C\n2042-01-30,98.21,C\n2042-01-31,48.95,A\n2042-02-01,75.71,A\n2042-02-02,105.63,B\n2042-02-03,76.38,B\n2042-02-04,106.1,A\n2042-02-05,120.45,B\n2042-02-06,128.88,C\n2042-02-07,63.21,C\n2042-02-08,165.9,A\n2042-02-09,123.02,B\n2042-02-10,99.95,A\n2042-02-11,132.69,C\n2042-02-12,170.96,A\n2042-02-13,56.84,A\n2042-02-14,93.24,A\n2042-02-15,28.65,A\n2042-02-16,105.78,C\n2042-02-17,110.96,A\n2042-02-18,98.83,A\n2042-02-19,80.36,B\n2042-02-20,38.51,A\n2042-02-21,111.21,B\n2042-02-22,143.66,A\n2042-02-23,82.27,B\n2042-02-24,113.2,A\n2042-02-25,52.96,B\n2042-02-26,113.72,A\n2042-02-27,88.2,A\n2042-02-28,77.13,B\n2042-03-01,77.19,A\n2042-03-02,88.19,A\n2042-03-03,111.47,A\n2042-03-04,60.64,A\n2042-03-05,88.09,C\n2042-03-06,98.76,A\n2042-03-07,67.03,C\n2042-03-08,153.9,C\n2042-03-09,74.4,A\n2042-03-10,79.92,B\n2042-03-11,70.65,B\n2042-03-12,153.95,A\n2042-03-13,116.59,C\n2042-03-14,71.9,B\n2042-03-15,133.19,A\n2042-03-16,106.02,A\n2042-03-17,107.72,C\n2042-03-18,89.67,B\n2042-03-19,77.52,A\n2042-03-20,110.13,B\n2042-03-21,83.65,A\n2042-03-22,154.5,A\n2042-03-23,61.92,A\n2042-03-24,146.08,C\n2042-03-25,96.66,C\n2042-03-26,108.97,C\n2042-03-27,124.99,B\n2042-03-28,81.09,C\n2042-03-29,125.93,C\n2042-03-30,18.77,B\n2042-03-31,78.4,B\n2042-04-01,72.28,B\n2042-04-02,62.39,B\n2042-04-03,93.55,C\n2042-04-04,111.57,C\n2042-04-05,121.08,B\n2042-04-06,107.23,C\n2042-04-07,80.03,A\n2042-04-08,111.07,C\n2042-04-09,103.73,A\n2042-04-10,128.08,B\n2042-04-11,123.31,B\n2042-04-12,100.91,C\n2042-04-13,73.08,B\n2042-04-14,80.41,C\n2042-04-15,83.51,C\n2042-04-16,90.54,C\n2042-04-17,108.04,C\n2042-04-18,121.46,A\n2042-04-19,98.47,A\n2042-04-20,100.48,B\n2042-04-21,104.3,A\n2042-04-22,84.52,B\n2042-04-23,72.24,B\n2042-04-24,47.34,C\n2042-04-25,79.15,C\n2042-04-26,114.19,C\n2042-04-27,78.91,C\n2042-04-28,92.87,C\n2042-04-29,113.99,C\n2042-04-30,115.62,A\n2042-05-01,67.61,B\n2042-05-02,129.91,B\n2042-05-03,133.82,C\n2042-05-04,66.1,A\n2042-05-05,75.28,C\n2042-05-06,135.73,A\n2042-05-07,95.59,A\n2042-05-08,33.41,B\n2042-05-09,58.01,B\n2042-05-10,92.52,A\n2042-05-11,65.11,B\n2042-05-12,87.6,A\n2042-05-13,100.97,A\n2042-05-14,143.87,A\n2042-05-15,91.29,B\n2042-05-16,132.18,C\n2042-05-17,142.86,B\n2042-05-18,143.03,C\n2042-05-19,108.54,B\n2042-05-20,41.1,B\n2042-05-21,113.82,C\n2042-05-22,131.45,A\n2042-05-23,97.57,C\n2042-05-24,152.34,A\n2042-05-25,125.8,A\n2042-05-26,117.05,B\n2042-05-27,132.59,B\n2042-05-28,110.0,B\n2042-05-29,25.22,B\n2042-05-30,85.03,B\n2042-05-31,135.92,C\n2042-06-01,75.77,C\n2042-06-02,93.3,C\n2042-06-03,68.65,C\n2042-06-04,153.52,B\n2042-06-05,150.44,C\n2042-06-06,73.48,C\n2042-06-07,104.42,A\n2042-06-08,79.59,B\n2042-06-09,118.45,C\n2042-06-10,115.29,C\n2042-06-11,124.35,C\n2042-06-12,109.96,B\n2042-06-13,188.06,C\n2042-06-14,69.16,A\n2042-06-15,48.68,B\n2042-06-16,88.21,A\n2042-06-17,52.96,B\n2042-06-18,55.23,C\n2042-06-19,69.96,B\n2042-06-20,72.33,B\n2042-06-21,71.36,B\n2042-06-22,115.55,A\n2042-06-23,84.81,B\n2042-06-24,94.61,C\n2042-06-25,126.83,B\n2042-06-26,77.01,B\n2042-06-27,92.06,C\n2042-06-28,110.97,C\n2042-06-29,41.75,A\n2042-06-30,28.45,C\n2042-07-01,110.38,C\n2042-07-02,114.76,C\n2042-07-03,161.93,B\n2042-07-04,104.74,A\n2042-07-05,124.59,C\n2042-07-06,94.14,C\n2042-07-07,108.92,C\n2042-07-08,150.02,B\n2042-07-09,36.28,B\n2042-07-10,112.41,A\n2042-07-11,164.07,A\n2042-07-12,108.46,B\n2042-07-13,149.52,C\n2042-07-14,81.12,C\n2042-07-15,99.04,A\n2042-07-16,101.01,C\n2042-07-17,33.47,B\n2042-07-18,64.95,A\n2042-07-19,67.09,B\n2042-07-20,124.66,B\n2042-07-21,126.16,C\n2042-07-22,109.72,B\n2042-07-23,124.72,B\n2042-07-24,95.08,C\n2042-07-25,136.06,B\n2042-07-26,100.26,C\n2042-07-27,101.74,B\n2042-07-28,92.03,C\n2042-07-29,120.53,C\n2042-07-30,87.36,A\n2042-07-31,129.29,B\n2042-08-01,66.04,B\n2042-08-02,67.03,A\n2042-08-03,120.62,C\n2042-08-04,97.34,C\n2042-08-05,86.38,A\n2042-08-06,126.7,A\n2042-08-07,81.78,C\n2042-08-08,93.99,C\n2042-08-09,141.98,A\n2042-08-10,146.17,B\n2042-08-11,147.17,C\n2042-08-12,91.54,A\n2042-08-13,136.92,B\n2042-08-14,71.53,B\n2042-08-15,72.79,B\n2042-08-16,29.49,B\n2042-08-17,150.19,A\n2042-08-18,74.08,C\n2042-08-19,96.2,B\n2042-08-20,72.69,B\n2042-08-21,120.85,A\n2042-08-22,88.73,B\n2042-08-23,60.86,A\n2042-08-24,113.25,A\n2042-08-25,56.99,C\n2042-08-26,74.88,B\n2042-08-27,36.79,C\n2042-08-28,57.28,A\n2042-08-29,105.36,B\n2042-08-30,41.06,A\n2042-08-31,55.57,A\n2042-09-01,99.93,A\n2042-09-02,83.69,B\n2042-09-03,159.76,A\n2042-09-04,155.02,A\n2042-09-05,73.96,B\n2042-09-06,116.71,C\n2042-09-07,122.48,A\n2042-09-08,82.08,C\n2042-09-09,55.13,C\n2042-09-10,99.63,C\n2042-09-11,94.81,C\n2042-09-12,86.93,C\n2042-09-13,113.27,C\n2042-09-14,77.48,B\n2042-09-15,65.44,B\n2042-09-16,103.27,C\n2042-09-17,111.58,B\n2042-09-18,64.7,B\n2042-09-19,127.84,C\n2042-09-20,46.89,C\n2042-09-21,82.66,A\n2042-09-22,92.26,B\n2042-09-23,80.91,C\n2042-09-24,83.05,B\n2042-09-25,156.13,B\n2042-09-26,74.47,B\n2042-09-27,173.24,A\n2042-09-28,113.11,C\n2042-09-29,102.76,B\n2042-09-30,114.29,C\n2042-10-01,175.9,B\n2042-10-02,70.19,C\n2042-10-03,66.39,C\n2042-10-04,77.35,C\n2042-10-05,100.96,B\n2042-10-06,48.23,A\n2042-10-07,100.67,C\n2042-10-08,150.81,A\n2042-10-09,104.54,C\n2042-10-10,91.22,A\n2042-10-11,81.46,C\n2042-10-12,126.96,A\n2042-10-13,75.23,C\n2042-10-14,155.39,C\n2042-10-15,158.0,A\n2042-10-16,107.7,C\n2042-10-17,99.65,B\n2042-10-18,130.92,C\n2042-10-19,132.84,C\n2042-10-20,75.82,B\n2042-10-21,84.2,B\n2042-10-22,60.33,B\n2042-10-23,112.27,A\n2042-10-24,120.0,B\n2042-10-25,59.5,A\n2042-10-26,86.34,B\n2042-10-27,59.58,A\n2042-10-28,73.04,B\n2042-10-29,116.53,B\n2042-10-30,78.0,C\n2042-10-31,90.98,B\n2042-11-01,51.18,C\n2042-11-02,105.61,B\n2042-11-03,98.67,B\n2042-11-04,78.13,C\n2042-11-05,94.33,C\n2042-11-06,105.41,A\n2042-11-07,75.55,C\n2042-11-08,55.71,A\n2042-11-09,154.17,B\n2042-11-10,81.0,C\n2042-11-11,97.1,C\n2042-11-12,130.33,B\n2042-11-13,205.87,B\n2042-11-14,62.67,B\n2042-11-15,113.49,B\n2042-11-16,78.77,C\n2042-11-17,83.84,B\n2042-11-18,78.39,B\n2042-11-19,66.23,C\n2042-11-20,98.76,A\n2042-11-21,55.76,C\n2042-11-22,128.4,A\n2042-11-23,101.65,B\n2042-11-24,112.67,C\n2042-11-25,86.01,B\n2042-11-26,88.98,C\n2042-11-27,108.63,A\n2042-11-28,65.08,C\n2042-11-29,89.26,B\n2042-11-30,74.21,C\n2042-12-01,73.28,B\n2042-12-02,36.7,C\n2042-12-03,140.18,A\n2042-12-04,79.4,C\n2042-12-05,134.9,C\n2042-12-06,132.06,C\n2042-12-07,61.42,C\n2042-12-08,67.35,A\n2042-12-09,176.82,B\n2042-12-10,152.53,C\n2042-12-11,99.05,C\n2042-12-12,107.72,C\n2042-12-13,81.07,A\n2042-12-14,112.97,B\n2042-12-15,78.48,B\n2042-12-16,39.03,C\n2042-12-17,60.35,C\n2042-12-18,100.63,B\n2042-12-19,66.07,C\n2042-12-20,97.98,C\n2042-12-21,67.76,C\n2042-12-22,76.53,A\n2042-12-23,125.59,A\n2042-12-24,107.42,B\n2042-12-25,99.71,A\n2042-12-26,126.37,A\n2042-12-27,114.42,B\n2042-12-28,143.4,A\n2042-12-29,86.2,B\n2042-12-30,87.11,C\n2042-12-31,60.97,B\n2043-01-01,87.13,B\n2043-01-02,128.94,B\n2043-01-03,139.0,C\n2043-01-04,128.81,C\n2043-01-05,46.17,C\n2043-01-06,132.49,B\n2043-01-07,44.84,B\n2043-01-08,126.94,B\n2043-01-09,88.97,B\n2043-01-10,121.08,B\n2043-01-11,60.03,B\n2043-01-12,135.14,B\n2043-01-13,56.05,A\n2043-01-14,79.95,C\n2043-01-15,70.06,A\n2043-01-16,47.37,C\n2043-01-17,102.84,B\n2043-01-18,154.18,A\n2043-01-19,180.01,B\n2043-01-20,102.91,A\n2043-01-21,133.5,A\n2043-01-22,106.96,B\n2043-01-23,114.97,A\n2043-01-24,89.74,A\n2043-01-25,105.85,C\n2043-01-26,116.12,B\n2043-01-27,82.65,B\n2043-01-28,97.68,A\n2043-01-29,98.25,B\n2043-01-30,124.79,C\n2043-01-31,68.02,C\n2043-02-01,98.45,B\n2043-02-02,157.36,B\n2043-02-03,134.92,C\n2043-02-04,140.48,B\n2043-02-05,100.73,A\n2043-02-06,109.63,B\n2043-02-07,130.07,A\n2043-02-08,146.81,C\n2043-02-09,41.45,B\n2043-02-10,60.54,C\n2043-02-11,139.28,B\n2043-02-12,91.69,A\n2043-02-13,79.85,B\n2043-02-14,62.38,C\n2043-02-15,83.35,A\n2043-02-16,57.93,B\n2043-02-17,162.73,C\n2043-02-18,99.33,C\n2043-02-19,98.26,B\n2043-02-20,117.64,C\n2043-02-21,128.87,A\n2043-02-22,128.38,B\n2043-02-23,132.21,A\n2043-02-24,81.07,C\n2043-02-25,91.06,C\n2043-02-26,123.07,C\n2043-02-27,84.18,A\n2043-02-28,99.85,C\n2043-03-01,111.96,B\n2043-03-02,123.56,B\n2043-03-03,46.67,C\n2043-03-04,121.44,B\n2043-03-05,92.99,A\n2043-03-06,121.22,A\n2043-03-07,123.31,C\n2043-03-08,148.24,B\n2043-03-09,140.6,B\n2043-03-10,140.4,C\n2043-03-11,143.31,A\n2043-03-12,108.74,C\n2043-03-13,100.97,B\n2043-03-14,126.18,B\n2043-03-15,121.96,C\n2043-03-16,90.73,A\n2043-03-17,112.14,A\n2043-03-18,98.95,C\n2043-03-19,74.38,B\n2043-03-20,138.71,A\n2043-03-21,101.66,C\n2043-03-22,161.46,B\n2043-03-23,51.24,A\n2043-03-24,83.3,A\n2043-03-25,100.98,C\n2043-03-26,134.12,B\n2043-03-27,111.98,A\n2043-03-28,109.31,C\n2043-03-29,66.18,A\n2043-03-30,65.29,B\n2043-03-31,109.68,A\n2043-04-01,59.08,B\n2043-04-02,84.04,A\n2043-04-03,115.03,A\n2043-04-04,70.53,C\n2043-04-05,148.16,A\n2043-04-06,136.72,B\n2043-04-07,52.86,C\n2043-04-08,72.03,B\n2043-04-09,31.43,B\n2043-04-10,76.52,C\n2043-04-11,122.99,B\n2043-04-12,87.58,A\n2043-04-13,113.82,C\n2043-04-14,54.33,A\n2043-04-15,123.94,A\n2043-04-16,83.35,B\n2043-04-17,100.37,B\n2043-04-18,97.21,A\n2043-04-19,61.85,C\n2043-04-20,65.41,A\n2043-04-21,129.03,A\n2043-04-22,111.97,B\n2043-04-23,131.5,B\n2043-04-24,109.74,A\n2043-04-25,104.29,A\n2043-04-26,48.22,B\n2043-04-27,91.89,A\n2043-04-28,99.18,B\n2043-04-29,158.59,A\n2043-04-30,120.61,C\n2043-05-01,146.08,C\n2043-05-02,117.93,A\n2043-05-03,78.66,B\n2043-05-04,103.51,A\n2043-05-05,107.09,A\n2043-05-06,71.83,B\n2043-05-07,88.47,A\n2043-05-08,93.83,B\n2043-05-09,148.17,A\n2043-05-10,37.58,B\n2043-05-11,136.17,A\n2043-05-12,71.9,C\n2043-05-13,58.84,B\n2043-05-14,135.4,C\n2043-05-15,121.96,B\n2043-05-16,137.64,B\n2043-05-17,124.02,B\n2043-05-18,161.8,B\n2043-05-19,116.76,A\n2043-05-20,95.31,A\n2043-05-21,110.29,B\n2043-05-22,137.3,A\n2043-05-23,76.62,B\n2043-05-24,80.44,B\n2043-05-25,90.73,C\n2043-05-26,36.86,B\n2043-05-27,104.14,B\n2043-05-28,43.42,A\n2043-05-29,101.59,B\n2043-05-30,125.96,B\n2043-05-31,85.69,C\n2043-06-01,150.31,A\n2043-06-02,56.18,A\n2043-06-03,135.74,C\n2043-06-04,120.25,B\n2043-06-05,48.52,C\n2043-06-06,181.32,A\n2043-06-07,98.09,C\n2043-06-08,73.68,A\n2043-06-09,57.79,C\n2043-06-10,93.4,B\n2043-06-11,96.18,B\n2043-06-12,103.08,A\n2043-06-13,91.07,C\n2043-06-14,119.0,A\n2043-06-15,88.37,B\n2043-06-16,41.95,A\n2043-06-17,95.52,B\n2043-06-18,28.98,A\n2043-06-19,110.61,C\n2043-06-20,70.62,B\n2043-06-21,104.9,A\n2043-06-22,139.42,B\n2043-06-23,71.54,C\n2043-06-24,131.84,B\n2043-06-25,87.0,C\n2043-06-26,152.6,B\n2043-06-27,89.05,C\n2043-06-28,74.79,B\n2043-06-29,120.62,C\n2043-06-30,86.93,A\n2043-07-01,74.76,A\n2043-07-02,85.66,B\n2043-07-03,98.66,C\n2043-07-04,140.27,A\n2043-07-05,41.98,B\n2043-07-06,100.69,A\n2043-07-07,118.75,C\n2043-07-08,105.8,B\n2043-07-09,139.3,C\n2043-07-10,97.77,C\n2043-07-11,94.46,C\n2043-07-12,117.77,A\n2043-07-13,102.98,B\n2043-07-14,88.29,C\n2043-07-15,115.48,A\n2043-07-16,80.42,C\n2043-07-17,81.66,A\n2043-07-18,84.16,B\n2043-07-19,140.59,B\n2043-07-20,3.37,B\n2043-07-21,111.87,B\n2043-07-22,101.03,C\n2043-07-23,49.57,A\n2043-07-24,109.29,B\n2043-07-25,114.27,C\n2043-07-26,117.69,B\n2043-07-27,20.45,B\n2043-07-28,140.99,B\n2043-07-29,77.87,A\n2043-07-30,120.6,A\n2043-07-31,122.23,A\n2043-08-01,73.7,B\n2043-08-02,119.81,C\n2043-08-03,88.44,C\n2043-08-04,37.86,C\n2043-08-05,135.03,B\n2043-08-06,76.75,A\n2043-08-07,74.37,C\n2043-08-08,87.91,B\n2043-08-09,108.17,B\n2043-08-10,98.28,C\n2043-08-11,116.44,C\n2043-08-12,59.82,B\n2043-08-13,77.73,C\n2043-08-14,96.52,B\n2043-08-15,156.94,C\n2043-08-16,96.59,A\n2043-08-17,132.56,B\n2043-08-18,101.16,A\n2043-08-19,85.49,C\n2043-08-20,92.6,B\n2043-08-21,94.95,C\n2043-08-22,118.11,B\n2043-08-23,141.24,C\n2043-08-24,110.79,A\n2043-08-25,135.61,A\n2043-08-26,71.06,C\n2043-08-27,87.56,A\n2043-08-28,78.27,A\n2043-08-29,109.43,C\n2043-08-30,89.23,B\n2043-08-31,121.89,C\n2043-09-01,72.07,B\n2043-09-02,101.29,A\n2043-09-03,120.61,A\n2043-09-04,38.04,A\n2043-09-05,163.65,A\n2043-09-06,91.51,A\n2043-09-07,39.25,B\n2043-09-08,104.74,B\n2043-09-09,101.0,B\n2043-09-10,97.54,C\n2043-09-11,129.74,A\n2043-09-12,122.42,A\n2043-09-13,108.94,A\n2043-09-14,113.5,C\n2043-09-15,96.87,C\n2043-09-16,153.26,C\n2043-09-17,55.62,A\n2043-09-18,104.19,B\n2043-09-19,101.95,C\n2043-09-20,124.4,C\n2043-09-21,74.68,B\n2043-09-22,47.69,C\n2043-09-23,104.7,B\n2043-09-24,108.44,A\n2043-09-25,144.69,B\n2043-09-26,61.89,B\n2043-09-27,134.64,A\n2043-09-28,171.02,C\n2043-09-29,88.67,A\n2043-09-30,61.27,C\n2043-10-01,87.79,C\n2043-10-02,100.42,B\n2043-10-03,116.91,A\n2043-10-04,82.26,B\n2043-10-05,146.66,A\n2043-10-06,66.56,A\n2043-10-07,47.82,B\n2043-10-08,133.2,C\n2043-10-09,31.76,C\n2043-10-10,93.88,A\n2043-10-11,193.53,C\n2043-10-12,99.64,B\n2043-10-13,66.38,B\n2043-10-14,98.28,B\n2043-10-15,98.48,A\n2043-10-16,104.82,A\n2043-10-17,119.87,C\n2043-10-18,68.83,B\n2043-10-19,150.63,B\n2043-10-20,107.35,A\n2043-10-21,121.8,A\n2043-10-22,81.16,A\n2043-10-23,138.59,B\n2043-10-24,92.29,B\n2043-10-25,108.33,C\n2043-10-26,72.01,B\n2043-10-27,70.09,C\n2043-10-28,120.65,C\n2043-10-29,86.69,B\n2043-10-30,74.39,C\n2043-10-31,80.7,C\n2043-11-01,120.97,A\n2043-11-02,81.13,B\n2043-11-03,96.47,C\n2043-11-04,100.45,C\n2043-11-05,42.48,C\n2043-11-06,55.87,B\n2043-11-07,110.09,A\n2043-11-08,77.89,A\n2043-11-09,131.7,B\n2043-11-10,129.0,B\n2043-11-11,121.58,B\n2043-11-12,154.59,B\n2043-11-13,97.51,A\n2043-11-14,119.47,C\n2043-11-15,82.47,C\n2043-11-16,173.57,B\n2043-11-17,159.51,B\n2043-11-18,100.36,C\n2043-11-19,111.6,A\n2043-11-20,140.78,B\n2043-11-21,95.59,B\n2043-11-22,80.3,A\n2043-11-23,126.22,A\n2043-11-24,102.35,A\n2043-11-25,105.49,B\n2043-11-26,74.97,A\n2043-11-27,48.47,B\n2043-11-28,107.08,B\n2043-11-29,127.15,A\n2043-11-30,93.23,C\n2043-12-01,143.09,A\n2043-12-02,78.88,C\n2043-12-03,68.36,A\n2043-12-04,97.98,A\n2043-12-05,97.52,B\n2043-12-06,123.1,B\n2043-12-07,76.75,B\n2043-12-08,141.04,B\n2043-12-09,159.01,B\n2043-12-10,98.83,B\n2043-12-11,137.49,B\n2043-12-12,68.31,C\n2043-12-13,132.26,C\n2043-12-14,131.42,B\n2043-12-15,125.9,C\n2043-12-16,99.53,A\n2043-12-17,11.37,C\n2043-12-18,80.75,B\n2043-12-19,81.22,C\n2043-12-20,70.65,A\n2043-12-21,103.67,C\n2043-12-22,87.43,B\n2043-12-23,75.26,A\n2043-12-24,68.24,A\n2043-12-25,78.53,C\n2043-12-26,133.64,A\n2043-12-27,80.17,A\n2043-12-28,84.55,A\n2043-12-29,86.3,B\n2043-12-30,110.17,A\n2043-12-31,113.69,C\n2044-01-01,8.66,C\n2044-01-02,130.65,A\n2044-01-03,143.09,C\n2044-01-04,68.49,A\n2044-01-05,70.83,C\n2044-01-06,86.49,A\n2044-01-07,77.32,A\n2044-01-08,106.72,A\n2044-01-09,41.6,B\n2044-01-10,66.46,C\n2044-01-11,146.18,A\n2044-01-12,72.15,B\n2044-01-13,137.3,B\n2044-01-14,110.66,A\n2044-01-15,45.72,A\n2044-01-16,119.06,A\n2044-01-17,96.47,A\n2044-01-18,86.34,A\n2044-01-19,107.35,C\n2044-01-20,114.24,A\n2044-01-21,121.23,A\n2044-01-22,135.35,C\n2044-01-23,112.24,B\n2044-01-24,58.73,C\n2044-01-25,85.79,B\n2044-01-26,188.29,B\n2044-01-27,121.73,C\n2044-01-28,171.08,A\n2044-01-29,141.86,B\n2044-01-30,107.25,A\n2044-01-31,109.04,C\n2044-02-01,98.9,B\n2044-02-02,47.87,A\n2044-02-03,100.54,B\n2044-02-04,136.32,B\n2044-02-05,82.01,C\n2044-02-06,112.36,A\n2044-02-07,125.55,A\n2044-02-08,72.71,B\n2044-02-09,86.46,A\n2044-02-10,69.46,B\n2044-02-11,75.34,C\n2044-02-12,61.37,C\n2044-02-13,72.99,A\n2044-02-14,142.58,C\n2044-02-15,72.54,C\n2044-02-16,103.79,A\n2044-02-17,84.91,B\n2044-02-18,135.37,C\n2044-02-19,139.33,A\n2044-02-20,125.92,A\n2044-02-21,109.18,A\n2044-02-22,93.07,C\n2044-02-23,64.24,C\n2044-02-24,158.31,B\n2044-02-25,77.21,B\n2044-02-26,119.91,B\n2044-02-27,131.79,B\n2044-02-28,83.34,B\n2044-02-29,131.14,B\n2044-03-01,122.93,A\n2044-03-02,151.84,B\n2044-03-03,85.92,C\n2044-03-04,61.42,B\n2044-03-05,84.84,C\n2044-03-06,106.27,B\n2044-03-07,101.75,A\n2044-03-08,88.2,A\n2044-03-09,42.72,C\n2044-03-10,114.11,B\n2044-03-11,159.17,A\n2044-03-12,113.93,A\n2044-03-13,118.27,B\n2044-03-14,117.17,C\n2044-03-15,111.91,C\n2044-03-16,127.95,C\n2044-03-17,111.86,A\n2044-03-18,90.62,B\n2044-03-19,76.12,A\n2044-03-20,92.53,A\n2044-03-21,109.42,A\n2044-03-22,90.52,A\n2044-03-23,109.43,B\n2044-03-24,113.17,A\n2044-03-25,125.0,A\n2044-03-26,138.48,C\n2044-03-27,102.34,A\n2044-03-28,60.77,B\n2044-03-29,89.65,A\n2044-03-30,101.32,B\n2044-03-31,44.93,B\n2044-04-01,141.66,B\n2044-04-02,104.27,C\n2044-04-03,111.44,C\n2044-04-04,140.97,A\n2044-04-05,81.1,A\n2044-04-06,117.22,A\n2044-04-07,96.7,B\n2044-04-08,94.48,B\n2044-04-09,76.83,B\n2044-04-10,149.97,A\n2044-04-11,88.73,A\n2044-04-12,120.98,A\n2044-04-13,43.75,A\n2044-04-14,135.4,C\n2044-04-15,131.57,C\n2044-04-16,102.92,C\n2044-04-17,93.19,A\n2044-04-18,112.0,C\n2044-04-19,168.66,C\n2044-04-20,47.1,A\n2044-04-21,123.46,C\n2044-04-22,72.65,A\n2044-04-23,99.4,A\n2044-04-24,92.56,A\n2044-04-25,87.15,B\n2044-04-26,99.69,A\n2044-04-27,65.97,C\n2044-04-28,130.88,C\n2044-04-29,46.18,A\n2044-04-30,116.19,B\n2044-05-01,90.2,B\n2044-05-02,64.87,B\n2044-05-03,106.07,C\n2044-05-04,53.29,B\n2044-05-05,101.19,B\n2044-05-06,57.31,A\n2044-05-07,113.84,B\n2044-05-08,72.27,B\n2044-05-09,86.47,B\n2044-05-10,108.82,A\n2044-05-11,76.82,A\n2044-05-12,98.26,B\n2044-05-13,54.59,C\n2044-05-14,118.93,C\n2044-05-15,152.95,A\n2044-05-16,183.77,B\n2044-05-17,102.74,C\n2044-05-18,123.65,B\n2044-05-19,46.58,A\n2044-05-20,145.08,C\n2044-05-21,123.29,A\n2044-05-22,85.48,B\n2044-05-23,137.12,A\n2044-05-24,92.37,A\n2044-05-25,86.26,A\n2044-05-26,114.26,A\n2044-05-27,68.1,C\n2044-05-28,130.65,B\n2044-05-29,68.32,C\n2044-05-30,68.5,B\n2044-05-31,72.71,B\n2044-06-01,122.79,B\n2044-06-02,81.5,B\n2044-06-03,59.56,B\n2044-06-04,81.83,C\n2044-06-05,99.04,B\n2044-06-06,64.57,A\n2044-06-07,65.45,C\n2044-06-08,163.34,C\n2044-06-09,74.18,B\n2044-06-10,90.31,A\n2044-06-11,88.98,A\n2044-06-12,87.36,B\n2044-06-13,118.64,B\n2044-06-14,128.44,A\n2044-06-15,90.82,C\n2044-06-16,62.91,C\n2044-06-17,138.23,B\n2044-06-18,97.14,C\n2044-06-19,151.91,C\n2044-06-20,32.63,A\n2044-06-21,105.86,C\n2044-06-22,157.56,A\n2044-06-23,89.4,B\n2044-06-24,93.24,B\n2044-06-25,108.87,A\n2044-06-26,83.24,A\n2044-06-27,90.64,B\n2044-06-28,89.87,A\n2044-06-29,81.18,B\n2044-06-30,136.87,B\n2044-07-01,113.83,B\n2044-07-02,107.52,B\n2044-07-03,124.26,B\n2044-07-04,92.07,B\n2044-07-05,76.77,C\n2044-07-06,81.92,C\n2044-07-07,100.05,B\n2044-07-08,68.22,C\n2044-07-09,84.99,A\n2044-07-10,84.52,A\n2044-07-11,163.74,B\n2044-07-12,16.79,C\n2044-07-13,94.61,A\n2044-07-14,151.77,A\n2044-07-15,103.66,A\n2044-07-16,122.6,A\n2044-07-17,102.99,C\n2044-07-18,79.98,A\n2044-07-19,32.74,A\n2044-07-20,106.16,A\n2044-07-21,76.73,B\n2044-07-22,75.99,A\n2044-07-23,85.03,A\n2044-07-24,152.35,C\n2044-07-25,172.15,A\n2044-07-26,106.52,A\n2044-07-27,86.06,A\n2044-07-28,64.68,B\n2044-07-29,114.06,C\n2044-07-30,153.49,C\n2044-07-31,92.58,B\n2044-08-01,28.63,B\n2044-08-02,120.1,C\n2044-08-03,129.56,A\n2044-08-04,52.42,C\n2044-08-05,105.29,B\n2044-08-06,113.54,A\n2044-08-07,97.81,C\n2044-08-08,76.28,A\n2044-08-09,130.48,A\n2044-08-10,87.9,B\n2044-08-11,55.44,C\n2044-08-12,38.52,B\n2044-08-13,137.83,C\n2044-08-14,118.5,C\n2044-08-15,102.64,A\n2044-08-16,86.79,A\n2044-08-17,84.13,A\n2044-08-18,67.73,B\n2044-08-19,87.06,A\n2044-08-20,36.4,B\n2044-08-21,77.92,A\n2044-08-22,90.67,B\n2044-08-23,118.3,C\n2044-08-24,98.8,B\n2044-08-25,123.85,B\n2044-08-26,140.47,B\n2044-08-27,131.75,C\n2044-08-28,114.12,B\n2044-08-29,100.36,C\n2044-08-30,70.58,A\n2044-08-31,122.2,A\n2044-09-01,150.39,C\n2044-09-02,110.26,B\n2044-09-03,157.52,A\n2044-09-04,54.23,B\n2044-09-05,65.38,B\n2044-09-06,125.66,C\n2044-09-07,107.93,A\n2044-09-08,139.41,C\n2044-09-09,99.54,C\n2044-09-10,73.96,C\n2044-09-11,93.59,C\n2044-09-12,54.02,B\n2044-09-13,66.14,A\n2044-09-14,129.17,A\n2044-09-15,126.18,A\n2044-09-16,110.89,B\n2044-09-17,48.37,B\n2044-09-18,89.25,C\n2044-09-19,63.25,C\n2044-09-20,135.3,C\n2044-09-21,82.49,C\n2044-09-22,67.43,A\n2044-09-23,100.89,C\n2044-09-24,80.8,B\n2044-09-25,144.51,B\n2044-09-26,77.96,B\n2044-09-27,58.02,C\n2044-09-28,35.41,A\n2044-09-29,99.08,A\n2044-09-30,83.14,C\n2044-10-01,57.13,C\n2044-10-02,135.68,B\n2044-10-03,120.02,B\n2044-10-04,99.15,A\n2044-10-05,140.93,B\n2044-10-06,119.88,B\n2044-10-07,75.31,C\n2044-10-08,68.82,A\n2044-10-09,124.54,C\n2044-10-10,111.89,A\n2044-10-11,105.09,C\n2044-10-12,74.61,A\n2044-10-13,118.24,B\n2044-10-14,77.34,A\n2044-10-15,96.94,A\n2044-10-16,143.41,C\n2044-10-17,80.17,A\n2044-10-18,52.81,A\n2044-10-19,68.42,A\n2044-10-20,129.83,C\n2044-10-21,107.64,A\n2044-10-22,96.13,C\n2044-10-23,84.73,B\n2044-10-24,114.56,C\n2044-10-25,74.53,A\n2044-10-26,109.17,B\n2044-10-27,78.06,A\n2044-10-28,73.07,C\n2044-10-29,80.88,B\n2044-10-30,88.74,B\n2044-10-31,98.54,B\n2044-11-01,107.06,A\n2044-11-02,-10.65,B\n2044-11-03,75.77,B\n2044-11-04,88.25,B\n2044-11-05,145.15,B\n2044-11-06,67.6,C\n2044-11-07,104.88,A\n2044-11-08,144.85,A\n2044-11-09,60.61,B\n2044-11-10,111.82,C\n2044-11-11,75.93,B\n2044-11-12,84.86,A\n2044-11-13,75.91,A\n2044-11-14,78.04,A\n2044-11-15,181.66,B\n2044-11-16,125.33,B\n2044-11-17,133.05,C\n2044-11-18,114.76,C\n2044-11-19,51.81,A\n2044-11-20,111.85,A\n2044-11-21,132.19,C\n2044-11-22,74.4,A\n2044-11-23,128.96,C\n2044-11-24,121.13,B\n2044-11-25,155.56,A\n2044-11-26,87.47,A\n2044-11-27,67.07,C\n2044-11-28,89.15,B\n2044-11-29,143.07,A\n2044-11-30,45.15,A\n2044-12-01,37.95,C\n2044-12-02,102.68,B\n2044-12-03,86.72,C\n2044-12-04,100.23,B\n2044-12-05,128.36,B\n2044-12-06,83.42,C\n2044-12-07,43.81,C\n2044-12-08,161.33,B\n2044-12-09,70.08,C\n2044-12-10,68.23,A\n2044-12-11,133.11,A\n2044-12-12,88.47,A\n2044-12-13,98.1,A\n2044-12-14,114.48,A\n2044-12-15,132.51,C\n2044-12-16,90.0,B\n2044-12-17,145.6,C\n2044-12-18,35.93,A\n2044-12-19,122.79,A\n2044-12-20,74.81,C\n2044-12-21,141.17,C\n2044-12-22,104.26,B\n2044-12-23,79.81,A\n2044-12-24,119.89,C\n2044-12-25,126.8,B\n2044-12-26,119.26,A\n2044-12-27,69.11,A\n2044-12-28,85.48,A\n2044-12-29,99.88,C\n2044-12-30,98.76,A\n2044-12-31,122.49,A\n2045-01-01,39.55,B\n2045-01-02,110.03,C\n2045-01-03,91.3,B\n2045-01-04,74.84,C\n2045-01-05,77.73,A\n2045-01-06,93.56,B\n2045-01-07,-8.03,C\n2045-01-08,78.96,C\n2045-01-09,73.73,A\n2045-01-10,115.98,A\n2045-01-11,75.83,B\n2045-01-12,93.55,B\n2045-01-13,146.27,C\n2045-01-14,129.83,B\n2045-01-15,127.07,A\n2045-01-16,85.0,B\n2045-01-17,116.29,B\n2045-01-18,92.98,C\n2045-01-19,94.12,A\n2045-01-20,112.29,C\n2045-01-21,82.09,A\n2045-01-22,144.79,A\n2045-01-23,95.2,C\n2045-01-24,118.55,B\n2045-01-25,143.67,C\n2045-01-26,130.95,B\n2045-01-27,57.73,C\n2045-01-28,113.35,C\n2045-01-29,125.52,B\n2045-01-30,126.76,B\n2045-01-31,66.6,B\n2045-02-01,80.29,C\n2045-02-02,129.84,B\n2045-02-03,77.15,B\n2045-02-04,91.01,B\n2045-02-05,147.28,A\n2045-02-06,98.57,B\n2045-02-07,78.2,B\n2045-02-08,73.67,B\n2045-02-09,109.98,A\n2045-02-10,60.01,A\n2045-02-11,92.58,B\n2045-02-12,110.0,B\n2045-02-13,138.97,B\n2045-02-14,120.03,B\n2045-02-15,78.58,A\n2045-02-16,135.35,B\n2045-02-17,91.01,A\n2045-02-18,62.93,C\n2045-02-19,94.84,A\n2045-02-20,143.15,C\n2045-02-21,104.96,A\n2045-02-22,100.38,A\n2045-02-23,149.12,A\n2045-02-24,57.06,B\n2045-02-25,61.43,A\n2045-02-26,51.52,B\n2045-02-27,111.18,A\n2045-02-28,104.99,A\n2045-03-01,146.28,C\n2045-03-02,107.3,A\n2045-03-03,87.88,B\n2045-03-04,100.05,C\n2045-03-05,103.53,C\n2045-03-06,49.85,A\n2045-03-07,181.31,A\n2045-03-08,115.6,C\n2045-03-09,106.3,B\n2045-03-10,118.45,A\n2045-03-11,121.28,C\n2045-03-12,95.78,B\n2045-03-13,81.06,A\n2045-03-14,117.98,C\n2045-03-15,111.57,C\n2045-03-16,75.7,A\n2045-03-17,142.57,B\n2045-03-18,108.8,B\n2045-03-19,121.89,C\n2045-03-20,141.76,B\n2045-03-21,100.66,C\n2045-03-22,109.51,A\n2045-03-23,124.3,B\n2045-03-24,78.25,A\n2045-03-25,101.62,C\n2045-03-26,103.77,A\n2045-03-27,71.32,B\n2045-03-28,169.53,A\n2045-03-29,57.78,B\n2045-03-30,61.06,B\n2045-03-31,139.43,B\n2045-04-01,121.4,B\n2045-04-02,69.22,B\n2045-04-03,118.22,C\n2045-04-04,144.04,A\n2045-04-05,99.09,C\n2045-04-06,177.96,A\n2045-04-07,48.28,A\n2045-04-08,120.81,A\n2045-04-09,135.0,C\n2045-04-10,126.74,B\n2045-04-11,93.87,B\n2045-04-12,91.29,C\n2045-04-13,113.32,B\n2045-04-14,124.47,A\n2045-04-15,118.07,B\n2045-04-16,131.2,B\n2045-04-17,69.43,B\n2045-04-18,93.17,A\n2045-04-19,71.8,B\n2045-04-20,88.42,B\n2045-04-21,130.46,B\n2045-04-22,103.24,C\n2045-04-23,178.21,C\n2045-04-24,105.8,B\n2045-04-25,101.79,C\n2045-04-26,103.19,C\n2045-04-27,80.87,A\n2045-04-28,98.26,C\n2045-04-29,133.83,C\n2045-04-30,103.42,B\n2045-05-01,76.2,A\n2045-05-02,72.25,A\n2045-05-03,86.49,A\n2045-05-04,102.22,C\n2045-05-05,135.81,B\n2045-05-06,59.79,A\n2045-05-07,90.41,A\n2045-05-08,104.33,C\n2045-05-09,44.99,A\n2045-05-10,74.99,B\n2045-05-11,79.6,A\n2045-05-12,76.12,B\n2045-05-13,105.76,B\n2045-05-14,95.18,B\n2045-05-15,83.34,C\n2045-05-16,112.84,C\n2045-05-17,151.69,B\n2045-05-18,149.18,B\n2045-05-19,42.32,A\n2045-05-20,117.31,A\n2045-05-21,94.66,A\n2045-05-22,104.65,C\n2045-05-23,88.05,A\n2045-05-24,103.53,B\n2045-05-25,104.54,B\n2045-05-26,82.63,C\n2045-05-27,73.11,C\n2045-05-28,86.54,A\n2045-05-29,107.03,C\n2045-05-30,117.97,B\n2045-05-31,125.6,B\n2045-06-01,85.17,C\n2045-06-02,108.52,B\n2045-06-03,128.88,C\n2045-06-04,134.97,B\n2045-06-05,98.24,A\n2045-06-06,93.37,C\n2045-06-07,105.73,C\n2045-06-08,65.51,C\n2045-06-09,94.19,B\n2045-06-10,122.34,A\n2045-06-11,119.26,C\n2045-06-12,91.89,B\n2045-06-13,106.53,C\n2045-06-14,75.26,B\n2045-06-15,121.1,A\n2045-06-16,97.67,B\n2045-06-17,91.72,B\n2045-06-18,89.15,C\n2045-06-19,98.11,B\n2045-06-20,86.56,C\n2045-06-21,119.74,B\n2045-06-22,88.14,A\n2045-06-23,147.79,B\n2045-06-24,143.96,C\n2045-06-25,73.25,B\n2045-06-26,102.09,B\n2045-06-27,80.48,A\n2045-06-28,90.2,C\n2045-06-29,160.07,A\n2045-06-30,90.77,B\n2045-07-01,110.81,A\n2045-07-02,116.24,B\n2045-07-03,69.98,B\n2045-07-04,74.35,A\n2045-07-05,63.82,A\n2045-07-06,73.42,B\n2045-07-07,14.5,A\n2045-07-08,116.34,A\n2045-07-09,161.63,B\n2045-07-10,104.43,C\n2045-07-11,57.51,A\n2045-07-12,80.72,A\n2045-07-13,86.23,C\n2045-07-14,69.47,A\n2045-07-15,125.81,B\n2045-07-16,124.31,A\n2045-07-17,65.34,B\n2045-07-18,121.75,C\n2045-07-19,145.8,C\n2045-07-20,157.08,C\n2045-07-21,185.96,A\n2045-07-22,72.94,C\n2045-07-23,184.73,A\n2045-07-24,89.37,A\n2045-07-25,82.23,A\n2045-07-26,109.81,C\n2045-07-27,82.54,C\n2045-07-28,111.97,A\n2045-07-29,60.4,C\n2045-07-30,92.84,A\n2045-07-31,138.57,C\n2045-08-01,98.29,B\n2045-08-02,104.25,C\n2045-08-03,114.26,C\n2045-08-04,135.12,C\n2045-08-05,50.34,A\n2045-08-06,117.54,A\n2045-08-07,91.96,B\n2045-08-08,105.0,B\n2045-08-09,131.8,B\n2045-08-10,64.17,A\n2045-08-11,69.62,B\n2045-08-12,41.94,B\n2045-08-13,83.53,C\n2045-08-14,139.91,B\n2045-08-15,96.38,C\n2045-08-16,59.8,A\n2045-08-17,85.43,A\n2045-08-18,55.36,C\n2045-08-19,66.24,B\n2045-08-20,111.66,A\n2045-08-21,64.78,C\n2045-08-22,133.38,A\n2045-08-23,97.87,A\n2045-08-24,102.57,B\n2045-08-25,91.65,A\n2045-08-26,123.19,C\n2045-08-27,123.49,B\n2045-08-28,110.05,C\n2045-08-29,116.94,C\n2045-08-30,93.64,C\n2045-08-31,116.27,B\n2045-09-01,89.86,A\n2045-09-02,100.07,C\n2045-09-03,91.76,A\n2045-09-04,86.28,A\n2045-09-05,79.33,C\n2045-09-06,114.03,A\n2045-09-07,139.32,B\n2045-09-08,135.13,B\n2045-09-09,62.3,A\n2045-09-10,138.68,A\n2045-09-11,90.46,B\n2045-09-12,118.02,A\n2045-09-13,72.81,A\n2045-09-14,104.26,A\n2045-09-15,109.41,A\n2045-09-16,55.46,C\n2045-09-17,118.25,A\n2045-09-18,140.38,B\n2045-09-19,94.76,C\n2045-09-20,140.81,C\n2045-09-21,94.31,B\n2045-09-22,125.49,A\n2045-09-23,33.44,C\n2045-09-24,172.56,C\n2045-09-25,144.86,A\n2045-09-26,91.46,A\n2045-09-27,40.84,B\n2045-09-28,108.64,C\n2045-09-29,56.68,A\n2045-09-30,113.35,A\n2045-10-01,112.35,B\n2045-10-02,71.76,C\n2045-10-03,133.63,A\n2045-10-04,124.17,B\n2045-10-05,129.27,C\n2045-10-06,100.27,C\n2045-10-07,57.53,C\n2045-10-08,129.34,C\n2045-10-09,91.89,B\n2045-10-10,58.31,A\n2045-10-11,140.56,C\n2045-10-12,162.98,C\n2045-10-13,98.76,A\n2045-10-14,164.77,C\n2045-10-15,102.74,C\n2045-10-16,88.59,A\n2045-10-17,81.09,A\n2045-10-18,149.11,C\n2045-10-19,76.6,A\n2045-10-20,122.06,B\n2045-10-21,83.23,B\n2045-10-22,86.19,C\n2045-10-23,130.91,C\n2045-10-24,88.6,A\n2045-10-25,87.86,A\n2045-10-26,133.98,C\n2045-10-27,147.92,C\n2045-10-28,119.34,B\n2045-10-29,80.36,A\n2045-10-30,84.68,C\n2045-10-31,102.4,C\n2045-11-01,109.63,A\n2045-11-02,70.36,A\n2045-11-03,121.69,C\n2045-11-04,102.57,C\n2045-11-05,48.53,C\n2045-11-06,72.13,A\n2045-11-07,84.88,C\n2045-11-08,97.81,C\n2045-11-09,156.68,C\n2045-11-10,107.15,B\n2045-11-11,74.78,B\n2045-11-12,106.62,A\n2045-11-13,89.44,C\n2045-11-14,109.74,C\n2045-11-15,117.42,A\n2045-11-16,136.28,A\n2045-11-17,99.27,A\n2045-11-18,149.17,C\n2045-11-19,127.24,B\n2045-11-20,78.78,A\n2045-11-21,166.3,C\n2045-11-22,109.81,A\n2045-11-23,74.95,C\n2045-11-24,149.59,A\n2045-11-25,162.33,C\n2045-11-26,99.01,A\n2045-11-27,84.89,B\n2045-11-28,94.83,A\n2045-11-29,121.44,A\n2045-11-30,138.34,B\n2045-12-01,117.11,A\n2045-12-02,103.05,B\n2045-12-03,144.94,C\n2045-12-04,90.61,B\n2045-12-05,130.93,B\n2045-12-06,91.36,C\n2045-12-07,112.93,B\n2045-12-08,96.59,B\n2045-12-09,98.32,C\n2045-12-10,88.68,A\n2045-12-11,140.35,A\n2045-12-12,78.01,B\n2045-12-13,77.79,A\n2045-12-14,163.63,A\n2045-12-15,129.34,B\n2045-12-16,28.45,C\n2045-12-17,83.04,A\n2045-12-18,45.64,B\n2045-12-19,71.48,C\n2045-12-20,5.84,A\n2045-12-21,95.89,C\n2045-12-22,65.74,B\n2045-12-23,101.8,C\n2045-12-24,51.25,C\n2045-12-25,102.49,B\n2045-12-26,87.55,A\n2045-12-27,145.06,A\n2045-12-28,142.34,C\n2045-12-29,123.56,C\n2045-12-30,90.2,C\n2045-12-31,159.56,C\n2046-01-01,53.41,A\n2046-01-02,57.89,B\n2046-01-03,97.49,B\n2046-01-04,167.62,B\n2046-01-05,93.93,C\n2046-01-06,94.5,C\n2046-01-07,56.74,B\n2046-01-08,128.38,A\n2046-01-09,128.6,B\n2046-01-10,123.67,A\n2046-01-11,81.12,C\n2046-01-12,78.8,B\n2046-01-13,61.21,B\n2046-01-14,102.52,C\n2046-01-15,67.59,A\n2046-01-16,64.68,B\n2046-01-17,87.29,B\n2046-01-18,85.33,A\n2046-01-19,83.86,B\n2046-01-20,113.99,B\n2046-01-21,102.37,C\n2046-01-22,91.36,A\n2046-01-23,105.49,B\n2046-01-24,70.25,B\n2046-01-25,103.16,C\n2046-01-26,104.52,B\n2046-01-27,54.91,C\n2046-01-28,129.87,B\n2046-01-29,73.56,C\n2046-01-30,102.71,A\n2046-01-31,134.93,A\n2046-02-01,127.77,B\n2046-02-02,101.03,B\n2046-02-03,89.13,C\n2046-02-04,128.43,C\n2046-02-05,120.24,A\n2046-02-06,81.45,B\n2046-02-07,76.15,C\n2046-02-08,37.83,A\n2046-02-09,104.62,A\n2046-02-10,97.52,C\n2046-02-11,134.09,B\n2046-02-12,110.77,A\n2046-02-13,140.03,C\n2046-02-14,79.08,A\n2046-02-15,134.14,B\n2046-02-16,84.13,C\n2046-02-17,123.33,B\n2046-02-18,56.24,B\n2046-02-19,107.05,A\n2046-02-20,40.46,B\n2046-02-21,91.99,A\n2046-02-22,113.21,C\n2046-02-23,84.37,C\n2046-02-24,68.49,A\n2046-02-25,125.38,A\n2046-02-26,96.9,A\n2046-02-27,85.31,B\n2046-02-28,101.26,C\n2046-03-01,179.1,C\n2046-03-02,134.61,B\n2046-03-03,134.88,A\n2046-03-04,113.07,C\n2046-03-05,104.99,B\n2046-03-06,28.12,A\n2046-03-07,79.63,C\n2046-03-08,85.58,A\n2046-03-09,43.75,B\n2046-03-10,65.28,C\n2046-03-11,40.57,A\n2046-03-12,98.85,A\n2046-03-13,124.56,A\n2046-03-14,133.2,B\n2046-03-15,112.44,A\n2046-03-16,71.1,C\n2046-03-17,42.04,C\n2046-03-18,47.29,A\n2046-03-19,61.93,B\n2046-03-20,73.33,C\n2046-03-21,95.21,C\n2046-03-22,69.58,C\n2046-03-23,110.17,A\n2046-03-24,93.03,C\n2046-03-25,100.32,A\n2046-03-26,134.21,A\n2046-03-27,64.59,A\n2046-03-28,112.56,A\n2046-03-29,99.58,A\n2046-03-30,90.0,C\n2046-03-31,126.56,A\n2046-04-01,126.37,A\n2046-04-02,71.61,A\n2046-04-03,94.54,A\n2046-04-04,108.44,A\n2046-04-05,91.81,A\n2046-04-06,117.55,C\n2046-04-07,108.44,A\n2046-04-08,154.98,A\n2046-04-09,54.09,C\n2046-04-10,96.33,C\n2046-04-11,72.74,B\n2046-04-12,118.12,C\n2046-04-13,51.48,C\n2046-04-14,110.39,A\n2046-04-15,81.1,B\n2046-04-16,77.98,A\n2046-04-17,56.61,B\n2046-04-18,86.05,A\n2046-04-19,132.54,C\n2046-04-20,99.24,C\n2046-04-21,118.83,B\n2046-04-22,149.94,A\n2046-04-23,107.91,A\n2046-04-24,83.24,C\n2046-04-25,114.02,C\n2046-04-26,83.67,B\n2046-04-27,84.87,C\n2046-04-28,54.4,B\n2046-04-29,127.79,B\n2046-04-30,88.42,B\n2046-05-01,105.46,B\n2046-05-02,68.92,C\n2046-05-03,113.08,B\n2046-05-04,77.14,C\n2046-05-05,95.75,B\n2046-05-06,126.33,C\n2046-05-07,123.72,B\n2046-05-08,99.34,C\n2046-05-09,46.96,A\n2046-05-10,189.78,B\n2046-05-11,152.38,B\n2046-05-12,121.68,A\n2046-05-13,77.4,A\n2046-05-14,96.59,B\n2046-05-15,83.7,C\n2046-05-16,137.92,B\n2046-05-17,107.88,C\n2046-05-18,76.48,A\n2046-05-19,85.88,C\n2046-05-20,133.64,B\n2046-05-21,148.0,A\n2046-05-22,145.85,A\n2046-05-23,122.71,B\n2046-05-24,92.42,B\n2046-05-25,2.75,A\n2046-05-26,108.91,C\n2046-05-27,99.95,C\n2046-05-28,104.2,C\n2046-05-29,101.62,B\n2046-05-30,60.21,A\n2046-05-31,116.72,C\n2046-06-01,118.97,A\n2046-06-02,120.47,C\n2046-06-03,128.4,A\n2046-06-04,118.12,B\n2046-06-05,87.7,B\n2046-06-06,97.87,B\n2046-06-07,102.73,C\n2046-06-08,103.23,B\n2046-06-09,129.95,B\n2046-06-10,119.31,B\n2046-06-11,162.32,A\n2046-06-12,166.19,A\n2046-06-13,102.39,A\n2046-06-14,-15.1,A\n2046-06-15,137.1,A\n2046-06-16,83.79,A\n2046-06-17,75.96,A\n2046-06-18,88.05,B\n2046-06-19,86.6,B\n2046-06-20,118.37,C\n2046-06-21,53.22,B\n2046-06-22,67.67,A\n2046-06-23,110.29,A\n2046-06-24,118.69,C\n2046-06-25,119.25,B\n2046-06-26,76.39,B\n2046-06-27,70.65,C\n2046-06-28,84.8,A\n2046-06-29,107.2,A\n2046-06-30,144.56,A\n2046-07-01,38.58,A\n2046-07-02,93.38,C\n2046-07-03,60.02,B\n2046-07-04,50.43,A\n2046-07-05,129.01,B\n2046-07-06,116.34,B\n2046-07-07,113.48,A\n2046-07-08,127.63,A\n2046-07-09,85.61,A\n2046-07-10,87.02,B\n2046-07-11,165.57,C\n2046-07-12,92.6,A\n2046-07-13,122.05,B\n2046-07-14,100.19,B\n2046-07-15,44.55,A\n2046-07-16,38.66,C\n2046-07-17,82.63,A\n2046-07-18,131.68,A\n2046-07-19,123.65,C\n2046-07-20,127.42,A\n2046-07-21,70.39,A\n2046-07-22,148.48,C\n2046-07-23,71.05,B\n2046-07-24,106.38,B\n2046-07-25,99.69,B\n2046-07-26,94.78,B\n2046-07-27,140.16,B\n2046-07-28,78.85,A\n2046-07-29,59.51,C\n2046-07-30,100.6,B\n2046-07-31,88.01,C\n2046-08-01,201.32,B\n2046-08-02,120.3,A\n2046-08-03,87.72,B\n2046-08-04,68.32,B\n2046-08-05,43.59,C\n2046-08-06,69.24,C\n2046-08-07,103.45,A\n2046-08-08,142.82,C\n2046-08-09,133.65,C\n2046-08-10,106.87,C\n2046-08-11,90.3,A\n2046-08-12,108.06,A\n2046-08-13,84.44,A\n2046-08-14,151.55,C\n2046-08-15,94.32,C\n2046-08-16,97.41,A\n2046-08-17,33.53,C\n2046-08-18,94.14,C\n2046-08-19,125.09,B\n2046-08-20,67.97,A\n2046-08-21,77.48,A\n2046-08-22,123.78,C\n2046-08-23,55.7,C\n2046-08-24,131.73,B\n2046-08-25,84.04,A\n2046-08-26,88.34,A\n2046-08-27,66.28,C\n2046-08-28,111.34,A\n2046-08-29,173.8,C\n2046-08-30,123.44,B\n2046-08-31,74.07,C\n2046-09-01,134.07,B\n2046-09-02,90.5,C\n2046-09-03,141.23,B\n2046-09-04,124.2,B\n2046-09-05,11.71,A\n2046-09-06,69.88,C\n2046-09-07,128.33,A\n2046-09-08,104.55,A\n2046-09-09,69.29,C\n2046-09-10,80.99,A\n2046-09-11,42.81,A\n2046-09-12,97.35,A\n2046-09-13,166.32,C\n2046-09-14,124.15,A\n2046-09-15,165.71,A\n2046-09-16,126.47,A\n2046-09-17,130.82,C\n2046-09-18,159.22,B\n2046-09-19,111.32,B\n2046-09-20,94.38,B\n2046-09-21,130.98,B\n2046-09-22,143.62,A\n2046-09-23,85.6,C\n2046-09-24,78.35,A\n2046-09-25,104.17,C\n2046-09-26,124.94,A\n2046-09-27,123.94,B\n2046-09-28,57.27,B\n2046-09-29,157.35,C\n2046-09-30,80.77,A\n2046-10-01,92.05,B\n2046-10-02,72.92,C\n2046-10-03,180.95,A\n2046-10-04,107.26,C\n2046-10-05,74.47,A\n2046-10-06,125.92,A\n2046-10-07,123.07,A\n2046-10-08,94.37,C\n2046-10-09,125.22,C\n2046-10-10,75.24,A\n2046-10-11,85.21,B\n2046-10-12,39.69,A\n2046-10-13,60.28,C\n2046-10-14,154.23,A\n2046-10-15,149.24,C\n2046-10-16,101.97,A\n2046-10-17,149.89,A\n2046-10-18,95.2,B\n2046-10-19,92.7,B\n2046-10-20,101.7,C\n2046-10-21,96.25,A\n2046-10-22,93.54,C\n2046-10-23,134.3,A\n2046-10-24,104.5,A\n2046-10-25,121.37,A\n2046-10-26,130.94,B\n2046-10-27,111.65,C\n2046-10-28,70.89,A\n2046-10-29,59.41,A\n2046-10-30,104.46,C\n2046-10-31,94.86,C\n2046-11-01,102.62,A\n2046-11-02,99.52,B\n2046-11-03,177.51,B\n2046-11-04,60.09,A\n2046-11-05,40.3,B\n2046-11-06,109.97,C\n2046-11-07,88.71,B\n2046-11-08,109.48,C\n2046-11-09,117.52,C\n2046-11-10,138.52,C\n2046-11-11,54.78,C\n2046-11-12,83.3,B\n2046-11-13,112.86,C\n2046-11-14,123.11,A\n2046-11-15,165.8,C\n2046-11-16,128.67,A\n2046-11-17,98.3,C\n2046-11-18,73.29,C\n2046-11-19,83.07,B\n2046-11-20,82.22,C\n2046-11-21,119.66,C\n2046-11-22,107.71,B\n2046-11-23,60.29,B\n2046-11-24,86.92,A\n2046-11-25,126.33,B\n2046-11-26,110.55,A\n2046-11-27,117.06,C\n2046-11-28,111.49,A\n2046-11-29,148.79,B\n2046-11-30,107.08,C\n2046-12-01,88.42,A\n2046-12-02,72.53,A\n2046-12-03,107.49,A\n2046-12-04,26.78,C\n2046-12-05,94.47,A\n2046-12-06,93.82,A\n2046-12-07,59.07,B\n2046-12-08,72.15,A\n2046-12-09,124.29,C\n2046-12-10,106.92,B\n2046-12-11,103.05,C\n2046-12-12,109.19,B\n2046-12-13,102.78,B\n2046-12-14,137.29,A\n2046-12-15,103.7,C\n2046-12-16,56.25,A\n2046-12-17,148.9,B\n2046-12-18,111.11,A\n2046-12-19,82.32,B\n2046-12-20,62.4,B\n2046-12-21,111.88,C\n2046-12-22,96.48,C\n2046-12-23,147.22,C\n2046-12-24,89.62,C\n2046-12-25,74.88,A\n2046-12-26,126.87,C\n2046-12-27,127.76,C\n2046-12-28,105.06,C\n2046-12-29,120.29,B\n2046-12-30,98.87,B\n2046-12-31,107.02,C\n2047-01-01,134.95,B\n2047-01-02,118.76,B\n2047-01-03,94.92,A\n2047-01-04,140.57,B\n2047-01-05,29.05,A\n2047-01-06,161.56,B\n2047-01-07,48.82,C\n2047-01-08,81.19,B\n2047-01-09,98.77,B\n2047-01-10,95.61,A\n2047-01-11,130.19,C\n2047-01-12,68.26,A\n2047-01-13,107.82,C\n2047-01-14,82.65,C\n2047-01-15,108.85,A\n2047-01-16,98.06,B\n2047-01-17,102.79,C\n2047-01-18,83.5,B\n2047-01-19,84.7,A\n2047-01-20,108.62,A\n2047-01-21,128.31,A\n2047-01-22,82.05,A\n2047-01-23,73.94,C\n2047-01-24,118.4,A\n2047-01-25,99.02,B\n2047-01-26,124.91,B\n2047-01-27,72.83,C\n2047-01-28,46.62,A\n2047-01-29,84.01,C\n2047-01-30,77.01,C\n2047-01-31,97.64,C\n2047-02-01,80.89,A\n2047-02-02,122.23,B\n2047-02-03,91.36,B\n2047-02-04,104.51,C\n2047-02-05,104.73,C\n2047-02-06,105.85,B\n2047-02-07,99.28,B\n2047-02-08,115.32,A\n2047-02-09,76.13,C\n2047-02-10,141.98,A\n2047-02-11,92.9,B\n2047-02-12,60.65,B\n2047-02-13,105.64,B\n2047-02-14,74.46,B\n2047-02-15,119.96,A\n2047-02-16,166.29,B\n2047-02-17,76.4,C\n2047-02-18,109.97,B\n2047-02-19,47.33,C\n2047-02-20,113.62,B\n2047-02-21,73.43,A\n2047-02-22,81.25,C\n2047-02-23,66.96,A\n2047-02-24,121.96,B\n2047-02-25,130.22,A\n2047-02-26,100.7,B\n2047-02-27,74.07,A\n2047-02-28,147.69,A\n2047-03-01,77.49,C\n2047-03-02,35.14,B\n2047-03-03,113.75,A\n2047-03-04,66.86,B\n2047-03-05,129.66,C\n2047-03-06,88.57,A\n2047-03-07,118.11,B\n2047-03-08,142.05,A\n2047-03-09,63.73,A\n2047-03-10,83.02,A\n2047-03-11,97.73,A\n2047-03-12,146.13,C\n2047-03-13,80.25,C\n2047-03-14,82.16,B\n2047-03-15,114.34,B\n2047-03-16,82.68,A\n2047-03-17,104.58,A\n2047-03-18,53.45,A\n2047-03-19,166.43,C\n2047-03-20,59.57,A\n2047-03-21,90.35,B\n2047-03-22,99.03,A\n2047-03-23,157.38,C\n2047-03-24,60.25,A\n2047-03-25,159.51,C\n2047-03-26,122.72,C\n2047-03-27,112.51,C\n2047-03-28,70.46,C\n2047-03-29,117.13,B\n2047-03-30,78.3,C\n2047-03-31,138.0,B\n2047-04-01,70.87,B\n2047-04-02,96.61,A\n2047-04-03,83.34,A\n2047-04-04,70.37,C\n2047-04-05,122.24,A\n2047-04-06,116.06,C\n2047-04-07,136.27,A\n2047-04-08,46.6,C\n2047-04-09,131.93,A\n2047-04-10,175.16,A\n2047-04-11,86.45,B\n2047-04-12,156.85,A\n2047-04-13,92.11,A\n2047-04-14,58.83,C\n2047-04-15,90.83,B\n2047-04-16,131.45,C\n2047-04-17,74.85,A\n2047-04-18,81.99,C\n2047-04-19,117.81,B\n2047-04-20,100.09,C\n2047-04-21,117.54,A\n2047-04-22,107.13,C\n2047-04-23,114.39,A\n2047-04-24,87.03,B\n2047-04-25,106.95,B\n2047-04-26,72.18,C\n2047-04-27,138.65,B\n2047-04-28,80.97,C\n2047-04-29,133.87,A\n2047-04-30,92.99,C\n2047-05-01,106.06,A\n2047-05-02,85.15,B\n2047-05-03,69.52,A\n2047-05-04,47.66,A\n2047-05-05,68.34,A\n2047-05-06,90.28,A\n2047-05-07,106.36,A\n2047-05-08,130.31,C\n2047-05-09,86.63,A\n2047-05-10,73.54,A\n2047-05-11,56.72,A\n2047-05-12,114.4,A\n2047-05-13,141.5,A\n2047-05-14,137.99,A\n2047-05-15,121.3,B\n2047-05-16,119.16,C\n2047-05-17,150.93,A\n2047-05-18,109.96,C\n2047-05-19,119.38,B\n2047-05-20,90.38,B\n2047-05-21,59.14,A\n2047-05-22,79.48,C\n2047-05-23,99.73,A\n2047-05-24,101.82,B\n2047-05-25,157.79,B\n2047-05-26,102.44,C\n2047-05-27,135.37,A\n2047-05-28,86.95,C\n2047-05-29,90.65,C\n2047-05-30,60.13,B\n2047-05-31,77.73,C\n2047-06-01,115.81,B\n2047-06-02,100.23,A\n2047-06-03,128.29,C\n2047-06-04,105.44,C\n2047-06-05,89.43,A\n2047-06-06,164.45,C\n2047-06-07,97.18,A\n2047-06-08,134.89,B\n2047-06-09,87.96,B\n2047-06-10,121.29,C\n2047-06-11,127.84,B\n2047-06-12,106.25,B\n2047-06-13,29.37,A\n2047-06-14,100.55,B\n2047-06-15,136.91,B\n2047-06-16,115.82,B\n2047-06-17,113.38,A\n2047-06-18,48.33,C\n2047-06-19,78.39,A\n2047-06-20,112.34,C\n2047-06-21,112.31,C\n2047-06-22,136.44,B\n2047-06-23,95.65,B\n2047-06-24,112.17,A\n2047-06-25,100.49,A\n2047-06-26,70.39,A\n2047-06-27,32.33,B\n2047-06-28,112.93,C\n2047-06-29,105.22,A\n2047-06-30,81.45,C\n2047-07-01,51.42,B\n2047-07-02,118.89,B\n2047-07-03,92.02,B\n2047-07-04,116.71,B\n2047-07-05,155.92,C\n2047-07-06,35.76,C\n2047-07-07,79.01,A\n2047-07-08,93.72,C\n2047-07-09,143.64,C\n2047-07-10,50.91,C\n2047-07-11,158.08,A\n2047-07-12,104.08,C\n2047-07-13,97.12,C\n2047-07-14,94.06,C\n2047-07-15,86.91,B\n2047-07-16,131.44,C\n2047-07-17,67.94,A\n2047-07-18,110.36,A\n2047-07-19,88.47,A\n2047-07-20,77.14,A\n2047-07-21,177.58,B\n2047-07-22,112.57,A\n2047-07-23,118.4,B\n2047-07-24,134.13,B\n2047-07-25,57.36,B\n2047-07-26,152.57,A\n2047-07-27,24.73,A\n2047-07-28,76.94,B\n2047-07-29,86.23,B\n2047-07-30,117.45,A\n2047-07-31,106.56,C\n2047-08-01,95.0,C\n2047-08-02,99.64,C\n2047-08-03,70.66,A\n2047-08-04,55.08,A\n2047-08-05,121.8,B\n2047-08-06,120.81,A\n2047-08-07,147.53,A\n2047-08-08,76.3,A\n2047-08-09,120.78,B\n2047-08-10,91.49,B\n2047-08-11,135.71,C\n2047-08-12,130.66,C\n2047-08-13,106.34,A\n2047-08-14,89.81,A\n2047-08-15,128.83,B\n2047-08-16,119.41,C\n2047-08-17,79.47,C\n2047-08-18,60.17,C\n2047-08-19,91.34,A\n2047-08-20,145.46,A\n2047-08-21,134.39,A\n2047-08-22,66.63,B\n2047-08-23,64.97,C\n2047-08-24,138.24,B\n2047-08-25,65.93,C\n2047-08-26,115.28,A\n2047-08-27,81.52,B\n2047-08-28,148.51,B\n2047-08-29,76.89,A\n2047-08-30,134.3,C\n2047-08-31,105.55,C\n2047-09-01,107.57,C\n2047-09-02,40.89,C\n2047-09-03,33.91,A\n2047-09-04,93.38,A\n2047-09-05,107.41,A\n2047-09-06,66.24,A\n2047-09-07,67.34,B\n2047-09-08,116.84,B\n2047-09-09,116.89,B\n2047-09-10,95.87,A\n2047-09-11,123.41,C\n2047-09-12,64.77,B\n2047-09-13,89.32,B\n2047-09-14,149.44,A\n2047-09-15,102.05,C\n2047-09-16,103.79,C\n2047-09-17,169.18,C\n2047-09-18,58.02,A\n2047-09-19,123.89,C\n2047-09-20,149.92,B\n2047-09-21,90.25,B\n2047-09-22,125.79,B\n2047-09-23,70.54,A\n2047-09-24,79.4,C\n2047-09-25,111.55,A\n2047-09-26,123.96,C\n2047-09-27,133.63,A\n2047-09-28,147.09,B\n2047-09-29,97.98,A\n2047-09-30,135.81,A\n2047-10-01,89.73,A\n2047-10-02,127.06,B\n2047-10-03,106.22,A\n2047-10-04,113.37,A\n2047-10-05,112.71,B\n2047-10-06,51.86,C\n2047-10-07,66.28,A\n2047-10-08,103.08,B\n2047-10-09,49.38,A\n2047-10-10,124.94,B\n2047-10-11,126.15,C\n2047-10-12,109.82,C\n2047-10-13,66.37,A\n2047-10-14,141.4,B\n2047-10-15,161.12,C\n2047-10-16,104.41,C\n2047-10-17,115.32,B\n2047-10-18,106.05,A\n2047-10-19,115.91,C\n2047-10-20,96.41,C\n2047-10-21,98.96,C\n2047-10-22,127.17,A\n2047-10-23,170.37,C\n2047-10-24,58.0,B\n2047-10-25,96.89,A\n2047-10-26,64.59,C\n2047-10-27,147.76,B\n2047-10-28,82.37,B\n2047-10-29,56.7,A\n2047-10-30,119.15,B\n2047-10-31,152.33,A\n2047-11-01,119.91,A\n2047-11-02,106.14,A\n2047-11-03,112.27,B\n2047-11-04,142.45,A\n2047-11-05,73.77,A\n2047-11-06,96.64,B\n2047-11-07,86.42,A\n2047-11-08,64.96,C\n2047-11-09,90.15,B\n2047-11-10,133.23,B\n2047-11-11,117.0,B\n2047-11-12,119.33,C\n2047-11-13,104.39,A\n2047-11-14,115.7,B\n2047-11-15,75.98,B\n2047-11-16,90.71,B\n2047-11-17,105.76,C\n2047-11-18,108.33,C\n2047-11-19,109.61,C\n2047-11-20,95.85,A\n2047-11-21,88.68,A\n2047-11-22,194.73,C\n2047-11-23,127.2,A\n2047-11-24,112.39,C\n2047-11-25,93.0,A\n2047-11-26,86.46,A\n2047-11-27,96.08,C\n2047-11-28,108.78,B\n2047-11-29,43.59,B\n2047-11-30,52.92,A\n2047-12-01,80.89,A\n2047-12-02,106.21,A\n2047-12-03,92.82,A\n2047-12-04,144.34,A\n2047-12-05,55.53,A\n2047-12-06,77.46,C\n2047-12-07,145.6,B\n2047-12-08,103.93,B\n2047-12-09,126.55,C\n2047-12-10,161.54,C\n2047-12-11,79.19,B\n2047-12-12,97.02,B\n2047-12-13,91.83,A\n2047-12-14,41.36,B\n2047-12-15,90.69,A\n2047-12-16,126.43,A\n2047-12-17,116.22,B\n2047-12-18,118.57,A\n2047-12-19,88.73,A\n2047-12-20,80.32,C\n2047-12-21,138.08,B\n2047-12-22,85.08,C\n2047-12-23,108.82,A\n2047-12-24,104.9,A\n2047-12-25,143.85,C\n2047-12-26,129.14,C\n2047-12-27,61.14,C\n2047-12-28,64.37,A\n2047-12-29,117.37,B\n2047-12-30,98.07,A\n2047-12-31,86.34,C\n2048-01-01,130.47,B\n2048-01-02,105.62,C\n2048-01-03,101.57,A\n2048-01-04,123.39,B\n2048-01-05,88.75,B\n2048-01-06,147.06,A\n2048-01-07,76.36,C\n2048-01-08,119.71,C\n2048-01-09,143.01,C\n2048-01-10,58.68,A\n2048-01-11,89.7,C\n2048-01-12,134.27,B\n2048-01-13,134.72,C\n2048-01-14,94.86,C\n2048-01-15,77.96,B\n2048-01-16,128.68,B\n2048-01-17,135.51,C\n2048-01-18,65.06,B\n2048-01-19,63.88,A\n2048-01-20,106.76,B\n2048-01-21,118.88,A\n2048-01-22,91.03,B\n2048-01-23,120.38,A\n2048-01-24,78.7,A\n2048-01-25,36.56,C\n2048-01-26,87.28,B\n2048-01-27,101.9,B\n2048-01-28,91.72,B\n2048-01-29,65.24,A\n2048-01-30,45.53,C\n2048-01-31,84.58,B\n2048-02-01,126.93,C\n2048-02-02,137.69,C\n2048-02-03,135.63,A\n2048-02-04,75.52,B\n2048-02-05,65.39,C\n2048-02-06,114.9,B\n2048-02-07,119.05,B\n2048-02-08,112.01,B\n2048-02-09,39.58,C\n2048-02-10,73.99,A\n2048-02-11,99.11,A\n2048-02-12,114.62,C\n2048-02-13,123.02,B\n2048-02-14,104.33,B\n2048-02-15,109.92,A\n2048-02-16,102.55,C\n2048-02-17,120.57,C\n2048-02-18,109.39,A\n2048-02-19,101.97,C\n2048-02-20,91.95,C\n2048-02-21,102.44,A\n2048-02-22,114.54,A\n2048-02-23,108.02,C\n2048-02-24,111.42,C\n2048-02-25,140.28,A\n2048-02-26,107.95,A\n2048-02-27,80.53,B\n2048-02-28,126.51,C\n2048-02-29,59.01,C\n2048-03-01,67.56,A\n2048-03-02,110.08,A\n2048-03-03,115.49,B\n2048-03-04,62.65,B\n2048-03-05,88.52,A\n2048-03-06,81.82,C\n2048-03-07,85.4,B\n2048-03-08,128.31,A\n2048-03-09,121.2,A\n2048-03-10,99.43,A\n2048-03-11,83.62,C\n2048-03-12,59.51,A\n2048-03-13,142.61,A\n2048-03-14,117.76,C\n2048-03-15,78.8,A\n2048-03-16,121.59,A\n2048-03-17,97.89,B\n2048-03-18,91.39,A\n2048-03-19,113.06,C\n2048-03-20,77.76,B\n2048-03-21,89.69,A\n2048-03-22,103.64,C\n2048-03-23,85.02,A\n2048-03-24,120.07,B\n2048-03-25,156.07,C\n2048-03-26,77.37,B\n2048-03-27,50.05,C\n2048-03-28,39.95,C\n2048-03-29,95.49,A\n2048-03-30,80.11,C\n2048-03-31,106.24,C\n2048-04-01,103.98,A\n2048-04-02,51.9,C\n2048-04-03,45.55,C\n2048-04-04,108.88,C\n2048-04-05,104.06,C\n2048-04-06,87.23,A\n2048-04-07,50.29,A\n2048-04-08,114.14,B\n2048-04-09,138.16,C\n2048-04-10,108.34,A\n2048-04-11,74.58,C\n2048-04-12,101.34,A\n2048-04-13,59.02,B\n2048-04-14,131.74,C\n2048-04-15,102.46,B\n2048-04-16,46.96,B\n2048-04-17,90.98,B\n2048-04-18,113.9,A\n2048-04-19,90.01,C\n2048-04-20,91.72,A\n2048-04-21,63.27,A\n2048-04-22,117.0,C\n2048-04-23,80.55,C\n2048-04-24,35.31,C\n2048-04-25,88.86,C\n2048-04-26,129.43,A\n2048-04-27,88.39,C\n2048-04-28,152.14,C\n2048-04-29,112.87,C\n2048-04-30,110.16,B\n2048-05-01,96.15,C\n2048-05-02,97.95,C\n2048-05-03,92.1,A\n2048-05-04,90.93,B\n2048-05-05,115.93,A\n2048-05-06,71.93,B\n2048-05-07,67.72,A\n2048-05-08,66.3,A\n2048-05-09,161.37,C\n2048-05-10,115.91,C\n2048-05-11,109.13,B\n2048-05-12,68.27,B\n2048-05-13,118.7,B\n2048-05-14,96.5,C\n2048-05-15,106.43,A\n2048-05-16,70.7,B\n2048-05-17,104.81,C\n2048-05-18,87.28,C\n2048-05-19,77.03,A\n2048-05-20,122.02,A\n2048-05-21,53.87,C\n2048-05-22,116.94,A\n2048-05-23,120.53,A\n2048-05-24,73.61,C\n2048-05-25,75.39,B\n2048-05-26,93.31,B\n2048-05-27,108.46,C\n2048-05-28,95.41,B\n2048-05-29,113.01,C\n2048-05-30,152.89,C\n2048-05-31,84.77,A\n2048-06-01,48.54,B\n2048-06-02,124.78,C\n2048-06-03,150.5,B\n2048-06-04,111.78,B\n2048-06-05,67.37,B\n2048-06-06,96.28,C\n2048-06-07,66.44,A\n2048-06-08,119.29,B\n2048-06-09,59.46,C\n2048-06-10,91.0,B\n2048-06-11,69.87,A\n2048-06-12,103.8,B\n2048-06-13,89.01,C\n2048-06-14,105.3,B\n2048-06-15,100.67,B\n2048-06-16,70.4,B\n2048-06-17,118.81,A\n2048-06-18,98.54,B\n2048-06-19,70.34,C\n2048-06-20,167.23,B\n2048-06-21,114.29,B\n2048-06-22,95.4,B\n2048-06-23,142.46,A\n2048-06-24,155.25,C\n2048-06-25,95.64,B\n2048-06-26,110.42,C\n2048-06-27,70.36,C\n2048-06-28,95.58,A\n2048-06-29,94.03,C\n2048-06-30,105.54,B\n2048-07-01,109.98,A\n2048-07-02,116.79,A\n2048-07-03,107.13,C\n2048-07-04,63.68,A\n2048-07-05,71.85,C\n2048-07-06,83.61,C\n2048-07-07,91.53,A\n2048-07-08,131.52,B\n2048-07-09,65.11,C\n2048-07-10,89.28,A\n2048-07-11,128.59,A\n2048-07-12,91.0,C\n2048-07-13,113.81,C\n2048-07-14,95.81,C\n2048-07-15,107.7,C\n2048-07-16,93.15,B\n2048-07-17,120.01,B\n2048-07-18,53.65,B\n2048-07-19,89.1,B\n2048-07-20,108.9,A\n2048-07-21,116.01,C\n2048-07-22,144.79,A\n2048-07-23,88.27,A\n2048-07-24,144.38,B\n2048-07-25,80.23,A\n2048-07-26,139.86,B\n2048-07-27,124.52,C\n2048-07-28,72.6,B\n2048-07-29,109.21,C\n2048-07-30,98.46,B\n2048-07-31,118.77,A\n2048-08-01,93.3,A\n2048-08-02,90.78,B\n2048-08-03,141.07,C\n2048-08-04,97.04,C\n2048-08-05,124.79,B\n2048-08-06,93.24,A\n2048-08-07,150.0,C\n2048-08-08,79.65,B\n2048-08-09,81.58,C\n2048-08-10,145.74,A\n2048-08-11,86.65,C\n2048-08-12,148.03,B\n2048-08-13,126.76,C\n2048-08-14,95.36,C\n2048-08-15,45.6,A\n2048-08-16,99.42,A\n2048-08-17,103.05,A\n2048-08-18,135.02,A\n2048-08-19,147.65,B\n2048-08-20,79.45,C\n2048-08-21,124.04,C\n2048-08-22,122.96,C\n2048-08-23,132.2,C\n2048-08-24,114.96,B\n2048-08-25,41.73,A\n2048-08-26,95.34,C\n2048-08-27,65.33,C\n2048-08-28,93.92,A\n2048-08-29,129.4,A\n2048-08-30,109.15,C\n2048-08-31,134.44,A\n2048-09-01,110.25,C\n2048-09-02,61.05,C\n2048-09-03,24.37,C\n2048-09-04,104.46,C\n2048-09-05,93.58,C\n2048-09-06,70.69,C\n2048-09-07,48.98,A\n2048-09-08,77.96,B\n2048-09-09,94.17,B\n2048-09-10,119.98,C\n2048-09-11,105.66,A\n2048-09-12,148.24,C\n2048-09-13,68.93,A\n2048-09-14,126.43,C\n2048-09-15,101.42,C\n2048-09-16,120.84,B\n2048-09-17,86.9,A\n2048-09-18,132.15,A\n2048-09-19,108.86,B\n2048-09-20,79.62,B\n2048-09-21,99.64,B\n2048-09-22,92.81,B\n2048-09-23,114.68,A\n2048-09-24,64.61,A\n2048-09-25,99.12,B\n2048-09-26,111.86,C\n2048-09-27,100.99,C\n2048-09-28,140.41,C\n2048-09-29,123.22,C\n2048-09-30,99.78,A\n2048-10-01,106.5,A\n2048-10-02,108.87,A\n2048-10-03,112.1,C\n2048-10-04,45.23,B\n2048-10-05,90.95,C\n2048-10-06,55.47,A\n2048-10-07,138.35,C\n2048-10-08,98.17,B\n2048-10-09,94.2,B\n2048-10-10,126.9,C\n2048-10-11,112.67,A\n2048-10-12,66.14,A\n2048-10-13,97.87,A\n2048-10-14,89.94,C\n2048-10-15,47.66,A\n2048-10-16,79.29,C\n2048-10-17,94.49,A\n2048-10-18,96.55,B\n2048-10-19,55.53,B\n2048-10-20,134.55,A\n2048-10-21,96.94,C\n2048-10-22,101.13,A\n2048-10-23,112.02,C\n2048-10-24,76.36,B\n2048-10-25,113.69,C\n2048-10-26,88.99,A\n2048-10-27,73.1,B\n2048-10-28,117.2,C\n2048-10-29,144.75,C\n2048-10-30,121.74,B\n2048-10-31,127.69,A\n2048-11-01,84.38,B\n2048-11-02,52.07,A\n2048-11-03,144.7,A\n2048-11-04,161.64,C\n2048-11-05,146.28,A\n2048-11-06,89.93,A\n2048-11-07,65.36,B\n2048-11-08,68.03,B\n2048-11-09,49.49,A\n2048-11-10,96.78,C\n2048-11-11,148.17,B\n2048-11-12,95.75,C\n2048-11-13,109.07,B\n2048-11-14,141.27,B\n2048-11-15,87.88,A\n2048-11-16,161.07,B\n2048-11-17,159.76,B\n2048-11-18,96.07,C\n2048-11-19,82.67,B\n2048-11-20,120.92,B\n2048-11-21,89.57,C\n2048-11-22,65.3,A\n2048-11-23,138.44,A\n2048-11-24,136.49,B\n2048-11-25,104.67,B\n2048-11-26,62.11,C\n2048-11-27,63.11,A\n2048-11-28,103.22,A\n2048-11-29,85.0,A\n2048-11-30,89.77,C\n2048-12-01,130.66,C\n2048-12-02,122.0,C\n2048-12-03,141.34,B\n2048-12-04,70.28,A\n2048-12-05,89.7,B\n2048-12-06,122.77,B\n2048-12-07,113.44,C\n2048-12-08,145.97,A\n2048-12-09,112.62,B\n2048-12-10,155.44,A\n2048-12-11,116.2,B\n2048-12-12,131.87,C\n2048-12-13,90.29,B\n2048-12-14,100.43,C\n2048-12-15,127.33,A\n2048-12-16,148.39,B\n2048-12-17,121.88,B\n2048-12-18,97.5,C\n2048-12-19,141.32,C\n2048-12-20,34.24,A\n2048-12-21,57.33,C\n2048-12-22,120.45,A\n2048-12-23,100.42,B\n2048-12-24,118.17,A\n2048-12-25,101.93,A\n2048-12-26,117.13,A\n2048-12-27,75.57,B\n2048-12-28,117.11,B\n2048-12-29,129.83,C\n2048-12-30,136.62,A\n2048-12-31,160.41,B\n2049-01-01,107.32,A\n2049-01-02,108.22,A\n2049-01-03,54.3,A\n2049-01-04,84.06,B\n2049-01-05,94.21,A\n2049-01-06,131.51,A\n2049-01-07,81.67,C\n2049-01-08,123.89,A\n2049-01-09,99.95,C\n2049-01-10,94.68,A\n2049-01-11,107.47,C\n2049-01-12,88.27,B\n2049-01-13,87.13,B\n2049-01-14,48.86,A\n2049-01-15,92.56,C\n2049-01-16,85.6,B\n2049-01-17,84.52,A\n2049-01-18,112.51,A\n2049-01-19,119.76,C\n2049-01-20,106.97,C\n2049-01-21,129.86,A\n2049-01-22,117.27,C\n2049-01-23,91.15,C\n2049-01-24,86.11,B\n2049-01-25,109.38,C\n2049-01-26,117.21,A\n2049-01-27,141.94,C\n2049-01-28,135.74,A\n2049-01-29,83.76,A\n2049-01-30,96.94,C\n2049-01-31,120.13,C\n2049-02-01,132.04,C\n2049-02-02,128.17,B\n2049-02-03,98.08,B\n2049-02-04,62.38,C\n2049-02-05,70.03,A\n2049-02-06,108.18,C\n2049-02-07,110.27,B\n2049-02-08,67.04,B\n2049-02-09,101.34,C\n2049-02-10,118.94,A\n2049-02-11,65.46,A\n2049-02-12,133.15,B\n2049-02-13,88.67,B\n2049-02-14,137.73,B\n2049-02-15,84.0,A\n2049-02-16,69.2,B\n2049-02-17,86.26,B\n2049-02-18,97.15,A\n2049-02-19,88.35,B\n2049-02-20,93.52,B\n2049-02-21,110.02,C\n2049-02-22,94.86,B\n2049-02-23,98.02,C\n2049-02-24,48.72,B\n2049-02-25,111.75,B\n2049-02-26,97.32,C\n2049-02-27,148.31,B\n2049-02-28,71.81,C\n2049-03-01,45.75,B\n2049-03-02,118.54,C\n2049-03-03,110.4,C\n2049-03-04,69.75,B\n2049-03-05,54.11,B\n2049-03-06,146.12,C\n2049-03-07,142.42,A\n2049-03-08,25.38,B\n2049-03-09,86.25,B\n2049-03-10,149.93,A\n2049-03-11,85.72,A\n2049-03-12,85.28,C\n2049-03-13,91.52,B\n2049-03-14,66.37,C\n2049-03-15,48.86,B\n2049-03-16,131.58,A\n2049-03-17,44.71,C\n2049-03-18,107.42,B\n2049-03-19,126.6,B\n2049-03-20,56.46,A\n2049-03-21,72.27,A\n2049-03-22,136.4,B\n2049-03-23,115.99,A\n2049-03-24,81.48,A\n2049-03-25,136.07,C\n2049-03-26,136.2,B\n2049-03-27,108.07,C\n2049-03-28,73.0,A\n2049-03-29,87.6,B\n2049-03-30,108.59,A\n2049-03-31,94.34,A\n2049-04-01,128.17,B\n2049-04-02,54.11,A\n2049-04-03,55.17,C\n2049-04-04,105.43,B\n2049-04-05,85.25,A\n2049-04-06,81.19,A\n2049-04-07,46.96,A\n2049-04-08,100.59,B\n2049-04-09,98.42,C\n2049-04-10,119.6,A\n2049-04-11,102.07,A\n2049-04-12,96.02,A\n2049-04-13,135.31,B\n2049-04-14,60.63,A\n2049-04-15,116.09,A\n2049-04-16,49.87,A\n2049-04-17,74.85,B\n2049-04-18,63.62,C\n2049-04-19,123.46,B\n2049-04-20,98.04,C\n2049-04-21,99.59,C\n2049-04-22,66.8,B\n2049-04-23,91.01,A\n2049-04-24,77.1,C\n2049-04-25,111.5,A\n2049-04-26,70.27,C\n2049-04-27,139.89,A\n2049-04-28,49.73,A\n2049-04-29,75.8,B\n2049-04-30,150.32,A\n2049-05-01,86.95,C\n2049-05-02,60.55,A\n2049-05-03,106.57,A\n2049-05-04,85.74,A\n2049-05-05,74.92,C\n2049-05-06,67.12,C\n2049-05-07,183.05,C\n2049-05-08,92.45,A\n2049-05-09,42.23,B\n2049-05-10,144.81,C\n2049-05-11,118.33,A\n2049-05-12,77.01,C\n2049-05-13,72.01,B\n2049-05-14,120.2,A\n2049-05-15,92.59,C\n2049-05-16,42.22,C\n2049-05-17,153.62,B\n2049-05-18,86.98,C\n2049-05-19,144.87,A\n2049-05-20,138.35,A\n2049-05-21,69.97,B\n2049-05-22,131.0,B\n2049-05-23,80.46,B\n2049-05-24,44.0,A\n2049-05-25,87.26,B\n2049-05-26,111.93,C\n2049-05-27,86.12,C\n2049-05-28,90.79,A\n2049-05-29,134.31,C\n2049-05-30,103.19,B\n2049-05-31,74.7,C\n2049-06-01,121.06,B\n2049-06-02,105.93,C\n2049-06-03,24.35,A\n2049-06-04,112.85,B\n2049-06-05,111.75,C\n2049-06-06,100.6,A\n2049-06-07,126.75,C\n2049-06-08,79.83,A\n2049-06-09,110.09,C\n2049-06-10,52.99,B\n2049-06-11,112.21,B\n2049-06-12,158.59,B\n2049-06-13,70.15,B\n2049-06-14,77.21,B\n2049-06-15,78.7,C\n2049-06-16,103.9,C\n2049-06-17,68.11,C\n2049-06-18,112.64,C\n2049-06-19,125.41,B\n2049-06-20,116.03,A\n2049-06-21,46.95,B\n2049-06-22,129.86,C\n2049-06-23,128.12,B\n2049-06-24,124.9,B\n2049-06-25,111.19,B\n2049-06-26,106.61,A\n2049-06-27,130.97,B\n2049-06-28,157.71,B\n2049-06-29,71.41,A\n2049-06-30,132.34,B\n2049-07-01,97.41,B\n2049-07-02,96.11,B\n2049-07-03,53.9,B\n2049-07-04,91.46,B\n2049-07-05,70.08,C\n2049-07-06,86.08,B\n2049-07-07,67.05,A\n2049-07-08,86.53,B\n2049-07-09,87.82,C\n2049-07-10,102.9,A\n2049-07-11,51.56,A\n2049-07-12,56.63,B\n2049-07-13,110.86,C\n2049-07-14,76.08,B\n2049-07-15,132.24,C\n2049-07-16,106.76,C\n2049-07-17,118.2,B\n2049-07-18,142.32,C\n2049-07-19,90.71,B\n2049-07-20,82.77,C\n2049-07-21,126.15,A\n2049-07-22,115.29,B\n2049-07-23,94.38,C\n2049-07-24,141.74,A\n2049-07-25,93.63,A\n2049-07-26,100.98,A\n2049-07-27,100.64,A\n2049-07-28,71.96,B\n2049-07-29,102.33,C\n2049-07-30,141.9,A\n2049-07-31,113.35,C\n2049-08-01,100.93,C\n2049-08-02,95.15,C\n2049-08-03,109.93,B\n2049-08-04,122.49,B\n2049-08-05,118.28,A\n2049-08-06,90.04,C\n2049-08-07,109.74,B\n2049-08-08,114.4,B\n2049-08-09,69.78,B\n2049-08-10,105.05,A\n2049-08-11,35.16,B\n2049-08-12,114.48,C\n2049-08-13,119.81,B\n2049-08-14,122.43,B\n2049-08-15,88.15,A\n2049-08-16,109.27,C\n2049-08-17,142.72,B\n2049-08-18,142.41,C\n2049-08-19,99.12,C\n2049-08-20,104.18,B\n2049-08-21,133.33,A\n2049-08-22,54.98,C\n2049-08-23,90.31,B\n2049-08-24,140.07,C\n2049-08-25,129.26,C\n2049-08-26,111.08,C\n2049-08-27,54.83,B\n2049-08-28,98.5,C\n2049-08-29,82.3,C\n2049-08-30,109.93,A\n2049-08-31,75.72,B\n2049-09-01,69.23,B\n2049-09-02,38.05,C\n2049-09-03,136.65,A\n2049-09-04,88.01,C\n2049-09-05,104.71,C\n2049-09-06,99.93,B\n2049-09-07,67.98,A\n2049-09-08,62.23,C\n2049-09-09,69.84,B\n2049-09-10,78.3,C\n2049-09-11,111.0,C\n2049-09-12,76.13,A\n2049-09-13,152.39,A\n2049-09-14,130.81,A\n2049-09-15,124.25,C\n2049-09-16,96.41,A\n2049-09-17,145.43,B\n2049-09-18,93.46,C\n2049-09-19,99.06,A\n2049-09-20,105.1,C\n2049-09-21,121.05,C\n2049-09-22,119.11,B\n2049-09-23,85.75,B\n2049-09-24,84.01,B\n2049-09-25,119.2,C\n2049-09-26,121.46,C\n2049-09-27,88.2,B\n2049-09-28,52.38,C\n2049-09-29,93.78,B\n2049-09-30,118.71,B\n2049-10-01,154.16,A\n2049-10-02,64.06,B\n2049-10-03,87.2,A\n2049-10-04,88.32,B\n2049-10-05,140.67,C\n2049-10-06,98.95,A\n2049-10-07,72.78,C\n2049-10-08,116.76,C\n2049-10-09,87.86,B\n2049-10-10,63.27,B\n2049-10-11,73.22,C\n2049-10-12,152.02,B\n2049-10-13,111.53,B\n2049-10-14,103.23,B\n2049-10-15,92.5,C\n2049-10-16,108.76,A\n2049-10-17,108.41,A\n2049-10-18,108.77,C\n2049-10-19,103.45,B\n2049-10-20,126.06,B\n2049-10-21,100.69,A\n2049-10-22,70.66,C\n2049-10-23,81.63,C\n2049-10-24,40.63,B\n2049-10-25,160.8,B\n2049-10-26,125.88,B\n2049-10-27,75.1,C\n2049-10-28,91.54,A\n2049-10-29,80.89,C\n2049-10-30,146.28,C\n2049-10-31,57.4,C\n2049-11-01,103.43,A\n2049-11-02,96.74,B\n2049-11-03,124.81,B\n2049-11-04,96.88,C\n2049-11-05,47.25,C\n2049-11-06,92.47,A\n2049-11-07,70.84,A\n2049-11-08,102.39,B\n2049-11-09,143.03,C\n2049-11-10,47.82,A\n2049-11-11,75.99,A\n2049-11-12,83.42,C\n2049-11-13,88.55,C\n2049-11-14,122.2,C\n2049-11-15,94.79,C\n2049-11-16,112.47,C\n2049-11-17,81.23,C\n2049-11-18,106.9,A\n2049-11-19,123.33,C\n2049-11-20,78.03,B\n2049-11-21,123.44,C\n2049-11-22,101.57,B\n2049-11-23,131.36,B\n2049-11-24,93.96,B\n2049-11-25,111.44,C\n2049-11-26,98.78,C\n2049-11-27,97.11,B\n2049-11-28,107.77,C\n2049-11-29,116.1,A\n2049-11-30,62.51,A\n2049-12-01,143.01,A\n2049-12-02,44.21,A\n2049-12-03,79.46,C\n2049-12-04,97.17,B\n2049-12-05,112.37,C\n2049-12-06,147.29,A\n2049-12-07,44.25,C\n2049-12-08,84.77,C\n2049-12-09,36.19,A\n2049-12-10,100.95,B\n2049-12-11,108.15,C\n2049-12-12,147.72,C\n2049-12-13,103.62,A\n2049-12-14,131.32,B\n2049-12-15,117.52,A\n2049-12-16,86.94,A\n2049-12-17,57.73,C\n2049-12-18,108.25,B\n2049-12-19,55.24,A\n2049-12-20,138.86,C\n2049-12-21,95.14,A\n2049-12-22,135.86,C\n2049-12-23,85.59,B\n2049-12-24,108.07,A\n2049-12-25,106.28,B\n2049-12-26,98.65,B\n2049-12-27,87.02,C\n2049-12-28,111.77,A\n2049-12-29,72.22,A\n2049-12-30,63.72,C\n2049-12-31,137.28,C\n2050-01-01,121.82,B\n2050-01-02,101.98,A\n2050-01-03,149.95,A\n2050-01-04,91.97,C\n2050-01-05,139.68,C\n2050-01-06,105.27,C\n2050-01-07,69.26,A\n2050-01-08,155.44,A\n2050-01-09,129.72,B\n2050-01-10,96.37,C\n2050-01-11,121.82,B\n2050-01-12,87.89,A\n2050-01-13,124.87,B\n2050-01-14,28.27,B\n2050-01-15,69.41,B\n2050-01-16,129.38,C\n2050-01-17,92.51,A\n2050-01-18,106.23,C\n2050-01-19,59.6,B\n2050-01-20,155.44,C\n2050-01-21,135.72,A\n2050-01-22,93.98,A\n2050-01-23,87.86,B\n2050-01-24,130.89,B\n2050-01-25,159.47,C\n2050-01-26,128.61,A\n2050-01-27,165.15,A\n2050-01-28,72.33,C\n2050-01-29,75.42,A\n2050-01-30,64.33,B\n2050-01-31,113.25,B\n2050-02-01,105.27,C\n2050-02-02,95.44,C\n2050-02-03,124.28,C\n2050-02-04,136.97,B\n2050-02-05,54.23,C\n2050-02-06,82.09,A\n2050-02-07,124.6,C\n2050-02-08,85.97,A\n2050-02-09,76.05,B\n2050-02-10,130.56,A\n2050-02-11,61.31,A\n2050-02-12,90.95,A\n2050-02-13,109.86,B\n2050-02-14,82.79,A\n2050-02-15,123.49,A\n2050-02-16,82.93,C\n2050-02-17,142.26,C\n2050-02-18,96.23,C\n2050-02-19,120.14,A\n2050-02-20,122.38,A\n2050-02-21,81.17,B\n2050-02-22,38.4,A\n2050-02-23,110.82,A\n2050-02-24,102.17,B\n2050-02-25,116.67,A\n2050-02-26,93.2,B\n2050-02-27,87.09,B\n2050-02-28,160.25,C\n2050-03-01,55.83,A\n2050-03-02,122.02,C\n2050-03-03,82.76,A\n2050-03-04,89.93,C\n2050-03-05,90.49,B\n2050-03-06,75.9,B\n2050-03-07,117.14,B\n2050-03-08,141.72,B\n2050-03-09,114.1,A\n2050-03-10,103.1,A\n2050-03-11,70.41,C\n2050-03-12,115.5,A\n2050-03-13,164.69,A\n2050-03-14,88.58,C\n2050-03-15,88.72,B\n2050-03-16,83.17,C\n2050-03-17,85.32,C\n2050-03-18,17.64,B\n2050-03-19,112.15,C\n2050-03-20,74.63,C\n2050-03-21,51.69,C\n2050-03-22,122.6,A\n2050-03-23,67.54,B\n2050-03-24,89.84,B\n2050-03-25,143.99,C\n2050-03-26,57.72,A\n2050-03-27,54.02,B\n2050-03-28,108.08,B\n2050-03-29,134.48,C\n2050-03-30,52.77,C\n2050-03-31,112.23,C\n2050-04-01,117.43,B\n2050-04-02,38.23,C\n2050-04-03,123.96,C\n2050-04-04,126.3,C\n2050-04-05,90.65,A\n2050-04-06,137.57,C\n2050-04-07,134.09,B\n2050-04-08,103.09,C\n2050-04-09,86.46,B\n2050-04-10,70.82,A\n2050-04-11,98.87,A\n2050-04-12,117.09,C\n2050-04-13,90.27,B\n2050-04-14,34.07,C\n2050-04-15,138.06,C\n2050-04-16,105.71,B\n2050-04-17,88.61,B\n2050-04-18,125.71,B\n2050-04-19,145.74,B\n2050-04-20,15.94,B\n2050-04-21,113.03,A\n2050-04-22,57.2,A\n2050-04-23,114.97,A\n2050-04-24,103.84,B\n2050-04-25,119.07,B\n2050-04-26,85.02,B\n2050-04-27,43.8,C\n2050-04-28,86.49,A\n2050-04-29,112.21,A\n2050-04-30,130.46,C\n2050-05-01,64.85,C\n2050-05-02,130.81,A\n2050-05-03,80.56,B\n2050-05-04,110.87,A\n2050-05-05,84.7,B\n2050-05-06,105.47,A\n2050-05-07,126.76,A\n2050-05-08,63.19,C\n2050-05-09,79.6,C\n2050-05-10,105.55,B\n2050-05-11,172.05,B\n2050-05-12,74.1,A\n2050-05-13,161.2,C\n2050-05-14,160.13,B\n2050-05-15,97.08,B\n2050-05-16,91.06,B\n2050-05-17,79.24,A\n2050-05-18,97.95,B\n2050-05-19,57.8,B\n2050-05-20,97.71,C\n2050-05-21,144.91,C\n2050-05-22,81.67,C\n2050-05-23,92.78,A\n2050-05-24,48.97,C\n2050-05-25,111.22,C\n2050-05-26,107.93,C\n2050-05-27,101.91,C\n2050-05-28,93.5,B\n2050-05-29,91.22,A\n2050-05-30,115.06,B\n2050-05-31,99.14,B\n2050-06-01,109.45,A\n2050-06-02,106.45,B\n2050-06-03,133.7,C\n2050-06-04,120.31,A\n2050-06-05,105.79,B\n2050-06-06,145.55,B\n2050-06-07,112.61,C\n2050-06-08,139.15,B\n2050-06-09,118.21,B\n2050-06-10,44.74,B\n2050-06-11,139.15,A\n2050-06-12,104.06,A\n2050-06-13,98.44,B\n2050-06-14,108.44,B\n2050-06-15,89.65,C\n2050-06-16,106.81,A\n2050-06-17,82.24,A\n2050-06-18,138.23,A\n2050-06-19,96.59,B\n2050-06-20,88.52,C\n2050-06-21,72.25,C\n2050-06-22,108.06,B\n2050-06-23,105.61,B\n2050-06-24,111.65,C\n2050-06-25,69.18,C\n2050-06-26,51.96,A\n2050-06-27,118.77,B\n2050-06-28,144.54,A\n2050-06-29,69.21,C\n2050-06-30,147.68,A\n2050-07-01,108.52,C\n2050-07-02,99.79,C\n2050-07-03,88.07,A\n2050-07-04,58.46,B\n2050-07-05,129.63,A\n2050-07-06,76.73,A\n2050-07-07,76.61,B\n2050-07-08,129.76,B\n2050-07-09,43.51,A\n2050-07-10,54.29,C\n2050-07-11,99.46,A\n2050-07-12,108.8,C\n2050-07-13,114.54,B\n2050-07-14,90.95,B\n2050-07-15,175.91,C\n2050-07-16,93.16,A\n2050-07-17,93.08,B\n2050-07-18,76.09,C\n2050-07-19,103.72,C\n2050-07-20,118.45,A\n2050-07-21,136.11,C\n2050-07-22,55.32,C\n2050-07-23,92.62,A\n2050-07-24,113.4,B\n2050-07-25,117.61,B\n2050-07-26,100.2,B\n2050-07-27,116.27,C\n2050-07-28,75.73,C\n2050-07-29,146.74,A\n2050-07-30,129.78,C\n2050-07-31,59.65,C\n2050-08-01,67.75,B\n2050-08-02,68.59,C\n2050-08-03,120.34,C\n2050-08-04,61.46,C\n2050-08-05,90.07,A\n2050-08-06,113.64,C\n2050-08-07,98.02,A\n2050-08-08,107.77,B\n2050-08-09,68.03,C\n2050-08-10,117.85,A\n2050-08-11,81.6,B\n2050-08-12,90.26,C\n2050-08-13,107.13,A\n2050-08-14,103.09,A\n2050-08-15,100.72,B\n2050-08-16,77.48,C\n2050-08-17,91.01,C\n2050-08-18,100.07,C\n2050-08-19,58.75,A\n2050-08-20,86.12,C\n2050-08-21,111.63,A\n2050-08-22,61.68,A\n2050-08-23,134.7,B\n2050-08-24,138.09,A\n2050-08-25,62.65,B\n2050-08-26,140.73,B\n2050-08-27,95.69,C\n2050-08-28,112.56,B\n2050-08-29,85.1,C\n2050-08-30,127.69,B\n2050-08-31,112.18,A\n2050-09-01,75.58,C\n2050-09-02,65.04,A\n2050-09-03,56.19,C\n2050-09-04,105.64,A\n2050-09-05,117.17,B\n2050-09-06,119.53,C\n2050-09-07,96.46,C\n2050-09-08,65.29,C\n2050-09-09,79.54,B\n2050-09-10,134.57,A\n2050-09-11,85.46,B\n2050-09-12,104.3,B\n2050-09-13,67.73,B\n2050-09-14,110.1,A\n2050-09-15,86.27,B\n2050-09-16,131.4,C\n2050-09-17,123.21,B\n2050-09-18,85.13,A\n2050-09-19,96.2,C\n2050-09-20,83.47,A\n2050-09-21,57.42,B\n2050-09-22,74.28,A\n2050-09-23,105.95,C\n2050-09-24,109.76,A\n2050-09-25,104.46,C\n2050-09-26,90.95,C\n2050-09-27,127.99,B\n2050-09-28,101.91,B\n2050-09-29,121.1,C\n2050-09-30,46.79,A\n2050-10-01,-17.67,C\n2050-10-02,108.51,B\n2050-10-03,57.26,A\n2050-10-04,93.63,C\n2050-10-05,90.94,B\n2050-10-06,147.62,C\n2050-10-07,112.0,C\n2050-10-08,104.98,C\n2050-10-09,125.11,A\n2050-10-10,111.05,B\n2050-10-11,86.27,B\n2050-10-12,132.14,C\n2050-10-13,66.71,C\n2050-10-14,109.7,C\n2050-10-15,53.57,A\n2050-10-16,106.12,B\n2050-10-17,105.79,C\n2050-10-18,150.29,B\n2050-10-19,53.05,C\n2050-10-20,81.97,B\n2050-10-21,124.68,C\n2050-10-22,110.06,A\n2050-10-23,105.41,C\n2050-10-24,71.32,C\n2050-10-25,82.28,C\n2050-10-26,137.35,C\n2050-10-27,98.45,C\n2050-10-28,111.26,B\n2050-10-29,96.54,B\n2050-10-30,64.51,A\n2050-10-31,128.01,C\n2050-11-01,115.1,B\n2050-11-02,169.17,B\n2050-11-03,69.53,C\n2050-11-04,124.68,C\n2050-11-05,60.65,A\n2050-11-06,104.72,A\n2050-11-07,129.3,B\n2050-11-08,37.84,A\n2050-11-09,93.85,B\n2050-11-10,83.1,A\n2050-11-11,78.94,C\n2050-11-12,111.56,A\n2050-11-13,133.62,B\n2050-11-14,96.0,A\n2050-11-15,106.16,B\n2050-11-16,103.43,B\n2050-11-17,100.05,A\n2050-11-18,67.32,A\n2050-11-19,133.25,B\n2050-11-20,108.86,C\n2050-11-21,111.85,A\n2050-11-22,75.8,B\n2050-11-23,86.22,C\n2050-11-24,94.72,B\n2050-11-25,53.98,A\n2050-11-26,49.36,B\n2050-11-27,81.98,B\n2050-11-28,115.68,B\n2050-11-29,80.41,A\n2050-11-30,69.44,C\n2050-12-01,75.56,C\n2050-12-02,86.34,C\n2050-12-03,89.38,C\n2050-12-04,164.97,A\n2050-12-05,131.64,B\n2050-12-06,130.74,C\n2050-12-07,135.06,C\n2050-12-08,70.04,A\n2050-12-09,63.33,B\n2050-12-10,66.79,B\n2050-12-11,140.04,C\n2050-12-12,83.58,C\n2050-12-13,62.46,B\n2050-12-14,87.07,A\n2050-12-15,92.84,B\n2050-12-16,108.05,B\n2050-12-17,107.53,C\n2050-12-18,75.15,A\n2050-12-19,120.57,A\n2050-12-20,80.82,A\n2050-12-21,95.31,C\n2050-12-22,120.6,A\n2050-12-23,80.75,A\n2050-12-24,82.92,C\n2050-12-25,117.75,B\n2050-12-26,74.27,A\n2050-12-27,88.46,B\n2050-12-28,116.67,A\n2050-12-29,161.62,B\n2050-12-30,118.16,B\n2050-12-31,114.18,C\n2051-01-01,115.92,C\n2051-01-02,67.8,A\n2051-01-03,103.47,A\n2051-01-04,99.11,C\n2051-01-05,109.65,A\n2051-01-06,103.5,A\n2051-01-07,111.39,C\n2051-01-08,90.66,B\n2051-01-09,101.76,A\n2051-01-10,63.17,C\n2051-01-11,90.4,A\n2051-01-12,141.73,A\n2051-01-13,109.56,C\n2051-01-14,99.37,C\n2051-01-15,156.88,A\n2051-01-16,59.17,A\n2051-01-17,66.72,B\n2051-01-18,151.38,B\n2051-01-19,101.38,C\n2051-01-20,92.72,A\n2051-01-21,64.02,C\n2051-01-22,113.43,C\n2051-01-23,116.33,A\n2051-01-24,67.17,B\n2051-01-25,95.79,C\n2051-01-26,89.93,C\n2051-01-27,107.68,A\n2051-01-28,66.42,B\n2051-01-29,137.56,B\n2051-01-30,127.47,A\n2051-01-31,89.1,C\n2051-02-01,93.51,A\n2051-02-02,95.91,A\n2051-02-03,76.65,B\n2051-02-04,86.7,C\n2051-02-05,91.89,C\n2051-02-06,148.39,B\n2051-02-07,128.4,C\n2051-02-08,128.04,C\n2051-02-09,111.1,A\n2051-02-10,66.48,A\n2051-02-11,98.76,A\n2051-02-12,129.11,C\n2051-02-13,62.25,C\n2051-02-14,119.1,C\n2051-02-15,169.68,C\n2051-02-16,131.85,B\n2051-02-17,142.42,B\n2051-02-18,106.55,C\n2051-02-19,129.04,A\n2051-02-20,78.46,A\n2051-02-21,123.88,B\n2051-02-22,119.63,B\n2051-02-23,108.7,C\n2051-02-24,139.48,C\n2051-02-25,83.84,C\n2051-02-26,94.39,C\n2051-02-27,102.86,C\n2051-02-28,69.11,A\n2051-03-01,106.81,A\n2051-03-02,48.43,C\n2051-03-03,131.41,A\n2051-03-04,101.4,C\n2051-03-05,84.37,C\n2051-03-06,74.97,B\n2051-03-07,92.96,A\n2051-03-08,120.65,A\n2051-03-09,116.43,B\n2051-03-10,78.83,A\n2051-03-11,74.62,B\n2051-03-12,119.71,A\n2051-03-13,78.33,C\n2051-03-14,101.8,B\n2051-03-15,103.71,C\n2051-03-16,109.36,C\n2051-03-17,125.75,A\n2051-03-18,62.11,B\n2051-03-19,42.67,C\n2051-03-20,82.98,C\n2051-03-21,154.05,B\n2051-03-22,160.67,A\n2051-03-23,154.94,C\n2051-03-24,94.21,A\n2051-03-25,46.54,B\n2051-03-26,29.71,A\n2051-03-27,81.58,B\n2051-03-28,94.06,A\n2051-03-29,139.02,A\n2051-03-30,126.03,C\n2051-03-31,106.82,A\n2051-04-01,73.3,C\n2051-04-02,71.18,B\n2051-04-03,107.62,B\n2051-04-04,120.91,C\n2051-04-05,111.76,A\n2051-04-06,68.96,B\n2051-04-07,119.52,B\n2051-04-08,112.78,B\n2051-04-09,67.88,C\n2051-04-10,76.47,B\n2051-04-11,120.65,B\n2051-04-12,92.96,B\n2051-04-13,147.67,B\n2051-04-14,115.03,C\n2051-04-15,85.4,C\n2051-04-16,99.69,A\n2051-04-17,101.9,B\n2051-04-18,78.15,C\n2051-04-19,72.62,A\n2051-04-20,121.04,A\n2051-04-21,125.36,B\n2051-04-22,118.11,A\n2051-04-23,145.46,C\n2051-04-24,83.75,A\n2051-04-25,150.23,A\n2051-04-26,72.97,C\n2051-04-27,69.62,A\n2051-04-28,47.2,A\n2051-04-29,86.63,A\n2051-04-30,84.89,A\n2051-05-01,115.78,B\n2051-05-02,107.32,B\n2051-05-03,64.21,C\n2051-05-04,88.22,A\n2051-05-05,88.86,C\n2051-05-06,46.72,B\n2051-05-07,70.57,A\n2051-05-08,76.88,A\n2051-05-09,143.01,B\n2051-05-10,105.74,A\n2051-05-11,119.87,A\n2051-05-12,55.04,B\n2051-05-13,135.83,C\n2051-05-14,139.03,C\n2051-05-15,40.05,A\n2051-05-16,78.84,B\n2051-05-17,114.87,C\n2051-05-18,119.33,C\n"
  },
  {
    "path": "cookbook/pocketflow-batch-node/flow.py",
    "content": "from pocketflow import Flow, Node\nfrom nodes import CSVProcessor\n\nclass ShowStats(Node):\n    \"\"\"Node to display the final statistics.\"\"\"\n    \n    def prep(self, shared):\n        \"\"\"Get statistics from shared store.\"\"\"\n        return shared[\"statistics\"]\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Display the statistics.\"\"\"\n        stats = prep_res\n        print(\"\\nFinal Statistics:\")\n        print(f\"- Total Sales: ${stats['total_sales']:,.2f}\")\n        print(f\"- Average Sale: ${stats['average_sale']:,.2f}\")\n        print(f\"- Total Transactions: {stats['total_transactions']:,}\\n\")\n        return \"end\"\n\ndef create_flow():\n    \"\"\"Create and return the processing flow.\"\"\"\n    # Create nodes\n    processor = CSVProcessor(chunk_size=1000)\n    show_stats = ShowStats()\n    \n    # Connect nodes\n    processor - \"show_stats\" >> show_stats\n    \n    # Create and return flow\n    return Flow(start=processor) "
  },
  {
    "path": "cookbook/pocketflow-batch-node/main.py",
    "content": "import os\nfrom flow import create_flow\n\ndef main():\n    \"\"\"Run the batch processing example.\"\"\"\n    # Create data directory if it doesn't exist\n    os.makedirs(\"data\", exist_ok=True)\n    \n    # Create sample CSV if it doesn't exist\n    if not os.path.exists(\"data/sales.csv\"):\n        print(\"Creating sample sales.csv...\")\n        import pandas as pd\n        import numpy as np\n        \n        # Generate sample data\n        np.random.seed(42)\n        n_rows = 10000\n        df = pd.DataFrame({\n            \"date\": pd.date_range(\"2024-01-01\", periods=n_rows),\n            \"amount\": np.random.normal(100, 30, n_rows).round(2),\n            \"product\": np.random.choice([\"A\", \"B\", \"C\"], n_rows)\n        })\n        df.to_csv(\"data/sales.csv\", index=False)\n    \n    # Initialize shared store\n    shared = {\n        \"input_file\": \"data/sales.csv\"\n    }\n    \n    # Create and run flow\n    print(f\"Processing sales.csv in chunks...\")\n    flow = create_flow()\n    flow.run(shared)\n\nif __name__ == \"__main__\":\n    main() "
  },
  {
    "path": "cookbook/pocketflow-batch-node/nodes.py",
    "content": "import pandas as pd\nfrom pocketflow import BatchNode\n\nclass CSVProcessor(BatchNode):\n    \"\"\"BatchNode that processes a large CSV file in chunks.\"\"\"\n    \n    def __init__(self, chunk_size=1000):\n        \"\"\"Initialize with chunk size.\"\"\"\n        super().__init__()\n        self.chunk_size = chunk_size\n    \n    def prep(self, shared):\n        \"\"\"Split CSV file into chunks.\n        \n        Returns an iterator of DataFrames, each containing chunk_size rows.\n        \"\"\"\n        # Read CSV in chunks\n        chunks = pd.read_csv(\n            shared[\"input_file\"],\n            chunksize=self.chunk_size\n        )\n        return chunks\n    \n    def exec(self, chunk):\n        \"\"\"Process a single chunk of the CSV.\n        \n        Args:\n            chunk: pandas DataFrame containing chunk_size rows\n            \n        Returns:\n            dict: Statistics for this chunk\n        \"\"\"\n        return {\n            \"total_sales\": chunk[\"amount\"].sum(),\n            \"num_transactions\": len(chunk),\n            \"total_amount\": chunk[\"amount\"].sum()\n        }\n    \n    def post(self, shared, prep_res, exec_res_list):\n        \"\"\"Combine results from all chunks.\n        \n        Args:\n            prep_res: Original chunks iterator\n            exec_res_list: List of results from each chunk\n            \n        Returns:\n            str: Action to take next\n        \"\"\"\n        # Combine statistics from all chunks\n        total_sales = sum(res[\"total_sales\"] for res in exec_res_list)\n        total_transactions = sum(res[\"num_transactions\"] for res in exec_res_list)\n        total_amount = sum(res[\"total_amount\"] for res in exec_res_list)\n        \n        # Calculate final statistics\n        shared[\"statistics\"] = {\n            \"total_sales\": total_sales,\n            \"average_sale\": total_amount / total_transactions,\n            \"total_transactions\": total_transactions\n        }\n        \n        return \"show_stats\" "
  },
  {
    "path": "cookbook/pocketflow-batch-node/requirements.txt",
    "content": "pocketflow\npandas>=2.0.0 "
  },
  {
    "path": "cookbook/pocketflow-chat/README.md",
    "content": "#  Simple PocketFlow Chat\n\nA basic chat application using PocketFlow with OpenAI's GPT-4o model.\n\n## Features\n\n- Conversational chat interface in the terminal\n- Maintains full conversation history for context\n- Simple implementation demonstrating PocketFlow's node and flow concepts\n\n## Run It\n\n1. Make sure your OpenAI API key is set:\n    ```bash\n    export OPENAI_API_KEY=\"your-api-key-here\"\n    ```\n    Alternatively, you can edit the `utils.py` file to include your API key directly.\n\n2. Install requirements and run the application:\n    ```bash\n    pip install -r requirements.txt\n    python main.py\n    ```\n\n## How It Works\n\n```mermaid\nflowchart LR\n    chat[ChatNode] -->|continue| chat\n```\n\nThe chat application uses:\n- A single `ChatNode` with a self-loop that:\n  - Takes user input in the `prep` method\n  - Sends the complete conversation history to GPT-4o\n  - Adds responses to the conversation history\n  - Loops back to continue the chat until the user types 'exit'\n\n\n## Files\n\n- [`main.py`](./main.py): Implementation of the ChatNode and chat flow\n- [`utils.py`](./utils.py): Simple wrapper for calling the OpenAI API\n "
  },
  {
    "path": "cookbook/pocketflow-chat/main.py",
    "content": "from pocketflow import Node, Flow\nfrom utils import call_llm\n\nclass ChatNode(Node):\n    def prep(self, shared):\n        # Initialize messages if this is the first run\n        if \"messages\" not in shared:\n            shared[\"messages\"] = []\n            print(\"Welcome to the chat! Type 'exit' to end the conversation.\")\n        \n        # Get user input\n        user_input = input(\"\\nYou: \")\n        \n        # Check if user wants to exit\n        if user_input.lower() == 'exit':\n            return None\n        \n        # Add user message to history\n        shared[\"messages\"].append({\"role\": \"user\", \"content\": user_input})\n        \n        # Return all messages for the LLM\n        return shared[\"messages\"]\n\n    def exec(self, messages):\n        if messages is None:\n            return None\n        \n        # Call LLM with the entire conversation history\n        response = call_llm(messages)\n        return response\n\n    def post(self, shared, prep_res, exec_res):\n        if prep_res is None or exec_res is None:\n            print(\"\\nGoodbye!\")\n            return None  # End the conversation\n        \n        # Print the assistant's response\n        print(f\"\\nAssistant: {exec_res}\")\n        \n        # Add assistant message to history\n        shared[\"messages\"].append({\"role\": \"assistant\", \"content\": exec_res})\n        \n        # Loop back to continue the conversation\n        return \"continue\"\n\n# Create the flow with self-loop\nchat_node = ChatNode()\nchat_node - \"continue\" >> chat_node  # Loop back to continue conversation\n\nflow = Flow(start=chat_node)\n\n# Start the chat\nif __name__ == \"__main__\":\n    shared = {}\n    flow.run(shared)\n"
  },
  {
    "path": "cookbook/pocketflow-chat/requirements.txt",
    "content": "pocketflow>=0.0.1\nopenai>=1.0.0"
  },
  {
    "path": "cookbook/pocketflow-chat/utils.py",
    "content": "from openai import OpenAI\nimport os\n\ndef call_llm(messages):\n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    \n    response = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=messages,\n        temperature=0.7\n    )\n    \n    return response.choices[0].message.content\n\nif __name__ == \"__main__\":\n    # Test the LLM call\n    messages = [{\"role\": \"user\", \"content\": \"In a few words, what's the meaning of life?\"}]\n    response = call_llm(messages)\n    print(f\"Prompt: {messages[0]['content']}\")\n    print(f\"Response: {response}\")\n\n"
  },
  {
    "path": "cookbook/pocketflow-chat-guardrail/README.md",
    "content": "#  Travel Advisor Chat with Guardrails\n\nA travel-focused chat application using PocketFlow with OpenAI's GPT-4o model, enhanced with input validation to ensure only travel-related queries are processed.\n\n## Features\n\n- Travel advisor chatbot that answers questions about destinations, planning, accommodations, etc.\n- **Topic-specific guardrails** to ensure only travel-related queries are accepted\n\n## Run It\n\n1. Make sure your OpenAI API key is set:\n    ```bash\n    export OPENAI_API_KEY=\"your-api-key-here\"\n    ```\n    Alternatively, you can edit the `utils.py` file to include your API key directly.\n\n2. Install requirements and run the application:\n    ```bash\n    pip install -r requirements.txt\n    python main.py\n    ```\n\n## How It Works\n\n```mermaid\nflowchart LR\n    user[UserInputNode] -->|validate| guardrail[GuardrailNode]\n    guardrail -->|retry| user\n    guardrail -->|process| llm[LLMNode]\n    llm -->|continue| user\n```\n\nThe chat application uses:\n- A `UserInputNode` that collects user input in its `exec` method\n- A `GuardrailNode` that validates if the query is travel-related using:\n  - Basic validation checks (empty input, too short)\n  - LLM-based validation to determine if the query relates to travel\n- An `LLMNode` that processes valid travel queries using GPT-4o with a travel advisor system prompt\n- Flow connections that route inputs through validation before processing and handle retries for non-travel related queries\n\n## Files\n\n- [`main.py`](./main.py): Implementation of the nodes and chat flow\n- [`utils.py`](./utils.py): Utilities for calling the OpenAI API\n\n## Example Outputs\n\n``` \nWelcome to the Travel Advisor Chat! Type 'exit' to end the conversation.\n\nYou: Plan my trip to Thailand\n\nTravel Advisor: Thailand offers a blend of vibrant cities, serene beaches, and rich culture. Begin in Bangkok to explore the Grand Palace and local markets. Head north to Chiang Mai for temples and elephant sanctuaries. Fly south to the islands; Phuket or Koh Samui for beaches, diving, and nightlife. Consider visiting during the cool season (November-February) for pleasant weather. Accommodation ranges from budget hostels to luxury resorts. Internal flights, trains, and buses connect major destinations. Don’t miss local cuisine, from street food to fine dining. Ensure your passport is valid for six months and consider travel insurance for peace of mind. Enjoy!\n\nYou: exit\n\nGoodbye! Safe travels!\n```\n\n```\nWelcome to the Travel Advisor Chat! Type 'exit' to end the conversation.\n\nYou: How to study large language models?\n\nTravel Advisor: The query is not related to travel advice, destinations, planning, or other travel topics. It is about studying large language models, which is a topic related to artificial intelligence and machine learning.\n\nYou: exit\n\nGoodbye! Safe travels!\n```"
  },
  {
    "path": "cookbook/pocketflow-chat-guardrail/main.py",
    "content": "from pocketflow import Node, Flow\nfrom utils import call_llm\n\nclass UserInputNode(Node):\n    def prep(self, shared):\n        # Initialize messages if this is the first run\n        if \"messages\" not in shared:\n            shared[\"messages\"] = []\n            print(\"Welcome to the Travel Advisor Chat! Type 'exit' to end the conversation.\")\n        \n        return None\n\n    def exec(self, _):\n        # Get user input\n        user_input = input(\"\\nYou: \")\n        return user_input\n\n    def post(self, shared, prep_res, exec_res):\n        user_input = exec_res\n        \n        # Check if user wants to exit\n        if user_input and user_input.lower() == 'exit':\n            print(\"\\nGoodbye! Safe travels!\")\n            return None  # End the conversation\n        \n        # Store user input in shared\n        shared[\"user_input\"] = user_input\n        \n        # Move to guardrail validation\n        return \"validate\"\n\nclass GuardrailNode(Node):\n    def prep(self, shared):\n        # Get the user input from shared data\n        user_input = shared.get(\"user_input\", \"\")\n        return user_input\n    \n    def exec(self, user_input):\n        # Basic validation checks\n        if not user_input or user_input.strip() == \"\":\n            return False, \"Your query is empty. Please provide a travel-related question.\"\n        \n        if len(user_input.strip()) < 3:\n            return False, \"Your query is too short. Please provide more details about your travel question.\"\n        \n        # LLM-based validation for travel topics\n        prompt = f\"\"\"\nEvaluate if the following user query is related to travel advice, destinations, planning, or other travel topics.\nThe chat should ONLY answer travel-related questions and reject any off-topic, harmful, or inappropriate queries.\nUser query: {user_input}\nReturn your evaluation in YAML format:\n```yaml\nvalid: true/false\nreason: [Explain why the query is valid or invalid]\n```\"\"\"\n        \n        # Call LLM with the validation prompt\n        messages = [{\"role\": \"user\", \"content\": prompt}]\n        response = call_llm(messages)\n        \n        # Extract YAML content\n        yaml_content = response.split(\"```yaml\")[1].split(\"```\")[0].strip() if \"```yaml\" in response else response\n        \n        import yaml\n        result = yaml.safe_load(yaml_content)\n        assert result is not None, \"Error: Invalid YAML format\"\n        assert \"valid\" in result and \"reason\" in result, \"Error: Invalid YAML format\"\n        is_valid = result.get(\"valid\", False)\n        reason = result.get(\"reason\", \"Missing reason in YAML response\")\n        \n        return is_valid, reason\n    \n    def post(self, shared, prep_res, exec_res):\n        is_valid, message = exec_res\n        \n        if not is_valid:\n            # Display error message to user\n            print(f\"\\nTravel Advisor: {message}\")\n            # Skip LLM call and go back to user input\n            return \"retry\"\n        \n        # Valid input, add to message history\n        shared[\"messages\"].append({\"role\": \"user\", \"content\": shared[\"user_input\"]})\n        # Proceed to LLM processing\n        return \"process\"\n\nclass LLMNode(Node):\n    def prep(self, shared):\n        # Add system message if not present\n        if not any(msg.get(\"role\") == \"system\" for msg in shared[\"messages\"]):\n            shared[\"messages\"].insert(0, {\n                \"role\": \"system\", \n                \"content\": \"You are a helpful travel advisor that provides information about destinations, travel planning, accommodations, transportation, activities, and other travel-related topics. Only respond to travel-related queries and keep responses informative and friendly. Your response are concise in 100 words.\"\n            })\n        \n        # Return all messages for the LLM\n        return shared[\"messages\"]\n\n    def exec(self, messages):\n        # Call LLM with the entire conversation history\n        response = call_llm(messages)\n        return response\n\n    def post(self, shared, prep_res, exec_res):\n        # Print the assistant's response\n        print(f\"\\nTravel Advisor: {exec_res}\")\n        \n        # Add assistant message to history\n        shared[\"messages\"].append({\"role\": \"assistant\", \"content\": exec_res})\n        \n        # Loop back to continue the conversation\n        return \"continue\"\n\n# Create the flow with nodes and connections\nuser_input_node = UserInputNode()\nguardrail_node = GuardrailNode()\nllm_node = LLMNode()\n\n# Create flow connections\nuser_input_node - \"validate\" >> guardrail_node\nguardrail_node - \"retry\" >> user_input_node  # Loop back if input is invalid\nguardrail_node - \"process\" >> llm_node\nllm_node - \"continue\" >> user_input_node     # Continue conversation\n\nflow = Flow(start=user_input_node)\n\n# Start the chat\nif __name__ == \"__main__\":\n    shared = {}\n    flow.run(shared)\n"
  },
  {
    "path": "cookbook/pocketflow-chat-guardrail/requirements.txt",
    "content": "pocketflow>=0.0.1\nopenai>=1.0.0"
  },
  {
    "path": "cookbook/pocketflow-chat-guardrail/utils.py",
    "content": "from openai import OpenAI\nimport os\n\ndef call_llm(messages):\n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    \n    response = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=messages,\n        temperature=0.7\n    )\n    \n    return response.choices[0].message.content\n\nif __name__ == \"__main__\":\n    # Test the LLM call\n    messages = [{\"role\": \"user\", \"content\": \"In a few words, what's the meaning of life?\"}]\n    response = call_llm(messages)\n    print(f\"Prompt: {messages[0]['content']}\")\n    print(f\"Response: {response}\")\n"
  },
  {
    "path": "cookbook/pocketflow-chat-memory/README.md",
    "content": "# PocketFlow Chat with Memory\n\nA chat application with memory retrieval using PocketFlow. This example maintains a sliding window of recent conversations while retrieving relevant past conversations based on context. \n\nThis implementation is based directly on the tutorial: [Build AI Agent Memory From Scratch — Tutorial For Dummies](https://zacharyhuang.substack.com/p/build-ai-agent-memory-from-scratch).\n\n## Features\n\n- Maintains a window of 3 most recent conversation pairs\n- Archives older conversations with embeddings\n- Uses vector similarity to retrieve the most relevant past conversation\n- Combines recent context (3 pairs) with retrieved context (1 pair) for better responses\n\n## Run It\n\n1. Make sure your OpenAI API key is set:\n    ```bash\n    export OPENAI_API_KEY=\"your-api-key-here\"\n    ```\n\n2. Install requirements and run the application:\n    ```bash\n    pip install -r requirements.txt\n    python main.py\n    ```\n    \n## How It Works\n\n```mermaid\nflowchart LR\n    Question[GetUserQuestionNode] -->|retrieve| Retrieve[RetrieveNode]\n    Retrieve -->|answer| Answer[AnswerNode]\n    Answer -->|question| Question\n    Answer -->|embed| Embed[EmbedNode]\n    Embed -->|question| Question\n```\n\nThe chat application uses:\n- Four specialized nodes:\n  - `GetUserQuestionNode`: Handles interactive user input\n  - `RetrieveNode`: Finds relevant past conversations using vector similarity\n  - `AnswerNode`: Generates responses using both recent and retrieved context\n  - `EmbedNode`: Archives older conversations with embeddings\n- A sliding window approach that maintains only the 3 most recent conversation pairs in active context\n\n## Files\n\n- [`nodes.py`](./nodes.py): Four node implementations with clear separation of concerns\n- [`flow.py`](./flow.py): Chat flow structure definition\n- [`main.py`](./main.py): Entry point for running the demo\n- [`utils/`](./utils/): Utility functions for embeddings, LLM calls, and vector operations\n\n\n## Example Output\n\n```\n==================================================\nPocketFlow Chat with Memory\n==================================================\nThis chat keeps your 3 most recent conversations\nand brings back relevant past conversations when helpful\nType 'exit' to end the conversation\n==================================================\nWelcome to the interactive chat! Type 'exit' to end the conversation.\n\nYou: remember, my cat name is Whiskers and she only drinks from running water taps\n\nAssistant: Got it! Whiskers only drinks from running water taps. If you have any questions or need advice related to her, feel free to ask!\n\nYou: remember, I have a peanut allergy but I can eat almonds and cashews without problems\n\nAssistant: Understood! You have a peanut allergy but can safely eat almonds and cashews. If you need any assistance or information related to your dietary preferences or restrictions, just let me know!\n\nYou: remember, my anniversary is June 17th and I always forget to buy a gift until the last minute\n\nAssistant: Got it! Your anniversary is on June 17th, and you often remember to buy a gift at the last minute. If you'd like, I can help you brainstorm gift ideas or set reminders as the date approaches!\n\nYou: remember, I lived in Portugal for 3 years when I was a child and still speak some Portuguese\n\nAssistant: That's great to know! You lived in Portugal for three years during your childhood and still speak some Portuguese. If you ever want to practice or need help with anything related to Portuguese language or culture, feel free to ask!\n✅ Added conversation to index at position 0\n✅ Index now contains 1 conversations\n\nYou: remember, my favorite dessert is tiramisu but only if it's made with real mascarpone cheese\n🔍 Finding relevant conversation for: remember, my favorite dessert ...\n📄 Retrieved conversation (distance: 0.5008)\n\nAssistant: Got it! Your favorite dessert is tiramisu, but only when it's made with real mascarpone cheese. If you ever need recommendations or recipes, just let me know!\n✅ Added conversation to index at position 1\n✅ Index now contains 2 conversations\n\nYou: remember, I collect vintage mechanical watches and my most valuable one is a 1965 Omega Seamaster\n🔍 Finding relevant conversation for: remember, I collect vintage me...\n📄 Retrieved conversation (distance: 0.5374)\n\nAssistant: Got it! You collect vintage mechanical watches, and your most valuable piece is a 1965 Omega Seamaster. If you have questions about watches or need assistance with your collection, feel free to reach out!\n✅ Added conversation to index at position 2\n✅ Index now contains 3 conversations\n\nYou: what's my cat name?\n🔍 Finding relevant conversation for: what's my cat name?...\n📄 Retrieved conversation (distance: 0.3643)\n\nAssistant: Your cat's name is Whiskers.\n✅ Added conversation to index at position 3\n✅ Index now contains 4 conversations\n```\n"
  },
  {
    "path": "cookbook/pocketflow-chat-memory/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import GetUserQuestionNode, RetrieveNode, AnswerNode, EmbedNode\n\ndef create_chat_flow():\n    # Create the nodes\n    question_node = GetUserQuestionNode()\n    retrieve_node = RetrieveNode()\n    answer_node = AnswerNode()\n    embed_node = EmbedNode()\n    \n    # Connect the flow:\n    # 1. Start with getting a question\n    # 2. Retrieve relevant conversations\n    # 3. Generate an answer\n    # 4. Optionally embed old conversations\n    # 5. Loop back to get the next question\n\n    # Main flow path\n    question_node - \"retrieve\" >> retrieve_node\n    retrieve_node - \"answer\" >> answer_node\n    \n    # When we need to embed old conversations\n    answer_node - \"embed\" >> embed_node\n    \n    # Loop back for next question\n    answer_node - \"question\" >> question_node\n    embed_node - \"question\" >> question_node\n    \n    # Create the flow starting with question node\n    return Flow(start=question_node)\n\n# Initialize the flow\nchat_flow = create_chat_flow() "
  },
  {
    "path": "cookbook/pocketflow-chat-memory/main.py",
    "content": "from flow import chat_flow\n\ndef run_chat_memory_demo():\n    \"\"\"\n    Run an interactive chat interface with memory retrieval.\n    \n    Features:\n    1. Maintains a window of the 3 most recent conversation pairs\n    2. Archives older conversations with embeddings\n    3. Retrieves 1 relevant past conversation when needed\n    4. Total context to LLM: 3 recent pairs + 1 retrieved pair\n    \"\"\"\n    \n    print(\"=\" * 50)\n    print(\"PocketFlow Chat with Memory\")\n    print(\"=\" * 50)\n    print(\"This chat keeps your 3 most recent conversations\")\n    print(\"and brings back relevant past conversations when helpful\")\n    print(\"Type 'exit' to end the conversation\")\n    print(\"=\" * 50)\n    \n    # Run the chat flow\n    chat_flow.run({})\n\nif __name__ == \"__main__\":\n    run_chat_memory_demo()"
  },
  {
    "path": "cookbook/pocketflow-chat-memory/nodes.py",
    "content": "from pocketflow import Node\nfrom utils.vector_index import create_index, add_vector, search_vectors\nfrom utils.call_llm import call_llm\nfrom utils.get_embedding import get_embedding\n\nclass GetUserQuestionNode(Node):\n    def prep(self, shared):\n        \"\"\"Initialize messages if first run\"\"\"\n        if \"messages\" not in shared:\n            shared[\"messages\"] = []\n            print(\"Welcome to the interactive chat! Type 'exit' to end the conversation.\")\n        \n        return None\n    \n    def exec(self, _):\n        \"\"\"Get user input interactively\"\"\"\n        # Get interactive input from user\n        user_input = input(\"\\nYou: \")\n            \n        # Check if user wants to exit\n        if user_input.lower() == 'exit':\n            return None\n            \n        return user_input\n    \n    def post(self, shared, prep_res, exec_res):\n        # If exec_res is None, the user wants to exit\n        if exec_res is None:\n            print(\"\\nGoodbye!\")\n            return None  # End the conversation\n            \n        # Add user message to current messages\n        shared[\"messages\"].append({\"role\": \"user\", \"content\": exec_res})\n        \n        return \"retrieve\"\n\nclass AnswerNode(Node):\n    def prep(self, shared):\n        \"\"\"Prepare context for the LLM\"\"\"\n        if not shared.get(\"messages\"):\n            return None\n            \n        # 1. Get the last 3 conversation pairs (or fewer if not available)\n        recent_messages = shared[\"messages\"][-6:] if len(shared[\"messages\"]) > 6 else shared[\"messages\"]\n        \n        # 2. Add the retrieved relevant conversation if available\n        context = []\n        if shared.get(\"retrieved_conversation\"):\n            # Add a system message to indicate this is a relevant past conversation\n            context.append({\n                \"role\": \"system\", \n                \"content\": \"The following is a relevant past conversation that may help with the current query:\"\n            })\n            context.extend(shared[\"retrieved_conversation\"])\n            context.append({\n                \"role\": \"system\", \n                \"content\": \"Now continue the current conversation:\"\n            })\n        \n        # 3. Add the recent messages\n        context.extend(recent_messages)\n        \n        return context\n    \n    def exec(self, messages):\n        \"\"\"Generate a response using the LLM\"\"\"\n        if messages is None:\n            return None\n        \n        # Call LLM with the context\n        response = call_llm(messages)\n        return response\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Process the LLM response\"\"\"\n        if prep_res is None or exec_res is None:\n            return None  # End the conversation\n        \n        # Print the assistant's response\n        print(f\"\\nAssistant: {exec_res}\")\n        \n        # Add assistant message to history\n        shared[\"messages\"].append({\"role\": \"assistant\", \"content\": exec_res})\n        \n        # If we have more than 6 messages (3 conversation pairs), archive the oldest pair\n        if len(shared[\"messages\"]) > 6:\n            return \"embed\"\n        \n        # We only end if the user explicitly typed 'exit'\n        # Even if last_question is set, we continue in interactive mode\n        return \"question\"\n\nclass EmbedNode(Node):\n    def prep(self, shared):\n        \"\"\"Extract the oldest conversation pair for embedding\"\"\"\n        if len(shared[\"messages\"]) <= 6:\n            return None\n            \n        # Extract the oldest user-assistant pair\n        oldest_pair = shared[\"messages\"][:2]\n        # Remove them from current messages\n        shared[\"messages\"] = shared[\"messages\"][2:]\n        \n        return oldest_pair\n    \n    def exec(self, conversation):\n        \"\"\"Embed a conversation\"\"\"\n        if not conversation:\n            return None\n            \n        # Combine user and assistant messages into a single text for embedding\n        user_msg = next((msg for msg in conversation if msg[\"role\"] == \"user\"), {\"content\": \"\"})\n        assistant_msg = next((msg for msg in conversation if msg[\"role\"] == \"assistant\"), {\"content\": \"\"})\n        combined = f\"User: {user_msg['content']} Assistant: {assistant_msg['content']}\"\n        \n        # Generate embedding\n        embedding = get_embedding(combined)\n        \n        return {\n            \"conversation\": conversation,\n            \"embedding\": embedding\n        }\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store the embedding and add to index\"\"\"\n        if not exec_res:\n            # If there's nothing to embed, just continue with the next question\n            return \"question\"\n            \n        # Initialize vector index if not exist\n        if \"vector_index\" not in shared:\n            shared[\"vector_index\"] = create_index()\n            shared[\"vector_items\"] = []  # Track items separately\n            \n        # Add the embedding to the index and store the conversation\n        position = add_vector(shared[\"vector_index\"], exec_res[\"embedding\"])\n        shared[\"vector_items\"].append(exec_res[\"conversation\"])\n        \n        print(f\"✅ Added conversation to index at position {position}\")\n        print(f\"✅ Index now contains {len(shared['vector_items'])} conversations\")\n        \n        # Continue with the next question\n        return \"question\"\n\nclass RetrieveNode(Node):\n    def prep(self, shared):\n        \"\"\"Get the current query for retrieval\"\"\"\n        if not shared.get(\"messages\"):\n            return None\n            \n        # Get the latest user message for searching\n        latest_user_msg = next((msg for msg in reversed(shared[\"messages\"]) \n                                if msg[\"role\"] == \"user\"), {\"content\": \"\"})\n        \n        # Check if we have a vector index with items\n        if (\"vector_index\" not in shared or \n            \"vector_items\" not in shared or \n            len(shared[\"vector_items\"]) == 0):\n            return None\n            \n        return {\n            \"query\": latest_user_msg[\"content\"],\n            \"vector_index\": shared[\"vector_index\"],\n            \"vector_items\": shared[\"vector_items\"]\n        }\n    \n    def exec(self, inputs):\n        \"\"\"Find the most relevant past conversation\"\"\"\n        if not inputs:\n            return None\n            \n        query = inputs[\"query\"]\n        vector_index = inputs[\"vector_index\"]\n        vector_items = inputs[\"vector_items\"]\n        \n        print(f\"🔍 Finding relevant conversation for: {query[:30]}...\")\n        \n        # Create embedding for the query\n        query_embedding = get_embedding(query)\n        \n        # Search for the most similar conversation\n        indices, distances = search_vectors(vector_index, query_embedding, k=1)\n        \n        if not indices:\n            return None\n            \n        # Get the corresponding conversation\n        conversation = vector_items[indices[0]]\n        \n        return {\n            \"conversation\": conversation,\n            \"distance\": distances[0]\n        }\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store the retrieved conversation\"\"\"\n        if exec_res is not None:\n            shared[\"retrieved_conversation\"] = exec_res[\"conversation\"]\n            print(f\"📄 Retrieved conversation (distance: {exec_res['distance']:.4f})\")\n        else:\n            shared[\"retrieved_conversation\"] = None\n        \n        return \"answer\""
  },
  {
    "path": "cookbook/pocketflow-chat-memory/requirements.txt",
    "content": "pocketflow>=0.0.2\nnumpy>=1.20.0\nfaiss-cpu>=1.7.0\nopenai>=1.0.0\n"
  },
  {
    "path": "cookbook/pocketflow-chat-memory/utils/__init__.py",
    "content": "\n"
  },
  {
    "path": "cookbook/pocketflow-chat-memory/utils/call_llm.py",
    "content": "import os\nfrom openai import OpenAI\n\ndef call_llm(messages):\n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    \n    response = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=messages,\n        temperature=0.7\n    )\n    \n    return response.choices[0].message.content\n\nif __name__ == \"__main__\":\n    # Test the LLM call\n    messages = [{\"role\": \"user\", \"content\": \"In a few words, what's the meaning of life?\"}]\n    response = call_llm(messages)\n    print(f\"Prompt: {messages[0]['content']}\")\n    print(f\"Response: {response}\") "
  },
  {
    "path": "cookbook/pocketflow-chat-memory/utils/get_embedding.py",
    "content": "import os\nimport numpy as np\nfrom openai import OpenAI\n\ndef get_embedding(text):\n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"YOUR_API_KEY\"))\n    \n    response = client.embeddings.create(\n        model=\"text-embedding-ada-002\",\n        input=text\n    )\n    \n    # Extract the embedding vector from the response\n    embedding = response.data[0].embedding\n    \n    # Convert to numpy array for consistency with other embedding functions\n    return np.array(embedding, dtype=np.float32)\n\n\nif __name__ == \"__main__\":\n    # Test the embedding function\n    text1 = \"The quick brown fox jumps over the lazy dog.\"\n    text2 = \"Python is a popular programming language for data science.\"\n    \n    emb1 = get_embedding(text1)\n    emb2 = get_embedding(text2)\n    \n    print(f\"Embedding 1 shape: {emb1.shape}\")\n    print(f\"Embedding 2 shape: {emb2.shape}\")\n    \n    # Calculate similarity (dot product)\n    similarity = np.dot(emb1, emb2)\n    print(f\"Similarity between texts: {similarity:.4f}\") "
  },
  {
    "path": "cookbook/pocketflow-chat-memory/utils/vector_index.py",
    "content": "import numpy as np\nimport faiss\n\ndef create_index(dimension=1536):\n    return faiss.IndexFlatL2(dimension)\n\ndef add_vector(index, vector):\n    # Make sure the vector is a numpy array with the right shape for FAISS\n    vector = np.array(vector).reshape(1, -1).astype(np.float32)\n    \n    # Add the vector to the index\n    index.add(vector)\n    \n    # Return the position (index.ntotal is the total number of vectors in the index)\n    return index.ntotal - 1\n\ndef search_vectors(index, query_vector, k=1):\n    \"\"\"Search for the k most similar vectors to the query vector\n    \n    Args:\n        index: The FAISS index\n        query_vector: The query vector (numpy array or list)\n        k: Number of results to return (default: 1)\n        \n    Returns:\n        tuple: (indices, distances) where:\n            - indices is a list of positions in the index\n            - distances is a list of the corresponding distances\n    \"\"\"\n    # Make sure we don't try to retrieve more vectors than exist in the index\n    k = min(k, index.ntotal)\n    if k == 0:\n        return [], []\n        \n    # Make sure the query is a numpy array with the right shape for FAISS\n    query_vector = np.array(query_vector).reshape(1, -1).astype(np.float32)\n    \n    # Search the index\n    distances, indices = index.search(query_vector, k)\n    \n    return indices[0].tolist(), distances[0].tolist()\n\n# Example usage\nif __name__ == \"__main__\":\n    # Create a new index\n    index = create_index(dimension=3)\n    \n    # Add some random vectors and track them separately\n    items = []\n    for i in range(5):\n        vector = np.random.random(3)\n        position = add_vector(index, vector)\n        items.append(f\"Item {i}\")\n        print(f\"Added vector at position {position}\")\n        \n    print(f\"Index contains {index.ntotal} vectors\")\n    \n    # Search for a similar vector\n    query = np.random.random(3)\n    indices, distances = search_vectors(index, query, k=2)\n    \n    print(\"Query:\", query)\n    print(\"Found indices:\", indices)\n    print(\"Distances:\", distances)\n    print(\"Retrieved items:\", [items[idx] for idx in indices]) "
  },
  {
    "path": "cookbook/pocketflow-cli-hitl/README.md",
    "content": "# PocketFlow Command-Line Joke Generator (Human-in-the-Loop Example)\n\nA simple, interactive command-line application that generates jokes based on user-provided topics and direct human feedback. This serves as a clear example of a Human-in-the-Loop (HITL) workflow orchestrated by PocketFlow.\n\n## Features\n\n- **Interactive Joke Generation**: Ask for jokes on any topic.\n- **Human-in-the-Loop Feedback**: Dislike a joke? Your feedback directly influences the next generation attempt.\n- **Minimalist Design**: A straightforward example of using PocketFlow for HITL tasks.\n- **Powered by LLMs**: (Uses Anthropic Claude via an API call for joke generation).\n\n## Getting Started\n\nThis project is part of the PocketFlow cookbook examples. It's assumed you have already cloned the [PocketFlow repository](https://github.com/the-pocket/PocketFlow) and are in the `cookbook/pocketflow-cli-hitl` directory.\n\n1.  **Install required dependencies**:\n    ```bash\n    pip install -r requirements.txt\n    ```\n\n2.  **Set up your Anthropic API key**:\n    The application uses Anthropic Claude to generate jokes. You need to set your API key as an environment variable.\n    ```bash\n    export ANTHROPIC_API_KEY=\"your-anthropic-api-key-here\"\n    ```\n    You can test if your `call_llm.py` utility is working by running it directly:\n    ```bash\n    python utils/call_llm.py\n    ```\n\n3.  **Run the Joke Generator**:\n    ```bash\n    python main.py\n    ```\n\n## How It Works\n\nThe system uses a simple PocketFlow workflow:\n\n```mermaid\nflowchart TD\n    GetTopic[GetTopicNode] --> GenerateJoke[GenerateJokeNode]\n    GenerateJoke --> GetFeedback[GetFeedbackNode]\n    GetFeedback -- \"Approve\" --> Z((End))\n    GetFeedback -- \"Disapprove\" --> GenerateJoke\n```\n\n1.  **GetTopicNode**: Prompts the user to enter a topic for the joke.\n2.  **GenerateJokeNode**: Sends the topic (and any previously disliked jokes as context) to an LLM to generate a new joke.\n3.  **GetFeedbackNode**: Shows the joke to the user and asks if they liked it.\n    *   If **yes** (approved), the application ends.\n    *   If **no** (disapproved), the disliked joke is recorded, and the flow loops back to `GenerateJokeNode` to try again.\n\n## Sample Output\n\nHere's an example of an interaction with the Joke Generator:\n\n```\nWelcome to the Command-Line Joke Generator!\nWhat topic would you like a joke about? Pocket Flow: 100-line LLM framework\n\nJoke: Pocket Flow: Finally, an LLM framework that fits in your pocket! Too bad your model still needs a data center.\nDid you like this joke? (yes/no): no\nOkay, let me try another one.\n\nJoke: Pocket Flow: A 100-line LLM framework where 99 lines are imports and the last line is `print(\"TODO: implement intelligence\")`.\nDid you like this joke? (yes/no): yes\nGreat! Glad you liked it.\n\nThanks for using the Joke Generator!\n```\n\n## Files\n\n-   [`main.py`](./main.py): Entry point for the application.\n-   [`flow.py`](./flow.py): Defines the PocketFlow graph and node connections.\n-   [`nodes.py`](./nodes.py): Contains the definitions for `GetTopicNode`, `GenerateJokeNode`, and `GetFeedbackNode`.\n-   [`utils/call_llm.py`](./utils/call_llm.py): Utility function to interact with the LLM (Anthropic Claude).\n-   [`requirements.txt`](./requirements.txt): Lists project dependencies.\n-   [`docs/design.md`](./docs/design.md): The design document for this application."
  },
  {
    "path": "cookbook/pocketflow-cli-hitl/docs/design.md",
    "content": "# Design Doc: Command-Line Joke Generator\n\n> Please DON'T remove notes for AI\n\n## Requirements\n\n> Notes for AI: Keep it simple and clear.\n> If the requirements are abstract, write concrete user stories\n\nThe system will be a command-line application that:\n1. Asks the user for a topic for a joke.\n2. Generates a joke based on the provided topic.\n3. Asks the user if they approve of the joke.\n4. If the user approves, the application can end or offer to generate another joke (for simplicity, we'll end for now).\n5. If the user does not approve, the application should:\n    a. Take note that the user disliked the previous joke.\n    b. Generate a new joke about the same topic, attempting to make it different from the disliked one.\n    c. Repeat step 3.\n\n## Flow Design\n\n> Notes for AI:\n> 1. Consider the design patterns of agent, map-reduce, rag, and workflow. Apply them if they fit.\n> 2. Present a concise, high-level description of the workflow.\n\n### Applicable Design Pattern:\n\n**Agent**: The system acts as an agent that interacts with the user. It takes user input (topic, feedback), performs an action (generates a joke), and then decides the next step based on user feedback (either end or try generating another joke). This iterative process of generation and feedback fits the agent pattern.\n\n### Flow high-level Design:\n\n1.  **GetTopicNode**: Prompts the user to enter the topic for the joke.\n2.  **GenerateJokeNode**: Generates a joke based on the topic and any previous feedback.\n3.  **GetFeedbackNode**: Presents the joke to the user and asks for approval. Based on the feedback, it either transitions to end the flow or back to `GenerateJokeNode`.\n\n```mermaid\nflowchart TD\n    GetTopic[GetTopicNode] --> GenerateJoke[GenerateJokeNode]\n    GenerateJoke --> GetFeedback[GetFeedbackNode]\n    GetFeedback -- \"Approve\" --> Z((End))\n    GetFeedback -- \"Disapprove\" --> GenerateJoke\n```\n## Utility Functions\n\n> Notes for AI:\n> 1. Understand the utility function definition thoroughly by reviewing the doc.\n> 2. Include only the necessary utility functions, based on nodes in the flow.\n\n1.  **Call LLM** (`utils/call_llm.py`)\n    *   *Input*: `prompt` (str), potentially including context like previously disliked jokes.\n    *   *Output*: `response` (str) - the generated joke.\n    *   *Necessity*: Used by `GenerateJokeNode` to generate jokes.\n\n## Node Design\n\n### Shared Store\n\n> Notes for AI: Try to minimize data redundancy\n\nThe shared store structure is organized as follows:\n\n```python\nshared = {\n    \"topic\": None,             # Stores the user-provided joke topic\n    \"current_joke\": None,      # Stores the most recently generated joke\n    \"disliked_jokes\": [],    # A list to store jokes the user didn't like, for context\n    \"user_feedback\": None      # Stores the user's latest feedback (e.g., \"approve\", \"disapprove\")\n}\n```\n\n### Node Steps\n\n> Notes for AI: Carefully decide whether to use Batch/Async Node/Flow.\n\n1.  **GetTopicNode**\n    *   *Purpose*: To get the desired joke topic from the user.\n    *   *Type*: Regular\n    *   *Steps*:\n        *   `prep`: (None needed for the first run, or could check if a topic already exists if we were to loop for a new topic)\n        *   `exec`: Prompt the user via `input()` for a joke topic.\n        *   `post`: Store the user's input topic into `shared[\"topic\"]`. Return `\"default\"` action to proceed to `GenerateJokeNode`.\n\n2.  **GenerateJokeNode**\n    *   *Purpose*: To generate a joke using an LLM, based on the topic and any previously disliked jokes.\n    *   *Type*: Regular\n    *   *Steps*:\n        *   `prep`: Read `shared[\"topic\"]` and `shared[\"disliked_jokes\"]`. Construct a prompt for the LLM, including the topic and a message like \"The user did not like the following jokes: [list of disliked jokes]. Please generate a new, different joke about [topic].\"\n        *   `exec`: Call the `call_llm` utility function with the prepared prompt.\n        *   `post`: Store the generated joke in `shared[\"current_joke\"]`. Print the joke to the console. Return `\"default\"` action to proceed to `GetFeedbackNode`.\n\n3.  **GetFeedbackNode**\n    *   *Purpose*: To get feedback from the user about the generated joke and decide the next step.\n    *   *Type*: Regular\n    *   *Steps*:\n        *   `prep`: Read `shared[\"current_joke\"]`.\n        *   `exec`: Prompt the user (e.g., \"Did you like this joke? (yes/no) or (approve/disapprove): \"). Get user's input.\n        *   `post`:\n            *   If user input indicates approval (e.g., \"yes\", \"approve\"):\n                *   Store \"approve\" in `shared[\"user_feedback\"]`.\n                *   Return `\"Approve\"` action (leading to flow termination or a thank you message).\n            *   If user input indicates disapproval (e.g., \"no\", \"disapprove\"):\n                *   Store \"disapprove\" in `shared[\"user_feedback\"]`.\n                *   Add `shared[\"current_joke\"]` to the `shared[\"disliked_jokes\"]` list.\n                *   Return `\"Disapprove\"` action (leading back to `GenerateJokeNode`).\n"
  },
  {
    "path": "cookbook/pocketflow-cli-hitl/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import GetTopicNode, GenerateJokeNode, GetFeedbackNode\n\ndef create_joke_flow() -> Flow:\n    \"\"\"Creates and returns the joke generation flow.\"\"\"\n    get_topic_node = GetTopicNode()\n    generate_joke_node = GenerateJokeNode()\n    get_feedback_node = GetFeedbackNode()\n\n    get_topic_node >> generate_joke_node\n    generate_joke_node >> get_feedback_node\n    get_feedback_node - \"Disapprove\" >> generate_joke_node\n\n    joke_flow = Flow(start=get_topic_node)\n    return joke_flow "
  },
  {
    "path": "cookbook/pocketflow-cli-hitl/main.py",
    "content": "from flow import create_joke_flow\n\ndef main():\n    \"\"\"Main function to run the joke generator application.\"\"\"\n    print(\"Welcome to the Command-Line Joke Generator!\")\n\n    shared = {\n        \"topic\": None,\n        \"current_joke\": None,\n        \"disliked_jokes\": [],\n        \"user_feedback\": None\n    }\n\n    joke_flow = create_joke_flow()\n    joke_flow.run(shared)\n\n    print(\"\\nThanks for using the Joke Generator!\")\n\nif __name__ == \"__main__\":\n    main() "
  },
  {
    "path": "cookbook/pocketflow-cli-hitl/nodes.py",
    "content": "from pocketflow import Node\nfrom utils.call_llm import call_llm\n\nclass GetTopicNode(Node):\n    \"\"\"Prompts the user to enter the topic for the joke.\"\"\"\n    def exec(self, _shared):\n        return input(\"What topic would you like a joke about? \")\n\n    def post(self, shared, _prep_res, exec_res):\n        shared[\"topic\"] = exec_res\n\nclass GenerateJokeNode(Node):\n    \"\"\"Generates a joke based on the topic and any previous feedback.\"\"\"\n    def prep(self, shared):\n        topic = shared.get(\"topic\", \"anything\")\n        disliked_jokes = shared.get(\"disliked_jokes\", [])\n        \n        prompt = f\"Please generate an one-liner joke about: {topic}. Make it short and funny.\"\n        if disliked_jokes:\n            disliked_str = \"; \".join(disliked_jokes)\n            prompt = f\"The user did not like the following jokes: [{disliked_str}]. Please generate a new, different joke about {topic}.\"\n        return prompt\n\n    def exec(self, prep_res):\n        return call_llm(prep_res)\n\n    def post(self, shared, _prep_res, exec_res):\n        shared[\"current_joke\"] = exec_res\n        print(f\"\\nJoke: {exec_res}\")\n\nclass GetFeedbackNode(Node):\n    \"\"\"Presents the joke to the user and asks for approval.\"\"\"\n    def exec(self, _prep_res):\n        while True:\n            feedback = input(\"Did you like this joke? (yes/no): \").strip().lower()\n            if feedback in [\"yes\", \"y\", \"no\", \"n\"]:\n                return feedback\n            print(\"Invalid input. Please type 'yes' or 'no'.\")\n\n    def post(self, shared, _prep_res, exec_res):\n        if exec_res in [\"yes\", \"y\"]:\n            shared[\"user_feedback\"] = \"approve\"\n            print(\"Great! Glad you liked it.\")\n            return \"Approve\"\n        else:\n            shared[\"user_feedback\"] = \"disapprove\"\n            current_joke = shared.get(\"current_joke\")\n            if current_joke:\n                if \"disliked_jokes\" not in shared:\n                    shared[\"disliked_jokes\"] = []\n                shared[\"disliked_jokes\"].append(current_joke)\n            print(\"Okay, let me try another one.\")\n            return \"Disapprove\" "
  },
  {
    "path": "cookbook/pocketflow-cli-hitl/requirements.txt",
    "content": "pocketflow>=0.0.1\nanthropic>=0.20.0 # Or a recent version"
  },
  {
    "path": "cookbook/pocketflow-cli-hitl/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-cli-hitl/utils/call_llm.py",
    "content": "from anthropic import Anthropic\nimport os\n\ndef call_llm(prompt: str) -> str:\n    client = Anthropic(api_key=os.environ.get(\"ANTHROPIC_API_KEY\", \"your-anthropic-api-key\")) # Default if key not found\n    response = client.messages.create(\n        model=\"claude-3-haiku-20240307\", # Using a smaller model for jokes\n        max_tokens=150, # Jokes don't need to be very long\n        messages=[\n            {\"role\": \"user\", \"content\": prompt}\n        ]\n    )\n    return response.content[0].text\n\nif __name__ == \"__main__\":\n    print(\"Testing Anthropic LLM call for jokes:\")\n    joke_prompt = \"Tell me a one-liner joke about a cat.\"\n    print(f\"Prompt: {joke_prompt}\")\n    try:\n        response = call_llm(joke_prompt)\n        print(f\"Response: {response}\")\n    except Exception as e:\n        print(f\"Error calling LLM: {e}\")\n        print(\"Please ensure your ANTHROPIC_API_KEY environment variable is set correctly.\")"
  },
  {
    "path": "cookbook/pocketflow-code-generator/README.md",
    "content": "# PocketFlow Code Generator\n\nAn intelligent AI system that takes LeetCode-style coding problems and automatically generates comprehensive test cases, implements solutions, and iteratively improves them until all tests pass.\n\n- Check out the [Substack Post Tutorial](https://pocketflow.substack.com/p/build-your-own-ai-code-generator) for more!\n\n## Features\n\n- **Automatic Test Case Generation**: Creates diverse test cases including edge cases\n- **Intelligent Code Implementation**: Generates `run_code` functions with proper algorithms\n- **Iterative Improvement**: Analyzes failures and decides whether to revise tests or code\n- **Rich Debugging Output**: Detailed progress tracking and validation\n\n## Getting Started\n\n1. Install required dependencies:\n```bash\npip install -r requirements.txt\n```\n\n2. Set up your Anthropic API key:\n    ```bash\n    export ANTHROPIC_API_KEY=\"your-api-key-here\"\n    ```\n    Test your API key is working:\n    ```bash\n    python utils/call_llm.py\n    ```\n\n3. Run the code generator with the default Two Sum problem:\n```bash\npython main.py\n```\n\n4. Or provide your own problem:\n```bash\npython main.py \"Reverse a linked list. Given the head of a singly linked list, reverse the list and return the reversed list.\"\n```\n\n## How It Works\n\nThe system follows an intelligent workflow combining **Agent** and **Workflow** design patterns:\n\n```mermaid\nflowchart TD\n    start[Problem Input] --> generateTests[Generate Test Cases]\n    generateTests --> implement[Implement Function]\n    implement --> runTests[Run Tests - Batch]\n    runTests --> decision{All Tests Pass?}\n    decision -->|Yes| success[Success!]\n    decision -->|No| revise[Revise - Agent Decision]\n    revise --> runTests\n    decision -->|Max Iterations| maxIter[Max Iterations Reached]\n```\n\n### The Process\n\n1. **GenerateTestCases**: Creates 5-7 comprehensive test cases from problem description\n2. **ImplementFunction**: Writes a `run_code` function based on problem and test cases  \n3. **RunTests**: Executes function against all test cases using batch processing\n4. **Revise**: Analyzes failures and makes intelligent decisions to revise test cases and/or function code\n5. **Loop**: Continues until all tests pass or max iterations reached\n\n## Sample Output\n\nHere's what you'll see when running the Two Sum example:\n\n```\nStarting PocketFlow Code Generator...\n\n=== Generated 7 Test Cases ===\n1. Basic case - solution at beginning\n   input: {'nums': [2, 7, 11, 15], 'target': 9}\n   expected: [0, 1]\n2. Basic case - solution in middle\n   input: {'nums': [3, 2, 4], 'target': 6}\n   expected: [1, 2]\n3. Edge case - minimum array size with duplicates\n   input: {'nums': [3, 3], 'target': 6}\n   expected: [0, 1]\n4. Case with negative numbers\n   input: {'nums': [-1, -2, -3, -4, -5], 'target': -8}\n   expected: [2, 4]\n5. Case with zero and negative target\n   input: {'nums': [0, 4, 3, 0], 'target': 0}\n   expected: [0, 3]\n6. Case with solution at the end\n   input: {'nums': [1, 2, 3, 4, 5, 6], 'target': 11}\n   expected: [4, 5]\n7. Larger array case\n   input: {'nums': [5, 75, 25, 45, 42, 2, 11, 9, 55, 12], 'target': 14}\n   expected: [2, 6]\n\n=== Implemented Function ===\ndef run_code(nums, target):\n    # Dictionary to store number -> index mapping\n    num_to_index = {}\n    \n    # Iterate through the array\n    for i, num in enumerate(nums):\n        # Calculate what number we need to reach the target\n        complement = target - num\n        \n        # Check if the complement exists in our map\n        if complement in num_to_index:\n            # Found the pair! Return indices\n            return [num_to_index[complement], i]\n        \n        # Store current number and its index\n        num_to_index[num] = i\n    \n    # Should never reach here given problem constraints\n    return []\n\n=== Test Results: 6/7 Passed ===\nFailed tests:\n1. Larger array case:\n   error: Expected [2, 6], got [0, 7]\n   expected: [2, 6]\n\n=== Revisions (Iteration 1) ===\nRevising test cases:\n  Test 7: 'Larger array case' -> 'Larger array case'\n    old input: {'nums': [5, 75, 25, 45, 42, 2, 11, 9, 55, 12], 'target': 14}\n    new input: {'nums': [5, 75, 25, 45, 42, 2, 11, 9, 55, 12], 'target': 14}\n    old expected: [2, 6]\n    new expected: [0, 7]\n\n=== Test Results: 7/7 Passed ===\n```\n\n## Key Features\n\n### Intelligent Decision Making\nThe **Revise** node acts as an agent that analyzes test failures and decides whether to:\n- Fix test cases (if they have incorrect expected outputs)  \n- Fix the function implementation (if the logic is wrong)\n- Or both\n\n### Structured Output with Validation\nAll LLM interactions use YAML format with:\n- **Reasoning fields**: Transparent decision-making process\n- **Validation asserts**: Ensures outputs match expected structure\n- **Rich debugging**: Comprehensive logging of all steps\n\n### Batch Processing\nThe **RunTests** node uses PocketFlow's BatchNode to efficiently test the function against all test cases in parallel.\n\n## Files\n\n- [`main.py`](./main.py): Entry point with sample Two Sum problem\n- [`flow.py`](./flow.py): Connects all nodes into the complete workflow  \n- [`nodes.py`](./nodes.py): Core logic nodes with validation and debugging\n- [`utils/call_llm.py`](./utils/call_llm.py): Anthropic Claude API wrapper\n- [`utils/code_executor.py`](./utils/code_executor.py): Safe Python code execution utility\n- [`doc/design.md`](./doc/design.md): Detailed system design documentation\n\n## Design Patterns Used\n\n- **[Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html)**: Sequential steps of test generation → coding → testing\n- **[Agent](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html)**: Intelligent decision-making when tests fail\n- **[Batch](https://the-pocket.github.io/PocketFlow/core_abstraction/batch.html)**: Efficient parallel test execution\n- **[Structured Output](https://the-pocket.github.io/PocketFlow/design_pattern/structure.html)**: YAML validation for reliable LLM outputs \n"
  },
  {
    "path": "cookbook/pocketflow-code-generator/doc/design.md",
    "content": "# Design Doc: PocketFlow Code Generator\n\n> Please DON'T remove notes for AI\n\n## Requirements\n\n> Notes for AI: Keep it simple and clear.\n> If the requirements are abstract, write concrete user stories\n\n**User Story**: As a developer, I want an AI system that can take a LeetCode-style coding problem and automatically:\n1. Generate comprehensive test cases including edge cases\n2. Implement a solution function\n3. Test the implementation against the test cases\n4. When tests fail, intelligently decide whether to revise the test cases or the function\n5. Iterate until all tests pass\n\n**Sample Problem**: Two Sum - Given an array of integers and a target, return indices of two numbers that add up to the target.\n\nThis is well-suited for AI because:\n- ✅ Routine task: Test case generation follows patterns\n- ✅ Creative task: Code generation from clear problem descriptions  \n- ✅ Clear decision criteria: Whether to revise tests vs implementation\n\n## Flow Design\n\n> Notes for AI:\n> 1. Consider the design patterns of agent, map-reduce, rag, and workflow. Apply them if they fit.\n> 2. Present a concise, high-level description of the workflow.\n\n### Applicable Design Pattern:\n\n1. **Workflow Pattern**: Sequential steps of test generation → coding → testing\n2. **Agent Pattern**: Decision-making when tests fail with structured output\n   - *Context*: Test results, current test cases, and function code\n   - *Actions*: Structured output to revise test cases and/or function\n\n### Flow high-level Design:\n\n1. **Generate Test Cases**: Create comprehensive input/output test pairs from problem description\n2. **Implement Function**: Write `def run_code` function based on problem and current test cases  \n3. **Run Tests**: Execute function against all test cases using batch processing\n4. **Revise**: Analyze failures and output structured revisions for test cases and/or function\n5. **Loop back to Run Tests** until all pass\n\n```mermaid\nflowchart TD\n    start[Problem Input] --> generateTests[Generate Test Cases]\n    generateTests --> implement[Implement Function]\n    implement --> runTests[Run Tests - Batch]\n    runTests --> decision{All Tests Pass?}\n    decision -->|Yes| success[Success!]\n    decision -->|No| revise[Revise]\n    revise --> runTests\n```\n\n## Utility Functions\n\n> Notes for AI:\n> 1. Understand the utility function definition thoroughly by reviewing the doc.\n> 2. Include only the necessary utility functions, based on nodes in the flow.\n\n1. **Call LLM** (`utils/call_llm.py`)\n   - *Input*: prompt (str)\n   - *Output*: response (str)\n   - Used by all LLM-powered nodes for generating tests, code, and analysis\n\n2. **Execute Python Code** (`utils/code_executor.py`)\n   - *Input*: function_code (str), input (dict/list/any)\n   - *Output*: output (any), error (str)\n   - Used by RunTests batch node to safely execute generated code against individual input\n\n## Node Design\n\n### Shared Memory\n\n> Notes for AI: Try to minimize data redundancy\n\nThe shared memory structure is organized as follows:\n\n```python\nshared = {\n    \"problem\": \"Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.\",\n    \"test_cases\": [\n        {\"name\": \"Basic case\", \"input\": {\"nums\": [2,7,11,15], \"target\": 9}, \"expected\": [0,1]},\n        {\"name\": \"Different order\", \"input\": {\"nums\": [3,2,4], \"target\": 6}, \"expected\": [1,2]},\n        # ... more test cases\n    ],\n    \"function_code\": \"def run_code(nums, target): ...\",\n    \"test_results\": [\n        {\"test_case\": {...}, \"passed\": True/False, \"error\": \"...\"},\n        # ... results for each test case\n    ],\n    \"iteration_count\": 0,\n    \"max_iterations\": 5\n}\n```\n\n### Node Steps\n\n> Notes for AI: Carefully decide whether to use Batch/Async Node/Flow.\n\n1. **GenerateTestCases Node**\n  - *Purpose*: Create comprehensive test cases including edge cases from problem description\n  - *Type*: Regular Node\n  - *Steps*:\n    - *prep*: Read problem description from shared store\n    - *exec*: Call LLM to generate diverse test cases in structured format\n    - *post*: Store test cases directly in shared[\"test_cases\"]\n\n2. **ImplementFunction Node**\n  - *Purpose*: Generate `def run_code` function based on problem and current test cases\n  - *Type*: Regular Node  \n  - *Steps*:\n    - *prep*: Read problem description and test cases from shared store\n    - *exec*: Call LLM to implement `def run_code` function with clean code output\n    - *post*: Store function code directly in shared[\"function_code\"]\n\n3. **RunTests Node**\n  - *Purpose*: Execute function against all test cases using batch processing\n  - *Type*: Batch Node\n  - *Steps*:\n    - *prep*: Read function code from shared store, return list of test cases\n    - *exec*: Use code executor utility to run function against each individual test case\n    - *post*: Store all results in shared[\"test_results\"], return \"success\" if all pass else \"failure\"\n\n4. **Revise Node** (Agent with Structured Output)\n  - *Purpose*: Analyze test failures and output structured revisions for test cases and/or function\n  - *Type*: Regular Node (Agent decision-making)\n  - *Steps*:\n    - *prep*: Read test results, test cases, function code, iteration count from shared store\n    - *exec*: Call LLM to analyze failures and output structured YAML with revised test cases and/or function code\n    - *post*: Update shared[\"test_cases\"] and/or shared[\"function_code\"] based on structured output\n"
  },
  {
    "path": "cookbook/pocketflow-code-generator/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import GenerateTestCases, ImplementFunction, RunTests, Revise\n\ndef create_code_generator_flow():\n    \"\"\"Creates and returns the code generator flow.\"\"\"\n    # Create nodes\n    generate_tests = GenerateTestCases()\n    implement_function = ImplementFunction()\n    run_tests = RunTests()\n    revise = Revise()\n\n    # Define transitions\n    generate_tests >> implement_function\n    implement_function >> run_tests\n    run_tests - \"failure\" >> revise\n    revise >> run_tests\n\n    # Create flow starting with test generation\n    flow = Flow(start=generate_tests)\n    return flow "
  },
  {
    "path": "cookbook/pocketflow-code-generator/main.py",
    "content": "import sys\nfrom flow import create_code_generator_flow\n\ndef main():\n    \"\"\"Runs the PocketFlow Code Generator application.\"\"\"\n    print(\"Starting PocketFlow Code Generator...\")\n    \n    # Check if problem is provided as argument\n    if len(sys.argv) > 1:\n        problem = \" \".join(sys.argv[1:])\n    else:\n        # Default Two Sum problem\n        problem = \"\"\"Two Sum\n\nGiven an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.\n\nYou may assume that each input would have exactly one solution, and you may not use the same element twice.\n\nExample 1:\nInput: nums = [2,7,11,15], target = 9\nOutput: [0,1]\n\nExample 2:\nInput: nums = [3,2,4], target = 6\nOutput: [1,2]\n\nExample 3:\nInput: nums = [3,3], target = 6\nOutput: [0,1]\"\"\"\n\n    shared = {\n        \"problem\": problem,\n        \"test_cases\": [],  # Will be populated with [{name, input, expected}, ...]\n        \"function_code\": \"\",\n        \"test_results\": [],\n        \"iteration_count\": 0,\n        \"max_iterations\": 5\n    }\n\n    # Create and run the flow\n    flow = create_code_generator_flow()\n    flow.run(shared)\n    \n    print(\"\\n=== Final Results ===\")\n    print(f\"Problem: {shared['problem'][:50]}...\")\n    print(f\"Iterations: {shared['iteration_count']}\")\n    print(f\"Function:\\n{shared['function_code']}\")\n    print(f\"Test Results: {len([r for r in shared['test_results'] if r['passed']])}/{len(shared['test_results'])} passed\")\n\nif __name__ == \"__main__\":\n    main() "
  },
  {
    "path": "cookbook/pocketflow-code-generator/nodes.py",
    "content": "import yaml\nfrom pocketflow import Node, BatchNode\nfrom utils.call_llm import call_llm\nfrom utils.code_executor import execute_python\n\nclass GenerateTestCases(Node):\n    def prep(self, shared):\n        return shared[\"problem\"]\n\n    def exec(self, problem):\n        prompt = f\"\"\"Generate 5-7 test cases for this coding problem:\n\n{problem}\n\nOutput in this YAML format with reasoning:\n```yaml\nreasoning: |\n    The input parameters should be: param1 as a string, and param2 as a number.\n    To test the function, I will consider basic cases, edge cases, and corner cases.\n    For this problem, I need to test...\ntest_cases:\n  - name: \"Basic case\"\n    input: {{param1: value1, param2: value2}}\n    expected: result1\n  - name: \"Edge case - empty\"\n    input: {{param1: value3, param2: value4}}\n    expected: result2\n```\"\"\"\n        response = call_llm(prompt)\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        result = yaml.safe_load(yaml_str)\n        \n        # Validation asserts\n        assert \"test_cases\" in result, \"Result must have 'test_cases' field\"\n        assert isinstance(result[\"test_cases\"], list), \"test_cases must be a list\"\n        \n        for i, test_case in enumerate(result[\"test_cases\"]):\n            assert \"name\" in test_case, f\"Test case {i} missing 'name' field\"\n            assert isinstance(test_case[\"name\"], str), f\"Test case {i} 'name' must be string\"\n            assert \"input\" in test_case, f\"Test case {i} missing 'input' field\"\n            assert isinstance(test_case[\"input\"], dict), f\"Test case {i} 'input' must be dict\"\n            assert \"expected\" in test_case, f\"Test case {i} missing 'expected' field\"\n        \n        return result\n\n    def post(self, shared, prep_res, exec_res):\n        shared[\"test_cases\"] = exec_res[\"test_cases\"]\n        \n        # Print all generated test cases\n        print(f\"\\n=== Generated {len(exec_res['test_cases'])} Test Cases ===\")\n        for i, test_case in enumerate(exec_res[\"test_cases\"], 1):\n            print(f\"{i}. {test_case['name']}\")\n            print(f\"   input: {test_case['input']}\")\n            print(f\"   expected: {test_case['expected']}\")\n\nclass ImplementFunction(Node):\n    def prep(self, shared):\n        return shared[\"problem\"], shared[\"test_cases\"]\n\n    def exec(self, inputs):\n        problem, test_cases = inputs\n        \n        # Format test cases nicely for the prompt\n        formatted_tests = \"\"\n        for i, test in enumerate(test_cases, 1):\n            formatted_tests += f\"{i}. {test['name']}\\n\"\n            formatted_tests += f\"   input: {test['input']}\\n\"\n            formatted_tests += f\"   expected: {test['expected']}\\n\\n\"\n        \n        prompt = f\"\"\"Implement a solution for this problem:\n\n{problem}\n\nTest cases to consider:\n{formatted_tests}\n\nIMPORTANT: The function name must be exactly \"run_code\"\n\nOutput in this YAML format:\n```yaml\nreasoning: |\n    To implement this function, I will...\n    My approach is...\nfunction_code: |\n    def run_code(...):\n        # your implementation\n        return result\n```\"\"\"\n        response = call_llm(prompt)\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        result = yaml.safe_load(yaml_str)\n        \n        # Validation asserts\n        assert \"function_code\" in result, \"Result must have 'function_code' field\"\n        assert isinstance(result[\"function_code\"], str), \"function_code must be string\"\n        assert \"def run_code\" in result[\"function_code\"], \"Function must be named 'run_code'\"\n        \n        return result[\"function_code\"]\n\n    def post(self, shared, prep_res, exec_res):\n        shared[\"function_code\"] = exec_res\n        \n        # Print the implemented function\n        print(f\"\\n=== Implemented Function ===\")\n        print(exec_res)\n\nclass RunTests(BatchNode):\n    def prep(self, shared):\n        function_code = shared[\"function_code\"]\n        test_cases = shared[\"test_cases\"]\n        # Return list of tuples (function_code, test_case)\n        return [(function_code, test_case) for test_case in test_cases]\n\n    def exec(self, test_data):\n        function_code, test_case = test_data\n        output, error = execute_python(function_code, test_case[\"input\"])\n        \n        if error:\n            return {\n                \"test_case\": test_case,\n                \"passed\": False,\n                \"actual\": None,\n                \"expected\": test_case[\"expected\"],\n                \"error\": error\n            }\n        \n        passed = output == test_case[\"expected\"]\n        return {\n            \"test_case\": test_case,\n            \"passed\": passed,\n            \"actual\": output,\n            \"expected\": test_case[\"expected\"],\n            \"error\": None if passed else f\"Expected {test_case['expected']}, got {output}\"\n        }\n\n    def post(self, shared, prep_res, exec_res_list):\n        shared[\"test_results\"] = exec_res_list\n        all_passed = all(result[\"passed\"] for result in exec_res_list)\n        shared[\"iteration_count\"] = shared.get(\"iteration_count\", 0) + 1\n        \n        # Print test results\n        passed_count = len([r for r in exec_res_list if r[\"passed\"]])\n        total_count = len(exec_res_list)\n        print(f\"\\n=== Test Results: {passed_count}/{total_count} Passed ===\")\n        \n        failed_tests = [r for r in exec_res_list if not r[\"passed\"]]\n        if failed_tests:\n            print(\"Failed tests:\")\n            for i, result in enumerate(failed_tests, 1):\n                test_case = result['test_case']\n                print(f\"{i}. {test_case['name']}:\")\n                if result['error']:\n                    print(f\"   error: {result['error']}\")\n                else:\n                    print(f\"   output: {result['actual']}\")\n                print(f\"   expected: {result['expected']}\")\n        \n        if all_passed:\n            return \"success\"\n        elif shared[\"iteration_count\"] >= shared.get(\"max_iterations\", 5):\n            return \"max_iterations\"\n        else:\n            return \"failure\"\n\nclass Revise(Node):\n    def prep(self, shared):\n        failed_tests = [r for r in shared[\"test_results\"] if not r[\"passed\"]]\n        return {\n            \"problem\": shared[\"problem\"],\n            \"test_cases\": shared[\"test_cases\"],\n            \"function_code\": shared[\"function_code\"],\n            \"failed_tests\": failed_tests\n        }\n\n    def exec(self, inputs):\n        # Format current test cases nicely\n        formatted_tests = \"\"\n        for i, test in enumerate(inputs['test_cases'], 1):\n            formatted_tests += f\"{i}. {test['name']}\\n\"\n            formatted_tests += f\"   input: {test['input']}\\n\"\n            formatted_tests += f\"   expected: {test['expected']}\\n\\n\"\n        \n        # Format failed tests nicely\n        formatted_failures = \"\"\n        for i, result in enumerate(inputs['failed_tests'], 1):\n            test_case = result['test_case']\n            formatted_failures += f\"{i}. {test_case['name']}:\\n\"\n            if result['error']:\n                formatted_failures += f\"   error: {result['error']}\\n\"\n            else:\n                formatted_failures += f\"   output: {result['actual']}\\n\"\n            formatted_failures += f\"   expected: {result['expected']}\\n\\n\"\n\n        prompt = f\"\"\"Problem: {inputs['problem']}\n\nCurrent test cases:\n{formatted_tests}\n\nCurrent function:\n```python\n{inputs['function_code']}\n```\n\nFailed tests:\n{formatted_failures}\n\nAnalyze the failures and output revisions in YAML. You can revise test cases, function code, or both:\n\n```yaml\nreasoning: |\n    Looking at the failures, I see that...\n    The issue appears to be...\n    I will revise...\ntest_cases:  # Dictionary mapping test case index (1-based) to revised test case\n  1:\n    name: \"Revised test name\"\n    input: {{...}}\n    expected: ...\nfunction_code: |  # Include this if revising function\n  def run_code(...):\n    return ...\n```\"\"\"\n        response = call_llm(prompt)\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        result = yaml.safe_load(yaml_str)\n        \n        # Validation asserts\n        if \"test_cases\" in result:\n            assert isinstance(result[\"test_cases\"], dict), \"test_cases must be a dictionary\"\n            for index_str, test_case in result[\"test_cases\"].items():\n                assert isinstance(index_str, (str, int)), \"test_cases keys must be strings or ints\"\n                assert \"name\" in test_case, f\"Revised test case {index_str} missing 'name' field\"\n                assert \"input\" in test_case, f\"Revised test case {index_str} missing 'input' field\"\n                assert \"expected\" in test_case, f\"Revised test case {index_str} missing 'expected' field\"\n        \n        if \"function_code\" in result:\n            assert isinstance(result[\"function_code\"], str), \"function_code must be string\"\n            assert \"def run_code\" in result[\"function_code\"], \"Function must be named 'run_code'\"\n        \n        return result\n\n    def post(self, shared, prep_res, exec_res):\n        # Print what is being revised\n        print(f\"\\n=== Revisions (Iteration {shared['iteration_count']}) ===\")\n        \n        # Handle test case revisions - map indices to actual test cases\n        if \"test_cases\" in exec_res:\n            current_tests = shared[\"test_cases\"].copy()\n            print(\"Revising test cases:\")\n            for index_str, revised_test in exec_res[\"test_cases\"].items():\n                index = int(index_str) - 1  # Convert to 0-based\n                if 0 <= index < len(current_tests):\n                    old_test = current_tests[index]\n                    print(f\"  Test {index_str}: '{old_test['name']}' -> '{revised_test['name']}'\")\n                    print(f\"    old input: {old_test['input']}\")\n                    print(f\"    new input: {revised_test['input']}\")\n                    print(f\"    old expected: {old_test['expected']}\")\n                    print(f\"    new expected: {revised_test['expected']}\")\n                    current_tests[index] = revised_test\n            shared[\"test_cases\"] = current_tests\n            \n        if \"function_code\" in exec_res:\n            print(\"Revising function code:\")\n            print(\"New function:\")\n            print(exec_res[\"function_code\"])\n            shared[\"function_code\"] = exec_res[\"function_code\"] "
  },
  {
    "path": "cookbook/pocketflow-code-generator/requirements.txt",
    "content": "anthropic\npocketflow\npyyaml "
  },
  {
    "path": "cookbook/pocketflow-code-generator/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-code-generator/utils/call_llm.py",
    "content": "from anthropic import Anthropic\nimport os\n\ndef call_llm(prompt):\n    client = Anthropic(api_key=os.environ.get(\"ANTHROPIC_API_KEY\", \"your-api-key\"))\n    response = client.messages.create(\n        model=\"claude-sonnet-4-20250514\",\n        max_tokens=6000,\n        messages=[\n            {\"role\": \"user\", \"content\": prompt}\n        ]\n    )\n    return response.content[0].text\n\nif __name__ == \"__main__\":\n    print(\"## Testing call_llm\")\n    prompt = \"In a few words, what is the meaning of life?\"\n    print(f\"## Prompt: {prompt}\")\n    response = call_llm(prompt)\n    print(f\"## Response: {response}\")"
  },
  {
    "path": "cookbook/pocketflow-code-generator/utils/code_executor.py",
    "content": "import sys\nimport io\nimport traceback\nfrom contextlib import redirect_stdout, redirect_stderr\n\ndef execute_python(function_code, input):\n    try:\n        namespace = {\"__builtins__\": __builtins__}\n        stdout_capture = io.StringIO()\n        stderr_capture = io.StringIO()\n        \n        with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):\n            exec(function_code, namespace)\n            \n            if \"run_code\" not in namespace:\n                return None, \"Function 'run_code' not found\"\n            \n            run_code = namespace[\"run_code\"]\n            \n            if isinstance(input, dict):\n                result = run_code(**input)\n            elif isinstance(input, (list, tuple)):\n                result = run_code(*input)\n            else:\n                result = run_code(input)\n            \n            return result, None\n                \n    except Exception as e:\n        return None, f\"{type(e).__name__}: {str(e)}\"\n\nif __name__ == \"__main__\":\n    # Test 1: Working function\n    function_code = \"\"\"\ndef run_code(nums, target):\n    for i in range(len(nums)):\n        for j in range(i + 1, len(nums)):\n            if nums[i] + nums[j] == target:\n                return [i, j]\n    return []\n\"\"\"\n    \n    input = {\"nums\": [2, 7, 11, 15], \"target\": 9}\n    output, error = execute_python(function_code, input)\n    print(f\"Output: {output}\")\n    print(f\"Error: {error}\")\n    \n    # Test 2: Function with error\n    broken_function_code = \"\"\"\ndef run_code(nums, target):\n    return nums[100]  # Index error\n\"\"\"\n    \n    output2, error2 = execute_python(broken_function_code, input)\n    print(f\"Output: {output2}\")\n    print(f\"Error: {error2}\") "
  },
  {
    "path": "cookbook/pocketflow-communication/README.md",
    "content": "# PocketFlow Communication Example\n\nThis example demonstrates the [Communication](https://the-pocket.github.io/PocketFlow/communication.html) concept in PocketFlow, specifically focusing on the Shared Store pattern.\n\n## Overview\n\nThe example implements a simple word counter that shows how nodes can communicate using a shared store. It demonstrates:\n\n- How to initialize and structure a shared store\n- How nodes can read from and write to the shared store\n- How to maintain state across multiple node executions\n- Best practices for shared store usage\n\n## Project Structure\n\n```\npocketflow-communication/\n├── README.md\n├── requirements.txt\n├── main.py\n├── flow.py\n└── nodes.py\n```\n\n## Installation\n\n```bash\npip install -r requirements.txt\n```\n\n## Usage\n\n```bash\npython main.py\n```\n\nEnter text when prompted. The program will:\n1. Count words in the text\n2. Store statistics in the shared store\n3. Display running statistics (total texts, total words, average)\n\nEnter 'q' to quit.\n\n## How it Works\n\nThe example uses three nodes:\n\n1. `TextInput`: Reads user input and initializes the shared store\n2. `WordCounter`: Counts words and updates statistics in the shared store\n3. `ShowStats`: Displays statistics from the shared store\n\nThis demonstrates how nodes can share and maintain state using the shared store pattern. "
  },
  {
    "path": "cookbook/pocketflow-communication/flow.py",
    "content": "\"\"\"Flow configuration for the communication example.\"\"\"\n\nfrom pocketflow import Flow\nfrom nodes import TextInput, WordCounter, ShowStats, EndNode\n\ndef create_flow():\n    \"\"\"Create and configure the flow with all nodes.\"\"\"\n    # Create nodes\n    text_input = TextInput()\n    word_counter = WordCounter()\n    show_stats = ShowStats()\n    end_node = EndNode()\n    \n    # Configure transitions\n    text_input - \"count\" >> word_counter\n    word_counter - \"show\" >> show_stats\n    show_stats - \"continue\" >> text_input\n    text_input - \"exit\" >> end_node\n    \n    # Create and return flow\n    return Flow(start=text_input) "
  },
  {
    "path": "cookbook/pocketflow-communication/main.py",
    "content": "from flow import create_flow\n\ndef main():\n    \"\"\"Run the communication example.\"\"\"\n    flow = create_flow()\n    shared = {}\n    flow.run(shared)\n\nif __name__ == \"__main__\":\n    main() "
  },
  {
    "path": "cookbook/pocketflow-communication/nodes.py",
    "content": "\"\"\"Node implementations for the communication example.\"\"\"\n\nfrom pocketflow import Node\n\nclass EndNode(Node):\n    \"\"\"Node that handles flow termination.\"\"\"\n    pass\n\nclass TextInput(Node):\n    \"\"\"Node that reads text input and initializes the shared store.\"\"\"\n    \n    def prep(self, shared):\n        \"\"\"Get user input and ensure shared store is initialized.\"\"\"\n        return input(\"Enter text (or 'q' to quit): \")\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store text and initialize/update statistics.\"\"\"\n        if prep_res == 'q':\n            return \"exit\"\n        \n        # Store the text\n        shared[\"text\"] = prep_res\n        \n        # Initialize statistics if they don't exist\n        if \"stats\" not in shared:\n            shared[\"stats\"] = {\n                \"total_texts\": 0,\n                \"total_words\": 0\n            }\n        shared[\"stats\"][\"total_texts\"] += 1\n        \n        return \"count\"\n\nclass WordCounter(Node):\n    \"\"\"Node that counts words in the text.\"\"\"\n    \n    def prep(self, shared):\n        \"\"\"Get text from shared store.\"\"\"\n        return shared[\"text\"]\n    \n    def exec(self, text):\n        \"\"\"Count words in the text.\"\"\"\n        return len(text.split())\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Update word count statistics.\"\"\"\n        shared[\"stats\"][\"total_words\"] += exec_res\n        return \"show\"\n\nclass ShowStats(Node):\n    \"\"\"Node that displays statistics from the shared store.\"\"\"\n    \n    def prep(self, shared):\n        \"\"\"Get statistics from shared store.\"\"\"\n        return shared[\"stats\"]\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Display statistics and continue the flow.\"\"\"\n        stats = prep_res\n        print(f\"\\nStatistics:\")\n        print(f\"- Texts processed: {stats['total_texts']}\")\n        print(f\"- Total words: {stats['total_words']}\")\n        print(f\"- Average words per text: {stats['total_words'] / stats['total_texts']:.1f}\\n\")\n        return \"continue\" "
  },
  {
    "path": "cookbook/pocketflow-communication/requirements.txt",
    "content": "pocketflow==0.1.0 "
  },
  {
    "path": "cookbook/pocketflow-fastapi-background/README.md",
    "content": "# PocketFlow FastAPI Background Jobs with Real-time Progress\n\nA web application demonstrating PocketFlow workflows running as FastAPI background jobs with real-time progress updates via Server-Sent Events (SSE).\n\n<p align=\"center\">\n  <img \n    src=\"./assets/banner.png\" width=\"800\"\n  />\n</p>\n\n## Features\n\n- **Modern Web UI**: Clean interface with real-time progress visualization\n- **Background Processing**: Non-blocking article generation using FastAPI BackgroundTasks\n- **Server-Sent Events**: Real-time progress streaming without polling\n- **Granular Progress**: Section-by-section updates during content generation\n- **PocketFlow Integration**: Three-node workflow (Outline → Content → Style)\n\n## How to Run\n\n1. Install Dependencies:\n   ```bash\n   pip install -r requirements.txt\n   ```\n\n2. Set your OpenAI API key:\n   ```bash\n   export OPENAI_API_KEY=your_api_key_here\n   ```\n\n3. Run the FastAPI Server:\n   ```bash\n   python main.py\n   ```\n\n4. Access the Web UI:\n   Open your browser and navigate to `http://localhost:8000`.\n\n5. Use the Application:\n   - Enter an article topic or click suggested topics\n   - Click \"Generate Article\" to start background processing\n   - Watch real-time progress updates with step indicators\n   - Copy the final article when complete\n\n## How It Works\n\nThe application uses PocketFlow to define a three-step article generation workflow. FastAPI handles web requests and manages real-time SSE communication for progress updates.\n\n**PocketFlow Workflow:**\n\n```mermaid\nflowchart LR\n    A[Generate Outline] --> B[Write Content]\n    B --> C[Apply Style]\n```\n\n1. **`GenerateOutline`**: Creates structured outline with up to 3 sections\n2. **`WriteContent` (BatchNode)**: Writes content for each section individually, sending progress updates\n3. **`ApplyStyle`**: Polishes the article with conversational tone\n\n**FastAPI & SSE Integration:**\n\n- The `/start-job` endpoint creates a unique job, initializes an SSE queue, and schedules the workflow using `BackgroundTasks`\n- Nodes send progress updates to the job-specific `sse_queue` during execution\n- The `/progress/{job_id}` endpoint streams real-time updates to the client via Server-Sent Events\n- The web UI displays progress with animated bars, step indicators, and detailed status messages\n\n**Progress Updates:**\n- 33%: Outline generation complete\n- 33-66%: Content writing (individual section updates)\n- 66-100%: Style application\n- 100%: Article ready\n\n## Files\n\n- [`main.py`](./main.py): FastAPI application with background jobs and SSE endpoints\n- [`flow.py`](./flow.py): PocketFlow workflow definition connecting the three nodes\n- [`nodes.py`](./nodes.py): Workflow nodes (GenerateOutline, WriteContent BatchNode, ApplyStyle)\n- [`utils/call_llm.py`](./utils/call_llm.py): OpenAI LLM utility function\n- [`static/index.html`](./static/index.html): Modern job submission form with topic suggestions\n- [`static/progress.html`](./static/progress.html): Real-time progress monitoring with animations"
  },
  {
    "path": "cookbook/pocketflow-fastapi-background/docs/design.md",
    "content": "# Design Doc: PocketFlow FastAPI Background Job with SSE Progress\n\n> Please DON'T remove notes for AI\n\n## Requirements\n\n> Notes for AI: Keep it simple and clear.\n> If the requirements are abstract, write concrete user stories\n\n**User Story**: As a user, I want to submit an article topic via a web API and receive real-time progress updates while the article is being generated in the background, so I can see the workflow progress without blocking the UI.\n\n**Core Requirements**:\n1. Submit article topic via REST API endpoint\n2. Start background job for article generation workflow\n3. Receive real-time progress updates via Server-Sent Events (SSE)\n4. Get final article result when workflow completes\n5. Handle multiple concurrent requests\n\n**Technical Requirements**:\n- FastAPI web server with REST endpoints\n- Background task processing using asyncio\n- Server-Sent Events for progress streaming\n- Simple web interface to test the functionality\n\n## Flow Design\n\n> Notes for AI:\n> 1. Consider the design patterns of agent, map-reduce, rag, and workflow. Apply them if they fit.\n> 2. Present a concise, high-level description of the workflow.\n\n### Applicable Design Pattern:\n\n**Workflow Pattern**: Sequential processing of article generation steps with progress reporting at each stage.\n\n### Flow High-level Design:\n\n1. **Generate Outline Node**: Creates a structured outline for the article topic\n2. **Write Content Node**: Writes content for each section in the outline  \n3. **Apply Style Node**: Applies conversational styling to the final article\n\nEach node puts progress updates into an asyncio.Queue for SSE streaming.\n\n```mermaid\nflowchart LR\n    outline[Generate Outline] --> content[Write Content]\n    content --> styling[Apply Style]\n```\n\n## Utility Functions\n\n> Notes for AI:\n> 1. Understand the utility function definition thoroughly by reviewing the doc.\n> 2. Include only the necessary utility functions, based on nodes in the flow.\n\n1. **Call LLM** (`utils/call_llm.py`)\n   - *Input*: prompt (str)\n   - *Output*: response (str)\n   - Used by all workflow nodes for LLM tasks\n\n## Node Design\n\n### Shared Store\n\n> Notes for AI: Try to minimize data redundancy\n\nThe shared store structure is organized as follows:\n\n```python\nshared = {\n    \"topic\": \"user-provided-topic\",\n    \"sse_queue\": asyncio.Queue(),  # For sending SSE updates\n    \"sections\": [\"section1\", \"section2\", \"section3\"],\n    \"draft\": \"combined-section-content\",\n    \"final_article\": \"styled-final-article\"\n}\n```\n\n### Node Steps\n\n> Notes for AI: Carefully decide whether to use Batch/Async Node/Flow.\n\n1. **Generate Outline Node**\n   - *Purpose*: Create a structured outline with 3 main sections using YAML output\n   - *Type*: Regular Node (synchronous LLM call)\n   - *Steps*:\n     - *prep*: Read \"topic\" from shared store\n     - *exec*: Call LLM to generate YAML outline, parse and validate structure\n     - *post*: Write \"sections\" to shared store, put progress update in sse_queue\n\n2. **Write Content Node**\n   - *Purpose*: Generate concise content for each outline section\n   - *Type*: BatchNode (processes each section independently)\n   - *Steps*:\n     - *prep*: Read \"sections\" from shared store (returns list of sections)\n     - *exec*: For one section, call LLM to write 100-word content\n     - *post*: Combine all section content into \"draft\", put progress update in sse_queue\n\n3. **Apply Style Node**\n   - *Purpose*: Apply conversational, engaging style to the combined content\n   - *Type*: Regular Node (single LLM call for styling)\n   - *Steps*:\n     - *prep*: Read \"draft\" from shared store\n     - *exec*: Call LLM to rewrite in conversational style\n     - *post*: Write \"final_article\" to shared store, put completion update in sse_queue\n"
  },
  {
    "path": "cookbook/pocketflow-fastapi-background/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import GenerateOutline, WriteContent, ApplyStyle\n\ndef create_article_flow():\n    \"\"\"\n    Create and configure the article writing workflow\n    \"\"\"\n    # Create node instances\n    outline_node = GenerateOutline()\n    content_node = WriteContent()\n    style_node = ApplyStyle()\n    \n    # Connect nodes in sequence\n    outline_node >> content_node >> style_node\n    \n    # Create flow starting with outline node\n    article_flow = Flow(start=outline_node)\n    \n    return article_flow "
  },
  {
    "path": "cookbook/pocketflow-fastapi-background/main.py",
    "content": "import asyncio\nimport json\nimport uuid\nfrom fastapi import FastAPI, BackgroundTasks, Form\nfrom fastapi.responses import StreamingResponse\nfrom fastapi.staticfiles import StaticFiles\nfrom fastapi.responses import FileResponse\nfrom flow import create_article_flow\n\napp = FastAPI()\n\n# Mount static files\napp.mount(\"/static\", StaticFiles(directory=\"static\"), name=\"static\")\n\n# Store active jobs and their SSE queues\nactive_jobs = {}\n\ndef run_article_workflow(job_id: str, topic: str):\n    \"\"\"Run the article workflow in background\"\"\"\n    try:\n        # Get the pre-created queue from active_jobs\n        sse_queue = active_jobs[job_id]\n        shared = {\n            \"topic\": topic,\n            \"sse_queue\": sse_queue,\n            \"sections\": [],\n            \"draft\": \"\",\n            \"final_article\": \"\"\n        }\n        \n        # Run the workflow\n        flow = create_article_flow()\n        flow.run(shared)\n        \n    except Exception as e:\n        # Send error message\n        error_msg = {\"step\": \"error\", \"progress\": 0, \"data\": {\"error\": str(e)}}\n        if job_id in active_jobs:\n            active_jobs[job_id].put_nowait(error_msg)\n\n@app.post(\"/start-job\")\nasync def start_job(background_tasks: BackgroundTasks, topic: str = Form(...)):\n    \"\"\"Start a new article generation job\"\"\"\n    job_id = str(uuid.uuid4())\n    \n    # Create SSE queue and register job immediately\n    sse_queue = asyncio.Queue()\n    active_jobs[job_id] = sse_queue\n    \n    # Start background task\n    background_tasks.add_task(run_article_workflow, job_id, topic)\n    \n    return {\"job_id\": job_id, \"topic\": topic, \"status\": \"started\"}\n\n@app.get(\"/progress/{job_id}\")\nasync def get_progress(job_id: str):\n    \"\"\"Stream progress updates via SSE\"\"\"\n    \n    async def event_stream():\n        if job_id not in active_jobs:\n            yield f\"data: {json.dumps({'error': 'Job not found'})}\\n\\n\"\n            return\n            \n        sse_queue = active_jobs[job_id]\n        \n        # Send initial connection confirmation\n        yield f\"data: {json.dumps({'step': 'connected', 'progress': 0, 'data': {'message': 'Connected to job progress'}})}\\n\\n\"\n        \n        try:\n            while True:\n                # Wait for next progress update\n                try:\n                    # Use asyncio.wait_for to avoid blocking forever\n                    progress_msg = await asyncio.wait_for(sse_queue.get(), timeout=1.0)\n                    yield f\"data: {json.dumps(progress_msg)}\\n\\n\"\n                    \n                    # If job is complete, clean up and exit\n                    if progress_msg.get(\"step\") == \"complete\":\n                        del active_jobs[job_id]\n                        break\n                        \n                except asyncio.TimeoutError:\n                    # Send heartbeat to keep connection alive\n                    yield f\"data: {json.dumps({'heartbeat': True})}\\n\\n\"\n                    \n        except Exception as e:\n            yield f\"data: {json.dumps({'error': str(e)})}\\n\\n\"\n    \n    return StreamingResponse(\n        event_stream(),\n        media_type=\"text/plain\",\n        headers={\n            \"Cache-Control\": \"no-cache\",\n            \"Connection\": \"keep-alive\",\n            \"Content-Type\": \"text/event-stream\"\n        }\n    )\n\n@app.get(\"/\")\nasync def get_index():\n    \"\"\"Serve the main page\"\"\"\n    return FileResponse(\"static/index.html\")\n\n@app.get(\"/progress.html\")\nasync def get_progress_page():\n    \"\"\"Serve the progress page\"\"\"\n    return FileResponse(\"static/progress.html\")\n\nif __name__ == \"__main__\":\n    import uvicorn\n    uvicorn.run(app, host=\"0.0.0.0\", port=8000) "
  },
  {
    "path": "cookbook/pocketflow-fastapi-background/nodes.py",
    "content": "import yaml\nfrom pocketflow import Node, BatchNode\nfrom utils.call_llm import call_llm\n\nclass GenerateOutline(Node):\n    def prep(self, shared):\n        return shared[\"topic\"]\n    \n    def exec(self, topic):\n        prompt = f\"\"\"\nCreate a simple outline for an article about {topic}.\nInclude at most 3 main sections (no subsections).\n\nOutput the sections in YAML format as shown below:\n\n```yaml\nsections:\n    - First section title\n    - Second section title  \n    - Third section title\n```\"\"\"\n        response = call_llm(prompt)\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        structured_result = yaml.safe_load(yaml_str)\n        return structured_result\n    \n    def post(self, shared, prep_res, exec_res):\n        sections = exec_res[\"sections\"]\n        shared[\"sections\"] = sections\n        \n        # Send progress update via SSE queue\n        progress_msg = {\"step\": \"outline\", \"progress\": 33, \"data\": {\"sections\": sections}}\n        shared[\"sse_queue\"].put_nowait(progress_msg)\n        \n        return \"default\"\n\nclass WriteContent(BatchNode):\n    def prep(self, shared):\n        # Store sections and sse_queue for use in exec\n        self.sections = shared.get(\"sections\", [])\n        self.sse_queue = shared[\"sse_queue\"]\n        return self.sections\n    \n    def exec(self, section):\n        prompt = f\"\"\"\nWrite a short paragraph (MAXIMUM 100 WORDS) about this section:\n\n{section}\n\nRequirements:\n- Explain the idea in simple, easy-to-understand terms\n- Use everyday language, avoiding jargon\n- Keep it very concise (no more than 100 words)\n- Include one brief example or analogy\n\"\"\"\n        content = call_llm(prompt)\n        \n        # Send progress update for this section\n        current_section_index = self.sections.index(section) if section in self.sections else 0\n        total_sections = len(self.sections)\n        \n        # Progress from 33% (after outline) to 66% (before styling)\n        # Each section contributes (66-33)/total_sections = 33/total_sections percent\n        section_progress = 33 + ((current_section_index + 1) * 33 // total_sections)\n        \n        progress_msg = {\n            \"step\": \"content\", \n            \"progress\": section_progress, \n            \"data\": {\n                \"section\": section,\n                \"completed_sections\": current_section_index + 1,\n                \"total_sections\": total_sections\n            }\n        }\n        self.sse_queue.put_nowait(progress_msg)\n        \n        return f\"## {section}\\n\\n{content}\\n\"\n    \n    def post(self, shared, prep_res, exec_res_list):\n        draft = \"\\n\".join(exec_res_list)\n        shared[\"draft\"] = draft\n        return \"default\"\n\nclass ApplyStyle(Node):\n    def prep(self, shared):\n        return shared[\"draft\"]\n    \n    def exec(self, draft):\n        prompt = f\"\"\"\nRewrite the following draft in a conversational, engaging style:\n\n{draft}\n\nMake it:\n- Conversational and warm in tone\n- Include rhetorical questions that engage the reader\n- Add analogies and metaphors where appropriate\n- Include a strong opening and conclusion\n\"\"\"\n        return call_llm(prompt)\n    \n    def post(self, shared, prep_res, exec_res):\n        shared[\"final_article\"] = exec_res\n        \n        # Send completion update via SSE queue\n        progress_msg = {\"step\": \"complete\", \"progress\": 100, \"data\": {\"final_article\": exec_res}}\n        shared[\"sse_queue\"].put_nowait(progress_msg)\n        \n        return \"default\" "
  },
  {
    "path": "cookbook/pocketflow-fastapi-background/requirements.txt",
    "content": "fastapi\nuvicorn\nopenai\npyyaml\npython-multipart "
  },
  {
    "path": "cookbook/pocketflow-fastapi-background/static/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Article Generator</title>\n    <style>\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n            min-height: 100vh;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            padding: 20px;\n        }\n\n        .container {\n            background: white;\n            border-radius: 20px;\n            padding: 40px;\n            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);\n            max-width: 500px;\n            width: 100%;\n            text-align: center;\n        }\n\n        .logo {\n            font-size: 2.5rem;\n            font-weight: 700;\n            background: linear-gradient(135deg, #667eea, #764ba2);\n            -webkit-background-clip: text;\n            -webkit-text-fill-color: transparent;\n            background-clip: text;\n            margin-bottom: 10px;\n        }\n\n        .subtitle {\n            color: #6b7280;\n            font-size: 1.1rem;\n            margin-bottom: 40px;\n            font-weight: 400;\n        }\n\n        .form-group {\n            margin-bottom: 30px;\n            text-align: left;\n        }\n\n        label {\n            display: block;\n            font-weight: 600;\n            color: #374151;\n            margin-bottom: 8px;\n            font-size: 0.95rem;\n        }\n\n        input[type=\"text\"] {\n            width: 100%;\n            padding: 16px 20px;\n            border: 2px solid #e5e7eb;\n            border-radius: 12px;\n            font-size: 1rem;\n            transition: all 0.3s ease;\n            background: #f9fafb;\n        }\n\n        input[type=\"text\"]:focus {\n            outline: none;\n            border-color: #667eea;\n            background: white;\n            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);\n        }\n\n        .submit-btn {\n            width: 100%;\n            padding: 16px;\n            background: linear-gradient(135deg, #667eea, #764ba2);\n            color: white;\n            border: none;\n            border-radius: 12px;\n            font-size: 1.1rem;\n            font-weight: 600;\n            cursor: pointer;\n            transition: all 0.3s ease;\n            margin-top: 10px;\n        }\n\n        .submit-btn:hover {\n            transform: translateY(-2px);\n            box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);\n        }\n\n        .submit-btn:active {\n            transform: translateY(0);\n        }\n\n        .example-topics {\n            margin-top: 30px;\n            padding-top: 30px;\n            border-top: 1px solid #e5e7eb;\n        }\n\n        .example-topics h3 {\n            color: #6b7280;\n            font-size: 0.9rem;\n            font-weight: 600;\n            margin-bottom: 15px;\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n        }\n\n        .topic-tags {\n            display: flex;\n            flex-wrap: wrap;\n            gap: 8px;\n            justify-content: center;\n        }\n\n        .topic-tag {\n            background: #f3f4f6;\n            color: #6b7280;\n            padding: 6px 12px;\n            border-radius: 20px;\n            font-size: 0.85rem;\n            cursor: pointer;\n            transition: all 0.2s ease;\n            border: 1px solid transparent;\n        }\n\n        .topic-tag:hover {\n            background: #e5e7eb;\n            color: #374151;\n        }\n\n        @media (max-width: 480px) {\n            .container {\n                padding: 30px 20px;\n            }\n            \n            .logo {\n                font-size: 2rem;\n            }\n        }\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <div class=\"logo\">✨ Article AI</div>\n        <p class=\"subtitle\">Generate engaging articles with AI assistance</p>\n        \n        <form id=\"articleForm\" action=\"/start-job\" method=\"post\">\n            <div class=\"form-group\">\n                <label for=\"topic\">What would you like to write about?</label>\n                <input type=\"text\" id=\"topic\" name=\"topic\" placeholder=\"Enter your topic here...\" required>\n            </div>\n            \n            <button type=\"submit\" class=\"submit-btn\">Generate Article</button>\n        </form>\n\n        <div class=\"example-topics\">\n            <h3>Popular Topics</h3>\n            <div class=\"topic-tags\">\n                <span class=\"topic-tag\" onclick=\"setTopic('AI Safety')\">AI Safety</span>\n                <span class=\"topic-tag\" onclick=\"setTopic('Climate Change')\">Climate Change</span>\n                <span class=\"topic-tag\" onclick=\"setTopic('Space Exploration')\">Space Exploration</span>\n                <span class=\"topic-tag\" onclick=\"setTopic('Renewable Energy')\">Renewable Energy</span>\n                <span class=\"topic-tag\" onclick=\"setTopic('Mental Health')\">Mental Health</span>\n                <span class=\"topic-tag\" onclick=\"setTopic('Future of Work')\">Future of Work</span>\n            </div>\n        </div>\n    </div>\n\n    <script>\n        function setTopic(topic) {\n            document.getElementById('topic').value = topic;\n        }\n\n        document.getElementById('articleForm').addEventListener('submit', async function(e) {\n            e.preventDefault();\n            \n            const submitBtn = document.querySelector('.submit-btn');\n            const originalText = submitBtn.textContent;\n            \n            // Show loading state\n            submitBtn.textContent = 'Starting...';\n            submitBtn.disabled = true;\n            \n            try {\n                const formData = new FormData(this);\n                const response = await fetch('/start-job', {\n                    method: 'POST',\n                    body: formData\n                });\n                \n                const result = await response.json();\n                \n                if (response.ok) {\n                    // Redirect to progress page\n                    window.location.href = `/progress.html?job_id=${result.job_id}&topic=${encodeURIComponent(result.topic)}`;\n                } else {\n                    throw new Error('Failed to start job');\n                }\n            } catch (error) {\n                alert('Error starting job: ' + error.message);\n                submitBtn.textContent = originalText;\n                submitBtn.disabled = false;\n            }\n        });\n    </script>\n</body>\n</html> "
  },
  {
    "path": "cookbook/pocketflow-fastapi-background/static/progress.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Generating Article...</title>\n    <style>\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n            min-height: 100vh;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            padding: 20px;\n        }\n\n        .container {\n            background: white;\n            border-radius: 20px;\n            padding: 40px;\n            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);\n            max-width: 600px;\n            width: 100%;\n            text-align: center;\n        }\n\n        .logo {\n            font-size: 2rem;\n            font-weight: 700;\n            background: linear-gradient(135deg, #667eea, #764ba2);\n            -webkit-background-clip: text;\n            -webkit-text-fill-color: transparent;\n            background-clip: text;\n            margin-bottom: 10px;\n        }\n\n        .topic-title {\n            color: #374151;\n            font-size: 1.5rem;\n            font-weight: 600;\n            margin-bottom: 30px;\n        }\n\n        .progress-container {\n            margin: 30px 0;\n        }\n\n        .progress-bar {\n            width: 100%;\n            height: 8px;\n            background: #f3f4f6;\n            border-radius: 10px;\n            overflow: hidden;\n            margin-bottom: 20px;\n        }\n\n        .progress-fill {\n            height: 100%;\n            background: linear-gradient(90deg, #667eea, #764ba2);\n            border-radius: 10px;\n            width: 0%;\n            transition: width 0.5s ease;\n            position: relative;\n        }\n\n        .progress-fill::after {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            bottom: 0;\n            background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);\n            animation: shimmer 2s infinite;\n        }\n\n        @keyframes shimmer {\n            0% { transform: translateX(-100%); }\n            100% { transform: translateX(100%); }\n        }\n\n        .progress-text {\n            color: #6b7280;\n            font-size: 1rem;\n            font-weight: 500;\n            margin-bottom: 10px;\n        }\n\n        .progress-percentage {\n            color: #374151;\n            font-size: 2rem;\n            font-weight: 700;\n            margin-bottom: 20px;\n        }\n\n        .status-card {\n            background: #f9fafb;\n            border: 1px solid #e5e7eb;\n            border-radius: 12px;\n            padding: 20px;\n            margin: 20px 0;\n            text-align: left;\n        }\n\n        .status-title {\n            color: #374151;\n            font-weight: 600;\n            margin-bottom: 10px;\n            display: flex;\n            align-items: center;\n            gap: 8px;\n        }\n\n        .status-content {\n            color: #6b7280;\n            line-height: 1.5;\n        }\n\n        .spinner {\n            display: inline-block;\n            width: 16px;\n            height: 16px;\n            border: 2px solid #e5e7eb;\n            border-top: 2px solid #667eea;\n            border-radius: 50%;\n            animation: spin 1s linear infinite;\n        }\n\n        @keyframes spin {\n            0% { transform: rotate(0deg); }\n            100% { transform: rotate(360deg); }\n        }\n\n        .step-indicator {\n            display: flex;\n            justify-content: space-between;\n            margin: 30px 0;\n            position: relative;\n        }\n\n        .step-indicator::before {\n            content: '';\n            position: absolute;\n            top: 15px;\n            left: 15px;\n            right: 15px;\n            height: 2px;\n            background: #e5e7eb;\n            z-index: 1;\n        }\n\n        .step {\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            position: relative;\n            z-index: 2;\n        }\n\n        .step-circle {\n            width: 30px;\n            height: 30px;\n            border-radius: 50%;\n            background: #e5e7eb;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-weight: 600;\n            font-size: 0.8rem;\n            margin-bottom: 8px;\n            transition: all 0.3s ease;\n        }\n\n        .step-circle.active {\n            background: linear-gradient(135deg, #667eea, #764ba2);\n            color: white;\n        }\n\n        .step-circle.completed {\n            background: #10b981;\n            color: white;\n        }\n\n        .step-label {\n            font-size: 0.8rem;\n            color: #6b7280;\n            text-align: center;\n            max-width: 80px;\n        }\n\n        .result-section {\n            display: none;\n            text-align: left;\n            margin-top: 30px;\n        }\n\n        .result-section.show {\n            display: block;\n        }\n\n        .article-content {\n            background: #f9fafb;\n            border: 1px solid #e5e7eb;\n            border-radius: 12px;\n            padding: 25px;\n            line-height: 1.6;\n            color: #374151;\n            white-space: pre-wrap;\n            max-height: 400px;\n            overflow-y: auto;\n        }\n\n        .action-buttons {\n            display: flex;\n            gap: 15px;\n            margin-top: 20px;\n            justify-content: center;\n        }\n\n        .btn {\n            padding: 12px 24px;\n            border-radius: 10px;\n            font-weight: 600;\n            text-decoration: none;\n            transition: all 0.3s ease;\n            cursor: pointer;\n            border: none;\n            font-size: 0.95rem;\n        }\n\n        .btn-primary {\n            background: linear-gradient(135deg, #667eea, #764ba2);\n            color: white;\n        }\n\n        .btn-primary:hover {\n            transform: translateY(-2px);\n            box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);\n        }\n\n        .btn-secondary {\n            background: #f3f4f6;\n            color: #374151;\n            border: 1px solid #e5e7eb;\n        }\n\n        .btn-secondary:hover {\n            background: #e5e7eb;\n        }\n\n        .error-message {\n            background: #fef2f2;\n            border: 1px solid #fecaca;\n            color: #dc2626;\n            padding: 15px;\n            border-radius: 10px;\n            margin: 20px 0;\n        }\n\n        @media (max-width: 480px) {\n            .container {\n                padding: 30px 20px;\n            }\n            \n            .step-indicator {\n                margin: 20px 0;\n            }\n            \n            .step-label {\n                font-size: 0.7rem;\n                max-width: 60px;\n            }\n            \n            .action-buttons {\n                flex-direction: column;\n            }\n        }\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <div class=\"logo\">✨ Article AI</div>\n        <div class=\"topic-title\" id=\"topicTitle\">Generating your article...</div>\n        \n        <div class=\"progress-container\">\n            <div class=\"progress-percentage\" id=\"progressPercentage\">0%</div>\n            <div class=\"progress-bar\">\n                <div class=\"progress-fill\" id=\"progressFill\"></div>\n            </div>\n            <div class=\"progress-text\" id=\"progressText\">Initializing...</div>\n        </div>\n\n        <div class=\"step-indicator\">\n            <div class=\"step\">\n                <div class=\"step-circle\" id=\"step1\">1</div>\n                <div class=\"step-label\">Outline</div>\n            </div>\n            <div class=\"step\">\n                <div class=\"step-circle\" id=\"step2\">2</div>\n                <div class=\"step-label\">Content</div>\n            </div>\n            <div class=\"step\">\n                <div class=\"step-circle\" id=\"step3\">3</div>\n                <div class=\"step-label\">Style</div>\n            </div>\n        </div>\n\n        <div class=\"status-card\" id=\"statusCard\">\n            <div class=\"status-title\" id=\"statusTitle\">\n                <span class=\"spinner\"></span>\n                Getting started...\n            </div>\n            <div class=\"status-content\" id=\"statusContent\">\n                Preparing to generate your article. This may take a few moments.\n            </div>\n        </div>\n\n        <div class=\"result-section\" id=\"resultSection\">\n            <h3 style=\"margin-bottom: 15px; color: #374151;\">Your Article is Ready! 🎉</h3>\n            <div class=\"article-content\" id=\"articleContent\"></div>\n            <div class=\"action-buttons\">\n                <button class=\"btn btn-primary\" onclick=\"copyToClipboard()\">Copy Article</button>\n                <a href=\"/\" class=\"btn btn-secondary\">Generate Another</a>\n            </div>\n        </div>\n\n        <div class=\"error-message\" id=\"errorMessage\" style=\"display: none;\"></div>\n    </div>\n\n    <script>\n        const urlParams = new URLSearchParams(window.location.search);\n        const jobId = urlParams.get('job_id');\n        const topic = urlParams.get('topic');\n\n        if (topic) {\n            document.getElementById('topicTitle').textContent = `\"${topic}\"`;\n        }\n\n        if (!jobId) {\n            showError('No job ID provided');\n        } else {\n            connectToProgress();\n        }\n\n        function connectToProgress() {\n            const eventSource = new EventSource(`/progress/${jobId}`);\n            \n            eventSource.onmessage = function(event) {\n                try {\n                    const data = JSON.parse(event.data);\n                    handleProgressUpdate(data);\n                } catch (error) {\n                    console.error('Error parsing SSE data:', error);\n                }\n            };\n            \n            eventSource.onerror = function(error) {\n                console.error('SSE connection error:', error);\n                showError('Connection lost. Please refresh the page.');\n                eventSource.close();\n            };\n        }\n\n        function handleProgressUpdate(data) {\n            if (data.error) {\n                showError(data.error);\n                return;\n            }\n\n            if (data.heartbeat) {\n                return; // Ignore heartbeat messages\n            }\n\n            const progress = data.progress || 0;\n            updateProgress(progress);\n\n            switch (data.step) {\n                case 'connected':\n                    updateStatus('🔗 Connected', 'Successfully connected to the article generation process.');\n                    break;\n                    \n                case 'outline':\n                    updateStepIndicator(1);\n                    if (data.data && data.data.sections) {\n                        updateStatus('📝 Creating Outline', `Generated outline with ${data.data.sections.length} sections`);\n                    } else {\n                        updateStatus('📝 Creating Outline', 'Generating article structure and main points...');\n                    }\n                    break;\n                    \n                case 'content':\n                    updateStepIndicator(2);\n                    if (data.data && data.data.section) {\n                        updateStatus('✍️ Writing Content', \n                            `Writing section: \"${data.data.section}\" (${data.data.completed_sections}/${data.data.total_sections})`);\n                    } else {\n                        updateStatus('✍️ Writing Content', 'Creating detailed content for each section...');\n                    }\n                    break;\n                    \n                case 'style':\n                    updateStepIndicator(3);\n                    updateStatus('🎨 Applying Style', 'Polishing the article with engaging, conversational tone...');\n                    break;\n                    \n                case 'complete':\n                    updateStepIndicator(3, true);\n                    updateProgress(100);\n                    updateStatus('✅ Complete!', 'Your article has been generated successfully.');\n                    if (data.data && data.data.final_article) {\n                        showResult(data.data.final_article);\n                    }\n                    break;\n            }\n        }\n\n        function updateProgress(percentage) {\n            document.getElementById('progressPercentage').textContent = `${percentage}%`;\n            document.getElementById('progressFill').style.width = `${percentage}%`;\n        }\n\n        function updateStatus(title, content) {\n            document.getElementById('statusTitle').innerHTML = `<span class=\"spinner\"></span> ${title}`;\n            document.getElementById('statusContent').textContent = content;\n        }\n\n        function updateStepIndicator(step, completed = false) {\n            // Reset all steps\n            for (let i = 1; i <= 3; i++) {\n                const stepElement = document.getElementById(`step${i}`);\n                stepElement.className = 'step-circle';\n                if (i < step) {\n                    stepElement.classList.add('completed');\n                    stepElement.innerHTML = '✓';\n                } else if (i === step) {\n                    stepElement.classList.add(completed ? 'completed' : 'active');\n                    stepElement.innerHTML = completed ? '✓' : i;\n                } else {\n                    stepElement.innerHTML = i;\n                }\n            }\n        }\n\n        function showResult(article) {\n            document.getElementById('statusCard').style.display = 'none';\n            document.getElementById('articleContent').textContent = article;\n            document.getElementById('resultSection').classList.add('show');\n        }\n\n        function showError(message) {\n            document.getElementById('errorMessage').textContent = message;\n            document.getElementById('errorMessage').style.display = 'block';\n            document.getElementById('statusCard').style.display = 'none';\n        }\n\n        function copyToClipboard() {\n            const article = document.getElementById('articleContent').textContent;\n            navigator.clipboard.writeText(article).then(() => {\n                const btn = event.target;\n                const originalText = btn.textContent;\n                btn.textContent = 'Copied!';\n                btn.style.background = '#10b981';\n                setTimeout(() => {\n                    btn.textContent = originalText;\n                    btn.style.background = '';\n                }, 2000);\n            }).catch(err => {\n                console.error('Failed to copy: ', err);\n                alert('Failed to copy to clipboard');\n            });\n        }\n    </script>\n</body>\n</html> "
  },
  {
    "path": "cookbook/pocketflow-fastapi-background/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-fastapi-background/utils/call_llm.py",
    "content": "import os\nfrom openai import OpenAI\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n\nif __name__ == \"__main__\":\n    print(call_llm(\"Tell me a short joke\")) "
  },
  {
    "path": "cookbook/pocketflow-fastapi-hitl/README.md",
    "content": "# PocketFlow Web Human-in-the-Loop (HITL) Feedback Service\n\nThis project demonstrates a minimal web application for human-in-the-loop workflows using PocketFlow, FastAPI, and Server-Sent Events (SSE). Users can submit text, have it processed (simulated), review the output, and approve or reject it, potentially triggering reprocessing until approved.\n\n<p align=\"center\">\n  <img \n    src=\"./assets/banner.png\" width=\"800\"\n  />\n</p>\n\n## Features\n\n-   **Web UI:** Simple interface for submitting tasks and providing feedback.\n-   **PocketFlow Workflow:** Manages the process -> review -> result/reprocess logic.\n-   **FastAPI Backend:** Serves the UI and handles API requests asynchronously.\n-   **Server-Sent Events (SSE):** Provides real-time status updates to the client without polling.\n\n## How to Run\n\n1.  Install Dependencies:\n    ```bash\n    pip install -r requirements.txt\n    ```\n\n2.  Run the FastAPI Server:\n    Use Uvicorn (or another ASGI server):\n    ```bash\n    uvicorn server:app --reload --port 8000\n    ```\n    *(The `--reload` flag is useful for development.)*\n\n3.  Access the Web UI:\n    Open your web browser and navigate to `http://127.0.0.1:8000`.\n\n4.  Use the Application:\n    *   Enter text into the textarea and click \"Submit\".\n    *   Observe the status updates pushed via SSE.\n    *   When prompted (\"waiting_for_review\"), use the \"Approve\" or \"Reject\" buttons.\n    *   If rejected, the process loops back. If approved, the final result is displayed.\n\n## How It Works\n\nThe application uses PocketFlow to define and execute the feedback loop workflow. FastAPI handles web requests and manages the real-time SSE communication.\n\n**PocketFlow Workflow:**\n\nThe core logic is orchestrated by an `AsyncFlow` defined in `flow.py`:\n\n```mermaid\nflowchart TD\n    subgraph FeedbackFlow[MinimalFeedbackFlow]\n        Process[ProcessNode] -- default --> Review[ReviewNode]\n        Review -- approved --> Result[ResultNode]\n        Review -- rejected --> Process\n    end\n```\n\n1.  **`ProcessNode`**: Receives input text, calls the minimal `process_task` utility, and stores the output.\n2.  **`ReviewNode` (Async)**:\n    *   Pushes a \"waiting_for_review\" status with the processed output to the SSE queue.\n    *   Waits asynchronously for an external signal (triggered by the `/feedback` API endpoint).\n    *   Based on the received feedback (\"approved\" or \"rejected\"), determines the next step in the flow. Stores the result if approved.\n3.  **`ResultNode`**: Logs the final approved result.\n\n**FastAPI & SSE Integration:**\n\n*   The `/submit` endpoint creates a unique task, initializes the PocketFlow `shared` state (including an `asyncio.Event` for review and an `asyncio.Queue` for SSE), and schedules the flow execution using `BackgroundTasks`.\n*   Nodes within the flow (specifically `ReviewNode`'s prep logic) put status updates onto the task-specific `sse_queue`.\n*   The `/stream/{task_id}` endpoint uses `StreamingResponse` to read from the task's `sse_queue` and push formatted status updates to the connected client via Server-Sent Events.\n*   The `/feedback/{task_id}` endpoint receives the human's decision, updates the `shared` state, and sets the `asyncio.Event` to unblock the waiting `ReviewNode`.\n\nThis setup allows for a decoupled workflow logic (PocketFlow) and web interaction layer (FastAPI), with efficient real-time updates pushed to the user.\n\n## Files\n\n-   [`server.py`](./server.py): The main FastAPI application handling HTTP requests, SSE, state management, and background task scheduling.\n-   [`nodes.py`](./nodes.py): Defines the PocketFlow `Node` classes (`ProcessNode`, `ReviewNode`, `ResultNode`) for the workflow steps.\n-   [`flow.py`](./flow.py): Defines the PocketFlow `AsyncFlow` that connects the nodes into the feedback loop.\n-   [`utils/process_task.py`](./utils/process_task.py): Contains the minimal simulation function for task processing.\n-   [`templates/index.html`](./templates/index.html): The HTML structure for the frontend user interface.\n-   [`static/style.css`](./static/style.css): Basic CSS for styling the frontend.\n-   [`requirements.txt`](./requirements.txt): Project dependencies (FastAPI, Uvicorn, Jinja2, PocketFlow).\n"
  },
  {
    "path": "cookbook/pocketflow-fastapi-hitl/docs/design.md",
    "content": "#  Human-in-the-Loop Web Service\n\n## 1. Requirements\n\n*   **Goal:** Create a web service for task submission, processing, human review (Approve/Reject loop via UI), and finalization.\n*   **Interface:** Simple web UI (HTML/JS) for input, status display, and feedback buttons.\n*   **Backend:** FastAPI using PocketFlow for workflow management.\n*   **Real-time Updates:** Use Server-Sent Events (SSE) to push status changes (pending, running, waiting_for_review, completed, failed) and intermediate results to the client without page reloads.\n*   **State:** Use in-memory storage for task state (Warning: Not suitable for production).\n\n## 2. Flow Design\n\n*   **Core Pattern:** Workflow with a conditional loop based on human feedback. SSE for asynchronous status communication.\n*   **Nodes:**\n    1.  `ProcessNode` (Regular): Takes input, executes the (simulated) task processing.\n    2.  `ReviewNode` (Async): Waits for human feedback signaled via an `asyncio.Event`. Pushes \"waiting\\_for\\_review\" status to the SSE queue.\n    3.  `ResultNode` (Regular): Marks the task as complete and logs the final result.\n*   **Shared Store (`shared` dict per task):**\n    *   `task_input`: Initial data from user.\n    *   `processed_output`: Result from `ProcessNode`.\n    *   `feedback`: 'approved' or 'rejected' set by the `/feedback` endpoint.\n    *   `review_event`: `asyncio.Event` used by `ReviewNode` to wait and `/feedback` to signal.\n    *   `final_result`: The approved output.\n    *   `current_attempt`: Tracks reprocessing count.\n    *   `task_id`: Unique identifier for the task.\n*   **SSE Communication:** An `asyncio.Queue` (stored alongside the `shared` store in the server's global `tasks` dict, *not directly in PocketFlow's shared store*) is used per task. Nodes (or wrapper code) put status updates onto this queue. The `/stream` endpoint reads from the queue and sends SSE messages.\n*   **Mermaid Diagram:**\n\n```mermaid\nflowchart TD\n    Process[Process Task] -- \"default\" --> Review{Wait for Feedback}\n    Review -- \"approved\" --> Result[Final Result]\n    Review -- \"rejected\" --> Process\n```\n\n## 3. Utilities\n\nFor this specific example, the core \"utility\" is the processing logic itself. Let's simulate it with a simple function. The FastAPI server acts as the external interface.\n\n* `process_task(input_data)`: A placeholder function. In a real scenario, this might call an LLM (`utils/call_llm.py`).\n\n## 4. Node Design (Detailed)\n\n*   **`ProcessNode` (Node):**\n    *   `prep`: Reads `task_input`, `current_attempt` from `shared`.\n    *   `exec`: Calls `utils.process_task.process_task`.\n    *   `post`: Writes `processed_output` to `shared`, increments `current_attempt`. Returns \"default\".\n*   **`ReviewNode` (AsyncNode):**\n    *   `prep_async`: (As modified/wrapped by server.py) Reads `review_event`, `processed_output` from `shared`. **Puts \"waiting\\_for\\_review\" status onto the task's SSE queue.**\n    *   `exec_async`: `await shared[\"review_event\"].wait()`.\n    *   `post_async`: Reads `feedback` from `shared`. Clears the event. Returns \"approved\" or \"rejected\". If approved, stores `processed_output` into `final_result`.\n*   **`ResultNode` (Node):**\n    *   `prep`: Reads `final_result` from `shared`.\n    *   `exec`: Prints/logs the final result.\n    *   `post`: Returns `None` (ends flow)."
  },
  {
    "path": "cookbook/pocketflow-fastapi-hitl/flow.py",
    "content": "from pocketflow import AsyncFlow\nfrom nodes import ProcessNode, ReviewNode, ResultNode\n\ndef create_feedback_flow():\n    \"\"\"Creates the minimal feedback workflow.\"\"\"\n    process_node = ProcessNode()\n    review_node = ReviewNode()\n    result_node = ResultNode()\n\n    # Define transitions\n    process_node >> review_node\n    review_node - \"approved\" >> result_node\n    review_node - \"rejected\" >> process_node # Loop back\n\n    # Create the AsyncFlow\n    flow = AsyncFlow(start=process_node)\n    print(\"Minimal feedback flow created.\")\n    return flow"
  },
  {
    "path": "cookbook/pocketflow-fastapi-hitl/nodes.py",
    "content": "from pocketflow import Node, AsyncNode\nfrom utils.process_task import process_task\n\nclass ProcessNode(Node):\n    def prep(self, shared):\n        task_input = shared.get(\"task_input\", \"No input\")\n        print(\"ProcessNode Prep\")\n        return task_input\n\n    def exec(self, prep_res):\n        return process_task(prep_res)\n\n    def post(self, shared, prep_res, exec_res):\n        shared[\"processed_output\"] = exec_res\n        print(\"ProcessNode Post: Output stored.\")\n        return \"default\" # Go to ReviewNode\n\nclass ReviewNode(AsyncNode):\n    async def prep_async(self, shared):\n        review_event = shared.get(\"review_event\")\n        queue = shared.get(\"sse_queue\") # Expect queue in shared\n        processed_output = shared.get(\"processed_output\", \"N/A\")\n\n        if not review_event or not queue:\n            print(\"ERROR: ReviewNode Prep - Missing review_event or sse_queue in shared store!\")\n            return None # Signal failure\n\n        # Push status update to SSE queue\n        status_update = {\n            \"status\": \"waiting_for_review\",\n            \"output_to_review\": processed_output\n        }\n        await queue.put(status_update)\n        print(\"ReviewNode Prep: Put 'waiting_for_review' on SSE queue.\")\n\n        return review_event # Return event for exec_async\n\n    async def exec_async(self, prep_res):\n        review_event = prep_res\n        if not review_event:\n            print(\"ReviewNode Exec: Skipping wait (no event from prep).\")\n            return\n        print(\"ReviewNode Exec: Waiting on review_event...\")\n        await review_event.wait()\n        print(\"ReviewNode Exec: review_event set.\")\n\n    async def post_async(self, shared, prep_res, exec_res):\n        feedback = shared.get(\"feedback\")\n        print(f\"ReviewNode Post: Processing feedback '{feedback}'\")\n\n        # Clear the event for potential loops\n        review_event = shared.get(\"review_event\")\n        if review_event:\n            review_event.clear()\n        shared[\"feedback\"] = None # Reset feedback\n\n        if feedback == \"approved\":\n            shared[\"final_result\"] = shared.get(\"processed_output\")\n            print(\"ReviewNode Post: Action=approved\")\n            return \"approved\"\n        else:\n            print(\"ReviewNode Post: Action=rejected\")\n            return \"rejected\"\n\nclass ResultNode(Node):\n     def prep(self, shared):\n         print(\"ResultNode Prep\")\n         return shared.get(\"final_result\", \"No final result.\")\n\n     def exec(self, prep_res):\n         print(f\"--- FINAL RESULT ---\")\n         print(prep_res)\n         print(f\"--------------------\")\n         return prep_res\n\n     def post(self, shared, prep_res, exec_res):\n         print(\"ResultNode Post: Flow finished.\")\n         return None # End flow"
  },
  {
    "path": "cookbook/pocketflow-fastapi-hitl/requirements.txt",
    "content": "pocketflow>=0.0.1\nfastapi\nuvicorn[standard] # ASGI server for FastAPI\njinja2 # For HTML templating"
  },
  {
    "path": "cookbook/pocketflow-fastapi-hitl/server.py",
    "content": "import asyncio\nimport uuid\nimport json\nimport os\nfrom fastapi import FastAPI, Request, HTTPException, status, BackgroundTasks # Import BackgroundTasks\nfrom fastapi.responses import HTMLResponse, StreamingResponse\nfrom fastapi.staticfiles import StaticFiles\nfrom fastapi.templating import Jinja2Templates\nfrom pydantic import BaseModel, Field # Import Pydantic for request/response models\nfrom typing import Dict, Any, Literal # For type hinting\n\nfrom flow import create_feedback_flow # PocketFlow imports\n\n# --- Configuration ---\napp = FastAPI(title=\"Minimal Feedback Loop API\")\n\nstatic_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'static'))\nif os.path.isdir(static_dir):\n    app.mount(\"/static\", StaticFiles(directory=static_dir), name=\"static\")\nelse:\n    print(f\"Warning: Static directory '{static_dir}' not found.\")\n\ntemplate_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'templates'))\nif os.path.isdir(template_dir):\n    templates = Jinja2Templates(directory=template_dir)\nelse:\n    print(f\"Warning: Template directory '{template_dir}' not found.\")\n    templates = None\n\n# --- State Management (In-Memory - NOT FOR PRODUCTION) ---\n# Global dictionary to store task state. In production, use Redis, DB, etc.\ntasks: Dict[str, Dict[str, Any]] = {}\n# Structure: task_id -> {\"shared\": dict, \"status\": str, \"task_obj\": asyncio.Task | None}\n\n\n# --- Background Flow Runner ---\n# This function remains mostly the same, as it defines the work to be done.\n# It will be scheduled by FastAPI's BackgroundTasks now.\nasync def run_flow_background(task_id: str, flow, shared: Dict[str, Any]):\n    \"\"\"Runs the flow in background, uses queue in shared for SSE.\"\"\"\n    # Check if task exists (might have been cancelled/deleted)\n    if task_id not in tasks:\n        print(f\"Background task {task_id}: Task not found, aborting.\")\n        return\n    queue = shared.get(\"sse_queue\")\n    if not queue:\n        print(f\"ERROR: Task {task_id} missing sse_queue in shared store!\")\n        tasks[task_id][\"status\"] = \"failed\"\n        # Cannot report failure via SSE if queue is missing\n        return\n\n    tasks[task_id][\"status\"] = \"running\"\n    await queue.put({\"status\": \"running\"})\n    print(f\"Task {task_id}: Background flow starting.\")\n\n    final_status = \"unknown\"\n    error_message = None\n    try:\n        # Execute the potentially long-running PocketFlow\n        await flow.run_async(shared)\n\n        # Determine final status based on shared state after flow completion\n        if shared.get(\"final_result\") is not None:\n            final_status = \"completed\"\n        else:\n            # If flow ends without setting final_result\n            final_status = \"finished_incomplete\"\n        print(f\"Task {task_id}: Flow finished with status: {final_status}\")\n\n    except Exception as e:\n        final_status = \"failed\"\n        error_message = str(e)\n        print(f\"Task {task_id}: Flow execution failed: {e}\")\n        # Consider logging traceback here in production\n    finally:\n        # Ensure task still exists before updating state\n        if task_id in tasks:\n            tasks[task_id][\"status\"] = final_status\n            final_update = {\"status\": final_status}\n            if final_status == \"completed\":\n                final_update[\"final_result\"] = shared.get(\"final_result\")\n            elif error_message:\n                final_update[\"error\"] = error_message\n            # Put final status update onto the queue\n            await queue.put(final_update)\n\n        # Signal the end of the SSE stream by putting None\n        # Must happen regardless of whether task was deleted mid-run\n        if queue:\n           await queue.put(None)\n        print(f\"Task {task_id}: Background task ended. Final update sentinel put on queue.\")\n        # Remove the reference to the completed/failed asyncio Task object\n        if task_id in tasks:\n            tasks[task_id][\"task_obj\"] = None\n\n# --- Pydantic Models for Request/Response Validation ---\nclass SubmitRequest(BaseModel):\n    data: str = Field(..., min_length=1, description=\"Input data for the task\")\n\nclass SubmitResponse(BaseModel):\n    message: str = \"Task submitted\"\n    task_id: str\n\nclass FeedbackRequest(BaseModel):\n    feedback: Literal[\"approved\", \"rejected\"] # Use Literal for specific choices\n\nclass FeedbackResponse(BaseModel):\n    message: str\n\n# --- FastAPI Routes ---\n@app.get(\"/\", response_class=HTMLResponse, include_in_schema=False)\nasync def get_index(request: Request):\n    \"\"\"Serves the main HTML frontend.\"\"\"\n    if templates is None:\n        raise HTTPException(status_code=500, detail=\"Templates directory not configured.\")\n    return templates.TemplateResponse(\"index.html\", {\"request\": request})\n\n@app.post(\"/submit\", response_model=SubmitResponse, status_code=status.HTTP_202_ACCEPTED)\nasync def submit_task(\n    submit_request: SubmitRequest, # Use Pydantic model for validation\n    background_tasks: BackgroundTasks # Inject BackgroundTasks instance\n):\n    \"\"\"\n    Submits a new task. The actual processing runs in the background.\n    Returns immediately with the task ID.\n    \"\"\"\n    task_id = str(uuid.uuid4())\n    feedback_event = asyncio.Event()\n    status_queue = asyncio.Queue()\n\n    shared = {\n        \"task_input\": submit_request.data,\n        \"processed_output\": None,\n        \"feedback\": None,\n        \"review_event\": feedback_event,\n        \"sse_queue\": status_queue,\n        \"final_result\": None,\n        \"task_id\": task_id\n    }\n\n    flow = create_feedback_flow()\n\n    # Store task state BEFORE scheduling background task\n    tasks[task_id] = {\n        \"shared\": shared,\n        \"status\": \"pending\",\n        \"task_obj\": None # Placeholder for the asyncio Task created by BackgroundTasks\n    }\n\n    await status_queue.put({\"status\": \"pending\", \"task_id\": task_id})\n\n    # Schedule the flow execution using FastAPI's BackgroundTasks\n    # This runs AFTER the response has been sent\n    background_tasks.add_task(run_flow_background, task_id, flow, shared)\n    # Note: We don't get a direct reference to the asyncio Task object this way,\n    # which is fine for this minimal example. If cancellation were needed,\n    # managing asyncio.create_task manually would be necessary.\n\n    print(f\"Task {task_id}: Submitted, scheduled for background execution.\")\n    return SubmitResponse(task_id=task_id)\n\n\n@app.post(\"/feedback/{task_id}\", response_model=FeedbackResponse)\nasync def provide_feedback(task_id: str, feedback_request: FeedbackRequest):\n    \"\"\"Provides feedback (approved/rejected) to potentially unblock a waiting task.\"\"\"\n    if task_id not in tasks:\n        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=\"Task not found\")\n\n    task_info = tasks[task_id]\n    shared = task_info[\"shared\"]\n    queue = shared.get(\"sse_queue\")\n    review_event = shared.get(\"review_event\")\n\n    async def report_error(message, status_code=status.HTTP_400_BAD_REQUEST):\n        # Helper to log, put status on queue, and raise HTTP exception\n        print(f\"Task {task_id}: Feedback error - {message}\")\n        if queue: await queue.put({\"status\": \"feedback_error\", \"error\": message})\n        raise HTTPException(status_code=status_code, detail=message)\n\n    if not review_event:\n        # This indicates an internal setup error if the task exists but has no event\n        await report_error(\"Task not configured for feedback\", status.HTTP_500_INTERNAL_SERVER_ERROR)\n    if review_event.is_set():\n        # Prevent processing feedback multiple times or if the task isn't waiting\n        await report_error(\"Task not awaiting feedback or feedback already sent\", status.HTTP_409_CONFLICT)\n\n    feedback = feedback_request.feedback # Already validated by Pydantic\n    print(f\"Task {task_id}: Received feedback via POST: {feedback}\")\n\n    # Update status *before* setting the event, so client sees 'processing' first\n    if queue: await queue.put({\"status\": \"processing_feedback\", \"feedback_value\": feedback})\n    tasks[task_id][\"status\"] = \"processing_feedback\" # Update central status tracker\n\n    # Store feedback and signal the waiting ReviewNode\n    shared[\"feedback\"] = feedback\n    review_event.set()\n\n    return FeedbackResponse(message=f\"Feedback '{feedback}' received\")\n\n\n# --- SSE Endpoint ---\n@app.get(\"/stream/{task_id}\")\nasync def stream_status(task_id: str):\n    \"\"\"Streams status updates for a given task using Server-Sent Events.\"\"\"\n    if task_id not in tasks or \"sse_queue\" not in tasks[task_id][\"shared\"]:\n         raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=\"Task or queue not found\")\n\n    queue = tasks[task_id][\"shared\"][\"sse_queue\"]\n\n    async def event_generator():\n        \"\"\"Yields SSE messages from the task's queue.\"\"\"\n        print(f\"SSE Stream: Client connected for {task_id}\")\n        try:\n            while True:\n                # Wait for the next status update from the queue\n                update = await queue.get()\n                if update is None: # Sentinel value indicates end of stream\n                    print(f\"SSE Stream: Sentinel received for {task_id}, closing stream.\")\n                    yield f\"data: {json.dumps({'status': 'stream_closed'})}\\n\\n\"\n                    break\n\n                sse_data = json.dumps(update)\n                print(f\"SSE Stream: Sending for {task_id}: {sse_data}\")\n                yield f\"data: {sse_data}\\n\\n\" # SSE format: \"data: <json>\\n\\n\"\n                queue.task_done() # Acknowledge processing the queue item\n\n        except asyncio.CancelledError:\n            # This happens if the client disconnects\n            print(f\"SSE Stream: Client disconnected for {task_id}.\")\n        except Exception as e:\n            # Log unexpected errors during streaming\n            print(f\"SSE Stream: Error in generator for {task_id}: {e}\")\n            # Optionally send an error message to the client if possible\n            try:\n                yield f\"data: {json.dumps({'status': 'stream_error', 'error': str(e)})}\\n\\n\"\n            except Exception: # Catch errors if yield fails (e.g., connection already closed)\n                pass\n        finally:\n            print(f\"SSE Stream: Generator finished for {task_id}.\")\n            # Consider cleanup here (e.g., removing task if no longer needed)\n            # if task_id in tasks: del tasks[task_id]\n\n    # Use FastAPI/Starlette's StreamingResponse for SSE\n    headers = {'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'}\n    return StreamingResponse(event_generator(), media_type=\"text/event-stream\", headers=headers)\n\n# --- Main Execution Guard (for running with uvicorn) ---\nif __name__ == \"__main__\":\n    print(\"Starting FastAPI server using Uvicorn is recommended:\")\n    print(\"uvicorn server:app --reload --host 0.0.0.0 --port 8000\")\n    # Example using uvicorn programmatically (less common than CLI)\n    # import uvicorn\n    # uvicorn.run(app, host=\"0.0.0.0\", port=8000)"
  },
  {
    "path": "cookbook/pocketflow-fastapi-hitl/static/style.css",
    "content": "body {\n    font-family: sans-serif;\n    margin: 0; /* Remove default body margin */\n    padding: 20px; /* Add some padding around the content */\n    background-color: #f8f9fa; /* Lighter grey background */\n    display: flex; /* Enable Flexbox */\n    flex-direction: column; /* Stack children vertically */\n    align-items: center; /* Center children horizontally */\n    min-height: 100vh; /* Ensure body takes at least full viewport height */\n    box-sizing: border-box; /* Include padding in height calculation */\n}\n\nh1 {\n    text-align: center; /* Center the main title */\n    color: #343a40;\n    margin-bottom: 25px;\n}\n\n/* Style the main containers */\n.container, .status-container {\n    background: #ffffff;\n    padding: 20px 25px; /* More padding */\n    border: 1px solid #dee2e6; /* Softer border */\n    margin-bottom: 20px;\n    border-radius: 6px; /* Slightly rounder corners */\n    width: 90%; /* Responsive width */\n    max-width: 650px; /* Max width for readability */\n    box-shadow: 0 2px 5px rgba(0,0,0,0.05); /* Subtle shadow */\n    box-sizing: border-box; /* Include padding/border in width */\n}\n\ntextarea {\n    width: 100%; /* Take full width of parent container */\n    padding: 10px;\n    margin-bottom: 10px;\n    border: 1px solid #ced4da;\n    border-radius: 4px;\n    font-size: 1em;\n    min-height: 60px;\n    box-sizing: border-box;\n}\n\nbutton {\n    padding: 9px 15px; /* Slightly adjusted padding */\n    margin-right: 8px;\n    cursor: pointer;\n    border: none; /* Remove default border */\n    border-radius: 4px;\n    font-weight: 500;\n    transition: background-color 0.2s ease;\n}\n\nbutton:disabled {\n    cursor: not-allowed;\n    opacity: 0.6;\n}\n\n/* Specific button styling */\n#submit-button {\n    background-color: #0d6efd; /* Bootstrap primary blue */\n    color: white;\n}\n#submit-button:hover:not(:disabled) {\n    background-color: #0b5ed7;\n}\n\n.approve {\n    background-color: #198754; /* Bootstrap success green */\n    color: white;\n}\n.approve:hover:not(:disabled) {\n    background-color: #157347;\n}\n\n.reject {\n    background-color: #dc3545; /* Bootstrap danger red */\n    color: white;\n}\n.reject:hover:not(:disabled) {\n    background-color: #bb2d3b;\n}\n\n\n#task-id-display {\n    font-size: 0.9em;\n    color: #6c757d; /* Bootstrap secondary text color */\n    margin-bottom: 8px;\n    word-wrap: break-word;\n}\n\n#status-display {\n    font-weight: bold;\n    margin-bottom: 15px;\n    padding: 10px;\n    background-color: #e9ecef; /* Light grey background */\n    border: 1px solid #dee2e6;\n    border-radius: 4px;\n    color: #495057;\n}\n\n.hidden {\n    display: none;\n}\n\n/* Review/Result Box Styling */\n.review-box, .result-box {\n    border: 1px solid #dee2e6;\n    padding: 15px;\n    margin-top: 15px;\n    border-radius: 4px;\n    background-color: #f8f9fa; /* Very light background */\n}\n\nh2, h3 {\n    margin-top: 0; /* Remove default top margin */\n    margin-bottom: 15px;\n    color: #495057;\n}\n\nh3 {\n     border-bottom: 1px solid #eee;\n     padding-bottom: 8px;\n}\n\npre {\n    background-color: #e9ecef;\n    padding: 12px;\n    border: 1px solid #ced4da;\n    border-radius: 4px;\n    white-space: pre-wrap;\n    word-wrap: break-word;\n    max-height: 250px; /* Adjusted height */\n    overflow-y: auto;\n    font-family: monospace;\n    font-size: 0.95em;\n    color: #212529;\n}"
  },
  {
    "path": "cookbook/pocketflow-fastapi-hitl/templates/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Pocket Flow Web Feedback</title>\n    <link rel=\"stylesheet\" href=\"{{ url_for('static', path='style.css') }}\">\n</head>\n<body>\n    <h1>Pocket Flow Web Feedback</h1>\n\n    <div class=\"container\">\n        <textarea id=\"task-input\" rows=\"3\" placeholder=\"Enter text to process...\"></textarea>\n        <button id=\"submit-button\">Submit</button>\n    </div>\n\n    <div class=\"status-container\">\n        <h2>Status</h2>\n        <div id=\"task-id-display\">Task ID: N/A</div>\n        <div id=\"status-display\">Submit a task.</div>\n\n        <div id=\"review-section\" class=\"hidden review-box\">\n            <h3>Review Output</h3>\n            <pre id=\"review-output\"></pre>\n            <button id=\"approve-button\" class=\"feedback-button approve\">Approve</button>\n            <button id=\"reject-button\" class=\"feedback-button reject\">Reject</button>\n        </div>\n\n        <div id=\"result-section\" class=\"hidden result-box\">\n            <h3>Final Result</h3>\n            <pre id=\"final-result\"></pre>\n        </div>\n    </div>\n\n    <script>\n        const taskInput = document.getElementById('task-input');\n        const submitButton = document.getElementById('submit-button');\n        const taskIdDisplay = document.getElementById('task-id-display');\n        const statusDisplay = document.getElementById('status-display');\n        const reviewSection = document.getElementById('review-section');\n        const reviewOutput = document.getElementById('review-output');\n        const approveButton = document.getElementById('approve-button');\n        const rejectButton = document.getElementById('reject-button');\n        const resultSection = document.getElementById('result-section');\n        const finalResult = document.getElementById('final-result');\n\n        let currentTaskId = null;\n        let eventSource = null;\n\n        submitButton.addEventListener('click', handleSubmit);\n        approveButton.addEventListener('click', () => handleFeedback('approved'));\n        rejectButton.addEventListener('click', () => handleFeedback('rejected'));\n\n        async function handleSubmit() {\n            const data = taskInput.value.trim();\n            if (!data) return alert('Input is empty.');\n\n            resetUI();\n            statusDisplay.textContent = 'Submitting...';\n            submitButton.disabled = true;\n\n            try {\n                const response = await fetch('/submit', {\n                    method: 'POST',\n                    headers: { 'Content-Type': 'application/json' },\n                    body: JSON.stringify({ data: data })\n                });\n                if (!response.ok) throw new Error(`Submit failed: ${response.status}`);\n                const result = await response.json();\n                currentTaskId = result.task_id;\n                taskIdDisplay.textContent = `Task ID: ${currentTaskId}`;\n                startSSEListener(currentTaskId);\n            } catch (error) {\n                console.error('Submit error:', error);\n                statusDisplay.textContent = `Submit Error: ${error.message}`;\n                resetUI();\n            } finally {\n                submitButton.disabled = false;\n            }\n        }\n\n        function startSSEListener(taskId) {\n            closeSSEListener(); // Close existing connection\n            eventSource = new EventSource(`/stream/${taskId}`);\n            eventSource.onmessage = handleSSEMessage;\n            eventSource.onerror = handleSSEError;\n            eventSource.onopen = () => console.log(`SSE connected for ${taskId}`);\n        }\n\n        function handleSSEMessage(event) {\n            console.log(\"SSE data:\", event.data);\n            try {\n                const data = JSON.parse(event.data);\n                updateUI(data);\n            } catch (e) { console.error(\"SSE parse error:\", e); }\n        }\n\n        function handleSSEError(error) {\n            console.error(\"SSE Error:\", error);\n            statusDisplay.textContent = \"Status stream error. Connection closed.\";\n            closeSSEListener();\n        }\n\n        function closeSSEListener() {\n            if (eventSource) {\n                eventSource.close();\n                eventSource = null;\n                console.log(\"SSE connection closed.\");\n            }\n        }\n\n        function updateUI(data) {\n             // Always update main status\n            statusDisplay.textContent = `Status: ${data.status || 'Unknown'}`;\n\n            // Hide sections, then show relevant one\n            reviewSection.classList.add('hidden');\n            resultSection.classList.add('hidden');\n            approveButton.disabled = false; // Re-enable by default\n            rejectButton.disabled = false;\n\n            switch(data.status) {\n                case 'waiting_for_review':\n                    reviewOutput.textContent = data.output_to_review || '';\n                    reviewSection.classList.remove('hidden');\n                    break;\n                case 'processing_feedback':\n                    approveButton.disabled = true; // Disable while processing\n                    rejectButton.disabled = true;\n                    break;\n                case 'completed':\n                    finalResult.textContent = data.final_result || '';\n                    resultSection.classList.remove('hidden');\n                    closeSSEListener();\n                    break;\n                case 'failed':\n                case 'feedback_error':\n                     statusDisplay.textContent = `Status: ${data.status} - ${data.error || 'Unknown error'}`;\n                     closeSSEListener();\n                     break;\n                case 'finished_incomplete':\n                     statusDisplay.textContent = `Status: Flow finished unexpectedly.`;\n                     closeSSEListener();\n                     break;\n                case 'stream_closed':\n                    // Server closed the stream gracefully (usually after completed/failed)\n                    if (!['completed', 'failed', 'finished_incomplete'].includes(tasks[currentTaskId]?.status)) {\n                         statusDisplay.textContent = \"Status: Connection closed by server.\";\n                    }\n                    closeSSEListener();\n                    break;\n                case 'pending':\n                case 'running':\n                     // Just update status text, wait for next message\n                     break;\n            }\n        }\n\n       async function handleFeedback(feedbackValue) {\n            if (!currentTaskId) return;\n            approveButton.disabled = true;\n            rejectButton.disabled = true;\n            statusDisplay.textContent = `Sending ${feedbackValue}...`; // Optimistic UI update\n\n            try {\n                const response = await fetch(`/feedback/${currentTaskId}`, {\n                    method: 'POST',\n                    headers: { 'Content-Type': 'application/json' },\n                    body: JSON.stringify({ feedback: feedbackValue })\n                });\n                if (!response.ok) { // Rely on SSE for status change or error reporting\n                     const errorData = await response.json().catch(()=>({error: `Feedback failed: ${response.status}`}));\n                     throw new Error(errorData.error);\n                }\n                console.log(`Feedback ${feedbackValue} POST successful.`);\n                // Successful POST - wait for SSE to update status to 'processing', then 'running' etc.\n            } catch (error) {\n                console.error('Feedback error:', error);\n                statusDisplay.textContent = `Feedback Error: ${error.message}`;\n                // Re-enable buttons if feedback POST failed\n                approveButton.disabled = false;\n                rejectButton.disabled = false;\n            }\n        }\n\n        function resetUI() {\n            closeSSEListener();\n            currentTaskId = null;\n            taskIdDisplay.textContent = 'Task ID: N/A';\n            statusDisplay.textContent = 'Submit a task.';\n            reviewSection.classList.add('hidden');\n            resultSection.classList.add('hidden');\n            taskInput.value = '';\n            submitButton.disabled = false;\n            approveButton.disabled = false;\n            rejectButton.disabled = false;\n        }\n    </script>\n</body>\n</html>"
  },
  {
    "path": "cookbook/pocketflow-fastapi-hitl/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-fastapi-hitl/utils/process_task.py",
    "content": "import time\n\ndef process_task(input_data):\n    \"\"\"Minimal simulation of processing the input data.\"\"\"\n    print(f\"Processing: '{input_data[:50]}...'\")\n    \n    # Simulate work\n    time.sleep(2)\n\n    processed_result = f\"Processed: {input_data}\"\n    print(f\"Finished processing.\")\n    return processed_result\n\n# We don't need a separate utils/call_llm.py for this minimal example,\n# but you would add it here if ProcessNode used an LLM.\n\n"
  },
  {
    "path": "cookbook/pocketflow-fastapi-websocket/README.md",
    "content": "# PocketFlow FastAPI WebSocket Chat\n\nReal-time chat interface with streaming LLM responses using PocketFlow, FastAPI, and WebSocket.\n\n<p align=\"center\">\n  <img \n    src=\"./assets/banner.png\" width=\"800\"\n  />\n</p>\n\n## Features\n\n- **Real-time Streaming**: See AI responses typed out in real-time as the LLM generates them\n- **Conversation Memory**: Maintains chat history across messages\n- **Modern UI**: Clean, responsive chat interface with gradient design\n- **WebSocket Connection**: Persistent connection for instant communication\n- **PocketFlow Integration**: Uses PocketFlow `AsyncNode` and `AsyncFlow` for streaming\n\n## How to Run\n\n1. **Set OpenAI API Key:**\n   ```bash\n   export OPENAI_API_KEY=\"your-openai-api-key\"\n   ```\n\n2. **Install Dependencies:**\n   ```bash\n   pip install -r requirements.txt\n   ```\n\n3. **Run the Application:**\n   ```bash\n   python main.py\n   ```\n\n4. **Access the Web UI:**\n   Open `http://localhost:8000` in your browser.\n\n## Usage\n\n1. **Type Message**: Enter your message in the input field\n2. **Send**: Press Enter or click Send button\n3. **Watch Streaming**: See the AI response appear in real-time\n4. **Continue Chat**: Conversation history is maintained automatically\n\n## Files\n\n- [`main.py`](./main.py): FastAPI application with WebSocket endpoint\n- [`nodes.py`](./nodes.py): PocketFlow `StreamingChatNode` definition\n- [`flow.py`](./flow.py): PocketFlow `AsyncFlow` for chat processing\n- [`utils/stream_llm.py`](./utils/stream_llm.py): OpenAI streaming utility\n- [`static/index.html`](./static/index.html): Modern chat interface\n- [`requirements.txt`](./requirements.txt): Project dependencies\n- [`docs/design.md`](./docs/design.md): System design documentation\n- [`README.md`](./README.md): This file "
  },
  {
    "path": "cookbook/pocketflow-fastapi-websocket/docs/design.md",
    "content": "# Design Doc: FastAPI WebSocket Chat Interface\n\n> Please DON'T remove notes for AI\n\n## Requirements\n\n> Notes for AI: Keep it simple and clear.\n> If the requirements are abstract, write concrete user stories\n\n**User Story**: As a user, I want to interact with an AI chatbot through a web interface where:\n1. I can send messages and receive real-time streaming responses\n2. The connection stays persistent (WebSocket)\n3. I can see the AI response being typed out in real-time as the LLM generates it\n4. The interface is minimal and easy to use\n\n**Technical Requirements**:\n- FastAPI backend with WebSocket support\n- Real-time bidirectional communication\n- True LLM streaming integration using PocketFlow AsyncNode\n- Simple HTML/JavaScript frontend\n- Minimal dependencies\n\n## Flow Design\n\n> Notes for AI:\n> 1. Consider the design patterns of agent, map-reduce, rag, and workflow. Apply them if they fit.\n> 2. Present a concise, high-level description of the workflow.\n\n### Applicable Design Pattern:\n\n**Single Async Node Pattern**: One PocketFlow AsyncNode handles the entire LLM streaming process with real-time WebSocket streaming\n\n### Flow high-level Design:\n\n**PocketFlow AsyncFlow**: Just one async node\n1. **Streaming Chat Node**: Processes message, calls LLM with real streaming, sends chunks immediately to WebSocket\n\n**Integration**: FastAPI WebSocket endpoint calls the PocketFlow AsyncFlow\n\n```mermaid\nflowchart TD\n    user((User Browser)) --> websocket(FastAPI WebSocket)\n    websocket --> flow[Streaming Chat AsyncNode]\n    flow --> websocket\n    websocket --> user\n    \n    style user fill:#e1f5fe\n    style websocket fill:#f3e5f5\n    style flow fill:#e8f5e8,stroke:#4caf50,stroke-width:3px\n```\n\n## Utility Functions\n\n> Notes for AI:\n> 1. Understand the utility function definition thoroughly by reviewing the doc.\n> 2. Include only the necessary utility functions, based on nodes in the flow.\n\n1. **Stream LLM** (`utils/stream_llm.py`)\n   - *Input*: messages (list of chat history)\n   - *Output*: generator yielding real-time response chunks from OpenAI API\n   - Used by streaming chat node to get LLM chunks as they're generated\n\n## Node Design\n\n### Shared Store\n\n> Notes for AI: Try to minimize data redundancy\n\nThe shared store structure is organized as follows:\n\n```python\nshared = {\n    \"websocket\": None,           # WebSocket connection object\n    \"user_message\": \"\",          # Current user message\n    \"conversation_history\": []   # List of message history with roles\n}\n```\n\n### Node Steps\n\n> Notes for AI: Carefully decide whether to use Batch/Async Node/Flow.\n\n1. **Streaming Chat Node**\n  - *Purpose*: Process user message, call LLM with real streaming, and send chunks immediately via WebSocket\n  - *Type*: AsyncNode (for real-time streaming)\n  - *Steps*:\n    - *prep*: Read user message, build conversation history with new message\n    - *exec_async*: Call streaming LLM utility, stream each chunk immediately to WebSocket as received\n    - *post*: Update conversation history with complete assistant response\n"
  },
  {
    "path": "cookbook/pocketflow-fastapi-websocket/flow.py",
    "content": "from pocketflow import AsyncFlow\nfrom nodes import StreamingChatNode\n\ndef create_streaming_chat_flow():\n    chat_node = StreamingChatNode()\n    return AsyncFlow(start=chat_node) "
  },
  {
    "path": "cookbook/pocketflow-fastapi-websocket/main.py",
    "content": "import json\nfrom fastapi import FastAPI, WebSocket, WebSocketDisconnect\nfrom fastapi.staticfiles import StaticFiles\nfrom fastapi.responses import FileResponse\nfrom flow import create_streaming_chat_flow\n\napp = FastAPI()\napp.mount(\"/static\", StaticFiles(directory=\"static\"), name=\"static\")\n\n@app.get(\"/\")\nasync def get_chat_interface():\n    return FileResponse(\"static/index.html\")\n\n@app.websocket(\"/ws\")\nasync def websocket_endpoint(websocket: WebSocket):\n    await websocket.accept()\n    \n    # Initialize conversation history for this connection\n    shared_store = {\n        \"websocket\": websocket,\n        \"conversation_history\": []\n    }\n    \n    try:\n        while True:\n            data = await websocket.receive_text()\n            message = json.loads(data)\n            \n            # Update only the current message, keep conversation history\n            shared_store[\"user_message\"] = message.get(\"content\", \"\")\n            \n            flow = create_streaming_chat_flow()\n            await flow.run_async(shared_store)\n            \n    except WebSocketDisconnect:\n        pass\n\nif __name__ == \"__main__\":\n    import uvicorn\n    uvicorn.run(app, host=\"0.0.0.0\", port=8000) "
  },
  {
    "path": "cookbook/pocketflow-fastapi-websocket/nodes.py",
    "content": "import asyncio\nimport json\nfrom pocketflow import AsyncNode\nfrom utils.stream_llm import stream_llm\n\nclass StreamingChatNode(AsyncNode):\n    async def prep_async(self, shared):\n        user_message = shared.get(\"user_message\", \"\")\n        websocket = shared.get(\"websocket\")\n        \n        conversation_history = shared.get(\"conversation_history\", [])\n        conversation_history.append({\"role\": \"user\", \"content\": user_message})\n        \n        return conversation_history, websocket\n    \n    async def exec_async(self, prep_res):\n        messages, websocket = prep_res\n        \n        await websocket.send_text(json.dumps({\"type\": \"start\", \"content\": \"\"}))\n        \n        full_response = \"\"\n        async for chunk_content in stream_llm(messages):\n            full_response += chunk_content\n            await websocket.send_text(json.dumps({\n                \"type\": \"chunk\", \n                \"content\": chunk_content\n            }))\n        \n        await websocket.send_text(json.dumps({\"type\": \"end\", \"content\": \"\"}))\n        \n        return full_response, websocket\n    \n    async def post_async(self, shared, prep_res, exec_res):\n        full_response, websocket = exec_res\n        \n        conversation_history = shared.get(\"conversation_history\", [])\n        conversation_history.append({\"role\": \"assistant\", \"content\": full_response})\n        shared[\"conversation_history\"] = conversation_history "
  },
  {
    "path": "cookbook/pocketflow-fastapi-websocket/requirements.txt",
    "content": "fastapi==0.104.1\nuvicorn[standard]==0.24.0\nopenai==1.3.8\npocketflow "
  },
  {
    "path": "cookbook/pocketflow-fastapi-websocket/static/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title>PocketFlow Chat</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <style>\n        * { margin: 0; padding: 0; box-sizing: border-box; }\n        \n        body { \n            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n            min-height: 100vh;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            padding: 20px;\n        }\n        \n        .chat-container { \n            background: rgba(255, 255, 255, 0.95);\n            backdrop-filter: blur(10px);\n            border-radius: 20px;\n            width: 100%;\n            max-width: 600px;\n            height: 80vh;\n            display: flex;\n            flex-direction: column;\n            box-shadow: 0 20px 40px rgba(0,0,0,0.1);\n            overflow: hidden;\n        }\n        \n        .header {\n            padding: 20px;\n            background: rgba(255, 255, 255, 0.1);\n            border-bottom: 1px solid rgba(255, 255, 255, 0.2);\n            text-align: center;\n        }\n        \n        .header h1 {\n            font-size: 24px;\n            font-weight: 600;\n            color: #333;\n            margin-bottom: 5px;\n        }\n        \n        .status {\n            font-size: 14px;\n            color: #666;\n            font-weight: 500;\n        }\n        \n        .messages { \n            flex: 1;\n            overflow-y: auto;\n            padding: 20px;\n            display: flex;\n            flex-direction: column;\n            gap: 16px;\n        }\n        \n        .message { \n            max-width: 80%;\n            padding: 12px 16px;\n            border-radius: 18px;\n            font-size: 15px;\n            line-height: 1.4;\n            word-wrap: break-word;\n        }\n        \n        .user-message { \n            background: linear-gradient(135deg, #667eea, #764ba2);\n            color: white;\n            align-self: flex-end;\n            border-bottom-right-radius: 4px;\n        }\n        \n        .ai-message { \n            background: #f1f3f4;\n            color: #333;\n            align-self: flex-start;\n            border-bottom-left-radius: 4px;\n        }\n        \n        .input-container { \n            padding: 20px;\n            background: rgba(255, 255, 255, 0.1);\n            border-top: 1px solid rgba(255, 255, 255, 0.2);\n            display: flex;\n            gap: 12px;\n        }\n        \n        #messageInput { \n            flex: 1;\n            padding: 12px 16px;\n            border: none;\n            border-radius: 25px;\n            background: white;\n            font-size: 15px;\n            outline: none;\n            box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n        }\n        \n        #messageInput::placeholder {\n            color: #999;\n        }\n        \n        #sendButton { \n            padding: 12px 24px;\n            background: linear-gradient(135deg, #667eea, #764ba2);\n            color: white;\n            border: none;\n            border-radius: 25px;\n            cursor: pointer;\n            font-size: 15px;\n            font-weight: 600;\n            transition: all 0.2s ease;\n            box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n        }\n        \n        #sendButton:hover:not(:disabled) {\n            transform: translateY(-1px);\n            box-shadow: 0 4px 15px rgba(0,0,0,0.2);\n        }\n        \n        #sendButton:disabled { \n            background: #ccc;\n            cursor: not-allowed;\n            transform: none;\n        }\n        \n        .messages::-webkit-scrollbar {\n            width: 6px;\n        }\n        \n        .messages::-webkit-scrollbar-track {\n            background: transparent;\n        }\n        \n        .messages::-webkit-scrollbar-thumb {\n            background: rgba(0,0,0,0.2);\n            border-radius: 3px;\n        }\n    </style>\n</head>\n<body>\n    <div class=\"chat-container\">\n        <div class=\"header\">\n            <h1>PocketFlow Chat</h1>\n            <div class=\"status\" id=\"status\">Connecting...</div>\n        </div>\n        \n        <div class=\"messages\" id=\"messages\"></div>\n        \n        <div class=\"input-container\">\n            <input type=\"text\" id=\"messageInput\" placeholder=\"Type your message...\" disabled>\n            <button id=\"sendButton\" disabled>Send</button>\n        </div>\n    </div>\n\n    <script>\n        const ws = new WebSocket(`ws://localhost:8000/ws`);\n        const messagesDiv = document.getElementById('messages');\n        const messageInput = document.getElementById('messageInput');\n        const sendButton = document.getElementById('sendButton');\n        const statusDiv = document.getElementById('status');\n        \n        let isStreaming = false;\n        let currentAiMessage = null;\n\n        ws.onopen = function() {\n            statusDiv.textContent = 'Connected';\n            messageInput.disabled = false;\n            sendButton.disabled = false;\n            messageInput.focus();\n        };\n\n        ws.onmessage = function(event) {\n            const data = JSON.parse(event.data);\n            \n            if (data.type === 'start') {\n                isStreaming = true;\n                currentAiMessage = document.createElement('div');\n                currentAiMessage.className = 'message ai-message';\n                messagesDiv.appendChild(currentAiMessage);\n                messagesDiv.scrollTop = messagesDiv.scrollHeight;\n                sendButton.disabled = true;\n                statusDiv.textContent = 'AI is typing...';\n                \n            } else if (data.type === 'chunk') {\n                if (currentAiMessage) {\n                    currentAiMessage.textContent += data.content;\n                    messagesDiv.scrollTop = messagesDiv.scrollHeight;\n                }\n                \n            } else if (data.type === 'end') {\n                isStreaming = false;\n                currentAiMessage = null;\n                sendButton.disabled = false;\n                statusDiv.textContent = 'Connected';\n                messageInput.focus();\n            }\n        };\n\n        ws.onclose = function() {\n            statusDiv.textContent = 'Disconnected';\n            messageInput.disabled = true;\n            sendButton.disabled = true;\n        };\n\n        function sendMessage() {\n            const message = messageInput.value.trim();\n            if (message && !isStreaming) {\n                const userMessage = document.createElement('div');\n                userMessage.className = 'message user-message';\n                userMessage.textContent = message;\n                messagesDiv.appendChild(userMessage);\n                messagesDiv.scrollTop = messagesDiv.scrollHeight;\n\n                ws.send(JSON.stringify({\n                    type: 'message',\n                    content: message\n                }));\n\n                messageInput.value = '';\n                statusDiv.textContent = 'Sending...';\n            }\n        }\n\n        sendButton.addEventListener('click', sendMessage);\n        messageInput.addEventListener('keypress', function(e) {\n            if (e.key === 'Enter') {\n                e.preventDefault();\n                sendMessage();\n            }\n        });\n    </script>\n</body>\n</html> "
  },
  {
    "path": "cookbook/pocketflow-fastapi-websocket/utils/__init__.py",
    "content": "# Utils package for FastAPI WebSocket Chat Interface "
  },
  {
    "path": "cookbook/pocketflow-fastapi-websocket/utils/stream_llm.py",
    "content": "import os\nfrom openai import AsyncOpenAI\n\nasync def stream_llm(messages):\n    client = AsyncOpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    \n    stream = await client.chat.completions.create(\n        model=\"gpt-4o-mini\",\n        messages=messages,\n        stream=True,\n        temperature=0.7\n    )\n    \n    async for chunk in stream:\n        if chunk.choices[0].delta.content is not None:\n            yield chunk.choices[0].delta.content\n\nif __name__ == \"__main__\":\n    import asyncio\n    \n    async def test():\n        messages = [{\"role\": \"user\", \"content\": \"Hello!\"}]\n        async for chunk in stream_llm(messages):\n            print(chunk, end=\"\", flush=True)\n        print()\n    \n    asyncio.run(test()) "
  },
  {
    "path": "cookbook/pocketflow-flow/README.md",
    "content": "# Text Converter Flow\n\nThis project demonstrates an interactive text transformation tool built with PocketFlow.\n\n## Features\n\n- Convert text to UPPERCASE\n- Convert text to lowercase\n- Reverse text\n- Remove extra spaces\n- Interactive command-line interface\n- Continuous flow with option to process multiple texts\n\n## Getting Started\n\n1. Install the required dependencies:\n\n```bash\npip install -r requirements.txt\n```\n\n2. Run the application:\n\n```bash\npython main.py\n```\n\n## How It Works\n\nThe workflow features an interactive loop with branching paths:\n\n```mermaid\ngraph TD\n    Input[TextInput Node] -->|transform| Transform[TextTransform Node]\n    Transform -->|input| Input\n    Transform -->|exit| End[End]\n    Input -->|exit| End\n```\n\nHere's what each part does:\n1. **TextInput Node**: Collects text input and handles menu choices\n2. **TextTransform Node**: Applies the selected transformation to the text\n\n## Example Output\n\n```\nWelcome to Text Converter!\n=========================\n\nEnter text to convert: Pocket Flow is a 100-line LLM framework\n\nChoose transformation:\n1. Convert to UPPERCASE\n2. Convert to lowercase\n3. Reverse text\n4. Remove extra spaces\n5. Exit\n\nYour choice (1-5): 1\n\nResult: POCKET FLOW IS A 100-LINE LLM FRAMEWORK\n\nConvert another text? (y/n): n\n\nThank you for using Text Converter!\n```\n\n## Files\n\n- [`main.py`](./main.py): Main entry point for running the text converter\n- [`flow.py`](./flow.py): Defines the nodes and flow for text transformation\n- [`requirements.txt`](./requirements.txt): Lists the required dependencies\n"
  },
  {
    "path": "cookbook/pocketflow-flow/flow.py",
    "content": "from pocketflow import Node, Flow\n\nclass TextInput(Node):\n    def prep(self, shared):\n        \"\"\"Get text input from user.\"\"\"\n        if \"text\" not in shared:\n            text = input(\"\\nEnter text to convert: \")\n            shared[\"text\"] = text\n        return shared[\"text\"]\n\n    def post(self, shared, prep_res, exec_res):\n        print(\"\\nChoose transformation:\")\n        print(\"1. Convert to UPPERCASE\")\n        print(\"2. Convert to lowercase\")\n        print(\"3. Reverse text\")\n        print(\"4. Remove extra spaces\")\n        print(\"5. Exit\")\n        \n        choice = input(\"\\nYour choice (1-5): \")\n        \n        if choice == \"5\":\n            return \"exit\"\n        \n        shared[\"choice\"] = choice\n        return \"transform\"\n\nclass TextTransform(Node):\n    def prep(self, shared):\n        return shared[\"text\"], shared[\"choice\"]\n    \n    def exec(self, inputs):\n        text, choice = inputs\n        \n        if choice == \"1\":\n            return text.upper()\n        elif choice == \"2\":\n            return text.lower()\n        elif choice == \"3\":\n            return text[::-1]\n        elif choice == \"4\":\n            return \" \".join(text.split())\n        else:\n            return \"Invalid option!\"\n    \n    def post(self, shared, prep_res, exec_res):\n        print(\"\\nResult:\", exec_res)\n        \n        if input(\"\\nConvert another text? (y/n): \").lower() == 'y':\n            shared.pop(\"text\", None)  # Remove previous text\n            return \"input\"\n        return \"exit\"\n\nclass EndNode(Node):\n    pass\n\n# Create nodes\ntext_input = TextInput()\ntext_transform = TextTransform()\nend_node = EndNode()\n\n# Connect nodes\ntext_input - \"transform\" >> text_transform\ntext_transform - \"input\" >> text_input\ntext_transform - \"exit\" >> end_node\n\n# Create flow\nflow = Flow(start=text_input) "
  },
  {
    "path": "cookbook/pocketflow-flow/main.py",
    "content": "from flow import flow\n\ndef main():\n    print(\"\\nWelcome to Text Converter!\")\n    print(\"=========================\")\n    \n    # Initialize shared store\n    shared = {}\n    \n    # Run the flow\n    flow.run(shared)\n    \n    print(\"\\nThank you for using Text Converter!\")\n\nif __name__ == \"__main__\":\n    main() "
  },
  {
    "path": "cookbook/pocketflow-flow/requirements.txt",
    "content": "pocketflow>=0.1.0 "
  },
  {
    "path": "cookbook/pocketflow-google-calendar/.gitignore",
    "content": ".env\nPipfile.lock\ncredentials.json\ntoken.pickle"
  },
  {
    "path": "cookbook/pocketflow-google-calendar/Pipfile",
    "content": "[[source]]\nurl = \"https://pypi.org/simple\"\nverify_ssl = true\nname = \"pypi\"\n\n[packages]\npython-dotenv = \">=0.19.0\"\npocketflow = \">=0.0.2\"\ngoogle-auth-oauthlib = \">=1.0.0\"\ngoogle-auth-httplib2 = \">=0.1.0\"\ngoogle-api-python-client = \">=2.0.0\"\n\n[dev-packages]\n\n[requires]\npython_version = \"3.13\"\n"
  },
  {
    "path": "cookbook/pocketflow-google-calendar/README.md",
    "content": "# Pocket Google Calendar\n\nAn application based on the Pocket Flow framework for Google Calendar integration.\n\n## 📋 Description\n\nThis project implements a Google Calendar integration using the Pocket Flow framework, allowing efficient management of events and appointments through a simple and intuitive interface.\n\n## 🚀 Features\n\n- Google Calendar API Integration\n- Event Management\n- Appointment Viewing\n- Flow-based Interface using Pocket Flow\n\n## 🛠️ Technologies Used\n\n- Python\n- Pocket Flow Framework\n- Google Calendar API\n- Pipenv for dependency management\n\n## 📦 Installation\n\n1. Clone the repository:\n```bash\ngit clone [REPOSITORY_URL]\ncd pocket-google-calendar\n```\n\n2. Install dependencies using Pipenv:\n```bash\npipenv install\n```\n\n## 🔑 Credentials Setup\n\n1. Go to the [Google Cloud Console](https://console.cloud.google.com/)\n2. Create a new project or select an existing one\n3. Enable the Google Calendar API for your project\n4. Create credentials:\n   - Go to \"APIs & Services\" > \"Credentials\"\n   - Click \"Create Credentials\" > \"OAuth client ID\"\n   - Choose \"Desktop application\" as the application type\n   - Download the credentials file\n   - Rename it to `credentials.json`\n   - Place it in the root directory of the project\n\n## 🌍 Environment Variables\n\nCreate a `.env` file in the root directory with the following variables:\n\n```env\n# Google Calendar API Configuration\nGOOGLE_CALENDAR_ID=your_calendar_id@group.calendar.google.com\nGOOGLE_APPLICATION_CREDENTIALS=credentials.json\n\n# Application Configuration\nTIMEZONE=America/Sao_Paulo  # or your preferred timezone\n```\n\n## 🔧 Configuration\n\n1. Activate the virtual environment:\n```bash\npipenv shell\n```\n\n2. Run the application:\n```bash\npython main.py\n```\n\n## Expected Output\n\nWhen running the example, you'll see an output similar to this:\n\n```\n=== Listing your calendars ===\n- Primary Calendar\n- Work\n- Personal\n\n=== Creating an example event ===\nEvent created successfully!\nEvent ID: abc123xyz\n```\n\n\n## 📁 Project Structure\n\n```\npocket-google-calendar/\n├── main.py           # Application entry point\n├── nodes.py          # Pocket Flow node definitions\n├── utils/            # Utilities and helper functions\n├── Pipfile           # Pipenv configuration\n├── credentials.json  # Google Calendar API credentials\n├── .env             # Environment variables\n└── token.pickle      # Google Calendar authentication token\n```\n\n## 🤝 Contributing\n\n1. Fork the project\n2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)\n3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)\n4. Push to the Branch (`git push origin feature/AmazingFeature`)\n5. Open a Pull Request\n\n## 📝 License\n\nThis project is under the MIT License. See the [LICENSE](LICENSE) file for more details.\n\n## ✨ Acknowledgments\n\n- [Pocket Flow](https://github.com/the-pocket/PocketFlow) - Framework used\n- [Google Calendar API](https://developers.google.com/calendar) - Integration API "
  },
  {
    "path": "cookbook/pocketflow-google-calendar/main.py",
    "content": "from pocketflow import Flow\nfrom nodes import CreateCalendarEventNode, ListCalendarEventsNode, ListCalendarsNode\nfrom datetime import datetime, timedelta\n\ndef create_calendar_flow():\n    \"\"\"Creates a flow to manage calendar events.\"\"\"\n    # Create nodes\n    create_event_node = CreateCalendarEventNode()\n    list_events_node = ListCalendarEventsNode()\n    \n    # Connect nodes\n    create_event_node - \"success\" >> list_events_node\n    create_event_node - \"error\" >> None\n    \n    # Create flow\n    return Flow(start=create_event_node)\n\ndef list_calendars_flow():\n    \"\"\"Creates a flow to list all user calendars.\"\"\"\n    list_calendars_node = ListCalendarsNode()\n    return Flow(start=list_calendars_node)\n\ndef main():\n    # Example: List all calendars\n    print(\"=== Listing your calendars ===\")\n    flow = list_calendars_flow()\n    shared = {}\n    flow.run(shared)\n    \n    if 'available_calendars' in shared:\n        for cal in shared['available_calendars']:\n            print(f\"- {cal.get('summary')}\")\n\n    # Example: Create a simple event\n    print(\"\\n=== Creating an example event ===\")\n    flow = create_calendar_flow()\n\n    shared = {\n        'event_summary': 'Example Meeting',\n        'event_description': 'An example meeting created by PocketFlow',\n        'event_start_time': datetime.now() + timedelta(days=1),\n        'event_end_time': datetime.now() + timedelta(days=1, hours=1),\n        'days_to_list': 7\n    }\n\n    flow.run(shared)\n    \n    if 'last_created_event' in shared:\n        print(\"Event created successfully!\")\n        print(f\"Event ID: {shared['last_created_event']['id']}\")\n\nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "cookbook/pocketflow-google-calendar/nodes.py",
    "content": "from pocketflow import Node\nfrom utils.google_calendar import create_event, list_events, list_calendar_lists\nfrom datetime import datetime, timedelta\n\nclass CreateCalendarEventNode(Node):\n    def prep(self, shared):\n        \"\"\"Prepares the necessary data to create an event.\"\"\"\n        return {\n            'summary': shared.get('event_summary'),\n            'description': shared.get('event_description'),\n            'start_time': shared.get('event_start_time'),\n            'end_time': shared.get('event_end_time')\n        }\n    \n    def exec(self, event_data):\n        \"\"\"Creates a new calendar event.\"\"\"\n        try:\n            event = create_event(\n                summary=event_data['summary'],\n                description=event_data['description'],\n                start_time=event_data['start_time'],\n                end_time=event_data['end_time']\n            )\n            return {'success': True, 'event': event}\n        except Exception as e:\n            return {'success': False, 'error': str(e)}\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Stores the event creation result.\"\"\"\n        if exec_res['success']:\n            shared['last_created_event'] = exec_res['event']\n            return 'success'\n        else:\n            shared['error'] = exec_res['error']\n            return 'error'\n\nclass ListCalendarEventsNode(Node):\n    def prep(self, shared):\n        \"\"\"Prepares parameters to list events.\"\"\"\n        return {\n            'days': shared.get('days_to_list', 7)\n        }\n    \n    def exec(self, params):\n        \"\"\"Lists calendar events.\"\"\"\n        try:\n            events = list_events(days=params['days'])\n            return {'success': True, 'events': events}\n        except Exception as e:\n            return {'success': False, 'error': str(e)}\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Stores the list of events.\"\"\"\n        if exec_res['success']:\n            shared['calendar_events'] = exec_res['events']\n            return 'success'\n        else:\n            shared['error'] = exec_res['error']\n            return 'error'\n\nclass ListCalendarsNode(Node):\n    def prep(self, shared):\n        \"\"\"No special preparation needed to list calendars.\"\"\"\n        return {}\n\n    def exec(self, params):\n        \"\"\"Lists all available calendars for the user.\"\"\"\n        try:\n            calendars = list_calendar_lists()\n            return {'success': True, 'calendars': calendars}\n        except Exception as e:\n            return {'success': False, 'error': str(e)}\n\n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Stores the list of calendars in the shared store.\"\"\"\n        if exec_res['success']:\n            shared['available_calendars'] = exec_res['calendars']\n            return 'success'\n        else:\n            shared['error'] = exec_res['error']\n            return 'error' "
  },
  {
    "path": "cookbook/pocketflow-google-calendar/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-google-calendar/utils/google_calendar.py",
    "content": "from google.oauth2.credentials import Credentials\nfrom google_auth_oauthlib.flow import InstalledAppFlow\nfrom google.auth.transport.requests import Request\nfrom googleapiclient.discovery import build\nimport os.path\nimport os\nimport pickle\nfrom datetime import datetime, timedelta\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\nCALENDAR_ID = os.getenv('GOOGLE_CALENDAR_ID')\nGOOGLE_APPLICATION_CREDENTIALS = os.getenv('GOOGLE_APPLICATION_CREDENTIALS')\nTIMEZONE = os.getenv('TIMEZONE')\n\nSCOPES = ['https://www.googleapis.com/auth/calendar']\n\ndef get_calendar_service():\n    \"\"\"Gets the authenticated Google Calendar service.\"\"\"\n    creds = None\n    if os.path.exists('token.pickle'):\n        with open('token.pickle', 'rb') as token:\n            creds = pickle.load(token)\n    \n    if not creds or not creds.valid:\n        if creds and creds.expired and creds.refresh_token:\n            creds.refresh(Request())\n        else:\n            flow = InstalledAppFlow.from_client_secrets_file(\n                GOOGLE_APPLICATION_CREDENTIALS, SCOPES)\n            creds = flow.run_local_server(port=0)\n        with open('token.pickle', 'wb') as token:\n            pickle.dump(creds, token)\n\n    return build('calendar', 'v3', credentials=creds)\n\ndef create_event(summary, description, start_time, end_time, timezone=TIMEZONE):\n    \"\"\"Creates a new event in Google Calendar.\"\"\"\n    service = get_calendar_service()\n    \n    event = {\n        'summary': summary,\n        'description': description,\n        'start': {\n            'dateTime': start_time.isoformat(),\n            'timeZone': timezone,\n        },\n        'end': {\n            'dateTime': end_time.isoformat(),\n            'timeZone': timezone,\n        },\n    }\n\n    event = service.events().insert(calendarId=CALENDAR_ID, body=event).execute()\n    return event\n\ndef list_events(days=7):\n    \"\"\"Lists events for the next X days.\"\"\"\n    service = get_calendar_service()\n    \n    now = datetime.utcnow()\n    time_min = now.isoformat() + 'Z'\n    time_max = (now + timedelta(days=days)).isoformat() + 'Z'\n\n    events_result = service.events().list(\n        calendarId=CALENDAR_ID,\n        timeMin=time_min,\n        timeMax=time_max,\n        singleEvents=True,\n        orderBy='startTime'\n    ).execute()\n    \n    return events_result.get('items', [])\n\ndef create_custom_calendar(calendar_name, description=\"\"):\n    \"\"\"Creates a new custom calendar in Google Calendar.\"\"\"\n    service = get_calendar_service()\n    \n    calendar = {\n        'summary': calendar_name,\n        'description': description,\n        'timeZone': TIMEZONE\n    }\n\n    created_calendar = service.calendars().insert(body=calendar).execute()\n    return created_calendar\n\ndef list_calendar_lists():\n    \"\"\"Lists all available calendars for the user.\"\"\"\n    service = get_calendar_service()\n    \n    calendar_list = service.calendarList().list().execute()\n    return calendar_list.get('items', []) "
  },
  {
    "path": "cookbook/pocketflow-gradio-hitl/README.md",
    "content": "# PocketFlow Gradio HITL Example\n\nA web-based application that demonstrates Human-in-the-Loop (HITL) workflow orchestration using PocketFlow and Gradio. This example provides an interactive interface for users to engage with AI-powered tasks while maintaining human oversight and feedback.\n\n## Features\n\n- **Web-based Interface**: Built with Gradio for an accessible and user-friendly experience\n- **Human-in-the-Loop Integration**: Seamless integration of human feedback into the AI workflow\n- **Modern UI**: Clean and intuitive interface for better user interaction\n- **Powered by LLMs**: Utilizes OpenAI's models for intelligent task processing\n- **Flow Visualization**: Real-time visualization of node execution sequence and workflow progress\n- **Interactive Debugging**: Monitor and understand the decision-making process through visual feedback\n\n## Getting Started\n\nThis project is part of the PocketFlow cookbook examples. It's assumed you have already cloned the [PocketFlow repository](https://github.com/the-pocket/PocketFlow) and are in the `cookbook/pocketflow-gradio-hitl` directory.\n\n1. **Install required dependencies**:\n    ```bash\n    pip install -r requirements.txt\n    ```\n\n2. **Set up your OpenAI API key**:\n    The application uses OpenAI models for processing. You need to set your API key as an environment variable:\n    ```bash\n    export OPENAI_API_KEY=\"your-openai-api-key-here\"\n    ```\n\n3. **Run the Application**:\n    ```bash\n    python main.py\n    ```\n    This will start the Gradio web interface, typically accessible at `http://localhost:7860`\n\n## How It Works\n\nThe system implements a PocketFlow workflow with a web interface:\n\n```mermaid\nflowchart TD\n    DecideAction[Decide Action Node] --> |\"check-weather\"| CheckWeather[Check Weather Node]\n    CheckWeather --> DecideAction\n    DecideAction --> |\"book-hotel\"| BookHotel[Book Hotel Node]\n    BookHotel --> DecideAction\n    DecideAction --> |\"follow-up\"| FollowUp[Follow Up Node]\n    DecideAction --> |\"result-notification\"| ResultNotification[Result Notification Node]\n```\n\nThe workflow consists of the following nodes:\n\n1. **Decide Action Node**: The central decision-making node that determines the next action based on user input and context\n2. **Check Weather Node**: Provides weather information for specified cities and dates\n3. **Book Hotel Node**: Handles hotel reservation requests with check-in and check-out dates\n4. **Follow Up Node**: Manages user interactions by asking clarifying questions or handling out-of-scope requests\n5. **Result Notification Node**: Delivers action results and offers additional assistance\n\nThe flow is orchestrated through a series of directed connections:\n- The Decide Action node can trigger weather checks, hotel bookings, follow-ups, or result notifications\n- Weather checks and hotel bookings can feed back to the Decide Action node for further processing\n- Follow-up and result notification nodes provide the final steps in the workflow\n\n### Flow Visualization\n\nThe application provides real-time visualization of the workflow execution:\n- The sequence of node activations is displayed chronologically\n- Users can see which decision paths are being taken\n- The visualization helps in understanding the AI's decision-making process\n\n![flow visualization](./assets/flow_visualization.png)\n\n## Sample Output\n\nHere's an example of book hotel:\n\n![book hotel](./assets/book_hotel.png)\n\nHere's an example of changing intention mid-conversation:\n\n![change intention](./assets/change_intention.png)\n\n## Files\n\n- [`main.py`](./main.py): Entry point for the application and Gradio interface setup\n- [`flow.py`](./flow.py): Defines the PocketFlow graph and node connections\n- [`nodes.py`](./nodes.py): Contains the node definitions for the workflow\n- [`utils/`](./utils/): Contains utility functions and helper modules\n- [`requirements.txt`](./requirements.txt): Lists project dependencies\n\n## Requirements\n\n- Python 3.8+\n- PocketFlow >= 0.0.2\n- Gradio >= 5.29.1\n- OpenAI >= 1.78.1\n"
  },
  {
    "path": "cookbook/pocketflow-gradio-hitl/flow.py",
    "content": "from pocketflow import Flow\n\nfrom nodes import (\n    DecideAction,\n    CheckWeather,\n    BookHotel,\n    FollowUp,\n    ResultNotification,\n)\n\n\ndef create_flow():\n    \"\"\"\n    Create and connect the nodes to form a complete agent flow.\n    \"\"\"\n    decide_action = DecideAction()\n    check_weather = CheckWeather()\n    book_hotel = BookHotel()\n    follow_up = FollowUp()\n    result_notification = ResultNotification()\n\n    decide_action - \"check-weather\" >> check_weather\n    check_weather >> decide_action\n    decide_action - \"book-hotel\" >> book_hotel\n    book_hotel >> decide_action\n    decide_action - \"follow-up\" >> follow_up\n    decide_action - \"result-notification\" >> result_notification\n\n    return Flow(start=decide_action)\n"
  },
  {
    "path": "cookbook/pocketflow-gradio-hitl/main.py",
    "content": "import time\nimport uuid\nfrom concurrent.futures import ThreadPoolExecutor\nfrom queue import Queue\n\nimport gradio as gr\nfrom gradio import ChatMessage\n\nfrom flow import create_flow\n\n# create global thread pool\nchatflow_thread_pool = ThreadPoolExecutor(\n    max_workers=5,\n    thread_name_prefix=\"chatflow_worker\",\n)\n\n\ndef chat_fn(message, history, uuid):\n    \"\"\"\n    Main chat function that handles the conversation flow and message processing.\n    \n    Args:\n        message (str): The current user message\n        history (list): Previous conversation history\n        uuid (UUID): Unique identifier for the conversation\n    \n    Yields:\n        ChatMessage: Streams of thought process and chat responses\n    \"\"\"\n    # Log conversation details\n    print(f\"Conversation ID: {str(uuid)}\\nHistory: {history}\\nQuery: {message}\\n---\")\n    \n    # Initialize queues for chat messages and flow thoughts\n    chat_queue = Queue()\n    flow_queue = Queue()\n    \n    # Create shared context for the flow\n    shared = {\n        \"conversation_id\": str(uuid),\n        \"query\": message,\n        \"history\": history,\n        \"queue\": chat_queue,\n        \"flow_queue\": flow_queue,\n    }\n    \n    # Create and run the chat flow in a separate thread\n    chat_flow = create_flow()\n    chatflow_thread_pool.submit(chat_flow.run, shared)\n\n    # Initialize thought response tracking\n    start_time = time.time()\n    thought_response = ChatMessage(\n        content=\"\", metadata={\"title\": \"Flow Log\", \"id\": 0, \"status\": \"pending\"}\n    )\n    yield thought_response\n\n    # Process and accumulate thoughts from the flow queue\n    accumulated_thoughts = \"\"\n    while True:\n        thought = flow_queue.get()\n        if thought is None:\n            break\n        accumulated_thoughts += f\"- {thought}\\n\\n\"\n        thought_response.content = accumulated_thoughts.strip()\n        yield thought_response\n        flow_queue.task_done()\n\n    # Mark thought processing as complete and record duration\n    thought_response.metadata[\"status\"] = \"done\"\n    thought_response.metadata[\"duration\"] = time.time() - start_time\n    yield thought_response\n\n    # Process and yield chat messages from the chat queue\n    while True:\n        msg = chat_queue.get()\n        if msg is None:\n            break\n        chat_response = [thought_response, ChatMessage(content=msg)]\n        yield chat_response\n        chat_queue.task_done()\n\n\ndef clear_fn():\n    print(\"Clearing conversation\")\n    return uuid.uuid4()\n\n\nwith gr.Blocks(fill_height=True, theme=\"ocean\") as demo:\n    uuid_state = gr.State(uuid.uuid4())\n    demo.load(clear_fn, outputs=[uuid_state])\n\n    chatbot = gr.Chatbot(type=\"messages\", scale=1)\n    chatbot.clear(clear_fn, outputs=[uuid_state])\n\n    gr.ChatInterface(\n        fn=chat_fn,\n        type=\"messages\",\n        additional_inputs=[uuid_state],\n        chatbot=chatbot,\n        title=\"PocketFlow Gradio Demo\",\n    )\n\n\ndemo.launch()\n"
  },
  {
    "path": "cookbook/pocketflow-gradio-hitl/nodes.py",
    "content": "from datetime import datetime\nfrom queue import Queue\n\nimport yaml\nfrom pocketflow import Node\n\nfrom utils.call_llm import call_llm\nfrom utils.call_mock_api import call_book_hotel_api, call_check_weather_api\nfrom utils.conversation import load_conversation, save_conversation\nfrom utils.format_chat_history import format_chat_history\n\n\nclass DecideAction(Node):\n    def prep(self, shared):\n        conversation_id = shared[\"conversation_id\"]\n        session = load_conversation(conversation_id)\n        return session, shared[\"history\"], shared[\"query\"]\n\n    def exec(self, prep_res):\n        session, history, query = prep_res\n        prompt = f\"\"\"\n### INSTRUCTIONS\nYou are a lifestyle assistant capable of helping users book hotels and check weather conditions.\nYou need to decide the next action based on your last action, action execution result, chat history, and current user question.\n\n### CHAT HISTORY\n{format_chat_history(history)}\n\n### CURRENT USER QUESTION\nuser: {query}\n\n### CONTEXT\nLast Action: {session.get(\"last_action\", None)}\nLast Action Result: {session.get(\"action_result\", None)}\nCurrent Date: {datetime.now().date()} \n\n### ACTION SPACE\n[1] check-weather\nDescription: When the user asks about the weather, use this tool.\nParameters:\n    - name: city\n        description: The city to check the weather\n        required: true\n        example: Beijing\n    - name: date\n        description: The date to check the weather, if not provided, use the current date\n        required: false\n        example: 2025-05-28\n\n[2] book-hotel\nDescription: When the user wants to book a hotel, use this tool.\nParameters:\n    - name: hotel\n        description: The name of the hotel to be booked\n        required: true\n        example: ShanghaiHilton Hotel\n    - name: checkin_date\n        description: The check-in date\n        required: true\n        example: 2025-05-28\n    - name: checkout_date\n        description: The check-out date\n        required: true\n        example: 2025-05-29\n\n[3] follow-up\nDescription: 1. When the user's question is out of the scope of booking hotels and checking weather, use this tool to guide the user; 2. When the current information cannot meet the parameter requirements of the corresponding tool, use this tool to ask the user.\nParameters:\n    - name: question\n        description: Your guidance or follow-up to the user, maintain an enthusiastic and lively language style, and use the same language as the user's question.\n        required: true\n        example: Which hotel would you like to book?😊\n\n[4] result-notification\nDescription: When the booking of a hotel or checking the weather is completed, use this tool to notify the user of the result and ask if they need any other help. If you find that the user's question is not completed in the history conversation, you can guide the user to complete the intention in the last step.\nParameters:\n    - name: result\n        description: Notify the user of the result based on the Last Action Result. Maintain an enthusiastic and lively language style, and use the same language as the user's question.\n        required: true\n        example: The hotel has been successfully booked for you. 😉\\n\\nThe check-in date is XX, and the check-out date is XX. Thank you for using it. Would you like any other help?😀\n\n## NEXT ACTION\nDecide the next action based on the context and available actions.\nReturn your response in this format:\n\n```yaml\nthinking: |\n    <your step-by-step reasoning process>\naction: check-weather OR book-hotel OR follow-up OR result-notification\nreason: <why you chose this action>\nquestion: <if action is follow-up>\ncity: <if action is check-weather> \nhotel: <if action is book-hotel>\ncheckin_date: <if action is book-hotel>\ncheckout_date: <if action is book-hotel>\nresult: <if action is result-notification>\n```\n\nIMPORTANT: Make sure to:\n1. Use proper indentation (4 spaces) for all multi-line fields\n2. Use the | character for multi-line text fields\n3. Keep single-line fields without the | character\n\"\"\"\n\n        response = call_llm(prompt.strip())\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        print(f\"🤖 Agent response: \\n{yaml_str}\")\n        decision = yaml.safe_load(yaml_str)\n        return decision\n\n    def post(self, shared, prep_res, exec_res):\n        conversation_id = shared[\"conversation_id\"]\n        session: dict = load_conversation(conversation_id)\n        \"\"\"Save the decision and determine the next step in the flow.\"\"\"\n        # If LLM decided to search, save the search query\n        session[\"last_action\"] = exec_res[\"action\"]\n        flow_log: Queue = shared[\"flow_queue\"]\n\n        for line in exec_res[\"thinking\"].split(\"\\n\"):\n            line = line.replace(\"-\", \"\").strip()\n            if line:\n                flow_log.put(f\"🤔 {line}\")\n\n        if exec_res[\"action\"] == \"check-weather\":\n            session[\"check_weather_params\"] = {\n                \"city\": exec_res[\"city\"],\n                \"date\": exec_res.get(\"date\", None),\n            }\n            flow_log.put(f\"➡️ Agent decided to check weather for: {exec_res['city']}\")\n        elif exec_res[\"action\"] == \"book-hotel\":\n            session[\"book_hotel_params\"] = {\n                \"hotel\": exec_res[\"hotel\"],\n                \"checkin_date\": exec_res[\"checkin_date\"],\n                \"checkout_date\": exec_res[\"checkout_date\"],\n            }\n            flow_log.put(f\"➡️ Agent decided to book hotel: {exec_res['hotel']}\")\n        elif exec_res[\"action\"] == \"follow-up\":\n            session[\"follow_up_params\"] = {\"question\": exec_res[\"question\"]}\n            flow_log.put(f\"➡️ Agent decided to follow up: {exec_res['question']}\")\n        elif exec_res[\"action\"] == \"result-notification\":\n            session[\"result_notification_params\"] = {\"result\": exec_res[\"result\"]}\n            flow_log.put(f\"➡️ Agent decided to notify the result: {exec_res['result']}\")\n        save_conversation(conversation_id, session)\n        # Return the action to determine the next node in the flow\n        return exec_res[\"action\"]\n\n\nclass CheckWeather(Node):\n    def prep(self, shared):\n        conversation_id = shared[\"conversation_id\"]\n        session: dict = load_conversation(conversation_id)\n        city = session[\"check_weather_params\"][\"city\"]\n        date = session[\"check_weather_params\"].get(\"date\", None)\n        return city, date\n\n    def exec(self, prep_res):\n        city, date = prep_res\n        return call_check_weather_api(city, date)\n\n    def post(self, shared, prep_res, exec_res):\n        flow_log: Queue = shared[\"flow_queue\"]\n        flow_log.put(f\"⬅️ Check weather result: {exec_res}\")\n\n        conversation_id = shared[\"conversation_id\"]\n        session: dict = load_conversation(conversation_id)\n        session[\"action_result\"] = exec_res\n        save_conversation(conversation_id, session)\n        return \"default\"\n\n\nclass BookHotel(Node):\n    def prep(self, shared):\n        conversation_id = shared[\"conversation_id\"]\n        session: dict = load_conversation(conversation_id)\n\n        hotel = session[\"book_hotel_params\"][\"hotel\"]\n        checkin_date = session[\"book_hotel_params\"][\"checkin_date\"]\n        checkout_date = session[\"book_hotel_params\"][\"checkout_date\"]\n        return hotel, checkin_date, checkout_date\n\n    def exec(self, prep_res):\n        hotel, checkin_date, checkout_date = prep_res\n        return call_book_hotel_api(hotel, checkin_date, checkout_date)\n\n    def post(self, shared, prep_res, exec_res):\n        flow_log: Queue = shared[\"flow_queue\"]\n        flow_log.put(f\"⬅️ Book hotel result: {exec_res}\")\n\n        conversation_id = shared[\"conversation_id\"]\n        session: dict = load_conversation(conversation_id)\n        session[\"action_result\"] = exec_res\n        save_conversation(conversation_id, session)\n        return \"default\"\n\n\nclass FollowUp(Node):\n    def prep(self, shared):\n        flow_log: Queue = shared[\"flow_queue\"]\n        flow_log.put(None)\n\n        conversation_id = shared[\"conversation_id\"]\n        session: dict = load_conversation(conversation_id)\n        question = session[\"follow_up_params\"][\"question\"]\n        return question, shared[\"queue\"]\n\n    def exec(self, prep_res):\n        question, queue = prep_res\n        queue.put(question)\n        queue.put(None)\n        return question\n\n    def post(self, shared, prep_res, exec_res):\n        conversation_id = shared[\"conversation_id\"]\n        session: dict = load_conversation(conversation_id)\n        session[\"action_result\"] = exec_res\n        return \"done\"\n\n\nclass ResultNotification(Node):\n    def prep(self, shared):\n        flow_log: Queue = shared[\"flow_queue\"]\n        flow_log.put(None)\n\n        conversation_id = shared[\"conversation_id\"]\n        session: dict = load_conversation(conversation_id)\n        result = session[\"result_notification_params\"][\"result\"]\n        return result, shared[\"queue\"]\n\n    def exec(self, prep_res):\n        result, queue = prep_res\n        queue.put(result)\n        queue.put(None)\n        return result\n\n    def post(self, shared, prep_res, exec_res):\n        conversation_id = shared[\"conversation_id\"]\n        session: dict = load_conversation(conversation_id)\n        session[\"action_result\"] = None\n        session[\"last_action\"] = None\n        save_conversation(conversation_id, session)\n        return \"done\"\n"
  },
  {
    "path": "cookbook/pocketflow-gradio-hitl/requirements.txt",
    "content": "pocketflow>=0.0.2\ngradio>=5.29.1\nopenai>=1.78.1"
  },
  {
    "path": "cookbook/pocketflow-gradio-hitl/utils/call_llm.py",
    "content": "import os\n\nfrom openai import OpenAI\nfrom openai.types.chat.chat_completion import ChatCompletion\n\napi_key = os.getenv(\"OPENAI_API_KEY\")\nbase_url = \"https://api.openai.com/v1\"\nmodel = \"gpt-4o\"\n\n\ndef call_llm(message: str):\n    print(f\"Calling LLM with message: \\n{message}\")\n    client = OpenAI(api_key=api_key, base_url=base_url)\n    response: ChatCompletion = client.chat.completions.create(\n        model=model, messages=[{\"role\": \"user\", \"content\": message}]\n    )\n    return response.choices[0].message.content\n\n\nif __name__ == \"__main__\":\n    print(call_llm(\"Hello, how are you?\"))\n"
  },
  {
    "path": "cookbook/pocketflow-gradio-hitl/utils/call_mock_api.py",
    "content": "import random\nfrom datetime import date, datetime\n\n\ndef call_check_weather_api(city: str, date: date | None):\n    if date is None:\n        date = datetime.now().date()\n\n    current_date = datetime.now().date()\n\n    # calculate date difference\n    date_diff = (date - current_date).days\n\n    # check if the date is within the allowed range\n    if abs(date_diff) > 7:\n        return f\"Failed to check weather: Date {date} is more than 7 days away from current date.\"\n\n    return f\"The weather in {city} on {date} is {random.choice(['sunny', 'cloudy', 'rainy', 'snowy'])}, and the temperature is {random.randint(10, 30)}°C.\"\n\n\ndef call_book_hotel_api(hotel: str, checkin_date: date, checkout_date: date):\n    current_date = datetime.now().date()\n\n    # check if the checkin date is after the current date\n    if checkin_date <= current_date:\n        return (\n            f\"Failed to book hotel {hotel}: Check-in date must be after current date.\"\n        )\n\n    # check if the checkin date is before the checkout date\n    if checkin_date >= checkout_date:\n        return f\"Failed to book hotel {hotel}, because the checkin date is after the checkout date.\"\n\n    # check if the date difference is more than 7 days\n    date_diff = (checkout_date - checkin_date).days\n    if date_diff > 7:\n        return f\"Failed to book hotel {hotel}: Stay duration cannot exceed 7 days.\"\n\n    return f\"Booked hotel {hotel} from {checkin_date.strftime('%Y-%m-%d')} to {checkout_date.strftime('%Y-%m-%d')} successfully.\"\n"
  },
  {
    "path": "cookbook/pocketflow-gradio-hitl/utils/conversation.py",
    "content": "conversation_cache = {}\n\n\ndef load_conversation(conversation_id: str):\n    print(f\"Loading conversation {conversation_id}\")\n    return conversation_cache.get(conversation_id, {})\n\n\ndef save_conversation(conversation_id: str, session: dict):\n    print(f\"Saving conversation {session}\")\n    conversation_cache[conversation_id] = session\n"
  },
  {
    "path": "cookbook/pocketflow-gradio-hitl/utils/format_chat_history.py",
    "content": "def format_chat_history(history):\n    \"\"\"\n    Format the chat history for LLM\n\n    Args:\n        history (list): The chat history list, each element contains role and content\n\n    Returns:\n        str: The formatted chat history string\n    \"\"\"\n    if not history:\n        return \"No history\"\n\n    formatted_history = []\n    for message in history:\n        role = \"user\" if message[\"role\"] == \"user\" else \"assistant\"\n        content = message[\"content\"]\n        # filter out the thinking content\n        if role == \"assistant\":\n            if (\n                content.startswith(\"- 🤔\")\n                or content.startswith(\"- ➡️\")\n                or content.startswith(\"- ⬅️\")\n            ):\n                continue\n        formatted_history.append(f\"{role}: {content}\")\n\n    return \"\\n\".join(formatted_history)\n"
  },
  {
    "path": "cookbook/pocketflow-hello-world/README.md",
    "content": "# PocketFlow Hello World\n\nYour first PocketFlow application! This simple example demonstrates how to create a basic PocketFlow app from scratch.\n\n## Project Structure\n\n```\n.\n├── docs/          # Documentation files\n├── utils/         # Utility functions\n├── flow.py        # PocketFlow implementation\n├── main.py        # Main application entry point\n└── README.md      # Project documentation\n```\n\n## Setup\n\n1. Create a virtual environment:\n```bash\npython -m venv venv\nsource venv/bin/activate  # On Windows: venv\\Scripts\\activate\n```\n\n2. Install dependencies:\n```bash\npip install -r requirements.txt\n```\n\n3. Run the example:\n```bash\npython main.py\n```\n\n## What This Example Demonstrates\n\n- How to create your first PocketFlow application\n- Basic PocketFlow concepts and usage\n- Simple example of PocketFlow's capabilities\n\n## Additional Resources\n\n- [PocketFlow Documentation](https://the-pocket.github.io/PocketFlow/) "
  },
  {
    "path": "cookbook/pocketflow-hello-world/docs/design.md",
    "content": "# Your Project Title\n\n## Project Requirements\nA description of the project requirements. \n\n## Utility Functions\n\n1. **Call LLM** (`utils/call_llm.py`)\n\n## Flow Design\n\n1. **First Node**\n2. **Second Node**\n3. **Third Node**\n\n### Flow Diagram\n\n```mermaid\nflowchart TD\n    firstNode[First Node] --> secondNode[Second Node]\n    secondNode --> thirdNode[Third Node]\n```\n\n## Data Structure\n\nThe shared memory structure will be organized as follows:\n\n```python\nshared = {\n    \"key\": \"value\"\n}\n```\n\n## Node Designs\n\n### 1. First Node\n- **Purpose**: What the node does\n- **Design**: Regular Node (no Batch/Async)\n- **Data Access**: \n  - Read: \"key\" from shared store\n  - Write: \"key\" to shared store\n\n### 2. Second Node\n...\n\n### 3. Third Node\n"
  },
  {
    "path": "cookbook/pocketflow-hello-world/flow.py",
    "content": "from pocketflow import Node, Flow\nfrom utils.call_llm import call_llm\n\n# An example node and flow\n# Please replace this with your own node and flow\nclass AnswerNode(Node):\n    def prep(self, shared):\n        # Read question from shared\n        return shared[\"question\"]\n    \n    def exec(self, question):\n        return call_llm(question)\n    \n    def post(self, shared, prep_res, exec_res):\n        # Store the answer in shared\n        shared[\"answer\"] = exec_res\n\nanswer_node = AnswerNode()\nqa_flow = Flow(start=answer_node)"
  },
  {
    "path": "cookbook/pocketflow-hello-world/main.py",
    "content": "from flow import qa_flow\n\n# Example main function\n# Please replace this with your own main function\ndef main():\n    shared = {\n        \"question\": \"In one sentence, what's the end of universe?\",\n        \"answer\": None\n    }\n\n    qa_flow.run(shared)\n    print(\"Question:\", shared[\"question\"])\n    print(\"Answer:\", shared[\"answer\"])\n\nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "cookbook/pocketflow-hello-world/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-hello-world/utils/call_llm.py",
    "content": "from openai import OpenAI\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=\"YOUR_API_KEY_HERE\")\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n    \nif __name__ == \"__main__\":\n    prompt = \"What is the meaning of life?\"\n    print(call_llm(prompt))"
  },
  {
    "path": "cookbook/pocketflow-llm-streaming/README.md",
    "content": "#  LLM Streaming and Interruption\n\nDemonstrates real-time LLM response streaming with user interrupt capability.\n\n- Check out the [Substack Post Tutorial](https://zacharyhuang.substack.com/p/streaming-llm-responses-tutorial) for more!\n\n## Features\n\n- Real-time display of LLM responses as they're generated\n- User interrupt with ENTER key at any time\n\n## Run It\n\n```bash\npip install -r requirements.txt\npython main.py\n```\n\n## How It Works\n\nStreamNode:\n1. Creates interrupt listener thread\n2. Fetches content chunks from LLM\n3. Displays chunks in real-time\n4. Handles user interruption\n\n## API Key\n\nBy default, demo uses fake streaming responses. To use real OpenAI streaming:\n\n1. Edit main.py to replace the fake_stream_llm with stream_llm:\n```python\n# Change this line:\nchunks = fake_stream_llm(prompt)\n# To this:\nchunks = stream_llm(prompt)\n```\n\n2. Make sure your OpenAI API key is set:\n```bash\nexport OPENAI_API_KEY=\"your-api-key-here\"\n```\n\n## Files\n\n- `main.py`: StreamNode implementation\n- `utils.py`: Real and fake LLM streaming functions\n \n"
  },
  {
    "path": "cookbook/pocketflow-llm-streaming/main.py",
    "content": "import time\nimport threading\nfrom pocketflow import Node, Flow\nfrom utils import fake_stream_llm, stream_llm\n\nclass StreamNode(Node):\n    def prep(self, shared):\n        # Create interrupt event\n        interrupt_event = threading.Event()\n\n        # Start a thread to listen for user interrupt\n        def wait_for_interrupt():\n            input(\"Press ENTER at any time to interrupt streaming...\\n\")\n            interrupt_event.set()\n        listener_thread = threading.Thread(target=wait_for_interrupt)\n        listener_thread.start()\n        \n        # Get prompt from shared store\n        prompt = shared[\"prompt\"]\n        # Get chunks from LLM function\n        chunks = stream_llm(prompt)\n        return chunks, interrupt_event, listener_thread\n\n    def exec(self, prep_res):\n        chunks, interrupt_event, listener_thread = prep_res\n        for chunk in chunks:\n            if interrupt_event.is_set():\n                print(\"User interrupted streaming.\")\n                break\n            \n            if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content is not None:\n                chunk_content = chunk.choices[0].delta.content\n                print(chunk_content, end=\"\", flush=True)\n                time.sleep(0.1)  # simulate latency\n        return interrupt_event, listener_thread\n\n    def post(self, shared, prep_res, exec_res):\n        interrupt_event, listener_thread = exec_res\n        # Join the interrupt listener so it doesn't linger\n        interrupt_event.set()\n        listener_thread.join()\n        return \"default\"\n\n# Usage:\nnode = StreamNode()\nflow = Flow(start=node)\n\nshared = {\"prompt\": \"What's the meaning of life?\"}\nflow.run(shared)\n"
  },
  {
    "path": "cookbook/pocketflow-llm-streaming/requirements.txt",
    "content": "pocketflow>=0.0.1\nopenai>=1.0.0"
  },
  {
    "path": "cookbook/pocketflow-llm-streaming/utils.py",
    "content": "from openai import OpenAI\nimport os\n\ndef stream_llm(prompt):\n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n\n    # Make a streaming chat completion request\n    response = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[\n            {\"role\": \"user\", \"content\": prompt}\n        ],\n        temperature=0.7,\n        stream=True  # Enable streaming\n    )\n    return response\n\ndef fake_stream_llm(prompt, predefined_text=\"This is a fake response. Today is a sunny day. The sun is shining. The birds are singing. The flowers are blooming. The bees are buzzing. The wind is blowing. The clouds are drifting. The sky is blue. The grass is green. The trees are tall. The water is clear. The fish are swimming. The sun is shining. The birds are singing. The flowers are blooming. The bees are buzzing. The wind is blowing. The clouds are drifting. The sky is blue. The grass is green. The trees are tall. The water is clear. The fish are swimming.\"):\n    \"\"\"\n    Returns a list of simple objects that mimic the structure needed\n    for OpenAI streaming responses.\n    \"\"\"\n    # Split text into small chunks\n    chunk_size = 10\n    chunks = []\n    \n    # Create the chunks using a simple class outside the nested structure\n    class SimpleObject:\n        def __init__(self, **kwargs):\n            for key, value in kwargs.items():\n                setattr(self, key, value)\n    \n    # Build the chunks\n    for i in range(0, len(predefined_text), chunk_size):\n        text_chunk = predefined_text[i:i+chunk_size]\n        \n        # Create the nested structure using simple objects\n        delta = SimpleObject(content=text_chunk)\n        choice = SimpleObject(delta=delta)\n        chunk = SimpleObject(choices=[choice])\n        \n        chunks.append(chunk)\n    \n    return chunks\n\nif __name__ == \"__main__\":\n    print(\"## Testing streaming LLM\")\n    prompt = \"What's the meaning of life?\"\n    print(f\"## Prompt: {prompt}\")\n    # response = fake_stream_llm(prompt)\n    response = stream_llm(prompt)\n    print(f\"## Response: \")\n    for chunk in response:\n        if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content is not None:\n            chunk_content = chunk.choices[0].delta.content\n            # Print the incoming text without a newline (simulate real-time streaming)\n            print(chunk_content, end=\"\", flush=True)\n\n"
  },
  {
    "path": "cookbook/pocketflow-majority-vote/README.md",
    "content": "# Majority Vote Reasoning\n\nThis project demonstrates a majority vote implementation that enables LLMs to solve complex reasoning problems by aggregating multiple independent attempts. It's designed to improve problem-solving accuracy through consensus-based reasoning.\n\n## Features\n\n- Improves model reliability on complex problems through multiple attempts\n- Works with models like Claude 3.7 Sonnet\n- Solves problems that single attempts often fail on\n- Provides detailed reasoning traces for verification\n- Uses a consensus approach to reduce the impact of occasional reasoning errors\n\n## Getting Started\n\n1. Install the required packages:\n```bash\npip install -r requirements.txt\n```\n\n2. Set up your API key:\n```bash\nexport ANTHROPIC_API_KEY=\"your-api-key-here\"\n```\n\n3. Run a test problem to see majority voting in action:\n```bash\npython main.py\n```\n\n4. Try your own reasoning problem:\n```bash\npython main.py --problem \"Your complex reasoning problem here\" --tries 5\n```\n\n## How It Works\n\nThe implementation uses a MajorityVoteNode that processes multiple attempts and finds consensus:\n\n```mermaid\nflowchart LR\n    mv[MajorityVoteNode] \n```\n\nThe MajorityVoteNode:\n1. Makes multiple independent attempts to solve the same problem\n2. Collects structured answers from each attempt\n3. Determines the most frequent answer as the final solution\n4. Returns the consensus answer\n\nThis approach helps overcome occasional reasoning errors that might occur in individual attempts.\n\n## Example Problem\n\nExample Problem from [Quant Interview](https://www.youtube.com/watch?v=SCP7JptxPU0):\n\n```\nYou work at a shoe factory. In front of you, there are three pairs of shoes (six individual shoes) with the following sizes: two size 4s, two size 5s, and two size 6s. The factory defines an \"acceptable pair\" as two shoes that differ in size by a maximum of one size (e.g., a size 5 and a size 6 would be an acceptable pair). If you close your eyes and randomly pick three pairs of shoes without replacement, what is the probability that you end up drawing three acceptable pairs?\n```\n\nBelow is an example of how the majority vote approach uses Claude 3.7 Sonnet to solve this complex problem:\n\n```\n========================\nAll structured answers: ['0.333', '0.333', '0.333', '0.6', '0.333']\nMajority vote => 0.333\nFrequency => 4\n========================\n\n=== Final Answer ===\n0.333\n====================\n```\n\nThis shows that 4 out of 5 attempts yielded the same answer (0.333), which is chosen as the final solution.\n\n## Files\n\n- [`main.py`](./main.py): Implementation of the majority vote node and flow\n- [`utils.py`](./utils.py): Simple wrapper for calling the Anthropic model"
  },
  {
    "path": "cookbook/pocketflow-majority-vote/main.py",
    "content": "import argparse\nfrom pocketflow import BatchNode, Flow\nimport collections\nfrom utils import call_llm\nimport yaml\n\nclass MajorityVoteNode(BatchNode):\n    def prep(self, shared):\n        question = shared.get(\"question\", \"(No question provided)\")\n        attempts_count = shared.get(\"num_tries\", 3)\n        return [question for _ in range(attempts_count)]\n\n    def exec(self, single_question: str):\n        prompt = f\"\"\"\nYou are a helpful assistant. Please answer the user's question below.\nQuestion: {single_question}\n\nReturn strictly using the following YAML structure:\n```yaml\nthinking: |\n    (Your thinking process here)\nanswer: 0.123 # Final answer as a decimal with 3 decimal places\n```\"\"\"\n        raw_response = call_llm(prompt)\n        yaml_part = raw_response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        parsed = yaml.safe_load(yaml_part)\n\n        # Validate we have at least 'answer' field\n        if not isinstance(parsed, dict) or 'answer' not in parsed:\n            raise RuntimeError(f\"Missing 'answer' in YAML: {parsed}\")\n\n        # Return only the 'answer' field for the majority vote.\n        return str(parsed['answer'])\n    \n    def exec_fallback(self, prep_res, exc):\n        return None\n\n    def post(self, shared, prep_res, exec_res_list):\n        # Count frequency for non-None answers\n        exec_res_list = [res for res in exec_res_list if res is not None]\n        counter = collections.Counter(exec_res_list)\n        best_answer, freq = counter.most_common(1)[0]\n\n        # Store final\n        shared[\"majority_answer\"] = best_answer\n\n        print(\"========================\")\n        print(\"All structured answers:\", exec_res_list)\n        print(\"Majority vote =>\", best_answer)\n        print(\"Frequency =>\", freq)\n        print(\"========================\")\n\n        # End the flow\n        return \"end\"\n\nif __name__ == \"__main__\":\n    # Set up argument parser\n    parser = argparse.ArgumentParser(description=\"Run majority vote reasoning on a problem\")\n    parser.add_argument(\"--problem\", type=str, help=\"Your reasoning problem to solve\")\n    parser.add_argument(\"--tries\", type=int, default=5, help=\"Number of attempts to make (default: 5)\")\n    args = parser.parse_args()\n    \n    # Default problem if none provided\n    default_problem = \"\"\"You work at a shoe factory. In front of you, there are three pairs of shoes (six individual shoes) with the following sizes: two size 4s, two size 5s, and two size 6s. The factory defines an \"acceptable pair\" as two shoes that differ in size by a maximum of one size (e.g., a size 5 and a size 6 would be an acceptable pair). If you close your eyes and randomly pick three pairs of shoes without replacement, what is the probability that you end up drawing three acceptable pairs?\"\"\"\n    \n    shared = {\n        \"question\": args.problem if args.problem else default_problem,\n        \"num_tries\": args.tries\n    }\n\n    majority_node = MajorityVoteNode()\n    flow = Flow(start=majority_node)\n    flow.run(shared)\n\n    print(\"\\n=== Final Answer ===\")\n    print(shared[\"majority_answer\"])\n    print(\"====================\")"
  },
  {
    "path": "cookbook/pocketflow-majority-vote/requirements.txt",
    "content": "pocketflow>=0.0.1\nanthropic>=0.15.0\npyyaml>=6.0"
  },
  {
    "path": "cookbook/pocketflow-majority-vote/utils.py",
    "content": "from anthropic import Anthropic\nimport os\n\ndef call_llm(prompt):\n    client = Anthropic(api_key=os.environ.get(\"ANTHROPIC_API_KEY\", \"your-api-key\"))\n    response = client.messages.create(\n        model=\"claude-3-7-sonnet-20250219\",\n        max_tokens=10000,\n        messages=[\n            {\"role\": \"user\", \"content\": prompt}\n        ]\n    )\n    return response.content[0].text\n\nif __name__ == \"__main__\":\n    print(\"## Testing call_llm\")\n    prompt = \"In a few words, what is the meaning of life?\"\n    print(f\"## Prompt: {prompt}\")\n    response = call_llm(prompt)\n    print(f\"## Response: {response}\")"
  },
  {
    "path": "cookbook/pocketflow-map-reduce/README.md",
    "content": "# Resume Qualification - Map Reduce Example\n\nA PocketFlow example that demonstrates how to implement a Map-Reduce pattern for processing and evaluating resumes.\n\n## Features\n\n- Read and process multiple resume files using a Map-Reduce pattern\n- Evaluate each resume individually using an LLM with structured YAML output\n- Determine if candidates qualify for technical roles based on specific criteria\n- Aggregate results to generate qualification statistics and summaries\n\n## Getting Started\n\n1. Install the required dependencies:\n\n```bash\npip install -r requirements.txt\n```\n\n2. Set your OpenAI API key as an environment variable:\n\n```bash\nexport OPENAI_API_KEY=your_api_key_here\n```\n\n3. Run the application:\n\n```bash\npython main.py\n```\n\n## How It Works\n\nThe workflow follows a classic Map-Reduce pattern with three sequential nodes:\n\n```mermaid\nflowchart LR\n    ReadResumes[Map: Read Resumese] --> EvaluateResumes[Batch: Evaluate Resumes]\n    EvaluateResumes --> ReduceResults[Reduce: Aggregate Results]\n```\n\nHere's what each node does:\n\n1. **ReadResumesNode (Map Phase)**: Reads all resume files from the data directory and stores them in the shared data store\n2. **EvaluateResumesNode (Batch Processing)**: Processes each resume individually using an LLM to determine if candidates qualify\n3. **ReduceResultsNode (Reduce Phase)**: Aggregates evaluation results and produces a summary of qualified candidates\n\n## Files\n\n- [`main.py`](./main.py): Main entry point for running the resume qualification workflow\n- [`flow.py`](./flow.py): Defines the flow that connects the nodes\n- [`nodes.py`](./nodes.py): Contains the node classes for each step in the workflow\n- [`utils.py`](./utils.py): Utility functions including the LLM wrapper\n- [`requirements.txt`](./requirements.txt): Lists the required dependencies\n- [`data/`](./data/): Directory containing sample resume files for evaluation\n\n## Example Output\n\n```\nStarting resume qualification processing...\n\n===== Resume Qualification Summary =====\nTotal candidates evaluated: 5\nQualified candidates: 2 (40.0%)\n\nQualified candidates:\n- Emily Johnson\n- John Smith\n\nDetailed evaluation results:\n✗ Michael Williams (resume3.txt)\n✓ Emily Johnson (resume2.txt)\n✗ Lisa Chen (resume4.txt)\n✗ Robert Taylor (resume5.txt)\n✓ John Smith (resume1.txt)\n\nResume processing complete!\n```"
  },
  {
    "path": "cookbook/pocketflow-map-reduce/data/resume1.txt",
    "content": "John Smith\nSoftware Engineer\n\nEducation:\n- Master of Computer Science, Stanford University, 2018\n- Bachelor of Computer Science, MIT, 2016\n\nExperience:\n- Senior Software Engineer, Google, 2019-present\n  * Led the development of cloud infrastructure projects\n  * Implemented scalable solutions using Kubernetes and Docker\n  * Reduced system latency by 40% through optimization\n\n- Software Developer, Microsoft, 2016-2019\n  * Worked on Azure cloud services\n  * Built RESTful APIs for enterprise solutions\n\nSkills:\n- Programming: Python, Java, C++, JavaScript\n- Technologies: Docker, Kubernetes, AWS, Azure\n- Tools: Git, Jenkins, Jira\n\nProjects:\n- Developed a recommendation engine that increased user engagement by 25%\n- Created a sentiment analysis tool using NLP techniques "
  },
  {
    "path": "cookbook/pocketflow-map-reduce/data/resume2.txt",
    "content": "Emily Johnson\nData Scientist\n\nEducation:\n- Ph.D. in Statistics, UC Berkeley, 2020\n- Master of Science in Mathematics, UCLA, 2016\n\nExperience:\n- Data Scientist, Netflix, 2020-present\n  * Developed machine learning models for content recommendation\n  * Implemented A/B testing frameworks to optimize user experience\n  * Collaborated with product teams to define metrics and KPIs\n\n- Data Analyst, Amazon, 2016-2020\n  * Analyzed user behavior patterns to improve conversion rates\n  * Created dashboards and visualizations for executive decision-making\n\nSkills:\n- Programming: R, Python, SQL\n- Machine Learning: TensorFlow, PyTorch, scikit-learn\n- Data Visualization: Tableau, PowerBI, matplotlib\n\nPublications:\n- \"Advances in Recommendation Systems\" - Journal of Machine Learning, 2021\n- \"Statistical Methods for Big Data\" - Conference on Data Science, 2019 "
  },
  {
    "path": "cookbook/pocketflow-map-reduce/data/resume3.txt",
    "content": "Michael Williams\nMarketing Manager\n\nEducation:\n- MBA, Harvard Business School, 2015\n- Bachelor of Arts in Communications, NYU, 2010\n\nExperience:\n- Marketing Director, Apple, 2018-present\n  * Managed a team of 15 marketing professionals\n  * Developed and executed global marketing campaigns\n  * Increased brand awareness by 30% through digital initiatives\n\n- Marketing Manager, Coca-Cola, 2015-2018\n  * Led product launches across North America\n  * Coordinated with external agencies on advertising campaigns\n\nSkills:\n- Digital Marketing: SEO, SEM, Social Media Marketing\n- Analytics: Google Analytics, Adobe Analytics\n- Tools: HubSpot, Salesforce, Marketo\n\nAchievements:\n- Marketing Excellence Award, 2020\n- Led campaign that won Cannes Lions Award, 2019 "
  },
  {
    "path": "cookbook/pocketflow-map-reduce/data/resume4.txt",
    "content": "Lisa Chen\nFrontend Developer\n\nEducation:\n- Bachelor of Fine Arts, Rhode Island School of Design, 2019\n\nExperience:\n- UI/UX Designer, Airbnb, 2020-present\n  * Designed user interfaces for mobile and web applications\n  * Created wireframes and prototypes for new features\n  * Conducted user research and usability testing\n\n- Junior Designer, Freelance, 2019-2020\n  * Worked with small businesses on branding and website design\n  * Developed responsive web designs using HTML, CSS, and JavaScript\n\nSkills:\n- Design: Figma, Sketch, Adobe XD\n- Development: HTML, CSS, JavaScript, React\n- Tools: Git, Zeplin\n\nPortfolio Highlights:\n- Redesigned checkout flow resulting in 15% conversion increase\n- Created custom icon set for mobile application\n- Designed responsive email templates\n\nCertifications:\n- UI/UX Design Certificate, Coursera, 2019 "
  },
  {
    "path": "cookbook/pocketflow-map-reduce/data/resume5.txt",
    "content": "Robert Taylor\nSales Representative\n\nEducation:\n- Bachelor of Business Administration, University of Texas, 2017\n\nExperience:\n- Account Executive, Salesforce, 2019-present\n  * Exceeded sales targets by 25% for three consecutive quarters\n  * Managed a portfolio of 50+ enterprise clients\n  * Developed and implemented strategic account plans\n\n- Sales Associate, Oracle, 2017-2019\n  * Generated new business opportunities through cold calling\n  * Assisted senior sales representatives with client presentations\n\nSkills:\n- CRM Systems: Salesforce, HubSpot\n- Communication: Negotiation, Public Speaking\n- Tools: Microsoft Office Suite, Google Workspace\n\nAchievements:\n- Top Sales Representative Award, Q2 2020\n- President's Club, 2021\n\nInterests:\n- Volunteer sales coach for local small businesses\n- Member of Toastmasters International "
  },
  {
    "path": "cookbook/pocketflow-map-reduce/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import ReadResumesNode, EvaluateResumesNode, ReduceResultsNode\n\ndef create_resume_processing_flow():\n    \"\"\"Create a map-reduce flow for processing resumes.\"\"\"\n    # Create nodes\n    read_resumes_node = ReadResumesNode()\n    evaluate_resumes_node = EvaluateResumesNode()\n    reduce_results_node = ReduceResultsNode()\n    \n    # Connect nodes\n    read_resumes_node >> evaluate_resumes_node >> reduce_results_node\n    \n    # Create flow\n    return Flow(start=read_resumes_node)"
  },
  {
    "path": "cookbook/pocketflow-map-reduce/main.py",
    "content": "from flow import create_resume_processing_flow\n\ndef main():\n    # Initialize shared store\n    shared = {}\n    \n    # Create the resume processing flow\n    resume_flow = create_resume_processing_flow()\n    \n    # Run the flow\n    print(\"Starting resume qualification processing...\")\n    resume_flow.run(shared)\n    \n    # Display final summary information (additional to what's already printed in ReduceResultsNode)\n    if \"summary\" in shared:\n        print(\"\\nDetailed evaluation results:\")\n        for filename, evaluation in shared.get(\"evaluations\", {}).items():\n            qualified = \"✓\" if evaluation.get(\"qualifies\", False) else \"✗\"\n            name = evaluation.get(\"candidate_name\", \"Unknown\")\n            print(f\"{qualified} {name} ({filename})\")\n    \n    print(\"\\nResume processing complete!\")\n\nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "cookbook/pocketflow-map-reduce/nodes.py",
    "content": "from pocketflow import Node, BatchNode\nfrom utils import call_llm\nimport yaml\nimport os\n\nclass ReadResumesNode(Node):\n    \"\"\"Map phase: Read all resumes from the data directory into shared storage.\"\"\"\n    \n    def exec(self, _):\n        resume_files = {}\n        data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), \"data\")\n        \n        for filename in os.listdir(data_dir):\n            if filename.endswith(\".txt\"):\n                file_path = os.path.join(data_dir, filename)\n                with open(file_path, 'r', encoding='utf-8') as file:\n                    resume_files[filename] = file.read()\n        \n        return resume_files\n    \n    def post(self, shared, prep_res, exec_res):\n        shared[\"resumes\"] = exec_res\n        return \"default\"\n\n\nclass EvaluateResumesNode(BatchNode):\n    \"\"\"Batch processing: Evaluate each resume to determine if the candidate qualifies.\"\"\"\n    \n    def prep(self, shared):\n        return list(shared[\"resumes\"].items())\n    \n    def exec(self, resume_item):\n        \"\"\"Evaluate a single resume.\"\"\"\n        filename, content = resume_item\n        \n        prompt = f\"\"\"\nEvaluate the following resume and determine if the candidate qualifies for an advanced technical role.\nCriteria for qualification:\n- At least a bachelor's degree in a relevant field\n- At least 3 years of relevant work experience\n- Strong technical skills relevant to the position\n\nResume:\n{content}\n\nReturn your evaluation in YAML format:\n```yaml\ncandidate_name: [Name of the candidate]\nqualifies: [true/false]\nreasons:\n  - [First reason for qualification/disqualification]\n  - [Second reason, if applicable]\n```\n\"\"\"\n        response = call_llm(prompt)\n        \n        # Extract YAML content\n        yaml_content = response.split(\"```yaml\")[1].split(\"```\")[0].strip() if \"```yaml\" in response else response\n        result = yaml.safe_load(yaml_content)\n        \n        return (filename, result)\n\n    def post(self, shared, prep_res, exec_res_list):\n        shared[\"evaluations\"] = {filename: result for filename, result in exec_res_list}\n        return \"default\"\n\n\nclass ReduceResultsNode(Node):\n    \"\"\"Reduce node: Count and print out how many candidates qualify.\"\"\"\n    \n    def prep(self, shared):\n        return shared[\"evaluations\"]\n    \n    def exec(self, evaluations):\n        qualified_count = 0\n        total_count = len(evaluations)\n        qualified_candidates = []\n        \n        for filename, evaluation in evaluations.items():\n            if evaluation.get(\"qualifies\", False):\n                qualified_count += 1\n                qualified_candidates.append(evaluation.get(\"candidate_name\", \"Unknown\"))\n        \n        summary = {\n            \"total_candidates\": total_count,\n            \"qualified_count\": qualified_count,\n            \"qualified_percentage\": round(qualified_count / total_count * 100, 1) if total_count > 0 else 0,\n            \"qualified_names\": qualified_candidates\n        }\n        \n        return summary\n    \n    def post(self, shared, prep_res, exec_res):\n        shared[\"summary\"] = exec_res\n        \n        print(\"\\n===== Resume Qualification Summary =====\")\n        print(f\"Total candidates evaluated: {exec_res['total_candidates']}\")\n        print(f\"Qualified candidates: {exec_res['qualified_count']} ({exec_res['qualified_percentage']}%)\")\n        \n        if exec_res['qualified_names']:\n            print(\"\\nQualified candidates:\")\n            for name in exec_res['qualified_names']:\n                print(f\"- {name}\")\n        \n        return \"default\" "
  },
  {
    "path": "cookbook/pocketflow-map-reduce/requirements.txt",
    "content": "pocketflow>=0.0.1\nopenai>=1.0.0\npyyaml>=6.0 "
  },
  {
    "path": "cookbook/pocketflow-map-reduce/utils.py",
    "content": "import os\nfrom openai import OpenAI\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n\n# Example usage\nif __name__ == \"__main__\":\n    print(call_llm(\"Tell me a short joke\")) "
  },
  {
    "path": "cookbook/pocketflow-mcp/README.md",
    "content": "# PocketFlow MCP Demo\n\nThis project shows how to build an agent that performs addition using PocketFlow and Model Context Protocol (MCP). It presents a comparison between using MCP and basic function calling approaches.\n\nThis implementation is based  on the tutorial: [MCP Simply Explained: Function Calling Rebranded or Genuine Breakthrough?](https://zacharyhuang.substack.com/p/mcp-simply-explained-function-calling)\n\n## Features\n\n- Mathematical operation tools through a simple terminal interface\n- Integration with Model Context Protocol (MCP)\n- Comparison between MCP and direct function calling\n- **Simple toggle** between MCP and local function calling\n\n## How to Run\n\n1. Set your API key:\n   ```bash\n   export OPENAI_API_KEY=\"your-api-key-here\"\n   ```\n   Or update it directly in `utils.py`\n\n2. Install and run:\n   ```bash\n   pip install -r requirements.txt\n   python main.py\n   ```\n\n## MCP vs Function Calling\n\nTo compare both approaches, this demo provides local function alternatives that don't require MCP:\n\n- **Toggle with a simple flag:** Set `MCP = True` or `MCP = False` at the top of `utils.py` to switch between MCP and local implementations.\n- No code changes needed! The application automatically uses either:\n  - MCP server tools when `MCP = True`\n  - Local function implementations when `MCP = False`\n\nThis allows you to see the difference between the two approaches while keeping the same workflow.\n\n### Function Calling\n- Functions are directly embedded in application code\n- Each new tool requires modifying the application\n- Tools are defined within the application itself\n\n### MCP Approach\n- Tools live in separate MCP servers\n- Standard protocol for all tool interactions\n- New tools can be added without changing the agent\n- AI can interact with tools through a consistent interface\n\n## How It Works\n\n```mermaid\nflowchart LR\n    tools[GetToolsNode] -->|decide| decide[DecideToolNode]\n    decide -->|execute| execute[ExecuteToolNode]\n```\n\nThe agent uses PocketFlow to create a workflow where:\n1. It takes user input about numbers\n2. Connects to the MCP server for mathematical operations (or uses local functions based on the `MCP` flag)\n3. Returns the result\n\n## Files\n\n- [`main.py`](./main.py): Implementation of the addition agent using PocketFlow\n- [`utils.py`](./utils.py): Helper functions for API calls and MCP integration\n- [`simple_server.py`](./simple_server.py): MCP server that provides the addition tool\n"
  },
  {
    "path": "cookbook/pocketflow-mcp/main.py",
    "content": "from pocketflow import Node, Flow\nfrom utils import call_llm, get_tools, call_tool\nimport yaml\nimport sys\n\nclass GetToolsNode(Node):\n    def prep(self, shared):\n        \"\"\"Initialize and get tools\"\"\"\n        # The question is now passed from main via shared\n        print(\"🔍 Getting available tools...\")\n        return \"simple_server.py\"\n\n    def exec(self, server_path):\n        \"\"\"Retrieve tools from the MCP server\"\"\"\n        tools = get_tools(server_path)\n        return tools\n\n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store tools and process to decision node\"\"\"\n        tools = exec_res\n        shared[\"tools\"] = tools\n        \n        # Format tool information for later use\n        tool_info = []\n        for i, tool in enumerate(tools, 1):\n            properties = tool.inputSchema.get('properties', {})\n            required = tool.inputSchema.get('required', [])\n            \n            params = []\n            for param_name, param_info in properties.items():\n                param_type = param_info.get('type', 'unknown')\n                req_status = \"(Required)\" if param_name in required else \"(Optional)\"\n                params.append(f\"    - {param_name} ({param_type}): {req_status}\")\n            \n            tool_info.append(f\"[{i}] {tool.name}\\n  Description: {tool.description}\\n  Parameters:\\n\" + \"\\n\".join(params))\n        \n        shared[\"tool_info\"] = \"\\n\".join(tool_info)\n        return \"decide\"\n\nclass DecideToolNode(Node):\n    def prep(self, shared):\n        \"\"\"Prepare the prompt for LLM to process the question\"\"\"\n        tool_info = shared[\"tool_info\"]\n        question = shared[\"question\"]\n        \n        prompt = f\"\"\"\n### CONTEXT\nYou are an assistant that can use tools via Model Context Protocol (MCP).\n\n### ACTION SPACE\n{tool_info}\n\n### TASK\nAnswer this question: \"{question}\"\n\n## NEXT ACTION\nAnalyze the question, extract any numbers or parameters, and decide which tool to use.\nReturn your response in this format:\n\n```yaml\nthinking: |\n    <your step-by-step reasoning about what the question is asking and what numbers to extract>\ntool: <name of the tool to use>\nreason: <why you chose this tool>\nparameters:\n    <parameter_name>: <parameter_value>\n    <parameter_name>: <parameter_value>\n```\nIMPORTANT: \n1. Extract numbers from the question properly\n2. Use proper indentation (4 spaces) for multi-line fields\n3. Use the | character for multi-line text fields\n\"\"\"\n        return prompt\n\n    def exec(self, prompt):\n        \"\"\"Call LLM to process the question and decide which tool to use\"\"\"\n        print(\"🤔 Analyzing question and deciding which tool to use...\")\n        response = call_llm(prompt)\n        return response\n\n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Extract decision from YAML and save to shared context\"\"\"\n        try:\n            yaml_str = exec_res.split(\"```yaml\")[1].split(\"```\")[0].strip()\n            decision = yaml.safe_load(yaml_str)\n            \n            shared[\"tool_name\"] = decision[\"tool\"]\n            shared[\"parameters\"] = decision[\"parameters\"]\n            shared[\"thinking\"] = decision.get(\"thinking\", \"\")\n            \n            print(f\"💡 Selected tool: {decision['tool']}\")\n            print(f\"🔢 Extracted parameters: {decision['parameters']}\")\n            \n            return \"execute\"\n        except Exception as e:\n            print(f\"❌ Error parsing LLM response: {e}\")\n            print(\"Raw response:\", exec_res)\n            return None\n\nclass ExecuteToolNode(Node):\n    def prep(self, shared):\n        \"\"\"Prepare tool execution parameters\"\"\"\n        return shared[\"tool_name\"], shared[\"parameters\"]\n\n    def exec(self, inputs):\n        \"\"\"Execute the chosen tool\"\"\"\n        tool_name, parameters = inputs\n        print(f\"🔧 Executing tool '{tool_name}' with parameters: {parameters}\")\n        result = call_tool(\"simple_server.py\", tool_name, parameters)\n        return result\n\n    def post(self, shared, prep_res, exec_res):\n        print(f\"\\n✅ Final Answer: {exec_res}\")\n        return \"done\"\n\n\nif __name__ == \"__main__\":\n    # Default question\n    default_question = \"What is 982713504867129384651 plus 73916582047365810293746529?\"\n    \n    # Get question from command line if provided with --\n    question = default_question\n    for arg in sys.argv[1:]:\n        if arg.startswith(\"--\"):\n            question = arg[2:]\n            break\n    \n    print(f\"🤔 Processing question: {question}\")\n    \n    # Create nodes\n    get_tools_node = GetToolsNode()\n    decide_node = DecideToolNode()\n    execute_node = ExecuteToolNode()\n    \n    # Connect nodes\n    get_tools_node - \"decide\" >> decide_node\n    decide_node - \"execute\" >> execute_node\n    \n    # Create and run flow\n    flow = Flow(start=get_tools_node)\n    shared = {\"question\": question}\n    flow.run(shared)"
  },
  {
    "path": "cookbook/pocketflow-mcp/requirements.txt",
    "content": "pocketflow>=0.0.1\nopenai>=1.0.0\nfastmcp\npyyaml\n"
  },
  {
    "path": "cookbook/pocketflow-mcp/simple_server.py",
    "content": "from fastmcp import FastMCP\n\n# Create a named server\nmcp = FastMCP(\"Math Operations Server\")\n\n# Define mathematical operation tools\n@mcp.tool()\ndef add(a: int, b: int) -> int:\n    \"\"\"Add two numbers together\"\"\"\n    return a + b\n\n@mcp.tool()\ndef subtract(a: int, b: int) -> int:\n    \"\"\"Subtract b from a\"\"\"\n    return a - b\n\n@mcp.tool()\ndef multiply(a: int, b: int) -> int:\n    \"\"\"Multiply two numbers together\"\"\"\n    return a * b\n\n@mcp.tool()\ndef divide(a: int, b: int) -> float:\n    \"\"\"Divide a by b\"\"\"\n    if b == 0:\n        raise ValueError(\"Division by zero is not allowed\")\n    return a / b\n\n# Start the server\nif __name__ == \"__main__\":\n    mcp.run()"
  },
  {
    "path": "cookbook/pocketflow-mcp/utils.py",
    "content": "from openai import OpenAI\nimport os\nimport asyncio\nfrom mcp import ClientSession, StdioServerParameters\nfrom mcp.client.stdio import stdio_client\n\n# Global flag to control whether to use MCP or local implementation\nMCP = False\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n\ndef get_tools(server_script_path=None):\n    \"\"\"Get available tools, either from MCP server or locally based on MCP global setting.\"\"\"\n    if MCP:\n        return mcp_get_tools(server_script_path)\n    else:\n        return local_get_tools(server_script_path)\n    \ndef mcp_get_tools(server_script_path):\n    \"\"\"Get available tools from an MCP server.\n    \"\"\"\n    async def _get_tools():\n        server_params = StdioServerParameters(\n            command=\"python\",\n            args=[server_script_path]\n        )\n        \n        async with stdio_client(server_params) as (read, write):\n            async with ClientSession(read, write) as session:\n                await session.initialize()\n                tools_response = await session.list_tools()\n                return tools_response.tools\n    \n    return asyncio.run(_get_tools())\n\ndef local_get_tools(server_script_path=None):\n    \"\"\"A simple dummy implementation of get_tools without MCP.\"\"\"\n    tools = [\n        {\n            \"name\": \"add\",\n            \"description\": \"Add two numbers together\",\n            \"inputSchema\": {\n                \"properties\": {\n                    \"a\": {\"type\": \"integer\"},\n                    \"b\": {\"type\": \"integer\"}\n                },\n                \"required\": [\"a\", \"b\"]\n            }\n        },\n        {\n            \"name\": \"subtract\",\n            \"description\": \"Subtract b from a\",\n            \"inputSchema\": {\n                \"properties\": {\n                    \"a\": {\"type\": \"integer\"},\n                    \"b\": {\"type\": \"integer\"}\n                },\n                \"required\": [\"a\", \"b\"]\n            }\n        },\n        {\n            \"name\": \"multiply\",\n            \"description\": \"Multiply two numbers together\",\n            \"inputSchema\": {\n                \"properties\": {\n                    \"a\": {\"type\": \"integer\"},\n                    \"b\": {\"type\": \"integer\"}\n                },\n                \"required\": [\"a\", \"b\"]\n            }\n        },\n        {\n            \"name\": \"divide\",\n            \"description\": \"Divide a by b\",\n            \"inputSchema\": {\n                \"properties\": {\n                    \"a\": {\"type\": \"integer\"},\n                    \"b\": {\"type\": \"integer\"}\n                },\n                \"required\": [\"a\", \"b\"]\n            }\n        }\n    ]\n\n    class DictObject(dict):\n        \"\"\"A simple class that behaves both as a dictionary and as an object with attributes.\"\"\"\n        def __init__(self, data):\n            super().__init__(data)\n            for key, value in data.items():\n                if isinstance(value, dict):\n                    self[key] = DictObject(value)\n                elif isinstance(value, list) and value and isinstance(value[0], dict):\n                    self[key] = [DictObject(item) for item in value]\n        \n        def __getattr__(self, key):\n            try:\n                return self[key]\n            except KeyError:\n                raise AttributeError(f\"'DictObject' object has no attribute '{key}'\")\n\n    return [DictObject(tool) for tool in tools]\n\ndef call_tool(server_script_path=None, tool_name=None, arguments=None):\n    \"\"\"Call a tool, either from MCP server or locally based on MCP global setting.\"\"\"\n    if MCP:\n        return mcp_call_tool(server_script_path, tool_name, arguments)\n    else:\n        return local_call_tool(server_script_path, tool_name, arguments)\n    \ndef mcp_call_tool(server_script_path=None, tool_name=None, arguments=None):\n    \"\"\"Call a tool on an MCP server.\n    \"\"\"\n    async def _call_tool():\n        server_params = StdioServerParameters(\n            command=\"python\",\n            args=[server_script_path]\n        )\n        \n        async with stdio_client(server_params) as (read, write):\n            async with ClientSession(read, write) as session:\n                await session.initialize()\n                result = await session.call_tool(tool_name, arguments)\n                return result.content[0].text\n    \n    return asyncio.run(_call_tool())\n\ndef local_call_tool(server_script_path=None, tool_name=None, arguments=None):\n    \"\"\"A simple dummy implementation of call_tool without MCP.\"\"\"\n    # Simple implementation of tools\n    if tool_name == \"add\":\n        if \"a\" in arguments and \"b\" in arguments:\n            return arguments[\"a\"] + arguments[\"b\"]\n        else:\n            return \"Error: Missing required arguments 'a' or 'b'\"\n    elif tool_name == \"subtract\":\n        if \"a\" in arguments and \"b\" in arguments:\n            return arguments[\"a\"] - arguments[\"b\"]\n        else:\n            return \"Error: Missing required arguments 'a' or 'b'\"\n    elif tool_name == \"multiply\":\n        if \"a\" in arguments and \"b\" in arguments:\n            return arguments[\"a\"] * arguments[\"b\"]\n        else:\n            return \"Error: Missing required arguments 'a' or 'b'\"\n    elif tool_name == \"divide\":\n        if \"a\" in arguments and \"b\" in arguments:\n            if arguments[\"b\"] == 0:\n                return \"Error: Division by zero is not allowed\"\n            return arguments[\"a\"] / arguments[\"b\"]\n        else:\n            return \"Error: Missing required arguments 'a' or 'b'\"\n    else:\n        return f\"Error: Unknown tool '{tool_name}'\"\n\nif __name__ == \"__main__\":\n    print(\"=== Testing call_llm ===\")\n    prompt = \"In a few words, what is the meaning of life?\"\n    print(f\"Prompt: {prompt}\")\n    response = call_llm(prompt)\n    print(f\"Response: {response}\")\n\n        # Find available tools\n    print(\"=== Finding available tools ===\")\n    tools = get_tools(\"simple_server.py\")\n    \n    # Print tool information nicely formatted\n    for i, tool in enumerate(tools, 1):\n        print(f\"\\nTool {i}: {tool.name}\")\n        print(\"=\" * (len(tool.name) + 8))\n        print(f\"Description: {tool.description}\")\n        \n        # Parameters section\n        print(\"Parameters:\")\n        properties = tool.inputSchema.get('properties', {})\n        required = tool.inputSchema.get('required', [])\n        \n        # No parameters case\n        if not properties:\n            print(\"  None\")\n        \n        # Print each parameter with its details\n        for param_name, param_info in properties.items():\n            param_type = param_info.get('type', 'unknown')\n            req_status = \"(Required)\" if param_name in required else \"(Optional)\"\n            print(f\"  • {param_name}: {param_type} {req_status}\")\n    \n    # Call a tool\n    print(\"\\n=== Calling the add tool ===\")\n    a, b = 5, 3\n    result = call_tool(\"simple_server.py\", \"add\", {\"a\": a, \"b\": b})\n    print(f\"Result of {a} + {b} = {result}\")\n    \n    # You can easily call with different parameters\n    a, b = 10, 20\n    result = call_tool(\"simple_server.py\", \"add\", {\"a\": a, \"b\": b})\n    print(f\"Result of {a} + {b} = {result}\")\n\n"
  },
  {
    "path": "cookbook/pocketflow-multi-agent/README.md",
    "content": "# Multi-Agent Taboo Game\n\nA PocketFlow example that demonstrates how to implement asynchronous multi-agent communication using the Taboo word guessing game.\n\n## Features\n\n- Implement asynchronous communication between two AI agents (Hinter and Guesser)\n- Use AsyncNode for non-blocking agent interactions\n- Create dynamic conversation flow through asyncio message queues\n- Demonstrate complex turn-based game mechanics with LLMs\n- Automatically terminate the game when the correct word is guessed\n\n## Getting Started\n\n1. Install the required dependencies:\n\n```bash\npip install -r requirements.txt\n```\n\n2. Set your OpenAI API key as an environment variable:\n\n```bash\nexport OPENAI_API_KEY=your_api_key_here\n```\n\n3. Run the application:\n\n```bash\npython main.py\n```\n\n## How It Works\n\nThe workflow follows an asynchronous multi-agent communication pattern:\n\n```mermaid\nflowchart LR\n    AsyncHinter[AsyncHinter Node] <--> MessageQueue{Message Queue}\n    MessageQueue <--> AsyncGuesser[AsyncGuesser Node]\n```\n\nHere's what each component does:\n\n1. **AsyncHinter Node**: Generates hints about the target word while avoiding forbidden words\n2. **AsyncGuesser Node**: Makes guesses based on the hints received from the Hinter\n3. **Message Queue**: Facilitates asynchronous communication between the agents\n\n## Files\n\n- [`main.py`](./main.py): Main entry point implementing the AsyncHinter and AsyncGuesser nodes and game flow\n- [`utils.py`](./utils.py): Utility functions including LLM wrappers for generating hints and guesses\n- [`requirements.txt`](./requirements.txt): Lists the required dependencies\n\n## Example Output\n\n```\n=========== Taboo Game Starting! ===========\nTarget word: nostalgic\nForbidden words: ['memory', 'past', 'remember', 'feeling', 'longing']\n============================================\n\nHinter: Here's your hint - Sentiment for earlier times.\nGuesser: I guess it's - Nostalgia\n\nHinter: Here's your hint - Sentiment for earlier times.\nGuesser: I guess it's - Reminiscence\n\nHinter: Here's your hint - Yearning for days gone by.\nGuesser: I guess it's - Sentimentality\n\nHinter: Here's your hint - Reliving cherished moments or experiences.\nGuesser: I guess it's - Memories\n\nHinter: Here's your hint - Recollection of cherished experiences.\nGuesser: I guess it's - Reflection\n\nHinter: Here's your hint - Yearning for earlier times.\nGuesser: I guess it's - Longing\n\nHinter: Here's your hint - Sentiment for earlier times.\nGuesser: I guess it's - Nostalgic\nGame Over - Correct guess!"
  },
  {
    "path": "cookbook/pocketflow-multi-agent/main.py",
    "content": "import asyncio\nfrom pocketflow import AsyncNode, AsyncFlow\nfrom utils import call_llm\n\nclass AsyncHinter(AsyncNode):\n    async def prep_async(self, shared):\n        # Wait for message from guesser (or empty string at start)\n        guess = await shared[\"hinter_queue\"].get()\n        if guess == \"GAME_OVER\":\n            return None\n        return shared[\"target_word\"], shared[\"forbidden_words\"], shared.get(\"past_guesses\", [])\n\n    async def exec_async(self, inputs):\n        if inputs is None:\n            return None\n        target, forbidden, past_guesses = inputs\n        prompt = f\"Generate hint for '{target}'\\nForbidden words: {forbidden}\"\n        if past_guesses:\n            prompt += f\"\\nPrevious wrong guesses: {past_guesses}\\nMake hint more specific.\"\n        prompt += \"\\nUse at most 5 words.\"\n        \n        hint = call_llm(prompt)\n        print(f\"\\nHinter: Here's your hint - {hint}\")\n        return hint\n\n    async def post_async(self, shared, prep_res, exec_res):\n        if exec_res is None:\n            return \"end\"\n        # Send hint to guesser\n        await shared[\"guesser_queue\"].put(exec_res)\n        return \"continue\"\n\nclass AsyncGuesser(AsyncNode):\n    async def prep_async(self, shared):\n        # Wait for hint from hinter\n        hint = await shared[\"guesser_queue\"].get()\n        return hint, shared.get(\"past_guesses\", [])\n\n    async def exec_async(self, inputs):\n        hint, past_guesses = inputs\n        prompt = f\"Given hint: {hint}, past wrong guesses: {past_guesses}, make a new guess. Directly reply a single word:\"\n        guess = call_llm(prompt)\n        print(f\"Guesser: I guess it's - {guess}\")\n        return guess\n\n    async def post_async(self, shared, prep_res, exec_res):\n        # Check if guess is correct\n        if exec_res.lower() == shared[\"target_word\"].lower():\n            print(\"Game Over - Correct guess!\")\n            await shared[\"hinter_queue\"].put(\"GAME_OVER\")\n            return \"end\"\n            \n        # Store the guess in shared state\n        if \"past_guesses\" not in shared:\n            shared[\"past_guesses\"] = []\n        shared[\"past_guesses\"].append(exec_res)\n        \n        # Send guess to hinter\n        await shared[\"hinter_queue\"].put(exec_res)\n        return \"continue\"\n\nasync def main():\n    # Set up game\n    shared = {\n        \"target_word\": \"nostalgic\",\n        \"forbidden_words\": [\"memory\", \"past\", \"remember\", \"feeling\", \"longing\"],\n        \"hinter_queue\": asyncio.Queue(),\n        \"guesser_queue\": asyncio.Queue()\n    }\n    \n    print(\"=========== Taboo Game Starting! ===========\")\n    print(f\"Target word: {shared['target_word']}\")\n    print(f\"Forbidden words: {shared['forbidden_words']}\")\n    print(\"============================================\")\n\n    # Initialize by sending empty guess to hinter\n    await shared[\"hinter_queue\"].put(\"\")\n\n    # Create nodes and flows\n    hinter = AsyncHinter()\n    guesser = AsyncGuesser()\n\n    # Set up flows\n    hinter_flow = AsyncFlow(start=hinter)\n    guesser_flow = AsyncFlow(start=guesser)\n\n    # Connect nodes to themselves for looping\n    hinter - \"continue\" >> hinter\n    guesser - \"continue\" >> guesser\n\n    # Run both agents concurrently\n    await asyncio.gather(\n        hinter_flow.run_async(shared),\n        guesser_flow.run_async(shared)\n    )\n    \n    print(\"=========== Game Complete! ===========\")\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "cookbook/pocketflow-multi-agent/requirements.txt",
    "content": "pocketflow>=0.0.1\nopenai>=1.0.0\npyyaml>=6.0 "
  },
  {
    "path": "cookbook/pocketflow-multi-agent/utils.py",
    "content": "import os\nfrom openai import OpenAI\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    r = client.chat.completions.create(\n        model=\"gpt-4o-mini\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n\n# Example usage\nif __name__ == \"__main__\":\n    print(call_llm(\"Tell me a short joke\")) "
  },
  {
    "path": "cookbook/pocketflow-nested-batch/README.md",
    "content": "# PocketFlow Nested BatchFlow Example\n\nThis example demonstrates Nested BatchFlow using a simple school grades calculator.\n\n## What this Example Does\n\nCalculates average grades for:\n1. Each student in a class\n2. Each class in the school\n\n## Structure\n```\nschool/\n├── class_a/\n│   ├── student1.txt  (grades: 7.5, 8.0, 9.0)\n│   └── student2.txt  (grades: 8.5, 7.0, 9.5)\n└── class_b/\n    ├── student3.txt  (grades: 6.5, 8.5, 7.0)\n    └── student4.txt  (grades: 9.0, 9.5, 8.0)\n```\n\n## How it Works\n\n1. **Outer BatchFlow (SchoolBatchFlow)**\n   - Processes each class folder\n   - Returns parameters like: `{\"class\": \"class_a\"}`\n\n2. **Inner BatchFlow (ClassBatchFlow)**\n   - Processes each student file in a class\n   - Returns parameters like: `{\"student\": \"student1.txt\"}`\n\n3. **Base Flow**\n   - Loads student grades\n   - Calculates average\n   - Saves result\n\n## Running the Example\n\n```bash\npip install -r requirements.txt\npython main.py\n```\n\n## Expected Output\n\n```\nProcessing class_a...\n- student1: Average = 8.2\n- student2: Average = 8.3\nClass A Average: 8.25\n\nProcessing class_b...\n- student3: Average = 7.3\n- student4: Average = 8.8\nClass B Average: 8.05\n\nSchool Average: 8.15\n```\n\n## Key Concepts\n\n1. **Nested BatchFlow**: One BatchFlow inside another\n2. **Parameter Inheritance**: Inner flow gets parameters from outer flow\n3. **Hierarchical Processing**: Process data in a tree-like structure "
  },
  {
    "path": "cookbook/pocketflow-nested-batch/flow.py",
    "content": "import os\nfrom pocketflow import Flow, BatchFlow\nfrom nodes import LoadGrades, CalculateAverage\n\ndef create_base_flow():\n    \"\"\"Create base flow for processing one student's grades.\"\"\"\n    # Create nodes\n    load = LoadGrades()\n    calc = CalculateAverage()\n    \n    # Connect nodes\n    load - \"calculate\" >> calc\n    \n    # Create and return flow\n    return Flow(start=load)\n\nclass ClassBatchFlow(BatchFlow):\n    \"\"\"BatchFlow for processing all students in a class.\"\"\"\n    \n    def prep(self, shared):\n        \"\"\"Generate parameters for each student in the class.\"\"\"\n        # Get class folder from parameters\n        class_folder = self.params[\"class\"]\n        \n        # List all student files\n        class_path = os.path.join(\"school\", class_folder)\n        students = [f for f in os.listdir(class_path) if f.endswith(\".txt\")]\n        \n        # Return parameters for each student\n        return [{\"student\": student} for student in students]\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Calculate and print class average.\"\"\"\n        class_name = self.params[\"class\"]\n        class_results = shared[\"results\"][class_name]\n        class_average = sum(class_results.values()) / len(class_results)\n        \n        print(f\"Class {class_name.split('_')[1].upper()} Average: {class_average:.2f}\\n\")\n        return \"default\"\n\nclass SchoolBatchFlow(BatchFlow):\n    \"\"\"BatchFlow for processing all classes in the school.\"\"\"\n    \n    def prep(self, shared):\n        \"\"\"Generate parameters for each class.\"\"\"\n        # List all class folders\n        classes = [d for d in os.listdir(\"school\") if os.path.isdir(os.path.join(\"school\", d))]\n        \n        # Return parameters for each class\n        return [{\"class\": class_name} for class_name in classes]\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Calculate and print school average.\"\"\"\n        all_grades = []\n        for class_results in shared[\"results\"].values():\n            all_grades.extend(class_results.values())\n            \n        school_average = sum(all_grades) / len(all_grades)\n        print(f\"School Average: {school_average:.2f}\")\n        return \"default\"\n\ndef create_flow():\n    \"\"\"Create the complete nested batch processing flow.\"\"\"\n    # Create base flow for single student\n    base_flow = create_base_flow()\n    \n    # Wrap in ClassBatchFlow for processing all students in a class\n    class_flow = ClassBatchFlow(start=base_flow)\n    \n    # Wrap in SchoolBatchFlow for processing all classes\n    school_flow = SchoolBatchFlow(start=class_flow)\n    \n    return school_flow "
  },
  {
    "path": "cookbook/pocketflow-nested-batch/main.py",
    "content": "import os\nfrom flow import create_flow\n\ndef create_sample_data():\n    \"\"\"Create sample grade files.\"\"\"\n    # Create directory structure\n    os.makedirs(\"school/class_a\", exist_ok=True)\n    os.makedirs(\"school/class_b\", exist_ok=True)\n    \n    # Sample grades\n    data = {\n        \"class_a\": {\n            \"student1.txt\": [7.5, 8.0, 9.0],\n            \"student2.txt\": [8.5, 7.0, 9.5]\n        },\n        \"class_b\": {\n            \"student3.txt\": [6.5, 8.5, 7.0],\n            \"student4.txt\": [9.0, 9.5, 8.0]\n        }\n    }\n    \n    # Create files\n    for class_name, students in data.items():\n        for student, grades in students.items():\n            file_path = os.path.join(\"school\", class_name, student)\n            with open(file_path, 'w') as f:\n                for grade in grades:\n                    f.write(f\"{grade}\\n\")\n\ndef main():\n    \"\"\"Run the nested batch example.\"\"\"\n    # Create sample data\n    create_sample_data()\n    \n    print(\"Processing school grades...\\n\")\n    \n    # Create and run flow\n    flow = create_flow()\n    flow.run({})\n\nif __name__ == \"__main__\":\n    main() "
  },
  {
    "path": "cookbook/pocketflow-nested-batch/nodes.py",
    "content": "import os\nfrom pocketflow import Node\n\nclass LoadGrades(Node):\n    \"\"\"Node that loads grades from a student's file.\"\"\"\n    \n    def prep(self, shared):\n        \"\"\"Get file path from parameters.\"\"\"\n        class_name = self.params[\"class\"]\n        student_file = self.params[\"student\"]\n        return os.path.join(\"school\", class_name, student_file)\n    \n    def exec(self, file_path):\n        \"\"\"Load and parse grades from file.\"\"\"\n        with open(file_path, 'r') as f:\n            # Each line is a grade\n            grades = [float(line.strip()) for line in f]\n        return grades\n    \n    def post(self, shared, prep_res, grades):\n        \"\"\"Store grades in shared store.\"\"\"\n        shared[\"grades\"] = grades\n        return \"calculate\"\n\nclass CalculateAverage(Node):\n    \"\"\"Node that calculates average grade.\"\"\"\n    \n    def prep(self, shared):\n        \"\"\"Get grades from shared store.\"\"\"\n        return shared[\"grades\"]\n    \n    def exec(self, grades):\n        \"\"\"Calculate average.\"\"\"\n        return sum(grades) / len(grades)\n    \n    def post(self, shared, prep_res, average):\n        \"\"\"Store and print result.\"\"\"\n        # Store in results dictionary\n        if \"results\" not in shared:\n            shared[\"results\"] = {}\n        \n        class_name = self.params[\"class\"]\n        student = self.params[\"student\"]\n        \n        if class_name not in shared[\"results\"]:\n            shared[\"results\"][class_name] = {}\n            \n        shared[\"results\"][class_name][student] = average\n        \n        # Print individual result\n        print(f\"- {student}: Average = {average:.1f}\")\n        return \"default\" "
  },
  {
    "path": "cookbook/pocketflow-nested-batch/requirements.txt",
    "content": "pocketflow "
  },
  {
    "path": "cookbook/pocketflow-nested-batch/school/class_a/student1.txt",
    "content": "7.5\n8.0\n9.0\n"
  },
  {
    "path": "cookbook/pocketflow-nested-batch/school/class_a/student2.txt",
    "content": "8.5\n7.0\n9.5\n"
  },
  {
    "path": "cookbook/pocketflow-nested-batch/school/class_b/student3.txt",
    "content": "6.5\n8.5\n7.0\n"
  },
  {
    "path": "cookbook/pocketflow-nested-batch/school/class_b/student4.txt",
    "content": "9.0\n9.5\n8.0\n"
  },
  {
    "path": "cookbook/pocketflow-node/README.md",
    "content": "# PocketFlow Summarize\n\nA practical example demonstrating how to use PocketFlow to build a robust text summarization tool with error handling and retries. This example showcases core PocketFlow concepts in a real-world application.\n\n## Features\n\n- Text summarization using LLMs (Large Language Models)\n- Automatic retry mechanism (up to 3 attempts) on API failures\n- Graceful error handling with fallback responses\n- Clean separation of concerns using PocketFlow's Node architecture\n\n## Project Structure\n\n```\n.\n├── docs/          # Documentation files\n├── utils/         # Utility functions (LLM API wrapper)\n├── flow.py        # PocketFlow implementation with Summarize Node\n├── main.py        # Main application entry point\n└── README.md      # Project documentation\n```\n\n## Implementation Details\n\nThe example implements a simple but robust text summarization workflow:\n\n1. **Summarize Node** (`flow.py`):\n   - `prep()`: Retrieves text from the shared store\n   - `exec()`: Calls LLM to summarize text in 10 words\n   - `exec_fallback()`: Provides graceful error handling\n   - `post()`: Stores the summary back in shared store\n\n2. **Flow Structure**:\n   - Single node flow for demonstration\n   - Configured with 3 retries for reliability\n   - Uses shared store for data passing\n\n## Setup\n\n1. Create a virtual environment:\n```bash\npython -m venv venv\nsource venv/bin/activate  # On Windows: venv\\Scripts\\activate\n```\n\n2. Install dependencies:\n```bash\npip install -r requirements.txt\n```\n\n3. Configure your environment:\n   - Set up your LLM API key (check utils/call_llm.py for configuration)\n\n4. Run the example:\n```bash\npython main.py\n```\n\n## Example Usage\n\nThe example comes with a sample text about PocketFlow, but you can modify `main.py` to summarize your own text:\n\n```python\nshared = {\"data\": \"Your text to summarize here...\"}\nflow.run(shared)\nprint(\"Summary:\", shared[\"summary\"])\n```\n\n## What You'll Learn\n\nThis example demonstrates several key PocketFlow concepts:\n\n- **Node Architecture**: How to structure LLM tasks using prep/exec/post pattern\n- **Error Handling**: Implementing retry mechanisms and fallbacks\n- **Shared Store**: Using shared storage for data flow between steps\n- **Flow Creation**: Setting up a basic PocketFlow workflow\n\n## Additional Resources\n\n- [PocketFlow Documentation](https://the-pocket.github.io/PocketFlow/)\n- [Node Concept Guide](https://the-pocket.github.io/PocketFlow/node.html)\n- [Flow Design Patterns](https://the-pocket.github.io/PocketFlow/flow.html) "
  },
  {
    "path": "cookbook/pocketflow-node/flow.py",
    "content": "from pocketflow import Node, Flow\nfrom utils.call_llm import call_llm\n\nclass Summarize(Node):\n    def prep(self, shared):\n        \"\"\"Read and preprocess data from shared store.\"\"\"\n        return shared[\"data\"]\n\n    def exec(self, prep_res):\n        \"\"\"Execute the summarization using LLM.\"\"\"\n        if not prep_res:\n            return \"Empty text\"\n        prompt = f\"Summarize this text in 10 words: {prep_res}\"\n        summary = call_llm(prompt)  # might fail\n        return summary\n\n    def exec_fallback(self, shared, prep_res, exc):\n        \"\"\"Provide a simple fallback instead of crashing.\"\"\"\n        return \"There was an error processing your request.\"\n\n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store the summary in shared store.\"\"\"\n        shared[\"summary\"] = exec_res\n        # Return \"default\" by not returning\n\n# Create the flow\nsummarize_node = Summarize(max_retries=3)\nflow = Flow(start=summarize_node) "
  },
  {
    "path": "cookbook/pocketflow-node/main.py",
    "content": "from flow import flow\n\ndef main():\n    # Example text to summarize\n    text = \"\"\"\n    PocketFlow is a minimalist LLM framework that models workflows as a Nested Directed Graph.\n    Nodes handle simple LLM tasks, connecting through Actions for Agents.\n    Flows orchestrate these nodes for Task Decomposition, and can be nested.\n    It also supports Batch processing and Async execution.\n    \"\"\"\n\n    # Initialize shared store\n    shared = {\"data\": text}\n    \n    # Run the flow\n    flow.run(shared)\n    \n    # Print result\n    print(\"\\nInput text:\", text)\n    print(\"\\nSummary:\", shared[\"summary\"])\n\nif __name__ == \"__main__\":\n    main() "
  },
  {
    "path": "cookbook/pocketflow-node/requirements.txt",
    "content": "pocketflow\nopenai>=1.0.0 "
  },
  {
    "path": "cookbook/pocketflow-node/utils/call_llm.py",
    "content": "from openai import OpenAI\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=\"YOUR_API_KEY_HERE\")\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n    \nif __name__ == \"__main__\":\n    prompt = \"What is the meaning of life?\"\n    print(call_llm(prompt))"
  },
  {
    "path": "cookbook/pocketflow-parallel-batch/README.md",
    "content": "# Parallel Batch Translation Process\n\nThis project demonstrates using PocketFlow's async and parallel features (`AsyncFlow`, `AsyncParallelBatchNode`) to translate a document into multiple languages concurrently.\n\n- Check out the [Substack Post Tutorial](https://pocketflow.substack.com/p/parallel-llm-calls-from-scratch-tutorial) for more!\n\n## Goal\n\nTranslate `../../README.md` into multiple languages (Chinese, Spanish, etc.) in parallel, saving each to a file in the `translations/` directory. The main goal is to compare execution time against a sequential process.\n\n## Getting Started\n\n1. Install requirements:\n```bash\npip install -r requirements.txt\n```\n\n2. Set API Key:\n   Set the environment variable for your Anthropic API key.\n   ```bash\n   export ANTHROPIC_API_KEY=\"your-api-key-here\"\n   ```\n   *(Replace `\"your-api-key-here\"` with your actual key)*\n   *(Alternatively, place `ANTHROPIC_API_KEY=your-api-key-here` in a `.env` file)*\n\n3. Verify API Key (Optional):\n   Run a quick check using the utility script.\n   ```bash\n   python utils.py\n   ```\n   *(Note: This requires a valid API key to be set.)*\n\n4. Run the translation process:\n   ```bash\n   python main.py\n   ```\n\n## How It Works\n\nThe implementation uses an `AsyncParallelBatchNode` that processes translation requests concurrently. The `TranslateTextNodeParallel`:\n\n1. Prepares batches, pairing the source text with each target language.\n\n2. Executes translation calls to the LLM for all languages concurrently using `async` operations.\n\n3. Saves the translated content to individual files (`translations/README_LANGUAGE.md`).\n\nThis approach leverages `asyncio` and parallel execution to speed up I/O-bound tasks like multiple API calls.\n\n## Example Output & Comparison\n\nRunning this parallel version significantly reduces the total time compared to a sequential approach:\n\n```\n# --- Sequential Run Output (from pocketflow-batch) ---\nStarting sequential translation into 8 languages...\nTranslated Chinese text\n...\nTranslated Korean text\nSaved translation to translations/README_CHINESE.md\n...\nSaved translation to translations/README_KOREAN.md\n\nTotal sequential translation time: ~1136 seconds\n\n=== Translation Complete ===\nTranslations saved to: translations\n============================\n\n\n# --- Parallel Run Output (this example) ---\nStarting parallel translation into 8 languages...\nTranslated French text\nTranslated Portuguese text\n... # Messages may appear interleaved\nTranslated Spanish text\nSaved translation to translations/README_CHINESE.md\n...\nSaved translation to translations/README_KOREAN.md\n\nTotal parallel translation time: ~209 seconds\n\n=== Translation Complete ===\nTranslations saved to: translations\n============================\n```\n*(Actual times will vary based on API response speed and system.)*\n\n## Files\n\n- [`main.py`](./main.py): Implements the parallel batch translation node and flow.\n- [`utils.py`](./utils.py): Async wrapper for calling the Anthropic model.\n- [`requirements.txt`](./requirements.txt): Project dependencies (includes `aiofiles`).\n- [`translations/`](./translations/): Output directory (created automatically). \n"
  },
  {
    "path": "cookbook/pocketflow-parallel-batch/main.py",
    "content": "import asyncio\nimport time\nimport os\nfrom pocketflow import AsyncFlow, AsyncParallelBatchNode\nfrom utils import call_llm\n\n# --- Node Definitions ---\n\nclass TranslateTextNodeParallel(AsyncParallelBatchNode):\n    \"\"\"Translates README into multiple languages in parallel and saves files.\"\"\"\n    async def prep_async(self, shared):\n        \"\"\"Reads text and target languages from shared store.\"\"\"\n        text = shared.get(\"text\", \"(No text provided)\")\n        languages = shared.get(\"languages\", [])\n        return [(text, lang) for lang in languages]\n\n    async def exec_async(self, data_tuple):\n        \"\"\"Calls the async LLM utility for each target language.\"\"\"\n        text, language = data_tuple\n        \n        prompt = f\"\"\"\nPlease translate the following markdown file into {language}. \nBut keep the original markdown format, links and code blocks.\nDirectly return the translated text, without any other text or comments.\n\nOriginal: \n{text}\n\nTranslated:\"\"\"\n        \n        result = await call_llm(prompt)\n        print(f\"Translated {language} text\")\n        return {\"language\": language, \"translation\": result}\n\n    async def post_async(self, shared, prep_res, exec_res_list):\n        \"\"\"Stores the dictionary of {language: translation} pairs and writes to files.\"\"\"\n        output_dir = shared.get(\"output_dir\", \"translations\")\n        os.makedirs(output_dir, exist_ok=True)\n        \n        for result in exec_res_list:\n            if isinstance(result, dict):\n                language = result.get(\"language\", \"unknown\")\n                translation = result.get(\"translation\", \"\")\n                \n                filename = os.path.join(output_dir, f\"README_{language.upper()}.md\")\n                try:\n                    import aiofiles\n                    async with aiofiles.open(filename, \"w\", encoding=\"utf-8\") as f:\n                        await f.write(translation)\n                    print(f\"Saved translation to {filename}\")\n                except ImportError:\n                    with open(filename, \"w\", encoding=\"utf-8\") as f:\n                        f.write(translation)\n                    print(f\"Saved translation to {filename} (sync fallback)\")\n                except Exception as e:\n                    print(f\"Error writing file {filename}: {e}\")\n            else:\n                print(f\"Warning: Skipping invalid result item: {result}\")\n        return \"default\"\n\n# --- Flow Creation ---\n\ndef create_parallel_translation_flow():\n    \"\"\"Creates and returns the parallel translation flow.\"\"\"\n    translate_node = TranslateTextNodeParallel(max_retries=3)\n    return AsyncFlow(start=translate_node)\n\n# --- Main Execution ---\n\nasync def main():\n    source_readme_path = \"../../README.md\"\n    try:\n        with open(source_readme_path, \"r\", encoding='utf-8') as f:\n            text = f.read()\n    except FileNotFoundError:\n        print(f\"Error: Could not find the source README file at {source_readme_path}\")\n        exit(1)\n    except Exception as e:\n        print(f\"Error reading file {source_readme_path}: {e}\")\n        exit(1)\n\n    shared = {\n        \"text\": text,\n        \"languages\": [\"Chinese\", \"Spanish\", \"Japanese\", \"German\", \"Russian\", \"Portuguese\", \"French\", \"Korean\"],\n        \"output_dir\": \"translations\"\n    }\n\n    translation_flow = create_parallel_translation_flow()\n\n    print(f\"Starting parallel translation into {len(shared['languages'])} languages...\")\n    start_time = time.perf_counter()\n\n    await translation_flow.run_async(shared)\n\n    end_time = time.perf_counter()\n    duration = end_time - start_time\n    print(f\"\\nTotal parallel translation time: {duration:.4f} seconds\")\n    print(\"\\n=== Translation Complete ===\")\n    print(f\"Translations saved to: {shared['output_dir']}\")\n    print(\"============================\")\n\nif __name__ == \"__main__\":\n    asyncio.run(main()) "
  },
  {
    "path": "cookbook/pocketflow-parallel-batch/requirements.txt",
    "content": "pocketflow>=0.0.2\nanthropic>=0.15.0\npython-dotenv\nhttpx\naiofiles "
  },
  {
    "path": "cookbook/pocketflow-parallel-batch/translations/README_CHINESE.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-line minimalist LLM framework\" width=\"600\"/>\n</div>\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | 中文 | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow 是一个[100行代码](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)的极简主义LLM框架\n\n- **轻量级**：仅100行代码。零臃肿，零依赖，零供应商锁定。\n  \n- **表达力强**：包含你喜爱的一切—([多-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[智能体](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html)，[工作流](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html)，[RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html)等等。\n\n- **[智能体编码](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**：让AI智能体（例如Cursor AI）构建智能体—生产力提升10倍！\n\nPocket Flow入门：\n- 安装方式，```pip install pocketflow```或者直接复制[源代码](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)（仅100行）。\n- 了解更多，查看[文档](https://the-pocket.github.io/PocketFlow/)。了解动机，阅读[故事](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just)。\n- 有问题？查看这个[AI助手](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant)，或[创建issue！](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 加入我们的[Discord](https://discord.gg/hUHHE9Sa6T)，与其他使用Pocket Flow构建应用的开发者交流！\n- 🎉 Pocket Flow最初是Python版本，但我们现在有[Typescript](https://github.com/The-Pocket/PocketFlow-Typescript)，[Java](https://github.com/The-Pocket/PocketFlow-Java)，[C++](https://github.com/The-Pocket/PocketFlow-CPP)和[Go](https://github.com/The-Pocket/PocketFlow-Go)版本！\n\n## 为什么选择Pocket Flow？\n\n当前的LLM框架过于臃肿... 你只需要100行代码就能构建LLM框架！\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **抽象**          | **应用特定包装器**                                      | **供应商特定包装器**                                    | **代码行数**       | **大小**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agent, Chain               | 很多 <br><sup><sub>(例如，QA, 摘要)</sub></sup>              | 很多 <br><sup><sub>(例如，OpenAI, Pinecone等)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agent, Chain            | 很多 <br><sup><sub>(例如，FileReadTool, SerperDevTool)</sub></sup>         | 很多 <br><sup><sub>(例如，OpenAI, Anthropic, Pinecone等)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agent                      | 一些 <br><sup><sub>(例如，CodeAgent, VisitWebTool)</sub></sup>         | 一些 <br><sup><sub>(例如，DuckDuckGo, Hugging Face等)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agent, Graph           | 一些 <br><sup><sub>(例如，语义搜索)</sub></sup>                     | 一些 <br><sup><sub>(例如，PostgresStore, SqliteSaver等) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agent                | 一些 <br><sup><sub>(例如，Tool Agent, Chat Agent)</sub></sup>              | 很多 <sup><sub>[可选]<br> (例如，OpenAI, Pinecone等)</sub></sup>        | 7K <br><sup><sub>(仅核心)</sub></sup>    | +26MB <br><sup><sub>(仅核心)</sub></sup>          |\n| **PocketFlow** | **Graph**                    | **无**                                                 | **无**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Pocket Flow如何工作？\n\n这[100行代码](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)捕捉了LLM框架的核心抽象：图（Graph）！\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\n从这里开始，很容易实现流行的设计模式，如([多-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[智能体](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html)，[工作流](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html)，[RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html)等。\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ 以下是基础教程：\n\n<div align=\"center\">\n  \n|  名称  | 难度    |  描述  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [聊天](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *入门*   | 带有对话历史的基础聊天机器人 |\n| [结构化输出](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *入门* | 通过提示从简历中提取结构化数据 |\n| [工作流](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *入门*   | 一个写作工作流，包括大纲编写、内容创作和样式应用 |\n| [智能体](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *入门*   | 一个可以搜索网络并回答问题的研究智能体 |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *入门*   | 一个简单的检索增强生成过程 |\n| [批处理](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *入门* | 一个将markdown内容翻译成多种语言的批处理器 |\n| [流式处理](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *入门*   | 具有用户中断功能的实时LLM流式演示 |\n| [聊天护栏](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *入门*  | 一个仅处理旅行相关查询的旅行顾问聊天机器人 |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *初级* | 使用map-reduce模式进行批量评估的简历资格处理器 |\n| [多智能体](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *初级* | 两个智能体之间异步通信的禁忌词游戏 |\n| [监督者](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *初级* | 研究智能体变得不可靠...让我们构建一个监督流程|\n| [并行](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *初级*   | 展示3倍加速的并行执行演示 |\n| [并行流](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *初级*   | 展示使用多个过滤器实现8倍加速的并行图像处理演示 |\n| [多数投票](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *初级* | 通过聚合多次解决方案尝试来提高推理准确性 |\n| [思考](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *初级*   | 通过思维链解决复杂推理问题 |\n| [记忆](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *初级* | 具有短期和长期记忆的聊天机器人 |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *初级* | 使用自动调试循环将自然语言转换为SQL查询 |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *初级* |  使用模型上下文协议进行数值运算的智能体 |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *初级* | 使用智能体到智能体协议包装的智能体，用于智能体间通信 |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *初级* | 具有SSE更新的人工审核循环的最小Web服务 |\n\n</div>\n\n👀 想看其他入门教程？[创建一个issue！](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## 如何使用Pocket Flow？\n\n🚀 通过**智能体编码**—最快的LLM应用开发范式—*人类设计*，*智能体编码*！\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ 以下是更复杂LLM应用的示例：\n\n<div align=\"center\">\n  \n|  应用名称     |  难度    | 主题  | 人类设计 | 智能体代码 |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [用Cursor构建Cursor](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>我们很快将达到奇点...</sup></sub> | ★★★ <br> *高级*   | [智能体](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [设计文档](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Flow代码](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [代码库知识构建器](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>生命太短暂，不应该困惑地盯着他人的代码</sup></sub> |  ★★☆ <br> *中级* | [工作流](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [设计文档](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Flow代码](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [询问AI Paul Graham](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>询问AI Paul Graham，以防你没被录取</sup></sub> | ★★☆ <br> *中级*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [设计文档](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Flow代码](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Youtube摘要器](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> 像你5岁一样向你解释YouTube视频 </sup></sub> | ★☆☆ <br> *初级*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [设计文档](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Flow代码](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [冷启动生成器](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> 将冷门线索转变为热门的即时破冰工具 </sup></sub> | ★☆☆ <br> *初级*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Web搜索](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [设计文档](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Flow代码](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- 想学习**智能体编码**？\n\n  - 查看[我的YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1)获取关于如何制作上述应用的视频教程！\n\n  - 想构建自己的LLM应用？阅读这篇[文章](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)！从[这个模板](https://github.com/The-Pocket/PocketFlow-Template-Python)开始！"
  },
  {
    "path": "cookbook/pocketflow-parallel-batch/translations/README_FRENCH.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – framework LLM minimaliste en 100 lignes\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | Français | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow est un framework LLM minimaliste en [100 lignes](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)\n\n- **Léger** : Seulement 100 lignes. Zéro superflu, zéro dépendance, zéro verrouillage fournisseur.\n  \n- **Expressif** : Tout ce que vous aimez — ([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agents](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), et plus encore.\n\n- **[Programmation Agentique](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)** : Laissez les Agents IA (par exemple, Cursor AI) créer des Agents — augmentez votre productivité par 10 !\n\nCommencer avec Pocket Flow :\n- Pour installer, ```pip install pocketflow``` ou copiez simplement le [code source](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) (seulement 100 lignes).\n- Pour en savoir plus, consultez la [documentation](https://the-pocket.github.io/PocketFlow/). Pour comprendre la motivation, lisez l'[histoire](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just).\n- Des questions ? Consultez cet [Assistant IA](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant), ou [créez une issue !](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 Rejoignez notre [Discord](https://discord.gg/hUHHE9Sa6T) pour vous connecter avec d'autres développeurs utilisant Pocket Flow !\n- 🎉 Pocket Flow est initialement en Python, mais nous avons maintenant des versions en [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP) et [Go](https://github.com/The-Pocket/PocketFlow-Go) !\n\n## Pourquoi Pocket Flow ?\n\nLes frameworks LLM actuels sont surchargés... Vous n'avez besoin que de 100 lignes pour un framework LLM !\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **Abstraction**          | **Wrappers spécifiques aux applications**                                      | **Wrappers spécifiques aux fournisseurs**                                    | **Lignes**       | **Taille**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agent, Chain               | Nombreux <br><sup><sub>(ex., QA, Résumé)</sub></sup>              | Nombreux <br><sup><sub>(ex., OpenAI, Pinecone, etc.)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agent, Chain            | Nombreux <br><sup><sub>(ex., FileReadTool, SerperDevTool)</sub></sup>         | Nombreux <br><sup><sub>(ex., OpenAI, Anthropic, Pinecone, etc.)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agent                      | Quelques <br><sup><sub>(ex., CodeAgent, VisitWebTool)</sub></sup>         | Quelques <br><sup><sub>(ex., DuckDuckGo, Hugging Face, etc.)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agent, Graph           | Quelques <br><sup><sub>(ex., Recherche Sémantique)</sub></sup>                     | Quelques <br><sup><sub>(ex., PostgresStore, SqliteSaver, etc.) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agent                | Quelques <br><sup><sub>(ex., Tool Agent, Chat Agent)</sub></sup>              | Nombreux <sup><sub>[Optionnel]<br> (ex., OpenAI, Pinecone, etc.)</sub></sup>        | 7K <br><sup><sub>(core-only)</sub></sup>    | +26MB <br><sup><sub>(core-only)</sub></sup>          |\n| **PocketFlow** | **Graph**                    | **Aucun**                                                 | **Aucun**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Comment fonctionne Pocket Flow ?\n\nLes [100 lignes](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) capturent l'abstraction fondamentale des frameworks LLM : le Graph !\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nDe là, il est facile d'implémenter des modèles de conception populaires comme ([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agents](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), etc.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ Voici des tutoriels de base :\n\n<div align=\"center\">\n  \n|  Nom  | Difficulté    |  Description  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *Débutant*   | Un chatbot basique avec historique de conversation |\n| [Sortie Structurée](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *Débutant* | Extraction de données structurées à partir de CV par prompt |\n| [Workflow](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *Débutant*   | Un workflow d'écriture qui planifie, rédige du contenu et applique un style |\n| [Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Débutant*   | Un agent de recherche qui peut chercher sur le web et répondre aux questions |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *Débutant*   | Un processus simple de génération augmentée par récupération |\n| [Batch](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *Débutant* | Un processeur par lots qui traduit du contenu markdown en plusieurs langues |\n| [Streaming](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Débutant*   | Une démo de streaming LLM en temps réel avec capacité d'interruption utilisateur |\n| [Garde-fou de Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *Débutant*  | Un chatbot conseiller de voyage qui ne traite que les requêtes liées au voyage |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *Intermédiaire* | Un processeur de qualification de CV utilisant le modèle map-reduce pour l'évaluation par lots |\n| [Multi-Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *Intermédiaire* | Un jeu de Tabou pour la communication asynchrone entre deux agents |\n| [Superviseur](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Intermédiaire* | L'agent de recherche devient peu fiable... Construisons un processus de supervision |\n| [Parallèle](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Intermédiaire*   | Une démo d'exécution parallèle montrant une accélération de 3x |\n| [Flux Parallèle](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *Intermédiaire*   | Une démo de traitement d'image parallèle montrant une accélération de 8x avec plusieurs filtres |\n| [Vote Majoritaire](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *Intermédiaire* | Améliorer la précision du raisonnement en agrégeant plusieurs tentatives de solution |\n| [Réflexion](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Intermédiaire*   | Résoudre des problèmes de raisonnement complexes grâce à la Chaîne de Pensée |\n| [Mémoire](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Intermédiaire* | Un chatbot avec mémoire à court et long terme |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *Intermédiaire* | Convertir le langage naturel en requêtes SQL avec une boucle d'auto-débogage |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *Intermédiaire* |  Agent utilisant le Protocole de Contexte de Modèle pour les opérations numériques |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *Intermédiaire* | Agent encapsulé avec le protocole Agent-to-Agent pour la communication inter-agent |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *Intermédiaire* | Un service web minimal pour une boucle de révision humaine avec mises à jour SSE |\n\n</div>\n\n👀 Vous voulez voir d'autres tutoriels pour débutants ? [Créez une issue !](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## Comment utiliser Pocket Flow ?\n\n🚀 Par la **Programmation Agentique** — le paradigme de développement d'applications LLM le plus rapide — où *les humains conçoivent* et *les agents programment* !\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ Voici des exemples d'applications LLM plus complexes :\n\n<div align=\"center\">\n  \n|  Nom de l'application     |  Difficulté    | Sujets  | Conception Humaine | Code Agent |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Construire Cursor avec Cursor](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>Nous atteindrons bientôt la singularité ...</sup></sub> | ★★★ <br> *Avancé*   | [Agent](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [Document de conception](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Code Flow](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [Constructeur de Connaissances de Base de Code](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>La vie est trop courte pour rester perplexe devant le code des autres</sup></sub> |  ★★☆ <br> *Moyen* | [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [Document de conception](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Code Flow](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [Interroger l'IA Paul Graham](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>Interrogez l'IA Paul Graham, au cas où vous ne seriez pas accepté</sup></sub> | ★★☆ <br> *Moyen*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [Document de conception](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Code Flow](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Résumeur Youtube](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> Vous explique les vidéos YouTube comme si vous aviez 5 ans </sup></sub> | ★☆☆ <br> *Intermédiaire*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [Document de conception](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Code Flow](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [Générateur d'Accroche pour Email](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> Des brise-glaces instantanés qui transforment les prospects froids en prospects chauds </sup></sub> | ★☆☆ <br> *Intermédiaire*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Recherche Web](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [Document de conception](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Code Flow](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- Vous voulez apprendre la **Programmation Agentique** ?\n\n  - Consultez [ma chaîne YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1) pour des tutoriels vidéo sur la façon dont certaines applications ci-dessus sont créées !\n\n  - Vous voulez créer votre propre application LLM ? Lisez cet [article](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to) ! Commencez avec [ce modèle](https://github.com/The-Pocket/PocketFlow-Template-Python) !"
  },
  {
    "path": "cookbook/pocketflow-parallel-batch/translations/README_GERMAN.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-Zeilen minimalistisches LLM-Framework\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | Deutsch | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![Lizenz: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow ist ein [100-zeiliges](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) minimalistisches LLM-Framework\n\n- **Leichtgewichtig**: Nur 100 Zeilen. Kein Ballast, keine Abhängigkeiten, keine Anbieterbindung.\n  \n- **Ausdrucksstark**: Alles, was Sie lieben—([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agenten](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), und mehr.\n\n- **[Agenten-basiertes Programmieren](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: Lassen Sie KI-Agenten (z.B. Cursor AI) Agenten bauen—10-fache Produktivitätssteigerung!\n\nErste Schritte mit Pocket Flow:\n- Zur Installation, ```pip install pocketflow```oder kopieren Sie einfach den [Quellcode](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) (nur 100 Zeilen).\n- Um mehr zu erfahren, schauen Sie in die [Dokumentation](https://the-pocket.github.io/PocketFlow/). Um die Motivation zu verstehen, lesen Sie die [Geschichte](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just).\n- Haben Sie Fragen? Schauen Sie sich diesen [KI-Assistenten](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant) an, oder [erstellen Sie ein Issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 Treten Sie unserem [Discord](https://discord.gg/hUHHE9Sa6T) bei, um sich mit anderen Entwicklern zu vernetzen, die mit Pocket Flow arbeiten!\n- 🎉 Pocket Flow ist ursprünglich in Python geschrieben, aber wir haben jetzt auch Versionen für [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP) und [Go](https://github.com/The-Pocket/PocketFlow-Go)!\n\n## Warum Pocket Flow?\n\nAktuelle LLM-Frameworks sind aufgebläht... Sie brauchen nur 100 Zeilen für ein LLM-Framework!\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **Abstraktion**          | **App-spezifische Wrapper**                                      | **Anbieter-spezifische Wrapper**                                    | **Zeilen**       | **Größe**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agent, Chain               | Viele <br><sup><sub>(z.B. QA, Zusammenfassung)</sub></sup>              | Viele <br><sup><sub>(z.B. OpenAI, Pinecone, etc.)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agent, Chain            | Viele <br><sup><sub>(z.B. FileReadTool, SerperDevTool)</sub></sup>         | Viele <br><sup><sub>(z.B. OpenAI, Anthropic, Pinecone, etc.)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agent                      | Einige <br><sup><sub>(z.B. CodeAgent, VisitWebTool)</sub></sup>         | Einige <br><sup><sub>(z.B. DuckDuckGo, Hugging Face, etc.)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agent, Graph           | Einige <br><sup><sub>(z.B. Semantic Search)</sub></sup>                     | Einige <br><sup><sub>(z.B. PostgresStore, SqliteSaver, etc.) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agent                | Einige <br><sup><sub>(z.B. Tool Agent, Chat Agent)</sub></sup>              | Viele <sup><sub>[Optional]<br> (z.B. OpenAI, Pinecone, etc.)</sub></sup>        | 7K <br><sup><sub>(nur Kern)</sub></sup>    | +26MB <br><sup><sub>(nur Kern)</sub></sup>          |\n| **PocketFlow** | **Graph**                    | **Keine**                                                 | **Keine**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Wie funktioniert Pocket Flow?\n\nDie [100 Zeilen](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) erfassen die Kernabstraktion von LLM-Frameworks: Graph!\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nVon dort aus ist es einfach, beliebte Designmuster wie ([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agenten](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), etc. zu implementieren.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ Hier sind grundlegende Tutorials:\n\n<div align=\"center\">\n  \n|  Name  | Schwierigkeit    |  Beschreibung  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *Anfänger*   | Ein einfacher Chatbot mit Gesprächsverlauf |\n| [Strukturierte Ausgabe](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *Anfänger* | Extraktion strukturierter Daten aus Lebensläufen durch Prompting |\n| [Workflow](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *Anfänger*   | Ein Schreib-Workflow, der gliedert, Inhalte schreibt und Formatierungen anwendet |\n| [Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Anfänger*   | Ein Recherche-Agent, der im Web suchen und Fragen beantworten kann |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *Anfänger*   | Ein einfacher Abrufsaugmentierter Generierungsprozess |\n| [Batch](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *Anfänger* | Ein Batch-Prozessor, der Markdown-Inhalte in mehrere Sprachen übersetzt |\n| [Streaming](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Anfänger*   | Eine Echtzeit-LLM-Streaming-Demo mit Benutzer-Unterbrechungsfunktion |\n| [Chat-Leitplanke](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *Anfänger*  | Ein Reiseberater-Chatbot, der nur reisebezogene Anfragen verarbeitet |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *Einsteiger* | Ein Lebenslauf-Qualifikationsprozessor, der das Map-Reduce-Muster für Batch-Auswertungen verwendet |\n| [Multi-Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *Einsteiger* | Ein Tabu-Wortspiel für asynchrone Kommunikation zwischen zwei Agenten |\n| [Supervisor](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Einsteiger* | Forschungsagent wird unzuverlässig... Bauen wir einen Überwachungsprozess auf|\n| [Parallel](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Einsteiger*   | Eine parallele Ausführungsdemo, die 3-fache Beschleunigung zeigt |\n| [Paralleler Flow](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *Einsteiger*   | Eine parallele Bildverarbeitungsdemo, die 8-fache Beschleunigung mit mehreren Filtern zeigt |\n| [Mehrheitswahl](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *Einsteiger* | Verbesserte Schlussfolgerungsgenauigkeit durch Aggregation mehrerer Lösungsversuche |\n| [Denken](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Einsteiger*   | Lösen komplexer Schlussfolgerungsprobleme durch Chain-of-Thought |\n| [Gedächtnis](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Einsteiger* | Ein Chatbot mit Kurz- und Langzeitgedächtnis |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *Einsteiger* | Konvertierung natürlicher Sprache in SQL-Abfragen mit Auto-Debug-Schleife |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *Einsteiger* |  Agent mit Model Context Protocol für numerische Operationen |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *Einsteiger* | Agent mit Agent-to-Agent-Protokoll für Inter-Agenten-Kommunikation |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *Einsteiger* | Ein minimaler Webdienst für eine menschliche Überprüfungsschleife mit SSE-Updates |\n\n</div>\n\n👀 Möchten Sie andere Tutorials für Anfänger sehen? [Erstellen Sie ein Issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## Wie verwendet man Pocket Flow?\n\n🚀 Durch **Agenten-basiertes Programmieren**—das schnellste LLM-App-Entwicklungsparadigma, bei dem *Menschen entwerfen* und *Agenten programmieren*!\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ Hier sind Beispiele für komplexere LLM-Apps:\n\n<div align=\"center\">\n  \n|  App-Name     |  Schwierigkeit    | Themen  | Menschlicher Entwurf | Agent-Code |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Cursor mit Cursor bauen](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>Wir werden bald die Singularität erreichen ...</sup></sub> | ★★★ <br> *Fortgeschritten*   | [Agent](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [Design-Dokument](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Flow-Code](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [Codebase-Wissensgenerator](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>Das Leben ist zu kurz, um ratlos fremden Code anzustarren</sup></sub> |  ★★☆ <br> *Mittel* | [Workflow](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [Design-Dokument](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Flow-Code](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [Frage KI Paul Graham](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>Frage KI Paul Graham, falls du nicht reinkommst</sup></sub> | ★★☆ <br> *Mittel*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [Design-Dokument](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Flow-Code](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Youtube-Zusammenfasser](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> Erklärt YouTube-Videos so, als wärst du 5 </sup></sub> | ★☆☆ <br> *Einsteiger*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [Design-Dokument](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Flow-Code](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [Cold-Opener-Generator](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> Sofortige Eisbrecher, die kalte Leads heiß machen </sup></sub> | ★☆☆ <br> *Einsteiger*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Web-Suche](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [Design-Dokument](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Flow-Code](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- Möchten Sie **Agenten-basiertes Programmieren** lernen?\n\n  - Schauen Sie sich [meinen YouTube-Kanal](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1) für Video-Tutorials an, wie einige der oben genannten Apps erstellt wurden!\n\n  - Möchten Sie Ihre eigene LLM-App erstellen? Lesen Sie diesen [Beitrag](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)! Beginnen Sie mit [dieser Vorlage](https://github.com/The-Pocket/PocketFlow-Template-Python)!"
  },
  {
    "path": "cookbook/pocketflow-parallel-batch/translations/README_JAPANESE.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100行のミニマリストLLMフレームワーク\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\nEnglish | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flowは[たった100行](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)のミニマリストLLMフレームワークです\n\n- **軽量**: わずか100行。余分なものなし、依存関係なし、ベンダーロックインなし。\n  \n- **表現力豊か**: あなたが愛するすべてのもの—([マルチ](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[エージェント](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html)、[ワークフロー](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html)、[RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html)など。\n\n- **[エージェンティックコーディング](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: AIエージェント（例：Cursor AI）にエージェントを構築させる—生産性が10倍向上！\n\nPocket Flowを始めるには：\n- インストールするには、```pip install pocketflow```または[ソースコード](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)（わずか100行）をコピーするだけです。\n- 詳細については、[ドキュメント](https://the-pocket.github.io/PocketFlow/)をご覧ください。開発の動機については、[ストーリー](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just)をお読みください。\n- 質問がありますか？この[AIアシスタント](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant)をチェックするか、[問題を作成してください！](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 [Discord](https://discord.gg/hUHHE9Sa6T)に参加して、Pocket Flowで開発している他の開発者とつながりましょう！\n- 🎉 Pocket Flowは最初はPythonですが、現在は[Typescript](https://github.com/The-Pocket/PocketFlow-Typescript)、[Java](https://github.com/The-Pocket/PocketFlow-Java)、[C++](https://github.com/The-Pocket/PocketFlow-CPP)、[Go](https://github.com/The-Pocket/PocketFlow-Go)バージョンもあります！\n\n## なぜPocket Flow？\n\n現在のLLMフレームワークは膨大すぎます... LLMフレームワークには100行だけで十分です！\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **抽象化**          | **アプリ固有のラッパー**                                      | **ベンダー固有のラッパー**                                    | **行数**       | **サイズ**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | エージェント、チェーン               | 多数 <br><sup><sub>(例：QA、要約)</sub></sup>              | 多数 <br><sup><sub>(例：OpenAI、Pineconeなど)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | エージェント、チェーン            | 多数 <br><sup><sub>(例：FileReadTool、SerperDevTool)</sub></sup>         | 多数 <br><sup><sub>(例：OpenAI、Anthropic、Pineconeなど)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | エージェント                      | 一部 <br><sup><sub>(例：CodeAgent、VisitWebTool)</sub></sup>         | 一部 <br><sup><sub>(例：DuckDuckGo、Hugging Faceなど)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | エージェント、グラフ           | 一部 <br><sup><sub>(例：セマンティック検索)</sub></sup>                     | 一部 <br><sup><sub>(例：PostgresStore、SqliteSaverなど) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | エージェント                | 一部 <br><sup><sub>(例：ツールエージェント、チャットエージェント)</sub></sup>              | 多数 <sup><sub>[オプション]<br> (例：OpenAI、Pineconeなど)</sub></sup>        | 7K <br><sup><sub>(コアのみ)</sub></sup>    | +26MB <br><sup><sub>(コアのみ)</sub></sup>          |\n| **PocketFlow** | **グラフ**                    | **なし**                                                 | **なし**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Pocket Flowはどのように機能するのか？\n\n[100行](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)がLLMフレームワークの中核的抽象化を捉えています：グラフ！\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nそこから、([マルチ](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[エージェント](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html)、[ワークフロー](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html)、[RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html)などの人気のあるデザインパターンを簡単に実装できます。\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ 以下は基本的なチュートリアルです：\n\n<div align=\"center\">\n  \n|  名前  | 難易度    |  説明  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [チャット](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *超簡単*   | 会話履歴を持つ基本的なチャットボット |\n| [構造化出力](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *超簡単* | プロンプトを使って履歴書から構造化データを抽出する |\n| [ワークフロー](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *超簡単*   | アウトライン作成、コンテンツ作成、スタイル適用を行うライティングワークフロー |\n| [エージェント](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *超簡単*   | ウェブを検索して質問に答えることができる調査エージェント |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *超簡単*   | シンプルな検索拡張生成プロセス |\n| [バッチ処理](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *超簡単* | マークダウンコンテンツを複数の言語に翻訳するバッチプロセッサ |\n| [ストリーミング](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *超簡単*   | ユーザー割り込み機能を備えたリアルタイムLLMストリーミングデモ |\n| [チャットガードレール](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *超簡単*  | 旅行関連のクエリのみを処理する旅行アドバイザーチャットボット |\n| [マップリデュース](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *初級* | マップリデュースパターンを使用したバッチ評価の履歴書資格処理プログラム |\n| [マルチエージェント](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *初級* | 2つのエージェント間の非同期通信のためのタブーワードゲーム |\n| [スーパーバイザー](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *初級* | 調査エージェントが信頼性を失っています... 監視プロセスを構築しましょう |\n| [並列処理](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *初級*   | 3倍の高速化を示す並列実行デモ |\n| [並列フロー](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *初級*   | 複数のフィルターによる8倍の高速化を示す並列画像処理デモ |\n| [多数決](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *初級* | 複数の解決策を集約して推論の精度を向上させる |\n| [思考](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *初級*   | 思考の連鎖を通じて複雑な推論問題を解決する |\n| [メモリ](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *初級* | 短期記憶と長期記憶を持つチャットボット |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *初級* | 自動デバッグループを備えた自然言語からSQLクエリへの変換 |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *初級* | 数値演算のためのモデルコンテキストプロトコルを使用するエージェント |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *初級* | エージェント間通信のためのエージェント間プロトコルでラップされたエージェント |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *初級* | SSE更新を備えた人間レビューループのためのミニマルなウェブサービス |\n\n</div>\n\n👀 他の超初心者向けチュートリアルを見たいですか？[問題を作成してください！](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## Pocket Flowの使い方\n\n🚀 **エージェンティックコーディング**を通じて—*人間が設計し*、*エージェントがコーディングする*最速のLLMアプリ開発パラダイム！\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"イメージ代替テキスト\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ 以下はより複雑なLLMアプリの例です：\n\n<div align=\"center\">\n  \n|  アプリ名     |  難易度    | トピック  | 人間の設計 | エージェントのコード |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [CursorでCursorを構築する](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>もうすぐシンギュラリティに達します...</sup></sub> | ★★★ <br> *上級*   | [エージェント](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [設計ドキュメント](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [フローコード](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [コードベース知識ビルダー](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>他人のコードを混乱して見つめるほど人生は短くない</sup></sub> |  ★★☆ <br> *中級* | [ワークフロー](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [設計ドキュメント](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [フローコード](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [AI Paul Grahamに質問する](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>採用されない場合に備えて、AI Paul Grahamに質問しましょう</sup></sub> | ★★☆ <br> *中級*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [マップリデュース](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [設計ドキュメント](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [フローコード](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Youtubeサマライザー](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> 5歳児にもわかるようにYouTube動画を説明 </sup></sub> | ★☆☆ <br> *初級*   | [マップリデュース](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [設計ドキュメント](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [フローコード](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [コールドオープナージェネレーター](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> 冷たいリードを熱くする即席アイスブレイカー </sup></sub> | ★☆☆ <br> *初級*   | [マップリデュース](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [ウェブ検索](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [設計ドキュメント](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [フローコード](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- **エージェンティックコーディング**を学びたいですか？\n\n  - 上記のアプリの作り方に関するビデオチュートリアルについては、[私のYouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1)をチェックしてください！\n\n  - 自分のLLMアプリを構築したいですか？この[投稿](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)を読んでください！[このテンプレート](https://github.com/The-Pocket/PocketFlow-Template-Python)から始めましょう！"
  },
  {
    "path": "cookbook/pocketflow-parallel-batch/translations/README_KOREAN.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-line minimalist LLM framework\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | 한국어\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow는 [100줄](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)의 미니멀리스트 LLM 프레임워크입니다\n\n- **경량화**: 단 100줄. 불필요한 요소 없음, 의존성 없음, 벤더 종속성 없음.\n  \n- **표현력**: 여러분이 좋아하는 모든 것—([멀티-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[에이전트](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [워크플로우](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) 등.\n\n- **[에이전트 코딩](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: AI 에이전트(예: Cursor AI)가 에이전트를 구축하도록 하세요—생산성 10배 향상!\n\nPocket Flow 시작하기:\n- 설치하려면 ```pip install pocketflow```나 [소스 코드](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)(단 100줄)를 복사하세요.\n- 더 알아보려면 [문서](https://the-pocket.github.io/PocketFlow/)를 확인하세요. 개발 동기에 대해 알고 싶다면 [이야기](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just)를 읽어보세요.\n- 질문이 있으신가요? [AI 어시스턴트](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant)를 확인하거나, [이슈를 생성하세요!](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 Pocket Flow로 개발하는 다른 개발자들과 소통하려면 [Discord](https://discord.gg/hUHHE9Sa6T)에 가입하세요!\n- 🎉 Pocket Flow는 처음에 Python으로 개발되었지만, 이제 [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP) 및 [Go](https://github.com/The-Pocket/PocketFlow-Go) 버전도 있습니다!\n\n## 왜 Pocket Flow인가?\n\n현재 LLM 프레임워크들은 너무 비대합니다... LLM 프레임워크는 단 100줄이면 충분합니다!\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **추상화**          | **앱 특화 래퍼**                                      | **벤더 특화 래퍼**                                    | **코드 줄**       | **크기**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agent, Chain               | 많음 <br><sup><sub>(예: QA, 요약)</sub></sup>              | 많음 <br><sup><sub>(예: OpenAI, Pinecone 등)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agent, Chain            | 많음 <br><sup><sub>(예: FileReadTool, SerperDevTool)</sub></sup>         | 많음 <br><sup><sub>(예: OpenAI, Anthropic, Pinecone 등)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agent                      | 일부 <br><sup><sub>(예: CodeAgent, VisitWebTool)</sub></sup>         | 일부 <br><sup><sub>(예: DuckDuckGo, Hugging Face 등)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agent, Graph           | 일부 <br><sup><sub>(예: Semantic Search)</sub></sup>                     | 일부 <br><sup><sub>(예: PostgresStore, SqliteSaver 등) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agent                | 일부 <br><sup><sub>(예: Tool Agent, Chat Agent)</sub></sup>              | 많음 <sup><sub>[선택적]<br> (예: OpenAI, Pinecone 등)</sub></sup>        | 7K <br><sup><sub>(핵심만)</sub></sup>    | +26MB <br><sup><sub>(핵심만)</sub></sup>          |\n| **PocketFlow** | **Graph**                    | **없음**                                                 | **없음**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Pocket Flow는 어떻게 작동하나요?\n\n[100줄](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)의 코드는 LLM 프레임워크의 핵심 추상화인 그래프를 구현합니다!\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\n이를 기반으로 ([멀티-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[에이전트](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [워크플로우](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) 등의 인기 있는 디자인 패턴을 쉽게 구현할 수 있습니다.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ 아래는 기본 튜토리얼입니다:\n\n<div align=\"center\">\n  \n|  이름  | 난이도    |  설명  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [채팅](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *초보*   | 대화 기록을 가진 기본 채팅봇 |\n| [구조화된 출력](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *초보* | 프롬프트를 통해 이력서에서 구조화된 데이터 추출 |\n| [워크플로우](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *초보*   | 개요 작성, 내용 작성, 스타일 적용이 포함된 작성 워크플로우 |\n| [에이전트](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *초보*   | 웹을 검색하고 질문에 답할 수 있는 연구 에이전트 |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *초보*   | 간단한 검색 증강 생성 프로세스 |\n| [배치](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *초보* | 마크다운 콘텐츠를 여러 언어로 번역하는 배치 프로세서 |\n| [스트리밍](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *초보*   | 사용자 중단 기능이 있는 실시간 LLM 스트리밍 데모 |\n| [채팅 가드레일](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *초보*  | 여행 관련 쿼리만 처리하는 여행 상담 채팅봇 |\n| [맵-리듀스](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *초급* | 배치 평가를 위한 맵-리듀스 패턴을 사용하는 이력서 자격 처리기 |\n| [멀티-에이전트](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *초급* | 두 에이전트 간의 비동기 통신을 위한 금지어 게임 |\n| [감독자](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *초급* | 연구 에이전트가 불안정할 때... 감독 프로세스를 구축해 봅시다 |\n| [병렬](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *초급*   | 3배 속도 향상을 보여주는 병렬 실행 데모 |\n| [병렬 플로우](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *초급*   | 여러 필터를 사용한 8배 속도 향상을 보여주는 병렬 이미지 처리 데모 |\n| [다수결 투표](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *초급* | 여러 솔루션 시도를 집계하여 추론 정확도 향상 |\n| [사고](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *초급*   | Chain-of-Thought를 통한 복잡한 추론 문제 해결 |\n| [메모리](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *초급* | 단기 및 장기 메모리가 있는 채팅봇 |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *초급* | 자동 디버그 루프가 있는 자연어에서 SQL 쿼리로 변환 |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *초급* | 수치 연산을 위한 모델 컨텍스트 프로토콜을 사용하는 에이전트 |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *초급* | 에이전트 간 통신을 위한 Agent-to-Agent 프로토콜로 래핑된 에이전트 |\n| [웹 HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *초급* | SSE 업데이트가 있는 인간 검토 루프를 위한 최소한의 웹 서비스 |\n\n</div>\n\n👀 더 많은 초보자용 튜토리얼을 보고 싶으신가요? [이슈를 생성하세요!](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## Pocket Flow를 어떻게 사용하나요?\n\n🚀 **에이전트 코딩**을 통해—가장 빠른 LLM 앱 개발 패러다임으로, *인간이 설계*하고 *에이전트가 코딩*합니다!\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ 아래는 더 복잡한 LLM 앱의 예시입니다:\n\n<div align=\"center\">\n  \n|  앱 이름     |  난이도    | 주제  | 인간 설계 | 에이전트 코드 |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Cursor로 Cursor 만들기](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>곧 기술적 특이점에 도달할 것입니다...</sup></sub> | ★★★ <br> *고급*   | [에이전트](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [설계 문서](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [플로우 코드](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [코드베이스 지식 빌더](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>인생은 다른 사람의 코드를 혼란스럽게 바라볼 만큼 길지 않습니다</sup></sub> |  ★★☆ <br> *중급* | [워크플로우](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [설계 문서](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [플로우 코드](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [AI Paul Graham에게 물어보기](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>합격하지 못한 경우를 대비해 AI Paul Graham에게 물어보세요</sup></sub> | ★★☆ <br> *중급*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [맵 리듀스](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [설계 문서](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [플로우 코드](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [유튜브 요약기](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> 5살 아이에게 설명하듯 YouTube 동영상 설명 </sup></sub> | ★☆☆ <br> *초급*   | [맵 리듀스](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [설계 문서](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [플로우 코드](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [콜드 오프너 생성기](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> 차가운 잠재 고객을 뜨겁게 만드는 즉각적인 아이스브레이커 </sup></sub> | ★☆☆ <br> *초급*   | [맵 리듀스](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [웹 검색](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [설계 문서](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [플로우 코드](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- **에이전트 코딩**을 배우고 싶으신가요?\n\n  - 위에 소개된 앱들이 어떻게 만들어졌는지 비디오 튜토리얼을 보려면 [제 YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1)를 확인하세요!\n\n  - 자신만의 LLM 앱을 만들고 싶으신가요? 이 [포스트](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)를 읽어보세요! [이 템플릿](https://github.com/The-Pocket/PocketFlow-Template-Python)으로 시작하세요!"
  },
  {
    "path": "cookbook/pocketflow-parallel-batch/translations/README_PORTUGUESE.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-line minimalist LLM framework\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | Português | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow é um framework minimalista para LLM com [apenas 100 linhas](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)\n\n- **Leve**: Apenas 100 linhas. Zero inchaço, zero dependências, zero aprisionamento a fornecedores.\n  \n- **Expressivo**: Tudo o que você adora—([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agentes](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Fluxo de Trabalho](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), e mais.\n\n- **[Codificação Agêntica](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: Deixe que Agentes de IA (ex: Cursor AI) construam Agentes—aumento de produtividade de 10x!\n\nComece com o Pocket Flow:\n- Para instalar, ```pip install pocketflow``` ou apenas copie o [código-fonte](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) (apenas 100 linhas).\n- Para saber mais, consulte a [documentação](https://the-pocket.github.io/PocketFlow/). Para entender a motivação, leia a [história](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just).\n- Tem perguntas? Consulte este [Assistente de IA](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant), ou [crie uma issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 Junte-se ao nosso [Discord](https://discord.gg/hUHHE9Sa6T) para se conectar com outros desenvolvedores construindo com o Pocket Flow!\n- 🎉 O Pocket Flow é inicialmente em Python, mas agora temos versões em [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP) e [Go](https://github.com/The-Pocket/PocketFlow-Go)!\n\n## Por que Pocket Flow?\n\nOs frameworks LLM atuais são pesados... Você só precisa de 100 linhas para um Framework LLM!\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **Abstração**          | **Wrappers Específicos para Aplicações**                                      | **Wrappers Específicos para Fornecedores**                                    | **Linhas**       | **Tamanho**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agente, Cadeia               | Muitos <br><sup><sub>(ex: QA, Summarization)</sub></sup>              | Muitos <br><sup><sub>(ex: OpenAI, Pinecone, etc.)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agente, Cadeia            | Muitos <br><sup><sub>(ex: FileReadTool, SerperDevTool)</sub></sup>         | Muitos <br><sup><sub>(ex: OpenAI, Anthropic, Pinecone, etc.)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agente                      | Alguns <br><sup><sub>(ex: CodeAgent, VisitWebTool)</sub></sup>         | Alguns <br><sup><sub>(ex: DuckDuckGo, Hugging Face, etc.)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agente, Grafo           | Alguns <br><sup><sub>(ex: Semantic Search)</sub></sup>                     | Alguns <br><sup><sub>(ex: PostgresStore, SqliteSaver, etc.) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agente                | Alguns <br><sup><sub>(ex: Tool Agent, Chat Agent)</sub></sup>              | Muitos <sup><sub>[Opcional]<br> (ex: OpenAI, Pinecone, etc.)</sub></sup>        | 7K <br><sup><sub>(somente core)</sub></sup>    | +26MB <br><sup><sub>(somente core)</sub></sup>          |\n| **PocketFlow** | **Grafo**                    | **Nenhum**                                                 | **Nenhum**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Como funciona o Pocket Flow?\n\nAs [100 linhas](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) capturam a abstração central dos frameworks LLM: o Grafo!\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nA partir daí, é fácil implementar padrões de design populares como ([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agentes](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Fluxo de Trabalho](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), etc.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ Abaixo estão tutoriais básicos:\n\n<div align=\"center\">\n  \n|  Nome  | Dificuldade    |  Descrição  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *Básico*   | Um chatbot básico com histórico de conversação |\n| [Saída Estruturada](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *Básico* | Extraindo dados estruturados de currículos por prompt |\n| [Fluxo de Trabalho](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *Básico*   | Um fluxo de escrita que esboça, escreve conteúdo e aplica estilo |\n| [Agente](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Básico*   | Um agente de pesquisa que pode buscar na web e responder perguntas |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *Básico*   | Um processo simples de Geração Aumentada por Recuperação |\n| [Processamento em Lote](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *Básico* | Um processador em lote que traduz conteúdo markdown para vários idiomas |\n| [Streaming](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Básico*   | Uma demonstração de streaming LLM em tempo real com capacidade de interrupção pelo usuário |\n| [Guardrail de Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *Básico*  | Um chatbot de consultoria de viagens que processa apenas consultas relacionadas a viagens |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *Iniciante* | Um processador de qualificação de currículos usando o padrão map-reduce para avaliação em lote |\n| [Multi-Agente](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *Iniciante* | Um jogo de Tabu para comunicação assíncrona entre dois agentes |\n| [Supervisor](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Iniciante* | O agente de pesquisa está ficando pouco confiável... Vamos criar um processo de supervisão |\n| [Paralelo](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Iniciante*   | Uma demonstração de execução paralela que mostra um aumento de velocidade de 3x |\n| [Fluxo Paralelo](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *Iniciante*   | Uma demonstração de processamento de imagem paralelo mostrando um aumento de velocidade de 8x com múltiplos filtros |\n| [Voto Majoritário](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *Iniciante* | Melhore a precisão de raciocínio agregando múltiplas tentativas de solução |\n| [Pensamento](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Iniciante*   | Resolva problemas complexos de raciocínio através de Cadeia-de-Pensamento |\n| [Memória](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Iniciante* | Um chatbot com memória de curto e longo prazo |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *Iniciante* | Converta linguagem natural para consultas SQL com um loop de autodepuração |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *Iniciante* | Agente usando o Protocolo de Contexto de Modelo para operações numéricas |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *Iniciante* | Agente envolvido com o protocolo Agente-para-Agente para comunicação entre agentes |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *Iniciante* | Um serviço web mínimo para um loop de revisão humana com atualizações SSE |\n\n</div>\n\n👀 Quer ver outros tutoriais para iniciantes? [Crie uma issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## Como usar o Pocket Flow?\n\n🚀 Através da **Codificação Agêntica**—o paradigma mais rápido de desenvolvimento de aplicativos LLM—onde *humanos projetam* e *agentes codificam*!\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ Abaixo estão exemplos de aplicativos LLM mais complexos:\n\n<div align=\"center\">\n  \n|  Nome do Aplicativo     |  Dificuldade    | Tópicos  | Design Humano | Código do Agente |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Construir Cursor com Cursor](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>Logo chegaremos à singularidade ...</sup></sub> | ★★★ <br> *Avançado*   | [Agente](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [Doc de Design](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Código de Fluxo](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [Construtor de Conhecimento de Base de Código](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>A vida é curta demais para ficar olhando o código dos outros em confusão</sup></sub> |  ★★☆ <br> *Médio* | [Fluxo de Trabalho](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [Doc de Design](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Código de Fluxo](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [Pergunte à IA Paul Graham](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>Pergunte à IA Paul Graham, caso você não consiga entrar</sup></sub> | ★★☆ <br> *Médio*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [Doc de Design](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Código de Fluxo](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Resumidor de Youtube](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> Explica vídeos do YouTube para você como se você tivesse 5 anos </sup></sub> | ★☆☆ <br> *Iniciante*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [Doc de Design](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Código de Fluxo](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [Gerador de Abertura a Frio](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> Quebra-gelos instantâneos que transformam leads frios em quentes </sup></sub> | ★☆☆ <br> *Iniciante*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Busca Web](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [Doc de Design](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Código de Fluxo](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- Quer aprender **Codificação Agêntica**?\n\n  - Confira [meu YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1) para tutoriais em vídeo sobre como alguns dos aplicativos acima são feitos!\n\n  - Quer construir seu próprio aplicativo LLM? Leia este [post](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)! Comece com [este modelo](https://github.com/The-Pocket/PocketFlow-Template-Python)!"
  },
  {
    "path": "cookbook/pocketflow-parallel-batch/translations/README_RUSSIAN.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-line minimalist LLM framework\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | [Español](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_SPANISH.md) | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | Русский| [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow — это минималистичный фреймворк для LLM всего в [100 строк](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)\n\n- **Легкий**: Всего 100 строк. Никакого лишнего веса, никаких зависимостей, никакой привязки к вендорам.\n  \n- **Выразительный**: Всё, что вы любите — ([Мульти-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Агенты](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Рабочие процессы](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) и многое другое.\n\n- **[Агентское кодирование](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: Позвольте ИИ-агентам (например, Cursor AI) создавать других агентов — повысьте продуктивность в 10 раз!\n\nНачало работы с Pocket Flow:\n- Для установки, ```pip install pocketflow``` или просто скопируйте [исходный код](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) (всего 100 строк).\n- Чтобы узнать больше, ознакомьтесь с [документацией](https://the-pocket.github.io/PocketFlow/). Чтобы понять мотивацию, прочитайте [историю](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just).\n- Есть вопросы? Спросите этого [ИИ-ассистента](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant) или [создайте issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 Присоединяйтесь к нашему [Discord](https://discord.gg/hUHHE9Sa6T), чтобы общаться с другими разработчиками, использующими Pocket Flow!\n- 🎉 Pocket Flow изначально написан на Python, но теперь у нас есть версии на [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP) и [Go](https://github.com/The-Pocket/PocketFlow-Go)!\n\n## Почему Pocket Flow?\n\nСовременные фреймворки для LLM слишком громоздкие... Для фреймворка LLM достаточно всего 100 строк!\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **Абстракция**          | **Обертки для конкретных приложений**                                      | **Обертки для конкретных вендоров**                                    | **Строк**       | **Размер**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agent, Chain               | Много <br><sup><sub>(напр., QA, Суммаризация)</sub></sup>              | Много <br><sup><sub>(напр., OpenAI, Pinecone и т.д.)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agent, Chain            | Много <br><sup><sub>(напр., FileReadTool, SerperDevTool)</sub></sup>         | Много <br><sup><sub>(напр., OpenAI, Anthropic, Pinecone и т.д.)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agent                      | Несколько <br><sup><sub>(напр., CodeAgent, VisitWebTool)</sub></sup>         | Несколько <br><sup><sub>(напр., DuckDuckGo, Hugging Face и т.д.)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agent, Graph           | Несколько <br><sup><sub>(напр., Semantic Search)</sub></sup>                     | Несколько <br><sup><sub>(напр., PostgresStore, SqliteSaver и т.д.) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agent                | Несколько <br><sup><sub>(напр., Tool Agent, Chat Agent)</sub></sup>              | Много <sup><sub>[Опционально]<br> (напр., OpenAI, Pinecone и т.д.)</sub></sup>        | 7K <br><sup><sub>(только ядро)</sub></sup>    | +26MB <br><sup><sub>(только ядро)</sub></sup>          |\n| **PocketFlow** | **Graph**                    | **Нет**                                                 | **Нет**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## Как работает Pocket Flow?\n\n[100 строк](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) охватывают ключевую абстракцию фреймворков LLM: Граф!\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nОтсюда легко реализовать популярные шаблоны проектирования, такие как ([Мульти-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Агенты](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Рабочие процессы](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) и другие.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ Ниже приведены базовые руководства:\n\n<div align=\"center\">\n  \n|  Название  | Сложность    |  Описание  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [Чат](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *Простейший*   | Базовый чат-бот с историей разговора |\n| [Структурированный вывод](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *Простейший* | Извлечение структурированных данных из резюме с помощью промптов |\n| [Рабочий процесс](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *Простейший*   | Процесс написания, который создает план, пишет контент и применяет стили |\n| [Агент](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Простейший*   | Исследовательский агент, который может искать в интернете и отвечать на вопросы |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *Простейший*   | Простой процесс генерации с извлечением информации |\n| [Пакетная обработка](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *Простейший* | Пакетный процессор, который переводит markdown-контент на несколько языков |\n| [Потоковая передача](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Простейший*   | Демонстрация потоковой передачи LLM в реальном времени с возможностью прерывания пользователем |\n| [Ограничение чата](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *Простейший*  | Чат-бот туристического консультанта, обрабатывающий только запросы, связанные с путешествиями |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *Начальный* | Процессор квалификации резюме, использующий паттерн map-reduce для пакетной оценки |\n| [Мульти-агент](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *Начальный* | Игра Табу для асинхронного общения между двумя агентами |\n| [Супервизор](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Начальный* | Исследовательский агент становится ненадежным... Давайте создадим процесс надзора|\n| [Параллельное выполнение](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Начальный*   | Демонстрация параллельного выполнения, показывающая 3-кратное ускорение |\n| [Параллельный поток](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *Начальный*   | Демонстрация параллельной обработки изображений, показывающая 8-кратное ускорение с несколькими фильтрами |\n| [Голосование большинством](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *Начальный* | Повышение точности рассуждений путем агрегации нескольких попыток решения |\n| [Мышление](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Начальный*   | Решение сложных задач рассуждения с помощью цепочки размышлений |\n| [Память](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Начальный* | Чат-бот с кратковременной и долговременной памятью |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *Начальный* | Преобразование естественного языка в SQL-запросы с автоматическим циклом отладки |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *Начальный* | Агент, использующий протокол контекста модели для числовых операций |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *Начальный* | Агент, обернутый протоколом агент-к-агенту для межагентного взаимодействия |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *Начальный* | Минимальный веб-сервис для цикла проверки человеком с обновлениями SSE |\n\n</div>\n\n👀 Хотите увидеть другие руководства для начинающих? [Создайте issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## Как использовать Pocket Flow?\n\n🚀 Через **Агентское кодирование** — самую быструю парадигму разработки LLM-приложений, где *люди проектируют*, а *агенты кодируют*!\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ Ниже приведены примеры более сложных LLM-приложений:\n\n<div align=\"center\">\n  \n|  Название приложения     |  Сложность    | Темы  | Дизайн от человека | Код от агента |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Создание Cursor с помощью Cursor](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>Скоро достигнем сингулярности ...</sup></sub> | ★★★ <br> *Продвинутый*   | [Агент](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [Документация по дизайну](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Код потока](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [Конструктор знаний о кодовой базе](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>Жизнь слишком коротка, чтобы в растерянности смотреть на чужой код</sup></sub> |  ★★☆ <br> *Средний* | [Рабочий процесс](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [Документация по дизайну](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Код потока](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [Спроси ИИ Пола Грэма](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>Спроси ИИ Пола Грэма, если тебя не приняли</sup></sub> | ★★☆ <br> *Средний*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [Документация по дизайну](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Код потока](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Суммаризатор YouTube](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> Объясняет YouTube-видео как для 5-летнего </sup></sub> | ★☆☆ <br> *Начальный*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [Документация по дизайну](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Код потока](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [Генератор холодных открытий](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> Мгновенные ледоколы, превращающие холодных лидов в горячих </sup></sub> | ★☆☆ <br> *Начальный*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Веб-поиск](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [Документация по дизайну](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Код потока](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- Хотите изучить **Агентское кодирование**?\n\n  - Посмотрите [мой YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1) для видеоуроков о том, как создаются некоторые из вышеперечисленных приложений!\n\n  - Хотите создать свое собственное LLM-приложение? Прочитайте эту [статью](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)! Начните с [этого шаблона](https://github.com/The-Pocket/PocketFlow-Template-Python)!"
  },
  {
    "path": "cookbook/pocketflow-parallel-batch/translations/README_SPANISH.md",
    "content": "<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/title.png\" alt=\"Pocket Flow – 100-line minimalist LLM framework\" width=\"600\"/>\n</div>\n\n<!-- [English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) -->\n\n[English](https://github.com/The-Pocket/PocketFlow/blob/main/README.md) | [中文](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_CHINESE.md) | Español | [日本語](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_JAPANESE.md) | [Deutsch](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_GERMAN.md) | [Русский](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_RUSSIAN.md) | [Português](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_PORTUGUESE.md) | [Français](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_FRENCH.md) | [한국어](https://github.com/The-Pocket/PocketFlow/blob/main/cookbook/pocketflow-batch/translations/README_KOREAN.md)\n\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://the-pocket.github.io/PocketFlow/)\n <a href=\"https://discord.gg/hUHHE9Sa6T\">\n    <img src=\"https://img.shields.io/discord/1346833819172601907?logo=discord&style=flat\">\n</a>\n\nPocket Flow es un framework minimalista de LLM de [100 líneas](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py)\n\n- **Ligero**: Solo 100 líneas. Cero hinchazón, cero dependencias, cero vinculación a proveedores.\n  \n- **Expresivo**: Todo lo que amas—([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agentes](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Flujo de Trabajo](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), y más.\n\n- **[Programación mediante Agentes](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)**: Permite que los Agentes de IA (por ejemplo, Cursor AI) construyan Agentes—¡multiplicando la productividad por 10!\n\nComienza con Pocket Flow:\n- Para instalar, ```pip install pocketflow``` o simplemente copia el [código fuente](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) (solo 100 líneas).\n- Para aprender más, consulta la [documentación](https://the-pocket.github.io/PocketFlow/). Para conocer la motivación, lee la [historia](https://zacharyhuang.substack.com/p/i-built-an-llm-framework-in-just).\n- ¿Tienes preguntas? Consulta este [Asistente de IA](https://chatgpt.com/g/g-677464af36588191b9eba4901946557b-pocket-flow-assistant), o [¡crea un issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n- 🎉 ¡Únete a nuestro [Discord](https://discord.gg/hUHHE9Sa6T) para conectar con otros desarrolladores construyendo con Pocket Flow!\n- 🎉 Pocket Flow inicialmente está en Python, ¡pero ahora tenemos versiones en [Typescript](https://github.com/The-Pocket/PocketFlow-Typescript), [Java](https://github.com/The-Pocket/PocketFlow-Java), [C++](https://github.com/The-Pocket/PocketFlow-CPP) y [Go](https://github.com/The-Pocket/PocketFlow-Go)!\n\n## ¿Por qué Pocket Flow?\n\nLos frameworks actuales de LLM están sobrecargados... ¡Solo necesitas 100 líneas para un framework de LLM!\n\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/meme.jpg\" width=\"400\"/>\n\n\n  |                | **Abstracción**          | **Envolturas Específicas de Aplicación**                                      | **Envolturas Específicas de Proveedor**                                    | **Líneas**       | **Tamaño**    |\n|----------------|:-----------------------------: |:-----------------------------------------------------------:|:------------------------------------------------------------:|:---------------:|:----------------------------:|\n| LangChain  | Agente, Cadena               | Muchas <br><sup><sub>(p.ej., QA, Resumen)</sub></sup>              | Muchas <br><sup><sub>(p.ej., OpenAI, Pinecone, etc.)</sub></sup>                   | 405K          | +166MB                     |\n| CrewAI     | Agente, Cadena            | Muchas <br><sup><sub>(p.ej., FileReadTool, SerperDevTool)</sub></sup>         | Muchas <br><sup><sub>(p.ej., OpenAI, Anthropic, Pinecone, etc.)</sub></sup>        | 18K           | +173MB                     |\n| SmolAgent   | Agente                      | Algunas <br><sup><sub>(p.ej., CodeAgent, VisitWebTool)</sub></sup>         | Algunas <br><sup><sub>(p.ej., DuckDuckGo, Hugging Face, etc.)</sub></sup>           | 8K            | +198MB                     |\n| LangGraph   | Agente, Grafo           | Algunas <br><sup><sub>(p.ej., Búsqueda Semántica)</sub></sup>                     | Algunas <br><sup><sub>(p.ej., PostgresStore, SqliteSaver, etc.) </sub></sup>        | 37K           | +51MB                      |\n| AutoGen    | Agente                | Algunas <br><sup><sub>(p.ej., Tool Agent, Chat Agent)</sub></sup>              | Muchas <sup><sub>[Opcional]<br> (p.ej., OpenAI, Pinecone, etc.)</sub></sup>        | 7K <br><sup><sub>(solo-núcleo)</sub></sup>    | +26MB <br><sup><sub>(solo-núcleo)</sub></sup>          |\n| **PocketFlow** | **Grafo**                    | **Ninguna**                                                 | **Ninguna**                                                  | **100**       | **+56KB**                  |\n\n</div>\n\n## ¿Cómo funciona Pocket Flow?\n\nLas [100 líneas](https://github.com/The-Pocket/PocketFlow/blob/main/pocketflow/__init__.py) capturan la abstracción principal de los frameworks de LLM: ¡el Grafo!\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/abstraction.png\" width=\"900\"/>\n</div>\n<br>\n\nA partir de ahí, es fácil implementar patrones de diseño populares como ([Multi-](https://the-pocket.github.io/PocketFlow/design_pattern/multi_agent.html))[Agentes](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html), [Flujo de Trabajo](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html), [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html), etc.\n<br>\n<div align=\"center\">\n  <img src=\"https://github.com/The-Pocket/.github/raw/main/assets/design.png\" width=\"900\"/>\n</div>\n<br>\n✨ A continuación se presentan tutoriales básicos:\n\n<div align=\"center\">\n  \n|  Nombre  | Dificultad    |  Descripción  |  \n| :-------------:  | :-------------: | :--------------------- |  \n| [Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat) | ☆☆☆ <br> *Principiante*   | Un chatbot básico con historial de conversación |\n| [Salida Estructurada](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-structured-output) | ☆☆☆ <br> *Principiante* | Extracción de datos estructurados de currículums mediante prompts |\n| [Flujo de Trabajo](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-workflow) | ☆☆☆ <br> *Principiante*   | Un flujo de escritura que esquematiza, escribe contenido y aplica estilo |\n| [Agente](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Principiante*   | Un agente de investigación que puede buscar en la web y responder preguntas |\n| [RAG](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-rag) | ☆☆☆ <br> *Principiante*   | Un simple proceso de Generación aumentada por Recuperación |\n| [Procesamiento por Lotes](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-batch) | ☆☆☆ <br> *Principiante* | Un procesador por lotes que traduce contenido markdown a múltiples idiomas |\n| [Streaming](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Principiante*   | Una demostración de streaming LLM en tiempo real con capacidad de interrupción del usuario |\n| [Protección de Chat](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-guardrail) | ☆☆☆ <br> *Principiante*  | Un chatbot asesor de viajes que solo procesa consultas relacionadas con viajes |\n| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ★☆☆ <br> *Inicial* | Un procesador de calificación de currículums que utiliza el patrón map-reduce para evaluación por lotes |\n| [Multi-Agente](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *Inicial* | Un juego de palabras Tabú para comunicación asíncrona entre dos agentes |\n| [Supervisor](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Inicial* | El agente de investigación se vuelve poco fiable... Construyamos un proceso de supervisión|\n| [Paralelo](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Inicial*   | Una demostración de ejecución paralela que muestra una aceleración de 3x |\n| [Flujo Paralelo](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch-flow) | ★☆☆ <br> *Inicial*   | Una demostración de procesamiento de imágenes en paralelo que muestra una aceleración de 8x con múltiples filtros |\n| [Voto por Mayoría](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-majority-vote) | ★☆☆ <br> *Inicial* | Mejora de la precisión del razonamiento mediante la agregación de múltiples intentos de solución |\n| [Pensamiento](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Inicial*   | Resolver problemas de razonamiento complejos a través de Cadena de Pensamiento |\n| [Memoria](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Inicial* | Un chatbot con memoria a corto y largo plazo |\n| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆ <br> *Inicial* | Convertir lenguaje natural a consultas SQL con un bucle de auto-depuración |\n| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆ <br> *Inicial* | Agente que utiliza el Protocolo de Contexto de Modelo para operaciones numéricas |\n| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆ <br> *Inicial* | Agente envuelto con protocolo Agente-a-Agente para comunicación entre agentes |\n| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆ <br> *Inicial* | Un servicio web mínimo para un bucle de revisión humana con actualizaciones SSE |\n\n</div>\n\n👀 ¿Quieres ver otros tutoriales para principiantes? [¡Crea un issue!](https://github.com/The-Pocket/PocketFlow/issues/new)\n\n## ¿Cómo usar Pocket Flow?\n\n🚀 A través de la **Programación mediante Agentes**—el paradigma de desarrollo de aplicaciones LLM más rápido- donde *los humanos diseñan* y *los agentes programan*!\n\n<br>\n<div align=\"center\">\n  <a href=\"https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to\" target=\"_blank\">\n    <img src=\"https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F423a39af-49e8-483b-bc5a-88cc764350c6_1050x588.png\" width=\"700\" alt=\"IMAGE ALT TEXT\" style=\"cursor: pointer;\">\n  </a>\n</div>\n<br>\n\n✨ A continuación hay ejemplos de aplicaciones LLM más complejas:\n\n<div align=\"center\">\n  \n|  Nombre de la App     |  Dificultad    | Temas  | Diseño Humano | Código del Agente |\n| :-------------:  | :-------------: | :---------------------: |  :---: |  :---: |\n| [Construir Cursor con Cursor](https://github.com/The-Pocket/Tutorial-Cursor) <br> <sup><sub>Pronto alcanzaremos la singularidad ...</sup></sub> | ★★★ <br> *Avanzado*   | [Agente](https://the-pocket.github.io/PocketFlow/design_pattern/agent.html) | [Doc de Diseño](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/docs/design.md) | [Código de Flujo](https://github.com/The-Pocket/Tutorial-Cursor/blob/main/flow.py)\n| [Constructor de Conocimiento de Código Base](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) <br> <sup><sub>La vida es demasiado corta para mirar el código de otros con confusión</sup></sub> |  ★★☆ <br> *Medio* | [Flujo de Trabajo](https://the-pocket.github.io/PocketFlow/design_pattern/workflow.html) | [Doc de Diseño](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/docs/design.md) | [Código de Flujo](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/blob/main/flow.py)\n| [Pregunta a AI Paul Graham](https://github.com/The-Pocket/Tutorial-YC-Partner) <br> <sup><sub>Pregunta a AI Paul Graham, en caso de que no entres</sup></sub> | ★★☆ <br> *Medio*  | [RAG](https://the-pocket.github.io/PocketFlow/design_pattern/rag.html) <br> [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [TTS](https://the-pocket.github.io/PocketFlow/utility_function/text_to_speech.html) | [Doc de Diseño](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/docs/design.md) | [Código de Flujo](https://github.com/The-Pocket/Tutorial-AI-Paul-Graham/blob/main/flow.py)\n| [Resumidor de Youtube](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple)  <br> <sup><sub> Explica videos de YouTube como si tuvieras 5 años </sup></sub> | ★☆☆ <br> *Principiante*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) |  [Doc de Diseño](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/docs/design.md) | [Código de Flujo](https://github.com/The-Pocket/Tutorial-Youtube-Made-Simple/blob/main/flow.py)\n| [Generador de Introducción para Email Frío](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization)  <br> <sup><sub> Rompehielos instantáneos que convierten leads fríos en calientes </sup></sub> | ★☆☆ <br> *Principiante*   | [Map Reduce](https://the-pocket.github.io/PocketFlow/design_pattern/mapreduce.html) <br> [Búsqueda Web](https://the-pocket.github.io/PocketFlow/utility_function/websearch.html) |  [Doc de Diseño](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/docs/design.md) | [Código de Flujo](https://github.com/The-Pocket/Tutorial-Cold-Email-Personalization/blob/master/flow.py)\n\n</div>\n\n- ¿Quieres aprender **Programación mediante Agentes**?\n\n  - ¡Consulta [mi YouTube](https://www.youtube.com/@ZacharyLLM?sub_confirmation=1) para ver tutoriales en video sobre cómo se crearon algunas de las aplicaciones anteriores!\n\n  - ¿Quieres construir tu propia aplicación LLM? ¡Lee este [post](https://zacharyhuang.substack.com/p/agentic-coding-the-most-fun-way-to)! ¡Comienza con [esta plantilla](https://github.com/The-Pocket/PocketFlow-Template-Python)!"
  },
  {
    "path": "cookbook/pocketflow-parallel-batch/utils.py",
    "content": "import os\nimport asyncio\nfrom anthropic import AsyncAnthropic\n\n# Async version of the simple wrapper, using Anthropic\nasync def call_llm(prompt):\n    \"\"\"Async wrapper for Anthropic API call.\"\"\"\n    client = AsyncAnthropic(api_key=os.environ.get(\"ANTHROPIC_API_KEY\", \"your-api-key\"))\n    response = await client.messages.create(\n        model=\"claude-3-7-sonnet-20250219\",\n        max_tokens=20000,\n        thinking={\n            \"type\": \"enabled\",\n            \"budget_tokens\": 16000\n        },\n        messages=[\n            {\"role\": \"user\", \"content\": prompt}\n        ],\n    )\n    return response.content[1].text\n\nif __name__ == \"__main__\":\n    async def run_test():\n        print(\"## Testing async call_llm with Anthropic\")\n        prompt = \"In a few words, what is the meaning of life?\"\n        print(f\"## Prompt: {prompt}\")\n        response = await call_llm(prompt)\n        print(f\"## Response: {response}\")\n\n    asyncio.run(run_test()) "
  },
  {
    "path": "cookbook/pocketflow-parallel-batch-flow/README.md",
    "content": "# Parallel Image Processor\n\nDemonstrates how AsyncParallelBatchFlow processes multiple images with multiple filters >8x faster than sequential processing.\n\n## Features\n\n  ```mermaid\n  graph TD\n      subgraph AsyncParallelBatchFlow[Image Processing Flow]\n          subgraph AsyncFlow[Per Image-Filter Flow]\n              A[Load Image] --> B[Apply Filter]\n              B --> C[Save Image]\n          end\n      end\n  ```\n  \n- Processes images with multiple filters in parallel\n- Applies three different filters (grayscale, blur, sepia)\n- Shows significant speed improvement over sequential processing\n- Manages system resources with semaphores\n\n## Run It\n\n```bash\npip install -r requirements.txt\npython main.py\n```\n\n## Output\n\n```=== Processing Images in Parallel ===\nParallel Image Processor\n------------------------------\nFound 3 images:\n- images/bird.jpg\n- images/cat.jpg\n- images/dog.jpg\n\nRunning sequential batch flow...\nProcessing 3 images with 3 filters...\nTotal combinations: 9\nLoading image: images/bird.jpg\nApplying grayscale filter...\nSaved: output/bird_grayscale.jpg\n...etc\n\nTiming Results:\nSequential batch processing: 13.76 seconds\nParallel batch processing: 1.71 seconds\nSpeedup: 8.04x\n\nProcessing complete! Check the output/ directory for results.\n```\n\n## Key Points\n\n- **Sequential**: Total time = sum of all item times\n  - Good for: Rate-limited APIs, maintaining order\n\n- **Parallel**: Total time ≈ longest single item time\n  - Good for: I/O-bound tasks, independent operations \n"
  },
  {
    "path": "cookbook/pocketflow-parallel-batch-flow/flow.py",
    "content": "\"\"\"Flow definitions for parallel image processing.\"\"\"\n\nfrom pocketflow import AsyncParallelBatchFlow, AsyncBatchFlow\nfrom nodes import LoadImage, ApplyFilter, SaveImage\n\ndef create_base_flow():\n    \"\"\"Create flow for processing a single image with one filter.\"\"\"\n    # Create nodes\n    load = LoadImage()\n    apply_filter = ApplyFilter()\n    save = SaveImage()\n    \n    # Connect nodes\n    load - \"apply_filter\" >> apply_filter\n    apply_filter - \"save\" >> save\n    \n    # Create flow\n    return load\n\nclass ImageBatchFlow(AsyncBatchFlow):\n    \"\"\"Flow that processes multiple images with multiple filters in batch.\"\"\"\n    \n    async def prep_async(self, shared):\n        \"\"\"Generate parameters for each image-filter combination.\"\"\"\n        # Get list of images and filters\n        images = shared.get(\"images\", [])\n        filters = [\"grayscale\", \"blur\", \"sepia\"]\n        \n        # Create parameter combinations\n        params = []\n        for image_path in images:\n            for filter_type in filters:\n                params.append({\n                    \"image_path\": image_path,\n                    \"filter\": filter_type\n                })\n        \n        print(f\"Processing {len(images)} images with {len(filters)} filters...\")\n        print(f\"Total combinations: {len(params)}\")\n        return params\n\nclass ImageParallelBatchFlow(AsyncParallelBatchFlow):\n    \"\"\"Flow that processes multiple images with multiple filters in parallel.\"\"\"\n\n    async def prep_async(self, shared):\n        \"\"\"Generate parameters for each image-filter combination.\"\"\"\n        # Get list of images and filters\n        images = shared.get(\"images\", [])\n        filters = [\"grayscale\", \"blur\", \"sepia\"]\n        \n        # Create parameter combinations\n        params = []\n        for image_path in images:\n            for filter_type in filters:\n                params.append({\n                    \"image_path\": image_path,\n                    \"filter\": filter_type\n                })\n        \n        print(f\"Processing {len(images)} images with {len(filters)} filters...\")\n        print(f\"Total combinations: {len(params)}\")\n        return params\n\ndef create_flows():\n    \"\"\"Create the complete parallel processing flow.\"\"\"\n    # Create base flow for single image processing\n    base_flow = create_base_flow()\n    \n    # Wrap in parallel batch flow\n    return ImageBatchFlow(start=base_flow), ImageParallelBatchFlow(start=base_flow)"
  },
  {
    "path": "cookbook/pocketflow-parallel-batch-flow/main.py",
    "content": "import os\nimport asyncio\nimport time\nfrom flow import create_flows\n\ndef get_image_paths():\n    \"\"\"Get paths of existing images in the images directory.\"\"\"\n    images_dir = \"images\"\n    if not os.path.exists(images_dir):\n        raise ValueError(f\"Directory '{images_dir}' not found!\")\n    \n    # List all jpg files in the images directory\n    image_paths = []\n    for filename in os.listdir(images_dir):\n        if filename.lower().endswith(('.jpg', '.jpeg', '.png')):\n            image_paths.append(os.path.join(images_dir, filename))\n    \n    if not image_paths:\n        raise ValueError(f\"No images found in '{images_dir}' directory!\")\n    \n    print(f\"Found {len(image_paths)} images:\")\n    for path in image_paths:\n        print(f\"- {path}\")\n    \n    return image_paths\n\nasync def main():\n    \"\"\"Run the parallel image processing example.\"\"\"\n    print(\"Parallel Image Processor\")\n    print(\"-\" * 30)\n    \n    # Get existing image paths\n    image_paths = get_image_paths()\n    \n    # Create shared store with image paths\n    shared = {\"images\": image_paths}\n    \n    # Create both flows\n    batch_flow, parallel_batch_flow = create_flows()\n    \n    # Run and time batch flow\n    start_time = time.time()\n    print(\"\\nRunning sequential batch flow...\")\n    await batch_flow.run_async(shared)\n    batch_time = time.time() - start_time\n    \n    # Run and time parallel batch flow\n    start_time = time.time()\n    print(\"\\nRunning parallel batch flow...\")\n    await parallel_batch_flow.run_async(shared)\n    parallel_time = time.time() - start_time\n    \n    # Print timing results\n    print(\"\\nTiming Results:\")\n    print(f\"Sequential batch processing: {batch_time:.2f} seconds\")\n    print(f\"Parallel batch processing: {parallel_time:.2f} seconds\")\n    print(f\"Speedup: {batch_time/parallel_time:.2f}x\")\n    \n    print(\"\\nProcessing complete! Check the output/ directory for results.\")\n\nif __name__ == \"__main__\":\n    asyncio.run(main()) "
  },
  {
    "path": "cookbook/pocketflow-parallel-batch-flow/nodes.py",
    "content": "\"\"\"AsyncNode implementations for image processing.\"\"\"\nimport os\nimport asyncio\nfrom PIL import Image, ImageFilter\nimport numpy as np\nfrom pocketflow import AsyncNode\n\nclass LoadImage(AsyncNode):\n    \"\"\"Node that loads an image from file.\"\"\"\n    async def prep_async(self, shared):\n        \"\"\"Get image path from parameters.\"\"\"\n        image_path = self.params[\"image_path\"]\n        print(f\"Loading image: {image_path}\")\n        return image_path\n    \n    async def exec_async(self, image_path):\n        \"\"\"Load image using PIL.\"\"\"\n        # Simulate I/O delay\n        await asyncio.sleep(0.5)\n        return Image.open(image_path)\n    \n    async def post_async(self, shared, prep_res, exec_res):\n        \"\"\"Store image in shared store.\"\"\"\n        shared[\"image\"] = exec_res\n        return \"apply_filter\"\n\nclass ApplyFilter(AsyncNode):\n    \"\"\"Node that applies a filter to an image.\"\"\"\n    async def prep_async(self, shared):\n        \"\"\"Get image and filter type.\"\"\"\n        image = shared[\"image\"]\n        filter_type = self.params[\"filter\"]\n        print(f\"Applying {filter_type} filter...\")\n        return image, filter_type\n    \n    async def exec_async(self, inputs):\n        \"\"\"Apply the specified filter.\"\"\"\n        image, filter_type = inputs\n        \n        # Simulate processing delay\n        await asyncio.sleep(0.5)\n        \n        if filter_type == \"grayscale\":\n            return image.convert(\"L\")\n        elif filter_type == \"blur\":\n            return image.filter(ImageFilter.BLUR)\n        elif filter_type == \"sepia\":\n            # Convert to array for sepia calculation\n            img_array = np.array(image)\n            sepia_matrix = np.array([\n                [0.393, 0.769, 0.189],\n                [0.349, 0.686, 0.168],\n                [0.272, 0.534, 0.131]\n            ])\n            sepia_array = img_array.dot(sepia_matrix.T)\n            sepia_array = np.clip(sepia_array, 0, 255).astype(np.uint8)\n            return Image.fromarray(sepia_array)\n        else:\n            raise ValueError(f\"Unknown filter: {filter_type}\")\n    \n    async def post_async(self, shared, prep_res, exec_res):\n        \"\"\"Store filtered image.\"\"\"\n        shared[\"filtered_image\"] = exec_res\n        return \"save\"\n\nclass SaveImage(AsyncNode):\n    \"\"\"Node that saves the processed image.\"\"\"\n    async def prep_async(self, shared):\n        \"\"\"Prepare output path.\"\"\"\n        image = shared[\"filtered_image\"]\n        base_name = os.path.splitext(os.path.basename(self.params[\"image_path\"]))[0]\n        filter_type = self.params[\"filter\"]\n        output_path = f\"output/{base_name}_{filter_type}.jpg\"\n        \n        # Create output directory if needed\n        os.makedirs(\"output\", exist_ok=True)\n        \n        return image, output_path\n    \n    async def exec_async(self, inputs):\n        \"\"\"Save the image.\"\"\"\n        image, output_path = inputs\n        \n        # Simulate I/O delay\n        await asyncio.sleep(0.5)\n        \n        image.save(output_path)\n        return output_path\n    \n    async def post_async(self, shared, prep_res, exec_res):\n        \"\"\"Print success message.\"\"\"\n        print(f\"Saved: {exec_res}\")\n        return \"default\" "
  },
  {
    "path": "cookbook/pocketflow-parallel-batch-flow/requirements.txt",
    "content": "pocketflow\nPillow>=10.0.0  # For image processing\nnumpy>=1.24.0   # For image array operations "
  },
  {
    "path": "cookbook/pocketflow-rag/README.md",
    "content": "# Retrieval Augmented Generation (RAG)\n\nThis project demonstrates a simplified RAG system that retrieves relevant documents based on user queries and generates answers using an LLM. This implementation is based directly on the tutorial: [Retrieval Augmented Generation (RAG) from Scratch — Tutorial For Dummies](https://zacharyhuang.substack.com/p/retrieval-augmented-generation-rag).\n\n\n## Features\n\n- Document chunking for processing long texts\n- FAISS-powered vector-based document retrieval\n- LLM-powered answer generation\n\n## How to Run\n\n1. Set your API key:\n   ```bash\n   export OPENAI_API_KEY=\"your-api-key-here\"\n   ```\n   Or update it directly in `utils.py`\n\n   Let's do a quick check to make sure your API key is working properly:\n\n   ```bash\n   python utils.py\n   ```\n\n2. Install and run with the default query:\n   ```bash\n   pip install -r requirements.txt\n   python main.py\n   ```\n\n3. Run the application with a sample query:\n\n   ```bash\n   python main.py --\"How does the Q-Mesh protocol achieve high transaction speeds?\"\n   ```\n\n## How It Works\n\nThe magic happens through a two-phase pipeline implemented with PocketFlow:\n\n```mermaid\ngraph TD\n    subgraph OfflineFlow[Offline Document Indexing]\n        ChunkDocs[ChunkDocumentsNode] --> EmbedDocs[EmbedDocumentsNode] --> CreateIndex[CreateIndexNode]\n    end\n    \n    subgraph OnlineFlow[Online Processing]\n        EmbedQuery[EmbedQueryNode] --> RetrieveDoc[RetrieveDocumentNode] --> GenerateAnswer[GenerateAnswerNode]\n    end\n```\n\nHere's what each part does:\n1. **ChunkDocumentsNode**: Breaks documents into smaller chunks for better retrieval\n2. **EmbedDocumentsNode**: Converts document chunks into vector representations\n3. **CreateIndexNode**: Creates a searchable FAISS index from embeddings\n4. **EmbedQueryNode**: Converts user query into the same vector space\n5. **RetrieveDocumentNode**: Finds the most similar document using vector search\n6. **GenerateAnswerNode**: Uses an LLM to generate an answer based on the retrieved content\n\n## Example Output\n\n```\n✅ Created 5 chunks from 5 documents\n✅ Created 5 document embeddings\n🔍 Creating search index...\n✅ Index created with 5 vectors\n🔍 Embedding query: How to install PocketFlow?\n🔎 Searching for relevant documents...\n📄 Retrieved document (index: 0, distance: 0.3427)\n📄 Most relevant text: \"Pocket Flow is a 100-line minimalist LLM framework\n        Lightweight: Just 100 lines. Zero bloat, zero dependencies, zero vendor lock-in.\n        Expressive: Everything you love—(Multi-)Agents, Workflow, RAG, and more.\n        Agentic Coding: Let AI Agents (e.g., Cursor AI) build Agents—10x productivity boost!\n        To install, pip install pocketflow or just copy the source code (only 100 lines).\"\n\n🤖 Generated Answer:\nTo install PocketFlow, use the command `pip install pocketflow` or simply copy its 100 lines of source code.\n```\n"
  },
  {
    "path": "cookbook/pocketflow-rag/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import EmbedDocumentsNode, CreateIndexNode, EmbedQueryNode, RetrieveDocumentNode, ChunkDocumentsNode, GenerateAnswerNode\n\ndef get_offline_flow():\n    # Create offline flow for document indexing\n    chunk_docs_node = ChunkDocumentsNode()\n    embed_docs_node = EmbedDocumentsNode()\n    create_index_node = CreateIndexNode()\n    \n    # Connect the nodes\n    chunk_docs_node >> embed_docs_node >> create_index_node\n    \n    offline_flow = Flow(start=chunk_docs_node)\n    return offline_flow\n\ndef get_online_flow():\n    # Create online flow for document retrieval and answer generation\n    embed_query_node = EmbedQueryNode()\n    retrieve_doc_node = RetrieveDocumentNode()\n    generate_answer_node = GenerateAnswerNode()\n    \n    # Connect the nodes\n    embed_query_node >> retrieve_doc_node >> generate_answer_node\n    \n    online_flow = Flow(start=embed_query_node)\n    return online_flow\n\n# Initialize flows\noffline_flow = get_offline_flow()\nonline_flow = get_online_flow()"
  },
  {
    "path": "cookbook/pocketflow-rag/main.py",
    "content": "import sys\nfrom flow import offline_flow, online_flow\n\ndef run_rag_demo():\n    \"\"\"\n    Run a demonstration of the RAG system.\n    \n    This function:\n    1. Indexes a set of sample documents (offline flow)\n    2. Takes a query from the command line\n    3. Retrieves the most relevant document (online flow)\n    4. Generates an answer using an LLM\n    \"\"\"\n\n    # Sample texts - specialized/fictional content that benefits from RAG\n    texts = [\n        # PocketFlow framework\n        \"\"\"Pocket Flow is a 100-line minimalist LLM framework\n        Lightweight: Just 100 lines. Zero bloat, zero dependencies, zero vendor lock-in.\n        Expressive: Everything you love—(Multi-)Agents, Workflow, RAG, and more.\n        Agentic Coding: Let AI Agents (e.g., Cursor AI) build Agents—10x productivity boost!\n        To install, pip install pocketflow or just copy the source code (only 100 lines).\"\"\",\n        \n        # Fictional medical device\n        \"\"\"NeurAlign M7 is a revolutionary non-invasive neural alignment device.\n        Targeted magnetic resonance technology increases neuroplasticity in specific brain regions.\n        Clinical trials showed 72% improvement in PTSD treatment outcomes.\n        Developed by Cortex Medical in 2024 as an adjunct to standard cognitive therapy.\n        Portable design allows for in-home use with remote practitioner monitoring.\"\"\",\n        \n        # Made-up historical event\n        \"\"\"The Velvet Revolution of Caldonia (1967-1968) ended Generalissimo Verak's 40-year rule.\n        Led by poet Eliza Markovian through underground literary societies.\n        Culminated in the Great Silence Protest with 300,000 silent protesters.\n        First democratic elections held in March 1968 with 94% voter turnout.\n        Became a model for non-violent political transitions in neighboring regions.\"\"\",\n        \n        # Fictional technology \n        \"\"\"Q-Mesh is QuantumLeap Technologies' instantaneous data synchronization protocol.\n        Utilizes directed acyclic graph consensus for 500,000 transactions per second.\n        Consumes 95% less energy than traditional blockchain systems.\n        Adopted by three central banks for secure financial data transfer.\n        Released in February 2024 after five years of development in stealth mode.\"\"\",\n        \n        # Made-up scientific research\n        \"\"\"Harlow Institute's Mycelium Strain HI-271 removes 99.7% of PFAS from contaminated soil.\n        Engineered fungi create symbiotic relationships with native soil bacteria.\n        Breaks down \"forever chemicals\" into non-toxic compounds within 60 days.\n        Field tests successfully remediated previously permanently contaminated industrial sites.\n        Deployment costs 80% less than traditional chemical extraction methods.\"\"\"\n    ]\n    \n    print(\"=\" * 50)\n    print(\"PocketFlow RAG Document Retrieval\")\n    print(\"=\" * 50)\n    \n    # Default query about the fictional technology\n    default_query = \"How to install PocketFlow?\"\n    \n    # Get query from command line if provided with --\n    query = default_query\n    for arg in sys.argv[1:]:\n        if arg.startswith(\"--\"):\n            query = arg[2:]\n            break\n    \n    # Single shared store for both flows\n    shared = {\n        \"texts\": texts,\n        \"embeddings\": None,\n        \"index\": None,\n        \"query\": query,\n        \"query_embedding\": None,\n        \"retrieved_document\": None,\n        \"generated_answer\": None\n    }\n    \n    # Initialize and run the offline flow (document indexing)\n    offline_flow.run(shared)\n    \n    # Run the online flow to retrieve the most relevant document and generate an answer\n    online_flow.run(shared)\n\n\nif __name__ == \"__main__\":\n    run_rag_demo()"
  },
  {
    "path": "cookbook/pocketflow-rag/nodes.py",
    "content": "from pocketflow import Node, Flow, BatchNode\nimport numpy as np\nimport faiss\nfrom utils import call_llm, get_embedding, fixed_size_chunk\n\n# Nodes for the offline flow\nclass ChunkDocumentsNode(BatchNode):\n    def prep(self, shared):\n        \"\"\"Read texts from shared store\"\"\"\n        return shared[\"texts\"]\n    \n    def exec(self, text):\n        \"\"\"Chunk a single text into smaller pieces\"\"\"\n        return fixed_size_chunk(text)\n    \n    def post(self, shared, prep_res, exec_res_list):\n        \"\"\"Store chunked texts in the shared store\"\"\"\n        # Flatten the list of lists into a single list of chunks\n        all_chunks = []\n        for chunks in exec_res_list:\n            all_chunks.extend(chunks)\n        \n        # Replace the original texts with the flat list of chunks\n        shared[\"texts\"] = all_chunks\n        \n        print(f\"✅ Created {len(all_chunks)} chunks from {len(prep_res)} documents\")\n        return \"default\"\n    \nclass EmbedDocumentsNode(BatchNode):\n    def prep(self, shared):\n        \"\"\"Read texts from shared store and return as an iterable\"\"\"\n        return shared[\"texts\"]\n    \n    def exec(self, text):\n        \"\"\"Embed a single text\"\"\"\n        return get_embedding(text)\n    \n    def post(self, shared, prep_res, exec_res_list):\n        \"\"\"Store embeddings in the shared store\"\"\"\n        embeddings = np.array(exec_res_list, dtype=np.float32)\n        shared[\"embeddings\"] = embeddings\n        print(f\"✅ Created {len(embeddings)} document embeddings\")\n        return \"default\"\n\nclass CreateIndexNode(Node):\n    def prep(self, shared):\n        \"\"\"Get embeddings from shared store\"\"\"\n        return shared[\"embeddings\"]\n    \n    def exec(self, embeddings):\n        \"\"\"Create FAISS index and add embeddings\"\"\"\n        print(\"🔍 Creating search index...\")\n        dimension = embeddings.shape[1]\n        \n        # Create a flat L2 index\n        index = faiss.IndexFlatL2(dimension)\n        \n        # Add the embeddings to the index\n        index.add(embeddings)\n        \n        return index\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store the index in shared store\"\"\"\n        shared[\"index\"] = exec_res\n        print(f\"✅ Index created with {exec_res.ntotal} vectors\")\n        return \"default\"\n\n# Nodes for the online flow\nclass EmbedQueryNode(Node):\n    def prep(self, shared):\n        \"\"\"Get query from shared store\"\"\"\n        return shared[\"query\"]\n    \n    def exec(self, query):\n        \"\"\"Embed the query\"\"\"\n        print(f\"🔍 Embedding query: {query}\")\n        query_embedding = get_embedding(query)\n        return np.array([query_embedding], dtype=np.float32)\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store query embedding in shared store\"\"\"\n        shared[\"query_embedding\"] = exec_res\n        return \"default\"\n\nclass RetrieveDocumentNode(Node):\n    def prep(self, shared):\n        \"\"\"Get query embedding, index, and texts from shared store\"\"\"\n        return shared[\"query_embedding\"], shared[\"index\"], shared[\"texts\"]\n    \n    def exec(self, inputs):\n        \"\"\"Search the index for similar documents\"\"\"\n        print(\"🔎 Searching for relevant documents...\")\n        query_embedding, index, texts = inputs\n        \n        # Search for the most similar document\n        distances, indices = index.search(query_embedding, k=1)\n        \n        # Get the index of the most similar document\n        best_idx = indices[0][0]\n        distance = distances[0][0]\n        \n        # Get the corresponding text\n        most_relevant_text = texts[best_idx]\n        \n        return {\n            \"text\": most_relevant_text,\n            \"index\": best_idx,\n            \"distance\": distance\n        }\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store retrieved document in shared store\"\"\"\n        shared[\"retrieved_document\"] = exec_res\n        print(f\"📄 Retrieved document (index: {exec_res['index']}, distance: {exec_res['distance']:.4f})\")\n        print(f\"📄 Most relevant text: \\\"{exec_res['text']}\\\"\")\n        return \"default\"\n    \nclass GenerateAnswerNode(Node):\n    def prep(self, shared):\n        \"\"\"Get query, retrieved document, and any other context needed\"\"\"\n        return shared[\"query\"], shared[\"retrieved_document\"]\n    \n    def exec(self, inputs):\n        \"\"\"Generate an answer using the LLM\"\"\"\n        query, retrieved_doc = inputs\n        \n        prompt = f\"\"\"\nBriefly answer the following question based on the context provided:\nQuestion: {query}\nContext: {retrieved_doc['text']}\nAnswer:\n\"\"\"\n        \n        answer = call_llm(prompt)\n        return answer\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store generated answer in shared store\"\"\"\n        shared[\"generated_answer\"] = exec_res\n        print(\"\\n🤖 Generated Answer:\")\n        print(exec_res)\n        return \"default\"\n"
  },
  {
    "path": "cookbook/pocketflow-rag/requirements.txt",
    "content": "pocketflow>=0.0.1\nnumpy>=1.20.0\nfaiss-cpu>=1.7.0\nopenai>=1.0.0"
  },
  {
    "path": "cookbook/pocketflow-rag/utils.py",
    "content": "import os\nimport numpy as np\nfrom openai import OpenAI\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n\ndef get_embedding(text):\n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    \n    response = client.embeddings.create(\n        model=\"text-embedding-ada-002\",\n        input=text\n    )\n    \n    # Extract the embedding vector from the response\n    embedding = response.data[0].embedding\n    \n    # Convert to numpy array for consistency with other embedding functions\n    return np.array(embedding, dtype=np.float32)\n\ndef fixed_size_chunk(text, chunk_size=2000):\n    chunks = []\n    for i in range(0, len(text), chunk_size):\n        chunks.append(text[i : i + chunk_size])\n    return chunks\n\nif __name__ == \"__main__\":\n    print(\"=== Testing call_llm ===\")\n    prompt = \"In a few words, what is the meaning of life?\"\n    print(f\"Prompt: {prompt}\")\n    response = call_llm(prompt)\n    print(f\"Response: {response}\")\n\n    print(\"=== Testing embedding function ===\")\n    \n    text1 = \"The quick brown fox jumps over the lazy dog.\"\n    text2 = \"Python is a popular programming language for data science.\"\n    \n    oai_emb1 = get_embedding(text1)\n    oai_emb2 = get_embedding(text2)\n    print(f\"OpenAI Embedding 1 shape: {oai_emb1.shape}\")\n    oai_similarity = np.dot(oai_emb1, oai_emb2)\n    print(f\"OpenAI similarity between texts: {oai_similarity:.4f}\")"
  },
  {
    "path": "cookbook/pocketflow-streamlit-fsm/README.md",
    "content": "# PocketFlow Streamlit Image Generation HITL\n\nHuman-in-the-Loop (HITL) image generation application using PocketFlow and Streamlit. Enter text prompts, generate images with OpenAI, and approve/regenerate results.\n\n<p align=\"center\">\n  <img \n    src=\"./assets/banner.png\" width=\"800\"\n  />\n</p>\n\n## Features\n\n-   **Image Generation:** Uses OpenAI's `gpt-image-1` model to generate images from text prompts\n-   **Human Review:** Interactive interface to approve or regenerate images\n-   **State Machine:** Clean state-based workflow (`initial_input` → `user_feedback` → `final`)\n-   **PocketFlow Integration:** Uses PocketFlow `Node` and `Flow` for image generation with built-in retries\n-   **Session State Management:** Streamlit session state acts as PocketFlow's shared store\n-   **In-Memory Images:** Images stored as base64 strings, no disk storage required\n\n## How to Run\n\n1.  **Set OpenAI API Key:**\n    ```bash\n    export OPENAI_API_KEY=\"your-openai-api-key\"\n    ```\n\n2.  **Install Dependencies:**\n    ```bash\n    pip install -r requirements.txt\n    ```\n\n3.  **Run the Streamlit Application:**\n    ```bash\n    streamlit run app.py\n    ```\n\n4.  **Access the Web UI:**\n    Open the URL provided by Streamlit (usually `http://localhost:8501`).\n\n## Usage\n\n1. **Enter Prompt**: Describe the image you want to generate\n2. **Generate**: Click \"Generate Image\" to create the image\n3. **Review**: View the generated image and choose:\n   - **Approve**: Accept the image and move to final result\n   - **Regenerate**: Generate a new image with the same prompt\n4. **Final**: View approved image and optionally start over\n\n## Files\n\n-   [`app.py`](./app.py): Main Streamlit application with state-based UI\n-   [`nodes.py`](./nodes.py): PocketFlow `GenerateImageNode` definition\n-   [`flow.py`](./flow.py): PocketFlow `Flow` for image generation\n-   [`utils/generate_image.py`](./utils/generate_image.py): OpenAI image generation utility\n-   [`requirements.txt`](./requirements.txt): Project dependencies\n-   [`docs/design.md`](./docs/design.md): System design documentation\n-   [`README.md`](./README.md): This file\n"
  },
  {
    "path": "cookbook/pocketflow-streamlit-fsm/app.py",
    "content": "import streamlit as st\nimport base64\nfrom flow import create_generation_flow\n\nst.title(\"PocketFlow Image Generation HITL\")\n\n# Initialize session state for shared store\nif 'stage' not in st.session_state:\n    st.session_state.stage = \"initial_input\"\n    st.session_state.task_input = \"\"\n    st.session_state.generated_image = \"\"\n    st.session_state.final_result = \"\"\n    st.session_state.error_message = \"\"\n\n# Debug info\nwith st.expander(\"Session State\"):\n    st.json({k: v for k, v in st.session_state.items() if not k.startswith(\"_\")})\n\n# State-based UI\nif st.session_state.stage == \"initial_input\":\n    st.header(\"1. Generate Image\")\n    \n    prompt = st.text_area(\"Enter image prompt:\", value=st.session_state.task_input, height=100)\n    \n    if st.button(\"Generate Image\"):\n        if prompt.strip():\n            st.session_state.task_input = prompt\n            st.session_state.error_message = \"\"\n            \n            try:\n                with st.spinner(\"Generating image...\"):\n                    flow = create_generation_flow()\n                    flow.run(st.session_state)\n                st.rerun()\n            except Exception as e:\n                st.session_state.error_message = str(e)\n        else:\n            st.error(\"Please enter a prompt\")\n\nelif st.session_state.stage == \"user_feedback\":\n    st.header(\"2. Review Generated Image\")\n    \n    if st.session_state.generated_image:\n        # Display image\n        image_bytes = base64.b64decode(st.session_state.generated_image)\n        st.image(image_bytes, caption=f\"Prompt: {st.session_state.task_input}\")\n        \n        col1, col2 = st.columns(2)\n        \n        with col1:\n            if st.button(\"Approve\", use_container_width=True):\n                st.session_state.final_result = st.session_state.generated_image\n                st.session_state.stage = \"final\"\n                st.rerun()\n        \n        with col2:\n            if st.button(\"Regenerate\", use_container_width=True):\n                try:\n                    with st.spinner(\"Regenerating image...\"):\n                        flow = create_generation_flow()\n                        flow.run(st.session_state)\n                    st.rerun()\n                except Exception as e:\n                    st.session_state.error_message = str(e)\n\nelif st.session_state.stage == \"final\":\n    st.header(\"3. Final Result\")\n    st.success(\"Image approved!\")\n    \n    if st.session_state.final_result:\n        image_bytes = base64.b64decode(st.session_state.final_result)\n        st.image(image_bytes, caption=f\"Final approved image: {st.session_state.task_input}\")\n    \n    if st.button(\"Start Over\", use_container_width=True):\n        st.session_state.stage = \"initial_input\"\n        st.session_state.task_input = \"\"\n        st.session_state.generated_image = \"\"\n        st.session_state.final_result = \"\"\n        st.session_state.error_message = \"\"\n        st.rerun()\n\n# Show errors\nif st.session_state.error_message:\n    st.error(st.session_state.error_message)\n\n"
  },
  {
    "path": "cookbook/pocketflow-streamlit-fsm/docs/design.md",
    "content": "# Design Doc: PocketFlow Streamlit Image Generation HITL\n\n> Human-in-the-Loop image generation application using PocketFlow and Streamlit\n\n## Requirements\n\n**User Story**: As a user, I want to:\n1. Enter a text prompt describing an image I want to generate\n2. Have the system generate an image based on my prompt using OpenAI's image generation API\n3. Review the generated image in the web interface\n4. Approve the image if I'm satisfied, OR regenerate with the same prompt if I want a different result\n5. See the final approved image as the completed result\n\n**Technical Requirements**:\n- Use OpenAI's image generation API (via responses.create with image_generation tool)\n- Keep generated images in memory (base64 format) - no disk storage\n- Provide clear approve/regenerate workflow\n- Handle API errors gracefully with retries\n- Maintain session state between generations\n\n## Flow Design\n\n### Applicable Design Pattern:\n\n**State Machine with Multiple Subflows**: Each state has its own user interface and workflow. Users interact with different UI elements in each state, and the app transitions to the next state based on user actions and feedback.\n\n### States & User Interface:\n\n1. **initial_input** - User sees text input field, enters prompt, clicks \"Generate Image\" button\n2. **user_feedback** - User sees generated image, has \"Approve\" and \"Regenerate\" buttons \n3. **final** - User sees final approved image and \"Start Over\" button\n\n### Flow High-level Design & Transitions:\n\n```mermaid\nflowchart TD\n    Start([Start]) --> IS[initial_input]\n    IS --> GI[GenerateImage]\n    GI --> UF[user_feedback]\n    UF -->|Regenerate| GI\n    UF -->|Approve| F[final]\n    F --> IS\n    \n    %% Legend\n    classDef stateStyle fill:#e1f5fe,stroke:#01579b,stroke-width:2px\n    classDef nodeStyle fill:#fff3e0,stroke:#e65100,stroke-width:2px\n    \n    class IS,UF,F stateStyle\n    class GI nodeStyle\n```\n\n**Legend:**\n- 🔷 **Blue rectangles**: User interface states (initial_input, user_feedback, final)\n- 🔶 **Orange rectangles**: PocketFlow processing nodes (GenerateImage)\n\n## Utility Functions\n\n1. **Generate Image** (`utils/generate_image.py`)\n   - *Input*: prompt (str)\n   - *Output*: base64 image data (str)\n   - *Purpose*: Calls OpenAI's image generation API and returns base64 encoded image\n   - *Error Handling*: Includes retry logic for API failures\n\n## Node Design\n\n### Shared Memory\n\n**Using Streamlit Session State as Shared Store**: We use `st.session_state` directly as the shared store for PocketFlow, eliminating the need for separate data structures.\n\nThe session state structure for the image generation workflow:\n\n```python\nst.session_state = {\n    # User input and workflow state\n    \"task_input\": \"user's text prompt for image generation\",\n    \"stage\": \"current workflow stage (initial_input/user_feedback/final)\",\n    \"error_message\": \"any error messages for user feedback\",\n    \n    # Processing data\n    \"input_used_by_process\": \"prompt used for generation\",\n    \"generated_image\": \"base64 encoded image data\",\n    \"final_result\": \"final approved image data\",\n    \n    # Streamlit built-in keys (managed automatically)\n    # \"_streamlit_*\": various internal streamlit state\n}\n```\n\n### Node Steps\n\n**Initial Input Flow Nodes:**\n\n1. **Image Generation Node**\n   - *Purpose*: Generate image using OpenAI API based on the prompt\n   - *Type*: Regular (with retries for API reliability)\n   - *Steps*:\n     - *prep*: Read \"input_used_by_process\" from st.session_state\n     - *exec*: Call generate_image utility with the prompt, return base64 image data\n     - *post*: Write base64 image data to \"generated_image\" in st.session_state\n\n**User Feedback Flow:**\n- Reuses the same `GenerateImage` node when user clicks \"Regenerate\"\n\n**Final Flow:**\n- No processing nodes needed - the `final` state simply displays the approved image from `generated_image` and provides UI for starting over\n"
  },
  {
    "path": "cookbook/pocketflow-streamlit-fsm/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import GenerateImageNode\n\ndef create_generation_flow():\n    \"\"\"Creates a flow for image generation (initial or regeneration).\"\"\"\n    generate_image_node = GenerateImageNode()\n    return Flow(start=generate_image_node)\n\n\n"
  },
  {
    "path": "cookbook/pocketflow-streamlit-fsm/nodes.py",
    "content": "from pocketflow import Node\nfrom utils.generate_image import generate_image\n\nclass GenerateImageNode(Node):\n    \"\"\"Generates image from text prompt using OpenAI API.\"\"\"\n    \n    def prep(self, shared):\n        return shared.get(\"task_input\", \"\")\n\n    def exec(self, prompt):\n        return generate_image(prompt)\n\n    def post(self, shared, prep_res, exec_res):\n        shared[\"input_used_by_process\"] = prep_res\n        shared[\"generated_image\"] = exec_res\n        shared[\"stage\"] = \"user_feedback\"\n        return \"default\""
  },
  {
    "path": "cookbook/pocketflow-streamlit-fsm/requirements.txt",
    "content": "streamlit\npocketflow\nopenai\n"
  },
  {
    "path": "cookbook/pocketflow-streamlit-fsm/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-streamlit-fsm/utils/generate_image.py",
    "content": "from openai import OpenAI\nimport os\nimport base64\n\ndef generate_image(prompt: str) -> str:\n    client = OpenAI(api_key=os.getenv(\"OPENAI_API_KEY\"))\n    \n    response = client.images.generate(\n        model=\"gpt-image-1\",\n        prompt=prompt,\n        n=1,\n        size=\"1024x1024\"\n    )\n    \n    image_b64 = response.data[0].b64_json\n    print(f\"Generated image ({len(image_b64)} chars)\")\n    return image_b64\n\nif __name__ == \"__main__\":\n    test_prompt = \"A gray tabby cat hugging an otter with an orange scarf\"\n    print(f\"Generating image for prompt: {test_prompt[:50]}...\")\n    \n    image_base64 = generate_image(test_prompt)\n    print(f\"Success! Generated {len(image_base64)} characters of base64 data\")\n    \n    # Write image to local file for testing\n    image_bytes = base64.b64decode(image_base64)\n    with open(\"test_generated_image.png\", \"wb\") as f:\n        f.write(image_bytes)\n    print(\"Test image saved as test_generated_image.png\") "
  },
  {
    "path": "cookbook/pocketflow-structured-output/README.md",
    "content": "# Structured Output Demo\n\nA minimal demo application showing how to use PocketFlow to extract structured data from a resume using direct prompting and YAML formatting. Why YAML? Check out the [doc](https://the-pocket.github.io/PocketFlow/design_pattern/structure.html).\n\nThis implementation is based on: [Structured Output for Beginners: 3 Must-Know Prompting Tips](https://zacharyhuang.substack.com/p/structured-output-for-beginners-3).\n\n## Features\n\n- Extracts structured data using prompt engineering\n- Validates output structure before processing\n\n## Run It\n\n1. Install the packages you need with this simple command:\n    ```bash\n    pip install -r requirements.txt\n    ```\n\n2. Make sure your OpenAI API key is set:\n    ```bash\n    export OPENAI_API_KEY=\"your-api-key-here\"\n    ```\n    Alternatively, you can edit the [`utils.py`](./utils.py) file to include your API key directly.\n\n    Let's do a quick check to make sure your API key is working properly:\n\n    ```bash\n    python utils.py\n    ```\n\n3. Edit [data.txt](./data.txt) with the resume you want to parse (a sample resume is already included)\n\n4. Run the application:\n    ```bash\n    python main.py\n    ```\n\n## How It Works\n\n```mermaid\nflowchart LR\n    parser[ResumeParserNode]\n```\n\nThe Resume Parser application uses a single node that:\n1. Takes resume text from the shared state (loaded from data.txt)\n2. Sends the resume to an LLM with a prompt that requests YAML formatted output\n3. Extracts and validates the structured YAML data\n4. Outputs the structured result\n\n## Files\n\n- [`main.py`](./main.py): Implementation of the ResumeParserNode\n- [`utils.py`](./utils.py): LLM utilities\n- [`data.txt`](./data.txt): Sample resume text file\n \n## Example Output\n\n```\n=== Resume Parser - Structured Output with Indexes & Comments ===\n\n\n=== STRUCTURED RESUME DATA (Comments & Skill Index List) ===\n\nname: JOHN SMTIH\nemail: johnsmtih1983@gnail.com\nexperience:\n- {title: SALES MANAGER, company: ABC Corportaion}\n- {title: ASST. MANAGER, company: XYZ Industries}\n- {title: CUSTOMER SERVICE REPRESENTATIVE, company: Fast Solutions Inc}\nskill_indexes: [0, 1, 2, 3, 4]\n\n\n============================================================\n\n✅ Extracted resume information.\n\n--- Found Target Skills (from Indexes) ---\n- Team leadership & management (Index: 0)\n- CRM software (Index: 1)\n- Project management (Index: 2)\n- Public speaking (Index: 3)\n- Microsoft Office (Index: 4)\n----------------------------------------\n```\n"
  },
  {
    "path": "cookbook/pocketflow-structured-output/data.txt",
    "content": "# JOHN SMTIH\n\n**Email:** johnsmtih1983@gnail.com\n**Phone:** (555) 123-4556\n**Address:** 123 Main st, Anytown, USA\n\n## PROFFESIONAL SUMMARY\n\nDedicated and hardworking professional with over 10 years of exprience in business manegement. Known for finding creatve solutions to complex problems and excelent communication skills. Seeking new opportunites to leverage my expertise in a dynamic environment.\n\n## WORK EXPERENCE\n\n### SALES MANAGER\n**ABC Corportaion** | Anytown, USA | June 2018 - Present\n- Oversee a team of 12 sales represenatives and achieve quarterly targets\n- Increased departmnet revenue by 24% in fiscal year 2019-2020\n- Implemneted new CRM system that improved efficiency by 15%\n- Collabarate with Marketing team on product launch campaigns\n- Developed training materials for new hiers\n\n### ASST. MANAGER\n**XYZ Industries** | Somewhere Else, USA | March 2015 - may 2018\n- Assisted the Regional Manager in daily operations and reporting\n- managed inventory and vendor relations\n- Trained and mentored junior staff members\n- Recieved \"Employee of the Month\" award 4 times\n\n### CUSTOMER SERVICE REPRESENTATIVE\n**Fast Solutions Inc** | Another City, USA | January 2010 - February 2015\n* Responded to customer inquiries via phone email, and in-person\n* Resolved customer complaints and escalated issues when necessary\n* Maintained a 95% customer satsfaction rating\n\n\n## EDUCATIONS\n\n**Bachelor of Buisness Administration**\nUniversity of Somewhere | 2006 - 2010\nGPA: 3.6/4.0\n\n**Assosiate Degree in Communications**\nCommunity College | 2004-2006\n\n## SKILSS\n\n- Microsoft Office: *Excel, Word, Powerpoint* (Advanced)\n- Customer relationship management (CRM) software\n- Team leadership & managment\n- Project management\n- Public speking\n- Time managemant\n\n## REFERENCES\n\nAvailable upon reqeust\n\n### OTHER ACTVITIES\n- Volunteer at the local food bank (2016-present)\n- Member of Toastmasters International\n- Enjoy hiking and photografy"
  },
  {
    "path": "cookbook/pocketflow-structured-output/main.py",
    "content": "import yaml\nimport os  # Needed for the utils import below\nfrom pocketflow import Node, Flow\nfrom utils import call_llm # Assumes utils.py with call_llm exists\n\nclass ResumeParserNode(Node):\n    def prep(self, shared):\n        \"\"\"Return resume text and target skills from shared state.\"\"\"\n        return {\n            \"resume_text\": shared[\"resume_text\"],\n            \"target_skills\": shared.get(\"target_skills\", [])\n        }\n\n    def exec(self, prep_res):\n        \"\"\"Extract structured data from resume using prompt engineering.\n        Requests YAML output with comments and skill indexes as a list.\n        \"\"\"\n        resume_text = prep_res[\"resume_text\"]\n        target_skills = prep_res[\"target_skills\"]\n\n        # Format skills with indexes for the prompt\n        skill_list_for_prompt = \"\\n\".join([f\"{i}: {skill}\" for i, skill in enumerate(target_skills)])\n\n        # Simplified Prompt focusing on key instructions and format\n        prompt = f\"\"\"\nAnalyze the resume below. Output ONLY the requested information in YAML format.\n\n**Resume:**\n```\n{resume_text}\n```\n\n**Target Skills (use these indexes):**\n```\n{skill_list_for_prompt}\n```\n\n**YAML Output Requirements:**\n- Extract `name` (string).\n- Extract `email` (string).\n- Extract `experience` (list of objects with `title` and `company`).\n- Extract `skill_indexes` (list of integers found from the Target Skills list).\n- **Add a YAML comment (`#`) explaining the source BEFORE `name`, `email`, `experience`, each item in `experience`, and `skill_indexes`.**\n\n**Example Format:**\n```yaml\n# Found name at top\nname: Jane Doe\n# Found email in contact info\nemail: jane@example.com\n# Experience section analysis\nexperience:\n  # First job listed\n  - title: Manager\n    company: Corp A\n  # Second job listed\n  - title: Assistant\n    company: Corp B\n# Skills identified from the target list based on resume content\nskill_indexes:\n  # Found 0 at top  \n  - 0\n  # Found 2 in experience\n  - 2\n```\n\nGenerate the YAML output now:\n\"\"\"\n        response = call_llm(prompt)\n\n        # --- Minimal YAML Extraction ---\n        # Assumes LLM correctly uses ```yaml blocks\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        structured_result = yaml.safe_load(yaml_str)\n        # --- End Minimal Extraction ---\n\n        # --- Basic Validation ---\n        assert structured_result is not None, \"Validation Failed: Parsed YAML is None\"\n        assert \"name\" in structured_result, \"Validation Failed: Missing 'name'\"\n        assert \"email\" in structured_result, \"Validation Failed: Missing 'email'\"\n        assert \"experience\" in structured_result, \"Validation Failed: Missing 'experience'\"\n        assert isinstance(structured_result.get(\"experience\"), list), \"Validation Failed: 'experience' is not a list\"\n        assert \"skill_indexes\" in structured_result, \"Validation Failed: Missing 'skill_indexes'\"\n        skill_indexes_val = structured_result.get(\"skill_indexes\")\n        assert skill_indexes_val is None or isinstance(skill_indexes_val, list), \"Validation Failed: 'skill_indexes' is not a list or None\"\n        if isinstance(skill_indexes_val, list):\n             for index in skill_indexes_val:\n                 assert isinstance(index, int), f\"Validation Failed: Skill index '{index}' is not an integer\"\n        # --- End Basic Validation ---\n\n        return structured_result\n\n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store structured data and print it.\"\"\"\n        shared[\"structured_data\"] = exec_res\n\n        print(\"\\n=== STRUCTURED RESUME DATA (Comments & Skill Index List) ===\\n\")\n        # Dump YAML ensuring block style for readability\n        print(yaml.dump(exec_res, sort_keys=False, allow_unicode=True, default_flow_style=None))\n        print(\"\\n============================================================\\n\")\n        print(\"✅ Extracted resume information.\")\n\n\n# === Main Execution Logic ===\nif __name__ == \"__main__\":\n    print(\"=== Resume Parser - Structured Output with Indexes & Comments ===\\n\")\n\n    # --- Configuration ---\n    target_skills_to_find = [\n        \"Team leadership & management\", # 0\n        \"CRM software\",                 # 1\n        \"Project management\",           # 2\n        \"Public speaking\",              # 3\n        \"Microsoft Office\",             # 4\n        \"Python\",                       # 5\n        \"Data Analysis\"                 # 6\n    ]\n    resume_file = 'data.txt' # Assumes data.txt contains the resume\n\n    # --- Prepare Shared State ---\n    shared = {}\n    try:\n        with open(resume_file, 'r') as file:\n            shared[\"resume_text\"] = file.read()\n    except FileNotFoundError:\n        print(f\"Error: Resume file '{resume_file}' not found.\")\n        exit(1) # Exit if resume file is missing\n\n    shared[\"target_skills\"] = target_skills_to_find\n\n    # --- Define and Run Flow ---\n    parser_node = ResumeParserNode(max_retries=3, wait=10)\n    flow = Flow(start=parser_node)\n    flow.run(shared) # Execute the parsing node\n\n    # --- Display Found Skills ---\n    if \"structured_data\" in shared and \"skill_indexes\" in shared[\"structured_data\"]:\n         print(\"\\n--- Found Target Skills (from Indexes) ---\")\n         found_indexes = shared[\"structured_data\"][\"skill_indexes\"]\n         if found_indexes: # Check if the list is not empty or None\n             for index in found_indexes:\n                 if 0 <= index < len(target_skills_to_find):\n                     print(f\"- {target_skills_to_find[index]} (Index: {index})\")\n                 else:\n                     print(f\"- Warning: Found invalid skill index {index}\")\n         else:\n             print(\"No target skills identified from the list.\")\n         print(\"----------------------------------------\\n\")"
  },
  {
    "path": "cookbook/pocketflow-structured-output/requirements.txt",
    "content": "pocketflow>=0.0.1\nopenai>=1.0.0"
  },
  {
    "path": "cookbook/pocketflow-structured-output/utils.py",
    "content": "import os\nfrom openai import OpenAI\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n\n# Example usage\nif __name__ == \"__main__\":\n    print(call_llm(\"Tell me a short joke\")) "
  },
  {
    "path": "cookbook/pocketflow-supervisor/README.md",
    "content": "# Research Supervisor\n\nThis project demonstrates a supervisor that oversees an unreliable [research agent](../pocketflow-agent) to ensure high-quality answers.\n\n## Features\n\n- Evaluates responses for quality and relevance\n- Rejects nonsensical or unreliable answers\n- Requests new answers until a quality response is produced\n\n## Getting Started\n\n1. Install the packages you need with this simple command:\n```bash\npip install -r requirements.txt\n```\n\n2. Let's get your OpenAI API key ready:\n\n```bash\nexport OPENAI_API_KEY=\"your-api-key-here\"\n```\n\n3. Let's do a quick check to make sure your API key is working properly:\n\n```bash\npython utils.py\n```\n\nThis will test both the LLM call and web search features. If you see responses, you're good to go!\n\n4. Try out the agent with the default question (about Nobel Prize winners):\n\n```bash\npython main.py\n```\n\n5. Got a burning question? Ask anything you want by using the `--` prefix:\n\n```bash\npython main.py --\"What is quantum computing?\"\n```\n\n## How It Works?\n\nThe magic happens through a simple but powerful graph structure with these main components:\n\n```mermaid\ngraph TD\n    subgraph InnerAgent[Inner Research Agent]\n        DecideAction -->|\"search\"| SearchWeb\n        DecideAction -->|\"answer\"| UnreliableAnswerNode\n        SearchWeb -->|\"decide\"| DecideAction\n    end\n    \n    InnerAgent --> SupervisorNode\n    SupervisorNode -->|\"retry\"| InnerAgent\n```\n\nHere's what each part does:\n1. **DecideAction**: The brain that figures out whether to search or answer based on current context\n2. **SearchWeb**: The researcher that goes out and finds information using web search\n3. **UnreliableAnswerNode**: Generates answers (with a 50% chance of being unreliable)\n4. **SupervisorNode**: Quality control that validates answers and rejects nonsensical ones\n\n## Example Output\n\n```\n🤔 Processing question: Who won the Nobel Prize in Physics 2024?\n🤔 Agent deciding what to do next...\n🔍 Agent decided to search for: Nobel Prize in Physics 2024 winner\n🌐 Searching the web for: Nobel Prize in Physics 2024 winner\n📚 Found information, analyzing results...\n🤔 Agent deciding what to do next...\n💡 Agent decided to answer the question\n🤪 Generating unreliable dummy answer...\n✅ Answer generated successfully\n    🔍 Supervisor checking answer quality...\n    ❌ Supervisor rejected answer: Answer appears to be nonsensical or unhelpful\n🤔 Agent deciding what to do next...\n💡 Agent decided to answer the question\n✍️ Crafting final answer...\n✅ Answer generated successfully\n    🔍 Supervisor checking answer quality...\n    ✅ Supervisor approved answer: Answer appears to be legitimate\n\n🎯 Final Answer:\nThe Nobel Prize in Physics for 2024 was awarded jointly to John J. Hopfield and Geoffrey Hinton. They were recognized \"for foundational discoveries and inventions that enable machine learning with artificial neural networks.\" Their work has been pivotal in the field of artificial intelligence, specifically in developing the theories and technologies that support machine learning using artificial neural networks. John Hopfield is associated with Princeton University, while Geoffrey Hinton is connected to the University of Toronto. Their achievements have laid essential groundwork for advancements in AI and its widespread application across various domains.\n```\n\n## Files\n\n- [`main.py`](./main.py): The starting point - runs the whole show!\n- [`flow.py`](./flow.py): Connects everything together into a smart agent with supervision\n- [`nodes.py`](./nodes.py): The building blocks that make decisions, take actions, and validate answers\n- [`utils.py`](./utils.py): Helper functions for talking to the LLM and searching the web\n"
  },
  {
    "path": "cookbook/pocketflow-supervisor/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import DecideAction, SearchWeb, UnreliableAnswerNode, SupervisorNode\n\ndef create_agent_inner_flow():\n    \"\"\"\n    Create the inner research agent flow without supervision.\n    \n    This flow handles the research cycle:\n    1. DecideAction node decides whether to search or answer\n    2. If search, go to SearchWeb node and return to decide\n    3. If answer, go to UnreliableAnswerNode\n    \n    Returns:\n        Flow: A research agent flow\n    \"\"\"\n    # Create instances of each node\n    decide = DecideAction()\n    search = SearchWeb()\n    answer = UnreliableAnswerNode()\n    \n    # Connect the nodes\n    # If DecideAction returns \"search\", go to SearchWeb\n    decide - \"search\" >> search\n    \n    # If DecideAction returns \"answer\", go to UnreliableAnswerNode\n    decide - \"answer\" >> answer\n    \n    # After SearchWeb completes and returns \"decide\", go back to DecideAction\n    search - \"decide\" >> decide\n    \n    # Create and return the inner flow, starting with the DecideAction node\n    return Flow(start=decide)\n\ndef create_agent_flow():\n    \"\"\"\n    Create a supervised agent flow by treating the entire agent flow as a node\n    and placing the supervisor outside of it.\n    \n    The flow works like this:\n    1. Inner agent flow does research and generates an answer\n    2. SupervisorNode checks if the answer is valid\n    3. If answer is valid, flow completes\n    4. If answer is invalid, restart the inner agent flow\n    \n    Returns:\n        Flow: A complete research agent flow with supervision\n    \"\"\"\n    # Create the inner flow\n    agent_flow = create_agent_inner_flow()\n    \n    # Create the supervisor node\n    supervisor = SupervisorNode()\n    \n    # Connect the components\n    # After agent_flow completes, go to supervisor\n    agent_flow >> supervisor\n    \n    # If supervisor rejects the answer, go back to agent_flow\n    supervisor - \"retry\" >> agent_flow\n    \n    # Create and return the outer flow, starting with the agent_flow\n    return Flow(start=agent_flow) "
  },
  {
    "path": "cookbook/pocketflow-supervisor/main.py",
    "content": "import sys\nfrom flow import create_agent_flow\n\ndef main():\n    \"\"\"Simple function to process a question with supervised answers.\"\"\"\n    # Default question\n    default_question = \"Who won the Nobel Prize in Physics 2024?\"\n    \n    # Get question from command line if provided with --\n    question = default_question\n    for arg in sys.argv[1:]:\n        if arg.startswith(\"--\"):\n            question = arg[2:]\n            break\n    \n    # Create the agent flow with supervision\n    agent_flow = create_agent_flow()\n    \n    # Process the question\n    shared = {\"question\": question}\n    print(f\"🤔 Processing question: {question}\")\n    agent_flow.run(shared)\n    print(\"\\n🎯 Final Answer:\")\n    print(shared.get(\"answer\", \"No answer found\"))\n\nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "cookbook/pocketflow-supervisor/nodes.py",
    "content": "from pocketflow import Node\nfrom utils import call_llm, search_web\nimport yaml\nimport random\n\nclass DecideAction(Node):\n    def prep(self, shared):\n        \"\"\"Prepare the context and question for the decision-making process.\"\"\"\n        # Get the current context (default to \"No previous search\" if none exists)\n        context = shared.get(\"context\", \"No previous search\")\n        # Get the question from the shared store\n        question = shared[\"question\"]\n        # Return both for the exec step\n        return question, context\n        \n    def exec(self, inputs):\n        \"\"\"Call the LLM to decide whether to search or answer.\"\"\"\n        question, context = inputs\n        \n        print(f\"🤔 Agent deciding what to do next...\")\n        \n        # Create a prompt to help the LLM decide what to do next\n        prompt = f\"\"\"\n### CONTEXT\nYou are a research assistant that can search the web.\nQuestion: {question}\nPrevious Research: {context}\n\n### ACTION SPACE\n[1] search\n  Description: Look up more information on the web\n  Parameters:\n    - query (str): What to search for\n\n[2] answer\n  Description: Answer the question with current knowledge\n  Parameters:\n    - answer (str): Final answer to the question\n\n## NEXT ACTION\nDecide the next action based on the context and available actions.\nReturn your response in this format:\n\n```yaml\nthinking: |\n    <your step-by-step reasoning process>\naction: search OR answer\nreason: <why you chose this action>\nsearch_query: <specific search query if action is search>\n```\"\"\"\n        \n        # Call the LLM to make a decision\n        response = call_llm(prompt)\n        \n        # Parse the response to get the decision\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        decision = yaml.safe_load(yaml_str)\n        \n        return decision\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Save the decision and determine the next step in the flow.\"\"\"\n        # If LLM decided to search, save the search query\n        if exec_res[\"action\"] == \"search\":\n            shared[\"search_query\"] = exec_res[\"search_query\"]\n            print(f\"🔍 Agent decided to search for: {exec_res['search_query']}\")\n        else:\n            print(f\"💡 Agent decided to answer the question\")\n        \n        # Return the action to determine the next node in the flow\n        return exec_res[\"action\"]\n\nclass SearchWeb(Node):\n    def prep(self, shared):\n        \"\"\"Get the search query from the shared store.\"\"\"\n        return shared[\"search_query\"]\n        \n    def exec(self, search_query):\n        \"\"\"Search the web for the given query.\"\"\"\n        # Call the search utility function\n        print(f\"🌐 Searching the web for: {search_query}\")\n        results = search_web(search_query)\n        return results\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Save the search results and go back to the decision node.\"\"\"\n        # Add the search results to the context in the shared store\n        previous = shared.get(\"context\", \"\")\n        shared[\"context\"] = previous + \"\\n\\nSEARCH: \" + shared[\"search_query\"] + \"\\nRESULTS: \" + exec_res\n        \n        print(f\"📚 Found information, analyzing results...\")\n        \n        # Always go back to the decision node after searching\n        return \"decide\"\n\nclass UnreliableAnswerNode(Node):\n    def prep(self, shared):\n        \"\"\"Get the question and context for answering.\"\"\"\n        return shared[\"question\"], shared.get(\"context\", \"\")\n        \n    def exec(self, inputs):\n        \"\"\"Call the LLM to generate a final answer with 50% chance of returning a dummy answer.\"\"\"\n        question, context = inputs\n        \n        # 50% chance to return a dummy answer\n        if random.random() < 0.5:\n            print(f\"🤪 Generating unreliable dummy answer...\")\n            return \"Sorry, I'm on a coffee break right now. All information I provide is completely made up anyway. The answer to your question is 42, or maybe purple unicorns. Who knows? Certainly not me!\"\n        \n        print(f\"✍️ Crafting final answer...\")\n        \n        # Create a prompt for the LLM to answer the question\n        prompt = f\"\"\"\n### CONTEXT\nBased on the following information, answer the question.\nQuestion: {question}\nResearch: {context}\n\n## YOUR ANSWER:\nProvide a comprehensive answer using the research results.\n\"\"\"\n        # Call the LLM to generate an answer\n        answer = call_llm(prompt)\n        return answer\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Save the final answer and complete the flow.\"\"\"\n        # Save the answer in the shared store\n        shared[\"answer\"] = exec_res\n        \n        print(f\"✅ Answer generated successfully\")\n\nclass SupervisorNode(Node):\n    def prep(self, shared):\n        \"\"\"Get the current answer for evaluation.\"\"\"\n        return shared[\"answer\"]\n    \n    def exec(self, answer):\n        \"\"\"Check if the answer is valid or nonsensical.\"\"\"\n        print(f\"    🔍 Supervisor checking answer quality...\")\n        \n        # Check for obvious markers of the nonsense answers\n        nonsense_markers = [\n            \"coffee break\", \n            \"purple unicorns\", \n            \"made up\", \n            \"42\", \n            \"Who knows?\"\n        ]\n        \n        # Check if the answer contains any nonsense markers\n        is_nonsense = any(marker in answer for marker in nonsense_markers)\n        \n        if is_nonsense:\n            return {\"valid\": False, \"reason\": \"Answer appears to be nonsensical or unhelpful\"}\n        else:\n            return {\"valid\": True, \"reason\": \"Answer appears to be legitimate\"}\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Decide whether to accept the answer or restart the process.\"\"\"\n        if exec_res[\"valid\"]:\n            print(f\"    ✅ Supervisor approved answer: {exec_res['reason']}\")\n        else:\n            print(f\"    ❌ Supervisor rejected answer: {exec_res['reason']}\")\n            # Clean up the bad answer\n            shared[\"answer\"] = None\n            # Add a note about the rejected answer\n            context = shared.get(\"context\", \"\")\n            shared[\"context\"] = context + \"\\n\\nNOTE: Previous answer attempt was rejected by supervisor.\"\n            return \"retry\" "
  },
  {
    "path": "cookbook/pocketflow-supervisor/requirements.txt",
    "content": "pocketflow>=0.0.1\naiohttp>=3.8.0  # For async HTTP requests\nopenai>=1.0.0   # For async LLM calls \nduckduckgo-search>=7.5.2    # For web search"
  },
  {
    "path": "cookbook/pocketflow-supervisor/utils.py",
    "content": "from openai import OpenAI\nimport os\nfrom duckduckgo_search import DDGS\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n\ndef search_web(query):\n    results = DDGS().text(query, max_results=5)\n    # Convert results to a string\n    results_str = \"\\n\\n\".join([f\"Title: {r['title']}\\nURL: {r['href']}\\nSnippet: {r['body']}\" for r in results])\n    return results_str\n    \nif __name__ == \"__main__\":\n    print(\"## Testing call_llm\")\n    prompt = \"In a few words, what is the meaning of life?\"\n    print(f\"## Prompt: {prompt}\")\n    response = call_llm(prompt)\n    print(f\"## Response: {response}\")\n\n    print(\"## Testing search_web\")\n    query = \"Who won the Nobel Prize in Physics 2024?\"\n    print(f\"## Query: {query}\")\n    results = search_web(query)\n    print(f\"## Results: {results}\")"
  },
  {
    "path": "cookbook/pocketflow-tao/README.md",
    "content": "# PocketFlow TAO (Thought-Action-Observation)\n\nA powerful pattern that enables AI agents to solve complex problems through structured thinking, action execution, and result observation. This example demonstrates how to implement the TAO pattern using PocketFlow.\n\n## Project Structure\n\n```\n.\n├── flow.py        # PocketFlow implementation of TAO pattern\n├── main.py        # Main application entry point\n├── nodes.py       # TAO node definitions\n├── requirements.txt # Project dependencies\n└── README.md      # Project documentation\n```\n\n## Overview\n\nThe TAO pattern consists of three key steps:\n1. **Thought**: The agent deeply analyzes the problem and forms a solution strategy\n2. **Action**: Concrete actions are executed based on the thinking\n3. **Observation**: Results are evaluated and feedback is gathered\n\nThis cycle continues until the problem is solved or termination conditions are met.\n\n## Setup\n\n1. Create a virtual environment:\n```bash\npython -m venv venv\nsource venv/bin/activate  # On Windows: venv\\Scripts\\activate\n```\n\n2. Install dependencies:\n```bash\npip install -r requirements.txt\n```\n\n3. Set API key (if using specific LLM services):\n```bash\nexport OPENAI_API_KEY=\"your-api-key-here\"\n# Or set in code\n```\n\n## How to Run\n\nExecute the example:\n```bash\npython main.py\n```\n\n## How It Works\n\nThe TAO pattern is implemented as a flow in PocketFlow, with each step handled by specialized nodes:\n\n```mermaid\ngraph TD\n    Problem[Problem Input] --> ThoughtNode\n    ThoughtNode[Thought Node] --> ActionNode[Action Node]\n    ActionNode --> ObservationNode[Observation Node]\n    ObservationNode --> DecisionNode{Problem Solved?}\n    DecisionNode -->|Yes| Solution[Solution]\n    DecisionNode -->|No| ThoughtNode\n```\n\nEach TAO cycle generates new insights for the problem-solving process, allowing the AI to iteratively approach an optimal solution.\n\n## Use Cases\n\n- Complex problem solving\n- Multi-step reasoning tasks\n- Projects requiring iterative improvement\n- Reinforcement learning-style AI applications\n\n## Example Output\n\n```\nQuery: I need to understand the latest developments in artificial intelligence\n\n🤔 Thought 1: Decided to execute search\n🚀 Executing action: search, input: latest developments in artificial intelligence 2023\n✅ Action completed, result obtained\n👁️ Observation: The search result indicates that information was r...\n🎯 Final Answer: As of October 2023, some of the latest developments in artificial intelligence include advances in large language models like GPT-4, increased focus on AI alignment and safety, improvements in reinforcement learning, and the integration of AI into more industries such as healthcare, finance, and autonomous vehicles. Researchers are also exploring ethical considerations and regulatory frameworks to ensure responsible AI deployment. For the most current updates beyond this date, I recommend checking recent publications, official AI research organization releases, or news sources specializing in technology.\n\nFlow ended, thank you for using!\n\nFinal Answer:\nAs of October 2023, some of the latest developments in artificial intelligence include advances in large language models like GPT-4, increased focus on AI alignment and safety, improvements in reinforcement learning, and the integration of AI into more industries such as healthcare, finance, and autonomous vehicles. Researchers are also exploring ethical considerations and regulatory frameworks to ensure responsible AI deployment. For the most current updates beyond this date, I recommend checking recent publications, official AI research organization releases, or news sources specializing in technology.\n```\n\n## Advanced Usage\n\nThe TAO pattern can be extended by:\n- Adding memory components to store past thoughts and observations.\n- Implementing adaptive action selection strategies.\n- Integrating external tools and APIs.\n- Adding human feedback loops.\n- Adding max attempt to control the iteration.\n\n## Additional Resources\n\n- [PocketFlow Documentation](https://the-pocket.github.io/PocketFlow/)\n- [Understanding AI Agents through the Thought-Action-Observation Cycle](https://huggingface.co/learn/agents-course/en/unit1/agent-steps-and-structure)"
  },
  {
    "path": "cookbook/pocketflow-tao/flow.py",
    "content": "# flow.py\n\nfrom pocketflow import Flow\nfrom nodes import ThinkNode, ActionNode, ObserveNode, EndNode\n\ndef create_tao_flow():\n    \"\"\"\n    Create a Thought-Action-Observation loop flow\n    \n    How the flow works:\n    1. ThinkNode decides the next action\n    2. ActionNode executes the action\n    3. ObserveNode observes the action result\n    4. Return to ThinkNode to continue thinking, or end the flow\n    \n    Returns:\n        Flow: Complete TAO loop flow\n    \"\"\"\n    # Create node instances\n    think = ThinkNode()\n    action = ActionNode()\n    observe = ObserveNode()\n    end = EndNode()\n    \n    # Connect nodes\n    # If ThinkNode returns \"action\", go to ActionNode\n    think - \"action\" >> action\n    \n    # If ThinkNode returns \"end\", end the flow\n    think - \"end\" >> end\n    \n    # After ActionNode completes, go to ObserveNode\n    action - \"observe\" >> observe\n    \n    # After ObserveNode completes, return to ThinkNode\n    observe - \"think\" >> think\n    \n    # Create and return flow, starting from ThinkNode\n    return Flow(start=think)"
  },
  {
    "path": "cookbook/pocketflow-tao/main.py",
    "content": "# main.py\n\nfrom flow import create_tao_flow\n\ndef main():\n    \n    query = \"\"\"I need to understand the latest developments in artificial intelligence\"\"\"\n    \n    # Create shared data\n    shared = {\n        \"query\": query,\n        \"thoughts\": [],\n        \"observations\": [],\n        \"current_thought_number\": 0\n    }\n    \n    # Create and run flow\n    tao_flow = create_tao_flow()\n    tao_flow.run(shared)\n    \n    # Print final result\n    if \"final_answer\" in shared:\n        print(\"\\nFinal Answer:\")\n        print(shared[\"final_answer\"])\n    else:\n        print(\"\\nFlow did not produce a final answer\")\n\nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "cookbook/pocketflow-tao/nodes.py",
    "content": "# nodes.py\n\nfrom pocketflow import Node\nimport yaml\nfrom utils import call_llm\n\nclass ThinkNode(Node):\n    def prep(self, shared):\n        \"\"\"Prepare the context needed for thinking\"\"\"\n        query = shared.get(\"query\", \"\")\n        observations = shared.get(\"observations\", [])\n        thoughts = shared.get(\"thoughts\", [])\n        current_thought_number = shared.get(\"current_thought_number\", 0)\n        \n        # Update thought count\n        shared[\"current_thought_number\"] = current_thought_number + 1\n        \n        # Format previous observations\n        observations_text = \"\\n\".join([f\"Observation {i+1}: {obs}\" for i, obs in enumerate(observations)])\n        if not observations_text:\n            observations_text = \"No observations yet.\"\n            \n        return {\n            \"query\": query,\n            \"observations_text\": observations_text,\n            \"thoughts\": thoughts,\n            \"current_thought_number\": current_thought_number + 1\n        }\n    \n    def exec(self, prep_res):\n        \"\"\"Execute the thinking process, decide the next action\"\"\"\n        query = prep_res[\"query\"]\n        observations_text = prep_res[\"observations_text\"]\n        current_thought_number = prep_res[\"current_thought_number\"]\n        \n        # Build the prompt\n        prompt = f\"\"\"\n        You are an AI assistant solving a problem. Based on the user's query and previous observations, think about what action to take next.\n        \n        User query: {query}\n        \n        Previous observations:\n        {observations_text}\n        \n        Please think about the next action and return your thinking process and decision in YAML format:\n        ```yaml\n        thinking: |\n            <detailed thinking process>\n        action: <action name, such as 'search' or 'answer'>\n        action_input: <input parameters for the action>\n        is_final: <set to true if this is the final answer, otherwise false>\n        ```\n        \"\"\"\n        \n        # Call LLM to get thinking result\n        response = call_llm(prompt)\n        \n        # Parse YAML response\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        thought_data = yaml.safe_load(yaml_str)\n        \n        # Add thought number\n        thought_data[\"thought_number\"] = current_thought_number\n        \n        return thought_data\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Save the thinking result and decide the next step in the flow\"\"\"\n        # Save thinking result\n        if \"thoughts\" not in shared:\n            shared[\"thoughts\"] = []\n        shared[\"thoughts\"].append(exec_res)\n        \n        # Save action information\n        shared[\"current_action\"] = exec_res[\"action\"]\n        shared[\"current_action_input\"] = exec_res[\"action_input\"]\n        \n        # If it's the final answer, end the flow\n        if exec_res.get(\"is_final\", False):\n            shared[\"final_answer\"] = exec_res[\"action_input\"]\n            print(f\"🎯 Final Answer: {exec_res['action_input']}\")\n            return \"end\"\n        \n        # Otherwise continue with the action\n        print(f\"🤔 Thought {exec_res['thought_number']}: Decided to execute {exec_res['action']}\")\n        return \"action\"\n\nclass ActionNode(Node):\n    def prep(self, shared):\n        \"\"\"Prepare to execute action\"\"\"\n        action = shared[\"current_action\"]\n        action_input = shared[\"current_action_input\"]\n        return action, action_input\n    \n    def exec(self, inputs):\n        \"\"\"Execute action and return result\"\"\"\n        action, action_input = inputs\n        \n        print(f\"🚀 Executing action: {action}, input: {action_input}\")\n        \n        # Execute different operations based on action type\n        if action == \"search\":\n            # Simulate search operation\n            result = self.search_web(action_input)\n        elif action == \"calculate\":\n            # Simulate calculation operation\n            result = self.calculate(action_input)\n        elif action == \"answer\":\n            # Direct return answer\n            result = action_input\n        else:\n            # Unknown action type\n            result = f\"Unknown action type: {action}\"\n        \n        return result\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Save action result\"\"\"\n        # Save the current action result\n        shared[\"current_action_result\"] = exec_res\n        print(f\"✅ Action completed, result obtained\")\n        \n        # Continue to observation node\n        return \"observe\"\n    \n    # Simulated tool functions\n    def search_web(self, query):\n        # This should be actual search logic\n        return f\"Search results: Information about '{query}'...\"\n    \n    def calculate(self, expression):\n        # This should be actual calculation logic\n        try:\n            return f\"Calculation result: {eval(expression)}\"\n        except:\n            return f\"Unable to calculate expression: {expression}\"\n\nclass ObserveNode(Node):\n    def prep(self, shared):\n        \"\"\"Prepare observation data\"\"\"\n        action = shared[\"current_action\"]\n        action_input = shared[\"current_action_input\"]\n        action_result = shared[\"current_action_result\"]\n        return action, action_input, action_result\n    \n    def exec(self, inputs):\n        \"\"\"Analyze action results, generate observation\"\"\"\n        action, action_input, action_result = inputs\n        \n        # Build prompt\n        prompt = f\"\"\"\n        You are an observer, needing to analyze action results and provide objective observations.\n        \n        Action: {action}\n        Action input: {action_input}\n        Action result: {action_result}\n        \n        Please provide a concise observation of this result. Don't make decisions, just describe what you see.\n        \"\"\"\n        \n        # Call LLM to get observation result\n        observation = call_llm(prompt)\n        \n        print(f\"👁️ Observation: {observation[:50]}...\")\n        return observation\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Save observation result and decide next flow step\"\"\"\n        # Save observation result\n        if \"observations\" not in shared:\n            shared[\"observations\"] = []\n        shared[\"observations\"].append(exec_res)\n        \n        # Continue thinking\n        return \"think\"\n    \n\n    \nclass EndNode(Node):\n    def prep(self, shared):\n        \"\"\"Prepare end node\"\"\"\n        \n        return {}\n    def exec(self, prep_res):\n        \"\"\"Execute end operation\"\"\"\n        print(\"Flow ended, thank you for using!\")\n        return None\n    def post(self, shared, prep_res, exec_res):\n        \"\"\"End flow\"\"\"\n        return None"
  },
  {
    "path": "cookbook/pocketflow-tao/utils.py",
    "content": "# utils.py\n\nfrom openai import OpenAI\nimport os\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"Your Key Here\"),base_url=os.environ.get(\"OPENAI_API_BASE\", \"Your API Base Here\"))\n    r = client.chat.completions.create(\n        model=os.environ.get(\"OPENAI_MODEL\", \"openai/gpt-4.1-nano\"),\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n\nif __name__ == \"__main__\":\n    print(\"## Testing call_llm\")\n    prompt = \"In a few words, what is the meaning of life?\"\n    print(f\"## Prompt: {prompt}\")\n    response = call_llm(prompt)\n    print(f\"## Response: {response}\")"
  },
  {
    "path": "cookbook/pocketflow-text2sql/README.md",
    "content": "# Text-to-SQL Workflow\n\nA PocketFlow example demonstrating a text-to-SQL workflow that converts natural language questions into executable SQL queries for an SQLite database, including an LLM-powered debugging loop for failed queries.\n\n- Check out the [Substack Post Tutorial](https://zacharyhuang.substack.com/p/text-to-sql-from-scratch-tutorial) for more!\n\n## Features\n\n-   **Schema Awareness**: Automatically retrieves the database schema to provide context to the LLM.\n-   **LLM-Powered SQL Generation**: Uses an LLM (GPT-4o) to translate natural language questions into SQLite queries (using YAML structured output).\n-   **Automated Debugging Loop**: If SQL execution fails, an LLM attempts to correct the query based on the error message. This process repeats up to a configurable number of times.\n## Getting Started\n\n1.  **Install Packages:**\n    ```bash\n    pip install -r requirements.txt\n    ```\n\n2.  **Set API Key:**\n    Set the environment variable for your OpenAI API key.\n    ```bash\n    export OPENAI_API_KEY=\"your-api-key-here\"\n    ```\n    *(Replace `\"your-api-key-here\"` with your actual key)*\n\n3.  **Verify API Key (Optional):**\n    Run a quick check using the utility script. If successful, it will print a short joke.\n    ```bash\n    python utils.py\n    ```\n    *(Note: This requires a valid API key to be set.)*\n\n4.  **Run Default Example:**\n    Execute the main script. This will create the sample `ecommerce.db` if it doesn't exist and run the workflow with a default query.\n    ```bash\n    python main.py\n    ```\n    The default query is:\n    > Show me the names and email addresses of customers from New York\n\n5.  **Run Custom Query:**\n    Provide your own natural language query as command-line arguments after the script name.\n    ```bash\n    python main.py What is the total stock quantity for products in the 'Accessories' category?\n    ```\n    Or, for queries with spaces, ensure they are treated as a single argument by the shell if necessary (quotes might help depending on your shell):\n    ```bash\n    python main.py \"List orders placed in the last 30 days with status 'shipped'\"\n    ```\n\n## How It Works\n\nThe workflow uses several nodes connected in a sequence, with a loop for debugging failed SQL queries.\n\n```mermaid\ngraph LR\n    A[Get Schema] --> B[Generate SQL]\n    B --> C[Execute SQL]\n    C -- Success --> E[End]\n    C -- SQLite Error --> D{Debug SQL Attempt}\n    D -- Corrected SQL --> C\n    C -- Max Retries Reached --> F[End with Error]\n\n    style E fill:#dff,stroke:#333,stroke-width:2px\n    style F fill:#fdd,stroke:#333,stroke-width:2px\n\n```\n\n**Node Descriptions:**\n\n1.  **`GetSchema`**: Connects to the SQLite database (`ecommerce.db` by default) and extracts the schema (table names and columns).\n2.  **`GenerateSQL`**: Takes the natural language query and the database schema, prompts the LLM to generate an SQLite query (expecting YAML output with the SQL), and parses the result.\n3.  **`ExecuteSQL`**: Attempts to run the generated SQL against the database.\n    *   If successful, the results are stored, and the flow ends successfully.\n    *   If an `sqlite3.Error` occurs (e.g., syntax error), it captures the error message and triggers the debug loop.\n4.  **`DebugSQL`**: If `ExecuteSQL` failed, this node takes the original query, schema, failed SQL, and error message, prompts the LLM to generate a *corrected* SQL query (again, expecting YAML).\n5.  **(Loop)**: The corrected SQL from `DebugSQL` is passed back to `ExecuteSQL` for another attempt.\n6.  **(End Conditions)**: The loop continues until `ExecuteSQL` succeeds or the maximum number of debug attempts (default: 3) is reached.\n\n## Files\n\n-   [`main.py`](./main.py): Main entry point to run the workflow. Handles command-line arguments for the query.\n-   [`flow.py`](./flow.py): Defines the PocketFlow `Flow` connecting the different nodes, including the debug loop logic.\n-   [`nodes.py`](./nodes.py): Contains the `Node` classes for each step (`GetSchema`, `GenerateSQL`, `ExecuteSQL`, `DebugSQL`).\n-   [`utils.py`](./utils.py): Contains the minimal `call_llm` utility function.\n-   [`populate_db.py`](./populate_db.py): Script to create and populate the sample `ecommerce.db` SQLite database.\n-   [`requirements.txt`](./requirements.txt): Lists Python package dependencies.\n-   [`README.md`](./README.md): This file.\n\n## Example Output (Successful Run)\n\n```\n=== Starting Text-to-SQL Workflow ===\nQuery: 'total products per category'\nDatabase: ecommerce.db\nMax Debug Retries on SQL Error: 3\n=============================================\n\n===== DB SCHEMA =====\n\nTable: customers\n  - customer_id (INTEGER)\n  - first_name (TEXT)\n  - last_name (TEXT)\n  - email (TEXT)\n  - registration_date (DATE)\n  - city (TEXT)\n  - country (TEXT)\n\nTable: sqlite_sequence\n  - name ()\n  - seq ()\n\nTable: products\n  - product_id (INTEGER)\n  - name (TEXT)\n  - description (TEXT)\n  - category (TEXT)\n  - price (REAL)\n  - stock_quantity (INTEGER)\n\nTable: orders\n  - order_id (INTEGER)\n  - customer_id (INTEGER)\n  - order_date (TIMESTAMP)\n  - status (TEXT)\n  - total_amount (REAL)\n  - shipping_address (TEXT)\n\nTable: order_items\n  - order_item_id (INTEGER)\n  - order_id (INTEGER)\n  - product_id (INTEGER)\n  - quantity (INTEGER)\n  - price_per_unit (REAL)\n\n=====================\n\n\n===== GENERATED SQL (Attempt 1) =====\n\nSELECT category, COUNT(*) AS total_products\nFROM products\nGROUP BY category\n\n====================================\n\nSQL executed in 0.000 seconds.\n\n===== SQL EXECUTION SUCCESS =====\n\ncategory | total_products\n-------------------------\nAccessories | 3\nApparel | 1\nElectronics | 3\nHome Goods | 2\nSports | 1\n\n=== Workflow Completed Successfully ===\n====================================\n```\n"
  },
  {
    "path": "cookbook/pocketflow-text2sql/docs/design.md",
    "content": "# Design Doc: Text-to-SQL Agent\n\n> Please DON'T remove notes for AI\n\n## Requirements\n\n> Notes for AI: Keep it simple and clear.\n> If the requirements are abstract, write concrete user stories\n\nThe system should take a natural language query and a path to an SQLite database as input. It should then:\n1.  Extract the schema from the database.\n2.  Generate an SQL query based on the natural language query and the schema.\n3.  Execute the SQL query against the database.\n4.  If the SQL execution fails, attempt to debug and retry the SQL generation and execution up to a specified maximum number of attempts.\n5.  Return the final results of the SQL query or an error message if the process fails.\n\n## Flow Design\n\n> Notes for AI:\n> 1. Consider the design patterns of agent, map-reduce, rag, and workflow. Apply them if they fit.\n> 2. Present a concise, high-level description of the workflow.\n\n### Applicable Design Pattern:\n\nThe primary design pattern is a **Workflow** with an embedded **Agent**-like behavior for debugging.\n-   **Workflow**: The process follows a sequence: Get Schema -> Generate SQL -> Execute SQL.\n-   **Agent (for Debugging)**: If `ExecuteSQL` fails, the `DebugSQL` node acts like an agent, taking the error and previous SQL as context to generate a revised SQL query. This forms a loop back to `ExecuteSQL`.\n\n### Flow high-level Design:\n\n1.  **`GetSchema`**: Retrieves the database schema.\n2.  **`GenerateSQL`**: Generates an SQL query from a natural language question and the schema.\n3.  **`ExecuteSQL`**: Executes the generated SQL. If successful, the flow ends. If an error occurs, it transitions to `DebugSQL`.\n4.  **`DebugSQL`**: Attempts to correct the failed SQL query based on the error message. It then transitions back to `ExecuteSQL` to try the corrected query.\n\n```mermaid\nflowchart TD\n    A[GetSchema] --> B[GenerateSQL]\n    B --> C{ExecuteSQL}\n    C -- Success --> D[End]\n    C -- Error --> E[DebugSQL]\n    E --> C\n```\n\n## Utility Functions\n\n> Notes for AI:\n> 1. Understand the utility function definition thoroughly by reviewing the doc.\n> 2. Include only the necessary utility functions, based on nodes in the flow.\n\n1.  **Call LLM** (`utils/call_llm.py`)\n    *   *Input*: `prompt` (str)\n    *   *Output*: `response` (str)\n    *   *Necessity*: Used by `GenerateSQL` and `DebugSQL` nodes to interact with the language model for SQL generation and correction.\n\n*Database interaction (e.g., `sqlite3.connect`, `cursor.execute`) is handled directly within the nodes and is not abstracted into separate utility functions in this implementation.*\n\n## Node Design\n\n### Shared Store\n\n> Notes for AI: Try to minimize data redundancy\n\nThe shared store structure is organized as follows:\n\n```python\nshared = {\n    \"db_path\": \"path/to/database.db\",       # Input: Path to the SQLite database\n    \"natural_query\": \"User's question\",      # Input: Natural language query from the user\n    \"max_debug_attempts\": 3,                # Input: Max retries for the debug loop\n    \"schema\": None,                         # Output of GetSchema: String representation of DB schema\n    \"generated_sql\": None,                  # Output of GenerateSQL/DebugSQL: The SQL query string\n    \"execution_error\": None,                # Output of ExecuteSQL (on failure): Error message\n    \"debug_attempts\": 0,                    # Internal: Counter for debug attempts\n    \"final_result\": None,                   # Output of ExecuteSQL (on success): Query results\n    \"result_columns\": None,                 # Output of ExecuteSQL (on success): Column names for results\n    \"final_error\": None                     # Output: Overall error message if flow fails after retries\n}\n```\n\n### Node Steps\n\n> Notes for AI: Carefully decide whether to use Batch/Async Node/Flow.\n\n1.  **`GetSchema`**\n    *   *Purpose*: To extract and store the schema of the target SQLite database.\n    *   *Type*: Regular\n    *   *Steps*:\n        *   *`prep`*: Reads `db_path` from the shared store.\n        *   *`exec`*: Connects to the SQLite database, inspects `sqlite_master` and `PRAGMA table_info` to build a string representation of all tables and their columns.\n        *   *`post`*: Writes the extracted `schema` string to the shared store.\n\n2.  **`GenerateSQL`**\n    *   *Purpose*: To generate an SQL query based on the user's natural language query and the database schema.\n    *   *Type*: Regular\n    *   *Steps*:\n        *   *`prep`*: Reads `natural_query` and `schema` from the shared store.\n        *   *`exec`*: Constructs a prompt for the LLM, including the schema and the natural language query, asking for an SQL query in YAML format. Calls the `call_llm` utility. Parses the YAML response to extract the SQL query.\n        *   *`post`*: Writes the `generated_sql` to the shared store. Resets `debug_attempts` to 0.\n\n3.  **`ExecuteSQL`**\n    *   *Purpose*: To execute the generated SQL query against the database and handle results or errors.\n    *   *Type*: Regular\n    *   *Steps*:\n        *   *`prep`*: Reads `db_path` and `generated_sql` from the shared store.\n        *   *`exec`*: Connects to the SQLite database and executes the `generated_sql`. It determines if the query is a SELECT or an DML/DDL statement to fetch results or commit changes. Returns a tuple `(success_boolean, result_or_error_message, column_names_list)`.\n        *   *`post`*:\n            *   If successful: Stores `final_result` and `result_columns` in the shared store. Returns no action (ends the flow path).\n            *   If failed: Stores `execution_error` in the shared store. Increments `debug_attempts`. If `debug_attempts` is less than `max_debug_attempts`, returns `\"error_retry\"` action to trigger the `DebugSQL` node. Otherwise, sets `final_error` and returns no action.\n\n4.  **`DebugSQL`**\n    *   *Purpose*: To attempt to correct a failed SQL query using LLM based on the error message.\n    *   *Type*: Regular\n    *   *Steps*:\n        *   *`prep`*: Reads `natural_query`, `schema`, `generated_sql` (the failed one), and `execution_error` from the shared store.\n        *   *`exec`*: Constructs a prompt for the LLM, providing the failed SQL, the original query, the schema, and the error message, asking for a corrected SQL query in YAML format. Calls the `call_llm` utility. Parses the YAML response to extract the corrected SQL query.\n        *   *`post`*: Overwrites `generated_sql` in the shared store with the corrected SQL. Removes `execution_error` from the shared store. Returns a default action to go back to `ExecuteSQL`.\n"
  },
  {
    "path": "cookbook/pocketflow-text2sql/flow.py",
    "content": "from pocketflow import Flow, Node\nfrom nodes import GetSchema, GenerateSQL, ExecuteSQL, DebugSQL\n\ndef create_text_to_sql_flow():\n    \"\"\"Creates the text-to-SQL workflow with a debug loop.\"\"\"\n    get_schema_node = GetSchema()\n    generate_sql_node = GenerateSQL()\n    execute_sql_node = ExecuteSQL()\n    debug_sql_node = DebugSQL()\n\n    # Define the main flow sequence using the default transition operator\n    get_schema_node >> generate_sql_node >> execute_sql_node\n\n    # --- Define the debug loop connections using the correct operator ---\n    # If ExecuteSQL returns \"error_retry\", go to DebugSQL\n    execute_sql_node - \"error_retry\" >> debug_sql_node\n\n    # If DebugSQL returns \"default\", go back to ExecuteSQL\n    # debug_sql_node - \"default\" >> execute_sql_node # Explicitly for \"default\"\n    # OR using the shorthand for default:\n    debug_sql_node >> execute_sql_node\n\n    # Create the flow\n    text_to_sql_flow = Flow(start=get_schema_node)\n    return text_to_sql_flow"
  },
  {
    "path": "cookbook/pocketflow-text2sql/main.py",
    "content": "import sys\nimport os\nfrom flow import create_text_to_sql_flow\nfrom populate_db import populate_database, DB_FILE\n\ndef run_text_to_sql(natural_query, db_path=DB_FILE, max_debug_retries=3):\n    if not os.path.exists(db_path) or os.path.getsize(db_path) == 0:\n        print(f\"Database at {db_path} missing or empty. Populating...\")\n        populate_database(db_path)\n\n    shared = {\n        \"db_path\": db_path,\n        \"natural_query\": natural_query,\n        \"max_debug_attempts\": max_debug_retries,\n        \"debug_attempts\": 0,\n        \"final_result\": None,\n        \"final_error\": None\n    }\n\n    print(f\"\\n=== Starting Text-to-SQL Workflow ===\")\n    print(f\"Query: '{natural_query}'\")\n    print(f\"Database: {db_path}\")\n    print(f\"Max Debug Retries on SQL Error: {max_debug_retries}\")\n    print(\"=\" * 45)\n\n    flow = create_text_to_sql_flow()\n    flow.run(shared) # Let errors inside the loop be handled by the flow logic\n\n    # Check final state based on shared data\n    if shared.get(\"final_error\"):\n            print(\"\\n=== Workflow Completed with Error ===\")\n            print(f\"Error: {shared['final_error']}\")\n    elif shared.get(\"final_result\") is not None:\n            print(\"\\n=== Workflow Completed Successfully ===\")\n            # Result already printed by ExecuteSQL node\n    else:\n            # Should not happen if flow logic is correct and covers all end states\n            print(\"\\n=== Workflow Completed (Unknown State) ===\")\n\n    print(\"=\" * 36)\n    return shared\n\nif __name__ == \"__main__\":\n    if len(sys.argv) > 1:\n        query = \" \".join(sys.argv[1:])\n    else:\n        query = \"total products per category\"\n\n    run_text_to_sql(query) "
  },
  {
    "path": "cookbook/pocketflow-text2sql/nodes.py",
    "content": "import sqlite3\nimport time\nimport yaml # Import yaml here as nodes use it\nfrom pocketflow import Node\nfrom utils.call_llm import call_llm\n\nclass GetSchema(Node):\n    def prep(self, shared):\n        return shared[\"db_path\"]\n\n    def exec(self, db_path):\n        conn = sqlite3.connect(db_path)\n        cursor = conn.cursor()\n        cursor.execute(\"SELECT name FROM sqlite_master WHERE type='table';\")\n        tables = cursor.fetchall()\n        schema = []\n        for table_name_tuple in tables:\n            table_name = table_name_tuple[0]\n            schema.append(f\"Table: {table_name}\")\n            cursor.execute(f\"PRAGMA table_info({table_name});\")\n            columns = cursor.fetchall()\n            for col in columns:\n                schema.append(f\"  - {col[1]} ({col[2]})\")\n            schema.append(\"\")\n        conn.close()\n        return \"\\n\".join(schema).strip()\n\n    def post(self, shared, prep_res, exec_res):\n        shared[\"schema\"] = exec_res\n        print(\"\\n===== DB SCHEMA =====\\n\")\n        print(exec_res)\n        print(\"\\n=====================\\n\")\n        # return \"default\"\n\nclass GenerateSQL(Node):\n    def prep(self, shared):\n        return shared[\"natural_query\"], shared[\"schema\"]\n\n    def exec(self, prep_res):\n        natural_query, schema = prep_res\n        prompt = f\"\"\"\nGiven SQLite schema:\n{schema}\n\nQuestion: \"{natural_query}\"\n\nRespond ONLY with a YAML block containing the SQL query under the key 'sql':\n```yaml\nsql: |\n  SELECT ...\n```\"\"\"\n        llm_response = call_llm(prompt)\n        yaml_str = llm_response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        structured_result = yaml.safe_load(yaml_str)\n        sql_query = structured_result[\"sql\"].strip().rstrip(';')\n        return sql_query\n\n    def post(self, shared, prep_res, exec_res):\n        # exec_res is now the parsed SQL query string\n        shared[\"generated_sql\"] = exec_res\n        # Reset debug attempts when *successfully* generating new SQL\n        shared[\"debug_attempts\"] = 0\n        print(f\"\\n===== GENERATED SQL (Attempt {shared.get('debug_attempts', 0) + 1}) =====\\n\")\n        print(exec_res)\n        print(\"\\n====================================\\n\")\n        # return \"default\"\n\nclass ExecuteSQL(Node):\n    def prep(self, shared):\n        return shared[\"db_path\"], shared[\"generated_sql\"]\n\n    def exec(self, prep_res):\n        db_path, sql_query = prep_res\n        try:\n            conn = sqlite3.connect(db_path)\n            cursor = conn.cursor()\n            start_time = time.time()\n            cursor.execute(sql_query)\n\n            is_select = sql_query.strip().upper().startswith((\"SELECT\", \"WITH\"))\n            if is_select:\n                results = cursor.fetchall()\n                column_names = [desc[0] for desc in cursor.description] if cursor.description else []\n            else:\n                conn.commit()\n                results = f\"Query OK. Rows affected: {cursor.rowcount}\"\n                column_names = []\n            conn.close()\n            duration = time.time() - start_time\n            print(f\"SQL executed in {duration:.3f} seconds.\")\n            return (True, results, column_names)\n        except sqlite3.Error as e:\n            print(f\"SQLite Error during execution: {e}\")\n            if 'conn' in locals() and conn:\n                 try:\n                     conn.close()\n                 except Exception:\n                     pass\n            return (False, str(e), [])\n\n    def post(self, shared, prep_res, exec_res):\n        success, result_or_error, column_names = exec_res\n\n        if success:\n            shared[\"final_result\"] = result_or_error\n            shared[\"result_columns\"] = column_names\n            print(\"\\n===== SQL EXECUTION SUCCESS =====\\n\")\n            # (Same result printing logic as before)\n            if isinstance(result_or_error, list):\n                 if column_names: print(\" | \".join(column_names)); print(\"-\" * (sum(len(str(c)) for c in column_names) + 3 * (len(column_names) -1)))\n                 if not result_or_error: print(\"(No results found)\")\n                 else:\n                     for row in result_or_error: print(\" | \".join(map(str, row)))\n            else: print(result_or_error)\n            print(\"\\n=================================\\n\")\n            return\n        else:\n            # Execution failed (SQLite error caught in exec)\n            shared[\"execution_error\"] = result_or_error # Store the error message\n            shared[\"debug_attempts\"] = shared.get(\"debug_attempts\", 0) + 1\n            max_attempts = shared.get(\"max_debug_attempts\", 3) # Get max attempts from shared\n\n            print(f\"\\n===== SQL EXECUTION FAILED (Attempt {shared['debug_attempts']}) =====\\n\")\n            print(f\"Error: {shared['execution_error']}\")\n            print(\"=========================================\\n\")\n\n            if shared[\"debug_attempts\"] >= max_attempts:\n                print(f\"Max debug attempts ({max_attempts}) reached. Stopping.\")\n                shared[\"final_error\"] = f\"Failed to execute SQL after {max_attempts} attempts. Last error: {shared['execution_error']}\"\n                return\n            else:\n                print(\"Attempting to debug the SQL...\")\n                return \"error_retry\" # Signal to go to DebugSQL\n\nclass DebugSQL(Node):\n    def prep(self, shared):\n        return (\n            shared.get(\"natural_query\"),\n            shared.get(\"schema\"),\n            shared.get(\"generated_sql\"),\n            shared.get(\"execution_error\")\n        )\n\n    def exec(self, prep_res):\n        natural_query, schema, failed_sql, error_message = prep_res\n        prompt = f\"\"\"\nThe following SQLite SQL query failed:\n```sql\n{failed_sql}\n```\nIt was generated for: \"{natural_query}\"\nSchema:\n{schema}\nError: \"{error_message}\"\n\nProvide a corrected SQLite query.\n\nRespond ONLY with a YAML block containing the corrected SQL under the key 'sql':\n```yaml\nsql: |\n  SELECT ... -- corrected query\n```\"\"\"\n        llm_response = call_llm(prompt)\n\n        yaml_str = llm_response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        structured_result = yaml.safe_load(yaml_str)\n        corrected_sql = structured_result[\"sql\"].strip().rstrip(';')\n        return corrected_sql\n\n    def post(self, shared, prep_res, exec_res):\n        # exec_res is the corrected SQL string\n        shared[\"generated_sql\"] = exec_res # Overwrite with the new attempt\n        shared.pop(\"execution_error\", None) # Clear the previous error for the next ExecuteSQL attempt\n\n        print(f\"\\n===== REVISED SQL (Attempt {shared.get('debug_attempts', 0) + 1}) =====\\n\")\n        print(exec_res)\n        print(\"\\n====================================\\n\")"
  },
  {
    "path": "cookbook/pocketflow-text2sql/populate_db.py",
    "content": "import sqlite3\nimport os\nimport random\nfrom datetime import datetime, timedelta\n\nDB_FILE = \"ecommerce.db\"\n\ndef populate_database(db_file=DB_FILE):\n    \"\"\"Creates and populates the SQLite database.\"\"\"\n    if os.path.exists(db_file):\n        os.remove(db_file)\n        print(f\"Removed existing database: {db_file}\")\n\n    conn = sqlite3.connect(db_file)\n    cursor = conn.cursor()\n\n    # Create Tables\n    cursor.execute(\"\"\"\n    CREATE TABLE customers (\n        customer_id INTEGER PRIMARY KEY AUTOINCREMENT,\n        first_name TEXT NOT NULL,\n        last_name TEXT NOT NULL,\n        email TEXT UNIQUE NOT NULL,\n        registration_date DATE NOT NULL,\n        city TEXT,\n        country TEXT DEFAULT 'USA'\n    );\n    \"\"\")\n    print(\"Created 'customers' table.\")\n\n    cursor.execute(\"\"\"\n    CREATE TABLE products (\n        product_id INTEGER PRIMARY KEY AUTOINCREMENT,\n        name TEXT NOT NULL,\n        description TEXT,\n        category TEXT NOT NULL,\n        price REAL NOT NULL CHECK (price > 0),\n        stock_quantity INTEGER NOT NULL DEFAULT 0 CHECK (stock_quantity >= 0)\n    );\n    \"\"\")\n    print(\"Created 'products' table.\")\n\n    cursor.execute(\"\"\"\n    CREATE TABLE orders (\n        order_id INTEGER PRIMARY KEY AUTOINCREMENT,\n        customer_id INTEGER NOT NULL,\n        order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n        status TEXT NOT NULL CHECK (status IN ('pending', 'processing', 'shipped', 'delivered', 'cancelled')),\n        total_amount REAL,\n        shipping_address TEXT,\n        FOREIGN KEY (customer_id) REFERENCES customers (customer_id)\n    );\n    \"\"\")\n    print(\"Created 'orders' table.\")\n\n    cursor.execute(\"\"\"\n    CREATE TABLE order_items (\n        order_item_id INTEGER PRIMARY KEY AUTOINCREMENT,\n        order_id INTEGER NOT NULL,\n        product_id INTEGER NOT NULL,\n        quantity INTEGER NOT NULL CHECK (quantity > 0),\n        price_per_unit REAL NOT NULL,\n        FOREIGN KEY (order_id) REFERENCES orders (order_id),\n        FOREIGN KEY (product_id) REFERENCES products (product_id)\n    );\n    \"\"\")\n    print(\"Created 'order_items' table.\")\n\n    # Insert Sample Data\n    customers_data = [\n        ('Alice', 'Smith', 'alice.s@email.com', '2023-01-15', 'New York', 'USA'),\n        ('Bob', 'Johnson', 'b.johnson@email.com', '2023-02-20', 'Los Angeles', 'USA'),\n        ('Charlie', 'Williams', 'charlie.w@email.com', '2023-03-10', 'Chicago', 'USA'),\n        ('Diana', 'Brown', 'diana.b@email.com', '2023-04-05', 'Houston', 'USA'),\n        ('Ethan', 'Davis', 'ethan.d@email.com', '2023-05-12', 'Phoenix', 'USA'),\n        ('Fiona', 'Miller', 'fiona.m@email.com', '2023-06-18', 'Philadelphia', 'USA'),\n        ('George', 'Wilson', 'george.w@email.com', '2023-07-22', 'San Antonio', 'USA'),\n        ('Hannah', 'Moore', 'hannah.m@email.com', '2023-08-30', 'San Diego', 'USA'),\n        ('Ian', 'Taylor', 'ian.t@email.com', '2023-09-05', 'Dallas', 'USA'),\n        ('Julia', 'Anderson', 'julia.a@email.com', '2023-10-11', 'San Jose', 'USA')\n    ]\n    cursor.executemany(\"INSERT INTO customers (first_name, last_name, email, registration_date, city, country) VALUES (?, ?, ?, ?, ?, ?)\", customers_data)\n    print(f\"Inserted {len(customers_data)} customers.\")\n\n    products_data = [\n        ('Laptop Pro', 'High-end laptop for professionals', 'Electronics', 1200.00, 50),\n        ('Wireless Mouse', 'Ergonomic wireless mouse', 'Accessories', 25.50, 200),\n        ('Mechanical Keyboard', 'RGB backlit mechanical keyboard', 'Accessories', 75.00, 150),\n        ('4K Monitor', '27-inch 4K UHD Monitor', 'Electronics', 350.00, 80),\n        ('Smartphone X', 'Latest generation smartphone', 'Electronics', 999.00, 120),\n        ('Coffee Maker', 'Drip coffee maker', 'Home Goods', 50.00, 300),\n        ('Running Shoes', 'Comfortable running shoes', 'Apparel', 90.00, 250),\n        ('Yoga Mat', 'Eco-friendly yoga mat', 'Sports', 30.00, 400),\n        ('Desk Lamp', 'Adjustable LED desk lamp', 'Home Goods', 45.00, 180),\n        ('Backpack', 'Durable backpack for travel', 'Accessories', 60.00, 220)\n    ]\n    cursor.executemany(\"INSERT INTO products (name, description, category, price, stock_quantity) VALUES (?, ?, ?, ?, ?)\", products_data)\n    print(f\"Inserted {len(products_data)} products.\")\n\n    orders_data = []\n    start_date = datetime.now() - timedelta(days=60)\n    order_statuses = ['pending', 'processing', 'shipped', 'delivered', 'cancelled']\n    for i in range(1, 21): # Create 20 orders\n        customer_id = random.randint(1, 10)\n        order_date = start_date + timedelta(days=random.randint(0, 59), hours=random.randint(0, 23))\n        status = random.choice(order_statuses)\n        shipping_address = f\"{random.randint(100, 999)} Main St, Anytown\"\n        orders_data.append((customer_id, order_date.strftime('%Y-%m-%d %H:%M:%S'), status, None, shipping_address)) # Total amount calculated later\n\n    cursor.executemany(\"INSERT INTO orders (customer_id, order_date, status, total_amount, shipping_address) VALUES (?, ?, ?, ?, ?)\", orders_data)\n    print(f\"Inserted {len(orders_data)} orders.\")\n\n    order_items_data = []\n    order_totals = {} # Keep track of totals per order\n    for order_id in range(1, 21):\n        num_items = random.randint(1, 4)\n        order_total = 0\n        for _ in range(num_items):\n            product_id = random.randint(1, 10)\n            quantity = random.randint(1, 5)\n            # Get product price\n            cursor.execute(\"SELECT price FROM products WHERE product_id = ?\", (product_id,))\n            price_per_unit = cursor.fetchone()[0]\n            order_items_data.append((order_id, product_id, quantity, price_per_unit))\n            order_total += quantity * price_per_unit\n        order_totals[order_id] = round(order_total, 2)\n\n    cursor.executemany(\"INSERT INTO order_items (order_id, product_id, quantity, price_per_unit) VALUES (?, ?, ?, ?)\", order_items_data)\n    print(f\"Inserted {len(order_items_data)} order items.\")\n\n    # Update order totals\n    for order_id, total_amount in order_totals.items():\n        cursor.execute(\"UPDATE orders SET total_amount = ? WHERE order_id = ?\", (total_amount, order_id))\n    print(\"Updated order totals.\")\n\n    conn.commit()\n    conn.close()\n    print(f\"Database '{db_file}' created and populated successfully.\")\n\nif __name__ == \"__main__\":\n    populate_database()"
  },
  {
    "path": "cookbook/pocketflow-text2sql/requirements.txt",
    "content": "pocketflow>=0.0.1\nopenai>=1.0.0\npyyaml>=6.0\nsqlite3>=3.0\n"
  },
  {
    "path": "cookbook/pocketflow-text2sql/utils/call_llm.py",
    "content": "import os\nfrom openai import OpenAI\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n\n# Example usage\nif __name__ == \"__main__\":\n    print(call_llm(\"Tell me a short joke\")) "
  },
  {
    "path": "cookbook/pocketflow-thinking/README.md",
    "content": "# Chain-of-Thought\n\nThis project demonstrates an implementation that orchestrates a Chain-of-Thought process, enabling LLMs to solve complex reasoning problems by thinking step-by-step. It's designed to improve problem-solving accuracy through deliberate, structured reasoning managed externally.\n\nThis implementation is based on: [Build Chain-of-Thought From Scratch - Tutorial for Dummies](https://zacharyhuang.substack.com/p/build-chain-of-thought-from-scratch).\n\n## Features\n\n- Improves model reasoning on complex problems.\n- Leverages capable instruction-following models (e.g., Claude 3.7 Sonnet, GPT-4 series) to perform structured Chain-of-Thought reasoning.\n- Solves problems that direct prompting often fails on by breaking them down systematically.\n- Provides detailed reasoning traces, including step-by-step evaluation and planning, for verification.\n\n## Getting Started\n\n1.  **Install Packages:**\n    ```bash\n    pip install -r requirements.txt\n    ```\n\n2.  **Set API Key:**\n    ```bash\n    export ANTHROPIC_API_KEY=\"your-api-key-here\"\n    ```\n\n3.  **Verify API Key (Optional):**\n    Run a quick check to ensure your key and environment are set up correctly.\n    ```bash\n    python utils.py\n    ```\n\n4.  **Run Default Example:**\n    Execute the main script to see the process in action with the default Jane Street problem.\n    ```bash\n    python main.py\n    ```\n    The default question is:\n    > You keep rolling a fair die until you roll three, four, five in that order consecutively on three rolls. What is the probability that you roll the die an odd number of times?\n\n5.  **Run Custom Problem:**\n    Provide your own reasoning problem using the `--` argument.\n    ```bash\n    python main.py --\"Your complex reasoning problem here\"\n    ```\n\n## How It Works\n\nThe implementation uses a self-looping PocketFlow node (`ChainOfThoughtNode`) that guides an LLM through a structured problem-solving process:\n\n```mermaid\nflowchart LR\n    cot[ChainOfThoughtNode] -->|\"continue\"| cot\n```\n\nIn each loop (thought step), the node directs the LLM to:\n1.  Evaluate the previous thought's reasoning and results.\n2.  Execute the next pending step according to a maintained plan.\n3.  Update the plan, marking the step done (with results) or noting issues.\n4.  Refine the plan if steps need breaking down or errors require correction.\n5.  Decide if further thinking (`next_thought_needed`) is required based on the plan state.\n\nThis external orchestration enforces a systematic approach, helping models tackle problems that are difficult with a single prompt.\n\n## Comparison with Different Approaches\n\n-   **Standard Prompting**: Techniques like asking the model to \"think step by step\" within a single prompt can help, but the reasoning might lack depth or structure, and the model can easily lose track or make unrecoverable errors.\n-   **Native Extended Thinking Modes**: Some models (like Claude 3.7, GPT-o1, etc.) offer dedicated modes or features explicitly for extended reasoning, often yielding strong results directly via API calls.\n-   **This Implementation**: Demonstrates how to orchestrate a structured Chain-of-Thought process using standard LLMs (even those without a specific native 'extended thinking' mode), managing the steps, planning, and evaluation externally via prompt engineering and flow control.\n\n## Example Thinking Process\n\nLet's try out this challenging [Jane Street Quant Trading Interview Question](https://www.youtube.com/watch?v=gQJTkuEVPrU):\n\n> **Problem**: You keep rolling a fair die until you roll three, four, five in that order consecutively on three rolls. What is the probability that you roll the die an odd number of times?\n\nThis problem demonstrates why structured Chain-of-Thought is valuable:\n\n-   **Standard models (single prompt)**: Often get the wrong answer or provide flawed reasoning.\n-   **Models using native thinking modes**: Can find the correct answer (216/431 ≈ 0.5012), though performance and reasoning clarity may vary.\n-   **This implementation (orchestrating a capable LLM)**: Can guide the model towards the correct answer by enforcing a step-by-step plan, evaluation, and refinement loop.\n\nFor comparison:\n-   [Claude 3.7 Sonnet (single prompt)](https://claude.ai/share/da139326-42fe-42d9-9d7b-35870daa5c1b): Wrong answer\n-   [Claude 3.7 Sonnet (using built-in thinking)](https://claude.ai/share/6f4140ed-f33c-4949-8778-a57719498e40): Correct answer after 3m, 45s\n-   [GPT-o1 (using built-in thinking)](https://chatgpt.com/share/67fee0fd-2600-8000-bcdf-76e40a986ee4): Correct answer after 2m, 0s\n-   [GPT-o1 pro (using built-in thinking)](https://chatgpt.com/share/67fee11b-530c-8000-92d1-609b6ca49c9c): Correct answer after 4m, 24s\n\nBelow is an example output trace showing how **this implementation guides Claude 3.7 Sonnet** through the problem-solving process:\n\n> **Note:** Even with structured thinking orchestration, models don't always get the right answer, especially on very complex or novel problems. However, this approach significantly improves the robustness of the reasoning process and provides a traceable path for verification and debugging.\n\n```\n🤔 Processing question: You keep rolling a fair die until you roll three, four, five in that order consecutively on three rolls. What is the probability that you roll the die an odd number of times?\n\nThought 1:\n  Let me think through this problem by setting up a clear approach.\n\n  We need to find the probability that the total number of die rolls will be odd, given we stop when we roll \"3,4,5\" consecutively in that order.\n\n  This is a problem about a stochastic process that ends when a specific pattern occurs. To solve it, I'll:\n  1. Set up a Markov chain model to track progress toward the target sequence\n  2. Calculate expected lengths and probabilities for different outcomes\n  3. Determine if the total number of rolls has equal probability of being odd vs. even\n\n  First, I need to understand what states we can be in while rolling the die:\n  - State 0: Haven't rolled any of the sequence yet\n  - State 1: Just rolled a 3, waiting for 4\n  - State 2: Rolled 3 followed by 4, waiting for 5\n  - State 3: Success! Rolled the full \"3,4,5\" sequence\n\n  When the die is rolled, we transition between these states with certain probabilities. Each state depends only on the previous 0-2 rolls, making this a Markov process.\n\nCurrent Plan Status:\n    - [Done] Understand the problem structure: Identified that we need the probability of an odd number of total rolls until getting '3,4,5' consecutively. Set up a Markov chain with states representing progress toward target sequence.\n    - [Pending] Set up the Markov model with transition probabilities\n      - [Pending] Define relevant states in the Markov chain\n      - [Pending] Calculate transition probabilities between states\n    - [Pending] Calculate generating functions for the process\n    - [Pending] Determine probability of odd number of rolls\n      - [Pending] Extract coefficient information from generating function\n      - [Pending] Compute final probability value\n    - [Pending] Verify solution with alternative approach\n    - [Pending] Conclusion\n--------------------------------------------------\n\nThought 2:\n  Evaluation of Thought 1: Correct. The approach of using a Markov chain to model this problem is appropriate. Identifying the states (0: no part of sequence, 1: just rolled 3, 2: rolled 3,4, 3: success with 3,4,5) correctly captures the progression toward our target sequence.\n\n  I'll now set up the Markov model with transition probabilities. Each state represents our progress toward the target sequence \"3,4,5\".\n\n  State 0: Haven't rolled any relevant part of the sequence yet\n  - If we roll a 3: Move to State 1 (probability 1/6)\n  - If we roll anything else: Stay in State 0 (probability 5/6)\n\n  State 1: Just rolled a 3, waiting for a 4\n  - If we roll a 4: Move to State 2 (probability 1/6)\n  - If we roll a 3: Stay in State 1 (probability 1/6)\n  - If we roll anything else: Return to State 0 (probability 4/6)\n\n  State 2: Rolled 3 followed by 4, waiting for a 5\n  - If we roll a 5: Move to State 3 (success - probability 1/6)\n  - If we roll a 3: Move to State 1 (probability 1/6)\n  - If we roll anything else: Return to State 0 (probability 4/6)\n\n  State 3: Success! Rolled the sequence \"3,4,5\"\n  - This is an absorbing state; once we reach it, we stop rolling.\n\n  These transition probabilities can be represented as a matrix:\n  P = [\n      [5/6, 1/6, 0,   0  ],\n      [4/6, 1/6, 1/6, 0  ],\n      [4/6, 1/6, 0,   1/6],\n      [0,   0,   0,   1  ]\n  ]\n\n  This completes the setup of the Markov model with transition probabilities.\n\nCurrent Plan Status:\n    - [Done] Understand the problem structure: Identified that we need the probability of an odd number of total rolls until getting '3,4,5' consecutively. Set up a Markov chain with states representing progress toward target sequence.\n    - [Done] Set up the Markov model with transition probabilities: Defined 4 states (0: no sequence, 1: just rolled 3, 2: rolled 3,4, 3: success with 3,4,5) and calculated transition probabilities between all states in matrix form.\n    - [Pending] Calculate generating functions for the process\n      - [Pending] Set up generating function equations for each state\n      - [Pending] Solve the system of equations to find generating functions\n    - [Pending] Determine probability of odd number of rolls\n      - [Pending] Extract coefficient information from generating function\n      - [Pending] Compute final probability value\n    - [Pending] Verify solution with alternative approach\n    - [Pending] Conclusion\n--------------------------------------------------\n\nThought 3:\n  Evaluation of Thought 2: Correct. The Markov chain setup with the four states and the transition probability matrix accurately represents our problem. Now I'll proceed to calculating the generating functions for this process.\n\n  To calculate the generating functions, I'll use the variable z to represent a single die roll. For each state, the generating function gives us information about the number of rolls until we reach our success state.\n\n  Let G₀(z), G₁(z), G₂(z), and G₃(z) be the generating functions for reaching the success state (State 3) when starting from State 0, 1, 2, and 3 respectively.\n\n  G₃(z) = 1 (since we're already in the success state)\n\n  For the other states, we can write equations based on the first roll and subsequent state:\n\n  G₀(z) = z·(5/6·G₀(z) + 1/6·G₁(z))\n  This means: we roll once (factor z), then with probability 5/6 we stay in State 0, or with probability 1/6 we move to State 1.\n\n  G₁(z) = z·(4/6·G₀(z) + 1/6·G₁(z) + 1/6·G₂(z))\n  When in State 1, we roll once (factor z), then either move to State 0, stay in State 1, or advance to State 2.\n\n  G₂(z) = z·(4/6·G₀(z) + 1/6·G₁(z) + 1/6·G₃(z))\n      = z·(4/6·G₀(z) + 1/6·G₁(z) + 1/6·1)\n      = z·(4/6·G₀(z) + 1/6·G₁(z) + 1/6)\n\n  Now I can solve this system of equations for G₀(z):\n\n  From G₀(z) = z·(5/6·G₀(z) + 1/6·G₁(z)):\n  G₀(z) - 5z/6·G₀(z) = z/6·G₁(z)\n  G₀(z)(1 - 5z/6) = z/6·G₁(z)\n  G₀(z) = (z/6·G₁(z))/(1 - 5z/6) ... (1)\n\n  From G₁(z) = z·(4/6·G₀(z) + 1/6·G₁(z) + 1/6·G₂(z)):\n  G₁(z) - z/6·G₁(z) = 4z/6·G₀(z) + z/6·G₂(z)\n  G₁(z)(1 - z/6) = 4z/6·G₀(z) + z/6·G₂(z) ... (2)\n\n  From G₂(z) = z·(4/6·G₀(z) + 1/6·G₁(z) + 1/6):\n  G₂(z) = 4z/6·G₀(z) + z/6·G₁(z) + z/6 ... (3)\n\n  Substituting (3) into (2):\n  G₁(z)(1 - z/6) = 4z/6·G₀(z) + z/6·(4z/6·G₀(z) + z/6·G₁(z) + z/6)\n  G₁(z)(1 - z/6) = 4z/6·G₀(z) + 4z²/36·G₀(z) + z²/36·G₁(z) + z²/36\n  G₁(z)(1 - z/6 - z²/36) = 4z/6·G₀(z)(1 + z/6) + z²/36\n  G₁(z) = (4z/6·G₀(z)(1 + z/6) + z²/36)/(1 - z/6 - z²/36) ... (4)\n\n  Substituting (4) into (1) gives us G₀(z) in terms of z only, which I can then simplify to find the closed form.\n\n  This is getting algebraically intricate. Let me take a step back and consider a different approach using first-step analysis with parity.\n\nCurrent Plan Status:\n    - [Done] Understand the problem structure: Identified that we need probability of an odd number of total rolls until getting '3,4,5' consecutively. Set up a Markov chain with states representing progress toward target sequence.\n    - [Done] Set up the Markov model with transition probabilities: Defined 4 states (0: no sequence, 1: just rolled 3, 2: rolled 3,4, 3: success with 3,4,5) and calculated transition probabilities between all states in matrix form.\n    - [Verification Needed] Calculate generating functions for the process (The generating function approach became algebraically complicated. Consider a more direct approach using first-step analysis with parity.)\n      - [Done] Set up generating function equations for each state: Established equations G₀(z), G₁(z), G₂(z), and G₃(z) to track rolls until success.\n      - [Verification Needed] Solve the system of equations to find generating functions (Equations become unwieldy; may not be most efficient approach.)\n    - [Pending] Use first-step analysis with parity\n      - [Pending] Define probability variables with parity information\n      - [Pending] Set up equations relating odd/even outcome probabilities\n      - [Pending] Solve the system of equations\n    - [Pending] Determine probability of odd number of rolls\n      - [Pending] Extract final probability from analysis\n      - [Pending] Verify the mathematical result\n    - [Pending] Verify solution with alternative approach\n    - [Pending] Conclusion\n--------------------------------------------------\n\nThought 4:\n  Evaluation of Thought 3: Correct, but pivoting is wise. The generating function approach, while theoretically sound, is becoming algebraically complex. The decision to switch to a first-step analysis with parity is appropriate for this problem.\n\n  I'll now use first-step analysis focusing specifically on the parity of the number of rolls.\n\n  Let's define:\n  - P₀: Probability of rolling an odd number of times when starting in State 0\n  - P₁: Probability of rolling an odd number of times when starting in State 1\n  - P₂: Probability of rolling an odd number of times when starting in State 2\n\n  Using first-step analysis, we can write equations for each state:\n\n  For State 0:\n  - If we roll a 3 (prob 1/6), we go to State 1 with 1 roll used (odd). The total number of rolls will be odd if the remaining rolls from State 1 are even. \n  - If we roll anything else (prob 5/6), we stay in State 0 with 1 roll used (odd). The total will be odd if the remaining rolls from State 0 are even.\n\n  Therefore: P₀ = (1/6)*(1-P₁) + (5/6)*(1-P₀) = 5/6 - 5P₀/6 + 1/6 - P₁/6\n\n  For State 1:\n  - If we roll a 4 (prob 1/6), we go to State 2 with 1 roll used (odd). The total will be odd if remaining rolls from State 2 are even.\n  - If we roll a 3 (prob 1/6), we stay in State 1 with 1 roll used (odd). The total will be odd if remaining rolls from State 1 are even.\n  - If we roll anything else (prob 4/6), we go to State 0 with 1 roll used (odd). The total will be odd if remaining rolls from State 0 are even.\n\n  Therefore: P₁ = (1/6)*(1-P₂) + (1/6)*(1-P₁) + (4/6)*(1-P₀) = 1 - P₂/6 - P₁/6 - 4P₀/6\n\n  For State 2:\n  - If we roll a 5 (prob 1/6), we're done with 1 more roll (odd). Total is definitely odd.\n  - If we roll a 3 (prob 1/6), we go to State 1 with 1 roll used (odd). The total will be odd if remaining rolls from State 1 are even.\n  - If we roll anything else (prob 4/6), we go to State 0 with 1 roll used (odd). The total will be odd if remaining rolls from State 0 are even.\n\n  Therefore: P₂ = (1/6)*1 + (1/6)*(1-P₁) + (4/6)*(1-P₀) = 1/6 + 1/6 - P₁/6 + 4/6 - 4P₀/6 = 6/6 - P₁/6 - 4P₀/6\n\n  Now I have a system of 3 equations with 3 unknowns:\n  P₀ = 1 - 5P₀/6 - P₁/6\n  P₁ = 1 - P₁/6 - P₂/6 - 4P₀/6\n  P₂ = 1 - P₁/6 - 4P₀/6\n\n  Simplifying:\n  (6/6)P₀ = 6/6 - 5P₀/6 - P₁/6\n  (6/6)P₁ = 6/6 - P₁/6 - P₂/6 - 4P₀/6\n  (6/6)P₂ = 6/6 - P₁/6 - 4P₀/6\n\n  Further simplifying:\n  11P₀/6 + P₁/6 = 1\n  4P₀/6 + 7P₁/6 + P₂/6 = 1\n  4P₀/6 + P₁/6 + 6P₂/6 = 1\n\n  This is a linear system of equations that I can solve.\n\nCurrent Plan Status:\n    - [Done] Understand the problem structure: Identified that we need probability of an odd number of total rolls until getting '3,4,5' consecutively. Set up a Markov chain with states representing progress toward target sequence.\n    - [Done] Set up the Markov model with transition probabilities: Defined 4 states (0: no sequence, 1: just rolled 3, 2: rolled 3,4, 3: success with 3,4,5) and calculated transition probabilities between all states in matrix form.\n    - [Verification Needed] Calculate generating functions for the process (Approach became algebraically complicated; shifted to more direct first-step analysis with parity.)\n      - [Done] Set up generating function equations for each state: Established equations G₀(z), G₁(z), G₂(z), and G₃(z) to track rolls until success.\n      - [Verification Needed] Solve the system of equations to find generating functions (Equations became unwieldy; more efficient to use first-step analysis with parity instead.)\n    - [Done] Use first-step analysis with parity: Defined variables P₀, P₁, P₂ for probability of odd rolls from each state. Set up system of equations tracking odd/even outcomes.\n      - [Done] Define probability variables with parity information: Defined P₀, P₁, P₂ as probabilities of odd number of rolls starting from respective states.\n      - [Done] Set up equations relating odd/even outcome probabilities: Derived equations: 11P₀/6 + P₁/6 = 1, 4P₀/6 + 7P₁/6 + P₂/6 = 1, 4P₀/6 + P₁/6 + 6P₂/6 = 1.\n      - [Pending] Solve the system of equations\n    - [Pending] Determine probability of odd number of rolls\n      - [Pending] Extract final probability from analysis\n      - [Pending] Verify the mathematical result\n    - [Pending] Verify solution with alternative approach\n    - [Pending] Conclusion\n--------------------------------------------------\n\nThought 5:\n  Evaluation of Thought 4: Correct. The first-step analysis with parity is well set up, with appropriate variables P₀, P₁, and P₂ representing the probability of rolling an odd number of times from each state. The system of equations is correctly derived, though I notice it's written in slightly different forms across the thought. Let me solve this system now.\n\n  Taking the system of equations from the end of Thought 4:\n  11P₀/6 + P₁/6 = 1\n  4P₀/6 + 7P₁/6 + P₂/6 = 1\n  4P₀/6 + P₁/6 + 6P₂/6 = 1\n\n  Let's multiply all equations by 6 to eliminate fractions:\n  11P₀ + P₁ = 6\n  4P₀ + 7P₁ + P₂ = 6\n  4P₀ + P₁ + 6P₂ = 6\n\n  Subtracting the third equation from the second:\n  (4P₀ + 7P₁ + P₂) - (4P₀ + P₁ + 6P₂) = 6 - 6\n  6P₁ - 5P₂ = 0\n  6P₁ = 5P₂\n  P₁ = 5P₂/6\n\n  From the first equation:\n  11P₀ + P₁ = 6\n  11P₀ + 5P₂/6 = 6\n  66P₀ + 5P₂ = 36\n\n  From the third equation:\n  4P₀ + P₁ + 6P₂ = 6\n  4P₀ + 5P₂/6 + 6P₂ = 6\n  24P₀ + 5P₂ + 36P₂ = 36\n  24P₀ + 41P₂ = 36\n\n  Now solving for P₀ in terms of P₂:\n  24P₀ + 41P₂ = 36\n  24P₀ = 36 - 41P₂\n  P₀ = (36 - 41P₂)/24 = 3/2 - 41P₂/24\n\n  Substituting back into 66P₀ + 5P₂ = 36:\n  66(3/2 - 41P₂/24) + 5P₂ = 36\n  99/2 - 66*41P₂/24 + 5P₂ = 36\n  99/2 - 2706P₂/24 + 5P₂ = 36\n  99/2 - 2706P₂/24 + 120P₂/24 = 36\n  99/2 - (2706-120)P₂/24 = 36\n  99/2 - 2586P₂/24 = 36\n  99 - 2586P₂/12 = 72\n  99 - 72 = 2586P₂/12\n  27 = 2586P₂/12\n  27*12 = 2586P₂\n  324 = 2586P₂\n  P₂ = 324/2586 = 108/862 = 54/431\n\n  Now we can find P₁:\n  P₁ = 5P₂/6 = 5(54/431)/6 = 270/2586 = 45/431\n\n  And P₀:\n  P₀ = 3/2 - 41P₂/24 = 3/2 - 41(54/431)/24 = 3/2 - 2214/10344 = 3/2 - 1107/5172\n\n  Let's calculate: \n  P₀ = 3/2 - 1107/5172 = 7758/5172 - 1107/5172 = 6651/5172 = 2217/1724\n\n  Now we have:\n  P₀ = 2217/1724\n  P₁ = 45/431\n  P₂ = 54/431\n\n  Wait, I see an error in my calculation. Let me restart with the system of equations.\n\n  From the equations:\n  11P₀/6 + P₁/6 = 1\n  4P₀/6 + 7P₁/6 + P₂/6 = 1\n  4P₀/6 + P₁/6 + 6P₂/6 = 1\n\n  Multiplying by 6:\n  11P₀ + P₁ = 6\n  4P₀ + 7P₁ + P₂ = 6\n  4P₀ + P₁ + 6P₂ = 6\n\n  From the first: P₁ = 6 - 11P₀\n\n  Substituting into the second:\n  4P₀ + 7(6 - 11P₀) + P₂ = 6\n  4P₀ + 42 - 77P₀ + P₂ = 6\n  4P₀ - 77P₀ + P₂ = 6 - 42\n  -73P₀ + P₂ = -36\n  P₂ = -36 + 73P₀\n\n  Substituting P₁ and P₂ into the third equation:\n  4P₀ + (6 - 11P₀) + 6(-36 + 73P₀) = 6\n  4P₀ + 6 - 11P₀ - 216 + 438P₀ = 6\n  4P₀ - 11P₀ + 438P₀ = 6 - 6 + 216\n  431P₀ = 216\n  P₀ = 216/431 = 8/16 = 1/2\n\n  Now we can find:\n  P₁ = 6 - 11P₀ = 6 - 11(216/431) = 6 - 2376/431 = 2586/431 - 2376/431 = 210/431\n  P₂ = -36 + 73P₀ = -36 + 73(216/431) = -36 + 15768/431 = -15552/431 + 15768/431 = 216/431\n\n  The answer to our original question is P₀, which is 216/431.\n\n  Actually, I notice the fraction simplified to 1/2 above is incorrect (216/431 ≠ 8/16). Let's double-check with the equations:\n\n  From 11P₀ + P₁ = 6 and P₁ = 210/431, we get:\n  11(216/431) + 210/431 = 6\n  2376/431 + 210/431 = 6\n  2586/431 = 6\n  2586 = 2586\n\n  This confirms our answer: P₀ = 216/431, which is the probability of rolling the die an odd number of times.\n\nCurrent Plan Status:\n    - [Done] Understand the problem structure: Identified that we need probability of an odd number of total rolls until getting '3,4,5' consecutively. Set up a Markov chain with states representing progress toward target sequence.\n    - [Done] Set up the Markov model with transition probabilities: Defined 4 states (0: no sequence, 1: just rolled 3, 2: rolled 3,4, 3: success with 3,4,5) and calculated transition probabilities between all states in matrix form.\n    - [Verification Needed] Calculate generating functions for the process (The generating function approach was algebraically complex; pivoted to first-step analysis with parity instead.)\n      - [Done] Set up generating function equations for each state: Established equations G₀(z), G₁(z), G₂(z), and G₃(z) to track rolls until success.\n      - [Verification Needed] Solve the system of equations to find generating functions (Equations became unwieldy; more efficient to use first-step analysis with parity instead.)\n    - [Done] Use first-step analysis with parity: Successfully applied first-step analysis with parity to solve the problem directly.\n      - [Done] Define probability variables with parity information: Defined P₀, P₁, P₂ as probabilities of odd number of rolls starting from respective states.\n      - [Done] Set up equations relating odd/even outcome probabilities: Derived equations: 11P₀/6 + P₁/6 = 1, 4P₀/6 + 7P₁/6 + P₂/6 = 1, 4P₀/6 + P₁/6 + 6P₂/6 = 1.\n      - [Done] Solve the system of equations: Found P₀ = 216/431, P₁ = 210/431, P₂ = 216/431.\n    - [Done] Determine probability of odd number of rolls: The probability of rolling the die an odd number of times is 216/431 ≈ 0.501\n      - [Done] Extract final probability from analysis: Identified P₀ = 216/431 as the answer to our original question.\n      - [Done] Verify the mathematical result: Verified solution by substituting back into original equations.\n    - [Pending] Verify solution with alternative approach\n    - [Pending] Conclusion\n--------------------------------------------------\n\nThought 6:\n  Evaluation of Thought 5: Correct. The calculation was methodical and accurate, resulting in the solution P₀ = 216/431. The self-correction when an arithmetic error was spotted was appropriate. The substitution back into the original equation verified that the answer is correct.\n\n  Let me now verify our solution using an alternative approach. The problem asks for the probability of an odd number of rolls when stopping at the sequence \"3,4,5\". An alternative way to approach this is to consider the complementary probability - the probability of an even number of rolls.\n\n  Since P₀ = 216/431 is the probability of an odd number of rolls, the probability of an even number of rolls must be 1 - 216/431 = 215/431.\n\n  Let me verify this with the equations we derived:\n  - Let Q₀, Q₁, Q₂ be the probabilities of rolling an even number of times from states 0, 1, and 2 respectively.\n  - Since the events \"odd number of rolls\" and \"even number of rolls\" are complementary: Q₀ = 1-P₀, Q₁ = 1-P₁, Q₂ = 1-P₂\n  - Therefore: Q₀ = 1 - 216/431 = 215/431, Q₁ = 1 - 210/431 = 221/431, Q₂ = 1 - 216/431 = 215/431\n\n  Now, I can derive the equations for Q₀, Q₁, Q₂ directly:\n\n  For State 0:\n  - If we roll a 3 (prob 1/6), we transition to State 1 after 1 roll (odd). The total will be even if the remaining rolls from State 1 are odd.\n  - If we roll anything else (prob 5/6), we stay in State 0 after 1 roll (odd). The total will be even if the remaining rolls from State 0 are odd.\n\n  Therefore: Q₀ = (1/6)(P₁) + (5/6)(P₀) = P₁/6 + 5P₀/6 = 210/431/6 + 5(216/431)/6 = 210/2586 + 1080/2586 = 1290/2586 = 215/431\n\n  For State 1:\n  - If we roll a 4 (prob 1/6), we go to State 2 after 1 roll (odd). The total will be even if the remaining rolls from State 2 are odd.\n  - If we roll a 3 (prob 1/6), we stay in State 1 after 1 roll (odd). The total will be even if the remaining rolls from State 1 are odd.\n  - If we roll anything else (prob 4/6), we go to State 0 after 1 roll (odd). The total will be even if the remaining rolls from State 0 are odd.\n\n  Therefore: Q₁ = (1/6)(P₂) + (1/6)(P₁) + (4/6)(P₀) = P₂/6 + P₁/6 + 4P₀/6 = 216/431/6 + 210/431/6 + 4(216/431)/6 = 216/2586 + 210/2586 + 864/2586 = 1290/2586 = 215/431\n\n  Wait, that doesn't match our expected Q₁ = 221/431. Let me double-check...\n\n  The direct calculation of Q₁ gives a different result than 1-P₁. This suggests an error somewhere. Let me reconsider our system:\n\n  If P₀ + Q₀ = 1, P₁ + Q₁ = 1, P₂ + Q₂ = 1, then our system should be consistent. Let me verify our values from Thought 5:\n\n  From 11P₀ + P₁ = 6 and P₀ = 216/431:\n  11(216/431) + P₁ = 6\n  2376/431 + P₁ = 6\n  P₁ = 6 - 2376/431 = 2586/431 - 2376/431 = 210/431\n\n  From 4P₀ + P₁ + 6P₂ = 6:\n  4(216/431) + 210/431 + 6P₂ = 6\n  864/431 + 210/431 + 6P₂ = 6\n  1074/431 + 6P₂ = 6\n  6P₂ = 6 - 1074/431 = 2586/431 - 1074/431 = 1512/431\n  P₂ = 1512/431/6 = 1512/2586 = 252/431\n\n  This doesn't match our calculated P₂ = 216/431 from Thought 5. Let me solve the system again, more carefully.\n\n  From the system:\n  11P₀ + P₁ = 6\n  4P₀ + 7P₁ + P₂ = 6\n  4P₀ + P₁ + 6P₂ = 6\n\n  Subtracting the first from the third:\n  (4P₀ + P₁ + 6P₂) - (11P₀ + P₁) = 6 - 6\n  4P₀ - 11P₀ + 6P₂ = 0\n  -7P₀ + 6P₂ = 0\n  6P₂ = 7P₀\n  P₂ = 7P₀/6\n\n  Subtracting the first from the second:\n  (4P₀ + 7P₁ + P₂) - (11P₀ + P₁) = 6 - 6\n  4P₀ - 11P₀ + 6P₁ + P₂ = 0\n  -7P₀ + 6P₁ + P₂ = 0\n\n  Substituting P₂ = 7P₀/6:\n  -7P₀ + 6P₁ + 7P₀/6 = 0\n  -42P₀ + 36P₁ + 7P₀ = 0\n  -35P₀ + 36P₁ = 0\n  36P₁ = 35P₀\n  P₁ = 35P₀/36\n\n  Now, from the first equation:\n  11P₀ + P₁ = 6\n  11P₀ + 35P₀/36 = 6\n  396P₀ + 35P₀ = 216\n  431P₀ = 216\n  P₀ = 216/431\n\n  Therefore:\n  P₁ = 35P₀/36 = 35(216/431)/36 = 35(6)/36(431/6) = 35/6 ÷ 431/6 = 35/431\n  P₂ = 7P₀/6 = 7(216/431)/6 = 7(6)/6(431/6) = 7/431/6 × 6 = 7/431 × 6 = 42/431\n\n  Hmm, these values are different from what we calculated in Thought 5. Let me check these values in the original equations:\n\n  11(216/431) + 35/431 = 6?\n  2376/431 + 35/431 = 6?\n  2411/431 = 6?\n  2411 = 2586?\n\n  This doesn't check out. Let me solve the system one more time using a different approach.\n\n  From the original system:\n  11P₀ + P₁ = 6                 (1)\n  4P₀ + 7P₁ + P₂ = 6            (2)\n  4P₀ + P₁ + 6P₂ = 6            (3)\n\n  From (3) - (1):\n  4P₀ + P₁ + 6P₂ - 11P₀ - P₁ = 6 - 6\n  -7P₀ + 6P₂ = 0\n  6P₂ = 7P₀\n  P₂ = 7P₀/6                    (4)\n\n  From (2) - (1):\n  4P₀ + 7P₁ + P₂ - 11P₀ - P₁ = 6 - 6\n  -7P₀ + 6P₁ + P₂ = 0\n\n  Substituting (4):\n  -7P₀ + 6P₁ + 7P₀/6 = 0\n  -42P₀ + 36P₁ + 7P₀ = 0\n  -35P₀ + 36P₁ = 0\n  36P₁ = 35P₀\n  P₁ = 35P₀/36                  (5)\n\n  Substituting (5) into (1):\n  11P₀ + 35P₀/36 = 6\n  (11×36)P₀ + 35P₀ = 6×36\n  396P₀ + 35P₀ = 216\n  431P₀ = 216\n  P₀ = 216/431\n\n  Now calculate:\n  P₁ = 35P₀/36 = 35(216/431)/36 = 7560/15516 = 35/72 × 216/431 = 35×216/36×431 = 7560/15516 = 35/72 × 216/431 = 7560/15516 = 1260/2586 = 210/431\n\n  Let me verify:\n  11(216/431) + 210/431 = 6?\n  2376/431 + 210/431 = 6?\n  2586/431 = 6?\n  2586 = 2586 ✓\n\n  Now for P₂:\n  P₂ = 7P₀/6 = 7(216/431)/6 = 1512/2586 = 252/431\n\n  Checking:\n  4(216/431) + 210/431 + 6(252/431) = 6?\n  864/431 + 210/431 + 1512/431 = 6?\n  2586/431 = 6?\n  2586 = 2586 ✓\n\n  Our final answer is: P₀ = 216/431, P₁ = 210/431, P₂ = 252/431\n\n  This confirms our result that the probability of rolling an odd number of times is 216/431 ≈ 0.5012.\n\nCurrent Plan Status:\n    - [Done] Understand the problem structure: Identified that we need probability of an odd number of total rolls until getting '3,4,5' consecutively. Set up a Markov chain with states representing progress toward target sequence.\n    - [Done] Set up the Markov model with transition probabilities: Defined 4 states (0: no sequence, 1: just rolled 3, 2: rolled 3,4, 3: success with 3,4,5) and calculated transition probabilities between all states in matrix form.\n    - [Verification Needed] Calculate generating functions for the process (The generating function approach was algebraically complex; pivoted to first-step analysis with parity instead.)\n      - [Done] Set up generating function equations for each state: Established equations G₀(z), G₁(z), G₂(z), and G₃(z) to track rolls until success.\n      - [Verification Needed] Solve the system of equations to find generating functions (Equations became unwieldy; more efficient to use first-step analysis with parity instead.)\n    - [Done] Use first-step analysis with parity: Successfully applied first-step analysis with parity to solve the problem directly.\n      - [Done] Define probability variables with parity information: Defined P₀, P₁, P₂ as probabilities of odd number of rolls starting from respective states.\n      - [Done] Set up equations relating odd/even outcome probabilities: Derived equations: 11P₀/6 + P₁/6 = 1, 4P₀/6 + 7P₁/6 + P₂/6 = 1, 4P₀/6 + P₁/6 + 6P₂/6 = 1.\n      - [Done] Solve the system of equations: Found P₀ = 216/431, P₁ = 210/431, P₂ = 252/431.\n    - [Done] Determine probability of odd number of rolls: The probability of rolling the die an odd number of times is 216/431 ≈ 0.5012.\n      - [Done] Extract final probability from analysis: Identified P₀ = 216/431 as the answer to our original question.\n      - [Done] Verify the mathematical result: Verified solution by substituting back into original equations.\n    - [Done] Verify solution with alternative approach: Double-checked our solution by re-solving the system and verifying that the complementary probability approach gives consistent results.\n    - [Pending] Conclusion\n--------------------------------------------------\n\nThought 7 (Conclusion):\n  Evaluation of Thought 6: Correct. The analysis carefully re-solved the system of equations to verify our answer and found P₀ = 216/431, P₁ = 210/431, and P₂ = 252/431. The verification process was thorough, checking the solution by substituting back into the original equations, confirming that the probability of rolling an odd number of times is 216/431 ≈ 0.5012.\n\n  Now I'll provide a conclusion that summarizes the problem, our approach, and the final answer.\n\n  We set out to find the probability that we roll a fair die an odd number of times until getting the sequence \"3,4,5\" consecutively in that order.\n\n  Our approach used a Markov chain model with states representing progress toward the target sequence:\n  - State 0: Haven't rolled any part of the sequence yet\n  - State 1: Just rolled a 3, waiting for 4\n  - State 2: Rolled 3 followed by 4, waiting for 5\n  - State 3: Success! Rolled the full \"3,4,5\" sequence\n\n  Initially, we considered a generating function approach, but pivoted to a more direct first-step analysis with parity. We defined variables P₀, P₁, and P₂ representing the probabilities of an odd number of total rolls when starting from each respective state.\n\n  Through careful derivation and solution of a system of linear equations, we found:\n  P₀ = 216/431 ≈ 0.5012\n  P₁ = 210/431 ≈ 0.4872\n  P₂ = 252/431 ≈ 0.5847\n\n  Since we start from State 0 (no part of the sequence rolled yet), the answer to our original question is P₀ = 216/431. \n\n  To verify this result, we solved the system multiple times and checked that our values satisfied the original equations.\n\n  The final answer is: The probability of rolling the die an odd number of times until getting the sequence \"3,4,5\" is 216/431 ≈ 0.5012.\n\n  This probability is slightly greater than 1/2, meaning it's slightly more likely to roll an odd number of times than an even number of times before completing the sequence.\n\nFinal Plan Status:\n    - [Done] Understand the problem structure: Identified that we need probability of an odd number of total rolls until getting '3,4,5' consecutively. Set up a Markov chain with states representing progress toward target sequence.\n    - [Done] Set up the Markov model with transition probabilities: Defined 4 states (0: no sequence, 1: just rolled 3, 2: rolled 3,4, 3: success with 3,4,5) and calculated transition probabilities between all states in matrix form.\n    - [Verification Needed] Calculate generating functions for the process (The generating function approach became algebraically complex; pivoted to first-step analysis with parity instead.)\n      - [Done] Set up generating function equations for each state: Established equations G₀(z), G₁(z), G₂(z), and G₃(z) to track rolls until success.\n      - [Verification Needed] Solve the system of equations to find generating functions (Equations became unwieldy; more efficient to use first-step analysis with parity instead.)\n    - [Done] Use first-step analysis with parity: Successfully applied first-step analysis with parity to solve the problem directly.\n      - [Done] Define probability variables with parity information: Defined P₀, P₁, P₂ as probabilities of odd number of rolls starting from respective states.\n      - [Done] Set up equations relating odd/even outcome probabilities: Derived equations: 11P₀/6 + P₁/6 = 1, 4P₀/6 + 7P₁/6 + P₂/6 = 1, 4P₀/6 + P₁/6 + 6P₂/6 = 1.\n      - [Done] Solve the system of equations: Found P₀ = 216/431, P₁ = 210/431, P₂ = 252/431.\n    - [Done] Determine probability of odd number of rolls: The probability of rolling the die an odd number of times is 216/431 ≈ 0.5012.\n      - [Done] Extract final probability from analysis: Identified P₀ = 216/431 as the answer to our original question.\n      - [Done] Verify the mathematical result: Verified solution by substituting back into original equations.\n    - [Done] Verify solution with alternative approach: Double-checked our solution by re-solving the system and verifying that the values satisfy all original equations.\n    - [Done] Conclusion: The probability of rolling the die an odd number of times until getting the sequence '3,4,5' is 216/431 ≈ 0.5012, which is slightly greater than 1/2.\n\n=== FINAL SOLUTION ===\nEvaluation of Thought 6: Correct. The analysis carefully re-solved the system of equations to verify our answer and found P₀ = 216/431, P₁ = 210/431, and P₂ = 252/431. The verification process was thorough, checking the solution by substituting back into the original equations, confirming that the probability of rolling an odd number of times is 216/431 ≈ 0.5012.\n\nNow I'll provide a conclusion that summarizes the problem, our approach, and the final answer.\n\nWe set out to find the probability that we roll a fair die an odd number of times until getting the sequence \"3,4,5\" consecutively in that order.\n\nOur approach used a Markov chain model with states representing progress toward the target sequence:\n- State 0: Haven't rolled any part of the sequence yet\n- State 1: Just rolled a 3, waiting for 4\n- State 2: Rolled 3 followed by 4, waiting for 5\n- State 3: Success! Rolled the full \"3,4,5\" sequence\n\nInitially, we considered a generating function approach, but pivoted to a more direct first-step analysis with parity. We defined variables P₀, P₁, and P₂ representing the probabilities of an odd number of total rolls when starting from each respective state.\n\nThrough careful derivation and solution of a system of linear equations, we found:\nP₀ = 216/431 ≈ 0.5012\nP₁ = 210/431 ≈ 0.4872\nP₂ = 252/431 ≈ 0.5847\n\nSince we start from State 0 (no part of the sequence rolled yet), the answer to our original question is P₀ = 216/431. \n\nTo verify this result, we solved the system multiple times and checked that our values satisfied the original equations.\n\nThe final answer is: The probability of rolling the die an odd number of times until getting the sequence \"3,4,5\" is 216/431 ≈ 0.5012.\n\nThis probability is slightly greater than 1/2, meaning it's slightly more likely to roll an odd number of times than an even number of times before completing the sequence.\n======================\n```\n"
  },
  {
    "path": "cookbook/pocketflow-thinking/design.md",
    "content": "# Chain of Thought Node Design\n\n## 1. Requirements\nCreate a self-looping Chain of Thought node that can:\n- Solve a problem step-by-step by maintaining and executing a structured plan.\n- Critically evaluate the previous step's reasoning and results before proceeding.\n- Refine the plan by breaking down complex steps into nested sub-steps.\n- Update the status of plan steps (`Pending`, `Done`, `Verification Needed`) and record concise results.\n- Handle potential errors identified during evaluation by adjusting the plan.\n- Provide a detailed trace of the thinking process and plan evolution.\n- Generate a final conclusion summarizing the solution when the plan is complete.\n\n## 2. Flow Design\nThis will be a simple flow with a single node that can call itself repeatedly based on whether more thinking is needed according to the plan:\n\n```mermaid\nflowchart LR\n    cot[ChainOfThoughtNode] -->|\"continue\"| cot\n```\n\n## 3. Utilities\nWe'll need one primary utility function:\n- `call_llm`: Call the LLM to generate the next thought (including evaluation, thinking, and updated plan) based on the problem, previous thoughts, and the current plan state. Helper functions (`format_plan`, `format_plan_for_prompt`) assist in presenting the plan.\n\n## 4. Node Design\n### Shared Store Design\n```python\nshared = {\n    \"problem\": str,             # The problem statement.\n    \"thoughts\": list[dict],     # List of thought dictionaries generated so far.\n    \"current_thought_number\": int, # Counter for the current thought being generated.\n    \"solution\": str | None    # Stores the final conclusion text when finished.\n}\n```\n\nEach thought dictionary added to the `shared[\"thoughts\"]` list will contain the structured output from the LLM's execution step, plus the thought number:\n```python\n{\n    \"thought_number\": int,      # The sequence number of this thought.\n    \"current_thinking\": str,    # Detailed text of the evaluation and thinking for this step.\n    \"planning\": list[dict],     # The updated plan structure (list of dictionaries).\n    \"next_thought_needed\": bool # Flag indicating if the loop should continue.\n}\n```\n\nThe `planning` list contains dictionaries representing steps, which can be nested:\n```python\n# Example structure for a plan step dictionary\n{\n    \"description\": str,                     # Description of the step.\n    \"status\": str,                          # \"Pending\", \"Done\", \"Verification Needed\".\n    \"result\": str | None,                   # Optional: Concise result when status is \"Done\".\n    \"mark\": str | None,                     # Optional: Reason for \"Verification Needed\".\n    \"sub_steps\": list[dict] | None          # Optional: Nested list for sub-steps.\n}\n```\n\n### Chain of Thought Node (`ChainOfThoughtNode`)\n-   **`type`**: Regular (self-looping node).\n-   **`prep`**:\n    -   Reads the problem statement and the list of previous thoughts from the shared store.\n    -   Formats the history of thoughts and the *last known plan structure* into a text representation suitable for the LLM prompt.\n    -   Determines if this is the first thought to adjust prompt instructions.\n    -   Increments and updates `shared[\"current_thought_number\"]`.\n-   **`exec`**:\n    -   Constructs a detailed prompt for the LLM, including:\n        -   The problem statement.\n        -   The formatted history of previous thoughts and plans.\n        -   Specific instructions for evaluating the previous thought, executing the next pending step, updating the plan structure (using the dictionary format), handling sub-steps, managing statuses/results, and indicating completion.\n        -   The required YAML output format (`current_thinking`, `planning`, `next_thought_needed`).\n    -   Calls the `call_llm` utility with the prompt.\n    -   Parses the LLM's YAML response.\n    -   Validates the presence and basic types of required keys (`current_thinking`, `planning`, `next_thought_needed`) using `assert`.\n    -   Adds the `thought_number` to the parsed data.\n-   **`post`**:\n    -   Appends the result dictionary from `exec` to the `shared[\"thoughts\"]` list.\n    -   Checks the `next_thought_needed` flag from the execution result.\n    -   If `False`:\n        -   Extracts the `current_thinking` content as the final `shared[\"solution\"]`.\n        -   Prints the final thought, plan, and solution.\n        -   Returns `\"end\"` to terminate the flow loop.\n    -   If `True`:\n        -   Prints the current thought number, thinking content, and formatted current plan status.\n        -   Returns `\"continue\"` to trigger the next iteration of the node."
  },
  {
    "path": "cookbook/pocketflow-thinking/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import ChainOfThoughtNode\n\ndef create_chain_of_thought_flow():\n    # Create a ChainOfThoughtNode\n    cot_node = ChainOfThoughtNode(max_retries=3, wait=10)\n    \n    # Connect the node to itself for the \"continue\" action\n    cot_node - \"continue\" >> cot_node\n    \n    # Create the flow\n    cot_flow = Flow(start=cot_node)\n    return cot_flow"
  },
  {
    "path": "cookbook/pocketflow-thinking/main.py",
    "content": "import sys\nfrom flow import create_chain_of_thought_flow\n\ndef main():\n    # Default question\n    default_question = \"You keep rolling a fair die until you roll three, four, five in that order consecutively on three rolls. What is the probability that you roll the die an odd number of times?\"\n    \n    # Get question from command line if provided with --\n    question = default_question\n    for arg in sys.argv[1:]:\n        if arg.startswith(\"--\"):\n            question = arg[2:]\n            break\n    \n    print(f\"🤔 Processing question: {question}\")   \n\n    # Create the flow\n    cot_flow = create_chain_of_thought_flow()\n\n    # Set up shared state\n    shared = {\n        \"problem\": question,\n        \"thoughts\": [],\n        \"current_thought_number\": 0,\n        \"total_thoughts_estimate\": 10,\n        \"solution\": None\n    }\n    \n    # Run the flow\n    cot_flow.run(shared)\n    \nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "cookbook/pocketflow-thinking/nodes.py",
    "content": "# cookbook/pocketflow-thinking/nodes.py\nfrom pocketflow import Node\nimport yaml\nfrom utils import call_llm\nimport textwrap\n\n# Helper function to format structured plan for printing\ndef format_plan(plan_items, indent_level=0):\n    indent = \"  \" * indent_level\n    output = []\n    if isinstance(plan_items, list):\n        for item in plan_items:\n            if isinstance(item, dict):\n                status = item.get('status', 'Unknown')\n                desc = item.get('description', 'No description')\n                result = item.get('result', '')\n                mark = item.get('mark', '') # For verification etc.\n\n                # Format the main step line\n                line = f\"{indent}- [{status}] {desc}\"\n                if result:\n                    line += f\": {result}\"\n                if mark:\n                    line += f\" ({mark})\"\n                output.append(line)\n\n                # Recursively format sub-steps if they exist\n                sub_steps = item.get('sub_steps')\n                if sub_steps:\n                    output.append(format_plan(sub_steps, indent_level + 1))\n            elif isinstance(item, str): # Basic fallback for string items\n                 output.append(f\"{indent}- {item}\")\n            else: # Fallback for unexpected types\n                 output.append(f\"{indent}- {str(item)}\")\n\n    elif isinstance(plan_items, str): # Handle case where plan is just an error string\n        output.append(f\"{indent}{plan_items}\")\n    else:\n        output.append(f\"{indent}# Invalid plan format: {type(plan_items)}\")\n\n    return \"\\n\".join(output)\n\n# Helper function to format structured plan for the prompt (simplified view)\ndef format_plan_for_prompt(plan_items, indent_level=0):\n    indent = \"  \" * indent_level\n    output = []\n    # Simplified formatting for prompt clarity\n    if isinstance(plan_items, list):\n        for item in plan_items:\n            if isinstance(item, dict):\n                status = item.get('status', 'Unknown')\n                desc = item.get('description', 'No description')\n                line = f\"{indent}- [{status}] {desc}\"\n                output.append(line)\n                sub_steps = item.get('sub_steps')\n                if sub_steps:\n                    # Indicate nesting without full recursive display in prompt\n                    output.append(format_plan_for_prompt(sub_steps, indent_level + 1))\n            else: # Fallback\n                 output.append(f\"{indent}- {str(item)}\")\n    else:\n        output.append(f\"{indent}{str(plan_items)}\")\n    return \"\\n\".join(output)\n\n\nclass ChainOfThoughtNode(Node):\n    def prep(self, shared):\n        problem = shared.get(\"problem\", \"\")\n        thoughts = shared.get(\"thoughts\", [])\n        current_thought_number = shared.get(\"current_thought_number\", 0)\n\n        shared[\"current_thought_number\"] = current_thought_number + 1\n\n        # Format previous thoughts and extract last plan structure\n        thoughts_text = \"\"\n        last_plan_structure = None # Will store the list of dicts\n        if thoughts:\n            thoughts_text_list = []\n            for i, t in enumerate(thoughts):\n                 thought_block = f\"Thought {t.get('thought_number', i+1)}:\\n\"\n                 thinking = textwrap.dedent(t.get('current_thinking', 'N/A')).strip()\n                 thought_block += f\"  Thinking:\\n{textwrap.indent(thinking, '    ')}\\n\"\n\n                 plan_list = t.get('planning', [])\n                 # Use the recursive helper for display formatting\n                 plan_str_formatted = format_plan(plan_list, indent_level=2)\n                 thought_block += f\"  Plan Status After Thought {t.get('thought_number', i+1)}:\\n{plan_str_formatted}\"\n\n                 if i == len(thoughts) - 1:\n                     last_plan_structure = plan_list # Keep the actual structure\n\n                 thoughts_text_list.append(thought_block)\n\n            thoughts_text = \"\\n--------------------\\n\".join(thoughts_text_list)\n        else:\n            thoughts_text = \"No previous thoughts yet.\"\n            # Suggest an initial plan structure using dictionaries\n            last_plan_structure = [\n                {'description': \"Understand the problem\", 'status': \"Pending\"},\n                {'description': \"Develop a high-level plan\", 'status': \"Pending\"},\n                {'description': \"Conclusion\", 'status': \"Pending\"}\n            ]\n\n        # Format the last plan structure for the prompt context using the specific helper\n        last_plan_text_for_prompt = format_plan_for_prompt(last_plan_structure) if last_plan_structure else \"# No previous plan available.\"\n\n        return {\n            \"problem\": problem,\n            \"thoughts_text\": thoughts_text,\n            \"last_plan_text\": last_plan_text_for_prompt,\n            \"last_plan_structure\": last_plan_structure, # Pass the raw structure too if needed for complex updates\n            \"current_thought_number\": current_thought_number + 1,\n            \"is_first_thought\": not thoughts\n        }\n\n    def exec(self, prep_res):\n        problem = prep_res[\"problem\"]\n        thoughts_text = prep_res[\"thoughts_text\"]\n        last_plan_text = prep_res[\"last_plan_text\"]\n        # last_plan_structure = prep_res[\"last_plan_structure\"] # Can use if needed\n        current_thought_number = prep_res[\"current_thought_number\"]\n        is_first_thought = prep_res[\"is_first_thought\"]\n\n        # --- Construct Prompt ---\n        # Instructions updated for dictionary structure\n        instruction_base = textwrap.dedent(f\"\"\"\n            Your task is to generate the next thought (Thought {current_thought_number}).\n\n            Instructions:\n            1.  **Evaluate Previous Thought:** If not the first thought, start `current_thinking` by evaluating Thought {current_thought_number - 1}. State: \"Evaluation of Thought {current_thought_number - 1}: [Correct/Minor Issues/Major Error - explain]\". Address errors first.\n            2.  **Execute Step:** Execute the first step in the plan with `status: Pending`.\n            3.  **Maintain Plan (Structure):** Generate an updated `planning` list. Each item should be a dictionary with keys: `description` (string), `status` (string: \"Pending\", \"Done\", \"Verification Needed\"), and optionally `result` (string, concise summary when Done) or `mark` (string, reason for Verification Needed). Sub-steps are represented by a `sub_steps` key containing a *list* of these dictionaries.\n            4.  **Update Current Step Status:** In the updated plan, change the `status` of the executed step to \"Done\" and add a `result` key with a concise summary. If verification is needed based on evaluation, change status to \"Verification Needed\" and add a `mark`.\n            5.  **Refine Plan (Sub-steps):** If a \"Pending\" step is complex, add a `sub_steps` key to its dictionary containing a list of new step dictionaries (status: \"Pending\") breaking it down. Keep the parent step's status \"Pending\" until all sub-steps are \"Done\".\n            6.  **Refine Plan (Errors):** Modify the plan logically based on evaluation findings (e.g., change status, add correction steps).\n            7.  **Final Step:** Ensure the plan progresses towards a final step dictionary like `{{'description': \"Conclusion\", 'status': \"Pending\"}}`.\n            8.  **Termination:** Set `next_thought_needed` to `false` ONLY when executing the step with `description: \"Conclusion\"`.\n        \"\"\")\n\n        # Context remains largely the same\n        if is_first_thought:\n            instruction_context = textwrap.dedent(\"\"\"\n                **This is the first thought:** Create an initial plan as a list of dictionaries (keys: description, status). Include sub-steps via the `sub_steps` key if needed. Then, execute the first step in `current_thinking` and provide the updated plan (marking step 1 `status: Done` with a `result`).\n            \"\"\")\n        else:\n            instruction_context = textwrap.dedent(f\"\"\"\n                **Previous Plan (Simplified View):**\n                {last_plan_text}\n\n                Start `current_thinking` by evaluating Thought {current_thought_number - 1}. Then, proceed with the first step where `status: Pending`. Update the plan structure (list of dictionaries) reflecting evaluation, execution, and refinements.\n            \"\"\")\n\n        # Output format example updated for dictionary structure\n        instruction_format = textwrap.dedent(\"\"\"\n            Format your response ONLY as a YAML structure enclosed in ```yaml ... ```:\n            ```yaml\n            current_thinking: |\n              # Evaluation of Thought N: [Assessment] ... (if applicable)\n              # Thinking for the current step...\n            planning:\n              # List of dictionaries (keys: description, status, Optional[result, mark, sub_steps])\n              - description: \"Step 1\"\n                status: \"Done\"\n                result: \"Concise result summary\"\n              - description: \"Step 2 Complex Task\" # Now broken down\n                status: \"Pending\" # Parent remains Pending\n                sub_steps:\n                  - description: \"Sub-task 2a\"\n                    status: \"Pending\"\n                  - description: \"Sub-task 2b\"\n                    status: \"Verification Needed\"\n                    mark: \"Result from Thought X seems off\"\n              - description: \"Step 3\"\n                status: \"Pending\"\n              - description: \"Conclusion\"\n                status: \"Pending\"\n            next_thought_needed: true # Set to false ONLY when executing the Conclusion step.\n            ```\n        \"\"\")\n\n        # Combine prompt parts\n        prompt = textwrap.dedent(f\"\"\"\n            You are a meticulous AI assistant solving a complex problem step-by-step using a structured plan. You critically evaluate previous steps, refine the plan with sub-steps if needed, and handle errors logically. Use the specified YAML dictionary structure for the plan.\n\n            Problem: {problem}\n\n            Previous thoughts:\n            {thoughts_text}\n            --------------------\n            {instruction_base}\n            {instruction_context}\n            {instruction_format}\n        \"\"\")\n        # --- End Prompt Construction ---\n\n        response = call_llm(prompt)\n\n        # Simple YAML extraction\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        thought_data = yaml.safe_load(yaml_str) # Can raise YAMLError\n\n        # --- Validation (using assert) ---\n        assert thought_data is not None, \"YAML parsing failed, result is None\"\n        assert \"current_thinking\" in thought_data, \"LLM response missing 'current_thinking'\"\n        assert \"next_thought_needed\" in thought_data, \"LLM response missing 'next_thought_needed'\"\n        assert \"planning\" in thought_data, \"LLM response missing 'planning'\"\n        assert isinstance(thought_data.get(\"planning\"), list), \"LLM response 'planning' is not a list\"\n        # Optional: Add deeper validation of list items being dicts if needed\n        # --- End Validation ---\n\n        # Add thought number\n        thought_data[\"thought_number\"] = current_thought_number\n        return thought_data\n\n\n    def post(self, shared, prep_res, exec_res):\n        # Add the new thought to the list\n        if \"thoughts\" not in shared:\n            shared[\"thoughts\"] = []\n        shared[\"thoughts\"].append(exec_res)\n\n        # Extract plan for printing using the updated recursive helper function\n        plan_list = exec_res.get(\"planning\", [\"Error: Planning data missing.\"])\n        plan_str_formatted = format_plan(plan_list, indent_level=1)\n\n        thought_num = exec_res.get('thought_number', 'N/A')\n        current_thinking = exec_res.get('current_thinking', 'Error: Missing thinking content.')\n        dedented_thinking = textwrap.dedent(current_thinking).strip()\n\n        # Determine if this is the conclusion step based on description\n        is_conclusion = False\n        if isinstance(plan_list, list):\n             # Check if the currently executed step (likely the last 'Done' or the current 'Pending' if evaluation failed) is Conclusion\n             # This logic is approximate - might need refinement based on how LLM handles status updates\n             for item in reversed(plan_list): # Check recent items first\n                 if isinstance(item, dict) and item.get('description') == \"Conclusion\":\n                     # If Conclusion is Done or it's Pending and we are ending, consider it conclusion\n                     if item.get('status') == \"Done\" or (item.get('status') == \"Pending\" and not exec_res.get(\"next_thought_needed\", True)):\n                         is_conclusion = True\n                         break\n                 # Simple check, might need nested search if Conclusion could be a sub-step\n\n        # Use is_conclusion flag OR the next_thought_needed flag for termination\n        if not exec_res.get(\"next_thought_needed\", True): # Primary termination signal\n            shared[\"solution\"] = dedented_thinking # Solution is the thinking content of the final step\n            print(f\"\\nThought {thought_num} (Conclusion):\")\n            print(f\"{textwrap.indent(dedented_thinking, '  ')}\")\n            print(\"\\nFinal Plan Status:\")\n            print(textwrap.indent(plan_str_formatted, '  '))\n            print(\"\\n=== FINAL SOLUTION ===\")\n            print(dedented_thinking)\n            print(\"======================\\n\")\n            return \"end\"\n\n        # Otherwise, continue the chain\n        print(f\"\\nThought {thought_num}:\")\n        print(f\"{textwrap.indent(dedented_thinking, '  ')}\")\n        print(\"\\nCurrent Plan Status:\")\n        print(textwrap.indent(plan_str_formatted, '  '))\n        print(\"-\" * 50)\n\n        return \"continue\""
  },
  {
    "path": "cookbook/pocketflow-thinking/requirements.txt",
    "content": "pocketflow>=0.0.1\nanthropic>=0.15.0   # For Claude API access "
  },
  {
    "path": "cookbook/pocketflow-thinking/utils.py",
    "content": "from anthropic import Anthropic\nimport os\n\ndef call_llm(prompt):\n    client = Anthropic(api_key=os.environ.get(\"ANTHROPIC_API_KEY\", \"your-api-key\"))\n    response = client.messages.create(\n        model=\"claude-3-7-sonnet-20250219\",\n        max_tokens=6000,\n        messages=[\n            {\"role\": \"user\", \"content\": prompt}\n        ]\n    )\n    return response.content[0].text\n\nif __name__ == \"__main__\":\n    print(\"## Testing call_llm\")\n    prompt = \"In a few words, what is the meaning of life?\"\n    print(f\"## Prompt: {prompt}\")\n    response = call_llm(prompt)\n    print(f\"## Response: {response}\")"
  },
  {
    "path": "cookbook/pocketflow-tool-crawler/README.md",
    "content": "# Web Crawler with Content Analysis\n\nA web crawler tool built with PocketFlow that crawls websites and analyzes content using LLM.\n\n## Features\n\n- Crawls websites while respecting domain boundaries\n- Extracts text content and links from pages\n- Analyzes content using GPT-4 to generate:\n  - Page summaries\n  - Main topics/keywords\n  - Content type classification\n- Processes pages in batches for efficiency\n- Generates a comprehensive analysis report\n\n## Installation\n\n1. Clone the repository\n2. Install dependencies:\n   ```bash\n   pip install -r requirements.txt\n   ```\n3. Set your OpenAI API key:\n   ```bash\n   export OPENAI_API_KEY='your-api-key'\n   ```\n\n## Usage\n\nRun the crawler:\n```bash\npython main.py\n```\n\nYou will be prompted to:\n1. Enter the website URL to crawl\n2. Specify maximum number of pages to crawl (default: 10)\n\nThe tool will then:\n1. Crawl the specified website\n2. Extract and analyze content using GPT-4\n3. Generate a report with findings\n\n## Project Structure\n\n```\npocketflow-tool-crawler/\n├── tools/\n│   ├── crawler.py     # Web crawling functionality\n│   └── parser.py      # Content analysis using LLM\n├── utils/\n│   └── call_llm.py    # LLM API wrapper\n├── nodes.py           # PocketFlow nodes\n├── flow.py           # Flow configuration\n├── main.py           # Main script\n└── requirements.txt   # Dependencies\n```\n\n## Limitations\n\n- Only crawls within the same domain\n- Text content only (no images/media)\n- Rate limited by OpenAI API\n- Basic error handling\n\n## Dependencies\n\n- pocketflow: Flow-based processing\n- requests: HTTP requests\n- beautifulsoup4: HTML parsing\n- openai: GPT-4 API access\n"
  },
  {
    "path": "cookbook/pocketflow-tool-crawler/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import CrawlWebsiteNode, AnalyzeContentBatchNode, GenerateReportNode\n\ndef create_flow() -> Flow:\n    \"\"\"Create and configure the crawling flow\n    \n    Returns:\n        Flow: Configured flow ready to run\n    \"\"\"\n    # Create nodes\n    crawl = CrawlWebsiteNode()\n    analyze = AnalyzeContentBatchNode()\n    report = GenerateReportNode()\n    \n    # Connect nodes\n    crawl >> analyze >> report\n    \n    # Create flow starting with crawl\n    return Flow(start=crawl)\n"
  },
  {
    "path": "cookbook/pocketflow-tool-crawler/main.py",
    "content": "import os\nfrom flow import create_flow\n\ndef main():\n    \"\"\"Run the web crawler flow\"\"\"\n    \n    # Get website URL from user\n    url = input(\"Enter website URL to crawl (e.g., https://example.com): \")\n    if not url:\n        print(\"Error: URL is required\")\n        return\n        \n    # Initialize shared data\n    shared = {\n        \"base_url\": url,\n        \"max_pages\": 1\n    }\n    \n    # Create and run flow\n    flow = create_flow()\n    flow.run(shared)\n    \n    # Results are in shared[\"report\"]\n    \nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "cookbook/pocketflow-tool-crawler/nodes.py",
    "content": "from pocketflow import Node, BatchNode\nfrom tools.crawler import WebCrawler\nfrom tools.parser import analyze_site\nfrom typing import List, Dict\n\nclass CrawlWebsiteNode(Node):\n    \"\"\"Node to crawl a website and extract content\"\"\"\n    \n    def prep(self, shared):\n        return shared.get(\"base_url\"), shared.get(\"max_pages\", 10)\n        \n    def exec(self, inputs):\n        base_url, max_pages = inputs\n        if not base_url:\n            return []\n            \n        crawler = WebCrawler(base_url, max_pages)\n        return crawler.crawl()\n        \n    def post(self, shared, prep_res, exec_res):\n        shared[\"crawl_results\"] = exec_res\n        return \"default\"\n\nclass AnalyzeContentBatchNode(BatchNode):\n    \"\"\"Node to analyze crawled content in batches\"\"\"\n    \n    def prep(self, shared):\n        results = shared.get(\"crawl_results\", [])\n        # Process in batches of 5 pages\n        batch_size = 5\n        return [results[i:i+batch_size] for i in range(0, len(results), batch_size)]\n        \n    def exec(self, batch):\n        return analyze_site(batch)\n        \n    def post(self, shared, prep_res, exec_res_list):\n        # Flatten results from all batches\n        all_results = []\n        for batch_results in exec_res_list:\n            all_results.extend(batch_results)\n            \n        shared[\"analyzed_results\"] = all_results\n        return \"default\"\n\nclass GenerateReportNode(Node):\n    \"\"\"Node to generate a summary report of the analysis\"\"\"\n    \n    def prep(self, shared):\n        return shared.get(\"analyzed_results\", [])\n        \n    def exec(self, results):\n        if not results:\n            return \"No results to report\"\n            \n        report = []\n        report.append(f\"Analysis Report\\n\")\n        report.append(f\"Total pages analyzed: {len(results)}\\n\")\n        \n        for page in results:\n            report.append(f\"\\nPage: {page['url']}\")\n            report.append(f\"Title: {page['title']}\")\n            \n            analysis = page.get(\"analysis\", {})\n            report.append(f\"Summary: {analysis.get('summary', 'N/A')}\")\n            report.append(f\"Topics: {', '.join(analysis.get('topics', []))}\")\n            report.append(f\"Content Type: {analysis.get('content_type', 'unknown')}\")\n            report.append(\"-\" * 80)\n            \n        return \"\\n\".join(report)\n        \n    def post(self, shared, prep_res, exec_res):\n        shared[\"report\"] = exec_res\n        print(\"\\nReport generated:\")\n        print(exec_res)\n        return \"default\"\n"
  },
  {
    "path": "cookbook/pocketflow-tool-crawler/requirements.txt",
    "content": "pocketflow>=0.1.0\nrequests>=2.31.0\nbeautifulsoup4>=4.12.0\nopenai>=1.0.0  # for content analysis\n"
  },
  {
    "path": "cookbook/pocketflow-tool-crawler/tools/crawler.py",
    "content": "import requests\nfrom bs4 import BeautifulSoup\nfrom urllib.parse import urljoin, urlparse\nfrom typing import Dict, List, Set\n\nclass WebCrawler:\n    \"\"\"Simple web crawler that extracts content and follows links\"\"\"\n    \n    def __init__(self, base_url: str, max_pages: int = 10):\n        self.base_url = base_url\n        self.max_pages = max_pages\n        self.visited: Set[str] = set()\n        \n    def is_valid_url(self, url: str) -> bool:\n        \"\"\"Check if URL belongs to the same domain\"\"\"\n        base_domain = urlparse(self.base_url).netloc\n        url_domain = urlparse(url).netloc\n        return base_domain == url_domain\n        \n    def extract_page_content(self, url: str) -> Dict:\n        \"\"\"Extract content from a single page\"\"\"\n        try:\n            response = requests.get(url)\n            response.raise_for_status()\n            \n            soup = BeautifulSoup(response.text, \"html.parser\")\n            \n            # Extract main content\n            content = {\n                \"url\": url,\n                \"title\": soup.title.string if soup.title else \"\",\n                \"text\": soup.get_text(separator=\"\\n\", strip=True),\n                \"links\": []\n            }\n            \n            # Extract links\n            for link in soup.find_all(\"a\"):\n                href = link.get(\"href\")\n                if href:\n                    absolute_url = urljoin(url, href)\n                    if self.is_valid_url(absolute_url):\n                        content[\"links\"].append(absolute_url)\n            \n            return content\n            \n        except Exception as e:\n            print(f\"Error crawling {url}: {str(e)}\")\n            return None\n    \n    def crawl(self) -> List[Dict]:\n        \"\"\"Crawl website starting from base_url\"\"\"\n        to_visit = [self.base_url]\n        results = []\n        \n        while to_visit and len(self.visited) < self.max_pages:\n            url = to_visit.pop(0)\n            \n            if url in self.visited:\n                continue\n                \n            print(f\"Crawling: {url}\")\n            content = self.extract_page_content(url)\n            \n            if content:\n                self.visited.add(url)\n                results.append(content)\n                \n                # Add new URLs to visit\n                new_urls = [url for url in content[\"links\"] \n                          if url not in self.visited \n                          and url not in to_visit]\n                to_visit.extend(new_urls)\n        \n        return results\n"
  },
  {
    "path": "cookbook/pocketflow-tool-crawler/tools/parser.py",
    "content": "from typing import Dict, List\nfrom utils.call_llm import call_llm\n\ndef analyze_content(content: Dict) -> Dict:\n    \"\"\"Analyze webpage content using LLM\n    \n    Args:\n        content (Dict): Webpage content with url, title and text\n        \n    Returns:\n        Dict: Analysis results including summary and topics\n    \"\"\"\n    prompt = f\"\"\"\nAnalyze this webpage content:\n\nTitle: {content['title']}\nURL: {content['url']}\nContent: {content['text'][:2000]}  # Limit content length\n\nPlease provide:\n1. A brief summary (2-3 sentences)\n2. Main topics/keywords (up to 5)\n3. Content type (article, product page, etc)\n\nOutput in YAML format:\n```yaml\nsummary: >\n    brief summary here\ntopics:\n    - topic 1\n    - topic 2\ncontent_type: type here\n```\n\"\"\"\n    \n    try:\n        response = call_llm(prompt)\n        # Extract YAML between code fences\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        \n        import yaml\n        analysis = yaml.safe_load(yaml_str)\n        \n        # Validate required fields\n        assert \"summary\" in analysis\n        assert \"topics\" in analysis\n        assert \"content_type\" in analysis\n        assert isinstance(analysis[\"topics\"], list)\n        \n        return analysis\n        \n    except Exception as e:\n        print(f\"Error analyzing content: {str(e)}\")\n        return {\n            \"summary\": \"Error analyzing content\",\n            \"topics\": [],\n            \"content_type\": \"unknown\"\n        }\n\ndef analyze_site(crawl_results: List[Dict]) -> List[Dict]:\n    \"\"\"Analyze all crawled pages\n    \n    Args:\n        crawl_results (List[Dict]): List of crawled page contents\n        \n    Returns:\n        List[Dict]: Original content with added analysis\n    \"\"\"\n    analyzed_results = []\n    \n    for content in crawl_results:\n        if content and content.get(\"text\"):\n            analysis = analyze_content(content)\n            content[\"analysis\"] = analysis\n            analyzed_results.append(content)\n            \n    return analyzed_results\n"
  },
  {
    "path": "cookbook/pocketflow-tool-crawler/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-tool-crawler/utils/call_llm.py",
    "content": "from openai import OpenAI\nimport os\n\n# Initialize OpenAI client\nclient = OpenAI(api_key=os.getenv(\"OPENAI_API_KEY\"))\n\ndef call_llm(prompt: str) -> str:\n    \"\"\"Call OpenAI API to analyze text\n    \n    Args:\n        prompt (str): Input prompt for the model\n        \n    Returns:\n        str: Model response\n    \"\"\"\n    try:\n        response = client.chat.completions.create(\n            model=\"gpt-4\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return response.choices[0].message.content\n        \n    except Exception as e:\n        print(f\"Error calling LLM API: {str(e)}\")\n        return \"\"\n\nif __name__ == \"__main__\":\n    # Test LLM call\n    response = call_llm(\"What is web crawling?\")\n    print(\"Response:\", response)\n"
  },
  {
    "path": "cookbook/pocketflow-tool-database/README.md",
    "content": "# SQLite Database with PocketFlow\n\nThis example demonstrates how to properly integrate SQLite database operations with PocketFlow, focusing on:\n\n1. Clean code organization with separation of concerns:\n   - Tools layer for database operations (`tools/database.py`)\n   - Node implementation for PocketFlow integration (`nodes.py`)\n   - Flow configuration (`flow.py`)\n   - Safe SQL query execution with parameter binding\n\n2. Best practices for database operations:\n   - Connection management with proper closing\n   - SQL injection prevention using parameterized queries\n   - Error handling and resource cleanup\n   - Simple schema management\n\n3. Example task management system:\n   - Database initialization\n   - Task creation\n   - Task listing\n   - Status tracking\n\n## Project Structure\n\n```\npocketflow-tool-database/\n├── tools/\n│   └── database.py    # SQLite database operations\n├── nodes.py          # PocketFlow node implementation\n├── flow.py          # Flow configuration\n└── main.py          # Example usage\n```\n\n## Setup\n\n1. Create a virtual environment:\n```bash\npython -m venv venv\nsource venv/bin/activate  # On Windows: venv\\Scripts\\activate\n```\n\n2. Install dependencies:\n```bash\npip install -r requirements.txt\n```\n\n## Usage\n\nRun the example:\n```bash\npython main.py\n```\n\nThis will:\n1. Initialize a SQLite database with a tasks table\n2. Create an example task\n3. List all tasks in the database\n4. Display the results\n\n## Key Concepts Demonstrated\n\n1. **Database Operations**\n   - Safe connection handling\n   - Query parameterization\n   - Schema management\n\n2. **Code Organization**\n   - Clear separation between database operations and PocketFlow components\n   - Modular project structure\n   - Type hints and documentation\n\n3. **PocketFlow Integration**\n   - Node implementation with prep->exec->post lifecycle\n   - Flow configuration\n   - Shared store usage for data passing\n\n## Example Output\n\n```\nDatabase Status: Database initialized\nTask Status: Task created successfully\n\nAll Tasks:\n- ID: 1\n  Title: Example Task\n  Description: This is an example task created using PocketFlow\n  Status: pending\n  Created: 2024-03-02 12:34:56\n```\n"
  },
  {
    "path": "cookbook/pocketflow-tool-database/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import InitDatabaseNode, CreateTaskNode, ListTasksNode\n\ndef create_database_flow():\n    \"\"\"Create a flow for database operations\"\"\"\n    \n    # Create nodes\n    init_db = InitDatabaseNode()\n    create_task = CreateTaskNode()\n    list_tasks = ListTasksNode()\n    \n    # Connect nodes\n    init_db >> create_task >> list_tasks\n    \n    # Create and return flow\n    return Flow(start=init_db)\n"
  },
  {
    "path": "cookbook/pocketflow-tool-database/main.py",
    "content": "from flow import create_database_flow\n\ndef main():\n    # Create the flow\n    flow = create_database_flow()\n    \n    # Prepare example task data\n    shared = {\n        \"task_title\": \"Example Task\",\n        \"task_description\": \"This is an example task created using PocketFlow\"\n    }\n    \n    # Run the flow\n    flow.run(shared)\n    \n    # Print results\n    print(\"Database Status:\", shared.get(\"db_status\"))\n    print(\"Task Status:\", shared.get(\"task_status\"))\n    print(\"\\nAll Tasks:\")\n    for task in shared.get(\"tasks\", []):\n        print(f\"- ID: {task[0]}\")\n        print(f\"  Title: {task[1]}\")\n        print(f\"  Description: {task[2]}\")\n        print(f\"  Status: {task[3]}\")\n        print(f\"  Created: {task[4]}\")\n        print()\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "cookbook/pocketflow-tool-database/nodes.py",
    "content": "from pocketflow import Node\nfrom tools.database import execute_sql, init_db\n\nclass InitDatabaseNode(Node):\n    \"\"\"Node for initializing the database\"\"\"\n    \n    def exec(self, _):\n        init_db()\n        return \"Database initialized\"\n        \n    def post(self, shared, prep_res, exec_res):\n        shared[\"db_status\"] = exec_res\n        return \"default\"\n\nclass CreateTaskNode(Node):\n    \"\"\"Node for creating a new task\"\"\"\n    \n    def prep(self, shared):\n        return (\n            shared.get(\"task_title\", \"\"),\n            shared.get(\"task_description\", \"\")\n        )\n        \n    def exec(self, inputs):\n        title, description = inputs\n        query = \"INSERT INTO tasks (title, description) VALUES (?, ?)\"\n        execute_sql(query, (title, description))\n        return \"Task created successfully\"\n        \n    def post(self, shared, prep_res, exec_res):\n        shared[\"task_status\"] = exec_res\n        return \"default\"\n\nclass ListTasksNode(Node):\n    \"\"\"Node for listing all tasks\"\"\"\n    \n    def exec(self, _):\n        query = \"SELECT * FROM tasks\"\n        return execute_sql(query)\n        \n    def post(self, shared, prep_res, exec_res):\n        shared[\"tasks\"] = exec_res\n        return \"default\"\n"
  },
  {
    "path": "cookbook/pocketflow-tool-database/requirements.txt",
    "content": "pocketflow>=0.1.0\npython-dotenv>=0.19.0\n"
  },
  {
    "path": "cookbook/pocketflow-tool-database/tools/database.py",
    "content": "import sqlite3\nfrom typing import List, Tuple, Any\n\ndef execute_sql(query: str, params: Tuple = None) -> List[Tuple[Any, ...]]:\n    \"\"\"Execute a SQL query and return results\n    \n    Args:\n        query (str): SQL query to execute\n        params (tuple, optional): Query parameters to prevent SQL injection\n        \n    Returns:\n        list: Query results as a list of tuples\n    \"\"\"\n    conn = sqlite3.connect(\"example.db\")\n    try:\n        cursor = conn.cursor()\n        if params:\n            cursor.execute(query, params)\n        else:\n            cursor.execute(query)\n        result = cursor.fetchall()\n        conn.commit()\n        return result\n    finally:\n        conn.close()\n\ndef init_db():\n    \"\"\"Initialize database with example table\"\"\"\n    create_table_sql = \"\"\"\n    CREATE TABLE IF NOT EXISTS tasks (\n        id INTEGER PRIMARY KEY AUTOINCREMENT,\n        title TEXT NOT NULL,\n        description TEXT,\n        status TEXT DEFAULT 'pending',\n        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n    )\n    \"\"\"\n    execute_sql(create_table_sql)\n"
  },
  {
    "path": "cookbook/pocketflow-tool-database/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-tool-embeddings/README.md",
    "content": "# OpenAI Embeddings with PocketFlow\n\nThis example demonstrates how to properly integrate OpenAI's text embeddings API with PocketFlow, focusing on:\n\n1. Clean code organization with separation of concerns:\n   - Tools layer for API interactions (`tools/embeddings.py`)\n   - Node implementation for PocketFlow integration (`nodes.py`)\n   - Flow configuration (`flow.py`)\n   - Centralized environment configuration (`utils/call_llm.py`)\n\n2. Best practices for API key management:\n   - Using environment variables\n   - Supporting both `.env` files and system environment variables\n   - Secure configuration handling\n\n3. Proper project structure:\n   - Modular code organization\n   - Clear separation between tools and PocketFlow components\n   - Reusable OpenAI client configuration\n\n## Project Structure\n\n```\npocketflow-tool-embeddings/\n├── tools/\n│   └── embeddings.py     # OpenAI embeddings API wrapper\n├── utils/\n│   └── call_llm.py      # Centralized OpenAI client configuration\n├── nodes.py             # PocketFlow node implementation\n├── flow.py             # Flow configuration\n└── main.py             # Example usage\n```\n\n## Setup\n\n1. Create a virtual environment:\n```bash\npython -m venv venv\nsource venv/bin/activate  # On Windows: venv\\Scripts\\activate\n```\n\n2. Install dependencies:\n```bash\npip install -r requirements.txt\n```\n\n3. Set up your OpenAI API key in one of two ways:\n   \n   a. Using a `.env` file:\n   ```bash\n   OPENAI_API_KEY=your_api_key_here\n   ```\n   \n   b. Or as a system environment variable:\n   ```bash\n   export OPENAI_API_KEY=your_api_key_here\n   ```\n\n## Usage\n\nRun the example:\n```bash\npython main.py\n```\n\nThis will:\n1. Load the OpenAI API key from environment\n2. Create a PocketFlow node to handle embedding generation\n3. Process a sample text and generate its embedding\n4. Display the embedding dimension and first few values\n\n## Key Concepts Demonstrated\n\n1. **Environment Configuration**\n   - Secure API key handling\n   - Flexible configuration options\n\n2. **Code Organization**\n   - Clear separation between tools and PocketFlow components\n   - Reusable OpenAI client configuration\n   - Modular project structure\n\n3. **PocketFlow Integration**\n   - Node implementation with prep->exec->post lifecycle\n   - Flow configuration\n   - Shared store usage for data passing "
  },
  {
    "path": "cookbook/pocketflow-tool-embeddings/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import EmbeddingNode\n\ndef create_embedding_flow():\n    \"\"\"Create a flow for text embedding\"\"\"\n    # Create embedding node\n    embedding = EmbeddingNode()\n    \n    # Create and return flow\n    return Flow(start=embedding) "
  },
  {
    "path": "cookbook/pocketflow-tool-embeddings/main.py",
    "content": "from flow import create_embedding_flow\n\ndef main():\n    # Create the flow\n    flow = create_embedding_flow()\n    \n    # Example text\n    text = \"What's the meaning of life?\"\n    \n    # Prepare shared data\n    shared = {\"text\": text}\n    \n    # Run the flow\n    flow.run(shared)\n    \n    # Print results\n    print(\"Text:\", text)\n    print(\"Embedding dimension:\", len(shared[\"embedding\"]))\n    print(\"First 5 values:\", shared[\"embedding\"][:5])\n\nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "cookbook/pocketflow-tool-embeddings/nodes.py",
    "content": "from pocketflow import Node\nfrom tools.embeddings import get_embedding\n\nclass EmbeddingNode(Node):\n    \"\"\"Node for getting embeddings from OpenAI API\"\"\"\n    \n    def prep(self, shared):\n        # Get text from shared store\n        return shared.get(\"text\", \"\")\n        \n    def exec(self, text):\n        # Get embedding using tool function\n        return get_embedding(text)\n        \n    def post(self, shared, prep_res, exec_res):\n        # Store embedding in shared store\n        shared[\"embedding\"] = exec_res\n        return \"default\" "
  },
  {
    "path": "cookbook/pocketflow-tool-embeddings/requirements.txt",
    "content": "openai>=1.0.0\nnumpy>=1.24.0\nfaiss-cpu>=1.7.0\npython-dotenv>=1.0.0\npocketflow>=0.1.0 "
  },
  {
    "path": "cookbook/pocketflow-tool-embeddings/tools/embeddings.py",
    "content": "from utils.call_llm import client\n\ndef get_embedding(text):\n    response = client.embeddings.create(\n        model=\"text-embedding-ada-002\",\n        input=text\n    )\n    return response.data[0].embedding"
  },
  {
    "path": "cookbook/pocketflow-tool-embeddings/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-tool-embeddings/utils/call_llm.py",
    "content": "import os\nfrom openai import OpenAI\n\n# No need for dotenv if using system environment variables\nclient = OpenAI(api_key=os.getenv(\"OPENAI_API_KEY\"))\n\ndef call_llm(prompt):    \n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n    \nif __name__ == \"__main__\":\n    prompt = \"What is the meaning of life?\"\n    print(call_llm(prompt)) "
  },
  {
    "path": "cookbook/pocketflow-tool-pdf-vision/README.md",
    "content": "# PocketFlow Tool: PDF Vision\n\nA PocketFlow example project demonstrating PDF processing with OpenAI's Vision API for OCR and text extraction.\n\n## Features\n\n- Convert PDF pages to images while maintaining quality and size limits\n- Extract text from scanned documents using GPT-4 Vision API\n- Support for custom extraction prompts\n- Maintain page order and formatting in extracted text\n- Batch processing of multiple PDFs from a directory\n\n## Installation\n\n1. Clone the repository\n2. Install dependencies:\n   ```bash\n   pip install -r requirements.txt\n   ```\n3. Set your OpenAI API key as an environment variable:\n   ```bash\n   export OPENAI_API_KEY=your_api_key_here\n   ```\n\n## Usage\n\n1. Place your PDF files in the `pdfs` directory\n2. Run the example:\n   ```bash\n   python main.py\n   ```\n   The script will process all PDF files in the `pdfs` directory and output the extracted text for each one.\n\n## Project Structure\n\n```\npocketflow-tool-pdf-vision/\n├── pdfs/           # Directory for PDF files to process\n├── tools/\n│   ├── pdf.py     # PDF to image conversion\n│   └── vision.py  # Vision API integration\n├── utils/\n│   └── call_llm.py # OpenAI client config\n├── nodes.py       # PocketFlow nodes\n├── flow.py        # Flow configuration\n└── main.py        # Example usage\n```\n\n## Flow Description\n\n1. **LoadPDFNode**: Loads PDF and converts pages to images\n2. **ExtractTextNode**: Processes images with Vision API\n3. **CombineResultsNode**: Combines extracted text from all pages\n\n## Customization\n\nYou can customize the extraction by modifying the prompt in `shared`:\n\n```python\nshared = {\n    \"pdf_path\": \"your_file.pdf\",\n    \"extraction_prompt\": \"Your custom prompt here\"\n}\n```\n\n## Limitations\n\n- Maximum PDF page size: 2000px (configurable in `tools/pdf.py`)\n- Vision API token limit: 1000 tokens per response\n- Image size limit: 20MB per image for Vision API\n\n## License\n\nMIT\n"
  },
  {
    "path": "cookbook/pocketflow-tool-pdf-vision/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import ProcessPDFBatchNode\n\ndef create_vision_flow():\n    \"\"\"Create a flow for batch PDF processing with Vision API\"\"\"\n    return Flow(start=ProcessPDFBatchNode())\n"
  },
  {
    "path": "cookbook/pocketflow-tool-pdf-vision/main.py",
    "content": "from flow import create_vision_flow\n\ndef main():\n    # Create and run flow\n    flow = create_vision_flow()\n    shared = {}\n    flow.run(shared)\n    \n    # Print results\n    if \"results\" in shared:\n        for result in shared[\"results\"]:\n            print(f\"\\nFile: {result['filename']}\")\n            print(\"-\" * 50)\n            print(result[\"text\"])\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "cookbook/pocketflow-tool-pdf-vision/nodes.py",
    "content": "from pocketflow import Node, BatchNode\nfrom tools.pdf import pdf_to_images\nfrom tools.vision import extract_text_from_image\nfrom typing import List, Dict, Any\nfrom pathlib import Path\nimport os\n\nclass ProcessPDFBatchNode(BatchNode):\n    \"\"\"Node for processing multiple PDFs from a directory\"\"\"\n    \n    def prep(self, shared):\n        # Get PDF directory path\n        root_dir = Path(__file__).parent\n        pdf_dir = root_dir / \"pdfs\"\n        \n        # List all PDFs\n        pdf_files = []\n        for file in os.listdir(pdf_dir):\n            if file.lower().endswith('.pdf'):\n                pdf_files.append({\n                    \"pdf_path\": str(pdf_dir / file),\n                    \"extraction_prompt\": shared.get(\"extraction_prompt\", \n                        \"Extract all text from this document, preserving formatting and layout.\")\n                })\n        \n        if not pdf_files:\n            print(\"No PDF files found in 'pdfs' directory!\")\n            return []\n            \n        print(f\"Found {len(pdf_files)} PDF files\")\n        return pdf_files\n    \n    def exec(self, item):\n        # Create flow for single PDF\n        flow = create_single_pdf_flow()\n        \n        # Process PDF\n        print(f\"\\nProcessing: {os.path.basename(item['pdf_path'])}\")\n        print(\"-\" * 50)\n        \n        # Run flow\n        shared = item.copy()\n        flow.run(shared)\n        \n        return {\n            \"filename\": os.path.basename(item[\"pdf_path\"]),\n            \"text\": shared.get(\"final_text\", \"No text extracted\")\n        }\n    \n    def post(self, shared, prep_res, exec_res_list):\n        shared[\"results\"] = exec_res_list\n        return \"default\"\n\nclass LoadPDFNode(Node):\n    \"\"\"Node for loading and converting a single PDF to images\"\"\"\n    \n    def prep(self, shared):\n        return shared.get(\"pdf_path\", \"\")\n        \n    def exec(self, pdf_path):\n        return pdf_to_images(pdf_path)\n        \n    def post(self, shared, prep_res, exec_res):\n        shared[\"page_images\"] = exec_res\n        return \"default\"\n\nclass ExtractTextNode(Node):\n    \"\"\"Node for extracting text from images using Vision API\"\"\"\n    \n    def prep(self, shared):\n        return (\n            shared.get(\"page_images\", []),\n            shared.get(\"extraction_prompt\", None)\n        )\n        \n    def exec(self, inputs):\n        images, prompt = inputs\n        results = []\n        \n        for img, page_num in images:\n            text = extract_text_from_image(img, prompt)\n            results.append({\n                \"page\": page_num,\n                \"text\": text\n            })\n            \n        return results\n        \n    def post(self, shared, prep_res, exec_res):\n        shared[\"extracted_text\"] = exec_res\n        return \"default\"\n\nclass CombineResultsNode(Node):\n    \"\"\"Node for combining and formatting extracted text\"\"\"\n    \n    def prep(self, shared):\n        return shared.get(\"extracted_text\", [])\n        \n    def exec(self, results):\n        # Sort by page number\n        sorted_results = sorted(results, key=lambda x: x[\"page\"])\n        \n        # Combine text with page numbers\n        combined = []\n        for result in sorted_results:\n            combined.append(f\"=== Page {result['page']} ===\\n{result['text']}\\n\")\n            \n        return \"\\n\".join(combined)\n        \n    def post(self, shared, prep_res, exec_res):\n        shared[\"final_text\"] = exec_res\n        return \"default\"\n\ndef create_single_pdf_flow():\n    \"\"\"Create a flow for processing a single PDF\"\"\"\n    from pocketflow import Flow\n    \n    # Create nodes\n    load_pdf = LoadPDFNode()\n    extract_text = ExtractTextNode()\n    combine_results = CombineResultsNode()\n    \n    # Connect nodes\n    load_pdf >> extract_text >> combine_results\n    \n    # Create and return flow\n    return Flow(start=load_pdf)\n"
  },
  {
    "path": "cookbook/pocketflow-tool-pdf-vision/requirements.txt",
    "content": "pocketflow>=0.1.0\nopenai>=1.0.0\nPyMuPDF>=1.22.0  # for PDF processing\nPillow>=10.0.0   # for image processing\n"
  },
  {
    "path": "cookbook/pocketflow-tool-pdf-vision/tools/pdf.py",
    "content": "import fitz  # PyMuPDF\nfrom PIL import Image\nimport io\nimport base64\nfrom typing import List, Tuple\n\ndef pdf_to_images(pdf_path: str, max_size: int = 2000) -> List[Tuple[Image.Image, int]]:\n    \"\"\"Convert PDF pages to PIL Images with size limit\n    \n    Args:\n        pdf_path (str): Path to PDF file\n        max_size (int): Maximum dimension (width/height) for images\n        \n    Returns:\n        list: List of tuples (PIL Image, page number)\n    \"\"\"\n    doc = fitz.open(pdf_path)\n    images = []\n    \n    try:\n        for page_num in range(len(doc)):\n            page = doc[page_num]\n            pix = page.get_pixmap()\n            \n            # Convert to PIL Image\n            img = Image.frombytes(\"RGB\", [pix.width, pix.height], pix.samples)\n            \n            # Resize if needed while maintaining aspect ratio\n            if max(img.size) > max_size:\n                ratio = max_size / max(img.size)\n                new_size = tuple(int(dim * ratio) for dim in img.size)\n                img = img.resize(new_size, Image.Resampling.LANCZOS)\n            \n            images.append((img, page_num + 1))\n            \n    finally:\n        doc.close()\n        \n    return images\n\ndef image_to_base64(image: Image.Image) -> str:\n    \"\"\"Convert PIL Image to base64 string\n    \n    Args:\n        image (PIL.Image): Image to convert\n        \n    Returns:\n        str: Base64 encoded image string\n    \"\"\"\n    buffer = io.BytesIO()\n    image.save(buffer, format=\"PNG\")\n    return base64.b64encode(buffer.getvalue()).decode('utf-8')\n"
  },
  {
    "path": "cookbook/pocketflow-tool-pdf-vision/tools/vision.py",
    "content": "from PIL import Image\nfrom utils.call_llm import client\nfrom tools.pdf import image_to_base64\n\ndef extract_text_from_image(image: Image.Image, prompt: str = None) -> str:\n    \"\"\"Extract text from image using OpenAI Vision API\n    \n    Args:\n        image (PIL.Image): Image to process\n        prompt (str, optional): Custom prompt for extraction. Defaults to general OCR.\n        \n    Returns:\n        str: Extracted text from image\n    \"\"\"\n    # Convert image to base64\n    img_base64 = image_to_base64(image)\n    \n    # Default prompt for general OCR\n    if prompt is None:\n        prompt = \"Please extract all text from this image.\"\n    \n    # Call Vision API\n    response = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\n            \"role\": \"user\",\n            \"content\": [\n                {\"type\": \"text\", \"text\": prompt},\n                {\"type\": \"image_url\", \"image_url\": {\"url\": f\"data:image/png;base64,{img_base64}\"}}\n            ]\n        }]\n    )\n    \n    return response.choices[0].message.content\n\nif __name__ == \"__main__\":\n    # Test vision processing\n    test_image = Image.open(\"example.png\")\n    result = extract_text_from_image(test_image)\n    print(\"Extracted text:\", result)\n"
  },
  {
    "path": "cookbook/pocketflow-tool-pdf-vision/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-tool-pdf-vision/utils/call_llm.py",
    "content": "import os\nfrom openai import OpenAI\nfrom pathlib import Path\n\n# Get the project root directory (parent of utils directory)\nROOT_DIR = Path(__file__).parent.parent\n\n# Initialize OpenAI client with API key from environment\nclient = OpenAI(api_key=os.getenv(\"OPENAI_API_KEY\"))\n"
  },
  {
    "path": "cookbook/pocketflow-tool-search/README.md",
    "content": "# Web Search with Analysis\n\nA web search tool built with PocketFlow that performs searches using SerpAPI and analyzes results using LLM.\n\n## Features\n\n- Web search using Google via SerpAPI\n- Extracts titles, snippets, and links\n- Analyzes search results using GPT-4 to provide:\n  - Result summaries\n  - Key points/facts\n  - Suggested follow-up queries\n- Clean command-line interface\n\n## Installation\n\n1. Clone the repository\n2. Install dependencies:\n   ```bash\n   pip install -r requirements.txt\n   ```\n3. Set required API keys:\n   ```bash\n   export SERPAPI_API_KEY='your-serpapi-key'\n   export OPENAI_API_KEY='your-openai-key'\n   ```\n\n## Usage\n\nRun the search tool:\n```bash\npython main.py\n```\n\nYou will be prompted to:\n1. Enter your search query\n2. Specify number of results to fetch (default: 5)\n\nThe tool will then:\n1. Perform the search using SerpAPI\n2. Analyze results using GPT-4\n3. Present a summary with key points and follow-up queries\n\n## Project Structure\n\n```\npocketflow-tool-search/\n├── tools/\n│   ├── search.py      # SerpAPI search functionality\n│   └── parser.py      # Result analysis using LLM\n├── utils/\n│   └── call_llm.py    # LLM API wrapper\n├── nodes.py           # PocketFlow nodes\n├── flow.py           # Flow configuration\n├── main.py           # Main script\n└── requirements.txt   # Dependencies\n```\n\n## Limitations\n\n- Requires SerpAPI subscription\n- Rate limited by both APIs\n- Basic error handling\n- Text results only\n\n## Dependencies\n\n- pocketflow: Flow-based processing\n- google-search-results: SerpAPI client\n- openai: GPT-4 API access\n- pyyaml: YAML processing\n"
  },
  {
    "path": "cookbook/pocketflow-tool-search/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import SearchNode, AnalyzeResultsNode\n\ndef create_flow() -> Flow:\n    \"\"\"Create and configure the search flow\n    \n    Returns:\n        Flow: Configured flow ready to run\n    \"\"\"\n    # Create nodes\n    search = SearchNode()\n    analyze = AnalyzeResultsNode()\n    \n    # Connect nodes\n    search >> analyze\n    \n    # Create flow starting with search\n    return Flow(start=search)\n"
  },
  {
    "path": "cookbook/pocketflow-tool-search/main.py",
    "content": "import os\nfrom flow import create_flow\n\ndef main():\n    \"\"\"Run the web search flow\"\"\"\n    \n    # Get search query from user\n    query = input(\"Enter search query: \")\n    if not query:\n        print(\"Error: Query is required\")\n        return\n        \n    # Initialize shared data\n    shared = {\n        \"query\": query,\n        \"num_results\": 5\n    }\n    \n    # Create and run flow\n    flow = create_flow()\n    flow.run(shared)\n    \n    # Results are in shared[\"analysis\"]\n    \nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "cookbook/pocketflow-tool-search/nodes.py",
    "content": "from pocketflow import Node\nfrom tools.search import SearchTool\nfrom tools.parser import analyze_results\nfrom typing import List, Dict\n\nclass SearchNode(Node):\n    \"\"\"Node to perform web search using SerpAPI\"\"\"\n    \n    def prep(self, shared):\n        return shared.get(\"query\"), shared.get(\"num_results\", 5)\n        \n    def exec(self, inputs):\n        query, num_results = inputs\n        if not query:\n            return []\n            \n        searcher = SearchTool()\n        return searcher.search(query, num_results)\n        \n    def post(self, shared, prep_res, exec_res):\n        shared[\"search_results\"] = exec_res\n        return \"default\"\n\nclass AnalyzeResultsNode(Node):\n    \"\"\"Node to analyze search results using LLM\"\"\"\n    \n    def prep(self, shared):\n        return shared.get(\"query\"), shared.get(\"search_results\", [])\n        \n    def exec(self, inputs):\n        query, results = inputs\n        if not results:\n            return {\n                \"summary\": \"No search results to analyze\",\n                \"key_points\": [],\n                \"follow_up_queries\": []\n            }\n            \n        return analyze_results(query, results)\n        \n    def post(self, shared, prep_res, exec_res):\n        shared[\"analysis\"] = exec_res\n        \n        # Print analysis\n        print(\"\\nSearch Analysis:\")\n        print(\"\\nSummary:\", exec_res[\"summary\"])\n        \n        print(\"\\nKey Points:\")\n        for point in exec_res[\"key_points\"]:\n            print(f\"- {point}\")\n            \n        print(\"\\nSuggested Follow-up Queries:\")\n        for query in exec_res[\"follow_up_queries\"]:\n            print(f\"- {query}\")\n            \n        return \"default\"\n"
  },
  {
    "path": "cookbook/pocketflow-tool-search/requirements.txt",
    "content": "pocketflow>=0.1.0\ngoogle-search-results>=2.4.2  # SerpAPI client\nopenai>=1.0.0  # for search result analysis\npyyaml>=6.0.1  # for structured output\n"
  },
  {
    "path": "cookbook/pocketflow-tool-search/tools/parser.py",
    "content": "from typing import Dict, List\nfrom utils.call_llm import call_llm\n\ndef analyze_results(query: str, results: List[Dict]) -> Dict:\n    \"\"\"Analyze search results using LLM\n    \n    Args:\n        query (str): Original search query\n        results (List[Dict]): Search results to analyze\n        \n    Returns:\n        Dict: Analysis including summary and key points\n    \"\"\"\n    # Format results for prompt\n    formatted_results = []\n    for i, result in enumerate(results, 1):\n        formatted_results.append(f\"\"\"\nResult {i}:\nTitle: {result['title']}\nSnippet: {result['snippet']}\nURL: {result['link']}\n\"\"\")\n    \n    prompt = f\"\"\"\nAnalyze these search results for the query: \"{query}\"\n\n{'\\n'.join(formatted_results)}\n\nPlease provide:\n1. A concise summary of the findings (2-3 sentences)\n2. Key points or facts (up to 5 bullet points)\n3. Suggested follow-up queries (2-3)\n\nOutput in YAML format:\n```yaml\nsummary: >\n    brief summary here\nkey_points:\n    - point 1\n    - point 2\nfollow_up_queries:\n    - query 1\n    - query 2\n```\n\"\"\"\n    \n    try:\n        response = call_llm(prompt)\n        # Extract YAML between code fences\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        \n        import yaml\n        analysis = yaml.safe_load(yaml_str)\n        \n        # Validate required fields\n        assert \"summary\" in analysis\n        assert \"key_points\" in analysis\n        assert \"follow_up_queries\" in analysis\n        assert isinstance(analysis[\"key_points\"], list)\n        assert isinstance(analysis[\"follow_up_queries\"], list)\n        \n        return analysis\n        \n    except Exception as e:\n        print(f\"Error analyzing results: {str(e)}\")\n        return {\n            \"summary\": \"Error analyzing results\",\n            \"key_points\": [],\n            \"follow_up_queries\": []\n        }"
  },
  {
    "path": "cookbook/pocketflow-tool-search/tools/search.py",
    "content": "import os\nfrom serpapi import GoogleSearch\nfrom typing import Dict, List, Optional\n\nclass SearchTool:\n    \"\"\"Tool for performing web searches using SerpAPI\"\"\"\n    \n    def __init__(self, api_key: Optional[str] = None):\n        \"\"\"Initialize search tool with API key\n        \n        Args:\n            api_key (str, optional): SerpAPI key. Defaults to env var SERPAPI_API_KEY.\n        \"\"\"\n        self.api_key = api_key or os.getenv(\"SERPAPI_API_KEY\")\n        if not self.api_key:\n            raise ValueError(\"SerpAPI key not found. Set SERPAPI_API_KEY env var.\")\n            \n    def search(self, query: str, num_results: int = 5) -> List[Dict]:\n        \"\"\"Perform Google search via SerpAPI\n        \n        Args:\n            query (str): Search query\n            num_results (int, optional): Number of results to return. Defaults to 5.\n            \n        Returns:\n            List[Dict]: Search results with title, snippet, and link\n        \"\"\"\n        # Configure search parameters\n        params = {\n            \"engine\": \"google\",\n            \"q\": query,\n            \"api_key\": self.api_key,\n            \"num\": num_results\n        }\n        \n        try:\n            # Execute search\n            search = GoogleSearch(params)\n            results = search.get_dict()\n            \n            # Extract organic results\n            if \"organic_results\" not in results:\n                return []\n                \n            processed_results = []\n            for result in results[\"organic_results\"][:num_results]:\n                processed_results.append({\n                    \"title\": result.get(\"title\", \"\"),\n                    \"snippet\": result.get(\"snippet\", \"\"),\n                    \"link\": result.get(\"link\", \"\")\n                })\n                \n            return processed_results\n            \n        except Exception as e:\n            print(f\"Search error: {str(e)}\")\n            return []\n"
  },
  {
    "path": "cookbook/pocketflow-tool-search/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-tool-search/utils/call_llm.py",
    "content": "import os\nfrom openai import OpenAI\nfrom pathlib import Path\n\n# Get the project root directory (parent of utils directory)\nROOT_DIR = Path(__file__).parent.parent\n\n# Initialize OpenAI client with API key from environment\nclient = OpenAI(api_key=os.getenv(\"OPENAI_API_KEY\"))\n\ndef call_llm(prompt: str) -> str:\n    \"\"\"Call OpenAI API to analyze text\n    \n    Args:\n        prompt (str): Input prompt for the model\n        \n    Returns:\n        str: Model response\n    \"\"\"\n    try:\n        response = client.chat.completions.create(\n            model=\"gpt-4o\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return response.choices[0].message.content\n        \n    except Exception as e:\n        print(f\"Error calling LLM API: {str(e)}\")\n        return \"\"\n\nif __name__ == \"__main__\":\n    # Test LLM call\n    response = call_llm(\"What is web search?\")\n    print(\"Response:\", response)\n"
  },
  {
    "path": "cookbook/pocketflow-tracing/README.md",
    "content": "# PocketFlow Tracing with Langfuse\n\nThis cookbook provides comprehensive observability for PocketFlow workflows using [Langfuse](https://langfuse.com/) as the tracing backend. With minimal code changes (just adding a decorator), you can automatically trace all node executions, inputs, outputs, and errors in your PocketFlow workflows.\n\n## 🎯 Features\n\n- **Automatic Tracing**: Trace entire flows with a single decorator\n- **Node-Level Observability**: Automatically trace `prep`, `exec`, and `post` phases of each node\n- **Input/Output Tracking**: Capture all data flowing through your workflow\n- **Error Tracking**: Automatically capture and trace exceptions\n- **Async Support**: Full support for AsyncFlow and AsyncNode\n- **Minimal Code Changes**: Just add `@trace_flow()` to your flow classes\n- **Langfuse Integration**: Leverage Langfuse's powerful observability platform\n\n## 🚀 Quick Start\n\n### 1. Install Dependencies\n\n```bash\npip install -r requirements.txt\n```\n\n### 2. Environment Setup\n\nCopy the example environment file and configure your Langfuse credentials:\n\n```bash\ncp .env.example .env\n```\n\nThen edit the `.env` file with your actual Langfuse configuration:\n\n```env\nLANGFUSE_SECRET_KEY=your-langfuse-secret-key\nLANGFUSE_PUBLIC_KEY=your-langfuse-public-key\nLANGFUSE_HOST=your-langfuse-host-url\nPOCKETFLOW_TRACING_DEBUG=true\n```\n\n**Note**: Replace the placeholder values with your actual Langfuse credentials and host URL.\n\n### 3. Basic Usage\n\n```python\nfrom pocketflow import Node, Flow\nfrom tracing import trace_flow\n\nclass MyNode(Node):\n    def prep(self, shared):\n        return shared[\"input\"]\n    \n    def exec(self, data):\n        return f\"Processed: {data}\"\n    \n    def post(self, shared, prep_res, exec_res):\n        shared[\"output\"] = exec_res\n        return \"default\"\n\n@trace_flow()  # 🎉 That's it! Your flow is now traced\nclass MyFlow(Flow):\n    def __init__(self):\n        super().__init__(start=MyNode())\n\n# Run your flow - tracing happens automatically\nflow = MyFlow()\nshared = {\"input\": \"Hello World\"}\nflow.run(shared)\n```\n\n## 📊 What Gets Traced\n\nWhen you apply the `@trace_flow()` decorator, the system automatically traces:\n\n### Flow Level\n- **Flow Start/End**: Overall execution time and status\n- **Input Data**: Initial shared state when flow starts\n- **Output Data**: Final shared state when flow completes\n- **Errors**: Any exceptions that occur during flow execution\n\n### Node Level\nFor each node in your flow, the system traces:\n\n- **prep() Phase**: \n  - Input: `shared` data\n  - Output: `prep_res` returned by prep method\n  - Execution time and any errors\n\n- **exec() Phase**:\n  - Input: `prep_res` from prep phase\n  - Output: `exec_res` returned by exec method\n  - Execution time and any errors\n  - Retry attempts (if configured)\n\n- **post() Phase**:\n  - Input: `shared`, `prep_res`, `exec_res`\n  - Output: Action string returned\n  - Execution time and any errors\n\n## 🔧 Configuration Options\n\n### Basic Configuration\n\n```python\nfrom tracing import trace_flow, TracingConfig\n\n# Use environment variables (default)\n@trace_flow()\nclass MyFlow(Flow):\n    pass\n\n# Custom flow name\n@trace_flow(flow_name=\"CustomFlowName\")\nclass MyFlow(Flow):\n    pass\n\n# Custom session and user IDs\n@trace_flow(session_id=\"session-123\", user_id=\"user-456\")\nclass MyFlow(Flow):\n    pass\n```\n\n### Advanced Configuration\n\n```python\nfrom tracing import TracingConfig\n\n# Create custom configuration\nconfig = TracingConfig(\n    langfuse_secret_key=\"your-secret-key\",\n    langfuse_public_key=\"your-public-key\", \n    langfuse_host=\"https://your-langfuse-instance.com\",\n    debug=True,\n    trace_inputs=True,\n    trace_outputs=True,\n    trace_errors=True\n)\n\n@trace_flow(config=config)\nclass MyFlow(Flow):\n    pass\n```\n\n## 📁 Examples\n\n### Basic Synchronous Flow\nSee `examples/basic_example.py` for a complete example of tracing a simple synchronous flow.\n\n```bash\ncd examples\npython basic_example.py\n```\n\n### Asynchronous Flow\nSee `examples/async_example.py` for tracing AsyncFlow and AsyncNode.\n\n```bash\ncd examples  \npython async_example.py\n```\n\n## 🔍 Viewing Traces\n\nAfter running your traced flows, visit your Langfuse dashboard to view the traces:\n\n**Dashboard URL**: Use the URL you configured in `LANGFUSE_HOST` environment variable\n\nIn the dashboard you'll see:\n- **Traces**: One trace per flow execution\n- **Spans**: Individual node phases (prep, exec, post)\n- **Input/Output Data**: All data flowing through your workflow\n- **Performance Metrics**: Execution times for each phase\n- **Error Details**: Stack traces and error messages\n\nThe tracings in examples.\n![alt text](screenshots/chrome_2025-06-27_12-05-28.png)\n\nDetailed tracing for a node.\n![langfuse](screenshots/chrome_2025-06-27_12-07-56.png)\n\n## 🛠️ Advanced Usage\n\n### Custom Tracer Configuration\n\n```python\nfrom tracing import TracingConfig, LangfuseTracer\n\n# Create custom configuration\nconfig = TracingConfig.from_env()\nconfig.debug = True\n\n# Use tracer directly (for advanced use cases)\ntracer = LangfuseTracer(config)\n```\n\n### Environment Variables\n\nYou can customize tracing behavior with these environment variables:\n\n```env\n# Required Langfuse configuration\nLANGFUSE_SECRET_KEY=your-secret-key\nLANGFUSE_PUBLIC_KEY=your-public-key\nLANGFUSE_HOST=your-langfuse-host\n\n# Optional tracing configuration\nPOCKETFLOW_TRACING_DEBUG=true\nPOCKETFLOW_TRACE_INPUTS=true\nPOCKETFLOW_TRACE_OUTPUTS=true\nPOCKETFLOW_TRACE_PREP=true\nPOCKETFLOW_TRACE_EXEC=true\nPOCKETFLOW_TRACE_POST=true\nPOCKETFLOW_TRACE_ERRORS=true\n\n# Optional session/user tracking\nPOCKETFLOW_SESSION_ID=your-session-id\nPOCKETFLOW_USER_ID=your-user-id\n```\n\n## 🐛 Troubleshooting\n\n### Common Issues\n\n1. **\"langfuse package not installed\"**\n   ```bash\n   pip install langfuse\n   ```\n\n2. **\"Langfuse client initialization failed\"**\n   - Check your `.env` file configuration\n   - Verify Langfuse server is running at the specified host\n   - Check network connectivity\n\n3. **\"No traces appearing in dashboard\"**\n   - Ensure `POCKETFLOW_TRACING_DEBUG=true` to see debug output\n   - Check that your flow is actually being executed\n   - Verify Langfuse credentials are correct\n\n### Debug Mode\n\nEnable debug mode to see detailed tracing information:\n\n```env\nPOCKETFLOW_TRACING_DEBUG=true\n```\n\nThis will print detailed information about:\n- Langfuse client initialization\n- Trace and span creation\n- Data serialization\n- Error messages\n\n## 📚 API Reference\n\n### `@trace_flow()`\n\nDecorator to add Langfuse tracing to PocketFlow flows.\n\n**Parameters:**\n- `config` (TracingConfig, optional): Custom configuration. If None, loads from environment.\n- `flow_name` (str, optional): Custom name for the flow. If None, uses class name.\n- `session_id` (str, optional): Session ID for grouping related traces.\n- `user_id` (str, optional): User ID for the trace.\n\n### `TracingConfig`\n\nConfiguration class for tracing settings.\n\n**Methods:**\n- `TracingConfig.from_env()`: Create config from environment variables\n- `validate()`: Check if configuration is valid\n- `to_langfuse_kwargs()`: Convert to Langfuse client kwargs\n\n### `LangfuseTracer`\n\nCore tracer class for Langfuse integration.\n\n**Methods:**\n- `start_trace()`: Start a new trace\n- `end_trace()`: End the current trace\n- `start_node_span()`: Start a span for node execution\n- `end_node_span()`: End a node execution span\n- `flush()`: Flush pending traces to Langfuse\n\n## 🤝 Contributing\n\nThis cookbook is designed to be a starting point for PocketFlow observability. Feel free to extend and customize it for your specific needs!\n\n## 📄 License\n\nThis cookbook follows the same license as PocketFlow.\n"
  },
  {
    "path": "cookbook/pocketflow-tracing/examples/async_example.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nAsync example demonstrating PocketFlow tracing with Langfuse.\n\nThis example shows how to use the @trace_flow decorator with AsyncFlow\nand AsyncNode to trace asynchronous workflows.\n\"\"\"\n\nimport asyncio\nimport sys\nimport os\nfrom dotenv import load_dotenv\n\n# Load environment variables\nload_dotenv()\n\n# Add parent directory to path to import pocketflow and tracing\nsys.path.insert(0, os.path.join(os.path.dirname(__file__), \"..\", \"..\", \"..\"))\nsys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))\n\nfrom pocketflow import AsyncNode, AsyncFlow\nfrom tracing import trace_flow, TracingConfig\n\n\nclass AsyncDataFetchNode(AsyncNode):\n    \"\"\"An async node that simulates fetching data.\"\"\"\n\n    async def prep_async(self, shared):\n        \"\"\"Extract the query from shared data.\"\"\"\n        query = shared.get(\"query\", \"default\")\n        return query\n\n    async def exec_async(self, query):\n        \"\"\"Simulate async data fetching.\"\"\"\n        print(f\"🔍 Fetching data for query: {query}\")\n\n        # Simulate async operation\n        await asyncio.sleep(1)\n\n        # Return mock data\n        data = {\n            \"query\": query,\n            \"results\": [f\"Result {i} for {query}\" for i in range(3)],\n            \"timestamp\": \"2024-01-01T00:00:00Z\",\n        }\n        return data\n\n    async def post_async(self, shared, prep_res, exec_res):\n        \"\"\"Store the fetched data.\"\"\"\n        shared[\"fetched_data\"] = exec_res\n        return \"process\"\n\n\nclass AsyncDataProcessNode(AsyncNode):\n    \"\"\"An async node that processes the fetched data.\"\"\"\n\n    async def prep_async(self, shared):\n        \"\"\"Get the fetched data.\"\"\"\n        return shared.get(\"fetched_data\", {})\n\n    async def exec_async(self, data):\n        \"\"\"Process the data asynchronously.\"\"\"\n        print(\"⚙️ Processing fetched data...\")\n\n        # Simulate async processing\n        await asyncio.sleep(0.5)\n\n        # Process the results\n        processed_results = []\n        for result in data.get(\"results\", []):\n            processed_results.append(f\"PROCESSED: {result}\")\n\n        return {\n            \"original_query\": data.get(\"query\"),\n            \"processed_results\": processed_results,\n            \"result_count\": len(processed_results),\n        }\n\n    async def post_async(self, shared, prep_res, exec_res):\n        \"\"\"Store the processed data.\"\"\"\n        shared[\"processed_data\"] = exec_res\n        return \"default\"\n\n\n@trace_flow(flow_name=\"AsyncDataProcessingFlow\")\nclass AsyncDataProcessingFlow(AsyncFlow):\n    \"\"\"An async flow that fetches and processes data.\"\"\"\n\n    def __init__(self):\n        # Create async nodes\n        fetch_node = AsyncDataFetchNode()\n        process_node = AsyncDataProcessNode()\n\n        # Connect nodes\n        fetch_node - \"process\" >> process_node\n\n        # Initialize async flow\n        super().__init__(start=fetch_node)\n\n\nasync def main():\n    \"\"\"Run the async tracing example.\"\"\"\n    print(\"🚀 Starting PocketFlow Async Tracing Example\")\n    print(\"=\" * 50)\n\n    # Create the async flow\n    flow = AsyncDataProcessingFlow()\n\n    # Prepare shared data\n    shared = {\"query\": \"machine learning tutorials\"}\n\n    print(f\"📥 Input: {shared}\")\n\n    # Run the async flow (this will be automatically traced)\n    try:\n        result = await flow.run_async(shared)\n        print(f\"📤 Output: {shared}\")\n        print(f\"🎯 Result: {result}\")\n        print(\"✅ Async flow completed successfully!\")\n\n        # Print the processed data\n        if \"processed_data\" in shared:\n            processed = shared[\"processed_data\"]\n            print(\n                f\"🎉 Processed {processed['result_count']} results for query: {processed['original_query']}\"\n            )\n            for result in processed[\"processed_results\"]:\n                print(f\"   - {result}\")\n\n    except Exception as e:\n        print(f\"❌ Async flow failed with error: {e}\")\n        raise\n\n    print(\"\\n📊 Check your Langfuse dashboard to see the async trace!\")\n    langfuse_host = os.getenv(\"LANGFUSE_HOST\", \"your-langfuse-host\")\n    print(f\"   Dashboard URL: {langfuse_host}\")\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "cookbook/pocketflow-tracing/examples/basic_example.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nBasic example demonstrating PocketFlow tracing with Langfuse.\n\nThis example shows how to use the @trace_flow decorator to automatically\ntrace a simple PocketFlow workflow.\n\"\"\"\n\nimport sys\nimport os\nfrom dotenv import load_dotenv\n\n# Load environment variables\nload_dotenv()\n\n# Add parent directory to path to import pocketflow and tracing\nsys.path.insert(0, os.path.join(os.path.dirname(__file__), \"..\", \"..\", \"..\"))\nsys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))\n\nfrom pocketflow import Node, Flow\nfrom tracing import trace_flow, TracingConfig\n\n\nclass GreetingNode(Node):\n    \"\"\"A simple node that creates a greeting message.\"\"\"\n\n    def prep(self, shared):\n        \"\"\"Extract the name from shared data.\"\"\"\n        name = shared.get(\"name\", \"World\")\n        return name\n\n    def exec(self, name):\n        \"\"\"Create a greeting message.\"\"\"\n        greeting = f\"Hello, {name}!\"\n        return greeting\n\n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store the greeting in shared data.\"\"\"\n        shared[\"greeting\"] = exec_res\n        return \"default\"\n\n\nclass UppercaseNode(Node):\n    \"\"\"A node that converts the greeting to uppercase.\"\"\"\n\n    def prep(self, shared):\n        \"\"\"Get the greeting from shared data.\"\"\"\n        return shared.get(\"greeting\", \"\")\n\n    def exec(self, greeting):\n        \"\"\"Convert to uppercase.\"\"\"\n        return greeting.upper()\n\n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Store the uppercase greeting.\"\"\"\n        shared[\"uppercase_greeting\"] = exec_res\n        return \"default\"\n\n\n@trace_flow(flow_name=\"BasicGreetingFlow\")\nclass BasicGreetingFlow(Flow):\n    \"\"\"A simple flow that creates and processes a greeting.\"\"\"\n\n    def __init__(self):\n        # Create nodes\n        greeting_node = GreetingNode()\n        uppercase_node = UppercaseNode()\n\n        # Connect nodes\n        greeting_node >> uppercase_node\n\n        # Initialize flow\n        super().__init__(start=greeting_node)\n\n\ndef main():\n    \"\"\"Run the basic tracing example.\"\"\"\n    print(\"🚀 Starting PocketFlow Tracing Basic Example\")\n    print(\"=\" * 50)\n\n    # Create the flow\n    flow = BasicGreetingFlow()\n\n    # Prepare shared data\n    shared = {\"name\": \"PocketFlow User\"}\n\n    print(f\"📥 Input: {shared}\")\n\n    # Run the flow (this will be automatically traced)\n    try:\n        result = flow.run(shared)\n        print(f\"📤 Output: {shared}\")\n        print(f\"🎯 Result: {result}\")\n        print(\"✅ Flow completed successfully!\")\n\n        # Print the final greeting\n        if \"uppercase_greeting\" in shared:\n            print(f\"🎉 Final greeting: {shared['uppercase_greeting']}\")\n\n    except Exception as e:\n        print(f\"❌ Flow failed with error: {e}\")\n        raise\n\n    print(\"\\n📊 Check your Langfuse dashboard to see the trace!\")\n    langfuse_host = os.getenv(\"LANGFUSE_HOST\", \"your-langfuse-host\")\n    print(f\"   Dashboard URL: {langfuse_host}\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "cookbook/pocketflow-tracing/requirements.txt",
    "content": "# Core dependencies for PocketFlow tracing\nlangfuse>=2.0.0,<3.0.0  # v2 low level SDK compatible with Langfuse servers\npython-dotenv>=1.0.0\n\n# Optional dependencies for enhanced functionality\npydantic>=2.0.0  # For data validation and serialization\n"
  },
  {
    "path": "cookbook/pocketflow-tracing/setup.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nSetup script for PocketFlow Tracing cookbook.\n\nThis script helps install dependencies and verify the setup.\n\"\"\"\n\nimport subprocess\nimport sys\nimport os\n\n\ndef install_dependencies():\n    \"\"\"Install required dependencies.\"\"\"\n    print(\"📦 Installing dependencies...\")\n    try:\n        subprocess.check_call(\n            [sys.executable, \"-m\", \"pip\", \"install\", \"-r\", \"requirements.txt\"]\n        )\n        print(\"✅ Dependencies installed successfully!\")\n        return True\n    except subprocess.CalledProcessError as e:\n        print(f\"❌ Failed to install dependencies: {e}\")\n        return False\n\n\ndef verify_setup():\n    \"\"\"Verify that the setup is working.\"\"\"\n    print(\"🔍 Verifying setup...\")\n    try:\n        # Try to import the tracing module\n        from tracing import trace_flow, TracingConfig\n\n        print(\"✅ Tracing module imported successfully!\")\n\n        # Try to load configuration\n        config = TracingConfig.from_env()\n        if config.validate():\n            print(\"✅ Configuration is valid!\")\n        else:\n            print(\"⚠️ Configuration validation failed - check your .env file\")\n\n        return True\n    except ImportError as e:\n        print(f\"❌ Failed to import tracing module: {e}\")\n        return False\n    except Exception as e:\n        print(f\"❌ Setup verification failed: {e}\")\n        return False\n\n\ndef main():\n    \"\"\"Main setup function.\"\"\"\n    print(\"🚀 PocketFlow Tracing Setup\")\n    print(\"=\" * 40)\n\n    # Check if we're in the right directory\n    if not os.path.exists(\"requirements.txt\"):\n        print(\n            \"❌ requirements.txt not found. Please run this script from the pocketflow-tracing directory.\"\n        )\n        sys.exit(1)\n\n    # Install dependencies\n    if not install_dependencies():\n        sys.exit(1)\n\n    # Verify setup\n    if not verify_setup():\n        sys.exit(1)\n\n    print(\"\\n🎉 Setup completed successfully!\")\n    print(\"\\n📚 Next steps:\")\n    print(\"1. Check the README.md for usage instructions\")\n    print(\"2. Run the examples: python examples/basic_example.py\")\n    print(\"3. Run the test suite: python test_tracing.py\")\n    print(\"4. Check your Langfuse dashboard (URL configured in LANGFUSE_HOST)\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "cookbook/pocketflow-tracing/test_tracing.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nTest script for PocketFlow tracing functionality.\n\nThis script tests the tracing implementation to ensure it works correctly\nwith Langfuse integration.\n\"\"\"\n\nimport sys\nimport os\nimport asyncio\nfrom dotenv import load_dotenv\n\n# Load environment variables\nload_dotenv()\n\n# Add paths for imports\nsys.path.insert(0, os.path.join(os.path.dirname(__file__), \"..\", \"..\"))\nsys.path.insert(0, os.path.dirname(__file__))\n\nfrom pocketflow import Node, Flow, AsyncNode, AsyncFlow\nfrom tracing import trace_flow, TracingConfig\nfrom utils import setup_tracing\n\n\nclass TestNode(Node):\n    \"\"\"Simple test node for tracing verification.\"\"\"\n\n    def prep(self, shared):\n        \"\"\"Test prep phase.\"\"\"\n        return shared.get(\"input\", \"test_input\")\n\n    def exec(self, prep_res):\n        \"\"\"Test exec phase.\"\"\"\n        return f\"processed_{prep_res}\"\n\n    def post(self, shared, prep_res, exec_res):\n        \"\"\"Test post phase.\"\"\"\n        shared[\"output\"] = exec_res\n        return \"default\"\n\n\nclass TestAsyncNode(AsyncNode):\n    \"\"\"Simple async test node for tracing verification.\"\"\"\n\n    async def prep_async(self, shared):\n        \"\"\"Test async prep phase.\"\"\"\n        await asyncio.sleep(0.1)  # Simulate async work\n        return shared.get(\"input\", \"async_test_input\")\n\n    async def exec_async(self, prep_res):\n        \"\"\"Test async exec phase.\"\"\"\n        await asyncio.sleep(0.1)  # Simulate async work\n        return f\"async_processed_{prep_res}\"\n\n    async def post_async(self, shared, prep_res, exec_res):\n        \"\"\"Test async post phase.\"\"\"\n        shared[\"output\"] = exec_res\n        return \"default\"\n\n\n@trace_flow(flow_name=\"TestSyncFlow\")\nclass TestSyncFlow(Flow):\n    \"\"\"Test synchronous flow with tracing.\"\"\"\n\n    def __init__(self):\n        super().__init__(start=TestNode())\n\n\n@trace_flow(flow_name=\"TestAsyncFlow\")\nclass TestAsyncFlow(AsyncFlow):\n    \"\"\"Test asynchronous flow with tracing.\"\"\"\n\n    def __init__(self):\n        super().__init__(start=TestAsyncNode())\n\n\ndef test_sync_flow():\n    \"\"\"Test synchronous flow tracing.\"\"\"\n    print(\"🧪 Testing synchronous flow tracing...\")\n\n    flow = TestSyncFlow()\n    shared = {\"input\": \"sync_test_data\"}\n\n    print(f\"   Input: {shared}\")\n    result = flow.run(shared)\n    print(f\"   Output: {shared}\")\n    print(f\"   Result: {result}\")\n\n    # Verify the flow worked\n    assert \"output\" in shared\n    assert shared[\"output\"] == \"processed_sync_test_data\"\n    print(\"   ✅ Sync flow test passed\")\n\n\nasync def test_async_flow():\n    \"\"\"Test asynchronous flow tracing.\"\"\"\n    print(\"🧪 Testing asynchronous flow tracing...\")\n\n    flow = TestAsyncFlow()\n    shared = {\"input\": \"async_test_data\"}\n\n    print(f\"   Input: {shared}\")\n    result = await flow.run_async(shared)\n    print(f\"   Output: {shared}\")\n    print(f\"   Result: {result}\")\n\n    # Verify the flow worked\n    assert \"output\" in shared\n    assert shared[\"output\"] == \"async_processed_async_test_data\"\n    print(\"   ✅ Async flow test passed\")\n\n\ndef test_configuration():\n    \"\"\"Test configuration loading and validation.\"\"\"\n    print(\"🧪 Testing configuration...\")\n\n    # Test loading from environment\n    config = TracingConfig.from_env()\n    print(f\"   Loaded config: debug={config.debug}\")\n\n    # Test validation\n    is_valid = config.validate()\n    print(f\"   Config valid: {is_valid}\")\n\n    if is_valid:\n        print(\"   ✅ Configuration test passed\")\n    else:\n        print(\n            \"   ⚠️ Configuration test failed (this may be expected if env vars not set)\"\n        )\n\n\ndef test_error_handling():\n    \"\"\"Test error handling in traced flows.\"\"\"\n    print(\"🧪 Testing error handling...\")\n\n    class ErrorNode(Node):\n        def exec(self, prep_res):\n            raise ValueError(\"Test error for tracing\")\n\n    @trace_flow(flow_name=\"TestErrorFlow\")\n    class ErrorFlow(Flow):\n        def __init__(self):\n            super().__init__(start=ErrorNode())\n\n    flow = ErrorFlow()\n    shared = {\"input\": \"error_test\"}\n\n    try:\n        flow.run(shared)\n        print(\"   ❌ Expected error but flow succeeded\")\n    except ValueError as e:\n        print(f\"   ✅ Error correctly caught and traced: {e}\")\n    except Exception as e:\n        print(f\"   ⚠️ Unexpected error type: {e}\")\n\n\nasync def main():\n    \"\"\"Run all tests.\"\"\"\n    print(\"🚀 Starting PocketFlow Tracing Tests\")\n    print(\"=\" * 50)\n\n    # Test configuration first\n    test_configuration()\n    print()\n\n    # Test setup (optional - only if environment is configured)\n    try:\n        print(\"🔧 Testing setup...\")\n        config = setup_tracing()\n        print(\"   ✅ Setup test passed\")\n    except Exception as e:\n        print(f\"   ⚠️ Setup test failed: {e}\")\n        print(\"   (This is expected if Langfuse is not configured)\")\n    print()\n\n    # Test sync flow\n    test_sync_flow()\n    print()\n\n    # Test async flow\n    await test_async_flow()\n    print()\n\n    # Test error handling\n    test_error_handling()\n    print()\n\n    print(\"🎉 All tests completed!\")\n    print(\"\\n📊 If Langfuse is configured, check your dashboard for traces:\")\n    langfuse_host = os.getenv(\"LANGFUSE_HOST\", \"your-langfuse-host\")\n    print(f\"   Dashboard URL: {langfuse_host}\")\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "cookbook/pocketflow-tracing/tracing/__init__.py",
    "content": "\"\"\"\nPocketFlow Tracing Module\n\nThis module provides observability and tracing capabilities for PocketFlow workflows\nusing Langfuse as the backend. It includes decorators and utilities to automatically\ntrace node execution, inputs, and outputs.\n\"\"\"\n\nfrom .config import TracingConfig\nfrom .core import LangfuseTracer\nfrom .decorator import trace_flow\n\n__all__ = [\"trace_flow\", \"TracingConfig\", \"LangfuseTracer\"]\n"
  },
  {
    "path": "cookbook/pocketflow-tracing/tracing/config.py",
    "content": "\"\"\"\nConfiguration module for PocketFlow tracing with Langfuse.\n\"\"\"\n\nimport os\nfrom dataclasses import dataclass\nfrom typing import Optional\nfrom dotenv import load_dotenv\n\n\n@dataclass\nclass TracingConfig:\n    \"\"\"Configuration class for PocketFlow tracing with Langfuse.\"\"\"\n    \n    # Langfuse configuration\n    langfuse_secret_key: Optional[str] = None\n    langfuse_public_key: Optional[str] = None\n    langfuse_host: Optional[str] = None\n    \n    # PocketFlow tracing configuration\n    debug: bool = False\n    trace_inputs: bool = True\n    trace_outputs: bool = True\n    trace_prep: bool = True\n    trace_exec: bool = True\n    trace_post: bool = True\n    trace_errors: bool = True\n    \n    # Session configuration\n    session_id: Optional[str] = None\n    user_id: Optional[str] = None\n    \n    @classmethod\n    def from_env(cls, env_file: Optional[str] = None) -> \"TracingConfig\":\n        \"\"\"\n        Create TracingConfig from environment variables.\n        \n        Args:\n            env_file: Optional path to .env file. If None, looks for .env in current directory.\n            \n        Returns:\n            TracingConfig instance with values from environment variables.\n        \"\"\"\n        # Load environment variables from .env file if it exists\n        if env_file:\n            load_dotenv(env_file)\n        else:\n            # Try to find .env file in current directory or parent directories\n            load_dotenv()\n        \n        return cls(\n            langfuse_secret_key=os.getenv(\"LANGFUSE_SECRET_KEY\"),\n            langfuse_public_key=os.getenv(\"LANGFUSE_PUBLIC_KEY\"),\n            langfuse_host=os.getenv(\"LANGFUSE_HOST\"),\n            debug=os.getenv(\"POCKETFLOW_TRACING_DEBUG\", \"false\").lower() == \"true\",\n            trace_inputs=os.getenv(\"POCKETFLOW_TRACE_INPUTS\", \"true\").lower() == \"true\",\n            trace_outputs=os.getenv(\"POCKETFLOW_TRACE_OUTPUTS\", \"true\").lower() == \"true\",\n            trace_prep=os.getenv(\"POCKETFLOW_TRACE_PREP\", \"true\").lower() == \"true\",\n            trace_exec=os.getenv(\"POCKETFLOW_TRACE_EXEC\", \"true\").lower() == \"true\",\n            trace_post=os.getenv(\"POCKETFLOW_TRACE_POST\", \"true\").lower() == \"true\",\n            trace_errors=os.getenv(\"POCKETFLOW_TRACE_ERRORS\", \"true\").lower() == \"true\",\n            session_id=os.getenv(\"POCKETFLOW_SESSION_ID\"),\n            user_id=os.getenv(\"POCKETFLOW_USER_ID\"),\n        )\n    \n    def validate(self) -> bool:\n        \"\"\"\n        Validate that required configuration is present.\n        \n        Returns:\n            True if configuration is valid, False otherwise.\n        \"\"\"\n        if not self.langfuse_secret_key:\n            if self.debug:\n                print(\"Warning: LANGFUSE_SECRET_KEY not set\")\n            return False\n            \n        if not self.langfuse_public_key:\n            if self.debug:\n                print(\"Warning: LANGFUSE_PUBLIC_KEY not set\")\n            return False\n            \n        if not self.langfuse_host:\n            if self.debug:\n                print(\"Warning: LANGFUSE_HOST not set\")\n            return False\n            \n        return True\n    \n    def to_langfuse_kwargs(self) -> dict:\n        \"\"\"\n        Convert configuration to kwargs for Langfuse client initialization.\n        \n        Returns:\n            Dictionary of kwargs for Langfuse client.\n        \"\"\"\n        kwargs = {}\n        \n        if self.langfuse_secret_key:\n            kwargs[\"secret_key\"] = self.langfuse_secret_key\n            \n        if self.langfuse_public_key:\n            kwargs[\"public_key\"] = self.langfuse_public_key\n            \n        if self.langfuse_host:\n            kwargs[\"host\"] = self.langfuse_host\n            \n        if self.debug:\n            kwargs[\"debug\"] = True\n            \n        return kwargs\n"
  },
  {
    "path": "cookbook/pocketflow-tracing/tracing/core.py",
    "content": "\"\"\"\nCore tracing functionality for PocketFlow with Langfuse integration.\n\"\"\"\n\nimport json\nimport time\nimport uuid\nfrom typing import Any, Dict, Optional, Union\nfrom datetime import datetime\n\ntry:\n    from langfuse import Langfuse\n\n    LANGFUSE_AVAILABLE = True\nexcept ImportError:\n    LANGFUSE_AVAILABLE = False\n    print(\"Warning: langfuse package not installed. Install with: pip install langfuse\")\n\nfrom .config import TracingConfig\n\n\nclass LangfuseTracer:\n    \"\"\"\n    Core tracer class that handles Langfuse integration for PocketFlow.\n    \"\"\"\n\n    def __init__(self, config: TracingConfig):\n        \"\"\"\n        Initialize the LangfuseTracer.\n\n        Args:\n            config: TracingConfig instance with Langfuse settings.\n        \"\"\"\n        self.config = config\n        self.client = None\n        self.current_trace = None\n        self.spans = {}  # Store spans by node ID\n\n        if LANGFUSE_AVAILABLE and config.validate():\n            try:\n                # Initialize Langfuse client with proper parameters\n                kwargs = {}\n                if config.langfuse_secret_key:\n                    kwargs[\"secret_key\"] = config.langfuse_secret_key\n                if config.langfuse_public_key:\n                    kwargs[\"public_key\"] = config.langfuse_public_key\n                if config.langfuse_host:\n                    kwargs[\"host\"] = config.langfuse_host\n                if config.debug:\n                    kwargs[\"debug\"] = True\n\n                self.client = Langfuse(**kwargs)\n                if config.debug:\n                    print(\n                        f\"✓ Langfuse client initialized with host: {config.langfuse_host}\"\n                    )\n            except Exception as e:\n                if config.debug:\n                    print(f\"✗ Failed to initialize Langfuse client: {e}\")\n                self.client = None\n        else:\n            if config.debug:\n                print(\"✗ Langfuse not available or configuration invalid\")\n\n    def start_trace(self, flow_name: str, input_data: Dict[str, Any]) -> Optional[str]:\n        \"\"\"\n        Start a new trace for a flow execution.\n\n        Args:\n            flow_name: Name of the flow being traced.\n            input_data: Input data for the flow.\n\n        Returns:\n            Trace ID if successful, None otherwise.\n        \"\"\"\n        if not self.client:\n            return None\n\n        try:\n            # Serialize input data safely\n            serialized_input = self._serialize_data(input_data)\n\n            # Use Langfuse v2 API to create a trace\n            self.current_trace = self.client.trace(\n                name=flow_name,\n                input=serialized_input,\n                metadata={\n                    \"framework\": \"PocketFlow\",\n                    \"trace_type\": \"flow_execution\",\n                    \"timestamp\": datetime.now().isoformat(),\n                },\n                session_id=self.config.session_id,\n                user_id=self.config.user_id,\n            )\n\n            # Get the trace ID\n            trace_id = self.current_trace.id\n\n            if self.config.debug:\n                print(f\"✓ Started trace: {trace_id} for flow: {flow_name}\")\n\n            return trace_id\n\n        except Exception as e:\n            if self.config.debug:\n                print(f\"✗ Failed to start trace: {e}\")\n            return None\n\n    def end_trace(self, output_data: Dict[str, Any], status: str = \"success\") -> None:\n        \"\"\"\n        End the current trace.\n\n        Args:\n            output_data: Output data from the flow.\n            status: Status of the trace execution.\n        \"\"\"\n        if not self.current_trace:\n            return\n\n        try:\n            # Serialize output data safely\n            serialized_output = self._serialize_data(output_data)\n\n            # Update the trace with output data using v2 API\n            self.current_trace.update(\n                output=serialized_output,\n                metadata={\n                    \"status\": status,\n                    \"end_timestamp\": datetime.now().isoformat(),\n                },\n            )\n\n            if self.config.debug:\n                print(f\"✓ Ended trace with status: {status}\")\n\n        except Exception as e:\n            if self.config.debug:\n                print(f\"✗ Failed to end trace: {e}\")\n        finally:\n            self.current_trace = None\n            self.spans.clear()\n\n    def start_node_span(\n        self, node_name: str, node_id: str, phase: str\n    ) -> Optional[str]:\n        \"\"\"\n        Start a span for a node execution phase.\n\n        Args:\n            node_name: Name/type of the node.\n            node_id: Unique identifier for the node instance.\n            phase: Execution phase (prep, exec, post).\n\n        Returns:\n            Span ID if successful, None otherwise.\n        \"\"\"\n        if not self.current_trace:\n            return None\n\n        try:\n            span_id = f\"{node_id}_{phase}\"\n\n            # Create a child span using v2 API\n            span = self.current_trace.span(\n                name=f\"{node_name}.{phase}\",\n                metadata={\n                    \"node_type\": node_name,\n                    \"node_id\": node_id,\n                    \"phase\": phase,\n                    \"start_timestamp\": datetime.now().isoformat(),\n                },\n            )\n\n            self.spans[span_id] = span\n\n            if self.config.debug:\n                print(f\"✓ Started span: {span_id}\")\n\n            return span_id\n\n        except Exception as e:\n            if self.config.debug:\n                print(f\"✗ Failed to start span: {e}\")\n            return None\n\n    def end_node_span(\n        self,\n        span_id: str,\n        input_data: Any = None,\n        output_data: Any = None,\n        error: Exception = None,\n    ) -> None:\n        \"\"\"\n        End a node execution span.\n\n        Args:\n            span_id: ID of the span to end.\n            input_data: Input data for the phase.\n            output_data: Output data from the phase.\n            error: Exception if the phase failed.\n        \"\"\"\n        if span_id not in self.spans:\n            return\n\n        try:\n            span = self.spans[span_id]\n\n            # Prepare update data\n            update_data = {}\n\n            if input_data is not None and self.config.trace_inputs:\n                update_data[\"input\"] = self._serialize_data(input_data)\n            if output_data is not None and self.config.trace_outputs:\n                update_data[\"output\"] = self._serialize_data(output_data)\n\n            if error and self.config.trace_errors:\n                update_data.update(\n                    {\n                        \"level\": \"ERROR\",\n                        \"status_message\": str(error),\n                        \"metadata\": {\n                            \"error_type\": type(error).__name__,\n                            \"error_message\": str(error),\n                            \"end_timestamp\": datetime.now().isoformat(),\n                        },\n                    }\n                )\n            else:\n                update_data.update(\n                    {\n                        \"level\": \"DEFAULT\",\n                        \"metadata\": {\"end_timestamp\": datetime.now().isoformat()},\n                    }\n                )\n\n            # Update the span with all data at once\n            span.update(**update_data)\n\n            # End the span\n            span.end()\n\n            if self.config.debug:\n                status = \"ERROR\" if error else \"SUCCESS\"\n                print(f\"✓ Ended span: {span_id} with status: {status}\")\n\n        except Exception as e:\n            if self.config.debug:\n                print(f\"✗ Failed to end span: {e}\")\n        finally:\n            if span_id in self.spans:\n                del self.spans[span_id]\n\n    def _serialize_data(self, data: Any) -> Any:\n        \"\"\"\n        Safely serialize data for Langfuse.\n\n        Args:\n            data: Data to serialize.\n\n        Returns:\n            Serialized data that can be sent to Langfuse.\n        \"\"\"\n        try:\n            # Handle common PocketFlow data types\n            if hasattr(data, \"__dict__\"):\n                # Convert objects to dict representation\n                return {\"_type\": type(data).__name__, \"_data\": str(data)}\n            elif isinstance(data, (dict, list, str, int, float, bool, type(None))):\n                # JSON-serializable types\n                return data\n            else:\n                # Fallback to string representation\n                return {\"_type\": type(data).__name__, \"_data\": str(data)}\n        except Exception:\n            # Ultimate fallback\n            return {\"_type\": \"unknown\", \"_data\": \"<serialization_failed>\"}\n\n    def flush(self) -> None:\n        \"\"\"Flush any pending traces to Langfuse.\"\"\"\n        if self.client:\n            try:\n                self.client.flush()\n                if self.config.debug:\n                    print(\"✓ Flushed traces to Langfuse\")\n            except Exception as e:\n                if self.config.debug:\n                    print(f\"✗ Failed to flush traces: {e}\")\n"
  },
  {
    "path": "cookbook/pocketflow-tracing/tracing/decorator.py",
    "content": "\"\"\"\nDecorator for tracing PocketFlow workflows with Langfuse.\n\"\"\"\n\nimport functools\nimport inspect\nimport uuid\nfrom typing import Any, Callable, Dict, Optional, Union\n\nfrom .config import TracingConfig\nfrom .core import LangfuseTracer\n\n\ndef trace_flow(\n    config: Optional[TracingConfig] = None,\n    flow_name: Optional[str] = None,\n    session_id: Optional[str] = None,\n    user_id: Optional[str] = None\n):\n    \"\"\"\n    Decorator to add Langfuse tracing to PocketFlow flows.\n    \n    This decorator automatically traces:\n    - Flow execution start/end\n    - Each node's prep, exec, and post phases\n    - Input and output data for each phase\n    - Errors and exceptions\n    \n    Args:\n        config: TracingConfig instance. If None, loads from environment.\n        flow_name: Custom name for the flow. If None, uses the flow class name.\n        session_id: Session ID for grouping related traces.\n        user_id: User ID for the trace.\n        \n    Returns:\n        Decorated flow class or function.\n        \n    Example:\n        ```python\n        from tracing import trace_flow\n        \n        @trace_flow()\n        class MyFlow(Flow):\n            def __init__(self):\n                super().__init__(start=MyNode())\n        \n        # Or with custom configuration\n        config = TracingConfig.from_env()\n        \n        @trace_flow(config=config, flow_name=\"CustomFlow\")\n        class MyFlow(Flow):\n            pass\n        ```\n    \"\"\"\n    def decorator(flow_class_or_func):\n        # Handle both class and function decoration\n        if inspect.isclass(flow_class_or_func):\n            return _trace_flow_class(flow_class_or_func, config, flow_name, session_id, user_id)\n        else:\n            return _trace_flow_function(flow_class_or_func, config, flow_name, session_id, user_id)\n    \n    return decorator\n\n\ndef _trace_flow_class(flow_class, config, flow_name, session_id, user_id):\n    \"\"\"Trace a Flow class by wrapping its methods.\"\"\"\n    \n    # Get or create config\n    if config is None:\n        config = TracingConfig.from_env()\n    \n    # Override session/user if provided\n    if session_id:\n        config.session_id = session_id\n    if user_id:\n        config.user_id = user_id\n    \n    # Get flow name\n    if flow_name is None:\n        flow_name = flow_class.__name__\n    \n    # Store original methods\n    original_init = flow_class.__init__\n    original_run = getattr(flow_class, 'run', None)\n    original_run_async = getattr(flow_class, 'run_async', None)\n    \n    def traced_init(self, *args, **kwargs):\n        \"\"\"Initialize the flow with tracing capabilities.\"\"\"\n        # Call original init\n        original_init(self, *args, **kwargs)\n        \n        # Add tracing attributes\n        self._tracer = LangfuseTracer(config)\n        self._flow_name = flow_name\n        self._trace_id = None\n        \n        # Patch all nodes in the flow\n        self._patch_nodes()\n    \n    def traced_run(self, shared):\n        \"\"\"Traced version of the run method.\"\"\"\n        if not hasattr(self, '_tracer'):\n            # Fallback if not properly initialized\n            return original_run(self, shared) if original_run else None\n            \n        # Start trace\n        self._trace_id = self._tracer.start_trace(self._flow_name, shared)\n        \n        try:\n            # Run the original flow\n            result = original_run(self, shared) if original_run else None\n            \n            # End trace successfully\n            self._tracer.end_trace(shared, \"success\")\n            \n            return result\n            \n        except Exception as e:\n            # End trace with error\n            self._tracer.end_trace(shared, \"error\")\n            raise\n        finally:\n            # Ensure cleanup\n            self._tracer.flush()\n    \n    async def traced_run_async(self, shared):\n        \"\"\"Traced version of the async run method.\"\"\"\n        if not hasattr(self, '_tracer'):\n            # Fallback if not properly initialized\n            return await original_run_async(self, shared) if original_run_async else None\n            \n        # Start trace\n        self._trace_id = self._tracer.start_trace(self._flow_name, shared)\n        \n        try:\n            # Run the original flow\n            result = await original_run_async(self, shared) if original_run_async else None\n            \n            # End trace successfully\n            self._tracer.end_trace(shared, \"success\")\n            \n            return result\n            \n        except Exception as e:\n            # End trace with error\n            self._tracer.end_trace(shared, \"error\")\n            raise\n        finally:\n            # Ensure cleanup\n            self._tracer.flush()\n    \n    def patch_nodes(self):\n        \"\"\"Patch all nodes in the flow to add tracing.\"\"\"\n        if not hasattr(self, 'start_node') or not self.start_node:\n            return\n            \n        visited = set()\n        nodes_to_patch = [self.start_node]\n        \n        while nodes_to_patch:\n            node = nodes_to_patch.pop(0)\n            if id(node) in visited:\n                continue\n                \n            visited.add(id(node))\n            \n            # Patch this node\n            self._patch_node(node)\n            \n            # Add successors to patch list\n            if hasattr(node, 'successors'):\n                for successor in node.successors.values():\n                    if successor and id(successor) not in visited:\n                        nodes_to_patch.append(successor)\n    \n    def patch_node(self, node):\n        \"\"\"Patch a single node to add tracing.\"\"\"\n        if hasattr(node, '_pocketflow_traced'):\n            return  # Already patched\n            \n        node_id = str(uuid.uuid4())\n        node_name = type(node).__name__\n        \n        # Store original methods\n        original_prep = getattr(node, 'prep', None)\n        original_exec = getattr(node, 'exec', None)\n        original_post = getattr(node, 'post', None)\n        original_prep_async = getattr(node, 'prep_async', None)\n        original_exec_async = getattr(node, 'exec_async', None)\n        original_post_async = getattr(node, 'post_async', None)\n        \n        # Create traced versions\n        if original_prep:\n            node.prep = self._create_traced_method(original_prep, node_id, node_name, 'prep')\n        if original_exec:\n            node.exec = self._create_traced_method(original_exec, node_id, node_name, 'exec')\n        if original_post:\n            node.post = self._create_traced_method(original_post, node_id, node_name, 'post')\n        if original_prep_async:\n            node.prep_async = self._create_traced_async_method(original_prep_async, node_id, node_name, 'prep')\n        if original_exec_async:\n            node.exec_async = self._create_traced_async_method(original_exec_async, node_id, node_name, 'exec')\n        if original_post_async:\n            node.post_async = self._create_traced_async_method(original_post_async, node_id, node_name, 'post')\n        \n        # Mark as traced\n        node._pocketflow_traced = True\n    \n    def create_traced_method(self, original_method, node_id, node_name, phase):\n        \"\"\"Create a traced version of a synchronous method.\"\"\"\n        @functools.wraps(original_method)\n        def traced_method(*args, **kwargs):\n            span_id = self._tracer.start_node_span(node_name, node_id, phase)\n            \n            try:\n                result = original_method(*args, **kwargs)\n                self._tracer.end_node_span(span_id, input_data=args, output_data=result)\n                return result\n            except Exception as e:\n                self._tracer.end_node_span(span_id, input_data=args, error=e)\n                raise\n                \n        return traced_method\n    \n    def create_traced_async_method(self, original_method, node_id, node_name, phase):\n        \"\"\"Create a traced version of an asynchronous method.\"\"\"\n        @functools.wraps(original_method)\n        async def traced_async_method(*args, **kwargs):\n            span_id = self._tracer.start_node_span(node_name, node_id, phase)\n            \n            try:\n                result = await original_method(*args, **kwargs)\n                self._tracer.end_node_span(span_id, input_data=args, output_data=result)\n                return result\n            except Exception as e:\n                self._tracer.end_node_span(span_id, input_data=args, error=e)\n                raise\n                \n        return traced_async_method\n    \n    # Replace methods on the class\n    flow_class.__init__ = traced_init\n    flow_class._patch_nodes = patch_nodes\n    flow_class._patch_node = patch_node\n    flow_class._create_traced_method = create_traced_method\n    flow_class._create_traced_async_method = create_traced_async_method\n    \n    if original_run:\n        flow_class.run = traced_run\n    if original_run_async:\n        flow_class.run_async = traced_run_async\n    \n    return flow_class\n\n\ndef _trace_flow_function(flow_func, config, flow_name, session_id, user_id):\n    \"\"\"Trace a flow function (for functional-style flows).\"\"\"\n    \n    # Get or create config\n    if config is None:\n        config = TracingConfig.from_env()\n    \n    # Override session/user if provided\n    if session_id:\n        config.session_id = session_id\n    if user_id:\n        config.user_id = user_id\n    \n    # Get flow name\n    if flow_name is None:\n        flow_name = flow_func.__name__\n    \n    tracer = LangfuseTracer(config)\n    \n    @functools.wraps(flow_func)\n    def traced_flow_func(*args, **kwargs):\n        # Assume first argument is shared data\n        shared = args[0] if args else {}\n        \n        # Start trace\n        trace_id = tracer.start_trace(flow_name, shared)\n        \n        try:\n            result = flow_func(*args, **kwargs)\n            tracer.end_trace(shared, \"success\")\n            return result\n        except Exception as e:\n            tracer.end_trace(shared, \"error\")\n            raise\n        finally:\n            tracer.flush()\n    \n    return traced_flow_func\n"
  },
  {
    "path": "cookbook/pocketflow-tracing/utils/__init__.py",
    "content": "\"\"\"\nUtility functions for PocketFlow tracing.\n\"\"\"\n\nfrom .setup import setup_tracing, test_langfuse_connection\n\n__all__ = ['setup_tracing', 'test_langfuse_connection']\n"
  },
  {
    "path": "cookbook/pocketflow-tracing/utils/setup.py",
    "content": "\"\"\"\nSetup and testing utilities for PocketFlow tracing.\n\"\"\"\n\nimport os\nimport sys\nfrom typing import Optional\n\n# Add parent directory to path for imports\nsys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))\n\ntry:\n    from langfuse import Langfuse\n    LANGFUSE_AVAILABLE = True\nexcept ImportError:\n    LANGFUSE_AVAILABLE = False\n\nfrom tracing import TracingConfig, LangfuseTracer\n\n\ndef setup_tracing(env_file: Optional[str] = None) -> TracingConfig:\n    \"\"\"\n    Set up tracing configuration and validate the setup.\n    \n    Args:\n        env_file: Optional path to .env file. If None, uses default location.\n        \n    Returns:\n        TracingConfig instance.\n        \n    Raises:\n        RuntimeError: If setup fails.\n    \"\"\"\n    print(\"🔧 Setting up PocketFlow tracing...\")\n    \n    # Check if langfuse is installed\n    if not LANGFUSE_AVAILABLE:\n        raise RuntimeError(\n            \"Langfuse package not installed. Install with: pip install langfuse\"\n        )\n    \n    # Load configuration\n    if env_file:\n        config = TracingConfig.from_env(env_file)\n        print(f\"✓ Loaded configuration from: {env_file}\")\n    else:\n        config = TracingConfig.from_env()\n        print(\"✓ Loaded configuration from environment\")\n    \n    # Validate configuration\n    if not config.validate():\n        raise RuntimeError(\n            \"Invalid tracing configuration. Please check your environment variables:\\n\"\n            \"- LANGFUSE_SECRET_KEY\\n\"\n            \"- LANGFUSE_PUBLIC_KEY\\n\" \n            \"- LANGFUSE_HOST\"\n        )\n    \n    print(\"✓ Configuration validated\")\n    \n    # Test connection\n    if test_langfuse_connection(config):\n        print(\"✓ Langfuse connection successful\")\n    else:\n        raise RuntimeError(\"Failed to connect to Langfuse. Check your configuration and network.\")\n    \n    print(\"🎉 PocketFlow tracing setup complete!\")\n    return config\n\n\ndef test_langfuse_connection(config: TracingConfig) -> bool:\n    \"\"\"\n    Test connection to Langfuse.\n    \n    Args:\n        config: TracingConfig instance.\n        \n    Returns:\n        True if connection successful, False otherwise.\n    \"\"\"\n    try:\n        # Create a test tracer\n        tracer = LangfuseTracer(config)\n        \n        if not tracer.client:\n            return False\n        \n        # Try to start and end a test trace\n        trace_id = tracer.start_trace(\"test_connection\", {\"test\": True})\n        if trace_id:\n            tracer.end_trace({\"test\": \"completed\"}, \"success\")\n            tracer.flush()\n            return True\n        \n        return False\n        \n    except Exception as e:\n        if config.debug:\n            print(f\"Connection test failed: {e}\")\n        return False\n\n\ndef print_configuration_help():\n    \"\"\"Print help information for configuring tracing.\"\"\"\n    print(\"\"\"\n🔧 PocketFlow Tracing Configuration Help\n\nTo use PocketFlow tracing, you need to configure Langfuse credentials.\n\n1. Create or update your .env file with:\n\nLANGFUSE_SECRET_KEY=your-secret-key\nLANGFUSE_PUBLIC_KEY=your-public-key\nLANGFUSE_HOST=your-langfuse-host\nPOCKETFLOW_TRACING_DEBUG=true\n\n2. Optional configuration:\n\nPOCKETFLOW_TRACE_INPUTS=true\nPOCKETFLOW_TRACE_OUTPUTS=true\nPOCKETFLOW_TRACE_PREP=true\nPOCKETFLOW_TRACE_EXEC=true\nPOCKETFLOW_TRACE_POST=true\nPOCKETFLOW_TRACE_ERRORS=true\nPOCKETFLOW_SESSION_ID=your-session-id\nPOCKETFLOW_USER_ID=your-user-id\n\n3. Install required packages:\n\npip install -r requirements.txt\n\n4. Test your setup:\n\npython -c \"from utils import setup_tracing; setup_tracing()\"\n\"\"\")\n\n\nif __name__ == \"__main__\":\n    \"\"\"Command-line interface for setup and testing.\"\"\"\n    import argparse\n    \n    parser = argparse.ArgumentParser(description=\"PocketFlow Tracing Setup\")\n    parser.add_argument(\"--test\", action=\"store_true\", help=\"Test Langfuse connection\")\n    parser.add_argument(\"--help-config\", action=\"store_true\", help=\"Show configuration help\")\n    parser.add_argument(\"--env-file\", type=str, help=\"Path to .env file\")\n    \n    args = parser.parse_args()\n    \n    if args.help_config:\n        print_configuration_help()\n        sys.exit(0)\n    \n    if args.test:\n        try:\n            config = setup_tracing(args.env_file)\n            print(\"\\n✅ All tests passed! Your tracing setup is ready.\")\n        except Exception as e:\n            print(f\"\\n❌ Setup failed: {e}\")\n            print(\"\\nFor help with configuration, run:\")\n            print(\"python utils/setup.py --help-config\")\n            sys.exit(1)\n    else:\n        print_configuration_help()\n"
  },
  {
    "path": "cookbook/pocketflow-visualization/README.md",
    "content": "# PocketFlow Visualization\n\nThis directory contains tools for visualizing PocketFlow workflow graphs using interactive D3.js visualizations.\n\n## Overview\n\nThe visualization tools allow you to:\n\n1. View PocketFlow nodes and flows as an interactive graph\n2. See how different flows connect to each other\n3. Understand the relationships between nodes within flows\n\n## Features\n\n- **Interactive Graph**: Nodes can be dragged to reorganize the layout\n- **Group Visualization**: Flows are displayed as groups with dashed borders\n- **Inter-Group Links**: Connections between flows are shown as dashed lines connecting group boundaries\n- **Action Labels**: Edge labels show the actions that trigger transitions between nodes\n\n## Requirements\n\n- Python 3.6 or higher\n- Modern web browser (Chrome, Firefox, Edge) for viewing the visualizations\n\n## Usage\n\n### 1. Basic Visualization\n\nTo visualize a PocketFlow graph, you can use the `visualize_flow` function in `visualize.py`:\n\n```python\nfrom visualize import visualize_flow\nfrom your_flow_module import your_flow\n\n# Generate visualization\nvisualize_flow(your_flow, \"Your Flow Name\")\n```\n\nThis will:\n1. Print a Mermaid diagram to the console\n2. Generate a D3.js visualization in the `./viz` directory\n\n### 2. Running the Example\n\nThe included example shows an order processing pipeline with payment, inventory, and shipping flows:\n\n```bash\n# Navigate to the directory\ncd cookbook/pocketflow-minimal-flow2flow\n\n# Run the visualization script\npython visualize.py\n```\n\nThis will generate visualization files in the `./viz` directory.\n\n### 3. Viewing the Visualization\n\nAfter running the script:\n\n1. Host with \n   ```\n   cd ./viz/\n   ```\n\n2. Interact with the visualization:\n   - **Drag nodes** to reorganize\n   - **Hover over nodes** to see node names\n   - **Observe connections** between nodes and flows\n\n## Customizing the Visualization\n\n### Adjusting Layout Parameters\n\nYou can adjust the force simulation parameters in `visualize.py` to change how nodes and groups are positioned:\n\n```javascript\n// Create a force simulation\nconst simulation = d3.forceSimulation(data.nodes)\n    // Controls the distance between connected nodes\n    .force(\"link\", d3.forceLink(data.links).id(d => d.id).distance(100))\n    // Controls how nodes repel each other - lower values bring nodes closer\n    .force(\"charge\", d3.forceManyBody().strength(-30))\n    // Centers the entire graph in the SVG\n    .force(\"center\", d3.forceCenter(width / 2, height / 2))\n    // Prevents nodes from overlapping - acts like a minimum distance\n    .force(\"collide\", d3.forceCollide().radius(50));\n```\n\n### Styling\n\nAdjust the CSS styles in the HTML template inside `create_d3_visualization` function to change colors, shapes, and other visual properties.\n\n## How It Works\n\nThe visualization process consists of three main steps:\n\n1. **Flow to JSON Conversion**: The `flow_to_json` function traverses the PocketFlow graph and converts it to a structure with nodes, links, and group information.\n\n2. **D3.js Visualization**: The JSON data is used to create an interactive D3.js visualization with:\n   - Nodes represented as circles\n   - Flows represented as dashed rectangles containing nodes\n   - Links showing connections within and between flows\n\n3. **Group Boundary Connections**: The visualization calculates intersection points with group boundaries to ensure inter-group links connect at the borders rather than centers.\n\n## Extending the Visualization\n\nYou can extend the visualization tools by:\n\n1. Adding new node shapes\n2. Implementing additional layout algorithms\n3. Adding tooltips with more detailed information\n4. Creating animation for flow execution\n\n## Troubleshooting\n\nIf you encounter any issues:\n\n- Make sure your flow objects are properly constructed with nodes connected correctly\n- Check the browser console for any JavaScript errors\n- Verify that the generated JSON data structure matches what you expect\n\n## Example Output\n\nThe visualization displays:\n- Payment processing flow nodes\n- Inventory management flow nodes\n- Shipping flow nodes\n- Group boundaries around each flow\n- Connections between flows (Payment → Inventory → Shipping)\n"
  },
  {
    "path": "cookbook/pocketflow-visualization/async_flow.py",
    "content": "from pocketflow import AsyncNode, AsyncFlow\nimport asyncio\n\n\n# Define Payment Nodes\nclass ValidatePayment(AsyncNode):\n    async def exec_async(self, prep_res):\n        print(\"1.1.Validating payment...\")\n        return \"Payment validated successfully\"\n\n    async def post_async(self, shared, prep_res, exec_res):\n        shared[\"payment_status\"] = exec_res\n        return \"default\"\n\n\nclass ProcessPayment(AsyncNode):\n    async def exec_async(self, prep_res):\n        print(\"1.2.Processing payment...\")\n        return \"Payment processed successfully\"\n\n    async def post_async(self, shared, prep_res, exec_res):\n        shared[\"payment_result\"] = exec_res\n        return \"default\"\n\n\nclass PaymentConfirmation(AsyncNode):\n    async def exec_async(self, prep_res):\n        print(\"1.3.Confirming payment...\")\n        return \"Payment confirmed\"\n\n    async def post_async(self, shared, prep_res, exec_res):\n        shared[\"payment_confirmation\"] = exec_res\n        return \"default\"\n\n\n# Define Inventory Nodes\nclass CheckStock(AsyncNode):\n    async def exec_async(self, prep_res):\n        print(\"2.1.Checking inventory stock...\")\n        return \"Stock available\"\n\n    async def post_async(self, shared, prep_res, exec_res):\n        shared[\"stock_status\"] = exec_res\n        return \"default\"\n\n\nclass ReserveItems(AsyncNode):\n    async def exec_async(self, prep_res):\n        print(\"2.2.Reserving items...\")\n        return \"Items reserved\"\n\n    async def post_async(self, shared, prep_res, exec_res):\n        shared[\"reservation_status\"] = exec_res\n        return \"default\"\n\n\nclass UpdateInventory(AsyncNode):\n    async def exec_async(self, prep_res):\n        print(\"2.3. Updating inventory...\")\n        return \"Inventory updated\"\n\n    async def post_async(self, shared, prep_res, exec_res):\n        shared[\"inventory_update\"] = exec_res\n        return \"default\"\n\n\n# Define Shipping Nodes\nclass CreateLabel(AsyncNode):\n    async def exec_async(self, prep_res):\n        print(\"3.1 Creating shipping label...\")\n        return \"Shipping label created\"\n\n    async def post_async(self, shared, prep_res, exec_res):\n        shared[\"shipping_label\"] = exec_res\n        return \"default\"\n\n\nclass AssignCarrier(AsyncNode):\n    async def exec_async(self, prep_res):\n        print(\"3.2 Assigning carrier...\")\n        return \"Carrier assigned\"\n\n    async def post_async(self, shared, prep_res, exec_res):\n        shared[\"carrier\"] = exec_res\n        return \"default\"\n\n\nclass SchedulePickup(AsyncNode):\n    async def exec_async(self, prep_res):\n        print(\"3.3 Scheduling pickup...\")\n        return \"Pickup scheduled\"\n\n    async def post_async(self, shared, prep_res, exec_res):\n        shared[\"pickup_status\"] = exec_res\n        return \"default\"\n\n\n# Create node instances\nvalidate_payment = ValidatePayment()\nprocess_payment = ProcessPayment()\npayment_confirmation = PaymentConfirmation()\n\ncheck_stock = CheckStock()\nreserve_items = ReserveItems()\nupdate_inventory = UpdateInventory()\n\ncreate_label = CreateLabel()\nassign_carrier = AssignCarrier()\nschedule_pickup = SchedulePickup()\n\n# Payment processing sub-flow\nvalidate_payment >> process_payment >> payment_confirmation\npayment_flow = AsyncFlow(start=validate_payment)\n\n# Inventory sub-flow\ncheck_stock >> reserve_items >> update_inventory\ninventory_flow = AsyncFlow(start=check_stock)\n\n# Shipping sub-flow\ncreate_label >> assign_carrier >> schedule_pickup\nshipping_flow = AsyncFlow(start=create_label)\n\n# Connect the flows into a main order pipeline\npayment_flow >> inventory_flow >> shipping_flow\n# payment_flow >> inventory_flow >> create_label\n# payment_flow >> inventory_flow >> assign_carrier\n\n\n# Create the master flow\nclass OrderFlow(AsyncFlow):\n    pass\n\n\norder_pipeline = OrderFlow(start=payment_flow)\n\n# Create shared data structure\nshared_data = {\n    \"order_id\": \"ORD-12345\",\n    \"customer\": \"John Doe\",\n    \"items\": [\n        {\"id\": \"ITEM-001\", \"name\": \"Smartphone\", \"price\": 999.99, \"quantity\": 1},\n        {\"id\": \"ITEM-002\", \"name\": \"Phone case\", \"price\": 29.99, \"quantity\": 1},\n    ],\n    \"shipping_address\": {\n        \"street\": \"123 Main St\",\n        \"city\": \"Anytown\",\n        \"state\": \"CA\",\n        \"zip\": \"12345\",\n    },\n}\n\n\n# Run the entire pipeline asynchronously\nasync def main():\n    await order_pipeline.run_async(shared_data)\n\n    # Print final status\n    print(\"\\nOrder processing completed!\")\n    print(f\"Payment: {shared_data.get('payment_confirmation')}\")\n    print(f\"Inventory: {shared_data.get('inventory_update')}\")\n    print(f\"Shipping: {shared_data.get('pickup_status')}\")\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "cookbook/pocketflow-visualization/async_loop_flow.py",
    "content": "from async_flow import *\nfrom pocketflow import Flow, AsyncParallelBatchNode, Node\n\n# Create node instances\nvalidate_payment = ValidatePayment()\nprocess_payment = ProcessPayment()\npayment_confirmation = PaymentConfirmation()\n\ncheck_stock = CheckStock()\nreserve_items = ReserveItems()\nupdate_inventory = UpdateInventory()\n\ncreate_label = CreateLabel()\nassign_carrier = AssignCarrier()\nschedule_pickup = SchedulePickup()\n\n# Payment processing sub-flow\nvalidate_payment >> process_payment\nvalidate_payment - \"out_of_stock\" >> validate_payment  # 循环重试\nprocess_payment - 'something fail' >> validate_payment\nprocess_payment - 'pass' >> payment_confirmation\npayment_flow = AsyncFlow(start=validate_payment)\n\n# Inventory sub-flow\ncheck_stock >> reserve_items >> update_inventory\ninventory_flow = AsyncFlow(start=check_stock)\n\n# Shipping sub-flow\ncreate_label >> assign_carrier >> schedule_pickup\nshipping_flow = AsyncFlow(start=create_label)\n\n# Connect the flows into a main order pipeline\npayment_flow >> inventory_flow >> shipping_flow\n# payment_flow >> inventory_flow >> create_label\n# payment_flow >> inventory_flow >> assign_carrier\n\n\n# Create the master flow\nclass OrderFlow(AsyncFlow):\n    pass\n\norder_pipeline = OrderFlow(start=payment_flow)\n\n# Create shared data structure\nshared_data = {\n    \"order_id\": \"ORD-12345\",\n    \"customer\": \"John Doe\",\n    \"items\": [\n        {\"id\": \"ITEM-001\", \"name\": \"Smartphone\", \"price\": 999.99, \"quantity\": 1},\n        {\"id\": \"ITEM-002\", \"name\": \"Phone case\", \"price\": 29.99, \"quantity\": 1},\n    ],\n    \"shipping_address\": {\n        \"street\": \"123 Main St\",\n        \"city\": \"Anytown\",\n        \"state\": \"CA\",\n        \"zip\": \"12345\",\n    },\n}\n\n\n# Run the entire pipeline asynchronously\nasync def main():\n    await order_pipeline.run_async(shared_data)\n\n    # Print final status\n    print(\"\\nOrder processing completed!\")\n    print(f\"Payment: {shared_data.get('payment_confirmation')}\")\n    print(f\"Inventory: {shared_data.get('inventory_update')}\")\n    print(f\"Shipping: {shared_data.get('pickup_status')}\")\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  },
  {
    "path": "cookbook/pocketflow-visualization/visualize.py",
    "content": "# %%\n\nimport json\nimport os\nimport http.server\nimport socketserver\nimport threading\nimport webbrowser\nimport time\nimport socket\nimport importlib\nimport sys\nfrom pathlib import Path\nfrom typing import Any, Optional, Tuple, Union\n\nfrom pocketflow import Flow\n\nfrom async_flow import order_pipeline\n\n\ndef build_mermaid(start):\n    ids, visited, lines = {}, set(), [\"graph LR\"]\n    ctr = 1\n\n    def get_id(n):\n        nonlocal ctr\n        return (\n            ids[n] if n in ids else (ids.setdefault(n, f\"N{ctr}\"), (ctr := ctr + 1))[0]\n        )\n\n    def link(a, b, action=None):\n        if action:\n            lines.append(f\"    {a} -->|{action}| {b}\")\n        else:\n            lines.append(f\"    {a} --> {b}\")\n\n    def walk(node, parent=None, action=None):\n        if node in visited:\n            return parent and link(parent, get_id(node), action)\n        visited.add(node)\n        if isinstance(node, Flow):\n            node.start_node and parent and link(parent, get_id(node.start_node), action)\n            lines.append(\n                f\"\\n    subgraph sub_flow_{get_id(node)}[{type(node).__name__}]\"\n            )\n            node.start_node and walk(node.start_node)\n            for act, nxt in node.successors.items():\n                node.start_node and walk(nxt, get_id(node.start_node), act) or (\n                    parent and link(parent, get_id(nxt), action)\n                ) or walk(nxt, None, act)\n            lines.append(\"    end\\n\")\n        else:\n            lines.append(f\"    {(nid := get_id(node))}['{type(node).__name__}']\")\n            parent and link(parent, nid, action)\n            [walk(nxt, nid, act) for act, nxt in node.successors.items()]\n\n    walk(start)\n    return \"\\n\".join(lines)\n\n\ndef flow_to_json(start):\n    \"\"\"Convert a flow to JSON format suitable for D3.js visualization.\n\n    This function walks through the flow graph and builds a structure with:\n    - nodes: All non-Flow nodes with their group memberships\n    - links: Connections between nodes within the same group\n    - group_links: Connections between different groups (for inter-flow connections)\n    - flows: Flow information for group labeling\n\n    Returns:\n        dict: A JSON-serializable dictionary with 'nodes' and 'links' arrays.\n    \"\"\"\n    nodes = []\n    links = []\n    group_links = []  # For connections between groups (Flow to Flow)\n    ids = {}\n    node_types = {}\n    flow_nodes = {}  # Keep track of flow nodes\n    ctr = 1\n    visited = set()\n\n    def get_id(n):\n        nonlocal ctr\n        if n not in ids:\n            ids[n] = ctr\n            node_types[ctr] = type(n).__name__\n            if isinstance(n, Flow):\n                flow_nodes[ctr] = n  # Store flow reference\n            ctr += 1\n        return ids[n]\n\n    def walk(node, parent=None, group=None, parent_group=None, action=None):\n        \"\"\"Recursively walk the flow graph to build the visualization data.\n\n        Args:\n            node: Current node being processed\n            parent: ID of the parent node that connects to this node\n            group: Group (Flow) ID this node belongs to\n            parent_group: Group ID of the parent node\n            action: Action label on the edge from parent to this node\n        \"\"\"\n        node_id = get_id(node)\n        if (node_id, action) in visited:\n            return\n        visited.add((node_id, action))\n\n        # Add node if not already in nodes list and not a Flow\n        if not any(n[\"id\"] == node_id for n in nodes) and not isinstance(node, Flow):\n            node_data = {\n                \"id\": node_id,\n                \"name\": node_types[node_id],\n                \"group\": group or 0,  # Default group\n            }\n            nodes.append(node_data)\n\n        # Add link from parent if exists\n        if parent and not isinstance(node, Flow):\n            links.append(\n                {\"source\": parent, \"target\": node_id, \"action\": action or \"default\"}\n            )\n\n        # Process different types of nodes\n        if isinstance(node, Flow):\n            # This is a Flow node - it becomes a group container\n            flow_group = node_id  # Use flow's ID as group for contained nodes\n\n            # Add a group-to-group link if this flow has a parent group\n            # This creates connections between nested flows\n            if parent_group is not None and parent_group != flow_group:\n                # Check if this link already exists\n                if not any(\n                    l[\"source\"] == parent_group and l[\"target\"] == flow_group\n                    for l in group_links\n                ):\n                    group_links.append(\n                        {\n                            \"source\": parent_group,\n                            \"target\": flow_group,\n                            \"action\": action or \"default\",\n                        }\n                    )\n\n            if node.start_node:\n                # Process the start node of this flow\n                walk(node.start_node, parent, flow_group, parent_group, action)\n\n                # Process successors of the flow's start node\n                for next_action, nxt in node.successors.items():\n                    walk(\n                        nxt,\n                        get_id(node.start_node),\n                        flow_group,\n                        parent_group,\n                        next_action,\n                    )\n        else:\n            # Process successors for regular nodes\n            for next_action, nxt in node.successors.items():\n                if isinstance(nxt, Flow):\n                    # This node connects to a flow - track the group relationship\n                    flow_group_id = get_id(nxt)\n                    walk(nxt, node_id, None, group, next_action)\n                else:\n                    # Regular node-to-node connection\n                    walk(nxt, node_id, group, parent_group, next_action)\n\n    # Start the traversal\n    walk(start)\n\n    # Post-processing: Generate group links based on node connections between different groups\n    # This ensures that when nodes in different groups are connected, we show a group-to-group\n    # link rather than a direct node-to-node link\n    node_groups = {n[\"id\"]: n[\"group\"] for n in nodes}\n    filtered_links = []\n\n    for link in links:\n        source_id = link[\"source\"]\n        target_id = link[\"target\"]\n        source_group = node_groups.get(source_id, 0)\n        target_group = node_groups.get(target_id, 0)\n\n        # If source and target are in different groups and both groups are valid\n        if source_group != target_group and source_group > 0 and target_group > 0:\n            # Add to group links if not already there\n            # This creates the dashed lines connecting group boxes\n            if not any(\n                gl[\"source\"] == source_group and gl[\"target\"] == target_group\n                for gl in group_links\n            ):\n                group_links.append(\n                    {\n                        \"source\": source_group,\n                        \"target\": target_group,\n                        \"action\": link[\"action\"],\n                    }\n                )\n            # Skip adding this link to filtered_links - we don't want direct node connections across groups\n        else:\n            # Keep links within the same group\n            filtered_links.append(link)\n\n    return {\n        \"nodes\": nodes,\n        \"links\": filtered_links,  # Use filtered links instead of all links\n        \"group_links\": group_links,\n        \"flows\": {str(k): v.__class__.__name__ for k, v in flow_nodes.items()},\n    }\n\n\ndef create_d3_visualization(\n    json_data,\n    output_dir=\"./viz\",\n    filename=\"flow_viz\",\n    html_title=\"PocketFlow Visualization\",\n):\n    \"\"\"Create a D3.js visualization from JSON data.\n\n    Args:\n        json_data: The JSON data for the visualization\n        output_dir: Directory to save the files\n        filename: Base filename (without extension)\n        html_title: Title for the HTML page\n\n    Returns:\n        str: Path to the HTML file\n    \"\"\"\n    # Create output directory if it doesn't exist\n    os.makedirs(output_dir, exist_ok=True)\n\n    # Save JSON data to file\n    json_path = os.path.join(output_dir, f\"{filename}.json\")\n    with open(json_path, \"w\") as f:\n        json.dump(json_data, f, indent=2)\n\n    # Create HTML file with D3.js visualization\n    html_content = r\"\"\"<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>TITLE_PLACEHOLDER</title>\n    <script src=\"https://d3js.org/d3.v7.min.js\"></script>\n    <style>\n        body {\n            font-family: Arial, sans-serif;\n            margin: 0;\n            padding: 0;\n            overflow: hidden;\n        }\n        svg {\n            width: 100vw;\n            height: 100vh;\n        }\n        .links path {\n            fill: none;\n            stroke: #999;\n            stroke-opacity: 0.6;\n            stroke-width: 1.5px;\n        }\n        .group-links path {\n            fill: none;\n            stroke: #333;\n            stroke-opacity: 0.8;\n            stroke-width: 2px;\n            stroke-dasharray: 5,5;\n        }\n        .nodes circle {\n            stroke: #fff;\n            stroke-width: 1.5px;\n        }\n        .node-labels {\n            font-size: 12px;\n            pointer-events: none;\n        }\n        .link-labels {\n            font-size: 10px;\n            fill: #666;\n            pointer-events: none;\n        }\n        .group-link-labels {\n            font-size: 11px;\n            font-weight: bold;\n            fill: #333;\n            pointer-events: none;\n        }\n        .group-container {\n            stroke: #333;\n            stroke-width: 1.5px;\n            stroke-dasharray: 5,5;\n            fill: rgba(200, 200, 200, 0.1);\n            rx: 10;\n            ry: 10;\n        }\n        .group-label {\n            font-size: 14px;\n            font-weight: bold;\n            pointer-events: none;\n        }\n    </style>\n</head>\n<body>\n    <svg id=\"graph\"></svg>\n    <script>\n        // Load data from file\n        d3.json(\"FILENAME_PLACEHOLDER.json\").then(data => {\n            const svg = d3.select(\"#graph\");\n            const width = window.innerWidth;\n            const height = window.innerHeight;\n            \n            // Define arrow markers for links\n            svg.append(\"defs\").append(\"marker\")\n                .attr(\"id\", \"arrowhead\")\n                .attr(\"viewBox\", \"0 -5 10 10\")\n                .attr(\"refX\", 25) // Position the arrow away from the target node\n                .attr(\"refY\", 0)\n                .attr(\"orient\", \"auto\")\n                .attr(\"markerWidth\", 6)\n                .attr(\"markerHeight\", 6)\n                .attr(\"xoverflow\", \"visible\")\n                .append(\"path\")\n                .attr(\"d\", \"M 0,-5 L 10,0 L 0,5\")\n                .attr(\"fill\", \"#999\");\n                \n            // Define thicker arrow markers for group links\n            svg.append(\"defs\").append(\"marker\")\n                .attr(\"id\", \"group-arrowhead\")\n                .attr(\"viewBox\", \"0 -5 10 10\")\n                .attr(\"refX\", 3) // Position at the boundary of the group\n                .attr(\"refY\", 0)\n                .attr(\"orient\", \"auto\")\n                .attr(\"markerWidth\", 8)\n                .attr(\"markerHeight\", 8)\n                .attr(\"xoverflow\", \"visible\")\n                .append(\"path\")\n                .attr(\"d\", \"M 0,-5 L 10,0 L 0,5\")\n                .attr(\"fill\", \"#333\");\n            \n            // Color scale for node groups\n            const color = d3.scaleOrdinal(d3.schemeCategory10);\n            \n            // Process the data to identify groups\n            const groups = {};\n            data.nodes.forEach(node => {\n                if (node.group > 0) {\n                    if (!groups[node.group]) {\n                        // Use the flow name instead of generic \"Group X\"\n                        const flowName = data.flows && data.flows[node.group] ? data.flows[node.group] : `Flow ${node.group}`;\n                        groups[node.group] = {\n                            id: node.group,\n                            name: flowName,\n                            nodes: [],\n                            x: 0,\n                            y: 0,\n                            width: 0,\n                            height: 0\n                        };\n                    }\n                    groups[node.group].nodes.push(node);\n                }\n            });\n            \n            // Create a force simulation\n            const simulation = d3.forceSimulation(data.nodes)\n                // Controls the distance between connected nodes\n                .force(\"link\", d3.forceLink(data.links).id(d => d.id).distance(100))\n                // Controls how nodes repel each other - lower values bring nodes closer\n                .force(\"charge\", d3.forceManyBody().strength(-30))\n                // Centers the entire graph in the SVG\n                .force(\"center\", d3.forceCenter(width / 2, height / 2))\n                // Prevents nodes from overlapping - acts like a minimum distance\n                .force(\"collide\", d3.forceCollide().radius(50));\n            \n            // Group forces - create a force to keep nodes in the same group closer together\n            // This creates the effect of nodes clustering within their group boxes\n            const groupForce = alpha => {\n                for (let i = 0; i < data.nodes.length; i++) {\n                    const node = data.nodes[i];\n                    if (node.group > 0) {\n                        const group = groups[node.group];\n                        if (group && group.nodes.length > 1) {\n                            // Calculate center of group\n                            let centerX = 0, centerY = 0;\n                            group.nodes.forEach(n => {\n                                centerX += n.x || 0;\n                                centerY += n.y || 0;\n                            });\n                            centerX /= group.nodes.length;\n                            centerY /= group.nodes.length;\n                            \n                            // Move nodes toward center\n                            const k = alpha * 0.3; // Increased from 0.1 to 0.3\n                            node.vx += (centerX - node.x) * k;\n                            node.vy += (centerY - node.y) * k;\n                        }\n                    }\n                }\n            };\n            \n            // Additional force to position groups in a more organized layout (like in the image)\n            // This arranges the groups horizontally/vertically based on their connections\n            const groupLayoutForce = alpha => {\n                // Get group centers\n                const groupCenters = Object.values(groups).map(g => {\n                    return { id: g.id, cx: 0, cy: 0 };\n                });\n                \n                // Calculate current center positions\n                Object.values(groups).forEach(g => {\n                    if (g.nodes.length > 0) {\n                        let cx = 0, cy = 0;\n                        g.nodes.forEach(n => {\n                            cx += n.x || 0;\n                            cy += n.y || 0;\n                        });\n                        \n                        const groupCenter = groupCenters.find(gc => gc.id === g.id);\n                        if (groupCenter) {\n                            groupCenter.cx = cx / g.nodes.length;\n                            groupCenter.cy = cy / g.nodes.length;\n                        }\n                    }\n                });\n                \n                // Apply forces to position groups\n                const k = alpha * 0.05;\n                \n                // Try to position groups in a more structured way\n                // Adjust these values to change the overall layout\n                for (let i = 0; i < data.group_links.length; i++) {\n                    const link = data.group_links[i];\n                    const source = groupCenters.find(g => g.id === link.source);\n                    const target = groupCenters.find(g => g.id === link.target);\n                    \n                    if (source && target) {\n                        // Add a horizontal force to align groups\n                        const desiredDx = 300; // Desired horizontal distance between linked groups\n                        const dx = target.cx - source.cx;\n                        const diff = desiredDx - Math.abs(dx);\n                        \n                        // Apply forces to group nodes\n                        groups[source.id].nodes.forEach(n => {\n                            if (dx > 0) {\n                                n.vx -= diff * k;\n                            } else {\n                                n.vx += diff * k;\n                            }\n                        });\n                        \n                        groups[target.id].nodes.forEach(n => {\n                            if (dx > 0) {\n                                n.vx += diff * k;\n                            } else {\n                                n.vx -= diff * k;\n                            }\n                        });\n                    }\n                }\n            };\n            \n            simulation.force(\"group\", groupForce);\n            simulation.force(\"groupLayout\", groupLayoutForce);\n            \n            // Create links with arrow paths instead of lines\n            const link = svg.append(\"g\")\n                .attr(\"class\", \"links\")\n                .selectAll(\"path\")\n                .data(data.links)\n                .enter()\n                .append(\"path\")\n                .attr(\"stroke-width\", 2)\n                .attr(\"stroke\", \"#999\")\n                .attr(\"marker-end\", \"url(#arrowhead)\");  // Add the arrowhead marker\n            \n            // Create group containers (drawn before nodes)\n            const groupContainers = svg.append(\"g\")\n                .attr(\"class\", \"groups\")\n                .selectAll(\"rect\")\n                .data(Object.values(groups))\n                .enter()\n                .append(\"rect\")\n                .attr(\"class\", \"group-container\")\n                .attr(\"fill\", d => d3.color(color(d.id)).copy({opacity: 0.2}));\n            \n            // Create group links between flows\n            const groupLink = svg.append(\"g\")\n                .attr(\"class\", \"group-links\")\n                .selectAll(\"path\")\n                .data(data.group_links || [])\n                .enter()\n                .append(\"path\")\n                .attr(\"stroke-width\", 2)\n                .attr(\"stroke\", \"#333\")\n                .attr(\"marker-end\", \"url(#group-arrowhead)\");\n                \n            // Create group link labels\n            const groupLinkLabel = svg.append(\"g\")\n                .attr(\"class\", \"group-link-labels\")\n                .selectAll(\"text\")\n                .data(data.group_links || [])\n                .enter()\n                .append(\"text\")\n                .text(d => d.action)\n                .attr(\"font-size\", \"11px\")\n                .attr(\"font-weight\", \"bold\")\n                .attr(\"fill\", \"#333\");\n            \n            // Create group labels\n            const groupLabels = svg.append(\"g\")\n                .attr(\"class\", \"group-labels\")\n                .selectAll(\"text\")\n                .data(Object.values(groups))\n                .enter()\n                .append(\"text\")\n                .attr(\"class\", \"group-label\")\n                .text(d => d.name)  // Now using the proper flow name\n                .attr(\"fill\", d => d3.color(color(d.id)).darker());\n            \n            // Create link labels\n            const linkLabel = svg.append(\"g\")\n                .attr(\"class\", \"link-labels\")\n                .selectAll(\"text\")\n                .data(data.links)\n                .enter()\n                .append(\"text\")\n                .text(d => d.action)\n                .attr(\"font-size\", \"10px\")\n                .attr(\"fill\", \"#666\");\n            \n            // Create nodes\n            const node = svg.append(\"g\")\n                .attr(\"class\", \"nodes\")\n                .selectAll(\"circle\")\n                .data(data.nodes)\n                .enter()\n                .append(\"circle\")\n                .attr(\"r\", 15)\n                .attr(\"fill\", d => color(d.group))\n                .call(d3.drag()\n                    .on(\"start\", dragstarted)\n                    .on(\"drag\", dragged)\n                    .on(\"end\", dragended));\n            \n            // Create node labels\n            const nodeLabel = svg.append(\"g\")\n                .attr(\"class\", \"node-labels\")\n                .selectAll(\"text\")\n                .data(data.nodes)\n                .enter()\n                .append(\"text\")\n                .text(d => d.name)\n                .attr(\"text-anchor\", \"middle\")\n                .attr(\"dy\", 25);\n            \n            // Add tooltip on hover\n            node.append(\"title\")\n                .text(d => d.name);\n            \n            // Update positions on each tick\n            simulation.on(\"tick\", () => {\n                // Update links with curved paths for bidirectional connections\n                link.attr(\"d\", d => {\n                    // Handle self-referencing links with a water-drop shape\n                    if (d.source === d.target) {\n                        const nodeX = d.source.x;\n                        const nodeY = d.source.y;\n                        const offsetX = 40;\n                        const offsetY = 10;\n                        const controlOffset = 50;\n                        \n                        // Create a water-drop shaped path\n                        return `M ${nodeX},${nodeY - 5}\n                                C ${nodeX + controlOffset},${nodeY - 30} \n                                  ${nodeX + offsetX},${nodeY + offsetY} \n                                  ${nodeX},${nodeY}`;\n                    }\n                    \n                    // Check if there's a reverse connection\n                    const isReverse = data.links.some(l => \n                        l.source === d.target && l.target === d.source\n                    );\n                    \n                    // If it's part of a bidirectional connection, curve the path\n                    if (isReverse) {\n                        const dx = d.target.x - d.source.x;\n                        const dy = d.target.y - d.source.y;\n                        const dr = Math.sqrt(dx * dx + dy * dy) * 0.9;\n                        \n                        return `M${d.source.x},${d.source.y}A${dr},${dr} 0 0,1 ${d.target.x},${d.target.y}`;\n                    }\n                    \n                    // For unidirectional connections, use straight lines\n                    return `M${d.source.x},${d.source.y} L${d.target.x},${d.target.y}`;\n                });\n                \n                // Update nodes\n                node\n                    .attr(\"cx\", d => d.x)\n                    .attr(\"cy\", d => d.y);\n                \n                // Update node labels\n                nodeLabel\n                    .attr(\"x\", d => d.x)\n                    .attr(\"y\", d => d.y);\n                \n                // Position link labels with offset for bidirectional connections\n                linkLabel.attr(\"x\", d => {\n                    // Handle self-referencing links\n                    if (d.source === d.target) {\n                        return d.source.x + 30;\n                    }\n                    \n                    // Check if there's a reverse connection\n                    const reverseLink = data.links.find(l => \n                        l.source === d.target && l.target === d.source\n                    );\n                    \n                    // If it's part of a bidirectional connection, offset the label\n                    if (reverseLink) {\n                        const dx = d.target.x - d.source.x;\n                        const dy = d.target.y - d.source.y;\n                        // Calculate perpendicular offset\n                        const length = Math.sqrt(dx * dx + dy * dy);\n                        const offsetX = -dy / length * 10; // Perpendicular offset\n                        \n                        return (d.source.x + d.target.x) / 2 + offsetX;\n                    }\n                    \n                    // For unidirectional connections, use midpoint\n                    return (d.source.x + d.target.x) / 2;\n                })\n                .attr(\"y\", d => {\n                    // Handle self-referencing links\n                    if (d.source === d.target) {\n                        return d.source.y;\n                    }\n                    \n                    // Check if there's a reverse connection\n                    const reverseLink = data.links.find(l => \n                        l.source === d.target && l.target === d.source\n                    );\n                    \n                    // If it's part of a bidirectional connection, offset the label\n                    if (reverseLink) {\n                        const dx = d.target.x - d.source.x;\n                        const dy = d.target.y - d.source.y;\n                        // Calculate perpendicular offset\n                        const length = Math.sqrt(dx * dx + dy * dy);\n                        const offsetY = dx / length * 10; // Perpendicular offset\n                        \n                        return (d.source.y + d.target.y) / 2 + offsetY;\n                    }\n                    \n                    // For unidirectional connections, use midpoint\n                    return (d.source.y + d.target.y) / 2;\n                });\n                \n                // Update group containers\n                groupContainers.each(function(d) {\n                    // If there are nodes in this group\n                    if (d.nodes.length > 0) {\n                        let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n                        \n                        // Find the bounding box for all nodes in the group\n                        d.nodes.forEach(n => {\n                            minX = Math.min(minX, n.x - 30);\n                            minY = Math.min(minY, n.y - 30);\n                            maxX = Math.max(maxX, n.x + 30);\n                            maxY = Math.max(maxY, n.y + 40); // Extra space for labels\n                        });\n                        \n                        // Add padding\n                        const padding = 20;\n                        minX -= padding;\n                        minY -= padding;\n                        maxX += padding;\n                        maxY += padding;\n                        \n                        // Save group dimensions\n                        d.x = minX;\n                        d.y = minY;\n                        d.width = maxX - minX;\n                        d.height = maxY - minY;\n                        d.centerX = minX + d.width / 2;\n                        d.centerY = minY + d.height / 2;\n                        \n                        // Set position and size of the group container\n                        d3.select(this)\n                            .attr(\"x\", minX)\n                            .attr(\"y\", minY)\n                            .attr(\"width\", d.width)\n                            .attr(\"height\", d.height);\n                        \n                        // Update group label position (top-left of group)\n                        groupLabels.filter(g => g.id === d.id)\n                            .attr(\"x\", minX + 10)\n                            .attr(\"y\", minY + 20);\n                    }\n                });\n                \n                // Update group links between flows\n                groupLink.attr(\"d\", d => {\n                    const sourceGroup = groups[d.source];\n                    const targetGroup = groups[d.target];\n                    \n                    if (!sourceGroup || !targetGroup) return \"\";\n                    \n                    // Find intersection points with group boundaries\n                    // This ensures links connect to the group's border rather than its center\n                    \n                    // Calculate centers of groups\n                    const sx = sourceGroup.centerX;\n                    const sy = sourceGroup.centerY;\n                    const tx = targetGroup.centerX;\n                    const ty = targetGroup.centerY;\n                    \n                    // Calculate angle between centers - used to find intersection points\n                    const angle = Math.atan2(ty - sy, tx - sx);\n                    \n                    // Calculate intersection points with source group borders\n                    // We cast a ray from center in the direction of the target\n                    let sourceX, sourceY;\n                    const cosA = Math.cos(angle);\n                    const sinA = Math.sin(angle);\n                    \n                    // Check intersection with horizontal borders (top and bottom)\n                    const ts_top = (sourceGroup.y - sy) / sinA;\n                    const ts_bottom = (sourceGroup.y + sourceGroup.height - sy) / sinA;\n                    \n                    // Check intersection with vertical borders (left and right)\n                    const ts_left = (sourceGroup.x - sx) / cosA;\n                    const ts_right = (sourceGroup.x + sourceGroup.width - sx) / cosA;\n                    \n                    // Use the closest positive intersection (first hit with the boundary)\n                    let t_source = Infinity;\n                    if (ts_top > 0) t_source = Math.min(t_source, ts_top);\n                    if (ts_bottom > 0) t_source = Math.min(t_source, ts_bottom);\n                    if (ts_left > 0) t_source = Math.min(t_source, ts_left);\n                    if (ts_right > 0) t_source = Math.min(t_source, ts_right);\n                    \n                    // Target group: Find intersection in the opposite direction\n                    // We cast a ray from target center toward the source\n                    let targetX, targetY;\n                    const oppositeAngle = angle + Math.PI;\n                    const cosOpp = Math.cos(oppositeAngle);\n                    const sinOpp = Math.sin(oppositeAngle);\n                    \n                    // Check intersections for target group\n                    const tt_top = (targetGroup.y - ty) / sinOpp;\n                    const tt_bottom = (targetGroup.y + targetGroup.height - ty) / sinOpp;\n                    const tt_left = (targetGroup.x - tx) / cosOpp;\n                    const tt_right = (targetGroup.x + targetGroup.width - tx) / cosOpp;\n                    \n                    // Use the closest positive intersection\n                    let t_target = Infinity;\n                    if (tt_top > 0) t_target = Math.min(t_target, tt_top);\n                    if (tt_bottom > 0) t_target = Math.min(t_target, tt_bottom);\n                    if (tt_left > 0) t_target = Math.min(t_target, tt_left);\n                    if (tt_right > 0) t_target = Math.min(t_target, tt_right);\n                    \n                    // Calculate actual border points using parametric equation:\n                    // point = center + t * direction\n                    if (t_source !== Infinity) {\n                        sourceX = sx + cosA * t_source;\n                        sourceY = sy + sinA * t_source;\n                    } else {\n                        sourceX = sx;\n                        sourceY = sy;\n                    }\n                    \n                    if (t_target !== Infinity) {\n                        targetX = tx + cosOpp * t_target;\n                        targetY = ty + sinOpp * t_target;\n                    } else {\n                        targetX = tx;\n                        targetY = ty;\n                    }\n                    \n                    // Create a straight line between the border points\n                    return `M${sourceX},${sourceY} L${targetX},${targetY}`;\n                });\n                \n                // Update group link labels\n                groupLinkLabel.attr(\"x\", d => {\n                    const sourceGroup = groups[d.source];\n                    const targetGroup = groups[d.target];\n                    if (!sourceGroup || !targetGroup) return 0;\n                    return (sourceGroup.centerX + targetGroup.centerX) / 2;\n                })\n                .attr(\"y\", d => {\n                    const sourceGroup = groups[d.source];\n                    const targetGroup = groups[d.target];\n                    if (!sourceGroup || !targetGroup) return 0;\n                    return (sourceGroup.centerY + targetGroup.centerY) / 2 - 10;\n                });\n            });\n            \n            // Drag functions\n            function dragstarted(event, d) {\n                if (!event.active) simulation.alphaTarget(0.3).restart();\n                d.fx = d.x;\n                d.fy = d.y;\n            }\n            \n            function dragged(event, d) {\n                d.fx = event.x;\n                d.fy = event.y;\n            }\n            \n            function dragended(event, d) {\n                if (!event.active) simulation.alphaTarget(0);\n                d.fx = null;\n                d.fy = null;\n            }\n        });\n    </script>\n</body>\n</html>\n\"\"\"\n\n    # Replace the placeholders with the actual values\n    html_content = html_content.replace(\"FILENAME_PLACEHOLDER\", filename)\n    html_content = html_content.replace(\"TITLE_PLACEHOLDER\", html_title)\n\n    # Write HTML to file\n    html_path = os.path.join(output_dir, f\"{filename}.html\")\n    with open(html_path, \"w\") as f:\n        f.write(html_content)\n\n    print(f\"Visualization created at {html_path}\")\n    return html_path\n\n\ndef find_free_port():\n    \"\"\"Find a free port on localhost.\"\"\"\n    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:\n        s.bind((\"\", 0))\n        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        return s.getsockname()[1]\n\n\ndef start_http_server(directory, port=None):\n    \"\"\"Start an HTTP server in the given directory.\n\n    Args:\n        directory: Directory to serve files from\n        port: Port to use (finds a free port if None)\n\n    Returns:\n        tuple: (server_thread, port)\n    \"\"\"\n    if port is None:\n        port = find_free_port()\n\n    # Get the absolute path of the directory\n    directory = str(Path(directory).absolute())\n\n    # Change to the directory to serve files\n    os.chdir(directory)\n\n    # Create HTTP server\n    handler = http.server.SimpleHTTPRequestHandler\n    httpd = socketserver.TCPServer((\"\", port), handler)\n\n    # Start server in a separate thread\n    server_thread = threading.Thread(target=httpd.serve_forever)\n    server_thread.daemon = (\n        True  # This makes the thread exit when the main program exits\n    )\n    server_thread.start()\n\n    print(f\"Server started at http://localhost:{port}\")\n    return server_thread, port\n\n\ndef serve_and_open_visualization(html_path, auto_open=True):\n    \"\"\"Serve the HTML file and open it in a browser.\n\n    Args:\n        html_path: Path to the HTML file\n        auto_open: Whether to automatically open the browser\n\n    Returns:\n        tuple: (server_thread, url)\n    \"\"\"\n    # Get the directory and filename\n    directory = os.path.dirname(os.path.abspath(html_path))\n    filename = os.path.basename(html_path)\n\n    # Start the server\n    server_thread, port = start_http_server(directory)\n\n    # Build the URL\n    url = f\"http://localhost:{port}/{filename}\"\n\n    # Open the URL in a browser\n    if auto_open:\n        print(f\"Opening {url} in your browser...\")\n        webbrowser.open(url)\n    else:\n        print(f\"Visualization available at {url}\")\n\n    return server_thread, url\n\n\ndef visualize_flow(\n    flow: Flow,\n    flow_name: str,\n    serve: bool = True,\n    auto_open: bool = True,\n    output_dir: str = \"./viz\",\n    html_title: Optional[str] = None,\n) -> Union[str, Tuple[str, Any, str]]:\n    \"\"\"Helper function to visualize a flow with both mermaid and D3.js\n\n    Args:\n        flow: Flow object to visualize\n        flow_name: Name of the flow (used for filename and display)\n        serve: Whether to start a server for the visualization\n        auto_open: Whether to automatically open in browser\n        output_dir: Directory to save visualization files\n        html_title: Custom title for the HTML page (defaults to flow_name if None)\n\n    Returns:\n        str or tuple: Path to HTML file, or (path, server_thread, url) if serve=True\n    \"\"\"\n    print(f\"\\n--- {flow_name} Mermaid Diagram ---\")\n    print(build_mermaid(start=flow))\n\n    print(f\"\\n--- {flow_name} D3.js Visualization ---\")\n    json_data = flow_to_json(flow)\n\n    # Create the visualization\n    output_filename = f\"{flow_name.lower().replace(' ', '_')}\"\n\n    # Use flow_name as the HTML title if not specified\n    if html_title is None:\n        html_title = f\"PocketFlow: {flow_name}\"\n\n    html_path = create_d3_visualization(\n        json_data,\n        output_dir=output_dir,\n        filename=output_filename,\n        html_title=html_title,\n    )\n\n    # Serve and open if requested\n    if serve:\n        server_thread, url = serve_and_open_visualization(html_path, auto_open)\n        return html_path, server_thread, url\n\n    return html_path\n\n\ndef load_flow_from_module(module_path: str, flow_variable: str) -> Flow:\n    \"\"\"Dynamically load a flow from a module.\n\n    Args:\n        module_path: Path to the module (e.g., 'my_package.my_module')\n        flow_variable: Name of the flow variable in the module\n\n    Returns:\n        Flow: The loaded flow object\n    \"\"\"\n    try:\n        module = importlib.import_module(module_path)\n        return getattr(module, flow_variable)\n    except (ImportError, AttributeError) as e:\n        print(f\"Error loading flow: {e}\")\n        sys.exit(1)\n\n\n# Example usage\nif __name__ == \"__main__\":\n    import argparse\n\n    parser = argparse.ArgumentParser(description=\"Visualize a PocketFlow flow\")\n    parser.add_argument(\n        \"--module\", default=\"async_loop_flow\", help=\"Module containing the flow\"\n    )\n    parser.add_argument(\n        \"--flow\", default=\"order_pipeline\", help=\"Flow variable name in the module\"\n    )\n    parser.add_argument(\n        \"--name\", default=\"Flow Visualization\", help=\"Name for the visualization\"\n    )\n    parser.add_argument(\n        \"--output-dir\", default=\"./viz\", help=\"Directory to save visualization files\"\n    )\n    parser.add_argument(\"--no-serve\", action=\"store_true\", help=\"Don't start a server\")\n    parser.add_argument(\n        \"--no-open\", action=\"store_true\", help=\"Don't open browser automatically\"\n    )\n    parser.add_argument(\"--title\", help=\"Custom HTML title\")\n\n    args = parser.parse_args()\n\n    # Load flow from the specified module\n    flow_obj = load_flow_from_module(args.module, args.flow)\n\n    # Visualize the flow\n    visualize_flow(\n        flow=flow_obj,\n        flow_name=args.name,\n        serve=not args.no_serve,\n        auto_open=not args.no_open,\n        output_dir=args.output_dir,\n        html_title=args.title,\n    )\n\n    # Keep server running if serving\n    if not args.no_serve:\n        try:\n            print(\"\\nServer is running. Press Ctrl+C to stop...\")\n            while True:\n                time.sleep(1)\n        except KeyboardInterrupt:\n            print(\"\\nShutting down...\")\n"
  },
  {
    "path": "cookbook/pocketflow-visualization/viz/flow_visualization.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>PocketFlow: Flow Visualization</title>\n    <script src=\"https://d3js.org/d3.v7.min.js\"></script>\n    <style>\n        body {\n            font-family: Arial, sans-serif;\n            margin: 0;\n            padding: 0;\n            overflow: hidden;\n        }\n        svg {\n            width: 100vw;\n            height: 100vh;\n        }\n        .links path {\n            fill: none;\n            stroke: #999;\n            stroke-opacity: 0.6;\n            stroke-width: 1.5px;\n        }\n        .group-links path {\n            fill: none;\n            stroke: #333;\n            stroke-opacity: 0.8;\n            stroke-width: 2px;\n            stroke-dasharray: 5,5;\n        }\n        .nodes circle {\n            stroke: #fff;\n            stroke-width: 1.5px;\n        }\n        .node-labels {\n            font-size: 12px;\n            pointer-events: none;\n        }\n        .link-labels {\n            font-size: 10px;\n            fill: #666;\n            pointer-events: none;\n        }\n        .group-link-labels {\n            font-size: 11px;\n            font-weight: bold;\n            fill: #333;\n            pointer-events: none;\n        }\n        .group-container {\n            stroke: #333;\n            stroke-width: 1.5px;\n            stroke-dasharray: 5,5;\n            fill: rgba(200, 200, 200, 0.1);\n            rx: 10;\n            ry: 10;\n        }\n        .group-label {\n            font-size: 14px;\n            font-weight: bold;\n            pointer-events: none;\n        }\n    </style>\n</head>\n<body>\n    <svg id=\"graph\"></svg>\n    <script>\n        // Load data from file\n        d3.json(\"flow_visualization.json\").then(data => {\n            const svg = d3.select(\"#graph\");\n            const width = window.innerWidth;\n            const height = window.innerHeight;\n            \n            // Define arrow markers for links\n            svg.append(\"defs\").append(\"marker\")\n                .attr(\"id\", \"arrowhead\")\n                .attr(\"viewBox\", \"0 -5 10 10\")\n                .attr(\"refX\", 25) // Position the arrow away from the target node\n                .attr(\"refY\", 0)\n                .attr(\"orient\", \"auto\")\n                .attr(\"markerWidth\", 6)\n                .attr(\"markerHeight\", 6)\n                .attr(\"xoverflow\", \"visible\")\n                .append(\"path\")\n                .attr(\"d\", \"M 0,-5 L 10,0 L 0,5\")\n                .attr(\"fill\", \"#999\");\n                \n            // Define thicker arrow markers for group links\n            svg.append(\"defs\").append(\"marker\")\n                .attr(\"id\", \"group-arrowhead\")\n                .attr(\"viewBox\", \"0 -5 10 10\")\n                .attr(\"refX\", 3) // Position at the boundary of the group\n                .attr(\"refY\", 0)\n                .attr(\"orient\", \"auto\")\n                .attr(\"markerWidth\", 8)\n                .attr(\"markerHeight\", 8)\n                .attr(\"xoverflow\", \"visible\")\n                .append(\"path\")\n                .attr(\"d\", \"M 0,-5 L 10,0 L 0,5\")\n                .attr(\"fill\", \"#333\");\n            \n            // Color scale for node groups\n            const color = d3.scaleOrdinal(d3.schemeCategory10);\n            \n            // Process the data to identify groups\n            const groups = {};\n            data.nodes.forEach(node => {\n                if (node.group > 0) {\n                    if (!groups[node.group]) {\n                        // Use the flow name instead of generic \"Group X\"\n                        const flowName = data.flows && data.flows[node.group] ? data.flows[node.group] : `Flow ${node.group}`;\n                        groups[node.group] = {\n                            id: node.group,\n                            name: flowName,\n                            nodes: [],\n                            x: 0,\n                            y: 0,\n                            width: 0,\n                            height: 0\n                        };\n                    }\n                    groups[node.group].nodes.push(node);\n                }\n            });\n            \n            // Create a force simulation\n            const simulation = d3.forceSimulation(data.nodes)\n                // Controls the distance between connected nodes\n                .force(\"link\", d3.forceLink(data.links).id(d => d.id).distance(100))\n                // Controls how nodes repel each other - lower values bring nodes closer\n                .force(\"charge\", d3.forceManyBody().strength(-30))\n                // Centers the entire graph in the SVG\n                .force(\"center\", d3.forceCenter(width / 2, height / 2))\n                // Prevents nodes from overlapping - acts like a minimum distance\n                .force(\"collide\", d3.forceCollide().radius(50));\n            \n            // Group forces - create a force to keep nodes in the same group closer together\n            // This creates the effect of nodes clustering within their group boxes\n            const groupForce = alpha => {\n                for (let i = 0; i < data.nodes.length; i++) {\n                    const node = data.nodes[i];\n                    if (node.group > 0) {\n                        const group = groups[node.group];\n                        if (group && group.nodes.length > 1) {\n                            // Calculate center of group\n                            let centerX = 0, centerY = 0;\n                            group.nodes.forEach(n => {\n                                centerX += n.x || 0;\n                                centerY += n.y || 0;\n                            });\n                            centerX /= group.nodes.length;\n                            centerY /= group.nodes.length;\n                            \n                            // Move nodes toward center\n                            const k = alpha * 0.3; // Increased from 0.1 to 0.3\n                            node.vx += (centerX - node.x) * k;\n                            node.vy += (centerY - node.y) * k;\n                        }\n                    }\n                }\n            };\n            \n            // Additional force to position groups in a more organized layout (like in the image)\n            // This arranges the groups horizontally/vertically based on their connections\n            const groupLayoutForce = alpha => {\n                // Get group centers\n                const groupCenters = Object.values(groups).map(g => {\n                    return { id: g.id, cx: 0, cy: 0 };\n                });\n                \n                // Calculate current center positions\n                Object.values(groups).forEach(g => {\n                    if (g.nodes.length > 0) {\n                        let cx = 0, cy = 0;\n                        g.nodes.forEach(n => {\n                            cx += n.x || 0;\n                            cy += n.y || 0;\n                        });\n                        \n                        const groupCenter = groupCenters.find(gc => gc.id === g.id);\n                        if (groupCenter) {\n                            groupCenter.cx = cx / g.nodes.length;\n                            groupCenter.cy = cy / g.nodes.length;\n                        }\n                    }\n                });\n                \n                // Apply forces to position groups\n                const k = alpha * 0.05;\n                \n                // Try to position groups in a more structured way\n                // Adjust these values to change the overall layout\n                for (let i = 0; i < data.group_links.length; i++) {\n                    const link = data.group_links[i];\n                    const source = groupCenters.find(g => g.id === link.source);\n                    const target = groupCenters.find(g => g.id === link.target);\n                    \n                    if (source && target) {\n                        // Add a horizontal force to align groups\n                        const desiredDx = 300; // Desired horizontal distance between linked groups\n                        const dx = target.cx - source.cx;\n                        const diff = desiredDx - Math.abs(dx);\n                        \n                        // Apply forces to group nodes\n                        groups[source.id].nodes.forEach(n => {\n                            if (dx > 0) {\n                                n.vx -= diff * k;\n                            } else {\n                                n.vx += diff * k;\n                            }\n                        });\n                        \n                        groups[target.id].nodes.forEach(n => {\n                            if (dx > 0) {\n                                n.vx += diff * k;\n                            } else {\n                                n.vx -= diff * k;\n                            }\n                        });\n                    }\n                }\n            };\n            \n            simulation.force(\"group\", groupForce);\n            simulation.force(\"groupLayout\", groupLayoutForce);\n            \n            // Create links with arrow paths instead of lines\n            const link = svg.append(\"g\")\n                .attr(\"class\", \"links\")\n                .selectAll(\"path\")\n                .data(data.links)\n                .enter()\n                .append(\"path\")\n                .attr(\"stroke-width\", 2)\n                .attr(\"stroke\", \"#999\")\n                .attr(\"marker-end\", \"url(#arrowhead)\");  // Add the arrowhead marker\n            \n            // Create group containers (drawn before nodes)\n            const groupContainers = svg.append(\"g\")\n                .attr(\"class\", \"groups\")\n                .selectAll(\"rect\")\n                .data(Object.values(groups))\n                .enter()\n                .append(\"rect\")\n                .attr(\"class\", \"group-container\")\n                .attr(\"fill\", d => d3.color(color(d.id)).copy({opacity: 0.2}));\n            \n            // Create group links between flows\n            const groupLink = svg.append(\"g\")\n                .attr(\"class\", \"group-links\")\n                .selectAll(\"path\")\n                .data(data.group_links || [])\n                .enter()\n                .append(\"path\")\n                .attr(\"stroke-width\", 2)\n                .attr(\"stroke\", \"#333\")\n                .attr(\"marker-end\", \"url(#group-arrowhead)\");\n                \n            // Create group link labels\n            const groupLinkLabel = svg.append(\"g\")\n                .attr(\"class\", \"group-link-labels\")\n                .selectAll(\"text\")\n                .data(data.group_links || [])\n                .enter()\n                .append(\"text\")\n                .text(d => d.action)\n                .attr(\"font-size\", \"11px\")\n                .attr(\"font-weight\", \"bold\")\n                .attr(\"fill\", \"#333\");\n            \n            // Create group labels\n            const groupLabels = svg.append(\"g\")\n                .attr(\"class\", \"group-labels\")\n                .selectAll(\"text\")\n                .data(Object.values(groups))\n                .enter()\n                .append(\"text\")\n                .attr(\"class\", \"group-label\")\n                .text(d => d.name)  // Now using the proper flow name\n                .attr(\"fill\", d => d3.color(color(d.id)).darker());\n            \n            // Create link labels\n            const linkLabel = svg.append(\"g\")\n                .attr(\"class\", \"link-labels\")\n                .selectAll(\"text\")\n                .data(data.links)\n                .enter()\n                .append(\"text\")\n                .text(d => d.action)\n                .attr(\"font-size\", \"10px\")\n                .attr(\"fill\", \"#666\");\n            \n            // Create nodes\n            const node = svg.append(\"g\")\n                .attr(\"class\", \"nodes\")\n                .selectAll(\"circle\")\n                .data(data.nodes)\n                .enter()\n                .append(\"circle\")\n                .attr(\"r\", 15)\n                .attr(\"fill\", d => color(d.group))\n                .call(d3.drag()\n                    .on(\"start\", dragstarted)\n                    .on(\"drag\", dragged)\n                    .on(\"end\", dragended));\n            \n            // Create node labels\n            const nodeLabel = svg.append(\"g\")\n                .attr(\"class\", \"node-labels\")\n                .selectAll(\"text\")\n                .data(data.nodes)\n                .enter()\n                .append(\"text\")\n                .text(d => d.name)\n                .attr(\"text-anchor\", \"middle\")\n                .attr(\"dy\", 25);\n            \n            // Add tooltip on hover\n            node.append(\"title\")\n                .text(d => d.name);\n            \n            // Update positions on each tick\n            simulation.on(\"tick\", () => {\n                // Update links with straight lines\n                link.attr(\"d\", d => {\n                    return `M${d.source.x},${d.source.y} L${d.target.x},${d.target.y}`;\n                });\n                \n                // Update nodes\n                node\n                    .attr(\"cx\", d => d.x)\n                    .attr(\"cy\", d => d.y);\n                \n                // Update node labels\n                nodeLabel\n                    .attr(\"x\", d => d.x)\n                    .attr(\"y\", d => d.y);\n                \n                // Position link labels at midpoint\n                linkLabel\n                    .attr(\"x\", d => (d.source.x + d.target.x) / 2)\n                    .attr(\"y\", d => (d.source.y + d.target.y) / 2);\n                \n                // Update group containers\n                groupContainers.each(function(d) {\n                    // If there are nodes in this group\n                    if (d.nodes.length > 0) {\n                        let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n                        \n                        // Find the bounding box for all nodes in the group\n                        d.nodes.forEach(n => {\n                            minX = Math.min(minX, n.x - 30);\n                            minY = Math.min(minY, n.y - 30);\n                            maxX = Math.max(maxX, n.x + 30);\n                            maxY = Math.max(maxY, n.y + 40); // Extra space for labels\n                        });\n                        \n                        // Add padding\n                        const padding = 20;\n                        minX -= padding;\n                        minY -= padding;\n                        maxX += padding;\n                        maxY += padding;\n                        \n                        // Save group dimensions\n                        d.x = minX;\n                        d.y = minY;\n                        d.width = maxX - minX;\n                        d.height = maxY - minY;\n                        d.centerX = minX + d.width / 2;\n                        d.centerY = minY + d.height / 2;\n                        \n                        // Set position and size of the group container\n                        d3.select(this)\n                            .attr(\"x\", minX)\n                            .attr(\"y\", minY)\n                            .attr(\"width\", d.width)\n                            .attr(\"height\", d.height);\n                        \n                        // Update group label position (top-left of group)\n                        groupLabels.filter(g => g.id === d.id)\n                            .attr(\"x\", minX + 10)\n                            .attr(\"y\", minY + 20);\n                    }\n                });\n                \n                // Update group links between flows\n                groupLink.attr(\"d\", d => {\n                    const sourceGroup = groups[d.source];\n                    const targetGroup = groups[d.target];\n                    \n                    if (!sourceGroup || !targetGroup) return \"\";\n                    \n                    // Find intersection points with group boundaries\n                    // This ensures links connect to the group's border rather than its center\n                    \n                    // Calculate centers of groups\n                    const sx = sourceGroup.centerX;\n                    const sy = sourceGroup.centerY;\n                    const tx = targetGroup.centerX;\n                    const ty = targetGroup.centerY;\n                    \n                    // Calculate angle between centers - used to find intersection points\n                    const angle = Math.atan2(ty - sy, tx - sx);\n                    \n                    // Calculate intersection points with source group borders\n                    // We cast a ray from center in the direction of the target\n                    let sourceX, sourceY;\n                    const cosA = Math.cos(angle);\n                    const sinA = Math.sin(angle);\n                    \n                    // Check intersection with horizontal borders (top and bottom)\n                    const ts_top = (sourceGroup.y - sy) / sinA;\n                    const ts_bottom = (sourceGroup.y + sourceGroup.height - sy) / sinA;\n                    \n                    // Check intersection with vertical borders (left and right)\n                    const ts_left = (sourceGroup.x - sx) / cosA;\n                    const ts_right = (sourceGroup.x + sourceGroup.width - sx) / cosA;\n                    \n                    // Use the closest positive intersection (first hit with the boundary)\n                    let t_source = Infinity;\n                    if (ts_top > 0) t_source = Math.min(t_source, ts_top);\n                    if (ts_bottom > 0) t_source = Math.min(t_source, ts_bottom);\n                    if (ts_left > 0) t_source = Math.min(t_source, ts_left);\n                    if (ts_right > 0) t_source = Math.min(t_source, ts_right);\n                    \n                    // Target group: Find intersection in the opposite direction\n                    // We cast a ray from target center toward the source\n                    let targetX, targetY;\n                    const oppositeAngle = angle + Math.PI;\n                    const cosOpp = Math.cos(oppositeAngle);\n                    const sinOpp = Math.sin(oppositeAngle);\n                    \n                    // Check intersections for target group\n                    const tt_top = (targetGroup.y - ty) / sinOpp;\n                    const tt_bottom = (targetGroup.y + targetGroup.height - ty) / sinOpp;\n                    const tt_left = (targetGroup.x - tx) / cosOpp;\n                    const tt_right = (targetGroup.x + targetGroup.width - tx) / cosOpp;\n                    \n                    // Use the closest positive intersection\n                    let t_target = Infinity;\n                    if (tt_top > 0) t_target = Math.min(t_target, tt_top);\n                    if (tt_bottom > 0) t_target = Math.min(t_target, tt_bottom);\n                    if (tt_left > 0) t_target = Math.min(t_target, tt_left);\n                    if (tt_right > 0) t_target = Math.min(t_target, tt_right);\n                    \n                    // Calculate actual border points using parametric equation:\n                    // point = center + t * direction\n                    if (t_source !== Infinity) {\n                        sourceX = sx + cosA * t_source;\n                        sourceY = sy + sinA * t_source;\n                    } else {\n                        sourceX = sx;\n                        sourceY = sy;\n                    }\n                    \n                    if (t_target !== Infinity) {\n                        targetX = tx + cosOpp * t_target;\n                        targetY = ty + sinOpp * t_target;\n                    } else {\n                        targetX = tx;\n                        targetY = ty;\n                    }\n                    \n                    // Create a straight line between the border points\n                    return `M${sourceX},${sourceY} L${targetX},${targetY}`;\n                });\n                \n                // Update group link labels\n                groupLinkLabel.attr(\"x\", d => {\n                    const sourceGroup = groups[d.source];\n                    const targetGroup = groups[d.target];\n                    if (!sourceGroup || !targetGroup) return 0;\n                    return (sourceGroup.centerX + targetGroup.centerX) / 2;\n                })\n                .attr(\"y\", d => {\n                    const sourceGroup = groups[d.source];\n                    const targetGroup = groups[d.target];\n                    if (!sourceGroup || !targetGroup) return 0;\n                    return (sourceGroup.centerY + targetGroup.centerY) / 2 - 10;\n                });\n            });\n            \n            // Drag functions\n            function dragstarted(event, d) {\n                if (!event.active) simulation.alphaTarget(0.3).restart();\n                d.fx = d.x;\n                d.fy = d.y;\n            }\n            \n            function dragged(event, d) {\n                d.fx = event.x;\n                d.fy = event.y;\n            }\n            \n            function dragended(event, d) {\n                if (!event.active) simulation.alphaTarget(0);\n                d.fx = null;\n                d.fy = null;\n            }\n        });\n    </script>\n</body>\n</html>\n"
  },
  {
    "path": "cookbook/pocketflow-visualization/viz/flow_visualization.json",
    "content": "{\n  \"nodes\": [\n    {\n      \"id\": 3,\n      \"name\": \"ValidatePayment\",\n      \"group\": 2\n    },\n    {\n      \"id\": 4,\n      \"name\": \"ProcessPayment\",\n      \"group\": 2\n    },\n    {\n      \"id\": 5,\n      \"name\": \"PaymentConfirmation\",\n      \"group\": 2\n    },\n    {\n      \"id\": 7,\n      \"name\": \"CheckStock\",\n      \"group\": 6\n    },\n    {\n      \"id\": 8,\n      \"name\": \"ReserveItems\",\n      \"group\": 6\n    },\n    {\n      \"id\": 9,\n      \"name\": \"UpdateInventory\",\n      \"group\": 6\n    },\n    {\n      \"id\": 11,\n      \"name\": \"CreateLabel\",\n      \"group\": 10\n    },\n    {\n      \"id\": 12,\n      \"name\": \"AssignCarrier\",\n      \"group\": 10\n    },\n    {\n      \"id\": 13,\n      \"name\": \"SchedulePickup\",\n      \"group\": 10\n    }\n  ],\n  \"links\": [\n    {\n      \"source\": 3,\n      \"target\": 4,\n      \"action\": \"default\"\n    },\n    {\n      \"source\": 4,\n      \"target\": 5,\n      \"action\": \"default\"\n    },\n    {\n      \"source\": 7,\n      \"target\": 8,\n      \"action\": \"default\"\n    },\n    {\n      \"source\": 8,\n      \"target\": 9,\n      \"action\": \"default\"\n    },\n    {\n      \"source\": 11,\n      \"target\": 12,\n      \"action\": \"default\"\n    },\n    {\n      \"source\": 12,\n      \"target\": 13,\n      \"action\": \"default\"\n    }\n  ],\n  \"group_links\": [\n    {\n      \"source\": 2,\n      \"target\": 6,\n      \"action\": \"default\"\n    },\n    {\n      \"source\": 6,\n      \"target\": 10,\n      \"action\": \"default\"\n    }\n  ],\n  \"flows\": {\n    \"1\": \"OrderFlow\",\n    \"2\": \"AsyncFlow\",\n    \"6\": \"AsyncFlow\",\n    \"10\": \"AsyncFlow\"\n  }\n}"
  },
  {
    "path": "cookbook/pocketflow-voice-chat/README.md",
    "content": "# PocketFlow Voice Chat\n\nThis project demonstrates a voice-based interactive chat application built with PocketFlow. Users can speak their queries, and the system will respond with spoken answers from an LLM, maintaining conversation history.\n\n- Check out the [Substack Post Tutorial](https://pocketflow.substack.com/p/build-your-own-voice-chatbot-from) for more!\n\n\n## Features\n\n-   **Voice Activity Detection (VAD)**: Automatically detects when the user starts and stops speaking.\n-   **Speech-to-Text (STT)**: Converts spoken audio into text using OpenAI.\n-   **LLM Interaction**: Processes the transcribed text with an LLM (e.g., GPT-4o), maintaining conversation history.\n-   **Text-to-Speech (TTS)**: Converts the LLM's text response back into audible speech using OpenAI.\n-   **Continuous Conversation**: Loops back to listen for the next user query after responding, allowing for an ongoing dialogue.\n\n## How to Run\n\n1.  **Set your OpenAI API key**:\n    ```bash\n    export OPENAI_API_KEY=\"your-api-key-here\"\n    ```\n    Ensure this environment variable is set, as the utility scripts for STT, LLM, and TTS rely on it.\n    You can test individual utility functions (e.g., `python utils/call_llm.py`, `python utils/text_to_speech.py`) to help verify your API key and setup.\n\n2.  **Install dependencies**:\n    Make sure you have Python installed. Then, install the required libraries using pip:\n    ```bash\n    pip install -r requirements.txt\n    ```\n    This will install libraries such as `openai`, `pocketflow`, `sounddevice`, `numpy`, `scipy`, and `soundfile`.\n\n    **Note for Linux users**: `sounddevice` may require PortAudio. If you encounter issues, you might need to install it first:\n    ```bash\n    sudo apt-get update && sudo apt-get install -y portaudio19-dev\n    ```\n\n3.  **Run the application**:\n    ```bash\n    python main.py\n    ```\n    Follow the console prompts. The application will start listening when you see \"Listening for your query...\".\n\n## How It Works\n\nThe application uses a PocketFlow workflow to manage the conversation steps:\n\n```mermaid\nflowchart TD\n    CaptureAudio[Capture Audio] --> SpeechToText[Speech to Text]\n    SpeechToText --> QueryLLM[Query LLM]\n    QueryLLM --> TextToSpeech[Text to Speech & Play]\n    TextToSpeech -- \"Next Turn\" --> CaptureAudio\n```\n\nHere's what each node in the flow does:\n\n1.  **`CaptureAudioNode`**: Records audio from the user's microphone. It uses Voice Activity Detection (VAD) to start recording when speech is detected and stop when silence is detected.\n2.  **`SpeechToTextNode`**: Takes the recorded audio data, converts it to a suitable format, and sends it to OpenAI's STT API (gpt-4o-transcribe) to get the transcribed text.\n3.  **`QueryLLMNode`**: Takes the transcribed text from the user, along with the existing conversation history, and sends it to an LLM (OpenAI's GPT-4o model) to generate an intelligent response.\n4.  **`TextToSpeechNode`**: Receives the text response from the LLM, converts it into audio using OpenAI's TTS API (gpt-4o-mini-tts), and plays the audio back to the user. If the conversation is set to continue, it transitions back to the `CaptureAudioNode`.\n\n## Example Interaction\n\nWhen you run `main.py`:\n\n1.  The console will display:\n    ```\n    Starting PocketFlow Voice Chat...\n    Speak your query after 'Listening for your query...' appears.\n    ...\n    ```\n2.  When you see `Listening for your query...`, speak clearly into your microphone.\n3.  After you stop speaking, the console will show updates:\n    ```\n    Audio captured (X.XXs), proceeding to STT.\n    Converting speech to text...\n    User: [Your transcribed query will appear here]\n    Sending query to LLM...\n    LLM: [The LLM's response text will appear here]\n    Converting LLM response to speech...\n    Playing LLM response...\n    ```\n4.  You will hear the LLM's response spoken aloud.\n5.  The application will then loop back, and you'll see `Listening for your query...` again, ready for your next input.\n\nThe conversation continues in this manner. To stop the application, you typically need to interrupt it (e.g., Ctrl+C in the terminal), as it's designed to loop continuously.\n"
  },
  {
    "path": "cookbook/pocketflow-voice-chat/docs/design.md",
    "content": "# Design Doc: PocketFlow Voice Chat\n\n> Please DON'T remove notes for AI\n\n## Requirements\n\n> Notes for AI: Keep it simple and clear.\n> If the requirements are abstract, write concrete user stories\n\n-   **Goal**: Enable users to interact with an LLM via voice in a continuous conversation, receiving spoken responses.\n-   **User Story 1**: As a user, I want to speak my query into a microphone so that the application can understand what I'm asking.\n-   **User Story 2**: As a user, I want the application to send my spoken query to an LLM for processing.\n-   **User Story 3**: As a user, I want to hear the LLM's response spoken back to me.\n-   **User Story 4**: As a user, after hearing the response, I want the application to be ready for my next spoken query without restarting.\n-   **Core Functionalities**:\n    1.  Capture audio input.\n    2.  Convert speech to text (STT).\n    3.  Process text with an LLM (maintaining conversation history).\n    4.  Convert LLM text response to speech (TTS).\n    5.  Play back synthesized audio.\n    6.  Loop back to capture new audio input for a continuous conversation.\n\n## Flow Design\n\n> Notes for AI:\n> 1. Consider the design patterns of agent, map-reduce, rag, and workflow. Apply them if they fit.\n> 2. Present a concise, high-level description of the workflow.\n\n### Applicable Design Pattern:\n\n-   **Workflow**: A sequential workflow with a loop is most appropriate. Each step (audio capture, STT, LLM query, TTS, audio playback) directly follows the previous, and after playback, the flow returns to the audio capture stage.\n\n### Flow high-level Design:\n\nThe application will operate in a loop to allow for continuous conversation:\n1.  **`CaptureAudioNode`**: Records audio from the user\\'s microphone when triggered.\n2.  **`SpeechToTextNode`**: Converts the recorded audio into text.\n3.  **`QueryLLMNode`**: Sends the transcribed text (with history) to an LLM and gets a text response.\n4.  **`TextToSpeechNode`**: Converts the LLM\\'s text response into in-memory audio data and then plays it. After completion, the flow transitions back to the `CaptureAudioNode`.\n\n```mermaid\nflowchart TD\n    CaptureAudio[Capture Audio] --> SpeechToText[Speech to Text]\n    SpeechToText --> QueryLLM[Query LLM]\n    QueryLLM --> TextToSpeech[Text to Speech & Play]\n    TextToSpeech -- \"Next Turn\" --> CaptureAudio\n```\n\n## Utility Functions\n\n> Notes for AI:\n> 1. Understand the utility function definition thoroughly by reviewing the doc.\n> 2. Include only the necessary utility functions, based on nodes in the flow.\n\n1.  **`record_audio()`** (`utils/audio_utils.py`)\n    -   *Input*: (Optional) `sample_rate` (int, Hz, e.g., `DEFAULT_SAMPLE_RATE`), `channels` (int, e.g., `DEFAULT_CHANNELS`), `chunk_size_ms` (int, e.g., `DEFAULT_CHUNK_SIZE_MS`), `silence_threshold_rms` (float, e.g., `DEFAULT_SILENCE_THRESHOLD_RMS`), `min_silence_duration_ms` (int, e.g., `DEFAULT_MIN_SILENCE_DURATION_MS`), `max_recording_duration_s` (int, e.g., `DEFAULT_MAX_RECORDING_DURATION_S`), `pre_roll_chunks_count` (int, e.g., `DEFAULT_PRE_ROLL_CHUNKS`).\n    -   *Output*: A tuple `(audio_data, sample_rate)` where `audio_data` is a NumPy array of float32 audio samples, and `sample_rate` is the recording sample rate (int). Returns `(None, sample_rate)` if no speech is detected or recording fails.\n    -   *Description*: Records audio from the microphone using silence-based Voice Activity Detection (VAD). Buffers `pre_roll_chunks_count` of audio and starts full recording when sound is detected above `silence_threshold_rms`. Stops after `min_silence_duration_ms` of sound below the threshold or if `max_recording_duration_s` is reached.\n    -   *Necessity*: Used by `CaptureAudioNode` to get user\\'s voice input.\n\n2.  **`speech_to_text_api(audio_data, sample_rate)`** (`utils/speech_to_text.py`)\n    -   *Input*: `audio_data` (bytes), `sample_rate` (int, though the API might infer this from the audio format).\n    -   *Output*: `transcribed_text` (str).\n    -   *Necessity*: Used by `SpeechToTextNode` to convert in-memory audio data to text.\n    -   *Example Model*: OpenAI `gpt-4o-transcribe`.\n\n3.  **`call_llm(messages)`** (`utils/call_llm.py`)\n    -   *Input*: `messages` (list of dicts, e.g., `[{\"role\": \"user\", \"content\": \"...\"}, {\"role\": \"assistant\", \"content\": \"...\"}]`). This should be the complete conversation history including the latest user query.\n    -   *Output*: `llm_response_text` (str)\n    -   *Necessity*: Used by `QueryLLMNode` to get an intelligent response.\n    -   *Example Model*: OpenAI `gpt-4o`.\n\n4.  **`text_to_speech_api(text_to_synthesize)`** (`utils/text_to_speech.py`)\n    -   *Input*: `text_to_synthesize` (str).\n    -   *Output*: A tuple `(audio_data, sample_rate)` where `audio_data` is in-memory audio as bytes (e.g., MP3 format from OpenAI) and `sample_rate` is the audio sample rate (int, e.g., 24000 Hz for OpenAI `gpt-4o-mini-tts`).\n    -   *Necessity*: Used by `TextToSpeechNode` to convert LLM text to speakable in-memory audio data.\n    -   *Example Model*: OpenAI `gpt-4o-mini-tts`.\n\n5.  **`play_audio_data(audio_data, sample_rate)`** (`utils/audio_utils.py`)\n    -   *Input*: `audio_data` (NumPy array of float32 audio samples), `sample_rate` (int).\n    -   *Output*: None\n    -   *Necessity*: Used by `TextToSpeechNode` (in its `post` method) to play the in-memory synthesized speech.\n\n## Node Design\n\n### Shared Memory\n\n> Notes for AI: Try to minimize data redundancy\n\nThe shared memory structure is organized as follows:\n\n```python\nshared = {\n    \"user_audio_data\": None,      # In-memory audio data (NumPy array) from user\n    \"user_audio_sample_rate\": None, # int: Sample rate of the user audio\n    \"chat_history\": [],            # list: Conversation history [{\"role\": \"user/assistant\", \"content\": \"...\"}]\n    \"continue_conversation\": True # boolean: Flag to control the main conversation loop\n}\n```\n\n### Node Steps\n\n> Notes for AI: Carefully decide whether to use Batch/Async Node/Flow.\n\n1.  **`CaptureAudioNode`**\n    -   *Purpose*: Record audio input from the user using VAD.\n    -   *Type*: Regular\n    -   *Steps*:\n        -   *prep*: Check `shared[\"continue_conversation\"]`. (Potentially load VAD parameters from `shared[\"config\"]` if dynamic).\n        -   *exec*: Call `utils.audio_utils.record_audio()` (passing VAD parameters if configured). This returns a NumPy array and sample rate.\n        -   *post*: `audio_numpy_array, sample_rate = exec_res`. Write `audio_numpy_array` to `shared[\"user_audio_data\"]` and `sample_rate` to `shared[\"user_audio_sample_rate\"]`. Returns `\"default\"`.\n\n2.  **`SpeechToTextNode`**\n    -   *Purpose*: Convert the recorded in-memory audio to text.\n    -   *Type*: Regular\n    -   *Steps*:\n        -   *prep*: Read `shared[\"user_audio_data\"]` (NumPy array) and `shared[\"user_audio_sample_rate\"]`. Return `(user_audio_data_numpy, user_audio_sample_rate)`.\n        -   *exec*: `audio_numpy_array, sample_rate = prep_res`. **Convert `audio_numpy_array` to audio `bytes` (e.g., in WAV format using `scipy.io.wavfile.write` to an `io.BytesIO` object).** Call `utils.speech_to_text.speech_to_text_api(audio_bytes, sample_rate)`.\n        -   *post*:\n            -   Let `transcribed_text = exec_res`.\n            -   Append `{\"role\": \"user\", \"content\": transcribed_text}` to `shared[\"chat_history\"]`.\n            -   Clear `shared[\"user_audio_data\"]` and `shared[\"user_audio_sample_rate\"]` as they are no longer needed.\n            -   Returns `\"default\"` (assuming STT is successful as per simplification).\n\n3.  **`QueryLLMNode`**\n    -   *Purpose*: Get a response from the LLM based on the user's query and conversation history.\n    -   *Type*: Regular\n    -   *Steps*:\n        -   *prep*: Read `shared[\"chat_history\"]`. Return `chat_history`.\n        -   *exec*: `history = prep_res`. Call `utils.call_llm.call_llm(messages=history)`.\n        -   *post*:\n            -   Let `llm_response = exec_res`.\n            -   Append `{\"role\": \"assistant\", \"content\": llm_response}` to `shared[\"chat_history\"]`.\n            -   Returns `\"default\"` (assuming LLM call is successful).\n\n4.  **`TextToSpeechNode`**\n    -   *Purpose*: Convert the LLM's text response into speech and play it.\n    -   *Type*: Regular\n    -   *Steps*:\n        -   *prep*: Read `shared[\"chat_history\"]`. Identify the last message, which should be the LLM's response. Return its content.\n        -   *exec*: `text_to_synthesize = prep_res`. Call `utils.text_to_speech.text_to_speech_api(text_to_synthesize)`. This returns `(llm_audio_bytes, llm_sample_rate)`.\n        -   *post*: `llm_audio_bytes, llm_sample_rate = exec_res`.\n            -   **Convert `llm_audio_bytes` (e.g., MP3 bytes from TTS API) to a NumPy array of audio samples (e.g., using a library like `pydub` or `soundfile` to decode).**\n            -   Call `utils.audio_utils.play_audio_data(llm_audio_numpy_array, llm_sample_rate)`.\n            -   (Optional) Log completion.\n            -   If `shared[\"continue_conversation\"]` is `True`, return `\"next_turn\"` to loop back.\n            -   Otherwise, return `\"end_conversation\"`.\n"
  },
  {
    "path": "cookbook/pocketflow-voice-chat/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import CaptureAudioNode, SpeechToTextNode, QueryLLMNode, TextToSpeechNode\n\ndef create_voice_chat_flow() -> Flow:\n    \"\"\"Creates and returns the voice chat flow.\"\"\"\n    # Create nodes\n    capture_audio = CaptureAudioNode()\n    speech_to_text = SpeechToTextNode()\n    query_llm = QueryLLMNode()\n    text_to_speech = TextToSpeechNode()\n\n    # Define transitions\n    capture_audio >> speech_to_text\n    speech_to_text >> query_llm\n    query_llm >> text_to_speech\n\n    # Loop back for next turn or end\n    text_to_speech - \"next_turn\" >> capture_audio\n    # \"end_conversation\" action from any node will terminate the flow naturally\n    # if no transition is defined for it from the current node.\n    # Alternatively, one could explicitly transition to an EndNode if desired.\n\n    # Create flow starting with the capture audio node\n    voice_chat_flow = Flow(start=capture_audio)\n    return voice_chat_flow "
  },
  {
    "path": "cookbook/pocketflow-voice-chat/main.py",
    "content": "from flow import create_voice_chat_flow\n\ndef main():\n    \"\"\"Runs the PocketFlow Voice Chat application.\"\"\"\n    print(\"Starting PocketFlow Voice Chat...\")\n    print(\"Speak your query after 'Listening for your query...' appears.\")\n    print(\"The conversation will continue until an error occurs or the loop is intentionally stopped.\")\n    print(\"To attempt to stop, you might need to cause an error (e.g., silence during capture if not handled by VAD to end gracefully) or modify shared[\\\"continue_conversation\\\"] if a mechanism is added.\")\n\n    shared = {\n        \"user_audio_data\": None,\n        \"user_audio_sample_rate\": None,\n        \"chat_history\": [],\n        \"continue_conversation\": True # Flag to control the main conversation loop\n    }\n\n    # Create the flow\n    voice_chat_flow = create_voice_chat_flow()\n\n    # Run the flow\n    # The flow will loop based on the \"next_turn\" action from TextToSpeechNode\n    # and the continue_conversation flag checked within nodes or if an error action is returned.\n    voice_chat_flow.run(shared)\n\nif __name__ == \"__main__\":\n    main() \n"
  },
  {
    "path": "cookbook/pocketflow-voice-chat/nodes.py",
    "content": "import numpy as np\nimport scipy.io.wavfile\nimport io\nimport soundfile # For converting MP3 bytes to NumPy array\n\nfrom pocketflow import Node\nfrom utils.audio_utils import record_audio, play_audio_data\nfrom utils.speech_to_text import speech_to_text_api\nfrom utils.call_llm import call_llm\nfrom utils.text_to_speech import text_to_speech_api\n\nclass CaptureAudioNode(Node):\n    \"\"\"Records audio input from the user using VAD.\"\"\"\n    def exec(self, _): # prep_res is not used as per design\n        print(\"\\nListening for your query...\")\n        audio_data, sample_rate = record_audio()\n        if audio_data is None:\n            return None, None\n        return audio_data, sample_rate\n\n    def post(self, shared, prep_res, exec_res):\n        audio_numpy_array, sample_rate = exec_res\n        if audio_numpy_array is None:\n            shared[\"user_audio_data\"] = None\n            shared[\"user_audio_sample_rate\"] = None\n            print(\"CaptureAudioNode: Failed to capture audio.\")\n            return \"end_conversation\" \n\n        shared[\"user_audio_data\"] = audio_numpy_array\n        shared[\"user_audio_sample_rate\"] = sample_rate\n        print(f\"Audio captured ({len(audio_numpy_array)/sample_rate:.2f}s), proceeding to STT.\")\n\nclass SpeechToTextNode(Node):\n    \"\"\"Converts the recorded in-memory audio to text.\"\"\"\n    def prep(self, shared):\n        user_audio_data = shared.get(\"user_audio_data\")\n        user_audio_sample_rate = shared.get(\"user_audio_sample_rate\")\n        if user_audio_data is None or user_audio_sample_rate is None:\n            print(\"SpeechToTextNode: No audio data to process.\")\n            return None # Signal to skip exec\n        return user_audio_data, user_audio_sample_rate\n\n    def exec(self, prep_res):\n        if prep_res is None:\n            return None # Skip if no audio data\n\n        audio_numpy_array, sample_rate = prep_res\n        \n        # Convert NumPy array to WAV bytes for the API\n        byte_io = io.BytesIO()\n        scipy.io.wavfile.write(byte_io, sample_rate, audio_numpy_array)\n        wav_bytes = byte_io.getvalue()\n        \n        print(\"Converting speech to text...\")\n        transcribed_text = speech_to_text_api(audio_data=wav_bytes, sample_rate=sample_rate)\n        return transcribed_text\n\n    def post(self, shared, prep_res, exec_res):\n        if exec_res is None:\n            print(\"SpeechToTextNode: STT API returned no text.\")\n            return \"end_conversation\" \n\n        transcribed_text = exec_res\n        print(f\"User: {transcribed_text}\")\n        \n        if \"chat_history\" not in shared:\n            shared[\"chat_history\"] = []\n        shared[\"chat_history\"].append({\"role\": \"user\", \"content\": transcribed_text})\n        \n        shared[\"user_audio_data\"] = None\n        shared[\"user_audio_sample_rate\"] = None\n        return \"default\"\n\nclass QueryLLMNode(Node):\n    \"\"\"Gets a response from the LLM.\"\"\"\n    def prep(self, shared):\n        chat_history = shared.get(\"chat_history\", [])\n        \n        if not chat_history:\n            print(\"QueryLLMNode: Chat history is empty. Skipping LLM call.\")\n            return None \n        \n        return chat_history\n\n    def exec(self, prep_res):\n        if prep_res is None: \n            return None \n\n        chat_history = prep_res\n        print(\"Sending query to LLM...\")\n        llm_response_text = call_llm(messages=chat_history)\n        return llm_response_text\n\n    def post(self, shared, prep_res, exec_res):\n        if exec_res is None:\n            print(\"QueryLLMNode: LLM API returned no response.\")\n            return \"end_conversation\" \n\n        llm_response_text = exec_res\n        print(f\"LLM: {llm_response_text}\")\n        \n        shared[\"chat_history\"].append({\"role\": \"assistant\", \"content\": llm_response_text})\n        return \"default\"\n\nclass TextToSpeechNode(Node):\n    \"\"\"Converts the LLM's text response into speech and plays it.\"\"\"\n    def prep(self, shared):\n        chat_history = shared.get(\"chat_history\", [])\n        if not chat_history:\n            print(\"TextToSpeechNode: Chat history is empty. No LLM response to synthesize.\")\n            return None\n        \n        last_message = chat_history[-1]\n        if last_message.get(\"role\") == \"assistant\" and last_message.get(\"content\"):\n            return last_message.get(\"content\")\n        else:\n            print(\"TextToSpeechNode: Last message not from assistant or no content. Skipping TTS.\")\n            return None\n\n    def exec(self, prep_res):\n        if prep_res is None:\n            return None, None\n            \n        llm_text_response = prep_res\n        print(\"Converting LLM response to speech...\")\n        llm_audio_bytes, llm_sample_rate = text_to_speech_api(llm_text_response)\n        return llm_audio_bytes, llm_sample_rate\n\n    def post(self, shared, prep_res, exec_res):\n        if exec_res is None or exec_res[0] is None:\n            print(\"TextToSpeechNode: TTS failed or was skipped.\")\n            return \"next_turn\" \n\n        llm_audio_bytes, llm_sample_rate = exec_res\n        \n        print(\"Playing LLM response...\")\n        try:\n            audio_segment, sr_from_file = soundfile.read(io.BytesIO(llm_audio_bytes))\n            play_audio_data(audio_segment, sr_from_file)\n        except Exception as e:\n            print(f\"Error playing TTS audio: {e}\")\n            return \"next_turn\" \n\n        if shared.get(\"continue_conversation\", True):\n            return \"next_turn\"\n        else:\n            print(\"Conversation ended by user flag.\")\n            return \"end_conversation\" "
  },
  {
    "path": "cookbook/pocketflow-voice-chat/requirements.txt",
    "content": "openai\npocketflow\nnumpy\nsounddevice\nscipy\nsoundfile "
  },
  {
    "path": "cookbook/pocketflow-voice-chat/utils/__init__.py",
    "content": ""
  },
  {
    "path": "cookbook/pocketflow-voice-chat/utils/audio_utils.py",
    "content": "import sounddevice as sd\nimport numpy as np\n\nDEFAULT_SAMPLE_RATE = 44100\nDEFAULT_CHANNELS = 1\nDEFAULT_CHUNK_SIZE_MS = 50  # Process audio in 50ms chunks for VAD\nDEFAULT_SILENCE_THRESHOLD_RMS = 0.01 # RMS value, needs tuning\nDEFAULT_MIN_SILENCE_DURATION_MS = 1000 # 1 second of silence to stop\nDEFAULT_MAX_RECORDING_DURATION_S = 15 # Safety cap for recording\nDEFAULT_PRE_ROLL_CHUNKS = 3 # Number of chunks to keep before speech starts\n\ndef record_audio(sample_rate = DEFAULT_SAMPLE_RATE,\n                 channels = DEFAULT_CHANNELS,\n                 chunk_size_ms = DEFAULT_CHUNK_SIZE_MS,\n                 silence_threshold_rms = DEFAULT_SILENCE_THRESHOLD_RMS,\n                 min_silence_duration_ms = DEFAULT_MIN_SILENCE_DURATION_MS,\n                 max_recording_duration_s = DEFAULT_MAX_RECORDING_DURATION_S,\n                 pre_roll_chunks_count = DEFAULT_PRE_ROLL_CHUNKS):\n    \"\"\"\n    Records audio from the microphone with silence-based VAD.\n    Returns in-memory audio data (NumPy array of float32) and sample rate.\n    Returns (None, sample_rate) if recording fails or max duration is met without speech.\n    \"\"\"\n    chunk_size_frames = int(sample_rate * chunk_size_ms / 1000)\n    min_silence_chunks = int(min_silence_duration_ms / chunk_size_ms)\n    max_chunks = int(max_recording_duration_s * 1000 / chunk_size_ms)\n\n    print(f\"Listening... (max {max_recording_duration_s}s). Speak when ready.\")\n    print(f\"(Silence threshold RMS: {silence_threshold_rms}, Min silence duration: {min_silence_duration_ms}ms)\")\n\n    recorded_frames = []\n    pre_roll_frames = []\n    is_recording = False\n    silence_counter = 0\n    chunks_recorded = 0\n\n    with sd.InputStream(samplerate=sample_rate, channels=channels, dtype='float32') as stream:\n\n        for i in range(max_chunks):\n            audio_chunk, overflowed = stream.read(chunk_size_frames)\n            if overflowed:\n                print(\"Warning: Audio buffer overflowed!\")\n            \n            rms = np.sqrt(np.mean(audio_chunk**2))\n\n            if is_recording:\n                recorded_frames.append(audio_chunk)\n                chunks_recorded += 1\n                if rms < silence_threshold_rms:\n                    silence_counter += 1\n                    if silence_counter >= min_silence_chunks:\n                        print(\"Silence detected, stopping recording.\")\n                        break\n                else:\n                    silence_counter = 0 # Reset silence counter on sound\n            else:\n                pre_roll_frames.append(audio_chunk)\n                if len(pre_roll_frames) > pre_roll_chunks_count:\n                    pre_roll_frames.pop(0)\n                \n                if rms > silence_threshold_rms:\n                    print(\"Speech detected, starting recording.\")\n                    is_recording = True\n                    for frame_to_add in pre_roll_frames:\n                        recorded_frames.append(frame_to_add)\n                    chunks_recorded = len(recorded_frames)\n                    pre_roll_frames.clear()\n            \n            if i == max_chunks - 1 and not is_recording:\n                print(\"No speech detected within the maximum recording duration.\")\n                return None, sample_rate\n\n        if not recorded_frames and is_recording:\n            print(\"Recording started but captured no frames before stopping. This might be due to immediate silence.\")\n\n    if not recorded_frames:\n        print(\"No audio was recorded.\")\n        return None, sample_rate\n\n    audio_data = np.concatenate(recorded_frames)\n    print(f\"Recording finished. Total duration: {len(audio_data)/sample_rate:.2f}s\")\n    return audio_data, sample_rate\n\ndef play_audio_data(audio_data, sample_rate):\n    \"\"\"Plays in-memory audio data (NumPy array).\"\"\"\n    try:\n        print(f\"Playing in-memory audio data (Sample rate: {sample_rate} Hz, Duration: {len(audio_data)/sample_rate:.2f}s)\")\n        sd.play(audio_data, sample_rate)\n        sd.wait()\n        print(\"Playback from memory finished.\")\n    except Exception as e:\n        print(f\"Error playing in-memory audio: {e}\")\n\n\nif __name__ == \"__main__\":\n    print(\"--- Testing audio_utils.py ---\")\n\n    # Test 1: record_audio() and play_audio_data() (in-memory)\n    print(\"\\n--- Test: Record and Play In-Memory Audio ---\")\n    print(\"Please speak into the microphone. Recording will start on sound and stop on silence.\")\n    recorded_audio, rec_sr = record_audio(\n        sample_rate=DEFAULT_SAMPLE_RATE,\n        silence_threshold_rms=0.02, \n        min_silence_duration_ms=1500,\n        max_recording_duration_s=10\n    )\n\n    if recorded_audio is not None and rec_sr is not None:\n        print(f\"Recorded audio data shape: {recorded_audio.shape}, Sample rate: {rec_sr} Hz\")\n        play_audio_data(recorded_audio, rec_sr)\n    else:\n        print(\"No audio recorded or recording failed.\")\n\n    print(\"\\n--- audio_utils.py tests finished. ---\") "
  },
  {
    "path": "cookbook/pocketflow-voice-chat/utils/call_llm.py",
    "content": "from openai import OpenAI\nimport os\n\ndef call_llm(messages):\n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    \n    response = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=messages,\n        temperature=0.7\n    )\n    \n    return response.choices[0].message.content\n\nif __name__ == \"__main__\":\n    # Test the LLM call\n    messages = [{\"role\": \"user\", \"content\": \"In a few words, what's the meaning of life?\"}]\n    response = call_llm(messages)\n    print(f\"Prompt: {messages[0]['content']}\")\n    print(f\"Response: {response}\")"
  },
  {
    "path": "cookbook/pocketflow-voice-chat/utils/speech_to_text.py",
    "content": "import os\nfrom openai import OpenAI\nimport io\n\ndef speech_to_text_api(audio_data: bytes, sample_rate: int):\n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\"))\n\n    # The API expects a file-like object. We can use io.BytesIO for in-memory bytes.\n    # We also need to give it a name, as if it were a file upload.\n    audio_file = io.BytesIO(audio_data)\n    audio_file.name = \"audio.wav\"  # Corrected to WAV format\n\n    transcript = client.audio.transcriptions.create(\n        model=\"gpt-4o-transcribe\",\n        file=audio_file\n        # language=\"en\" # Optional: specify language ISO-639-1 code\n        # prompt=\"PocketFlow, LLM\" # Optional: provide a prompt to guide the model\n    )\n    return transcript.text\n\nif __name__ == \"__main__\":\n    print(\"Testing Speech-to-Text API...\")\n    # The OpenAI client will raise an error if API key is not found or invalid.\n    # No explicit check here to keep it minimal.\n    test_audio_path = \"tts_output.mp3\"\n    if os.path.exists(test_audio_path):\n        print(f\"Found {test_audio_path}, using it for STT test.\")\n        with open(test_audio_path, \"rb\") as f:\n            audio_bytes_for_stt = f.read()\n        \n        # Sample rate for tts_output.mp3 from our TTS script is 24000\n        # but Whisper should ideally infer or handle common formats well.\n        stt_sample_rate = 24000 \n\n        transcribed_text = speech_to_text_api(audio_bytes_for_stt, stt_sample_rate)\n\n        if transcribed_text:\n            print(f\"Transcribed text: {transcribed_text}\")\n        else:\n            print(\"Failed to transcribe audio (API returned empty data).\")\n    else:\n        print(f\"Test audio file '{test_audio_path}' not found.\")\n        print(\"Please run the text_to_speech.py test first to generate it, or place your own audio file\")\n        print(\" (e.g., named 'test_audio.mp3') in the same directory as this script and modify the path.\")\n        print(\"Make sure it's a common audio format like MP3, WAV, M4A etc.\") "
  },
  {
    "path": "cookbook/pocketflow-voice-chat/utils/text_to_speech.py",
    "content": "import os\nfrom openai import OpenAI\n\ndef text_to_speech_api(text_to_synthesize: str):\n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\"))\n\n    response = client.audio.speech.create(\n        model=\"gpt-4o-mini-tts\",\n        voice=\"alloy\", # Other voices: echo, fable, onyx, nova, shimmer\n        input=text_to_synthesize,\n        response_format=\"mp3\" # Other formats: opus, aac, flac. MP3 is widely supported.\n                              # OpenAI default sample rate for tts-1 is 24kHz.\n    )\n    # The response.content is already bytes (the audio data)\n    # Alternatively, for streaming and saving to file: response.stream_to_file(\"output.mp3\")\n    audio_data_bytes = response.content\n    sample_rate = 24000 # OpenAI TTS model tts-1 outputs 24kHz\n    return audio_data_bytes, sample_rate\n\nif __name__ == \"__main__\":\n    print(\"Testing Text-to-Speech API...\")\n    # The OpenAI client will raise an error if API key is not found or invalid.\n    # No explicit check here to keep it minimal.\n    text = \"Hello from PocketFlow! This is a test of the text-to-speech functionality.\"\n    audio_bytes, rate = text_to_speech_api(text)\n    if audio_bytes and rate:\n        print(f\"Successfully converted text to speech. Audio data length: {len(audio_bytes)} bytes, Sample rate: {rate} Hz.\")\n        with open('tts_output.mp3', 'wb') as f:\n            f.write(audio_bytes)\n        print(\"Saved TTS output to tts_output.mp3\")\n    else: \n        print(\"Failed to convert text to speech (API returned empty data).\")"
  },
  {
    "path": "cookbook/pocketflow-workflow/README.md",
    "content": "# Article Writing Workflow\n\nA PocketFlow example that demonstrates an article writing workflow using a sequence of LLM calls.\n\n## Features\n\n- Generate a simple outline with up to 3 main sections using YAML structured output\n- Write concise (100 words max) content for each section in simple terms\n- Apply a conversational, engaging style to the final article\n\n## Getting Started\n\n1. Install the required dependencies:\n\n```bash\npip install -r requirements.txt\n```\n\n2. Set your OpenAI API key as an environment variable:\n\n```bash\nexport OPENAI_API_KEY=your_api_key_here\n```\n\n3. Run the application with a default topic (\"AI Safety\"):\n\n```bash\npython main.py\n```\n\n4. Or specify your own topic:\n\n```bash\npython main.py Climate Change\n```\n\n## How It Works\n\nThe workflow consists of three sequential nodes:\n\n```mermaid\ngraph LR\n    Outline[Generate Outline] --> Write[Write Content]\n    Write --> Style[Apply Style]\n```\n\nHere's what each node does:\n\n1. **Generate Outline**: Creates a simple outline with up to 3 main sections using YAML structured output\n2. **Write Simple Content**: Writes a concise 100-word explanation for each section\n3. **Apply Style**: Rewrites the combined content in a conversational, engaging style\n\n## Files\n\n- [`main.py`](./main.py): Main entry point for running the article workflow\n- [`flow.py`](./flow.py): Defines the flow that connects the nodes\n- [`nodes.py`](./nodes.py): Contains the node classes for each step in the workflow\n- [`utils/call_llm.py`](./utils/call_llm.py): LLM utility function\n- [`requirements.txt`](./requirements.txt): Lists the required dependencies\n\n## Example Output\n\n```\n=== Starting Article Workflow on Topic: AI Safety ===\n\n\n===== OUTLINE (YAML) =====\n\nsections:\n- Introduction to AI Safety\n- Key Challenges in AI Safety\n- Strategies for Ensuring AI Safety\n\n\n===== PARSED OUTLINE =====\n\n1. Introduction to AI Safety\n2. Key Challenges in AI Safety\n3. Strategies for Ensuring AI Safety\n\n=========================\n\n\n===== SECTION CONTENTS =====\n\n--- Introduction to AI Safety ---\nAI Safety is about making sure that artificial intelligence (AI) systems are helpful and not harmful. Imagine teaching a robot to help with chores. AI Safety is like setting ground rules for the robot so it doesn't accidentally cause trouble, like mistaking a pet for a toy. By ensuring AI systems understand their tasks and limitations, we can trust them to act safely. It's about creating guidelines and checks to ensure AI assists us without unintended consequences.\n\n--- Key Challenges in AI Safety ---\nAI safety is about ensuring that artificial intelligence systems operate in ways that are beneficial and not harmful. One key challenge is making sure AI makes decisions that align with human values. Imagine teaching a robot to fetch coffee, but it ends up knocking things over because it doesn't understand the mess it creates. Similarly, if AI systems don't fully grasp human intentions, they might act in unexpected ways. The task is to make AI smart enough to achieve goals without causing problems, much like training a puppy to follow rules without chewing on your shoes.\n\n--- Strategies for Ensuring AI Safety ---\nEnsuring AI safety is about making sure artificial intelligence behaves as expected and doesn’t cause harm. Imagine AI as a new driver on the road; we need rules and safeguards to prevent accidents. By testing AI systems under different conditions, setting clear rules for their behavior, and keeping human oversight, we can manage risks. For instance, just as cars have brakes to ensure safety, AI systems need to have fail-safes. This helps in building trust and avoiding unexpected issues, keeping both humans and AI on the right track.\n\n===========================\n\n\n===== FINAL ARTICLE =====\n\n# Welcome to the World of AI Safety\n\nHave you ever wondered what it would be like to have your very own robot helping you around the house? Sounds like a dream, right? But let’s hit pause for a moment. What if this robot mistook your fluffy cat for a toy? That’s exactly where AI Safety comes in. Think of AI Safety as setting some friendly ground rules for your household helper, ensuring that it knows the difference between doing chores and causing a bit of chaos. It’s all about making sure our AI allies play by the rules, making life easier without those pesky accidental hiccups.\n\n# Navigating the Maze of AI Challenges\n\nPicture this: you've asked your trusty robot to grab you a cup of coffee. But instead, it sends mugs flying and spills coffee because it doesn’t quite get the concept of a mess. Frustrating, isn’t it? One of the biggest hurdles in AI Safety is aligning AI decisions with our human values and intentions. It’s like training a puppy not to gnaw on your favorite pair of shoes. Our job is to teach AI how to reach its goals without stepping on our toes, all while being as reliable and lovable as a well-trained pup.\n\n# Steering AI Toward Safe Horizons\n\nNow, how do we keep our AI friends on the straight and narrow? Imagine AI as a new driver learning to navigate the roads of life. Just like we teach new drivers the rules of the road and equip cars with brakes for safety, we provide AI with guidelines and fail-safes to prevent any unintended mishaps. Testing AI systems in various scenarios and keeping a watchful human eye on them ensures they don’t veer off track. It’s all about building trust and creating a partnership where both humans and AI are cruising smoothly together.\n\n# Wrapping It Up\n\nAt the end of the day, AI Safety is about creating a harmonious relationship between humans and machines, where we trust our metal companions to support us without the fear of unexpected surprises. By setting boundaries and ensuring understanding, we’re not just building smarter machines—we’re crafting a future where AI and humanity can thrive together. So, next time you’re imagining that helpful robot assistant, rest easy knowing that AI Safety is making sure it's ready to lend a hand without dropping the ball—or your coffee mug!\n\n========================\n\n\n=== Workflow Completed ===\n\nTopic: AI Safety\nOutline Length: 96 characters\nDraft Length: 1690 characters\nFinal Article Length: 2266 characters\n```\n"
  },
  {
    "path": "cookbook/pocketflow-workflow/flow.py",
    "content": "from pocketflow import Flow\nfrom nodes import GenerateOutline, WriteSimpleContent, ApplyStyle\n\ndef create_article_flow():\n    \"\"\"\n    Create and configure the article writing workflow\n    \"\"\"\n    # Create node instances\n    outline_node = GenerateOutline()\n    write_node = WriteSimpleContent()\n    style_node = ApplyStyle()\n    \n    # Connect nodes in sequence\n    outline_node >> write_node >> style_node\n    \n    # Create flow starting with outline node\n    article_flow = Flow(start=outline_node)\n    \n    return article_flow"
  },
  {
    "path": "cookbook/pocketflow-workflow/main.py",
    "content": "from flow import create_article_flow\n\ndef run_flow(topic=\"AI Safety\"):\n    \"\"\"\n    Run the article writing workflow with a specific topic\n    \n    Args:\n        topic (str): The topic for the article\n    \"\"\"\n    # Initialize shared data with the topic\n    shared = {\"topic\": topic}\n    \n    # Print starting message\n    print(f\"\\n=== Starting Article Workflow on Topic: {topic} ===\\n\")\n    \n    # Run the flow\n    flow = create_article_flow()\n    flow.run(shared)\n    \n    # Output summary\n    print(\"\\n=== Workflow Completed ===\\n\")\n    print(f\"Topic: {shared['topic']}\")\n    print(f\"Outline Length: {len(shared['outline'])} characters\")\n    print(f\"Draft Length: {len(shared['draft'])} characters\")\n    print(f\"Final Article Length: {len(shared['final_article'])} characters\")\n    \n    return shared\n\nif __name__ == \"__main__\":\n    import sys\n    \n    # Get topic from command line if provided\n    topic = \"AI Safety\"  # Default topic\n    if len(sys.argv) > 1:\n        topic = \" \".join(sys.argv[1:])\n    \n    run_flow(topic)"
  },
  {
    "path": "cookbook/pocketflow-workflow/nodes.py",
    "content": "import re\nfrom pocketflow import Node, BatchNode\nfrom utils.call_llm import call_llm\nimport yaml\n\nclass GenerateOutline(Node):\n    def prep(self, shared):\n        return shared[\"topic\"]\n    \n    def exec(self, topic):\n        prompt = f\"\"\"\nCreate a simple outline for an article about {topic}.\nInclude at most 3 main sections (no subsections).\n\nOutput the sections in YAML format as shown below:\n\n```yaml\nsections:\n    - |\n        First section \n    - |\n        Second section\n    - |\n        Third section\n```\"\"\"\n        response = call_llm(prompt)\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        structured_result = yaml.safe_load(yaml_str)\n        return structured_result\n    \n    def post(self, shared, prep_res, exec_res):\n        # Store the structured data\n        shared[\"outline_yaml\"] = exec_res\n        \n        # Extract sections\n        sections = exec_res[\"sections\"]\n        shared[\"sections\"] = sections\n        \n        # Format for display\n        formatted_outline = \"\\n\".join([f\"{i+1}. {section}\" for i, section in enumerate(sections)])\n        shared[\"outline\"] = formatted_outline\n        \n        # Display the results\n        print(\"\\n===== OUTLINE (YAML) =====\\n\")\n        print(yaml.dump(exec_res, default_flow_style=False))\n        print(\"\\n===== PARSED OUTLINE =====\\n\")\n        print(formatted_outline)\n        print(\"\\n=========================\\n\")\n        \n        return \"default\"\n\nclass WriteSimpleContent(BatchNode):\n    def prep(self, shared):\n        # Get the list of sections to process and store for progress tracking\n        self.sections = shared.get(\"sections\", [])\n        return self.sections\n    \n    def exec(self, section):\n        prompt = f\"\"\"\nWrite a short paragraph (MAXIMUM 100 WORDS) about this section:\n\n{section}\n\nRequirements:\n- Explain the idea in simple, easy-to-understand terms\n- Use everyday language, avoiding jargon\n- Keep it very concise (no more than 100 words)\n- Include one brief example or analogy\n\"\"\"\n        content = call_llm(prompt)\n        \n        # Show progress for this section\n        current_section_index = self.sections.index(section) if section in self.sections else 0\n        total_sections = len(self.sections)\n        print(f\"✓ Completed section {current_section_index + 1}/{total_sections}: {section}\")\n        \n        return section, content\n    \n    def post(self, shared, prep_res, exec_res_list):\n        # exec_res_list contains [(section, content), (section, content), ...]\n        section_contents = {}\n        all_sections_content = []\n        \n        for section, content in exec_res_list:\n            section_contents[section] = content\n            all_sections_content.append(f\"## {section}\\n\\n{content}\\n\")\n        \n        draft = \"\\n\".join(all_sections_content)\n        \n        # Store the section contents and draft\n        shared[\"section_contents\"] = section_contents\n        shared[\"draft\"] = draft\n        \n        print(\"\\n===== SECTION CONTENTS =====\\n\")\n        for section, content in section_contents.items():\n            print(f\"--- {section} ---\")\n            print(content)\n            print()\n        print(\"===========================\\n\")\n        \n        return \"default\"\n\nclass ApplyStyle(Node):\n    def prep(self, shared):\n        \"\"\"\n        Get the draft from shared data\n        \"\"\"\n        return shared[\"draft\"]\n    \n    def exec(self, draft):\n        \"\"\"\n        Apply a specific style to the article\n        \"\"\"\n        prompt = f\"\"\"\n        Rewrite the following draft in a conversational, engaging style:\n        \n        {draft}\n        \n        Make it:\n        - Conversational and warm in tone\n        - Include rhetorical questions that engage the reader\n        - Add analogies and metaphors where appropriate\n        - Include a strong opening and conclusion\n        \"\"\"\n        return call_llm(prompt)\n    \n    def post(self, shared, prep_res, exec_res):\n        \"\"\"\n        Store the final article in shared data\n        \"\"\"\n        shared[\"final_article\"] = exec_res\n        print(\"\\n===== FINAL ARTICLE =====\\n\")\n        print(exec_res)\n        print(\"\\n========================\\n\")\n        return \"default\" "
  },
  {
    "path": "cookbook/pocketflow-workflow/requirements.txt",
    "content": "pocketflow>=0.0.1\nopenai>=1.0.0\npyyaml>=6.0 "
  },
  {
    "path": "cookbook/pocketflow-workflow/utils/call_llm.py",
    "content": "import os\nfrom openai import OpenAI\n\ndef call_llm(prompt):    \n    client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"your-api-key\"))\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}]\n    )\n    return r.choices[0].message.content\n\n# Example usage\nif __name__ == \"__main__\":\n    print(call_llm(\"Tell me a short joke\")) "
  },
  {
    "path": "cookbook/pocketflow_demo.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"! pip install pocketflow\\n\",\n    \"! pip install faiss-cpu\\n\",\n    \"! pip install openai\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"<p style=\\\"font-family: Arial, sans-serif; font-size: 36px; font-weight: bold; color: #333; margin: 0; padding: 0;\\\">\\n\",\n    \"Cookbook: Pocket Flow + Cursor AI\\n\",\n    \"</p>\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"<p style=\\\"font-family: Arial, sans-serif; font-size: 24px; font-weight: bold; color: #333; margin: 4px 0; padding: 0;\\\">\\n\",\n    \"1. Utility Function\\n\",\n    \"</p>\\n\",\n    \"\\n\",\n    \"<p style=\\\"font-family: Arial, sans-serif; font-size: 16px; color: #333;\\\">\\n\",\n    \"    Utility Functions are the <b>helper functions</b> like <i>calling an LLM, generating embeddings, or using external APIs</i>.  Pocket Flow is deliberately kept minimal and does <b>NOT</b> provide any of these. \\n\",\n    \"</p>\\n\",\n    \"\\n\",\n    \"<p style=\\\"font-family: Arial, sans-serif; font-size: 16px; color: #333;\\\">\\n\",\n    \"But don’t worry: you can simply ask Cursor AI to create them for you. \\n\",\n    \"</p>\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"> Help me implement (1) `call_llm` function that takes a prompt and returns the response from the OpenAI gpt-4o model. (2) `get_embedding` function that takes a text and returns the embedding from the OpenAI text-embedding-ada-002 model. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from openai import OpenAI\\n\",\n    \"import os\\n\",\n    \"\\n\",\n    \"def call_llm(prompt):\\n\",\n    \"    client = OpenAI(api_key=API_KEY)\\n\",\n    \"    response = client.chat.completions.create(\\n\",\n    \"        model=\\\"gpt-4o\\\",\\n\",\n    \"        messages=[{\\\"role\\\": \\\"user\\\", \\\"content\\\": prompt}]\\n\",\n    \"    )\\n\",\n    \"    return response.choices[0].message.content\\n\",\n    \"\\n\",\n    \"def get_embedding(text):\\n\",\n    \"    client = OpenAI(api_key=API_KEY)\\n\",\n    \"    response = client.embeddings.create(\\n\",\n    \"        model=\\\"text-embedding-ada-002\\\",\\n\",\n    \"        input=text\\n\",\n    \"    )\\n\",\n    \"    return response.data[0].embedding\\n\",\n    \"\\n\",\n    \"# Example usage:\\n\",\n    \"response = call_llm(\\\"What's the meaning of life?\\\")\\n\",\n    \"print(response)\\n\",\n    \"embedding = get_embedding(\\\"What's the meaning of life?\\\")\\n\",\n    \"print(embedding)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"<p style=\\\"font-family: Arial, sans-serif; font-size: 24px; font-weight: bold; color: #333; margin: 4px 0; padding: 0;\\\">\\n\",\n    \"2. Node\\n\",\n    \"</p>\\n\",\n    \"\\n\",\n    \"  <!-- Description of a Node -->\\n\",\n    \"  <p style=\\\"font-family: Arial, sans-serif; font-size: 16px; color: #333;\\\">\\n\",\n    \"    A <strong>Node</strong> is your smallest unit of work with 3 steps \\n\",\n    \"    <code>prep-&gt;exec-&gt;post</code>:\\n\",\n    \"  </p>\\n\",\n    \"\\n\",\n    \"<!-- Ordered list of steps with spacing between lines inside each list item -->\\n\",\n    \"<ol style=\\\"font-family: Arial, sans-serif; font-size: 16px; color: #333; margin: 20px 0; padding-left: 20px;\\\">\\n\",\n    \"\\n\",\n    \"<li style=\\\"margin-bottom: 16px;\\\">\\n\",\n    \"    <p style=\\\"margin: 0 0 8px 0;\\\">\\n\",\n    \"    <code>prep(shared)</code>\\n\",\n    \"    </p>\\n\",\n    \"    <p style=\\\"margin: 0 0 8px 0;\\\">\\n\",\n    \"    - Reads and preprocess data from the <strong>shared store</strong>.\\n\",\n    \"    </p>\\n\",\n    \"    <p style=\\\"margin: 0;\\\">\\n\",\n    \"    - E.g., load a file, query a database, or turn data into a string.\\n\",\n    \"    </p>\\n\",\n    \"</li>\\n\",\n    \"\\n\",\n    \"<li style=\\\"margin-bottom: 16px;\\\">\\n\",\n    \"    <p style=\\\"margin: 0 0 8px 0;\\\">\\n\",\n    \"    <code>exec(prep_res)</code>\\n\",\n    \"    </p>\\n\",\n    \"    <p style=\\\"margin: 0 0 8px 0;\\\">\\n\",\n    \"    - Executes the core logic\\n\",\n    \"    </p>\\n\",\n    \"    <p style=\\\"margin: 0 0 8px 0;\\\">\\n\",\n    \"    - E.g., call an LLM, invoke remote APIs, or embed texts.\\n\",\n    \"    </p>\\n\",\n    \"</li>\\n\",\n    \"\\n\",\n    \"<li style=\\\"margin-bottom: 16px;\\\">\\n\",\n    \"    <p style=\\\"margin: 0 0 8px 0;\\\">\\n\",\n    \"    <code>post(shared, prep_res, exec_res)</code>\\n\",\n    \"    </p>\\n\",\n    \"    <p style=\\\"margin: 0;\\\">\\n\",\n    \"    - Writes data back to the <strong>shared store</strong>.\\n\",\n    \"    </p>\\n\",\n    \"</li>\\n\",\n    \"\\n\",\n    \"</ol>\\n\",\n    \"\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"<img\\n\",\n    \"  src=\\\"data:image/png;base64,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\\\"\\n\",\n    \"  alt=\\\"My Image\\\"\\n\",\n    \"  width=\\\"300\\\"\\n\",\n    \"/>\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"> Help me implement a single summarization node that reads data from the shared store, calls an LLM to summarize the text into 50 words, and writes the summary back to the shared store. Then, test it with a shared store that have pre-loaded data from `./data/PaulGrahamEssaysLarge/before.txt`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Summary: This essay highlights the counterintuitive nature of startups, emphasizing that instincts often lead to mistakes. Key advice includes trusting instincts about people, not needing deep startup knowledge, and focusing on creating products users want. Startups are all-consuming, best pursued after college, and require openness to learning and serendipity.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from pocketflow import Node\\n\",\n    \"\\n\",\n    \"class SummarizeNode(Node):\\n\",\n    \"    def prep(self, shared):\\n\",\n    \"        # Read data from shared store\\n\",\n    \"        return shared[\\\"data\\\"][\\\"before.txt\\\"]\\n\",\n    \"        \\n\",\n    \"    def exec(self, text):\\n\",\n    \"        # Call LLM to summarize\\n\",\n    \"        prompt = f\\\"Summarize this text in 50 words:\\\\n\\\\n{text}\\\"\\n\",\n    \"        return call_llm(prompt)\\n\",\n    \"    \\n\",\n    \"    def post(self, shared, prep_res, exec_res):\\n\",\n    \"        # Store the summary back\\n\",\n    \"        shared[\\\"summary\\\"] = exec_res\\n\",\n    \"        # No specific next action needed\\n\",\n    \"        return \\\"default\\\"\\n\",\n    \"\\n\",\n    \"# Create test data\\n\",\n    \"shared = {\\n\",\n    \"    \\\"data\\\": {},\\n\",\n    \"    \\\"summary\\\": None\\n\",\n    \"}\\n\",\n    \"\\n\",\n    \"# Load the file\\n\",\n    \"with open(\\\"./data/PaulGrahamEssaysLarge/before.txt\\\", \\\"r\\\") as f:\\n\",\n    \"    shared[\\\"data\\\"][\\\"before.txt\\\"] = f.read()\\n\",\n    \"\\n\",\n    \"# Create and run the node\\n\",\n    \"summarize_node = SummarizeNode()\\n\",\n    \"summarize_node.run(shared)\\n\",\n    \"\\n\",\n    \"# Print the result\\n\",\n    \"print(\\\"Summary:\\\", shared[\\\"summary\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"  <!-- Title -->\\n\",\n    \"  <p style=\\\"font-family: Arial, sans-serif; font-size: 24px; font-weight: bold; color: #333; \\\">\\n\",\n    \"    3. Batch\\n\",\n    \"  </p>\\n\",\n    \"\\n\",\n    \"  <!-- Description of Batch processing -->\\n\",\n    \"  <p style=\\\"font-family: Arial, sans-serif; font-size: 16px; color: #333; margin-bottom: 16px;\\\">\\n\",\n    \"    <strong>Batch</strong> helps repeat the same work multiple items. \\n\",\n    \"    Instead of calling <code style=\\\"background: #f2f2f2; padding: 2px 4px; border-radius: 3px;\\\">exec()</code> once, a Batch Node calls \\n\",\n    \"    <code style=\\\"background: #f2f2f2; padding: 2px 4px; border-radius: 3px;\\\">exec()</code> \\n\",\n    \"    for each item in a list from <code style=\\\"background: #f2f2f2; padding: 2px 4px; border-radius: 3px;\\\">prep()</code>. \\n\",\n    \"  </p>\\n\",\n    \"  <p style=\\\"font-family: Arial, sans-serif; font-size: 16px; color: #333; margin-bottom: 16px;\\\">\\n\",\n    \"    Think of it as \\\"item-by-item\\\" processing:\\n\",\n    \"  </p>\\n\",\n    \"\\n\",\n    \"  <!-- Bullet points -->\\n\",\n    \"  <ul style=\\\"font-family: Arial, sans-serif; font-size: 16px; color: #333; list-style-type: disc; padding-left: 20px;\\\">\\n\",\n    \"    <li style=\\\"margin-bottom: 16px;\\\">\\n\",\n    \"      <code>prep(shared)</code>: Return a list of items.\\n\",\n    \"    </li>\\n\",\n    \"    <li style=\\\"margin-bottom: 16px;\\\">\\n\",\n    \"      <code>exec(item)</code>: Called once per item.\\n\",\n    \"    </li>\\n\",\n    \"    <li style=\\\"margin-bottom: 16px;\\\">\\n\",\n    \"      <code>post(shared, item_list, results_list)</code>: Combines all results.\\n\",\n    \"    </li>\\n\",\n    \"  </ul>\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"<img\\n\",\n    \"  src=\\\"data:image/png;base64,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\\\"\\n\",\n    \"  alt=\\\"My Image\\\"\\n\",\n    \"  width=\\\"150\\\"\\n\",\n    \"/>\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"> Help me implement a batch summarization node that reads the list of data from the shared store, calls an LLM to summarize the text into 50 words, and writes the summary back to the shared store. Then, test it with a shared store that have pre-loaded all text files from `./data/PaulGrahamEssaysLarge/`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Summaries:\\n\",\n      \"\\n\",\n      \"aord.txt:\\n\",\n      \"The text discusses the critical concern of whether startups are \\\"default alive\\\" or \\\"default dead,\\\" meaning whether they can reach profitability with existing resources. Many founders are unaware of this status. Addressing this concern early is vital since assumptions about easy fundraising can be misleading. Over-hiring is a common pitfall, emphasizing growth over prudent scaling.\\n\",\n      \"\\n\",\n      \"apple.txt:\\n\",\n      \"Apple's App Store approval process is harming its reputation with developers, damaging their goodwill and causing app delays. The approval system, akin to outdated software publishing, obstructs modern iterative app development. This misalignment with programmers' needs risks alienating talented potential employees and developers essential for Apple's platform success.\\n\",\n      \"\\n\",\n      \"avg.txt:\\n\",\n      \"In 1995, Paul Graham and Robert Morris founded Viaweb, a startup enabling users to create online stores. Using Lisp for its innovative capabilities, they gained a competitive edge due to Lisp's rapid development potential. Viaweb's success highlighted Lisp’s power, challenging conventional language choices and showcasing unconventional advantages in business.\\n\",\n      \"\\n\",\n      \"before.txt:\\n\",\n      \"The text advises potential startup founders to understand the counterintuitive nature of startups, emphasizing trust in instincts about people, focusing on solving user problems, and avoiding the illusion of gaming the system. It suggests gaining broad knowledge, exploring diverse interests, and delaying startup efforts until post-college to maximize potential and personal growth.\\n\",\n      \"\\n\",\n      \"addiction.txt:\\n\",\n      \"The text discusses the accelerating process of technological progress, leading to more addictive forms of various substances and experiences. It warns that this trend will continue, making it harder to distinguish between beneficial and harmful advancements. Society must adapt by developing new customs to manage increasing addiction, while individuals need to find personal strategies to avoid negative impacts.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from pocketflow import BatchNode\\n\",\n    \"import os\\n\",\n    \"\\n\",\n    \"class BatchSummarizeNode(BatchNode):\\n\",\n    \"    def prep(self, shared):\\n\",\n    \"        # Return list of (filename, content) tuples from shared store\\n\",\n    \"        return [(fn, content) for fn, content in shared[\\\"data\\\"].items()]\\n\",\n    \"        \\n\",\n    \"    def exec(self, item):\\n\",\n    \"        # Unpack the filename and content\\n\",\n    \"        filename, text = item\\n\",\n    \"        # Call LLM to summarize\\n\",\n    \"        prompt = f\\\"Summarize this text in 50 words:\\\\n\\\\n{text}\\\"\\n\",\n    \"        summary = call_llm(prompt)\\n\",\n    \"        return filename, summary\\n\",\n    \"    \\n\",\n    \"    def post(self, shared, prep_res, exec_res_list):\\n\",\n    \"        # Store all summaries in a dict by filename\\n\",\n    \"        shared[\\\"summaries\\\"] = {\\n\",\n    \"            filename: summary \\n\",\n    \"            for filename, summary in exec_res_list\\n\",\n    \"        }\\n\",\n    \"        return \\\"default\\\"\\n\",\n    \"\\n\",\n    \"# Create test data structure\\n\",\n    \"shared = {\\n\",\n    \"    \\\"data\\\": {},\\n\",\n    \"    \\\"summaries\\\": {}\\n\",\n    \"}\\n\",\n    \"\\n\",\n    \"# Load all files from the directory\\n\",\n    \"path = \\\"./data/PaulGrahamEssaysLarge\\\"\\n\",\n    \"for filename in os.listdir(path):\\n\",\n    \"    with open(os.path.join(path, filename), \\\"r\\\") as f:\\n\",\n    \"        shared[\\\"data\\\"][filename] = f.read()\\n\",\n    \"\\n\",\n    \"# Create and run the batch node\\n\",\n    \"batch_summarize = BatchSummarizeNode()\\n\",\n    \"batch_summarize.run(shared)\\n\",\n    \"\\n\",\n    \"# Print results\\n\",\n    \"print(\\\"Summaries:\\\")\\n\",\n    \"for filename, summary in shared[\\\"summaries\\\"].items():\\n\",\n    \"    print(f\\\"\\\\n{filename}:\\\")\\n\",\n    \"    print(summary)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"  <!-- Title -->\\n\",\n    \"  <p style=\\\"font-family: Arial, sans-serif; font-size: 24px; font-weight: bold; color: #333; \\\">\\n\",\n    \"    4. Flow\\n\",\n    \"  </p>\\n\",\n    \"\\n\",\n    \"  <!-- Brief description of Flow -->\\n\",\n    \"  <p style=\\\"font-family: Arial, sans-serif; font-size: 16px; color: #333; margin: 4px 0;\\\">\\n\",\n    \"    <strong>Flow</strong> connects your Nodes to a graph.\\n\",\n    \"  </p>\\n\",\n    \"\\n\",\n    \"  <!-- Unordered list of key points -->\\n\",\n    \"  <ul style=\\\"font-family: Arial, sans-serif; font-size: 16px; color: #333; list-style-type: disc; margin: 10px 0; padding-left: 20px;\\\">\\n\",\n    \"    <li style=\\\"margin-bottom: 8px;\\\">\\n\",\n    \"      <strong>Chaining</strong> \\n\",\n    \"      (<code style=\\\"background: #f2f2f2; padding: 2px 4px; border-radius: 3px;\\\">node_1 &gt;&gt; node_2</code>): Break down complex problems into simple chained steps.\\n\",\n    \"    </li>\\n\",\n    \"    <li style=\\\"margin-bottom: 8px;\\\">\\n\",\n    \"      <strong>Directed Branching</strong> \\n\",\n    \"      (<code style=\\\"background: #f2f2f2; padding: 2px 4px; border-radius: 3px;\\\">node_1 - \\\"action\\\" -&gt;&gt; node_2</code>): \\n\",\n    \"      Agentic decisions—where a Node’s \\n\",\n    \"      <code style=\\\"background: #f2f2f2; padding: 2px 4px; border-radius: 3px;\\\">post()</code> return the action string.\\n\",\n    \"    </li>\\n\",\n    \"    <li style=\\\"margin-bottom: 8px;\\\">\\n\",\n    \"      <strong>Set a Start Point</strong>: Create flow by specifying \\n\",\n    \"      <code style=\\\"background: #f2f2f2; padding: 2px 4px; border-radius: 3px;\\\">Flow(start=node_a)</code>. \\n\",\n    \"      Then call \\n\",\n    \"      <code style=\\\"background: #f2f2f2; padding: 2px 4px; border-radius: 3px;\\\">flow.run(shared)</code>.\\n\",\n    \"    </li>\\n\",\n    \"  </ul>\\n\",\n    \"\\n\",\n    \"  <!-- Closing note -->\\n\",\n    \"  <p style=\\\"font-family: Arial, sans-serif; font-size: 16px; color: #333; margin: 0; padding: 0;\\\">\\n\",\n    \"    That’s it! You can nest Flows, branch your actions, or keep it simple with a straight chain of Nodes.\\n\",\n    \"  </p>\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"<img\\n\",\n    \"  src=\\\"data:image/png;base64,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H4CoqCh9c51Rw5SqTZs2Ho9FzbtY14kQQgjf5hfTr93DQV5eHufPn/doxzU25eTJkxgMBm644QZ9c50pKCjweNyoUSOPx6L2XGyciBBCCN/kFxWZffv2af9/8uRJSktLPdoB9u7di9PppGXLlrRr107ffFWEhob6dJeeEEIIcbVpQaamZ1/UlbNnz7Jjxw7t8enTpyt03zgcDrZv3w5Ahw4dLlqRKS0tZfPmzUyePJnExETef/997Ha713B0pTp16nTJ33t2djYDBgwgMjKSyMhIoqOjZWuDyyCBUQgh/FMAPtylBLB//3727t2rPXY6nZSXl3ucc/LkSX744QcA7r//fgIDAz3aAcrKyli2bBnR0dH079+fefPmsWjRIqZMmYLVauX222/n9ddf5/jx4/qnVnDu3DkWLFhAz5496devH0lJSWzfvp2ysjKP83r37k3Dhg09jqkcDgezZ8/m4YcfZsOGDdrxU6dOkZSUxKhRo2o8zJw7d44PP/yQRx99FLPZjNlspl+/frz77rucO3dOf7oQQghx1fl819LGjRu5cOGC9rikpERbo0X166+/cvjwYRo1asSf/vQnjzaAI0eOMHz4cEaOHFnpB3Z5eTmffvopDzzwAF9//TVOp1N/CgBbtmzh/vvv54033mDnzp1kZmaSmppK7969GTRoEL/88gsAwcHBlQ70dTqdfPTRR0yfPh2AyMhIZs+ezbx587jzzjsBWLNmDd9//73umZfv119/pUePHrz55pvs3buXw4cPc/jwYTIzM5k2bRoff/xxhSAmhBBCXG0eQUa/+FV9V1hYyMaNGwEwGAza8Z9//tntrP9sTeB0OomIiKjQlXP8+HGsVitr1qzRjnXt2pV169aRm5tLbm4u69ato3fv3gAUFxczdOhQMjIy3F7lP3bt2sWwYcPIz88HIDAwkJYtW9K8eXMANmzYwNKlS3XPqigjI4O33noLALPZzPLly+nRowePPvooCxcu5I477sDpdLJnzx79Uy/L5s2b6dWrlzary2QyMXbsWObOncvChQvp3r0706dP55133tE/1af4cuVRCCGEdz5dkcnOzubHH3/EYDAwYcIEbVn1TZs2aTOXTp48qe0wbTabadasmcdrFBQUcPbsWe1xXFwcX3zxBVFRURgMBgwGA1FRUSQnJ7Nx40batGlDeXk548aN8xhkfPr0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\\\"\\n\",\n    \"  alt=\\\"My Image\\\"\\n\",\n    \"  width=\\\"350\\\"\\n\",\n    \"/>\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"> Help me implement a RAG chatbot that, given a user’s input question, finds the most relevant file based on embeddings and then answers the user's question. Test it with a shared store that has preloaded all text files from `./data/PaulGrahamEssaysLarge/`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"<img\\n\",\n    \"  src=\\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAxUAAADyCAYAAAA7tOitAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAEsqSURBVHhe7d15dBNV3wfwb0tBoVpZFIGmC/CiIIiIFhIqD4gibiBCQ6myKAiioqQCBQQFwRWKjVBFtrKIQkmQTTYXQLQ0BVlEBR926AQFZAtbsWnn/cPMPJlJUpqmS5J+P+dwDrlzk2aZO3N/dw0RRVEEERERERFRCYWqE4iIiIiIiLzBoIKIiIiIiHzCoIKIiIiIiHwSwjkVREQVTxAECIKA3Nxc+XFRNBqNOslFVFSUOknheq9xveNEFByKut4UdUy6Xrnj6XnO15WoqChotVrFcQpcDCqIiCqAIAgwmUywWCywWCyKY9JNV6fTITIyEgBgtVoVeSTuburububu0nzlTdDhTd7rBUNq0ndUHN68j+vx9n0WpTTfV2VUGue3u7LkLW/eh6cy7cyb93S9v329495wd76q05zLh/N1TPpM0nVPel5CQgJ0Oh2DjADGoIKIqBwJggCj0QiTySSnaTSaCruheqpouEv3VMFxlxfXqTR5ei2Jp9fEdY4RBRN1RV3iKd1ToOsu8Fb3GKg5H/f093zlrnFFo9Fg6tSp5X4tJN8xqCAiKidpaWkwGo3yY4PBAL1eX2Y37MroegHH9Y5Lrhf0uFPc18Z1Aq6ilOR9lQV3n7Wiz2N3FeOiuKtoe1Lcz3a99+DpdTylVyYmkwlGo1E+twwGA5KTk9XZyI8xqCAiKgeJiYmAozKm0+mQmpqqzkJEVKlJPRdGoxF6vR7Z2dnIyspSZyM/xaCCiKiMpaWlwWw2y8Oc9Hq9OgsRETk4XzMBIDMzU52F/BCDCiKiMmSxWOReiszMTI4TJiIqBpPJhBEjRkCr1SIqKoq9uwGA+1QQEZUhs9kMOMYHM6AgIioevV4v9+pmZ2e7rJJH/oc9FUREZSgmJgZarZbd90REXhIEQe7p5Vw0/8eeCiKiMiItG5uQkKA+RERE16HRaKDT6aDRaJCdna0+TH6GQQURURmRlkbkxGwiopIxGAwQBAGCIHAIlJ9jUEFEVEasVisDCiIiH2g0GnkVKPZW+DcGFUREZSQ7Oxtt27ZVJxMRkRekTfDYU+HfGFQQEZURd7sOExGRd6SeCl5T/RuDCiKiMqTT6dRJRETkBY1GA61Wy6DCzzGoICIiIiK/xn1+/B+DCiIiIiLya+z19X8MKoiIiIjIr3Fehf9jUEFEREREfk0KKsh/MaggIiIiooDAngr/xaCCiIiIiPweeyv8G4MKIiIiIgoIubm56iTyEwwqiIiIiCggcPiT/2JQQUREREQBwWq1qpPITzCoICIiIiIinzCoICIqA+yiJyIqfZxT4b8YVBARERGR3+PqT/6NQQUREREREfmEQQURERERBQQOLfVfDCqIiIiIiMgnDCqIiMoAW9OIiKgyYVBBREREREQ+YVBBREREREQ+YVBBREREREQ+YVBBRFSGuK46ERFVBgwqiIiIiMjvRUVFqZPIjzCoICIqA7m5ueokIiKioMWggoiIiIiIfMKggoioBEpjHwpBEGCxWNTJREREAYdBBRGRl0wmE4YPH65O9prJZEJ2drY6mYiIKOAwqCAi8pJer4cgCD71VgiCAKPRCL1erz5EREQUcBhUEBGVgE6ng8lkUicXm8lkgkaj4ZKzREQUFBhUEBGVQEJCAoxGY4l7KywWCxISEtTJRERUBDbE+C8GFUREJaDVaqHRaErcW2GxWDj0iYiIggaDCiKiEjIYDDCbzW57KwRB8NiiJh3zdJyIiCjQMKggIiohqafB2xWcTCYThz4REZUAd9X2XwwqiIh8IM2t8IbRaIROp1MnExERBSwGFUREPtDpdBAEodhzKywWCzQaDbRarfoQERFRwGJQQUTkA2nCttlsVqR7mi9hNps59ImIiIIOgwoiIh8lJCTAYrG4nbDtTOrR8BRwEBFR0SIjI9VJ5CcYVBAR+Sg5OblYy8tKx7mULBERBRsGFUREpUCn0yk2w3O3QonFYuFcCiIiH7Cn138xqCAiKgXSPAlPvRWCIDCoICLyQW5urjqJ/AiDCiKiUuA8Ydvd3Aop2EhOTlYfIiIiCngMKoiISklCQgIEQXC7GZ7ZbGa3PRERBS0GFUREpUSagK1eXlYQBAiCwKVkiYh84K4XmPwHgwoiolKi0Wig0WhgsVhQpUoVOZ1Dn4iISoe7RTDIPzCoICIqRQaDAQCwa9cuOc1sNnMZWSIiCmoMKoiISpFer4dGo5GDCmnoU9u2bdVZiYiIggaDCiKiUqbT6bB27VrFDtrsqSAi8o0gCFzwwo8xqCAiKmXOE7KNRiMnaBMRUdBjUEFEVMqkPSsknKBNRETBjkEFEVEZkHonpInbREREwYxBBRFRGZDmUDzwwAPqQ0REVALHjh3jnAo/FiKKoqhOJCIi323fvh1xcXHqZCIioqDDoIKIiIiIiHzCoIKIKh1BENRJsqKOAUBubq46qdxcbydZd8MC3KURERGVNgYVRFTunCvuzv93rrC7q9xbrVbFY3UF39PrFqWoSndRxzy5XsW/KOrPUxyePqendIn6s6kfqz9HZGSk/H/nvM75pHT1axGRK0EQkJ2drXispr7mSUpyrYCHv1HaNBpNufydQOHL9VB9HfbE+foMN9dorVarOF5WfA4qpEIhCAKsVqt8oktfhPMHLY3CUdEnqi8nh6S4J4ma+qQpqdL4DN7w9TeTzpvc3FxERUWhbdu25VpIyJXg2CU6NzdX/n2dfyfnfO7+L3E+F6X/q8uHu/NefcFUU5/j6seVmbvfwTlNfT12PuZ8jVcfc/e61/t9Pd0fpDKu0Wj421HQEBybYVosFlgsFsBRLnQ6nZzH3fWuuEq7rLi7tpY29fWmuNxdb9Q81TndKcv34YuiflP17xMZGSlfo6V7tES6lmq1Wuj1+iJf1xclDiosFgvMZjNMJpP6kPxmdTqdx5vG9ZT0B1Yr6x/cn5TVSaKmPpFLk/qC6lyJcVdIEhISyrSA0L/c3Qzh9Bu4K9vqMqwui+rHkqJ+S3fHvDkf1edXSbh7D8Xh6fOWhLvv2xP17+DM03vylO7M0/fgLt3db+R8A1SfU9JrWCwWaByVLoPB4Pa1ifyZdO00m80QHLtBJyQkQKfTsWGMypRUZ8rOzpbPP5Rx3cnroMJisWD48OEubw6OIIKFhMqaVEhycnLkiFyn0yEhIYHnXymSboZGo1FOK8sbYlEVWXfHiqosS9w9T82bCnp58Tb4Kc6NwV3F3pmn1/CUXtqcy7XJZFLc+KRKGcs5BRKLxYLExETAsV9NWVTiiIrL+Z4uNd5otdpS3Zy12EGF+s1IBYSooqnPzalTp7LS4QNBEGA0GuVeSK1WW+oXHqKiOAcY2dnZLsFFQkICz0fya4mJiXJPG+9J5E/UDYZarRaZmZnqbCVSrKAiLS2NFTbye9J5CkerECsd3lN/h2xZo4omCILcOy4FF4mJiQwsyG9JAYVer0dqaqr6MJFfcG5A1Gg0yMzM9Pl+X2RQIQgCEhMTIQhCqUYyRGXFubtZo9EgKytLnYXckCpuFouFARn5JWnoLQML8mdSQME6EwUC9UgPX+tMoeoEiRRQSOOuWDgoEGi1WmRlZcnR9ogRI9RZyA2pJTgzM5OVNPJLUiVNCi4yMzNhNpsVk7yJKlJaWhoDCgooGo0GycnJMBgMEATB5zpTlQkTJkxQJwLAoEGD0Lx5c3zzzTeYPXu2z10iROUlIiICERERMJvNiIiIgOCYyE3uSb2RHNpI/i4iIgI6nQ42mw0TJ07E1KlTMXz4cAwcOFCdlajc9e7dGxqNBuvWrVMfIvJrOp0OGo1GHv5c0jqT254KKVLJzc2FwWBgRYMCjl6vh8FgAADFUmqkJLWsMaCgQCG1rGk0GnlFKF9b14h8JS1sId13iAKNVG/ypc7kNqgwmUxISEiAxWLhUAgKWM6rkzkvi0r/slgsMBqNyMzMZEBBAWfq1KnIzs5GZGSkYldiooog3WO4KiYFsuTkZOh0uhLXmVyCirS0NOj1epjNZkbcFNA0jnXupeUpSUkq6wwoKBBJqxGazWZoNBq3G7ESlQeTyQRBELjSEwUFg8GA7OzsEvVWuAQVZrMZkZGR7KWgoCBt0iatcED/sjh2xmbDAQUyrVYLjUaDqKgomM1m9WGicpGTkwOwl4KChEajKXFvhSKosFgsEAQBVquVhYOCgtRbAUfATP8ym83cg4KCQnJyMkwmk3z/Iipv2dnZrDNRUJF6K7xdXU8RVOTm5kKv18s7mBIFA2kVA1Y4/sdkMrGXgoKC1rHjO1jGqYIIgoC2bduqk4kClrSdRG5urvpQkRRBhXRBFhyb3REFA41Gw/PZiTQMjL0UFCykRjDOnaLyJl1PS7oEJ5G/KsmwUkVQYbVa5d4KomAiTdj2tisvGOXk5LCMU1CRKnQs31RR2EhDwUaqN3nDZfiTxWJhNx4FHanS4W1XXjDi8EYKNlJvpLc3QKLSwICCgpFGo/G6MdZl9SewG4+CEC/6/yMIAr8PCjpSqxoDCypvvJ5SMCrJea0IKqKiooASvhCRv+O8iv9hGadgwwUZqCIIgiDXnYiCjVar9WqEh0tPBSsbFMwqe4XDZDIxuKKgxHsXEVHp86bepAgqIiMjOfSJghZbk/7F74GClVar5QpQVO4iIyPVSURBwdtGSJeeCqJgxQv/vy0O/B4oWHl7AyQiIs+sVqs6qUguS8qywkHBytvCEYysViu/BwpqPL+pvPGco2Dlbc8veyqoUuHFn8vqEhERUfF4M1+NQQVVGqxM8zug4KbRaHiOExGVEm8maUMdVOTm5noVkRBRYOE6/kRERFQW2FNBVMkwqKBgxZXNiIgqjiKoYGWDnNntdsydOxdvvPEG/vzzT/VhCkAs40RERHQ9JakvsKeiAkiV9dTUVFy5ckV92G+cPn0as2bNwhdffIGpU6dCFEV1loDD8db/KsnFgigQ8NwmKj179+5F165d0bdvX5w6dUp9mEiBQUUFsFgsmDRpEqZPn45t27apD/uNU6dO4fz584CjMu7PAVAwMplMSEtLUycHlUuXLiEjIwOffPIJzy8iIj9y7do1TJ48GXv27MGWLVuwZ88edRYiBZfhTxyTWvby8vIgiiKqV6+OOnXqqA/7jT///BN5eXkAgOPHj+Py5cvqLFSGoqKiYLFYEB8fjxEjRsBkMqmzeEVqwfWnxRimTJmCt99+G5MnT8bdd9+NRx99FPPmzcN///tf2Gw2dfZyc/r0aUyZMgVZWVnqQ0QUQBYuXOj1Wvv0r7Nnz2Lfvn3qZCKPKm1Phc1mg8ViwYYNG3DkyBFcu3ZNnaXM2O12AEBoaCjCwsLUh/3GwYMH1UkBLdACZq1Wi8zMTGRmZgIARowYIQcYFotFnb1YpIDCH4aIXL58GX/88Yf82G63Y9++fZgwYQIeeeQR3H333WjTpg1SU1MhCEK5Dr8zm81IT0/H6NGj8ffff6sPE1GA2LZtG4xGIzIyMvDNN9/4xbUvUBw6dAgnT54EAFSvXh233367OguRQqUJKux2O37++WcMHToUzZo1w913343ExEQMHjwYHTt2RNOmTbFgwYJyqbgcOXIEAHDLLbf4bU+F3W7Hb7/9pk6mCqDRaJCamoqsrCwkJCTAZDIhMTER8fHxAT08Kjw8HE2bNlUnK5w8eRLTp09HfHw8OnTogFWrVslBeXk4ffp0mS5SUFBQgDNnzpRrowZRZdKvXz80b94cGzZsQFpamhxgZGdnV2hvqL8TRREbNmwolzoR+aeSBOByUFGSJ/sju92OLVu2IDMzE2vXrkVmZiaGDBmCO++8Ez179sTq1avdjt0uLCzEZ599htOnT6sPlZnIyEiEh4erk/3C5cuXFbtP33rrrbjhhhsUeQJNZGSkOimgaDQaJCcnIysrCwaDAYIgwGg0+tx7UVEOHjyIH3/8ER07dsTKlSuxd+9eHDt2DEePHsXOnTvx6aef4t5775XzHzt2DK+++io6deqEX375RfFagSgvLw8vv/wyWrdujY4dO+L48ePqLOQlfxra52+uXbuGU6dOVbphrG3atIHBYMDAgQOh0+mQm5srBxdGoxFmsxm///67+mmV3oEDB/D111/Lj2+66SbccsstijxEai49FYF+Ud68eTP69u2LlJQUvPTSS0hJScG6devk1s1GjRqhX79++PjjjzFjxgzMmDED48aNQ/Xq1dUvVWYOHDgAAKhWrRpCQkLUh/3CtWvXFMM+atSo4ddDtSoTdXABx6TuQOq9OHfuHIYOHYpDhw7hwoULaNy4sRxgh4SEoE6dOnjiiSewYsUK7N69G2PGjMFNN90EOIKLHj16YOHChWXeinb16lWcOXNGnVwqjh49iq1btwIATpw4gdGjR+PixYvqbEQlIjWwSb3zd9xxB+Li4nDXXXfhnXfeKbOy88cff2Djxo1uG++8VVBQgJ9++glfffWVSw+l3W7H2bNnUVBQoEh3JyIiAo888ggMBgMMBgP0ej0iIiJcei9KMjzKufEtWNjtdsyaNQtnz56V06pWrYobb7xRkY9ILeh6KtypU6cOxowZgx07dmDTpk2YNGkSunfvjscffxyPP/44Bg0ahEWLFiE5ORm1a9dWP71UiaIoXxzr1auHGjVqqLP4BZvNprgp3H777X77XisrKbjIzMxEamoqtFptkb0X/lTG8/PzceHCBQDAr7/+qphboVarVi0MGTIEO3bswPjx41G9enXY7Xa8+eabWLVqlTp7qdu/fz+sVis2bNiA9957D6NHj8bo0aMxbdo0n5ZYjI2NxcMPPyw/zsrKwkcffVRmlb3KQAjyHePPnTuH7777DkePHlUfkomiiB9//BFxcXHo27ev2975vXv34urVq4o0id1ux5kzZ1wq8cWxe/du6PV6PP/883LA7KygoABff/01/vOf/yAmJgYxMTHo1q0bcnJy3J73+/fvx+DBg5GSkiKvPHT48GF0794djRs3xr333otGjRrhzTffLNb7jYiIgE6nk4OL5ORkdOnSBXBabc95eFQwn0tF2bRpE5YtW6ZOLleVtWct0Ln0VAS6G2+8UW79r169OqZNm4bt27djyJAhuPXWW9XZZffffz969epV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zopT7tXU54Inxc13Pb6cX6X1HgKJ+vtyfuypjKsrWBLpXtK2bdsSLXFI5K+k+5rzNfB61wtvrq0lUZLrcXFd77NVBPW1qjSVZsDijvq+68z5PJHy6RybNJblZ/Y5qHDHuaCUxUlUml9Iaf3opfme1MrytdVK4/cqzmt4unAV9Vznimywtpj7KynIyMnJKfJCVhzenM/elE9vXtebvP6kqPJREqXxep7KsrPi/h2r1aq4AbKcExEFjjIJKoiIiIiIqPJwu6M2ERERERFRcTGoICIiIiIinzCoICIiIiIinzCoICIqI9KiFURERMGOQQURURnJzs7G8OHD1clERERBh0EFERERERH5hEEFERERERH5hEEFERERERH5hEEFEVEZ4kRtIiKqDBhUEBGVkaioKHUSERFRUGJQQUREREREPmFQQUREREREPmFQQUREREREPmFQQUREREREPmFQQURUhrj6ExERVQYMKoiIyohGo1EnERERBSUGFURERERE5BMGFURERERE5BMGFUREZYhDoIiIqDJgUEFERERERD5hUEFEVIa4+hMREVUGDCqIiIiIiMgnDCqIiIiIiMgnDCqIiIiIiMgnDCqIiIiIiMgnDCqIiMoIJ2kTEVFlwaCCiIiIiIh8wqCCiIiIiIh8wqCCiIiIiIh8wqCCiIiIiIh8wqCCiIiIiIh8wqCCiKiM5ObmqpOIiIiCEoMKIiIiIiLyCYMKIiIiIiLyCYMKIiIiIiLyCYMKIiIiIiLyCYMKIiIiIiLyCYMKIqIyIgiCOomIiCgoMaggIiIiIiKfMKggIiIiIiKfMKggIiojzZo1Q/fu3dXJREREQSdEFEVRnUhERERERFRc7KkgIiIiIiKfMKggIiIiIiKfMKggIiIiIiKfMKggIiIiIiKfMKggIiIiIiKfMKggIiIiIiKfMKggIiIiIiKfMKggIgoQZ86cwciRIzF//nzk5eWpDxMREVUYBhVERAFi+/btWLp0KcaPH48ff/xRfZiIiKjCMKggIgoQBw8edPt/IiKiisaggogoAIiiiP3798uPDxw4oDhORERUkRhUEBEFgCtXruDEiRPqZCIiIr/AoIKIKACcOXMGR48eVScTERH5BQYVREQB4OTJkzh37pz8uH79+orjVLoEQYAgCOpkIiLygEEFEVEAuHDhAux2u/z4pptuUhyn0mU0GhEf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\\\"\\n\",\n    \"  alt=\\\"My Image\\\"\\n\",\n    \"  width=\\\"500\\\"\\n\",\n    \"/>\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\n\",\n      \"Q: how to find startup idea\\n\",\n      \"A: To find a startup idea, the context advises not to make a conscious effort to think of startup ideas, as this often results in bad and plausible-sounding ideas that can waste time. Instead, it suggests turning your mind into the type that generates startup ideas unconsciously. This can be achieved by:\\n\",\n      \"\\n\",\n      \"1. Learning extensively about things that matter.\\n\",\n      \"2. Working on problems that genuinely interest you.\\n\",\n      \"3. Collaborating with people you like and respect.\\n\",\n      \"\\n\",\n      \"By engaging in these activities, you'll naturally start to encounter ideas that have the potential to become startups, often without initially realizing it. The essay emphasizes that many successful startups, like Apple, Yahoo, Google, and Facebook, began as side projects rather than direct pursuits to start a company.\\n\",\n      \"\\n\",\n      \"Source: before.txt\\n\"\n     ]\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"/home/zh2408/.venv/lib/python3.9/site-packages/pocketflow/__init__.py:43: UserWarning: Flow ends: 'end' not found in ['answer', 'end']\\n\",\n      \"  if not nxt and curr.successors: warnings.warn(f\\\"Flow ends: '{action}' not found in {list(curr.successors)}\\\")\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from pocketflow import Node, Flow\\n\",\n    \"import faiss\\n\",\n    \"import numpy as np\\n\",\n    \"import os\\n\",\n    \"\\n\",\n    \"class PrepareEmbeddings(Node):\\n\",\n    \"    def prep(self, shared):\\n\",\n    \"        # Get list of (filename, content) pairs\\n\",\n    \"        return list(shared[\\\"data\\\"].items())\\n\",\n    \"        \\n\",\n    \"    def exec(self, items):\\n\",\n    \"        # Create embeddings for each document\\n\",\n    \"        embeddings = []\\n\",\n    \"        filenames = []\\n\",\n    \"        for filename, content in items:\\n\",\n    \"            embedding = get_embedding(content)\\n\",\n    \"            embeddings.append(embedding)\\n\",\n    \"            filenames.append(filename)\\n\",\n    \"            \\n\",\n    \"        # Create FAISS index\\n\",\n    \"        dim = len(embeddings[0])\\n\",\n    \"        index = faiss.IndexFlatL2(dim)\\n\",\n    \"        index.add(np.array(embeddings).astype('float32'))\\n\",\n    \"        \\n\",\n    \"        return index, filenames\\n\",\n    \"    \\n\",\n    \"    def post(self, shared, prep_res, exec_res):\\n\",\n    \"        # Store index and filenames in shared store\\n\",\n    \"        index, filenames = exec_res\\n\",\n    \"        shared[\\\"search_index\\\"] = index\\n\",\n    \"        shared[\\\"filenames\\\"] = filenames\\n\",\n    \"        return \\\"default\\\"\\n\",\n    \"\\n\",\n    \"class FindRelevantDocument(Node):\\n\",\n    \"    def prep(self, shared):\\n\",\n    \"        # Get user question\\n\",\n    \"        question = input(\\\"Enter your question (or press Enter to quit): \\\")\\n\",\n    \"        if not question:\\n\",\n    \"            return None\\n\",\n    \"        return question\\n\",\n    \"        \\n\",\n    \"    def exec(self, question):\\n\",\n    \"        if question is None:\\n\",\n    \"            return None\\n\",\n    \"            \\n\",\n    \"        # Get question embedding and search\\n\",\n    \"        query_embedding = get_embedding(question)\\n\",\n    \"        \\n\",\n    \"        # Search for most similar document\\n\",\n    \"        D, I = shared[\\\"search_index\\\"].search(\\n\",\n    \"            np.array([query_embedding]).astype('float32'),\\n\",\n    \"            k=1\\n\",\n    \"        )\\n\",\n    \"        most_relevant_idx = I[0][0]\\n\",\n    \"        most_relevant_file = shared[\\\"filenames\\\"][most_relevant_idx]\\n\",\n    \"        \\n\",\n    \"        return question, most_relevant_file\\n\",\n    \"        \\n\",\n    \"    def post(self, shared, prep_res, exec_res):\\n\",\n    \"        if exec_res is None:\\n\",\n    \"            return \\\"end\\\"\\n\",\n    \"            \\n\",\n    \"        question, filename = exec_res\\n\",\n    \"        shared[\\\"current_question\\\"] = question\\n\",\n    \"        shared[\\\"relevant_file\\\"] = filename\\n\",\n    \"        shared[\\\"context\\\"] = shared[\\\"data\\\"][filename]\\n\",\n    \"        return \\\"answer\\\"\\n\",\n    \"    \\n\",\n    \"class AnswerQuestion(Node):\\n\",\n    \"    def prep(self, shared):\\n\",\n    \"        return (\\n\",\n    \"            shared[\\\"current_question\\\"],\\n\",\n    \"            shared[\\\"context\\\"]\\n\",\n    \"        )\\n\",\n    \"        \\n\",\n    \"    def exec(self, inputs):\\n\",\n    \"        question, context = inputs\\n\",\n    \"        prompt = f\\\"\\\"\\\"\\n\",\n    \"Context: {context}\\n\",\n    \"\\n\",\n    \"Question: {question}\\n\",\n    \"\\n\",\n    \"Answer the question based on the context above. If the context doesn't contain relevant information, say so.\\n\",\n    \"Answer:\\\"\\\"\\\"\\n\",\n    \"        return call_llm(prompt)\\n\",\n    \"    \\n\",\n    \"    def post(self, shared, prep_res, exec_res):\\n\",\n    \"        print(f\\\"\\\\nQ: {shared['current_question']}\\\")\\n\",\n    \"        print(f\\\"A: {exec_res}\\\")\\n\",\n    \"        print(f\\\"\\\\nSource: {shared['relevant_file']}\\\")\\n\",\n    \"        return \\\"continue\\\"  # Loop back for more questions\\n\",\n    \"\\n\",\n    \"# Create test data\\n\",\n    \"shared = {\\\"data\\\": {}}\\n\",\n    \"\\n\",\n    \"# Load all files\\n\",\n    \"path = \\\"./data/PaulGrahamEssaysLarge\\\"\\n\",\n    \"for filename in os.listdir(path):\\n\",\n    \"    with open(os.path.join(path, filename), \\\"r\\\") as f:\\n\",\n    \"        shared[\\\"data\\\"][filename] = f.read()\\n\",\n    \"\\n\",\n    \"# Create nodes and flow\\n\",\n    \"prep_embeddings = PrepareEmbeddings()\\n\",\n    \"find_relevant = FindRelevantDocument()\\n\",\n    \"answer = AnswerQuestion()\\n\",\n    \"\\n\",\n    \"# Connect nodes\\n\",\n    \"prep_embeddings >> find_relevant\\n\",\n    \"find_relevant - \\\"answer\\\" >> answer\\n\",\n    \"find_relevant - \\\"end\\\" >> None\\n\",\n    \"answer - \\\"continue\\\" >> find_relevant\\n\",\n    \"\\n\",\n    \"# Create and run flow\\n\",\n    \"rag_flow = Flow(start=prep_embeddings)\\n\",\n    \"rag_flow.run(shared)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"myvenv\",\n   \"language\": \"python\",\n   \"name\": \"myvenv\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.9.2\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "docs/_config.yml",
    "content": "# Basic site settings\ntitle: Pocket Flow\ntagline: A 100-line LLM framework\ndescription: Pocket Flow – Minimalist LLM Framework in 100 Lines, Enabling LLMs to Program Themselves\n\n# Theme settings\nremote_theme: just-the-docs/just-the-docs\nsearch_enabled: true\n\n# SEO & sitemap\nplugins:\n  - jekyll-seo-tag\n  - jekyll-sitemap\n\njekyll-seo-tag:\n  social:\n    name: \"Pocket Flow\"\n    twitter: \"ZacharyHuang12\"\n    github: \"the-pocket/PocketFlow\"\n\n# Navigation\nnav_sort: case_sensitive\n\n# Aux links (shown in upper right)\naux_links:\n  \"View on GitHub\":\n    - \"//github.com/the-pocket/PocketFlow\"\n    \n# Color scheme\ncolor_scheme: light\n\n# Author settings\nauthor:\n    name: Zachary Huang\n    url: https://www.columbia.edu/~zh2408/\n    twitter: ZacharyHuang12\n\n# Mermaid settings\nmermaid:\n  version: \"9.1.3\"\n  config: |\n    directionLR\n\n# Callouts settings\ncallouts:\n  warning:\n    title: Warning\n    color: red\n  note:\n    title: Note\n    color: blue\n  best-practice:\n    title: Best Practice\n    color: green\n  \n# Custom navigation\nnav:\n  - Home: index.md\n  - GitHub: \"https://github.com/the-pocket/PocketFlow\"\n  - Discord: \"https://discord.gg/hUHHE9Sa6T\""
  },
  {
    "path": "docs/_includes/footer_custom.html",
    "content": "<!-- PocketFlow Chatbot - Start -->\n<script>\n(function() {\n    var script = document.createElement(\"script\");\n    script.src = \"https://askthispage.com/embed/chatbot.js\";\n    script.onload = function() {\n        initializeChatbot({\n            extra_urls: [\"https://github.com/The-Pocket/PocketFlow/blob/main/.cursorrules\"],\n            prefixes: [\"https://github.com/The-Pocket\",\"https://the-pocket.github.io/\"],\n            chatbotName: 'PocketFlow Website Chatbot',\n            wsUrl: 'wss://askthispage.com/api/ws/chat',\n            instruction: '',\n            isOpen: false\n        });\n    };\n    document.head.appendChild(script);\n})();\n</script>\n<!-- PocketFlow Chatbot - End -->"
  },
  {
    "path": "docs/core_abstraction/async.md",
    "content": "---\nlayout: default\ntitle: \"(Advanced) Async\"\nparent: \"Core Abstraction\"\nnav_order: 5\n---\n\n# (Advanced) Async\n\n**Async** Nodes implement `prep_async()`, `exec_async()`, `exec_fallback_async()`, and/or `post_async()`. This is useful for:\n\n1. **prep_async()**: For *fetching/reading data (files, APIs, DB)* in an I/O-friendly way.\n2. **exec_async()**: Typically used for async LLM calls.\n3. **post_async()**: For *awaiting user feedback*, *coordinating across multi-agents* or any additional async steps after `exec_async()`.\n\n**Note**: `AsyncNode` must be wrapped in `AsyncFlow`. `AsyncFlow` can also include regular (sync) nodes.\n\n### Example\n\n```python\nclass SummarizeThenVerify(AsyncNode):\n    async def prep_async(self, shared):\n        # Example: read a file asynchronously\n        doc_text = await read_file_async(shared[\"doc_path\"])\n        return doc_text\n\n    async def exec_async(self, prep_res):\n        # Example: async LLM call\n        summary = await call_llm_async(f\"Summarize: {prep_res}\")\n        return summary\n\n    async def post_async(self, shared, prep_res, exec_res):\n        # Example: wait for user feedback\n        decision = await gather_user_feedback(exec_res)\n        if decision == \"approve\":\n            shared[\"summary\"] = exec_res\n            return \"approve\"\n        return \"deny\"\n\nsummarize_node = SummarizeThenVerify()\nfinal_node = Finalize()\n\n# Define transitions\nsummarize_node - \"approve\" >> final_node\nsummarize_node - \"deny\"    >> summarize_node  # retry\n\nflow = AsyncFlow(start=summarize_node)\n\nasync def main():\n    shared = {\"doc_path\": \"document.txt\"}\n    await flow.run_async(shared)\n    print(\"Final Summary:\", shared.get(\"summary\"))\n\nasyncio.run(main())\n```"
  },
  {
    "path": "docs/core_abstraction/batch.md",
    "content": "---\nlayout: default\ntitle: \"Batch\"\nparent: \"Core Abstraction\"\nnav_order: 4\n---\n\n# Batch\n\n**Batch** makes it easier to handle large inputs in one Node or **rerun** a Flow multiple times. Example use cases:\n- **Chunk-based** processing (e.g., splitting large texts).\n- **Iterative** processing over lists of input items (e.g., user queries, files, URLs).\n\n## 1. BatchNode\n\nA **BatchNode** extends `Node` but changes `prep()` and `exec()`:\n\n- **`prep(shared)`**: returns an **iterable** (e.g., list, generator).\n- **`exec(item)`**: called **once** per item in that iterable.\n- **`post(shared, prep_res, exec_res_list)`**: after all items are processed, receives a **list** of results (`exec_res_list`) and returns an **Action**.\n\n\n### Example: Summarize a Large File\n\n```python\nclass MapSummaries(BatchNode):\n    def prep(self, shared):\n        # Suppose we have a big file; chunk it\n        content = shared[\"data\"]\n        chunk_size = 10000\n        chunks = [content[i:i+chunk_size] for i in range(0, len(content), chunk_size)]\n        return chunks\n\n    def exec(self, chunk):\n        prompt = f\"Summarize this chunk in 10 words: {chunk}\"\n        summary = call_llm(prompt)\n        return summary\n\n    def post(self, shared, prep_res, exec_res_list):\n        combined = \"\\n\".join(exec_res_list)\n        shared[\"summary\"] = combined\n        return \"default\"\n\nmap_summaries = MapSummaries()\nflow = Flow(start=map_summaries)\nflow.run(shared)\n```\n\n---\n\n## 2. BatchFlow\n\nA **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.\n\n### Key Differences from BatchNode\n\n**Important**: Unlike BatchNode, which processes items and modifies the shared store:\n\n1. BatchFlow returns **parameters to pass to the child Flow**, not data to process\n2. These parameters are accessed in child nodes via `self.params`, not from the shared store\n3. Each child Flow runs independently with a different set of parameters\n4. Child nodes can be regular Nodes, not BatchNodes (the batching happens at the Flow level)\n\n### Example: Summarize Many Files\n\n```python\nclass SummarizeAllFiles(BatchFlow):\n    def prep(self, shared):\n        # IMPORTANT: Return a list of param dictionaries (not data for processing)\n        filenames = list(shared[\"data\"].keys())  # e.g., [\"file1.txt\", \"file2.txt\", ...]\n        return [{\"filename\": fn} for fn in filenames]\n\n# Child node that accesses filename from params, not shared store\nclass LoadFile(Node):\n    def prep(self, shared):\n        # Access filename from params (not from shared)\n        filename = self.params[\"filename\"]  # Important! Use self.params, not shared\n        return filename\n        \n    def exec(self, filename):\n        with open(filename, 'r') as f:\n            return f.read()\n            \n    def post(self, shared, prep_res, exec_res):\n        # Store file content in shared\n        shared[\"current_file_content\"] = exec_res\n        return \"default\"\n\n# Summarize node that works on the currently loaded file\nclass Summarize(Node):\n    def prep(self, shared):\n        return shared[\"current_file_content\"]\n        \n    def exec(self, content):\n        prompt = f\"Summarize this file in 50 words: {content}\"\n        return call_llm(prompt)\n        \n    def post(self, shared, prep_res, exec_res):\n        # Store summary in shared, indexed by current filename\n        filename = self.params[\"filename\"]  # Again, using params\n        if \"summaries\" not in shared:\n            shared[\"summaries\"] = {}\n        shared[\"summaries\"][filename] = exec_res\n        return \"default\"\n\n# Create a per-file flow\nload_file = LoadFile()\nsummarize = Summarize()\nload_file >> summarize\nsummarize_file = Flow(start=load_file)\n\n# Wrap in a BatchFlow to process all files\nsummarize_all_files = SummarizeAllFiles(start=summarize_file)\nsummarize_all_files.run(shared)\n```\n\n### Under the Hood\n1. `prep(shared)` in the BatchFlow returns a list of param dicts—e.g., `[{\"filename\": \"file1.txt\"}, {\"filename\": \"file2.txt\"}, ...]`.\n2. The **BatchFlow** loops through each dict. For each one:\n   - It merges the dict with the BatchFlow's own `params` (if any): `{**batch_flow.params, **dict_from_prep}`\n   - It calls `flow.run(shared)` using the merged parameters\n   - **IMPORTANT**: These parameters are passed to the child Flow's nodes via `self.params`, NOT via the shared store\n3. This means the sub-Flow is run **repeatedly**, once for every param dict, with each node in the flow accessing the parameters via `self.params`.\n\n---\n\n## 3. Nested or Multi-Level Batches\n\nYou can nest a **BatchFlow** in another **BatchFlow**. For instance:\n- **Outer** batch: returns a list of directory param dicts (e.g., `{\"directory\": \"/pathA\"}`, `{\"directory\": \"/pathB\"}`, ...).\n- **Inner** batch: returning a list of per-file param dicts.\n\nAt 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.\n\n```python\n\nclass FileBatchFlow(BatchFlow):\n    def prep(self, shared):\n        # Access directory from params (set by parent)\n        directory = self.params[\"directory\"]\n        # e.g., files = [\"file1.txt\", \"file2.txt\", ...]\n        files = [f for f in os.listdir(directory) if f.endswith(\".txt\")]\n        return [{\"filename\": f} for f in files]\n\nclass DirectoryBatchFlow(BatchFlow):\n    def prep(self, shared):\n        directories = [ \"/path/to/dirA\", \"/path/to/dirB\"]\n        return [{\"directory\": d} for d in directories]\n\n# The actual processing node\nclass ProcessFile(Node):\n    def prep(self, shared):\n        # Access both directory and filename from params\n        directory = self.params[\"directory\"]  # From outer batch\n        filename = self.params[\"filename\"]    # From inner batch\n        full_path = os.path.join(directory, filename)\n        return full_path\n        \n    def exec(self, full_path):\n        # Process the file...\n        return f\"Processed {full_path}\"\n        \n    def post(self, shared, prep_res, exec_res):\n        # Store results, perhaps indexed by path\n        if \"results\" not in shared:\n            shared[\"results\"] = {}\n        shared[\"results\"][prep_res] = exec_res\n        return \"default\"\n\n# Set up the nested batch structure\nprocess_node = ProcessFile()\ninner_flow = FileBatchFlow(start=process_node)\nouter_flow = DirectoryBatchFlow(start=inner_flow)\n\n# Run it\nouter_flow.run(shared)\n```\n"
  },
  {
    "path": "docs/core_abstraction/communication.md",
    "content": "---\nlayout: default\ntitle: \"Communication\"\nparent: \"Core Abstraction\"\nnav_order: 3\n---\n\n# Communication\n\nNodes and Flows **communicate** in 2 ways:\n\n1. **Shared Store (for almost all the cases)** \n\n   - A global data structure (often an in-mem dict) that all nodes can read ( `prep()`) and write (`post()`).  \n   - Great for data results, large content, or anything multiple nodes need.\n   - You shall design the data structure and populate it ahead.\n     \n   - > **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).\n     {: .best-practice }\n\n2. **Params (only for [Batch](./batch.md))** \n   - 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**.\n   - Good for identifiers like filenames or numeric IDs, in Batch mode.\n\nIf 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).\n\n---\n\n## 1. Shared Store\n\n### Overview\n\nA shared store is typically an in-mem dictionary, like:\n```python\nshared = {\"data\": {}, \"summary\": {}, \"config\": {...}, ...}\n```\n\nIt 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.\n\n### Example\n\n```python\nclass LoadData(Node):\n    def post(self, shared, prep_res, exec_res):\n        # We write data to shared store\n        shared[\"data\"] = \"Some text content\"\n        return None\n\nclass Summarize(Node):\n    def prep(self, shared):\n        # We read data from shared store\n        return shared[\"data\"]\n\n    def exec(self, prep_res):\n        # Call LLM to summarize\n        prompt = f\"Summarize: {prep_res}\"\n        summary = call_llm(prompt)\n        return summary\n\n    def post(self, shared, prep_res, exec_res):\n        # We write summary to shared store\n        shared[\"summary\"] = exec_res\n        return \"default\"\n\nload_data = LoadData()\nsummarize = Summarize()\nload_data >> summarize\nflow = Flow(start=load_data)\n\nshared = {}\nflow.run(shared)\n```\n\nHere:\n- `LoadData` writes to `shared[\"data\"]`.\n- `Summarize` reads from `shared[\"data\"]`, summarizes, and writes to `shared[\"summary\"]`.\n\n---\n\n## 2. Params\n\n**Params** let you store *per-Node* or *per-Flow* config that doesn't need to live in the shared store. They are:\n- **Immutable** during a Node's run cycle (i.e., they don't change mid-`prep->exec->post`).\n- **Set** via `set_params()`.\n- **Cleared** and updated each time a parent Flow calls it.\n\n> Only set the uppermost Flow params because others will be overwritten by the parent Flow. \n> \n> If you need to set child node params, see [Batch](./batch.md).\n{: .warning }\n\nTypically, **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.\n\n### Example\n\n```python\n# 1) Create a Node that uses params\nclass SummarizeFile(Node):\n    def prep(self, shared):\n        # Access the node's param\n        filename = self.params[\"filename\"]\n        return shared[\"data\"].get(filename, \"\")\n\n    def exec(self, prep_res):\n        prompt = f\"Summarize: {prep_res}\"\n        return call_llm(prompt)\n\n    def post(self, shared, prep_res, exec_res):\n        filename = self.params[\"filename\"]\n        shared[\"summary\"][filename] = exec_res\n        return \"default\"\n\n# 2) Set params\nnode = SummarizeFile()\n\n# 3) Set Node params directly (for testing)\nnode.set_params({\"filename\": \"doc1.txt\"})\nnode.run(shared)\n\n# 4) Create Flow\nflow = Flow(start=node)\n\n# 5) Set Flow params (overwrites node params)\nflow.set_params({\"filename\": \"doc2.txt\"})\nflow.run(shared)  # The node summarizes doc2, not doc1\n```\n"
  },
  {
    "path": "docs/core_abstraction/flow.md",
    "content": "---\nlayout: default\ntitle: \"Flow\"\nparent: \"Core Abstraction\"\nnav_order: 2\n---\n\n# Flow\n\nA **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()`.\n\n## 1. Action-based Transitions\n\nEach Node's `post()` returns an **Action** string. By default, if `post()` doesn't return anything, we treat that as `\"default\"`.\n\nYou define transitions with the syntax:\n\n1. **Basic default transition**: `node_a >> node_b`\n  This means if `node_a.post()` returns `\"default\"`, go to `node_b`. \n  (Equivalent to `node_a - \"default\" >> node_b`)\n\n2. **Named action transition**: `node_a - \"action_name\" >> node_b`\n  This means if `node_a.post()` returns `\"action_name\"`, go to `node_b`.\n\nIt's possible to create loops, branching, or multi-step flows.\n\n## 2. Creating a Flow\n\nA **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.\n\n### Example: Simple Sequence\n\nHere's a minimal flow of two nodes in a chain:\n\n```python\nnode_a >> node_b\nflow = Flow(start=node_a)\nflow.run(shared)\n```\n\n- When you run the flow, it executes `node_a`.  \n- Suppose `node_a.post()` returns `\"default\"`.  \n- The flow then sees `\"default\"` Action is linked to `node_b` and runs `node_b`.  \n- `node_b.post()` returns `\"default\"` but we didn't define `node_b >> something_else`. So the flow ends there.\n\n### Example: Branching & Looping\n\nHere's a simple expense approval flow that demonstrates branching and looping. The `ReviewExpense` node can return three possible Actions:\n\n- `\"approved\"`: expense is approved, move to payment processing\n- `\"needs_revision\"`: expense needs changes, send back for revision \n- `\"rejected\"`: expense is denied, finish the process\n\nWe can wire them like this:\n\n```python\n# Define the flow connections\nreview - \"approved\" >> payment        # If approved, process payment\nreview - \"needs_revision\" >> revise   # If needs changes, go to revision\nreview - \"rejected\" >> finish         # If rejected, finish the process\n\nrevise >> review   # After revision, go back for another review\npayment >> finish  # After payment, finish the process\n\nflow = Flow(start=review)\n```\n\nLet's see how it flows:\n\n1. If `review.post()` returns `\"approved\"`, the expense moves to the `payment` node\n2. If `review.post()` returns `\"needs_revision\"`, it goes to the `revise` node, which then loops back to `review`\n3. If `review.post()` returns `\"rejected\"`, it moves to the `finish` node and stops\n\n```mermaid\nflowchart TD\n    review[Review Expense] -->|approved| payment[Process Payment]\n    review -->|needs_revision| revise[Revise Report]\n    review -->|rejected| finish[Finish Process]\n\n    revise --> review\n    payment --> finish\n```\n\n### Running Individual Nodes vs. Running a Flow\n\n- `node.run(shared)`: Just runs that node alone (calls `prep->exec->post()`), returns an Action. \n- `flow.run(shared)`: Executes from the start node, follows Actions to the next node, and so on until the flow can't continue.\n\n> `node.run(shared)` **does not** proceed to the successor.\n> This is mainly for debugging or testing a single node.\n> \n> Always use `flow.run(...)` in production to ensure the full pipeline runs correctly.\n{: .warning }\n\n## 3. Nested Flows\n\nA **Flow** can act like a Node, which enables powerful composition patterns. This means you can:\n\n1. Use a Flow as a Node within another Flow's transitions.  \n2. Combine multiple smaller Flows into a larger Flow for reuse.  \n3. Node `params` will be a merging of **all** parents' `params`.\n\n### Flow's Node Methods\n\nA **Flow** is also a **Node**, so it will run `prep()` and `post()`. However:\n\n- It **won't** run `exec()`, as its main logic is to orchestrate its nodes.\n- `post()` always receives `None` for `exec_res` and should instead get the flow execution results from the shared store.\n\n### Basic Flow Nesting\n\nHere's how to connect a flow to another node:\n\n```python\n# Create a sub-flow\nnode_a >> node_b\nsubflow = Flow(start=node_a)\n\n# Connect it to another node\nsubflow >> node_c\n\n# Create the parent flow\nparent_flow = Flow(start=subflow)\n```\n\nWhen `parent_flow.run()` executes:\n1. It starts `subflow`\n2. `subflow` runs through its nodes (`node_a->node_b`)\n3. After `subflow` completes, execution continues to `node_c`\n\n### Example: Order Processing Pipeline\n\nHere's a practical example that breaks down order processing into nested flows:\n\n```python\n# Payment processing sub-flow\nvalidate_payment >> process_payment >> payment_confirmation\npayment_flow = Flow(start=validate_payment)\n\n# Inventory sub-flow\ncheck_stock >> reserve_items >> update_inventory\ninventory_flow = Flow(start=check_stock)\n\n# Shipping sub-flow\ncreate_label >> assign_carrier >> schedule_pickup\nshipping_flow = Flow(start=create_label)\n\n# Connect the flows into a main order pipeline\npayment_flow >> inventory_flow >> shipping_flow\n\n# Create the master flow\norder_pipeline = Flow(start=payment_flow)\n\n# Run the entire pipeline\norder_pipeline.run(shared_data)\n```\n\nThis creates a clean separation of concerns while maintaining a clear execution path:\n\n```mermaid\nflowchart LR\n    subgraph order_pipeline[Order Pipeline]\n        subgraph paymentFlow[\"Payment Flow\"]\n            A[Validate Payment] --> B[Process Payment] --> C[Payment Confirmation]\n        end\n\n        subgraph inventoryFlow[\"Inventory Flow\"]\n            D[Check Stock] --> E[Reserve Items] --> F[Update Inventory]\n        end\n\n        subgraph shippingFlow[\"Shipping Flow\"]\n            G[Create Label] --> H[Assign Carrier] --> I[Schedule Pickup]\n        end\n\n        paymentFlow --> inventoryFlow\n        inventoryFlow --> shippingFlow\n    end\n```"
  },
  {
    "path": "docs/core_abstraction/index.md",
    "content": "---\nlayout: default\ntitle: \"Core Abstraction\"\nnav_order: 2\nhas_children: true\n---"
  },
  {
    "path": "docs/core_abstraction/node.md",
    "content": "---\nlayout: default\ntitle: \"Node\"\nparent: \"Core Abstraction\"\nnav_order: 1\n---\n\n# Node\n\nA **Node** is the smallest building block. Each Node has 3 steps `prep->exec->post`:\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/node.png?raw=true\" width=\"400\"/>\n</div>\n\n1. `prep(shared)`\n   - **Read and preprocess data** from `shared` store. \n   - Examples: *query DB, read files, or serialize data into a string*.\n   - Return `prep_res`, which is used by `exec()` and `post()`.\n\n2. `exec(prep_res)`\n   - **Execute compute logic**, with optional retries and error handling (below).\n   - Examples: *(mostly) LLM calls, remote APIs, tool use*.\n   - ⚠️ This shall be only for compute and **NOT** access `shared`.\n   - ⚠️ If retries enabled, ensure idempotent implementation.\n   - ⚠️ Defer exception handling to the Node's built-in retry mechanism.\n   - Return `exec_res`, which is passed to `post()`.\n\n3. `post(shared, prep_res, exec_res)`\n   - **Postprocess and write data** back to `shared`.\n   - Examples: *update DB, change states, log results*.\n   - **Decide the next action** by returning a *string* (`action = \"default\"` if *None*).\n\n> **Why 3 steps?** To enforce the principle of *separation of concerns*. The data storage and data processing are operated separately.\n>\n> All steps are *optional*. E.g., you can only implement `prep` and `post` if you just need to process data.\n{: .note }\n\n### Fault Tolerance & Retries\n\nYou can **retry** `exec()` if it raises an exception via two parameters when define the Node:\n\n- `max_retries` (int): Max times to run `exec()`. The default is `1` (**no** retry).\n- `wait` (int): The time to wait (in **seconds**) before next retry. By default, `wait=0` (no waiting). \n`wait` is helpful when you encounter rate-limits or quota errors from your LLM provider and need to back off.\n\n```python \nmy_node = SummarizeFile(max_retries=3, wait=10)\n```\n\nWhen an exception occurs in `exec()`, the Node automatically retries until:\n\n- It either succeeds, or\n- The Node has retried `max_retries - 1` times already and fails on the last attempt.\n\nYou can get the current retry times (0-based) from `self.cur_retry`.\n\n```python \nclass RetryNode(Node):\n    def exec(self, prep_res):\n        print(f\"Retry {self.cur_retry} times\")\n        raise Exception(\"Failed\")\n```\n\n### Graceful Fallback\n\nTo **gracefully handle** the exception (after all retries) rather than raising it, override:\n\n```python \ndef exec_fallback(self, prep_res, exc):\n    raise exc\n```\n\nBy default, it just re-raises exception. But you can return a fallback result instead, which becomes the `exec_res` passed to `post()`.\n\n### Example: Summarize file\n\n```python \nclass SummarizeFile(Node):\n    def prep(self, shared):\n        return shared[\"data\"]\n\n    def exec(self, prep_res):\n        if not prep_res:\n            return \"Empty file content\"\n        prompt = f\"Summarize this text in 10 words: {prep_res}\"\n        summary = call_llm(prompt)  # might fail\n        return summary\n\n    def exec_fallback(self, prep_res, exc):\n        # Provide a simple fallback instead of crashing\n        return \"There was an error processing your request.\"\n\n    def post(self, shared, prep_res, exec_res):\n        shared[\"summary\"] = exec_res\n        # Return \"default\" by not returning\n\nsummarize_node = SummarizeFile(max_retries=3)\n\n# node.run() calls prep->exec->post\n# If exec() fails, it retries up to 3 times before calling exec_fallback()\naction_result = summarize_node.run(shared)\n\nprint(\"Action returned:\", action_result)  # \"default\"\nprint(\"Summary stored:\", shared[\"summary\"])\n```"
  },
  {
    "path": "docs/core_abstraction/parallel.md",
    "content": "---\nlayout: default\ntitle: \"(Advanced) Parallel\"\nparent: \"Core Abstraction\"\nnav_order: 6\n---\n\n# (Advanced) Parallel\n\n**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. \n\n> 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.\n{: .warning }\n\n> - **Ensure Tasks Are Independent**: If each item depends on the output of a previous item, **do not** parallelize.\n> \n> - **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).\n> \n> - **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.\n{: .best-practice }\n\n## AsyncParallelBatchNode\n\nLike **AsyncBatchNode**, but run `exec_async()` in **parallel**:\n\n```python\nclass ParallelSummaries(AsyncParallelBatchNode):\n    async def prep_async(self, shared):\n        # e.g., multiple texts\n        return shared[\"texts\"]\n\n    async def exec_async(self, text):\n        prompt = f\"Summarize: {text}\"\n        return await call_llm_async(prompt)\n\n    async def post_async(self, shared, prep_res, exec_res_list):\n        shared[\"summary\"] = \"\\n\\n\".join(exec_res_list)\n        return \"default\"\n\nnode = ParallelSummaries()\nflow = AsyncFlow(start=node)\n```\n\n## AsyncParallelBatchFlow\n\nParallel version of **BatchFlow**. Each iteration of the sub-flow runs **concurrently** using different parameters:\n\n```python\nclass SummarizeMultipleFiles(AsyncParallelBatchFlow):\n    async def prep_async(self, shared):\n        return [{\"filename\": f} for f in shared[\"files\"]]\n\nsub_flow = AsyncFlow(start=LoadAndSummarizeFile())\nparallel_flow = SummarizeMultipleFiles(start=sub_flow)\nawait parallel_flow.run_async(shared)\n```"
  },
  {
    "path": "docs/design_pattern/agent.md",
    "content": "---\nlayout: default\ntitle: \"Agent\"\nparent: \"Design Pattern\"\nnav_order: 1\n---\n\n# Agent\n\nAgent is a powerful design pattern in which nodes can take dynamic actions based on the context.\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/agent.png?raw=true\" width=\"350\"/>\n</div>\n\n## Implement Agent with Graph\n\n1. **Context and Action:** Implement nodes that supply context and perform actions.  \n2. **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.\n3. **Agent Node:** Provide a prompt to decide action—for example:\n\n```python\nf\"\"\"\n### CONTEXT\nTask: {task_description}\nPrevious Actions: {previous_actions}\nCurrent State: {current_state}\n\n### ACTION SPACE\n[1] search\n  Description: Use web search to get results\n  Parameters:\n    - query (str): What to search for\n\n[2] answer\n  Description: Conclude based on the results\n  Parameters:\n    - result (str): Final answer to provide\n\n### NEXT ACTION\nDecide the next action based on the current context and available action space.\nReturn your response in the following format:\n\n```yaml\nthinking: |\n    <your step-by-step reasoning process>\naction: <action_name>\nparameters:\n    <parameter_name>: <parameter_value>\n```\"\"\"\n```\n\nThe core of building **high-performance** and **reliable** agents boils down to:\n\n1. **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.\n\n2. **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.\n\n## Example Good Action Design\n\n- **Incremental:** Feed content in manageable chunks (500 lines or 1 page) instead of all at once.\n\n- **Overview-zoom-in:** First provide high-level structure (table of contents, summary), then allow drilling into details (raw texts).\n\n- **Parameterized/Programmable:** Instead of fixed actions, enable parameterized (columns to select) or programmable (SQL queries) actions, for example, to read CSV files.\n\n- **Backtracking:** Let the agent undo the last step instead of restarting entirely, preserving progress when encountering errors or dead ends.\n\n## Example: Search Agent\n\nThis agent:\n1. Decides whether to search or answer\n2. If searches, loops back to decide if more search needed\n3. Answers when enough context gathered\n\n```python\nclass DecideAction(Node):\n    def prep(self, shared):\n        context = shared.get(\"context\", \"No previous search\")\n        query = shared[\"query\"]\n        return query, context\n        \n    def exec(self, inputs):\n        query, context = inputs\n        prompt = f\"\"\"\nGiven input: {query}\nPrevious search results: {context}\nShould I: 1) Search web for more info 2) Answer with current knowledge\nOutput in yaml:\n```yaml\naction: search/answer\nreason: why this action\nsearch_term: search phrase if action is search\n```\"\"\"\n        resp = call_llm(prompt)\n        yaml_str = resp.split(\"```yaml\")[1].split(\"```\")[0].strip()\n        result = yaml.safe_load(yaml_str)\n        \n        assert isinstance(result, dict)\n        assert \"action\" in result\n        assert \"reason\" in result\n        assert result[\"action\"] in [\"search\", \"answer\"]\n        if result[\"action\"] == \"search\":\n            assert \"search_term\" in result\n        \n        return result\n\n    def post(self, shared, prep_res, exec_res):\n        if exec_res[\"action\"] == \"search\":\n            shared[\"search_term\"] = exec_res[\"search_term\"]\n        return exec_res[\"action\"]\n\nclass SearchWeb(Node):\n    def prep(self, shared):\n        return shared[\"search_term\"]\n        \n    def exec(self, search_term):\n        return search_web(search_term)\n    \n    def post(self, shared, prep_res, exec_res):\n        prev_searches = shared.get(\"context\", [])\n        shared[\"context\"] = prev_searches + [\n            {\"term\": shared[\"search_term\"], \"result\": exec_res}\n        ]\n        return \"decide\"\n        \nclass DirectAnswer(Node):\n    def prep(self, shared):\n        return shared[\"query\"], shared.get(\"context\", \"\")\n        \n    def exec(self, inputs):\n        query, context = inputs\n        return call_llm(f\"Context: {context}\\nAnswer: {query}\")\n\n    def post(self, shared, prep_res, exec_res):\n       print(f\"Answer: {exec_res}\")\n       shared[\"answer\"] = exec_res\n\n# Connect nodes\ndecide = DecideAction()\nsearch = SearchWeb()\nanswer = DirectAnswer()\n\ndecide - \"search\" >> search\ndecide - \"answer\" >> answer\nsearch - \"decide\" >> decide  # Loop back\n\nflow = Flow(start=decide)\nflow.run({\"query\": \"Who won the Nobel Prize in Physics 2024?\"})\n```\n"
  },
  {
    "path": "docs/design_pattern/index.md",
    "content": "---\nlayout: default\ntitle: \"Design Pattern\"\nnav_order: 3\nhas_children: true\n---"
  },
  {
    "path": "docs/design_pattern/mapreduce.md",
    "content": "---\nlayout: default\ntitle: \"Map Reduce\"\nparent: \"Design Pattern\"\nnav_order: 4\n---\n\n# Map Reduce\n\nMapReduce is a design pattern suitable when you have either:\n- Large input data (e.g., multiple files to process), or\n- Large output data (e.g., multiple forms to fill)\n\nand there is a logical way to break the task into smaller, ideally independent parts. \n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/mapreduce.png?raw=true\" width=\"400\"/>\n</div>\n\nYou first break down the task using [BatchNode](../core_abstraction/batch.md) in the map phase, followed by aggregation in the reduce phase.\n\n### Example: Document Summarization\n\n```python\nclass SummarizeAllFiles(BatchNode):\n    def prep(self, shared):\n        files_dict = shared[\"files\"]  # e.g. 10 files\n        return list(files_dict.items())  # [(\"file1.txt\", \"aaa...\"), (\"file2.txt\", \"bbb...\"), ...]\n\n    def exec(self, one_file):\n        filename, file_content = one_file\n        summary_text = call_llm(f\"Summarize the following file:\\n{file_content}\")\n        return (filename, summary_text)\n\n    def post(self, shared, prep_res, exec_res_list):\n        shared[\"file_summaries\"] = dict(exec_res_list)\n\nclass CombineSummaries(Node):\n    def prep(self, shared):\n        return shared[\"file_summaries\"]\n\n    def exec(self, file_summaries):\n        # format as: \"File1: summary\\nFile2: summary...\\n\"\n        text_list = []\n        for fname, summ in file_summaries.items():\n            text_list.append(f\"{fname} summary:\\n{summ}\\n\")\n        big_text = \"\\n---\\n\".join(text_list)\n\n        return call_llm(f\"Combine these file summaries into one final summary:\\n{big_text}\")\n\n    def post(self, shared, prep_res, final_summary):\n        shared[\"all_files_summary\"] = final_summary\n\nbatch_node = SummarizeAllFiles()\ncombine_node = CombineSummaries()\nbatch_node >> combine_node\n\nflow = Flow(start=batch_node)\n\nshared = {\n    \"files\": {\n        \"file1.txt\": \"Alice was beginning to get very tired of sitting by her sister...\",\n        \"file2.txt\": \"Some other interesting text ...\",\n        # ...\n    }\n}\nflow.run(shared)\nprint(\"Individual Summaries:\", shared[\"file_summaries\"])\nprint(\"\\nFinal Summary:\\n\", shared[\"all_files_summary\"])\n```\n\n> **Performance Tip**: The example above works sequentially. You can speed up the map phase by running it in parallel. See [(Advanced) Parallel](../core_abstraction/parallel.md) for more details.\n{: .note }"
  },
  {
    "path": "docs/design_pattern/multi_agent.md",
    "content": "---\nlayout: default\ntitle: \"(Advanced) Multi-Agents\"\nparent: \"Design Pattern\"\nnav_order: 6\n---\n\n# (Advanced) Multi-Agents\n\nMultiple [Agents](./flow.md) can work together by handling subtasks and communicating the progress. \nCommunication between agents is typically implemented using message queues in shared storage.\n\n> Most of time, you don't need Multi-Agents. Start with a simple solution first.\n{: .best-practice }\n\n### Example Agent Communication: Message Queue\n\nHere's a simple example showing how to implement agent communication using `asyncio.Queue`. \nThe agent listens for messages, processes them, and continues listening:\n\n```python\nclass AgentNode(AsyncNode):\n    async def prep_async(self, _):\n        message_queue = self.params[\"messages\"]\n        message = await message_queue.get()\n        print(f\"Agent received: {message}\")\n        return message\n\n# Create node and flow\nagent = AgentNode()\nagent >> agent  # connect to self\nflow = AsyncFlow(start=agent)\n\n# Create heartbeat sender\nasync def send_system_messages(message_queue):\n    counter = 0\n    messages = [\n        \"System status: all systems operational\",\n        \"Memory usage: normal\",\n        \"Network connectivity: stable\",\n        \"Processing load: optimal\"\n    ]\n    \n    while True:\n        message = f\"{messages[counter % len(messages)]} | timestamp_{counter}\"\n        await message_queue.put(message)\n        counter += 1\n        await asyncio.sleep(1)\n\nasync def main():\n    message_queue = asyncio.Queue()\n    shared = {}\n    flow.set_params({\"messages\": message_queue})\n    \n    # Run both coroutines\n    await asyncio.gather(\n        flow.run_async(shared),\n        send_system_messages(message_queue)\n    )\n    \nasyncio.run(main())\n```\n\nThe output:\n\n```\nAgent received: System status: all systems operational | timestamp_0\nAgent received: Memory usage: normal | timestamp_1\nAgent received: Network connectivity: stable | timestamp_2\nAgent received: Processing load: optimal | timestamp_3\n```\n\n### Interactive Multi-Agent Example: Taboo Game\n\nHere's a more complex example where two agents play the word-guessing game Taboo. \nOne agent provides hints while avoiding forbidden words, and another agent tries to guess the target word:\n\n```python\nclass AsyncHinter(AsyncNode):\n    async def prep_async(self, shared):\n        guess = await shared[\"hinter_queue\"].get()\n        if guess == \"GAME_OVER\":\n            return None\n        return shared[\"target_word\"], shared[\"forbidden_words\"], shared.get(\"past_guesses\", [])\n\n    async def exec_async(self, inputs):\n        if inputs is None:\n            return None\n        target, forbidden, past_guesses = inputs\n        prompt = f\"Generate hint for '{target}'\\nForbidden words: {forbidden}\"\n        if past_guesses:\n            prompt += f\"\\nPrevious wrong guesses: {past_guesses}\\nMake hint more specific.\"\n        prompt += \"\\nUse at most 5 words.\"\n        \n        hint = call_llm(prompt)\n        print(f\"\\nHinter: Here's your hint - {hint}\")\n        return hint\n\n    async def post_async(self, shared, prep_res, exec_res):\n        if exec_res is None:\n            return \"end\"\n        await shared[\"guesser_queue\"].put(exec_res)\n        return \"continue\"\n\nclass AsyncGuesser(AsyncNode):\n    async def prep_async(self, shared):\n        hint = await shared[\"guesser_queue\"].get()\n        return hint, shared.get(\"past_guesses\", [])\n\n    async def exec_async(self, inputs):\n        hint, past_guesses = inputs\n        prompt = f\"Given hint: {hint}, past wrong guesses: {past_guesses}, make a new guess. Directly reply a single word:\"\n        guess = call_llm(prompt)\n        print(f\"Guesser: I guess it's - {guess}\")\n        return guess\n\n    async def post_async(self, shared, prep_res, exec_res):\n        if exec_res.lower() == shared[\"target_word\"].lower():\n            print(\"Game Over - Correct guess!\")\n            await shared[\"hinter_queue\"].put(\"GAME_OVER\")\n            return \"end\"\n            \n        if \"past_guesses\" not in shared:\n            shared[\"past_guesses\"] = []\n        shared[\"past_guesses\"].append(exec_res)\n        \n        await shared[\"hinter_queue\"].put(exec_res)\n        return \"continue\"\n\nasync def main():\n    # Set up game\n    shared = {\n        \"target_word\": \"nostalgia\",\n        \"forbidden_words\": [\"memory\", \"past\", \"remember\", \"feeling\", \"longing\"],\n        \"hinter_queue\": asyncio.Queue(),\n        \"guesser_queue\": asyncio.Queue()\n    }\n    \n    print(\"Game starting!\")\n    print(f\"Target word: {shared['target_word']}\")\n    print(f\"Forbidden words: {shared['forbidden_words']}\")\n\n    # Initialize by sending empty guess to hinter\n    await shared[\"hinter_queue\"].put(\"\")\n\n    # Create nodes and flows\n    hinter = AsyncHinter()\n    guesser = AsyncGuesser()\n\n    # Set up flows\n    hinter_flow = AsyncFlow(start=hinter)\n    guesser_flow = AsyncFlow(start=guesser)\n\n    # Connect nodes to themselves\n    hinter - \"continue\" >> hinter\n    guesser - \"continue\" >> guesser\n\n    # Run both agents concurrently\n    await asyncio.gather(\n        hinter_flow.run_async(shared),\n        guesser_flow.run_async(shared)\n    )\n\nasyncio.run(main())\n```\n\nThe Output:\n\n```\nGame starting!\nTarget word: nostalgia\nForbidden words: ['memory', 'past', 'remember', 'feeling', 'longing']\n\nHinter: Here's your hint - Thinking of childhood summer days\nGuesser: I guess it's - popsicle\n\nHinter: Here's your hint - When childhood cartoons make you emotional\nGuesser: I guess it's - nostalgic\n\nHinter: Here's your hint - When old songs move you\nGuesser: I guess it's - memories\n\nHinter: Here's your hint - That warm emotion about childhood\nGuesser: I guess it's - nostalgia\nGame Over - Correct guess!\n```"
  },
  {
    "path": "docs/design_pattern/rag.md",
    "content": "---\nlayout: default\ntitle: \"RAG\"\nparent: \"Design Pattern\"\nnav_order: 3\n---\n\n# RAG (Retrieval Augmented Generation)\n\nFor certain LLM tasks like answering questions, providing relevant context is essential. One common architecture is a **two-stage** RAG pipeline:\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/rag.png?raw=true\" width=\"400\"/>\n</div>\n\n1. **Offline stage**: Preprocess and index documents (\"building the index\").\n2. **Online stage**: Given a question, generate answers by retrieving the most relevant context.\n\n---\n## Stage 1: Offline Indexing\n\nWe create three Nodes:\n1. `ChunkDocs` – [chunks](../utility_function/chunking.md) raw text.\n2. `EmbedDocs` – [embeds](../utility_function/embedding.md) each chunk.\n3. `StoreIndex` – stores embeddings into a [vector database](../utility_function/vector.md).\n\n```python\nclass ChunkDocs(BatchNode):\n    def prep(self, shared):\n        # A list of file paths in shared[\"files\"]. We process each file.\n        return shared[\"files\"]\n\n    def exec(self, filepath):\n        # read file content. In real usage, do error handling.\n        with open(filepath, \"r\", encoding=\"utf-8\") as f:\n            text = f.read()\n        # chunk by 100 chars each\n        chunks = []\n        size = 100\n        for i in range(0, len(text), size):\n            chunks.append(text[i : i + size])\n        return chunks\n    \n    def post(self, shared, prep_res, exec_res_list):\n        # exec_res_list is a list of chunk-lists, one per file.\n        # flatten them all into a single list of chunks.\n        all_chunks = []\n        for chunk_list in exec_res_list:\n            all_chunks.extend(chunk_list)\n        shared[\"all_chunks\"] = all_chunks\n\nclass EmbedDocs(BatchNode):\n    def prep(self, shared):\n        return shared[\"all_chunks\"]\n\n    def exec(self, chunk):\n        return get_embedding(chunk)\n\n    def post(self, shared, prep_res, exec_res_list):\n        # Store the list of embeddings.\n        shared[\"all_embeds\"] = exec_res_list\n        print(f\"Total embeddings: {len(exec_res_list)}\")\n\nclass StoreIndex(Node):\n    def prep(self, shared):\n        # We'll read all embeds from shared.\n        return shared[\"all_embeds\"]\n\n    def exec(self, all_embeds):\n        # Create a vector index (faiss or other DB in real usage).\n        index = create_index(all_embeds)\n        return index\n\n    def post(self, shared, prep_res, index):\n        shared[\"index\"] = index\n\n# Wire them in sequence\nchunk_node = ChunkDocs()\nembed_node = EmbedDocs()\nstore_node = StoreIndex()\n\nchunk_node >> embed_node >> store_node\n\nOfflineFlow = Flow(start=chunk_node)\n```\n\nUsage example:\n\n```python\nshared = {\n    \"files\": [\"doc1.txt\", \"doc2.txt\"],  # any text files\n}\nOfflineFlow.run(shared)\n```\n\n---\n## Stage 2: Online Query & Answer\n\nWe have 3 nodes:\n1. `EmbedQuery` – embeds the user’s question.\n2. `RetrieveDocs` – retrieves top chunk from the index.\n3. `GenerateAnswer` – calls the LLM with the question + chunk to produce the final answer.\n\n```python\nclass EmbedQuery(Node):\n    def prep(self, shared):\n        return shared[\"question\"]\n\n    def exec(self, question):\n        return get_embedding(question)\n\n    def post(self, shared, prep_res, q_emb):\n        shared[\"q_emb\"] = q_emb\n\nclass RetrieveDocs(Node):\n    def prep(self, shared):\n        # We'll need the query embedding, plus the offline index/chunks\n        return shared[\"q_emb\"], shared[\"index\"], shared[\"all_chunks\"]\n\n    def exec(self, inputs):\n        q_emb, index, chunks = inputs\n        I, D = search_index(index, q_emb, top_k=1)\n        best_id = I[0][0]\n        relevant_chunk = chunks[best_id]\n        return relevant_chunk\n\n    def post(self, shared, prep_res, relevant_chunk):\n        shared[\"retrieved_chunk\"] = relevant_chunk\n        print(\"Retrieved chunk:\", relevant_chunk[:60], \"...\")\n\nclass GenerateAnswer(Node):\n    def prep(self, shared):\n        return shared[\"question\"], shared[\"retrieved_chunk\"]\n\n    def exec(self, inputs):\n        question, chunk = inputs\n        prompt = f\"Question: {question}\\nContext: {chunk}\\nAnswer:\"\n        return call_llm(prompt)\n\n    def post(self, shared, prep_res, answer):\n        shared[\"answer\"] = answer\n        print(\"Answer:\", answer)\n\nembed_qnode = EmbedQuery()\nretrieve_node = RetrieveDocs()\ngenerate_node = GenerateAnswer()\n\nembed_qnode >> retrieve_node >> generate_node\nOnlineFlow = Flow(start=embed_qnode)\n```\n\nUsage example:\n\n```python\n# Suppose we already ran OfflineFlow and have:\n# shared[\"all_chunks\"], shared[\"index\"], etc.\nshared[\"question\"] = \"Why do people like cats?\"\n\nOnlineFlow.run(shared)\n# final answer in shared[\"answer\"]\n```"
  },
  {
    "path": "docs/design_pattern/structure.md",
    "content": "---\nlayout: default\ntitle: \"Structured Output\"\nparent: \"Design Pattern\"\nnav_order: 5\n---\n\n# Structured Output\n\nIn many use cases, you may want the LLM to output a specific structure, such as a list or a dictionary with predefined keys.\n\nThere are several approaches to achieve a structured output:\n- **Prompting** the LLM to strictly return a defined structure.\n- Using LLMs that natively support **schema enforcement**.\n- **Post-processing** the LLM's response to extract structured content.\n\nIn practice, **Prompting** is simple and reliable for modern LLMs.\n\n### Example Use Cases\n\n- Extracting Key Information \n\n```yaml\nproduct:\n  name: Widget Pro\n  price: 199.99\n  description: |\n    A high-quality widget designed for professionals.\n    Recommended for advanced users.\n```\n\n- Summarizing Documents into Bullet Points\n\n```yaml\nsummary:\n  - This product is easy to use.\n  - It is cost-effective.\n  - Suitable for all skill levels.\n```\n\n- Generating Configuration Files\n\n```yaml\nserver:\n  host: 127.0.0.1\n  port: 8080\n  ssl: true\n```\n\n## Prompt Engineering\n\nWhen prompting the LLM to produce **structured** output:\n1. **Wrap** the structure in code fences (e.g., `yaml`).\n2. **Validate** that all required fields exist (and let `Node` handles retry).\n\n### Example Text Summarization\n\n```python\nclass SummarizeNode(Node):\n    def exec(self, prep_res):\n        # Suppose `prep_res` is the text to summarize.\n        prompt = f\"\"\"\nPlease summarize the following text as YAML, with exactly 3 bullet points\n\n{prep_res}\n\nNow, output:\n```yaml\nsummary:\n  - bullet 1\n  - bullet 2\n  - bullet 3\n```\"\"\"\n        response = call_llm(prompt)\n        yaml_str = response.split(\"```yaml\")[1].split(\"```\")[0].strip()\n\n        import yaml\n        structured_result = yaml.safe_load(yaml_str)\n\n        assert \"summary\" in structured_result\n        assert isinstance(structured_result[\"summary\"], list)\n\n        return structured_result\n```\n\n> Besides using `assert` statements, another popular way to validate schemas is [Pydantic](https://github.com/pydantic/pydantic)\n{: .note }\n\n### Why YAML instead of JSON?\n\nCurrent LLMs struggle with escaping. YAML is easier with strings since they don't always need quotes.\n\n**In JSON**  \n\n```json\n{\n  \"dialogue\": \"Alice said: \\\"Hello Bob.\\\\nHow are you?\\\\nI am good.\\\"\"\n}\n```\n\n- Every double quote inside the string must be escaped with `\\\"`.\n- Each newline in the dialogue must be represented as `\\n`.\n\n**In YAML**  \n\n```yaml\ndialogue: |\n  Alice said: \"Hello Bob.\n  How are you?\n  I am good.\"\n```\n\n- No need to escape interior quotes—just place the entire text under a block literal (`|`).\n- Newlines are naturally preserved without needing `\\n`."
  },
  {
    "path": "docs/design_pattern/workflow.md",
    "content": "---\nlayout: default\ntitle: \"Workflow\"\nparent: \"Design Pattern\"\nnav_order: 2\n---\n\n# Workflow\n\nMany 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.\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/workflow.png?raw=true\" width=\"400\"/>\n</div>\n\n> - You don't want to make each task **too coarse**, because it may be *too complex for one LLM call*.\n> - 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*.\n> \n> You usually need multiple *iterations* to find the *sweet spot*. If the task has too many *edge cases*, consider using [Agents](./agent.md).\n{: .best-practice }\n\n### Example: Article Writing\n\n```python\nclass GenerateOutline(Node):\n    def prep(self, shared): return shared[\"topic\"]\n    def exec(self, topic): return call_llm(f\"Create a detailed outline for an article about {topic}\")\n    def post(self, shared, prep_res, exec_res): shared[\"outline\"] = exec_res\n\nclass WriteSection(Node):\n    def prep(self, shared): return shared[\"outline\"]\n    def exec(self, outline): return call_llm(f\"Write content based on this outline: {outline}\")\n    def post(self, shared, prep_res, exec_res): shared[\"draft\"] = exec_res\n\nclass ReviewAndRefine(Node):\n    def prep(self, shared): return shared[\"draft\"]\n    def exec(self, draft): return call_llm(f\"Review and improve this draft: {draft}\")\n    def post(self, shared, prep_res, exec_res): shared[\"final_article\"] = exec_res\n\n# Connect nodes\noutline = GenerateOutline()\nwrite = WriteSection()\nreview = ReviewAndRefine()\n\noutline >> write >> review\n\n# Create and run flow\nwriting_flow = Flow(start=outline)\nshared = {\"topic\": \"AI Safety\"}\nwriting_flow.run(shared)\n```\n\nFor *dynamic cases*, consider using [Agents](./agent.md)."
  },
  {
    "path": "docs/guide.md",
    "content": "---\nlayout: default\ntitle: \"Agentic Coding\"\n---\n\n# Agentic Coding: Humans Design, Agents code!\n\n> If you are an AI agent 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.\n{: .warning }\n\n## Agentic Coding Steps\n\nAgentic Coding should be a collaboration between Human System Design and Agent Implementation:\n\n| Steps                  | Human      | AI        | Comment                                                                 |\n|:-----------------------|:----------:|:---------:|:------------------------------------------------------------------------|\n| 1. Requirements | ★★★ High  | ★☆☆ Low   | Humans understand the requirements and context.                    |\n| 2. Flow          | ★★☆ Medium | ★★☆ Medium |  Humans specify the high-level design, and the AI fills in the details. |\n| 3. Utilities   | ★★☆ Medium | ★★☆ Medium | Humans provide available external APIs and integrations, and the AI helps with implementation. |\n| 4. Data          | ★☆☆ Low    | ★★★ High   | AI designs the data schema, and humans verify.                            |\n| 5. Node          | ★☆☆ Low   | ★★★ High  | The AI helps design the node based on the flow.          |\n| 6. Implementation      | ★☆☆ Low   | ★★★ High  |  The AI implements the flow based on the design. |\n| 7. Optimization        | ★★☆ Medium | ★★☆ Medium | Humans evaluate the results, and the AI helps optimize. |\n| 8. Reliability         | ★☆☆ Low   | ★★★ High  |  The AI writes test cases and addresses corner cases.     |\n\n1. **Requirements**: Clarify the requirements for your project, and evaluate whether an AI system is a good fit. \n    - Understand AI systems' strengths and limitations:\n      - **Good for**: Routine tasks requiring common sense (filling forms, replying to emails)\n      - **Good for**: Creative tasks with well-defined inputs (building slides, writing SQL)\n      - **Not good for**: Ambiguous problems requiring complex decision-making (business strategy, startup planning)\n    - **Keep It User-Centric:** Explain the \"problem\" from the user's perspective rather than just listing features.\n    - **Balance complexity vs. impact**: Aim to deliver the highest value features with minimal complexity early.\n\n2. **Flow Design**: Outline at a high level, describe how your AI system orchestrates nodes.\n    - Identify applicable design patterns (e.g., [Map Reduce](./design_pattern/mapreduce.md), [Agent](./design_pattern/agent.md), [RAG](./design_pattern/rag.md)).\n      - For each node in the flow, start with a high-level one-line description of what it does.\n      - If using **Map Reduce**, specify how to map (what to split) and how to reduce (how to combine).\n      - If using **Agent**, specify what are the inputs (context) and what are the possible actions.\n      - If using **RAG**, specify what to embed, noting that there's usually both offline (indexing) and online (retrieval) workflows.\n    - Outline the flow and draw it in a mermaid diagram. For example:\n      ```mermaid\n      flowchart LR\n          start[Start] --> batch[Batch]\n          batch --> check[Check]\n          check -->|OK| process\n          check -->|Error| fix[Fix]\n          fix --> check\n          \n          subgraph process[Process]\n            step1[Step 1] --> step2[Step 2]\n          end\n          \n          process --> endNode[End]\n      ```\n    - > **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.  \n      {: .best-practice }\n\n3. **Utilities**: Based on the Flow Design, identify and implement necessary utility functions.\n    - Think of your AI system as the brain. It needs a body—these *external utility functions*—to interact with the real world:\n        <div align=\"center\"><img src=\"https://github.com/the-pocket/.github/raw/main/assets/utility.png?raw=true\" width=\"400\"/></div>\n\n        - Reading inputs (e.g., retrieving Slack messages, reading emails)\n        - Writing outputs (e.g., generating reports, sending emails)\n        - Using external tools (e.g., calling LLMs, searching the web)\n        - **NOTE**: *LLM-based tasks* (e.g., summarizing text, analyzing sentiment) are **NOT** utility functions; rather, they are *core functions* internal in the AI system.\n    - For each utility function, implement it and write a simple test.\n    - Document their input/output, as well as why they are necessary. For example:\n      - `name`: `get_embedding` (`utils/get_embedding.py`)\n      - `input`: `str`\n      - `output`: a vector of 3072 floats\n      - `necessity`: Used by the second node to embed text\n    - Example utility implementation:\n      ```python\n      # utils/call_llm.py\n      from openai import OpenAI\n\n      def call_llm(prompt):    \n          client = OpenAI(api_key=\"YOUR_API_KEY_HERE\")\n          r = client.chat.completions.create(\n              model=\"gpt-4o\",\n              messages=[{\"role\": \"user\", \"content\": prompt}]\n          )\n          return r.choices[0].message.content\n          \n      if __name__ == \"__main__\":\n          prompt = \"What is the meaning of life?\"\n          print(call_llm(prompt))\n      ```\n    - > **Sometimes, design Utilities before Flow:**  For example, for an LLM project to automate a legacy system, the bottleneck will likely be the available interface to that system. Start by designing the hardest utilities for interfacing, and then build the flow around them.\n      {: .best-practice }\n    - > **Avoid Exception Handling in Utilities**: If a utility function is called from a Node's `exec()` method, avoid using `try...except` blocks within the utility. Let the Node's built-in retry mechanism handle failures.\n      {: .warning }\n\n4. **Data Design**: Design the shared store that nodes will use to communicate.\n   - One core design principle for PocketFlow is to use a well-designed [shared store](./core_abstraction/communication.md)—a data contract that all nodes agree upon to retrieve and store data.\n      - For simple systems, use an in-memory dictionary.\n      - For more complex systems or when persistence is required, use a database.\n      - **Don't Repeat Yourself**: Use in-memory references or foreign keys.\n      - Example shared store design:\n        ```python\n        shared = {\n            \"user\": {\n                \"id\": \"user123\",\n                \"context\": {                # Another nested dict\n                    \"weather\": {\"temp\": 72, \"condition\": \"sunny\"},\n                    \"location\": \"San Francisco\"\n                }\n            },\n            \"results\": {}                   # Empty dict to store outputs\n        }\n        ```\n\n5. **Node Design**: Plan how each node will read and write data, and use utility functions.\n   - 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:\n     - `type`: Regular (or Batch, or Async)\n     - `prep`: Read \"text\" from the shared store\n     - `exec`: Call the embedding utility function. **Avoid exception handling here**; let the Node's retry mechanism manage failures.\n     - `post`: Write \"embedding\" to the shared store\n\n6. **Implementation**: Implement the initial nodes and flows based on the design.\n   - 🎉 If you've reached this step, humans have finished the design. Now *Agentic Coding* begins!\n   - **\"Keep it simple, stupid!\"** Avoid complex features and full-scale type checking.\n   - **FAIL FAST**! Leverage the built-in [Node](./core_abstraction/node.md) retry and fallback mechanisms to handle failures gracefully. This helps you quickly identify weak points in the system.\n   - Add logging throughout the code to facilitate debugging.\n\n7. **Optimization**:\n   - **Use Intuition**: For a quick initial evaluation, human intuition is often a good start.\n   - **Redesign Flow (Back to Step 3)**: Consider breaking down tasks further, introducing agentic decisions, or better managing input contexts.\n   - If your flow design is already solid, move on to micro-optimizations:\n     - **Prompt Engineering**: Use clear, specific instructions with examples to reduce ambiguity.\n     - **In-Context Learning**: Provide robust examples for tasks that are difficult to specify with instructions alone.\n\n   - > **You'll likely iterate a lot!** Expect to repeat Steps 3–6 hundreds of times.\n     >\n     > <div align=\"center\"><img src=\"https://github.com/the-pocket/.github/raw/main/assets/success.png?raw=true\" width=\"400\"/></div>\n     {: .best-practice }\n\n8. **Reliability**  \n   - **Node Retries**: Add checks in the node `exec` to ensure outputs meet requirements, and consider increasing `max_retries` and `wait` times.\n   - **Logging and Visualization**: Maintain logs of all attempts and visualize node results for easier debugging.\n   - **Self-Evaluation**: Add a separate node (powered by an LLM) to review outputs when results are uncertain.\n\n## Example LLM Project File Structure\n\n```\nmy_project/\n├── main.py\n├── nodes.py\n├── flow.py\n├── utils/\n│   ├── __init__.py\n│   ├── call_llm.py\n│   └── search_web.py\n├── requirements.txt\n└── docs/\n    └── design.md\n```\n\n- **`requirements.txt`**: Lists the Python dependencies for the project.\n  ```\n  PyYAML\n  pocketflow\n  ```\n\n- **`docs/design.md`**: Contains project documentation for each step above. This should be *high-level* and *no-code*.\n  ~~~\n  # Design Doc: Your Project Name\n\n  > Please DON'T remove notes for AI\n\n  ## Requirements\n\n  > Notes for AI: Keep it simple and clear.\n  > If the requirements are abstract, write concrete user stories\n\n\n  ## Flow Design\n\n  > Notes for AI:\n  > 1. Consider the design patterns of agent, map-reduce, rag, and workflow. Apply them if they fit.\n  > 2. Present a concise, high-level description of the workflow.\n\n  ### Applicable Design Pattern:\n\n  1. Map the file summary into chunks, then reduce these chunks into a final summary.\n  2. Agentic file finder\n    - *Context*: The entire summary of the file\n    - *Action*: Find the file\n\n  ### Flow high-level Design:\n\n  1. **First Node**: This node is for ...\n  2. **Second Node**: This node is for ...\n  3. **Third Node**: This node is for ...\n\n  ```mermaid\n  flowchart TD\n      firstNode[First Node] --> secondNode[Second Node]\n      secondNode --> thirdNode[Third Node]\n  ```\n  ## Utility Functions\n\n  > Notes for AI:\n  > 1. Understand the utility function definition thoroughly by reviewing the doc.\n  > 2. Include only the necessary utility functions, based on nodes in the flow.\n\n  1. **Call LLM** (`utils/call_llm.py`)\n    - *Input*: prompt (str)\n    - *Output*: response (str)\n    - Generally used by most nodes for LLM tasks\n\n  2. **Embedding** (`utils/get_embedding.py`)\n    - *Input*: str\n    - *Output*: a vector of 3072 floats\n    - Used by the second node to embed text\n\n  ## Node Design\n\n  ### Shared Store\n\n  > Notes for AI: Try to minimize data redundancy\n\n  The shared store structure is organized as follows:\n\n  ```python\n  shared = {\n      \"key\": \"value\"\n  }\n  ```\n\n  ### Node Steps\n\n  > Notes for AI: Carefully decide whether to use Batch/Async Node/Flow.\n\n  1. First Node\n    - *Purpose*: Provide a short explanation of the node’s function\n    - *Type*: Decide between Regular, Batch, or Async\n    - *Steps*:\n      - *prep*: Read \"key\" from the shared store\n      - *exec*: Call the utility function\n      - *post*: Write \"key\" to the shared store\n\n  2. Second Node\n    ...\n  ~~~\n\n\n- **`utils/`**: Contains all utility functions.\n  - It's recommended to dedicate one Python file to each API call, for example `call_llm.py` or `search_web.py`.\n  - Each file should also include a `main()` function to try that API call\n  ```python\n  from google import genai\n  import os\n\n  def call_llm(prompt: str) -> str:\n      client = genai.Client(\n          api_key=os.getenv(\"GEMINI_API_KEY\", \"\"),\n      )\n      model = os.getenv(\"GEMINI_MODEL\", \"gemini-2.5-flash\")\n      response = client.models.generate_content(model=model, contents=[prompt])\n      return response.text\n\n  if __name__ == \"__main__\":\n      test_prompt = \"Hello, how are you?\"\n\n      # First call - should hit the API\n      print(\"Making call...\")\n      response1 = call_llm(test_prompt, use_cache=False)\n      print(f\"Response: {response1}\")\n  ```\n\n- **`nodes.py`**: Contains all the node definitions.\n  ```python\n  # nodes.py\n  from pocketflow import Node\n  from utils.call_llm import call_llm\n\n  class GetQuestionNode(Node):\n      def exec(self, _):\n          # Get question directly from user input\n          user_question = input(\"Enter your question: \")\n          return user_question\n      \n      def post(self, shared, prep_res, exec_res):\n          # Store the user's question\n          shared[\"question\"] = exec_res\n          return \"default\"  # Go to the next node\n\n  class AnswerNode(Node):\n      def prep(self, shared):\n          # Read question from shared\n          return shared[\"question\"]\n      \n      def exec(self, question):\n          # Call LLM to get the answer\n          return call_llm(question)\n      \n      def post(self, shared, prep_res, exec_res):\n          # Store the answer in shared\n          shared[\"answer\"] = exec_res\n  ```\n- **`flow.py`**: Implements functions that create flows by importing node definitions and connecting them.\n  ```python\n  # flow.py\n  from pocketflow import Flow\n  from nodes import GetQuestionNode, AnswerNode\n\n  def create_qa_flow():\n      \"\"\"Create and return a question-answering flow.\"\"\"\n      # Create nodes\n      get_question_node = GetQuestionNode()\n      answer_node = AnswerNode()\n      \n      # Connect nodes in sequence\n      get_question_node >> answer_node\n      \n      # Create flow starting with input node\n      return Flow(start=get_question_node)\n  ```\n- **`main.py`**: Serves as the project's entry point.\n  ```python\n  # main.py\n  from flow import create_qa_flow\n\n  # Example main function\n  # Please replace this with your own main function\n  def main():\n      shared = {\n          \"question\": None,  # Will be populated by GetQuestionNode from user input\n          \"answer\": None     # Will be populated by AnswerNode\n      }\n\n      # Create the flow and run it\n      qa_flow = create_qa_flow()\n      qa_flow.run(shared)\n      print(f\"Question: {shared['question']}\")\n      print(f\"Answer: {shared['answer']}\")\n\n  if __name__ == \"__main__\":\n      main()\n  ```\n"
  },
  {
    "path": "docs/index.md",
    "content": "---\nlayout: default\ntitle: \"Home\"\nnav_order: 1\n---\n\n# Pocket Flow\n\nA [100-line](https://github.com/the-pocket/PocketFlow/blob/main/pocketflow/__init__.py) minimalist LLM framework for *Agents, Task Decomposition, RAG, etc*.\n\n- **Lightweight**: Just the core graph abstraction in 100 lines. ZERO dependencies, and vendor lock-in.\n- **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.  \n- **Agentic-Coding**: Intuitive enough for AI agents to help humans build complex LLM applications.\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/meme.jpg?raw=true\" alt=\"Pocket Flow – 100-line minimalist LLM framework\" width=\"400\"/>\n</div>\n\n\n## Core Abstraction\n\nWe model the LLM workflow as a **Graph + Shared Store**:\n\n- [Node](./core_abstraction/node.md) handles simple (LLM) tasks.\n- [Flow](./core_abstraction/flow.md) connects nodes through **Actions** (labeled edges).\n- [Shared Store](./core_abstraction/communication.md) enables communication between nodes within flows.\n- [Batch](./core_abstraction/batch.md) nodes/flows allow for data-intensive tasks.\n- [Async](./core_abstraction/async.md) nodes/flows allow waiting for asynchronous tasks.\n- [(Advanced) Parallel](./core_abstraction/parallel.md) nodes/flows handle I/O-bound tasks.\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/abstraction.png\" alt=\"Pocket Flow – Core Abstraction\" width=\"700\"/>\n</div>\n\n## Design Pattern\n\nFrom there, it’s easy to implement popular design patterns:\n\n- [Agent](./design_pattern/agent.md) autonomously makes decisions.\n- [Workflow](./design_pattern/workflow.md) chains multiple tasks into pipelines.\n- [RAG](./design_pattern/rag.md) integrates data retrieval with generation.\n- [Map Reduce](./design_pattern/mapreduce.md) splits data tasks into Map and Reduce steps.\n- [Structured Output](./design_pattern/structure.md) formats outputs consistently.\n- [(Advanced) Multi-Agents](./design_pattern/multi_agent.md) coordinate multiple agents.\n\n<div align=\"center\">\n  <img src=\"https://github.com/the-pocket/.github/raw/main/assets/design.png\" alt=\"Pocket Flow – Design Pattern\" width=\"700\"/>\n</div>\n\n## Utility Function\n\nWe **do not** provide built-in utilities. Instead, we offer *examples*—please *implement your own*:\n\n- [LLM Wrapper](./utility_function/llm.md)\n- [Viz and Debug](./utility_function/viz.md)\n- [Web Search](./utility_function/websearch.md)\n- [Chunking](./utility_function/chunking.md)\n- [Embedding](./utility_function/embedding.md)\n- [Vector Databases](./utility_function/vector.md)\n- [Text-to-Speech](./utility_function/text_to_speech.md)\n\n**Why not built-in?**: I believe it's a *bad practice* for vendor-specific APIs in a general framework:\n- *API Volatility*: Frequent changes lead to heavy maintenance for hardcoded APIs.\n- *Flexibility*: You may want to switch vendors, use fine-tuned models, or run them locally.\n- *Optimizations*: Prompt caching, batching, and streaming are easier without vendor lock-in.\n\n## Ready to build your Apps? \n\nCheck out [Agentic Coding Guidance](./guide.md), the fastest way to develop LLM projects with Pocket Flow!\n"
  },
  {
    "path": "docs/utility_function/chunking.md",
    "content": "---\nlayout: default\ntitle: \"Text Chunking\"\nparent: \"Utility Function\"\nnav_order: 4\n---\n\n# Text Chunking\n\nWe recommend some implementations of commonly used text chunking approaches.\n\n\n> Text Chunking is more a micro optimization, compared to the Flow Design.\n> \n> It's recommended to start with the Naive Chunking and optimize later.\n{: .best-practice }\n\n---\n\n## Example Python Code Samples\n\n### 1. Naive (Fixed-Size) Chunking\nSplits text by a fixed number of words, ignoring sentence or semantic boundaries.\n\n```python\ndef fixed_size_chunk(text, chunk_size=100):\n    chunks = []\n    for i in range(0, len(text), chunk_size):\n        chunks.append(text[i : i + chunk_size])\n    return chunks\n```\n\nHowever, sentences are often cut awkwardly, losing coherence.\n\n### 2. Sentence-Based Chunking\n\n```python\nimport nltk\n\ndef sentence_based_chunk(text, max_sentences=2):\n    sentences = nltk.sent_tokenize(text)\n    chunks = []\n    for i in range(0, len(sentences), max_sentences):\n        chunks.append(\" \".join(sentences[i : i + max_sentences]))\n    return chunks\n```\n\nHowever, might not handle very long sentences or paragraphs well.\n\n### 3. Other Chunking\n\n- **Paragraph-Based**: Split text by paragraphs (e.g., newlines). Large paragraphs can create big chunks.\n- **Semantic**: Use embeddings or topic modeling to chunk by semantic boundaries.\n- **Agentic**: Use an LLM to decide chunk boundaries based on context or meaning."
  },
  {
    "path": "docs/utility_function/embedding.md",
    "content": "---\nlayout: default\ntitle: \"Embedding\"\nparent: \"Utility Function\"\nnav_order: 5\n---\n\n# Embedding\n\nBelow you will find an overview table of various text embedding APIs, along with example Python code.\n\n>  Embedding is more a micro optimization, compared to the Flow Design.\n> \n> It's recommended to start with the most convenient one and optimize later.\n{: .best-practice }\n\n\n| **API** | **Free Tier** | **Pricing Model** | **Docs** |\n| --- | --- | --- | --- |\n| **OpenAI** | ~$5 credit | ~$0.0001/1K tokens | [OpenAI Embeddings](https://platform.openai.com/docs/api-reference/embeddings) |\n| **Azure OpenAI** | $200 credit | Same as OpenAI (~$0.0001/1K tokens) | [Azure OpenAI Embeddings](https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource?tabs=portal) |\n| **Google Vertex AI** | $300 credit | ~$0.025 / million chars | [Vertex AI Embeddings](https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings/get-text-embeddings) |\n| **AWS Bedrock** | No free tier, but AWS credits may apply | ~$0.00002/1K tokens (Titan V2) | [Amazon Bedrock](https://docs.aws.amazon.com/bedrock/) |\n| **Cohere** | Limited free tier | ~$0.0001/1K tokens | [Cohere Embeddings](https://docs.cohere.com/docs/cohere-embed) |\n| **Hugging Face** | ~$0.10 free compute monthly | Pay per second of compute | [HF Inference API](https://huggingface.co/docs/api-inference) |\n| **Jina** | 1M tokens free | Pay per token after | [Jina Embeddings](https://jina.ai/embeddings/) |\n\n## Example Python Code\n\n### 1. OpenAI\n```python\nfrom openai import OpenAI\n\nclient = OpenAI(api_key=\"YOUR_API_KEY\")\nresponse = client.embeddings.create(\n    model=\"text-embedding-ada-002\",\n    input=text\n)\n    \n# Extract the embedding vector from the response\nembedding = response.data[0].embedding\nembedding = np.array(embedding, dtype=np.float32)\nprint(embedding)\n```\n\n### 2. Azure OpenAI\n```python\nimport openai\n\nopenai.api_type = \"azure\"\nopenai.api_base = \"https://YOUR_RESOURCE_NAME.openai.azure.com\"\nopenai.api_version = \"2023-03-15-preview\"\nopenai.api_key = \"YOUR_AZURE_API_KEY\"\n\nresp = openai.Embedding.create(engine=\"ada-embedding\", input=\"Hello world\")\nvec = resp[\"data\"][0][\"embedding\"]\nprint(vec)\n```\n\n### 3. Google Vertex AI\n```python\nfrom vertexai.preview.language_models import TextEmbeddingModel\nimport vertexai\n\nvertexai.init(project=\"YOUR_GCP_PROJECT_ID\", location=\"us-central1\")\nmodel = TextEmbeddingModel.from_pretrained(\"textembedding-gecko@001\")\n\nemb = model.get_embeddings([\"Hello world\"])\nprint(emb[0])\n```\n\n### 4. AWS Bedrock\n```python\nimport boto3, json\n\nclient = boto3.client(\"bedrock-runtime\", region_name=\"us-east-1\")\nbody = {\"inputText\": \"Hello world\"}\nresp = client.invoke_model(modelId=\"amazon.titan-embed-text-v2:0\", contentType=\"application/json\", body=json.dumps(body))\nresp_body = json.loads(resp[\"body\"].read())\nvec = resp_body[\"embedding\"]\nprint(vec)\n```\n\n### 5. Cohere\n```python\nimport cohere\n\nco = cohere.Client(\"YOUR_API_KEY\")\nresp = co.embed(texts=[\"Hello world\"])\nvec = resp.embeddings[0]\nprint(vec)\n```\n\n### 6. Hugging Face\n```python\nimport requests\n\nAPI_URL = \"https://api-inference.huggingface.co/models/sentence-transformers/all-MiniLM-L6-v2\"\nHEADERS = {\"Authorization\": \"Bearer YOUR_HF_TOKEN\"}\n\nres = requests.post(API_URL, headers=HEADERS, json={\"inputs\": \"Hello world\"})\nvec = res.json()[0]\nprint(vec)\n```\n\n### 7. Jina\n```python\nimport requests\n\nurl = \"https://api.jina.ai/v2/embed\"\nheaders = {\"Authorization\": \"Bearer YOUR_JINA_TOKEN\"}\npayload = {\"data\": [\"Hello world\"], \"model\": \"jina-embeddings-v3\"}\nres = requests.post(url, headers=headers, json=payload)\nvec = res.json()[\"data\"][0][\"embedding\"]\nprint(vec)\n```\n\n"
  },
  {
    "path": "docs/utility_function/index.md",
    "content": "---\nlayout: default\ntitle: \"Utility Function\"\nnav_order: 4\nhas_children: true\n---"
  },
  {
    "path": "docs/utility_function/llm.md",
    "content": "---\nlayout: default\ntitle: \"LLM Wrapper\"\nparent: \"Utility Function\"\nnav_order: 1\n---\n\n# LLM Wrappers\n\nCheck out libraries like [litellm](https://github.com/BerriAI/litellm). \nHere, we provide some minimal example implementations:\n\n1. OpenAI\n    ```python\n    def call_llm(prompt):\n        from openai import OpenAI\n        client = OpenAI(api_key=\"YOUR_API_KEY_HERE\")\n        r = client.chat.completions.create(\n            model=\"gpt-4o\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return r.choices[0].message.content\n\n    # Example usage\n    call_llm(\"How are you?\")\n    ```\n    > Store the API key in an environment variable like OPENAI_API_KEY for security.\n    {: .best-practice }\n\n2. Claude (Anthropic)\n    ```python\n    def call_llm(prompt):\n        from anthropic import Anthropic\n        client = Anthropic(api_key=\"YOUR_API_KEY_HERE\")\n        r = client.messages.create(\n            model=\"claude-sonnet-4-0\",\n            messages=[\n                {\"role\": \"user\", \"content\": prompt}\n            ]\n        )\n        return r.content[0].text\n    ```\n\n3. Google (Generative AI Studio / PaLM API)\n    ```python\n    def call_llm(prompt):\n    from google import genai\n    client = genai.Client(api_key='GEMINI_API_KEY')\n        response = client.models.generate_content(\n        model='gemini-2.5-pro',\n        contents=prompt\n    )\n    return response.text\n    ```\n\n4. Azure (Azure OpenAI)\n    ```python\n    def call_llm(prompt):\n        from openai import AzureOpenAI\n        client = AzureOpenAI(\n            azure_endpoint=\"https://<YOUR_RESOURCE_NAME>.openai.azure.com/\",\n            api_key=\"YOUR_API_KEY_HERE\",\n            api_version=\"2023-05-15\"\n        )\n        r = client.chat.completions.create(\n            model=\"<YOUR_DEPLOYMENT_NAME>\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return r.choices[0].message.content\n    ```\n\n5. Ollama (Local LLM)\n    ```python\n    def call_llm(prompt):\n        from ollama import chat\n        response = chat(\n            model=\"llama2\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return response.message.content\n    ```\n    \n6. DeepSeek\n    ```python\n    def call_llm(prompt):\n        from openai import OpenAI\n        client = OpenAI(api_key=\"YOUR_DEEPSEEK_API_KEY\", base_url=\"https://api.deepseek.com\")\n        r = client.chat.completions.create(\n            model=\"deepseek-chat\",\n            messages=[{\"role\": \"user\", \"content\": prompt}]\n        )\n        return r.choices[0].message.content\n    ```\n\n\n## Improvements\nFeel free to enhance your `call_llm` function as needed. Here are examples:\n\n- Handle chat history:\n\n```python\ndef call_llm(messages):\n    from openai import OpenAI\n    client = OpenAI(api_key=\"YOUR_API_KEY_HERE\")\n    r = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=messages\n    )\n    return r.choices[0].message.content\n```\n\n- Add in-memory caching \n\n```python\nfrom functools import lru_cache\n\n@lru_cache(maxsize=1000)\ndef call_llm(prompt):\n    # Your implementation here\n    pass\n```\n\n> ⚠️ Caching conflicts with Node retries, as retries yield the same result.\n>\n> To address this, you could use cached results only if not retried.\n{: .warning }\n\n\n```python\nfrom functools import lru_cache\n\n@lru_cache(maxsize=1000)\ndef cached_call(prompt):\n    pass\n\ndef call_llm(prompt, use_cache):\n    if use_cache:\n        return cached_call(prompt)\n    # Call the underlying function directly\n    return cached_call.__wrapped__(prompt)\n\nclass SummarizeNode(Node):\n    def exec(self, text):\n        return call_llm(f\"Summarize: {text}\", self.cur_retry==0)\n```\n\n- Enable logging:\n\n```python\ndef call_llm(prompt):\n    import logging\n    logging.info(f\"Prompt: {prompt}\")\n    response = ... # Your implementation here\n    logging.info(f\"Response: {response}\")\n    return response\n```\n\n"
  },
  {
    "path": "docs/utility_function/text_to_speech.md",
    "content": "---\nlayout: default\ntitle: \"Text-to-Speech\"\nparent: \"Utility Function\"\nnav_order: 7\n---\n\n# Text-to-Speech\n\n| **Service**          | **Free Tier**         | **Pricing Model**                                            | **Docs**                                                            |\n|----------------------|-----------------------|--------------------------------------------------------------|---------------------------------------------------------------------|\n| **Amazon Polly**     | 5M std + 1M neural   | ~$4 /M (std), ~$16 /M (neural) after free tier               | [Polly Docs](https://aws.amazon.com/polly/)                         |\n| **Google Cloud TTS** | 4M std + 1M WaveNet  | ~$4 /M (std), ~$16 /M (WaveNet) pay-as-you-go                | [Cloud TTS Docs](https://cloud.google.com/text-to-speech)           |\n| **Azure TTS**        | 500K neural ongoing  | ~$15 /M (neural), discount at higher volumes                 | [Azure TTS Docs](https://azure.microsoft.com/products/cognitive-services/text-to-speech/) |\n| **IBM Watson TTS**   | 10K chars Lite plan  | ~$0.02 /1K (i.e. ~$20 /M). Enterprise options available       | [IBM Watson Docs](https://www.ibm.com/cloud/watson-text-to-speech)   |\n| **ElevenLabs**       | 10K chars monthly    | From ~$5/mo (30K chars) up to $330/mo (2M chars). Enterprise  | [ElevenLabs Docs](https://elevenlabs.io)                            |\n\n## Example Python Code\n\n### Amazon Polly\n```python\nimport boto3\n\npolly = boto3.client(\"polly\", region_name=\"us-east-1\",\n                     aws_access_key_id=\"YOUR_AWS_ACCESS_KEY_ID\",\n                     aws_secret_access_key=\"YOUR_AWS_SECRET_ACCESS_KEY\")\n\nresp = polly.synthesize_speech(\n    Text=\"Hello from Polly!\",\n    OutputFormat=\"mp3\",\n    VoiceId=\"Joanna\"\n)\n\nwith open(\"polly.mp3\", \"wb\") as f:\n    f.write(resp[\"AudioStream\"].read())\n```\n\n### Google Cloud TTS\n```python\nfrom google.cloud import texttospeech\n\nclient = texttospeech.TextToSpeechClient()\ninput_text = texttospeech.SynthesisInput(text=\"Hello from Google Cloud TTS!\")\nvoice = texttospeech.VoiceSelectionParams(language_code=\"en-US\")\naudio_cfg = texttospeech.AudioConfig(audio_encoding=texttospeech.AudioEncoding.MP3)\n\nresp = client.synthesize_speech(input=input_text, voice=voice, audio_config=audio_cfg)\n\nwith open(\"gcloud_tts.mp3\", \"wb\") as f:\n    f.write(resp.audio_content)\n```\n\n### Azure TTS\n```python\nimport azure.cognitiveservices.speech as speechsdk\n\nspeech_config = speechsdk.SpeechConfig(\n    subscription=\"AZURE_KEY\", region=\"AZURE_REGION\")\naudio_cfg = speechsdk.audio.AudioConfig(filename=\"azure_tts.wav\")\n\nsynthesizer = speechsdk.SpeechSynthesizer(\n    speech_config=speech_config,\n    audio_config=audio_cfg\n)\n\nsynthesizer.speak_text_async(\"Hello from Azure TTS!\").get()\n```\n\n### IBM Watson TTS\n```python\nfrom ibm_watson import TextToSpeechV1\nfrom ibm_cloud_sdk_core.authenticators import IAMAuthenticator\n\nauth = IAMAuthenticator(\"IBM_API_KEY\")\nservice = TextToSpeechV1(authenticator=auth)\nservice.set_service_url(\"IBM_SERVICE_URL\")\n\nresp = service.synthesize(\n    \"Hello from IBM Watson!\",\n    voice=\"en-US_AllisonV3Voice\",\n    accept=\"audio/mp3\"\n).get_result()\n\nwith open(\"ibm_tts.mp3\", \"wb\") as f:\n    f.write(resp.content)\n```\n\n### ElevenLabs\n```python\nimport requests\n\napi_key = \"ELEVENLABS_KEY\"\nvoice_id = \"ELEVENLABS_VOICE\"\nurl = f\"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}\"\nheaders = {\"xi-api-key\": api_key, \"Content-Type\": \"application/json\"}\n\njson_data = {\n    \"text\": \"Hello from ElevenLabs!\",\n    \"voice_settings\": {\"stability\": 0.75, \"similarity_boost\": 0.75}\n}\n\nresp = requests.post(url, headers=headers, json=json_data)\n\nwith open(\"elevenlabs.mp3\", \"wb\") as f:\n    f.write(resp.content)\n```\n\n"
  },
  {
    "path": "docs/utility_function/vector.md",
    "content": "---\nlayout: default\ntitle: \"Vector Databases\"\nparent: \"Utility Function\"\nnav_order: 6\n---\n\n# Vector Databases\n\n\nBelow is a  table of the popular vector search solutions:\n\n| **Tool** | **Free Tier** | **Pricing Model** | **Docs** |\n| --- | --- | --- | --- |\n| **FAISS** | N/A, self-host | Open-source | [Faiss.ai](https://faiss.ai) |\n| **Pinecone** | 2GB free | From $25/mo | [pinecone.io](https://pinecone.io) |\n| **Qdrant** | 1GB free cloud | Pay-as-you-go | [qdrant.tech](https://qdrant.tech) |\n| **Weaviate** | 14-day sandbox | From $25/mo | [weaviate.io](https://weaviate.io) |\n| **Milvus** | 5GB free cloud | PAYG or $99/mo dedicated | [milvus.io](https://milvus.io) |\n| **Chroma** | N/A, self-host | Free (Apache 2.0) | [trychroma.com](https://trychroma.com) |\n| **Redis** | 30MB free | From $5/mo | [redis.io](https://redis.io) |\n\n---\n## Example Python Code\n\nBelow are basic usage snippets for each tool.\n\n### FAISS\n```python\nimport faiss\nimport numpy as np\n\n# Dimensionality of embeddings\nd = 128\n\n# Create a flat L2 index\nindex = faiss.IndexFlatL2(d)\n\n# Random vectors\ndata = np.random.random((1000, d)).astype('float32')\nindex.add(data)\n\n# Query\nquery = np.random.random((1, d)).astype('float32')\nD, I = index.search(query, k=5)\n\nprint(\"Distances:\", D)\nprint(\"Neighbors:\", I)\n```\n\n### Pinecone\n```python\nimport pinecone\n\npinecone.init(api_key=\"YOUR_API_KEY\", environment=\"YOUR_ENV\")\n\nindex_name = \"my-index\"\n\n# Create the index if it doesn't exist\nif index_name not in pinecone.list_indexes():\n    pinecone.create_index(name=index_name, dimension=128)\n\n# Connect\nindex = pinecone.Index(index_name)\n\n# Upsert\nvectors = [\n    (\"id1\", [0.1]*128),\n    (\"id2\", [0.2]*128)\n]\nindex.upsert(vectors)\n\n# Query\nresponse = index.query([[0.15]*128], top_k=3)\nprint(response)\n```\n\n### Qdrant\n```python\nimport qdrant_client\nfrom qdrant_client.models import Distance, VectorParams, PointStruct\n\nclient = qdrant_client.QdrantClient(\n    url=\"https://YOUR-QDRANT-CLOUD-ENDPOINT\",\n    api_key=\"YOUR_API_KEY\"\n)\n\ncollection = \"my_collection\"\nclient.recreate_collection(\n    collection_name=collection,\n    vectors_config=VectorParams(size=128, distance=Distance.COSINE)\n)\n\npoints = [\n    PointStruct(id=1, vector=[0.1]*128, payload={\"type\": \"doc1\"}),\n    PointStruct(id=2, vector=[0.2]*128, payload={\"type\": \"doc2\"}),\n]\n\nclient.upsert(collection_name=collection, points=points)\n\nresults = client.search(\n    collection_name=collection,\n    query_vector=[0.15]*128,\n    limit=2\n)\nprint(results)\n```\n\n### Weaviate\n```python\nimport weaviate\n\nclient = weaviate.Client(\"https://YOUR-WEAVIATE-CLOUD-ENDPOINT\")\n\nschema = {\n    \"classes\": [\n        {\n            \"class\": \"Article\",\n            \"vectorizer\": \"none\"\n        }\n    ]\n}\nclient.schema.create(schema)\n\nobj = {\n    \"title\": \"Hello World\",\n    \"content\": \"Weaviate vector search\"\n}\nclient.data_object.create(obj, \"Article\", vector=[0.1]*128)\n\nresp = (\n    client.query\n    .get(\"Article\", [\"title\", \"content\"])\n    .with_near_vector({\"vector\": [0.15]*128})\n    .with_limit(3)\n    .do()\n)\nprint(resp)\n```\n\n### Milvus\n```python\nfrom pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection\nimport numpy as np\n\nconnections.connect(alias=\"default\", host=\"localhost\", port=\"19530\")\n\nfields = [\n    FieldSchema(name=\"id\", dtype=DataType.INT64, is_primary=True),\n    FieldSchema(name=\"embedding\", dtype=DataType.FLOAT_VECTOR, dim=128)\n]\nschema = CollectionSchema(fields)\ncollection = Collection(\"MyCollection\", schema)\n\nemb = np.random.rand(10, 128).astype('float32')\nids = list(range(10))\ncollection.insert([ids, emb])\n\nindex_params = {\n    \"index_type\": \"IVF_FLAT\",\n    \"params\": {\"nlist\": 128},\n    \"metric_type\": \"L2\"\n}\ncollection.create_index(\"embedding\", index_params)\ncollection.load()\n\nquery_emb = np.random.rand(1, 128).astype('float32')\nresults = collection.search(query_emb, \"embedding\", param={\"nprobe\": 10}, limit=3)\nprint(results)\n```\n\n### Chroma\n```python\nimport chromadb\nfrom chromadb.config import Settings\n\nclient = chromadb.Client(Settings(\n    chroma_db_impl=\"duckdb+parquet\",\n    persist_directory=\"./chroma_data\"\n))\n\ncoll = client.create_collection(\"my_collection\")\n\nvectors = [[0.1, 0.2, 0.3], [0.2, 0.2, 0.2]]\nmetas = [{\"doc\": \"text1\"}, {\"doc\": \"text2\"}]\nids = [\"id1\", \"id2\"]\ncoll.add(embeddings=vectors, metadatas=metas, ids=ids)\n\nres = coll.query(query_embeddings=[[0.15, 0.25, 0.3]], n_results=2)\nprint(res)\n```\n\n### Redis\n```python\nimport redis\nimport struct\n\nr = redis.Redis(host=\"localhost\", port=6379)\n\n# Create index\nr.execute_command(\n    \"FT.CREATE\", \"my_idx\", \"ON\", \"HASH\",\n    \"SCHEMA\", \"embedding\", \"VECTOR\", \"FLAT\", \"6\",\n    \"TYPE\", \"FLOAT32\", \"DIM\", \"128\",\n    \"DISTANCE_METRIC\", \"L2\"\n)\n\n# Insert\nvec = struct.pack('128f', *[0.1]*128)\nr.hset(\"doc1\", mapping={\"embedding\": vec})\n\n# Search\nqvec = struct.pack('128f', *[0.15]*128)\nq = \"*=>[KNN 3 @embedding $BLOB AS dist]\"\nres = r.ft(\"my_idx\").search(q, query_params={\"BLOB\": qvec})\nprint(res.docs)\n```\n\n"
  },
  {
    "path": "docs/utility_function/viz.md",
    "content": "---\nlayout: default\ntitle: \"Viz and Debug\"\nparent: \"Utility Function\"\nnav_order: 2\n---\n\n# Visualization and Debugging\n\nSimilar to LLM wrappers, we **don't** provide built-in visualization and debugging. Here, we recommend some *minimal* (and incomplete) implementations These examples can serve as a starting point for your own tooling.\n\n## 1. Visualization with Mermaid\n\nThis code recursively traverses the nested graph, assigns unique IDs to each node, and treats Flow nodes as subgraphs to generate Mermaid syntax for a hierarchical visualization.\n\n{% raw %}\n```python\ndef build_mermaid(start):\n    ids, visited, lines = {}, set(), [\"graph LR\"]\n    ctr = 1\n    def get_id(n):\n        nonlocal ctr\n        return ids[n] if n in ids else (ids.setdefault(n, f\"N{ctr}\"), (ctr := ctr + 1))[0]\n    def link(a, b):\n        lines.append(f\"    {a} --> {b}\")\n    def walk(node, parent=None):\n        if node in visited:\n            return parent and link(parent, get_id(node))\n        visited.add(node)\n        if isinstance(node, Flow):\n            node.start_node and parent and link(parent, get_id(node.start_node))\n            lines.append(f\"\\n    subgraph sub_flow_{get_id(node)}[{type(node).__name__}]\")\n            node.start_node and walk(node.start_node)\n            for nxt in node.successors.values():\n                node.start_node and walk(nxt, get_id(node.start_node)) or (parent and link(parent, get_id(nxt))) or walk(nxt)\n            lines.append(\"    end\\n\")\n        else:\n            lines.append(f\"    {(nid := get_id(node))}['{type(node).__name__}']\")\n            parent and link(parent, nid)\n            [walk(nxt, nid) for nxt in node.successors.values()]\n    walk(start)\n    return \"\\n\".join(lines)\n```\n{% endraw %}\n\n\nFor example, suppose we have a complex Flow for data science:\n\n```python\nclass DataPrepBatchNode(BatchNode):\n    def prep(self,shared): return []\nclass ValidateDataNode(Node): pass\nclass FeatureExtractionNode(Node): pass\nclass TrainModelNode(Node): pass\nclass EvaluateModelNode(Node): pass\nclass ModelFlow(Flow): pass\nclass DataScienceFlow(Flow):pass\n\nfeature_node = FeatureExtractionNode()\ntrain_node = TrainModelNode()\nevaluate_node = EvaluateModelNode()\nfeature_node >> train_node >> evaluate_node\nmodel_flow = ModelFlow(start=feature_node)\ndata_prep_node = DataPrepBatchNode()\nvalidate_node = ValidateDataNode()\ndata_prep_node >> validate_node >> model_flow\ndata_science_flow = DataScienceFlow(start=data_prep_node)\nresult = build_mermaid(start=data_science_flow)\n```\n\nThe code generates a Mermaid diagram:\n\n```mermaid\ngraph LR\n    subgraph sub_flow_N1[DataScienceFlow]\n    N2['DataPrepBatchNode']\n    N3['ValidateDataNode']\n    N2 --> N3\n    N3 --> N4\n\n    subgraph sub_flow_N5[ModelFlow]\n    N4['FeatureExtractionNode']\n    N6['TrainModelNode']\n    N4 --> N6\n    N7['EvaluateModelNode']\n    N6 --> N7\n    end\n\n    end\n```\n\nFor visualization based on d3.js, check out [the cookbook](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-visualization).\n\n## 2. Call Stack Debugging\n\nIt would be useful to print the Node call stacks for debugging. This can be achieved by inspecting the runtime call stack:\n\n```python\nimport inspect\n\ndef get_node_call_stack():\n    stack = inspect.stack()\n    node_names = []\n    seen_ids = set()\n    for frame_info in stack[1:]:\n        local_vars = frame_info.frame.f_locals\n        if 'self' in local_vars:\n            caller_self = local_vars['self']\n            if isinstance(caller_self, BaseNode) and id(caller_self) not in seen_ids:\n                seen_ids.add(id(caller_self))\n                node_names.append(type(caller_self).__name__)\n    return node_names\n```\n\nFor example, suppose we have a complex Flow for data science:\n\n```python\nclass DataPrepBatchNode(BatchNode): \n    def prep(self, shared): return []\nclass ValidateDataNode(Node): pass\nclass FeatureExtractionNode(Node): pass\nclass TrainModelNode(Node): pass\nclass EvaluateModelNode(Node): \n    def prep(self, shared):\n        stack = get_node_call_stack()\n        print(\"Call stack:\", stack)\nclass ModelFlow(Flow): pass\nclass DataScienceFlow(Flow):pass\n\nfeature_node = FeatureExtractionNode()\ntrain_node = TrainModelNode()\nevaluate_node = EvaluateModelNode()\nfeature_node >> train_node >> evaluate_node\nmodel_flow = ModelFlow(start=feature_node)\ndata_prep_node = DataPrepBatchNode()\nvalidate_node = ValidateDataNode()\ndata_prep_node >> validate_node >> model_flow\ndata_science_flow = DataScienceFlow(start=data_prep_node)\ndata_science_flow.run({})\n```\n\nThe output would be: `Call stack: ['EvaluateModelNode', 'ModelFlow', 'DataScienceFlow']`\n\nFor a more complete implementation, check out [the cookbook](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-tracing)."
  },
  {
    "path": "docs/utility_function/websearch.md",
    "content": "---\nlayout: default\ntitle: \"Web Search\"\nparent: \"Utility Function\"\nnav_order: 3\n---\n# Web Search\n\nWe recommend some implementations of commonly used web search tools.\n\n| **API**                         | **Free Tier**                                | **Pricing Model**                                              | **Docs**                                                  |\n|---------------------------------|-----------------------------------------------|-----------------------------------------------------------------|------------------------------------------------------------------------|\n| **Google Custom Search JSON API** | 100 queries/day free       | $5 per 1000 queries.           | [Link](https://developers.google.com/custom-search/v1/overview)        |\n| **Bing Web Search API**         | 1,000 queries/month               | $15–$25 per 1,000 queries. | [Link](https://azure.microsoft.com/en-us/services/cognitive-services/bing-web-search-api/) |\n| **DuckDuckGo Instant Answer**   | Completely free (Instant Answers only, **no URLs**) | No paid plans; usage unlimited, but data is limited             | [Link](https://duckduckgo.com/api)                                     |\n| **Brave Search API**         | 2,000 queries/month free | $3 per 1k queries for Base, $5 per 1k for Pro | [Link](https://brave.com/search/api/)                                  |\n| **SerpApi**              | 100 searches/month free            | Start at $75/month for 5,000 searches| [Link](https://serpapi.com/)                                             |\n| **RapidAPI**           | Many  options    | Many  options             | [Link](https://rapidapi.com/search?term=search&sortBy=ByRelevance)      |\n\n## Example Python Code\n\n### 1. Google Custom Search JSON API\n```python\nimport requests\n\nAPI_KEY = \"YOUR_API_KEY\"\nCX_ID = \"YOUR_CX_ID\"\nquery = \"example\"\n\nurl = \"https://www.googleapis.com/customsearch/v1\"\nparams = {\n    \"key\": API_KEY,\n    \"cx\": CX_ID,\n    \"q\": query\n}\n\nresponse = requests.get(url, params=params)\nresults = response.json()\nprint(results)\n```\n\n### 2. Bing Web Search API\n```python\nimport requests\n\nSUBSCRIPTION_KEY = \"YOUR_BING_API_KEY\"\nquery = \"example\"\n\nurl = \"https://api.bing.microsoft.com/v7.0/search\"\nheaders = {\"Ocp-Apim-Subscription-Key\": SUBSCRIPTION_KEY}\nparams = {\"q\": query}\n\nresponse = requests.get(url, headers=headers, params=params)\nresults = response.json()\nprint(results)\n```\n\n### 3. DuckDuckGo Instant Answer\n```python\nimport requests\n\nquery = \"example\"\nurl = \"https://api.duckduckgo.com/\"\nparams = {\n    \"q\": query,\n    \"format\": \"json\"\n}\n\nresponse = requests.get(url, params=params)\nresults = response.json()\nprint(results)\n```\n\n### 4. Brave Search API\n```python\nimport requests\n\nSUBSCRIPTION_TOKEN = \"YOUR_BRAVE_API_TOKEN\"\nquery = \"example\"\n\nurl = \"https://api.search.brave.com/res/v1/web/search\"\nheaders = {\n    \"X-Subscription-Token\": SUBSCRIPTION_TOKEN\n}\nparams = {\n    \"q\": query\n}\n\nresponse = requests.get(url, headers=headers, params=params)\nresults = response.json()\nprint(results)\n```\n\n### 5. SerpApi\n```python\nimport requests\n\nAPI_KEY = \"YOUR_SERPAPI_KEY\"\nquery = \"example\"\n\nurl = \"https://serpapi.com/search\"\nparams = {\n    \"engine\": \"google\",\n    \"q\": query,\n    \"api_key\": API_KEY\n}\n\nresponse = requests.get(url, params=params)\nresults = response.json()\nprint(results)\n```\n\n\n"
  },
  {
    "path": "pocketflow/__init__.py",
    "content": "import asyncio, warnings, copy, time\n\nclass BaseNode:\n    def __init__(self): self.params,self.successors={},{}\n    def set_params(self,params): self.params=params\n    def next(self,node,action=\"default\"):\n        if action in self.successors: warnings.warn(f\"Overwriting successor for action '{action}'\")\n        self.successors[action]=node; return node\n    def prep(self,shared): pass\n    def exec(self,prep_res): pass\n    def post(self,shared,prep_res,exec_res): pass\n    def _exec(self,prep_res): return self.exec(prep_res)\n    def _run(self,shared): p=self.prep(shared); e=self._exec(p); return self.post(shared,p,e)\n    def run(self,shared): \n        if self.successors: warnings.warn(\"Node won't run successors. Use Flow.\")  \n        return self._run(shared)\n    def __rshift__(self,other): return self.next(other)\n    def __sub__(self,action):\n        if isinstance(action,str): return _ConditionalTransition(self,action)\n        raise TypeError(\"Action must be a string\")\n\nclass _ConditionalTransition:\n    def __init__(self,src,action): self.src,self.action=src,action\n    def __rshift__(self,tgt): return self.src.next(tgt,self.action)\n\nclass Node(BaseNode):\n    def __init__(self,max_retries=1,wait=0): super().__init__(); self.max_retries,self.wait=max_retries,wait\n    def exec_fallback(self,prep_res,exc): raise exc\n    def _exec(self,prep_res):\n        for self.cur_retry in range(self.max_retries):\n            try: return self.exec(prep_res)\n            except Exception as e:\n                if self.cur_retry==self.max_retries-1: return self.exec_fallback(prep_res,e)\n                if self.wait>0: time.sleep(self.wait)\n\nclass BatchNode(Node):\n    def _exec(self,items): return [super(BatchNode,self)._exec(i) for i in (items or [])]\n\nclass Flow(BaseNode):\n    def __init__(self,start=None): super().__init__(); self.start_node=start\n    def start(self,start): self.start_node=start; return start\n    def get_next_node(self,curr,action):\n        nxt=curr.successors.get(action or \"default\")\n        if not nxt and curr.successors: warnings.warn(f\"Flow ends: '{action}' not found in {list(curr.successors)}\")\n        return nxt\n    def _orch(self,shared,params=None):\n        curr,p,last_action =copy.copy(self.start_node),(params or {**self.params}),None\n        while curr: curr.set_params(p); last_action=curr._run(shared); curr=copy.copy(self.get_next_node(curr,last_action))\n        return last_action\n    def _run(self,shared): p=self.prep(shared); o=self._orch(shared); return self.post(shared,p,o)\n    def post(self,shared,prep_res,exec_res): return exec_res\n\nclass BatchFlow(Flow):\n    def _run(self,shared):\n        pr=self.prep(shared) or []\n        for bp in pr: self._orch(shared,{**self.params,**bp})\n        return self.post(shared,pr,None)\n\nclass AsyncNode(Node):\n    async def prep_async(self,shared): pass\n    async def exec_async(self,prep_res): pass\n    async def exec_fallback_async(self,prep_res,exc): raise exc\n    async def post_async(self,shared,prep_res,exec_res): pass\n    async def _exec(self,prep_res): \n        for self.cur_retry in range(self.max_retries):\n            try: return await self.exec_async(prep_res)\n            except Exception as e:\n                if self.cur_retry==self.max_retries-1: return await self.exec_fallback_async(prep_res,e)\n                if self.wait>0: await asyncio.sleep(self.wait)\n    async def run_async(self,shared): \n        if self.successors: warnings.warn(\"Node won't run successors. Use AsyncFlow.\")  \n        return await self._run_async(shared)\n    async def _run_async(self,shared): p=await self.prep_async(shared); e=await self._exec(p); return await self.post_async(shared,p,e)\n    def _run(self,shared): raise RuntimeError(\"Use run_async.\")\n\nclass AsyncBatchNode(AsyncNode,BatchNode):\n    async def _exec(self,items): return [await super(AsyncBatchNode,self)._exec(i) for i in items]\n\nclass AsyncParallelBatchNode(AsyncNode,BatchNode):\n    async def _exec(self,items): return await asyncio.gather(*(super(AsyncParallelBatchNode,self)._exec(i) for i in items))\n\nclass AsyncFlow(Flow,AsyncNode):\n    async def _orch_async(self,shared,params=None):\n        curr,p,last_action =copy.copy(self.start_node),(params or {**self.params}),None\n        while curr: curr.set_params(p); last_action=await curr._run_async(shared) if isinstance(curr,AsyncNode) else curr._run(shared); curr=copy.copy(self.get_next_node(curr,last_action))\n        return last_action\n    async def _run_async(self,shared): p=await self.prep_async(shared); o=await self._orch_async(shared); return await self.post_async(shared,p,o)\n    async def post_async(self,shared,prep_res,exec_res): return exec_res\n\nclass AsyncBatchFlow(AsyncFlow,BatchFlow):\n    async def _run_async(self,shared):\n        pr=await self.prep_async(shared) or []\n        for bp in pr: await self._orch_async(shared,{**self.params,**bp})\n        return await self.post_async(shared,pr,None)\n\nclass AsyncParallelBatchFlow(AsyncFlow,BatchFlow):\n    async def _run_async(self,shared): \n        pr=await self.prep_async(shared) or []\n        await asyncio.gather(*(self._orch_async(shared,{**self.params,**bp}) for bp in pr))\n        return await self.post_async(shared,pr,None)"
  },
  {
    "path": "pocketflow/__init__.pyi",
    "content": "import asyncio\nfrom typing import Any, Dict, List, Optional, Union, TypeVar, Generic\n\n# Type variables for better type relationships\n_PrepResult = TypeVar('_PrepResult')\n_ExecResult = TypeVar('_ExecResult')\n_PostResult = TypeVar('_PostResult')\n\n# More specific parameter types\nParamValue = Union[str, int, float, bool, None, List[Any], Dict[str, Any]]\nSharedData = Dict[str, Any]\nParams = Dict[str, ParamValue]\n\nclass BaseNode(Generic[_PrepResult, _ExecResult, _PostResult]):\n    params: Params\n    successors: Dict[str, BaseNode[Any, Any, Any]]\n    \n    def __init__(self) -> None: ...\n    def set_params(self, params: Params) -> None: ...\n    def next(self, node: BaseNode[Any, Any, Any], action: str = \"default\") -> BaseNode[Any, Any, Any]: ...\n    def prep(self, shared: SharedData) -> _PrepResult: ...\n    def exec(self, prep_res: _PrepResult) -> _ExecResult: ...\n    def post(self, shared: SharedData, prep_res: _PrepResult, exec_res: _ExecResult) -> _PostResult: ...\n    def _exec(self, prep_res: _PrepResult) -> _ExecResult: ...\n    def _run(self, shared: SharedData) -> _PostResult: ...\n    def run(self, shared: SharedData) -> _PostResult: ...\n    def __rshift__(self, other: BaseNode[Any, Any, Any]) -> BaseNode[Any, Any, Any]: ...\n    def __sub__(self, action: str) -> _ConditionalTransition: ...\n\nclass _ConditionalTransition:\n    src: BaseNode[Any, Any, Any]\n    action: str\n    \n    def __init__(self, src: BaseNode[Any, Any, Any], action: str) -> None: ...\n    def __rshift__(self, tgt: BaseNode[Any, Any, Any]) -> BaseNode[Any, Any, Any]: ...\n\nclass Node(BaseNode[_PrepResult, _ExecResult, _PostResult]):\n    max_retries: int\n    wait: Union[int, float]\n    cur_retry: int\n    \n    def __init__(self, max_retries: int = 1, wait: Union[int, float] = 0) -> None: ...\n    def exec_fallback(self, prep_res: _PrepResult, exc: Exception) -> _ExecResult: ...\n    def _exec(self, prep_res: _PrepResult) -> _ExecResult: ...\n\nclass BatchNode(Node[Optional[List[_PrepResult]], List[_ExecResult], _PostResult]):\n    def _exec(self, items: Optional[List[_PrepResult]]) -> List[_ExecResult]: ...\n\nclass Flow(BaseNode[_PrepResult, Any, _PostResult]):\n    start_node: Optional[BaseNode[Any, Any, Any]]\n    \n    def __init__(self, start: Optional[BaseNode[Any, Any, Any]] = None) -> None: ...\n    def start(self, start: BaseNode[Any, Any, Any]) -> BaseNode[Any, Any, Any]: ...\n    def get_next_node(\n        self, curr: BaseNode[Any, Any, Any], action: Optional[str]\n    ) -> Optional[BaseNode[Any, Any, Any]]: ...\n    def _orch(\n        self, shared: SharedData, params: Optional[Params] = None\n    ) -> Any: ...\n    def _run(self, shared: SharedData) -> _PostResult: ...\n    def post(self, shared: SharedData, prep_res: _PrepResult, exec_res: Any) -> _PostResult: ...\n\nclass BatchFlow(Flow[Optional[List[Params]], Any, _PostResult]):\n    def _run(self, shared: SharedData) -> _PostResult: ...\n\nclass AsyncNode(Node[_PrepResult, _ExecResult, _PostResult]):\n    async def prep_async(self, shared: SharedData) -> _PrepResult: ...\n    async def exec_async(self, prep_res: _PrepResult) -> _ExecResult: ...\n    async def exec_fallback_async(self, prep_res: _PrepResult, exc: Exception) -> _ExecResult: ...\n    async def post_async(\n        self, shared: SharedData, prep_res: _PrepResult, exec_res: _ExecResult\n    ) -> _PostResult: ...\n    async def _exec(self, prep_res: _PrepResult) -> _ExecResult: ...\n    async def run_async(self, shared: SharedData) -> _PostResult: ...\n    async def _run_async(self, shared: SharedData) -> _PostResult: ...\n    def _run(self, shared: SharedData) -> _PostResult: ...\n\nclass AsyncBatchNode(AsyncNode[Optional[List[_PrepResult]], List[_ExecResult], _PostResult], BatchNode[Optional[List[_PrepResult]], List[_ExecResult], _PostResult]):\n    async def _exec(self, items: Optional[List[_PrepResult]]) -> List[_ExecResult]: ...\n\nclass AsyncParallelBatchNode(AsyncNode[Optional[List[_PrepResult]], List[_ExecResult], _PostResult], BatchNode[Optional[List[_PrepResult]], List[_ExecResult], _PostResult]):\n    async def _exec(self, items: Optional[List[_PrepResult]]) -> List[_ExecResult]: ...\n\nclass AsyncFlow(Flow[_PrepResult, Any, _PostResult], AsyncNode[_PrepResult, Any, _PostResult]):\n    async def _orch_async(\n        self, shared: SharedData, params: Optional[Params] = None\n    ) -> Any: ...\n    async def _run_async(self, shared: SharedData) -> _PostResult: ...\n    async def post_async(\n        self, shared: SharedData, prep_res: _PrepResult, exec_res: Any\n    ) -> _PostResult: ...\n\nclass AsyncBatchFlow(AsyncFlow[Optional[List[Params]], Any, _PostResult], BatchFlow[Optional[List[Params]], Any, _PostResult]):\n    async def _run_async(self, shared: SharedData) -> _PostResult: ...\n\nclass AsyncParallelBatchFlow(AsyncFlow[Optional[List[Params]], Any, _PostResult], BatchFlow[Optional[List[Params]], Any, _PostResult]):\n    async def _run_async(self, shared: SharedData) -> _PostResult: ..."
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup, find_packages\n\nsetup(\n    name=\"pocketflow\",\n    version=\"0.0.3\",\n    packages=find_packages(),\n    author=\"Zachary Huang\",\n    author_email=\"zh2408@columbia.edu\",\n    description=\"Pocket Flow: 100-line LLM framework. Let Agents build Agents!\",\n    url=\"https://github.com/The-Pocket/PocketFlow\",\n)"
  },
  {
    "path": "tests/test_async_batch_flow.py",
    "content": "import unittest\nimport asyncio\nimport sys\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent.parent))\nfrom pocketflow import AsyncNode, AsyncBatchFlow\n\nclass AsyncDataProcessNode(AsyncNode):\n    async def prep_async(self, shared_storage):\n        key = self.params.get('key')\n        data = shared_storage['input_data'][key]\n        if 'results' not in shared_storage:\n            shared_storage['results'] = {}\n        shared_storage['results'][key] = data\n        return data\n\n    async def post_async(self, shared_storage, prep_result, proc_result):\n        await asyncio.sleep(0.01)  # Simulate async work\n        key = self.params.get('key')\n        shared_storage['results'][key] = prep_result * 2  # Double the value\n        return \"processed\"\n\nclass AsyncErrorNode(AsyncNode):\n    async def post_async(self, shared_storage, prep_result, proc_result):\n        key = self.params.get('key')\n        if key == 'error_key':\n            raise ValueError(f\"Async error processing key: {key}\")\n        return \"processed\"\n\nclass TestAsyncBatchFlow(unittest.TestCase):\n    def setUp(self):\n        self.process_node = AsyncDataProcessNode()\n\n    def test_basic_async_batch_processing(self):\n        \"\"\"Test basic async batch processing with multiple keys\"\"\"\n        class SimpleTestAsyncBatchFlow(AsyncBatchFlow):\n            async def prep_async(self, shared_storage):\n                return [{'key': k} for k in shared_storage['input_data'].keys()]\n\n        shared_storage = {\n            'input_data': {\n                'a': 1,\n                'b': 2,\n                'c': 3\n            }\n        }\n\n        flow = SimpleTestAsyncBatchFlow(start=self.process_node)\n        asyncio.run(flow.run_async(shared_storage))\n\n        expected_results = {\n            'a': 2,  # 1 * 2\n            'b': 4,  # 2 * 2\n            'c': 6   # 3 * 2\n        }\n        self.assertEqual(shared_storage['results'], expected_results)\n\n    def test_empty_async_batch(self):\n        \"\"\"Test async batch processing with empty input\"\"\"\n        class EmptyTestAsyncBatchFlow(AsyncBatchFlow):\n            async def prep_async(self, shared_storage):\n                return [{'key': k} for k in shared_storage['input_data'].keys()]\n\n        shared_storage = {\n            'input_data': {}\n        }\n\n        flow = EmptyTestAsyncBatchFlow(start=self.process_node)\n        asyncio.run(flow.run_async(shared_storage))\n\n        self.assertEqual(shared_storage.get('results', {}), {})\n\n    def test_async_error_handling(self):\n        \"\"\"Test error handling during async batch processing\"\"\"\n        class ErrorTestAsyncBatchFlow(AsyncBatchFlow):\n            async def prep_async(self, shared_storage):\n                return [{'key': k} for k in shared_storage['input_data'].keys()]\n\n        shared_storage = {\n            'input_data': {\n                'normal_key': 1,\n                'error_key': 2,\n                'another_key': 3\n            }\n        }\n\n        flow = ErrorTestAsyncBatchFlow(start=AsyncErrorNode())\n        \n        with self.assertRaises(ValueError):\n            asyncio.run(flow.run_async(shared_storage))\n\n    def test_nested_async_flow(self):\n        \"\"\"Test async batch processing with nested flows\"\"\"\n        class AsyncInnerNode(AsyncNode):\n            async def post_async(self, shared_storage, prep_result, proc_result):\n                key = self.params.get('key')\n                if 'intermediate_results' not in shared_storage:\n                    shared_storage['intermediate_results'] = {}\n                shared_storage['intermediate_results'][key] = shared_storage['input_data'][key] + 1\n                await asyncio.sleep(0.01)\n                return \"next\"\n\n        class AsyncOuterNode(AsyncNode):\n            async def post_async(self, shared_storage, prep_result, proc_result):\n                key = self.params.get('key')\n                if 'results' not in shared_storage:\n                    shared_storage['results'] = {}\n                shared_storage['results'][key] = shared_storage['intermediate_results'][key] * 2\n                await asyncio.sleep(0.01)\n                return \"done\"\n\n        class NestedAsyncBatchFlow(AsyncBatchFlow):\n            async def prep_async(self, shared_storage):\n                return [{'key': k} for k in shared_storage['input_data'].keys()]\n\n        # Create inner flow\n        inner_node = AsyncInnerNode()\n        outer_node = AsyncOuterNode()\n        inner_node - \"next\" >> outer_node\n\n        shared_storage = {\n            'input_data': {\n                'x': 1,\n                'y': 2\n            }\n        }\n\n        flow = NestedAsyncBatchFlow(start=inner_node)\n        asyncio.run(flow.run_async(shared_storage))\n\n        expected_results = {\n            'x': 4,  # (1 + 1) * 2\n            'y': 6   # (2 + 1) * 2\n        }\n        self.assertEqual(shared_storage['results'], expected_results)\n\n    def test_custom_async_parameters(self):\n        \"\"\"Test async batch processing with additional custom parameters\"\"\"\n        class CustomParamAsyncNode(AsyncNode):\n            async def post_async(self, shared_storage, prep_result, proc_result):\n                key = self.params.get('key')\n                multiplier = self.params.get('multiplier', 1)\n                await asyncio.sleep(0.01)\n                if 'results' not in shared_storage:\n                    shared_storage['results'] = {}\n                shared_storage['results'][key] = shared_storage['input_data'][key] * multiplier\n                return \"done\"\n\n        class CustomParamAsyncBatchFlow(AsyncBatchFlow):\n            async def prep_async(self, shared_storage):\n                return [{\n                    'key': k,\n                    'multiplier': i + 1\n                } for i, k in enumerate(shared_storage['input_data'].keys())]\n\n        shared_storage = {\n            'input_data': {\n                'a': 1,\n                'b': 2,\n                'c': 3\n            }\n        }\n\n        flow = CustomParamAsyncBatchFlow(start=CustomParamAsyncNode())\n        asyncio.run(flow.run_async(shared_storage))\n\n        expected_results = {\n            'a': 1 * 1,  # first item, multiplier = 1\n            'b': 2 * 2,  # second item, multiplier = 2\n            'c': 3 * 3   # third item, multiplier = 3\n        }\n        self.assertEqual(shared_storage['results'], expected_results)\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "tests/test_async_batch_node.py",
    "content": "import unittest\nimport asyncio\nimport sys\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent.parent))\nfrom pocketflow import AsyncNode, AsyncBatchNode, AsyncFlow\n\nclass AsyncArrayChunkNode(AsyncBatchNode):\n    def __init__(self, chunk_size=10):\n        super().__init__()\n        self.chunk_size = chunk_size\n    \n    async def prep_async(self, shared_storage):\n        # Get array from shared storage and split into chunks\n        array = shared_storage.get('input_array', [])\n        chunks = []\n        for start in range(0, len(array), self.chunk_size):\n            end = min(start + self.chunk_size, len(array))\n            chunks.append(array[start:end])\n        return chunks\n    \n    async def exec_async(self, chunk):\n        # Simulate async processing of each chunk\n        await asyncio.sleep(0.01)\n        return sum(chunk)\n        \n    async def post_async(self, shared_storage, prep_result, proc_result):\n        # Store chunk results in shared storage\n        shared_storage['chunk_results'] = proc_result\n        return \"processed\"\n\nclass AsyncSumReduceNode(AsyncNode):\n    async def prep_async(self, shared_storage):\n        # Get chunk results from shared storage\n        chunk_results = shared_storage.get('chunk_results', [])\n        await asyncio.sleep(0.01)  # Simulate async processing\n        total = sum(chunk_results)\n        shared_storage['total'] = total\n        return \"reduced\"\n\nclass TestAsyncBatchNode(unittest.TestCase):\n    def test_array_chunking(self):\n        \"\"\"\n        Test that the array is correctly split into chunks and processed asynchronously\n        \"\"\"\n        shared_storage = {\n            'input_array': list(range(25))  # [0,1,2,...,24]\n        }\n        \n        chunk_node = AsyncArrayChunkNode(chunk_size=10)\n        asyncio.run(chunk_node.run_async(shared_storage))\n        \n        results = shared_storage['chunk_results']\n        self.assertEqual(results, [45, 145, 110])  # Sum of chunks [0-9], [10-19], [20-24]\n        \n    # def test_async_map_reduce_sum(self):\n    #     \"\"\"\n    #     Test a complete async map-reduce pipeline that sums a large array:\n    #     1. Map: Split array into chunks and sum each chunk asynchronously\n    #     2. Reduce: Sum all the chunk sums asynchronously\n    #     \"\"\"\n    #     array = list(range(100))\n    #     expected_sum = sum(array)  # 4950\n        \n    #     shared_storage = {\n    #         'input_array': array\n    #     }\n        \n    #     # Create nodes\n    #     chunk_node = AsyncArrayChunkNode(chunk_size=10)\n    #     reduce_node = AsyncSumReduceNode()\n        \n    #     # Connect nodes\n    #     chunk_node - \"processed\" >> reduce_node\n        \n    #     # Create and run pipeline\n    #     pipeline = AsyncFlow(start=chunk_node)\n    #     asyncio.run(pipeline.run_async(shared_storage))\n        \n    #     self.assertEqual(shared_storage['total'], expected_sum)\n        \n    # def test_uneven_chunks(self):\n    #     \"\"\"\n    #     Test that the async map-reduce works correctly with array lengths\n    #     that don't divide evenly by chunk_size\n    #     \"\"\"\n    #     array = list(range(25))\n    #     expected_sum = sum(array)  # 300\n        \n    #     shared_storage = {\n    #         'input_array': array\n    #     }\n        \n    #     chunk_node = AsyncArrayChunkNode(chunk_size=10)\n    #     reduce_node = AsyncSumReduceNode()\n        \n    #     chunk_node - \"processed\" >> reduce_node\n    #     pipeline = AsyncFlow(start=chunk_node)\n    #     asyncio.run(pipeline.run_async(shared_storage))\n        \n    #     self.assertEqual(shared_storage['total'], expected_sum)\n\n    # def test_custom_chunk_size(self):\n    #     \"\"\"\n    #     Test that the async map-reduce works with different chunk sizes\n    #     \"\"\"\n    #     array = list(range(100))\n    #     expected_sum = sum(array)\n        \n    #     shared_storage = {\n    #         'input_array': array\n    #     }\n        \n    #     # Use chunk_size=15 instead of default 10\n    #     chunk_node = AsyncArrayChunkNode(chunk_size=15)\n    #     reduce_node = AsyncSumReduceNode()\n        \n    #     chunk_node - \"processed\" >> reduce_node\n    #     pipeline = AsyncFlow(start=chunk_node)\n    #     asyncio.run(pipeline.run_async(shared_storage))\n        \n    #     self.assertEqual(shared_storage['total'], expected_sum)\n        \n    # def test_single_element_chunks(self):\n    #     \"\"\"\n    #     Test extreme case where chunk_size=1\n    #     \"\"\"\n    #     array = list(range(5))\n    #     expected_sum = sum(array)\n        \n    #     shared_storage = {\n    #         'input_array': array\n    #     }\n        \n    #     chunk_node = AsyncArrayChunkNode(chunk_size=1)\n    #     reduce_node = AsyncSumReduceNode()\n        \n    #     chunk_node - \"processed\" >> reduce_node\n    #     pipeline = AsyncFlow(start=chunk_node)\n    #     asyncio.run(pipeline.run_async(shared_storage))\n        \n    #     self.assertEqual(shared_storage['total'], expected_sum)\n\n    # def test_empty_array(self):\n    #     \"\"\"\n    #     Test edge case of empty input array\n    #     \"\"\"\n    #     shared_storage = {\n    #         'input_array': []\n    #     }\n        \n    #     chunk_node = AsyncArrayChunkNode(chunk_size=10)\n    #     reduce_node = AsyncSumReduceNode()\n        \n    #     chunk_node - \"processed\" >> reduce_node\n    #     pipeline = AsyncFlow(start=chunk_node)\n    #     asyncio.run(pipeline.run_async(shared_storage))\n        \n    #     self.assertEqual(shared_storage['total'], 0)\n\n    # def test_error_handling(self):\n    #     \"\"\"\n    #     Test error handling in async batch processing\n    #     \"\"\"\n    #     class ErrorAsyncBatchNode(AsyncBatchNode):\n    #         async def exec_async(self, item):\n    #             if item == 2:\n    #                 raise ValueError(\"Error processing item 2\")\n    #             return item\n\n    #     shared_storage = {\n    #         'input_array': [1, 2, 3]\n    #     }\n        \n    #     error_node = ErrorAsyncBatchNode()\n    #     with self.assertRaises(ValueError):\n    #         asyncio.run(error_node.run_async(shared_storage))\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "tests/test_async_flow.py",
    "content": "import unittest\nimport asyncio\nimport sys\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent.parent))\nfrom pocketflow import Node, AsyncNode, AsyncFlow\n\nclass AsyncNumberNode(AsyncNode):\n    \"\"\"\n    Simple async node that sets 'current' to a given number.\n    Demonstrates overriding .process() (sync) and using\n    post_async() for the async portion.\n    \"\"\"\n    def __init__(self, number):\n        super().__init__()\n        self.number = number\n\n    async def prep_async(self, shared_storage):\n        # Synchronous work is allowed inside an AsyncNode,\n        # but final 'condition' is determined by post_async().\n        shared_storage['current'] = self.number\n        return \"set_number\"\n\n    async def post_async(self, shared_storage, prep_result, proc_result):\n        # Possibly do asynchronous tasks here\n        await asyncio.sleep(0.01)\n        # Return a condition for the flow\n        return \"number_set\"\n\nclass AsyncIncrementNode(AsyncNode):\n    \"\"\"\n    Demonstrates incrementing the 'current' value asynchronously.\n    \"\"\"\n    async def prep_async(self, shared_storage):\n        shared_storage['current'] = shared_storage.get('current', 0) + 1\n        return \"incremented\"\n\n    async def post_async(self, shared_storage, prep_result, proc_result):\n        await asyncio.sleep(0.01)  # simulate async I/O\n        return \"done\"\n\nclass AsyncSignalNode(AsyncNode):\n    \"\"\" An async node that returns a specific signal string from post_async. \"\"\"\n    def __init__(self, signal=\"default_async_signal\"):\n        super().__init__()\n        self.signal = signal\n\n    # No prep needed usually if just signaling\n    async def prep_async(self, shared_storage):\n        await asyncio.sleep(0.01) # Simulate async work\n\n    async def post_async(self, shared_storage, prep_result, exec_result):\n        # Store the signal in shared storage for verification\n        shared_storage['last_async_signal_emitted'] = self.signal\n        await asyncio.sleep(0.01) # Simulate async work\n        print(self.signal)\n        return self.signal # Return the specific action string\n\nclass AsyncPathNode(AsyncNode):\n    \"\"\" An async node to indicate which path was taken in the outer flow. \"\"\"\n    def __init__(self, path_id):\n        super().__init__()\n        self.path_id = path_id\n\n    async def prep_async(self, shared_storage):\n        await asyncio.sleep(0.01) # Simulate async work\n        shared_storage['async_path_taken'] = self.path_id\n\n    # post_async implicitly returns None (for default transition out if needed)\n    async def post_async(self, shared_storage, prep_result, exec_result):\n         await asyncio.sleep(0.01)\n         # Return None by default\n\nclass TestAsyncNode(unittest.TestCase):\n    \"\"\"\n    Test the AsyncNode (and descendants) in isolation (not in a flow).\n    \"\"\"\n    def test_async_number_node_direct_call(self):\n        \"\"\"\n        Even though AsyncNumberNode is designed for an async flow,\n        we can still test it directly by calling run_async().\n        \"\"\"\n        async def run_node():\n            node = AsyncNumberNode(42)\n            shared_storage = {}\n            condition = await node.run_async(shared_storage)\n            return shared_storage, condition\n\n        shared_storage, condition = asyncio.run(run_node())\n        self.assertEqual(shared_storage['current'], 42)\n        self.assertEqual(condition, \"number_set\")\n\n    def test_async_increment_node_direct_call(self):\n        async def run_node():\n            node = AsyncIncrementNode()\n            shared_storage = {'current': 10}\n            condition = await node.run_async(shared_storage)\n            return shared_storage, condition\n\n        shared_storage, condition = asyncio.run(run_node())\n        self.assertEqual(shared_storage['current'], 11)\n        self.assertEqual(condition, \"done\")\n\n\nclass TestAsyncFlow(unittest.TestCase):\n    \"\"\"\n    Test how AsyncFlow orchestrates multiple async nodes.\n    \"\"\"\n    def test_simple_async_flow(self):\n        \"\"\"\n        Flow:\n          1) AsyncNumberNode(5) -> sets 'current' to 5\n          2) AsyncIncrementNode() -> increments 'current' to 6\n        \"\"\"\n\n        # Create our nodes\n        start = AsyncNumberNode(5)\n        inc_node = AsyncIncrementNode()\n\n        # Chain them: start >> inc_node\n        start - \"number_set\" >> inc_node\n\n        # Create an AsyncFlow with start\n        flow = AsyncFlow(start)\n\n        # We'll run the flow synchronously (which under the hood is asyncio.run())\n        shared_storage = {}\n        asyncio.run(flow.run_async(shared_storage))\n\n        self.assertEqual(shared_storage['current'], 6)\n\n    def test_async_flow_branching(self):\n        \"\"\"\n        Demonstrate a branching scenario where we return different\n        conditions. For example, you could have an async node that\n        returns \"go_left\" or \"go_right\" in post_async, but here\n        we'll keep it simpler for demonstration.\n        \"\"\"\n\n        class BranchingAsyncNode(AsyncNode):\n            def exec(self, data):\n                value = shared_storage.get(\"value\", 0)\n                shared_storage[\"value\"] = value\n                # We'll decide branch based on whether 'value' is positive\n                return None\n\n            async def post_async(self, shared_storage, prep_result, proc_result):\n                await asyncio.sleep(0.01)\n                if shared_storage[\"value\"] >= 0:\n                    return \"positive_branch\"\n                else:\n                    return \"negative_branch\"\n\n        class PositiveNode(Node):\n            def exec(self, data):\n                shared_storage[\"path\"] = \"positive\"\n                return None\n\n        class NegativeNode(Node):\n            def exec(self, data):\n                shared_storage[\"path\"] = \"negative\"\n                return None\n\n        shared_storage = {\"value\": 10}\n\n        start = BranchingAsyncNode()\n        positive_node = PositiveNode()\n        negative_node = NegativeNode()\n\n        # Condition-based chaining\n        start - \"positive_branch\" >> positive_node\n        start - \"negative_branch\" >> negative_node\n\n        flow = AsyncFlow(start)\n        asyncio.run(flow.run_async(shared_storage))\n\n        self.assertEqual(shared_storage[\"path\"], \"positive\", \n                         \"Should have taken the positive branch\")\n\n    def test_async_composition_with_action_propagation(self):\n        \"\"\"\n        Test AsyncFlow branches based on action from nested AsyncFlow's last node.\n        \"\"\"\n        async def run_test():\n            shared_storage = {}\n\n            # 1. Define an inner async flow ending with AsyncSignalNode\n            # Use existing AsyncNumberNode which should return None from post_async implicitly\n            inner_start_node = AsyncNumberNode(200)\n            inner_end_node = AsyncSignalNode(\"async_inner_done\") # post_async -> \"async_inner_done\"\n            inner_start_node - \"number_set\" >> inner_end_node\n            # Inner flow will execute start->end, Flow exec returns \"async_inner_done\"\n            inner_flow = AsyncFlow(start=inner_start_node)\n\n            # 2. Define target async nodes for the outer flow branches\n            path_a_node = AsyncPathNode(\"AsyncA\") # post_async -> None\n            path_b_node = AsyncPathNode(\"AsyncB\") # post_async -> None\n\n            # 3. Define the outer async flow starting with the inner async flow\n            outer_flow = AsyncFlow(start=inner_flow)\n\n            # 4. Define branches FROM the inner_flow object based on its returned action\n            inner_flow - \"async_inner_done\" >> path_b_node  # This path should be taken\n            inner_flow - \"other_action\" >> path_a_node      # This path should NOT be taken\n\n            # 5. Run the outer async flow and capture the last action\n            # Execution: inner_start -> inner_end -> path_b\n            last_action_outer = await outer_flow.run_async(shared_storage)\n\n            # 6. Return results for assertion\n            return shared_storage, last_action_outer\n\n        # Run the async test function\n        shared_storage, last_action_outer = asyncio.run(run_test())\n\n        # 7. Assert the results\n        # Check state after inner flow execution\n        self.assertEqual(shared_storage.get('current'), 200) # From AsyncNumberNode\n        self.assertEqual(shared_storage.get('last_async_signal_emitted'), \"async_inner_done\")\n        # Check that the correct outer path was taken\n        self.assertEqual(shared_storage.get('async_path_taken'), \"AsyncB\")\n        # Check the action returned by the outer flow. The last node executed was\n        # path_b_node, which returns None from its post_async method.\n        self.assertIsNone(last_action_outer)\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "tests/test_async_parallel_batch_flow.py",
    "content": "import unittest\nimport asyncio\nimport sys\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent.parent))\nfrom pocketflow import AsyncNode, AsyncParallelBatchNode, AsyncParallelBatchFlow\n\nclass AsyncParallelNumberProcessor(AsyncParallelBatchNode):\n    def __init__(self, delay=0.1):\n        super().__init__()\n        self.delay = delay\n    \n    async def prep_async(self, shared_storage):\n        batch = shared_storage['batches'][self.params['batch_id']]\n        return batch\n    \n    async def exec_async(self, number):\n        await asyncio.sleep(self.delay)  # Simulate async processing\n        return number * 2\n        \n    async def post_async(self, shared_storage, prep_result, exec_result):\n        if 'processed_numbers' not in shared_storage:\n            shared_storage['processed_numbers'] = {}\n        shared_storage['processed_numbers'][self.params['batch_id']] = exec_result\n        return \"processed\"\n\nclass AsyncAggregatorNode(AsyncNode):\n    async def prep_async(self, shared_storage):\n        # Combine all batch results in order\n        all_results = []\n        processed = shared_storage.get('processed_numbers', {})\n        for i in range(len(processed)):\n            all_results.extend(processed[i])\n        return all_results\n    \n    async def exec_async(self, prep_result):\n        await asyncio.sleep(0.01)\n        return sum(prep_result)\n    \n    async def post_async(self, shared_storage, prep_result, exec_result):\n        shared_storage['total'] = exec_result\n        return \"aggregated\"\n\nclass TestAsyncParallelBatchFlow(unittest.TestCase):\n    def setUp(self):\n        self.loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(self.loop)\n    \n    def tearDown(self):\n        self.loop.close()\n\n    def test_parallel_batch_flow(self):\n        \"\"\"\n        Test basic parallel batch processing flow with batch IDs\n        \"\"\"\n        class TestParallelBatchFlow(AsyncParallelBatchFlow):\n            async def prep_async(self, shared_storage):\n                return [{'batch_id': i} for i in range(len(shared_storage['batches']))]\n\n        shared_storage = {\n            'batches': [\n                [1, 2, 3],  # batch_id: 0\n                [4, 5, 6],  # batch_id: 1\n                [7, 8, 9]   # batch_id: 2\n            ]\n        }\n\n        processor = AsyncParallelNumberProcessor(delay=0.1)\n        aggregator = AsyncAggregatorNode()\n        \n        processor - \"processed\" >> aggregator\n        flow = TestParallelBatchFlow(start=processor)\n        \n        start_time = self.loop.time()\n        self.loop.run_until_complete(flow.run_async(shared_storage))\n        execution_time = self.loop.time() - start_time\n\n        # Verify each batch was processed correctly\n        expected_batch_results = {\n            0: [2, 4, 6],    # [1,2,3] * 2\n            1: [8, 10, 12],  # [4,5,6] * 2\n            2: [14, 16, 18]  # [7,8,9] * 2\n        }\n        self.assertEqual(shared_storage['processed_numbers'], expected_batch_results)\n        \n        # Verify total\n        expected_total = sum(num * 2 for batch in shared_storage['batches'] for num in batch)\n        self.assertEqual(shared_storage['total'], expected_total)\n        \n        # Verify parallel execution\n        self.assertLess(execution_time, 0.2)\n\n    def test_error_handling(self):\n        \"\"\"\n        Test error handling in parallel batch flow\n        \"\"\"\n        class ErrorProcessor(AsyncParallelNumberProcessor):\n            async def exec_async(self, item):\n                if item == 2:\n                    raise ValueError(f\"Error processing item {item}\")\n                return item\n\n        class ErrorBatchFlow(AsyncParallelBatchFlow):\n            async def prep_async(self, shared_storage):\n                return [{'batch_id': i} for i in range(len(shared_storage['batches']))]\n\n        shared_storage = {\n            'batches': [\n                [1, 2, 3],  # Contains error-triggering value\n                [4, 5, 6]\n            ]\n        }\n\n        processor = ErrorProcessor()\n        flow = ErrorBatchFlow(start=processor)\n        \n        with self.assertRaises(ValueError):\n            self.loop.run_until_complete(flow.run_async(shared_storage))\n\n    def test_multiple_batch_sizes(self):\n        \"\"\"\n        Test parallel batch flow with varying batch sizes\n        \"\"\"\n        class VaryingBatchFlow(AsyncParallelBatchFlow):\n            async def prep_async(self, shared_storage):\n                return [{'batch_id': i} for i in range(len(shared_storage['batches']))]\n\n        shared_storage = {\n            'batches': [\n                [1],           # batch_id: 0\n                [2, 3, 4],    # batch_id: 1\n                [5, 6],       # batch_id: 2\n                [7, 8, 9, 10] # batch_id: 3\n            ]\n        }\n\n        processor = AsyncParallelNumberProcessor(delay=0.05)\n        aggregator = AsyncAggregatorNode()\n        \n        processor - \"processed\" >> aggregator\n        flow = VaryingBatchFlow(start=processor)\n        \n        self.loop.run_until_complete(flow.run_async(shared_storage))\n        \n        # Verify each batch was processed correctly\n        expected_batch_results = {\n            0: [2],                 # [1] * 2\n            1: [4, 6, 8],          # [2,3,4] * 2\n            2: [10, 12],           # [5,6] * 2\n            3: [14, 16, 18, 20]    # [7,8,9,10] * 2\n        }\n        self.assertEqual(shared_storage['processed_numbers'], expected_batch_results)\n        \n        # Verify total\n        expected_total = sum(num * 2 for batch in shared_storage['batches'] for num in batch)\n        self.assertEqual(shared_storage['total'], expected_total)\n\nclass AsyncItemNode(AsyncNode):\n    async def prep_async(self, shared_storage):\n        return shared_storage['groups'][self.params['group']][self.params['item']]\n    async def exec_async(self, prep_res):\n        return prep_res * 2\n    async def post_async(self, shared_storage, prep_res, exec_res):\n        group = self.params['group']\n        if 'results' not in shared_storage:\n            shared_storage['results'] = {}\n        if group not in shared_storage['results']:\n            shared_storage['results'][group] = []\n        shared_storage['results'][group].append(exec_res)\n\nclass InnerAsyncParallelBatchFlow(AsyncParallelBatchFlow):\n    async def prep_async(self, shared_storage):\n        group = self.params['group']\n        return [{'item': i, 'group': group} for i in range(len(shared_storage['groups'][group]))]\n\nclass OuterAsyncParallelBatchFlow(AsyncParallelBatchFlow):\n    async def prep_async(self, shared_storage):\n        return [{'group': g} for g in shared_storage['groups']]\n\nclass TestNestedAsyncParallelBatchFlow(unittest.TestCase):\n    def setUp(self):\n        self.loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(self.loop)\n\n    def tearDown(self):\n        self.loop.close()\n\n    def test_nested_parallel_batch_flow(self):\n        \"\"\"Test AsyncParallelBatchFlow nested inside another (same structure as sync test)\"\"\"\n        item_node = AsyncItemNode()\n        inner_flow = InnerAsyncParallelBatchFlow(start=item_node)\n        outer_flow = OuterAsyncParallelBatchFlow(start=inner_flow)\n\n        shared_storage = {\n            'groups': {\n                'A': [1, 2],\n                'B': [3, 4],\n            }\n        }\n\n        self.loop.run_until_complete(outer_flow.run_async(shared_storage))\n\n        expected = {\n            'A': [2, 4],\n            'B': [6, 8],\n        }\n        self.assertEqual(sorted(shared_storage['results'].keys()), sorted(expected.keys()))\n        for group in expected:\n            self.assertCountEqual(shared_storage['results'][group], expected[group])\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "tests/test_async_parallel_batch_node.py",
    "content": "import unittest\nimport asyncio\nimport sys\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent.parent))\nfrom pocketflow import AsyncParallelBatchNode, AsyncParallelBatchFlow\n\nclass AsyncParallelNumberProcessor(AsyncParallelBatchNode):\n    def __init__(self, delay=0.1):\n        super().__init__()\n        self.delay = delay\n    \n    async def prep_async(self, shared_storage):\n        numbers = shared_storage.get('input_numbers', [])\n        return numbers\n    \n    async def exec_async(self, number):\n        await asyncio.sleep(self.delay)  # Simulate async processing\n        return number * 2\n        \n    async def post_async(self, shared_storage, prep_result, exec_result):\n        shared_storage['processed_numbers'] = exec_result\n        return \"processed\"\n\nclass TestAsyncParallelBatchNode(unittest.TestCase):\n    def setUp(self):\n        # Reset the event loop for each test\n        self.loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(self.loop)\n    \n    def tearDown(self):\n        self.loop.close()\n    \n    def test_parallel_processing(self):\n        \"\"\"\n        Test that numbers are processed in parallel by measuring execution time\n        \"\"\"\n        shared_storage = {\n            'input_numbers': list(range(5))\n        }\n        \n        processor = AsyncParallelNumberProcessor(delay=0.1)\n        \n        # Run the processor\n        start_time = asyncio.get_event_loop().time()\n        self.loop.run_until_complete(processor.run_async(shared_storage))\n        end_time = asyncio.get_event_loop().time()\n        \n        # Check results\n        expected = [0, 2, 4, 6, 8]  # Each number doubled\n        self.assertEqual(shared_storage['processed_numbers'], expected)\n        \n        # Since processing is parallel, total time should be approximately\n        # equal to the delay of a single operation, not delay * number_of_items\n        execution_time = end_time - start_time\n        self.assertLess(execution_time, 0.2)  # Should be around 0.1s plus minimal overhead\n    \n    def test_empty_input(self):\n        \"\"\"\n        Test processing of empty input\n        \"\"\"\n        shared_storage = {\n            'input_numbers': []\n        }\n        \n        processor = AsyncParallelNumberProcessor()\n        self.loop.run_until_complete(processor.run_async(shared_storage))\n        \n        self.assertEqual(shared_storage['processed_numbers'], [])\n    \n    def test_single_item(self):\n        \"\"\"\n        Test processing of a single item\n        \"\"\"\n        shared_storage = {\n            'input_numbers': [42]\n        }\n        \n        processor = AsyncParallelNumberProcessor()\n        self.loop.run_until_complete(processor.run_async(shared_storage))\n        \n        self.assertEqual(shared_storage['processed_numbers'], [84])\n    \n    def test_large_batch(self):\n        \"\"\"\n        Test processing of a large batch of numbers\n        \"\"\"\n        input_size = 100\n        shared_storage = {\n            'input_numbers': list(range(input_size))\n        }\n        \n        processor = AsyncParallelNumberProcessor(delay=0.01)\n        self.loop.run_until_complete(processor.run_async(shared_storage))\n        \n        expected = [x * 2 for x in range(input_size)]\n        self.assertEqual(shared_storage['processed_numbers'], expected)\n    \n    def test_error_handling(self):\n        \"\"\"\n        Test error handling during parallel processing\n        \"\"\"\n        class ErrorProcessor(AsyncParallelNumberProcessor):\n            async def exec_async(self, item):\n                if item == 2:\n                    raise ValueError(f\"Error processing item {item}\")\n                return item\n        \n        shared_storage = {\n            'input_numbers': [1, 2, 3]\n        }\n        \n        processor = ErrorProcessor()\n        with self.assertRaises(ValueError):\n            self.loop.run_until_complete(processor.run_async(shared_storage))\n    \n    def test_concurrent_execution(self):\n        \"\"\"\n        Test that tasks are actually running concurrently by tracking execution order\n        \"\"\"\n        execution_order = []\n        \n        class OrderTrackingProcessor(AsyncParallelNumberProcessor):\n            async def exec_async(self, item):\n                delay = 0.1 if item % 2 == 0 else 0.05\n                await asyncio.sleep(delay)\n                execution_order.append(item)\n                return item\n        \n        shared_storage = {\n            'input_numbers': list(range(4))  # [0, 1, 2, 3]\n        }\n        \n        processor = OrderTrackingProcessor()\n        self.loop.run_until_complete(processor.run_async(shared_storage))\n        \n        # Odd numbers should finish before even numbers due to shorter delay\n        self.assertLess(execution_order.index(1), execution_order.index(0))\n        self.assertLess(execution_order.index(3), execution_order.index(2))\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "tests/test_batch_flow.py",
    "content": "import unittest\nimport sys\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent.parent))\nfrom pocketflow import Node, BatchFlow, Flow\n\nclass DataProcessNode(Node):\n    def prep(self, shared_storage):\n        key = self.params.get('key')\n        data = shared_storage['input_data'][key]\n        if 'results' not in shared_storage:\n            shared_storage['results'] = {}\n        shared_storage['results'][key] = data * 2\n\nclass ErrorProcessNode(Node):\n    def prep(self, shared_storage):\n        key = self.params.get('key')\n        if key == 'error_key':\n            raise ValueError(f\"Error processing key: {key}\")\n        if 'results' not in shared_storage:\n            shared_storage['results'] = {}\n        shared_storage['results'][key] = True\n\nclass TestBatchFlow(unittest.TestCase):\n    def setUp(self):\n        self.process_node = DataProcessNode()\n        \n    def test_basic_batch_processing(self):\n        \"\"\"Test basic batch processing with multiple keys\"\"\"\n        class SimpleTestBatchFlow(BatchFlow):\n            def prep(self, shared_storage):\n                return [{'key': k} for k in shared_storage['input_data'].keys()]\n\n        shared_storage = {\n            'input_data': {\n                'a': 1,\n                'b': 2,\n                'c': 3\n            }\n        }\n\n        flow = SimpleTestBatchFlow(start=self.process_node)\n        flow.run(shared_storage)\n\n        expected_results = {\n            'a': 2,\n            'b': 4,\n            'c': 6\n        }\n        self.assertEqual(shared_storage['results'], expected_results)\n\n    def test_empty_input(self):\n        \"\"\"Test batch processing with empty input dictionary\"\"\"\n        class EmptyTestBatchFlow(BatchFlow):\n            def prep(self, shared_storage):\n                return [{'key': k} for k in shared_storage['input_data'].keys()]\n\n        shared_storage = {\n            'input_data': {}\n        }\n\n        flow = EmptyTestBatchFlow(start=self.process_node)\n        flow.run(shared_storage)\n\n        self.assertEqual(shared_storage.get('results', {}), {})\n\n    def test_single_item(self):\n        \"\"\"Test batch processing with single item\"\"\"\n        class SingleItemBatchFlow(BatchFlow):\n            def prep(self, shared_storage):\n                return [{'key': k} for k in shared_storage['input_data'].keys()]\n\n        shared_storage = {\n            'input_data': {\n                'single': 5\n            }\n        }\n\n        flow = SingleItemBatchFlow(start=self.process_node)\n        flow.run(shared_storage)\n\n        expected_results = {\n            'single': 10\n        }\n        self.assertEqual(shared_storage['results'], expected_results)\n\n    def test_error_handling(self):\n        \"\"\"Test error handling during batch processing\"\"\"\n        class ErrorTestBatchFlow(BatchFlow):\n            def prep(self, shared_storage):\n                return [{'key': k} for k in shared_storage['input_data'].keys()]\n\n        shared_storage = {\n            'input_data': {\n                'normal_key': 1,\n                'error_key': 2,\n                'another_key': 3\n            }\n        }\n\n        flow = ErrorTestBatchFlow(start=ErrorProcessNode())\n        \n        with self.assertRaises(ValueError):\n            flow.run(shared_storage)\n\n    def test_nested_flow(self):\n        \"\"\"Test batch processing with nested flows\"\"\"\n        class InnerNode(Node):\n            def exec(self, prep_result):\n                key = self.params.get('key')\n                if 'intermediate_results' not in shared_storage:\n                    shared_storage['intermediate_results'] = {}\n                shared_storage['intermediate_results'][key] = shared_storage['input_data'][key] + 1\n\n        class OuterNode(Node):\n            def exec(self, prep_result):\n                key = self.params.get('key')\n                if 'results' not in shared_storage:\n                    shared_storage['results'] = {}\n                shared_storage['results'][key] = shared_storage['intermediate_results'][key] * 2\n\n        class NestedBatchFlow(BatchFlow):\n            def prep(self, shared_storage):\n                return [{'key': k} for k in shared_storage['input_data'].keys()]\n\n        # Create inner flow\n        inner_node = InnerNode()\n        outer_node = OuterNode()\n        inner_node >> outer_node\n\n        shared_storage = {\n            'input_data': {\n                'x': 1,\n                'y': 2\n            }\n        }\n\n        flow = NestedBatchFlow(start=inner_node)\n        flow.run(shared_storage)\n\n        expected_results = {\n            'x': 4,  # (1 + 1) * 2\n            'y': 6   # (2 + 1) * 2\n        }\n        self.assertEqual(shared_storage['results'], expected_results)\n\n    def test_custom_parameters(self):\n        \"\"\"Test batch processing with additional custom parameters\"\"\"\n        class CustomParamNode(Node):\n            def exec(self, prep_result):\n                key = self.params.get('key')\n                multiplier = self.params.get('multiplier', 1)\n                if 'results' not in shared_storage:\n                    shared_storage['results'] = {}\n                shared_storage['results'][key] = shared_storage['input_data'][key] * multiplier\n\n        class CustomParamBatchFlow(BatchFlow):\n            def prep(self, shared_storage):\n                return [{\n                    'key': k,\n                    'multiplier': i + 1\n                } for i, k in enumerate(shared_storage['input_data'].keys())]\n\n        shared_storage = {\n            'input_data': {\n                'a': 1,\n                'b': 2,\n                'c': 3\n            }\n        }\n\n        flow = CustomParamBatchFlow(start=CustomParamNode())\n        flow.run(shared_storage)\n\n        expected_results = {\n            'a': 1 * 1,  # first item, multiplier = 1\n            'b': 2 * 2,  # second item, multiplier = 2\n            'c': 3 * 3   # third item, multiplier = 3\n        }\n        self.assertEqual(shared_storage['results'], expected_results)\n\n    def test_nested_batch_flow(self):\n        \"\"\"Test BatchFlow nested inside another BatchFlow (outer iterates groups, inner iterates items)\"\"\"\n        class ItemNode(Node):\n            def prep(self, shared_storage):\n                return shared_storage['groups'][self.params['group']][self.params['item']]\n            def exec(self, prep_res):\n                return prep_res * 2\n            def post(self, shared_storage, prep_res, exec_res):\n                group = self.params['group']\n                if 'results' not in shared_storage:\n                    shared_storage['results'] = {}\n                if group not in shared_storage['results']:\n                    shared_storage['results'][group] = []\n                shared_storage['results'][group].append(exec_res)\n\n        class InnerBatchFlow(BatchFlow):\n            def prep(self, shared_storage):\n                group = self.params['group']\n                return [{'item': i, 'group': group} for i in range(len(shared_storage['groups'][group]))]\n\n        class OuterBatchFlow(BatchFlow):\n            def prep(self, shared_storage):\n                return [{'group': g} for g in shared_storage['groups']]\n\n        item_node = ItemNode()\n        inner_flow = InnerBatchFlow(start=item_node)\n        outer_flow = OuterBatchFlow(start=inner_flow)\n\n        shared_storage = {\n            'groups': {\n                'A': [1, 2],\n                'B': [3, 4],\n            }\n        }\n\n        outer_flow.run(shared_storage)\n\n        expected = {\n            'A': [2, 4],\n            'B': [6, 8],\n        }\n        self.assertEqual(shared_storage['results'], expected)\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "tests/test_batch_node.py",
    "content": "import unittest\nimport sys\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent.parent))\nfrom pocketflow import Node, BatchNode, Flow\n\nclass ArrayChunkNode(BatchNode):\n    def __init__(self, chunk_size=10):\n        super().__init__()\n        self.chunk_size = chunk_size\n    \n    def prep(self, shared_storage):\n        # Get array from shared storage and split into chunks\n        array = shared_storage.get('input_array', [])\n        chunks = []\n        for start in range(0, len(array), self.chunk_size):\n            end = min(start + self.chunk_size, len(array))\n            chunks.append(array[start: end])\n        return chunks\n    \n    def exec(self, chunk):\n        # Process the chunk and return its sum\n        chunk_sum = sum(chunk)\n        return chunk_sum\n        \n    def post(self, shared_storage, prep_result, proc_result):\n        # Store chunk results in shared storage\n        shared_storage['chunk_results'] = proc_result\n        return \"default\"\n\nclass SumReduceNode(Node):\n    def prep(self, shared_storage):\n        # Get chunk results from shared storage and sum them\n        chunk_results = shared_storage.get('chunk_results', [])\n        total = sum(chunk_results)\n        shared_storage['total'] = total\n\nclass TestBatchNode(unittest.TestCase):\n    def test_array_chunking(self):\n        \"\"\"\n        Test that the array is correctly split into chunks\n        \"\"\"\n        shared_storage = {\n            'input_array': list(range(25))  # [0,1,2,...,24]\n        }\n        \n        chunk_node = ArrayChunkNode(chunk_size=10)\n        chunk_node.run(shared_storage)\n        results = shared_storage['chunk_results']\n        self.assertEqual(results, [45, 145, 110])\n        \n    def test_map_reduce_sum(self):\n        \"\"\"\n        Test a complete map-reduce pipeline that sums a large array:\n        1. Map: Split array into chunks and sum each chunk\n        2. Reduce: Sum all the chunk sums\n        \"\"\"\n        # Create test array: [0,1,2,...,99]\n        array = list(range(100))\n        expected_sum = sum(array)  # 4950\n        \n        shared_storage = {\n            'input_array': array\n        }\n        \n        # Create nodes\n        chunk_node = ArrayChunkNode(chunk_size=10)\n        reduce_node = SumReduceNode()\n        \n        # Connect nodes\n        chunk_node >> reduce_node\n        \n        # Create and run pipeline\n        pipeline = Flow(start=chunk_node)\n        pipeline.run(shared_storage)\n        \n        self.assertEqual(shared_storage['total'], expected_sum)\n        \n    def test_uneven_chunks(self):\n        \"\"\"\n        Test that the map-reduce works correctly with array lengths\n        that don't divide evenly by chunk_size\n        \"\"\"\n        array = list(range(25))\n        expected_sum = sum(array)  # 300\n        \n        shared_storage = {\n            'input_array': array\n        }\n        \n        chunk_node = ArrayChunkNode(chunk_size=10)\n        reduce_node = SumReduceNode()\n        \n        chunk_node >> reduce_node\n        pipeline = Flow(start=chunk_node)\n        pipeline.run(shared_storage)\n        \n        self.assertEqual(shared_storage['total'], expected_sum)\n\n    def test_custom_chunk_size(self):\n        \"\"\"\n        Test that the map-reduce works with different chunk sizes\n        \"\"\"\n        array = list(range(100))\n        expected_sum = sum(array)\n        \n        shared_storage = {\n            'input_array': array\n        }\n        \n        # Use chunk_size=15 instead of default 10\n        chunk_node = ArrayChunkNode(chunk_size=15)\n        reduce_node = SumReduceNode()\n        \n        chunk_node >> reduce_node\n        pipeline = Flow(start=chunk_node)\n        pipeline.run(shared_storage)\n        \n        self.assertEqual(shared_storage['total'], expected_sum)\n        \n    def test_single_element_chunks(self):\n        \"\"\"\n        Test extreme case where chunk_size=1\n        \"\"\"\n        array = list(range(5))\n        expected_sum = sum(array)\n        \n        shared_storage = {\n            'input_array': array\n        }\n        \n        chunk_node = ArrayChunkNode(chunk_size=1)\n        reduce_node = SumReduceNode()\n        \n        chunk_node >> reduce_node\n        pipeline = Flow(start=chunk_node)\n        pipeline.run(shared_storage)\n        \n        self.assertEqual(shared_storage['total'], expected_sum)\n\n    def test_empty_array(self):\n        \"\"\"\n        Test edge case of empty input array\n        \"\"\"\n        shared_storage = {\n            'input_array': []\n        }\n        \n        chunk_node = ArrayChunkNode(chunk_size=10)\n        reduce_node = SumReduceNode()\n        \n        chunk_node >> reduce_node\n        pipeline = Flow(start=chunk_node)\n        pipeline.run(shared_storage)\n        \n        self.assertEqual(shared_storage['total'], 0)\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "tests/test_fall_back.py",
    "content": "import unittest\nimport asyncio\nimport sys\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent.parent))\nfrom pocketflow import Node, AsyncNode, Flow, AsyncFlow\n\nclass FallbackNode(Node):\n    def __init__(self, should_fail=True, max_retries=1):\n        super().__init__(max_retries=max_retries)\n        self.should_fail = should_fail\n        self.attempt_count = 0\n    \n    def prep(self, shared_storage):\n        if 'results' not in shared_storage:\n            shared_storage['results'] = []\n        return None\n    \n    def exec(self, prep_result):\n        self.attempt_count += 1\n        if self.should_fail:\n            raise ValueError(\"Intentional failure\")\n        return \"success\"\n    \n    def exec_fallback(self, prep_result, exc):\n        return \"fallback\"\n    \n    def post(self, shared_storage, prep_result, exec_result):\n        shared_storage['results'].append({\n            'attempts': self.attempt_count,\n            'result': exec_result\n        })\n\nclass AsyncFallbackNode(AsyncNode):\n    def __init__(self, should_fail=True, max_retries=1):\n        super().__init__(max_retries=max_retries)\n        self.should_fail = should_fail\n        self.attempt_count = 0\n    \n    async def prep_async(self, shared_storage):\n        if 'results' not in shared_storage:\n            shared_storage['results'] = []\n        return None\n    \n    async def exec_async(self, prep_result):\n        self.attempt_count += 1\n        if self.should_fail:\n            raise ValueError(\"Intentional async failure\")\n        return \"success\"\n    \n    async def exec_fallback_async(self, prep_result, exc):\n        await asyncio.sleep(0.01)  # Simulate async work\n        return \"async_fallback\"\n    \n    async def post_async(self, shared_storage, prep_result, exec_result):\n        shared_storage['results'].append({\n            'attempts': self.attempt_count,\n            'result': exec_result\n        })\n\nclass TestExecFallback(unittest.TestCase):\n    def test_successful_execution(self):\n        \"\"\"Test that exec_fallback is not called when execution succeeds\"\"\"\n        shared_storage = {}\n        node = FallbackNode(should_fail=False)\n        result = node.run(shared_storage)\n        \n        self.assertEqual(len(shared_storage['results']), 1)\n        self.assertEqual(shared_storage['results'][0]['attempts'], 1)\n        self.assertEqual(shared_storage['results'][0]['result'], \"success\")\n\n    def test_fallback_after_failure(self):\n        \"\"\"Test that exec_fallback is called after all retries are exhausted\"\"\"\n        shared_storage = {}\n        node = FallbackNode(should_fail=True, max_retries=2)\n        result = node.run(shared_storage)\n        \n        self.assertEqual(len(shared_storage['results']), 1)\n        self.assertEqual(shared_storage['results'][0]['attempts'], 2)\n        self.assertEqual(shared_storage['results'][0]['result'], \"fallback\")\n\n    def test_fallback_in_flow(self):\n        \"\"\"Test that fallback works within a Flow\"\"\"\n        class ResultNode(Node):\n            def prep(self, shared_storage):\n                return shared_storage.get('results', [])\n                \n            def exec(self, prep_result):\n                return prep_result\n                \n            def post(self, shared_storage, prep_result, exec_result):\n                shared_storage['final_result'] = exec_result\n                return None\n        \n        shared_storage = {}\n        fallback_node = FallbackNode(should_fail=True)\n        result_node = ResultNode()\n        fallback_node >> result_node\n        \n        flow = Flow(start=fallback_node)\n        flow.run(shared_storage)\n        \n        self.assertEqual(len(shared_storage['results']), 1)\n        self.assertEqual(shared_storage['results'][0]['result'], \"fallback\")\n        self.assertEqual(shared_storage['final_result'], [{'attempts': 1, 'result': 'fallback'}] )\n\n    def test_no_fallback_implementation(self):\n        \"\"\"Test that default fallback behavior raises the exception\"\"\"\n        class NoFallbackNode(Node):\n            def prep(self, shared_storage):\n                if 'results' not in shared_storage:\n                    shared_storage['results'] = []\n                return None\n            \n            def exec(self, prep_result):\n                raise ValueError(\"Test error\")\n            \n            def post(self, shared_storage, prep_result, exec_result):\n                shared_storage['results'].append({'result': exec_result})\n                return exec_result\n        \n        shared_storage = {}\n        node = NoFallbackNode()\n        with self.assertRaises(ValueError):\n            node.run(shared_storage)\n\n    def test_retry_before_fallback(self):\n        \"\"\"Test that retries are attempted before calling fallback\"\"\"\n        shared_storage = {}\n        node = FallbackNode(should_fail=True, max_retries=3)\n        node.run(shared_storage)\n        \n        self.assertEqual(len(shared_storage['results']), 1)\n        self.assertEqual(shared_storage['results'][0]['attempts'], 3)\n        self.assertEqual(shared_storage['results'][0]['result'], \"fallback\")\n\nclass TestAsyncExecFallback(unittest.TestCase):\n    def setUp(self):\n        self.loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(self.loop)\n    \n    def tearDown(self):\n        self.loop.close()\n\n    def test_async_successful_execution(self):\n        \"\"\"Test that async exec_fallback is not called when execution succeeds\"\"\"\n        async def run_test():\n            shared_storage = {}\n            node = AsyncFallbackNode(should_fail=False)\n            await node.run_async(shared_storage)\n            return shared_storage\n        \n        shared_storage = self.loop.run_until_complete(run_test())\n        self.assertEqual(len(shared_storage['results']), 1)\n        self.assertEqual(shared_storage['results'][0]['attempts'], 1)\n        self.assertEqual(shared_storage['results'][0]['result'], \"success\")\n\n    def test_async_fallback_after_failure(self):\n        \"\"\"Test that async exec_fallback is called after all retries are exhausted\"\"\"\n        async def run_test():\n            shared_storage = {}\n            node = AsyncFallbackNode(should_fail=True, max_retries=2)\n            await node.run_async(shared_storage)\n            return shared_storage\n        \n        shared_storage = self.loop.run_until_complete(run_test())\n        \n        self.assertEqual(len(shared_storage['results']), 1)\n        self.assertEqual(shared_storage['results'][0]['attempts'], 2)\n        self.assertEqual(shared_storage['results'][0]['result'], \"async_fallback\")\n\n    def test_async_fallback_in_flow(self):\n        \"\"\"Test that async fallback works within an AsyncFlow\"\"\"\n        class AsyncResultNode(AsyncNode):\n            async def prep_async(self, shared_storage):\n                return shared_storage['results'][-1]['result']  # Get last result\n                \n            async def exec_async(self, prep_result):\n                return prep_result\n                \n            async def post_async(self, shared_storage, prep_result, exec_result):\n                shared_storage['final_result'] = exec_result\n                return \"done\"\n        \n        async def run_test():\n            shared_storage = {}\n            fallback_node = AsyncFallbackNode(should_fail=True)\n            result_node = AsyncResultNode()\n            fallback_node >> result_node\n            \n            flow = AsyncFlow(start=fallback_node)\n            await flow.run_async(shared_storage)\n            return shared_storage\n        \n        shared_storage = self.loop.run_until_complete(run_test())\n        self.assertEqual(len(shared_storage['results']), 1)\n        self.assertEqual(shared_storage['results'][0]['result'], \"async_fallback\")\n        self.assertEqual(shared_storage['final_result'], \"async_fallback\")\n\n    def test_async_no_fallback_implementation(self):\n        \"\"\"Test that default async fallback behavior raises the exception\"\"\"\n        class NoFallbackAsyncNode(AsyncNode):\n            async def prep_async(self, shared_storage):\n                if 'results' not in shared_storage:\n                    shared_storage['results'] = []\n                return None\n            \n            async def exec_async(self, prep_result):\n                raise ValueError(\"Test async error\")\n            \n            async def post_async(self, shared_storage, prep_result, exec_result):\n                shared_storage['results'].append({'result': exec_result})\n                return exec_result\n        \n        async def run_test():\n            shared_storage = {}\n            node = NoFallbackAsyncNode()\n            await node.run_async(shared_storage)\n        \n        with self.assertRaises(ValueError):\n            self.loop.run_until_complete(run_test())\n\n    def test_async_retry_before_fallback(self):\n        \"\"\"Test that retries are attempted before calling async fallback\"\"\"\n        async def run_test():\n            shared_storage = {}\n            node = AsyncFallbackNode(should_fail=True, max_retries=3)\n            result = await node.run_async(shared_storage)\n            return result, shared_storage\n        \n        result, shared_storage = self.loop.run_until_complete(run_test())\n        self.assertEqual(len(shared_storage['results']), 1)\n        self.assertEqual(shared_storage['results'][0]['attempts'], 3)\n        self.assertEqual(shared_storage['results'][0]['result'], \"async_fallback\")\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "tests/test_flow_basic.py",
    "content": "# tests/test_flow_basic.py\nimport unittest\nimport sys\nfrom pathlib import Path\nimport warnings\n\nsys.path.insert(0, str(Path(__file__).parent.parent))\nfrom pocketflow import Node, Flow\n\n# --- Node Definitions ---\n# Nodes intended for default transitions (>>) should NOT return a specific\n# action string from post. Let it return None by default.\n# Nodes intended for conditional transitions (-) MUST return the action string.\n\nclass NumberNode(Node):\n    def __init__(self, number):\n        super().__init__()\n        self.number = number\n    def prep(self, shared_storage):\n        shared_storage['current'] = self.number\n    # post implicitly returns None - used for default transition\n\nclass AddNode(Node):\n    def __init__(self, number):\n        super().__init__()\n        self.number = number\n    def prep(self, shared_storage):\n        shared_storage['current'] += self.number\n    # post implicitly returns None - used for default transition\n\nclass MultiplyNode(Node):\n    def __init__(self, number):\n        super().__init__()\n        self.number = number\n    def prep(self, shared_storage):\n        shared_storage['current'] *= self.number\n    # post implicitly returns None - used for default transition\n\nclass CheckPositiveNode(Node):\n   # This node IS designed for conditional branching\n   def prep(self, shared_storage):\n       pass\n   def post(self, shared_storage, prep_result, proc_result):\n        # MUST return the specific action string for branching\n        if shared_storage['current'] >= 0:\n            return 'positive'\n        else:\n            return 'negative'\n\nclass NoOpNode(Node):\n    # Just a placeholder node\n    pass # post implicitly returns None\n\nclass EndSignalNode(Node):\n    # A node specifically to return a value when it's the end\n    def __init__(self, signal=\"finished\"):\n        super().__init__()\n        self.signal = signal\n    def post(self, shared_storage, prep_result, exec_result):\n        return self.signal # Return a specific signal\n\n# --- Test Class ---\nclass TestFlowBasic(unittest.TestCase):\n\n    def test_start_method_initialization(self):\n        \"\"\"Test initializing flow with start() after creation.\"\"\"\n        shared_storage = {}\n        n1 = NumberNode(5)\n        pipeline = Flow()\n        pipeline.start(n1)\n        last_action = pipeline.run(shared_storage)\n        self.assertEqual(shared_storage['current'], 5)\n        # NumberNode.post returns None (default)\n        self.assertIsNone(last_action)\n\n    def test_start_method_chaining(self):\n        \"\"\"Test fluent chaining using start().next()...\"\"\"\n        shared_storage = {}\n        pipeline = Flow()\n        # Chain: NumberNode -> AddNode -> MultiplyNode\n        # All use default transitions (post returns None)\n        pipeline.start(NumberNode(5)).next(AddNode(3)).next(MultiplyNode(2))\n        last_action = pipeline.run(shared_storage)\n        self.assertEqual(shared_storage['current'], 16)\n        # Last node (MultiplyNode) post returns None\n        self.assertIsNone(last_action)\n\n    def test_sequence_with_rshift(self):\n        \"\"\"Test a simple linear pipeline using >>\"\"\"\n        shared_storage = {}\n        n1 = NumberNode(5)\n        n2 = AddNode(3)\n        n3 = MultiplyNode(2)\n\n        pipeline = Flow()\n        # All default transitions (post returns None)\n        pipeline.start(n1) >> n2 >> n3\n\n        last_action = pipeline.run(shared_storage)\n        self.assertEqual(shared_storage['current'], 16)\n        # Last node (n3: MultiplyNode) post returns None\n        self.assertIsNone(last_action)\n\n    def test_branching_positive(self):\n        \"\"\"Test positive branch: CheckPositiveNode returns 'positive'\"\"\"\n        shared_storage = {}\n        start_node = NumberNode(5)    # post -> None\n        check_node = CheckPositiveNode() # post -> 'positive' or 'negative'\n        add_if_positive = AddNode(10) # post -> None\n        add_if_negative = AddNode(-20) # post -> None (won't run)\n\n        pipeline = Flow()\n        # start -> check (default); check branches on 'positive'/'negative'\n        pipeline.start(start_node) >> check_node\n        check_node - \"positive\" >> add_if_positive\n        check_node - \"negative\" >> add_if_negative\n\n        # Execution: start_node -> check_node -> add_if_positive\n        last_action = pipeline.run(shared_storage)\n        self.assertEqual(shared_storage['current'], 15) # 5 + 10\n        # Last node executed was add_if_positive, its post returns None\n        self.assertIsNone(last_action)\n\n    def test_branching_negative(self):\n        \"\"\"Test negative branch: CheckPositiveNode returns 'negative'\"\"\"\n        shared_storage = {}\n        start_node = NumberNode(-5)   # post -> None\n        check_node = CheckPositiveNode() # post -> 'positive' or 'negative'\n        add_if_positive = AddNode(10) # post -> None (won't run)\n        add_if_negative = AddNode(-20) # post -> None\n\n        pipeline = Flow()\n        pipeline.start(start_node) >> check_node\n        check_node - \"positive\" >> add_if_positive\n        check_node - \"negative\" >> add_if_negative\n\n        # Execution: start_node -> check_node -> add_if_negative\n        last_action = pipeline.run(shared_storage)\n        self.assertEqual(shared_storage['current'], -25) # -5 + -20\n        # Last node executed was add_if_negative, its post returns None\n        self.assertIsNone(last_action)\n\n    def test_cycle_until_negative_ends_with_signal(self):\n        \"\"\"Test cycle, ending on a node that returns a signal\"\"\"\n        shared_storage = {}\n        n1 = NumberNode(10)           # post -> None\n        check = CheckPositiveNode()   # post -> 'positive' or 'negative'\n        subtract3 = AddNode(-3)       # post -> None\n        end_node = EndSignalNode(\"cycle_done\") # post -> \"cycle_done\"\n\n        pipeline = Flow()\n        pipeline.start(n1) >> check\n        # Branching from CheckPositiveNode\n        check - 'positive' >> subtract3\n        check - 'negative' >> end_node # End on negative branch\n        # After subtracting, go back to check (default transition)\n        subtract3 >> check\n\n        # Execution: n1->check->sub3->check->sub3->check->sub3->check->sub3->check->end_node\n        last_action = pipeline.run(shared_storage)\n        self.assertEqual(shared_storage['current'], -2) # 10 -> 7 -> 4 -> 1 -> -2\n        # Last node executed was end_node, its post returns \"cycle_done\"\n        self.assertEqual(last_action, \"cycle_done\")\n\n    def test_flow_ends_warning_default_missing(self):\n        \"\"\"Test warning when default transition is needed but not found\"\"\"\n        shared_storage = {}\n        # Node that returns a specific action from post\n        class ActionNode(Node):\n            def post(self, *args): return \"specific_action\"\n        start_node = ActionNode()\n        next_node = NoOpNode()\n\n        pipeline = Flow()\n        pipeline.start(start_node)\n        # Define successor only for the specific action\n        start_node - \"specific_action\" >> next_node\n\n        # Make start_node return None instead, triggering default search\n        start_node.post = lambda *args: None\n\n        with warnings.catch_warnings(record=True) as w:\n            warnings.simplefilter(\"always\")\n            # Run flow. start_node runs, post returns None.\n            # Flow looks for \"default\", but only \"specific_action\" exists.\n            last_action = pipeline.run(shared_storage)\n\n            self.assertEqual(len(w), 1)\n            self.assertTrue(issubclass(w[-1].category, UserWarning))\n            # Warning message should indicate \"default\" wasn't found\n            self.assertIn(\"Flow ends: 'None' not found in ['specific_action']\", str(w[-1].message))\n        # Last action is from start_node's post\n        self.assertIsNone(last_action)\n\n    def test_flow_ends_warning_specific_missing(self):\n        \"\"\"Test warning when specific action is returned but not found\"\"\"\n        shared_storage = {}\n        # Node that returns a specific action from post\n        class ActionNode(Node):\n            def post(self, *args): return \"specific_action\"\n        start_node = ActionNode()\n        next_node = NoOpNode()\n\n        pipeline = Flow()\n        pipeline.start(start_node)\n        # Define successor only for \"default\"\n        start_node >> next_node # same as start_node.next(next_node, \"default\")\n\n        with warnings.catch_warnings(record=True) as w:\n            warnings.simplefilter(\"always\")\n            # Run flow. start_node runs, post returns \"specific_action\".\n            # Flow looks for \"specific_action\", but only \"default\" exists.\n            last_action = pipeline.run(shared_storage)\n\n            self.assertEqual(len(w), 1)\n            self.assertTrue(issubclass(w[-1].category, UserWarning))\n            # Warning message should indicate \"specific_action\" wasn't found\n            self.assertIn(\"Flow ends: 'specific_action' not found in ['default']\", str(w[-1].message))\n        # Last action is from start_node's post\n        self.assertEqual(last_action, \"specific_action\")\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "tests/test_flow_composition.py",
    "content": "# tests/test_flow_composition.py\nimport unittest\nimport asyncio # Keep import, might be needed if other tests use it indirectly\nimport sys\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent.parent))\nfrom pocketflow import Node, Flow\n\n# --- Existing Nodes ---\nclass NumberNode(Node):\n    def __init__(self, number):\n        super().__init__()\n        self.number = number\n    def prep(self, shared_storage):\n        shared_storage['current'] = self.number\n    # post implicitly returns None\n\nclass AddNode(Node):\n    def __init__(self, number):\n        super().__init__()\n        self.number = number\n    def prep(self, shared_storage):\n        shared_storage['current'] += self.number\n    # post implicitly returns None\n\nclass MultiplyNode(Node):\n    def __init__(self, number):\n        super().__init__()\n        self.number = number\n    def prep(self, shared_storage):\n        shared_storage['current'] *= self.number\n    # post implicitly returns None\n\n# --- New Nodes for Action Propagation Test ---\nclass SignalNode(Node):\n    \"\"\"A node that returns a specific signal string from its post method.\"\"\"\n    def __init__(self, signal=\"default_signal\"):\n        super().__init__()\n        self.signal = signal\n    # No prep needed usually if just signaling\n    def post(self, shared_storage, prep_result, exec_result):\n        # Store the signal in shared storage for verification\n        shared_storage['last_signal_emitted'] = self.signal\n        return self.signal # Return the specific action string\n\nclass PathNode(Node):\n    \"\"\"A node to indicate which path was taken in the outer flow.\"\"\"\n    def __init__(self, path_id):\n        super().__init__()\n        self.path_id = path_id\n    def prep(self, shared_storage):\n        shared_storage['path_taken'] = self.path_id\n    # post implicitly returns None\n\n# --- Test Class ---\nclass TestFlowComposition(unittest.TestCase):\n\n    # --- Existing Tests (Unchanged) ---\n    def test_flow_as_node(self):\n        \"\"\"\n        1) Create a Flow (f1) starting with NumberNode(5), then AddNode(10), then MultiplyNode(2).\n        2) Create a second Flow (f2) whose start is f1.\n        3) Create a wrapper Flow (f3) that contains f2 to ensure proper execution.\n        Expected final result in shared_storage['current']: (5 + 10) * 2 = 30.\n        \"\"\"\n        shared_storage = {}\n        f1 = Flow(start=NumberNode(5))\n        f1 >> AddNode(10) >> MultiplyNode(2)\n        f2 = Flow(start=f1)\n        f3 = Flow(start=f2)\n        f3.run(shared_storage)\n        self.assertEqual(shared_storage['current'], 30)\n\n    def test_nested_flow(self):\n        \"\"\"\n        Demonstrates nested flows with proper wrapping:\n        inner_flow: NumberNode(5) -> AddNode(3)\n        middle_flow: starts with inner_flow -> MultiplyNode(4)\n        wrapper_flow: contains middle_flow to ensure proper execution\n        Expected final result: (5 + 3) * 4 = 32.\n        \"\"\"\n        shared_storage = {}\n        inner_flow = Flow(start=NumberNode(5))\n        inner_flow >> AddNode(3)\n        middle_flow = Flow(start=inner_flow)\n        middle_flow >> MultiplyNode(4)\n        wrapper_flow = Flow(start=middle_flow)\n        wrapper_flow.run(shared_storage)\n        self.assertEqual(shared_storage['current'], 32)\n\n    def test_flow_chaining_flows(self):\n        \"\"\"\n        Demonstrates chaining two flows with proper wrapping:\n        flow1: NumberNode(10) -> AddNode(10) # final = 20\n        flow2: MultiplyNode(2) # final = 40\n        wrapper_flow: contains both flow1 and flow2 to ensure proper execution\n        Expected final result: (10 + 10) * 2 = 40.\n        \"\"\"\n        shared_storage = {}\n        numbernode = NumberNode(10)\n        numbernode >> AddNode(10)\n        flow1 = Flow(start=numbernode)\n        flow2 = Flow(start=MultiplyNode(2))\n        flow1 >> flow2 # Default transition based on flow1 returning None\n        wrapper_flow = Flow(start=flow1)\n        wrapper_flow.run(shared_storage)\n        self.assertEqual(shared_storage['current'], 40)\n\n    def test_composition_with_action_propagation(self):\n        \"\"\"\n        Test that an outer flow can branch based on the action returned\n        by the last node's post() within an inner flow.\n        \"\"\"\n        shared_storage = {}\n\n        # 1. Define an inner flow that ends with a node returning a specific action\n        inner_start_node = NumberNode(100)       # current = 100, post -> None\n        inner_end_node = SignalNode(\"inner_done\") # post -> \"inner_done\"\n        inner_start_node >> inner_end_node\n        # Inner flow will execute start->end, and the Flow's execution will return \"inner_done\"\n        inner_flow = Flow(start=inner_start_node)\n\n        # 2. Define target nodes for the outer flow branches\n        path_a_node = PathNode(\"A\") # post -> None\n        path_b_node = PathNode(\"B\") # post -> None\n\n        # 3. Define the outer flow starting with the inner flow\n        outer_flow = Flow()\n        outer_flow.start(inner_flow) # Use the start() method\n\n        # 4. Define branches FROM the inner_flow object based on its returned action\n        inner_flow - \"inner_done\" >> path_b_node  # This path should be taken\n        inner_flow - \"other_action\" >> path_a_node # This path should NOT be taken\n\n        # 5. Run the outer flow and capture the last action\n        # Execution: inner_start -> inner_end -> path_b\n        last_action_outer = outer_flow.run(shared_storage)\n\n        # 6. Assert the results\n        # Check state after inner flow execution\n        self.assertEqual(shared_storage.get('current'), 100)\n        self.assertEqual(shared_storage.get('last_signal_emitted'), \"inner_done\")\n        # Check that the correct outer path was taken\n        self.assertEqual(shared_storage.get('path_taken'), \"B\")\n        # Check the action returned by the outer flow. The last node executed was\n        # path_b_node, which returns None from its post method.\n        self.assertIsNone(last_action_outer)\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "utils/update_pocketflow_mdc.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nScript to generate MDC files from the PocketFlow docs folder, creating one MDC file per MD file.\n\nUsage:\n    python update_pocketflow_mdc.py [--docs-dir PATH] [--rules-dir PATH]\n\"\"\"\n\nimport os\nimport re\nimport shutil\nfrom pathlib import Path\nimport sys\nimport html.parser\n\nclass HTMLTagStripper(html.parser.HTMLParser):\n    \"\"\"HTML Parser subclass to strip HTML tags from content\"\"\"\n    def __init__(self):\n        super().__init__()\n        self.reset()\n        self.strict = False\n        self.convert_charrefs = True\n        self.text = []\n    \n    def handle_data(self, data):\n        self.text.append(data)\n    \n    def get_text(self):\n        return ''.join(self.text)\n\ndef strip_html_tags(html_content):\n    \"\"\"Remove HTML tags from content\"\"\"\n    stripper = HTMLTagStripper()\n    stripper.feed(html_content)\n    return stripper.get_text()\n\ndef extract_frontmatter(file_path):\n    \"\"\"Extract title, parent, and nav_order from markdown frontmatter\"\"\"\n    frontmatter = {}\n    try:\n        with open(file_path, 'r', encoding='utf-8') as f:\n            content = f.read()\n            \n            # Extract frontmatter between --- markers\n            fm_match = re.search(r'^---\\s*(.+?)\\s*---', content, re.DOTALL)\n            if fm_match:\n                frontmatter_text = fm_match.group(1)\n                \n                # Extract fields\n                title_match = re.search(r'title:\\s*\"?([^\"\\n]+)\"?', frontmatter_text)\n                parent_match = re.search(r'parent:\\s*\"?([^\"\\n]+)\"?', frontmatter_text)\n                nav_order_match = re.search(r'nav_order:\\s*(\\d+)', frontmatter_text)\n                \n                if title_match:\n                    frontmatter['title'] = title_match.group(1)\n                if parent_match:\n                    frontmatter['parent'] = parent_match.group(1)\n                if nav_order_match:\n                    frontmatter['nav_order'] = int(nav_order_match.group(1))\n    except Exception as e:\n        print(f\"Error reading frontmatter from {file_path}: {e}\")\n    \n    return frontmatter\n\ndef extract_first_heading(file_path):\n    \"\"\"Extract the first heading from markdown content\"\"\"\n    try:\n        with open(file_path, 'r', encoding='utf-8') as f:\n            content = f.read()\n            \n            # Remove frontmatter\n            content = re.sub(r'^---.*?---\\s*', '', content, flags=re.DOTALL)\n            \n            # Find first heading\n            heading_match = re.search(r'#\\s+(.+)', content)\n            if heading_match:\n                return heading_match.group(1).strip()\n    except Exception as e:\n        print(f\"Error extracting heading from {file_path}: {e}\")\n    \n    # Fallback to filename if no heading found\n    return Path(file_path).stem.replace('_', ' ').title()\n\ndef get_mdc_description(md_file, frontmatter, heading):\n    \"\"\"Generate a description for the MDC file based on file metadata\"\"\"\n    section = \"\"\n    subsection = \"\"\n    \n    # Determine section from path\n    path_parts = Path(md_file).parts\n    if 'core_abstraction' in path_parts:\n        section = \"Core Abstraction\"\n    elif 'design_pattern' in path_parts:\n        section = \"Design Pattern\"\n    elif 'utility_function' in path_parts:\n        section = \"Utility Function\"\n    \n    # Use frontmatter title or heading as subsection\n    if 'title' in frontmatter:\n        subsection = frontmatter['title']\n    else:\n        subsection = heading\n    \n    # For the combined guide and index\n    if Path(md_file).name == \"guide.md\":\n        return \"Guidelines for using PocketFlow, Agentic Coding\"\n    \n    # For index.md at root level, use a different format\n    if Path(md_file).name == \"index.md\" and section == \"\":\n        return \"Guidelines for using PocketFlow, a minimalist LLM framework\"\n    \n    # For other files, create a more specific description\n    if section:\n        return f\"Guidelines for using PocketFlow, {section}, {subsection}\"\n    else:\n        return f\"Guidelines for using PocketFlow, {subsection}\"\n\ndef process_markdown_content(content, remove_local_refs=False):\n    \"\"\"Process markdown content to make it suitable for MDC file\"\"\"\n    # Remove frontmatter\n    content = re.sub(r'^---.*?---\\s*', '', content, flags=re.DOTALL)\n    \n    # Replace HTML div tags and their content\n    content = re.sub(r'<div.*?>.*?</div>', '', content, flags=re.DOTALL)\n    \n    if remove_local_refs:\n        # Replace markdown links to local documentation with just the text in brackets\n        # This prevents automatically including all docs when the file is loaded\n        # Keep the brackets around the text for better discoverability\n        content = re.sub(r'\\[([^\\]]+)\\]\\(\\./[^)]+\\)', r'[\\1]', content)\n    else:\n        # Adjust relative links to maintain references within the docs structure\n        content = re.sub(r'\\]\\(\\./([^)]+)\\)', r'](mdc:./\\1)', content)\n        \n        # Ensure links to md files work correctly\n        content = re.sub(r'\\]\\(mdc:\\./(.+?)\\.md\\)', r'](mdc:./\\1.md)', content)\n        content = re.sub(r'\\]\\(mdc:\\./(.+?)\\.html\\)', r'](mdc:./\\1.md)', content)\n    \n    # Strip remaining HTML tags\n    content = strip_html_tags(content)\n    \n    return content\n\ndef get_documentation_first_policy():\n    \"\"\"Return the DOCUMENTATION FIRST POLICY text to be included in the guide\"\"\"\n    return \"\"\"# DOCUMENTATION FIRST POLICY\n\n**CRITICAL INSTRUCTION**: When implementing a Pocket Flow app:\n\n1. **ALWAYS REQUEST MDC FILES FIRST** - Before writing any code, request and review all relevant MDC documentation files. This doc provides an explaination of the documents.\n2. **UNDERSTAND THE FRAMEWORK** - Gain comprehensive understanding of the Pocket Flow framework from documentation\n3. **AVOID ASSUMPTION-DRIVEN DEVELOPMENT** - Do not base your implementation on assumptions or guesswork. Even if the human didn't explicitly mention pocket flow in their request, if the code you are editing is using pocket flow, you should request relevant docs to help you understand best practice as well before editing.\n\n**VERIFICATION**: Begin each implementation with a brief summary of the documentation you've reviewed to inform your approach.\n\n\"\"\"\n\ndef generate_mdc_header(md_file, description, always_apply=False):\n    \"\"\"Generate MDC file header with appropriate frontmatter\"\"\"\n    # Determine if we should include globs\n    # For index.md and guide.md, we include **/*.py to provide high-level context for Python files\n    # For other files, leave it empty to be less intrusive\n    globs = \"**/*.py\" if always_apply else \"\"\n    \n    return f\"\"\"---\ndescription: {description}\nglobs: {globs}\nalwaysApply: {\"true\" if always_apply else \"false\"}\n---\n\"\"\"\n\ndef has_substantive_content(content):\n    \"\"\"Check if the processed content has substantive content beyond the frontmatter\"\"\"\n    # Remove frontmatter\n    content_without_frontmatter = re.sub(r'^---.*?---\\s*', '', content, flags=re.DOTALL)\n    \n    # Remove whitespace and common HTML/markdown formatting\n    cleaned_content = re.sub(r'\\s+', '', content_without_frontmatter)\n    cleaned_content = re.sub(r'{:.*?}', '', cleaned_content)\n    \n    # If there's almost nothing left after cleaning, consider it empty\n    return len(cleaned_content) > 20  # Arbitrary threshold, adjust as needed\n\ndef create_combined_guide(docs_dir, rules_dir):\n    \"\"\"Create a combined guide that includes both the guide and index content\"\"\"\n    docs_path = Path(docs_dir)\n    rules_path = Path(rules_dir)\n    \n    guide_file = docs_path / \"guide.md\"\n    index_file = docs_path / \"index.md\"\n    \n    if not guide_file.exists() or not index_file.exists():\n        print(\"Warning: guide.md or index.md not found, skipping combined guide creation\")\n        return False\n    \n    # Get guide content and index content\n    with open(guide_file, 'r', encoding='utf-8') as f:\n        guide_content = f.read()\n    \n    with open(index_file, 'r', encoding='utf-8') as f:\n        index_content = f.read()\n    \n    # Process the content\n    processed_guide = process_markdown_content(guide_content, remove_local_refs=True)\n    processed_index = process_markdown_content(index_content, remove_local_refs=True)\n    \n    # Get the documentation first policy\n    doc_first_policy = get_documentation_first_policy()\n    \n    # Combine the content with the documentation first policy at the beginning\n    combined_content = doc_first_policy + processed_guide + \"\\n\\n\" + processed_index\n    \n    # Generate the MDC header\n    description = \"Guidelines for using PocketFlow, Agentic Coding\"\n    mdc_header = generate_mdc_header(guide_file, description, always_apply=True)\n    \n    # Combine header and processed content\n    mdc_content = mdc_header + combined_content\n    \n    # Create the output path with the new filename\n    output_path = rules_path / \"guide_for_pocketflow.mdc\"\n    \n    # Write the MDC file\n    with open(output_path, 'w', encoding='utf-8') as f:\n        f.write(mdc_content)\n    \n    print(f\"Created combined guide MDC file: {output_path}\")\n    return True\n\ndef convert_md_to_mdc(md_file, output_dir, docs_dir, special_treatment=False):\n    \"\"\"Convert a markdown file to MDC format and save to the output directory\"\"\"\n    try:\n        print(f\"Processing: {md_file}\")\n        \n        # Skip guide.md and index.md as they'll be handled separately\n        file_name = Path(md_file).name\n        if file_name in [\"guide.md\", \"index.md\"]:\n            print(f\"Skipping {file_name} for individual processing - it will be included in the combined guide\")\n            return True\n        \n        # Skip empty index.md files in subfolders\n        parent_dir = Path(md_file).parent.name\n        \n        # Check if this is an index.md in a subfolder (not the main index.md)\n        if (file_name == \"index.md\" and parent_dir != \"docs\" and \n            parent_dir in [\"core_abstraction\", \"design_pattern\", \"utility_function\"]):\n            \n            # Read the content\n            with open(md_file, 'r', encoding='utf-8') as f:\n                content = f.read()\n                \n            # Skip if it doesn't have substantive content\n            if not has_substantive_content(content):\n                print(f\"Skipping empty subfolder index: {md_file}\")\n                return True\n        \n        # Extract metadata from file\n        frontmatter = extract_frontmatter(md_file)\n        heading = extract_first_heading(md_file)\n        description = get_mdc_description(md_file, frontmatter, heading)\n        \n        # Read the content\n        with open(md_file, 'r', encoding='utf-8') as f:\n            content = f.read()\n        \n        # Process the content\n        processed_content = process_markdown_content(content, remove_local_refs=special_treatment)\n        \n        # Generate the MDC header\n        mdc_header = generate_mdc_header(md_file, description, always_apply=special_treatment)\n        \n        # Combine header and processed content\n        mdc_content = mdc_header + processed_content\n        \n        # Perform a final check to ensure the processed content is substantive\n        if not has_substantive_content(processed_content):\n            print(f\"Skipping file with no substantive content after processing: {md_file}\")\n            return True\n        \n        # Get the path relative to the docs directory\n        rel_path = os.path.relpath(md_file, start=Path(docs_dir))\n        \n        # Extract just the filename and directory structure without the 'docs/' prefix\n        path_parts = Path(rel_path).parts\n        if len(path_parts) > 1 and path_parts[0] == 'docs':\n            # Remove the 'docs/' prefix from the path\n            rel_path = os.path.join(*path_parts[1:])\n        \n        # Create the output path\n        output_path = Path(output_dir) / rel_path\n        \n        # Create output directory if it doesn't exist\n        output_path.parent.mkdir(parents=True, exist_ok=True)\n        \n        # Change extension from .md to .mdc\n        output_path = output_path.with_suffix('.mdc')\n        \n        # Write the MDC file\n        with open(output_path, 'w', encoding='utf-8') as f:\n            f.write(mdc_content)\n        \n        print(f\"Created MDC file: {output_path}\")\n        return True\n    \n    except Exception as e:\n        print(f\"Error converting {md_file} to MDC: {e}\")\n        return False\n\ndef generate_mdc_files(docs_dir, rules_dir):\n    \"\"\"Generate MDC files from all markdown files in the docs directory\"\"\"\n    docs_path = Path(docs_dir)\n    rules_path = Path(rules_dir)\n    \n    # Make sure the docs directory exists\n    if not docs_path.exists() or not docs_path.is_dir():\n        raise ValueError(f\"Directory not found: {docs_dir}\")\n    \n    print(f\"Generating MDC files from docs in: {docs_dir}\")\n    print(f\"Output will be written to: {rules_dir}\")\n    \n    # Create the rules directory if it doesn't exist\n    rules_path.mkdir(parents=True, exist_ok=True)\n    \n    # Create the combined guide file first (includes both guide.md and index.md)\n    create_combined_guide(docs_dir, rules_dir)\n    \n    # Process all other markdown files\n    success_count = 0\n    failure_count = 0\n    \n    # Find all markdown files\n    md_files = list(docs_path.glob(\"**/*.md\"))\n    \n    # Skip the main index.md and guide.md files as we've already processed them in create_combined_guide\n    md_files = [f for f in md_files if f.name != \"index.md\" and f.name != \"guide.md\"]\n    \n    # Process each markdown file\n    for md_file in md_files:\n        if convert_md_to_mdc(md_file, rules_path, docs_dir):\n            success_count += 1\n        else:\n            failure_count += 1\n    \n    print(f\"\\nProcessed {len(md_files) + 1} markdown files:\")  # +1 for the combined guide\n    print(f\"  - Successfully converted: {success_count + 1}\")  # +1 for the combined guide\n    print(f\"  - Failed conversions: {failure_count}\")\n    \n    return success_count > 0 and failure_count == 0\n\nif __name__ == \"__main__\":\n    import argparse\n    \n    parser = argparse.ArgumentParser(description=\"Generate MDC files from PocketFlow docs\")\n    \n    # Get script directory\n    script_dir = Path(__file__).parent.absolute()\n    \n    # Default to PocketFlow/docs directory relative to script location\n    default_docs_dir = (script_dir.parent / \"docs\").as_posix()\n    \n    # Default rules directory - changed to .cursor/rules\n    default_rules_dir = (script_dir.parent / \".cursor\" / \"rules\").as_posix()\n    \n    parser.add_argument(\"--docs-dir\", \n                        default=default_docs_dir, \n                        help=\"Path to PocketFlow docs directory\")\n    parser.add_argument(\"--rules-dir\", \n                        default=default_rules_dir, \n                        help=\"Output directory for MDC files\")\n    \n    args = parser.parse_args()\n    \n    try:\n        success = generate_mdc_files(args.docs_dir, args.rules_dir)\n        sys.exit(0 if success else 1)\n    except Exception as e:\n        print(f\"Error: {e}\")\n        sys.exit(1) "
  }
]