Skip to content

feat(server,cli): bulk document ingestion across gRPC, HTTP, MCP, and CLI (#865)#870

Open
mosuka wants to merge 1 commit into
mainfrom
feat/865-server-cli-bulk
Open

feat(server,cli): bulk document ingestion across gRPC, HTTP, MCP, and CLI (#865)#870
mosuka wants to merge 1 commit into
mainfrom
feat/865-server-cli-bulk

Conversation

@mosuka

@mosuka mosuka commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

Exposes the batch ingestion API (#551 PR-3, on top of the merged Engine::put_documents / add_documents) across every server-side surface:

  • gRPC: DocumentService.PutDocuments / AddDocuments with repeated DocumentEntry { id, document } and an applied count response — unary, mirroring the SearchBatch precedent (client-side chunking handles the 4 MB default cap; a streaming RPC remains a future non-breaking addition). Entries without a document are rejected up front with the offending position. LaurusError::BatchIngest is now mapped in convert/error.rs by its source (schema violation at position k → INVALID_ARGUMENT, storage failure → INTERNAL), with the full batch context (failed_index, failed_id, applied) preserved in the status message.
  • HTTP gateway: POST /v1/documents:bulk?mode=put|add (default put) taking {"documents": [{"id", "document"}, ...]} and responding {"applied": N}. A router-build smoke test pins that axum 0.8 accepts the literal-colon path.
  • MCP: put_documents / add_documents tools taking a documents array (same entry shape), registered in the server instructions list.
  • CLI: laurus put docs / add docs — streaming JSONL bulk ingest (--file, --batch-size default 1000, --commit-every default 0 = final-only). Each line is {"id": "...", "document": {"fields": {...}}} (the same document JSON shape as --data). Unlike the singular commands it commits automatically; on a mid-file failure the error names the offending line, the applied prefix is committed, and re-running the remaining lines (or the whole file, under put) is idempotent. --commit-every prototypes the future CommitPolicy shape client-side, per the perf(lexical/index): batch ingestion API (add_documents / commit_batch) #551 plan.

Also fixed in passing

The CLI docs' --data examples showed a flat JSON object ('{"title":"Hello World"}'), which serde_json::from_str::<Document> has never accepted — the working shape is {"fields": {"title": {"Text": "..."}}} (verified with a probe; the flat form fails today). Both singular examples and the format description are corrected in en+ja alongside the new bulk sections.

Verification

  • laurus-server lib tests 41 green, including new: batch apply + in-batch dedup via GetDocuments, chunk accumulation, empty batch → applied: 0, entry-without-document → INVALID_ARGUMENT naming documents[1], and BatchIngest source-based status classification (caller fault vs storage fault).
  • laurus-cli tests 6 green, including a temp-dir end-to-end JSONL ingest (multiple batches, periodic commits, blank lines, duplicate-id dedup with last-wins) and line-numbered parse errors.
  • laurus-mcp 12 green (conversion suite unchanged).
  • fmt / clippy 1.95 + 1.97 (--features laurus/embeddings-all) clean / markdownlint clean across all updated docs (en+ja: grpc_api, http_gateway, mcp tools, cli commands).

Docs

docs/{src,ja/src}/laurus-server/{grpc_api.md, http_gateway.md}, laurus-mcp/tools.md, laurus-cli/commands.md: new bulk sections with fail-fast/no-rollback semantics, plus the corrected singular examples.

Closes #865. Refs #551.

… CLI (#865)

Expose the Engine::put_documents / add_documents batch API (#551) on
every server-side surface:

gRPC: DocumentService.PutDocuments / AddDocuments take repeated
DocumentEntry { id, document } and return the applied count - unary,
mirroring the SearchBatch precedent. Entries without a document are
rejected up front naming the offending position.
LaurusError::BatchIngest is now classified in convert/error.rs by its
source (caller mistakes surface as INVALID_ARGUMENT, storage failures
as INTERNAL) while the status message keeps the full batch context
(failed_index, failed_id, applied).

HTTP gateway: POST /v1/documents:bulk?mode=put|add (default put) takes
{"documents": [{"id", "document"}, ...]} and responds {"applied": N};
a router smoke test pins axum 0.8 accepting the literal-colon path.

MCP: put_documents / add_documents tools taking a documents array.

CLI: laurus put docs / add docs stream a JSONL file (--file,
--batch-size, --commit-every) through the batch API. Each line is
{"id": ..., "document": {"fields": ...}} - the same document JSON
shape as --data. Unlike the singular commands, bulk ingestion commits
automatically; a mid-file failure names the offending line, commits
the applied prefix, and re-running the remaining lines is idempotent.

Also fixes the pre-existing CLI docs bug where the --data examples
showed a flat JSON object that Document's serde shape never accepted;
the corrected examples use {"fields": {name: {Type: value}}}.

Closes #865. Refs #551
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(server,cli): bulk document ingestion — gRPC PutDocuments/AddDocuments, HTTP bulk, MCP tool, CLI JSONL (#551 PR-3)

1 participant