From e1850d5c4eec37c44b016eebe095922657d0448e Mon Sep 17 00:00:00 2001 From: Jiang <33757498+hijzy@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:43:23 +0800 Subject: [PATCH 1/8] fix: add full text search for neo4j db (#1095) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add full_text_search for neo4j * test: 改回去 --------- Co-authored-by: CaralHsi --- src/memos/graph_dbs/neo4j.py | 20 ++++++++++++++ src/memos/graph_dbs/neo4j_community.py | 20 ++++++++++++++ .../tree_text_memory/retrieve/searcher.py | 26 ++++++++++++------- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/memos/graph_dbs/neo4j.py b/src/memos/graph_dbs/neo4j.py index 23ce2408b..746051187 100644 --- a/src/memos/graph_dbs/neo4j.py +++ b/src/memos/graph_dbs/neo4j.py @@ -928,6 +928,26 @@ def search_by_embedding( return records + def search_by_fulltext( + self, + query_words: list[str], + top_k: int = 10, + scope: str | None = None, + status: str | None = None, + threshold: float | None = None, + search_filter: dict | None = None, + user_name: str | None = None, + filter: dict | None = None, + knowledgebase_ids: list[str] | None = None, + tsquery_config: str | None = None, + **kwargs, + ) -> list[dict]: + """ + TODO: 实现 Neo4j 的关键词检索, 以兼容 TreeTextMemory 的 keyword/fulltext 召回路径. + 目前先返回空列表, 避免切换到 Neo4j 后因缺失方法导致运行时报错. + """ + return [] + def get_by_metadata( self, filters: list[dict[str, Any]], diff --git a/src/memos/graph_dbs/neo4j_community.py b/src/memos/graph_dbs/neo4j_community.py index e34313fa2..cae7d6ca5 100644 --- a/src/memos/graph_dbs/neo4j_community.py +++ b/src/memos/graph_dbs/neo4j_community.py @@ -382,6 +382,26 @@ def search_by_embedding( return filtered_results + def search_by_fulltext( + self, + query_words: list[str], + top_k: int = 10, + scope: str | None = None, + status: str | None = None, + threshold: float | None = None, + search_filter: dict | None = None, + user_name: str | None = None, + filter: dict | None = None, + knowledgebase_ids: list[str] | None = None, + tsquery_config: str | None = None, + **kwargs, + ) -> list[dict]: + """ + TODO: 实现 Neo4j Community 的关键词检索, 以兼容 TreeTextMemory 的 keyword/fulltext 召回路径. + 目前先返回空列表, 避免切换到 Neo4j 后因缺失方法导致运行时报错. + """ + return [] + def _normalize_date_string(self, date_str: str) -> str: """ Normalize date string to ISO 8601 format for Neo4j datetime() function. diff --git a/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py b/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py index f00efccb6..c8e7acc25 100644 --- a/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py +++ b/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py @@ -511,16 +511,22 @@ def _retrieve_from_keyword( id_to_score: dict[str, float] = {} for scope in scopes: - hits = self.graph_store.search_by_fulltext( - query_words=tsquery_terms, - top_k=top_k * 2, - status="activated", - scope=scope, - search_filter=None, - filter=search_filter, - user_name=user_name, - tsquery_config="jiebaqry", - ) + try: + hits = self.graph_store.search_by_fulltext( + query_words=tsquery_terms, + top_k=top_k * 2, + status="activated", + scope=scope, + search_filter=None, + filter=search_filter, + user_name=user_name, + tsquery_config="jiebaqry", + ) + except Exception as e: + logger.warning( + f"[PATH-KEYWORD] search_by_fulltext failed, scope={scope}, user_name={user_name}" + ) + hits = [] for h in hits or []: hid = str(h.get("id") or "").strip().strip("'\"") if not hid: From 5b937614882b3e0f4ee6724738353496cc59a9f1 Mon Sep 17 00:00:00 2001 From: Jiang <33757498+hijzy@users.noreply.github.com> Date: Sat, 14 Feb 2026 10:16:57 +0800 Subject: [PATCH 2/8] fix: Add toggle for fulltext retrieval path (#1096) feat: Add toggle for fulltext retrieval path (FULLTEXT_CALL), default off --- src/memos/api/config.py | 2 ++ .../tree_text_memory/retrieve/searcher.py | 28 ++++++++++--------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/memos/api/config.py b/src/memos/api/config.py index d2dd19266..65049b0c2 100644 --- a/src/memos/api/config.py +++ b/src/memos/api/config.py @@ -1014,6 +1014,7 @@ def create_user_config(user_name: str, user_id: str) -> tuple["MOSConfig", "Gene "fast_graph": bool(os.getenv("FAST_GRAPH", "false") == "true"), "bm25": bool(os.getenv("BM25_CALL", "false") == "true"), "cot": bool(os.getenv("VEC_COT_CALL", "false") == "true"), + "fulltext": bool(os.getenv("FULLTEXT_CALL", "false") == "true"), }, "include_embedding": bool( os.getenv("INCLUDE_EMBEDDING", "false") == "true" @@ -1096,6 +1097,7 @@ def get_default_cube_config() -> "GeneralMemCubeConfig | None": "fast_graph": bool(os.getenv("FAST_GRAPH", "false") == "true"), "bm25": bool(os.getenv("BM25_CALL", "false") == "true"), "cot": bool(os.getenv("VEC_COT_CALL", "false") == "true"), + "fulltext": bool(os.getenv("FULLTEXT_CALL", "false") == "true"), }, "mode": os.getenv("ASYNC_MODE", "sync"), "include_embedding": bool( diff --git a/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py b/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py index f00efccb6..da6b1e32a 100644 --- a/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py +++ b/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py @@ -67,6 +67,7 @@ def __init__( self.internet_retriever = internet_retriever self.vec_cot = search_strategy.get("cot", False) if search_strategy else False self.use_fast_graph = search_strategy.get("fast_graph", False) if search_strategy else False + self.use_fulltext = search_strategy.get("fulltext", False) if search_strategy else False self.manual_close_internet = manual_close_internet self.tokenizer = tokenizer self._usage_executor = ContextThreadPoolExecutor(max_workers=4, thread_name_prefix="usage") @@ -380,20 +381,21 @@ def _retrieve_paths( user_name, ) ) - tasks.append( - executor.submit( - self._retrieve_from_keyword, - query, - parsed_goal, - query_embedding, - top_k, - memory_type, - search_filter, - search_priority, - user_name, - id_filter, + if self.use_fulltext: + tasks.append( + executor.submit( + self._retrieve_from_keyword, + query, + parsed_goal, + query_embedding, + top_k, + memory_type, + search_filter, + search_priority, + user_name, + id_filter, + ) ) - ) if search_tool_memory: tasks.append( executor.submit( From 81e96456cdb4d66b2fccb719432c1e1944294536 Mon Sep 17 00:00:00 2001 From: jiang Date: Wed, 25 Feb 2026 11:06:49 +0800 Subject: [PATCH 3/8] =?UTF-8?q?test:=20=E5=B0=86=20relativity=20=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E4=B8=BA=200.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/memos/api/product_models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/memos/api/product_models.py b/src/memos/api/product_models.py index 6fc03e735..ee8a1eadf 100644 --- a/src/memos/api/product_models.py +++ b/src/memos/api/product_models.py @@ -99,7 +99,7 @@ class ChatRequest(BaseRequest): manager_user_id: str | None = Field(None, description="Manager User ID") project_id: str | None = Field(None, description="Project ID") relativity: float = Field( - 0.0, + 0.5, ge=0, description=( "Relevance threshold for recalled memories. " @@ -339,7 +339,7 @@ class APISearchRequest(BaseRequest): ) relativity: float = Field( - 0.0, + 0.5, ge=0, description=( "Relevance threshold for recalled memories. " @@ -785,7 +785,7 @@ class APIChatCompleteRequest(BaseRequest): manager_user_id: str | None = Field(None, description="Manager User ID") project_id: str | None = Field(None, description="Project ID") relativity: float = Field( - 0.0, + 0.5, ge=0, description=( "Relevance threshold for recalled memories. " From 77018503d4d42e72f4822837820c16d5ea9b4a16 Mon Sep 17 00:00:00 2001 From: jiang Date: Wed, 25 Feb 2026 11:23:27 +0800 Subject: [PATCH 4/8] =?UTF-8?q?test:=20=E5=B0=86=20relativity=20=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E4=B8=BA=200.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/memos/api/product_models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/memos/api/product_models.py b/src/memos/api/product_models.py index ee8a1eadf..cba14a4fe 100644 --- a/src/memos/api/product_models.py +++ b/src/memos/api/product_models.py @@ -99,7 +99,7 @@ class ChatRequest(BaseRequest): manager_user_id: str | None = Field(None, description="Manager User ID") project_id: str | None = Field(None, description="Project ID") relativity: float = Field( - 0.5, + 0.4, ge=0, description=( "Relevance threshold for recalled memories. " @@ -339,7 +339,7 @@ class APISearchRequest(BaseRequest): ) relativity: float = Field( - 0.5, + 0.4, ge=0, description=( "Relevance threshold for recalled memories. " @@ -785,7 +785,7 @@ class APIChatCompleteRequest(BaseRequest): manager_user_id: str | None = Field(None, description="Manager User ID") project_id: str | None = Field(None, description="Project ID") relativity: float = Field( - 0.5, + 0.4, ge=0, description=( "Relevance threshold for recalled memories. " From 1537b97e978790b9eac8f7f9422b8a0f3306fcde Mon Sep 17 00:00:00 2001 From: jiang Date: Wed, 25 Feb 2026 11:39:44 +0800 Subject: [PATCH 5/8] =?UTF-8?q?test:=20=E5=B0=86=20relativity=20=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E4=B8=BA=200.45?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/memos/api/product_models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/memos/api/product_models.py b/src/memos/api/product_models.py index cba14a4fe..4482cbb93 100644 --- a/src/memos/api/product_models.py +++ b/src/memos/api/product_models.py @@ -99,7 +99,7 @@ class ChatRequest(BaseRequest): manager_user_id: str | None = Field(None, description="Manager User ID") project_id: str | None = Field(None, description="Project ID") relativity: float = Field( - 0.4, + 0.45, ge=0, description=( "Relevance threshold for recalled memories. " @@ -339,7 +339,7 @@ class APISearchRequest(BaseRequest): ) relativity: float = Field( - 0.4, + 0.45, ge=0, description=( "Relevance threshold for recalled memories. " @@ -785,7 +785,7 @@ class APIChatCompleteRequest(BaseRequest): manager_user_id: str | None = Field(None, description="Manager User ID") project_id: str | None = Field(None, description="Project ID") relativity: float = Field( - 0.4, + 0.45, ge=0, description=( "Relevance threshold for recalled memories. " From 0689f4c2e9a4bad2e2ce3f73303e79c22923db05 Mon Sep 17 00:00:00 2001 From: jiang Date: Wed, 25 Feb 2026 11:56:14 +0800 Subject: [PATCH 6/8] =?UTF-8?q?test:=20=E5=B0=86=20relativity=20=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E4=B8=BA=200.475?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/memos/api/product_models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/memos/api/product_models.py b/src/memos/api/product_models.py index 4482cbb93..d497a97ae 100644 --- a/src/memos/api/product_models.py +++ b/src/memos/api/product_models.py @@ -99,7 +99,7 @@ class ChatRequest(BaseRequest): manager_user_id: str | None = Field(None, description="Manager User ID") project_id: str | None = Field(None, description="Project ID") relativity: float = Field( - 0.45, + 0.475, ge=0, description=( "Relevance threshold for recalled memories. " @@ -339,7 +339,7 @@ class APISearchRequest(BaseRequest): ) relativity: float = Field( - 0.45, + 0.475, ge=0, description=( "Relevance threshold for recalled memories. " @@ -785,7 +785,7 @@ class APIChatCompleteRequest(BaseRequest): manager_user_id: str | None = Field(None, description="Manager User ID") project_id: str | None = Field(None, description="Project ID") relativity: float = Field( - 0.45, + 0.475, ge=0, description=( "Relevance threshold for recalled memories. " From b5430895557d0e8a8d12c98198e985bf24aa93e0 Mon Sep 17 00:00:00 2001 From: jiang Date: Wed, 25 Feb 2026 14:12:40 +0800 Subject: [PATCH 7/8] fix: set relativity 0.45 --- src/memos/api/product_models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/memos/api/product_models.py b/src/memos/api/product_models.py index d497a97ae..5bf27e985 100644 --- a/src/memos/api/product_models.py +++ b/src/memos/api/product_models.py @@ -99,12 +99,12 @@ class ChatRequest(BaseRequest): manager_user_id: str | None = Field(None, description="Manager User ID") project_id: str | None = Field(None, description="Project ID") relativity: float = Field( - 0.475, + 0.45, ge=0, description=( "Relevance threshold for recalled memories. " "Only memories with metadata.relativity >= relativity will be returned. " - "Use 0 to disable threshold filtering. Default: 0.3." + "Use 0 to disable threshold filtering. Default: 0.45." ), ) @@ -339,12 +339,12 @@ class APISearchRequest(BaseRequest): ) relativity: float = Field( - 0.475, + 0.45, ge=0, description=( "Relevance threshold for recalled memories. " "Only memories with metadata.relativity >= relativity will be returned. " - "Use 0 to disable threshold filtering. Default: 0.3." + "Use 0 to disable threshold filtering. Default: 0.45." ), ) @@ -785,12 +785,12 @@ class APIChatCompleteRequest(BaseRequest): manager_user_id: str | None = Field(None, description="Manager User ID") project_id: str | None = Field(None, description="Project ID") relativity: float = Field( - 0.475, + 0.45, ge=0, description=( "Relevance threshold for recalled memories. " "Only memories with metadata.relativity >= relativity will be returned. " - "Use 0 to disable threshold filtering. Default: 0.3." + "Use 0 to disable threshold filtering. Default: 0.45." ), ) From 1d2b074fb1c3fcc308adedf00ec6980a2312f4fd Mon Sep 17 00:00:00 2001 From: jiang Date: Wed, 25 Feb 2026 17:58:19 +0800 Subject: [PATCH 8/8] fix: translate comments to english --- src/memos/graph_dbs/neo4j.py | 4 ++-- src/memos/graph_dbs/neo4j_community.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/memos/graph_dbs/neo4j.py b/src/memos/graph_dbs/neo4j.py index 746051187..4c5d7d42b 100644 --- a/src/memos/graph_dbs/neo4j.py +++ b/src/memos/graph_dbs/neo4j.py @@ -943,8 +943,8 @@ def search_by_fulltext( **kwargs, ) -> list[dict]: """ - TODO: 实现 Neo4j 的关键词检索, 以兼容 TreeTextMemory 的 keyword/fulltext 召回路径. - 目前先返回空列表, 避免切换到 Neo4j 后因缺失方法导致运行时报错. + TODO: Implement fulltext search for Neo4j to be compatible with TreeTextMemory's keyword/fulltext recall path. + Currently, return an empty list to avoid runtime errors due to missing methods when switching to Neo4j. """ return [] diff --git a/src/memos/graph_dbs/neo4j_community.py b/src/memos/graph_dbs/neo4j_community.py index cae7d6ca5..ff4872255 100644 --- a/src/memos/graph_dbs/neo4j_community.py +++ b/src/memos/graph_dbs/neo4j_community.py @@ -397,8 +397,8 @@ def search_by_fulltext( **kwargs, ) -> list[dict]: """ - TODO: 实现 Neo4j Community 的关键词检索, 以兼容 TreeTextMemory 的 keyword/fulltext 召回路径. - 目前先返回空列表, 避免切换到 Neo4j 后因缺失方法导致运行时报错. + TODO: Implement fulltext search for Neo4j to be compatible with TreeTextMemory's keyword/fulltext recall path. + Currently, return an empty list to avoid runtime errors due to missing methods when switching to Neo4j. """ return []