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:
@@ -146,6 +146,10 @@ explicite (D9). Par défaut `false`.
|
|||||||
**`LOGS_PATH`** accepte le placeholder `{host}`, substitué par le host du profil
|
**`LOGS_PATH`** accepte le placeholder `{host}`, substitué par le host du profil
|
||||||
actif à chaque appel.
|
actif à chaque appel.
|
||||||
|
|
||||||
|
**`MAX_LOG_SEARCH_CHARS`** (défaut 25 000) : plafond en caractères de la réponse
|
||||||
|
de `search_logs` — au-delà, des résultats entiers sont écartés et signalés
|
||||||
|
(`truncated`, D24).
|
||||||
|
|
||||||
**Au runtime.** `profile-manager` est un singleton d'état global. Les services
|
**Au runtime.** `profile-manager` est un singleton d'état global. Les services
|
||||||
s'abonnent via `onSwitch()` pour invalider ce qui dépend du tenant :
|
s'abonnent via `onSwitch()` pour invalider ce qui dépend du tenant :
|
||||||
|
|
||||||
|
|||||||
+30
-13
@@ -182,22 +182,39 @@ async function searchLogs(args) {
|
|||||||
|
|
||||||
const result = await logService.searchLogs(keyword, max_results, context_lines);
|
const result = await logService.searchLogs(keyword, max_results, context_lines);
|
||||||
|
|
||||||
return {
|
// Garde-fou de taille (L3.1) : max_results borne le nombre de résultats,
|
||||||
content: [
|
// 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
|
||||||
type: 'text',
|
// plafond on écarte des résultats ENTIERS (jamais coupés au milieu de
|
||||||
text: JSON.stringify(
|
// 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,
|
success: true,
|
||||||
keyword: result.keyword,
|
keyword: result.keyword,
|
||||||
totalResults: result.totalResults,
|
totalResults: result.totalResults,
|
||||||
results: result.results,
|
returned: kept.length,
|
||||||
},
|
};
|
||||||
null,
|
if (omitted > 0) {
|
||||||
2
|
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 }],
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user