monocle-apptrace 0.3.1__tar.gz → 0.4.0b1__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.

Potentially problematic release.


This version of monocle-apptrace might be problematic. Click here for more details.

Files changed (102) hide show
  1. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/.gitignore +2 -1
  2. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/CHANGELOG.md +14 -1
  3. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/PKG-INFO +1 -1
  4. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/pyproject.toml +1 -1
  5. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/exporters/aws/s3_exporter.py +3 -1
  6. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/exporters/azure/blob_exporter.py +2 -2
  7. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/exporters/base_exporter.py +10 -4
  8. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/exporters/file_exporter.py +19 -4
  9. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/exporters/monocle_exporters.py +3 -3
  10. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/exporters/okahu/okahu_exporter.py +5 -2
  11. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/common/constants.py +5 -1
  12. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/common/instrumentor.py +24 -13
  13. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/common/span_handler.py +33 -18
  14. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/common/utils.py +62 -54
  15. monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/common/wrapper.py +231 -0
  16. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/common/wrapper_method.py +10 -5
  17. monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/aiohttp/_helper.py +66 -0
  18. monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/aiohttp/entities/http.py +51 -0
  19. monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/aiohttp/methods.py +13 -0
  20. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/flask/_helper.py +7 -2
  21. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/flask/entities/http.py +0 -1
  22. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +17 -4
  23. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/haystack/methods.py +8 -1
  24. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +13 -9
  25. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +14 -0
  26. monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +219 -0
  27. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/requests/_helper.py +1 -1
  28. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/teamsai/_helper.py +19 -1
  29. monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/__init__.py +0 -0
  30. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/actionplanner_output_processor.py +1 -1
  31. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/teamsai_output_processor.py +24 -18
  32. monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/teamsai/methods.py +60 -0
  33. monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/common/wrapper.py +0 -94
  34. monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +0 -71
  35. monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/teamsai/methods.py +0 -26
  36. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/CODEOWNERS.md +0 -0
  37. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/CODE_OF_CONDUCT.md +0 -0
  38. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/CONTRIBUTING.md +0 -0
  39. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/COPYRIGHT.template +0 -0
  40. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/LICENSE +0 -0
  41. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/MAINTAINER.md +0 -0
  42. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/Monocle_User_Guide.md +0 -0
  43. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/Monocle_committer_guide.md +0 -0
  44. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/Monocle_contributor_guide.md +0 -0
  45. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/NOTICE +0 -0
  46. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/README.md +0 -0
  47. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/SECURITY.md +0 -0
  48. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/README.md +0 -0
  49. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/__init__.py +0 -0
  50. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/__main__.py +0 -0
  51. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/exporters/aws/s3_exporter_opendal.py +0 -0
  52. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/exporters/azure/blob_exporter_opendal.py +0 -0
  53. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/exporters/exporter_processor.py +0 -0
  54. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/__init__.py +0 -0
  55. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/common/__init__.py +0 -0
  56. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/common/tracing.md +0 -0
  57. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/__init__.py +0 -0
  58. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/anthropic → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/aiohttp}/__init__.py +0 -0
  59. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/anthropic/entities → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/anthropic}/__init__.py +0 -0
  60. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/anthropic/_helper.py +0 -0
  61. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/botocore → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/anthropic/entities}/__init__.py +0 -0
  62. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/anthropic/entities/inference.py +0 -0
  63. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/anthropic/methods.py +0 -0
  64. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/botocore/entities → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/botocore}/__init__.py +0 -0
  65. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +0 -0
  66. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/flask → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/botocore/entities}/__init__.py +0 -0
  67. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +0 -0
  68. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +0 -0
  69. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/botocore/methods.py +0 -0
  70. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/haystack → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/flask}/__init__.py +0 -0
  71. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/flask/methods.py +0 -0
  72. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/haystack/entities → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/haystack}/__init__.py +0 -0
  73. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/langchain → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/haystack/entities}/__init__.py +0 -0
  74. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +0 -0
  75. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/haystack/entities/retrieval.py +0 -0
  76. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/langchain/entities → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/langchain}/__init__.py +0 -0
  77. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +0 -0
  78. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/langgraph → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/langchain/entities}/__init__.py +0 -0
  79. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +0 -0
  80. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/langchain/entities/retrieval.py +0 -0
  81. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/langchain/methods.py +0 -0
  82. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/langgraph/entities → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/langgraph}/__init__.py +0 -0
  83. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +0 -0
  84. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/llamaindex → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/langgraph/entities}/__init__.py +0 -0
  85. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +0 -0
  86. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +0 -0
  87. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/llamaindex/entities → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/llamaindex}/__init__.py +0 -0
  88. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/openai → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/llamaindex/entities}/__init__.py +0 -0
  89. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +0 -0
  90. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +0 -0
  91. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/llamaindex/entities/retrieval.py +0 -0
  92. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/openai/entities → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/openai}/__init__.py +0 -0
  93. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/openai/_helper.py +0 -0
  94. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/teamsai → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/openai/entities}/__init__.py +0 -0
  95. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +0 -0
  96. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/openai/methods.py +0 -0
  97. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/requests/__init__.py +0 -0
  98. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/requests/entities/http.py +0 -0
  99. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/src/monocle_apptrace/instrumentation/metamodel/requests/methods.py +0 -0
  100. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/teamsai/entities → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/teamsai}/__init__.py +0 -0
  101. {monocle_apptrace-0.3.1/src/monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference → monocle_apptrace-0.4.0b1/src/monocle_apptrace/instrumentation/metamodel/teamsai/entities}/__init__.py +0 -0
  102. {monocle_apptrace-0.3.1 → monocle_apptrace-0.4.0b1}/tox.ini +0 -0
@@ -13,4 +13,5 @@ dist
13
13
  .tox
14
14
  .DS_Store
15
15
  Pipfile**
16
- launch.json
16
+ launch.json
17
+ monocle_trace**.json
@@ -1,3 +1,16 @@
1
+ ## Version 0.4.0b1 (2025-05-14)
2
+
3
+ - Added conversation id in scope for teams ai bot ([#180](https://github.com/monocle2ai/monocle/pull/180))
4
+ - Update inference entity type of TeamsAI SDK ([#178](https://github.com/monocle2ai/monocle/pull/178))
5
+ - Added stream and async for openai ([#177](https://github.com/monocle2ai/monocle/pull/177))
6
+ - Update inference span of TeamsAI ([#176](https://github.com/monocle2ai/monocle/pull/176))
7
+ - Remove Preset span name and Bugfix for Event ([#175](https://github.com/monocle2ai/monocle/pull/175))
8
+ - Add haystack anthropic sample ([#174](https://github.com/monocle2ai/monocle/pull/174))
9
+ - aiohttp auto instrumentation ([#173](https://github.com/monocle2ai/monocle/pull/173))
10
+ - Add source path to spans and fix json syntax in file exporter ([#172](https://github.com/monocle2ai/monocle/pull/172))
11
+ - Added changes for openai streaming ([#171](https://github.com/monocle2ai/monocle/pull/171))
12
+ - Add llama index anthropic sample ([#170](https://github.com/monocle2ai/monocle/pull/170))
13
+
1
14
  ## Version 0.3.1 (2024-04-18)
2
15
 
3
16
  - Add MetaModel for Anthropic SDK ([#159](https://github.com/monocle2ai/monocle/pull/159))
@@ -9,7 +22,7 @@
9
22
  - Support monocle exporter list as parameter to `setup_monocle_telemetry()` ([#161](https://github.com/monocle2ai/monocle/pull/161))
10
23
  - Add langchain anthropic sample ([#165](https://github.com/monocle2ai/monocle/pull/165))
11
24
 
12
- ## Version 0.3.0 (2024-12-10)
25
+ ## Version 0.3.0 (2024-03-18)
13
26
 
14
27
  - Fixed issue with passing context in async case ([#150](https://github.com/monocle2ai/monocle/pull/150))
15
28
  - Added lambda processor ([#148](https://github.com/monocle2ai/monocle/pull/148))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: monocle_apptrace
3
- Version: 0.3.1
3
+ Version: 0.4.0b1
4
4
  Summary: package with monocle genAI tracing
5
5
  Project-URL: Homepage, https://github.com/monocle2ai/monocle
6
6
  Project-URL: Issues, https://github.com/monocle2ai/monocle/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "monocle_apptrace"
7
- version = "0.3.1"
7
+ version = "0.4.0b1"
8
8
  authors = []
9
9
  description = "package with monocle genAI tracing"
10
10
  readme = "README.md"
@@ -129,6 +129,8 @@ class S3SpanExporter(SpanExporterBase):
129
129
  # Serialize spans to JSON or any other format you prefer
130
130
  valid_json_list = []
131
131
  for span in spans:
132
+ if self.skip_export(span):
133
+ continue
132
134
  try:
133
135
  valid_json_list.append(span.to_json(indent=0).replace("\n", ""))
134
136
  except json.JSONDecodeError as e:
@@ -169,7 +171,7 @@ class S3SpanExporter(SpanExporterBase):
169
171
  Key=file_name,
170
172
  Body=span_data_batch
171
173
  )
172
- logger.info(f"Span batch uploaded to AWS S3 as {file_name}.")
174
+ logger.debug(f"Span batch uploaded to AWS S3 as {file_name}.")
173
175
 
174
176
  async def force_flush(self, timeout_millis: int = 30000) -> bool:
175
177
  await self.__export_spans() # Export any remaining spans in the queue
@@ -82,8 +82,8 @@ class AzureBlobSpanExporter(SpanExporterBase):
82
82
  # With Monocle,OpenTelemetry is always loaded. If the Azure trace package is installed, then it triggers the blob trace generation on every blob operation.
83
83
  # Thus, the Monocle span write ends up generating a blob span which again comes back to the exporter .. and would result in an infinite loop.
84
84
  # To avoid this, we check if the span has the Monocle SDK version attribute and skip it if it doesn't. That way the blob span genearted by Azure library are not exported.
85
- if not span.attributes.get(MONOCLE_SDK_VERSION):
86
- continue # TODO: All exporters to use same base class and check it there
85
+ if self.skip_export(span):
86
+ continue
87
87
  self.export_queue.append(span)
88
88
  if len(self.export_queue) >= self.max_batch_size:
89
89
  await self.__export_spans()
@@ -1,20 +1,21 @@
1
- import time
1
+ import time, os
2
2
  import random
3
3
  import logging
4
4
  from abc import ABC, abstractmethod
5
5
  from opentelemetry.sdk.trace import ReadableSpan
6
- from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
6
+ from opentelemetry.sdk.trace.export import SpanExportResult
7
+ from monocle_apptrace.instrumentation.common.constants import MONOCLE_SDK_VERSION
7
8
  from typing import Sequence
8
- import asyncio
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
12
  class SpanExporterBase(ABC):
13
- def __init__(self):
13
+ def __init__(self, export_monocle_only: bool = True):
14
14
  self.backoff_factor = 2
15
15
  self.max_retries = 10
16
16
  self.export_queue = []
17
17
  self.last_export_time = time.time()
18
+ self.export_monocle_only = export_monocle_only or os.environ.get("MONOCLE_EXPORTS_ONLY", True)
18
19
 
19
20
  @abstractmethod
20
21
  async def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
@@ -27,6 +28,11 @@ class SpanExporterBase(ABC):
27
28
  def shutdown(self) -> None:
28
29
  pass
29
30
 
31
+ def skip_export(self, span:ReadableSpan) -> bool:
32
+ if self.export_monocle_only and (not span.attributes.get(MONOCLE_SDK_VERSION)):
33
+ return True
34
+ return False
35
+
30
36
  @staticmethod
31
37
  def retry_with_backoff(retries=3, backoff_in_seconds=1, max_backoff_in_seconds=32, exceptions=(Exception,)):
32
38
  def decorator(func):
@@ -7,12 +7,13 @@ from typing import Optional, Callable, Sequence
7
7
  from opentelemetry.sdk.trace import ReadableSpan
8
8
  from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
9
9
  from opentelemetry.sdk.resources import SERVICE_NAME
10
+ from monocle_apptrace.exporters.base_exporter import SpanExporterBase
10
11
  from monocle_apptrace.exporters.exporter_processor import ExportTaskProcessor
11
12
 
12
13
  DEFAULT_FILE_PREFIX:str = "monocle_trace_"
13
14
  DEFAULT_TIME_FORMAT:str = "%Y-%m-%d_%H.%M.%S"
14
15
 
15
- class FileSpanExporter(SpanExporter):
16
+ class FileSpanExporter(SpanExporterBase):
16
17
  current_trace_id: int = None
17
18
  current_file_path: str = None
18
19
 
@@ -28,6 +29,7 @@ class FileSpanExporter(SpanExporter):
28
29
  + linesep,
29
30
  task_processor: Optional[ExportTaskProcessor] = None
30
31
  ):
32
+ super().__init__()
31
33
  self.out_handle:TextIOWrapper = None
32
34
  self.formatter = formatter
33
35
  self.service_name = service_name
@@ -39,21 +41,32 @@ class FileSpanExporter(SpanExporter):
39
41
  self.task_processor.start()
40
42
 
41
43
  def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
44
+ is_root_span = any(not span.parent for span in spans)
42
45
  if self.task_processor is not None and callable(getattr(self.task_processor, 'queue_task', None)):
43
46
  # Check if any span is a root span (no parent)
44
- is_root_span = any(not span.parent for span in spans)
45
47
  self.task_processor.queue_task(self._process_spans, spans, is_root_span)
46
48
  return SpanExportResult.SUCCESS
47
49
  else:
48
- return self._process_spans(spans)
50
+ return self._process_spans(spans, is_root_span=is_root_span)
49
51
 
50
52
  def _process_spans(self, spans: Sequence[ReadableSpan], is_root_span: bool = False) -> SpanExportResult:
53
+ first_span:bool = True
51
54
  for span in spans:
55
+ if self.skip_export(span):
56
+ continue
52
57
  if span.context.trace_id != self.current_trace_id:
53
58
  self.rotate_file(span.resource.attributes[SERVICE_NAME],
54
59
  span.context.trace_id)
60
+ first_span = True
61
+ if first_span:
62
+ first_span = False
63
+ else:
64
+ self.out_handle.write(",")
55
65
  self.out_handle.write(self.formatter(span))
56
- self.out_handle.flush()
66
+ if is_root_span:
67
+ self.reset_handle()
68
+ else:
69
+ self.out_handle.flush()
57
70
  return SpanExportResult.SUCCESS
58
71
 
59
72
  def rotate_file(self, trace_name:str, trace_id:int) -> None:
@@ -62,6 +75,7 @@ class FileSpanExporter(SpanExporter):
62
75
  self.file_prefix + trace_name + "_" + hex(trace_id) + "_"
63
76
  + datetime.now().strftime(self.time_format) + ".json")
64
77
  self.out_handle = open(self.current_file_path, "w", encoding='UTF-8')
78
+ self.out_handle.write("[")
65
79
  self.current_trace_id = trace_id
66
80
 
67
81
  def force_flush(self, timeout_millis: int = 30000) -> bool:
@@ -70,6 +84,7 @@ class FileSpanExporter(SpanExporter):
70
84
 
71
85
  def reset_handle(self) -> None:
72
86
  if self.out_handle is not None:
87
+ self.out_handle.write("]")
73
88
  self.out_handle.close()
74
89
  self.out_handle = None
75
90
 
@@ -1,6 +1,6 @@
1
1
  from typing import Dict, Any, List
2
2
  import os
3
- import logging
3
+ import logging, warnings
4
4
  from importlib import import_module
5
5
  from opentelemetry.sdk.trace.export import SpanExporter, ConsoleSpanExporter
6
6
  from monocle_apptrace.exporters.exporter_processor import LambdaExportTaskProcessor, is_aws_lambda_environment
@@ -34,7 +34,7 @@ def get_monocle_exporter(exporters_list:str=None) -> List[SpanExporter]:
34
34
  try:
35
35
  exporter_class_path = monocle_exporters[exporter_name]
36
36
  except KeyError:
37
- logger.debug(f"Unsupported Monocle span exporter '{exporter_name}', skipping.")
37
+ warnings.warn(f"Unsupported Monocle span exporter '{exporter_name}', skipping.")
38
38
  continue
39
39
  try:
40
40
  exporter_module = import_module(exporter_class_path["module"])
@@ -45,7 +45,7 @@ def get_monocle_exporter(exporters_list:str=None) -> List[SpanExporter]:
45
45
  else:
46
46
  exporters.append(exporter_class())
47
47
  except Exception as ex:
48
- logger.debug(
48
+ warnings.warn(
49
49
  f"Unable to initialize Monocle span exporter '{exporter_name}', error: {ex}. Using ConsoleSpanExporter as a fallback.")
50
50
  exporters.append(ConsoleSpanExporter())
51
51
  continue
@@ -6,7 +6,7 @@ import requests
6
6
  from opentelemetry.sdk.trace import ReadableSpan
7
7
  from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult, ConsoleSpanExporter
8
8
  from requests.exceptions import ReadTimeout
9
-
9
+ from monocle_apptrace.exporters.base_exporter import SpanExporterBase
10
10
  from monocle_apptrace.exporters.exporter_processor import ExportTaskProcessor
11
11
 
12
12
  REQUESTS_SUCCESS_STATUS_CODES = (200, 202)
@@ -15,7 +15,7 @@ OKAHU_PROD_INGEST_ENDPOINT = "https://ingest.okahu.co/api/v1/trace/ingest"
15
15
  logger = logging.getLogger(__name__)
16
16
 
17
17
 
18
- class OkahuSpanExporter(SpanExporter):
18
+ class OkahuSpanExporter(SpanExporterBase):
19
19
  def __init__(
20
20
  self,
21
21
  endpoint: Optional[str] = None,
@@ -24,6 +24,7 @@ class OkahuSpanExporter(SpanExporter):
24
24
  task_processor: ExportTaskProcessor = None
25
25
  ):
26
26
  """Okahu exporter."""
27
+ super().__init__()
27
28
  okahu_endpoint: str = os.environ.get("OKAHU_INGESTION_ENDPOINT", OKAHU_PROD_INGEST_ENDPOINT)
28
29
  self.endpoint = endpoint or okahu_endpoint
29
30
  api_key: str = os.environ.get("OKAHU_API_KEY")
@@ -58,6 +59,8 @@ class OkahuSpanExporter(SpanExporter):
58
59
 
59
60
  # append the batch object with all the spans object
60
61
  for span in spans:
62
+ if self.skip_export(span):
63
+ continue
61
64
  # create a object from serialized span
62
65
  obj = json.loads(span.to_json())
63
66
  if obj["parent_id"] is None:
@@ -51,7 +51,9 @@ llm_type_map = {
51
51
  "openaigenerator": "openai",
52
52
  "bedrockruntime":"aws_bedrock",
53
53
  "sagemakerruntime":"aws_sagemaker",
54
+ "anthropic": "anthropic",
54
55
  "chatanthropic":"anthropic",
56
+ "anthropicchatgenerator":"anthropic",
55
57
  }
56
58
 
57
59
  MONOCLE_INSTRUMENTOR = "monocle_apptrace"
@@ -74,4 +76,6 @@ WORKFLOW_TYPE_KEY = "monocle.workflow_type"
74
76
  ADD_NEW_WORKFLOW = "monocle.add_new_workflow"
75
77
  WORKFLOW_TYPE_GENERIC = "workflow.generic"
76
78
  MONOCLE_SDK_VERSION = "monocle_apptrace.version"
77
- MONOCLE_SDK_LANGUAGE = "monocle_apptrace.language"
79
+ MONOCLE_SDK_LANGUAGE = "monocle_apptrace.language"
80
+ MONOCLE_DETECTED_SPAN_ERROR = "monocle_apptrace.detected_span_error"
81
+ HTTP_SUCCESS_CODES = ('200', '201', '202', '204', '205', '206')
@@ -25,9 +25,9 @@ from monocle_apptrace.instrumentation.common.wrapper_method import (
25
25
  WrapperMethod,
26
26
  MONOCLE_SPAN_HANDLERS
27
27
  )
28
- from monocle_apptrace.instrumentation.common.wrapper import scope_wrapper, ascope_wrapper, wrapper_processor
28
+ from monocle_apptrace.instrumentation.common.wrapper import scope_wrapper, ascope_wrapper, monocle_wrapper, amonocle_wrapper
29
29
  from monocle_apptrace.instrumentation.common.utils import (
30
- set_scope, remove_scope, http_route_handler, load_scopes, async_wrapper, http_async_route_handler
30
+ set_scope, remove_scope, http_route_handler, load_scopes, http_async_route_handler
31
31
  )
32
32
  from monocle_apptrace.instrumentation.common.constants import MONOCLE_INSTRUMENTOR, WORKFLOW_TYPE_GENERIC
33
33
  from functools import wraps
@@ -68,13 +68,20 @@ class MonocleInstrumentor(BaseInstrumentor):
68
68
 
69
69
  def get_instrumentor(self, tracer):
70
70
  def instrumented_endpoint_invoke(to_wrap,wrapped, span_name, instance,fn):
71
- @wraps(fn)
72
- def with_instrumentation(*args, **kwargs):
73
- async_task = inspect.iscoroutinefunction(fn)
74
- boto_method_to_wrap = to_wrap.copy()
75
- boto_method_to_wrap['skip_span'] = False
76
- return wrapper_processor(async_task, tracer, NonFrameworkSpanHandler(),
77
- boto_method_to_wrap, fn, instance, args, kwargs)
71
+ if inspect.iscoroutinefunction(fn):
72
+ @wraps(fn)
73
+ async def with_instrumentation(*args, **kwargs):
74
+ boto_method_to_wrap = to_wrap.copy()
75
+ boto_method_to_wrap['skip_span'] = False
76
+ return await amonocle_wrapper(tracer, NonFrameworkSpanHandler(),
77
+ boto_method_to_wrap, fn, instance, "", args, kwargs)
78
+ else:
79
+ @wraps(fn)
80
+ def with_instrumentation(*args, **kwargs):
81
+ boto_method_to_wrap = to_wrap.copy()
82
+ boto_method_to_wrap['skip_span'] = False
83
+ return monocle_wrapper(tracer, NonFrameworkSpanHandler(),
84
+ boto_method_to_wrap, fn, instance, "", args, kwargs)
78
85
  return with_instrumentation
79
86
  return instrumented_endpoint_invoke
80
87
 
@@ -241,8 +248,8 @@ def start_trace():
241
248
  SpanHandler.set_workflow_properties(span)
242
249
  token = attach(updated_span_context)
243
250
  return token
244
- except:
245
- logger.warning("Failed to start trace")
251
+ except Exception as e:
252
+ logger.warning("Failed to start trace {e}")
246
253
  return None
247
254
 
248
255
  def stop_trace(token) -> None:
@@ -328,8 +335,12 @@ def monocle_trace_scope_method(scope_name: str, scope_value:str=None):
328
335
  if inspect.iscoroutinefunction(func):
329
336
  @wraps(func)
330
337
  async def wrapper(*args, **kwargs):
331
- result = async_wrapper(func, scope_name, scope_value, None, *args, **kwargs)
332
- return result
338
+ token = start_scope(scope_name, scope_value)
339
+ try:
340
+ result = await func(*args, **kwargs)
341
+ return result
342
+ finally:
343
+ stop_scope(token)
333
344
  return wrapper
334
345
  else:
335
346
  @wraps(func)
@@ -8,7 +8,7 @@ from monocle_apptrace.instrumentation.common.constants import (
8
8
  QUERY,
9
9
  service_name_map,
10
10
  service_type_map,
11
- MONOCLE_SDK_VERSION, MONOCLE_SDK_LANGUAGE
11
+ MONOCLE_SDK_VERSION, MONOCLE_SDK_LANGUAGE, MONOCLE_DETECTED_SPAN_ERROR
12
12
  )
13
13
  from monocle_apptrace.instrumentation.common.utils import set_attribute, get_scopes, MonocleSpanException, get_monocle_version
14
14
  from monocle_apptrace.instrumentation.common.constants import WORKFLOW_TYPE_KEY, WORKFLOW_TYPE_GENERIC
@@ -18,7 +18,8 @@ logger = logging.getLogger(__name__)
18
18
  WORKFLOW_TYPE_MAP = {
19
19
  "llama_index": "workflow.llamaindex",
20
20
  "langchain": "workflow.langchain",
21
- "haystack": "workflow.haystack"
21
+ "haystack": "workflow.haystack",
22
+ "teams.ai": "workflow.teams_ai",
22
23
  }
23
24
 
24
25
  class SpanHandler:
@@ -49,10 +50,11 @@ class SpanHandler:
49
50
  set_attribute(QUERY, args[0]['prompt_builder']['question'])
50
51
 
51
52
  @staticmethod
52
- def set_default_monocle_attributes(span: Span):
53
+ def set_default_monocle_attributes(span: Span, source_path = "" ):
53
54
  """ Set default monocle attributes for all spans """
54
55
  span.set_attribute(MONOCLE_SDK_VERSION, get_monocle_version())
55
56
  span.set_attribute(MONOCLE_SDK_LANGUAGE, "python")
57
+ span.set_attribute("span_source", source_path)
56
58
  for scope_key, scope_value in get_scopes().items():
57
59
  span.set_attribute(f"scope.{scope_key}", scope_value)
58
60
 
@@ -62,8 +64,6 @@ class SpanHandler:
62
64
  SpanHandler.set_workflow_attributes(to_wrap, span)
63
65
  SpanHandler.set_app_hosting_identifier_attribute(span)
64
66
 
65
- span.set_status(StatusCode.OK)
66
-
67
67
  @staticmethod
68
68
  def set_non_workflow_properties(span: Span, to_wrap = None):
69
69
  workflow_name = SpanHandler.get_workflow_name(span=span)
@@ -74,11 +74,14 @@ class SpanHandler:
74
74
  if span.status.status_code == StatusCode.UNSET:
75
75
  span.set_status(StatusCode.OK)
76
76
 
77
- def hydrate_span(self, to_wrap, wrapped, instance, args, kwargs, result, span):
78
- self.hydrate_attributes(to_wrap, wrapped, instance, args, kwargs, result, span)
79
- self.hydrate_events(to_wrap, wrapped, instance, args, kwargs, result, span)
77
+ def hydrate_span(self, to_wrap, wrapped, instance, args, kwargs, result, span) -> bool:
78
+ detected_error_in_attribute = self.hydrate_attributes(to_wrap, wrapped, instance, args, kwargs, result, span)
79
+ detected_error_in_event = self.hydrate_events(to_wrap, wrapped, instance, args, kwargs, result, span)
80
+ if detected_error_in_attribute or detected_error_in_event:
81
+ span.set_attribute(MONOCLE_DETECTED_SPAN_ERROR, True)
80
82
 
81
- def hydrate_attributes(self, to_wrap, wrapped, instance, args, kwargs, result, span):
83
+ def hydrate_attributes(self, to_wrap, wrapped, instance, args, kwargs, result, span:Span) -> bool:
84
+ detected_error:bool = False
82
85
  span_index = 0
83
86
  if SpanHandler.is_root_span(span):
84
87
  span_index = 2 # root span will have workflow and hosting entities pre-populated
@@ -86,7 +89,7 @@ class SpanHandler:
86
89
  'output_processor' in to_wrap and to_wrap["output_processor"] is not None):
87
90
  output_processor=to_wrap['output_processor']
88
91
  if 'type' in output_processor:
89
- span.set_attribute("span.type", output_processor['type'])
92
+ span.set_attribute("span.type", output_processor['type'])
90
93
  else:
91
94
  logger.warning("type of span not found or incorrect written in entity json")
92
95
  if 'attributes' in output_processor:
@@ -104,6 +107,7 @@ class SpanHandler:
104
107
  span.set_attribute(attribute_name, result)
105
108
  except MonocleSpanException as e:
106
109
  span.set_status(StatusCode.ERROR, e.message)
110
+ detected_error = True
107
111
  except Exception as e:
108
112
  logger.debug(f"Error processing accessor: {e}")
109
113
  else:
@@ -121,13 +125,14 @@ class SpanHandler:
121
125
 
122
126
  if span_index > 0:
123
127
  span.set_attribute("entity.count", span_index)
128
+ return detected_error
124
129
 
125
-
126
- def hydrate_events(self, to_wrap, wrapped, instance, args, kwargs, result, span):
130
+ def hydrate_events(self, to_wrap, wrapped, instance, args, kwargs, ret_result, span) -> bool:
131
+ detected_error:bool = False
127
132
  if not self.skip_processor(to_wrap, wrapped, instance, args, kwargs) and (
128
133
  'output_processor' in to_wrap and to_wrap["output_processor"] is not None):
129
134
  output_processor=to_wrap['output_processor']
130
- arguments = {"instance": instance, "args": args, "kwargs": kwargs, "result": result}
135
+ arguments = {"instance": instance, "args": args, "kwargs": kwargs, "result": ret_result}
131
136
  if 'events' in output_processor:
132
137
  events = output_processor['events']
133
138
  for event in events:
@@ -139,15 +144,25 @@ class SpanHandler:
139
144
  accessor = attribute.get("accessor")
140
145
  if accessor:
141
146
  try:
142
- if attribute_key is not None:
143
- event_attributes[attribute_key] = accessor(arguments)
144
- else:
145
- event_attributes.update(accessor(arguments))
147
+ result = accessor(arguments)
148
+ if result and isinstance(result, dict):
149
+ result = dict((key, value) for key, value in result.items() if value is not None)
150
+ if result and isinstance(result, (str, list, dict)):
151
+ if attribute_key is not None:
152
+ event_attributes[attribute_key] = result
153
+ else:
154
+ event_attributes.update(result)
146
155
  except MonocleSpanException as e:
147
156
  span.set_status(StatusCode.ERROR, e.message)
157
+ detected_error = True
148
158
  except Exception as e:
149
159
  logger.debug(f"Error evaluating accessor for attribute '{attribute_key}': {e}")
150
- span.add_event(name=event_name, attributes=event_attributes)
160
+ matching_timestamp = getattr(ret_result, "timestamps", {}).get(event_name, None)
161
+ if isinstance(matching_timestamp, int):
162
+ span.add_event(name=event_name, attributes=event_attributes, timestamp=matching_timestamp)
163
+ else:
164
+ span.add_event(name=event_name, attributes=event_attributes)
165
+ return detected_error
151
166
 
152
167
  @staticmethod
153
168
  def set_workflow_attributes(to_wrap, span: Span):
@@ -1,13 +1,13 @@
1
1
  import logging, json
2
2
  import os
3
+ import traceback
3
4
  from typing import Callable, Generic, Optional, TypeVar, Mapping
4
- import threading, asyncio
5
5
 
6
6
  from opentelemetry.context import attach, detach, get_current, get_value, set_value, Context
7
- from opentelemetry.trace import NonRecordingSpan, Span, get_tracer
7
+ from opentelemetry.trace import NonRecordingSpan, Span
8
8
  from opentelemetry.trace.propagation import _SPAN_KEY
9
9
  from opentelemetry.sdk.trace import id_generator, TracerProvider
10
- from opentelemetry.propagate import inject, extract
10
+ from opentelemetry.propagate import extract
11
11
  from opentelemetry import baggage
12
12
  from monocle_apptrace.instrumentation.common.constants import MONOCLE_SCOPE_NAME_PREFIX, SCOPE_METHOD_FILE, SCOPE_CONFIG_PATH, llm_type_map, MONOCLE_SDK_VERSION, ADD_NEW_WORKFLOW
13
13
  from importlib.metadata import version
@@ -17,7 +17,6 @@ U = TypeVar('U')
17
17
 
18
18
  logger = logging.getLogger(__name__)
19
19
 
20
- monocle_tracer_provider: TracerProvider = None
21
20
  embedding_model_context = {}
22
21
  scope_id_generator = id_generator.RandomIdGenerator()
23
22
  http_scopes:dict[str:str] = {}
@@ -43,14 +42,6 @@ class MonocleSpanException(Exception):
43
42
  """String representation of the exception."""
44
43
  return f"[Monocle Span Error: {self.message} {self.status}"
45
44
 
46
- def set_tracer_provider(tracer_provider: TracerProvider):
47
- global monocle_tracer_provider
48
- monocle_tracer_provider = tracer_provider
49
-
50
- def get_tracer_provider() -> TracerProvider:
51
- global monocle_tracer_provider
52
- return monocle_tracer_provider
53
-
54
45
  def set_span_attribute(span, name, value):
55
46
  if value is not None:
56
47
  if value != "":
@@ -93,7 +84,12 @@ def with_tracer_wrapper(func):
93
84
  except Exception as e:
94
85
  logger.error("Exception in attaching parent context: %s", e)
95
86
 
96
- val = func(tracer, handler, to_wrap, wrapped, instance, args, kwargs)
87
+ if traceback.extract_stack().__len__() > 2:
88
+ filename, line_number, _, _ = traceback.extract_stack()[-2]
89
+ source_path = f"{filename}:{line_number}"
90
+ else:
91
+ source_path = ""
92
+ val = func(tracer, handler, to_wrap, wrapped, instance, source_path, args, kwargs)
97
93
  return val
98
94
 
99
95
  return wrapper
@@ -275,49 +271,44 @@ async def http_async_route_handler(func, *args, **kwargs):
275
271
  headers = kwargs['req'].headers
276
272
  else:
277
273
  headers = None
278
- return async_wrapper(func, None, None, headers, *args, **kwargs)
279
-
280
- def run_async_with_scope(method, current_context, exceptions, *args, **kwargs):
281
- token = None
282
274
  try:
283
- if current_context:
284
- token = attach(current_context)
285
- return asyncio.run(method(*args, **kwargs))
286
- except Exception as e:
287
- exceptions['exception'] = e
288
- raise e
275
+ if headers is not None:
276
+ token = extract_http_headers(headers)
277
+ return await func(*args, **kwargs)
289
278
  finally:
290
- if token:
291
- detach(token)
279
+ if token is not None:
280
+ clear_http_scopes(token)
292
281
 
293
- def async_wrapper(method, scope_name=None, scope_value=None, headers=None, *args, **kwargs):
294
- try:
295
- run_loop = asyncio.get_running_loop()
296
- except RuntimeError:
297
- run_loop = None
298
-
299
- token = None
300
- exceptions = {}
301
- if scope_name:
302
- token = set_scope(scope_name, scope_value)
303
- elif headers:
304
- token = extract_http_headers(headers)
305
- current_context = get_current()
306
- try:
307
- if run_loop and run_loop.is_running():
308
- results = []
309
- thread = threading.Thread(target=lambda: results.append(run_async_with_scope(method, current_context, exceptions, *args, **kwargs)))
310
- thread.start()
311
- thread.join()
312
- if 'exception' in exceptions:
313
- raise exceptions['exception']
314
- return_value = results[0] if len(results) > 0 else None
315
- return return_value
316
- else:
317
- return run_async_with_scope(method, None, exceptions, *args, **kwargs)
318
- finally:
319
- if token:
320
- remove_scope(token)
282
+ # def run_async_with_scope(method, current_context, exceptions, *args, **kwargs):
283
+ # token = None
284
+ # try:
285
+ # if current_context:
286
+ # token = attach(current_context)
287
+ # return asyncio.run(method(*args, **kwargs))
288
+ # except Exception as e:
289
+ # exceptions['exception'] = e
290
+ # raise e
291
+ # finally:
292
+ # if token:
293
+ # detach(token)
294
+
295
+ # async def async_wrapper(method, headers=None, *args, **kwargs):
296
+ # current_context = get_current()
297
+ # try:
298
+ # if run_loop and run_loop.is_running():
299
+ # results = []
300
+ # thread = threading.Thread(target=lambda: results.append(run_async_with_scope(method, current_context, exceptions, *args, **kwargs)))
301
+ # thread.start()
302
+ # thread.join()
303
+ # if 'exception' in exceptions:
304
+ # raise exceptions['exception']
305
+ # return_value = results[0] if len(results) > 0 else None
306
+ # return return_value
307
+ # else:
308
+ # return run_async_with_scope(method, None, exceptions, *args, **kwargs)
309
+ # finally:
310
+ # if token:
311
+ # remove_scope(token)
321
312
 
322
313
  def get_monocle_version() -> str:
323
314
  global monocle_sdk_version
@@ -365,7 +356,24 @@ def try_option(func: Callable[..., T], *args, **kwargs) -> Option[T]:
365
356
  def get_llm_type(instance):
366
357
  try:
367
358
  t_name = type(instance).__name__.lower()
368
- llm_type = llm_type_map.get(type(instance).__name__.lower())
359
+ t_name = t_name.replace("async", "") if "async" in t_name else t_name
360
+ llm_type = llm_type_map.get(t_name)
369
361
  return llm_type
370
362
  except:
371
363
  pass
364
+
365
+ def patch_instance_method(obj, method_name, func):
366
+ """
367
+ Patch a special method (like __iter__) for a single instance.
368
+
369
+ Args:
370
+ obj: the instance to patch
371
+ method_name: the name of the method (e.g., '__iter__')
372
+ func: the new function, expecting (self, ...)
373
+ """
374
+ cls = obj.__class__
375
+ # Dynamically create a new class that inherits from obj's class
376
+ new_cls = type(f"Patched{cls.__name__}", (cls,), {
377
+ method_name: func
378
+ })
379
+ obj.__class__ = new_cls