Download code_release/code/memebench/methods/dense_rag.py from clinical-agent-memory-bench/meme-bench: direct link, hf CLI and curl.
- Browser
- Download file 3.53 kB
-
https://huggingface.co/spaces/clinical-agent-memory-bench/meme-bench/resolve/main/code_release/code/memebench/methods/dense_rag.py
- Command line
-
hf download hf://spaces/clinical-agent-memory-bench/meme-bench/code_release/code/memebench/methods/dense_rag.py
-
curl -L -o dense_rag.py https://huggingface.co/spaces/clinical-agent-memory-bench/meme-bench/resolve/main/code_release/code/memebench/methods/dense_rag.py
3.53 kB
| """Dense retrieval over visit chunks with a FAISS inner-product index. | |
| New chunks are embedded at each memorize(); chunking and the MEMORY block follow bm25_rag. | |
| """ | |
| from __future__ import annotations | |
| import time | |
| import numpy as np | |
| from .base import BaseAgent, MemoryBuildResult, MemoryContext | |
| class DenseRAGAgent(BaseAgent): | |
| def __init__(self, embedder=None, *, top_k: int, chunk_size: int, chunk_overlap: int, | |
| max_context_tokens: int, **params): | |
| super().__init__(**params) | |
| if embedder is None: | |
| raise ValueError("DenseRAGAgent requires an embedder (llm_client.make_embedder())") | |
| self.embedder = embedder | |
| self.top_k = top_k | |
| self.chunk_size = chunk_size | |
| self.chunk_overlap = chunk_overlap | |
| self.max_context_tokens = max_context_tokens | |
| self._chunks: list[str] = [] | |
| self._chunk_visits: list[int] = [] | |
| self._index = None | |
| def _normalize(vecs: list[list[float]]) -> np.ndarray: | |
| a = np.asarray(vecs, dtype="float32") | |
| norms = np.linalg.norm(a, axis=1, keepdims=True) | |
| norms[norms == 0] = 1.0 | |
| return a / norms | |
| def memorize(self, text: str, visit_number: int, date: str) -> MemoryBuildResult: | |
| t0 = time.time() | |
| header = text.splitlines()[0] if text else "" | |
| new_chunks = self.split_tokens(text, self.chunk_size, self.chunk_overlap, header=header) | |
| if new_chunks: | |
| vecs = self._normalize(self.embedder.embed(new_chunks)) | |
| if self._index is None: | |
| import faiss | |
| self._index = faiss.IndexFlatIP(vecs.shape[1]) | |
| self._index.add(vecs) | |
| self._chunks.extend(new_chunks) | |
| self._chunk_visits.extend([visit_number] * len(new_chunks)) | |
| return MemoryBuildResult(success=True, method="dense_rag", action="add_to_faiss", | |
| visit_number=visit_number, chunk_count=len(self._chunks), | |
| added_chunks=len(new_chunks), time_cost=time.time() - t0, | |
| extra={"embedding_model": self.embedder.model}) | |
| def _rank(self, query: str) -> list[int]: | |
| if self._index is None or not self._chunks: | |
| return [] | |
| qv = self._normalize(self.embedder.embed([query])) | |
| k = min(self.top_k, len(self._chunks)) | |
| _, idxs = self._index.search(qv, k) | |
| return [int(i) for i in idxs[0] if i >= 0] | |
| def build_memory_context(self, question: str, | |
| as_of: str = "", display_question: str | None = None) -> MemoryContext: | |
| idxs = self._rank(question) | |
| kept, used = [], 0 | |
| for i in idxs: | |
| n = self.count_tokens(self._chunks[i]) | |
| if used + n > self.max_context_tokens: | |
| break | |
| kept.append(i) | |
| used += n | |
| mem = "\n\n".join(f"[Memory {j + 1}]\n{self._chunks[i]}" for j, i in enumerate(kept)) | |
| visits = [self._chunk_visits[i] for i in kept] | |
| return MemoryContext( | |
| mem=mem, n_tok=self.count_tokens(mem), retrieved_count=len(kept), | |
| retrieved_visits=sorted(set(visits)), | |
| retrieved_memories=[{"visit": self._chunk_visits[i], | |
| "memory": self._chunks[i][:300]} for i in kept], | |
| context_truncated=len(kept) < len(idxs), | |
| extra={"original_retrieved_count": len(idxs), | |
| "embedding_model": self.embedder.model}, | |
| ) | |