feat(indexing): index-json-documents takes a typed documents array - #202
Open
adityamparikh wants to merge 6 commits into
Open
adityamparikh wants to merge 6 commits into
adityamparikh wants to merge 6 commits into
Conversation
The tool took its documents as a JSON string, so the model had to escape every quote and newline of the payload inside a JSON string argument. On a 61-document sample that escaping is about 12% of the output tokens, and a single mis-escaped quote fails the whole call after the payload has been generated. The parameter is now List<Map<String, Object>>, advertised as an array of objects, so the model emits native JSON and the SDK parses it once. JsonDocumentCreator gains a create(List<Map>) entry point that runs the same flattening as the string path; JsonDocumentCreatorTest pins that both produce identical documents. The index-data prompt tells the model to pass the array itself, and its opening line now names the format rather than the parameter. Tests keep their JSON text blocks and parse them through TestDocuments.json. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CiUHyyXLTo9ATdgg8eRFZJ Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CiUHyyXLTo9ATdgg8eRFZJ Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
This was referenced Sep 14, 2026
… points Cleanup pass over the typed-documents change. No behaviour change beyond the markdown prompt fix noted below. JsonDocumentCreator: create(List) was inserted between the create(String) javadoc and the method it documents, so javac bound only the trailing block -- create(String) lost its docs and create(List) inherited a @PARAM json it does not have. Moving it below reunites them. Both entry points now share flatten(JsonNode)/toDocument(JsonNode) instead of maintaining parallel loops, so objectsAndStringProduceTheSameDocuments guards a shared path rather than being the only thing keeping two hand-written loops in step. create(List) converts the whole list with one valueToTree call instead of one per document. Null moves down to the creator, which already owns the "nothing to index" policy, so it reports null the same way it reports empty. That drops the one-off IllegalArgumentException from indexJsonDocuments, which none of the three sibling index tools had. IndexTool gains the canonical format. The prompt's first %s was indexTool.paramName(), which worked only because paramName happened to equal the format keyword; renaming the JSON parameter to `documents` broke that, and re-normalising the raw argument lost the md -> markdown canonicalisation, so format=md rendered "You are indexing md data". Also: createSchemalessDocuments -> createSchemalessDocumentsFromJson, so the orchestrator keeps its ...From<Format> family; the stale @see on indexJsonDocuments; TestDocuments catches JsonProcessingException, which is what readValue declares and what survives Jackson 3, and hoists its TypeReference; and CollectionServiceIntegrationTest passes the documents it already holds instead of serialising and reparsing them, retiring a now-unused autowired ObjectMapper. Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JZoVjWoHU315uEngMQdhWE Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
The typed `documents` array has no string form to measure, so the `index-json-documents` path was already uncapped. Rather than add a second limit to match, remove the one on the string entry point so both JSON paths behave the same way. Payload size is now bounded by the heap and by whatever limit the transport in front of the server imposes, not by this creator. CSV and markdown keep their own caps; this changes JSON only. No test covered the limit, so nothing else moves. Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JZoVjWoHU315uEngMQdhWE Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Follows the JSON change: all three creators carried the same copy-pasted 10 MB constant and the same getBytes() length check, which materialised a second full copy of the payload purely to measure it, immediately before the parser read the string again. Payload size is now bounded by the heap and by whatever limit the transport in front of the server imposes, not by the creators. The XML path keeps its own "XML document too large" check in IndexingDocumentCreator, which is a separate limit with test coverage. No test covered either cap, so nothing else moves. Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JZoVjWoHU315uEngMQdhWE Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Completes the removal across all four formats. The XML limit lived in the orchestrator rather than in XmlDocumentCreator, and was the last of the copy-pasted 10 MB checks; like the others it allocated a full byte[] copy of the payload purely to measure it before the parser read the string. Removes MAX_XML_SIZE_BYTES, the check, the now-unused StandardCharsets import, and testCreateSchemalessDocumentsFromXmlWithLargeDocument, which built a 12 MB document to assert the limit fired. The null/blank validation on the XML path is unchanged and still covered. Payload size is now bounded by the heap and by whatever limit the transport in front of the server imposes. Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JZoVjWoHU315uEngMQdhWE Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
index-json-documentstook its documents as a JSON string, so the model had to escape every quote and newline of the payload inside a JSON string argument. On the 61-show sample that escaping is about 12% of the output tokens the model generates for the call, and one mis-escaped quote fails the whole call after the payload has been produced.The parameter is now
List<Map<String, Object>> documents, advertised in the tool schema as an array of objects. The model emits native JSON, the MCP client parses it once, and on the SDK 2.0 line (#23) the shape is validated before dispatch.What changed
JsonDocumentCreator.create(List<Map<String,Object>>): same flattening as the string path (nested objects toa_b, arrays to multi-valued fields, nulls skipped).JsonDocumentCreatorTestpins that both entry points produce identical documents.IndexingService.indexJsonDocumentstakes the typed list; description tells the model to pass the array itself, not a string. Theindex-dataprompt says the same for the JSON path and its opening line names the format rather than the parameter.TestDocuments.json(...)helper; the MCP-level tests send the array.Cleanup pass (
00e8c7c)create(List)andcreate(String)now shareflatten(JsonNode)/toDocument(JsonNode)instead of maintaining parallel loops, soobjectsAndStringProduceTheSameDocumentsguards a shared path rather than being the only thing keeping two hand-written loops in step.create(List)converts the whole list with onevalueToTreecall rather than one per document.create(List)had been inserted between thecreate(String)javadoc and the method it documents, so javac bound only the trailing block:create(String)lost its docs andcreate(List)inherited a@param jsonit does not have.IllegalArgumentExceptionfromindexJsonDocumentsthat none of the three sibling index tools had.IndexToolgained the canonical format. The prompt's first%swasindexTool.paramName(), which worked only becauseparamNamehappened to equal the format keyword; renaming the JSON parameter todocumentsbroke that, and re-normalising the raw argument lost themd→markdowncanonicalisation, soformat=mdrendered "You are indexing md data".createSchemalessDocuments→createSchemalessDocumentsFromJson, keeping the orchestrator's...From<Format>family; stale@see;TestDocumentscatchesJsonProcessingException(whatreadValuedeclares, and what survives Jackson 3) and hoists itsTypeReference;CollectionServiceIntegrationTestpasses the documents it already holds instead of serialising and reparsing them, retiring a now-unused autowiredObjectMapper.Input size limits removed (
0c0a2f2,327ae50,792ecd5)The typed
documentsarray has no string form to measure, so the JSON tool path was already uncapped. Rather than add a second limit to match, the existing 10 MB caps were removed across all four formats.All four carried the same copy-pasted
10 * 1024 * 1024constant and the samegetBytes()length check, which materialised a second full copy of the payload purely to measure it, immediately before the parser read the string again — in two different layers (JSON/CSV/markdown in the creators, XML in the orchestrator) with two different message formats.Payload size is now bounded by the heap and by whatever limit the transport in front of the server imposes. Blank/null validation is unchanged on every path and still covered. Only one test touched a limit —
testCreateSchemalessDocumentsFromXmlWithLargeDocument, which built a 12 MB document to assert the XML check fired — and it is removed with it.Not changed
CSV, XML and Markdown stay strings: those formats have few quotes and no repeated keys, so a string is already their cheapest wire form. Part of a set with
feat/markdown-multi-documentandfix/xml-record-fields; the three are independent and merge in any order.Verification
./gradlew buildon Java 25: green — 406 tests, 399 passed, 0 failed, 7 skipped (the pre-existing@DisabledOTLP/LGTM class). The count is one lower than before the cap removal, which is the deleted XML size test../gradlew nativeTest -Pnativeon GraalVM CE 25.0.2: 259 successful, 0 failed (the genericList<Map>parameter deserialises natively without extra hints). Measured before the cleanup commits; the cleanup adds no new types or reflection.🤖 Generated with Claude Code
https://claude.ai/code/session_01JZoVjWoHU315uEngMQdhWE