Switch chat agents to GPT-5.6 Luna (max reasoning) (#499)

This commit is contained in:
Ben Senescu 2026-08-26 16:39:51 -04:00 committed by GitHub
parent 61b32ec647
commit 4d7fb661f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 40 deletions

View File

@ -171,11 +171,12 @@ export class OnboardingChatAgent extends AIChatAgent {
// Cancel the (billable) LLM call if the user aborts/navigates away.
abortSignal: options?.abortSignal,
// Budget shared by reasoning + visible output. Reasoning tokens (enabled
// on the model) eat into this, so it's well above what the ~350-word
// strategy needs — otherwise the answer truncates mid-table once the
// model has spent the budget thinking. It's a ceiling, not a target: the
// model only generates (and we only bill) what it actually uses.
maxOutputTokens: 4000,
// on the model) eat into this, and at max effort they dwarf the ~350-word
// strategy — the old 4000 cap left almost no answer headroom and
// truncated mid-table once the model had spent the budget thinking.
// Deliberately roomy: it's a per-step ceiling, not a target — the model
// only generates (and we only bill) what it actually uses.
maxOutputTokens: 32_000,
stopWhen: stepCountIs(5),
// Meter LLM spend against the same credit pool as DataForSEO: sum the real
// per-step cost OpenRouter reports and deduct it. Best-effort, hosted-only.

View File

@ -302,9 +302,13 @@ export class SamChatAgent extends Think {
// SAM is meant to run complex multi-step work in one turn (site-read
// intake plus a full research chain, multi-competitor sweeps), so give
// it generous headroom — cost is bounded by per-step metering and the
// model stopping on its own, not by this cap.
// model stopping on its own, not by this cap. The per-step budget is
// shared by max-effort reasoning + visible output; a tight cap risks
// reasoning eating the reply (the issue #161 failure mode), so it's
// deliberately roomy — ~10x measured reasoning use — while keeping the
// worst-case turn (48 steps at the full cap) under ~$2.
maxSteps: 48,
maxOutputTokens: 6000,
maxOutputTokens: 32_000,
};
});
}

View File

@ -9,30 +9,12 @@ import {
// OpenRouter model slug used for the in-app chat agents (onboarding + SAM).
// Override with OPENROUTER_MODEL to swap models without a code change.
const DEFAULT_CHAT_AGENT_MODEL = "minimax/minimax-m3";
const DEFAULT_CHAT_AGENT_MODEL = "openai/gpt-5.6-luna";
// Previous default; kept reachable via OPENROUTER_MODEL for rollback. Its
// routing needs the ZDR/provider tuning below.
const MINIMAX_M3 = "minimax/minimax-m3";
/**
* Returns the AI SDK LanguageModel for the chat agents. `usage: { include: true }`
* turns on OpenRouter usage accounting so each response carries its real USD
* cost (providerMetadata.openrouter.usage.cost) which we meter against the
* shared usage-credit pool. `provider.order` prefers Together, then Atlas
* Cloud (fp8); `zdr: true` restricts routing to Zero-Data-Retention endpoints
* (prompts are never retained), which is the actual constraint it excludes
* MiniMax first-party without a hand-maintained allowlist. The account also
* enforces this ("Non-frontier requires ZDR" data policy); the request-level
* flag is belt-and-braces so the constraint survives a dashboard change.
* Fallbacks stay on within the ZDR set because pinning providers caused a
* prod outage (Jul 2026: Together upstream-rate-limited m3 and every chat
* turn 429'd); as of Jul 2026 the ZDR set for m3 is Together/AtlasCloud/
* Novita/Parasail at the same price plus Morph at 2x output as a last resort.
*
* `reasoning` turns on OpenRouter's reasoning-token channel so the model's
* chain-of-thought comes back as a separate reasoning stream instead of
* leaking into the visible answer text (MiniMax M3 otherwise dumps its
* `<think>` trace inline). `effort: "medium"` is OpenRouter's default
* stated explicitly only because the SDK type requires one once the channel
* is configured.
*/
export async function getChatAgentModel(): Promise<LanguageModelV3> {
const apiKey = await getRequiredEnvValue("OPENROUTER_API_KEY");
const modelId = await getOptionalEnvValue("OPENROUTER_MODEL");
@ -40,15 +22,37 @@ export async function getChatAgentModel(): Promise<LanguageModelV3> {
}
/**
* Synchronous variant for callers that already hold the env values. Think's
* `getModel()` hook is sync and runs on every turn, so the SAM agent reads the
* key/model from its DO env and builds the model here.
* Returns the AI SDK LanguageModel for the chat agents. `usage: { include: true }`
* turns on OpenRouter usage accounting so each response carries its real USD
* cost (providerMetadata.openrouter.usage.cost) which we meter against the
* shared usage-credit pool.
*
* Default model: GPT-5.6 Luna at `reasoning.effort: "max"` "max" is valid at
* the OpenRouter API for GPT-5.x but missing from the SDK's effort union, so
* the reasoning config rides in `extraBody`. Reasoning tokens stream on the
* separate reasoning channel and are billed as output tokens, which the usage
* accounting above captures.
*
* Sync on purpose: Think's `getModel()` hook is sync and runs on every turn,
* so the SAM agent reads the key/model from its DO env and builds here.
*/
export function buildChatAgentModel(
apiKey: string,
modelId?: string,
): LanguageModelV3 {
return createOpenRouter({ apiKey })(modelId ?? DEFAULT_CHAT_AGENT_MODEL, {
const model = modelId ?? DEFAULT_CHAT_AGENT_MODEL;
const openrouter = createOpenRouter({ apiKey });
// MiniMax M3 (env-override path only): `provider.order` prefers Together,
// then Atlas Cloud (fp8); `zdr: true` restricts routing to Zero-Data-
// Retention endpoints, which excludes MiniMax first-party — the account's
// "Non-frontier requires ZDR" data policy enforces the same, this flag is
// belt-and-braces. Fallbacks stay on within the ZDR set because pinning
// providers caused a prod outage (Jul 2026: Together upstream-rate-limited
// m3 and every chat turn 429'd). The explicit reasoning channel keeps m3's
// `<think>` trace out of the visible answer text.
if (model === MINIMAX_M3) {
return openrouter(model, {
usage: { include: true },
reasoning: { effort: "medium" },
provider: {
@ -57,4 +61,10 @@ export function buildChatAgentModel(
allow_fallbacks: true,
},
});
}
return openrouter(model, {
usage: { include: true },
extraBody: { reasoning: { effort: "max" } },
});
}