Så här bygger du ett RAG-system på 10 rader Python utan ramverk

Lås upp kraften i RAG (Retrieval Augmented Generation) med denna 10-raders Python-kodtutorial. Dyk in i informationshämtning, bygg ditt eget dokumentsystem och utnyttja LLM:er för robusta svar - allt utan att förlita dig på komplexa ramverk. Optimera ditt innehåll för SEO och engagemang.

15 februari 2025

party-gif

Upptäck hur du bygger ett kraftfullt hämtnings-förstärkt generationssystem (RAG) från grunden på bara 10 rader Python-kod, utan att förlita dig på några externa ramverk. Detta koncisa men omfattande tillvägagångssätt ger dig en djup förståelse för kärnkomponenterna, vilket ger dig möjlighet att skapa robusta och anpassningsbara AI-drivna lösningar för dina dokumentbaserade applikationer.

Förstå konceptet Retrieval Augmented Generation (RAG)

Retrieval Augmented Generation (RAG) är en teknik som används för informationshämtning från dina egna dokument. Det involverar en tvåstegsprocess:

  1. Skapande av kunskapsbas: Dokumenten delas upp i mindre underdokument, och inbäddningar beräknas för varje del. Dessa inbäddningar och de ursprungliga delarna lagras, vanligtvis i en vektorlagring.

  2. Hämtning och generering: När en ny användarfråga kommer in beräknas en inbäddning för frågan. De mest relevanta delarna hämtas genom att jämföra frågeninbäddningen med de lagrade delinbäddningarna. De hämtade delarna läggs sedan till den ursprungliga frågan och matas in i en språkmodell (LLM) för att generera det slutliga svaret.

Denna uppställning kallas en "retrieval-augmented generation"-pipeline, eftersom LLM:s generering förstärks med den relevanta information som hämtats från kunskapsbasen.

Medan det finns ramverk som Langchain och LlamaIndex som förenklar implementeringen av RAG-pipelines, kan kärnkomponenterna byggas med enbart Python, en inbäddningsmodell och en LLM. Detta tillvägagångssätt ger en bättre förståelse för de underliggande koncepten och möjliggör mer flexibilitet i anpassningen av pipelinen.

Lär dig dela upp dokument i stycken

När man bygger ett retrieval-augmented generation (RAG)-system är ett av de viktiga stegen att dela upp indatadokumenten i mindre, mer hanterbara delar. I det här exemplet delar vi upp indatadokumentet (en Wikipediaartikel) i stycken.

Motiveringen bakom detta tillvägagångssätt är att stycken ofta representerar logiska informationsenheter som kan hämtas effektivt och användas för att förstärka användarens fråga. Genom att dela upp dokumentet på detta sätt kan vi bättre identifiera de mest relevanta avsnitten att inkludera i det slutliga svaret.

Koden för det här steget ser ut så här:

# Dela upp dokumentet i stycken
chunks = [paragraph.strip() for paragraph in text.split('\n') if paragraph.strip()]

Här använder vi ett enkelt tillvägagångssätt där vi delar upp indatatexten på nyradstecken för att extrahera de enskilda styckena. Vi tar sedan bort eventuella ledande eller avslutande blanksteg från varje stycke för att säkerställa en ren representation.

Den resulterande chunks-listan innehåller de enskilda styckena, som sedan kan bäddas in och lagras i vektorlagringen. Detta gör det möjligt att effektivt hämta de mest relevanta styckena baserat på användarens fråga och inkludera dem i det slutliga svaret som genereras av språkmodellen.

Bädda in dokument och användarfrågor med hjälp av meningsvektorer

För att bädda in dokumentstyckena och användarfrågan kommer vi att använda Sentence Transformers-biblioteket. Detta bibliotek tillhandahåller förtränade modeller som kan generera högkvalitativa inbäddningar för text.

Först laddar vi inbäddningsmodellen:

from sentence_transformers import SentenceTransformer
embedding_model = SentenceTransformer('all-mpnet-base-v2')

Nästa steg är att beräkna inbäddningarna för dokumentstyckena:

chunk_embeddings = embedding_model.encode(chunks, normalize_embeddings=True)

Här är chunks en lista med textstyckena från dokumentet. Alternativet normalize_embeddings=True säkerställer att inbäddningarna normaliseras, vilket är viktigt för den senare similaritetsberäkningen.

För att få inbäddningen för användarfrågan kör vi helt enkelt:

query_embedding = embedding_model.encode([user_query], normalize_embeddings=True)[0]

Nu har vi inbäddningarna för dokumentstyckena och användarfrågan, som vi kan använda för hämtningssteget.

Hämta relevanta stycken baserat på kosinussimilaritet

För att hämta de mest relevanta styckena baserat på användarfrågan måste vi först beräkna cosinussimilariteten mellan frågeninbäddningen och inbäddningarna för dokumentstyckena. Här är hur vi kan göra det:

  1. Beräkna inbäddningen för användarfrågan med samma inbäddningsmodell som för dokumentstyckena.
  2. Beräkna prickprodukten mellan frågeninbäddningen och varje av dokumentstyckets inbäddningar. Detta ger oss cosinussimilaritetsskalorna.
  3. Sortera dokumentstyckena baserat på cosinussimilaritetsskalorna och välj de K högst rankade styckena.
  4. Hämta texten i de valda styckena för att använda som kontext för språkmodellen.

De viktiga stegen är:

  1. Beräkna frågeninbäddning
  2. Beräkna cosinussimilaritetsskalor
  3. Sortera och välj de K högst rankade styckena
  4. Hämta stycketextinnehåll

Detta enkla tillvägagångssätt gör det möjligt att effektivt hämta den mest relevanta informationen från dokumentsamlingen för att förstärka användarens fråga innan den skickas till språkmodellen för generering.

Förstärk användarfrågan med hämtade stycken och generera svar med hjälp av OpenAI GPT-4

För att generera ett svar med hjälp av de hämtade styckena och användarfrågan skapar vi först en prompt som innehåller de relevanta styckena och användarfrågan. Vi använder sedan OpenAI GPT-4-modellen för att generera ett svar baserat på denna prompt.

Här är koden:

# Hämta användarfrågan
user_query = "Vad är huvudstaden i Frankrike?"

# Hämta de mest relevanta styckena baserat på användarfrågan
top_chunk_ids = [6, 8, 5]
top_chunks = [chunks[i] for i in top_chunk_ids]

# Skapa prompten
prompt = "Använd följande kontext för att besvara frågan nedan. Om du inte vet svaret, säg att du inte vet och försök inte uppfinna ett svar.\n\nKontext:\n"
for chunk in top_chunks:
    prompt += chunk + "\n\n"
prompt += f"\nFråga: {user_query}"

# Generera svaret med hjälp av OpenAI GPT-4
response = openai_client.chat.create(
    model="gpt-4",
    messages=[
        {"role": "user", "content": prompt}
    ],
    max_tokens=1024,
    n=1,
    stop=None,
    temperature=0.7,
).choices[0].message.content

print(response)

I den här koden hämtar vi först de mest relevanta styckena baserat på användarfrågan genom att hitta de 3 högst rankade styckena. Vi skapar sedan en prompt som innehåller dessa stycken och användarfrågan. Slutligen använder vi OpenAI GPT-4-modellen för att generera ett svar baserat på denna prompt.

Det genererade svaret kommer att vara ett koncist och relevant svar på användarens fråga, med hjälp av informationen från de hämtade styckena.

Slutsats

I den här självstudien har vi lärt oss hur man bygger ett komplett chatsystem med ett dokumenthämtningssystem med bara 10 rader Python-kod. Vi har täckt de viktiga komponenterna i en Retrieval Augmented Generation (RAG)-pipeline, inklusive skapande av kunskapsbas, dokumentuppdelning, inbäddning, hämtning och generering med hjälp av en stor språkmodell.

De viktigaste lärdomarna är:

  1. Du kan implementera en grundläggande RAG-pipeline utan att behöva komplexa ramverk som Langchain eller LlamaIndex. Ren Python och några bibliotek är tillräckligt.
  2. Att dela upp dina dokument baserat på strukturen (t.ex. stycken) är en enkel men effektiv strategi för de flesta användningsfall.
  3. Att bädda in dokumentstyckena och användarfrågan, och sedan beräkna similaritetsskalor, gör det möjligt att hämta den mest relevanta informationen för att förstärka användarens fråga.
  4. Att integrera de hämtade styckena med användarens fråga och mata in det i en stor språkmodell möjliggör genereringen av ett relevant och informativt svar.

Medan det här exemplet ger en solid grund finns det många möjligheter att bygga mer robusta och avancerade RAG-system. Ramverk som Langchain och LlamaIndex kan vara till hjälp när man integrerar med olika vektorlager och språkmodeller. Men att börja med en ren Python-implementation kan hjälpa dig att bättre förstå kärnkoncepten och komponenterna i en RAG-pipeline.

Om du är intresserad av att utforska mer avancerade RAG-tekniker rekommenderar jag att du tittar på min kurs "RAG Beyond Basics", som går djupare in på att bygga komplexa, produktionsklara RAG-system.

FAQ