ตัวแทนถามตอบจากเอกสารหลายฉบับ
จัดทำดัชนีคลังเอกสารและตอบคำถามจากเอกสารทั้งหมด
ตัวแทนถามตอบจากเอกสารหลายฉบับ เป็นบทเรียน AI Agents ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Agents และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน
ภาพรวมการถามตอบจากหลายเอกสาร
เอเจนต์ถามตอบจากหลายเอกสารตอบคำถามด้วยการค้นคืนเนื้อหาที่เกี่ยวข้องจากชุดเอกสารจำนวน N ฉบับ สังเคราะห์คำตอบ และระบุแหล่งที่มาของข้อกล่าวอ้างแต่ละรายการ
ต่างจากการถามตอบจากเอกสารเดียว เอเจนต์หลายเอกสารต้องจัดการข้อมูลที่ขัดแย้งกันระหว่างแหล่งที่มา และให้เหตุผลว่าเอกสารใดเกี่ยวข้องกับคำถามมากที่สุด
การสร้างดัชนีเอกสารหลายฉบับ
ก่อนตอบคำถามใด ๆ เอกสารทั้งหมดต้องถูกสร้างดัชนี ซึ่งประกอบด้วยการแยกวิเคราะห์ การแบ่งเป็นชิ้น การทำเอ็มเบดดิง และการจัดเก็บในฐานข้อมูลเวกเตอร์ แต่ละชิ้นจะถูกจัดเก็บพร้อมเมทาดาทาที่เชื่อมโยงกลับไปยังเอกสารต้นทาง
import chromadb
from chromadb.utils import embedding_functions
import os
client = chromadb.PersistentClient(path='./doc_index')
ef = embedding_functions.OpenAIEmbeddingFunction(
api_key=os.getenv('OPENAI_API_KEY'),
model_name='text-embedding-3-small'
)
collection = client.get_or_create_collection('documents', embedding_function=ef)
def index_document(doc_id, doc_path, doc_title):
# Parse and chunk
chunks = pdf_to_chunks(doc_path, chunk_size=800, overlap=150)
for i, chunk in enumerate(chunks):
chunk_id = f'{doc_id}_chunk_{i}'
collection.add(
ids=[chunk_id],
documents=[chunk['text']],
metadatas=[{
'doc_id': doc_id,
'title': doc_title,
'page': chunk['page'],
'source_file': doc_path
}]
)
print(f'Indexed {len(chunks)} chunks from: {doc_title}')การค้นคืนชิ้นส่วนที่เกี่ยวข้อง
เมื่อมีคำถาม ให้ค้นฐานข้อมูลเวกเตอร์เพื่อหาชิ้นส่วนที่มีความคล้ายคลึงเชิงความหมายมากที่สุดจากเอกสารที่สร้างดัชนีไว้ทั้งหมด พารามิเตอร์ n_results จะควบคุมจำนวนชิ้นส่วนที่ค้นคืน
def retrieve_relevant_chunks(question, n_results=8):
results = collection.query(
query_texts=[question],
n_results=n_results,
include=['documents', 'metadatas', 'distances']
)
chunks = []
for i in range(len(results['documents'][0])):
chunks.append({
'text': results['documents'][0][i],
'metadata': results['metadatas'][0][i],
'distance': results['distances'][0][i],
'relevance': 1 - results['distances'][0][i] # cosine similarity proxy
})
# Sort by relevance
chunks.sort(key=lambda x: x['relevance'], reverse=True)
return chunksการระบุแหล่งที่มาในพรอมต์
เมื่อส่งชิ้นส่วนที่ค้นคืนให้ LLM ให้กำกับแต่ละชิ้นด้วยแหล่งที่มาของเอกสาร จากนั้น LLM จะสามารถอ้างอิงแหล่งที่มาด้วยหมายเลขในคำตอบ
def format_chunks_for_prompt(chunks, max_chars=4000):
sections = []
used_chars = 0
for i, chunk in enumerate(chunks, 1):
meta = chunk['metadata']
header = f"[Source {i}: {meta['title']}, page {meta.get('page', '?')}]"
content = chunk['text'][:600]
entry = f'{header}\n{content}'
if used_chars + len(entry) > max_chars:
break
sections.append(entry)
used_chars += len(entry)
return '\n\n'.join(sections)
QA_PROMPT = '''Answer the question based on the provided document excerpts.
Cite sources as [Source N]. If sources conflict, mention both views.
{context}
Question: {question}
Answer:'''
def answer_question(question):
chunks = retrieve_relevant_chunks(question, n_results=6)
context = format_chunks_for_prompt(chunks)
return llm_call(QA_PROMPT.format(context=context, question=question))การให้เหตุผลข้ามเอกสาร
คำถามบางข้อจำเป็นต้องสังเคราะห์ข้อมูลจากหลายเอกสาร ไม่ใช่เพียงค้นหาชิ้นส่วนที่ตรงกันชิ้นเดียว ตัวอย่างเช่น "สัญญาฉบับใดจากสามฉบับมีข้อกำหนดเรื่องค่าปรับต่ำที่สุด"
ใช้แนวทางสองขั้นตอน ได้แก่ ค้นคืนชิ้นส่วนที่เกี่ยวข้องจากแต่ละเอกสาร แล้วขอให้ LLM เปรียบเทียบและสังเคราะห์ข้อมูลข้ามเอกสารเหล่านั้น
def cross_document_compare(question, doc_ids):
# Retrieve best chunks per document
per_doc_chunks = {}
for doc_id in doc_ids:
results = collection.query(
query_texts=[question],
n_results=3,
where={'doc_id': {'$eq': doc_id}} # filter by document
)
if results['documents'][0]:
per_doc_chunks[doc_id] = results['documents'][0]
# Format with document labels
context_parts = []
for doc_id, texts in per_doc_chunks.items():
doc_label = f'Document {doc_id}'
combined = ' '.join(texts[:2])[:600]
context_parts.append(f'== {doc_label} ==\n{combined}')
comparison_context = '\n\n'.join(context_parts)
return llm_call(f'Compare these documents to answer: {question}\n\n{comparison_context}')การจัดการข้อมูลที่ขัดแย้งกัน
เอกสารต่างฉบับอาจระบุข้อเท็จจริงที่ขัดแย้งกัน เช่น สัญญาฉบับหนึ่งระบุว่าต้องชำระเงินภายใน 30 วัน แต่อีกฉบับระบุว่า 60 วัน เอเจนต์ต้องตรวจจับและแสดงความขัดแย้งเหล่านี้ แทนที่จะเลือกข้อมูลใดข้อมูลหนึ่งโดยไม่แจ้งให้ทราบ
CONFLICT_PROMPT = '''You are analyzing multiple document sources.
Some may contain conflicting information.
For each factual claim you make:
1. Cite the source document
2. If another source contradicts it, explicitly note the conflict
3. Indicate which source you believe is more authoritative, if possible
Document excerpts:
{context}
Question: {question}
Answer (with conflict notes where applicable):'''
def answer_with_conflict_detection(question):
chunks = retrieve_relevant_chunks(question, n_results=8)
context = format_chunks_for_prompt(chunks)
return llm_call(CONFLICT_PROMPT.format(
context=context, question=question
))การกรองตามค่าเกณฑ์ความเกี่ยวข้อง
ชิ้นส่วนที่ค้นคืนมาไม่ได้เกี่ยวข้องอย่างแท้จริงทั้งหมด ความคล้ายคลึงของเวกเตอร์มีความสมดุลระหว่างการเรียกคืนกับความแม่นยำ ให้กำหนดค่าเกณฑ์ความเกี่ยวข้องขั้นต่ำเพื่อไม่รวมชิ้นส่วนที่ตรงกันอย่างอ่อน ซึ่งอาจทำให้ LLM เข้าใจผิด
MIN_RELEVANCE = 0.72 # cosine similarity threshold
def retrieve_above_threshold(question, n_results=10, threshold=MIN_RELEVANCE):
chunks = retrieve_relevant_chunks(question, n_results=n_results)
relevant = [c for c in chunks if c['relevance'] >= threshold]
print(f'Retrieved: {len(chunks)}, Above threshold: {len(relevant)}')
if not relevant:
# Fallback: use top 3 even if below threshold
return chunks[:3]
return relevant
def answer_with_threshold(question):
chunks = retrieve_above_threshold(question)
if not chunks:
return 'I could not find relevant information in the indexed documents.'
context = format_chunks_for_prompt(chunks)
return llm_call(QA_PROMPT.format(context=context, question=question))การสร้างแหล่งอ้างอิง
หลังจากสร้างคำตอบแล้ว ให้แยกว่ามีการอ้างถึงเอกสารต้นทางใดบ้าง และส่งคืนเป็นรายการแบบมีโครงสร้าง วิธีนี้ช่วยให้ผู้ใช้ค้นหาเอกสารต้นฉบับเพื่อตรวจสอบได้
import re
def extract_citations(answer_text, chunks):
# Find all [Source N] references in the answer
cited_nums = set(int(m) for m in re.findall(r'\[Source (\d+)\]', answer_text))
citations = []
for num in sorted(cited_nums):
idx = num - 1
if idx < len(chunks):
meta = chunks[idx]['metadata']
citations.append({
'source_num': num,
'title': meta.get('title', 'Unknown'),
'page': meta.get('page', 'N/A'),
'file': meta.get('source_file', '')
})
return citations
def answer_with_citations(question):
chunks = retrieve_above_threshold(question)
context = format_chunks_for_prompt(chunks)
answer = llm_call(QA_PROMPT.format(context=context, question=question))
citations = extract_citations(answer, chunks)
return {'answer': answer, 'citations': citations}การจัดอันดับใหม่ด้วยตัวเข้ารหัสไขว้
การค้นคืนเวกเตอร์เบื้องต้นใช้ตัวเข้ารหัสแบบคู่ (รวดเร็วและเป็นการประมาณ) ส่วนตัวเข้ารหัสไขว้จะจัดอันดับผลลัพธ์ชั้นนำใหม่ด้วยการให้คะแนนคู่ (คำค้น ชิ้นส่วน) แต่ละคู่พร้อมกัน วิธีนี้แม่นยำกว่าแต่ช้ากว่า แนวทางสองขั้นตอนนี้ช่วยปรับปรุงคุณภาพคำตอบสุดท้าย
# pip install sentence-transformers
from sentence_transformers import CrossEncoder
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
def rerank_chunks(question, chunks, top_k=4):
# Score each chunk against the question
pairs = [(question, c['text']) for c in chunks]
scores = reranker.predict(pairs)
# Attach scores and re-sort
scored_chunks = list(zip(scores, chunks))
scored_chunks.sort(key=lambda x: x[0], reverse=True)
top_chunks = [chunk for _, chunk in scored_chunks[:top_k]]
print(f'Re-ranked {len(chunks)} chunks -> kept top {top_k}')
return top_chunks
def answer_with_reranking(question):
# Retrieve more initially
initial_chunks = retrieve_relevant_chunks(question, n_results=12)
# Re-rank for precision
top_chunks = rerank_chunks(question, initial_chunks, top_k=4)
context = format_chunks_for_prompt(top_chunks)
return llm_call(QA_PROMPT.format(context=context, question=question))การกรองเมทาดาทาระดับเอกสาร
เมื่อผู้ใช้ระบุเอกสารหรือช่วงวันที่เฉพาะ ให้กรองในระดับเมทาดาทาก่อนค้นหาเอ็มเบดดิง วิธีนี้ป้องกันไม่ให้เอกสารที่ไม่เกี่ยวข้องเข้ามาปะปนในผลลัพธ์
def retrieve_filtered(question, filters=None, n_results=8):
query_kwargs = {
'query_texts': [question],
'n_results': n_results,
'include': ['documents', 'metadatas', 'distances']
}
# ChromaDB metadata filters
# Example: {'doc_id': 'contract_2024', 'year': {'$gte': 2023}}
if filters:
query_kwargs['where'] = filters
results = collection.query(**query_kwargs)
return [
{'text': t, 'metadata': m, 'relevance': 1 - d}
for t, m, d in zip(
results['documents'][0],
results['metadatas'][0],
results['distances'][0]
)
]
# Example usage
chunks = retrieve_filtered(
'What are the payment terms?',
filters={'doc_id': {'$in': ['contract_a', 'contract_b']}}
)การอัปเดตดัชนีด้วยเอกสารใหม่
ชุดเอกสารเปลี่ยนแปลงไปตามเวลา มีการเพิ่มไฟล์ใหม่และปรับปรุงไฟล์เก่า กระบวนการสร้างดัชนีต้องรองรับการอัปเดตแบบเพิ่มทีละส่วน ได้แก่ เพิ่มเอกสารใหม่ สร้างดัชนีเอกสารที่อัปเดตอีกครั้ง และนำเอกสารที่ถูกลบออก
import os
import hashlib
# Track indexed documents by file hash
index_registry = {} # {filepath: {hash, doc_id, indexed_at}}
def file_hash(filepath):
with open(filepath, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()
def index_if_new_or_changed(filepath, title):
fhash = file_hash(filepath)
existing = index_registry.get(filepath)
if existing and existing['hash'] == fhash:
print(f'Skipping unchanged: {title}')
return existing['doc_id']
if existing:
# Remove old chunks from vector store
collection.delete(where={'doc_id': {'': existing['doc_id']}})
print(f'Re-indexing updated: {title}')
else:
print(f'Indexing new: {title}')
doc_id = hashlib.md5(filepath.encode()).hexdigest()[:8]
index_document(doc_id, filepath, title)
index_registry[filepath] = {'hash': fhash, 'doc_id': doc_id}
return doc_id
def sync_document_directory(directory):
pdf_files = [f for f in os.listdir(directory) if f.endswith('.pdf')]
for fname in pdf_files:
fpath = os.path.join(directory, fname)
title = fname.replace('.pdf', '').replace('_', ' ').title()
index_if_new_or_changed(fpath, title)
print(f'Sync complete: {len(pdf_files)} files processed')ตรวจสอบความรู้
ในระบบถามตอบจากหลายเอกสารที่ใช้การสร้างเนื้อหาเสริมด้วยการค้นคืน ค่าเกณฑ์ความเกี่ยวข้องมีหน้าที่ป้องกันอะไร
ทบทวน: เอเจนต์ถามตอบจากหลายเอกสาร
การถามตอบจากหลายเอกสาร: สร้างดัชนีเอกสารทั้งหมด (แยกวิเคราะห์ → แบ่งเป็นชิ้น → ทำเอ็มเบดดิง → จัดเก็บพร้อมเมทาดาทา) → ค้นคืนชิ้นส่วนที่เกี่ยวข้องจากเอกสารทั้งหมด → จัดรูปแบบพร้อมป้ายแหล่งที่มา → สังเคราะห์คำตอบพร้อมแหล่งอ้างอิง
เทคนิคขั้นสูง ได้แก่ การเปรียบเทียบข้ามเอกสารสำหรับคำถามเชิงเปรียบเทียบ การใช้พรอมต์เพื่อตรวจจับความขัดแย้ง การกรองตามค่าเกณฑ์ความเกี่ยวข้อง การจัดอันดับใหม่ด้วยตัวเข้ารหัสไขว้เพื่อเพิ่มความแม่นยำ และการกรองเมทาดาทาเพื่อจำกัดขอบเขตคำค้นให้เฉพาะเอกสารหรือช่วงวันที่ที่กำหนด
คำถามที่พบบ่อย
บทเรียน “ตัวแทนถามตอบจากเอกสารหลายฉบับ” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ตัวแทนถามตอบจากเอกสารหลายฉบับ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Agents ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ตัวแทนถามตอบจากเอกสารหลายฉบับ”
จัดทำดัชนีคลังเอกสารและตอบคำถามจากเอกสารทั้งหมด คุณปฏิบัติ AI Agents ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Agents หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Agents บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “ตัวแทนถามตอบจากเอกสารหลายฉบับ” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน AI Agents นี้ได้ไหม
ได้ บทเรียน AI Agents ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การแยกวิเคราะห์ PDF ด้วย PyMuPDF และ pdfplumber
- OCR สำหรับเอกสารสแกน
- ตัวแทนถามตอบจากเอกสารหลายฉบับ
- การจำแนกและกำหนดเส้นทางเอกสาร