Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions llm-proxy/backend/src/controllers/ProxyController.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,47 @@ async function proxyToOllama(req, res) {

} catch (e) {
logger.error(`❌ Internal Server Error [${shortHash}]: ${e.message || e}`);

// If the payload expects JSON, return a valid fallback JSON response instead of a raw 500/502 error object
// to prevent the App Script pipeline from crashing when trying to parse the error text.
let expectsJson = false;
if (payload.response_format && payload.response_format.type === "json_object") {
expectsJson = true;
} else if (messages) {
for (const msg of messages) {
if (msg.content && msg.content.toLowerCase().includes("json")) {
expectsJson = true;
break;
}
}
}

if (expectsJson) {
const fallbackJson = JSON.stringify({
final_evaluation: {
decision: "Error",
exclusion_code: "N/A",
reasoning: `Server Error: ${e.message || String(e)}`
},
logic_trace: {}
});

return res.status(500).json({
id: `error-${Date.now()}`,
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model: payload.model || "unknown",
choices: [{
index: 0,
message: {
role: "assistant",
content: fallbackJson
},
finish_reason: "stop"
}]
});
}

if (e.message && e.message.includes('fetch')) {
return res.status(502).json({ error: `Ollama connection error: ${e.message}` });
}
Expand Down
18 changes: 17 additions & 1 deletion llm-proxy/backend/src/services/RoutingService.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,23 @@ class RoutingService {
} else {
logger.warn(`⚠️ Invalid JSON detected for [${reqHash.substring(0, 8)}] on attempt ${attempt + 1}/${maxRetries}. Retrying...`);
if (attempt === maxRetries - 1) {
throw new Error("LLM failed to produce valid JSON after retries.");
logger.warn(`⚠️ Exhausted retries for [${reqHash.substring(0, 8)}]. Returning fallback error JSON.`);
const fallbackJson = JSON.stringify({
final_evaluation: {
decision: "Error",
exclusion_code: "N/A",
reasoning: "LLM failed to produce valid JSON after retries."
},
logic_trace: {}
});

// Overwrite the content with our fallback
if (responseData && responseData.choices && responseData.choices.length > 0) {
if (!responseData.choices[0].message) {
responseData.choices[0].message = { role: "assistant" };
}
responseData.choices[0].message.content = fallbackJson;
}
}
}
} else {
Expand Down
57 changes: 51 additions & 6 deletions llm-proxy/backend/src/utils/parsers.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,59 @@
function optimisticRepairJson(text) {
// A robust regex to find JSON string values and escape unescaped double quotes and newlines
const pattern = /("\w+"\s*:\s*")([\s\S]*?)("\s*(?:,|}|]))/g;
// 1. Repair quotes in flat string values
const pattern = /("\w+"\s*:\s*")([\s\S]*?)("\s*(?:,|}|\]))/g;

let repaired = text.replace(pattern, (match, start, content, end) => {
// If content contains standard object/array syntax or looks like nested JSON mapping, skip quote repair
if (/\{\s*\\?"\w+\\?"\s*:/.test(content)) {
return match;
}

return text.replace(pattern, (match, start, content, end) => {
// Escape double quotes by unescaping first to avoid double-escaping, then escape all
let repairedContent = content.replace(/\\"/g, '"').replace(/"/g, '\\"');
// Escape newlines
repairedContent = repairedContent.replace(/\n/g, '\\n').replace(/\r/g, '');
return start + repairedContent + end;
});

// 2. Safely encode newlines and control characters everywhere inside strings
let inString = false;
let escapeNext = false;
let result = '';

for (let i = 0; i < repaired.length; i++) {
const char = repaired[i];

if (escapeNext) {
result += char;
escapeNext = false;
continue;
}

if (char === '\\') {
result += char;
escapeNext = true;
continue;
}

if (char === '"') {
inString = !inString;
result += char;
continue;
}

if (inString) {
if (char === '\n') {
result += '\\n';
} else if (char === '\r') {
// skip
} else if (char === '\t') {
result += '\\t';
} else {
result += char;
}
} else {
result += char;
}
}

return result;
}

function extractJsonFromMixedText(text) {
Expand Down