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:
@@ -2,11 +2,14 @@ import { readFileSync } from 'node:fs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
fromTransformerMetadataRecord,
|
||||
normalizeStopReason,
|
||||
normalizeUpstreamFinalResponse,
|
||||
pullSseEventsWithDone,
|
||||
serializeFinalResponse,
|
||||
toTransformerMetadataRecord,
|
||||
type NormalizedFinalResponse,
|
||||
type TransformerMetadata,
|
||||
} from './normalized.js';
|
||||
|
||||
describe('shared normalized helpers', () => {
|
||||
@@ -143,6 +146,27 @@ describe('shared normalized helpers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('round-trips shared transformer metadata through transport-safe records', () => {
|
||||
const metadata: TransformerMetadata = {
|
||||
promptCacheKey: 'cache-key',
|
||||
truncation: 'auto',
|
||||
serviceTier: 'priority',
|
||||
citations: [{ uri: 'https://example.com/citation' }],
|
||||
thoughtSignature: 'sig-final',
|
||||
thoughtSignatures: ['sig-tool', 'sig-final'],
|
||||
geminiSafetySettings: [{ category: 'SAFE', threshold: 'BLOCK_NONE' }],
|
||||
geminiImageConfig: { aspectRatio: '16:9' },
|
||||
groundingMetadata: [{ webSearchQueries: ['cats'] }],
|
||||
usageMetadata: { totalTokenCount: 42 },
|
||||
passthrough: {
|
||||
cachedContent: 'cached/item-1',
|
||||
toolConfig: { functionCallingConfig: { mode: 'ANY' } },
|
||||
},
|
||||
};
|
||||
|
||||
expect(fromTransformerMetadataRecord(toTransformerMetadataRecord(metadata))).toEqual(metadata);
|
||||
});
|
||||
|
||||
it('normalizes known stop reasons', () => {
|
||||
expect(normalizeStopReason('max_output_tokens')).toBe('length');
|
||||
expect(normalizeStopReason('tool_use')).toBe('tool_calls');
|
||||
|
||||
@@ -78,6 +78,8 @@ export type TransformerMetadata = {
|
||||
includeObfuscation?: boolean | null;
|
||||
citations?: unknown;
|
||||
annotations?: unknown;
|
||||
groundingMetadata?: unknown;
|
||||
usageMetadata?: unknown;
|
||||
geminiSafetySettings?: unknown;
|
||||
geminiImageConfig?: unknown;
|
||||
thoughtSignature?: string | null;
|
||||
@@ -147,3 +149,133 @@ export function mergeNormalizedUsage(
|
||||
rejectedPredictionTokens: (merged.rejectedPredictionTokens || 0) + (incoming.rejectedPredictionTokens || 0),
|
||||
};
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return !!value && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function cloneJsonValue<T>(value: T): T {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => cloneJsonValue(item)) as T;
|
||||
}
|
||||
if (isRecord(value)) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, item]) => [key, cloneJsonValue(item)]),
|
||||
) as T;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const TRANSFORMER_METADATA_PASSTHROUGH_KEYS = new Set([
|
||||
'systemInstruction',
|
||||
'cachedContent',
|
||||
'toolConfig',
|
||||
'tools',
|
||||
'stopSequences',
|
||||
'responseModalities',
|
||||
'responseMimeType',
|
||||
'responseSchema',
|
||||
'candidateCount',
|
||||
'maxOutputTokens',
|
||||
'temperature',
|
||||
'topP',
|
||||
'topK',
|
||||
'presencePenalty',
|
||||
'frequencyPenalty',
|
||||
'seed',
|
||||
'responseLogprobs',
|
||||
'logprobs',
|
||||
'thinkingConfig',
|
||||
]);
|
||||
|
||||
export function toTransformerMetadataRecord(
|
||||
metadata: TransformerMetadata | undefined,
|
||||
): Record<string, unknown> | undefined {
|
||||
if (!metadata) return undefined;
|
||||
|
||||
const record: Record<string, unknown> = {};
|
||||
if (metadata.include !== undefined) record.include = cloneJsonValue(metadata.include);
|
||||
if (metadata.maxToolCalls !== undefined) record.maxToolCalls = metadata.maxToolCalls;
|
||||
if (metadata.promptCacheKey !== undefined) record.promptCacheKey = cloneJsonValue(metadata.promptCacheKey);
|
||||
if (metadata.promptCacheRetention !== undefined) {
|
||||
record.promptCacheRetention = cloneJsonValue(metadata.promptCacheRetention);
|
||||
}
|
||||
if (metadata.truncation !== undefined) record.truncation = cloneJsonValue(metadata.truncation);
|
||||
if (metadata.serviceTier !== undefined) record.serviceTier = cloneJsonValue(metadata.serviceTier);
|
||||
if (metadata.includeObfuscation !== undefined) record.includeObfuscation = metadata.includeObfuscation;
|
||||
if (metadata.citations !== undefined) record.citations = cloneJsonValue(metadata.citations);
|
||||
if (metadata.annotations !== undefined) record.annotations = cloneJsonValue(metadata.annotations);
|
||||
if (metadata.groundingMetadata !== undefined) {
|
||||
record.groundingMetadata = cloneJsonValue(metadata.groundingMetadata);
|
||||
}
|
||||
if (metadata.usageMetadata !== undefined) {
|
||||
record.usageMetadata = cloneJsonValue(metadata.usageMetadata);
|
||||
}
|
||||
if (metadata.geminiSafetySettings !== undefined) {
|
||||
record.safetySettings = cloneJsonValue(metadata.geminiSafetySettings);
|
||||
}
|
||||
if (metadata.geminiImageConfig !== undefined) {
|
||||
record.imageConfig = cloneJsonValue(metadata.geminiImageConfig);
|
||||
}
|
||||
if (metadata.thoughtSignature !== undefined) record.thoughtSignature = metadata.thoughtSignature;
|
||||
if (metadata.thoughtSignatures !== undefined) {
|
||||
record.thoughtSignatures = cloneJsonValue(metadata.thoughtSignatures);
|
||||
}
|
||||
if (isRecord(metadata.passthrough)) {
|
||||
for (const [key, value] of Object.entries(metadata.passthrough)) {
|
||||
if (record[key] === undefined) {
|
||||
record[key] = cloneJsonValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(record).length > 0 ? record : undefined;
|
||||
}
|
||||
|
||||
export function fromTransformerMetadataRecord(
|
||||
value: unknown,
|
||||
): TransformerMetadata | undefined {
|
||||
if (!isRecord(value)) return undefined;
|
||||
|
||||
const metadata: TransformerMetadata = {};
|
||||
if (value.include !== undefined) metadata.include = cloneJsonValue(value.include);
|
||||
if (typeof value.maxToolCalls === 'number' && Number.isFinite(value.maxToolCalls)) {
|
||||
metadata.maxToolCalls = value.maxToolCalls;
|
||||
}
|
||||
if (value.promptCacheKey !== undefined) metadata.promptCacheKey = cloneJsonValue(value.promptCacheKey);
|
||||
if (value.promptCacheRetention !== undefined) {
|
||||
metadata.promptCacheRetention = cloneJsonValue(value.promptCacheRetention);
|
||||
}
|
||||
if (value.truncation !== undefined) metadata.truncation = cloneJsonValue(value.truncation);
|
||||
if (value.serviceTier !== undefined) metadata.serviceTier = cloneJsonValue(value.serviceTier);
|
||||
if (typeof value.includeObfuscation === 'boolean') metadata.includeObfuscation = value.includeObfuscation;
|
||||
if (value.citations !== undefined) metadata.citations = cloneJsonValue(value.citations);
|
||||
if (value.annotations !== undefined) metadata.annotations = cloneJsonValue(value.annotations);
|
||||
if (value.groundingMetadata !== undefined) {
|
||||
metadata.groundingMetadata = cloneJsonValue(value.groundingMetadata);
|
||||
}
|
||||
if (value.usageMetadata !== undefined) {
|
||||
metadata.usageMetadata = cloneJsonValue(value.usageMetadata);
|
||||
}
|
||||
if (value.safetySettings !== undefined) {
|
||||
metadata.geminiSafetySettings = cloneJsonValue(value.safetySettings);
|
||||
}
|
||||
if (value.imageConfig !== undefined) {
|
||||
metadata.geminiImageConfig = cloneJsonValue(value.imageConfig);
|
||||
}
|
||||
if (typeof value.thoughtSignature === 'string') metadata.thoughtSignature = value.thoughtSignature;
|
||||
if (Array.isArray(value.thoughtSignatures)) {
|
||||
metadata.thoughtSignatures = value.thoughtSignatures
|
||||
.filter((item): item is string => typeof item === 'string');
|
||||
}
|
||||
|
||||
const passthroughEntries = Object.entries(value)
|
||||
.filter(([key]) => TRANSFORMER_METADATA_PASSTHROUGH_KEYS.has(key));
|
||||
if (passthroughEntries.length > 0) {
|
||||
metadata.passthrough = Object.fromEntries(
|
||||
passthroughEntries.map(([key, entry]) => [key, cloneJsonValue(entry)]),
|
||||
);
|
||||
}
|
||||
|
||||
return Object.keys(metadata).length > 0 ? metadata : undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user