-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(cli): respect OPENAI_MODEL precedence in CLI model resolution #3567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1b78d99
bba953d
124b20a
daa055f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -442,6 +442,187 @@ describe('modelConfigUtils', () => { | |
| ); | ||
| }); | ||
|
|
||
| it('should find modelProvider from OPENAI_MODEL when argv.model is not provided', () => { | ||
| const argv = {}; | ||
| const modelProvider: ProviderModelConfig = { | ||
| id: 'env-openai-model', | ||
| name: 'Env OpenAI Model', | ||
| generationConfig: { | ||
| samplingParams: { temperature: 0.6 }, | ||
| }, | ||
| }; | ||
| const settings = makeMockSettings({ | ||
| model: { name: 'settings-model' }, | ||
| modelProviders: { | ||
| [AuthType.USE_OPENAI]: [ | ||
| { id: 'settings-model', name: 'Settings Model' }, | ||
| modelProvider, | ||
| ], | ||
| }, | ||
| }); | ||
| const selectedAuthType = AuthType.USE_OPENAI; | ||
|
|
||
| vi.mocked(resolveModelConfig).mockReturnValue({ | ||
| config: { | ||
| model: 'env-openai-model', | ||
| apiKey: '', | ||
| baseUrl: '', | ||
| }, | ||
| sources: {}, | ||
| warnings: [], | ||
| }); | ||
|
|
||
| resolveCliGenerationConfig({ | ||
| argv, | ||
| settings, | ||
| selectedAuthType, | ||
| env: { OPENAI_MODEL: 'env-openai-model' }, | ||
| }); | ||
|
|
||
| expect(vi.mocked(resolveModelConfig)).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| modelProvider, | ||
| }), | ||
| ); | ||
| }); | ||
|
|
||
| it('should find modelProvider from QWEN_MODEL when OPENAI_MODEL is not provided', () => { | ||
| const argv = {}; | ||
| const modelProvider: ProviderModelConfig = { | ||
| id: 'qwen-env-model', | ||
| name: 'Qwen Env Model', | ||
| generationConfig: { | ||
| samplingParams: { temperature: 0.7 }, | ||
| }, | ||
| }; | ||
| const settings = makeMockSettings({ | ||
| model: { name: 'settings-model' }, | ||
| modelProviders: { | ||
| [AuthType.USE_OPENAI]: [ | ||
| { id: 'settings-model', name: 'Settings Model' }, | ||
| modelProvider, | ||
| ], | ||
| }, | ||
| }); | ||
| const selectedAuthType = AuthType.USE_OPENAI; | ||
|
|
||
| vi.mocked(resolveModelConfig).mockReturnValue({ | ||
| config: { | ||
| model: 'qwen-env-model', | ||
| apiKey: '', | ||
| baseUrl: '', | ||
| }, | ||
| sources: {}, | ||
| warnings: [], | ||
| }); | ||
|
|
||
| resolveCliGenerationConfig({ | ||
| argv, | ||
| settings, | ||
| selectedAuthType, | ||
| env: { QWEN_MODEL: 'qwen-env-model' }, | ||
| }); | ||
|
|
||
| expect(vi.mocked(resolveModelConfig)).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| modelProvider, | ||
| }), | ||
| ); | ||
| }); | ||
|
|
||
| it('should prefer OPENAI_MODEL over QWEN_MODEL and settings.model.name for USE_OPENAI provider lookup', () => { | ||
| const argv = {}; | ||
| const openAIProvider: ProviderModelConfig = { | ||
| id: 'openai-env-model', | ||
| name: 'OpenAI Env Model', | ||
| }; | ||
| const qwenProvider: ProviderModelConfig = { | ||
| id: 'qwen-env-model', | ||
| name: 'Qwen Env Model', | ||
| }; | ||
| const settings = makeMockSettings({ | ||
| model: { name: 'settings-model' }, | ||
| modelProviders: { | ||
| [AuthType.USE_OPENAI]: [ | ||
| { id: 'settings-model', name: 'Settings Model' }, | ||
| qwenProvider, | ||
| openAIProvider, | ||
| ], | ||
| }, | ||
| }); | ||
| const selectedAuthType = AuthType.USE_OPENAI; | ||
|
|
||
| vi.mocked(resolveModelConfig).mockReturnValue({ | ||
| config: { | ||
| model: 'openai-env-model', | ||
| apiKey: '', | ||
| baseUrl: '', | ||
| }, | ||
| sources: {}, | ||
| warnings: [], | ||
| }); | ||
|
|
||
| resolveCliGenerationConfig({ | ||
| argv, | ||
| settings, | ||
| selectedAuthType, | ||
| env: { | ||
| OPENAI_MODEL: 'openai-env-model', | ||
| QWEN_MODEL: 'qwen-env-model', | ||
| }, | ||
| }); | ||
|
|
||
| expect(vi.mocked(resolveModelConfig)).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| modelProvider: openAIProvider, | ||
| }), | ||
| ); | ||
| }); | ||
|
|
||
| it('should ignore OPENAI_MODEL for non-USE_OPENAI provider lookup', () => { | ||
| const argv = {}; | ||
| const settingsModelProvider: ProviderModelConfig = { | ||
| id: 'settings-model', | ||
| name: 'Settings Model', | ||
| }; | ||
| const unrelatedOpenAIProvider: ProviderModelConfig = { | ||
| id: 'openai-env-model', | ||
| name: 'OpenAI Env Model', | ||
| }; | ||
| const settings = makeMockSettings({ | ||
| model: { name: 'settings-model' }, | ||
| modelProviders: { | ||
| [AuthType.USE_ANTHROPIC]: [ | ||
| settingsModelProvider, | ||
| unrelatedOpenAIProvider, | ||
| ], | ||
| }, | ||
| }); | ||
| const selectedAuthType = AuthType.USE_ANTHROPIC; | ||
|
|
||
| vi.mocked(resolveModelConfig).mockReturnValue({ | ||
| config: { | ||
| model: 'settings-model', | ||
| apiKey: '', | ||
| baseUrl: '', | ||
| }, | ||
| sources: {}, | ||
| warnings: [], | ||
| }); | ||
|
|
||
| resolveCliGenerationConfig({ | ||
| argv, | ||
| settings, | ||
| selectedAuthType, | ||
| env: { OPENAI_MODEL: 'openai-env-model' }, | ||
| }); | ||
|
|
||
| expect(vi.mocked(resolveModelConfig)).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| modelProvider: settingsModelProvider, | ||
| }), | ||
| ); | ||
| }); | ||
| it('should not find modelProvider when authType is undefined', () => { | ||
| const argv = { model: 'test-model' }; | ||
| const settings = makeMockSettings({ | ||
|
|
@@ -723,5 +904,71 @@ describe('modelConfigUtils', () => { | |
| }), | ||
| ); | ||
| }); | ||
|
|
||
| it('should respect precedence: argv.model > OPENAI_MODEL > QWEN_MODEL > settings.model.name', () => { | ||
| const mockSettings = makeMockSettings({ | ||
| model: { name: 'settings-model' }, | ||
| modelProviders: { | ||
| [AuthType.USE_OPENAI]: [ | ||
| { id: 'settings-model' } as ProviderModelConfig, | ||
| { id: 'openai-env-model' } as ProviderModelConfig, | ||
| { id: 'qwen-env-model' } as ProviderModelConfig, | ||
| { id: 'cli-model' } as ProviderModelConfig, | ||
| ], | ||
| }, | ||
| }); | ||
|
|
||
| vi.mocked(resolveModelConfig).mockReturnValue({ | ||
| config: { model: 'cli-model', apiKey: '', baseUrl: '' }, | ||
| sources: {}, | ||
| warnings: [], | ||
| }); | ||
| const result1 = resolveCliGenerationConfig({ | ||
| argv: { model: 'cli-model' }, | ||
| settings: mockSettings, | ||
| selectedAuthType: AuthType.USE_OPENAI, | ||
| env: { OPENAI_MODEL: 'openai-env-model' }, | ||
| }); | ||
| expect(result1.model).toBe('cli-model'); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] This test only asserts the mocked Please assert on the — gpt-5.4 via Qwen Code /review |
||
|
|
||
| vi.mocked(resolveModelConfig).mockReturnValue({ | ||
| config: { model: 'openai-env-model', apiKey: '', baseUrl: '' }, | ||
| sources: {}, | ||
| warnings: [], | ||
| }); | ||
| const result2 = resolveCliGenerationConfig({ | ||
| argv: {}, | ||
| settings: mockSettings, | ||
| selectedAuthType: AuthType.USE_OPENAI, | ||
| env: { OPENAI_MODEL: 'openai-env-model', QWEN_MODEL: 'qwen-env-model' }, | ||
| }); | ||
| expect(result2.model).toBe('openai-env-model'); | ||
|
|
||
| vi.mocked(resolveModelConfig).mockReturnValue({ | ||
| config: { model: 'openai-env-model', apiKey: '', baseUrl: '' }, | ||
| sources: {}, | ||
| warnings: [], | ||
| }); | ||
| const result3 = resolveCliGenerationConfig({ | ||
| argv: {}, | ||
| settings: mockSettings, | ||
| selectedAuthType: AuthType.USE_OPENAI, | ||
| env: { OPENAI_MODEL: 'openai-env-model' }, | ||
| }); | ||
| expect(result3.model).toBe('openai-env-model'); | ||
|
|
||
| vi.mocked(resolveModelConfig).mockReturnValue({ | ||
| config: { model: 'settings-model', apiKey: '', baseUrl: '' }, | ||
| sources: {}, | ||
| warnings: [], | ||
| }); | ||
| const result4 = resolveCliGenerationConfig({ | ||
| argv: {}, | ||
| settings: mockSettings, | ||
| selectedAuthType: AuthType.USE_OPENAI, | ||
| env: {}, | ||
| }); | ||
| expect(result4.model).toBe('settings-model'); | ||
| }); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Suggestion] The test name says it validates the full precedence chain
argv.model > OPENAI_MODEL > QWEN_MODEL > settings.model.name, but the added cases never exercise theQWEN_MODEL-only fallback path. They cover CLI override,OPENAI_MODELoverQWEN_MODEL,OPENAI_MODELalone, and settings fallback, but not the branch whereOPENAI_MODELis absent andQWEN_MODELshould select the provider.A regression that removes or reorders the
QWEN_MODELfallback would not be caught despite this test claiming to cover the full precedence chain. Please add a case withenv: { QWEN_MODEL: 'qwen-env-model' }and noOPENAI_MODEL, expectingqwen-env-model, or narrow the test name to match the cases actually covered.— gpt-5.5 via Qwen Code /review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I missed the QWEN_MODEL-only fallback branch. I’ll add a regression test for env: { QWEN_MODEL: ... } with no OPENAI_MODEL so the suite actually covers the full precedence chain.