@@ -36,15 +36,37 @@ interface CompletableSpan {
3636export class HumanloopSpanProcessor implements SpanProcessor {
3737 private spanExporter : SpanExporter ;
3838 private children : Map < string , CompletableSpan [ ] > ;
39+ // List of all span IDs that are contained in a Flow trace
40+ // They are passed to the Exporter as a span attribute
41+ // so the Exporter knows when to complete a trace
42+ private prerequisites : Map < string , string [ ] > ;
3943
4044 constructor ( exporter : SpanExporter ) {
4145 this . spanExporter = exporter ;
4246 this . children = new Map ( ) ;
47+ this . prerequisites = new Map ( ) ;
4348 }
4449
4550 async forceFlush ( ) : Promise < void > { }
4651
4752 onStart ( span : Span , _ : Context ) : void {
53+ const spanId = span . spanContext ( ) . spanId ;
54+ const parentSpanId = span . parentSpanId ;
55+ if ( span . name === "humanloop.flow" ) {
56+ this . prerequisites . set ( spanId , [ ] ) ;
57+ }
58+ if ( parentSpanId !== undefined && isHumanloopSpan ( span ) ) {
59+ for ( const [ traceHead , allTraceNodes ] of this . prerequisites ) {
60+ if (
61+ parentSpanId === traceHead ||
62+ allTraceNodes . includes ( parentSpanId )
63+ ) {
64+ allTraceNodes . push ( spanId ) ;
65+ this . prerequisites . set ( traceHead , allTraceNodes ) ;
66+ break ;
67+ }
68+ }
69+ }
4870 // Handle stream case: when Prompt instrumented function calls a provider with streaming: true
4971 // The instrumentor span will end only when the ChunksResponse is consumed, which can happen
5072 // after the span created by the Prompt utility finishes. To handle this, we register all instrumentor
@@ -66,6 +88,7 @@ export class HumanloopSpanProcessor implements SpanProcessor {
6688 */
6789 onEnd ( span : ReadableSpan ) : void {
6890 if ( isHumanloopSpan ( span ) ) {
91+ // Wait for children to complete asynchronously
6992 new Promise < void > ( ( resolve ) => {
7093 const checkChildrenSpans = ( ) => {
7194 const childrenSpans = this . children . get ( span . spanContext ( ) . spanId ) ;
@@ -79,15 +102,28 @@ export class HumanloopSpanProcessor implements SpanProcessor {
79102 } ;
80103 checkChildrenSpans ( ) ;
81104 } ) . then ( ( _ ) => {
82- // All children/ instrumentor spans have arrived, we can process the
105+ // All instrumentor spans have arrived, we can process the
83106 // Humanloop parent span owning them
107+ if ( span . name === "humanloop.flow" ) {
108+ // If the span if a Flow Log, add attribute with all span IDs it
109+ // needs to wait before completion
110+ writeToOpenTelemetrySpan (
111+ span ,
112+ this . prerequisites . get ( span . spanContext ( ) . spanId ) || [ ] ,
113+ HUMANLOOP_LOG_KEY ,
114+ ) ;
115+ this . prerequisites . delete ( span . spanContext ( ) . spanId ) ;
116+ }
117+
84118 this . processSpanDispatch (
85119 span ,
86120 this . children . get ( span . spanContext ( ) . spanId ) || [ ] ,
87121 ) ;
122+
88123 // Release references
89124 this . children . delete ( span . spanContext ( ) . spanId ) ;
90- // Export the Humanloop span
125+
126+ // Pass Humanloop span to Exporter
91127 this . spanExporter . export ( [ span ] , ( result : ExportResult ) => {
92128 if ( result . code !== ExportResultCode . SUCCESS ) {
93129 console . error ( "Failed to export span:" , result . error ) ;
@@ -182,7 +218,7 @@ export class HumanloopSpanProcessor implements SpanProcessor {
182218 // Placeholder for processing other file types
183219 break ;
184220 default :
185- console . error ( "Unknown Humanloop File Span " , span ) ;
221+ console . error ( "Unknown Humanloop File span " , span ) ;
186222 }
187223 }
188224
0 commit comments