From 789c732866fa432690ecdecfa68bcce877743748 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Tue, 10 Dec 2024 22:52:17 -0500 Subject: [PATCH] x --- docs/docs/concepts/human_in_the_loop.md | 184 ++++++++++++++---------- 1 file changed, 109 insertions(+), 75 deletions(-) diff --git a/docs/docs/concepts/human_in_the_loop.md b/docs/docs/concepts/human_in_the_loop.md index 493d8f149..7b5896d3b 100644 --- a/docs/docs/concepts/human_in_the_loop.md +++ b/docs/docs/concepts/human_in_the_loop.md @@ -13,10 +13,37 @@ A **human-in-the-loop** (or "on-the-loop") workflow integrates human input into Key use cases for **human-in-the-loop** workflows in LLM-based applications include: -1. **🛠️ [Reviewing tool calls](#review-and-edit)**: Humans can review, edit, or approve tool calls requested by the LLM before tool execution. +1. **🛠️ Reviewing tool calls**: Humans can review, edit, or approve tool calls requested by the LLM before tool execution. 2. **✅ Validating LLM outputs**: Humans can review, edit, or approve content generated by the LLM. 3. **💡 Providing context**: Enable the LLM to explicitly request human input for clarification or additional details or to support multi-turn conversations. +## `interrupt` + +The [`interrupt` function][langgraph.types.interrupt] in LangGraph enables human-in-the-loop workflows by pausing the graph at a specific node, presenting information to a human, and resuming the graph with their input. This function is useful for tasks like approvals, edits, or collecting additional input. The [`interrupt` function][langgraph.types.interrupt] is used in conjunction with the [`Command`](../reference/types.md#langgraph.types.Command) object to resume the graph with a value provided by the human. + +```python +from langgraph.types import interrupt + +def human_node(state: State): + value = interrupt( + # Any JSON serializable value to surface to the human. + # For example, a question or a piece of text or a set of keys in the state + some_data + ) + ... + # Update the state with the human's input or route the graph based on the input. + ... + +# Run the graph and hit the breakpoint +thread_config = {"configurable": {"thread_id": "some_id"}} +graph.invoke(some_input, config=thread_config) + +# Resume the graph with the human's input +graph.invoke(Command(resume=value_from_human), config=thread_config) +``` + +Please read the [Breakpoints](breakpoints.md) guide for more information on using the `interrupt` function. + ## Design Patterns 1. **Approval**: Pause the graph before a critical step, such as an API call, to review and approve the action. If the action is rejected, you can prevent the graph from executing the step, and potentially take an alternative action. @@ -24,103 +51,103 @@ Key use cases for **human-in-the-loop** workflows in LLM-based applications incl 3. **Input**: Explicitly request human input at a particular step in the graph. This is useful for collecting additional information or context to inform the agent's decision-making process or for supporting **multi-turn conversations**. -### Approval +=== "Approval" -
-![image](img/human_in_the_loop/approve-or-reject.png){: style="max-height:400px"} -
Depending on the human's approval or rejection, the graph can proceed with the action or take an alternative path.
-
+
+ ![image](img/human_in_the_loop/approve-or-reject.png){: style="max-height:400px"} +
Depending on the human's approval or rejection, the graph can proceed with the action or take an alternative path.
+
-Pause the graph before a critical step, such as an API call, to review and approve the action. If the action is rejected, you can prevent the graph from executing the step, and potentially take an alternative action. - -```python -from langgraph.types import interrupt - -def human_approval(state: State): - ... - is_approved = interrupt( - { - "question": "Is this correct?", - # Surface the output that should be - # reviewed and approved by the human. - "llm_output": state["llm_output"] - } - ) - - if is_approved: - # Proceed with the action - ... - else: - # Do something else - ... - -# Add the node to the graph in an appropriate location -# and connect it to the relevant nodes. -graph_builder.add_node("human_approval", human_approval) -graph = graph_builder.compile(checkpointer=checkpointer) - -... - -# After running the graph and hitting the breakpoint, the graph will pause. -# Resume it with either an approval or rejection. -thread_config = {"configurable": {"thread_id": "some_id"}} -graph.invoke(Command(resume=True), config=thread_config) -``` - - -### Edit - -
-![image](img/human_in_the_loop/tool-call-review.png){: style="max-height:400px"} -
A human can review and edit the output from the LLM before proceeding. This is particularly -critical in applications where the tool calls requested by the LLM may be sensitive or require human oversight. -
-
- - -=== "Review tool calls" - - TODO: Create an example for tool call review. - - -=== "Review text output from the LLM and make any necessary edits." + Pause the graph before a critical step, such as an API call, to review and approve the action. If the action is rejected, you can prevent the graph from executing the step, and potentially take an alternative action. ```python from langgraph.types import interrupt - def human_editing(state: State): + def human_approval(state: State): ... - result = interrupt( - # Interrupt information to surface to the client. - # Can be any JSON serializable value. + is_approved = interrupt( { - "task": "Review the output from the LLM and make any necessary edits.", + "question": "Is this correct?", + # Surface the output that should be + # reviewed and approved by the human. "llm_output": state["llm_output"] } ) - # Update the state with the edited text - return { - "llm_output": result["edited_text"] - } + if is_approved: + # Proceed with the action + ... + else: + # Do something else + ... # Add the node to the graph in an appropriate location # and connect it to the relevant nodes. - graph_builder.add_node("human_editing", human_editing) + graph_builder.add_node("human_approval", human_approval) graph = graph_builder.compile(checkpointer=checkpointer) ... # After running the graph and hitting the breakpoint, the graph will pause. - # Resume it with the edited text. + # Resume it with either an approval or rejection. thread_config = {"configurable": {"thread_id": "some_id"}} - graph.invoke( - Command(resume={"edited_text": "The edited text"}), - config=thread_config - ) + graph.invoke(Command(resume=True), config=thread_config) ``` -### Multi-turn conversation (Input) + +=== "Review & Edit" + +
+ ![image](img/human_in_the_loop/tool-call-review.png){: style="max-height:400px"} +
A human can review and edit the output from the LLM before proceeding. This is particularly + critical in applications where the tool calls requested by the LLM may be sensitive or require human oversight. +
+
+ + + === "Review tool calls" + + TODO: Create an example for tool call review. + + + === "Review text output from the LLM and make any necessary edits." + + ```python + from langgraph.types import interrupt + + def human_editing(state: State): + ... + result = interrupt( + # Interrupt information to surface to the client. + # Can be any JSON serializable value. + { + "task": "Review the output from the LLM and make any necessary edits.", + "llm_output": state["llm_output"] + } + ) + + # Update the state with the edited text + return { + "llm_output": result["edited_text"] + } + + # Add the node to the graph in an appropriate location + # and connect it to the relevant nodes. + graph_builder.add_node("human_editing", human_editing) + graph = graph_builder.compile(checkpointer=checkpointer) + + ... + + # After running the graph and hitting the breakpoint, the graph will pause. + # Resume it with the edited text. + thread_config = {"configurable": {"thread_id": "some_id"}} + graph.invoke( + Command(resume={"edited_text": "The edited text"}), + config=thread_config + ) + ``` + +### Multi-turn conversation
![image](img/human_in_the_loop/multi-turn-conversation.png){: style="max-height:400px"} @@ -164,6 +191,13 @@ graph.invoke( ) ``` +## Best practices + +* Use the [`interrupt`](breakpoints.md#the-interrupt-function) function to set breakpoints and collect user input. +* Use [`Command`](breakpoints.md#the-command-primitive) to resume execution and control the graph state. +* Consider putting all side effects (e.g., API calls) after the `interrupt` to prevent duplication. +* Understand [how resuming from a breakpoint works](breakpoints.md#how-does-resuming-from-a-breakpoint-work) to avoid common gotchas. + ## Additional Resources 📚 - [**Conceptual Guide: Persistence**](persistence.md#replay): Read the persistence guide for more context on replaying.