Skip to content

Commit eed7a81

Browse files
feat(langchain): implement duration_ns metric for langchain callback async handler (#150)
1 parent de1897b commit eed7a81

File tree

2 files changed

+34
-5
lines changed

2 files changed

+34
-5
lines changed

src/galileo/handlers/langchain/async_handler.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,14 @@ async def _log_node_tree(self, node: Node) -> None:
116116

117117
# Log the current node based on its type
118118
if node.node_type in ("agent", "chain"):
119-
self._galileo_logger.add_workflow_span(input=input_, output=output, name=name, metadata=metadata, tags=tags)
119+
self._galileo_logger.add_workflow_span(
120+
input=input_,
121+
output=output,
122+
name=name,
123+
duration_ns=node.span_params.get("duration_ns"),
124+
metadata=metadata,
125+
tags=tags,
126+
)
120127
is_workflow_span = True
121128
elif node.node_type in ("llm", "chat"):
122129
self._galileo_logger.add_llm_span(
@@ -126,6 +133,7 @@ async def _log_node_tree(self, node: Node) -> None:
126133
temperature=node.span_params.get("temperature"),
127134
tools=node.span_params.get("tools"),
128135
name=name,
136+
duration_ns=node.span_params.get("duration_ns"),
129137
metadata=metadata,
130138
tags=tags,
131139
num_input_tokens=node.span_params.get("num_input_tokens"),
@@ -135,10 +143,22 @@ async def _log_node_tree(self, node: Node) -> None:
135143
)
136144
elif node.node_type == "retriever":
137145
self._galileo_logger.add_retriever_span(
138-
input=input_, output=output, name=name, metadata=metadata, tags=tags
146+
input=input_,
147+
output=output,
148+
name=name,
149+
duration_ns=node.span_params.get("duration_ns"),
150+
metadata=metadata,
151+
tags=tags,
139152
)
140153
elif node.node_type == "tool":
141-
self._galileo_logger.add_tool_span(input=input_, output=output, name=name, metadata=metadata, tags=tags)
154+
self._galileo_logger.add_tool_span(
155+
input=input_,
156+
output=output,
157+
name=name,
158+
duration_ns=node.span_params.get("duration_ns"),
159+
metadata=metadata,
160+
tags=tags,
161+
)
142162
else:
143163
_logger.warning(f"Unknown node type: {node.node_type}")
144164

@@ -187,6 +207,10 @@ async def _start_node(
187207

188208
# Create new node
189209
node = Node(node_type=node_type, span_params=kwargs, run_id=run_id, parent_run_id=parent_run_id)
210+
211+
if "start_time" not in node.span_params:
212+
node.span_params["start_time"] = time.perf_counter_ns()
213+
190214
self._nodes[node_id] = node
191215

192216
# Set as root node if needed
@@ -222,6 +246,8 @@ async def _end_node(self, run_id: UUID, **kwargs: Any) -> None:
222246
_logger.debug(f"No node exists for run_id {node_id}")
223247
return
224248

249+
node.span_params["duration_ns"] = time.perf_counter_ns() - node.span_params["start_time"]
250+
225251
# Update node parameters
226252
node.span_params.update(**kwargs)
227253

@@ -302,7 +328,7 @@ async def on_llm_start(
302328
model=model,
303329
temperature=temperature,
304330
metadata={k: str(v) for k, v in metadata.items()} if metadata else None,
305-
start_time=time.perf_counter(),
331+
start_time=time.perf_counter_ns(),
306332
time_to_first_token_ns=None,
307333
)
308334

@@ -315,7 +341,7 @@ async def on_llm_new_token(self, token: str, *, run_id: UUID, **kwargs: Any) ->
315341
if "time_to_first_token_ns" not in node.span_params or node.span_params["time_to_first_token_ns"] is None:
316342
start_time = node.span_params.get("start_time")
317343
if start_time is not None:
318-
node.span_params["time_to_first_token_ns"] = (time.perf_counter() - start_time) * 1e9
344+
node.span_params["time_to_first_token_ns"] = time.perf_counter_ns() - start_time
319345

320346
async def on_chat_model_start(
321347
self,

tests/test_langchain_async.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ async def test_start_node(self, callback: GalileoAsyncCallback):
6666
assert node.parent_run_id is None
6767
assert "name" in node.span_params
6868
assert node.span_params["name"] == "Parent Chain"
69+
assert node.span_params["start_time"] > 0
6970
assert str(parent_id) in callback._nodes
7071

7172
# Create a child node
@@ -117,6 +118,7 @@ async def test_on_chain_start_end(self, callback: GalileoAsyncCallback, galileo_
117118
assert str(run_id) in callback._nodes
118119
assert callback._nodes[str(run_id)].node_type == "chain"
119120
assert callback._nodes[str(run_id)].span_params["input"] == '{"query": "test question"}'
121+
assert callback._nodes[str(run_id)].span_params["start_time"] > 0
120122

121123
# End chain
122124
await callback.on_chain_end(outputs='{"result": "test answer"}', run_id=run_id)
@@ -223,6 +225,7 @@ async def test_on_llm_start_end(self, callback: GalileoAsyncCallback):
223225
assert callback._nodes[str(run_id)].span_params["num_input_tokens"] == 10
224226
assert callback._nodes[str(run_id)].span_params["num_output_tokens"] == 20
225227
assert callback._nodes[str(run_id)].span_params["total_tokens"] == 30
228+
assert callback._nodes[str(run_id)].span_params["duration_ns"] > 0
226229

227230
@mark.asyncio
228231
async def test_on_chat_model_start(self, callback: GalileoAsyncCallback):

0 commit comments

Comments
 (0)