文档解析
文档解析技术的本质在于将格式各异、版式多样、元素多种的文档数据,包括段落、表格、标题、公式、多列、图片等文档区块,转化为阅读顺序正确的字符串信息。
数据除了存储到数据库中,还有的以 PDF、TXT、Word、PPT、Excel、CSV、Markdown、XML、HTML 等多种格式存储。
langchaingo的文档加载器在documentloaders下,如果langchaingo提供的文档加载器不满足,可以通过实现Loader接口打造自己的文档加载器
package main
import (
"context"
"errors"
"fmt"
"github.com/tmc/langchaingo/documentloaders"
"github.com/tmc/langchaingo/schema"
"github.com/tmc/langchaingo/textsplitter"
"log"
"os"
"path/filepath"
)
func main() {
docs, err := LoadDoc(context.Background(), "./docs")
if err != nil {
log.Fatalf("文件加载失败:%s", err.Error())
}
for _, doc := range docs {
fmt.Printf("%s\n", doc)
}
}
// LoadDoc 对目录或单个文件进行加载
func LoadDoc(ctx context.Context, fileName string) ([]schema.Document, error) {
f, err := os.Lstat(fileName)
if err != nil {
return nil, err
}
// 加载单个文件
if !f.IsDir() {
return LoadFile(ctx, fileName)
}
// 加载文件夹下的文件
dir, err := os.ReadDir(fileName)
if err != nil {
return nil, err
}
docs := make([]schema.Document, 0, 100)
for _, d := range dir {
rd, err := LoadDoc(ctx, filepath.Join(fileName, d.Name()))
if err != nil {
return nil, err
}
docs = append(docs, rd...)
}
return docs, nil
}
// LoadFile 读取文件内容信息
func LoadFile(ctx context.Context, fileName string) ([]schema.Document, error) {
file, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer file.Close()
var (
loader documentloaders.Loader
)
finfo, err := file.Stat()
if err != nil {
return nil, err
}
ext := filepath.Ext(fileName)
switch ext {
case ".md":
loader = documentloaders.NewText(file)
case ".txt":
loader = documentloaders.NewText(file)
case ".pdf":
loader = documentloaders.NewPDF(file, finfo.Size())
default:
return nil, errors.New("不支持的文档类型:" + ext)
}
return loader.Load(ctx)
}
分块切片
文档数据(Documents)经过解析后,通过分块技术将信息内容划分为适当大小的文档片段(chunks),从而使系统能够高效处理和精准检索这些片段信息。分块的本质在于依据一定逻辑或语义原则,将较长文本拆解为更小的单元。
分块的目标在于确保每个片段在保留核心语义的同时,具备相对独立的语义完整性,从而使模型在处理时不必依赖广泛的上下文信息,增强检索召回的准确性。合理的分块能够确保检索到的片段与用户查询信息高度匹配,避免信息冗余或丢失。 有助于提升生成内容的连贯性,精心设计的独立语义片段可以降低模型对上下文的依赖,从而增强生成的逻辑性与一致性。还会影响系统的响应速度与效率,模型能够更快、更准确地处理和生成内容。
如果片段过大,可能导致向量无法精确捕捉内容的特定细节并且计算成本增加;若片段过小,则可能丢失上下文信息,导致句子碎片化和语义不连贯。
常见的分块策略包括:
- 固定大小分块
- 重叠分块
- 递归分块
- 文档特定分块
- 语义分块
- 混合分块
//// 不添加元数据,直接返回切割后的数据信息
//return loader.LoadAndSplit(ctx, spliter)
// 加载并拆分文档
docs, err := loader.Load(ctx)
if err != nil {
return nil, err
}
texts := make([]string, len(docs))
metadatas := make([]map[string]interface{}, len(docs))
for i, doc := range docs {
texts[i] = doc.PageContent
meta := doc.Metadata
for k, v := range metadata {
if _, ok := meta[k]; !ok {
meta[k] = v
}
}
metadatas[i] = meta
}
return textsplitter.CreateDocuments(spliter, texts, metadatas)