go Eino使用ADK开发agent

开发一个简单的帮助请假的agent,用户通过输入请假信息,agent自动完成请假信息补充,发起请假流程

1、安装依赖

shell 复制代码
go get -u github.com/cloudwego/eino@latest
go get -u github.com/cloudwego/eino-ext/components/model/openai@late

2、新建一个循环agent,用于判断用户输入的请假信息是否完整,如果不完整,询问用户输入相关的信息,核心在于创建一个工具,通过工具中断agent,获取问题用户的回答

go 复制代码
inferTool, err := utils.InferTool(
    "askQuestions",
    "询问用户补充信息",
    func(ctx context.Context, input InferToolInput) (string, error) {
       interrupted, _, storedState := compose.GetInterruptState[string](ctx)
       if !interrupted {
          return "", compose.StatefulInterrupt(ctx, input, input)
       }
       flow, data, t := compose.GetResumeContext[string](ctx)
       if !flow {
          return "", compose.StatefulInterrupt(ctx, input, storedState)
       }
       fmt.Println("###", t)
       if !data || t == "" {
          return "", fmt.Errorf("tool resumed without a user answer")
       }

       return t, nil
    })
if err != nil {
    panic(err)
}
go 复制代码
analysisAgent, _ := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{
       Name:        "leaveAnalysisAgent",
       Model:       chatModel,
       Description: "分析缺少请假信息",
       Instruction: `你是一个请假流程信息分析专家,根据提供的请假信息,分析以下请假关键信息
1、请假的开始和结束时间
2、请假的类型
3、请假的原因
如果请假的信息已经完整,请输入"EXIT:请假信息已完整" 来结束请假信息补充流程
`,
    })

    askAgent, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{
       Name:        "completeLeaveInfo",
       Model:       chatModel,
       Description: "根据分析结果询问用户补充请假信息",
       Instruction: `根据前面的信息补充问题,使用askQuestions工具,补充请假信息`,
       ToolsConfig: adk.ToolsConfig{
          ToolsNodeConfig: compose.ToolsNodeConfig{
             Tools: []tool.BaseTool{
                inferTool,
             },
          },
       },
    })

    exitAgent, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{
       Name:        "exitController",
       Model:       chatModel,
       Description: "控制优化循环的退出",
       Instruction: `检查前面的分析结果,如果请假流程信息分析专家认为请假信息已经完整(包含"EXIT"关键词),
则输出 "TERMINATE" 并生成退出动作来结束循环。否则继续下一轮优化。`,
    })

    agent, err := adk.NewLoopAgent(context.Background(), &adk.LoopAgentConfig{
       Name:        "leaveLoop",
       Description: "请假信息补充循环:分析缺少请假信息->询问用户补充请假信息->检查退出条件",
       SubAgents: []adk.Agent{
          analysisAgent,
          askAgent,
          exitAgent,
       },
       MaxIterations: 5,
    })

    if err != nil {
       panic(err)
    }

    leaveApproveTool, err := utils.InferTool[LeaveInfo, string]("leaveApproveTool", "发起请假流程", func(ctx context.Context, input LeaveInfo) (output string, err error) {
       log.Println("发起请假入参:", input)
       return "success", err
    })

    modelAgent, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{
       Name:        "leaveApproveAgent",
       Model:       chatModel,
       Description: "发起请假流程",
       Instruction: "根据前面的请假信息,使用leaveApproveTool发起请假流程",
       ToolsConfig: adk.ToolsConfig{
          ToolsNodeConfig: compose.ToolsNodeConfig{
             Tools: []tool.BaseTool{
                leaveApproveTool,
             },
          },
       },
    })

3、创建一个请假流程发起agent,请假信息补充完成之后,发起请假流程

go 复制代码
leaveApproveTool, err := utils.InferTool[LeaveInfo, string]("leaveApproveTool", "发起请假流程", func(ctx context.Context, input LeaveInfo) (output string, err error) {
    log.Println("发起请假入参:", input)
    return "success", err
})

modelAgent, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{
    Name:        "leaveApproveAgent",
    Model:       chatModel,
    Description: "发起请假流程",
    Instruction: "根据前面的请假信息,使用leaveApproveTool发起请假流程",
    ToolsConfig: adk.ToolsConfig{
       ToolsNodeConfig: compose.ToolsNodeConfig{
          Tools: []tool.BaseTool{
             leaveApproveTool,
          },
       },
    },
})

4、创建顺序流将整个流程串联起来,运行同时输入我们的信息帮我从今天起请两天假,查看打印agent的思考过程以及结果

go 复制代码
sequentialAgent, err := adk.NewSequentialAgent(context.Background(), &adk.SequentialAgentConfig{
       Name:        "leaveSequentialAgent",
       Description: "请假工作流",
       SubAgents: []adk.Agent{
          agent,
          modelAgent,
       },
    })

    runner := adk.NewRunner(context.Background(), adk.RunnerConfig{
       Agent:           sequentialAgent,
       CheckPointStore: store.NewInMemoryStore(),
       EnableStreaming: true,
    })

    iter := runner.Query(context.Background(), "帮我从今天起请两天假", adk.WithCheckPointID("deep-analysis-1"))
    for {
       lastEvent, interrupted := processEvents(iter)
       if !interrupted {
          break
       }

       interruptCtx := lastEvent.Action.Interrupted.InterruptContexts[0]
       interruptID := interruptCtx.ID
       fmt.Println(interruptCtx.Info.(InferToolInput).Question)
       fmt.Println("输入你的答案")
       var answer string
       n, err := fmt.Scanln(&answer)
       if n != 1 || err != nil {
          panic(err)
       }
       iter, err = runner.ResumeWithParams(context.Background(), "deep-analysis-1", &adk.ResumeParams{
          Targets: map[string]any{
             interruptID: answer,
          },
       })
       if err != nil {
          panic(err)
       }

    }

}

func processEvents(iter *adk.AsyncIterator[*adk.AgentEvent]) (*adk.AgentEvent, bool) {
    var lastEvent *adk.AgentEvent
    for {
       event, ok := iter.Next()
       if !ok {
          break
       }
       if event.Err != nil {
          log.Fatal(event.Err)
       }

       PrintContent(event)
       lastEvent = event
    }

    if lastEvent == nil {
       return nil, false
    }
    if lastEvent.Action != nil && lastEvent.Action.Interrupted != nil {
       return lastEvent, true
    }
    return lastEvent, false
}
func PrintContent(event *adk.AgentEvent) {
    if event.Output != nil && event.Output.MessageOutput != nil {
       output := event.Output.MessageOutput
       if output.IsStreaming {
          stream := output.MessageStream
          if stream != nil {

             for {
                chunk, err := stream.Recv()
                if err != nil {
                   if err == io.EOF {
                      break
                   }
                   fmt.Printf("error: %v", err)
                   return
                }
                if chunk.ReasoningContent != "" {
                   fmt.Print(chunk.ReasoningContent)
                }
                if chunk.Content != "" {
                   fmt.Print(chunk.Content)
                }

             }

          }

       } else {
          message := output.Message
          if message != nil {
             if message.Content != "" {
                log.Print(message)
             }

             if message.ReasoningContent != "" {
                fmt.Print(message.ReasoningContent)
             }

          }
       }

    }

    if event.Action != nil {
       if event.Action.TransferToAgent != nil {
          fmt.Printf("\naction: transfer to %v", event.Action.TransferToAgent.DestAgentName)
       }

       if event.Action.Interrupted != nil {
          for _, ic := range event.Action.Interrupted.InterruptContexts {
             str, ok := ic.Info.(fmt.Stringer)
             if ok {
                fmt.Printf("\n%s", str.String())
             } else {
                fmt.Printf("\n%v", ic.Info)
             }
          }
       }
       if event.Action.Exit {
          fmt.Printf("\naction: exit")
       }
    }

    if event.Err != nil {
       fmt.Printf("\nerror: %v", event.Err)
    }

}

type InferToolInput struct {
    Question string `json:"question" jsonschema:"required;description=你需要询问的问题"`
}

type LeaveInfo struct {
    StartDate   string `json:"startDate" jsonschema:"required;description=请假开始时间"`
    EndDate     string `json:"endDate" jsonschema:"required;description=请假结束时间"`
    LeaveType   string `json:"leaveType" jsonschema:"required;description=请假类型"`
    LeaveReason string `json:"leaveReason" jsonschema:"required;description=请假原因"`
}

5、查看结果

总结:整体体验不错,agent的中断和恢复,可以灵活的完成各种场景

相关推荐
唐叔在学习2 小时前
Python自动化指令进阶:UAC提权
后端·python
Assby2 小时前
Windows 在 PostgreSQL 上安装 vector 扩展
后端
12344522 小时前
【面试复盘】有了equals为什么还要hashcode
java·后端
小周在成长2 小时前
MyBatis 分页插件PageHelper
后端
Paladin_z2 小时前
Easy Query中间件的使用
后端
牛奔2 小时前
Go语言中结构体转Map优雅实现
开发语言·后端·macos·golang·xcode
掘金码甲哥2 小时前
我不允许谁还分不清这三种watch机制的区别
后端
张心独酌3 小时前
Rust新手练习案例库- rust-learning-example
开发语言·后端·rust
码事漫谈3 小时前
一文读懂“本体论”这个时髦词
后端