mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
fix(usage): 修复缓存创建 token 展示并在总量中计入缓存组件
- gateway/admin 用量载荷在 cache_creation_input_tokens 为 0 时回填 ephemeral_5m/1h 合计 - 标准化用量写入时将 cache_creation/cache_read token 计入 total_tokens - 前端列表与轮询同步新增分类字段,修复缓存 token 列显示
This commit is contained in:
@@ -61,6 +61,8 @@ export interface UsageRecordDetail {
|
||||
is_stream: boolean
|
||||
created_at: string
|
||||
cache_creation_input_tokens?: number
|
||||
cache_creation_ephemeral_5m_input_tokens?: number
|
||||
cache_creation_ephemeral_1h_input_tokens?: number
|
||||
cache_read_input_tokens?: number
|
||||
status_code: number
|
||||
error_message?: string
|
||||
@@ -284,8 +286,11 @@ export const meApi = {
|
||||
id: string
|
||||
status: 'pending' | 'streaming' | 'completed' | 'failed' | 'cancelled'
|
||||
input_tokens: number
|
||||
effective_input_tokens?: number | null
|
||||
output_tokens: number
|
||||
cache_creation_input_tokens?: number | null
|
||||
cache_creation_ephemeral_5m_input_tokens?: number | null
|
||||
cache_creation_ephemeral_1h_input_tokens?: number | null
|
||||
cache_read_input_tokens?: number | null
|
||||
cost: number
|
||||
actual_cost?: number | null
|
||||
@@ -296,6 +301,7 @@ export const meApi = {
|
||||
endpoint_api_format?: string | null
|
||||
has_format_conversion?: boolean | null
|
||||
has_fallback?: boolean | null
|
||||
target_model?: string | null
|
||||
}>
|
||||
}> {
|
||||
const params = ids ? { ids } : {}
|
||||
|
||||
@@ -13,6 +13,8 @@ export interface UsageRecord {
|
||||
effective_input_tokens?: number
|
||||
output_tokens: number
|
||||
cache_creation_input_tokens?: number
|
||||
cache_creation_ephemeral_5m_input_tokens?: number
|
||||
cache_creation_ephemeral_1h_input_tokens?: number
|
||||
cache_read_input_tokens?: number
|
||||
total_tokens: number
|
||||
cost?: number
|
||||
@@ -295,6 +297,8 @@ export const usageApi = {
|
||||
effective_input_tokens?: number | null
|
||||
output_tokens: number
|
||||
cache_creation_input_tokens?: number | null
|
||||
cache_creation_ephemeral_5m_input_tokens?: number | null
|
||||
cache_creation_ephemeral_1h_input_tokens?: number | null
|
||||
cache_read_input_tokens?: number | null
|
||||
cost: number
|
||||
actual_cost?: number | null
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { getCacheCreationTokens, getEffectiveInputTokens } from '../token-normalization'
|
||||
|
||||
describe('usage token normalization', () => {
|
||||
it('prefers explicit cache creation totals when present', () => {
|
||||
expect(getCacheCreationTokens({
|
||||
cache_creation_input_tokens: 18,
|
||||
cache_creation_ephemeral_5m_input_tokens: 5,
|
||||
cache_creation_ephemeral_1h_input_tokens: 7,
|
||||
})).toBe(18)
|
||||
})
|
||||
|
||||
it('rehydrates cache creation totals from classified fields when legacy total is zero', () => {
|
||||
expect(getCacheCreationTokens({
|
||||
cache_creation_input_tokens: 0,
|
||||
cache_creation_ephemeral_5m_input_tokens: 12,
|
||||
cache_creation_ephemeral_1h_input_tokens: 8,
|
||||
})).toBe(20)
|
||||
})
|
||||
|
||||
it('keeps effective input token normalization unchanged', () => {
|
||||
expect(getEffectiveInputTokens({
|
||||
input_tokens: 100,
|
||||
cache_read_input_tokens: 20,
|
||||
api_format: 'openai:chat',
|
||||
})).toBe(80)
|
||||
})
|
||||
})
|
||||
@@ -557,9 +557,9 @@
|
||||
<span>{{ formatTokens(record.output_tokens || 0) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 text-muted-foreground">
|
||||
<span :class="record.cache_creation_input_tokens ? 'text-foreground/70' : ''">{{ record.cache_creation_input_tokens ? formatTokens(record.cache_creation_input_tokens) : '-' }}</span>
|
||||
<span :class="hasPositiveTokens(getRecordCacheCreationTokens(record)) ? 'text-foreground/70' : ''">{{ formatOptionalTokens(getRecordCacheCreationTokens(record)) }}</span>
|
||||
<span>/</span>
|
||||
<span :class="record.cache_read_input_tokens ? 'text-foreground/70' : ''">{{ record.cache_read_input_tokens ? formatTokens(record.cache_read_input_tokens) : '-' }}</span>
|
||||
<span :class="hasPositiveTokens(record.cache_read_input_tokens) ? 'text-foreground/70' : ''">{{ formatOptionalTokens(record.cache_read_input_tokens) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -677,7 +677,7 @@ import {
|
||||
import { RefreshCcw, Search } from 'lucide-vue-next'
|
||||
import { formatTokens, formatCurrency } from '@/utils/format'
|
||||
import { formatDateTime } from '../composables'
|
||||
import { getEffectiveInputTokens } from '../token-normalization'
|
||||
import { getCacheCreationTokens, getEffectiveInputTokens } from '../token-normalization'
|
||||
import { isUsageRecordFailed, resolveDisplayRequestStatus } from '../utils/status'
|
||||
import { useRowClick } from '@/composables/useRowClick'
|
||||
import { formatApiFormat } from '@/api/endpoints/types/api-format'
|
||||
@@ -787,6 +787,18 @@ function getRecordEffectiveInputTokens(record: UsageRecord): number {
|
||||
return getEffectiveInputTokens(record)
|
||||
}
|
||||
|
||||
function getRecordCacheCreationTokens(record: UsageRecord): number {
|
||||
return getCacheCreationTokens(record)
|
||||
}
|
||||
|
||||
function hasPositiveTokens(value: number | null | undefined): boolean {
|
||||
return typeof value === 'number' && Number.isFinite(value) && value > 0
|
||||
}
|
||||
|
||||
function formatOptionalTokens(value: number | null | undefined): string {
|
||||
return hasPositiveTokens(value) ? formatTokens(value) : '-'
|
||||
}
|
||||
|
||||
// useDebounceFn 自动处理清理,无需 onUnmounted
|
||||
|
||||
// 判断是否应该显示格式转换信息
|
||||
|
||||
@@ -393,6 +393,12 @@ export function useUsageData(options: UseUsageDataOptions) {
|
||||
effective_input_tokens: existing.effective_input_tokens ?? record.effective_input_tokens,
|
||||
output_tokens: existing.output_tokens || record.output_tokens,
|
||||
cache_creation_input_tokens: existing.cache_creation_input_tokens ?? record.cache_creation_input_tokens,
|
||||
cache_creation_ephemeral_5m_input_tokens:
|
||||
existing.cache_creation_ephemeral_5m_input_tokens
|
||||
?? record.cache_creation_ephemeral_5m_input_tokens,
|
||||
cache_creation_ephemeral_1h_input_tokens:
|
||||
existing.cache_creation_ephemeral_1h_input_tokens
|
||||
?? record.cache_creation_ephemeral_1h_input_tokens,
|
||||
cache_read_input_tokens: existing.cache_read_input_tokens ?? record.cache_read_input_tokens,
|
||||
cost: existing.cost || record.cost,
|
||||
actual_cost: existing.actual_cost ?? record.actual_cost,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
type UsageTokenLike = {
|
||||
effective_input_tokens?: number | null
|
||||
input_tokens?: number | null
|
||||
cache_creation_input_tokens?: number | null
|
||||
cache_creation_ephemeral_5m_input_tokens?: number | null
|
||||
cache_creation_ephemeral_1h_input_tokens?: number | null
|
||||
cache_read_input_tokens?: number | null
|
||||
api_format?: string | null
|
||||
endpoint_api_format?: string | null
|
||||
@@ -17,6 +20,16 @@ function apiFamily(apiFormat: string | null | undefined): string {
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
export function getCacheCreationTokens(usage: UsageTokenLike): number {
|
||||
const explicit = toNonNegativeNumber(usage.cache_creation_input_tokens)
|
||||
const classified = toNonNegativeNumber(usage.cache_creation_ephemeral_5m_input_tokens)
|
||||
+ toNonNegativeNumber(usage.cache_creation_ephemeral_1h_input_tokens)
|
||||
if (explicit === 0 && classified > 0) {
|
||||
return classified
|
||||
}
|
||||
return explicit
|
||||
}
|
||||
|
||||
export function getEffectiveInputTokens(usage: UsageTokenLike): number {
|
||||
const explicit = toNonNegativeNumber(usage.effective_input_tokens)
|
||||
if (explicit > 0) {
|
||||
|
||||
@@ -98,6 +98,8 @@ export interface UsageRecord {
|
||||
effective_input_tokens?: number
|
||||
output_tokens: number
|
||||
cache_creation_input_tokens?: number
|
||||
cache_creation_ephemeral_5m_input_tokens?: number
|
||||
cache_creation_ephemeral_1h_input_tokens?: number
|
||||
cache_read_input_tokens?: number
|
||||
total_tokens: number
|
||||
cost: number
|
||||
|
||||
@@ -391,6 +391,10 @@ async function pollActiveRequests() {
|
||||
record.effective_input_tokens = update.effective_input_tokens ?? record.effective_input_tokens
|
||||
record.output_tokens = update.output_tokens
|
||||
record.cache_creation_input_tokens = update.cache_creation_input_tokens ?? undefined
|
||||
record.cache_creation_ephemeral_5m_input_tokens =
|
||||
update.cache_creation_ephemeral_5m_input_tokens ?? undefined
|
||||
record.cache_creation_ephemeral_1h_input_tokens =
|
||||
update.cache_creation_ephemeral_1h_input_tokens ?? undefined
|
||||
record.cache_read_input_tokens = update.cache_read_input_tokens ?? undefined
|
||||
record.cost = update.cost
|
||||
record.actual_cost = update.actual_cost ?? undefined
|
||||
|
||||
Reference in New Issue
Block a user