L3.1b : plafonne la taille de réponse de search_logs (MAX_LOG_SEARCH_CHARS)

max_results (50) borne le nombre de résultats mais pas le volume : les
context_lines multiplient la taille, et la réponse aux seuls défauts
atteignait 52-56 000 caractères — rejetée par le client MCP. Le tool
écarte désormais des résultats ENTIERS (jamais coupés au milieu de leur
contexte) jusqu'à passer sous le plafond, et le signale : truncated,
returned/omitted, hint actionnable (affiner keyword, réduire
context_lines, baisser max_results).

Plafond : MAX_LOG_SEARCH_CHARS, variable d'environnement avec défaut
25 000 — même style de lecture que MAX_QUERY_ROWS/QUERY_TIMEOUT
(parseInt(process.env.X) || défaut), documentée dans CLAUDE.md
(réglages partagés). 25 000 correspond à l'ordre de grandeur cible du
lot (~20-25 000 chars) et se mesure sur content[0].text, la taille qui
fait foi côté protocole. Les défauts max_results=50 et context_lines=2
sont inchangés : le correctif est le bornage signalé, pas un changement
de comportement par défaut.

Mesures via le protocole (LIMAGRAIN, content[0].text) :
- {"keyword":"Error"} : avant 52 162 chars / 50 résultats ; après
  24 164 chars, returned 16, omitted 34, truncated true, hint présent.
- {"keyword":"Error","max_results":3} : 4 855 chars, 3/3, pas de
  troncature signalée.
- mot-clé sans occurrence : 112 chars, 0 résultat, pas de truncated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Arthur Ria
2026-08-25 10:35:49 +02:00
parent c4d6b5650e
commit d0a6cc1a0b
2 changed files with 36 additions and 15 deletions
+32 -15
View File
@@ -182,22 +182,39 @@ async function searchLogs(args) {
const result = await logService.searchLogs(keyword, max_results, context_lines);
// Garde-fou de taille (L3.1) : max_results borne le nombre de résultats,
// pas le volume — les context_lines multiplient la taille (55 954 chars
// mesurés avec les seuls défauts, rejetés par le client MCP). Au-delà du
// plafond on écarte des résultats ENTIERS (jamais coupés au milieu de
// leur contexte) et on le signale : truncated + omitted + hint.
const cap = parseInt(process.env.MAX_LOG_SEARCH_CHARS) || 25000;
const buildText = (kept) => {
const omitted = result.results.length - kept.length;
const payload = {
success: true,
keyword: result.keyword,
totalResults: result.totalResults,
returned: kept.length,
};
if (omitted > 0) {
payload.truncated = true;
payload.omitted = omitted;
payload.hint = `Plafond de taille de réponse atteint (${cap} caractères) : ${kept.length} résultat(s) renvoyé(s) sur ${result.totalResults}, ${omitted} écarté(s). Affinez le keyword, réduisez context_lines ou baissez max_results.`;
}
payload.results = kept;
return JSON.stringify(payload, null, 2);
};
let kept = result.results.slice();
let text = buildText(kept);
while (text.length > cap && kept.length > 0) {
kept.pop();
text = buildText(kept);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
success: true,
keyword: result.keyword,
totalResults: result.totalResults,
results: result.results,
},
null,
2
),
},
],
content: [{ type: 'text', text }],
};
} catch (err) {
return {