cortexdb-agno 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.
- cortexdb_agno-0.1.0/.gitignore +79 -0
- cortexdb_agno-0.1.0/PKG-INFO +47 -0
- cortexdb_agno-0.1.0/README.md +34 -0
- cortexdb_agno-0.1.0/cortexdb_agno/__init__.py +13 -0
- cortexdb_agno-0.1.0/cortexdb_agno/memory.py +113 -0
- cortexdb_agno-0.1.0/cortexdb_agno/tools.py +97 -0
- cortexdb_agno-0.1.0/pyproject.toml +21 -0
- cortexdb_agno-0.1.0/tests/__init__.py +0 -0
- cortexdb_agno-0.1.0/tests/test_agno.py +150 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Rust
|
|
2
|
+
/target
|
|
3
|
+
**/*.rs.bk
|
|
4
|
+
|
|
5
|
+
# Environment / secrets
|
|
6
|
+
.env
|
|
7
|
+
.env.local
|
|
8
|
+
.env*.local
|
|
9
|
+
*.pem
|
|
10
|
+
*.key
|
|
11
|
+
.npmrc
|
|
12
|
+
|
|
13
|
+
# SQLite database
|
|
14
|
+
*.sqlite
|
|
15
|
+
*.sqlite-wal
|
|
16
|
+
*.sqlite-shm
|
|
17
|
+
|
|
18
|
+
# OS
|
|
19
|
+
.DS_Store
|
|
20
|
+
Thumbs.db
|
|
21
|
+
desktop.ini
|
|
22
|
+
|
|
23
|
+
# IDE
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
*.swp
|
|
27
|
+
*.swo
|
|
28
|
+
|
|
29
|
+
# Data directories
|
|
30
|
+
cortexdb_data*/
|
|
31
|
+
/data/
|
|
32
|
+
# Per-bench tenant stores (RocksDB + Tantivy + HNSW state; regeneratable per run)
|
|
33
|
+
/data_*/
|
|
34
|
+
# Experimental per-branch stores (not tracked on this branch but left gitignored
|
|
35
|
+
# so checkout from other branches doesn't surface them in git status)
|
|
36
|
+
/event_memory_store/
|
|
37
|
+
/llm_cache/
|
|
38
|
+
|
|
39
|
+
# Benchmark inputs and per-run outputs (kept local, regenerated each run)
|
|
40
|
+
benchmarks/longmemeval/data/
|
|
41
|
+
benchmarks/longmemeval/server_results/
|
|
42
|
+
benchmarks/longmemeval/fast_results/
|
|
43
|
+
benchmarks/longmemeval/micro_results/
|
|
44
|
+
benchmarks/longmemeval/server_logs/
|
|
45
|
+
benchmarks/longmemeval/*.log
|
|
46
|
+
benchmarks/locomo/locomo_results*.json
|
|
47
|
+
benchmarks/locomo/server_results/
|
|
48
|
+
benchmarks/locomo/*.log
|
|
49
|
+
/answer_out.json
|
|
50
|
+
|
|
51
|
+
# Local Claude Code state
|
|
52
|
+
.claude/
|
|
53
|
+
.tmp/
|
|
54
|
+
|
|
55
|
+
# Python
|
|
56
|
+
__pycache__/
|
|
57
|
+
*.pyc
|
|
58
|
+
.venv/
|
|
59
|
+
venv/
|
|
60
|
+
|
|
61
|
+
# Node
|
|
62
|
+
node_modules/
|
|
63
|
+
dist/
|
|
64
|
+
.next/
|
|
65
|
+
|
|
66
|
+
# Egg info
|
|
67
|
+
*.egg-info/
|
|
68
|
+
|
|
69
|
+
# Scratch/debug text files at root
|
|
70
|
+
/*.txt
|
|
71
|
+
/*.log
|
|
72
|
+
|
|
73
|
+
# Local debug / marketing / private content (not for repo)
|
|
74
|
+
harness/.reports/
|
|
75
|
+
harness_data_*/
|
|
76
|
+
blog/
|
|
77
|
+
sales/
|
|
78
|
+
videos/
|
|
79
|
+
local-instance/
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cortexdb-agno
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agno integration for CortexDB — long-term memory for AI systems
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: agno>=1.0
|
|
8
|
+
Requires-Dist: cortexdbai>=0.1.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
11
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# cortexdb-agno
|
|
15
|
+
|
|
16
|
+
Agno integration for CortexDB long-term memory.
|
|
17
|
+
|
|
18
|
+
> **LLM provider note (audit BLK-2):** the canonical example uses
|
|
19
|
+
> `from agno.models.openai import OpenAIChat`. CortexDB itself is
|
|
20
|
+
> LLM-agnostic — Agno picks the model. Swap providers with one line:
|
|
21
|
+
>
|
|
22
|
+
> ```python
|
|
23
|
+
> # OpenAI (default in the docs)
|
|
24
|
+
> from agno.models.openai import OpenAIChat
|
|
25
|
+
> model = OpenAIChat(id="gpt-4o")
|
|
26
|
+
>
|
|
27
|
+
> # Anthropic
|
|
28
|
+
> from agno.models.anthropic import Claude
|
|
29
|
+
> model = Claude(id="claude-sonnet-4-6")
|
|
30
|
+
>
|
|
31
|
+
> # Google
|
|
32
|
+
> from agno.models.google import Gemini
|
|
33
|
+
> model = Gemini(id="gemini-1.5-pro")
|
|
34
|
+
> ```
|
|
35
|
+
>
|
|
36
|
+
> Nothing in `cortexdb-agno` reads `OPENAI_API_KEY`.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install cortexdb-agno
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API base URL
|
|
45
|
+
|
|
46
|
+
Defaults to `https://api-v1.cortexdb.ai` (audit FRI-8). Override with
|
|
47
|
+
`CORTEXDB_API_URL`.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# cortexdb-agno
|
|
2
|
+
|
|
3
|
+
Agno integration for CortexDB long-term memory.
|
|
4
|
+
|
|
5
|
+
> **LLM provider note (audit BLK-2):** the canonical example uses
|
|
6
|
+
> `from agno.models.openai import OpenAIChat`. CortexDB itself is
|
|
7
|
+
> LLM-agnostic — Agno picks the model. Swap providers with one line:
|
|
8
|
+
>
|
|
9
|
+
> ```python
|
|
10
|
+
> # OpenAI (default in the docs)
|
|
11
|
+
> from agno.models.openai import OpenAIChat
|
|
12
|
+
> model = OpenAIChat(id="gpt-4o")
|
|
13
|
+
>
|
|
14
|
+
> # Anthropic
|
|
15
|
+
> from agno.models.anthropic import Claude
|
|
16
|
+
> model = Claude(id="claude-sonnet-4-6")
|
|
17
|
+
>
|
|
18
|
+
> # Google
|
|
19
|
+
> from agno.models.google import Gemini
|
|
20
|
+
> model = Gemini(id="gemini-1.5-pro")
|
|
21
|
+
> ```
|
|
22
|
+
>
|
|
23
|
+
> Nothing in `cortexdb-agno` reads `OPENAI_API_KEY`.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install cortexdb-agno
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API base URL
|
|
32
|
+
|
|
33
|
+
Defaults to `https://api-v1.cortexdb.ai` (audit FRI-8). Override with
|
|
34
|
+
`CORTEXDB_API_URL`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""CortexDB integration for Agno.
|
|
2
|
+
|
|
3
|
+
Provides a memory backend and agent tools that allow Agno agents
|
|
4
|
+
to use CortexDB as their long-term memory system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from cortexdb_agno.memory import CortexDBMemory
|
|
8
|
+
from cortexdb_agno.tools import CortexDBTools
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"CortexDBMemory",
|
|
12
|
+
"CortexDBTools",
|
|
13
|
+
]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Agno memory backend powered by CortexDB.
|
|
2
|
+
|
|
3
|
+
Provides a memory implementation compatible with Agno's memory system,
|
|
4
|
+
backed by CortexDB's long-term memory. This enables Agno agents to
|
|
5
|
+
persist and recall knowledge across sessions and conversations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
from cortexdb import Cortex
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CortexDBMemory:
|
|
16
|
+
"""Agno memory backend that stores and retrieves context via CortexDB.
|
|
17
|
+
|
|
18
|
+
Implements Agno's ``Memory`` interface backed by CortexDB's semantic
|
|
19
|
+
storage. This enables agents to share knowledge across sessions,
|
|
20
|
+
remember outcomes from previous runs, and build up persistent
|
|
21
|
+
context over time.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
client: An initialized CortexDB client instance.
|
|
25
|
+
scope: The hierarchical scope path for memory isolation.
|
|
26
|
+
|
|
27
|
+
Example::
|
|
28
|
+
|
|
29
|
+
from cortexdb import Cortex
|
|
30
|
+
from cortexdb_agno import CortexDBMemory
|
|
31
|
+
|
|
32
|
+
client = Cortex("http://localhost:3141")
|
|
33
|
+
memory = CortexDBMemory(client=client, scope="user:default")
|
|
34
|
+
|
|
35
|
+
# Store a memory
|
|
36
|
+
memory.add("The deployment window is 2-4am UTC on Tuesdays.")
|
|
37
|
+
|
|
38
|
+
# Search memories
|
|
39
|
+
results = memory.search("deployment schedule")
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
client: Cortex,
|
|
45
|
+
scope: str = "user:default",
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Initialize CortexDBMemory with a CortexDB client.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
client: An initialized CortexDB client instance.
|
|
51
|
+
scope: The hierarchical scope path for memory isolation.
|
|
52
|
+
"""
|
|
53
|
+
self._client = client
|
|
54
|
+
self._scope = scope
|
|
55
|
+
|
|
56
|
+
def add(self, content: str, metadata: Optional[dict[str, Any]] = None) -> dict[str, Any]:
|
|
57
|
+
"""Store a memory in CortexDB.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
content: The text content to store.
|
|
61
|
+
metadata: Optional metadata to associate with the memory.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The experience response dict from CortexDB.
|
|
65
|
+
"""
|
|
66
|
+
return self._client.experience(self._scope, text=content)
|
|
67
|
+
|
|
68
|
+
def search(self, query: str) -> dict[str, Any]:
|
|
69
|
+
"""Search for relevant memories in CortexDB.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
query: The search query string.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
A dict with a ``context`` key holding the recalled context block.
|
|
76
|
+
"""
|
|
77
|
+
result = self._client.recall(self._scope, query=query)
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
"context": result.get("context_block", "") if result else "",
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
def clear(self) -> dict[str, Any]:
|
|
84
|
+
"""Clear all memories for the current scope.
|
|
85
|
+
|
|
86
|
+
Issues a forget command to CortexDB to remove all stored memories.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The forget response dict from CortexDB.
|
|
90
|
+
"""
|
|
91
|
+
return self._client.forget(
|
|
92
|
+
self._scope,
|
|
93
|
+
confirm_all=True,
|
|
94
|
+
cascade="redact_events",
|
|
95
|
+
reason="Agno memory clear requested",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def get_context(self, query: str) -> str:
|
|
99
|
+
"""Retrieve formatted context for an agent prompt.
|
|
100
|
+
|
|
101
|
+
Convenience method that fetches relevant memories and formats them
|
|
102
|
+
as a single context string suitable for injection into an agent's
|
|
103
|
+
system prompt or message history.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
query: The query to find relevant context for.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
A formatted string of relevant memories.
|
|
110
|
+
"""
|
|
111
|
+
result = self._client.recall(self._scope, query=query)
|
|
112
|
+
|
|
113
|
+
return result.get("context_block", "") if result else ""
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Agno tools for interacting with CortexDB.
|
|
2
|
+
|
|
3
|
+
Provides a toolkit of tools that Agno agents can use to store, search,
|
|
4
|
+
and forget memories in CortexDB's long-term memory system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from agno.tools import Toolkit
|
|
12
|
+
|
|
13
|
+
from cortexdb import Cortex
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CortexDBTools(Toolkit):
|
|
17
|
+
"""Toolkit that creates Agno-compatible tools for CortexDB operations.
|
|
18
|
+
|
|
19
|
+
Provides search, store, and forget tools that can be passed directly
|
|
20
|
+
to an Agno ``Agent``.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
client: An initialized CortexDB client instance.
|
|
24
|
+
scope: The hierarchical scope path for memory isolation.
|
|
25
|
+
|
|
26
|
+
Example::
|
|
27
|
+
|
|
28
|
+
from agno.agent import Agent
|
|
29
|
+
from cortexdb import Cortex
|
|
30
|
+
from cortexdb_agno import CortexDBTools
|
|
31
|
+
|
|
32
|
+
client = Cortex("https://api-v1.cortexdb.ai", actor="user:app", bearer="v4.public...")
|
|
33
|
+
toolkit = CortexDBTools(client=client, scope="user:default")
|
|
34
|
+
|
|
35
|
+
agent = Agent(
|
|
36
|
+
name="Research Assistant",
|
|
37
|
+
tools=[toolkit],
|
|
38
|
+
)
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
client: Cortex,
|
|
44
|
+
scope: str = "user:default",
|
|
45
|
+
) -> None:
|
|
46
|
+
super().__init__(name="cortexdb")
|
|
47
|
+
self._client = client
|
|
48
|
+
self._scope = scope
|
|
49
|
+
self.register(self.search_memory)
|
|
50
|
+
self.register(self.store_memory)
|
|
51
|
+
self.register(self.forget_memory)
|
|
52
|
+
|
|
53
|
+
def search_memory(self, query: str) -> str:
|
|
54
|
+
"""Search CortexDB for relevant memories and past context.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
query: A natural language query describing what to search for.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The retrieved context string from CortexDB.
|
|
61
|
+
"""
|
|
62
|
+
result = self._client.recall(self._scope, query=query)
|
|
63
|
+
|
|
64
|
+
context = result.get("context_block", "") if result else ""
|
|
65
|
+
if not context:
|
|
66
|
+
return "No relevant memories found in CortexDB."
|
|
67
|
+
|
|
68
|
+
return context
|
|
69
|
+
|
|
70
|
+
def store_memory(self, content: str) -> str:
|
|
71
|
+
"""Store information in CortexDB's long-term memory.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
content: The information to store as a memory.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
A confirmation message.
|
|
78
|
+
"""
|
|
79
|
+
self._client.experience(self._scope, text=content)
|
|
80
|
+
return "Memory stored successfully in CortexDB."
|
|
81
|
+
|
|
82
|
+
def forget_memory(self, reason: str) -> str:
|
|
83
|
+
"""Forget all memories in this scope from CortexDB.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
reason: The reason for forgetting these memories.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
A confirmation message.
|
|
90
|
+
"""
|
|
91
|
+
self._client.forget(
|
|
92
|
+
self._scope,
|
|
93
|
+
confirm_all=True,
|
|
94
|
+
cascade="redact_events",
|
|
95
|
+
reason=reason,
|
|
96
|
+
)
|
|
97
|
+
return f"Memories in scope '{self._scope}' have been forgotten."
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cortexdb-agno"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Agno integration for CortexDB — long-term memory for AI systems"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"cortexdbai>=0.1.0",
|
|
14
|
+
"agno>=1.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=7.0",
|
|
20
|
+
"pytest-asyncio>=0.21",
|
|
21
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Tests for Agno CortexDB integration.
|
|
2
|
+
|
|
3
|
+
Validates the CortexDBMemory backend and CortexDBTools toolkit against
|
|
4
|
+
the v1 SDK surface, without requiring a live CortexDB or Agno install.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import unittest
|
|
11
|
+
from unittest.mock import MagicMock, patch
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Build mock modules before importing integration code.
|
|
15
|
+
mock_agno_tools = MagicMock()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _MockToolkit:
|
|
19
|
+
"""Stand-in for agno.tools.Toolkit."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, name="", **kwargs):
|
|
22
|
+
self.name = name
|
|
23
|
+
self.registered = []
|
|
24
|
+
|
|
25
|
+
def register(self, fn):
|
|
26
|
+
self.registered.append(fn)
|
|
27
|
+
return fn
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
mock_agno_tools.Toolkit = _MockToolkit
|
|
31
|
+
|
|
32
|
+
mock_agno = MagicMock()
|
|
33
|
+
mock_agno.tools = mock_agno_tools
|
|
34
|
+
mock_agno.agent = MagicMock()
|
|
35
|
+
|
|
36
|
+
mock_cortexdb = MagicMock()
|
|
37
|
+
|
|
38
|
+
with patch.dict(sys.modules, {
|
|
39
|
+
"agno": mock_agno,
|
|
40
|
+
"agno.tools": mock_agno_tools,
|
|
41
|
+
"agno.agent": mock_agno.agent,
|
|
42
|
+
"cortexdb": mock_cortexdb,
|
|
43
|
+
}):
|
|
44
|
+
from cortexdb_agno.memory import CortexDBMemory
|
|
45
|
+
from cortexdb_agno.tools import CortexDBTools
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TestCortexDBMemory(unittest.TestCase):
|
|
49
|
+
"""Tests for the CortexDBMemory class."""
|
|
50
|
+
|
|
51
|
+
def setUp(self) -> None:
|
|
52
|
+
self.client = MagicMock()
|
|
53
|
+
self.memory = CortexDBMemory(
|
|
54
|
+
client=self.client,
|
|
55
|
+
scope="user:test",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def test_init_stores_config(self) -> None:
|
|
59
|
+
self.assertIs(self.memory._client, self.client)
|
|
60
|
+
self.assertEqual(self.memory._scope, "user:test")
|
|
61
|
+
|
|
62
|
+
def test_init_defaults(self) -> None:
|
|
63
|
+
m = CortexDBMemory(client=self.client)
|
|
64
|
+
self.assertEqual(m._scope, "user:default")
|
|
65
|
+
|
|
66
|
+
def test_add_calls_experience(self) -> None:
|
|
67
|
+
self.memory.add("test content")
|
|
68
|
+
self.client.experience.assert_called_once_with(
|
|
69
|
+
"user:test", text="test content",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def test_search_calls_recall(self) -> None:
|
|
73
|
+
self.client.recall.return_value = {"context_block": "result"}
|
|
74
|
+
results = self.memory.search("query")
|
|
75
|
+
self.client.recall.assert_called_once_with("user:test", query="query")
|
|
76
|
+
self.assertEqual(results["context"], "result")
|
|
77
|
+
|
|
78
|
+
def test_search_empty(self) -> None:
|
|
79
|
+
self.client.recall.return_value = {"context_block": ""}
|
|
80
|
+
results = self.memory.search("nothing")
|
|
81
|
+
self.assertEqual(results["context"], "")
|
|
82
|
+
|
|
83
|
+
def test_clear_calls_forget(self) -> None:
|
|
84
|
+
self.memory.clear()
|
|
85
|
+
self.client.forget.assert_called_once_with(
|
|
86
|
+
"user:test",
|
|
87
|
+
confirm_all=True,
|
|
88
|
+
cascade="redact_events",
|
|
89
|
+
reason="Agno memory clear requested",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def test_get_context_returns_string(self) -> None:
|
|
93
|
+
self.client.recall.return_value = {"context_block": "first second"}
|
|
94
|
+
context = self.memory.get_context("query")
|
|
95
|
+
self.assertIn("first", context)
|
|
96
|
+
|
|
97
|
+
def test_get_context_empty(self) -> None:
|
|
98
|
+
self.client.recall.return_value = {"context_block": ""}
|
|
99
|
+
context = self.memory.get_context("query")
|
|
100
|
+
self.assertEqual(context, "")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class TestCortexDBTools(unittest.TestCase):
|
|
104
|
+
"""Tests for the CortexDBTools toolkit."""
|
|
105
|
+
|
|
106
|
+
def setUp(self) -> None:
|
|
107
|
+
self.client = MagicMock()
|
|
108
|
+
self.toolkit = CortexDBTools(
|
|
109
|
+
client=self.client,
|
|
110
|
+
scope="user:test",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def test_registers_three_tools(self) -> None:
|
|
114
|
+
self.assertEqual(len(self.toolkit.registered), 3)
|
|
115
|
+
|
|
116
|
+
def test_search_tool_calls_recall(self) -> None:
|
|
117
|
+
self.client.recall.return_value = {"context_block": "found"}
|
|
118
|
+
result = self.toolkit.search_memory("test query")
|
|
119
|
+
self.client.recall.assert_called_once_with("user:test", query="test query")
|
|
120
|
+
self.assertIn("found", result)
|
|
121
|
+
|
|
122
|
+
def test_search_tool_no_results(self) -> None:
|
|
123
|
+
self.client.recall.return_value = {"context_block": ""}
|
|
124
|
+
result = self.toolkit.search_memory("nothing")
|
|
125
|
+
self.assertIn("No relevant memories", result)
|
|
126
|
+
|
|
127
|
+
def test_store_tool_calls_experience(self) -> None:
|
|
128
|
+
result = self.toolkit.store_memory("important info")
|
|
129
|
+
self.client.experience.assert_called_once_with(
|
|
130
|
+
"user:test", text="important info",
|
|
131
|
+
)
|
|
132
|
+
self.assertIn("stored successfully", result)
|
|
133
|
+
|
|
134
|
+
def test_forget_tool_calls_forget(self) -> None:
|
|
135
|
+
result = self.toolkit.forget_memory("cleanup")
|
|
136
|
+
self.client.forget.assert_called_once_with(
|
|
137
|
+
"user:test",
|
|
138
|
+
confirm_all=True,
|
|
139
|
+
cascade="redact_events",
|
|
140
|
+
reason="cleanup",
|
|
141
|
+
)
|
|
142
|
+
self.assertIn("forgotten", result)
|
|
143
|
+
|
|
144
|
+
def test_default_config(self) -> None:
|
|
145
|
+
toolkit = CortexDBTools(client=self.client)
|
|
146
|
+
self.assertEqual(toolkit._scope, "user:default")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
unittest.main()
|