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的中断和恢复,可以灵活的完成各种场景

相关推荐
树码小子1 小时前
SpringIoC & DI (1):IOC介绍 & Spring IoC使用 & DI
java·后端·spring
墨染青竹梦悠然2 小时前
基于Django+vue的图书借阅管理系统
前端·vue.js·后端·python·django·毕业设计·毕设
怪兽毕设2 小时前
基于Django的洗衣服务平台设计与实现
后端·python·django·洗衣服务平台
程序员泠零澪回家种桔子3 小时前
微服务日志治理:ELK 栈实战指南
后端·elk·微服务·云原生·架构
qq_12498707533 小时前
基于html的书城阅读器系统的设计与实现(源码+论文+部署+安装)
前端·vue.js·spring boot·后端·mysql·信息可视化·html
CodeToGym3 小时前
【全栈进阶】Spring Boot 整合 WebSocket 实战:从实时告警到金融行情推送
java·后端·spring
Leinwin4 小时前
Moltbot 部署至 Azure Web App 完整指南:从本地到云端的安全高效跃迁
后端·python·flask
毕设源码-邱学长4 小时前
【开题答辩全过程】以 基于Springboot个人健康运动系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
愿你天黑有灯下雨有伞4 小时前
Spring Boot + FastExcel:打造完美的导入校验功能
java·spring boot·后端
云霄IT4 小时前
go语言post请求遭遇403反爬解决tls/ja3指纹或Cloudflare防护
开发语言·后端·golang