@emilshirokikh/slyos-sdk 1.2.2 → 1.3.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/dist/index.d.ts +86 -3
- package/dist/index.js +273 -0
- package/package.json +1 -1
- package/src/index.ts +415 -3
package/dist/index.d.ts
CHANGED
|
@@ -31,12 +31,81 @@ interface ProgressEvent {
|
|
|
31
31
|
detail?: any;
|
|
32
32
|
}
|
|
33
33
|
interface SlyEvent {
|
|
34
|
-
type: 'auth' | 'device_registered' | 'device_profiled' | 'model_download_start' | 'model_download_progress' | 'model_loaded' | 'inference_start' | 'inference_complete' | 'error';
|
|
34
|
+
type: 'auth' | 'device_registered' | 'device_profiled' | 'model_download_start' | 'model_download_progress' | 'model_loaded' | 'inference_start' | 'inference_complete' | 'error' | 'fallback_success' | 'fallback_error';
|
|
35
35
|
data?: any;
|
|
36
36
|
timestamp: number;
|
|
37
37
|
}
|
|
38
38
|
type ProgressCallback = (event: ProgressEvent) => void;
|
|
39
39
|
type EventCallback = (event: SlyEvent) => void;
|
|
40
|
+
interface OpenAIMessage {
|
|
41
|
+
role: 'system' | 'user' | 'assistant';
|
|
42
|
+
content: string;
|
|
43
|
+
}
|
|
44
|
+
interface OpenAIChatCompletionRequest {
|
|
45
|
+
messages: OpenAIMessage[];
|
|
46
|
+
temperature?: number;
|
|
47
|
+
top_p?: number;
|
|
48
|
+
max_tokens?: number;
|
|
49
|
+
frequency_penalty?: number;
|
|
50
|
+
presence_penalty?: number;
|
|
51
|
+
stop?: string | string[];
|
|
52
|
+
}
|
|
53
|
+
interface OpenAIChoice {
|
|
54
|
+
index: number;
|
|
55
|
+
message: OpenAIMessage;
|
|
56
|
+
finish_reason: string;
|
|
57
|
+
}
|
|
58
|
+
interface OpenAIUsage {
|
|
59
|
+
prompt_tokens: number;
|
|
60
|
+
completion_tokens: number;
|
|
61
|
+
total_tokens: number;
|
|
62
|
+
}
|
|
63
|
+
interface OpenAIChatCompletionResponse {
|
|
64
|
+
id: string;
|
|
65
|
+
object: 'chat.completion';
|
|
66
|
+
created: number;
|
|
67
|
+
model: string;
|
|
68
|
+
choices: OpenAIChoice[];
|
|
69
|
+
usage: OpenAIUsage;
|
|
70
|
+
}
|
|
71
|
+
interface BedrockTextGenerationConfig {
|
|
72
|
+
maxTokenCount?: number;
|
|
73
|
+
temperature?: number;
|
|
74
|
+
topP?: number;
|
|
75
|
+
topK?: number;
|
|
76
|
+
stopSequences?: string[];
|
|
77
|
+
}
|
|
78
|
+
interface BedrockInvokeRequest {
|
|
79
|
+
inputText: string;
|
|
80
|
+
textGenerationConfig?: BedrockTextGenerationConfig;
|
|
81
|
+
}
|
|
82
|
+
interface BedrockResult {
|
|
83
|
+
outputText: string;
|
|
84
|
+
tokenCount: number;
|
|
85
|
+
}
|
|
86
|
+
interface BedrockInvokeResponse {
|
|
87
|
+
results: BedrockResult[];
|
|
88
|
+
input_text_token_count?: number;
|
|
89
|
+
}
|
|
90
|
+
type FallbackProvider = 'openai' | 'bedrock';
|
|
91
|
+
interface FallbackConfig {
|
|
92
|
+
provider: FallbackProvider;
|
|
93
|
+
apiKey: string;
|
|
94
|
+
model: string;
|
|
95
|
+
region?: string;
|
|
96
|
+
}
|
|
97
|
+
interface SlyOSConfigWithFallback extends SlyOSConfig {
|
|
98
|
+
fallback?: FallbackConfig;
|
|
99
|
+
}
|
|
100
|
+
interface OpenAICompatibleClient {
|
|
101
|
+
chat: {
|
|
102
|
+
completions: {
|
|
103
|
+
create(request: OpenAIChatCompletionRequest & {
|
|
104
|
+
model: string;
|
|
105
|
+
}): Promise<OpenAIChatCompletionResponse>;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
}
|
|
40
109
|
declare class SlyOS {
|
|
41
110
|
private apiKey;
|
|
42
111
|
private apiUrl;
|
|
@@ -46,7 +115,8 @@ declare class SlyOS {
|
|
|
46
115
|
private deviceProfile;
|
|
47
116
|
private onProgress;
|
|
48
117
|
private onEvent;
|
|
49
|
-
|
|
118
|
+
private fallbackConfig;
|
|
119
|
+
constructor(config: SlyOSConfigWithFallback);
|
|
50
120
|
private emitProgress;
|
|
51
121
|
private emitEvent;
|
|
52
122
|
analyzeDevice(): Promise<DeviceProfile>;
|
|
@@ -75,6 +145,19 @@ declare class SlyOS {
|
|
|
75
145
|
}): Promise<void>;
|
|
76
146
|
generate(modelId: string, prompt: string, options?: GenerateOptions): Promise<string>;
|
|
77
147
|
transcribe(modelId: string, audioInput: any, options?: TranscribeOptions): Promise<string>;
|
|
148
|
+
chatCompletion(modelId: string, request: OpenAIChatCompletionRequest): Promise<OpenAIChatCompletionResponse>;
|
|
149
|
+
bedrockInvoke(modelId: string, request: BedrockInvokeRequest): Promise<BedrockInvokeResponse>;
|
|
150
|
+
private fallbackToOpenAI;
|
|
151
|
+
private fallbackToBedrock;
|
|
152
|
+
private fallbackToOpenAICloud;
|
|
153
|
+
private fallbackToBedrockCloud;
|
|
154
|
+
private invokeBedrockCloud;
|
|
155
|
+
private mapModelToOpenAI;
|
|
156
|
+
static openaiCompatible(config: {
|
|
157
|
+
apiKey: string;
|
|
158
|
+
apiUrl?: string;
|
|
159
|
+
fallback?: FallbackConfig;
|
|
160
|
+
}): OpenAICompatibleClient;
|
|
78
161
|
}
|
|
79
162
|
export default SlyOS;
|
|
80
|
-
export type { SlyOSConfig, GenerateOptions, TranscribeOptions, DeviceProfile, ProgressEvent, SlyEvent, QuantizationLevel, ModelCategory };
|
|
163
|
+
export type { SlyOSConfig, SlyOSConfigWithFallback, GenerateOptions, TranscribeOptions, DeviceProfile, ProgressEvent, SlyEvent, QuantizationLevel, ModelCategory, OpenAIMessage, OpenAIChatCompletionRequest, OpenAIChatCompletionResponse, OpenAIChoice, OpenAIUsage, BedrockTextGenerationConfig, BedrockInvokeRequest, BedrockInvokeResponse, BedrockResult, FallbackConfig, FallbackProvider, OpenAICompatibleClient, };
|
package/dist/index.js
CHANGED
|
@@ -153,6 +153,7 @@ class SlyOS {
|
|
|
153
153
|
this.deviceId = `device-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
154
154
|
this.onProgress = config.onProgress || null;
|
|
155
155
|
this.onEvent = config.onEvent || null;
|
|
156
|
+
this.fallbackConfig = config.fallback || null;
|
|
156
157
|
}
|
|
157
158
|
// ── Progress & Event Helpers ────────────────────────────────────
|
|
158
159
|
emitProgress(stage, progress, message, detail) {
|
|
@@ -500,5 +501,277 @@ class SlyOS {
|
|
|
500
501
|
throw error;
|
|
501
502
|
}
|
|
502
503
|
}
|
|
504
|
+
// ── OpenAI Compatibility ────────────────────────────────────────────
|
|
505
|
+
async chatCompletion(modelId, request) {
|
|
506
|
+
try {
|
|
507
|
+
// Convert OpenAI message format to a prompt string
|
|
508
|
+
const prompt = request.messages
|
|
509
|
+
.map(msg => {
|
|
510
|
+
if (msg.role === 'system') {
|
|
511
|
+
return `System: ${msg.content}`;
|
|
512
|
+
}
|
|
513
|
+
else if (msg.role === 'user') {
|
|
514
|
+
return `User: ${msg.content}`;
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
return `Assistant: ${msg.content}`;
|
|
518
|
+
}
|
|
519
|
+
})
|
|
520
|
+
.join('\n\n');
|
|
521
|
+
const response = await this.generate(modelId, prompt, {
|
|
522
|
+
temperature: request.temperature,
|
|
523
|
+
maxTokens: request.max_tokens,
|
|
524
|
+
topP: request.top_p,
|
|
525
|
+
});
|
|
526
|
+
// Estimate token counts (rough approximation: ~4 chars per token)
|
|
527
|
+
const promptTokens = Math.ceil(prompt.length / 4);
|
|
528
|
+
const completionTokens = Math.ceil(response.length / 4);
|
|
529
|
+
return {
|
|
530
|
+
id: `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
531
|
+
object: 'chat.completion',
|
|
532
|
+
created: Math.floor(Date.now() / 1000),
|
|
533
|
+
model: modelId,
|
|
534
|
+
choices: [
|
|
535
|
+
{
|
|
536
|
+
index: 0,
|
|
537
|
+
message: {
|
|
538
|
+
role: 'assistant',
|
|
539
|
+
content: response,
|
|
540
|
+
},
|
|
541
|
+
finish_reason: 'stop',
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
usage: {
|
|
545
|
+
prompt_tokens: promptTokens,
|
|
546
|
+
completion_tokens: completionTokens,
|
|
547
|
+
total_tokens: promptTokens + completionTokens,
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
// Fallback to cloud provider if configured
|
|
553
|
+
if (this.fallbackConfig?.provider === 'openai') {
|
|
554
|
+
return this.fallbackToOpenAI(modelId, request);
|
|
555
|
+
}
|
|
556
|
+
else if (this.fallbackConfig?.provider === 'bedrock') {
|
|
557
|
+
return this.fallbackToBedrock(modelId, request);
|
|
558
|
+
}
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
// ── AWS Bedrock Compatibility ──────────────────────────────────────
|
|
563
|
+
async bedrockInvoke(modelId, request) {
|
|
564
|
+
try {
|
|
565
|
+
const response = await this.generate(modelId, request.inputText, {
|
|
566
|
+
temperature: request.textGenerationConfig?.temperature,
|
|
567
|
+
maxTokens: request.textGenerationConfig?.maxTokenCount,
|
|
568
|
+
topP: request.textGenerationConfig?.topP,
|
|
569
|
+
});
|
|
570
|
+
// Estimate token counts
|
|
571
|
+
const inputTokens = Math.ceil(request.inputText.length / 4);
|
|
572
|
+
const outputTokens = Math.ceil(response.length / 4);
|
|
573
|
+
return {
|
|
574
|
+
results: [
|
|
575
|
+
{
|
|
576
|
+
outputText: response,
|
|
577
|
+
tokenCount: outputTokens,
|
|
578
|
+
},
|
|
579
|
+
],
|
|
580
|
+
input_text_token_count: inputTokens,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
// Fallback to cloud provider if configured
|
|
585
|
+
if (this.fallbackConfig?.provider === 'bedrock') {
|
|
586
|
+
return this.fallbackToBedrockCloud(modelId, request);
|
|
587
|
+
}
|
|
588
|
+
else if (this.fallbackConfig?.provider === 'openai') {
|
|
589
|
+
return this.fallbackToOpenAICloud(modelId, request);
|
|
590
|
+
}
|
|
591
|
+
throw error;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
// ── Fallback: OpenAI Cloud ────────────────────────────────────────
|
|
595
|
+
async fallbackToOpenAI(modelId, request) {
|
|
596
|
+
if (!this.fallbackConfig) {
|
|
597
|
+
throw new Error('OpenAI fallback not configured');
|
|
598
|
+
}
|
|
599
|
+
const mappedModel = this.mapModelToOpenAI(modelId);
|
|
600
|
+
const payload = {
|
|
601
|
+
model: this.fallbackConfig.model || mappedModel,
|
|
602
|
+
messages: request.messages,
|
|
603
|
+
temperature: request.temperature,
|
|
604
|
+
max_tokens: request.max_tokens,
|
|
605
|
+
top_p: request.top_p,
|
|
606
|
+
frequency_penalty: request.frequency_penalty,
|
|
607
|
+
presence_penalty: request.presence_penalty,
|
|
608
|
+
stop: request.stop,
|
|
609
|
+
};
|
|
610
|
+
try {
|
|
611
|
+
const response = await axios.post('https://api.openai.com/v1/chat/completions', payload, {
|
|
612
|
+
headers: {
|
|
613
|
+
Authorization: `Bearer ${this.fallbackConfig.apiKey}`,
|
|
614
|
+
'Content-Type': 'application/json',
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
this.emitEvent('fallback_success', { provider: 'openai', originalModel: modelId, mappedModel: this.fallbackConfig.model });
|
|
618
|
+
return response.data;
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
this.emitProgress('error', 0, `OpenAI fallback failed: ${error.message}`);
|
|
622
|
+
this.emitEvent('fallback_error', { provider: 'openai', error: error.message });
|
|
623
|
+
throw error;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
async fallbackToBedrock(modelId, request) {
|
|
627
|
+
if (!this.fallbackConfig) {
|
|
628
|
+
throw new Error('Bedrock fallback not configured');
|
|
629
|
+
}
|
|
630
|
+
// Convert OpenAI format to Bedrock's expected format (simplified)
|
|
631
|
+
const lastMessage = request.messages[request.messages.length - 1];
|
|
632
|
+
const inputText = lastMessage.content;
|
|
633
|
+
const bedrockResponse = await this.invokeBedrockCloud(inputText, {
|
|
634
|
+
temperature: request.temperature,
|
|
635
|
+
maxTokenCount: request.max_tokens,
|
|
636
|
+
topP: request.top_p,
|
|
637
|
+
});
|
|
638
|
+
// Convert Bedrock response back to OpenAI format
|
|
639
|
+
const promptTokens = Math.ceil(inputText.length / 4);
|
|
640
|
+
const completionTokens = bedrockResponse.results[0].tokenCount;
|
|
641
|
+
this.emitEvent('fallback_success', { provider: 'bedrock', originalModel: modelId, mappedModel: this.fallbackConfig.model });
|
|
642
|
+
return {
|
|
643
|
+
id: `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
644
|
+
object: 'chat.completion',
|
|
645
|
+
created: Math.floor(Date.now() / 1000),
|
|
646
|
+
model: modelId,
|
|
647
|
+
choices: [
|
|
648
|
+
{
|
|
649
|
+
index: 0,
|
|
650
|
+
message: {
|
|
651
|
+
role: 'assistant',
|
|
652
|
+
content: bedrockResponse.results[0].outputText,
|
|
653
|
+
},
|
|
654
|
+
finish_reason: 'stop',
|
|
655
|
+
},
|
|
656
|
+
],
|
|
657
|
+
usage: {
|
|
658
|
+
prompt_tokens: promptTokens,
|
|
659
|
+
completion_tokens: completionTokens,
|
|
660
|
+
total_tokens: promptTokens + completionTokens,
|
|
661
|
+
},
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
async fallbackToOpenAICloud(modelId, request) {
|
|
665
|
+
if (!this.fallbackConfig) {
|
|
666
|
+
throw new Error('OpenAI fallback not configured');
|
|
667
|
+
}
|
|
668
|
+
const mappedModel = this.mapModelToOpenAI(modelId);
|
|
669
|
+
const payload = {
|
|
670
|
+
model: this.fallbackConfig.model || mappedModel,
|
|
671
|
+
messages: [{ role: 'user', content: request.inputText }],
|
|
672
|
+
temperature: request.textGenerationConfig?.temperature,
|
|
673
|
+
max_tokens: request.textGenerationConfig?.maxTokenCount,
|
|
674
|
+
top_p: request.textGenerationConfig?.topP,
|
|
675
|
+
};
|
|
676
|
+
try {
|
|
677
|
+
const response = await axios.post('https://api.openai.com/v1/chat/completions', payload, {
|
|
678
|
+
headers: {
|
|
679
|
+
Authorization: `Bearer ${this.fallbackConfig.apiKey}`,
|
|
680
|
+
'Content-Type': 'application/json',
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
const outputText = response.data.choices[0].message.content;
|
|
684
|
+
const inputTokens = Math.ceil(request.inputText.length / 4);
|
|
685
|
+
const outputTokens = response.data.usage.completion_tokens;
|
|
686
|
+
this.emitEvent('fallback_success', { provider: 'openai', originalModel: modelId, mappedModel: this.fallbackConfig.model });
|
|
687
|
+
return {
|
|
688
|
+
results: [
|
|
689
|
+
{
|
|
690
|
+
outputText,
|
|
691
|
+
tokenCount: outputTokens,
|
|
692
|
+
},
|
|
693
|
+
],
|
|
694
|
+
input_text_token_count: inputTokens,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
this.emitProgress('error', 0, `OpenAI fallback failed: ${error.message}`);
|
|
699
|
+
this.emitEvent('fallback_error', { provider: 'openai', error: error.message });
|
|
700
|
+
throw error;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
async fallbackToBedrockCloud(modelId, request) {
|
|
704
|
+
if (!this.fallbackConfig) {
|
|
705
|
+
throw new Error('Bedrock fallback not configured');
|
|
706
|
+
}
|
|
707
|
+
try {
|
|
708
|
+
return await this.invokeBedrockCloud(request.inputText, request.textGenerationConfig);
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
this.emitProgress('error', 0, `Bedrock fallback failed: ${error.message}`);
|
|
712
|
+
this.emitEvent('fallback_error', { provider: 'bedrock', error: error.message });
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
async invokeBedrockCloud(inputText, config) {
|
|
717
|
+
if (!this.fallbackConfig) {
|
|
718
|
+
throw new Error('Bedrock fallback not configured');
|
|
719
|
+
}
|
|
720
|
+
const region = this.fallbackConfig.region || 'us-east-1';
|
|
721
|
+
const model = this.fallbackConfig.model || 'anthropic.claude-3-sonnet-20240229-v1:0';
|
|
722
|
+
// Bedrock endpoint format: https://bedrock-runtime.{region}.amazonaws.com/model/{modelId}/invoke
|
|
723
|
+
const endpoint = `https://bedrock-runtime.${region}.amazonaws.com/model/${model}/invoke`;
|
|
724
|
+
const payload = {
|
|
725
|
+
inputText,
|
|
726
|
+
textGenerationConfig: {
|
|
727
|
+
maxTokenCount: config?.maxTokenCount || 256,
|
|
728
|
+
temperature: config?.temperature || 0.7,
|
|
729
|
+
topP: config?.topP || 0.9,
|
|
730
|
+
topK: config?.topK,
|
|
731
|
+
stopSequences: config?.stopSequences,
|
|
732
|
+
},
|
|
733
|
+
};
|
|
734
|
+
try {
|
|
735
|
+
const response = await axios.post(endpoint, payload, {
|
|
736
|
+
headers: {
|
|
737
|
+
Authorization: `Bearer ${this.fallbackConfig.apiKey}`,
|
|
738
|
+
'Content-Type': 'application/json',
|
|
739
|
+
'X-Amz-Target': 'AmazonBedrockRuntime.InvokeModel',
|
|
740
|
+
},
|
|
741
|
+
});
|
|
742
|
+
this.emitEvent('fallback_success', { provider: 'bedrock', model });
|
|
743
|
+
return response.data;
|
|
744
|
+
}
|
|
745
|
+
catch (error) {
|
|
746
|
+
throw new Error(`Bedrock invocation failed: ${error.message}`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
mapModelToOpenAI(slyModelId) {
|
|
750
|
+
const modelMapping = {
|
|
751
|
+
'quantum-1.7b': 'gpt-4o-mini',
|
|
752
|
+
'quantum-3b': 'gpt-4o',
|
|
753
|
+
'quantum-code-3b': 'gpt-4o',
|
|
754
|
+
'quantum-8b': 'gpt-4-turbo',
|
|
755
|
+
};
|
|
756
|
+
return modelMapping[slyModelId] || 'gpt-4o-mini';
|
|
757
|
+
}
|
|
758
|
+
// ── Static OpenAI Compatible Factory ────────────────────────────────
|
|
759
|
+
static openaiCompatible(config) {
|
|
760
|
+
const instance = new SlyOS({
|
|
761
|
+
apiKey: config.apiKey,
|
|
762
|
+
apiUrl: config.apiUrl,
|
|
763
|
+
fallback: { ...config.fallback, provider: config.fallback?.provider || 'openai' },
|
|
764
|
+
});
|
|
765
|
+
return {
|
|
766
|
+
chat: {
|
|
767
|
+
completions: {
|
|
768
|
+
async create(request) {
|
|
769
|
+
const { model, ...chatRequest } = request;
|
|
770
|
+
return instance.chatCompletion(model, chatRequest);
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
},
|
|
774
|
+
};
|
|
775
|
+
}
|
|
503
776
|
}
|
|
504
777
|
export default SlyOS;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -55,7 +55,7 @@ interface ProgressEvent {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
interface SlyEvent {
|
|
58
|
-
type: 'auth' | 'device_registered' | 'device_profiled' | 'model_download_start' | 'model_download_progress' | 'model_loaded' | 'inference_start' | 'inference_complete' | 'error';
|
|
58
|
+
type: 'auth' | 'device_registered' | 'device_profiled' | 'model_download_start' | 'model_download_progress' | 'model_loaded' | 'inference_start' | 'inference_complete' | 'error' | 'fallback_success' | 'fallback_error';
|
|
59
59
|
data?: any;
|
|
60
60
|
timestamp: number;
|
|
61
61
|
}
|
|
@@ -63,6 +63,94 @@ interface SlyEvent {
|
|
|
63
63
|
type ProgressCallback = (event: ProgressEvent) => void;
|
|
64
64
|
type EventCallback = (event: SlyEvent) => void;
|
|
65
65
|
|
|
66
|
+
// ─── OpenAI Compatibility Types ──────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
interface OpenAIMessage {
|
|
69
|
+
role: 'system' | 'user' | 'assistant';
|
|
70
|
+
content: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface OpenAIChatCompletionRequest {
|
|
74
|
+
messages: OpenAIMessage[];
|
|
75
|
+
temperature?: number;
|
|
76
|
+
top_p?: number;
|
|
77
|
+
max_tokens?: number;
|
|
78
|
+
frequency_penalty?: number;
|
|
79
|
+
presence_penalty?: number;
|
|
80
|
+
stop?: string | string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface OpenAIChoice {
|
|
84
|
+
index: number;
|
|
85
|
+
message: OpenAIMessage;
|
|
86
|
+
finish_reason: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface OpenAIUsage {
|
|
90
|
+
prompt_tokens: number;
|
|
91
|
+
completion_tokens: number;
|
|
92
|
+
total_tokens: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface OpenAIChatCompletionResponse {
|
|
96
|
+
id: string;
|
|
97
|
+
object: 'chat.completion';
|
|
98
|
+
created: number;
|
|
99
|
+
model: string;
|
|
100
|
+
choices: OpenAIChoice[];
|
|
101
|
+
usage: OpenAIUsage;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── AWS Bedrock Compatibility Types ─────────────────────────────────
|
|
105
|
+
|
|
106
|
+
interface BedrockTextGenerationConfig {
|
|
107
|
+
maxTokenCount?: number;
|
|
108
|
+
temperature?: number;
|
|
109
|
+
topP?: number;
|
|
110
|
+
topK?: number;
|
|
111
|
+
stopSequences?: string[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface BedrockInvokeRequest {
|
|
115
|
+
inputText: string;
|
|
116
|
+
textGenerationConfig?: BedrockTextGenerationConfig;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface BedrockResult {
|
|
120
|
+
outputText: string;
|
|
121
|
+
tokenCount: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface BedrockInvokeResponse {
|
|
125
|
+
results: BedrockResult[];
|
|
126
|
+
input_text_token_count?: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ─── Fallback Configuration ─────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
type FallbackProvider = 'openai' | 'bedrock';
|
|
132
|
+
|
|
133
|
+
interface FallbackConfig {
|
|
134
|
+
provider: FallbackProvider;
|
|
135
|
+
apiKey: string;
|
|
136
|
+
model: string;
|
|
137
|
+
region?: string; // for Bedrock
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface SlyOSConfigWithFallback extends SlyOSConfig {
|
|
141
|
+
fallback?: FallbackConfig;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─── OpenAI Compatible Client ───────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
interface OpenAICompatibleClient {
|
|
147
|
+
chat: {
|
|
148
|
+
completions: {
|
|
149
|
+
create(request: OpenAIChatCompletionRequest & { model: string }): Promise<OpenAIChatCompletionResponse>;
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
66
154
|
// ─── Model Registry ─────────────────────────────────────────────────
|
|
67
155
|
|
|
68
156
|
const modelMap: Record<string, ModelInfo> = {
|
|
@@ -218,13 +306,15 @@ class SlyOS {
|
|
|
218
306
|
private deviceProfile: DeviceProfile | null = null;
|
|
219
307
|
private onProgress: ProgressCallback | null;
|
|
220
308
|
private onEvent: EventCallback | null;
|
|
309
|
+
private fallbackConfig: FallbackConfig | null;
|
|
221
310
|
|
|
222
|
-
constructor(config:
|
|
311
|
+
constructor(config: SlyOSConfigWithFallback) {
|
|
223
312
|
this.apiKey = config.apiKey;
|
|
224
313
|
this.apiUrl = config.apiUrl || 'https://api.slyos.world';
|
|
225
314
|
this.deviceId = `device-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
226
315
|
this.onProgress = config.onProgress || null;
|
|
227
316
|
this.onEvent = config.onEvent || null;
|
|
317
|
+
this.fallbackConfig = config.fallback || null;
|
|
228
318
|
}
|
|
229
319
|
|
|
230
320
|
// ── Progress & Event Helpers ────────────────────────────────────
|
|
@@ -625,7 +715,329 @@ class SlyOS {
|
|
|
625
715
|
throw error;
|
|
626
716
|
}
|
|
627
717
|
}
|
|
718
|
+
|
|
719
|
+
// ── OpenAI Compatibility ────────────────────────────────────────────
|
|
720
|
+
|
|
721
|
+
async chatCompletion(modelId: string, request: OpenAIChatCompletionRequest): Promise<OpenAIChatCompletionResponse> {
|
|
722
|
+
try {
|
|
723
|
+
// Convert OpenAI message format to a prompt string
|
|
724
|
+
const prompt = request.messages
|
|
725
|
+
.map(msg => {
|
|
726
|
+
if (msg.role === 'system') {
|
|
727
|
+
return `System: ${msg.content}`;
|
|
728
|
+
} else if (msg.role === 'user') {
|
|
729
|
+
return `User: ${msg.content}`;
|
|
730
|
+
} else {
|
|
731
|
+
return `Assistant: ${msg.content}`;
|
|
732
|
+
}
|
|
733
|
+
})
|
|
734
|
+
.join('\n\n');
|
|
735
|
+
|
|
736
|
+
const response = await this.generate(modelId, prompt, {
|
|
737
|
+
temperature: request.temperature,
|
|
738
|
+
maxTokens: request.max_tokens,
|
|
739
|
+
topP: request.top_p,
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
// Estimate token counts (rough approximation: ~4 chars per token)
|
|
743
|
+
const promptTokens = Math.ceil(prompt.length / 4);
|
|
744
|
+
const completionTokens = Math.ceil(response.length / 4);
|
|
745
|
+
|
|
746
|
+
return {
|
|
747
|
+
id: `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
748
|
+
object: 'chat.completion',
|
|
749
|
+
created: Math.floor(Date.now() / 1000),
|
|
750
|
+
model: modelId,
|
|
751
|
+
choices: [
|
|
752
|
+
{
|
|
753
|
+
index: 0,
|
|
754
|
+
message: {
|
|
755
|
+
role: 'assistant',
|
|
756
|
+
content: response,
|
|
757
|
+
},
|
|
758
|
+
finish_reason: 'stop',
|
|
759
|
+
},
|
|
760
|
+
],
|
|
761
|
+
usage: {
|
|
762
|
+
prompt_tokens: promptTokens,
|
|
763
|
+
completion_tokens: completionTokens,
|
|
764
|
+
total_tokens: promptTokens + completionTokens,
|
|
765
|
+
},
|
|
766
|
+
};
|
|
767
|
+
} catch (error: any) {
|
|
768
|
+
// Fallback to cloud provider if configured
|
|
769
|
+
if (this.fallbackConfig?.provider === 'openai') {
|
|
770
|
+
return this.fallbackToOpenAI(modelId, request);
|
|
771
|
+
} else if (this.fallbackConfig?.provider === 'bedrock') {
|
|
772
|
+
return this.fallbackToBedrock(modelId, request);
|
|
773
|
+
}
|
|
774
|
+
throw error;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// ── AWS Bedrock Compatibility ──────────────────────────────────────
|
|
779
|
+
|
|
780
|
+
async bedrockInvoke(modelId: string, request: BedrockInvokeRequest): Promise<BedrockInvokeResponse> {
|
|
781
|
+
try {
|
|
782
|
+
const response = await this.generate(modelId, request.inputText, {
|
|
783
|
+
temperature: request.textGenerationConfig?.temperature,
|
|
784
|
+
maxTokens: request.textGenerationConfig?.maxTokenCount,
|
|
785
|
+
topP: request.textGenerationConfig?.topP,
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
// Estimate token counts
|
|
789
|
+
const inputTokens = Math.ceil(request.inputText.length / 4);
|
|
790
|
+
const outputTokens = Math.ceil(response.length / 4);
|
|
791
|
+
|
|
792
|
+
return {
|
|
793
|
+
results: [
|
|
794
|
+
{
|
|
795
|
+
outputText: response,
|
|
796
|
+
tokenCount: outputTokens,
|
|
797
|
+
},
|
|
798
|
+
],
|
|
799
|
+
input_text_token_count: inputTokens,
|
|
800
|
+
};
|
|
801
|
+
} catch (error: any) {
|
|
802
|
+
// Fallback to cloud provider if configured
|
|
803
|
+
if (this.fallbackConfig?.provider === 'bedrock') {
|
|
804
|
+
return this.fallbackToBedrockCloud(modelId, request);
|
|
805
|
+
} else if (this.fallbackConfig?.provider === 'openai') {
|
|
806
|
+
return this.fallbackToOpenAICloud(modelId, request);
|
|
807
|
+
}
|
|
808
|
+
throw error;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// ── Fallback: OpenAI Cloud ────────────────────────────────────────
|
|
813
|
+
|
|
814
|
+
private async fallbackToOpenAI(modelId: string, request: OpenAIChatCompletionRequest): Promise<OpenAIChatCompletionResponse> {
|
|
815
|
+
if (!this.fallbackConfig) {
|
|
816
|
+
throw new Error('OpenAI fallback not configured');
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const mappedModel = this.mapModelToOpenAI(modelId);
|
|
820
|
+
const payload = {
|
|
821
|
+
model: this.fallbackConfig.model || mappedModel,
|
|
822
|
+
messages: request.messages,
|
|
823
|
+
temperature: request.temperature,
|
|
824
|
+
max_tokens: request.max_tokens,
|
|
825
|
+
top_p: request.top_p,
|
|
826
|
+
frequency_penalty: request.frequency_penalty,
|
|
827
|
+
presence_penalty: request.presence_penalty,
|
|
828
|
+
stop: request.stop,
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
try {
|
|
832
|
+
const response = await axios.post('https://api.openai.com/v1/chat/completions', payload, {
|
|
833
|
+
headers: {
|
|
834
|
+
Authorization: `Bearer ${this.fallbackConfig.apiKey}`,
|
|
835
|
+
'Content-Type': 'application/json',
|
|
836
|
+
},
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
this.emitEvent('fallback_success', { provider: 'openai', originalModel: modelId, mappedModel: this.fallbackConfig.model });
|
|
840
|
+
return response.data;
|
|
841
|
+
} catch (error: any) {
|
|
842
|
+
this.emitProgress('error', 0, `OpenAI fallback failed: ${error.message}`);
|
|
843
|
+
this.emitEvent('fallback_error', { provider: 'openai', error: error.message });
|
|
844
|
+
throw error;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
private async fallbackToBedrock(modelId: string, request: OpenAIChatCompletionRequest): Promise<OpenAIChatCompletionResponse> {
|
|
849
|
+
if (!this.fallbackConfig) {
|
|
850
|
+
throw new Error('Bedrock fallback not configured');
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Convert OpenAI format to Bedrock's expected format (simplified)
|
|
854
|
+
const lastMessage = request.messages[request.messages.length - 1];
|
|
855
|
+
const inputText = lastMessage.content;
|
|
856
|
+
|
|
857
|
+
const bedrockResponse = await this.invokeBedrockCloud(inputText, {
|
|
858
|
+
temperature: request.temperature,
|
|
859
|
+
maxTokenCount: request.max_tokens,
|
|
860
|
+
topP: request.top_p,
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
// Convert Bedrock response back to OpenAI format
|
|
864
|
+
const promptTokens = Math.ceil(inputText.length / 4);
|
|
865
|
+
const completionTokens = bedrockResponse.results[0].tokenCount;
|
|
866
|
+
|
|
867
|
+
this.emitEvent('fallback_success', { provider: 'bedrock', originalModel: modelId, mappedModel: this.fallbackConfig.model });
|
|
868
|
+
|
|
869
|
+
return {
|
|
870
|
+
id: `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
871
|
+
object: 'chat.completion',
|
|
872
|
+
created: Math.floor(Date.now() / 1000),
|
|
873
|
+
model: modelId,
|
|
874
|
+
choices: [
|
|
875
|
+
{
|
|
876
|
+
index: 0,
|
|
877
|
+
message: {
|
|
878
|
+
role: 'assistant',
|
|
879
|
+
content: bedrockResponse.results[0].outputText,
|
|
880
|
+
},
|
|
881
|
+
finish_reason: 'stop',
|
|
882
|
+
},
|
|
883
|
+
],
|
|
884
|
+
usage: {
|
|
885
|
+
prompt_tokens: promptTokens,
|
|
886
|
+
completion_tokens: completionTokens,
|
|
887
|
+
total_tokens: promptTokens + completionTokens,
|
|
888
|
+
},
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
private async fallbackToOpenAICloud(modelId: string, request: BedrockInvokeRequest): Promise<BedrockInvokeResponse> {
|
|
893
|
+
if (!this.fallbackConfig) {
|
|
894
|
+
throw new Error('OpenAI fallback not configured');
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const mappedModel = this.mapModelToOpenAI(modelId);
|
|
898
|
+
const payload = {
|
|
899
|
+
model: this.fallbackConfig.model || mappedModel,
|
|
900
|
+
messages: [{ role: 'user', content: request.inputText }],
|
|
901
|
+
temperature: request.textGenerationConfig?.temperature,
|
|
902
|
+
max_tokens: request.textGenerationConfig?.maxTokenCount,
|
|
903
|
+
top_p: request.textGenerationConfig?.topP,
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
try {
|
|
907
|
+
const response = await axios.post('https://api.openai.com/v1/chat/completions', payload, {
|
|
908
|
+
headers: {
|
|
909
|
+
Authorization: `Bearer ${this.fallbackConfig.apiKey}`,
|
|
910
|
+
'Content-Type': 'application/json',
|
|
911
|
+
},
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
const outputText = response.data.choices[0].message.content;
|
|
915
|
+
const inputTokens = Math.ceil(request.inputText.length / 4);
|
|
916
|
+
const outputTokens = response.data.usage.completion_tokens;
|
|
917
|
+
|
|
918
|
+
this.emitEvent('fallback_success', { provider: 'openai', originalModel: modelId, mappedModel: this.fallbackConfig.model });
|
|
919
|
+
|
|
920
|
+
return {
|
|
921
|
+
results: [
|
|
922
|
+
{
|
|
923
|
+
outputText,
|
|
924
|
+
tokenCount: outputTokens,
|
|
925
|
+
},
|
|
926
|
+
],
|
|
927
|
+
input_text_token_count: inputTokens,
|
|
928
|
+
};
|
|
929
|
+
} catch (error: any) {
|
|
930
|
+
this.emitProgress('error', 0, `OpenAI fallback failed: ${error.message}`);
|
|
931
|
+
this.emitEvent('fallback_error', { provider: 'openai', error: error.message });
|
|
932
|
+
throw error;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
private async fallbackToBedrockCloud(modelId: string, request: BedrockInvokeRequest): Promise<BedrockInvokeResponse> {
|
|
937
|
+
if (!this.fallbackConfig) {
|
|
938
|
+
throw new Error('Bedrock fallback not configured');
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
try {
|
|
942
|
+
return await this.invokeBedrockCloud(request.inputText, request.textGenerationConfig);
|
|
943
|
+
} catch (error: any) {
|
|
944
|
+
this.emitProgress('error', 0, `Bedrock fallback failed: ${error.message}`);
|
|
945
|
+
this.emitEvent('fallback_error', { provider: 'bedrock', error: error.message });
|
|
946
|
+
throw error;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
private async invokeBedrockCloud(inputText: string, config?: BedrockTextGenerationConfig): Promise<BedrockInvokeResponse> {
|
|
951
|
+
if (!this.fallbackConfig) {
|
|
952
|
+
throw new Error('Bedrock fallback not configured');
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
const region = this.fallbackConfig.region || 'us-east-1';
|
|
956
|
+
const model = this.fallbackConfig.model || 'anthropic.claude-3-sonnet-20240229-v1:0';
|
|
957
|
+
|
|
958
|
+
// Bedrock endpoint format: https://bedrock-runtime.{region}.amazonaws.com/model/{modelId}/invoke
|
|
959
|
+
const endpoint = `https://bedrock-runtime.${region}.amazonaws.com/model/${model}/invoke`;
|
|
960
|
+
|
|
961
|
+
const payload = {
|
|
962
|
+
inputText,
|
|
963
|
+
textGenerationConfig: {
|
|
964
|
+
maxTokenCount: config?.maxTokenCount || 256,
|
|
965
|
+
temperature: config?.temperature || 0.7,
|
|
966
|
+
topP: config?.topP || 0.9,
|
|
967
|
+
topK: config?.topK,
|
|
968
|
+
stopSequences: config?.stopSequences,
|
|
969
|
+
},
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
try {
|
|
973
|
+
const response = await axios.post(endpoint, payload, {
|
|
974
|
+
headers: {
|
|
975
|
+
Authorization: `Bearer ${this.fallbackConfig.apiKey}`,
|
|
976
|
+
'Content-Type': 'application/json',
|
|
977
|
+
'X-Amz-Target': 'AmazonBedrockRuntime.InvokeModel',
|
|
978
|
+
},
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
this.emitEvent('fallback_success', { provider: 'bedrock', model });
|
|
982
|
+
return response.data;
|
|
983
|
+
} catch (error: any) {
|
|
984
|
+
throw new Error(`Bedrock invocation failed: ${error.message}`);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
private mapModelToOpenAI(slyModelId: string): string {
|
|
989
|
+
const modelMapping: Record<string, string> = {
|
|
990
|
+
'quantum-1.7b': 'gpt-4o-mini',
|
|
991
|
+
'quantum-3b': 'gpt-4o',
|
|
992
|
+
'quantum-code-3b': 'gpt-4o',
|
|
993
|
+
'quantum-8b': 'gpt-4-turbo',
|
|
994
|
+
};
|
|
995
|
+
return modelMapping[slyModelId] || 'gpt-4o-mini';
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// ── Static OpenAI Compatible Factory ────────────────────────────────
|
|
999
|
+
|
|
1000
|
+
static openaiCompatible(config: { apiKey: string; apiUrl?: string; fallback?: FallbackConfig }): OpenAICompatibleClient {
|
|
1001
|
+
const instance = new SlyOS({
|
|
1002
|
+
apiKey: config.apiKey,
|
|
1003
|
+
apiUrl: config.apiUrl,
|
|
1004
|
+
fallback: { ...config.fallback, provider: config.fallback?.provider || 'openai' } as FallbackConfig,
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
return {
|
|
1008
|
+
chat: {
|
|
1009
|
+
completions: {
|
|
1010
|
+
async create(request: OpenAIChatCompletionRequest & { model: string }): Promise<OpenAIChatCompletionResponse> {
|
|
1011
|
+
const { model, ...chatRequest } = request;
|
|
1012
|
+
return instance.chatCompletion(model, chatRequest);
|
|
1013
|
+
},
|
|
1014
|
+
},
|
|
1015
|
+
},
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
628
1018
|
}
|
|
629
1019
|
|
|
630
1020
|
export default SlyOS;
|
|
631
|
-
export type {
|
|
1021
|
+
export type {
|
|
1022
|
+
SlyOSConfig,
|
|
1023
|
+
SlyOSConfigWithFallback,
|
|
1024
|
+
GenerateOptions,
|
|
1025
|
+
TranscribeOptions,
|
|
1026
|
+
DeviceProfile,
|
|
1027
|
+
ProgressEvent,
|
|
1028
|
+
SlyEvent,
|
|
1029
|
+
QuantizationLevel,
|
|
1030
|
+
ModelCategory,
|
|
1031
|
+
OpenAIMessage,
|
|
1032
|
+
OpenAIChatCompletionRequest,
|
|
1033
|
+
OpenAIChatCompletionResponse,
|
|
1034
|
+
OpenAIChoice,
|
|
1035
|
+
OpenAIUsage,
|
|
1036
|
+
BedrockTextGenerationConfig,
|
|
1037
|
+
BedrockInvokeRequest,
|
|
1038
|
+
BedrockInvokeResponse,
|
|
1039
|
+
BedrockResult,
|
|
1040
|
+
FallbackConfig,
|
|
1041
|
+
FallbackProvider,
|
|
1042
|
+
OpenAICompatibleClient,
|
|
1043
|
+
};
|