lmnr 0.6.20__tar.gz → 0.6.21__tar.gz

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.
Files changed (96) hide show
  1. {lmnr-0.6.20 → lmnr-0.6.21}/PKG-INFO +8 -7
  2. {lmnr-0.6.20 → lmnr-0.6.21}/pyproject.toml +15 -6
  3. lmnr-0.6.21/src/lmnr/opentelemetry_lib/decorators/__init__.py +281 -0
  4. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/__init__.py +674 -0
  5. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/config.py +13 -0
  6. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_emitter.py +211 -0
  7. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/span_utils.py +256 -0
  8. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/streaming.py +295 -0
  9. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/utils.py +179 -0
  10. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/version.py +1 -0
  11. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/__init__.py +485 -0
  12. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/config.py +8 -0
  13. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_emitter.py +143 -0
  14. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_models.py +41 -0
  15. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/span_utils.py +229 -0
  16. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/utils.py +92 -0
  17. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/version.py +1 -0
  18. lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_models.py +41 -0
  19. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py +3 -3
  20. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/tracing/__init__.py +1 -1
  21. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +12 -7
  22. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/tracing/processor.py +1 -1
  23. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/utils/package_check.py +9 -0
  24. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/browser/browser_use_otel.py +4 -2
  25. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/browser/patchright_otel.py +0 -26
  26. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/browser/playwright_otel.py +51 -78
  27. lmnr-0.6.21/src/lmnr/sdk/browser/pw_utils.py +583 -0
  28. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/decorators.py +39 -4
  29. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/evaluations.py +23 -9
  30. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/laminar.py +75 -48
  31. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/version.py +1 -1
  32. lmnr-0.6.20/src/lmnr/opentelemetry_lib/decorators/__init__.py +0 -231
  33. lmnr-0.6.20/src/lmnr/sdk/browser/pw_utils.py +0 -338
  34. {lmnr-0.6.20 → lmnr-0.6.21}/README.md +0 -0
  35. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/__init__.py +0 -0
  36. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/cli.py +0 -0
  37. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/.flake8 +0 -0
  38. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/__init__.py +0 -0
  39. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/litellm/__init__.py +0 -0
  40. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/litellm/utils.py +0 -0
  41. {lmnr-0.6.20/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared → lmnr-0.6.21/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic}/event_models.py +0 -0
  42. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +0 -0
  43. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/config.py +0 -0
  44. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/schema_utils.py +0 -0
  45. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py +0 -0
  46. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py +0 -0
  47. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/utils.py +0 -0
  48. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/__init__.py +0 -0
  49. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/__init__.py +0 -0
  50. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +0 -0
  51. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +0 -0
  52. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/config.py +0 -0
  53. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +0 -0
  54. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_emitter.py +0 -0
  55. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +0 -0
  56. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v0/__init__.py +0 -0
  57. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/__init__.py +0 -0
  58. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +0 -0
  59. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +0 -0
  60. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +0 -0
  61. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/version.py +0 -0
  62. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/opentelemetry/__init__.py +0 -0
  63. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/skyvern/__init__.py +0 -0
  64. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/tracing/attributes.py +0 -0
  65. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/tracing/context_properties.py +0 -0
  66. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/tracing/exporter.py +0 -0
  67. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/tracing/instruments.py +0 -0
  68. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/tracing/tracer.py +0 -0
  69. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/utils/__init__.py +0 -0
  70. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/opentelemetry_lib/utils/json_encoder.py +0 -0
  71. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/py.typed +0 -0
  72. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/__init__.py +0 -0
  73. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/browser/__init__.py +0 -0
  74. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/browser/rrweb/rrweb.umd.min.cjs +0 -0
  75. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/browser/utils.py +0 -0
  76. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/asynchronous/async_client.py +0 -0
  77. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/asynchronous/resources/__init__.py +0 -0
  78. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/asynchronous/resources/agent.py +0 -0
  79. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/asynchronous/resources/base.py +0 -0
  80. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/asynchronous/resources/browser_events.py +0 -0
  81. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/asynchronous/resources/evals.py +0 -0
  82. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/asynchronous/resources/evaluators.py +0 -0
  83. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/asynchronous/resources/tags.py +0 -0
  84. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/synchronous/resources/__init__.py +0 -0
  85. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/synchronous/resources/agent.py +0 -0
  86. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/synchronous/resources/base.py +0 -0
  87. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/synchronous/resources/browser_events.py +0 -0
  88. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/synchronous/resources/evals.py +0 -0
  89. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/synchronous/resources/evaluators.py +0 -0
  90. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/synchronous/resources/tags.py +0 -0
  91. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/client/synchronous/sync_client.py +0 -0
  92. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/datasets.py +0 -0
  93. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/eval_control.py +0 -0
  94. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/log.py +0 -0
  95. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/types.py +0 -0
  96. {lmnr-0.6.20 → lmnr-0.6.21}/src/lmnr/sdk/utils.py +0 -0
@@ -1,10 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lmnr
3
- Version: 0.6.20
3
+ Version: 0.6.21
4
4
  Summary: Python SDK for Laminar
5
5
  Author: lmnr.ai
6
6
  Author-email: lmnr.ai <founders@lmnr.ai>
7
7
  License-Expression: Apache-2.0
8
+ Classifier: License :: OSI Approved :: Apache Software License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
8
14
  Requires-Dist: pydantic>=2.0.3,<3.0.0
9
15
  Requires-Dist: python-dotenv>=1.0
10
16
  Requires-Dist: opentelemetry-api>=1.33.0
@@ -18,15 +24,14 @@ Requires-Dist: tenacity>=8.0
18
24
  Requires-Dist: grpcio>=1
19
25
  Requires-Dist: httpx>=0.25.0
20
26
  Requires-Dist: opentelemetry-instrumentation-threading>=0.54b0
27
+ Requires-Dist: orjson>=3.10.18
21
28
  Requires-Dist: opentelemetry-instrumentation-alephalpha>=0.40.12 ; extra == 'alephalpha'
22
29
  Requires-Dist: opentelemetry-instrumentation-alephalpha>=0.40.12 ; extra == 'all'
23
- Requires-Dist: opentelemetry-instrumentation-anthropic>=0.40.12 ; extra == 'all'
24
30
  Requires-Dist: opentelemetry-instrumentation-bedrock>=0.40.12 ; extra == 'all'
25
31
  Requires-Dist: opentelemetry-instrumentation-chromadb>=0.40.12 ; extra == 'all'
26
32
  Requires-Dist: opentelemetry-instrumentation-cohere>=0.40.12 ; extra == 'all'
27
33
  Requires-Dist: opentelemetry-instrumentation-crewai>=0.40.12 ; extra == 'all'
28
34
  Requires-Dist: opentelemetry-instrumentation-google-generativeai<0.40.10 ; extra == 'all'
29
- Requires-Dist: opentelemetry-instrumentation-groq>=0.40.12 ; extra == 'all'
30
35
  Requires-Dist: opentelemetry-instrumentation-haystack>=0.40.12 ; extra == 'all'
31
36
  Requires-Dist: opentelemetry-instrumentation-lancedb>=0.40.12 ; extra == 'all'
32
37
  Requires-Dist: opentelemetry-instrumentation-langchain>=0.40.12 ; extra == 'all'
@@ -45,13 +50,11 @@ Requires-Dist: opentelemetry-instrumentation-transformers>=0.40.12 ; extra == 'a
45
50
  Requires-Dist: opentelemetry-instrumentation-vertexai>=0.40.12 ; extra == 'all'
46
51
  Requires-Dist: opentelemetry-instrumentation-watsonx>=0.40.12 ; extra == 'all'
47
52
  Requires-Dist: opentelemetry-instrumentation-weaviate>=0.40.12 ; extra == 'all'
48
- Requires-Dist: opentelemetry-instrumentation-anthropic>=0.40.12 ; extra == 'anthropic'
49
53
  Requires-Dist: opentelemetry-instrumentation-bedrock>=0.40.12 ; extra == 'bedrock'
50
54
  Requires-Dist: opentelemetry-instrumentation-chromadb>=0.40.12 ; extra == 'chromadb'
51
55
  Requires-Dist: opentelemetry-instrumentation-cohere>=0.40.12 ; extra == 'cohere'
52
56
  Requires-Dist: opentelemetry-instrumentation-crewai>=0.40.12 ; extra == 'crewai'
53
57
  Requires-Dist: opentelemetry-instrumentation-google-generativeai<0.40.10 ; extra == 'google-generativeai'
54
- Requires-Dist: opentelemetry-instrumentation-groq>=0.40.12 ; extra == 'groq'
55
58
  Requires-Dist: opentelemetry-instrumentation-haystack>=0.40.12 ; extra == 'haystack'
56
59
  Requires-Dist: opentelemetry-instrumentation-lancedb>=0.40.12 ; extra == 'lancedb'
57
60
  Requires-Dist: opentelemetry-instrumentation-langchain>=0.40.12 ; extra == 'langchain'
@@ -73,13 +76,11 @@ Requires-Dist: opentelemetry-instrumentation-weaviate>=0.40.12 ; extra == 'weavi
73
76
  Requires-Python: >=3.10, <4
74
77
  Provides-Extra: alephalpha
75
78
  Provides-Extra: all
76
- Provides-Extra: anthropic
77
79
  Provides-Extra: bedrock
78
80
  Provides-Extra: chromadb
79
81
  Provides-Extra: cohere
80
82
  Provides-Extra: crewai
81
83
  Provides-Extra: google-generativeai
82
- Provides-Extra: groq
83
84
  Provides-Extra: haystack
84
85
  Provides-Extra: lancedb
85
86
  Provides-Extra: langchain
@@ -6,7 +6,7 @@
6
6
 
7
7
  [project]
8
8
  name = "lmnr"
9
- version = "0.6.20"
9
+ version = "0.6.21"
10
10
  description = "Python SDK for Laminar"
11
11
  authors = [
12
12
  { name = "lmnr.ai", email = "founders@lmnr.ai" }
@@ -37,6 +37,17 @@ dependencies = [
37
37
  "grpcio>=1",
38
38
  "httpx>=0.25.0",
39
39
  "opentelemetry-instrumentation-threading>=0.54b0",
40
+ "orjson>=3.10.18",
41
+ ]
42
+ # poetry would auto-generate these based on requires-python, but other build
43
+ # systems don't, so we need to specify them manually.
44
+ classifiers = [
45
+ "License :: OSI Approved :: Apache Software License",
46
+ "Programming Language :: Python :: 3",
47
+ "Programming Language :: Python :: 3.10",
48
+ "Programming Language :: Python :: 3.11",
49
+ "Programming Language :: Python :: 3.12",
50
+ "Programming Language :: Python :: 3.13",
40
51
  ]
41
52
 
42
53
  [project.scripts]
@@ -51,7 +62,6 @@ lmnr = "lmnr.cli:cli"
51
62
  # `poetry add 'lmnr[anthropic,groq]'`
52
63
 
53
64
  alephalpha=["opentelemetry-instrumentation-alephalpha>=0.40.12"]
54
- anthropic=["opentelemetry-instrumentation-anthropic>=0.40.12"]
55
65
  bedrock=["opentelemetry-instrumentation-bedrock>=0.40.12"]
56
66
  chromadb=["opentelemetry-instrumentation-chromadb>=0.40.12"]
57
67
  cohere=["opentelemetry-instrumentation-cohere>=0.40.12"]
@@ -63,7 +73,6 @@ crewai=["opentelemetry-instrumentation-crewai>=0.40.12"]
63
73
  # wrapping google-genai.
64
74
  # https://github.com/traceloop/openllmetry/pull/3014
65
75
  google-generativeai=["opentelemetry-instrumentation-google-generativeai<0.40.10"]
66
- groq=["opentelemetry-instrumentation-groq>=0.40.12"]
67
76
  haystack=["opentelemetry-instrumentation-haystack>=0.40.12"]
68
77
  lancedb=["opentelemetry-instrumentation-lancedb>=0.40.12"]
69
78
  langchain=["opentelemetry-instrumentation-langchain>=0.40.12"]
@@ -88,14 +97,12 @@ weaviate=["opentelemetry-instrumentation-weaviate>=0.40.12"]
88
97
  # like `uv add lmnr --all-extras`
89
98
  all = [
90
99
  "opentelemetry-instrumentation-alephalpha>=0.40.12",
91
- "opentelemetry-instrumentation-anthropic>=0.40.12",
92
100
  "opentelemetry-instrumentation-bedrock>=0.40.12",
93
101
  "opentelemetry-instrumentation-chromadb>=0.40.12",
94
102
  "opentelemetry-instrumentation-cohere>=0.40.12",
95
103
  "opentelemetry-instrumentation-crewai>=0.40.12",
96
104
  # See comment above on the google-generativeai extra.
97
105
  "opentelemetry-instrumentation-google-generativeai<0.40.10",
98
- "opentelemetry-instrumentation-groq>=0.40.12",
99
106
  "opentelemetry-instrumentation-haystack>=0.40.12",
100
107
  "opentelemetry-instrumentation-lancedb>=0.40.12",
101
108
  "opentelemetry-instrumentation-langchain>=0.40.12",
@@ -133,10 +140,12 @@ dev = [
133
140
  "langchain-core>=0.3.64",
134
141
  "langchain>=0.3.25",
135
142
  "litellm>=1.72.6",
143
+ "groq>=0.30.0",
144
+ "anthropic>=0.57.1",
136
145
  ]
137
146
 
138
147
  [build-system]
139
- requires = ["uv_build>=0.7.19,<0.8"]
148
+ requires = ["uv_build>=0.7.21,<0.8"]
140
149
  build-backend = "uv_build"
141
150
 
142
151
  [tool.uv.workspace]
@@ -0,0 +1,281 @@
1
+ from functools import wraps
2
+ import logging
3
+ import pydantic
4
+ import orjson
5
+ import types
6
+ from typing import Any, AsyncGenerator, Callable, Generator, Literal
7
+
8
+ from opentelemetry import trace
9
+ from opentelemetry import context as context_api
10
+ from opentelemetry.trace import Span
11
+
12
+ from lmnr.sdk.utils import get_input_from_func_args, is_method
13
+ from lmnr.opentelemetry_lib import MAX_MANUAL_SPAN_PAYLOAD_SIZE
14
+ from lmnr.opentelemetry_lib.tracing.tracer import get_tracer
15
+ from lmnr.opentelemetry_lib.tracing.attributes import (
16
+ ASSOCIATION_PROPERTIES,
17
+ SPAN_INPUT,
18
+ SPAN_OUTPUT,
19
+ SPAN_TYPE,
20
+ )
21
+ from lmnr.opentelemetry_lib.tracing import TracerWrapper
22
+ from lmnr.sdk.log import get_default_logger
23
+
24
+ logger = get_default_logger(__name__)
25
+
26
+ DEFAULT_PLACEHOLDER = {}
27
+
28
+
29
+ def default_json(o):
30
+ if isinstance(o, pydantic.BaseModel):
31
+ return o.model_dump()
32
+
33
+ # Handle various sequence types, but not strings or bytes
34
+ if isinstance(o, (list, tuple, set, frozenset)):
35
+ return list(o)
36
+
37
+ try:
38
+ return str(o)
39
+ except Exception:
40
+ pass
41
+ return DEFAULT_PLACEHOLDER
42
+
43
+
44
+ def json_dumps(data: dict) -> str:
45
+ try:
46
+ return orjson.dumps(
47
+ data,
48
+ default=default_json,
49
+ option=orjson.OPT_SERIALIZE_DATACLASS
50
+ | orjson.OPT_SERIALIZE_UUID
51
+ | orjson.OPT_UTC_Z
52
+ | orjson.OPT_NON_STR_KEYS,
53
+ ).decode("utf-8")
54
+ except Exception:
55
+ # Log the exception and return a placeholder if serialization completely fails
56
+ logging.warning("Failed to serialize data to JSON, type: %s", type(data))
57
+ return "{}" # Return an empty JSON object as a fallback
58
+
59
+
60
+ def _setup_span(
61
+ span_name: str, span_type: str, association_properties: dict[str, Any] | None
62
+ ):
63
+ """Set up a span with the given name, type, and association properties."""
64
+ with get_tracer() as tracer:
65
+ span = tracer.start_span(span_name, attributes={SPAN_TYPE: span_type})
66
+
67
+ if association_properties is not None:
68
+ for key, value in association_properties.items():
69
+ span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{key}", value)
70
+
71
+ return span
72
+
73
+
74
+ def _process_input(
75
+ span: Span,
76
+ fn: Callable,
77
+ args: tuple,
78
+ kwargs: dict,
79
+ ignore_input: bool,
80
+ ignore_inputs: list[str] | None,
81
+ input_formatter: Callable[..., str] | None,
82
+ ):
83
+ """Process and set input attributes on the span."""
84
+ if ignore_input:
85
+ return
86
+
87
+ try:
88
+ if input_formatter is not None:
89
+ inp = input_formatter(*args, **kwargs)
90
+ if not isinstance(inp, str):
91
+ inp = json_dumps(inp)
92
+ else:
93
+ inp = json_dumps(
94
+ get_input_from_func_args(
95
+ fn,
96
+ is_method=is_method(fn),
97
+ func_args=args,
98
+ func_kwargs=kwargs,
99
+ ignore_inputs=ignore_inputs,
100
+ )
101
+ )
102
+
103
+ if len(inp) > MAX_MANUAL_SPAN_PAYLOAD_SIZE:
104
+ span.set_attribute(SPAN_INPUT, "Laminar: input too large to record")
105
+ else:
106
+ span.set_attribute(SPAN_INPUT, inp)
107
+ except Exception:
108
+ msg = "Failed to process input, ignoring"
109
+ if input_formatter is not None:
110
+ # Only warn the user if they provided an input formatter
111
+ # because it's their responsibility to make sure it works.
112
+ logger.warning(msg, exc_info=True)
113
+ else:
114
+ logger.debug(msg, exc_info=True)
115
+ pass
116
+
117
+
118
+ def _process_output(
119
+ span: Span,
120
+ result: Any,
121
+ ignore_output: bool,
122
+ output_formatter: Callable[..., str] | None,
123
+ ):
124
+ """Process and set output attributes on the span."""
125
+ if ignore_output:
126
+ return
127
+
128
+ try:
129
+ if output_formatter is not None:
130
+ output = output_formatter(result)
131
+ if not isinstance(output, str):
132
+ output = json_dumps(output)
133
+ else:
134
+ output = json_dumps(result)
135
+
136
+ if len(output) > MAX_MANUAL_SPAN_PAYLOAD_SIZE:
137
+ span.set_attribute(SPAN_OUTPUT, "Laminar: output too large to record")
138
+ else:
139
+ span.set_attribute(SPAN_OUTPUT, output)
140
+ except Exception:
141
+ msg = "Failed to process output, ignoring"
142
+ if output_formatter is not None:
143
+ # Only warn the user if they provided an output formatter
144
+ # because it's their responsibility to make sure it works.
145
+ logger.warning(msg, exc_info=True)
146
+ else:
147
+ logger.debug(msg, exc_info=True)
148
+ pass
149
+
150
+
151
+ def _cleanup_span(span: Span, ctx_token):
152
+ """Clean up span and context."""
153
+ span.end()
154
+ context_api.detach(ctx_token)
155
+
156
+
157
+ def observe_base(
158
+ name: str | None = None,
159
+ ignore_input: bool = False,
160
+ ignore_inputs: list[str] | None = None,
161
+ ignore_output: bool = False,
162
+ span_type: Literal["DEFAULT", "LLM", "TOOL"] = "DEFAULT",
163
+ association_properties: dict[str, Any] | None = None,
164
+ input_formatter: Callable[..., str] | None = None,
165
+ output_formatter: Callable[..., str] | None = None,
166
+ ):
167
+ def decorate(fn):
168
+ @wraps(fn)
169
+ def wrap(*args, **kwargs):
170
+ if not TracerWrapper.verify_initialized():
171
+ return fn(*args, **kwargs)
172
+
173
+ span_name = name or fn.__name__
174
+
175
+ span = _setup_span(span_name, span_type, association_properties)
176
+ ctx = trace.set_span_in_context(span, context_api.get_current())
177
+ ctx_token = context_api.attach(ctx)
178
+
179
+ _process_input(
180
+ span, fn, args, kwargs, ignore_input, ignore_inputs, input_formatter
181
+ )
182
+
183
+ try:
184
+ res = fn(*args, **kwargs)
185
+ except Exception as e:
186
+ _process_exception(span, e)
187
+ _cleanup_span(span, ctx_token)
188
+ raise e
189
+
190
+ # span will be ended in the generator
191
+ if isinstance(res, types.GeneratorType):
192
+ return _handle_generator(span, ctx_token, res)
193
+ if isinstance(res, types.AsyncGeneratorType):
194
+ # async def foo() -> AsyncGenerator[int, None]:
195
+ # is not considered async in a classical sense in Python,
196
+ # so we handle this inside the sync wrapper.
197
+ # In particular, CO_COROUTINE is different from CO_ASYNC_GENERATOR.
198
+ # Flags are listed from LSB here:
199
+ # https://docs.python.org/3/library/inspect.html#inspect-module-co-flags
200
+ # See also: https://groups.google.com/g/python-tulip/c/6rWweGXLutU?pli=1
201
+ return _ahandle_generator(span, ctx_token, res)
202
+
203
+ _process_output(span, res, ignore_output, output_formatter)
204
+ _cleanup_span(span, ctx_token)
205
+ return res
206
+
207
+ return wrap
208
+
209
+ return decorate
210
+
211
+
212
+ # Async Decorators
213
+ def async_observe_base(
214
+ name: str | None = None,
215
+ ignore_input: bool = False,
216
+ ignore_inputs: list[str] | None = None,
217
+ ignore_output: bool = False,
218
+ span_type: Literal["DEFAULT", "LLM", "TOOL"] = "DEFAULT",
219
+ association_properties: dict[str, Any] | None = None,
220
+ input_formatter: Callable[..., str] | None = None,
221
+ output_formatter: Callable[..., str] | None = None,
222
+ ):
223
+ def decorate(fn):
224
+ @wraps(fn)
225
+ async def wrap(*args, **kwargs):
226
+ if not TracerWrapper.verify_initialized():
227
+ return await fn(*args, **kwargs)
228
+
229
+ span_name = name or fn.__name__
230
+
231
+ span = _setup_span(span_name, span_type, association_properties)
232
+ ctx = trace.set_span_in_context(span, context_api.get_current())
233
+ ctx_token = context_api.attach(ctx)
234
+
235
+ _process_input(
236
+ span, fn, args, kwargs, ignore_input, ignore_inputs, input_formatter
237
+ )
238
+
239
+ try:
240
+ res = await fn(*args, **kwargs)
241
+ except Exception as e:
242
+ _process_exception(span, e)
243
+ _cleanup_span(span, ctx_token)
244
+ raise e
245
+
246
+ # span will be ended in the generator
247
+ if isinstance(res, types.AsyncGeneratorType):
248
+ # probably unreachable, read the comment in the similar
249
+ # part of the sync wrapper.
250
+ return await _ahandle_generator(span, ctx_token, res)
251
+
252
+ _process_output(span, res, ignore_output, output_formatter)
253
+ _cleanup_span(span, ctx_token)
254
+ return res
255
+
256
+ return wrap
257
+
258
+ return decorate
259
+
260
+
261
+ def _handle_generator(span: Span, ctx_token, res: Generator[Any, Any, Any]):
262
+ yield from res
263
+
264
+ span.end()
265
+ if ctx_token is not None:
266
+ context_api.detach(ctx_token)
267
+
268
+
269
+ async def _ahandle_generator(span: Span, ctx_token, res: AsyncGenerator[Any, Any]):
270
+ # async with contextlib.aclosing(res) as closing_gen:
271
+ async for part in res:
272
+ yield part
273
+
274
+ span.end()
275
+ if ctx_token is not None:
276
+ context_api.detach(ctx_token)
277
+
278
+
279
+ def _process_exception(span: Span, e: Exception):
280
+ # Note that this `escaped` is sent as a StringValue("True"), not a boolean.
281
+ span.record_exception(e, escaped=True)