diff --git a/.osc-metadata/sync.json b/.osc-metadata/sync.json new file mode 100644 index 00000000..62fdbcaa --- /dev/null +++ b/.osc-metadata/sync.json @@ -0,0 +1,5 @@ +{ + "fork_synced_at": "2026-07-12T08:08:26.930714+00:00", + "commits_behind_before_sync": 0, + "action_taken": "noop" +} \ No newline at end of file diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index c7de122e..446ace2d 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -16,6 +16,7 @@ import { getAIModel, SINGLE_SYSTEM_PROVIDERS, supportsPromptCaching, + supportsTemperature, } from "@/lib/ai-providers" import { findCachedResponse } from "@/lib/cached-responses" import { @@ -254,6 +255,16 @@ async function handleChatRequest(req: Request): Promise { `[Prompt Caching] ${shouldCache ? "ENABLED" : "DISABLED"} for model: ${modelId}`, ) + const configuredTemperature = process.env.TEMPERATURE + const temperature = supportsTemperature(modelId) + ? configuredTemperature + : undefined + if (configuredTemperature !== undefined && temperature === undefined) { + console.log( + `[Temperature] SKIPPED for model: ${modelId} (parameter is not supported)`, + ) + } + // Get the appropriate system prompt based on model (extended for Opus/Haiku 4.5) const systemMessage = getSystemPrompt(modelId, minimalStyle) const finalSystemMessage = customSystemMessage @@ -762,8 +773,8 @@ Call this tool to get shape names and usage syntax for a specific library.`, }, }, }, - ...(process.env.TEMPERATURE !== undefined && { - temperature: parseFloat(process.env.TEMPERATURE), + ...(temperature !== undefined && { + temperature: parseFloat(temperature), }), }) diff --git a/env.example b/env.example index d1fafb5c..d59ee91e 100644 --- a/env.example +++ b/env.example @@ -118,7 +118,8 @@ AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0 # Temperature (Optional) # Controls randomness in AI responses. Lower = more deterministic. -# Leave unset for models that don't support temperature (e.g., GPT-5.1 reasoning models) +# Automatically ignored for Claude Opus 4.7/4.8 models that deprecate temperature. +# Leave unset for other models that don't support it (e.g., GPT-5.1 reasoning models) # TEMPERATURE=0 # Access Control (Optional) diff --git a/lib/ai-providers.ts b/lib/ai-providers.ts index 61e19e7b..07f7b2fe 100644 --- a/lib/ai-providers.ts +++ b/lib/ai-providers.ts @@ -1440,6 +1440,15 @@ export function supportsPromptCaching(modelId: string): boolean { ) } +/** + * Check if a model supports the temperature sampling parameter. + * Claude Opus 4.7 and 4.8 reject sampling parameters entirely. + */ +export function supportsTemperature(modelId: string): boolean { + // Match plain, Bedrock inference-profile, and version-suffixed model IDs. + return !/claude-opus-4[.-](?:7|8)(?=$|[-.:])/i.test(modelId) +} + /** * Get the AI model for diagram validation. * Uses VALIDATION_MODEL env var if set, otherwise falls back to AI_MODEL. diff --git a/tests/unit/ai-providers.test.ts b/tests/unit/ai-providers.test.ts index 826cbb00..293dc440 100644 --- a/tests/unit/ai-providers.test.ts +++ b/tests/unit/ai-providers.test.ts @@ -4,6 +4,7 @@ import { isAihubmixStandardBaseURL, resolveBaseURL, supportsPromptCaching, + supportsTemperature, } from "@/lib/ai-providers" import { extractAihubmixModelIds } from "@/lib/aihubmix-models" @@ -182,6 +183,30 @@ describe("supportsPromptCaching", () => { }) }) +describe("supportsTemperature", () => { + it.each([ + "claude-opus-4-7-20260416", + "anthropic.claude-opus-4-7-v1:0", + "us.anthropic.claude-opus-4-8-20260528-v1:0", + "global.anthropic.claude-opus-4-8", + "anthropic/claude-opus-4.7", + "anthropic/claude-opus-4.8", + ])("returns false for Claude Opus models without temperature (%s)", (modelId) => { + expect(supportsTemperature(modelId)).toBe(false) + }) + + it.each([ + "claude-sonnet-4-5", + "anthropic.claude-3-5-sonnet-20241022-v2:0", + "gpt-4o", + "gemini-2.0-flash", + "", + "unknown-model", + ])("returns true for models that may support temperature (%s)", (modelId) => { + expect(supportsTemperature(modelId)).toBe(true) + }) +}) + vi.mock("ollama-ai-provider-v2", () => { const mockModel = { modelId: "test-model" } const mockProviderFn = vi.fn(() => mockModel)