@@ -611,104 +611,156 @@ human-in-the-loop框架保持与现有代码的完全向后兼容性。所有先
611611
612612### 1. 图中断兼容性
613613
614- 在节点/工具中使用 ` NewInterruptAndRerunErr ` 的先前图中断流程继续保持不变:
614+ 在节点/工具中使用已弃用的 ` NewInterruptAndRerunErr ` 或 ` InterruptAndRerun ` 的先前图中断流程将继续被支持,但需要一个关键的额外步骤:** 错误包装** 。
615+
616+ 由于这些遗留函数不是地址感知的,调用它们的组件有责任捕获错误,并使用 ` WrapInterruptAndRerunIfNeeded ` 辅助函数将地址信息包装进去。这通常在协调遗留组件的复合节点内部完成。
617+
618+ > ** 注意** :如果您选择** 不** 使用 ` WrapInterruptAndRerunIfNeeded ` ,遗留行为将被保留。最终用户仍然可以像以前一样使用 ` ExtractInterruptInfo ` 从错误中获取信息。但是,由于产生的中断上下文将缺少正确的地址,因此将无法对该特定中断点使用新的定向恢复 API。要完全启用新的地址感知功能,必须进行包装。
615619
616620``` go
617- // 先前的方法仍然有效
618- func myTool (ctx context .Context , input string ) (string , error ) {
621+ // 1. 一个使用已弃用中断的遗留工具
622+ func myLegacyTool (ctx context .Context , input string ) (string , error ) {
619623 // ... tool 逻辑
624+ // 这个错误不是地址感知的。
620625 return " " , compose.NewInterruptAndRerunErr (" 需要用户批准" )
621626}
622627
623- // 最终用户代码保持不变
628+ // 2. 一个调用遗留工具的复合节点
629+ var legacyToolNode = compose.InvokableLambda (func (ctx context .Context , input string ) (string , error ) {
630+ out , err := myLegacyTool (ctx, input)
631+ if err != nil {
632+ // 关键:调用者必须包装错误以添加地址。
633+ // "tool:legacy_tool" 段将被附加到当前地址。
634+ segment := compose.AddressSegment {Type: " tool" , ID: " legacy_tool" }
635+ return " " , compose.WrapInterruptAndRerunIfNeeded (ctx, segment, err)
636+ }
637+ return out, nil
638+ })
639+
640+ // 3. 最终用户代码现在可以看到完整地址。
624641_ , err := graph.Invoke (ctx, input)
625642if err != nil {
626643 interruptInfo , exists := compose.ExtractInterruptInfo (err)
627644 if exists {
628- // 现在你可以获得增强的信息
629- fmt.Printf (" 中断上下文: %+v \n " , interruptInfo.InterruptContexts )
630- // 先前的字段仍然可用
631- fmt.Printf (" Before nodes: %v \n " , interruptInfo.BeforeNodes )
645+ // 中断上下文现在将拥有一个正确的、完全限定的地址。
646+ fmt.Printf (" Interrupt Address: %s \n " , interruptInfo.InterruptContexts [0 ].Address .String ())
632647 }
633648}
634649```
635650
636- ** 增强功能** :` InterruptInfo ` 现在包含一个额外的 ` []*InterruptCtx ` 字段,提供对中断链的结构化访问,同时保留所有现有功能。
651+ ** 增强功能** :通过包装错误,` InterruptInfo ` 将包含一个正确的 ` []*InterruptCtx ` ,其中包含完全限定的地址,从而允许遗留组件无缝集成到新的人机协同框架中。
652+
653+ ### 2. 对编译时静态中断图的兼容性
654+
655+ 通过 ` WithInterruptBeforeNodes ` 或 ` WithInterruptAfterNodes ` 添加的先前静态中断图继续有效,但状态处理的方式得到了显著改进。
656+
657+ 当静态中断被触发时,会生成一个 ` InterruptCtx ` ,其地址指向图本身。关键在于,` InterruptCtx.Info ` 字段现在直接暴露了该图的状态。
637658
638- ### 2. 静态图中断兼容性
659+ 这启用了一个更直接、更直观的工作流:
660+ 1 . 最终用户收到 ` InterruptCtx ` ,并可以通过 ` .Info ` 字段检查图的实时状态。
661+ 2 . 他们可以直接修改这个状态对象。
662+ 3 . 然后,他们可以通过 ` ResumeWithData ` 和 ` InterruptCtx.ID ` 将修改后的状态对象传回以恢复执行。
639663
640- 通过 ` WithInterruptBeforeNodes ` 或 ` WithInterruptAfterNodes ` 添加到图上的先前静态中断继续工作:
664+ 这种新模式通常不再需要使用旧的 ` WithStateModifier ` 选项,尽管为了完全的向后兼容性,该选项仍然可用。
641665
642666``` go
643- // 先前的静态中断配置仍然有效
644- graph , err := myGraph.Compile (ctx,
645- compose.WithInterruptBeforeNodes ([]string {" critical_node" }),
646- compose.WithInterruptAfterNodes ([]string {" validation_node" })
647- )
667+ // 1. 定义一个拥有自己本地状态的图
668+ type MyGraphState struct {
669+ SomeValue string
670+ }
648671
649- // 当中断时,最终用户获得增强的信息
650- interruptInfo , exists := compose.ExtractInterruptInfo (err)
651- if exists {
652- // 先前的字段仍然可用
653- fmt.Printf (" Before nodes: %v \n " , interruptInfo.BeforeNodes )
654- fmt.Printf (" After nodes: %v \n " , interruptInfo.AfterNodes )
655-
656- // 新的增强功能:访问结构化的中断上下文
657- if len (interruptInfo.InterruptContexts ) > 0 {
658- interruptCtx := interruptInfo.InterruptContexts [0 ]
659- fmt.Printf (" 图状态: %+v \n " , interruptCtx.Info )
660-
661- // 最终用户可以直接修改状态并将其传回
662- // 不再需要使用 WithStateModifier(尽管它仍然有效)
672+ g := compose.NewGraph [string , string ](compose.WithGenLocalState (func (ctx context.Context ) *MyGraphState {
673+ return &MyGraphState{SomeValue: " initial" }
674+ }))
675+ // ... 向图中添加节点1和节点2 ...
676+
677+ // 2. 使用静态中断点编译图
678+ // 这将在 "node_1" 节点完成后中断图本身。
679+ graph , err := g.Compile (ctx, compose.WithInterruptAfterNodes ([]string {" node_1" }))
680+
681+ // 3. 运行图,这将触发静态中断
682+ _, err = graph.Invoke (ctx, " start" )
683+
684+ // 4. 提取中断上下文和图的状态
685+ interruptInfo , isInterrupt := compose.ExtractInterruptInfo (err)
686+ if isInterrupt {
687+ interruptCtx := interruptInfo.InterruptContexts [0 ]
688+
689+ // .Info 字段暴露了图的当前状态
690+ graphState , ok := interruptCtx.Info .(*MyGraphState)
691+ if ok {
692+ // 5. 直接修改状态
693+ fmt.Printf (" Original state value: %s \n " , graphState.SomeValue ) // 打印 "initial"
694+ graphState.SomeValue = " a-new-value-from-user"
695+
696+ // 6. 通过传回修改后的状态对象来恢复
697+ resumeCtx := compose.ResumeWithData (context.Background (), interruptCtx.ID , graphState)
698+ result , err := graph.Invoke (resumeCtx, " start" )
699+ // ... 执行将继续,并且 node_2 现在将看到修改后的状态。
663700 }
664701}
665702```
666703
667- ** 增强功能** :除了 ` []BeforeNodes ` 和 ` []AfterNodes ` ,现在还返回一个 ` InterruptCtx ` ,使最终用户可以直接访问图状态。对于喜欢先前方法的开发者,` WithStateModifier ` 选项仍然可用。
668-
669704### 3. Agent 中断兼容性
670705
671- 先前的 agent 中断流程继续保持不变:
706+ 与旧版 agent 的兼容性是在数据结构层面维护的,确保了旧的 agent 实现能在新框架内继续运作。其关键在于 ` adk.InterruptInfo ` 和 ` adk.ResumeInfo ` 结构体是如何被填充的。
707+
708+ ** 对最终用户(应用层)而言:**
709+ 当从 agent 收到一个中断时,` adk.InterruptInfo ` 结构体中会同时填充以下两者:
710+ - 新的、结构化的 ` InterruptContexts ` 字段。
711+ - 遗留的 ` Data ` 字段,它将包含原始的中断信息(例如 ` ChatModelAgentInterruptInfo ` 或 ` WorkflowInterruptInfo ` )。
712+
713+ 这使得最终用户可以逐步迁移他们的应用逻辑来使用更丰富的 ` InterruptContexts ` ,同时在需要时仍然可以访问旧的 ` Data ` 字段。
714+
715+ ** 对 Agent 开发者而言:**
716+ 当一个旧版 agent 的 ` Resume ` 方法被调用时,它收到的 ` adk.ResumeInfo ` 结构体仍然包含现已弃用的嵌入式 ` InterruptInfo ` 字段。该字段被填充了相同的遗留数据结构,允许 agent 开发者维持其现有的恢复逻辑,而无需立即更新到新的地址感知 API。
672717
673718``` go
674- // Agent 中断模式保持不变
675- func (a *myAgent ) Run (ctx context .Context , input *adk .AgentInput ) *adk .AsyncIterator [*adk .AgentEvent ] {
676- // ... agent 逻辑
677- return adk.Interrupt (ctx, " 需要用户输入" )
719+ // --- 最终用户视角 ---
720+
721+ // 在 agent 运行后,你收到了一个中断事件。
722+ if event.Action != nil && event.Action .Interrupted != nil {
723+ interruptInfo := event.Action .Interrupted
724+
725+ // 1. 新方式:访问结构化的中断上下文
726+ if len (interruptInfo.InterruptContexts ) > 0 {
727+ fmt.Printf (" New structured context available: %+v \n " , interruptInfo.InterruptContexts [0 ])
728+ }
729+
730+ // 2. 旧方式(仍然有效):访问遗留的 Data 字段
731+ if chatInterrupt , ok := interruptInfo.Data .(*adk.ChatModelAgentInterruptInfo ); ok {
732+ fmt.Printf (" Legacy ChatModelAgentInterruptInfo still accessible.\n " )
733+ // ... 使用旧结构体的逻辑
734+ }
678735}
679736
680- // 最终用户访问模式保持不变
681- func (a *myAgent ) Resume (ctx context .Context , info *adk .ResumeInfo ) *adk .AsyncIterator [*adk .AgentEvent ] {
682- // 先前的信息仍然可用
683- if info.WasInterrupted {
684- fmt.Printf (" 中断数据: %v \n " , info.InterruptInfo .Data )
685- // 增强:现在还可以访问结构化的中断上下文
686- if info.InterruptInfo != nil && len (info.InterruptInfo .InterruptContexts ) > 0 {
687- fmt.Printf (" 中断上下文: %+v \n " , info.InterruptInfo .InterruptContexts [0 ])
737+
738+ // --- Agent 开发者视角 ---
739+
740+ // 在一个旧版 agent 的 Resume 方法内部:
741+ func (a *myLegacyAgent ) Resume (ctx context .Context , info *adk .ResumeInfo ) *adk .AsyncIterator [*adk .AgentEvent ] {
742+ // 已弃用的嵌入式 InterruptInfo 字段仍然会被填充。
743+ // 这使得旧的恢复逻辑可以继续工作。
744+ if info.InterruptInfo != nil {
745+ if chatInterrupt , ok := info.InterruptInfo .Data .(*adk.ChatModelAgentInterruptInfo ); ok {
746+ // ... 依赖于旧的 ChatModelAgentInterruptInfo 结构体的现有恢复逻辑
747+ fmt.Println (" Resuming based on legacy InterruptInfo.Data field." )
688748 }
689749 }
690750
691- // 恢复逻辑:继续正常执行或处理恢复数据
692- if info.IsResumeTarget && info.ResumeData != nil {
693- // 处理恢复数据并继续
694- return a.handleResumeWithData (ctx, info.ResumeData )
695- }
696-
697- // 继续正常执行
698- return a.Run (ctx, input)
751+ // ... 继续执行
752+ return a.Run (ctx, &adk.AgentInput {Input: " resumed execution" })
699753}
700754```
701755
702- ** 增强功能** :来自 ` AgentEvent ` 的 ` InterruptInfo ` 包含所有先前的信息,最终用户仍然可以在 ` ResumeInfo ` 中访问此信息,并额外受益于结构化的中断上下文。
703-
704756### 迁移优势
705757
706- - ** 无重大变更 ** :现有代码无需修改即可继续工作 。
707- - ** 渐进式采用** :团队可以按照自己的节奏采用新功能 。
708- - ** 增强的功能** :新的寻址系统在保留现有 API 的同时提供了更丰富的上下文 。
709- - ** 灵活的状态管理** :最终用户可以选择直接状态修改(新)或 ` WithStateModifier ` (现有) 。
758+ - ** 保留遗留行为 ** : 现有代码将继续按其原有方式运行。旧的中断模式不会导致程序崩溃,但它们也不会在不经修改的情况下自动获得新的地址感知能力 。
759+ - ** 渐进式采用** : 团队可以根据具体情况选择性地启用新功能。例如,你可以只在你需要定向恢复的工作流中,用 ` WrapInterruptAndRerunIfNeeded ` 来包装遗留的中断 。
760+ - ** 增强的功能** : 新的寻址系统为所有中断提供了更丰富的结构化上下文 ( ` InterruptCtx ` ),同时旧的数据字段仍然会被填充以实现完全兼容 。
761+ - ** 灵活的状态管理** : 对于静态图中断,你可以选择通过 ` .Info ` 字段进行现代、直接的状态修改,或者继续使用旧的 ` WithStateModifier ` 选项 。
710762
711- 这种向后兼容性确保了现有用户的平滑过渡,同时为human -in-the-loop交互提供了强大的新功能 。
763+ 这种向后兼容性模型确保了现有用户的平滑过渡,同时为采用强大的新的 human -in-the-loop 交互功能提供了清晰的路径 。
712764
713765## 实现示例
714766
0 commit comments