From 564a3ff6c8e2dd2e6beccf019efbb3c2e852717b Mon Sep 17 00:00:00 2001 From: shanchunhua Date: Sat, 26 Sep 2026 20:28:41 +0800 Subject: [PATCH 1/2] fix: adapt openviking backend to options api --- pyproject.toml | 2 +- tests/test_openviking_knowledgebase.py | 128 +++++++++++++++--- tests/test_openviking_long_term_memory.py | 123 ++++++++++------- uv.lock | 2 +- .../backends/openviking_backend.py | 52 ++++--- .../openviking_backend.py | 8 +- 6 files changed, 225 insertions(+), 90 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a067e6551..ee99a8601 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ dependencies = [ "vikingdb-python-sdk>=0.1.32", # For Viking DB "agentkit-sdk-python>=0.8.0", "websockets>=15,<16", # Stream temporary AgentKit Sandbox conversations - "openviking-sdk>=0.1.3", + "openviking-sdk>=0.1.9", "python-frontmatter==1.1.0", "PyYAML>=6.0.2", "tos>=2.8.4", # For TOS storage and Viking DB diff --git a/tests/test_openviking_knowledgebase.py b/tests/test_openviking_knowledgebase.py index c91d916b6..1a8f566f7 100644 --- a/tests/test_openviking_knowledgebase.py +++ b/tests/test_openviking_knowledgebase.py @@ -57,22 +57,69 @@ def initialize(self): def close(self): self.close_calls += 1 - def add_resource(self, **kwargs): - self.add_resource_calls.append(kwargs) - path = kwargs.get("path") + def add_resource( + self, + path: str, + to: str | None = None, + parent: str | None = None, + wait: bool = False, + timeout: float | None = None, + options: dict[str, Any] | None = None, + ): + call = { + "path": path, + "to": to, + "parent": parent, + "wait": wait, + "timeout": timeout, + "options": options, + } + self.add_resource_calls.append(call) if path and Path(path).is_file(): self.added_texts.append(Path(path).read_text(encoding="utf-8")) return { "status": "ok", - "result": {"root_uri": kwargs.get("to") or kwargs.get("parent")}, + "result": {"root_uri": to or parent}, } - def find(self, **kwargs): - self.find_calls.append(kwargs) + def find( + self, + query: str = "", + target_uri: str | list[str] = "", + limit: int = 10, + image: Any = None, + options: dict[str, Any] | None = None, + ): + self.find_calls.append( + { + "query": query, + "target_uri": target_uri, + "limit": limit, + "image": image, + "options": options, + } + ) return self.find_response - def search(self, **kwargs): - self.search_calls.append(kwargs) + def search( + self, + query: str = "", + session_id: str | None = None, + target_uri: str | list[str] = "", + limit: int = 10, + image: Any = None, + options: dict[str, Any] | None = None, + ): + self.search_calls.append( + { + "query": query, + "session_id": session_id, + "target_uri": target_uri, + "limit": limit, + "image": image, + "options": options, + } + ) return self.search_response def read(self, uri: str, offset: int = 0, limit: int = -1): @@ -347,6 +394,17 @@ def test_add_from_files_imports_each_file_under_target_uri(): ) assert all(call["wait"] is True for call in client.add_resource_calls) assert all(call["timeout"] == 300 for call in client.add_resource_calls) + assert all( + call["options"] + == { + "strict": False, + "reason": "", + "instruction": "", + "directly_upload_media": True, + "telemetry": False, + } + for call in client.add_resource_calls + ) def test_add_from_directory_imports_to_target_uri_with_structure(): @@ -359,17 +417,16 @@ def test_add_from_directory_imports_to_target_uri_with_structure(): { "path": "./docs", "to": "viking://user/owner/resources/demo/", + "parent": None, "wait": True, "timeout": 300, - "strict": False, - "ignore_dirs": None, - "include": None, - "exclude": None, - "directly_upload_media": True, - "preserve_structure": True, - "watch_interval": 0, - "args": None, - "telemetry": False, + "options": { + "strict": False, + "directly_upload_media": True, + "preserve_structure": True, + "watch_interval": 0, + "telemetry": False, + }, } ] @@ -423,6 +480,37 @@ def test_search_converts_resources_to_entries_without_memories_or_skills(): } assert client.find_calls[0]["target_uri"] == "viking://user/owner/resources/demo/" assert client.find_calls[0]["limit"] == 3 + assert client.find_calls[0]["options"] == {"telemetry": False} + + +def test_find_search_options_are_nested_for_new_sdk_signature(): + client = FakeOpenVikingClient() + backend = make_backend(client, index="demo", hydrate_results=False) + + backend.search( + "policy", + top_k=4, + score_threshold=0.45, + filter={"owner": "support"}, + context_type="resource", + tags=["faq", "policy"], + telemetry={"trace": "enabled"}, + ) + + assert client.search_calls == [] + assert client.find_calls[0] == { + "query": "policy", + "target_uri": "viking://user/owner/resources/demo/", + "limit": 4, + "image": None, + "options": { + "score_threshold": 0.45, + "filter": {"owner": "support"}, + "context_type": "resource", + "tags": ["faq", "policy"], + "telemetry": {"trace": "enabled"}, + }, + } def test_hydrate_file_resource_reads_content(): @@ -527,6 +615,10 @@ def test_score_threshold_and_context_search_are_forwarded(): backend.search("policy", top_k=7, session_id="s1") assert client.find_calls == [] - assert client.search_calls[0]["score_threshold"] == 0.3 assert client.search_calls[0]["limit"] == 7 assert client.search_calls[0]["session_id"] == "s1" + assert client.search_calls[0]["options"] == { + "score_threshold": 0.3, + "telemetry": False, + } + assert "session" not in client.search_calls[0] diff --git a/tests/test_openviking_long_term_memory.py b/tests/test_openviking_long_term_memory.py index b3ecb47ce..da3cfd919 100644 --- a/tests/test_openviking_long_term_memory.py +++ b/tests/test_openviking_long_term_memory.py @@ -49,52 +49,64 @@ def initialize(self): def close(self): pass - def create_session(self, *, session_id=None, **kwargs): + def create_session(self, session_id=None, options=None): calls.append( { "method": "create_session", "actor_peer_id": self.kwargs.get("actor_peer_id"), "payload": { "session_id": session_id, - **kwargs, + "options": options, }, } ) return responses.get("create_session", {}) def add_message( - self, session_id, *, role, content=None, peer_id=None, **kwargs + self, + session_id, + role, + content=None, + parts=None, + options=None, + peer_id=None, ): + payload = { + "session_id": session_id, + "role": role, + "content": content, + "peer_id": peer_id, + } + if parts is not None: + payload["parts"] = parts + if options is not None: + payload["options"] = options calls.append( { "method": "add_message", "actor_peer_id": self.kwargs.get("actor_peer_id"), - "payload": { - "session_id": session_id, - "role": role, - "content": content, - "peer_id": peer_id, - **kwargs, - }, + "payload": payload, } ) return responses.get("add_message", {}) - def commit_session(self, *, session_id, keep_recent_count=0, **kwargs): + def commit_session(self, session_id, keep_recent_count=0, options=None): + payload = { + "session_id": session_id, + "keep_recent_count": keep_recent_count, + } + if options is not None: + payload["options"] = options calls.append( { "method": "commit_session", "actor_peer_id": self.kwargs.get("actor_peer_id"), - "payload": { - "session_id": session_id, - "keep_recent_count": keep_recent_count, - **kwargs, - }, + "payload": payload, } ) return responses.get("commit_session", {}) - def find(self, *, query, target_uri="", limit=10, context_type=None, **kwargs): + def find(self, query="", target_uri="", limit=10, image=None, options=None): calls.append( { "method": "find", @@ -102,9 +114,9 @@ def find(self, *, query, target_uri="", limit=10, context_type=None, **kwargs): "payload": { "query": query, "target_uri": target_uri, - "context_type": context_type, "limit": limit, - **kwargs, + "image": image, + "options": options, }, } ) @@ -149,51 +161,63 @@ def initialize(self): def close(self): self._record("close") - def create_session(self, *, session_id=None, **kwargs): + def create_session(self, session_id=None, options=None): self._record( "create_session", { "session_id": session_id, - **kwargs, + "options": options, }, ) return responses.get("create_session", {}) def add_message( - self, session_id, *, role, content=None, peer_id=None, **kwargs + self, + session_id, + role, + content=None, + parts=None, + options=None, + peer_id=None, ): + payload = { + "session_id": session_id, + "role": role, + "content": content, + "peer_id": peer_id, + } + if parts is not None: + payload["parts"] = parts + if options is not None: + payload["options"] = options self._record( "add_message", - { - "session_id": session_id, - "role": role, - "content": content, - "peer_id": peer_id, - **kwargs, - }, + payload, ) return responses.get("add_message", {}) - def commit_session(self, *, session_id, keep_recent_count=0, **kwargs): + def commit_session(self, session_id, keep_recent_count=0, options=None): + payload = { + "session_id": session_id, + "keep_recent_count": keep_recent_count, + } + if options is not None: + payload["options"] = options self._record( "commit_session", - { - "session_id": session_id, - "keep_recent_count": keep_recent_count, - **kwargs, - }, + payload, ) return responses.get("commit_session", {}) - def find(self, *, query, target_uri="", limit=10, context_type=None, **kwargs): + def find(self, query="", target_uri="", limit=10, image=None, options=None): self._record( "find", { "query": query, "target_uri": target_uri, - "context_type": context_type, "limit": limit, - **kwargs, + "image": image, + "options": options, }, ) return responses.get("find", {}) @@ -241,7 +265,7 @@ def test_openviking_backend_writes_peer_messages_and_commits(monkeypatch): assert calls[0] == { "method": "create_session", "actor_peer_id": None, - "payload": {"session_id": openviking_session_id}, + "payload": {"session_id": openviking_session_id, "options": None}, } assert calls[1] == { "method": "add_message", @@ -277,7 +301,7 @@ def test_openviking_backend_sdk_create_payload(monkeypatch): { "method": "create_session", "actor_peer_id": None, - "payload": {"session_id": "sa1"}, + "payload": {"session_id": "sa1", "options": None}, } ] @@ -300,7 +324,8 @@ def test_openviking_backend_uses_configured_memory_policy(monkeypatch): backend._create_session(client=client, session_id="sa1") - assert calls[0]["payload"]["memory_policy"] == memory_policy + assert calls[0]["payload"]["options"]["memory_policy"] == memory_policy + assert "memory_policy" not in calls[0]["payload"] def test_openviking_backend_reads_memory_policy_from_env(monkeypatch): @@ -324,7 +349,8 @@ def test_openviking_backend_reads_memory_policy_from_env(monkeypatch): backend._create_session(client=client, session_id="sa1") - assert calls[0]["payload"]["memory_policy"] == memory_policy + assert calls[0]["payload"]["options"]["memory_policy"] == memory_policy + assert "memory_policy" not in calls[0]["payload"] def test_openviking_backend_sdk_message_payload(monkeypatch): @@ -391,10 +417,12 @@ def test_openviking_backend_search_uses_find_with_actor_peer(monkeypatch): "payload": { "query": "用户偏好", "target_uri": "viking://user/agent_scene/peers/alice/memories", - "context_type": "memory", "limit": 3, + "image": None, + "options": {"context_type": "memory"}, }, } + assert "context_type" not in calls[0]["payload"] def test_openviking_backend_search_falls_back_to_find_without_session(monkeypatch): @@ -426,8 +454,9 @@ def test_openviking_backend_search_falls_back_to_find_without_session(monkeypatc "payload": { "query": "用户偏好", "target_uri": "viking://user/default/peers/alice/memories", - "context_type": "memory", "limit": 3, + "image": None, + "options": {"context_type": "memory"}, }, } ] @@ -510,8 +539,9 @@ def test_openviking_backend_sdk_find_payload(monkeypatch): "payload": { "query": "我的专属暗号和回答风格偏好是什么?", "target_uri": "viking://user/default/peers/a1/memories", - "context_type": "memory", "limit": 10, + "image": None, + "options": {"context_type": "memory"}, }, } ] @@ -729,8 +759,9 @@ async def test_long_term_memory_openviking_search_runs_sdk_off_event_loop_thread assert calls[1]["payload"] == { "query": "用户偏好", "target_uri": "viking://user/agent_scene/peers/alice/memories", - "context_type": "memory", "limit": 2, + "image": None, + "options": {"context_type": "memory"}, } diff --git a/uv.lock b/uv.lock index 373c15611..5096d969c 100644 --- a/uv.lock +++ b/uv.lock @@ -5929,7 +5929,7 @@ requires-dist = [ { name = "opensearch-py", marker = "extra == 'extensions'", specifier = ">=2.8.0" }, { name = "opentelemetry-exporter-otlp", specifier = "==1.37.0" }, { name = "opentelemetry-instrumentation-logging", specifier = ">=0.56b0" }, - { name = "openviking-sdk", specifier = ">=0.1.3" }, + { name = "openviking-sdk", specifier = ">=0.1.9" }, { name = "pillow", specifier = ">=10.0.0" }, { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.2.0" }, { name = "prometheus-client", marker = "extra == 'eval'", specifier = ">=0.22.1" }, diff --git a/veadk/knowledgebase/backends/openviking_backend.py b/veadk/knowledgebase/backends/openviking_backend.py index 28aab4ad9..5bf9c298e 100644 --- a/veadk/knowledgebase/backends/openviking_backend.py +++ b/veadk/knowledgebase/backends/openviking_backend.py @@ -58,6 +58,10 @@ def _parse_float(value: str | None, default: float | None) -> float | None: return float(value) +def _compact_options(options: dict[str, Any]) -> dict[str, Any]: + return {key: value for key, value in options.items() if value is not None} + + def _default_target_uri(openviking_user_id: str, index: str) -> str: openviking_user_id = _validate_safe_path_segment( openviking_user_id, @@ -184,7 +188,7 @@ def _build_client(self) -> Any: except ImportError as second_error: raise ImportError( "OpenViking knowledgebase backend requires 'openviking-sdk'. " - "Please install it via `pip install openviking-sdk>=0.1.3`." + "Please install it via `pip install openviking-sdk>=0.1.9`." ) from second_error logger.debug(f"Fallback import used after error: {first_error}") @@ -233,20 +237,23 @@ def _normalize_target_uri(self, target_uri: str) -> str: @override def add_from_directory(self, directory: str, **kwargs) -> bool: client = self._ensure_client() + options = { + "strict": kwargs.get("strict", False), + "ignore_dirs": kwargs.get("ignore_dirs"), + "include": kwargs.get("include"), + "exclude": kwargs.get("exclude"), + "directly_upload_media": kwargs.get("directly_upload_media", True), + "preserve_structure": kwargs.get("preserve_structure", True), + "watch_interval": kwargs.get("watch_interval", 0), + "args": kwargs.get("args"), + "telemetry": kwargs.get("telemetry", False), + } client.add_resource( path=directory, to=kwargs.get("target_uri", self.target_uri), wait=kwargs.get("wait", self.wait), timeout=kwargs.get("timeout", self.import_timeout), - strict=kwargs.get("strict", False), - ignore_dirs=kwargs.get("ignore_dirs"), - include=kwargs.get("include"), - exclude=kwargs.get("exclude"), - directly_upload_media=kwargs.get("directly_upload_media", True), - preserve_structure=kwargs.get("preserve_structure", True), - watch_interval=kwargs.get("watch_interval", 0), - args=kwargs.get("args"), - telemetry=kwargs.get("telemetry", False), + options=_compact_options(options), ) return True @@ -254,16 +261,19 @@ def add_from_directory(self, directory: str, **kwargs) -> bool: def add_from_files(self, files: list[str], **kwargs) -> bool: client = self._ensure_client() for file in files: + options = { + "strict": kwargs.get("strict", False), + "reason": kwargs.get("reason", ""), + "instruction": kwargs.get("instruction", ""), + "directly_upload_media": kwargs.get("directly_upload_media", True), + "telemetry": kwargs.get("telemetry", False), + } client.add_resource( path=file, parent=kwargs.get("target_uri", self.target_uri), wait=kwargs.get("wait", self.wait), timeout=kwargs.get("timeout", self.import_timeout), - strict=kwargs.get("strict", False), - reason=kwargs.get("reason", ""), - instruction=kwargs.get("instruction", ""), - directly_upload_media=kwargs.get("directly_upload_media", True), - telemetry=kwargs.get("telemetry", False), + options=_compact_options(options), ) return True @@ -286,18 +296,20 @@ def search(self, query: str, top_k: int = 5, **kwargs) -> list[KnowledgebaseEntr client = self._ensure_client() method = client.search if use_context_search else client.find - call_kwargs = { - "query": query, - "target_uri": target_uri, - "limit": top_k, + options = { "score_threshold": score_threshold, "filter": kwargs.get("filter"), "context_type": kwargs.get("context_type"), "tags": kwargs.get("tags"), "telemetry": kwargs.get("telemetry", False), } + call_kwargs = { + "query": query, + "target_uri": target_uri, + "limit": top_k, + "options": _compact_options(options), + } if use_context_search: - call_kwargs["session"] = kwargs.get("session") call_kwargs["session_id"] = kwargs.get("session_id") result = method(**call_kwargs) diff --git a/veadk/memory/long_term_memory_backends/openviking_backend.py b/veadk/memory/long_term_memory_backends/openviking_backend.py index 1c2c7a9ec..a6758595d 100644 --- a/veadk/memory/long_term_memory_backends/openviking_backend.py +++ b/veadk/memory/long_term_memory_backends/openviking_backend.py @@ -206,10 +206,10 @@ def _new_client(self, *, actor_peer_id: str | None = None) -> SyncHTTPClient: def _create_session(self, *, client: SyncHTTPClient, session_id: str) -> None: try: - kwargs: dict[str, Any] = {"session_id": session_id} + options: dict[str, Any] = {} if self.memory_policy is not None: - kwargs["memory_policy"] = self.memory_policy - client.create_session(**kwargs) + options["memory_policy"] = self.memory_policy + client.create_session(session_id=session_id, options=options or None) except Exception as e: if self._is_existing_session_error(e): logger.debug(f"OpenViking session already exists, continue: {e}") @@ -245,8 +245,8 @@ def _search_with_actor_client( return client.find( query=query, target_uri=target_uri, - context_type="memory", limit=top_k, + options={"context_type": "memory"}, ) finally: client.close() From b09a3d7f5dae72f735a4e13286062bfab97c4d19 Mon Sep 17 00:00:00 2001 From: shanchunhua Date: Mon, 28 Sep 2026 11:37:31 +0800 Subject: [PATCH 2/2] test: stabilize codex inactivity timeout test --- tests/cli/test_codex_app_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cli/test_codex_app_server.py b/tests/cli/test_codex_app_server.py index f3c6ce384..0e6b278bf 100644 --- a/tests/cli/test_codex_app_server.py +++ b/tests/cli/test_codex_app_server.py @@ -1791,7 +1791,7 @@ async def send(self, raw: str) -> None: ) elapsed += 2 terminal_callback = loop.call_later( - 0.01, + 0.5, self.queue.put_nowait, json.dumps( {