Skip to content

Commit c3cc96b

Browse files
author
Andrei Bratu
committed
Removed dependency on order of spans arrival in flow tests
1 parent 432e413 commit c3cc96b

File tree

1 file changed

+52
-37
lines changed

1 file changed

+52
-37
lines changed

tests/utilities/test_flow_decorator.py

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -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

142153
def 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

173190
def 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

Comments
 (0)