Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/tests/monitoring/test_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,3 +421,57 @@ def my_workflow():
my_workflow()

mock_alerter.notify.assert_called_once()


def test_reserved_keys_excluded_from_cache_vector(mock_tracer_deps, monkeypatch):
"""
Regression: the @vectorize decorator injects bookkeeping keys
(function_uuid, exec_source, trace_id) into call kwargs. Those must NOT
leak into the vectorized text — otherwise a stored execution embeds a
different input than the cache-lookup path (which only sees the caller's
own args) and the semantic cache can never hit at a sane threshold.
See tracer._RESERVED_VECTOR_KEYS.
"""
from vectorwave.monitoring.tracer import _create_input_vector_data

class _SpyVectorizer:
def __init__(self):
self.texts = []

def embed(self, text):
self.texts.append(text)
return [0.0] * 8

spy = _SpyVectorizer()
monkeypatch.setattr(f"{TRACER_MODULE_PATH}.get_vectorizer",
MagicMock(return_value=spy))

@trace_root()
@trace_span(capture_return_value=True)
def summarize(text, **_injected):
return f"summary of {text}"

summarize(
"hello world",
function_uuid="uuid-should-not-leak",
exec_source="REALTIME",
trace_id="trace-should-not-leak",
)

assert spy.texts, "vectorizer.embed was never called for the successful span"
stored_text = spy.texts[-1]

# Neither the reserved key names nor their values may appear in the vector.
for leaked in ("function_uuid", "exec_source",
"uuid-should-not-leak", "REALTIME", "trace-should-not-leak"):
assert leaked not in stored_text, f"{leaked!r} leaked into cache vector text"

# The stored text must equal what the cache-lookup path builds from the
# caller's own args — so identical inputs embed to identical vectors.
expected = _create_input_vector_data(
func_name="summarize",
args=("hello world",),
kwargs={},
sensitive_keys=mock_tracer_deps["settings"].sensitive_keys,
)["text"]
assert stored_text == expected
17 changes: 16 additions & 1 deletion src/vectorwave/monitoring/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ def _create_span_properties(
return span_properties


# Keys the @vectorize decorator injects into call kwargs for its own bookkeeping.
# They must be excluded from the vectorized text so a stored execution embeds the
# same input as the cache-lookup path (which only sees the caller's own args).
_RESERVED_VECTOR_KEYS = frozenset({"function_uuid", "exec_source", "trace_id"})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve user trace_id when vectorizing cached inputs

When a @vectorize(semantic_cache=True) function actually declares trace_id as a parameter, _init_trace_root intentionally leaves that caller argument in kwargs, and the cache-lookup path embeds the original kwargs via _check_and_return_cached_result. Adding trace_id to this stripped set means the stored execution vector drops a legitimate input while lookup vectors still include it, so those functions either stop hitting the semantic cache or can compare against vectors that ignored an input the function may depend on. trace_id should only be stripped when it was consumed as tracer metadata, not from user-owned function arguments.

Useful? React with 👍 / 👎.



def _create_input_vector_data(
func_name: str,
args: tuple,
Expand Down Expand Up @@ -240,10 +246,19 @@ def _perform_background_logging(ctx: SpanContext):
if vectorizer is not None:
try:
if ctx.status == "SUCCESS" and ctx.capture_return_value:
# The cache lookup embeds only the caller's original args/kwargs.
# Match it here by stripping the keys the decorator injects
# (function_uuid, exec_source, trace_id) — otherwise the stored
# vector never aligns with the lookup vector and the semantic
# cache can never hit at a sane threshold.
clean_kwargs = {
k: v for k, v in ctx.kwargs.items()
if k not in _RESERVED_VECTOR_KEYS
}
input_vector_data = _create_input_vector_data(
func_name=ctx.func.__name__,
args=ctx.args,
kwargs=ctx.kwargs,
kwargs=clean_kwargs,
sensitive_keys=ctx.tracer.settings.sensitive_keys
)
vector_to_add = vectorizer.embed(input_vector_data['text'])
Expand Down
Loading