@studio-foundation/ralph 0.3.0-beta.1 → 0.3.0-beta.6
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/context-enricher.d.ts +3 -0
- package/dist/context-enricher.d.ts.map +1 -0
- package/dist/context-enricher.js +7 -0
- package/dist/context-enricher.js.map +1 -0
- package/dist/contracts.d.ts +4 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +22 -0
- package/dist/contracts.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +1 -1
- package/dist/index.js.map +1 -0
- package/dist/loop.d.ts +40 -0
- package/dist/loop.d.ts.map +1 -0
- package/dist/loop.js +75 -0
- package/dist/loop.js.map +1 -0
- package/dist/retry-strategy.d.ts +5 -0
- package/dist/retry-strategy.d.ts.map +1 -0
- package/dist/retry-strategy.js +19 -0
- package/dist/retry-strategy.js.map +1 -0
- package/dist/validator.d.ts +14 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +111 -0
- package/dist/validator.js.map +1 -0
- package/package.json +5 -2
- package/ARCHITECTURE.md +0 -41
- package/configs/examples/analysis.contract.yaml +0 -19
- package/configs/examples/code-generation.contract.yaml +0 -21
- package/src/context-enricher.ts +0 -9
- package/src/contracts.ts +0 -28
- package/src/loop.ts +0 -122
- package/src/retry-strategy.ts +0 -23
- package/src/validator.ts +0 -160
- package/tests/loop.test.ts +0 -355
- package/tests/retry.test.ts +0 -87
- package/tests/validator.test.ts +0 -625
- package/tsconfig.json +0 -24
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-enricher.d.ts","sourceRoot":"","sources":["../src/context-enricher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE9D,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAK3E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-enricher.js","sourceRoot":"","sources":["../src/context-enricher.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,iBAAiB,CAAI,KAAoB;IACvD,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,OAAO,GAAG,CAAC;QAC1B,gBAAgB,EAAE,KAAK,CAAC,WAAW;KACpC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAGxE;AAED,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAiBjE"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Load and parse output contracts from YAML
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
export async function loadContract(path) {
|
|
5
|
+
const content = await readFile(path, 'utf-8');
|
|
6
|
+
return parseContract(content);
|
|
7
|
+
}
|
|
8
|
+
export function parseContract(yamlContent) {
|
|
9
|
+
const parsed = yaml.load(yamlContent);
|
|
10
|
+
// Validation basique
|
|
11
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
12
|
+
throw new Error('Invalid contract: expected object');
|
|
13
|
+
}
|
|
14
|
+
if (!parsed.name || typeof parsed.name !== 'string') {
|
|
15
|
+
throw new Error('Invalid contract: missing or invalid name');
|
|
16
|
+
}
|
|
17
|
+
if (parsed.version === undefined || typeof parsed.version !== 'number') {
|
|
18
|
+
throw new Error('Invalid contract: missing or invalid version');
|
|
19
|
+
}
|
|
20
|
+
return parsed;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=contracts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,SAAS,CAAC;AAG3B,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY;IAC7C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC9C,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAmB,CAAC;IAExD,qBAAqB;IACrB,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAElC,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC"}
|
package/dist/loop.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ValidationResult } from '@studio-foundation/contracts';
|
|
2
|
+
export interface ExecutionContext {
|
|
3
|
+
attempt: number;
|
|
4
|
+
previousFailures: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface RetryEvent<T> {
|
|
7
|
+
attempt: number;
|
|
8
|
+
result: T;
|
|
9
|
+
validation: ValidationResult;
|
|
10
|
+
allFailures: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface RetryStrategy {
|
|
13
|
+
getDelay(attempt: number): number;
|
|
14
|
+
}
|
|
15
|
+
export interface RalphConfig<T> {
|
|
16
|
+
executor: (context: ExecutionContext) => Promise<T>;
|
|
17
|
+
validator: (result: T) => ValidationResult | Promise<ValidationResult>;
|
|
18
|
+
maxAttempts: number;
|
|
19
|
+
retryStrategy: RetryStrategy;
|
|
20
|
+
onRetry?: (event: RetryEvent<T>) => void | Promise<void>;
|
|
21
|
+
onSuccess?: (result: T, attempts: number) => void | Promise<void>;
|
|
22
|
+
onExhausted?: (lastResult: T, allFailures: string[]) => void | Promise<void>;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
}
|
|
25
|
+
export type RalphResult<T> = {
|
|
26
|
+
status: 'success';
|
|
27
|
+
result: T;
|
|
28
|
+
attempts: number;
|
|
29
|
+
} | {
|
|
30
|
+
status: 'exhausted';
|
|
31
|
+
lastResult: T;
|
|
32
|
+
failures: string[];
|
|
33
|
+
attempts: number;
|
|
34
|
+
} | {
|
|
35
|
+
status: 'cancelled';
|
|
36
|
+
lastResult?: T;
|
|
37
|
+
attempts: number;
|
|
38
|
+
};
|
|
39
|
+
export declare function ralph<T>(config: RalphConfig<T>): Promise<RalphResult<T>>;
|
|
40
|
+
//# sourceMappingURL=loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,CAAC;IACV,UAAU,EAAE,gBAAgB,CAAC;IAC7B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,QAAQ,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACpD,SAAS,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvE,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,IACrB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5E;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,wBAAsB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CA0E9E"}
|
package/dist/loop.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export async function ralph(config) {
|
|
2
|
+
const { executor, validator, maxAttempts, retryStrategy, onRetry, onSuccess, onExhausted, signal } = config;
|
|
3
|
+
let attempt = 1;
|
|
4
|
+
const allFailures = [];
|
|
5
|
+
let lastResult;
|
|
6
|
+
while (attempt <= maxAttempts) {
|
|
7
|
+
// Check cancellation before each attempt
|
|
8
|
+
if (signal?.aborted) {
|
|
9
|
+
return { status: 'cancelled', lastResult, attempts: attempt };
|
|
10
|
+
}
|
|
11
|
+
// 1. Execute avec contexte
|
|
12
|
+
const context = {
|
|
13
|
+
attempt,
|
|
14
|
+
previousFailures: [...allFailures]
|
|
15
|
+
};
|
|
16
|
+
let result;
|
|
17
|
+
try {
|
|
18
|
+
result = await executor(context);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
// If signal was aborted, the executor likely threw an AbortError
|
|
22
|
+
if (signal?.aborted) {
|
|
23
|
+
return { status: 'cancelled', lastResult, attempts: attempt };
|
|
24
|
+
}
|
|
25
|
+
throw err; // Re-throw non-abort errors
|
|
26
|
+
}
|
|
27
|
+
lastResult = result;
|
|
28
|
+
// 2. Validate
|
|
29
|
+
const validation = await Promise.resolve(validator(result));
|
|
30
|
+
// 3. Si valide → SUCCESS
|
|
31
|
+
if (validation.valid) {
|
|
32
|
+
await onSuccess?.(result, attempt);
|
|
33
|
+
return { status: 'success', result, attempts: attempt };
|
|
34
|
+
}
|
|
35
|
+
// 4. Si invalide → accumuler erreurs
|
|
36
|
+
allFailures.push(...validation.errors);
|
|
37
|
+
// 5. Si dernière tentative → EXHAUSTED
|
|
38
|
+
if (attempt >= maxAttempts) {
|
|
39
|
+
await onExhausted?.(result, allFailures);
|
|
40
|
+
return { status: 'exhausted', lastResult: result, attempts: attempt, failures: allFailures };
|
|
41
|
+
}
|
|
42
|
+
// Check cancellation before retry
|
|
43
|
+
if (signal?.aborted) {
|
|
44
|
+
return { status: 'cancelled', lastResult: result, attempts: attempt };
|
|
45
|
+
}
|
|
46
|
+
// 6. Callback + delay + retry
|
|
47
|
+
const retryEvent = {
|
|
48
|
+
attempt,
|
|
49
|
+
result,
|
|
50
|
+
validation,
|
|
51
|
+
allFailures: [...allFailures]
|
|
52
|
+
};
|
|
53
|
+
await onRetry?.(retryEvent);
|
|
54
|
+
const delay = retryStrategy.getDelay(attempt);
|
|
55
|
+
if (delay > 0) {
|
|
56
|
+
await abortableDelay(delay, signal);
|
|
57
|
+
}
|
|
58
|
+
attempt++;
|
|
59
|
+
}
|
|
60
|
+
// Unreachable mais TypeScript est content
|
|
61
|
+
throw new Error('ralph loop should have returned');
|
|
62
|
+
}
|
|
63
|
+
/** Sleep that resolves immediately if signal is aborted */
|
|
64
|
+
function abortableDelay(ms, signal) {
|
|
65
|
+
if (signal?.aborted)
|
|
66
|
+
return Promise.resolve();
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const timer = setTimeout(resolve, ms);
|
|
69
|
+
signal?.addEventListener('abort', () => {
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
resolve();
|
|
72
|
+
}, { once: true });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=loop.js.map
|
package/dist/loop.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.js","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RetryStrategy } from './loop.js';
|
|
2
|
+
export declare function noDelay(): RetryStrategy;
|
|
3
|
+
export declare function fixedDelay(ms: number): RetryStrategy;
|
|
4
|
+
export declare function exponentialBackoff(baseMs: number, maxMs: number): RetryStrategy;
|
|
5
|
+
//# sourceMappingURL=retry-strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-strategy.d.ts","sourceRoot":"","sources":["../src/retry-strategy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,wBAAgB,OAAO,IAAI,aAAa,CAIvC;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,CAIpD;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAO/E"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function noDelay() {
|
|
2
|
+
return {
|
|
3
|
+
getDelay: () => 0,
|
|
4
|
+
};
|
|
5
|
+
}
|
|
6
|
+
export function fixedDelay(ms) {
|
|
7
|
+
return {
|
|
8
|
+
getDelay: () => ms,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function exponentialBackoff(baseMs, maxMs) {
|
|
12
|
+
return {
|
|
13
|
+
getDelay: (attempt) => {
|
|
14
|
+
const delay = baseMs * Math.pow(2, attempt - 1);
|
|
15
|
+
return Math.min(delay, maxMs);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=retry-strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-strategy.js","sourceRoot":"","sources":["../src/retry-strategy.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,OAAO;IACrB,OAAO;QACL,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;KAClB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EAAU;IACnC,OAAO;QACL,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAE;KACnB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,KAAa;IAC9D,OAAO;QACL,QAAQ,EAAE,CAAC,OAAe,EAAE,EAAE;YAC5B,MAAM,KAAK,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;YAChD,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ValidationResult, OutputContract, ToolCall, ToolCallRequirements } from '@studio-foundation/contracts';
|
|
2
|
+
export type { ToolCallRequirements } from '@studio-foundation/contracts';
|
|
3
|
+
export type Validator<T> = (result: T) => ValidationResult | Promise<ValidationResult>;
|
|
4
|
+
export interface AgentRunResult {
|
|
5
|
+
output: unknown;
|
|
6
|
+
tool_calls: ToolCall[];
|
|
7
|
+
}
|
|
8
|
+
export declare function validateSchema(output: unknown, contract: OutputContract): ValidationResult;
|
|
9
|
+
export declare function validateToolCalls(toolCalls: ToolCall[], requirements?: ToolCallRequirements): ValidationResult;
|
|
10
|
+
export declare function validateRequiredTools(toolCalls: ToolCall[], requirements?: ToolCallRequirements): ValidationResult;
|
|
11
|
+
export declare function validateCountedTools(toolCalls: ToolCall[], requirements?: ToolCallRequirements): ValidationResult;
|
|
12
|
+
export declare function validateToolGroups(toolCalls: ToolCall[], requirements?: ToolCallRequirements): ValidationResult;
|
|
13
|
+
export declare function compose<T>(...validators: Validator<T>[]): Validator<T>;
|
|
14
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAErH,YAAY,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEzE,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAEvF,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,QAAQ,EAAE,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,GAAG,gBAAgB,CA+B1F;AAMD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,YAAY,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CA4B9G;AAOD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,YAAY,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAkBlH;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,YAAY,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAmBjH;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,YAAY,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAoB/G;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAYtE"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export function validateSchema(output, contract) {
|
|
2
|
+
const errors = [];
|
|
3
|
+
const warnings = [];
|
|
4
|
+
// If no schema defined, consider it valid
|
|
5
|
+
if (!contract.schema || !contract.schema.required_fields) {
|
|
6
|
+
return { valid: true, errors, warnings };
|
|
7
|
+
}
|
|
8
|
+
const requiredFields = contract.schema.required_fields;
|
|
9
|
+
// Output must be an object to check fields
|
|
10
|
+
if (output === null || typeof output !== 'object') {
|
|
11
|
+
errors.push(`Expected object output, got ${output === null ? 'null' : typeof output}`);
|
|
12
|
+
return { valid: false, errors, warnings };
|
|
13
|
+
}
|
|
14
|
+
const outputObj = output;
|
|
15
|
+
// Check each required field
|
|
16
|
+
for (const field of requiredFields) {
|
|
17
|
+
if (!(field in outputObj)) {
|
|
18
|
+
errors.push(`Missing required field: ${field}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
valid: errors.length === 0,
|
|
23
|
+
errors,
|
|
24
|
+
warnings
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function isSuccessfulToolCall(tc) {
|
|
28
|
+
return !tc.error;
|
|
29
|
+
}
|
|
30
|
+
export function validateToolCalls(toolCalls, requirements) {
|
|
31
|
+
const errors = [];
|
|
32
|
+
const warnings = [];
|
|
33
|
+
const successfulCount = toolCalls.filter(isSuccessfulToolCall).length;
|
|
34
|
+
const failedCount = toolCalls.length - successfulCount;
|
|
35
|
+
if (requirements?.minimum !== undefined) {
|
|
36
|
+
if (successfulCount < requirements.minimum) {
|
|
37
|
+
const plural = requirements.minimum === 1 ? '' : 's';
|
|
38
|
+
const excluded = failedCount > 0 ? ` (${failedCount} failed excluded)` : '';
|
|
39
|
+
errors.push(`Expected at least ${requirements.minimum} successful tool call${plural}, got ${successfulCount} successful${excluded}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (requirements?.maximum !== undefined) {
|
|
43
|
+
if (successfulCount > requirements.maximum) {
|
|
44
|
+
const plural = successfulCount === 1 ? '' : 's';
|
|
45
|
+
errors.push(`Tool call limit exceeded: made ${successfulCount} successful call${plural}, maximum is ${requirements.maximum}. ` +
|
|
46
|
+
`This may indicate a loop. Check that the agent is not repeating the same operation.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
50
|
+
}
|
|
51
|
+
/** Normalize tool name: dots → hyphens so both conventions match */
|
|
52
|
+
function normalizeToolName(name) {
|
|
53
|
+
return name.replace(/\./g, '-');
|
|
54
|
+
}
|
|
55
|
+
export function validateRequiredTools(toolCalls, requirements) {
|
|
56
|
+
const errors = [];
|
|
57
|
+
const warnings = [];
|
|
58
|
+
if (requirements?.required_tools && requirements.required_tools.length > 0) {
|
|
59
|
+
for (const requiredTool of requirements.required_tools) {
|
|
60
|
+
const normalizedRequired = normalizeToolName(requiredTool);
|
|
61
|
+
const matchingCalls = toolCalls.filter(tc => normalizeToolName(tc.name) === normalizedRequired);
|
|
62
|
+
if (matchingCalls.length === 0) {
|
|
63
|
+
errors.push(`Required tool '${requiredTool}' was not called`);
|
|
64
|
+
}
|
|
65
|
+
else if (!matchingCalls.some(isSuccessfulToolCall)) {
|
|
66
|
+
errors.push(`Required tool '${requiredTool}' has no successful calls (called ${matchingCalls.length} time${matchingCalls.length === 1 ? '' : 's'}, all failed)`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
71
|
+
}
|
|
72
|
+
export function validateCountedTools(toolCalls, requirements) {
|
|
73
|
+
const errors = [];
|
|
74
|
+
const warnings = [];
|
|
75
|
+
if (requirements?.counted_tools && requirements.counted_tools.length > 0 && requirements?.minimum !== undefined) {
|
|
76
|
+
const countedSet = new Set(requirements.counted_tools.map(normalizeToolName));
|
|
77
|
+
const count = toolCalls.filter(tc => countedSet.has(normalizeToolName(tc.name)) && isSuccessfulToolCall(tc)).length;
|
|
78
|
+
if (count < requirements.minimum) {
|
|
79
|
+
const toolNames = requirements.counted_tools.join(', ');
|
|
80
|
+
errors.push(`Expected at least ${requirements.minimum} successful call${requirements.minimum === 1 ? '' : 's'} to counted tools [${toolNames}], got ${count}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
84
|
+
}
|
|
85
|
+
export function validateToolGroups(toolCalls, requirements) {
|
|
86
|
+
const errors = [];
|
|
87
|
+
const warnings = [];
|
|
88
|
+
if (requirements?.required_tool_groups) {
|
|
89
|
+
for (const group of requirements.required_tool_groups) {
|
|
90
|
+
if (group.length === 0)
|
|
91
|
+
continue;
|
|
92
|
+
const normalizedGroup = new Set(group.map(normalizeToolName));
|
|
93
|
+
const satisfied = toolCalls.some(tc => normalizedGroup.has(normalizeToolName(tc.name)) && isSuccessfulToolCall(tc));
|
|
94
|
+
if (!satisfied) {
|
|
95
|
+
errors.push(`Expected at least one successful call from [${group.join(', ')}], got none`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
100
|
+
}
|
|
101
|
+
export function compose(...validators) {
|
|
102
|
+
return async (result) => {
|
|
103
|
+
const results = await Promise.all(validators.map(v => Promise.resolve(v(result))));
|
|
104
|
+
return {
|
|
105
|
+
valid: results.every(r => r.valid),
|
|
106
|
+
errors: results.flatMap(r => r.errors),
|
|
107
|
+
warnings: results.flatMap(r => r.warnings)
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@studio-foundation/ralph",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.6",
|
|
4
4
|
"description": "RALPH loop engine - Recursive Automated Loop for Persistent Handling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
14
17
|
"keywords": [
|
|
15
18
|
"studio",
|
|
16
19
|
"ralph",
|
|
@@ -21,7 +24,7 @@
|
|
|
21
24
|
"license": "AGPL-3.0-only",
|
|
22
25
|
"dependencies": {
|
|
23
26
|
"js-yaml": "^4.1.1",
|
|
24
|
-
"@studio-foundation/contracts": "0.3.0-beta.
|
|
27
|
+
"@studio-foundation/contracts": "0.3.0-beta.6"
|
|
25
28
|
},
|
|
26
29
|
"devDependencies": {
|
|
27
30
|
"@types/js-yaml": "^4.0.9",
|
package/ARCHITECTURE.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# @studio-foundation/ralph
|
|
2
|
-
|
|
3
|
-
RALPH loop engine — retry intelligent avec validation.
|
|
4
|
-
"Recursive Automated Loop for Persistent Handling"
|
|
5
|
-
|
|
6
|
-
## Concept
|
|
7
|
-
|
|
8
|
-
`ralph<T>()` prend un `executor` générique et un `validator`. Il exécute, valide, retry si fail, s'arrête sur AbortSignal.
|
|
9
|
-
C'est tout. C'est générique. Ça marche pour n'importe quoi, pas juste des LLMs.
|
|
10
|
-
|
|
11
|
-
## Règles
|
|
12
|
-
|
|
13
|
-
- `ralph()` est UNE fonction. Pas une classe, pas un framework.
|
|
14
|
-
- Le `executor` est `() => Promise<T>` — ralph ne sait pas que c'est un LLM derrière
|
|
15
|
-
- La validation est composable via `compose(...validators)`
|
|
16
|
-
- Les stratégies de retry sont pluggables (`exponentialBackoff`, `fixedDelay`, `noDelay`)
|
|
17
|
-
- JAMAIS de dépendance sur `@studio-foundation/runner` ou `@studio-foundation/engine` — ralph est agnostique
|
|
18
|
-
- Dépend UNIQUEMENT de `@studio-foundation/contracts`
|
|
19
|
-
|
|
20
|
-
## Résultat
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
type RalphResult<T> =
|
|
24
|
-
| { status: 'success'; result: T; attempts: number }
|
|
25
|
-
| { status: 'exhausted'; lastResult: T; failures: string[]; attempts: number }
|
|
26
|
-
| { status: 'cancelled'; lastResult?: T; attempts: number } // AbortSignal
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Fichiers clés
|
|
30
|
-
|
|
31
|
-
- `loop.ts` — `ralph()` la fonction principale + `RalphConfig`, `RalphResult`
|
|
32
|
-
- `validator.ts` — `validateSchema`, `validateToolCalls`, `validateRequiredTools`, `compose`
|
|
33
|
-
- `retry-strategy.ts` — `exponentialBackoff(min, max)`, `fixedDelay(ms)`, `noDelay()`
|
|
34
|
-
- `context-enricher.ts` — enrichir contexte entre retries
|
|
35
|
-
- `contracts.ts` — types internes ralph
|
|
36
|
-
|
|
37
|
-
## Anti-patterns
|
|
38
|
-
|
|
39
|
-
- NE PAS mettre de logique LLM ici
|
|
40
|
-
- NE PAS importer `@studio-foundation/runner`
|
|
41
|
-
- NE PAS hardcoder des règles de validation — tout vient des contracts YAML
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
name: analysis
|
|
2
|
-
version: 1
|
|
3
|
-
|
|
4
|
-
schema:
|
|
5
|
-
required_fields:
|
|
6
|
-
- summary
|
|
7
|
-
- recommendations
|
|
8
|
-
summary:
|
|
9
|
-
min_length: 50
|
|
10
|
-
recommendations:
|
|
11
|
-
min_items: 1
|
|
12
|
-
|
|
13
|
-
tool_calls:
|
|
14
|
-
minimum: 0 # Analysis doesn't require tool calls
|
|
15
|
-
|
|
16
|
-
custom_rules:
|
|
17
|
-
- name: actionable-recommendations
|
|
18
|
-
description: "Recommendations must be specific and actionable"
|
|
19
|
-
check: "recommendations.length > 0"
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
name: code-generation
|
|
2
|
-
version: 1
|
|
3
|
-
|
|
4
|
-
schema:
|
|
5
|
-
required_fields:
|
|
6
|
-
- summary
|
|
7
|
-
- files_changed
|
|
8
|
-
files_changed:
|
|
9
|
-
min_items: 1
|
|
10
|
-
item_schema:
|
|
11
|
-
required: [path, action, content]
|
|
12
|
-
|
|
13
|
-
tool_calls:
|
|
14
|
-
minimum: 1
|
|
15
|
-
required_tools:
|
|
16
|
-
- repo_manager.write_file
|
|
17
|
-
|
|
18
|
-
custom_rules:
|
|
19
|
-
- name: no-theatre
|
|
20
|
-
description: "Agent must actually call tools, not just describe calling them"
|
|
21
|
-
check: "tool_calls_count > 0 OR stage_kind != code_generation"
|
package/src/context-enricher.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Enrich context between retries
|
|
2
|
-
import type { ExecutionContext, RetryEvent } from './loop.js';
|
|
3
|
-
|
|
4
|
-
export function buildRetryContext<T>(event: RetryEvent<T>): ExecutionContext {
|
|
5
|
-
return {
|
|
6
|
-
attempt: event.attempt + 1,
|
|
7
|
-
previousFailures: event.allFailures
|
|
8
|
-
};
|
|
9
|
-
}
|
package/src/contracts.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// Load and parse output contracts from YAML
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import yaml from 'js-yaml';
|
|
4
|
-
import type { OutputContract } from '@studio-foundation/contracts';
|
|
5
|
-
|
|
6
|
-
export async function loadContract(path: string): Promise<OutputContract> {
|
|
7
|
-
const content = await readFile(path, 'utf-8');
|
|
8
|
-
return parseContract(content);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function parseContract(yamlContent: string): OutputContract {
|
|
12
|
-
const parsed = yaml.load(yamlContent) as OutputContract;
|
|
13
|
-
|
|
14
|
-
// Validation basique
|
|
15
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
16
|
-
throw new Error('Invalid contract: expected object');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (!parsed.name || typeof parsed.name !== 'string') {
|
|
20
|
-
throw new Error('Invalid contract: missing or invalid name');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (parsed.version === undefined || typeof parsed.version !== 'number') {
|
|
24
|
-
throw new Error('Invalid contract: missing or invalid version');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return parsed;
|
|
28
|
-
}
|