Eino 学习第一阶段:新手上路训练手册
📚 手册简介
欢迎来到Eino框架学习的第一阶段!本手册将带你从零开始,完成Eino开发环境的搭建,并亲手实现你的第一个专利代写助手工具——检索关键词生成器。
根据我们之前规划的路线图,第一阶段的目标是:
- ✅ 环境搭建与Hello World
- ✅ 核心组件独立使用(ChatModel/Tool)
- ✅ 简单Chain编排(提示词模板+模型)
按照这个手册,你将在2-3小时内完成全部学习任务,并获得一个可运行的、与专利场景相关的Demo。
第一部分:环境搭建(30分钟)
1.1 前置条件检查
在开始之前,请确保你的开发环境满足以下要求:
| 环境项 | 要求 | 检查命令 |
|---|---|---|
| Go版本 | 1.18或更高 | go version |
| Git | 已安装 | git --version |
| OpenAI API Key | 或兼容的模型服务 | 准备好API密钥 |
💡 提示:如果没有OpenAI账号,也可以使用Ollama本地部署开源模型,Eino同样支持。
1.2 创建项目目录
打开终端,执行以下命令创建你的第一个Eino项目:
# 创建项目目录
mkdir -p ~/projects/patent-agent-learning
cd ~/projects/patent-agent-learning
# 初始化Go module
go mod init patent-agent-learning
# 创建目录结构
mkdir -p cmd/retrieval-keywords
mkdir -p pkg/tools
1.3 安装Eino依赖
Eino框架由核心库和扩展库组成,我们需要添加以下依赖:
# 添加Eino核心库
go get github.com/cloudwego/eino@latest
# 添加Eino扩展组件(模型实现等)
go get github.com/cloudwego/eino-ext@latest
# 添加OpenAI模型支持
go get github.com/cloudwego/eino-ext/components/model/openai
# 如果需要使用Ollama
go get github.com/cloudwego/eino-ext/components/model/ollama
# 整理依赖
go mod tidy
⚠️ 注意:如果遇到
kin-openapi版本冲突,在go.mod中添加以下替换语句:replace github.com/getkin/kin-openapi => github.com/getkin/kin-openapi v0.118.0
1.4 验证安装
创建一个简单的测试文件,验证环境是否正常:
创建cmd/hello/main.go:
package main
import (
"context"
"fmt"
"log"
"github.com/cloudwego/eino/schema"
)
func main() {
fmt.Println("Eino 环境搭建成功!")
// 简单验证schema包可用
msg := schema.UserMessage("你好,Eino!")
log.Printf("创建消息成功: Role=%s, Content=%s", msg.Role, msg.Content)
}
运行测试:
go run cmd/hello/main.go
看到输出表示环境搭建成功。
第二部分:认识Eino核心概念(30分钟)
在开始编码前,我们需要先理解Eino的几个核心概念。可以把Eino想象成一支足球队:
2.1 核心组件(队员)
| 组件名 | 功能类比 | 在我们的专利场景中 |
|---|---|---|
| ChatModel | 中场大脑 | 负责理解发明点、生成检索词 |
| Tool | 边锋/前锋 | 负责执行具体动作(如调用检索API) |
| ChatTemplate | 战术板 | 把用户输入格式化成模型能理解的Prompt |
| Lambda | 自由人 | 自定义函数,处理数据格式转换 |
2.2 消息结构(传球)
Eino中使用schema.Message表示对话消息,每个消息包含:
type Message struct {
Role Role // system/user/assistant/tool
Content string // 消息内容
// ... 其他字段
}
四种角色:
- system:系统指令,定义AI的行为和角色
- user:用户输入
- assistant:模型的回复
- tool:工具调用的结果
2.3 编排方式(战术)
第一阶段我们只关注最简单的编排方式——Chain(链式编排):
chain, _ := NewChain[InputType, OutputType]().
AppendChatTemplate(prompt). // 1. 格式化输入
AppendChatModel(model). // 2. 调用模型
Compile(ctx) // 3. 编译成可执行对象
Chain的特点是数据单向流动,始终向前,非常适合我们第一阶段要实现的关键词生成器。
第三部分:专利检索关键词生成器实战(60分钟)
现在,让我们把理论付诸实践,实现第一个专利场景工具。
3.1 场景定义
需求:输入一段技术交底书中的"发明点描述",自动生成一组专利检索关键词,用于后续的专利检索。
输入示例:
“本发明涉及一种基于深度学习的图像识别方法,特别是一种利用卷积神经网络对医学影像中的肿瘤区域进行自动分割和分类的技术。”
期望输出:
- 核心关键词:深度学习、图像识别、卷积神经网络、医学影像、肿瘤分割、肿瘤分类
- 扩展关键词:CNN、医学图像处理、病灶检测、语义分割
- 分类号:G06T 7/00, G06K 9/00
3.2 创建配置文件
创建.env文件(或直接设置环境变量):
# .env 文件
OPENAI_API_KEY=你的OpenAI密钥
OPENAI_MODEL=gpt-4o # 或 gpt-3.5-turbo
3.3 第一步:独立使用ChatModel(20分钟)
首先,我们学习如何单独使用ChatModel组件,不涉及编排。
创建cmd/retrieval-keywords/simple_chat.go:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/schema"
"github.com/joho/godotenv"
)
func main() {
// 加载环境变量
godotenv.Load()
ctx := context.Background()
// 1. 创建ChatModel实例
model, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
Model: os.Getenv("OPENAI_MODEL"),
APIKey: os.Getenv("OPENAI_API_KEY"),
})
if err != nil {
log.Fatal("创建模型失败:", err)
}
// 2. 构建消息
messages := []*schema.Message{
schema.SystemMessage("你是一位专业的专利检索分析师。你的任务是从技术交底书中提取核心关键词、扩展关键词和可能的IPC分类号。"),
schema.UserMessage(`
请为以下发明点生成专利检索关键词:
发明点:一种基于区块链的供应链金融风控方法,通过智能合约自动验证交易真实性,并基于验证结果动态调整信用额度。
请按以下格式输出:
核心关键词:【逗号分隔】
扩展关键词:【逗号分隔】
建议分类号:【逗号分隔】
`),
}
// 3. 调用Generate方法(非流式)
fmt.Println("正在生成检索关键词...")
result, err := model.Generate(ctx, messages)
if err != nil {
log.Fatal("生成失败:", err)
}
// 4. 输出结果
fmt.Println("\n===== 生成结果 =====")
fmt.Println(result.Content)
}
运行测试:
go run cmd/retrieval-keywords/simple_chat.go
3.4 第二步:引入ChatTemplate(20分钟)
上面的代码把Prompt硬编码在代码中,不利于修改和维护。我们引入ChatTemplate来分离Prompt模板。
创建pkg/template/keyword_templates.go:
package template
import (
"github.com/cloudwego/eino/components/prompt"
"github.com/cloudwego/eino/schema"
)
// NewKeywordTemplate 创建检索关键词生成的Prompt模板
func NewKeywordTemplate() prompt.ChatTemplate {
// 使用FString格式(Python风格)创建模板
return prompt.FromMessages(schema.FString,
// System消息
schema.SystemMessage(`
你是一位专业的{role},擅长{expertise}。
请以{style}的风格输出结果。
`),
// User消息,包含变量占位符
schema.UserMessage(`
请为以下发明点生成专利检索关键词:
发明点:{invention_point}
请按以下格式输出:
核心关键词:【逗号分隔】
扩展关键词:【逗号分隔】
建议分类号:【逗号分隔】
`),
)
}
修改cmd/retrieval-keywords/with_template.go:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/schema"
"github.com/joho/godotenv"
keywordtmpl "patent-agent-learning/pkg/template" // 根据你的模块名调整
)
func main() {
godotenv.Load()
ctx := context.Background()
// 1. 创建模型
model, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
Model: os.Getenv("OPENAI_MODEL"),
APIKey: os.Getenv("OPENAI_API_KEY"),
})
if err != nil {
log.Fatal("创建模型失败:", err)
}
// 2. 创建模板
template := keywordtmpl.NewKeywordTemplate()
// 3. 使用模板格式化消息
messages, err := template.Format(ctx, map[string]any{
"role": "专利检索分析师",
"expertise": "专利检索、关键词提取、IPC分类",
"style": "专业、准确、结构化",
"invention_point": "一种基于深度学习的图像识别方法,特别是一种利用卷积神经网络对医学影像中的肿瘤区域进行自动分割和分类的技术。",
})
if err != nil {
log.Fatal("模板格式化失败:", err)
}
// 4. 打印格式化后的消息,看看模板效果
fmt.Println("===== 发送给模型的消息 =====")
for i, msg := range messages {
fmt.Printf("[%s]: %.100s...\n", msg.Role, msg.Content)
}
// 5. 调用模型
fmt.Println("\n===== 正在生成检索关键词 =====")
result, err := model.Generate(ctx, messages)
if err != nil {
log.Fatal("生成失败:", err)
}
fmt.Println(result.Content)
}
3.5 第三步:Chain编排(20分钟)
现在,我们把"模板格式化"和"模型调用"两个步骤编排成一个Chain,让代码更简洁、更模块化。
创建cmd/retrieval-keywords/with_chain.go:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
"github.com/joho/godotenv"
keywordtmpl "patent-agent-learning/pkg/template"
)
func main() {
godotenv.Load()
ctx := context.Background()
// 1. 创建模型
model, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
Model: os.Getenv("OPENAI_MODEL"),
APIKey: os.Getenv("OPENAI_API_KEY"),
})
if err != nil {
log.Fatal("创建模型失败:", err)
}
// 2. 创建模板
template := keywordtmpl.NewKeywordTemplate()
// 3. 构建Chain:模板 + 模型
// Chain的输入类型是map[string]any(模板变量),输出类型是*schema.Message(模型输出)
chain := compose.NewChain[map[string]any, *schema.Message]()
chain.AppendChatTemplate(template) // 第一步:模板格式化
chain.AppendChatModel(model) // 第二步:模型调用
// 4. 编译Chain
runnable, err := chain.Compile(ctx)
if err != nil {
log.Fatal("编译Chain失败:", err)
}
// 5. 准备输入
input := map[string]any{
"role": "专利检索分析师",
"expertise": "专利检索、关键词提取、IPC分类",
"style": "专业、准确、结构化",
"invention_point": "一种基于区块链的供应链金融风控方法,通过智能合约自动验证交易真实性,并基于验证结果动态调整信用额度。",
}
// 6. 执行Chain
fmt.Println("===== 执行Chain =====")
result, err := runnable.Invoke(ctx, input)
if err != nil {
log.Fatal("Chain执行失败:", err)
}
fmt.Println("\n===== 生成结果 =====")
fmt.Println(result.Content)
// 7. 尝试流式输出
fmt.Println("\n===== 流式输出效果 =====")
streamResult, err := runnable.Stream(ctx, input)
if err != nil {
log.Fatal("流式执行失败:", err)
}
defer streamResult.Close()
fmt.Print("生成中: ")
for {
chunk, err := streamResult.Recv()
if err != nil {
break
}
fmt.Print(chunk.Content)
}
fmt.Println("\n===== 流式结束 =====")
}
3.6 代码解读:Eino Chain的工作原理
让我们深入理解这段代码中Eino做了什么:
// 1. 创建Chain,指定输入输出类型
chain := compose.NewChain[map[string]any, *schema.Message]()
// 2. 添加节点:模板节点接收map[string]any,输出[]*schema.Message
chain.AppendChatTemplate(template)
// 3. 添加节点:模型节点接收[]*schema.Message,输出*schema.Message
chain.AppendChatModel(model)
// 4. 编译:Eino会检查类型是否匹配,并生成可执行对象
runnable, _ := chain.Compile(ctx)
// 5. 调用时,数据自动流经每个节点
result, _ := runnable.Invoke(ctx, input)
// 流程: input(map) -> 模板 -> []Message -> 模型 -> *Message
Chain的三大优势:
- 类型安全:编译时检查输入输出类型是否匹配
- 简洁直观:用
AppendXXX定义执行顺序 - 可复用:编译后的
Runnable可以被多次调用
第四部分:进阶练习与探索(30分钟)
完成上述核心任务后,你可以尝试以下扩展练习:
4.1 练习1:使用Ollama本地模型
如果你想使用本地模型(保护专利数据隐私),尝试改用Ollama:
import "github.com/cloudwego/eino-ext/components/model/ollama"
model, err := ollama.NewChatModel(ctx, &ollama.ChatModelConfig{
BaseURL: "http://localhost:11434",
Model: "llama2", // 或 codellama, mistral 等
})
4.2 练习2:添加简单的工具调用
创建一个模拟的专利检索工具,虽然第一阶段不要求完整实现Tool,但可以体验一下:
type MockSearchTool struct{}
func (m *MockSearchTool) Name() string { return "patent_search" }
func (m *MockSearchTool) Invoke(ctx context.Context, keywords string) (string, error) {
// 模拟检索,返回一些假数据
return fmt.Sprintf("使用关键词 '%s' 检索到 10 篇相关专利", keywords), nil
}
4.3 练习3:探索官方示例
克隆Eino官方示例仓库,运行里面的示例:
git clone https://github.com/cloudwego/eino-examples.git
cd eino-examples/quickstart/chat
go run main.go # 运行官方最简示例
重点关注:
quickstart/chat/- 最基础的对话示例compose/chain/- Chain编排示例flow/agent/react/- ReAct Agent示例(第二阶段会用到)
第五部分:常见问题排查
5.1 依赖安装失败
问题:go get时出现网络超时
解决:设置Go代理
go env -w GOPROXY=https://goproxy.cn,direct
5.2 kin-openapi版本冲突
问题:编译时出现github.com/getkin/kin-openapi相关错误
解决:在go.mod中添加replace指令
replace github.com/getkin/kin-openapi => github.com/getkin/kin-openapi v0.118.0
5.3 模型调用返回空结果
问题:模型返回空字符串或错误 解决:
- 检查API Key是否正确
- 检查模型名称是否支持(gpt-4o/gpt-3.5-turbo)
- 添加错误处理,打印详细错误信息
5.4 模板格式化错误
问题:template.Format返回错误
解决:
- 检查模板中的变量名是否与传入的map key一致
- 确保
MessagesPlaceholder的使用正确
第六部分:阶段成果验收清单
完成本手册后,请对照清单检查是否达成学习目标:
| 任务 | 完成情况 | 备注 |
|---|---|---|
| ✅ 成功安装Go 1.18+和Eino依赖 | □ | |
| ✅ 能独立创建ChatModel并调用Generate | □ | |
| ✅ 理解Message的四种角色 | □ | |
| ✅ 能使用ChatTemplate分离Prompt | □ | |
| ✅ 能构建并执行简单的Chain | □ | |
| ✅ 体验了流式输出Stream | □ | |
| ✅ 运行了专利关键词生成器 | □ | |
| ✅ 尝试了至少一个进阶练习 | □ |
代码成果:你拥有了一个可运行的检索关键词生成器,能够根据发明点描述生成结构化的检索词列表。
知识成果:你理解了Eino的组件抽象、消息结构和Chain编排方式,为第二阶段学习ReAct Agent打下了基础。
下一步:第二阶段预告
恭喜你完成第一阶段的学习!你已经掌握了Eino的基础用法。在第二阶段,我们将:
- 学习ReAct Agent模式:让智能体能够"思考-行动-观察"
- 开发第一个Tool:实现真正的专利检索工具
- 构建"检索分析员"Agent:能自主完成"检索 -> 分析 -> 生成报告"的完整流程
请确保你对本手册的内容有充分理解,特别是Chain编排和ChatTemplate的使用,它们是后续学习的基础。
遇到问题? 参考以下资源:
