@iola_adm/iola-cli 0.1.101 → 0.1.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.js +108 -6
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -6512,7 +6512,15 @@ async function localToolAsk(question, providerConfig, options) {
|
|
|
6512
6512
|
}
|
|
6513
6513
|
}
|
|
6514
6514
|
const runId = `run-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
6515
|
-
|
|
6515
|
+
let result;
|
|
6516
|
+
try {
|
|
6517
|
+
result = await executeToolPlan(validated, { ...options, runId });
|
|
6518
|
+
} catch (error) {
|
|
6519
|
+
const friendlyError = formatToolExecutionError(error, validated);
|
|
6520
|
+
if (!friendlyError) throw error;
|
|
6521
|
+
if (!options.quiet) console.log(friendlyError);
|
|
6522
|
+
return friendlyError;
|
|
6523
|
+
}
|
|
6516
6524
|
const answer = formatToolResult(result, options);
|
|
6517
6525
|
|
|
6518
6526
|
if (!options["no-history"] && isFeatureEnabled("sqlite-history")) {
|
|
@@ -6686,7 +6694,7 @@ function normalizeIolaRouterPlan(raw, question, options = {}) {
|
|
|
6686
6694
|
if (casualAnswer) return { directAnswer: casualAnswer };
|
|
6687
6695
|
return inferToolPlan(question, options);
|
|
6688
6696
|
}
|
|
6689
|
-
return { steps: [{ tool, args: payload.args || {} }] };
|
|
6697
|
+
return { steps: [{ tool, args: { ...(payload.args || {}), source_question: question } }] };
|
|
6690
6698
|
}
|
|
6691
6699
|
if (payload.action === "direct_answer") {
|
|
6692
6700
|
return { directAnswer: payload.answer || "" };
|
|
@@ -6783,14 +6791,18 @@ async function resolvePublicEntityField(args = {}) {
|
|
|
6783
6791
|
inn: args.inn,
|
|
6784
6792
|
field: requestedField,
|
|
6785
6793
|
must_refute_user_value: args.must_refute_user_value,
|
|
6794
|
+
source_question: args.source_question,
|
|
6786
6795
|
};
|
|
6787
6796
|
try {
|
|
6788
|
-
|
|
6797
|
+
const resolved = await postJson(endpoint, stripInternalResolveArgs(payload));
|
|
6798
|
+
return await correctResolvedEntityByQuestionName(resolved, payload) || resolved;
|
|
6789
6799
|
} catch (error) {
|
|
6790
6800
|
const fallbackField = pickResolveFieldFallback(requestedField, error);
|
|
6791
6801
|
if (fallbackField && fallbackField !== requestedField) {
|
|
6792
6802
|
try {
|
|
6793
|
-
|
|
6803
|
+
const fallbackPayload = { ...payload, field: fallbackField };
|
|
6804
|
+
const resolved = await postJson(endpoint, stripInternalResolveArgs(fallbackPayload));
|
|
6805
|
+
return await correctResolvedEntityByQuestionName(resolved, fallbackPayload) || resolved;
|
|
6794
6806
|
} catch (retryError) {
|
|
6795
6807
|
const resolvedBySearch = await resolvePublicEntityFieldViaSearch({ ...payload, field: fallbackField }, retryError);
|
|
6796
6808
|
if (resolvedBySearch) return resolvedBySearch;
|
|
@@ -6812,12 +6824,84 @@ async function resolvePublicEntityFieldViaSearch(payload, originalError) {
|
|
|
6812
6824
|
const candidates = await searchPublicEntities({ layer: payload.layer, query, limit: 10 });
|
|
6813
6825
|
const candidate = pickResolvedEntityCandidate(candidates, payload);
|
|
6814
6826
|
if (!candidate?.inn) return null;
|
|
6815
|
-
return postJson(`${await getApiBaseUrl()}/resolve-entity-field`, {
|
|
6827
|
+
return postJson(`${await getApiBaseUrl()}/resolve-entity-field`, stripInternalResolveArgs({
|
|
6816
6828
|
layer: payload.layer,
|
|
6817
6829
|
inn: candidate.inn,
|
|
6818
6830
|
field: payload.field,
|
|
6819
6831
|
must_refute_user_value: payload.must_refute_user_value,
|
|
6820
|
-
});
|
|
6832
|
+
}));
|
|
6833
|
+
}
|
|
6834
|
+
|
|
6835
|
+
function stripInternalResolveArgs(payload) {
|
|
6836
|
+
const { source_question: _sourceQuestion, ...publicPayload } = payload || {};
|
|
6837
|
+
return publicPayload;
|
|
6838
|
+
}
|
|
6839
|
+
|
|
6840
|
+
async function correctResolvedEntityByQuestionName(resolved, payload) {
|
|
6841
|
+
const questionNameQuery = extractEntityNameQueryFromQuestion(payload.source_question, payload.layer);
|
|
6842
|
+
if (!questionNameQuery) return null;
|
|
6843
|
+
const resolvedEntity = resolved?.entity || resolved || {};
|
|
6844
|
+
if (entityNameMatchesQuery(resolvedEntity.name, questionNameQuery)) return null;
|
|
6845
|
+
|
|
6846
|
+
const candidates = await searchPublicEntities({ layer: payload.layer, query: questionNameQuery, limit: 5 });
|
|
6847
|
+
const candidate = pickNamedEntityCandidate(candidates, questionNameQuery);
|
|
6848
|
+
if (!candidate?.inn || candidate.inn === resolvedEntity.inn) return null;
|
|
6849
|
+
|
|
6850
|
+
return postJson(`${await getApiBaseUrl()}/resolve-entity-field`, stripInternalResolveArgs({
|
|
6851
|
+
layer: payload.layer,
|
|
6852
|
+
inn: candidate.inn,
|
|
6853
|
+
field: payload.field,
|
|
6854
|
+
must_refute_user_value: payload.must_refute_user_value,
|
|
6855
|
+
}));
|
|
6856
|
+
}
|
|
6857
|
+
|
|
6858
|
+
function extractEntityNameQueryFromQuestion(question, layer) {
|
|
6859
|
+
let text = String(question || "").toLocaleLowerCase("ru-RU");
|
|
6860
|
+
const correction = text.match(/(?:просил|просила|просили)\s+(.+?)\s+а\s+не(?:\s|$)/iu);
|
|
6861
|
+
if (correction?.[1]) text = correction[1];
|
|
6862
|
+
|
|
6863
|
+
const stopWords = new Set([
|
|
6864
|
+
"а", "в", "во", "где", "же", "и", "или", "как", "какая", "какие", "какой", "кто", "на", "не",
|
|
6865
|
+
"найди", "находится", "подскажи", "покажи", "просил", "скажи", "так", "там", "это",
|
|
6866
|
+
"адрес", "директор", "директора", "заведующая", "заведующий", "инн", "почта", "сайт", "телефон",
|
|
6867
|
+
"гимназия", "детсад", "детсада", "детский", "лицей", "мбдоу", "мбоу", "сад", "сада", "садик",
|
|
6868
|
+
"сош", "школа", "школе", "школу", "школы",
|
|
6869
|
+
]);
|
|
6870
|
+
const tokens = [...text.normalize("NFC").matchAll(/[\p{L}\d]+/gu)]
|
|
6871
|
+
.map((match) => normalizeEntityText(match[0]))
|
|
6872
|
+
.filter((token) => token && !stopWords.has(token) && !/^\d+$/.test(token));
|
|
6873
|
+
const uniqueTokens = [...new Set(tokens)];
|
|
6874
|
+
if (uniqueTokens.length === 0) return "";
|
|
6875
|
+
if (uniqueTokens.length === 1 && uniqueTokens[0].length < 5) return "";
|
|
6876
|
+
return uniqueTokens.join(" ");
|
|
6877
|
+
}
|
|
6878
|
+
|
|
6879
|
+
function pickNamedEntityCandidate(candidates, query) {
|
|
6880
|
+
if (!Array.isArray(candidates) || candidates.length === 0) return null;
|
|
6881
|
+
const tokens = entityQueryTokens(query);
|
|
6882
|
+
const exact = candidates.find((item) => entityNameMatchesQuery(item.name, query));
|
|
6883
|
+
if (exact) return exact;
|
|
6884
|
+
if (candidates.length === 1 && Number(candidates[0].score || 0) >= 0.5) return candidates[0];
|
|
6885
|
+
return candidates.find((item) => {
|
|
6886
|
+
const name = normalizeEntityText(item.name || "");
|
|
6887
|
+
return Number(item.score || 0) >= 0.8 && tokens.filter((token) => name.includes(token)).length >= Math.ceil(tokens.length / 2);
|
|
6888
|
+
}) || null;
|
|
6889
|
+
}
|
|
6890
|
+
|
|
6891
|
+
function entityNameMatchesQuery(name, query) {
|
|
6892
|
+
const normalizedName = normalizeEntityText(name || "");
|
|
6893
|
+
const tokens = entityQueryTokens(query);
|
|
6894
|
+
return tokens.length > 0 && tokens.every((token) => normalizedName.includes(token));
|
|
6895
|
+
}
|
|
6896
|
+
|
|
6897
|
+
function entityQueryTokens(query) {
|
|
6898
|
+
return [...String(query || "").matchAll(/[\p{L}\d]+/gu)]
|
|
6899
|
+
.map((match) => normalizeEntityText(match[0]))
|
|
6900
|
+
.filter(Boolean);
|
|
6901
|
+
}
|
|
6902
|
+
|
|
6903
|
+
function normalizeEntityText(text) {
|
|
6904
|
+
return String(text || "").toLocaleLowerCase("ru-RU").replace(/ё/g, "е");
|
|
6821
6905
|
}
|
|
6822
6906
|
|
|
6823
6907
|
function buildEntitySearchQuery(layer, number) {
|
|
@@ -6880,6 +6964,24 @@ function parseErrorJsonDetails(error) {
|
|
|
6880
6964
|
}
|
|
6881
6965
|
}
|
|
6882
6966
|
|
|
6967
|
+
function formatToolExecutionError(error, plan) {
|
|
6968
|
+
const details = parseErrorJsonDetails(error);
|
|
6969
|
+
if (details?.error !== "entity_not_found") return "";
|
|
6970
|
+
|
|
6971
|
+
const step = (plan?.steps || []).find((item) => item.tool === "resolve_entity_field" || item.tool === "search_entities");
|
|
6972
|
+
const args = step?.args || {};
|
|
6973
|
+
const layer = normalizeEntityLayer(args.layer);
|
|
6974
|
+
const number = args.entity_number ?? args.number;
|
|
6975
|
+
const name = args.entity_name || args.name;
|
|
6976
|
+
const entityLabel = layer === "kindergartens" ? "детский сад" : "школу";
|
|
6977
|
+
const selector = number !== undefined && number !== null && number !== ""
|
|
6978
|
+
? `${entityLabel} № ${number}`
|
|
6979
|
+
: name
|
|
6980
|
+
? `${entityLabel} "${name}"`
|
|
6981
|
+
: "такую организацию";
|
|
6982
|
+
return `В открытом слое не нашел ${selector}. Проверьте номер или название.`;
|
|
6983
|
+
}
|
|
6984
|
+
|
|
6883
6985
|
function availableToolNames(options = {}) {
|
|
6884
6986
|
const names = new Set(LOCAL_TOOLS);
|
|
6885
6987
|
for (const tool of getLocalMcpToolNames()) names.add(tool);
|