Merge pull request #135 from Askhz/feat/costrict-cheapest-model-for-haiku-tasks
feat(costrict): use cheapest model by creditConsumption for haiku-tier auxiliary tasks
This commit is contained in:
commit
6e8bb0f72d
|
|
@ -3,7 +3,7 @@
|
|||
* 将 Anthropic 模型名映射到 CoStrict 模型名
|
||||
*/
|
||||
|
||||
import { getCachedCoStrictModels } from './models.js'
|
||||
import { getCachedCoStrictModels, getCheapestCoStrictModel } from './models.js'
|
||||
|
||||
function getModelFamily(model: string): 'haiku' | 'sonnet' | 'opus' | null {
|
||||
if (/haiku/i.test(model)) return 'haiku'
|
||||
|
|
@ -34,6 +34,11 @@ export function resolveCoStrictModel(anthropicModel: string): string {
|
|||
const envVar = `COSTRICT_DEFAULT_${family.toUpperCase()}_MODEL`
|
||||
const override = process.env[envVar]
|
||||
if (override) return override
|
||||
// haiku 族无 env var 时,自动选取 creditConsumption 最低的模型
|
||||
if (family === 'haiku') {
|
||||
const cheapest = getCheapestCoStrictModel()
|
||||
if (cheapest) return cheapest
|
||||
}
|
||||
}
|
||||
|
||||
// 优先级 3: 直接透传原始模型名(用户手动配置的模型名优先于环境变量)
|
||||
|
|
|
|||
|
|
@ -90,3 +90,17 @@ export function clearModelCache(): void {
|
|||
export function getCachedCoStrictModels(): CoStrictModel[] {
|
||||
return modelCache?.models ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存模型列表中选取 creditConsumption 最低的模型,
|
||||
* 作为 Anthropic Haiku 的等价物用于轻量级辅助请求。
|
||||
* 缓存为空时返回 undefined(调用方应 fallback 到主模型)。
|
||||
*/
|
||||
export function getCheapestCoStrictModel(): string | undefined {
|
||||
const cached = getCachedCoStrictModels()
|
||||
if (cached.length === 0) return undefined
|
||||
const sorted = [...cached].sort(
|
||||
(a, b) => (a.creditConsumption ?? Infinity) - (b.creditConsumption ?? Infinity),
|
||||
)
|
||||
return sorted[0]?.id
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { feature } from 'bun:bundle'
|
||||
import { logForDebugging } from '../utils/debug.js'
|
||||
import { errorMessage } from '../utils/errors.js'
|
||||
import { getDefaultSonnetModel } from '../utils/model/model.js'
|
||||
import { getDefaultSonnetModel, getSmallFastModel } from '../utils/model/model.js'
|
||||
import { getAPIProvider } from '../utils/model/providers.js'
|
||||
import { sideQuery } from '../utils/sideQuery.js'
|
||||
import type { LangfuseSpan } from '../services/langfuse/index.js'
|
||||
import { jsonParse } from '../utils/slowOperations.js'
|
||||
|
|
@ -99,8 +100,10 @@ async function selectRelevantMemories(
|
|||
: ''
|
||||
|
||||
try {
|
||||
const selectionModel =
|
||||
getAPIProvider() === 'costrict' ? getSmallFastModel() : getDefaultSonnetModel()
|
||||
const result = await sideQuery({
|
||||
model: getDefaultSonnetModel(),
|
||||
model: selectionModel,
|
||||
system: SELECT_MEMORIES_SYSTEM_PROMPT,
|
||||
skipSystemPromptPrefix: true,
|
||||
messages: [
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import {
|
|||
runForkedAgent,
|
||||
} from '../../utils/forkedAgent.js'
|
||||
import { getFsImplementation } from '../../utils/fsOperations.js'
|
||||
import { getSmallFastModel } from '../../utils/model/model.js'
|
||||
import { getAPIProvider } from '../../utils/model/providers.js'
|
||||
import {
|
||||
type REPLHookContext,
|
||||
registerPostSamplingHook,
|
||||
|
|
@ -322,13 +324,19 @@ const extractSessionMemory = sequential(async function (
|
|||
// Run session memory extraction using runForkedAgent for prompt caching
|
||||
// runForkedAgent creates an isolated context to prevent mutation of parent state
|
||||
// Pass setupContext.readFileState so the forked agent can edit the memory file
|
||||
const cacheSafeParams = createCacheSafeParams(context)
|
||||
const smallModel = getSmallFastModel()
|
||||
const modelOverride =
|
||||
getAPIProvider() === 'costrict'
|
||||
? { options: { ...cacheSafeParams.toolUseContext.options, mainLoopModel: smallModel, model: smallModel } }
|
||||
: {}
|
||||
await runForkedAgent({
|
||||
promptMessages: [createUserMessage({ content: userPrompt })],
|
||||
cacheSafeParams: createCacheSafeParams(context),
|
||||
cacheSafeParams,
|
||||
canUseTool: createMemoryFileCanUseTool(memoryPath),
|
||||
querySource: 'session_memory',
|
||||
forkLabel: 'session_memory',
|
||||
overrides: { readFileState: setupContext.readFileState },
|
||||
overrides: { readFileState: setupContext.readFileState, ...modelOverride },
|
||||
})
|
||||
|
||||
// Log extraction event for tracking frequency
|
||||
|
|
@ -424,6 +432,11 @@ export async function manuallyExtractSessionMemory(
|
|||
const systemPrompt = asSystemPrompt(rawSystemPrompt)
|
||||
|
||||
// Run session memory extraction using runForkedAgent
|
||||
const smallModelManual = getSmallFastModel()
|
||||
const modelOverrideManual =
|
||||
getAPIProvider() === 'costrict'
|
||||
? { options: { ...setupContext.options, mainLoopModel: smallModelManual, model: smallModelManual } }
|
||||
: {}
|
||||
await runForkedAgent({
|
||||
promptMessages: [createUserMessage({ content: userPrompt })],
|
||||
cacheSafeParams: {
|
||||
|
|
@ -436,7 +449,7 @@ export async function manuallyExtractSessionMemory(
|
|||
canUseTool: createMemoryFileCanUseTool(memoryPath),
|
||||
querySource: 'session_memory',
|
||||
forkLabel: 'session_memory_manual',
|
||||
overrides: { readFileState: setupContext.readFileState },
|
||||
overrides: { readFileState: setupContext.readFileState, ...modelOverrideManual },
|
||||
})
|
||||
|
||||
// Log manual extraction event
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ import {
|
|||
import { getFeatureValue_CACHED_MAY_BE_STALE } from '../analytics/growthbook.js'
|
||||
import { logEvent } from '../analytics/index.js'
|
||||
import { sanitizeToolNameForAnalytics } from '../analytics/metadata.js'
|
||||
import { getSmallFastModel } from '../../utils/model/model.js'
|
||||
import { getAPIProvider } from '../../utils/model/providers.js'
|
||||
import {
|
||||
buildExtractAutoOnlyPrompt,
|
||||
buildExtractCombinedPrompt,
|
||||
|
|
@ -367,6 +369,11 @@ export function initExtractMemories(): void {
|
|||
|
||||
const canUseTool = createAutoMemCanUseTool(memoryDir)
|
||||
const cacheSafeParams = createCacheSafeParams(context)
|
||||
const smallModel = getSmallFastModel()
|
||||
const modelOverride =
|
||||
getAPIProvider() === 'costrict'
|
||||
? { options: { ...cacheSafeParams.toolUseContext.options, mainLoopModel: smallModel, model: smallModel } }
|
||||
: {}
|
||||
|
||||
// Only run extraction every N eligible turns (tengu_bramble_lintel, default 1).
|
||||
// Trailing extractions (from stashed contexts) skip this check since they
|
||||
|
|
@ -415,12 +422,9 @@ export function initExtractMemories(): void {
|
|||
canUseTool,
|
||||
querySource: 'extract_memories',
|
||||
forkLabel: 'extract_memories',
|
||||
// The extractMemories subagent does not need to record to transcript.
|
||||
// Doing so can create race conditions with the main thread.
|
||||
skipTranscript: true,
|
||||
// Well-behaved extractions complete in 2-4 turns (read → write).
|
||||
// A hard cap prevents verification rabbit-holes from burning turns.
|
||||
maxTurns: 5,
|
||||
overrides: modelOverride,
|
||||
})
|
||||
|
||||
// Advance the cursor only after a successful run. If the agent errors
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ export type ModelSetting = ModelName | ModelAlias | null
|
|||
|
||||
export function getSmallFastModel(): ModelName {
|
||||
const provider = getAPIProvider()
|
||||
// CoStrict has no haiku alias — use the main loop model to stay on the same provider
|
||||
if (provider === 'costrict') return getMainLoopModel()
|
||||
// CoStrict has no haiku alias — resolve to cheapest available model by creditConsumption
|
||||
if (provider === 'costrict') return getDefaultHaikuModel()
|
||||
// Provider-specific small fast model
|
||||
if (provider === 'openai' && process.env.OPENAI_SMALL_FAST_MODEL) {
|
||||
return process.env.OPENAI_SMALL_FAST_MODEL
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import { getOpenAIClient } from '../services/api/openai/client.js'
|
|||
import { resolveOpenAIModel } from '@ant/model-provider'
|
||||
import { createCoStrictFetch } from '../costrict/provider/fetch.js'
|
||||
import { resolveCoStrictModel } from '../costrict/provider/modelMapping.js'
|
||||
import { getCachedCoStrictModels } from '../costrict/provider/models.js'
|
||||
import { getCoStrictBaseURL } from '../costrict/provider/auth.js'
|
||||
import { loadCoStrictCredentials } from '../costrict/provider/credentials.js'
|
||||
import { getProxyFetchOptions } from './proxy.js'
|
||||
|
|
@ -157,7 +158,14 @@ export async function sideQuery(opts: SideQueryOptions): Promise<BetaMessage> {
|
|||
fetch: createCoStrictFetch() as unknown as typeof fetch,
|
||||
defaultHeaders: { 'User-Agent': getUserAgent() },
|
||||
})
|
||||
return sideQueryOpenAICompat(opts, client, resolveCoStrictModel(getMainLoopModel()), 'CoStrict')
|
||||
const costrictModel = resolveCoStrictModel(opts.model)
|
||||
const cachedModels = getCachedCoStrictModels()
|
||||
const isKnown =
|
||||
cachedModels.length === 0 || cachedModels.some(m => m.id === costrictModel)
|
||||
const effectiveModel = isKnown
|
||||
? costrictModel
|
||||
: resolveCoStrictModel(getMainLoopModel())
|
||||
return sideQueryOpenAICompat(opts, client, effectiveModel, 'CoStrict')
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user