【Eino 框架入门】ChatModel 最简示例

5 阅读2分钟

【Eino 框架入门】ChatModel 最简示例

用 Eino 调用大模型的最小代码。核心就是:创建 ChatModel → 构造 messages → 调用 Stream。

核心代码

// 1. 创建 ChatModel
cm, _ := openai.NewChatModel(ctx, &openai.ChatModelConfig{
    APIKey:  cfg.APIKey,
    Model:   cfg.Model,
    BaseURL: cfg.BaseURL,
})

// 2. 构造 messages
messages := []*schema.Message{
    schema.SystemMessage("You are a helpful assistant."),
    schema.UserMessage("Hello!"),
}

// 3. 流式调用
stream, _ := cm.Stream(ctx, messages)
for {
    frame, err := stream.Recv()
    if errors.Is(err, io.EOF) {
        break
    }
    fmt.Print(frame.Content)
}
stream.Close()

三个要点:

  • ChatModel 是接口,不是具体类型 - 业务代码只依赖接口
  • messages[]*schema.Message,角色决定语义
  • Stream() 返回流,Generate() 返回完整回复

Message 角色

schema.SystemMessage("...")    // 系统指令,放最前面
schema.UserMessage("...")      // 用户输入
schema.AssistantMessage("...", nil)  // 模型回复
schema.ToolMessage("...", "call_id") // 工具结果

最简对话:一条 SystemMessage + 一条 UserMessage。

完整代码:交互式聊天

package main

import (
	"bufio"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/cloudwego/eino-ext/components/model/openai"
	"github.com/cloudwego/eino/schema"
)

const systemPrompt = "You are a helpful assistant."

type config struct {
	APIKey  string `json:"api_key"`
	Model   string `json:"model"`
	BaseURL string `json:"base_url,omitempty"`
}

func main() {
	ctx := context.Background()

	cfg, err := loadConfig("config.json")
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	cm, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
		APIKey:  cfg.APIKey,
		Model:   cfg.Model,
		BaseURL: cfg.BaseURL,
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	fmt.Println("Eino Chat (type your question, Ctrl+C to exit)")
	fmt.Println()

	scanner := bufio.NewScanner(os.Stdin)
	for {
		fmt.Print("[user] ")
		if !scanner.Scan() {
			break
		}
		query := strings.TrimSpace(scanner.Text())
		if query == "" {
			continue
		}

		messages := []*schema.Message{
			schema.SystemMessage(systemPrompt),
			schema.UserMessage(query),
		}

		fmt.Print("[assistant] ")
		stream, err := cm.Stream(ctx, messages)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			continue
		}

		for {
			frame, err := stream.Recv()
			if errors.Is(err, io.EOF) {
				break
			}
			if err != nil {
				fmt.Fprintln(os.Stderr, err)
				break
			}
			if frame != nil {
				fmt.Print(frame.Content)
			}
		}
		stream.Close()
		fmt.Println()
		fmt.Println()
	}
}

func loadConfig(path string) (*config, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read config: %w", err)
	}
	var cfg config
	if err := json.Unmarshal(data, &cfg); err != nil {
		return nil, fmt.Errorf("parse config: %w", err)
	}
	if cfg.APIKey == "" {
		return nil, fmt.Errorf("config: api_key is required")
	}
	if cfg.Model == "" {
		return nil, fmt.Errorf("config: model is required")
	}
	return &cfg, nil
}

注意点

for {
    frame, err := stream.Recv()
    if errors.Is(err, io.EOF) {
        break  // 流正常结束,不是错误
    }
    if frame != nil {
        fmt.Print(frame.Content)  // frame 可能为 nil
    }
}
stream.Close()  // 必须关闭
  • frame.Content 是增量片段,不是完整回复
  • io.EOF 是正常结束,err != nil && !errors.Is(err, io.EOF) 才是错误
  • frame 可能为 nil,需要判空
  • 必须调用 Close()

配置

config.json

{
    "api_key": "your-api-key",
    "model": "gpt-4o-mini",
    "base_url": ""
}

base_url 留空用 OpenAI 官方地址。智谱、DeepSeek、SiliconFlow 等兼容 OpenAI 格式的服务商,填对应地址即可。

运行

cd eino-demo/cmd/ch01
go run .
Eino Chat (type your question, Ctrl+C to exit)

[user] 写一个 Go HTTP 服务器
[assistant] package main...

这个示例的局限

每次输入都是新对话,模型记不住上一轮说了什么。要实现多轮对话,需要自己维护 history 并追加到 messages。下一章用 Agent + Runner 会更方便。

参考