[BUG] dsh-llm-pi-ai: tool_call arguments O(n²) reparse in long sessions causes 500 Upstream gateway error #3923
Replies: 1 comment
|
Independently reproduced with numbers — your O(n²) diagnosis holds, and it bites well before "long session" territory. Measurement (fault injection at the wire only; the OpenAI client, SSE parser and accumulator under test are unmodified pi-ai 0.84.1): one tool call whose JSON
8× the chunks → 48× the time, and per-chunk cost rises with the accumulated length (0.081 → 0.493 ms) — the quadratic signature, exactly as you predicted. Harness + raw verdicts: Two things worth adding to your report:
Happy to re-run this harness against a candidate patch if it helps — it's self-contained ( |
Uh oh!
There was an error while loading. Please reload this page.
Summary
@deepseek-ai/dsh-llm-pi-ai's OpenAI Chat Completions adapter accumulatesstreaming
tool_calls[].function.argumentsdeltas into a single string,then re-parses the entire accumulated string on every chunk using
partial-json. This is O(n²) in the length ofargumentsper tool call.In long-running sessions (e.g., agent sessions that last hours and
accumulate large assistant message histories), this combines with the
session's growing
messagespayload to produce requests whose bodiesexceed provider gateway limits, causing 500/503
Upstream gateway errorresponses from relay hosts such as
relay.framefly.com.cn.Environment
@deepseek-ai/dshv0.1.0-rc.6)@deepseek-ai/dsh-web-app@deepseek-ai/dsh-llm-pi-aillm-pi-ai.providers.<route>usingapi: openai-completionsagainst a relay that fronts OpenAI'schat/completions(e.g.,relay.framefly.com.cn)gpt-5.5(andgpt-5.4-mini,gpt-5.6-sol, etc.)curlcalls succeedwith sub-second TTFT and no 500 responses
dsh-llm-deepseekagainst the same provider has no such failuresSymptoms
openai-relay/gpt-5.5see occasionalturn/end kind=errorevents with reason{"code":"SERVER","message":"500: {\"message\":\"Upstream gateway error\",\"type\":\"api_error\"}"}or
503: {"message":"Service temporarily unavailable"}≈0.01%), but accumulates the longer the session runs
accumulated history plus tool_calls makes the request body large
dsh-llm-deepseekon the same provider pathshows no such errors in equivalent workloads
Reproduction
Direct LLM access (relay.framefly.com.cn) succeeds in 8/8 cases.
The same call initiated through DSH via the
llm-pi-aiprovider eventuallyreturns a 500. The session's
request.contextevent shows the same providerand model are in use, and the body of the failing request is significantly
larger than a fresh session's (long history + many tool_calls).
Root cause
In
@deepseek-ai/dsh-llm-pi-ai's OpenAI Chat Completions stream handler(
dist/api/openai-completions.js), tool_call argument deltas are concatenatedinto a single string and re-parsed on every chunk:
parseStreamingJsoninvokespartial-json'sparse()on the fullaccumulated string, so the cost of streaming one
tool_callwithndelta characters is O(n²) in
n. Across hundreds of tool calls in a longsession, this is both slow and (more importantly) the streaming handler
holds on to ever-growing
partialArgsstrings per call, and the assembledhistory sent to the provider is bloated by stringified reparses.
The harness
Blockassembleralready provides incremental block assemblythat tolerates
delta-only protocols. The adapter does not need toaccumulate
partialArgsat all — it could yield rawtool-call-deltachunks and let the harness's assembler manage concatenation (this is
exactly what
dsh-llm-deepseekdoes).Workaround
Two practical mitigations until this is fixed upstream:
dsh-llm-deepseekfor long sessions (it does not have this issue).retryPolicyto the affected provider in~/.dsh/settings.yamlso the harness'sdsh-llm-retryrecovers fromthe 500/503 instead of failing the turn:
dsh-llm-pi-aiforces the inner pi-ai SDK'smaxRetriesto 0,so the retry must be configured at the harness layer.
Suggested fix
Replace the per-chunk
partialArgsaccumulator +parseStreamingJsonwith raw
tool-call-deltayields and let the harnessBlockassemblerhandle the concatenation. The adapter should yield one
tool-call-deltachunk per incomingtool_calls[i].function.argumentsdelta, with
index,id,name,argumentsfields populated, and relyon the harness to assemble the final
argumentsstring for eachtool-callblock (this is whatdsh-llm-deepseekalready does).The same fix applies to the
tool-call-deltaaccumulation thatpi-messages.jsperforms downstream of the adapter — once the adapterstops accumulating, the entire pi-ai pipeline becomes delta-only and the
Blockassembler'sdeltas arriving for an index already closed by block-end are ignored (malformed stream)invariant holds.Reference
https://github.com/deepseek-ai/deepseek-harnesspackages/llm-pi-ai/dist/api/openai-completions.jspackages/llm-deepseek/lib/index.js(seeasync *stream+translatePayload)Blockassembler(correct incremental assembly):packages/llm/lib/types/assembler.tsFiled by: 老六 (AvatarV5)
Date: 2026-08-19
Affected version:
@deepseek-ai/dsh-llm-pi-aishipped with@deepseek-ai/dshv0.1.0-rc.6All reactions