In this blog we will walk through a comprehensive example of indexing research papers with extracting different metadata — beyond full text chunking and embedding — and build semantic embeddings for indexing and querying. Мы будем очень благодарны, если вы сможете Если вам поможет этот урок. ⭐ star CocoIndex на GitHub CocoIndex на GitHub Используйте случаи Академический поиск и поиск, а также агенты ИИ на основе исследований Системы бумажных рекомендаций Графы исследовательских знаний Семантический анализ научной литературы Чего мы достигнем Давайте взглянем на это Как пример PDF Вот что мы хотим достичь: Extract the paper metadata, including file name, title, author information, abstract, and number of pages. Build vector embeddings for the metadata, such as the title and abstract, for semantic search. Это позволяет улучшить семантические результаты поиска, основанные на метаданных, например, вы можете сравнивать текстовые запросы с заголовками и абстрактами. Build an index of authors and all the file names associated with each author to answer questions like "Give me all the papers by Jeff Dean." If you want to perform full PDF embedding for the paper, you can also refer to . this article Вы можете найти полный код . здесь Если эта статья вам полезна, пожалуйста, дайте нам звезду Чтобы помочь нам расти. GitHub Основные компоненты PDF Preprocessing Reads PDFs using and extracts: pypdf Total number of pages First page content (used as a proxy for metadata-rich information) Markdown Conversion Converts the first page to Markdown using . Marker LLM-Powered Metadata Extraction Sends the first-page Markdown to GPT-4o using CocoIndex's function. ExtractByLlm Extracted metadata includes and more. (string) title (with name, email, and affiliation) authors (string) abstract Semantic Embedding The title is embedded directly using the model by the SentenceTransformer. all-MiniLM-L6-v2 Abstracts are chunked based on semantic punctuation and token count, then each chunk is embedded individually. Relational Data Collection Authors are unrolled and collected into an relation, enabling queries like: author_papers Show all papers by X Which co-authors worked with Y? Предпосылки . Install PostgreSQL CocoIndex uses PostgreSQL internally for incremental processing. . Configure your OpenAI API key В качестве альтернативы, у нас есть врожденная поддержка для Gemini, Ollama, LiteLLM, . Руководство Вы можете выбрать своего любимого поставщика LLM и работать полностью на местах. Определение индексационного потока Этот проект демонстрирует несколько более всеобъемлющий пример понимания метаданных ближе к реальным случаям использования. Вы увидите, как легко достичь этого дизайна CocoIndex в пределах 100 линий логики индексации - . Код Чтобы помочь вам лучше ориентироваться в том, через что мы пойдем, вот диаграмма потока. Импортировать список документов в PDF. For each file: Extract the first page of the paper. Convert the first page to Markdown. Extract metadata (title, authors, abstract) from the first page. Split the abstract into chunks, and compute embeddings for each chunk. Export to the following tables in Postgres with PGVector: Metadata (title, authors, abstract) for each paper. Author-to-paper mapping, for author-based query. Embeddings for titles and abstract chunks, for semantic search. Давайте займемся зумом по шагам. Импортировать документы @cocoindex.flow_def(name="PaperMetadata") def paper_metadata_flow( flow_builder: cocoindex.FlowBuilder, data_scope: cocoindex.DataScope ) -> None: data_scope["documents"] = flow_builder.add_source( cocoindex.sources.LocalFile(path="papers", binary=True), refresh_interval=datetime.timedelta(seconds=10), ) будет создаваться таблица с подполями ( , ) , flow_builder.add_source filename content Мы можем ссылаться на Для большей подробности. Документация Экстракт и сбор метаданных Вычеркните первую страницу для базовой информации Определите индивидуальную функцию для извлечения первой страницы и количества страниц PDF. @dataclasses.dataclass class PaperBasicInfo: num_pages: int first_page: bytes @cocoindex.op.function() def extract_basic_info(content: bytes) -> PaperBasicInfo: """Extract the first pages of a PDF.""" reader = PdfReader(io.BytesIO(content)) output = io.BytesIO() writer = PdfWriter() writer.add_page(reader.pages[0]) writer.write(output) return PaperBasicInfo(num_pages=len(reader.pages), first_page=output.getvalue()) Теперь включите это в свой поток. Мы извлекаем метаданные с первой страницы, чтобы минимизировать затраты на обработку, так как весь PDF может быть очень большим. with data_scope["documents"].row() as doc: doc["basic_info"] = doc["content"].transform(extract_basic_info) После этого шага вы должны иметь основную информацию о каждой бумаге. Показать основную информацию Мы конвертируем первую страницу в Markdown с помощью маркера. В качестве альтернативы вы можете легко подключить свой любимый PDF-апсер, например Docling. Определите функцию конвертера маркеров и запомните ее, так как ее инициализация требует много ресурсов. Это обеспечивает повторное использование одного и того же инстанции конвертера для различных входных файлов. @cache def get_marker_converter() -> PdfConverter: config_parser = ConfigParser({}) return PdfConverter( create_model_dict(), config=config_parser.generate_config_dict() ) Включите его в привычную функцию. @cocoindex.op.function(gpu=True, cache=True, behavior_version=1) def pdf_to_markdown(content: bytes) -> str: """Convert to Markdown.""" with tempfile.NamedTemporaryFile(delete=True, suffix=".pdf") as temp_file: temp_file.write(content) temp_file.flush() text, _, _ = text_from_rendered(get_marker_converter()(temp_file.name)) return text Передайте его своей трансформации with data_scope["documents"].row() as doc: doc["first_page_md"] = doc["basic_info"]["first_page"].transform( pdf_to_markdown ) После этого шага, вы должны иметь первую страницу каждой бумаги в формате Markdown. Экстракт базовой информации с LLM CocoIndex поддерживает структурированную экстракцию LLM с сложными и встроенными схемами. Если вы заинтересованы в том, чтобы узнать больше о гнездовых схемах, обратитесь к . Эта статья @dataclasses.dataclass class PaperMetadata: """ Metadata for a paper. """ title: str authors: list[Author] abstract: str Включите его в С определенным классом данных, CocoIndex автоматически анализирует ответ LLM в классе данных. ExtractByLlm 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="Please extract the metadata from the first page of the paper.", ) ) После этого шага вы должны иметь метаданные каждой бумаги. Сбор бумажных метаданных paper_metadata = data_scope.add_collector() with data_scope["documents"].row() as doc: # ... process # Collect metadata paper_metadata.collect( filename=doc["filename"], title=doc["metadata"]["title"], authors=doc["metadata"]["authors"], abstract=doc["metadata"]["abstract"], num_pages=doc["basic_info"]["num_pages"], ) Просто соберите все, что вам нужно :) Коллекция Два Информация author ФИЛЕНАМ author ФИЛЕНАМ Здесь мы хотим собрать Author → Papers в отдельную таблицу, чтобы создать функциональность поиска. Просто собранный автором. author_papers = data_scope.add_collector() with data_scope["documents"].row() as doc: with doc["metadata"]["authors"].row() as author: author_papers.collect( author_name=author["name"], filename=doc["filename"], ) Вычислить и собрать вставки Титул 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( custom_languages=[ cocoindex.functions.CustomLanguageSpec( language_name="abstract", separators_regex=[r"[.?!]+\s+", r"[:;]\s+", r",\s+", r"\s+"], ) ] ), language="abstract", chunk_size=500, min_chunk_size=200, 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" ) ) После этого шага вы должны иметь вставки абстрактных кусочков каждой бумаги. Коллекция Embeddings metadata_embeddings = data_scope.add_collector() with data_scope["documents"].row() as doc: # ... process # collect title embedding metadata_embeddings.collect( id=cocoindex.GeneratedField.UUID, filename=doc["filename"], location="title", text=doc["metadata"]["title"], embedding=doc["title_embedding"], ) with doc["abstract_chunks"].row() as chunk: # ... process # collect abstract chunks embeddings metadata_embeddings.collect( id=cocoindex.GeneratedField.UUID, filename=doc["filename"], location="abstract", text=chunk["text"], embedding=chunk["embedding"], ) Экспорт Наконец, мы экспортируем данные в 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(), primary_key_fields=["id"], vector_indexes=[ cocoindex.VectorIndexDef( field_name="embedding", metric=cocoindex.VectorSimilarityMetric.COSINE_SIMILARITY, ) ], ) В данном примере мы используем PGVector как встраивающие магазины/ С помощью CocoIndex вы можете сделать один переключатель линий на других поддерживаемых базах данных Vector, таких как Qdrant, см. это Для большей подробности. Руководство Мы стремимся стандартизировать интерфейсы и сделать это похожим на строительство LEGO. Смотреть в CocoInsight шаг за шагом Вы можете пройти через проект шаг за шагом в Смотреть Кокоинсайт Точно, как строится каждое поле и что происходит за сценами. Искать индекс Вы можете ознакомиться с этим разделом О О Текстовые вставки Как создать запрос против встраиваний. На данный момент CocoIndex не предоставляет дополнительный интерфейс запросов. Мы можем писать SQL или полагаться на механизм запросов целевым хранилищем. Многие базы данных уже оптимизировали имплементации запросов с помощью собственных лучших практик Пространство запросов имеет отличные решения для запросов, перекладирования и других функций, связанных с поиском. Если вам нужна помощь в написании запроса, пожалуйста, не стесняйтесь связаться с нами по адресу . Дискворд Поддержите нас Мы постоянно совершенствуемся, и в ближайшее время появятся новые функции и примеры. Если эта статья вам полезна, пожалуйста, дайте нам звезду Чтобы помочь нам расти. GitHub Спасибо за чтение!