@llumiverse/drivers 0.23.0 → 0.24.0
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/lib/cjs/azure/azure_foundry.js +46 -2
- package/lib/cjs/azure/azure_foundry.js.map +1 -1
- package/lib/cjs/bedrock/index.js +140 -15
- package/lib/cjs/bedrock/index.js.map +1 -1
- package/lib/cjs/groq/index.js +115 -85
- package/lib/cjs/groq/index.js.map +1 -1
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/openai/index.js +310 -114
- package/lib/cjs/openai/index.js.map +1 -1
- package/lib/cjs/openai/openai_compatible.js +62 -0
- package/lib/cjs/openai/openai_compatible.js.map +1 -0
- package/lib/cjs/openai/openai_format.js +32 -39
- package/lib/cjs/openai/openai_format.js.map +1 -1
- package/lib/cjs/vertexai/index.js +147 -0
- package/lib/cjs/vertexai/index.js.map +1 -1
- package/lib/cjs/vertexai/models/claude.js +88 -2
- package/lib/cjs/vertexai/models/claude.js.map +1 -1
- package/lib/cjs/vertexai/models/gemini.js +59 -20
- package/lib/cjs/vertexai/models/gemini.js.map +1 -1
- package/lib/cjs/xai/index.js +10 -16
- package/lib/cjs/xai/index.js.map +1 -1
- package/lib/esm/azure/azure_foundry.js +46 -2
- package/lib/esm/azure/azure_foundry.js.map +1 -1
- package/lib/esm/bedrock/index.js +141 -16
- package/lib/esm/bedrock/index.js.map +1 -1
- package/lib/esm/groq/index.js +115 -85
- package/lib/esm/groq/index.js.map +1 -1
- package/lib/esm/index.js +1 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/openai/index.js +311 -115
- package/lib/esm/openai/index.js.map +1 -1
- package/lib/esm/openai/openai_compatible.js +55 -0
- package/lib/esm/openai/openai_compatible.js.map +1 -0
- package/lib/esm/openai/openai_format.js +32 -39
- package/lib/esm/openai/openai_format.js.map +1 -1
- package/lib/esm/vertexai/index.js +148 -1
- package/lib/esm/vertexai/index.js.map +1 -1
- package/lib/esm/vertexai/models/claude.js +87 -2
- package/lib/esm/vertexai/models/claude.js.map +1 -1
- package/lib/esm/vertexai/models/gemini.js +60 -21
- package/lib/esm/vertexai/models/gemini.js.map +1 -1
- package/lib/esm/xai/index.js +10 -16
- package/lib/esm/xai/index.js.map +1 -1
- package/lib/types/azure/azure_foundry.d.ts +7 -5
- package/lib/types/azure/azure_foundry.d.ts.map +1 -1
- package/lib/types/bedrock/index.d.ts +5 -0
- package/lib/types/bedrock/index.d.ts.map +1 -1
- package/lib/types/groq/index.d.ts.map +1 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/openai/index.d.ts +13 -7
- package/lib/types/openai/index.d.ts.map +1 -1
- package/lib/types/openai/openai_compatible.d.ts +26 -0
- package/lib/types/openai/openai_compatible.d.ts.map +1 -0
- package/lib/types/openai/openai_format.d.ts +4 -2
- package/lib/types/openai/openai_format.d.ts.map +1 -1
- package/lib/types/vertexai/index.d.ts +11 -0
- package/lib/types/vertexai/index.d.ts.map +1 -1
- package/lib/types/vertexai/models/claude.d.ts +8 -0
- package/lib/types/vertexai/models/claude.d.ts.map +1 -1
- package/lib/types/vertexai/models/gemini.d.ts +1 -1
- package/lib/types/vertexai/models/gemini.d.ts.map +1 -1
- package/lib/types/xai/index.d.ts +2 -3
- package/lib/types/xai/index.d.ts.map +1 -1
- 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/src/bedrock/index.ts
CHANGED
|
@@ -6,13 +6,26 @@ import { BedrockRuntime, ConverseRequest, ConverseResponse, ConverseStreamOutput
|
|
|
6
6
|
import { S3Client } from "@aws-sdk/client-s3";
|
|
7
7
|
import { AwsCredentialIdentity, Provider } from "@aws-sdk/types";
|
|
8
8
|
import {
|
|
9
|
-
AbstractDriver, AIModel,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
AbstractDriver, AIModel,
|
|
10
|
+
BedrockClaudeOptions,
|
|
11
|
+
BedrockGptOssOptions,
|
|
12
|
+
BedrockPalmyraOptions,
|
|
13
|
+
Completion, CompletionChunkObject, DataSource, DriverOptions, EmbeddingsOptions, EmbeddingsResult,
|
|
14
|
+
ExecutionOptions, ExecutionTokenUsage,
|
|
15
|
+
getMaxTokensLimitBedrock,
|
|
16
|
+
getModelCapabilities,
|
|
17
|
+
modelModalitiesToArray,
|
|
18
|
+
ModelOptions,
|
|
19
|
+
NovaCanvasOptions,
|
|
20
|
+
PromptSegment,
|
|
14
21
|
StatelessExecutionOptions,
|
|
15
|
-
|
|
22
|
+
stripBinaryFromConversation,
|
|
23
|
+
truncateLargeTextInConversation,
|
|
24
|
+
deserializeBinaryFromStorage,
|
|
25
|
+
getConversationMeta,
|
|
26
|
+
incrementConversationTurn,
|
|
27
|
+
TextFallbackOptions, ToolDefinition, ToolUse, TrainingJob, TrainingJobStatus, TrainingOptions,
|
|
28
|
+
CompletionResult
|
|
16
29
|
} from "@llumiverse/core";
|
|
17
30
|
import { transformAsyncIterator } from "@llumiverse/core/async";
|
|
18
31
|
import { formatNovaPrompt, NovaMessagesPrompt } from "@llumiverse/core/formatters";
|
|
@@ -22,9 +35,9 @@ import { formatNovaImageGenerationPayload, NovaImageGenerationTaskType } from ".
|
|
|
22
35
|
import { forceUploadFile } from "./s3.js";
|
|
23
36
|
import {
|
|
24
37
|
formatTwelvelabsPegasusPrompt,
|
|
25
|
-
TwelvelabsPegasusRequest,
|
|
26
38
|
TwelvelabsMarengoRequest,
|
|
27
|
-
TwelvelabsMarengoResponse
|
|
39
|
+
TwelvelabsMarengoResponse,
|
|
40
|
+
TwelvelabsPegasusRequest
|
|
28
41
|
} from "./twelvelabs.js";
|
|
29
42
|
|
|
30
43
|
const supportStreamingCache = new LRUCache<string, boolean>(4096);
|
|
@@ -114,7 +127,6 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
114
127
|
this._executor = new BedrockRuntime({
|
|
115
128
|
region: this.options.region,
|
|
116
129
|
credentials: this.options.credentials,
|
|
117
|
-
|
|
118
130
|
});
|
|
119
131
|
}
|
|
120
132
|
return this._executor;
|
|
@@ -350,6 +362,91 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
350
362
|
return canStream;
|
|
351
363
|
}
|
|
352
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Build conversation context after streaming completion.
|
|
367
|
+
* Reconstructs the assistant message from accumulated results and applies stripping.
|
|
368
|
+
*/
|
|
369
|
+
buildStreamingConversation(
|
|
370
|
+
prompt: BedrockPrompt,
|
|
371
|
+
result: unknown[],
|
|
372
|
+
toolUse: unknown[] | undefined,
|
|
373
|
+
options: ExecutionOptions
|
|
374
|
+
): ConverseRequest | undefined {
|
|
375
|
+
// Only handle ConverseRequest prompts (not NovaMessagesPrompt or TwelvelabsPegasusRequest)
|
|
376
|
+
if (options.model.includes("canvas") || options.model.includes("twelvelabs.pegasus")) {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const conversePrompt = prompt as ConverseRequest;
|
|
381
|
+
const completionResults = result as CompletionResult[];
|
|
382
|
+
|
|
383
|
+
// Convert accumulated results to text content for assistant message
|
|
384
|
+
const textContent = completionResults
|
|
385
|
+
.map(r => {
|
|
386
|
+
switch (r.type) {
|
|
387
|
+
case 'text':
|
|
388
|
+
return r.value;
|
|
389
|
+
case 'json':
|
|
390
|
+
return typeof r.value === 'string' ? r.value : JSON.stringify(r.value);
|
|
391
|
+
case 'image':
|
|
392
|
+
// Skip images in conversation - they're in the result
|
|
393
|
+
return '';
|
|
394
|
+
default:
|
|
395
|
+
return String((r as any).value || '');
|
|
396
|
+
}
|
|
397
|
+
})
|
|
398
|
+
.join('');
|
|
399
|
+
|
|
400
|
+
// Deserialize any base64-encoded binary data back to Uint8Array
|
|
401
|
+
const incomingConversation = deserializeBinaryFromStorage(options.conversation) as ConverseRequest;
|
|
402
|
+
|
|
403
|
+
// Start with the conversation from options combined with the prompt
|
|
404
|
+
let conversation = updateConversation(incomingConversation, conversePrompt);
|
|
405
|
+
|
|
406
|
+
// Build assistant message content
|
|
407
|
+
const messageContent: any[] = [];
|
|
408
|
+
if (textContent) {
|
|
409
|
+
messageContent.push({ text: textContent });
|
|
410
|
+
}
|
|
411
|
+
// Add tool use blocks if present
|
|
412
|
+
if (toolUse && toolUse.length > 0) {
|
|
413
|
+
for (const tool of toolUse as ToolUse[]) {
|
|
414
|
+
messageContent.push({
|
|
415
|
+
toolUse: {
|
|
416
|
+
toolUseId: tool.id,
|
|
417
|
+
name: tool.tool_name,
|
|
418
|
+
input: tool.tool_input,
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Add assistant message
|
|
425
|
+
const assistantMessage: ConverseRequest = {
|
|
426
|
+
messages: [{
|
|
427
|
+
content: messageContent.length > 0 ? messageContent : [{ text: '' }],
|
|
428
|
+
role: "assistant"
|
|
429
|
+
}],
|
|
430
|
+
modelId: conversePrompt.modelId,
|
|
431
|
+
};
|
|
432
|
+
conversation = updateConversation(conversation, assistantMessage);
|
|
433
|
+
|
|
434
|
+
// Increment turn counter
|
|
435
|
+
conversation = incrementConversationTurn(conversation) as ConverseRequest;
|
|
436
|
+
|
|
437
|
+
// Apply stripping based on options
|
|
438
|
+
const currentTurn = getConversationMeta(conversation).turnNumber;
|
|
439
|
+
const stripOptions = {
|
|
440
|
+
keepForTurns: options.stripImagesAfterTurns ?? Infinity,
|
|
441
|
+
currentTurn,
|
|
442
|
+
textMaxTokens: options.stripTextMaxTokens
|
|
443
|
+
};
|
|
444
|
+
let processedConversation = stripBinaryFromConversation(conversation, stripOptions);
|
|
445
|
+
processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
|
|
446
|
+
|
|
447
|
+
return processedConversation as ConverseRequest;
|
|
448
|
+
}
|
|
449
|
+
|
|
353
450
|
async requestTextCompletion(prompt: BedrockPrompt, options: ExecutionOptions): Promise<Completion> {
|
|
354
451
|
// Handle Twelvelabs Pegasus models
|
|
355
452
|
if (options.model.includes("twelvelabs.pegasus")) {
|
|
@@ -358,7 +455,10 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
358
455
|
|
|
359
456
|
// Handle other Bedrock models that use Converse API
|
|
360
457
|
const conversePrompt = prompt as ConverseRequest;
|
|
361
|
-
|
|
458
|
+
|
|
459
|
+
// Deserialize any base64-encoded binary data back to Uint8Array before API call
|
|
460
|
+
const incomingConversation = deserializeBinaryFromStorage(options.conversation) as ConverseRequest;
|
|
461
|
+
let conversation = updateConversation(incomingConversation, conversePrompt);
|
|
362
462
|
|
|
363
463
|
const payload = this.preparePayload(conversation, options);
|
|
364
464
|
const executor = this.getExecutor();
|
|
@@ -372,6 +472,9 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
372
472
|
modelId: conversePrompt.modelId,
|
|
373
473
|
});
|
|
374
474
|
|
|
475
|
+
// Increment turn counter for deferred stripping
|
|
476
|
+
conversation = incrementConversationTurn(conversation) as ConverseRequest;
|
|
477
|
+
|
|
375
478
|
let tool_use: ToolUse[] | undefined = undefined;
|
|
376
479
|
//Get tool requests, we check tool use regardless of finish reason, as you can hit length and still get a valid response.
|
|
377
480
|
tool_use = res.output?.message?.content?.reduce((tools: ToolUse[], c) => {
|
|
@@ -389,10 +492,22 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
389
492
|
tool_use = undefined;
|
|
390
493
|
}
|
|
391
494
|
|
|
495
|
+
// Strip/serialize binary data based on options.stripImagesAfterTurns
|
|
496
|
+
const currentTurn = getConversationMeta(conversation).turnNumber;
|
|
497
|
+
const stripOptions = {
|
|
498
|
+
keepForTurns: options.stripImagesAfterTurns ?? Infinity,
|
|
499
|
+
currentTurn,
|
|
500
|
+
textMaxTokens: options.stripTextMaxTokens
|
|
501
|
+
};
|
|
502
|
+
let processedConversation = stripBinaryFromConversation(conversation, stripOptions);
|
|
503
|
+
|
|
504
|
+
// Truncate large text content if configured
|
|
505
|
+
processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
|
|
506
|
+
|
|
392
507
|
const completion = {
|
|
393
508
|
...this.getExtractedExecution(res, conversePrompt, options),
|
|
394
509
|
original_response: options.include_original_response ? res : undefined,
|
|
395
|
-
conversation:
|
|
510
|
+
conversation: processedConversation,
|
|
396
511
|
tool_use: tool_use,
|
|
397
512
|
};
|
|
398
513
|
|
|
@@ -496,7 +611,13 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
496
611
|
|
|
497
612
|
// Handle other Bedrock models that use Converse API
|
|
498
613
|
const conversePrompt = prompt as ConverseRequest;
|
|
499
|
-
|
|
614
|
+
|
|
615
|
+
// Include conversation history (same as non-streaming)
|
|
616
|
+
// Deserialize any base64-encoded binary data back to Uint8Array before API call
|
|
617
|
+
const incomingConversation = deserializeBinaryFromStorage(options.conversation) as ConverseRequest;
|
|
618
|
+
const conversation = updateConversation(incomingConversation, conversePrompt);
|
|
619
|
+
|
|
620
|
+
const payload = this.preparePayload(conversation, options);
|
|
500
621
|
const executor = this.getExecutor();
|
|
501
622
|
return executor.converseStream({
|
|
502
623
|
...payload,
|
|
@@ -642,22 +763,38 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
|
|
|
642
763
|
prompt.messages = converseJSONprefill(prompt.messages);
|
|
643
764
|
}
|
|
644
765
|
|
|
766
|
+
// Clean undefined values from additionalField since AWS Bedrock requires valid JSON
|
|
767
|
+
// and will throw an exception for unrecognized parameters
|
|
768
|
+
const cleanedAdditionalFields = removeUndefinedValues(additionalField);
|
|
769
|
+
const cleanedModelOptions = removeUndefinedValues({
|
|
770
|
+
maxTokens: model_options.max_tokens,
|
|
771
|
+
temperature: model_options.temperature,
|
|
772
|
+
topP: model_options.top_p,
|
|
773
|
+
stopSequences: model_options.stop_sequence,
|
|
774
|
+
} satisfies InferenceConfiguration);
|
|
775
|
+
|
|
776
|
+
//Construct the final request payload
|
|
777
|
+
// We only add fields that are defined to avoid AWS errors
|
|
645
778
|
const request: ConverseRequest = {
|
|
646
|
-
messages: prompt.messages,
|
|
647
|
-
system: prompt.system,
|
|
648
779
|
modelId: options.model,
|
|
649
|
-
inferenceConfig: {
|
|
650
|
-
maxTokens: model_options.max_tokens,
|
|
651
|
-
temperature: model_options.temperature,
|
|
652
|
-
topP: model_options.top_p,
|
|
653
|
-
stopSequences: model_options.stop_sequence,
|
|
654
|
-
} satisfies InferenceConfiguration,
|
|
655
|
-
additionalModelRequestFields: {
|
|
656
|
-
...additionalField,
|
|
657
|
-
}
|
|
658
780
|
};
|
|
659
781
|
|
|
660
|
-
|
|
782
|
+
if (prompt.messages) {
|
|
783
|
+
request.messages = prompt.messages;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (prompt.system) {
|
|
787
|
+
request.system = prompt.system;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (Object.keys(cleanedModelOptions).length > 0) {
|
|
791
|
+
request.inferenceConfig = cleanedModelOptions
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (Object.keys(cleanedAdditionalFields).length > 0) {
|
|
795
|
+
request.additionalModelRequestFields = cleanedAdditionalFields;
|
|
796
|
+
}
|
|
797
|
+
|
|
661
798
|
if (tool_defs?.length) {
|
|
662
799
|
request.toolConfig = {
|
|
663
800
|
tools: tool_defs,
|
|
@@ -1087,6 +1224,33 @@ function getToolDefinition(tool: ToolDefinition): Tool.ToolSpecMember {
|
|
|
1087
1224
|
}
|
|
1088
1225
|
}
|
|
1089
1226
|
|
|
1227
|
+
/**
|
|
1228
|
+
* Recursively removes undefined values from an object.
|
|
1229
|
+
* AWS Bedrock's additionalModelRequestFields must be valid JSON, and undefined is not valid JSON.
|
|
1230
|
+
* Any unrecognized parameters will cause an exception.
|
|
1231
|
+
*/
|
|
1232
|
+
function removeUndefinedValues<T extends Record<string, any>>(obj: T): Partial<T> {
|
|
1233
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
1234
|
+
return obj;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const cleaned: any = {};
|
|
1238
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1239
|
+
if (value !== undefined) {
|
|
1240
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
1241
|
+
const cleanedNested = removeUndefinedValues(value);
|
|
1242
|
+
// Only include nested objects if they have properties after cleaning
|
|
1243
|
+
if (Object.keys(cleanedNested).length > 0) {
|
|
1244
|
+
cleaned[key] = cleanedNested;
|
|
1245
|
+
}
|
|
1246
|
+
} else {
|
|
1247
|
+
cleaned[key] = value;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return cleaned;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1090
1254
|
/**
|
|
1091
1255
|
* Update the conversation messages
|
|
1092
1256
|
* @param prompt
|
package/src/groq/index.ts
CHANGED
|
@@ -3,9 +3,13 @@ import { transformAsyncIterator } from "@llumiverse/core/async";
|
|
|
3
3
|
import { formatOpenAILikeMultimodalPrompt } from "../openai/openai_format.js";
|
|
4
4
|
|
|
5
5
|
import Groq from "groq-sdk";
|
|
6
|
+
import type OpenAI from "openai";
|
|
6
7
|
import type { ChatCompletionMessageParam, ChatCompletionTool } from "groq-sdk/resources/chat/completions";
|
|
7
8
|
import type { FunctionParameters } from "groq-sdk/resources/shared";
|
|
8
9
|
|
|
10
|
+
type ResponseInputItem = OpenAI.Responses.ResponseInputItem;
|
|
11
|
+
type EasyInputMessage = OpenAI.Responses.EasyInputMessage;
|
|
12
|
+
|
|
9
13
|
interface GroqDriverOptions extends DriverOptions {
|
|
10
14
|
apiKey: string;
|
|
11
15
|
endpoint_url?: string;
|
|
@@ -49,104 +53,13 @@ export class GroqDriver extends AbstractDriver<GroqDriverOptions, ChatCompletion
|
|
|
49
53
|
|
|
50
54
|
protected async formatPrompt(segments: PromptSegment[], opts: ExecutionOptions): Promise<ChatCompletionMessageParam[]> {
|
|
51
55
|
// Use OpenAI's multimodal formatter as base then convert to Groq types
|
|
52
|
-
const
|
|
56
|
+
const responseItems = await formatOpenAILikeMultimodalPrompt(segments, {
|
|
53
57
|
...opts,
|
|
54
58
|
multimodal: true,
|
|
55
59
|
});
|
|
56
60
|
|
|
57
|
-
// Convert
|
|
58
|
-
|
|
59
|
-
const groqMessages: ChatCompletionMessageParam[] = openaiMessages.map(msg => {
|
|
60
|
-
// Handle OpenAI developer messages - convert to system messages for Groq
|
|
61
|
-
if (msg.role === 'developer' || msg.role === 'system') {
|
|
62
|
-
const systemMsg: ChatCompletionMessageParam = {
|
|
63
|
-
role: 'system',
|
|
64
|
-
content: Array.isArray(msg.content)
|
|
65
|
-
? msg.content.map(part => part.text).join('\n')
|
|
66
|
-
: msg.content,
|
|
67
|
-
// Preserve name if present
|
|
68
|
-
...(msg.name && { name: msg.name })
|
|
69
|
-
};
|
|
70
|
-
return systemMsg;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Handle user messages - filter content parts to only supported types
|
|
74
|
-
if (msg.role === 'user') {
|
|
75
|
-
let content: string | Array<{ type: 'text', text: string } | { type: 'image_url', image_url: { url: string, detail?: 'auto' | 'low' | 'high' } }> | undefined = undefined;
|
|
76
|
-
|
|
77
|
-
if (typeof msg.content === 'string') {
|
|
78
|
-
content = msg.content;
|
|
79
|
-
} else if (Array.isArray(msg.content)) {
|
|
80
|
-
// Filter to only text and image_url parts that Groq supports
|
|
81
|
-
const supportedParts = msg.content.filter(part =>
|
|
82
|
-
part.type === 'text' || part.type === 'image_url'
|
|
83
|
-
).map(part => {
|
|
84
|
-
if (part.type === 'text') {
|
|
85
|
-
return { type: 'text' as const, text: part.text };
|
|
86
|
-
} else if (part.type === 'image_url') {
|
|
87
|
-
return {
|
|
88
|
-
type: 'image_url' as const,
|
|
89
|
-
image_url: {
|
|
90
|
-
url: part.image_url.url,
|
|
91
|
-
...(part.image_url.detail && { detail: part.image_url.detail })
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
}).filter(Boolean) as Array<{ type: 'text', text: string } | { type: 'image_url', image_url: { url: string, detail?: 'auto' | 'low' | 'high' } }>;
|
|
97
|
-
|
|
98
|
-
content = supportedParts.length > 0 ? supportedParts : 'Content not supported';
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const userMsg: ChatCompletionMessageParam = {
|
|
102
|
-
role: 'user',
|
|
103
|
-
content: content ?? "",
|
|
104
|
-
// Preserve name if present
|
|
105
|
-
...(msg.name && { name: msg.name })
|
|
106
|
-
};
|
|
107
|
-
return userMsg;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Handle assistant messages - handle content arrays if needed
|
|
111
|
-
if (msg.role === 'assistant') {
|
|
112
|
-
const assistantMsg: ChatCompletionMessageParam = {
|
|
113
|
-
role: 'assistant',
|
|
114
|
-
content: Array.isArray(msg.content)
|
|
115
|
-
? msg.content.map(part => 'text' in part ? part.text : '').filter(Boolean).join('\n') || null
|
|
116
|
-
: msg.content,
|
|
117
|
-
// Preserve other assistant message properties
|
|
118
|
-
...(msg.tool_calls && { tool_calls: msg.tool_calls }),
|
|
119
|
-
...(msg.name && { name: msg.name })
|
|
120
|
-
};
|
|
121
|
-
return assistantMsg;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// For tool and function messages, they should be compatible
|
|
125
|
-
if (msg.role === 'tool') {
|
|
126
|
-
const toolMsg: ChatCompletionMessageParam = {
|
|
127
|
-
role: 'tool',
|
|
128
|
-
tool_call_id: msg.tool_call_id,
|
|
129
|
-
content: Array.isArray(msg.content)
|
|
130
|
-
? msg.content.map(part => part.text).join('\n')
|
|
131
|
-
: msg.content
|
|
132
|
-
};
|
|
133
|
-
return toolMsg;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (msg.role === 'function') {
|
|
137
|
-
const functionMsg: ChatCompletionMessageParam = {
|
|
138
|
-
role: 'function',
|
|
139
|
-
name: msg.name,
|
|
140
|
-
content: msg.content
|
|
141
|
-
};
|
|
142
|
-
return functionMsg;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Fallback - should not reach here but provides type safety
|
|
146
|
-
throw new Error(`Unsupported message role: ${(msg as any).role}`);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
return groqMessages;
|
|
61
|
+
// Convert ResponseInputItem[] to Groq ChatCompletionMessageParam[]
|
|
62
|
+
return convertResponseItemsToGroqMessages(responseItems);
|
|
150
63
|
}
|
|
151
64
|
|
|
152
65
|
private getToolDefinitions(tools: ToolDefinition[] | undefined): ChatCompletionTool[] | undefined {
|
|
@@ -342,4 +255,117 @@ function updateConversation(
|
|
|
342
255
|
messages: ChatCompletionMessageParam[]
|
|
343
256
|
): ChatCompletionMessageParam[] {
|
|
344
257
|
return (conversation || []).concat(messages);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Convert ResponseInputItem[] to Groq ChatCompletionMessageParam[]
|
|
262
|
+
*/
|
|
263
|
+
function convertResponseItemsToGroqMessages(items: ResponseInputItem[]): ChatCompletionMessageParam[] {
|
|
264
|
+
const messages: ChatCompletionMessageParam[] = [];
|
|
265
|
+
|
|
266
|
+
for (const item of items) {
|
|
267
|
+
// Handle EasyInputMessage (has role and content)
|
|
268
|
+
if ('role' in item && 'content' in item) {
|
|
269
|
+
const msg = item as EasyInputMessage;
|
|
270
|
+
const role = msg.role;
|
|
271
|
+
|
|
272
|
+
// Handle system/developer messages
|
|
273
|
+
if (role === 'system' || role === 'developer') {
|
|
274
|
+
let content: string;
|
|
275
|
+
if (typeof msg.content === 'string') {
|
|
276
|
+
content = msg.content;
|
|
277
|
+
} else if (Array.isArray(msg.content)) {
|
|
278
|
+
content = msg.content
|
|
279
|
+
.filter((part): part is OpenAI.Responses.ResponseInputText => part.type === 'input_text')
|
|
280
|
+
.map(part => part.text)
|
|
281
|
+
.join('\n');
|
|
282
|
+
} else {
|
|
283
|
+
content = '';
|
|
284
|
+
}
|
|
285
|
+
messages.push({ role: 'system', content });
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Handle user messages
|
|
290
|
+
if (role === 'user') {
|
|
291
|
+
let content: string | Array<{ type: 'text', text: string } | { type: 'image_url', image_url: { url: string, detail?: 'auto' | 'low' | 'high' } }>;
|
|
292
|
+
if (typeof msg.content === 'string') {
|
|
293
|
+
content = msg.content;
|
|
294
|
+
} else if (Array.isArray(msg.content)) {
|
|
295
|
+
const parts: Array<{ type: 'text', text: string } | { type: 'image_url', image_url: { url: string, detail?: 'auto' | 'low' | 'high' } }> = [];
|
|
296
|
+
for (const part of msg.content) {
|
|
297
|
+
if (part.type === 'input_text') {
|
|
298
|
+
parts.push({ type: 'text', text: part.text });
|
|
299
|
+
} else if (part.type === 'input_image') {
|
|
300
|
+
const imgPart = part as OpenAI.Responses.ResponseInputImage;
|
|
301
|
+
if (imgPart.image_url) {
|
|
302
|
+
parts.push({
|
|
303
|
+
type: 'image_url',
|
|
304
|
+
image_url: {
|
|
305
|
+
url: imgPart.image_url,
|
|
306
|
+
...(imgPart.detail && { detail: imgPart.detail })
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
content = parts.length > 0 ? parts : '';
|
|
313
|
+
} else {
|
|
314
|
+
content = '';
|
|
315
|
+
}
|
|
316
|
+
messages.push({ role: 'user', content });
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Handle assistant messages
|
|
321
|
+
if (role === 'assistant') {
|
|
322
|
+
let content: string | null;
|
|
323
|
+
if (typeof msg.content === 'string') {
|
|
324
|
+
content = msg.content;
|
|
325
|
+
} else if (Array.isArray(msg.content)) {
|
|
326
|
+
content = msg.content
|
|
327
|
+
.filter((part): part is OpenAI.Responses.ResponseInputText => part.type === 'input_text')
|
|
328
|
+
.map(part => part.text)
|
|
329
|
+
.join('\n') || null;
|
|
330
|
+
} else {
|
|
331
|
+
content = null;
|
|
332
|
+
}
|
|
333
|
+
messages.push({ role: 'assistant', content });
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Handle function_call_output (tool response)
|
|
339
|
+
if ('type' in item && item.type === 'function_call_output') {
|
|
340
|
+
const output = item as OpenAI.Responses.ResponseInputItem.FunctionCallOutput;
|
|
341
|
+
messages.push({
|
|
342
|
+
role: 'tool',
|
|
343
|
+
tool_call_id: output.call_id,
|
|
344
|
+
content: typeof output.output === 'string' ? output.output : JSON.stringify(output.output),
|
|
345
|
+
});
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Handle function_call (assistant tool call)
|
|
350
|
+
if ('type' in item && item.type === 'function_call') {
|
|
351
|
+
const call = item as OpenAI.Responses.ResponseFunctionToolCall;
|
|
352
|
+
// Groq expects tool_calls in assistant message, but we handle them separately
|
|
353
|
+
// This is a simplification - in practice tool_calls come from model responses
|
|
354
|
+
messages.push({
|
|
355
|
+
role: 'assistant',
|
|
356
|
+
content: null,
|
|
357
|
+
tool_calls: [{
|
|
358
|
+
id: call.call_id,
|
|
359
|
+
type: 'function',
|
|
360
|
+
function: {
|
|
361
|
+
name: call.name,
|
|
362
|
+
arguments: call.arguments,
|
|
363
|
+
}
|
|
364
|
+
}]
|
|
365
|
+
});
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return messages;
|
|
345
371
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./huggingface_ie.js";
|
|
|
5
5
|
export * from "./mistral/index.js";
|
|
6
6
|
export * from "./openai/azure_openai.js";
|
|
7
7
|
export * from "./openai/openai.js";
|
|
8
|
+
export * from "./openai/openai_compatible.js";
|
|
8
9
|
export * from "./replicate.js";
|
|
9
10
|
export * from "./test-driver/index.js";
|
|
10
11
|
export * from "./togetherai/index.js";
|