@llumiverse/drivers 0.23.0 → 0.24.0-dev.20260203.164053Z
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/README.md +141 -218
- package/package.json +12 -12
- package/src/azure/azure_foundry.ts +56 -7
- package/src/bedrock/index.ts +188 -24
- package/src/groq/index.ts +120 -94
- package/src/index.ts +1 -0
- package/src/openai/index.ts +363 -136
- package/src/openai/openai_compatible.ts +74 -0
- package/src/openai/openai_format.ts +44 -54
- package/src/vertexai/index.ts +186 -0
- package/src/vertexai/models/claude.ts +97 -2
- package/src/vertexai/models/gemini.ts +78 -27
- package/src/xai/index.ts +10 -17
- package/lib/cjs/adobe/firefly.js +0 -120
- package/lib/cjs/adobe/firefly.js.map +0 -1
- package/lib/cjs/azure/azure_foundry.js +0 -388
- package/lib/cjs/azure/azure_foundry.js.map +0 -1
- package/lib/cjs/bedrock/converse.js +0 -285
- package/lib/cjs/bedrock/converse.js.map +0 -1
- package/lib/cjs/bedrock/index.js +0 -966
- package/lib/cjs/bedrock/index.js.map +0 -1
- package/lib/cjs/bedrock/nova-image-payload.js +0 -207
- package/lib/cjs/bedrock/nova-image-payload.js.map +0 -1
- package/lib/cjs/bedrock/payloads.js +0 -3
- package/lib/cjs/bedrock/payloads.js.map +0 -1
- package/lib/cjs/bedrock/s3.js +0 -107
- package/lib/cjs/bedrock/s3.js.map +0 -1
- package/lib/cjs/bedrock/twelvelabs.js +0 -87
- package/lib/cjs/bedrock/twelvelabs.js.map +0 -1
- package/lib/cjs/groq/index.js +0 -293
- package/lib/cjs/groq/index.js.map +0 -1
- package/lib/cjs/huggingface_ie.js +0 -201
- package/lib/cjs/huggingface_ie.js.map +0 -1
- package/lib/cjs/index.js +0 -30
- package/lib/cjs/index.js.map +0 -1
- package/lib/cjs/mistral/index.js +0 -173
- package/lib/cjs/mistral/index.js.map +0 -1
- package/lib/cjs/mistral/types.js +0 -83
- package/lib/cjs/mistral/types.js.map +0 -1
- package/lib/cjs/openai/azure_openai.js +0 -72
- package/lib/cjs/openai/azure_openai.js.map +0 -1
- package/lib/cjs/openai/index.js +0 -469
- package/lib/cjs/openai/index.js.map +0 -1
- package/lib/cjs/openai/openai.js +0 -21
- package/lib/cjs/openai/openai.js.map +0 -1
- package/lib/cjs/openai/openai_format.js +0 -138
- package/lib/cjs/openai/openai_format.js.map +0 -1
- package/lib/cjs/package.json +0 -3
- package/lib/cjs/replicate.js +0 -275
- package/lib/cjs/replicate.js.map +0 -1
- 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/cjs/togetherai/index.js +0 -126
- package/lib/cjs/togetherai/index.js.map +0 -1
- package/lib/cjs/togetherai/interfaces.js +0 -3
- package/lib/cjs/togetherai/interfaces.js.map +0 -1
- package/lib/cjs/vertexai/debug.js +0 -12
- package/lib/cjs/vertexai/debug.js.map +0 -1
- package/lib/cjs/vertexai/embeddings/embeddings-image.js +0 -27
- package/lib/cjs/vertexai/embeddings/embeddings-image.js.map +0 -1
- package/lib/cjs/vertexai/embeddings/embeddings-text.js +0 -23
- package/lib/cjs/vertexai/embeddings/embeddings-text.js.map +0 -1
- package/lib/cjs/vertexai/index.js +0 -429
- package/lib/cjs/vertexai/index.js.map +0 -1
- package/lib/cjs/vertexai/models/claude.js +0 -399
- package/lib/cjs/vertexai/models/claude.js.map +0 -1
- package/lib/cjs/vertexai/models/gemini.js +0 -832
- package/lib/cjs/vertexai/models/gemini.js.map +0 -1
- package/lib/cjs/vertexai/models/imagen.js +0 -303
- package/lib/cjs/vertexai/models/imagen.js.map +0 -1
- package/lib/cjs/vertexai/models/llama.js +0 -183
- package/lib/cjs/vertexai/models/llama.js.map +0 -1
- package/lib/cjs/vertexai/models.js +0 -35
- package/lib/cjs/vertexai/models.js.map +0 -1
- package/lib/cjs/watsonx/index.js +0 -161
- package/lib/cjs/watsonx/index.js.map +0 -1
- package/lib/cjs/watsonx/interfaces.js +0 -3
- package/lib/cjs/watsonx/interfaces.js.map +0 -1
- package/lib/cjs/xai/index.js +0 -71
- package/lib/cjs/xai/index.js.map +0 -1
- package/lib/esm/adobe/firefly.js +0 -116
- package/lib/esm/adobe/firefly.js.map +0 -1
- package/lib/esm/azure/azure_foundry.js +0 -382
- package/lib/esm/azure/azure_foundry.js.map +0 -1
- package/lib/esm/bedrock/converse.js +0 -278
- package/lib/esm/bedrock/converse.js.map +0 -1
- package/lib/esm/bedrock/index.js +0 -962
- package/lib/esm/bedrock/index.js.map +0 -1
- package/lib/esm/bedrock/nova-image-payload.js +0 -203
- package/lib/esm/bedrock/nova-image-payload.js.map +0 -1
- package/lib/esm/bedrock/payloads.js +0 -2
- package/lib/esm/bedrock/payloads.js.map +0 -1
- package/lib/esm/bedrock/s3.js +0 -99
- package/lib/esm/bedrock/s3.js.map +0 -1
- package/lib/esm/bedrock/twelvelabs.js +0 -84
- package/lib/esm/bedrock/twelvelabs.js.map +0 -1
- package/lib/esm/groq/index.js +0 -286
- package/lib/esm/groq/index.js.map +0 -1
- package/lib/esm/huggingface_ie.js +0 -197
- package/lib/esm/huggingface_ie.js.map +0 -1
- package/lib/esm/index.js +0 -14
- package/lib/esm/index.js.map +0 -1
- package/lib/esm/mistral/index.js +0 -169
- package/lib/esm/mistral/index.js.map +0 -1
- package/lib/esm/mistral/types.js +0 -80
- package/lib/esm/mistral/types.js.map +0 -1
- package/lib/esm/openai/azure_openai.js +0 -68
- package/lib/esm/openai/azure_openai.js.map +0 -1
- package/lib/esm/openai/index.js +0 -464
- package/lib/esm/openai/index.js.map +0 -1
- package/lib/esm/openai/openai.js +0 -14
- package/lib/esm/openai/openai.js.map +0 -1
- package/lib/esm/openai/openai_format.js +0 -134
- package/lib/esm/openai/openai_format.js.map +0 -1
- package/lib/esm/replicate.js +0 -268
- package/lib/esm/replicate.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/togetherai/index.js +0 -122
- package/lib/esm/togetherai/index.js.map +0 -1
- package/lib/esm/togetherai/interfaces.js +0 -2
- package/lib/esm/togetherai/interfaces.js.map +0 -1
- package/lib/esm/vertexai/debug.js +0 -6
- package/lib/esm/vertexai/debug.js.map +0 -1
- package/lib/esm/vertexai/embeddings/embeddings-image.js +0 -24
- package/lib/esm/vertexai/embeddings/embeddings-image.js.map +0 -1
- package/lib/esm/vertexai/embeddings/embeddings-text.js +0 -20
- package/lib/esm/vertexai/embeddings/embeddings-text.js.map +0 -1
- package/lib/esm/vertexai/index.js +0 -424
- package/lib/esm/vertexai/index.js.map +0 -1
- package/lib/esm/vertexai/models/claude.js +0 -394
- package/lib/esm/vertexai/models/claude.js.map +0 -1
- package/lib/esm/vertexai/models/gemini.js +0 -827
- package/lib/esm/vertexai/models/gemini.js.map +0 -1
- package/lib/esm/vertexai/models/imagen.js +0 -299
- package/lib/esm/vertexai/models/imagen.js.map +0 -1
- package/lib/esm/vertexai/models/llama.js +0 -179
- package/lib/esm/vertexai/models/llama.js.map +0 -1
- package/lib/esm/vertexai/models.js +0 -32
- package/lib/esm/vertexai/models.js.map +0 -1
- package/lib/esm/watsonx/index.js +0 -157
- package/lib/esm/watsonx/index.js.map +0 -1
- package/lib/esm/watsonx/interfaces.js +0 -2
- package/lib/esm/watsonx/interfaces.js.map +0 -1
- package/lib/esm/xai/index.js +0 -64
- package/lib/esm/xai/index.js.map +0 -1
- package/lib/types/adobe/firefly.d.ts +0 -30
- package/lib/types/adobe/firefly.d.ts.map +0 -1
- package/lib/types/azure/azure_foundry.d.ts +0 -50
- package/lib/types/azure/azure_foundry.d.ts.map +0 -1
- package/lib/types/bedrock/converse.d.ts +0 -9
- package/lib/types/bedrock/converse.d.ts.map +0 -1
- package/lib/types/bedrock/index.d.ts +0 -63
- package/lib/types/bedrock/index.d.ts.map +0 -1
- package/lib/types/bedrock/nova-image-payload.d.ts +0 -74
- package/lib/types/bedrock/nova-image-payload.d.ts.map +0 -1
- package/lib/types/bedrock/payloads.d.ts +0 -12
- package/lib/types/bedrock/payloads.d.ts.map +0 -1
- package/lib/types/bedrock/s3.d.ts +0 -23
- package/lib/types/bedrock/s3.d.ts.map +0 -1
- package/lib/types/bedrock/twelvelabs.d.ts +0 -50
- package/lib/types/bedrock/twelvelabs.d.ts.map +0 -1
- package/lib/types/groq/index.d.ts +0 -27
- package/lib/types/groq/index.d.ts.map +0 -1
- package/lib/types/huggingface_ie.d.ts +0 -35
- package/lib/types/huggingface_ie.d.ts.map +0 -1
- package/lib/types/index.d.ts +0 -14
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/mistral/index.d.ts +0 -25
- package/lib/types/mistral/index.d.ts.map +0 -1
- package/lib/types/mistral/types.d.ts +0 -132
- package/lib/types/mistral/types.d.ts.map +0 -1
- package/lib/types/openai/azure_openai.d.ts +0 -25
- package/lib/types/openai/azure_openai.d.ts.map +0 -1
- package/lib/types/openai/index.d.ts +0 -25
- package/lib/types/openai/index.d.ts.map +0 -1
- package/lib/types/openai/openai.d.ts +0 -15
- package/lib/types/openai/openai.d.ts.map +0 -1
- package/lib/types/openai/openai_format.d.ts +0 -19
- package/lib/types/openai/openai_format.d.ts.map +0 -1
- package/lib/types/replicate.d.ts +0 -48
- package/lib/types/replicate.d.ts.map +0 -1
- 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
- package/lib/types/togetherai/index.d.ts +0 -23
- package/lib/types/togetherai/index.d.ts.map +0 -1
- package/lib/types/togetherai/interfaces.d.ts +0 -96
- package/lib/types/togetherai/interfaces.d.ts.map +0 -1
- package/lib/types/vertexai/debug.d.ts +0 -2
- package/lib/types/vertexai/debug.d.ts.map +0 -1
- package/lib/types/vertexai/embeddings/embeddings-image.d.ts +0 -11
- package/lib/types/vertexai/embeddings/embeddings-image.d.ts.map +0 -1
- package/lib/types/vertexai/embeddings/embeddings-text.d.ts +0 -10
- package/lib/types/vertexai/embeddings/embeddings-text.d.ts.map +0 -1
- package/lib/types/vertexai/index.d.ts +0 -54
- package/lib/types/vertexai/index.d.ts.map +0 -1
- package/lib/types/vertexai/models/claude.d.ts +0 -20
- package/lib/types/vertexai/models/claude.d.ts.map +0 -1
- package/lib/types/vertexai/models/gemini.d.ts +0 -18
- package/lib/types/vertexai/models/gemini.d.ts.map +0 -1
- package/lib/types/vertexai/models/imagen.d.ts +0 -75
- package/lib/types/vertexai/models/imagen.d.ts.map +0 -1
- package/lib/types/vertexai/models/llama.d.ts +0 -20
- package/lib/types/vertexai/models/llama.d.ts.map +0 -1
- package/lib/types/vertexai/models.d.ts +0 -15
- package/lib/types/vertexai/models.d.ts.map +0 -1
- package/lib/types/watsonx/index.d.ts +0 -27
- package/lib/types/watsonx/index.d.ts.map +0 -1
- package/lib/types/watsonx/interfaces.d.ts +0 -65
- package/lib/types/watsonx/interfaces.d.ts.map +0 -1
- package/lib/types/xai/index.d.ts +0 -19
- package/lib/types/xai/index.d.ts.map +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { AIModel, DriverOptions, ModelType, Providers, getModelCapabilities, modelModalitiesToArray } from "@llumiverse/core";
|
|
2
|
+
import OpenAI from "openai";
|
|
3
|
+
import { BaseOpenAIDriver } from "./index.js";
|
|
4
|
+
|
|
5
|
+
export interface OpenAICompatibleDriverOptions extends DriverOptions {
|
|
6
|
+
/**
|
|
7
|
+
* The API key for the OpenAI-compatible service
|
|
8
|
+
*/
|
|
9
|
+
apiKey: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The base URL of the OpenAI-compatible API endpoint
|
|
13
|
+
* Example: https://api.example.com/v1
|
|
14
|
+
*/
|
|
15
|
+
endpoint: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A generic driver for OpenAI-compatible APIs.
|
|
20
|
+
* This can be used with any service that implements the OpenAI API spec,
|
|
21
|
+
* such as xAI (Grok), LM Studio, Ollama, vLLM, LocalAI, etc.
|
|
22
|
+
*/
|
|
23
|
+
export class OpenAICompatibleDriver extends BaseOpenAIDriver {
|
|
24
|
+
service: OpenAI;
|
|
25
|
+
readonly provider = Providers.openai_compatible;
|
|
26
|
+
|
|
27
|
+
constructor(opts: OpenAICompatibleDriverOptions) {
|
|
28
|
+
super(opts);
|
|
29
|
+
|
|
30
|
+
if (!opts.apiKey) {
|
|
31
|
+
throw new Error("apiKey is required");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!opts.endpoint) {
|
|
35
|
+
throw new Error("endpoint is required for OpenAI-compatible driver");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.service = new OpenAI({
|
|
39
|
+
apiKey: opts.apiKey,
|
|
40
|
+
baseURL: opts.endpoint,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async listModels(): Promise<AIModel[]> {
|
|
45
|
+
try {
|
|
46
|
+
const result = (await this.service.models.list()).data;
|
|
47
|
+
|
|
48
|
+
const models = result.map((m) => {
|
|
49
|
+
const modelCapability = getModelCapabilities(m.id, "openai");
|
|
50
|
+
let owner = m.owned_by;
|
|
51
|
+
if (owner === "system") {
|
|
52
|
+
owner = "unknown";
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
id: m.id,
|
|
56
|
+
name: m.id,
|
|
57
|
+
provider: this.provider,
|
|
58
|
+
owner: owner,
|
|
59
|
+
type: ModelType.Text,
|
|
60
|
+
can_stream: true,
|
|
61
|
+
is_multimodal: false,
|
|
62
|
+
input_modalities: modelModalitiesToArray(modelCapability.input),
|
|
63
|
+
output_modalities: modelModalitiesToArray(modelCapability.output),
|
|
64
|
+
tool_support: modelCapability.tool_support,
|
|
65
|
+
} satisfies AIModel<string>;
|
|
66
|
+
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
67
|
+
|
|
68
|
+
return models;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
this.logger.warn({ error }, "[OpenAICompatible] Failed to list models, returning empty list");
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -3,16 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
import { PromptRole, PromptOptions, PromptSegment } from "@llumiverse/common";
|
|
5
5
|
import { readStreamAsBase64 } from "@llumiverse/core";
|
|
6
|
+
import type OpenAI from "openai";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
ChatCompletionUserMessageParam,
|
|
12
|
-
ChatCompletionSystemMessageParam,
|
|
13
|
-
ChatCompletionAssistantMessageParam,
|
|
14
|
-
ChatCompletionToolMessageParam
|
|
15
|
-
} from 'openai/resources/chat/completions';
|
|
8
|
+
// Types for Response API
|
|
9
|
+
type ResponseInputItem = OpenAI.Responses.ResponseInputItem;
|
|
10
|
+
type ResponseInputContent = OpenAI.Responses.ResponseInputContent;
|
|
11
|
+
type EasyInputMessage = OpenAI.Responses.EasyInputMessage;
|
|
16
12
|
|
|
17
13
|
export interface OpenAITextMessage {
|
|
18
14
|
content: string;
|
|
@@ -47,14 +43,14 @@ export function formatOpenAILikeTextPrompt(segments: PromptSegment[]): OpenAITex
|
|
|
47
43
|
}
|
|
48
44
|
|
|
49
45
|
|
|
50
|
-
export async function formatOpenAILikeMultimodalPrompt(segments: PromptSegment[], opts: PromptOptions & OpenAIPromptFormatterOptions): Promise<
|
|
51
|
-
const system:
|
|
52
|
-
const safety:
|
|
53
|
-
const others:
|
|
46
|
+
export async function formatOpenAILikeMultimodalPrompt(segments: PromptSegment[], opts: PromptOptions & OpenAIPromptFormatterOptions): Promise<ResponseInputItem[]> {
|
|
47
|
+
const system: ResponseInputItem[] = [];
|
|
48
|
+
const safety: ResponseInputItem[] = [];
|
|
49
|
+
const others: ResponseInputItem[] = [];
|
|
54
50
|
|
|
55
51
|
for (const msg of segments) {
|
|
56
52
|
|
|
57
|
-
const parts:
|
|
53
|
+
const parts: ResponseInputContent[] = [];
|
|
58
54
|
|
|
59
55
|
//generate the parts based on PromptSegment
|
|
60
56
|
if (msg.files) {
|
|
@@ -62,54 +58,56 @@ export async function formatOpenAILikeMultimodalPrompt(segments: PromptSegment[]
|
|
|
62
58
|
const stream = await file.getStream();
|
|
63
59
|
const data = await readStreamAsBase64(stream);
|
|
64
60
|
parts.push({
|
|
65
|
-
type: "
|
|
66
|
-
image_url: {
|
|
67
|
-
|
|
68
|
-
//detail: "auto" //This is modified just before execution to "low" | "high" | "auto"
|
|
69
|
-
},
|
|
61
|
+
type: "input_image",
|
|
62
|
+
image_url: `data:${file.mime_type || "image/jpeg"};base64,${data}`,
|
|
63
|
+
detail: "auto",
|
|
70
64
|
})
|
|
71
65
|
}
|
|
72
66
|
}
|
|
73
67
|
|
|
74
68
|
if (msg.content) {
|
|
75
69
|
parts.push({
|
|
70
|
+
type: "input_text",
|
|
76
71
|
text: msg.content,
|
|
77
|
-
type: "text"
|
|
78
72
|
})
|
|
79
73
|
}
|
|
80
74
|
|
|
81
75
|
|
|
82
76
|
if (msg.role === PromptRole.system) {
|
|
83
77
|
// For system messages, filter to only text parts
|
|
84
|
-
const textParts = parts.filter((part): part is
|
|
85
|
-
const
|
|
78
|
+
const textParts = parts.filter((part): part is OpenAI.Responses.ResponseInputText => part.type === 'input_text');
|
|
79
|
+
const textContent = textParts.length === 1 && !msg.files ? textParts[0].text : textParts;
|
|
80
|
+
const systemMsg: EasyInputMessage = {
|
|
86
81
|
role: "system",
|
|
87
|
-
content:
|
|
82
|
+
content: textContent,
|
|
88
83
|
};
|
|
89
84
|
system.push(systemMsg);
|
|
90
85
|
|
|
91
86
|
if (opts.useToolForFormatting && opts.schema) {
|
|
92
87
|
system.forEach(s => {
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
88
|
+
if ((s as EasyInputMessage).role === 'system') {
|
|
89
|
+
const sysMsg = s as EasyInputMessage;
|
|
90
|
+
if (typeof sysMsg.content === 'string') {
|
|
91
|
+
sysMsg.content = "TOOL: " + sysMsg.content;
|
|
92
|
+
} else if (Array.isArray(sysMsg.content)) {
|
|
93
|
+
sysMsg.content.forEach((c: any) => {
|
|
94
|
+
if (c.type === "input_text") c.text = "TOOL: " + c.text;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
99
97
|
}
|
|
100
98
|
});
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
} else if (msg.role === PromptRole.safety) {
|
|
104
|
-
const textParts = parts.filter((part): part is
|
|
105
|
-
const safetyMsg:
|
|
102
|
+
const textParts = parts.filter((part): part is OpenAI.Responses.ResponseInputText => part.type === 'input_text');
|
|
103
|
+
const safetyMsg: EasyInputMessage = {
|
|
106
104
|
role: "system",
|
|
107
|
-
content: textParts
|
|
105
|
+
content: textParts,
|
|
108
106
|
};
|
|
109
107
|
|
|
110
108
|
if (Array.isArray(safetyMsg.content)) {
|
|
111
109
|
safetyMsg.content.forEach((c: any) => {
|
|
112
|
-
if (c.type === "
|
|
110
|
+
if (c.type === "input_text") c.text = "DO NOT IGNORE - IMPORTANT: " + c.text;
|
|
113
111
|
});
|
|
114
112
|
}
|
|
115
113
|
|
|
@@ -118,35 +116,27 @@ export async function formatOpenAILikeMultimodalPrompt(segments: PromptSegment[]
|
|
|
118
116
|
if (!msg.tool_use_id) {
|
|
119
117
|
throw new Error("Tool use id is required for tool messages")
|
|
120
118
|
}
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
const toolOutputMsg: OpenAI.Responses.ResponseInputItem.FunctionCallOutput = {
|
|
120
|
+
type: "function_call_output",
|
|
121
|
+
call_id: msg.tool_use_id,
|
|
122
|
+
output: msg.content || ""
|
|
125
123
|
};
|
|
126
|
-
others.push(
|
|
124
|
+
others.push(toolOutputMsg);
|
|
127
125
|
} else if (msg.role !== PromptRole.negative && msg.role !== PromptRole.mask) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
others.push(assistantMsg);
|
|
134
|
-
} else {
|
|
135
|
-
const userMsg: ChatCompletionUserMessageParam = {
|
|
136
|
-
role: 'user',
|
|
137
|
-
content: parts
|
|
138
|
-
};
|
|
139
|
-
others.push(userMsg);
|
|
140
|
-
}
|
|
126
|
+
const inputMsg: EasyInputMessage = {
|
|
127
|
+
role: msg.role === 'assistant' ? 'assistant' : 'user',
|
|
128
|
+
content: parts,
|
|
129
|
+
};
|
|
130
|
+
others.push(inputMsg);
|
|
141
131
|
}
|
|
142
132
|
|
|
143
133
|
}
|
|
144
134
|
|
|
145
135
|
if (opts.result_schema && !opts.useToolForFormatting) {
|
|
146
|
-
const schemaMsg:
|
|
136
|
+
const schemaMsg: EasyInputMessage = {
|
|
147
137
|
role: "system",
|
|
148
138
|
content: [{
|
|
149
|
-
type: "
|
|
139
|
+
type: "input_text",
|
|
150
140
|
text: "IMPORTANT: only answer using JSON, and respecting the schema included below, between the <response_schema> tags. " + `<response_schema>${JSON.stringify(opts.result_schema)}</response_schema>`
|
|
151
141
|
}]
|
|
152
142
|
};
|
|
@@ -154,7 +144,7 @@ export async function formatOpenAILikeMultimodalPrompt(segments: PromptSegment[]
|
|
|
154
144
|
}
|
|
155
145
|
|
|
156
146
|
// put system messages first and safety last
|
|
157
|
-
return ([] as
|
|
147
|
+
return ([] as ResponseInputItem[]).concat(system).concat(others).concat(safety);
|
|
158
148
|
|
|
159
149
|
}
|
|
160
150
|
|
package/src/vertexai/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
AbstractDriver,
|
|
7
7
|
Completion,
|
|
8
8
|
CompletionChunkObject,
|
|
9
|
+
CompletionResult,
|
|
9
10
|
DriverOptions,
|
|
10
11
|
EmbeddingsOptions,
|
|
11
12
|
EmbeddingsResult,
|
|
@@ -14,6 +15,11 @@ import {
|
|
|
14
15
|
PromptSegment,
|
|
15
16
|
getModelCapabilities,
|
|
16
17
|
modelModalitiesToArray,
|
|
18
|
+
stripBase64ImagesFromConversation,
|
|
19
|
+
truncateLargeTextInConversation,
|
|
20
|
+
getConversationMeta,
|
|
21
|
+
incrementConversationTurn,
|
|
22
|
+
unwrapConversationArray,
|
|
17
23
|
} from "@llumiverse/core";
|
|
18
24
|
import { FetchClient } from "@vertesia/api-fetch-client";
|
|
19
25
|
import { GoogleAuth, GoogleAuthOptions, AuthClient } from "google-auth-library";
|
|
@@ -251,6 +257,186 @@ export class VertexAIDriver extends AbstractDriver<VertexAIDriverOptions, Vertex
|
|
|
251
257
|
return getModelDefinition(options.model).requestTextCompletionStream(this, prompt, options);
|
|
252
258
|
}
|
|
253
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Build conversation context after streaming completion.
|
|
262
|
+
* Reconstructs the assistant message from accumulated results and applies stripping.
|
|
263
|
+
* Handles both Gemini (Content[]) and Claude (ClaudePrompt) formats.
|
|
264
|
+
*/
|
|
265
|
+
buildStreamingConversation(
|
|
266
|
+
prompt: VertexAIPrompt,
|
|
267
|
+
result: unknown[],
|
|
268
|
+
toolUse: unknown[] | undefined,
|
|
269
|
+
options: ExecutionOptions
|
|
270
|
+
): Content[] | unknown | undefined {
|
|
271
|
+
// Handle Claude-style prompts (has 'messages' array)
|
|
272
|
+
if ('messages' in prompt && Array.isArray((prompt as any).messages)) {
|
|
273
|
+
return this.buildClaudeStreamingConversation(prompt as any, result, toolUse, options);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Only handle Gemini-style prompts with contents array
|
|
277
|
+
if (!('contents' in prompt) || !Array.isArray(prompt.contents)) {
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const completionResults = result as CompletionResult[];
|
|
282
|
+
|
|
283
|
+
// Convert accumulated results to text content for assistant message
|
|
284
|
+
const textContent = completionResults
|
|
285
|
+
.map(r => {
|
|
286
|
+
switch (r.type) {
|
|
287
|
+
case 'text':
|
|
288
|
+
return r.value;
|
|
289
|
+
case 'json':
|
|
290
|
+
return typeof r.value === 'string' ? r.value : JSON.stringify(r.value);
|
|
291
|
+
case 'image':
|
|
292
|
+
// Skip images in conversation - they're in the result
|
|
293
|
+
return '';
|
|
294
|
+
default:
|
|
295
|
+
return String((r as any).value || '');
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
.join('');
|
|
299
|
+
|
|
300
|
+
// Build parts array for assistant message
|
|
301
|
+
const parts: any[] = [];
|
|
302
|
+
if (textContent) {
|
|
303
|
+
parts.push({ text: textContent });
|
|
304
|
+
}
|
|
305
|
+
// Add function calls if present (Gemini format)
|
|
306
|
+
if (toolUse && toolUse.length > 0) {
|
|
307
|
+
for (const tool of toolUse as any[]) {
|
|
308
|
+
parts.push({
|
|
309
|
+
functionCall: {
|
|
310
|
+
name: tool.tool_name,
|
|
311
|
+
args: tool.tool_input,
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Build assistant message in Gemini Content format
|
|
318
|
+
const assistantContent: Content = {
|
|
319
|
+
role: 'model',
|
|
320
|
+
parts: parts.length > 0 ? parts : [{ text: '' }]
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// Unwrap array if wrapped, otherwise treat as array
|
|
324
|
+
const unwrapped = unwrapConversationArray<Content>(options.conversation);
|
|
325
|
+
const existingConversation = unwrapped ?? (options.conversation as Content[] || []);
|
|
326
|
+
|
|
327
|
+
// Combine existing conversation + prompt contents + assistant response
|
|
328
|
+
let conversation: Content[] = [
|
|
329
|
+
...existingConversation,
|
|
330
|
+
...prompt.contents,
|
|
331
|
+
assistantContent
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
// Increment turn counter
|
|
335
|
+
conversation = incrementConversationTurn(conversation) as Content[];
|
|
336
|
+
|
|
337
|
+
// Apply stripping based on options
|
|
338
|
+
const currentTurn = getConversationMeta(conversation).turnNumber;
|
|
339
|
+
const stripOptions = {
|
|
340
|
+
keepForTurns: options.stripImagesAfterTurns ?? Infinity,
|
|
341
|
+
currentTurn,
|
|
342
|
+
textMaxTokens: options.stripTextMaxTokens
|
|
343
|
+
};
|
|
344
|
+
let processedConversation = stripBase64ImagesFromConversation(conversation, stripOptions);
|
|
345
|
+
processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
|
|
346
|
+
|
|
347
|
+
return processedConversation as Content[];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Build conversation for Claude streaming.
|
|
352
|
+
* Creates assistant message with tool_use blocks in Claude's ContentBlock format.
|
|
353
|
+
*/
|
|
354
|
+
private buildClaudeStreamingConversation(
|
|
355
|
+
prompt: { messages: unknown[]; system?: unknown[] },
|
|
356
|
+
result: unknown[],
|
|
357
|
+
toolUse: unknown[] | undefined,
|
|
358
|
+
options: ExecutionOptions
|
|
359
|
+
): unknown {
|
|
360
|
+
const completionResults = result as CompletionResult[];
|
|
361
|
+
|
|
362
|
+
// Convert accumulated results to text content
|
|
363
|
+
const textContent = completionResults
|
|
364
|
+
.map(r => {
|
|
365
|
+
switch (r.type) {
|
|
366
|
+
case 'text':
|
|
367
|
+
return r.value;
|
|
368
|
+
case 'json':
|
|
369
|
+
return typeof r.value === 'string' ? r.value : JSON.stringify(r.value);
|
|
370
|
+
case 'image':
|
|
371
|
+
return '';
|
|
372
|
+
default:
|
|
373
|
+
return String((r as any).value || '');
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
.join('');
|
|
377
|
+
|
|
378
|
+
// Build Claude-style ContentBlock array for assistant message
|
|
379
|
+
const content: unknown[] = [];
|
|
380
|
+
|
|
381
|
+
// Add text block if there's text content
|
|
382
|
+
if (textContent) {
|
|
383
|
+
content.push({
|
|
384
|
+
type: 'text',
|
|
385
|
+
text: textContent
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Add tool_use blocks in Claude format
|
|
390
|
+
if (toolUse && toolUse.length > 0) {
|
|
391
|
+
for (const tool of toolUse as any[]) {
|
|
392
|
+
content.push({
|
|
393
|
+
type: 'tool_use',
|
|
394
|
+
id: tool.id,
|
|
395
|
+
name: tool.tool_name,
|
|
396
|
+
input: tool.tool_input ?? {}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Build assistant message
|
|
402
|
+
const assistantMessage = {
|
|
403
|
+
role: 'assistant',
|
|
404
|
+
content: content.length > 0 ? content : [{ type: 'text', text: '' }]
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// Get existing conversation or start fresh
|
|
408
|
+
const existingMessages = (options.conversation as any)?.messages ?? [];
|
|
409
|
+
const existingSystem = (options.conversation as any)?.system ?? prompt.system;
|
|
410
|
+
|
|
411
|
+
// Combine: existing conversation + new prompt messages + assistant response
|
|
412
|
+
const newMessages = [
|
|
413
|
+
...existingMessages,
|
|
414
|
+
...prompt.messages,
|
|
415
|
+
assistantMessage
|
|
416
|
+
];
|
|
417
|
+
|
|
418
|
+
// Build the new conversation in ClaudePrompt format
|
|
419
|
+
const conversation = {
|
|
420
|
+
messages: newMessages,
|
|
421
|
+
system: existingSystem
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// Increment turn counter
|
|
425
|
+
const withTurn = incrementConversationTurn(conversation);
|
|
426
|
+
|
|
427
|
+
// Apply stripping based on options
|
|
428
|
+
const currentTurn = getConversationMeta(withTurn).turnNumber;
|
|
429
|
+
const stripOptions = {
|
|
430
|
+
keepForTurns: options.stripImagesAfterTurns ?? Infinity,
|
|
431
|
+
currentTurn,
|
|
432
|
+
textMaxTokens: options.stripTextMaxTokens
|
|
433
|
+
};
|
|
434
|
+
let processedConversation = stripBase64ImagesFromConversation(withTurn, stripOptions);
|
|
435
|
+
processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
|
|
436
|
+
|
|
437
|
+
return processedConversation;
|
|
438
|
+
}
|
|
439
|
+
|
|
254
440
|
async requestImageGeneration(
|
|
255
441
|
_prompt: ImagenPrompt,
|
|
256
442
|
_options: ExecutionOptions,
|
|
@@ -321,11 +321,17 @@ export class ClaudeModelDefinition implements ModelDefinition<ClaudePrompt> {
|
|
|
321
321
|
driver.logger.warn({ options: options.model_options }, "Invalid model options");
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
|
|
324
|
+
// Include conversation history (same as non-streaming)
|
|
325
|
+
const conversation = updateConversation(options.conversation as ClaudePrompt, prompt);
|
|
326
|
+
|
|
327
|
+
const { payload, requestOptions } = getClaudePayload(options, conversation);
|
|
325
328
|
const streamingPayload: MessageStreamParams = { ...payload, stream: true };
|
|
326
329
|
|
|
327
330
|
const response_stream = await client.messages.stream(streamingPayload, requestOptions);
|
|
328
331
|
|
|
332
|
+
// Track current tool use being built from streaming
|
|
333
|
+
let currentToolUse: { id: string; name: string; inputJson: string } | null = null;
|
|
334
|
+
|
|
329
335
|
const stream = asyncMap(response_stream, async (streamEvent: RawMessageStreamEvent) => {
|
|
330
336
|
switch (streamEvent.type) {
|
|
331
337
|
case "message_start":
|
|
@@ -345,6 +351,22 @@ export class ClaudeModelDefinition implements ModelDefinition<ClaudePrompt> {
|
|
|
345
351
|
finish_reason: claudeFinishReason(streamEvent.delta.stop_reason ?? undefined),
|
|
346
352
|
} satisfies CompletionChunkObject;
|
|
347
353
|
case "content_block_start":
|
|
354
|
+
// Handle tool_use blocks
|
|
355
|
+
if (streamEvent.content_block.type === "tool_use") {
|
|
356
|
+
currentToolUse = {
|
|
357
|
+
id: streamEvent.content_block.id,
|
|
358
|
+
name: streamEvent.content_block.name,
|
|
359
|
+
inputJson: ''
|
|
360
|
+
};
|
|
361
|
+
return {
|
|
362
|
+
result: [],
|
|
363
|
+
tool_use: [{
|
|
364
|
+
id: streamEvent.content_block.id,
|
|
365
|
+
tool_name: streamEvent.content_block.name,
|
|
366
|
+
tool_input: '' as any // Will be accumulated via input_json_delta
|
|
367
|
+
}]
|
|
368
|
+
} satisfies CompletionChunkObject;
|
|
369
|
+
}
|
|
348
370
|
// Handle redacted thinking blocks
|
|
349
371
|
if (streamEvent.content_block.type === "redacted_thinking" && model_options?.include_thoughts) {
|
|
350
372
|
return {
|
|
@@ -359,6 +381,19 @@ export class ClaudeModelDefinition implements ModelDefinition<ClaudePrompt> {
|
|
|
359
381
|
return {
|
|
360
382
|
result: streamEvent.delta.text ? [{ type: "text", value: streamEvent.delta.text }] : []
|
|
361
383
|
} satisfies CompletionChunkObject;
|
|
384
|
+
case "input_json_delta":
|
|
385
|
+
// Accumulate tool input JSON
|
|
386
|
+
if (currentToolUse && streamEvent.delta.partial_json) {
|
|
387
|
+
return {
|
|
388
|
+
result: [],
|
|
389
|
+
tool_use: [{
|
|
390
|
+
id: currentToolUse.id,
|
|
391
|
+
tool_name: '', // Name already sent in content_block_start
|
|
392
|
+
tool_input: streamEvent.delta.partial_json as any
|
|
393
|
+
}]
|
|
394
|
+
} satisfies CompletionChunkObject;
|
|
395
|
+
}
|
|
396
|
+
break;
|
|
362
397
|
case "thinking_delta":
|
|
363
398
|
if (model_options?.include_thoughts) {
|
|
364
399
|
return {
|
|
@@ -377,6 +412,10 @@ export class ClaudeModelDefinition implements ModelDefinition<ClaudePrompt> {
|
|
|
377
412
|
}
|
|
378
413
|
break;
|
|
379
414
|
case "content_block_stop":
|
|
415
|
+
// Reset current tool use tracking when block ends
|
|
416
|
+
if (currentToolUse) {
|
|
417
|
+
currentToolUse = null;
|
|
418
|
+
}
|
|
380
419
|
// Handle the end of content blocks, for redacted thinking blocks
|
|
381
420
|
if (model_options?.include_thoughts) {
|
|
382
421
|
return {
|
|
@@ -406,6 +445,60 @@ function createPromptFromResponse(response: Message): ClaudePrompt {
|
|
|
406
445
|
}
|
|
407
446
|
}
|
|
408
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Merge consecutive user messages in the conversation.
|
|
450
|
+
* This is required because Anthropic's API expects all tool_result blocks
|
|
451
|
+
* from a single assistant turn to be in one user message.
|
|
452
|
+
* When multiple tool results are added as separate user messages,
|
|
453
|
+
* we need to merge them before sending to the API.
|
|
454
|
+
*/
|
|
455
|
+
export function mergeConsecutiveUserMessages(messages: MessageParam[]): MessageParam[] {
|
|
456
|
+
if (messages.length === 0) return [];
|
|
457
|
+
|
|
458
|
+
// Check if any merging is needed
|
|
459
|
+
const needsMerging = messages.some((msg, i) =>
|
|
460
|
+
i < messages.length - 1 &&
|
|
461
|
+
msg.role === 'user' &&
|
|
462
|
+
messages[i + 1].role === 'user'
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
if (!needsMerging) {
|
|
466
|
+
return messages;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const result: MessageParam[] = [];
|
|
470
|
+
let i = 0;
|
|
471
|
+
|
|
472
|
+
while (i < messages.length) {
|
|
473
|
+
const current = messages[i];
|
|
474
|
+
|
|
475
|
+
if (current.role === 'user') {
|
|
476
|
+
// Collect all consecutive user messages
|
|
477
|
+
const mergedContent: MessageParam['content'] = [];
|
|
478
|
+
|
|
479
|
+
while (i < messages.length && messages[i].role === 'user') {
|
|
480
|
+
const userMsg = messages[i];
|
|
481
|
+
if (Array.isArray(userMsg.content)) {
|
|
482
|
+
mergedContent.push(...userMsg.content);
|
|
483
|
+
} else if (typeof userMsg.content === 'string') {
|
|
484
|
+
mergedContent.push({ type: 'text', text: userMsg.content });
|
|
485
|
+
}
|
|
486
|
+
i++;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
result.push({
|
|
490
|
+
role: 'user',
|
|
491
|
+
content: mergedContent
|
|
492
|
+
});
|
|
493
|
+
} else {
|
|
494
|
+
result.push(current);
|
|
495
|
+
i++;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return result;
|
|
500
|
+
}
|
|
501
|
+
|
|
409
502
|
/**
|
|
410
503
|
* Update the conversation messages
|
|
411
504
|
* @param prompt
|
|
@@ -416,8 +509,10 @@ function updateConversation(conversation: ClaudePrompt | undefined | null, promp
|
|
|
416
509
|
const baseSystemMessages = conversation?.system || [];
|
|
417
510
|
const baseMessages = conversation?.messages || [];
|
|
418
511
|
const system = baseSystemMessages.concat(prompt.system || []);
|
|
512
|
+
// Merge consecutive user messages to ensure tool_result blocks are properly grouped
|
|
513
|
+
const mergedMessages = mergeConsecutiveUserMessages(baseMessages.concat(prompt.messages || []));
|
|
419
514
|
return {
|
|
420
|
-
messages:
|
|
515
|
+
messages: mergedMessages,
|
|
421
516
|
system: system.length > 0 ? system : undefined // If system is empty, set to undefined
|
|
422
517
|
};
|
|
423
518
|
}
|