@luanpoppe/ai 1.1.5 → 1.1.6
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/.env.example +13 -1
- package/README.md +795 -0
- package/dist/@types/embedding-model-names.d.ts +11 -0
- package/dist/@types/embedding-model-names.d.ts.map +1 -0
- package/dist/@types/embedding-model-names.js +3 -0
- package/dist/@types/embedding-model-names.js.map +1 -0
- package/dist/@types/model-names.d.ts +5 -1
- package/dist/@types/model-names.d.ts.map +1 -1
- package/dist/@types/openrouter-provider.d.ts +25 -0
- package/dist/@types/openrouter-provider.d.ts.map +1 -0
- package/dist/@types/openrouter-provider.js +3 -0
- package/dist/@types/openrouter-provider.js.map +1 -0
- package/dist/ai.d.ts +7 -0
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +56 -5
- package/dist/ai.js.map +1 -1
- package/dist/audio/gemini-transcription.d.ts +4 -0
- package/dist/audio/gemini-transcription.d.ts.map +1 -0
- package/dist/audio/gemini-transcription.js +37 -0
- package/dist/audio/gemini-transcription.js.map +1 -0
- package/dist/audio/index.d.ts +33 -0
- package/dist/audio/index.d.ts.map +1 -0
- package/dist/audio/index.js +43 -0
- package/dist/audio/index.js.map +1 -0
- package/dist/audio/openrouter-client.d.ts +2 -0
- package/dist/audio/openrouter-client.d.ts.map +1 -0
- package/dist/audio/openrouter-client.js +7 -0
- package/dist/audio/openrouter-client.js.map +1 -0
- package/dist/audio/speech-openai.d.ts +4 -0
- package/dist/audio/speech-openai.d.ts.map +1 -0
- package/dist/audio/speech-openai.js +65 -0
- package/dist/audio/speech-openai.js.map +1 -0
- package/dist/audio/speech-openrouter.d.ts +4 -0
- package/dist/audio/speech-openrouter.d.ts.map +1 -0
- package/dist/audio/speech-openrouter.js +89 -0
- package/dist/audio/speech-openrouter.js.map +1 -0
- package/dist/audio/transcription-openai.d.ts +11 -0
- package/dist/audio/transcription-openai.d.ts.map +1 -0
- package/dist/audio/transcription-openai.js +204 -0
- package/dist/audio/transcription-openai.js.map +1 -0
- package/dist/audio/transcription-openrouter.d.ts +4 -0
- package/dist/audio/transcription-openrouter.d.ts.map +1 -0
- package/dist/audio/transcription-openrouter.js +44 -0
- package/dist/audio/transcription-openrouter.js.map +1 -0
- package/dist/audio/types.d.ts +92 -0
- package/dist/audio/types.d.ts.map +1 -0
- package/dist/audio/types.js +6 -0
- package/dist/audio/types.js.map +1 -0
- package/dist/audio/utils.d.ts +5 -0
- package/dist/audio/utils.d.ts.map +1 -0
- package/dist/audio/utils.js +25 -0
- package/dist/audio/utils.js.map +1 -0
- package/dist/embeddings/embed-gemini.d.ts +3 -0
- package/dist/embeddings/embed-gemini.d.ts.map +1 -0
- package/dist/embeddings/embed-gemini.js +38 -0
- package/dist/embeddings/embed-gemini.js.map +1 -0
- package/dist/embeddings/embed-openai.d.ts +3 -0
- package/dist/embeddings/embed-openai.d.ts.map +1 -0
- package/dist/embeddings/embed-openai.js +44 -0
- package/dist/embeddings/embed-openai.js.map +1 -0
- package/dist/embeddings/embed-openrouter.d.ts +3 -0
- package/dist/embeddings/embed-openrouter.d.ts.map +1 -0
- package/dist/embeddings/embed-openrouter.js +47 -0
- package/dist/embeddings/embed-openrouter.js.map +1 -0
- package/dist/embeddings/index.d.ts +19 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +36 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/route.d.ts +12 -0
- package/dist/embeddings/route.d.ts.map +1 -0
- package/dist/embeddings/route.js +42 -0
- package/dist/embeddings/route.js.map +1 -0
- package/dist/embeddings/types.d.ts +37 -0
- package/dist/embeddings/types.d.ts.map +1 -0
- package/dist/embeddings/types.js +6 -0
- package/dist/embeddings/types.js.map +1 -0
- package/dist/embeddings/utils.d.ts +6 -0
- package/dist/embeddings/utils.d.ts.map +1 -0
- package/dist/embeddings/utils.js +25 -0
- package/dist/embeddings/utils.js.map +1 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -3
- package/dist/index.js.map +1 -1
- package/dist/langchain/audio-transcription.d.ts +8 -13
- package/dist/langchain/audio-transcription.d.ts.map +1 -1
- package/dist/langchain/audio-transcription.js +8 -92
- package/dist/langchain/audio-transcription.js.map +1 -1
- package/dist/langchain/messages.d.ts +6 -1
- package/dist/langchain/messages.d.ts.map +1 -1
- package/dist/langchain/messages.js +35 -7
- package/dist/langchain/messages.js.map +1 -1
- package/dist/langchain/models.d.ts +38 -0
- package/dist/langchain/models.d.ts.map +1 -1
- package/dist/langchain/models.js +63 -4
- package/dist/langchain/models.js.map +1 -1
- package/dist/langchain/structured-output.d.ts +9 -0
- package/dist/langchain/structured-output.d.ts.map +1 -0
- package/dist/langchain/structured-output.js +70 -0
- package/dist/langchain/structured-output.js.map +1 -0
- package/dist/langchain/tools.d.ts +1 -1
- package/dist/utils/openrouter-client.d.ts +4 -0
- package/dist/utils/openrouter-client.d.ts.map +1 -0
- package/dist/utils/openrouter-client.js +16 -0
- package/dist/utils/openrouter-client.js.map +1 -0
- package/docs/LANGCHAIN_WHISPER_WINDOWS_BUG.md +2 -0
- package/package.json +4 -4
- package/src/@types/embedding-model-names.ts +36 -0
- package/src/@types/model-names.ts +12 -1
- package/src/@types/openrouter-provider.ts +27 -0
- package/src/ai.ts +89 -10
- package/src/audio/gemini-transcription.ts +47 -0
- package/src/audio/index.ts +101 -0
- package/src/audio/openrouter-client.ts +4 -0
- package/src/audio/speech-openai.ts +79 -0
- package/src/audio/speech-openrouter.ts +121 -0
- package/src/audio/transcription-openai.ts +259 -0
- package/src/audio/transcription-openrouter.ts +59 -0
- package/src/audio/types.ts +127 -0
- package/src/audio/utils.ts +20 -0
- package/src/embeddings/embed-gemini.ts +54 -0
- package/src/embeddings/embed-openai.ts +53 -0
- package/src/embeddings/embed-openrouter.ts +64 -0
- package/src/embeddings/index.ts +83 -0
- package/src/embeddings/route.ts +62 -0
- package/src/embeddings/types.ts +53 -0
- package/src/embeddings/utils.ts +21 -0
- package/src/index.ts +51 -5
- package/src/langchain/audio-transcription.ts +19 -90
- package/src/langchain/messages.ts +84 -12
- package/src/langchain/models.ts +130 -3
- package/src/langchain/structured-output.ts +77 -0
- package/src/utils/openrouter-client.ts +10 -0
- package/tests/e2e/README.md +53 -0
- package/tests/e2e/ai-audio-multiprovider.test.ts +118 -0
- package/tests/e2e/ai-deepseek-structured-output.test.ts +133 -0
- package/tests/e2e/ai-embeddings.test.ts +87 -0
- package/tests/e2e/ai-local-models.test.ts +65 -0
- package/tests/unit/audio/index.test.ts +17 -0
- package/tests/unit/audio/speech-openai.test.ts +74 -0
- package/tests/unit/audio/speech-openrouter.test.ts +88 -0
- package/tests/unit/audio/transcription-openai.test.ts +130 -0
- package/tests/unit/audio/transcription-openrouter.test.ts +82 -0
- package/tests/unit/embeddings/embed-gemini.test.ts +52 -0
- package/tests/unit/embeddings/embed-openai.test.ts +68 -0
- package/tests/unit/embeddings/embed-openrouter.test.ts +62 -0
- package/tests/unit/embeddings/index.test.ts +55 -0
- package/tests/unit/index.test.ts +209 -0
- package/tests/unit/langchain/messages.test.ts +37 -0
- package/tests/unit/langchain/models.test.ts +223 -1
- package/tests/unit/langchain/structured-output.test.ts +49 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { AudioMimeType } from "../@types/audio";
|
|
2
|
+
import type { OpenRouterProviderPreferences } from "../@types/openrouter-provider";
|
|
3
|
+
|
|
4
|
+
export type WhisperModel =
|
|
5
|
+
| "whisper-1"
|
|
6
|
+
| "gpt-4o-transcribe"
|
|
7
|
+
| "gpt-4o-mini-transcribe"
|
|
8
|
+
| "gpt-4o-mini-transcribe-2025-12-15"
|
|
9
|
+
| "gpt-4o-transcribe-diarize";
|
|
10
|
+
|
|
11
|
+
export type OpenAITranscriptionResponseFormat =
|
|
12
|
+
| "json"
|
|
13
|
+
| "text"
|
|
14
|
+
| "srt"
|
|
15
|
+
| "verbose_json"
|
|
16
|
+
| "vtt"
|
|
17
|
+
| "diarized_json";
|
|
18
|
+
|
|
19
|
+
export type OpenAITranscriptionOptions = {
|
|
20
|
+
model?: WhisperModel;
|
|
21
|
+
languageIn2Digits?: string;
|
|
22
|
+
prompt?: string;
|
|
23
|
+
responseFormat?: OpenAITranscriptionResponseFormat;
|
|
24
|
+
temperature?: number;
|
|
25
|
+
timestampGranularities?: ("word" | "segment")[];
|
|
26
|
+
format?: string | AudioMimeType;
|
|
27
|
+
/** Obrigatório para `gpt-4o-transcribe-diarize` em áudio > 30s */
|
|
28
|
+
chunkingStrategy?: "auto" | { type: "server_vad" };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type TranscriptionWord = {
|
|
32
|
+
word: string;
|
|
33
|
+
start: number;
|
|
34
|
+
end: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type TranscriptionSegment = {
|
|
38
|
+
id?: number;
|
|
39
|
+
start: number;
|
|
40
|
+
end: number;
|
|
41
|
+
text: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type DiarizedSegment = {
|
|
45
|
+
speaker: string;
|
|
46
|
+
start: number;
|
|
47
|
+
end: number;
|
|
48
|
+
text: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type OpenAITranscriptionDetailed = {
|
|
52
|
+
text: string;
|
|
53
|
+
language?: string;
|
|
54
|
+
duration?: number;
|
|
55
|
+
words?: TranscriptionWord[];
|
|
56
|
+
segments?: TranscriptionSegment[];
|
|
57
|
+
speakers?: DiarizedSegment[];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type OpenRouterTranscriptionOptions = {
|
|
61
|
+
model: string;
|
|
62
|
+
format?: string | AudioMimeType;
|
|
63
|
+
language?: string;
|
|
64
|
+
temperature?: number;
|
|
65
|
+
openRouterProvider?: OpenRouterProviderPreferences;
|
|
66
|
+
/** Quando true, não aplica default de provider (ex.: deepseek only) */
|
|
67
|
+
openRouterAllowAllProviders?: boolean;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type OpenRouterTranscriptionResult = {
|
|
71
|
+
text: string;
|
|
72
|
+
usage?: {
|
|
73
|
+
seconds?: number;
|
|
74
|
+
cost?: number;
|
|
75
|
+
input_tokens?: number;
|
|
76
|
+
output_tokens?: number;
|
|
77
|
+
total_tokens?: number;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type OpenAISpeechModel =
|
|
82
|
+
| "tts-1"
|
|
83
|
+
| "tts-1-hd"
|
|
84
|
+
| "gpt-4o-mini-tts"
|
|
85
|
+
| "gpt-4o-mini-tts-2025-12-15";
|
|
86
|
+
|
|
87
|
+
export type OpenAISpeechResponseFormat =
|
|
88
|
+
| "mp3"
|
|
89
|
+
| "opus"
|
|
90
|
+
| "aac"
|
|
91
|
+
| "flac"
|
|
92
|
+
| "wav"
|
|
93
|
+
| "pcm";
|
|
94
|
+
|
|
95
|
+
export type OpenAISpeechOptions = {
|
|
96
|
+
model?: OpenAISpeechModel;
|
|
97
|
+
voice: string;
|
|
98
|
+
responseFormat?: OpenAISpeechResponseFormat;
|
|
99
|
+
speed?: number;
|
|
100
|
+
/** Apenas modelos gpt-4o-mini-tts* */
|
|
101
|
+
instructions?: string;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export type OpenRouterSpeechOptions = {
|
|
105
|
+
model: string;
|
|
106
|
+
voice: string;
|
|
107
|
+
responseFormat?: "mp3" | "pcm";
|
|
108
|
+
speed?: number;
|
|
109
|
+
instructions?: string;
|
|
110
|
+
openRouterProvider?: OpenRouterProviderPreferences;
|
|
111
|
+
openRouterAllowAllProviders?: boolean;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export type SpeechResult = {
|
|
115
|
+
audio: Buffer;
|
|
116
|
+
contentType: string;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export type GeminiTranscriptionOptions = {
|
|
120
|
+
model?: string;
|
|
121
|
+
prompt?: string;
|
|
122
|
+
googleGeminiToken?: string;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/** A API pública da Anthropic não oferece STT/TTS nativo (apenas texto/imagem/documento). */
|
|
126
|
+
export const ANTHROPIC_NO_NATIVE_AUDIO =
|
|
127
|
+
"A API da Anthropic não suporta áudio nativo. Use STT (OpenAI/OpenRouter) e depois AI.call com modelos anthropic." as const;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AudioBuffer, AudioMimeType } from "../@types/audio";
|
|
2
|
+
import { MIME_TO_EXTENSION } from "../@types/audio";
|
|
3
|
+
|
|
4
|
+
export function getAudioExtension(format?: string | AudioMimeType): string {
|
|
5
|
+
if (!format) return "mp3";
|
|
6
|
+
if (format.startsWith("audio/")) {
|
|
7
|
+
return MIME_TO_EXTENSION[format as AudioMimeType] ?? "mp3";
|
|
8
|
+
}
|
|
9
|
+
return format.replace(/^\./, "");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function toAudioBuffer(audioBuffer: AudioBuffer): Buffer {
|
|
13
|
+
if (audioBuffer instanceof Buffer) return audioBuffer;
|
|
14
|
+
if (audioBuffer instanceof ArrayBuffer) return Buffer.from(audioBuffer);
|
|
15
|
+
return Buffer.from(audioBuffer as Uint8Array);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function bufferToBase64(audioBuffer: AudioBuffer): string {
|
|
19
|
+
return toAudioBuffer(audioBuffer).toString("base64");
|
|
20
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { GoogleGenerativeAIEmbeddings } from "@langchain/google-genai";
|
|
2
|
+
import type { EmbeddingResult, GeminiEmbeddingOptions } from "./types";
|
|
3
|
+
import { normalizeInput } from "./utils";
|
|
4
|
+
|
|
5
|
+
export async function embedGemini(
|
|
6
|
+
input: string | string[],
|
|
7
|
+
options: GeminiEmbeddingOptions,
|
|
8
|
+
googleGeminiToken?: string,
|
|
9
|
+
): Promise<EmbeddingResult> {
|
|
10
|
+
if (!googleGeminiToken) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"googleGeminiToken é obrigatório para embedGemini.",
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const texts = normalizeInput(input);
|
|
17
|
+
const modelParams: ConstructorParameters<typeof GoogleGenerativeAIEmbeddings>[0] =
|
|
18
|
+
{
|
|
19
|
+
apiKey: googleGeminiToken,
|
|
20
|
+
model: options.model,
|
|
21
|
+
};
|
|
22
|
+
if (options.taskType) {
|
|
23
|
+
modelParams.taskType = options.taskType as NonNullable<
|
|
24
|
+
typeof modelParams.taskType
|
|
25
|
+
>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const embeddingsModel = new GoogleGenerativeAIEmbeddings(modelParams);
|
|
29
|
+
|
|
30
|
+
const embeddings =
|
|
31
|
+
texts.length === 1
|
|
32
|
+
? [await embeddingsModel.embedQuery(texts[0]!)]
|
|
33
|
+
: await embeddingsModel.embedDocuments(texts);
|
|
34
|
+
|
|
35
|
+
const result: EmbeddingResult = {
|
|
36
|
+
embeddings,
|
|
37
|
+
model: options.model,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (options.dimensions !== undefined && options.model === "gemini-embedding-001") {
|
|
41
|
+
result.embeddings = result.embeddings.map((vec) =>
|
|
42
|
+
truncateAndNormalizeL2(vec, options.dimensions!),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function truncateAndNormalizeL2(vector: number[], dimensions: number): number[] {
|
|
50
|
+
const truncated = vector.slice(0, dimensions);
|
|
51
|
+
const norm = Math.sqrt(truncated.reduce((sum, v) => sum + v * v, 0));
|
|
52
|
+
if (norm === 0) return truncated;
|
|
53
|
+
return truncated.map((v) => v / norm);
|
|
54
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import type { OpenAIEmbeddingOptions, EmbeddingResult } from "./types";
|
|
3
|
+
import { normalizeInput } from "./utils";
|
|
4
|
+
|
|
5
|
+
function createOpenAIClient(apiKey?: string): OpenAI {
|
|
6
|
+
if (!apiKey) {
|
|
7
|
+
throw new Error("OpenAI API key is not passed in the embedding parameters");
|
|
8
|
+
}
|
|
9
|
+
return new OpenAI({ apiKey });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function mapEmbeddingResponse(
|
|
13
|
+
response: OpenAI.CreateEmbeddingResponse,
|
|
14
|
+
model: string,
|
|
15
|
+
): EmbeddingResult {
|
|
16
|
+
const embeddings = response.data
|
|
17
|
+
.sort((a, b) => a.index - b.index)
|
|
18
|
+
.map((item) => item.embedding as number[]);
|
|
19
|
+
|
|
20
|
+
const result: EmbeddingResult = {
|
|
21
|
+
embeddings,
|
|
22
|
+
model: response.model ?? model,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (response.usage) {
|
|
26
|
+
result.usage = {
|
|
27
|
+
prompt_tokens: response.usage.prompt_tokens,
|
|
28
|
+
total_tokens: response.usage.total_tokens,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function embedOpenAI(
|
|
36
|
+
input: string | string[],
|
|
37
|
+
options: OpenAIEmbeddingOptions,
|
|
38
|
+
openAIApiKey?: string,
|
|
39
|
+
): Promise<EmbeddingResult> {
|
|
40
|
+
const openai = createOpenAIClient(openAIApiKey);
|
|
41
|
+
const texts = normalizeInput(input);
|
|
42
|
+
|
|
43
|
+
const response = await openai.embeddings.create({
|
|
44
|
+
model: options.model,
|
|
45
|
+
input: texts.length === 1 ? texts[0]! : texts,
|
|
46
|
+
...(options.dimensions !== undefined ? { dimensions: options.dimensions } : {}),
|
|
47
|
+
...(options.encodingFormat
|
|
48
|
+
? { encoding_format: options.encodingFormat }
|
|
49
|
+
: {}),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return mapEmbeddingResponse(response, options.model);
|
|
53
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type OpenAI from "openai";
|
|
2
|
+
import { resolveOpenRouterProvider } from "../langchain/models";
|
|
3
|
+
import { createOpenRouterOpenAIClient } from "../utils/openrouter-client";
|
|
4
|
+
import type { EmbeddingResult, OpenRouterEmbeddingOptions } from "./types";
|
|
5
|
+
import { normalizeInput, stripOpenRouterPrefix } from "./utils";
|
|
6
|
+
|
|
7
|
+
function mapEmbeddingResponse(
|
|
8
|
+
response: OpenAI.CreateEmbeddingResponse,
|
|
9
|
+
model: string,
|
|
10
|
+
): EmbeddingResult {
|
|
11
|
+
const embeddings = response.data
|
|
12
|
+
.sort((a, b) => a.index - b.index)
|
|
13
|
+
.map((item) => item.embedding as number[]);
|
|
14
|
+
|
|
15
|
+
const result: EmbeddingResult = {
|
|
16
|
+
embeddings,
|
|
17
|
+
model: response.model ?? model,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
if (response.usage) {
|
|
21
|
+
result.usage = {
|
|
22
|
+
prompt_tokens: response.usage.prompt_tokens,
|
|
23
|
+
total_tokens: response.usage.total_tokens,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function embedOpenRouter(
|
|
31
|
+
input: string | string[],
|
|
32
|
+
options: OpenRouterEmbeddingOptions,
|
|
33
|
+
openRouterApiKey?: string,
|
|
34
|
+
): Promise<EmbeddingResult> {
|
|
35
|
+
if (!openRouterApiKey) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"OpenRouter API key is not passed in the embedding parameters",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const openRouterModel = stripOpenRouterPrefix(options.model);
|
|
42
|
+
const client = createOpenRouterOpenAIClient(openRouterApiKey);
|
|
43
|
+
const texts = normalizeInput(input);
|
|
44
|
+
|
|
45
|
+
const provider = resolveOpenRouterProvider(openRouterModel, {
|
|
46
|
+
openRouterProvider: options.openRouterProvider,
|
|
47
|
+
openRouterAllowAllProviders: options.openRouterAllowAllProviders,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const body: Record<string, unknown> = {
|
|
51
|
+
model: openRouterModel,
|
|
52
|
+
input: texts.length === 1 ? texts[0]! : texts,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (options.dimensions !== undefined) body.dimensions = options.dimensions;
|
|
56
|
+
if (options.encodingFormat) body.encoding_format = options.encodingFormat;
|
|
57
|
+
if (provider) body.provider = provider;
|
|
58
|
+
|
|
59
|
+
const response = await client.embeddings.create(
|
|
60
|
+
body as unknown as OpenAI.EmbeddingCreateParams,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return mapEmbeddingResponse(response, openRouterModel);
|
|
64
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { AIEmbeddingModelNames } from "../@types/embedding-model-names";
|
|
2
|
+
import { embedOpenAI } from "./embed-openai";
|
|
3
|
+
import { embedOpenRouter } from "./embed-openrouter";
|
|
4
|
+
import { embedGemini } from "./embed-gemini";
|
|
5
|
+
import {
|
|
6
|
+
embedWithModel,
|
|
7
|
+
type EmbedOptions,
|
|
8
|
+
} from "./route";
|
|
9
|
+
import type {
|
|
10
|
+
EmbedApiKeys,
|
|
11
|
+
EmbeddingResult,
|
|
12
|
+
GeminiEmbeddingOptions,
|
|
13
|
+
GeminiEmbeddingTaskType,
|
|
14
|
+
OpenAIEmbeddingOptions,
|
|
15
|
+
OpenRouterEmbeddingOptions,
|
|
16
|
+
} from "./types";
|
|
17
|
+
|
|
18
|
+
export type {
|
|
19
|
+
AIEmbeddingModelNames,
|
|
20
|
+
OpenAIEmbeddingModel,
|
|
21
|
+
GeminiEmbeddingModel,
|
|
22
|
+
OpenRouterEmbeddingModel,
|
|
23
|
+
OpenRouterPrefixedEmbeddingModel,
|
|
24
|
+
} from "../@types/embedding-model-names";
|
|
25
|
+
|
|
26
|
+
export type {
|
|
27
|
+
EmbeddingResult,
|
|
28
|
+
EmbeddingUsage,
|
|
29
|
+
OpenAIEmbeddingOptions,
|
|
30
|
+
OpenRouterEmbeddingOptions,
|
|
31
|
+
GeminiEmbeddingOptions,
|
|
32
|
+
GeminiEmbeddingTaskType,
|
|
33
|
+
EmbedApiKeys,
|
|
34
|
+
} from "./types";
|
|
35
|
+
|
|
36
|
+
export type { EmbedOptions } from "./route";
|
|
37
|
+
|
|
38
|
+
export { ANTHROPIC_NO_NATIVE_EMBEDDINGS } from "./types";
|
|
39
|
+
export { embedWithModel };
|
|
40
|
+
|
|
41
|
+
export class AIEmbeddings {
|
|
42
|
+
static embedOpenAI = embedOpenAI;
|
|
43
|
+
static embedOpenRouter = embedOpenRouter;
|
|
44
|
+
static embedGemini = embedGemini;
|
|
45
|
+
|
|
46
|
+
static async embed(
|
|
47
|
+
input: string | string[],
|
|
48
|
+
options: EmbedOptions,
|
|
49
|
+
keys: EmbedApiKeys,
|
|
50
|
+
): Promise<EmbeddingResult> {
|
|
51
|
+
return embedWithModel(input, options, keys);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static async embedDocuments(
|
|
55
|
+
texts: string[],
|
|
56
|
+
options: EmbedOptions,
|
|
57
|
+
keys: EmbedApiKeys,
|
|
58
|
+
): Promise<number[][]> {
|
|
59
|
+
const taskType =
|
|
60
|
+
options.taskType ??
|
|
61
|
+
(options.model.startsWith("gemini-embedding-")
|
|
62
|
+
? ("RETRIEVAL_DOCUMENT" as const)
|
|
63
|
+
: undefined);
|
|
64
|
+
|
|
65
|
+
const result = await embedWithModel(texts, { ...options, taskType }, keys);
|
|
66
|
+
return result.embeddings;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static async embedQuery(
|
|
70
|
+
text: string,
|
|
71
|
+
options: EmbedOptions,
|
|
72
|
+
keys: EmbedApiKeys,
|
|
73
|
+
): Promise<number[]> {
|
|
74
|
+
const taskType =
|
|
75
|
+
options.taskType ??
|
|
76
|
+
(options.model.startsWith("gemini-embedding-")
|
|
77
|
+
? ("RETRIEVAL_QUERY" as const)
|
|
78
|
+
: undefined);
|
|
79
|
+
|
|
80
|
+
const result = await embedWithModel(text, { ...options, taskType }, keys);
|
|
81
|
+
return result.embeddings[0] ?? [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EmbedApiKeys,
|
|
3
|
+
EmbeddingResult,
|
|
4
|
+
GeminiEmbeddingOptions,
|
|
5
|
+
OpenAIEmbeddingOptions,
|
|
6
|
+
OpenRouterEmbeddingOptions,
|
|
7
|
+
} from "./types";
|
|
8
|
+
import type { AIEmbeddingModelNames } from "../@types/embedding-model-names";
|
|
9
|
+
import { embedOpenAI } from "./embed-openai";
|
|
10
|
+
import { embedOpenRouter } from "./embed-openrouter";
|
|
11
|
+
import { embedGemini } from "./embed-gemini";
|
|
12
|
+
import {
|
|
13
|
+
isGeminiEmbeddingModel,
|
|
14
|
+
isOpenAIEmbeddingModel,
|
|
15
|
+
isOpenRouterEmbeddingModel,
|
|
16
|
+
} from "./utils";
|
|
17
|
+
|
|
18
|
+
export type EmbedOptions = {
|
|
19
|
+
model: AIEmbeddingModelNames;
|
|
20
|
+
dimensions?: number;
|
|
21
|
+
encodingFormat?: "float" | "base64";
|
|
22
|
+
taskType?: GeminiEmbeddingOptions["taskType"];
|
|
23
|
+
openRouterProvider?: OpenRouterEmbeddingOptions["openRouterProvider"];
|
|
24
|
+
openRouterAllowAllProviders?: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function embedWithModel(
|
|
28
|
+
input: string | string[],
|
|
29
|
+
options: EmbedOptions,
|
|
30
|
+
keys: EmbedApiKeys,
|
|
31
|
+
): Promise<EmbeddingResult> {
|
|
32
|
+
const { model } = options;
|
|
33
|
+
|
|
34
|
+
if (isOpenRouterEmbeddingModel(model)) {
|
|
35
|
+
const orOptions: OpenRouterEmbeddingOptions = { model };
|
|
36
|
+
if (options.dimensions !== undefined) orOptions.dimensions = options.dimensions;
|
|
37
|
+
if (options.encodingFormat) orOptions.encodingFormat = options.encodingFormat;
|
|
38
|
+
if (options.openRouterProvider) {
|
|
39
|
+
orOptions.openRouterProvider = options.openRouterProvider;
|
|
40
|
+
}
|
|
41
|
+
if (options.openRouterAllowAllProviders !== undefined) {
|
|
42
|
+
orOptions.openRouterAllowAllProviders = options.openRouterAllowAllProviders;
|
|
43
|
+
}
|
|
44
|
+
return embedOpenRouter(input, orOptions, keys.openRouterApiKey);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (isOpenAIEmbeddingModel(model)) {
|
|
48
|
+
const oaiOptions: OpenAIEmbeddingOptions = { model };
|
|
49
|
+
if (options.dimensions !== undefined) oaiOptions.dimensions = options.dimensions;
|
|
50
|
+
if (options.encodingFormat) oaiOptions.encodingFormat = options.encodingFormat;
|
|
51
|
+
return embedOpenAI(input, oaiOptions, keys.openAIApiKey);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (isGeminiEmbeddingModel(model)) {
|
|
55
|
+
const geminiOptions: GeminiEmbeddingOptions = { model };
|
|
56
|
+
if (options.dimensions !== undefined) geminiOptions.dimensions = options.dimensions;
|
|
57
|
+
if (options.taskType) geminiOptions.taskType = options.taskType;
|
|
58
|
+
return embedGemini(input, geminiOptions, keys.googleGeminiToken);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw new Error(`[@luanpoppe/ai] Modelo de embedding não suportado: ${model}`);
|
|
62
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { OpenRouterProviderPreferences } from "../@types/openrouter-provider";
|
|
2
|
+
import type { AIEmbeddingModelNames } from "../@types/embedding-model-names";
|
|
3
|
+
|
|
4
|
+
export type GeminiEmbeddingTaskType =
|
|
5
|
+
| "RETRIEVAL_DOCUMENT"
|
|
6
|
+
| "RETRIEVAL_QUERY"
|
|
7
|
+
| "SEMANTIC_SIMILARITY"
|
|
8
|
+
| "CLASSIFICATION"
|
|
9
|
+
| "CLUSTERING"
|
|
10
|
+
| "QUESTION_ANSWERING"
|
|
11
|
+
| "FACT_VERIFICATION"
|
|
12
|
+
| "CODE_RETRIEVAL_QUERY";
|
|
13
|
+
|
|
14
|
+
export type EmbeddingUsage = {
|
|
15
|
+
prompt_tokens?: number;
|
|
16
|
+
total_tokens?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type EmbeddingResult = {
|
|
20
|
+
embeddings: number[][];
|
|
21
|
+
model: string;
|
|
22
|
+
usage?: EmbeddingUsage;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type OpenAIEmbeddingOptions = {
|
|
26
|
+
model: AIEmbeddingModelNames;
|
|
27
|
+
dimensions?: number;
|
|
28
|
+
encodingFormat?: "float" | "base64";
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type OpenRouterEmbeddingOptions = {
|
|
32
|
+
model: AIEmbeddingModelNames;
|
|
33
|
+
dimensions?: number;
|
|
34
|
+
encodingFormat?: "float" | "base64";
|
|
35
|
+
openRouterProvider?: OpenRouterProviderPreferences;
|
|
36
|
+
openRouterAllowAllProviders?: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type GeminiEmbeddingOptions = {
|
|
40
|
+
model: AIEmbeddingModelNames;
|
|
41
|
+
dimensions?: number;
|
|
42
|
+
taskType?: GeminiEmbeddingTaskType;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type EmbedApiKeys = {
|
|
46
|
+
openAIApiKey?: string;
|
|
47
|
+
googleGeminiToken?: string;
|
|
48
|
+
openRouterApiKey?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** A API pública da Anthropic não oferece embeddings nativos (recomenda Voyage AI para RAG com Claude). */
|
|
52
|
+
export const ANTHROPIC_NO_NATIVE_EMBEDDINGS =
|
|
53
|
+
"A API da Anthropic não suporta embeddings nativos. Use OpenAI, Gemini ou OpenRouter para vetorizar texto antes de AI.call com modelos anthropic." as const;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function normalizeInput(input: string | string[]): string[] {
|
|
2
|
+
return Array.isArray(input) ? input : [input];
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function stripOpenRouterPrefix(model: string): string {
|
|
6
|
+
return model.startsWith("openrouter/")
|
|
7
|
+
? model.slice("openrouter/".length)
|
|
8
|
+
: model;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isOpenAIEmbeddingModel(model: string): boolean {
|
|
12
|
+
return model.startsWith("text-embedding-");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isGeminiEmbeddingModel(model: string): boolean {
|
|
16
|
+
return model.startsWith("gemini-embedding-");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isOpenRouterEmbeddingModel(model: string): boolean {
|
|
20
|
+
return model.startsWith("openrouter/");
|
|
21
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,20 @@ export type {
|
|
|
18
18
|
export type { AIModelNames } from "./@types/model-names";
|
|
19
19
|
|
|
20
20
|
export { AIModels } from "./langchain/models";
|
|
21
|
-
export
|
|
21
|
+
export {
|
|
22
|
+
DEFAULT_OLLAMA_BASE_URL,
|
|
23
|
+
DEFAULT_LOCAL_API_KEY,
|
|
24
|
+
} from "./langchain/models";
|
|
25
|
+
export type {
|
|
26
|
+
LLMModelConfig,
|
|
27
|
+
OllamaModelConfig,
|
|
28
|
+
OpenAICompatibleModelConfig,
|
|
29
|
+
ReasoningEffort,
|
|
30
|
+
OpenRouterMaxPrice,
|
|
31
|
+
OpenRouterProviderPreferences,
|
|
32
|
+
OpenRouterProviderSort,
|
|
33
|
+
} from "./langchain/models";
|
|
34
|
+
export { resolveOpenRouterProvider } from "./langchain/models";
|
|
22
35
|
export { AIMessages } from "./langchain/messages";
|
|
23
36
|
export { AITools } from "./langchain/tools";
|
|
24
37
|
export { AIMemory } from "./langchain/memory";
|
|
@@ -35,6 +48,41 @@ export type {
|
|
|
35
48
|
HistoryMessageItem,
|
|
36
49
|
GetHistoryResult,
|
|
37
50
|
} from "./langchain/memory";
|
|
51
|
+
export { AIAudio } from "./audio";
|
|
52
|
+
export type {
|
|
53
|
+
WhisperModel,
|
|
54
|
+
OpenAITranscriptionOptions,
|
|
55
|
+
OpenAITranscriptionResponseFormat,
|
|
56
|
+
OpenAITranscriptionDetailed,
|
|
57
|
+
OpenRouterTranscriptionOptions,
|
|
58
|
+
OpenRouterTranscriptionResult,
|
|
59
|
+
OpenAISpeechOptions,
|
|
60
|
+
OpenAISpeechModel,
|
|
61
|
+
OpenRouterSpeechOptions,
|
|
62
|
+
SpeechResult,
|
|
63
|
+
TranscriptionWord,
|
|
64
|
+
TranscriptionSegment,
|
|
65
|
+
DiarizedSegment,
|
|
66
|
+
GeminiTranscriptionOptions,
|
|
67
|
+
} from "./audio";
|
|
68
|
+
export { ANTHROPIC_NO_NATIVE_AUDIO } from "./audio";
|
|
69
|
+
export { AIEmbeddings, ANTHROPIC_NO_NATIVE_EMBEDDINGS } from "./embeddings";
|
|
70
|
+
export type {
|
|
71
|
+
AIEmbeddingModelNames,
|
|
72
|
+
OpenAIEmbeddingModel,
|
|
73
|
+
GeminiEmbeddingModel,
|
|
74
|
+
OpenRouterEmbeddingModel,
|
|
75
|
+
OpenRouterPrefixedEmbeddingModel,
|
|
76
|
+
EmbeddingResult,
|
|
77
|
+
EmbeddingUsage,
|
|
78
|
+
OpenAIEmbeddingOptions,
|
|
79
|
+
OpenRouterEmbeddingOptions,
|
|
80
|
+
GeminiEmbeddingOptions,
|
|
81
|
+
GeminiEmbeddingTaskType,
|
|
82
|
+
EmbedApiKeys,
|
|
83
|
+
EmbedOptions,
|
|
84
|
+
} from "./embeddings";
|
|
85
|
+
/** @deprecated Use AIAudio instead */
|
|
38
86
|
export { AIAudioTranscription } from "./langchain/audio-transcription";
|
|
39
87
|
export { AudioUtils } from "./utils/audio-utils";
|
|
40
88
|
export { ImageUtils } from "./utils/image-utils";
|
|
@@ -46,10 +94,8 @@ export type {
|
|
|
46
94
|
ImageContentBlock,
|
|
47
95
|
HumanMessageWithImageOptions,
|
|
48
96
|
} from "./langchain/messages";
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
WhisperTranscriptionOptions,
|
|
52
|
-
} from "./langchain/audio-transcription";
|
|
97
|
+
/** @deprecated Use OpenAITranscriptionOptions from AIAudio */
|
|
98
|
+
export type { WhisperTranscriptionOptions } from "./langchain/audio-transcription";
|
|
53
99
|
|
|
54
100
|
// Aliases para compatibilidade (deprecated - serão removidos em 2.0.0)
|
|
55
101
|
/** @deprecated Use AI instead. Will be removed in 2.0.0 */
|