字数 1632,阅读大约需 9 分钟
前言
大家好,我是码一行。
在 AI 应用开发中,文档解析是一个常见的需求,尤其是 PDF 文档的解析。
Eino 作为 Go 语言编写的 LLM 应用开发框架,提供了强大的文档解析能力。
不知道怎么用的,请看历史文章:
本文会分为两大章节:
- 如何解析 PDF
- 如何封装成 MCP
详细介绍如何使用 Eino 框架实现 PDF 解析,并基于 Go 官方的 MCP(Model Context Protocol)库将其封装为 MCP 服务,方便本地调用和集成。
第一章:Eino 如何解析 PDF
依赖库如下:
- github.com/cloudwego/eino-ext/components/document/parser/pdf
- github.com/cloudwego/eino/components/document/parser
- github.com/cloudwego/eino/schema
1. 实现原理
Eino 解析 PDF 的核心原理是:
- 使用 Eino 内置的 PDF 解析组件读取 PDF 文件内容
- 将解析的文本按照排版结构进行智能切割
- 识别章节标题和内容分隔特征
- 将内容组织成结构化的 Document 对象,便于后续处理
2. 实现方案
- 依赖选择 :使用
github.com/cloudwego/eino-ext/components/document/parser/pdf库进行基本 PDF 解析 - 排版切割 :实现
splitByLayout函数,根据章节标题特征进行智能切割 - 章节识别:识别常见的中文文档章节标题,如"教育经历"、"个人优势"、"专业技能"等
- 结果组织 :将切割后的内容组织成多个
schema.Document对象,每个对象对应一个章节
3. 代码实现
go
package parser
import (
"context"
"regexp"
"strings"
"os"
"github.com/cloudwego/eino-ext/components/document/parser/pdf"
"github.com/cloudwego/eino/components/document/parser"
"github.com/cloudwego/eino/schema"
)
func ParserPdf(ctx context.Context, path string, options ...parser.Option) ([]*schema.Document, error) {
parser, _ := pdf.NewPDFParser(ctx, &pdf.Config{
ToPages: false, // 是否按页面分割文档
})
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
// 解析文档
docs, err := parser.Parse(ctx, file, options...)
if err != nil {
return nil, err
}
// 如果解析结果为空,直接返回
if len(docs) == 0 {
return docs, nil
}
// 按照排版结构切割文档
return splitByLayout(docs[0].Content), nil
}
// splitByLayout 按照排版结构切割文本,将内容分割为多个章节
func splitByLayout(content string) []*schema.Document {
// 定义章节标题列表
sectionTitles := []string{
"教育经历", "个人优势", "专业技能", "工作经历", "项目经历",
"教育背景", "个人简介", "专业能力", "实习经历", "工作经验",
"项目经验", "获奖情况", "证书", "自我评价",
}
// 预处理:在章节标题前添加换行符,便于后续分割
processedContent := content
for _, title := range sectionTitles {
processedContent = strings.ReplaceAll(processedContent, title, "\n"+title+"\n")
}
// 定义章节标题正则表达式,匹配可能有换行符的章节标题
sectionRegex := regexp.MustCompile(`(?m)^\s*(教育经历|个人优势|专业技能|工作经历|项目经历|教育背景|个人简介|专业能力|实习经历|工作经验|项目经验|获奖情况|证书|自我评价)\s*$`)
// 查找所有章节标题的位置
matches := sectionRegex.FindAllStringIndex(processedContent, -1)
if len(matches) == 0 {
// 如果没有找到章节标题,返回原始内容
return []*schema.Document{
{
Content: content,
MetaData: map[string]any{"section": "完整内容"},
},
}
}
var result []*schema.Document
// 处理第一个章节之前的内容
if matches[0][0] > 0 {
preContent := strings.TrimSpace(processedContent[:matches[0][0]])
if preContent != "" {
result = append(result, &schema.Document{
Content: preContent,
MetaData: map[string]any{"section": "头部信息"},
})
}
}
// 处理每个章节
for i, match := range matches {
sectionTitle := processedContent[match[0]:match[1]]
var sectionContent string
// 确定章节内容的结束位置
if i < len(matches)-1 {
// 不是最后一个章节,结束位置是下一个章节的开始
sectionContent = strings.TrimSpace(processedContent[match[1]:matches[i+1][0]])
} else {
// 最后一个章节,结束位置是文本末尾
sectionContent = strings.TrimSpace(processedContent[match[1]:])
}
// 添加章节到结果中
result = append(result, &schema.Document{
Content: sectionContent,
MetaData: map[string]any{"section": sectionTitle},
})
}
return result
}
第二章:Eino 如何封装 MCP
依赖库如下:
1. 实现原理
Eino 封装 MCP 的核心原理是:
- 注册 PDF 解析工具到 MCP 服务
- 处理 MCP 请求,调用相应的工具实现
- 返回符合 MCP 协议的响应
2. 实现方案
- 依赖引入:引入 Go 官方 MCP 库
- 服务实现:实现 MCP 服务的核心接口
- 工具注册:将 PDF 解析工具注册到 MCP 服务
- HTTP 服务:使用 Gin 框架暴露 MCP 服务端点
3. 代码实现
go
package parser
import (
"context"
"log"
"net/http"
"github.com/gin-gonic/gin"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
func init() {
serverMCP = mcp.NewServer(&mcp.Implementation{
Name: "ParserPDF",
Version: "1.0.0",
}, nil)
// 初始化时注册 ParserPDF 工具
CreateTool("ParserPDF", "解析 PDF 文件,支持本地路径和 Web URL")
}
func parserPdf(ctx context.Context, req *mcp.CallToolRequest, input McpPdfPrams) (*mcp.CallToolResult, McpPdfResp, error) {
data, err := ParserPdf(ctx, input.Path)
if err != nil {
return nil, McpPdfResp{}, err
}
if len(data) == 0 {
return &mcp.CallToolResult{}, McpPdfResp{Content: ""}, nil
}
return &mcp.CallToolResult{}, McpPdfResp{Content: data[0].Content}, nil
}
func CreateTool(name, desc string) {
mcp.AddTool(serverMCP, &mcp.Tool{
Name: name,
Description: desc,
}, parserPdf)
}
func Run(ctx context.Context) error {
// 启动一个新的Goroutine来运行MCP服务器(使用StdioTransport)
go func() {
if err := serverMCP.Run(ctx, &mcp.StdioTransport{}); err != nil {
log.Printf("MCP服务器运行失败: %v\n", err)
}
}()
// 创建HTTP服务器以支持外部Agent调用
r := gin.Default()
// 设置外部调用接口
r.POST("/api/parser/pdf", func(c *gin.Context) {
var req struct {
Path string `json:"path"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误: " + err.Error()})
return
}
// 直接调用PDF解析函数
data, err := ParserPdf(c.Request.Context(), req.Path)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "PDF解析失败: " + err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "success",
"data": data,
})
})
// 提供MCP工具信息接口
r.GET("/api/mcp/info", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"name": "ParserPDF",
"version": "1.0.0",
"description": "解析PDF文件,支持本地路径和Web URL",
})
})
// 启动HTTP服务器,监听7777端口
log.Printf("MCP外部调用服务器启动,监听端口7777")
return r.Run(":7777")
}
结语
这里并没有 Eino 实现的 MCP Server ,根据最新的官方文档,并没有发现可以实现 MCP Server,只是可以 MCP Client 的方式调用外部或内部的 MCP Server。目前等待官方的最新消息。
通过本文的实践,我们可以看到:
- Eino 框架提供了强大的文档解析能力,能够方便地实现 PDF 文档的解析和排版切割。
- 基于 Go 官方的 MCP 库,可以快速实现标准化的 MCP 服务,提高服务的兼容性和互操作性。
- MCP 协议为 AI 应用提供了标准化的工具调用方式,方便不同组件之间的集成。
这种实现方式具有良好的扩展性和可维护性,可以方便地集成到各种 AI 应用中,满足不同场景下的文档解析需求。
随着 AI 应用的不断发展,基于 MCP 协议的工具调用将在更多场景中得到应用,为 AI 应用提供更强大的能力扩展。