fix(provider): 将rust分支的gemini cli端点行为对齐到python分支 (#321)

* fix(provider): 对齐 Vertex/Gemini 上游发包与 Python master

- provider-transport: 为 custom+aiplatform 推断 Vertex API key 上下文并统一 URL 构建顺序,复用共享 request_url 构建最终上游地址
- ai-pipeline/gateway: Vertex Gemini 路径改为仅使用 URL query key,不再向上游附带 x-goog-api-key header;同步对齐 standard/admin/test-connection/runtime miss 摘要中的最终 URL
- gemini conversion: 按 Python master 输出 Gemini 请求体,补齐 system_instruction / generation_config / tool_config / function_declarations 形态,并移植 Gemini schema 清洗逻辑
- scheduler/executor: 将最终 upstream_url、mapped_model、key_name 写入候选 extra_data,运行时 miss 诊断优先展示真实展开后的上游 URL 便于服务器排障

* fix(provider): 修复 Vertex provider 测试与本地调度链路

* fix(provider): 对齐 Vertex 本地执行与 Rust CI
This commit is contained in:
Entropy.Xu
2026-04-23 23:01:06 +08:00
committed by GitHub
parent ccec46eddd
commit 0f94f92c37
29 changed files with 1879 additions and 497 deletions

View File

@@ -2,7 +2,10 @@ import { ref, onBeforeUnmount } from 'vue'
import { isAxiosError } from 'axios'
import { useToast } from './useToast'
import {
testModel,
testModelFailover,
type TestAttemptDetail,
type TestModelResponse,
type TestModelFailoverResponse,
} from '@/api/endpoints/providers'
import { requestTraceApi, type RequestTrace } from '@/api/requestTrace'
@@ -14,6 +17,7 @@ export interface StartTestParams {
displayLabel: string
apiFormat?: string
endpointId?: string
endpointBaseUrl?: string
message?: string
requestHeaders?: Record<string, unknown>
requestBody?: Record<string, unknown>
@@ -33,6 +37,7 @@ export interface UseModelTestOptions {
export function useModelTest(options: UseModelTestOptions) {
const { providerId, pollInterval = 800 } = options
const { success: showSuccess, error: showError } = useToast()
const LOCAL_FAILOVER_UNCONFIGURED_MESSAGE = 'Rust local provider-query failover simulation is not configured'
const testing = ref(false)
const testMode = ref<'global' | 'direct'>('global')
@@ -61,6 +66,78 @@ export function useModelTest(options: UseModelTestOptions) {
return false
}
function normalizeDirectTestResult(
params: StartTestParams,
result: TestModelResponse,
): TestModelFailoverResponse {
const responsePayload = result.data?.response
const failureMessage = typeof result.error === 'string' && result.error.trim()
? result.error.trim()
: (
typeof responsePayload?.error === 'string'
? responsePayload.error
: responsePayload?.error?.message
) || null
const syntheticAttempt: TestAttemptDetail = {
candidate_index: 1,
endpoint_api_format: params.apiFormat || '-',
endpoint_base_url: params.endpointBaseUrl || '',
key_name: null,
key_id: '',
auth_type: '',
effective_model: result.model || params.modelName,
status: result.success ? 'success' : 'failed',
skip_reason: null,
error_message: result.success ? null : failureMessage,
status_code: responsePayload?.status_code ?? null,
latency_ms: null,
request_url: null,
request_headers: (params.requestHeaders as Record<string, unknown> | undefined) ?? null,
request_body: params.requestBody ?? null,
response_headers: null,
response_body: (responsePayload as Record<string, unknown> | undefined)
?? (result.data as Record<string, unknown> | undefined)
?? null,
}
return {
success: result.success,
model: result.model || params.modelName,
provider: result.provider || { id: providerId(), name: providerId() },
attempts: [syntheticAttempt],
total_candidates: 1,
total_attempts: 1,
data: (result.data as Record<string, unknown> | undefined) ?? null,
error: failureMessage,
}
}
async function runDirectTest(
params: StartTestParams,
reqId: string,
signal?: AbortSignal,
): Promise<TestModelFailoverResponse> {
return normalizeDirectTestResult(params, await testModel({
provider_id: providerId(),
model_name: params.modelName,
api_format: params.apiFormat,
endpoint_id: params.endpointId,
...(normalizedMessage(params.message) ? { message: normalizedMessage(params.message) } : {}),
...(params.requestHeaders ? { request_headers: params.requestHeaders } : {}),
...(params.requestBody ? { request_body: params.requestBody } : {}),
request_id: reqId,
concurrency: params.concurrency,
}, {
signal,
}))
}
function normalizedMessage(message?: string): string | undefined {
return typeof message === 'string' && message.trim()
? message.trim()
: undefined
}
async function pollTestTrace(reqId: string, token: number) {
try {
const trace = await requestTraceApi.getRequestTrace(reqId, { attemptedOnly: false })
@@ -136,25 +213,33 @@ export function useModelTest(options: UseModelTestOptions) {
startPolling(reqId)
try {
const normalizedMessage = typeof params.message === 'string' && params.message.trim()
? params.message.trim()
: undefined
const message = normalizedMessage(params.message)
const result = await testModelFailover({
provider_id: providerId(),
mode: params.mode,
model_name: params.modelName,
failover_models: [params.modelName],
api_format: params.apiFormat,
endpoint_id: params.endpointId,
...(normalizedMessage ? { message: normalizedMessage } : {}),
...(params.requestHeaders ? { request_headers: params.requestHeaders } : {}),
...(params.requestBody ? { request_body: params.requestBody } : {}),
request_id: reqId,
concurrency: params.concurrency,
}, {
signal: abortController.signal,
})
let result = params.mode === 'direct'
? await runDirectTest(params, reqId, abortController.signal)
: await testModelFailover({
provider_id: providerId(),
mode: params.mode,
model_name: params.modelName,
failover_models: [params.modelName],
api_format: params.apiFormat,
endpoint_id: params.endpointId,
...(message ? { message } : {}),
...(params.requestHeaders ? { request_headers: params.requestHeaders } : {}),
...(params.requestBody ? { request_body: params.requestBody } : {}),
request_id: reqId,
concurrency: params.concurrency,
}, {
signal: abortController.signal,
})
if (
params.mode === 'global'
&& !result.success
&& result.error === LOCAL_FAILOVER_UNCONFIGURED_MESSAGE
) {
result = await runDirectTest(params, reqId, abortController.signal)
}
const keepTraceContext = resultHasTraceContext(result)
if (result.success) {