Skip to content

Commit 6f6cc2c

Browse files
chore[python]: Set dotted order for openai agents sdk integration (#1676)
Currently our openai agents sdk calls create/update run methods without `trace_id` and `dotted_order`, which means tracing will happen in the main thread. This fix sets those variables in the agents integration Issue discovered in: openai/openai-agents-python#529
1 parent b6e374e commit 6f6cc2c

File tree

4 files changed

+99
-26
lines changed

4 files changed

+99
-26
lines changed

python/langsmith/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from langsmith.utils import ContextThreadPoolExecutor
2121

2222
# Avoid calling into importlib on every call to __version__
23-
__version__ = "0.3.31"
23+
__version__ = "0.3.33"
2424
version = __version__ # for backwards compatibility
2525

2626

python/langsmith/wrappers/_agent_utils.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import json
22
import logging
3-
from typing import Any, Dict, Literal
3+
from datetime import datetime, timezone
4+
from typing import Any, Dict, Literal, Optional
5+
from uuid import uuid4
46

57
try:
68
from agents import tracing # type: ignore[import]
@@ -220,3 +222,16 @@ def extract_span_data(span: tracing.Span) -> Dict[str, Any]:
220222
return {}
221223

222224
return data
225+
226+
def ensure_dotted_order(
227+
start_time: Optional[datetime],
228+
run_id: Optional[str],
229+
parent_dotted_order: Optional[str] = None,
230+
) -> str:
231+
"""Create a dotted order from a start time and run id."""
232+
st = start_time or datetime.now(timezone.utc)
233+
id_ = run_id or str(uuid4())
234+
current_dotted_order = st.strftime("%Y%m%dT%H%M%S%fZ") + id_
235+
if parent_dotted_order is not None:
236+
return parent_dotted_order + "." + current_dotted_order
237+
return current_dotted_order

python/langsmith/wrappers/_openai_agents.py

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import datetime
21
import logging
3-
import uuid
4-
from typing import Dict, Optional
2+
from datetime import datetime, timezone
3+
from typing import Dict, Optional, TypedDict
4+
from uuid import uuid4
55

66
from langsmith import run_trees as rt
77

@@ -80,6 +80,13 @@ def __init__(self, *args, **kwargs):
8080

8181
if HAVE_AGENTS:
8282

83+
class RunData(TypedDict):
84+
id: str
85+
trace_id: str
86+
start_time: datetime
87+
dotted_order: str
88+
parent_run_id: Optional[str]
89+
8390
class OpenAIAgentsTracingProcessor(tracing.TracingProcessor): # type: ignore[no-redef]
8491
"""Tracing processor for the `OpenAI Agents SDK <https://openai.github.io/openai-agents-python/>`_.
8592
@@ -154,7 +161,7 @@ def __init__(
154161
self._first_response_inputs: dict = {}
155162
self._last_response_outputs: dict = {}
156163

157-
self._runs: Dict[str, str] = {}
164+
self._runs: Dict[str, RunData] = {}
158165

159166
def on_trace_start(self, trace: tracing.Trace) -> None:
160167
if self._name:
@@ -163,8 +170,22 @@ def on_trace_start(self, trace: tracing.Trace) -> None:
163170
run_name = trace.name
164171
else:
165172
run_name = "Agent workflow"
166-
trace_run_id = str(uuid.uuid4())
167-
self._runs[trace.trace_id] = trace_run_id
173+
trace_run_id = str(uuid4())
174+
175+
start_time = datetime.now(timezone.utc)
176+
177+
dotted_order = agent_utils.ensure_dotted_order(
178+
start_time=start_time,
179+
run_id=trace_run_id,
180+
)
181+
self._runs[trace.trace_id] = RunData(
182+
id=trace_run_id,
183+
trace_id=trace_run_id,
184+
start_time=start_time,
185+
dotted_order=dotted_order,
186+
parent_run_id=None,
187+
)
188+
168189
run_extra = {"metadata": self._metadata or {}}
169190

170191
trace_dict = trace.export() or {}
@@ -177,23 +198,30 @@ def on_trace_start(self, trace: tracing.Trace) -> None:
177198
inputs={},
178199
run_type="chain",
179200
id=trace_run_id,
201+
trace_id=trace_run_id,
202+
dotted_order=dotted_order,
203+
start_time=start_time,
180204
revision_id=None,
181205
extra=run_extra,
182206
tags=self._tags,
183207
project_name=self._project_name,
184208
)
209+
185210
self.client.create_run(**run_data)
186211
except Exception as e:
187212
logger.exception(f"Error creating trace run: {e}")
188213

189214
def on_trace_end(self, trace: tracing.Trace) -> None:
190-
run_id = self._runs.pop(trace.trace_id, None)
215+
run = self._runs.pop(trace.trace_id, None)
191216
trace_dict = trace.export() or {}
192217
metadata = {**(trace_dict.get("metadata") or {}), **(self._metadata or {})}
193-
if run_id:
218+
219+
if run:
194220
try:
195221
self.client.update_run(
196-
run_id=run_id,
222+
run_id=run["id"],
223+
trace_id=run["trace_id"],
224+
dotted_order=run["dotted_order"],
197225
inputs=self._first_response_inputs.pop(trace.trace_id, {}),
198226
outputs=self._last_response_outputs.pop(trace.trace_id, {}),
199227
extra={"metadata": metadata},
@@ -202,9 +230,38 @@ def on_trace_end(self, trace: tracing.Trace) -> None:
202230
logger.exception(f"Error updating trace run: {e}")
203231

204232
def on_span_start(self, span: tracing.Span) -> None:
205-
parent_run_id = self._runs.get(span.parent_id or span.trace_id)
206-
span_run_id = str(uuid.uuid4())
207-
self._runs[span.span_id] = span_run_id
233+
parent_run = (
234+
self._runs.get(span.parent_id)
235+
if span.parent_id
236+
else self._runs.get(span.trace_id)
237+
)
238+
239+
if parent_run is None:
240+
logger.warning(
241+
f"No trace info found for span, skipping: {span.span_id}"
242+
)
243+
return
244+
245+
trace_id = parent_run["trace_id"]
246+
247+
span_run_id = str(uuid4())
248+
span_start_time = (
249+
datetime.fromisoformat(span.started_at)
250+
if span.started_at
251+
else datetime.now(timezone.utc)
252+
)
253+
dotted_order = agent_utils.ensure_dotted_order(
254+
start_time=span_start_time,
255+
run_id=span_run_id,
256+
parent_dotted_order=parent_run["dotted_order"] if parent_run else None,
257+
)
258+
self._runs[span.span_id] = RunData(
259+
id=span_run_id,
260+
trace_id=trace_id,
261+
start_time=span_start_time,
262+
dotted_order=dotted_order,
263+
parent_run_id=parent_run["id"],
264+
)
208265

209266
run_name = agent_utils.get_run_name(span)
210267
run_type = agent_utils.get_run_type(span)
@@ -215,20 +272,20 @@ def on_span_start(self, span: tracing.Span) -> None:
215272
name=run_name,
216273
run_type=run_type,
217274
id=span_run_id,
218-
parent_run_id=parent_run_id,
275+
trace_id=trace_id,
276+
parent_run_id=parent_run["id"],
277+
dotted_order=dotted_order,
219278
inputs=extracted.get("inputs", {}),
220279
)
221280
if span.started_at:
222-
run_data["start_time"] = datetime.datetime.fromisoformat(
223-
span.started_at
224-
)
281+
run_data["start_time"] = datetime.fromisoformat(span.started_at)
225282
self.client.create_run(**run_data)
226283
except Exception as e:
227284
logger.exception(f"Error creating span run: {e}")
228285

229286
def on_span_end(self, span: tracing.Span) -> None:
230-
run_id = self._runs.pop(span.span_id, None)
231-
if run_id:
287+
run = self._runs.pop(span.span_id, None)
288+
if run:
232289
extracted = agent_utils.extract_span_data(span)
233290
metadata = extracted.get("metadata", {})
234291
metadata["openai_parent_id"] = span.parent_id
@@ -238,16 +295,17 @@ def on_span_end(self, span: tracing.Span) -> None:
238295
outputs = extracted.pop("outputs", {})
239296
inputs = extracted.pop("inputs", {})
240297
run_data: dict = dict(
241-
run_id=run_id,
298+
run_id=run["id"],
299+
trace_id=run["trace_id"],
300+
dotted_order=run["dotted_order"],
301+
parent_run_id=run["parent_run_id"],
242302
error=str(span.error) if span.error else None,
243303
outputs=outputs,
244304
inputs=inputs,
245305
extra=extracted,
246306
)
247307
if span.ended_at:
248-
run_data["end_time"] = datetime.datetime.fromisoformat(
249-
span.ended_at
250-
)
308+
run_data["end_time"] = datetime.fromisoformat(span.ended_at)
251309

252310
if isinstance(span.span_data, tracing.ResponseSpanData):
253311
self._first_response_inputs[span.trace_id] = (

python/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "langsmith"
3-
version = "0.3.32"
3+
version = "0.3.33"
44
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
55
authors = ["LangChain <[email protected]>"]
66
license = "MIT"
@@ -154,4 +154,4 @@ langsmith_plugin = "langsmith.pytest_plugin"
154154

155155
[tool.poetry_bumpversion.file."langsmith/__init__.py"]
156156
search = '__version__ = "{current_version}"'
157-
replace = '__version__ = "{new_version}"'
157+
replace = '__version__ = "{new_version}"'

0 commit comments

Comments
 (0)