mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat(provider): 模型测试支持自定义请求头,优化对话框布局与并发策略
- 前后端新增 request_headers 字段,测试时可自定义额外请求头 - ModelTestDialog 拆分为请求头/请求体并排双栏布局,增加格式化与重置按钮 - 区分 Pool 托管(并发5)和单 Key Provider(并发1)的测试并发数 - JsonImportInput 新增 multiple prop 支持单文件模式 - KeyFormDialog Service Account 输入改用 JsonImportInput,支持拖拽导入
This commit is contained in:
@@ -194,6 +194,7 @@ export interface TestModelFailoverRequest {
|
|||||||
api_format?: string
|
api_format?: string
|
||||||
endpoint_id?: string
|
endpoint_id?: string
|
||||||
message?: string
|
message?: string
|
||||||
|
request_headers?: Record<string, unknown>
|
||||||
request_body?: Record<string, unknown>
|
request_body?: Record<string, unknown>
|
||||||
request_id?: string
|
request_id?: string
|
||||||
concurrency?: number
|
concurrency?: number
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
ref="fileInputRef"
|
ref="fileInputRef"
|
||||||
type="file"
|
type="file"
|
||||||
:accept="accept"
|
:accept="accept"
|
||||||
multiple
|
:multiple="multiple"
|
||||||
class="hidden"
|
class="hidden"
|
||||||
@change="handleFileSelect"
|
@change="handleFileSelect"
|
||||||
>
|
>
|
||||||
@@ -94,6 +94,7 @@ const props = withDefaults(defineProps<{
|
|||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
resetKey?: string | number
|
resetKey?: string | number
|
||||||
accept?: string
|
accept?: string
|
||||||
|
multiple?: boolean
|
||||||
dropTitle?: string
|
dropTitle?: string
|
||||||
dropHint?: string
|
dropHint?: string
|
||||||
manualLabel?: string
|
manualLabel?: string
|
||||||
@@ -106,6 +107,7 @@ const props = withDefaults(defineProps<{
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
resetKey: '',
|
resetKey: '',
|
||||||
accept: '.json,.txt',
|
accept: '.json,.txt',
|
||||||
|
multiple: true,
|
||||||
dropTitle: '拖入导入文件或点击选择',
|
dropTitle: '拖入导入文件或点击选择',
|
||||||
dropHint: '支持 .json / .txt,可多选',
|
dropHint: '支持 .json / .txt,可多选',
|
||||||
manualLabel: '',
|
manualLabel: '',
|
||||||
@@ -218,13 +220,19 @@ function mergeFileContents(contents: string[]): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function readFiles(files: File[]) {
|
async function readFiles(files: File[]) {
|
||||||
const validFiles = files.filter(isValidFileType)
|
const sourceFiles = props.multiple ? files : files.slice(0, 1)
|
||||||
|
|
||||||
|
if (!props.multiple && files.length > 1) {
|
||||||
|
emitError('仅支持选择 1 个文件,已读取第一个文件', '提示')
|
||||||
|
}
|
||||||
|
|
||||||
|
const validFiles = sourceFiles.filter(isValidFileType)
|
||||||
if (validFiles.length === 0) {
|
if (validFiles.length === 0) {
|
||||||
emitError('仅支持 .json 或 .txt 文件', '格式错误')
|
emitError('仅支持 .json 或 .txt 文件', '格式错误')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (validFiles.length < files.length) {
|
if (validFiles.length < sourceFiles.length) {
|
||||||
emitError(`已忽略 ${files.length - validFiles.length} 个不支持的文件`, '提示')
|
emitError(`已忽略 ${sourceFiles.length - validFiles.length} 个不支持的文件`, '提示')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface StartTestParams {
|
|||||||
apiFormat?: string
|
apiFormat?: string
|
||||||
endpointId?: string
|
endpointId?: string
|
||||||
message?: string
|
message?: string
|
||||||
|
requestHeaders?: Record<string, unknown>
|
||||||
requestBody?: Record<string, unknown>
|
requestBody?: Record<string, unknown>
|
||||||
concurrency?: number
|
concurrency?: number
|
||||||
onSuccess?: (result: TestModelFailoverResponse) => void
|
onSuccess?: (result: TestModelFailoverResponse) => void
|
||||||
@@ -139,6 +140,7 @@ export function useModelTest(options: UseModelTestOptions) {
|
|||||||
api_format: params.apiFormat,
|
api_format: params.apiFormat,
|
||||||
endpoint_id: params.endpointId,
|
endpoint_id: params.endpointId,
|
||||||
...(normalizedMessage ? { message: normalizedMessage } : {}),
|
...(normalizedMessage ? { message: normalizedMessage } : {}),
|
||||||
|
...(params.requestHeaders ? { request_headers: params.requestHeaders } : {}),
|
||||||
...(params.requestBody ? { request_body: params.requestBody } : {}),
|
...(params.requestBody ? { request_body: params.requestBody } : {}),
|
||||||
request_id: reqId,
|
request_id: reqId,
|
||||||
concurrency: params.concurrency,
|
concurrency: params.concurrency,
|
||||||
|
|||||||
@@ -79,18 +79,19 @@
|
|||||||
{{ editingKey ? '' : '*' }}
|
{{ editingKey ? '' : '*' }}
|
||||||
</Label>
|
</Label>
|
||||||
<template v-if="form.auth_type === 'service_account'">
|
<template v-if="form.auth_type === 'service_account'">
|
||||||
<Textarea
|
<JsonImportInput
|
||||||
:id="apiKeyInputId"
|
|
||||||
v-model="form.auth_config_text"
|
v-model="form.auth_config_text"
|
||||||
:required="!editingKey"
|
:disabled="saving"
|
||||||
:placeholder="editingKey ? '留空表示不修改' : '粘贴完整的 Service Account JSON'"
|
:reset-key="formNonce"
|
||||||
class="min-h-[120px] font-mono text-xs"
|
accept=".json,.txt,application/json,text/plain"
|
||||||
autocomplete="off"
|
:multiple="false"
|
||||||
spellcheck="false"
|
drop-title="拖入 Service Account JSON 或点击选择"
|
||||||
|
drop-hint="支持 .json / .txt,单文件导入"
|
||||||
|
:manual-placeholder="editingKey ? '留空表示不修改,或粘贴完整的 Service Account JSON' : '粘贴完整的 Service Account JSON'"
|
||||||
|
:manual-description="serviceAccountDescription"
|
||||||
|
textarea-class="min-h-[160px] font-mono text-xs break-all !rounded-xl"
|
||||||
|
@error="handleServiceAccountImportError"
|
||||||
/>
|
/>
|
||||||
<p class="text-xs text-muted-foreground mt-1">
|
|
||||||
JSON 格式,包含 project_id、private_key 等字段
|
|
||||||
</p>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<Input
|
<Input
|
||||||
@@ -330,13 +331,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
import { Dialog, Button, Input, Label, Switch, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Textarea } from '@/components/ui'
|
import { Dialog, Button, Input, Label, Switch, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@/components/ui'
|
||||||
import { Key, SquarePen } from 'lucide-vue-next'
|
import { Key, SquarePen } from 'lucide-vue-next'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
import { useFormDialog } from '@/composables/useFormDialog'
|
import { useFormDialog } from '@/composables/useFormDialog'
|
||||||
import { parseApiError } from '@/utils/errorParser'
|
import { parseApiError } from '@/utils/errorParser'
|
||||||
import { parseNumberInput, parseNullableNumberInput } from '@/utils/form'
|
import { parseNumberInput, parseNullableNumberInput } from '@/utils/form'
|
||||||
import { log } from '@/utils/logger'
|
import { log } from '@/utils/logger'
|
||||||
|
import JsonImportInput from '@/components/common/JsonImportInput.vue'
|
||||||
import {
|
import {
|
||||||
addProviderKey,
|
addProviderKey,
|
||||||
updateProviderKey,
|
updateProviderKey,
|
||||||
@@ -415,6 +417,12 @@ const visibleApiFormats = computed(() => {
|
|||||||
|
|
||||||
const showAuthTypeSelector = computed(() => props.providerType === 'vertex_ai')
|
const showAuthTypeSelector = computed(() => props.providerType === 'vertex_ai')
|
||||||
|
|
||||||
|
const serviceAccountDescription = computed(() => (
|
||||||
|
props.editingKey
|
||||||
|
? '留空表示不修改;JSON 格式,包含 project_id、private_key 等字段'
|
||||||
|
: 'JSON 格式,包含 project_id、private_key 等字段'
|
||||||
|
))
|
||||||
|
|
||||||
// 默认认证类型
|
// 默认认证类型
|
||||||
const defaultAuthType = 'api_key' as const
|
const defaultAuthType = 'api_key' as const
|
||||||
|
|
||||||
@@ -676,6 +684,10 @@ function parseAuthConfig(): Record<string, unknown> | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleServiceAccountImportError(payload: { message: string, title?: string }) {
|
||||||
|
showError(payload.message, payload.title || '错误')
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
// 必须有 providerId
|
// 必须有 providerId
|
||||||
if (!props.providerId) {
|
if (!props.providerId) {
|
||||||
|
|||||||
@@ -319,13 +319,18 @@
|
|||||||
:testing="modelTest.testing.value"
|
:testing="modelTest.testing.value"
|
||||||
:trace="modelTest.testTrace.value"
|
:trace="modelTest.testTrace.value"
|
||||||
:request-id="modelTest.requestId.value"
|
:request-id="modelTest.requestId.value"
|
||||||
|
:request-headers-draft="testRequestHeadersDraft"
|
||||||
|
:request-headers-reset-value="testRequestHeadersResetValue"
|
||||||
|
:request-headers-error="testRequestHeadersError"
|
||||||
:request-body-draft="testRequestBodyDraft"
|
:request-body-draft="testRequestBodyDraft"
|
||||||
|
:request-body-reset-value="testRequestBodyResetValue"
|
||||||
:request-body-error="testRequestBodyError"
|
:request-body-error="testRequestBodyError"
|
||||||
:start-disabled="!selectedTestEndpoint || !!testRequestBodyError"
|
:start-disabled="!selectedTestEndpoint || !!testRequestHeadersError || !!testRequestBodyError"
|
||||||
@close="handleTestDialogClose"
|
@close="handleTestDialogClose"
|
||||||
@back="handleTestDialogBack"
|
@back="handleTestDialogBack"
|
||||||
@select-endpoint="handleSelectTestEndpoint"
|
@select-endpoint="handleSelectTestEndpoint"
|
||||||
@start="handleStartMappingTest"
|
@start="handleStartMappingTest"
|
||||||
|
@update:request-headers-draft="testRequestHeadersDraft = $event"
|
||||||
@update:request-body-draft="testRequestBodyDraft = $event"
|
@update:request-body-draft="testRequestBodyDraft = $event"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -353,8 +358,12 @@ import { updateModel } from '@/api/endpoints/models'
|
|||||||
import { parseApiError } from '@/utils/errorParser'
|
import { parseApiError } from '@/utils/errorParser'
|
||||||
import type { ProviderWithEndpointsSummary } from '@/api/endpoints'
|
import type { ProviderWithEndpointsSummary } from '@/api/endpoints'
|
||||||
import {
|
import {
|
||||||
|
buildDefaultModelTestRequestHeaders,
|
||||||
buildDefaultModelTestRequestBody,
|
buildDefaultModelTestRequestBody,
|
||||||
|
parseModelTestRequestHeadersDraft,
|
||||||
parseModelTestRequestBodyDraft,
|
parseModelTestRequestBodyDraft,
|
||||||
|
POOL_TEST_CONCURRENCY,
|
||||||
|
SINGLE_TEST_CONCURRENCY,
|
||||||
} from './model-test-request'
|
} from './model-test-request'
|
||||||
|
|
||||||
interface MappingItem {
|
interface MappingItem {
|
||||||
@@ -409,8 +418,14 @@ const pendingMappingKey = ref<string | null>(null)
|
|||||||
const testingModelName = ref<string | null>(null)
|
const testingModelName = ref<string | null>(null)
|
||||||
const preselectedModelId = ref<string | null>(null)
|
const preselectedModelId = ref<string | null>(null)
|
||||||
const selectedTestEndpoint = ref<ProviderEndpoint | null>(null)
|
const selectedTestEndpoint = ref<ProviderEndpoint | null>(null)
|
||||||
|
const testRequestHeadersDraft = ref('')
|
||||||
|
const testRequestHeadersResetValue = ref('')
|
||||||
const testRequestBodyDraft = ref('')
|
const testRequestBodyDraft = ref('')
|
||||||
|
const testRequestBodyResetValue = ref('')
|
||||||
|
const isPoolManagedProvider = computed(() => Boolean(props.provider.pool_advanced))
|
||||||
const activeEndpoints = computed(() => (props.endpoints ?? []).filter(endpoint => endpoint.is_active))
|
const activeEndpoints = computed(() => (props.endpoints ?? []).filter(endpoint => endpoint.is_active))
|
||||||
|
const parsedTestRequestHeaders = computed(() => parseModelTestRequestHeadersDraft(testRequestHeadersDraft.value))
|
||||||
|
const testRequestHeadersError = computed(() => parsedTestRequestHeaders.value.error)
|
||||||
const parsedTestRequestBody = computed(() => parseModelTestRequestBodyDraft(testRequestBodyDraft.value))
|
const parsedTestRequestBody = computed(() => parseModelTestRequestBodyDraft(testRequestBodyDraft.value))
|
||||||
const testRequestBodyError = computed(() => parsedTestRequestBody.value.error)
|
const testRequestBodyError = computed(() => parsedTestRequestBody.value.error)
|
||||||
|
|
||||||
@@ -651,7 +666,10 @@ function handleTestDialogClose() {
|
|||||||
testingModelName.value = null
|
testingModelName.value = null
|
||||||
testingMapping.value = null
|
testingMapping.value = null
|
||||||
selectedTestEndpoint.value = null
|
selectedTestEndpoint.value = null
|
||||||
|
testRequestHeadersDraft.value = ''
|
||||||
|
testRequestHeadersResetValue.value = ''
|
||||||
testRequestBodyDraft.value = ''
|
testRequestBodyDraft.value = ''
|
||||||
|
testRequestBodyResetValue.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTestDialogBack() {
|
function handleTestDialogBack() {
|
||||||
@@ -678,7 +696,10 @@ function runMappingTest(testingKey: string, modelName: string) {
|
|||||||
testingMapping.value = null
|
testingMapping.value = null
|
||||||
testingModelName.value = modelName
|
testingModelName.value = modelName
|
||||||
selectedTestEndpoint.value = activeEndpoints.value[0] ?? null
|
selectedTestEndpoint.value = activeEndpoints.value[0] ?? null
|
||||||
testRequestBodyDraft.value = buildDefaultModelTestRequestBody(modelName)
|
testRequestHeadersResetValue.value = buildDefaultModelTestRequestHeaders()
|
||||||
|
testRequestHeadersDraft.value = testRequestHeadersResetValue.value
|
||||||
|
testRequestBodyResetValue.value = buildDefaultModelTestRequestBody(modelName)
|
||||||
|
testRequestBodyDraft.value = testRequestBodyResetValue.value
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleStartMappingTest() {
|
async function handleStartMappingTest() {
|
||||||
@@ -689,6 +710,12 @@ async function handleStartMappingTest() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { value: requestHeaders, error: requestHeadersError } = parsedTestRequestHeaders.value
|
||||||
|
if (!requestHeaders || requestHeadersError) {
|
||||||
|
showError(`测试请求头无效: ${requestHeadersError || '无效 JSON'}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const { value: requestBody, error } = parsedTestRequestBody.value
|
const { value: requestBody, error } = parsedTestRequestBody.value
|
||||||
if (!requestBody || error) {
|
if (!requestBody || error) {
|
||||||
showError(`测试请求体无效: ${error || '无效 JSON'}`)
|
showError(`测试请求体无效: ${error || '无效 JSON'}`)
|
||||||
@@ -703,7 +730,9 @@ async function handleStartMappingTest() {
|
|||||||
displayLabel: `[${endpoint.api_format}] 映射 "${testingModelName.value}"`,
|
displayLabel: `[${endpoint.api_format}] 映射 "${testingModelName.value}"`,
|
||||||
apiFormat: endpoint.api_format,
|
apiFormat: endpoint.api_format,
|
||||||
endpointId: endpoint.id,
|
endpointId: endpoint.id,
|
||||||
|
requestHeaders,
|
||||||
requestBody,
|
requestBody,
|
||||||
|
concurrency: isPoolManagedProvider.value ? POOL_TEST_CONCURRENCY : SINGLE_TEST_CONCURRENCY,
|
||||||
})
|
})
|
||||||
if (pendingMappingKey.value === currentMappingKey) {
|
if (pendingMappingKey.value === currentMappingKey) {
|
||||||
pendingMappingKey.value = null
|
pendingMappingKey.value = null
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog
|
<Dialog
|
||||||
:open="open"
|
:open="open"
|
||||||
size="2xl"
|
size="3xl"
|
||||||
:close-on-backdrop="false"
|
:close-on-backdrop="false"
|
||||||
@update:open="(val: boolean) => { if (!val) emit('close') }"
|
@update:open="(val: boolean) => { if (!val) emit('close') }"
|
||||||
>
|
>
|
||||||
@@ -37,12 +37,12 @@
|
|||||||
当前测试会固定到选中的端点
|
当前测试会固定到选中的端点
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="grid gap-2 md:grid-cols-2">
|
||||||
<button
|
<button
|
||||||
v-for="endpoint in endpoints"
|
v-for="endpoint in endpoints"
|
||||||
:key="endpoint.id"
|
:key="endpoint.id"
|
||||||
type="button"
|
type="button"
|
||||||
class="w-full rounded-lg border px-3 py-3 text-left transition-colors"
|
class="h-full w-full rounded-lg border px-3 py-3 text-left transition-colors"
|
||||||
:class="selectedEndpoint?.id === endpoint.id
|
:class="selectedEndpoint?.id === endpoint.id
|
||||||
? 'border-primary bg-primary/5'
|
? 'border-primary bg-primary/5'
|
||||||
: 'border-border/60 hover:bg-muted/40'"
|
: 'border-border/60 hover:bg-muted/40'"
|
||||||
@@ -65,33 +65,91 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="grid gap-4 lg:grid-cols-2 lg:items-start">
|
||||||
<div class="flex items-center justify-between gap-3">
|
<div class="space-y-2">
|
||||||
<div class="text-sm font-medium">
|
<div class="flex items-center justify-between gap-3">
|
||||||
测试请求体
|
<div class="text-sm font-medium">
|
||||||
|
测试请求头
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class="h-8 w-8 rounded-lg text-muted-foreground"
|
||||||
|
title="格式化请求头 JSON"
|
||||||
|
@click="formatRequestHeadersDraft"
|
||||||
|
>
|
||||||
|
<Code2 class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class="h-8 w-8 rounded-lg text-muted-foreground"
|
||||||
|
title="重置请求头"
|
||||||
|
@click="resetRequestHeadersDraft"
|
||||||
|
>
|
||||||
|
<RotateCcw class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Textarea
|
||||||
variant="ghost"
|
:model-value="requestHeadersDraft"
|
||||||
size="sm"
|
class="min-h-[260px] font-mono text-xs"
|
||||||
@click="formatRequestBodyDraft"
|
placeholder="输入 JSON 请求头"
|
||||||
|
@update:model-value="emit('update:requestHeadersDraft', $event)"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="requestHeadersError"
|
||||||
|
class="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive"
|
||||||
>
|
>
|
||||||
格式化 JSON
|
{{ requestHeadersError }}
|
||||||
</Button>
|
</div>
|
||||||
|
<div class="rounded-md border border-border/60 bg-muted/20 px-3 py-2 text-[11px] text-muted-foreground">
|
||||||
|
这里的请求头会合并到测试请求里;鉴权头和必要系统头仍由后端按端点规则补齐。
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Textarea
|
|
||||||
:model-value="requestBodyDraft"
|
<div class="space-y-2">
|
||||||
class="min-h-[260px] font-mono text-xs"
|
<div class="flex items-center justify-between gap-3">
|
||||||
placeholder="输入 JSON 请求体"
|
<div class="text-sm font-medium">
|
||||||
@update:model-value="emit('update:requestBodyDraft', $event)"
|
测试请求体
|
||||||
/>
|
</div>
|
||||||
<div
|
<div class="flex items-center gap-1">
|
||||||
v-if="requestBodyError"
|
<Button
|
||||||
class="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive"
|
variant="ghost"
|
||||||
>
|
size="icon"
|
||||||
{{ requestBodyError }}
|
class="h-8 w-8 rounded-lg text-muted-foreground"
|
||||||
</div>
|
title="格式化请求体 JSON"
|
||||||
<div class="rounded-md border border-border/60 bg-muted/20 px-3 py-2 text-[11px] text-muted-foreground">
|
@click="formatRequestBodyDraft"
|
||||||
会强制使用当前测试模型;这里编辑的是测试基础请求体,实际发送时会按端点格式转换并应用规则。
|
>
|
||||||
|
<Code2 class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class="h-8 w-8 rounded-lg text-muted-foreground"
|
||||||
|
title="重置请求体"
|
||||||
|
@click="resetRequestBodyDraft"
|
||||||
|
>
|
||||||
|
<RotateCcw class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Textarea
|
||||||
|
:model-value="requestBodyDraft"
|
||||||
|
class="min-h-[260px] font-mono text-xs"
|
||||||
|
placeholder="输入 JSON 请求体"
|
||||||
|
@update:model-value="emit('update:requestBodyDraft', $event)"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="requestBodyError"
|
||||||
|
class="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive"
|
||||||
|
>
|
||||||
|
{{ requestBodyError }}
|
||||||
|
</div>
|
||||||
|
<div class="rounded-md border border-border/60 bg-muted/20 px-3 py-2 text-[11px] text-muted-foreground">
|
||||||
|
会强制使用当前测试模型;这里编辑的是测试基础请求体,实际发送时会按端点格式转换并应用规则。
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -582,7 +640,6 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
|
||||||
@click="emit('close')"
|
@click="emit('close')"
|
||||||
>
|
>
|
||||||
{{ showSetup ? '取消' : '关闭' }}
|
{{ showSetup ? '取消' : '关闭' }}
|
||||||
@@ -590,7 +647,6 @@
|
|||||||
<Button
|
<Button
|
||||||
v-if="showResult"
|
v-if="showResult"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
|
||||||
@click="emit('back')"
|
@click="emit('back')"
|
||||||
>
|
>
|
||||||
返回
|
返回
|
||||||
@@ -601,7 +657,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { Check, Copy, Loader2, Maximize2, Minimize2 } from 'lucide-vue-next'
|
import { Check, Code2, Copy, Loader2, Maximize2, Minimize2, RotateCcw } from 'lucide-vue-next'
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Card,
|
Card,
|
||||||
@@ -635,7 +691,11 @@ const props = defineProps<{
|
|||||||
testing?: boolean
|
testing?: boolean
|
||||||
trace?: RequestTrace | null
|
trace?: RequestTrace | null
|
||||||
requestId?: string | null
|
requestId?: string | null
|
||||||
|
requestHeadersDraft?: string
|
||||||
|
requestHeadersResetValue?: string
|
||||||
|
requestHeadersError?: string | null
|
||||||
requestBodyDraft?: string
|
requestBodyDraft?: string
|
||||||
|
requestBodyResetValue?: string
|
||||||
requestBodyError?: string | null
|
requestBodyError?: string | null
|
||||||
startDisabled?: boolean
|
startDisabled?: boolean
|
||||||
}>()
|
}>()
|
||||||
@@ -645,10 +705,12 @@ const emit = defineEmits<{
|
|||||||
back: []
|
back: []
|
||||||
start: []
|
start: []
|
||||||
selectEndpoint: [endpointId: string]
|
selectEndpoint: [endpointId: string]
|
||||||
|
'update:requestHeadersDraft': [value: string]
|
||||||
'update:requestBodyDraft': [value: string]
|
'update:requestBodyDraft': [value: string]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const endpoints = computed(() => props.endpoints ?? [])
|
const endpoints = computed(() => props.endpoints ?? [])
|
||||||
|
const requestHeadersDraft = computed(() => props.requestHeadersDraft ?? '')
|
||||||
const requestBodyDraft = computed(() => props.requestBodyDraft ?? '')
|
const requestBodyDraft = computed(() => props.requestBodyDraft ?? '')
|
||||||
const traceCandidates = computed(() => props.trace?.candidates ?? [])
|
const traceCandidates = computed(() => props.trace?.candidates ?? [])
|
||||||
const showSetup = computed(() => props.open && !props.testing && !props.result)
|
const showSetup = computed(() => props.open && !props.testing && !props.result)
|
||||||
@@ -664,7 +726,7 @@ const dialogTitle = computed(() => {
|
|||||||
|
|
||||||
const dialogDescription = computed(() => {
|
const dialogDescription = computed(() => {
|
||||||
if (showSetup.value && props.selectingModelName) {
|
if (showSetup.value && props.selectingModelName) {
|
||||||
return `为 ${props.selectingModelName} 选择端点并编辑测试请求体`
|
return `为 ${props.selectingModelName} 选择端点并编辑测试请求头与请求体`
|
||||||
}
|
}
|
||||||
if (props.testing && props.selectedEndpoint) {
|
if (props.testing && props.selectedEndpoint) {
|
||||||
return `正在通过 ${formatApiFormat(props.selectedEndpoint.api_format)} 测试 ${props.selectingModelName || '模型'}`
|
return `正在通过 ${formatApiFormat(props.selectedEndpoint.api_format)} 测试 ${props.selectingModelName || '模型'}`
|
||||||
@@ -969,11 +1031,39 @@ function collapseInspectionContent() {
|
|||||||
inspectionExpandDepth.value = 0
|
inspectionExpandDepth.value = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatRequestHeadersDraft() {
|
||||||
|
formatJsonDraft(requestHeadersDraft.value, value => emit('update:requestHeadersDraft', value), '{}')
|
||||||
|
}
|
||||||
|
|
||||||
function formatRequestBodyDraft() {
|
function formatRequestBodyDraft() {
|
||||||
|
formatJsonDraft(requestBodyDraft.value, value => emit('update:requestBodyDraft', value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetRequestHeadersDraft() {
|
||||||
|
emit('update:requestHeadersDraft', props.requestHeadersResetValue ?? '{}')
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetRequestBodyDraft() {
|
||||||
|
emit('update:requestBodyDraft', props.requestBodyResetValue ?? '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatJsonDraft(
|
||||||
|
draft: string,
|
||||||
|
onFormatted: (value: string) => void,
|
||||||
|
emptyFallback?: string,
|
||||||
|
) {
|
||||||
|
const normalized = draft.trim()
|
||||||
|
if (!normalized) {
|
||||||
|
if (emptyFallback !== undefined) {
|
||||||
|
onFormatted(emptyFallback)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(requestBodyDraft.value)
|
const parsed = JSON.parse(normalized)
|
||||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return
|
||||||
emit('update:requestBodyDraft', JSON.stringify(parsed, null, 2))
|
onFormatted(JSON.stringify(parsed, null, 2))
|
||||||
} catch {
|
} catch {
|
||||||
// keep user input untouched when JSON is invalid
|
// keep user input untouched when JSON is invalid
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,13 +221,18 @@
|
|||||||
:testing="modelTest.testing.value"
|
:testing="modelTest.testing.value"
|
||||||
:trace="modelTest.testTrace.value"
|
:trace="modelTest.testTrace.value"
|
||||||
:request-id="modelTest.requestId.value"
|
:request-id="modelTest.requestId.value"
|
||||||
|
:request-headers-draft="testRequestHeadersDraft"
|
||||||
|
:request-headers-reset-value="testRequestHeadersResetValue"
|
||||||
|
:request-headers-error="testRequestHeadersError"
|
||||||
:request-body-draft="testRequestBodyDraft"
|
:request-body-draft="testRequestBodyDraft"
|
||||||
|
:request-body-reset-value="testRequestBodyResetValue"
|
||||||
:request-body-error="testRequestBodyError"
|
:request-body-error="testRequestBodyError"
|
||||||
:start-disabled="!selectedTestEndpoint || !!testRequestBodyError"
|
:start-disabled="!selectedTestEndpoint || !!testRequestHeadersError || !!testRequestBodyError"
|
||||||
@close="handleTestDialogClose"
|
@close="handleTestDialogClose"
|
||||||
@back="handleTestDialogBack"
|
@back="handleTestDialogBack"
|
||||||
@start="handleStartPendingTest"
|
@start="handleStartPendingTest"
|
||||||
@select-endpoint="handleSelectTestEndpoint"
|
@select-endpoint="handleSelectTestEndpoint"
|
||||||
|
@update:request-headers-draft="testRequestHeadersDraft = $event"
|
||||||
@update:request-body-draft="testRequestBodyDraft = $event"
|
@update:request-body-draft="testRequestBodyDraft = $event"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -252,8 +257,12 @@ import { formatApiFormat } from '@/api/endpoints/types/api-format'
|
|||||||
import type { ProviderWithEndpointsSummary } from '@/api/endpoints'
|
import type { ProviderWithEndpointsSummary } from '@/api/endpoints'
|
||||||
import ModelTestDialog from './ModelTestDialog.vue'
|
import ModelTestDialog from './ModelTestDialog.vue'
|
||||||
import {
|
import {
|
||||||
|
buildDefaultModelTestRequestHeaders,
|
||||||
buildDefaultModelTestRequestBody,
|
buildDefaultModelTestRequestBody,
|
||||||
|
parseModelTestRequestHeadersDraft,
|
||||||
parseModelTestRequestBodyDraft,
|
parseModelTestRequestBodyDraft,
|
||||||
|
POOL_TEST_CONCURRENCY,
|
||||||
|
SINGLE_TEST_CONCURRENCY,
|
||||||
} from './model-test-request'
|
} from './model-test-request'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -280,8 +289,14 @@ const localModels = ref<Model[]>([])
|
|||||||
const togglingModelId = ref<string | null>(null)
|
const togglingModelId = ref<string | null>(null)
|
||||||
const pendingTestModel = ref<Model | null>(null)
|
const pendingTestModel = ref<Model | null>(null)
|
||||||
const selectedTestEndpoint = ref<ProviderEndpoint | null>(null)
|
const selectedTestEndpoint = ref<ProviderEndpoint | null>(null)
|
||||||
|
const testRequestHeadersDraft = ref('')
|
||||||
|
const testRequestHeadersResetValue = ref('')
|
||||||
const testRequestBodyDraft = ref('')
|
const testRequestBodyDraft = ref('')
|
||||||
|
const testRequestBodyResetValue = ref('')
|
||||||
|
const isPoolManagedProvider = computed(() => Boolean(props.provider.pool_advanced))
|
||||||
const activeEndpoints = computed(() => (props.endpoints ?? []).filter(endpoint => endpoint.is_active))
|
const activeEndpoints = computed(() => (props.endpoints ?? []).filter(endpoint => endpoint.is_active))
|
||||||
|
const parsedTestRequestHeaders = computed(() => parseModelTestRequestHeadersDraft(testRequestHeadersDraft.value))
|
||||||
|
const testRequestHeadersError = computed(() => parsedTestRequestHeaders.value.error)
|
||||||
const parsedTestRequestBody = computed(() => parseModelTestRequestBodyDraft(testRequestBodyDraft.value))
|
const parsedTestRequestBody = computed(() => parseModelTestRequestBodyDraft(testRequestBodyDraft.value))
|
||||||
const testRequestBodyError = computed(() => parsedTestRequestBody.value.error)
|
const testRequestBodyError = computed(() => parsedTestRequestBody.value.error)
|
||||||
const models = computed(() => props.models ?? localModels.value)
|
const models = computed(() => props.models ?? localModels.value)
|
||||||
@@ -449,7 +464,10 @@ function handleTestDialogClose() {
|
|||||||
modelTest.resetState()
|
modelTest.resetState()
|
||||||
pendingTestModel.value = null
|
pendingTestModel.value = null
|
||||||
selectedTestEndpoint.value = null
|
selectedTestEndpoint.value = null
|
||||||
|
testRequestHeadersDraft.value = ''
|
||||||
|
testRequestHeadersResetValue.value = ''
|
||||||
testRequestBodyDraft.value = ''
|
testRequestBodyDraft.value = ''
|
||||||
|
testRequestBodyResetValue.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTestDialogBack() {
|
function handleTestDialogBack() {
|
||||||
@@ -474,6 +492,12 @@ async function handleStartPendingTest() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { value: requestHeaders, error: requestHeadersError } = parsedTestRequestHeaders.value
|
||||||
|
if (!requestHeaders || requestHeadersError) {
|
||||||
|
showError(`测试请求头无效: ${requestHeadersError || '无效 JSON'}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const { value: requestBody, error } = parsedTestRequestBody.value
|
const { value: requestBody, error } = parsedTestRequestBody.value
|
||||||
if (!requestBody || error) {
|
if (!requestBody || error) {
|
||||||
showError(`测试请求体无效: ${error || '无效 JSON'}`)
|
showError(`测试请求体无效: ${error || '无效 JSON'}`)
|
||||||
@@ -490,8 +514,9 @@ async function handleStartPendingTest() {
|
|||||||
displayLabel: `${endpointPrefix}${modelName}`,
|
displayLabel: `${endpointPrefix}${modelName}`,
|
||||||
apiFormat: endpoint.api_format,
|
apiFormat: endpoint.api_format,
|
||||||
endpointId: endpoint.id,
|
endpointId: endpoint.id,
|
||||||
|
requestHeaders,
|
||||||
requestBody,
|
requestBody,
|
||||||
concurrency: 5,
|
concurrency: isPoolManagedProvider.value ? POOL_TEST_CONCURRENCY : SINGLE_TEST_CONCURRENCY,
|
||||||
onError: () => {
|
onError: () => {
|
||||||
if (activeEndpoints.value.length > 1) {
|
if (activeEndpoints.value.length > 1) {
|
||||||
return true
|
return true
|
||||||
@@ -510,9 +535,12 @@ async function testModelConnection(model: Model) {
|
|||||||
|
|
||||||
pendingTestModel.value = model
|
pendingTestModel.value = model
|
||||||
selectedTestEndpoint.value = activeEndpoints.value[0] ?? null
|
selectedTestEndpoint.value = activeEndpoints.value[0] ?? null
|
||||||
testRequestBodyDraft.value = buildDefaultModelTestRequestBody(
|
testRequestHeadersResetValue.value = buildDefaultModelTestRequestHeaders()
|
||||||
|
testRequestHeadersDraft.value = testRequestHeadersResetValue.value
|
||||||
|
testRequestBodyResetValue.value = buildDefaultModelTestRequestBody(
|
||||||
model.global_model_name || model.provider_model_name,
|
model.global_model_name || model.provider_model_name,
|
||||||
)
|
)
|
||||||
|
testRequestBodyDraft.value = testRequestBodyResetValue.value
|
||||||
modelTest.testResult.value = null
|
modelTest.testResult.value = null
|
||||||
modelTest.dialogOpen.value = true
|
modelTest.dialogOpen.value = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
const DEFAULT_MODEL_TEST_MESSAGE = 'Hello! This is a test message.'
|
const DEFAULT_MODEL_TEST_MESSAGE = 'Hello! This is a test message.'
|
||||||
|
|
||||||
|
/** Pool-managed provider runs concurrent checks; single-key provider does not. */
|
||||||
|
export const POOL_TEST_CONCURRENCY = 5
|
||||||
|
export const SINGLE_TEST_CONCURRENCY = 1
|
||||||
|
|
||||||
export function buildDefaultModelTestRequestBody(modelName: string): string {
|
export function buildDefaultModelTestRequestBody(modelName: string): string {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
model: modelName,
|
model: modelName,
|
||||||
@@ -15,14 +19,23 @@ export function buildDefaultModelTestRequestBody(modelName: string): string {
|
|||||||
}, null, 2)
|
}, null, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseModelTestRequestBodyDraft(
|
export function buildDefaultModelTestRequestHeaders(): string {
|
||||||
|
return JSON.stringify({}, null, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseModelTestJsonObjectDraft(
|
||||||
draft: string,
|
draft: string,
|
||||||
|
options: {
|
||||||
|
emptyValue: Record<string, unknown> | null
|
||||||
|
emptyError: string | null
|
||||||
|
invalidTypeError: string
|
||||||
|
},
|
||||||
): { value: Record<string, unknown> | null; error: string | null } {
|
): { value: Record<string, unknown> | null; error: string | null } {
|
||||||
const normalized = draft.trim()
|
const normalized = draft.trim()
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
return {
|
return {
|
||||||
value: null,
|
value: options.emptyValue,
|
||||||
error: '测试请求体不能为空',
|
error: options.emptyError,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +44,7 @@ export function parseModelTestRequestBodyDraft(
|
|||||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||||
return {
|
return {
|
||||||
value: null,
|
value: null,
|
||||||
error: '测试请求体必须是 JSON 对象',
|
error: options.invalidTypeError,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -45,3 +58,23 @@ export function parseModelTestRequestBodyDraft(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseModelTestRequestBodyDraft(
|
||||||
|
draft: string,
|
||||||
|
): { value: Record<string, unknown> | null; error: string | null } {
|
||||||
|
return parseModelTestJsonObjectDraft(draft, {
|
||||||
|
emptyValue: null,
|
||||||
|
emptyError: '测试请求体不能为空',
|
||||||
|
invalidTypeError: '测试请求体必须是 JSON 对象',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseModelTestRequestHeadersDraft(
|
||||||
|
draft: string,
|
||||||
|
): { value: Record<string, unknown> | null; error: string | null } {
|
||||||
|
return parseModelTestJsonObjectDraft(draft, {
|
||||||
|
emptyValue: {},
|
||||||
|
emptyError: null,
|
||||||
|
invalidTypeError: '测试请求头必须是 JSON 对象',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ class TestModelFailoverRequest(BaseModel):
|
|||||||
api_format: str | None = None # 指定 API 格式(endpoint signature)
|
api_format: str | None = None # 指定 API 格式(endpoint signature)
|
||||||
endpoint_id: str | None = None # 指定仅使用该端点测试
|
endpoint_id: str | None = None # 指定仅使用该端点测试
|
||||||
message: str | None = None
|
message: str | None = None
|
||||||
|
request_headers: dict[str, Any] | None = None
|
||||||
request_body: dict[str, Any] | None = None
|
request_body: dict[str, Any] | None = None
|
||||||
request_id: str | None = None
|
request_id: str | None = None
|
||||||
concurrency: int = Field(default=1, ge=1, le=20)
|
concurrency: int = Field(default=1, ge=1, le=20)
|
||||||
@@ -312,6 +313,31 @@ def _build_test_request_payload(request: TestModelFailoverRequest) -> dict[str,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _build_test_request_headers(request: TestModelFailoverRequest) -> dict[str, str]:
|
||||||
|
if not isinstance(request.request_headers, dict):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
headers: dict[str, str] = {}
|
||||||
|
for raw_key, raw_value in request.request_headers.items():
|
||||||
|
key = str(raw_key or "").strip()
|
||||||
|
if not key or raw_value is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(raw_value, str):
|
||||||
|
value = raw_value
|
||||||
|
elif isinstance(raw_value, (bool, int, float)):
|
||||||
|
value = str(raw_value)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
value = json.dumps(raw_value, ensure_ascii=False)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
value = str(raw_value)
|
||||||
|
|
||||||
|
headers[key] = value
|
||||||
|
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
def _extract_test_debug_payload(response: dict[str, Any]) -> dict[str, Any] | None:
|
def _extract_test_debug_payload(response: dict[str, Any]) -> dict[str, Any] | None:
|
||||||
debug = response.get("debug")
|
debug = response.get("debug")
|
||||||
if not isinstance(debug, dict):
|
if not isinstance(debug, dict):
|
||||||
@@ -1660,6 +1686,7 @@ async def _execute_test_check(
|
|||||||
key: Any,
|
key: Any,
|
||||||
effective_model: str,
|
effective_model: str,
|
||||||
request_payload: dict[str, Any],
|
request_payload: dict[str, Any],
|
||||||
|
request_headers: dict[str, str] | None,
|
||||||
request_timeout: float,
|
request_timeout: float,
|
||||||
provider_type: str,
|
provider_type: str,
|
||||||
user: User | None,
|
user: User | None,
|
||||||
@@ -1679,6 +1706,8 @@ async def _execute_test_check(
|
|||||||
|
|
||||||
auth_type = str(getattr(key, "auth_type", "api_key") or "api_key").lower()
|
auth_type = str(getattr(key, "auth_type", "api_key") or "api_key").lower()
|
||||||
extra_headers = get_extra_headers_from_endpoint(endpoint) or {}
|
extra_headers = get_extra_headers_from_endpoint(endpoint) or {}
|
||||||
|
if request_headers:
|
||||||
|
extra_headers.update(request_headers)
|
||||||
if auth_type == "oauth":
|
if auth_type == "oauth":
|
||||||
account_id = (auth_config or {}).get("account_id")
|
account_id = (auth_config or {}).get("account_id")
|
||||||
if account_id:
|
if account_id:
|
||||||
@@ -1738,6 +1767,7 @@ async def _run_concurrent_test(
|
|||||||
is_cancelled: Callable[[], Awaitable[bool]],
|
is_cancelled: Callable[[], Awaitable[bool]],
|
||||||
request_id: str,
|
request_id: str,
|
||||||
request_payload: dict[str, Any],
|
request_payload: dict[str, Any],
|
||||||
|
request_headers: dict[str, str] | None,
|
||||||
effective_model_by_candidate_index: dict[int, str],
|
effective_model_by_candidate_index: dict[int, str],
|
||||||
request_timeout: float,
|
request_timeout: float,
|
||||||
provider_type: str,
|
provider_type: str,
|
||||||
@@ -1873,6 +1903,7 @@ async def _run_concurrent_test(
|
|||||||
str(request_payload.get("model", "") or ""),
|
str(request_payload.get("model", "") or ""),
|
||||||
),
|
),
|
||||||
request_payload=request_payload,
|
request_payload=request_payload,
|
||||||
|
request_headers=request_headers,
|
||||||
request_timeout=request_timeout,
|
request_timeout=request_timeout,
|
||||||
provider_type=provider_type,
|
provider_type=provider_type,
|
||||||
user=user,
|
user=user,
|
||||||
@@ -2354,6 +2385,7 @@ async def test_model_failover(
|
|||||||
).model_dump()
|
).model_dump()
|
||||||
|
|
||||||
request_payload = _build_test_request_payload(request)
|
request_payload = _build_test_request_payload(request)
|
||||||
|
request_headers = _build_test_request_headers(request)
|
||||||
request_id = str(request.request_id or f"provider-test-{uuid4().hex[:12]}")
|
request_id = str(request.request_id or f"provider-test-{uuid4().hex[:12]}")
|
||||||
request_timeout = float(getattr(provider, "request_timeout", 0) or TimeoutDefaults.HTTP_REQUEST)
|
request_timeout = float(getattr(provider, "request_timeout", 0) or TimeoutDefaults.HTTP_REQUEST)
|
||||||
provider_type = str(getattr(provider, "provider_type", "") or "").lower()
|
provider_type = str(getattr(provider, "provider_type", "") or "").lower()
|
||||||
@@ -2372,6 +2404,7 @@ async def test_model_failover(
|
|||||||
key=key,
|
key=key,
|
||||||
effective_model=effective_model,
|
effective_model=effective_model,
|
||||||
request_payload=request_payload,
|
request_payload=request_payload,
|
||||||
|
request_headers=request_headers or None,
|
||||||
request_timeout=request_timeout,
|
request_timeout=request_timeout,
|
||||||
provider_type=provider_type,
|
provider_type=provider_type,
|
||||||
user=current_user,
|
user=current_user,
|
||||||
@@ -2416,6 +2449,7 @@ async def test_model_failover(
|
|||||||
is_cancelled=http_request.is_disconnected,
|
is_cancelled=http_request.is_disconnected,
|
||||||
request_id=request_id,
|
request_id=request_id,
|
||||||
request_payload=dict(request_payload),
|
request_payload=dict(request_payload),
|
||||||
|
request_headers=request_headers or None,
|
||||||
effective_model_by_candidate_index=effective_model_by_candidate_index,
|
effective_model_by_candidate_index=effective_model_by_candidate_index,
|
||||||
request_timeout=request_timeout,
|
request_timeout=request_timeout,
|
||||||
provider_type=provider_type,
|
provider_type=provider_type,
|
||||||
@@ -2434,7 +2468,7 @@ async def test_model_failover(
|
|||||||
is_stream=False,
|
is_stream=False,
|
||||||
capability_requirements=None,
|
capability_requirements=None,
|
||||||
request_body_state=MutableRequestBodyState(dict(request_payload)),
|
request_body_state=MutableRequestBodyState(dict(request_payload)),
|
||||||
request_headers=None,
|
request_headers=request_headers or None,
|
||||||
request_body=dict(request_payload),
|
request_body=dict(request_payload),
|
||||||
affinity_key=f"provider-test:{provider.id}",
|
affinity_key=f"provider-test:{provider.id}",
|
||||||
create_pending_usage=False,
|
create_pending_usage=False,
|
||||||
|
|||||||
Reference in New Issue
Block a user