mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
feat: 拆分 usage 记录的请求体/响应体为客户端侧与提供商侧
将 request_body/response_body 语义明确为客户端原始请求体和提供商原始响应体, 新增 provider_request_body(格式转换后发给提供商的请求体)和 client_response_body (格式转换后返回给客户端的响应体),支持跨格式转换场景下分别查看两侧数据。 - 数据库新增 provider_request_body/client_response_body 及对应压缩字段 - 全链路(telemetry/recording/handler/stream_context)传递新字段 - 维护调度器同步支持新字段的压缩与清理 - 前端请求详情抽屉支持请求体/响应体/响应头的客户端/提供商视图切换
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
"""Add provider_request_body and client_response_body columns to usage table
|
||||||
|
|
||||||
|
Revision ID: 7e8f9a0b1c2d
|
||||||
|
Revises: 6d7e8f9a0b1c
|
||||||
|
Create Date: 2026-02-20 18:00:00.000000
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "7e8f9a0b1c2d"
|
||||||
|
down_revision: str | None = "6d7e8f9a0b1c"
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
existing_columns = {col["name"] for col in inspector.get_columns("usage")}
|
||||||
|
|
||||||
|
if "provider_request_body" not in existing_columns:
|
||||||
|
op.add_column("usage", sa.Column("provider_request_body", sa.JSON(), nullable=True))
|
||||||
|
if "provider_request_body_compressed" not in existing_columns:
|
||||||
|
op.add_column(
|
||||||
|
"usage", sa.Column("provider_request_body_compressed", sa.LargeBinary(), nullable=True)
|
||||||
|
)
|
||||||
|
if "client_response_body" not in existing_columns:
|
||||||
|
op.add_column("usage", sa.Column("client_response_body", sa.JSON(), nullable=True))
|
||||||
|
if "client_response_body_compressed" not in existing_columns:
|
||||||
|
op.add_column(
|
||||||
|
"usage", sa.Column("client_response_body_compressed", sa.LargeBinary(), nullable=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
existing_columns = {col["name"] for col in inspector.get_columns("usage")}
|
||||||
|
|
||||||
|
for col in (
|
||||||
|
"client_response_body_compressed",
|
||||||
|
"client_response_body",
|
||||||
|
"provider_request_body_compressed",
|
||||||
|
"provider_request_body",
|
||||||
|
):
|
||||||
|
if col in existing_columns:
|
||||||
|
op.drop_column("usage", col)
|
||||||
@@ -168,9 +168,11 @@ export interface RequestDetail {
|
|||||||
request_headers?: Record<string, any>
|
request_headers?: Record<string, any>
|
||||||
request_body?: Record<string, any>
|
request_body?: Record<string, any>
|
||||||
provider_request_headers?: Record<string, any>
|
provider_request_headers?: Record<string, any>
|
||||||
|
provider_request_body?: Record<string, any>
|
||||||
response_headers?: Record<string, any>
|
response_headers?: Record<string, any>
|
||||||
client_response_headers?: Record<string, any>
|
client_response_headers?: Record<string, any>
|
||||||
response_body?: Record<string, any>
|
response_body?: Record<string, any>
|
||||||
|
client_response_body?: Record<string, any>
|
||||||
metadata?: Record<string, any>
|
metadata?: Record<string, any>
|
||||||
// 阶梯计费信息
|
// 阶梯计费信息
|
||||||
tiered_pricing?: {
|
tiered_pricing?: {
|
||||||
|
|||||||
@@ -449,6 +449,46 @@
|
|||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 响应头:客户端/提供商切换 -->
|
||||||
|
<template v-if="activeTab === 'response-headers' && hasProviderResponseHeaders">
|
||||||
|
<button
|
||||||
|
title="客户端"
|
||||||
|
class="p-1.5 rounded transition-colors"
|
||||||
|
:class="dataSource === 'client' ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted'"
|
||||||
|
@click="dataSource = 'client'"
|
||||||
|
>
|
||||||
|
<Monitor class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
title="提供商"
|
||||||
|
class="p-1.5 rounded transition-colors"
|
||||||
|
:class="dataSource === 'provider' ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted'"
|
||||||
|
@click="dataSource = 'provider'"
|
||||||
|
>
|
||||||
|
<Server class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 请求体/响应体:客户端/提供商切换 -->
|
||||||
|
<template v-if="['request-body', 'response-body'].includes(activeTab) && hasProviderBody">
|
||||||
|
<button
|
||||||
|
title="客户端"
|
||||||
|
class="p-1.5 rounded transition-colors"
|
||||||
|
:class="dataSource === 'client' ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted'"
|
||||||
|
@click="dataSource = 'client'"
|
||||||
|
>
|
||||||
|
<Monitor class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
title="提供商"
|
||||||
|
class="p-1.5 rounded transition-colors"
|
||||||
|
:class="dataSource === 'provider' ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted'"
|
||||||
|
@click="dataSource = 'provider'"
|
||||||
|
>
|
||||||
|
<Server class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 请求体/响应体专用:JSON/对话 视图切换(单按钮) -->
|
<!-- 请求体/响应体专用:JSON/对话 视图切换(单按钮) -->
|
||||||
<template v-if="supportsConversationView">
|
<template v-if="supportsConversationView">
|
||||||
<button
|
<button
|
||||||
@@ -562,7 +602,7 @@
|
|||||||
<!-- JSON 视图 -->
|
<!-- JSON 视图 -->
|
||||||
<JsonContent
|
<JsonContent
|
||||||
v-else
|
v-else
|
||||||
:data="detail.request_body"
|
:data="currentRequestBody"
|
||||||
:view-mode="viewMode"
|
:view-mode="viewMode"
|
||||||
:expand-depth="currentExpandDepth"
|
:expand-depth="currentExpandDepth"
|
||||||
:is-dark="isDark"
|
:is-dark="isDark"
|
||||||
@@ -572,7 +612,7 @@
|
|||||||
|
|
||||||
<TabsContent value="response-headers">
|
<TabsContent value="response-headers">
|
||||||
<JsonContent
|
<JsonContent
|
||||||
:data="actualResponseHeaders"
|
:data="currentResponseHeaderData"
|
||||||
:view-mode="viewMode"
|
:view-mode="viewMode"
|
||||||
:expand-depth="currentExpandDepth"
|
:expand-depth="currentExpandDepth"
|
||||||
:is-dark="isDark"
|
:is-dark="isDark"
|
||||||
@@ -590,7 +630,7 @@
|
|||||||
<!-- JSON 视图 -->
|
<!-- JSON 视图 -->
|
||||||
<JsonContent
|
<JsonContent
|
||||||
v-else
|
v-else
|
||||||
:data="detail.response_body"
|
:data="currentResponseBody"
|
||||||
:view-mode="viewMode"
|
:view-mode="viewMode"
|
||||||
:expand-depth="currentExpandDepth"
|
:expand-depth="currentExpandDepth"
|
||||||
:is-dark="isDark"
|
:is-dark="isDark"
|
||||||
@@ -713,6 +753,38 @@ const hasProviderHeaders = computed(() => {
|
|||||||
Object.keys(detail.value.provider_request_headers).length > 0)
|
Object.keys(detail.value.provider_request_headers).length > 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 检测是否有提供商请求体(格式转换后的)
|
||||||
|
const hasProviderBody = computed(() => {
|
||||||
|
return !!(detail.value?.provider_request_body && hasContent(detail.value.provider_request_body))
|
||||||
|
|| !!(detail.value?.client_response_body && hasContent(detail.value.client_response_body))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 检测是否有两套响应头(客户端侧 + 提供商侧)
|
||||||
|
const hasProviderResponseHeaders = computed(() => {
|
||||||
|
return !!(detail.value?.response_headers &&
|
||||||
|
Object.keys(detail.value.response_headers).length > 0) &&
|
||||||
|
!!(detail.value?.client_response_headers &&
|
||||||
|
Object.keys(detail.value.client_response_headers).length > 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取当前数据源的请求体数据
|
||||||
|
const currentRequestBody = computed(() => {
|
||||||
|
if (!detail.value) return null
|
||||||
|
if (dataSource.value === 'provider' && detail.value.provider_request_body) {
|
||||||
|
return detail.value.provider_request_body
|
||||||
|
}
|
||||||
|
return detail.value.request_body
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取当前数据源的响应体数据
|
||||||
|
const currentResponseBody = computed(() => {
|
||||||
|
if (!detail.value) return null
|
||||||
|
if (dataSource.value === 'client' && detail.value.client_response_body) {
|
||||||
|
return detail.value.client_response_body
|
||||||
|
}
|
||||||
|
return detail.value.response_body
|
||||||
|
})
|
||||||
|
|
||||||
// 获取当前数据源的请求头数据
|
// 获取当前数据源的请求头数据
|
||||||
const currentHeaderData = computed(() => {
|
const currentHeaderData = computed(() => {
|
||||||
if (!detail.value) return null
|
if (!detail.value) return null
|
||||||
@@ -723,18 +795,20 @@ const currentHeaderData = computed(() => {
|
|||||||
|
|
||||||
// 请求体渲染结果
|
// 请求体渲染结果
|
||||||
const requestRenderResult = computed<RenderResult>(() => {
|
const requestRenderResult = computed<RenderResult>(() => {
|
||||||
if (!detail.value?.request_body) {
|
const body = currentRequestBody.value
|
||||||
|
if (!body) {
|
||||||
return { blocks: [], isStream: false }
|
return { blocks: [], isStream: false }
|
||||||
}
|
}
|
||||||
return renderRequest(detail.value.request_body, detail.value.response_body, detail.value.api_format)
|
return renderRequest(body, currentResponseBody.value, detail.value?.api_format)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 响应体渲染结果
|
// 响应体渲染结果
|
||||||
const responseRenderResult = computed<RenderResult>(() => {
|
const responseRenderResult = computed<RenderResult>(() => {
|
||||||
if (!detail.value?.response_body) {
|
const body = currentResponseBody.value
|
||||||
|
if (!body) {
|
||||||
return { blocks: [], isStream: false }
|
return { blocks: [], isStream: false }
|
||||||
}
|
}
|
||||||
return renderResponse(detail.value.response_body, detail.value.request_body, detail.value.api_format)
|
return renderResponse(body, currentRequestBody.value, detail.value?.api_format)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 当前 Tab 是否支持对话视图
|
// 当前 Tab 是否支持对话视图
|
||||||
@@ -874,10 +948,16 @@ function hasContent(data: unknown): boolean {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取实际的响应头(优先 client_response_headers,回退到 response_headers)
|
// 获取当前数据源的响应头数据
|
||||||
const actualResponseHeaders = computed(() => {
|
const currentResponseHeaderData = computed(() => {
|
||||||
if (!detail.value) return null
|
if (!detail.value) return null
|
||||||
// 优先返回客户端响应头,如果没有则回退到提供商响应头
|
if (dataSource.value === 'client' && hasContent(detail.value.client_response_headers)) {
|
||||||
|
return detail.value.client_response_headers
|
||||||
|
}
|
||||||
|
if (dataSource.value === 'provider' && hasContent(detail.value.response_headers)) {
|
||||||
|
return detail.value.response_headers
|
||||||
|
}
|
||||||
|
// 回退:优先 client,再 provider
|
||||||
if (hasContent(detail.value.client_response_headers)) {
|
if (hasContent(detail.value.client_response_headers)) {
|
||||||
return detail.value.client_response_headers
|
return detail.value.client_response_headers
|
||||||
}
|
}
|
||||||
@@ -893,11 +973,11 @@ const visibleTabs = computed(() => {
|
|||||||
case 'request-headers':
|
case 'request-headers':
|
||||||
return hasContent(detail.value!.request_headers)
|
return hasContent(detail.value!.request_headers)
|
||||||
case 'request-body':
|
case 'request-body':
|
||||||
return hasContent(detail.value!.request_body)
|
return hasContent(detail.value!.request_body) || hasContent(detail.value!.provider_request_body)
|
||||||
case 'response-headers':
|
case 'response-headers':
|
||||||
return hasContent(actualResponseHeaders.value)
|
return hasContent(detail.value!.response_headers) || hasContent(detail.value!.client_response_headers)
|
||||||
case 'response-body':
|
case 'response-body':
|
||||||
return hasContent(detail.value!.response_body)
|
return hasContent(detail.value!.response_body) || hasContent(detail.value!.client_response_body)
|
||||||
case 'metadata':
|
case 'metadata':
|
||||||
return hasContent(detail.value!.metadata)
|
return hasContent(detail.value!.metadata)
|
||||||
default:
|
default:
|
||||||
@@ -932,9 +1012,9 @@ async function loadDetail(id: string, silent = false) {
|
|||||||
// 首次加载时选择默认 tab
|
// 首次加载时选择默认 tab
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
const visibleTabNames = visibleTabs.value.map(t => t.name)
|
const visibleTabNames = visibleTabs.value.map(t => t.name)
|
||||||
if (detail.value.request_body && visibleTabNames.includes('request-body')) {
|
if ((detail.value.request_body || detail.value.provider_request_body) && visibleTabNames.includes('request-body')) {
|
||||||
activeTab.value = 'request-body'
|
activeTab.value = 'request-body'
|
||||||
} else if (detail.value.response_body && visibleTabNames.includes('response-body')) {
|
} else if ((detail.value.response_body || detail.value.client_response_body) && visibleTabNames.includes('response-body')) {
|
||||||
activeTab.value = 'response-body'
|
activeTab.value = 'response-body'
|
||||||
} else if (visibleTabNames.length > 0) {
|
} else if (visibleTabNames.length > 0) {
|
||||||
activeTab.value = visibleTabNames[0]
|
activeTab.value = visibleTabNames[0]
|
||||||
@@ -1192,13 +1272,13 @@ function copyContent(tabName: string) {
|
|||||||
: detail.value.request_headers
|
: detail.value.request_headers
|
||||||
break
|
break
|
||||||
case 'request-body':
|
case 'request-body':
|
||||||
data = detail.value.request_body
|
data = currentRequestBody.value
|
||||||
break
|
break
|
||||||
case 'response-headers':
|
case 'response-headers':
|
||||||
data = actualResponseHeaders.value
|
data = currentResponseHeaderData.value
|
||||||
break
|
break
|
||||||
case 'response-body':
|
case 'response-body':
|
||||||
data = detail.value.response_body
|
data = currentResponseBody.value
|
||||||
break
|
break
|
||||||
case 'metadata':
|
case 'metadata':
|
||||||
data = detail.value.metadata
|
data = detail.value.metadata
|
||||||
|
|||||||
@@ -1278,9 +1278,11 @@ class AdminUsageDetailAdapter(AdminApiAdapter):
|
|||||||
"request_headers": usage_record.request_headers,
|
"request_headers": usage_record.request_headers,
|
||||||
"request_body": usage_record.get_request_body(),
|
"request_body": usage_record.get_request_body(),
|
||||||
"provider_request_headers": usage_record.provider_request_headers,
|
"provider_request_headers": usage_record.provider_request_headers,
|
||||||
|
"provider_request_body": usage_record.get_provider_request_body(),
|
||||||
"response_headers": usage_record.response_headers,
|
"response_headers": usage_record.response_headers,
|
||||||
"client_response_headers": usage_record.client_response_headers,
|
"client_response_headers": usage_record.client_response_headers,
|
||||||
"response_body": usage_record.get_response_body(),
|
"response_body": usage_record.get_response_body(),
|
||||||
|
"client_response_body": usage_record.get_client_response_body(),
|
||||||
"metadata": usage_record.request_metadata,
|
"metadata": usage_record.request_metadata,
|
||||||
"tiered_pricing": tiered_pricing_info,
|
"tiered_pricing": tiered_pricing_info,
|
||||||
"video_billing": video_billing_info,
|
"video_billing": video_billing_info,
|
||||||
|
|||||||
@@ -1046,6 +1046,10 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
|||||||
|
|
||||||
output_state = {"started": False}
|
output_state = {"started": False}
|
||||||
|
|
||||||
|
# 保留提供商原始响应到 provider_parsed_chunks
|
||||||
|
if ctx.record_parsed_chunks and isinstance(response_json, dict):
|
||||||
|
ctx.provider_parsed_chunks.append(response_json)
|
||||||
|
|
||||||
async def _streamified() -> AsyncGenerator[bytes]:
|
async def _streamified() -> AsyncGenerator[bytes]:
|
||||||
for ev in iter_internal_response_as_stream_events(internal_resp):
|
for ev in iter_internal_response_as_stream_events(internal_resp):
|
||||||
converted_events = tgt_norm.stream_event_from_internal(ev, state)
|
converted_events = tgt_norm.stream_event_from_internal(ev, state)
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ class SyncRequestContext:
|
|||||||
key_id: str | None = None
|
key_id: str | None = None
|
||||||
mapped_model_result: str | None = None
|
mapped_model_result: str | None = None
|
||||||
sync_proxy_info: dict[str, Any] | None = None
|
sync_proxy_info: dict[str, Any] | None = None
|
||||||
|
provider_response_json: dict[str, Any] | None = None # 格式转换前的提供商原始响应
|
||||||
|
|
||||||
|
|
||||||
class ChatSyncExecutor:
|
class ChatSyncExecutor:
|
||||||
@@ -182,8 +183,6 @@ class ChatSyncExecutor:
|
|||||||
cache_creation_tokens = usage_info.get("cache_creation_input_tokens", 0)
|
cache_creation_tokens = usage_info.get("cache_creation_input_tokens", 0)
|
||||||
cached_tokens = usage_info.get("cache_read_input_tokens", 0)
|
cached_tokens = usage_info.get("cache_read_input_tokens", 0)
|
||||||
|
|
||||||
actual_request_body = ctx.provider_request_body or original_request_body
|
|
||||||
|
|
||||||
# 非流式成功时,返回给客户端的是提供商响应头(透传)
|
# 非流式成功时,返回给客户端的是提供商响应头(透传)
|
||||||
# JSONResponse 会自动设置 content-type,但我们记录实际返回的完整头
|
# JSONResponse 会自动设置 content-type,但我们记录实际返回的完整头
|
||||||
client_response_headers = filter_proxy_response_headers(ctx.response_headers)
|
client_response_headers = filter_proxy_response_headers(ctx.response_headers)
|
||||||
@@ -200,10 +199,12 @@ class ChatSyncExecutor:
|
|||||||
response_time_ms=response_time_ms,
|
response_time_ms=response_time_ms,
|
||||||
status_code=ctx.status_code,
|
status_code=ctx.status_code,
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
response_headers=ctx.response_headers,
|
response_headers=ctx.response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
response_body=ctx.response_json,
|
response_body=ctx.provider_response_json or ctx.response_json,
|
||||||
|
client_response_body=ctx.response_json if ctx.provider_response_json else None,
|
||||||
|
provider_request_body=ctx.provider_request_body,
|
||||||
cache_creation_tokens=cache_creation_tokens,
|
cache_creation_tokens=cache_creation_tokens,
|
||||||
cache_read_tokens=cached_tokens,
|
cache_read_tokens=cached_tokens,
|
||||||
is_stream=False,
|
is_stream=False,
|
||||||
@@ -242,7 +243,6 @@ class ChatSyncExecutor:
|
|||||||
# Thinking 签名错误:TaskService 层已处理整流重试但仍失败
|
# Thinking 签名错误:TaskService 层已处理整流重试但仍失败
|
||||||
# 记录实际发送给 Provider 的请求体,便于排查问题根因
|
# 记录实际发送给 Provider 的请求体,便于排查问题根因
|
||||||
response_time_ms = handler.elapsed_ms()
|
response_time_ms = handler.elapsed_ms()
|
||||||
actual_request_body = ctx.provider_request_body or original_request_body
|
|
||||||
request_metadata = handler._build_request_metadata() or {}
|
request_metadata = handler._build_request_metadata() or {}
|
||||||
if ctx.sync_proxy_info:
|
if ctx.sync_proxy_info:
|
||||||
request_metadata["proxy"] = ctx.sync_proxy_info
|
request_metadata["proxy"] = ctx.sync_proxy_info
|
||||||
@@ -252,7 +252,8 @@ class ChatSyncExecutor:
|
|||||||
response_time_ms=response_time_ms,
|
response_time_ms=response_time_ms,
|
||||||
status_code=e.status_code or 400,
|
status_code=e.status_code or 400,
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
|
provider_request_body=ctx.provider_request_body,
|
||||||
error_message=str(e),
|
error_message=str(e),
|
||||||
is_stream=False,
|
is_stream=False,
|
||||||
provider_id=ctx.provider_id,
|
provider_id=ctx.provider_id,
|
||||||
@@ -275,7 +276,6 @@ class ChatSyncExecutor:
|
|||||||
|
|
||||||
except UpstreamClientException as e:
|
except UpstreamClientException as e:
|
||||||
response_time_ms = handler.elapsed_ms()
|
response_time_ms = handler.elapsed_ms()
|
||||||
actual_request_body = ctx.provider_request_body or original_request_body
|
|
||||||
request_metadata = handler._build_request_metadata() or {}
|
request_metadata = handler._build_request_metadata() or {}
|
||||||
if ctx.sync_proxy_info:
|
if ctx.sync_proxy_info:
|
||||||
request_metadata["proxy"] = ctx.sync_proxy_info
|
request_metadata["proxy"] = ctx.sync_proxy_info
|
||||||
@@ -285,7 +285,8 @@ class ChatSyncExecutor:
|
|||||||
response_time_ms=response_time_ms,
|
response_time_ms=response_time_ms,
|
||||||
status_code=_get_error_status_code(e),
|
status_code=_get_error_status_code(e),
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
|
provider_request_body=ctx.provider_request_body,
|
||||||
error_message=str(e),
|
error_message=str(e),
|
||||||
is_stream=False,
|
is_stream=False,
|
||||||
api_format=api_format,
|
api_format=api_format,
|
||||||
@@ -327,8 +328,6 @@ class ChatSyncExecutor:
|
|||||||
elif isinstance(e, ProviderTimeoutException):
|
elif isinstance(e, ProviderTimeoutException):
|
||||||
status_code = 504
|
status_code = 504
|
||||||
|
|
||||||
actual_request_body = ctx.provider_request_body or original_request_body
|
|
||||||
|
|
||||||
# 尝试从异常中提取响应头
|
# 尝试从异常中提取响应头
|
||||||
error_response_headers: dict[str, str] = {}
|
error_response_headers: dict[str, str] = {}
|
||||||
if isinstance(e, ProviderRateLimitException) and e.response_headers:
|
if isinstance(e, ProviderRateLimitException) and e.response_headers:
|
||||||
@@ -346,7 +345,8 @@ class ChatSyncExecutor:
|
|||||||
status_code=status_code,
|
status_code=status_code,
|
||||||
error_message=extract_client_error_message(e),
|
error_message=extract_client_error_message(e),
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
|
provider_request_body=ctx.provider_request_body,
|
||||||
is_stream=False,
|
is_stream=False,
|
||||||
api_format=api_format,
|
api_format=api_format,
|
||||||
provider_request_headers=ctx.provider_request_headers,
|
provider_request_headers=ctx.provider_request_headers,
|
||||||
@@ -667,6 +667,7 @@ class ChatSyncExecutor:
|
|||||||
|
|
||||||
# 跨格式:响应转换回 client_format(失败触发 failover)
|
# 跨格式:响应转换回 client_format(失败触发 failover)
|
||||||
if needs_conversion and isinstance(ctx.response_json, dict):
|
if needs_conversion and isinstance(ctx.response_json, dict):
|
||||||
|
ctx.provider_response_json = ctx.response_json.copy()
|
||||||
registry = get_format_converter_registry()
|
registry = get_format_converter_registry()
|
||||||
ctx.response_json = registry.convert_response(
|
ctx.response_json = registry.convert_response(
|
||||||
ctx.response_json,
|
ctx.response_json,
|
||||||
@@ -700,8 +701,6 @@ class ChatSyncExecutor:
|
|||||||
elif isinstance(error, ProviderTimeoutException):
|
elif isinstance(error, ProviderTimeoutException):
|
||||||
status_code = 504
|
status_code = 504
|
||||||
|
|
||||||
actual_request_body = ctx.provider_request_body or original_request_body
|
|
||||||
|
|
||||||
# 失败时返回给客户端的是 JSON 错误响应
|
# 失败时返回给客户端的是 JSON 错误响应
|
||||||
client_response_headers = {"content-type": "application/json"}
|
client_response_headers = {"content-type": "application/json"}
|
||||||
|
|
||||||
@@ -716,7 +715,8 @@ class ChatSyncExecutor:
|
|||||||
status_code=status_code,
|
status_code=status_code,
|
||||||
error_message=extract_client_error_message(error),
|
error_message=extract_client_error_message(error),
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
|
provider_request_body=ctx.provider_request_body,
|
||||||
is_stream=True,
|
is_stream=True,
|
||||||
api_format=ctx.api_format,
|
api_format=ctx.api_format,
|
||||||
provider_request_headers=ctx.provider_request_headers,
|
provider_request_headers=ctx.provider_request_headers,
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ class CliEventMixin:
|
|||||||
ctx.data_count += 1
|
ctx.data_count += 1
|
||||||
if ctx.record_parsed_chunks:
|
if ctx.record_parsed_chunks:
|
||||||
ctx.parsed_chunks.append(data)
|
ctx.parsed_chunks.append(data)
|
||||||
|
else:
|
||||||
|
# 格式转换场景:保留提供商原始数据
|
||||||
|
if ctx.record_parsed_chunks:
|
||||||
|
ctx.provider_parsed_chunks.append(data)
|
||||||
|
|
||||||
event_type = event_name or data.get("type", "")
|
event_type = event_name or data.get("type", "")
|
||||||
|
|
||||||
|
|||||||
@@ -211,19 +211,8 @@ class CliMonitorMixin:
|
|||||||
bg_db, user, api_key, ctx.request_id, self.client_ip
|
bg_db, user, api_key, ctx.request_id, self.client_ip
|
||||||
)
|
)
|
||||||
|
|
||||||
response_body = {
|
response_body = ctx.build_response_body(response_time_ms)
|
||||||
"chunks": ctx.parsed_chunks,
|
client_response_body = ctx.build_client_response_body(response_time_ms)
|
||||||
"metadata": {
|
|
||||||
"stream": True,
|
|
||||||
"total_chunks": len(ctx.parsed_chunks),
|
|
||||||
"data_count": ctx.data_count,
|
|
||||||
"has_completion": ctx.has_completion,
|
|
||||||
"response_time_ms": response_time_ms,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# 使用实际发送给 Provider 的请求体(如果有),否则用原始请求体
|
|
||||||
actual_request_body = ctx.provider_request_body or original_request_body
|
|
||||||
|
|
||||||
# 根据状态码决定记录成功还是失败
|
# 根据状态码决定记录成功还是失败
|
||||||
# 499 = 客户端取消(不算系统失败);其他 4xx/5xx 视为失败
|
# 499 = 客户端取消(不算系统失败);其他 4xx/5xx 视为失败
|
||||||
@@ -242,15 +231,17 @@ class CliMonitorMixin:
|
|||||||
first_byte_time_ms=ctx.first_byte_time_ms,
|
first_byte_time_ms=ctx.first_byte_time_ms,
|
||||||
status_code=ctx.status_code,
|
status_code=ctx.status_code,
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
is_stream=True,
|
is_stream=True,
|
||||||
api_format=ctx.api_format,
|
api_format=ctx.api_format,
|
||||||
provider_request_headers=ctx.provider_request_headers,
|
provider_request_headers=ctx.provider_request_headers,
|
||||||
|
provider_request_body=ctx.provider_request_body,
|
||||||
input_tokens=ctx.input_tokens,
|
input_tokens=ctx.input_tokens,
|
||||||
output_tokens=ctx.output_tokens,
|
output_tokens=ctx.output_tokens,
|
||||||
cache_creation_tokens=ctx.cache_creation_tokens,
|
cache_creation_tokens=ctx.cache_creation_tokens,
|
||||||
cache_read_tokens=ctx.cached_tokens,
|
cache_read_tokens=ctx.cached_tokens,
|
||||||
response_body=response_body,
|
response_body=response_body,
|
||||||
|
client_response_body=client_response_body,
|
||||||
response_headers=ctx.response_headers,
|
response_headers=ctx.response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
endpoint_api_format=ctx.provider_api_format or None,
|
endpoint_api_format=ctx.provider_api_format or None,
|
||||||
@@ -273,16 +264,18 @@ class CliMonitorMixin:
|
|||||||
status_code=ctx.status_code,
|
status_code=ctx.status_code,
|
||||||
error_message=ctx.error_message or f"HTTP {ctx.status_code}",
|
error_message=ctx.error_message or f"HTTP {ctx.status_code}",
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
is_stream=True,
|
is_stream=True,
|
||||||
api_format=ctx.api_format,
|
api_format=ctx.api_format,
|
||||||
provider_request_headers=ctx.provider_request_headers,
|
provider_request_headers=ctx.provider_request_headers,
|
||||||
|
provider_request_body=ctx.provider_request_body,
|
||||||
# 预估 token 信息(来自 message_start 事件)
|
# 预估 token 信息(来自 message_start 事件)
|
||||||
input_tokens=ctx.input_tokens,
|
input_tokens=ctx.input_tokens,
|
||||||
output_tokens=ctx.output_tokens,
|
output_tokens=ctx.output_tokens,
|
||||||
cache_creation_tokens=ctx.cache_creation_tokens,
|
cache_creation_tokens=ctx.cache_creation_tokens,
|
||||||
cache_read_tokens=ctx.cached_tokens,
|
cache_read_tokens=ctx.cached_tokens,
|
||||||
response_body=response_body,
|
response_body=response_body,
|
||||||
|
client_response_body=client_response_body,
|
||||||
response_headers=ctx.response_headers,
|
response_headers=ctx.response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
# 格式转换追踪
|
# 格式转换追踪
|
||||||
@@ -319,7 +312,10 @@ class CliMonitorMixin:
|
|||||||
and ctx.input_tokens == 0
|
and ctx.input_tokens == 0
|
||||||
and ctx.output_tokens == 0
|
and ctx.output_tokens == 0
|
||||||
):
|
):
|
||||||
self._estimate_tokens_for_incomplete_stream(ctx, actual_request_body)
|
# 用实际发给 Provider 的请求体估算 token(格式转换时与客户端请求体不同)
|
||||||
|
self._estimate_tokens_for_incomplete_stream(
|
||||||
|
ctx, ctx.provider_request_body or original_request_body
|
||||||
|
)
|
||||||
|
|
||||||
# 流式成功时,返回给客户端的是提供商响应头 + SSE 必需头
|
# 流式成功时,返回给客户端的是提供商响应头 + SSE 必需头
|
||||||
client_response_headers = filter_proxy_response_headers(ctx.response_headers)
|
client_response_headers = filter_proxy_response_headers(ctx.response_headers)
|
||||||
@@ -346,10 +342,12 @@ class CliMonitorMixin:
|
|||||||
first_byte_time_ms=ctx.first_byte_time_ms, # 传递首字时间
|
first_byte_time_ms=ctx.first_byte_time_ms, # 传递首字时间
|
||||||
status_code=ctx.status_code,
|
status_code=ctx.status_code,
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
response_headers=ctx.response_headers,
|
response_headers=ctx.response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
response_body=response_body,
|
response_body=response_body,
|
||||||
|
client_response_body=client_response_body,
|
||||||
|
provider_request_body=ctx.provider_request_body,
|
||||||
cache_creation_tokens=ctx.cache_creation_tokens,
|
cache_creation_tokens=ctx.cache_creation_tokens,
|
||||||
cache_read_tokens=ctx.cached_tokens,
|
cache_read_tokens=ctx.cached_tokens,
|
||||||
is_stream=True,
|
is_stream=True,
|
||||||
@@ -487,9 +485,6 @@ class CliMonitorMixin:
|
|||||||
ctx.status_code = status_code
|
ctx.status_code = status_code
|
||||||
ctx.error_message = str(error)
|
ctx.error_message = str(error)
|
||||||
|
|
||||||
# 使用实际发送给 Provider 的请求体(如果有),否则用原始请求体
|
|
||||||
actual_request_body = ctx.provider_request_body or original_request_body
|
|
||||||
|
|
||||||
# 失败时返回给客户端的是 JSON 错误响应
|
# 失败时返回给客户端的是 JSON 错误响应
|
||||||
client_response_headers = {"content-type": "application/json"}
|
client_response_headers = {"content-type": "application/json"}
|
||||||
|
|
||||||
@@ -501,10 +496,11 @@ class CliMonitorMixin:
|
|||||||
status_code=status_code,
|
status_code=status_code,
|
||||||
error_message=extract_client_error_message(error),
|
error_message=extract_client_error_message(error),
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
is_stream=True,
|
is_stream=True,
|
||||||
api_format=ctx.api_format,
|
api_format=ctx.api_format,
|
||||||
provider_request_headers=ctx.provider_request_headers,
|
provider_request_headers=ctx.provider_request_headers,
|
||||||
|
provider_request_body=ctx.provider_request_body,
|
||||||
response_headers=ctx.response_headers,
|
response_headers=ctx.response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
# 格式转换追踪
|
# 格式转换追踪
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ class CliStreamMixin:
|
|||||||
|
|
||||||
# 重置上下文状态(重试时清除之前的数据,避免累积)
|
# 重置上下文状态(重试时清除之前的数据,避免累积)
|
||||||
ctx.parsed_chunks = []
|
ctx.parsed_chunks = []
|
||||||
|
ctx.provider_parsed_chunks = []
|
||||||
ctx.chunk_count = 0
|
ctx.chunk_count = 0
|
||||||
ctx.data_count = 0
|
ctx.data_count = 0
|
||||||
ctx.has_completion = False
|
ctx.has_completion = False
|
||||||
|
|||||||
@@ -480,6 +480,7 @@ class CliSyncMixin:
|
|||||||
response_json = {}
|
response_json = {}
|
||||||
|
|
||||||
# 跨格式:响应转换回 client_format(失败不触发 failover,保守回退为原始响应)
|
# 跨格式:响应转换回 client_format(失败不触发 failover,保守回退为原始响应)
|
||||||
|
provider_response_json: dict[str, Any] | None = None
|
||||||
if (
|
if (
|
||||||
needs_conversion
|
needs_conversion
|
||||||
and provider_api_format
|
and provider_api_format
|
||||||
@@ -487,6 +488,7 @@ class CliSyncMixin:
|
|||||||
and isinstance(response_json, dict)
|
and isinstance(response_json, dict)
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
|
provider_response_json = response_json.copy()
|
||||||
registry = get_format_converter_registry()
|
registry = get_format_converter_registry()
|
||||||
response_json = registry.convert_response(
|
response_json = registry.convert_response(
|
||||||
response_json,
|
response_json,
|
||||||
@@ -499,6 +501,7 @@ class CliSyncMixin:
|
|||||||
)
|
)
|
||||||
except Exception as conv_err:
|
except Exception as conv_err:
|
||||||
logger.warning("非流式响应格式转换失败,使用原始响应: {}", conv_err)
|
logger.warning("非流式响应格式转换失败,使用原始响应: {}", conv_err)
|
||||||
|
provider_response_json = None
|
||||||
|
|
||||||
# 使用解析器提取 usage
|
# 使用解析器提取 usage
|
||||||
usage = self.parser.extract_usage_from_response(response_json)
|
usage = self.parser.extract_usage_from_response(response_json)
|
||||||
@@ -509,9 +512,6 @@ class CliSyncMixin:
|
|||||||
|
|
||||||
output_text = self.parser.extract_text_content(response_json)[:200]
|
output_text = self.parser.extract_text_content(response_json)[:200]
|
||||||
|
|
||||||
# 使用实际发送给 Provider 的请求体(如果有),否则用原始请求体
|
|
||||||
actual_request_body = provider_request_body or original_request_body
|
|
||||||
|
|
||||||
# 非流式成功时,返回给客户端的是提供商响应头(透传)
|
# 非流式成功时,返回给客户端的是提供商响应头(透传)
|
||||||
client_response_headers = filter_proxy_response_headers(response_headers)
|
client_response_headers = filter_proxy_response_headers(response_headers)
|
||||||
client_response_headers["content-type"] = "application/json"
|
client_response_headers["content-type"] = "application/json"
|
||||||
@@ -527,10 +527,12 @@ class CliSyncMixin:
|
|||||||
response_time_ms=response_time_ms,
|
response_time_ms=response_time_ms,
|
||||||
status_code=status_code,
|
status_code=status_code,
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
response_headers=response_headers,
|
response_headers=response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
response_body=response_json,
|
response_body=provider_response_json or response_json,
|
||||||
|
client_response_body=response_json if provider_response_json else None,
|
||||||
|
provider_request_body=provider_request_body,
|
||||||
cache_creation_tokens=cache_creation_tokens,
|
cache_creation_tokens=cache_creation_tokens,
|
||||||
cache_read_tokens=cached_tokens,
|
cache_read_tokens=cached_tokens,
|
||||||
is_stream=False,
|
is_stream=False,
|
||||||
@@ -563,7 +565,6 @@ class CliSyncMixin:
|
|||||||
# Thinking 签名错误:TaskService 层已处理整流重试但仍失败
|
# Thinking 签名错误:TaskService 层已处理整流重试但仍失败
|
||||||
# 记录实际发送给 Provider 的请求体,便于排查问题根因
|
# 记录实际发送给 Provider 的请求体,便于排查问题根因
|
||||||
response_time_ms = int((time.time() - sync_start_time) * 1000)
|
response_time_ms = int((time.time() - sync_start_time) * 1000)
|
||||||
actual_request_body = provider_request_body or original_request_body
|
|
||||||
request_metadata = self._build_request_metadata() or {}
|
request_metadata = self._build_request_metadata() or {}
|
||||||
if sync_proxy_info:
|
if sync_proxy_info:
|
||||||
request_metadata["proxy"] = sync_proxy_info
|
request_metadata["proxy"] = sync_proxy_info
|
||||||
@@ -573,7 +574,8 @@ class CliSyncMixin:
|
|||||||
response_time_ms=response_time_ms,
|
response_time_ms=response_time_ms,
|
||||||
status_code=e.status_code or 400,
|
status_code=e.status_code or 400,
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
|
provider_request_body=provider_request_body,
|
||||||
error_message=str(e),
|
error_message=str(e),
|
||||||
is_stream=False,
|
is_stream=False,
|
||||||
api_format=api_format,
|
api_format=api_format,
|
||||||
@@ -592,9 +594,6 @@ class CliSyncMixin:
|
|||||||
elif isinstance(e, ProviderTimeoutException):
|
elif isinstance(e, ProviderTimeoutException):
|
||||||
status_code = 504
|
status_code = 504
|
||||||
|
|
||||||
# 使用实际发送给 Provider 的请求体(如果有),否则用原始请求体
|
|
||||||
actual_request_body = provider_request_body or original_request_body
|
|
||||||
|
|
||||||
# 尝试从异常中提取响应头
|
# 尝试从异常中提取响应头
|
||||||
error_response_headers: dict[str, str] = {}
|
error_response_headers: dict[str, str] = {}
|
||||||
if isinstance(e, ProviderRateLimitException) and e.response_headers:
|
if isinstance(e, ProviderRateLimitException) and e.response_headers:
|
||||||
@@ -612,7 +611,8 @@ class CliSyncMixin:
|
|||||||
status_code=status_code,
|
status_code=status_code,
|
||||||
error_message=extract_client_error_message(e),
|
error_message=extract_client_error_message(e),
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
|
provider_request_body=provider_request_body,
|
||||||
is_stream=False,
|
is_stream=False,
|
||||||
api_format=api_format,
|
api_format=api_format,
|
||||||
provider_request_headers=provider_request_headers,
|
provider_request_headers=provider_request_headers,
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ class StreamContext:
|
|||||||
data_count: int = 0
|
data_count: int = 0
|
||||||
chunk_count: int = 0
|
chunk_count: int = 0
|
||||||
parsed_chunks: list[dict[str, Any]] = field(default_factory=list)
|
parsed_chunks: list[dict[str, Any]] = field(default_factory=list)
|
||||||
|
# 格式转换时保留提供商原始 chunks(转换前的数据)
|
||||||
|
provider_parsed_chunks: list[dict[str, Any]] = field(default_factory=list)
|
||||||
# 是否记录 parsed_chunks(可用于降低高并发/长流式响应的内存占用)
|
# 是否记录 parsed_chunks(可用于降低高并发/长流式响应的内存占用)
|
||||||
record_parsed_chunks: bool = True
|
record_parsed_chunks: bool = True
|
||||||
|
|
||||||
@@ -143,6 +145,7 @@ class StreamContext:
|
|||||||
保留 model 和 api_format,重置其他所有状态。
|
保留 model 和 api_format,重置其他所有状态。
|
||||||
"""
|
"""
|
||||||
self.parsed_chunks = []
|
self.parsed_chunks = []
|
||||||
|
self.provider_parsed_chunks = []
|
||||||
self.chunk_count = 0
|
self.chunk_count = 0
|
||||||
self.data_count = 0
|
self.data_count = 0
|
||||||
self.has_completion = False
|
self.has_completion = False
|
||||||
@@ -299,7 +302,29 @@ class StreamContext:
|
|||||||
构建响应体元数据
|
构建响应体元数据
|
||||||
|
|
||||||
用于记录到 Usage 表的 response_body 字段。
|
用于记录到 Usage 表的 response_body 字段。
|
||||||
|
当有格式转换时,返回提供商原始 chunks;否则返回 parsed_chunks。
|
||||||
"""
|
"""
|
||||||
|
chunks = self.provider_parsed_chunks if self.provider_parsed_chunks else self.parsed_chunks
|
||||||
|
return {
|
||||||
|
"chunks": chunks,
|
||||||
|
"metadata": {
|
||||||
|
"stream": True,
|
||||||
|
"total_chunks": len(chunks),
|
||||||
|
"data_count": self.data_count,
|
||||||
|
"has_completion": self.has_completion,
|
||||||
|
"response_time_ms": response_time_ms,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build_client_response_body(self, response_time_ms: int) -> dict[str, Any] | None:
|
||||||
|
"""
|
||||||
|
构建客户端侧响应体元数据
|
||||||
|
|
||||||
|
仅当有格式转换时返回(parsed_chunks 是转换后的客户端格式);
|
||||||
|
无格式转换时返回 None(此时 parsed_chunks 已在 response_body 中)。
|
||||||
|
"""
|
||||||
|
if not self.provider_parsed_chunks:
|
||||||
|
return None
|
||||||
return {
|
return {
|
||||||
"chunks": self.parsed_chunks,
|
"chunks": self.parsed_chunks,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|||||||
@@ -263,6 +263,10 @@ class StreamProcessor:
|
|||||||
ctx.data_count += 1
|
ctx.data_count += 1
|
||||||
if ctx.record_parsed_chunks:
|
if ctx.record_parsed_chunks:
|
||||||
ctx.parsed_chunks.append(data)
|
ctx.parsed_chunks.append(data)
|
||||||
|
else:
|
||||||
|
# 格式转换场景:保留提供商原始数据
|
||||||
|
if ctx.record_parsed_chunks:
|
||||||
|
ctx.provider_parsed_chunks.append(data)
|
||||||
|
|
||||||
# 检查完成
|
# 检查完成
|
||||||
event_type = event_name or data.get("type", "")
|
event_type = event_name or data.get("type", "")
|
||||||
|
|||||||
@@ -100,8 +100,6 @@ class StreamTelemetryRecorder:
|
|||||||
writer = await self._get_telemetry_writer(bg_db, ctx, response_time_ms)
|
writer = await self._get_telemetry_writer(bg_db, ctx, response_time_ms)
|
||||||
if writer is None:
|
if writer is None:
|
||||||
return
|
return
|
||||||
actual_request_body = ctx.provider_request_body or original_request_body
|
|
||||||
|
|
||||||
# 兜底估算:流未正常完成且 token 均为 0 时,从请求体粗略估算
|
# 兜底估算:流未正常完成且 token 均为 0 时,从请求体粗略估算
|
||||||
# 覆盖 Chat Handler 路径(CLI Handler 在更早的位置已做估算,
|
# 覆盖 Chat Handler 路径(CLI Handler 在更早的位置已做估算,
|
||||||
# 若已估算过则 token > 0,此处条件不会触发)
|
# 若已估算过则 token > 0,此处条件不会触发)
|
||||||
@@ -112,7 +110,10 @@ class StreamTelemetryRecorder:
|
|||||||
and ctx.input_tokens == 0
|
and ctx.input_tokens == 0
|
||||||
and ctx.output_tokens == 0
|
and ctx.output_tokens == 0
|
||||||
):
|
):
|
||||||
self._estimate_tokens_for_incomplete_stream(ctx, actual_request_body)
|
# 用实际发给 Provider 的请求体估算 token(格式转换时与客户端请求体不同)
|
||||||
|
self._estimate_tokens_for_incomplete_stream(
|
||||||
|
ctx, ctx.provider_request_body or original_request_body
|
||||||
|
)
|
||||||
|
|
||||||
should_log_body = SystemConfigService.should_log_body(bg_db)
|
should_log_body = SystemConfigService.should_log_body(bg_db)
|
||||||
include_bodies = (
|
include_bodies = (
|
||||||
@@ -123,6 +124,9 @@ class StreamTelemetryRecorder:
|
|||||||
response_body = (
|
response_body = (
|
||||||
ctx.build_response_body(response_time_ms) if include_bodies else None
|
ctx.build_response_body(response_time_ms) if include_bodies else None
|
||||||
)
|
)
|
||||||
|
client_response_body = (
|
||||||
|
ctx.build_client_response_body(response_time_ms) if include_bodies else None
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._dispatch_record(
|
await self._dispatch_record(
|
||||||
@@ -130,9 +134,10 @@ class StreamTelemetryRecorder:
|
|||||||
writer,
|
writer,
|
||||||
ctx,
|
ctx,
|
||||||
original_headers,
|
original_headers,
|
||||||
actual_request_body,
|
original_request_body,
|
||||||
response_body,
|
response_body,
|
||||||
response_time_ms,
|
response_time_ms,
|
||||||
|
client_response_body=client_response_body,
|
||||||
)
|
)
|
||||||
except Exception as writer_error:
|
except Exception as writer_error:
|
||||||
if not isinstance(writer, QueueTelemetryWriter):
|
if not isinstance(writer, QueueTelemetryWriter):
|
||||||
@@ -151,14 +156,17 @@ class StreamTelemetryRecorder:
|
|||||||
return
|
return
|
||||||
if response_body is None and should_log_body:
|
if response_body is None and should_log_body:
|
||||||
response_body = ctx.build_response_body(response_time_ms)
|
response_body = ctx.build_response_body(response_time_ms)
|
||||||
|
if client_response_body is None and should_log_body:
|
||||||
|
client_response_body = ctx.build_client_response_body(response_time_ms)
|
||||||
await self._dispatch_record(
|
await self._dispatch_record(
|
||||||
bg_db,
|
bg_db,
|
||||||
db_writer,
|
db_writer,
|
||||||
ctx,
|
ctx,
|
||||||
original_headers,
|
original_headers,
|
||||||
actual_request_body,
|
original_request_body,
|
||||||
response_body,
|
response_body,
|
||||||
response_time_ms,
|
response_time_ms,
|
||||||
|
client_response_body=client_response_body,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 更新候选记录状态
|
# 更新候选记录状态
|
||||||
@@ -180,9 +188,10 @@ class StreamTelemetryRecorder:
|
|||||||
writer: TelemetryWriter,
|
writer: TelemetryWriter,
|
||||||
ctx: StreamContext,
|
ctx: StreamContext,
|
||||||
original_headers: dict[str, str],
|
original_headers: dict[str, str],
|
||||||
actual_request_body: dict[str, Any],
|
original_request_body: dict[str, Any],
|
||||||
response_body: dict[str, Any] | None,
|
response_body: dict[str, Any] | None,
|
||||||
response_time_ms: int,
|
response_time_ms: int,
|
||||||
|
client_response_body: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""记录成功的请求"""
|
"""记录成功的请求"""
|
||||||
# 流式成功时,返回给客户端的是提供商响应头 + SSE 必需头
|
# 流式成功时,返回给客户端的是提供商响应头 + SSE 必需头
|
||||||
@@ -209,10 +218,12 @@ class StreamTelemetryRecorder:
|
|||||||
first_byte_time_ms=ctx.first_byte_time_ms, # 传递首字时间
|
first_byte_time_ms=ctx.first_byte_time_ms, # 传递首字时间
|
||||||
status_code=ctx.status_code,
|
status_code=ctx.status_code,
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
response_headers=ctx.response_headers,
|
response_headers=ctx.response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
response_body=response_body,
|
response_body=response_body,
|
||||||
|
client_response_body=client_response_body,
|
||||||
|
provider_request_body=ctx.provider_request_body,
|
||||||
cache_creation_tokens=ctx.cache_creation_tokens,
|
cache_creation_tokens=ctx.cache_creation_tokens,
|
||||||
cache_read_tokens=ctx.cached_tokens,
|
cache_read_tokens=ctx.cached_tokens,
|
||||||
is_stream=True,
|
is_stream=True,
|
||||||
@@ -236,9 +247,10 @@ class StreamTelemetryRecorder:
|
|||||||
writer: TelemetryWriter,
|
writer: TelemetryWriter,
|
||||||
ctx: StreamContext,
|
ctx: StreamContext,
|
||||||
original_headers: dict[str, str],
|
original_headers: dict[str, str],
|
||||||
actual_request_body: dict[str, Any],
|
original_request_body: dict[str, Any],
|
||||||
response_body: dict[str, Any] | None,
|
response_body: dict[str, Any] | None,
|
||||||
response_time_ms: int,
|
response_time_ms: int,
|
||||||
|
client_response_body: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""记录失败的请求"""
|
"""记录失败的请求"""
|
||||||
# 失败时返回给客户端的是 JSON 错误响应,如果没有设置则使用默认值
|
# 失败时返回给客户端的是 JSON 错误响应,如果没有设置则使用默认值
|
||||||
@@ -258,15 +270,17 @@ class StreamTelemetryRecorder:
|
|||||||
status_code=ctx.status_code,
|
status_code=ctx.status_code,
|
||||||
error_message=ctx.error_message or f"HTTP {ctx.status_code}",
|
error_message=ctx.error_message or f"HTTP {ctx.status_code}",
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
is_stream=True,
|
is_stream=True,
|
||||||
api_format=ctx.api_format,
|
api_format=ctx.api_format,
|
||||||
provider_request_headers=ctx.provider_request_headers,
|
provider_request_headers=ctx.provider_request_headers,
|
||||||
|
provider_request_body=ctx.provider_request_body,
|
||||||
input_tokens=ctx.input_tokens,
|
input_tokens=ctx.input_tokens,
|
||||||
output_tokens=ctx.output_tokens,
|
output_tokens=ctx.output_tokens,
|
||||||
cache_creation_tokens=ctx.cache_creation_tokens,
|
cache_creation_tokens=ctx.cache_creation_tokens,
|
||||||
cache_read_tokens=ctx.cached_tokens,
|
cache_read_tokens=ctx.cached_tokens,
|
||||||
response_body=response_body,
|
response_body=response_body,
|
||||||
|
client_response_body=client_response_body,
|
||||||
response_headers=ctx.response_headers,
|
response_headers=ctx.response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
provider_id=ctx.provider_id,
|
provider_id=ctx.provider_id,
|
||||||
@@ -289,9 +303,10 @@ class StreamTelemetryRecorder:
|
|||||||
writer: TelemetryWriter,
|
writer: TelemetryWriter,
|
||||||
ctx: StreamContext,
|
ctx: StreamContext,
|
||||||
original_headers: dict[str, str],
|
original_headers: dict[str, str],
|
||||||
actual_request_body: dict[str, Any],
|
original_request_body: dict[str, Any],
|
||||||
response_body: dict[str, Any] | None,
|
response_body: dict[str, Any] | None,
|
||||||
response_time_ms: int,
|
response_time_ms: int,
|
||||||
|
client_response_body: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""记录客户端取消的请求"""
|
"""记录客户端取消的请求"""
|
||||||
client_response_headers = ctx.client_response_headers or {
|
client_response_headers = ctx.client_response_headers or {
|
||||||
@@ -310,15 +325,17 @@ class StreamTelemetryRecorder:
|
|||||||
first_byte_time_ms=ctx.first_byte_time_ms,
|
first_byte_time_ms=ctx.first_byte_time_ms,
|
||||||
status_code=ctx.status_code,
|
status_code=ctx.status_code,
|
||||||
request_headers=original_headers,
|
request_headers=original_headers,
|
||||||
request_body=actual_request_body,
|
request_body=original_request_body,
|
||||||
is_stream=True,
|
is_stream=True,
|
||||||
api_format=ctx.api_format,
|
api_format=ctx.api_format,
|
||||||
provider_request_headers=ctx.provider_request_headers,
|
provider_request_headers=ctx.provider_request_headers,
|
||||||
|
provider_request_body=ctx.provider_request_body,
|
||||||
input_tokens=ctx.input_tokens,
|
input_tokens=ctx.input_tokens,
|
||||||
output_tokens=ctx.output_tokens,
|
output_tokens=ctx.output_tokens,
|
||||||
cache_creation_tokens=ctx.cache_creation_tokens,
|
cache_creation_tokens=ctx.cache_creation_tokens,
|
||||||
cache_read_tokens=ctx.cached_tokens,
|
cache_read_tokens=ctx.cached_tokens,
|
||||||
response_body=response_body,
|
response_body=response_body,
|
||||||
|
client_response_body=client_response_body,
|
||||||
response_headers=ctx.response_headers,
|
response_headers=ctx.response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
provider_id=ctx.provider_id,
|
provider_id=ctx.provider_id,
|
||||||
@@ -481,9 +498,10 @@ class StreamTelemetryRecorder:
|
|||||||
writer: TelemetryWriter,
|
writer: TelemetryWriter,
|
||||||
ctx: StreamContext,
|
ctx: StreamContext,
|
||||||
original_headers: dict[str, str],
|
original_headers: dict[str, str],
|
||||||
actual_request_body: dict[str, Any],
|
original_request_body: dict[str, Any],
|
||||||
response_body: dict[str, Any] | None,
|
response_body: dict[str, Any] | None,
|
||||||
response_time_ms: int,
|
response_time_ms: int,
|
||||||
|
client_response_body: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""根据上下文状态分发到对应的记录方法"""
|
"""根据上下文状态分发到对应的记录方法"""
|
||||||
if ctx.is_success():
|
if ctx.is_success():
|
||||||
@@ -491,9 +509,10 @@ class StreamTelemetryRecorder:
|
|||||||
writer,
|
writer,
|
||||||
ctx,
|
ctx,
|
||||||
original_headers,
|
original_headers,
|
||||||
actual_request_body,
|
original_request_body,
|
||||||
response_body,
|
response_body,
|
||||||
response_time_ms,
|
response_time_ms,
|
||||||
|
client_response_body=client_response_body,
|
||||||
)
|
)
|
||||||
# Queue writer 异步落库可能造成 UI 延迟,先直接更新 Usage 状态
|
# Queue writer 异步落库可能造成 UI 延迟,先直接更新 Usage 状态
|
||||||
if isinstance(writer, QueueTelemetryWriter):
|
if isinstance(writer, QueueTelemetryWriter):
|
||||||
@@ -508,9 +527,10 @@ class StreamTelemetryRecorder:
|
|||||||
writer,
|
writer,
|
||||||
ctx,
|
ctx,
|
||||||
original_headers,
|
original_headers,
|
||||||
actual_request_body,
|
original_request_body,
|
||||||
response_body,
|
response_body,
|
||||||
response_time_ms,
|
response_time_ms,
|
||||||
|
client_response_body=client_response_body,
|
||||||
)
|
)
|
||||||
# Queue writer 异步落库可能造成 UI 延迟,先直接更新 Usage 状态
|
# Queue writer 异步落库可能造成 UI 延迟,先直接更新 Usage 状态
|
||||||
if isinstance(writer, QueueTelemetryWriter):
|
if isinstance(writer, QueueTelemetryWriter):
|
||||||
@@ -525,9 +545,10 @@ class StreamTelemetryRecorder:
|
|||||||
writer,
|
writer,
|
||||||
ctx,
|
ctx,
|
||||||
original_headers,
|
original_headers,
|
||||||
actual_request_body,
|
original_request_body,
|
||||||
response_body,
|
response_body,
|
||||||
response_time_ms,
|
response_time_ms,
|
||||||
|
client_response_body=client_response_body,
|
||||||
)
|
)
|
||||||
# Queue writer 异步落库可能造成 UI 延迟,先直接更新 Usage 状态
|
# Queue writer 异步落库可能造成 UI 延迟,先直接更新 Usage 状态
|
||||||
if isinstance(writer, QueueTelemetryWriter):
|
if isinstance(writer, QueueTelemetryWriter):
|
||||||
|
|||||||
@@ -392,15 +392,19 @@ class Usage(Base):
|
|||||||
|
|
||||||
# 完整请求和响应记录
|
# 完整请求和响应记录
|
||||||
request_headers = Column(JSON, nullable=True) # 客户端请求头
|
request_headers = Column(JSON, nullable=True) # 客户端请求头
|
||||||
request_body = Column(JSON, nullable=True) # 请求体(7天内未压缩)
|
request_body = Column(JSON, nullable=True) # 客户端原始请求体(7天内未压缩)
|
||||||
provider_request_headers = Column(JSON, nullable=True) # 向提供商发送的请求头
|
provider_request_headers = Column(JSON, nullable=True) # 向提供商发送的请求头
|
||||||
|
provider_request_body = Column(JSON, nullable=True) # 发给提供商的请求体(格式转换后)
|
||||||
response_headers = Column(JSON, nullable=True) # 提供商响应头
|
response_headers = Column(JSON, nullable=True) # 提供商响应头
|
||||||
|
response_body = Column(JSON, nullable=True) # 提供商原始响应体(7天内未压缩)
|
||||||
client_response_headers = Column(JSON, nullable=True) # 返回给客户端的响应头
|
client_response_headers = Column(JSON, nullable=True) # 返回给客户端的响应头
|
||||||
response_body = Column(JSON, nullable=True) # 响应体(7天内未压缩)
|
client_response_body = Column(JSON, nullable=True) # 返回给客户端的响应体(格式转换后)
|
||||||
|
|
||||||
# 压缩存储字段(7天后自动压缩到这里)
|
# 压缩存储字段(7天后自动压缩到这里)
|
||||||
request_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的请求体
|
request_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的客户端请求体
|
||||||
response_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的响应体
|
provider_request_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的提供商请求体
|
||||||
|
response_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的提供商响应体
|
||||||
|
client_response_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的客户端响应体
|
||||||
|
|
||||||
# 元数据
|
# 元数据
|
||||||
request_metadata = Column(JSON, nullable=True) # 存储额外信息
|
request_metadata = Column(JSON, nullable=True) # 存储额外信息
|
||||||
@@ -421,7 +425,7 @@ class Usage(Base):
|
|||||||
provider_api_key = relationship("ProviderAPIKey")
|
provider_api_key = relationship("ProviderAPIKey")
|
||||||
|
|
||||||
def get_request_body(self) -> Any:
|
def get_request_body(self) -> Any:
|
||||||
"""获取请求体(自动解压)"""
|
"""获取客户端原始请求体(自动解压)"""
|
||||||
if self.request_body is not None:
|
if self.request_body is not None:
|
||||||
return self.request_body
|
return self.request_body
|
||||||
if self.request_body_compressed is not None:
|
if self.request_body_compressed is not None:
|
||||||
@@ -430,8 +434,18 @@ class Usage(Base):
|
|||||||
return decompress_json(self.request_body_compressed)
|
return decompress_json(self.request_body_compressed)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_provider_request_body(self) -> Any:
|
||||||
|
"""获取发给提供商的请求体(自动解压)"""
|
||||||
|
if self.provider_request_body is not None:
|
||||||
|
return self.provider_request_body
|
||||||
|
if self.provider_request_body_compressed is not None:
|
||||||
|
from src.utils.compression import decompress_json
|
||||||
|
|
||||||
|
return decompress_json(self.provider_request_body_compressed)
|
||||||
|
return None
|
||||||
|
|
||||||
def get_response_body(self) -> Any:
|
def get_response_body(self) -> Any:
|
||||||
"""获取响应体(自动解压)"""
|
"""获取提供商原始响应体(自动解压)"""
|
||||||
if self.response_body is not None:
|
if self.response_body is not None:
|
||||||
return self.response_body
|
return self.response_body
|
||||||
if self.response_body_compressed is not None:
|
if self.response_body_compressed is not None:
|
||||||
@@ -440,6 +454,16 @@ class Usage(Base):
|
|||||||
return decompress_json(self.response_body_compressed)
|
return decompress_json(self.response_body_compressed)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_client_response_body(self) -> Any:
|
||||||
|
"""获取返回给客户端的响应体(自动解压)"""
|
||||||
|
if self.client_response_body is not None:
|
||||||
|
return self.client_response_body
|
||||||
|
if self.client_response_body_compressed is not None:
|
||||||
|
from src.utils.compression import decompress_json
|
||||||
|
|
||||||
|
return decompress_json(self.client_response_body_compressed)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class UserQuota(Base):
|
class UserQuota(Base):
|
||||||
"""用户配额历史记录"""
|
"""用户配额历史记录"""
|
||||||
|
|||||||
@@ -126,15 +126,19 @@ class Usage(Base):
|
|||||||
|
|
||||||
# 完整请求和响应记录
|
# 完整请求和响应记录
|
||||||
request_headers = Column(JSON, nullable=True) # 客户端请求头
|
request_headers = Column(JSON, nullable=True) # 客户端请求头
|
||||||
request_body = Column(JSON, nullable=True) # 请求体(7天内未压缩)
|
request_body = Column(JSON, nullable=True) # 客户端原始请求体(7天内未压缩)
|
||||||
provider_request_headers = Column(JSON, nullable=True) # 向提供商发送的请求头
|
provider_request_headers = Column(JSON, nullable=True) # 向提供商发送的请求头
|
||||||
|
provider_request_body = Column(JSON, nullable=True) # 发给提供商的请求体(格式转换后)
|
||||||
response_headers = Column(JSON, nullable=True) # 提供商响应头
|
response_headers = Column(JSON, nullable=True) # 提供商响应头
|
||||||
|
response_body = Column(JSON, nullable=True) # 提供商原始响应体(7天内未压缩)
|
||||||
client_response_headers = Column(JSON, nullable=True) # 返回给客户端的响应头
|
client_response_headers = Column(JSON, nullable=True) # 返回给客户端的响应头
|
||||||
response_body = Column(JSON, nullable=True) # 响应体(7天内未压缩)
|
client_response_body = Column(JSON, nullable=True) # 返回给客户端的响应体(格式转换后)
|
||||||
|
|
||||||
# 压缩存储字段(7天后自动压缩到这里)
|
# 压缩存储字段(7天后自动压缩到这里)
|
||||||
request_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的请求体
|
request_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的客户端请求体
|
||||||
response_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的响应体
|
provider_request_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的提供商请求体
|
||||||
|
response_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的提供商响应体
|
||||||
|
client_response_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的客户端响应体
|
||||||
|
|
||||||
# 元数据
|
# 元数据
|
||||||
request_metadata = Column(JSON, nullable=True) # 存储额外信息
|
request_metadata = Column(JSON, nullable=True) # 存储额外信息
|
||||||
@@ -155,7 +159,7 @@ class Usage(Base):
|
|||||||
provider_api_key = relationship("ProviderAPIKey")
|
provider_api_key = relationship("ProviderAPIKey")
|
||||||
|
|
||||||
def get_request_body(self) -> Any:
|
def get_request_body(self) -> Any:
|
||||||
"""获取请求体(自动解压)"""
|
"""获取客户端原始请求体(自动解压)"""
|
||||||
if self.request_body is not None:
|
if self.request_body is not None:
|
||||||
return self.request_body
|
return self.request_body
|
||||||
if self.request_body_compressed is not None:
|
if self.request_body_compressed is not None:
|
||||||
@@ -164,8 +168,18 @@ class Usage(Base):
|
|||||||
return decompress_json(self.request_body_compressed)
|
return decompress_json(self.request_body_compressed)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_provider_request_body(self) -> Any:
|
||||||
|
"""获取发给提供商的请求体(自动解压)"""
|
||||||
|
if self.provider_request_body is not None:
|
||||||
|
return self.provider_request_body
|
||||||
|
if self.provider_request_body_compressed is not None:
|
||||||
|
from src.utils.compression import decompress_json
|
||||||
|
|
||||||
|
return decompress_json(self.provider_request_body_compressed)
|
||||||
|
return None
|
||||||
|
|
||||||
def get_response_body(self) -> Any:
|
def get_response_body(self) -> Any:
|
||||||
"""获取响应体(自动解压)"""
|
"""获取提供商原始响应体(自动解压)"""
|
||||||
if self.response_body is not None:
|
if self.response_body is not None:
|
||||||
return self.response_body
|
return self.response_body
|
||||||
if self.response_body_compressed is not None:
|
if self.response_body_compressed is not None:
|
||||||
@@ -174,6 +188,16 @@ class Usage(Base):
|
|||||||
return decompress_json(self.response_body_compressed)
|
return decompress_json(self.response_body_compressed)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_client_response_body(self) -> Any:
|
||||||
|
"""获取返回给客户端的响应体(自动解压)"""
|
||||||
|
if self.client_response_body is not None:
|
||||||
|
return self.client_response_body
|
||||||
|
if self.client_response_body_compressed is not None:
|
||||||
|
from src.utils.compression import decompress_json
|
||||||
|
|
||||||
|
return decompress_json(self.client_response_body_compressed)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class RequestCandidate(Base):
|
class RequestCandidate(Base):
|
||||||
"""请求候选记录 - 追踪所有候选(包括未使用的)"""
|
"""请求候选记录 - 追踪所有候选(包括未使用的)"""
|
||||||
|
|||||||
@@ -928,9 +928,20 @@ class MaintenanceScheduler:
|
|||||||
# 1. 查询需要压缩的记录
|
# 1. 查询需要压缩的记录
|
||||||
# 注意:排除已经是 NULL 或 JSON null 的记录
|
# 注意:排除已经是 NULL 或 JSON null 的记录
|
||||||
records = (
|
records = (
|
||||||
batch_db.query(Usage.id, Usage.request_body, Usage.response_body)
|
batch_db.query(
|
||||||
|
Usage.id,
|
||||||
|
Usage.request_body,
|
||||||
|
Usage.response_body,
|
||||||
|
Usage.provider_request_body,
|
||||||
|
Usage.client_response_body,
|
||||||
|
)
|
||||||
.filter(Usage.created_at < cutoff_time)
|
.filter(Usage.created_at < cutoff_time)
|
||||||
.filter((Usage.request_body.isnot(None)) | (Usage.response_body.isnot(None)))
|
.filter(
|
||||||
|
(Usage.request_body.isnot(None))
|
||||||
|
| (Usage.response_body.isnot(None))
|
||||||
|
| (Usage.provider_request_body.isnot(None))
|
||||||
|
| (Usage.client_response_body.isnot(None))
|
||||||
|
)
|
||||||
.limit(batch_size)
|
.limit(batch_size)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
@@ -940,9 +951,12 @@ class MaintenanceScheduler:
|
|||||||
|
|
||||||
# 过滤掉实际值为 None 的记录(JSON null 被解析为 Python None)
|
# 过滤掉实际值为 None 的记录(JSON null 被解析为 Python None)
|
||||||
valid_records = [
|
valid_records = [
|
||||||
(rid, req, resp)
|
r
|
||||||
for rid, req, resp in records
|
for r in records
|
||||||
if req is not None or resp is not None
|
if r.request_body is not None
|
||||||
|
or r.response_body is not None
|
||||||
|
or r.provider_request_body is not None
|
||||||
|
or r.client_response_body is not None
|
||||||
]
|
]
|
||||||
|
|
||||||
if not valid_records:
|
if not valid_records:
|
||||||
@@ -950,17 +964,22 @@ class MaintenanceScheduler:
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
f"检测到 {len(records)} 条记录的 body 字段为 JSON null,进行清理"
|
f"检测到 {len(records)} 条记录的 body 字段为 JSON null,进行清理"
|
||||||
)
|
)
|
||||||
for record_id, _, _ in records:
|
for r in records:
|
||||||
batch_db.execute(
|
batch_db.execute(
|
||||||
update(Usage)
|
update(Usage)
|
||||||
.where(Usage.id == record_id)
|
.where(Usage.id == r.id)
|
||||||
.values(request_body=null(), response_body=null())
|
.values(
|
||||||
|
request_body=null(),
|
||||||
|
response_body=null(),
|
||||||
|
provider_request_body=null(),
|
||||||
|
client_response_body=null(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
batch_db.commit()
|
batch_db.commit()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 检测是否有重复的 ID(说明更新未生效)
|
# 检测是否有重复的 ID(说明更新未生效)
|
||||||
current_ids = {r[0] for r in valid_records}
|
current_ids = {r.id for r in valid_records}
|
||||||
repeated_ids = current_ids & processed_ids
|
repeated_ids = current_ids & processed_ids
|
||||||
if repeated_ids:
|
if repeated_ids:
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -972,28 +991,40 @@ class MaintenanceScheduler:
|
|||||||
batch_success = 0
|
batch_success = 0
|
||||||
|
|
||||||
# 2. 逐条更新(确保每条都正确处理)
|
# 2. 逐条更新(确保每条都正确处理)
|
||||||
for record_id, req_body, resp_body in valid_records:
|
for r in valid_records:
|
||||||
try:
|
try:
|
||||||
# 使用 null() 确保设置的是 SQL NULL 而不是 JSON null
|
# 使用 null() 确保设置的是 SQL NULL 而不是 JSON null
|
||||||
result = batch_db.execute(
|
result = batch_db.execute(
|
||||||
update(Usage)
|
update(Usage)
|
||||||
.where(Usage.id == record_id)
|
.where(Usage.id == r.id)
|
||||||
.values(
|
.values(
|
||||||
request_body=null(),
|
request_body=null(),
|
||||||
response_body=null(),
|
response_body=null(),
|
||||||
|
provider_request_body=null(),
|
||||||
|
client_response_body=null(),
|
||||||
request_body_compressed=(
|
request_body_compressed=(
|
||||||
compress_json(req_body) if req_body else None
|
compress_json(r.request_body) if r.request_body else None
|
||||||
),
|
),
|
||||||
response_body_compressed=(
|
response_body_compressed=(
|
||||||
compress_json(resp_body) if resp_body else None
|
compress_json(r.response_body) if r.response_body else None
|
||||||
|
),
|
||||||
|
provider_request_body_compressed=(
|
||||||
|
compress_json(r.provider_request_body)
|
||||||
|
if r.provider_request_body
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
client_response_body_compressed=(
|
||||||
|
compress_json(r.client_response_body)
|
||||||
|
if r.client_response_body
|
||||||
|
else None
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if result.rowcount > 0:
|
if result.rowcount > 0:
|
||||||
batch_success += 1
|
batch_success += 1
|
||||||
processed_ids.add(record_id)
|
processed_ids.add(r.id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"压缩记录 {record_id} 失败: {e}")
|
logger.warning(f"压缩记录 {r.id} 失败: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
batch_db.commit()
|
batch_db.commit()
|
||||||
@@ -1050,6 +1081,8 @@ class MaintenanceScheduler:
|
|||||||
.filter(
|
.filter(
|
||||||
(Usage.request_body_compressed.isnot(None))
|
(Usage.request_body_compressed.isnot(None))
|
||||||
| (Usage.response_body_compressed.isnot(None))
|
| (Usage.response_body_compressed.isnot(None))
|
||||||
|
| (Usage.provider_request_body_compressed.isnot(None))
|
||||||
|
| (Usage.client_response_body_compressed.isnot(None))
|
||||||
)
|
)
|
||||||
.limit(batch_size)
|
.limit(batch_size)
|
||||||
.all()
|
.all()
|
||||||
@@ -1067,6 +1100,8 @@ class MaintenanceScheduler:
|
|||||||
.values(
|
.values(
|
||||||
request_body_compressed=null(),
|
request_body_compressed=null(),
|
||||||
response_body_compressed=null(),
|
response_body_compressed=null(),
|
||||||
|
provider_request_body_compressed=null(),
|
||||||
|
client_response_body_compressed=null(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -146,9 +146,11 @@ class UsageBillingIntegrationMixin:
|
|||||||
request_headers=params.request_headers,
|
request_headers=params.request_headers,
|
||||||
request_body=params.request_body,
|
request_body=params.request_body,
|
||||||
provider_request_headers=params.provider_request_headers,
|
provider_request_headers=params.provider_request_headers,
|
||||||
|
provider_request_body=params.provider_request_body,
|
||||||
response_headers=params.response_headers,
|
response_headers=params.response_headers,
|
||||||
client_response_headers=params.client_response_headers,
|
client_response_headers=params.client_response_headers,
|
||||||
response_body=params.response_body,
|
response_body=params.response_body,
|
||||||
|
client_response_body=params.client_response_body,
|
||||||
request_id=params.request_id,
|
request_id=params.request_id,
|
||||||
provider_id=params.provider_id,
|
provider_id=params.provider_id,
|
||||||
provider_endpoint_id=params.provider_endpoint_id,
|
provider_endpoint_id=params.provider_endpoint_id,
|
||||||
|
|||||||
@@ -56,9 +56,11 @@ def build_usage_params(
|
|||||||
request_headers: dict[str, Any] | None,
|
request_headers: dict[str, Any] | None,
|
||||||
request_body: Any | None,
|
request_body: Any | None,
|
||||||
provider_request_headers: dict[str, Any] | None,
|
provider_request_headers: dict[str, Any] | None,
|
||||||
|
provider_request_body: Any | None,
|
||||||
response_headers: dict[str, Any] | None,
|
response_headers: dict[str, Any] | None,
|
||||||
client_response_headers: dict[str, Any] | None,
|
client_response_headers: dict[str, Any] | None,
|
||||||
response_body: Any | None,
|
response_body: Any | None,
|
||||||
|
client_response_body: Any | None,
|
||||||
request_id: str,
|
request_id: str,
|
||||||
provider_id: str | None,
|
provider_id: str | None,
|
||||||
provider_endpoint_id: str | None,
|
provider_endpoint_id: str | None,
|
||||||
@@ -103,16 +105,26 @@ def build_usage_params(
|
|||||||
|
|
||||||
# 处理请求体和响应体(可能需要截断)
|
# 处理请求体和响应体(可能需要截断)
|
||||||
processed_request_body = None
|
processed_request_body = None
|
||||||
|
processed_provider_request_body = None
|
||||||
processed_response_body = None
|
processed_response_body = None
|
||||||
|
processed_client_response_body = None
|
||||||
if should_log_body:
|
if should_log_body:
|
||||||
if request_body:
|
if request_body:
|
||||||
processed_request_body = SystemConfigService.truncate_body(
|
processed_request_body = SystemConfigService.truncate_body(
|
||||||
db, request_body, is_request=True
|
db, request_body, is_request=True
|
||||||
)
|
)
|
||||||
|
if provider_request_body:
|
||||||
|
processed_provider_request_body = SystemConfigService.truncate_body(
|
||||||
|
db, provider_request_body, is_request=True
|
||||||
|
)
|
||||||
if response_body:
|
if response_body:
|
||||||
processed_response_body = SystemConfigService.truncate_body(
|
processed_response_body = SystemConfigService.truncate_body(
|
||||||
db, response_body, is_request=False
|
db, response_body, is_request=False
|
||||||
)
|
)
|
||||||
|
if client_response_body:
|
||||||
|
processed_client_response_body = SystemConfigService.truncate_body(
|
||||||
|
db, client_response_body, is_request=False
|
||||||
|
)
|
||||||
|
|
||||||
# 处理响应头
|
# 处理响应头
|
||||||
processed_response_headers = None
|
processed_response_headers = None
|
||||||
@@ -197,9 +209,11 @@ def build_usage_params(
|
|||||||
"request_headers": processed_request_headers,
|
"request_headers": processed_request_headers,
|
||||||
"request_body": processed_request_body,
|
"request_body": processed_request_body,
|
||||||
"provider_request_headers": processed_provider_request_headers,
|
"provider_request_headers": processed_provider_request_headers,
|
||||||
|
"provider_request_body": processed_provider_request_body,
|
||||||
"response_headers": processed_response_headers,
|
"response_headers": processed_response_headers,
|
||||||
"client_response_headers": processed_client_response_headers,
|
"client_response_headers": processed_client_response_headers,
|
||||||
"response_body": processed_response_body,
|
"response_body": processed_response_body,
|
||||||
|
"client_response_body": processed_client_response_body,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -231,9 +245,12 @@ def update_existing_usage(
|
|||||||
existing_usage.request_body = usage_params["request_body"]
|
existing_usage.request_body = usage_params["request_body"]
|
||||||
if usage_params["provider_request_headers"] is not None:
|
if usage_params["provider_request_headers"] is not None:
|
||||||
existing_usage.provider_request_headers = usage_params["provider_request_headers"]
|
existing_usage.provider_request_headers = usage_params["provider_request_headers"]
|
||||||
|
if usage_params["provider_request_body"] is not None:
|
||||||
|
existing_usage.provider_request_body = usage_params["provider_request_body"]
|
||||||
existing_usage.response_body = usage_params["response_body"]
|
existing_usage.response_body = usage_params["response_body"]
|
||||||
existing_usage.response_headers = usage_params["response_headers"]
|
existing_usage.response_headers = usage_params["response_headers"]
|
||||||
existing_usage.client_response_headers = usage_params["client_response_headers"]
|
existing_usage.client_response_headers = usage_params["client_response_headers"]
|
||||||
|
existing_usage.client_response_body = usage_params["client_response_body"]
|
||||||
|
|
||||||
# 更新 token 和费用信息
|
# 更新 token 和费用信息
|
||||||
existing_usage.input_tokens = usage_params["input_tokens"]
|
existing_usage.input_tokens = usage_params["input_tokens"]
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ class UsageRecordParams:
|
|||||||
request_headers: dict[str, Any] | None
|
request_headers: dict[str, Any] | None
|
||||||
request_body: Any | None
|
request_body: Any | None
|
||||||
provider_request_headers: dict[str, Any] | None
|
provider_request_headers: dict[str, Any] | None
|
||||||
|
provider_request_body: Any | None
|
||||||
response_headers: dict[str, Any] | None
|
response_headers: dict[str, Any] | None
|
||||||
client_response_headers: dict[str, Any] | None
|
client_response_headers: dict[str, Any] | None
|
||||||
response_body: Any | None
|
response_body: Any | None
|
||||||
|
client_response_body: Any | None
|
||||||
request_id: str
|
request_id: str
|
||||||
provider_id: str | None
|
provider_id: str | None
|
||||||
provider_endpoint_id: str | None
|
provider_endpoint_id: str | None
|
||||||
|
|||||||
@@ -81,9 +81,11 @@ def _event_to_record(event: UsageEvent) -> dict[str, Any]:
|
|||||||
"request_headers": data.get("request_headers"),
|
"request_headers": data.get("request_headers"),
|
||||||
"request_body": _parse_body(data.get("request_body")),
|
"request_body": _parse_body(data.get("request_body")),
|
||||||
"provider_request_headers": data.get("provider_request_headers"),
|
"provider_request_headers": data.get("provider_request_headers"),
|
||||||
|
"provider_request_body": _parse_body(data.get("provider_request_body")),
|
||||||
"response_headers": data.get("response_headers"),
|
"response_headers": data.get("response_headers"),
|
||||||
"client_response_headers": data.get("client_response_headers"),
|
"client_response_headers": data.get("client_response_headers"),
|
||||||
"response_body": _parse_body(data.get("response_body")),
|
"response_body": _parse_body(data.get("response_body")),
|
||||||
|
"client_response_body": _parse_body(data.get("client_response_body")),
|
||||||
"provider_id": data.get("provider_id"),
|
"provider_id": data.get("provider_id"),
|
||||||
"provider_endpoint_id": data.get("provider_endpoint_id"),
|
"provider_endpoint_id": data.get("provider_endpoint_id"),
|
||||||
"provider_api_key_id": data.get("provider_api_key_id"),
|
"provider_api_key_id": data.get("provider_api_key_id"),
|
||||||
@@ -464,9 +466,11 @@ class UsageQueueConsumer:
|
|||||||
request_headers=data.get("request_headers"),
|
request_headers=data.get("request_headers"),
|
||||||
request_body=_parse_body(data.get("request_body")),
|
request_body=_parse_body(data.get("request_body")),
|
||||||
provider_request_headers=data.get("provider_request_headers"),
|
provider_request_headers=data.get("provider_request_headers"),
|
||||||
|
provider_request_body=_parse_body(data.get("provider_request_body")),
|
||||||
response_headers=data.get("response_headers"),
|
response_headers=data.get("response_headers"),
|
||||||
client_response_headers=data.get("client_response_headers"),
|
client_response_headers=data.get("client_response_headers"),
|
||||||
response_body=_parse_body(data.get("response_body")),
|
response_body=_parse_body(data.get("response_body")),
|
||||||
|
client_response_body=_parse_body(data.get("client_response_body")),
|
||||||
request_id=event.request_id,
|
request_id=event.request_id,
|
||||||
provider_id=data.get("provider_id"),
|
provider_id=data.get("provider_id"),
|
||||||
provider_endpoint_id=data.get("provider_endpoint_id"),
|
provider_endpoint_id=data.get("provider_endpoint_id"),
|
||||||
|
|||||||
@@ -78,9 +78,11 @@ class UsageRecordingMixin(UsageBillingIntegrationMixin):
|
|||||||
request_headers: dict[str, Any] | None = None,
|
request_headers: dict[str, Any] | None = None,
|
||||||
request_body: Any | None = None,
|
request_body: Any | None = None,
|
||||||
provider_request_headers: dict[str, Any] | None = None,
|
provider_request_headers: dict[str, Any] | None = None,
|
||||||
|
provider_request_body: Any | None = None,
|
||||||
response_headers: dict[str, Any] | None = None,
|
response_headers: dict[str, Any] | None = None,
|
||||||
client_response_headers: dict[str, Any] | None = None,
|
client_response_headers: dict[str, Any] | None = None,
|
||||||
response_body: Any | None = None,
|
response_body: Any | None = None,
|
||||||
|
client_response_body: Any | None = None,
|
||||||
request_id: str | None = None,
|
request_id: str | None = None,
|
||||||
provider_id: str | None = None,
|
provider_id: str | None = None,
|
||||||
provider_endpoint_id: str | None = None,
|
provider_endpoint_id: str | None = None,
|
||||||
@@ -125,9 +127,11 @@ class UsageRecordingMixin(UsageBillingIntegrationMixin):
|
|||||||
request_headers=request_headers,
|
request_headers=request_headers,
|
||||||
request_body=request_body,
|
request_body=request_body,
|
||||||
provider_request_headers=provider_request_headers,
|
provider_request_headers=provider_request_headers,
|
||||||
|
provider_request_body=provider_request_body,
|
||||||
response_headers=response_headers,
|
response_headers=response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
response_body=response_body,
|
response_body=response_body,
|
||||||
|
client_response_body=client_response_body,
|
||||||
request_id=request_id,
|
request_id=request_id,
|
||||||
provider_id=provider_id,
|
provider_id=provider_id,
|
||||||
provider_endpoint_id=provider_endpoint_id,
|
provider_endpoint_id=provider_endpoint_id,
|
||||||
@@ -196,9 +200,11 @@ class UsageRecordingMixin(UsageBillingIntegrationMixin):
|
|||||||
request_headers: dict[str, Any] | None = None,
|
request_headers: dict[str, Any] | None = None,
|
||||||
request_body: Any | None = None,
|
request_body: Any | None = None,
|
||||||
provider_request_headers: dict[str, Any] | None = None,
|
provider_request_headers: dict[str, Any] | None = None,
|
||||||
|
provider_request_body: Any | None = None,
|
||||||
response_headers: dict[str, Any] | None = None,
|
response_headers: dict[str, Any] | None = None,
|
||||||
client_response_headers: dict[str, Any] | None = None,
|
client_response_headers: dict[str, Any] | None = None,
|
||||||
response_body: Any | None = None,
|
response_body: Any | None = None,
|
||||||
|
client_response_body: Any | None = None,
|
||||||
request_id: str | None = None,
|
request_id: str | None = None,
|
||||||
provider_id: str | None = None,
|
provider_id: str | None = None,
|
||||||
provider_endpoint_id: str | None = None,
|
provider_endpoint_id: str | None = None,
|
||||||
@@ -245,9 +251,11 @@ class UsageRecordingMixin(UsageBillingIntegrationMixin):
|
|||||||
request_headers=request_headers,
|
request_headers=request_headers,
|
||||||
request_body=request_body,
|
request_body=request_body,
|
||||||
provider_request_headers=provider_request_headers,
|
provider_request_headers=provider_request_headers,
|
||||||
|
provider_request_body=provider_request_body,
|
||||||
response_headers=response_headers,
|
response_headers=response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
response_body=response_body,
|
response_body=response_body,
|
||||||
|
client_response_body=client_response_body,
|
||||||
request_id=request_id,
|
request_id=request_id,
|
||||||
provider_id=provider_id,
|
provider_id=provider_id,
|
||||||
provider_endpoint_id=provider_endpoint_id,
|
provider_endpoint_id=provider_endpoint_id,
|
||||||
@@ -383,9 +391,11 @@ class UsageRecordingMixin(UsageBillingIntegrationMixin):
|
|||||||
request_headers: dict[str, Any] | None = None,
|
request_headers: dict[str, Any] | None = None,
|
||||||
request_body: Any | None = None,
|
request_body: Any | None = None,
|
||||||
provider_request_headers: dict[str, Any] | None = None,
|
provider_request_headers: dict[str, Any] | None = None,
|
||||||
|
provider_request_body: Any | None = None,
|
||||||
response_headers: dict[str, Any] | None = None,
|
response_headers: dict[str, Any] | None = None,
|
||||||
client_response_headers: dict[str, Any] | None = None,
|
client_response_headers: dict[str, Any] | None = None,
|
||||||
response_body: Any | None = None,
|
response_body: Any | None = None,
|
||||||
|
client_response_body: Any | None = None,
|
||||||
request_id: str | None = None,
|
request_id: str | None = None,
|
||||||
provider_id: str | None = None,
|
provider_id: str | None = None,
|
||||||
provider_endpoint_id: str | None = None,
|
provider_endpoint_id: str | None = None,
|
||||||
@@ -444,9 +454,11 @@ class UsageRecordingMixin(UsageBillingIntegrationMixin):
|
|||||||
request_headers=request_headers,
|
request_headers=request_headers,
|
||||||
request_body=request_body,
|
request_body=request_body,
|
||||||
provider_request_headers=provider_request_headers,
|
provider_request_headers=provider_request_headers,
|
||||||
|
provider_request_body=provider_request_body,
|
||||||
response_headers=response_headers,
|
response_headers=response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
response_body=response_body,
|
response_body=response_body,
|
||||||
|
client_response_body=client_response_body,
|
||||||
request_id=request_id,
|
request_id=request_id,
|
||||||
provider_id=provider_id,
|
provider_id=provider_id,
|
||||||
provider_endpoint_id=provider_endpoint_id,
|
provider_endpoint_id=provider_endpoint_id,
|
||||||
@@ -743,9 +755,11 @@ class UsageRecordingMixin(UsageBillingIntegrationMixin):
|
|||||||
request_headers=record.get("request_headers"),
|
request_headers=record.get("request_headers"),
|
||||||
request_body=record.get("request_body"),
|
request_body=record.get("request_body"),
|
||||||
provider_request_headers=record.get("provider_request_headers"),
|
provider_request_headers=record.get("provider_request_headers"),
|
||||||
|
provider_request_body=record.get("provider_request_body"),
|
||||||
response_headers=record.get("response_headers"),
|
response_headers=record.get("response_headers"),
|
||||||
client_response_headers=record.get("client_response_headers"),
|
client_response_headers=record.get("client_response_headers"),
|
||||||
response_body=record.get("response_body"),
|
response_body=record.get("response_body"),
|
||||||
|
client_response_body=record.get("client_response_body"),
|
||||||
request_id=request_id,
|
request_id=request_id,
|
||||||
provider_id=record.get("provider_id"),
|
provider_id=record.get("provider_id"),
|
||||||
provider_endpoint_id=record.get("provider_endpoint_id"),
|
provider_endpoint_id=record.get("provider_endpoint_id"),
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ class MessageTelemetry:
|
|||||||
cache_read_tokens: int = 0,
|
cache_read_tokens: int = 0,
|
||||||
is_stream: bool = False,
|
is_stream: bool = False,
|
||||||
provider_request_headers: dict[str, Any] | None = None,
|
provider_request_headers: dict[str, Any] | None = None,
|
||||||
|
provider_request_body: Any | None = None,
|
||||||
|
client_response_body: Any | None = None,
|
||||||
# 时间指标
|
# 时间指标
|
||||||
first_byte_time_ms: int | None = None, # 首字时间/TTFB
|
first_byte_time_ms: int | None = None, # 首字时间/TTFB
|
||||||
# Provider 侧追踪信息(用于记录真实成本)
|
# Provider 侧追踪信息(用于记录真实成本)
|
||||||
@@ -117,9 +119,11 @@ class MessageTelemetry:
|
|||||||
request_headers=request_headers,
|
request_headers=request_headers,
|
||||||
request_body=request_body,
|
request_body=request_body,
|
||||||
provider_request_headers=provider_request_headers or {},
|
provider_request_headers=provider_request_headers or {},
|
||||||
|
provider_request_body=provider_request_body,
|
||||||
response_headers=response_headers,
|
response_headers=response_headers,
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
response_body=response_body,
|
response_body=response_body,
|
||||||
|
client_response_body=client_response_body,
|
||||||
request_id=self.request_id,
|
request_id=self.request_id,
|
||||||
# Provider 侧追踪信息(用于记录真实成本)
|
# Provider 侧追踪信息(用于记录真实成本)
|
||||||
provider_id=provider_id,
|
provider_id=provider_id,
|
||||||
@@ -164,6 +168,7 @@ class MessageTelemetry:
|
|||||||
is_stream: bool,
|
is_stream: bool,
|
||||||
api_format: str | None = None,
|
api_format: str | None = None,
|
||||||
provider_request_headers: dict[str, Any] | None = None,
|
provider_request_headers: dict[str, Any] | None = None,
|
||||||
|
provider_request_body: Any | None = None,
|
||||||
# 预估 token 信息(来自 message_start 事件,用于中断请求的成本估算)
|
# 预估 token 信息(来自 message_start 事件,用于中断请求的成本估算)
|
||||||
input_tokens: int = 0,
|
input_tokens: int = 0,
|
||||||
output_tokens: int = 0,
|
output_tokens: int = 0,
|
||||||
@@ -172,6 +177,7 @@ class MessageTelemetry:
|
|||||||
response_body: dict[str, Any] | None = None,
|
response_body: dict[str, Any] | None = None,
|
||||||
response_headers: dict[str, Any] | None = None,
|
response_headers: dict[str, Any] | None = None,
|
||||||
client_response_headers: dict[str, Any] | None = None,
|
client_response_headers: dict[str, Any] | None = None,
|
||||||
|
client_response_body: Any | None = None,
|
||||||
# Provider 侧追踪信息(用于 curl 复现等场景)
|
# Provider 侧追踪信息(用于 curl 复现等场景)
|
||||||
provider_id: str | None = None,
|
provider_id: str | None = None,
|
||||||
provider_endpoint_id: str | None = None,
|
provider_endpoint_id: str | None = None,
|
||||||
@@ -225,9 +231,11 @@ class MessageTelemetry:
|
|||||||
request_headers=request_headers,
|
request_headers=request_headers,
|
||||||
request_body=request_body,
|
request_body=request_body,
|
||||||
provider_request_headers=provider_request_headers or {},
|
provider_request_headers=provider_request_headers or {},
|
||||||
|
provider_request_body=provider_request_body,
|
||||||
response_headers=response_headers or {},
|
response_headers=response_headers or {},
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
response_body=response_body or {"error": error_message},
|
response_body=response_body or {"error": error_message},
|
||||||
|
client_response_body=client_response_body,
|
||||||
request_id=self.request_id,
|
request_id=self.request_id,
|
||||||
# Provider 侧追踪信息
|
# Provider 侧追踪信息
|
||||||
provider_id=provider_id,
|
provider_id=provider_id,
|
||||||
@@ -252,6 +260,7 @@ class MessageTelemetry:
|
|||||||
is_stream: bool,
|
is_stream: bool,
|
||||||
api_format: str | None = None,
|
api_format: str | None = None,
|
||||||
provider_request_headers: dict[str, Any] | None = None,
|
provider_request_headers: dict[str, Any] | None = None,
|
||||||
|
provider_request_body: Any | None = None,
|
||||||
input_tokens: int = 0,
|
input_tokens: int = 0,
|
||||||
output_tokens: int = 0,
|
output_tokens: int = 0,
|
||||||
cache_creation_tokens: int = 0,
|
cache_creation_tokens: int = 0,
|
||||||
@@ -259,6 +268,7 @@ class MessageTelemetry:
|
|||||||
response_body: dict[str, Any] | None = None,
|
response_body: dict[str, Any] | None = None,
|
||||||
response_headers: dict[str, Any] | None = None,
|
response_headers: dict[str, Any] | None = None,
|
||||||
client_response_headers: dict[str, Any] | None = None,
|
client_response_headers: dict[str, Any] | None = None,
|
||||||
|
client_response_body: Any | None = None,
|
||||||
# Provider 侧追踪信息
|
# Provider 侧追踪信息
|
||||||
provider_id: str | None = None,
|
provider_id: str | None = None,
|
||||||
provider_endpoint_id: str | None = None,
|
provider_endpoint_id: str | None = None,
|
||||||
@@ -299,9 +309,11 @@ class MessageTelemetry:
|
|||||||
request_headers=request_headers,
|
request_headers=request_headers,
|
||||||
request_body=request_body,
|
request_body=request_body,
|
||||||
provider_request_headers=provider_request_headers or {},
|
provider_request_headers=provider_request_headers or {},
|
||||||
|
provider_request_body=provider_request_body,
|
||||||
response_headers=response_headers or {},
|
response_headers=response_headers or {},
|
||||||
client_response_headers=client_response_headers,
|
client_response_headers=client_response_headers,
|
||||||
response_body=response_body or {},
|
response_body=response_body or {},
|
||||||
|
client_response_body=client_response_body,
|
||||||
request_id=self.request_id,
|
request_id=self.request_id,
|
||||||
# Provider 侧追踪信息
|
# Provider 侧追踪信息
|
||||||
provider_id=provider_id,
|
provider_id=provider_id,
|
||||||
|
|||||||
@@ -268,14 +268,28 @@ class QueueTelemetryWriter(TelemetryWriter):
|
|||||||
max_size=self._max_request_body_size,
|
max_size=self._max_request_body_size,
|
||||||
is_request=True,
|
is_request=True,
|
||||||
)
|
)
|
||||||
|
provider_request_body = self._truncate_body(
|
||||||
|
kwargs.get("provider_request_body"),
|
||||||
|
max_size=self._max_request_body_size,
|
||||||
|
is_request=True,
|
||||||
|
)
|
||||||
response_body = self._truncate_body(
|
response_body = self._truncate_body(
|
||||||
kwargs.get("response_body"),
|
kwargs.get("response_body"),
|
||||||
max_size=self._max_response_body_size,
|
max_size=self._max_response_body_size,
|
||||||
is_request=False,
|
is_request=False,
|
||||||
)
|
)
|
||||||
|
client_response_body = self._truncate_body(
|
||||||
|
kwargs.get("client_response_body"),
|
||||||
|
max_size=self._max_response_body_size,
|
||||||
|
is_request=False,
|
||||||
|
)
|
||||||
if request_body is not None:
|
if request_body is not None:
|
||||||
data["request_body"] = request_body
|
data["request_body"] = request_body
|
||||||
|
if provider_request_body is not None:
|
||||||
|
data["provider_request_body"] = provider_request_body
|
||||||
if response_body is not None:
|
if response_body is not None:
|
||||||
data["response_body"] = response_body
|
data["response_body"] = response_body
|
||||||
|
if client_response_body is not None:
|
||||||
|
data["client_response_body"] = client_response_body
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|||||||
Reference in New Issue
Block a user