Obsługa długich tekstów

Podczas pracy z plikami, takimi jak pliki PDF, możesz napotkać tekst, który przekracza okno kontekstowe modelu językowego. Aby przetworzyć ten tekst, rozważ następujące strategie:

  1. Zmień LLM Wybierz inny LLM, który obsługuje większe okno kontekstowe.
  2. Brute Force Podziel dokument na fragmenty i wyodrębnij zawartość z każdego fragmentu.
  3. RAG Podziel dokument na fragmenty, zindeksuj fragmenty i wyodrębnij zawartość tylko z podzbioru fragmentów, które wydają się "istotne".

Pamiętaj, że te strategie mają różne kompromisy, a najlepsza strategia zależy prawdopodobnie od aplikacji, którą projektujesz!

Konfiguracja

Potrzebujemy przykładowych danych! Pobierzmy artykuł o samochodach z Wikipedii i załadujmy go jako dokument LangChain.

import re
import requests
from langchain_community.document_loaders import BSHTMLLoader

response = requests.get("https://en.wikipedia.org/wiki/Car")
with open("car.html", "w", encoding="utf-8") as f:
    f.write(response.text)
loader = BSHTMLLoader("car.html")
document = loader.load()[0]
document.page_content = re.sub("\n\n+", "\n", document.page_content)
print(len(document.page_content))
78967

Zdefiniuj schemat

Tutaj zdefiniujemy schemat w celu wyodrębnienia kluczowych wydarzeń z tekstu.

from typing import List, Optional
from langchain.chains import create_structured_output_runnable
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI

class KeyDevelopment(BaseModel):
    """Informacje o wydarzeniach w historii samochodów."""

    year: int = Field(
        ..., description="Rok, w którym nastąpiło istotne historyczne wydarzenie."
    )
    description: str = Field(
        ..., description="Co wydarzyło się w tym roku? Jaka była ta historia?"
    )
    evidence: str = Field(
        ...,
        description="Powtórz w dosłowny sposób zdanie/zdania, z których wyodrębniono informacje o roku i opisie",
    )
class ExtractionData(BaseModel):
    """Wyekstrahowane informacje o kluczowych wydarzeniach w historii samochodów."""

    key_developments: List[KeyDevelopment]

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "Jesteś ekspertem w identyfikowaniu kluczowych historycznych wydarzeń w tekście. "
            "Wyodrębnij tylko istotne historyczne wydarzenia. Nie wyodrębniaj niczego, jeśli w tekście nie ma istotnych informacji.",
        ),
        ("human", "{text}"),
    ]
)
llm = ChatOpenAI(
    model="gpt-4-0125-preview",
    temperature=0,
)

extractor = prompt | llm.with_structured_output(
    schema=ExtractionData,
    method="function_calling",
    include_raw=False,
)
/home/eugene/.pyenv/versions/3.11.2/envs/langchain_3_11/lib/python3.11/site-packages/langchain_core/_api/beta_decorator.py:86: LangChainBetaWarning: The function `with_structured_output` is in beta. It is actively being worked on, so the API may change.
  warn_beta(

Metoda brutalnej siły

Podziel dokumenty na fragmenty, tak aby każdy fragment mieścił się w oknie kontekstowym LLMs.

from langchain_text_splitters import TokenTextSplitter

text_splitter = TokenTextSplitter(
    chunk_size=2000,
    chunk_overlap=20,
)

texts = text_splitter.split_text(document.page_content)

Użyj funkcji .batch, aby uruchomić wyodrębnianie równolegle dla każdego fragmentu!

tip

Często można użyć .batch(), aby zrównoleglić wyodrębnienia! batch wykorzystuje wewnętrznie pulę wątków do pomocy w zrównoleglaniu obciążeń pracy.

Jeśli Twój model jest dostępny za pośrednictwem interfejsu API, ta funkcja prawdopodobnie przyspieszy proces wyodrębniania!

first_few = texts[:3]

extractions = extractor.batch(
    [{"text": text} for text in first_few],
    {"max_concurrency": 5},  # ograniczaj równoległość przez określenie maksymalnej równoległości!
)

Scalanie wyników

Po wydobyciu danych z różnych fragmentów, będziemy chcieli połączyć zebrane informacje.

kluczowe_zdarzenia = []

for ekstrakcja in ekstrakcje:
    kluczowe_zdarzenia.extend(ekstrakcja.kluczowe_zdarzenia)

kluczowe_zdarzenia[:20]
[KeyDevelopment(year=1966, description="The Toyota Corolla began production, recognized as the world's best-selling automobile.", evidence="The Toyota Corolla has been in production since 1966 and is recognized as the world's best-selling automobile."),
 KeyDevelopment(year=1769, description='Nicolas-Joseph Cugnot built the first steam-powered road vehicle.', evidence='French inventor Nicolas-Joseph Cugnot built the first steam-powered road vehicle in 1769.'),
 KeyDevelopment(year=1808, description='François Isaac de Rivaz designed and constructed the first internal combustion-powered automobile.', evidence='French-born Swiss inventor François Isaac de Rivaz designed and constructed the first internal combustion-powered automobile in 1808.'),
 KeyDevelopment(year=1886, description='Carl Benz patented his Benz Patent-Motorwagen, inventing the modern car.', evidence='The modern car—a practical, marketable automobile for everyday use—was invented in 1886, when German inventor Carl Benz patented his Benz Patent-Motorwagen.'),
 KeyDevelopment(year=1908, description='The 1908 Model T, an affordable car for the masses, was manufactured by the Ford Motor Company.', evidence='One of the first cars affordable by the masses was the 1908 Model T, an American car manufactured by the Ford Motor Company.'),
 KeyDevelopment(year=1881, description='Gustave Trouvé demonstrated a three-wheeled car powered by electricity.', evidence='In November 1881, French inventor Gustave Trouvé demonstrated a three-wheeled car powered by electricity at the International Exposition of Electricity.'),
 KeyDevelopment(year=1888, description="Bertha Benz undertook the first road trip by car to prove the road-worthiness of her husband's invention.", evidence="In August 1888, Bertha Benz, the wife of Carl Benz, undertook the first road trip by car, to prove the road-worthiness of her husband's invention."),
 KeyDevelopment(year=1896, description='Benz designed and patented the first internal-combustion flat engine, called boxermotor.', evidence='In 1896, Benz designed and patented the first internal-combustion flat engine, called boxermotor.'),
 KeyDevelopment(year=1897, description='Nesselsdorfer Wagenbau produced the Präsident automobil, one of the first factory-made cars in the world.', evidence='The first motor car in central Europe and one of the first factory-made cars in the world, was produced by Czech company Nesselsdorfer Wagenbau (later renamed to Tatra) in 1897, the Präsident automobil.'),
 KeyDevelopment(year=1890, description='Daimler Motoren Gesellschaft (DMG) was founded by Daimler and Maybach in Cannstatt.', evidence='Daimler and Maybach founded Daimler Motoren Gesellschaft (DMG) in Cannstatt in 1890.'),
 KeyDevelopment(year=1902, description='A new model DMG car was produced and named Mercedes after the Maybach engine.', evidence='Two years later, in 1902, a new model DMG car was produced and the model was named Mercedes after the Maybach engine, which generated 35 hp.'),
 KeyDevelopment(year=1891, description='Auguste Doriot and Louis Rigoulot completed the longest trip by a petrol-driven vehicle using a Daimler powered Peugeot Type 3.', evidence='In 1891, Auguste Doriot and his Peugeot colleague Louis Rigoulot completed the longest trip by a petrol-driven vehicle when their self-designed and built Daimler powered Peugeot Type 3 completed 2,100 kilometres (1,300 mi) from Valentigney to Paris and Brest and back again.'),
 KeyDevelopment(year=1895, description='George Selden was granted a US patent for a two-stroke car engine.', evidence='After a delay of 16 years and a series of attachments to his application, on 5 November 1895, Selden was granted a US patent (U.S. patent 549,160) for a two-stroke car engine.'),
 KeyDevelopment(year=1893, description='The first running, petrol-driven American car was built and road-tested by the Duryea brothers.', evidence='In 1893, the first running, petrol-driven American car was built and road-tested by the Duryea brothers of Springfield, Massachusetts.'),
 KeyDevelopment(year=1897, description='Rudolf Diesel built the first diesel engine.', evidence='In 1897, he built the first diesel engine.'),
 KeyDevelopment(year=1901, description='Ransom Olds started large-scale, production-line manufacturing of affordable cars at his Oldsmobile factory.', evidence='Large-scale, production-line manufacturing of affordable cars was started by Ransom Olds in 1901 at his Oldsmobile factory in Lansing, Michigan.'),
 KeyDevelopment(year=1913, description="Henry Ford began the world's first moving assembly line for cars at the Highland Park Ford Plant.", evidence="This concept was greatly expanded by Henry Ford, beginning in 1913 with the world's first moving assembly line for cars at the Highland Park Ford Plant."),
 KeyDevelopment(year=1914, description="Ford's assembly line worker could buy a Model T with four months' pay.", evidence="In 1914, an assembly line worker could buy a Model T with four months' pay."),
 KeyDevelopment(year=1926, description='Fast-drying Duco lacquer was developed, allowing for a variety of car colors.', evidence='Only Japan black would dry fast enough, forcing the company to drop the variety of colours available before 1913, until fast-drying Duco lacquer was developed in 1926.')]

How can I assist you today?

podejście oparte na RAG

Kolejnym prostym pomysłem jest podzielenie tekstu na kawałki, ale zamiast wyodrębniać informacje z każdego kawałka, skup się tylko na najbardziej istotnych fragmentach.

uwaga

Może być trudno określić, które kawałki są istotne.

Na przykład w artykule o samochodach, z którego korzystamy tutaj, większość artykułu zawiera kluczowe informacje dotyczące rozwoju. Dlatego korzystając z RAG, prawdopodobnie odrzucimy wiele istotnych informacji.

Sugerujemy przetestowanie tego podejścia w swoim przypadku użycia i określenie, czy działa czy nie.

Oto prosty przykład, który polega na użyciu FAISS do przechowywania wektorów.

from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document
from langchain_core.runnables import RunnableLambda
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import CharacterTextSplitter

texts = text_splitter.split_text(document.page_content)
vectorstore = FAISS.from_texts(texts, embedding=OpenAIEmbeddings())

retriever = vectorstore.as_retriever(
    search_kwargs={"k": 1}
)  # Wyodrębnij tylko z pierwszego dokumentu

W tym przypadku extractor RAG patrzy tylko na pierwszy dokument.

rag_extractor = {
    "text": retriever | (lambda docs: docs[0].page_content)  # pobierz zawartość pierwszego dokumentu
} | extractor
results = rag_extractor.invoke("Główne wydarzenia związane z samochodami")
for key_development in results.key_developments:
    print(key_development)
year=1924 description="Pierwszy masowo produkowany samochód w Niemczech, Opel 4PS Laubfrosch, został wyprodukowany, co uczyniło Opel największym producentem samochodów w Niemczech z 37,5% udziału w rynku." evidence="Pierwszy masowo produkowany samochód w Niemczech, Opel 4PS Laubfrosch (Żabka drzewna), zszedł z taśmy produkcyjnej w Rüsselsheim w 1924 roku, szybko uczynił Opela największym producentem samochodów w Niemczech, posiadając 37,5 procent udziału w rynku."
year=1925 description='Morris miał 41% całkowitej brytyjskiej produkcji samochodów, dominując na rynku.' evidence='W 1925 roku Morris posiadał 41 procent całkowitej brytyjskiej produkcji samochodów.'
year=1925 description='Citroën, Renault i Peugeot wyprodukowały 550 000 samochodów we Francji, dominując na rynku.' evidence="Citroën zrobił to samo we Francji, wkrocząc na rynek samochodowy w 1919 roku; pomiędzy nimi a innymi tanimi samochodami, jak Renault 10CV i Peugeot 5CV, wyprodukowali 550 000 samochodów w 1925 roku."
year=2017 description='Produkcja samochodów na benzynę osiągnęła szczyt.' evidence='Produkcja samochodów na benzynę osiągnęła szczyt w 2017 roku.'

Typowe problemy

Różne metody mają swoje zalety i wady związane z kosztem, prędkością i dokładnością.

Uważaj na te problemy:

  • Podział treści oznacza, że LLM może nie wyodrębnić informacji, jeśli jest ona rozproszona po wielu kawałkach.
  • Duże pokrycie kawałków może powodować dwukrotne wyodrębnienie tych samych informacji, więc bądź gotowy do usunięcia zduplikowanych.
  • LLM może zmyślać dane. Jeśli szukasz pojedynczego faktu w dużym tekście i stosujesz brutalne podejście, możesz otrzymać więcej zmyślonych danych.