AI文章生成 - Eino集成DeepSeek
03 - AI 文章生成 — Eino 集成 DeepSeek
本章目标
- 了解 CloudWeGo Eino 框架的核心抽象
- 创建 DeepSeek ChatModel 并调用
- 设计 System Prompt 模板
- 解析 AI 返回的文章内容(标题 + 标签提取)
- 将 AgentService 注册为 Wails Service
3.1 为什么选择 Eino
在 Go 生态中调用大模型,你有几种选择:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 直接 HTTP 调用 OpenAI 兼容 API | 简单直接 | 需要自己处理重试、流式、上下文管理 |
| 使用模型官方 SDK | 功能完整 | 锁定模型供应商 |
| CloudWeGo Eino | 统一抽象,可切换模型 | 学习曲线稍高 |
Eino 是字节跳动开源的 AI 应用开发框架。它的 ChatModel 接口统一了不同大模型的调用方式:
// Eino 的 ChatModel 接口(简化版)
type BaseChatModel interface {
Generate(ctx context.Context, messages []*Message) (*Message, error)
Stream(ctx context.Context, messages []*Message) (*MessageStream, error)
}
📌 关键概念: 使用 Eino 后,从 DeepSeek 切换到 OpenAI、通义千问、文心一言等,只需要换一个
ChatModel实现,业务代码无需修改。
3.2 添加依赖
go get github.com/cloudwego/eino@latest
go get github.com/cloudwego/eino-ext/components/model/deepseek@latest
查看 go.mod,确认新增了类似以下依赖:
require (
github.com/cloudwego/eino v0.8.4
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20251117090452-bd6375a0b3cf
...
)
3.3 创建 AgentService
创建 backend/agent.go:
package backend
import (
"context"
"fmt"
"github.com/cloudwego/eino-ext/components/model/deepseek"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/schema"
)
请求和响应结构体
// ArtRequest 内容创作请求
type ArtRequest struct {
Topic string `json:"topic"` // 写作主题
PetType string `json:"pet_type"` // 宠物类型(如:猫、狗、兔子等)
Style string `json:"style"` // 写作风格(科普、故事、指南等)
Keywords string `json:"keywords"` // 关键词提示
WordCount int `json:"word_count"` // 目标字数
ImageCount int `json:"image_count"` // 配图数量
}
// ArticleResult 创作结果
type ArticleResult struct {
Title string `json:"title"`
Content string `json:"content"`
SearchTags []string `json:"search_tags"` // AI 提取的关键词
}
AgentService 结构体
// AgentService AI 内容创作服务
type AgentService struct {
configService *ConfigService
}
// NewAgentService 创建 Agent 服务
func NewAgentService(cs *ConfigService) *AgentService {
return &AgentService{configService: cs}
}
3.4 创建 DeepSeek ChatModel
// createChatModel 创建 DeepSeek ChatModel
func (as *AgentService) createChatModel(ctx context.Context) (model.BaseChatModel, error) {
cfg, err := as.configService.Load()
if err != nil {
return nil, fmt.Errorf("加载配置失败: %w", err)
}
if cfg.DeepSeekAPIKey == "" {
return nil, fmt.Errorf("请先配置 DeepSeek API Key")
}
temperature := float32(0.7)
maxTokens := 4096
modelName := cfg.DeepSeekModel
if modelName == "" {
modelName = "deepseek-chat"
}
chatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{
APIKey: cfg.DeepSeekAPIKey,
Model: modelName,
Temperature: temperature,
MaxTokens: maxTokens,
})
if err != nil {
return nil, fmt.Errorf("创建 DeepSeek 模型失败: %w", err)
}
return chatModel, nil
}
参数说明
| 参数 | 说明 | 推荐值 |
|---|---|---|
Temperature |
随机性 0-2,越高越有创意,越低越确定 | 内容创作建议 0.7-0.9 |
MaxTokens |
最大输出 token 数,中英文混合约 1.5 字符/token | 800 字文章设 4096 足够 |
Model |
模型名称 | deepseek-chat(通用)或 deepseek-reasoner(推理) |
3.5 设计 System Prompt
System Prompt 是 AI 创作的灵魂。一个高质量的 Prompt 需要包含:
- 角色设定 — 你是谁
- 任务描述 — 你要做什么
- 约束条件 — 怎么做的规则
- 输出格式 — 以什么结构输出
// buildSystemPrompt 构建系统提示词
func buildSystemPrompt(req ArtRequest) string {
petType := req.PetType
if petType == "" {
petType = "宠物"
}
wordCount := req.WordCount
if wordCount <= 0 {
wordCount = 800
}
style := req.Style
if style == "" {
style = "科普"
}
return fmt.Sprintf(`你是一个专业的今日头条宠物内容创作者,精通宠物知识科普写作。
【写作要求】
- 宠物类型:%s
- 写作风格:%s
- 目标字数:约 %d 字
- 写作风格要求:今日头条风格,标题吸引眼球但真实,内容通俗易懂、专业准确
- 文章结构:引人入胜的标题 → 开篇引入 → 分点科普 → 总结建议
- 语言要求:接地气但不低俗,专业但不晦涩,适合普通宠物主人阅读
【关键词提取要求】
文章写完后,请在文章末尾用 "[TAGS]" 标签附上 3-5 个最适合搜索配图的关键词(英文,用逗号分隔)。
如:[TAGS] golden retriever puppy, pet care, dog training
请确保内容原创、准确、有价值。`, petType, style, wordCount)
}
💡 知识点: Prompt 中的
[TAGS]是一个自定义的结构化标记,用于后续程序化解析 AI 输出。这比让 AI 返回 JSON 更可靠(AI 可能生成格式不正确的 JSON),也比纯文本更易于解析。
3.6 调用模型生成文章
// GenerateArticle 生成文章(前端调用)
func (as *AgentService) GenerateArticle(ctx context.Context, req ArtRequest) (*ArticleResult, error) {
// 1. 创建 ChatModel
chatModel, err := as.createChatModel(ctx)
if err != nil {
return nil, err
}
// 2. 构建消息
messages := []*schema.Message{
{
Role: schema.System,
Content: buildSystemPrompt(req),
},
{
Role: schema.User,
Content: fmt.Sprintf("请根据以上要求,创作一篇关于%s的宠物%s文章。\n主题:%s\n关键词提示:%s",
req.PetType, req.Style, req.Topic, req.Keywords),
},
}
// 3. 调用模型生成
resp, err := chatModel.Generate(ctx, messages)
if err != nil {
return nil, fmt.Errorf("AI 生成失败: %w", err)
}
// 4. 解析结果:分离文章内容和搜索标签
title, content, tags := parseArticleResponse(resp.Content, req.Topic)
return &ArticleResult{
Title: title,
Content: content,
SearchTags: tags,
}, nil
}
消息角色说明
| 角色 | 含义 | 用法 |
|---|---|---|
schema.System |
系统级指令 | 设定 AI 的身份、行为、输出格式 |
schema.User |
用户消息 | 具体的创作请求 |
schema.Assistant |
AI 回复 | 多轮对话中使用(本项目不需要) |
⚠️ 注意: DeepSeek 的 System Prompt 权重很高。如果你写的 System Prompt 不清晰,AI 的输出可能会偏离预期。建议在正式使用前多测试几版 Prompt。
3.7 解析 AI 返回结果
AI 返回的是一整段文本。我们需要从中提取:
- 标题 — 文章第一行(或
#开头的行) - 正文 —
[TAGS]之前的所有内容 - 搜索标签 —
[TAGS]之后逗号分隔的英文关键词
// parseArticleResponse 解析 AI 返回的文章,提取标题、正文和搜索标签
func parseArticleResponse(raw string, fallbackTopic string) (title, content string, tags []string) {
// 查找 [TAGS] 标签
tagsIdx := -1
for i := 0; i < len(raw); i++ {
if i+6 <= len(raw) && raw[i:i+6] == "[TAGS]" {
tagsIdx = i
break
}
}
if tagsIdx >= 0 {
content = raw[:tagsIdx]
tagStr := raw[tagsIdx+6:]
// 按逗号或换行分割标签
for _, t := range splitByComma(tagStr) {
t = trim(t)
if t != "" {
tags = append(tags, t)
}
}
} else {
content = raw
// AI 没有返回标签时,用默认标签兜底
tags = []string{"pet care", "cute pets", "animal health"}
}
// 提取标题
title = extractTitle(content, fallbackTopic)
return
}
标题提取逻辑
func extractTitle(content string, fallback string) string {
lines := splitByNewline(content)
// 优先取 # 开头的行作为标题
for _, line := range lines {
line = trim(line)
if len(line) > 2 && line[0] == '#' {
return trimLeft(line, "# ")
}
}
// 否则取第一个非空行
for _, line := range lines {
line = trim(line)
if line != "" && len(line) > 5 {
return line
}
}
return fallback
}
💡 知识点: 这些字符串处理函数(
splitByComma、splitByNewline、trim、trimLeft)都是手工实现的,没有使用strings包。这是为了展示 Go 的字符串底层操作。实际项目中推荐直接使用strings包。
3.8 注册到 main.go
func main() {
configService := backend.NewConfigService()
agentService := backend.NewAgentService(configService)
app := application.New(application.Options{
Name: "pet-content-creator",
Services: []application.Service{
application.NewService(configService),
application.NewService(agentService),
},
// ...
})
// ...
}
3.9 测试 AI 生成
你可以先写一个快速测试脚本(不经过 UI)来验证 AI 生成效果:
// backend/agent_test.go
package backend
import (
"context"
"testing"
)
func TestGenerateArticle(t *testing.T) {
cs := NewConfigService()
as := NewAgentService(cs)
ctx := context.Background()
req := ArtRequest{
Topic: "如何让猫咪多喝水",
PetType: "猫",
Style: "科普",
WordCount: 500,
}
result, err := as.GenerateArticle(ctx, req)
if err != nil {
t.Fatalf("生成失败: %v", err)
}
t.Logf("标题: %s", result.Title)
t.Logf("标签: %v", result.SearchTags)
t.Logf("内容前 200 字: %s...", result.Content[:200])
}
运行测试:
# 确保配置了 API Key
go test ./backend -run TestGenerateArticle -v
⚠️ 注意: 测试会真实调用 DeepSeek API 并产生费用。建议设较小的 MaxTokens 值。
本章总结
| 你已经学会 | 对应能力 |
|---|---|
| CloudWeGo Eino ChatModel 抽象 | 统一模型调用接口 |
| DeepSeek ChatModel 创建与配置 | 调用国产大模型 |
| System Prompt 设计 | 控制 AI 输出质量 |
[TAGS] 结构化标记 |
可编程解析 AI 输出 |
| 标题 + 标签提取 | 文本后处理 |
🔧 动手练习
- 修改 System Prompt,让 AI 生成不同风格的文章(如小红书风格、知乎风格)
- 将 Temperature 调整为 0.3 和 1.2,对比生成效果
- 增加
[SUMMARY]标记,让 AI 在文章开头生成一段 50 字摘要 - 实现错误重试逻辑:如果 AI 返回了
[TAGS]但标签为空,自动重试一次