Skip to content

Commit 400f833

Browse files
committed
fix: useLLM / gestion chunks JSON incomplets
refactoring du code, utilisation d'un buffer pour gérer les cas où le chunk n'est pas un objet JSON complet
1 parent 7e70afc commit 400f833

3 files changed

Lines changed: 71 additions & 62 deletions

File tree

js/LLM/useLLM.js

Lines changed: 69 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,43 @@ function detectApiType(chunkElement) {
3030

3131
let LLMactive = false;
3232

33+
function isJSONComplete(str) {
34+
try {
35+
JSON.parse(str);
36+
return true;
37+
} catch {
38+
return false;
39+
}
40+
}
41+
42+
function parseChunkSafely(chunk, incompleteBuffer) {
43+
incompleteBuffer += chunk;
44+
if (!isJSONComplete(incompleteBuffer)) {
45+
return { parsed: null, incompleteChunkBuffer: incompleteBuffer };
46+
}
47+
const parsed = JSON.parse(incompleteBuffer);
48+
incompleteBuffer = "";
49+
return { parsed: parsed, incompleteChunkBuffer: incompleteBuffer };
50+
}
51+
52+
function extractCohereText(chunkObject, version) {
53+
switch (version) {
54+
case "v1":
55+
return chunkObject.text ? chunkObject.text : "";
56+
case "v2":
57+
return chunkObject.message &&
58+
chunkObject.message.content &&
59+
chunkObject.message.content[0]
60+
? chunkObject.message.content[0].text
61+
: "";
62+
default:
63+
throw new Error("Version d'API Cohere non supportée : " + version);
64+
}
65+
}
66+
3367
// Pour pouvoir lire le stream diffusé par l'API utilisée pour se connecter à une IA
3468
async function readStream(streamableObject, chatMessage, APItype) {
69+
let incompleteChunkBuffer = "";
3570
const LLM_MAX_PROCESSING_TIME = yaml.useLLM.maxProcessingTime;
3671
if (!streamableObject.getReader) {
3772
throw new TypeError(
@@ -73,83 +108,57 @@ async function readStream(streamableObject, chatMessage, APItype) {
73108
if (!APItype) {
74109
APItype = detectApiType(chunkElement);
75110
}
76-
if (APItype === "cohere") {
77-
// Cas de l'API Cohere
78-
// détection de la version utilisée
79-
const cohereAPIversion = yaml.useLLM.url.match(/v\d+/)?.[0];
80-
function textInChunk(chunkObject) {
81-
switch (cohereAPIversion) {
82-
case "v1":
83-
return chunkObject.text;
84-
case "v2":
85-
return chunkObject.message.content[0].text;
86-
default:
87-
throw new Error(
88-
"Version d'API Cohere non supportée : " + cohereAPIversion,
89-
);
90-
}
111+
112+
let cleanedChunk = chunkElement.trim();
113+
114+
if (APItype === "openai") {
115+
cleanedChunk = cleanedChunk.replace("data: ", "");
116+
if (cleanedChunk.indexOf("[DONE]") !== -1) {
117+
LLMactive = false;
118+
continue;
91119
}
120+
}
121+
122+
const parsingChunk = parseChunkSafely(
123+
cleanedChunk,
124+
incompleteChunkBuffer,
125+
);
126+
const chunkObject = parsingChunk.parsed;
127+
incompleteChunkBuffer = parsingChunk.incompleteChunkBuffer;
128+
129+
if (!chunkObject) continue;
92130

93-
const chunkObject = JSON.parse(chunkElement.trim());
131+
if (APItype === "cohere") {
132+
const cohereAPIversion =
133+
yaml &&
134+
yaml.useLLM &&
135+
yaml.useLLM.url &&
136+
yaml.useLLM.url.match(/v\d+/)
137+
? yaml.useLLM.url.match(/v\d+/)[0]
138+
: "v2";
94139
if (chunkObject.event_type === "text-generation" && LLMactive) {
95-
const chunkMessage = textInChunk(chunkObject) || "";
140+
const chunkMessage =
141+
extractCohereText(chunkObject, cohereAPIversion) || "";
96142
accumulatedChunks += chunkMessage;
97143
chatMessage.innerHTML = markdownToHTML(accumulatedChunks);
98144
}
99-
LLMactive = chunkObject.is_finished ? false : true;
145+
LLMactive = !chunkObject.is_finished;
100146
} else if (APItype === "ollama") {
101147
// Cas de l'API Ollama
102-
const chunkObjectString = chunkElement.trim();
103-
// Vérifie si le flux a fini
104-
if (chunkObjectString.includes('"done":true')) {
148+
if (cleanedChunk.indexOf('"done":true') !== -1) {
105149
LLMactive = false;
106150
continue;
107151
}
108-
let chunkObject;
109-
try {
110-
chunkObject = JSON.parse(chunkObjectString);
111-
} catch (jsonError) {
112-
console.warn(
113-
"Erreur JSON.parse sur chunkObjectString :",
114-
chunkObjectString,
115-
jsonError,
116-
);
117-
continue;
118-
}
119152
if (chunkObject.message && chunkObject.message.content) {
120-
const chunkMessage = chunkObject.message.content || "";
153+
const chunkMessage = chunkObject.message.content;
121154
accumulatedChunks += chunkMessage;
122155
chatMessage.innerHTML = markdownToHTML(accumulatedChunks);
123156
}
124157
} else if (APItype === "openai") {
125158
// Cas du modèle openAI
126-
const chunkObjectString = chunkElement.replace("data: ", "").trim();
127-
128-
// Vérifie si le flux a fini
129-
if (chunkObjectString.includes("[DONE]")) {
130-
LLMactive = false;
131-
continue;
132-
}
133-
134-
let chunkObject;
135-
try {
136-
chunkObject = JSON.parse(chunkObjectString);
137-
} catch (jsonError) {
138-
console.warn(
139-
"Erreur JSON.parse sur chunkObjectString :",
140-
chunkObjectString,
141-
jsonError,
142-
);
143-
continue;
144-
}
145-
146-
if (
147-
chunkObject.choices &&
148-
chunkObject.choices[0] &&
149-
chunkObject.choices[0].delta &&
150-
chunkObject.choices[0].delta.content
151-
) {
152-
const chunkMessage = chunkObject.choices[0].delta.content || "";
159+
const choice = chunkObject.choices && chunkObject.choices[0];
160+
if (choice && choice.delta && choice.delta.content) {
161+
const chunkMessage = choice.delta.content;
153162
accumulatedChunks += chunkMessage;
154163
chatMessage.innerHTML = markdownToHTML(accumulatedChunks);
155164
}

script.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

script.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)