paint-brush
Vượt ra ngoài nhúng văn bản: Giải quyết các khoảng trống trong ứng dụng RAG cho truy vấn dữ liệu có cấu trúctừ tác giả@neo4j
Bài viết mới

Vượt ra ngoài nhúng văn bản: Giải quyết các khoảng trống trong ứng dụng RAG cho truy vấn dữ liệu có cấu trúc

từ tác giả Neo4j17m2024/10/29
Read on Terminal Reader

dài quá đọc không nổi

Trong khi nhúng văn bản rất hiệu quả đối với văn bản phi cấu trúc trong các ứng dụng RAG, chúng lại không hiệu quả đối với các truy vấn có cấu trúc. Biểu đồ kiến thức cung cấp một giải pháp.
featured image - Vượt ra ngoài nhúng văn bản: Giải quyết các khoảng trống trong ứng dụng RAG cho truy vấn dữ liệu có cấu trúc
Neo4j HackerNoon profile picture
0-item
1-item


Mọi người đều thích các mô hình nhúng văn bản, và có lý do chính đáng: Chúng rất giỏi trong việc mã hóa văn bản phi cấu trúc, giúp dễ dàng khám phá nội dung ngữ nghĩa tương tự. Không có gì ngạc nhiên khi chúng tạo thành xương sống của hầu hết các ứng dụng RAG, đặc biệt là với sự nhấn mạnh hiện tại vào việc mã hóa và truy xuất thông tin có liên quan từ các tài liệu và các nguồn văn bản khác. Tuy nhiên, có những ví dụ rõ ràng về những câu hỏi mà người ta có thể đặt ra về cách tiếp cận nhúng văn bản vào các ứng dụng RAG còn thiếu sót và cung cấp thông tin không chính xác.


Như đã đề cập, nhúng văn bản rất tuyệt vời trong việc mã hóa văn bản phi cấu trúc. Mặt khác, chúng không tuyệt vời trong việc xử lý thông tin có cấu trúc và các hoạt động như lọc , sắp xếp hoặc tổng hợp . Hãy tưởng tượng một câu hỏi đơn giản như:


Bộ phim nào được đánh giá cao nhất ra mắt năm 2024?


Để trả lời câu hỏi này, trước tiên chúng ta phải lọc theo năm phát hành, sau đó sắp xếp theo xếp hạng. Chúng ta sẽ xem xét cách tiếp cận ngây thơ với nhúng văn bản thực hiện như thế nào và sau đó trình bày cách xử lý những câu hỏi như vậy. Bài đăng trên blog này cho thấy rằng khi xử lý các hoạt động dữ liệu có cấu trúc như lọc, sắp xếp hoặc tổng hợp, bạn cần sử dụng các công cụ khác cung cấp cấu trúc như biểu đồ kiến thức.


Mã có sẵn trên GitHub .

Thiết lập môi trường

Đối với bài đăng trên blog này, chúng tôi sẽ sử dụng dự án đề xuất trong Neo4j Sandbox . Dự án đề xuất sử dụng tập dữ liệu MovieLens , bao gồm phim, diễn viên, xếp hạng và nhiều thông tin khác.


Sơ đồ đồ thị của cơ sở dữ liệu khuyến nghị.


Đoạn mã sau sẽ khởi tạo trình bao bọc LangChain để kết nối với Cơ sở dữ liệu 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)


Ngoài ra, bạn sẽ cần khóa API OpenAI để nhập vào đoạn mã sau:


 os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:")


Cơ sở dữ liệu chứa 10.000 bộ phim, nhưng các nhúng văn bản vẫn chưa được lưu trữ. Để tránh tính toán nhúng cho tất cả các phim, chúng tôi sẽ gắn nhãn 1.000 phim được đánh giá cao nhất bằng nhãn phụ có tên là Target :


 graph.query(""" MATCH (m:Movie) WHERE m.imdbRating IS NOT NULL WITH m ORDER BY m.imdbRating DESC LIMIT 1000 SET m:Target """)

Tính toán và lưu trữ nhúng văn bản

Quyết định nhúng nội dung gì là một cân nhắc quan trọng. Vì chúng tôi sẽ trình bày cách lọc theo năm và sắp xếp theo xếp hạng, sẽ không công bằng nếu loại trừ các chi tiết đó khỏi văn bản nhúng. Đó là lý do tại sao tôi chọn ghi lại năm phát hành, xếp hạng, tiêu đề và mô tả của từng bộ phim.


Sau đây là ví dụ về văn bản chúng tôi sẽ nhúng cho bộ phim The Wolf of Wall Street :


 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


Bạn có thể nói rằng đây không phải là cách tiếp cận tốt để nhúng dữ liệu có cấu trúc và tôi sẽ không tranh luận vì tôi không biết cách tiếp cận tốt nhất. Có lẽ thay vì các mục khóa-giá trị, chúng ta nên chuyển đổi chúng thành văn bản hoặc thứ gì đó. Hãy cho tôi biết nếu bạn có một số ý tưởng về cách nào có thể hiệu quả hơn.


Đối tượng Vector Neo4j trong LangChain có phương thức thuận tiện from_existing_graph, nơi bạn có thể chọn thuộc tính văn bản nào sẽ được mã hóa:


 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", )


Trong ví dụ này, chúng tôi sử dụng mô hình text-embedding-3-small của OpenAI để tạo nhúng. Chúng tôi khởi tạo đối tượng Neo4jVector bằng phương thức from_existing_graph. Tham số node_label lọc các nút cần mã hóa, cụ thể là các nút được gắn nhãn Target . Tham số text_node_properties xác định các thuộc tính nút cần nhúng, bao gồm plot , title , yearimdbRating . Cuối cùng, embedding_node_property xác định thuộc tính nơi các nhúng được tạo sẽ được lưu trữ, được chỉ định là embedding .

Cách tiếp cận ngây thơ

Chúng ta hãy bắt đầu bằng cách tìm một bộ phim dựa trên cốt truyện hoặc mô tả của nó:


 pretty_print( neo4j_vector.similarity_search( "What is a movie where a little boy meets his hero?" ) )


Kết quả:


 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


Nhìn chung, kết quả có vẻ khá chắc chắn. Luôn có một cậu bé tham gia, mặc dù tôi không chắc liệu cậu bé có luôn gặp được anh hùng của mình hay không. Mặt khác, tập dữ liệu chỉ bao gồm 1.000 bộ phim, vì vậy các tùy chọn có phần hạn chế.


Bây giờ chúng ta hãy thử một truy vấn yêu cầu một số lọc cơ bản:


 pretty_print( neo4j_vector.similarity_search( "Which movies are from year 2016?" ) )


Kết quả:


 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


Thật buồn cười, nhưng không có một bộ phim nào từ năm 2016 được chọn. Có lẽ chúng ta có thể có được kết quả tốt hơn với việc chuẩn bị văn bản khác nhau để mã hóa. Tuy nhiên, nhúng văn bản không áp dụng được ở đây vì chúng ta đang xử lý một hoạt động dữ liệu có cấu trúc đơn giản, trong đó chúng ta cần lọc tài liệu hoặc, trong ví dụ này, phim dựa trên thuộc tính siêu dữ liệu. Lọc siêu dữ liệu là một kỹ thuật đã được thiết lập tốt, thường được sử dụng để nâng cao độ chính xác của các hệ thống RAG.


Truy vấn tiếp theo mà chúng ta sẽ thử đòi hỏi một chút sắp xếp:


 pretty_print( neo4j_vector.similarity_search("Which movie has the highest imdb score?") )


Kết quả:


 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


Nếu bạn quen thuộc với xếp hạng IMDb, bạn biết rằng có rất nhiều phim đạt điểm trên 8,3. Tựa phim được xếp hạng cao nhất trong cơ sở dữ liệu của chúng tôi thực sự là một loạt phim — Band of Brothers — với xếp hạng ấn tượng là 9,6. Một lần nữa, nhúng văn bản hoạt động kém khi sắp xếp kết quả.


Chúng ta hãy cùng đánh giá một câu hỏi đòi hỏi một số loại tổng hợp:


 pretty_print(neo4j_vector.similarity_search("How many movies are there?"))


Kết quả:


 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


Kết quả chắc chắn không hữu ích ở đây vì chúng tôi nhận được bốn bộ phim ngẫu nhiên. Thực tế là không thể rút ra kết luận từ bốn bộ phim ngẫu nhiên này rằng có tổng cộng 1.000 bộ phim mà chúng tôi đã gắn thẻ và nhúng cho ví dụ này.


Vậy giải pháp là gì? Rất đơn giản: Các câu hỏi liên quan đến các hoạt động có cấu trúc như lọc, sắp xếp và tổng hợp cần có các công cụ được thiết kế để hoạt động với dữ liệu có cấu trúc.

Công cụ cho dữ liệu có cấu trúc

Hiện tại, có vẻ như hầu hết mọi người nghĩ về phương pháp text2query, trong đó LLM tạo ra truy vấn cơ sở dữ liệu để tương tác với cơ sở dữ liệu dựa trên câu hỏi và lược đồ được cung cấp. Đối với Neo4j, đây là text2cypher, nhưng cũng có text2sql cho cơ sở dữ liệu SQL. Tuy nhiên, trên thực tế, phương pháp này không đáng tin cậy và không đủ mạnh để sử dụng trong sản xuất.


Đánh giá thế hệ câu lệnh Cypher. Trích từ bài đăng trên blog của tôi về đánh giá Cypher .


Bạn có thể sử dụng các kỹ thuật như chuỗi suy nghĩ, ví dụ ít cảnh hoặc tinh chỉnh, nhưng đạt được độ chính xác cao vẫn gần như không thể ở giai đoạn này. Phương pháp text2query hoạt động tốt đối với các câu hỏi đơn giản trên các lược đồ cơ sở dữ liệu đơn giản, nhưng đó không phải là thực tế của môi trường sản xuất. Để giải quyết vấn đề này, chúng tôi chuyển sự phức tạp của việc tạo truy vấn cơ sở dữ liệu khỏi LLM và coi đó là một vấn đề về mã, trong đó chúng tôi tạo truy vấn cơ sở dữ liệu một cách xác định dựa trên các đầu vào hàm. Ưu điểm là tính mạnh mẽ được cải thiện đáng kể, mặc dù phải trả giá bằng tính linh hoạt giảm. Tốt hơn là thu hẹp phạm vi của ứng dụng RAG và trả lời các câu hỏi đó một cách chính xác, thay vì cố gắng trả lời mọi thứ nhưng lại trả lời không chính xác.


Vì chúng ta đang tạo các truy vấn cơ sở dữ liệu — trong trường hợp này là các câu lệnh Cypher — dựa trên các đầu vào hàm, chúng ta có thể tận dụng các khả năng công cụ của LLM. Trong quá trình này, LLM sẽ điền các tham số có liên quan dựa trên đầu vào của người dùng, trong khi hàm xử lý việc truy xuất thông tin cần thiết. Đối với bản trình diễn này, trước tiên chúng ta sẽ triển khai hai công cụ: một công cụ để đếm phim và một công cụ khác để liệt kê chúng, sau đó tạo một tác nhân LLM bằng LangGraph.

Công cụ đếm phim

Chúng tôi bắt đầu bằng cách triển khai một công cụ để đếm phim dựa trên các bộ lọc được xác định trước. Đầu tiên, chúng tôi phải xác định các bộ lọc đó là gì và mô tả cho LLM khi nào và cách sử dụng chúng:


 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 cung cấp một số cách để xác định đầu vào hàm, nhưng tôi thích cách tiếp cận Pydantic hơn. Trong ví dụ này, chúng tôi có ba bộ lọc có sẵn để tinh chỉnh kết quả phim: min_year, max_year và min_rating. Các bộ lọc này dựa trên dữ liệu có cấu trúc và là tùy chọn, vì người dùng có thể chọn bao gồm bất kỳ, tất cả hoặc không có bộ lọc nào. Ngoài ra, chúng tôi đã giới thiệu đầu vào grouping_key cho biết hàm có nên nhóm số đếm theo một thuộc tính cụ thể hay không. Trong trường hợp này, nhóm duy nhất được hỗ trợ là theo năm, như được xác định trong enumsection.


Bây giờ chúng ta hãy định nghĩa hàm thực tế:


 @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)


Hàm movie_count tạo ra truy vấn Cypher để đếm phim dựa trên bộ lọc tùy chọn và khóa nhóm. Nó bắt đầu bằng cách xác định danh sách các bộ lọc với các giá trị tương ứng được cung cấp làm đối số. Các bộ lọc được sử dụng để xây dựng động mệnh đề WHERE, mệnh đề này chịu trách nhiệm áp dụng các điều kiện lọc được chỉ định trong câu lệnh Cypher, chỉ bao gồm các điều kiện mà giá trị không phải là None.


Sau đó, mệnh đề RETURN của truy vấn Cypher được xây dựng, nhóm theo grouping_key được cung cấp hoặc chỉ đếm tổng số phim. Cuối cùng, hàm thực thi truy vấn và trả về kết quả.


Hàm này có thể được mở rộng với nhiều đối số hơn và logic phức tạp hơn nếu cần, nhưng điều quan trọng là phải đảm bảo rằng hàm vẫn rõ ràng để LLM có thể gọi đúng và chính xác.

Công cụ để liệt kê phim

Một lần nữa, chúng ta phải bắt đầu bằng cách xác định các đối số của hàm:


 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")


Chúng tôi giữ nguyên ba bộ lọc như trong hàm đếm phim nhưng thêm đối số mô tả. Đối số này cho phép chúng tôi tìm kiếm và liệt kê các bộ phim dựa trên cốt truyện của chúng bằng cách sử dụng tìm kiếm tương tự vectơ. Chỉ vì chúng tôi đang sử dụng các công cụ và bộ lọc có cấu trúc không có nghĩa là chúng tôi không thể kết hợp nhúng văn bản và phương pháp tìm kiếm vectơ. Vì chúng tôi không muốn trả về tất cả các bộ phim hầu hết thời gian, chúng tôi bao gồm một đầu vào k tùy chọn với giá trị mặc định. Ngoài ra, để liệt kê, chúng tôi muốn sắp xếp các bộ phim để chỉ trả về những bộ phim có liên quan nhất. Trong trường hợp này, chúng tôi có thể sắp xếp chúng theo xếp hạng hoặc năm phát hành.


Chúng ta hãy triển khai hàm này:


 @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


Hàm này lấy danh sách phim dựa trên nhiều bộ lọc tùy chọn: mô tả, phạm vi năm, xếp hạng tối thiểu và tùy chọn sắp xếp. Nếu chỉ có mô tả được đưa ra mà không có bộ lọc nào khác, hàm sẽ thực hiện tìm kiếm độ tương đồng chỉ mục vectơ để tìm phim có liên quan. Khi áp dụng các bộ lọc bổ sung, hàm sẽ xây dựng truy vấn Cypher để khớp phim dựa trên các tiêu chí đã chỉ định, chẳng hạn như năm phát hành và xếp hạng IMDb, kết hợp chúng với độ tương đồng dựa trên mô tả tùy chọn. Sau đó, kết quả được sắp xếp theo điểm tương đồng, xếp hạng IMDb hoặc năm và giới hạn ở k phim.

Kết hợp tất cả lại với nhau như một tác nhân LangGraph

Chúng tôi sẽ triển khai một tác nhân ReAct đơn giản bằng cách sử dụng LangGraph.


Triển khai tác nhân LangGraph.


Tác nhân bao gồm một bước LLM và công cụ. Khi chúng ta tương tác với tác nhân, trước tiên chúng ta sẽ gọi LLM để quyết định xem chúng ta có nên sử dụng công cụ hay không. Sau đó, chúng ta sẽ chạy một vòng lặp:


  1. Nếu tác nhân yêu cầu thực hiện hành động (ví dụ: gọi công cụ), chúng tôi sẽ chạy các công cụ và chuyển kết quả trở lại cho tác nhân.
  2. Nếu tác nhân không yêu cầu chạy công cụ, chúng tôi sẽ hoàn tất (phản hồi cho người dùng).


Việc triển khai mã đơn giản nhất có thể. Đầu tiên, chúng tôi liên kết các công cụ với LLM và xác định bước trợ lý:


 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"])]}


Tiếp theo chúng ta định nghĩa luồng 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()


Chúng tôi định nghĩa hai nút trong LangGraph và liên kết chúng với một cạnh có điều kiện. Nếu một công cụ được gọi, luồng sẽ được hướng đến các công cụ; nếu không, kết quả sẽ được gửi lại cho người dùng.


Bây giờ chúng ta hãy thử nghiệm tác nhân của chúng ta:


 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ết quả:


Kết quả


Trong bước đầu tiên, tác nhân chọn sử dụng công cụ movie-list với tham số mô tả phù hợp. Không rõ tại sao nó lại chọn giá trị k là 5, nhưng có vẻ như nó thiên về con số đó. Công cụ trả về năm bộ phim có liên quan nhất dựa trên cốt truyện và LLM chỉ tóm tắt chúng cho người dùng ở phần cuối.


Nếu chúng ta hỏi ChatGPT tại sao nó lại thích giá trị k là 5, chúng ta sẽ nhận được câu trả lời sau.



Tiếp theo, chúng ta hãy đặt một câu hỏi phức tạp hơn một chút, yêu cầu phải lọc siêu dữ liệu:


 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()


Kết quả:


Kết quả


Lần này, các đối số bổ sung được sử dụng để lọc phim chỉ từ những năm 1990. Ví dụ này sẽ là một ví dụ điển hình về lọc siêu dữ liệu sử dụng phương pháp lọc trước. Câu lệnh Cypher được tạo ra trước tiên sẽ thu hẹp các bộ phim bằng cách lọc theo năm phát hành của chúng. Trong phần tiếp theo, câu lệnh Cypher sử dụng nhúng văn bản và tìm kiếm tương tự vector để tìm phim về một cô bé gặp gỡ anh hùng của mình.


Chúng ta hãy thử đếm số phim dựa trên các điều kiện khác nhau:


 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()


Kết quả:


Kết quả


Với một công cụ chuyên dụng để đếm, độ phức tạp chuyển từ LLM sang công cụ, để LLM chỉ chịu trách nhiệm điền các tham số hàm có liên quan. Sự tách biệt các tác vụ này làm cho hệ thống hiệu quả và mạnh mẽ hơn, đồng thời giảm độ phức tạp của đầu vào LLM.


Vì tác nhân có thể gọi nhiều công cụ theo trình tự hoặc song song, chúng ta hãy thử nghiệm nó bằng một lệnh phức tạp hơn:


 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()


Kết quả


Kết quả


Như đã đề cập, tác nhân có thể gọi nhiều công cụ để thu thập tất cả thông tin cần thiết để trả lời câu hỏi. Trong ví dụ này, tác nhân bắt đầu bằng cách liệt kê các bộ phim được đánh giá cao nhất để xác định thời điểm phát hành bộ phim được đánh giá cao nhất. Sau khi có dữ liệu đó, tác nhân sẽ gọi công cụ đếm phim để thu thập số lượng phim được phát hành sau năm đã chỉ định, sử dụng khóa nhóm như được xác định trong câu hỏi.

Bản tóm tắt

Mặc dù nhúng văn bản rất tuyệt vời để tìm kiếm trong dữ liệu phi cấu trúc, nhưng chúng lại không hiệu quả khi nói đến các hoạt động có cấu trúc như lọc , sắp xếptổng hợp . Các tác vụ này yêu cầu các công cụ được thiết kế cho dữ liệu có cấu trúc, cung cấp độ chính xác và tính linh hoạt cần thiết để xử lý các hoạt động này. Điểm mấu chốt là việc mở rộng bộ công cụ trong hệ thống của bạn cho phép bạn giải quyết nhiều truy vấn của người dùng hơn, giúp ứng dụng của bạn mạnh mẽ và linh hoạt hơn. Kết hợp các phương pháp tiếp cận dữ liệu có cấu trúc và các kỹ thuật tìm kiếm văn bản phi cấu trúc có thể cung cấp phản hồi chính xác và phù hợp hơn, cuối cùng là nâng cao trải nghiệm của người dùng trong các ứng dụng RAG.


Như thường lệ, mã có sẵn trên GitHub .


Để tìm hiểu thêm về chủ đề này, hãy tham gia cùng chúng tôi tại NODES 2024 vào ngày 7 tháng 11, hội nghị dành cho nhà phát triển trực tuyến miễn phí của chúng tôi về các ứng dụng thông minh, biểu đồ kiến thức và AI. Đăng ký ngay!