Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,70 @@ public async Task GetNearestMatchAsyncReturnsExpectedAsync()
Assert.True(topNResult.Value.Item2 >= threshold);
}

[Fact]
public async Task GetNearestMatchesUsesCosineSimilarityForOppositeMeaningAsync()
{
// Arrange
var queryEmbedding = new float[] { 1, 0 };
string collection = "test_collection" + this._collectionNum;
this._collectionNum++;
await this._db.CreateCollectionAsync(collection);

// These text/embedding pairs deliberately model the high-similarity range
// reported by the issue's embedding audit; this unit test does not compute
// embeddings and therefore does not claim to reproduce a model's output.
_ = await this._db.UpsertAsync(collection, MemoryRecord.LocalRecord(
id: "matching",
text: "Withhold the study drug when chest tightness is reported.",
description: "matching polarity",
embedding: new float[] { 0.98f, 0.199f }));
_ = await this._db.UpsertAsync(collection, MemoryRecord.LocalRecord(
id: "opposite",
text: "Administer the study drug when chest tightness is reported.",
description: "opposite polarity",
embedding: new float[] { 0.98f, 0.199f }));

// Act
var results = await this._db.GetNearestMatchesAsync(
collection,
queryEmbedding,
limit: 2,
minRelevanceScore: 0.75).ToArrayAsync();

// Assert
Assert.Equal(2, results.Length);
Assert.Contains(results, result => result.Item1.Metadata.Id == "matching");
Assert.Contains(results, result => result.Item1.Metadata.Id == "opposite");
Assert.All(results, result => Assert.True(result.Item2 >= 0.75));
}

[Fact]
public async Task GetNearestMatchesFiltersLowSimilarityParaphraseAsync()
{
// Arrange
var queryEmbedding = new float[] { 1, 0 };
string collection = "test_collection" + this._collectionNum;
this._collectionNum++;
await this._db.CreateCollectionAsync(collection);

_ = await this._db.UpsertAsync(collection, MemoryRecord.LocalRecord(
id: "paraphrase",
text: "Do not administer the medication if the participant reports chest tightness.",
description: "semantically equivalent paraphrase",
embedding: new float[] { 0.6f, 0.8f }));

// Act
var results = await this._db.GetNearestMatchesAsync(
collection,
queryEmbedding,
limit: 1,
minRelevanceScore: 0.75).ToArrayAsync();

// Assert: a fixed similarity threshold cannot recover a semantically
// equivalent result whose embedding falls below that threshold.
Assert.Empty(results);
}

[Fact]
public async Task GetNearestMatchesDifferentiatesIdenticalVectorsByKeyAsync()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Copyright (c) Microsoft. All rights reserved.
"""Regenerate the offline relevance fixture from a local LM Studio server."""

import json
import struct
import urllib.request
from datetime import UTC, datetime
from pathlib import Path

MODEL = "text-embedding-nomic-embed-text-v1.5"
CHECKPOINT = "nomic-ai/nomic-embed-text-v1.5"
ENDPOINT = "http://127.0.0.1:1234/v1/embeddings"
TEXTS = {
"stored": "Before deleting user data, require explicit confirmation.",
"negated_query": "Before deleting user data, do not require explicit confirmation.",
"paraphrase_stored": "Erase records only after the subject has authorized the action.",
"paraphrase_query": "Obtain affirmative consent from the person before erasing any of their personal information.",
}
OUTPUT = Path(__file__).with_name("recorded_nomic_relevance_embeddings.json")


def embed(text: str) -> list[float]:
request = urllib.request.Request(
ENDPOINT,
data=json.dumps({"model": MODEL, "input": text}).encode(),
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(request) as response:
return json.load(response)["data"][0]["embedding"]


def cosine(left: list[float], right: list[float]) -> float:
dot = sum(a * b for a, b in zip(left, right, strict=True))
left_norm = sum(value * value for value in left) ** 0.5
right_norm = sum(value * value for value in right) ** 0.5
return dot / (left_norm * right_norm)


vectors = {name: embed(text) for name, text in TEXTS.items()}
OUTPUT.write_text(
json.dumps(
{
"model": MODEL,
"checkpoint": CHECKPOINT,
"endpoint": ENDPOINT,
"generator": "python tests/unit/memory/generate_recorded_nomic_relevance_embeddings.py",
"generated_at_utc": datetime.now(UTC).isoformat(),
"dimension": len(vectors["stored"]),
"float_format": "IEEE-754 float32",
"texts": TEXTS,
"cosine_similarity": {
"stored_to_negated_query": cosine(vectors["stored"], vectors["negated_query"]),
"paraphrase_stored_to_query": cosine(vectors["paraphrase_stored"], vectors["paraphrase_query"]),
},
"vectors": {
name: list(struct.unpack(f"<{len(vector)}f", struct.pack(f"<{len(vector)}f", *vector)))
for name, vector in vectors.items()
},
},
indent=2,
)
+ "\n"
)
Loading
Loading