数据加载 (Data Loading)
在 RAG 系统中,数据加载是第一步。我们需要将各种格式的非结构化数据(PDF, Word, Markdown, HTML)加载到内存中,并统一转换为标准格式。
1. 核心数据结构
在 Golang 中,我们通常定义一个通用的 Document 结构体来承载数据:
go
type Document struct {
PageContent string // 文档正文
Metadata map[string]interface{} // 元数据(如文件名、页码、来源URL)
}这与 langchaingo 中的 schema.Document 是一致的。
2. 常见文件格式加载
2.1 加载 TXT / Markdown 文件
对于纯文本文件,使用 Golang 标准库 os 和 io 即可轻松搞定。
go
package main
import (
"context"
"fmt"
"os"
"github.com/tmc/langchaingo/documentloaders"
)
func LoadText() {
f, _ := os.Open("example.md")
defer f.Close()
// 使用 langchaingo 的 TextLoader
loader := documentloaders.NewText(f)
docs, err := loader.Load(context.Background())
if err != nil {
panic(err)
}
fmt.Println(docs[0].PageContent)
}2.2 加载 PDF 文件
PDF 解析稍微复杂一些,推荐使用 github.com/ledongthuc/pdf 或 rsc.io/pdf。langchaingo 暂未内置 PDF Loader,我们可以自己封装一个。
封装 PDFLoader 示例:
go
package main
import (
"bytes"
"context"
"fmt"
"github.com/ledongthuc/pdf"
"github.com/tmc/langchaingo/schema"
)
// PDFLoader 实现 documentloaders.Loader 接口
type PDFLoader struct {
path string
}
func NewPDFLoader(path string) *PDFLoader {
return &PDFLoader{path: path}
}
func (l *PDFLoader) Load(ctx context.Context) ([]schema.Document, error) {
f, r, err := pdf.Open(l.path)
if err != nil {
return nil, err
}
defer f.Close()
var buf bytes.Buffer
b, err := r.GetPlainText() // 获取纯文本
if err != nil {
return nil, err
}
buf.ReadFrom(b)
return []schema.Document{
{
PageContent: buf.String(),
Metadata: map[string]any{
"source": l.path,
},
},
}, nil
}
func main() {
loader := NewPDFLoader("manual.pdf")
docs, _ := loader.Load(context.Background())
fmt.Printf("PDF 内容长度: %d\n", len(docs[0].PageContent))
}2.3 加载网页 (HTML)
可以使用 goquery 库来提取网页正文,去除 HTML 标签。
go
import (
"net/http"
"strings"
"github.com/PuerkitoBio/goquery"
)
func FetchURL(url string) string {
res, _ := http.Get(url)
defer res.Body.Close()
doc, _ := goquery.NewDocumentFromReader(res.Body)
// 移除 script 和 style 标签
doc.Find("script,style").Each(func(i int, s *goquery.Selection) {
s.Remove()
})
return strings.TrimSpace(doc.Text())
}3. 并发加载优化
Golang 的最大优势在于并发。当需要加载数千个文档时,一定要利用 Goroutine。
go
func LoadDir(dir string) []schema.Document {
files, _ := os.ReadDir(dir)
ch := make(chan schema.Document, len(files))
// 启动并发加载
for _, file := range files {
go func(f os.DirEntry) {
// ... 加载逻辑 ...
ch <- doc
}(file)
}
// 收集结果
var allDocs []schema.Document
for range files {
allDocs = append(allDocs, <-ch)
}
return allDocs
}总结
数据加载的核心是将异构数据转化为同构的 Document 对象。在 Golang 中,利用接口(Interface)和并发(Goroutine)可以构建出非常高效的数据处理管道(ETL Pipeline)。
