低代码、零门槛打造学术论文语义搜索:CocoIndex全流程实战(开源项目直达)

70 阅读3分钟

用Python+向量索引打造学术论文语义搜索引擎(CocoIndex实战教程)研究者和技术社区都面临一个痛点:大量PDF论文散落在硬盘,想按作者、领域甚至语义检索变得极其困难。关键字搜索已经不能满足真实需求,语义搜索+元数据检索才是新时代的打开方式。

本文实战分享如何用CocoIndex,一套开源AI数据管道,只需不到200行Python代码,把本地PDF库变身为支持语义查询、作者检索、SQL联表、自动增量刷新的一站式学术搜索系统。

Gemini_Generated_Image_jz91w1jz91w1jz91.png

为什么不要只把PDF强行embed?

主流RAG检索都是:按页或分块插入PDF到向量数据库,只能模糊召回。 但实际问题包括:

  • 没法按作者/年份/页数精确筛选
  • 「展示Geoffrey Hinton 2019之后的全部论文」根本无法一条SQL解决
  • 向量模型常常embed了大量无关内容,附录/ author info 占大头
  • 文本检索和“top-K向量”混合逻辑极其复杂

CocoIndex的方案是:

  • 保持元数据(标题、作者、摘要、页数)结构化和独立
  • 嵌入title和摘要分块(chunk)实现语义召回
  • 所有实体直接存储在PostgreSQL+PGVector,SQL随意查

技术架构/全流程拆解

  1. 监控papers目录,自动获取新PDF(支持刷新)
  2. 每篇PDF:只提取第一页,保存页数
  3. 用Marker(或Docling)把第一页转Markdown
  4. LLM结构化抽取title/author/abstract(直接dataclass)
  5. 摘要分块并embed,每个chunk用all-MiniLM-L6-v2模型算向量
  6. 三张表存Postgres:
    • paper_metadata
    • author_papers
    • metadata_embeddings

关键代码与表结构

@cocoindex.flow_def(name="PaperMetadata")
def paper_metadata_flow(flow_builder, data_scope):
    data_scope["documents"] = flow_builder.add_source(
        cocoindex.sources.LocalFile(path="papers", binary=True),
        refresh_interval=datetime.timedelta(seconds=10)
    )

@dataclasses.dataclass
class PaperBasicInfo:
    num_pages: int
    first_page: bytes

@cocoindex.op.function()
def extract_basic_info(content: bytes) -> PaperBasicInfo:
    reader = PdfReader(io.BytesIO(content))
    writer = PdfWriter()
    writer.add_page(reader.pages[0])
    output = io.BytesIO()
    writer.write(output)
    return PaperBasicInfo(
        num_pages=len(reader.pages),
        first_page=output.getvalue()
    )

LLM与嵌入

@dataclasses.dataclass
class Author:
    name: str

@dataclasses.dataclass
class PaperMetadata:
    title: str
    authors: list[Author]
    abstract: str

with data_scope["documents"].row() as doc:
    doc["first_page_md"] = doc["basic_info"]["first_page"].transform(pdf_to_markdown)
    doc["metadata"] = doc["first_page_md"].transform(
        cocoindex.functions.ExtractByLlm(
            llm_spec=cocoindex.LlmSpec(
                api_type=cocoindex.LlmApiType.OPENAI, model="gpt-4o"
            ),
            output_type=PaperMetadata,
            instruction="请抽取标题、作者和摘要"
        )
    )
    doc["title_embedding"] = doc["metadata"]["title"].transform(
      cocoindex.functions.SentenceTransformerEmbed(
        model="sentence-transformers/all-MiniLM-L6-v2"
      )
    )
    doc["abstract_chunks"] = doc["metadata"]["abstract"].transform(
      cocoindex.functions.SplitRecursively(
        chunk_size=500,
        chunk_overlap=150
      )
    )
    with doc["abstract_chunks"].row() as chunk:
      chunk["embedding"] = chunk["text"].transform(
        cocoindex.functions.SentenceTransformerEmbed(
          model="sentence-transformers/all-MiniLM-L6-v2"
        )
      )

Postgres表结构与典型查询

paper_metadata.export(
    "paper_metadata",
    cocoindex.targets.Postgres(),
    primary_key_fields=["filename"]
)
author_papers.export(
    "author_papers",
    cocoindex.targets.Postgres(),
    primary_key_fields=["author_name", "filename"]
)
metadata_embeddings.export(
    "metadata_embeddings",
    cocoindex.targets.Postgres(),
    vector_indexes=[cocoindex.VectorIndexDef(field_name="embedding")]
)

查询案例(直接SQL)

语义检索:

SELECT filename, text FROM metadata_embeddings ORDER BY embedding <=> '[query_vector]' LIMIT 10;

作者检索:

SELECT filename FROM author_papers WHERE author_name = 'Geoffrey Hinton';

混合筛选:

SELECT p.title, p.abstract FROM paper_metadata p JOIN author_papers a ON p.filename = a.filename WHERE a.author_name = 'Yann LeCun' AND p.num_pages < 20;

为什么CocoIndex一定要入手?

  • Python全流程,开源易用,数据安全本地可控
  • 支持增量处理,批量PDF随时插入自动索引
  • 类型安全,结构化数据抽取简洁可靠
  • 支持多模型、可替换AI算子/数据库
  • 质量高、社区活跃,GitHub已超千星

快速起步和扩展交流

你还在用手工搜论文?赶快让CocoIndex一键升级你的科研搜索力,看懂这套架构,GitHub直接star!