Tomáš Kubica

RAG bez magie, část 2

Vlastní data (RAG) pro vašeho AI agenta část druhá - graf znalostí a pokročilé metody

Knowledge graph, sumarizace konceptů a průchod grafem bez frameworkové magie. Pořád Python, PostgreSQL a filmová data.

V minulém díle jsme šli bez magie do vyzkoušení technik jako je full-textové a sémantické vyhledávání, query rewriting, reranking a hybridní search, takže jsme se neschovávali za nějaký framework nebo službu a ponořili se do Pythonu a PostgreSQL. Tím jsme dokázali našeho AI agenta obohatit o cílená data a odpovídat na zvídavé filmové otázky uživatelů.

Dnes budeme pokračovat pokročilejšími metodami a přestože se necháme inspirovat technikami jako je GraphRAG, uplácáme si to opět ze studijních důvodů sami.

Problém

některé otázky nejsou o jednom dokumentu, ale o agregaci, tématech, žánrech a vztazích.

Nápad

k filmům přidáme koncepty, hrany a sumarizace konceptů. Tedy malý knowledge graph.

Experiment

porovnám breath-first a depth-first varianty s průchodem grafu.

Pointa

pevně daný postup nestačí. Další meta-úroveň je plánování strategie přes LLM.

Proč tabulka přestává stačit

Minule jsme viděli, že základní přístup k RAGu je velmi úspěšný zejména u dotazů, pro jejichž zodpovězení je potřeba mít specifický detailní kontext. Typicky například dotaz na název filmu, na který si uživatel nemůže vzpomenout, ale je schopen říct o čem to bylo a přidat pár takových detailů.

Nicméně jsou otázky, pro jejichž odpovězení nestačí kontext jen několika filmů, ale jsou třeba o agregacích, konceptech, žánrech, scenériích a tak podobně. Pro jejich odpovězení je tak ideální mít jednak sumarizovaný pohled a dále představu o nějakých vlastnostech, kategoriích, jejich zástupcích a vzájemných vztazích.

Technicky řečeno nestačí nám tabulka, ale graf uzlů (node) a propojení podle různých vztahů (edge, hrana) s tím, že u každého konceptu (typu nodu) potřebujeme ještě sumarizaci a vysvětlení tohoto konceptu. Tedy chceme knowledge graph.

  • uzly typu Movie,
  • koncepty Genre, Character, Theme, Setting a Series,
  • hrany jako IN_GENRE, FEATURES_CHARACTER, INCLUDES_THEME, SET_IN, PART_OF_SERIES,
  • sumarizace konceptů vytvořené LLM nad filmy, které k nim patří.

V následující ukázce se budu volně inspirovat metodou GraphRAG, ale určitě se odchýlím, zjednoduším, abychom si to mohli postavit skutečně sami bez magie.

Vycházejme z předpokladu, že u filmů máme opět jen název a popisek, nic dalšího. V realitě filmů bychom některé graph informace měli v datové sadě vytažené rovnou - herce, režiséra apod. Podobně jako v GraphRAG použijme jazykový model (LLM) k extrakci konceptů jako je prostředí, postava, žánr, téma a série.

Na základě toho potom sestavíme celkový graf a pro každý tento koncept vezmeme filmy, které k němu patří a použijeme LLM k vytvoření sumarizace této vlastnosti.

Základním konceptem je vyhledání vstupních uzlů do grafu (nody jsou filmy, žánry a podobné koncepty) a pak nějaké procházení grafu (jdeme po hranách, spojnicích), kde objevujeme další uzly a případně kombinujeme s nějakým snížením počtu nalezených uzlů přes reranking nebo sémantickou podobnost z minulého dílu.

Otázka je, na jaké vstupní body se zaměřit.

Strategie Kde začnu Co tím získám Co riskuji
Depth-first konkrétní filmy detail a konkrétní příklady chybí globálnější kontext
Breath-first koncepty a jejich sumarizace nadhled, témata, agregace méně vazby na konkrétní filmy
Traversal pohyb po hranách propojení konceptů a filmů může explodovat počet kandidátů
Reranking redukce kandidátů menší a relevantnější kontext pořád nemusí pochopit záměr otázky

Tím to pro dnešek skončí, ale ještě chci připravit další díl, ve kterém budeme s využitím LLM plánovat a iterativně procházet celým procesem podobně, jako je to u metody DRIFT (Dynamic Reasoning and Inference with Flexible Traversal). Tohle, i když ve značně vylepšené a komplikovanější formě, je pod kapotou Deep Research na platformách jako je Perplexity, Google Gemini nebo ChatGPT.

Jak graf postavím

Jako minule, otevřete si prosím k článku patřičný notebook - odkaz na konkrétní dám do každé kapitolky.

  1. 1
    Extrakce konceptů

    z popisu filmu vytáhneme žánry, postavy, témata, prostředí a série.

  2. 2
    Uložení grafu

    v PostgreSQL přes Apache AGE založíme nody a hrany.

  3. 3
    Sumarizace konceptů

    pro každý koncept necháme LLM shrnout, co znamená v rámci naší kolekce filmů.

  4. 4
    Vektorizace

    filmy i koncepty dostanou embedding pro sémantické hledání.

  5. 5
    Dotazování

    kombinujeme semantic search, traversal a reranking.

Nejprve musíme extrahovat koncepty z popisu filmů. Tady je notebook.

Víceméně jednoduše jdeme film po filmu s jednoduchým promptem:

Extract structured information from the following movie title and overview.

Title: {{title}}
Overview: {{overview}}

Please extract the following information in JSON format:
- genres: [list of genres]
- characters: [list of character names]
- themes: [list of thematic elements]
- setting: [time periods and/or locations]
- series: [list of series or saga names]

If there are no relevant details for a category, return an empty array.

doplněným o structure output, tedy o předvídatelný formát výstupu:

class EnhancedMovie(BaseModel):
    genres: List[str]
    characters: List[str]
    themes: List[str]
    setting: List[str]
    series: List[str]

Všechno naházím do JSON souborů a nakonec z něj vytvořím jeden velký. Výsledek je tady.

Uložení do PostgreSQL hledejte v tomto notebooku.

PostgreSQL má k dispozici speciální extension, která z klasické relační databáze udělá grafovou podporující rozšířený dotazovací jazyk cypher. Alternativou je Gremlin jazyk, ale to tato extension nepodporuje. Místo nějaké specializované databáze typu Neo4J tak můžeme použít Apache AGE, která je kompatibilní s Azure Database for PostgreSQL Flexible Server.

Nejdřív pozakládáme nody typu Movie a následně nody typu Genre, Character, Theme, Setting a Series. Zatím v nich nebude víc, než jméno, například horor nebo Harry Potter. Pak přidáme jejich hrany.

Trochu nepříjemné pro mě je, že cypher query se musí uzavřít do SQL query, čímž mi nefungovalo použití parametrů v psycopg2, takže jsem musel jít přes f-stringy a escapovat jednoduché uvozovky.

Jak se zjistí později tahle "rozdvojenost" je nepříjemným limitem jazyka, kdy bude obtížné kombinovat sémantické vyhledávání (vyžaduje klasické PostgreSQL tabulky) s grafovým - jak uvidíte, budu to lepit v Pythonu. Což ale vzhledem k potřebě rerankingu nakonec nevadilo.

Rozhodně se plánuji podívat na využití Cosmos DB a jeho schopnost vektorového hledání v kombinaci s graf dotazy, jestli to tam není lepší.

Jdeme do notebooku pro sumarizaci.

V dalším kroku potřebuji pro jednotlivé nalezené koncepty (Sci-Fi, 13. století, Star Wars, Praha, Rytíř, Albert Einstein) udělat sumarizaci, tedy shrnout, co to je. LLM naservíruji seznam příslušných filmů.

U některých konceptů, třeba žánr drama, vyteču z kontextového okna - to jsem pro jednoduchou ukázku nechtěl řešit, tak to prostě oříznu. Ono jich zas tak moc co se nevejdou není.

TASK:
Create a comprehensive summary of the "{{name}}" character archetype based on movies featuring this type of character. Make sure all information is based on the movies in the collection and not on external knowledge.

Instructions:
1. Define essential traits, motivations, and narrative functions of the "{{name}}" archetype.
2. Provide examples of at least 5 movies prominently featuring this archetype.
3. Describe typical audience expectations and emotional responses associated with this archetype.
4. Highlight common narrative arcs and character development patterns involving this archetype.
5. Explain how this archetype typically interacts with specific genres, settings, or themes.
6. Include aggregated data from the movie collection to support your summary.

Vektorizaci uděláme v notebooku.

Tady bylo nutné vyřešit jeden zádrhel. AGE pod kapotou funguje tak, že v PostgreSQL vytvoří tabulku nodů s unikátním ID a atributem properties. Když potom u nodů uložím nějaké jejich parametry, například popis filmu nebo sumarizace konceptu, ukládá to do properties sloupečku jako JSON.

Pokud bych do toho přidal embedding jako vlastnost nodu, skončí to někde uvnitř JSON a nebude možné použít pgvector pro hledání podobností. To je nepříjemný limit, který jsem vyřešil tak, že držím separátní standardní tabulky čistě pro embedding - id a vektor per film nebo koncepty.

Velké finále: ptáme se přes graf

Teď přichází velké finále a jdeme odpovídat na otázky. Hledejte v tomto notebooku.

Rozšířil jsem okruh otázek, ať je to zajímavější:

questions = [
    "What movies are about Abby?",
    "I have seen all Star Wars movies and would like tips for something similar I can watch next.",
    "What is the most common genre of the movies where one of key figures is called Mark?",
    "Are there any movies where Prague takes major role and present city as mysterious and ancient?",
    "When movies about drugs are concerned, is it usually rather serious or funny?",
    "What are some movies featuring a strong female lead that also involve adventure?",
    "Which Western films feature outlaws riding to their doom in the American Southwest?"
]

Ty otázky schválně nejsou všechny stejného typu. Některé hledají konkrétní film, jiné podobnost, jiné agregaci a jiné atmosféru.

Otázka Pozorování
Abby Setrvalý stav.
Star Wars Méně filmů, ale zdá se mi přesnější. Stále se nám to nedaří plně cracknout.
Mark Asi lepší, globální kontext pomohl. Nicméně tady bych potřeboval asi jít a ručně to rozsoudit.
Praha Zajímavá změna filmu. Pokud vím Iluzionista se odehrává ve Vídni, ale film se točil hlavně v Praze. Tady se nám do extrakce evidentně vloudila implicitní znalost LLM, které ji dělalo.
Drogy Tradičně dobré, trochu více kontextu okolo.
Silná žena + adventure Kupodivu spíše zhoršení, dané asi menším množstvím filmů v kroku 1.
Westerny Také spíše zhoršení.

Pojďme najít vstupní uzly a já to udělám přes všechny typy, tedy jak popisky filmů, tak popisky všech konceptů (Character, Genre, Theme, Series, Setting). Uděláme sémantický search na popisky, vezmeme 20 nejbližších a naservírujeme je LLM.

Otázka Pozorování
Abby S obrovskou převahou vybírá Character nody s Abby nebo podobným jménem, ale odpověď není nic moc, protože sumarizace konceptů neobsahují dost referencí na filmy.
Star Wars Chytá se na Setting a Series, například Imperial Era, Clone Wars Era, Various Planets nebo Star Wars Saga. Odpověď na filmy je tentokrát velmi slušná.
Mark Všechno nody typu Character, dle očekávání.
Praha Vrací hlavně Setting, byť Praha tam není; možná ji LLM nevyextrahovalo jako samostatný koncept. Hodně tam jsou Evropská města a Czechoslovakia.
Drogy Hodně Theme (Drug Trade, Substance Abuse) a důležité Genre (Stoner a komedie). Díky příkladům v sumarizacích je odpověď slušná.
Silná žena + adventure Nachází hodně uzlů Character.
Westerny z jihozápadu Logicky docela dost Setting.

Cílem dnes bylo vyzkoušet si koncepty a jako demonstrace to myslím dobře poslouží. Reálné úlohy budou větší a složitější a věřím, že síla těchto postupů se v nich projeví ještě výrazněji.

Pro mě je fascinující hlavně to, jak breath-first i bez jednotlivých filmů dává zajímavé výsledky a jsou kvalitativně jiné: víc porozumění tématům, ale menší znalost detailů - což je logické.

Breath-first

dobrý pro otázky nad tématy, žánry, náladou a agregacemi.

Depth-first

dobrý pro konkrétní filmové tipy a detailní odpovědi.

Traversal

umí přidat souvislosti, ale někdy zhorší přesnost.

Největší problém

postup máme dopředu daný a spoléháme, že sémantika a reranking všechno vyřeší.

Přitom víme, že právě u Star Wars tohle tragicky nefunguje - ani embedding ani reranking není dostatečně silný pro pochopení, že hledáme "jako Star Wars" a ne Star Wars.

Kam bych to posunul dál

Jak by to vypadalo, kdybychom využili zmíněné techniky, ale na příkladu Star Wars volili postup ručně?

  1. 1
    Nezačínal bych filmy

    z otázky bych usoudil, že přímo hledat filmy není optimální a začal bych koncepty - zejména Theme, Setting a Genre. Character nebo Series moc ne, to mi nenajde filmy Star Wars podobné, ale spíše stejné.

  2. 2
    Rozpadl bych dotaz na větve

    pro každý z těchto konceptů bych možná otázku přeformuloval, aby sémantický search byl přesnější. Query rewriting by vytvořil plán tří větví.

  3. 3
    Nechal bych LLM vybrat koncepty

    z vrácených názvů nejbližších konceptů bych se obrátil na LLM s tím, které z nich se nejlépe hodí k otázce. Tady to bude o pokročilém LLM, které pochopí, že nechceme koncepty "Star Wars", "Imperial Era" nebo "Dark Side", ale spíše "Sci-Fi", "Space", "Planets", "Galaxy" a tak podobně.

  4. 4
    Procházel bych graf k filmům

    na vybraných konceptech bych začal procházet graf směrem k filmům. Po nějaké době bych se zastavil a zeptal se LLM na názor. Máme teď lepší podklady pro odpověď a můžeme hledání ukončit nebo nám pořád něco chybí?

  5. 5
    Dovolil bych změnu strategie

    pokud něco chybí, LLMku už na začátku představíme naše možnosti - co jak se dá vyhledávat (full-text, depth, breath, ...). Očekávám, že možná změní strategii a třeba vytvoří klíčová slova pro full-text a řekne si o něj nebo vygeneruje něco jako doplňující otázku pro sémantiku.

  6. 6
    Iteroval bych do limitu

    takhle to půjde tak dlouho, dokud něco nevyprší nebo dokud nebude mít LLM pocit, že už je podkladů dost a lze vytvořit odpověď.

Nějak takhle funguje deep research a už jsou k tomu i různé open source frameworky nebo designové návrhy, třeba DRIFT. Nicméně stejně jako minule a dnes, pokusím se jen základní myšlenky vzít a poskládat něco od nuly.

Tak zas někdy příště.

Někdy nestačí lepší similarity search; agent musí umět rozhodnout, kudy se za odpovědí vydat.