Prompt 模板:用变量组装发给 AI 的消息
系列「企业级 AI Agent 实现拆解」E14 篇。上一篇 E13 讲了 Indexer——把知识块存进向量数据库。这篇回到 Agent 的核心流程:怎么把用户问题、对话历史、检索到的知识块,拼成一段完整的 prompt 发给 AI。答案是 ChatTemplate。
读完这篇你会知道
ChatTemplate接口是什么:就一个Format()方法- 三种模板语法:FString(
{var})、GoTemplate({{.var}})、Jinja2({{var}})MessagesPlaceholder:怎么把整段对话历史插进 prompt- 如何在 Chain 和 Graph 里用 ChatTemplate
- 一个完整示例:带角色设定、对话历史、用户问题的 prompt 组装
一、为什么要有 Prompt 模板
直接拼字符串不行吗?
// 这样写很快出问题:
prompt := "你是" + role + "。用户问:" + question
问题是:不同的对话需要插入不同的历史记录,有些 prompt 很长,变量位置多,拼接代码杂乱,还容易忘掉哪个变量没传。
ChatTemplate 做的是把 prompt 结构和变量填充分开:你写一次模板,运行时填入变量,得到结构化的消息列表。
二、ChatTemplate 接口
源码在 eino/components/prompt/interface.go:
type ChatTemplate interface {
Format(ctx context.Context, vs map[string]any, opts ...Option) ([]*schema.Message, error)
}
就一个 Format()。
- 传入:变量 map,比如
{"role": "程序员鼓励师", "question": "代码报错了怎么办"} - 返回:
[]*schema.Message,结构化的消息列表,可以直接发给 ChatModel
三、三种模板语法
源码在 eino/schema/message.go,支持三种格式:
type FormatType uint8
const (
FString FormatType = 0 // Python 风格:{variable}
GoTemplate FormatType = 1 // Go 模板:{{.variable}}
Jinja2 FormatType = 2 // Jinja2 风格:{{variable}}
)
实现上三种格式分别用不同的库:
| 格式 | 语法 | 底层实现 |
|---|---|---|
| FString | {name} |
pyfmt 库 |
| GoTemplate | {{.name}} |
Go 标准库 text/template |
| Jinja2 | {{name}} |
eino 内置 Jinja2 实现 |
三种格式,相同的输入,输出完全一样。 选哪种看团队习惯。国内项目用 FString 最多,和 Python 一致;复杂模板(循环、条件判断)选 Jinja2;Go 纯血项目选 GoTemplate。
四、基础用法:构建模板
用 FromMessages() 创建模板:
// 源码:eino/components/prompt/chat_template.go
func FromMessages(formatType schema.FormatType, templates ...schema.MessagesTemplate) *DefaultChatTemplate
举例:
template := prompt.FromMessages(schema.FString,
schema.SystemMessage("你是一个{role},用{style}的语气回答。"),
schema.UserMessage("{question}"),
)
messages, _ := template.Format(ctx, map[string]any{
"role": "技术顾问",
"style": "专业简洁",
"question": "Go 的 goroutine 和线程有什么区别?",
})
// messages[0]: system "你是一个技术顾问,用专业简洁的语气回答。"
// messages[1]: user "Go 的 goroutine 和线程有什么区别?"
注意:模板里有的变量必须在 vs 里传,少一个就报运行时错误。 这是 eino 官方文档里特别说明的"陷阱",没有编译期检查。
五、MessagesPlaceholder:插入整段对话历史
单条消息里的 {var} 替换只适合文字内容。但对话历史是一个消息列表,不是一个字符串。MessagesPlaceholder 就是干这个的:
// 源码:eino/schema/message.go
func MessagesPlaceholder(key string, optional bool) MessagesTemplate
用法:
template := prompt.FromMessages(schema.FString,
schema.SystemMessage("你是一个{role}。"),
schema.MessagesPlaceholder("chat_history", true), // optional=true:没传就跳过
schema.UserMessage("{question}"),
)
messages, _ := template.Format(ctx, map[string]any{
"role": "AI 助手",
"chat_history": []*schema.Message{
schema.UserMessage("你好"),
schema.AssistantMessage("你好!有什么可以帮你的?", nil),
},
"question": "帮我写一个冒泡排序",
})
// 输出顺序:
// system: 你是一个AI助手。
// user: 你好
// assistant: 你好!有什么可以帮你的?
// user: 帮我写一个冒泡排序
optional 参数:
true:chat_history没传,直接跳过,不报错false:chat_history没传,返回 error
多轮对话用 optional=true,第一轮没有历史时不会报错。
六、在 Chain 和 Graph 里用
Chain 用法(最简洁)
// 源码参考:eino-examples/compose/chain/main.go
chain := compose.NewChain[map[string]any, *schema.Message]()
chain.
AppendChatTemplate(prompt.FromMessages(
schema.FString,
schema.SystemMessage("You are a {role}."),
schema.UserMessage("{input}"),
)).
AppendChatModel(chatModel)
output, _ := chain.Invoke(ctx, map[string]any{
"role": "cat",
"input": "你的叫声是怎样的?",
})
// output.Content: "喵喵喵~"
Graph 用法(更灵活)
// 源码参考:eino-examples/compose/graph/simple/graph.go
g := compose.NewGraph[map[string]any, *schema.Message]()
pt := prompt.FromMessages(
schema.FString,
schema.UserMessage("what's the weather in {location}?"),
)
g.AddChatTemplateNode("prompt", pt)
g.AddChatModelNode("model", chatModel)
g.AddEdge(compose.START, "prompt")
g.AddEdge("prompt", "model")
g.AddEdge("model", compose.END)
r, _ := g.Compile(ctx)
ret, _ := r.Invoke(ctx, map[string]any{"location": "beijing"})
七、完整示例:带历史的多轮对话
来自 eino-examples/quickstart/chat/template.go:
func createTemplate() prompt.ChatTemplate {
return prompt.FromMessages(schema.FString,
// 系统提示:定义 AI 的角色
schema.SystemMessage("你是一个{role}。你需要用{style}的语气回答问题。"),
// 对话历史:可选,首轮没有时自动跳过
schema.MessagesPlaceholder("chat_history", true),
// 用户当前问题
schema.UserMessage("问题: {question}"),
)
}
// 使用:
messages, _ := template.Format(ctx, map[string]any{
"role": "程序员鼓励师",
"style": "积极、温暖且专业",
"question": "我的代码一直报错,感觉好沮丧,该怎么办?",
"chat_history": []*schema.Message{
schema.UserMessage("你好"),
schema.AssistantMessage("嘿!我是你的程序员鼓励师!", nil),
},
})
// 发给 ChatModel 的消息序列:
// [system] 你是一个程序员鼓励师。你需要用积极、温暖且专业的语气回答问题。
// [user] 你好
// [assist] 嘿!我是你的程序员鼓励师!
// [user] 问题: 我的代码一直报错,感觉好沮丧,该怎么办?
八、Jinja2 适合复杂 prompt
当 prompt 里需要条件判断或循环时,选 Jinja2:
// 来自 eino-examples/adk/multiagent 的真实示例
var plannerPrompt = prompt.FromMessages(schema.Jinja2,
schema.SystemMessage(`You are an expert planner specializing in Excel data processing.`),
schema.UserMessage(`
User Query: {{ user_query }}
Current Time: {{ current_time }}
File Preview:
{{ file_preview }}
`),
)
msgs, _ := plannerPrompt.Format(ctx, map[string]any{
"user_query": "计算每个产品的平均销售额",
"current_time": time.Now().Format(time.RFC3339),
"file_preview": "列A: 产品名, 列B: 销售额...",
})
九、支持的消息角色
// eino/schema/message.go
const (
System RoleType = "system" // 系统设定
User RoleType = "user" // 用户输入
Assistant RoleType = "assistant" // AI 回复
Tool RoleType = "tool" // 工具调用结果
)
对应的便捷构造函数:
schema.SystemMessage("...")
schema.UserMessage("...")
schema.AssistantMessage("...", toolCalls)
小结
变量 map {"role": "X", "question": "Y", "chat_history": [...]}
↓ ChatTemplate.Format()
├── SystemMessage 替换变量 → system 消息
├── MessagesPlaceholder → 展开为 N 条历史消息
└── UserMessage 替换变量 → user 消息
[]*schema.Message → 直接发给 ChatModel
选哪种模板语法?
| 场景 | 推荐 |
|---|---|
| 简单变量替换,团队熟悉 Python | FString {var} |
| 纯 Go 项目,用标准库 | GoTemplate {{.var}} |
| 需要条件判断 / 循环的复杂 prompt | Jinja2 {{var}} |
ChatTemplate 是 AI Agent 的"嘴"——你描述得越清楚,AI 答得越准。值得认真设计。
更多推荐



所有评论(0)