@jsonstudio/llms 0.6.375 → 0.6.467
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/dist/conversion/codecs/gemini-openai-codec.js +15 -1
- package/dist/conversion/compat/actions/iflow-web-search.d.ts +18 -0
- package/dist/conversion/compat/actions/iflow-web-search.js +87 -0
- package/dist/conversion/compat/profiles/chat-glm.json +4 -0
- package/dist/conversion/compat/profiles/chat-iflow.json +5 -1
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +5 -1
- package/dist/conversion/hub/pipeline/session-identifiers.d.ts +9 -0
- package/dist/conversion/hub/pipeline/session-identifiers.js +76 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +31 -2
- package/dist/conversion/hub/process/chat-process.js +89 -25
- package/dist/conversion/responses/responses-openai-bridge.js +75 -4
- package/dist/conversion/shared/anthropic-message-utils.js +41 -6
- package/dist/conversion/shared/errors.d.ts +20 -0
- package/dist/conversion/shared/errors.js +28 -0
- package/dist/conversion/shared/responses-conversation-store.js +30 -3
- package/dist/conversion/shared/responses-output-builder.js +68 -6
- package/dist/filters/special/request-toolcalls-stringify.d.ts +13 -0
- package/dist/filters/special/request-toolcalls-stringify.js +103 -3
- package/dist/filters/special/response-tool-text-canonicalize.d.ts +16 -0
- package/dist/filters/special/response-tool-text-canonicalize.js +27 -3
- package/dist/router/virtual-router/classifier.js +4 -2
- package/dist/router/virtual-router/engine.d.ts +30 -0
- package/dist/router/virtual-router/engine.js +600 -42
- package/dist/router/virtual-router/provider-registry.d.ts +15 -0
- package/dist/router/virtual-router/provider-registry.js +40 -0
- package/dist/router/virtual-router/routing-instructions.d.ts +34 -0
- package/dist/router/virtual-router/routing-instructions.js +383 -0
- package/dist/router/virtual-router/sticky-session-store.d.ts +3 -0
- package/dist/router/virtual-router/sticky-session-store.js +110 -0
- package/dist/router/virtual-router/tool-signals.js +0 -22
- package/dist/router/virtual-router/types.d.ts +35 -0
- package/dist/servertool/engine.js +42 -1
- package/dist/servertool/handlers/web-search.js +157 -4
- package/dist/servertool/types.d.ts +6 -0
- package/package.json +1 -1
|
@@ -41,12 +41,53 @@ export async function runServerToolOrchestration(options) {
|
|
|
41
41
|
const followupBody = followup.body && typeof followup.body === 'object'
|
|
42
42
|
? followup.body
|
|
43
43
|
: engineResult.finalChatResponse;
|
|
44
|
+
const decorated = decorateFinalChatWithServerToolContext(followupBody, engineResult.execution);
|
|
44
45
|
return {
|
|
45
|
-
chat:
|
|
46
|
+
chat: decorated,
|
|
46
47
|
executed: true,
|
|
47
48
|
flowId: engineResult.execution.flowId
|
|
48
49
|
};
|
|
49
50
|
}
|
|
51
|
+
function decorateFinalChatWithServerToolContext(chat, execution) {
|
|
52
|
+
if (!execution || !execution.context) {
|
|
53
|
+
return chat;
|
|
54
|
+
}
|
|
55
|
+
// 目前仅对 web_search flow 附加原文摘要,避免影响其它 ServerTool。
|
|
56
|
+
if (execution.flowId !== 'web_search_flow') {
|
|
57
|
+
return chat;
|
|
58
|
+
}
|
|
59
|
+
const ctx = execution.context;
|
|
60
|
+
const web = ctx.web_search;
|
|
61
|
+
const summary = web && typeof web.summary === 'string' && web.summary.trim().length
|
|
62
|
+
? web.summary.trim()
|
|
63
|
+
: '';
|
|
64
|
+
if (!summary) {
|
|
65
|
+
return chat;
|
|
66
|
+
}
|
|
67
|
+
const engineId = web && typeof web.engineId === 'string' && web.engineId.trim().length
|
|
68
|
+
? web.engineId.trim()
|
|
69
|
+
: undefined;
|
|
70
|
+
const label = engineId
|
|
71
|
+
? `【web_search 原文 | engine: ${engineId}】`
|
|
72
|
+
: '【web_search 原文】';
|
|
73
|
+
const cloned = JSON.parse(JSON.stringify(chat));
|
|
74
|
+
const choices = Array.isArray(cloned.choices) ? cloned.choices : [];
|
|
75
|
+
if (!choices.length) {
|
|
76
|
+
return cloned;
|
|
77
|
+
}
|
|
78
|
+
const first = choices[0] && typeof choices[0] === 'object' ? choices[0] : null;
|
|
79
|
+
if (!first || !first.message || typeof first.message !== 'object') {
|
|
80
|
+
return cloned;
|
|
81
|
+
}
|
|
82
|
+
const message = first.message;
|
|
83
|
+
const baseContent = typeof message.content === 'string' ? message.content : '';
|
|
84
|
+
const suffix = `${label}\n${summary}`;
|
|
85
|
+
message.content =
|
|
86
|
+
baseContent && baseContent.trim().length
|
|
87
|
+
? `${baseContent}\n\n${suffix}`
|
|
88
|
+
: suffix;
|
|
89
|
+
return cloned;
|
|
90
|
+
}
|
|
50
91
|
function resolveRouteHint(adapterContext, flowId) {
|
|
51
92
|
const rawRoute = adapterContext.routeId;
|
|
52
93
|
const routeId = typeof rawRoute === 'string' && rawRoute.trim() ? rawRoute.trim() : '';
|
|
@@ -62,7 +62,14 @@ const handler = async (ctx) => {
|
|
|
62
62
|
payload: followupPayload,
|
|
63
63
|
metadata: buildFollowupMetadata(ctx.adapterContext, 'web_search')
|
|
64
64
|
}
|
|
65
|
-
: undefined
|
|
65
|
+
: undefined,
|
|
66
|
+
context: {
|
|
67
|
+
web_search: {
|
|
68
|
+
engineId: chosenEngine.id,
|
|
69
|
+
providerKey: chosenEngine.providerKey,
|
|
70
|
+
summary: chosenResult.summary
|
|
71
|
+
}
|
|
72
|
+
}
|
|
66
73
|
};
|
|
67
74
|
return {
|
|
68
75
|
chatResponse: patched,
|
|
@@ -89,7 +96,9 @@ function getWebSearchConfig(ctx) {
|
|
|
89
96
|
const enginesRaw = Array.isArray(record.engines) ? record.engines : [];
|
|
90
97
|
const engines = [];
|
|
91
98
|
for (const entry of enginesRaw) {
|
|
92
|
-
const obj = entry && typeof entry === 'object' && !Array.isArray(entry)
|
|
99
|
+
const obj = entry && typeof entry === 'object' && !Array.isArray(entry)
|
|
100
|
+
? entry
|
|
101
|
+
: null;
|
|
93
102
|
if (!obj)
|
|
94
103
|
continue;
|
|
95
104
|
const id = typeof obj.id === 'string' && obj.id.trim() ? obj.id.trim() : undefined;
|
|
@@ -104,12 +113,26 @@ function getWebSearchConfig(ctx) {
|
|
|
104
113
|
(obj.serverTools &&
|
|
105
114
|
typeof obj.serverTools === 'object' &&
|
|
106
115
|
obj.serverTools.enabled === false);
|
|
116
|
+
let searchEngineList;
|
|
117
|
+
const rawSearchList = obj.searchEngineList;
|
|
118
|
+
if (Array.isArray(rawSearchList)) {
|
|
119
|
+
const normalizedList = [];
|
|
120
|
+
for (const item of rawSearchList) {
|
|
121
|
+
if (typeof item === 'string' && item.trim().length) {
|
|
122
|
+
normalizedList.push(item.trim());
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (normalizedList.length) {
|
|
126
|
+
searchEngineList = normalizedList;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
107
129
|
engines.push({
|
|
108
130
|
id,
|
|
109
131
|
providerKey,
|
|
110
132
|
description: typeof obj.description === 'string' && obj.description.trim() ? obj.description.trim() : undefined,
|
|
111
133
|
default: obj.default === true,
|
|
112
|
-
...(serverToolsDisabled ? { serverToolsDisabled: true } : {})
|
|
134
|
+
...(serverToolsDisabled ? { serverToolsDisabled: true } : {}),
|
|
135
|
+
...(searchEngineList ? { searchEngineList } : {})
|
|
113
136
|
});
|
|
114
137
|
}
|
|
115
138
|
if (!engines.length) {
|
|
@@ -179,6 +202,10 @@ function isGeminiWebSearchEngine(engine) {
|
|
|
179
202
|
key.startsWith('antigravity.') ||
|
|
180
203
|
key.startsWith('gemini.'));
|
|
181
204
|
}
|
|
205
|
+
function isIflowWebSearchEngine(engine) {
|
|
206
|
+
const key = engine.providerKey.toLowerCase();
|
|
207
|
+
return key.startsWith('iflow.');
|
|
208
|
+
}
|
|
182
209
|
function normalizeResultCount(value) {
|
|
183
210
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
184
211
|
const normalized = Math.trunc(value);
|
|
@@ -197,7 +224,22 @@ async function executeWebSearchBackend(args) {
|
|
|
197
224
|
try {
|
|
198
225
|
logServerToolWebSearch(engine, ctx.options.requestId, query);
|
|
199
226
|
const requestSuffix = `:web_search:${engine.id}`;
|
|
200
|
-
|
|
227
|
+
// 对于 iFlow,直接通过 providerInvoker 调用 /chat/retrieve,
|
|
228
|
+
// 即使 reenterPipeline 可用,也不走 Chat 模型 + tools。
|
|
229
|
+
if (isIflowWebSearchEngine(engine) && ctx.options.providerInvoker) {
|
|
230
|
+
const backendResult = await executeIflowWebSearchViaProvider({
|
|
231
|
+
ctx,
|
|
232
|
+
engine,
|
|
233
|
+
query,
|
|
234
|
+
recency,
|
|
235
|
+
count: args.resultCount,
|
|
236
|
+
requestSuffix
|
|
237
|
+
});
|
|
238
|
+
summary = backendResult.summary;
|
|
239
|
+
hits = backendResult.hits;
|
|
240
|
+
ok = backendResult.ok;
|
|
241
|
+
}
|
|
242
|
+
else if (ctx.options.reenterPipeline) {
|
|
201
243
|
const payload = buildWebSearchReenterPayload(engine, query, recency, args.resultCount);
|
|
202
244
|
const followup = await ctx.options.reenterPipeline({
|
|
203
245
|
entryEndpoint: '/v1/chat/completions',
|
|
@@ -384,6 +426,117 @@ async function executeWebSearchViaProvider(args) {
|
|
|
384
426
|
}
|
|
385
427
|
return extractTextFromChatLike(providerResponse);
|
|
386
428
|
}
|
|
429
|
+
async function executeIflowWebSearchViaProvider(args) {
|
|
430
|
+
const { ctx, engine, query, count, requestSuffix } = args;
|
|
431
|
+
if (!ctx.options.providerInvoker) {
|
|
432
|
+
return {
|
|
433
|
+
summary: '',
|
|
434
|
+
hits: [],
|
|
435
|
+
ok: false
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
const searchEngineList = Array.isArray(engine.searchEngineList) && engine.searchEngineList.length
|
|
439
|
+
? engine.searchEngineList
|
|
440
|
+
: ['GOOGLE', 'BING', 'SCHOLAR', 'AIPGC', 'PDF'];
|
|
441
|
+
const searchBody = {
|
|
442
|
+
query,
|
|
443
|
+
history: {},
|
|
444
|
+
userId: 2,
|
|
445
|
+
userIp: '42.120.74.197',
|
|
446
|
+
appCode: 'SEARCH_CHATBOT',
|
|
447
|
+
chatId: Date.now(),
|
|
448
|
+
phase: 'UNIFY',
|
|
449
|
+
enableQueryRewrite: false,
|
|
450
|
+
enableRetrievalSecurity: false,
|
|
451
|
+
enableIntention: false,
|
|
452
|
+
searchEngineList
|
|
453
|
+
};
|
|
454
|
+
let providerKey = engine.providerKey;
|
|
455
|
+
try {
|
|
456
|
+
const adapter = ctx.adapterContext && typeof ctx.adapterContext === 'object'
|
|
457
|
+
? ctx.adapterContext
|
|
458
|
+
: null;
|
|
459
|
+
const target = adapter && adapter.target && typeof adapter.target === 'object'
|
|
460
|
+
? adapter.target
|
|
461
|
+
: null;
|
|
462
|
+
const targetProviderKey = target && typeof target.providerKey === 'string' && target.providerKey.trim()
|
|
463
|
+
? target.providerKey.trim()
|
|
464
|
+
: undefined;
|
|
465
|
+
if (targetProviderKey) {
|
|
466
|
+
providerKey = targetProviderKey;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
// best-effort: fallback to engine.providerKey
|
|
471
|
+
}
|
|
472
|
+
const payload = {
|
|
473
|
+
data: searchBody,
|
|
474
|
+
metadata: {
|
|
475
|
+
entryEndpoint: '/chat/retrieve',
|
|
476
|
+
iflowWebSearch: true,
|
|
477
|
+
routeName: 'web_search'
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
const backend = await ctx.options.providerInvoker({
|
|
481
|
+
providerKey,
|
|
482
|
+
providerType: undefined,
|
|
483
|
+
modelId: undefined,
|
|
484
|
+
providerProtocol: ctx.options.providerProtocol,
|
|
485
|
+
payload,
|
|
486
|
+
entryEndpoint: '/v1/chat/retrieve',
|
|
487
|
+
requestId: `${ctx.options.requestId}${requestSuffix}`,
|
|
488
|
+
routeHint: 'web_search'
|
|
489
|
+
});
|
|
490
|
+
const providerResponse = backend.providerResponse && typeof backend.providerResponse === 'object'
|
|
491
|
+
? backend.providerResponse
|
|
492
|
+
: null;
|
|
493
|
+
if (!providerResponse) {
|
|
494
|
+
return {
|
|
495
|
+
summary: '',
|
|
496
|
+
hits: [],
|
|
497
|
+
ok: false
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
const container = providerResponse;
|
|
501
|
+
const rawHits = Array.isArray(container.data) ? container.data : [];
|
|
502
|
+
const hits = [];
|
|
503
|
+
for (const item of rawHits) {
|
|
504
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
505
|
+
continue;
|
|
506
|
+
const record = item;
|
|
507
|
+
const link = typeof record.url === 'string' && record.url.trim() ? record.url.trim() : '';
|
|
508
|
+
if (!link)
|
|
509
|
+
continue;
|
|
510
|
+
const title = typeof record.title === 'string' && record.title.trim() ? record.title.trim() : undefined;
|
|
511
|
+
const publishDate = typeof record.time === 'string' && record.time.trim() ? record.time.trim() : undefined;
|
|
512
|
+
const content = typeof record.abstractInfo === 'string' && record.abstractInfo.trim()
|
|
513
|
+
? record.abstractInfo.trim()
|
|
514
|
+
: undefined;
|
|
515
|
+
hits.push({
|
|
516
|
+
title,
|
|
517
|
+
link,
|
|
518
|
+
publish_date: publishDate,
|
|
519
|
+
content
|
|
520
|
+
});
|
|
521
|
+
if (hits.length >= count) {
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
let summary = '';
|
|
526
|
+
if (typeof container.message === 'string' && container.message.trim()) {
|
|
527
|
+
summary = container.message.trim();
|
|
528
|
+
}
|
|
529
|
+
if (!summary && hits.length) {
|
|
530
|
+
summary = formatHitsSummary(hits);
|
|
531
|
+
}
|
|
532
|
+
const successField = container.success;
|
|
533
|
+
const ok = typeof successField === 'boolean' ? successField : hits.length > 0;
|
|
534
|
+
return {
|
|
535
|
+
summary,
|
|
536
|
+
hits,
|
|
537
|
+
ok
|
|
538
|
+
};
|
|
539
|
+
}
|
|
387
540
|
function injectWebSearchToolResult(base, toolCall, engine, query, backendResult) {
|
|
388
541
|
const cloned = cloneJson(base);
|
|
389
542
|
const existingOutputs = Array.isArray(cloned.tool_outputs)
|
|
@@ -57,6 +57,12 @@ export interface ServerToolFollowupPlan {
|
|
|
57
57
|
export interface ServerToolExecution {
|
|
58
58
|
flowId: string;
|
|
59
59
|
followup?: ServerToolFollowupPlan;
|
|
60
|
+
/**
|
|
61
|
+
* Optional tool-specific context for the execution result.
|
|
62
|
+
* For example, web_search handler may attach { web_search: { engineId, providerKey, summary } }
|
|
63
|
+
* so that orchestration layer can decorate final Chat response without touching host code.
|
|
64
|
+
*/
|
|
65
|
+
context?: JsonObject;
|
|
60
66
|
}
|
|
61
67
|
/**
|
|
62
68
|
* ServerSideToolEngineResult:ServerTool 引擎出参。
|