Schema 与消息类型
schema 包定义了 Eino 中所有数据流转的基本类型:对话消息、流式数据、工具调用、文档等。
Message — 对话基本单位
type Message struct {
Role RoleType // system / user / assistant / tool
Content string // 文本内容
ToolCalls []ToolCall // 仅 assistant 消息可包含
// 其他字段:MultiContent、ReasoningContent、ToolCallID 等
}四种 Role 的对话结构
一轮完整的 Tool Calling 对话:
[0] SystemMessage("你是助手") ← 角色设定,放在最前面
[1] UserMessage("今天天气怎么样?") ← 用户提问
[2] AssistantMessage("", [{tool: get_weather, args: {city:"北京"}}]) ← 模型决定调工具
[3] ToolMessage("23°C,晴", "call_abc") ← 工具执行结果,必须紧跟 [2]
[4] AssistantMessage("北京今天23°C,天气晴朗") ← 模型基于工具结果回复🚨
ToolMessage必须紧跟在含ToolCall的AssistantMessage之后,且call_id必须匹配。
构造函数
schema.SystemMessage("You are a helpful assistant.")
schema.UserMessage("今天天气怎么样?")
// 不含工具调用的回复
schema.AssistantMessage("我不知道。", nil)
// 含工具调用的回复
schema.AssistantMessage("", []schema.ToolCall{{
ID: "call_abc",
Function: schema.FunctionCall{Name: "get_weather", Arguments: `{"city":"北京"}`},
}})
// 工具执行结果——注意 call_id 必须与上面 ToolCall.ID 一致
schema.ToolMessage("23°C,晴", "call_abc")ToolCall — LLM 决策调用工具
type ToolCall struct {
ID string // 本次调用的唯一 ID
Function FunctionCall
}
type FunctionCall struct {
Name string // 目标工具名称,必须匹配 ToolInfo.Name
Arguments string // JSON 格式的参数
}只有当 LLM 主动决定调用工具时,Message.ToolCalls 才不为空。此时 Message.Content 通常为空。
ToolCall 生命周期
LLM 收到用户消息
│
├── 决定直接回复
│ → AssistantMessage("回复内容", nil) → END
│
└── 决定调用工具
→ AssistantMessage("", [{ID:"c1", Function:{Name:"search", Arguments:{q:"..."}}}])
│
▼ (框架自动)
查 ToolInfo 找到 search 工具
解析 Arguments JSON → 调用 InvokableRun/StreamableRun
得到结果 "找到 3 条结果: ..."
│
▼ (框架自动)
ToolMessage("找到 3 条结果: ...", "c1") 追加到 Messages
│
▼
LLM 再次推理(看到工具结果)→ 决定继续调工具 或 直接回复StreamReader — 流式迭代器
type StreamReader[T any] struct { ... }
func (sr *StreamReader[T]) Recv() (T, error)
func (sr *StreamReader[T]) Close()| 行为 | 说明 |
|---|---|
Recv() |
阻塞直到下一个 chunk,返回 io.EOF 结束 |
Close() |
必须调用——释放底层 goroutine 和资源 |
stream, _ := model.Stream(ctx, messages)
defer stream.Close() // ← 一定 defer
for {
chunk, err := stream.Recv()
if errors.Is(err, io.EOF) { break }
if err != nil { break }
fmt.Print(chunk.Content)
}🚨 即使提前
break退出循环,defer stream.Close()也会执行(前提是你写了 defer)。
工具方法
// 从数组创建(测试/简单场景)
sr := schema.StreamReaderFromArray([]string{"a", "b", "c"})
// 从 channel 创建
sr := schema.StreamReaderFromChannel(ch)
// 一对多复制(多消费者)
sr1, sr2 := sr.Copy(2) // 两个独立的 ReaderDocument — 知识库文档
type Document struct {
ID string // 唯一标识
Content string // 文档正文
MetaData map[string]any // 元数据:来源、作者、时间、页数等
}RAG 中贯穿全程的数据结构:
Loader → []*Document → Transformer → []*Document → Embedding → Indexer → Vector DB
│
用户问题 → Embedding → Retriever → []*Document → ChatModel(拼接后回答)ToolInfo — 工具的 “说明书”
这是 LLM 判断何时调用工具、如何传参的唯一依据——写清楚至关重要。
type ToolInfo struct {
Name string // 工具名,LLM 通过它指定调哪个
Desc string // 描述,LLM 据此判断是否该调用
ParamsOneOf *ParamsOneOf // 参数 JSON Schema
}ParamsOneOf — 构建参数 Schema
// 简单参数
toolInfo := &schema.ToolInfo{
Name: "calculator",
Desc: "执行四则运算,支持 + - * /",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"expression": {
Type: schema.String,
Desc: "算式,如 '2+3*4'",
Required: true, // 必填
},
}),
}
// 复杂参数:多组参数互斥(OneOf 语义)
toolInfo := &schema.ToolInfo{
Name: "send_notification",
Desc: "发送通知给用户",
ParamsOneOf: schema.NewParamsOneOfByParams(
// 组 A:通过用户 ID
map[string]*schema.ParameterInfo{
"user_id": {Type: schema.String, Desc: "用户 ID", Required: true},
"message": {Type: schema.String, Desc: "通知内容", Required: true},
},
// 组 B:通过邮箱
map[string]*schema.ParameterInfo{
"email": {Type: schema.String, Desc: "邮箱地址", Required: true},
"message": {Type: schema.String, Desc: "通知内容", Required: true},
},
),
}
// LLM 可以选择用 {user_id + message} 或 {email + message}ParameterInfo 字段
| 字段 | 说明 |
|---|---|
Type |
String / Integer / Number / Boolean / Object / Array |
Desc |
参数说明——LLM 据此决定传什么值 |
Required |
是否必填 |
Enum |
限定可选值 |
SubParams |
嵌套对象/数组的子参数 |
💡 框架自动将
ParamsOneOf转为 OpenAI function calling / Claude tool use / Gemini function calling 等各厂商的原生格式。
MessageType 与泛型体系
ADK 使用 [M MessageType] 泛型参数化,支持两种消息类型:
type MessageType interface {
*schema.Message | *schema.AgenticMessage
}| 类型 | 说明 |
|---|---|
*schema.Message |
标准消息,覆盖全部 ADK 特性 |
*schema.AgenticMessage |
v0.9+, 结构化内容块,更灵活的多模态支持 |
类型别名速查
构造时如果不传泛型参数,默认使用 *schema.Message:
| 泛型类型 | 默认别名 |
|---|---|
TypedAgent[*schema.Message] |
Agent |
TypedAgentInput[*schema.Message] |
AgentInput |
TypedAgentEvent[*schema.Message] |
AgentEvent |
TypedAgentOutput[*schema.Message] |
AgentOutput |
TypedMessageVariant[*schema.Message] |
MessageVariant |
TypedChatModelAgent[*schema.Message] |
ChatModelAgent |
TypedRunner[*schema.Message] |
Runner |
// 标准路径(99% 的场景)
agent, _ := adk.NewChatModelAgent(ctx, config) // 自动用 *schema.Message
// 泛型路径(需要结构化消息块时)
agent, _ := adk.NewTypedChatModelAgent[*schema.AgenticMessage](ctx, typedConfig)常见陷阱
| 陷阱 | 说明 |
|---|---|
| 🚨 StreamReader 未 Close | goroutine 泄漏的元凶——一定 defer Close() |
| 🚨 混淆 Role 顺序 | system → user → assistant → tool → assistant,打乱后模型行为异常 |
| 🚨 ToolMessage 忘带 CallID | call_id 必须与前面 ToolCall.ID 一致,否则框架无法关联 |
| 🚨 ToolInfo.Desc 太笼统 | LLM 靠它决定是否调用;“执行操作"不如"查询股票实时价格” |
| 🚨 ParamsOneOf 参数 Schema 不完整 | 缺少 Required 标记 → LLM 可能不传;缺少 Desc → LLM 乱传 |