forked from boos/BoosAPI
Refactor reasoning handling and metadata management in responses
- Updated chat stream tests to clarify reasoning behavior and ensure correct handling of include lists. - Enhanced responses proxy route to normalize include lists and apply default reasoning includes based on request context. - Improved transformer metadata handling, ensuring proper serialization and deserialization of metadata records. - Added tests for preserving reasoning signatures and managing citations in OpenAI-compatible responses. - Refined the logic for injecting default includes in responses and ensured compatibility with codex surface defaults.
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
createGeminiGenerateContentAggregateState,
|
||||
} from './aggregator.js';
|
||||
import {
|
||||
extractTransformerMetadata,
|
||||
extractResponseMetadata,
|
||||
serializeGeminiAggregateResponse,
|
||||
} from './outbound.js';
|
||||
@@ -240,6 +241,60 @@ describe('Gemini aggregate/outbound/usage', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('projects Gemini richer metadata into the shared transformer metadata contract', () => {
|
||||
const state = createGeminiGenerateContentAggregateState();
|
||||
applyGeminiGenerateContentAggregate(state, {
|
||||
responseId: 'resp-shared-meta',
|
||||
modelVersion: 'gemini-3.1-pro-preview',
|
||||
candidates: [
|
||||
{
|
||||
index: 0,
|
||||
finishReason: 'STOP',
|
||||
groundingMetadata: { webSearchQueries: ['cats'] },
|
||||
citationMetadata: { citations: [{ uri: 'https://example.com/cats' }] },
|
||||
content: {
|
||||
parts: [
|
||||
{ text: 'reasoning', thought: true, thoughtSignature: 'sig-final' },
|
||||
{ text: 'answer' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
usageMetadata: {
|
||||
promptTokenCount: 15,
|
||||
candidatesTokenCount: 5,
|
||||
totalTokenCount: 24,
|
||||
},
|
||||
});
|
||||
|
||||
const metadata = extractTransformerMetadata(state, {
|
||||
cachedContent: 'cached/item-1',
|
||||
safetySettings: [{ category: 'SAFE', threshold: 'BLOCK_NONE' }],
|
||||
toolConfig: { functionCallingConfig: { mode: 'ANY' } },
|
||||
generationConfig: {
|
||||
imageConfig: { aspectRatio: '16:9' },
|
||||
},
|
||||
});
|
||||
|
||||
expect(metadata).toEqual({
|
||||
citations: [{ citations: [{ uri: 'https://example.com/cats' }] }],
|
||||
groundingMetadata: [{ webSearchQueries: ['cats'] }],
|
||||
thoughtSignature: 'sig-final',
|
||||
thoughtSignatures: ['sig-final'],
|
||||
usageMetadata: {
|
||||
promptTokenCount: 15,
|
||||
candidatesTokenCount: 5,
|
||||
totalTokenCount: 24,
|
||||
},
|
||||
geminiSafetySettings: [{ category: 'SAFE', threshold: 'BLOCK_NONE' }],
|
||||
geminiImageConfig: { aspectRatio: '16:9' },
|
||||
passthrough: {
|
||||
cachedContent: 'cached/item-1',
|
||||
toolConfig: { functionCallingConfig: { mode: 'ANY' } },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('merges split candidate metadata and emits top-level metadata in candidate order', () => {
|
||||
const state = createGeminiGenerateContentAggregateState();
|
||||
applyGeminiGenerateContentAggregate(state, [
|
||||
|
||||
@@ -3,6 +3,10 @@ import {
|
||||
createGeminiGenerateContentAggregateState,
|
||||
type GeminiGenerateContentAggregateState,
|
||||
} from './aggregator.js';
|
||||
import {
|
||||
toTransformerMetadataRecord,
|
||||
type TransformerMetadata,
|
||||
} from '../../shared/normalized.js';
|
||||
|
||||
function normalizeBaseUrl(baseUrl: string): string {
|
||||
return (baseUrl || '').replace(/\/+$/, '');
|
||||
@@ -176,14 +180,28 @@ function extractOrderedThoughtSignatures(
|
||||
};
|
||||
}
|
||||
|
||||
function extractRequestSemantics(requestPayload: unknown): GeminiRecord {
|
||||
function ensurePassthrough(metadata: TransformerMetadata): Record<string, unknown> {
|
||||
if (!metadata.passthrough) metadata.passthrough = {};
|
||||
return metadata.passthrough;
|
||||
}
|
||||
|
||||
function extractRequestSemantics(requestPayload: unknown): TransformerMetadata {
|
||||
if (!isRecord(requestPayload)) return {};
|
||||
|
||||
const metadata: GeminiRecord = {};
|
||||
if (requestPayload.systemInstruction !== undefined) metadata.systemInstruction = cloneJsonValue(requestPayload.systemInstruction);
|
||||
if (requestPayload.cachedContent !== undefined) metadata.cachedContent = cloneJsonValue(requestPayload.cachedContent);
|
||||
if (requestPayload.safetySettings !== undefined) metadata.safetySettings = cloneJsonValue(requestPayload.safetySettings);
|
||||
if (requestPayload.toolConfig !== undefined) metadata.toolConfig = cloneJsonValue(requestPayload.toolConfig);
|
||||
const metadata: TransformerMetadata = {};
|
||||
const passthrough = ensurePassthrough(metadata);
|
||||
if (requestPayload.systemInstruction !== undefined) {
|
||||
passthrough.systemInstruction = cloneJsonValue(requestPayload.systemInstruction);
|
||||
}
|
||||
if (requestPayload.cachedContent !== undefined) {
|
||||
passthrough.cachedContent = cloneJsonValue(requestPayload.cachedContent);
|
||||
}
|
||||
if (requestPayload.safetySettings !== undefined) {
|
||||
metadata.geminiSafetySettings = cloneJsonValue(requestPayload.safetySettings);
|
||||
}
|
||||
if (requestPayload.toolConfig !== undefined) {
|
||||
passthrough.toolConfig = cloneJsonValue(requestPayload.toolConfig);
|
||||
}
|
||||
|
||||
const generationConfig = isRecord(requestPayload.generationConfig) ? requestPayload.generationConfig : null;
|
||||
if (generationConfig) {
|
||||
@@ -207,7 +225,11 @@ function extractRequestSemantics(requestPayload: unknown): GeminiRecord {
|
||||
];
|
||||
for (const key of preservedKeys) {
|
||||
if (generationConfig[key] !== undefined) {
|
||||
metadata[key] = cloneJsonValue(generationConfig[key]);
|
||||
if (key === 'imageConfig') {
|
||||
metadata.geminiImageConfig = cloneJsonValue(generationConfig[key]);
|
||||
} else {
|
||||
passthrough[key] = cloneJsonValue(generationConfig[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,10 +248,14 @@ function extractRequestSemantics(requestPayload: unknown): GeminiRecord {
|
||||
.filter((item) => Object.keys(item).length > 0);
|
||||
|
||||
if (requestTools.length > 0) {
|
||||
metadata.tools = requestTools;
|
||||
passthrough.tools = requestTools;
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata.passthrough && Object.keys(metadata.passthrough).length <= 0) {
|
||||
delete metadata.passthrough;
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@@ -251,9 +277,9 @@ export function serializeGeminiAggregateResponse(
|
||||
return response;
|
||||
}
|
||||
|
||||
export function extractResponseMetadata(payload: unknown, requestPayload?: unknown): GeminiRecord {
|
||||
export function extractTransformerMetadata(payload: unknown, requestPayload?: unknown): TransformerMetadata {
|
||||
const state = ensureAggregateState(payload);
|
||||
const metadata: GeminiRecord = extractRequestSemantics(requestPayload);
|
||||
const metadata = extractRequestSemantics(requestPayload);
|
||||
|
||||
const citations = extractOrderedCandidateMetadata(state, 'citationMetadata');
|
||||
const groundingMetadata = extractOrderedCandidateMetadata(state, 'groundingMetadata');
|
||||
@@ -273,10 +299,15 @@ export function extractResponseMetadata(payload: unknown, requestPayload?: unkno
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export function extractResponseMetadata(payload: unknown, requestPayload?: unknown): GeminiRecord {
|
||||
return toTransformerMetadataRecord(extractTransformerMetadata(payload, requestPayload)) ?? {};
|
||||
}
|
||||
|
||||
export const geminiGenerateContentOutbound = {
|
||||
resolveBaseUrl,
|
||||
resolveModelsUrl,
|
||||
resolveActionUrl,
|
||||
extractTransformerMetadata,
|
||||
extractResponseMetadata,
|
||||
serializeAggregateResponse: serializeGeminiAggregateResponse,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user