Skip to content
Open
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
453 changes: 331 additions & 122 deletions libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py

Large diffs are not rendered by default.

3,527 changes: 2,277 additions & 1,250 deletions libs/oracledb/poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion libs/oracledb/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langchain-oracledb"
version = "1.0.2"
version = "1.0.3"
description = "An integration package connecting Oracle Database and LangChain"
authors = []
readme = "README.md"
Expand Down Expand Up @@ -28,6 +28,7 @@ syrupy = "^4.0.2"
pytest-asyncio = "^0.23.2"
pytest-watcher = "^0.3.4"
sentence-transformers = "^5.0.0"
langchain-tests = "^0.3.21"

[tool.poetry.group.codespell]
optional = true
Expand Down
2 changes: 2 additions & 0 deletions libs/oracledb/tests/integration_tests/embeddings/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) 2025 Oracle and/or its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
32 changes: 32 additions & 0 deletions libs/oracledb/tests/integration_tests/embeddings/test_standard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import Type

import oracledb
import pytest
from langchain_tests.integration_tests import EmbeddingsIntegrationTests

from langchain_oracledb import OracleEmbeddings

username = ""
password = ""
dsn = ""

try:
oracledb.connect(user=username, password=password, dsn=dsn)
except Exception as e:
pytest.skip(
allow_module_level=True,
reason=f"Database connection failed: {e}, skipping tests.",
)


class TestOracleEmbeddingsModelIntegration(EmbeddingsIntegrationTests):
@property
def embeddings_class(self) -> Type[OracleEmbeddings]:
# Return the embeddings model class to test here
return OracleEmbeddings

@property
def embedding_model_params(self) -> dict:
# Return initialization parameters for the model.
conn = oracledb.connect(user=username, password=password, dsn=dsn)
return {"conn": conn, "params": {"provider": "database", "model": "allminilm"}}
144 changes: 98 additions & 46 deletions libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
import sys
import threading

import numpy as np
import oracledb
import pytest
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores.utils import DistanceStrategy

from langchain_oracledb.embeddings import OracleEmbeddings
from langchain_oracledb.vectorstores.oraclevs import (
INTERNAL_ID_KEY,
OracleVS,
_acreate_table,
_aindex_exists,
Expand Down Expand Up @@ -1164,6 +1166,7 @@ def test_add_texts_test() -> None:
vs_obj = OracleVS(connection, model, "TB7", DistanceStrategy.EUCLIDEAN_DISTANCE)
ids6 = ['"Good afternoon"', '"India"']
vs_obj.add_texts(texts2, ids=ids6)
assert len(vs_obj.add_texts(texts2, ids=ids6)) == 0
drop_table_purge(connection, "TB7")

# 4. Add records with ids and metadatas
Expand Down Expand Up @@ -1207,30 +1210,6 @@ def add(val: str) -> None:
thread_2.join()
drop_table_purge(connection, "TB10")

# 7. Add 2 same record concurrently
# Expectation:Successful, For one of the insert,get primary key violation error
def add1(val: str) -> None:
model = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-mpnet-base-v2"
)
vs_obj = OracleVS(
connection, model, "TB11", DistanceStrategy.EUCLIDEAN_DISTANCE
)
texts = [val]
ids10 = texts
vs_obj.add_texts(texts, ids=ids10)

try:
thread_1 = threading.Thread(target=add1, args=("Sri Ram"))
thread_2 = threading.Thread(target=add1, args=("Sri Ram"))
thread_1.start()
thread_2.start()
thread_1.join()
thread_2.join()
except Exception:
pass
drop_table_purge(connection, "TB11")

# 8. create object with table name of type <schema_name.table_name>
# Expectation:U1 does not exist
with pytest.raises(RuntimeError):
Expand Down Expand Up @@ -1316,6 +1295,7 @@ async def test_add_texts_test_async() -> None:
)
ids6 = ['"Good afternoon"', '"India"']
await vs_obj.aadd_texts(texts2, ids=ids6)
assert len(await vs_obj.aadd_texts(texts2, ids=ids6)) == 0
await adrop_table_purge(connection, "TB7")

# 4. Add records with ids and metadatas
Expand Down Expand Up @@ -1361,26 +1341,6 @@ async def add(val: str) -> None:
await asyncio.gather(task_1, task_2)
await adrop_table_purge(connection, "TB10")

# 7. Add 2 same record concurrently
# Expectation:Successful, For one of the insert,get primary key violation error
async def add1(val: str) -> None:
model = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-mpnet-base-v2"
)
vs_obj = await OracleVS.acreate(
connection, model, "TB11", DistanceStrategy.EUCLIDEAN_DISTANCE
)
texts = [val]
ids10 = texts
await vs_obj.aadd_texts(texts, ids=ids10)

with pytest.raises(RuntimeError):
task_1 = asyncio.create_task(add1("Sri Ram"))
task_2 = asyncio.create_task(add1("Sri Ram"))
await asyncio.gather(task_1, task_2)

await adrop_table_purge(connection, "TB11")

# 8. create object with table name of type <schema_name.table_name>
# Expectation:U1 does not exist
with pytest.raises(RuntimeError):
Expand Down Expand Up @@ -1694,7 +1654,9 @@ def test_perform_search_test() -> None:
vs.similarity_search(query, 2, filter=db_filter)

# Similarity search with relevance score
vs.similarity_search_with_score(query, 2)
res = vs.similarity_search_with_score(query, 2)
assert all(isinstance(_r[1], float) for _r in res)
assert res[0][1] <= res[1][1]

# Similarity search with relevance score with filter
vs.similarity_search_with_score(query, 2, filter=db_filter)
Expand Down Expand Up @@ -1786,7 +1748,9 @@ async def test_perform_search_test_async() -> None:
await vs.asimilarity_search(query, 2, filter=db_filter)

# Similarity search with relevance score
await vs.asimilarity_search_with_score(query, 2)
res = await vs.asimilarity_search_with_score(query, 2)
assert all(isinstance(_r[1], float) for _r in res)
assert res[0][1] <= res[1][1]

# Similarity search with relevance score with filter
await vs.asimilarity_search_with_score(query, 2, filter=db_filter)
Expand Down Expand Up @@ -2517,6 +2481,14 @@ def test_oracle_embeddings() -> None:
res = vs_obj.similarity_search("database", 1)

assert "Database" in res[0].page_content
assert "100" == res[0].id

embedding = model.embed_query("Database Document")
res = vs_obj.similarity_search_by_vector_returning_embeddings(embedding, 1) # type: ignore

# distance
assert all(np.isclose([res[0][1]], [0])) # type: ignore
assert all(np.isclose(res[0][2], embedding)) # type: ignore

drop_table_purge(connection, "TB1")

Expand Down Expand Up @@ -2555,6 +2527,14 @@ async def test_oracle_embeddings_async(caplog: pytest.LogCaptureFixture) -> None
res = await vs_obj.asimilarity_search("database", 1)

assert "Database" in res[0].page_content
assert "100" == res[0].id

embedding = model.embed_query("Database Document")
res = await vs_obj.asimilarity_search_by_vector_returning_embeddings(embedding, 1) # type: ignore

# distance
assert all(np.isclose([res[0][1]], [0])) # type: ignore
assert all(np.isclose(res[0][2], embedding)) # type: ignore

await adrop_table_purge(connection, "TB1")

Expand Down Expand Up @@ -2986,3 +2966,75 @@ def model1(_) -> list[float]: # type: ignore[no-untyped-def]
result = await vs.asimilarity_search("Hello", k=3, filter=_f)

await adrop_table_purge(connection, "TB10")


##################################
####### test_reserved ######
##################################


def test_reserved() -> None:
try:
connection = oracledb.connect(user=username, password=password, dsn=dsn)
except Exception:
sys.exit(1)

drop_table_purge(connection, "TB1")

embedder_params = {"provider": "database", "model": "allminilm"}
proxy = ""

# instance
model = OracleEmbeddings(conn=connection, params=embedder_params, proxy=proxy)

vs_obj = OracleVS(connection, model, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE)

texts = ["Database Document", "Code Document"]
metadata = [
{"id": "100", "link": "Document Example Test 1", INTERNAL_ID_KEY: "my_temp_id"},
{"id": "101", "link": "Document Example Test 2"},
]

with pytest.raises(ValueError, match="reserved"):
vs_obj.add_texts(texts, metadata, ids=["1", "2"])

drop_table_purge(connection, "TB1")

connection.close()


@pytest.mark.asyncio
async def test_reserved_async() -> None:
try:
connection = await oracledb.connect_async(
user=username, password=password, dsn=dsn
)

connection_sync = oracledb.connect(user=username, password=password, dsn=dsn)
except Exception:
sys.exit(1)

await adrop_table_purge(connection, "TB1")

embedder_params = {"provider": "database", "model": "allminilm"}
proxy = ""

# instance
model = OracleEmbeddings(conn=connection_sync, params=embedder_params, proxy=proxy)

vs_obj = await OracleVS.acreate(
connection, model, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE
)

texts = ["Database Document", "Code Document"]
metadata = [
{"id": "100", "link": "Document Example Test 1", INTERNAL_ID_KEY: "my_temp_id"},
{"id": "101", "link": "Document Example Test 2"},
]

with pytest.raises(ValueError, match="reserved"):
await vs_obj.aadd_texts(texts, metadata, ids=["1", "2"])

await adrop_table_purge(connection, "TB1")

connection.close()
120 changes: 120 additions & 0 deletions libs/oracledb/tests/integration_tests/vectorstores/test_standard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
from collections.abc import Generator
from typing import AsyncGenerator

import oracledb
import pytest
import pytest_asyncio
from langchain_core.vectorstores import VectorStore
from langchain_tests.integration_tests.vectorstores import VectorStoreIntegrationTests

from langchain_oracledb import OracleVS
from langchain_oracledb.embeddings import OracleEmbeddings
from langchain_oracledb.vectorstores.oraclevs import adrop_table_purge, drop_table_purge

username = ""
password = ""
dsn = ""

try:
oracledb.connect(user=username, password=password, dsn=dsn)
except Exception as e:
pytest.skip(
allow_module_level=True,
reason=f"Database connection failed: {e}, skipping tests.",
)


class TestOracleVSStandardSync(VectorStoreIntegrationTests):
@property
def has_async(self) -> bool:
"""Configurable property to enable or disable sync tests."""
return False

@pytest.fixture()
def vectorstore(self) -> Generator[VectorStore, None, None]: # type: ignore[override]
"""Get an empty vectorstore for unit tests."""
conn = oracledb.connect(user=username, password=password, dsn=dsn)
drop_table_purge(conn, "standard_tests")
store = OracleVS(
conn,
embedding_function=self.get_embeddings(),
table_name="standard_tests",
mutate_on_duplicate=True,
)
yield store


class TestOracleVSOracleEmbeddingsStandardSync(VectorStoreIntegrationTests):
@property
def has_async(self) -> bool:
"""Configurable property to enable or disable sync tests."""
return False

@pytest.fixture()
def vectorstore(self) -> Generator[VectorStore, None, None]: # type: ignore[override]
"""Get an empty vectorstore for unit tests."""
conn = oracledb.connect(user=username, password=password, dsn=dsn)
drop_table_purge(conn, "standard_tests")
embedder_params = {"provider": "database", "model": "allminilm"}
proxy = ""

# instance
model = OracleEmbeddings(conn=conn, params=embedder_params, proxy=proxy)

store = OracleVS(
conn,
embedding_function=model,
table_name="standard_tests",
mutate_on_duplicate=True,
)
yield store


class TestOracleVSStandardAsync(VectorStoreIntegrationTests):
@property
def has_sync(self) -> bool:
"""Configurable property to enable or disable sync tests."""
return False

@pytest_asyncio.fixture
async def vectorstore(self) -> AsyncGenerator[VectorStore, None]:
"""Get an empty vectorstore for unit tests (async version)."""

conn = await oracledb.connect_async(user=username, password=password, dsn=dsn)
await adrop_table_purge(conn, "standard_tests")

store = await OracleVS.acreate(
conn,
embedding_function=self.get_embeddings(),
table_name="standard_tests",
mutate_on_duplicate=True,
)
yield store


class TestOracleVSOracleEmbeddingsStandardAsync(VectorStoreIntegrationTests):
@property
def has_sync(self) -> bool:
"""Configurable property to enable or disable sync tests."""
return False

@pytest_asyncio.fixture
async def vectorstore(self) -> AsyncGenerator[VectorStore, None]:
"""Get an empty vectorstore for unit tests (async version)."""

conn = await oracledb.connect_async(user=username, password=password, dsn=dsn)
await adrop_table_purge(conn, "standard_tests")
embedder_params = {"provider": "database", "model": "allminilm"}
proxy = ""

# OracleEmbeddings does not support asyncconnection
conn_syn = oracledb.connect(user=username, password=password, dsn=dsn)
model = OracleEmbeddings(conn=conn_syn, params=embedder_params, proxy=proxy)

store = await OracleVS.acreate(
conn,
embedding_function=model,
table_name="standard_tests",
mutate_on_duplicate=True,
)
yield store
2 changes: 2 additions & 0 deletions libs/oracledb/tests/unit_tests/embeddings/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) 2025 Oracle and/or its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
Loading