@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
|
@@ -1,18 +1,56 @@
|
|
|
1
1
|
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
2
|
+
import { ChatOllama } from "@langchain/ollama";
|
|
2
3
|
import { ChatOpenAI } from "@langchain/openai";
|
|
4
|
+
import type { OpenRouterProviderPreferences } from "../@types/openrouter-provider";
|
|
5
|
+
export declare const DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434";
|
|
6
|
+
export declare const DEFAULT_LOCAL_API_KEY = "not-needed";
|
|
7
|
+
export type { OpenRouterMaxPrice, OpenRouterProviderPreferences, OpenRouterProviderSort, } from "../@types/openrouter-provider";
|
|
3
8
|
/** Nível de esforço de raciocínio para modelos OpenAI (o1, gpt-5, etc.). Valores: "low" | "medium" | "high" */
|
|
4
9
|
export type ReasoningEffort = "low" | "medium" | "high";
|
|
10
|
+
export type OllamaModelConfig = {
|
|
11
|
+
model: string;
|
|
12
|
+
baseUrl?: string | undefined;
|
|
13
|
+
maxTokens?: number | undefined;
|
|
14
|
+
temperature?: number | undefined;
|
|
15
|
+
numCtx?: number | undefined;
|
|
16
|
+
};
|
|
17
|
+
export type OpenAICompatibleModelConfig = {
|
|
18
|
+
model: string;
|
|
19
|
+
baseURL: string;
|
|
20
|
+
apiKey?: string | undefined;
|
|
21
|
+
maxTokens?: number | undefined;
|
|
22
|
+
temperature?: number | undefined;
|
|
23
|
+
};
|
|
5
24
|
export type LLMModelConfig = {
|
|
6
25
|
model: string;
|
|
7
26
|
apiKey?: string | undefined;
|
|
27
|
+
/** Override da URL do servidor (Ollama nativo ou OpenAI-compatible local) */
|
|
28
|
+
baseUrl?: string | undefined;
|
|
8
29
|
maxTokens?: number | undefined;
|
|
9
30
|
temperature?: number | undefined;
|
|
31
|
+
/** Tamanho do contexto (Ollama nativo, `num_ctx`) */
|
|
32
|
+
numCtx?: number | undefined;
|
|
10
33
|
/** Nível de esforço de raciocínio (modelos OpenAI: o1, gpt-5, etc.) */
|
|
11
34
|
reasoningEffort?: ReasoningEffort | undefined;
|
|
35
|
+
/** Preferências de roteamento de providers do OpenRouter */
|
|
36
|
+
openRouterProvider?: OpenRouterProviderPreferences | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Se true, não aplica `only: ["deepseek"]` em modelos `deepseek/*` via OpenRouter.
|
|
39
|
+
* Use para aceitar o roteamento padrão do OpenRouter (todos os providers).
|
|
40
|
+
*/
|
|
41
|
+
openRouterAllowAllProviders?: boolean | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Força `response_format: { type: "json_object" }` no OpenRouter (ex.: DeepSeek).
|
|
44
|
+
* Uso interno via `callStructuredOutput`.
|
|
45
|
+
*/
|
|
46
|
+
openRouterForceJsonObject?: boolean | undefined;
|
|
12
47
|
};
|
|
48
|
+
export declare function resolveOpenRouterProvider(model: string, config: Pick<LLMModelConfig, "openRouterProvider" | "openRouterAllowAllProviders">): OpenRouterProviderPreferences | undefined;
|
|
13
49
|
export declare class AIModels {
|
|
14
50
|
static gpt(params: LLMModelConfig): ChatOpenAI<import("@langchain/openai").ChatOpenAICallOptions>;
|
|
15
51
|
static gemini(params: LLMModelConfig): ChatGoogleGenerativeAI;
|
|
16
52
|
static openrouter(params: LLMModelConfig): ChatOpenAI<import("@langchain/openai").ChatOpenAICallOptions>;
|
|
53
|
+
static ollama(params: OllamaModelConfig): ChatOllama;
|
|
54
|
+
static openaiCompatible(params: OpenAICompatibleModelConfig): ChatOpenAI<import("@langchain/openai").ChatOpenAICallOptions>;
|
|
17
55
|
}
|
|
18
56
|
//# sourceMappingURL=models.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/langchain/models.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EAEvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,UAAU,EAAoB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/langchain/models.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EAEvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,UAAU,EAAwB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,UAAU,EAAoB,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAEnF,eAAO,MAAM,uBAAuB,2BAA2B,CAAC;AAEhE,eAAO,MAAM,qBAAqB,eAAe,CAAC;AAElD,YAAY,EACV,kBAAkB,EAClB,6BAA6B,EAC7B,sBAAsB,GACvB,MAAM,+BAA+B,CAAC;AAEvC,+GAA+G;AAC/G,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAExD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,uEAAuE;IACvE,eAAe,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IAC9C,4DAA4D;IAC5D,kBAAkB,CAAC,EAAE,6BAA6B,GAAG,SAAS,CAAC;IAC/D;;;OAGG;IACH,2BAA2B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAClD;;;OAGG;IACH,yBAAyB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACjD,CAAC;AAMF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,IAAI,CACV,cAAc,EACd,oBAAoB,GAAG,6BAA6B,CACrD,GACA,6BAA6B,GAAG,SAAS,CAc3C;AAED,qBAAa,QAAQ;IACnB,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc;IAsBjC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc;IAmBpC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,cAAc;IA+CxC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB;IAevC,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,2BAA2B;CAsB5D"}
|
package/dist/langchain/models.js
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AIModels = void 0;
|
|
3
|
+
exports.AIModels = exports.DEFAULT_LOCAL_API_KEY = exports.DEFAULT_OLLAMA_BASE_URL = void 0;
|
|
4
|
+
exports.resolveOpenRouterProvider = resolveOpenRouterProvider;
|
|
4
5
|
const google_genai_1 = require("@langchain/google-genai");
|
|
6
|
+
const ollama_1 = require("@langchain/ollama");
|
|
5
7
|
const openai_1 = require("@langchain/openai");
|
|
8
|
+
exports.DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434";
|
|
9
|
+
exports.DEFAULT_LOCAL_API_KEY = "not-needed";
|
|
10
|
+
const DEEPSEEK_OPENROUTER_PROVIDER = {
|
|
11
|
+
only: ["deepseek"],
|
|
12
|
+
};
|
|
13
|
+
function resolveOpenRouterProvider(model, config) {
|
|
14
|
+
if (config.openRouterProvider !== undefined) {
|
|
15
|
+
return config.openRouterProvider;
|
|
16
|
+
}
|
|
17
|
+
if (config.openRouterAllowAllProviders === true) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
if (model.startsWith("deepseek/")) {
|
|
21
|
+
return DEEPSEEK_OPENROUTER_PROVIDER;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
6
25
|
class AIModels {
|
|
7
26
|
static gpt(params) {
|
|
8
27
|
const { model, apiKey, maxTokens, temperature, reasoningEffort } = params;
|
|
@@ -39,7 +58,7 @@ class AIModels {
|
|
|
39
58
|
return new google_genai_1.ChatGoogleGenerativeAI(options);
|
|
40
59
|
}
|
|
41
60
|
static openrouter(params) {
|
|
42
|
-
const { apiKey, maxTokens, model, temperature, reasoningEffort } = params;
|
|
61
|
+
const { apiKey, maxTokens, model, temperature, reasoningEffort, openRouterProvider, openRouterAllowAllProviders, openRouterForceJsonObject, } = params;
|
|
43
62
|
if (!apiKey)
|
|
44
63
|
throw new Error("OpenRouter API key is not passed in the model parameters");
|
|
45
64
|
const options = {
|
|
@@ -53,14 +72,54 @@ class AIModels {
|
|
|
53
72
|
options.maxTokens = maxTokens;
|
|
54
73
|
if (temperature)
|
|
55
74
|
options.temperature = temperature;
|
|
56
|
-
|
|
75
|
+
const provider = resolveOpenRouterProvider(model, {
|
|
76
|
+
openRouterProvider,
|
|
77
|
+
openRouterAllowAllProviders,
|
|
78
|
+
});
|
|
79
|
+
if (reasoningEffort || provider || openRouterForceJsonObject) {
|
|
57
80
|
options.modelKwargs = {
|
|
58
81
|
...(options.modelKwargs ?? {}),
|
|
59
|
-
reasoning_effort: reasoningEffort,
|
|
82
|
+
...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
|
|
83
|
+
...(provider ? { provider } : {}),
|
|
84
|
+
...(openRouterForceJsonObject
|
|
85
|
+
? { response_format: { type: "json_object" } }
|
|
86
|
+
: {}),
|
|
60
87
|
};
|
|
61
88
|
}
|
|
62
89
|
return new openai_1.ChatOpenAI(options);
|
|
63
90
|
}
|
|
91
|
+
static ollama(params) {
|
|
92
|
+
const { model, baseUrl, maxTokens, temperature, numCtx } = params;
|
|
93
|
+
const options = {
|
|
94
|
+
model,
|
|
95
|
+
baseUrl: baseUrl ?? exports.DEFAULT_OLLAMA_BASE_URL,
|
|
96
|
+
};
|
|
97
|
+
if (temperature !== undefined)
|
|
98
|
+
options.temperature = temperature;
|
|
99
|
+
if (numCtx !== undefined)
|
|
100
|
+
options.numCtx = numCtx;
|
|
101
|
+
if (maxTokens !== undefined)
|
|
102
|
+
options.numPredict = maxTokens;
|
|
103
|
+
return new ollama_1.ChatOllama(options);
|
|
104
|
+
}
|
|
105
|
+
static openaiCompatible(params) {
|
|
106
|
+
const { model, baseURL, apiKey, maxTokens, temperature } = params;
|
|
107
|
+
if (!baseURL) {
|
|
108
|
+
throw new Error("baseURL é obrigatório para servidores OpenAI-compatible (LM Studio, Ollama /v1, vLLM, etc.).");
|
|
109
|
+
}
|
|
110
|
+
const options = {
|
|
111
|
+
model,
|
|
112
|
+
apiKey: apiKey ?? exports.DEFAULT_LOCAL_API_KEY,
|
|
113
|
+
configuration: {
|
|
114
|
+
baseURL,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
if (maxTokens)
|
|
118
|
+
options.maxTokens = maxTokens;
|
|
119
|
+
if (temperature !== undefined)
|
|
120
|
+
options.temperature = temperature;
|
|
121
|
+
return new openai_1.ChatOpenAI(options);
|
|
122
|
+
}
|
|
64
123
|
}
|
|
65
124
|
exports.AIModels = AIModels;
|
|
66
125
|
//# sourceMappingURL=models.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/langchain/models.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/langchain/models.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BaseMessage } from "langchain";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
/** Modelos DeepSeek (direto ou via OpenRouter) não aceitam `json_schema`; apenas `json_object`. */
|
|
4
|
+
export declare function isDeepSeekJsonObjectOnlyModel(aiModel: string): boolean;
|
|
5
|
+
export declare function buildJsonObjectSystemAppend(schema: z.ZodSchema): string;
|
|
6
|
+
export declare function mergeSystemPromptWithJsonSchema(systemPrompt: string | undefined, schema: z.ZodSchema): string;
|
|
7
|
+
/** Extrai e faz parse de JSON na última mensagem do assistente (suporta fences ```json). */
|
|
8
|
+
export declare function parseJsonFromAssistantMessages(messages: BaseMessage[]): unknown;
|
|
9
|
+
//# sourceMappingURL=structured-output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structured-output.d.ts","sourceRoot":"","sources":["../../src/langchain/structured-output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,CAAC,MAAM,KAAK,CAAC;AAEpB,mGAAmG;AACnG,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAItE;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,GAAG,MAAM,CAQvE;AAED,wBAAgB,+BAA+B,CAC7C,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,MAAM,EAAE,CAAC,CAAC,SAAS,GAClB,MAAM,CAIR;AAkBD,4FAA4F;AAC5F,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,WAAW,EAAE,GACtB,OAAO,CA4BT"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isDeepSeekJsonObjectOnlyModel = isDeepSeekJsonObjectOnlyModel;
|
|
7
|
+
exports.buildJsonObjectSystemAppend = buildJsonObjectSystemAppend;
|
|
8
|
+
exports.mergeSystemPromptWithJsonSchema = mergeSystemPromptWithJsonSchema;
|
|
9
|
+
exports.parseJsonFromAssistantMessages = parseJsonFromAssistantMessages;
|
|
10
|
+
const zod_1 = __importDefault(require("zod"));
|
|
11
|
+
/** Modelos DeepSeek (direto ou via OpenRouter) não aceitam `json_schema`; apenas `json_object`. */
|
|
12
|
+
function isDeepSeekJsonObjectOnlyModel(aiModel) {
|
|
13
|
+
return (aiModel.startsWith("openrouter/deepseek/") || aiModel.startsWith("deepseek/"));
|
|
14
|
+
}
|
|
15
|
+
function buildJsonObjectSystemAppend(schema) {
|
|
16
|
+
const jsonSchema = zod_1.default.toJSONSchema(schema);
|
|
17
|
+
return [
|
|
18
|
+
"",
|
|
19
|
+
"IMPORTANTE: Responda somente com um objeto JSON válido (sem markdown, sem texto extra).",
|
|
20
|
+
"O JSON deve seguir este schema:",
|
|
21
|
+
JSON.stringify(jsonSchema, null, 2),
|
|
22
|
+
].join("\n");
|
|
23
|
+
}
|
|
24
|
+
function mergeSystemPromptWithJsonSchema(systemPrompt, schema) {
|
|
25
|
+
const base = systemPrompt?.trim() ?? "";
|
|
26
|
+
const append = buildJsonObjectSystemAppend(schema);
|
|
27
|
+
return base ? `${base}\n${append}` : append.trim();
|
|
28
|
+
}
|
|
29
|
+
function contentToString(content) {
|
|
30
|
+
if (typeof content === "string")
|
|
31
|
+
return content;
|
|
32
|
+
if (Array.isArray(content)) {
|
|
33
|
+
return content
|
|
34
|
+
.map((part) => {
|
|
35
|
+
if (typeof part === "string")
|
|
36
|
+
return part;
|
|
37
|
+
if (part && typeof part === "object" && "text" in part) {
|
|
38
|
+
return String(part.text ?? "");
|
|
39
|
+
}
|
|
40
|
+
return "";
|
|
41
|
+
})
|
|
42
|
+
.join("");
|
|
43
|
+
}
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
/** Extrai e faz parse de JSON na última mensagem do assistente (suporta fences ```json). */
|
|
47
|
+
function parseJsonFromAssistantMessages(messages) {
|
|
48
|
+
const last = messages.at(-1);
|
|
49
|
+
if (!last) {
|
|
50
|
+
throw new Error("[@luanpoppe/ai] Nenhuma mensagem do assistente para extrair JSON.");
|
|
51
|
+
}
|
|
52
|
+
const raw = contentToString(last.content).trim();
|
|
53
|
+
if (!raw) {
|
|
54
|
+
throw new Error("[@luanpoppe/ai] Resposta vazia ao extrair JSON estruturado.");
|
|
55
|
+
}
|
|
56
|
+
const fenced = raw.match(/^```(?:json)?\s*([\s\S]*?)```$/i);
|
|
57
|
+
const jsonText = (fenced?.[1] ?? raw).trim();
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(jsonText);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
const start = jsonText.indexOf("{");
|
|
63
|
+
const end = jsonText.lastIndexOf("}");
|
|
64
|
+
if (start >= 0 && end > start) {
|
|
65
|
+
return JSON.parse(jsonText.slice(start, end + 1));
|
|
66
|
+
}
|
|
67
|
+
throw new Error("[@luanpoppe/ai] Não foi possível fazer parse do JSON retornado pelo modelo.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=structured-output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structured-output.js","sourceRoot":"","sources":["../../src/langchain/structured-output.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,6 @@ export type CreateToolParams = {
|
|
|
7
7
|
schema: z.ZodSchema;
|
|
8
8
|
};
|
|
9
9
|
export declare class AITools {
|
|
10
|
-
createTool(params: CreateToolParams): import("langchain").DynamicStructuredTool<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, unknown, unknown, unknown, string>;
|
|
10
|
+
createTool(params: CreateToolParams): import("langchain").DynamicStructuredTool<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, unknown, unknown, unknown, unknown, string>;
|
|
11
11
|
}
|
|
12
12
|
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter-client.d.ts","sourceRoot":"","sources":["../../src/utils/openrouter-client.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,eAAO,MAAM,mBAAmB,iCAAiC,CAAC;AAElE,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKnE"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OPENROUTER_API_BASE = void 0;
|
|
7
|
+
exports.createOpenRouterOpenAIClient = createOpenRouterOpenAIClient;
|
|
8
|
+
const openai_1 = __importDefault(require("openai"));
|
|
9
|
+
exports.OPENROUTER_API_BASE = "https://openrouter.ai/api/v1";
|
|
10
|
+
function createOpenRouterOpenAIClient(apiKey) {
|
|
11
|
+
return new openai_1.default({
|
|
12
|
+
apiKey,
|
|
13
|
+
baseURL: exports.OPENROUTER_API_BASE,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=openrouter-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter-client.js","sourceRoot":"","sources":["../../src/utils/openrouter-client.ts"],"names":[],"mappings":";;;;;;AAIA,oEAKC;AATD,oDAA4B;AAEf,QAAA,mBAAmB,GAAG,8BAA8B,CAAC;AAElE,SAAgB,4BAA4B,CAAC,MAAc;IACzD,OAAO,IAAI,gBAAM,CAAC;QAChB,MAAM;QACN,OAAO,EAAE,2BAAmB;KAC7B,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Bug do LangChain no Windows: Transcrição de Áudio com Whisper - 2026-02-02
|
|
2
2
|
|
|
3
|
+
> **Nota (2026):** `@luanpoppe/ai` não usa mais `OpenAIWhisperAudio` de `@langchain/community` (pacote sunset pela LangChain). A transcrição OpenAI usa o SDK `openai` diretamente (`toFile` com buffer em memória). Este documento permanece como contexto histórico do bug no loader do LangChain.
|
|
4
|
+
|
|
3
5
|
## Resumo
|
|
4
6
|
|
|
5
7
|
O loader `OpenAIWhisperAudio` do LangChain passa o **caminho completo do arquivo** (ex: `C:\Users\...\whisper-xxx.webm`) para a API da OpenAI. No Windows, isso pode causar falhas no envio multipart devido a backslashes e caracteres especiais no path.
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luanpoppe/ai",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.1.6",
|
|
4
|
+
"description": "TypeScript SDK for LLM chat, structured output, memory, audio STT/TTS, and embeddings via OpenAI, Gemini, OpenRouter, and local models (Ollama/LM Studio).",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"keywords": [],
|
|
7
7
|
"author": "",
|
|
8
8
|
"license": "ISC",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@langchain/
|
|
10
|
+
"@langchain/ollama": "^1.2.6",
|
|
11
11
|
"@langchain/langgraph": "^1.1.1",
|
|
12
12
|
"openai": "^6.17.0",
|
|
13
|
-
"@langchain/core": "^1.1.
|
|
13
|
+
"@langchain/core": "^1.1.48",
|
|
14
14
|
"@langchain/google-genai": "^2.1.12",
|
|
15
15
|
"@langchain/openai": "^1.2.3",
|
|
16
16
|
"langchain": "^1.2.12",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type OpenAIEmbeddingModel =
|
|
2
|
+
| "text-embedding-3-small"
|
|
3
|
+
| "text-embedding-3-large"
|
|
4
|
+
| "text-embedding-ada-002";
|
|
5
|
+
|
|
6
|
+
export type GeminiEmbeddingModel =
|
|
7
|
+
| "gemini-embedding-001"
|
|
8
|
+
| "gemini-embedding-2-preview";
|
|
9
|
+
|
|
10
|
+
type OpenRouterOpenAIEmbeddingModel = `openai/${OpenAIEmbeddingModel}`;
|
|
11
|
+
|
|
12
|
+
type OpenRouterGoogleEmbeddingModel = `google/${GeminiEmbeddingModel}`;
|
|
13
|
+
|
|
14
|
+
type OpenRouterQwenEmbeddingModel =
|
|
15
|
+
| "qwen/qwen3-embedding-8b"
|
|
16
|
+
| "qwen/qwen3-embedding-4b"
|
|
17
|
+
| "qwen/qwen3-embedding-0.6b";
|
|
18
|
+
|
|
19
|
+
type OpenRouterOtherEmbeddingModel =
|
|
20
|
+
| "BAAI/bge-m3"
|
|
21
|
+
| "perplexity/pplx-embed-v1-4b"
|
|
22
|
+
| "perplexity/pplx-embed-v1-0.6b";
|
|
23
|
+
|
|
24
|
+
export type OpenRouterEmbeddingModel =
|
|
25
|
+
| OpenRouterOpenAIEmbeddingModel
|
|
26
|
+
| OpenRouterGoogleEmbeddingModel
|
|
27
|
+
| OpenRouterQwenEmbeddingModel
|
|
28
|
+
| OpenRouterOtherEmbeddingModel;
|
|
29
|
+
|
|
30
|
+
export type OpenRouterPrefixedEmbeddingModel =
|
|
31
|
+
`openrouter/${OpenRouterEmbeddingModel}`;
|
|
32
|
+
|
|
33
|
+
export type AIEmbeddingModelNames =
|
|
34
|
+
| OpenAIEmbeddingModel
|
|
35
|
+
| GeminiEmbeddingModel
|
|
36
|
+
| OpenRouterPrefixedEmbeddingModel;
|
|
@@ -114,4 +114,15 @@ type OpenRouterProvidersModels =
|
|
|
114
114
|
|
|
115
115
|
type OpenRouterModels = `openrouter/${OpenRouterProvidersModels}`;
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
/** Modelos via API nativa do Ollama (prefixo `ollama/`) */
|
|
118
|
+
type OllamaModels = `ollama/${string}`;
|
|
119
|
+
|
|
120
|
+
/** Modelos via API compatível com OpenAI — LM Studio, Ollama `/v1`, vLLM, etc. (prefixo `local/`) */
|
|
121
|
+
type LocalModels = `local/${string}`;
|
|
122
|
+
|
|
123
|
+
export type AIModelNames =
|
|
124
|
+
| ChatGPTModels
|
|
125
|
+
| GeminiModels
|
|
126
|
+
| OpenRouterModels
|
|
127
|
+
| OllamaModels
|
|
128
|
+
| LocalModels;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preferências de roteamento de providers do OpenRouter.
|
|
3
|
+
* @see https://openrouter.ai/docs/guides/routing/provider-selection
|
|
4
|
+
*/
|
|
5
|
+
export type OpenRouterProviderSort =
|
|
6
|
+
| "price"
|
|
7
|
+
| "latency"
|
|
8
|
+
| "throughput"
|
|
9
|
+
| { by: "price" | "latency" | "throughput"; partition?: "model" | "none" };
|
|
10
|
+
|
|
11
|
+
/** Teto de preço em USD por milhão de tokens (prompt/completion) ou por request/imagem. */
|
|
12
|
+
export type OpenRouterMaxPrice = {
|
|
13
|
+
prompt?: number;
|
|
14
|
+
completion?: number;
|
|
15
|
+
image?: number;
|
|
16
|
+
request?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type OpenRouterProviderPreferences = {
|
|
20
|
+
only?: string[];
|
|
21
|
+
order?: string[];
|
|
22
|
+
ignore?: string[];
|
|
23
|
+
allow_fallbacks?: boolean;
|
|
24
|
+
require_parameters?: boolean;
|
|
25
|
+
sort?: OpenRouterProviderSort;
|
|
26
|
+
max_price?: OpenRouterMaxPrice;
|
|
27
|
+
};
|
package/src/ai.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AIModels,
|
|
3
|
+
DEFAULT_OLLAMA_BASE_URL,
|
|
4
|
+
LLMModelConfig,
|
|
5
|
+
} from "./langchain/models";
|
|
6
|
+
import {
|
|
7
|
+
isDeepSeekJsonObjectOnlyModel,
|
|
8
|
+
mergeSystemPromptWithJsonSchema,
|
|
9
|
+
parseJsonFromAssistantMessages,
|
|
10
|
+
} from "./langchain/structured-output";
|
|
2
11
|
import z from "zod";
|
|
3
12
|
import { createAgent, modelRetryMiddleware } from "langchain";
|
|
4
13
|
import type { AIAgent } from "./@types/agent";
|
|
@@ -27,6 +36,12 @@ type AIConstructor = {
|
|
|
27
36
|
googleGeminiToken?: string;
|
|
28
37
|
openAIApiKey?: string;
|
|
29
38
|
openRouterApiKey?: string;
|
|
39
|
+
/** URL base da API nativa do Ollama (modelos `ollama/*`). Padrão: http://127.0.0.1:11434 */
|
|
40
|
+
ollamaBaseUrl?: string;
|
|
41
|
+
/** URL base OpenAI-compatible para modelos `local/*` (ex.: http://localhost:1234/v1) */
|
|
42
|
+
localBaseUrl?: string;
|
|
43
|
+
/** API key enviada ao servidor local; muitos aceitam qualquer valor */
|
|
44
|
+
localApiKey?: string;
|
|
30
45
|
/** Lista padrão de modelos de fallback (usada em call/callStructuredOutput quando não passada no método) */
|
|
31
46
|
aiModelsFallback?: AIModelNames[];
|
|
32
47
|
/** Configuração de persistência de histórico (memory, sqlite, postgres, redis, mongodb) ou instância AIMemory */
|
|
@@ -164,24 +179,56 @@ export class AI {
|
|
|
164
179
|
|
|
165
180
|
const { result: response, agent } = await this.invokeWithRetryAndFallback(
|
|
166
181
|
params,
|
|
167
|
-
(paramsForModel) =>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
182
|
+
(paramsForModel) => {
|
|
183
|
+
const useJsonObject = isDeepSeekJsonObjectOnlyModel(
|
|
184
|
+
paramsForModel.aiModel,
|
|
185
|
+
);
|
|
186
|
+
const agentParams = useJsonObject
|
|
187
|
+
? this.withDeepSeekJsonObjectParams(paramsForModel, outputSchema)
|
|
188
|
+
: paramsForModel;
|
|
189
|
+
|
|
190
|
+
return createAgent({
|
|
191
|
+
...this.standardAgent(agentParams, checkpointer),
|
|
192
|
+
...(useJsonObject
|
|
193
|
+
? { responseFormat: undefined as any }
|
|
194
|
+
: {
|
|
195
|
+
responseFormat: this.normalizeSchemaForOpenAI(
|
|
196
|
+
outputSchema,
|
|
197
|
+
paramsForModel.aiModel,
|
|
198
|
+
) as any,
|
|
199
|
+
}),
|
|
200
|
+
});
|
|
201
|
+
},
|
|
175
202
|
(agent) => agent.invoke({ messages }, invokeConfig as any),
|
|
176
203
|
);
|
|
177
204
|
|
|
178
205
|
this._memory?.setAgent(agent);
|
|
179
206
|
|
|
180
|
-
const parsedResponse =
|
|
207
|
+
const parsedResponse =
|
|
208
|
+
response?.structuredResponse != null
|
|
209
|
+
? outputSchema.parse(response.structuredResponse)
|
|
210
|
+
: outputSchema.parse(parseJsonFromAssistantMessages(response.messages));
|
|
181
211
|
|
|
182
212
|
return { response: parsedResponse };
|
|
183
213
|
}
|
|
184
214
|
|
|
215
|
+
private withDeepSeekJsonObjectParams<T extends AICallParams>(
|
|
216
|
+
params: T,
|
|
217
|
+
outputSchema: z.ZodSchema,
|
|
218
|
+
): T {
|
|
219
|
+
return {
|
|
220
|
+
...params,
|
|
221
|
+
systemPrompt: mergeSystemPromptWithJsonSchema(
|
|
222
|
+
params.systemPrompt,
|
|
223
|
+
outputSchema,
|
|
224
|
+
),
|
|
225
|
+
modelConfig: {
|
|
226
|
+
...params.modelConfig,
|
|
227
|
+
openRouterForceJsonObject: true,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
185
232
|
/**
|
|
186
233
|
* Normaliza schemas Zod para compatibilidade com OpenAI/OpenRouter
|
|
187
234
|
* OpenAI exige que todos os campos em properties estejam no array required
|
|
@@ -278,6 +325,38 @@ export class AI {
|
|
|
278
325
|
...config,
|
|
279
326
|
model: modelName,
|
|
280
327
|
apiKey: this.config.openRouterApiKey,
|
|
328
|
+
openRouterProvider: modelConfig?.openRouterProvider,
|
|
329
|
+
openRouterAllowAllProviders: modelConfig?.openRouterAllowAllProviders,
|
|
330
|
+
openRouterForceJsonObject: modelConfig?.openRouterForceJsonObject,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (aiModel.startsWith("ollama/")) {
|
|
335
|
+
const modelName = aiModel.replace(/^ollama\//, "");
|
|
336
|
+
return AIModels.ollama({
|
|
337
|
+
model: modelName,
|
|
338
|
+
baseUrl: modelConfig?.baseUrl ?? this.config.ollamaBaseUrl ?? DEFAULT_OLLAMA_BASE_URL,
|
|
339
|
+
maxTokens: config.maxTokens,
|
|
340
|
+
temperature: config.temperature,
|
|
341
|
+
numCtx: modelConfig?.numCtx,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (aiModel.startsWith("local/")) {
|
|
346
|
+
const modelName = aiModel.replace(/^local\//, "");
|
|
347
|
+
const baseURL = modelConfig?.baseUrl ?? this.config.localBaseUrl;
|
|
348
|
+
if (!baseURL) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
'[@luanpoppe/ai] localBaseUrl é obrigatório no construtor de AI (ou modelConfig.baseUrl na chamada) para modelos "local/*". ' +
|
|
351
|
+
"Ex.: localBaseUrl: \"http://localhost:1234/v1\" (LM Studio) ou \"http://127.0.0.1:11434/v1\" (Ollama OpenAI-compatible).",
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
return AIModels.openaiCompatible({
|
|
355
|
+
model: modelName,
|
|
356
|
+
baseURL,
|
|
357
|
+
apiKey: this.config.localApiKey,
|
|
358
|
+
maxTokens: config.maxTokens,
|
|
359
|
+
temperature: config.temperature,
|
|
281
360
|
});
|
|
282
361
|
}
|
|
283
362
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
2
|
+
import { HumanMessage } from "langchain";
|
|
3
|
+
import type { AudioBuffer } from "../@types/audio";
|
|
4
|
+
import { AudioUtils } from "../utils/audio-utils";
|
|
5
|
+
import type { GeminiTranscriptionOptions } from "./types";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_GEMINI_TRANSCRIBE_PROMPT =
|
|
8
|
+
"Transcreva o áudio a seguir em texto. Se possível, inclua timestamps aproximados no formato [mm:ss]. Responda apenas com a transcrição.";
|
|
9
|
+
|
|
10
|
+
export async function transcribeWithGeminiPrompt(
|
|
11
|
+
audioBuffer: AudioBuffer,
|
|
12
|
+
options: GeminiTranscriptionOptions = {},
|
|
13
|
+
): Promise<string> {
|
|
14
|
+
const apiKey = options.googleGeminiToken;
|
|
15
|
+
if (!apiKey) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
"googleGeminiToken é obrigatório para transcribeWithGeminiPrompt.",
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const model = new ChatGoogleGenerativeAI({
|
|
22
|
+
model: options.model ?? "gemini-2.5-flash",
|
|
23
|
+
apiKey,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const base64Data = AudioUtils.bufferToBase64(audioBuffer);
|
|
27
|
+
const mimeType = AudioUtils.detectAudioMimeType(audioBuffer);
|
|
28
|
+
|
|
29
|
+
const message = new HumanMessage({
|
|
30
|
+
content: [
|
|
31
|
+
{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: options.prompt ?? DEFAULT_GEMINI_TRANSCRIBE_PROMPT,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: "audio",
|
|
37
|
+
source_type: "base64",
|
|
38
|
+
data: base64Data,
|
|
39
|
+
mime_type: mimeType,
|
|
40
|
+
},
|
|
41
|
+
] as any,
|
|
42
|
+
} as any);
|
|
43
|
+
|
|
44
|
+
const response = await model.invoke([message]);
|
|
45
|
+
const content = response.content;
|
|
46
|
+
return typeof content === "string" ? content : JSON.stringify(content);
|
|
47
|
+
}
|