3.2 KiB
3.2 KiB
Human-in-the-Loop Web Service
1. Requirements
- Goal: Create a web service for task submission, processing, human review (Approve/Reject loop via UI), and finalization.
- Interface: Simple web UI (HTML/JS) for input, status display, and feedback buttons.
- Backend: Flask application using PocketFlow for workflow management.
- 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.
- State: Use in-memory storage for task state (Warning: Not suitable for production).
2. Flow Design
- Core Pattern: Workflow with a conditional loop based on human feedback. SSE for asynchronous status communication.
- Nodes:
ProcessNode(Regular): Takes input, executes the (simulated) task processing.ReviewNode(Async): Waits for human feedback signaled via anasyncio.Event. Pushes "waiting_for_review" status to the SSE queue.ResultNode(Regular): Marks the task as complete and logs the final result.
- Shared Store (
shareddict per task):task_input: Initial data from user.processed_output: Result fromProcessNode.feedback: 'approved' or 'rejected' set by the/feedbackendpoint.review_event:asyncio.Eventused byReviewNodeto wait and/feedbackto signal.final_result: The approved output.current_attempt: Tracks reprocessing count.task_id: Unique identifier for the task.
- SSE Communication: An
asyncio.Queue(stored alongside thesharedstore in the server's globaltasksdict, not directly in PocketFlow's shared store) is used per task. Nodes (or wrapper code) put status updates onto this queue. The/streamendpoint reads from the queue and sends SSE messages. - Mermaid Diagram:
flowchart TD
Process[Process Task] -- "default" --> Review{Wait for Feedback}
Review -- "approved" --> Result[Final Result]
Review -- "rejected" --> Process
3. Utilities
For this specific example, the core "utility" is the processing logic itself. Let's simulate it with a simple function. The Flask server acts as the external interface.
process_task(input_data): A placeholder function. In a real scenario, this might call an LLM (utils/call_llm.py).
4. Node Design (Detailed)
ProcessNode(Node):prep: Readstask_input,current_attemptfromshared.exec: Callsutils.process_task.process_task.post: Writesprocessed_outputtoshared, incrementscurrent_attempt. Returns "default".
ReviewNode(AsyncNode):prep_async: (As modified/wrapped by server.py) Readsreview_event,processed_outputfromshared. Puts "waiting_for_review" status onto the task's SSE queue.exec_async:await shared["review_event"].wait().post_async: Readsfeedbackfromshared. Clears the event. Returns "approved" or "rejected". If approved, storesprocessed_outputintofinal_result.
ResultNode(Node):prep: Readsfinal_resultfromshared.exec: Prints/logs the final result.post: ReturnsNone(ends flow).