Trích xuất văn bản từ tài liệu gốc (HTML, PDF, Markdown, v.v.).
Phân đoạn văn bản thành các kích thước cụ thể dựa trên cấu trúc tài liệu và ngữ nghĩa.
Lưu trữ các đoạn trong cơ sở dữ liệu vectơ được khóa bằng cách nhúng đoạn đó.
Truy xuất các đoạn có liên quan đến câu hỏi để sử dụng làm ngữ cảnh khi tạo câu trả lời.
Tuy nhiên, RAG dựa trên độ tương tự của vectơ có một số điểm yếu. Vì nó tập trung vào thông tin tương tự với câu hỏi nên sẽ khó trả lời các câu hỏi liên quan đến nhiều chủ đề và/hoặc yêu cầu nhiều bước nhảy – chẳng hạn. Ngoài ra, nó giới hạn số lượng khối được lấy.
Mỗi đoạn đến từ một nguồn riêng biệt, do đó, trong trường hợp thông tin phần lớn giống nhau tồn tại ở nhiều nơi, nó cần chọn giữa việc truy xuất nhiều bản sao của thông tin (và có thể bỏ sót thông tin khác) hoặc chỉ chọn một bản sao để có thêm các phần khác nhau, sau đó bỏ lỡ sắc thái của các nguồn khác.
Cách tiếp cận này có một số lợi ích so với cách tiếp cận dựa trên sự tương đồng:
Nhiều sự kiện có thể được trích xuất từ một nguồn duy nhất và được liên kết với nhiều thực thể khác nhau trong biểu đồ tri thức. Điều này cho phép truy xuất chỉ các dữ kiện liên quan từ một nguồn nhất định chứ không phải toàn bộ đoạn, bao gồm cả thông tin không liên quan.
Nếu nhiều nguồn nói cùng một điều, chúng sẽ tạo ra cùng một nút hoặc cạnh. Thay vì coi chúng là những dữ kiện riêng biệt (và truy xuất nhiều bản sao), chúng có thể được coi là cùng một nút hoặc cạnh và chỉ được truy xuất một lần. Điều này cho phép truy xuất nhiều thông tin đa dạng hơn và/hoặc chỉ tập trung vào các thông tin xuất hiện từ nhiều nguồn.
Biểu đồ có thể được duyệt qua nhiều bước – không chỉ truy xuất thông tin liên quan trực tiếp đến các thực thể trong câu hỏi mà còn kéo lùi những thứ cách đó 2 hoặc 3 bước. Theo cách tiếp cận RAG thông thường, điều này sẽ yêu cầu nhiều vòng truy vấn.
Ngoài những lợi ích của việc sử dụng biểu đồ tri thức cho RAG, LLM còn giúp việc tạo biểu đồ tri thức trở nên dễ dàng hơn. Thay vì yêu cầu các chuyên gia về chủ đề phải xây dựng biểu đồ tri thức một cách cẩn thận, LLM và lời nhắc có thể được sử dụng để trích xuất thông tin từ tài liệu.
Bài đăng này khám phá việc sử dụng biểu đồ tri thức cho RAG, sử dụng
Sau đó, chúng tôi sẽ tạo các runnable LangChain để trích xuất các thực thể từ câu hỏi và truy xuất các biểu đồ con có liên quan. Chúng ta sẽ thấy rằng các thao tác cần thiết để triển khai RAG bằng biểu đồ tri thức không yêu cầu cơ sở dữ liệu biểu đồ hoặc ngôn ngữ truy vấn biểu đồ, cho phép áp dụng phương pháp này bằng cách sử dụng kho dữ liệu thông thường mà bạn có thể đang sử dụng.
Như đã đề cập trước đó, biểu đồ tri thức biểu thị các thực thể riêng biệt dưới dạng nút. Ví dụ: một nút có thể đại diện cho người “Marie Curie” hoặc “tiếng Pháp” cho ngôn ngữ. Trong LangChain, mỗi nút có một tên và một loại. Chúng tôi sẽ xem xét cả hai khi xác định duy nhất một nút, để phân biệt ngôn ngữ “tiếng Pháp” với quốc tịch “tiếng Pháp”.
Mối quan hệ giữa các thực thể tương ứng với các cạnh trong biểu đồ. Mỗi cạnh bao gồm nguồn (ví dụ: người Marie Curie), mục tiêu (giải thưởng Nobel) và một loại, cho biết nguồn liên quan như thế nào đến mục tiêu (ví dụ: “đã thắng”).
Một biểu đồ tri thức ví dụ được trích từ một đoạn văn về Marie Curie sử dụng LangChain được hiển thị bên dưới:
Tùy thuộc vào mục tiêu của bạn, bạn có thể chọn thêm thuộc tính vào các nút và cạnh. Ví dụ: bạn có thể sử dụng thuộc tính để xác định thời điểm đoạt giải Nobel và hạng mục. Đây có thể hữu ích để lọc các cạnh và nút khi duyệt qua biểu đồ trong quá trình truy xuất.
Các thực thể và mối quan hệ bao gồm biểu đồ tri thức có thể được tạo trực tiếp hoặc được nhập từ các nguồn dữ liệu nổi tiếng hiện có. Điều này hữu ích khi bạn muốn sắp xếp kiến thức một cách cẩn thận nhưng lại gây khó khăn cho việc kết hợp thông tin mới một cách nhanh chóng hoặc xử lý lượng lớn thông tin.
May mắn thay, LLM giúp việc trích xuất thông tin từ nội dung trở nên dễ dàng, vì vậy chúng ta có thể sử dụng chúng để trích xuất biểu đồ tri thức.
Dưới đây, tôi sử dụng
LangChain hỗ trợ các tùy chọn khác như
from langchain_experimental.graph_transformers import LLMGraphTransformer from langchain_openai import ChatOpenAI from langchain_core.documents import Document # Prompt used by LLMGraphTransformer is tuned for Gpt4. llm = ChatOpenAI(temperature=0, model_name="gpt-4") llm_transformer = LLMGraphTransformer(llm=llm) text = """ Marie Curie, was a Polish and naturalised-French physicist and chemist who conducted pioneering research on radioactivity. She was the first woman to win a Nobel Prize, the first person to win a Nobel Prize twice, and the only person to win a Nobel Prize in two scientific fields. Her husband, Pierre Curie, was a co-winner of her first Nobel Prize, making them the first-ever married couple to win the Nobel Prize and launching the Curie family legacy of five Nobel Prizes. She was, in 1906, the first woman to become a professor at the University of Paris. """ documents = [Document(page_content=text)] graph_documents = llm_transformer.convert_to_graph_documents(documents) print(f"Nodes:{graph_documents[0].nodes}") print(f"Relationships:{graph_documents[0].relationships}")
Phần này cho thấy cách trích xuất biểu đồ tri thức bằng cách sử dụng LLMGraphTransformer
của LangChain. Bạn có thể sử dụng render_graph_document
được tìm thấy trong kho lưu trữ để hiển thị GraphDocument
LangChain để kiểm tra trực quan.
Trong bài đăng sau, chúng tôi sẽ thảo luận về cách bạn có thể kiểm tra toàn bộ biểu đồ tri thức cũng như biểu đồ con được trích xuất từ mỗi tài liệu và cách bạn có thể áp dụng kỹ thuật nhắc nhở và kỹ thuật kiến thức để cải thiện việc trích xuất tự động.
Việc trả lời các câu hỏi bằng biểu đồ tri thức yêu cầu một số bước. Trước tiên, chúng tôi xác định nơi bắt đầu duyệt qua biểu đồ tri thức. Trong ví dụ này, tôi sẽ nhắc LLM trích xuất các thực thể từ câu hỏi. Sau đó, biểu đồ tri thức được duyệt qua để truy xuất tất cả các mối quan hệ trong một khoảng cách nhất định tính từ các điểm bắt đầu đó. Độ sâu truyền tải mặc định là 3. Các mối quan hệ được truy xuất và câu hỏi ban đầu được sử dụng để tạo lời nhắc và ngữ cảnh để LLM trả lời câu hỏi.
Giống như việc trích xuất biểu đồ tri thức, việc trích xuất các thực thể trong câu hỏi có thể được thực hiện bằng cách sử dụng một mô hình đặc biệt hoặc LLM với lời nhắc cụ thể. Để đơn giản, chúng tôi sẽ sử dụng LLM với lời nhắc sau, bao gồm cả câu hỏi và thông tin về định dạng cần trích xuất. Chúng tôi sử dụng mô hình Pydantic với tên và loại để có cấu trúc phù hợp.
QUERY_ENTITY_EXTRACT_PROMPT = ( "A question is provided below. Given the question, extract up to 5 " "entity names and types from the text. Focus on extracting the key entities " "that we can use to best lookup answers to the question. Avoid stopwords.\n" "---------------------\n" "{question}\n" "---------------------\n" "{format_instructions}\n" ) def extract_entities(llm): prompt = ChatPromptTemplate.from_messages([keyword_extraction_prompt]) class SimpleNode(BaseModel): """Represents a node in a graph with associated properties.""" id: str = Field(description="Name or human-readable unique identifier.") type: str = optional_enum_field(node_types, description="The type or label of the node.") class SimpleNodeList(BaseModel): """Represents a list of simple nodes.""" nodes: List[SimpleNode] output_parser = JsonOutputParser(pydantic_object=SimpleNodeList) return ( RunnablePassthrough.assign( format_instructions=lambda _: output_parser.get_format_instructions(), ) | ChatPromptTemplate.from_messages([QUERY_ENTITY_EXTRACT_PROMPT]) | llm | output_parser | RunnableLambda( lambda node_list: [(n["id"], n["type"]) for n in node_list["nodes"]]) )
Chạy ví dụ trên chúng ta có thể thấy các thực thể được trích xuất:
# Example showing extracted entities (nodes) extract_entities(llm).invoke({ "question": "Who is Marie Curie?"}) # Output: [Marie Curie(Person)]
Tất nhiên, LangChain Runnable có thể được sử dụng trong một chuỗi để trích xuất các thực thể từ một câu hỏi.
Trong tương lai, chúng ta sẽ thảo luận về các cách cải thiện việc trích xuất thực thể, chẳng hạn như xem xét các thuộc tính nút hoặc sử dụng các vectơ nhúng và tìm kiếm tương tự để xác định các điểm bắt đầu có liên quan. Để giữ cho bài đăng đầu tiên này đơn giản, chúng ta sẽ tuân theo lời nhắc ở trên và chuyển sang duyệt qua biểu đồ tri thức để truy xuất knowledge-subgraph
và đưa nó làm ngữ cảnh trong lời nhắc.
Chuỗi trước đó cung cấp cho chúng ta các nút được đề cập. Chúng ta có thể sử dụng các thực thể đó và kho lưu trữ biểu đồ để truy xuất bộ ba kiến thức liên quan. Giống như RAG, chúng tôi thả chúng vào dấu nhắc như một phần của ngữ cảnh và tạo ra câu trả lời.
def _combine_relations(relations): return "\n".join(map(repr, relations)) ANSWER_PROMPT = ( "The original question is given below." "This question has been used to retrieve information from a knowledge graph." "The matching triples are shown below." "Use the information in the triples to answer the original question.\n\n" "Original Question: {question}\n\n" "Knowledge Graph Triples:\n{context}\n\n" "Response:" ) chain = ( { "question": RunnablePassthrough() } # extract_entities is provided by the Cassandra knowledge graph library # and extracts entitise as shown above. | RunnablePassthrough.assign(entities = extract_entities(llm)) | RunnablePassthrough.assign( # graph_store.as_runnable() is provided by the CassandraGraphStore # and takes one or more entities and retrieves the relevant sub-graph(s). triples = itemgetter("entities") | graph_store.as_runnable()) | RunnablePassthrough.assign( context = itemgetter("triples") | RunnableLambda(_combine_relations)) | ChatPromptTemplate.from_messages([ANSWER_PROMPT]) | llm )
Chuỗi trên có thể được thực thi để trả lời một câu hỏi. Ví dụ:
chain.invoke("Who is Marie Curie?") # Output AIMessage( content="Marie Curie is a Polish and French chemist, physicist, and professor who " "researched radioactivity. She was married to Pierre Curie and has worked at " "the University of Paris. She is also a recipient of the Nobel Prize.", response_metadata={ 'token_usage': {'completion_tokens': 47, 'prompt_tokens': 213, 'total_tokens': 260}, 'model_name': 'gpt-4', ... } )
Mặc dù việc sử dụng DB biểu đồ để lưu trữ biểu đồ tri thức có vẻ trực quan nhưng thực tế không cần thiết. Việc truy xuất biểu đồ tri thức phụ xung quanh một vài nút là một thao tác truyền tải biểu đồ đơn giản, trong khi DB biểu đồ được thiết kế cho các truy vấn phức tạp hơn nhiều để tìm kiếm đường dẫn với các chuỗi thuộc tính cụ thể. Hơn nữa, việc truyền tải thường chỉ ở độ sâu 2 hoặc 3, vì các nút bị loại bỏ xa hơn sẽ nhanh chóng trở nên không liên quan đến câu hỏi. Điều này có thể được thể hiện dưới dạng một vài vòng truy vấn đơn giản (một vòng cho mỗi bước) hoặc một phép nối SQL.
Loại bỏ nhu cầu về cơ sở dữ liệu đồ thị riêng biệt giúp sử dụng đồ thị tri thức dễ dàng hơn. Ngoài ra, việc sử dụng Astra DB hoặc Apache Cassandra sẽ đơn giản hóa việc ghi giao dịch vào cả biểu đồ và dữ liệu khác được lưu trữ ở cùng một nơi và có khả năng mở rộng quy mô tốt hơn. Chi phí đó sẽ chỉ đáng giá nếu bạn dự định tạo và thực hiện các truy vấn biểu đồ bằng cách sử dụng Gremlin hoặc Cypher hoặc thứ gì đó tương tự.
Nhưng điều này đơn giản là quá mức cần thiết để truy xuất biểu đồ tri thức phụ và nó mở ra cơ hội cho một loạt các vấn đề khác, chẳng hạn như các truy vấn đi chệch hướng về mặt hiệu suất.
Việc truyền tải này rất dễ thực hiện bằng Python. Bạn có thể tìm thấy mã đầy đủ để triển khai điều này (cả đồng bộ và không đồng bộ) bằng CQL và Trình điều khiển Cassandra trong
def fetch_relation(tg: asyncio.TaskGroup, depth: int, source: Node) -> AsyncPagedQuery: paged_query = AsyncPagedQuery( depth, session.execute_async(query, (source.name, source.type)) ) return tg.create_task(paged_query.next()) results = set() async with asyncio.TaskGroup() as tg: if isinstance(start, Node): start = [start] discovered = {t: 0 for t in start} pending = {fetch_relation(tg, 1, source) for source in start} while pending: done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED) for future in done: depth, relations, more = future.result() for relation in relations: results.add(relation) # Schedule the future for more results from the same query. if more is not None: pending.add(tg.create_task(more.next())) # Schedule futures for the next step. if depth < steps: # We've found a path of length `depth` to each of the targets. # We need to update `discovered` to include the shortest path. # And build `to_visit` to be all of the targets for which this is # the new shortest path. to_visit = set() for r in relations: previous = discovered.get(r.target, steps + 1) if depth < previous: discovered[r.target] = depth to_visit.add(r.target) for source in to_visit: pending.add(fetch_relation(tg, depth + 1, source)) return results
Bài viết này đã hướng dẫn cách xây dựng và sử dụng tính năng trích xuất và truy xuất đồ thị tri thức để trả lời câu hỏi. Điểm mấu chốt là bạn không cần cơ sở dữ liệu đồ thị có ngôn ngữ truy vấn đồ thị như Gremlin hoặc Cypher để thực hiện việc này ngay hôm nay. Một cơ sở dữ liệu tuyệt vời như Astra xử lý song song nhiều truy vấn một cách hiệu quả có thể xử lý việc này.
Trên thực tế, bạn chỉ có thể viết một chuỗi truy vấn đơn giản để truy xuất biểu đồ tri thức phụ cần thiết để trả lời một truy vấn cụ thể. Điều này giúp kiến trúc của bạn đơn giản (không có phần phụ thuộc bổ sung) và cho phép bạn
Chúng tôi đã sử dụng những ý tưởng tương tự này để triển khai các mẫu GraphRAG cho Cassandra và Astra DB. Chúng tôi sẽ đóng góp chúng cho LangChain và nỗ lực mang lại những cải tiến khác cho việc sử dụng biểu đồ tri thức với LLM trong tương lai!
Bởi Ben Chambers, DataStax