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

前言

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

一、环境准备

1.1 安装依赖

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 配置文件:

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

二、统一配置管理

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

// 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
}

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

configPath := flag.String("config", "config.yml", "配置文件路径")
flag.Parse()

三、单轮对话实现

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

3.1 完整代码

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 完整代码

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 完整代码

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 完整代码

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 获取:

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:控制重复内容

运行方式

# 单轮对话
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
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐