Zatímco většina lidí se zaměřuje na generování rozšířeného vyhledávání (RAG) před nestrukturovaným textem, jako jsou firemní dokumenty nebo dokumentace, já jsem docela optimistický na systémy vyhledávání před strukturovanými informacemi, zejména grafy znalostí . O GraphRAG, konkrétně o implementaci Microsoftu, bylo hodně vzrušení. Vstupními daty je však při jejich implementaci nestrukturovaný text ve formě dokumentů, který je pomocí velkého jazykového modelu (LLM) transformován do znalostního grafu.
V tomto blogovém příspěvku si ukážeme, jak implementovat retriever nad znalostním grafem obsahujícím strukturované informace ze systému hlášení nežádoucích událostí FDA (FAERS) , který nabízí informace o nežádoucích účincích léků. Pokud jste se někdy zabývali znalostními grafy a vyhledáváním, vaše první myšlenka by mohla být použít LLM ke generování databázových dotazů k získání relevantních informací z grafu znalostí k zodpovězení dané otázky. Generování databázových dotazů pomocí LLM se však stále vyvíjí a možná ještě nenabízí nejkonzistentnější nebo nejrobustnější řešení. Jaké jsou tedy v tuto chvíli životaschopné alternativy?
Podle mého názoru je nejlepší současné řešení dynamické generování dotazů. Spíše než spoléhat se výhradně na LLM při generování kompletního dotazu, tato metoda využívá logickou vrstvu, která deterministicky generuje databázový dotaz z předdefinovaných vstupních parametrů. Toto řešení lze implementovat pomocí LLM s podporou volání funkcí. Výhoda použití funkce volání funkce spočívá ve schopnosti definovat LLM, jak má připravit strukturovaný vstup do funkce. Tento přístup zajišťuje, že proces generování dotazů je řízený a konzistentní a zároveň umožňuje flexibilitu vstupu uživatele.
Obrázek ilustruje proces porozumění dotazu uživatele za účelem získání konkrétních informací. Tok zahrnuje tři hlavní kroky:
Uživatel se ptá na běžné vedlejší účinky léku Lyrica pro osoby mladší 35 let.
LLM rozhodne, kterou funkci zavolat a jaké parametry potřebuje. V tomto příkladu zvolila funkci s názvem side_effects s parametry zahrnujícími lék Lyrica a maximální věk 35 let.
Identifikovaná funkce a parametry se používají k deterministickému a dynamickému generování příkazu databázového dotazu (Cypher) k získání relevantních informací.
Podpora volání funkcí je zásadní pro pokročilé případy použití LLM, jako je umožnění LLM používat více retrieverů na základě záměru uživatele nebo vytváření toků více agentů. Napsal jsem několik článků pomocí komerčních LLM s nativní podporou volání funkcí. Použijeme však nedávno vydanou Llama-3.1, vynikající open-source LLM s nativní podporou volání funkcí.
Kód je k dispozici na GitHubu .
K ukládání informací o nežádoucích účincích použijeme Neo4j, což je nativní grafová databáze. Bezplatný cloudový projekt Sandbox, který je dodáván s předem vyplněnými FAERS, můžete nastavit pomocí tohoto odkazu .
Instance databáze s instancí má graf s následujícím schématem.
Schéma se soustředí na uzel Případ, který propojuje různé aspekty zprávy o bezpečnosti léčiv, včetně příslušných léčiv, prožitých reakcí, výsledků a předepsaných terapií. Každý lék je charakterizován tím, zda je primární, sekundární, souběžný nebo interagující. Případy jsou také spojeny s informacemi o výrobci, věkové skupině pacienta a zdroji zprávy. Toto schéma umožňuje strukturovaným způsobem sledovat a analyzovat vztahy mezi drogami, jejich reakcemi a výsledky.
Začneme vytvořením připojení k databázi vytvořením instance objektu Neo4jGraph:
os.environ["NEO4J_URI"] = "bolt://18.206.157.187:7687" os.environ["NEO4J_USERNAME"] = "neo4j" os.environ["NEO4J_PASSWORD"] = "elevation-reservist-thousands" graph = Neo4jGraph(refresh_schema=False)
Existuje mnoho možností, jak hostovat LLM s otevřeným zdrojovým kódem, jako je Llama-3.1. Použijeme katalog NVIDIA API , který poskytuje inferenční mikroslužby NVIDIA NIM a podporuje volání funkcí pro modely Llama 3.1. Když si vytvoříte účet, získáte 1 000 tokenů, což je více než dost na to, abyste je mohli sledovat. Budete muset vytvořit klíč API a zkopírovat jej do poznámkového bloku:
os.environ["NVIDIA_API_KEY"] = "nvapi-" llm = ChatNVIDIA(model="meta/llama-3.1-70b-instruct")
Použijeme lamu-3.1–70b, protože verze 8b má několik škytavek s volitelnými parametry v definicích funkcí.
Pěkná věc na mikroslužbách NVIDIA NIM je, že je můžete snadno hostovat lokálně, pokud máte problémy se zabezpečením nebo jinými problémy, takže je lze snadno vyměnit a do konfigurace LLM stačí přidat parametr adresy URL:
# connect to an local NIM running at localhost:8000, # specifying a specific model llm = ChatNVIDIA( base_url="http://localhost:8000/v1", model="meta/llama-3.1-70b-instruct" )
Nakonfigurujeme jeden nástroj se čtyřmi volitelnými parametry. Na základě těchto parametrů vytvoříme odpovídající příkaz Cypher, abychom získali relevantní informace z grafu znalostí. Náš nástroj bude schopen identifikovat nejčastější nežádoucí účinky na základě vstupního léku, věku a výrobce léku.
@tool def get_side_effects( drug: Optional[str] = Field( description="disease mentioned in the question. Return None if no mentioned." ), min_age: Optional[int] = Field( description="Minimum age of the patient. Return None if no mentioned." ), max_age: Optional[int] = Field( description="Maximum age of the patient. Return None if no mentioned." ), manufacturer: Optional[str] = Field( description="manufacturer of the drug. Return None if no mentioned." ), ): """Useful for when you need to find common side effects.""" params = {} filters = [] side_effects_base_query = """ MATCH (c:Case)-[:HAS_REACTION]->(r:Reaction), (c)-[:IS_PRIMARY_SUSPECT]->(d:Drug) """ if drug and isinstance(drug, str): candidate_drugs = [el["candidate"] for el in get_candidates(drug, "drug")] if not candidate_drugs: return "The mentioned drug was not found" filters.append("d.name IN $drugs") params["drugs"] = candidate_drugs if min_age and isinstance(min_age, int): filters.append("c.age > $min_age ") params["min_age"] = min_age if max_age and isinstance(max_age, int): filters.append("c.age < $max_age ") params["max_age"] = max_age if manufacturer and isinstance(manufacturer, str): candidate_manufacturers = [ el["candidate"] for el in get_candidates(manufacturer, "manufacturer") ] if not candidate_manufacturers: return "The mentioned manufacturer was not found" filters.append( "EXISTS {(c)<-[:REGISTERED]-(:Manufacturer {manufacturerName: $manufacturer})}" ) params["manufacturer"] = candidate_manufacturers[0] if filters: side_effects_base_query += " WHERE " side_effects_base_query += " AND ".join(filters) side_effects_base_query += """ RETURN d.name AS drug, r.description AS side_effect, count(*) AS count ORDER BY count DESC LIMIT 10 """ print(f"Using parameters: {params}") data = graph.query(side_effects_base_query, params=params) return data
Funkce get_side_effects je navržena k získání běžných vedlejších účinků léků z grafu znalostí pomocí zadaných kritérií vyhledávání. Přijímá volitelné parametry pro název léku, věkové rozmezí pacienta a výrobce léku pro přizpůsobení vyhledávání. Každý parametr má popis předaný LLM spolu s popisem funkce, což LLM umožňuje pochopit, jak je používat. Funkce poté vytvoří dynamický dotaz Cypher na základě poskytnutých vstupů, provede tento dotaz proti znalostnímu grafu a vrátí výsledná data o vedlejších účincích.
Pojďme otestovat funkci:
get_side_effects("lyrica") # Using parameters: {'drugs': ['LYRICA', 'LYRICA CR']} # [{'drug': 'LYRICA', 'side_effect': 'Pain', 'count': 32}, # {'drug': 'LYRICA', 'side_effect': 'Fall', 'count': 21}, # {'drug': 'LYRICA', 'side_effect': 'Intentional product use issue', 'count': 20}, # {'drug': 'LYRICA', 'side_effect': 'Insomnia', 'count': 19}, # ...
Náš nástroj nejprve zmapoval lék Lyrica zmíněný v otázce na hodnoty „['LYRICA', 'LYRICA CR']“ v grafu znalostí a poté provedl odpovídající příkaz Cypher, aby našel nejčastější vedlejší účinky.
Jediné, co zbývá udělat, je nakonfigurovat agenta LLM, který může pomocí definovaného nástroje odpovídat na otázky o vedlejších účincích léku.
Obrázek znázorňuje uživatele, který komunikuje s agentem Llama 3.1, aby se zeptal na vedlejší účinky drogy. Agent přistupuje k nástroji pro vedlejší účinky, který získává informace z grafu znalostí a poskytuje uživateli relevantní data.
Začneme definováním šablony výzvy:
prompt = ChatPromptTemplate.from_messages( [ ( "system", "You are a helpful assistant that finds information about common side effects. " "If tools require follow up questions, " "make sure to ask the user for clarification. Make sure to include any " "available options that need to be clarified in the follow up questions " "Do only the things the user specifically requested. ", ), MessagesPlaceholder(variable_name="chat_history"), ("user", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad"), ] )
Šablona výzvy obsahuje systémovou zprávu, volitelnou historii chatu a vstup uživatele. Agent_scratchpad je vyhrazen pro LLM, protože někdy potřebuje k zodpovězení otázky více kroků, jako je spuštění a načtení informací z nástrojů.
Knihovna LangChain usnadňuje přidávání nástrojů do LLM pomocí metody bind_tools:
tools = [get_side_effects] llm_with_tools = llm.bind_tools(tools=tools) agent = ( { "input": lambda x: x["input"], "chat_history": lambda x: _format_chat_history(x["chat_history"]) if x.get("chat_history") else [], "agent_scratchpad": lambda x: format_to_openai_function_messages( x["intermediate_steps"] ), } | prompt | llm_with_tools | OpenAIFunctionsAgentOutputParser() ) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True).with_types( input_type=AgentInput, output_type=Output )
Agent zpracovává vstup prostřednictvím transformací a obslužných programů, které formátují historii chatu, aplikují LLM s vázanými nástroji a analyzují výstup. Nakonec je agentovi nastaven exekutor, který řídí tok provádění, specifikuje typy vstupu a výstupu a zahrnuje nastavení výřečnosti pro podrobné protokolování během provádění.
Pojďme otestovat agenta:
agent_executor.invoke( { "input": "What are the most common side effects when using lyrica for people below 35 years old?" } )
Výsledky:
LLM zjistil, že potřebuje použít funkci get_side_effects s vhodnými argumenty. Funkce poté dynamicky vygeneruje příkaz Cypher, načte příslušné informace a vrátí je do LLM, aby vygenerovala konečnou odpověď.
Funkce volání funkcí jsou výkonným doplňkem k modelům s otevřeným zdrojovým kódem, jako je Llama 3.1, a umožňují strukturovanější a kontrolovanější interakce s externími zdroji dat a nástroji. Kromě dotazování na nestrukturované dokumenty nabízejí agenti na grafech vzrušující možnosti interakce se znalostními grafy a strukturovanými daty. Díky snadnému hostování těchto modelů pomocí platforem, jako jsou mikroslužby NVIDIA NIM, jsou stále dostupnější.
Jako vždy je kód dostupný na GitHubu .
Chcete-li se o tomto tématu dozvědět více, připojte se k nám na NODES 2024 7. listopadu, na naší bezplatné virtuální konferenci vývojářů o inteligentních aplikacích, znalostních grafech a AI. Zaregistrujte se nyní!