Eino-Document 组件使用指南
Eino-Document 是 cloudwego/eino 生态中的文档处理库,提供了从多种来源加载文档、解析不同格式、对文档进行分割 transform 等全链路能力。本文介绍Document 各组件的用法及典型应用场景,代码链接。
目录
- [1. 核心概念](#1. 核心概念)
- [2. 文档加载(Loader)](#2. 文档加载(Loader))
- [2.1 本地文件加载器](#2.1 本地文件加载器)
- [2.2 Web URL 加载器](#2.2 Web URL 加载器)
- [2.3 AWS S3 加载器](#2.3 AWS S3 加载器)
- [3. 文档解析器(Parser)](#3. 文档解析器(Parser))
- [3.1 文本解析器](#3.1 文本解析器)
- [3.2 HTML 解析器](#3.2 HTML 解析器)
- [3.3 PDF 解析器](#3.3 PDF 解析器)
- [4. 扩展解析器(ExtParser)](#4. 扩展解析器(ExtParser))
- [5. 文档分割器(Splitter)](#5. 文档分割器(Splitter))
- [5.1 递归字符分割器](#5.1 递归字符分割器)
- [5.2 Markdown 标题分割器](#5.2 Markdown 标题分割器)
- [5.3 语义分割器](#5.3 语义分割器)
- [6. 完整实战:加载 HTML → 解析 → 分割](#6. 完整实战:加载 HTML → 解析 → 分割)
- [7. 总结](#7. 总结)
1. 核心概念
Eino-Document 将文档处理流程抽象为以下几层:
| 层次 | 组件 | 职责 |
|---|---|---|
| 加载层 | Loader | 从各类来源(本地文件、Web URL、S3)读取原始数据 |
| 解析层 | Parser | 将原始数据(HTML、PDF、纯文本)解析为结构化的 schema.Document |
| 分割层 | Splitter | 将大文档切分为适合 LLM 处理的小块(Chunk) |
| 编排层 | ExtParser | 根据文件扩展名自动选择合适的解析器 |
核心数据结构 schema.Document 包含两个字段:
go
type Document struct {
Content string // 文档正文内容
MetaData map[string]any // 元数据(如文件名、标题、语言等)
}
2. 文档加载(Loader)
Loader 负责从不同来源获取文档内容,返回 []*schema.Document。所有 Loader 都实现了 Loader 接口:
go
type Loader interface {
Load(ctx context.Context, source document.Source) ([]*schema.Document, error)
}
2.1 本地文件加载器
使用 eino-ext/components/document/loader/file,支持根据文件扩展名自动选择解析器。
go
import fileloader "github.com/cloudwego/eino-ext/components/document/loader/file"
loader, err := fileloader.NewFileLoader(ctx, &fileloader.FileLoaderConfig{
// UseNameAsID: true, // 可选:使用文件名作为文档 ID
})
docs, err := loader.Load(ctx, document.Source{
URI: "./testdata/sample.txt",
})
Loader 会自动将文件名、扩展名、源路径等信息注入 MetaData:
go
// 读取元数据
if fileName, ok := doc.MetaData[fileloader.MetaKeyFileName].(string); ok {
fmt.Println("文件名:", fileName)
}
if extension, ok := doc.MetaData[fileloader.MetaKeyExtension].(string); ok {
fmt.Println("扩展名:", extension)
}
2.2 Web URL 加载器
使用 eino-ext/components/document/loader/url,默认使用 HTML 解析器处理网页内容。
go
import urlloader "github.com/cloudwego/eino-ext/components/document/loader/url"
loader, err := urlloader.NewLoader(ctx, &urlloader.LoaderConfig{})
docs, err := loader.Load(ctx, document.Source{
URI: "https://example.com",
})
2.3 AWS S3 加载器
使用 eino-ext/components/document/loader/s3,支持从 S3 bucket 加载文件。
go
import s3loader "github.com/cloudwego/eino-ext/components/document/loader/s3"
region := "us-east-1"
loader, err := s3loader.NewS3Loader(ctx, &s3loader.LoaderConfig{
Region: ®ion,
// 也可通过环境变量或 AWS 配置文件自动读取凭证
})
// URI 格式:s3://bucket-name/path/to/file
docs, err := loader.Load(ctx, document.Source{
URI: "s3://my-bucket/documents/article.pdf",
})
注意 :
Region为指针类型*string,需要取地址传入。
3. 文档解析器(Parser)
Parser 将原始数据解析为 schema.Document。接口定义如下:
go
type Parser interface {
Parse(ctx context.Context, reader io.Reader, opts ...Option) ([]*schema.Document, error)
}
3.1 文本解析器
使用内置的 parser.TextParser,直接解析纯文本。
go
import "github.com/cloudwego/eino/components/document/parser"
textParser := parser.TextParser{}
reader := strings.NewReader("这是要解析的文本内容。\n可以包含多行。")
docs, err := textParser.Parse(ctx, reader, parser.WithURI("text://sample.txt"))
3.2 HTML 解析器
使用 eino-ext/components/document/parser/html,可提取 <body> 等指定部分,并自动解析元数据(标题、描述、语言、字符集)。
go
import htmlparser "github.com/cloudwego/eino-ext/components/document/parser/html"
htmlParser, err := htmlparser.NewParser(ctx, &htmlparser.Config{
Selector: &htmlparser.BodySelector, // 只提取 body 内容
})
reader := strings.NewReader(htmlContent)
docs, err := htmlParser.Parse(ctx, reader, parser.WithURI("https://example.com/page.html"))
解析完成后可读取丰富的元数据:
go
if title, ok := doc.MetaData[htmlparser.MetaKeyTitle].(string); ok {
fmt.Println("标题:", title)
}
if desc, ok := doc.MetaData[htmlparser.MetaKeyDesc].(string); ok {
fmt.Println("描述:", desc)
}
if lang, ok := doc.MetaData[htmlparser.MetaKeyLang].(string); ok {
fmt.Println("语言:", lang)
}
3.3 PDF 解析器
使用 eino-ext/components/document/parser/pdf,支持按页分割或合并为单个文档。
go
import pdfparser "github.com/cloudwego/eino-ext/components/document/parser/pdf"
pdfParser, err := pdfparser.NewPDFParser(ctx, &pdfparser.Config{
ToPages: false, // false: 合并所有页;true: 每页一个文档
})
file, _ := os.Open("./testdata/sample.pdf")
docs, err := pdfParser.Parse(ctx, file,
parser.WithURI("./testdata/sample.pdf"),
// pdfparser.WithToPages(true), // 可选:运行时覆盖配置
)
4. 扩展解析器(ExtParser)
ExtParser 根据文件扩展名自动分派对应的解析器,是生产环境中处理多格式文件的推荐方式。
go
import (
"github.com/cloudwego/eino-ext/components/document/parser/html"
"github.com/cloudwego/eino-ext/components/document/parser/pdf"
"github.com/cloudwego/eino/components/document/parser"
)
htmlParser, _ := htmlparser.NewParser(ctx, &htmlparser.Config{})
pdfParser, _ := pdfparser.NewPDFParser(ctx, &pdfparser.Config{ToPages: false})
extParser, err := parser.NewExtParser(ctx, &parser.ExtParserConfig{
Parsers: map[string]parser.Parser{
".html": htmlParser,
".htm": htmlParser,
".pdf": pdfParser,
},
// FallbackParser: 不支持的扩展名会降级使用 TextParser
})
// 根据 URI 自动选择解析器
file, _ := os.Open("testdata/sample.html")
docs, err := extParser.Parse(ctx, file, parser.WithURI("testdata/sample.html"))
5. 文档分割器(Splitter)
当文档长度超过 LLM 的上下文窗口时,需要将文档切分为小块(Chunk)。所有 Splitter 都实现了 Transformer 接口:
go
type Transformer interface {
Transform(ctx context.Context, docs []*schema.Document) ([]*schema.Document, error)
}
5.1 递归字符分割器
RecursiveSplitter 按分隔符递归切分文档,是最通用的分割方式。
go
import recursive "github.com/cloudwego/eino-ext/components/document/transformer/splitter/recursive"
splitter, err := recursive.NewSplitter(ctx, &recursive.Config{
ChunkSize: 500, // 每个块最多 500 字符
OverlapSize: 50, // 块之间重叠 50 字符(保持上下文连续)
Separators: []string{"\n", ".", "?", "!"},
KeepType: recursive.KeepTypeNone, // 丢弃分隔符
// KeepTypeStart / KeepTypeEnd 可保留分隔符在块开头或结尾
})
chunks, err := splitter.Transform(ctx, []*schema.Document{doc})
核心参数说明:
| 参数 | 说明 |
|---|---|
ChunkSize |
每个块的最大字符数 |
OverlapSize |
相邻块之间的重叠字符数,用于保持语义连贯 |
Separators |
分隔符列表,按优先级顺序尝试切割 |
KeepType |
分隔符保留策略(None/Start/End) |
5.2 Markdown 标题分割器
HeaderSplitter 根据 Markdown 标题结构(#、##、###)进行分割,保留文档结构完整性。
go
import markdown "github.com/cloudwego/eino-ext/components/document/transformer/splitter/markdown"
splitter, err := markdown.NewHeaderSplitter(ctx, &markdown.HeaderConfig{
Headers: map[string]string{
"#": "title", // 一级标题 → 元数据 title
"##": "section", // 二级标题 → 元数据 section
"###": "subsection", // 三级标题 → 元数据 subsection
},
TrimHeaders: false, // false: 保留标题在内容中;true: 移除标题行
})
chunks, err := splitter.Transform(ctx, []*schema.Document{doc})
注意 :Markdown 分割器基于标题结构,不支持
ChunkSize参数。
5.3 语义分割器
SemanticSplitter 利用 Embedding 模型计算文本块之间的语义相似度,在语义断点处分割,适合保持内容的语义完整性。
go
import (
semantic "github.com/cloudwego/eino-ext/components/document/transformer/splitter/semantic"
ark "github.com/cloudwego/eino-ext/components/embedding/ark"
)
embedder, err := ark.NewEmbedder(ctx, &ark.EmbeddingConfig{
APIKey: os.Getenv("ARK_API_KEY"),
Model: os.Getenv("ARK_MODEL"),
})
splitter, err := semantic.NewSplitter(ctx, &semantic.Config{
Embedding: embedder, // 计算语义相似度的 Embedding 模型
Percentile: 0.7, // 分割阈值(越小越激进,默认 0.9)
BufferSize: 1, // 拼接邻居块的数量,提高准确性
MinChunkSize: 50, // 最小块大小
Separators: []string{"\n", ".", "?", "!"},
})
chunks, err := splitter.Transform(ctx, []*schema.Document{doc})
6. 完整实战:加载 HTML → 解析 → 分割
以下示例演示从本地 HTML 文件加载,经由 RecursiveSplitter 分割的完整流程:
go
package main
import (
"context"
"fmt"
"log"
fileloader "github.com/cloudwego/eino-ext/components/document/loader/file"
recursive "github.com/cloudwego/eino-ext/components/document/transformer/splitter/recursive"
"github.com/cloudwego/eino/components/document"
"github.com/cloudwego/eino/schema"
)
func main() {
ctx := context.Background()
// 步骤 1:创建文件加载器
fileLoader, err := fileloader.NewFileLoader(ctx, &fileloader.FileLoaderConfig{})
if err != nil {
log.Fatalf("创建文件加载器失败: %v", err)
}
// 步骤 2:加载 HTML 文件
docs, err := fileLoader.Load(ctx, document.Source{
URI: "../testdata/article.html",
})
if err != nil {
log.Fatalf("加载文档失败: %v", err)
}
fmt.Printf("加载了 %d 个文档\n", len(docs))
// 步骤 3:创建递归分割器
splitter, err := recursive.NewSplitter(ctx, &recursive.Config{
ChunkSize: 500,
OverlapSize: 50,
Separators: []string{"\n", ".", "?", "!"},
KeepType: recursive.KeepTypeNone,
})
if err != nil {
log.Fatalf("创建分割器失败: %v", err)
}
// 步骤 4:分割文档
var allChunks []*schema.Document
for _, doc := range docs {
chunks, err := splitter.Transform(ctx, []*schema.Document{doc})
if err != nil {
log.Printf("分割文档失败: %v", err)
continue
}
allChunks = append(allChunks, chunks...)
}
fmt.Printf("分割后得到 %d 个文档块\n\n", len(allChunks))
// 步骤 5:打印结果
for i, chunk := range allChunks {
fmt.Printf("=== 块 %d ===\n", i+1)
fmt.Printf("内容长度: %d 字符\n", len(chunk.Content))
fmt.Printf("内容预览: %s...\n", chunk.Content[:min(100, len(chunk.Content))])
fmt.Printf("元数据: %v\n\n", chunk.MetaData)
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
7. 总结
Eino-Document 提供了从加载 → 解析 → 分割的完整文档处理链路:
| 场景 | 推荐方案 |
|---|---|
| 加载本地文件 | FileLoader + 自动识别扩展名 |
| 加载网页内容 | URLLoader |
| 加载 S3 文件 | S3Loader |
| 多格式统一解析 | ExtParser |
| 通用文本分割 | RecursiveSplitter |
| Markdown 文档分割 | HeaderSplitter |
| 语义感知分割 | SemanticSplitter + Embedding |
通过组合不同的 Loader、Parser 和 Splitter,可以灵活构建满足各类需求的文档处理流水线。