@output.ai/core 0.0.16 → 0.1.1
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/README.md +16 -22
- package/package.json +19 -7
- package/src/consts.js +2 -11
- package/src/interface/evaluator.js +8 -4
- package/src/interface/step.js +1 -1
- package/src/interface/webhook.js +16 -4
- package/src/interface/workflow.js +28 -48
- package/src/internal_activities/index.js +3 -37
- package/src/tracing/index.d.ts +47 -0
- package/src/tracing/index.js +45 -0
- package/src/tracing/internal_interface.js +66 -0
- package/src/tracing/processors/local/index.js +50 -0
- package/src/tracing/processors/local/index.spec.js +67 -0
- package/src/tracing/processors/s3/index.js +51 -0
- package/src/tracing/processors/s3/index.spec.js +64 -0
- package/src/tracing/processors/s3/redis_client.js +19 -0
- package/src/tracing/processors/s3/redis_client.spec.js +50 -0
- package/src/tracing/processors/s3/s3_client.js +33 -0
- package/src/tracing/processors/s3/s3_client.spec.js +67 -0
- package/src/tracing/tools/build_trace_tree.js +76 -0
- package/src/tracing/tools/build_trace_tree.spec.js +99 -0
- package/src/tracing/tools/utils.js +28 -0
- package/src/tracing/tools/utils.spec.js +14 -0
- package/src/tracing/trace_engine.js +63 -0
- package/src/tracing/trace_engine.spec.js +91 -0
- package/src/utils.js +8 -0
- package/src/worker/catalog_workflow/index.js +2 -1
- package/src/worker/catalog_workflow/index.spec.js +6 -10
- package/src/worker/configs.js +24 -0
- package/src/worker/index.js +7 -8
- package/src/worker/interceptors/activity.js +15 -17
- package/src/worker/interceptors/workflow.js +18 -1
- package/src/worker/loader.js +40 -31
- package/src/worker/loader.spec.js +22 -29
- package/src/worker/loader_tools.js +63 -0
- package/src/worker/loader_tools.spec.js +85 -0
- package/src/worker/sinks.js +60 -10
- package/src/configs.js +0 -36
- package/src/configs.spec.js +0 -379
- package/src/worker/internal_utils.js +0 -60
- package/src/worker/internal_utils.spec.js +0 -134
- package/src/worker/tracer/index.js +0 -75
- package/src/worker/tracer/index.test.js +0 -102
- package/src/worker/tracer/tracer_tree.js +0 -85
- package/src/worker/tracer/tracer_tree.test.js +0 -115
- /package/src/{worker/async_storage.js → async_storage.js} +0 -0
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
-
import { tmpdir, EOL } from 'node:os';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import { THIS_LIB_NAME } from '#consts';
|
|
6
|
-
|
|
7
|
-
const createTempDir = () => mkdtempSync( join( tmpdir(), 'output-sdk-trace-' ) );
|
|
8
|
-
|
|
9
|
-
// Single mocks (configured per-test by mutating the backing objects)
|
|
10
|
-
const mockConfig = { tracing: { enabled: false } };
|
|
11
|
-
vi.mock( '#configs', () => mockConfig );
|
|
12
|
-
|
|
13
|
-
const mockStorageData = {
|
|
14
|
-
activityId: 's1',
|
|
15
|
-
activityType: 'Step 1',
|
|
16
|
-
workflowId: 'wf1',
|
|
17
|
-
workflowType: 'prompt',
|
|
18
|
-
workflowPath: '/workflows/prompt.js',
|
|
19
|
-
parentWorkflowId: undefined,
|
|
20
|
-
rootWorkflowId: undefined,
|
|
21
|
-
rootWorkflowType: undefined
|
|
22
|
-
};
|
|
23
|
-
vi.mock( '../async_storage.js', () => ( {
|
|
24
|
-
Storage: { load: () => mockStorageData }
|
|
25
|
-
} ) );
|
|
26
|
-
|
|
27
|
-
vi.mock( './tracer_tree.js', () => ( { buildLogTree: vi.fn() } ) );
|
|
28
|
-
|
|
29
|
-
describe( 'tracer/index', () => {
|
|
30
|
-
beforeEach( () => {
|
|
31
|
-
vi.resetModules();
|
|
32
|
-
vi.clearAllMocks();
|
|
33
|
-
vi.useFakeTimers();
|
|
34
|
-
vi.setSystemTime( new Date( '2020-01-01T00:00:00.000Z' ) );
|
|
35
|
-
} );
|
|
36
|
-
|
|
37
|
-
afterEach( () => {
|
|
38
|
-
vi.useRealTimers();
|
|
39
|
-
} );
|
|
40
|
-
|
|
41
|
-
it( 'writes a raw log entry and calls buildLogTree (mocked)', async () => {
|
|
42
|
-
const originalArgv2 = process.argv[2];
|
|
43
|
-
const tmp = createTempDir();
|
|
44
|
-
process.argv[2] = tmp;
|
|
45
|
-
|
|
46
|
-
mockConfig.tracing.enabled = true;
|
|
47
|
-
|
|
48
|
-
const { buildLogTree } = await import( './tracer_tree.js' );
|
|
49
|
-
const { trace } = await import( './index.js' );
|
|
50
|
-
|
|
51
|
-
const input = { foo: 1 };
|
|
52
|
-
trace( { lib: THIS_LIB_NAME, event: 'workflow_start', input, output: null } );
|
|
53
|
-
|
|
54
|
-
expect( buildLogTree ).toHaveBeenCalledTimes( 1 );
|
|
55
|
-
const logPath = buildLogTree.mock.calls[0][0];
|
|
56
|
-
|
|
57
|
-
const raw = readFileSync( logPath, 'utf-8' );
|
|
58
|
-
const [ firstLine ] = raw.split( EOL );
|
|
59
|
-
const entry = JSON.parse( firstLine );
|
|
60
|
-
|
|
61
|
-
expect( entry ).toMatchObject( {
|
|
62
|
-
lib: THIS_LIB_NAME,
|
|
63
|
-
event: 'workflow_start',
|
|
64
|
-
input,
|
|
65
|
-
output: null,
|
|
66
|
-
stepId: 's1',
|
|
67
|
-
stepName: 'Step 1',
|
|
68
|
-
workflowId: 'wf1',
|
|
69
|
-
workflowType: 'prompt',
|
|
70
|
-
workflowPath: '/workflows/prompt.js'
|
|
71
|
-
} );
|
|
72
|
-
expect( typeof entry.timestamp ).toBe( 'number' );
|
|
73
|
-
|
|
74
|
-
rmSync( tmp, { recursive: true, force: true } );
|
|
75
|
-
process.argv[2] = originalArgv2;
|
|
76
|
-
} );
|
|
77
|
-
|
|
78
|
-
it( 'does nothing when tracing is disabled', async () => {
|
|
79
|
-
const originalArgv2 = process.argv[2];
|
|
80
|
-
const tmp = createTempDir();
|
|
81
|
-
process.argv[2] = tmp;
|
|
82
|
-
|
|
83
|
-
mockConfig.tracing.enabled = false;
|
|
84
|
-
const { buildLogTree } = await import( './tracer_tree.js' );
|
|
85
|
-
const { trace } = await import( './index.js' );
|
|
86
|
-
|
|
87
|
-
trace( { lib: THIS_LIB_NAME, event: 'workflow_start', input: {}, output: null } );
|
|
88
|
-
|
|
89
|
-
expect( buildLogTree ).not.toHaveBeenCalled();
|
|
90
|
-
|
|
91
|
-
rmSync( tmp, { recursive: true, force: true } );
|
|
92
|
-
process.argv[2] = originalArgv2;
|
|
93
|
-
} );
|
|
94
|
-
|
|
95
|
-
it( 'setupGlobalTracer installs global symbol', async () => {
|
|
96
|
-
mockConfig.tracing.enabled = false;
|
|
97
|
-
const { setupGlobalTracer } = await import( './index.js' );
|
|
98
|
-
setupGlobalTracer();
|
|
99
|
-
const sym = Symbol.for( '__trace' );
|
|
100
|
-
expect( typeof globalThis[sym] ).toBe( 'function' );
|
|
101
|
-
} );
|
|
102
|
-
} );
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { EOL } from 'os';
|
|
3
|
-
import { THIS_LIB_NAME, TraceEvent } from '#consts';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Sorting function that compares two objects and ASC sort them by either .startedAt or, if not present, .timestamp
|
|
7
|
-
*
|
|
8
|
-
* @param {object} a
|
|
9
|
-
* @param {object} b
|
|
10
|
-
* @returns {number} The sorting result [1,-1]
|
|
11
|
-
*/
|
|
12
|
-
const timestampAscSort = ( a, b ) => {
|
|
13
|
-
if ( a.startedAt ) {
|
|
14
|
-
return a.startedAt > b.startedAt ? 1 : 1;
|
|
15
|
-
}
|
|
16
|
-
return a.timestamp > b.timestamp ? 1 : -1;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Add a member to an array an sort it. It is a mutating method.
|
|
21
|
-
*
|
|
22
|
-
* @param {array} arr - The arr to be changed
|
|
23
|
-
* @param {any} entry - The entry to be added
|
|
24
|
-
* @param {Function} sorter - The sort function to be used (within .filter)
|
|
25
|
-
*/
|
|
26
|
-
const pushSort = ( arr, entry, sorter ) => {
|
|
27
|
-
arr.push( entry );
|
|
28
|
-
arr.sort( sorter );
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Transform the trace file into a tree of events, where nested events are represented as children of parent events.
|
|
33
|
-
* And the events STEP_START/STEP_END and WORKFLOW_START/WORKFLOW_END are combined into single events with start and end timestamps.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} src - The trace src filename
|
|
36
|
-
*/
|
|
37
|
-
export const buildLogTree = src => {
|
|
38
|
-
const content = readFileSync( src, 'utf-8' );
|
|
39
|
-
const entries = content.split( EOL ).slice( 0, -1 ).map( c => JSON.parse( c ) );
|
|
40
|
-
|
|
41
|
-
const stepsMap = new Map();
|
|
42
|
-
const workflowsMap = new Map();
|
|
43
|
-
|
|
44
|
-
// close steps/workflows
|
|
45
|
-
for ( const entry of entries.filter( e => e.lib === THIS_LIB_NAME ) ) {
|
|
46
|
-
const { event, workflowId, workflowType, workflowPath, parentWorkflowId, stepId, stepName, input, output, timestamp } = entry;
|
|
47
|
-
|
|
48
|
-
const baseEntry = { children: [], startedAt: timestamp, workflowId };
|
|
49
|
-
if ( [ TraceEvent.EVALUATOR_START, TraceEvent.STEP_START ].includes( event ) ) {
|
|
50
|
-
const eventName = event === TraceEvent.EVALUATOR_START ? 'evaluator' : 'step';
|
|
51
|
-
stepsMap.set( `${workflowId}:${stepId}`, { event: eventName, input, stepId, stepName, ...baseEntry } );
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if ( [ TraceEvent.EVALUATOR_END, TraceEvent.STEP_END ].includes( event ) ) {
|
|
55
|
-
Object.assign( stepsMap.get( `${workflowId}:${stepId}` ) ?? {}, { output, endedAt: timestamp } );
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if ( event === TraceEvent.WORKFLOW_START ) {
|
|
59
|
-
workflowsMap.set( workflowId, { event: 'workflow', input, parentWorkflowId, workflowPath, workflowType, ...baseEntry } );
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if ( event === TraceEvent.WORKFLOW_END ) {
|
|
63
|
-
Object.assign( workflowsMap.get( workflowId ) ?? {}, { output, endedAt: timestamp } );
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// insert operations inside steps
|
|
68
|
-
for ( const entry of entries.filter( e => e.lib !== THIS_LIB_NAME ) ) {
|
|
69
|
-
pushSort( stepsMap.get( `${entry.workflowId}:${entry.stepId}` ).children, entry, timestampAscSort );
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// insert steps into workflows
|
|
73
|
-
for ( const step of stepsMap.values() ) {
|
|
74
|
-
pushSort( workflowsMap.get( step.workflowId ).children, step, timestampAscSort );
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// insert children workflows
|
|
78
|
-
for ( const workflow of [ ...workflowsMap.values() ].filter( w => w.parentWorkflowId ) ) {
|
|
79
|
-
pushSort( workflowsMap.get( workflow.parentWorkflowId ).children, workflow, timestampAscSort );
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const rootWorkflow = [ ...workflowsMap.values() ].find( w => !w.parentWorkflowId );
|
|
83
|
-
|
|
84
|
-
writeFileSync( src.replace( /\.raw$/, '.json' ), JSON.stringify( rootWorkflow, undefined, 2 ), 'utf-8' );
|
|
85
|
-
};
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { writeFileSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
-
import { mkdtempSync } from 'node:fs';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'path';
|
|
6
|
-
import { EOL } from 'os';
|
|
7
|
-
import { buildLogTree } from './tracer_tree.js';
|
|
8
|
-
import { THIS_LIB_NAME, TraceEvent } from '#consts';
|
|
9
|
-
|
|
10
|
-
const createTempDir = () => mkdtempSync( join( tmpdir(), 'output-sdk-trace-tree-' ) );
|
|
11
|
-
|
|
12
|
-
describe( 'tracer/tracer_tree', () => {
|
|
13
|
-
it( 'builds a tree JSON from a raw log file', () => {
|
|
14
|
-
const tmp = createTempDir();
|
|
15
|
-
const rawPath = join( tmp, 'run-123.raw' );
|
|
16
|
-
|
|
17
|
-
const entries = [
|
|
18
|
-
// root workflow start
|
|
19
|
-
{
|
|
20
|
-
lib: THIS_LIB_NAME,
|
|
21
|
-
event: TraceEvent.WORKFLOW_START,
|
|
22
|
-
input: { a: 1 },
|
|
23
|
-
output: null,
|
|
24
|
-
timestamp: 1000,
|
|
25
|
-
stepId: undefined,
|
|
26
|
-
stepName: undefined,
|
|
27
|
-
workflowId: 'wf1',
|
|
28
|
-
workflowType: 'prompt',
|
|
29
|
-
workflowPath: '/workflows/prompt.js',
|
|
30
|
-
parentWorkflowId: undefined
|
|
31
|
-
},
|
|
32
|
-
// step start
|
|
33
|
-
{
|
|
34
|
-
lib: THIS_LIB_NAME,
|
|
35
|
-
event: TraceEvent.STEP_START,
|
|
36
|
-
input: { x: 1 },
|
|
37
|
-
output: null,
|
|
38
|
-
timestamp: 2000,
|
|
39
|
-
stepId: 's1',
|
|
40
|
-
stepName: 'Step 1',
|
|
41
|
-
workflowId: 'wf1',
|
|
42
|
-
workflowType: 'prompt',
|
|
43
|
-
workflowPath: '/workflows/prompt.js',
|
|
44
|
-
parentWorkflowId: undefined
|
|
45
|
-
},
|
|
46
|
-
// non-core operation within step
|
|
47
|
-
{
|
|
48
|
-
lib: 'tool',
|
|
49
|
-
event: 'call',
|
|
50
|
-
input: { y: 2 },
|
|
51
|
-
output: { y: 3 },
|
|
52
|
-
timestamp: 3000,
|
|
53
|
-
stepId: 's1',
|
|
54
|
-
stepName: 'Step 1',
|
|
55
|
-
workflowId: 'wf1'
|
|
56
|
-
},
|
|
57
|
-
// step end
|
|
58
|
-
{
|
|
59
|
-
lib: THIS_LIB_NAME,
|
|
60
|
-
event: TraceEvent.STEP_END,
|
|
61
|
-
input: null,
|
|
62
|
-
output: { done: true },
|
|
63
|
-
timestamp: 4000,
|
|
64
|
-
stepId: 's1',
|
|
65
|
-
stepName: 'Step 1',
|
|
66
|
-
workflowId: 'wf1',
|
|
67
|
-
workflowType: 'prompt',
|
|
68
|
-
workflowPath: '/workflows/prompt.js',
|
|
69
|
-
parentWorkflowId: undefined
|
|
70
|
-
},
|
|
71
|
-
// workflow end
|
|
72
|
-
{
|
|
73
|
-
lib: THIS_LIB_NAME,
|
|
74
|
-
event: TraceEvent.WORKFLOW_END,
|
|
75
|
-
input: null,
|
|
76
|
-
output: { ok: true },
|
|
77
|
-
timestamp: 5000,
|
|
78
|
-
stepId: undefined,
|
|
79
|
-
stepName: undefined,
|
|
80
|
-
workflowId: 'wf1',
|
|
81
|
-
workflowType: 'prompt',
|
|
82
|
-
workflowPath: '/workflows/prompt.js',
|
|
83
|
-
parentWorkflowId: undefined
|
|
84
|
-
}
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
writeFileSync( rawPath, entries.map( e => JSON.stringify( e ) ).join( EOL ) + EOL, 'utf-8' );
|
|
88
|
-
|
|
89
|
-
buildLogTree( rawPath );
|
|
90
|
-
|
|
91
|
-
const tree = JSON.parse( readFileSync( rawPath.replace( /.raw$/, '.json' ), 'utf-8' ) );
|
|
92
|
-
|
|
93
|
-
expect( tree.event ).toBe( 'workflow' );
|
|
94
|
-
expect( tree.workflowId ).toBe( 'wf1' );
|
|
95
|
-
expect( tree.workflowType ).toBe( 'prompt' );
|
|
96
|
-
expect( tree.startedAt ).toBe( 1000 );
|
|
97
|
-
expect( tree.endedAt ).toBe( 5000 );
|
|
98
|
-
expect( tree.output ).toEqual( { ok: true } );
|
|
99
|
-
expect( Array.isArray( tree.children ) ).toBe( true );
|
|
100
|
-
expect( tree.children.length ).toBe( 1 );
|
|
101
|
-
|
|
102
|
-
const step = tree.children[0];
|
|
103
|
-
expect( step.event ).toBe( 'step' );
|
|
104
|
-
expect( step.stepId ).toBe( 's1' );
|
|
105
|
-
expect( step.startedAt ).toBe( 2000 );
|
|
106
|
-
expect( step.endedAt ).toBe( 4000 );
|
|
107
|
-
expect( step.output ).toEqual( { done: true } );
|
|
108
|
-
expect( step.children.length ).toBe( 1 );
|
|
109
|
-
expect( step.children[0].lib ).toBe( 'tool' );
|
|
110
|
-
expect( step.children[0].timestamp ).toBe( 3000 );
|
|
111
|
-
|
|
112
|
-
rmSync( tmp, { recursive: true, force: true } );
|
|
113
|
-
} );
|
|
114
|
-
} );
|
|
115
|
-
|
|
File without changes
|