@loopstack/delegate-error-example-workflow 0.21.6 → 0.22.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/delegate-error-example.module.d.ts.map +1 -1
- package/dist/delegate-error-example.module.js +1 -2
- package/dist/delegate-error-example.module.js.map +1 -1
- package/dist/delegate-error.workflow.d.ts +8 -6
- package/dist/delegate-error.workflow.d.ts.map +1 -1
- package/dist/delegate-error.workflow.js +46 -38
- package/dist/delegate-error.workflow.js.map +1 -1
- package/dist/templates/system.md +12 -0
- package/package.json +10 -34
- package/src/__tests__/delegate-error.workflow.spec.ts +89 -70
- package/src/delegate-error-example.module.ts +1 -2
- package/src/delegate-error.workflow.ts +52 -41
- package/src/templates/system.md +12 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delegate-error-example.module.d.ts","sourceRoot":"","sources":["../src/delegate-error-example.module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"delegate-error-example.module.d.ts","sourceRoot":"","sources":["../src/delegate-error-example.module.ts"],"names":[],"mappings":"AAQA,qBAKa,0BAA0B;CAAG"}
|
|
@@ -9,7 +9,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.DelegateErrorExampleModule = void 0;
|
|
10
10
|
const common_1 = require("@nestjs/common");
|
|
11
11
|
const claude_module_1 = require("@loopstack/claude-module");
|
|
12
|
-
const core_1 = require("@loopstack/core");
|
|
13
12
|
const delegate_error_workflow_1 = require("./delegate-error.workflow");
|
|
14
13
|
const failing_sub_workflow_tool_1 = require("./tools/failing-sub-workflow.tool");
|
|
15
14
|
const runtime_error_tool_1 = require("./tools/runtime-error.tool");
|
|
@@ -20,7 +19,7 @@ let DelegateErrorExampleModule = class DelegateErrorExampleModule {
|
|
|
20
19
|
exports.DelegateErrorExampleModule = DelegateErrorExampleModule;
|
|
21
20
|
exports.DelegateErrorExampleModule = DelegateErrorExampleModule = __decorate([
|
|
22
21
|
(0, common_1.Module)({
|
|
23
|
-
imports: [
|
|
22
|
+
imports: [claude_module_1.ClaudeModule],
|
|
24
23
|
providers: [strict_schema_tool_1.StrictSchemaTool, runtime_error_tool_1.RuntimeErrorTool, failing_sub_workflow_tool_1.FailingSubWorkflowTool, failing_workflow_1.FailingWorkflow, delegate_error_workflow_1.DelegateErrorWorkflow],
|
|
25
24
|
exports: [delegate_error_workflow_1.DelegateErrorWorkflow],
|
|
26
25
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delegate-error-example.module.js","sourceRoot":"","sources":["../src/delegate-error-example.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,4DAAwD;AACxD,
|
|
1
|
+
{"version":3,"file":"delegate-error-example.module.js","sourceRoot":"","sources":["../src/delegate-error-example.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,4DAAwD;AACxD,uEAAkE;AAClE,iFAA2E;AAC3E,mEAA8D;AAC9D,mEAA8D;AAC9D,mEAA+D;AAOxD,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;CAAG,CAAA;AAA7B,gEAA0B;qCAA1B,0BAA0B;IALtC,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,CAAC;QACvB,SAAS,EAAE,CAAC,qCAAgB,EAAE,qCAAgB,EAAE,kDAAsB,EAAE,kCAAe,EAAE,+CAAqB,CAAC;QAC/G,OAAO,EAAE,CAAC,+CAAqB,CAAC;KACjC,CAAC;GACW,0BAA0B,CAAG"}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { ClaudeGenerateText, ClaudeGenerateTextResult, DelegateToolCalls, DelegateToolCallsResult, UpdateToolResult } from '@loopstack/claude-module';
|
|
2
1
|
import { BaseWorkflow } from '@loopstack/common';
|
|
2
|
+
import type { LlmDelegateResult, LlmGenerateTextResult, LlmResultMeta } from '@loopstack/llm-provider-module';
|
|
3
|
+
import { LlmDelegateToolCallsTool, LlmGenerateTextTool, LlmUpdateToolResultTool } from '@loopstack/llm-provider-module';
|
|
3
4
|
import { FailingSubWorkflowTool } from './tools/failing-sub-workflow.tool';
|
|
4
5
|
import { RuntimeErrorTool } from './tools/runtime-error.tool';
|
|
5
6
|
import { StrictSchemaTool } from './tools/strict-schema.tool';
|
|
6
7
|
export declare class DelegateErrorWorkflow extends BaseWorkflow {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
llmGenerateText: LlmGenerateTextTool;
|
|
9
|
+
llmDelegateToolCalls: LlmDelegateToolCallsTool;
|
|
10
|
+
llmUpdateToolResult: LlmUpdateToolResultTool;
|
|
10
11
|
strictSchema: StrictSchemaTool;
|
|
11
12
|
runtimeError: RuntimeErrorTool;
|
|
12
13
|
failingSubWorkflow: FailingSubWorkflowTool;
|
|
13
|
-
llmResult?:
|
|
14
|
-
|
|
14
|
+
llmResult?: LlmGenerateTextResult;
|
|
15
|
+
llmMeta?: LlmResultMeta;
|
|
16
|
+
delegateResult?: LlmDelegateResult;
|
|
15
17
|
turnCount: number;
|
|
16
18
|
setup(): Promise<void>;
|
|
17
19
|
llmTurn(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delegate-error.workflow.d.ts","sourceRoot":"","sources":["../src/delegate-error.workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"delegate-error.workflow.d.ts","sourceRoot":"","sources":["../src/delegate-error.workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EASb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC9G,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EAEnB,uBAAuB,EACxB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAU9D,qBAGa,qBAAsB,SAAQ,YAAY;IAMrD,eAAe,EAAE,mBAAmB,CAAC;IACD,oBAAoB,EAAE,wBAAwB,CAAC;IAC/C,mBAAmB,EAAE,uBAAuB,CAAC;IACnE,YAAY,EAAE,gBAAgB,CAAC;IAC/B,YAAY,EAAE,gBAAgB,CAAC;IAC/B,kBAAkB,EAAE,sBAAsB,CAAC;IAEzD,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,cAAc,CAAC,EAAE,iBAAiB,CAAC;IACnC,SAAS,EAAG,MAAM,CAAC;IAGb,KAAK;IAoBL,OAAO;IAgBP,gBAAgB;IAahB,kBAAkB,CAAC,OAAO,EAAE,OAAO;IAUnC,aAAa;IAab,kBAAkB;IASlB,OAAO;IAMb,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,SAAS;CAGlB"}
|
|
@@ -10,19 +10,20 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.DelegateErrorWorkflow = void 0;
|
|
13
|
-
const claude_module_1 = require("@loopstack/claude-module");
|
|
14
13
|
const common_1 = require("@loopstack/common");
|
|
14
|
+
const llm_provider_module_1 = require("@loopstack/llm-provider-module");
|
|
15
15
|
const failing_sub_workflow_tool_1 = require("./tools/failing-sub-workflow.tool");
|
|
16
16
|
const runtime_error_tool_1 = require("./tools/runtime-error.tool");
|
|
17
17
|
const strict_schema_tool_1 = require("./tools/strict-schema.tool");
|
|
18
18
|
let DelegateErrorWorkflow = class DelegateErrorWorkflow extends common_1.BaseWorkflow {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
llmGenerateText;
|
|
20
|
+
llmDelegateToolCalls;
|
|
21
|
+
llmUpdateToolResult;
|
|
22
22
|
strictSchema;
|
|
23
23
|
runtimeError;
|
|
24
24
|
failingSubWorkflow;
|
|
25
25
|
llmResult;
|
|
26
|
+
llmMeta;
|
|
26
27
|
delegateResult;
|
|
27
28
|
turnCount;
|
|
28
29
|
async setup() {
|
|
@@ -33,7 +34,7 @@ let DelegateErrorWorkflow = class DelegateErrorWorkflow extends common_1.BaseWor
|
|
|
33
34
|
'This workflow tests how tool errors are handled and fed back to the LLM.\n\n' +
|
|
34
35
|
'The LLM will deliberately trigger errors, then self-correct.',
|
|
35
36
|
});
|
|
36
|
-
await this.repository.save(
|
|
37
|
+
await this.repository.save(llm_provider_module_1.LlmMessageDocument, {
|
|
37
38
|
role: 'user',
|
|
38
39
|
content: 'Follow the instructions in your system prompt exactly. ' +
|
|
39
40
|
'Start with step 1: call strictSchema with no arguments.',
|
|
@@ -41,41 +42,42 @@ let DelegateErrorWorkflow = class DelegateErrorWorkflow extends common_1.BaseWor
|
|
|
41
42
|
}
|
|
42
43
|
async llmTurn() {
|
|
43
44
|
this.turnCount++;
|
|
44
|
-
const result = await this.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
'Complete these steps IN ORDER. Do exactly one step per turn:\n\n' +
|
|
49
|
-
'1. Call the `strictSchema` tool with NO arguments (empty object {}). This will fail — that is expected.\n' +
|
|
50
|
-
'2. After seeing the validation error, call `strictSchema` correctly with { "name": "World" }.\n' +
|
|
51
|
-
'3. Call the `runtimeError` tool with { "shouldFail": true }. This will fail — that is expected.\n' +
|
|
52
|
-
'4. After seeing the runtime error, call `runtimeError` with { "shouldFail": false }.\n' +
|
|
53
|
-
'5. Call the `failingSubWorkflow` tool with {}. This launches a sub-workflow that will fail — that is expected.\n' +
|
|
54
|
-
'6. After seeing the sub-workflow error, respond with a brief summary of what happened (do NOT call any tools).\n\n' +
|
|
55
|
-
'IMPORTANT: Only perform ONE step per turn. Do NOT skip steps. Do NOT wait for user input between steps.',
|
|
56
|
-
claude: { model: 'claude-sonnet-4-6' },
|
|
57
|
-
messagesSearchTag: 'message',
|
|
58
|
-
tools: ['strictSchema', 'runtimeError', 'failingSubWorkflow'],
|
|
45
|
+
const result = await this.llmGenerateText.call({}, {
|
|
46
|
+
config: {
|
|
47
|
+
system: this.render(__dirname + '/templates/system.md'),
|
|
48
|
+
},
|
|
59
49
|
});
|
|
60
50
|
this.llmResult = result.data;
|
|
51
|
+
this.llmMeta = result.metadata;
|
|
61
52
|
}
|
|
62
53
|
async executeToolCalls() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
await this.repository.save(llm_provider_module_1.LlmMessageDocument, this.llmResult.message, {
|
|
55
|
+
meta: { response: this.llmResult.response, provider: this.llmMeta.provider },
|
|
56
|
+
});
|
|
57
|
+
const result = await this.llmDelegateToolCalls.call({
|
|
58
|
+
message: this.llmResult.message,
|
|
66
59
|
callback: { transition: 'toolResultReceived' },
|
|
67
60
|
});
|
|
68
61
|
this.delegateResult = result.data;
|
|
69
62
|
}
|
|
70
63
|
async toolResultReceived(payload) {
|
|
71
|
-
const result = await this.
|
|
64
|
+
const result = await this.llmUpdateToolResult.call({
|
|
72
65
|
delegateResult: this.delegateResult,
|
|
73
66
|
completedTool: payload,
|
|
74
|
-
document: claude_module_1.ClaudeMessageDocument,
|
|
75
67
|
});
|
|
76
68
|
this.delegateResult = result.data;
|
|
77
69
|
}
|
|
78
|
-
async toolsComplete() {
|
|
70
|
+
async toolsComplete() {
|
|
71
|
+
await this.repository.save(llm_provider_module_1.LlmMessageDocument, {
|
|
72
|
+
role: 'user',
|
|
73
|
+
content: this.delegateResult.toolResults.map((tr) => ({
|
|
74
|
+
type: 'tool_result',
|
|
75
|
+
toolCallId: tr.toolCallId,
|
|
76
|
+
content: tr.content ?? '',
|
|
77
|
+
isError: tr.isError ?? false,
|
|
78
|
+
})),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
79
81
|
async cancelPendingTools() {
|
|
80
82
|
const workflowId = this.ctx.context.workflowId;
|
|
81
83
|
if (workflowId) {
|
|
@@ -83,31 +85,37 @@ let DelegateErrorWorkflow = class DelegateErrorWorkflow extends common_1.BaseWor
|
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
async respond() {
|
|
86
|
-
await this.repository.save(
|
|
88
|
+
await this.repository.save(llm_provider_module_1.LlmMessageDocument, this.llmResult.message, {
|
|
89
|
+
meta: { response: this.llmResult.response, provider: this.llmMeta.provider },
|
|
90
|
+
});
|
|
87
91
|
}
|
|
88
92
|
hasToolCalls() {
|
|
89
|
-
return this.llmResult?.
|
|
93
|
+
return this.llmResult?.message.stopReason === 'tool_use';
|
|
90
94
|
}
|
|
91
95
|
allToolsComplete() {
|
|
92
96
|
return !!this.delegateResult?.allCompleted;
|
|
93
97
|
}
|
|
94
98
|
isEndTurn() {
|
|
95
|
-
return this.llmResult?.
|
|
99
|
+
return this.llmResult?.message.stopReason === 'end_turn';
|
|
96
100
|
}
|
|
97
101
|
};
|
|
98
102
|
exports.DelegateErrorWorkflow = DelegateErrorWorkflow;
|
|
99
103
|
__decorate([
|
|
100
|
-
(0, common_1.InjectTool)(
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
(0, common_1.InjectTool)({
|
|
105
|
+
provider: 'claude',
|
|
106
|
+
model: 'claude-sonnet-4-6',
|
|
107
|
+
tools: ['strictSchema', 'runtimeError', 'failingSubWorkflow'],
|
|
108
|
+
}),
|
|
109
|
+
__metadata("design:type", llm_provider_module_1.LlmGenerateTextTool)
|
|
110
|
+
], DelegateErrorWorkflow.prototype, "llmGenerateText", void 0);
|
|
103
111
|
__decorate([
|
|
104
|
-
(0, common_1.InjectTool)(),
|
|
105
|
-
__metadata("design:type",
|
|
106
|
-
], DelegateErrorWorkflow.prototype, "
|
|
112
|
+
(0, common_1.InjectTool)({ provider: 'claude' }),
|
|
113
|
+
__metadata("design:type", llm_provider_module_1.LlmDelegateToolCallsTool)
|
|
114
|
+
], DelegateErrorWorkflow.prototype, "llmDelegateToolCalls", void 0);
|
|
107
115
|
__decorate([
|
|
108
|
-
(0, common_1.InjectTool)(),
|
|
109
|
-
__metadata("design:type",
|
|
110
|
-
], DelegateErrorWorkflow.prototype, "
|
|
116
|
+
(0, common_1.InjectTool)({ provider: 'claude' }),
|
|
117
|
+
__metadata("design:type", llm_provider_module_1.LlmUpdateToolResultTool)
|
|
118
|
+
], DelegateErrorWorkflow.prototype, "llmUpdateToolResult", void 0);
|
|
111
119
|
__decorate([
|
|
112
120
|
(0, common_1.InjectTool)(),
|
|
113
121
|
__metadata("design:type", strict_schema_tool_1.StrictSchemaTool)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delegate-error.workflow.js","sourceRoot":"","sources":["../src/delegate-error.workflow.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"delegate-error.workflow.js","sourceRoot":"","sources":["../src/delegate-error.workflow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
You are a test assistant that follows instructions precisely. You are fully autonomous — do NOT ask the user for input or confirmation. Just proceed through each step on your own.
|
|
2
|
+
|
|
3
|
+
Complete these steps IN ORDER. Do exactly one step per turn:
|
|
4
|
+
|
|
5
|
+
1. Call the `strictSchema` tool with NO arguments (empty object {}). This will fail — that is expected.
|
|
6
|
+
2. After seeing the validation error, call `strictSchema` correctly with { "name": "World" }.
|
|
7
|
+
3. Call the `runtimeError` tool with { "shouldFail": true }. This will fail — that is expected.
|
|
8
|
+
4. After seeing the runtime error, call `runtimeError` with { "shouldFail": false }.
|
|
9
|
+
5. Call the `failingSubWorkflow` tool with {}. This launches a sub-workflow that will fail — that is expected.
|
|
10
|
+
6. After seeing the sub-workflow error, respond with a brief summary of what happened (do NOT call any tools).
|
|
11
|
+
|
|
12
|
+
IMPORTANT: Only perform ONE step per turn. Do NOT skip steps. Do NOT wait for user input between steps.
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"example",
|
|
10
10
|
"workflow"
|
|
11
11
|
],
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.22.0",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Jakob Klippel",
|
|
@@ -26,13 +26,14 @@
|
|
|
26
26
|
"compile": "tsc --noEmit",
|
|
27
27
|
"format": "prettier --write .",
|
|
28
28
|
"lint": "eslint .",
|
|
29
|
-
"test": "
|
|
29
|
+
"test": "vitest run",
|
|
30
30
|
"watch": "nest build --watch"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@loopstack/claude-module": "^0.
|
|
34
|
-
"@loopstack/
|
|
35
|
-
"@loopstack/
|
|
33
|
+
"@loopstack/claude-module": "^0.24.0",
|
|
34
|
+
"@loopstack/llm-provider-module": "^0.3.0",
|
|
35
|
+
"@loopstack/common": "^0.30.0",
|
|
36
|
+
"@loopstack/core": "^0.30.0",
|
|
36
37
|
"@nestjs/common": "^11.1.19",
|
|
37
38
|
"zod": "^4.3.6"
|
|
38
39
|
},
|
|
@@ -40,34 +41,9 @@
|
|
|
40
41
|
"dist",
|
|
41
42
|
"src"
|
|
42
43
|
],
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"transform": {
|
|
48
|
-
"^.+\\.ts$": "ts-jest"
|
|
49
|
-
},
|
|
50
|
-
"testTimeout": 10000,
|
|
51
|
-
"forceExit": true,
|
|
52
|
-
"maxWorkers": 1
|
|
53
|
-
},
|
|
54
|
-
"loopstack": {
|
|
55
|
-
"modules": [
|
|
56
|
-
{
|
|
57
|
-
"path": "src/delegate-error-example.module.ts",
|
|
58
|
-
"className": "DelegateErrorExampleModule"
|
|
59
|
-
}
|
|
60
|
-
],
|
|
61
|
-
"workflows": [
|
|
62
|
-
{
|
|
63
|
-
"path": "src/delegate-error.workflow.ts",
|
|
64
|
-
"className": "DelegateErrorWorkflow",
|
|
65
|
-
"propertyName": "delegateError"
|
|
66
|
-
}
|
|
67
|
-
],
|
|
68
|
-
"installModes": [
|
|
69
|
-
"add",
|
|
70
|
-
"install"
|
|
71
|
-
]
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"vitest": "^4.1.6",
|
|
46
|
+
"@swc/core": "^1.15.33",
|
|
47
|
+
"unplugin-swc": "^1.5.9"
|
|
72
48
|
}
|
|
73
49
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { TestingModule } from '@nestjs/testing';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
3
|
+
import { ClaudeModule } from '@loopstack/claude-module';
|
|
4
|
+
import { WorkflowProcessorService } from '@loopstack/core';
|
|
5
|
+
import { LlmGenerateTextTool } from '@loopstack/llm-provider-module';
|
|
4
6
|
import { ToolMock, createStatelessContext, createWorkflowTest } from '@loopstack/testing';
|
|
5
7
|
import { DelegateErrorWorkflow } from '../delegate-error.workflow';
|
|
6
8
|
import { FailingSubWorkflowTool } from '../tools/failing-sub-workflow.tool';
|
|
@@ -11,17 +13,20 @@ import { FailingWorkflow } from '../workflows/failing.workflow';
|
|
|
11
13
|
/**
|
|
12
14
|
* Helper to create a mock LLM response with a single tool_use block.
|
|
13
15
|
*/
|
|
14
|
-
function mockToolUseResponse(toolName: string,
|
|
16
|
+
function mockToolUseResponse(toolName: string, args: Record<string, unknown>, toolCallId = 'toolu_test_1') {
|
|
15
17
|
return {
|
|
16
18
|
data: {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
message: {
|
|
20
|
+
id: 'msg_test',
|
|
21
|
+
role: 'assistant',
|
|
22
|
+
content: [
|
|
23
|
+
{ type: 'text', text: `Calling ${toolName}...` },
|
|
24
|
+
{ type: 'tool_call', id: toolCallId, name: toolName, args },
|
|
25
|
+
],
|
|
26
|
+
stopReason: 'tool_use',
|
|
27
|
+
},
|
|
24
28
|
},
|
|
29
|
+
metadata: { provider: 'claude', model: 'claude-sonnet-4-6' },
|
|
25
30
|
};
|
|
26
31
|
}
|
|
27
32
|
|
|
@@ -31,11 +36,14 @@ function mockToolUseResponse(toolName: string, input: Record<string, unknown>, t
|
|
|
31
36
|
function mockEndTurnResponse(text: string) {
|
|
32
37
|
return {
|
|
33
38
|
data: {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
message: {
|
|
40
|
+
id: 'msg_final',
|
|
41
|
+
role: 'assistant',
|
|
42
|
+
content: [{ type: 'text', text }],
|
|
43
|
+
stopReason: 'end_turn',
|
|
44
|
+
},
|
|
38
45
|
},
|
|
46
|
+
metadata: { provider: 'claude', model: 'claude-sonnet-4-6' },
|
|
39
47
|
};
|
|
40
48
|
}
|
|
41
49
|
|
|
@@ -43,20 +51,20 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
43
51
|
let module: TestingModule;
|
|
44
52
|
let workflow: DelegateErrorWorkflow;
|
|
45
53
|
let processor: WorkflowProcessorService;
|
|
46
|
-
let
|
|
54
|
+
let mockLlm: ToolMock;
|
|
47
55
|
|
|
48
56
|
beforeEach(async () => {
|
|
49
57
|
module = await createWorkflowTest()
|
|
50
58
|
.forWorkflow(DelegateErrorWorkflow)
|
|
51
|
-
.withImports(
|
|
52
|
-
.withToolOverride(
|
|
59
|
+
.withImports(ClaudeModule)
|
|
60
|
+
.withToolOverride(LlmGenerateTextTool)
|
|
53
61
|
// Real tools — we want to test actual validation and runtime errors
|
|
54
62
|
.withProviders(StrictSchemaTool, RuntimeErrorTool, FailingSubWorkflowTool, FailingWorkflow)
|
|
55
63
|
.compile();
|
|
56
64
|
|
|
57
65
|
workflow = module.get(DelegateErrorWorkflow);
|
|
58
66
|
processor = module.get(WorkflowProcessorService);
|
|
59
|
-
|
|
67
|
+
mockLlm = module.get(LlmGenerateTextTool);
|
|
60
68
|
});
|
|
61
69
|
|
|
62
70
|
afterEach(async () => {
|
|
@@ -68,9 +76,8 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
68
76
|
});
|
|
69
77
|
|
|
70
78
|
describe('validation error (bad schema args)', () => {
|
|
71
|
-
it('should capture validation error as
|
|
72
|
-
|
|
73
|
-
mockClaude.call
|
|
79
|
+
it('should capture validation error as isError tool result and loop back to ready', async () => {
|
|
80
|
+
mockLlm.call
|
|
74
81
|
.mockResolvedValueOnce(mockToolUseResponse('strictSchema', {}))
|
|
75
82
|
.mockResolvedValueOnce(mockEndTurnResponse('Done.'));
|
|
76
83
|
|
|
@@ -79,24 +86,20 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
79
86
|
|
|
80
87
|
expect(result.hasError).toBe(false);
|
|
81
88
|
expect(result.place).toBe('end');
|
|
89
|
+
expect(mockLlm.call).toHaveBeenCalledTimes(2);
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// Should have tool result documents with is_error
|
|
89
|
-
const toolResultDocs = result.documents.filter((d) =>
|
|
90
|
-
d.content?.toolResults?.some((tr: { is_error?: boolean }) => tr.is_error),
|
|
91
|
+
const toolResultDocs = result.documents.filter(
|
|
92
|
+
(d) =>
|
|
93
|
+
Array.isArray(d.content?.content) &&
|
|
94
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
91
95
|
);
|
|
92
96
|
expect(toolResultDocs.length).toBeGreaterThan(0);
|
|
93
97
|
});
|
|
94
98
|
});
|
|
95
99
|
|
|
96
100
|
describe('runtime error (tool throws)', () => {
|
|
97
|
-
it('should capture runtime error as
|
|
98
|
-
|
|
99
|
-
mockClaude.call
|
|
101
|
+
it('should capture runtime error as isError tool result and loop back to ready', async () => {
|
|
102
|
+
mockLlm.call
|
|
100
103
|
.mockResolvedValueOnce(mockToolUseResponse('runtimeError', { shouldFail: true }))
|
|
101
104
|
.mockResolvedValueOnce(mockEndTurnResponse('Done.'));
|
|
102
105
|
|
|
@@ -105,10 +108,12 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
105
108
|
|
|
106
109
|
expect(result.hasError).toBe(false);
|
|
107
110
|
expect(result.place).toBe('end');
|
|
108
|
-
expect(
|
|
111
|
+
expect(mockLlm.call).toHaveBeenCalledTimes(2);
|
|
109
112
|
|
|
110
|
-
const toolResultDocs = result.documents.filter(
|
|
111
|
-
d
|
|
113
|
+
const toolResultDocs = result.documents.filter(
|
|
114
|
+
(d) =>
|
|
115
|
+
Array.isArray(d.content?.content) &&
|
|
116
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
112
117
|
);
|
|
113
118
|
expect(toolResultDocs.length).toBeGreaterThan(0);
|
|
114
119
|
});
|
|
@@ -116,8 +121,7 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
116
121
|
|
|
117
122
|
describe('successful tool call', () => {
|
|
118
123
|
it('should process successful tool call without errors', async () => {
|
|
119
|
-
|
|
120
|
-
mockClaude.call
|
|
124
|
+
mockLlm.call
|
|
121
125
|
.mockResolvedValueOnce(mockToolUseResponse('strictSchema', { name: 'World' }))
|
|
122
126
|
.mockResolvedValueOnce(mockEndTurnResponse('Done.'));
|
|
123
127
|
|
|
@@ -126,19 +130,20 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
126
130
|
|
|
127
131
|
expect(result.hasError).toBe(false);
|
|
128
132
|
expect(result.place).toBe('end');
|
|
129
|
-
expect(
|
|
133
|
+
expect(mockLlm.call).toHaveBeenCalledTimes(2);
|
|
130
134
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
const errorToolResults = result.documents.filter(
|
|
136
|
+
(d) =>
|
|
137
|
+
Array.isArray(d.content?.content) &&
|
|
138
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
134
139
|
);
|
|
135
140
|
expect(errorToolResults).toHaveLength(0);
|
|
136
141
|
});
|
|
137
142
|
});
|
|
138
143
|
|
|
139
144
|
describe('error metadata in tool result documents', () => {
|
|
140
|
-
it('should include validation error message in
|
|
141
|
-
|
|
145
|
+
it('should include validation error message in isError tool result', async () => {
|
|
146
|
+
mockLlm.call
|
|
142
147
|
.mockResolvedValueOnce(mockToolUseResponse('strictSchema', {}))
|
|
143
148
|
.mockResolvedValueOnce(mockEndTurnResponse('Done.'));
|
|
144
149
|
|
|
@@ -147,19 +152,22 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
147
152
|
|
|
148
153
|
expect(result.hasError).toBe(false);
|
|
149
154
|
|
|
150
|
-
const toolResultDocs = result.documents.filter(
|
|
151
|
-
d
|
|
155
|
+
const toolResultDocs = result.documents.filter(
|
|
156
|
+
(d) =>
|
|
157
|
+
Array.isArray(d.content?.content) &&
|
|
158
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
152
159
|
);
|
|
153
160
|
expect(toolResultDocs.length).toBeGreaterThan(0);
|
|
154
161
|
|
|
155
|
-
const errorResult = toolResultDocs[0].content.
|
|
162
|
+
const errorResult = toolResultDocs[0].content.content.find(
|
|
163
|
+
(b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError,
|
|
164
|
+
);
|
|
156
165
|
expect(errorResult.content).toBeDefined();
|
|
157
|
-
// Zod v4 error message for missing required string field
|
|
158
166
|
expect(errorResult.content).toContain('invalid_type');
|
|
159
167
|
});
|
|
160
168
|
|
|
161
|
-
it('should include runtime error message in
|
|
162
|
-
|
|
169
|
+
it('should include runtime error message in isError tool result', async () => {
|
|
170
|
+
mockLlm.call
|
|
163
171
|
.mockResolvedValueOnce(mockToolUseResponse('runtimeError', { shouldFail: true }))
|
|
164
172
|
.mockResolvedValueOnce(mockEndTurnResponse('Done.'));
|
|
165
173
|
|
|
@@ -168,49 +176,60 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
168
176
|
|
|
169
177
|
expect(result.hasError).toBe(false);
|
|
170
178
|
|
|
171
|
-
const toolResultDocs = result.documents.filter(
|
|
172
|
-
d
|
|
179
|
+
const toolResultDocs = result.documents.filter(
|
|
180
|
+
(d) =>
|
|
181
|
+
Array.isArray(d.content?.content) &&
|
|
182
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
173
183
|
);
|
|
174
184
|
expect(toolResultDocs.length).toBeGreaterThan(0);
|
|
175
185
|
|
|
176
|
-
const errorResult = toolResultDocs[0].content.
|
|
186
|
+
const errorResult = toolResultDocs[0].content.content.find(
|
|
187
|
+
(b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError,
|
|
188
|
+
);
|
|
177
189
|
expect(errorResult.content).toContain('external service unavailable');
|
|
178
190
|
});
|
|
179
191
|
});
|
|
180
192
|
|
|
181
193
|
describe('validation and runtime errors produce identical structure', () => {
|
|
182
|
-
it('should produce the same
|
|
183
|
-
|
|
184
|
-
|
|
194
|
+
it('should produce the same isError tool result shape for both error types', async () => {
|
|
195
|
+
mockLlm.call
|
|
196
|
+
.mockResolvedValueOnce(mockToolUseResponse('strictSchema', {}))
|
|
197
|
+
.mockResolvedValueOnce(mockEndTurnResponse('Recovered.'));
|
|
185
198
|
const context1 = createStatelessContext();
|
|
186
199
|
const result1 = await processor.process(workflow, {}, context1);
|
|
187
200
|
|
|
188
|
-
const validationErrorDoc = result1.documents.find(
|
|
189
|
-
d
|
|
201
|
+
const validationErrorDoc = result1.documents.find(
|
|
202
|
+
(d) =>
|
|
203
|
+
Array.isArray(d.content?.content) &&
|
|
204
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
205
|
+
);
|
|
206
|
+
const validationError = validationErrorDoc?.content.content.find(
|
|
207
|
+
(b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError,
|
|
190
208
|
);
|
|
191
|
-
const validationError = validationErrorDoc?.content.toolResults.find((tr: { is_error?: boolean }) => tr.is_error);
|
|
192
209
|
|
|
193
|
-
|
|
194
|
-
|
|
210
|
+
mockLlm.call
|
|
211
|
+
.mockResolvedValueOnce(mockToolUseResponse('runtimeError', { shouldFail: true }))
|
|
212
|
+
.mockResolvedValueOnce(mockEndTurnResponse('Recovered.'));
|
|
195
213
|
const context2 = createStatelessContext();
|
|
196
214
|
const result2 = await processor.process(workflow, {}, context2);
|
|
197
215
|
|
|
198
|
-
const runtimeErrorDoc = result2.documents.find(
|
|
199
|
-
d
|
|
216
|
+
const runtimeErrorDoc = result2.documents.find(
|
|
217
|
+
(d) =>
|
|
218
|
+
Array.isArray(d.content?.content) &&
|
|
219
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
220
|
+
);
|
|
221
|
+
const runtimeError = runtimeErrorDoc?.content.content.find(
|
|
222
|
+
(b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError,
|
|
200
223
|
);
|
|
201
|
-
const runtimeError = runtimeErrorDoc?.content.toolResults.find((tr: { is_error?: boolean }) => tr.is_error);
|
|
202
224
|
|
|
203
|
-
// Both should have the same structure
|
|
204
225
|
expect(validationError).toBeDefined();
|
|
205
226
|
expect(runtimeError).toBeDefined();
|
|
206
|
-
expect(validationError.
|
|
207
|
-
expect(runtimeError.
|
|
208
|
-
expect(validationError.is_error).toBe(true);
|
|
209
|
-
expect(runtimeError.is_error).toBe(true);
|
|
227
|
+
expect(validationError.isError).toBe(true);
|
|
228
|
+
expect(runtimeError.isError).toBe(true);
|
|
210
229
|
expect(typeof validationError.content).toBe('string');
|
|
211
230
|
expect(typeof runtimeError.content).toBe('string');
|
|
212
|
-
expect(typeof validationError.
|
|
213
|
-
expect(typeof runtimeError.
|
|
231
|
+
expect(typeof validationError.toolCallId).toBe('string');
|
|
232
|
+
expect(typeof runtimeError.toolCallId).toBe('string');
|
|
214
233
|
});
|
|
215
234
|
});
|
|
216
235
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Module } from '@nestjs/common';
|
|
2
2
|
import { ClaudeModule } from '@loopstack/claude-module';
|
|
3
|
-
import { LoopCoreModule } from '@loopstack/core';
|
|
4
3
|
import { DelegateErrorWorkflow } from './delegate-error.workflow';
|
|
5
4
|
import { FailingSubWorkflowTool } from './tools/failing-sub-workflow.tool';
|
|
6
5
|
import { RuntimeErrorTool } from './tools/runtime-error.tool';
|
|
@@ -8,7 +7,7 @@ import { StrictSchemaTool } from './tools/strict-schema.tool';
|
|
|
8
7
|
import { FailingWorkflow } from './workflows/failing.workflow';
|
|
9
8
|
|
|
10
9
|
@Module({
|
|
11
|
-
imports: [
|
|
10
|
+
imports: [ClaudeModule],
|
|
12
11
|
providers: [StrictSchemaTool, RuntimeErrorTool, FailingSubWorkflowTool, FailingWorkflow, DelegateErrorWorkflow],
|
|
13
12
|
exports: [DelegateErrorWorkflow],
|
|
14
13
|
})
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ClaudeGenerateText,
|
|
3
|
-
ClaudeGenerateTextResult,
|
|
4
|
-
ClaudeMessageDocument,
|
|
5
|
-
DelegateToolCalls,
|
|
6
|
-
DelegateToolCallsResult,
|
|
7
|
-
UpdateToolResult,
|
|
8
|
-
} from '@loopstack/claude-module';
|
|
9
1
|
import {
|
|
10
2
|
BaseWorkflow,
|
|
11
3
|
Final,
|
|
@@ -17,6 +9,13 @@ import {
|
|
|
17
9
|
Transition,
|
|
18
10
|
Workflow,
|
|
19
11
|
} from '@loopstack/common';
|
|
12
|
+
import type { LlmDelegateResult, LlmGenerateTextResult, LlmResultMeta } from '@loopstack/llm-provider-module';
|
|
13
|
+
import {
|
|
14
|
+
LlmDelegateToolCallsTool,
|
|
15
|
+
LlmGenerateTextTool,
|
|
16
|
+
LlmMessageDocument,
|
|
17
|
+
LlmUpdateToolResultTool,
|
|
18
|
+
} from '@loopstack/llm-provider-module';
|
|
20
19
|
import { FailingSubWorkflowTool } from './tools/failing-sub-workflow.tool';
|
|
21
20
|
import { RuntimeErrorTool } from './tools/runtime-error.tool';
|
|
22
21
|
import { StrictSchemaTool } from './tools/strict-schema.tool';
|
|
@@ -33,15 +32,21 @@ import { StrictSchemaTool } from './tools/strict-schema.tool';
|
|
|
33
32
|
uiConfig: __dirname + '/delegate-error.ui.yaml',
|
|
34
33
|
})
|
|
35
34
|
export class DelegateErrorWorkflow extends BaseWorkflow {
|
|
36
|
-
@InjectTool(
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
@InjectTool({
|
|
36
|
+
provider: 'claude',
|
|
37
|
+
model: 'claude-sonnet-4-6',
|
|
38
|
+
tools: ['strictSchema', 'runtimeError', 'failingSubWorkflow'],
|
|
39
|
+
})
|
|
40
|
+
llmGenerateText: LlmGenerateTextTool;
|
|
41
|
+
@InjectTool({ provider: 'claude' }) llmDelegateToolCalls: LlmDelegateToolCallsTool;
|
|
42
|
+
@InjectTool({ provider: 'claude' }) llmUpdateToolResult: LlmUpdateToolResultTool;
|
|
39
43
|
@InjectTool() strictSchema: StrictSchemaTool;
|
|
40
44
|
@InjectTool() runtimeError: RuntimeErrorTool;
|
|
41
45
|
@InjectTool() failingSubWorkflow: FailingSubWorkflowTool;
|
|
42
46
|
|
|
43
|
-
llmResult?:
|
|
44
|
-
|
|
47
|
+
llmResult?: LlmGenerateTextResult;
|
|
48
|
+
llmMeta?: LlmResultMeta;
|
|
49
|
+
delegateResult?: LlmDelegateResult;
|
|
45
50
|
turnCount!: number;
|
|
46
51
|
|
|
47
52
|
@Initial({ to: 'ready' })
|
|
@@ -56,7 +61,7 @@ export class DelegateErrorWorkflow extends BaseWorkflow {
|
|
|
56
61
|
'The LLM will deliberately trigger errors, then self-correct.',
|
|
57
62
|
});
|
|
58
63
|
|
|
59
|
-
await this.repository.save(
|
|
64
|
+
await this.repository.save(LlmMessageDocument, {
|
|
60
65
|
role: 'user',
|
|
61
66
|
content:
|
|
62
67
|
'Follow the instructions in your system prompt exactly. ' +
|
|
@@ -67,32 +72,27 @@ export class DelegateErrorWorkflow extends BaseWorkflow {
|
|
|
67
72
|
@Transition({ from: 'ready', to: 'prompt_executed' })
|
|
68
73
|
async llmTurn() {
|
|
69
74
|
this.turnCount++;
|
|
70
|
-
const result: ToolResult<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
'3. Call the `runtimeError` tool with { "shouldFail": true }. This will fail — that is expected.\n' +
|
|
79
|
-
'4. After seeing the runtime error, call `runtimeError` with { "shouldFail": false }.\n' +
|
|
80
|
-
'5. Call the `failingSubWorkflow` tool with {}. This launches a sub-workflow that will fail — that is expected.\n' +
|
|
81
|
-
'6. After seeing the sub-workflow error, respond with a brief summary of what happened (do NOT call any tools).\n\n' +
|
|
82
|
-
'IMPORTANT: Only perform ONE step per turn. Do NOT skip steps. Do NOT wait for user input between steps.',
|
|
83
|
-
claude: { model: 'claude-sonnet-4-6' },
|
|
84
|
-
messagesSearchTag: 'message',
|
|
85
|
-
tools: ['strictSchema', 'runtimeError', 'failingSubWorkflow'],
|
|
86
|
-
});
|
|
75
|
+
const result: ToolResult<LlmGenerateTextResult, LlmResultMeta> = await this.llmGenerateText.call(
|
|
76
|
+
{},
|
|
77
|
+
{
|
|
78
|
+
config: {
|
|
79
|
+
system: this.render(__dirname + '/templates/system.md'),
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
);
|
|
87
83
|
this.llmResult = result.data;
|
|
84
|
+
this.llmMeta = result.metadata;
|
|
88
85
|
}
|
|
89
86
|
|
|
90
87
|
@Transition({ from: 'prompt_executed', to: 'awaiting_tools', priority: 10 })
|
|
91
88
|
@Guard('hasToolCalls')
|
|
92
89
|
async executeToolCalls() {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
await this.repository.save(LlmMessageDocument, this.llmResult!.message, {
|
|
91
|
+
meta: { response: this.llmResult!.response, provider: this.llmMeta!.provider },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const result: ToolResult<LlmDelegateResult> = await this.llmDelegateToolCalls.call({
|
|
95
|
+
message: this.llmResult!.message,
|
|
96
96
|
callback: { transition: 'toolResultReceived' },
|
|
97
97
|
});
|
|
98
98
|
this.delegateResult = result.data;
|
|
@@ -100,17 +100,26 @@ export class DelegateErrorWorkflow extends BaseWorkflow {
|
|
|
100
100
|
|
|
101
101
|
@Transition({ from: 'awaiting_tools', to: 'awaiting_tools', wait: true })
|
|
102
102
|
async toolResultReceived(payload: unknown) {
|
|
103
|
-
const result = await this.
|
|
103
|
+
const result = await this.llmUpdateToolResult.call({
|
|
104
104
|
delegateResult: this.delegateResult!,
|
|
105
105
|
completedTool: payload,
|
|
106
|
-
document: ClaudeMessageDocument,
|
|
107
106
|
});
|
|
108
|
-
this.delegateResult = result.data as
|
|
107
|
+
this.delegateResult = result.data as LlmDelegateResult;
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
@Transition({ from: 'awaiting_tools', to: 'ready' })
|
|
112
111
|
@Guard('allToolsComplete')
|
|
113
|
-
async toolsComplete() {
|
|
112
|
+
async toolsComplete() {
|
|
113
|
+
await this.repository.save(LlmMessageDocument, {
|
|
114
|
+
role: 'user',
|
|
115
|
+
content: this.delegateResult!.toolResults.map((tr) => ({
|
|
116
|
+
type: 'tool_result' as const,
|
|
117
|
+
toolCallId: tr.toolCallId,
|
|
118
|
+
content: tr.content ?? '',
|
|
119
|
+
isError: tr.isError ?? false,
|
|
120
|
+
})),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
114
123
|
|
|
115
124
|
@Transition({ from: 'awaiting_tools', to: 'ready', wait: true })
|
|
116
125
|
async cancelPendingTools() {
|
|
@@ -123,11 +132,13 @@ export class DelegateErrorWorkflow extends BaseWorkflow {
|
|
|
123
132
|
@Final({ from: 'prompt_executed' })
|
|
124
133
|
@Guard('isEndTurn')
|
|
125
134
|
async respond() {
|
|
126
|
-
await this.repository.save(
|
|
135
|
+
await this.repository.save(LlmMessageDocument, this.llmResult!.message, {
|
|
136
|
+
meta: { response: this.llmResult!.response, provider: this.llmMeta!.provider },
|
|
137
|
+
});
|
|
127
138
|
}
|
|
128
139
|
|
|
129
140
|
private hasToolCalls(): boolean {
|
|
130
|
-
return this.llmResult?.
|
|
141
|
+
return this.llmResult?.message.stopReason === 'tool_use';
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
private allToolsComplete(): boolean {
|
|
@@ -135,6 +146,6 @@ export class DelegateErrorWorkflow extends BaseWorkflow {
|
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
private isEndTurn(): boolean {
|
|
138
|
-
return this.llmResult?.
|
|
149
|
+
return this.llmResult?.message.stopReason === 'end_turn';
|
|
139
150
|
}
|
|
140
151
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
You are a test assistant that follows instructions precisely. You are fully autonomous — do NOT ask the user for input or confirmation. Just proceed through each step on your own.
|
|
2
|
+
|
|
3
|
+
Complete these steps IN ORDER. Do exactly one step per turn:
|
|
4
|
+
|
|
5
|
+
1. Call the `strictSchema` tool with NO arguments (empty object {}). This will fail — that is expected.
|
|
6
|
+
2. After seeing the validation error, call `strictSchema` correctly with { "name": "World" }.
|
|
7
|
+
3. Call the `runtimeError` tool with { "shouldFail": true }. This will fail — that is expected.
|
|
8
|
+
4. After seeing the runtime error, call `runtimeError` with { "shouldFail": false }.
|
|
9
|
+
5. Call the `failingSubWorkflow` tool with {}. This launches a sub-workflow that will fail — that is expected.
|
|
10
|
+
6. After seeing the sub-workflow error, respond with a brief summary of what happened (do NOT call any tools).
|
|
11
|
+
|
|
12
|
+
IMPORTANT: Only perform ONE step per turn. Do NOT skip steps. Do NOT wait for user input between steps.
|