@h-ai/ai 0.1.0-alpha.49 → 0.1.0-alpha.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -2
- package/dist/{ai-audio-ws-protocol-BAOyKbBU.d.ts → ai-audio-ws-protocol-COIRfUFF.d.ts} +1 -1
- package/dist/{ai-reasoning-types-DwxulYKu.d.ts → ai-reasoning-types-CuITylkA.d.ts} +217 -43
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +1 -1
- package/dist/{chunk-4FDOGXIS.js → chunk-PLNY4UUT.js} +29 -14
- package/dist/chunk-PLNY4UUT.js.map +1 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +625 -250
- package/dist/index.js.map +1 -1
- package/package.json +11 -7
- package/dist/chunk-4FDOGXIS.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { aiM, AIConfigSchema, MemoryConfigSchema, RetrievalConfigSchema, KnowledgeConfigSchema, TokenConfigSchema, SummaryConfigSchema, CompressConfigSchema, AudioConfigSchema, ImageConfigSchema, resolveImageModel, resolveModelEntry, resolveAudioModel, resolveModelApi } from './chunk-
|
|
2
|
-
export { A2AConfigSchema, A2ASkillConfigSchema, AIConfigSchema, AUDIO_WS_PATH, ApiTypeSchema, AudioConfigSchema, AudioFormatSchema, AudioModelEntrySchema, AudioProviderSchema, AudioWsClientMessageSchema, AudioWsDoneMessageSchema, AudioWsStartMessageSchema, AudioWsTextMessageSchema, CompressConfigSchema, CompressionStrategySchema, EmbeddingConfigSchema, EntityTypeSchema, FileConfigSchema, ImageConfigSchema, ImageModelEntrySchema, ImageProviderSchema, KnowledgeConfigSchema, LLMConfigSchema, MCPConfigSchema, MCPServerCapabilitiesSchema, MCPServerConfigSchema, MemoryConfigSchema, MemoryTypeSchema, ModelEntrySchema, ModelScenarioSchema, OptionalSecretSchema, RetrievalConfigSchema, RetrievalSourceSchema, SummaryConfigSchema, TokenConfigSchema, resolveAudioModel, resolveImageModel, resolveModelApi, resolveModelEntry, serializePlayableAudio } from './chunk-
|
|
1
|
+
import { aiM, AIConfigSchema, MemoryConfigSchema, RetrievalConfigSchema, KnowledgeConfigSchema, TokenConfigSchema, SummaryConfigSchema, CompressConfigSchema, AudioConfigSchema, ImageConfigSchema, resolveImageModel, resolveModelEntry, resolveAudioModel, ensureAudioCredential, resolveModelApi } from './chunk-PLNY4UUT.js';
|
|
2
|
+
export { A2AConfigSchema, A2ASkillConfigSchema, AIConfigSchema, AUDIO_WS_PATH, ApiTypeSchema, AudioConfigSchema, AudioFormatSchema, AudioModelEntrySchema, AudioOperationSchema, AudioOperationsSchema, AudioProviderSchema, AudioWsClientMessageSchema, AudioWsDoneMessageSchema, AudioWsStartMessageSchema, AudioWsTextMessageSchema, CompressConfigSchema, CompressionStrategySchema, EmbeddingConfigSchema, EntityTypeSchema, FileConfigSchema, ImageConfigSchema, ImageModelEntrySchema, ImageProviderSchema, KnowledgeConfigSchema, LLMConfigSchema, MCPConfigSchema, MCPServerCapabilitiesSchema, MCPServerConfigSchema, MemoryConfigSchema, MemoryTypeSchema, ModelEntrySchema, ModelScenarioSchema, OptionalSecretSchema, RetrievalConfigSchema, RetrievalSourceSchema, SummaryConfigSchema, TokenConfigSchema, ensureAudioCredential, resolveAudioModel, resolveImageModel, resolveModelApi, resolveModelEntry, serializePlayableAudio } from './chunk-PLNY4UUT.js';
|
|
3
3
|
import { HaiAIError } from './chunk-JC7QBT3C.js';
|
|
4
4
|
export { HaiAIError } from './chunk-JC7QBT3C.js';
|
|
5
5
|
import { core, err, ok } from '@h-ai/core';
|
|
@@ -339,6 +339,40 @@ function fromBase64(base64) {
|
|
|
339
339
|
function concatChunks(chunks) {
|
|
340
340
|
return new Uint8Array(Buffer.concat(chunks.map((c) => Buffer.from(c))));
|
|
341
341
|
}
|
|
342
|
+
var AUDIO_UPLOAD_EXT = { pcm16: "wav", wav: "wav", mp3: "mp3", opus: "ogg" };
|
|
343
|
+
var AUDIO_UPLOAD_MIME = { pcm16: "audio/wav", wav: "audio/wav", mp3: "audio/mpeg", opus: "audio/opus" };
|
|
344
|
+
function toAudioUploadPart(audio, baseName = "audio") {
|
|
345
|
+
if (audio.format === "pcm16") {
|
|
346
|
+
const wav = wrapPcm16ToWav(audio.data, audio.sampleRate ?? 16e3, audio.channels ?? 1);
|
|
347
|
+
return { data: wav, filename: `${baseName}.wav`, mimeType: "audio/wav" };
|
|
348
|
+
}
|
|
349
|
+
return { data: audio.data, filename: `${baseName}.${AUDIO_UPLOAD_EXT[audio.format]}`, mimeType: AUDIO_UPLOAD_MIME[audio.format] };
|
|
350
|
+
}
|
|
351
|
+
function toAudioBlob(part) {
|
|
352
|
+
return new Blob([new Uint8Array(part.data)], { type: part.mimeType });
|
|
353
|
+
}
|
|
354
|
+
function wrapPcm16ToWav(pcm, sampleRate, channels = 1) {
|
|
355
|
+
const bitsPerSample = 16;
|
|
356
|
+
const byteRate = sampleRate * channels * bitsPerSample / 8;
|
|
357
|
+
const blockAlign = channels * bitsPerSample / 8;
|
|
358
|
+
const dataSize = pcm.length;
|
|
359
|
+
const buffer = Buffer.alloc(44 + dataSize);
|
|
360
|
+
buffer.write("RIFF", 0);
|
|
361
|
+
buffer.writeUInt32LE(36 + dataSize, 4);
|
|
362
|
+
buffer.write("WAVE", 8);
|
|
363
|
+
buffer.write("fmt ", 12);
|
|
364
|
+
buffer.writeUInt32LE(16, 16);
|
|
365
|
+
buffer.writeUInt16LE(1, 20);
|
|
366
|
+
buffer.writeUInt16LE(channels, 22);
|
|
367
|
+
buffer.writeUInt32LE(sampleRate, 24);
|
|
368
|
+
buffer.writeUInt32LE(byteRate, 28);
|
|
369
|
+
buffer.writeUInt16LE(blockAlign, 32);
|
|
370
|
+
buffer.writeUInt16LE(bitsPerSample, 34);
|
|
371
|
+
buffer.write("data", 36);
|
|
372
|
+
buffer.writeUInt32LE(dataSize, 40);
|
|
373
|
+
Buffer.from(pcm).copy(buffer, 44);
|
|
374
|
+
return new Uint8Array(buffer);
|
|
375
|
+
}
|
|
342
376
|
function errorMessage(error) {
|
|
343
377
|
return error instanceof Error ? error.message : String(error);
|
|
344
378
|
}
|
|
@@ -362,6 +396,14 @@ function mapStreamError(error, signal) {
|
|
|
362
396
|
function toAudioErrorResult(error, signal) {
|
|
363
397
|
return { success: false, error: mapStreamError(error, signal) };
|
|
364
398
|
}
|
|
399
|
+
function combineSignal(signal, timeout) {
|
|
400
|
+
const timeoutSignal = AbortSignal.timeout(timeout);
|
|
401
|
+
return signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
402
|
+
}
|
|
403
|
+
async function describeHttpError(response) {
|
|
404
|
+
const text = await response.text().catch(() => "");
|
|
405
|
+
return `HTTP ${response.status} ${text.slice(0, 200)}`.trim();
|
|
406
|
+
}
|
|
365
407
|
var logger2 = core.logger.child({ module: "ai", scope: "audio-ws" });
|
|
366
408
|
function openAudioWebSocket(url, headers, options) {
|
|
367
409
|
return new Promise((resolve, reject) => {
|
|
@@ -759,7 +801,11 @@ function createDoubaoAudioProvider() {
|
|
|
759
801
|
conn.close();
|
|
760
802
|
}
|
|
761
803
|
}
|
|
762
|
-
return {
|
|
804
|
+
return {
|
|
805
|
+
transcription: { transcribe, transcribeStream },
|
|
806
|
+
synthesis: { synthesize, synthesizeStream, resolveSynthesisOutput },
|
|
807
|
+
getCapabilities: () => DOUBAO_CAPABILITIES
|
|
808
|
+
};
|
|
763
809
|
}
|
|
764
810
|
function resolveSynthesisOutput(request) {
|
|
765
811
|
const format = request.format ?? "pcm16";
|
|
@@ -776,6 +822,120 @@ function decodeBinaryMessage(message) {
|
|
|
776
822
|
return void 0;
|
|
777
823
|
return decodeFrame(message.binary);
|
|
778
824
|
}
|
|
825
|
+
var logger4 = core.logger.child({ module: "ai", scope: "audio-indextts" });
|
|
826
|
+
var INDEX_TTS_CAPABILITIES = {
|
|
827
|
+
synthesize: {
|
|
828
|
+
supported: true,
|
|
829
|
+
incrementalTextInput: false,
|
|
830
|
+
streamingAudioOutput: false,
|
|
831
|
+
languageSelection: true,
|
|
832
|
+
presetVoice: false,
|
|
833
|
+
speakerReference: true,
|
|
834
|
+
speakerReferenceRequired: true,
|
|
835
|
+
styleReference: true,
|
|
836
|
+
instruction: false,
|
|
837
|
+
speedControl: true,
|
|
838
|
+
targetDuration: true,
|
|
839
|
+
supportedLanguages: ["zh", "en", "ja"]
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
var HEADER_DURATION_MS = "x-hai-audio-duration-ms";
|
|
843
|
+
var HEADER_DURATION_MATCHED = "x-hai-duration-matched";
|
|
844
|
+
var HEADER_APPLIED_SPEED = "x-hai-applied-speed";
|
|
845
|
+
var HEADER_SAMPLE_RATE = "x-hai-audio-sample-rate";
|
|
846
|
+
var HEADER_CHANNELS = "x-hai-audio-channels";
|
|
847
|
+
function createIndexTtsAudioProvider() {
|
|
848
|
+
async function synthesize(request) {
|
|
849
|
+
const { model, text, language, speakerReference, styleReference, styleStrength, speed, targetDurationMs, durationToleranceMs, format, sampleRate, signal } = request;
|
|
850
|
+
if (!speakerReference)
|
|
851
|
+
return err(HaiAIError.AUDIO_INVALID_REQUEST, aiM("ai_audioInvalidRequest", { params: { reason: "speaker_reference is required" } }));
|
|
852
|
+
const out = resolveSynthesisOutput2({ format, sampleRate });
|
|
853
|
+
try {
|
|
854
|
+
const form = new FormData();
|
|
855
|
+
form.append("text", text);
|
|
856
|
+
form.append("model", model.model);
|
|
857
|
+
if (language)
|
|
858
|
+
form.append("language", language);
|
|
859
|
+
appendReference(form, "speaker_reference", speakerReference);
|
|
860
|
+
if (styleReference)
|
|
861
|
+
appendReference(form, "style_reference", styleReference);
|
|
862
|
+
if (styleStrength !== void 0)
|
|
863
|
+
form.append("style_strength", String(styleStrength));
|
|
864
|
+
if (speed !== void 0)
|
|
865
|
+
form.append("speed", String(speed));
|
|
866
|
+
if (targetDurationMs !== void 0)
|
|
867
|
+
form.append("target_duration_ms", String(targetDurationMs));
|
|
868
|
+
if (durationToleranceMs !== void 0)
|
|
869
|
+
form.append("duration_tolerance_ms", String(durationToleranceMs));
|
|
870
|
+
form.append("response_format", out.format);
|
|
871
|
+
if (out.sampleRate !== void 0)
|
|
872
|
+
form.append("sample_rate", String(out.sampleRate));
|
|
873
|
+
const headers = {};
|
|
874
|
+
if (model.apiKey)
|
|
875
|
+
headers.Authorization = `Bearer ${model.apiKey}`;
|
|
876
|
+
const response = await fetch(`${model.baseUrl.replace(/\/$/, "")}/audio/speech`, {
|
|
877
|
+
method: "POST",
|
|
878
|
+
headers,
|
|
879
|
+
body: form,
|
|
880
|
+
signal: combineSignal(signal, model.timeout)
|
|
881
|
+
});
|
|
882
|
+
if (!response.ok)
|
|
883
|
+
return err(HaiAIError.AUDIO_UPSTREAM_ERROR, aiM("ai_audioUpstreamError", { params: { error: await describeHttpError(response) } }));
|
|
884
|
+
const data = new Uint8Array(await response.arrayBuffer());
|
|
885
|
+
return ok(buildSynthesisResult(data, out, response.headers));
|
|
886
|
+
} catch (error) {
|
|
887
|
+
logger4.debug("IndexTTS synthesize failed", { error: errorMessage(error) });
|
|
888
|
+
return toAudioErrorResult(error, signal);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
return {
|
|
892
|
+
synthesis: { synthesize, resolveSynthesisOutput: resolveSynthesisOutput2 },
|
|
893
|
+
getCapabilities: () => INDEX_TTS_CAPABILITIES
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
function resolveSynthesisOutput2(request) {
|
|
897
|
+
const format = request.format ?? "wav";
|
|
898
|
+
return { format, sampleRate: format === "pcm16" ? request.sampleRate ?? 24e3 : void 0, channels: 1 };
|
|
899
|
+
}
|
|
900
|
+
function appendReference(form, field, reference) {
|
|
901
|
+
const part = toAudioUploadPart(reference.audio, field);
|
|
902
|
+
form.append(field, toAudioBlob(part), part.filename);
|
|
903
|
+
if (reference.transcript)
|
|
904
|
+
form.append(`${field}_text`, reference.transcript);
|
|
905
|
+
if (reference.language)
|
|
906
|
+
form.append(`${field}_language`, reference.language);
|
|
907
|
+
}
|
|
908
|
+
function buildSynthesisResult(data, out, headers) {
|
|
909
|
+
const durationMs = parseNumberHeader(headers.get(HEADER_DURATION_MS));
|
|
910
|
+
const sampleRate = parseNumberHeader(headers.get(HEADER_SAMPLE_RATE)) ?? out.sampleRate;
|
|
911
|
+
const channels = parseNumberHeader(headers.get(HEADER_CHANNELS)) === 2 ? 2 : out.channels;
|
|
912
|
+
const durationMatched = parseBooleanHeader(headers.get(HEADER_DURATION_MATCHED));
|
|
913
|
+
const appliedSpeed = parseNumberHeader(headers.get(HEADER_APPLIED_SPEED));
|
|
914
|
+
const metadata = {};
|
|
915
|
+
if (durationMatched !== void 0)
|
|
916
|
+
metadata.durationMatched = durationMatched;
|
|
917
|
+
if (appliedSpeed !== void 0)
|
|
918
|
+
metadata.speed = appliedSpeed;
|
|
919
|
+
return {
|
|
920
|
+
data,
|
|
921
|
+
format: out.format,
|
|
922
|
+
sampleRate,
|
|
923
|
+
channels,
|
|
924
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
925
|
+
...Object.keys(metadata).length ? { metadata } : {}
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
function parseNumberHeader(value) {
|
|
929
|
+
if (value === null)
|
|
930
|
+
return void 0;
|
|
931
|
+
const parsed = Number(value);
|
|
932
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
933
|
+
}
|
|
934
|
+
function parseBooleanHeader(value) {
|
|
935
|
+
if (value === null)
|
|
936
|
+
return void 0;
|
|
937
|
+
return value === "true" || value === "1";
|
|
938
|
+
}
|
|
779
939
|
|
|
780
940
|
// src/llm/ai-llm-stream.ts
|
|
781
941
|
function createStreamProcessor() {
|
|
@@ -928,7 +1088,7 @@ function encodeSSE(event) {
|
|
|
928
1088
|
}
|
|
929
1089
|
|
|
930
1090
|
// src/audio/providers/ai-audio-provider-mimo.ts
|
|
931
|
-
var
|
|
1091
|
+
var logger5 = core.logger.child({ module: "ai", scope: "audio-mimo" });
|
|
932
1092
|
var MIMO_CAPABILITIES = {
|
|
933
1093
|
transcribe: { supported: true, realtimeAudioInput: false, speechBoundaryEvents: false, streamingTranscriptOutput: true },
|
|
934
1094
|
synthesize: { supported: true, incrementalTextInput: false, streamingAudioOutput: true }
|
|
@@ -955,7 +1115,7 @@ function createMimoAudioProvider() {
|
|
|
955
1115
|
const text = body.choices?.[0]?.message?.content ?? "";
|
|
956
1116
|
return ok({ text });
|
|
957
1117
|
} catch (error) {
|
|
958
|
-
|
|
1118
|
+
logger5.debug("MiMo transcribe failed", { error: errorMessage(error) });
|
|
959
1119
|
return toAudioErrorResult(error, signal);
|
|
960
1120
|
}
|
|
961
1121
|
}
|
|
@@ -988,7 +1148,7 @@ function createMimoAudioProvider() {
|
|
|
988
1148
|
}
|
|
989
1149
|
async function synthesize(request) {
|
|
990
1150
|
const { model, text, voice, instruction, format, sampleRate, signal } = request;
|
|
991
|
-
const out =
|
|
1151
|
+
const out = resolveSynthesisOutput3({ format, sampleRate });
|
|
992
1152
|
try {
|
|
993
1153
|
const response = await postChat(model.baseUrl, model.apiKey, {
|
|
994
1154
|
model: model.model,
|
|
@@ -1003,13 +1163,13 @@ function createMimoAudioProvider() {
|
|
|
1003
1163
|
return err(HaiAIError.AUDIO_UPSTREAM_ERROR, aiM("ai_audioUpstreamError", { params: { error: "missing audio data" } }));
|
|
1004
1164
|
return ok({ data: fromBase64(base64), format: out.format, sampleRate: out.sampleRate, channels: out.channels });
|
|
1005
1165
|
} catch (error) {
|
|
1006
|
-
|
|
1166
|
+
logger5.debug("MiMo synthesize failed", { error: errorMessage(error) });
|
|
1007
1167
|
return toAudioErrorResult(error, signal);
|
|
1008
1168
|
}
|
|
1009
1169
|
}
|
|
1010
1170
|
async function* synthesizeStream(request) {
|
|
1011
1171
|
const { model, text, voice, instruction, format, signal } = request;
|
|
1012
|
-
const outFormat =
|
|
1172
|
+
const outFormat = resolveSynthesisOutput3({ format }).format;
|
|
1013
1173
|
const segments = typeof text === "string" ? [text] : streamSentences(text);
|
|
1014
1174
|
for await (const segment of segments) {
|
|
1015
1175
|
if (!segment)
|
|
@@ -1029,9 +1189,13 @@ function createMimoAudioProvider() {
|
|
|
1029
1189
|
}
|
|
1030
1190
|
}
|
|
1031
1191
|
}
|
|
1032
|
-
return {
|
|
1192
|
+
return {
|
|
1193
|
+
transcription: { transcribe, transcribeStream },
|
|
1194
|
+
synthesis: { synthesize, synthesizeStream, resolveSynthesisOutput: resolveSynthesisOutput3 },
|
|
1195
|
+
getCapabilities: () => MIMO_CAPABILITIES
|
|
1196
|
+
};
|
|
1033
1197
|
}
|
|
1034
|
-
function
|
|
1198
|
+
function resolveSynthesisOutput3(request) {
|
|
1035
1199
|
const format = request.format === "wav" ? "wav" : "pcm16";
|
|
1036
1200
|
return { format, sampleRate: format === "pcm16" ? request.sampleRate ?? 24e3 : void 0, channels: 1 };
|
|
1037
1201
|
}
|
|
@@ -1080,15 +1244,7 @@ async function* readChunks(response) {
|
|
|
1080
1244
|
reader.releaseLock();
|
|
1081
1245
|
}
|
|
1082
1246
|
}
|
|
1083
|
-
|
|
1084
|
-
const timeoutSignal = AbortSignal.timeout(timeout);
|
|
1085
|
-
return signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
1086
|
-
}
|
|
1087
|
-
async function describeHttpError(response) {
|
|
1088
|
-
const text = await response.text().catch(() => "");
|
|
1089
|
-
return `HTTP ${response.status} ${text.slice(0, 200)}`.trim();
|
|
1090
|
-
}
|
|
1091
|
-
var logger5 = core.logger.child({ module: "ai", scope: "audio-openai" });
|
|
1247
|
+
var logger6 = core.logger.child({ module: "ai", scope: "audio-openai" });
|
|
1092
1248
|
var OPENAI_CAPABILITIES = {
|
|
1093
1249
|
transcribe: { supported: true, realtimeAudioInput: false, speechBoundaryEvents: false, streamingTranscriptOutput: false },
|
|
1094
1250
|
synthesize: { supported: true, incrementalTextInput: false, streamingAudioOutput: true }
|
|
@@ -1099,12 +1255,6 @@ var OPENAI_SPEECH_FORMAT = {
|
|
|
1099
1255
|
mp3: "mp3",
|
|
1100
1256
|
opus: "opus"
|
|
1101
1257
|
};
|
|
1102
|
-
var OPENAI_UPLOAD_FILENAME = {
|
|
1103
|
-
pcm16: "audio.wav",
|
|
1104
|
-
wav: "audio.wav",
|
|
1105
|
-
mp3: "audio.mp3",
|
|
1106
|
-
opus: "audio.ogg"
|
|
1107
|
-
};
|
|
1108
1258
|
function createOpenAIAudioProvider() {
|
|
1109
1259
|
function createClient(apiKey, baseUrl, timeout) {
|
|
1110
1260
|
return new OpenAI3({ apiKey, baseURL: baseUrl, timeout });
|
|
@@ -1124,7 +1274,7 @@ function createOpenAIAudioProvider() {
|
|
|
1124
1274
|
}, { signal });
|
|
1125
1275
|
return ok({ text: result.text });
|
|
1126
1276
|
} catch (error) {
|
|
1127
|
-
|
|
1277
|
+
logger6.debug("OpenAI transcribe failed", { error: errorMessage(error) });
|
|
1128
1278
|
return toAudioErrorResult(error, signal);
|
|
1129
1279
|
}
|
|
1130
1280
|
}
|
|
@@ -1161,7 +1311,7 @@ function createOpenAIAudioProvider() {
|
|
|
1161
1311
|
const { model, text, voice, instruction, format, sampleRate, signal } = request;
|
|
1162
1312
|
if (!model.apiKey)
|
|
1163
1313
|
return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_audioMissingApiKey", { params: { provider: "openai" } }));
|
|
1164
|
-
const out =
|
|
1314
|
+
const out = resolveSynthesisOutput4({ format, sampleRate });
|
|
1165
1315
|
try {
|
|
1166
1316
|
const client = createClient(model.apiKey, model.baseUrl, model.timeout);
|
|
1167
1317
|
const response = await client.audio.speech.create({
|
|
@@ -1174,7 +1324,7 @@ function createOpenAIAudioProvider() {
|
|
|
1174
1324
|
const data = new Uint8Array(await response.arrayBuffer());
|
|
1175
1325
|
return ok({ data, format: out.format, sampleRate: out.sampleRate, channels: out.channels });
|
|
1176
1326
|
} catch (error) {
|
|
1177
|
-
|
|
1327
|
+
logger6.debug("OpenAI synthesize failed", { error: errorMessage(error) });
|
|
1178
1328
|
return toAudioErrorResult(error, signal);
|
|
1179
1329
|
}
|
|
1180
1330
|
}
|
|
@@ -1182,7 +1332,7 @@ function createOpenAIAudioProvider() {
|
|
|
1182
1332
|
const { model, text, voice, instruction, format, signal } = request;
|
|
1183
1333
|
if (!model.apiKey)
|
|
1184
1334
|
throw audioError(HaiAIError.CONFIGURATION_ERROR, aiM("ai_audioMissingApiKey", { params: { provider: "openai" } }));
|
|
1185
|
-
const outFormat =
|
|
1335
|
+
const outFormat = resolveSynthesisOutput4({ format }).format;
|
|
1186
1336
|
const client = createClient(model.apiKey, model.baseUrl, model.timeout);
|
|
1187
1337
|
const segments = typeof text === "string" ? [text] : streamSentences(text);
|
|
1188
1338
|
for await (const segment of segments) {
|
|
@@ -1214,42 +1364,21 @@ function createOpenAIAudioProvider() {
|
|
|
1214
1364
|
}
|
|
1215
1365
|
}
|
|
1216
1366
|
}
|
|
1217
|
-
return {
|
|
1367
|
+
return {
|
|
1368
|
+
transcription: { transcribe, transcribeStream },
|
|
1369
|
+
synthesis: { synthesize, synthesizeStream, resolveSynthesisOutput: resolveSynthesisOutput4 },
|
|
1370
|
+
getCapabilities: () => OPENAI_CAPABILITIES
|
|
1371
|
+
};
|
|
1218
1372
|
}
|
|
1219
|
-
function
|
|
1373
|
+
function resolveSynthesisOutput4(request) {
|
|
1220
1374
|
const format = request.format ?? "mp3";
|
|
1221
1375
|
return { format, sampleRate: format === "pcm16" ? request.sampleRate ?? 24e3 : void 0, channels: 1 };
|
|
1222
1376
|
}
|
|
1223
1377
|
async function toUploadFile(audio) {
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
return toFile(Buffer.from(wav), "audio.wav");
|
|
1227
|
-
}
|
|
1228
|
-
return toFile(Buffer.from(audio.data), OPENAI_UPLOAD_FILENAME[audio.format]);
|
|
1378
|
+
const part = toAudioUploadPart(audio);
|
|
1379
|
+
return toFile(Buffer.from(part.data), part.filename);
|
|
1229
1380
|
}
|
|
1230
|
-
|
|
1231
|
-
const bitsPerSample = 16;
|
|
1232
|
-
const byteRate = sampleRate * channels * bitsPerSample / 8;
|
|
1233
|
-
const blockAlign = channels * bitsPerSample / 8;
|
|
1234
|
-
const dataSize = pcm.length;
|
|
1235
|
-
const buffer = Buffer.alloc(44 + dataSize);
|
|
1236
|
-
buffer.write("RIFF", 0);
|
|
1237
|
-
buffer.writeUInt32LE(36 + dataSize, 4);
|
|
1238
|
-
buffer.write("WAVE", 8);
|
|
1239
|
-
buffer.write("fmt ", 12);
|
|
1240
|
-
buffer.writeUInt32LE(16, 16);
|
|
1241
|
-
buffer.writeUInt16LE(1, 20);
|
|
1242
|
-
buffer.writeUInt16LE(channels, 22);
|
|
1243
|
-
buffer.writeUInt32LE(sampleRate, 24);
|
|
1244
|
-
buffer.writeUInt32LE(byteRate, 28);
|
|
1245
|
-
buffer.writeUInt16LE(blockAlign, 32);
|
|
1246
|
-
buffer.writeUInt16LE(bitsPerSample, 34);
|
|
1247
|
-
buffer.write("data", 36);
|
|
1248
|
-
buffer.writeUInt32LE(dataSize, 40);
|
|
1249
|
-
Buffer.from(pcm).copy(buffer, 44);
|
|
1250
|
-
return new Uint8Array(buffer);
|
|
1251
|
-
}
|
|
1252
|
-
var logger6 = core.logger.child({ module: "ai", scope: "audio-qwen" });
|
|
1381
|
+
var logger7 = core.logger.child({ module: "ai", scope: "audio-qwen" });
|
|
1253
1382
|
var QWEN_CAPABILITIES = {
|
|
1254
1383
|
transcribe: { supported: true, realtimeAudioInput: true, speechBoundaryEvents: true, streamingTranscriptOutput: true },
|
|
1255
1384
|
synthesize: { supported: true, incrementalTextInput: true, streamingAudioOutput: true }
|
|
@@ -1282,7 +1411,7 @@ function createQwenAudioProvider() {
|
|
|
1282
1411
|
}
|
|
1283
1412
|
return ok({ text: finalText });
|
|
1284
1413
|
} catch (error) {
|
|
1285
|
-
|
|
1414
|
+
logger7.debug("Qwen transcribe failed", { error: errorMessage(error) });
|
|
1286
1415
|
return toAudioErrorResult(error);
|
|
1287
1416
|
}
|
|
1288
1417
|
}
|
|
@@ -1354,21 +1483,21 @@ function createQwenAudioProvider() {
|
|
|
1354
1483
|
}
|
|
1355
1484
|
async function synthesize(request) {
|
|
1356
1485
|
const { format, sampleRate } = request;
|
|
1357
|
-
const out =
|
|
1486
|
+
const out = resolveSynthesisOutput5({ format, sampleRate });
|
|
1358
1487
|
try {
|
|
1359
1488
|
const chunks = [];
|
|
1360
1489
|
for await (const audio of synthesizeStream({ model: request.model, text: request.text, voice: request.voice, instruction: request.instruction, format: out.format, sampleRate, signal: request.signal }))
|
|
1361
1490
|
chunks.push(audio);
|
|
1362
1491
|
return ok({ data: concatChunks(chunks), format: out.format, sampleRate: out.sampleRate, channels: out.channels });
|
|
1363
1492
|
} catch (error) {
|
|
1364
|
-
|
|
1493
|
+
logger7.debug("Qwen synthesize failed", { error: errorMessage(error) });
|
|
1365
1494
|
return toAudioErrorResult(error);
|
|
1366
1495
|
}
|
|
1367
1496
|
}
|
|
1368
1497
|
async function* synthesizeStream(request) {
|
|
1369
1498
|
const { model, text, voice, instruction, format, sampleRate, signal } = request;
|
|
1370
1499
|
const isStreamInput = typeof text !== "string";
|
|
1371
|
-
const outFormat =
|
|
1500
|
+
const outFormat = resolveSynthesisOutput5({ format }).format;
|
|
1372
1501
|
const conn = await connect(model, signal);
|
|
1373
1502
|
try {
|
|
1374
1503
|
conn.send(JSON.stringify({
|
|
@@ -1417,9 +1546,13 @@ function createQwenAudioProvider() {
|
|
|
1417
1546
|
conn.close();
|
|
1418
1547
|
}
|
|
1419
1548
|
}
|
|
1420
|
-
return {
|
|
1549
|
+
return {
|
|
1550
|
+
transcription: { transcribe, transcribeStream },
|
|
1551
|
+
synthesis: { synthesize, synthesizeStream, resolveSynthesisOutput: resolveSynthesisOutput5 },
|
|
1552
|
+
getCapabilities: () => QWEN_CAPABILITIES
|
|
1553
|
+
};
|
|
1421
1554
|
}
|
|
1422
|
-
function
|
|
1555
|
+
function resolveSynthesisOutput5(request) {
|
|
1423
1556
|
const format = request.format ?? "pcm16";
|
|
1424
1557
|
return { format, sampleRate: format === "pcm16" ? request.sampleRate ?? 24e3 : void 0, channels: 1 };
|
|
1425
1558
|
}
|
|
@@ -1430,9 +1563,95 @@ function parseEvent(text) {
|
|
|
1430
1563
|
return {};
|
|
1431
1564
|
}
|
|
1432
1565
|
}
|
|
1566
|
+
var logger8 = core.logger.child({ module: "ai", scope: "audio-whisper" });
|
|
1567
|
+
var WHISPER_CAPABILITIES = {
|
|
1568
|
+
transcribe: {
|
|
1569
|
+
supported: true,
|
|
1570
|
+
realtimeAudioInput: false,
|
|
1571
|
+
speechBoundaryEvents: false,
|
|
1572
|
+
streamingTranscriptOutput: false,
|
|
1573
|
+
languageHint: true,
|
|
1574
|
+
languageDetection: true,
|
|
1575
|
+
segmentTimestamps: true,
|
|
1576
|
+
wordTimestamps: true,
|
|
1577
|
+
contextHints: true,
|
|
1578
|
+
vad: true,
|
|
1579
|
+
speakerDiarization: false
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
function createWhisperAudioProvider() {
|
|
1583
|
+
async function transcribe(request) {
|
|
1584
|
+
const { model, audio, language, contextHints, timestampGranularities, vad, signal } = request;
|
|
1585
|
+
try {
|
|
1586
|
+
const part = toAudioUploadPart(audio);
|
|
1587
|
+
const form = new FormData();
|
|
1588
|
+
form.append("file", toAudioBlob(part), part.filename);
|
|
1589
|
+
form.append("model", model.model);
|
|
1590
|
+
if (language)
|
|
1591
|
+
form.append("language", language);
|
|
1592
|
+
if (contextHints?.length)
|
|
1593
|
+
form.append("prompt", contextHints.join(", "));
|
|
1594
|
+
for (const granularity of timestampGranularities ?? [])
|
|
1595
|
+
form.append("timestamp_granularities", granularity);
|
|
1596
|
+
if (vad !== void 0)
|
|
1597
|
+
form.append("vad", String(vad));
|
|
1598
|
+
const headers = {};
|
|
1599
|
+
if (model.apiKey)
|
|
1600
|
+
headers.Authorization = `Bearer ${model.apiKey}`;
|
|
1601
|
+
const response = await fetch(`${model.baseUrl.replace(/\/$/, "")}/audio/transcriptions`, {
|
|
1602
|
+
method: "POST",
|
|
1603
|
+
headers,
|
|
1604
|
+
body: form,
|
|
1605
|
+
signal: combineSignal(signal, model.timeout)
|
|
1606
|
+
});
|
|
1607
|
+
if (!response.ok)
|
|
1608
|
+
return err(HaiAIError.AUDIO_UPSTREAM_ERROR, aiM("ai_audioUpstreamError", { params: { error: await describeHttpError(response) } }));
|
|
1609
|
+
const body = await response.json();
|
|
1610
|
+
return ok(mapWhisperResponse(body));
|
|
1611
|
+
} catch (error) {
|
|
1612
|
+
logger8.debug("Whisper transcribe failed", { error: errorMessage(error) });
|
|
1613
|
+
return toAudioErrorResult(error, signal);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
return {
|
|
1617
|
+
transcription: { transcribe },
|
|
1618
|
+
getCapabilities: () => WHISPER_CAPABILITIES
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
function mapWhisperResponse(body) {
|
|
1622
|
+
const segments = body.segments?.map(mapWhisperSegment).filter((segment) => segment !== null);
|
|
1623
|
+
return {
|
|
1624
|
+
text: body.text ?? "",
|
|
1625
|
+
language: body.language,
|
|
1626
|
+
durationMs: body.durationMs,
|
|
1627
|
+
...segments?.length ? { segments } : {}
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
function mapWhisperSegment(segment) {
|
|
1631
|
+
if (typeof segment.startMs !== "number" || typeof segment.endMs !== "number")
|
|
1632
|
+
return null;
|
|
1633
|
+
const words = segment.words?.map(mapWhisperWord).filter((word) => word !== null);
|
|
1634
|
+
return {
|
|
1635
|
+
...segment.id !== void 0 ? { id: String(segment.id) } : {},
|
|
1636
|
+
text: segment.text ?? "",
|
|
1637
|
+
startMs: segment.startMs,
|
|
1638
|
+
endMs: segment.endMs,
|
|
1639
|
+
...words?.length ? { words } : {}
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
function mapWhisperWord(word) {
|
|
1643
|
+
if (typeof word.startMs !== "number" || typeof word.endMs !== "number")
|
|
1644
|
+
return null;
|
|
1645
|
+
return {
|
|
1646
|
+
text: word.text ?? "",
|
|
1647
|
+
startMs: word.startMs,
|
|
1648
|
+
endMs: word.endMs,
|
|
1649
|
+
...typeof word.confidence === "number" ? { confidence: word.confidence } : {}
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1433
1652
|
|
|
1434
1653
|
// src/audio/ai-audio-functions.ts
|
|
1435
|
-
var
|
|
1654
|
+
var logger9 = core.logger.child({ module: "ai", scope: "audio" });
|
|
1436
1655
|
function createAudioOperations(config) {
|
|
1437
1656
|
const audioConfig = AudioConfigSchema.parse(config.audio ?? {});
|
|
1438
1657
|
const providerCache = /* @__PURE__ */ new Map();
|
|
@@ -1449,6 +1668,19 @@ function createAudioOperations(config) {
|
|
|
1449
1668
|
return audioError(HaiAIError.AUDIO_INPUT_TOO_LARGE, aiM("ai_audioInputTooLarge", { params: { limit: audioConfig.maxAudioBytes } }));
|
|
1450
1669
|
return null;
|
|
1451
1670
|
}
|
|
1671
|
+
function guardSynthesisReferences(options) {
|
|
1672
|
+
if (options.speakerReference) {
|
|
1673
|
+
const e = guardAudioSize(options.speakerReference.audio);
|
|
1674
|
+
if (e)
|
|
1675
|
+
return e;
|
|
1676
|
+
}
|
|
1677
|
+
if (options.styleReference) {
|
|
1678
|
+
const e = guardAudioSize(options.styleReference.audio);
|
|
1679
|
+
if (e)
|
|
1680
|
+
return e;
|
|
1681
|
+
}
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1452
1684
|
function withStreamTimeout(signal) {
|
|
1453
1685
|
const timeout = AbortSignal.timeout(audioConfig.maxStreamDurationMs);
|
|
1454
1686
|
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
@@ -1462,14 +1694,26 @@ function createAudioOperations(config) {
|
|
|
1462
1694
|
const resolved = resolveAudioModel(audioConfig, "transcribe", request.model, config.llm.apiKey);
|
|
1463
1695
|
if (!resolved.success)
|
|
1464
1696
|
return resolved;
|
|
1697
|
+
const provider = getProvider(resolved.data.provider);
|
|
1698
|
+
const transcription = provider.transcription;
|
|
1699
|
+
if (!transcription)
|
|
1700
|
+
return err(HaiAIError.AUDIO_UNSUPPORTED_INPUT, aiM("ai_audioUnsupportedInput", { params: { provider: resolved.data.provider, reason: "transcription" } }));
|
|
1701
|
+
const credErr = ensureAudioCredential(resolved.data);
|
|
1702
|
+
if (credErr)
|
|
1703
|
+
return { success: false, error: credErr };
|
|
1704
|
+
if (request.strictCapabilities) {
|
|
1705
|
+
const capErr = checkTranscribeCapabilities(resolved.data.provider, request, provider.getCapabilities(resolved.data));
|
|
1706
|
+
if (capErr)
|
|
1707
|
+
return { success: false, error: capErr };
|
|
1708
|
+
}
|
|
1465
1709
|
const startedAt = Date.now();
|
|
1466
1710
|
const context = { provider: resolved.data.provider, model: resolved.data.model, audioBytes: request.audio.data.length };
|
|
1467
|
-
|
|
1468
|
-
const result = await
|
|
1711
|
+
logger9.debug("Audio transcription started", context);
|
|
1712
|
+
const result = await transcription.transcribe({ model: resolved.data, audio: request.audio, language: request.language, contextHints: request.contextHints, timestampGranularities: request.timestampGranularities, vad: request.vad, signal: request.signal });
|
|
1469
1713
|
if (result.success)
|
|
1470
|
-
|
|
1714
|
+
logger9.info("Audio transcription completed", { ...context, durationMs: Date.now() - startedAt, textLength: result.data.text.length });
|
|
1471
1715
|
else
|
|
1472
|
-
|
|
1716
|
+
logger9.warn("Audio transcription failed", { ...context, durationMs: Date.now() - startedAt, code: result.error.code, error: result.error.message });
|
|
1473
1717
|
return result;
|
|
1474
1718
|
}
|
|
1475
1719
|
async function* transcribeStream(request) {
|
|
@@ -1483,75 +1727,112 @@ function createAudioOperations(config) {
|
|
|
1483
1727
|
const resolved = resolveAudioModel(audioConfig, "transcribe", request.model, config.llm.apiKey);
|
|
1484
1728
|
if (!resolved.success)
|
|
1485
1729
|
throw resolved.error;
|
|
1730
|
+
const provider = getProvider(resolved.data.provider);
|
|
1731
|
+
const transcription = provider.transcription;
|
|
1732
|
+
if (!transcription)
|
|
1733
|
+
throw audioError(HaiAIError.AUDIO_UNSUPPORTED_INPUT, aiM("ai_audioUnsupportedInput", { params: { provider: resolved.data.provider, reason: "transcription" } }));
|
|
1734
|
+
const credErr = ensureAudioCredential(resolved.data);
|
|
1735
|
+
if (credErr)
|
|
1736
|
+
throw credErr;
|
|
1737
|
+
if (request.strictCapabilities) {
|
|
1738
|
+
const capErr = checkTranscribeCapabilities(resolved.data.provider, request, provider.getCapabilities(resolved.data));
|
|
1739
|
+
if (capErr)
|
|
1740
|
+
throw capErr;
|
|
1741
|
+
}
|
|
1486
1742
|
const startedAt = Date.now();
|
|
1487
1743
|
const context = { provider: resolved.data.provider, model: resolved.data.model };
|
|
1488
|
-
|
|
1744
|
+
logger9.debug("Audio transcription stream started", context);
|
|
1489
1745
|
const signal = withStreamTimeout(request.signal);
|
|
1490
1746
|
try {
|
|
1491
|
-
yield*
|
|
1492
|
-
|
|
1747
|
+
yield* transcribeStreamOrFallback(transcription, { model: resolved.data, audio: request.audio, language: request.language, contextHints: request.contextHints, timestampGranularities: request.timestampGranularities, vad: request.vad, signal });
|
|
1748
|
+
logger9.info("Audio transcription stream completed", { ...context, durationMs: Date.now() - startedAt });
|
|
1493
1749
|
} catch (error) {
|
|
1494
|
-
|
|
1750
|
+
logger9.warn("Audio transcription stream failed", { ...context, durationMs: Date.now() - startedAt, error });
|
|
1495
1751
|
throw mapStreamError(error, signal);
|
|
1496
1752
|
}
|
|
1497
1753
|
}
|
|
1498
1754
|
async function synthesize(request) {
|
|
1499
1755
|
if (!request.text)
|
|
1500
1756
|
return err(HaiAIError.AUDIO_INVALID_REQUEST, aiM("ai_audioInvalidRequest", { params: { reason: "empty text" } }));
|
|
1757
|
+
const validErr = validateSynthesisRequest(request);
|
|
1758
|
+
if (validErr)
|
|
1759
|
+
return { success: false, error: validErr };
|
|
1760
|
+
const refErr = guardSynthesisReferences(request);
|
|
1761
|
+
if (refErr)
|
|
1762
|
+
return { success: false, error: refErr };
|
|
1501
1763
|
const resolved = resolveAudioModel(audioConfig, "synthesize", request.model, config.llm.apiKey);
|
|
1502
1764
|
if (!resolved.success)
|
|
1503
1765
|
return resolved;
|
|
1766
|
+
const provider = getProvider(resolved.data.provider);
|
|
1767
|
+
const synthesis = provider.synthesis;
|
|
1768
|
+
if (!synthesis)
|
|
1769
|
+
return err(HaiAIError.AUDIO_UNSUPPORTED_INPUT, aiM("ai_audioUnsupportedInput", { params: { provider: resolved.data.provider, reason: "synthesis" } }));
|
|
1770
|
+
const credErr = ensureAudioCredential(resolved.data);
|
|
1771
|
+
if (credErr)
|
|
1772
|
+
return { success: false, error: credErr };
|
|
1773
|
+
if (request.strictCapabilities) {
|
|
1774
|
+
const capErr = checkSynthesizeCapabilities(resolved.data.provider, request, provider.getCapabilities(resolved.data));
|
|
1775
|
+
if (capErr)
|
|
1776
|
+
return { success: false, error: capErr };
|
|
1777
|
+
}
|
|
1504
1778
|
const startedAt = Date.now();
|
|
1505
1779
|
const context = { provider: resolved.data.provider, model: resolved.data.model, textLength: request.text.length, voice: request.voice };
|
|
1506
|
-
|
|
1507
|
-
const result = await
|
|
1780
|
+
logger9.debug("Audio synthesis started", context);
|
|
1781
|
+
const result = await synthesis.synthesize({ model: resolved.data, text: request.text, language: request.language, voice: request.voice, speakerReference: request.speakerReference, styleReference: request.styleReference, styleStrength: request.styleStrength, instruction: request.instruction, speed: request.speed, targetDurationMs: request.targetDurationMs, durationToleranceMs: request.durationToleranceMs, format: request.format, sampleRate: request.sampleRate, signal: request.signal });
|
|
1508
1782
|
if (result.success)
|
|
1509
|
-
|
|
1783
|
+
logger9.info("Audio synthesis completed", { ...context, durationMs: Date.now() - startedAt, audioBytes: result.data.data.length, format: result.data.format });
|
|
1510
1784
|
else
|
|
1511
|
-
|
|
1785
|
+
logger9.warn("Audio synthesis failed", { ...context, durationMs: Date.now() - startedAt, code: result.error.code, error: result.error.message });
|
|
1512
1786
|
return result;
|
|
1513
1787
|
}
|
|
1514
1788
|
async function* synthesizeStream(request) {
|
|
1789
|
+
const validErr = validateSynthesisRequest(request);
|
|
1790
|
+
if (validErr)
|
|
1791
|
+
throw validErr;
|
|
1792
|
+
const refErr = guardSynthesisReferences(request);
|
|
1793
|
+
if (refErr)
|
|
1794
|
+
throw refErr;
|
|
1515
1795
|
const resolved = resolveAudioModel(audioConfig, "synthesize", request.model, config.llm.apiKey);
|
|
1516
1796
|
if (!resolved.success)
|
|
1517
1797
|
throw resolved.error;
|
|
1798
|
+
const provider = getProvider(resolved.data.provider);
|
|
1799
|
+
const synthesis = provider.synthesis;
|
|
1800
|
+
if (!synthesis)
|
|
1801
|
+
throw audioError(HaiAIError.AUDIO_UNSUPPORTED_INPUT, aiM("ai_audioUnsupportedInput", { params: { provider: resolved.data.provider, reason: "synthesis" } }));
|
|
1802
|
+
const credErr = ensureAudioCredential(resolved.data);
|
|
1803
|
+
if (credErr)
|
|
1804
|
+
throw credErr;
|
|
1805
|
+
if (request.strictCapabilities) {
|
|
1806
|
+
const capErr = checkSynthesizeCapabilities(resolved.data.provider, request, provider.getCapabilities(resolved.data));
|
|
1807
|
+
if (capErr)
|
|
1808
|
+
throw capErr;
|
|
1809
|
+
}
|
|
1518
1810
|
const startedAt = Date.now();
|
|
1519
1811
|
const context = { provider: resolved.data.provider, model: resolved.data.model, voice: request.voice };
|
|
1520
|
-
|
|
1812
|
+
logger9.debug("Audio synthesis stream started", context);
|
|
1521
1813
|
const signal = withStreamTimeout(request.signal);
|
|
1522
|
-
const
|
|
1523
|
-
const output = provider.resolveSynthesisOutput({ format: request.format, sampleRate: request.sampleRate });
|
|
1814
|
+
const output = synthesis.resolveSynthesisOutput({ format: request.format, sampleRate: request.sampleRate });
|
|
1524
1815
|
try {
|
|
1525
1816
|
const segments = isSynthesisTextSegment(request.text) ? singleSegment(request.text) : request.text;
|
|
1526
1817
|
for await (const segment of segments) {
|
|
1527
1818
|
if (!segment.id || !segment.text)
|
|
1528
1819
|
throw audioError(HaiAIError.AUDIO_INVALID_REQUEST, aiM("ai_audioInvalidRequest", { params: { reason: "empty segment id or text" } }));
|
|
1529
1820
|
yield { type: "segment_started", segmentId: segment.id, text: segment.text, format: output.format, sampleRate: output.sampleRate, channels: output.channels };
|
|
1530
|
-
for await (const data of
|
|
1821
|
+
for await (const data of synthesizeSegmentAudio(synthesis, { model: resolved.data, text: segment.text, language: request.language, voice: request.voice, speakerReference: request.speakerReference, styleReference: request.styleReference, styleStrength: request.styleStrength, instruction: request.instruction, speed: request.speed, format: request.format, sampleRate: request.sampleRate, signal }))
|
|
1531
1822
|
yield { type: "audio", segmentId: segment.id, data };
|
|
1532
1823
|
yield { type: "segment_done", segmentId: segment.id };
|
|
1533
1824
|
}
|
|
1534
|
-
|
|
1825
|
+
logger9.info("Audio synthesis stream completed", { ...context, durationMs: Date.now() - startedAt });
|
|
1535
1826
|
} catch (error) {
|
|
1536
|
-
|
|
1827
|
+
logger9.warn("Audio synthesis stream failed", { ...context, durationMs: Date.now() - startedAt, error });
|
|
1537
1828
|
throw mapStreamError(error, signal);
|
|
1538
1829
|
}
|
|
1539
1830
|
}
|
|
1540
1831
|
function getCapabilities(request) {
|
|
1541
|
-
const
|
|
1542
|
-
if (!
|
|
1543
|
-
return
|
|
1544
|
-
const
|
|
1545
|
-
if (!entry)
|
|
1546
|
-
return err(HaiAIError.AUDIO_MODEL_NOT_FOUND, aiM("ai_audioModelNotFound", { params: { model: targetId } }));
|
|
1547
|
-
const operations = entry.operations;
|
|
1548
|
-
if (!operations.includes(request.operation)) {
|
|
1549
|
-
return err(
|
|
1550
|
-
HaiAIError.AUDIO_UNSUPPORTED_INPUT,
|
|
1551
|
-
aiM("ai_audioUnsupportedInput", { params: { provider: entry.provider, reason: `model ${entry.id} does not support ${request.operation}` } })
|
|
1552
|
-
);
|
|
1553
|
-
}
|
|
1554
|
-
const capabilities = getProvider(entry.provider).capabilities;
|
|
1832
|
+
const resolved = resolveAudioModel(audioConfig, request.operation, request.model, config.llm.apiKey);
|
|
1833
|
+
if (!resolved.success)
|
|
1834
|
+
return resolved;
|
|
1835
|
+
const capabilities = getProvider(resolved.data.provider).getCapabilities(resolved.data);
|
|
1555
1836
|
return ok(request.operation === "transcribe" ? { transcribe: capabilities.transcribe } : { synthesize: capabilities.synthesize });
|
|
1556
1837
|
}
|
|
1557
1838
|
return { transcribe, transcribeStream, synthesize, synthesizeStream, getCapabilities };
|
|
@@ -1562,6 +1843,96 @@ function isSynthesisTextSegment(value) {
|
|
|
1562
1843
|
async function* singleSegment(segment) {
|
|
1563
1844
|
yield segment;
|
|
1564
1845
|
}
|
|
1846
|
+
function invalidRequest(reason) {
|
|
1847
|
+
return audioError(HaiAIError.AUDIO_INVALID_REQUEST, aiM("ai_audioInvalidRequest", { params: { reason } }));
|
|
1848
|
+
}
|
|
1849
|
+
function unsupportedCapability(provider, feature) {
|
|
1850
|
+
return audioError(HaiAIError.AUDIO_UNSUPPORTED_INPUT, aiM("ai_audioUnsupportedInput", { params: { provider, reason: `${feature} not supported by this model` } }));
|
|
1851
|
+
}
|
|
1852
|
+
function validateSynthesisOptions(options) {
|
|
1853
|
+
if (options.styleStrength !== void 0 && (options.styleStrength < 0 || options.styleStrength > 1))
|
|
1854
|
+
return invalidRequest("styleStrength must be between 0 and 1");
|
|
1855
|
+
if (options.styleStrength !== void 0 && !options.styleReference)
|
|
1856
|
+
return invalidRequest("styleStrength requires styleReference");
|
|
1857
|
+
if (options.speed !== void 0 && (!Number.isFinite(options.speed) || options.speed <= 0))
|
|
1858
|
+
return invalidRequest("speed must be greater than 0");
|
|
1859
|
+
return null;
|
|
1860
|
+
}
|
|
1861
|
+
function validateSynthesisRequest(request) {
|
|
1862
|
+
const base = validateSynthesisOptions(request);
|
|
1863
|
+
if (base)
|
|
1864
|
+
return base;
|
|
1865
|
+
const targetDurationMs = "targetDurationMs" in request ? request.targetDurationMs : void 0;
|
|
1866
|
+
const durationToleranceMs = "durationToleranceMs" in request ? request.durationToleranceMs : void 0;
|
|
1867
|
+
if (targetDurationMs !== void 0 && (!Number.isInteger(targetDurationMs) || targetDurationMs <= 0))
|
|
1868
|
+
return invalidRequest("targetDurationMs must be a positive integer");
|
|
1869
|
+
if (durationToleranceMs !== void 0 && targetDurationMs === void 0)
|
|
1870
|
+
return invalidRequest("durationToleranceMs requires targetDurationMs");
|
|
1871
|
+
if (durationToleranceMs !== void 0 && (!Number.isInteger(durationToleranceMs) || durationToleranceMs < 0))
|
|
1872
|
+
return invalidRequest("durationToleranceMs must be a non-negative integer");
|
|
1873
|
+
if (request.speed !== void 0 && targetDurationMs !== void 0)
|
|
1874
|
+
return invalidRequest("speed and targetDurationMs cannot be used together");
|
|
1875
|
+
return null;
|
|
1876
|
+
}
|
|
1877
|
+
function checkTranscribeCapabilities(provider, options, capabilities) {
|
|
1878
|
+
const caps = capabilities.transcribe;
|
|
1879
|
+
if (!caps?.supported)
|
|
1880
|
+
return unsupportedCapability(provider, "transcription");
|
|
1881
|
+
if (options.timestampGranularities?.includes("word") && !caps.wordTimestamps)
|
|
1882
|
+
return unsupportedCapability(provider, "word timestamps");
|
|
1883
|
+
if (options.timestampGranularities?.includes("segment") && !caps.segmentTimestamps)
|
|
1884
|
+
return unsupportedCapability(provider, "segment timestamps");
|
|
1885
|
+
if (options.vad && !caps.vad)
|
|
1886
|
+
return unsupportedCapability(provider, "vad");
|
|
1887
|
+
if (options.contextHints?.length && !caps.contextHints)
|
|
1888
|
+
return unsupportedCapability(provider, "context hints");
|
|
1889
|
+
return null;
|
|
1890
|
+
}
|
|
1891
|
+
function checkSynthesizeCapabilities(provider, options, capabilities) {
|
|
1892
|
+
const caps = capabilities.synthesize;
|
|
1893
|
+
if (!caps?.supported)
|
|
1894
|
+
return unsupportedCapability(provider, "synthesis");
|
|
1895
|
+
if (options.speakerReference && !caps.speakerReference)
|
|
1896
|
+
return unsupportedCapability(provider, "speaker reference");
|
|
1897
|
+
if (options.styleReference && !caps.styleReference)
|
|
1898
|
+
return unsupportedCapability(provider, "style reference");
|
|
1899
|
+
if (options.instruction && !caps.instruction)
|
|
1900
|
+
return unsupportedCapability(provider, "instruction");
|
|
1901
|
+
if (options.speed !== void 0 && !caps.speedControl)
|
|
1902
|
+
return unsupportedCapability(provider, "speed control");
|
|
1903
|
+
if (options.targetDurationMs !== void 0 && !caps.targetDuration)
|
|
1904
|
+
return unsupportedCapability(provider, "target duration");
|
|
1905
|
+
return null;
|
|
1906
|
+
}
|
|
1907
|
+
async function* transcribeStreamOrFallback(transcription, request) {
|
|
1908
|
+
if (transcription.transcribeStream) {
|
|
1909
|
+
yield* transcription.transcribeStream(request);
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
if ("chunks" in request.audio)
|
|
1913
|
+
throw audioError(HaiAIError.AUDIO_UNSUPPORTED_INPUT, aiM("ai_audioUnsupportedInput", { params: { provider: request.model.provider, reason: "streaming audio input" } }));
|
|
1914
|
+
const result = await transcription.transcribe({ model: request.model, audio: request.audio, language: request.language, contextHints: request.contextHints, timestampGranularities: request.timestampGranularities, vad: request.vad, signal: request.signal });
|
|
1915
|
+
if (!result.success)
|
|
1916
|
+
throw result.error;
|
|
1917
|
+
yield { type: "transcript", text: result.data.text, final: true };
|
|
1918
|
+
}
|
|
1919
|
+
async function* synthesizeSegmentAudio(synthesis, request) {
|
|
1920
|
+
if (synthesis.synthesizeStream) {
|
|
1921
|
+
yield* synthesis.synthesizeStream(request);
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
const text = typeof request.text === "string" ? request.text : await collectText(request.text);
|
|
1925
|
+
const result = await synthesis.synthesize({ model: request.model, text, language: request.language, voice: request.voice, speakerReference: request.speakerReference, styleReference: request.styleReference, styleStrength: request.styleStrength, instruction: request.instruction, speed: request.speed, format: request.format, sampleRate: request.sampleRate, signal: request.signal });
|
|
1926
|
+
if (!result.success)
|
|
1927
|
+
throw result.error;
|
|
1928
|
+
yield result.data.data;
|
|
1929
|
+
}
|
|
1930
|
+
async function collectText(stream) {
|
|
1931
|
+
let text = "";
|
|
1932
|
+
for await (const part of stream)
|
|
1933
|
+
text += part;
|
|
1934
|
+
return text;
|
|
1935
|
+
}
|
|
1565
1936
|
function createProvider(name) {
|
|
1566
1937
|
switch (name) {
|
|
1567
1938
|
case "openai":
|
|
@@ -1572,9 +1943,13 @@ function createProvider(name) {
|
|
|
1572
1943
|
return createQwenAudioProvider();
|
|
1573
1944
|
case "doubao":
|
|
1574
1945
|
return createDoubaoAudioProvider();
|
|
1946
|
+
case "whisper":
|
|
1947
|
+
return createWhisperAudioProvider();
|
|
1948
|
+
case "indextts":
|
|
1949
|
+
return createIndexTtsAudioProvider();
|
|
1575
1950
|
}
|
|
1576
1951
|
}
|
|
1577
|
-
var
|
|
1952
|
+
var logger10 = core.logger.child({ module: "ai", scope: "compress" });
|
|
1578
1953
|
var CONVERSATION_SUMMARY_PREFIX = "[Conversation Summary]";
|
|
1579
1954
|
var CONVERSATION_SUMMARY_NAME = "hai_internal_conversation_summary_v1";
|
|
1580
1955
|
function completeCompression(result, maxTokens) {
|
|
@@ -1712,7 +2087,7 @@ function createCompressOperations(config, token, summary, modelMaxTokens) {
|
|
|
1712
2087
|
const maxTokens = resolveMaxTokens(options?.maxTokens);
|
|
1713
2088
|
const preserveSystem = options?.preserveSystem ?? true;
|
|
1714
2089
|
const preserveLastN = options?.preserveLastN ?? config.preserveLastN;
|
|
1715
|
-
|
|
2090
|
+
logger10.trace("Compressing context", { strategy, maxTokens, messageCount: messages.length });
|
|
1716
2091
|
try {
|
|
1717
2092
|
const originalTokens = token.estimateMessages(messages);
|
|
1718
2093
|
if (originalTokens <= maxTokens) {
|
|
@@ -1731,7 +2106,7 @@ function createCompressOperations(config, token, summary, modelMaxTokens) {
|
|
|
1731
2106
|
preserveLastN
|
|
1732
2107
|
);
|
|
1733
2108
|
const compressedTokens = token.estimateMessages(compressed);
|
|
1734
|
-
|
|
2109
|
+
logger10.trace("Sliding window compression completed", { originalTokens, compressedTokens, removedCount });
|
|
1735
2110
|
return completeCompression({
|
|
1736
2111
|
messages: compressed,
|
|
1737
2112
|
originalTokens,
|
|
@@ -1764,7 +2139,7 @@ ${summaryText}`
|
|
|
1764
2139
|
};
|
|
1765
2140
|
const compressed = [...systemMessages2, summaryMessage, ...preserved];
|
|
1766
2141
|
const compressedTokens = token.estimateMessages(compressed);
|
|
1767
|
-
|
|
2142
|
+
logger10.trace("Summary compression completed", { originalTokens, compressedTokens, removedCount: toSummarize2.length });
|
|
1768
2143
|
return completeCompression({
|
|
1769
2144
|
messages: compressed,
|
|
1770
2145
|
originalTokens,
|
|
@@ -1805,7 +2180,7 @@ ${summaryText}`
|
|
|
1805
2180
|
};
|
|
1806
2181
|
const compressed = [...systemMessages, summaryMessage, ...preservedMessages];
|
|
1807
2182
|
const compressedTokens = token.estimateMessages(compressed);
|
|
1808
|
-
|
|
2183
|
+
logger10.trace("Hybrid compression completed", { originalTokens, compressedTokens, removedCount: toSummarize.length });
|
|
1809
2184
|
return completeCompression({
|
|
1810
2185
|
messages: compressed,
|
|
1811
2186
|
originalTokens,
|
|
@@ -1821,7 +2196,7 @@ ${summaryText}`
|
|
|
1821
2196
|
removedCount: windowRemoved
|
|
1822
2197
|
}, maxTokens);
|
|
1823
2198
|
} catch (error) {
|
|
1824
|
-
|
|
2199
|
+
logger10.error("Context compression failed", { error });
|
|
1825
2200
|
return err(HaiAIError.CONTEXT_COMPRESS_FAILED, aiM("ai_contextCompressFailed", { params: { error: String(error) } }), error);
|
|
1826
2201
|
}
|
|
1827
2202
|
}
|
|
@@ -1836,7 +2211,7 @@ function sessionStoreKey(scope) {
|
|
|
1836
2211
|
}
|
|
1837
2212
|
|
|
1838
2213
|
// src/context/ai-context-functions.ts
|
|
1839
|
-
var
|
|
2214
|
+
var logger11 = core.logger.child({ module: "ai", scope: "context" });
|
|
1840
2215
|
function createContextOperations(compressConfig, tokenOps, compressOps, contextStore, sessionStore, deps) {
|
|
1841
2216
|
function resolveMaxTokens(optionMaxTokens) {
|
|
1842
2217
|
const fromOption = optionMaxTokens ?? compressConfig.defaultMaxTokens;
|
|
@@ -1882,7 +2257,7 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
|
|
|
1882
2257
|
types: options.memory.types,
|
|
1883
2258
|
model: options.memory.extractionModel,
|
|
1884
2259
|
systemPrompt: options.memory.extractionSystemPrompt
|
|
1885
|
-
}).catch((e) =>
|
|
2260
|
+
}).catch((e) => logger11.warn("Memory extract failed", { error: e })).finally(() => {
|
|
1886
2261
|
pendingMemoryTasks.delete(task);
|
|
1887
2262
|
});
|
|
1888
2263
|
pendingMemoryTasks.add(task);
|
|
@@ -1961,7 +2336,7 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
|
|
|
1961
2336
|
const currentTokens = tokenOps.estimateMessages(state.messages);
|
|
1962
2337
|
if (currentTokens <= managerMaxTokens)
|
|
1963
2338
|
return ok(void 0);
|
|
1964
|
-
|
|
2339
|
+
logger11.trace("Auto-compressing context", { currentTokens, budget: managerMaxTokens });
|
|
1965
2340
|
const compressResult = await compressOps.tryCompress(state.messages, {
|
|
1966
2341
|
strategy,
|
|
1967
2342
|
maxTokens: managerMaxTokens,
|
|
@@ -1970,7 +2345,7 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
|
|
|
1970
2345
|
summaryModel
|
|
1971
2346
|
});
|
|
1972
2347
|
if (!compressResult.success) {
|
|
1973
|
-
|
|
2348
|
+
logger11.warn("Auto-compression failed, keeping original messages", { error: compressResult.error });
|
|
1974
2349
|
return compressResult;
|
|
1975
2350
|
}
|
|
1976
2351
|
if (compressResult.data.summary) {
|
|
@@ -2104,7 +2479,7 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
|
|
|
2104
2479
|
return extracted;
|
|
2105
2480
|
return ok({ summary: summaryText, memories: extracted.data });
|
|
2106
2481
|
} catch (error) {
|
|
2107
|
-
|
|
2482
|
+
logger11.error("Context consolidate failed", { error });
|
|
2108
2483
|
return err(HaiAIError.MEMORY_PROMOTE_FAILED, aiM("ai_memoryPromoteFailed", { params: { error: String(error) } }), error);
|
|
2109
2484
|
}
|
|
2110
2485
|
},
|
|
@@ -2239,7 +2614,7 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
|
|
|
2239
2614
|
turnId = await finalizeAssistantReply(message, fallbackReply, epoch);
|
|
2240
2615
|
return ok({ reply: fallbackReply, model: lastModel, turnId, usage: lastUsage });
|
|
2241
2616
|
} catch (error) {
|
|
2242
|
-
|
|
2617
|
+
logger11.error("Context chat failed", { error });
|
|
2243
2618
|
return err(HaiAIError.INTERNAL_ERROR, aiM("ai_internalError", { params: { error: String(error) } }), error);
|
|
2244
2619
|
} finally {
|
|
2245
2620
|
settleGeneration(turnId, release);
|
|
@@ -2504,7 +2879,7 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
|
|
|
2504
2879
|
if (persisted) {
|
|
2505
2880
|
initialMessages = persisted.messages;
|
|
2506
2881
|
initialSummaries = persisted.summaries;
|
|
2507
|
-
|
|
2882
|
+
logger11.trace("Context manager restored from store", { scope, messageCount: initialMessages.length });
|
|
2508
2883
|
}
|
|
2509
2884
|
}
|
|
2510
2885
|
const manager = buildManager(
|
|
@@ -2566,7 +2941,7 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
|
|
|
2566
2941
|
}
|
|
2567
2942
|
};
|
|
2568
2943
|
}
|
|
2569
|
-
var
|
|
2944
|
+
var logger12 = core.logger.child({ module: "ai", scope: "embedding" });
|
|
2570
2945
|
function createEmbeddingOperations(config) {
|
|
2571
2946
|
const embeddingConfig = config.embedding ?? { batchSize: 100 };
|
|
2572
2947
|
let cachedClient = null;
|
|
@@ -2612,7 +2987,7 @@ function createEmbeddingOperations(config) {
|
|
|
2612
2987
|
}
|
|
2613
2988
|
});
|
|
2614
2989
|
} catch (error) {
|
|
2615
|
-
|
|
2990
|
+
logger12.error("Embedding API call failed", { error });
|
|
2616
2991
|
return err(HaiAIError.EMBEDDING_API_ERROR, aiM("ai_internalError", { params: { error: String(error) } }), error);
|
|
2617
2992
|
}
|
|
2618
2993
|
}
|
|
@@ -2670,7 +3045,7 @@ function createEmbeddingOperations(config) {
|
|
|
2670
3045
|
}
|
|
2671
3046
|
};
|
|
2672
3047
|
}
|
|
2673
|
-
var
|
|
3048
|
+
var logger13 = core.logger.child({ module: "ai", scope: "file" });
|
|
2674
3049
|
var HTML_SCRIPT_BLOCK_REGEX = /<script[^>]*>[\s\S]*?<\/script[^>]*>/gi;
|
|
2675
3050
|
var HTML_STYLE_BLOCK_REGEX = /<style[^>]*>[\s\S]*?<\/style[^>]*>/gi;
|
|
2676
3051
|
var HTML_LIST_OPEN_TAG_REGEX = /<[ou]l[^>]*>/gi;
|
|
@@ -2910,7 +3285,7 @@ async function parseImageWithOcr(content, mimeType, llmOps, model, outputFormat
|
|
|
2910
3285
|
const dataUrl = `data:${mimeType};base64,${base64}`;
|
|
2911
3286
|
const defaultPrompt = outputFormat === "markdown" ? "Please extract all text content from this image as Markdown, preserving document structure (headings, bullet lists, bold/italic). Return only the Markdown without additional commentary." : "Please extract all text content from this image accurately. Return only the extracted text without any additional commentary.";
|
|
2912
3287
|
const prompt = customPrompt ?? defaultPrompt;
|
|
2913
|
-
|
|
3288
|
+
logger13.trace("Running OCR via vision LLM", { mimeType, model });
|
|
2914
3289
|
const result = await llmOps.chat({
|
|
2915
3290
|
model,
|
|
2916
3291
|
messages: [
|
|
@@ -2961,7 +3336,7 @@ function createFileOperations(config, llmOps) {
|
|
|
2961
3336
|
const { content, filename, options = {} } = request;
|
|
2962
3337
|
const mimeType = detectMimeType(content, filename, options.mimeType);
|
|
2963
3338
|
const outputFormat = options.outputFormat ?? "text";
|
|
2964
|
-
|
|
3339
|
+
logger13.trace("Parsing file", { filename, mimeType, outputFormat, useOcr: options.useOcr });
|
|
2965
3340
|
if (options.useOcr) {
|
|
2966
3341
|
if (!Buffer.isBuffer(content)) {
|
|
2967
3342
|
return err(HaiAIError.FILE_INVALID_CONTENT, aiM("ai_fileInvalidContent", { params: { reason: "OCR requires Buffer content" } }));
|
|
@@ -2997,7 +3372,7 @@ function createFileOperations(config, llmOps) {
|
|
|
2997
3372
|
metadata: { filename, mimeType, charCount: pdfResult.data.text.length }
|
|
2998
3373
|
});
|
|
2999
3374
|
}
|
|
3000
|
-
|
|
3375
|
+
logger13.warn("PDF native parser failed, falling back to OCR", { filename });
|
|
3001
3376
|
return runOcr(content, mimeType, filename, options.model, outputFormat, options.systemPrompt);
|
|
3002
3377
|
}
|
|
3003
3378
|
if (mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || mimeType === "application/msword" || mimeType === "application/zip") {
|
|
@@ -3012,7 +3387,7 @@ function createFileOperations(config, llmOps) {
|
|
|
3012
3387
|
metadata: { filename, mimeType, charCount: docxResult.data.length }
|
|
3013
3388
|
});
|
|
3014
3389
|
}
|
|
3015
|
-
|
|
3390
|
+
logger13.warn("DOCX native parser failed, falling back to OCR", { filename });
|
|
3016
3391
|
return runOcr(content, mimeType, filename, options.model, outputFormat, options.systemPrompt);
|
|
3017
3392
|
}
|
|
3018
3393
|
if (IMAGE_MIME_TYPES.has(mimeType)) {
|
|
@@ -3028,7 +3403,7 @@ function createFileOperations(config, llmOps) {
|
|
|
3028
3403
|
try {
|
|
3029
3404
|
return await doParse(request);
|
|
3030
3405
|
} catch (error) {
|
|
3031
|
-
|
|
3406
|
+
logger13.error("File parse failed unexpectedly", { filename: request.filename, error });
|
|
3032
3407
|
return err(HaiAIError.FILE_PARSE_FAILED, aiM("ai_fileParseFailed", { params: { error: String(error) } }), error);
|
|
3033
3408
|
}
|
|
3034
3409
|
},
|
|
@@ -3376,7 +3751,7 @@ function createSeedreamImageProvider() {
|
|
|
3376
3751
|
}
|
|
3377
3752
|
|
|
3378
3753
|
// src/image/ai-image-functions.ts
|
|
3379
|
-
var
|
|
3754
|
+
var logger14 = core.logger.child({ module: "ai", scope: "image" });
|
|
3380
3755
|
function createImageOperations(config) {
|
|
3381
3756
|
const imageConfig = ImageConfigSchema.parse(config.image ?? {});
|
|
3382
3757
|
const providers = /* @__PURE__ */ new Map();
|
|
@@ -3400,7 +3775,7 @@ function createImageOperations(config) {
|
|
|
3400
3775
|
return err(HaiAIError.IMAGE_CANCELLED, aiM("ai_imageCancelled"));
|
|
3401
3776
|
const resolved = resolveImageModel(imageConfig, request.model);
|
|
3402
3777
|
if (!resolved.success) {
|
|
3403
|
-
|
|
3778
|
+
logger14.warn("Image model resolution failed", {
|
|
3404
3779
|
code: resolved.error.code,
|
|
3405
3780
|
error: resolved.error.message,
|
|
3406
3781
|
requestedModel: request.model
|
|
@@ -3415,7 +3790,7 @@ function createImageOperations(config) {
|
|
|
3415
3790
|
width: request.size?.width,
|
|
3416
3791
|
height: request.size?.height
|
|
3417
3792
|
};
|
|
3418
|
-
|
|
3793
|
+
logger14.debug("Image generation started", logContext);
|
|
3419
3794
|
const generated = await provider(resolved.data.provider).generate({
|
|
3420
3795
|
model: resolved.data,
|
|
3421
3796
|
prompt: request.prompt.trim(),
|
|
@@ -3424,7 +3799,7 @@ function createImageOperations(config) {
|
|
|
3424
3799
|
signal: request.signal
|
|
3425
3800
|
});
|
|
3426
3801
|
if (!generated.success) {
|
|
3427
|
-
|
|
3802
|
+
logger14.warn("Image generation failed", {
|
|
3428
3803
|
...logContext,
|
|
3429
3804
|
durationMs: Date.now() - startedAt,
|
|
3430
3805
|
code: generated.error.code,
|
|
@@ -3433,7 +3808,7 @@ function createImageOperations(config) {
|
|
|
3433
3808
|
});
|
|
3434
3809
|
return generated;
|
|
3435
3810
|
}
|
|
3436
|
-
|
|
3811
|
+
logger14.info("Image generation completed", {
|
|
3437
3812
|
...logContext,
|
|
3438
3813
|
durationMs: Date.now() - startedAt,
|
|
3439
3814
|
imageCount: generated.data.length,
|
|
@@ -3457,7 +3832,7 @@ function createProvider2(name) {
|
|
|
3457
3832
|
return createPollinationsImageProvider();
|
|
3458
3833
|
}
|
|
3459
3834
|
}
|
|
3460
|
-
var
|
|
3835
|
+
var logger15 = core.logger.child({ module: "ai", scope: "knowledge-entity" });
|
|
3461
3836
|
var LEADING_MARKDOWN_FENCE_REGEX = /^```(?:json)?\n?/;
|
|
3462
3837
|
var TRAILING_MARKDOWN_FENCE_REGEX = /\n?```$/;
|
|
3463
3838
|
var DEFAULT_ENTITY_TYPES = ["person", "project", "concept", "organization", "location", "event", "other"];
|
|
@@ -3495,16 +3870,16 @@ async function extractEntities(llm, text, model, entityTypes, systemPrompt) {
|
|
|
3495
3870
|
enablePersist: false
|
|
3496
3871
|
});
|
|
3497
3872
|
if (!chatResult.success) {
|
|
3498
|
-
|
|
3873
|
+
logger15.warn("Entity extraction LLM call failed", { error: chatResult.error });
|
|
3499
3874
|
return err(HaiAIError.KNOWLEDGE_ENTITY_EXTRACT_FAILED, aiM("ai_knowledgeEntityExtractFailed", { params: { error: String(chatResult.error.message) } }), chatResult.error);
|
|
3500
3875
|
}
|
|
3501
3876
|
const content = chatResult.data.choices[0]?.message?.content ?? "";
|
|
3502
3877
|
const validTypes = new Set(resolvedTypes);
|
|
3503
3878
|
const entities = parseEntityResponse(content, validTypes);
|
|
3504
|
-
|
|
3879
|
+
logger15.trace("Entity extraction completed", { textLength: text.length, entityCount: entities.length });
|
|
3505
3880
|
return ok(entities);
|
|
3506
3881
|
} catch (error) {
|
|
3507
|
-
|
|
3882
|
+
logger15.error("Entity extraction failed", { error });
|
|
3508
3883
|
return err(HaiAIError.KNOWLEDGE_ENTITY_EXTRACT_FAILED, aiM("ai_knowledgeEntityExtractFailed", { params: { error: String(error) } }), error);
|
|
3509
3884
|
}
|
|
3510
3885
|
}
|
|
@@ -3524,10 +3899,10 @@ function parseEntityResponse(content, validTypes) {
|
|
|
3524
3899
|
return entities.filter(isValidEntity).map((e) => normalizeEntity(e, validTypes));
|
|
3525
3900
|
}
|
|
3526
3901
|
}
|
|
3527
|
-
|
|
3902
|
+
logger15.warn("Entity extraction returned unexpected format", { content: content.slice(0, 200) });
|
|
3528
3903
|
return [];
|
|
3529
3904
|
} catch {
|
|
3530
|
-
|
|
3905
|
+
logger15.warn("Entity extraction returned invalid JSON", { content: content.slice(0, 200) });
|
|
3531
3906
|
return [];
|
|
3532
3907
|
}
|
|
3533
3908
|
}
|
|
@@ -3556,7 +3931,7 @@ async function extractEntitiesBatch(llm, chunks, model, entityTypes, systemPromp
|
|
|
3556
3931
|
if (result.success) {
|
|
3557
3932
|
return result.data.map((e) => ({ entity: e, chunkId: chunk.chunkId }));
|
|
3558
3933
|
}
|
|
3559
|
-
|
|
3934
|
+
logger15.warn("Entity extraction failed for chunk", { chunkId: chunk.chunkId });
|
|
3560
3935
|
return [];
|
|
3561
3936
|
})
|
|
3562
3937
|
);
|
|
@@ -3590,7 +3965,7 @@ async function extractEntitiesBatch(llm, chunks, model, entityTypes, systemPromp
|
|
|
3590
3965
|
}
|
|
3591
3966
|
|
|
3592
3967
|
// src/knowledge/ai-knowledge-functions.ts
|
|
3593
|
-
var
|
|
3968
|
+
var logger16 = core.logger.child({ module: "ai", scope: "knowledge" });
|
|
3594
3969
|
var DEFAULT_KNOWLEDGE_ASK_SYSTEM_PROMPT = `You are an expert assistant. Answer the user's question based ONLY on the provided context.
|
|
3595
3970
|
|
|
3596
3971
|
Rules:
|
|
@@ -3622,14 +3997,14 @@ function createKnowledgeOperations(config, llm, embedding, datapipe2, store) {
|
|
|
3622
3997
|
}
|
|
3623
3998
|
const collection = options?.collection ?? config.collection;
|
|
3624
3999
|
const dimension = options?.dimension ?? config.dimension;
|
|
3625
|
-
|
|
4000
|
+
logger16.debug("Setting up knowledge base", { collection, dimension });
|
|
3626
4001
|
try {
|
|
3627
4002
|
await store.initialize(collection, dimension);
|
|
3628
4003
|
await store.registerCollection(collection, dimension);
|
|
3629
|
-
|
|
4004
|
+
logger16.debug("Knowledge base setup completed", { collection });
|
|
3630
4005
|
return ok(void 0);
|
|
3631
4006
|
} catch (error) {
|
|
3632
|
-
|
|
4007
|
+
logger16.error("Knowledge base setup failed", { error });
|
|
3633
4008
|
return err(HaiAIError.KNOWLEDGE_SETUP_FAILED, aiM("ai_knowledgeSetupFailed", { params: { error: String(error) } }), error);
|
|
3634
4009
|
}
|
|
3635
4010
|
},
|
|
@@ -3667,7 +4042,7 @@ function createKnowledgeOperations(config, llm, embedding, datapipe2, store) {
|
|
|
3667
4042
|
...config.chunkOptions,
|
|
3668
4043
|
...input.chunkOptions
|
|
3669
4044
|
};
|
|
3670
|
-
|
|
4045
|
+
logger16.trace("Ingesting document", { documentId: input.documentId, collection, contentLength: input.content.length });
|
|
3671
4046
|
try {
|
|
3672
4047
|
const cleanResult = datapipe2.clean(input.content, cleanOptions);
|
|
3673
4048
|
const cleanedText = cleanResult.success ? cleanResult.data : input.content;
|
|
@@ -3742,11 +4117,11 @@ function createKnowledgeOperations(config, llm, embedding, datapipe2, store) {
|
|
|
3742
4117
|
}
|
|
3743
4118
|
}
|
|
3744
4119
|
} else {
|
|
3745
|
-
|
|
4120
|
+
logger16.warn("Entity extraction failed, skipping", { error: entityResult.error });
|
|
3746
4121
|
}
|
|
3747
4122
|
}
|
|
3748
4123
|
const duration = Date.now() - startTime;
|
|
3749
|
-
|
|
4124
|
+
logger16.debug("Document ingested", {
|
|
3750
4125
|
documentId: input.documentId,
|
|
3751
4126
|
chunkCount: chunks.length,
|
|
3752
4127
|
entityCount: extractedEntities.length,
|
|
@@ -3759,7 +4134,7 @@ function createKnowledgeOperations(config, llm, embedding, datapipe2, store) {
|
|
|
3759
4134
|
duration
|
|
3760
4135
|
});
|
|
3761
4136
|
} catch (error) {
|
|
3762
|
-
|
|
4137
|
+
logger16.error("Document ingestion failed", { documentId: input.documentId, error });
|
|
3763
4138
|
return err(HaiAIError.KNOWLEDGE_INGEST_FAILED, aiM("ai_knowledgeIngestFailed", { params: { error: String(error) } }), error);
|
|
3764
4139
|
}
|
|
3765
4140
|
},
|
|
@@ -3782,7 +4157,7 @@ function createKnowledgeOperations(config, llm, embedding, datapipe2, store) {
|
|
|
3782
4157
|
const startTime = Date.now();
|
|
3783
4158
|
const topK = options?.topK ?? 10;
|
|
3784
4159
|
const enableEntityBoost = options?.enableEntityBoost ?? true;
|
|
3785
|
-
|
|
4160
|
+
logger16.trace("Knowledge retrieval", { query: query.slice(0, 100), collection, topK });
|
|
3786
4161
|
try {
|
|
3787
4162
|
const embedResult = await embedding.embedText(query);
|
|
3788
4163
|
if (!embedResult.success) {
|
|
@@ -3860,7 +4235,7 @@ function createKnowledgeOperations(config, llm, embedding, datapipe2, store) {
|
|
|
3860
4235
|
}
|
|
3861
4236
|
}
|
|
3862
4237
|
const duration = Date.now() - startTime;
|
|
3863
|
-
|
|
4238
|
+
logger16.trace("Knowledge retrieval completed", { resultCount: items.length, duration });
|
|
3864
4239
|
return ok({
|
|
3865
4240
|
items,
|
|
3866
4241
|
citations: Array.from(citationMap.values()),
|
|
@@ -3868,7 +4243,7 @@ function createKnowledgeOperations(config, llm, embedding, datapipe2, store) {
|
|
|
3868
4243
|
duration
|
|
3869
4244
|
});
|
|
3870
4245
|
} catch (error) {
|
|
3871
|
-
|
|
4246
|
+
logger16.error("Knowledge retrieval failed", { error });
|
|
3872
4247
|
return err(HaiAIError.KNOWLEDGE_RETRIEVE_FAILED, aiM("ai_knowledgeRetrieveFailed", { params: { error: String(error) } }), error);
|
|
3873
4248
|
}
|
|
3874
4249
|
},
|
|
@@ -3916,7 +4291,7 @@ ${contextText}
|
|
|
3916
4291
|
}
|
|
3917
4292
|
const choice = chatResult.data.choices[0];
|
|
3918
4293
|
const answer = choice?.message?.content ?? "";
|
|
3919
|
-
|
|
4294
|
+
logger16.trace("Knowledge ask completed", {
|
|
3920
4295
|
contextCount: items.length,
|
|
3921
4296
|
model: chatResult.data.model
|
|
3922
4297
|
});
|
|
@@ -4032,13 +4407,13 @@ ${contextText}
|
|
|
4032
4407
|
try {
|
|
4033
4408
|
await store.removeVectors(collection, vectorIds);
|
|
4034
4409
|
} catch (vecError) {
|
|
4035
|
-
|
|
4410
|
+
logger16.warn("Failed to delete some vectors", { documentId, error: vecError });
|
|
4036
4411
|
}
|
|
4037
4412
|
}
|
|
4038
4413
|
await store.deleteDocumentEntities(documentId, collection);
|
|
4039
4414
|
await store.removeDocumentEntityRelations(documentId, collection);
|
|
4040
4415
|
await store.removeDocument(documentId, collection);
|
|
4041
|
-
|
|
4416
|
+
logger16.debug("Document removed", { documentId, collection });
|
|
4042
4417
|
return ok(void 0);
|
|
4043
4418
|
} catch (error) {
|
|
4044
4419
|
return err(HaiAIError.KNOWLEDGE_INGEST_FAILED, aiM("ai_knowledgeIngestFailed", { params: { error: String(error) } }), error);
|
|
@@ -4099,13 +4474,13 @@ ${contextText}
|
|
|
4099
4474
|
return err(HaiAIError.KNOWLEDGE_SETUP_FAILED, aiM("ai_knowledgeSetupFailed", { params: { error: "KnowledgeStore not available. Provider may not support knowledge operations." } }));
|
|
4100
4475
|
}
|
|
4101
4476
|
const col = collection ?? config.collection;
|
|
4102
|
-
|
|
4477
|
+
logger16.debug("Deleting knowledge collection", { collection: col });
|
|
4103
4478
|
try {
|
|
4104
4479
|
await store.deleteCollection(col);
|
|
4105
|
-
|
|
4480
|
+
logger16.debug("Knowledge collection deleted", { collection: col });
|
|
4106
4481
|
return ok(void 0);
|
|
4107
4482
|
} catch (error) {
|
|
4108
|
-
|
|
4483
|
+
logger16.error("Knowledge collection deletion failed", { collection: col, error });
|
|
4109
4484
|
return err(HaiAIError.KNOWLEDGE_SETUP_FAILED, aiM("ai_knowledgeSetupFailed", { params: { error: String(error) } }), error);
|
|
4110
4485
|
}
|
|
4111
4486
|
}
|
|
@@ -4938,7 +5313,7 @@ function createLLMProvider(deps) {
|
|
|
4938
5313
|
}
|
|
4939
5314
|
|
|
4940
5315
|
// src/llm/ai-llm-functions.ts
|
|
4941
|
-
var
|
|
5316
|
+
var logger17 = core.logger.child({ module: "ai", scope: "llm" });
|
|
4942
5317
|
function createAILLMFunctions(config, deps) {
|
|
4943
5318
|
const provider = createLLMProvider({ config });
|
|
4944
5319
|
const recordStore = deps?.recordStore;
|
|
@@ -4956,7 +5331,7 @@ function createAILLMFunctions(config, deps) {
|
|
|
4956
5331
|
}
|
|
4957
5332
|
async function saveRecordAndSession(record, messages) {
|
|
4958
5333
|
await recordStore.save(record.id, record, { objectId: record.objectId, sessionId: record.sessionId });
|
|
4959
|
-
|
|
5334
|
+
logger17.trace("Chat record saved", { id: record.id, objectId: record.objectId, sessionId: record.sessionId });
|
|
4960
5335
|
if (sessionStore) {
|
|
4961
5336
|
const sessionKey = sessionStoreKey({ objectId: record.objectId, sessionId: record.sessionId });
|
|
4962
5337
|
const existing = await sessionStore.get(sessionKey);
|
|
@@ -4977,10 +5352,10 @@ function createAILLMFunctions(config, deps) {
|
|
|
4977
5352
|
async function chatWithRecord(request) {
|
|
4978
5353
|
const start = Date.now();
|
|
4979
5354
|
const context = { model: request.model ?? request.tempModel?.model, messageCount: request.messages.length };
|
|
4980
|
-
|
|
5355
|
+
logger17.debug("LLM chat started", context);
|
|
4981
5356
|
const result = await provider.chat(request);
|
|
4982
5357
|
if (result.success) {
|
|
4983
|
-
|
|
5358
|
+
logger17.info("LLM chat completed", {
|
|
4984
5359
|
...context,
|
|
4985
5360
|
model: result.data.model,
|
|
4986
5361
|
durationMs: Date.now() - start,
|
|
@@ -4988,7 +5363,7 @@ function createAILLMFunctions(config, deps) {
|
|
|
4988
5363
|
totalTokens: result.data.usage?.total_tokens
|
|
4989
5364
|
});
|
|
4990
5365
|
} else {
|
|
4991
|
-
|
|
5366
|
+
logger17.warn("LLM chat failed", {
|
|
4992
5367
|
...context,
|
|
4993
5368
|
durationMs: Date.now() - start,
|
|
4994
5369
|
code: result.error.code,
|
|
@@ -5018,7 +5393,7 @@ function createAILLMFunctions(config, deps) {
|
|
|
5018
5393
|
};
|
|
5019
5394
|
await saveRecordAndSession(record, request.messages);
|
|
5020
5395
|
} catch (error) {
|
|
5021
|
-
|
|
5396
|
+
logger17.debug("Failed to save chat record", { error });
|
|
5022
5397
|
}
|
|
5023
5398
|
}
|
|
5024
5399
|
return result;
|
|
@@ -5026,7 +5401,7 @@ function createAILLMFunctions(config, deps) {
|
|
|
5026
5401
|
async function* chatStreamWithRecord(request) {
|
|
5027
5402
|
const start = Date.now();
|
|
5028
5403
|
const logContext = { model: request.model ?? request.tempModel?.model, messageCount: request.messages.length };
|
|
5029
|
-
|
|
5404
|
+
logger17.debug("LLM chat stream started", logContext);
|
|
5030
5405
|
const shouldRecord = !!(request.objectId && recordStore && request.enablePersist !== false);
|
|
5031
5406
|
let content = "";
|
|
5032
5407
|
let finishReason = null;
|
|
@@ -5062,7 +5437,7 @@ function createAILLMFunctions(config, deps) {
|
|
|
5062
5437
|
}
|
|
5063
5438
|
}
|
|
5064
5439
|
}
|
|
5065
|
-
|
|
5440
|
+
logger17.info("LLM chat stream completed", {
|
|
5066
5441
|
...logContext,
|
|
5067
5442
|
model: resolvedModel || logContext.model,
|
|
5068
5443
|
durationMs: Date.now() - start,
|
|
@@ -5070,7 +5445,7 @@ function createAILLMFunctions(config, deps) {
|
|
|
5070
5445
|
finishReason
|
|
5071
5446
|
});
|
|
5072
5447
|
} catch (error) {
|
|
5073
|
-
|
|
5448
|
+
logger17.warn("LLM chat stream failed", { ...logContext, durationMs: Date.now() - start, error });
|
|
5074
5449
|
throw error;
|
|
5075
5450
|
}
|
|
5076
5451
|
if (shouldRecord) {
|
|
@@ -5095,7 +5470,7 @@ function createAILLMFunctions(config, deps) {
|
|
|
5095
5470
|
};
|
|
5096
5471
|
await saveRecordAndSession(record, request.messages);
|
|
5097
5472
|
} catch (error) {
|
|
5098
|
-
|
|
5473
|
+
logger17.debug("Failed to save stream chat record", { error });
|
|
5099
5474
|
}
|
|
5100
5475
|
}
|
|
5101
5476
|
}
|
|
@@ -5335,25 +5710,25 @@ function createAIMCPFunctions(_deps) {
|
|
|
5335
5710
|
}
|
|
5336
5711
|
};
|
|
5337
5712
|
}
|
|
5338
|
-
function createScopedMemory(
|
|
5713
|
+
function createScopedMemory(core32, binding) {
|
|
5339
5714
|
const { objectId, scope } = binding;
|
|
5340
5715
|
const accessScope = { objectId, scope };
|
|
5341
5716
|
return {
|
|
5342
|
-
extract: (messages, options) =>
|
|
5343
|
-
recall: (query, options) =>
|
|
5344
|
-
injectMemories: (messages, options) =>
|
|
5345
|
-
add: (entry) =>
|
|
5346
|
-
update: (memoryId, updates) =>
|
|
5347
|
-
get: (memoryId) =>
|
|
5348
|
-
remove: (memoryId) =>
|
|
5349
|
-
list: (options) =>
|
|
5350
|
-
listPage: (options) =>
|
|
5351
|
-
clear: (options) =>
|
|
5717
|
+
extract: (messages, options) => core32.extract(messages, { ...options, objectId, scope }),
|
|
5718
|
+
recall: (query, options) => core32.recall(query, { ...options, objectId, scope }),
|
|
5719
|
+
injectMemories: (messages, options) => core32.injectMemories(messages, { ...options, objectId, scope }),
|
|
5720
|
+
add: (entry) => core32.add({ ...entry, objectId, scope }),
|
|
5721
|
+
update: (memoryId, updates) => core32.update(memoryId, updates, accessScope),
|
|
5722
|
+
get: (memoryId) => core32.get(memoryId, accessScope),
|
|
5723
|
+
remove: (memoryId) => core32.remove(memoryId, accessScope),
|
|
5724
|
+
list: (options) => core32.list({ ...options, objectId, scope }),
|
|
5725
|
+
listPage: (options) => core32.listPage({ ...options, objectId, scope }),
|
|
5726
|
+
clear: (options) => core32.clear({ ...options, objectId, scope })
|
|
5352
5727
|
};
|
|
5353
5728
|
}
|
|
5354
|
-
function createMemoryOperations(
|
|
5729
|
+
function createMemoryOperations(core32) {
|
|
5355
5730
|
return {
|
|
5356
|
-
...
|
|
5731
|
+
...core32,
|
|
5357
5732
|
/**
|
|
5358
5733
|
* 清空记忆(空过滤保护)。
|
|
5359
5734
|
*
|
|
@@ -5365,20 +5740,20 @@ function createMemoryOperations(core30) {
|
|
|
5365
5740
|
if (!hasFilter) {
|
|
5366
5741
|
return err(HaiAIError.MEMORY_STORE_FAILED, aiM("ai_memoryClearRequiresFilter"));
|
|
5367
5742
|
}
|
|
5368
|
-
return
|
|
5743
|
+
return core32.clear(options);
|
|
5369
5744
|
},
|
|
5370
|
-
scoped: (binding) => createScopedMemory(
|
|
5745
|
+
scoped: (binding) => createScopedMemory(core32, binding),
|
|
5371
5746
|
admin: {
|
|
5372
5747
|
async clearAll(options) {
|
|
5373
5748
|
if (options?.confirm !== true) {
|
|
5374
5749
|
return err(HaiAIError.MEMORY_STORE_FAILED, aiM("ai_memoryClearAllUnconfirmed"));
|
|
5375
5750
|
}
|
|
5376
|
-
return
|
|
5751
|
+
return core32.clear();
|
|
5377
5752
|
}
|
|
5378
5753
|
}
|
|
5379
5754
|
};
|
|
5380
5755
|
}
|
|
5381
|
-
var
|
|
5756
|
+
var logger18 = core.logger.child({ module: "ai", scope: "memory-extractor" });
|
|
5382
5757
|
var LEADING_MARKDOWN_FENCE_REGEX2 = /^```(?:json)?\n?/;
|
|
5383
5758
|
var TRAILING_MARKDOWN_FENCE_REGEX2 = /\n?```$/;
|
|
5384
5759
|
var MEMORY_EXTRACTION_SYSTEM_PROMPT = `You are a memory extraction assistant. Analyze the conversation and extract important information worth remembering for future interactions.
|
|
@@ -5431,7 +5806,7 @@ Only extract memories of these types: ${options.types.join(", ")}`;
|
|
|
5431
5806
|
signal: options?.signal
|
|
5432
5807
|
});
|
|
5433
5808
|
if (!chatResult.success) {
|
|
5434
|
-
|
|
5809
|
+
logger18.warn("Memory extraction LLM call failed", { error: chatResult.error });
|
|
5435
5810
|
return err(HaiAIError.MEMORY_EXTRACT_FAILED, aiM("ai_memoryExtractFailed", { params: { error: String(chatResult.error.message) } }), chatResult.error);
|
|
5436
5811
|
}
|
|
5437
5812
|
const content = chatResult.data.choices[0]?.message?.content ?? "";
|
|
@@ -5446,10 +5821,10 @@ Only extract memories of these types: ${options.types.join(", ")}`;
|
|
|
5446
5821
|
if (options?.objectId) {
|
|
5447
5822
|
entries = entries.map((e) => ({ ...e, objectId: options.objectId }));
|
|
5448
5823
|
}
|
|
5449
|
-
|
|
5824
|
+
logger18.trace("Memory extraction completed", { messageCount: messages.length, extractedCount: entries.length });
|
|
5450
5825
|
return ok(entries);
|
|
5451
5826
|
} catch (error) {
|
|
5452
|
-
|
|
5827
|
+
logger18.error("Memory extraction failed", { error });
|
|
5453
5828
|
return err(HaiAIError.MEMORY_EXTRACT_FAILED, aiM("ai_memoryExtractFailed", { params: { error: String(error) } }), error);
|
|
5454
5829
|
}
|
|
5455
5830
|
}
|
|
@@ -5470,10 +5845,10 @@ function parseMemoryResponse(content) {
|
|
|
5470
5845
|
return memories.filter(isValidMemoryItem).map(normalizeMemoryItem);
|
|
5471
5846
|
}
|
|
5472
5847
|
}
|
|
5473
|
-
|
|
5848
|
+
logger18.warn("Memory extraction returned unexpected format", { content: content.slice(0, 200) });
|
|
5474
5849
|
return [];
|
|
5475
5850
|
} catch {
|
|
5476
|
-
|
|
5851
|
+
logger18.warn("Memory extraction returned invalid JSON", { content: content.slice(0, 200) });
|
|
5477
5852
|
return [];
|
|
5478
5853
|
}
|
|
5479
5854
|
}
|
|
@@ -5492,7 +5867,7 @@ function normalizeMemoryItem(item) {
|
|
|
5492
5867
|
importance
|
|
5493
5868
|
};
|
|
5494
5869
|
}
|
|
5495
|
-
var
|
|
5870
|
+
var logger19 = core.logger.child({ module: "ai", scope: "memory" });
|
|
5496
5871
|
function isMemoryAccessDenied(entry, accessScope) {
|
|
5497
5872
|
if (!accessScope)
|
|
5498
5873
|
return false;
|
|
@@ -5577,16 +5952,16 @@ ${memoryText}
|
|
|
5577
5952
|
});
|
|
5578
5953
|
}
|
|
5579
5954
|
}
|
|
5580
|
-
|
|
5955
|
+
logger19.trace("Memories enriched", { count: recallResult.data.length, position });
|
|
5581
5956
|
return ok(result);
|
|
5582
5957
|
} catch (error) {
|
|
5583
|
-
|
|
5958
|
+
logger19.error("Memory enrichment failed", { error });
|
|
5584
5959
|
return err(HaiAIError.MEMORY_ENRICH_FAILED, aiM("ai_memoryEnrichFailed", { params: { error: String(error) } }), error);
|
|
5585
5960
|
}
|
|
5586
5961
|
}
|
|
5587
5962
|
|
|
5588
5963
|
// src/memory/providers/ai-memory-provider-mem0-oss.ts
|
|
5589
|
-
var
|
|
5964
|
+
var logger20 = core.logger.child({ module: "ai", scope: "memory-mem0-oss" });
|
|
5590
5965
|
var DEFAULT_IMPORTANCE = 0.5;
|
|
5591
5966
|
var DEFAULT_OBJECT_ID = "hai-global";
|
|
5592
5967
|
var MEMORY_TYPES = /* @__PURE__ */ new Set(["fact", "preference", "event", "entity", "instruction"]);
|
|
@@ -5697,7 +6072,7 @@ function buildVectorStoreConfig(deps) {
|
|
|
5697
6072
|
);
|
|
5698
6073
|
}
|
|
5699
6074
|
if (backend)
|
|
5700
|
-
|
|
6075
|
+
logger20.warn("Mem0 OSS falls back to in-memory vector store (allowEphemeralFallback=true); data will not survive restart", { backend: backend.type });
|
|
5701
6076
|
return ok({
|
|
5702
6077
|
provider: "memory",
|
|
5703
6078
|
persistent: false,
|
|
@@ -5779,7 +6154,7 @@ async function extractMemories2(context, messages, options) {
|
|
|
5779
6154
|
}));
|
|
5780
6155
|
return ok(stored.filter((entry) => entry !== void 0));
|
|
5781
6156
|
} catch (error) {
|
|
5782
|
-
|
|
6157
|
+
logger20.error("Mem0 OSS extract failed", { error });
|
|
5783
6158
|
return err(HaiAIError.MEMORY_EXTRACT_FAILED, aiM("ai_memoryExtractFailed", { params: { error: String(error) } }), error);
|
|
5784
6159
|
}
|
|
5785
6160
|
}
|
|
@@ -5807,7 +6182,7 @@ async function recallMemories(context, query, options) {
|
|
|
5807
6182
|
}
|
|
5808
6183
|
return ok(entries.slice(0, topK));
|
|
5809
6184
|
} catch (error) {
|
|
5810
|
-
|
|
6185
|
+
logger20.error("Mem0 OSS recall failed", { error });
|
|
5811
6186
|
return err(HaiAIError.MEMORY_RECALL_FAILED, aiM("ai_memoryRecallFailed", { params: { error: String(error) } }), error);
|
|
5812
6187
|
}
|
|
5813
6188
|
}
|
|
@@ -5824,7 +6199,7 @@ async function addMemory(context, entry) {
|
|
|
5824
6199
|
throw new Error("Mem0 OSS did not return the stored memory");
|
|
5825
6200
|
return ok(toMemoryEntry(stored, objectId));
|
|
5826
6201
|
} catch (error) {
|
|
5827
|
-
|
|
6202
|
+
logger20.error("Mem0 OSS add failed", { error });
|
|
5828
6203
|
return err(HaiAIError.MEMORY_STORE_FAILED, aiM("ai_memoryStoreFailed", { params: { error: String(error) } }), error);
|
|
5829
6204
|
}
|
|
5830
6205
|
}
|
|
@@ -5863,7 +6238,7 @@ async function updateMemory(context, memoryId, updates, accessScope) {
|
|
|
5863
6238
|
throw new Error("Mem0 OSS did not return the updated memory");
|
|
5864
6239
|
return ok(toMemoryEntry(stored, objectId));
|
|
5865
6240
|
} catch (error) {
|
|
5866
|
-
|
|
6241
|
+
logger20.error("Mem0 OSS update failed", { id: memoryId, error });
|
|
5867
6242
|
return err(HaiAIError.MEMORY_STORE_FAILED, aiM("ai_memoryStoreFailed", { params: { error: String(error) } }), error);
|
|
5868
6243
|
}
|
|
5869
6244
|
}
|
|
@@ -5911,10 +6286,10 @@ async function createMem0OssMemoryOperations(deps) {
|
|
|
5911
6286
|
try {
|
|
5912
6287
|
mod = await import('mem0ai/oss');
|
|
5913
6288
|
} catch (error) {
|
|
5914
|
-
|
|
6289
|
+
logger20.error("Failed to load mem0ai/oss", { error });
|
|
5915
6290
|
throw new Error('mem0ai is required for memory.provider="mem0"; install it with `pnpm add mem0ai`');
|
|
5916
6291
|
}
|
|
5917
|
-
|
|
6292
|
+
logger20.info("Mem0 OSS memory initialized", {
|
|
5918
6293
|
vectorProvider: configResult.data.vectorProvider,
|
|
5919
6294
|
persistent: configResult.data.persistent
|
|
5920
6295
|
});
|
|
@@ -5981,7 +6356,7 @@ async function createMem0OssMemoryOperations(deps) {
|
|
|
5981
6356
|
const matched = await listMemories(context, { objectId: options?.objectId, types: options?.types, scope: options?.scope });
|
|
5982
6357
|
for (const entry of matched)
|
|
5983
6358
|
await context.memory.delete(entry.id);
|
|
5984
|
-
|
|
6359
|
+
logger20.debug("Mem0 OSS memories cleared", { removed: matched.length, objectId: options?.objectId, types: options?.types, scoped: Boolean(options?.scope) });
|
|
5985
6360
|
return ok(void 0);
|
|
5986
6361
|
} catch (error) {
|
|
5987
6362
|
return err(HaiAIError.MEMORY_STORE_FAILED, aiM("ai_memoryStoreFailed", { params: { error: String(error) } }), error);
|
|
@@ -5989,7 +6364,7 @@ async function createMem0OssMemoryOperations(deps) {
|
|
|
5989
6364
|
}
|
|
5990
6365
|
};
|
|
5991
6366
|
}
|
|
5992
|
-
var
|
|
6367
|
+
var logger21 = core.logger.child({ module: "ai", scope: "memory-consolidation" });
|
|
5993
6368
|
var DEFAULT_IMPORTANCE2 = 0.5;
|
|
5994
6369
|
var MEMORY_TYPES2 = /* @__PURE__ */ new Set(["fact", "preference", "event", "entity", "instruction"]);
|
|
5995
6370
|
var LEADING_CODE_FENCE_REGEX = /^```(?:json)?\n?/;
|
|
@@ -6090,7 +6465,7 @@ async function applyConsolidationOps(deps, ops, options) {
|
|
|
6090
6465
|
if (op.event === "DELETE" && op.id) {
|
|
6091
6466
|
const removeResult = await deps.remove(op.id);
|
|
6092
6467
|
if (!removeResult.success)
|
|
6093
|
-
|
|
6468
|
+
logger21.warn("Consolidation delete skipped", { id: op.id, error: removeResult.error.message });
|
|
6094
6469
|
continue;
|
|
6095
6470
|
}
|
|
6096
6471
|
if (op.event === "UPDATE" && op.id) {
|
|
@@ -6120,7 +6495,7 @@ async function applyConsolidationOps(deps, ops, options) {
|
|
|
6120
6495
|
return ok(entries);
|
|
6121
6496
|
}
|
|
6122
6497
|
async function extractAndConsolidate(deps, messages, options) {
|
|
6123
|
-
|
|
6498
|
+
logger21.trace("Consolidation extract", { messageCount: messages.length });
|
|
6124
6499
|
const extractResult = await extractMemories(deps.llm, messages, {
|
|
6125
6500
|
types: options?.types,
|
|
6126
6501
|
model: options?.model,
|
|
@@ -6174,7 +6549,7 @@ async function extractAndConsolidate(deps, messages, options) {
|
|
|
6174
6549
|
try {
|
|
6175
6550
|
ops = parseConsolidationOps(rawContent, candidates, relatedIds);
|
|
6176
6551
|
} catch {
|
|
6177
|
-
|
|
6552
|
+
logger21.warn("Consolidation returned invalid JSON, falling back to ADD-all", { content: rawContent.slice(0, 200) });
|
|
6178
6553
|
ops = candidates.map((candidate) => ({
|
|
6179
6554
|
event: "ADD",
|
|
6180
6555
|
text: candidate.content,
|
|
@@ -6185,12 +6560,12 @@ async function extractAndConsolidate(deps, messages, options) {
|
|
|
6185
6560
|
const applyResult = await applyConsolidationOps(deps, ops, options);
|
|
6186
6561
|
if (!applyResult.success)
|
|
6187
6562
|
return applyResult;
|
|
6188
|
-
|
|
6563
|
+
logger21.trace("Consolidation completed", { candidates: candidates.length, related: related.length, ops: ops.length, written: applyResult.data.length });
|
|
6189
6564
|
return ok(applyResult.data);
|
|
6190
6565
|
}
|
|
6191
6566
|
|
|
6192
6567
|
// src/memory/providers/ai-memory-provider-native.ts
|
|
6193
|
-
var
|
|
6568
|
+
var logger22 = core.logger.child({ module: "ai", scope: "memory-native" });
|
|
6194
6569
|
var EVICTION_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
6195
6570
|
function generateId() {
|
|
6196
6571
|
return `mem_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -6221,7 +6596,7 @@ function createNativeMemoryOperations(config, llm, embedding, store, vectorStore
|
|
|
6221
6596
|
const result = await embedding.embedText(text);
|
|
6222
6597
|
if (result.success)
|
|
6223
6598
|
return result.data;
|
|
6224
|
-
|
|
6599
|
+
logger22.warn("Failed to compute embedding for memory", { error: result.error });
|
|
6225
6600
|
return void 0;
|
|
6226
6601
|
}
|
|
6227
6602
|
async function evictLowest(candidates) {
|
|
@@ -6308,10 +6683,10 @@ function createNativeMemoryOperations(config, llm, embedding, store, vectorStore
|
|
|
6308
6683
|
}
|
|
6309
6684
|
}
|
|
6310
6685
|
await store.save(memoryId, existing, { objectId: existing.objectId });
|
|
6311
|
-
|
|
6686
|
+
logger22.trace("Memory updated", { id: memoryId });
|
|
6312
6687
|
return ok(existing);
|
|
6313
6688
|
} catch (error) {
|
|
6314
|
-
|
|
6689
|
+
logger22.error("Memory update failed", { id: memoryId, error });
|
|
6315
6690
|
return err(HaiAIError.MEMORY_STORE_FAILED, aiM("ai_memoryStoreFailed", { params: { error: String(error) } }), error);
|
|
6316
6691
|
}
|
|
6317
6692
|
}
|
|
@@ -6319,7 +6694,7 @@ function createNativeMemoryOperations(config, llm, embedding, store, vectorStore
|
|
|
6319
6694
|
const topK = options?.topK ?? config.defaultTopK;
|
|
6320
6695
|
const recencyWeight = options?.recencyWeight ?? 1 - config.recencyDecay;
|
|
6321
6696
|
const candidateMultiplier = Math.max(1, options?.candidateMultiplier ?? config.candidateMultiplier);
|
|
6322
|
-
|
|
6697
|
+
logger22.trace("Recalling memories", { query: query.slice(0, 100), topK, updateAccessStats });
|
|
6323
6698
|
try {
|
|
6324
6699
|
const where = {};
|
|
6325
6700
|
const types = options?.types;
|
|
@@ -6385,10 +6760,10 @@ function createNativeMemoryOperations(config, llm, embedding, store, vectorStore
|
|
|
6385
6760
|
}
|
|
6386
6761
|
results.push({ ...entry });
|
|
6387
6762
|
}
|
|
6388
|
-
|
|
6763
|
+
logger22.trace("Memory recall completed", { query: query.slice(0, 50), resultCount: results.length, updateAccessStats });
|
|
6389
6764
|
return ok(results);
|
|
6390
6765
|
} catch (error) {
|
|
6391
|
-
|
|
6766
|
+
logger22.error("Memory recall failed", { error });
|
|
6392
6767
|
return err(HaiAIError.MEMORY_RECALL_FAILED, aiM("ai_memoryRecallFailed", { params: { error: String(error) } }), error);
|
|
6393
6768
|
}
|
|
6394
6769
|
}
|
|
@@ -6436,10 +6811,10 @@ function createNativeMemoryOperations(config, llm, embedding, store, vectorStore
|
|
|
6436
6811
|
try {
|
|
6437
6812
|
const vector = await computeVector(entry.content);
|
|
6438
6813
|
const stored = await saveEntry(entry, vector);
|
|
6439
|
-
|
|
6814
|
+
logger22.trace("Memory added", { id: stored.id, type: stored.type });
|
|
6440
6815
|
return ok(stored);
|
|
6441
6816
|
} catch (error) {
|
|
6442
|
-
|
|
6817
|
+
logger22.error("Memory add failed", { error });
|
|
6443
6818
|
return err(HaiAIError.MEMORY_STORE_FAILED, aiM("ai_memoryStoreFailed", { params: { error: String(error) } }), error);
|
|
6444
6819
|
}
|
|
6445
6820
|
},
|
|
@@ -6485,7 +6860,7 @@ function createNativeMemoryOperations(config, llm, embedding, store, vectorStore
|
|
|
6485
6860
|
return err(HaiAIError.MEMORY_NOT_FOUND, aiM("ai_memoryNotFound", { params: { id: memoryId } }));
|
|
6486
6861
|
}
|
|
6487
6862
|
await vectorStore.remove(memoryId);
|
|
6488
|
-
|
|
6863
|
+
logger22.trace("Memory removed", { id: memoryId });
|
|
6489
6864
|
return ok(void 0);
|
|
6490
6865
|
},
|
|
6491
6866
|
/**
|
|
@@ -6565,7 +6940,7 @@ function createNativeMemoryOperations(config, llm, embedding, store, vectorStore
|
|
|
6565
6940
|
if (!options?.types && !options?.objectId && !options?.scope) {
|
|
6566
6941
|
await store.clear();
|
|
6567
6942
|
await vectorStore.clear();
|
|
6568
|
-
|
|
6943
|
+
logger22.debug("All memories cleared");
|
|
6569
6944
|
return ok(void 0);
|
|
6570
6945
|
}
|
|
6571
6946
|
const where = {};
|
|
@@ -6583,13 +6958,13 @@ function createNativeMemoryOperations(config, llm, embedding, store, vectorStore
|
|
|
6583
6958
|
await store.remove(entry.id);
|
|
6584
6959
|
await vectorStore.remove(entry.id);
|
|
6585
6960
|
}
|
|
6586
|
-
|
|
6961
|
+
logger22.debug("Memories cleared", { removed: toRemove.length, objectId: options.objectId, types: options.types, scoped: Boolean(options.scope) });
|
|
6587
6962
|
return ok(void 0);
|
|
6588
6963
|
}
|
|
6589
6964
|
};
|
|
6590
6965
|
return operations;
|
|
6591
6966
|
}
|
|
6592
|
-
var
|
|
6967
|
+
var logger23 = core.logger.child({ module: "ai", scope: "persona" });
|
|
6593
6968
|
var DEFAULT_OBJECT_ID2 = "system";
|
|
6594
6969
|
function personaKey(objectId, id) {
|
|
6595
6970
|
return JSON.stringify([objectId, id]);
|
|
@@ -6622,7 +6997,7 @@ function createPersonaOperations(store) {
|
|
|
6622
6997
|
updatedAt: now
|
|
6623
6998
|
};
|
|
6624
6999
|
await store.save(key, profile, { objectId });
|
|
6625
|
-
|
|
7000
|
+
logger23.trace("Persona saved", { id: profile.id, objectId });
|
|
6626
7001
|
return ok(profile);
|
|
6627
7002
|
} catch (error) {
|
|
6628
7003
|
return err(HaiAIError.PERSONA_SAVE_FAILED, aiM("ai_personaSaveFailed", { params: { error: String(error) } }), error);
|
|
@@ -6675,7 +7050,7 @@ function createPersonaOperations(store) {
|
|
|
6675
7050
|
}
|
|
6676
7051
|
};
|
|
6677
7052
|
}
|
|
6678
|
-
var
|
|
7053
|
+
var logger24 = core.logger.child({ module: "ai", scope: "rag" });
|
|
6679
7054
|
var DEFAULT_RAG_SYSTEM_PROMPT = `You are a helpful assistant. Answer the user's question based on the provided context.
|
|
6680
7055
|
If the context doesn't contain relevant information, say so honestly.
|
|
6681
7056
|
When using information from the context, cite the source by its number, e.g. [1] or [2].`;
|
|
@@ -6704,7 +7079,7 @@ function createRagOperations(llm, retrieval) {
|
|
|
6704
7079
|
* @returns `ok(RagResult)` 含 answer、context 列表与引用信源;检索或生成失败时返回错误
|
|
6705
7080
|
*/
|
|
6706
7081
|
async query(query, options) {
|
|
6707
|
-
|
|
7082
|
+
logger24.trace("Starting RAG query", { query: query.slice(0, 100) });
|
|
6708
7083
|
try {
|
|
6709
7084
|
const retrieveResult = await retrieval.retrieve({
|
|
6710
7085
|
query,
|
|
@@ -6753,7 +7128,7 @@ ${contextText}
|
|
|
6753
7128
|
}
|
|
6754
7129
|
const choice = chatResult.data.choices[0];
|
|
6755
7130
|
const answer = choice?.message?.content ?? "";
|
|
6756
|
-
|
|
7131
|
+
logger24.trace("RAG query completed", {
|
|
6757
7132
|
contextCount: contextItems.length,
|
|
6758
7133
|
model: chatResult.data.model
|
|
6759
7134
|
});
|
|
@@ -6780,7 +7155,7 @@ ${contextText}
|
|
|
6780
7155
|
} : void 0
|
|
6781
7156
|
});
|
|
6782
7157
|
} catch (error) {
|
|
6783
|
-
|
|
7158
|
+
logger24.error("RAG query failed", { error });
|
|
6784
7159
|
return err(HaiAIError.RAG_FAILED, aiM("ai_internalError", { params: { error: String(error) } }), error);
|
|
6785
7160
|
}
|
|
6786
7161
|
},
|
|
@@ -6788,7 +7163,7 @@ ${contextText}
|
|
|
6788
7163
|
* 流式 RAG 查询:先产出 context,再逐 chunk 产出 delta,最后 done
|
|
6789
7164
|
*/
|
|
6790
7165
|
async *queryStream(query, options) {
|
|
6791
|
-
|
|
7166
|
+
logger24.trace("Starting RAG stream", { query: query.slice(0, 100) });
|
|
6792
7167
|
const retrieveResult = await retrieval.retrieve({
|
|
6793
7168
|
query,
|
|
6794
7169
|
sources: options?.sources,
|
|
@@ -6865,7 +7240,7 @@ ${contextText}
|
|
|
6865
7240
|
}
|
|
6866
7241
|
};
|
|
6867
7242
|
}
|
|
6868
|
-
var
|
|
7243
|
+
var logger25 = core.logger.child({ module: "ai", scope: "reasoning" });
|
|
6869
7244
|
var REACT_SYSTEM_PROMPT = `You are a reasoning agent that follows the ReAct (Reasoning and Acting) framework.
|
|
6870
7245
|
For each step:
|
|
6871
7246
|
1. Think: Analyze what you know and what you need to find out.
|
|
@@ -7105,7 +7480,7 @@ ${query}` }
|
|
|
7105
7480
|
return {
|
|
7106
7481
|
async run(query, options) {
|
|
7107
7482
|
const strategy = options?.strategy ?? "react";
|
|
7108
|
-
|
|
7483
|
+
logger25.trace("Starting reasoning", { strategy, maxRounds: options?.maxRounds });
|
|
7109
7484
|
try {
|
|
7110
7485
|
switch (strategy) {
|
|
7111
7486
|
case "react":
|
|
@@ -7118,7 +7493,7 @@ ${query}` }
|
|
|
7118
7493
|
return err(HaiAIError.REASONING_STRATEGY_NOT_FOUND, aiM("ai_internalError", { params: { error: `Unknown strategy: ${strategy}` } }));
|
|
7119
7494
|
}
|
|
7120
7495
|
} catch (error) {
|
|
7121
|
-
|
|
7496
|
+
logger25.error("Reasoning failed", { error });
|
|
7122
7497
|
return err(HaiAIError.REASONING_FAILED, aiM("ai_internalError", { params: { error: String(error) } }), error);
|
|
7123
7498
|
}
|
|
7124
7499
|
},
|
|
@@ -7146,7 +7521,7 @@ ${query}` }
|
|
|
7146
7521
|
}
|
|
7147
7522
|
};
|
|
7148
7523
|
}
|
|
7149
|
-
var
|
|
7524
|
+
var logger26 = core.logger.child({ module: "ai", scope: "rerank" });
|
|
7150
7525
|
function createRerankOperations(config) {
|
|
7151
7526
|
function normalizeDocuments(documents) {
|
|
7152
7527
|
if (documents.length === 0) {
|
|
@@ -7184,7 +7559,7 @@ function createRerankOperations(config) {
|
|
|
7184
7559
|
if (request.returnDocuments) {
|
|
7185
7560
|
body.return_documents = true;
|
|
7186
7561
|
}
|
|
7187
|
-
|
|
7562
|
+
logger26.trace("Calling rerank API", { model: resolved.model, documentCount: texts.length, topN: request.topN });
|
|
7188
7563
|
try {
|
|
7189
7564
|
const response = await fetch(`${resolved.baseUrl}/v1/rerank`, {
|
|
7190
7565
|
method: "POST",
|
|
@@ -7196,7 +7571,7 @@ function createRerankOperations(config) {
|
|
|
7196
7571
|
});
|
|
7197
7572
|
if (!response.ok) {
|
|
7198
7573
|
const errorText = await response.text().catch(() => response.statusText);
|
|
7199
|
-
|
|
7574
|
+
logger26.error("Rerank API request failed", { status: response.status, error: errorText });
|
|
7200
7575
|
return err(HaiAIError.RERANK_API_ERROR, aiM("ai_rerankApiFailed", { params: { error: `HTTP ${response.status}: ${errorText}` } }));
|
|
7201
7576
|
}
|
|
7202
7577
|
const data = await response.json();
|
|
@@ -7206,10 +7581,10 @@ function createRerankOperations(config) {
|
|
|
7206
7581
|
relevanceScore: item.relevance_score,
|
|
7207
7582
|
document: item.document?.text
|
|
7208
7583
|
}));
|
|
7209
|
-
|
|
7584
|
+
logger26.trace("Rerank completed", { resultCount: results.length });
|
|
7210
7585
|
return ok({ model: resolved.model, results });
|
|
7211
7586
|
} catch (error) {
|
|
7212
|
-
|
|
7587
|
+
logger26.error("Rerank API call failed", { error });
|
|
7213
7588
|
return err(HaiAIError.RERANK_API_ERROR, aiM("ai_rerankApiFailed", { params: { error: String(error) } }), error);
|
|
7214
7589
|
}
|
|
7215
7590
|
}
|
|
@@ -7241,7 +7616,7 @@ function createRerankOperations(config) {
|
|
|
7241
7616
|
}
|
|
7242
7617
|
};
|
|
7243
7618
|
}
|
|
7244
|
-
var
|
|
7619
|
+
var logger27 = core.logger.child({ module: "ai", scope: "retrieval" });
|
|
7245
7620
|
function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rerankOps) {
|
|
7246
7621
|
const vectorStoreCache = /* @__PURE__ */ new Map();
|
|
7247
7622
|
function getVectorStore(collection) {
|
|
@@ -7268,7 +7643,7 @@ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rer
|
|
|
7268
7643
|
return err(HaiAIError.RETRIEVAL_FAILED, aiM("ai_internalError", { params: { error: `Source '${source.id}' already exists` } }));
|
|
7269
7644
|
}
|
|
7270
7645
|
await sourceStore.save(source.id, source);
|
|
7271
|
-
|
|
7646
|
+
logger27.debug("Retrieval source added", { sourceId: source.id, collection: source.collection });
|
|
7272
7647
|
return ok(void 0);
|
|
7273
7648
|
},
|
|
7274
7649
|
/**
|
|
@@ -7285,7 +7660,7 @@ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rer
|
|
|
7285
7660
|
return err(HaiAIError.RETRIEVAL_SOURCE_NOT_FOUND, aiM("ai_internalError", { params: { error: `Source '${sourceId}' not found` } }));
|
|
7286
7661
|
}
|
|
7287
7662
|
await sourceStore.remove(sourceId);
|
|
7288
|
-
|
|
7663
|
+
logger27.debug("Retrieval source removed", { sourceId });
|
|
7289
7664
|
return ok(void 0);
|
|
7290
7665
|
},
|
|
7291
7666
|
/**
|
|
@@ -7364,27 +7739,27 @@ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rer
|
|
|
7364
7739
|
allItems.length = 0;
|
|
7365
7740
|
allItems.push(...reranked);
|
|
7366
7741
|
} else {
|
|
7367
|
-
|
|
7742
|
+
logger27.warn("Rerank failed, falling back to vector scores", { error: rerankResult.error });
|
|
7368
7743
|
}
|
|
7369
7744
|
}
|
|
7370
7745
|
allItems.sort((a, b) => b.score - a.score);
|
|
7371
7746
|
const topK = request.topK ?? 10;
|
|
7372
7747
|
const limitedItems = allItems.slice(0, topK);
|
|
7373
7748
|
const duration = Date.now() - startTime;
|
|
7374
|
-
|
|
7749
|
+
logger27.trace("Retrieval completed", { query: request.query, resultCount: limitedItems.length, duration });
|
|
7375
7750
|
return ok({
|
|
7376
7751
|
items: limitedItems,
|
|
7377
7752
|
query: request.query,
|
|
7378
7753
|
duration
|
|
7379
7754
|
});
|
|
7380
7755
|
} catch (error) {
|
|
7381
|
-
|
|
7756
|
+
logger27.error("Retrieval failed", { error });
|
|
7382
7757
|
return err(HaiAIError.RETRIEVAL_FAILED, aiM("ai_internalError", { params: { error: String(error) } }), error);
|
|
7383
7758
|
}
|
|
7384
7759
|
}
|
|
7385
7760
|
};
|
|
7386
7761
|
}
|
|
7387
|
-
var
|
|
7762
|
+
var logger28 = core.logger.child({ module: "ai", scope: "summary" });
|
|
7388
7763
|
var SUMMARIZE_SYSTEM_PROMPT = `You are a conversation summarizer. Create a concise summary of the conversation that preserves:
|
|
7389
7764
|
1. Key facts and decisions made
|
|
7390
7765
|
2. Important context and background information
|
|
@@ -7438,7 +7813,7 @@ ${INCREMENTAL_SUMMARIZE_SUFFIX.replace("{previousSummary}", options.previousSumm
|
|
|
7438
7813
|
return ok(chatResult.data.choices[0]?.message?.content ?? "");
|
|
7439
7814
|
}
|
|
7440
7815
|
async function summarize(messages, options) {
|
|
7441
|
-
|
|
7816
|
+
logger28.trace("Summarizing messages", { messageCount: messages.length });
|
|
7442
7817
|
try {
|
|
7443
7818
|
const result = await generate(messages, options);
|
|
7444
7819
|
if (!result.success)
|
|
@@ -7450,7 +7825,7 @@ ${INCREMENTAL_SUMMARIZE_SUFFIX.replace("{previousSummary}", options.previousSumm
|
|
|
7450
7825
|
coveredMessages: messages.filter((m) => m.role !== "system").length
|
|
7451
7826
|
});
|
|
7452
7827
|
} catch (error) {
|
|
7453
|
-
|
|
7828
|
+
logger28.error("Context summarization failed", { error });
|
|
7454
7829
|
return err(HaiAIError.CONTEXT_SUMMARIZE_FAILED, aiM("ai_contextSummarizeFailed", { params: { error: String(error) } }), error);
|
|
7455
7830
|
}
|
|
7456
7831
|
}
|
|
@@ -7494,7 +7869,7 @@ function createTokenOperations(config) {
|
|
|
7494
7869
|
}
|
|
7495
7870
|
|
|
7496
7871
|
// src/ai-functions.ts
|
|
7497
|
-
var
|
|
7872
|
+
var logger29 = core.logger.child({ module: "ai", scope: "functions" });
|
|
7498
7873
|
async function createAISubsystems(config, deps) {
|
|
7499
7874
|
const { storeProvider, datapipe: datapipeDep } = deps;
|
|
7500
7875
|
const memoryParsed = MemoryConfigSchema.parse(config.memory ?? {});
|
|
@@ -7516,7 +7891,7 @@ async function createAISubsystems(config, deps) {
|
|
|
7516
7891
|
const retrieval = createRetrievalOperations(embedding, storeProvider, sourceStore, rerank);
|
|
7517
7892
|
if (config.retrieval?.sources?.length) {
|
|
7518
7893
|
const configSources = RetrievalConfigSchema.parse(config.retrieval).sources ?? [];
|
|
7519
|
-
sourceStore.saveMany(configSources.map((s) => ({ id: s.id, data: s }))).catch((e) =>
|
|
7894
|
+
sourceStore.saveMany(configSources.map((s) => ({ id: s.id, data: s }))).catch((e) => logger29.warn("Failed to pre-register retrieval sources", { error: e }));
|
|
7520
7895
|
}
|
|
7521
7896
|
const rag = createRagOperations(llm, retrieval);
|
|
7522
7897
|
const knowledgeParsed = KnowledgeConfigSchema.parse(config.knowledge ?? {});
|
|
@@ -7550,7 +7925,7 @@ async function createAISubsystems(config, deps) {
|
|
|
7550
7925
|
const image = createImageOperations(config);
|
|
7551
7926
|
return { llm, mcp, embedding, reasoning, rerank, retrieval, rag, knowledge, memory, persona, token, summary, compress, context, file, audio, image };
|
|
7552
7927
|
}
|
|
7553
|
-
var
|
|
7928
|
+
var logger30 = core.logger.child({ module: "ai", scope: "store-provider-db" });
|
|
7554
7929
|
var MAX_BATCH_SQL_PARAMS = 900;
|
|
7555
7930
|
var ReldbAIRelStore = class {
|
|
7556
7931
|
sql;
|
|
@@ -8007,7 +8382,7 @@ var DbKnowledgeStore = class {
|
|
|
8007
8382
|
for (const stmt of statements) {
|
|
8008
8383
|
const result = await this.sql.execute(stmt);
|
|
8009
8384
|
if (!result.success) {
|
|
8010
|
-
|
|
8385
|
+
logger30.error("Knowledge schema creation failed", { sql: stmt.trim().slice(0, 60), error: result.error });
|
|
8011
8386
|
throw new Error(`Knowledge schema creation failed: ${String(result.error)}`);
|
|
8012
8387
|
}
|
|
8013
8388
|
}
|
|
@@ -8275,7 +8650,7 @@ function createDbStoreProvider(deps) {
|
|
|
8275
8650
|
async initialize() {
|
|
8276
8651
|
if (relStores.length > 0) {
|
|
8277
8652
|
await Promise.all(relStores.map((s) => s.createTable()));
|
|
8278
|
-
|
|
8653
|
+
logger30.debug("DB store provider initialized", { tableCount: relStores.length });
|
|
8279
8654
|
}
|
|
8280
8655
|
},
|
|
8281
8656
|
async close() {
|
|
@@ -8488,7 +8863,7 @@ function createMemoryStoreProvider() {
|
|
|
8488
8863
|
}
|
|
8489
8864
|
|
|
8490
8865
|
// src/ai-main.ts
|
|
8491
|
-
var
|
|
8866
|
+
var logger31 = core.logger.child({ module: "ai", scope: "main" });
|
|
8492
8867
|
var initInProgress = false;
|
|
8493
8868
|
var currentConfig = null;
|
|
8494
8869
|
var currentLLM = null;
|
|
@@ -8629,7 +9004,7 @@ async function resetAllState() {
|
|
|
8629
9004
|
try {
|
|
8630
9005
|
await storeProvider.close();
|
|
8631
9006
|
} catch (error) {
|
|
8632
|
-
|
|
9007
|
+
logger31.error("AI store provider close failed", { error });
|
|
8633
9008
|
}
|
|
8634
9009
|
}
|
|
8635
9010
|
}
|
|
@@ -8646,19 +9021,19 @@ var streamOperations = {
|
|
|
8646
9021
|
var ai = {
|
|
8647
9022
|
async init(config, options) {
|
|
8648
9023
|
if (initInProgress) {
|
|
8649
|
-
|
|
9024
|
+
logger31.warn("AI init already in progress, skipping concurrent call");
|
|
8650
9025
|
return err(HaiAIError.INIT_IN_PROGRESS, aiM("ai_initInProgress"));
|
|
8651
9026
|
}
|
|
8652
9027
|
initInProgress = true;
|
|
8653
9028
|
try {
|
|
8654
9029
|
if (currentConfig) {
|
|
8655
|
-
|
|
9030
|
+
logger31.warn("AI module is already initialized, reinitializing");
|
|
8656
9031
|
await ai.close();
|
|
8657
9032
|
}
|
|
8658
|
-
|
|
9033
|
+
logger31.info("Initializing AI module");
|
|
8659
9034
|
const parseResult = AIConfigSchema.safeParse(config ?? {});
|
|
8660
9035
|
if (!parseResult.success) {
|
|
8661
|
-
|
|
9036
|
+
logger31.error("AI config validation failed", { error: parseResult.error.message });
|
|
8662
9037
|
return err(
|
|
8663
9038
|
HaiAIError.CONFIGURATION_ERROR,
|
|
8664
9039
|
aiM("ai_configError", { params: { error: parseResult.error.message } }),
|
|
@@ -8673,7 +9048,7 @@ var ai = {
|
|
|
8673
9048
|
storeProvider = createDbStoreProviderFromModules();
|
|
8674
9049
|
} else {
|
|
8675
9050
|
storeProvider = createMemoryStoreProvider();
|
|
8676
|
-
|
|
9051
|
+
logger31.warn("AI is using the ephemeral in-memory store; initialize reldb and vecdb before ai.init() for persistent data");
|
|
8677
9052
|
}
|
|
8678
9053
|
currentStoreProvider = storeProvider;
|
|
8679
9054
|
const subs = await createAISubsystems(parsed, {
|
|
@@ -8699,11 +9074,11 @@ var ai = {
|
|
|
8699
9074
|
currentImage = subs.image;
|
|
8700
9075
|
currentA2AConfig = parsed.a2a;
|
|
8701
9076
|
currentConfig = parsed;
|
|
8702
|
-
|
|
9077
|
+
logger31.info("AI module initialized", { model: parsed.llm?.model });
|
|
8703
9078
|
return ok(void 0);
|
|
8704
9079
|
} catch (error) {
|
|
8705
9080
|
await resetAllState();
|
|
8706
|
-
|
|
9081
|
+
logger31.error("AI module initialization failed", { error });
|
|
8707
9082
|
return err(
|
|
8708
9083
|
HaiAIError.CONFIGURATION_ERROR,
|
|
8709
9084
|
aiM("ai_initFailed", {
|
|
@@ -8783,12 +9158,12 @@ var ai = {
|
|
|
8783
9158
|
},
|
|
8784
9159
|
async close() {
|
|
8785
9160
|
if (!currentConfig) {
|
|
8786
|
-
|
|
9161
|
+
logger31.info("AI module already closed, skipping");
|
|
8787
9162
|
return;
|
|
8788
9163
|
}
|
|
8789
|
-
|
|
9164
|
+
logger31.info("Closing AI module");
|
|
8790
9165
|
await resetAllState();
|
|
8791
|
-
|
|
9166
|
+
logger31.info("AI module closed");
|
|
8792
9167
|
}
|
|
8793
9168
|
};
|
|
8794
9169
|
function createMcpServer(options) {
|