feat(otel): OpenTelemetry metrics + span instrumentation for core mem… - #1718
feat(otel): OpenTelemetry metrics + span instrumentation for core mem…#1718henrikrexed wants to merge 3 commits into
Conversation
…ory operations Add an optional OTel metrics layer mirroring the existing tracer.py NoOp/OTel dual-impl pattern, and extend span coverage to search/search_/remove_episode. - graphiti_core/meter.py (new): GraphitiMeter ABC, NoOpMeter, OpenTelemetryMeter, create_meter(); instruments memory.operation.duration/count, memory.items.stored/invalidated, memory.query.result_count. - graphiti_core/graphiti.py: wire meter= param; add spans+metrics to search/search_/remove_episode; add stored/invalidated metrics to add_episode(_bulk). - graphiti_core/tracer.py: guard only span *creation* so an op that raises inside a span surfaces the real error instead of RuntimeError from the generator. - OTEL_TRACING.md: document the metrics layer. - docs/rfc-otel-instrumentation.md, docs/upstream-pr-description.md: RFC + PR body. Default meter=None -> NoOpMeter: zero behaviour change, no new required deps. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
I have read the CLA Document and I hereby sign the CLA behalf on myself, e-mail: example@example.com or I have read the CLA Document and I hereby sign the CLA behalf of my company, e-mail: example@example.com Signature is valid for 6 months. This bot will be retriggered when the Contributor License Agreement comment has been provided. Posted by the CLA Assistant Lite bot. |
…p#1718 - meter.py: drop unused UpDownCounter import (F401); unquote instance-attr type annotations (UP037, safe — PEP 526 does not evaluate self.x: T); keep the meter: 'Meter' param annotation quoted for no-OTel import safety; convert the query-results try/except/pass to contextlib.suppress (SIM105). - graphiti.py: ruff import-sort (I001) + format. Co-Authored-By: Paperclip <noreply@paperclip.ing>
|
I have read the CLA Document and I hereby sign the CLA behalf on myself, e-mail: henrik.rexed@dynatrace.com |
Summary
Adds optional OpenTelemetry metrics and extends span instrumentation to Graphiti's core memory
operations. Users who run Graphiti with an OTel SDK can now observe latency, volume, and query
efficiency of
add_episode,search,search_, andremove_episodein their existing backends(Jaeger/Tempo for traces; Prometheus/Grafana/Datadog/Honeycomb for metrics). With no OTel meter
passed, everything is a no-op — zero behaviour change and no new required dependencies for
existing users.
This mirrors the design of the existing
graphiti_core/tracer.pyabstraction, so the meter followsthe same NoOp/OTel dual-impl discipline already established in the codebase.
Motivation
Graphiti is increasingly used as the production memory layer for AI agents. Operators currently
cannot attribute latency to specific Graphiti operations or distinguish LLM calls from graph writes,
and there is no metrics signal at all. This PR closes that gap:
Design
Dual-impl, zero-dependency pattern
GraphitiMeterABC withNoOpMeter(default, no deps) andOpenTelemetryMeter(active when the caller passes an
opentelemetry.metrics.Meter).create_meter(otel_meter=None)factory returnsNoOpMeterwhenotel_meter is Noneor whenopentelemetry-apiis not installed. All instrumentation errors are suppressed — telemetry neverbreaks a memory operation.
Graphiti.__init__gains an optionalmeter: GraphitiMeter | None = Noneparameter alongside theexisting
tracer. DefaultNone→NoOpMeter.
Metric instruments (
memory.*, OTel naming)
memory.operation.durationmsmemory.operation.namememory.operation.count1memory.operation.name,memory.operation.status(ok/error)memory.items.stored1memory.item.type(node/edge/episode)memory.items.invalidated1memory.item.typememory.query.result_count1memory.operation.nameSpans + metrics per operation
add_episodeadd_episode_bulksearchsearch_remove_episodeExample usage
Files changed (upstream scope)
graphiti_core/meter.pyGraphitiMeterABC,NoOpMeter,OpenTelemetryMeter,create_meter()graphiti_core/graphiti.pymeterparam; add metric calls; add spans tosearch/search_/remove_episodegraphiti_core/__init__.pyOTEL_TRACING.mdpyproject.tomltracingextra already providesopentelemetry-api/-sdk; no new required depTesting
make check(Ruff + Pyright + Pytest) passes; no existing tests modified.OTel Collector into a backend (18 metric data points + 5 spans observed). Existing users with no
meterargument exercise theNoOpMeterpath — confirmed zero overhead / no new deps.
Backwards compatibility
meter=None→NoOpMeter: no behaviour change.opentelemetry-apiremains optional (only pulled by the existingtracingextra).
Open questions for reviewers
memory.query.result_countstay aHistogram(percentiles) or become anUpDownCounter?add_episode/search/ ... the desiredmemory.operation.nameattribute values, or shouldthey map to a specific semconv registry string?
meterparam through the MCP/FastAPI server layers later (this PRinstruments only the core library)?