@loopstack/delegate-error-example-workflow 0.21.6 → 0.21.7
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.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 +5 -4
- package/src/__tests__/delegate-error.workflow.spec.ts +86 -68
- package/src/delegate-error.workflow.ts +52 -41
- package/src/templates/system.md +12 -0
|
@@ -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.21.
|
|
12
|
+
"version": "0.21.7",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Jakob Klippel",
|
|
@@ -30,9 +30,10 @@
|
|
|
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.23.0",
|
|
34
|
+
"@loopstack/llm-provider-module": "^0.2.0",
|
|
35
|
+
"@loopstack/common": "^0.29.0",
|
|
36
|
+
"@loopstack/core": "^0.29.0",
|
|
36
37
|
"@nestjs/common": "^11.1.19",
|
|
37
38
|
"zod": "^4.3.6"
|
|
38
39
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TestingModule } from '@nestjs/testing';
|
|
2
|
-
import {
|
|
2
|
+
import { ClaudeModule } from '@loopstack/claude-module';
|
|
3
3
|
import { LoopCoreModule, WorkflowProcessorService } from '@loopstack/core';
|
|
4
|
+
import { LlmGenerateTextTool } from '@loopstack/llm-provider-module';
|
|
4
5
|
import { ToolMock, createStatelessContext, createWorkflowTest } from '@loopstack/testing';
|
|
5
6
|
import { DelegateErrorWorkflow } from '../delegate-error.workflow';
|
|
6
7
|
import { FailingSubWorkflowTool } from '../tools/failing-sub-workflow.tool';
|
|
@@ -11,17 +12,20 @@ import { FailingWorkflow } from '../workflows/failing.workflow';
|
|
|
11
12
|
/**
|
|
12
13
|
* Helper to create a mock LLM response with a single tool_use block.
|
|
13
14
|
*/
|
|
14
|
-
function mockToolUseResponse(toolName: string,
|
|
15
|
+
function mockToolUseResponse(toolName: string, args: Record<string, unknown>, toolCallId = 'toolu_test_1') {
|
|
15
16
|
return {
|
|
16
17
|
data: {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
message: {
|
|
19
|
+
id: 'msg_test',
|
|
20
|
+
role: 'assistant',
|
|
21
|
+
content: [
|
|
22
|
+
{ type: 'text', text: `Calling ${toolName}...` },
|
|
23
|
+
{ type: 'tool_call', id: toolCallId, name: toolName, args },
|
|
24
|
+
],
|
|
25
|
+
stopReason: 'tool_use',
|
|
26
|
+
},
|
|
24
27
|
},
|
|
28
|
+
metadata: { provider: 'claude', model: 'claude-sonnet-4-6' },
|
|
25
29
|
};
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -31,11 +35,14 @@ function mockToolUseResponse(toolName: string, input: Record<string, unknown>, t
|
|
|
31
35
|
function mockEndTurnResponse(text: string) {
|
|
32
36
|
return {
|
|
33
37
|
data: {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
message: {
|
|
39
|
+
id: 'msg_final',
|
|
40
|
+
role: 'assistant',
|
|
41
|
+
content: [{ type: 'text', text }],
|
|
42
|
+
stopReason: 'end_turn',
|
|
43
|
+
},
|
|
38
44
|
},
|
|
45
|
+
metadata: { provider: 'claude', model: 'claude-sonnet-4-6' },
|
|
39
46
|
};
|
|
40
47
|
}
|
|
41
48
|
|
|
@@ -43,20 +50,20 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
43
50
|
let module: TestingModule;
|
|
44
51
|
let workflow: DelegateErrorWorkflow;
|
|
45
52
|
let processor: WorkflowProcessorService;
|
|
46
|
-
let
|
|
53
|
+
let mockLlm: ToolMock;
|
|
47
54
|
|
|
48
55
|
beforeEach(async () => {
|
|
49
56
|
module = await createWorkflowTest()
|
|
50
57
|
.forWorkflow(DelegateErrorWorkflow)
|
|
51
58
|
.withImports(LoopCoreModule, ClaudeModule)
|
|
52
|
-
.withToolOverride(
|
|
59
|
+
.withToolOverride(LlmGenerateTextTool)
|
|
53
60
|
// Real tools — we want to test actual validation and runtime errors
|
|
54
61
|
.withProviders(StrictSchemaTool, RuntimeErrorTool, FailingSubWorkflowTool, FailingWorkflow)
|
|
55
62
|
.compile();
|
|
56
63
|
|
|
57
64
|
workflow = module.get(DelegateErrorWorkflow);
|
|
58
65
|
processor = module.get(WorkflowProcessorService);
|
|
59
|
-
|
|
66
|
+
mockLlm = module.get(LlmGenerateTextTool);
|
|
60
67
|
});
|
|
61
68
|
|
|
62
69
|
afterEach(async () => {
|
|
@@ -68,9 +75,8 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
68
75
|
});
|
|
69
76
|
|
|
70
77
|
describe('validation error (bad schema args)', () => {
|
|
71
|
-
it('should capture validation error as
|
|
72
|
-
|
|
73
|
-
mockClaude.call
|
|
78
|
+
it('should capture validation error as isError tool result and loop back to ready', async () => {
|
|
79
|
+
mockLlm.call
|
|
74
80
|
.mockResolvedValueOnce(mockToolUseResponse('strictSchema', {}))
|
|
75
81
|
.mockResolvedValueOnce(mockEndTurnResponse('Done.'));
|
|
76
82
|
|
|
@@ -79,24 +85,20 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
79
85
|
|
|
80
86
|
expect(result.hasError).toBe(false);
|
|
81
87
|
expect(result.place).toBe('end');
|
|
88
|
+
expect(mockLlm.call).toHaveBeenCalledTimes(2);
|
|
82
89
|
|
|
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),
|
|
90
|
+
const toolResultDocs = result.documents.filter(
|
|
91
|
+
(d) =>
|
|
92
|
+
Array.isArray(d.content?.content) &&
|
|
93
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
91
94
|
);
|
|
92
95
|
expect(toolResultDocs.length).toBeGreaterThan(0);
|
|
93
96
|
});
|
|
94
97
|
});
|
|
95
98
|
|
|
96
99
|
describe('runtime error (tool throws)', () => {
|
|
97
|
-
it('should capture runtime error as
|
|
98
|
-
|
|
99
|
-
mockClaude.call
|
|
100
|
+
it('should capture runtime error as isError tool result and loop back to ready', async () => {
|
|
101
|
+
mockLlm.call
|
|
100
102
|
.mockResolvedValueOnce(mockToolUseResponse('runtimeError', { shouldFail: true }))
|
|
101
103
|
.mockResolvedValueOnce(mockEndTurnResponse('Done.'));
|
|
102
104
|
|
|
@@ -105,10 +107,12 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
105
107
|
|
|
106
108
|
expect(result.hasError).toBe(false);
|
|
107
109
|
expect(result.place).toBe('end');
|
|
108
|
-
expect(
|
|
110
|
+
expect(mockLlm.call).toHaveBeenCalledTimes(2);
|
|
109
111
|
|
|
110
|
-
const toolResultDocs = result.documents.filter(
|
|
111
|
-
d
|
|
112
|
+
const toolResultDocs = result.documents.filter(
|
|
113
|
+
(d) =>
|
|
114
|
+
Array.isArray(d.content?.content) &&
|
|
115
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
112
116
|
);
|
|
113
117
|
expect(toolResultDocs.length).toBeGreaterThan(0);
|
|
114
118
|
});
|
|
@@ -116,8 +120,7 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
116
120
|
|
|
117
121
|
describe('successful tool call', () => {
|
|
118
122
|
it('should process successful tool call without errors', async () => {
|
|
119
|
-
|
|
120
|
-
mockClaude.call
|
|
123
|
+
mockLlm.call
|
|
121
124
|
.mockResolvedValueOnce(mockToolUseResponse('strictSchema', { name: 'World' }))
|
|
122
125
|
.mockResolvedValueOnce(mockEndTurnResponse('Done.'));
|
|
123
126
|
|
|
@@ -126,19 +129,20 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
126
129
|
|
|
127
130
|
expect(result.hasError).toBe(false);
|
|
128
131
|
expect(result.place).toBe('end');
|
|
129
|
-
expect(
|
|
132
|
+
expect(mockLlm.call).toHaveBeenCalledTimes(2);
|
|
130
133
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
const errorToolResults = result.documents.filter(
|
|
135
|
+
(d) =>
|
|
136
|
+
Array.isArray(d.content?.content) &&
|
|
137
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
134
138
|
);
|
|
135
139
|
expect(errorToolResults).toHaveLength(0);
|
|
136
140
|
});
|
|
137
141
|
});
|
|
138
142
|
|
|
139
143
|
describe('error metadata in tool result documents', () => {
|
|
140
|
-
it('should include validation error message in
|
|
141
|
-
|
|
144
|
+
it('should include validation error message in isError tool result', async () => {
|
|
145
|
+
mockLlm.call
|
|
142
146
|
.mockResolvedValueOnce(mockToolUseResponse('strictSchema', {}))
|
|
143
147
|
.mockResolvedValueOnce(mockEndTurnResponse('Done.'));
|
|
144
148
|
|
|
@@ -147,19 +151,22 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
147
151
|
|
|
148
152
|
expect(result.hasError).toBe(false);
|
|
149
153
|
|
|
150
|
-
const toolResultDocs = result.documents.filter(
|
|
151
|
-
d
|
|
154
|
+
const toolResultDocs = result.documents.filter(
|
|
155
|
+
(d) =>
|
|
156
|
+
Array.isArray(d.content?.content) &&
|
|
157
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
152
158
|
);
|
|
153
159
|
expect(toolResultDocs.length).toBeGreaterThan(0);
|
|
154
160
|
|
|
155
|
-
const errorResult = toolResultDocs[0].content.
|
|
161
|
+
const errorResult = toolResultDocs[0].content.content.find(
|
|
162
|
+
(b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError,
|
|
163
|
+
);
|
|
156
164
|
expect(errorResult.content).toBeDefined();
|
|
157
|
-
// Zod v4 error message for missing required string field
|
|
158
165
|
expect(errorResult.content).toContain('invalid_type');
|
|
159
166
|
});
|
|
160
167
|
|
|
161
|
-
it('should include runtime error message in
|
|
162
|
-
|
|
168
|
+
it('should include runtime error message in isError tool result', async () => {
|
|
169
|
+
mockLlm.call
|
|
163
170
|
.mockResolvedValueOnce(mockToolUseResponse('runtimeError', { shouldFail: true }))
|
|
164
171
|
.mockResolvedValueOnce(mockEndTurnResponse('Done.'));
|
|
165
172
|
|
|
@@ -168,49 +175,60 @@ describe('DelegateErrorWorkflow', () => {
|
|
|
168
175
|
|
|
169
176
|
expect(result.hasError).toBe(false);
|
|
170
177
|
|
|
171
|
-
const toolResultDocs = result.documents.filter(
|
|
172
|
-
d
|
|
178
|
+
const toolResultDocs = result.documents.filter(
|
|
179
|
+
(d) =>
|
|
180
|
+
Array.isArray(d.content?.content) &&
|
|
181
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
173
182
|
);
|
|
174
183
|
expect(toolResultDocs.length).toBeGreaterThan(0);
|
|
175
184
|
|
|
176
|
-
const errorResult = toolResultDocs[0].content.
|
|
185
|
+
const errorResult = toolResultDocs[0].content.content.find(
|
|
186
|
+
(b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError,
|
|
187
|
+
);
|
|
177
188
|
expect(errorResult.content).toContain('external service unavailable');
|
|
178
189
|
});
|
|
179
190
|
});
|
|
180
191
|
|
|
181
192
|
describe('validation and runtime errors produce identical structure', () => {
|
|
182
|
-
it('should produce the same
|
|
183
|
-
|
|
184
|
-
|
|
193
|
+
it('should produce the same isError tool result shape for both error types', async () => {
|
|
194
|
+
mockLlm.call
|
|
195
|
+
.mockResolvedValueOnce(mockToolUseResponse('strictSchema', {}))
|
|
196
|
+
.mockResolvedValueOnce(mockEndTurnResponse('Recovered.'));
|
|
185
197
|
const context1 = createStatelessContext();
|
|
186
198
|
const result1 = await processor.process(workflow, {}, context1);
|
|
187
199
|
|
|
188
|
-
const validationErrorDoc = result1.documents.find(
|
|
189
|
-
d
|
|
200
|
+
const validationErrorDoc = result1.documents.find(
|
|
201
|
+
(d) =>
|
|
202
|
+
Array.isArray(d.content?.content) &&
|
|
203
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
204
|
+
);
|
|
205
|
+
const validationError = validationErrorDoc?.content.content.find(
|
|
206
|
+
(b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError,
|
|
190
207
|
);
|
|
191
|
-
const validationError = validationErrorDoc?.content.toolResults.find((tr: { is_error?: boolean }) => tr.is_error);
|
|
192
208
|
|
|
193
|
-
|
|
194
|
-
|
|
209
|
+
mockLlm.call
|
|
210
|
+
.mockResolvedValueOnce(mockToolUseResponse('runtimeError', { shouldFail: true }))
|
|
211
|
+
.mockResolvedValueOnce(mockEndTurnResponse('Recovered.'));
|
|
195
212
|
const context2 = createStatelessContext();
|
|
196
213
|
const result2 = await processor.process(workflow, {}, context2);
|
|
197
214
|
|
|
198
|
-
const runtimeErrorDoc = result2.documents.find(
|
|
199
|
-
d
|
|
215
|
+
const runtimeErrorDoc = result2.documents.find(
|
|
216
|
+
(d) =>
|
|
217
|
+
Array.isArray(d.content?.content) &&
|
|
218
|
+
d.content.content.some((b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError),
|
|
219
|
+
);
|
|
220
|
+
const runtimeError = runtimeErrorDoc?.content.content.find(
|
|
221
|
+
(b: { type?: string; isError?: boolean }) => b.type === 'tool_result' && b.isError,
|
|
200
222
|
);
|
|
201
|
-
const runtimeError = runtimeErrorDoc?.content.toolResults.find((tr: { is_error?: boolean }) => tr.is_error);
|
|
202
223
|
|
|
203
|
-
// Both should have the same structure
|
|
204
224
|
expect(validationError).toBeDefined();
|
|
205
225
|
expect(runtimeError).toBeDefined();
|
|
206
|
-
expect(validationError.
|
|
207
|
-
expect(runtimeError.
|
|
208
|
-
expect(validationError.is_error).toBe(true);
|
|
209
|
-
expect(runtimeError.is_error).toBe(true);
|
|
226
|
+
expect(validationError.isError).toBe(true);
|
|
227
|
+
expect(runtimeError.isError).toBe(true);
|
|
210
228
|
expect(typeof validationError.content).toBe('string');
|
|
211
229
|
expect(typeof runtimeError.content).toBe('string');
|
|
212
|
-
expect(typeof validationError.
|
|
213
|
-
expect(typeof runtimeError.
|
|
230
|
+
expect(typeof validationError.toolCallId).toBe('string');
|
|
231
|
+
expect(typeof runtimeError.toolCallId).toBe('string');
|
|
214
232
|
});
|
|
215
233
|
});
|
|
216
234
|
});
|
|
@@ -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.
|