Усім падабаюцца мадэлі ўбудавання тэксту, і гэта нездарма: яны выдатна спраўляюцца з кадзіраваннем неструктураванага тэксту, палягчаючы выяўленне семантычна падобнага кантэнту. Нядзіўна, што яны складаюць аснову большасці прыкладанняў RAG, асабліва з улікам цяперашняга акцэнту на кадаванні і атрыманні адпаведнай інфармацыі з дакументаў і іншых тэкставых рэсурсаў. Аднак ёсць выразныя прыклады пытанняў, калі падыход да ўбудавання тэксту ў прыкладанні RAG недастатковы і дае няправільную інфармацыю.
Як ужо згадвалася, тэкставыя ўбудовы выдатна падыходзяць для кадавання неструктураванага тэксту. З іншага боку, яны не так добра спраўляюцца са структураванай інфармацыяй і такімі аперацыямі, як фільтраванне , сартаванне або агрэгацыі . Уявіце сабе простае пытанне накшталт:
Які фільм з самым высокім рэйтынгам выпушчаны ў 2024 годзе?
Каб адказаць на гэтае пытанне, мы павінны спачатку адфільтраваць па годзе выпуску, а затым адсартаваць па рэйтынгу. Мы разгледзім, як працуе наіўны падыход з убудаваннем тэксту, а затым прадэманструем, як вырашаць такія пытанні. Гэта паведамленне ў блогу дэманструе, што пры працы са структураванымі дадзенымі, такімі як фільтраванне, сартаванне або агрэгаванне, неабходна выкарыстоўваць іншыя інструменты, якія забяспечваюць структуру, напрыклад, графікі ведаў.
Код даступны на GitHub .
Для гэтага паведамлення ў блогу мы будзем выкарыстоўваць праект рэкамендацый у Neo4j Sandbox . Праект рэкамендацый выкарыстоўвае набор дадзеных MovieLens , які змяшчае фільмы, акцёраў, рэйтынгі і іншую інфармацыю.
Наступны код створыць асобнік абалонкі LangChain для падлучэння да базы дадзеных Neo4j:
os.environ["NEO4J_URI"] = "bolt://44.204.178.84:7687" os.environ["NEO4J_USERNAME"] = "neo4j" os.environ["NEO4J_PASSWORD"] = "minimums-triangle-saving" graph = Neo4jGraph(refresh_schema=False)
Акрамя таго, вам спатрэбіцца ключ OpenAI API, які вы перадаеце ў наступным кодзе:
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:")
База даных змяшчае 10 000 фільмаў, але тэкставыя ўстаўкі яшчэ не захоўваюцца. Каб пазбегнуць вылічэння ўбудавання для ўсіх з іх, мы пазначым 1000 фільмаў з самым высокім рэйтынгам другаснай пазнакай пад назвай Target :
graph.query(""" MATCH (m:Movie) WHERE m.imdbRating IS NOT NULL WITH m ORDER BY m.imdbRating DESC LIMIT 1000 SET m:Target """)
Рашэнне аб тым, што ўстаўляць, з'яўляецца важным момантам. Паколькі мы будзем дэманстраваць фільтрацыю па гадах і сартаванне па рэйтынгу, было б несправядліва выключаць гэтыя дэталі з убудаванага тэксту. Вось чаму я вырашыў зафіксаваць год выхаду, рэйтынг, назву і апісанне кожнага фільма.
Вось прыклад тэксту, які мы ўбудуем у фільм "Воўк з Уол-стрыт" :
plot: Based on the true story of Jordan Belfort, from his rise to a wealthy stock-broker living the high life to his fall involving crime, corruption and the federal government. title: Wolf of Wall Street, The year: 2013 imdbRating: 8.2
Можна сказаць, што гэта не вельмі добры падыход да ўбудавання структураваных даных, і я б не стаў спрачацца, бо не ведаю лепшага падыходу. Магчыма, замест элементаў "ключ-значэнне" мы павінны пераўтварыць іх у тэкст ці нешта падобнае. Дайце мне ведаць, калі ў вас ёсць ідэі аб тым, што можа працаваць лепш.
Аб'ект Neo4j Vector у LangChain мае зручны метад from_existing_graph, дзе вы можаце выбраць, якія ўласцівасці тэксту трэба закадзіраваць:
embedding = OpenAIEmbeddings(model="text-embedding-3-small") neo4j_vector = Neo4jVector.from_existing_graph( embedding=embedding, index_name="movies", node_label="Target", text_node_properties=["plot", "title", "year", "imdbRating"], embedding_node_property="embedding", )
У гэтым прыкладзе мы выкарыстоўваем мадэль text-embedding-3-small OpenAI для генерацыі ўбудавання. Мы ініцыялізуем аб'ект Neo4jVector з дапамогай метаду from_existing_graph. Параметр node_label фільтруе вузлы, якія будуць закадзіраваны, у прыватнасці тыя, якія пазначаны Target . Параметр text_node_properties вызначае ўласцівасці вузла, якія трэба ўбудаваць, у тым ліку plot , title , year і imdbRating . Нарэшце, уласцівасць embedding_node_property вызначае ўласцівасць, дзе будуць захоўвацца згенераваныя ўбудовы, пазначаную як убудаванне .
Давайце пачнем з таго, што паспрабуем знайсці фільм па сюжэце або апісанні:
pretty_print( neo4j_vector.similarity_search( "What is a movie where a little boy meets his hero?" ) )
Вынікі:
plot: A young boy befriends a giant robot from outer space that a paranoid government agent wants to destroy. title: Iron Giant, The year: 1999 imdbRating: 8.0 plot: After the death of a friend, a writer recounts a boyhood journey to find the body of a missing boy. title: Stand by Me year: 1986 imdbRating: 8.1 plot: A young, naive boy sets out alone on the road to find his wayward mother. Soon he finds an unlikely protector in a crotchety man and the two have a series of unexpected adventures along the way. title: Kikujiro (Kikujirô no natsu) year: 1999 imdbRating: 7.9 plot: While home sick in bed, a young boy's grandfather reads him a story called The Princess Bride. title: Princess Bride, The year: 1987 imdbRating: 8.1
Вынікі ў цэлым здаюцца даволі саліднымі. Тут пастаянна ўдзельнічае маленькі хлопчык, хаця я не ўпэўнены, ці заўсёды ён сустракае свайго героя. Зноў жа, набор даных уключае толькі 1000 фільмаў, так што варыянты некалькі абмежаваныя.
Зараз давайце паспрабуем запыт, які патрабуе элементарнай фільтрацыі:
pretty_print( neo4j_vector.similarity_search( "Which movies are from year 2016?" ) )
Вынікі:
plot: Six short stories that explore the extremities of human behavior involving people in distress. title: Wild Tales year: 2014 imdbRating: 8.1 plot: A young man who survives a disaster at sea is hurtled into an epic journey of adventure and discovery. While cast away, he forms an unexpected connection with another survivor: a fearsome Bengal tiger. title: Life of Pi year: 2012 imdbRating: 8.0 plot: Based on the true story of Jordan Belfort, from his rise to a wealthy stock-broker living the high life to his fall involving crime, corruption and the federal government. title: Wolf of Wall Street, The year: 2013 imdbRating: 8.2 plot: After young Riley is uprooted from her Midwest life and moved to San Francisco, her emotions - Joy, Fear, Anger, Disgust and Sadness - conflict on how best to navigate a new city, house, and school. title: Inside Out year: 2015 imdbRating: 8.3
Пацешна, але абраны не быў ні адзін фільм 2016 года. Магчыма, мы маглі б атрымаць лепшыя вынікі з іншай падрыхтоўкай тэксту для кадавання. Аднак тэкставыя ўбудовы тут непрымяняльныя, паколькі мы маем справу з простай аперацыяй са структураванымі дадзенымі, дзе нам трэба адфільтраваць дакументы або, у гэтым прыкладзе, фільмы на аснове ўласцівасці метададзеных. Фільтрацыя метададзеных - гэта ўстояная методыка, якая часта выкарыстоўваецца для павышэння дакладнасці сістэм RAG.
Наступны запыт, які мы паспрабуем, патрабуе крыху сартавання:
pretty_print( neo4j_vector.similarity_search("Which movie has the highest imdb score?") )
Вынікі:
plot: A silent film production company and cast make a difficult transition to sound. title: Singin' in the Rain year: 1952 imdbRating: 8.3 plot: A film about the greatest pre-Woodstock rock music festival. title: Monterey Pop year: 1968 imdbRating: 8.1 plot: This movie documents the Apollo missions perhaps the most definitively of any movie under two hours. Al Reinert watched all the footage shot during the missions--over 6,000,000 feet of it, ... title: For All Mankind year: 1989 imdbRating: 8.2 plot: An unscrupulous movie producer uses an actress, a director and a writer to achieve success. title: Bad and the Beautiful, The year: 1952 imdbRating: 7.9
Калі вы знаёмыя з рэйтынгамі IMDb, вы ведаеце, што ёсць шмат фільмаў з адзнакай вышэй за 8,3. Назва з самым высокім рэйтынгам у нашай базе даных - гэта серыял — Band of Brothers — з уражваючым рэйтынгам 9,6. Зноў жа, убудаванне тэксту працуе дрэнна, калі справа даходзіць да сартавання вынікаў.
Давайце таксама ацэнім пытанне, якое патрабуе нейкага агрэгавання:
pretty_print(neo4j_vector.similarity_search("How many movies are there?"))
Вынікі:
plot: Ten television drama films, each one based on one of the Ten Commandments. title: Decalogue, The (Dekalog) year: 1989 imdbRating: 9.2 plot: A documentary which challenges former Indonesian death-squad leaders to reenact their mass-killings in whichever cinematic genres they wish, including classic Hollywood crime scenarios and lavish musical numbers. title: Act of Killing, The year: 2012 imdbRating: 8.2 plot: A meek Hobbit and eight companions set out on a journey to destroy the One Ring and the Dark Lord Sauron. title: Lord of the Rings: The Fellowship of the Ring, The year: 2001 imdbRating: 8.8 plot: While Frodo and Sam edge closer to Mordor with the help of the shifty Gollum, the divided fellowship makes a stand against Sauron's new ally, Saruman, and his hordes of Isengard. title: Lord of the Rings: The Two Towers, The year: 2002 imdbRating: 8.7
Вынікі дакладна не карысныя, таму што мы атрымліваем чатыры выпадковыя фільмы. З гэтых чатырох выпадковых фільмаў практычна немагчыма зрабіць выснову, што ў гэтым прыкладзе існуе 1000 фільмаў, якія мы пазначылі і ўбудавалі.
Такім чынам, якое рашэнне? Усё проста: пытанні, звязаныя са структураванымі аперацыямі, такімі як фільтраванне, сартаванне і агрэгаванне, патрабуюць інструментаў, прызначаных для працы са структураванымі дадзенымі.
На дадзены момант здаецца, што большасць людзей думае аб падыходзе text2query, калі LLM генеруе запыт да базы дадзеных для ўзаемадзеяння з базай дадзеных на аснове прадастаўленага пытання і схемы. Для Neo4j гэта text2cypher, але ёсць таксама text2sql для баз дадзеных SQL. Аднак на практыцы аказваецца, што ён ненадзейны і недастаткова трывалы для вытворчага выкарыстання.
Ацэнка генерацыі аператара Cypher. Узята з майго паведамлення ў блогу пра ацэнку Cypher .
Вы можаце выкарыстоўваць такія метады, як ланцужок думак, некалькі прыкладаў або тонкая налада, але дасягненне высокай дакладнасці застаецца амаль немагчымым на гэтым этапе. Падыход text2query добра працуе для простых пытанняў па простых схемах баз дадзеных, але гэта не рэальнасць вытворчых асяроддзяў. Каб вырашыць гэта, мы адыходзім ад складанасці генерацыі запытаў да базы дадзеных ад LLM і разглядаем гэта як праблему кода, дзе мы генеруем запыты да базы дадзеных дэтэрмінаваным чынам на аснове ўваходных дадзеных функцый. Перавагай з'яўляецца значна палепшаная трываласць, хоць гэта адбываецца за кошт зніжэння гнуткасці. Лепш звузіць вобласць прымянення RAG і дакладна адказаць на гэтыя пытанні, а не спрабаваць адказаць на ўсё, але рабіць гэта недакладна.
Паколькі мы генеруем запыты да базы дадзеных — у дадзеным выпадку аператары Cypher — на аснове ўваходных дадзеных функцый, мы можам выкарыстоўваць інструментальныя магчымасці LLM. У гэтым працэсе LLM запаўняе адпаведныя параметры на аснове ўводу карыстальніка, а функцыя апрацоўвае атрыманне неабходнай інфармацыі. Для гэтай дэманстрацыі мы спачатку ўкаранім два інструменты: адзін для падліку фільмаў і другі для іх спісу, а потым створым агента LLM з дапамогай LangGraph.
Мы пачынаем з укаранення інструмента для падліку фільмаў на аснове загадзя вызначаных фільтраў. Спачатку мы павінны вызначыць, што гэта за фільтры, і апісаць LLM, калі і як іх выкарыстоўваць:
class MovieCountInput(BaseModel): min_year: Optional[int] = Field( description="Minimum release year of the movies" ) max_year: Optional[int] = Field( description="Maximum release year of the movies" ) min_rating: Optional[float] = Field(description="Minimum imdb rating") grouping_key: Optional[str] = Field( description="The key to group by the aggregation", enum=["year"] )
LangChain прапануе некалькі спосабаў вызначэння ўводу функцый, але я аддаю перавагу падыход Pydantic. У гэтым прыкладзе ў нас ёсць тры даступныя фільтры для ўдакладнення вынікаў фільмаў: min_year, max_year і min_rating. Гэтыя фільтры заснаваныя на структураваных дадзеных і з'яўляюцца неабавязковымі, бо карыстальнік можа ўключыць любы з іх, усе або ніводнага з іх. Акрамя таго, мы ўвялі ўвод grouping_key, які паведамляе функцыі, ці групаваць лік па пэўнай уласцівасці. У гэтым выпадку падтрымліваецца толькі групаванне па гадах, як вызначана ў раздзеле пераліку.
Зараз давайце вызначым фактычную функцыю:
@tool("movie-count", args_schema=MovieCountInput) def movie_count( min_year: Optional[int], max_year: Optional[int], min_rating: Optional[float], grouping_key: Optional[str], ) -> List[Dict]: """Calculate the count of movies based on particular filters""" filters = [ ("t.year >= $min_year", min_year), ("t.year <= $max_year", max_year), ("t.imdbRating >= $min_rating", min_rating), ] # Create the parameters dynamically from function inputs params = { extract_param_name(condition): value for condition, value in filters if value is not None } where_clause = " AND ".join( [condition for condition, value in filters if value is not None] ) cypher_statement = "MATCH (t:Target) " if where_clause: cypher_statement += f"WHERE {where_clause} " return_clause = ( f"t.`{grouping_key}`, count(t) AS movie_count" if grouping_key else "count(t) AS movie_count" ) cypher_statement += f"RETURN {return_clause}" print(cypher_statement) # Debugging output return graph.query(cypher_statement, params=params)
Функцыя movie_count стварае запыт Cypher для падліку фільмаў на аснове дадатковых фільтраў і ключа групоўкі. Ён пачынаецца з вызначэння спісу фільтраў з адпаведнымі значэннямі, прадстаўленымі ў якасці аргументаў. Фільтры выкарыстоўваюцца для дынамічнай пабудовы прапановы WHERE, якая адказвае за прымяненне зададзеных умоў фільтрацыі ў аператары Cypher, уключаючы толькі тыя ўмовы, значэнні якіх не з'яўляюцца None.
Затым ствараецца прапанова RETURN запыту Cypher альбо групоўка па прадастаўленым grouping_key, альбо проста падлік агульнай колькасці фільмаў. Нарэшце, функцыя выконвае запыт і вяртае вынікі.
Функцыю можна пашырыць дадатковымі аргументамі і больш уключанай логікай, калі гэта неабходна, але важна пераканацца, што яна застаецца зразумелай, каб магістр права мог выклікаць яе правільна і дакладна.
Зноў жа, мы павінны пачаць з вызначэння аргументаў функцыі:
class MovieListInput(BaseModel): sort_by: str = Field( description="How to sort movies, can be one of either latest, rating", enum=["latest", "rating"], ) k: Optional[int] = Field(description="Number of movies to return") description: Optional[str] = Field(description="Description of the movies") min_year: Optional[int] = Field( description="Minimum release year of the movies" ) max_year: Optional[int] = Field( description="Maximum release year of the movies" ) min_rating: Optional[float] = Field(description="Minimum imdb rating")
Мы захоўваем тыя ж тры фільтры, што і ў функцыі падліку фільмаў, але дадаем аргумент апісання. Гэты аргумент дазваляе шукаць і складаць спіс фільмаў на аснове іх сюжэту з дапамогай вектарнага пошуку падабенства. Тое, што мы выкарыстоўваем структураваныя інструменты і фільтры, не азначае, што мы не можам уключыць метады ўбудавання тэксту і вектарнага пошуку. Паколькі мы не жадаем вяртаць усе фільмы часцей за ўсё, мы ўключаем дадатковы ўвод k са значэннем па змаўчанні. Акрамя таго, для спісу мы хочам сартаваць фільмы, каб вярнуць толькі найбольш актуальныя. У гэтым выпадку мы можам адсартаваць іх па рэйтынгу або годзе выпуску.
Рэалізуем функцыю:
@tool("movie-list", args_schema=MovieListInput) def movie_list( sort_by: str = "rating", k : int = 4, description: Optional[str] = None, min_year: Optional[int] = None, max_year: Optional[int] = None, min_rating: Optional[float] = None, ) -> List[Dict]: """List movies based on particular filters""" # Handle vector-only search when no prefiltering is applied if description and not min_year and not max_year and not min_rating: return neo4j_vector.similarity_search(description, k=k) filters = [ ("t.year >= $min_year", min_year), ("t.year <= $max_year", max_year), ("t.imdbRating >= $min_rating", min_rating), ] # Create parameters dynamically from function arguments params = { key.split("$")[1]: value for key, value in filters if value is not None } where_clause = " AND ".join( [condition for condition, value in filters if value is not None] ) cypher_statement = "MATCH (t:Target) " if where_clause: cypher_statement += f"WHERE {where_clause} " # Add the return clause with sorting cypher_statement += " RETURN t.title AS title, t.year AS year, t.imdbRating AS rating ORDER BY " # Handle sorting logic based on description or other criteria if description: cypher_statement += ( "vector.similarity.cosine(t.embedding, $embedding) DESC " ) params["embedding"] = embedding.embed_query(description) elif sort_by == "rating": cypher_statement += "t.imdbRating DESC " else: # sort by latest year cypher_statement += "t.year DESC " cypher_statement += " LIMIT toInteger($limit)" params["limit"] = k or 4 print(cypher_statement) # Debugging output data = graph.query(cypher_statement, params=params) return data
Гэтая функцыя атрымлівае спіс фільмаў на аснове некалькіх дадатковых фільтраў: апісанне, дыяпазон гадоў, мінімальны рэйтынг і параметры сартавання. Калі даецца толькі апісанне без іншых фільтраў, ён выконвае пошук падабенства вектарнага індэкса, каб знайсці адпаведныя фільмы. Калі прымяняюцца дадатковыя фільтры, функцыя стварае запыт Cypher для супастаўлення фільмаў на аснове зададзеных крытэрыяў, такіх як год выпуску і рэйтынг IMDb, спалучаючы іх з дадатковым падабенствам на аснове апісання. Затым вынікі сартуюцца альбо па ацэнцы падабенства, рэйтынгу IMDb, альбо па годзе і абмяжоўваюцца k фільмамі.
Мы ўкаранім просты агент ReAct з дапамогай LangGraph.
Агент складаецца з ступені LLM і інструментаў. Калі мы ўзаемадзейнічаем з агентам, мы спачатку патэлефануем LLM, каб вырашыць, ці варта нам выкарыстоўваць інструменты. Затым мы запусцім цыкл:
Рэалізацыя кода максімальна простая. Спачатку мы прывязваем інструменты да LLM і вызначаем крок памочніка:
llm = ChatOpenAI(model='gpt-4-turbo') tools = [movie_count, movie_list] llm_with_tools = llm.bind_tools(tools) # System message sys_msg = SystemMessage(content="You are a helpful assistant tasked with finding and explaining relevant information about movies.") # Node def assistant(state: MessagesState): return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"])]}
Далей мы вызначаем паток LangGraph:
# Graph builder = StateGraph(MessagesState) # Define nodes: these do the work builder.add_node("assistant", assistant) builder.add_node("tools", ToolNode(tools)) # Define edges: these determine how the control flow moves builder.add_edge(START, "assistant") builder.add_conditional_edges( "assistant", # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END tools_condition, ) builder.add_edge("tools", "assistant") react_graph = builder.compile()
Мы вызначаем два вузлы ў LangGraph і звязваем іх умоўным рабром. Калі выклікаецца інструмент, паток накіроўваецца да інструментаў; у адваротным выпадку вынікі адпраўляюцца назад карыстальніку.
Давайце праверым наш агент:
messages = [ HumanMessage( content="What are the some movies about a girl meeting her hero?" ) ] messages = react_graph.invoke({"messages": messages}) for m in messages["messages"]: m.pretty_print()
Вынікі:
На першым этапе агент выбірае выкарыстанне інструмента спісу фільмаў з адпаведным параметрам апісання. Незразумела, чаму ён выбірае значэнне k роўнае 5, але здаецца, што гэтае лік аддае перавагу. Інструмент вяртае пяць найбольш актуальных фільмаў на аснове сюжэту, а LLM проста абагульняе іх для карыстальніка ў канцы.
Калі мы спытаем ChatGPT, чаму яму падабаецца значэнне k роўнае 5, мы атрымаем наступны адказ.
Далей зададзім крыху больш складанае пытанне, якое патрабуе фільтрацыі метададзеных:
messages = [ HumanMessage( content="What are the movies from the 90s about a girl meeting her hero?" ) ] messages = react_graph.invoke({"messages": messages}) for m in messages["messages"]: m.pretty_print()
Вынікі:
На гэты раз дадатковыя аргументы былі выкарыстаны для фільтрацыі фільмаў толькі 1990-х гадоў. Гэты прыклад быў бы тыповым прыкладам фільтрацыі метададзеных з выкарыстаннем падыходу папярэдняй фільтрацыі. Створаная заява Cypher спачатку звужае спіс фільмаў шляхам фільтрацыі па годзе выпуску. У наступнай частцы заява Cypher выкарыстоўвае ўбудаванне тэксту і пошук вектарнага падабенства, каб знайсці фільмы пра маленькую дзяўчынку, якая сустракае свайго героя.
Давайце паспрабуем палічыць фільмы па розных умовах:
messages = [ HumanMessage( content="How many movies are from the 90s have the rating higher than 9.1?" ) ] messages = react_graph.invoke({"messages": messages}) for m in messages["messages"]: m.pretty_print()
Вынікі:
Дзякуючы спецыяльнаму інструменту для падліку, складанасць пераходзіць ад LLM да інструмента, пакідаючы LLM адказным толькі за запаўненне адпаведных параметраў функцыі. Такі падзел задач робіць сістэму больш эфектыўнай і надзейнай і зніжае складанасць уводу LLM.
Паколькі агент можа выклікаць некалькі інструментаў паслядоўна або паралельна, давайце праверым яго з чымсьці яшчэ больш складаным:
messages = [ HumanMessage( content="How many were movies released per year made after the highest rated movie?" ) ] messages = react_graph.invoke({"messages": messages}) for m in messages["messages"]: m.pretty_print()
Вынікі
Як ужо згадвалася, агент можа выклікаць некалькі інструментаў, каб сабраць усю неабходную інфармацыю для адказу на пытанне. У гэтым прыкладзе ён пачынаецца са спісу фільмаў з самым высокім рэйтынгам, каб вызначыць, калі быў выпушчаны фільм з самым высокім рэйтынгам. Атрымаўшы гэтыя даныя, ён выклікае інструмент падліку фільмаў, каб сабраць колькасць фільмаў, выпушчаных пасля вызначанага года, выкарыстоўваючы ключ групоўкі, як вызначана ў пытанні.
У той час як тэкставыя ўбудовы выдатна падыходзяць для пошуку ў неструктураваных дадзеных, яны недастатковыя, калі справа даходзіць да структураваных аперацый, такіх як фільтраванне , сартаванне і агрэгаванне . Гэтыя задачы патрабуюць інструментаў, прызначаных для структураваных даных, якія забяспечваюць дакладнасць і гібкасць, неабходныя для выканання гэтых аперацый. Ключавым высновай з'яўляецца тое, што пашырэнне набору інструментаў у вашай сістэме дазваляе вам вырашаць больш шырокі спектр запытаў карыстальнікаў, робячы вашы прыкладанні больш надзейнымі і ўніверсальнымі. Камбінаванне падыходаў да структураваных даных і метадаў пошуку па неструктураваным тэксце можа даць больш дакладныя і адпаведныя адказы, што ў канчатковым выніку паляпшае карыстальніцкі досвед у праграмах RAG.
Як заўсёды, код даступны на GitHub .
Каб даведацца больш аб гэтай тэме, далучайцеся да нас на NODES 2024 7 лістапада, нашай бясплатнай віртуальнай канферэнцыі распрацоўшчыкаў па інтэлектуальных праграмах, графах ведаў і AI. Зарэгіструйцеся зараз!