Go語言開發(fā)AI智能體有多絲滑?字節(jié)重磅開源Eino框架,內含保姆級教程
開發(fā)基于大模型的軟件應用,就像指揮一支足球隊:組件是能力各異的隊員,編排是靈活多變的戰(zhàn)術,數(shù)據(jù)是流轉的足球。
Eino 是字節(jié)跳動開源的大模型應用開發(fā)框架,擁有穩(wěn)定的內核,靈活的擴展性,完善的工具生態(tài),可靠且易維護,背靠豆包、抖音等應用的豐富實踐經(jīng)驗。初次使用 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
}
真正的運行,需要的是具體的組件實現(xiàn):
Eino 的開發(fā)過程中,首先要做的是決定 “我需要使用哪個組件抽象”,再決定 “我需要使用哪個具體組件實現(xiàn)”。就像足球隊先決定 “我要上 1 個前鋒”,再挑選 “誰來擔任這個前鋒”。
組件可以像使用任何的 Go interface 一樣單獨使用。但要想發(fā)揮 Eino 這支球隊真正的威力,需要多個組件協(xié)同編排,成為一個相互聯(lián)結的整體。
制定戰(zhàn)術
在 Eino 編排場景中,每個組件成為了 “節(jié)點”(Node),節(jié)點之間 1 對 1 的流轉關系成為了 “邊”(Edge),N 選 1 的流轉關系成為了 “分支”(Branch)?;?Eino 開發(fā)的應用,經(jīng)過對各種組件的靈活編排,就像一支足球隊可以采用各種陣型,能夠支持無限豐富的業(yè)務場景。
足球隊的戰(zhàn)術千變萬化,但卻有跡可循,有的注重控球,有的簡單直接。對 Eino 而言,針對不同的業(yè)務形態(tài),也有更合適的編排方式:
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"})
了解工具
現(xiàn)在想象下你接手的足球隊用了一些黑科技,比如:在每個隊員接球和出球的瞬間,身上的球衣可以自動的記錄接球和出球的速度、角度并傳遞給場邊的服務器,這樣比賽結束后,就可以統(tǒng)計出每個隊員觸球的情況和處理球的時間。
在 Eino 中,每個組件運行的開始和結束,也可以通過 Callbacks 機制拿到輸入輸出及一些額外信息,處理橫切面需求。比如一個簡單的打日志能力:
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))
再想象一下,這個足球隊的黑科技不止一種,還可以讓教練在比賽前制作 “錦囊” 并藏在球衣里,當隊員接球時,這個錦囊就會播放教練事先錄制好的妙計,比如 “別猶豫,直接射門!”。
聽上去很有趣,但有一個難點:有的錦囊是給全隊所有隊員的,有的錦囊是只給一類隊員(比如所有前鋒)的,而有的錦囊甚至是只給單個隊員的。如何有效的做到錦囊妙計的分發(fā)?
在 Eino 中,類似的問題是 graph 運行過程中 call option 的分發(fā):
// 所有節(jié)點都生效的 call option
compiledGraph.Invoke(ctx, input, WithCallbacks(handler))
// 只對特定類型節(jié)點生效的 call option
compiledGraph.Invoke(ctx, input, WithChatModelOption(model.WithTemperature(0.5)))
// 只對特定節(jié)點生效的 call option
compiledGraph.Invoke(ctx, input, WithCallbacks(handler).DesignateNode("node_1"))
發(fā)現(xiàn)獨門秘笈
現(xiàn)在,想象一下你的球隊里有一些明星球員(中場大腦 ChatModel 和鋒線尖刀 StreamableTool)身懷絕技,他們踢出的球速度如此之快,甚至出現(xiàn)了殘影,看上去就像是把一個完整的足球切成了很多片!
面對這樣的 “流式” 足球,對手球員手足無措,不知道該如何接球,但是你的球隊的所有隊員,都能夠完美的接球,要么直接一個片一個片的接收 “流式” 足球并第一時間處理,要么自動的把所有片拼接成完整的足球后再處理。身懷這樣的獨門秘笈,你的球隊具備了面對其他球隊的降維打擊能力!
在 Eino 中,開發(fā)者只需要關注一個組件在 “真實業(yè)務場景” 中,是否可以處理流式的輸入,以及是否可以生成流式的輸出。根據(jù)這個真實的場景,具體的組件實現(xiàn)(包括 Lambda Function)就去實現(xiàn)符合這個流式范式的方法:
// ChatModel 實現(xiàn)了 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 可以實現(xiàn)任意四種流式范式
// 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)
Eino 編排能力會自動做兩個重要的事情:
1. 上游是流,但是下游只能接收非流時,自動拼接(Concat)。
2. 上游是非流,但是下游只能接收流時,自動流化(T -> StreamReader [T])。
除此之外,Eino 編排能力還會自動處理流的合并、復制等各種細節(jié),把大模型應用的核心 —— 流處理做到了極致。
一場訓練賽 -- Eino 智能助手
好了,現(xiàn)在你已經(jīng)初步了解了 Eino 這支明星球隊的主要能力,是時候通過隊員 (組件)、戰(zhàn)術 (編排)、工具 (切面、可視化) 來一場訓練賽,去親自體驗一下它的強大。
場景設定
Eino 智能助手:根據(jù)用戶請求,從知識庫檢索必要的信息并按需調用多種工具,以完成對用戶的請求的處理。工具列表如下:
- DuckDuckGo:從 DuckDuckGo 搜索互聯(lián)網(wǎng)信息
- EinoTool:獲取 Eino 的工程信息,比如倉庫鏈接、文檔鏈接等
- GitClone:克隆指定倉庫到本地
- 任務管理 (TaskManager):添加、查看、刪除 任務
- OpenURL:使用系統(tǒng)的默認應用打開文件、Web 等類型的鏈接
這里呈現(xiàn)一個 Demo 樣例,大家可根據(jù)自己的場景,更換自己的知識庫和工具,以搭建自己所需的智能助手。
先來一起看看基于 Eino 搭建起來的 Agent 助手能實現(xiàn)什么效果:
構建這個 Eino 智能助手分兩步:
- Knowledge Indexing(索引知識庫):將我們在特定領域沉淀的知識,以分詞、向量化等多種手段,構建成索引,以便在接收用戶請求時,索引出合適的上下文。本文采用向量化索引來構建知識庫。
- Eino Agent(Eino 智能助手):根據(jù)用戶的請求信息以及我們預先構建好的可調用的工具,讓 ChatModel 幫我們決策下一步應該執(zhí)行什么動作或輸出最終結果。Tool 的執(zhí)行結果會再次輸入給 ChatModel,讓 ChatModel 再一次判斷下一步的動作,直至完成用戶的請求。
任務工作流
索引知識庫 (Knowledge Indexing)
將 Markdown 格式的 Eino 用戶手冊,以合適的策略進行拆分和向量化,存入到 RedisSearch 的 VectorStore 中,作為 Eino 知識庫。
Eino 智能體 (Eino Agent)
根據(jù)用戶請求,從 Eino 知識庫召回信息,采用 ChatTemplate 構建消息,請求 React Agent,視需求循環(huán)調用對應工具,直至完成處理用戶的請求。
所需工具
在從零開始構建「Eino 智能助手」這個實踐場景中,需要下列工具:
索引知識庫
示例的倉庫路徑:https://github.com/cloudwego/eino-examples/tree/main/quickstart/eino_assistant
下文中,采用相對于此目錄的相對路徑來標識資源位置
構建一個命令行工具,遞歸遍歷指定目錄下的所有 Markdown 文件。按照標題將 Markdown 文件內容分成不同的片段,并采用火山云的豆包向量化模型逐個將文本片段進行向量化,存儲到 Redis VectorStore 中。
指令行工具目錄:cmd/knowledge_indexing
Markdown 文件目錄:cmd/knowledge_indexing/eino-dcos
開發(fā)「索引知識庫」應用時,首先采用 Eino 框架提供的 Goland EinoDev 插件,以可視化拖拽和編排的形式構建 KnowledgeIndexing 的核心應用邏輯,生成代碼到 eino_graph/knowledge_indexing 目錄。
代碼生成后,首先手動將該目錄下的各組件的構造方法補充完整,然后在業(yè)務場景中,調用 BuildKnowledgeIndexing 方法,構建并使用 Eino Graph 實例。
接下來將逐步介紹,KnowledgeIndexing 的開發(fā)過程:
大模型資源創(chuàng)建
火山引擎是字節(jié)跳動的云服務平臺,可從中注冊和調用豆包大模型(有大量免費額度)。
- 創(chuàng)建 doubao-embedding-large 作為知識庫構建時的向量化模型,以及創(chuàng)建 doubao-pro-4k 資源作為 agent 對話時的模型。
- 「火山引擎在線推理」:https://console.volcengine.com/ark
啟動 Redis Stack
本文將使用 Redis 作為 Vector Database,為方便用戶構建環(huán)境,Docker 的快捷指令如下:
- 在 eino-examples/quickstart/eino_assistant 提供 docker-compose.yml
- 在 eino-examples/quickstart/eino_assistant/data 目錄下提供了 Redis 的初始知識庫
直接用 redis 官方的 redis stack 鏡像啟動即可
# 切換到 eino_assistant 目錄
cd xxx/eino-examples/quickstart/eino_assistant
docker-compose up -d
- 完成啟動后,打開本地的 8001 可進入 redis stack 的 web 界面
在瀏覽器打開鏈接:http://127.0.0.1:8001
可視化開發(fā)
「Eino 可視化開發(fā)」是為了降低 Eino AI 應用開發(fā)的學習曲線,提升開發(fā)效率。對于熟悉 Eino 的開發(fā)者,也可選擇跳過「Eino 可視化開發(fā)」階段,直接基于 Eino 的 API 進行全碼開發(fā)。
1. 安裝 EinoDev 插件,并打開 Eino Workflow 功能
- 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
- 其他置空
2. 按照上文「索引知識庫」中的流程說明,從 Eino Workflow 中選擇需要使用的組件庫,本文需要用到如下組件:
- document/loader/file —— 從指定 URI 加載文件,解析成文本內容,以 schema.Document 列表形式返回。
- document/transformer/splitter/markdown —— 將從 FileLoader 中加載到的文本內容,進一步拆分成合適的大小,以平衡向量化計算 / 存儲的尺寸限制和召回的效果。
- indexer/redis —— 將 schema.Document 的原文、索引字段 存儲在 Redis Vector Database 中
- embedding/ark —— 采用 Ark 平臺的向量化模型,對 schema.Document 中的 Content 等內容進行向量化計算
3. 將選中的組件按照預期的拓撲結構進行編排,完成編排后,點擊 “生成代碼” 到指定目錄。
- 「索引知識庫」的代碼生成到:eino_assistant/eino/knowledgeindexing
- 本示例可直接復制 eino/knowledge_indexing.json 中的 Graph Schema,來快速構建示例中的圖
4. 按需完善各個組件的構造函數(shù),在構造函數(shù)中補充創(chuàng)建組件實例時,需要的配置內容
5. 補充好組件的配置內容后,即可調用 BuildKnowledgeIndexing 方法,在業(yè)務場景使用
完善代碼
- 通過可視化開發(fā),生成的 Eino 編排代碼,無法保證可直接使用,需要人工閱讀和檢查下代碼的完整性
- 生成核心函數(shù)是 BuildKnowledgeIndexing (),用戶可在需要的地方調用此方法,創(chuàng)建實例進行使用
在「索引知識庫」的場景下,需要將 BuildKnowledgeIndexing 封裝成一個指令,從環(huán)境變量中讀取模型配置等信息,初始化 BuildKnowledgeIndexing 的配置內容,掃描指定目錄下的 Markdown 文件,執(zhí)行對 Markdown 進行索引和存儲的操作。
詳細代碼可查看:cmd/knowledgeindexing/main.go
運行
PS: 示例項目中,已經(jīng)內置了 eino 的一部分文檔向量化到 redis 中
1. 在 .env 文件中按照注釋說明,獲取并填寫 ARK_EMBEDDING_MODEL 和 ARK_API_KEY 的值,按如下指令,運行 KnowledgeIndexing 指令
cd xxx/eino-examples/quickstart/eino_assistant # 進入 eino assistant 的 example 中
# 修改 .env 中所需的環(huán)境變量 (大模型信息、trace 平臺信息)
source .env
# 因示例的Markdown文件存放在 cmd/knowledgeindexing/eino-docs 目錄,代碼中指定了相對路徑 eino-docs,所以需在 cmd/knowledgeindexing 運行指令
cd cmd/knowledgeindexing
go run main.go
2. 執(zhí)行運行成功后,即完成 Eino 知識庫的構建,可在 Redis Web UI 中看到向量化之后的內容
在瀏覽器打開鏈接:http://127.0.0.1:8001
Eino 智能體
示例的倉庫路徑:https://github.com/cloudwego/eino-examples/tree/main/quickstart/eino_assistant
下文中,采用相對于此目錄的相對路徑來標識資源位置
構建一個基于從 Redis VectorStore 中召回的 Eino 知識回答用戶問題,幫用戶執(zhí)行某些操作的 ReAct Agent,即典型的 RAG ReAct Agent??筛鶕?jù)對話上下文,自動幫用戶記錄任務、Clone 倉庫,打開鏈接等。
大模型資源創(chuàng)建
繼續(xù)使用「索引知識庫」章節(jié)中創(chuàng)建的 doubao-embedding-large 和 doubao-pro-4k
啟動 RedisSearch
繼續(xù)使用「索引知識庫」章節(jié)中啟動的 Redis Stack
可視化開發(fā)
1. 打開 EinoDev 插件,進入到 Eino Workflow 頁面,新建一張畫布
- 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
- 其他置空
2. 按照上文「Eino 智能體」中的流程說明,從 Eino Workflow 中選擇需要使用的組件庫,本文需要用到如下組件:
- lambda: 將開發(fā)者任意的函數(shù) func (ctx context.Context, input I) (output O, err error),轉換成可被編排的節(jié)點,在 EinoAgent 中,有兩個轉換場景:
(1)將 *UserMessage 消息轉換成 ChatTemplate 節(jié)點的 map [string] any
(2)將 *UserMessage 轉換成 RedisRetriever 的輸入 query
- retriever/redis —— 根據(jù)用戶 Query 從 Redis Vector Database 根據(jù)語義相關性,召回和 Query 相關的上下文,以 schema.Document List 的形式返回。
- prompt/chatTemplate —— 通過字符串字面量構建 Prompt 模板,支持 文本替換符 和 消息替換符,將輸入的任意 map [string] any,轉換成可直接輸入給模型的 Message List。
- flow/agent/react —— 基于開發(fā)者提供的 ChatModel 和 可調用的工具集,針對用戶的問題,自動決策下一步的 Action,直至能夠產(chǎn)生最終的回答。
- model/ark —— Ark 平臺提供的能夠進行對話文本補全的大模型,例如豆包模型。作為 ReAct Agent 的依賴注入。
- 可調用的工具列表——互聯(lián)網(wǎng)搜索工具 (DuckDuckGo)、EinoTool、GitClone、任務管理 (TaskManager)、 OpenURL
3. 將選中的組件按照預期的拓撲結構進行編排,完成編排后,點擊 “生成代碼” 到指定目錄。
- 本示例中,「Eino 智能體」的代碼生成到:eino/einoagent
- 本示例可直接復制 eino/eino_agent.json 中的 Graph Schema,來快速構建示例中的圖
4. 按需完善各個組件的構造函數(shù),在構造函數(shù)中補充創(chuàng)建組件實例時,需要的配置內容
5. 補充好組件的配置內容后,即可調用 BuildEinoAgent 方法,在業(yè)務場景使用
完善代碼
在「Eino 智能體」的場景下,BuildEinoAgent 構建的 Graph 實例可做到:根據(jù)用戶請求和對話歷史,從 Eino 知識庫中召回上下文, 然后結合可調用的工具列表,將 ChatModel 循環(huán)決策下一步是調用工具或輸出最終結果。
下圖即是對生成的 BuildEinoAgent 函數(shù)的應用,將 Eino Agent 封裝成 HTTP 服務接口:
運行
1. 在 .env 文件中按照注釋說明,獲取并填寫對應各變量的值,按如下指令,啟動 Eino Agent Server
cd eino-examples/eino_assistant # 進入 eino assistant 的 example 中
# 修改 .env 中所需的環(huán)境變量 (大模型信息、trace 平臺信息)
source .env
# 為了使用 data 目錄,需要在 eino_assistant 目錄下執(zhí)行指令
go run cmd/einoagent/*.go
2. 啟動后可訪問如下鏈接,打開 Eino Agent Web
Eino Agent Web:http://127.0.0.1:8080/agent/
觀測 (可選)
如果在運行時,在 .env 文件中指定了 LANGFUSE_PUBLIC_KEY 和 LANGFUSE_SECRET_KEY,便可在 Langfuse 平臺中,登錄對應的賬號,查看請求的 Trace 詳情。
相關鏈接:
項目地址:https://github.com/cloudwego/eino,https://github.com/cloudwego/eino-ext
Eino 用戶手冊:https://www.cloudwego.io/zh/docs/eino/
項目官網(wǎng):https://www.cloudwego.io