diff --git a/components/chat/chat-page.tsx b/components/chat/chat-page.tsx index fd28134..397e94f 100644 --- a/components/chat/chat-page.tsx +++ b/components/chat/chat-page.tsx @@ -231,7 +231,7 @@ export function ChatPage() { category: "gateway", hasModels: true, id: AUTO_PROVIDER_ID, - label: "Auto · Smart Routing", + label: "ModelHub", localModels: [AUTO_MODEL], runtime: { authMode: "none", @@ -1224,7 +1224,7 @@ export function ChatPage() { Roteamento - Auto · Smart Routing + ModelHub diff --git a/server/lib/routing/complexity-scorer.test.ts b/server/lib/routing/complexity-scorer.test.ts index e80ec0b..957cd41 100644 --- a/server/lib/routing/complexity-scorer.test.ts +++ b/server/lib/routing/complexity-scorer.test.ts @@ -15,11 +15,25 @@ describe('scoreComplexity', () => { expect(result.rawScore).toBe(0) }) - it('returns standard tier for medium-length question', () => { + it('returns standard tier for a code generation request', () => { const result = scoreComplexity(msg( - 'Can you write a short Python function to reverse a string and explain what it does?' + 'Faça um exemplo em Python de pelo menos 10 linhas que envolva bastante custo computacional para executar.' )) - expect(['simple', 'standard']).toContain(result.tier) + expect(result.tier).toBe('standard') + }) + + it('returns standard tier for a comparison with recommendation', () => { + const result = scoreComplexity(msg( + 'Compare REST e GraphQL para uma aplicação SaaS pequena. Dê prós, contras e recomendação.' + )) + expect(result.tier).toBe('standard') + }) + + it('returns complex tier for a multi-component architecture request', () => { + const result = scoreComplexity(msg( + 'Desenhe uma arquitetura para um gateway multi-provider de IA com autenticação, rate limit, fallback, logs de uso e dashboard. Liste componentes, fluxos e riscos.' + )) + expect(result.tier).toBe('complex') }) it('detects code_block signal for fenced code', () => { diff --git a/server/lib/routing/complexity-scorer.ts b/server/lib/routing/complexity-scorer.ts index 1da0d51..4c16ed4 100644 --- a/server/lib/routing/complexity-scorer.ts +++ b/server/lib/routing/complexity-scorer.ts @@ -85,6 +85,8 @@ const MATH_PATTERNS = [ const CODE_PATTERN = /```[\s\S]{20,}/ const INLINE_CODE_PATTERN = /`[^`]+`/g +const CODE_REQUEST_PATTERN = /\b(c[oó]digo|code|python|typescript|javascript|fun[cç][aã]o|function|script|programa|implemente|implement|escreva|write)\b/i +const ARCHITECTURE_REQUEST_PATTERN = /\b(arquitetura|architecture|architect|design a system)\b/i const PLANNING_KEYWORDS = [ 'design a system', 'architect', 'design pattern', 'best approach', 'best practice', @@ -230,6 +232,22 @@ export function scoreComplexity( signals.push('short_message') } + // Pedidos de código e análise comparativa exigem ao menos o tier standard. + if ( + !signals.includes('heartbeat') && + (CODE_REQUEST_PATTERN.test(lastText) || reasoningMatch) + ) { + tier = floorTier(tier, 'standard') + signals.push('standard_task_floor') + } + + // Arquitetura com múltiplos requisitos exige composição e análise sistêmica. + const requirementCount = (lastText.match(/[,;]/g) ?? []).length + if (ARCHITECTURE_REQUEST_PATTERN.test(lastText) && requirementCount >= 3) { + tier = floorTier(tier, 'complex') + signals.push('architecture_floor') + } + // Tools ativas exigem um modelo que saiba usá-las → piso "standard". if (options.hasTools && tier === 'simple' && !signals.includes('heartbeat')) { tier = 'standard' diff --git a/server/routes/v1.ts b/server/routes/v1.ts index 78a0fda..aefeb35 100644 --- a/server/routes/v1.ts +++ b/server/routes/v1.ts @@ -42,7 +42,18 @@ async function resolveAutoRouting( if (!userId) return null const messages = Array.isArray(body.messages) - ? (body.messages as Array<{ role: string; content: unknown }>) + ? (body.messages as Array<{ role?: unknown; content?: unknown; parts?: unknown }>).map((message) => ({ + role: typeof message.role === 'string' ? message.role : '', + content: message.content ?? (Array.isArray(message.parts) + ? message.parts + .filter((part): part is { text: string; type: string } => + typeof part === 'object' && part !== null && + 'type' in part && part.type === 'text' && + 'text' in part && typeof part.text === 'string') + .map((part) => part.text) + .join('\n') + : ''), + })) : [] const tools = Array.isArray(body.tools) diff --git a/server/tests/v1-auto-routing.test.ts b/server/tests/v1-auto-routing.test.ts index 4c5fd89..c8f03d8 100644 --- a/server/tests/v1-auto-routing.test.ts +++ b/server/tests/v1-auto-routing.test.ts @@ -71,7 +71,7 @@ describe('POST /v1/chat/completions auto routing', () => { it('authenticates the web session and dispatches the model selected by routing', async () => { const response = await v1Fetch(new Request('https://modelhub.test/v1/chat/completions', { body: JSON.stringify({ - messages: [{ content: 'hello', role: 'user' }], + messages: [{ parts: [{ text: 'hello', type: 'text' }], role: 'user' }], model: 'auto', }), headers: { 'content-type': 'application/json' },