Eino - 从0到1跑通大模型调用

Eino - 从0到1跑通大模型调用

前言

Eino 是字节跳动开源的 AI 应用开发框架,提供了丰富的大模型组件支持。本文将基于 代码示例,详细介绍如何使用 Eino 框架实现单轮对话、多轮对话、流式输出以及模型参数配置,帮助你从零开始掌握大模型调用,代码链接

一、环境准备

1.1 安装依赖

bash 复制代码
go get github.com/cloudwego/eino@latest
go get github.com/cloudwego/eino-ext/components/model/openai@latest
go get gopkg.in/yaml.v2@latest

1.2 配置文件

创建 config.yml 配置文件:

yaml 复制代码
model:
  base_url: "https://api.minimaxi.com/v1"
  api_key: "your-api-key"
  model_name: "MiniMax-M2.7"
  timeout: 30          # 超时时间(秒)
  temperature: 0.7     # 控制输出随机性,范围 [0.0, 2.0]
  top_p: 0.9          # 核采样参数,范围 [0.0, 1.0]
  max_tokens: 500     # 最大生成token数量

app:
  host: "0.0.0.0"
  port: 8080

二、统一配置管理

为了代码复用,我们定义统一的配置结构体:

go 复制代码
// Config 模型配置
type Config struct {
    Model ModelConfig `yaml:"model"`
    App   AppConfig   `yaml:"app"`
}

// ModelConfig 大模型配置
type ModelConfig struct {
    BaseURL    string  `yaml:"base_url"`
    APIKey     string  `yaml:"api_key"`
    ModelName  string  `yaml:"model_name"`
    Timeout    int     `yaml:"timeout"`
    Temperature float64 `yaml:"temperature"`
    TopP       float64 `yaml:"top_p"`
    MaxTokens  int     `yaml:"max_tokens"`
}

// AppConfig 应用配置
type AppConfig struct {
    Host string `yaml:"host"`
    Port int    `yaml:"port"`
}

// loadConfig 加载配置文件
func loadConfig(configPath string) (*Config, error) {
    data, err := os.ReadFile(configPath)
    if err != nil {
        return nil, fmt.Errorf("读取配置文件失败: %w", err)
    }

    var config Config
    if err := yaml.Unmarshal(data, &config); err != nil {
        return nil, fmt.Errorf("解析配置文件失败: %w", err)
    }

    return &config, nil
}

通过命令行参数指定配置文件路径:

go 复制代码
configPath := flag.String("config", "config.yml", "配置文件路径")
flag.Parse()

三、单轮对话实现

单轮对话是最基础的场景,用户发送一条消息,AI 返回一条响应。

3.1 完整代码

go 复制代码
func main() {
    // 0. 解析命令行参数
    configPath := flag.String("config", "config.yml", "配置文件路径")
    flag.Parse()

    // 1. 加载配置
    cfg, err := loadConfig(*configPath)
    if err != nil {
        log.Fatalf("加载配置失败: %v", err)
    }

    ctx := context.Background()

    // 2. 创建 ChatModel
    chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
        APIKey:  cfg.Model.APIKey,
        Model:   cfg.Model.ModelName,
        BaseURL: cfg.Model.BaseURL,
    })
    if err != nil {
        log.Fatalf("创建失败: %v", err)
    }

    // 3. 构建消息
    messages := []*schema.Message{
        schema.SystemMessage("你是一个懂得哲学的程序员。"),
        schema.UserMessage("什么是存在主义?"),
    }

    // 4. 生成响应
    response, err := chatModel.Generate(ctx, messages)
    if err != nil {
        log.Fatalf("生成失败: %v", err)
    }

    // 5. 输出结果
    fmt.Printf("回答:\n%s\n", response.Content)

    // 6. 输出 Token 使用统计
    if response.ResponseMeta != nil && response.ResponseMeta.Usage != nil {
        fmt.Printf("\nToken 使用统计:\n")
        fmt.Printf("  输入 Token: %d\n", response.ResponseMeta.Usage.PromptTokens)
        fmt.Printf("  输出 Token: %d\n", response.ResponseMeta.Usage.CompletionTokens)
        fmt.Printf("  总计 Token: %d\n", response.ResponseMeta.Usage.TotalTokens)
    }
}

3.2 核心步骤解析

步骤 说明
1. 加载配置 从 YAML 文件读取模型配置
2. 创建 ChatModel 初始化大模型客户端
3. 构建消息 使用 schema.SystemMessageschema.UserMessage 构建对话
4. 生成响应 调用 chatModel.Generate() 获取非流式响应
5. 输出结果 打印 AI 回复内容和 Token 使用统计

四、模型参数配置

不同的应用场景需要不同的参数配置,Eino 提供了丰富的参数支持。

4.1 完整代码

go 复制代码
func main() {
    configPath := flag.String("config", "config.yml", "配置文件路径")
    flag.Parse()
    cfg, err := loadConfig(*configPath)
    if err != nil {
        log.Fatalf("加载配置失败: %v", err)
    }

    ctx := context.Background()

    // 示例1: 基础配置
    basicExample(ctx, cfg)

    // 示例2: 高级配置
    advancedExample(ctx, cfg)

    // 示例3: 创意写作配置
    creativeExample(ctx, cfg)
}

// 基础配置示例
func basicExample(ctx context.Context, cfg *Config) {
    timeout := time.Duration(cfg.Model.Timeout) * time.Second
    if timeout == 0 {
        timeout = 30 * time.Second
    }

    temperature := float32(cfg.Model.Temperature)
    maxTokens := cfg.Model.MaxTokens

    chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
        APIKey:     cfg.Model.APIKey,
        Model:      cfg.Model.ModelName,
        BaseURL:    cfg.Model.BaseURL,
        Timeout:    timeout,
        Temperature: &temperature,
        MaxTokens:   &maxTokens,
    })
    // ...
}

// 高级配置示例 - 精确控制输出
func advancedExample(ctx context.Context, cfg *Config) {
    temperature := float32(0.7)
    topP := float32(0.9)
    maxTokens := 500
    presencePenalty := float32(0.6)
    frequencyPenalty := float32(0.5)

    chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
        APIKey:  cfg.Model.APIKey,
        Model:   cfg.Model.ModelName,
        BaseURL: cfg.Model.BaseURL,
        Timeout: 30 * time.Second,

        // 生成参数
        Temperature: &temperature,
        TopP:        &topP,
        MaxTokens:   &maxTokens,

        // 停止序列 - 遇到这些文本时停止生成
        Stop: []string{"\n\n", "总结:"},

        // 惩罚参数 - 控制重复度
        PresencePenalty:  &presencePenalty,
        FrequencyPenalty: &frequencyPenalty,
    })
    // ...
}

// 创意写作配置 - 高随机性
func creativeExample(ctx context.Context, cfg *Config) {
    temperature := float32(1.2)
    topP := float32(0.95)
    maxTokens := 800
    presencePenalty := float32(0.3)
    frequencyPenalty := float32(0.3)

    chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
        // 高温度设置,适合创意写作
        Temperature: &temperature,
        TopP:        &topP,
        MaxTokens:   &maxTokens,

        // 减少重复惩罚,允许一定的重复
        PresencePenalty:  &presencePenalty,
        FrequencyPenalty: &frequencyPenalty,
    })
    // ...
}

4.2 参数详解

参数 类型 说明 推荐值
Temperature *float32 控制输出随机性,值越高越随机 0.7 (基础)、1.2+ (创意)
TopP *float32 核采样参数,值越低越聚焦 0.9 (基础)、0.95 (创意)
MaxTokens *int 最大生成 token 数量 500 (基础)、800+ (长文本)
PresencePenalty *float32 存在惩罚,鼓励新话题 0.6 (精确)、0.3 (创意)
FrequencyPenalty *float32 频率惩罚,减少重复 0.5 (精确)、0.3 (创意)
Stop []string 停止序列,遇到这些文本停止生成 -

4.3 场景化配置建议

场景 Temperature TopP MaxTokens PresencePenalty FrequencyPenalty
基础问答 0.7 0.9 500 0.6 0.5
技术文档 0.7 0.9 500 0.6 0.5
创意写作 1.2+ 0.95 800+ 0.3 0.3
代码生成 0.3-0.5 0.9 1000+ 0.6 0.5

五、多轮对话实现

多轮对话需要维护对话历史,将 AI 的回复也加入消息列表,实现上下文理解。

5.1 完整代码

go 复制代码
func main() {
    configPath := flag.String("config", "config.yml", "配置文件路径")
    flag.Parse()
    cfg, err := loadConfig(*configPath)
    if err != nil {
        log.Fatalf("加载配置失败: %v", err)
    }

    ctx := context.Background()

    // 创建 ChatModel
    timeout := time.Duration(cfg.Model.Timeout) * time.Second
    if timeout == 0 {
        timeout = 30 * time.Second
    }

    temperature := float32(cfg.Model.Temperature)
    maxTokens := cfg.Model.MaxTokens

    chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
        APIKey:     cfg.Model.APIKey,
        Model:      cfg.Model.ModelName,
        BaseURL:    cfg.Model.BaseURL,
        Timeout:    timeout,
        Temperature: &temperature,
        MaxTokens:   &maxTokens,
    })
    if err != nil {
        log.Fatalf("创建失败: %v", err)
    }

    // 对话历史
    messages := []*schema.Message{
        schema.SystemMessage("你是一个懂得哲学的程序员。"),
    }

    scanner := bufio.NewScanner(os.Stdin)
    fmt.Println("开始对话(输入 'exit' 退出):")

    for {
        fmt.Print("\n你: ")
        if !scanner.Scan() {
            break
        }

        userInput := strings.TrimSpace(scanner.Text())
        if userInput == "exit" {
            fmt.Println("再见!")
            break
        }

        if userInput == "" {
            continue
        }

        // 添加用户消息
        messages = append(messages, schema.UserMessage(userInput))

        // 生成 AI 响应
        response, err := chatModel.Generate(ctx, messages)
        if err != nil {
            log.Printf("生成失败: %v", err)
            continue
        }

        // 添加 AI 响应到历史,实现多轮对话
        messages = append(messages, response)

        fmt.Printf("\nAI: %s\n", response.Content)
    }
}

5.2 核心逻辑

复制代码
┌─────────────────────────────────────────────────────┐
│                    对话循环                          │
├─────────────────────────────────────────────────────┤
│                                                     │
│   用户输入 ──→ 添加到 messages ──→ chatModel.Generate │
│                                                     │
│         ▲                                          │
│         │                                          │
│         │ append(response)                         │
│         │                                          │
│   AI 响应 ◀── messages ◀────────────────────────────┤
│                                                     │
└─────────────────────────────────────────────────────┘

5.3 关键点

  1. 对话历史累积 :每次对话后,将用户消息和 AI 响应都加入 messages 列表
  2. SystemMessage 保持不变:系统提示只在列表开头,不重复添加
  3. 上下文理解:通过传递完整对话历史,AI 能够理解上下文

六、流式输出实现

流式输出让 AI 的回复可以逐字显示,带来更好的用户体验。

6.1 完整代码

go 复制代码
func main() {
    configPath := flag.String("config", "config.yml", "配置文件路径")
    flag.Parse()
    cfg, err := loadConfig(*configPath)
    if err != nil {
        log.Fatalf("加载配置失败: %v", err)
    }

    ctx := context.Background()

    // 创建 ChatModel
    timeout := time.Duration(cfg.Model.Timeout) * time.Second
    if timeout == 0 {
        timeout = 30 * time.Second
    }

    temperature := float32(cfg.Model.Temperature)
    maxTokens := cfg.Model.MaxTokens

    chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
        APIKey:     cfg.Model.APIKey,
        Model:      cfg.Model.ModelName,
        BaseURL:    cfg.Model.BaseURL,
        Timeout:    timeout,
        Temperature: &temperature,
        MaxTokens:   &maxTokens,
    })
    if err != nil {
        log.Fatalf("创建失败: %v", err)
    }

    // 构建消息
    messages := []*schema.Message{
        schema.SystemMessage("你是一个懂得哲学的程序员。"),
        schema.UserMessage("什么是存在主义?"),
    }

    // 流式生成
    stream, err := chatModel.Stream(ctx, messages)
    if err != nil {
        log.Fatalf("流式生成失败: %v", err)
    }
    defer stream.Close() // 记得关闭流

    fmt.Print("AI 回复: ")

    // 逐块接收并打印
    for {
        chunk, err := stream.Recv()
        if err != nil {
            if errors.Is(err, io.EOF) {
                // 流结束
                break
            }
            log.Fatalf("接收失败: %v", err)
        }

        // 打印内容(打字机效果)
        fmt.Print(chunk.Content)
    }

    fmt.Println("\n\n完成!")
}

6.2 流式 vs 非流式

特性 非流式 (Generate) 流式 (Stream)
返回时机 完整响应生成后 逐 token 返回
用户体验 等待时间长 即时响应,打字机效果
代码复杂度 简单 需要处理循环接收
适用场景 短回复、需要完整结果 长回复、实时交互

6.3 流式处理流程

复制代码
chatModel.Stream()
       │
       ▼
   stream.Recv() ──→ chunk.Content ──→ fmt.Print()
       │                            (逐字打印)
       │ chunk
       ▼
   stream.Recv()
       │
       │ io.EOF
       ▼
    结束

七、Token 使用统计

Eino 提供了完整的 Token 使用统计,通过 response.ResponseMeta.Usage 获取:

go 复制代码
if response.ResponseMeta != nil && response.ResponseMeta.Usage != nil {
    fmt.Printf("输入 Token: %d\n", response.ResponseMeta.Usage.PromptTokens)
    fmt.Printf("输出 Token: %d\n", response.ResponseMeta.Usage.CompletionTokens)
    fmt.Printf("总计 Token: %d\n", response.ResponseMeta.Usage.TotalTokens)

    // 缓存 Token(如果有)
    if response.ResponseMeta.Usage.PromptTokenDetails.CachedTokens > 0 {
        fmt.Printf("缓存 Token: %d\n", response.ResponseMeta.Usage.PromptTokenDetails.CachedTokens)
    }
}

八、总结

本文详细介绍了使用 Eino 框架调用大模型的四种核心场景:

场景 方法 特点
单轮对话 chatModel.Generate() 简单直接,一次请求一次响应
参数配置 ChatModelConfig 灵活控制输出质量
多轮对话 维护 messages 历史 上下文理解,连续交互
流式输出 chatModel.Stream() 即时响应,打字机效果

关键配置参数

  • Temperature:控制随机性,创意场景调高,精确场景调低
  • TopP:核采样,与 Temperature 二选一使用
  • MaxTokens:限制输出长度,避免过长回复
  • PresencePenalty / FrequencyPenalty:控制重复内容

运行方式

bash 复制代码
# 单轮对话
go run single_chat.go -config ../../config.yml

# 参数配置示例
go run param_conf.go -config ../../config.yml

# 多轮对话
go run multi_chat.go -config ../../config.yml

# 流式输出
go run stream_chat.go -config ../../config.yml
相关推荐
geovindu10 小时前
go: Strategy Pattern
开发语言·设计模式·golang·策略模式
汤姆yu10 小时前
Harness智能体能力与应用场景
智能体·harness
开发小程序的之朴11 小时前
基于Go语言的企业级CMS系统架构设计与性能分析——以AnQiCMS为例
开发语言·golang·系统架构
初心未改HD12 小时前
Go语言net/http与Web开发:构建高性能HTTP服务
开发语言·golang
m0_6294947313 小时前
LangGraph 构建AI Agent智能体
人工智能·大模型·langgraph
刘大猫.13 小时前
宝马发布全新AI智能座舱助手 能理解用户复杂出行需求
人工智能·算法·机器学习·ai·大模型·算力·ai智能座舱助手
memories19813 小时前
Go 语言 Channel(管道/通道)
开发语言·后端·golang
初心未改HD13 小时前
Go语言结构体Struct:内存布局、标签、接收者与内存对齐
开发语言·golang
jieyucx14 小时前
Go 数据结构入门:线性表、顺序表、链表
数据结构·链表·golang