"""File / document / archive reading tools.""" import json import os import zipfile from pathlib import Path from langchain_core.tools import tool @tool def read_pdf(file_path: str) -> str: """ Extract text content from a PDF file. Args: file_path: Path to the PDF file to read. Returns: The text content of the PDF, with page separators. """ from pypdf import PdfReader try: reader = PdfReader(file_path) text = [] for i, page in enumerate(reader.pages): page_text = page.extract_text() if page_text: text.append(f"--- Page {i+1} ---\n{page_text}") return "\n\n".join(text) if text else "[Empty PDF]" except Exception as e: return f"[read_pdf] failed to read PDF: {e}" @tool def read_docx(file_path: str) -> str: """ Extract text content from a Word document (.docx). Args: file_path: Path to the Word document to read. Returns: The text content of the document. """ from docx import Document try: doc = Document(file_path) text_parts = [] paragraphs = [para.text for para in doc.paragraphs if para.text.strip()] if paragraphs: text_parts.append("\n".join(paragraphs)) for i, table in enumerate(doc.tables): rows = [" | ".join(cell.text.strip() for cell in row.cells) for row in table.rows] rows = [r for r in rows if r.strip()] if rows: text_parts.append(f"--- Table {i+1} ---\n" + "\n".join(rows)) return "\n\n".join(text_parts) if text_parts else "[Empty document]" except Exception as e: return f"[read_docx] failed to read DOCX: {e}" @tool def read_pptx(file_path: str) -> str: """ Extract text content from a PowerPoint presentation (.pptx). Args: file_path: Path to the PowerPoint file to read. Returns: The text content from all slides. """ from pptx import Presentation try: prs = Presentation(file_path) text = [] for slide_num, slide in enumerate(prs.slides, 1): slide_text = [f"--- Slide {slide_num} ---"] for shape in slide.shapes: if hasattr(shape, "text") and shape.text.strip(): slide_text.append(shape.text) if len(slide_text) > 1: text.append("\n".join(slide_text)) return "\n\n".join(text) if text else "[Empty presentation]" except Exception as e: return f"[read_pptx] failed to read PPTX: {e}" @tool def read_text_file(file_path: str) -> str: """ Read content from a plain text file (.txt). Args: file_path: Path to the text file to read. Returns: The content of the text file. """ try: with open(file_path, 'r', encoding='utf-8', errors='replace') as f: return f.read() except Exception as e: return f"[read_text_file] failed: {e}" @tool def read_csv(file_path: str) -> str: """ Read and analyze a CSV file using polars. Args: file_path: Path to the CSV file to read. Returns: Summary of the CSV including schema, row count, and data preview. """ import polars as pl try: df = pl.read_csv(file_path) output = f"CSV File — {len(df)} rows, {len(df.columns)} columns\n" output += f"Columns: {df.columns}\n\n" output += f"Column Statistics:\n{df.describe()}\n\n" output += f"Data (first 20 rows):\n{df.head(20)}" if len(df) <= 50: output += f"\n\nComplete data:\n{df}" return output except Exception as e: return f"[read_csv] failed to read CSV: {e}" @tool def read_excel(file_path: str, sheet_id: int = 0) -> str: """ Read and analyze an Excel file (.xlsx) using polars. Args: file_path: Path to the Excel file to read. sheet_id: The sheet index to read (0-based). Default is 0 (first sheet). Returns: Summary of the Excel sheet including schema, row count, and data preview. """ import polars as pl import openpyxl try: wb = openpyxl.load_workbook(file_path, read_only=True) sheet_names = wb.sheetnames wb.close() except Exception: sheet_names = [] try: df = pl.read_excel(file_path, sheet_id=sheet_id) sheet_label = sheet_names[sheet_id] if sheet_id < len(sheet_names) else str(sheet_id) output = f"Excel File — Available sheets: {sheet_names}\n\n" output += f"Sheet {sheet_id} ('{sheet_label}') — {len(df)} rows, {len(df.columns)} columns\n" output += f"Columns: {df.columns}\n\n" output += f"Column Statistics:\n{df.describe()}\n\n" output += f"Data (first 20 rows):\n{df.head(20)}" if len(df) <= 50: output += f"\n\nComplete data:\n{df}" return output except Exception as e: return f"[read_excel] failed to read Excel: {e}" @tool def read_jsonld(file_path: str) -> str: """ Read and parse a JSON-LD file. Args: file_path: Path to the JSON-LD file to read. Returns: The formatted JSON content. """ try: with open(file_path, 'r') as f: data = json.load(f) return f"JSON-LD Content:\n{json.dumps(data, indent=2)}" except Exception as e: return f"[read_jsonld] failed to read JSON-LD: {e}" @tool def read_pdb(file_path: str) -> str: """ Read and analyze a PDB (Protein Data Bank) file for protein structure analysis. Args: file_path: Path to the PDB file to read. Returns: Analysis of the protein structure including atoms, chains, and coordinates. """ from Bio.PDB import PDBParser import numpy as np try: parser = PDBParser(QUIET=True) structure = parser.get_structure("protein", file_path) info = ["=== PDB Structure Analysis ==="] atoms = list(structure.get_atoms()) info.append(f"Total atoms: {len(atoms)}") for model in structure: info.append(f"\nModel {model.id}:") for chain in model: residues = list(chain.get_residues()) info.append(f" Chain {chain.id}: {len(residues)} residues") if len(atoms) >= 2: info.append("\nFirst atoms (for distance calculations):") for i, atom in enumerate(atoms[:5]): coord = atom.get_coord() info.append( f" Atom {i+1}: {atom.get_name()} at " f"[{coord[0]:.4f}, {coord[1]:.4f}, {coord[2]:.4f}]" ) dist = np.linalg.norm(atoms[0].get_coord() - atoms[1].get_coord()) info.append(f"\nDistance between first two atoms: {dist:.4f} Angstroms") return "\n".join(info) except Exception as e: return f"[read_pdb] failed to read PDB: {e}" @tool def read_python_file(file_path: str) -> str: """ Read a Python source code file. Args: file_path: Path to the Python file to read. Returns: The Python code content. """ try: with open(file_path, 'r') as f: code = f.read() return f"Python Code:\n```python\n{code}\n```" except Exception as e: return f"[read_python_file] failed: {e}" @tool def extract_zip(file_path: str) -> str: """ Extract a ZIP archive and list its contents. Args: file_path: Path to the ZIP file to extract. Returns: List of files extracted from the archive with their paths. """ try: extract_dir = Path(file_path).parent / Path(file_path).stem extract_dir.mkdir(exist_ok=True) with zipfile.ZipFile(file_path, 'r') as zip_ref: zip_ref.extractall(extract_dir) results = [f"ZIP Archive extracted to: {extract_dir}\n\nContents:"] for root, dirs, files in os.walk(extract_dir): for file in files: full_path = os.path.join(root, file) rel_path = os.path.relpath(full_path, extract_dir) file_size = os.path.getsize(full_path) results.append(f" - {rel_path} ({file_size} bytes)") results.append(f"\nUse the appropriate read tool on the extracted files at: {extract_dir}/") return "\n".join(results) except Exception as e: return f"[extract_zip] failed: {e}"