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 模型名
|
* 将 Anthropic 模型名映射到 CoStrict 模型名
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getCachedCoStrictModels } from './models.js'
|
import { getCachedCoStrictModels, getCheapestCoStrictModel } from './models.js'
|
||||||
|
|
||||||
function getModelFamily(model: string): 'haiku' | 'sonnet' | 'opus' | null {
|
function getModelFamily(model: string): 'haiku' | 'sonnet' | 'opus' | null {
|
||||||
if (/haiku/i.test(model)) return 'haiku'
|
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 envVar = `COSTRICT_DEFAULT_${family.toUpperCase()}_MODEL`
|
||||||
const override = process.env[envVar]
|
const override = process.env[envVar]
|
||||||
if (override) return override
|
if (override) return override
|
||||||
|
// haiku 族无 env var 时,自动选取 creditConsumption 最低的模型
|
||||||
|
if (family === 'haiku') {
|
||||||
|
const cheapest = getCheapestCoStrictModel()
|
||||||
|
if (cheapest) return cheapest
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先级 3: 直接透传原始模型名(用户手动配置的模型名优先于环境变量)
|
// 优先级 3: 直接透传原始模型名(用户手动配置的模型名优先于环境变量)
|
||||||
|
|
|
||||||
|
|
@ -90,3 +90,17 @@ export function clearModelCache(): void {
|
||||||
export function getCachedCoStrictModels(): CoStrictModel[] {
|
export function getCachedCoStrictModels(): CoStrictModel[] {
|
||||||
return modelCache?.models ?? []
|
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 { feature } from 'bun:bundle'
|
||||||
import { logForDebugging } from '../utils/debug.js'
|
import { logForDebugging } from '../utils/debug.js'
|
||||||
import { errorMessage } from '../utils/errors.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 { sideQuery } from '../utils/sideQuery.js'
|
||||||
import type { LangfuseSpan } from '../services/langfuse/index.js'
|
import type { LangfuseSpan } from '../services/langfuse/index.js'
|
||||||
import { jsonParse } from '../utils/slowOperations.js'
|
import { jsonParse } from '../utils/slowOperations.js'
|
||||||
|
|
@ -99,8 +100,10 @@ async function selectRelevantMemories(
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const selectionModel =
|
||||||
|
getAPIProvider() === 'costrict' ? getSmallFastModel() : getDefaultSonnetModel()
|
||||||
const result = await sideQuery({
|
const result = await sideQuery({
|
||||||
model: getDefaultSonnetModel(),
|
model: selectionModel,
|
||||||
system: SELECT_MEMORIES_SYSTEM_PROMPT,
|
system: SELECT_MEMORIES_SYSTEM_PROMPT,
|
||||||
skipSystemPromptPrefix: true,
|
skipSystemPromptPrefix: true,
|
||||||
messages: [
|
messages: [
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ import {
|
||||||
runForkedAgent,
|
runForkedAgent,
|
||||||
} from '../../utils/forkedAgent.js'
|
} from '../../utils/forkedAgent.js'
|
||||||
import { getFsImplementation } from '../../utils/fsOperations.js'
|
import { getFsImplementation } from '../../utils/fsOperations.js'
|
||||||
|
import { getSmallFastModel } from '../../utils/model/model.js'
|
||||||
|
import { getAPIProvider } from '../../utils/model/providers.js'
|
||||||
import {
|
import {
|
||||||
type REPLHookContext,
|
type REPLHookContext,
|
||||||
registerPostSamplingHook,
|
registerPostSamplingHook,
|
||||||
|
|
@ -322,13 +324,19 @@ const extractSessionMemory = sequential(async function (
|
||||||
// Run session memory extraction using runForkedAgent for prompt caching
|
// Run session memory extraction using runForkedAgent for prompt caching
|
||||||
// runForkedAgent creates an isolated context to prevent mutation of parent state
|
// runForkedAgent creates an isolated context to prevent mutation of parent state
|
||||||
// Pass setupContext.readFileState so the forked agent can edit the memory file
|
// 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({
|
await runForkedAgent({
|
||||||
promptMessages: [createUserMessage({ content: userPrompt })],
|
promptMessages: [createUserMessage({ content: userPrompt })],
|
||||||
cacheSafeParams: createCacheSafeParams(context),
|
cacheSafeParams,
|
||||||
canUseTool: createMemoryFileCanUseTool(memoryPath),
|
canUseTool: createMemoryFileCanUseTool(memoryPath),
|
||||||
querySource: 'session_memory',
|
querySource: 'session_memory',
|
||||||
forkLabel: 'session_memory',
|
forkLabel: 'session_memory',
|
||||||
overrides: { readFileState: setupContext.readFileState },
|
overrides: { readFileState: setupContext.readFileState, ...modelOverride },
|
||||||
})
|
})
|
||||||
|
|
||||||
// Log extraction event for tracking frequency
|
// Log extraction event for tracking frequency
|
||||||
|
|
@ -424,6 +432,11 @@ export async function manuallyExtractSessionMemory(
|
||||||
const systemPrompt = asSystemPrompt(rawSystemPrompt)
|
const systemPrompt = asSystemPrompt(rawSystemPrompt)
|
||||||
|
|
||||||
// Run session memory extraction using runForkedAgent
|
// Run session memory extraction using runForkedAgent
|
||||||
|
const smallModelManual = getSmallFastModel()
|
||||||
|
const modelOverrideManual =
|
||||||
|
getAPIProvider() === 'costrict'
|
||||||
|
? { options: { ...setupContext.options, mainLoopModel: smallModelManual, model: smallModelManual } }
|
||||||
|
: {}
|
||||||
await runForkedAgent({
|
await runForkedAgent({
|
||||||
promptMessages: [createUserMessage({ content: userPrompt })],
|
promptMessages: [createUserMessage({ content: userPrompt })],
|
||||||
cacheSafeParams: {
|
cacheSafeParams: {
|
||||||
|
|
@ -436,7 +449,7 @@ export async function manuallyExtractSessionMemory(
|
||||||
canUseTool: createMemoryFileCanUseTool(memoryPath),
|
canUseTool: createMemoryFileCanUseTool(memoryPath),
|
||||||
querySource: 'session_memory',
|
querySource: 'session_memory',
|
||||||
forkLabel: 'session_memory_manual',
|
forkLabel: 'session_memory_manual',
|
||||||
overrides: { readFileState: setupContext.readFileState },
|
overrides: { readFileState: setupContext.readFileState, ...modelOverrideManual },
|
||||||
})
|
})
|
||||||
|
|
||||||
// Log manual extraction event
|
// Log manual extraction event
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@ import {
|
||||||
import { getFeatureValue_CACHED_MAY_BE_STALE } from '../analytics/growthbook.js'
|
import { getFeatureValue_CACHED_MAY_BE_STALE } from '../analytics/growthbook.js'
|
||||||
import { logEvent } from '../analytics/index.js'
|
import { logEvent } from '../analytics/index.js'
|
||||||
import { sanitizeToolNameForAnalytics } from '../analytics/metadata.js'
|
import { sanitizeToolNameForAnalytics } from '../analytics/metadata.js'
|
||||||
|
import { getSmallFastModel } from '../../utils/model/model.js'
|
||||||
|
import { getAPIProvider } from '../../utils/model/providers.js'
|
||||||
import {
|
import {
|
||||||
buildExtractAutoOnlyPrompt,
|
buildExtractAutoOnlyPrompt,
|
||||||
buildExtractCombinedPrompt,
|
buildExtractCombinedPrompt,
|
||||||
|
|
@ -367,6 +369,11 @@ export function initExtractMemories(): void {
|
||||||
|
|
||||||
const canUseTool = createAutoMemCanUseTool(memoryDir)
|
const canUseTool = createAutoMemCanUseTool(memoryDir)
|
||||||
const cacheSafeParams = createCacheSafeParams(context)
|
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).
|
// Only run extraction every N eligible turns (tengu_bramble_lintel, default 1).
|
||||||
// Trailing extractions (from stashed contexts) skip this check since they
|
// Trailing extractions (from stashed contexts) skip this check since they
|
||||||
|
|
@ -415,12 +422,9 @@ export function initExtractMemories(): void {
|
||||||
canUseTool,
|
canUseTool,
|
||||||
querySource: 'extract_memories',
|
querySource: 'extract_memories',
|
||||||
forkLabel: '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,
|
skipTranscript: true,
|
||||||
// Well-behaved extractions complete in 2-4 turns (read → write).
|
|
||||||
// A hard cap prevents verification rabbit-holes from burning turns.
|
|
||||||
maxTurns: 5,
|
maxTurns: 5,
|
||||||
|
overrides: modelOverride,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Advance the cursor only after a successful run. If the agent errors
|
// 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 {
|
export function getSmallFastModel(): ModelName {
|
||||||
const provider = getAPIProvider()
|
const provider = getAPIProvider()
|
||||||
// CoStrict has no haiku alias — use the main loop model to stay on the same provider
|
// CoStrict has no haiku alias — resolve to cheapest available model by creditConsumption
|
||||||
if (provider === 'costrict') return getMainLoopModel()
|
if (provider === 'costrict') return getDefaultHaikuModel()
|
||||||
// Provider-specific small fast model
|
// Provider-specific small fast model
|
||||||
if (provider === 'openai' && process.env.OPENAI_SMALL_FAST_MODEL) {
|
if (provider === 'openai' && process.env.OPENAI_SMALL_FAST_MODEL) {
|
||||||
return 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 { resolveOpenAIModel } from '@ant/model-provider'
|
||||||
import { createCoStrictFetch } from '../costrict/provider/fetch.js'
|
import { createCoStrictFetch } from '../costrict/provider/fetch.js'
|
||||||
import { resolveCoStrictModel } from '../costrict/provider/modelMapping.js'
|
import { resolveCoStrictModel } from '../costrict/provider/modelMapping.js'
|
||||||
|
import { getCachedCoStrictModels } from '../costrict/provider/models.js'
|
||||||
import { getCoStrictBaseURL } from '../costrict/provider/auth.js'
|
import { getCoStrictBaseURL } from '../costrict/provider/auth.js'
|
||||||
import { loadCoStrictCredentials } from '../costrict/provider/credentials.js'
|
import { loadCoStrictCredentials } from '../costrict/provider/credentials.js'
|
||||||
import { getProxyFetchOptions } from './proxy.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,
|
fetch: createCoStrictFetch() as unknown as typeof fetch,
|
||||||
defaultHeaders: { 'User-Agent': getUserAgent() },
|
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 {
|
const {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user