156 lines
5.2 KiB
Python
156 lines
5.2 KiB
Python
"""Pinecone memory manager — namespace-based session memory for agents.
|
|
|
|
Provides helpers for storing and retrieving agent conversation memory
|
|
using Pinecone namespaces. Each session gets its own namespace for isolation,
|
|
with cross-session search available via the global namespace.
|
|
|
|
Usage:
|
|
export PINECONE_API_KEY="your-key"
|
|
export OPENAI_API_KEY="your-key"
|
|
python memory_manager.py --index-name agent-memory --action store \
|
|
--session-id sess-001 --text "User discussed project architecture"
|
|
python memory_manager.py --index-name agent-memory --action recall \
|
|
--query "architecture decisions"
|
|
python memory_manager.py --index-name agent-memory --action cleanup \
|
|
--session-id sess-001
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
|
|
def get_pinecone_client():
|
|
"""Initialize Pinecone client from environment."""
|
|
try:
|
|
from pinecone import Pinecone
|
|
except ImportError:
|
|
print("Error: pinecone-client not installed. Run: pip install pinecone-client", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
api_key = os.environ.get("PINECONE_API_KEY")
|
|
if not api_key:
|
|
print("Error: PINECONE_API_KEY environment variable not set.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
return Pinecone(api_key=api_key)
|
|
|
|
|
|
def get_embeddings():
|
|
"""Get the embedding model."""
|
|
try:
|
|
from langchain_openai import OpenAIEmbeddings
|
|
except ImportError:
|
|
print("Error: langchain-openai not installed. Run: pip install langchain-openai", file=sys.stderr)
|
|
sys.exit(1)
|
|
return OpenAIEmbeddings()
|
|
|
|
|
|
def store_memory(index, session_id: str, text: str, metadata: dict | None = None):
|
|
"""Store a memory entry in the session namespace."""
|
|
embeddings = get_embeddings()
|
|
vector = embeddings.embed_query(text)
|
|
|
|
doc_id = hashlib.sha256(f"{session_id}:{text}:{time.time()}".encode()).hexdigest()[:16]
|
|
entry_metadata = {
|
|
"text": text[:1000],
|
|
"session_id": session_id,
|
|
"timestamp": int(time.time()),
|
|
}
|
|
if metadata:
|
|
entry_metadata.update(metadata)
|
|
|
|
index.upsert(
|
|
vectors=[{"id": doc_id, "values": vector, "metadata": entry_metadata}],
|
|
namespace=session_id,
|
|
)
|
|
print(f"Stored memory [{doc_id}] in namespace '{session_id}'")
|
|
return doc_id
|
|
|
|
|
|
def recall_memories(index, query: str, session_id: str | None = None, top_k: int = 5):
|
|
"""Recall memories matching a query, optionally scoped to a session."""
|
|
embeddings = get_embeddings()
|
|
query_vector = embeddings.embed_query(query)
|
|
|
|
kwargs = {"vector": query_vector, "top_k": top_k, "include_metadata": True}
|
|
if session_id:
|
|
kwargs["namespace"] = session_id
|
|
|
|
results = index.query(**kwargs)
|
|
|
|
print(f"\nRecalling memories for: {query!r}")
|
|
if session_id:
|
|
print(f"Scoped to session: {session_id}")
|
|
print(f"Found {len(results['matches'])} results:\n")
|
|
|
|
for match in results["matches"]:
|
|
score = match["score"]
|
|
text = match["metadata"].get("text", "")[:200]
|
|
sess = match["metadata"].get("session_id", "unknown")
|
|
ts = match["metadata"].get("timestamp", 0)
|
|
print(f" [{score:.4f}] session={sess} time={ts}")
|
|
print(f" {text}")
|
|
print()
|
|
|
|
return results
|
|
|
|
|
|
def cleanup_session(index, session_id: str):
|
|
"""Delete all vectors in a session namespace."""
|
|
index.delete(delete_all=True, namespace=session_id)
|
|
print(f"Cleaned up namespace '{session_id}'")
|
|
|
|
|
|
def show_stats(index):
|
|
"""Show index statistics."""
|
|
stats = index.describe_index_stats()
|
|
print(f"Total vectors: {stats['total_vector_count']}")
|
|
namespaces = stats.get("namespaces", {})
|
|
if namespaces:
|
|
print(f"Namespaces ({len(namespaces)}):")
|
|
for ns, info in sorted(namespaces.items()):
|
|
print(f" '{ns}': {info['vector_count']} vectors")
|
|
else:
|
|
print("No namespaces found.")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Pinecone agent memory manager")
|
|
parser.add_argument("--index-name", required=True, help="Pinecone index name")
|
|
parser.add_argument(
|
|
"--action",
|
|
choices=["store", "recall", "cleanup", "stats"],
|
|
required=True,
|
|
)
|
|
parser.add_argument("--session-id", help="Session namespace ID")
|
|
parser.add_argument("--text", help="Text to store as memory")
|
|
parser.add_argument("--query", help="Query for recall")
|
|
parser.add_argument("--top-k", type=int, default=5, help="Number of results")
|
|
args = parser.parse_args()
|
|
|
|
pc = get_pinecone_client()
|
|
index = pc.Index(args.index_name)
|
|
|
|
if args.action == "store":
|
|
if not args.session_id or not args.text:
|
|
parser.error("--session-id and --text required for store action")
|
|
store_memory(index, args.session_id, args.text)
|
|
elif args.action == "recall":
|
|
if not args.query:
|
|
parser.error("--query required for recall action")
|
|
recall_memories(index, args.query, session_id=args.session_id, top_k=args.top_k)
|
|
elif args.action == "cleanup":
|
|
if not args.session_id:
|
|
parser.error("--session-id required for cleanup action")
|
|
cleanup_session(index, args.session_id)
|
|
elif args.action == "stats":
|
|
show_stats(index)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|