Fixing reasoning on Qwen3

This commit is contained in:
Glen Maddern 2025-07-11 15:51:32 +10:00
commit 5b54994da7
7 changed files with 48 additions and 76 deletions

View file

@ -455,7 +455,7 @@
"id": "qwen/qwen3-32b",
"name": "Qwen3 32B",
"attachment": false,
"reasoning": false,
"reasoning": true,
"temperature": true,
"tool_call": true,
"knowledge": "2024-11-08",

View file

@ -1,5 +1,5 @@
import { useState, useEffect, useCallback } from 'react'
import { Model, providers, SupportedProvider, SUPPORTED_PROVIDERS, FAVORITES_KEY } from '../types/models'
import { Model, providers, SUPPORTED_PROVIDERS, FAVORITES_KEY } from '../types/models'
// Load models data from generated JSON
import modelsData from '../data/models.json'
@ -31,7 +31,6 @@ export function useModels() {
contextLimit: modelData.limit.context,
outputLimit: modelData.limit.output,
cost: modelData.cost,
providerOptions: getProviderOptions(providerId, modelId),
}
allModels.push(model)
}
@ -114,27 +113,3 @@ export function useModels() {
getToolSupportingModels,
}
}
// Helper function to get provider-specific options
function getProviderOptions(providerId: SupportedProvider, modelId: string) {
// Add any provider-specific options here
switch (providerId) {
case 'groq':
// Handle specific Groq model options
if (modelId === 'qwen/qwen3-32b') {
return {
groq: {
reasoningFormat: 'parsed',
},
}
}
break
case 'anthropic':
// Handle specific Anthropic model options
break
case 'openrouter':
// Handle specific OpenRouter model options
break
}
return undefined
}

View file

@ -8,6 +8,7 @@ import { type Model } from '../types/models'
import { getAuthHeaders } from '../utils/auth'
import { type Tool } from 'use-mcp/react'
import { useConversationUpdater } from './useConversationUpdater'
import { getProviderOptions } from '../utils/modelOptions.ts'
// Type guard for messages with content
const hasContent = (message: Message): message is UserMessage | AssistantMessage | SystemMessage => {
@ -166,7 +167,7 @@ export const useStreamResponse = ({
tools: Object.keys(aiTools).length > 0 ? aiTools : undefined,
maxSteps: 5, // Allow up to 5 steps for tool calling
abortSignal: controller.signal,
...selectedModel.providerOptions,
providerOptions: getProviderOptions(selectedModel.provider.id, selectedModel.modelId),
})
// Use fullStream to get all events including tool calls, results, and text
@ -229,19 +230,13 @@ export const useStreamResponse = ({
} else {
console.warn('Tool call event missing toolName:', event)
}
// @ts-expect-error I have no idea why this is suddenly failing...
} else if (event.type === 'tool-result') {
// @ts-expect-error wat
if (event.toolName && event.toolCallId) {
newMessage = {
role: 'tool-result',
// @ts-expect-error the
toolName: event.toolName,
// @ts-expect-error actual
toolArgs: event.args || {},
// @ts-expect-error goddamn
toolResult: event.result,
// @ts-expect-error fek
callId: event.toolCallId,
}
scrollToBottom(true)

View file

@ -39,8 +39,6 @@ export interface Provider {
oauth?: {
authorizeUrl: string
tokenUrl: string
clientId: string
scopes: string[]
}
}
@ -60,7 +58,6 @@ export interface Model {
cache_read?: number
cache_write?: number
}
providerOptions?: any
}
export const providers: Record<SupportedProvider, Provider> = {
@ -72,6 +69,10 @@ export const providers: Record<SupportedProvider, Provider> = {
documentationUrl: 'https://console.groq.com/docs',
authType: 'apiKey',
apiKeyHeader: 'Authorization',
// oauth: {
// authorizeUrl: 'http://localhost:3000/keys/request',
// tokenUrl: 'https://openrouter.ai/api/v1/auth/keys'
// },
},
anthropic: {
id: 'anthropic',
@ -92,8 +93,6 @@ export const providers: Record<SupportedProvider, Provider> = {
oauth: {
authorizeUrl: 'https://openrouter.ai/auth',
tokenUrl: 'https://openrouter.ai/api/v1/auth/keys',
clientId: '', // OpenRouter doesn't use client_id
scopes: [], // OpenRouter doesn't use scopes
},
},
}

View file

@ -122,22 +122,16 @@ export async function beginOAuthFlow(providerId: SupportedProvider): Promise<voi
let authUrl: URL
if (providerId === 'openrouter') {
// OpenRouter uses a different OAuth flow
// OpenRouter uses a different redirect name flow
authUrl = new URL(provider.oauth.authorizeUrl)
authUrl.searchParams.set('callback_url', getRedirectUri(providerId))
authUrl.searchParams.set('code_challenge', codeChallenge)
authUrl.searchParams.set('code_challenge_method', 'S256')
} else {
// Standard OAuth2 flow for other providers
// Groq uses redirect_uri
authUrl = new URL(provider.oauth.authorizeUrl)
authUrl.searchParams.set('client_id', provider.oauth.clientId)
authUrl.searchParams.set('redirect_uri', getRedirectUri(providerId))
authUrl.searchParams.set('response_type', 'code')
authUrl.searchParams.set('scope', provider.oauth.scopes.join(' '))
authUrl.searchParams.set('state', state)
authUrl.searchParams.set('code_challenge', codeChallenge)
authUrl.searchParams.set('code_challenge_method', 'S256')
}
authUrl.searchParams.set('code_challenge', codeChallenge)
authUrl.searchParams.set('code_challenge_method', 'S256')
// Open popup or redirect
const popup = window.open(authUrl.toString(), `oauth_${providerId}`, 'width=600,height=700')
@ -227,7 +221,6 @@ export async function completeOAuthFlow(providerId: SupportedProvider, code: str
// Standard OAuth2 flow for other providers
const requestBody = new URLSearchParams({
grant_type: 'authorization_code',
client_id: provider.oauth.clientId,
code,
redirect_uri: getRedirectUri(providerId),
code_verifier: pkceState.code_verifier,

View file

@ -0,0 +1,35 @@
import { type SupportedProvider } from '../types/models'
import modelsData from '../data/models.json'
// Helper function to check if a model supports reasoning
export function supportsReasoning(providerId: SupportedProvider, modelId: string): boolean {
const providerModels = modelsData[providerId] as Record<string, any>
if (!providerModels) return false
const model = providerModels[modelId]
return model?.reasoning === true
}
// Helper function to get provider-specific options
export function getProviderOptions(providerId: SupportedProvider, modelId: string): Record<string, any> | undefined {
// Add any provider-specific options here
switch (providerId) {
case 'groq':
// Handle specific Groq model options - set reasoningFormat for all reasoning models
if (supportsReasoning(providerId, modelId)) {
return {
groq: {
reasoningFormat: 'parsed',
},
}
}
break
case 'anthropic':
// Handle specific Anthropic model options
break
case 'openrouter':
// Handle specific OpenRouter model options
break
}
return undefined
}

View file

@ -1,32 +1,8 @@
import { type Model, providers, type SupportedProvider } from '../types/models'
import { type Model, providers } from '../types/models'
// Import models data directly
import modelsData from '../data/models.json'
// Helper function to get provider-specific options
function getProviderOptions(providerId: SupportedProvider, modelId: string) {
// Add any provider-specific options here
switch (providerId) {
case 'groq':
// Handle specific Groq model options
if (modelId === 'qwen/qwen3-32b') {
return {
groq: {
reasoningFormat: 'parsed',
},
}
}
break
case 'anthropic':
// Handle specific Anthropic model options
break
case 'openrouter':
// Handle specific OpenRouter model options
break
}
return undefined
}
const MODEL_PREFERENCE_KEY = 'aiChatTemplate_selectedModel'
function getAvailableModels(): Model[] {
@ -47,7 +23,6 @@ function getAvailableModels(): Model[] {
contextLimit: modelData.limit.context,
outputLimit: modelData.limit.output,
cost: modelData.cost,
providerOptions: getProviderOptions(providerId as any, modelId),
}
models.push(model)
}