feat(usage): 使用记录展示映射前的推理强度

用量行新增 requested_reasoning_effort,记录分组策略与模型族改写前的客户端请求值;推理强度列与模型列一致,主行显示请求值,有映射时用 ↳ 展示实际转发值。
This commit is contained in:
刘佳林
2026-08-25 15:10:52 +08:00
parent aa2c4e8d13
commit 11ada80d58
40 changed files with 595 additions and 97 deletions
@@ -91,8 +91,16 @@
</template>
<template #cell-reasoning_effort="{ row }">
<span class="text-sm text-gray-900 dark:text-white">
{{ formatReasoningEffort(row.reasoning_effort) }}
<div v-if="hasReasoningEffortMapping(row)" class="space-y-0.5 text-xs">
<div class="font-medium text-gray-900 dark:text-white">
{{ formatReasoningEffort(row.requested_reasoning_effort) }}
</div>
<div class="text-gray-500 dark:text-gray-400">
<span class="mr-0.5">↳</span>{{ formatReasoningEffort(row.reasoning_effort) }}
</div>
</div>
<span v-else class="text-sm text-gray-900 dark:text-white">
{{ formatReasoningEffort(row.requested_reasoning_effort || row.reasoning_effort) }}
</span>
</template>
@@ -500,7 +508,7 @@
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { formatDateTime, formatReasoningEffort } from '@/utils/format'
import { formatDateTime, formatReasoningEffort, reasoningEffortValuesEqual } from '@/utils/format'
import { formatCacheTokens, formatMultiplier } from '@/utils/formatters'
import { formatTokenPricePerMillion } from '@/utils/usagePricing'
import { getUsageServiceTierLabel } from '@/utils/usageServiceTier'
@@ -587,6 +595,12 @@ const ipGeoBatchLoading = ref(false)
const showIpGeoToolbar = computed(() => props.columns.some((col) => col.key === 'ip_address'))
const hasReasoningEffortMapping = (row: AdminUsageLog): boolean => {
const requested = row.requested_reasoning_effort?.trim() || ''
const forwarded = row.reasoning_effort?.trim() || ''
return requested !== '' && forwarded !== '' && !reasoningEffortValuesEqual(requested, forwarded)
}
const sentUpstreamModel = (row: AdminUsageLog): string => row.upstream_model?.trim() || row.model?.trim() || ''
const normalizeModelVariant = (model: string): string => model
@@ -84,6 +84,7 @@ const DataTableStub = {
<div>
<div v-for="row in data" :key="row.request_id">
<slot name="cell-model" :row="row" :value="row.model" />
<slot name="cell-reasoning_effort" :row="row" :value="row.reasoning_effort" />
<slot name="cell-billing_mode" :row="row" />
<slot name="cell-tokens" :row="row" />
<slot name="cell-cost" :row="row" />
@@ -256,6 +257,61 @@ describe('admin UsageTable tooltip', () => {
expect(text).toContain('claude-sonnet-4-20250514')
})
it('shows requested and forwarded reasoning effort separately when they differ', () => {
const wrapper = mount(UsageTable, {
props: {
data: [{
request_id: 'req-admin-effort-1',
model: 'gpt-5.4',
requested_reasoning_effort: 'max',
reasoning_effort: 'xhigh',
}],
loading: false,
columns: [],
},
global: {
stubs: {
DataTable: DataTableStub,
EmptyState: true,
Icon: true,
Teleport: true,
},
},
})
const text = wrapper.text()
expect(text).toContain('Max')
expect(text).toContain('XHigh')
expect(text).toContain('↳')
})
it('shows a single reasoning effort when requested matches forwarded', () => {
const wrapper = mount(UsageTable, {
props: {
data: [{
request_id: 'req-admin-effort-2',
model: 'gpt-5.6-sol',
requested_reasoning_effort: 'max',
reasoning_effort: 'max',
}],
loading: false,
columns: [],
},
global: {
stubs: {
DataTable: DataTableStub,
EmptyState: true,
Icon: true,
Teleport: true,
},
},
})
const text = wrapper.text()
expect(text).toContain('Max')
expect(text).not.toContain('↳')
})
it.each([
{
name: 'possible version variant',
@@ -561,6 +617,7 @@ const DataTableStubWithUser = {
<div v-for="row in data" :key="row.request_id">
<slot name="cell-user" :row="row" />
<slot name="cell-model" :row="row" :value="row.model" />
<slot name="cell-reasoning_effort" :row="row" :value="row.reasoning_effort" />
<slot name="cell-billing_mode" :row="row" />
<slot name="cell-tokens" :row="row" />
<slot name="cell-cost" :row="row" />