L5.2 : plafonne la taille de reponse des trois outils de requete
Aucune borne de VOLUME n'existait sur query_wms_entities, call_query_api et
search_wms_data -- seulement une borne de LIGNES (MAX_QUERY_ROWS). Le modele
Writing serialise l'agregat complet (navigations, $id) : une seule ligne
Products y pese 95 288 caracteres, la ou la meme ligne Reading en fait ~4 500.
Toutes ces reponses depassaient le seuil de rejet du client MCP (D24), qui
renvoie un echec opaque plutot qu'un resultat partiel.
Plafond commun MAX_QUERY_RESPONSE_CHARS (defaut 25 000, meme ordre de grandeur
que MAX_LOG_SEARCH_CHARS), applique par src/services/response-limit.js : on
ecarte des LIGNES ENTIERES, jamais coupees au milieu, et on signale avec le
vocabulaire D24 (truncated / returned / omitted / hint). Le helper est partage
parce que les trois outils partagent le meme mecanisme -- ce que les mecanismes
de get_system_parameters et search_logs, eux, ne font pas. La recherche
dichotomique evite 200 reconstructions d'une charge utile de ~1 Mo.
Mesures avant/apres (protocole, LIMAGRAIN, longueur de content[0].text) :
query_wms_entities Products limit 200 957 234 -> 24 432
count 200, returned 5, omitted 195, truncated
search_wms_data "PAL" 847 543 -> 22 992
totalFound 150, returned 4, omitted 146 ; reparti en tourniquet :
Products 2, Containers 1, Tasks 1 -- sans quoi Products, en tete,
consommerait tout le budget et les deux autres reviendraient a zero
resultat sans que rien ne le dise
call_query_api Products query_type 1 95 288 -> 738
cas limite : returned 0, omitted 1, truncated, hint expliquant le
volume Writing et renvoyant vers Reading
Sous le plafond, rien ne change -- verifie identique OCTET POUR OCTET contre
la version precedente :
query_wms_entities Container limit 1 4 476 -> 4 476
call_query_api Products limit 2 9 671 -> 9 671
get_entity_schema Container 11 172 -> 11 172
search_wms_data sous plafond 11 767 -> 11 767
count_wms_entities Products 120 -> 120 (non concerne)
MAX_QUERY_ROWS et les limites par defaut des outils sont inchanges : le
correctif est le bornage signale, pas une reduction silencieuse. L'avertissement
de volume rejoint la description du parametre query_type (D25) de
query_wms_entities et call_query_api, pas celle de count_wms_entities dont la
reponse est un scalaire.
Baseline preservee : 23 outils, 6 resources.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -64,7 +64,8 @@ src/
|
||||
│ ├── workflow-service.js Workflows, lazy loading + cache
|
||||
│ ├── ad-service.js Application Dictionary, 20 types, cache par type
|
||||
│ ├── wms-query-service.js Construction d'expressions LINQ
|
||||
│ └── log-service.js Lecture et recherche dans les fichiers de logs
|
||||
│ ├── log-service.js Lecture et recherche dans les fichiers de logs
|
||||
│ └── response-limit.js Plafond de taille commun aux outils de requête (D24)
|
||||
└── tools/ 23 outils MCP
|
||||
├── wms-query-tools.js query_wms_entities, count_wms_entities,
|
||||
│ get_entity_schema, search_wms_data
|
||||
@@ -150,6 +151,11 @@ actif à chaque appel.
|
||||
de `search_logs` — au-delà, des résultats entiers sont écartés et signalés
|
||||
(`truncated`, D24).
|
||||
|
||||
**`MAX_QUERY_RESPONSE_CHARS`** (défaut 25 000) : même plafond pour
|
||||
`query_wms_entities`, `call_query_api` et `search_wms_data` — au-delà, des
|
||||
lignes entières sont écartées et signalées (D24). `count_wms_entities` n'est
|
||||
pas concerné.
|
||||
|
||||
**Au runtime.** `profile-manager` est un singleton d'état global. Les services
|
||||
s'abonnent via `onSwitch()` pour invalider ce qui dépend du tenant :
|
||||
|
||||
@@ -204,6 +210,22 @@ restent complètes, `dataTotalChars` est porté par toute réponse, et la tranch
|
||||
est **verbatim** — concaténer les tranches dans l'ordre des offsets reconstitue
|
||||
la définition à l'octet près. Ne la résumez pas, ne la « parsez » pas.
|
||||
|
||||
**Les trois outils de requête plafonnent leur volume** via
|
||||
`src/services/response-limit.js` (`MAX_QUERY_RESPONSE_CHARS`) : au-delà, des
|
||||
lignes entières sont écartées, jamais coupées au milieu. Sous le plafond, la
|
||||
réponse est inchangée **octet pour octet** — c'est la contrainte à préserver si
|
||||
vous y touchez.
|
||||
|
||||
| Outil | Unité écartée | Total porté |
|
||||
|---|---|---|
|
||||
| `query_wms_entities` | une ligne | `count` (déjà présent) |
|
||||
| `call_query_api` | une ligne | `totalRows` (ajouté à la coupe) |
|
||||
| `search_wms_data` | un résultat, réparti en tourniquet entre les entités | `totalFound` (déjà présent) |
|
||||
|
||||
Cas limite réel : **une seule ligne Writing dépasse le plafond** (95 288
|
||||
caractères mesurés) — la réponse est alors `returned: 0`, `omitted: 1`,
|
||||
`truncated: true`, avec un hint qui renvoie vers Reading.
|
||||
|
||||
---
|
||||
|
||||
## Écrire une requête WMS
|
||||
|
||||
+40
-2
@@ -465,8 +465,11 @@ Les mécanismes restent **volontairement locaux**, car ils diffèrent :
|
||||
on continue avec l'offset suivant) ; `search_logs` plafonne le volume
|
||||
(`MAX_LOG_SEARCH_CHARS`, défaut 25 000 caractères) en écartant des résultats
|
||||
**entiers** — jamais coupés au milieu de leurs lignes de contexte — et annonce
|
||||
en plus `omitted`, le compte écarté. Pas de helper partagé : le factoriser
|
||||
forcerait une abstraction commune à deux mécanismes qui n'en ont pas.
|
||||
en plus `omitted`, le compte écarté. Ces deux-là ne partagent pas de helper : le
|
||||
factoriser forcerait une abstraction commune à deux mécanismes qui n'en ont pas.
|
||||
Les **trois outils de requête**, eux, partagent le même mécanisme — ils
|
||||
partagent donc `src/services/response-limit.js` (voir ci-dessous). Le critère
|
||||
est le mécanisme, pas le nombre d'appelants.
|
||||
|
||||
**Périmètre étendu (lot 5, 25/08/2026).** Trois familles d'outils dépassaient
|
||||
encore le seuil, toutes mesurées sur `LIMAGRAI2512` :
|
||||
@@ -484,6 +487,41 @@ restent complètes dans chaque tranche ; seul `data` est fenêtré, et
|
||||
la seule différence avec l'ancienne réponse est ces trois champs de fenêtre
|
||||
(+65 caractères mesurés).
|
||||
|
||||
`query_wms_entities`, `call_query_api` et `search_wms_data` **plafonnent leur
|
||||
volume** (`MAX_QUERY_RESPONSE_CHARS`, défaut 25 000 — même ordre de grandeur que
|
||||
`MAX_LOG_SEARCH_CHARS`) en écartant des **lignes entières**, via le helper
|
||||
commun `src/services/response-limit.js` (recherche dichotomique : ~8
|
||||
constructions au lieu de 200 retraits ligne à ligne sur des charges utiles de
|
||||
~1 Mo) :
|
||||
|
||||
| Appel | Avant | Après |
|
||||
|---|---:|---:|
|
||||
| `query_wms_entities("Products", limit: 200)` | 957 234 | 24 432 (5 lignes sur 200) |
|
||||
| `search_wms_data("PAL")` | 847 543 | 22 992 (4 résultats sur 150) |
|
||||
| `call_query_api("Products", query_type: 1, limit: 1)` | 95 288 | 738 |
|
||||
|
||||
Trois points de cadrage, tous vérifiés en exécution :
|
||||
|
||||
- **Sous le plafond, rien ne change.** Aucun champ ajouté, réponse identique
|
||||
**octet pour octet** (mesuré sur `query_wms_entities("Container", limit: 1)`,
|
||||
`call_query_api`, `get_entity_schema`, `search_wms_data` sous plafond).
|
||||
`MAX_QUERY_ROWS` et les limites par défaut des outils sont inchangés :
|
||||
le correctif est le bornage signalé, pas une réduction silencieuse.
|
||||
- **Cas limite : une seule ligne dépasse le plafond.** Réel en Writing —
|
||||
`call_query_api("Products", query_type: 1, limit: 1)` répond `returned: 0`,
|
||||
`omitted: 1`, `truncated: true`, avec un hint qui explique le volume Writing
|
||||
et renvoie vers Reading. C'est moins bon qu'un résultat, mais c'est mieux
|
||||
qu'un rejet client opaque.
|
||||
- **`search_wms_data` répartit en tourniquet** les résultats gardés entre les
|
||||
entités, et porte `returned`/`omitted` par entité en plus des totaux. Sans
|
||||
cela, une entité volumineuse placée en tête consommerait tout le budget et
|
||||
les suivantes reviendraient à zéro résultat sans que rien ne le dise —
|
||||
exactement le faux négatif que corrige L5.4.
|
||||
|
||||
`count_wms_entities` n'est pas concerné (`QueryScalarExecute` renvoie un
|
||||
scalaire), et son `query_type` ne porte donc pas l'avertissement de volume
|
||||
ajouté aux deux autres.
|
||||
|
||||
Deux garde-fous de cadrage :
|
||||
|
||||
- **Ne pas réduire les défauts existants** (`max_results` 50, `context_lines` 2)
|
||||
|
||||
@@ -96,14 +96,6 @@ rejet client (~70 000 caractères, D24) :
|
||||
| `get_workflow_details(CST_SendRejectContainersToPK)` | ~101 800 |
|
||||
| `call_query_api("Products", query_type: 1, limit: 1)` — **une seule ligne** Writing | **95 288** |
|
||||
|
||||
### L5.2 — Garde de taille sur les outils de requête
|
||||
|
||||
Une ligne Writing = un agrégat complet sérialisé (95 288 caractères là où la
|
||||
même ligne Reading en fait ~4 500) ; 200 lignes Reading = ~957 000 ; `search_wms_data`
|
||||
= ~848 000. Garde de taille commune sur `query_wms_entities`, `call_query_api`
|
||||
et `search_wms_data` : lignes entières écartées, `truncated`/`returned`/`omitted`/`hint`
|
||||
(D24). Ne pas réduire `MAX_QUERY_ROWS` ni les limites par défaut.
|
||||
|
||||
### L5.3 — Champ `tool` absent des erreurs construites localement
|
||||
|
||||
Les `catch` locaux d'`api-tools.js` renvoient `{ success: false, error }` sans
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Response Limit
|
||||
* Garde de taille commune aux trois outils de requête (D24, lot 5).
|
||||
*
|
||||
* Mesures du 25/08/2026 sur `LIMAGRAI2512`, toutes au-dessus du seuil de rejet
|
||||
* du client MCP (~70 000 caractères) : 957 234 caractères pour 200 lignes
|
||||
* Reading, 847 543 pour `search_wms_data("PAL")`, et 95 288 pour **une seule**
|
||||
* ligne Writing — le modèle Writing sérialise l'agrégat complet (navigations,
|
||||
* `$id`…) là où la même ligne Reading fait ~4 500.
|
||||
*
|
||||
* Contrairement aux mécanismes de `get_system_parameters` et `search_logs`
|
||||
* (locaux car différents, D24), les trois outils de requête partagent le même
|
||||
* mécanisme — d'où ce module : on écarte des **lignes entières**, jamais
|
||||
* coupées au milieu.
|
||||
*/
|
||||
|
||||
const DEFAULT_MAX_RESPONSE_CHARS = 25000;
|
||||
|
||||
/**
|
||||
* Plafond en caractères d'une réponse d'outil de requête.
|
||||
* Même ordre de grandeur que `MAX_LOG_SEARCH_CHARS` (D24).
|
||||
*/
|
||||
function getMaxResponseChars() {
|
||||
return parseInt(process.env.MAX_QUERY_RESPONSE_CHARS) || DEFAULT_MAX_RESPONSE_CHARS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Avertissement de volume propre aux contextes non-Reading (D25). Repris tel
|
||||
* quel dans les hints et dans la description du paramètre `query_type`.
|
||||
*/
|
||||
const WRITING_VOLUME_NOTE =
|
||||
'En query_type != 0, une ligne est un agrégat complet sérialisé (navigations, $id…) : ' +
|
||||
'95 288 caractères mesurés pour UNE seule ligne Products en Writing, contre ~4 500 en Reading. ' +
|
||||
'Repassez en query_type: 0 si le modèle Reading suffit.';
|
||||
|
||||
/**
|
||||
* Trouve le plus grand nombre d'éléments dont la réponse tient sous le plafond.
|
||||
*
|
||||
* @param {number} total - nombre d'éléments disponibles
|
||||
* @param {(kept: number) => string} buildText - construit la réponse sérialisée
|
||||
* pour `kept` éléments. Doit être croissante en `kept` et porter
|
||||
* elle-même les champs de troncature quand `kept < total`.
|
||||
* @returns {{ text: string, kept: number, truncated: boolean, cap: number }}
|
||||
*/
|
||||
function fitToCap(total, buildText) {
|
||||
const cap = getMaxResponseChars();
|
||||
|
||||
const full = buildText(total);
|
||||
if (full.length <= cap) {
|
||||
return { text: full, kept: total, truncated: false, cap };
|
||||
}
|
||||
|
||||
// Recherche dichotomique : ~8 constructions pour 200 lignes, là où un retrait
|
||||
// ligne à ligne en ferait 200 sur des charges utiles de ~1 Mo.
|
||||
let lo = 0;
|
||||
let hi = total - 1;
|
||||
let best = -1;
|
||||
let bestText = null;
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
const text = buildText(mid);
|
||||
if (text.length <= cap) {
|
||||
best = mid;
|
||||
bestText = text;
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Cas limite réel en Writing : une seule ligne dépasse déjà le plafond. On
|
||||
// renvoie l'enveloppe vide et signalée — moins bon qu'un résultat, mais mieux
|
||||
// qu'un rejet client opaque.
|
||||
if (best < 0) {
|
||||
best = 0;
|
||||
bestText = buildText(0);
|
||||
}
|
||||
|
||||
return { text: bestText, kept: best, truncated: true, cap };
|
||||
}
|
||||
|
||||
/**
|
||||
* Champs de troncature communs aux outils de requête — vocabulaire D24 exact
|
||||
* (`truncated`, `returned`, `omitted`, `hint`). Le total avant la coupe est
|
||||
* ajouté par l'appelant : `query_wms_entities` et `search_wms_data` le portent
|
||||
* déjà (`count`, `totalFound`), `call_query_api` non.
|
||||
*
|
||||
* @param {number} returned - éléments effectivement renvoyés
|
||||
* @param {number} total - éléments disponibles avant la coupe
|
||||
* @param {number} queryType - QueryContextType de l'appel (D25)
|
||||
* @param {string} unit - nom de l'unité écartée, au singulier ('ligne', 'résultat')
|
||||
* @param {boolean} [feminine] - accord du hint sur `unit` ('ligne' est féminin)
|
||||
* @param {string} [extraHint] - phrase supplémentaire propre à l'outil
|
||||
*/
|
||||
function truncationSignal({ returned, total, queryType = 0, unit, feminine = false, extraHint }) {
|
||||
const cap = getMaxResponseChars();
|
||||
const omitted = total - returned;
|
||||
const plafond = `Plafond de taille de réponse atteint (${cap} caractères, MAX_QUERY_RESPONSE_CHARS)`;
|
||||
const e = feminine ? 'e' : '';
|
||||
const aucun = feminine ? 'Aucune' : 'Aucun';
|
||||
const unSeul = feminine ? 'une seule' : 'un seul';
|
||||
const entiers = feminine ? 'entières' : 'entiers';
|
||||
|
||||
// Cas limite réel en Writing : même une seule ligne dépasse le plafond.
|
||||
let hint = returned === 0
|
||||
? `${plafond} : ${aucun} ${unit} ne tient dans la réponse — ${unSeul} ${unit} dépasse déjà le plafond ` +
|
||||
`à ${feminine ? 'elle' : 'lui'} seul${e}. Restreignez la requête (filter plus étroit, autre entité) : ` +
|
||||
`le contenu n'est pas coupé au milieu, il est écarté en entier.`
|
||||
: `${plafond} : ${returned} ${unit}(s) renvoyé${e}(s) sur ${total}, ${omitted} écarté${e}(s) — des ` +
|
||||
`${unit}s ${entiers}, jamais coupé${e}s au milieu. Réduisez limit ou ajoutez un filter pour cibler.`;
|
||||
|
||||
if (extraHint) hint += ` ${extraHint}`;
|
||||
if (queryType) hint += ` ${WRITING_VOLUME_NOTE}`;
|
||||
|
||||
return { truncated: true, returned, omitted, hint };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getMaxResponseChars,
|
||||
fitToCap,
|
||||
truncationSignal,
|
||||
WRITING_VOLUME_NOTE,
|
||||
DEFAULT_MAX_RESPONSE_CHARS,
|
||||
};
|
||||
+32
-14
@@ -1,6 +1,7 @@
|
||||
const apiService = require('../services/api-service').getInstance();
|
||||
const entityResolver = require('../services/entity-resolver');
|
||||
const { assertValidQueryType } = require('../services/wms-query-service');
|
||||
const { fitToCap, truncationSignal } = require('../services/response-limit');
|
||||
|
||||
/**
|
||||
* Tools MCP pour interagir avec les APIs WMS
|
||||
@@ -39,7 +40,7 @@ function listTools() {
|
||||
},
|
||||
query_type: {
|
||||
type: 'number',
|
||||
description: 'QueryContextType (défaut: 0 = Reading — statuts en chaînes, à garder sauf raison explicite). Opt-in : 1 = Writing (statuts en ÉNUMÉRATIONS — les comparaisons de chaînes comme == "Release" ÉCHOUENT), 2 = DataWarehouse (souvent non configuré), 3 = Metrics (modèle de données distinct). En query_type != 0, un nom d\'entité inconnu du Metadata Reading est transmis tel quel avec un warning.',
|
||||
description: 'QueryContextType (défaut: 0 = Reading — statuts en chaînes, à garder sauf raison explicite). Opt-in : 1 = Writing (statuts en ÉNUMÉRATIONS — les comparaisons de chaînes comme == "Release" ÉCHOUENT), 2 = DataWarehouse (souvent non configuré), 3 = Metrics (modèle de données distinct). En query_type != 0, un nom d\'entité inconnu du Metadata Reading est transmis tel quel avec un warning. ATTENTION VOLUME : une ligne Writing est un agrégat complet sérialisé — 95 288 caractères mesurés pour UNE ligne Products, contre ~4 500 en Reading. La réponse est plafonnée (MAX_QUERY_RESPONSE_CHARS) et les lignes en trop sont écartées avec un signal truncated.',
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
@@ -123,25 +124,42 @@ async function callQueryAPI(args) {
|
||||
queryType,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(
|
||||
{
|
||||
const head = {
|
||||
success: true,
|
||||
entityType: entity_type,
|
||||
resolvedTableName: tableName,
|
||||
...(warning ? { warning } : {}),
|
||||
...(queryType !== 0 ? { queryType } : {}),
|
||||
result,
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Garde de taille (D24) : une SEULE ligne Writing faisait 95 288 caractères
|
||||
// — le modèle Writing sérialise l'agrégat complet. On écarte des lignes
|
||||
// entières ; sous le plafond, la réponse est strictement celle d'avant.
|
||||
if (!Array.isArray(result)) {
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify({ ...head, result }, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
const buildText = (kept) => {
|
||||
const payload = { ...head };
|
||||
if (kept < result.length) {
|
||||
// Cet outil ne porte pas de champ de total : on l'ajoute (D24).
|
||||
payload.totalRows = result.length;
|
||||
Object.assign(payload, truncationSignal({
|
||||
returned: kept,
|
||||
total: result.length,
|
||||
queryType,
|
||||
unit: 'ligne',
|
||||
feminine: true,
|
||||
}));
|
||||
}
|
||||
payload.result = result.slice(0, kept);
|
||||
return JSON.stringify(payload, null, 2);
|
||||
};
|
||||
|
||||
const { text } = fitToCap(result.length, buildText);
|
||||
return { content: [{ type: 'text', text }] };
|
||||
} catch (err) {
|
||||
return {
|
||||
content: [
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
const wmsQueryService = require('../services/wms-query-service');
|
||||
const { fitToCap, truncationSignal } = require('../services/response-limit');
|
||||
|
||||
/**
|
||||
* List available WMS query tools
|
||||
@@ -46,7 +47,7 @@ Never guess enum string values — they differ between Reading and Writing model
|
||||
},
|
||||
query_type: {
|
||||
type: 'number',
|
||||
description: 'QueryContextType (default: 0 = Reading — status fields are strings, keep it unless you know why). Opt-in: 1 = Writing (status fields become ENUMS — string comparisons like == "Release" FAIL), 2 = DataWarehouse (often not configured), 3 = Metrics (different data model). Entity names unknown to the Reading metadata are passed through as-is with a warning when query_type != 0.',
|
||||
description: 'QueryContextType (default: 0 = Reading — status fields are strings, keep it unless you know why). Opt-in: 1 = Writing (status fields become ENUMS — string comparisons like == "Release" FAIL), 2 = DataWarehouse (often not configured), 3 = Metrics (different data model). Entity names unknown to the Reading metadata are passed through as-is with a warning when query_type != 0. VOLUME WARNING: a Writing row is a full serialised aggregate (navigations, $id…) — 95 288 characters measured for ONE Products row, against ~4 500 in Reading. The response is capped (MAX_QUERY_RESPONSE_CHARS) and excess rows are dropped whole, with a truncated signal.',
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
@@ -177,6 +178,10 @@ async function executeTool(name, args) {
|
||||
|
||||
/**
|
||||
* Tool: query_wms_entities
|
||||
*
|
||||
* Garde de taille (D24) : 200 lignes Reading faisaient 957 234 caractères, au
|
||||
* delà du seuil de rejet du client MCP. On écarte des lignes ENTIÈRES depuis la
|
||||
* fin ; sous le plafond, la réponse est strictement celle d'avant.
|
||||
*/
|
||||
async function queryWmsEntities(args) {
|
||||
const { entity_type, select_expression = 'z => z', filter, limit = 100, query_type = 0 } = args;
|
||||
@@ -191,15 +196,34 @@ async function queryWmsEntities(args) {
|
||||
query_type
|
||||
);
|
||||
|
||||
const { data, ...head } = result;
|
||||
|
||||
// Une réponse non tabulaire (forme inattendue) ne se borne pas par lignes.
|
||||
if (!Array.isArray(data)) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
...result
|
||||
}, null, 2)
|
||||
}]
|
||||
content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }]
|
||||
};
|
||||
}
|
||||
|
||||
// `count` (dans head) porte déjà le total avant la coupe — c'est le total
|
||||
// exigé par D24, inutile d'en ajouter un second.
|
||||
const buildText = (kept) => {
|
||||
const payload = { success: true, ...head };
|
||||
if (kept < data.length) {
|
||||
Object.assign(payload, truncationSignal({
|
||||
returned: kept,
|
||||
total: data.length,
|
||||
queryType: query_type,
|
||||
unit: 'ligne',
|
||||
feminine: true,
|
||||
}));
|
||||
}
|
||||
payload.data = data.slice(0, kept);
|
||||
return JSON.stringify(payload, null, 2);
|
||||
};
|
||||
|
||||
const { text } = fitToCap(data.length, buildText);
|
||||
return { content: [{ type: 'text', text }] };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,17 +296,52 @@ async function searchWmsData(args) {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
keyword,
|
||||
totalFound,
|
||||
results
|
||||
}, null, 2)
|
||||
}]
|
||||
// Garde de taille (D24) : search_wms_data("PAL") faisait 847 543 caractères.
|
||||
// L'unité écartée est un RÉSULTAT entier ; les résultats gardés sont répartis
|
||||
// en tourniquet entre les entités, pour qu'une entité volumineuse placée en
|
||||
// tête n'efface pas silencieusement les suivantes — c'est exactement le
|
||||
// faux négatif que L5.4 corrige par ailleurs.
|
||||
const entityKeys = Object.keys(results).filter(k => Array.isArray(results[k].data));
|
||||
const slots = [];
|
||||
const maxRows = entityKeys.reduce((m, k) => Math.max(m, results[k].data.length), 0);
|
||||
for (let i = 0; i < maxRows; i++) {
|
||||
for (const k of entityKeys) {
|
||||
if (i < results[k].data.length) slots.push(k);
|
||||
}
|
||||
}
|
||||
|
||||
const buildText = (kept) => {
|
||||
const keepCount = {};
|
||||
entityKeys.forEach(k => { keepCount[k] = 0; });
|
||||
for (let i = 0; i < kept; i++) keepCount[slots[i]]++;
|
||||
|
||||
const payload = { success: true, keyword, totalFound };
|
||||
if (kept < slots.length) {
|
||||
Object.assign(payload, truncationSignal({
|
||||
returned: kept,
|
||||
total: slots.length,
|
||||
unit: 'résultat',
|
||||
extraHint: 'Les résultats gardés sont répartis entre les entités : voir returned/omitted par entité. ' +
|
||||
'Relancez query_wms_entities entité par entité avec un filter plus précis pour voir le reste.',
|
||||
}));
|
||||
}
|
||||
|
||||
payload.results = {};
|
||||
for (const [k, v] of Object.entries(results)) {
|
||||
if (!Array.isArray(v.data)) {
|
||||
payload.results[k] = v; // entité en erreur : { error, count }, déjà minuscule
|
||||
continue;
|
||||
}
|
||||
const keptRows = v.data.slice(0, keepCount[k]);
|
||||
payload.results[k] = keptRows.length < v.data.length
|
||||
? { count: v.count, returned: keptRows.length, omitted: v.data.length - keptRows.length, data: keptRows }
|
||||
: { count: v.count, data: keptRows };
|
||||
}
|
||||
return JSON.stringify(payload, null, 2);
|
||||
};
|
||||
|
||||
const { text } = fitToCap(slots.length, buildText);
|
||||
return { content: [{ type: 'text', text }] };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
Reference in New Issue
Block a user