@outputai/core 0.4.1-dev.622e67b.0 → 0.4.1-dev.7aa9a5f.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/package.json +5 -1
- package/src/activity_integration/tracing.d.ts +5 -10
- package/src/activity_integration/tracing.js +4 -9
- package/src/consts.js +4 -0
- package/src/interface/aggregations.js +24 -0
- package/src/interface/aggregations.spec.js +91 -0
- package/src/interface/workflow.js +35 -17
- package/src/interface/workflow.spec.js +183 -7
- package/src/tracing/processors/local/index.js +10 -4
- package/src/tracing/processors/local/index.spec.js +52 -21
- package/src/tracing/processors/s3/index.js +3 -3
- package/src/tracing/processors/s3/index.spec.js +26 -1
- package/src/tracing/processors/s3/s3_client.js +11 -3
- package/src/tracing/processors/s3/s3_client.spec.js +27 -15
- package/src/tracing/tools/build_trace_tree.js +1 -1
- package/src/tracing/tools/build_trace_tree.spec.js +49 -11
- package/src/tracing/tools/utils.js +0 -28
- package/src/tracing/tools/utils.spec.js +2 -134
- package/src/tracing/trace_attribute.d.ts +38 -0
- package/src/tracing/trace_attribute.js +80 -0
- package/src/tracing/trace_engine.js +12 -2
- package/src/worker/index.js +1 -1
- package/src/worker/index.spec.js +1 -1
- package/src/worker/interceptors/activity.js +9 -2
- package/src/worker/interceptors/activity.spec.js +16 -3
- package/src/worker/interceptors.js +2 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import Decimal from 'decimal.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* All attributes inherit from this
|
|
5
|
+
*/
|
|
6
|
+
export class BaseAttribute {
|
|
7
|
+
activityId;
|
|
8
|
+
activityName;
|
|
9
|
+
date = Date.now();
|
|
10
|
+
type;
|
|
11
|
+
|
|
12
|
+
constructor( type ) {
|
|
13
|
+
this.type = type;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
setActivity( id, name ) {
|
|
17
|
+
this.activityId = id;
|
|
18
|
+
this.activityName = name;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class HTTPRequestCount extends BaseAttribute {
|
|
23
|
+
static TYPE = 'http:request:count';
|
|
24
|
+
url;
|
|
25
|
+
requestId;
|
|
26
|
+
|
|
27
|
+
constructor( url, requestId ) {
|
|
28
|
+
super( HTTPRequestCount.TYPE );
|
|
29
|
+
this.url = url;
|
|
30
|
+
this.requestId = requestId;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class HTTPRequestCost extends BaseAttribute {
|
|
35
|
+
static TYPE = 'http:request:cost';
|
|
36
|
+
url;
|
|
37
|
+
requestId;
|
|
38
|
+
total = 0;
|
|
39
|
+
|
|
40
|
+
constructor( url, requestId, total ) {
|
|
41
|
+
super( HTTPRequestCost.TYPE );
|
|
42
|
+
this.url = url;
|
|
43
|
+
this.requestId = requestId;
|
|
44
|
+
this.total = total;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class LLMUsage extends BaseAttribute {
|
|
49
|
+
static TYPE = 'llm:usage';
|
|
50
|
+
modelId;
|
|
51
|
+
usage = [];
|
|
52
|
+
total = 0;
|
|
53
|
+
tokensUsed = 0;
|
|
54
|
+
|
|
55
|
+
constructor( modelId ) {
|
|
56
|
+
super( LLMUsage.TYPE );
|
|
57
|
+
this.modelId = modelId;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
addUsage( { type, ppm, amount } ) {
|
|
61
|
+
const total = Decimal( amount ).div( 1_000_000 ).mul( ppm ).toNumber();
|
|
62
|
+
this.usage.push( {
|
|
63
|
+
type,
|
|
64
|
+
ppm,
|
|
65
|
+
amount,
|
|
66
|
+
total
|
|
67
|
+
} );
|
|
68
|
+
this.total = Decimal( this.total ).add( total ).toNumber();
|
|
69
|
+
this.tokensUsed = Decimal( this.tokensUsed ).add( amount ).toNumber();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Types of ADD_ATTR attributes
|
|
75
|
+
*/
|
|
76
|
+
export const Attribute = {
|
|
77
|
+
LLMUsage,
|
|
78
|
+
HTTPRequestCost,
|
|
79
|
+
HTTPRequestCount
|
|
80
|
+
};
|
|
@@ -4,8 +4,10 @@ import { serializeError } from './tools/utils.js';
|
|
|
4
4
|
import { isStringboolTrue } from '#utils';
|
|
5
5
|
import * as localProcessor from './processors/local/index.js';
|
|
6
6
|
import * as s3Processor from './processors/s3/index.js';
|
|
7
|
-
import { ComponentType } from '#consts';
|
|
7
|
+
import { ComponentType, Signal } from '#consts';
|
|
8
8
|
import { createChildLogger } from '#logger';
|
|
9
|
+
import { EventAction } from './trace_consts.js';
|
|
10
|
+
import { BaseAttribute } from './trace_attribute.js';
|
|
9
11
|
|
|
10
12
|
const log = createChildLogger( 'Tracing' );
|
|
11
13
|
|
|
@@ -91,7 +93,15 @@ export const addEventAction = ( action, { kind, name, id, parentId, details, exe
|
|
|
91
93
|
export function addEventActionWithContext( action, options ) {
|
|
92
94
|
const storeContent = Storage.load();
|
|
93
95
|
if ( storeContent ) { // If there is no storageContext this was not called from a Temporal environment
|
|
94
|
-
const { parentId, executionContext } = storeContent;
|
|
96
|
+
const { parentId, parentName, executionContext, workflowHandle } = storeContent;
|
|
97
|
+
if ( action === EventAction.ADD_ATTR ) {
|
|
98
|
+
const attribute = options.details;
|
|
99
|
+
if ( !( attribute instanceof BaseAttribute ) ) {
|
|
100
|
+
throw new Error( `${EventAction.ADD_ATTR} called argument that is not a BaseAttribute instance` );
|
|
101
|
+
}
|
|
102
|
+
attribute.setActivity( parentId, parentName );
|
|
103
|
+
workflowHandle.signal( Signal.ADD_ATTRIBUTE, attribute );
|
|
104
|
+
}
|
|
95
105
|
addEventAction( action, { ...options, parentId, executionContext } );
|
|
96
106
|
}
|
|
97
107
|
};
|
package/src/worker/index.js
CHANGED
|
@@ -69,7 +69,7 @@ const callerDir = process.argv[2];
|
|
|
69
69
|
workflowsPath,
|
|
70
70
|
activities,
|
|
71
71
|
sinks,
|
|
72
|
-
interceptors: initInterceptors( { activities, workflows } ),
|
|
72
|
+
interceptors: initInterceptors( { activities, workflows, connection } ),
|
|
73
73
|
maxConcurrentWorkflowTaskExecutions,
|
|
74
74
|
maxConcurrentActivityTaskExecutions,
|
|
75
75
|
maxCachedWorkflows,
|
package/src/worker/index.spec.js
CHANGED
|
@@ -123,7 +123,7 @@ describe( 'worker/index', () => {
|
|
|
123
123
|
maxConcurrentActivityTaskPolls: configValues.maxConcurrentActivityTaskPolls,
|
|
124
124
|
maxConcurrentWorkflowTaskPolls: configValues.maxConcurrentWorkflowTaskPolls
|
|
125
125
|
} ) );
|
|
126
|
-
expect( initInterceptorsMock ).toHaveBeenCalledWith( { activities: {}, workflows: [] } );
|
|
126
|
+
expect( initInterceptorsMock ).toHaveBeenCalledWith( { activities: {}, workflows: [], connection: mockConnection } );
|
|
127
127
|
expect( registerShutdownMock ).toHaveBeenCalledWith( { worker: mockWorker, log: mockLog } );
|
|
128
128
|
expect( startCatalogMock ).toHaveBeenCalledWith( {
|
|
129
129
|
connection: mockConnection,
|
|
@@ -5,6 +5,7 @@ import { headersToObject } from '../sandboxed_utils.js';
|
|
|
5
5
|
import { BusEventType, METADATA_ACCESS_SYMBOL } from '#consts';
|
|
6
6
|
import { activityHeartbeatEnabled, activityHeartbeatIntervalMs } from '../configs.js';
|
|
7
7
|
import { messageBus } from '#bus';
|
|
8
|
+
import { Client } from '@temporalio/client';
|
|
8
9
|
|
|
9
10
|
/*
|
|
10
11
|
This interceptor wraps every activity execution with cross-cutting concerns:
|
|
@@ -23,7 +24,7 @@ import { messageBus } from '#bus';
|
|
|
23
24
|
- Headers injected by the workflow interceptor (executionContext)
|
|
24
25
|
*/
|
|
25
26
|
export class ActivityExecutionInterceptor {
|
|
26
|
-
constructor( { activities, workflows } ) {
|
|
27
|
+
constructor( { activities, workflows, connection } ) {
|
|
27
28
|
this.activities = activities;
|
|
28
29
|
this.workflowsMap = workflows.reduce( ( map, w ) => {
|
|
29
30
|
map.set( w.name, w );
|
|
@@ -32,14 +33,19 @@ export class ActivityExecutionInterceptor {
|
|
|
32
33
|
}
|
|
33
34
|
return map;
|
|
34
35
|
}, new Map() );
|
|
36
|
+
this.connection = connection;
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
async execute( input, next ) {
|
|
38
40
|
const startDate = Date.now();
|
|
41
|
+
const client = new Client( { connection: this.connection } );
|
|
42
|
+
|
|
39
43
|
const { workflowExecution: { workflowId }, activityId: id, activityType: name, workflowType: workflowName } = Context.current().info;
|
|
40
44
|
const { executionContext } = headersToObject( input.headers );
|
|
41
45
|
const { type: kind } = this.activities?.[name]?.[METADATA_ACCESS_SYMBOL];
|
|
42
46
|
|
|
47
|
+
const workflowHandle = client.workflow.getHandle( workflowId );
|
|
48
|
+
|
|
43
49
|
messageBus.emit( BusEventType.ACTIVITY_START, { id, name, kind, workflowId, workflowName } );
|
|
44
50
|
Tracing.addEventStart( { id, name, kind, parentId: workflowId, details: input.args[0], executionContext } );
|
|
45
51
|
|
|
@@ -56,7 +62,8 @@ export class ActivityExecutionInterceptor {
|
|
|
56
62
|
intervals.heartbeat = activityHeartbeatEnabled && setInterval( () => Context.current().heartbeat(), activityHeartbeatIntervalMs );
|
|
57
63
|
|
|
58
64
|
// Wraps the execution with accessible metadata for the activity
|
|
59
|
-
const
|
|
65
|
+
const ctx = { parentId: id, parentName: name, executionContext, workflowFilename, workflowHandle };
|
|
66
|
+
const output = await Storage.runWithContext( async _ => next( input ), ctx );
|
|
60
67
|
|
|
61
68
|
messageBus.emit( BusEventType.ACTIVITY_END, { id, name, kind, workflowId, workflowName, duration: Date.now() - startDate } );
|
|
62
69
|
Tracing.addEventEnd( { id, details: output, executionContext } );
|
|
@@ -2,6 +2,8 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
2
2
|
import { BusEventType } from '#consts';
|
|
3
3
|
|
|
4
4
|
const METADATA_ACCESS_SYMBOL = vi.hoisted( () => Symbol( '__metadata' ) );
|
|
5
|
+
const workflowHandleMock = vi.hoisted( () => ( { signal: vi.fn() } ) );
|
|
6
|
+
const getHandleMock = vi.hoisted( () => vi.fn( () => workflowHandleMock ) );
|
|
5
7
|
|
|
6
8
|
const heartbeatMock = vi.fn();
|
|
7
9
|
const runWithContextMock = vi.hoisted( () => vi.fn().mockImplementation( async fn => fn() ) );
|
|
@@ -21,6 +23,14 @@ vi.mock( '@temporalio/activity', () => ( {
|
|
|
21
23
|
}
|
|
22
24
|
} ) );
|
|
23
25
|
|
|
26
|
+
vi.mock( '@temporalio/client', () => ( {
|
|
27
|
+
Client: class Client {
|
|
28
|
+
workflow = {
|
|
29
|
+
getHandle: getHandleMock
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
} ) );
|
|
33
|
+
|
|
24
34
|
vi.mock( '#async_storage', () => ( {
|
|
25
35
|
Storage: {
|
|
26
36
|
runWithContext: runWithContextMock
|
|
@@ -108,12 +118,15 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
108
118
|
expect( addEventErrorMock ).not.toHaveBeenCalled();
|
|
109
119
|
expect( runWithContextMock ).toHaveBeenCalledWith(
|
|
110
120
|
expect.any( Function ),
|
|
111
|
-
{
|
|
121
|
+
expect.objectContaining( {
|
|
112
122
|
parentId: 'act-1',
|
|
123
|
+
parentName: 'myWorkflow#myStep',
|
|
113
124
|
executionContext: { workflowId: 'wf-1' },
|
|
114
|
-
workflowFilename: '/workflows/myWorkflow.js'
|
|
115
|
-
|
|
125
|
+
workflowFilename: '/workflows/myWorkflow.js',
|
|
126
|
+
workflowHandle: workflowHandleMock
|
|
127
|
+
} )
|
|
116
128
|
);
|
|
129
|
+
expect( getHandleMock ).toHaveBeenCalledWith( 'wf-1' );
|
|
117
130
|
} );
|
|
118
131
|
|
|
119
132
|
it( 'records trace error event on failed execution', async () => {
|
|
@@ -4,7 +4,7 @@ import { ActivityExecutionInterceptor } from './interceptors/activity.js';
|
|
|
4
4
|
|
|
5
5
|
const __dirname = dirname( fileURLToPath( import.meta.url ) );
|
|
6
6
|
|
|
7
|
-
export const initInterceptors = ( { activities, workflows } ) => ( {
|
|
7
|
+
export const initInterceptors = ( { activities, workflows, connection } ) => ( {
|
|
8
8
|
workflowModules: [ join( __dirname, './interceptors/workflow.js' ) ],
|
|
9
|
-
activityInbound: [ () => new ActivityExecutionInterceptor( { activities, workflows } ) ]
|
|
9
|
+
activityInbound: [ () => new ActivityExecutionInterceptor( { activities, workflows, connection } ) ]
|
|
10
10
|
} );
|