Spaces:
Sleeping
Sleeping
| """ | |
| pdf_parser.py | |
| βββββββββββββ | |
| Utility called by fastapi_main.py before invoking the LangGraph pipeline. | |
| Accepts a list of (filename, raw_bytes) tuples, parses each PDF with LiteParse, | |
| and returns a list of {"filename": str, "content": str} dicts ready to be stored | |
| in the LangGraph state as `pdf_context`. | |
| Each file's extracted text is truncated to MAX_SOURCE_CHARS (same cap used for | |
| web-scraped content) so the synthesis token budget is not blown. | |
| """ | |
| import tempfile | |
| import os | |
| from liteparse import LiteParse | |
| from config import MAX_SOURCE_CHARS | |
| def parse_pdfs(file_bytes_list: list[tuple[str, bytes]]) -> list[dict] | None: | |
| """ | |
| Parse a list of PDF files and return their text content. | |
| Args: | |
| file_bytes_list: List of (filename, raw_bytes) tuples. | |
| Returns: | |
| List of {"filename": str, "content": str} dicts, or None if input is empty. | |
| Files that fail to parse are skipped with a warning printed to stdout. | |
| """ | |
| if not file_bytes_list: | |
| return None | |
| parser = LiteParse() | |
| results = [] | |
| for filename, raw_bytes in file_bytes_list: | |
| # Write to a temp file because LiteParse expects a file path | |
| suffix = os.path.splitext(filename)[-1] or ".pdf" | |
| tmp = None | |
| try: | |
| with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp: | |
| tmp.write(raw_bytes) | |
| tmp_path = tmp.name | |
| print(f" [PDF] Parsing: {filename} ({len(raw_bytes) / 1024:.1f} KB)") | |
| parsed = parser.parse(tmp_path) | |
| # Concatenate all page texts | |
| full_text = "\n\n".join( | |
| page.text for page in parsed.pages if page.text and page.text.strip() | |
| ) | |
| if not full_text.strip(): | |
| print(f" β οΈ [PDF] No text extracted from: {filename} (may be image-only)") | |
| continue | |
| # Truncate to the same per-source cap used for web content | |
| if len(full_text) > MAX_SOURCE_CHARS: | |
| marker = "\n\n[PDF content truncated to fit the source budget.]" | |
| full_text = full_text[: MAX_SOURCE_CHARS - len(marker)].rsplit(" ", 1)[0] + marker | |
| results.append({"filename": filename, "content": full_text}) | |
| print(f" β [PDF] Parsed: {filename} ({len(full_text):,} chars extracted)") | |
| except Exception as exc: | |
| print(f" β οΈ [PDF] Failed to parse '{filename}': {type(exc).__name__}: {exc}") | |
| finally: | |
| # Always clean up the temp file | |
| if tmp is not None and os.path.exists(tmp.name): | |
| try: | |
| os.unlink(tmp.name) | |
| except OSError: | |
| pass | |
| return results if results else None | |