oc_chat
Loading...
Searching...
No Matches
memory.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oc_chat/tools/memory.py
4
5"""
6Persistent memory via ChromaDB vector store.
7
8Purpose
9-------
10Provides :func:`remember` and :func:`recall` for long-term semantic
11memory across REPL sessions. Backed by a local ChromaDB persistent
12client at ``./memory_store/`` with cosine-similarity search.
13
14Falls back to the structured ``missing_dependency`` shape that the
15dependency healer can intercept when ``chromadb`` is not installed.
16
17Responsibilities
18----------------
19- :func:`_get_collection` — lazily build (or reload) the persistent
20 collection on first access.
21- :func:`remember` — embed and store a text chunk with optional
22 metadata; returns the assigned id.
23- :func:`recall` — semantic-similarity query returning the top-k
24 matches with their metadata and distances.
25
26Requires: ``pip install chromadb``
27
28Diagnostics
29-----------
30Domain: OC_CHAT.TOOLS
31Levels:
32 L2 — lifecycle events
33 L3 — semantic details
34 L4 — deep tracing
35
36Contracts
37---------
38- The collection is process-singleton — repeat calls reuse the same
39 client/collection handles.
40- Missing ``chromadb`` returns ``{"missing_dependency": "chromadb",
41 ...}`` rather than raising, so the healer can offer to install.
42- The on-disk store at ``memory_store/`` is opaque to callers; only
43 the public ``remember`` / ``recall`` API is supported.
44
45Dependencies
46------------
47- oc_diagnostics (structured debug logging via _dbg)
48- oc_chat.tools.filesystem (lazy; _sandbox_root - places the memory store under the effective sandbox root)
49- chromadb (lazy; the persistent vector store client and its cosine-similarity collection, optional)
50
51See Also
52--------
53oc_chat/docs/conventions/CONTRACTS.md §3 Tool registry contract; §4 Tool return-
54value conventions.
55"""
56from pathlib import Path
57
58from oc_diagnostics import _dbg
59
60def _store_dir() -> str:
61 """Resolve the memory store dir under the effective sandbox root.
62
63 Resolved at first use (not import time) so a session sandbox override set
64 after import is honoured — keeping the long-term memory store aligned with
65 the read/write sandbox (M8 F77; M8.0 WU-1) instead of the import-time
66 process cwd.
67 """
68 from oc_chat.tools.filesystem import _sandbox_root
69
70 return str(_sandbox_root() / "memory_store")
71_COLLECTION_NAME = "oc_chat_memory"
72_client = None
73_collection = None
74
75
77 """Return (or lazily create) the ChromaDB collection."""
78 func = "_get_collection"
79 global _client, _collection
80 if _collection is not None:
81 return _collection
82 try:
83 import chromadb
84 except ImportError:
85 raise ImportError("chromadb not installed. Run: pip install chromadb")
86
87 store_dir = _store_dir()
88 _dbg("OC_CHAT.TOOLS", func, f"first-time init at {store_dir}", 2)
89 _client = chromadb.PersistentClient(path=store_dir)
90 _collection = _client.get_or_create_collection(
91 name=_COLLECTION_NAME,
92 metadata={"hnsw:space": "cosine"},
93 )
94 return _collection
95
96
97def remember(text: str, metadata: dict | None = None) -> dict:
98 """Store a piece of information in long-term persistent memory."""
99 try:
100 col = _get_collection()
101 except ImportError as e:
102 return {"error": str(e)}
103
104 import uuid
105 doc_id = str(uuid.uuid4())
106 col.add(documents=[text], ids=[doc_id], metadatas=[metadata or {}])
107 return {"stored": True, "id": doc_id}
108
109
110def recall(query: str, n_results: int = 5) -> dict:
111 """Retrieve relevant memories using semantic similarity search."""
112 try:
113 col = _get_collection()
114 except ImportError as e:
115 return {"error": str(e)}
116
117 count = col.count()
118 if count == 0:
119 return {"memories": [], "note": "Memory store is empty."}
120
121 results = col.query(
122 query_texts=[query],
123 n_results=min(n_results, count),
124 )
125 memories = []
126 for doc, meta in zip(
127 results.get("documents", [[]])[0],
128 results.get("metadatas", [[]])[0],
129 ):
130 memories.append({"text": doc, "metadata": meta})
131
132 return {"memories": memories}
133
134
135TOOLS = {
136 "remember": remember,
137 "recall": recall,
138}
139
140SCHEMAS = [
141 {
142 "type": "function",
143 "function": {
144 "name": "remember",
145 "description": "Store a piece of information in long-term persistent memory.",
146 "parameters": {
147 "type": "object",
148 "properties": {
149 "text": {"type": "string", "description": "Information to remember"},
150 "metadata": {"type": "object", "description": "Optional key-value metadata"},
151 },
152 "required": ["text"],
153 },
154 },
155 },
156 {
157 "type": "function",
158 "function": {
159 "name": "recall",
160 "description": "Retrieve relevant memories using semantic similarity search.",
161 "parameters": {
162 "type": "object",
163 "properties": {
164 "query": {"type": "string", "description": "Search query"},
165 "n_results": {"type": "integer", "description": "Number of memories to return (default 5)"},
166 },
167 "required": ["query"],
168 },
169 },
170 },
171]
dict recall(str query, int n_results=5)
Definition memory.py:110
dict remember(str text, dict|None metadata=None)
Definition memory.py:97