Tables in an uploaded .docx reach the model as a flat list of cells
What happens
Every table in an uploaded .docx reaches the model as a flat list of cell values. wordDocToText in packages/api/src/files/documents/crud.ts calls mammoth.extractRawText, and mammoth's raw-text writer has no table support: it emits each cell as its own paragraph and drops the row and column structure entirely.
Steps to reproduce
Build a three-column table in a .docx and run it through the same call the parser makes. Against eaa91b3:
const mammoth = require('mammoth');
const raw = await mammoth.extractRawText({ buffer: fs.readFileSync('table.docx') });Document:
| Region | Product | Units |
|---|---|---|
| EU | Cable | 12 |
| US | Hub | 7 |
What extractRawText returns, verbatim:
Intro paragraph.
Region
Product
Units
EU
Cable
12
US
Hub
7
Closing paragraph.12 is no longer connected to EU or to Units, and US sits between the two rows as if it were another value in the same list. Nothing in the text says a table was ever there.
Expected behavior
The cells stay associated with their row and their column, the way excelSheetToText already keeps a spreadsheet's rows together through utils.sheet_to_csv. Something like:
Region, Product, Units
EU, Cable, 12
US, Hub, 7Why it matters
A table is usually the densest part of a Word document — invoice line items, a spec sheet, a comparison, a schedule. Asking a model "what does the EU pay per cable" over the text above cannot be answered correctly except by luck, because the association it needs was destroyed before the model saw it.
The information is not lost upstream: mammoth.convertToHtml on the same file returns the full structure.
<p>Intro paragraph.</p><table><tr><td><p>Region</p></td><td><p>Product</p></td><td><p>Units</p></td></tr>
<tr><td><p>EU</p></td><td><p>Cable</p></td><td><p>12</p></td></tr>…</table><p>Closing paragraph.</p>Two things I checked so they do not cost you time:
mammoth.convertToMarkdowndoes not help — its markdown writer flattens tables the same way (measured on mammoth 1.11.0, the pinned version).- so the fix means converting mammoth's HTML rather than its raw text, and that is an architecture call rather than a one-liner:
packages/apihas no HTML-to-text converter today, thoughjsdomandsanitize-htmlare already peer dependencies andhtml.tsowns the HTML side of this pipeline.
I have this reproduced with a regression test and I am happy to open a PR, but I did not want to pick the conversion approach for you. If you tell me which you would prefer — a small local renderer over mammoth's HTML, reusing something in html.ts, or a converter dependency — I will build it that way.
Environment
- LibreChat source at
eaa91b3onmain mammoth1.11.0, the version pinned inpackages/api/package.json- Node.js 24.2.0, macOS
- Reproduced against the source rather than a running instance; the failure is in the parser and shows up in a unit-level call.
Source: danny-avila/LibreChat