lmnr 0.6.8__tar.gz → 0.6.10__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 (59) hide show
  1. {lmnr-0.6.8 → lmnr-0.6.10}/PKG-INFO +55 -55
  2. lmnr-0.6.10/pyproject.toml +142 -0
  3. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/cli.py +66 -27
  4. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/__init__.py +2 -0
  5. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +90 -15
  6. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py +81 -58
  7. lmnr-0.6.10/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py +121 -0
  8. lmnr-0.6.10/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/utils.py +60 -0
  9. lmnr-0.6.10/src/lmnr/opentelemetry_lib/tracing/__init__.py +162 -0
  10. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +12 -0
  11. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/tracing/context_properties.py +8 -1
  12. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/tracing/instruments.py +2 -0
  13. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/tracing/processor.py +1 -1
  14. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/asynchronous/resources/evals.py +4 -0
  15. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/synchronous/resources/evals.py +4 -0
  16. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/evaluations.py +13 -17
  17. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/laminar.py +7 -0
  18. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/types.py +2 -2
  19. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/version.py +1 -1
  20. lmnr-0.6.8/pyproject.toml +0 -138
  21. lmnr-0.6.8/src/lmnr/opentelemetry_lib/tracing/__init__.py +0 -157
  22. {lmnr-0.6.8 → lmnr-0.6.10}/LICENSE +0 -0
  23. {lmnr-0.6.8 → lmnr-0.6.10}/README.md +0 -0
  24. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/__init__.py +0 -0
  25. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/.flake8 +0 -0
  26. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/decorators/__init__.py +0 -0
  27. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/config.py +0 -0
  28. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/tracing/attributes.py +0 -0
  29. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/tracing/exporter.py +0 -0
  30. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/tracing/tracer.py +0 -0
  31. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/utils/__init__.py +0 -0
  32. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/utils/json_encoder.py +0 -0
  33. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/opentelemetry_lib/utils/package_check.py +0 -0
  34. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/py.typed +0 -0
  35. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/__init__.py +0 -0
  36. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/browser/__init__.py +0 -0
  37. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/browser/browser_use_otel.py +0 -0
  38. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/browser/patchright_otel.py +0 -0
  39. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/browser/playwright_otel.py +0 -0
  40. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/browser/pw_utils.py +0 -0
  41. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/browser/rrweb/rrweb.umd.min.cjs +0 -0
  42. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/browser/utils.py +0 -0
  43. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/asynchronous/async_client.py +0 -0
  44. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/asynchronous/resources/__init__.py +0 -0
  45. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/asynchronous/resources/agent.py +0 -0
  46. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/asynchronous/resources/base.py +0 -0
  47. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/asynchronous/resources/browser_events.py +0 -0
  48. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/asynchronous/resources/tags.py +0 -0
  49. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/synchronous/resources/__init__.py +0 -0
  50. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/synchronous/resources/agent.py +0 -0
  51. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/synchronous/resources/base.py +0 -0
  52. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/synchronous/resources/browser_events.py +0 -0
  53. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/synchronous/resources/tags.py +0 -0
  54. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/client/synchronous/sync_client.py +0 -0
  55. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/datasets.py +0 -0
  56. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/decorators.py +0 -0
  57. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/eval_control.py +0 -0
  58. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/log.py +0 -0
  59. {lmnr-0.6.8 → lmnr-0.6.10}/src/lmnr/sdk/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lmnr
3
- Version: 0.6.8
3
+ Version: 0.6.10
4
4
  Summary: Python SDK for Laminar
5
5
  License: Apache-2.0
6
6
  Author: lmnr.ai
@@ -46,61 +46,61 @@ Requires-Dist: httpx (>=0.25.0)
46
46
  Requires-Dist: opentelemetry-api (>=1.33.0)
47
47
  Requires-Dist: opentelemetry-exporter-otlp-proto-grpc (>=1.33.0)
48
48
  Requires-Dist: opentelemetry-exporter-otlp-proto-http (>=1.33.0)
49
- Requires-Dist: opentelemetry-instrumentation-alephalpha (>=0.40.5) ; extra == "alephalpha"
50
- Requires-Dist: opentelemetry-instrumentation-alephalpha (>=0.40.5) ; extra == "all"
51
- Requires-Dist: opentelemetry-instrumentation-anthropic (>=0.40.5) ; extra == "all"
52
- Requires-Dist: opentelemetry-instrumentation-anthropic (>=0.40.5) ; extra == "anthropic"
53
- Requires-Dist: opentelemetry-instrumentation-bedrock (>=0.40.5) ; extra == "all"
54
- Requires-Dist: opentelemetry-instrumentation-bedrock (>=0.40.5) ; extra == "bedrock"
55
- Requires-Dist: opentelemetry-instrumentation-chromadb (>=0.40.5) ; extra == "all"
56
- Requires-Dist: opentelemetry-instrumentation-chromadb (>=0.40.5) ; extra == "chromadb"
57
- Requires-Dist: opentelemetry-instrumentation-cohere (>=0.40.5) ; extra == "all"
58
- Requires-Dist: opentelemetry-instrumentation-cohere (>=0.40.5) ; extra == "cohere"
59
- Requires-Dist: opentelemetry-instrumentation-crewai (>=0.40.5) ; extra == "all"
60
- Requires-Dist: opentelemetry-instrumentation-crewai (>=0.40.5) ; extra == "crewai"
61
- Requires-Dist: opentelemetry-instrumentation-google-generativeai (>=0.40.5) ; extra == "all"
62
- Requires-Dist: opentelemetry-instrumentation-google-generativeai (>=0.40.5) ; extra == "google-generativeai"
63
- Requires-Dist: opentelemetry-instrumentation-groq (>=0.40.5) ; extra == "all"
64
- Requires-Dist: opentelemetry-instrumentation-groq (>=0.40.5) ; extra == "groq"
65
- Requires-Dist: opentelemetry-instrumentation-haystack (>=0.40.5) ; extra == "all"
66
- Requires-Dist: opentelemetry-instrumentation-haystack (>=0.40.5) ; extra == "haystack"
67
- Requires-Dist: opentelemetry-instrumentation-lancedb (>=0.40.5) ; extra == "all"
68
- Requires-Dist: opentelemetry-instrumentation-lancedb (>=0.40.5) ; extra == "lancedb"
69
- Requires-Dist: opentelemetry-instrumentation-langchain (>=0.40.5) ; extra == "all"
70
- Requires-Dist: opentelemetry-instrumentation-langchain (>=0.40.5) ; extra == "langchain"
71
- Requires-Dist: opentelemetry-instrumentation-llamaindex (>=0.40.5) ; extra == "all"
72
- Requires-Dist: opentelemetry-instrumentation-llamaindex (>=0.40.5) ; extra == "llamaindex"
73
- Requires-Dist: opentelemetry-instrumentation-marqo (>=0.40.5) ; extra == "all"
74
- Requires-Dist: opentelemetry-instrumentation-marqo (>=0.40.5) ; extra == "marqo"
75
- Requires-Dist: opentelemetry-instrumentation-mcp (>=0.40.5) ; extra == "all"
76
- Requires-Dist: opentelemetry-instrumentation-mcp (>=0.40.5) ; extra == "mcp"
77
- Requires-Dist: opentelemetry-instrumentation-milvus (>=0.40.5) ; extra == "all"
78
- Requires-Dist: opentelemetry-instrumentation-milvus (>=0.40.5) ; extra == "milvus"
79
- Requires-Dist: opentelemetry-instrumentation-mistralai (>=0.40.5) ; extra == "all"
80
- Requires-Dist: opentelemetry-instrumentation-mistralai (>=0.40.5) ; extra == "mistralai"
81
- Requires-Dist: opentelemetry-instrumentation-ollama (>=0.40.5) ; extra == "all"
82
- Requires-Dist: opentelemetry-instrumentation-ollama (>=0.40.5) ; extra == "ollama"
83
- Requires-Dist: opentelemetry-instrumentation-openai (>=0.40.5) ; extra == "all"
84
- Requires-Dist: opentelemetry-instrumentation-openai (>=0.40.5) ; extra == "openai"
85
- Requires-Dist: opentelemetry-instrumentation-pinecone (>=0.40.5) ; extra == "all"
86
- Requires-Dist: opentelemetry-instrumentation-pinecone (>=0.40.5) ; extra == "pinecone"
87
- Requires-Dist: opentelemetry-instrumentation-qdrant (>=0.40.5) ; extra == "all"
88
- Requires-Dist: opentelemetry-instrumentation-qdrant (>=0.40.5) ; extra == "qdrant"
89
- Requires-Dist: opentelemetry-instrumentation-replicate (>=0.40.5) ; extra == "all"
90
- Requires-Dist: opentelemetry-instrumentation-replicate (>=0.40.5) ; extra == "replicate"
91
- Requires-Dist: opentelemetry-instrumentation-sagemaker (>=0.40.5) ; extra == "all"
92
- Requires-Dist: opentelemetry-instrumentation-sagemaker (>=0.40.5) ; extra == "sagemaker"
49
+ Requires-Dist: opentelemetry-instrumentation-alephalpha (>=0.40.8) ; extra == "alephalpha"
50
+ Requires-Dist: opentelemetry-instrumentation-alephalpha (>=0.40.8) ; extra == "all"
51
+ Requires-Dist: opentelemetry-instrumentation-anthropic (>=0.40.8) ; extra == "all"
52
+ Requires-Dist: opentelemetry-instrumentation-anthropic (>=0.40.8) ; extra == "anthropic"
53
+ Requires-Dist: opentelemetry-instrumentation-bedrock (>=0.40.8) ; extra == "all"
54
+ Requires-Dist: opentelemetry-instrumentation-bedrock (>=0.40.8) ; extra == "bedrock"
55
+ Requires-Dist: opentelemetry-instrumentation-chromadb (>=0.40.8) ; extra == "all"
56
+ Requires-Dist: opentelemetry-instrumentation-chromadb (>=0.40.8) ; extra == "chromadb"
57
+ Requires-Dist: opentelemetry-instrumentation-cohere (>=0.40.8) ; extra == "all"
58
+ Requires-Dist: opentelemetry-instrumentation-cohere (>=0.40.8) ; extra == "cohere"
59
+ Requires-Dist: opentelemetry-instrumentation-crewai (>=0.40.8) ; extra == "all"
60
+ Requires-Dist: opentelemetry-instrumentation-crewai (>=0.40.8) ; extra == "crewai"
61
+ Requires-Dist: opentelemetry-instrumentation-google-generativeai (>=0.40.8) ; extra == "all"
62
+ Requires-Dist: opentelemetry-instrumentation-google-generativeai (>=0.40.8) ; extra == "google-generativeai"
63
+ Requires-Dist: opentelemetry-instrumentation-groq (>=0.40.8) ; extra == "all"
64
+ Requires-Dist: opentelemetry-instrumentation-groq (>=0.40.8) ; extra == "groq"
65
+ Requires-Dist: opentelemetry-instrumentation-haystack (>=0.40.8) ; extra == "all"
66
+ Requires-Dist: opentelemetry-instrumentation-haystack (>=0.40.8) ; extra == "haystack"
67
+ Requires-Dist: opentelemetry-instrumentation-lancedb (>=0.40.8) ; extra == "all"
68
+ Requires-Dist: opentelemetry-instrumentation-lancedb (>=0.40.8) ; extra == "lancedb"
69
+ Requires-Dist: opentelemetry-instrumentation-langchain (>=0.40.8) ; extra == "all"
70
+ Requires-Dist: opentelemetry-instrumentation-langchain (>=0.40.8) ; extra == "langchain"
71
+ Requires-Dist: opentelemetry-instrumentation-llamaindex (>=0.40.8) ; extra == "all"
72
+ Requires-Dist: opentelemetry-instrumentation-llamaindex (>=0.40.8) ; extra == "llamaindex"
73
+ Requires-Dist: opentelemetry-instrumentation-marqo (>=0.40.8) ; extra == "all"
74
+ Requires-Dist: opentelemetry-instrumentation-marqo (>=0.40.8) ; extra == "marqo"
75
+ Requires-Dist: opentelemetry-instrumentation-mcp (>=0.40.8) ; extra == "all"
76
+ Requires-Dist: opentelemetry-instrumentation-mcp (>=0.40.8) ; extra == "mcp"
77
+ Requires-Dist: opentelemetry-instrumentation-milvus (>=0.40.8) ; extra == "all"
78
+ Requires-Dist: opentelemetry-instrumentation-milvus (>=0.40.8) ; extra == "milvus"
79
+ Requires-Dist: opentelemetry-instrumentation-mistralai (>=0.40.8) ; extra == "all"
80
+ Requires-Dist: opentelemetry-instrumentation-mistralai (>=0.40.8) ; extra == "mistralai"
81
+ Requires-Dist: opentelemetry-instrumentation-ollama (>=0.40.8) ; extra == "all"
82
+ Requires-Dist: opentelemetry-instrumentation-ollama (>=0.40.8) ; extra == "ollama"
83
+ Requires-Dist: opentelemetry-instrumentation-openai (>=0.40.8) ; extra == "all"
84
+ Requires-Dist: opentelemetry-instrumentation-openai (>=0.40.8) ; extra == "openai"
85
+ Requires-Dist: opentelemetry-instrumentation-pinecone (>=0.40.8) ; extra == "all"
86
+ Requires-Dist: opentelemetry-instrumentation-pinecone (>=0.40.8) ; extra == "pinecone"
87
+ Requires-Dist: opentelemetry-instrumentation-qdrant (>=0.40.8) ; extra == "all"
88
+ Requires-Dist: opentelemetry-instrumentation-qdrant (>=0.40.8) ; extra == "qdrant"
89
+ Requires-Dist: opentelemetry-instrumentation-replicate (>=0.40.8) ; extra == "all"
90
+ Requires-Dist: opentelemetry-instrumentation-replicate (>=0.40.8) ; extra == "replicate"
91
+ Requires-Dist: opentelemetry-instrumentation-sagemaker (>=0.40.8) ; extra == "all"
92
+ Requires-Dist: opentelemetry-instrumentation-sagemaker (>=0.40.8) ; extra == "sagemaker"
93
93
  Requires-Dist: opentelemetry-instrumentation-threading (>=0.54b0)
94
- Requires-Dist: opentelemetry-instrumentation-together (>=0.40.5) ; extra == "all"
95
- Requires-Dist: opentelemetry-instrumentation-together (>=0.40.5) ; extra == "together"
96
- Requires-Dist: opentelemetry-instrumentation-transformers (>=0.40.5) ; extra == "all"
97
- Requires-Dist: opentelemetry-instrumentation-transformers (>=0.40.5) ; extra == "transformers"
98
- Requires-Dist: opentelemetry-instrumentation-vertexai (>=0.40.5) ; extra == "all"
99
- Requires-Dist: opentelemetry-instrumentation-vertexai (>=0.40.5) ; extra == "vertexai"
100
- Requires-Dist: opentelemetry-instrumentation-watsonx (>=0.40.5) ; extra == "all"
101
- Requires-Dist: opentelemetry-instrumentation-watsonx (>=0.40.5) ; extra == "watsonx"
102
- Requires-Dist: opentelemetry-instrumentation-weaviate (>=0.40.5) ; extra == "all"
103
- Requires-Dist: opentelemetry-instrumentation-weaviate (>=0.40.5) ; extra == "weaviate"
94
+ Requires-Dist: opentelemetry-instrumentation-together (>=0.40.8) ; extra == "all"
95
+ Requires-Dist: opentelemetry-instrumentation-together (>=0.40.8) ; extra == "together"
96
+ Requires-Dist: opentelemetry-instrumentation-transformers (>=0.40.8) ; extra == "all"
97
+ Requires-Dist: opentelemetry-instrumentation-transformers (>=0.40.8) ; extra == "transformers"
98
+ Requires-Dist: opentelemetry-instrumentation-vertexai (>=0.40.8) ; extra == "all"
99
+ Requires-Dist: opentelemetry-instrumentation-vertexai (>=0.40.8) ; extra == "vertexai"
100
+ Requires-Dist: opentelemetry-instrumentation-watsonx (>=0.40.8) ; extra == "all"
101
+ Requires-Dist: opentelemetry-instrumentation-watsonx (>=0.40.8) ; extra == "watsonx"
102
+ Requires-Dist: opentelemetry-instrumentation-weaviate (>=0.40.8) ; extra == "all"
103
+ Requires-Dist: opentelemetry-instrumentation-weaviate (>=0.40.8) ; extra == "weaviate"
104
104
  Requires-Dist: opentelemetry-sdk (>=1.33.0)
105
105
  Requires-Dist: opentelemetry-semantic-conventions (>=0.54b0)
106
106
  Requires-Dist: opentelemetry-semantic-conventions-ai (>=0.4.8)
@@ -0,0 +1,142 @@
1
+ # Laminar Python
2
+
3
+ # If you are looking for information about possible extras installations,
4
+ # i.e. what you can pass into `pip install 'lmnr[extra1,extra2]'`, please see the
5
+ # `[project.optional-dependencies]` section below.
6
+
7
+ [project]
8
+ name = "lmnr"
9
+ version = "0.6.10"
10
+ description = "Python SDK for Laminar"
11
+ authors = [
12
+ { name = "lmnr.ai", email = "founders@lmnr.ai" }
13
+ ]
14
+ readme = "README.md"
15
+ requires-python = ">=3.10,<4"
16
+ license = "Apache-2.0"
17
+ dependencies = [
18
+ "pydantic (>=2.0.3,<3.0.0)",
19
+ "python-dotenv (>=1.0)",
20
+ "opentelemetry-api (>=1.33.0)",
21
+ "opentelemetry-sdk (>=1.33.0)",
22
+ "opentelemetry-exporter-otlp-proto-http (>=1.33.0)",
23
+ "opentelemetry-exporter-otlp-proto-grpc (>=1.33.0)",
24
+ "opentelemetry-semantic-conventions (>=0.54b0)",
25
+ "opentelemetry-semantic-conventions-ai (>=0.4.8)",
26
+ "tqdm (>=4.0)",
27
+ "argparse (>=1.0)",
28
+ "tenacity (>=8.0)",
29
+ # Since 1.68.0, grpcio writes a warning message
30
+ # that looks scary, but is harmless.
31
+ # WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
32
+ # E0000 00:00:1737439981.199902 9456033 init.cc:229] grpc_wait_for_shutdown_with_timeout() timed out.
33
+ #
34
+ # Remove this comment when we make sure that grpcio has resolved this.
35
+ # Related issue:
36
+ # https://discuss.ai.google.dev/t/warning-all-log-messages-before-absl-initializelog-is-called-are-written-to-stderr-e0000-001731955515-629532-17124-init-cc-229-grpc-wait-for-shutdown-with-timeout-timed-out/50020
37
+ # https://github.com/grpc/grpc/issues/38490
38
+ "grpcio>=1",
39
+ "httpx>=0.25.0",
40
+ "opentelemetry-instrumentation-threading>=0.54b0",
41
+ ]
42
+
43
+ [project.scripts]
44
+ lmnr = "lmnr.cli:cli"
45
+
46
+ [project.optional-dependencies]
47
+ # List of all possible extras. You can specify one or more of these extras
48
+ # when installing the package, using any of the following examples:
49
+ # `pip install 'lmnr[anthropic,openai]'`
50
+ # `uv pip install 'lmnr[anthropic,openai]'`
51
+ # `uv add lmnr --extra anthropic --extra openai`
52
+ # `poetry add 'lmnr[anthropic,openai]'`
53
+
54
+ alephalpha=["opentelemetry-instrumentation-alephalpha>=0.40.8"]
55
+ anthropic=["opentelemetry-instrumentation-anthropic>=0.40.8"]
56
+ bedrock=["opentelemetry-instrumentation-bedrock>=0.40.8"]
57
+ chromadb=["opentelemetry-instrumentation-chromadb>=0.40.8"]
58
+ cohere=["opentelemetry-instrumentation-cohere>=0.40.8"]
59
+ crewai=["opentelemetry-instrumentation-crewai>=0.40.8"]
60
+ google-generativeai=["opentelemetry-instrumentation-google-generativeai>=0.40.8"]
61
+ groq=["opentelemetry-instrumentation-groq>=0.40.8"]
62
+ haystack=["opentelemetry-instrumentation-haystack>=0.40.8"]
63
+ lancedb=["opentelemetry-instrumentation-lancedb>=0.40.8"]
64
+ langchain=["opentelemetry-instrumentation-langchain>=0.40.8"]
65
+ llamaindex=["opentelemetry-instrumentation-llamaindex>=0.40.8"]
66
+ marqo=["opentelemetry-instrumentation-marqo>=0.40.8"]
67
+ mcp=["opentelemetry-instrumentation-mcp>=0.40.8"]
68
+ milvus=["opentelemetry-instrumentation-milvus>=0.40.8"]
69
+ mistralai=["opentelemetry-instrumentation-mistralai>=0.40.8"]
70
+ ollama=["opentelemetry-instrumentation-ollama>=0.40.8"]
71
+ openai=["opentelemetry-instrumentation-openai>=0.40.8"]
72
+ pinecone=["opentelemetry-instrumentation-pinecone>=0.40.8"]
73
+ qdrant=["opentelemetry-instrumentation-qdrant>=0.40.8"]
74
+ replicate=["opentelemetry-instrumentation-replicate>=0.40.8"]
75
+ sagemaker=["opentelemetry-instrumentation-sagemaker>=0.40.8"]
76
+ together=["opentelemetry-instrumentation-together>=0.40.8"]
77
+ transformers=["opentelemetry-instrumentation-transformers>=0.40.8"]
78
+ vertexai=["opentelemetry-instrumentation-vertexai>=0.40.8"]
79
+ watsonx=["opentelemetry-instrumentation-watsonx>=0.40.8"]
80
+ weaviate=["opentelemetry-instrumentation-weaviate>=0.40.8"]
81
+ # `all` is the group added for convenience, if you want to install all
82
+ # the instrumentations.
83
+ # we suggest using package-manager-specific commands instead,
84
+ # like `uv add lmnr --all-extras`
85
+ all = [
86
+ "opentelemetry-instrumentation-alephalpha>=0.40.8",
87
+ "opentelemetry-instrumentation-anthropic>=0.40.8",
88
+ "opentelemetry-instrumentation-bedrock>=0.40.8",
89
+ "opentelemetry-instrumentation-chromadb>=0.40.8",
90
+ "opentelemetry-instrumentation-cohere>=0.40.8",
91
+ "opentelemetry-instrumentation-crewai>=0.40.8",
92
+ "opentelemetry-instrumentation-google-generativeai>=0.40.8",
93
+ "opentelemetry-instrumentation-groq>=0.40.8",
94
+ "opentelemetry-instrumentation-haystack>=0.40.8",
95
+ "opentelemetry-instrumentation-lancedb>=0.40.8",
96
+ "opentelemetry-instrumentation-langchain>=0.40.8",
97
+ "opentelemetry-instrumentation-llamaindex>=0.40.8",
98
+ "opentelemetry-instrumentation-marqo>=0.40.8",
99
+ "opentelemetry-instrumentation-mcp>=0.40.8",
100
+ "opentelemetry-instrumentation-milvus>=0.40.8",
101
+ "opentelemetry-instrumentation-mistralai>=0.40.8",
102
+ "opentelemetry-instrumentation-ollama>=0.40.8",
103
+ "opentelemetry-instrumentation-openai>=0.40.8",
104
+ "opentelemetry-instrumentation-pinecone>=0.40.8",
105
+ "opentelemetry-instrumentation-qdrant>=0.40.8",
106
+ "opentelemetry-instrumentation-replicate>=0.40.8",
107
+ "opentelemetry-instrumentation-sagemaker>=0.40.8",
108
+ "opentelemetry-instrumentation-together>=0.40.8",
109
+ "opentelemetry-instrumentation-transformers>=0.40.8",
110
+ "opentelemetry-instrumentation-vertexai>=0.40.8",
111
+ "opentelemetry-instrumentation-watsonx>=0.40.8",
112
+ "opentelemetry-instrumentation-weaviate>=0.40.8"
113
+ ]
114
+
115
+ [dependency-groups]
116
+ dev = [
117
+ "autopep8>=2.3.2",
118
+ "flake8>=7.2.0",
119
+ "pytest>=8.3.5",
120
+ "pytest-sugar>=1.0.0",
121
+ "pytest-asyncio>=0.26.0",
122
+ "playwright>=1.52.0",
123
+ "vcrpy>=7.0.0",
124
+ "openai>=1.77.0",
125
+ "pytest-recording>=0.13.4",
126
+ "patchright>=1.52.3",
127
+ "google-genai>=1.19.0",
128
+ "langgraph>=0.4.8",
129
+ "langchain-core>=0.3.64",
130
+ "langchain>=0.3.25",
131
+ ]
132
+
133
+ [build-system]
134
+ requires = ["poetry-core"]
135
+ build-backend = "poetry.core.masonry.api"
136
+
137
+ [tool.uv.workspace]
138
+ members = ["examples/fastapi-app"]
139
+ # we can move to uv_build, once it's more stable
140
+ # https://github.com/astral-sh/uv/issues/3957
141
+ # requires = ["uv_build>=0.6.16,<0.7"]
142
+ # build-backend = "uv_build"
@@ -1,6 +1,8 @@
1
1
  from argparse import ArgumentParser
2
2
  import asyncio
3
+ import glob
3
4
  import importlib.util
5
+ import json
4
6
  import os
5
7
  import re
6
8
  import sys
@@ -23,27 +25,27 @@ def add_cursor_rules():
23
25
  """Download laminar.mdc file from a hardcoded public URL and save it to .cursor/rules/laminar.mdc"""
24
26
  # Hardcoded URL for the laminar.mdc file
25
27
  url = "https://raw.githubusercontent.com/lmnr-ai/lmnr/dev/rules/laminar.mdc"
26
-
28
+
27
29
  # Create .cursor/rules directory if it doesn't exist
28
30
  rules_dir = Path(".cursor/rules")
29
31
  rules_dir.mkdir(parents=True, exist_ok=True)
30
-
32
+
31
33
  # Define the target file path
32
34
  target_file = rules_dir / "laminar.mdc"
33
-
35
+
34
36
  try:
35
37
  LOG.info(f"Downloading laminar.mdc from {url}")
36
-
38
+
37
39
  # Download the file
38
40
  with urllib.request.urlopen(url) as response:
39
41
  content = response.read()
40
-
42
+
41
43
  # Write the content to the target file (this will overwrite if it exists)
42
- with open(target_file, 'wb') as f:
44
+ with open(target_file, "wb") as f:
43
45
  f.write(content)
44
-
46
+
45
47
  LOG.info(f"Successfully downloaded laminar.mdc to {target_file}")
46
-
48
+
47
49
  except urllib.error.URLError as e:
48
50
  LOG.error(f"Failed to download file from {url}: {e}")
49
51
  sys.exit(1)
@@ -55,7 +57,7 @@ def add_cursor_rules():
55
57
  async def run_evaluation(args):
56
58
  sys.path.append(os.getcwd())
57
59
 
58
- if args.file is None:
60
+ if len(args.file) == 0:
59
61
  files = [
60
62
  os.path.join(EVAL_DIR, f)
61
63
  for f in os.listdir(EVAL_DIR)
@@ -71,9 +73,17 @@ async def run_evaluation(args):
71
73
  LOG.info(f"Located {len(files)} evaluation files in {EVAL_DIR}")
72
74
 
73
75
  else:
74
- files = [args.file]
76
+ files = []
77
+ for pattern in args.file:
78
+ matches = glob.glob(pattern)
79
+ if matches:
80
+ files.extend(matches)
81
+ else:
82
+ # If no matches found, treat as literal filename
83
+ files.append(pattern)
75
84
 
76
85
  prep_token = PREPARE_ONLY.set(True)
86
+ scores = []
77
87
  try:
78
88
  for file in files:
79
89
  LOG.info(f"Running evaluation from {file}")
@@ -83,33 +93,56 @@ async def run_evaluation(args):
83
93
  spec = importlib.util.spec_from_file_location(name, file)
84
94
  if spec is None or spec.loader is None:
85
95
  LOG.error(f"Could not load module specification from {file}")
86
- if args.fail_on_error:
87
- return
88
- continue
96
+ if args.continue_on_error:
97
+ continue
98
+ return
89
99
  mod = importlib.util.module_from_spec(spec)
90
100
  sys.modules[name] = mod
91
101
 
92
102
  spec.loader.exec_module(mod)
93
- evaluations: list[Evaluation] | None = EVALUATION_INSTANCES.get()
94
- if evaluations is None:
95
- LOG.warning("Evaluation instance not found")
96
- if args.fail_on_error:
97
- return
98
- continue
103
+ evaluations = []
104
+ try:
105
+ evaluations: list[Evaluation] | None = EVALUATION_INSTANCES.get()
106
+ if evaluations is None:
107
+ raise LookupError()
108
+ # may be raised by `get()` or manually by us above
109
+ except LookupError:
110
+ log_evaluation_instance_not_found()
111
+ if args.continue_on_error:
112
+ continue
113
+ return
99
114
 
100
115
  LOG.info(f"Loaded {len(evaluations)} evaluations from {file}")
101
116
 
102
117
  for evaluation in evaluations:
103
118
  try:
104
- await evaluation.run()
119
+ eval_scores = await evaluation.run()
120
+ scores.append(
121
+ {
122
+ "file": file,
123
+ "scores": eval_scores,
124
+ }
125
+ )
105
126
  except Exception as e:
106
127
  LOG.error(f"Error running evaluation: {e}")
107
- if args.fail_on_error:
128
+ if not args.continue_on_error:
108
129
  raise
130
+
131
+ if args.output_file:
132
+ with open(args.output_file, "w") as f:
133
+ json.dump(scores, f, indent=2)
109
134
  finally:
110
135
  PREPARE_ONLY.reset(prep_token)
111
136
 
112
137
 
138
+ def log_evaluation_instance_not_found():
139
+ LOG.warning(
140
+ "Evaluation instance not found. "
141
+ "`evaluate` must be called at the top level of the file, "
142
+ "not inside a function when running evaluations from the CLI."
143
+ )
144
+
145
+
113
146
  def cli():
114
147
  parser = ArgumentParser(
115
148
  prog="lmnr",
@@ -125,21 +158,27 @@ def cli():
125
158
  )
126
159
  parser_eval.add_argument(
127
160
  "file",
128
- nargs="?",
129
- help="A file containing the evaluation to run."
161
+ nargs="*",
162
+ help="Files or a file containing the evaluation to run."
130
163
  + "If no file name is provided, all evaluation files in the `evals` directory are run as long"
131
164
  + "as they match *_eval.py or eval_*.py",
132
- default=None,
165
+ default=[],
133
166
  )
134
167
 
135
168
  parser_eval.add_argument(
136
- "--fail-on-error",
169
+ "--continue-on-error",
137
170
  action="store_true",
138
171
  default=False,
139
- help="Fail on error",
172
+ help="Continue execution upon errors",
173
+ )
174
+
175
+ parser_eval.add_argument(
176
+ "--output-file",
177
+ help="Output file to write the results to. Outputs are written in JSON format.",
178
+ nargs="?",
140
179
  )
141
180
 
142
- parser_download = subparsers.add_parser(
181
+ subparsers.add_parser(
143
182
  "add-cursor-rules",
144
183
  description="Download laminar.mdc file and add it to .cursor/rules",
145
184
  help="Download laminar.mdc file and add it to .cursor/rules",
@@ -54,6 +54,8 @@ class TracerManager:
54
54
 
55
55
  @staticmethod
56
56
  def flush() -> bool:
57
+ if not hasattr(TracerManager, "_TracerManager__tracer_wrapper"):
58
+ return False
57
59
  return TracerManager.__tracer_wrapper.flush()
58
60
 
59
61
  @staticmethod
@@ -1,6 +1,7 @@
1
1
  """OpenTelemetry Google Generative AI API instrumentation"""
2
2
 
3
3
  from collections import defaultdict
4
+ import json
4
5
  import logging
5
6
  import os
6
7
  from typing import AsyncGenerator, Callable, Collection, Generator
@@ -11,7 +12,9 @@ from .config import (
11
12
  Config,
12
13
  )
13
14
  from .utils import (
15
+ ProcessedContentPart,
14
16
  dont_throw,
17
+ get_content,
15
18
  role_from_content_union,
16
19
  set_span_attribute,
17
20
  process_content_union,
@@ -159,7 +162,7 @@ def _set_request_attributes(span, args, kwargs):
159
162
  set_span_attribute(
160
163
  span,
161
164
  f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.content",
162
- process_content_union(system_instruction),
165
+ (get_content(process_content_union(system_instruction)) or {}).get("text", ""),
163
166
  )
164
167
  set_span_attribute(
165
168
  span, f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.role", "system"
@@ -169,11 +172,42 @@ def _set_request_attributes(span, args, kwargs):
169
172
  if not isinstance(contents, list):
170
173
  contents = [contents]
171
174
  for content in contents:
175
+ processed_content = process_content_union(content)
176
+ content_str = get_content(processed_content)
172
177
  set_span_attribute(
173
178
  span,
174
179
  f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.content",
175
- process_content_union(content),
180
+ (
181
+ content_str
182
+ if isinstance(content_str, str)
183
+ else json.dumps(content_str)
184
+ ),
176
185
  )
186
+ blocks = (
187
+ processed_content
188
+ if isinstance(processed_content, list)
189
+ else [processed_content]
190
+ )
191
+ for j, block in enumerate(blocks):
192
+ block_dict = to_dict(block)
193
+ if not block_dict.get("function_call"):
194
+ continue
195
+ function_call = to_dict(block_dict.get("function_call", {}))
196
+ set_span_attribute(
197
+ span,
198
+ f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.tool_calls.{j}.name",
199
+ function_call.get("name"),
200
+ )
201
+ set_span_attribute(
202
+ span,
203
+ f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.tool_calls.{j}.id",
204
+ function_call.get("id"),
205
+ )
206
+ set_span_attribute(
207
+ span,
208
+ f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.tool_calls.{j}.arguments",
209
+ json.dumps(function_call.get("arguments")),
210
+ )
177
211
  set_span_attribute(
178
212
  span,
179
213
  f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.role",
@@ -218,23 +252,64 @@ def _set_response_attributes(span, response: types.GenerateContentResponse):
218
252
  )
219
253
 
220
254
  if should_send_prompts():
221
- if len(candidates) > 1:
222
- for i, candidate in enumerate(candidates):
255
+ set_span_attribute(
256
+ span, f"{gen_ai_attributes.GEN_AI_COMPLETION}.0.role", "model"
257
+ )
258
+ candidates_list = candidates if isinstance(candidates, list) else [candidates]
259
+ for i, candidate in enumerate(candidates_list):
260
+ processed_content = process_content_union(candidate.content)
261
+ if isinstance(processed_content, list):
262
+ if all(
263
+ isinstance(item, dict) and item.get("type") == "text"
264
+ for item in processed_content
265
+ ):
266
+ content_str = processed_content[0]["text"]
267
+ elif all(
268
+ isinstance(item, ProcessedContentPart) and item.content
269
+ for item in processed_content
270
+ ):
271
+ content_str = processed_content[0].content
272
+ else:
273
+ content_str = get_content(processed_content)
274
+ else:
275
+ content_str = get_content(processed_content)
276
+ set_span_attribute(
277
+ span, f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.role", "model"
278
+ )
279
+ set_span_attribute(
280
+ span,
281
+ f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.content",
282
+ (
283
+ content_str
284
+ if isinstance(content_str, str)
285
+ else json.dumps(content_str)
286
+ ),
287
+ )
288
+ blocks = (
289
+ processed_content
290
+ if isinstance(processed_content, list)
291
+ else [processed_content]
292
+ )
293
+ for j, block in enumerate(blocks):
294
+ block_dict = to_dict(block)
295
+ if not block_dict.get("function_call"):
296
+ continue
297
+ function_call = to_dict(block_dict.get("function_call", {}))
223
298
  set_span_attribute(
224
299
  span,
225
- f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.content",
226
- process_content_union(candidate.content),
300
+ f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.tool_calls.{j}.name",
301
+ function_call.get("name"),
227
302
  )
228
303
  set_span_attribute(
229
- span, f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.role", "assistant"
304
+ span,
305
+ f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.tool_calls.{j}.id",
306
+ function_call.get("id"),
307
+ )
308
+ set_span_attribute(
309
+ span,
310
+ f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.tool_calls.{j}.arguments",
311
+ json.dumps(function_call.get("arguments")),
230
312
  )
231
- else:
232
- set_span_attribute(
233
- span, f"{gen_ai_attributes.GEN_AI_COMPLETION}.0.content", response.text
234
- )
235
- set_span_attribute(
236
- span, f"{gen_ai_attributes.GEN_AI_COMPLETION}.0.role", "assistant"
237
- )
238
313
 
239
314
 
240
315
  @dont_throw
@@ -433,7 +508,7 @@ class GoogleGenAiSdkInstrumentor(BaseInstrumentor):
433
508
 
434
509
  def _instrument(self, **kwargs):
435
510
  tracer_provider = kwargs.get("tracer_provider")
436
- tracer = get_tracer(__name__, "0.0.1a0", tracer_provider)
511
+ tracer = get_tracer(__name__, "0.0.1a1", tracer_provider)
437
512
 
438
513
  for wrapped_method in WRAPPED_METHODS:
439
514
  wrap_function_wrapper(