# -*- coding: utf-8 -*- """ 文件内容读取工具模块 提供统一的文件内容提取能力,自动识别文件格式,支持: - PDF(复用 tools/pdf_tools 的 PyPDF2 提取) - DOCX(Word 文档) - XLSX(Excel 表格) - PPTX(PowerPoint 演示文稿) - TXT / MD / CSV / JSON 等纯文本文件 所有工具函数供 DeepAgent 调用,返回结构化 JSON 文本。 """ import json import os import re # ── 最大提取字符数 ── _MAX_CONTENT_CHARS = 15000 async def read_file_content(file_path: str) -> str: """读取用户上传的文件内容。当用户上传了附件,你需要查看文件中的文字内容时调用此工具。 支持的文件格式: - PDF 文档(.pdf) - Word 文档(.docx) - Excel 表格(.xlsx) - PowerPoint 演示文稿(.pptx) - 纯文本(.txt / .md / .csv / .json) 典型调用场景: 1. 用户上传了一个 Word 报告,需要你阅读和分析内容 2. 用户上传了 Excel 数据表,需要你提取和理解数据 3. 用户上传了 PDF 文档,需要你从中获取信息 Args: file_path: 文件的完整路径(从用户消息中的「文件临时路径」获取) Returns: JSON 格式,包含 file_path、format(文件格式)、content(提取的文本)、 content_length(字符数)。文本最多保留 15000 字符,超出部分截断。 """ if not file_path or not file_path.strip(): return json.dumps({"error": "文件路径不能为空"}, ensure_ascii=False) fp = file_path.strip() if not os.path.exists(fp): return json.dumps( {"error": f"文件不存在: {fp}", "file_path": fp}, ensure_ascii=False, ) ext = os.path.splitext(fp)[1].lower() try: if ext == ".pdf": text = _read_pdf(fp) fmt = "pdf" elif ext == ".docx": text = _read_docx(fp) fmt = "docx" elif ext in (".xlsx", ".xlsm"): text = _read_xlsx(fp) fmt = "xlsx" elif ext == ".pptx": text = _read_pptx(fp) fmt = "pptx" elif ext in (".txt", ".md", ".csv", ".json", ".xml", ".html", ".htm", ".log", ".yaml", ".yml", ".py", ".js", ".ts", ".sql", ".cfg", ".ini"): text = _read_text(fp) fmt = ext.lstrip(".") else: return json.dumps( { "error": f"不支持的文件格式「{ext}」。支持:pdf / docx / xlsx / pptx / txt / md / csv / json", "file_path": fp, "format": ext, }, ensure_ascii=False, ) except Exception as e: return json.dumps( {"error": f"文件读取失败: {str(e)}", "file_path": fp, "format": ext}, ensure_ascii=False, ) # ── 截断 ── content_length = len(text) if content_length > _MAX_CONTENT_CHARS: text = text[:_MAX_CONTENT_CHARS] + f"\n…(内容已截断,原始 {content_length} 字符)" return json.dumps( { "file_path": fp, "format": fmt, "content": text, "content_length": min(content_length, _MAX_CONTENT_CHARS), }, ensure_ascii=False, ) # ═══════════════════════════════════════════════════════════════ # 各格式解析器(内部函数,不直接暴露给 Agent) # ═══════════════════════════════════════════════════════════════ def _read_pdf(file_path: str) -> str: """PDF 文本提取(复用 tools/pdf_tools 的 PyPDF2 提取器)。""" from tools.pdf_tools import _extract_with_pypdf text = _extract_with_pypdf(file_path) if text: return text.strip() return "(PDF 文件无可提取的文字内容,可能是扫描件或图片型 PDF)" def _read_docx(file_path: str) -> str: """Word 文档 (.docx) 文本提取。 提取内容: - 正文段落文本 - 表格中的文字(标为 [表格] 前缀) """ from docx import Document doc = Document(file_path) parts = [] for element in doc.element.body: tag = element.tag.split("}")[-1] if "}" in element.tag else element.tag if tag == "p": # 段落 ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"} texts = element.findall(".//w:t", ns) line = "".join(t.text or "" for t in texts).strip() if line: parts.append(line) elif tag == "tbl": # 表格 rows = [] for row in element.findall(".//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tr"): cells = [] for cell in row.findall(".//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tc"): texts = cell.findall(".//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}t") cell_text = " ".join(t.text or "" for t in texts).strip() cells.append(cell_text) rows.append(" | ".join(cells)) if rows: parts.append("[表格] " + "\n[表格] ".join(rows)) result = "\n".join(parts).strip() return result if result else "(Word 文档中未找到文字内容)" def _read_xlsx(file_path: str) -> str: """Excel 表格 (.xlsx) 文本提取。 每个 Sheet 标记为 [Sheet: xxx],每行用 | 分隔单元格。 """ from openpyxl import load_workbook wb = load_workbook(file_path, read_only=True, data_only=True) parts = [] for sheet_name in wb.sheetnames: ws = wb[sheet_name] parts.append(f"[Sheet: {sheet_name}]") row_count = 0 for row in ws.iter_rows(max_row=200, values_only=True): row_vals = [str(v) if v is not None else "" for v in row] # 跳过完全空行 if any(c.strip() for c in row_vals): parts.append(" | ".join(row_vals)) row_count += 1 if row_count == 0: parts.append("(空表)") wb.close() result = "\n".join(parts).strip() return result if result else "(Excel 文件中未找到数据)" def _read_pptx(file_path: str) -> str: """PowerPoint 演示文稿 (.pptx) 文本提取。 每张幻灯片标记为 [幻灯片 N],提取所有文本框和占位符中的文字。 """ from pptx import Presentation prs = Presentation(file_path) parts = [] for i, slide in enumerate(prs.slides, 1): parts.append(f"[幻灯片 {i}]") for shape in slide.shapes: if shape.has_text_frame: for paragraph in shape.text_frame.paragraphs: line = paragraph.text.strip() if line: parts.append(line) if shape.has_table: table = shape.table for row in table.rows: cells = [cell.text.strip() for cell in row.cells] parts.append(" | ".join(cells)) result = "\n".join(parts).strip() return result if result else "(PPT 中未找到文字内容)" def _read_text(file_path: str) -> str: """纯文本文件读取(UTF-8 / GBK 自动检测)。""" for encoding in ("utf-8", "gbk", "gb2312", "latin-1"): try: with open(file_path, "r", encoding=encoding) as f: return f.read().strip() except (UnicodeDecodeError, UnicodeError): continue return "(无法解码文件内容,可能是二进制文件)"