Commit 9b28ffa
Fix MCP document full-text retrieval crash on cloud storage (bytes not JSON-serializable) (#1841)
* Fix MCP document full-text retrieval crash on cloud storage backends
get_document_text and the document MCP resource read txt_extract_file via
.open("r").read() and forwarded the result straight into json.dumps. On
S3/GCS (django-storages), text-mode reads return bytes, so json.dumps raised
"TypeError: Object of type bytes is not JSON serializable" and downstream MCP
clients could not retrieve document full text. LOCAL FileSystemStorage returns
str, which is why dev and the test suite never reproduced it.
Add a shared read_field_file_text() helper in utils/files.py that decodes bytes
to str when a backend ignores text mode, and route both MCP read sites
(tools.get_document_text, resources.get_document_resource) through it. Also
consolidate the duplicated isinstance(..., bytes) decode workaround in
tasks/embeddings_task.py and pipeline/parsers/oc_markdown_parser.py onto the
helper, and fix a latent instance of the same bug in
agents/memory.read_memory_content (which decoded bytes only in its exception
fallback, not its primary .open("r").read() branch).
Add a regression test that simulates a bytes-returning backend through
get_document_text and asserts the result is JSON-serializable, plus direct
unit tests for the helper.
* Fix linter (mypy/pyupgrade) and route remaining txt_extract_file reads through helper
- Type read_field_file_text() param as a structural Protocol instead of the
concrete FieldFile, so the duck-typed _FakeFieldFile test double type-checks
without # type: ignore (fixes the 4 mypy arg-type errors).
- pyupgrade: modernize .encode("utf-8") -> .encode() in test_files_utils.py.
- Address review: route the 7 remaining production txt_extract_file.open("r")
call sites through read_field_file_text() (thumbnailer, export_v2,
extraction_grounding, and the pii/document_indexing/search/annotations LLM
core tools) so they no longer break on cloud storage.
- Address review: MCP get_document_text / get_document_resource now decode with
errors="replace" so a few undecodable bytes don't silently yield an empty
document.
* Fix stale test expectations after MCP cloud-storage read refactor
test_markdown_parser: parser now reads via read_field_file_text (which
calls txt_extract_file.open("r")), not default_storage; the bytes/str
storage simulation now patches FieldFile.open instead of the removed
default_storage import.
test_agent_memory: read_memory_content intentionally collapsed its
two-level open()->.read() fallback; an open() failure now logs a warning
and returns "". Updated the two pre-existing fallback assertions to the
new contract (the bytes-vs-str normalization lives in the helper's
primary path).
* Address review: trim helper docstrings, fresh open handle, route remaining txt reads
- read_field_file_text + Protocol docstrings trimmed to one line (bug
context belongs in git log).
- MCP regression test patches FieldFile.open with side_effect so each
open() yields a fresh _BytesHandle instead of a shared, once-consumable
return_value.
- Route the two text_extracts.py txt_extract_file.read().decode() sites
through read_field_file_text(errors="replace") so undecodable bytes
substitute U+FFFD rather than crashing the LLM read path.
* Address review: errors=replace on LLM tool reads, resources bytes test, docstring + test-name fixes
- Route the four agent-facing LLM core tools (annotations, document_indexing,
pii, search) through read_field_file_text with errors="replace" so a few
undecodable bytes can't raise UnicodeDecodeError in an agent tool (CLAUDE.md
fault-tolerance rule); positions stay internally consistent.
- Add get_document_resource bytes-from-cloud regression test mirroring the
existing get_document_text test (closes the resources.py coverage gap).
- Expand the terse read_field_file_text docstring with the django-storages
rationale and errors-policy guidance.
- Rename test_both_read_paths_fail_returns_empty ->
test_open_failure_returns_empty_when_read_would_also_fail to match the
single-path read after the two-level fallback was removed.
* Address review: dedup _BytesHandle test helper, document errors='replace' rationale
- Extract the duplicated _BytesHandle inner class from the two MCP cloud-storage
bytes regression tests into a single module-level _BytesFieldFileHandle +
_patch_fieldfile_open_returns_bytes helper.
- Document that text_extracts.py's errors='replace' is intentional (agent-facing,
fault-tolerant per CLAUDE.md; pipeline/export paths stay strict).
- Tighten _TextReadableFieldFile.open return type to ContextManager[IO[Any]].
- Explain the FieldFile class-level (not instance) open() patch in test_markdown_parser.
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 26a864b commit 9b28ffa
19 files changed
Lines changed: 317 additions & 77 deletions
File tree
- opencontractserver
- agents
- llms/tools/core_tools
- mcp
- tests
- pipeline
- base
- parsers
- tasks
- tests
- utils
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
101 | 148 | | |
102 | 149 | | |
103 | 150 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| 32 | + | |
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
| |||
172 | 173 | | |
173 | 174 | | |
174 | 175 | | |
175 | | - | |
176 | | - | |
| 176 | + | |
177 | 177 | | |
178 | | - | |
179 | | - | |
180 | | - | |
181 | | - | |
182 | | - | |
183 | | - | |
184 | | - | |
185 | | - | |
186 | | - | |
187 | | - | |
188 | | - | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
189 | 183 | | |
190 | 184 | | |
191 | 185 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
| |||
279 | 280 | | |
280 | 281 | | |
281 | 282 | | |
282 | | - | |
283 | | - | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
284 | 288 | | |
285 | 289 | | |
286 | 290 | | |
| |||
Lines changed: 6 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
| |||
151 | 152 | | |
152 | 153 | | |
153 | 154 | | |
154 | | - | |
155 | | - | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
156 | 160 | | |
157 | 161 | | |
158 | 162 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
| |||
107 | 108 | | |
108 | 109 | | |
109 | 110 | | |
110 | | - | |
111 | | - | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
112 | 115 | | |
113 | 116 | | |
114 | 117 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
137 | 138 | | |
138 | 139 | | |
139 | 140 | | |
140 | | - | |
141 | | - | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
142 | 146 | | |
143 | 147 | | |
144 | 148 | | |
| |||
Lines changed: 11 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
6 | 8 | | |
7 | 9 | | |
8 | 10 | | |
| |||
101 | 103 | | |
102 | 104 | | |
103 | 105 | | |
104 | | - | |
105 | | - | |
106 | | - | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
107 | 112 | | |
108 | 113 | | |
109 | 114 | | |
| |||
161 | 166 | | |
162 | 167 | | |
163 | 168 | | |
164 | | - | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
165 | 172 | | |
166 | 173 | | |
167 | 174 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
| 14 | + | |
13 | 15 | | |
14 | 16 | | |
15 | 17 | | |
| |||
87 | 89 | | |
88 | 90 | | |
89 | 91 | | |
90 | | - | |
91 | | - | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
92 | 98 | | |
93 | 99 | | |
94 | 100 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
| |||
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
16 | 53 | | |
17 | 54 | | |
18 | 55 | | |
| |||
616 | 653 | | |
617 | 654 | | |
618 | 655 | | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
619 | 701 | | |
620 | 702 | | |
621 | 703 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
17 | 18 | | |
18 | 19 | | |
19 | 20 | | |
| |||
148 | 149 | | |
149 | 150 | | |
150 | 151 | | |
151 | | - | |
152 | | - | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
153 | 158 | | |
154 | 159 | | |
155 | 160 | | |
| |||
0 commit comments