langwatch 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
langwatch/__version__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Version information for LangWatch."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.5.0"
@@ -157,6 +157,7 @@ class SpanMetrics(TypedDict, total=False):
157
157
  prompt_tokens: Optional[int]
158
158
  completion_tokens: Optional[int]
159
159
  cost: Optional[float]
160
+ first_token_ms: Optional[int]
160
161
 
161
162
 
162
163
  class SpanParams(TypedDict, total=False):
@@ -1,6 +1,7 @@
1
1
  from functools import wraps
2
2
  from opentelemetry import trace
3
- from typing import TYPE_CHECKING, TypeVar, Callable, Any
3
+ from opentelemetry.trace import Span
4
+ from typing import TYPE_CHECKING, TypeVar, Callable, Any, Optional, Union, Dict
4
5
  import json
5
6
 
6
7
  from langwatch.attributes import AttributeKey
@@ -11,9 +12,37 @@ if TYPE_CHECKING:
11
12
  T = TypeVar("T", bound="Prompt")
12
13
 
13
14
 
15
+ def _set_attribute_if_not_none(
16
+ span: Span, key: str, value: Optional[Union[str, int, float, bool]]
17
+ ) -> None:
18
+ """Set span attribute only if value is not None."""
19
+ if value is not None:
20
+ span.set_attribute(key, value)
21
+
22
+
14
23
  class PromptTracing:
15
24
  """Namespace for Prompt method tracing decorators"""
16
25
 
26
+ @staticmethod
27
+ def _set_prompt_attributes(span: Span, prompt: "Prompt") -> None:
28
+ """Set prompt-related attributes on the span."""
29
+ _set_attribute_if_not_none(
30
+ span, AttributeKey.LangWatchPromptId, getattr(prompt, "id", None)
31
+ )
32
+ _set_attribute_if_not_none(
33
+ span, AttributeKey.LangWatchPromptHandle, getattr(prompt, "handle", None)
34
+ )
35
+ _set_attribute_if_not_none(
36
+ span,
37
+ AttributeKey.LangWatchPromptVersionId,
38
+ getattr(prompt, "version_id", None),
39
+ )
40
+ _set_attribute_if_not_none(
41
+ span,
42
+ AttributeKey.LangWatchPromptVersionNumber,
43
+ getattr(prompt, "version", None),
44
+ )
45
+
17
46
  @staticmethod
18
47
  def _create_compile_decorator(
19
48
  span_name: str,
@@ -28,26 +57,15 @@ class PromptTracing:
28
57
  with trace.get_tracer(__name__).start_as_current_span(
29
58
  PromptTracing._create_span_name(span_name)
30
59
  ) as span:
31
- # Set base prompt
32
- span.set_attributes(
33
- {
34
- AttributeKey.LangWatchPromptId: getattr(self, "id", None),
35
- AttributeKey.LangWatchPromptHandle: getattr(
36
- self, "handle", None
37
- ),
38
- AttributeKey.LangWatchPromptVersionId: getattr(
39
- self, "version_id", None
40
- ),
41
- AttributeKey.LangWatchPromptVersionNumber: getattr(
42
- self, "version", None
43
- ),
44
- }
45
- )
60
+ # Set prompt attributes
61
+ PromptTracing._set_prompt_attributes(span, self)
46
62
 
47
63
  # Create variables dict from args and kwargs
48
- variables_dict: dict[str, Any] = {}
49
- if args and args[0] is not None:
50
- variables_dict.update(args[0])
64
+ variables_dict: Dict[str, Any] = {}
65
+ if args:
66
+ first_arg = args[0]
67
+ if first_arg is not None and hasattr(first_arg, "update"):
68
+ variables_dict.update(first_arg)
51
69
  variables_dict.update(kwargs)
52
70
 
53
71
  span.set_attribute(
@@ -88,7 +106,7 @@ class PromptTracing:
88
106
  @staticmethod
89
107
  def _create_span_name(span_name: str) -> str:
90
108
  """Create a span name for the prompt"""
91
- return "Prompt" + "." + span_name
109
+ return f"Prompt.{span_name}"
92
110
 
93
111
 
94
112
  prompt_tracing = PromptTracing()
@@ -46,3 +46,45 @@ def test_metadata_not_lost_on_multiple_updates():
46
46
  # None update should not clear
47
47
  trace.update(metadata=None)
48
48
  assert trace.metadata == {"x": 1, "y": 99, "z": 3}
49
+
50
+
51
+ def test_metrics_update():
52
+ # Test updating metrics including first_token_ms
53
+ trace = LangWatchTrace()
54
+
55
+ # Update with first_token_ms
56
+ trace.update(metrics={"first_token_ms": 150})
57
+
58
+ # Update with additional metrics
59
+ trace.update(metrics={"prompt_tokens": 100, "completion_tokens": 50})
60
+
61
+ # Update first_token_ms again
62
+ trace.update(metrics={"first_token_ms": 200})
63
+
64
+ # Verify the metrics are properly handled (they should be passed to root_span.update)
65
+ # Since we can't easily test the internal span state without mocking,
66
+ # we'll test that the update method doesn't raise any errors
67
+ assert True # If we get here, the updates succeeded
68
+
69
+
70
+ def test_metrics_update_with_root_span():
71
+ # Test metrics update when there's an active root span
72
+ trace = LangWatchTrace()
73
+
74
+ with trace:
75
+ # Update metrics while span is active
76
+ trace.update(metrics={"first_token_ms": 150, "prompt_tokens": 100})
77
+
78
+ # Verify metrics were set on the root span
79
+ assert trace.root_span is not None
80
+ assert trace.root_span.metrics is not None
81
+ assert trace.root_span.metrics["first_token_ms"] == 150
82
+ assert trace.root_span.metrics["prompt_tokens"] == 100
83
+
84
+ # Update again with different values
85
+ trace.update(metrics={"first_token_ms": 200, "completion_tokens": 50})
86
+
87
+ # Verify the metrics were updated (merged, not replaced)
88
+ assert trace.root_span.metrics["first_token_ms"] == 200 # Updated
89
+ assert trace.root_span.metrics["prompt_tokens"] == 100 # Preserved
90
+ assert trace.root_span.metrics["completion_tokens"] == 50 # Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langwatch
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: LangWatch Python SDK, for monitoring your LLMs
5
5
  Author-email: Langwatch Engineers <engineering@langwatch.ai>
6
6
  License: MIT
@@ -1,5 +1,5 @@
1
1
  langwatch/__init__.py,sha256=TzPHzqCFGZJByI3sAIKrNB33Qi4PqVmgYDZuBwPnhPc,4222
2
- langwatch/__version__.py,sha256=gmt7xPIf5roNsx_qI3mY2ACMUeQCTAkZI1eT66Nb_r0,64
2
+ langwatch/__version__.py,sha256=0ja7-HUqZpz4fmKILMG1h_z6bE7hCrXVbzVz1PDcc8s,64
3
3
  langwatch/attributes.py,sha256=nXdI_G85wQQCAdAcwjCiLYdEYj3wATmfgCmhlf6dVIk,3910
4
4
  langwatch/batch_evaluation.py,sha256=piez7TYqUZPb9NlIShTuTPmSzrZqX-vm2Grz_NGXe04,16078
5
5
  langwatch/client.py,sha256=WTNcYSik7kZ2kH-qGDnhbMTosc8e_Xhab_lZlfh5TC8,25559
@@ -14,7 +14,7 @@ langwatch/state.py,sha256=qXvPAjO90jdokCU6tPSwjHIac4QU_5N0pSd9dfmc9kY,1204
14
14
  langwatch/tracer.py,sha256=t5FOdP1es9H_pPGqGUBLXCyEln0tTi4m4M9b6WxCrPU,975
15
15
  langwatch/types.py,sha256=h6r3tNTzWqENx-9j_JPmOMZfFoKq9SNpEtxpAACk2G0,3114
16
16
  langwatch/dataset/__init__.py,sha256=hZBcbjXuBO2qE5osJtd9wIE9f45F6-jpNTrne5nk4eE,2606
17
- langwatch/domain/__init__.py,sha256=luuin3-6mmMqDaOJE_1wb7M1ccjhhZL80UN0TBA_TXM,6040
17
+ langwatch/domain/__init__.py,sha256=gSCOV3WkRhp_--9D1vxw7BYpnMRbpGh-2NbsXd4KZC0,6074
18
18
  langwatch/dspy/__init__.py,sha256=E9rqyhOyIO3E-GxJSZlHtsvjapFjKQGF85QRcpKSvKE,34280
19
19
  langwatch/evaluation/__init__.py,sha256=Jy7PW5VQbMoDGdOLRlQmDEvo_9TDkBLmrLrfocxddLM,281
20
20
  langwatch/evaluation/evaluation.py,sha256=AF3VXCcTGB3F8ChsjwBxqjUXkLzvTbkWbiWYxRzVWik,16037
@@ -390,7 +390,7 @@ langwatch/prompts/prompt_api_service.py,sha256=tHhwIRjUBSM43_jwDAoGCHJjvvqVeSCrU
390
390
  langwatch/prompts/prompt_facade.py,sha256=47matSK4G2Ce3HWUnO13-k7jlDuZQePGCck4gkbTmXM,5052
391
391
  langwatch/prompts/types.py,sha256=p1bRMvfCCpGGiVwzFtQijVtWl5GWugL_vBOFc4B2348,269
392
392
  langwatch/prompts/decorators/prompt_service_tracing.py,sha256=uSYw0vExo7AuxbcCRnxbYl6UOfOQSC0IsisSqYy153Y,2395
393
- langwatch/prompts/decorators/prompt_tracing.py,sha256=GD1iA7j7_BzqO7iwkRUt4-RQrursuRg1gvtu89SsiY8,3443
393
+ langwatch/prompts/decorators/prompt_tracing.py,sha256=x_PQvJlGbGF1h2HtGNiqaZ8K1qNd1jRf5pTOBTQx-7M,3963
394
394
  langwatch/prompts/types/__init__.py,sha256=uEnjOQC4LkLMWQ0fXfKe573xKOvoMdPgC6uY-yo9B_g,506
395
395
  langwatch/prompts/types/prompt_data.py,sha256=g_EQ94-PGfa4Ptwd3e2rMqoIZiX052MEEZKyF77m9D0,3137
396
396
  langwatch/prompts/types/structures.py,sha256=cB94bn-qhFgHHYXcrmJV6Bk9idk5ZmyfXhFNQAaXw-M,951
@@ -399,7 +399,7 @@ langwatch/telemetry/sampling.py,sha256=XDf6ZoXiwpHaHDYd_dDszSqH8_9-CHFNsGAZWOW1V
399
399
  langwatch/telemetry/span.py,sha256=g-RGWfQk4Q3b2TpipiHqjEV7rwmidaUHp54q51UxQ6s,32801
400
400
  langwatch/telemetry/tracing.py,sha256=oyCAqW-9sFFRYPWy9epZVN0aNvqToRY4_PGxQAtS-dI,27622
401
401
  langwatch/telemetry/types.py,sha256=Q9H7nT3GMK1aluRB7CCX8BR7VFKrQY_vdFdyF4Yc98U,501
402
- langwatch/telemetry/__tests__/test_tracing.py,sha256=mD_SAO-dD5m81EVQ909AwGROnnofxuL3DXCoaUTrS3M,1710
402
+ langwatch/telemetry/__tests__/test_tracing.py,sha256=Px2vcpbRWBgwwaXzw3MgRfkcL-If2LmPAwaFN1sLyvY,3350
403
403
  langwatch/utils/__init__.py,sha256=3rqQTgzEtmICJW_KSPuLa5q8p5udxt5SRi28Z2vZB10,138
404
404
  langwatch/utils/capture.py,sha256=uVKPqHCm-o8CpabsUfhqbNFr5sgUHzcKnBadvL2oIwI,1172
405
405
  langwatch/utils/exceptions.py,sha256=J2_0EZ_GMRTJvCQ-ULX4LOG63r1R-0TCbKg9sskgl5A,498
@@ -407,6 +407,6 @@ langwatch/utils/initialization.py,sha256=1KoZmkHOvGEVF0j-4t4xRQdA_2C_SPiF7qFXqEG
407
407
  langwatch/utils/module.py,sha256=KLBNOK3mA9gCSifCcQX_lOtU48BJQDWvFKtF6NMvwVA,688
408
408
  langwatch/utils/transformation.py,sha256=76MGXyrYTxM0Yri36NJqLK-XxL4BBYdmKWAXXlw3D4Q,7690
409
409
  langwatch/utils/utils.py,sha256=ZCOSie4o9LdJ7odshNfCNjmgwgQ27ojc5ENqt1rXuSs,596
410
- langwatch-0.4.0.dist-info/METADATA,sha256=zLOJ0SExvVcq7aQHUvBxMIID5nySprQULX86Lh54ohc,13152
411
- langwatch-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
412
- langwatch-0.4.0.dist-info/RECORD,,
410
+ langwatch-0.5.0.dist-info/METADATA,sha256=ugdfXP6p37gyVnRFgfZczEpUMb-F3k17S0ovXlpGr1s,13152
411
+ langwatch-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
412
+ langwatch-0.5.0.dist-info/RECORD,,