@llumiverse/drivers 0.22.2 → 0.22.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/adobe/firefly.js +2 -2
- package/lib/cjs/adobe/firefly.js.map +1 -1
- package/lib/cjs/azure/azure_foundry.js +11 -11
- package/lib/cjs/azure/azure_foundry.js.map +1 -1
- package/lib/cjs/bedrock/index.js +7 -7
- package/lib/cjs/bedrock/index.js.map +1 -1
- package/lib/cjs/groq/index.js +2 -2
- package/lib/cjs/groq/index.js.map +1 -1
- package/lib/cjs/huggingface_ie.js +3 -4
- package/lib/cjs/huggingface_ie.js.map +1 -1
- package/lib/cjs/index.js +2 -2
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/mistral/index.js +2 -2
- package/lib/cjs/mistral/index.js.map +1 -1
- package/lib/cjs/openai/azure_openai.js +1 -1
- package/lib/cjs/openai/azure_openai.js.map +1 -1
- package/lib/cjs/openai/index.js +3 -3
- package/lib/cjs/openai/index.js.map +1 -1
- package/lib/cjs/replicate.js +4 -4
- package/lib/cjs/replicate.js.map +1 -1
- package/lib/cjs/togetherai/index.js +2 -2
- package/lib/cjs/togetherai/index.js.map +1 -1
- package/lib/cjs/vertexai/index.js +82 -40
- package/lib/cjs/vertexai/index.js.map +1 -1
- package/lib/cjs/vertexai/models/claude.js +4 -4
- package/lib/cjs/vertexai/models/claude.js.map +1 -1
- package/lib/cjs/vertexai/models/gemini.js +15 -5
- package/lib/cjs/vertexai/models/gemini.js.map +1 -1
- package/lib/cjs/vertexai/models/imagen.js +2 -5
- package/lib/cjs/vertexai/models/imagen.js.map +1 -1
- package/lib/cjs/watsonx/index.js +3 -3
- package/lib/cjs/watsonx/index.js.map +1 -1
- package/lib/esm/adobe/firefly.js +2 -2
- package/lib/esm/adobe/firefly.js.map +1 -1
- package/lib/esm/azure/azure_foundry.js +11 -11
- package/lib/esm/azure/azure_foundry.js.map +1 -1
- package/lib/esm/bedrock/index.js +8 -8
- package/lib/esm/bedrock/index.js.map +1 -1
- package/lib/esm/groq/index.js +2 -2
- package/lib/esm/groq/index.js.map +1 -1
- package/lib/esm/huggingface_ie.js +4 -5
- package/lib/esm/huggingface_ie.js.map +1 -1
- package/lib/esm/index.js +2 -2
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/mistral/index.js +2 -2
- package/lib/esm/mistral/index.js.map +1 -1
- package/lib/esm/openai/azure_openai.js +1 -1
- package/lib/esm/openai/azure_openai.js.map +1 -1
- package/lib/esm/openai/index.js +3 -3
- package/lib/esm/openai/index.js.map +1 -1
- package/lib/esm/replicate.js +4 -4
- package/lib/esm/replicate.js.map +1 -1
- package/lib/esm/togetherai/index.js +2 -2
- package/lib/esm/togetherai/index.js.map +1 -1
- package/lib/esm/vertexai/index.js +83 -41
- package/lib/esm/vertexai/index.js.map +1 -1
- package/lib/esm/vertexai/models/claude.js +4 -4
- package/lib/esm/vertexai/models/claude.js.map +1 -1
- package/lib/esm/vertexai/models/gemini.js +15 -5
- package/lib/esm/vertexai/models/gemini.js.map +1 -1
- package/lib/esm/vertexai/models/imagen.js +3 -6
- package/lib/esm/vertexai/models/imagen.js.map +1 -1
- package/lib/esm/watsonx/index.js +3 -3
- package/lib/esm/watsonx/index.js.map +1 -1
- package/lib/types/bedrock/index.d.ts +1 -0
- package/lib/types/bedrock/index.d.ts.map +1 -1
- package/lib/types/huggingface_ie.d.ts +3 -3
- package/lib/types/huggingface_ie.d.ts.map +1 -1
- package/lib/types/index.d.ts +2 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/replicate.d.ts.map +1 -1
- package/lib/types/vertexai/index.d.ts +12 -11
- package/lib/types/vertexai/index.d.ts.map +1 -1
- package/lib/types/vertexai/models/gemini.d.ts.map +1 -1
- package/lib/types/vertexai/models/imagen.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/bedrock/index.ts +5 -4
- package/src/vertexai/index.ts +53 -6
- package/src/vertexai/models/gemini.ts +18 -6
- package/src/vertexai/models/imagen.ts +3 -7
- package/lib/cjs/test-driver/TestErrorCompletionStream.js +0 -20
- package/lib/cjs/test-driver/TestErrorCompletionStream.js.map +0 -1
- package/lib/cjs/test-driver/TestValidationErrorCompletionStream.js +0 -24
- package/lib/cjs/test-driver/TestValidationErrorCompletionStream.js.map +0 -1
- package/lib/cjs/test-driver/index.js +0 -109
- package/lib/cjs/test-driver/index.js.map +0 -1
- package/lib/cjs/test-driver/utils.js +0 -30
- package/lib/cjs/test-driver/utils.js.map +0 -1
- package/lib/esm/src/adobe/firefly.js +0 -116
- package/lib/esm/src/adobe/firefly.js.map +0 -1
- package/lib/esm/src/azure/azure_foundry.js +0 -382
- package/lib/esm/src/azure/azure_foundry.js.map +0 -1
- package/lib/esm/src/bedrock/converse.js +0 -278
- package/lib/esm/src/bedrock/converse.js.map +0 -1
- package/lib/esm/src/bedrock/index.js +0 -962
- package/lib/esm/src/bedrock/index.js.map +0 -1
- package/lib/esm/src/bedrock/nova-image-payload.js +0 -203
- package/lib/esm/src/bedrock/nova-image-payload.js.map +0 -1
- package/lib/esm/src/bedrock/payloads.js +0 -2
- package/lib/esm/src/bedrock/payloads.js.map +0 -1
- package/lib/esm/src/bedrock/s3.js +0 -99
- package/lib/esm/src/bedrock/s3.js.map +0 -1
- package/lib/esm/src/bedrock/twelvelabs.js +0 -84
- package/lib/esm/src/bedrock/twelvelabs.js.map +0 -1
- package/lib/esm/src/groq/index.js +0 -286
- package/lib/esm/src/groq/index.js.map +0 -1
- package/lib/esm/src/huggingface_ie.js +0 -197
- package/lib/esm/src/huggingface_ie.js.map +0 -1
- package/lib/esm/src/index.js +0 -14
- package/lib/esm/src/index.js.map +0 -1
- package/lib/esm/src/mistral/index.js +0 -169
- package/lib/esm/src/mistral/index.js.map +0 -1
- package/lib/esm/src/mistral/types.js +0 -80
- package/lib/esm/src/mistral/types.js.map +0 -1
- package/lib/esm/src/openai/azure_openai.js +0 -68
- package/lib/esm/src/openai/azure_openai.js.map +0 -1
- package/lib/esm/src/openai/index.js +0 -464
- package/lib/esm/src/openai/index.js.map +0 -1
- package/lib/esm/src/openai/openai.js +0 -14
- package/lib/esm/src/openai/openai.js.map +0 -1
- package/lib/esm/src/openai/openai_format.js +0 -134
- package/lib/esm/src/openai/openai_format.js.map +0 -1
- package/lib/esm/src/replicate.js +0 -268
- package/lib/esm/src/replicate.js.map +0 -1
- package/lib/esm/src/test/TestErrorCompletionStream.js +0 -16
- package/lib/esm/src/test/TestErrorCompletionStream.js.map +0 -1
- package/lib/esm/src/test/TestValidationErrorCompletionStream.js +0 -20
- package/lib/esm/src/test/TestValidationErrorCompletionStream.js.map +0 -1
- package/lib/esm/src/test/index.js +0 -91
- package/lib/esm/src/test/index.js.map +0 -1
- package/lib/esm/src/test/utils.js +0 -25
- package/lib/esm/src/test/utils.js.map +0 -1
- package/lib/esm/src/test-driver/TestErrorCompletionStream.js +0 -16
- package/lib/esm/src/test-driver/TestErrorCompletionStream.js.map +0 -1
- package/lib/esm/src/test-driver/TestValidationErrorCompletionStream.js +0 -20
- package/lib/esm/src/test-driver/TestValidationErrorCompletionStream.js.map +0 -1
- package/lib/esm/src/test-driver/index.js +0 -91
- package/lib/esm/src/test-driver/index.js.map +0 -1
- package/lib/esm/src/test-driver/utils.js +0 -25
- package/lib/esm/src/test-driver/utils.js.map +0 -1
- package/lib/esm/src/togetherai/index.js +0 -122
- package/lib/esm/src/togetherai/index.js.map +0 -1
- package/lib/esm/src/togetherai/interfaces.js +0 -2
- package/lib/esm/src/togetherai/interfaces.js.map +0 -1
- package/lib/esm/src/vertexai/debug.js +0 -6
- package/lib/esm/src/vertexai/debug.js.map +0 -1
- package/lib/esm/src/vertexai/embeddings/embeddings-image.js +0 -24
- package/lib/esm/src/vertexai/embeddings/embeddings-image.js.map +0 -1
- package/lib/esm/src/vertexai/embeddings/embeddings-text.js +0 -20
- package/lib/esm/src/vertexai/embeddings/embeddings-text.js.map +0 -1
- package/lib/esm/src/vertexai/index.js +0 -383
- package/lib/esm/src/vertexai/index.js.map +0 -1
- package/lib/esm/src/vertexai/models/claude.js +0 -394
- package/lib/esm/src/vertexai/models/claude.js.map +0 -1
- package/lib/esm/src/vertexai/models/gemini.js +0 -817
- package/lib/esm/src/vertexai/models/gemini.js.map +0 -1
- package/lib/esm/src/vertexai/models/imagen.js +0 -302
- package/lib/esm/src/vertexai/models/imagen.js.map +0 -1
- package/lib/esm/src/vertexai/models/llama.js +0 -179
- package/lib/esm/src/vertexai/models/llama.js.map +0 -1
- package/lib/esm/src/vertexai/models.js +0 -32
- package/lib/esm/src/vertexai/models.js.map +0 -1
- package/lib/esm/src/watsonx/index.js +0 -157
- package/lib/esm/src/watsonx/index.js.map +0 -1
- package/lib/esm/src/watsonx/interfaces.js +0 -2
- package/lib/esm/src/watsonx/interfaces.js.map +0 -1
- package/lib/esm/src/xai/index.js +0 -64
- package/lib/esm/src/xai/index.js.map +0 -1
- package/lib/esm/test-driver/TestErrorCompletionStream.js +0 -16
- package/lib/esm/test-driver/TestErrorCompletionStream.js.map +0 -1
- package/lib/esm/test-driver/TestValidationErrorCompletionStream.js +0 -20
- package/lib/esm/test-driver/TestValidationErrorCompletionStream.js.map +0 -1
- package/lib/esm/test-driver/index.js +0 -91
- package/lib/esm/test-driver/index.js.map +0 -1
- package/lib/esm/test-driver/utils.js +0 -25
- package/lib/esm/test-driver/utils.js.map +0 -1
- package/lib/esm/tsconfig.tsbuildinfo +0 -1
- package/lib/types/src/adobe/firefly.d.ts +0 -29
- package/lib/types/src/azure/azure_foundry.d.ts +0 -49
- package/lib/types/src/bedrock/converse.d.ts +0 -8
- package/lib/types/src/bedrock/index.d.ts +0 -61
- package/lib/types/src/bedrock/nova-image-payload.d.ts +0 -73
- package/lib/types/src/bedrock/payloads.d.ts +0 -11
- package/lib/types/src/bedrock/s3.d.ts +0 -22
- package/lib/types/src/bedrock/twelvelabs.d.ts +0 -49
- package/lib/types/src/groq/index.d.ts +0 -26
- package/lib/types/src/huggingface_ie.d.ts +0 -34
- package/lib/types/src/index.d.ts +0 -13
- package/lib/types/src/mistral/index.d.ts +0 -24
- package/lib/types/src/mistral/types.d.ts +0 -131
- package/lib/types/src/openai/azure_openai.d.ts +0 -24
- package/lib/types/src/openai/index.d.ts +0 -24
- package/lib/types/src/openai/openai.d.ts +0 -14
- package/lib/types/src/openai/openai_format.d.ts +0 -18
- package/lib/types/src/replicate.d.ts +0 -47
- package/lib/types/src/test/TestErrorCompletionStream.d.ts +0 -8
- package/lib/types/src/test/TestValidationErrorCompletionStream.d.ts +0 -8
- package/lib/types/src/test/index.d.ts +0 -23
- package/lib/types/src/test/utils.d.ts +0 -4
- package/lib/types/src/test-driver/TestErrorCompletionStream.d.ts +0 -8
- package/lib/types/src/test-driver/TestValidationErrorCompletionStream.d.ts +0 -8
- package/lib/types/src/test-driver/index.d.ts +0 -23
- package/lib/types/src/test-driver/utils.d.ts +0 -4
- package/lib/types/src/togetherai/index.d.ts +0 -22
- package/lib/types/src/togetherai/interfaces.d.ts +0 -95
- package/lib/types/src/vertexai/debug.d.ts +0 -1
- package/lib/types/src/vertexai/embeddings/embeddings-image.d.ts +0 -10
- package/lib/types/src/vertexai/embeddings/embeddings-text.d.ts +0 -9
- package/lib/types/src/vertexai/index.d.ts +0 -52
- package/lib/types/src/vertexai/models/claude.d.ts +0 -19
- package/lib/types/src/vertexai/models/gemini.d.ts +0 -17
- package/lib/types/src/vertexai/models/imagen.d.ts +0 -74
- package/lib/types/src/vertexai/models/llama.d.ts +0 -19
- package/lib/types/src/vertexai/models.d.ts +0 -14
- package/lib/types/src/watsonx/index.d.ts +0 -26
- package/lib/types/src/watsonx/interfaces.d.ts +0 -64
- package/lib/types/src/xai/index.d.ts +0 -18
- package/lib/types/test-driver/TestErrorCompletionStream.d.ts +0 -9
- package/lib/types/test-driver/TestErrorCompletionStream.d.ts.map +0 -1
- package/lib/types/test-driver/TestValidationErrorCompletionStream.d.ts +0 -9
- package/lib/types/test-driver/TestValidationErrorCompletionStream.d.ts.map +0 -1
- package/lib/types/test-driver/index.d.ts +0 -24
- package/lib/types/test-driver/index.d.ts.map +0 -1
- package/lib/types/test-driver/utils.d.ts +0 -5
- package/lib/types/test-driver/utils.d.ts.map +0 -1
|
@@ -1,817 +0,0 @@
|
|
|
1
|
-
import { FinishReason, FunctionCallingConfigMode, HarmBlockThreshold, HarmCategory, Modality, Type } from "@google/genai";
|
|
2
|
-
import { getMaxTokensLimitVertexAi, ModelType, PromptRole, readStreamAsBase64 } from "@llumiverse/core";
|
|
3
|
-
import { asyncMap } from "@llumiverse/core/async";
|
|
4
|
-
function supportsStructuredOutput(options) {
|
|
5
|
-
// Gemini 1.0 Ultra does not support JSON output, 1.0 Pro does.
|
|
6
|
-
return !!options.result_schema && !options.model.includes("ultra");
|
|
7
|
-
}
|
|
8
|
-
const geminiSafetySettings = [
|
|
9
|
-
{
|
|
10
|
-
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
|
|
11
|
-
threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
|
|
15
|
-
threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
|
|
19
|
-
threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
|
|
23
|
-
threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
category: HarmCategory.HARM_CATEGORY_UNSPECIFIED,
|
|
27
|
-
threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
category: HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
|
|
31
|
-
threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
|
|
32
|
-
}
|
|
33
|
-
];
|
|
34
|
-
function getGeminiPayload(options, prompt) {
|
|
35
|
-
const model_options = options.model_options;
|
|
36
|
-
const tools = getToolDefinitions(options.tools);
|
|
37
|
-
const useStructuredOutput = supportsStructuredOutput(options) && !tools;
|
|
38
|
-
const thinkingConfigNeeded = model_options?.include_thoughts
|
|
39
|
-
|| model_options?.thinking_budget_tokens
|
|
40
|
-
|| options.model.includes("gemini-2.5");
|
|
41
|
-
const configNanoBanana = {
|
|
42
|
-
systemInstruction: prompt.system,
|
|
43
|
-
safetySettings: geminiSafetySettings,
|
|
44
|
-
responseModalities: [Modality.TEXT, Modality.IMAGE], // This is an error if only Text, and Only Image just gets blank responses.
|
|
45
|
-
candidateCount: 1,
|
|
46
|
-
//Model options
|
|
47
|
-
temperature: model_options?.temperature,
|
|
48
|
-
topP: model_options?.top_p,
|
|
49
|
-
maxOutputTokens: geminiMaxTokens(options),
|
|
50
|
-
stopSequences: model_options?.stop_sequence,
|
|
51
|
-
imageConfig: {
|
|
52
|
-
aspectRatio: model_options?.image_aspect_ratio,
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
const config = {
|
|
56
|
-
systemInstruction: prompt.system,
|
|
57
|
-
safetySettings: geminiSafetySettings,
|
|
58
|
-
tools: tools ? [tools] : undefined,
|
|
59
|
-
toolConfig: tools ? {
|
|
60
|
-
functionCallingConfig: {
|
|
61
|
-
mode: FunctionCallingConfigMode.AUTO,
|
|
62
|
-
}
|
|
63
|
-
} : undefined,
|
|
64
|
-
candidateCount: 1,
|
|
65
|
-
//JSON/Structured output
|
|
66
|
-
responseMimeType: useStructuredOutput ? "application/json" : undefined,
|
|
67
|
-
responseSchema: useStructuredOutput ? parseJSONtoSchema(options.result_schema, true) : undefined,
|
|
68
|
-
//Model options
|
|
69
|
-
temperature: model_options?.temperature,
|
|
70
|
-
topP: model_options?.top_p,
|
|
71
|
-
topK: model_options?.top_k,
|
|
72
|
-
maxOutputTokens: geminiMaxTokens(options),
|
|
73
|
-
stopSequences: model_options?.stop_sequence,
|
|
74
|
-
presencePenalty: model_options?.presence_penalty,
|
|
75
|
-
frequencyPenalty: model_options?.frequency_penalty,
|
|
76
|
-
seed: model_options?.seed,
|
|
77
|
-
thinkingConfig: thinkingConfigNeeded ?
|
|
78
|
-
{
|
|
79
|
-
includeThoughts: model_options?.include_thoughts ?? false,
|
|
80
|
-
thinkingBudget: geminiThinkingBudget(options),
|
|
81
|
-
} : undefined,
|
|
82
|
-
};
|
|
83
|
-
return {
|
|
84
|
-
model: options.model,
|
|
85
|
-
contents: prompt.contents,
|
|
86
|
-
config: options.model.toLowerCase().includes("image") ? configNanoBanana : config,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Convert JSONSchema to Gemini Schema,
|
|
91
|
-
* Make all properties required by default
|
|
92
|
-
* Properties previously marked as optional will be marked as nullable.
|
|
93
|
-
*/
|
|
94
|
-
function parseJSONtoSchema(schema, requiredAll = false) {
|
|
95
|
-
if (!schema) {
|
|
96
|
-
return {};
|
|
97
|
-
}
|
|
98
|
-
return convertSchema(schema, 0, requiredAll);
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Convert JSONSchema type to Gemini Schema Type
|
|
102
|
-
*/
|
|
103
|
-
function convertType(type) {
|
|
104
|
-
if (!type)
|
|
105
|
-
return undefined;
|
|
106
|
-
// Handle single type
|
|
107
|
-
if (typeof type === 'string') {
|
|
108
|
-
switch (type) {
|
|
109
|
-
case 'string': return Type.STRING;
|
|
110
|
-
case 'number': return Type.NUMBER;
|
|
111
|
-
case 'integer': return Type.INTEGER;
|
|
112
|
-
case 'boolean': return Type.BOOLEAN;
|
|
113
|
-
case 'object': return Type.OBJECT;
|
|
114
|
-
case 'array': return Type.ARRAY;
|
|
115
|
-
default: return type; // For unsupported types, return as is
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
// For array of types, take the first valid one as the primary type
|
|
119
|
-
// The full set of types will be handled with anyOf
|
|
120
|
-
for (const t of type) {
|
|
121
|
-
const converted = convertType(t);
|
|
122
|
-
if (converted)
|
|
123
|
-
return converted;
|
|
124
|
-
}
|
|
125
|
-
return undefined;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Deep clone and convert the schema from JSONSchema to Gemini Schema
|
|
129
|
-
* @throws {Error} If circular references are detected (max depth exceeded)
|
|
130
|
-
*/
|
|
131
|
-
function convertSchema(jsSchema, depth = 0, requiredAll = false) {
|
|
132
|
-
// Prevent circular references
|
|
133
|
-
if (depth > 20) {
|
|
134
|
-
throw new Error("Maximum schema depth (20) exceeded. Possible circular reference detected.");
|
|
135
|
-
}
|
|
136
|
-
if (!jsSchema)
|
|
137
|
-
return {};
|
|
138
|
-
// Create new schema object rather than mutating
|
|
139
|
-
const result = {};
|
|
140
|
-
// Handle types
|
|
141
|
-
result.type = convertSchemaType(jsSchema);
|
|
142
|
-
// Handle description
|
|
143
|
-
if (jsSchema.description) {
|
|
144
|
-
result.description = jsSchema.description;
|
|
145
|
-
}
|
|
146
|
-
// Handle properties and required fields
|
|
147
|
-
if (jsSchema.properties) {
|
|
148
|
-
const propertyResult = convertSchemaProperties(jsSchema, depth + 1, requiredAll);
|
|
149
|
-
Object.assign(result, propertyResult);
|
|
150
|
-
}
|
|
151
|
-
// Handle items for arrays
|
|
152
|
-
if (jsSchema.items) {
|
|
153
|
-
result.items = convertSchema(jsSchema.items, depth + 1);
|
|
154
|
-
}
|
|
155
|
-
// Handle enum values
|
|
156
|
-
if (jsSchema.enum) {
|
|
157
|
-
result.enum = [...jsSchema.enum]; // Create a copy instead of reference
|
|
158
|
-
}
|
|
159
|
-
// Copy constraints
|
|
160
|
-
Object.assign(result, extractConstraints(jsSchema));
|
|
161
|
-
return result;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Convert schema type information, handling anyOf for multiple types
|
|
165
|
-
*/
|
|
166
|
-
function convertSchemaType(jsSchema) {
|
|
167
|
-
// Handle multiple types using anyOf
|
|
168
|
-
if (jsSchema.type && Array.isArray(jsSchema.type) && jsSchema.type.length > 1) {
|
|
169
|
-
// Since anyOf is an advanced type, we'll return the first valid type
|
|
170
|
-
// and handle the multi-type case separately in the schema
|
|
171
|
-
return convertType(jsSchema.type[0]);
|
|
172
|
-
}
|
|
173
|
-
// Handle single type
|
|
174
|
-
else if (jsSchema.type) {
|
|
175
|
-
return convertType(jsSchema.type);
|
|
176
|
-
}
|
|
177
|
-
return undefined;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Handle properties conversion and required fields
|
|
181
|
-
*/
|
|
182
|
-
function convertSchemaProperties(jsSchema, depth, requiredAll) {
|
|
183
|
-
const result = { properties: {} };
|
|
184
|
-
if (jsSchema.required) {
|
|
185
|
-
result.required = [...jsSchema.required]; // Create a copy
|
|
186
|
-
}
|
|
187
|
-
// Extract property ordering from the object keys
|
|
188
|
-
const propertyNames = Object.keys(jsSchema.properties || {});
|
|
189
|
-
// Set property ordering based on the existing order in the schema
|
|
190
|
-
if (propertyNames.length > 0) {
|
|
191
|
-
result.propertyOrdering = propertyNames;
|
|
192
|
-
if (requiredAll) {
|
|
193
|
-
// Mark all properties as required by default
|
|
194
|
-
// This ensures the model fills all fields
|
|
195
|
-
result.required = propertyNames;
|
|
196
|
-
// Get the original required properties
|
|
197
|
-
const originalRequired = jsSchema.required || [];
|
|
198
|
-
// Make previously optional properties nullable since we're marking them as required
|
|
199
|
-
for (const key of propertyNames) {
|
|
200
|
-
const propSchema = jsSchema.properties?.[key];
|
|
201
|
-
if (propSchema && !originalRequired.includes(key)) {
|
|
202
|
-
// Initialize the property if needed
|
|
203
|
-
if (!result.properties[key]) {
|
|
204
|
-
result.properties[key] = {};
|
|
205
|
-
}
|
|
206
|
-
// Mark as nullable
|
|
207
|
-
result.properties[key].nullable = true;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
// Convert each property schema
|
|
213
|
-
for (const [key, value] of Object.entries(jsSchema.properties || {})) {
|
|
214
|
-
if (!result.properties[key]) {
|
|
215
|
-
result.properties[key] = {};
|
|
216
|
-
}
|
|
217
|
-
// Merge with converted schema
|
|
218
|
-
result.properties[key] = {
|
|
219
|
-
...result.properties[key],
|
|
220
|
-
...convertSchema(value, depth)
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
// Override with explicit propertyOrdering if present
|
|
224
|
-
if (jsSchema.propertyOrdering) {
|
|
225
|
-
result.propertyOrdering = [...jsSchema.propertyOrdering]; // Create a copy
|
|
226
|
-
}
|
|
227
|
-
return result;
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Extract schema constraints (min/max values, formats, etc.)
|
|
231
|
-
*/
|
|
232
|
-
function extractConstraints(jsSchema) {
|
|
233
|
-
const constraints = {};
|
|
234
|
-
if (jsSchema.minimum !== undefined)
|
|
235
|
-
constraints.minimum = jsSchema.minimum;
|
|
236
|
-
if (jsSchema.maximum !== undefined)
|
|
237
|
-
constraints.maximum = jsSchema.maximum;
|
|
238
|
-
if (jsSchema.minLength !== undefined)
|
|
239
|
-
constraints.minLength = jsSchema.minLength;
|
|
240
|
-
if (jsSchema.maxLength !== undefined)
|
|
241
|
-
constraints.maxLength = jsSchema.maxLength;
|
|
242
|
-
if (jsSchema.minItems !== undefined)
|
|
243
|
-
constraints.minItems = jsSchema.minItems;
|
|
244
|
-
if (jsSchema.maxItems !== undefined)
|
|
245
|
-
constraints.maxItems = jsSchema.maxItems;
|
|
246
|
-
if (jsSchema.nullable !== undefined)
|
|
247
|
-
constraints.nullable = jsSchema.nullable;
|
|
248
|
-
if (jsSchema.pattern)
|
|
249
|
-
constraints.pattern = jsSchema.pattern;
|
|
250
|
-
if (jsSchema.format)
|
|
251
|
-
constraints.format = jsSchema.format;
|
|
252
|
-
if (jsSchema.default !== undefined)
|
|
253
|
-
constraints.default = jsSchema.default;
|
|
254
|
-
if (jsSchema.example !== undefined)
|
|
255
|
-
constraints.example = jsSchema.example;
|
|
256
|
-
return constraints;
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Check if a value is empty (null, undefined, empty string, empty array, empty object)
|
|
260
|
-
* @param value The value to check
|
|
261
|
-
* @returns True if the value is considered empty
|
|
262
|
-
*/
|
|
263
|
-
function isEmpty(value) {
|
|
264
|
-
if (value === null || value === undefined) {
|
|
265
|
-
return true;
|
|
266
|
-
}
|
|
267
|
-
if (typeof value === 'string' && value.trim() === '') {
|
|
268
|
-
return true;
|
|
269
|
-
}
|
|
270
|
-
if (Array.isArray(value) && value.length === 0) {
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
// Check for empty object (no own enumerable properties)
|
|
274
|
-
if (typeof value === 'object' && Object.keys(value).length === 0) {
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
|
-
// Check for array of empty objects
|
|
278
|
-
if (Array.isArray(value) && value.every(item => isEmpty(item))) {
|
|
279
|
-
return true;
|
|
280
|
-
}
|
|
281
|
-
return false;
|
|
282
|
-
}
|
|
283
|
-
// No array cleaning function needed as we're only working with JSONObjects
|
|
284
|
-
/**
|
|
285
|
-
* Clean up the JSON result by removing empty values for optional fields
|
|
286
|
-
* Uses immutable patterns to create a new Content object rather than modifying the original
|
|
287
|
-
* @param content The original content from Gemini
|
|
288
|
-
* @param result_schema The JSON schema to use for cleaning
|
|
289
|
-
* @returns A new Content object with cleaned JSON text
|
|
290
|
-
*/
|
|
291
|
-
function cleanEmptyFieldsContent(content, result_schema) {
|
|
292
|
-
// If no schema provided, return original content
|
|
293
|
-
if (!result_schema) {
|
|
294
|
-
return content;
|
|
295
|
-
}
|
|
296
|
-
// Create a new content object (shallow copy)
|
|
297
|
-
const cleanedContent = { ...content };
|
|
298
|
-
// Create a new parts array if it exists
|
|
299
|
-
if (cleanedContent.parts) {
|
|
300
|
-
cleanedContent.parts = cleanedContent.parts.map(part => {
|
|
301
|
-
// Only process parts with text
|
|
302
|
-
if (!part.text) {
|
|
303
|
-
return part; // Return unchanged if no text
|
|
304
|
-
}
|
|
305
|
-
// Create a new part object
|
|
306
|
-
const newPart = { ...part };
|
|
307
|
-
try {
|
|
308
|
-
// Parse JSON, clean it based on schema, then stringify
|
|
309
|
-
const jsonText = JSON.parse(part.text);
|
|
310
|
-
// Skip cleaning if not an object
|
|
311
|
-
if (typeof jsonText === 'object' && jsonText !== null && !Array.isArray(jsonText)) {
|
|
312
|
-
const cleanedJson = removeEmptyFields(jsonText, result_schema);
|
|
313
|
-
newPart.text = JSON.stringify(cleanedJson);
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
// Keep original if not an object (string, number, array, etc.)
|
|
317
|
-
newPart.text = part.text;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
catch (e) {
|
|
321
|
-
// On error, keep the original text
|
|
322
|
-
console.warn("Error parsing Gemini output to JSON in part:", e);
|
|
323
|
-
}
|
|
324
|
-
return newPart;
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
return cleanedContent;
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Removes empty optional fields from the JSON result based on the provided schema
|
|
331
|
-
* @param object The object to clean
|
|
332
|
-
* @param schema The JSON schema to use for cleaning
|
|
333
|
-
* @returns A new object with empty optional fields removed
|
|
334
|
-
*/
|
|
335
|
-
function removeEmptyFields(object, schema) {
|
|
336
|
-
if (!object) {
|
|
337
|
-
return object;
|
|
338
|
-
}
|
|
339
|
-
if (Array.isArray(object)) {
|
|
340
|
-
return removeEmptyJSONArray(object, schema);
|
|
341
|
-
}
|
|
342
|
-
if (typeof object == 'object' || object === null) {
|
|
343
|
-
return removeEmptyJSONObject(object, schema);
|
|
344
|
-
}
|
|
345
|
-
return object;
|
|
346
|
-
}
|
|
347
|
-
function removeEmptyJSONObject(object, schema) {
|
|
348
|
-
// Get the original required properties from schema
|
|
349
|
-
const requiredProps = schema.required || [];
|
|
350
|
-
const cleanedResult = { ...object };
|
|
351
|
-
// Process each property
|
|
352
|
-
for (const [key, value] of Object.entries(object)) {
|
|
353
|
-
const isRequired = requiredProps.includes(key);
|
|
354
|
-
const propSchema = schema.properties?.[key];
|
|
355
|
-
// Recursively clean nested objects based on their schema
|
|
356
|
-
cleanedResult[key] = removeEmptyFields(value, propSchema ?? {});
|
|
357
|
-
if (isEmpty(value)) {
|
|
358
|
-
if (isRequired) {
|
|
359
|
-
continue; // Keep required fields even if empty
|
|
360
|
-
}
|
|
361
|
-
else {
|
|
362
|
-
delete cleanedResult[key]; // Remove empty optional fields
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return cleanedResult;
|
|
367
|
-
}
|
|
368
|
-
function removeEmptyJSONArray(array, schema) {
|
|
369
|
-
const cleanedArray = array.map(item => {
|
|
370
|
-
return removeEmptyFields(item, schema);
|
|
371
|
-
});
|
|
372
|
-
// Filter out empty objects from the array
|
|
373
|
-
return cleanedArray.filter(item => !isEmpty(item));
|
|
374
|
-
}
|
|
375
|
-
function collectTextParts(content) {
|
|
376
|
-
const results = [];
|
|
377
|
-
const parts = content.parts;
|
|
378
|
-
if (parts) {
|
|
379
|
-
for (const part of parts) {
|
|
380
|
-
if (part.text) {
|
|
381
|
-
results.push({
|
|
382
|
-
type: "text",
|
|
383
|
-
value: part.text
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return results;
|
|
389
|
-
}
|
|
390
|
-
function collectInlineDataParts(content) {
|
|
391
|
-
const results = [];
|
|
392
|
-
const parts = content.parts;
|
|
393
|
-
if (parts) {
|
|
394
|
-
for (const part of parts) {
|
|
395
|
-
if (part.inlineData) {
|
|
396
|
-
const base64ImageBytes = part.inlineData.data ?? "";
|
|
397
|
-
const mimeType = part.inlineData.mimeType ?? "image/png";
|
|
398
|
-
const imageUrl = `data:${mimeType};base64,${base64ImageBytes}`;
|
|
399
|
-
results.push({
|
|
400
|
-
type: "image",
|
|
401
|
-
value: imageUrl
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
return results;
|
|
407
|
-
}
|
|
408
|
-
function collectToolUseParts(content) {
|
|
409
|
-
const out = [];
|
|
410
|
-
const parts = content.parts ?? [];
|
|
411
|
-
for (const part of parts) {
|
|
412
|
-
if (part.functionCall) {
|
|
413
|
-
out.push({
|
|
414
|
-
id: part.functionCall.name ?? '',
|
|
415
|
-
tool_name: part.functionCall.name ?? '',
|
|
416
|
-
tool_input: part.functionCall.args,
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
return out.length > 0 ? out : undefined;
|
|
421
|
-
}
|
|
422
|
-
export function mergeConsecutiveRole(contents) {
|
|
423
|
-
if (!contents || contents.length === 0)
|
|
424
|
-
return [];
|
|
425
|
-
const needsMerging = contents.some((content, i) => i < contents.length - 1 && content.role === contents[i + 1].role);
|
|
426
|
-
// If no merging needed, return original array
|
|
427
|
-
if (!needsMerging) {
|
|
428
|
-
return contents;
|
|
429
|
-
}
|
|
430
|
-
const result = [];
|
|
431
|
-
let currentContent = { ...contents[0], parts: [...(contents[0].parts || [])] };
|
|
432
|
-
for (let i = 1; i < contents.length; i++) {
|
|
433
|
-
if (currentContent.role === contents[i].role) {
|
|
434
|
-
// Same role - concatenate parts (without merging individual parts)
|
|
435
|
-
currentContent.parts = (currentContent.parts || []).concat(...(contents[i].parts || []));
|
|
436
|
-
}
|
|
437
|
-
else {
|
|
438
|
-
// Different role - push current and start new
|
|
439
|
-
result.push(currentContent);
|
|
440
|
-
currentContent = { ...contents[i], parts: [...(contents[i].parts || [])] };
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
result.push(currentContent);
|
|
444
|
-
return result;
|
|
445
|
-
}
|
|
446
|
-
const supportedFinishReasons = [
|
|
447
|
-
FinishReason.MAX_TOKENS,
|
|
448
|
-
FinishReason.STOP,
|
|
449
|
-
FinishReason.FINISH_REASON_UNSPECIFIED,
|
|
450
|
-
];
|
|
451
|
-
function geminiMaxTokens(option) {
|
|
452
|
-
const model_options = option.model_options;
|
|
453
|
-
if (model_options?.max_tokens) {
|
|
454
|
-
return model_options.max_tokens;
|
|
455
|
-
}
|
|
456
|
-
if (option.model.includes("gemini-2.5")) {
|
|
457
|
-
const maxSupportedTokens = getMaxTokensLimitVertexAi(option.model);
|
|
458
|
-
const thinkingBudget = geminiThinkingBudget(option) ?? 0;
|
|
459
|
-
return Math.min(maxSupportedTokens, 16000 + thinkingBudget);
|
|
460
|
-
}
|
|
461
|
-
return undefined;
|
|
462
|
-
}
|
|
463
|
-
function geminiThinkingBudget(option) {
|
|
464
|
-
const model_options = option.model_options;
|
|
465
|
-
if (model_options?.thinking_budget_tokens) {
|
|
466
|
-
return model_options.thinking_budget_tokens;
|
|
467
|
-
}
|
|
468
|
-
// Set minimum thinking level by default.
|
|
469
|
-
// Docs: https://ai.google.dev/gemini-api/docs/thinking#set-budget
|
|
470
|
-
if (option.model.includes("gemini-2.5")) {
|
|
471
|
-
if (option.model.includes("pro")) {
|
|
472
|
-
return 128;
|
|
473
|
-
}
|
|
474
|
-
return 0;
|
|
475
|
-
}
|
|
476
|
-
return undefined;
|
|
477
|
-
}
|
|
478
|
-
export class GeminiModelDefinition {
|
|
479
|
-
model;
|
|
480
|
-
constructor(modelId) {
|
|
481
|
-
this.model = {
|
|
482
|
-
id: modelId,
|
|
483
|
-
name: modelId,
|
|
484
|
-
provider: 'vertexai',
|
|
485
|
-
type: ModelType.Text,
|
|
486
|
-
can_stream: true
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
preValidationProcessing(result, options) {
|
|
490
|
-
// Guard clause, if no result_schema, error, or tool use, skip processing
|
|
491
|
-
if (!options.result_schema || !result.result || result.tool_use || result.error) {
|
|
492
|
-
return { result, options };
|
|
493
|
-
}
|
|
494
|
-
try {
|
|
495
|
-
// Extract text content for JSON processing - only process first text result
|
|
496
|
-
const textResult = result.result.find(r => r.type === 'text')?.value;
|
|
497
|
-
if (textResult) {
|
|
498
|
-
const jsonResult = JSON.parse(textResult);
|
|
499
|
-
const cleanedJson = JSON.stringify(removeEmptyFields(jsonResult, options.result_schema));
|
|
500
|
-
// Replace the text result with cleaned version
|
|
501
|
-
result.result = result.result.map(r => r.type === 'text' ? { ...r, value: cleanedJson } : r);
|
|
502
|
-
}
|
|
503
|
-
return { result, options };
|
|
504
|
-
}
|
|
505
|
-
catch (error) {
|
|
506
|
-
// Log error during processing but don't fail the completion
|
|
507
|
-
console.warn('Error during Gemini JSON pre-validation: ', error);
|
|
508
|
-
// Return original result if cleanup fails
|
|
509
|
-
return { result, options };
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
async createPrompt(_driver, segments, options) {
|
|
513
|
-
const splits = options.model.split("/");
|
|
514
|
-
const modelName = splits[splits.length - 1];
|
|
515
|
-
options = { ...options, model: modelName };
|
|
516
|
-
const schema = options.result_schema;
|
|
517
|
-
let contents = [];
|
|
518
|
-
let system = { role: "user", parts: [] }; // Single content block for system messages
|
|
519
|
-
const safety = [];
|
|
520
|
-
for (const msg of segments) {
|
|
521
|
-
// Role specific handling
|
|
522
|
-
if (msg.role === PromptRole.system) {
|
|
523
|
-
// Text only for system messages
|
|
524
|
-
if (msg.files && msg.files.length > 0) {
|
|
525
|
-
throw new Error("Gemini does not support files/images etc. in system messages. Only text content is allowed.");
|
|
526
|
-
}
|
|
527
|
-
if (msg.content) {
|
|
528
|
-
system.parts?.push({
|
|
529
|
-
text: msg.content
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
else if (msg.role === PromptRole.tool) {
|
|
534
|
-
if (!msg.tool_use_id) {
|
|
535
|
-
throw new Error("Tool response missing tool_use_id");
|
|
536
|
-
}
|
|
537
|
-
contents.push({
|
|
538
|
-
role: 'user',
|
|
539
|
-
parts: [
|
|
540
|
-
{
|
|
541
|
-
functionResponse: {
|
|
542
|
-
name: msg.tool_use_id,
|
|
543
|
-
response: formatFunctionResponse(msg.content || ''),
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
]
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
else { // PromptRole.user, PromptRole.assistant, PromptRole.safety
|
|
550
|
-
const parts = [];
|
|
551
|
-
// Text content handling
|
|
552
|
-
if (msg.content) {
|
|
553
|
-
parts.push({
|
|
554
|
-
text: msg.content,
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
// File content handling
|
|
558
|
-
if (msg.files) {
|
|
559
|
-
for (const f of msg.files) {
|
|
560
|
-
const stream = await f.getStream();
|
|
561
|
-
const data = await readStreamAsBase64(stream);
|
|
562
|
-
parts.push({
|
|
563
|
-
inlineData: {
|
|
564
|
-
data,
|
|
565
|
-
mimeType: f.mime_type
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
if (parts.length > 0) {
|
|
571
|
-
if (msg.role === PromptRole.safety) {
|
|
572
|
-
safety.push({
|
|
573
|
-
role: 'user',
|
|
574
|
-
parts,
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
else {
|
|
578
|
-
contents.push({
|
|
579
|
-
role: msg.role === PromptRole.assistant ? 'model' : 'user',
|
|
580
|
-
parts,
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
// Adding JSON Schema to system message
|
|
587
|
-
if (schema) {
|
|
588
|
-
if (supportsStructuredOutput(options) && !options.tools) {
|
|
589
|
-
// Gemini structured output is unnecessarily sparse. Adding encouragement to fill the fields.
|
|
590
|
-
// Putting JSON in prompt is not recommended by Google, when using structured output.
|
|
591
|
-
system.parts?.push({ text: "Fill all appropriate fields in the JSON output." });
|
|
592
|
-
}
|
|
593
|
-
else {
|
|
594
|
-
// Fallback to putting the schema in the system instructions, if not using structured output.
|
|
595
|
-
if (options.tools) {
|
|
596
|
-
system.parts?.push({
|
|
597
|
-
text: "When not calling tools, the output must be a JSON object using the following JSON Schema:\n" + JSON.stringify(schema)
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
else {
|
|
601
|
-
system.parts?.push({ text: "The output must be a JSON object using the following JSON Schema:\n" + JSON.stringify(schema) });
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
// If no system messages, set system to undefined.
|
|
606
|
-
if (!system.parts || system.parts.length === 0) {
|
|
607
|
-
system = undefined;
|
|
608
|
-
}
|
|
609
|
-
// Add safety messages to the end of contents. They are in effect user messages that come at the end.
|
|
610
|
-
if (safety.length > 0) {
|
|
611
|
-
contents = contents.concat(safety);
|
|
612
|
-
}
|
|
613
|
-
// Merge consecutive messages with the same role. Note: this may not be necessary, works without it, keeping to match previous behavior.
|
|
614
|
-
contents = mergeConsecutiveRole(contents);
|
|
615
|
-
return { contents, system };
|
|
616
|
-
}
|
|
617
|
-
usageMetadataToTokenUsage(usageMetadata) {
|
|
618
|
-
if (!usageMetadata || !usageMetadata.totalTokenCount) {
|
|
619
|
-
return {};
|
|
620
|
-
}
|
|
621
|
-
const tokenUsage = { total: usageMetadata.totalTokenCount, prompt: usageMetadata.promptTokenCount };
|
|
622
|
-
//Output/Response side
|
|
623
|
-
tokenUsage.result = (usageMetadata.candidatesTokenCount ?? 0)
|
|
624
|
-
+ (usageMetadata.thoughtsTokenCount ?? 0)
|
|
625
|
-
+ (usageMetadata.toolUsePromptTokenCount ?? 0);
|
|
626
|
-
if ((tokenUsage.total ?? 0) != (tokenUsage.prompt ?? 0) + tokenUsage.result) {
|
|
627
|
-
console.warn("[VertexAI] Gemini token usage mismatch: total does not equal prompt + result", {
|
|
628
|
-
total: tokenUsage.total,
|
|
629
|
-
prompt: tokenUsage.prompt,
|
|
630
|
-
result: tokenUsage.result
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
if (!tokenUsage.result) {
|
|
634
|
-
tokenUsage.result = undefined; // If no result, mark as undefined
|
|
635
|
-
}
|
|
636
|
-
return tokenUsage;
|
|
637
|
-
}
|
|
638
|
-
async requestTextCompletion(driver, prompt, options) {
|
|
639
|
-
const splits = options.model.split("/");
|
|
640
|
-
let region = undefined;
|
|
641
|
-
if (splits[0] === "locations" && splits.length >= 2) {
|
|
642
|
-
region = splits[1];
|
|
643
|
-
}
|
|
644
|
-
const modelName = splits[splits.length - 1];
|
|
645
|
-
options = { ...options, model: modelName };
|
|
646
|
-
let conversation = updateConversation(options.conversation, prompt.contents);
|
|
647
|
-
prompt.contents = conversation;
|
|
648
|
-
if (options.model.includes("gemini-2.5-flash-image")) {
|
|
649
|
-
region = "global"; // Gemini Flash Image only available in global region, this is for nano-banana model
|
|
650
|
-
}
|
|
651
|
-
const client = driver.getGoogleGenAIClient(region);
|
|
652
|
-
const payload = getGeminiPayload(options, prompt);
|
|
653
|
-
const response = await client.models.generateContent(payload);
|
|
654
|
-
const token_usage = this.usageMetadataToTokenUsage(response.usageMetadata);
|
|
655
|
-
let tool_use;
|
|
656
|
-
let finish_reason, result;
|
|
657
|
-
const candidate = response.candidates && response.candidates[0];
|
|
658
|
-
if (candidate) {
|
|
659
|
-
switch (candidate.finishReason) {
|
|
660
|
-
case FinishReason.MAX_TOKENS:
|
|
661
|
-
finish_reason = "length";
|
|
662
|
-
break;
|
|
663
|
-
case FinishReason.STOP:
|
|
664
|
-
finish_reason = "stop";
|
|
665
|
-
break;
|
|
666
|
-
default: finish_reason = candidate.finishReason;
|
|
667
|
-
}
|
|
668
|
-
const content = candidate.content;
|
|
669
|
-
if (candidate.finishReason && !supportedFinishReasons.includes(candidate.finishReason)) {
|
|
670
|
-
throw new Error(`Unsupported finish reason: ${candidate.finishReason}, `
|
|
671
|
-
+ `finish message: ${candidate.finishMessage}, `
|
|
672
|
-
+ `content: ${JSON.stringify(content, null, 2)}, safety: ${JSON.stringify(candidate.safetyRatings, null, 2)}`);
|
|
673
|
-
}
|
|
674
|
-
if (content) {
|
|
675
|
-
tool_use = collectToolUseParts(content);
|
|
676
|
-
// We clean the content before validation, so we can update the conversation.
|
|
677
|
-
const cleanedContent = cleanEmptyFieldsContent(content, options.result_schema);
|
|
678
|
-
const textResults = collectTextParts(cleanedContent);
|
|
679
|
-
const imageResults = collectInlineDataParts(cleanedContent);
|
|
680
|
-
result = [...textResults, ...imageResults];
|
|
681
|
-
conversation = updateConversation(conversation, [cleanedContent]);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
if (tool_use) {
|
|
685
|
-
finish_reason = "tool_use";
|
|
686
|
-
}
|
|
687
|
-
return {
|
|
688
|
-
result: result && result.length > 0 ? result : [{ type: "text", value: '' }],
|
|
689
|
-
token_usage: token_usage,
|
|
690
|
-
finish_reason: finish_reason,
|
|
691
|
-
original_response: options.include_original_response ? response : undefined,
|
|
692
|
-
conversation,
|
|
693
|
-
tool_use
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
async requestTextCompletionStream(driver, prompt, options) {
|
|
697
|
-
const splits = options.model.split("/");
|
|
698
|
-
let region = undefined;
|
|
699
|
-
if (splits[0] === "locations" && splits.length >= 2) {
|
|
700
|
-
region = splits[1];
|
|
701
|
-
}
|
|
702
|
-
const modelName = splits[splits.length - 1];
|
|
703
|
-
options = { ...options, model: modelName };
|
|
704
|
-
if (options.model.includes("gemini-2.5-flash-image")) {
|
|
705
|
-
region = "global"; // Gemini Flash Image only available in global region, this is for nano-banana model
|
|
706
|
-
}
|
|
707
|
-
const client = driver.getGoogleGenAIClient(region);
|
|
708
|
-
const payload = getGeminiPayload(options, prompt);
|
|
709
|
-
const response = await client.models.generateContentStream(payload);
|
|
710
|
-
const stream = asyncMap(response, async (item) => {
|
|
711
|
-
const token_usage = this.usageMetadataToTokenUsage(item.usageMetadata);
|
|
712
|
-
if (item.candidates && item.candidates.length > 0) {
|
|
713
|
-
for (const candidate of item.candidates) {
|
|
714
|
-
let tool_use;
|
|
715
|
-
let finish_reason;
|
|
716
|
-
switch (candidate.finishReason) {
|
|
717
|
-
case FinishReason.MAX_TOKENS:
|
|
718
|
-
finish_reason = "length";
|
|
719
|
-
break;
|
|
720
|
-
case FinishReason.STOP:
|
|
721
|
-
finish_reason = "stop";
|
|
722
|
-
break;
|
|
723
|
-
default: finish_reason = candidate.finishReason;
|
|
724
|
-
}
|
|
725
|
-
if (candidate.finishReason && !supportedFinishReasons.includes(candidate.finishReason)) {
|
|
726
|
-
throw new Error(`Unsupported finish reason: ${candidate.finishReason}, `
|
|
727
|
-
+ `finish message: ${candidate.finishMessage}, `
|
|
728
|
-
+ `content: ${JSON.stringify(candidate.content, null, 2)}, safety: ${JSON.stringify(candidate.safetyRatings, null, 2)}`);
|
|
729
|
-
}
|
|
730
|
-
if (candidate.content?.role === 'model') {
|
|
731
|
-
const textResults = collectTextParts(candidate.content);
|
|
732
|
-
const imageResults = collectInlineDataParts(candidate.content);
|
|
733
|
-
const combinedResults = [...textResults, ...imageResults];
|
|
734
|
-
tool_use = collectToolUseParts(candidate.content);
|
|
735
|
-
if (tool_use) {
|
|
736
|
-
finish_reason = "tool_use";
|
|
737
|
-
}
|
|
738
|
-
return {
|
|
739
|
-
result: combinedResults.length > 0 ? combinedResults : [],
|
|
740
|
-
token_usage: token_usage,
|
|
741
|
-
finish_reason: finish_reason,
|
|
742
|
-
tool_use,
|
|
743
|
-
};
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
//No normal output, returning block reason if it exists.
|
|
748
|
-
return {
|
|
749
|
-
result: item.promptFeedback?.blockReasonMessage ? [{ type: "text", value: item.promptFeedback.blockReasonMessage }] : [],
|
|
750
|
-
finish_reason: item.promptFeedback?.blockReason ?? "",
|
|
751
|
-
token_usage: token_usage,
|
|
752
|
-
};
|
|
753
|
-
});
|
|
754
|
-
return stream;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
function getToolDefinitions(tools) {
|
|
758
|
-
if (!tools || tools.length === 0) {
|
|
759
|
-
return undefined;
|
|
760
|
-
}
|
|
761
|
-
// VertexAI Gemini only supports one tool at a time.
|
|
762
|
-
// For multiple tools, we have multiple functions in one tool.
|
|
763
|
-
return {
|
|
764
|
-
functionDeclarations: tools.map(getToolFunction),
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
function getToolFunction(tool) {
|
|
768
|
-
// If input_schema is a string, parse it; if it's already an object, use it directly
|
|
769
|
-
let toolSchema;
|
|
770
|
-
// Using a try-catch for safety, as the input_schema might not be a valid JSONSchema
|
|
771
|
-
try {
|
|
772
|
-
toolSchema = parseJSONtoSchema(tool.input_schema, false);
|
|
773
|
-
}
|
|
774
|
-
catch (e) {
|
|
775
|
-
toolSchema = { ...tool.input_schema, type: Type.OBJECT };
|
|
776
|
-
}
|
|
777
|
-
return {
|
|
778
|
-
name: tool.name,
|
|
779
|
-
description: tool.description,
|
|
780
|
-
parameters: toolSchema,
|
|
781
|
-
};
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* Update the conversation messages
|
|
785
|
-
* @param prompt
|
|
786
|
-
* @param response
|
|
787
|
-
* @returns
|
|
788
|
-
*/
|
|
789
|
-
function updateConversation(conversation, prompt) {
|
|
790
|
-
return (conversation || []).concat(prompt);
|
|
791
|
-
}
|
|
792
|
-
/**
|
|
793
|
-
*
|
|
794
|
-
* Gemini supports JSON output in the response. so we test if the response is a valid JSON object. otherwise we treat the response as a string.
|
|
795
|
-
*
|
|
796
|
-
* This is an excerpt from googleapis.github.io/python-genai:
|
|
797
|
-
*
|
|
798
|
-
* The function response in JSON object format.
|
|
799
|
-
* Use “output” key to specify function output and “error” key to specify error details (if any).
|
|
800
|
-
* If “output” and “error” keys are not specified, then whole “response” is treated as function output.
|
|
801
|
-
* @see https://googleapis.github.io/python-genai/genai.html#genai.types.FunctionResponse
|
|
802
|
-
*/
|
|
803
|
-
function formatFunctionResponse(response) {
|
|
804
|
-
response = response.trim();
|
|
805
|
-
if (response.startsWith("{") && response.endsWith("}")) {
|
|
806
|
-
try {
|
|
807
|
-
return JSON.parse(response);
|
|
808
|
-
}
|
|
809
|
-
catch (e) {
|
|
810
|
-
return { output: response };
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
else {
|
|
814
|
-
return { output: response };
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
//# sourceMappingURL=gemini.js.map
|