@@ -90,17 +90,22 @@ def test_decorators_without_flow(
9090 # on the OpenAI call span to finish first
9191 time .sleep (1 )
9292 spans = exporter .get_finished_spans ()
93- # THEN 3 spans arrive at the exporter in the following order:
94- # 0. Intercepted OpenAI call, which is ignored by the exporter
95- # 1. Tool Span (called after the OpenAI call but before the Prompt Span finishes)
96- # 2. Prompt Span
93+
94+ # THEN 3 spans arrive at the exporter:
9795 assert len (spans ) == 3
96+
97+ for i in range (3 ):
98+ if spans [i ].name == "humanloop.tool" :
99+ tool_span = spans [i ]
100+ elif spans [i ].name == "humanloop.prompt" :
101+ prompt_span = spans [i ]
102+
98103 assert read_from_opentelemetry_span (
99- span = spans [ 1 ] ,
104+ span = tool_span ,
100105 key = HUMANLOOP_FILE_KEY ,
101106 )["tool" ]
102107 assert read_from_opentelemetry_span (
103- span = spans [ 2 ] ,
108+ span = prompt_span ,
104109 key = HUMANLOOP_FILE_KEY ,
105110 )["prompt" ]
106111
@@ -126,17 +131,23 @@ def test_decorators_with_flow_decorator(
126131 },
127132 ]
128133 )
129- # THEN 4 spans arrive at the exporter in the following order:
130- # 0. Intercepted OpenAI call, which is ignored by the exporter
131- # 1. Tool Span (called after the OpenAI call but before the Prompt Span finishes)
132- # 2. Prompt Span
133- # 3. Flow Span
134- spans = exporter .get_finished_spans ()
134+
135+ # THEN 4 spans arrive at the exporter:
136+ spans : list [ReadableSpan ] = exporter .get_finished_spans ()
135137 assert len (spans ) == 4
138+
139+ for i in range (4 ):
140+ if spans [i ].name == "humanloop.flow" :
141+ flow_span = spans [i ]
142+ elif spans [i ].name == "humanloop.prompt" :
143+ prompt_span = spans [i ]
144+ elif spans [i ].name == "humanloop.tool" :
145+ tool_span = spans [i ]
146+
136147 # THEN the span are returned bottom to top
137- assert read_from_opentelemetry_span (span = spans [ 1 ] , key = HUMANLOOP_FILE_KEY )["tool" ]
138- assert read_from_opentelemetry_span (span = spans [ 2 ] , key = HUMANLOOP_FILE_KEY )["prompt" ]
139- assert read_from_opentelemetry_span (span = spans [ 3 ] , key = HUMANLOOP_FILE_KEY )["flow" ]
148+ assert read_from_opentelemetry_span (span = tool_span , key = HUMANLOOP_FILE_KEY )["tool" ]
149+ assert read_from_opentelemetry_span (span = prompt_span , key = HUMANLOOP_FILE_KEY )["prompt" ]
150+ assert read_from_opentelemetry_span (span = flow_span , key = HUMANLOOP_FILE_KEY )["flow" ]
140151
141152
142153def test_flow_decorator_flow_in_flow (
@@ -155,19 +166,25 @@ def test_flow_decorator_flow_in_flow(
155166 # on the OpenAI call span to finish first
156167 time .sleep (1 )
157168
158- # THEN 5 spans are arrive at the exporter in the following order:
159- # 0. Intercepted OpenAI call, which is ignored by the exporter
160- # 1. Tool Span (called after the OpenAI call but before the Prompt Span finishes)
161- # 2. Prompt Span
162- # 3. Nested Flow Span
163- # 4. Flow Span
164- spans = exporter .get_finished_spans ()
169+ # THEN 5 spans arrive at the exporter
170+ spans : list [ReadableSpan ] = exporter .get_finished_spans ()
165171 assert len (spans ) == 5
166- assert read_from_opentelemetry_span (span = spans [1 ], key = HUMANLOOP_FILE_KEY )["tool" ]
167- assert read_from_opentelemetry_span (span = spans [2 ], key = HUMANLOOP_FILE_KEY )["prompt" ]
168- assert read_from_opentelemetry_span (span = spans [3 ], key = HUMANLOOP_FILE_KEY )["flow" ] != {}
172+
173+ for i in range (5 ):
174+ if spans [i ].name == "humanloop.flow" and spans [i ].parent is None :
175+ flow_span = spans [i ]
176+ elif spans [i ].name == "humanloop.flow" and spans [i ].parent :
177+ nested_flow_span = spans [i ]
178+ elif spans [i ].name == "humanloop.prompt" :
179+ prompt_span = spans [i ]
180+ elif spans [i ].name == "humanloop.tool" :
181+ tool_span = spans [i ]
182+
183+ assert read_from_opentelemetry_span (span = tool_span , key = HUMANLOOP_FILE_KEY )["tool" ]
184+ assert read_from_opentelemetry_span (span = prompt_span , key = HUMANLOOP_FILE_KEY )["prompt" ]
185+ assert read_from_opentelemetry_span (span = nested_flow_span , key = HUMANLOOP_FILE_KEY )["flow" ] != {}
169186 with pytest .raises (KeyError ):
170- read_from_opentelemetry_span (span = spans [ 4 ] , key = HUMANLOOP_FILE_KEY )["flow" ] != {}
187+ read_from_opentelemetry_span (span = flow_span , key = HUMANLOOP_FILE_KEY )["flow" ] != {}
171188
172189
173190def test_flow_decorator_with_hl_exporter (
@@ -187,17 +204,17 @@ def test_flow_decorator_with_hl_exporter(
187204 # Exporter is threaded, need to wait threads shutdown
188205 time .sleep (3 )
189206
190- # THEN 4 spans are arrive at the exporter in the following order:
191- # 0. Intercepted OpenAI call, which is ignored by the exporter
192- # 1. Tool Span (called after the OpenAI call but before the Prompt Span finishes)
193- # 2. Prompt Span
194- # 3. Flow Span
195207 assert len (mock_export_method .call_args_list ) == 4
196208
197- tool_span = mock_export_method .call_args_list [1 ][0 ][0 ][0 ]
198- prompt_span = mock_export_method .call_args_list [2 ][0 ][0 ][0 ]
199- flow_span = mock_export_method .call_args_list [3 ][0 ][0 ][0 ]
200- # THEN the last uploaded span is the Flow
209+ for i in range (4 ):
210+ span = mock_export_method .call_args_list [i ][0 ][0 ][0 ]
211+ if span .name == "humanloop.flow" :
212+ flow_span = span
213+ elif span .name == "humanloop.prompt" :
214+ prompt_span = span
215+ elif span .name == "humanloop.tool" :
216+ tool_span = span
217+
201218 assert read_from_opentelemetry_span (
202219 span = flow_span ,
203220 key = HUMANLOOP_FILE_KEY ,
@@ -216,8 +233,6 @@ def test_flow_decorator_with_hl_exporter(
216233 key = HUMANLOOP_FILE_KEY ,
217234 )
218235
219- # NOTE: The type: ignore comments are caused by the MagicMock used to mock the HTTP client
220-
221236 # THEN the first Log uploaded is the Flow
222237 first_log = exporter ._client .flows .log .call_args_list [0 ][1 ] # type: ignore
223238 assert "flow" in first_log
0 commit comments