Add AWS Bedrock three-tier gateway routing (LiteLLM via Colin)

Routes AI tools across three tiers based on task complexity:
- Azure GPT-4o-mini always: redact, translate, timeline-basic, search-legal (mechanical tasks)
- Claude Haiku 4.5 (Bedrock): ask, summarize, timeline-deep, citations (Norwegian nuance)
- Claude Sonnet 4.6 (Bedrock): korrespond, legal-analysis, deep-research, barnevernet-analyze,
  discrepancy-find, advocate (public-facing legal output)

No AWS credentials in app — credentials live in LiteLLM on Colin (same as nova-lite).
Rollback: DBN_BEDROCK_ENABLED=false in .env, no code push needed.

Includes extended thinking support for Pro deep-research via chatWithThinking().
Claude Opus 4.7 constant added for future premium tier (needs litellm_config.yaml entry).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 15:22:48 +02:00
parent 17ad54cf36
commit 8a11001bff
11 changed files with 520 additions and 43 deletions
+89
View File
@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/bootstrap.php';
/**
* Maps tool names × user tiers to LiteLLM model names for Bedrock Claude.
*
* These names must match the model_name keys in Colin's litellm_config.yaml.
* AWS credentials live only in LiteLLM — not in .env, not here.
*
* Both models are already in Colin's litellm_config.yaml:
* claude-sonnet-bedrock → bedrock/eu.anthropic.claude-sonnet-4-6 (Claude Sonnet 4.6)
* claude-haiku-bedrock → bedrock/eu.anthropic.claude-haiku-4-5-20251001-v1:0 (Claude Haiku 4.5)
* AWS IAM key: AKIA46PTFRQF2CQ47ANX (bnl-bedrock user, AmazonBedrockFullAccess)
*/
final class DbnBedrockModelRouter
{
// LiteLLM model name constants (must match litellm_config.yaml on Colin)
public const LITELLM_SONNET = 'claude-sonnet-bedrock';
public const LITELLM_HAIKU = 'claude-haiku-bedrock';
// Opus — future premium tier; add 'claude-opus-bedrock' to litellm_config.yaml on Colin first
public const LITELLM_OPUS = 'claude-opus-bedrock';
// Actual Bedrock model IDs routed by LiteLLM (for reference)
public const BEDROCK_SONNET = 'eu.anthropic.claude-sonnet-4-6';
public const BEDROCK_HAIKU = 'eu.anthropic.claude-haiku-4-5-20251001-v1:0';
public const BEDROCK_OPUS = 'eu.anthropic.claude-opus-4-7'; // not yet in litellm_config.yaml
// Models that support extended thinking (via LiteLLM thinking param passthrough)
private const THINKING_MODELS = [
self::LITELLM_SONNET,
// self::LITELLM_OPUS, // uncomment after claude-opus-bedrock added to litellm_config.yaml
];
// Tools pinned to Azure GPT-4o-mini regardless of DBN_BEDROCK_ENABLED.
// These are mechanical/structural — regex, date extraction, translation — no quality gain from Claude.
private const AZURE_PINNED = ['redact', 'translate', 'timeline', 'search-legal', 'search'];
// Tools routed to Claude Haiku 4.5 (fast, good Norwegian comprehension, 4x cheaper than Sonnet).
// Pro users escalate to Sonnet.
private const HAIKU_TOOLS = ['ask', 'summarize', 'timeline-deep', 'citations'];
/**
* Returns ['gateway' => 'azure'|'bedrock', 'model' => string|null].
* gateway='azure' means always use Azure regardless of DBN_BEDROCK_ENABLED.
* gateway='bedrock' means use Bedrock when enabled, fall back to Azure when not.
* model is null for azure-pinned tools (caller uses DbnAzureOpenAiGateway directly).
*/
public static function routeForTool(string $tool, string $tier = 'free'): array
{
$tier = in_array($tier, ['free', 'plus', 'pro'], true) ? $tier : 'free';
if (in_array($tool, self::AZURE_PINNED, true)) {
return ['gateway' => 'azure', 'model' => null];
}
if (in_array($tool, self::HAIKU_TOOLS, true)) {
// Pro users get Sonnet for these tools; free/plus get Haiku
$model = ($tier === 'pro') ? self::LITELLM_SONNET : self::LITELLM_HAIKU;
return ['gateway' => 'bedrock', 'model' => $model];
}
// All drafting/reasoning tools → Sonnet (korrespond, legal-analysis, deep-research,
// barnevernet-analyze, discrepancy-find, advocate)
return ['gateway' => 'bedrock', 'model' => self::LITELLM_SONNET];
}
/** @deprecated Use routeForTool() — kept for any direct callers outside the factory. */
public static function modelForTool(string $tool, string $tier = 'free'): string
{
$route = self::routeForTool($tool, $tier);
return $route['model'] ?? self::LITELLM_SONNET;
}
public static function supportsThinking(string $modelName): bool
{
return in_array($modelName, self::THINKING_MODELS, true);
}
public static function maxTokensForTool(string $tool): int
{
return match ($tool) {
'deep-research', 'barnevernet-analyze', 'advocate' => 4000,
'legal-analysis', 'korrespond', 'discrepancy-find' => 3000,
default => 2000,
};
}
}