AIxiv专栏是机器之心发布学术、技术内容的栏目。过去数年,机器之心AIxiv专栏接收报道了2000多篇内容,覆盖全球各大高校与企业的顶级实验室,有效促进了学术交流与传播。如果您有优秀的工作想要分享,欢迎投稿或者联系报道。投稿邮箱:liyazhou@jiqizhixin.com;zhaoyunfeng@jiqizhixin.com
开发基于大模型的软件应用,就像指挥一支足球队:组件是能力各异的队员,编排是灵活多变的战术,数据是流转的足球。
Eino 是字节跳动开源的大模型应用开发框架,拥有稳定的内核,灵活的扩展性,完善的工具生态,可靠且易维护,背靠豆包、抖音等应用的丰富实践经验。初次使用 Eino,就像接手一支实力雄厚的足球队,即使教练是初出茅庐的潜力新人,也可以踢出高质量、有内容的比赛。
下面就让我们一起踏上新手上路之旅!
认识队员
Eino 应用的基本构成元素是功能各异的组件,就像足球队由不同位置角色的队员组成:
这些组件抽象代表了固定的输入输出类型、Option 类型和方法签名:
type ChatModel interface {
Generate(ctx context.Context, input []*schema.Message, opts ...Option) (*schema.Message, error)
Stream(ctx context.Context, input []*schema.Message, opts ...Option) (
*schema.StreamReader[*schema.Message], error)
BindTools(tools []*schema.ToolInfo) error
}
真正的运行,需要的是具体的组件实现:
Eino 的开发过程中,首先要做的是决定 “我需要使用哪个组件抽象”,再决定 “我需要使用哪个具体组件实现”。就像足球队先决定 “我要上 1 个前锋”,再挑选 “谁来担任这个前锋”。
组件可以像使用任何的 Go interface 一样单独使用。但要想发挥 Eino 这支球队真正的威力,需要多个组件协同编排,成为一个相互联结的整体。
制定战术
在 Eino 编排场景中,每个组件成为了 “节点”(Node),节点之间 1 对 1 的流转关系成为了 “边”(Edge),N 选 1 的流转关系成为了 “分支”(Branch)。基于 Eino 开发的应用,经过对各种组件的灵活编排,就像一支足球队可以采用各种阵型,能够支持无限丰富的业务场景。
足球队的战术千变万化,但却有迹可循,有的注重控球,有的简单直接。对 Eino 而言,针对不同的业务形态,也有更合适的编排方式:
Chain,如简单的 ChatTemplate + ChatModel 的 Chain:
chain, _ := NewChain[map[string]any, *Message]().
AppendChatTemplate(prompt).
AppendChatModel(model).
Compile(ctx)
chain.Invoke(ctx, map[string]any{"query": "what's your name?"})
Graph,如 ReAct Agent:
graph := NewGraph[map[string]any, *schema.Message]()
_ = graph.AddChatTemplateNode("node_template", chatTpl)
_ = graph.AddChatModelNode("node_model", chatModel)
_ = graph.AddToolsNode("node_tools", toolsNode)
_ = graph.AddLambdaNode("node_converter", takeOne)
_ = graph.AddEdge(START, "node_template")
_ = graph.AddEdge("node_template", "node_model")
_ = graph.AddBranch("node_model", branch)
_ = graph.AddEdge("node_tools", "node_converter")
_ = graph.AddEdge("node_converter", END)
compiledGraph, err := graph.Compile(ctx)
if err != nil {
return err
}
out, err := r.Invoke(ctx, map[string]any{"query":"Beijing's weather this weekend"})
handler := NewHandlerBuilder().
OnStartFn(
func (ctx context.Context, info *RunInfo, input CallbackInput) context.Context {
log.Printf("onStart, runInfo: % v, input: % v", info, input)
return ctx
}).
OnEndFn(
func (ctx context.Context, info *RunInfo, output CallbackOutput) context.Context {
log.Printf("onEnd, runInfo: % v, out: % v", info, output)
return ctx
}).
Build()
// 注入到 graph 运行中
compiledGraph.Invoke(ctx, input, WithCallbacks(handler))
// 所有节点都生效的 call option
compiledGraph.Invoke(ctx, input, WithCallbacks(handler))
// 只对特定类型节点生效的 call option
compiledGraph.Invoke(ctx, input, WithChatModelOption(model.WithTemperature(0.5)))
// 只对特定节点生效的 call option
compiledGraph.Invoke(ctx, input, WithCallbacks(handler).DesignateNode("node_1"))
// ChatModel 实现了 Invoke(输入输出均非流)和 Stream(输入非流,输出流)两个范式
type ChatModel interface {
Generate(ctx context.Context, input []*Message, opts ...Option) (*Message, error)
Stream(ctx context.Context, input []*Message, opts ...Option) (
*schema.StreamReader[*Message], error
)
}
// Lambda 可以实现任意四种流式范式
// Invoke is the type of the invokable lambda function.
type Invoke[I, O, TOption any] func(ctx context.Context, input I, opts ...TOption) (
output O, err error)
// Stream is the type of the streamable lambda function.
type Stream[I, O, TOption any] func(ctx context.Context,
input I, opts ...TOption) (output *schema.StreamReader[O], err error)
// Collect is the type of the collectable lambda function.
type Collect[I, O, TOption any] func(ctx context.Context,
input *schema.StreamReader[I], opts ...TOption) (output O, err error)
// Transform is the type of the transformable lambda function.
type Transform[I, O, TOption any] func(ctx context.Context,
input *schema.StreamReader[I], opts ...TOption) (output *schema.StreamReader[O], err error)
DuckDuckGo:从 DuckDuckGo 搜索互联网信息 EinoTool:获取 Eino 的工程信息,比如仓库链接、文档链接等 GitClone:克隆指定仓库到本地 任务管理 (TaskManager):添加、查看、删除 任务 OpenURL:使用系统的默认应用打开文件、Web 等类型的链接
Knowledge Indexing(索引知识库):将我们在特定领域沉淀的知识,以分词、向量化等多种手段,构建成索引,以便在接收用户请求时,索引出合适的上下文。本文采用向量化索引来构建知识库。 Eino Agent(Eino 智能助手):根据用户的请求信息以及我们预先构建好的可调用的工具,让 ChatModel 帮我们决策下一步应该执行什么动作或输出最终结果。Tool 的执行结果会再次输入给 ChatModel,让 ChatModel 再一次判断下一步的动作,直至完成用户的请求。
示例的仓库路径:https://github.com/cloudwego/eino-examples/tree/main/quickstart/eino_assistant 下文中,采用相对于此目录的相对路径来标识资源位置
指令行工具目录:cmd/knowledge_indexing Markdown 文件目录:cmd/knowledge_indexing/eino-dcos
创建 doubao-embedding-large 作为知识库构建时的向量化模型,以及创建 doubao-pro-4k 资源作为 agent 对话时的模型。 「火山引擎在线推理」:https://console.volcengine.com/ark
在 eino-examples/quickstart/eino_assistant 提供 docker-compose.yml 在 eino-examples/quickstart/eino_assistant/data 目录下提供了 Redis 的初始知识库
# 切换到 eino_assistant 目录
cd xxx/eino-examples/quickstart/eino_assistant
docker-compose up -d
完成启动后,打开本地的 8001 可进入 redis stack 的 web 界面
在浏览器打开链接:http://127.0.0.1:8001
「Eino 可视化开发」是为了降低 Eino AI 应用开发的学习曲线,提升开发效率。对于熟悉 Eino 的开发者,也可选择跳过「Eino 可视化开发」阶段,直接基于 Eino 的 API 进行全码开发。
Graph name: KnowledgeIndexing Node trigger mode: Triggered after all predecessor nodes are executed Input type: document.Source Import path of input type: github.com/cloudwego/eino/components/document Output type: [] string 其他置空
document/loader/file —— 从指定 URI 加载文件,解析成文本内容,以 schema.Document 列表形式返回。 document/transformer/splitter/markdown —— 将从 FileLoader 中加载到的文本内容,进一步拆分成合适的大小,以平衡向量化计算 / 存储的尺寸限制和召回的效果。 indexer/redis —— 将 schema.Document 的原文、索引字段 存储在 Redis Vector Database 中 embedding/ark —— 采用 Ark 平台的向量化模型,对 schema.Document 中的 Content 等内容进行向量化计算
「索引知识库」的代码生成到:eino_assistant/eino/knowledgeindexing 本示例可直接复制 eino/knowledge_indexing.json 中的 Graph Schema,来快速构建示例中的图
通过可视化开发,生成的 Eino 编排代码,无法保证可直接使用,需要人工阅读和检查下代码的完整性 生成核心函数是 BuildKnowledgeIndexing (),用户可在需要的地方调用此方法,创建实例进行使用
详细代码可查看:cmd/knowledgeindexing/main.go
PS: 示例项目中,已经内置了 eino 的一部分文档向量化到 redis 中
cd xxx/eino-examples/quickstart/eino_assistant # 进入 eino assistant 的 example 中
# 修改 .env 中所需的环境变量 (大模型信息、trace 平台信息)
source .env
# 因示例的Markdown文件存放在 cmd/knowledgeindexing/eino-docs 目录,代码中指定了相对路径 eino-docs,所以需在 cmd/knowledgeindexing 运行指令
cd cmd/knowledgeindexing
go run main.go
在浏览器打开链接:http://127.0.0.1:8001
示例的仓库路径:https://github.com/cloudwego/eino-examples/tree/main/quickstart/eino_assistant 下文中,采用相对于此目录的相对路径来标识资源位置
Graph Name: EinoAgent Node Trigger Mode: Triggered after all predecessor nodes are executed Input Type Name: *UserMessage Input Package Path: "" Output Type Name: *schema.Message Output Import Path: github.com/cloudwego/eino/schema 其他置空
lambda: 将开发者任意的函数 func (ctx context.Context, input I) (output O, err error),转换成可被编排的节点,在 EinoAgent 中,有两个转换场景:
retriever/redis —— 根据用户 Query 从 Redis Vector Database 根据语义相关性,召回和 Query 相关的上下文,以 schema.Document List 的形式返回。 prompt/chatTemplate —— 通过字符串字面量构建 Prompt 模板,支持 文本替换符 和 消息替换符,将输入的任意 map [string] any,转换成可直接输入给模型的 Message List。 flow/agent/react —— 基于开发者提供的 ChatModel 和 可调用的工具集,针对用户的问题,自动决策下一步的 Action,直至能够产生最终的回答。 model/ark —— Ark 平台提供的能够进行对话文本补全的大模型,例如豆包模型。作为 ReAct Agent 的依赖注入。 可调用的工具列表——互联网搜索工具 (DuckDuckGo)、EinoTool、GitClone、任务管理 (TaskManager)、 OpenURL
本示例中,「Eino 智能体」的代码生成到:eino/einoagent
本示例可直接复制 eino/eino_agent.json 中的 Graph Schema,来快速构建示例中的图
cd eino-examples/eino_assistant # 进入 eino assistant 的 example 中
# 修改 .env 中所需的环境变量 (大模型信息、trace 平台信息)
source .env
# 为了使用 data 目录,需要在 eino_assistant 目录下执行指令
go run cmd/einoagent/*.go
Eino Agent Web:http://127.0.0.1:8080/agent/