Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-langsmith for 
openSUSE:Factory checked in at 2026-10-02 23:05:05
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-langsmith (Old)
 and      /work/SRC/openSUSE:Factory/.python-langsmith.new.1631729 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-langsmith"

Fri Oct  2 23:05:05 2026 rev:32 rq:1382062 version:0.14.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-langsmith/python-langsmith.changes        
2026-10-01 22:21:27.096467120 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-langsmith.new.1631729/python-langsmith.changes
   2026-10-02 23:06:23.658951116 +0200
@@ -1,0 +2,17 @@
+Fri Oct  2 08:46:01 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 0.14.3:
+  * The OpenTelemetry exporter writes the gen_ai.prompt and
+    gen_ai.completion attributes as JSON strings instead of raw bytes,
+    which raised a TypeError in the exporter
+  * Agent addressing is now sent as one typed address object (kind, id,
+    environment) instead of the flat agent_id and agent_environment
+    fields; a retried batch that re-sends payloads it already rendered
+    no longer trips the destination-collision warning
+  * Sandbox websockets retry a transient failure that closes the socket
+    before a command starts
+  * No new runtime dependencies; requires-python stays >= 3.10
+  * Many more fixes and improvements; see upstream's release notes for
+    the full list
+
+-------------------------------------------------------------------

Old:
----
  langsmith-0.14.2.tar.gz

New:
----
  langsmith-0.14.3.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-langsmith.spec ++++++
--- /var/tmp/diff_new_pack.FXJQVR/_old  2026-10-02 23:06:24.550988687 +0200
+++ /var/tmp/diff_new_pack.FXJQVR/_new  2026-10-02 23:06:24.552988771 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           python-langsmith
-Version:        0.14.2
+Version:        0.14.3
 Release:        0
 Summary:        Client library for the LangSmith LLM tracing and evaluation 
platform
 License:        MIT

++++++ langsmith-0.14.2.tar.gz -> langsmith-0.14.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langsmith-0.14.2/.bumpversion.cfg 
new/langsmith-0.14.3/.bumpversion.cfg
--- old/langsmith-0.14.2/.bumpversion.cfg       2020-02-02 01:00:00.000000000 
+0100
+++ new/langsmith-0.14.3/.bumpversion.cfg       2020-02-02 01:00:00.000000000 
+0100
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 0.14.2
+current_version = 0.14.3
 parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
 serialize = {major}.{minor}.{patch}
 search = {current_version}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langsmith-0.14.2/PKG-INFO 
new/langsmith-0.14.3/PKG-INFO
--- old/langsmith-0.14.2/PKG-INFO       2020-02-02 01:00:00.000000000 +0100
+++ new/langsmith-0.14.3/PKG-INFO       2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.5
 Name: langsmith
-Version: 0.14.2
+Version: 0.14.3
 Summary: Client library to connect to the LangSmith Observability and 
Evaluation Platform.
 Project-URL: Homepage, https://smith.langchain.com/
 Project-URL: Documentation, https://docs.smith.langchain.com/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langsmith-0.14.2/langsmith/__init__.py 
new/langsmith-0.14.3/langsmith/__init__.py
--- old/langsmith-0.14.2/langsmith/__init__.py  2020-02-02 01:00:00.000000000 
+0100
+++ new/langsmith-0.14.3/langsmith/__init__.py  2020-02-02 01:00:00.000000000 
+0100
@@ -51,7 +51,7 @@
 
 # Avoid calling into importlib on every call to __version__
 
-__version__ = "0.14.2"
+__version__ = "0.14.3"
 version = __version__  # for backwards compatibility
 
 # Metadata key to hide a traced run from LangSmith's Messages View.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langsmith-0.14.2/langsmith/_address.py 
new/langsmith-0.14.3/langsmith/_address.py
--- old/langsmith-0.14.2/langsmith/_address.py  2020-02-02 01:00:00.000000000 
+0100
+++ new/langsmith-0.14.3/langsmith/_address.py  2020-02-02 01:00:00.000000000 
+0100
@@ -6,10 +6,11 @@
     to a project. This API may change without notice.
 
 The handle is propagated whole -- through context variables, run trees,
-replicas and distributed-tracing headers -- and only unpacked into wire fields
-where a run or feedback is serialized. Every dimension is a dataclass field
-whose `wire` metadata names its payload key; the env var, header and payload
-handling below are derived from the fields, so a new dimension is a new field.
+replicas and distributed-tracing headers -- and only rendered into the
+`address` payload object where a run or feedback is serialized. Every dimension
+is a dataclass field whose `wire` metadata names its header key; the env var,
+header and payload handling below are derived from the fields, so a new
+dimension is a new field.
 
 Example:
     ```python
@@ -91,8 +92,22 @@
         """Return the payload keys an address renders to."""
         return tuple(f.metadata["wire"] for f in dataclasses.fields(cls))
 
+    def _to_payload(self) -> dict[str, Any]:
+        """Render as the `address` object of a run / feedback payload.
+
+        `kind` and `environment` are uppercase and case-sensitive on the wire,
+        whatever the caller passed.
+        """
+        return {
+            "address": {
+                "kind": "AGENT",
+                "id": self.agent_id,
+                "environment": self.agent_environment.upper(),
+            }
+        }
+
     def _to_wire(self) -> dict[str, str]:
-        """Render as run / feedback payload fields, omitting unset ones."""
+        """Render as header / replica fields, omitting unset ones."""
         return {
             f.metadata["wire"]: value
             for f in dataclasses.fields(self)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/langsmith-0.14.2/langsmith/_internal/_agent_addressing.py 
new/langsmith-0.14.3/langsmith/_internal/_agent_addressing.py
--- old/langsmith-0.14.2/langsmith/_internal/_agent_addressing.py       
2020-02-02 01:00:00.000000000 +0100
+++ new/langsmith-0.14.3/langsmith/_internal/_agent_addressing.py       
2020-02-02 01:00:00.000000000 +0100
@@ -22,8 +22,8 @@
 5. the env vars (`LANGSMITH_PROJECT`, `LANGSMITH_AGENT_*`)
 
 The address travels as a whole `Address` everywhere -- context variables, run
-trees, replicas, headers -- and is unpacked into wire fields only here, in
-`apply_to_payload` and `FeedbackCreate.model_dump`.
+trees, replicas, headers -- and is rendered into the `address` payload object
+only here, in `apply_to_payload` and `FeedbackCreate.model_dump`.
 """
 
 from __future__ import annotations
@@ -225,6 +225,10 @@
         )
 
 
+def _is_rendered(value: Any) -> bool:
+    return isinstance(value, dict) and "kind" in value
+
+
 def apply_to_payload(payload: dict, *, update: bool = False) -> None:
     """Render a run payload's address into its wire fields.
 
@@ -236,6 +240,9 @@
     address from the post that established it, and one naming nothing is
     resolved by run id.
     """
+    if _is_rendered(payload.get("address")):
+        # A retried batch re-sends the caller's own dicts, already rendered.
+        return
     address = check_address(payload.pop("address", None))
     named_project = (
         payload.get("session_id") is not None or payload.get("session_name") 
is not None
@@ -245,7 +252,7 @@
     if address is None:
         return
     warn_is_beta()
-    payload.update(address._to_wire())
+    payload.update(address._to_payload())
     if not named_project:
         payload.pop("session_name", None)
         payload.pop("session_id", None)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langsmith-0.14.2/langsmith/_internal/_operations.py 
new/langsmith-0.14.3/langsmith/_internal/_operations.py
--- old/langsmith-0.14.2/langsmith/_internal/_operations.py     2020-02-02 
01:00:00.000000000 +0100
+++ new/langsmith-0.14.3/langsmith/_internal/_operations.py     2020-02-02 
01:00:00.000000000 +0100
@@ -219,7 +219,7 @@
 
 
 # Allow-list of routing fields the collision warning may log; never 
credentials.
-_DESTINATION_FIELDS = ("session_name", "session_id", "agent_id", 
"agent_environment")
+_DESTINATION_FIELDS = ("session_name", "session_id", "address")
 
 
 def _warn_if_destinations_differ(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/langsmith-0.14.2/langsmith/_internal/otel/_otel_exporter.py 
new/langsmith-0.14.3/langsmith/_internal/otel/_otel_exporter.py
--- old/langsmith-0.14.2/langsmith/_internal/otel/_otel_exporter.py     
2020-02-02 01:00:00.000000000 +0100
+++ new/langsmith-0.14.3/langsmith/_internal/otel/_otel_exporter.py     
2020-02-02 01:00:00.000000000 +0100
@@ -64,6 +64,7 @@
 
 logger = logging.getLogger(__name__)
 
+
 # OpenTelemetry GenAI semconv attribute names
 GEN_AI_OPERATION_NAME = "gen_ai.operation.name"
 GEN_AI_SYSTEM = "gen_ai.system"
@@ -757,7 +758,7 @@
                             GEN_AI_REQUEST_EXTRA_BODY, inputs["extra_body"]
                         )
 
-                span.set_attribute(GENAI_PROMPT, op.inputs)
+                span.set_attribute(GENAI_PROMPT, op.inputs.decode("utf-8"))
 
             except Exception:
                 logger.debug(
@@ -839,7 +840,7 @@
                                 GEN_AI_USAGE_OUTPUT_TOKEN_DETAILS, 
output_token_details
                             )
 
-                span.set_attribute(GENAI_COMPLETION, op.outputs)
+                span.set_attribute(GENAI_COMPLETION, 
op.outputs.decode("utf-8"))
 
             except Exception:
                 logger.debug(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langsmith-0.14.2/langsmith/sandbox/_ws_execute.py 
new/langsmith-0.14.3/langsmith/sandbox/_ws_execute.py
--- old/langsmith-0.14.2/langsmith/sandbox/_ws_execute.py       2020-02-02 
01:00:00.000000000 +0100
+++ new/langsmith-0.14.3/langsmith/sandbox/_ws_execute.py       2020-02-02 
01:00:00.000000000 +0100
@@ -483,7 +483,7 @@
                 raise SandboxServerReloadError(
                     "Server is reloading, reconnect to resume"
                 ) from e
-            raise SandboxConnectionError(
+            raise SandboxRetryableConnectionError(
                 f"WebSocket connection closed unexpectedly: {e}"
             ) from e
         except (OSError, RuntimeError) as e:
@@ -681,7 +681,7 @@
                 raise SandboxServerReloadError(
                     "Server is reloading, reconnect to resume"
                 ) from e
-            raise SandboxConnectionError(
+            raise SandboxRetryableConnectionError(
                 f"WebSocket connection closed unexpectedly: {e}"
             ) from e
         except (OSError, RuntimeError) as e:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langsmith-0.14.2/langsmith/schemas.py 
new/langsmith-0.14.3/langsmith/schemas.py
--- old/langsmith-0.14.2/langsmith/schemas.py   2020-02-02 01:00:00.000000000 
+0100
+++ new/langsmith-0.14.3/langsmith/schemas.py   2020-02-02 01:00:00.000000000 
+0100
@@ -701,7 +701,6 @@
         # `Address` can't be imported here at runtime (see the import above),
         # so the field is declared loosely and checked by `_check_address`.
         address: Optional[Any] = Field(default=None, exclude=True)
-    """(beta) An address, rendered into its wire fields on dump."""
 
     @field_validator("address")
     @classmethod
@@ -711,10 +710,10 @@
         return check_address(value)
 
     def model_dump(self, **kwargs: Any) -> dict[str, Any]:
-        """Dump, with `address` unpacked into its wire fields."""
+        """Dump the feedback, including its address."""
         dumped = super().model_dump(**kwargs)
         if self.address is not None:
-            dumped.update(self.address._to_wire())
+            dumped.update(self.address._to_payload())
         return dumped
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/langsmith-0.14.2/tests/unit_tests/sandbox/test_async_ws_execute.py 
new/langsmith-0.14.3/tests/unit_tests/sandbox/test_async_ws_execute.py
--- old/langsmith-0.14.2/tests/unit_tests/sandbox/test_async_ws_execute.py      
2020-02-02 01:00:00.000000000 +0100
+++ new/langsmith-0.14.3/tests/unit_tests/sandbox/test_async_ws_execute.py      
2020-02-02 01:00:00.000000000 +0100
@@ -1175,3 +1175,30 @@
             with pytest.raises(SandboxConnectionError, match="no valid HTTP 
response"):
                 async for _ in msg_stream:
                     pass
+
+    @pytest.mark.asyncio
+    async def test_abrupt_execute_close_is_retryable(self):
+        from websockets.exceptions import ConnectionClosedError, 
InvalidHandshake
+
+        from langsmith.sandbox._exceptions import 
SandboxRetryableConnectionError
+
+        ws = MagicMock()
+        ws.send = AsyncMock()
+        ws.__aiter__.side_effect = ConnectionClosedError(None, None)
+        connect = MagicMock()
+        connect.return_value.__aenter__ = AsyncMock(return_value=ws)
+        connect.return_value.__aexit__ = AsyncMock(return_value=False)
+
+        with patch(
+            "langsmith.sandbox._ws_execute._ensure_websockets_async",
+            return_value=(connect, ConnectionClosedError, InvalidHandshake),
+        ):
+            msg_stream, _ = await run_ws_stream_async(
+                "https://sb.example.com";, "key", "echo hi"
+            )
+            with pytest.raises(
+                SandboxRetryableConnectionError,
+                match="no close frame received or sent",
+            ):
+                async for _ in msg_stream:
+                    pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/langsmith-0.14.2/tests/unit_tests/sandbox/test_ws_execute.py 
new/langsmith-0.14.3/tests/unit_tests/sandbox/test_ws_execute.py
--- old/langsmith-0.14.2/tests/unit_tests/sandbox/test_ws_execute.py    
2020-02-02 01:00:00.000000000 +0100
+++ new/langsmith-0.14.3/tests/unit_tests/sandbox/test_ws_execute.py    
2020-02-02 01:00:00.000000000 +0100
@@ -1136,7 +1136,7 @@
         with pytest.raises(SandboxRetryableConnectionError, 
match="error_id=err-503"):
             _raise_for_invalid_handshake(exc, 
"ws://example.com/sb-123/execute/ws")
 
-    @pytest.mark.parametrize("status", [500, 502, 504])
+    @pytest.mark.parametrize("status", [429, 500, 502, 504])
     def test_transient_5xx_is_retryable_and_preserves_error_id(self, status):
         from langsmith.sandbox._ws_execute import _raise_for_invalid_handshake
 
@@ -1234,3 +1234,22 @@
             )
             with pytest.raises(SandboxConnectionError, match="no valid HTTP 
response"):
                 list(msg_stream)
+
+    def test_abrupt_execute_close_is_retryable(self):
+        from websockets.exceptions import ConnectionClosedError, 
InvalidHandshake
+
+        ws = MagicMock()
+        ws.__iter__.side_effect = ConnectionClosedError(None, None)
+        connect = MagicMock()
+        connect.return_value.__enter__.return_value = ws
+
+        with patch(
+            "langsmith.sandbox._ws_execute._ensure_websockets",
+            return_value=(connect, ConnectionClosedError, InvalidHandshake),
+        ):
+            msg_stream, _ = run_ws_stream("https://sb.example.com";, "key", 
"echo hi")
+            with pytest.raises(
+                SandboxRetryableConnectionError,
+                match="no close frame received or sent",
+            ):
+                list(msg_stream)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langsmith-0.14.2/tests/unit_tests/test_address.py 
new/langsmith-0.14.3/tests/unit_tests/test_address.py
--- old/langsmith-0.14.2/tests/unit_tests/test_address.py       2020-02-02 
01:00:00.000000000 +0100
+++ new/langsmith-0.14.3/tests/unit_tests/test_address.py       2020-02-02 
01:00:00.000000000 +0100
@@ -427,12 +427,16 @@
 
 
 class TestWire:
-    def test_a_run_payload_carries_the_wire_fields(self) -> None:
+    def test_a_run_payload_carries_the_address_object(self) -> None:
         payload = RunTree(name="r", address=SUPPORT)._get_dicts_safe()
         _agent_addressing.apply_to_payload(payload)
-        assert payload["agent_id"] == "support"
-        assert payload["agent_environment"] == "production"
-        assert "address" not in payload
+        assert payload["address"] == {
+            "kind": "AGENT",
+            "id": "support",
+            "environment": "PRODUCTION",
+        }
+        assert "agent_id" not in payload
+        assert "agent_environment" not in payload
         assert "session_name" not in payload
 
     @pytest.mark.parametrize("update", [False, True])
@@ -442,10 +446,25 @@
         """`batch_ingest_runs` / `multipart_ingest` dump pydantic runs."""
         run = RunTree(name="r", address=SUPPORT)
         payload = client._run_transform(run, update=update)
-        assert (payload["agent_id"], payload["agent_environment"]) == (
-            "support",
-            "production",
-        )
+        assert payload["address"] == {
+            "kind": "AGENT",
+            "id": "support",
+            "environment": "PRODUCTION",
+        }
+
+    def test_a_rendered_payload_is_not_rendered_again(self) -> None:
+        """A retried batch re-sends the caller's dicts, already rendered."""
+        payload = {"id": "x", "address": SUPPORT}
+        _agent_addressing.apply_to_payload(payload)
+        rendered = dict(payload)
+        _agent_addressing.apply_to_payload(payload)
+        assert payload == rendered
+
+    def test_retrying_the_same_batch_keeps_the_address(self, client: Client) 
-> None:
+        run = {"name": "r", "address": SUPPORT, "run_type": "chain"}
+        first = client._run_transform(run, copy=False)["address"]
+        second = client._run_transform(run, copy=False)["address"]
+        assert first == second == SUPPORT._to_payload()["address"]
 
     def test_a_project_payload_is_untouched(self) -> None:
         payload = {"name": "r", "session_name": "p"}
@@ -458,9 +477,9 @@
         _set_env(monkeypatch, LANGSMITH_AGENT_ID="a", 
LANGSMITH_AGENT_ENVIRONMENT="e")
         payload: dict = {"id": "x"}
         _agent_addressing.apply_to_payload(payload, update=True)
-        assert "agent_id" not in payload
+        assert "address" not in payload
 
-    def test_feedback_carries_the_wire_fields(self) -> None:
+    def test_feedback_carries_the_address_object(self) -> None:
         feedback = FeedbackCreate(
             key="k",
             feedback_source={"type": "api"},  # type: ignore[arg-type]
@@ -470,8 +489,12 @@
             address=SUPPORT,
         )
         dumped = feedback.model_dump(exclude_none=True)
-        assert dumped["agent_id"] == "support"
-        assert "address" not in dumped
+        assert dumped["address"] == {
+            "kind": "AGENT",
+            "id": "support",
+            "environment": "PRODUCTION",
+        }
+        assert "agent_id" not in dumped
 
     @pytest.mark.parametrize(
         "project",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langsmith-0.14.2/tests/unit_tests/test_client.py 
new/langsmith-0.14.3/tests/unit_tests/test_client.py
--- old/langsmith-0.14.2/tests/unit_tests/test_client.py        2020-02-02 
01:00:00.000000000 +0100
+++ new/langsmith-0.14.3/tests/unit_tests/test_client.py        2020-02-02 
01:00:00.000000000 +0100
@@ -8267,10 +8267,12 @@
     patch_body = _wait_for_part(session, "patch")
 
     for kind, body in (("post", post_body), ("patch", patch_body)):
-        assert (body.get("agent_id"), body.get("agent_environment")) == (
-            "my-agent",
-            "staging",
-        ), kind
+        assert body.get("address") == {
+            "kind": "AGENT",
+            "id": "my-agent",
+            "environment": "STAGING",
+        }, kind
+        assert "agent_id" not in body, kind
         assert "session_name" not in body, kind
         assert "session_id" not in body, kind
 
@@ -8288,8 +8290,7 @@
 
     body = _wait_for_part(session, "post")
     assert body.get("session_name") == "default"
-    assert "agent_id" not in body
-    assert "agent_environment" not in body
+    assert "address" not in body
 
 
 class TestAConfiguredProjectTravelsWithTheAgent:
@@ -8317,8 +8318,7 @@
 
         body = _wait_for_part(session, "post")
         assert body.get("session_name") == "explicit"
-        assert "agent_id" not in body
-        assert "agent_environment" not in body
+        assert "address" not in body
 
     def test_the_client_warns_once_at_construction(
         self, monkeypatch: pytest.MonkeyPatch
@@ -8419,10 +8419,9 @@
         patch: dict = {"session_name": None, "session_id": None}
         _agent_addressing.apply_to_payload(patch, update=True)
         assert post == {"session_name": "myproj"}
-        # No agent fields added. The null session keys are left exactly as
+        # No address added. The null session keys are left exactly as
         # `update_run` built them, which is what `main` sends today.
-        assert "agent_id" not in patch
-        assert "agent_environment" not in patch
+        assert "address" not in patch
         assert patch == {"session_name": None, "session_id": None}
 
 
@@ -8549,8 +8548,7 @@
             }
         )
         body = json.loads(serialized.feedback)
-        assert "agent_id" not in body
-        assert "agent_environment" not in body
+        assert "address" not in body
 
     def test_agent_addressing_reaches_the_feedback_part(self) -> None:
         serialized = _operations.serialize_feedback_dict(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langsmith-0.14.2/tests/unit_tests/test_operations.py 
new/langsmith-0.14.3/tests/unit_tests/test_operations.py
--- old/langsmith-0.14.2/tests/unit_tests/test_operations.py    2020-02-02 
01:00:00.000000000 +0100
+++ new/langsmith-0.14.3/tests/unit_tests/test_operations.py    2020-02-02 
01:00:00.000000000 +0100
@@ -231,6 +231,7 @@
 
 
 def test_destination_fields_cover_address() -> None:
-    from langsmith._address import Address
+    import langsmith as ls
 
-    assert set(Address._wire_keys()) <= set(_DESTINATION_FIELDS)
+    address = ls.address(agent_id="a", agent_environment="e")
+    assert set(address._to_payload()) <= set(_DESTINATION_FIELDS)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/langsmith-0.14.2/tests/unit_tests/test_otel_exporter.py 
new/langsmith-0.14.3/tests/unit_tests/test_otel_exporter.py
--- old/langsmith-0.14.2/tests/unit_tests/test_otel_exporter.py 2020-02-02 
01:00:00.000000000 +0100
+++ new/langsmith-0.14.3/tests/unit_tests/test_otel_exporter.py 2020-02-02 
01:00:00.000000000 +0100
@@ -11,6 +11,7 @@
 import pytest
 
 from langsmith import Client
+from langsmith._internal import _orjson
 from langsmith._internal.otel._otel_exporter import (
     GEN_AI_RESPONSE_FINISH_REASONS,
     GEN_AI_TOOL_CALL_ID,
@@ -507,3 +508,29 @@
         endpoint="https://api.smith.langchain.com/otel";,
         headers={"x-api-key": "lsv2_pt_test123"},
     )
+
+
+def test_io_attributes_are_str_not_bytes():
+    """Export input/output JSON strings regardless of OTel version."""
+    with patch(
+        "langsmith._internal.otel._otel_exporter._import_otel_exporter"
+    ) as mock_import:
+        mock_import.return_value = (MagicMock(),) * 8
+        exporter = OTELExporter(span_ttl_seconds=1)
+
+    inputs = {"messages": [{"role": "user", "content": "こんにちは 🌍"}]}
+    outputs = {"generations": [[{"text": "café ✅"}]]}
+    span = MagicMock()
+    op = SimpleNamespace(
+        id=uuid.uuid4(),
+        inputs=_orjson.dumps(inputs),
+        outputs=_orjson.dumps(outputs),
+    )
+
+    exporter._set_io_attributes(span, op)
+
+    attrs = {call.args[0]: call.args[1] for call in 
span.set_attribute.call_args_list}
+    assert isinstance(attrs["gen_ai.prompt"], str)
+    assert isinstance(attrs["gen_ai.completion"], str)
+    assert json.loads(attrs["gen_ai.prompt"]) == inputs
+    assert json.loads(attrs["gen_ai.completion"]) == outputs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langsmith-0.14.2/uv.lock new/langsmith-0.14.3/uv.lock
--- old/langsmith-0.14.2/uv.lock        2020-02-02 01:00:00.000000000 +0100
+++ new/langsmith-0.14.3/uv.lock        2020-02-02 01:00:00.000000000 +0100
@@ -3643,14 +3643,14 @@
 
 [[package]]
 name = "pyjwt"
-version = "2.13.0"
+version = "2.15.0"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
     { name = "typing-extensions", marker = "python_full_version < '3.11'" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz";,
 hash = 
"sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size 
= 107515, upload-time = "2026-05-21T19:54:36.618Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/02/a5/5197bfd06417837ac079921c66fa6393f1dea3557272a263cebfef69e432/pyjwt-2.15.0.tar.gz";,
 hash = 
"sha256:b11c5f9791d7bf51c2b39a81ed669f6b2dbbd669df2942f6c60167e9e3d1abe4", size 
= 120513, upload-time = "2026-09-23T16:56:00.689Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl";,
 hash = 
"sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size 
= 31274, upload-time = "2026-05-21T19:54:35.362Z" },
+    { url = 
"https://files.pythonhosted.org/packages/e8/55/40e45bf052ee8ee12a4dfd785519660f8effa7b065442b91646ec6828619/pyjwt-2.15.0-py3-none-any.whl";,
 hash = 
"sha256:7a3742debf6b879e912dbb9819ceec1594be812452b78c5f2e2dfc56564954f8", size 
= 33680, upload-time = "2026-09-23T16:55:59.241Z" },
 ]
 
 [package.optional-dependencies]
@@ -4993,11 +4993,11 @@
 
 [[package]]
 name = "urllib3"
-version = "2.7.0"
+version = "2.8.0"
 source = { registry = "https://pypi.org/simple"; }
-sdist = { url = 
"https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz";,
 hash = 
"sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size 
= 433602, upload-time = "2026-05-07T16:13:18.596Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz";,
 hash = 
"sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size 
= 458972, upload-time = "2026-09-15T19:29:36.253Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl";,
 hash = 
"sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size 
= 131087, upload-time = "2026-05-07T16:13:17.151Z" },
+    { url = 
"https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl";,
 hash = 
"sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size 
= 135717, upload-time = "2026-09-15T19:29:34.577Z" },
 ]
 
 [[package]]

Reply via email to