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:
parent
bff8ab0311
commit
10e43edc09
@ -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")
|
||||
|
||||
@ -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):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user