時(shí)態(tài)智能AI代理:構(gòu)建能感知時(shí)間變化的下一代RAG知識(shí)系統(tǒng)
RAG正在進(jìn)化,但真正的突破不在參數(shù),而在時(shí)間維度。如何讓AI代理理解“知識(shí)的時(shí)間性”?如何在系統(tǒng)中嵌入“時(shí)態(tài)感知”的機(jī)制?本文以產(chǎn)品視角切入,提出“動(dòng)作-時(shí)間-知識(shí)”三元協(xié)同模型,為AI產(chǎn)品經(jīng)理提供下一代知識(shí)系統(tǒng)的設(shè)計(jì)思路。
用于回答問(wèn)題的 RAG 或代理架構(gòu)依賴于隨著時(shí)間的推移不斷更新的動(dòng)態(tài)知識(shí)庫(kù),例如財(cái)務(wù)報(bào)告或文檔,以便推理和規(guī)劃步驟保持邏輯和準(zhǔn)確。
為了處理這樣的知識(shí)庫(kù),其中規(guī)模不斷增長(zhǎng),幻覺(jué)的機(jī)會(huì)可能會(huì)增加,需要一個(gè)單獨(dú)的邏輯時(shí)間(時(shí)間感知)代理管道來(lái)管理 AI 產(chǎn)品中這個(gè)不斷發(fā)展的知識(shí)庫(kù)?。
- 百分位語(yǔ)義分塊:將大型原始文檔分解為具有上下文意義的小文本塊;
- 原子事實(shí):使用LLM讀取每個(gè)塊并提取原子事實(shí)、它們的時(shí)間戳和所涉及的實(shí)體;
- 實(shí)體解析:通過(guò)自動(dòng)查找和合并重復(fù)實(shí)體(例如,“AMD”和“AdvancedMicroDevices”)來(lái)清理數(shù)據(jù);
- 時(shí)間無(wú)效:當(dāng)新信息到達(dá)時(shí),通過(guò)將過(guò)時(shí)的事實(shí)標(biāo)記為“過(guò)期”來(lái)智能識(shí)別和解決矛盾;
- 知識(shí)圖譜構(gòu)建:將最終的、干凈的、帶有時(shí)間戳的事實(shí)組裝成一個(gè)連接的圖形結(jié)構(gòu),我們的AI代理可以查詢;
- 優(yōu)化的知識(shí)庫(kù):將最終的動(dòng)態(tài)知識(shí)圖譜存儲(chǔ)在可擴(kuò)展的云數(shù)據(jù)庫(kù)中,創(chuàng)建可靠、最新的“大腦”,在此基礎(chǔ)上構(gòu)建最終的RAG或代理系統(tǒng);
預(yù)處理和分析我們的動(dòng)態(tài)數(shù)據(jù)
公司定期分享其財(cái)務(wù)業(yè)績(jī)的最新信息,例如股價(jià)走勢(shì)、執(zhí)行領(lǐng)導(dǎo)層變動(dòng)等重大發(fā)展,以及前瞻性預(yù)期,例如季度收入是否預(yù)計(jì)同比增長(zhǎng) 12% 等。
在醫(yī)療領(lǐng)域,ICD 編碼是數(shù)據(jù)不斷發(fā)展的另一個(gè)例子,從 ICD-9 到 ICD-10 的過(guò)渡使診斷代碼從約 14,000 個(gè)增加到 68,000 個(gè)。
讓我們加載這個(gè)數(shù)據(jù)集并對(duì)其進(jìn)行一些統(tǒng)計(jì)分析以適應(yīng)它。
# Import loader for Hugging Face datasets
from langchain_community.document_loaders import HuggingFaceDatasetLoader
# Dataset configuration
hf_dataset_name = “jlh-ibm/earnings_call” # HF dataset name
subset_name = “transcripts” # Dataset subset to load
# Create the loader (defaults to ‘train’ split)
loader = HuggingFaceDatasetLoader(
path=hf_dataset_name,
name=subset_name,
page_content_column=”transcript” # Column containing the main text
)
# This is the key step. The loader processes the dataset and returns a list of LangChain Document objects.
documents = loader.load()
我們專注于該數(shù)據(jù)集的成績(jī)單子集,其中包含有關(guān)不同公司的原始文本信息。這是數(shù)據(jù)集的基本結(jié)構(gòu),可作為任何 RAG 或 AI 代理架構(gòu)的起點(diǎn)。
# Let’s inspect the result to see the difference
print(f”Loaded {len(documents)} documents.”)
#
#
## OUTPUT ####
Loaded 188 documents.
我們的數(shù)據(jù)中共有 188 份成績(jī)單。這些成績(jī)單屬于不同的公司,我們需要計(jì)算我們的數(shù)據(jù)集中代表了多少個(gè)獨(dú)特的公司。
# Count how many documents each company has
company_counts = {}
# Loop over all loaded documents
for doc in documents:
company = doc.metadata.get(“company”) # Extract company from metadata
if company:
company_counts[company] = company_counts.get(company, 0) + 1
# Display the counts
print(“Total company counts:”)
for company, count in company_counts.items():
print(f”
– {company}: {count}”)
#
#
## OUTPUT ####
Total company counts:
– AMD: 19
– AAPL: 19
– INTC: 19
– MU: 17
– GOOGL: 19
– ASML: 19
– CSCO: 19
– NVDA: 19
– AMZN: 19
– MSFT: 19
幾乎所有公司的分配比例都相等。查看隨機(jī)成績(jī)單的元數(shù)據(jù)。
# Print metadata for two sample documents (index 0 and 33)
print(“Metadata for document[0]:”)
print(documents[0].metadata)
print(“\nMetadata for document[33]:”)
print(documents[33].metadata)
#
#
## OUTPUT ####
{‘company’: ‘AMD’, ‘date’: datetime.date(2016, 7, 21)}
{‘company’: ‘AMZN’, ‘date’: datetime.date(2019, 10, 24)}
公司字段僅指示成績(jī)單屬于哪家公司,?日期字段表示信息所基于的時(shí)間范圍。
# Print the first 200 characters of the first document’s content
first_doc = documents[0]
print(first_doc.page_content[:200])
#
#
## OUTPUT ####
Thomson Reuters StreetEvents Event Transcript
E D I T E D V E R S I O N
Q2 2016 Advanced Micro Devices Inc Earnings Call
JULY 21, 2016 / 9:00PM GMT
=====================================
通過(guò)打印文檔的樣本,我們可以獲得高級(jí)概述。例如,當(dāng)前示例顯示了 AMD 的季度報(bào)告。
成績(jī)單可能很長(zhǎng),因?yàn)樗鼈兇斫o定時(shí)間范圍內(nèi)的信息并包含大量詳細(xì)信息。我們需要檢查這 188 份成績(jī)單平均包含多少個(gè)單詞。
# Calculate the average number of words per document
total_words = sum(len(doc.page_content.split()) for doc in documents)
average_words = total_words / len(documents) if documents else 0
print(f”Average number of words in documents: {average_words:.2f}”)
#
#
## OUTPUT ####
Average number of words in documents: 8797.124
每個(gè)成績(jī)單 ~9K 字是相當(dāng)大的,因?yàn)樗隙ò罅啃畔?。但這正是我們所需要的,創(chuàng)建一個(gè)結(jié)構(gòu)良好的知識(shí)庫(kù) AI 代理涉及處理大量信息,而不僅僅是幾個(gè)小文檔。
通常,財(cái)務(wù)數(shù)據(jù)基于不同的時(shí)間范圍,每個(gè)時(shí)間范圍代表有關(guān)該時(shí)期發(fā)生情況的不同信息。我們可以使用純 Python 代碼而不是 LLM 從成績(jī)單中提取這些時(shí)間范圍,以節(jié)省成本。
import re
from datetime import datetime
# Helper function to extract a quarter string (e.g., “Q1 2023”) from text
def find_quarter(text: str) -> str | None:
“””Return the first quarter-year match found in the text, or None if absent.”””
# Match pattern: ‘Q’ followed by 1 digit, a space, and a 4-digit year
match = re.findall(r”Q\d\s\d{4}”, text)
return match[0] if match else None
# Test on the first document
quarter = find_quarter(documents[0].page_content)
print(f”Extracted Quarter for the first document: {quarter}”)
#
#
## OUTPUT ####
Extracted Quarter for the first document: Q2 2016
執(zhí)行季度日期提取的更好方法是通過(guò)法學(xué)碩士,因?yàn)樗麄兛梢愿钊氲乩斫鈹?shù)據(jù)。但是,由于我們的數(shù)據(jù)在文本方面已經(jīng)結(jié)構(gòu)良好,因此我們暫時(shí)可以在沒(méi)有它們的情況下繼續(xù)進(jìn)行。
百分位語(yǔ)義分塊
通常,我們根據(jù)隨機(jī)拆分或有意義的句子邊界(例如以句號(hào)結(jié)尾)對(duì)數(shù)據(jù)進(jìn)行分塊。但是,這種方法可能會(huì)導(dǎo)致丟失一些信息。
如果我們?cè)诰涮?hào)處拆分,我們就失去了凈收入增長(zhǎng)是由于運(yùn)營(yíng)費(fèi)用下降的緊湊聯(lián)系。
我們將在這里使用基于百分位數(shù)的分塊。讓我們先了解這種方法,然后再實(shí)施它。
- 該文檔使用正則表達(dá)式拆分為句子,通常在.、?或!之后中斷。
- 每個(gè)句子都使用嵌入模型轉(zhuǎn)換為高維向量。
- 計(jì)算連續(xù)句子向量之間的語(yǔ)義距離,值越大表示主題變化越大。
- 收集所有距離,并確定所選的百分位數(shù)(例如95個(gè)百分位數(shù))以捕獲異常大的跳躍。
- 距離大于或等于此閾值的邊界標(biāo)記為塊斷點(diǎn)。
- 這些邊界之間的句子被分組為塊,應(yīng)用min_chunk_size以避免過(guò)小的塊,并在需要時(shí)buffer_size添加重疊。
from langchain_nebius import NebiusEmbeddings
# Set Nebius API key (?? Avoid hardcoding secrets in production code)
os.environ[“NEBIUS_API_KEY”] = “YOUR_API_KEY_HERE”
#
1. Initialize Nebius embedding model
embeddings = NebiusEmbeddings(model=”Qwen/Qwen3-Embedding-8B”)
我們正在使用?Qwen3–8B?通過(guò) LangChain 中的 Nebius AI 生成嵌入。當(dāng)然,LangChain 模塊下還支持許多其他嵌入提供者。
from langchain_experimental.text_splitter import SemanticChunker
# Create a semantic chunker using percentile thresholding
langchain_semantic_chunker = SemanticChunker(
embeddings,
breakpoint_threshold_type=”percentile”, # Use percentile-based splitting
breakpoint_threshold_amount=95 # split at 95th percentile
)
我們選擇了第 95 個(gè)百分位值,這意味著如果連續(xù)句子之間的距離超過(guò)該值,則將其視為斷點(diǎn)。使用循環(huán),我們可以簡(jiǎn)單地在成績(jī)單上啟動(dòng)分塊過(guò)程。
# Store the new, smaller chunk documents
chunked_documents_lc = []
# Printing total number of docs (188) We already know that
print(f”Processing {len(documents)} documents using LangChain’s SemanticChunker…”)
# Chunk each transcript document
for doc in tqdm(documents, desc=”Chunking Transcripts with LangChain”):
# Extract quarter info and copy existing metadata
quarter = find_quarter(doc.page_content)
parent_metadata = doc.metadata.copy()
parent_metadata[“quarter”] = quarter
# Perform semantic chunking (returns Document objects with metadata attached)
chunks = langchain_semantic_chunker.create_documents(
[doc.page_content],
metadatas=[parent_metadata]
)
# Collect all chunks
chunked_documents_lc.extend(chunks)
#
#
## OUTPUT ####
Processing 188 documents using LangChains SemanticChunker…
Chunking Transcripts with LangChain: 100%|██████████| 188/188 [01:03:44<00:00, 224.91s/it]
這個(gè)過(guò)程將花費(fèi)很多時(shí)間,因?yàn)檎缥覀冎翱吹降?,每個(gè)成績(jī)單大約有 8K 字。為了加快速度,我們可以使用異步函數(shù)來(lái)減少運(yùn)行時(shí)間。但是,為了更容易理解,這個(gè)循環(huán)完全符合我們想要實(shí)現(xiàn)的目的。
# Analyze the results of the LangChain chunking process
original_doc_count = len(docs_to_process)
chunked_doc_count = len(chunked_documents_lc)
print(f”Original number of documents (transcripts): {original_doc_count}”)
print(f”Number of new documents (chunks): {chunked_doc_count}”)
print(f”Average chunks per transcript: {chunked_doc_count / original_doc_count:.2f}”)
#
#
## OUTPUT ####
Original number of documents (transcripts): 188
Number of new documents (chunks): 3556
Average chunks per transcript: 19.00
平均而言,我們每個(gè)成績(jī)單有 19 個(gè)塊。讓我們檢查一下我們的一個(gè)成績(jī)單中的隨機(jī)塊。
# Inspect the 11th chunk (index 10)
sample_chunk = chunked_documents_lc[10]
print(“Sample Chunk Content (first 30 chars):”)
print(sample_chunk.page_content[:30] + “…”)
print(“\nSample Chunk Metadata:”)
print(sample_chunk.metadata)
# Calculate average word count per chunk
total_chunk_words = sum(len(doc.page_content.split()) for doc in chunked_documents_lc)
average_chunk_words = total_chunk_words / chunked_doc_count if chunked_documents_lc else 0
print(f”\nAverage number of words per chunk: {average_chunk_words:.2f}”)
#
#
## OUTPUT ####
Sample Chunk Content (first 30 chars):
No, that is a fair question, Matt. So we have been very focused …
Sample Chunk Metadata:
{‘company’: ‘AMD’, ‘date’: datetime.date(2016, 7, 21), ‘quarter’: ‘Q2 2016’}
Average number of words per chunk: 445.42
我們的塊數(shù)據(jù)元數(shù)據(jù)略有變化,包括一些附加信息,例如塊所屬的季度,以及 Python 友好格式的日期時(shí)間,以便于檢索。
使用語(yǔ)句代理提取原子事實(shí)
現(xiàn)在我們已經(jīng)將數(shù)據(jù)整齊地組織成有意義的小塊,我們可以開始使用 LLM 來(lái)讀取這些塊并提取核心事實(shí)。
我們需要將文本分解為盡可能小的“原子”事實(shí)。例如,我們想要的不是單個(gè)復(fù)雜的句子,而是可以獨(dú)立存在的個(gè)別主張。
這個(gè)過(guò)程使我們的人工智能系統(tǒng)以后更容易理解、查詢和推理信息。
為了確保我們的 LLM 為我們提供干凈、可預(yù)測(cè)的輸出,我們需要給它一套嚴(yán)格的指令。在 Python 中執(zhí)行此作的最佳方法是使用?Pydantic?模型。這些模型充當(dāng) LLM 必須遵循的“模式”或“模板”。
首先,讓我們使用枚舉定義標(biāo)簽允許的類別。我們正在使用?(str, Enum)。
from enum import Enum
# Enum for temporal labels describing time sensitivity
class TemporalType(str, Enum):
ATEMPORAL = “ATEMPORAL” # Facts that are always true (e.g., “Earth is a planet”)
STATIC = “STATIC” # Facts about a single point in time (e.g., “Product X launched on Jan 1st”)
DYNAMIC = “DYNAMIC” # Facts describing an ongoing state (e.g., “Lisa Su is the CEO”)
每個(gè)類別都捕獲不同類型的時(shí)間參考:
- 非時(shí)間:普遍正確且隨時(shí)間不變的陳述(例如,“水在100攝氏度時(shí)沸騰”);
- 靜態(tài)的:在特定時(shí)間點(diǎn)成為事實(shí)并此后保持不變的陳述(例如,“John于2020年6月1日被聘為經(jīng)理”);
- 動(dòng)態(tài):可能會(huì)隨著時(shí)間的推移而變化并需要時(shí)間上下文才能準(zhǔn)確解釋的陳述(例如,“約翰是團(tuán)隊(duì)的經(jīng)理”);
# Enum for statement labels classifying statement nature
class StatementType(str, Enum):
FACT = “FACT” # An objective, verifiable claim
OPINION = “OPINION” # A subjective belief or judgment
PREDICTION = “PREDICTION” # A statement about a future event
StatementType 枚舉顯示每個(gè)語(yǔ)句的類型。
- 事實(shí):當(dāng)時(shí)的陳述是真實(shí)的,但以后可能會(huì)改變(例如,“公司上個(gè)季度賺了500萬(wàn)美元”);
- 意見:個(gè)人信念或感受,只有在說(shuō)出來(lái)時(shí)才是真實(shí)的(例如,“我認(rèn)為這個(gè)產(chǎn)品會(huì)做得很好”);
- 預(yù)測(cè):對(duì)未來(lái)的猜測(cè),從現(xiàn)在到預(yù)測(cè)時(shí)間結(jié)束都是正確的(例如,“明年的銷售額將增長(zhǎng)”);
通過(guò)定義這些固定類別,我們確保代理對(duì)其提取的信息進(jìn)行分類的方式的一致性。
現(xiàn)在,讓我們創(chuàng)建 Pydantic 模型,該模型將使用這些枚舉來(lái)定義輸出的結(jié)構(gòu)。
from pydantic import BaseModel, field_validator
# This model defines the structure for a single extracted statement
class RawStatement(BaseModel):
statement: str
statement_type: StatementType
temporal_type: TemporalType
# This model is a container for the list of statements from one chunk
class RawStatementList(BaseModel):
statements: list[RawStatement]
這些模型是我們與 LLM 的合同。我們告訴它,“當(dāng)你處理完一個(gè)塊時(shí),你的答案必須是一個(gè) JSON 對(duì)象,其中包含一個(gè)名為”語(yǔ)句“的列表,并且該列表中的每個(gè)項(xiàng)目都必須有一個(gè)語(yǔ)句?、一個(gè)?statement_type?和一個(gè)?temporal_type”。
這些定義只是對(duì)每個(gè)標(biāo)簽的描述。他們通過(guò)解釋為什么 LLM 應(yīng)該為提取的信息分配特定標(biāo)簽,為 LLM 提供更好的支持?,F(xiàn)在,我們需要使用這些定義創(chuàng)建提示模板。
# Format label definitions into a clean string for prompt injection
definitions_text = “”
for section_key, section_dict in LABEL_DEFINITIONS.items():
# Add a section header with underscores replaced by spaces and uppercased
definitions_text += f”==== {section_key.replace(‘_’, ‘ ‘).upper()} DEFINITIONS ====\n”
# Add each category and its definition under the section
for category, details in section_dict.items():
definitions_text += f”
– {category}: {details.get(‘definition’, ”)}\n”
這個(gè)?definitions_text?字符串將是我們提示的關(guān)鍵部分,為 LLM 提供準(zhǔn)確執(zhí)行任務(wù)所需的“教科書”定義。
現(xiàn)在,我們將構(gòu)建主提示模板。該模板將所有內(nèi)容匯集在一起:輸入、任務(wù)指令、標(biāo)簽定義以及一個(gè)關(guān)鍵示例,以向 LLM 展示良好輸出的確切外觀。
請(qǐng)注意我們?nèi)绾问褂?{{?和?}}?在 JSON 示例中使用大括號(hào)“轉(zhuǎn)義”。這告訴 LangChain 這些是文本的一部分,而不是要填充的變量。
from langchain_core.prompts import ChatPromptTemplate
# Define the prompt template for statement extraction and labeling
statement_extraction_prompt_template = “””
You are an expert extracting atomic statements from text.
Inputs:
– main_entity: {main_entity}
– document_chunk: {document_chunk}
Tasks:
1. Extract clear, single-subject statements.
2. Label each as FACT, OPINION, or PREDICTION.
3. Label each temporally as STATIC, DYNAMIC, or ATEMPORAL.
4. Resolve references to main_entity and include dates/quantities.
Return ONLY a JSON object with the statements and labels.
“””
# Create a ChatPromptTemplate from the string template
prompt = ChatPromptTemplate.from_template(statement_extraction_prompt_template)
最后,我們將一切連接起來(lái)。我們將創(chuàng)建一個(gè) LangChain“鏈”,將我們的提示鏈接到 LLM,并告訴 LLM 根據(jù)我們的?RawStatementList?模型構(gòu)建其輸出。
我們將通過(guò) Nebius 使用?deepseek-ai/DeepSeek-V3?模型來(lái)完成此任務(wù),因?yàn)樗δ軓?qiáng)大且擅長(zhǎng)遵循復(fù)雜的指令。
from langchain_nebius import ChatNebius
import json
# Initialize our LLM
llm = ChatNebius(model=”deepseek-ai/DeepSeek-V3″)
# Create the chain: prompt -> LLM -> structured output parser
statement_extraction_chain = prompt | llm.with_structured_output(RawStatementList)
到目前為止,我們已經(jīng)完成了原子事實(shí)步驟,我們準(zhǔn)確地提取了時(shí)間和基于語(yǔ)句的事實(shí),這些事實(shí)可以在以后根據(jù)需要進(jìn)行更新。
使用驗(yàn)證檢查代理精確定位時(shí)間
我們已經(jīng)成功地從文本中提取了原子語(yǔ)句?,F(xiàn)在,我們需要提取?when。每個(gè)語(yǔ)句都需要一個(gè)精確的時(shí)間戳來(lái)告訴我們它何時(shí)有效。
產(chǎn)品是上周推出還是去年推出?或者 CEO 是在 2016 年或 2018 年擔(dān)任過(guò)她的職務(wù)嗎?
這是使我們的知識(shí)庫(kù)真正“時(shí)間化”的最重要一步。
從自然語(yǔ)言中提取日期很棘手。一份聲明可能會(huì)說(shuō)下個(gè)季度?、?三個(gè)月前或?2017 年?。人類理解這一點(diǎn),但計(jì)算機(jī)需要一個(gè)具體的日期,比如?2017-01-01。
我們的目標(biāo)是創(chuàng)建一個(gè)專門的代理,它可以讀取聲明,并使用原始文檔的發(fā)布日期作為參考,找出兩個(gè)關(guān)鍵時(shí)間戳:
- valid_at:事實(shí)成為現(xiàn)實(shí)的日期;
- invalid_at:事實(shí)不再為真的日期(如果它不再有效);
就像以前一樣,我們將首先定義 Pydantic 模型,以確保我們的 LLM 為我們提供干凈、結(jié)構(gòu)化的日期輸出。
首先,我們需要一個(gè)強(qiáng)大的輔助函數(shù)來(lái)解析 LLM 可能返回的各種日期格式。此函數(shù)可以處理?2017?或?2016–07–21?等字符串,并將它們轉(zhuǎn)換為正確的 Python?日期時(shí)間對(duì)象。
from pydantic import BaseModel, Field, field_validator
from datetime import datetime
# Model for raw temporal range with date strings as ISO 8601
class RawTemporalRange(BaseModel):
valid_at: str | None = Field(None, description=”The start date/time in ISO 8601 format.”)
invalid_at: str | None = Field(None, description=”The end date/time in ISO 8601 format.”)
# Model for validated temporal range with datetime objects
class TemporalValidityRange(BaseModel):
valid_at: datetime | None = None
invalid_at: datetime | None = None
# Validator to parse date strings into datetime objects before assignment
@field_validator(“valid_at”, “invalid_at”, mode=”before”)
@classmethod
def _parse_date_string(cls, value: str | datetime | None) -> datetime | None:
return parse_date_str(value)
這種兩步模型方法是一個(gè)很好的實(shí)踐。它將原始 LLM 輸出與我們干凈、經(jīng)過(guò)驗(yàn)證的應(yīng)用程序數(shù)據(jù)分開,使管道更加健壯。
接下來(lái),我們專門為此日期提取任務(wù)創(chuàng)建一個(gè)新提示。我們將向 LLM 提供我們提取的語(yǔ)句之一,并要求它根據(jù)上下文確定有效日期。
# Prompt guiding the LLM to extract temporal validity ranges from statements
date_extraction_prompt_template = “””
You are a temporal information extraction specialist.
INPUTS:
– statement: “{statement}”
– statement_type: “{statement_type}”
– temporal_type: “{temporal_type}”
– publication_date: “{publication_date}”
– quarter: “{quarter}”
TASK:
– Analyze the statement and determine the temporal validity range (valid_at, invalid_at).
– Use the publication date as the reference point for relative expressions (e.g., “currently”).
– If a relationship is ongoing or its end is not specified, `invalid_at` should be null.
GUIDANCE:
– For STATIC statements, `valid_at` is the date the event occurred, and `invalid_at` is null.
– For DYNAMIC statements, `valid_at` is when the state began, and `invalid_at` is when it ended.
– Return dates in ISO 8601 format (e.g., YYYY-MM-DDTHH:MM:SSZ).
**Output format**
Return ONLY a valid JSON object matching the schema for `RawTemporalRange`.
“””
# Create a LangChain prompt template from the string
date_extraction_prompt = ChatPromptTemplate.from_template(date_extraction_prompt_template)
此提示非常專業(yè)化。它告訴 LLM 像“時(shí)間專家”一樣行事,并為如何處理不同類型的語(yǔ)句提供了明確的規(guī)則。
現(xiàn)在我們?yōu)榇瞬襟E構(gòu)建 LangChain 鏈,并在我們之前提取的語(yǔ)句之一上對(duì)其進(jìn)行測(cè)試。
# Reuse the existing LLM instance.
# Create a chain by connecting the date extraction prompt
# with the LLM configured to output structured RawTemporalRange objects.
date_extraction_chain = date_extraction_prompt | llm.with_structured_output(RawTemporalRange)
這是一個(gè)動(dòng)態(tài)事實(shí),因此我們預(yù)計(jì)日期?valid_at,但未指定結(jié)束日期。
LLM 正確解釋了“2017 年上半年”,并將其轉(zhuǎn)換為精確的日期范圍。它明白,這個(gè)動(dòng)態(tài)聲明有明確的開始和結(jié)束。
我們現(xiàn)在已經(jīng)成功地在我們的事實(shí)中添加了時(shí)間維度。下一步是將陳述的文本進(jìn)一步分解為知識(shí)圖譜的基本結(jié)構(gòu),即三元組?。
將事實(shí)結(jié)構(gòu)化為三元組
現(xiàn)在,我們需要將這些自然語(yǔ)言句子轉(zhuǎn)換為人工智能代理可以輕松理解和連接的格式。
三元組將一個(gè)事實(shí)分解為三個(gè)核心組成部分:
- 主題:事實(shí)是關(guān)于的主要實(shí)體;
- 謂語(yǔ):關(guān)系或行動(dòng);
- 對(duì)象:主題相關(guān)的實(shí)體或概念;
通過(guò)將我們所有的陳述轉(zhuǎn)換為這種格式,我們可以構(gòu)建一個(gè)相互關(guān)聯(lián)的事實(shí)網(wǎng)絡(luò),即我們的知識(shí)圖譜。
和以前一樣,我們將首先定義?Pydantic?模型,這些模型將構(gòu)建 LLM 的輸出。這種提取是我們迄今為止最復(fù)雜的,因?yàn)?LLM 需要同時(shí)識(shí)別實(shí)體(名詞)和關(guān)系(三元組)。
首先,讓我們定義我們希望代理使用的謂詞關(guān)系的固定列表。這確保了我們整個(gè)知識(shí)圖譜的一致性。
from enum import Enum # Import the Enum base class to create enumerated constants
# Enum representing a fixed set of relationship predicates for graph consistency
class Predicate(str, Enum):
# Each member of this Enum represents a specific type of relationship between entities
IS_A = “IS_A” # Represents an “is a” relationship (e.g., a Dog IS_A Animal)
HAS_A = “HAS_A” # Represents possession or composition (e.g., a Car HAS_A Engine)
LOCATED_IN = “LOCATED_IN” # Represents location relationship (e.g., Store LOCATED_IN City)
HOLDS_ROLE = “HOLDS_ROLE” # Represents role or position held (e.g., Person HOLDS_ROLE Manager)
PRODUCES = “PRODUCES” # Represents production or creation relationship
SELLS = “SELLS” # Represents selling relationship between entities
LAUNCHED = “LAUNCHED” # Represents launch events (e.g., Product LAUNCHED by Company)
DEVELOPED = “DEVELOPED” # Represents development relationship (e.g., Software DEVELOPED by Team)
ADOPTED_BY = “ADOPTED_BY” # Represents adoption relationship (e.g., Policy ADOPTED_BY Organization)
INVESTS_IN = “INVESTS_IN” # Represents investment relationships (e.g., Company INVESTS_IN Startup)
COLLABORATES_WITH = “COLLABORATES_WITH” # Represents collaboration between entities
SUPPLIES = “SUPPLIES” # Represents supplier relationship (e.g., Supplier SUPPLIES Parts)
HAS_REVENUE = “HAS_REVENUE” # Represents revenue relationship for entities
INCREASED = “INCREASED” # Represents an increase event or metric change
DECREASED = “DECREASED” # Represents a decrease event or metric change
RESULTED_IN = “RESULTED_IN” # Represents causal relationship (e.g., Event RESULTED_IN Outcome)
TARGETS = “TARGETS” # Represents target or goal relationship
PART_OF = “PART_OF” # Represents part-whole relationship (e.g., Wheel PART_OF Car)
DISCONTINUED = “DISCONTINUED” # Represents discontinued status or event
SECURED = “SECURED” # Represents secured or obtained relationship (e.g., Funding SECURED by Company)
現(xiàn)在,我們定義了原始實(shí)體和三元組的模型,以及一個(gè)容器模型?RawExtraction,以保存整個(gè)輸出。
from pydantic import BaseModel, Field
from typing import List, Optional
# Model representing an entity extracted by the LLM
class RawEntity(BaseModel):
entity_idx: int = Field(description=”A temporary, 0-indexed ID for this entity.”)
name: str = Field(description=”The name of the entity, e.g., ‘AMD’ or ‘Lisa Su’.”)
type: str = Field(“Unknown”, description=”The type of entity, e.g., ‘Organization’, ‘Person’.”)
description: str = Field(“”, description=”A brief description of the entity.”)
# Model representing a single subject-predicate-object triplet
class RawTriplet(BaseModel):
subject_name: str
subject_id: int = Field(description=”The entity_idx of the subject.”)
predicate: Predicate
object_name: str
object_id: int = Field(description=”The entity_idx of the object.”)
value: Optional[str] = Field(None, description=”An optional value, e.g., ‘10%’.”)
# Container for all entities and triplets extracted from a single statement
class RawExtraction(BaseModel):
entities: List[RawEntity]
triplets: List[RawTriplet]
這個(gè)結(jié)構(gòu)非常巧妙。它要求 LLM 首先列出它找到的所有實(shí)體,并為每個(gè)實(shí)體提供一個(gè)臨時(shí)編號(hào) (entity_idx)。
然后,它要求法學(xué)碩士使用這些數(shù)字構(gòu)建三元組,在關(guān)系和所涉及的實(shí)體之間建立清晰的聯(lián)系。
接下來(lái),我們創(chuàng)建將指導(dǎo) LLM 的提示和定義。此提示非常具體,指示模型僅關(guān)注關(guān)系,忽略我們已經(jīng)提取的任何基于時(shí)間的信息。
現(xiàn)在是主要提示模板。同樣,我們小心翼翼地使用?{{?和?}}?轉(zhuǎn)義 JSON 示例,以便 LangChain 可以正確解析它。
# Prompt for extracting entities and subject-predicate-object triplets from a statement
triplet_extraction_prompt_template = “””
You are an information-extraction assistant.
Task: From the statement, identify all entities (people, organizations, products, concepts) and all triplets (subject, predicate, object) describing their relationships.
Statement: “{statement}”
Predicate list:
{predicate_instructions}
Guidelines:
– List entities with unique `entity_idx`.
– List triplets linking subjects and objects by `entity_idx`.
– Exclude temporal expressions from entities and triplets.
Example:
Statement: “Google’s revenue increased by 10% from January through March.”
Output: {{
“entities”: [
{{“entity_idx”: 0, “name”: “Google”, “type”: “Organization”, “description”: “A multinational technology company.”}},
{{“entity_idx”: 1, “name”: “Revenue”, “type”: “Financial Metric”, “description”: “Income from normal business.”}}
],
“triplets”: [
{{“subject_name”: “Google”, “subject_id”: 0, “predicate”: “INCREASED”, “object_name”: “Revenue”, “object_id”: 1, “value”: “10%”}}
]
}}
Return ONLY a valid JSON object matching `RawExtraction`.
“””
# Initializing the prompt template
triplet_extraction_prompt = ChatPromptTemplate.from_template(triplet_extraction_prompt_template)
最后,我們創(chuàng)建第三條鏈并在我們的一個(gè)語(yǔ)句上對(duì)其進(jìn)行測(cè)試。
# Create the chain for triplet and entity extraction.
triplet_extraction_chain = triplet_extraction_prompt | llm.with_structured_output(RawExtraction)
# Let’s use the same statement we’ve been working with.
sample_statement_for_triplets = extracted_statements_list.statements[0]
print(f”–
– Running triplet extraction for statement —“)
print(f’Statement: “{sample_statement_for_triplets.statement}”‘)
# Invoke the chain.
raw_extraction_result = triplet_extraction_chain.invoke({
“statement”: sample_statement_for_triplets.statement,
“predicate_instructions”: predicate_instructions_text
})
print(“\n–
– Triplet Extraction Result —“)
print(raw_extraction_result.model_dump_json(indent=2))
LLM 正確地將“AMD”和“server launch”識(shí)別為關(guān)鍵實(shí)體,并將它們與?TARGETS?謂詞聯(lián)系起來(lái),完美地捕捉到了原句的含義。
我們現(xiàn)在已經(jīng)完成了所有單獨(dú)的提取步驟。我們有一個(gè)系統(tǒng),可以獲取一段文本并提取語(yǔ)句、日期、實(shí)體和三元組。下一步是將所有這些信息組合成一個(gè)統(tǒng)一的對(duì)象,該對(duì)象表示一個(gè)完整的?“時(shí)間事件”。
組裝時(shí)態(tài)事件
我們現(xiàn)在已經(jīng)完成了所有單獨(dú)的提取步驟。我們有一個(gè)系統(tǒng),可以獲取一段文本并提?。?/p>
- 陳述:原子事實(shí);
- 日期:每個(gè)事實(shí)的“時(shí)間”;
- 實(shí)體和三元組:結(jié)構(gòu)化格式的“誰(shuí)”和“什么”;
我們提取過(guò)程的最后一步是將所有這些部分放在一起。我們將創(chuàng)建一個(gè)名為 TemporalEvent 的主數(shù)據(jù)模型,該模型將有關(guān)單個(gè)語(yǔ)句的所有信息合并到一個(gè)干凈、統(tǒng)一的對(duì)象中。
此?TemporalEvent?將是我們?cè)谝牍艿赖钠溆嗖糠种惺褂玫闹行膶?duì)象。它將包含所有內(nèi)容:原始語(yǔ)句、其類型、其時(shí)間范圍以及指向從中派生的所有三元組的鏈接。
首先,我們將?RawEntity?和?RawTriplet?對(duì)象轉(zhuǎn)換為最終的持久實(shí)體和三元組形式,并配有唯一的 UUID。
# Assume these are already defined from previous steps:
# sample_statement, final_temporal_range, raw_extraction_result
print(“–
– Assembling the final TemporalEvent —“)
#
1. Convert raw entities to persistent Entity objects with UUIDs
idx_to_entity_map: dict[int, Entity] = {}
final_entities: list[Entity] = []
for raw_entity in raw_extraction_result.entities:
entity = Entity(
name=raw_entity.name,
type=raw_entity.type,
description=raw_entity.description
)
idx_to_entity_map[raw_entity.entity_idx] = entity
final_entities.append(entity)
print(f”Created {len(final_entities)} persistent Entity objects.”)
#
2. Convert raw triplets to persistent Triplet objects, linking entities via UUIDs
final_triplets: list[Triplet] = []
for raw_triplet in raw_extraction_result.triplets:
subject_entity = idx_to_entity_map[raw_triplet.subject_id]
object_entity = idx_to_entity_map[raw_triplet.object_id]
triplet = Triplet(
subject_name=subject_entity.name,
subject_id=subject_entity.id,
predicate=raw_triplet.predicate,
object_name=object_entity.name,
object_id=object_entity.id,
value=raw_triplet.value
)
final_triplets.append(triplet)
print(f”Created {len(final_triplets)} persistent Triplet objects.”)
我們已經(jīng)成功地跟蹤了一條信息,從原始段落一直到完全結(jié)構(gòu)化的、帶有時(shí)間戳的?TemporalEvent。
但是對(duì)每個(gè)語(yǔ)句手動(dòng)執(zhí)行此作是不可能的。下一步是使用?LangGraph?自動(dòng)化整個(gè)裝配線,以一次處理我們所有的塊。
使用 LangGraph 自動(dòng)化管道
到目前為止,我們已經(jīng)構(gòu)建了三個(gè)強(qiáng)大的、專門的“代理”(或鏈):一個(gè)用于提取語(yǔ)句,一個(gè)用于日期,一個(gè)用于三元組。我們還了解了如何手動(dòng)將它們的輸出組合成最終的?TemporalEvent。
構(gòu)建 LangGraph 的第一步是定義其“狀態(tài)”。狀態(tài)是圖的內(nèi)存,它是在每個(gè)節(jié)點(diǎn)之間傳遞的數(shù)據(jù)。我們將定義一個(gè)狀態(tài),該狀態(tài)可以保存我們的塊列表和我們?cè)诖诉^(guò)程中創(chuàng)建的所有新對(duì)象。
from typing import List, TypedDict
from langchain_core.documents import Document
class GraphState(TypedDict):
“””
TypedDict representing the overall state of the knowledge graph ingestion.
Attributes:
chunks: List of Document chunks being processed.
temporal_events: List of TemporalEvent objects extracted from chunks.
entities: List of Entity objects in the graph.
triplets: List of Triplet objects representing relationships.
“””
chunks: List[Document]
temporal_events: List[TemporalEvent]
entities: List[Entity]
triplets: List[Triplet]
現(xiàn)在,我們將把之前的所有邏輯組合成一個(gè)強(qiáng)大的函數(shù)。該函數(shù)將是我們圖表中的主要“節(jié)點(diǎn)”。它從狀態(tài)中獲取塊列表,并通過(guò)三個(gè)并行步驟編排整個(gè)提取過(guò)程。
我們已經(jīng)成功地實(shí)現(xiàn)了提取管道的自動(dòng)化。然而,數(shù)據(jù)仍然是“原始的”。例如,LLM 可能已將“AMD”和“Advanced Micro Devices”提取為兩個(gè)獨(dú)立的實(shí)體。下一步是在我們的裝配線上添加一個(gè)質(zhì)量控制站:?實(shí)體解析?。
使用實(shí)體解析清理我們的數(shù)據(jù)
我們的自動(dòng)化管道現(xiàn)在正在提取大量信息。但是,如果您仔細(xì)觀察實(shí)體,您可能會(huì)注意到一個(gè)問(wèn)題。
這是一個(gè)關(guān)鍵問(wèn)題。如果我們不修復(fù)它,我們的知識(shí)圖譜就會(huì)變得混亂和不可靠。有關(guān)“AMD”的查詢會(huì)遺漏與“Advanced Micro Devices”相關(guān)的事實(shí)。
這稱為實(shí)體解析?。目標(biāo)是識(shí)別引用同一現(xiàn)實(shí)世界實(shí)體的所有不同名稱(或“提及”),并將它們合并到一個(gè)單一的、權(quán)威的、“規(guī)范”的 ID 下。
為了解決這個(gè)問(wèn)題,我們將在我們的 LangGraph 裝配線上添加一個(gè)新的質(zhì)量控制站。該節(jié)點(diǎn)將:
- 使用模糊字符串匹配對(duì)具有相似名稱的實(shí)體進(jìn)行集群;
- 為群集中的所有實(shí)體分配一個(gè)規(guī)范ID;
- 更新我們的三胞胎以使用這些新的、干凈的ID;
為了跟蹤我們的規(guī)范實(shí)體,我們需要一個(gè)地方來(lái)存儲(chǔ)它們。在本教程中,我們將使用 Python 的內(nèi)置?sqlite3?庫(kù)創(chuàng)建一個(gè)簡(jiǎn)單的內(nèi)存數(shù)據(jù)庫(kù)。在實(shí)際應(yīng)用程序中,這將是一個(gè)持久的生產(chǎn)級(jí)數(shù)據(jù)庫(kù)。
讓我們創(chuàng)建內(nèi)存數(shù)據(jù)庫(kù)和我們需要的表。
import sqlite3
def setup_in_memory_db():
“””
Sets up an in-memory SQLite database and creates the ‘entities’ table.
The ‘entities’ table schema:
– id: TEXT, Primary Key
– name: TEXT, name of the entity
– type: TEXT, type/category of the entity
– description: TEXT, description of the entity
– is_canonical: INTEGER, flag to indicate if entity is canonical (default 1)
Returns:
sqlite3.Connection: A connection object to the in-memory database.
“””
# Establish connection to an in-memory SQLite database
conn = sqlite3.connect(“:memory:”)
# Create a cursor object to execute SQL commands
cursor = conn.cursor()
# Create the ‘entities’ table if it doesn’t already exist
cursor.execute(“””
CREATE TABLE IF NOT EXISTS entities (
id TEXT PRIMARY KEY,
name TEXT,
type TEXT,
description TEXT,
is_canonical INTEGER DEFAULT 1
)
“””)
# Commit changes to save the table schema
conn.commit()
# Return the connection object for further use
return conn
# Create the database connection and set up the entities table
db_conn = setup_in_memory_db()
這個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù)將充當(dāng)我們的代理識(shí)別的所有干凈、權(quán)威實(shí)體的中央注冊(cè)表。
現(xiàn)在我們將為新的 LangGraph 節(jié)點(diǎn)創(chuàng)建函數(shù)。此函數(shù)將包含用于查找和合并重復(fù)實(shí)體的邏輯。對(duì)于模糊字符串匹配,我們將使用一個(gè)名為?rapidfuzz?的便捷庫(kù)。您可以簡(jiǎn)單地使用?pip install rapidfuzz?安裝它。
這是解析的實(shí)體輸出:
# —
– Sample of a Resolved Entity —
{
“id”: “1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d”,
“name”: “Advanced Micro Devices”,
“type”: “Organization”,
“description”: “A semiconductor company.”,
“resolved_id”: “b1c2d3e4-f5g6-4h7i-8j9k-0l1m2n3o4p5q”
}
# —
– Sample Triplet with Resolved IDs —
{
“id”: “c1d2e3f4-a5b6-4c7d-8e9f-0g1h2i3j4k5l”,
“subject_name”: “AMD”,
“subject_id”: “b1c2d3e4-f5g6-4h7i-8j9k-0l1m2n3o4p5q”,
“predicate”: “TARGETS”,
“object_name”: “server launch”,
“object_id”: “d1e2f3a4-b5c6-4d7e-8f9g-0h1i2j3k4l5m”,
“value”: null
}
這太完美了。您可以看到,名為“Advanced Micro Devices”的實(shí)體現(xiàn)在有一個(gè)指向規(guī)范實(shí)體 ID 的?resolved_id(可能是名為“AMD”的實(shí)體)。我們所有的三胞胎現(xiàn)在都使用這些干凈的規(guī)范 ID。
我們的數(shù)據(jù)現(xiàn)在不僅結(jié)構(gòu)化且?guī)в袝r(shí)間戳,而且干凈且一致。下一步是我們代理最聰明的部分:處理與失效代理的矛盾。
使用無(wú)效代理使我們的知識(shí)充滿活力
我們的數(shù)據(jù)現(xiàn)在被分塊、提取、結(jié)構(gòu)化和清理。但我們?nèi)匀粵](méi)有解決時(shí)間數(shù)據(jù)的核心挑戰(zhàn):事實(shí)會(huì)隨著時(shí)間的推移而變化。
想象一下,我們的知識(shí)庫(kù)包含以下事實(shí):?(John Smith) –[HOLDS_ROLE]–> (CFO)?.這是一個(gè)動(dòng)態(tài)的事實(shí),這意味著它在一段時(shí)間內(nèi)是正確的。當(dāng)我們的代理人閱讀一份新文件時(shí),會(huì)發(fā)生什么,上面寫著?“Jane Doe 于 2024 年 1 月 1 日被任命為首席財(cái)務(wù)官”?
第一個(gè)事實(shí)現(xiàn)在已經(jīng)過(guò)時(shí)了。一個(gè)簡(jiǎn)單的知識(shí)庫(kù)不會(huì)知道這一點(diǎn),但一個(gè)臨時(shí)的知識(shí)庫(kù)必須知道這一點(diǎn)。這就是無(wú)效代理的工作:充當(dāng)裁判,發(fā)現(xiàn)這些矛盾,并更新舊事實(shí)以將其標(biāo)記為過(guò)期。
為此,我們將向 LangGraph 管道添加一個(gè)最終節(jié)點(diǎn)。該節(jié)點(diǎn)將:
- 為我們所有的新語(yǔ)句生成嵌入,以理解它們的語(yǔ)義含義;
- 將新的動(dòng)態(tài)事實(shí)與我們數(shù)據(jù)庫(kù)中的現(xiàn)有事實(shí)進(jìn)行比較;
- 使用LLM對(duì)新事實(shí)是否使舊事實(shí)無(wú)效做出最終判斷;
- 如果事實(shí)無(wú)效,它將更新其invalid_at時(shí)間戳;
首先,我們需要為失效邏輯準(zhǔn)備我們的環(huán)境。這涉及將事件表和三元組表添加到我們的內(nèi)存數(shù)據(jù)庫(kù)中。這模擬了代理可以檢查的真實(shí)、持久的知識(shí)庫(kù)。
# Obtain a cursor from the existing database connection
cursor = db_conn.cursor()
# Create the ‘events’ table to store event-related data
cursor.execute(“””
CREATE TABLE IF NOT EXISTS events (
id TEXT PRIMARY KEY, –
– Unique identifier for each event
chunk_id TEXT, –
– Identifier for the chunk this event belongs to
statement TEXT, –
– Textual representation of the event
statement_type TEXT, –
– Type/category of the statement (e.g., assertion, question)
temporal_type TEXT, –
– Temporal classification (e.g., past, present, future)
valid_at TEXT, –
– Timestamp when the event becomes valid
invalid_at TEXT, –
– Timestamp when the event becomes invalid
embedding BLOB –
– Optional embedding data stored as binary (e.g., vector)
)
“””)
# Create the ‘triplets’ table to store relations between entities for events
cursor.execute(“””
CREATE TABLE IF NOT EXISTS triplets (
id TEXT PRIMARY KEY, –
– Unique identifier for each triplet
event_id TEXT, –
– Foreign key referencing ‘events.id’
subject_id TEXT, –
– Subject entity ID in the triplet
predicate TEXT –
– Predicate describing relation or action
)
“””)
# Commit all changes to the in-memory database
db_conn.commit()
這樣就完成了整個(gè)引入管道。我們構(gòu)建了一個(gè)系統(tǒng),可以從原始文本中自動(dòng)創(chuàng)建一個(gè)干凈、結(jié)構(gòu)化和時(shí)間感知的知識(shí)庫(kù)。下一步也是最后一步是獲取這些豐富的數(shù)據(jù)并構(gòu)建我們的知識(shí)圖譜。
組裝時(shí)態(tài)知識(shí)圖譜
到目前為止,我們已經(jīng)成功地將原始、混亂的成績(jī)單轉(zhuǎn)換為干凈、結(jié)構(gòu)化和時(shí)間感知的事實(shí)集合。
我們已成功實(shí)現(xiàn):
- 提取:提取語(yǔ)句、日期和三元組;
- 解決方法:清理重復(fù)實(shí)體;
- 無(wú)效:更新不再正確的事實(shí);
現(xiàn)在,是時(shí)候獲取這些最終的高質(zhì)量數(shù)據(jù)并構(gòu)建我們的知識(shí)圖譜了。該圖將是我們的檢索代理將查詢以回答用戶問(wèn)題的“大腦”。
圖表是此類數(shù)據(jù)的完美結(jié)構(gòu),因?yàn)樗际顷P(guān)于連接的。
構(gòu)建和測(cè)試多步驟檢索代理
我們已經(jīng)成功地構(gòu)建了我們的“智能數(shù)據(jù)庫(kù)”,這是一個(gè)豐富、干凈且具有時(shí)間感知能力的知識(shí)圖譜?,F(xiàn)在回報(bào)來(lái)了,構(gòu)建一個(gè)智能代理,可以與此圖進(jìn)行對(duì)話以回答復(fù)雜的問(wèn)題。
一個(gè)簡(jiǎn)單的 RAG 系統(tǒng)可能會(huì)找到一個(gè)相關(guān)事實(shí)并使用它來(lái)回答一個(gè)問(wèn)題。但是,如果答案需要連接多條信息怎么辦?
簡(jiǎn)單的檢索系統(tǒng)無(wú)法回答這個(gè)問(wèn)題。它需要一個(gè)多步驟的過(guò)程:
- 查找有關(guān)2016年AMD和數(shù)據(jù)中心的事實(shí);
- 查找有關(guān)2017年AMD和數(shù)據(jù)中心的事實(shí);
- 比較兩年的結(jié)果;
- 綜合最終摘要;
這就是我們的多步驟檢索代理將要做的事情。我們將使用 LangGraph 構(gòu)建它,它將包含三個(gè)關(guān)鍵組件:一個(gè)規(guī)劃器、一組工具和一個(gè) Orchestrator。
在我們的主代理開始工作之前,我們希望它思考并制定一個(gè)高層次的計(jì)劃。這使得代理更加可靠和專注。規(guī)劃器是一個(gè)簡(jiǎn)單的 LLM 鏈,其唯一工作是將用戶的問(wèn)題分解為一系列可作的步驟。
代理成功地按照計(jì)劃進(jìn)行作,用不同的日期范圍調(diào)用其工具兩次,然后將結(jié)果綜合成一個(gè)完美的比較答案。這證明了將時(shí)間知識(shí)圖譜與多步驟代理相結(jié)合的力量。
我們下一步能做什么?
我們構(gòu)建了一個(gè)強(qiáng)大的原型,演示了如何創(chuàng)建一個(gè)知識(shí)庫(kù),它不僅僅是一個(gè)靜態(tài)庫(kù),而是一個(gè)動(dòng)態(tài)系統(tǒng),它了解事實(shí)如何隨時(shí)間演變。
現(xiàn)在我們的代理工作了,下一個(gè)關(guān)鍵問(wèn)題是:“它工作得如何??回答這個(gè)問(wèn)題需要一個(gè)正式的評(píng)估過(guò)程。主要有三種方法:
- 黃金答案(黃金標(biāo)準(zhǔn)):您創(chuàng)建一組測(cè)試問(wèn)題,并讓人類專家編寫完美的答案。然后,您將代理的輸出與這些“黃金”答案進(jìn)行比較。這是最準(zhǔn)確的方法,但速度慢且成本高昂。
- LLM-as-Judge(可擴(kuò)展的方法):您使用強(qiáng)大的LLM(如GPT-4)充當(dāng)“法官”。它對(duì)代理的答案的正確性和相關(guān)性進(jìn)行評(píng)分。這既快速又便宜,非常適合快速測(cè)試和迭代。
- 人工反饋(真實(shí)世界測(cè)試):部署代理后,您可以添加簡(jiǎn)單的反饋按鈕(如豎起大拇指/豎起大拇指)以讓用戶對(duì)答案進(jìn)行評(píng)分。這告訴您您的代理對(duì)實(shí)際任務(wù)有多大用處。
本文由 @來(lái)學(xué)習(xí)一下 原創(chuàng)發(fā)布于人人都是產(chǎn)品經(jīng)理。未經(jīng)作者許可,禁止轉(zhuǎn)載
題圖來(lái)自Unsplash,基于CC0協(xié)議
該文觀點(diǎn)僅代表作者本人,人人都是產(chǎn)品經(jīng)理平臺(tái)僅提供信息存儲(chǔ)空間服務(wù)
- 目前還沒(méi)評(píng)論,等你發(fā)揮!