fix(openviking): fallback summary reads to content/read for file URIs

OpenViking returns 500 for /content/abstract and /content/overview when URI points to mem_*.md files.
Add resilient fallback to /content/read for non-pseudo summary file URIs while preserving pseudo summary normalization.
Also add regression tests for fallback behavior.
This commit is contained in:
hitesh 2026-04-15 18:59:23 -04:00 committed by Teknium
parent bff8ab0311
commit 10e43edc09
2 changed files with 88 additions and 14 deletions

View File

@ -594,16 +594,28 @@ class OpenVikingMemoryProvider(MemoryProvider):
level = args.get("level", "overview")
# OpenViking v0.3.3 expects directory URIs for abstract/overview.
resolved_uri = self._normalize_summary_uri(uri) if level in ("abstract", "overview") else uri
summary_level = level in ("abstract", "overview")
# OpenViking expects directory URIs for pseudo summary files
# (e.g. viking://user/hermes/.overview.md).
resolved_uri = self._normalize_summary_uri(uri) if summary_level else uri
used_fallback = False
# Map our level names to OpenViking GET endpoints
# Map our level names to OpenViking GET endpoints.
endpoint = "/api/v1/content/read"
if level == "abstract":
resp = self._client.get("/api/v1/content/abstract", params={"uri": resolved_uri})
elif level == "full":
resp = self._client.get("/api/v1/content/read", params={"uri": resolved_uri})
else: # overview
resp = self._client.get("/api/v1/content/overview", params={"uri": resolved_uri})
endpoint = "/api/v1/content/abstract"
elif level == "overview":
endpoint = "/api/v1/content/overview"
try:
resp = self._client.get(endpoint, params={"uri": resolved_uri})
except Exception:
# OpenViking may return HTTP 500 for abstract/overview reads on normal
# file URIs (mem_*.md). For those, gracefully fallback to full read.
if not summary_level or resolved_uri != uri:
raise
resp = self._client.get("/api/v1/content/read", params={"uri": uri})
used_fallback = True
result = self._unwrap_result(resp)
# Content endpoints may return either plain strings or objects.
@ -614,16 +626,26 @@ class OpenVikingMemoryProvider(MemoryProvider):
else:
content = ""
# Truncate very long content to avoid flooding the context
if len(content) > 8000:
content = content[:8000] + "\n\n[... truncated, use a more specific URI or abstract level]"
# Truncate long content to avoid flooding context.
max_len = 8000
if level == "overview":
max_len = 4000
elif level == "abstract":
max_len = 1200
return json.dumps({
if len(content) > max_len:
content = content[:max_len] + "\n\n[... truncated, use a more specific URI or full level]"
payload = {
"uri": uri,
"resolved_uri": resolved_uri,
"level": level,
"content": content,
}, ensure_ascii=False)
}
if used_fallback:
payload["fallback"] = "content/read"
return json.dumps(payload, ensure_ascii=False)
def _tool_browse(self, args: dict) -> str:
action = args.get("action", "list")

View File

@ -12,7 +12,10 @@ class FakeVikingClient:
def get(self, path, params=None, **kwargs):
self.calls.append((path, params or {}))
return self.responses[(path, tuple(sorted((params or {}).items())))]
response = self.responses[(path, tuple(sorted((params or {}).items())))]
if isinstance(response, Exception):
raise response
return response
class TestOpenVikingSummaryUriNormalization:
@ -68,6 +71,55 @@ class TestOpenVikingRead:
{"uri": "viking://user/hermes/memories/profile.md"},
)]
def test_overview_file_uri_falls_back_to_content_read_on_summary_error(self):
provider = OpenVikingMemoryProvider()
file_uri = "viking://user/hermes/memories/entities/mem_abc.md"
provider._client = FakeVikingClient(
{
(
"/api/v1/content/overview",
(("uri", file_uri),),
): RuntimeError("500 Internal Server Error"),
(
"/api/v1/content/read",
(("uri", file_uri),),
): {"result": {"content": "fallback full content"}},
}
)
result = json.loads(provider._tool_read({"uri": file_uri, "level": "overview"}))
assert result["uri"] == file_uri
assert result["resolved_uri"] == file_uri
assert result["level"] == "overview"
assert result["fallback"] == "content/read"
assert result["content"] == "fallback full content"
assert provider._client.calls == [
("/api/v1/content/overview", {"uri": file_uri}),
("/api/v1/content/read", {"uri": file_uri}),
]
def test_summary_uri_error_does_not_fallback_and_raises(self):
provider = OpenVikingMemoryProvider()
provider._client = FakeVikingClient(
{
(
"/api/v1/content/overview",
(("uri", "viking://user/hermes"),),
): RuntimeError("500 Internal Server Error"),
}
)
try:
provider._tool_read({"uri": "viking://user/hermes/.overview.md", "level": "overview"})
assert False, "Expected summary endpoint error to be raised"
except RuntimeError:
pass
assert provider._client.calls == [
("/api/v1/content/overview", {"uri": "viking://user/hermes"}),
]
class TestOpenVikingBrowse:
def test_list_browse_unwraps_and_normalizes_entry_shapes(self):