Eino官方文档入口
官方文档已经非常详细,本文记录在学习Eino框架的一些理解与思考。
传送门:Eino快速开始:https://www.cloudwego.io/zh/docs/eino/quick_start/chapter_01_chatmodel_and_message/
最小对话模型
需求
调用指定模型API,在控制台输入发送给AI的话语,并在控制台流式打印AI的响应。
实现
Eino为以上需求提供了示例代码,下面会基于源码捋一捋思路和流程。
仓库地址:github.com/cloudwego/e...
克隆命令:
shell
git clone https://github.com/cloudwego/eino-examples.git --depth=1
修改模型配置
Eino会默认使用openai的模型,为了方便,我将其修改为智谱旗下的模型(与openai完全兼容),当然,也可以配置其他模型
首先,进入到 eino-examples/quickstart/ 目录, 当前执行目录为 eino-examples ,后续所有相对路径的根目录为 quickstart 。
shell
cd ./quickstart
找到 eino_assistant 目录下的.env文件,添加如下配置, 注意,值无需引号包裹:
.env
export ZHIPU_API_KEY=<your api key>
export ZHIPU_MODEL=<the model name you used>
export ZHIPU_BASE_URL=<官方开放的访问地址,如智谱:https://open.bigmodel.cn/api/paas/v4/>
保存修改,然后在当前控制台执行
shell
source ./quickstart/eino_assistant/.env
这是因为控制台拿到的是系统环境变量的副本,修改了原配置文件,终端私有的环境变量并不会自动更新
接下来验证配置能够正常访问
shell
echo $ZHIPU_MODEL
控制台应该打印你配置的模型名称,如:

然后打开 ./quickstart/chatwitheino/cmd/ch01 目录下的 main.go文件
修改newChatModel函数,使用自定义模型配置
go
func newChatModel(ctx context.Context) (model.ToolCallingChatModel, error) {
if os.Getenv("MODEL_TYPE") == "ark" {
return ark.NewChatModel(ctx, &ark.ChatModelConfig{
APIKey: os.Getenv("ARK_API_KEY"),
Model: os.Getenv("ARK_MODEL"),
BaseURL: os.Getenv("ARK_BASE_URL"),
})
}
return openai.NewChatModel(ctx, &openai.ChatModelConfig{
APIKey: os.Getenv("ZHIPU_API_KEY"),
Model: os.Getenv("ZHIPU_MODEL"),
BaseURL: os.Getenv("ZHIPU_BASE_URL"),
})
}
在学习过程中并未找到MODEL_TYPE的定义位置,直接忽略掉if块,不影响程序运行。
运行项目
最后,执行 ./chatwitheino/cmd/ch01 目录下的main.go
shell
go run ./chatwitheino/cmd/ch01/main.go -- "用一句话解释 Eino 的 Component 设计解决了什么问题?"
应该得到如下输出:

至此,使用eino框架实现一个对话功能就完成了,接下来探究一下源码,完整代码在仓库中,各位小伙伴可以自行获取
原理与思路
宏观流程

源码实现
main
go
var instruction string
flag.StringVar(&instruction, "instruction", "You are a helpful assistant.", "")
flag.Parse()
定义系统级指令,通过命令行参数指定,默认值为"You are a helpful assistant.", 该指令会和用户输入一并发送给模型
go
query := strings.TrimSpace(strings.Join(flag.Args(), " "))
if query == "" {
_, _ = fmt.Fprintln(os.Stderr, "usage: go run ./cmd/ch01 -- \"your question\"")
os.Exit(2)
}
从命令行提取并拼接用户输入,去除首尾空格。假设执行命令为 go run ./chatwitheino/cmd/ch01/main.go -- "你" -- "好 ", 那么query的值为"你 好"
go
ctx := context.Background()
cm, err := newChatModel(ctx)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
创建空的上下文,通过newChatModel函数构建一个chatModel对象,用于后续动作的执行
go
messages := []*schema.Message{
schema.SystemMessage(instruction),
schema.UserMessage(query),
}
将系统级指令和用户的输入封装为一个统一的Message
go
stream, err := cm.Stream(ctx, messages)
Stream方法是一个核心方法,它做了以下工作:
- 构建符合openai/智谱 API的HTTP请求,承载Message
- 通过网络向ZHIPU_BASE_URL指定的端点发出请求
- 构建流式连接,等待模型响应
stream对象是一个流式读取器,类型为*schema.StreamReader[*schema.Message]
这是一个泛型结构体,定义语句是type StreamReader[T any] struct,在此处内部泛型类型被传递成schema.Message
go
for {
frame, err := stream.Recv()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if frame != nil {
_, _ = fmt.Fprint(os.Stdout, frame.Content)
}
}
调用Recv,循环读取部分流,阅读源码,是通过一个channel传输流
这意味着,如果一直没有收到模型的响应,代码会一直阻塞,直到超时。
然后将读取到的内容打印至控制台,直到产生EOF错误,退出循环
注意:EOF错误表示流的所有内容已经读取完毕
newChatModel
go
return openai.NewChatModel(ctx, &openai.ChatModelConfig{
APIKey: os.Getenv("ZHIPU_API_KEY"),
Model: os.Getenv("ZHIPU_MODEL"),
BaseURL: os.Getenv("ZHIPU_BASE_URL"),
})
使用用户定义的模型构造一个chatModel对象,并返回
最后
本文会不定时更新,仍在持续学习中~
如有错误,欢迎指正