Skip to content

fix: unbreak document ingestion (docling to_formats) and OpenRouter chat model ids - #356

Open
Deodat-Lawson wants to merge 2 commits into
mainfrom
fix/docling-to-formats-and-chat-model-ids
Open

fix: unbreak document ingestion (docling to_formats) and OpenRouter chat model ids#356
Deodat-Lawson wants to merge 2 commits into
mainfrom
fix/docling-to-formats-and-chat-model-ids

Conversation

@Deodat-Lawson

@Deodat-Lawson Deodat-Lawson commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Two independent bugs found while bringing the Compose stack up and testing document ingestion end to end. Both broke a core path silently, and both were verified against the running stack rather than reasoned about.

1. Document ingestion was completely broken (services/document-converter)

Every Office/PDF upload failed. docling-serve validates each to_formats entry against its OutputFormat enum, but the client sent the JSON string '["md"]', so the entire string arrived as one bogus enum member:

{"detail":[{"type":"enum","loc":["body","to_formats",0],
 "msg":"Input should be 'md', 'json', 'yaml', 'html', ..."}]}

The failure chain, and why it was hard to spot:

  1. POST /v1/convert/file422
  2. Converter surfaced a typed 502 docling-failed
  3. Worker retried 3×, then parked the outbox event with backoff
  4. Zero chunks produced
  5. Retrieval found nothing, and the QA route answered "No relevant content found for the given question." — byte-identical to the message for an empty workspace

So a fully broken ingestion pipeline was indistinguishable from a workspace with no documents. Diagnosing it required querying pdr_ai_v2_event_outbox.last_error directly, because the client discards docling's response body and logs only the status code.

Verified both directions against the running service: '["md"]' → 422, "md" → 200 with the expected md_content. After the fix the parked event processed on first retry and produced 8 embedded retrieval chunks from a real .docx.

The unit test asserted the JSON-array form, so it had pinned the broken shape and would have blocked this fix in CI. Updated to match.

2. Chat 404'd on every request (apps/web/config/chat-models.yaml)

chat-models.yaml declared bare Gemini ids (gemini-2.5-flash) while CHAT_BASE_URL points at https://openrouter.ai/api/v1. Ids go to the endpoint verbatim and OpenRouter serves google/gemini-2.5-flash, not the bare name — so every chat request failed.

Only the id: fields change. The preset: keys already carried the google/ prefix, because they name a bundled behavior entry rather than a model on the endpoint — they were correct as written.

The comment above the block described the ids as "the BARE ids Google's endpoint expects" and no longer matched, so it now explains both directions: keep the prefix for OpenRouter, drop it for Google's own compatibility endpoint.

Reviewer notes

  • The docling break was not caused by anything in this repo. docker-compose.yml:240 is image: ghcr.io/docling-project/docling-serve with no tag, so it resolves to latest and the image moved underneath us. Every docker compose pull re-rolls that dice. Pinning it to a tag or digest is deliberately not in this PR — it's a separate call about how this project wants to handle upstream image drift, but it's the fix for the whole class of problem.
  • Consider logging docling's response body on non-2xx in docling.ts. The 422 detail names the exact offending field; discarding it turned a one-line bug into a database-archaeology session.
  • The two commits are independent and can be split if preferred.
  • services/* is outside the pnpm workspace, so its vitest suite was not run locally — the document-converter CI job covers it. The behavioral fix was verified directly against the live service instead.

Related: #355 (the empty-state message that made this so hard to diagnose).


Note

Medium Risk
Touches document conversion and default chat model routing—core user paths—but the edits are small, well-scoped contract/config fixes with an updated unit assertion.

Overview
Restores two broken core paths: document ingestion and default chat.

Ingestion: docling-serve now receives to_formats as the enum value md instead of the JSON string ["md"], which was rejected as 422 and left uploads looking like an empty workspace. The converter unit test is updated to pin the correct form field.

Chat: Default Gemini ids in chat-models.yaml get OpenRouter’s google/ prefix so they match CHAT_BASE_URL. Presets are unchanged. Comments now say to drop the prefix if talking to Google’s own compatibility endpoint.

Reviewed by Cursor Bugbot for commit b42225d. Bugbot is set up for automated code reviews on this repo. Configure here.

Deodat-Lawson and others added 2 commits August 23, 2026 19:00
docling-serve validates each to_formats entry against its OutputFormat
enum. Sending the JSON string '["md"]' makes the whole string arrive as
a single, bogus enum member, so every conversion failed:

  {"detail":[{"type":"enum","loc":["body","to_formats",0],
   "msg":"Input should be 'md', 'json', 'yaml', 'html', ..."}]}

The converter surfaced that as a typed 502 docling-failed, the worker
retried three times and parked the outbox event, and the document
produced zero chunks. Retrieval then found nothing and the QA route
answered "No relevant content found for the given question" — identical
to the message for an empty workspace, which made a broken ingestion
pipeline look like an empty one.

Verified against the running service: '["md"]' returns 422 and "md"
returns 200 with the expected md_content. After the fix the parked event
processed on first retry and produced 8 embedded retrieval chunks.

The unit test asserted the JSON-array form, so it locked in the broken
shape and would have blocked this fix in CI. Updated to match.

Nothing in this repo changed to cause the break: docker-compose.yml
pins docling-serve to an implicit `latest`, so the image moved under us.
Pinning it is left as a separate decision.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
chat-models.yaml declared bare Gemini ids (gemini-2.5-flash) while
CHAT_BASE_URL points at https://openrouter.ai/api/v1. Ids are sent to
the endpoint verbatim, and OpenRouter does not serve the bare names —
it serves google/gemini-2.5-flash — so every chat request 404'd.

Only the `id:` fields change. The `preset:` keys already carried the
google/ prefix because they name a bundled behavior entry rather than a
model on the endpoint, so they were correct as written.

Also refreshes the comment above the block, which described the ids as
"the BARE ids Google's endpoint expects" and no longer matched. It now
explains both directions: keep the prefix for OpenRouter, drop it when
talking to Google's own compatibility endpoint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
launch-stack Ready Ready Preview Aug 23, 2026 11:05pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
pdr-ai-v2 Ignored Ignored Aug 23, 2026 11:05pm

@cursor

cursor Bot commented Aug 23, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_9404a880-b9e4-4d1d-a834-21430b62e2f6)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b42225d0fb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# a bundled behavior entry, not a model on the endpoint.
primary:
id: gemini-2.5-flash
id: google/gemini-2.5-flash

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep bare IDs for the built-in Gemini endpoint

When CHAT_BASE_URL is unset—the supported default path—resolveChatEndpoint still selects Google's compatibility endpoint (apps/web/src/server/chat-endpoint.ts:155-175), and the documented .env.example also points there. This configuration now sends google/gemini-2.5-* verbatim even though that endpoint expects bare Gemini IDs, so primary, fast, and reasoning chat requests fail for default and documented Google deployments. Keep the shipped IDs bare, or change the actual endpoint default and provide a separate configuration for direct Google users.

Useful? React with 👍 / 👎.

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.

1 participant