@mcp-abap-adt/llm-agent-server-libs 29.0.0 → 30.1.0
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/factories/controller-factory.d.ts +2 -2
- package/dist/factories/controller-factory.d.ts.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pipelines/server-context.d.ts +2 -2
- package/dist/pipelines/server-context.d.ts.map +1 -1
- package/dist/smart-agent/config-reload-watcher.d.ts +7 -0
- package/dist/smart-agent/config-reload-watcher.d.ts.map +1 -1
- package/dist/smart-agent/config-reload-watcher.js +23 -6
- package/dist/smart-agent/config-reload-watcher.js.map +1 -1
- package/dist/smart-agent/config-validator.d.ts.map +1 -1
- package/dist/smart-agent/config-validator.js +198 -6
- package/dist/smart-agent/config-validator.js.map +1 -1
- package/dist/smart-agent/config.d.ts.map +1 -1
- package/dist/smart-agent/config.js +9 -1
- package/dist/smart-agent/config.js.map +1 -1
- package/dist/smart-agent/controller/controller-coordinator-handler.d.ts +2 -2
- package/dist/smart-agent/controller/controller-coordinator-handler.d.ts.map +1 -1
- package/dist/smart-agent/controller/recall.d.ts +4 -2
- package/dist/smart-agent/controller/recall.d.ts.map +1 -1
- package/dist/smart-agent/controller/recall.js +5 -3
- package/dist/smart-agent/controller/recall.js.map +1 -1
- package/dist/smart-agent/controller/target-state.d.ts +2 -2
- package/dist/smart-agent/controller/target-state.d.ts.map +1 -1
- package/dist/smart-agent/controller/target-state.js +2 -2
- package/dist/smart-agent/controller/target-state.js.map +1 -1
- package/dist/smart-agent/decision-config.d.ts +32 -0
- package/dist/smart-agent/decision-config.d.ts.map +1 -0
- package/dist/smart-agent/decision-config.js +18 -0
- package/dist/smart-agent/decision-config.js.map +1 -0
- package/dist/smart-agent/embedder-knowledge-index.d.ts +4 -2
- package/dist/smart-agent/embedder-knowledge-index.d.ts.map +1 -1
- package/dist/smart-agent/embedder-knowledge-index.js +5 -3
- package/dist/smart-agent/embedder-knowledge-index.js.map +1 -1
- package/dist/smart-agent/http/adapter-route-handler.d.ts +1 -1
- package/dist/smart-agent/http/adapter-route-handler.d.ts.map +1 -1
- package/dist/smart-agent/http/adapter-route-handler.js +34 -3
- package/dist/smart-agent/http/adapter-route-handler.js.map +1 -1
- package/dist/smart-agent/http/chat-route-handler.d.ts.map +1 -1
- package/dist/smart-agent/http/chat-route-handler.js +35 -4
- package/dist/smart-agent/http/chat-route-handler.js.map +1 -1
- package/dist/smart-agent/http/config-route-handler.d.ts +6 -3
- package/dist/smart-agent/http/config-route-handler.d.ts.map +1 -1
- package/dist/smart-agent/http/config-route-handler.js +7 -5
- package/dist/smart-agent/http/config-route-handler.js.map +1 -1
- package/dist/smart-agent/http/request-abort.d.ts +11 -0
- package/dist/smart-agent/http/request-abort.d.ts.map +1 -0
- package/dist/smart-agent/http/request-abort.js +19 -0
- package/dist/smart-agent/http/request-abort.js.map +1 -0
- package/dist/smart-agent/knowledge/make-knowledge-backend.d.ts +3 -2
- package/dist/smart-agent/knowledge/make-knowledge-backend.d.ts.map +1 -1
- package/dist/smart-agent/knowledge/make-knowledge-backend.js.map +1 -1
- package/dist/smart-agent/llm/llm-circuit-breakers.d.ts +21 -0
- package/dist/smart-agent/llm/llm-circuit-breakers.d.ts.map +1 -0
- package/dist/smart-agent/llm/llm-circuit-breakers.js +43 -0
- package/dist/smart-agent/llm/llm-circuit-breakers.js.map +1 -0
- package/dist/smart-agent/llm/role-llm-resolver.d.ts +3 -0
- package/dist/smart-agent/llm/role-llm-resolver.d.ts.map +1 -1
- package/dist/smart-agent/llm/role-llm-resolver.js +4 -1
- package/dist/smart-agent/llm/role-llm-resolver.js.map +1 -1
- package/dist/smart-agent/rag-config.d.ts +21 -6
- package/dist/smart-agent/rag-config.d.ts.map +1 -1
- package/dist/smart-agent/rag-config.js +2 -2
- package/dist/smart-agent/rag-config.js.map +1 -1
- package/dist/smart-agent/resolve-agent-embedder.d.ts +18 -1
- package/dist/smart-agent/resolve-agent-embedder.d.ts.map +1 -1
- package/dist/smart-agent/resolve-agent-embedder.js +37 -0
- package/dist/smart-agent/resolve-agent-embedder.js.map +1 -1
- package/dist/smart-agent/resolve-config-sections.d.ts +7 -0
- package/dist/smart-agent/resolve-config-sections.d.ts.map +1 -1
- package/dist/smart-agent/resolve-config-sections.js +65 -0
- package/dist/smart-agent/resolve-config-sections.js.map +1 -1
- package/dist/smart-agent/resolve-reranker.d.ts +10 -0
- package/dist/smart-agent/resolve-reranker.d.ts.map +1 -0
- package/dist/smart-agent/resolve-reranker.js +8 -0
- package/dist/smart-agent/resolve-reranker.js.map +1 -0
- package/dist/smart-agent/resolve-retrieval.d.ts +26 -0
- package/dist/smart-agent/resolve-retrieval.d.ts.map +1 -0
- package/dist/smart-agent/resolve-retrieval.js +96 -0
- package/dist/smart-agent/resolve-retrieval.js.map +1 -0
- package/dist/smart-agent/skill-host-embedder-resolver.d.ts +17 -0
- package/dist/smart-agent/skill-host-embedder-resolver.d.ts.map +1 -0
- package/dist/smart-agent/skill-host-embedder-resolver.js +21 -0
- package/dist/smart-agent/skill-host-embedder-resolver.js.map +1 -0
- package/dist/smart-agent/skill-plugins-config.d.ts +7 -5
- package/dist/smart-agent/skill-plugins-config.d.ts.map +1 -1
- package/dist/smart-agent/skill-plugins-config.js +18 -0
- package/dist/smart-agent/skill-plugins-config.js.map +1 -1
- package/dist/smart-agent/skill-plugins-host-factory.d.ts +4 -2
- package/dist/smart-agent/skill-plugins-host-factory.d.ts.map +1 -1
- package/dist/smart-agent/skill-plugins-host-factory.js +4 -1
- package/dist/smart-agent/skill-plugins-host-factory.js.map +1 -1
- package/dist/smart-agent/smart-server.d.ts +76 -6
- package/dist/smart-agent/smart-server.d.ts.map +1 -1
- package/dist/smart-agent/smart-server.js +249 -82
- package/dist/smart-agent/smart-server.js.map +1 -1
- package/dist/smart-agent/tools-rag-handle.d.ts +2 -2
- package/dist/smart-agent/tools-rag-handle.d.ts.map +1 -1
- package/dist/smart-agent/tools-rag-handle.js +4 -2
- package/dist/smart-agent/tools-rag-handle.js.map +1 -1
- package/package.json +7 -7
|
@@ -6,8 +6,8 @@ import http from 'node:http';
|
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
7
|
import { resolve as pathResolve } from 'node:path';
|
|
8
8
|
import { pathToFileURL } from 'node:url';
|
|
9
|
-
import { buildNamespacedTools, defaultToolNamespace, isReadinessReporter, SimpleRagProviderRegistry, } from '@mcp-abap-adt/llm-agent';
|
|
10
|
-
import { ClaudeSkillManager, CodexSkillManager, FileSystemPluginLoader, FileSystemSkillManager, getDefaultPluginDirs, HealthChecker, InMemoryKnowledgeBackend, KnowledgeRag, mergePluginExports, SessionRequestLogger, SmartAgentBuilder, SmartAgentSubAgent, WindowContextStrategy, } from '@mcp-abap-adt/llm-agent-libs';
|
|
9
|
+
import { asymmetricEmbedder, buildNamespacedTools, CircuitBreaker, defaultToolNamespace, isReadinessReporter, SimpleRagProviderRegistry, symmetricEmbedder, withCircuitBreaker, } from '@mcp-abap-adt/llm-agent';
|
|
10
|
+
import { applyRetrievalStrategy, ClaudeSkillManager, CodexSkillManager, FileSystemPluginLoader, FileSystemSkillManager, getDefaultPluginDirs, HealthChecker, InMemoryKnowledgeBackend, KnowledgeRag, mergePluginExports, SessionRequestLogger, SmartAgentBuilder, SmartAgentSubAgent, WindowContextStrategy, } from '@mcp-abap-adt/llm-agent-libs';
|
|
11
11
|
import { DefaultMcpFailureClassifier, MCPClientWrapper, McpClientAdapter, } from '@mcp-abap-adt/llm-agent-mcp';
|
|
12
12
|
import { prefetchEmbedderFactories } from '@mcp-abap-adt/llm-agent-rag';
|
|
13
13
|
import { PACKAGE_VERSION } from '../generated/version.js';
|
|
@@ -21,9 +21,12 @@ import { CORS_HEADERS, jsonError, writeNotReady, } from './http/response-helpers
|
|
|
21
21
|
import { HttpRouteTable } from './http/route-table.js';
|
|
22
22
|
import { handleSessionDelete, handleSessionResume, handleSessionsList, } from './http/sessions-route-handler.js';
|
|
23
23
|
import { handleUsageRoute } from './http/usage-route-handler.js';
|
|
24
|
+
import { LlmCircuitBreakers } from './llm/llm-circuit-breakers.js';
|
|
24
25
|
import { RoleLlmResolver, } from './llm/role-llm-resolver.js';
|
|
25
26
|
import { assertRagConfigShape, embedderSectionFor, toMakeRagInput, } from './rag-config.js';
|
|
26
|
-
import {
|
|
27
|
+
import { resolveRetrievalEmbedder } from './resolve-agent-embedder.js';
|
|
28
|
+
import { resolveReranker } from './resolve-reranker.js';
|
|
29
|
+
import { resolveRetrievalStrategies, unknownRetrievalKeyWarnings, } from './resolve-retrieval.js';
|
|
27
30
|
import { makeToolsRagHandle } from './tools-rag-handle.js';
|
|
28
31
|
import { assertWorkerLlmConfig, parseWorkerLlm } from './worker-llm.js';
|
|
29
32
|
export { writeNotReady } from './http/response-helpers.js';
|
|
@@ -236,6 +239,39 @@ export class SmartServer {
|
|
|
236
239
|
_mainLlm;
|
|
237
240
|
_classifierLlm;
|
|
238
241
|
_helperLlm;
|
|
242
|
+
/**
|
|
243
|
+
* `circuitBreaker:` — one LLM breaker per `llm:` key, wrapped where each LLM
|
|
244
|
+
* is created (held main/classifier/helper and every resolver entry), so every
|
|
245
|
+
* session and role — controller and stepper included — shares it (§14.2).
|
|
246
|
+
*/
|
|
247
|
+
_llmBreakers;
|
|
248
|
+
/** `circuitBreaker:` — the one embedder breaker, fed by the retrieval embedder. */
|
|
249
|
+
_embedderBreaker;
|
|
250
|
+
/** The one reranker of this server, resolved once in `_buildInfra()` (§7.4). */
|
|
251
|
+
_reranker;
|
|
252
|
+
/**
|
|
253
|
+
* `rag.retrieval` resolved once in `_buildInfra` — one strategy per listed
|
|
254
|
+
* store key, server-wide (§13.4). Applied at creation of the server-built
|
|
255
|
+
* stores (`tools` / `history`, workers' too) and, through
|
|
256
|
+
* `withRetrievalStrategy`, in every builder's `ragStores` projection.
|
|
257
|
+
*/
|
|
258
|
+
_retrievalStrategies = new Map();
|
|
259
|
+
/** `agent.toolSelection`, resolved once; applied to every builder (§13.4). */
|
|
260
|
+
_toolSelectionStrategy;
|
|
261
|
+
/** Plugin output validator, resolved once; applied to every builder (§14.1). */
|
|
262
|
+
_outputValidator;
|
|
263
|
+
/** Plugin query expander, resolved once; applied to every builder (§14.1). */
|
|
264
|
+
_queryExpander;
|
|
265
|
+
/** DI > plugin > YAML `skills:`, resolved once; vectorized at startup only. */
|
|
266
|
+
_skillManager;
|
|
267
|
+
/**
|
|
268
|
+
* `agent.llmCallStrategy`, resolved once as a FACTORY: a strategy may be
|
|
269
|
+
* stateful (`fallback` disables streaming stickily), so each builder gets its
|
|
270
|
+
* own instance (§14.1).
|
|
271
|
+
*/
|
|
272
|
+
_llmCallStrategyFactory;
|
|
273
|
+
/** DI > plugin > default `ClineClientAdapter`, resolved once (§14.1). */
|
|
274
|
+
_clientAdapters = [];
|
|
239
275
|
_fileLogger;
|
|
240
276
|
_mergedEmbedderFactories;
|
|
241
277
|
/**
|
|
@@ -276,6 +312,12 @@ export class SmartServer {
|
|
|
276
312
|
* (the stepper catalog handle), which falls back to catalog order regardless.
|
|
277
313
|
*/
|
|
278
314
|
_toolsRag;
|
|
315
|
+
/**
|
|
316
|
+
* The strategy-wrapped history store, shared by the startup agent and every
|
|
317
|
+
* session agent (one store; reads are scoped per session by metadata).
|
|
318
|
+
* Unset when `rag:` is not configured.
|
|
319
|
+
*/
|
|
320
|
+
_historyRag;
|
|
279
321
|
/**
|
|
280
322
|
* The providers every build of this server creates through, and the catalogs
|
|
281
323
|
* a session registry is hydrated from. One object, handed to every build:
|
|
@@ -453,6 +495,9 @@ export class SmartServer {
|
|
|
453
495
|
...(deps.connectMcpWithDescriptors
|
|
454
496
|
? { connectMcpWithDescriptors: deps.connectMcpWithDescriptors }
|
|
455
497
|
: {}),
|
|
498
|
+
...(deps.makeDecisionModel
|
|
499
|
+
? { makeDecisionModel: deps.makeDecisionModel }
|
|
500
|
+
: {}),
|
|
456
501
|
};
|
|
457
502
|
}
|
|
458
503
|
async start() {
|
|
@@ -526,17 +571,25 @@ export class SmartServer {
|
|
|
526
571
|
temperature: optionalNumber(helperCfg.temperature),
|
|
527
572
|
})
|
|
528
573
|
: undefined;
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
this.
|
|
574
|
+
// `circuitBreaker:` → the shared breakers (§14.2): created once, before any
|
|
575
|
+
// LLM is held, and wrapped where each LLM is created — never in a builder.
|
|
576
|
+
if (this.cfg.circuitBreaker) {
|
|
577
|
+
this._llmBreakers = new LlmCircuitBreakers(this.cfg.circuitBreaker);
|
|
578
|
+
this._embedderBreaker = new CircuitBreaker(this.cfg.circuitBreaker);
|
|
579
|
+
}
|
|
580
|
+
this._mainLlm = this.guardLlm(mainLlm, 'main');
|
|
581
|
+
this._classifierLlm = this.guardLlm(classifierLlm, 'classifier');
|
|
582
|
+
this._helperLlm = helperLlm && this.guardLlm(helperLlm, 'helper');
|
|
532
583
|
this._llmMap = llmMap;
|
|
533
584
|
this._mainTemp = mainTemp;
|
|
585
|
+
const breakers = this._llmBreakers;
|
|
534
586
|
this._roleLlm = new RoleLlmResolver({
|
|
535
587
|
getMain: () => this._mainLlm,
|
|
536
588
|
getHelper: () => this._helperLlm,
|
|
537
589
|
getClassifier: () => this._classifierLlm,
|
|
538
590
|
getLlmMap: () => this._llmMap,
|
|
539
591
|
build: (entry) => this._deps.makeLlm(entry),
|
|
592
|
+
...(breakers ? { wrap: (llm, key) => breakers.wrap(llm, key) } : {}),
|
|
540
593
|
});
|
|
541
594
|
// The programmatic subAgentConfigs path never passed the YAML check, so a
|
|
542
595
|
// worker's named keys are checked here too (§4.6.7): startup fails loudly.
|
|
@@ -588,6 +641,54 @@ export class SmartServer {
|
|
|
588
641
|
const registered = mergePluginExports(plugins, mod, spec);
|
|
589
642
|
log({ event: 'plugin_specifier_loaded', spec, registered });
|
|
590
643
|
}
|
|
644
|
+
// ---- Reranker (§7.4) -------------------------------------------------
|
|
645
|
+
// Resolved ONCE here — the infra build shared by start() and the embeddable
|
|
646
|
+
// buildAgent() — and applied by buildBaseBuilder outside the
|
|
647
|
+
// applyServerExtras gate, so per-session agents get it too.
|
|
648
|
+
this._reranker = await resolveReranker({
|
|
649
|
+
pluginReranker: plugins.reranker,
|
|
650
|
+
});
|
|
651
|
+
// ---- Validator, expander, skills, LLM-call strategy, adapters (§14.1) --
|
|
652
|
+
// Resolved ONCE here and applied by buildBaseBuilder to every builder
|
|
653
|
+
// (per-session agents serve the requests).
|
|
654
|
+
this._outputValidator = plugins.outputValidator;
|
|
655
|
+
this._queryExpander = plugins.queryExpander;
|
|
656
|
+
this._skillManager =
|
|
657
|
+
this.cfg.skillManager ??
|
|
658
|
+
plugins.skillManager ??
|
|
659
|
+
resolveSkillManager(this.cfg.skills);
|
|
660
|
+
const strategyName = this.cfg.agent?.llmCallStrategy;
|
|
661
|
+
if (strategyName) {
|
|
662
|
+
const { StreamingLlmCallStrategy, NonStreamingLlmCallStrategy, FallbackLlmCallStrategy, } = await import('@mcp-abap-adt/llm-agent');
|
|
663
|
+
const strategies = {
|
|
664
|
+
streaming: () => new StreamingLlmCallStrategy(),
|
|
665
|
+
'non-streaming': () => new NonStreamingLlmCallStrategy(),
|
|
666
|
+
fallback: () => new FallbackLlmCallStrategy(this._fileLogger),
|
|
667
|
+
};
|
|
668
|
+
this._llmCallStrategyFactory = strategies[strategyName];
|
|
669
|
+
}
|
|
670
|
+
const { ClineClientAdapter } = await import('@mcp-abap-adt/llm-agent');
|
|
671
|
+
this._clientAdapters = [
|
|
672
|
+
...(this.cfg.clientAdapters ?? []),
|
|
673
|
+
...plugins.clientAdapters,
|
|
674
|
+
new ClineClientAdapter(),
|
|
675
|
+
];
|
|
676
|
+
// ---- Per-store retrieval strategies (§13.4) ---------------------------
|
|
677
|
+
// Resolved ONCE, server-wide. An explicit per-store strategy (embedding
|
|
678
|
+
// included) wins over the plugin reranker above; an unlisted store keeps
|
|
679
|
+
// today's behaviour. Decision-backed rerankers share one decision model.
|
|
680
|
+
this._retrievalStrategies = await resolveRetrievalStrategies({
|
|
681
|
+
retrieval: this.cfg.rag?.retrieval,
|
|
682
|
+
decisionCfg: this.cfg.decision,
|
|
683
|
+
makeDecisionModel: this._deps.makeDecisionModel,
|
|
684
|
+
resolveLlm: (key) => this.roleLlm().resolveNamed(key),
|
|
685
|
+
});
|
|
686
|
+
const toolSelectionCfg = this.cfg.agent?.toolSelection;
|
|
687
|
+
this._toolSelectionStrategy = toolSelectionCfg?.strategy
|
|
688
|
+
? resolveToolSelectionStrategy(toolSelectionCfg.strategy, {
|
|
689
|
+
minScore: toolSelectionCfg.minScore,
|
|
690
|
+
})
|
|
691
|
+
: undefined;
|
|
591
692
|
// ---- Pipeline-plugin registry: factories (§4.6.7) -------------------
|
|
592
693
|
// Built-ins are server code — parse, validate, construct with typed settings.
|
|
593
694
|
// A dynamic instance export is registered as a factory that ignores its
|
|
@@ -653,7 +754,13 @@ export class SmartServer {
|
|
|
653
754
|
});
|
|
654
755
|
// Resolve the embedder ONCE so the same instance feeds both makeRag and the
|
|
655
756
|
// subagent context-builder's toolSource (#137). See resolve-agent-embedder.
|
|
656
|
-
|
|
757
|
+
// ONE retrieval embedder: stores write with its `embedDocument`, every
|
|
758
|
+
// search path embeds with its `embedQuery` (an asymmetric model is two
|
|
759
|
+
// halves behind it — see resolveRetrievalEmbedder).
|
|
760
|
+
const resolvedEmbedder = await resolveRetrievalEmbedder(this.cfg.rag, this._deps.embedder ?? this.cfg.embedder, this._deps.resolveEmbedder, mergedEmbedderFactories, this._fileLogger,
|
|
761
|
+
// The embedder breaker sees the real embedding calls (§14.2): each half
|
|
762
|
+
// is wrapped below the document/query role. Worker embedders keep none.
|
|
763
|
+
this.embedderBreakerWrap());
|
|
657
764
|
// Hold the resolved embedder so buildServerCtx can thread it onto every
|
|
658
765
|
// pipeline context (the controller pipeline needs it for target-state).
|
|
659
766
|
this._resolvedEmbedder = resolvedEmbedder;
|
|
@@ -684,9 +791,29 @@ export class SmartServer {
|
|
|
684
791
|
const buildHost = this._deps.skillHost
|
|
685
792
|
? async () => this._deps.skillHost
|
|
686
793
|
: () => this._deps.buildSkillHost(skillCfg, {
|
|
687
|
-
resolveEmbedder: (ec) =>
|
|
688
|
-
|
|
689
|
-
|
|
794
|
+
resolveEmbedder: (ec) => {
|
|
795
|
+
// The agent's retrieval embedder (built from the DI instance
|
|
796
|
+
// when one was injected) serves the skills too.
|
|
797
|
+
if (reuseAgentEmbedder) {
|
|
798
|
+
if (!resolvedEmbedder) {
|
|
799
|
+
throw new Error('skillPlugins: reusing the agent embedder, but none was resolved');
|
|
800
|
+
}
|
|
801
|
+
return resolvedEmbedder;
|
|
802
|
+
}
|
|
803
|
+
const section = embedderSectionFor(ec.embedder, ec.model);
|
|
804
|
+
const build = (sec) => this._deps.resolveEmbedder(sec, {
|
|
805
|
+
extraFactories: mergedEmbedderFactories,
|
|
806
|
+
});
|
|
807
|
+
return ec.asymmetric && section.factory === undefined
|
|
808
|
+
? asymmetricEmbedder({
|
|
809
|
+
document: build({
|
|
810
|
+
...section,
|
|
811
|
+
inputType: 'document',
|
|
812
|
+
}),
|
|
813
|
+
query: build({ ...section, inputType: 'query' }),
|
|
814
|
+
})
|
|
815
|
+
: symmetricEmbedder(build(section));
|
|
816
|
+
},
|
|
690
817
|
// Real pg `Pool` provider for a `postgres` catalog (qdrant
|
|
691
818
|
// deployment). Lazily imports `pg` and ensures the catalog table
|
|
692
819
|
// exists on first use; pass the configured table so the DDL targets
|
|
@@ -722,12 +849,13 @@ export class SmartServer {
|
|
|
722
849
|
// through its own. Two calls, two stores — the history store never shared
|
|
723
850
|
// the tools store's instance.
|
|
724
851
|
const input = toMakeRagInput(this.cfg.rag.store, resolvedEmbedder, 'rag');
|
|
725
|
-
toolsRag = await this._deps.makeRag(input);
|
|
726
|
-
historyRag = await this._deps.makeRag(input);
|
|
852
|
+
toolsRag = this.withStrategy('tools', await this._deps.makeRag(input));
|
|
853
|
+
historyRag = this.withStrategy('history', await this._deps.makeRag(input));
|
|
727
854
|
}
|
|
728
855
|
// Capture the tools store for the flat/smart pipeline's ToolSelectHandler
|
|
729
856
|
// (and white-box vectorization assertions). See field doc.
|
|
730
857
|
this._toolsRag = toolsRag;
|
|
858
|
+
this._historyRag = historyRag;
|
|
731
859
|
// NOTE: the legacy per-pipeline named-RAG multistore (`pipeline.rag.{name}`)
|
|
732
860
|
// is GONE with the `pipeline: {name,config}` migration. The top-level `rag:`
|
|
733
861
|
// block above is the single source of truth for the tools/history stores.
|
|
@@ -846,9 +974,10 @@ export class SmartServer {
|
|
|
846
974
|
// Assemble everything EXCEPT the coordinator via the shared base-builder
|
|
847
975
|
// factory; the coordinator gate below wires the chosen variant.
|
|
848
976
|
const builder = await this.buildBaseBuilder({
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
977
|
+
// The held (breaker-guarded) instances: the builder never wraps an LLM.
|
|
978
|
+
mainLlm: this._mainLlm,
|
|
979
|
+
classifierLlm: this._classifierLlm,
|
|
980
|
+
helperLlm: this._helperLlm,
|
|
852
981
|
fileLogger,
|
|
853
982
|
toolsRag,
|
|
854
983
|
historyRag,
|
|
@@ -875,6 +1004,9 @@ export class SmartServer {
|
|
|
875
1004
|
const { agent: smartAgent, chat, streamChat, close: closeAgent, circuitBreakers, ragStores, modelProvider, } = agentHandle;
|
|
876
1005
|
const { ragRegistry: globalRagRegistry, mcpClients: globalMcpClients } = agentHandle;
|
|
877
1006
|
this._globalRagRegistry = globalRagRegistry;
|
|
1007
|
+
for (const w of unknownRetrievalKeyWarnings(this.cfg.rag?.retrieval, globalRagRegistry.list().map((c) => c.name))) {
|
|
1008
|
+
this.warn(w);
|
|
1009
|
+
}
|
|
878
1010
|
// Two limits of this server, stated rather than fixed (§6.4): it registers
|
|
879
1011
|
// no RAG providers, so no session registry has a catalog to hydrate from —
|
|
880
1012
|
// each holds only the deployment's globals; and its sessions carry a
|
|
@@ -1018,7 +1150,8 @@ export class SmartServer {
|
|
|
1018
1150
|
agent: smartAgent,
|
|
1019
1151
|
startTime,
|
|
1020
1152
|
version: this.cfg.version ?? PACKAGE_VERSION,
|
|
1021
|
-
|
|
1153
|
+
// Re-read per check: a PUT /v1/config swap replaces a key's breaker.
|
|
1154
|
+
circuitBreakers: this.breakerList() ?? circuitBreakers,
|
|
1022
1155
|
});
|
|
1023
1156
|
// Startup health check removed — use `npm run models:check` for diagnostics.
|
|
1024
1157
|
// Running health check at startup wastes rate-limit budget when combined
|
|
@@ -1238,11 +1371,13 @@ export class SmartServer {
|
|
|
1238
1371
|
cache: this._workers.cache,
|
|
1239
1372
|
// Worker-OWN tools RAG (from subCfg.rag, if declared). Built once;
|
|
1240
1373
|
// re-wired per-session by reference — never re-vectorized.
|
|
1374
|
+
// rag.retrieval is server-wide: the worker's stores take the MAIN
|
|
1375
|
+
// config's strategy for their key (§13.4).
|
|
1241
1376
|
makeToolsRag: subCfg.rag
|
|
1242
|
-
? async () => this._deps.makeRag(await this._workerRagInput(name, subCfg.rag, subCfg.embedder, embedderFactories))
|
|
1377
|
+
? async () => this.withStrategy('tools', await this._deps.makeRag(await this._workerRagInput(name, subCfg.rag, subCfg.embedder, embedderFactories)))
|
|
1243
1378
|
: undefined,
|
|
1244
1379
|
makeHistoryRag: subCfg.rag
|
|
1245
|
-
? async () => this._deps.makeRag(await this._workerRagInput(name, subCfg.rag, subCfg.embedder, embedderFactories))
|
|
1380
|
+
? async () => this.withStrategy('history', await this._deps.makeRag(await this._workerRagInput(name, subCfg.rag, subCfg.embedder, embedderFactories)))
|
|
1246
1381
|
: undefined,
|
|
1247
1382
|
// Worker-OWN MCP clients. DI list (subCfg.mcpClients) wins; otherwise
|
|
1248
1383
|
// SmartAgentBuilder's own MCP-connect path handles `subCfg.mcp` — we
|
|
@@ -1304,6 +1439,12 @@ export class SmartServer {
|
|
|
1304
1439
|
else if (injected?.mcpClients && injected.mcpClients.length > 0) {
|
|
1305
1440
|
subBuilder = subBuilder.withMcpClients(injected.mcpClients);
|
|
1306
1441
|
}
|
|
1442
|
+
// rag.retrieval is server-wide: a worker's projection (named collections
|
|
1443
|
+
// of the shared registry included) gets the same per-store strategies.
|
|
1444
|
+
// Its own tools/history are already wrapped; the brand keeps them as is.
|
|
1445
|
+
for (const [key, strategy] of this._retrievalStrategies) {
|
|
1446
|
+
subBuilder = subBuilder.withRetrievalStrategy(key, strategy);
|
|
1447
|
+
}
|
|
1307
1448
|
const handle = await subBuilder.build();
|
|
1308
1449
|
// Backfill the per-worker cache from the BUILT handle (review HIGH #7).
|
|
1309
1450
|
// Only runs on the primary build path (no `injected`) so per-session
|
|
@@ -1318,9 +1459,19 @@ export class SmartServer {
|
|
|
1318
1459
|
}
|
|
1319
1460
|
// -- Pipeline-context dep sources (promoted from the inline coordinator-gate
|
|
1320
1461
|
// closures; consumed by buildServerCtx, which later tasks call) ----------
|
|
1462
|
+
/**
|
|
1463
|
+
* A server-built store (`tools` / `history`, main or worker) wrapped in the
|
|
1464
|
+
* strategy `rag.retrieval` configures for its key; unlisted → unchanged.
|
|
1465
|
+
* Idempotent (`applyRetrievalStrategy`), so the builder's projection of the
|
|
1466
|
+
* same store never wraps it twice — one rerank per query.
|
|
1467
|
+
*/
|
|
1468
|
+
withStrategy(key, store) {
|
|
1469
|
+
const strategy = this._retrievalStrategies.get(key);
|
|
1470
|
+
return strategy ? applyRetrievalStrategy(store, strategy) : store;
|
|
1471
|
+
}
|
|
1321
1472
|
/** A worker's own store input: its embedder through the seam, then paired. */
|
|
1322
1473
|
async _workerRagInput(name, rag, diEmbedder, extraFactories) {
|
|
1323
|
-
const embedder = await
|
|
1474
|
+
const embedder = await resolveRetrievalEmbedder(rag, diEmbedder, this._deps.resolveEmbedder, extraFactories, this._fileLogger);
|
|
1324
1475
|
return toMakeRagInput(rag.store, embedder, `subagent '${name}' rag`);
|
|
1325
1476
|
}
|
|
1326
1477
|
/** `ctx.resolveLlm(role)` — the role's default, through the held role map. */
|
|
@@ -1338,6 +1489,23 @@ export class SmartServer {
|
|
|
1338
1489
|
? this.roleLlm().resolveNamed(key)
|
|
1339
1490
|
: this.roleLlm().resolve(role);
|
|
1340
1491
|
}
|
|
1492
|
+
/** `llm` behind its key's circuit breaker when `circuitBreaker:` is set. */
|
|
1493
|
+
guardLlm(llm, key) {
|
|
1494
|
+
return this._llmBreakers ? this._llmBreakers.wrap(llm, key) : llm;
|
|
1495
|
+
}
|
|
1496
|
+
/** The `wrap` argument of the main `resolveRetrievalEmbedder` call, if any. */
|
|
1497
|
+
embedderBreakerWrap() {
|
|
1498
|
+
const breaker = this._embedderBreaker;
|
|
1499
|
+
return breaker ? (e) => withCircuitBreaker(e, breaker) : undefined;
|
|
1500
|
+
}
|
|
1501
|
+
/** `/health`'s breakers: every key's current LLM breaker, then the embedder's. */
|
|
1502
|
+
breakerList() {
|
|
1503
|
+
const llm = this._llmBreakers;
|
|
1504
|
+
const embedder = this._embedderBreaker;
|
|
1505
|
+
if (!llm || !embedder)
|
|
1506
|
+
return undefined;
|
|
1507
|
+
return () => [...llm.list(), embedder];
|
|
1508
|
+
}
|
|
1341
1509
|
roleLlm() {
|
|
1342
1510
|
if (!this._roleLlm) {
|
|
1343
1511
|
throw new Error('role LLM lookup invoked before _buildInfra built the resolver');
|
|
@@ -1750,11 +1918,14 @@ export class SmartServer {
|
|
|
1750
1918
|
* supplies its own scope's values (startup = global; session = session-scoped);
|
|
1751
1919
|
* every `.withXxx` is applied conditionally on its `parts` field so both work.
|
|
1752
1920
|
*
|
|
1753
|
-
* `applyServerExtras`
|
|
1754
|
-
* (
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
1921
|
+
* `applyServerExtras` now guards only the YAML `mcp:` auto-connect fallback,
|
|
1922
|
+
* which stays startup-only (one connection). It also decides whether the skill
|
|
1923
|
+
* manager vectorizes its skills into the tools store (startup build only).
|
|
1924
|
+
* Not gated, because per-session agents serve the requests: the reranker
|
|
1925
|
+
* (§7.4), the per-store retrieval strategies and `agent.toolSelection`
|
|
1926
|
+
* (§13.4), the shared embedder circuit breaker (§14.2), and the output
|
|
1927
|
+
* validator, query expander, skill manager, LLM-call strategy and client
|
|
1928
|
+
* adapters (§14.1).
|
|
1758
1929
|
*/
|
|
1759
1930
|
async buildBaseBuilder(parts) {
|
|
1760
1931
|
let builder = new SmartAgentBuilder({
|
|
@@ -1797,64 +1968,55 @@ export class SmartServer {
|
|
|
1797
1968
|
if (parts.requestLogger) {
|
|
1798
1969
|
builder = builder.withRequestLogger(parts.requestLogger);
|
|
1799
1970
|
}
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1971
|
+
// Not gated: requests are served by per-session agents, built with
|
|
1972
|
+
// applyServerExtras=false; the startup agent is infrastructure only.
|
|
1973
|
+
if (this._reranker) {
|
|
1974
|
+
builder = builder.withReranker(this._reranker);
|
|
1975
|
+
}
|
|
1976
|
+
// Not gated, same reason: the per-store strategies reach every store the
|
|
1977
|
+
// pipeline reads through the builder's `ragStores` projection (named
|
|
1978
|
+
// collections included). Server-built stores are already wrapped; the
|
|
1979
|
+
// projection sees the brand and leaves them as they are.
|
|
1980
|
+
for (const [key, strategy] of this._retrievalStrategies) {
|
|
1981
|
+
builder = builder.withRetrievalStrategy(key, strategy);
|
|
1982
|
+
}
|
|
1983
|
+
// Not gated, same reason: `agent.toolSelection` must reach the per-session
|
|
1984
|
+
// agents that serve requests (§13.4). With a reranked `tools` store,
|
|
1985
|
+
// `minScore` compares reranker probabilities in [0, 1], not cosine.
|
|
1986
|
+
if (this._toolSelectionStrategy) {
|
|
1987
|
+
builder = builder.withToolSelectionStrategy(this._toolSelectionStrategy);
|
|
1988
|
+
}
|
|
1989
|
+
// Not gated, same reason: the ONE embedder breaker guards the stores of
|
|
1990
|
+
// every agent (§14.2). The LLMs given above are already breaker-guarded.
|
|
1991
|
+
if (this._embedderBreaker) {
|
|
1992
|
+
builder = builder.withCircuitBreakers({
|
|
1993
|
+
embedder: this._embedderBreaker,
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
// Not gated, same reason: the output validator, query expander, skill
|
|
1997
|
+
// manager, LLM-call strategy and client adapters reach the per-session
|
|
1998
|
+
// agents that serve requests (§14.1). Skills are vectorized into the tools
|
|
1999
|
+
// store by the startup build only.
|
|
2000
|
+
if (this._queryExpander) {
|
|
2001
|
+
builder = builder.withQueryExpander(this._queryExpander);
|
|
2002
|
+
}
|
|
2003
|
+
if (this._outputValidator) {
|
|
2004
|
+
builder = builder.withOutputValidator(this._outputValidator);
|
|
2005
|
+
}
|
|
2006
|
+
if (this._skillManager) {
|
|
2007
|
+
builder = builder.withSkillManager(this._skillManager, {
|
|
2008
|
+
vectorize: parts.applyServerExtras,
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
if (this._llmCallStrategyFactory) {
|
|
2012
|
+
builder = builder.withLlmCallStrategy(this._llmCallStrategyFactory());
|
|
2013
|
+
}
|
|
2014
|
+
for (const adapter of this._clientAdapters) {
|
|
2015
|
+
builder = builder.withClientAdapter(adapter);
|
|
1842
2016
|
}
|
|
1843
2017
|
if (parts.mcpClients) {
|
|
1844
2018
|
builder = builder.withMcpClients(parts.mcpClients);
|
|
1845
2019
|
}
|
|
1846
|
-
if (parts.applyServerExtras) {
|
|
1847
|
-
// Client adapters (DI > plugin; ClineClientAdapter is the default).
|
|
1848
|
-
const { ClineClientAdapter } = await import('@mcp-abap-adt/llm-agent');
|
|
1849
|
-
const adapterSources = [
|
|
1850
|
-
...(this.cfg.clientAdapters ?? []),
|
|
1851
|
-
...(parts.plugins?.clientAdapters ?? []),
|
|
1852
|
-
new ClineClientAdapter(),
|
|
1853
|
-
];
|
|
1854
|
-
for (const adapter of adapterSources) {
|
|
1855
|
-
builder = builder.withClientAdapter(adapter);
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
2020
|
if (parts.workerRegistry.size > 0) {
|
|
1859
2021
|
builder = builder.withSubAgents(parts.workerRegistry);
|
|
1860
2022
|
}
|
|
@@ -1909,6 +2071,7 @@ export class SmartServer {
|
|
|
1909
2071
|
const inst = await this.buildPipelineInstance({
|
|
1910
2072
|
sessionId: parts.sessionId,
|
|
1911
2073
|
parts,
|
|
2074
|
+
historyRag: this._historyRag,
|
|
1912
2075
|
});
|
|
1913
2076
|
// Register the pipeline's disposal hook keyed by sessionId. A prior
|
|
1914
2077
|
// instance for the same sessionId (e.g. invalidateAll rebuild) is closed
|
|
@@ -2102,7 +2265,7 @@ export class SmartServer {
|
|
|
2102
2265
|
return;
|
|
2103
2266
|
}
|
|
2104
2267
|
await rc.server._withSession(rc.req, rc.res, async (graph, sessionId, traceId) => {
|
|
2105
|
-
await handleAdapterRequest(rc.req, rc.res, graph.agent ?? rc.smartAgent, anthropicAdapter, { sessionId, traceId, graph }, this.cfg.agent?.heartbeatIntervalMs);
|
|
2268
|
+
await handleAdapterRequest(rc.req, rc.res, graph.agent ?? rc.smartAgent, anthropicAdapter, { sessionId, traceId, graph }, this.cfg.agent?.heartbeatIntervalMs, rc.log);
|
|
2106
2269
|
});
|
|
2107
2270
|
},
|
|
2108
2271
|
});
|
|
@@ -2133,14 +2296,18 @@ export class SmartServer {
|
|
|
2133
2296
|
return {
|
|
2134
2297
|
modelResolver: this.cfg.modelResolver,
|
|
2135
2298
|
skipModelValidation: this.cfg.skipModelValidation === true,
|
|
2299
|
+
// A swapped-in LLM gets a fresh breaker for its key (§14.2).
|
|
2136
2300
|
setMainLlm: (llm) => {
|
|
2137
|
-
this._mainLlm = llm;
|
|
2301
|
+
this._mainLlm = this.guardLlm(llm, 'main');
|
|
2302
|
+
return this._mainLlm;
|
|
2138
2303
|
},
|
|
2139
2304
|
setClassifierLlm: (llm) => {
|
|
2140
|
-
this._classifierLlm = llm;
|
|
2305
|
+
this._classifierLlm = this.guardLlm(llm, 'classifier');
|
|
2306
|
+
return this._classifierLlm;
|
|
2141
2307
|
},
|
|
2142
2308
|
setHelperLlm: (llm) => {
|
|
2143
|
-
this._helperLlm = llm;
|
|
2309
|
+
this._helperLlm = this.guardLlm(llm, 'helper');
|
|
2310
|
+
return this._helperLlm;
|
|
2144
2311
|
},
|
|
2145
2312
|
mirrorAgentCfg: (patch) => {
|
|
2146
2313
|
const merged = {
|