forked from HKUDS/OpenSpace
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathretrieve_memory_tool.py
More file actions
140 lines (117 loc) · 5.07 KB
/
Copy pathretrieve_memory_tool.py
File metadata and controls
140 lines (117 loc) · 5.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
"""RetrieveMemoryTool — mid-iteration Viking memory retrieval for GroundingAgent.
Registered as an internal tool alongside ``retrieve_skill`` so the LLM
can pull Viking memories (tool knowledge, patterns, past cases,
anti-patterns) during execution when the initial enrichment is
insufficient.
The pre-execution enrichment in ``tool_layer.execute()`` injects L0
abstracts into iter-1 and strips them at iter-2. This tool gives the
agent a way to re-query dynamically if it discovers mid-execution that
it needs additional cross-session context — e.g. when a tool fails
unexpectedly and the agent wants to check for known issues.
Unlike the pre-execution path, this tool accepts an optional
``category`` argument so the agent can target the most relevant
memory bucket (``tools`` for fail reasons, ``antipatterns`` to check
if an approach has been tried before, etc.).
"""
from __future__ import annotations
from typing import TYPE_CHECKING, List, Optional
from openspace.grounding.core.tool.local_tool import LocalTool
from openspace.grounding.core.types import BackendType
from openspace.utils.logging import Logger
if TYPE_CHECKING:
from .client import OpenVikingClient
logger = Logger.get_logger(__name__)
_VALID_CATEGORIES = {
"tools", "patterns", "skills", "cases", "preferences", "antipatterns",
}
class RetrieveMemoryTool(LocalTool):
"""Internal tool: mid-iteration cross-session memory retrieval.
Thin wrapper around :class:`OpenVikingClient` — formats the result
as a plain-text block suitable for direct injection into the LLM
context. Fails gracefully (returns an explanatory string, never
raises) so the iteration loop is never broken by Viking issues.
"""
_name = "retrieve_memory"
_description = (
"Query the cross-session memory store (OpenViking) for relevant "
"knowledge when the current approach isn't working. Returns "
"abstracts from prior tasks: tool knowledge, patterns, past "
"cases, known failure modes (anti-patterns), and user preferences. "
"Use this when encountering an unexpected error, before trying a "
"risky approach, or when you need user preference context mid-task. "
"Arguments: query (required), category (optional: tools, patterns, "
"skills, cases, preferences, antipatterns — omit to search all)."
)
backend_type = BackendType.SYSTEM
def __init__(
self,
viking_client: "OpenVikingClient",
score_threshold: float = 0.0,
):
super().__init__()
self._client = viking_client
self._score_threshold = score_threshold
async def _arun(
self,
query: str,
category: Optional[str] = None,
limit: int = 5,
) -> str:
if not query or not isinstance(query, str):
return "retrieve_memory error: query is required."
limit = max(1, min(int(limit or 5), 10))
try:
if not await self._client.is_available():
return "retrieve_memory: OpenViking server is not available."
except Exception as exc:
return f"retrieve_memory: health check failed: {exc}"
# Normalize category → target_uri
norm: Optional[str] = None
target_uri = ""
if category:
raw = category.strip().lower().replace("-", "_")
if raw in _VALID_CATEGORIES:
norm = raw
if raw == "preferences":
target_uri = self._client.user_memory_uri("preferences")
else:
target_uri = self._client.agent_memory_uri(raw)
else:
return (
f"retrieve_memory error: invalid category '{category}'. "
f"Valid: {sorted(_VALID_CATEGORIES)}."
)
try:
results = await self._client.find_memories(
query,
target_uri=target_uri,
limit=limit,
score_threshold=self._score_threshold,
)
except Exception as exc:
return f"retrieve_memory error: {exc}"
if not results:
scope = norm or "all categories"
return f"retrieve_memory: no matches found for '{query[:80]}' in {scope}."
lines: List[str] = [
f"# Retrieved from OpenViking — query: {query[:80]!r}",
f"# Category: {norm or 'all'}, {len(results)} result(s)",
"",
]
for i, m in enumerate(results, 1):
abstract = (
m.get("abstract") or m.get("summary")
or m.get("content") or m.get("uri", "")
)
score = m.get("score", 0.0)
uri = m.get("uri", "")
lines.append(f"{i}. [score={score:.2f}] {abstract}")
if uri:
lines.append(f" uri: {uri}")
if norm == "antipatterns":
lines.insert(
3,
"## WARNING: these are FAILED approaches. Do NOT repeat them "
"unless you have evidence they will work now.\n"
)
return "\n".join(lines)