lmnr 0.4.17b0__py2.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.
Files changed (50) hide show
  1. lmnr/__init__.py +5 -0
  2. lmnr/cli.py +39 -0
  3. lmnr/sdk/__init__.py +0 -0
  4. lmnr/sdk/decorators.py +66 -0
  5. lmnr/sdk/evaluations.py +354 -0
  6. lmnr/sdk/laminar.py +403 -0
  7. lmnr/sdk/log.py +39 -0
  8. lmnr/sdk/types.py +155 -0
  9. lmnr/sdk/utils.py +99 -0
  10. lmnr/traceloop_sdk/.flake8 +12 -0
  11. lmnr/traceloop_sdk/.python-version +1 -0
  12. lmnr/traceloop_sdk/__init__.py +89 -0
  13. lmnr/traceloop_sdk/config/__init__.py +9 -0
  14. lmnr/traceloop_sdk/decorators/__init__.py +0 -0
  15. lmnr/traceloop_sdk/decorators/base.py +178 -0
  16. lmnr/traceloop_sdk/instruments.py +34 -0
  17. lmnr/traceloop_sdk/tests/__init__.py +1 -0
  18. lmnr/traceloop_sdk/tests/cassettes/test_association_properties/test_langchain_and_external_association_properties.yaml +101 -0
  19. lmnr/traceloop_sdk/tests/cassettes/test_association_properties/test_langchain_association_properties.yaml +99 -0
  20. lmnr/traceloop_sdk/tests/cassettes/test_manual/test_manual_report.yaml +98 -0
  21. lmnr/traceloop_sdk/tests/cassettes/test_manual/test_resource_attributes.yaml +98 -0
  22. lmnr/traceloop_sdk/tests/cassettes/test_privacy_no_prompts/test_simple_workflow.yaml +199 -0
  23. lmnr/traceloop_sdk/tests/cassettes/test_prompt_management/test_prompt_management.yaml +202 -0
  24. lmnr/traceloop_sdk/tests/cassettes/test_sdk_initialization/test_resource_attributes.yaml +199 -0
  25. lmnr/traceloop_sdk/tests/cassettes/test_tasks/test_task_io_serialization_with_langchain.yaml +96 -0
  26. lmnr/traceloop_sdk/tests/cassettes/test_workflows/test_simple_aworkflow.yaml +98 -0
  27. lmnr/traceloop_sdk/tests/cassettes/test_workflows/test_simple_workflow.yaml +199 -0
  28. lmnr/traceloop_sdk/tests/cassettes/test_workflows/test_streaming_workflow.yaml +167 -0
  29. lmnr/traceloop_sdk/tests/conftest.py +111 -0
  30. lmnr/traceloop_sdk/tests/test_association_properties.py +229 -0
  31. lmnr/traceloop_sdk/tests/test_manual.py +48 -0
  32. lmnr/traceloop_sdk/tests/test_nested_tasks.py +47 -0
  33. lmnr/traceloop_sdk/tests/test_privacy_no_prompts.py +50 -0
  34. lmnr/traceloop_sdk/tests/test_sdk_initialization.py +57 -0
  35. lmnr/traceloop_sdk/tests/test_tasks.py +32 -0
  36. lmnr/traceloop_sdk/tests/test_workflows.py +262 -0
  37. lmnr/traceloop_sdk/tracing/__init__.py +1 -0
  38. lmnr/traceloop_sdk/tracing/attributes.py +9 -0
  39. lmnr/traceloop_sdk/tracing/content_allow_list.py +24 -0
  40. lmnr/traceloop_sdk/tracing/context_manager.py +13 -0
  41. lmnr/traceloop_sdk/tracing/tracing.py +913 -0
  42. lmnr/traceloop_sdk/utils/__init__.py +26 -0
  43. lmnr/traceloop_sdk/utils/in_memory_span_exporter.py +61 -0
  44. lmnr/traceloop_sdk/utils/json_encoder.py +20 -0
  45. lmnr/traceloop_sdk/utils/package_check.py +8 -0
  46. lmnr/traceloop_sdk/version.py +1 -0
  47. lmnr-0.4.17b0.dist-info/LICENSE +75 -0
  48. lmnr-0.4.17b0.dist-info/METADATA +250 -0
  49. lmnr-0.4.17b0.dist-info/RECORD +50 -0
  50. lmnr-0.4.17b0.dist-info/WHEEL +4 -0
@@ -0,0 +1,262 @@
1
+ # import json
2
+
3
+ # import pytest
4
+ # from openai import OpenAI, AsyncOpenAI
5
+ # from opentelemetry.semconv_ai import SpanAttributes
6
+ # from lmnr.traceloop_sdk import Traceloop
7
+ # from lmnr.traceloop_sdk.decorators import workflow, task, aworkflow, atask
8
+
9
+
10
+ # @pytest.fixture
11
+ # def openai_client():
12
+ # return OpenAI()
13
+
14
+
15
+ # @pytest.fixture
16
+ # def async_openai_client():
17
+ # return AsyncOpenAI()
18
+
19
+
20
+ # # commented out because of the version parameter in task decorator
21
+ # # @pytest.mark.vcr
22
+ # # def test_simple_workflow(exporter, openai_client):
23
+ # # @task(name="something_creator", version=2)
24
+ # # def create_something(what: str, subject: str):
25
+ # # Traceloop.set_prompt(
26
+ # # "Tell me a {what} about {subject}", {"what": what, "subject": subject}, 5
27
+ # # )
28
+ # # completion = openai_client.chat.completions.create(
29
+ # # model="gpt-3.5-turbo",
30
+ # # messages=[{"role": "user", "content": f"Tell me a {what} about {subject}"}],
31
+ # # )
32
+ # # return completion.choices[0].message.content
33
+
34
+ # # @workflow(name="pirate_joke_generator", version=1)
35
+ # # def joke_workflow():
36
+ # # return create_something("joke", subject="OpenTelemetry")
37
+
38
+ # # joke = joke_workflow()
39
+
40
+ # # spans = exporter.get_finished_spans()
41
+ # # assert [span.name for span in spans] == [
42
+ # # "openai.chat",
43
+ # # "something_creator.task",
44
+ # # "pirate_joke_generator.workflow",
45
+ # # ]
46
+ # # open_ai_span = next(span for span in spans if span.name == "openai.chat")
47
+ # # assert (
48
+ # # open_ai_span.attributes[f"{SpanAttributes.LLM_PROMPTS}.0.content"]
49
+ # # == "Tell me a joke about OpenTelemetry"
50
+ # # )
51
+ # # assert open_ai_span.attributes.get(f"{SpanAttributes.LLM_COMPLETIONS}.0.content")
52
+ # # assert (
53
+ # # open_ai_span.attributes.get("traceloop.prompt.template")
54
+ # # == "Tell me a {what} about {subject}"
55
+ # # )
56
+ # # assert (
57
+ # # open_ai_span.attributes.get("traceloop.prompt.template_variables.what")
58
+ # # == "joke"
59
+ # # )
60
+ # # assert (
61
+ # # open_ai_span.attributes.get("traceloop.prompt.template_variables.subject")
62
+ # # == "OpenTelemetry"
63
+ # # )
64
+ # # assert open_ai_span.attributes.get("traceloop.prompt.version") == 5
65
+
66
+ # # workflow_span = next(
67
+ # # span for span in spans if span.name == "pirate_joke_generator.workflow"
68
+ # # )
69
+ # # task_span = next(span for span in spans if span.name == "something_creator.task")
70
+ # # assert json.loads(task_span.attributes[SpanAttributes.TRACELOOP_ENTITY_INPUT]) == {
71
+ # # "args": ["joke"],
72
+ # # "kwargs": {"subject": "OpenTelemetry"},
73
+ # # }
74
+
75
+ # # assert (
76
+ # # json.loads(task_span.attributes.get(SpanAttributes.TRACELOOP_ENTITY_OUTPUT))
77
+ # # == joke
78
+ # # )
79
+ # # assert task_span.parent.span_id == workflow_span.context.span_id
80
+ # # assert (
81
+ # # workflow_span.attributes[SpanAttributes.TRACELOOP_ENTITY_NAME]
82
+ # # == "pirate_joke_generator"
83
+ # # )
84
+ # # assert workflow_span.attributes[SpanAttributes.TRACELOOP_ENTITY_VERSION] == 1
85
+ # # assert task_span.attributes[SpanAttributes.TRACELOOP_ENTITY_VERSION] == 2
86
+
87
+
88
+ # @pytest.mark.vcr
89
+ # @pytest.mark.asyncio
90
+ # async def test_simple_aworkflow(exporter, async_openai_client):
91
+ # @atask(name="something_creator", version=2)
92
+ # async def create_something(what: str, subject: str):
93
+ # Traceloop.set_prompt(
94
+ # "Tell me a {what} about {subject}", {"what": what, "subject": subject}, 5
95
+ # )
96
+ # completion = await async_openai_client.chat.completions.create(
97
+ # model="gpt-3.5-turbo",
98
+ # messages=[{"role": "user", "content": f"Tell me a {what} about {subject}"}],
99
+ # )
100
+ # return completion.choices[0].message.content
101
+
102
+ # @aworkflow(name="pirate_joke_generator", version=1)
103
+ # async def joke_workflow():
104
+ # return await create_something("joke", subject="OpenTelemetry")
105
+
106
+ # joke = await joke_workflow()
107
+
108
+ # spans = exporter.get_finished_spans()
109
+ # assert [span.name for span in spans] == [
110
+ # "openai.chat",
111
+ # "something_creator.task",
112
+ # "pirate_joke_generator.workflow",
113
+ # ]
114
+ # open_ai_span = next(span for span in spans if span.name == "openai.chat")
115
+ # assert (
116
+ # open_ai_span.attributes[f"{SpanAttributes.LLM_PROMPTS}.0.content"]
117
+ # == "Tell me a joke about OpenTelemetry"
118
+ # )
119
+ # assert open_ai_span.attributes.get(f"{SpanAttributes.LLM_COMPLETIONS}.0.content")
120
+ # assert (
121
+ # open_ai_span.attributes.get("traceloop.prompt.template")
122
+ # == "Tell me a {what} about {subject}"
123
+ # )
124
+ # assert (
125
+ # open_ai_span.attributes.get("traceloop.prompt.template_variables.what")
126
+ # == "joke"
127
+ # )
128
+ # assert (
129
+ # open_ai_span.attributes.get("traceloop.prompt.template_variables.subject")
130
+ # == "OpenTelemetry"
131
+ # )
132
+ # assert open_ai_span.attributes.get("traceloop.prompt.version") == 5
133
+
134
+ # workflow_span = next(
135
+ # span for span in spans if span.name == "pirate_joke_generator.workflow"
136
+ # )
137
+ # task_span = next(span for span in spans if span.name == "something_creator.task")
138
+ # assert json.loads(task_span.attributes[SpanAttributes.TRACELOOP_ENTITY_INPUT]) == {
139
+ # "args": ["joke"],
140
+ # "kwargs": {"subject": "OpenTelemetry"},
141
+ # }
142
+
143
+ # assert (
144
+ # json.loads(task_span.attributes.get(SpanAttributes.TRACELOOP_ENTITY_OUTPUT))
145
+ # == joke
146
+ # )
147
+ # assert task_span.parent.span_id == workflow_span.context.span_id
148
+ # assert (
149
+ # workflow_span.attributes[SpanAttributes.TRACELOOP_ENTITY_NAME]
150
+ # == "pirate_joke_generator"
151
+ # )
152
+ # assert workflow_span.attributes[SpanAttributes.TRACELOOP_ENTITY_VERSION] == 1
153
+ # assert task_span.attributes[SpanAttributes.TRACELOOP_ENTITY_VERSION] == 2
154
+
155
+
156
+ # @pytest.mark.vcr
157
+ # def test_streaming_workflow(exporter, openai_client):
158
+
159
+ # @task(name="pirate_joke_generator")
160
+ # def joke_task():
161
+ # response_stream = openai_client.chat.completions.create(
162
+ # model="gpt-3.5-turbo",
163
+ # messages=[
164
+ # {"role": "user", "content": "Tell me a joke about OpenTelemetry"}
165
+ # ],
166
+ # stream=True,
167
+ # )
168
+ # for chunk in response_stream:
169
+ # yield chunk
170
+
171
+ # @task(name="joke_runner")
172
+ # def joke_runner():
173
+ # res = joke_task()
174
+ # return res
175
+
176
+ # @workflow(name="joke_manager")
177
+ # def joke_workflow():
178
+ # res = joke_runner()
179
+ # for chunk in res:
180
+ # pass
181
+
182
+ # joke_workflow()
183
+
184
+ # spans = exporter.get_finished_spans()
185
+ # assert set([span.name for span in spans]) == set(
186
+ # [
187
+ # "openai.chat",
188
+ # "pirate_joke_generator.task",
189
+ # "joke_runner.task",
190
+ # "joke_manager.workflow",
191
+ # ]
192
+ # )
193
+ # generator_span = next(
194
+ # span for span in spans if span.name == "pirate_joke_generator.task"
195
+ # )
196
+ # runner_span = next(span for span in spans if span.name == "joke_runner.task")
197
+ # manager_span = next(span for span in spans if span.name == "joke_manager.workflow")
198
+ # openai_span = next(span for span in spans if span.name == "openai.chat")
199
+
200
+ # assert openai_span.parent.span_id == generator_span.context.span_id
201
+ # assert generator_span.parent.span_id == runner_span.context.span_id
202
+ # assert runner_span.parent.span_id == manager_span.context.span_id
203
+ # assert openai_span.end_time <= manager_span.end_time
204
+
205
+
206
+ # def test_unrelated_entities(exporter):
207
+ # @workflow(name="workflow_1")
208
+ # def workflow_1():
209
+ # return
210
+
211
+ # @task(name="task_1")
212
+ # def task_1():
213
+ # return
214
+
215
+ # workflow_1()
216
+ # task_1()
217
+
218
+ # spans = exporter.get_finished_spans()
219
+ # assert [span.name for span in spans] == ["workflow_1.workflow", "task_1.task"]
220
+
221
+ # workflow_1_span = spans[0]
222
+ # task_1_span = spans[1]
223
+
224
+ # assert (
225
+ # workflow_1_span.attributes[SpanAttributes.TRACELOOP_ENTITY_NAME] == "workflow_1"
226
+ # )
227
+ # assert workflow_1_span.attributes[SpanAttributes.TRACELOOP_SPAN_KIND] == "workflow"
228
+
229
+ # assert task_1_span.attributes[SpanAttributes.TRACELOOP_ENTITY_NAME] == "task_1"
230
+ # assert task_1_span.attributes[SpanAttributes.TRACELOOP_SPAN_KIND] == "task"
231
+ # assert task_1_span.parent is None
232
+
233
+
234
+ # def test_unserializable_workflow(exporter):
235
+ # @task(name="unserializable_task")
236
+ # def unserializable_task(obj: object):
237
+ # return object()
238
+
239
+ # @workflow(name="unserializable_workflow")
240
+ # def unserializable_workflow(obj: object):
241
+ # return unserializable_task(obj)
242
+
243
+ # unserializable_task(object())
244
+
245
+ # spans = exporter.get_finished_spans()
246
+ # assert [span.name for span in spans] == ["unserializable_task.task"]
247
+
248
+
249
+ # @pytest.mark.asyncio
250
+ # async def test_unserializable_async_workflow(exporter):
251
+ # @atask(name="unserializable_task")
252
+ # async def unserializable_task(obj: object):
253
+ # return object()
254
+
255
+ # @aworkflow(name="unserializable_workflow")
256
+ # async def unserializable_workflow(obj: object):
257
+ # return await unserializable_task(obj)
258
+
259
+ # await unserializable_task(object())
260
+
261
+ # spans = exporter.get_finished_spans()
262
+ # assert [span.name for span in spans] == ["unserializable_task.task"]
@@ -0,0 +1 @@
1
+ from lmnr.traceloop_sdk.tracing.context_manager import get_tracer
@@ -0,0 +1,9 @@
1
+ SPAN_INPUT = "lmnr.span.input"
2
+ SPAN_OUTPUT = "lmnr.span.output"
3
+ SPAN_TYPE = "lmnr.span.type"
4
+ SPAN_PATH = "lmnr.span.path"
5
+
6
+ ASSOCIATION_PROPERTIES = "lmnr.association.properties"
7
+ SESSION_ID = "session_id"
8
+ USER_ID = "user_id"
9
+ TRACE_TYPE = "trace_type"
@@ -0,0 +1,24 @@
1
+ # Manages list of associated properties for which content tracing
2
+ # (prompts, vector embeddings, etc.) is allowed.
3
+ class ContentAllowList:
4
+ def __new__(cls) -> "ContentAllowList":
5
+ if not hasattr(cls, "instance"):
6
+ obj = cls.instance = super(ContentAllowList, cls).__new__(cls)
7
+ obj._allow_list: list[dict] = []
8
+
9
+ return cls.instance
10
+
11
+ def is_allowed(self, association_properties: dict) -> bool:
12
+ for allow_list_item in self._allow_list:
13
+ if all(
14
+ [
15
+ association_properties.get(key) == value
16
+ for key, value in allow_list_item.items()
17
+ ]
18
+ ):
19
+ return True
20
+
21
+ return False
22
+
23
+ def load(self, response_json: dict):
24
+ self._allow_list = response_json["associationPropertyAllowList"]
@@ -0,0 +1,13 @@
1
+ from contextlib import contextmanager
2
+
3
+ from lmnr.traceloop_sdk.tracing.tracing import TracerWrapper
4
+
5
+
6
+ @contextmanager
7
+ def get_tracer(flush_on_exit: bool = False):
8
+ wrapper = TracerWrapper()
9
+ try:
10
+ yield wrapper.get_tracer()
11
+ finally:
12
+ if flush_on_exit:
13
+ wrapper.flush()