Design: isolate model-router auto-routing text to the final user message
Isolate model-router auto-routing text to the final user message
- Proposal Issue: #4707
- Requirement:
SPEC-4707001 - Audited baseline:
faccaad586a3cdc9e85dc7fa39358ff31a6453b2
Goal
Make extractLastUserMessage honor its existing contract: routing text comes
only from the final role: user message. A textless final user turn must not
reuse text captured from an earlier user turn.
Current implementation locations
plugins/wasm-go/extensions/model-router/main.goextractLastUserMessagedeclareslastUserContentoutside the message loop.- Array content updates it only for
type: textitems. handleJsonBodypasses the helper result into regex routing and falls back todefaultModelonly when no target is selected.
plugins/wasm-go/extensions/model-router/main_test.goalready covers simple strings, multimodal text+image, multiple text parts, missing user messages, and multiple user turns, but not a textless final turn.
Selected design
- Traverse
messagesfrom the end until the first message withrole: user. - Inspect only that message's
contentand return immediately after processing it; never carry text state across user messages. - If content is a string, return its string value exactly as today.
- If content is an array, preserve the current behavior of selecting the last
type: textitem'stextvalue within that same message. - If the final user message has no text item, return
"". - Do not change rule ordering, regex matching,
defaultModel, or request/body rewrite logic.
An equivalent forward traversal is acceptable only if per-user state is reset before every user message and tests prove that a later textless user message clears earlier text. Reverse traversal is preferred because it encodes the contract directly and stops as soon as the authoritative user turn is found.
Behavior matrix
| Final user content | Earlier user text | Routing text |
|---|---|---|
string hello |
any | hello |
| text + image | any | text from final user message |
| multiple text parts | any | final text part in final user message |
| image-only | Please write code |
empty string |
| no user message | N/A | empty string |
Scope
Production change is limited to extractLastUserMessage in main.go; focused
helper and request-path regressions belong in main_test.go. No configuration,
README, VERSION, shared SDK, or unrelated routing behavior is changed.
Compatibility and risks
- Existing final-user string and multimodal-with-text behavior is preserved.
- Existing last-text-part behavior inside one user message is intentionally kept; concatenating text parts is outside this fix.
- The change affects only cases where a later user message has no text, where the current behavior contradicts the helper's documented last-user contract.
- The main risk is accidentally changing text extraction for supported payloads; existing tests plus new table-driven cases guard that surface.
Focused automated verification
Record exact baseline and fixed SHAs. From
plugins/wasm-go/extensions/model-router on the fixed head run:
go test ./... -count=1
go test -race ./... -count=1
go vet ./...
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o /tmp/model-router-last-user.wasm ./
sha256sum /tmp/model-router-last-user.wasm
git diff --check BASELINE_SHA...FIXED_SHAAdd helper regressions for image-only final user content, final text+image,
multiple text parts, and no user message. Add a request-path regression whose
earlier user text matches a code rule while the final user turn is image-only;
the expected selected model is the configured defaultModel, not the code
model.
Real proxy-Wasm red/green verification
Because this is a bug fix, native tests are not sufficient. Use the repository's Wasm runtime verification approach with the same pinned Envoy/Higress gateway image, same plugin configuration, same upstream fixture, and identical request for baseline and fixed modules.
Configure auto routing with defaultModel: qwen-turbo and a rule matching
(?i)code to qwen-coder. Send POST /v1/chat/completions with
model: higress/auto, an earlier user message Please write code, and a final
image-only user message. The upstream fixture records the received model field
and x-higress-llm-model.
Required red/green assertions:
- Baseline: upstream observes
qwen-coder, demonstrating stale earlier text influenced routing. - Fixed: upstream observes
qwen-turbo, demonstrating the final textless user turn produces no rule match and existing fallback applies. - Run at least three identical requests per revision with unique request IDs.
- Record client status/body hash, upstream captured body/header values, gateway access/plugin logs, exact source SHA, Wasm SHA-256, image digest, config, requests, assertion output, and cleanup proof.
- If baseline behavior differs from the source-derived prediction, stop and amend the Proposal/Design before claiming the bug is reproduced.
Rejected alternatives
- Concatenate all user turns: changes routing policy and preserves the bug class.
- Interpret image or other non-text parts: requires a separate multimodal routing design and model capability.
- Change rule/default fallback semantics: unnecessary for the stale-state defect.
Rollback
Revert the helper change and its tests. No configuration, persisted state, or migration is introduced.
Agent participation
A coding agent materially assisted with source analysis and this Design. No implementation, tests, or runtime verification have begun. Implementation and verification remain blocked until Proposal #4707 and this Design receive explicit Higress maintainer approval and the implementation/verification TASKs are authorized.
Source: higress-group/higress