langchain-openviking 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langchain_openviking-0.1.0/PKG-INFO +96 -0
- langchain_openviking-0.1.0/README.md +60 -0
- langchain_openviking-0.1.0/README_CN.md +47 -0
- langchain_openviking-0.1.0/pyproject.toml +98 -0
- langchain_openviking-0.1.0/setup.cfg +4 -0
- langchain_openviking-0.1.0/src/langchain_openviking/__init__.py +106 -0
- langchain_openviking-0.1.0/src/langchain_openviking/_async_client_cache.py +124 -0
- langchain_openviking-0.1.0/src/langchain_openviking/_testing_utils.py +62 -0
- langchain_openviking-0.1.0/src/langchain_openviking/_uri.py +61 -0
- langchain_openviking-0.1.0/src/langchain_openviking/actor_peer.py +48 -0
- langchain_openviking-0.1.0/src/langchain_openviking/client.py +872 -0
- langchain_openviking-0.1.0/src/langchain_openviking/context.py +944 -0
- langchain_openviking-0.1.0/src/langchain_openviking/history.py +292 -0
- langchain_openviking-0.1.0/src/langchain_openviking/messages.py +206 -0
- langchain_openviking-0.1.0/src/langchain_openviking/middleware.py +598 -0
- langchain_openviking-0.1.0/src/langchain_openviking/py.typed +1 -0
- langchain_openviking-0.1.0/src/langchain_openviking/recording.py +697 -0
- langchain_openviking-0.1.0/src/langchain_openviking/retrievers.py +309 -0
- langchain_openviking-0.1.0/src/langchain_openviking/store.py +599 -0
- langchain_openviking-0.1.0/src/langchain_openviking/testing.py +409 -0
- langchain_openviking-0.1.0/src/langchain_openviking/tools.py +878 -0
- langchain_openviking-0.1.0/src/langchain_openviking.egg-info/PKG-INFO +96 -0
- langchain_openviking-0.1.0/src/langchain_openviking.egg-info/SOURCES.txt +28 -0
- langchain_openviking-0.1.0/src/langchain_openviking.egg-info/dependency_links.txt +1 -0
- langchain_openviking-0.1.0/src/langchain_openviking.egg-info/requires.txt +17 -0
- langchain_openviking-0.1.0/src/langchain_openviking.egg-info/scm_file_list.json +25 -0
- langchain_openviking-0.1.0/src/langchain_openviking.egg-info/scm_version.json +8 -0
- langchain_openviking-0.1.0/src/langchain_openviking.egg-info/top_level.txt +1 -0
- langchain_openviking-0.1.0/tests/installed_base_smoke.py +45 -0
- langchain_openviking-0.1.0/tests/installed_smoke.py +59 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langchain-openviking
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: OpenViking integrations for LangChain and LangGraph
|
|
5
|
+
Author-email: ByteDance <noreply@bytedance.com>
|
|
6
|
+
License-Expression: AGPL-3.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/volcengine/openviking
|
|
8
|
+
Project-URL: Documentation, https://openviking.ai
|
|
9
|
+
Project-URL: Repository, https://github.com/volcengine/openviking
|
|
10
|
+
Project-URL: Issues, https://github.com/volcengine/openviking/issues
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: langchain-core<2.0.0,>=1.0.0
|
|
23
|
+
Requires-Dist: openviking-sdk>=0.1.1
|
|
24
|
+
Requires-Dist: pydantic>=2.0.0
|
|
25
|
+
Provides-Extra: langgraph
|
|
26
|
+
Requires-Dist: langchain<2.0.0,>=1.0.0; extra == "langgraph"
|
|
27
|
+
Requires-Dist: langgraph<2.0.0,>=1.0.0; extra == "langgraph"
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest>=7.0.0; extra == "test"
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build>=1.2.0; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
35
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
36
|
+
|
|
37
|
+
# langchain-openviking
|
|
38
|
+
|
|
39
|
+
`langchain-openviking` is the official OpenViking integration package for
|
|
40
|
+
LangChain and LangGraph applications. It keeps framework-specific adapters
|
|
41
|
+
separate from the OpenViking server and communicates with remote OpenViking
|
|
42
|
+
instances through the lightweight `openviking-sdk` package.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
For LangChain retrievers, tools, message history, and context wrappers:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install langchain-openviking
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For the LangGraph store and middleware:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install "langchain-openviking[langgraph]"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from langchain_openviking import OpenVikingRetriever
|
|
62
|
+
from openviking_sdk import SyncHTTPClient
|
|
63
|
+
|
|
64
|
+
client = SyncHTTPClient(
|
|
65
|
+
url="http://127.0.0.1:1933",
|
|
66
|
+
api_key="your-user-api-key",
|
|
67
|
+
)
|
|
68
|
+
client.initialize()
|
|
69
|
+
retriever = OpenVikingRetriever(
|
|
70
|
+
client=client,
|
|
71
|
+
target_uri="viking://user/memories",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
documents = retriever.invoke("What deployment preferences should I remember?")
|
|
76
|
+
finally:
|
|
77
|
+
client.close()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The package also provides `OpenVikingSessionRecorder`,
|
|
81
|
+
`OpenVikingContextMiddleware`, `OpenVikingStore`,
|
|
82
|
+
`OpenVikingChatMessageHistory`, and `create_openviking_tools`.
|
|
83
|
+
|
|
84
|
+
## Client ownership
|
|
85
|
+
|
|
86
|
+
- A client supplied through `client=` or `async_client=` remains caller-owned.
|
|
87
|
+
- Clients created from `url=` are managed by the adapter and can be closed with
|
|
88
|
+
`close()` or `aclose()` as documented by each adapter.
|
|
89
|
+
- Embedded `path=` mode requires the full `openviking` package. It is kept for
|
|
90
|
+
compatibility, while the standalone package's default boundary is HTTP.
|
|
91
|
+
|
|
92
|
+
The previous `openviking.integrations.langchain` import path remains available
|
|
93
|
+
from the full `openviking` distribution as a compatibility shim.
|
|
94
|
+
|
|
95
|
+
See the [OpenViking documentation](https://openviking.ai) and the repository's
|
|
96
|
+
`examples/langchain-langgraph` directory for complete examples.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# langchain-openviking
|
|
2
|
+
|
|
3
|
+
`langchain-openviking` is the official OpenViking integration package for
|
|
4
|
+
LangChain and LangGraph applications. It keeps framework-specific adapters
|
|
5
|
+
separate from the OpenViking server and communicates with remote OpenViking
|
|
6
|
+
instances through the lightweight `openviking-sdk` package.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
For LangChain retrievers, tools, message history, and context wrappers:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install langchain-openviking
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
For the LangGraph store and middleware:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install "langchain-openviking[langgraph]"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from langchain_openviking import OpenVikingRetriever
|
|
26
|
+
from openviking_sdk import SyncHTTPClient
|
|
27
|
+
|
|
28
|
+
client = SyncHTTPClient(
|
|
29
|
+
url="http://127.0.0.1:1933",
|
|
30
|
+
api_key="your-user-api-key",
|
|
31
|
+
)
|
|
32
|
+
client.initialize()
|
|
33
|
+
retriever = OpenVikingRetriever(
|
|
34
|
+
client=client,
|
|
35
|
+
target_uri="viking://user/memories",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
documents = retriever.invoke("What deployment preferences should I remember?")
|
|
40
|
+
finally:
|
|
41
|
+
client.close()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The package also provides `OpenVikingSessionRecorder`,
|
|
45
|
+
`OpenVikingContextMiddleware`, `OpenVikingStore`,
|
|
46
|
+
`OpenVikingChatMessageHistory`, and `create_openviking_tools`.
|
|
47
|
+
|
|
48
|
+
## Client ownership
|
|
49
|
+
|
|
50
|
+
- A client supplied through `client=` or `async_client=` remains caller-owned.
|
|
51
|
+
- Clients created from `url=` are managed by the adapter and can be closed with
|
|
52
|
+
`close()` or `aclose()` as documented by each adapter.
|
|
53
|
+
- Embedded `path=` mode requires the full `openviking` package. It is kept for
|
|
54
|
+
compatibility, while the standalone package's default boundary is HTTP.
|
|
55
|
+
|
|
56
|
+
The previous `openviking.integrations.langchain` import path remains available
|
|
57
|
+
from the full `openviking` distribution as a compatibility shim.
|
|
58
|
+
|
|
59
|
+
See the [OpenViking documentation](https://openviking.ai) and the repository's
|
|
60
|
+
`examples/langchain-langgraph` directory for complete examples.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# langchain-openviking
|
|
2
|
+
|
|
3
|
+
`langchain-openviking` 是 OpenViking 官方维护的 LangChain 和 LangGraph
|
|
4
|
+
集成包。框架适配逻辑不再依赖 OpenViking 服务端实现,远程访问统一通过轻量的
|
|
5
|
+
`openviking-sdk` 完成。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
LangChain Retriever、Tools、Message History 和 Context Wrapper:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install langchain-openviking
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
LangGraph Store 和 Middleware:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install "langchain-openviking[langgraph]"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 快速开始
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from langchain_openviking import OpenVikingRetriever
|
|
25
|
+
from openviking_sdk import SyncHTTPClient
|
|
26
|
+
|
|
27
|
+
client = SyncHTTPClient(
|
|
28
|
+
url="http://127.0.0.1:1933",
|
|
29
|
+
api_key="your-user-api-key",
|
|
30
|
+
)
|
|
31
|
+
client.initialize()
|
|
32
|
+
retriever = OpenVikingRetriever(
|
|
33
|
+
client=client,
|
|
34
|
+
target_uri="viking://user/memories",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
documents = retriever.invoke("需要记住哪些部署偏好?")
|
|
39
|
+
finally:
|
|
40
|
+
client.close()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
外部传入的 client 仍由调用方管理。通过 `url=` 创建的 client 由适配器管理。
|
|
44
|
+
`path=` 嵌入模式需要另外安装完整的 `openviking` 包。
|
|
45
|
+
|
|
46
|
+
完整 `openviking` 包会继续保留原有的
|
|
47
|
+
`openviking.integrations.langchain` 导入路径,并转发到本包,方便现有应用平滑迁移。
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "setuptools-scm>=8.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "langchain-openviking"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "OpenViking integrations for LangChain and LangGraph"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "ByteDance", email = "noreply@bytedance.com"},
|
|
13
|
+
]
|
|
14
|
+
license = "AGPL-3.0"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"langchain-core>=1.0.0,<2.0.0",
|
|
28
|
+
"openviking-sdk>=0.1.1",
|
|
29
|
+
"pydantic>=2.0.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
langgraph = [
|
|
34
|
+
"langchain>=1.0.0,<2.0.0",
|
|
35
|
+
"langgraph>=1.0.0,<2.0.0",
|
|
36
|
+
]
|
|
37
|
+
test = [
|
|
38
|
+
"pytest>=7.0.0",
|
|
39
|
+
"pytest-asyncio>=0.21.0",
|
|
40
|
+
]
|
|
41
|
+
dev = [
|
|
42
|
+
"build>=1.2.0",
|
|
43
|
+
"mypy>=1.0.0",
|
|
44
|
+
"ruff>=0.1.0",
|
|
45
|
+
"twine>=5.0.0",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://github.com/volcengine/openviking"
|
|
50
|
+
Documentation = "https://openviking.ai"
|
|
51
|
+
Repository = "https://github.com/volcengine/openviking"
|
|
52
|
+
Issues = "https://github.com/volcengine/openviking/issues"
|
|
53
|
+
|
|
54
|
+
[tool.setuptools]
|
|
55
|
+
package-dir = {"" = "src"}
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.packages.find]
|
|
58
|
+
where = ["src"]
|
|
59
|
+
include = ["langchain_openviking*"]
|
|
60
|
+
|
|
61
|
+
[tool.setuptools.package-data]
|
|
62
|
+
langchain_openviking = ["py.typed"]
|
|
63
|
+
|
|
64
|
+
[tool.setuptools_scm]
|
|
65
|
+
root = "../.."
|
|
66
|
+
local_scheme = "no-local-version"
|
|
67
|
+
tag_regex = "^langchain-openviking@(?P<version>[0-9]+(?:\\.[0-9]+)*)$"
|
|
68
|
+
git_describe_command = "git describe --dirty --tags --long --match langchain-openviking@*"
|
|
69
|
+
|
|
70
|
+
[tool.mypy]
|
|
71
|
+
python_version = "3.10"
|
|
72
|
+
warn_return_any = false
|
|
73
|
+
warn_unused_configs = true
|
|
74
|
+
check_untyped_defs = true
|
|
75
|
+
no_implicit_optional = false
|
|
76
|
+
warn_redundant_casts = true
|
|
77
|
+
warn_unused_ignores = true
|
|
78
|
+
ignore_missing_imports = true
|
|
79
|
+
|
|
80
|
+
[tool.pytest.ini_options]
|
|
81
|
+
asyncio_mode = "auto"
|
|
82
|
+
|
|
83
|
+
[tool.ruff]
|
|
84
|
+
line-length = 100
|
|
85
|
+
target-version = "py310"
|
|
86
|
+
|
|
87
|
+
[tool.ruff.lint]
|
|
88
|
+
select = ["E", "W", "F", "I", "C", "B"]
|
|
89
|
+
ignore = ["E501", "B008", "C901", "B006", "B904", "E741", "E722", "B027"]
|
|
90
|
+
|
|
91
|
+
[tool.ruff.lint.per-file-ignores]
|
|
92
|
+
"__init__.py" = ["F401"]
|
|
93
|
+
|
|
94
|
+
[tool.ruff.format]
|
|
95
|
+
quote-style = "double"
|
|
96
|
+
indent-style = "space"
|
|
97
|
+
skip-magic-trailing-comma = false
|
|
98
|
+
line-ending = "auto"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd.
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0
|
|
3
|
+
"""LangChain and LangGraph integrations for OpenViking.
|
|
4
|
+
|
|
5
|
+
The base distribution depends on ``langchain-core``. LangGraph middleware is
|
|
6
|
+
loaded only when the ``langgraph`` extra (or compatible dependencies) is
|
|
7
|
+
installed.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from importlib.util import find_spec
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from langchain_openviking.actor_peer import (
|
|
16
|
+
has_request_actor_peer_support,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"InMemoryOpenVikingClient",
|
|
21
|
+
"OpenVikingChatMessageHistory",
|
|
22
|
+
"OpenVikingCancellationProgress",
|
|
23
|
+
"OpenVikingCommitPolicy",
|
|
24
|
+
"OpenVikingContextRunnable",
|
|
25
|
+
"OpenVikingPartialWriteError",
|
|
26
|
+
"OpenVikingRecordResult",
|
|
27
|
+
"OpenVikingRetriever",
|
|
28
|
+
"OpenVikingSessionContextAssembler",
|
|
29
|
+
"OpenVikingSessionRecorder",
|
|
30
|
+
"OpenVikingStore",
|
|
31
|
+
"create_openviking_tools",
|
|
32
|
+
"get_openviking_cancellation_progress",
|
|
33
|
+
"has_request_actor_peer_support",
|
|
34
|
+
"with_openviking_context",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
if find_spec("langchain") is not None:
|
|
38
|
+
__all__.append("OpenVikingContextMiddleware")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def __getattr__(name: str) -> Any:
|
|
42
|
+
if name == "OpenVikingRetriever":
|
|
43
|
+
from langchain_openviking.retrievers import OpenVikingRetriever
|
|
44
|
+
|
|
45
|
+
return OpenVikingRetriever
|
|
46
|
+
if name == "create_openviking_tools":
|
|
47
|
+
from langchain_openviking.tools import create_openviking_tools
|
|
48
|
+
|
|
49
|
+
return create_openviking_tools
|
|
50
|
+
if name == "OpenVikingStore":
|
|
51
|
+
from langchain_openviking.store import OpenVikingStore
|
|
52
|
+
|
|
53
|
+
return OpenVikingStore
|
|
54
|
+
if name == "OpenVikingChatMessageHistory":
|
|
55
|
+
from langchain_openviking.history import OpenVikingChatMessageHistory
|
|
56
|
+
|
|
57
|
+
return OpenVikingChatMessageHistory
|
|
58
|
+
if name == "OpenVikingSessionContextAssembler":
|
|
59
|
+
from langchain_openviking.context import OpenVikingSessionContextAssembler
|
|
60
|
+
|
|
61
|
+
return OpenVikingSessionContextAssembler
|
|
62
|
+
if name == "OpenVikingContextRunnable":
|
|
63
|
+
from langchain_openviking.context import OpenVikingContextRunnable
|
|
64
|
+
|
|
65
|
+
return OpenVikingContextRunnable
|
|
66
|
+
if name == "OpenVikingSessionRecorder":
|
|
67
|
+
from langchain_openviking.recording import OpenVikingSessionRecorder
|
|
68
|
+
|
|
69
|
+
return OpenVikingSessionRecorder
|
|
70
|
+
if name == "OpenVikingPartialWriteError":
|
|
71
|
+
from langchain_openviking.recording import OpenVikingPartialWriteError
|
|
72
|
+
|
|
73
|
+
return OpenVikingPartialWriteError
|
|
74
|
+
if name == "OpenVikingRecordResult":
|
|
75
|
+
from langchain_openviking.recording import OpenVikingRecordResult
|
|
76
|
+
|
|
77
|
+
return OpenVikingRecordResult
|
|
78
|
+
if name == "OpenVikingCancellationProgress":
|
|
79
|
+
from langchain_openviking.recording import (
|
|
80
|
+
OpenVikingCancellationProgress,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return OpenVikingCancellationProgress
|
|
84
|
+
if name == "get_openviking_cancellation_progress":
|
|
85
|
+
from langchain_openviking.recording import (
|
|
86
|
+
get_openviking_cancellation_progress,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return get_openviking_cancellation_progress
|
|
90
|
+
if name == "OpenVikingCommitPolicy":
|
|
91
|
+
from langchain_openviking.client import OpenVikingCommitPolicy
|
|
92
|
+
|
|
93
|
+
return OpenVikingCommitPolicy
|
|
94
|
+
if name == "with_openviking_context":
|
|
95
|
+
from langchain_openviking.context import with_openviking_context
|
|
96
|
+
|
|
97
|
+
return with_openviking_context
|
|
98
|
+
if name == "OpenVikingContextMiddleware":
|
|
99
|
+
from langchain_openviking.middleware import OpenVikingContextMiddleware
|
|
100
|
+
|
|
101
|
+
return OpenVikingContextMiddleware
|
|
102
|
+
if name == "InMemoryOpenVikingClient":
|
|
103
|
+
from langchain_openviking.testing import InMemoryOpenVikingClient
|
|
104
|
+
|
|
105
|
+
return InMemoryOpenVikingClient
|
|
106
|
+
raise AttributeError(name)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd.
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0
|
|
3
|
+
"""Private event-loop scoped cache for integration-owned async clients."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import inspect
|
|
9
|
+
import threading
|
|
10
|
+
import weakref
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from typing import Any, Protocol
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _Closeable(Protocol):
|
|
16
|
+
def close(self) -> Any: ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _AsyncCloseable(Protocol):
|
|
20
|
+
def aclose(self) -> Any: ...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _close_client(client: _Closeable) -> Any:
|
|
24
|
+
return client.close()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _aclose_client(client: _AsyncCloseable) -> Any:
|
|
28
|
+
return client.aclose()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LoopScopedAsyncClientCache:
|
|
32
|
+
"""Cache integration-owned async clients per running event loop.
|
|
33
|
+
|
|
34
|
+
HTTP clients may bind internal asyncio primitives to the loop that first
|
|
35
|
+
uses them. Keeping this small cache private lets the adapter own that
|
|
36
|
+
lifecycle without importing OpenViking server utilities.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self) -> None:
|
|
40
|
+
self._clients_by_loop: weakref.WeakKeyDictionary[asyncio.AbstractEventLoop, Any] = (
|
|
41
|
+
weakref.WeakKeyDictionary()
|
|
42
|
+
)
|
|
43
|
+
self._fallback_client: Any = None
|
|
44
|
+
self._lock = threading.Lock()
|
|
45
|
+
|
|
46
|
+
def __copy__(self) -> LoopScopedAsyncClientCache:
|
|
47
|
+
"""Return an empty cache instead of sharing live async clients."""
|
|
48
|
+
|
|
49
|
+
return type(self)()
|
|
50
|
+
|
|
51
|
+
def __deepcopy__(self, memo: dict[int, Any]) -> LoopScopedAsyncClientCache:
|
|
52
|
+
"""Return an empty cache instead of copying loop-bound clients."""
|
|
53
|
+
|
|
54
|
+
copied = type(self)()
|
|
55
|
+
memo[id(self)] = copied
|
|
56
|
+
return copied
|
|
57
|
+
|
|
58
|
+
def get(self, factory: Callable[[], Any]) -> Any:
|
|
59
|
+
"""Return the client for the current loop, creating it if necessary."""
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
loop = asyncio.get_running_loop()
|
|
63
|
+
except RuntimeError:
|
|
64
|
+
with self._lock:
|
|
65
|
+
if self._fallback_client is None:
|
|
66
|
+
self._fallback_client = factory()
|
|
67
|
+
return self._fallback_client
|
|
68
|
+
|
|
69
|
+
with self._lock:
|
|
70
|
+
client = self._clients_by_loop.get(loop)
|
|
71
|
+
if client is None:
|
|
72
|
+
client = factory()
|
|
73
|
+
self._clients_by_loop[loop] = client
|
|
74
|
+
return client
|
|
75
|
+
|
|
76
|
+
def has_clients(self) -> bool:
|
|
77
|
+
"""Return whether the cache currently retains any clients."""
|
|
78
|
+
|
|
79
|
+
with self._lock:
|
|
80
|
+
return bool(self._clients_by_loop) or self._fallback_client is not None
|
|
81
|
+
|
|
82
|
+
def pop_all(self) -> list[Any]:
|
|
83
|
+
"""Remove and return every retained client."""
|
|
84
|
+
|
|
85
|
+
with self._lock:
|
|
86
|
+
clients = list(self._clients_by_loop.values())
|
|
87
|
+
self._clients_by_loop.clear()
|
|
88
|
+
if self._fallback_client is not None:
|
|
89
|
+
clients.append(self._fallback_client)
|
|
90
|
+
self._fallback_client = None
|
|
91
|
+
return clients
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
async def _close_clients(clients: list[Any], close_client: Callable[[Any], Any]) -> None:
|
|
95
|
+
seen: set[int] = set()
|
|
96
|
+
for client in clients:
|
|
97
|
+
client_id = id(client)
|
|
98
|
+
if client_id in seen:
|
|
99
|
+
continue
|
|
100
|
+
seen.add(client_id)
|
|
101
|
+
result = close_client(client)
|
|
102
|
+
if inspect.isawaitable(result):
|
|
103
|
+
await result
|
|
104
|
+
|
|
105
|
+
def close_all(self, close_client: Callable[[Any], Any]) -> None:
|
|
106
|
+
"""Close and clear all cached clients on a best-effort basis."""
|
|
107
|
+
|
|
108
|
+
clients = self.pop_all()
|
|
109
|
+
if not clients:
|
|
110
|
+
return
|
|
111
|
+
try:
|
|
112
|
+
loop = asyncio.get_running_loop()
|
|
113
|
+
except RuntimeError:
|
|
114
|
+
loop = None
|
|
115
|
+
if loop and loop.is_running():
|
|
116
|
+
loop.create_task(self._close_clients(clients, close_client))
|
|
117
|
+
else:
|
|
118
|
+
asyncio.run(self._close_clients(clients, close_client))
|
|
119
|
+
|
|
120
|
+
def close_all_with_close(self) -> None:
|
|
121
|
+
self.close_all(_close_client)
|
|
122
|
+
|
|
123
|
+
def close_all_with_aclose(self) -> None:
|
|
124
|
+
self.close_all(_aclose_client)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd.
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0
|
|
3
|
+
"""Private deterministic helpers for the in-memory integration test client."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import math
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
_IDENTIFIER_PATTERN = re.compile(r"^[a-zA-Z0-9_.@-]+$")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def normalize_peer_id(peer_id: str | None) -> str | None:
|
|
14
|
+
"""Normalize a peer identifier using OpenViking's public wire constraints."""
|
|
15
|
+
|
|
16
|
+
if peer_id is None:
|
|
17
|
+
return None
|
|
18
|
+
if not isinstance(peer_id, str):
|
|
19
|
+
raise ValueError("Invalid peer_id: peer_id must be a string.")
|
|
20
|
+
normalized = peer_id.strip()
|
|
21
|
+
if not normalized:
|
|
22
|
+
return None
|
|
23
|
+
if normalized in {".", ".."}:
|
|
24
|
+
raise ValueError("Invalid peer_id: peer_id must not be '.' or '..'")
|
|
25
|
+
if not _IDENTIFIER_PATTERN.fullmatch(normalized):
|
|
26
|
+
raise ValueError("Invalid peer_id: peer_id must be alpha_numeric string.")
|
|
27
|
+
if normalized.count("@") > 1:
|
|
28
|
+
raise ValueError("Invalid peer_id: peer_id must have at most one @.")
|
|
29
|
+
return normalized
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _is_cjk_code_point(code_point: int) -> bool:
|
|
33
|
+
return (
|
|
34
|
+
0x3400 <= code_point <= 0x4DBF
|
|
35
|
+
or 0x4E00 <= code_point <= 0x9FFF
|
|
36
|
+
or 0xF900 <= code_point <= 0xFAFF
|
|
37
|
+
or 0x20000 <= code_point <= 0x2EBEF
|
|
38
|
+
or 0x3040 <= code_point <= 0x30FF
|
|
39
|
+
or 0x31F0 <= code_point <= 0x31FF
|
|
40
|
+
or 0xAC00 <= code_point <= 0xD7AF
|
|
41
|
+
or 0x1100 <= code_point <= 0x11FF
|
|
42
|
+
or 0x3130 <= code_point <= 0x318F
|
|
43
|
+
or 0xFF00 <= code_point <= 0xFFEF
|
|
44
|
+
or 0x3000 <= code_point <= 0x303F
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def estimate_text_tokens(text: str | None) -> int:
|
|
49
|
+
"""Estimate tokens conservatively without depending on server internals."""
|
|
50
|
+
|
|
51
|
+
if not text:
|
|
52
|
+
return 0
|
|
53
|
+
weight = 0.0
|
|
54
|
+
for char in text:
|
|
55
|
+
code_point = ord(char)
|
|
56
|
+
if _is_cjk_code_point(code_point):
|
|
57
|
+
weight += 1.5
|
|
58
|
+
elif code_point > 0xFFFF:
|
|
59
|
+
weight += 2.0
|
|
60
|
+
else:
|
|
61
|
+
weight += 0.25
|
|
62
|
+
return math.ceil(weight)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd.
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0
|
|
3
|
+
"""Narrow Viking URI parsing used by the LangGraph store adapter."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
_CONTENT_SEGMENTS = frozenset({"memories", "resources", "skills"})
|
|
10
|
+
_PEER_CONTENT_SEGMENTS = frozenset({"memories", "resources"})
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class UriClassification:
|
|
15
|
+
"""Structural URI information needed to validate store record paths."""
|
|
16
|
+
|
|
17
|
+
parts: tuple[str, ...]
|
|
18
|
+
scope: str
|
|
19
|
+
content_index: int | None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _uri_parts(uri: str) -> tuple[str, ...]:
|
|
23
|
+
value = str(uri or "").split("?", 1)[0].strip()
|
|
24
|
+
if not value:
|
|
25
|
+
return ()
|
|
26
|
+
if value == "/":
|
|
27
|
+
value = "viking://"
|
|
28
|
+
elif not value.startswith("viking://"):
|
|
29
|
+
value = f"viking://{value.lstrip('/')}"
|
|
30
|
+
if value.rstrip("/") == "viking:":
|
|
31
|
+
return ()
|
|
32
|
+
return tuple(part for part in value[len("viking://") :].split("/") if part)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _content_segment_index(parts: tuple[str, ...]) -> int | None:
|
|
36
|
+
if len(parts) >= 2 and parts[:2] == ("agent", "skills"):
|
|
37
|
+
return 1
|
|
38
|
+
if len(parts) >= 3 and parts[0] == "agent" and parts[2] in _CONTENT_SEGMENTS:
|
|
39
|
+
return 2
|
|
40
|
+
if len(parts) < 2 or parts[0] != "user":
|
|
41
|
+
return None
|
|
42
|
+
if parts[1] in _CONTENT_SEGMENTS:
|
|
43
|
+
return 1
|
|
44
|
+
if len(parts) >= 4 and parts[1] == "peers" and parts[3] in _PEER_CONTENT_SEGMENTS:
|
|
45
|
+
return 3
|
|
46
|
+
if len(parts) >= 5 and parts[2] == "peers" and parts[4] in _PEER_CONTENT_SEGMENTS:
|
|
47
|
+
return 4
|
|
48
|
+
if len(parts) >= 3 and parts[2] in _CONTENT_SEGMENTS:
|
|
49
|
+
return 2
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def classify_uri(uri: str) -> UriClassification:
|
|
54
|
+
"""Classify the user/agent namespace shape of a Viking URI."""
|
|
55
|
+
|
|
56
|
+
parts = _uri_parts(uri)
|
|
57
|
+
return UriClassification(
|
|
58
|
+
parts=parts,
|
|
59
|
+
scope=parts[0] if parts else "",
|
|
60
|
+
content_index=_content_segment_index(parts),
|
|
61
|
+
)
|