AI Agent开发(1) - eino框架使用

目录

  • 简介
  • Agent
    • [React Agent](#React Agent)
    • ADK
  • 问题
    • [流式输出tool call的参数拼接问题](#流式输出tool call的参数拼接问题)

背景

  • 最近要在一个线上项目引入Agent技术,因为我们这个项目是golang的,同时python解释性语言在性能、安全性稳定性等方面都比go差太远,所以考虑使用开源的golang Agent框架来进行搭建

简介

https://www.cloudwego.io/zh/docs/eino/overview/eino_open_source/

  • eino是字节推出的LLM应用开发框架,它使用golang作为开发语言,提供了众多的支持LLM应用开发的工具,下面介绍下这个框架的逻辑,以及如何开发一个ai agent,并集成到现有的项目内,以及调用大模型的过程中遇到的一些问题
  • 官网已经给出了一些使用的示例,可以参考https://www.cloudwego.io/zh/docs/eino/quick_start/simple_llm_application/搭建一个简单的LLM聊天功能
  • 框架支持的内容比较丰富,除了支持Agent,还支持Chain、Graph等组件,但我们这次主要说Agent

Agent

  • eino框架提供了两种Agent管理的方案,分别位于flowadk包下

React Agent

go 复制代码
	agt, err := react.NewAgent(ctx, &react.AgentConfig{
		ToolCallingModel: openaiCli, // 可调用的模型
		ToolsConfig: compose.ToolsNodeConfig{
			Tools: todoTools, // 可调用的工具
		},
		MaxStep: 20, // 总交互次数,一次agent + 工具调用 相当于两个step
		MessageRewriter: func(ctx context.Context, input []*schema.Message) []*schema.Message {
			res := make([]*schema.Message, len(input)+1)
			res = append(res, schema.SystemMessage("你是一个xxx")) // 系统提示词
			return res
		},
		MessageModifier: func(ctx context.Context, input []*schema.Message) []*schema.Message {
			// 可进行上下文压缩, 或者存储历史记录
			return input
		},
	})
  • 这种在单agent场景还可以。但是如果要使用多Agent协同的话,最好使用下面的ADK

ADK

  • Agent Development Kit,是eino开发团队,基于Google-ADK的设计,提供的 Go 语言 的 Agents 开发的灵活组合框架,即 Agent、Multi-Agent 开发框架,并为多 Agent 交互场景沉淀了通用的上下文传递、事件流分发和转换、任务控制权转让、中断与恢复、通用切面等能力。
  • 下面是一个简单创建Agent的初始化方法
go 复制代码
	agt, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
		Model: openaiCli,
		ToolsConfig: adk.ToolsConfig{
			ToolsNodeConfig: compose.ToolsNodeConfig{
				Tools: todoTools,
			},
		},
		OutputKey:     "main-agent",
		MaxIterations: 50,
		Name:          "main-agent",
		Description:   "master agent",
		Instruction: `角色:xxx助手
核心职责:专注于xxx。
输出规范:xxx`,
	})
	if err != nil {
		return nil, err
	}
  • 你可能需要多个Agent协同工作,这可以这样实现
go 复制代码
	mAgt, err := adk.SetSubAgents(ctx, agt, subAgts) // 把subAgts加入到agt里面,作为agt的子agent
	if err != nil {
		return nil, fmt.Errorf("set sub agents failed: %w", err)
	}
  • 因为普通的http交互比较简单,下面讲解下如何实现流式输出。首先,我们可以提供一个对外的sse服务,类似下面这样(依赖了"github.com/tmaxmax/go-sse"包,这个包可以实现POST请求建立SSE连接,之前简单看了下https://github.com/r3labs/sse.git这个sse的框架,好像不如上面那个框架好用)
go 复制代码
	sess, err := sse.Upgrade(resp.ResponseWriter, req.Request)
	if err != nil {
		WriteError(req, resp, http.StatusBadRequest, fmt.Errorf("sse upgrade failed: %w", err))
		return
	}
  • 重点是下面的创建stream通信部分,对于adk的runner对象的Stream方法,它是返回了一个AsyncIterator对象,这个对象内部有一个数组对象,且使用了一个Cond对象来保护这个数组(有兴趣可以看代码实现),每次有新的LLM/tool事件生成的时候,数组里面会写入一个AgentEvent事件,所以需要持续监听这个对象,直到任务结束。
go 复制代码
// 主agent的流式消息返回
func (m *MainAgent) Stream(ctx context.Context, input []*schema.Message) *adk.AsyncIterator[*adk.AgentEvent] {
	return adk.NewRunner(ctx, adk.RunnerConfig{
		EnableStreaming: true,
		Agent:           m.agent,
	}).Run(ctx, input)
}

func (a *AgentService) Stream(ctx context.Context, session *sse.Session, cDto *dto.ChatDto) error {
	thisSession := &schema.Message{
		Role:    schema.User,
		Content: cDto.Message,
	}

	next := a.MainAgent.Stream(ctx, []*schema.Message{
		thisSession,
	})

	var cs bytes.Buffer

	for {
		event, ok := next.Next()
		if !ok {
			break
		}
		if event.Err != nil {
			return fmt.Errorf("failed to generate stream: %w", event.Err)
		}
		if event.Output == nil {
			log.Logger.Info("failed to generate stream:", "reason", "no output")
			continue
		}
		if event.Output.MessageOutput == nil {
			log.Logger.Info("failed to generate stream:", "reason", "no message output")
			continue
		}
		if event.Output.MessageOutput.MessageStream == nil {
			log.Logger.Info("failed to generate stream:", "reason", "no message stream")
			continue
		}
		var msg sse.Message

		for {
			ms, msErr := event.Output.MessageOutput.MessageStream.Recv()
			if errors.Is(msErr, io.EOF) {
				break
			}
			if msErr != nil {
				return fmt.Errorf("failed to generate stream: %w", msErr)
			}
			cs.WriteString(ms.Content)
		}
		msg.AppendData(cs.String())
		cs.Reset()

		err := session.Send(&msg) // 注意每次发送这个,会自动换行
		if err != nil {
			log.Logger.Error(err, "agent service send message failed")
			return err
		}
		if err = session.Flush(); err != nil { // 刷新缓冲区,发送数据
			log.Logger.Error(err, "agent service flush message failed")
			return err
		}
	}

	return nil
}
  • 上面是一个完整的流消息事件的交互过程。这里每次交互都是独立的请求,不会保留上下文,需要自行处理

问题

流式输出tool call的参数拼接问题

相关文档和issue如下
https://platform.openai.com/docs/guides/function-calling?api-mode=chat#handling-function-calls

https://github.com/ollama/ollama/issues/7881

  • 观察模型是否有返回index字段。需要有这个,否则无法识别是否是同一个tool的调用

本文介绍了基本使用,由于涉及到细节点非常多,会在后面的文章逐步完善

相关推荐
初九之潜龙勿用2 小时前
GMM NZ 全流程详解实战:FSDP MOE 训练加速
人工智能·pytorch·python
爱看科技2 小时前
亚马逊百亿美元注资OpenAI,微美全息以多模态算力生态抢夺AI模型热潮!
人工智能
架构精进之路2 小时前
一文搞懂什么是 Vibe Coding?
人工智能·后端
奋进的电子工程师2 小时前
AI与网络测试的结合,会碰撞出怎样的火花?
人工智能·信息与通信
SEO_juper2 小时前
你的品牌被AI记住了,还是遗忘了?通过一次快速审计找到答案与策略
人工智能·ai
xhyyvr2 小时前
VR消防安全知识竞赛:“燃”动智慧,“竞”学消防
人工智能·vr·vr消防安全·vr消防安全体验馆
张较瘦_2 小时前
[论文阅读] AI + 硬件开发 | 硬件设计新范式:LLM赋能行为驱动开发,解决验证痛点的实战方案
论文阅读·人工智能·驱动开发
cici158742 小时前
基于高斯混合模型(GMM)的说话人识别系统MATLAB实现
开发语言·人工智能·matlab
10岁的博客2 小时前
AI创新大赛:技术深度与创意碰撞
人工智能