Repository navigation
Replies: 1 comment
The compat table already models the opposite direction — that asymmetry is the useful framingYour reading is sound, and there is prior art in the tree that names this precisely. The knob you want exists for the other direction. The pi-ai compat table carries ( /** Whether thinking blocks must travel as text in `<thinking>` delimiters; `openai-completions`. */
requiresThinkingAsText?: booleanThat models outbound: "this endpoint wants thinking text wrapped in delimiters." Your gateway does the inbound thing — it sends Where the tags arrive, and where a fix can stand. With case 'text_delta':
yield { type: 'text-delta', index: event.contentIndex, text: event.delta }
case 'text_end':
yield { type: 'block-end', index: event.contentIndex, block: { type: 'text', text: event.content } }Your The response side is a legitimate place for this and needs no core change. Two things it has to get right, both from your own report:
One caveat on reachability. A response-side wrapper can restructure the chunk stream, but it cannot reach anything an adapter has already folded into a finished block before yielding it — I hit exactly that limit on a different marker in this same seam family, where the provider accumulated the sentinel internally and the terminal event carried no chunk to intercept. If you ever see tags survive despite a correct parser, check the adapter's I cannot reach your gateway from here, so I have not re-run your reproduction against a live endpoint; the minimal SSE records and your patch branch are what make the report actionable. |
Uh oh!
There was an error while loading. Please reload this page.
Grok gateway
<think>output stays in the answer instead of the reasoning disclosureI am reporting this through Discussions because CONTRIBUTING.md says external PRs are not currently accepted. I have a tested, opt-in compatibility patch available for reference; this is not a request to bypass that policy.
Observed behavior
With
@deepseek-ai/dsh-llm-pi-aiand an OpenAI Chat Completions-compatible gateway servinggrok-4.7/ Low, some responses contain a leading<think>...</think>section indelta.content. Harness displays those tags and their contents in the ordinary answer, while its reasoning disclosure remains separate.The same route can return native
reasoning_contentin another request. A tool round can also end the text block before</think>arrives. Therefore this report is about a gateway's mixed output representations; I have not established whether the gateway or its upstream model introduces the tags, and I am not claiming that the direct xAI API behaves this way.Checked against official
masterat86a402200d95fecef4529d969836f1fe13c6cb0d(0.2.1-alpha.4, pi-ai1.1.0) on Windows / Node 24.Minimal reproduction
A deterministic Chat Completions stream can send the following payloads (each as an SSE
data:record):{"choices":[{"index":0,"delta":{"role":"assistant","content":"<thi"},"finish_reason":null}]} {"choices":[{"index":0,"delta":{"content":"nk>checking the result</think>42"},"finish_reason":null}]} {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}Follow with
data: [DONE].Current result: one text block containing
<think>checking the result</think>42.With explicit compatibility enabled: a reasoning block containing
checking the result, followed by a text block containing42; the existing UI can display the reasoning in its disclosure. Ordinary prose that quotes tags should remain unchanged.Proposed compatibility option
Independent fix branch · single commit · diff against the checked upstream base.
The patch adds
parseThinkTags: trueto a model entry ormodelOverrides. It is disabled by default and accepted only foropenai-completionsmodels. For example, inside an existing gateway route:The parser:
<think>section in a native text block, including tags split across chunks. An unclosed section remains reasoning until that block ends.<thinking>tags, and later tags after the first closing section literal.EMPTY_RESPONSEif stripping empty tags leaves an otherwise successfully completed response with no content; tool-only, truncated, aborted and failed responses retain their existing outcomes.This changes the provider adapter/configuration and adds regression coverage and documentation. It does not modify the Agent loop or the UI/model selector, and does not rewrite existing conversations.
Validation
On the branch above:
think-tags.ts.pnpm run buildpassed all 9 build/check steps;pnpm run lintpassed; commit hooks passed.pnpm run doc-sync: 42 passed, 1 failed. The failure is in unchanged upstreampython/sdk/uv.lock:113: the wheel size6344383happens to resolve to a commit prefix in my local object database, soverify-repository-referencesflags it. The lockfile is identical to the checked upstream base; no unrelated workaround is included.The real gateway endpoint and credentials are intentionally omitted. The branch includes a key-gated
think-tags.e2e.tssmoke usingDSH_GROK_BASE_URL,DSH_GROK_MODELandDSH_GROK_API_KEY, plus keyless regression tests.Would an explicit per-model compatibility option like this fit the adapter's intended scope, or would you prefer this normalization to live in an external middleware plugin?
All reactions