時(shí)態(tài)智能AI代理:構(gòu)建能感知時(shí)間變化的下一代RAG知識(shí)系統(tǒng)

0 評(píng)論 2009 瀏覽 4 收藏 56 分鐘

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ù)?。

  1. 百分位語(yǔ)義分塊:將大型原始文檔分解為具有上下文意義的小文本塊;
  2. 原子事實(shí):使用LLM讀取每個(gè)塊并提取原子事實(shí)、它們的時(shí)間戳和所涉及的實(shí)體;
  3. 實(shí)體解析:通過(guò)自動(dòng)查找和合并重復(fù)實(shí)體(例如,“AMD”和“AdvancedMicroDevices”)來(lái)清理數(shù)據(jù);
  4. 時(shí)間無(wú)效:當(dāng)新信息到達(dá)時(shí),通過(guò)將過(guò)時(shí)的事實(shí)標(biāo)記為“過(guò)期”來(lái)智能識(shí)別和解決矛盾;
  5. 知識(shí)圖譜構(gòu)建:將最終的、干凈的、帶有時(shí)間戳的事實(shí)組裝成一個(gè)連接的圖形結(jié)構(gòu),我們的AI代理可以查詢;
  6. 優(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ǔ)句的類型。

  1. 事實(shí):當(dāng)時(shí)的陳述是真實(shí)的,但以后可能會(huì)改變(例如,“公司上個(gè)季度賺了500萬(wàn)美元”);
  2. 意見:個(gè)人信念或感受,只有在說(shuō)出來(lái)時(shí)才是真實(shí)的(例如,“我認(rèn)為這個(gè)產(chǎn)品會(huì)做得很好”);
  3. 預(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è)核心組成部分:

  1. 主題:事實(shí)是關(guān)于的主要實(shí)體;
  2. 謂語(yǔ):關(guān)系或行動(dòng);
  3. 對(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>

  1. 陳述:原子事實(shí);
  2. 日期:每個(gè)事實(shí)的“時(shí)間”;
  3. 實(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)將:

  1. 使用模糊字符串匹配對(duì)具有相似名稱的實(shí)體進(jìn)行集群;
  2. 為群集中的所有實(shí)體分配一個(gè)規(guī)范ID;
  3. 更新我們的三胞胎以使用這些新的、干凈的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)將:

  1. 為我們所有的新語(yǔ)句生成嵌入,以理解它們的語(yǔ)義含義;
  2. 將新的動(dòng)態(tài)事實(shí)與我們數(shù)據(jù)庫(kù)中的現(xiàn)有事實(shí)進(jìn)行比較;
  3. 使用LLM對(duì)新事實(shí)是否使舊事實(shí)無(wú)效做出最終判斷;
  4. 如果事實(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):

  1. 提取:提取語(yǔ)句、日期和三元組;
  2. 解決方法:清理重復(fù)實(shí)體;
  3. 無(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ò)程:

  1. 查找有關(guān)2016年AMD和數(shù)據(jù)中心的事實(shí);
  2. 查找有關(guān)2017年AMD和數(shù)據(jù)中心的事實(shí);
  3. 比較兩年的結(jié)果;
  4. 綜合最終摘要;

這就是我們的多步驟檢索代理將要做的事情。我們將使用 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ò)程。主要有三種方法:

  1. 黃金答案(黃金標(biāo)準(zhǔn)):您創(chuàng)建一組測(cè)試問(wèn)題,并讓人類專家編寫完美的答案。然后,您將代理的輸出與這些“黃金”答案進(jìn)行比較。這是最準(zhǔn)確的方法,但速度慢且成本高昂。
  2. LLM-as-Judge(可擴(kuò)展的方法):您使用強(qiáng)大的LLM(如GPT-4)充當(dāng)“法官”。它對(duì)代理的答案的正確性和相關(guān)性進(jìn)行評(píng)分。這既快速又便宜,非常適合快速測(cè)試和迭代。
  3. 人工反饋(真實(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ù)

更多精彩內(nèi)容,請(qǐng)關(guān)注人人都是產(chǎn)品經(jīng)理微信公眾號(hào)或下載App
評(píng)論
評(píng)論請(qǐng)登錄
  1. 目前還沒(méi)評(píng)論,等你發(fā)揮!