AgentraXhelpAgent / ingestion.py
Shurem's picture
update help agent instructions
ab4952c
Raw
History Blame Contribute Delete
4.85 kB
from pathlib import Path
from typing import Any
from dotenv import load_dotenv
load_dotenv()
from chroma import get_collection # noqa: E402 β€” after load_dotenv
DOCUMENTS_DIR = Path(__file__).parent / "documents"
def parse_document(file_path: str | Path) -> list[dict[str, Any]]:
"""Return list of {text, page_number} dicts from a .pdf, .docx, or .md file."""
file_path = Path(file_path)
suffix = file_path.suffix.lower()
if suffix == ".pdf":
from pypdf import PdfReader
reader = PdfReader(str(file_path))
pages = []
for i, page in enumerate(reader.pages):
text = page.extract_text() or ""
if text.strip():
pages.append({"text": text, "page_number": i + 1})
return pages
if suffix == ".docx":
from docx import Document
doc = Document(str(file_path))
full_text = "\n".join(p.text for p in doc.paragraphs if p.text.strip())
return [{"text": full_text, "page_number": None}]
if suffix == ".md":
text = file_path.read_text(encoding="utf-8")
return [{"text": text, "page_number": None}]
raise ValueError(f"Unsupported file type: {suffix!r}. Supported: .pdf, .docx, .md")
def chunk_text(
text: str,
chunk_size: int = 500,
overlap: int = 50,
) -> list[str]:
"""Split text into overlapping word-based chunks."""
words = text.split()
if not words:
return []
chunks: list[str] = []
start = 0
while start < len(words):
end = min(start + chunk_size, len(words))
chunks.append(" ".join(words[start:end]))
if end == len(words):
break
start += chunk_size - overlap
return chunks
def embed_and_store(
chunks: list[str],
metadata: dict[str, Any],
) -> None:
"""Embed chunks and upsert them into ChromaDB with per-chunk metadata."""
if not chunks:
return
collection = get_collection()
source_file = metadata.get("source_file", "unknown")
page_number = metadata.get("page_number") # may be None for docx
ids = [
f"{source_file}__p{page_number}__c{i}" if page_number is not None else f"{source_file}__c{i}"
for i in range(len(chunks))
]
chunk_metadata = [
{
"source_file": source_file,
"chunk_index": i,
**({"page_number": page_number} if page_number is not None else {}),
}
for i in range(len(chunks))
]
collection.upsert(documents=chunks, metadatas=chunk_metadata, ids=ids)
def ingest_all_documents(extra_dirs: list[Path] | None = None) -> dict[str, Any]:
"""Scan documents/ folder (and any extra_dirs) for supported files and index them."""
supported = {".pdf", ".docx", ".md"}
search_dirs = [DOCUMENTS_DIR] + (extra_dirs or [])
files: list[Path] = []
for d in search_dirs:
if d.exists() and d.is_dir():
files.extend(f for f in d.iterdir() if f.suffix.lower() in supported)
if not files:
print("No documents found in", DOCUMENTS_DIR)
return {"ingested": 0, "files": []}
results: list[str] = []
total_chunks = 0
for file_path in files:
print(f"Ingesting: {file_path.name}")
try:
pages = parse_document(file_path)
file_chunks = 0
for page in pages:
chunks = chunk_text(page["text"])
embed_and_store(
chunks,
metadata={
"source_file": file_path.name,
"page_number": page["page_number"],
},
)
file_chunks += len(chunks)
total_chunks += file_chunks
results.append(file_path.name)
print(f" -> {file_chunks} chunks stored")
except Exception as e:
print(f" -> ERROR: {e}")
print(f"\nDone. {len(results)} file(s), {total_chunks} total chunks.")
return {"ingested": len(results), "total_chunks": total_chunks, "files": results}
def ingest_scraped_content(data: dict) -> None:
"""Chunk and upsert scraped website content into ChromaDB."""
parts = []
if data.get("title"):
parts.append(data["title"])
if data.get("description"):
parts.append(data["description"])
if data.get("headings"):
parts.append("\n".join(data["headings"]))
if data.get("body_text"):
parts.append(data["body_text"])
full_text = "\n\n".join(parts)
chunks = chunk_text(full_text)
if not chunks:
return
url = data.get("url", "scraped")
scraped_at = data.get("scraped_at", "")
embed_and_store(
chunks,
metadata={"source_file": url, "scraped_at": scraped_at},
)
if __name__ == "__main__":
ingest_all_documents()