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' },