零基础 Eino + MCP 项目实战

本文将为读者深入介绍 Eino 与 MCP(Model Context Protocol)的核心概念,并通过实际案例展示如何部署和调用大型语言模型(LLM)。无论是本地部署还是远程调用 LLM,我们都将提供详细的操作指南。

通过一个实用的旅游规划项目,我们将展示如何利用 LLM 技术创建一个能够生成详细酒店住宿方案的智能助手。这个项目不仅演示了 LLM 的强大功能,也为读者提供了一个可复制的模板,以便在自己的应用中实现类似功能。

在本教程中,您将学习如何:

  • 理解 Eino 与 MCP 的基础架构和工作原理
  • 选择并配置适合您需求的 LLM 模型
  • 构建一个能够提供详细酒店住宿信息的交互式系统
  • 优化模型响应以提升用户体验

无论您是 AI 开发新手还是有经验的开发者,本指南都将帮助您快速掌握这些强大工具的使用方法,并应用到实际项目中。

Eino 整体架构

Eino作为字节跳动开源的基于Golang的大模型应用框架,它结合了开源社区中多个优秀的LLM应用开发框架,例如Langchain和LlamaIndex, 提供了一个可扩展性,可靠性和有效性都很强的符合Go语言编程的LLM应用开发框架。

官方文档中有着很详细的实例去介绍Eino的各个组件,这里就不做过多阐述了,详细内容可以参考
Eino: 概述

Eino主框架:
  • Eino: 负责基本的类型和组件抽象定义,系统编排能力等

  • EinoExt组件实现, 提供各种官方支持的工具及原子组件和编排能力等

EinoDev 插件:
Eino 代码实例:

MCP(Model Context Protocol)

MCP 是由Anthropic推出的用于LLM应用和外部数据源或工具之间通信的标准协议。可以把MCP当作AI应用程序的USB-C接口,规范了应用程序应该按照什么样的格式为LLM提供上下文。
 

工作原理

MCP协议采用了一种创新的三层架构设计,将LLM与资源间的通信分为客户端、服务器和资源三个核心组件。

客户端负责向MCP服务器发送请求,而服务器则充当中介,将这些请求精确转发至相应资源。这种分层架构优化了访问控制机制,确保资源只能被授权用户访问,从而提高了整体系统安全性。

MCP协议的工作流程如下:

  • 连接建立:客户端向服务器发起连接请求,建立稳定的通信通道
  • 请求传输:客户端根据特定需求构造请求消息并发送至服务器
  • 请求处理:服务器接收并解析请求,执行相应操作(如数据库查询或文件读取)
  • 结果返回:服务器将处理结果封装为响应消息并回传给客户端
  • 连接终止:任务完成后,客户端可主动关闭连接,或由服务器在超时后自动断开

本质上遵循Client-Server架构,其中包含以下几个核心概念:

  • MCP 主机(MCP Hosts):发起请求的 LLM 应用程序。
  • MCP 客户端(MCP Clients):在主机程序内部,与 MCP server 保持 1:1 的连接。
  • MCP 服务器(MCP Servers):为 MCP client 提供上下文、工具和 prompt 信息。
  • 本地资源(Local Resources):本地计算机中可供 MCP server 安全访问的资源(例如文件、数据库)。
  • 远程资源(Remote Resources):MCP server 可以连接到的远程资源(例如通过 API)。

MCP客户端

MCP客户端就像是一个信使,它代表应用程序向服务器发送请求。它的工作包括:

  • 准备和发送请求
  • 建立与服务器的连接
  • 接收服务器返回的结果
  • 处理可能出现的错误

简单来说,客户端就是"提问方",它向服务器提出需求并等待回应。

MCP服务器

MCP服务器则像是一个管理员,它接收客户端的请求并负责:

  • 检查请求是否有权限访问资源
  • 将请求转发给正确的资源
  • 收集资源的回应
  • 将结果发回给客户端

服务器是"中间人",它确保只有合适的请求才能获取资源,并帮助管理资源的使用。

互动过程
  1. 客户端向服务器说"你好"并提供身份信息
  2. 服务器检查身份并回应"你好"
  3. 客户端发送特定请求,如"我需要某个数据"
  4. 服务器检查权限,找到相应资源
  5. 服务器获取数据并发送回客户端
  6. 完成后,双方结束对话

这种设计就像一个安保严密的图书馆:你(客户端)不能直接拿书(资源),必须通过图书管理员(服务器),他会检查你的借书证,然后帮你找到并提供你需要的书。

目前Claude Desktop 和Cursor都支持了MCP server的接入能力,而且他们也同时作为MCP client提供给用户比较好的调用体验,但对于这两者的上手都需要一定的时间成本,本文将教会大家如何在本地快速搭建一个MCP client。
随着MCP被广泛的认可和使用,有越来越多的MCP server被开源供大家使用,其中不乏一些基础能力,如对filesystem的交互,与git工具的继承等。eg: 官方的MCP Server列表


项目实战

介绍完Eino 以及 MCP,我们便可以开始今天的Demo。在开始之前,需要做以下的准备

环境准备

1. Go 安装

这里以Mac开发环境为例,如果本地有Golang环境则可以跳过该步骤:

brew install go
go version
2. node.js 安装
brew install node
3. 本地部署deepseek model
  1. 准备Ollama环境。 在Ollama官网上下载Ollama, 选择对应的操作系统下载: Download Ollama on macOS 
  2. 下载完成后,双击打开,选择移动到Application中
  3. 打开终端Terminal,输入ollama, 如果显示如下内容表示Ollama已经安装完成了。
  4. 安装模型, 在ollama官网中,选择你想要的模型: Ollama Search. 这里deepseek-r1为例 
  5. 选择适合的model后,本地执行 
    ollama run deepseek-r1:7b

如果出现上述的对话界面则表示安装成功

4. 使用火山方舟大模型

由于本地的存储和内存空间有限,本地运行的模型大部分的表现都很一般,类似于deepseek就有含有不同参数的板板,deepseek-r1:7b只有4GB 但是 deepseek-r1:671b 却有404GB。我们可以尝试去接入完整的model来获得更好的使用体验。
这里采用的是字节跳动的火山模型平台:火山方舟大模型服务平台-火山引擎

注册/登陆后可以快速上手,对于新用户,主流的模型都有50万免费token:
1. 配置API Key
2. 选择合适的Model 并获取Model ID

项目

 完整代码地址:https://github.com/Gilvt-WTJ/LLM

1. 本地新建一个文件目录,并执行 
mkdir eino_mcp & cd eino_mcp
go mod init github.com/mcp_eino/demo
2. 获取相关依赖
go get github.com/cloudwego/eino-ext/components/ollama
go get github.com/mark3labs/mcp-go/client
go get github.com/cloudwego/eino-ext/components/tool/mcp
go get github.com/cloudwego/eino/callbacks
go get github.com/cloudwego/eino-ext/components/model/ark
3. 创建文件目录如下
.
├── agent
│   ├── react_agent.go -> 主逻辑
├── go.mod
├── go.sum
├── hotel_plan.txt -> 存放最终结果
├── main.go
├── mcp_tool
│   └── mcp_tool.go -> 不同MCP server转化成的eino tools
├── model
│   ├── ark.go -> 从方舟平台拿到的模型
│   └── ollama.go -> 通过ollama拿到的本地模型
└── utils
    └── utils.go -> 用于记录LLM model思考的中间过程
4. 获取LLM model

这里有两种形式去拿到该应用的基础模型:本地模型和远程方舟平台

本地获取

import (
	"context"
	"github.com/cloudwego/eino-ext/components/model/ollama"
	"github.com/cloudwego/eino/components/model"
	"log"
)

func GetDeepseekModelFromOllama(ctx context.Context) model.ChatModel {
	chatModel, err := ollama.NewChatModel(ctx, &ollama.ChatModelConfig{
		BaseURL: "http://localhost:11434",
		Model:   "deepseek-r1:7b",
	})
	if err != nil {
		log.Fatalf("create deepseek-r1 model failed: %v", err)
	}
	return chatModel
}

远程获取
需要用户的API key, model region以及Model name,这个参考上面的教程.

import (
	"context"
	"github.com/cloudwego/eino-ext/components/model/ark"
)

func GetDoubaoChatModel(ctx context.Context) *ark.ChatModel {
	model, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{
		APIKey: "API-Key", // How to get API Key: https://www.volcengine.com/docs/82379/1399008#b00dee71
		Region: "cn-beijing",
		Model:  "doubao-1.5-pro-256k-250115", // Model List: https://www.volcengine.com/docs/82379/1330310
	})
	if err != nil {
		panic(err)
	}
	return model
}
5. 调用不同的MCP server

因为任务目标是分析用户意图,从Airbnb那里拿到结果,并把结果写进本地文件中。所以在此过程中,需要能够和Airbnb端进行交互以及和本地文件系统进行交互的能力,因此我们从MCP 官方server中选择了: mcp-server-airbnb and filesystem

import (
	"context"
	"github.com/mark3labs/mcp-go/client"
	"github.com/mark3labs/mcp-go/mcp"
	"time"
)

func GetFileSystemMcpCli(ctx context.Context, filePath string) (*client.StdioMCPClient, error) {
	client, err := client.NewStdioMCPClient(
		"npx",
		[]string{},
		"-y",
		"@modelcontextprotocol/server-filesystem",
		filePath,
	)
	if err != nil {
		return nil, err
	}
	ctx, cancel := context.WithTimeout(ctx, time.Second*10)
	defer cancel()

	initRequest := mcp.InitializeRequest{}
	initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
	initRequest.Params.ClientInfo = mcp.Implementation{
		Name:    "filesystem-client",
		Version: "1.0.0",
	}
	_, err = client.Initialize(ctx, initRequest)
	if err != nil {
		return nil, err
	}
	return client, nil
}

func GetAirbnbMCPCli(ctx context.Context) (*client.StdioMCPClient, error) {
	client, err := client.NewStdioMCPClient(
		"npx",
		[]string{},
		"-y",
		"@openbnb/mcp-server-airbnb",
		"--ignore-robots-txt",
	)
	if err != nil {
		return nil, err
	}
	ctx, cancel := context.WithTimeout(ctx, time.Second*10)
	defer cancel()

	initRequest := mcp.InitializeRequest{}
	initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
	initRequest.Params.ClientInfo = mcp.Implementation{
		Name:    "airbnb-client",
		Version: "1.0.0",
	}
	_, err = client.Initialize(ctx, initRequest)
	if err != nil {
		return nil, err
	}
	return client, nil
}
6. 转换MCP server中的能力成为eino框架中的tools
// 获取MCP Client
airbnbCli, err := mcp_tool.GetAirbnbMCPCli(ctx)
	if err != nil {
		fmt.Printf("failed to get mcp_tool cli: %v", err)
		return nil
	}
// 转换成 tool
abnbTools, err := eino_mcp.GetTools(ctx, &eino_mcp.Config{
		Cli:          airbnbCli,
		ToolNameList: []string{"airbnb_search", "airbnb_listing_details"},
	})
	if err != nil {
		fmt.Printf("failed to list tools: %v", err)
		return nil
	}
// agent config 
agentConfig := &react.AgentConfig{
		Model:   model.GetDoubaoChatModel(ctx),
		MaxStep: 12,
	}
// Assign tool
agentConfig.ToolsConfig.Tools = abnbTools
// Create an agent
agent, err := react.NewAgent(ctx, agentConfig)
	if err != nil {
		fmt.Printf("failed to new agent: %v", err)
		return nil
	}
7. 写用户输入和系统Prompt

这个阶段可以用传统的stream流对话的方式进行,也可以用传统的Stdio方式进行,我们这里拿后者作为事例。
在main.go 中加入以下代码,并运行main.go即可得到完整结果。
比较重要的是 systemPrompt 以及 userInput. 通常用户可以自定义这两者去实现想要的结果。
例如,userInput 现在是: 

我在2025年5月1日到5月7日要去Las Vegas游玩,一共有四个人包括三个大人,一个小孩子,每天的预算是500美元,前三天住一个酒店,后四天住一个酒店。你可以帮我找到合适的住宿么并把结果输出到 hotel_plan.txt中"

如果我现在想要改变时间成为2025-4-1~2025-4-4 有十个人,结果记录在travel_plan.csv中,那么结果也会是截然不同。

import (
	"context"
	"fmt"
	"github.com/cloudwego/eino/callbacks"
	"github.com/cloudwego/eino/schema"
	"github.com/mcp_eino/demo/agent"
	"github.com/mcp_eino/demo/utils"
	"log"
)

func main() {
	ctx := context.Background()
	reactAgent := agent.GetDemoReactAgent(ctx)
	if reactAgent == nil {
		panic("failed to get react agent")
	}
	systemPrompt := `
# 酒店定制专家系统

## 角色
你是一位酒店定制专家,帮助用户根据其需求找到最合适的酒店。拟定每日住宿计划并把结果写到本地文件中。

## 职责
1. 分析用户的酒店需求
2. 提供最多3种完整旅行方案
3. 将推荐结果保存到文件中

## 信息收集
询问用户以下信息:
- 目的地
- 旅行日期(开始和结束日期)
- 预算范围
- 旅客人数

## 推荐方法
1. 分析用户需求
2. 提供最多3种旅行方案
3. 每个方案必须包含以下内容:
   - 方案名称/主题
   - 每天的详细住宿安排(日期+酒店)
   - 总体预算估算

## 每个酒店信息应包括:
- 酒店名称
- 位置
- 星级
- 每晚价格
- 推荐房型
- 2-3项主要设施
- 简短描述(不超过2句话)

## 输出格式
1. **需求摘要**:用户旅行需求概要
2. **方案列表**:最多3种旅行方案,每种方案包含每天的住宿安排
3. **预订建议**:1-2条重要注意事项

## 文件创建
将完整推荐内容保存到本地文件中,格式整洁易读。

## 响应风格
- 专业简洁
- 内容完整但不冗长
- 确保信息准确
`
	callbacks.InitCallbackHandlers([]callbacks.Handler{&utils.LoggerCallbacks{}})
	userInput := "我在2025年5月1日到5月7日要去Las Vegas游玩,一共有四个人包括三个大人,一个小孩子,每天的预算是500美元,前三天住一个酒店,后四天住一个酒店。你可以帮我找到合适的住宿么并把结果输出到 hotel_plan.txt中"
	msg, err := reactAgent.Generate(ctx, []*schema.Message{
		{
			Role:    schema.User,
			Content: userInput,
		},
		{
			Role:    schema.System,
			Content: systemPrompt,
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(msg.Content)
}

结果如此

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值