3. UX and Human-in-the-Loop-——Lesson 4:Dynamic breakpoints动态断点(未索引))
https://academy.langchain.com/courses/intro-to-langgraphhttps://github.com/shangxiang0907/langchain-academy文章目录Dynamic breakpoints 动态断点Review 复习Goals 目标Usage with LangGraph API 与 LangGraph API 配合使用的注意事项Dynamic breakpoints 动态断点Review 复习We discussed motivations for human-in-the-loop:我们讨论了引入人工干预human-in-the-loop的动机(1)Approval- We can interrupt our agent, surface state to a user, and allow the user to accept an action1审批Approval——我们可以中断代理执行将当前状态呈现给用户并允许用户接受某项操作(2)Debugging- We can rewind the graph to reproduce or avoid issues2调试Debugging——我们可以倒回图以复现或规避问题(3)Editing- You can modify the state3编辑Editing——您可以修改图的状态We covered breakpoints as a general way to stop the graph at specific steps, which enables use-cases likeApproval我们介绍了断点作为一种通用机制用于在特定步骤处停止图的执行从而支持审批等用例We also showed how to edit graph state, and introduce human feedback.我们还展示了如何编辑图状态并引入人工反馈。Goals 目标Breakpoints are set by the developer on a specific node during graph compilation.断点由开发者在图编译期间针对特定节点设置。But, sometimes it is helpful to allow the graphdynamically interruptitself!但有时允许图动态中断自身会很有帮助This is an internal breakpoint, and can be achieved usingNodeInterrupt.这是一种内部断点可通过NodeInterrupt实现。This has a few specific benefits:这具有若干特定优势(1) You can do it conditionally (from inside a node based on developer-defined logic).1您可以有条件地触发中断即在节点内部、基于开发者定义的逻辑(2) You can communicate to the user why it’s interrupted (by passing whatever you want to theNodeInterrupt).2您可以向用户说明中断原因通过向NodeInterrupt传入任意所需信息Let’s create a graph where aNodeInterruptis thrown based on the length of the input.让我们构建一个图使其根据输入长度触发NodeInterrupt。%%capture--no-stderr%pip install--quiet-U langgraph langchain_openai langgraph_sdkfromIPython.displayimportImage,displayfromtyping_extensionsimportTypedDictfromlanggraph.checkpoint.memoryimportMemorySaverfromlanggraph.errorsimportNodeInterruptfromlanggraph.graphimportSTART,END,StateGraphclassState(TypedDict):input:strdefstep_1(state:State)-State:print(---Step 1---)returnstatedefstep_2(state:State)-State:# Lets optionally raise a NodeInterrupt if the length of the input is longer than 5 charactersiflen(state[input])5:raiseNodeInterrupt(fReceived input that is longer than 5 characters:{state[input]})print(---Step 2---)returnstatedefstep_3(state:State)-State:print(---Step 3---)returnstate builderStateGraph(State)builder.add_node(step_1,step_1)builder.add_node(step_2,step_2)builder.add_node(step_3,step_3)builder.add_edge(START,step_1)builder.add_edge(step_1,step_2)builder.add_edge(step_2,step_3)builder.add_edge(step_3,END)# Set up memorymemoryMemorySaver()# Compile the graph with memorygraphbuilder.compile(checkpointermemory)# Viewdisplay(Image(graph.get_graph().draw_mermaid_png()))Let’s run the graph with an input that’s longer than 5 characters.让我们使用一个长度超过 5 个字符的输入来运行该图。initial_input{input:hello world}thread_config{configurable:{thread_id:1}}# Run the graph until the first interruptionforeventingraph.stream(initial_input,thread_config,stream_modevalues):print(event){input: hello world} ---Step 1--- {input: hello world}If we inspect the graph state at this point, we the node set to execute next (step_2).此时若检查图状态可见下一个待执行节点为step_2。stategraph.get_state(thread_config)print(state.next)(step_2,)We can see that theInterruptis logged to state.我们可以看到Interrupt已被记录到状态中。print(state.tasks)(PregelTask(id6eb3910d-e231-5ba2-b25e-28ad575690bd, namestep_2, errorNone, interrupts(Interrupt(valueReceived input that is longer than 5 characters: hello world, whenduring),), stateNone),)We can try to resume the graph from the breakpoint.我们可以尝试从该断点恢复图的执行。But, this just re-runs the same node!但这样只会重新运行同一个节点Unless state is changed we will be stuck here.除非状态被修改否则我们将一直卡在此处。foreventingraph.stream(None,thread_config,stream_modevalues):print(event){input: hello world}stategraph.get_state(thread_config)print(state.next)(step_2,)Now, we can update state.现在我们可以更新状态。graph.update_state(thread_config,{input:hi},){configurable: {thread_id: 1, checkpoint_ns: , checkpoint_id: 1ef6a434-06cf-6f1e-8002-0ea6dc69e075}}foreventingraph.stream(None,thread_config,stream_modevalues):print(event){input: hi} ---Step 2--- {input: hi} ---Step 3--- {input: hi}Usage with LangGraph API 与 LangGraph API 配合使用的注意事项⚠️ Notice⚠️ 注意Since filming these videos, we’ve updated Studio so that it can now be run locally and accessed through your browser.自录制这些视频以来我们已更新 Studio使其现在可本地运行并通过浏览器访问。This is the preferred way to run Studio instead of using the Desktop App shown in the video.这是运行 Studio 的首选方式而非视频中展示的桌面应用。It is now calledLangSmith Studioinstead ofLangGraph Studio.它现在被称为LangSmith Studio而非LangGraph Studio。Detailed setup instructions are available in the “Getting Setup” guide at the start of the course.详细的安装说明请参阅本课程开头的“环境准备”指南。You can find a description of Studio here, and specific details for local deployment here.您可在此处查看 Studio 的介绍文档链接以及本地部署的具体细节链接。To start the local development server, run the following command in your terminal in the/studiodirectory in this module:要在本地启动开发服务器请在本模块的/studio目录下于终端中运行以下命令langgraph devYou should see the following output:您应看到如下输出- API: http://127.0.0.1:2024 - Studio UI: https://smith.langchain.com/studio/?baseUrlhttp://127.0.0.1:2024 - API Docs: http://127.0.0.1:2024/docsOpen your browser and navigate to theStudio UIURL shown above.打开浏览器并导航至上方显示的Studio UIURL。ifgoogle.colabinstr(get_ipython()):raiseException(Unfortunately LangGraph Studio is currently not supported on Google Colab)We connect to it via the SDK.我们通过 SDK 连接到它。fromlanggraph_sdkimportget_client# This is the URL of the local development serverURLhttp://127.0.0.1:2024clientget_client(urlURL)# Search all hosted graphsassistantsawaitclient.assistants.search()threadawaitclient.threads.create()input_dict{input:hello world}asyncforchunkinclient.runs.stream(thread[thread_id],assistant_iddynamic_breakpoints,inputinput_dict,stream_modevalues,):print(fReceiving new event of type:{chunk.event}...)print(chunk.data)print(\n\n)Receiving new event of type: metadata... {run_id: 1ef6a43a-1b04-64d0-9a79-1caff72c8a89} Receiving new event of type: values... {input: hello world} Receiving new event of type: values... {input: hello world}current_stateawaitclient.threads.get_state(thread[thread_id])current_state[next][step_2]awaitclient.threads.update_state(thread[thread_id],{input:hi!}){configurable: {thread_id: ea8c2912-987e-49d9-b890-6e81d46065f9, checkpoint_ns: , checkpoint_id: 1ef6a43a-64b2-6e85-8002-3cf4f2873968}, checkpoint_id: 1ef6a43a-64b2-6e85-8002-3cf4f2873968}asyncforchunkinclient.runs.stream(thread[thread_id],assistant_iddynamic_breakpoints,inputNone,stream_modevalues,):print(fReceiving new event of type:{chunk.event}...)print(chunk.data)print(\n\n)Receiving new event of type: metadata... {run_id: 1ef64c33-fb34-6eaf-8b59-1d85c5b8acc9} Receiving new event of type: values... {input: hi!} Receiving new event of type: values... {input: hi!}current_stateawaitclient.threads.get_state(thread[thread_id])current_state{values: {input: hi!}, next: [step_2], tasks: [{id: 858e41b2-6501-585c-9bca-55c1e729ef91, name: step_2, error: None, interrupts: [], state: None}], metadata: {step: 2, source: update, writes: {step_1: {input: hi!}}, parents: {}, graph_id: dynamic_breakpoints}, created_at: 2024-09-03T22:27:05.70726000:00, checkpoint_id: 1ef6a43a-64b2-6e85-8002-3cf4f2873968, parent_checkpoint_id: 1ef6a43a-1cb8-6c3d-8001-7b11d0d34f00}