← Back to PRs

#22741: fix(models): add DashScope/Qwen to normalizeModelCompat developer role guard (#22710)

by lailoo open 2026-02-21 16:02 View on GitHub →
agents size: S experienced-contributor
## Summary - **Bug**: DashScope Qwen3 reasoning models fail with `developer is not one of ['system', 'assistant', 'user', 'tool', 'function']` because `normalizeModelCompat()` only guards Z.ai but not DashScope/Qwen providers - **Root cause**: `normalizeModelCompat()` in `src/agents/model-compat.ts` only checks for Z.ai provider/baseUrl, so DashScope and Qwen Portal models with `reasoning: true` get `supportsDeveloperRole` defaulting to `true` from pi-ai's `detectCompat()`, causing `developer` role to be sent - **Fix**: Extend `normalizeModelCompat()` to also detect DashScope (`dashscope` provider / `dashscope.aliyuncs.com` baseUrl) and Qwen Portal (`qwen-portal` provider / `portal.qwen.ai` baseUrl), setting `supportsDeveloperRole: false` for these providers Fixes #22710 ## Problem When a DashScope/Qwen model has `reasoning: true`, the openai-completions provider sends `developer` role for system prompts. DashScope's compatible-mode API does not support the `developer` role and returns HTTP 400: ``` developer is not one of ['system', 'assistant', 'user', 'tool', 'function'] ``` **Before fix:** ```bash curl -X POST https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $KEY" \ -d '{"model":"qwen3.5-plus","messages":[{"role":"developer","content":"You are helpful."},{"role":"user","content":"Hello"}]}' # → 400: "developer is not one of ['system', 'assistant', 'user', 'tool', 'function']" ``` ## Approach This fix follows the same pattern established for Z.ai in `normalizeModelCompat()`. Z.ai previously encountered the identical issue — its compatible API doesn't support the `developer` role either — and the project introduced `normalizeModelCompat()` as an application-level guard to set `supportsDeveloperRole: false` before models reach pi-ai's provider pipeline. DashScope and Qwen Portal have the same constraint, so we extend the existing `needsDeveloperRoleOff` guard to cover them: ```typescript const needsDeveloperRoleOff = provider === "zai" || baseUrl.includes("api.z.ai") || provider === "dashscope" || // new baseUrl.includes("dashscope.aliyuncs.com") || // new provider === "qwen-portal" || // new baseUrl.includes("portal.qwen.ai"); // new ``` **Why this approach works well:** - Minimal change — extends an existing, proven pattern rather than introducing new abstractions - Centralized guard — catches all DashScope/Qwen models regardless of where they're configured (user config, presets, etc.) - Defensive — both `provider` string and `baseUrl` substring are checked, so it works even if only one is set **Suggestion for a more robust long-term fix:** The current approach (both this PR and the original Z.ai guard) uses a blacklist: providers not in the list default to `supportsDeveloperRole: true`. This means every new provider that doesn't support `developer` role requires a code change. A more robust approach would be to invert the default in pi-ai's `detectCompat()` — use a whitelist of providers known to support `developer` role (OpenAI, Azure OpenAI, etc.), and default all others to `supportsDeveloperRole: false`. The `system` role is universally supported by all OpenAI-compatible APIs, while `developer` role is only supported by a few. Defaulting to `system` would be safer and eliminate the need for per-provider patches. This would be a change in pi-ai's `detectCompat()` rather than in `normalizeModelCompat()`, so it's out of scope for this PR. ## Changes - `src/agents/model-compat.ts` — Extend provider detection to include `dashscope`/`dashscope.aliyuncs.com` and `qwen-portal`/`portal.qwen.ai`, refactor the guard into a single `needsDeveloperRoleOff` boolean - `src/agents/model-compat.e2e.test.ts` — Add 3 regression tests: DashScope provider, Qwen Portal provider, and explicit compat preservation - `src/agents/dashscope-developer-role.live.test.ts` — Add live e2e test verifying real DashScope API behavior (requires `DASHSCOPE_LIVE_TEST=1` + API key) - `CHANGELOG.md` — Add fix entry **After fix:** DashScope/Qwen models get `supportsDeveloperRole: false` via `normalizeModelCompat()`, so the provider sends `system` role instead of `developer`, and requests succeed. ## Test plan - [x] Live e2e: `completeSimple()` with `normalizeModelCompat` → DashScope returns response ✅ - [x] Live e2e: `completeSimple()` without `normalizeModelCompat` (developer role) → DashScope returns 400 error ✅ - [x] Real API curl: `developer` role → 400; `system` role → 200 - [x] New test: DashScope provider gets `supportsDeveloperRole: false` - [x] New test: Qwen Portal provider gets `supportsDeveloperRole: false` - [x] New test: Explicit DashScope compat `false` is preserved - [x] All 3 existing Z.ai tests still pass (6 total e2e tests pass) - [x] Format check passes ## Effect on User Experience **Before:** DashScope Qwen3 reasoning models fail on every request with a cryptic 400 error about unsupported `developer` role. Users must manually add `compat: { supportsDeveloperRole: false }` to their model config as a workaround. **After:** DashScope and Qwen Portal models automatically get the correct compat setting. Reasoning models work out of the box without manual configuration. <!-- greptile_comment --> <h3>Greptile Summary</h3> Extends `normalizeModelCompat()` to disable the unsupported `developer` role for DashScope and Qwen Portal providers. Previously only Z.ai was guarded, causing DashScope/Qwen reasoning models to fail with HTTP 400 errors. The fix adds provider/baseUrl detection for both DashScope (`dashscope`/`dashscope.aliyuncs.com`) and Qwen Portal (`qwen-portal`/`portal.qwen.ai`), consolidating the logic into a clear `needsDeveloperRoleOff` boolean. - Refactored provider detection to use a single boolean guard - Added comprehensive test coverage for both new providers - Preserves explicit compat settings to avoid unnecessary mutations <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk - The fix is a straightforward extension of existing, well-tested logic. It adds defensive checks for two additional providers using the same pattern as Z.ai, with comprehensive test coverage including edge cases. The early-return structure prevents any impact on non-openai-completions models, and explicit compat preservation ensures no unintended mutations. - No files require special attention <sub>Last reviewed commit: 499c2ea</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs