diff --git a/deploy/test/index.spec.ts.snap b/deploy/test/index.spec.ts.snap index 65cf0ad..af7dcb5 100644 --- a/deploy/test/index.spec.ts.snap +++ b/deploy/test/index.spec.ts.snap @@ -14,8 +14,8 @@ exports[`deploy > should call openai via gateway > llm 1`] = ` }, }, ], - "created": 1761828474, - "id": "chatcmpl-CWMMElxV7Z5jV4zs2g2cRQjZTsY8M", + "created": 1762272055, + "id": "chatcmpl-CYDklwaN7x9okuWTnABMCrZykoiRj", "model": "gpt-5-2025-08-07", "object": "chat.completion", "service_tier": "default", @@ -118,7 +118,7 @@ exports[`deploy > should call openai via gateway > span 1`] = ` { "key": "gen_ai.response.id", "value": { - "stringValue": "chatcmpl-CWMMElxV7Z5jV4zs2g2cRQjZTsY8M", + "stringValue": "chatcmpl-CYDklwaN7x9okuWTnABMCrZykoiRj", }, }, { @@ -293,7 +293,7 @@ exports[`deploy > should call openai via gateway > span 1`] = ` { "key": "http.response.body.text", "value": { - "stringValue": "{"id":"chatcmpl-CWMMElxV7Z5jV4zs2g2cRQjZTsY8M","object":"chat.completion","created":1761828474,"model":"gpt-5-2025-08-07","choices":[{"index":0,"message":{"role":"assistant","content":"Paris.","refusal":null,"annotations":[]},"finish_reason":"stop"}],"usage":{"prompt_tokens":23,"completion_tokens":75,"total_tokens":98,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":64,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0},"pydantic_ai_gateway":{"cost_estimate":0.00077875}},"service_tier":"default","system_fingerprint":null}", + "stringValue": "{"id":"chatcmpl-CYDklwaN7x9okuWTnABMCrZykoiRj","object":"chat.completion","created":1762272055,"model":"gpt-5-2025-08-07","choices":[{"index":0,"message":{"role":"assistant","content":"Paris.","refusal":null,"annotations":[]},"finish_reason":"stop"}],"usage":{"prompt_tokens":23,"completion_tokens":75,"total_tokens":98,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":64,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0},"pydantic_ai_gateway":{"cost_estimate":0.00077875}},"service_tier":"default","system_fingerprint":null}", }, }, { diff --git a/gateway/package.json b/gateway/package.json index 43291c6..5e846ca 100644 --- a/gateway/package.json +++ b/gateway/package.json @@ -13,6 +13,7 @@ "@opentelemetry/resources": "^2.0.1", "@pydantic/genai-prices": "^0.0.35", "@pydantic/logfire-api": "^0.9.0", + "@streamparser/json-whatwg": "^0.0.22", "eventsource-parser": "^3.0.6", "mime-types": "^3.0.1", "ts-pattern": "^5.8.0" diff --git a/gateway/src/api/google.ts b/gateway/src/api/google.ts index 088094d..7b0f88b 100644 --- a/gateway/src/api/google.ts +++ b/gateway/src/api/google.ts @@ -19,11 +19,11 @@ import type { TextPart, } from '../otel/genai' import { isMapping, type JsonData } from '../providers/default' -import { BaseAPI } from './base' +import { BaseAPI, type ExtractedRequest, type ExtractedResponse, type ExtractorConfig } from './base' export { GenerateContentResponse } from '@google/genai' -export class GoogleAPI extends BaseAPI { +export class GoogleAPI extends BaseAPI { requestStopSequences = (_request: GoogleRequest): string[] | undefined => { return _request.generationConfig?.stopSequences ?? undefined } @@ -67,6 +67,28 @@ export class GoogleAPI extends BaseAPI { systemInstructions = (_request: GoogleRequest): TextPart[] | undefined => { return systemInstructions(_request.systemInstruction) } + + // SafeExtractor implementation + + requestExtractors: ExtractorConfig = { + requestModel: (_request: GoogleRequest) => { + this.extractedRequest.requestModel = this.requestModel + }, + } + + chunkExtractors: ExtractorConfig = { + usage: (chunk: GenerateContentResponse) => { + if (chunk.usageMetadata) { + // TODO(Marcelo): This is likely to be wrong, since we are not summing the usage. + this.extractedResponse.usage = this.extractUsage(chunk) + } + }, + responseModel: (chunk: GenerateContentResponse) => { + if (chunk.modelVersion) { + this.extractedResponse.responseModel = chunk.modelVersion + } + }, + } } function mapContent(content: Content): ChatMessage { diff --git a/gateway/src/index.ts b/gateway/src/index.ts index 6798c26..42d44bd 100644 --- a/gateway/src/index.ts +++ b/gateway/src/index.ts @@ -45,7 +45,7 @@ export async function gatewayFetch( ctx: ExecutionContext, options: GatewayOptions, ): Promise { - let { pathname: proxyPath } = url + let { pathname: proxyPath, search: queryString } = url if (options.proxyPrefixLength) { proxyPath = proxyPath.slice(options.proxyPrefixLength) } @@ -53,7 +53,7 @@ export async function gatewayFetch( if (proxyPath === '/') { return index(request, options) } else { - return await gateway(request, proxyPath, ctx, options) + return await gateway(request, `${proxyPath}${queryString}`, ctx, options) } } catch (error) { if (error instanceof ResponseError) { diff --git a/gateway/src/providers/default.ts b/gateway/src/providers/default.ts index 4a0cacc..eeeb737 100644 --- a/gateway/src/providers/default.ts +++ b/gateway/src/providers/default.ts @@ -321,9 +321,7 @@ export class DefaultProviderProxy { } } - const isStreaming = - responseHeaders.get('content-type')?.startsWith('text/event-stream') || - ('stream' in requestBodyData && requestBodyData.stream === true) + const isStreaming = this.isStreaming(responseHeaders, requestBodyData) if (isStreaming) { return this.dispatchStreaming(prepResult, response, responseHeaders) } @@ -465,6 +463,13 @@ export class DefaultProviderProxy { } } + protected isStreaming(responseHeaders: Headers, requestBodyData: JsonData): boolean { + return ( + responseHeaders.get('content-type')?.toLowerCase().startsWith('text/event-stream') || + ('stream' in requestBodyData && requestBodyData.stream === true) + ) + } + protected isWhitelistedEndpoint(): boolean { return false } diff --git a/gateway/src/providers/google/auth.ts b/gateway/src/providers/google/auth.ts index 40e8506..7a5842f 100644 --- a/gateway/src/providers/google/auth.ts +++ b/gateway/src/providers/google/auth.ts @@ -1,6 +1,6 @@ import { ResponseError } from '../../utils' -export async function authToken(credentials: string, kv: KVNamespace): Promise { +export async function authToken(credentials: string, kv: KVNamespace, subFetch: typeof fetch): Promise { const serviceAccountHash = await hash(credentials) const cacheKey = `gcp-auth:${serviceAccountHash}` const cachedToken = await kv.get(cacheKey, { cacheTtl: 300 }) @@ -9,7 +9,7 @@ export async function authToken(credentials: string, kv: KVNamespace): Promise { return `${signingInput}.${b64UrlEncodeArray(signature)}` } -async function getAccessToken(jwt: string): Promise { +async function getAccessToken(jwt: string, subFetch: typeof fetch): Promise { const body = new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: jwt }) - const response = await fetch(tokenUrl, { + const response = await subFetch(tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, signal: AbortSignal.timeout(10000), diff --git a/gateway/src/providers/google/index.ts b/gateway/src/providers/google/index.ts index 9bd985b..4c2d643 100644 --- a/gateway/src/providers/google/index.ts +++ b/gateway/src/providers/google/index.ts @@ -19,7 +19,7 @@ export class GoogleVertexProvider extends DefaultProviderProxy { if (!path) { return { error: 'Unable to parse path' } } - return `${this.providerProxy.baseUrl}${path}` + return `${stripTrailingSlash(this.providerProxy.baseUrl)}/${stripLeadingSlash(path)}` } else { return { error: 'baseUrl is required for the Google Provider' } } @@ -72,7 +72,8 @@ export class GoogleVertexProvider extends DefaultProviderProxy { this.flavor = 'anthropic' } - return `/${version}/projects/${projectId}/locations/${region}/publishers/${publisher}/models/${modelAndApi}` + const path = `/${version}/projects/${projectId}/locations/${region}/publishers/${publisher}/models/${modelAndApi}` + return path } async prepRequest() { @@ -92,7 +93,7 @@ export class GoogleVertexProvider extends DefaultProviderProxy { } async requestHeaders(headers: Headers): Promise { - const token = await authToken(this.providerProxy.credentials, this.options.kv) + const token = await authToken(this.providerProxy.credentials, this.options.kv, this.options.subFetch) headers.set('Authorization', `Bearer ${token}`) } } @@ -104,10 +105,9 @@ export class GoogleVertexProvider extends DefaultProviderProxy { * @param url - The URL to extract the region from e.g. https://europe-west4-aiplatform.googleapis.com or https://aiplatform.googleapis.com. */ function regionFromUrl(url: string): null | string { - if (url.includes('https://aiplatform.googleapis.com')) { - return 'global' - } - // The group includes regions with hyphen like "europe-west4" - const match = url.match(/^https:\/\/(.+?)-aiplatform\.googleapis\.com$/) - return match?.[1] ?? null + const match = url.match(/^https:\/\/([^-]+)-aiplatform\.googleapis\.com$/) + return match?.[1] ?? 'global' } + +const stripTrailingSlash = (url: string): string => (url.endsWith('/') ? url.slice(0, -1) : url) +const stripLeadingSlash = (url: string): string => (url.startsWith('/') ? url.slice(1) : url) diff --git a/gateway/test/env.d.ts b/gateway/test/env.d.ts index ba8995c..0b7b98d 100644 --- a/gateway/test/env.d.ts +++ b/gateway/test/env.d.ts @@ -6,6 +6,7 @@ interface Env { GROQ_API_KEY: string ANTHROPIC_API_KEY: string AWS_BEARER_TOKEN_BEDROCK: string + GOOGLE_SERVICE_ACCOUNT_KEY: string } declare module 'cloudflare:test' { diff --git a/gateway/test/gateway.spec.ts.snap b/gateway/test/gateway.spec.ts.snap index ff6698b..2c98122 100644 --- a/gateway/test/gateway.spec.ts.snap +++ b/gateway/test/gateway.spec.ts.snap @@ -14,8 +14,8 @@ exports[`custom proxyPrefixLength > inference > proxyPrefixLength 1`] = ` }, }, ], - "created": 1761823178, - "id": "chatcmpl-CWKyoLFrrxfDdUZO6hAaDA7rYn3Fo", + "created": 1762271642, + "id": "chatcmpl-CYDe6BCWOKGGGTlQLofyQ2DP3QTRV", "model": "gpt-5-2025-08-07", "object": "chat.completion", "service_tier": "default", diff --git a/gateway/test/providers/anthropic.spec.ts.snap b/gateway/test/providers/anthropic.spec.ts.snap index f440728..d94df4b 100644 --- a/gateway/test/providers/anthropic.spec.ts.snap +++ b/gateway/test/providers/anthropic.spec.ts.snap @@ -1130,7 +1130,7 @@ exports[`anthropic > should call anthropic via gateway with stream > span 1`] = { "key": "logfire.json_schema", "value": { - "stringValue": "{"type":"object","properties":{"gen_ai.system":{"type":"string"},"gen_ai.operation.name":{"type":"string"},"gen_ai.request.model":{"type":"string"},"gen_ai.request.max_tokens":{"type":"number"},"gen_ai.response.model":{"type":"string"},"gen_ai.response.id":{"type":"string"},"gen_ai.usage.input_tokens":{"type":"number"},"gen_ai.usage.cache_read_tokens":{"type":"number"},"gen_ai.usage.cache_write_tokens":{"type":"number"},"gen_ai.usage.output_tokens":{"type":"number"},"http.request.method":{"type":"string"},"url.full":{"type":"string"},"http.request.header.accept":{"type":"string"},"http.request.header.anthropic-version":{"type":"string"},"http.request.header.authorization":{"type":"string"},"http.request.header.content-type":{"type":"string"},"http.request.header.user-agent":{"type":"string"},"http.request.header.x-stainless-arch":{"type":"string"},"http.request.header.x-stainless-lang":{"type":"string"},"http.request.header.x-stainless-os":{"type":"string"},"http.request.header.x-stainless-package-version":{"type":"string"},"http.request.header.x-stainless-retry-count":{"type":"string"},"http.request.header.x-stainless-runtime":{"type":"string"},"http.request.header.x-stainless-runtime-version":{"type":"string"},"http.request.header.x-stainless-timeout":{"type":"string"},"http.response.status_code":{"type":"number"},"http.response.header.server":{"type":"string"},"http.response.header.transfer-encoding":{"type":"string"}}}", + "stringValue": "{"type":"object","properties":{"gen_ai.system":{"type":"string"},"gen_ai.operation.name":{"type":"string"},"gen_ai.request.model":{"type":"string"},"gen_ai.request.max_tokens":{"type":"number"},"gen_ai.response.model":{"type":"string"},"gen_ai.response.id":{"type":"string"},"gen_ai.usage.input_tokens":{"type":"number"},"gen_ai.usage.cache_read_tokens":{"type":"number"},"gen_ai.usage.cache_write_tokens":{"type":"number"},"gen_ai.usage.output_tokens":{"type":"number"},"http.request.method":{"type":"string"},"url.full":{"type":"string"},"http.request.header.accept":{"type":"string"},"http.request.header.anthropic-version":{"type":"string"},"http.request.header.authorization":{"type":"string"},"http.request.header.content-type":{"type":"string"},"http.request.header.user-agent":{"type":"string"},"http.request.header.x-stainless-arch":{"type":"string"},"http.request.header.x-stainless-lang":{"type":"string"},"http.request.header.x-stainless-os":{"type":"string"},"http.request.header.x-stainless-package-version":{"type":"string"},"http.request.header.x-stainless-retry-count":{"type":"string"},"http.request.header.x-stainless-runtime":{"type":"string"},"http.request.header.x-stainless-runtime-version":{"type":"string"},"http.request.header.x-stainless-timeout":{"type":"string"},"http.response.status_code":{"type":"number"},"http.response.header.content-type":{"type":"string"},"http.response.header.server":{"type":"string"},"http.response.header.transfer-encoding":{"type":"string"}}}", }, }, { @@ -1295,6 +1295,12 @@ exports[`anthropic > should call anthropic via gateway with stream > span 1`] = "intValue": 200, }, }, + { + "key": "http.response.header.content-type", + "value": { + "stringValue": "text/event-stream; charset=utf-8", + }, + }, { "key": "http.response.header.server", "value": { diff --git a/gateway/test/providers/google.spec.ts b/gateway/test/providers/google.spec.ts index 5f28e61..460e48c 100644 --- a/gateway/test/providers/google.spec.ts +++ b/gateway/test/providers/google.spec.ts @@ -1,25 +1,78 @@ -import { GoogleGenAI } from '@google/genai' import { describe, expect } from 'vitest' import { test } from '../setup' +const body = JSON.stringify({ + contents: [{ parts: [{ text: "Samuel lived in London and was born on Jan 28th '87" }], role: 'user' }], + systemInstruction: { parts: [{ text: 'Extract information about the person' }], role: 'user' }, + tools: [ + { + functionDeclarations: [ + { + description: 'The final response which ends this conversation', + name: 'final_result', + parameters: { + properties: { + name: { description: 'The name of the person.', type: 'STRING' }, + dob: { + description: 'The date of birth of the person. MUST BE A VALID ISO 8601 date. (format: date)', + type: 'STRING', + }, + city: { description: 'The city where the person lives.', type: 'STRING' }, + }, + required: ['name', 'dob', 'city'], + type: 'OBJECT', + }, + }, + ], + }, + ], + toolConfig: { functionCallingConfig: { mode: 'ANY', allowedFunctionNames: ['final_result'] } }, + generationConfig: { temperature: 0.5, topP: 0.9, stopSequences: ['potato'] }, +}) +const headers = { + Authorization: 'healthy', + 'x-goog-api-client': 'google-genai-sdk/1.36.0 gl-python/3.13.0', + 'x-goog-api-key': 'unset', + accept: '*/*', + 'accept-encoding': 'deflate', + 'content-type': 'application/json', + 'content-length': body.length.toString(), + 'user-agent': + 'pydantic-ai/1.0.19.dev5+b3b34f9, google-genai-sdk/1.36.0 gl-python/3.13.0 via Pydantic AI Gateway unknown, contact engineering@pydantic.dev', + traceparent: '00-019a4effa21047ac31372f093cb8e712-8b60768281864a49-01', +} + describe('google', () => { // TODO(Marcelo): When Google supports `fetch` parameter, we can fix this: https://github.com/googleapis/js-genai/issues/999 - test.fails('google-vertex/default', async ({ gateway }) => { - const { otelBatch } = gateway - - // The `authToken` is passed as `Authorization` header with the anthropic client. - const client = new GoogleGenAI({ - apiKey: 'healthy', - httpOptions: { baseUrl: 'https://example.com/google-vertex' }, - }) - - const response = await client.models.generateContent({ - model: 'gemini-2.5-flash', - contents: 'What is the capital of france?', - config: { maxOutputTokens: 1024, topP: 0.95, topK: 1, temperature: 0.5, stopSequences: ['potato'] }, - }) - - expect(response).toMatchSnapshot('llm') + test('google-vertex/default', async ({ gateway }) => { + const { fetch, otelBatch } = gateway + + const response = await fetch( + 'https://example.com/gemini/v1beta1/projects/pydantic-ai/locations/global/publishers/google/models/gemini-2.5-flash:generateContent?alt=sse', + { method: 'POST', headers, body }, + ) + + const content = await response.text() + + expect(content).toMatchSnapshot('llm') + expect(otelBatch, 'otelBatch length not 1').toHaveLength(1) + expect(JSON.parse(otelBatch[0]!).resourceSpans?.[0].scopeSpans?.[0].spans?.[0]?.attributes).toMatchSnapshot('span') + }) + + test('google-vertex/stream', async ({ gateway }) => { + const { fetch, otelBatch } = gateway + + const response = await fetch( + 'https://example.com/gemini/v1beta1/projects/pydantic-ai/locations/global/publishers/google/models/gemini-2.5-flash:streamGenerateContent?alt=sse', + { method: 'POST', headers: { ...headers, 'x-vcr-filename': 'stream' }, body }, + ) + + const chunks: object[] = [] + for await (const chunk of response.body!) { + chunks.push(chunk) + } + + expect(chunks).toMatchSnapshot('chunks') expect(otelBatch, 'otelBatch length not 1').toHaveLength(1) expect(JSON.parse(otelBatch[0]!).resourceSpans?.[0].scopeSpans?.[0].spans?.[0]?.attributes).toMatchSnapshot('span') }) diff --git a/gateway/test/providers/google.spec.ts.snap b/gateway/test/providers/google.spec.ts.snap new file mode 100644 index 0000000..e2d6e98 --- /dev/null +++ b/gateway/test/providers/google.spec.ts.snap @@ -0,0 +1,264 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`google > google-vertex/default > llm 1`] = `"Path /gemini/v1beta1/projects/pydantic-ai/locations/global/publishers/google/models/gemini-2.5-flash:generateContent not supported"`; + +exports[`google > google-vertex/default > span 1`] = ` +[ + { + "key": "logfire.msg", + "value": { + "stringValue": "chat gemini-2.5-flash, unexpected response: 404", + }, + }, + { + "key": "logfire.json_schema", + "value": { + "stringValue": "{"type":"object","properties":{"gen_ai.operation.name":{"type":"string"},"gen_ai.request.model":{"type":"string"},"gen_ai.system":{"type":"string"},"http.response.status_code":{"type":"number"},"http.request.body.text":{"type":"string"},"http.response.body.text":{"type":"string"}}}", + }, + }, + { + "key": "logfire.level_num", + "value": { + "intValue": 13, + }, + }, + { + "key": "gen_ai.operation.name", + "value": { + "stringValue": "chat", + }, + }, + { + "key": "gen_ai.request.model", + "value": { + "stringValue": "gemini-2.5-flash", + }, + }, + { + "key": "gen_ai.system", + "value": { + "stringValue": "google-vertex", + }, + }, + { + "key": "http.response.status_code", + "value": { + "intValue": 404, + }, + }, + { + "key": "http.request.body.text", + "value": { + "stringValue": "{"contents":[{"parts":[{"text":"Samuel lived in London and was born on Jan 28th '87"}],"role":"user"}],"systemInstruction":{"parts":[{"text":"Extract information about the person"}],"role":"user"},"tools":[{"functionDeclarations":[{"description":"The final response which ends this conversation","name":"final_result","parameters":{"properties":{"name":{"description":"The name of the person.","type":"STRING"},"dob":{"description":"The date of birth of the person. MUST BE A VALID ISO 8601 date. (format: date)","type":"STRING"},"city":{"description":"The city where the person lives.","type":"STRING"}},"required":["name","dob","city"],"type":"OBJECT"}}]}],"toolConfig":{"functionCallingConfig":{"mode":"ANY","allowedFunctionNames":["final_result"]}},"generationConfig":{"temperature":0.5,"topP":0.9,"stopSequences":["potato"]}}", + }, + }, + { + "key": "http.response.body.text", + "value": { + "stringValue": "Path /gemini/v1beta1/projects/pydantic-ai/locations/global/publishers/google/models/gemini-2.5-flash:generateContent not supported", + }, + }, +] +`; + +exports[`google > google-vertex/stream > chunks 1`] = ` +[ + Uint8Array [ + 80, + 97, + 116, + 104, + 32, + 47, + 103, + 101, + 109, + 105, + 110, + 105, + 47, + 118, + 49, + 98, + 101, + 116, + 97, + 49, + 47, + 112, + 114, + 111, + 106, + 101, + 99, + 116, + 115, + 47, + 112, + 121, + 100, + 97, + 110, + 116, + 105, + 99, + 45, + 97, + 105, + 47, + 108, + 111, + 99, + 97, + 116, + 105, + 111, + 110, + 115, + 47, + 103, + 108, + 111, + 98, + 97, + 108, + 47, + 112, + 117, + 98, + 108, + 105, + 115, + 104, + 101, + 114, + 115, + 47, + 103, + 111, + 111, + 103, + 108, + 101, + 47, + 109, + 111, + 100, + 101, + 108, + 115, + 47, + 103, + 101, + 109, + 105, + 110, + 105, + 45, + 50, + 46, + 53, + 45, + 102, + 108, + 97, + 115, + 104, + 58, + 115, + 116, + 114, + 101, + 97, + 109, + 71, + 101, + 110, + 101, + 114, + 97, + 116, + 101, + 67, + 111, + 110, + 116, + 101, + 110, + 116, + 32, + 110, + 111, + 116, + 32, + 115, + 117, + 112, + 112, + 111, + 114, + 116, + 101, + 100, + ], +] +`; + +exports[`google > google-vertex/stream > span 1`] = ` +[ + { + "key": "logfire.msg", + "value": { + "stringValue": "chat gemini-2.5-flash, unexpected response: 404", + }, + }, + { + "key": "logfire.json_schema", + "value": { + "stringValue": "{"type":"object","properties":{"gen_ai.operation.name":{"type":"string"},"gen_ai.request.model":{"type":"string"},"gen_ai.system":{"type":"string"},"http.response.status_code":{"type":"number"},"http.request.body.text":{"type":"string"},"http.response.body.text":{"type":"string"}}}", + }, + }, + { + "key": "logfire.level_num", + "value": { + "intValue": 13, + }, + }, + { + "key": "gen_ai.operation.name", + "value": { + "stringValue": "chat", + }, + }, + { + "key": "gen_ai.request.model", + "value": { + "stringValue": "gemini-2.5-flash", + }, + }, + { + "key": "gen_ai.system", + "value": { + "stringValue": "google-vertex", + }, + }, + { + "key": "http.response.status_code", + "value": { + "intValue": 404, + }, + }, + { + "key": "http.request.body.text", + "value": { + "stringValue": "{"contents":[{"parts":[{"text":"Samuel lived in London and was born on Jan 28th '87"}],"role":"user"}],"systemInstruction":{"parts":[{"text":"Extract information about the person"}],"role":"user"},"tools":[{"functionDeclarations":[{"description":"The final response which ends this conversation","name":"final_result","parameters":{"properties":{"name":{"description":"The name of the person.","type":"STRING"},"dob":{"description":"The date of birth of the person. MUST BE A VALID ISO 8601 date. (format: date)","type":"STRING"},"city":{"description":"The city where the person lives.","type":"STRING"}},"required":["name","dob","city"],"type":"OBJECT"}}]}],"toolConfig":{"functionCallingConfig":{"mode":"ANY","allowedFunctionNames":["final_result"]}},"generationConfig":{"temperature":0.5,"topP":0.9,"stopSequences":["potato"]}}", + }, + }, + { + "key": "http.response.body.text", + "value": { + "stringValue": "Path /gemini/v1beta1/projects/pydantic-ai/locations/global/publishers/google/models/gemini-2.5-flash:streamGenerateContent not supported", + }, + }, +] +`; diff --git a/gateway/test/providers/openai.spec.ts b/gateway/test/providers/openai.spec.ts index 5c467f8..a454368 100644 --- a/gateway/test/providers/openai.spec.ts +++ b/gateway/test/providers/openai.spec.ts @@ -129,18 +129,18 @@ describe('openai', () => { expect(completion).toMatchSnapshot('llm') expect(completion.usage).toMatchInlineSnapshot(` { - "input_tokens": 1139, + "input_tokens": 1808, "input_tokens_details": { "cached_tokens": 0, }, - "output_tokens": 469, + "output_tokens": 1061, "output_tokens_details": { - "reasoning_tokens": 448, + "reasoning_tokens": 1024, }, "pydantic_ai_gateway": { - "cost_estimate": 0.006113749999999999, + "cost_estimate": 0.01287, }, - "total_tokens": 1608, + "total_tokens": 2869, } `) expect(otelBatch, 'otelBatch length not 1').toHaveLength(1) diff --git a/gateway/test/providers/openai.spec.ts.snap b/gateway/test/providers/openai.spec.ts.snap index 98d29b3..2574d82 100644 --- a/gateway/test/providers/openai.spec.ts.snap +++ b/gateway/test/providers/openai.spec.ts.snap @@ -14,8 +14,8 @@ exports[`openai > openai chat > llm 1`] = ` }, }, ], - "created": 1761823178, - "id": "chatcmpl-CWKyoLFrrxfDdUZO6hAaDA7rYn3Fo", + "created": 1762271642, + "id": "chatcmpl-CYDe6BCWOKGGGTlQLofyQ2DP3QTRV", "model": "gpt-5-2025-08-07", "object": "chat.completion", "service_tier": "default", @@ -120,7 +120,7 @@ exports[`openai > openai chat > span 1`] = ` { "key": "gen_ai.response.id", "value": { - "stringValue": "chatcmpl-CWKyoLFrrxfDdUZO6hAaDA7rYn3Fo", + "stringValue": "chatcmpl-CYDe6BCWOKGGGTlQLofyQ2DP3QTRV", }, }, { @@ -296,7 +296,7 @@ exports[`openai > openai chat > span 1`] = ` { "key": "http.response.body.text", "value": { - "stringValue": "{"id":"chatcmpl-CWKyoLFrrxfDdUZO6hAaDA7rYn3Fo","object":"chat.completion","created":1761823178,"model":"gpt-5-2025-08-07","choices":[{"index":0,"message":{"role":"assistant","content":"Paris.","refusal":null,"annotations":[]},"finish_reason":"stop"}],"usage":{"prompt_tokens":23,"completion_tokens":11,"total_tokens":34,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0},"pydantic_ai_gateway":{"cost_estimate":0.00013875}},"service_tier":"default","system_fingerprint":null}", + "stringValue": "{"id":"chatcmpl-CYDe6BCWOKGGGTlQLofyQ2DP3QTRV","object":"chat.completion","created":1762271642,"model":"gpt-5-2025-08-07","choices":[{"index":0,"message":{"role":"assistant","content":"Paris.","refusal":null,"annotations":[]},"finish_reason":"stop"}],"usage":{"prompt_tokens":23,"completion_tokens":11,"total_tokens":34,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0},"pydantic_ai_gateway":{"cost_estimate":0.00013875}},"service_tier":"default","system_fingerprint":null}", }, }, { @@ -360,8 +360,8 @@ exports[`openai > openai chat legacy name > llm 1`] = ` }, }, ], - "created": 1761823178, - "id": "chatcmpl-CWKyoLFrrxfDdUZO6hAaDA7rYn3Fo", + "created": 1762271642, + "id": "chatcmpl-CYDe6BCWOKGGGTlQLofyQ2DP3QTRV", "model": "gpt-5-2025-08-07", "object": "chat.completion", "service_tier": "default", @@ -466,7 +466,7 @@ exports[`openai > openai chat legacy name > span 1`] = ` { "key": "gen_ai.response.id", "value": { - "stringValue": "chatcmpl-CWKyoLFrrxfDdUZO6hAaDA7rYn3Fo", + "stringValue": "chatcmpl-CYDe6BCWOKGGGTlQLofyQ2DP3QTRV", }, }, { @@ -642,7 +642,7 @@ exports[`openai > openai chat legacy name > span 1`] = ` { "key": "http.response.body.text", "value": { - "stringValue": "{"id":"chatcmpl-CWKyoLFrrxfDdUZO6hAaDA7rYn3Fo","object":"chat.completion","created":1761823178,"model":"gpt-5-2025-08-07","choices":[{"index":0,"message":{"role":"assistant","content":"Paris.","refusal":null,"annotations":[]},"finish_reason":"stop"}],"usage":{"prompt_tokens":23,"completion_tokens":11,"total_tokens":34,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0},"pydantic_ai_gateway":{"cost_estimate":0.00013875}},"service_tier":"default","system_fingerprint":null}", + "stringValue": "{"id":"chatcmpl-CYDe6BCWOKGGGTlQLofyQ2DP3QTRV","object":"chat.completion","created":1762271642,"model":"gpt-5-2025-08-07","choices":[{"index":0,"message":{"role":"assistant","content":"Paris.","refusal":null,"annotations":[]},"finish_reason":"stop"}],"usage":{"prompt_tokens":23,"completion_tokens":11,"total_tokens":34,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0},"pydantic_ai_gateway":{"cost_estimate":0.00013875}},"service_tier":"default","system_fingerprint":null}", }, }, { @@ -706,10 +706,10 @@ exports[`openai > openai chat stream > chunks 1`] = ` "index": 0, }, ], - "created": 1761823216, - "id": "chatcmpl-CWKzQzo2QLXPzzjG5ZhftNDkOKT2q", + "created": 1762271713, + "id": "chatcmpl-CYDfFsepopZlcPzyFj5cdegEMUIO3", "model": "gpt-5-2025-08-07", - "obfuscation": "znk17e1VnU", + "obfuscation": "1oeFyzekfM", "object": "chat.completion.chunk", "service_tier": "default", "system_fingerprint": null, @@ -725,10 +725,10 @@ exports[`openai > openai chat stream > chunks 1`] = ` "index": 0, }, ], - "created": 1761823216, - "id": "chatcmpl-CWKzQzo2QLXPzzjG5ZhftNDkOKT2q", + "created": 1762271713, + "id": "chatcmpl-CYDfFsepopZlcPzyFj5cdegEMUIO3", "model": "gpt-5-2025-08-07", - "obfuscation": "9uyCALM", + "obfuscation": "iNYovHl", "object": "chat.completion.chunk", "service_tier": "default", "system_fingerprint": null, @@ -744,10 +744,10 @@ exports[`openai > openai chat stream > chunks 1`] = ` "index": 0, }, ], - "created": 1761823216, - "id": "chatcmpl-CWKzQzo2QLXPzzjG5ZhftNDkOKT2q", + "created": 1762271713, + "id": "chatcmpl-CYDfFsepopZlcPzyFj5cdegEMUIO3", "model": "gpt-5-2025-08-07", - "obfuscation": "DxMvC33A2iV", + "obfuscation": "z2nujSb0c5n", "object": "chat.completion.chunk", "service_tier": "default", "system_fingerprint": null, @@ -761,10 +761,10 @@ exports[`openai > openai chat stream > chunks 1`] = ` "index": 0, }, ], - "created": 1761823216, - "id": "chatcmpl-CWKzQzo2QLXPzzjG5ZhftNDkOKT2q", + "created": 1762271713, + "id": "chatcmpl-CYDfFsepopZlcPzyFj5cdegEMUIO3", "model": "gpt-5-2025-08-07", - "obfuscation": "6BmmA2", + "obfuscation": "OvkX97", "object": "chat.completion.chunk", "service_tier": "default", "system_fingerprint": null, @@ -772,10 +772,10 @@ exports[`openai > openai chat stream > chunks 1`] = ` }, { "choices": [], - "created": 1761823216, - "id": "chatcmpl-CWKzQzo2QLXPzzjG5ZhftNDkOKT2q", + "created": 1762271713, + "id": "chatcmpl-CYDfFsepopZlcPzyFj5cdegEMUIO3", "model": "gpt-5-2025-08-07", - "obfuscation": "8u6q1bA9GJu", + "obfuscation": "d9x8d108MYZ", "object": "chat.completion.chunk", "service_tier": "default", "system_fingerprint": null, @@ -809,7 +809,7 @@ exports[`openai > openai chat stream > span 1`] = ` { "key": "logfire.json_schema", "value": { - "stringValue": "{"type":"object","properties":{"gen_ai.system":{"type":"string"},"gen_ai.operation.name":{"type":"string"},"gen_ai.request.model":{"type":"string"},"gen_ai.request.max_tokens":{"type":"number"},"gen_ai.response.model":{"type":"string"},"gen_ai.response.id":{"type":"string"},"gen_ai.usage.input_tokens":{"type":"number"},"gen_ai.usage.cache_read_tokens":{"type":"number"},"gen_ai.usage.output_tokens":{"type":"number"},"gen_ai.usage.input_audio_tokens":{"type":"number"},"gen_ai.usage.output_audio_tokens":{"type":"number"},"http.request.method":{"type":"string"},"url.full":{"type":"string"},"http.request.header.accept":{"type":"string"},"http.request.header.authorization":{"type":"string"},"http.request.header.content-length":{"type":"string"},"http.request.header.content-type":{"type":"string"},"http.request.header.user-agent":{"type":"string"},"http.request.header.x-stainless-arch":{"type":"string"},"http.request.header.x-stainless-lang":{"type":"string"},"http.request.header.x-stainless-os":{"type":"string"},"http.request.header.x-stainless-package-version":{"type":"string"},"http.request.header.x-stainless-retry-count":{"type":"string"},"http.request.header.x-stainless-runtime":{"type":"string"},"http.request.header.x-stainless-runtime-version":{"type":"string"},"http.request.header.x-stainless-timeout":{"type":"string"},"http.response.status_code":{"type":"number"},"http.response.header.server":{"type":"string"},"http.response.header.transfer-encoding":{"type":"string"}}}", + "stringValue": "{"type":"object","properties":{"gen_ai.system":{"type":"string"},"gen_ai.operation.name":{"type":"string"},"gen_ai.request.model":{"type":"string"},"gen_ai.request.max_tokens":{"type":"number"},"gen_ai.response.model":{"type":"string"},"gen_ai.response.id":{"type":"string"},"gen_ai.usage.input_tokens":{"type":"number"},"gen_ai.usage.cache_read_tokens":{"type":"number"},"gen_ai.usage.output_tokens":{"type":"number"},"gen_ai.usage.input_audio_tokens":{"type":"number"},"gen_ai.usage.output_audio_tokens":{"type":"number"},"http.request.method":{"type":"string"},"url.full":{"type":"string"},"http.request.header.accept":{"type":"string"},"http.request.header.authorization":{"type":"string"},"http.request.header.content-length":{"type":"string"},"http.request.header.content-type":{"type":"string"},"http.request.header.user-agent":{"type":"string"},"http.request.header.x-stainless-arch":{"type":"string"},"http.request.header.x-stainless-lang":{"type":"string"},"http.request.header.x-stainless-os":{"type":"string"},"http.request.header.x-stainless-package-version":{"type":"string"},"http.request.header.x-stainless-retry-count":{"type":"string"},"http.request.header.x-stainless-runtime":{"type":"string"},"http.request.header.x-stainless-runtime-version":{"type":"string"},"http.request.header.x-stainless-timeout":{"type":"string"},"http.response.status_code":{"type":"number"},"http.response.header.content-type":{"type":"string"},"http.response.header.server":{"type":"string"},"http.response.header.transfer-encoding":{"type":"string"}}}", }, }, { @@ -851,7 +851,7 @@ exports[`openai > openai chat stream > span 1`] = ` { "key": "gen_ai.response.id", "value": { - "stringValue": "chatcmpl-CWKzQzo2QLXPzzjG5ZhftNDkOKT2q", + "stringValue": "chatcmpl-CYDfFsepopZlcPzyFj5cdegEMUIO3", }, }, { @@ -980,6 +980,12 @@ exports[`openai > openai chat stream > span 1`] = ` "intValue": 200, }, }, + { + "key": "http.response.header.content-type", + "value": { + "stringValue": "text/event-stream; charset=utf-8", + }, + }, { "key": "http.response.header.server", "value": { @@ -1001,9 +1007,9 @@ exports[`openai > openai responses > llm 1`] = ` "billing": { "payer": "developer", }, - "created_at": 1761823182, + "created_at": 1762271645, "error": null, - "id": "resp_09393ae2ee2c946a00690349ce17e081a1a43492a969b778ed", + "id": "resp_0460eb44ce4bf29800690a219dd0108193abee8e830ce725cb", "incomplete_details": null, "instructions": "reply concisely", "max_output_tokens": null, @@ -1013,7 +1019,7 @@ exports[`openai > openai responses > llm 1`] = ` "object": "response", "output": [ { - "id": "rs_09393ae2ee2c946a00690349cea39481a199b6fa1ff1db1ccc", + "id": "rs_0460eb44ce4bf29800690a219ea7b08193be21548d7c847f56", "summary": [], "type": "reasoning", }, @@ -1022,20 +1028,21 @@ exports[`openai > openai responses > llm 1`] = ` { "annotations": [], "logprobs": [], - "text": "Usually blue in daylight. It can appear red/orange at sunrise/sunset, gray when overcast, and dark at night.", + "text": "Usually blue on a clear day (due to Rayleigh scattering), but it can be red/orange at sunrise/sunset, gray when cloudy, and black at night.", "type": "output_text", }, ], - "id": "msg_09393ae2ee2c946a00690349d1570081a1844badf7835697d5", + "id": "msg_0460eb44ce4bf29800690a21a2388c8193b3f20e6d76de9744", "role": "assistant", "status": "completed", "type": "message", }, ], - "output_text": "Usually blue in daylight. It can appear red/orange at sunrise/sunset, gray when overcast, and dark at night.", + "output_text": "Usually blue on a clear day (due to Rayleigh scattering), but it can be red/orange at sunrise/sunset, gray when cloudy, and black at night.", "parallel_tool_calls": true, "previous_response_id": null, "prompt_cache_key": null, + "prompt_cache_retention": null, "reasoning": { "effort": "medium", "summary": null, @@ -1061,14 +1068,14 @@ exports[`openai > openai responses > llm 1`] = ` "input_tokens_details": { "cached_tokens": 0, }, - "output_tokens": 96, + "output_tokens": 168, "output_tokens_details": { - "reasoning_tokens": 64, + "reasoning_tokens": 128, }, "pydantic_ai_gateway": { - "cost_estimate": 0.000985, + "cost_estimate": 0.0017050000000000001, }, - "total_tokens": 116, + "total_tokens": 188, }, "user": null, } @@ -1143,7 +1150,7 @@ exports[`openai > openai responses > span 1`] = ` { "key": "gen_ai.response.id", "value": { - "stringValue": "resp_09393ae2ee2c946a00690349ce17e081a1a43492a969b778ed", + "stringValue": "resp_0460eb44ce4bf29800690a219dd0108193abee8e830ce725cb", }, }, { @@ -1245,7 +1252,7 @@ exports[`openai > openai responses > span 1`] = ` { "key": "content", "value": { - "stringValue": "Usually blue in daylight. It can appear red/orange at sunrise/sunset, gray when overcast, and dark at night.", + "stringValue": "Usually blue on a clear day (due to Rayleigh scattering), but it can be red/orange at sunrise/sunset, gray when cloudy, and black at night.", }, }, ], @@ -1285,7 +1292,7 @@ exports[`openai > openai responses > span 1`] = ` { "key": "http.response.body.text", "value": { - "stringValue": "{"id":"resp_09393ae2ee2c946a00690349ce17e081a1a43492a969b778ed","object":"response","created_at":1761823182,"status":"completed","background":false,"billing":{"payer":"developer"},"error":null,"incomplete_details":null,"instructions":"reply concisely","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5-2025-08-07","output":[{"id":"rs_09393ae2ee2c946a00690349cea39481a199b6fa1ff1db1ccc","type":"reasoning","summary":[]},{"id":"msg_09393ae2ee2c946a00690349d1570081a1844badf7835697d5","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"Usually blue in daylight. It can appear red/orange at sunrise/sunset, gray when overcast, and dark at night."}],"role":"assistant"}],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"reasoning":{"effort":"medium","summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1,"truncation":"disabled","usage":{"input_tokens":20,"input_tokens_details":{"cached_tokens":0},"output_tokens":96,"output_tokens_details":{"reasoning_tokens":64},"total_tokens":116,"pydantic_ai_gateway":{"cost_estimate":0.000985}},"user":null,"metadata":{}}", + "stringValue": "{"id":"resp_0460eb44ce4bf29800690a219dd0108193abee8e830ce725cb","object":"response","created_at":1762271645,"status":"completed","background":false,"billing":{"payer":"developer"},"error":null,"incomplete_details":null,"instructions":"reply concisely","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5-2025-08-07","output":[{"id":"rs_0460eb44ce4bf29800690a219ea7b08193be21548d7c847f56","type":"reasoning","summary":[]},{"id":"msg_0460eb44ce4bf29800690a21a2388c8193b3f20e6d76de9744","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"Usually blue on a clear day (due to Rayleigh scattering), but it can be red/orange at sunrise/sunset, gray when cloudy, and black at night."}],"role":"assistant"}],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":"medium","summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1,"truncation":"disabled","usage":{"input_tokens":20,"input_tokens_details":{"cached_tokens":0},"output_tokens":168,"output_tokens_details":{"reasoning_tokens":128},"total_tokens":188,"pydantic_ai_gateway":{"cost_estimate":0.0017050000000000001}},"user":null,"metadata":{}}", }, }, { @@ -1313,7 +1320,7 @@ exports[`openai > openai responses > span 1`] = ` { "key": "gen_ai.usage.output_tokens", "value": { - "intValue": 96, + "intValue": 168, }, }, { @@ -1345,8 +1352,8 @@ exports[`openai > openai responses legacy name > llm 1`] = ` }, }, ], - "created": 1761823178, - "id": "chatcmpl-CWKyoLFrrxfDdUZO6hAaDA7rYn3Fo", + "created": 1762271642, + "id": "chatcmpl-CYDe6BCWOKGGGTlQLofyQ2DP3QTRV", "model": "gpt-5-2025-08-07", "object": "chat.completion", "service_tier": "default", @@ -1451,7 +1458,7 @@ exports[`openai > openai responses legacy name > span 1`] = ` { "key": "gen_ai.response.id", "value": { - "stringValue": "chatcmpl-CWKyoLFrrxfDdUZO6hAaDA7rYn3Fo", + "stringValue": "chatcmpl-CYDe6BCWOKGGGTlQLofyQ2DP3QTRV", }, }, { @@ -1627,7 +1634,7 @@ exports[`openai > openai responses legacy name > span 1`] = ` { "key": "http.response.body.text", "value": { - "stringValue": "{"id":"chatcmpl-CWKyoLFrrxfDdUZO6hAaDA7rYn3Fo","object":"chat.completion","created":1761823178,"model":"gpt-5-2025-08-07","choices":[{"index":0,"message":{"role":"assistant","content":"Paris.","refusal":null,"annotations":[]},"finish_reason":"stop"}],"usage":{"prompt_tokens":23,"completion_tokens":11,"total_tokens":34,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0},"pydantic_ai_gateway":{"cost_estimate":0.00013875}},"service_tier":"default","system_fingerprint":null}", + "stringValue": "{"id":"chatcmpl-CYDe6BCWOKGGGTlQLofyQ2DP3QTRV","object":"chat.completion","created":1762271642,"model":"gpt-5-2025-08-07","choices":[{"index":0,"message":{"role":"assistant","content":"Paris.","refusal":null,"annotations":[]},"finish_reason":"stop"}],"usage":{"prompt_tokens":23,"completion_tokens":11,"total_tokens":34,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0},"pydantic_ai_gateway":{"cost_estimate":0.00013875}},"service_tier":"default","system_fingerprint":null}", }, }, { @@ -1683,9 +1690,9 @@ exports[`openai > openai responses with builtin tools > llm 1`] = ` "billing": { "payer": "developer", }, - "created_at": 1761823186, + "created_at": 1762271730, "error": null, - "id": "resp_032eeb0f1946b3fc00690349d295108195a02e83631653e35f", + "id": "resp_0dc4a3c582e21a7f00690a21f2f5a881a38c2bc17284ab0bdb", "incomplete_details": null, "instructions": "be precise", "max_output_tokens": null, @@ -1695,39 +1702,94 @@ exports[`openai > openai responses with builtin tools > llm 1`] = ` "object": "response", "output": [ { - "id": "rs_032eeb0f1946b3fc00690349d5b7b88195bce44ca9ceab4941", + "id": "rs_0dc4a3c582e21a7f00690a21f5c01c81a387696a1225044c74", "summary": [], "type": "reasoning", }, { - "code": "import decimal, math -n=123902139123 -math.isqrt(n), math.isqrt(n)**2 == n, math.sqrt(n)", - "container_id": "cntr_690349d4fe9c8190a698334dee208a580180e82f7f5ef49c", - "id": "ci_032eeb0f1946b3fc00690349dd01688195a33f5e06b171c4fe", + "code": "import decimal, math, fractions, sys, statistics, random", + "container_id": "cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18", + "id": "ci_0dc4a3c582e21a7f00690a21f79eb081a385bc9ea91607bb8e", "outputs": null, "status": "completed", "type": "code_interpreter_call", }, { - "id": "rs_032eeb0f1946b3fc00690349dffc9481958d20478e41cd0e6d", + "code": "n = 123_902_139_123 +# Check if perfect square +is_square = int(n**0.5)**2 == n +sqrt_n = n**0.5 +int_part = int(sqrt_n) +is_square, int_part", + "container_id": "cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18", + "id": "ci_0dc4a3c582e21a7f00690a21fb50d081a3839fffe407f88965", + "outputs": null, + "status": "completed", + "type": "code_interpreter_call", + }, + { + "id": "rs_0dc4a3c582e21a7f00690a21fdb20081a38c8d71579c417602", + "summary": [], + "type": "reasoning", + }, + { + "code": "int_part, int_part**2, (int_part+1)**2", + "container_id": "cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18", + "id": "ci_0dc4a3c582e21a7f00690a21fe485c81a3bc4ba9ccc7418d6e", + "outputs": null, + "status": "completed", + "type": "code_interpreter_call", + }, + { + "id": "rs_0dc4a3c582e21a7f00690a220009f081a3ae01ee9362963591", "summary": [], "type": "reasoning", }, { "code": "from decimal import Decimal, getcontext getcontext().prec = 50 -n=Decimal(123902139123) -s = n.sqrt() -s", - "container_id": "cntr_690349d4fe9c8190a698334dee208a580180e82f7f5ef49c", - "id": "ci_032eeb0f1946b3fc00690349e486f081958929018fa377592f", +sqrt_val = Decimal(n).sqrt() +sqrt_val", + "container_id": "cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18", + "id": "ci_0dc4a3c582e21a7f00690a2200de5881a3b0fb27e1e2317788", "outputs": null, "status": "completed", "type": "code_interpreter_call", }, { - "id": "rs_032eeb0f1946b3fc00690349e8e29c81958735b9417d916d13", + "id": "rs_0dc4a3c582e21a7f00690a22031aa881a38cd6660b4d830c50", + "summary": [], + "type": "reasoning", + }, + { + "code": "float_val = float(sqrt_val) +float_val", + "container_id": "cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18", + "id": "ci_0dc4a3c582e21a7f00690a2209f9cc81a39c0e1ddfa029c7a6", + "outputs": null, + "status": "completed", + "type": "code_interpreter_call", + }, + { + "id": "rs_0dc4a3c582e21a7f00690a220baf1481a3bda6abb8b9c09f88", + "summary": [], + "type": "reasoning", + }, + { + "code": "from decimal import ROUND_HALF_UP +def round_str(dec, places): + q = Decimal(10) ** -places + return str(dec.quantize(q, rounding=ROUND_HALF_UP)) +for p in [6, 9, 12, 15]: + print(p, round_str(sqrt_val, p))", + "container_id": "cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18", + "id": "ci_0dc4a3c582e21a7f00690a220c125481a3ad139dde1b01ef42", + "outputs": null, + "status": "completed", + "type": "code_interpreter_call", + }, + { + "id": "rs_0dc4a3c582e21a7f00690a220ee65081a3b4a3c606ed32f88c", "summary": [], "type": "reasoning", }, @@ -1736,20 +1798,21 @@ s", { "annotations": [], "logprobs": [], - "text": "√123902139123 ≈ 351997.35669888204", + "text": "The square root of 123,902,139,123 is approximately 351,997.356698882044. It’s not a perfect square.", "type": "output_text", }, ], - "id": "msg_032eeb0f1946b3fc00690349edec7c819599bc5f0620f08cb7", + "id": "msg_0dc4a3c582e21a7f00690a221614e081a3b9e5621bd0f3ba23", "role": "assistant", "status": "completed", "type": "message", }, ], - "output_text": "√123902139123 ≈ 351997.35669888204", + "output_text": "The square root of 123,902,139,123 is approximately 351,997.356698882044. It’s not a perfect square.", "parallel_tool_calls": true, "previous_response_id": null, "prompt_cache_key": null, + "prompt_cache_retention": null, "reasoning": { "effort": "medium", "summary": null, @@ -1778,18 +1841,18 @@ s", "top_p": 1, "truncation": "disabled", "usage": { - "input_tokens": 1139, + "input_tokens": 1808, "input_tokens_details": { "cached_tokens": 0, }, - "output_tokens": 469, + "output_tokens": 1061, "output_tokens_details": { - "reasoning_tokens": 448, + "reasoning_tokens": 1024, }, "pydantic_ai_gateway": { - "cost_estimate": 0.006113749999999999, + "cost_estimate": 0.01287, }, - "total_tokens": 1608, + "total_tokens": 2869, }, "user": null, } @@ -1864,7 +1927,7 @@ exports[`openai > openai responses with builtin tools > span 1`] = ` { "key": "gen_ai.response.id", "value": { - "stringValue": "resp_032eeb0f1946b3fc00690349d295108195a02e83631653e35f", + "stringValue": "resp_0dc4a3c582e21a7f00690a21f2f5a881a38c2bc17284ab0bdb", }, }, { @@ -1971,7 +2034,184 @@ exports[`openai > openai responses with builtin tools > span 1`] = ` { "key": "id", "value": { - "stringValue": "ci_032eeb0f1946b3fc00690349dd01688195a33f5e06b171c4fe", + "stringValue": "ci_0dc4a3c582e21a7f00690a21f79eb081a385bc9ea91607bb8e", + }, + }, + { + "key": "type", + "value": { + "stringValue": "code_interpreter_call", + }, + }, + { + "key": "status", + "value": { + "stringValue": "completed", + }, + }, + { + "key": "code", + "value": { + "stringValue": "import decimal, math, fractions, sys, statistics, random", + }, + }, + { + "key": "container_id", + "value": { + "stringValue": "cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18", + }, + }, + { + "key": "outputs", + "value": {}, + }, + ], + }, + }, + }, + ], + }, + }, + ], + }, + }, + }, + ], + }, + }, + { + "kvlistValue": { + "values": [ + { + "key": "role", + "value": { + "stringValue": "assistant", + }, + }, + { + "key": "parts", + "value": { + "arrayValue": { + "values": [ + { + "kvlistValue": { + "values": [ + { + "key": "type", + "value": { + "stringValue": "unknown", + }, + }, + { + "key": "part", + "value": { + "kvlistValue": { + "values": [ + { + "key": "id", + "value": { + "stringValue": "ci_0dc4a3c582e21a7f00690a21fb50d081a3839fffe407f88965", + }, + }, + { + "key": "type", + "value": { + "stringValue": "code_interpreter_call", + }, + }, + { + "key": "status", + "value": { + "stringValue": "completed", + }, + }, + { + "key": "code", + "value": { + "stringValue": "n = 123_902_139_123 +# Check if perfect square +is_square = int(n**0.5)**2 == n +sqrt_n = n**0.5 +int_part = int(sqrt_n) +is_square, int_part", + }, + }, + { + "key": "container_id", + "value": { + "stringValue": "cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18", + }, + }, + { + "key": "outputs", + "value": {}, + }, + ], + }, + }, + }, + ], + }, + }, + ], + }, + }, + }, + ], + }, + }, + { + "kvlistValue": { + "values": [ + { + "key": "role", + "value": { + "stringValue": "assistant", + }, + }, + { + "key": "parts", + "value": { + "arrayValue": { + "values": [], + }, + }, + }, + ], + }, + }, + { + "kvlistValue": { + "values": [ + { + "key": "role", + "value": { + "stringValue": "assistant", + }, + }, + { + "key": "parts", + "value": { + "arrayValue": { + "values": [ + { + "kvlistValue": { + "values": [ + { + "key": "type", + "value": { + "stringValue": "unknown", + }, + }, + { + "key": "part", + "value": { + "kvlistValue": { + "values": [ + { + "key": "id", + "value": { + "stringValue": "ci_0dc4a3c582e21a7f00690a21fe485c81a3bc4ba9ccc7418d6e", }, }, { @@ -1989,15 +2229,13 @@ exports[`openai > openai responses with builtin tools > span 1`] = ` { "key": "code", "value": { - "stringValue": "import decimal, math -n=123902139123 -math.isqrt(n), math.isqrt(n)**2 == n, math.sqrt(n)", + "stringValue": "int_part, int_part**2, (int_part+1)**2", }, }, { "key": "container_id", "value": { - "stringValue": "cntr_690349d4fe9c8190a698334dee208a580180e82f7f5ef49c", + "stringValue": "cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18", }, }, { @@ -2069,7 +2307,7 @@ math.isqrt(n), math.isqrt(n)**2 == n, math.sqrt(n)", { "key": "id", "value": { - "stringValue": "ci_032eeb0f1946b3fc00690349e486f081958929018fa377592f", + "stringValue": "ci_0dc4a3c582e21a7f00690a2200de5881a3b0fb27e1e2317788", }, }, { @@ -2089,15 +2327,212 @@ math.isqrt(n), math.isqrt(n)**2 == n, math.sqrt(n)", "value": { "stringValue": "from decimal import Decimal, getcontext getcontext().prec = 50 -n=Decimal(123902139123) -s = n.sqrt() -s", +sqrt_val = Decimal(n).sqrt() +sqrt_val", + }, + }, + { + "key": "container_id", + "value": { + "stringValue": "cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18", + }, + }, + { + "key": "outputs", + "value": {}, + }, + ], + }, + }, + }, + ], + }, + }, + ], + }, + }, + }, + ], + }, + }, + { + "kvlistValue": { + "values": [ + { + "key": "role", + "value": { + "stringValue": "assistant", + }, + }, + { + "key": "parts", + "value": { + "arrayValue": { + "values": [], + }, + }, + }, + ], + }, + }, + { + "kvlistValue": { + "values": [ + { + "key": "role", + "value": { + "stringValue": "assistant", + }, + }, + { + "key": "parts", + "value": { + "arrayValue": { + "values": [ + { + "kvlistValue": { + "values": [ + { + "key": "type", + "value": { + "stringValue": "unknown", + }, + }, + { + "key": "part", + "value": { + "kvlistValue": { + "values": [ + { + "key": "id", + "value": { + "stringValue": "ci_0dc4a3c582e21a7f00690a2209f9cc81a39c0e1ddfa029c7a6", + }, + }, + { + "key": "type", + "value": { + "stringValue": "code_interpreter_call", + }, + }, + { + "key": "status", + "value": { + "stringValue": "completed", + }, + }, + { + "key": "code", + "value": { + "stringValue": "float_val = float(sqrt_val) +float_val", + }, + }, + { + "key": "container_id", + "value": { + "stringValue": "cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18", + }, + }, + { + "key": "outputs", + "value": {}, + }, + ], + }, + }, + }, + ], + }, + }, + ], + }, + }, + }, + ], + }, + }, + { + "kvlistValue": { + "values": [ + { + "key": "role", + "value": { + "stringValue": "assistant", + }, + }, + { + "key": "parts", + "value": { + "arrayValue": { + "values": [], + }, + }, + }, + ], + }, + }, + { + "kvlistValue": { + "values": [ + { + "key": "role", + "value": { + "stringValue": "assistant", + }, + }, + { + "key": "parts", + "value": { + "arrayValue": { + "values": [ + { + "kvlistValue": { + "values": [ + { + "key": "type", + "value": { + "stringValue": "unknown", + }, + }, + { + "key": "part", + "value": { + "kvlistValue": { + "values": [ + { + "key": "id", + "value": { + "stringValue": "ci_0dc4a3c582e21a7f00690a220c125481a3ad139dde1b01ef42", + }, + }, + { + "key": "type", + "value": { + "stringValue": "code_interpreter_call", + }, + }, + { + "key": "status", + "value": { + "stringValue": "completed", + }, + }, + { + "key": "code", + "value": { + "stringValue": "from decimal import ROUND_HALF_UP +def round_str(dec, places): + q = Decimal(10) ** -places + return str(dec.quantize(q, rounding=ROUND_HALF_UP)) +for p in [6, 9, 12, 15]: + print(p, round_str(sqrt_val, p))", }, }, { "key": "container_id", "value": { - "stringValue": "cntr_690349d4fe9c8190a698334dee208a580180e82f7f5ef49c", + "stringValue": "cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18", }, }, { @@ -2164,7 +2599,7 @@ s", { "key": "content", "value": { - "stringValue": "√123902139123 ≈ 351997.35669888204", + "stringValue": "The square root of 123,902,139,123 is approximately 351,997.356698882044. It’s not a perfect square.", }, }, ], @@ -2212,7 +2647,7 @@ s", { "key": "http.response.body.text", "value": { - "stringValue": "{"id":"resp_032eeb0f1946b3fc00690349d295108195a02e83631653e35f","object":"response","created_at":1761823186,"status":"completed","background":false,"billing":{"payer":"developer"},"error":null,"incomplete_details":null,"instructions":"be precise","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5-2025-08-07","output":[{"id":"rs_032eeb0f1946b3fc00690349d5b7b88195bce44ca9ceab4941","type":"reasoning","summary":[]},{"id":"ci_032eeb0f1946b3fc00690349dd01688195a33f5e06b171c4fe","type":"code_interpreter_call","status":"completed","code":"import decimal, math\\r\\nn=123902139123\\r\\nmath.isqrt(n), math.isqrt(n)**2 == n, math.sqrt(n)","container_id":"cntr_690349d4fe9c8190a698334dee208a580180e82f7f5ef49c","outputs":null},{"id":"rs_032eeb0f1946b3fc00690349dffc9481958d20478e41cd0e6d","type":"reasoning","summary":[]},{"id":"ci_032eeb0f1946b3fc00690349e486f081958929018fa377592f","type":"code_interpreter_call","status":"completed","code":"from decimal import Decimal, getcontext\\r\\ngetcontext().prec = 50\\r\\nn=Decimal(123902139123)\\r\\ns = n.sqrt()\\r\\ns","container_id":"cntr_690349d4fe9c8190a698334dee208a580180e82f7f5ef49c","outputs":null},{"id":"rs_032eeb0f1946b3fc00690349e8e29c81958735b9417d916d13","type":"reasoning","summary":[]},{"id":"msg_032eeb0f1946b3fc00690349edec7c819599bc5f0620f08cb7","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"√123902139123 ≈ 351997.35669888204"}],"role":"assistant"}],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"reasoning":{"effort":"medium","summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"code_interpreter","container":{"type":"auto"}}],"top_logprobs":0,"top_p":1,"truncation":"disabled","usage":{"input_tokens":1139,"input_tokens_details":{"cached_tokens":0},"output_tokens":469,"output_tokens_details":{"reasoning_tokens":448},"total_tokens":1608,"pydantic_ai_gateway":{"cost_estimate":0.006113749999999999}},"user":null,"metadata":{}}", + "stringValue": "{"id":"resp_0dc4a3c582e21a7f00690a21f2f5a881a38c2bc17284ab0bdb","object":"response","created_at":1762271730,"status":"completed","background":false,"billing":{"payer":"developer"},"error":null,"incomplete_details":null,"instructions":"be precise","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5-2025-08-07","output":[{"id":"rs_0dc4a3c582e21a7f00690a21f5c01c81a387696a1225044c74","type":"reasoning","summary":[]},{"id":"ci_0dc4a3c582e21a7f00690a21f79eb081a385bc9ea91607bb8e","type":"code_interpreter_call","status":"completed","code":"import decimal, math, fractions, sys, statistics, random","container_id":"cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18","outputs":null},{"id":"ci_0dc4a3c582e21a7f00690a21fb50d081a3839fffe407f88965","type":"code_interpreter_call","status":"completed","code":"n = 123_902_139_123\\n# Check if perfect square\\nis_square = int(n**0.5)**2 == n\\nsqrt_n = n**0.5\\nint_part = int(sqrt_n)\\nis_square, int_part","container_id":"cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18","outputs":null},{"id":"rs_0dc4a3c582e21a7f00690a21fdb20081a38c8d71579c417602","type":"reasoning","summary":[]},{"id":"ci_0dc4a3c582e21a7f00690a21fe485c81a3bc4ba9ccc7418d6e","type":"code_interpreter_call","status":"completed","code":"int_part, int_part**2, (int_part+1)**2","container_id":"cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18","outputs":null},{"id":"rs_0dc4a3c582e21a7f00690a220009f081a3ae01ee9362963591","type":"reasoning","summary":[]},{"id":"ci_0dc4a3c582e21a7f00690a2200de5881a3b0fb27e1e2317788","type":"code_interpreter_call","status":"completed","code":"from decimal import Decimal, getcontext\\ngetcontext().prec = 50\\nsqrt_val = Decimal(n).sqrt()\\nsqrt_val","container_id":"cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18","outputs":null},{"id":"rs_0dc4a3c582e21a7f00690a22031aa881a38cd6660b4d830c50","type":"reasoning","summary":[]},{"id":"ci_0dc4a3c582e21a7f00690a2209f9cc81a39c0e1ddfa029c7a6","type":"code_interpreter_call","status":"completed","code":"float_val = float(sqrt_val)\\nfloat_val","container_id":"cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18","outputs":null},{"id":"rs_0dc4a3c582e21a7f00690a220baf1481a3bda6abb8b9c09f88","type":"reasoning","summary":[]},{"id":"ci_0dc4a3c582e21a7f00690a220c125481a3ad139dde1b01ef42","type":"code_interpreter_call","status":"completed","code":"from decimal import ROUND_HALF_UP\\ndef round_str(dec, places):\\n q = Decimal(10) ** -places\\n return str(dec.quantize(q, rounding=ROUND_HALF_UP))\\nfor p in [6, 9, 12, 15]:\\n print(p, round_str(sqrt_val, p))","container_id":"cntr_690a21f4fa088191a854e0303fd8e69406563a23f7fe0f18","outputs":null},{"id":"rs_0dc4a3c582e21a7f00690a220ee65081a3b4a3c606ed32f88c","type":"reasoning","summary":[]},{"id":"msg_0dc4a3c582e21a7f00690a221614e081a3b9e5621bd0f3ba23","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The square root of 123,902,139,123 is approximately 351,997.356698882044. It’s not a perfect square."}],"role":"assistant"}],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":"medium","summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"code_interpreter","container":{"type":"auto"}}],"top_logprobs":0,"top_p":1,"truncation":"disabled","usage":{"input_tokens":1808,"input_tokens_details":{"cached_tokens":0},"output_tokens":1061,"output_tokens_details":{"reasoning_tokens":1024},"total_tokens":2869,"pydantic_ai_gateway":{"cost_estimate":0.01287}},"user":null,"metadata":{}}", }, }, { @@ -2224,7 +2659,7 @@ s", { "key": "gen_ai.usage.input_tokens", "value": { - "intValue": 1139, + "intValue": 1808, }, }, { @@ -2240,7 +2675,7 @@ s", { "key": "gen_ai.usage.output_tokens", "value": { - "intValue": 469, + "intValue": 1061, }, }, { diff --git a/gateway/test/setup.ts b/gateway/test/setup.ts index 070221d..334ee5e 100644 --- a/gateway/test/setup.ts +++ b/gateway/test/setup.ts @@ -52,6 +52,9 @@ function testGateway(): TestGateway { const bodyArray = init?.body as Uint8Array otelBatch.push(new TextDecoder().decode(bodyArray)) return new Response('OK', { status: 200 }) + } else if (hostname === 'oauth2.googleapis.com') { + // Mock GCP token response for tests + return new Response(JSON.stringify({ access_token: 'mock-gcp-token' }), { status: 200 }) } else { return await fetch(url, init) } diff --git a/gateway/test/worker.ts b/gateway/test/worker.ts index 51a2485..b14a4c7 100644 --- a/gateway/test/worker.ts +++ b/gateway/test/worker.ts @@ -99,6 +99,13 @@ class TestKeysDB extends KeysDbD1 { credentials: env.AWS_BEARER_TOKEN_BEDROCK, apiTypes: ['anthropic', 'converse'], }, + { + baseUrl: 'http://localhost:8005/gemini', + providerId: 'google-vertex', + injectCost: true, + credentials: env.GOOGLE_SERVICE_ACCOUNT_KEY, + apiTypes: ['gemini', 'anthropic'], + }, ] } diff --git a/gateway/vitest.config.mts b/gateway/vitest.config.mts index 195806f..eb402f2 100644 --- a/gateway/vitest.config.mts +++ b/gateway/vitest.config.mts @@ -1,6 +1,34 @@ /** biome-ignore-all lint/style/useNamingConvention: env vars */ import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config' +// This is a fake private key, it doesn't have access to any resources, but it's a valid private key. +const FAKE_PRIVATE_KEY = [ + '-----BEGIN PRIVATE KEY-----', + 'MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMFrZYX4gZ20qv88', + 'jD0QCswXgcxgP7Ta06G47QEFprDVcv4WMUBDJVAKofzVcYyhsasWsOSxcpA8LIi9', + '/VS2Otf8CmIK6nPBCD17Qgt8/IQYXOS4U2EBh0yjo0HQ4vFpkqium4lLWxrAZohA', + '8r82clV08iLRUW3J+xvN23iPHyVDAgMBAAECgYBScRJe3iNxMvbHv+kOhe30O/jJ', + 'QiUlUzhtcEMk8mGwceqHvrHTcEtRKJcPC3NQvALcp9lSQQhRzjQ1PLXkC6BcfKFd', + '03q5tVPmJiqsHbSyUyHWzdlHP42xWpl/RmX/DfRKGhPOvufZpSTzkmKWtN+7osHu', + '7eiMpg2EDswCvOgf0QJBAPXLYwHbZLaM2KEMDgJSse5ZTE/0VMf+5vSTGUmHkr9c', + 'Wx2G1i258kc/JgsXInPbq4BnK9hd0Xj2T5cmEmQtm4UCQQDJc02DFnPnjPnnDUwg', + 'BPhrCyW+rnBGUVjehveu4XgbGx7l3wsbORTaKdCX3HIKUupgfFwFcDlMUzUy6fPO', + 'IuQnAkA8FhVE/fIX4kSO0hiWnsqafr/2B7+2CG1DOraC0B6ioxwvEqhHE17T5e8R', + '5PzqH7hEMnR4dy7fCC+avpbeYHvVAkA5W58iR+5Qa49r/hlCtKeWsuHYXQqSuu62', + 'zW8QWBo+fYZapRsgcSxCwc0msBm4XstlFYON+NoXpUlsabiFZOHZAkEA8Ffq3xoU', + 'y0eYGy3MEzxx96F+tkl59lfkwHKWchWZJ95vAKWJaHx9WFxSWiJofbRna8Iim6pY', + 'BootYWyTCfjjwA==', + '-----END PRIVATE KEY-----', +].join('\\n') + +const FAKE_SERVICE_ACCOUNT_KEY = ` +{ + "client_email": "test@example.com", + "private_key": "${FAKE_PRIVATE_KEY}", + "project_id": "pydantic-ai" +} +` + export default defineWorkersConfig({ test: { // from https://github.com/cloudflare/workers-sdk/issues/6581#issuecomment-2653472683 @@ -25,6 +53,7 @@ export default defineWorkersConfig({ GROQ_API_KEY: process.env.GROQ_API_KEY ?? 'GROQ_API_KEY-unset', ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY ?? 'ANTHROPIC_API_KEY-unset', AWS_BEARER_TOKEN_BEDROCK: process.env.AWS_BEARER_TOKEN_BEDROCK ?? 'AWS_BEARER_TOKEN_BEDROCK-unset', + GOOGLE_SERVICE_ACCOUNT_KEY: process.env.GOOGLE_SERVICE_ACCOUNT_KEY ?? FAKE_SERVICE_ACCOUNT_KEY, }, }, }, diff --git a/package-lock.json b/package-lock.json index 9cd4833..b8f8cfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "@opentelemetry/resources": "^2.0.1", "@pydantic/genai-prices": "^0.0.35", "@pydantic/logfire-api": "^0.9.0", + "@streamparser/json-whatwg": "^0.0.22", "eventsource-parser": "^3.0.6", "mime-types": "^3.0.1", "ts-pattern": "^5.8.0" @@ -4370,6 +4371,21 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/@streamparser/json": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.22.tgz", + "integrity": "sha512-b6gTSBjJ8G8SuO3Gbbj+zXbVx8NSs1EbpbMKpzGLWMdkR+98McH9bEjSz3+0mPJf68c5nxa3CrJHp5EQNXM6zQ==", + "license": "MIT" + }, + "node_modules/@streamparser/json-whatwg": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@streamparser/json-whatwg/-/json-whatwg-0.0.22.tgz", + "integrity": "sha512-qRYYbaytZYXfGMFNtPAUrbK2CJ0KmGk2l+uLxW1hKVLOt3aBaWGsskiUCdGoaoSTeJBHswNqi59wSppg+IVWKQ==", + "license": "MIT", + "dependencies": { + "@streamparser/json": "^0.0.22" + } + }, "node_modules/@types/chai": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", diff --git a/proxy-vcr/proxy_vcr/cassettes/google-vertex-3fc5cd750ad872a644781b8d4a149f2d8f685bbd2122866dcb4714cf6c38082c.yaml b/proxy-vcr/proxy_vcr/cassettes/google-vertex-3fc5cd750ad872a644781b8d4a149f2d8f685bbd2122866dcb4714cf6c38082c.yaml new file mode 100644 index 0000000..211c7c2 --- /dev/null +++ b/proxy-vcr/proxy_vcr/cassettes/google-vertex-3fc5cd750ad872a644781b8d4a149f2d8f685bbd2122866dcb4714cf6c38082c.yaml @@ -0,0 +1,72 @@ +interactions: +- request: + body: '{"contents":[{"parts":[{"text":"Samuel lived in London and was born on + Jan 28th ''87"}],"role":"user"}],"systemInstruction":{"parts":[{"text":"Extract + information about the person"}],"role":"user"},"tools":[{"functionDeclarations":[{"description":"The + final response which ends this conversation","name":"final_result","parameters":{"properties":{"name":{"description":"The + name of the person.","type":"STRING"},"dob":{"description":"The date of birth + of the person. MUST BE A VALID ISO 8601 date. (format: date)","type":"STRING"},"city":{"description":"The + city where the person lives.","type":"STRING"}},"required":["name","dob","city"],"type":"OBJECT"}}]}],"toolConfig":{"functionCallingConfig":{"mode":"ANY","allowedFunctionNames":["final_result"]}},"generationConfig":{"temperature":0.5,"topP":0.9,"stopSequences":["potato"]}}' + headers: + accept: + - '*/*' + accept-encoding: + - deflate + connection: + - keep-alive + content-length: + - '830' + content-type: + - application/json + host: + - aiplatform.googleapis.com + traceparent: + - 00-019a4effa21047ac31372f093cb8e712-8b60768281864a49-01 + user-agent: + - pydantic-ai/1.0.19.dev5+b3b34f9, google-genai-sdk/1.36.0 gl-python/3.13.0 + via Pydantic AI Gateway unknown, contact engineering@pydantic.dev via Pydantic + AI Gateway test, contact engineering@pydantic.dev + x-goog-api-client: + - google-genai-sdk/1.36.0 gl-python/3.13.0 + x-goog-api-key: + - unset + method: POST + uri: https://aiplatform.googleapis.com/v1beta1/projects/pydantic-ai/locations/global/publishers/google/models/gemini-2.5-flash:generateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": + [{\"functionCall\": {\"name\": \"final_result\",\"args\": {\"name\": \"Samuel\",\"city\": + \"London\",\"dob\": \"1987-01-28\"}},\"thoughtSignature\": \"CswDAePx/15lDrSIcIjN85FpyyOl3oASu2R23QD4Z3cj5XRUKOD/3/mMqcHv5AeXc/L+P1eVNpq5C3xM9/8zil6gOOZI91F/r1kmKvxmCECD1SW+p1dtzG6eljHd51vd2Gx7eqtEek1ORzeLP4zSWY2GDlZA9fZIs/uIfhLyOlkiiB1P/GAAUmBPT/TZqdOpQZXBt8MAUrbTOQfbhQ1qbxdrYRveZRMzXS898K6NmjrN5quNiaUgwEbc2J6NAoDOl5jdK8tIt7m25qdpjSYMpAGYD0c0Le2yPf8eO6A9J6zYp1lqVCTifby4/nP5RkVM2e1L4pH6oYisgQsyDUSEDCSG2GXhGO9WU5wFYwkjlhB8ghLU92kVgr/Rq54K/0GaYGBgzKi+YrD8c6QKSTZWTw/46D8lQ1goL1Y1YdtRiRGNFRJqKmSbkxjIOQoIk4glxcsm7L2lucL4miz4yZioBe7HEPDtmQY0FEKM5DQhwuj+AF1bBl5WE/9NYUsuGY3DxSi0lVd2v8+zd0op9U8z3PX4cmdKVkf/kA9TuQSPXBZpYgj7CssoMBT4ssLVoUgetOrNvJL99liSvsPOBZaLDKpNTXQA9GdZvQltoSolOg==\"}]},\"finishReason\": + \"STOP\",\"avgLogprobs\": -0.48033348719278973}],\"usageMetadata\": {\"promptTokenCount\": + 79,\"candidatesTokenCount\": 18,\"totalTokenCount\": 240,\"trafficType\": + \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": + 79}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": + 18}],\"thoughtsTokenCount\": 143},\"modelVersion\": \"gemini-2.5-flash\",\"createTime\": + \"2025-11-04T15:31:56.289691Z\",\"responseId\": \"bBwKaZvXEemDn9kP56mN8QI\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Length: + - '1272' + Content-Type: + - text/event-stream + Date: + - Tue, 04 Nov 2025 15:31:59 GMT + Server: + - scaffolding on HTTPServer2 + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/proxy-vcr/proxy_vcr/cassettes/google-vertex-stream.yaml b/proxy-vcr/proxy_vcr/cassettes/google-vertex-stream.yaml new file mode 100644 index 0000000..7ae8a49 --- /dev/null +++ b/proxy-vcr/proxy_vcr/cassettes/google-vertex-stream.yaml @@ -0,0 +1,62 @@ +interactions: +- request: + body: '{"contents":[{"parts":[{"text":"Samuel lived in London and was born on + Jan 28th ''87"}],"role":"user"}],"systemInstruction":{"parts":[{"text":"Extract + information about the person"}],"role":"user"},"tools":[{"functionDeclarations":[{"description":"The + final response which ends this conversation","name":"final_result","parameters":{"properties":{"name":{"description":"The + name of the person.","type":"STRING"},"dob":{"description":"The date of birth + of the person. MUST BE A VALID ISO 8601 date. (format: date)","type":"STRING"},"city":{"description":"The + city where the person lives.","type":"STRING"}},"required":["name","dob","city"],"type":"OBJECT"}}]}],"toolConfig":{"functionCallingConfig":{"mode":"ANY","allowedFunctionNames":["final_result"]}},"generationConfig":{"temperature":0.5,"topP":0.9,"stopSequences":["potato"]}}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '830' + host: + - aiplatform.googleapis.com + user-agent: + - python-httpx/0.28.1 + method: POST + uri: https://aiplatform.googleapis.com/v1beta1/projects/pydantic-ai/locations/global/publishers/google/models/gemini-2.5-flash:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": + [{\"functionCall\": {\"name\": \"final_result\",\"args\": {\"name\": \"Samuel\",\"dob\": + \"1987-01-28\",\"city\": \"London\"}},\"thoughtSignature\": \"CiwB4/H/XrFHcqJ5pwIEMQnBhQBHazYmUb7XC1cZhL68mxASIIbmWyFjuzXxawqAAQHj8f9epXxVjCDy0/oGNXN1LFyOtdAaqPjsycM5BxCYAPEwlGkiTMVUtJaGnZOZVjMCA1E7jjwjyaUnLi0JXUn/kHHpNZIKHHFz26Uo5OUNYt8CanXEsbfLY6i+5wIMvLYURSxBeWq814uEwV+Sif6z37WfaNvU5KrR9ek41rCRCmkB4/H/XlhR1HkHgmKGkNZnCbzL4aLYVnnA39p5YFaZvsOvv8OTwed/arFcVREVegT9jIusC+BHbxQL6jxAi/r+7Yxo4D6LFkh68j/dbGz/Y1kVy+dwqrQHJ4+c9UuPOZnMKzts+eIip6gKewHj8f9eDAdCB1AcgaXceQTctqq0PeycbHisSqLKqb0bktTud5S11F2l6iB2agdWl6J/4Lrx7Xg0mDpvJmIZbmDGGr9zAy+PyXTDpca8XwEPDOCbOaosDGofqvAcfIlCSph5m56vwlIfn8QD6HfLsUCuG8XQZF13bc3rkAp1AePx/17HSA1oMLywdmQx6jXY5g+3iBVcskBePcXaL4Jd4FeSUSqD63zVuVSuoh3QJlU0zOYyrfZL+f3Lt5VgUgLG0si8OvoDXAzPn9pu4XRRoLBKJqgB26LPWU0XI/vv11L8eXREncg46Z5AUbs2vGJ1sQMnCqUBAePx/14DiPsegv86CHeOTaMg8p7dwzDDSOfDmLXycjtGqZKMytXUXiWcBs3mtNi0rx4D4yQihmPQClBfTkpPx4pT9+wGi86Nf97CCKADwIfDPJBQF4LTUvA24v1ay9REpdaI22jadcHlE2sTvD28im63HsQ5vmoV0YYLiaj82jd52vyYLKDzb7+LgkWl2V4UhJBHyTFk+wMgkPiRuftpgAkjDlmuCkgB4/H/XtKR+rJrbrTPrKAdaDts8M1WzGEpscucnYFpeqIOkQ7GeadukWQec5GBsJVTQ6rsmaRzp6n42BAlrLmoIUIacMt2fE8=\"}]},\"finishReason\": + \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 79,\"candidatesTokenCount\": + 18,\"totalTokenCount\": 267,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 79}],\"candidatesTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 18}],\"thoughtsTokenCount\": 170},\"modelVersion\": + \"gemini-2.5-flash\",\"createTime\": \"2025-11-04T16:00:09.183509Z\",\"responseId\": + \"CSMKadWZC7G36MEP1Yfl-Ag\"}\r\n\r\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Disposition: + - attachment + Content-Type: + - text/event-stream + Date: + - Tue, 04 Nov 2025 16:00:11 GMT + Server: + - scaffolding on HTTPServer2 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/proxy-vcr/proxy_vcr/cassettes/openai-3bc303f40e7bb2fd35f89381e2a0b1845c054b1f6879bc2daddf8f97bd89b60d.yaml b/proxy-vcr/proxy_vcr/cassettes/openai-3bc303f40e7bb2fd35f89381e2a0b1845c054b1f6879bc2daddf8f97bd89b60d.yaml index 0c13a33..a4fcc65 100644 --- a/proxy-vcr/proxy_vcr/cassettes/openai-3bc303f40e7bb2fd35f89381e2a0b1845c054b1f6879bc2daddf8f97bd89b60d.yaml +++ b/proxy-vcr/proxy_vcr/cassettes/openai-3bc303f40e7bb2fd35f89381e2a0b1845c054b1f6879bc2daddf8f97bd89b60d.yaml @@ -24,16 +24,16 @@ interactions: response: body: string: !!binary | - H4sIAAAAAAAAA4yST0sDMRDF7/spQs67pdvaP/RWLL0o6EVEpSwxmd2NZpOQzEpL6XeXpLW7xQpe - cpjfzOO9yewTQqgUdEEorxnyxqrs9vluZ+7Xzm3LlXh6fZjWS7ZaztyLHq8NTcOEef8Ajj9TA24a - qwCl0UfMHTCEoJrPpvl8NM5n8wgaI0CFscpiNslGw9EkG86z4ew0VxvJwdMFeUsIIWQf3+BQC9jS - BRmmP5UGvGcV0MW5iRDqjAoVyryXHplGmnaQG42go+lH5qQf9KGDsvUsWNOtUj3AtDbIQrRoa3Mi - h7ORUmrp68IB80YHcY/G0kgPCSGbGKy98EqtM43FAs0nRNnR+ChHu0V2MM9PEA0y1dXHN+kVtUIA - Mql8bzGUM16D6Ca7LbJWSNMDSS/bbzPXtI+5pa7+I98BzsEiiMI6EJJfBu7aHIQz+6vtvONomHpw - X5JDgRJc+AcBJWvV8QSo33mEpiilrsBZJ+MdhK9ODsk3AAAA//8DAD2WcKgEAwAA + H4sIAAAAAAAAA4ySTUsDMRCG7/srQs7d0m6/ZI+20IOCrRRFpCwxmW2j2SQks2KR/ndJ+rFbrOAl + h3lmXt53Mt8JIVQKmhPKtwx5ZVU6fZnB+Hb6/HA3n89Xanlvyt0ymy0Gy9XjE+2ECfP2DhxPU11u + KqsApdEHzB0whKDan4yzbNIfD7MIKiNAhbGNxXSUZr1slPZu0t7kOLc1koOnOXlNCCHkO77BoRbw + RXPS65wqFXjPNkDzcxMh1BkVKpR5Lz0yjbTTQG40go6mF8xJ321DB2XtWbCma6VagGltkIVo0db6 + SPZnI6XU0m8LB8wbHcQ9Gksj3SeErGOw+sIrtc5UFgs0HxBls8FBjjaLbGC/f4RokKmmPhh2rqgV + ApBJ5VuLoZzxLYhmstkiq4U0LZC0sv02c037kFvqzX/kG8A5WARRWAdC8svATZuDcGZ/tZ13HA1T + D+5TcihQggv/IKBktTqcAPU7j1AVpdQbcNbJeAfhq5N98gMAAP//AwDsjmSCBAMAAA== headers: CF-RAY: - - 996a84cfabfa8e65-AMS + - 9995499d7c4bf64d-AMS Connection: - keep-alive Content-Encoding: @@ -41,15 +41,13 @@ interactions: Content-Type: - application/json Date: - - Thu, 30 Oct 2025 11:19:41 GMT + - Tue, 04 Nov 2025 15:54:03 GMT Server: - cloudflare Set-Cookie: - - __cf_bm=MVOz_vf1d.29xNg5eAq9hZGUpEToHZ9rpFhKtO12GDM-1761823181-1.0.1.1-f_gaGN5YtX1DvFTkOFoMkGEiQh3rNh89AjWKvmjk0_.XbDAuRV_tnz21YzeMVcacrAa5yTXsqWXN_9hwojHD6iRS.NTgTTLPQd8AGlqn.vM; - path=/; expires=Thu, 30-Oct-25 11:49:41 GMT; domain=.api.openai.com; HttpOnly; + - __cf_bm=EemaSMEgvKKqVG.O5AeulwhHEh5WXvpwh3IykhcSnx4-1762271643-1.0.1.1-Csg7QIhFaWrjU_CeOR3a_fcnJi66AifjPCiBkcKHemruFIfepEJhDXFZVCw7no_eUguFrpt2kzpFTH3iC3uzwLIxO6UWv1aknuTXrVNLqtQ; + path=/; expires=Tue, 04-Nov-25 16:24:03 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None - - _cfuvid=sZHytwxgk7vJY7ep7PlpK5yip7G55QSHyqVz43MKEw4-1761823181130-0.0.1.1-604800000; - path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None Strict-Transport-Security: - max-age=31536000; includeSubDomains; preload Transfer-Encoding: @@ -65,29 +63,29 @@ interactions: openai-organization: - pydantic-28gund openai-processing-ms: - - '2346' + - '1846' openai-project: - proj_dKobscVY9YJxeEaDJen54e3d openai-version: - '2020-10-01' x-envoy-upstream-service-time: - - '2516' + - '2067' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: - - '10000' + - '15000' x-ratelimit-limit-tokens: - - '4000000' + - '40000000' x-ratelimit-remaining-requests: - - '9999' + - '14999' x-ratelimit-remaining-tokens: - - '3999982' + - '39999982' x-ratelimit-reset-requests: - - 6ms + - 4ms x-ratelimit-reset-tokens: - 0s x-request-id: - - req_bd39a0c98bf743328b8b0a7dd528124b + - req_ed6395d36bd84e05ad0132bc5c740ae7 status: code: 200 message: OK diff --git a/proxy-vcr/proxy_vcr/cassettes/openai-77d5759136cb2982b73adf57a010bc66d6ed83681bdf01439a369e81765faeef.yaml b/proxy-vcr/proxy_vcr/cassettes/openai-77d5759136cb2982b73adf57a010bc66d6ed83681bdf01439a369e81765faeef.yaml index 1721121..5b9fc9e 100644 --- a/proxy-vcr/proxy_vcr/cassettes/openai-77d5759136cb2982b73adf57a010bc66d6ed83681bdf01439a369e81765faeef.yaml +++ b/proxy-vcr/proxy_vcr/cassettes/openai-77d5759136cb2982b73adf57a010bc66d6ed83681bdf01439a369e81765faeef.yaml @@ -1,93 +1,91 @@ interactions: -- request: - body: "{\n \"model\": \"gpt-5\",\n \"messages\": [\n {\n \"role\": \"developer\",\n - \ \"content\": \"You are a helpful assistant.\"\n },\n {\n \"role\": - \"user\",\n \"content\": \"What is the capital of France?\"\n }\n ]\n}" - headers: - accept: - - '*/*' - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '215' - content-type: - - application/json - cookie: - - _cfuvid=sZHytwxgk7vJY7ep7PlpK5yip7G55QSHyqVz43MKEw4-1761823181130-0.0.1.1-604800000 - host: - - api.openai.com - user-agent: - - python-httpx/0.28.1 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - body: - string: !!binary | - H4sIAAAAAAAAA3SST0/DMAzF7/0UUc4rGmWjZVfEcRIgBAKEqpB4XUaaRLGL+KN9d5RsLEXAJQf/ - 7Kf3HH8WjHGt+IJxuRYke2/K87vl8sK83dYP883t7AOrrpLXV5uHG7xvlnwSJ9zzBiR9Tx1J13sD - pJ3dYRlAEETV4/r0uKmaWT1LoHcKTBzrPJXzsppW83LalNN6P7d2WgLyBXssGGPsM73RoVXwxhds - Ovmu9IAoOuCLQxNjPDgTK1wgaiRhiU8ylM4S2GT6UgSNR2MYYDWgiNbsYMwICGsdiRgt2Xrak+3B - yEpbjes2gEBnoziS8zzRbcHYUwo2/PDKfXC9p5bcCyTZ6mQnx/MiM6zne0iOhMn1s2byh1qrgIQ2 - OFoMl0KuQeXJvEUxKO1GoBhl+23mL+1dbm27rHI6+1c/AynBE6jWB1Ba/kyc2wLEO/uv7bDk5Jgj - hFctoSUNIX6EgpUYzO4GOL4jQd+utO0g+KDTIcS/LrbFFwAAAP//AwBUFYYUBQMAAA== - headers: - CF-RAY: - - 996b061b7e5e66bb-AMS - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 30 Oct 2025 12:47:57 GMT - Server: - - cloudflare - Set-Cookie: - - __cf_bm=gj8UniL2VQhMUeZTBWJKjVhzKkZtnqVVk74KXycT.QU-1761828477-1.0.1.1-FA9NJITJ0eio4TImYwcMv9L8AP_HOpLiSzJxSxeyhPYu7KkUPuWtlSCk2E4EE_lib8jz63N9rfB7VPwYDK8uqwPbZhExH6DZM0KldDMsKWI; - path=/; expires=Thu, 30-Oct-25 13:17:57 GMT; domain=.api.openai.com; HttpOnly; - Secure; SameSite=None - Strict-Transport-Security: - - max-age=31536000; includeSubDomains; preload - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - alt-svc: - - h3=":443"; ma=86400 - cf-cache-status: - - DYNAMIC - openai-organization: - - pydantic-28gund - openai-processing-ms: - - '3290' - openai-project: - - proj_dKobscVY9YJxeEaDJen54e3d - openai-version: - - '2020-10-01' - x-envoy-upstream-service-time: - - '3308' - x-openai-proxy-wasm: - - v0.1 - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '4000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '3999982' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 0s - x-request-id: - - req_de1ccc4e45794892be9cdd3466893a58 - status: - code: 200 - message: OK + - request: + body: + "{\n \"model\": \"gpt-5\",\n \"messages\": [\n {\n \"role\": \"developer\",\n + \ \"content\": \"You are a helpful assistant.\"\n },\n {\n \"role\": + \"user\",\n \"content\": \"What is the capital of France?\"\n }\n ]\n}" + headers: + accept: + - "*/*" + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - "215" + content-type: + - application/json + host: + - api.openai.com + user-agent: + - python-httpx/0.28.1 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA3SSzU7DMBCE73kKy+cGhUAa6I2fKwghJAQIRcbetm4c27I3QFX13ZGdUqeiXHzY + b3c0s95NRgiVgs4I5UuGvLMqv3m5bdUXu6+/L03bPz/pq+u7G/e6bo18XNFJmDAfK+D4O3XCTWcV + oDR6wNwBQwiqp/W0LOuyqKoIOiNAhbGFxbzKy6Ks8uIiL+rd3NJIDp7OyFtGCCGb+AaHWsA3nZFi + 8lvpwHu2ADrbNxFCnVGhQpn30iPTSCcJcqMRdDT9wJz0J2PoYN57FqzpXqkRYFobZCFatPW+I9u9 + kbnU0i8bB8wbHcQ9Gksj3WaEvMdg/YFXap3pLDZoWoiy5dkgR9MiE6yrHUSDTKX65cXkiFojAJlU + frQYyhlfgkiTaYusF9KMQDbK9tfMMe0ht9SLpDI9/1c/Ac7BIojGOhCSHyZObQ7Cnf3Xtl9ydEw9 + uE/JoUEJLnyEgDnr1XAD1K89QtfMpV6As07GQwh/nW2zHwAAAP//AwDEqLh9BQMAAA== + headers: + CF-RAY: + - 999553b98d079fe1-AMS + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 04 Nov 2025 16:00:58 GMT + Server: + - cloudflare + Set-Cookie: + - _cfuvid=UBQpySOnSt6IR9DLxPFwUIoRDx9Cqm85._iC.MDdCow-1762272058246-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - pydantic-28gund + openai-processing-ms: + - "2732" + openai-project: + - proj_dKobscVY9YJxeEaDJen54e3d + openai-version: + - "2020-10-01" + x-envoy-upstream-service-time: + - "2774" + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - "15000" + x-ratelimit-limit-tokens: + - "40000000" + x-ratelimit-remaining-requests: + - "14999" + x-ratelimit-remaining-tokens: + - "39999982" + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_89c169bce248450b8abbdb8749ab0ac8 + status: + code: 200 + message: OK version: 1 diff --git a/proxy-vcr/proxy_vcr/cassettes/openai-bb9af470db8a6f5d27e4bec9910973ab2560c553630e55f5dd956224b9fc39cd.yaml b/proxy-vcr/proxy_vcr/cassettes/openai-bb9af470db8a6f5d27e4bec9910973ab2560c553630e55f5dd956224b9fc39cd.yaml index 9ef7b06..d7ca0e3 100644 --- a/proxy-vcr/proxy_vcr/cassettes/openai-bb9af470db8a6f5d27e4bec9910973ab2560c553630e55f5dd956224b9fc39cd.yaml +++ b/proxy-vcr/proxy_vcr/cassettes/openai-bb9af470db8a6f5d27e4bec9910973ab2560c553630e55f5dd956224b9fc39cd.yaml @@ -16,9 +16,6 @@ interactions: - '319' content-type: - application/json - cookie: - - __cf_bm=MVOz_vf1d.29xNg5eAq9hZGUpEToHZ9rpFhKtO12GDM-1761823181-1.0.1.1-f_gaGN5YtX1DvFTkOFoMkGEiQh3rNh89AjWKvmjk0_.XbDAuRV_tnz21YzeMVcacrAa5yTXsqWXN_9hwojHD6iRS.NTgTTLPQd8AGlqn.vM; - _cfuvid=sZHytwxgk7vJY7ep7PlpK5yip7G55QSHyqVz43MKEw4-1761823181130-0.0.1.1-604800000 host: - api.openai.com user-agent: @@ -27,19 +24,19 @@ interactions: uri: https://api.openai.com/v1/chat/completions response: body: - string: 'data: {"id":"chatcmpl-CWKzQzo2QLXPzzjG5ZhftNDkOKT2q","object":"chat.completion.chunk","created":1761823216,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"znk17e1VnU"} + string: 'data: {"id":"chatcmpl-CYDfFsepopZlcPzyFj5cdegEMUIO3","object":"chat.completion.chunk","created":1762271713,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"1oeFyzekfM"} - data: {"id":"chatcmpl-CWKzQzo2QLXPzzjG5ZhftNDkOKT2q","object":"chat.completion.chunk","created":1761823216,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"Paris"},"finish_reason":null}],"usage":null,"obfuscation":"9uyCALM"} + data: {"id":"chatcmpl-CYDfFsepopZlcPzyFj5cdegEMUIO3","object":"chat.completion.chunk","created":1762271713,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"Paris"},"finish_reason":null}],"usage":null,"obfuscation":"iNYovHl"} - data: {"id":"chatcmpl-CWKzQzo2QLXPzzjG5ZhftNDkOKT2q","object":"chat.completion.chunk","created":1761823216,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"."},"finish_reason":null}],"usage":null,"obfuscation":"DxMvC33A2iV"} + data: {"id":"chatcmpl-CYDfFsepopZlcPzyFj5cdegEMUIO3","object":"chat.completion.chunk","created":1762271713,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"."},"finish_reason":null}],"usage":null,"obfuscation":"z2nujSb0c5n"} - data: {"id":"chatcmpl-CWKzQzo2QLXPzzjG5ZhftNDkOKT2q","object":"chat.completion.chunk","created":1761823216,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":null,"obfuscation":"6BmmA2"} + data: {"id":"chatcmpl-CYDfFsepopZlcPzyFj5cdegEMUIO3","object":"chat.completion.chunk","created":1762271713,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":null,"obfuscation":"OvkX97"} - data: {"id":"chatcmpl-CWKzQzo2QLXPzzjG5ZhftNDkOKT2q","object":"chat.completion.chunk","created":1761823216,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":23,"completion_tokens":11,"total_tokens":34,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"8u6q1bA9GJu"} + data: {"id":"chatcmpl-CYDfFsepopZlcPzyFj5cdegEMUIO3","object":"chat.completion.chunk","created":1762271713,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":23,"completion_tokens":11,"total_tokens":34,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"d9x8d108MYZ"} data: [DONE] @@ -48,13 +45,13 @@ interactions: ' headers: CF-RAY: - - 996a85bbf9c58e65-AMS + - 99954b5e9a142383-AMS Connection: - keep-alive Content-Type: - text/event-stream; charset=utf-8 Date: - - Thu, 30 Oct 2025 11:20:18 GMT + - Tue, 04 Nov 2025 15:55:14 GMT Server: - cloudflare Strict-Transport-Security: @@ -72,29 +69,29 @@ interactions: openai-organization: - pydantic-28gund openai-processing-ms: - - '2257' + - '1373' openai-project: - proj_dKobscVY9YJxeEaDJen54e3d openai-version: - '2020-10-01' x-envoy-upstream-service-time: - - '2271' + - '1387' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: - - '10000' + - '15000' x-ratelimit-limit-tokens: - - '4000000' + - '40000000' x-ratelimit-remaining-requests: - - '9999' + - '14999' x-ratelimit-remaining-tokens: - - '3999982' + - '39999982' x-ratelimit-reset-requests: - - 6ms + - 4ms x-ratelimit-reset-tokens: - 0s x-request-id: - - req_51fcc2d0ded04c69b8c9381d841084c1 + - req_22e611c54e0a420ba711cbdbcb64e3ed status: code: 200 message: OK diff --git a/proxy-vcr/proxy_vcr/cassettes/openai-e49ad150dd6581653dd61fbb73921b255ff9f19310f99aba15e749007fc8daaa.yaml b/proxy-vcr/proxy_vcr/cassettes/openai-e49ad150dd6581653dd61fbb73921b255ff9f19310f99aba15e749007fc8daaa.yaml index 7a40e35..7dd9f73 100644 --- a/proxy-vcr/proxy_vcr/cassettes/openai-e49ad150dd6581653dd61fbb73921b255ff9f19310f99aba15e749007fc8daaa.yaml +++ b/proxy-vcr/proxy_vcr/cassettes/openai-e49ad150dd6581653dd61fbb73921b255ff9f19310f99aba15e749007fc8daaa.yaml @@ -13,9 +13,6 @@ interactions: - '96' content-type: - application/json - cookie: - - __cf_bm=MVOz_vf1d.29xNg5eAq9hZGUpEToHZ9rpFhKtO12GDM-1761823181-1.0.1.1-f_gaGN5YtX1DvFTkOFoMkGEiQh3rNh89AjWKvmjk0_.XbDAuRV_tnz21YzeMVcacrAa5yTXsqWXN_9hwojHD6iRS.NTgTTLPQd8AGlqn.vM; - _cfuvid=sZHytwxgk7vJY7ep7PlpK5yip7G55QSHyqVz43MKEw4-1761823181130-0.0.1.1-604800000 host: - api.openai.com user-agent: @@ -25,22 +22,23 @@ interactions: response: body: string: !!binary | - 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 + AP//AwCO/LDXvgYAAA== headers: CF-RAY: - - 996a84e54a6c8e65-AMS + - 999549b7382cf64d-AMS Connection: - keep-alive Content-Encoding: @@ -48,7 +46,7 @@ interactions: Content-Type: - application/json Date: - - Thu, 30 Oct 2025 11:19:46 GMT + - Tue, 04 Nov 2025 15:54:11 GMT Server: - cloudflare Strict-Transport-Security: @@ -64,27 +62,27 @@ interactions: openai-organization: - pydantic-28gund openai-processing-ms: - - '3897' + - '5664' openai-project: - proj_dKobscVY9YJxeEaDJen54e3d openai-version: - '2020-10-01' x-envoy-upstream-service-time: - - '3899' + - '5668' x-ratelimit-limit-requests: - - '10000' + - '15000' x-ratelimit-limit-tokens: - - '4000000' + - '40000000' x-ratelimit-remaining-requests: - - '9999' + - '14999' x-ratelimit-remaining-tokens: - - '4000000' + - '40000000' x-ratelimit-reset-requests: - - 6ms + - 4ms x-ratelimit-reset-tokens: - 0s x-request-id: - - req_6951bcaa4b8a4177b5f08f1469956c09 + - req_3088bb5e847e443e91fa8ab6d846e30b status: code: 200 message: OK diff --git a/proxy-vcr/proxy_vcr/cassettes/openai-fd2589389653eb8d00f2dec475e668144ac84c9ab6552fefa490c87ad778b5b0.yaml b/proxy-vcr/proxy_vcr/cassettes/openai-fd2589389653eb8d00f2dec475e668144ac84c9ab6552fefa490c87ad778b5b0.yaml index 19bda64..ffd26bb 100644 --- a/proxy-vcr/proxy_vcr/cassettes/openai-fd2589389653eb8d00f2dec475e668144ac84c9ab6552fefa490c87ad778b5b0.yaml +++ b/proxy-vcr/proxy_vcr/cassettes/openai-fd2589389653eb8d00f2dec475e668144ac84c9ab6552fefa490c87ad778b5b0.yaml @@ -15,9 +15,6 @@ interactions: - '224' content-type: - application/json - cookie: - - __cf_bm=MVOz_vf1d.29xNg5eAq9hZGUpEToHZ9rpFhKtO12GDM-1761823181-1.0.1.1-f_gaGN5YtX1DvFTkOFoMkGEiQh3rNh89AjWKvmjk0_.XbDAuRV_tnz21YzeMVcacrAa5yTXsqWXN_9hwojHD6iRS.NTgTTLPQd8AGlqn.vM; - _cfuvid=sZHytwxgk7vJY7ep7PlpK5yip7G55QSHyqVz43MKEw4-1761823181130-0.0.1.1-604800000 host: - api.openai.com user-agent: @@ -27,26 +24,34 @@ interactions: response: body: string: !!binary | - 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 + AAA= headers: CF-RAY: - - 996a85011b428e65-AMS + - 99954bcbdfcf1add-AMS Connection: - keep-alive Content-Encoding: @@ -54,7 +59,7 @@ interactions: Content-Type: - application/json Date: - - Thu, 30 Oct 2025 11:20:15 GMT + - Tue, 04 Nov 2025 15:56:08 GMT Server: - cloudflare Strict-Transport-Security: @@ -70,27 +75,27 @@ interactions: openai-organization: - pydantic-28gund openai-processing-ms: - - '29275' + - '37258' openai-project: - proj_dKobscVY9YJxeEaDJen54e3d openai-version: - '2020-10-01' x-envoy-upstream-service-time: - - '29280' + - '37261' x-ratelimit-limit-requests: - - '10000' + - '15000' x-ratelimit-limit-tokens: - - '4000000' + - '40000000' x-ratelimit-remaining-requests: - - '9999' + - '14999' x-ratelimit-remaining-tokens: - - '3999285' + - '39998024' x-ratelimit-reset-requests: - - 6ms + - 4ms x-ratelimit-reset-tokens: - - 10ms + - 2ms x-request-id: - - req_db1c9f23d7374e2589acb02a6dc26d34 + - req_f6c05f852ea546d68198f61792fe49f6 status: code: 200 message: OK diff --git a/proxy-vcr/proxy_vcr/main.py b/proxy-vcr/proxy_vcr/main.py index 4cf10a0..155c6eb 100644 --- a/proxy-vcr/proxy_vcr/main.py +++ b/proxy-vcr/proxy_vcr/main.py @@ -19,6 +19,7 @@ GROQ_BASE_URL = 'https://api.groq.com' ANTHROPIC_BASE_URL = 'https://api.anthropic.com' BEDROCK_BASE_URL = 'https://bedrock-runtime.us-east-1.amazonaws.com' +GOOGLE_BASE_URL = 'https://aiplatform.googleapis.com' current_file_dir = pathlib.Path(__file__).parent @@ -28,8 +29,8 @@ serializer='yaml', cassette_library_dir=(current_file_dir / 'cassettes').as_posix(), record_mode=RecordMode.ONCE, - match_on=['uri', 'method'], - filter_headers=['Authorization', 'x-api-key', 'x-amz-security-token'], + match_on=['uri', 'method', 'query'], + filter_headers=['Authorization', 'x-api-key', 'x-amz-security-token', 'cookie'], ) @@ -44,24 +45,24 @@ async def proxy(request: Request) -> Response: body = await request.body() # We should cache based on request body content, so we should make a hash of the request body. - body_hash = hashlib.sha256(body).hexdigest() + vcr_suffix = request.headers.get('x-vcr-filename', hashlib.sha256(body).hexdigest()) if request.url.path.startswith('/openai'): client = cast(httpx.AsyncClient, request.scope['state']['httpx_client']) url = OPENAI_BASE_URL + request.url.path.strip('/openai') - with vcr.use_cassette(cassette_name('openai', body_hash)): # type: ignore[reportUnknownReturnType] + with vcr.use_cassette(cassette_name('openai', vcr_suffix)): # type: ignore[reportUnknownReturnType] headers = {'Authorization': auth_header, 'content-type': 'application/json'} response = await client.post(url, content=body, headers=headers) elif request.url.path.startswith('/groq'): client = cast(httpx.AsyncClient, request.scope['state']['httpx_client']) url = GROQ_BASE_URL + request.url.path[len('/groq') :] - with vcr.use_cassette(cassette_name('groq', body_hash)): # type: ignore[reportUnknownReturnType] + with vcr.use_cassette(cassette_name('groq', vcr_suffix)): # type: ignore[reportUnknownReturnType] headers = {'Authorization': auth_header, 'content-type': 'application/json'} response = await client.post(url, content=body, headers=headers) elif request.url.path.startswith('/bedrock'): client = cast(httpx.AsyncClient, request.scope['state']['httpx_client']) url = BEDROCK_BASE_URL + request.url.path[len('/bedrock') :] - with vcr.use_cassette(cassette_name('bedrock', body_hash)): # type: ignore[reportUnknownReturnType] + with vcr.use_cassette(cassette_name('bedrock', vcr_suffix)): # type: ignore[reportUnknownReturnType] headers = { 'Authorization': auth_header, 'content-type': 'application/json', @@ -72,7 +73,7 @@ async def proxy(request: Request) -> Response: client = cast(httpx.AsyncClient, request.scope['state']['httpx_client']) url = ANTHROPIC_BASE_URL + request.url.path[len('/anthropic') :] api_key = request.headers.get('x-api-key', '') - with vcr.use_cassette(cassette_name('anthropic', body_hash)): # type: ignore[reportUnknownReturnType] + with vcr.use_cassette(cassette_name('anthropic', vcr_suffix)): # type: ignore[reportUnknownReturnType] anthropic_beta_headers = {} if anthropic_beta := request.headers.get('anthropic-beta'): anthropic_beta_headers = {'anthropic-beta': anthropic_beta} @@ -84,15 +85,23 @@ async def proxy(request: Request) -> Response: **anthropic_beta_headers, } response = await client.post(url, content=body, headers=headers) + elif request.url.path.startswith('/google-vertex'): + client = cast(httpx.AsyncClient, request.scope['state']['httpx_client']) + url = GOOGLE_BASE_URL + request.url.path[len('/google-vertex') :] + '?' + request.url.query + headers = {'Authorization': auth_header, 'host': 'aiplatform.googleapis.com'} + # It's a bit weird, but if we don't set the host header, it will fail. This seems very weird from Google's side. + with vcr.use_cassette(cassette_name('google-vertex', vcr_suffix)): # type: ignore[reportUnknownReturnType] + response = await client.post(url, content=body, headers=headers) else: raise HTTPException(status_code=404, detail=f'Path {request.url.path} not supported') - if response.headers.get('content-type').startswith('text/event-stream'): + content_type = cast(str | None, response.headers.get('content-type')) + if content_type and content_type.startswith('text/event-stream'): async def generator(): async for chunk in response.aiter_bytes(): yield chunk - return StreamingResponse(generator(), status_code=response.status_code) + return StreamingResponse(generator(), status_code=response.status_code, headers={'content-type': content_type}) return JSONResponse(response.json(), status_code=response.status_code) @@ -120,5 +129,5 @@ async def health_check(_: Request) -> Response: ) -def cassette_name(provider: str, hash: str) -> str: - return f'{provider}-{hash}.yaml' +def cassette_name(provider: str, vcr_suffix: str) -> str: + return f'{provider}-{vcr_suffix}.yaml' diff --git a/proxy-vcr/pyproject.toml b/proxy-vcr/pyproject.toml index ec722ff..6fa816f 100644 --- a/proxy-vcr/pyproject.toml +++ b/proxy-vcr/pyproject.toml @@ -8,6 +8,7 @@ version = "0.1.0" description = "The Proxy VCR is a tool that records and replays HTTP requests." readme = "README.md" dependencies = [ + "google-auth>=2.40.3", "httptools>=0.6.4", "httpx>=0.28.1", "openai>=1.99.9", diff --git a/pyproject.toml b/pyproject.toml index 2f59e2a..7b63e6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,4 +43,4 @@ include = ["examples", "proxy-vcr"] venv = ".venv" [tool.codespell] -ignore-words-list = ["afterAll", "fO"] +ignore-words-list = ["afterAll", "fO", "uE"] diff --git a/uv.lock b/uv.lock index 3ad1296..0113e2b 100644 --- a/uv.lock +++ b/uv.lock @@ -1599,6 +1599,7 @@ name = "proxy-vcr" version = "0.1.0" source = { editable = "proxy-vcr" } dependencies = [ + { name = "google-auth" }, { name = "httptools" }, { name = "httpx" }, { name = "openai" }, @@ -1613,6 +1614,7 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "google-auth", specifier = ">=2.40.3" }, { name = "httptools", specifier = ">=0.6.4" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "openai", specifier = ">=1.99.9" },