#4710·higress

Design: isolate model-router auto-routing text to the final user message

Author: zmuxunyCreated Sep 16, 2026Updated Sep 16, 2026

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.go
    • extractLastUserMessage declares lastUserContent outside the message loop.
    • Array content updates it only for type: text items.
    • handleJsonBody passes the helper result into regex routing and falls back to defaultModel only when no target is selected.
  • plugins/wasm-go/extensions/model-router/main_test.go already covers simple strings, multimodal text+image, multiple text parts, missing user messages, and multiple user turns, but not a textless final turn.

Selected design

  1. Traverse messages from the end until the first message with role: user.
  2. Inspect only that message's content and return immediately after processing it; never carry text state across user messages.
  3. If content is a string, return its string value exactly as today.
  4. If content is an array, preserve the current behavior of selecting the last type: text item's text value within that same message.
  5. If the final user message has no text item, return "".
  6. 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:

bash
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_SHA

Add 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.