Skip to content

Commit c1c9e19

Browse files
authored
[feat] Add hook extension for mem0 instrumentation (#95)
[feat] Add hook extension for mem0 instrumentation
2 parents 5142f2c + 3ae8446 commit c1c9e19

File tree

7 files changed

+1045
-17
lines changed

7 files changed

+1045
-17
lines changed

CHANGELOG-loongsuite.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121

2222
# Added
2323

24+
- `loongsuite-instrumentation-mem0`: add hook extension
25+
([#95](https://github.com/alibaba/loongsuite-python-agent/pull/95))
26+
2427
- `loongsuite-instrumentation-mem0`: add support for mem0
2528
([#67](https://github.com/alibaba/loongsuite-python-agent/pull/67))

instrumentation-loongsuite/loongsuite-instrumentation-mem0/src/opentelemetry/instrumentation/mem0/__init__.py

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
VectorStoreWrapper,
2424
)
2525
from opentelemetry.instrumentation.mem0.package import _instruments
26+
from opentelemetry.instrumentation.mem0.types import set_memory_hooks
2627
from opentelemetry.instrumentation.mem0.version import __version__
2728
from opentelemetry.instrumentation.utils import unwrap
2829
from opentelemetry.semconv.schemas import Schemas
@@ -127,6 +128,12 @@ def _instrument(self, **kwargs: Any) -> None:
127128
# Optional: logger provider for GenAI events (util will no-op if not provided)
128129
logger_provider = kwargs.get("logger_provider")
129130

131+
# Optional hooks for extensions (e.g. commercial metrics). We only pass through.
132+
memory_before_hook = kwargs.get("memory_before_hook")
133+
memory_after_hook = kwargs.get("memory_after_hook")
134+
inner_before_hook = kwargs.get("inner_before_hook")
135+
inner_after_hook = kwargs.get("inner_after_hook")
136+
130137
# Create util GenAI handler (strong dependency, no fallback).
131138
# Avoid singleton here so tests (and multiple tracer providers) don't leak across runs.
132139
telemetry_handler = ExtendedTelemetryHandler(
@@ -144,13 +151,33 @@ def _instrument(self, **kwargs: Any) -> None:
144151
)
145152

146153
# Execute instrumentation (traces only, metrics removed)
147-
self._instrument_memory_operations(telemetry_handler)
148-
self._instrument_memory_client_operations(telemetry_handler)
154+
self._instrument_memory_operations(
155+
telemetry_handler,
156+
memory_before_hook=memory_before_hook,
157+
memory_after_hook=memory_after_hook,
158+
)
159+
self._instrument_memory_client_operations(
160+
telemetry_handler,
161+
memory_before_hook=memory_before_hook,
162+
memory_after_hook=memory_after_hook,
163+
)
149164
# Sub-phases controlled by toggle, avoid binding wrapper when disabled to reduce overhead
150165
if mem0_config.is_internal_phases_enabled():
151-
self._instrument_vector_operations(tracer)
152-
self._instrument_graph_operations(tracer)
153-
self._instrument_reranker_operations(tracer)
166+
self._instrument_vector_operations(
167+
tracer,
168+
inner_before_hook=inner_before_hook,
169+
inner_after_hook=inner_after_hook,
170+
)
171+
self._instrument_graph_operations(
172+
tracer,
173+
inner_before_hook=inner_before_hook,
174+
inner_after_hook=inner_after_hook,
175+
)
176+
self._instrument_reranker_operations(
177+
tracer,
178+
inner_before_hook=inner_before_hook,
179+
inner_after_hook=inner_after_hook,
180+
)
154181

155182
def _uninstrument(self, **kwargs: Any) -> None:
156183
"""Remove instrumentation."""
@@ -392,7 +419,13 @@ def _wrapper(wrapped, instance, args, kwargs):
392419

393420
return _wrapper
394421

395-
def _instrument_memory_operations(self, telemetry_handler):
422+
def _instrument_memory_operations(
423+
self,
424+
telemetry_handler,
425+
*,
426+
memory_before_hook=None,
427+
memory_after_hook=None,
428+
):
396429
"""Instrument Memory and AsyncMemory operations."""
397430
try:
398431
if (
@@ -407,6 +440,11 @@ def _instrument_memory_operations(self, telemetry_handler):
407440
return
408441

409442
wrapper = MemoryOperationWrapper(telemetry_handler)
443+
set_memory_hooks(
444+
wrapper,
445+
memory_before_hook=memory_before_hook,
446+
memory_after_hook=memory_after_hook,
447+
)
410448

411449
# Instrument Memory (sync)
412450
for method in self._public_methods_of(
@@ -444,7 +482,13 @@ def _instrument_memory_operations(self, telemetry_handler):
444482
except Exception as e:
445483
logger.debug(f"Failed to instrument Memory operations: {e}")
446484

447-
def _instrument_memory_client_operations(self, telemetry_handler):
485+
def _instrument_memory_client_operations(
486+
self,
487+
telemetry_handler,
488+
*,
489+
memory_before_hook=None,
490+
memory_after_hook=None,
491+
):
448492
"""Instrument MemoryClient and AsyncMemoryClient operations."""
449493
try:
450494
if (
@@ -459,6 +503,11 @@ def _instrument_memory_client_operations(self, telemetry_handler):
459503
return
460504

461505
wrapper = MemoryOperationWrapper(telemetry_handler)
506+
set_memory_hooks(
507+
wrapper,
508+
memory_before_hook=memory_before_hook,
509+
memory_after_hook=memory_after_hook,
510+
)
462511

463512
# Instrument MemoryClient (sync)
464513
for method in self._public_methods_of(
@@ -594,7 +643,13 @@ def _factory_wrapper(wrapped, instance, args, kwargs):
594643
except Exception as e:
595644
logger.debug(f"Failed to wrap {factory_class}.create: {e}")
596645

597-
def _instrument_vector_operations(self, tracer):
646+
def _instrument_vector_operations(
647+
self,
648+
tracer,
649+
*,
650+
inner_before_hook=None,
651+
inner_after_hook=None,
652+
):
598653
"""Instrument VectorStore operations."""
599654
try:
600655
# Require both VectorStoreBase and VectorStoreFactory to be available
@@ -632,7 +687,11 @@ def _instrument_vector_operations(self, tracer):
632687
]
633688

634689
# Create VectorStoreWrapper instance (trace-only)
635-
vector_wrapper = VectorStoreWrapper(tracer)
690+
vector_wrapper = VectorStoreWrapper(
691+
tracer,
692+
inner_before_hook=inner_before_hook,
693+
inner_after_hook=inner_after_hook,
694+
)
636695

637696
# Use generic factory wrapping method
638697
self._wrap_factory_for_phase(
@@ -647,7 +706,13 @@ def _instrument_vector_operations(self, tracer):
647706
except Exception as e:
648707
logger.debug(f"Failed to instrument vector store operations: {e}")
649708

650-
def _instrument_graph_operations(self, tracer):
709+
def _instrument_graph_operations(
710+
self,
711+
tracer,
712+
*,
713+
inner_before_hook=None,
714+
inner_after_hook=None,
715+
):
651716
"""Instrument GraphStore operations."""
652717
try:
653718
# If factories are unavailable, graph subphase instrumentation cannot be enabled
@@ -687,7 +752,11 @@ def _instrument_graph_operations(self, tracer):
687752
]
688753

689754
# Create GraphStoreWrapper instance (trace-only)
690-
graph_wrapper = GraphStoreWrapper(tracer)
755+
graph_wrapper = GraphStoreWrapper(
756+
tracer,
757+
inner_before_hook=inner_before_hook,
758+
inner_after_hook=inner_after_hook,
759+
)
691760

692761
# Use generic factory wrapping method
693762
self._wrap_factory_for_phase(
@@ -702,7 +771,13 @@ def _instrument_graph_operations(self, tracer):
702771
except Exception as e:
703772
logger.debug(f"Failed to instrument graph store operations: {e}")
704773

705-
def _instrument_reranker_operations(self, tracer):
774+
def _instrument_reranker_operations(
775+
self,
776+
tracer,
777+
*,
778+
inner_before_hook=None,
779+
inner_after_hook=None,
780+
):
706781
"""Instrument Reranker operations."""
707782
try:
708783
if not _FACTORIES_AVAILABLE or RerankerFactory is None:
@@ -713,7 +788,11 @@ def _instrument_reranker_operations(self, tracer):
713788
return
714789

715790
# Create RerankerWrapper instance (trace-only)
716-
reranker_wrapper = RerankerWrapper(tracer)
791+
reranker_wrapper = RerankerWrapper(
792+
tracer,
793+
inner_before_hook=inner_before_hook,
794+
inner_after_hook=inner_after_hook,
795+
)
717796

718797
# Use generic factory wrapping method
719798
self._wrap_factory_for_phase(

0 commit comments

Comments
 (0)