langflower 0.0.7 → 0.0.9
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/ui-dist/{chunk-Y6VMBLSJ.js → chunk-7UMVYN3A.js} +1 -1
- package/ui-dist/{chunk-6DQ2XSRW.js → chunk-MDGXX73Z.js} +3 -3
- package/ui-dist/index.html +1 -1
- package/ui-dist/main-2JJGCNTD.js +313 -0
- package/vendor/common-nodes/dist/ai/chat-completion-stream.d.ts +2 -0
- package/vendor/common-nodes/dist/ai/critique/node.d.ts +246 -0
- package/vendor/common-nodes/dist/ai/critique/node.js +2 -2
- package/vendor/common-nodes/dist/ai/fake-llm/node.d.ts +246 -0
- package/vendor/common-nodes/dist/ai/features/chat-completion-stream.d.ts +53 -0
- package/vendor/common-nodes/dist/ai/features/chat-completion-stream.js +1 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/autokick-recovery.d.ts +13 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/autokick-recovery.js +17 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/classify-llm-failure.d.ts +2 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/classify-llm-failure.js +113 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/dead-loop-detector.d.ts +28 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/dead-loop-detector.js +194 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/live-get-tools.d.ts +9 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/live-get-tools.js +21 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-reducer.d.ts +2 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-reducer.js +216 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-types.d.ts +125 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-types.js +39 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/normalize-llm-recovery-policy.d.ts +2 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/normalize-llm-recovery-policy.js +59 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/operators/normalize-tool-result.d.ts +1 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/operators/normalize-tool-result.js +7 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/operators/observe-provider-stream.d.ts +43 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/operators/observe-provider-stream.js +96 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/run-agent-loop.d.ts +32 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/run-agent-loop.js +58 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/run-llm-loop.d.ts +80 -0
- package/vendor/common-nodes/dist/ai/features/llm-loop/run-llm-loop.js +681 -0
- package/vendor/common-nodes/dist/ai/features/llm-role-preset.d.ts +69 -0
- package/vendor/common-nodes/dist/ai/features/llm-role-preset.js +265 -0
- package/vendor/common-nodes/dist/ai/features/llm-session/llm-session-shell.d.ts +148 -0
- package/vendor/common-nodes/dist/ai/features/llm-session/llm-session-shell.js +198 -0
- package/vendor/common-nodes/dist/ai/features/llm-session/run-session-machine.d.ts +21 -0
- package/vendor/common-nodes/dist/ai/features/llm-session/run-session-machine.js +118 -0
- package/vendor/common-nodes/dist/ai/features/openai/context-length-error.d.ts +14 -0
- package/vendor/common-nodes/dist/ai/features/openai/context-length-error.js +63 -0
- package/vendor/common-nodes/dist/ai/features/openai/create-chat-completion-stream.d.ts +19 -0
- package/vendor/common-nodes/dist/ai/features/openai/create-chat-completion-stream.js +182 -0
- package/vendor/common-nodes/dist/ai/features/openai/list-provider-models.d.ts +28 -0
- package/vendor/common-nodes/dist/ai/features/openai/list-provider-models.js +52 -0
- package/vendor/common-nodes/dist/ai/features/openai/llm-context-compaction.d.ts +31 -0
- package/vendor/common-nodes/dist/ai/features/openai/llm-context-compaction.js +326 -0
- package/vendor/common-nodes/dist/ai/features/openai/normalize-compaction-params.d.ts +14 -0
- package/vendor/common-nodes/dist/ai/features/openai/normalize-compaction-params.js +21 -0
- package/vendor/common-nodes/dist/ai/features/openai/prepare-chat-completion.d.ts +30 -0
- package/vendor/common-nodes/dist/ai/features/openai/prepare-chat-completion.js +132 -0
- package/vendor/common-nodes/dist/ai/features/path-choice/control-tools.d.ts +20 -0
- package/vendor/common-nodes/dist/ai/features/path-choice/control-tools.js +70 -0
- package/vendor/common-nodes/dist/ai/features/path-choice/run-reactive-path-choice-loop.d.ts +35 -0
- package/vendor/common-nodes/dist/ai/features/path-choice/run-reactive-path-choice-loop.js +106 -0
- package/vendor/common-nodes/dist/ai/features/prompt/build-effective-system-prompt.d.ts +9 -0
- package/vendor/common-nodes/dist/ai/features/prompt/build-effective-system-prompt.js +21 -0
- package/vendor/common-nodes/dist/ai/features/prompt/normalize-max-iterations.d.ts +13 -0
- package/vendor/common-nodes/dist/ai/features/prompt/normalize-max-iterations.js +23 -0
- package/vendor/common-nodes/dist/ai/features/prompt/resolve-chat-provider-model.d.ts +10 -0
- package/vendor/common-nodes/dist/ai/features/prompt/resolve-chat-provider-model.js +8 -0
- package/vendor/common-nodes/dist/ai/features/run-host-services.d.ts +54 -0
- package/vendor/common-nodes/dist/ai/features/run-host-services.js +29 -0
- package/vendor/common-nodes/dist/ai/features/scripted-chat-completion-stream.d.ts +17 -0
- package/vendor/common-nodes/dist/ai/features/scripted-chat-completion-stream.js +61 -0
- package/vendor/common-nodes/dist/ai/features/sub-agent-protocol.d.ts +39 -0
- package/vendor/common-nodes/dist/ai/features/sub-agent-protocol.js +43 -0
- package/vendor/common-nodes/dist/ai/features/ui-schema/llm-compaction-ui-schema.d.ts +14 -0
- package/vendor/common-nodes/dist/ai/features/ui-schema/llm-compaction-ui-schema.js +18 -0
- package/vendor/common-nodes/dist/ai/features/ui-schema/llm-panel-ui-schema.d.ts +80 -0
- package/vendor/common-nodes/dist/ai/features/ui-schema/llm-panel-ui-schema.js +76 -0
- package/vendor/common-nodes/dist/ai/features/ui-schema/llm-recovery-ui-schema.d.ts +113 -0
- package/vendor/common-nodes/dist/ai/features/ui-schema/llm-recovery-ui-schema.js +131 -0
- package/vendor/common-nodes/dist/ai/features/wait-for-subagent-result.d.ts +6 -0
- package/vendor/common-nodes/dist/ai/features/wait-for-subagent-result.js +30 -0
- package/vendor/common-nodes/dist/ai/llm-loop/autokick-recovery.d.ts +13 -0
- package/vendor/common-nodes/dist/ai/llm-loop/autokick-recovery.js +17 -0
- package/vendor/common-nodes/dist/ai/llm-loop/dead-loop-detector.d.ts +28 -0
- package/vendor/common-nodes/dist/ai/llm-loop/dead-loop-detector.js +194 -0
- package/vendor/common-nodes/dist/ai/llm-loop/llm-loop-reducer.js +36 -0
- package/vendor/common-nodes/dist/ai/llm-loop/llm-loop-types.d.ts +35 -0
- package/vendor/common-nodes/dist/ai/llm-loop/llm-loop-types.js +19 -0
- package/vendor/common-nodes/dist/ai/llm-loop/normalize-llm-recovery-policy.js +52 -9
- package/vendor/common-nodes/dist/ai/llm-loop/operators/observe-provider-stream.d.ts +8 -2
- package/vendor/common-nodes/dist/ai/llm-loop/operators/observe-provider-stream.js +43 -13
- package/vendor/common-nodes/dist/ai/llm-loop/run-llm-loop.d.ts +3 -5
- package/vendor/common-nodes/dist/ai/llm-loop/run-llm-loop.js +116 -1
- package/vendor/common-nodes/dist/ai/llm-recovery-ui-schema.d.ts +82 -0
- package/vendor/common-nodes/dist/ai/llm-recovery-ui-schema.js +95 -0
- package/vendor/common-nodes/dist/ai/llm-session-shell.d.ts +4 -9
- package/vendor/common-nodes/dist/ai/llm-session-shell.js +2 -4
- package/vendor/common-nodes/dist/ai/nodes/critique/node.d.ts +588 -0
- package/vendor/common-nodes/dist/ai/nodes/critique/node.js +240 -0
- package/vendor/common-nodes/dist/ai/nodes/fake-llm/node.d.ts +571 -0
- package/vendor/common-nodes/dist/ai/nodes/fake-llm/node.js +208 -0
- package/vendor/common-nodes/dist/ai/nodes/openai-llm/node.d.ts +587 -0
- package/vendor/common-nodes/dist/ai/nodes/openai-llm/node.js +91 -0
- package/vendor/common-nodes/dist/ai/nodes/review/node.d.ts +593 -0
- package/vendor/common-nodes/dist/ai/nodes/review/node.js +240 -0
- package/vendor/common-nodes/dist/ai/nodes/sub-agent/node.d.ts +630 -0
- package/vendor/common-nodes/dist/ai/nodes/sub-agent/node.js +302 -0
- package/vendor/common-nodes/dist/ai/openai/create-chat-completion-stream.js +6 -0
- package/vendor/common-nodes/dist/ai/openai/prepare-chat-completion.d.ts +2 -0
- package/vendor/common-nodes/dist/ai/openai/prepare-chat-completion.js +18 -0
- package/vendor/common-nodes/dist/ai/openai-llm/node.d.ts +246 -0
- package/vendor/common-nodes/dist/ai/review/node.d.ts +246 -0
- package/vendor/common-nodes/dist/ai/review/node.js +2 -2
- package/vendor/common-nodes/dist/ai/sub-agent/node.d.ts +246 -0
- package/vendor/common-nodes/dist/ai/sub-agent/node.js +2 -2
- package/vendor/common-nodes/dist/catalog.js +7 -5
- package/vendor/common-nodes/dist/compile-custom-nodes/node.d.ts +21 -0
- package/vendor/common-nodes/dist/compile-custom-nodes/node.js +45 -0
- package/vendor/common-nodes/dist/crawl/crawl/node.js +1 -1
- package/vendor/common-nodes/dist/crawl/fetch-url/node.js +1 -1
- package/vendor/common-nodes/dist/flow/repeat/node.js +6 -1
- package/vendor/common-nodes/dist/langflower-tools/emit-registration-tools.d.ts +18 -0
- package/vendor/common-nodes/dist/langflower-tools/emit-registration-tools.js +29 -0
- package/vendor/common-nodes/dist/langflower-tools/node.d.ts +20 -0
- package/vendor/common-nodes/dist/langflower-tools/node.js +77 -0
- package/vendor/common-nodes/dist/text/append-file/node.js +1 -1
- package/vendor/common-nodes/dist/text/read-file/node.js +1 -1
- package/vendor/common-nodes/dist/text/write-file/node.js +1 -1
- package/vendor/common-nodes/dist/tools/inventory-tool-round.d.ts +2 -2
- package/vendor/common-nodes/package.json +10 -10
- package/vendor/compiler/dist/compile-project-nodes.d.ts +2 -1
- package/vendor/compiler/dist/compile-project-nodes.js +35 -6
- package/vendor/compiler/dist/resolve-host-types.d.ts +0 -5
- package/vendor/compiler/dist/resolve-host-types.js +0 -8
- package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.d.ts +1 -1
- package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.js +1 -1
- package/vendor/node-sdk/dist/node-factory/define-llm-node/recovery-notice.d.ts +8 -0
- package/vendor/node-sdk/dist/node-factory/define-llm-node/recovery-notice.js +14 -0
- package/vendor/runtime/dist/runtime-editor.d.ts +5 -1
- package/vendor/runtime/dist/runtime-editor.js +19 -0
- package/vendor/runtime/dist/runtime-runner.js +17 -17
- package/vendor/runtime/dist/runtime.d.ts +2 -1
- package/vendor/runtime/dist/runtime.js +1 -0
- package/vendor/runtime/dist/testing/workflows/workflow-events.d.ts +8 -6
- package/vendor/runtime/dist/testing/workflows/workflow-events.js +19 -31
- package/vendor/runtime/dist/types.d.ts +33 -38
- package/vendor/runtime/dist/types.js +2 -1
- package/vendor/server/dist/bridge/attach-langflower-bridge.js +3 -2
- package/vendor/server/dist/bridge/bridge-event-log.js +17 -102
- package/vendor/server/dist/bridge/build-execution-context.d.ts +5 -2
- package/vendor/server/dist/bridge/build-execution-context.js +45 -5
- package/vendor/server/dist/bridge/forward-runner-event.d.ts +1 -1
- package/vendor/server/dist/bridge/forward-runner-event.js +7 -12
- package/vendor/server/dist/bridge/get-live-wired-tools.d.ts +12 -0
- package/vendor/server/dist/bridge/get-live-wired-tools.js +80 -0
- package/vendor/server/dist/bridge/langflower-tools-rpc.d.ts +7 -0
- package/vendor/server/dist/bridge/langflower-tools-rpc.js +29 -0
- package/vendor/server/dist/bridge/settings-draft-controller.js +26 -3
- package/vendor/server/dist/bridge/wire-custom-palette-handlers.d.ts +2 -1
- package/vendor/server/dist/bridge/wire-custom-palette-handlers.js +3 -5
- package/vendor/server/dist/bridge/wire-runner-handlers.js +15 -8
- package/vendor/server/dist/checkpoint/run-checkpoint-session.d.ts +1 -1
- package/vendor/server/dist/checkpoint/run-checkpoint-session.js +10 -9
- package/vendor/server/dist/palette/compile-and-hot-swap-custom-nodes.d.ts +10 -0
- package/vendor/server/dist/palette/compile-and-hot-swap-custom-nodes.js +23 -0
- package/vendor/server/dist/workflow/apply-editor-mutation.d.ts +5 -0
- package/vendor/server/dist/workflow/apply-editor-mutation.js +32 -0
- package/vendor/server/skeleton/instructions.md +9 -5
- package/vendor/server/skeleton/nodes/my-nodes/README.md +4 -3
- package/vendor/server/skeleton/schemas/langflower-config.schema.json +11 -1
- package/vendor/server/skeleton/skills/langflower-helper/SKILL.md +49 -19
- package/vendor/server/skeleton/skills/langflower-helper/architecture.md +22 -9
- package/vendor/server/skeleton/skills/langflower-helper/layout.md +15 -15
- package/vendor/server/skeleton/skills/langflower-node-writer/SKILL.md +19 -9
- package/vendor/server/skeleton/skills/langflower-workflow-writer/SKILL.md +14 -10
- package/vendor/server/skeleton/workflows/starter.json +30 -3
- package/vendor/shared/dist/execution/derive-run-settle-outcome.d.ts +1 -1
- package/vendor/shared/dist/execution/derive-run-settle-outcome.js +4 -3
- package/vendor/shared/dist/execution/derive-run-settle-outcome.test.js +9 -9
- package/vendor/shared/dist/langflower-bus-config.d.ts +6 -14
- package/vendor/shared/dist/langflower-bus-config.js +4 -12
- package/vendor/shared/dist/langflower-config/resolve-wired-tool-options.js +6 -0
- package/vendor/shared/dist/langflower-config/resolve-wired-tool-options.test.js +30 -0
- package/vendor/shared/dist/langflower-ws-waits.d.ts +7 -14
- package/vendor/shared/dist/langflower-ws-waits.js +14 -14
- package/vendor/websocket-bridge/dist/bridge-codec.d.ts +1 -0
- package/vendor/websocket-bridge/dist/bridge-codec.js +11 -11
- package/vendor/websocket-bridge/dist/bridge-frame.js +1 -1
- package/vendor/websocket-bridge/dist/bridge-subjects.d.ts +1 -1
- package/vendor/websocket-bridge/dist/bridge-subjects.js +2 -2
- package/vendor/websocket-bridge/dist/bridge-types.d.ts +13 -1
- package/vendor/websocket-bridge/dist/create-client.js +3 -3
- package/vendor/websocket-bridge/dist/create-server.js +12 -6
- package/vendor/websocket-bridge/package.json +8 -0
- package/ui-dist/main-SMO3L7U5.js +0 -311
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export const DEFAULT_AUTOKICK_USER_MESSAGE = 'I notice you are repeating yourself. Please stop and provide a concise answer.';
|
|
1
2
|
export const DEFAULT_LLM_RECOVERY_POLICY = {
|
|
2
3
|
streamIdleTimeoutMs: 90_000,
|
|
3
4
|
toolTimeoutMs: 60_000,
|
|
@@ -5,6 +6,22 @@ export const DEFAULT_LLM_RECOVERY_POLICY = {
|
|
|
5
6
|
maxTransientRetries: 2,
|
|
6
7
|
retryBaseDelayMs: 1_000,
|
|
7
8
|
maxToolResultChars: 40_000,
|
|
9
|
+
autokickOnIdle: true,
|
|
10
|
+
deadLoopEnabled: true,
|
|
11
|
+
maxAutokickAttempts: 0,
|
|
12
|
+
autokickBackoffMs: 60_000,
|
|
13
|
+
autokickMaxBackoffMs: 960_000,
|
|
14
|
+
autokickUserMessage: DEFAULT_AUTOKICK_USER_MESSAGE,
|
|
15
|
+
autokickPenaltyDelta: {
|
|
16
|
+
frequency: 0.3,
|
|
17
|
+
presence: 0.3,
|
|
18
|
+
},
|
|
19
|
+
deadLoop: {
|
|
20
|
+
maxWindowTokens: 1_000,
|
|
21
|
+
consecutiveThreshold: 5,
|
|
22
|
+
minRepetitions: 3,
|
|
23
|
+
minPatternTokens: 2,
|
|
24
|
+
},
|
|
8
25
|
};
|
|
9
26
|
export const initialLlmLoopState = (messages) => ({
|
|
10
27
|
phase: 'prepare',
|
|
@@ -17,4 +34,6 @@ export const initialLlmLoopState = (messages) => ({
|
|
|
17
34
|
draft: '',
|
|
18
35
|
},
|
|
19
36
|
pendingToolCalls: [],
|
|
37
|
+
autokickAttempts: 0,
|
|
38
|
+
autokickKickAttempts: 0,
|
|
20
39
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_LLM_RECOVERY_POLICY, } from './llm-loop-types.js';
|
|
1
|
+
import { DEFAULT_AUTOKICK_USER_MESSAGE, DEFAULT_LLM_RECOVERY_POLICY, } from './llm-loop-types.js';
|
|
2
2
|
const normalizePositiveInteger = (value, fallback, max) => {
|
|
3
3
|
const number = typeof value === 'number' ? value : Number(value);
|
|
4
4
|
if (!Number.isFinite(number) || number < 0) {
|
|
@@ -6,11 +6,54 @@ const normalizePositiveInteger = (value, fallback, max) => {
|
|
|
6
6
|
}
|
|
7
7
|
return Math.min(max, Math.floor(number));
|
|
8
8
|
};
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
9
|
+
const normalizeBoolean = (value, fallback) => typeof value === 'boolean' ? value : fallback;
|
|
10
|
+
const normalizePenalty = (value, fallback) => {
|
|
11
|
+
const number = typeof value === 'number' ? value : Number(value);
|
|
12
|
+
if (!Number.isFinite(number)) {
|
|
13
|
+
return fallback;
|
|
14
|
+
}
|
|
15
|
+
return Math.min(2, Math.max(-2, number));
|
|
16
|
+
};
|
|
17
|
+
const readRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
18
|
+
? value
|
|
19
|
+
: undefined;
|
|
20
|
+
const nestedDeadLoop = (params) => readRecord(params['deadLoop']) ?? {};
|
|
21
|
+
const nestedPenalty = (params) => readRecord(params['autokickPenaltyDelta']) ?? {};
|
|
22
|
+
export const normalizeLlmRecoveryPolicy = (params) => {
|
|
23
|
+
const deadLoop = nestedDeadLoop(params);
|
|
24
|
+
const penalty = nestedPenalty(params);
|
|
25
|
+
const autokickBackoffMs = Math.max(1_000, normalizePositiveInteger(params['autokickBackoffMs'], DEFAULT_LLM_RECOVERY_POLICY.autokickBackoffMs, 3_600_000));
|
|
26
|
+
const autokickMaxBackoffMs = Math.max(autokickBackoffMs, normalizePositiveInteger(params['autokickMaxBackoffMs'], DEFAULT_LLM_RECOVERY_POLICY.autokickMaxBackoffMs, 3_600_000));
|
|
27
|
+
const kick = typeof params['autokickUserMessage'] === 'string' &&
|
|
28
|
+
params['autokickUserMessage'].trim().length > 0
|
|
29
|
+
? params['autokickUserMessage'].trim()
|
|
30
|
+
: DEFAULT_AUTOKICK_USER_MESSAGE;
|
|
31
|
+
return {
|
|
32
|
+
streamIdleTimeoutMs: normalizePositiveInteger(params['streamIdleTimeoutMs'], DEFAULT_LLM_RECOVERY_POLICY.streamIdleTimeoutMs, 3_600_000),
|
|
33
|
+
toolTimeoutMs: normalizePositiveInteger(params['toolTimeoutMs'], DEFAULT_LLM_RECOVERY_POLICY.toolTimeoutMs, 3_600_000),
|
|
34
|
+
subagentTimeoutMs: normalizePositiveInteger(params['subagentTimeoutMs'], DEFAULT_LLM_RECOVERY_POLICY.subagentTimeoutMs, 86_400_000),
|
|
35
|
+
maxTransientRetries: normalizePositiveInteger(params['maxTransientRetries'], DEFAULT_LLM_RECOVERY_POLICY.maxTransientRetries, 10),
|
|
36
|
+
retryBaseDelayMs: normalizePositiveInteger(params['retryBaseDelayMs'], DEFAULT_LLM_RECOVERY_POLICY.retryBaseDelayMs, 60_000),
|
|
37
|
+
maxToolResultChars: normalizePositiveInteger(params['maxToolResultChars'], DEFAULT_LLM_RECOVERY_POLICY.maxToolResultChars, 1_000_000),
|
|
38
|
+
autokickOnIdle: normalizeBoolean(params['autokickOnIdle'], DEFAULT_LLM_RECOVERY_POLICY.autokickOnIdle),
|
|
39
|
+
deadLoopEnabled: normalizeBoolean(params['deadLoopEnabled'], DEFAULT_LLM_RECOVERY_POLICY.deadLoopEnabled),
|
|
40
|
+
maxAutokickAttempts: normalizePositiveInteger(params['maxAutokickAttempts'], DEFAULT_LLM_RECOVERY_POLICY.maxAutokickAttempts, 1_000),
|
|
41
|
+
autokickBackoffMs,
|
|
42
|
+
autokickMaxBackoffMs,
|
|
43
|
+
autokickUserMessage: kick,
|
|
44
|
+
autokickPenaltyDelta: {
|
|
45
|
+
frequency: normalizePenalty(penalty['frequency'] ?? params['autokickPenaltyFrequency'], DEFAULT_LLM_RECOVERY_POLICY.autokickPenaltyDelta.frequency),
|
|
46
|
+
presence: normalizePenalty(penalty['presence'] ?? params['autokickPenaltyPresence'], DEFAULT_LLM_RECOVERY_POLICY.autokickPenaltyDelta.presence),
|
|
47
|
+
},
|
|
48
|
+
deadLoop: {
|
|
49
|
+
maxWindowTokens: Math.min(8_000, Math.max(10, normalizePositiveInteger(deadLoop['maxWindowTokens'] ??
|
|
50
|
+
params['deadLoopMaxWindowTokens'], DEFAULT_LLM_RECOVERY_POLICY.deadLoop.maxWindowTokens, 8_000))),
|
|
51
|
+
consecutiveThreshold: Math.max(1, normalizePositiveInteger(deadLoop['consecutiveThreshold'] ??
|
|
52
|
+
params['deadLoopConsecutiveThreshold'], DEFAULT_LLM_RECOVERY_POLICY.deadLoop.consecutiveThreshold, 100)),
|
|
53
|
+
minRepetitions: Math.max(1, normalizePositiveInteger(deadLoop['minRepetitions'] ??
|
|
54
|
+
params['deadLoopMinRepetitions'], DEFAULT_LLM_RECOVERY_POLICY.deadLoop.minRepetitions, 20)),
|
|
55
|
+
minPatternTokens: Math.max(1, normalizePositiveInteger(deadLoop['minPatternTokens'] ??
|
|
56
|
+
params['deadLoopMinPatternTokens'], DEFAULT_LLM_RECOVERY_POLICY.deadLoop.minPatternTokens, 100)),
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SteerControlPayload } from '@langflower/node-sdk/llm';
|
|
2
2
|
import { type Observable } from 'rxjs';
|
|
3
3
|
import type { ChatCompletionFinishReason, ChatCompletionStreamChunk, ChatCompletionToolCall } from '../../chat-completion-stream.js';
|
|
4
|
+
import { type DeadLoopChannel, type DeadLoopDetectorOptions } from '../dead-loop-detector.js';
|
|
4
5
|
import type { LlmFailure } from '../llm-loop-types.js';
|
|
5
6
|
export type ProviderStreamFact = {
|
|
6
7
|
readonly kind: 'provider.reasoning';
|
|
@@ -18,6 +19,10 @@ export type ProviderStreamFact = {
|
|
|
18
19
|
} | {
|
|
19
20
|
readonly kind: 'provider.idle';
|
|
20
21
|
readonly idleMs: number;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: 'provider.dead-loop';
|
|
24
|
+
readonly channel: DeadLoopChannel;
|
|
25
|
+
readonly reason: 'consecutive' | 'cyclic';
|
|
21
26
|
} | {
|
|
22
27
|
readonly kind: 'provider.failed';
|
|
23
28
|
readonly failure: LlmFailure;
|
|
@@ -28,10 +33,11 @@ export type ObserveProviderStreamOptions = {
|
|
|
28
33
|
readonly cancel$: Observable<void>;
|
|
29
34
|
readonly idleTimeoutMs: number;
|
|
30
35
|
readonly onAbort?: () => void;
|
|
36
|
+
readonly deadLoop?: DeadLoopDetectorOptions | false;
|
|
31
37
|
};
|
|
32
38
|
/**
|
|
33
39
|
* Convert the provider AsyncIterable into typed, cancellable RxJS facts.
|
|
34
|
-
* Pause and
|
|
35
|
-
* surrounding LLM loop.
|
|
40
|
+
* Pause, idle, and dead-loop are terminal for one provider attempt, not
|
|
41
|
+
* terminal for the surrounding LLM loop.
|
|
36
42
|
*/
|
|
37
43
|
export declare const observeProviderStream: (options: ObserveProviderStreamOptions) => Observable<ProviderStreamFact>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isSteerControlPause } from '@langflower/node-sdk/llm';
|
|
2
|
-
import { catchError, defer, endWith, filter, finalize, from, map, merge, of, switchMap, take, takeUntil, takeWhile, tap, timeout, } from 'rxjs';
|
|
2
|
+
import { catchError, concatMap, defer, endWith, filter, finalize, from, map, merge, of, switchMap, take, takeUntil, takeWhile, tap, timeout, } from 'rxjs';
|
|
3
3
|
import { classifyLlmFailure } from '../classify-llm-failure.js';
|
|
4
|
+
import { createDeadLoopDetector, } from '../dead-loop-detector.js';
|
|
4
5
|
const toProviderStreamFact = (chunk) => {
|
|
5
6
|
switch (chunk.kind) {
|
|
6
7
|
case 'reasoning':
|
|
@@ -23,32 +24,62 @@ const toProviderStreamFact = (chunk) => {
|
|
|
23
24
|
const isTerminalProviderFact = (fact) => fact.kind === 'provider.done' ||
|
|
24
25
|
fact.kind === 'provider.paused' ||
|
|
25
26
|
fact.kind === 'provider.idle' ||
|
|
27
|
+
fact.kind === 'provider.dead-loop' ||
|
|
26
28
|
fact.kind === 'provider.failed';
|
|
27
|
-
const withIdleTimeout = (source$, idleTimeoutMs) => {
|
|
29
|
+
const withIdleTimeout = (source$, idleTimeoutMs, onIdle) => {
|
|
28
30
|
if (idleTimeoutMs <= 0) {
|
|
29
31
|
return source$;
|
|
30
32
|
}
|
|
31
33
|
return source$.pipe(timeout({
|
|
32
34
|
first: idleTimeoutMs,
|
|
33
35
|
each: idleTimeoutMs,
|
|
34
|
-
with: () =>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
with: () => {
|
|
37
|
+
onIdle();
|
|
38
|
+
return of({
|
|
39
|
+
kind: 'provider.idle',
|
|
40
|
+
idleMs: idleTimeoutMs,
|
|
41
|
+
});
|
|
42
|
+
},
|
|
38
43
|
}));
|
|
39
44
|
};
|
|
45
|
+
const guardDeadLoop = (fact, detectors, abort) => {
|
|
46
|
+
if (detectors === undefined ||
|
|
47
|
+
(fact.kind !== 'provider.reasoning' && fact.kind !== 'provider.draft')) {
|
|
48
|
+
return of(fact);
|
|
49
|
+
}
|
|
50
|
+
const channel = fact.kind === 'provider.reasoning' ? 'reasoning' : 'draft';
|
|
51
|
+
const detected = detectors[channel].push(fact.text);
|
|
52
|
+
if (detected.ok) {
|
|
53
|
+
return of(fact);
|
|
54
|
+
}
|
|
55
|
+
abort();
|
|
56
|
+
return of(fact, {
|
|
57
|
+
kind: 'provider.dead-loop',
|
|
58
|
+
channel: detected.error.channel,
|
|
59
|
+
reason: detected.error.reason,
|
|
60
|
+
});
|
|
61
|
+
};
|
|
40
62
|
/**
|
|
41
63
|
* Convert the provider AsyncIterable into typed, cancellable RxJS facts.
|
|
42
|
-
* Pause and
|
|
43
|
-
* surrounding LLM loop.
|
|
64
|
+
* Pause, idle, and dead-loop are terminal for one provider attempt, not
|
|
65
|
+
* terminal for the surrounding LLM loop.
|
|
44
66
|
*/
|
|
45
67
|
export const observeProviderStream = (options) => defer(() => {
|
|
46
68
|
const abort = new AbortController();
|
|
47
|
-
const
|
|
69
|
+
const stopProvider = () => {
|
|
48
70
|
abort.abort();
|
|
49
71
|
options.onAbort?.();
|
|
72
|
+
};
|
|
73
|
+
const detectors = options.deadLoop === false
|
|
74
|
+
? undefined
|
|
75
|
+
: {
|
|
76
|
+
reasoning: createDeadLoopDetector('reasoning', options.deadLoop ?? {}),
|
|
77
|
+
draft: createDeadLoopDetector('draft', options.deadLoop ?? {}),
|
|
78
|
+
};
|
|
79
|
+
const pause$ = options.pause$.pipe(filter(isSteerControlPause), take(1), tap(() => {
|
|
80
|
+
stopProvider();
|
|
50
81
|
}), map(() => ({ kind: 'provider.paused' })));
|
|
51
|
-
const chunks$ = withIdleTimeout(from(options.createStream(abort.signal)).pipe(switchMap((stream) => from(stream)),
|
|
82
|
+
const chunks$ = withIdleTimeout(from(options.createStream(abort.signal)).pipe(switchMap((stream) => from(stream)), concatMap((chunk) => guardDeadLoop(toProviderStreamFact(chunk), detectors, stopProvider)), catchError((error) => of({
|
|
52
83
|
kind: 'provider.failed',
|
|
53
84
|
failure: classifyLlmFailure(error),
|
|
54
85
|
})), endWith({
|
|
@@ -58,9 +89,8 @@ export const observeProviderStream = (options) => defer(() => {
|
|
|
58
89
|
message: 'Provider stream completed without a done chunk.',
|
|
59
90
|
recoverable: true,
|
|
60
91
|
},
|
|
61
|
-
})), options.idleTimeoutMs);
|
|
92
|
+
})), options.idleTimeoutMs, stopProvider);
|
|
62
93
|
return merge(chunks$, pause$).pipe(takeWhile((fact) => !isTerminalProviderFact(fact), true), takeUntil(options.cancel$), finalize(() => {
|
|
63
|
-
|
|
64
|
-
options.onAbort?.();
|
|
94
|
+
stopProvider();
|
|
65
95
|
}));
|
|
66
96
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ToolHandle } from '@langflower/node-sdk';
|
|
2
|
-
import { type SteerControlPayload } from '@langflower/node-sdk/llm';
|
|
2
|
+
import { type LlmRecoveryNotice, type SteerControlPayload } from '@langflower/node-sdk/llm';
|
|
3
3
|
import type { Harness } from '@langflower/tools/create-project-harness';
|
|
4
4
|
import type { ToolHandlerContext } from '@langflower/tools/domain-tool-configs';
|
|
5
5
|
import type { PermissionAskRequest } from '@langflower/tools/permission';
|
|
@@ -17,11 +17,9 @@ export type SharedLlmLoopChunk = {
|
|
|
17
17
|
} | {
|
|
18
18
|
readonly kind: 'toolLog';
|
|
19
19
|
readonly text: string;
|
|
20
|
-
} | {
|
|
20
|
+
} | ({
|
|
21
21
|
readonly kind: 'recoveryNotice';
|
|
22
|
-
|
|
23
|
-
readonly text: string;
|
|
24
|
-
} | {
|
|
22
|
+
} & LlmRecoveryNotice) | {
|
|
25
23
|
readonly kind: 'historySync';
|
|
26
24
|
readonly messages: readonly ChatCompletionMessage[];
|
|
27
25
|
} | {
|
|
@@ -4,6 +4,7 @@ import { invokeInventoryTool, previewToolLogText, resolveSpawnPayload, SPAWN_SUB
|
|
|
4
4
|
import { prepareChatCompletion } from '../openai/prepare-chat-completion.js';
|
|
5
5
|
import { compactMessagesWithSummary, estimateRequestTokens, resolveForceTargetTokens, } from '../openai/llm-context-compaction.js';
|
|
6
6
|
import { classifyLlmFailure } from './classify-llm-failure.js';
|
|
7
|
+
import { autokickBackoffMs, autokickPenalties } from './autokick-recovery.js';
|
|
7
8
|
import { reduceLlmLoop } from './llm-loop-reducer.js';
|
|
8
9
|
import { initialLlmLoopState, } from './llm-loop-types.js';
|
|
9
10
|
import { observeProviderStream, } from './operators/observe-provider-stream.js';
|
|
@@ -17,6 +18,10 @@ const failureText = (failure) => {
|
|
|
17
18
|
const status = failure.status === undefined ? '' : ` HTTP ${failure.status}`;
|
|
18
19
|
return `Provider failure${status}: ${failure.message}`;
|
|
19
20
|
};
|
|
21
|
+
const joinsAutokickWait = (kind) => kind === 'rate-limit' ||
|
|
22
|
+
kind === 'provider-unavailable' ||
|
|
23
|
+
kind === 'network';
|
|
24
|
+
const retryReasonFromFailure = (kind) => joinsAutokickWait(kind) ? kind : undefined;
|
|
20
25
|
const isIncompleteToolCallJson = (calls) => {
|
|
21
26
|
for (const call of calls) {
|
|
22
27
|
const args = call.arguments.trim();
|
|
@@ -39,6 +44,61 @@ const isOutputTruncation = (fact) => {
|
|
|
39
44
|
const calls = fact.toolCalls ?? [];
|
|
40
45
|
return calls.length > 0 && isIncompleteToolCallJson(calls);
|
|
41
46
|
};
|
|
47
|
+
const canScheduleAutokick = (state, recovery) => recovery.autokickOnIdle &&
|
|
48
|
+
(recovery.maxAutokickAttempts === 0 ||
|
|
49
|
+
state.autokickAttempts < recovery.maxAutokickAttempts);
|
|
50
|
+
const autokickPackets = (state, options, args) => {
|
|
51
|
+
if (!canScheduleAutokick(state, options.recovery)) {
|
|
52
|
+
return concat(of(emit({
|
|
53
|
+
kind: 'recoveryNotice',
|
|
54
|
+
code: 'suspended',
|
|
55
|
+
text: args.suspendedText,
|
|
56
|
+
})), of(transition(state)));
|
|
57
|
+
}
|
|
58
|
+
const attempt = state.autokickAttempts + 1;
|
|
59
|
+
const backoffMs = args.reason === 'dead-loop'
|
|
60
|
+
? options.recovery.retryBaseDelayMs
|
|
61
|
+
: Math.max(autokickBackoffMs(attempt, options.recovery.autokickBackoffMs, options.recovery.autokickMaxBackoffMs), args.retryAfterMs ?? 0);
|
|
62
|
+
const nowMs = Date.now();
|
|
63
|
+
const interrupt$ = (options.steerControl$ ?? NEVER).pipe(filter((payload) => isSteerControlPause(payload) || payload.kind === 'steer'), take(1), map((payload) => ({ kind: 'interrupt', payload })));
|
|
64
|
+
return concat(of(emit({
|
|
65
|
+
kind: 'recoveryNotice',
|
|
66
|
+
code: 'retry',
|
|
67
|
+
text: `⚠ ${args.retryDetail}. Retrying ${attempt} in ${backoffMs}ms.`,
|
|
68
|
+
attempt,
|
|
69
|
+
reason: args.reason,
|
|
70
|
+
backoffMs,
|
|
71
|
+
nextAttemptAt: nowMs + backoffMs,
|
|
72
|
+
...(state.lastAutokickAt !== undefined
|
|
73
|
+
? { lastAttemptAt: state.lastAutokickAt }
|
|
74
|
+
: {}),
|
|
75
|
+
})), race(of({ kind: 'elapsed' }).pipe(delay(backoffMs)), interrupt$).pipe(switchMap((event) => {
|
|
76
|
+
if (event.kind === 'interrupt') {
|
|
77
|
+
if (isSteerControlPause(event.payload)) {
|
|
78
|
+
return concat(of(emit({
|
|
79
|
+
kind: 'toolLog',
|
|
80
|
+
text: 'Paused. Send Steer feedback or Resume to continue.',
|
|
81
|
+
})), of(transition(reduceLlmLoop(state, {
|
|
82
|
+
type: 'stream.paused',
|
|
83
|
+
}))));
|
|
84
|
+
}
|
|
85
|
+
const text = event.payload.kind === 'steer'
|
|
86
|
+
? event.payload.text
|
|
87
|
+
: undefined;
|
|
88
|
+
return of(transition(reduceLlmLoop(state, {
|
|
89
|
+
type: 'steer.received',
|
|
90
|
+
...(text !== undefined ? { text } : {}),
|
|
91
|
+
})));
|
|
92
|
+
}
|
|
93
|
+
return of(transition(reduceLlmLoop(state, {
|
|
94
|
+
type: 'autokick.scheduled',
|
|
95
|
+
atMs: Date.now(),
|
|
96
|
+
...(args.kickUserMessage !== undefined
|
|
97
|
+
? { kickUserMessage: args.kickUserMessage }
|
|
98
|
+
: {}),
|
|
99
|
+
})));
|
|
100
|
+
})));
|
|
101
|
+
};
|
|
42
102
|
const recoveryPackets = (state, failure, options) => {
|
|
43
103
|
if (!failure.recoverable) {
|
|
44
104
|
return concat(of(emit({
|
|
@@ -65,12 +125,31 @@ const recoveryPackets = (state, failure, options) => {
|
|
|
65
125
|
const delayMs = failure.retryAfterMs ??
|
|
66
126
|
options.recovery.retryBaseDelayMs * 2 ** (attempt - 1);
|
|
67
127
|
const next = reduceLlmLoop(state, { type: 'retry.scheduled' });
|
|
128
|
+
const reason = retryReasonFromFailure(failure.kind);
|
|
68
129
|
return concat(of(emit({
|
|
69
130
|
kind: 'recoveryNotice',
|
|
70
131
|
code: 'retry',
|
|
71
132
|
text: `⚠ ${failureText(failure)}. Retrying ${attempt}/${options.recovery.maxTransientRetries} in ${delayMs}ms.`,
|
|
133
|
+
attempt,
|
|
134
|
+
backoffMs: delayMs,
|
|
135
|
+
nextAttemptAt: Date.now() + delayMs,
|
|
136
|
+
...(reason !== undefined ? { reason } : {}),
|
|
137
|
+
...(state.lastAutokickAt !== undefined
|
|
138
|
+
? { lastAttemptAt: state.lastAutokickAt }
|
|
139
|
+
: {}),
|
|
72
140
|
})), of(transition(next)).pipe(delay(delayMs)));
|
|
73
141
|
}
|
|
142
|
+
if (joinsAutokickWait(failure.kind) &&
|
|
143
|
+
canScheduleAutokick(state, options.recovery)) {
|
|
144
|
+
return autokickPackets(state, options, {
|
|
145
|
+
retryDetail: failureText(failure),
|
|
146
|
+
suspendedText: `⚠ ${failureText(failure)}. Retry budget exhausted; paused for Steer or Resume.`,
|
|
147
|
+
reason: failure.kind,
|
|
148
|
+
...(failure.retryAfterMs !== undefined
|
|
149
|
+
? { retryAfterMs: failure.retryAfterMs }
|
|
150
|
+
: {}),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
74
153
|
return concat(of(emit({
|
|
75
154
|
kind: 'recoveryNotice',
|
|
76
155
|
code: 'suspended',
|
|
@@ -165,6 +244,12 @@ const providerFactAction = (fact) => {
|
|
|
165
244
|
return { type: 'stream.paused' };
|
|
166
245
|
case 'provider.idle':
|
|
167
246
|
return { type: 'stream.idle', idleMs: fact.idleMs };
|
|
247
|
+
case 'provider.dead-loop':
|
|
248
|
+
return {
|
|
249
|
+
type: 'stream.dead-loop',
|
|
250
|
+
channel: fact.channel,
|
|
251
|
+
reason: fact.reason,
|
|
252
|
+
};
|
|
168
253
|
case 'provider.failed':
|
|
169
254
|
return undefined;
|
|
170
255
|
}
|
|
@@ -198,14 +283,32 @@ const providerFactPackets = (frame, options) => {
|
|
|
198
283
|
};
|
|
199
284
|
const hasPartial = frame.state.partial.reasoning.length > 0 ||
|
|
200
285
|
frame.state.partial.draft.length > 0;
|
|
286
|
+
const suspendedText = hasPartial
|
|
287
|
+
? `⚠ ${failure.message} Paused to avoid repeating partial work.`
|
|
288
|
+
: `⚠ ${failure.message}. Retry budget exhausted; paused for Steer or Resume.`;
|
|
289
|
+
if (options.recovery.autokickOnIdle) {
|
|
290
|
+
return autokickPackets(frame.state, options, {
|
|
291
|
+
retryDetail: failure.message,
|
|
292
|
+
suspendedText,
|
|
293
|
+
reason: 'idle',
|
|
294
|
+
kickUserMessage: options.recovery.autokickUserMessage,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
201
297
|
return hasPartial
|
|
202
298
|
? concat(of(emit({
|
|
203
299
|
kind: 'recoveryNotice',
|
|
204
300
|
code: 'suspended',
|
|
205
|
-
text:
|
|
301
|
+
text: suspendedText,
|
|
206
302
|
})), of(transition(frame.state)))
|
|
207
303
|
: recoveryPackets(frame.state, failure, options);
|
|
208
304
|
}
|
|
305
|
+
case 'provider.dead-loop':
|
|
306
|
+
return autokickPackets(frame.state, options, {
|
|
307
|
+
retryDetail: 'Provider output entered a repetition loop',
|
|
308
|
+
suspendedText: '⚠ Provider output entered a repetition loop. Paused to avoid repeating partial work.',
|
|
309
|
+
reason: 'dead-loop',
|
|
310
|
+
kickUserMessage: options.recovery.autokickUserMessage,
|
|
311
|
+
});
|
|
209
312
|
case 'provider.failed':
|
|
210
313
|
return recoveryPackets(frame.state, fact.failure, options);
|
|
211
314
|
case 'provider.done': {
|
|
@@ -292,6 +395,9 @@ const prepareAndStream = (state, options, cancel$, cancelSignal) => {
|
|
|
292
395
|
const attempt = new AbortController();
|
|
293
396
|
const handleCancel = () => attempt.abort();
|
|
294
397
|
cancelSignal.addEventListener('abort', handleCancel, { once: true });
|
|
398
|
+
const penalties = state.autokickKickAttempts > 0
|
|
399
|
+
? autokickPenalties(state.autokickKickAttempts, options.recovery.autokickPenaltyDelta)
|
|
400
|
+
: undefined;
|
|
295
401
|
const preparationSource$ = from(prepareChatCompletion({
|
|
296
402
|
factory: options.factory,
|
|
297
403
|
providerId: options.providerId,
|
|
@@ -300,6 +406,12 @@ const prepareAndStream = (state, options, cancel$, cancelSignal) => {
|
|
|
300
406
|
tools: options.chatTools,
|
|
301
407
|
signal: attempt.signal,
|
|
302
408
|
compaction: options.compaction,
|
|
409
|
+
...(penalties === undefined
|
|
410
|
+
? {}
|
|
411
|
+
: {
|
|
412
|
+
frequency_penalty: penalties.frequency,
|
|
413
|
+
presence_penalty: penalties.presence,
|
|
414
|
+
}),
|
|
303
415
|
})).pipe(map((prepared) => ({ kind: 'prepared', prepared })), catchError((error) => of({ kind: 'failed', error })));
|
|
304
416
|
const preparation$ = options.recovery.streamIdleTimeoutMs > 0
|
|
305
417
|
? preparationSource$.pipe(timeout({
|
|
@@ -360,6 +472,9 @@ const prepareAndStream = (state, options, cancel$, cancelSignal) => {
|
|
|
360
472
|
cancel$,
|
|
361
473
|
idleTimeoutMs: options.recovery.streamIdleTimeoutMs,
|
|
362
474
|
onAbort: () => attempt.abort(),
|
|
475
|
+
deadLoop: options.recovery.deadLoopEnabled
|
|
476
|
+
? options.recovery.deadLoop
|
|
477
|
+
: false,
|
|
363
478
|
}).pipe(scan((frame, fact) => {
|
|
364
479
|
const action = providerFactAction(fact);
|
|
365
480
|
return {
|
|
@@ -28,4 +28,86 @@ export declare const llmRecoveryUiSchema: readonly [{
|
|
|
28
28
|
readonly min: 0;
|
|
29
29
|
readonly max: 32;
|
|
30
30
|
readonly step: 1;
|
|
31
|
+
}, {
|
|
32
|
+
readonly field: "autokickOnIdle";
|
|
33
|
+
readonly type: "boolean";
|
|
34
|
+
readonly label: "Autokick on idle and dead-loop";
|
|
35
|
+
readonly default: boolean;
|
|
36
|
+
}, {
|
|
37
|
+
readonly field: "deadLoopEnabled";
|
|
38
|
+
readonly type: "boolean";
|
|
39
|
+
readonly label: "Detect repetition dead loops";
|
|
40
|
+
readonly default: boolean;
|
|
41
|
+
}, {
|
|
42
|
+
readonly field: "maxAutokickAttempts";
|
|
43
|
+
readonly type: "number";
|
|
44
|
+
readonly label: "Max autokick reconnects (0 = unlimited)";
|
|
45
|
+
readonly default: number;
|
|
46
|
+
readonly min: 0;
|
|
47
|
+
readonly max: 1000;
|
|
48
|
+
readonly step: 1;
|
|
49
|
+
}, {
|
|
50
|
+
readonly field: "autokickBackoffMs";
|
|
51
|
+
readonly type: "number";
|
|
52
|
+
readonly label: "Autokick backoff base (ms)";
|
|
53
|
+
readonly default: number;
|
|
54
|
+
readonly min: 1000;
|
|
55
|
+
readonly step: 1000;
|
|
56
|
+
}, {
|
|
57
|
+
readonly field: "autokickMaxBackoffMs";
|
|
58
|
+
readonly type: "number";
|
|
59
|
+
readonly label: "Autokick backoff clamp (ms)";
|
|
60
|
+
readonly default: number;
|
|
61
|
+
readonly min: 1000;
|
|
62
|
+
readonly step: 1000;
|
|
63
|
+
}, {
|
|
64
|
+
readonly field: "autokickUserMessage";
|
|
65
|
+
readonly type: "string";
|
|
66
|
+
readonly label: "Autokick kick message";
|
|
67
|
+
readonly default: string;
|
|
68
|
+
}, {
|
|
69
|
+
readonly field: "autokickPenaltyFrequency";
|
|
70
|
+
readonly type: "number";
|
|
71
|
+
readonly label: "Autokick frequency_penalty delta";
|
|
72
|
+
readonly default: number;
|
|
73
|
+
readonly min: -2;
|
|
74
|
+
readonly max: 2;
|
|
75
|
+
readonly step: 0.1;
|
|
76
|
+
}, {
|
|
77
|
+
readonly field: "autokickPenaltyPresence";
|
|
78
|
+
readonly type: "number";
|
|
79
|
+
readonly label: "Autokick presence_penalty delta";
|
|
80
|
+
readonly default: number;
|
|
81
|
+
readonly min: -2;
|
|
82
|
+
readonly max: 2;
|
|
83
|
+
readonly step: 0.1;
|
|
84
|
+
}, {
|
|
85
|
+
readonly field: "deadLoopMaxWindowTokens";
|
|
86
|
+
readonly type: "number";
|
|
87
|
+
readonly label: "Dead-loop window (tokens)";
|
|
88
|
+
readonly default: number;
|
|
89
|
+
readonly min: 10;
|
|
90
|
+
readonly max: 8000;
|
|
91
|
+
readonly step: 1;
|
|
92
|
+
}, {
|
|
93
|
+
readonly field: "deadLoopConsecutiveThreshold";
|
|
94
|
+
readonly type: "number";
|
|
95
|
+
readonly label: "Dead-loop consecutive identical deltas";
|
|
96
|
+
readonly default: number;
|
|
97
|
+
readonly min: 1;
|
|
98
|
+
readonly step: 1;
|
|
99
|
+
}, {
|
|
100
|
+
readonly field: "deadLoopMinRepetitions";
|
|
101
|
+
readonly type: "number";
|
|
102
|
+
readonly label: "Dead-loop cyclic repetitions";
|
|
103
|
+
readonly default: number;
|
|
104
|
+
readonly min: 1;
|
|
105
|
+
readonly step: 1;
|
|
106
|
+
}, {
|
|
107
|
+
readonly field: "deadLoopMinPatternTokens";
|
|
108
|
+
readonly type: "number";
|
|
109
|
+
readonly label: "Dead-loop min pattern tokens";
|
|
110
|
+
readonly default: number;
|
|
111
|
+
readonly min: 1;
|
|
112
|
+
readonly step: 1;
|
|
31
113
|
}];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_LLM_RECOVERY_POLICY } from './llm-loop/llm-loop-types.js';
|
|
1
2
|
/** Shared Inspector fields for provider/tool recovery policy. */
|
|
2
3
|
export const llmRecoveryUiSchema = [
|
|
3
4
|
{
|
|
@@ -33,4 +34,98 @@ export const llmRecoveryUiSchema = [
|
|
|
33
34
|
max: 32,
|
|
34
35
|
step: 1,
|
|
35
36
|
},
|
|
37
|
+
{
|
|
38
|
+
field: 'autokickOnIdle',
|
|
39
|
+
type: 'boolean',
|
|
40
|
+
label: 'Autokick on idle and dead-loop',
|
|
41
|
+
default: DEFAULT_LLM_RECOVERY_POLICY.autokickOnIdle,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
field: 'deadLoopEnabled',
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
label: 'Detect repetition dead loops',
|
|
47
|
+
default: DEFAULT_LLM_RECOVERY_POLICY.deadLoopEnabled,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
field: 'maxAutokickAttempts',
|
|
51
|
+
type: 'number',
|
|
52
|
+
label: 'Max autokick reconnects (0 = unlimited)',
|
|
53
|
+
default: DEFAULT_LLM_RECOVERY_POLICY.maxAutokickAttempts,
|
|
54
|
+
min: 0,
|
|
55
|
+
max: 1000,
|
|
56
|
+
step: 1,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
field: 'autokickBackoffMs',
|
|
60
|
+
type: 'number',
|
|
61
|
+
label: 'Autokick backoff base (ms)',
|
|
62
|
+
default: DEFAULT_LLM_RECOVERY_POLICY.autokickBackoffMs,
|
|
63
|
+
min: 1000,
|
|
64
|
+
step: 1000,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
field: 'autokickMaxBackoffMs',
|
|
68
|
+
type: 'number',
|
|
69
|
+
label: 'Autokick backoff clamp (ms)',
|
|
70
|
+
default: DEFAULT_LLM_RECOVERY_POLICY.autokickMaxBackoffMs,
|
|
71
|
+
min: 1000,
|
|
72
|
+
step: 1000,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
field: 'autokickUserMessage',
|
|
76
|
+
type: 'string',
|
|
77
|
+
label: 'Autokick kick message',
|
|
78
|
+
default: DEFAULT_LLM_RECOVERY_POLICY.autokickUserMessage,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
field: 'autokickPenaltyFrequency',
|
|
82
|
+
type: 'number',
|
|
83
|
+
label: 'Autokick frequency_penalty delta',
|
|
84
|
+
default: DEFAULT_LLM_RECOVERY_POLICY.autokickPenaltyDelta.frequency,
|
|
85
|
+
min: -2,
|
|
86
|
+
max: 2,
|
|
87
|
+
step: 0.1,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
field: 'autokickPenaltyPresence',
|
|
91
|
+
type: 'number',
|
|
92
|
+
label: 'Autokick presence_penalty delta',
|
|
93
|
+
default: DEFAULT_LLM_RECOVERY_POLICY.autokickPenaltyDelta.presence,
|
|
94
|
+
min: -2,
|
|
95
|
+
max: 2,
|
|
96
|
+
step: 0.1,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
field: 'deadLoopMaxWindowTokens',
|
|
100
|
+
type: 'number',
|
|
101
|
+
label: 'Dead-loop window (tokens)',
|
|
102
|
+
default: DEFAULT_LLM_RECOVERY_POLICY.deadLoop.maxWindowTokens,
|
|
103
|
+
min: 10,
|
|
104
|
+
max: 8000,
|
|
105
|
+
step: 1,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
field: 'deadLoopConsecutiveThreshold',
|
|
109
|
+
type: 'number',
|
|
110
|
+
label: 'Dead-loop consecutive identical deltas',
|
|
111
|
+
default: DEFAULT_LLM_RECOVERY_POLICY.deadLoop.consecutiveThreshold,
|
|
112
|
+
min: 1,
|
|
113
|
+
step: 1,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
field: 'deadLoopMinRepetitions',
|
|
117
|
+
type: 'number',
|
|
118
|
+
label: 'Dead-loop cyclic repetitions',
|
|
119
|
+
default: DEFAULT_LLM_RECOVERY_POLICY.deadLoop.minRepetitions,
|
|
120
|
+
min: 1,
|
|
121
|
+
step: 1,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
field: 'deadLoopMinPatternTokens',
|
|
125
|
+
type: 'number',
|
|
126
|
+
label: 'Dead-loop min pattern tokens',
|
|
127
|
+
default: DEFAULT_LLM_RECOVERY_POLICY.deadLoop.minPatternTokens,
|
|
128
|
+
min: 1,
|
|
129
|
+
step: 1,
|
|
130
|
+
},
|
|
36
131
|
];
|
|
@@ -2,7 +2,7 @@ import type { ToolHandle, configureOutput, makeInput } from '@langflower/node-sd
|
|
|
2
2
|
import type { ToolHandlerContext } from '@langflower/tools/domain-tool-configs';
|
|
3
3
|
import type { PermissionAskRequest } from '@langflower/tools/permission';
|
|
4
4
|
import type { ChatCompletionMessage } from './chat-completion-stream.js';
|
|
5
|
-
import type
|
|
5
|
+
import { type LlmInventoryInputs, type LlmRecoveryNotice, type SteerControlPayload } from '@langflower/node-sdk/llm';
|
|
6
6
|
import type { PortMeta } from '@langflower/node-sdk';
|
|
7
7
|
import type { combineStatefulObservables, StatefulObservable } from '@rx-evo/stateful-observable';
|
|
8
8
|
import { type Observable, type OperatorFunction } from 'rxjs';
|
|
@@ -67,11 +67,9 @@ type StandardLlmAgentChunk = {
|
|
|
67
67
|
} | {
|
|
68
68
|
readonly kind: 'toolLog';
|
|
69
69
|
readonly text: string;
|
|
70
|
-
} | {
|
|
70
|
+
} | ({
|
|
71
71
|
readonly kind: 'recoveryNotice';
|
|
72
|
-
|
|
73
|
-
readonly text: string;
|
|
74
|
-
} | {
|
|
72
|
+
} & LlmRecoveryNotice) | {
|
|
75
73
|
readonly kind: 'response';
|
|
76
74
|
readonly text: string;
|
|
77
75
|
} | {
|
|
@@ -142,10 +140,7 @@ export declare const bindLlmAgentSession: <Context extends LlmAgentInventoryCont
|
|
|
142
140
|
outputs: StatefulObservable<string, unknown, import("@langflower/node-sdk").OutputParams & PortMeta>[];
|
|
143
141
|
inventoryOutputs: {
|
|
144
142
|
toolLog$: StatefulObservable<string, [unknown, unknown, unknown, unknown, unknown, unknown], undefined>;
|
|
145
|
-
recovery$: StatefulObservable<
|
|
146
|
-
code: "suspended" | "retry";
|
|
147
|
-
text: string;
|
|
148
|
-
}, [unknown, unknown, unknown, unknown, unknown, unknown], undefined>;
|
|
143
|
+
recovery$: StatefulObservable<LlmRecoveryNotice, [unknown, unknown, unknown, unknown, unknown, unknown], undefined>;
|
|
149
144
|
subagent$: StatefulObservable<SubAgentSpawnPayload, [unknown, unknown, unknown, unknown, unknown, unknown], undefined>;
|
|
150
145
|
};
|
|
151
146
|
};
|