file_reader_tools.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. # -*- coding: utf-8 -*-
  2. """
  3. 文件内容读取工具模块
  4. 提供统一的文件内容提取能力,自动识别文件格式,支持:
  5. - PDF(复用 tools/pdf_tools 的 PyPDF2 提取)
  6. - DOCX(Word 文档)
  7. - XLSX(Excel 表格)
  8. - PPTX(PowerPoint 演示文稿)
  9. - TXT / MD / CSV / JSON 等纯文本文件
  10. 所有工具函数供 DeepAgent 调用,返回结构化 JSON 文本。
  11. """
  12. import json
  13. import os
  14. import re
  15. # ── 最大提取字符数 ──
  16. _MAX_CONTENT_CHARS = 15000
  17. async def read_file_content(file_path: str) -> str:
  18. """读取用户上传的文件内容。当用户上传了附件,你需要查看文件中的文字内容时调用此工具。
  19. 支持的文件格式:
  20. - PDF 文档(.pdf)
  21. - Word 文档(.docx)
  22. - Excel 表格(.xlsx)
  23. - PowerPoint 演示文稿(.pptx)
  24. - 纯文本(.txt / .md / .csv / .json)
  25. 典型调用场景:
  26. 1. 用户上传了一个 Word 报告,需要你阅读和分析内容
  27. 2. 用户上传了 Excel 数据表,需要你提取和理解数据
  28. 3. 用户上传了 PDF 文档,需要你从中获取信息
  29. Args:
  30. file_path: 文件的完整路径(从用户消息中的「文件临时路径」获取)
  31. Returns:
  32. JSON 格式,包含 file_path、format(文件格式)、content(提取的文本)、
  33. content_length(字符数)。文本最多保留 15000 字符,超出部分截断。
  34. """
  35. if not file_path or not file_path.strip():
  36. return json.dumps({"error": "文件路径不能为空"}, ensure_ascii=False)
  37. fp = file_path.strip()
  38. if not os.path.exists(fp):
  39. return json.dumps(
  40. {"error": f"文件不存在: {fp}", "file_path": fp},
  41. ensure_ascii=False,
  42. )
  43. ext = os.path.splitext(fp)[1].lower()
  44. try:
  45. if ext == ".pdf":
  46. text = _read_pdf(fp)
  47. fmt = "pdf"
  48. elif ext == ".docx":
  49. text = _read_docx(fp)
  50. fmt = "docx"
  51. elif ext in (".xlsx", ".xlsm"):
  52. text = _read_xlsx(fp)
  53. fmt = "xlsx"
  54. elif ext == ".pptx":
  55. text = _read_pptx(fp)
  56. fmt = "pptx"
  57. elif ext in (".txt", ".md", ".csv", ".json", ".xml", ".html", ".htm", ".log", ".yaml", ".yml", ".py", ".js", ".ts", ".sql", ".cfg", ".ini"):
  58. text = _read_text(fp)
  59. fmt = ext.lstrip(".")
  60. else:
  61. return json.dumps(
  62. {
  63. "error": f"不支持的文件格式「{ext}」。支持:pdf / docx / xlsx / pptx / txt / md / csv / json",
  64. "file_path": fp,
  65. "format": ext,
  66. },
  67. ensure_ascii=False,
  68. )
  69. except Exception as e:
  70. return json.dumps(
  71. {"error": f"文件读取失败: {str(e)}", "file_path": fp, "format": ext},
  72. ensure_ascii=False,
  73. )
  74. # ── 截断 ──
  75. content_length = len(text)
  76. if content_length > _MAX_CONTENT_CHARS:
  77. text = text[:_MAX_CONTENT_CHARS] + f"\n…(内容已截断,原始 {content_length} 字符)"
  78. return json.dumps(
  79. {
  80. "file_path": fp,
  81. "format": fmt,
  82. "content": text,
  83. "content_length": min(content_length, _MAX_CONTENT_CHARS),
  84. },
  85. ensure_ascii=False,
  86. )
  87. # ═══════════════════════════════════════════════════════════════
  88. # 各格式解析器(内部函数,不直接暴露给 Agent)
  89. # ═══════════════════════════════════════════════════════════════
  90. def _read_pdf(file_path: str) -> str:
  91. """PDF 文本提取(复用 tools/pdf_tools 的 PyPDF2 提取器)。"""
  92. from tools.pdf_tools import _extract_with_pypdf
  93. text = _extract_with_pypdf(file_path)
  94. if text:
  95. return text.strip()
  96. return "(PDF 文件无可提取的文字内容,可能是扫描件或图片型 PDF)"
  97. def _read_docx(file_path: str) -> str:
  98. """Word 文档 (.docx) 文本提取。
  99. 提取内容:
  100. - 正文段落文本
  101. - 表格中的文字(标为 [表格] 前缀)
  102. """
  103. from docx import Document
  104. doc = Document(file_path)
  105. parts = []
  106. for element in doc.element.body:
  107. tag = element.tag.split("}")[-1] if "}" in element.tag else element.tag
  108. if tag == "p":
  109. # 段落
  110. ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
  111. texts = element.findall(".//w:t", ns)
  112. line = "".join(t.text or "" for t in texts).strip()
  113. if line:
  114. parts.append(line)
  115. elif tag == "tbl":
  116. # 表格
  117. rows = []
  118. for row in element.findall(".//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tr"):
  119. cells = []
  120. for cell in row.findall(".//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tc"):
  121. texts = cell.findall(".//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}t")
  122. cell_text = " ".join(t.text or "" for t in texts).strip()
  123. cells.append(cell_text)
  124. rows.append(" | ".join(cells))
  125. if rows:
  126. parts.append("[表格] " + "\n[表格] ".join(rows))
  127. result = "\n".join(parts).strip()
  128. return result if result else "(Word 文档中未找到文字内容)"
  129. def _read_xlsx(file_path: str) -> str:
  130. """Excel 表格 (.xlsx) 文本提取。
  131. 每个 Sheet 标记为 [Sheet: xxx],每行用 | 分隔单元格。
  132. """
  133. from openpyxl import load_workbook
  134. wb = load_workbook(file_path, read_only=True, data_only=True)
  135. parts = []
  136. for sheet_name in wb.sheetnames:
  137. ws = wb[sheet_name]
  138. parts.append(f"[Sheet: {sheet_name}]")
  139. row_count = 0
  140. for row in ws.iter_rows(max_row=200, values_only=True):
  141. row_vals = [str(v) if v is not None else "" for v in row]
  142. # 跳过完全空行
  143. if any(c.strip() for c in row_vals):
  144. parts.append(" | ".join(row_vals))
  145. row_count += 1
  146. if row_count == 0:
  147. parts.append("(空表)")
  148. wb.close()
  149. result = "\n".join(parts).strip()
  150. return result if result else "(Excel 文件中未找到数据)"
  151. def _read_pptx(file_path: str) -> str:
  152. """PowerPoint 演示文稿 (.pptx) 文本提取。
  153. 每张幻灯片标记为 [幻灯片 N],提取所有文本框和占位符中的文字。
  154. """
  155. from pptx import Presentation
  156. prs = Presentation(file_path)
  157. parts = []
  158. for i, slide in enumerate(prs.slides, 1):
  159. parts.append(f"[幻灯片 {i}]")
  160. for shape in slide.shapes:
  161. if shape.has_text_frame:
  162. for paragraph in shape.text_frame.paragraphs:
  163. line = paragraph.text.strip()
  164. if line:
  165. parts.append(line)
  166. if shape.has_table:
  167. table = shape.table
  168. for row in table.rows:
  169. cells = [cell.text.strip() for cell in row.cells]
  170. parts.append(" | ".join(cells))
  171. result = "\n".join(parts).strip()
  172. return result if result else "(PPT 中未找到文字内容)"
  173. def _read_text(file_path: str) -> str:
  174. """纯文本文件读取(UTF-8 / GBK 自动检测)。"""
  175. for encoding in ("utf-8", "gbk", "gb2312", "latin-1"):
  176. try:
  177. with open(file_path, "r", encoding=encoding) as f:
  178. return f.read().strip()
  179. except (UnicodeDecodeError, UnicodeError):
  180. continue
  181. return "(无法解码文件内容,可能是二进制文件)"