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.
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.
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. V některých případech bychom se měli pustit do další vrstvy, tedy sjednotit v každém konceptu výskyty ještě do nějakých nadkategorií (třeba kategorie středověk, do které budou spadat různé podrobnější časové éry) a opět sumarizovat (teď to budou sumarizace sumarizací) a začlenit do grafu.
V tomto bodě nutno říct, že tato přípravná fáze je velmi náročná na čas a spotřebované tokeny LLM. Navíc pokud se k datům něco přidá, musí se pro ně postup spustit také. To je nevýhoda tohoto přístupu a objevují se i varianty, které část těch volání LLM odkládají na později, ad-hoc, až, když je to potřeba - například LazyGraphRAG.
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. Ukážeme si depth-first přístup, kdy nejprve budeme prohledávat krajní uzly ve formě filmů (vlastně to co minule) a následně budeme přes graf hledat nadřazené koncepty, jejich sumarizace přidáme do kontextu a ještě se vrátíme zpátky k filmům, které mají co nejvíce konceptů společných. Druhý přístup je breath-first, kdy jako vstupní nody budeme prohledávat koncepty a jejich sumarizace a teprve z nich se posuneme k filmům.
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.
Jako minule, otevřete si prosím k článku patřičný notebook - odkaz na konkrétní dám do každé kapitolky.
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:
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) a přidáme jejich hrany (IN_GENRE, FEATURES_CHARACTER, INCLUDES_THEME, SET_IN, PART_OF_SERIES).
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í CosmosDB 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í).
Takhle třeba vypadá prompt pro extrakci postav:
TASK:
Create a comprehensive summary of the "" 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 "" 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 a když potom u nodů uložím nějaké jejich parametry (například popis filmu nebo sumarizace konceptu), tak to ukládá do properties sloupečku jako JSON. Pokud bych do toho přidal embedding (vektor) jako vlastnost nodu, skončí to někde uvnitř JSON a nebude možné použít pgvektor 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.
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?"
]
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.
Všimněte si, že otázka na Abby s obrovskou převahou vybírá typ nodu Character a u každého je něco s Abby nebo podobným jménem. To je super, jenže sumarizace těchto konceptů neobsahuje reference na filmy (možná jen nějaké příklady zástupců), takže výsledná odpověď není nic moc.
Otázka na “něco jako Star Wars” se hodně chytala na Setting (Imperial Era, Clone Wars Era, Various Planets) a je tam Series - Star Wars Saga. Odpověď na filmy je tentokrát velmi slušná! Jde o to, že do sumarizace konceptu jsem vždy chtěl uvádět nějaké typické zástupce a tak je možné, že v textu jsou i jiné, než Star Wars filmy.
Otázka na Marka - všechno nody typu Character, dle očekávání.
Otázka na Prahu jako mysteriózní město opět sedí, protože vrací hlavně uzly typu Setting byť Praha tam není, ale možná ji LLM nevyextrahovalo jako samostatný koncept. Nicméně jsou tam hodně Evropská města a Czechoslovakia.
Agregační otázka na filmy o drogách a zda převažují vážné nebo vtipné dává pro změnu hodně z kategorie Theme (Drug Trade, Substance Abuse) a pro nás důležité dva Genre (Stoner a komedie). Díky filmům jako příkladům v sumarizacích je už teď odpověď myslím velmi slušná.
Motiv dobrodružství se silnou ženou v čele nachází hodně uzlů Character.
Westernovka z jihozápadu logicky docela dost Setting.
Pojďme teď udělat to důležité - přidejme k výsledkům nějaký pohyb po grafu, zejména z konceptů směrem k filmům. Pokud jich je tam víc, vybereme nejlepší přes reranking a nakonec ještě výsledek omezíme a znovu uděláme reranking.
U Abby je 6 filmů místo 2, mnohem lepší.
Star Wars dopadl podobně - k tomu se vrátíme, ani tento postup není tak dokonalý, jak bychom mohli chtít.
Otázka Mark dopadla prakticky stejně.
Praha spíše hůř, byť se o filmu Nesnesitelná lehkost bytí dozvídáme víc.
Drogové filmy za mě určitě zlepšení a nejen, že je výsledků víc, ale připadají mi přesnější.
Filmy se silnou ženou spíše horší.
Westerny podobné.
Když bych to srovnal se špičkou z minulého dílu, tedy hybrid search, query rewriting a reranking, tak myslím, že pokročilejší metoda byla lepší (ale nemůžu plně srovnávat, limitovali jsme minule počet filmů na 10).
Zkusme to teď jinak. Uděláme jednoduchý sémantický search výhradně na filmy, podobně jako v minulém díle (jasně - nemáme tu full-text část, jen sémantickou) a do LLM pošleme rovnou 30 nejlepších filmů.
Abby - problém (to už jsme viděli minule, tam nás zachránil full-text).
Star Wars - super, víc filmů pomohlo, přesně, jak jsme minule spekulovali.
Mark - zdá se, že dost špatně, agregační dotaz a chybí nám globálnější kontext.
Praha - celkem ok, pořád ten stejný jeden film.
Drogy - výčet filmů je celkem ok, ale odpovědi chybí globálnější kontext. Nepopisuje, v čem jsou ty filmy vážné nebo vtipné, umí dát jen výčet.
Silná žena - zdá se mi dobré.
Western - dobré.
Tady je to v kódu trochu komplikovanější, ale postup jsem zkusil tenhle:
Abby - setrvalý stav.
Star Wars - méně filmů, ale zdá se mi přesnější. Tohle se nám stále nedaří plně cracknout a později zkusíme přemýšlet proč.
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čit 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 - kupodivu spíše zhoršení (dané asi menším množstvím filmů v kroku 1 v porovnání s předchozím odstavcem).
Western - také spíše zhoršení.
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é). Nicméně myslím, že problém je, že postup máme dopředu daný a spoléháme se, ž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.
Jak by to vypadalo, kdybychom využili zmíněné techniky, ale na příkladu Star Wars volili postup ručně?
Nějak takhle funguje deep research a už jsou k tomu i různé open source frameworky nebo designové návrhy (třeba ten 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ě.