万字解读带你跑通 Eino,带你在 2025 打通 Go LLM 应用开发的任督二脉 🎉🎉🎉

2,298 阅读13分钟

Hello,大家好久不见呀。想不到 2025 年刚开始,CloudWeGo 团队就做出了大动作,开源了 Go 生态下第一个由官方维护的 AI 应用开发框架

如果你对我们感兴趣,欢迎加入到 豆包 MarsCode|校园 Campus 与我们一同玩耍呀~

作为一名 Go 开发者,曾经和 LLM 应用开发斗智斗勇的日子

在 24 年中,接手了校内知识库平台的建设,基于校内伙伴们的情况,选择了 Java 生态中 Spring Cloud Alibaba 和 Go 生态中的 HertzKitex 做开发,其中 Java 负责主要业务流程部分的开发,而 Go 更多参与到基础设施与中间件的开发中。 在 AI 的冲击与对信息检索效率的考验下,我们团队开始尝试构建 RAG 流程。而在团队中有 AI 开发经验的也就只有我一人,比较尴尬的一点是,我又不想写 Python,同时对 Java 也没有那么娴熟,还是想使用 Go 来构建整套 RAG 流程。但是在选来选去中,发现 Python 有应用广泛的 LangChainLangGraph 作为坚实的后盾,在 Java 生态中,Spring AI Alibaba 也有着业内顶级大佬阿里的背书,但是作为近几年云原生时代大火的 Go 语言,却缺失了能够站得住跟脚的 AI 应用开发框架,当时在 LangChain-go 的社区徘徊了好久,发现社区中持续向前走的意见并不统一。不过好在在 2025 年,字节跳动开源团队 CloudWeGo 开源出了 Go 生态中第一份由大厂背书的 AI 应用开发框架,真可谓是千呼万唤始出来啊~

第一次见到 Eino 是 24 年底在 GitHub 中看到了 Eino,便在尝试从源码入手,不断尝试这个崭新的框架啦。不过万万没想到,就在短短一段期末考试的时光,Eino 便已经登陆了 CloudWeGo 官网,并且已经有一份相当详细的文档了。那么今天我们就跟随着官方的步伐,去从官方文档中,把这个框架玩起来。

从 Eino 中去看到 Go 生态在 LLM 时代的野心

正可谓是后发先至,由于 Eino 的后发优势,使其在设计时,充分吸纳了 LangChainLangGraph 等 AI 应用开发框架的优势,在更契合 Golang 编程习惯的前提下,提供了更丰富的生态与更优秀的体验。

Eino[‘aino]  (近似音: i know,希望应用程序达到 “i know” 的愿景) 旨在提供基于 Golang 语言的终极大模型应用开发框架。 它从开源社区中的诸多优秀 LLM 应用开发框架,如 LangChain 和 LlamaIndex 等获取灵感,同时借鉴前沿研究成果与实际应用,提供了一个强调简洁性、可扩展性、可靠性与有效性,且更符合 Go 语言编程惯例的 LLM 应用开发框架。

—— 这是 CloudWeGo 团队对其定位作出的描述

Eino 提供的价值如下:

  • 精心整理的一系列 组件(component)  抽象与实现,可轻松复用与组合,用于构建 LLM 应用。
  • 强大的 编排(orchestration)  框架,为用户承担繁重的类型检查、流式处理、并发管理、切面注入、选项赋值等工作。
  • 一套精心设计、注重简洁明了的 API
  • 以集成 流程(flow)  和 示例(example)  形式不断扩充的最佳实践集合。
  • 一套实用 工具(DevOps tools) ,涵盖从可视化开发与调试到在线追踪与评估的整个开发生命周期。

Eino 可在 AI 应用开发周期中的不同阶段,规范、简化和提效:

  • Development: 开箱即用的 AI 相关组件;常见的 Flow 范式;对并发、异步、流式友好的图编排;完善的流处理能力等。这些均可对 AI 应用的开发提供很大助力。
  • Debugging: 可对图编排的应用,进行可视化的开发调试
  • Deployment: 提供丰富的对 AI 应用的评测能力
  • Maintenance: 提供丰富的切面对 AI 应用进行观测、监控

从 Eino 的宏观事业下来看,CloudWeGo 的野心不可谓不大

从宏观角度来看,Eino 从框架学习、开发、测试、部署与观测这六个常见的 AI 应用开发阶段考虑,针对每个阶段的特点,提出了对应的解决方案。

image.png

从样例开始,让每一个 Go LLM 开发者零成本上手

在学习过程中,Eino 在 Eino Examples 中提供了大量易于理解的示例,并且针对常见的 AI 应用场景做了对应的 Demo 供大家学习与参考。

其中针对 Eino 的每一项核心能力都有针对性的示例供大家上手体验与学习,并在 quickstart 很贴心的为想要快速上手的同学提供了完善的示例。

image.png 我们可以看到,CloudWeGo 团队对 Eino 的用户不可谓是不上心了,这可真的是把偏心写在了脸上🙈

从开发角度来讲,Eino 不仅为 AI 应用开发提供了高效的解决方案,更提供了强大的扩展能力

image.png Eino 整体分为三个部分,分别提供了核心能力的抽象、具体实现与开发、调试、评测的管理能力:

  1. Eino: 其实我感觉这部分更贴切的叫法叫做 Eino Core,因为在这一层面,提供了 Eino 核心能力的抽象,其中主要包含以下几个部分:

    • 丰富的 Component 组件
    • Graph、Chain 等图编排能力
    • 丰富的 Stream 流处理机制
    • Callback 高性能切面机制
  2. Eino Ext: 这一部分主要由组件实现、通用切面实现、组件使用示例等,与各种各样的 Eino 扩展能力

  3. Eino Devops: 在这一部分中对应的开发能力、调试能力与评测能力,不过目前还是以期待为主,也希望早日与其见面~

使用 Eino 动手跑通一个最简易的 LLM 应用

不知不觉说了这么多内容了,那下面让我们来看一下 Eino 到底是一个仅仅理论满分的花架子,还是一个文武双全的全能手呢?

下面让我们实际来编写一个最简易的 Prompt 提示词工程来看一下 Eino 的表现吧~

在这个 Demo 中,我们将使用 Eino 来实现一个通过 openAI 的方式调用 DeepSeek 的 prompt 工程项目。

image.png

那么写 Demo 的第一步是什么呢~ 那必然是MarsCode启动!!!

如果你也想随时随地拥有一台 2c4g 的服务器和一个贴心的代码助手,帮你解答在编程中的任何困惑与编写大量重复代码的话,那不妨也来试一下豆包 MarsCode | 云IDE,真的是爱不释手✌️

如果你也是在校大学生,也希望结识到一群志同道合的朋友,亦或是想通过自己的努力帮助到更多对 AI 与编程感兴趣的朋友,那也欢迎你加入到我们豆包 MarsCode | 校园 Campus

环境准备

  1. 项目创建 现在让我们一同在 MarsCode 云 IDE 中创建一个 Go 语言项目 image.png

  2. 引入相关依赖 我们可以在终端通过 go get 命令引入 Eino 相关依赖项

go get github.com/cloudwego/eino
go get github.com/cloudwego/eino-ext

这样就可以在终端与 go.mod 文件中看到我们成功引入了 Eino 相关依赖

image.png

  1. 申请 LLM 模型 API Key 我们在这篇文章中使用当前爆火的国产新兴模型 DeepSeek | 深度求索 我们在其开放平台 Api Key页面,创建一个全新的 Api Key 作为本次 Demo 的 Api Key,关于其调用方式可以参考其接口文档

代码编写

  1. 创建 prompt
package main

import (
	"github.com/cloudwego/eino/components/prompt"
	"github.com/cloudwego/eino/schema"
)

var (
	SystemMessageDefaultTemplate = `你是一个{role}。你需要用{style}的语气回答问题。你的目标是帮助程序员保持积极乐观的心态,提供技术建议的同时也要关注他们的心理健康。`
	UserMessageDefaultTemplate   = `问题: {question}`
) 

func creatPrompt() *prompt.DefaultChatTemplate {
    // 创建模板,使用 FString 格式
    return prompt.FromMessages(schema.FString,
        // 系统消息模板
        schema.SystemMessage(SystemMessageDefaultTemplate),
        
        // 插入示例对话,可选
        schema.MessagesPlaceholder("examples", true),
        
        // 插入对话历史,必须
        schema.MessagesPlaceholder("chat_history", false),
        
        // 用户消息模板
        schema.UserMessage(UserMessageDefaultTemplate),
    )
}
  1. 使用模版生成消息
func getMessage(template *prompt.DefaultChatTemplate, chatHistory, example []*schema.Message, userQuestion string) (result []*schema.Message, err error) {
	// 使用模板生成消息
    messages, err := template.Format(context.Background(), map[string]any{
        "role": "程序员鼓励师",
        "style": "积极、温暖且专业",
        "question": userQuestion,
        // 对话历史(必需的)
        "chat_history": chatHistory,
        // 示例对话(可选的)
        "examples": example,
    })
    if err != nil {
        return nil, err
    }
	return messages, err
}
  1. 创建大模型客户端
func createChatModel(ctx context.Context, apiKey string) (*openai.ChatModel, error) {
	chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
		Model: "deepseek-chat",
		APIKey: apiKey,
		BaseURL: "https://api.deepseek.com",
	})
	if err != nil {
		return nil, err
	}

	return chatModel, nil
}
  1. 完成调用
  • 普通调用
chatHistory := []*schema.Message{}
example := []*schema.Message{
		schema.UserMessage("我觉得自己写的代码太烂了"),
		schema.AssistantMessage("每个程序员都经历过这个阶段!重要的是你在不断学习和进步。让我们一起看看代码,我相信通过重构和优化,它会变得更好。记住,Rome wasn't built in a day,代码质量是通过持续改进来提升的。", nil),
}

for {
	fmt.Print("请输入你的问题:")
	// 读取用户输入
	var userQuestion string
	count, err := fmt.Scanln(&userQuestion)
	if err != nil {
		fmt.Println("读取输入时发生错误:", err)
		return
	}
	if count <= 0 {
		fmt.Println("没有输入任何内容")
		return
	}

	message, err := getMessage(ctx, template, chatHistory, example, userQuestion)
	if err != nil {
		fmt.Println("生成消息时发生错误:", err)
		return
	}
	outMsg, err := chatModel.Generate(ctx, message)
	if err != nil {
		fmt.Println("生成消息时发生错误:", err)
		return
	}

	chatHistory = append(chatHistory, outMsg)
	fmt.Println("AI回复:", outMsg.Content)
}
  • 流式调用
// 实现流式输出
stream, err := chatModel.Stream(ctx, message)
if err != nil {
    fmt.Println("生成消息时发生错误:", err)
    return
 }
defer stream.Close()

for {
    var context string
    response, err := stream.Recv()
    if errors.Is(err, io.EOF) {
        // 将用户的问题和模型的回答添加到聊天历史中
        resp := schema.AssistantMessage(context, nil)
        chatHistory = append(chatHistory, resp)
        fmt.Println()
        break
    }
    if err != nil {
        fmt.Println("接收流式响应时发生错误:", err)
        return
    }
    fmt.Print(response.Content)
    context += response.Content
    }
}

image.png

完整代码

  • 普通调用
package main

import (
	"context"
	"fmt"

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

var (
	ModelType                    = "deepseek-chat"
	OwnerAPIKey                  = "YOUR_API_KEY"
	DeepSeekBaseURL              = "https://api.deepseek.com"
	SystemMessageDefaultTemplate = `你是一个{role}。你需要用{style}的语气回答问题。你的目标是帮助程序员保持积极乐观的心态,提供技术建议的同时也要关注他们的心理健康。`
	UserMessageDefaultTemplate   = `问题: {question}`
)

func main() {
	ctx := context.Background()
        // 创建提示词模版
	template := createPrompt()
        // 创建模型配置
	chatModelConfig := &openai.ChatModelConfig{
		Model:   ModelType,
		APIKey:  OwnerAPIKey,
		BaseURL: DeepSeekBaseURL,
	}
        // 创建模型客户端
	chatModel, err := createChatModel(ctx, chatModelConfig)
	if err != nil {
		fmt.Println("创建聊天模型时发生错误:", err)
		return
	}
        // 构造聊天历史(必须)与示例(可选)
	chatHistory := []*schema.Message{}
	example := []*schema.Message{
		schema.UserMessage("我觉得自己写的代码太烂了"),
		schema.AssistantMessage("每个程序员都经历过这个阶段!重要的是你在不断学习和进步。让我们一起看看代码,我相信通过重构和优化,它会变得更好。记住,Rome wasn't built in a day,代码质量是通过持续改进来提升的。", nil),
	}

	for {
		fmt.Print("请输入你的问题:")
		// 读取用户输入
		var userQuestion string
		count, err := fmt.Scanln(&userQuestion)
		if err != nil {
			fmt.Println("读取输入时发生错误:", err)
			return
		}
		if count <= 0 {
			fmt.Println("没有输入任何内容")
			return
		}

           // 构造消息
		message, err := getMessage(ctx, template, chatHistory, example, userQuestion)
		if err != nil {
			fmt.Println("生成消息时发生错误:", err)
			return
		}
                
           // 调用模型生成答案
		outMsg, err := chatModel.Generate(ctx, message)
		if err != nil {
			fmt.Println("生成消息时发生错误:", err)
			return
		}

            // 将答案追加到聊天历史中
		chatHistory = append(chatHistory, outMsg)
		fmt.Println("AI回复:", outMsg.Content)
	}
}

func createPrompt() *prompt.DefaultChatTemplate {
	// 创建模板,使用 FString 格式
	return prompt.FromMessages(schema.FString,
		// 系统消息模板
		schema.SystemMessage(SystemMessageDefaultTemplate),

		// 插入可选的示例对话
		schema.MessagesPlaceholder("examples", true),

		// 插入必需的对话历史
		schema.MessagesPlaceholder("chat_history", false),

		// 用户消息模板
		schema.UserMessage(UserMessageDefaultTemplate),
	)
}

func getMessage(ctx context.Context, template *prompt.DefaultChatTemplate, chatHistory, example []*schema.Message, userQuestion string) (result []*schema.Message, err error) {
	// 使用模板生成消息
	messages, err := template.Format(ctx, map[string]any{
		"role":     "程序员鼓励师",
		"style":    "积极、温暖且专业",
		"question": userQuestion,
		// 对话历史(必需的)
		"chat_history": chatHistory,
		// 示例对话(可选的)
		"examples": example,
	})
	if err != nil {
		return nil, err
	}

	return messages, err
}

func createChatModel(ctx context.Context, config *openai.ChatModelConfig) (*openai.ChatModel, error) {
	chatModel, err := openai.NewChatModel(ctx, config)
	if err != nil {
		return nil, err
	}

	return chatModel, nil
}
  • 流式调用
package main

import (
	"context"
	"errors"
	"fmt"
	"io"

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

var (
	ModelType                    = "deepseek-chat"
	OwnerAPIKey                  = "YOUR_API_KEY"
	DeepSeekBaseURL              = "https://api.deepseek.com"
	SystemMessageDefaultTemplate = `你是一个{role}。你需要用{style}的语气回答问题。你的目标是帮助程序员保持积极乐观的心态,提供技术建议的同时也要关注他们的心理健康。`
	UserMessageDefaultTemplate   = `问题: {question}`
)

func main() {
	ctx := context.Background()
	template := createPrompt()
	chatModelConfig := &openai.ChatModelConfig{
		Model:   ModelType,
		APIKey:  OwnerAPIKey,
		BaseURL: DeepSeekBaseURL,
	}
	chatModel, err := createChatModel(ctx, chatModelConfig)
	if err != nil {
		fmt.Println("创建聊天模型时发生错误:", err)
		return
	}
	chatHistory := []*schema.Message{}
	example := []*schema.Message{
		schema.UserMessage("我觉得自己写的代码太烂了"),
		schema.AssistantMessage("每个程序员都经历过这个阶段!重要的是你在不断学习和进步。让我们一起看看代码,我相信通过重构和优化,它会变得更好。记住,Rome wasn't built in a day,代码质量是通过持续改进来提升的。", nil),
	}

	for {
		fmt.Print("请输入你的问题:")
		// 读取用户输入
		var userQuestion string
		count, err := fmt.Scanln(&userQuestion)
		if err != nil {
			fmt.Println("读取输入时发生错误:", err)
			return
		}
		if count <= 0 {
			fmt.Println("没有输入任何内容")
			return
		}
		message, err := getMessage(ctx, template, chatHistory, example, userQuestion)
		if err != nil {
			fmt.Println("生成消息时发生错误:", err)
			return
		}

		// 实现流式输出
		stream, err := chatModel.Stream(ctx, message)
		if err != nil {
    		fmt.Println("生成消息时发生错误:", err)
    		return
		}
		defer stream.Close()

		for {
			var context string
	    	response, err := stream.Recv()
    		if errors.Is(err, io.EOF) {
				// 将用户的问题和模型的回答添加到聊天历史中
				resp := schema.AssistantMessage(context, nil)
				chatHistory = append(chatHistory, resp)
        		fmt.Println()
        		break
    		}
    		if err != nil {
        		fmt.Println("接收流式响应时发生错误:", err)
        		return
    		}
    		fmt.Print(response.Content)
    		context += response.Content
		}
	}
}

func createPrompt() *prompt.DefaultChatTemplate {
	// 创建模板,使用 FString 格式
	return prompt.FromMessages(schema.FString,
		// 系统消息模板
		schema.SystemMessage(SystemMessageDefaultTemplate),

		// 插入可选的示例对话
		schema.MessagesPlaceholder("examples", true),

		// 插入必需的对话历史
		schema.MessagesPlaceholder("chat_history", false),

		// 用户消息模板
		schema.UserMessage(UserMessageDefaultTemplate),
	)
}

func getMessage(ctx context.Context, template *prompt.DefaultChatTemplate, chatHistory, example []*schema.Message, userQuestion string) (result []*schema.Message, err error) {
	// 使用模板生成消息
	messages, err := template.Format(ctx, map[string]any{
		"role":     "程序员鼓励师",
		"style":    "积极、温暖且专业",
		"question": userQuestion,
		// 对话历史(必需的)
		"chat_history": chatHistory,
		// 示例对话(可选的)
		"examples": example,
	})
	if err != nil {
		return nil, err
	}

	return messages, err
}

func createChatModel(ctx context.Context, config *openai.ChatModelConfig) (*openai.ChatModel, error) {
	chatModel, err := openai.NewChatModel(ctx, config)
	if err != nil {
		return nil, err
	}

	return chatModel, nil
}

Eino 到底是不是 Go LLM 开发者所期待的 AI 应用开发框架呢?

从个人角度出发,Eino 作为一个刚刚诞生之初的 AI 应用开发框架,贴合 Go 生态且相对丰富的组件与编排能力,以及本篇文章中暂且没有聊到的 Tools 等工具,现在对 Eino 的了解可谓是冰山一角,而其展现的活力已经是十分惊人了,如果后续能够形成一套统一且完善的生态,那也将带领着 Go 在 LLM 领域闯出一番相当不错的天地。同时在当前境遇下,Eino 所独有的可持续性,使我也愿意为 Eino 投入一番经历。

在日后我也将会继续从 Eino 官方示例、架构设计与源码解读等多个方面持续解读 Eino 这一个 Go 生态下崭新的 LLM 应用开发框架

p.s. CloudWeGo 团队是不是该打钱了🤓🤓🤓,还有 DeepSeek🤫🤫🤫

谢谢大家,我是 KingYen.,一名不入流大学的大三计科生,欢迎你的喜欢~