@luanpoppe/ai 1.1.4 → 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 +21 -5
- 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 +12 -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 +104 -6
- 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 +52 -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,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
|
@@ -15,9 +15,23 @@ export type {
|
|
|
15
15
|
AICallStructuredOutputParams,
|
|
16
16
|
AICallStructuredOutputReturn,
|
|
17
17
|
} from "./@types/ai-call";
|
|
18
|
+
export type { AIModelNames } from "./@types/model-names";
|
|
18
19
|
|
|
19
20
|
export { AIModels } from "./langchain/models";
|
|
20
|
-
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";
|
|
21
35
|
export { AIMessages } from "./langchain/messages";
|
|
22
36
|
export { AITools } from "./langchain/tools";
|
|
23
37
|
export { AIMemory } from "./langchain/memory";
|
|
@@ -34,6 +48,41 @@ export type {
|
|
|
34
48
|
HistoryMessageItem,
|
|
35
49
|
GetHistoryResult,
|
|
36
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 */
|
|
37
86
|
export { AIAudioTranscription } from "./langchain/audio-transcription";
|
|
38
87
|
export { AudioUtils } from "./utils/audio-utils";
|
|
39
88
|
export { ImageUtils } from "./utils/image-utils";
|
|
@@ -45,10 +94,8 @@ export type {
|
|
|
45
94
|
ImageContentBlock,
|
|
46
95
|
HumanMessageWithImageOptions,
|
|
47
96
|
} from "./langchain/messages";
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
WhisperTranscriptionOptions,
|
|
51
|
-
} from "./langchain/audio-transcription";
|
|
97
|
+
/** @deprecated Use OpenAITranscriptionOptions from AIAudio */
|
|
98
|
+
export type { WhisperTranscriptionOptions } from "./langchain/audio-transcription";
|
|
52
99
|
|
|
53
100
|
// Aliases para compatibilidade (deprecated - serão removidos em 2.0.0)
|
|
54
101
|
/** @deprecated Use AI instead. Will be removed in 2.0.0 */
|
|
@@ -1,105 +1,34 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import OpenAI from "openai";
|
|
4
|
-
import { toFile } from "openai";
|
|
5
1
|
import type { AudioBuffer, AudioMimeType } from "../@types/audio";
|
|
6
|
-
import {
|
|
2
|
+
import { AIAudio } from "../audio";
|
|
3
|
+
import type {
|
|
4
|
+
OpenAITranscriptionOptions,
|
|
5
|
+
WhisperModel,
|
|
6
|
+
} from "../audio/types";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
export type WhisperModel =
|
|
10
|
-
| "whisper-1"
|
|
11
|
-
| "gpt-4o-transcribe"
|
|
12
|
-
| "gpt-4o-mini-transcribe"
|
|
13
|
-
| "gpt-4o-mini-transcribe-2025-12-15"
|
|
14
|
-
| "gpt-4o-transcribe-diarize";
|
|
8
|
+
export type { WhisperModel };
|
|
15
9
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
model?: WhisperModel;
|
|
19
|
-
languageIn2Digits?: string;
|
|
20
|
-
prompt?: string;
|
|
21
|
-
responseFormat?: "json" | "text" | "srt" | "verbose_json" | "vtt";
|
|
22
|
-
temperature?: number;
|
|
23
|
-
timestampGranularities?: ("word" | "segment")[];
|
|
24
|
-
/** Formato do áudio: extensão ("mp3", "wav", "webm") ou MIME type ("audio/wav", "audio/webm") */
|
|
25
|
-
format?: string | AudioMimeType;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
function getExtension(format?: string | AudioMimeType): string {
|
|
29
|
-
if (!format) return "mp3";
|
|
30
|
-
if (format.startsWith("audio/")) {
|
|
31
|
-
return MIME_TO_EXTENSION[format as AudioMimeType] ?? "mp3";
|
|
32
|
-
}
|
|
33
|
-
return format.replace(/^\./, "");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function toBuffer(audioBuffer: AudioBuffer): Buffer {
|
|
37
|
-
if (audioBuffer instanceof Buffer) return audioBuffer;
|
|
38
|
-
if (audioBuffer instanceof ArrayBuffer) return Buffer.from(audioBuffer);
|
|
39
|
-
return Buffer.from(audioBuffer as Uint8Array);
|
|
40
|
-
}
|
|
10
|
+
/** @deprecated Use OpenAITranscriptionOptions from AIAudio */
|
|
11
|
+
export type WhisperTranscriptionOptions = OpenAITranscriptionOptions;
|
|
41
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Use {@link AIAudio.transcribeWithWhisper} instead.
|
|
15
|
+
*/
|
|
42
16
|
export class AIAudioTranscription {
|
|
43
|
-
static
|
|
17
|
+
static transcribeWithWhisper(
|
|
44
18
|
audioBuffer: AudioBuffer,
|
|
45
19
|
options: WhisperTranscriptionOptions = {},
|
|
46
|
-
openAIApiKey?: string
|
|
20
|
+
openAIApiKey?: string,
|
|
47
21
|
): Promise<string> {
|
|
48
|
-
|
|
49
|
-
process.env.OPENAI_API_KEY = openAIApiKey;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const buffer = toBuffer(audioBuffer);
|
|
53
|
-
const extension = getExtension(options.format);
|
|
54
|
-
const fileName = `whisper-${Date.now()}.${extension}`;
|
|
55
|
-
|
|
56
|
-
const file = await toFile(buffer, fileName);
|
|
57
|
-
|
|
58
|
-
const openai = new OpenAI();
|
|
59
|
-
|
|
60
|
-
const transcriptionParams: OpenAI.Audio.TranscriptionCreateParams = {
|
|
61
|
-
file,
|
|
62
|
-
model: options.model ?? "whisper-1",
|
|
63
|
-
response_format: options.responseFormat ?? "text",
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
if (options.languageIn2Digits) {
|
|
67
|
-
transcriptionParams.language = options.languageIn2Digits;
|
|
68
|
-
}
|
|
69
|
-
if (options.prompt) {
|
|
70
|
-
transcriptionParams.prompt = options.prompt;
|
|
71
|
-
}
|
|
72
|
-
if (options.temperature !== undefined) {
|
|
73
|
-
transcriptionParams.temperature = options.temperature;
|
|
74
|
-
}
|
|
75
|
-
if (options.timestampGranularities) {
|
|
76
|
-
transcriptionParams.timestamp_granularities =
|
|
77
|
-
options.timestampGranularities;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const response = await openai.audio.transcriptions.create(
|
|
81
|
-
transcriptionParams
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
return typeof response === "string" ? response : response.text;
|
|
22
|
+
return AIAudio.transcribeWithWhisper(audioBuffer, options, openAIApiKey);
|
|
85
23
|
}
|
|
86
24
|
|
|
87
|
-
static
|
|
25
|
+
static transcribeFileWithWhisper(
|
|
88
26
|
filePath: string,
|
|
89
27
|
options: WhisperTranscriptionOptions = {},
|
|
90
|
-
openAIApiKey?: string
|
|
28
|
+
openAIApiKey?: string,
|
|
91
29
|
): Promise<string> {
|
|
92
|
-
|
|
93
|
-
throw new Error(`Arquivo não encontrado: ${filePath}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const audioBuffer = fs.readFileSync(filePath);
|
|
97
|
-
const format =
|
|
98
|
-
options.format ?? (path.extname(filePath).replace(/^\./, "") || "mp3");
|
|
99
|
-
return this.transcribeWithWhisper(
|
|
100
|
-
audioBuffer,
|
|
101
|
-
{ ...options, format },
|
|
102
|
-
openAIApiKey
|
|
103
|
-
);
|
|
30
|
+
return AIAudio.transcribeFileWithWhisper(filePath, options, openAIApiKey);
|
|
104
31
|
}
|
|
105
32
|
}
|
|
33
|
+
|
|
34
|
+
export type { AudioBuffer, AudioMimeType };
|
|
@@ -3,7 +3,7 @@ import { AudioUtils } from "../utils/audio-utils";
|
|
|
3
3
|
import { ImageUtils } from "../utils/image-utils";
|
|
4
4
|
import type { AudioBuffer, AudioMimeType } from "../@types/audio";
|
|
5
5
|
import type { ImageBuffer, ImageMimeType } from "../@types/image";
|
|
6
|
-
import {
|
|
6
|
+
import { AIAudio } from "../audio";
|
|
7
7
|
|
|
8
8
|
export type MessageInput = SystemMessage | HumanMessage | AIMessage;
|
|
9
9
|
|
|
@@ -39,8 +39,11 @@ export type HumanMessageWithAudioOptions = {
|
|
|
39
39
|
filename?: string;
|
|
40
40
|
};
|
|
41
41
|
text?: string;
|
|
42
|
-
provider?: "openai" | "gemini" | "auto";
|
|
42
|
+
provider?: "openai" | "gemini" | "openrouter" | "auto";
|
|
43
43
|
openAIApiKey?: string;
|
|
44
|
+
openRouterApiKey?: string;
|
|
45
|
+
/** Modelo OpenRouter STT (ex.: openai/whisper-1) quando provider é openrouter ou auto com OR */
|
|
46
|
+
openRouterTranscriptionModel?: string;
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
export class AIMessages {
|
|
@@ -83,16 +86,31 @@ export class AIMessages {
|
|
|
83
86
|
static async humanAudio(
|
|
84
87
|
options: HumanMessageWithAudioOptions
|
|
85
88
|
): Promise<HumanMessage> {
|
|
86
|
-
const {
|
|
89
|
+
const {
|
|
90
|
+
audio,
|
|
91
|
+
text,
|
|
92
|
+
provider = "auto",
|
|
93
|
+
openAIApiKey,
|
|
94
|
+
openRouterApiKey,
|
|
95
|
+
openRouterTranscriptionModel,
|
|
96
|
+
} = options;
|
|
87
97
|
const { buffer, mimeType, filename } = audio;
|
|
88
98
|
|
|
99
|
+
const resolvedProvider = this.resolveAudioProvider(
|
|
100
|
+
provider,
|
|
101
|
+
openAIApiKey,
|
|
102
|
+
openRouterApiKey,
|
|
103
|
+
);
|
|
104
|
+
|
|
89
105
|
return this.createHumanMessageWithAudio(
|
|
90
106
|
buffer,
|
|
91
107
|
text,
|
|
92
108
|
mimeType,
|
|
93
109
|
filename,
|
|
94
|
-
|
|
95
|
-
openAIApiKey
|
|
110
|
+
resolvedProvider,
|
|
111
|
+
openAIApiKey,
|
|
112
|
+
openRouterApiKey,
|
|
113
|
+
openRouterTranscriptionModel,
|
|
96
114
|
);
|
|
97
115
|
}
|
|
98
116
|
|
|
@@ -100,22 +118,45 @@ export class AIMessages {
|
|
|
100
118
|
return new AIMessage(message);
|
|
101
119
|
}
|
|
102
120
|
|
|
121
|
+
private static resolveAudioProvider(
|
|
122
|
+
provider: "openai" | "gemini" | "openrouter" | "auto",
|
|
123
|
+
openAIApiKey?: string,
|
|
124
|
+
openRouterApiKey?: string,
|
|
125
|
+
): "openai" | "gemini" | "openrouter" {
|
|
126
|
+
if (provider !== "auto") return provider;
|
|
127
|
+
if (openAIApiKey) return "openai";
|
|
128
|
+
if (openRouterApiKey) return "openrouter";
|
|
129
|
+
return "gemini";
|
|
130
|
+
}
|
|
131
|
+
|
|
103
132
|
private static async createHumanMessageWithAudio(
|
|
104
133
|
audioBuffer: AudioBuffer,
|
|
105
134
|
text?: string,
|
|
106
135
|
mimeType?: AudioMimeType,
|
|
107
136
|
filename?: string,
|
|
108
|
-
provider: "openai" | "gemini" | "
|
|
109
|
-
openAIApiKey?: string
|
|
137
|
+
provider: "openai" | "gemini" | "openrouter" = "gemini",
|
|
138
|
+
openAIApiKey?: string,
|
|
139
|
+
openRouterApiKey?: string,
|
|
140
|
+
openRouterTranscriptionModel = "openai/whisper-1",
|
|
110
141
|
): Promise<HumanMessage> {
|
|
111
|
-
|
|
112
|
-
if (provider === "openai")
|
|
142
|
+
if (provider === "openai") {
|
|
113
143
|
return await this.processAudioOpenAi(
|
|
114
144
|
audioBuffer,
|
|
115
145
|
mimeType,
|
|
116
146
|
text,
|
|
117
|
-
openAIApiKey
|
|
147
|
+
openAIApiKey,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (provider === "openrouter") {
|
|
152
|
+
return await this.processAudioOpenRouter(
|
|
153
|
+
audioBuffer,
|
|
154
|
+
mimeType,
|
|
155
|
+
text,
|
|
156
|
+
openRouterApiKey,
|
|
157
|
+
openRouterTranscriptionModel,
|
|
118
158
|
);
|
|
159
|
+
}
|
|
119
160
|
|
|
120
161
|
// Para Gemini e outros, usa multimodal direto (trabalho síncrono, mas retorna Promise para compatibilidade)
|
|
121
162
|
const base64Data = AudioUtils.bufferToBase64(audioBuffer);
|
|
@@ -168,10 +209,10 @@ export class AIMessages {
|
|
|
168
209
|
? { format: mimeType }
|
|
169
210
|
: { language: "pt" };
|
|
170
211
|
|
|
171
|
-
const transcribedText = await
|
|
212
|
+
const transcribedText = await AIAudio.transcribeWithWhisper(
|
|
172
213
|
audioBuffer,
|
|
173
214
|
transcriptionOptions,
|
|
174
|
-
openAIApiKey
|
|
215
|
+
openAIApiKey,
|
|
175
216
|
);
|
|
176
217
|
|
|
177
218
|
// Combina o texto original (se fornecido) com a transcrição
|
|
@@ -192,4 +233,35 @@ export class AIMessages {
|
|
|
192
233
|
);
|
|
193
234
|
}
|
|
194
235
|
}
|
|
236
|
+
|
|
237
|
+
private static async processAudioOpenRouter(
|
|
238
|
+
audioBuffer: AudioBuffer,
|
|
239
|
+
mimeType?: AudioMimeType,
|
|
240
|
+
text?: string,
|
|
241
|
+
openRouterApiKey?: string,
|
|
242
|
+
openRouterTranscriptionModel = "openai/whisper-1",
|
|
243
|
+
) {
|
|
244
|
+
if (!openRouterApiKey) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
"openRouterApiKey é necessária quando provider é 'openrouter'.",
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const format = mimeType ?? AudioUtils.detectAudioMimeType(audioBuffer);
|
|
251
|
+
const { text: transcribedText } = await AIAudio.transcribeOpenRouter(
|
|
252
|
+
audioBuffer,
|
|
253
|
+
{
|
|
254
|
+
model: openRouterTranscriptionModel,
|
|
255
|
+
format,
|
|
256
|
+
...(text ? {} : { language: "pt" }),
|
|
257
|
+
},
|
|
258
|
+
openRouterApiKey,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const finalText = text
|
|
262
|
+
? `${text}\n\nÁudio transcrito: ${transcribedText}`
|
|
263
|
+
: `Áudio transcrito: ${transcribedText}`;
|
|
264
|
+
|
|
265
|
+
return new HumanMessage(finalText);
|
|
266
|
+
}
|
|
195
267
|
}
|