Skip to content
Draft
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
2 changes: 2 additions & 0 deletions docs/en/agent-integrations/07-langchain-langgraph.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ Use `close()` after synchronous work or `await store.aclose()` after async work.
also supports `with OpenVikingStore(...) as store` and `async with` for automatic cleanup.
Cleanup releases only internally created clients; injected clients remain caller-owned.
Closing an unused Store creates no client, and a closed Store cannot be reused.
Cleanup waits for active backend requests before releasing the owned client. Use `aclose()`
in async code so this wait does not block the event loop.

Following LangGraph's `BaseStore` contract, passing `None` as the value deletes the item and is
equivalent to `delete()`. Deleting a key that does not exist is a no-op.
Expand Down
2 changes: 2 additions & 0 deletions docs/zh/agent-integrations/07-langchain-langgraph.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ store.close()
`with OpenVikingStore(...) as store` 或 `async with` 自动清理。
清理仅释放 Store 内部创建的客户端,注入的客户端仍由调用方管理。
关闭未使用的 Store 不会创建客户端,关闭后不可复用。
清理会等待正在执行的后端请求结束,再释放内部创建的客户端。
异步代码应使用 `aclose()`,避免等待过程阻塞事件循环。

按 LangGraph `BaseStore` 约定,传入 `None` 作为值表示删除该条目,等价于 `delete()`。
删除不存在的键不会产生任何影响。
Expand Down
7 changes: 7 additions & 0 deletions integrations/frameworks/langchain/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ from openviking_sdk import SyncHTTPClient
client = SyncHTTPClient(
url="http://127.0.0.1:1933",
api_key="your-user-api-key",
auth_mode="api_key",
)
client.initialize()
retriever = OpenVikingRetriever(
Expand All @@ -49,6 +50,12 @@ finally:
client.close()
```

For API-key-only integrations, pass `auth_mode="api_key"` alongside `api_key`.
The authentication mode is forwarded to owned synchronous and asynchronous SDK
clients, including clients rebuilt during safe-read recovery. Leaving it unset
preserves the SDK's existing configuration defaults. The same option is available
on the recorder, retriever, message history, store, tools, and context wrappers.

The package also provides `OpenVikingSessionRecorder`,
`OpenVikingContextMiddleware`, `OpenVikingStore`,
`OpenVikingChatMessageHistory`, and `create_openviking_tools`.
Expand Down
2 changes: 1 addition & 1 deletion integrations/frameworks/langchain/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ classifiers = [
]
dependencies = [
"langchain-core>=1.0.0,<2.0.0",
"openviking-sdk>=0.1.1",
"openviking-sdk>=0.1.7",
"pydantic>=2.0.0",
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class OpenVikingConnection:
extra_headers: dict[str, str] | None = None
auto_initialize: bool = True
async_client: Any = None
auth_mode: str | None = None

def __deepcopy__(self, memo: dict[int, Any]) -> OpenVikingConnection:
"""Copy configuration while preserving caller-owned client identities."""
Expand All @@ -85,6 +86,7 @@ def __deepcopy__(self, memo: dict[int, Any]) -> OpenVikingConnection:
async_client=self.async_client,
url=self.url,
api_key=self.api_key,
auth_mode=self.auth_mode,
account=self.account,
user=self.user,
user_id=self.user_id,
Expand Down Expand Up @@ -561,6 +563,7 @@ def _create_client_from_connection(connection: OpenVikingConnection) -> Any:
actor_peer_id=connection.actor_peer_id,
timeout=connection.timeout,
extra_headers=connection.extra_headers,
**({"auth_mode": connection.auth_mode} if connection.auth_mode is not None else {}),
)

if connection.auto_initialize and hasattr(client, "initialize"):
Expand All @@ -581,6 +584,7 @@ async def _create_async_client_from_connection(connection: OpenVikingConnection)
actor_peer_id=connection.actor_peer_id,
timeout=connection.timeout,
extra_headers=connection.extra_headers,
**({"auth_mode": connection.auth_mode} if connection.auth_mode is not None else {}),
)

try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ def __init__(
retriever: OpenVikingRetriever | None = None,
url: str | None = None,
api_key: str | None = None,
auth_mode: str | None = None,
account: str | None = None,
user: str | None = None,
user_id: str | None = None,
Expand All @@ -210,6 +211,7 @@ def __init__(
async_client=async_client,
url=url,
api_key=api_key,
auth_mode=auth_mode,
account=account,
user=user,
user_id=user_id,
Expand All @@ -223,6 +225,7 @@ def __init__(
async_client=async_client,
url=url,
api_key=api_key,
auth_mode=auth_mode,
account=account,
user=user,
user_id=user_id,
Expand Down Expand Up @@ -639,6 +642,7 @@ def with_openviking_context(
async_client: Any = None,
url: str | None = None,
api_key: str | None = None,
auth_mode: str | None = None,
account: str | None = None,
user: str | None = None,
user_id: str | None = None,
Expand Down Expand Up @@ -680,6 +684,7 @@ def with_openviking_context(
async_client=async_client,
url=url,
api_key=api_key,
auth_mode=auth_mode,
account=account,
user=user,
user_id=user_id,
Expand All @@ -700,6 +705,7 @@ def with_openviking_context(
async_client=async_client,
url=url,
api_key=api_key,
auth_mode=auth_mode,
account=account,
user=user,
user_id=user_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def __init__(
async_client: Any = None,
url: str | None = None,
api_key: str | None = None,
auth_mode: str | None = None,
account: str | None = None,
user: str | None = None,
user_id: str | None = None,
Expand Down Expand Up @@ -84,6 +85,7 @@ def __init__(
async_client=async_client,
url=url,
api_key=api_key,
auth_mode=auth_mode,
account=account,
user=user,
user_id=user_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def __init__(
retriever: OpenVikingRetriever | None = None,
url: str | None = None,
api_key: str | None = None,
auth_mode: str | None = None,
account: str | None = None,
user: str | None = None,
user_id: str | None = None,
Expand Down Expand Up @@ -141,6 +142,7 @@ def __init__(
async_client=async_client,
url=url,
api_key=api_key,
auth_mode=auth_mode,
account=account,
user=user,
user_id=user_id,
Expand All @@ -153,6 +155,7 @@ def __init__(
async_client=async_client,
url=url,
api_key=api_key,
auth_mode=auth_mode,
account=account,
user=user,
user_id=user_id,
Expand All @@ -168,6 +171,7 @@ def __init__(
retriever=self.retriever,
url=url,
api_key=api_key,
auth_mode=auth_mode,
account=account,
user=user,
user_id=user_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ def __init__(
async_client: Any = None,
url: str | None = None,
api_key: str | None = None,
auth_mode: str | None = None,
account: str | None = None,
user: str | None = None,
user_id: str | None = None,
Expand All @@ -215,6 +216,7 @@ def __init__(
async_client=async_client,
url=url,
api_key=api_key,
auth_mode=auth_mode,
account=account,
user=user,
user_id=user_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class OpenVikingRetriever(BaseRetriever):
async_client: Any = None
url: str | None = None
api_key: str | None = None
auth_mode: str | None = None
account: str | None = None
user: str | None = None
user_id: str | None = None
Expand Down Expand Up @@ -127,6 +128,7 @@ def _get_client(self) -> Any:
client=self.client,
url=self.url,
api_key=self.api_key,
auth_mode=self.auth_mode,
account=self.account,
user=self.user,
user_id=self.user_id,
Expand Down Expand Up @@ -156,6 +158,7 @@ async def get_async_client(self) -> Any:
async_client=self.async_client,
url=self.url,
api_key=self.api_key,
auth_mode=self.auth_mode,
account=self.account,
user=self.user,
user_id=self.user_id,
Expand Down
61 changes: 41 additions & 20 deletions integrations/frameworks/langchain/src/langchain_openviking/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import asyncio
import json
import logging
import threading
from collections.abc import Mapping
from datetime import datetime, timezone
from types import TracebackType
Expand Down Expand Up @@ -81,6 +82,7 @@ def __init__(
client: Any = None,
url: str | None = None,
api_key: str | None = None,
auth_mode: str | None = None,
account: str | None = None,
user: str | None = None,
user_id: str | None = None,
Expand All @@ -98,6 +100,7 @@ def __init__(
client=client,
url=url,
api_key=api_key,
auth_mode=auth_mode,
account=account,
user=user,
user_id=user_id,
Expand All @@ -109,18 +112,22 @@ def __init__(
self.wait: bool = wait
self.search_fetch_limit: int = search_fetch_limit
self._client_cache: Any = None
self._client_lock = threading.Lock()
self._call_condition = threading.Condition()
self._active_calls = 0
self._owns_client = client is None
self._closed = False

def close(self) -> None:
"""Close the Store and release its internally created client, if any."""
if self._closed:
return
self._closed = True
client = self._client_cache
self._client_cache = None
if client is not None and self._owns_client:
client.close()
with self._call_condition:
self._closed = True
self._call_condition.wait_for(lambda: self._active_calls == 0)
with self._client_lock:
client = self._client_cache
self._client_cache = None
if client is not None and self._owns_client:
client.close()

async def aclose(self) -> None:
"""Close the Store without blocking the event loop."""
Expand Down Expand Up @@ -316,8 +323,7 @@ def _semantic_search(
limit: int,
offset: int,
) -> list[Any]:
result = call_openviking(
self._client(),
result = self._call(
"find",
query=query,
target_uri=self._index_prefix_uri(namespace_prefix),
Expand Down Expand Up @@ -357,7 +363,7 @@ def _all_data_uris(self, namespace_prefix: tuple[str, ...]) -> list[str]:
seen: set[str] = set()
for pattern in ("*.json", "**/*.json"):
try:
result = call_openviking(self._client(), "glob", pattern=pattern, uri=base_uri)
result = self._call("glob", pattern=pattern, uri=base_uri)
except Exception:
continue
for uri in _extract_uris(result):
Expand All @@ -367,17 +373,34 @@ def _all_data_uris(self, namespace_prefix: tuple[str, ...]) -> list[str]:
return uris

def _client(self) -> Any:
self._raise_if_closed()
if self._client_cache is None:
self._client_cache = ensure_client(self._connection)
return self._client_cache
with self._client_lock:
self._raise_if_closed()
if self._client_cache is None:
client = ensure_client(self._connection)
if self._closed:
if self._owns_client:
client.close()
self._raise_if_closed()
self._client_cache = client
return self._client_cache

def _call(self, method_name: str, /, **kwargs: Any) -> Any:
with self._call_condition:
self._raise_if_closed()
self._active_calls += 1
try:
return call_openviking(self._client(), method_name, **kwargs)
finally:
with self._call_condition:
self._active_calls -= 1
self._call_condition.notify_all()

def _raise_if_closed(self) -> None:
if self._closed:
raise RuntimeError("OpenVikingStore is closed")

def _read_record(self, uri: str) -> dict[str, Any]:
content = call_openviking(self._client(), "read", uri=uri)
content = self._call("read", uri=uri)
if isinstance(content, bytes):
content = content.decode("utf-8")
return json.loads(str(content))
Expand Down Expand Up @@ -416,8 +439,7 @@ def _write(self, uri: str, content: str) -> None:
raise create_exc

def _write_create(self, uri: str, content: str) -> None:
call_openviking(
self._client(),
self._call(
"write",
uri=uri,
content=content,
Expand All @@ -426,8 +448,7 @@ def _write_create(self, uri: str, content: str) -> None:
)

def _write_replace(self, uri: str, content: str) -> None:
call_openviking(
self._client(),
self._call(
"write",
uri=uri,
content=content,
Expand All @@ -437,7 +458,7 @@ def _write_replace(self, uri: str, content: str) -> None:

def _remove(self, uri: str) -> None:
try:
call_openviking(self._client(), "rm", uri=uri, recursive=False)
self._call("rm", uri=uri, recursive=False)
except Exception as exc:
if not is_not_found_error(exc):
raise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def create_openviking_tools(
client: Any = None,
url: str | None = None,
api_key: str | None = None,
auth_mode: str | None = None,
account: str | None = None,
user: str | None = None,
user_id: str | None = None,
Expand Down Expand Up @@ -79,6 +80,7 @@ def get_client() -> Any:
client=client,
url=url,
api_key=api_key,
auth_mode=auth_mode,
account=account,
user=user,
user_id=user_id,
Expand Down
Loading