Dans ce blog, nous allons parcourir un exemple complet d'indexation des documents de recherche avec l'extraction de différentes métadonnées - au-delà de l'emballage et de l'emballage de texte complet - et de la construction d'emballages sémantiques pour l'indexation et la requête. Nous serions très reconnaissants si vous pouviez Si vous trouvez ce tutoriel utile. ⭐ star CocoIndex sur GitHub CocoIndex sur GitHub Utiliser les cas Recherche et récupération académiques, ainsi que des agents d’IA basés sur la recherche Systèmes de recommandation papier Graphiques de connaissances Analyse sémantique de la littérature scientifique What we will achieve Jetons un coup d'oeil à ce comme un exemple. Le PDF Voici ce que nous voulons réaliser : 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. Cela permet de meilleurs résultats de recherche sémantique axés sur les métadonnées. Par exemple, vous pouvez correspondre aux requêtes de texte avec des titres et des abstracts. 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 Vous trouverez le code complet . ici Si cet article vous est utile, s'il vous plaît nous donner une étoile pour nous aider à grandir. Github Composants de base 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? Pré-requis . Install PostgreSQL CocoIndex uses PostgreSQL internally for incremental processing. . Configure your OpenAI API key Alternativement, nous avons un support natif pour Gemini, Ollama, LiteLLM, le . guide Vous pouvez choisir votre fournisseur de LLM préféré et travailler entièrement sur site. Définir le flux d’indexation Ce projet démontre un exemple un peu plus complet de compréhension des métadonnées plus proche des cas d'utilisation du monde réel. Vous verrez à quel point il est facile d'atteindre ce design par CocoIndex dans 100 lignes de logique d'indexation - . Le code Pour mieux vous aider à naviguer dans ce que nous allons traverser, voici un diagramme de flux. Importer une liste de documents en 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. Zoom dans les étapes. Importer les papiers @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), ) créera une table avec des sous-champs ( , à , à flow_builder.add_source filename content Nous pouvons nous référer au Pour plus de détails. Documentation Extraire et collecter des métadonnées Extrait de la première page pour les informations de base Définissez une fonction personnalisée pour extraire la première page et le nombre de pages du 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()) Mettez cela dans votre flux. Nous extrayons des métadonnées de la première page pour minimiser les coûts de traitement, car l'ensemble du PDF peut être très grand. with data_scope["documents"].row() as doc: doc["basic_info"] = doc["content"].transform(extract_basic_info) Après cette étape, vous devriez avoir les informations de base de chaque papier. Renseignements de base Nous convertirons la première page en Markdown à l'aide de Marker. Alternativement, vous pouvez facilement brancher votre analyseur PDF préféré, comme Docling. Définissez une fonction de conversion de marqueur et cachez-la, car son initialisation est intensive en ressources. Cela garantit que la même instance de convertisseur est réutilisée pour différents fichiers d'entrée. @cache def get_marker_converter() -> PdfConverter: config_parser = ConfigParser({}) return PdfConverter( create_model_dict(), config=config_parser.generate_config_dict() ) Plug it into a custom function. @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 Passez-le à votre transformation with data_scope["documents"].row() as doc: doc["first_page_md"] = doc["basic_info"]["first_page"].transform( pdf_to_markdown ) Après cette étape, vous devriez avoir la première page de chaque papier au format Markdown. Extract basic info with LLM CocoIndex soutient nativement l'extraction structurée LLM avec des schémas complexes et nichés. Si vous êtes intéressé à en savoir plus sur les schémas niché, consultez . Cet article @dataclasses.dataclass class PaperMetadata: """ Metadata for a paper. """ title: str authors: list[Author] abstract: str Pliez-le dans le Avec une classe de données définie, CocoIndex analysera automatiquement la réponse LLM dans la classe de données. 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.", ) ) After this step, you should have the metadata of each paper. Collecter des métadonnées papier 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"], ) Recueillez tout ce dont vous avez besoin :) Collectif to Informations auteur filename auteur fileté Nous avons déjà extrait la liste des auteurs.Ici, nous voulons collecter auteur → papiers dans un tableau distinct pour construire une fonctionnalité de recherche. Collecte par l’auteur. 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"], ) Calculer et recueillir des embeddings Titre doc["title_embedding"] = doc["metadata"]["title"].transform( cocoindex.functions.SentenceTransformerEmbed( model="sentence-transformers/all-MiniLM-L6-v2" ) ) abstrait Diviser les abstracts en morceaux, intégrer chaque morceau et recueillir leurs intégrations. Parfois, l’abstraction peut être très longue. 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, ) Après cette étape, vous devriez avoir les morceaux abstraits de chaque papier. Mélangez chaque morceau et recueillez leurs entrées. with doc["abstract_chunks"].row() as chunk: chunk["embedding"] = chunk["text"].transform( cocoindex.functions.SentenceTransformerEmbed( model="sentence-transformers/all-MiniLM-L6-v2" ) ) Après cette étape, vous devriez avoir les intégrations des morceaux abstraits de chaque papier. Collecte des 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"], ) Exportations Enfin, nous exportons les données vers 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, ) ], ) Dans cet exemple, nous utilisons PGVector comme entrepôts/ Avec CocoIndex, vous pouvez faire un commutateur de ligne sur d'autres bases de données vectorielles prises en charge telles que Qdrant, voir ceci Pour plus de détails. Guide Nous visons à normaliser les interfaces et à en faire comme construire LEGO. Voir dans CocoInsight étape par étape Vous pouvez parcourir le projet étape par étape dans à voir CocoInsight Exactement comment chaque champ est construit et ce qui se passe derrière les scènes. Rechercher l'index Vous pouvez vous référer à cette section de à propos Textes intégrés Comment construire une requête contre les embeddings. Pour le moment, CocoIndex ne fournit pas d'interface de requête supplémentaire. Nous pouvons écrire SQL ou compter sur le moteur de requête par le stockage cible. De nombreuses bases de données ont déjà optimisé les implémentations de requêtes avec leurs propres meilleures pratiques L'espace de requête dispose d'excellentes solutions pour la requête, le rearrangement et d'autres fonctionnalités liées à la recherche. Si vous avez besoin d'aide avec l'écriture de la requête, s'il vous plaît se sentir libre de nous contacter à . discorde nous soutenir Nous améliorons constamment, et d'autres fonctionnalités et exemples viennent bientôt. Si cet article vous est utile, s'il vous plaît nous donner une étoile pour nous aider à grandir. Github Merci pour la lecture !