Agent
在AI应用开发中,Agent(智能体)是一种能够自主决策、执行任务并与环境交互的程序。
- 理解用户需求:分析用户输入的问题或指令
- 制定行动计划:决定需要执行哪些步骤来完成任务
- 调用工具:使用各种工具获取信息或执行操作
- 生成结果:整合所有信息,给出最终答案
Eino框架提供了强大的Agent支持,包括ReAct Agent和Multi-Agent两种主要类型。
1. ReAct
ReAct Agent是基于"推理-行动"循环的智能体,它能够通过思考和行动的交替来解决问题。
ReAct Agent的工作流程如下:
- 接收用户输入:获取用户的问题或指令
- 推理阶段:分析问题,决定是否需要调用工具
- 行动阶段:如果需要,调用相应的工具获取信息
- 整合结果:将工具返回的信息与原有知识结合
- 生成回答:给出最终的答案
- 循环执行:如果问题复杂,可能需要多次"推理-行动"循环
Eino框架中,通过react包提供ReAct模式Agent实现的封装:
func NewAgent(ctx context.Context, config *AgentConfig) (_ *Agent, err error) {
...
}
1
2
3
2
3
配置:
type AgentConfig struct {
// 工具调用chatModel
ToolCallingModel model.ToolCallingChatModel
// 工具节点
ToolsConfig compose.ToolsNodeConfig
// 调用ChatModel之前做提示词修改,一般用于插入上下文,动态添加系统提示词等
MessageModifier MessageModifier
// 最大步数,防止陷入死循环。默认12
MaxStep int `json:"max_step"`
//直接返回结果的工具列表,直接返回工具结果,不会进行下一步思考
ToolReturnDirectly map[string]struct{}
// 自定义流式输出中,是否包含了工具调用信息
StreamToolCallChecker func(ctx context.Context, modelOutput *schema.StreamReader[*schema.Message]) (bool, error)
// 底层是一个Graph,可以设置一个Graph名称
GraphName string
//模型节点名称 默认ChatModel
ModelNodeName string
// 工具节点名称 默认Tools
ToolsNodeName string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1.1 实例
需求:实现一个房产经纪人,通过查询用户的信息,给用户推荐房产
package main
import (
"context"
"fmt"
"github.com/cloudwego/eino-ext/components/model/ollama"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/components/tool/utils"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/flow/agent/react"
"github.com/cloudwego/eino/schema"
)
// 定义工具的输入输出结构
type userInfoRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
type userInfoResponse struct {
Name string `json:"name"`
Email string `json:"email"`
Company string `json:"company"`
Position string `json:"position"`
Salary string `json:"salary"`
}
func main() {
ctx := context.Background()
// 1. 创建模型
model, err := ollama.NewChatModel(ctx, &ollama.ChatModelConfig{
BaseURL: "http://localhost:11434",
Model: "modelscope.cn/Qwen/Qwen3-32B-GGUF:latest",
})
if err != nil {
panic(err)
}
// 2. 创建工具
userInfoTool := utils.NewTool(
&schema.ToolInfo{
Name: "user_info",
Desc: "根据用户的姓名和邮箱,查询用户的公司、职位、薪酬信息,薪酬是月薪,单位人民币",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"name": {
Type: "string",
Desc: "用户的姓名",
},
"email": {
Type: "string",
Desc: "用户的邮箱",
},
}),
},
func(ctx context.Context, input *userInfoRequest) (output *userInfoResponse, err error) {
// 模拟从数据库或其他服务获取用户信息
return &userInfoResponse{
Name: input.Name,
Email: input.Email,
Company: "字节跳动",
Position: "高级工程师",
Salary: "60000",
}, nil
})
// 3. 创建ReAct Agent
agent, err := react.NewAgent(ctx, &react.AgentConfig{
ToolCallingModel: model,
ToolsConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{userInfoTool},
},
MessageModifier: func(ctx context.Context, input []*schema.Message) []*schema.Message {
res := make([]*schema.Message, 0, len(input)+1)
res = append(res, schema.SystemMessage(`
你是一名专业的房产经纪人。你的任务是根据用户的职位和薪酬信息,为其推荐最合适的房产。
请严格遵循以下步骤:
1. 首先,调用 user_info 工具获取用户的详细信息(公司、职位、薪酬)。
2. 然后,根据下方提供的“房产信息”和“购房建议规则”,为用户生成一份个性化的购房建议。
3. 推荐时要明确说明为什么这个房产适合他,比如预算匹配度、通勤便利性、生活品质等。
--- 房产信息 ---
### A. 楼盘列表
**1. 瀚海星辰 (ID: A-01)**
- **区域**: 海淀区-中关村
- **特点**: 顶级学区房, 毗邻多所名校, 周围遍布知名科技公司(如字节跳动、腾讯等)。
- **户型**: 120平米三居室
- **总价**: 约1500万人民币
- **适合人群**: 科技公司高管、重视子女教育的家庭。
**2. 国贸天际 (ID: B-02)**
- **区域**: 朝阳区-国贸CBD
- **特点**: 城市核心地标, 270度落地窗俯瞰CBD夜景, 奢华精装修,顶级商业配套。
- **户型**: 280平米大平层
- **总价**: 约3500万人民币
- **适合人群**: 企业家、公司创始人(CEO/C-level)、金融精英、追求顶级生活品质人士。
**3. 未来之城 (ID: C-03)**
- **区域**: 通州区-城市副中心
- **特点**: 新兴规划区域, 潜力巨大, 环境优美, 配套设施完善, 性价比高。
- **户型**: 140平米四居室
- **总价**: 约800万人民币
- **适合人群**: 在国贸或副中心工作的白领、首次改善型购房家庭。
**4. 文艺 loft (ID: D-04)**
- **区域**: 朝阳区-798艺术区
- **特点**: 设计师风格, 挑高5米, 充满艺术气息, 交通便利。
- **户型**: 60平米复式Loft
- **总价**: 约450万人民币
- **适合人群**: 年轻单身贵族、设计师、创意工作者。
### B. 购房建议规则
1. **预算评估**:
- 房屋总价建议不超过家庭年收入的10倍。
- 月供(按30年商业贷款,利率4%估算)不应超过家庭月收入的50%。
2. **职住平衡**: 推荐的房产区域应与用户公司所在地有较好的通勤关系。例如,在字节跳动工作的高管,优先推荐海淀区的“瀚海星辰”。
3. **身份匹配**: 房产的“适合人群”标签应与用户的职位和身份高度匹配。例如,CEO身份的用户应优先考虑“国贸天际”这类彰显身份的豪宅。
`))
res = append(res, input...)
return res
},
})
if err != nil {
panic(err)
}
// 4. 使用Agent处理用户请求
result, err := agent.Generate(ctx, []*schema.Message{
schema.UserMessage("我叫 zhangsan, 邮箱是 zhangsan@bytedance.com, 帮我推荐一处房产,用中文回答问题"),
})
if err != nil {
panic(err)
}
fmt.Println("Agent回答:", result.Content)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
2. Multi-Agent
当任务变得复杂时,单一Agent可能难以胜任。Multi-Agent系统通过多个专门的Agent协作来解决复杂问题。
Host Multi-Agent采用"主控-专家"模式:
- Host Agent:负责理解用户意图,决定将任务分配给哪个专家
- Specialist Agents:专门处理特定类型任务的专家Agent
在multiagent/host包下面:
func NewMultiAgent(ctx context.C、、。ontext, config *MultiAgentConfig) (*MultiAgent, error) {
....
}
1
2
3
2
3
配置:
type MultiAgentConfig struct {
Host Host // 主控Agent
Specialists []*Specialist //专家Agent
Name string // the name of the host multi-agent
HostNodeName string // 主控节点名称 默认 "host"
//流式输出中的工具调用检查器 判断流式输出中是否存在工具调用
StreamToolCallChecker func(ctx context.Context, modelOutput *schema.StreamReader[*schema.Message]) (bool, error)
// 汇总Agent,将多个专家agent的结果整合提炼,总结成一份最终报告
Summarizer *Summarizer
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
2.1 实例
需求:日记助手,根据用户的输入判断是写日记还是读日记
package main
import (
"bufio"
"context"
"fmt"
"io"
"strings"
"github.com/cloudwego/eino-ext/components/model/ollama"
"github.com/cloudwego/eino/flow/agent"
"github.com/cloudwego/eino/flow/agent/multiagent/host"
"github.com/cloudwego/eino/schema"
)
const (
ollamaBaseURL = "http://localhost:11434"
ollamaModelName = "modelscope.cn/Qwen/Qwen3-32B-GGUF:latest"
)
// 创建Host Agent
func newHost(ctx context.Context) (*host.Host, error) {
chatModel, err := ollama.NewChatModel(ctx, &ollama.ChatModelConfig{
BaseURL: ollamaBaseURL,
Model: ollamaModelName,
})
if err != nil {
return nil, err
}
return &host.Host{
ToolCallingModel: chatModel,
SystemPrompt: "你是一个日记助手,可以帮助用户写日记、读日记。调用提供的",
}, nil
}
// 创建写日记专家
func newWriteJournalSpecialist(ctx context.Context) (*host.Specialist, error) {
chatModel, err := ollama.NewChatModel(ctx, &ollama.ChatModelConfig{
BaseURL: ollamaBaseURL,
Model: ollamaModelName,
})
if err != nil {
return nil, err
}
return &host.Specialist{
ChatModel: chatModel,
SystemPrompt: "请将用户输入的内容写入日记。请勿返回任何内容。",
AgentMeta: host.AgentMeta{
Name: "write_journal",
IntendedUse: "将用户输入的内容写入日记",
},
Invokable: func(ctx context.Context, input []*schema.Message, opts ...agent.AgentOption) (*schema.Message, error) {
return &schema.Message{
Role: schema.Assistant,
Content: "日记已保存",
}, nil
},
Streamable: func(ctx context.Context, input []*schema.Message, opts ...agent.AgentOption) (*schema.StreamReader[*schema.Message], error) {
return &schema.StreamReader[*schema.Message]{}, nil
},
}, nil
}
// 创建读日记专家
func newReadJournalSpecialist(ctx context.Context) (*host.Specialist, error) {
chatModel, err := ollama.NewChatModel(ctx, &ollama.ChatModelConfig{
BaseURL: ollamaBaseURL,
Model: ollamaModelName,
})
if err != nil {
return nil, err
}
return &host.Specialist{
ChatModel: chatModel,
SystemPrompt: "请将日记内容返回给用户。请勿返回任何内容。",
AgentMeta: host.AgentMeta{
Name: "view_journal_content",
IntendedUse: "读取并显示日记内容",
},
Invokable: func(ctx context.Context, input []*schema.Message, opts ...agent.AgentOption) (*schema.Message, error) {
return &schema.Message{
Role: schema.Assistant,
Content: "今天天气很好\n学习了Eino框架\n创建了一个Multi-Agent系统\n",
}, nil
},
Streamable: func(ctx context.Context, input []*schema.Message, opts ...agent.AgentOption) (*schema.StreamReader[*schema.Message], error) {
journal, err := readJournal()
if err != nil {
return nil, err
}
reader, writer := schema.Pipe[*schema.Message](0)
go func() {
scanner := bufio.NewScanner(journal)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
message := &schema.Message{
Role: schema.Assistant,
Content: line + "\n",
}
writer.Send(message, nil)
}
if err := scanner.Err(); err != nil {
writer.Send(nil, err)
}
writer.Close()
}()
return reader, nil
},
}, nil
}
// 模拟读取日记的函数
func readJournal() (io.Reader, error) {
// 实际应用中这里会从文件或数据库读取内容
content := "今天天气很好\n学习了Eino框架\n创建了一个Multi-Agent系统\n"
return strings.NewReader(content), nil
}
func main() {
ctx := context.Background()
// 创建Host和专家Agents
h, err := newHost(ctx)
if err != nil {
panic(err)
}
writer, err := newWriteJournalSpecialist(ctx)
if err != nil {
panic(err)
}
reader, err := newReadJournalSpecialist(ctx)
if err != nil {
panic(err)
}
// 创建Multi-Agent系统
hostMA, err := host.NewMultiAgent(ctx, &host.MultiAgentConfig{
Host: *h,
Specialists: []*host.Specialist{
writer,
reader,
},
})
if err != nil {
panic(err)
}
// 交互式使用示例
fmt.Println("=== 日记助手 ===")
msg := &schema.Message{
Role: schema.User,
Content: "写日志,今天学习了eino框架的multiagent",
}
// 流式获取结果
out, err := hostMA.Generate(ctx, []*schema.Message{msg})
if err != nil {
fmt.Printf("错误: %v\n", err)
}
fmt.Print("助手: ")
fmt.Println(out.Content)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
