Added sanitation for better relationship mapping (#3300)
This commit is contained in:
@@ -249,23 +249,23 @@ RELATIONS_STRUCT_TOOL = {
|
|||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"source_entity": {
|
"source": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The source entity of the relationship.",
|
"description": "The source entity of the relationship.",
|
||||||
},
|
},
|
||||||
"relatationship": {
|
"relationship": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The relationship between the source and destination entities.",
|
"description": "The relationship between the source and destination entities.",
|
||||||
},
|
},
|
||||||
"destination_entity": {
|
"destination": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The destination entity of the relationship.",
|
"description": "The destination entity of the relationship.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"source_entity",
|
"source",
|
||||||
"relatationship",
|
"relationship",
|
||||||
"destination_entity",
|
"destination",
|
||||||
],
|
],
|
||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
},
|
},
|
||||||
|
|||||||
+19
-27
@@ -1,8 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
import unicodedata
|
|
||||||
|
|
||||||
from mem0.memory.utils import format_entities
|
from mem0.memory.utils import format_entities, sanitize_relationship_for_cypher
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from langchain_neo4j import Neo4jGraph
|
from langchain_neo4j import Neo4jGraph
|
||||||
@@ -57,13 +55,20 @@ class MemoryGraph:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.llm_provider = "openai_structured"
|
# Default to openai if no specific provider is configured
|
||||||
if self.config.llm.provider:
|
self.llm_provider = "openai"
|
||||||
|
if self.config.llm and self.config.llm.provider:
|
||||||
self.llm_provider = self.config.llm.provider
|
self.llm_provider = self.config.llm.provider
|
||||||
if self.config.graph_store.llm:
|
if self.config.graph_store and self.config.graph_store.llm and self.config.graph_store.llm.provider:
|
||||||
self.llm_provider = self.config.graph_store.llm.provider
|
self.llm_provider = self.config.graph_store.llm.provider
|
||||||
|
|
||||||
self.llm = LlmFactory.create(self.llm_provider, self.config.graph_store.llm.config if self.config.graph_store.llm.config else self.config.llm.config)
|
# Get LLM config with proper null checks
|
||||||
|
llm_config = None
|
||||||
|
if self.config.graph_store and self.config.graph_store.llm and hasattr(self.config.graph_store.llm, "config"):
|
||||||
|
llm_config = self.config.graph_store.llm.config
|
||||||
|
elif hasattr(self.config.llm, "config"):
|
||||||
|
llm_config = self.config.llm.config
|
||||||
|
self.llm = LlmFactory.create(self.llm_provider, llm_config)
|
||||||
self.user_id = None
|
self.user_id = None
|
||||||
self.threshold = 0.7
|
self.threshold = 0.7
|
||||||
|
|
||||||
@@ -600,26 +605,17 @@ class MemoryGraph:
|
|||||||
results.append(result)
|
results.append(result)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def _sanitize_name(self, name):
|
def _remove_spaces_from_entities(self, entity_list):
|
||||||
normalized = unicodedata.normalize('NFKD', name)
|
|
||||||
ascii_text = normalized.encode('ascii', 'ignore').decode('ascii')
|
|
||||||
sanitized = re.sub(r'\W+', '_', ascii_text).lower()
|
|
||||||
return sanitized.strip('_')
|
|
||||||
|
|
||||||
def _sanitize_entities(self, entity_list):
|
|
||||||
for item in entity_list:
|
for item in entity_list:
|
||||||
item["source"] = self._sanitize_name(item["source"])
|
item["source"] = item["source"].lower().replace(" ", "_")
|
||||||
item["relationship"] = self._sanitize_name(item["relationship"])
|
# Use the sanitization function for relationships to handle special characters
|
||||||
item["destination"] = self._sanitize_name(item["destination"])
|
item["relationship"] = sanitize_relationship_for_cypher(item["relationship"].lower().replace(" ", "_"))
|
||||||
|
item["destination"] = item["destination"].lower().replace(" ", "_")
|
||||||
return entity_list
|
return entity_list
|
||||||
|
|
||||||
def _search_source_node(self, source_embedding, filters, threshold=0.9):
|
def _search_source_node(self, source_embedding, filters, threshold=0.9):
|
||||||
|
|
||||||
# Build WHERE conditions
|
# Build WHERE conditions
|
||||||
where_conditions = [
|
where_conditions = ["source_candidate.embedding IS NOT NULL", "source_candidate.user_id = $user_id"]
|
||||||
"source_candidate.embedding IS NOT NULL",
|
|
||||||
"source_candidate.user_id = $user_id"
|
|
||||||
]
|
|
||||||
if filters.get("agent_id"):
|
if filters.get("agent_id"):
|
||||||
where_conditions.append("source_candidate.agent_id = $agent_id")
|
where_conditions.append("source_candidate.agent_id = $agent_id")
|
||||||
if filters.get("run_id"):
|
if filters.get("run_id"):
|
||||||
@@ -655,12 +651,8 @@ class MemoryGraph:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _search_destination_node(self, destination_embedding, filters, threshold=0.9):
|
def _search_destination_node(self, destination_embedding, filters, threshold=0.9):
|
||||||
|
|
||||||
# Build WHERE conditions
|
# Build WHERE conditions
|
||||||
where_conditions = [
|
where_conditions = ["destination_candidate.embedding IS NOT NULL", "destination_candidate.user_id = $user_id"]
|
||||||
"destination_candidate.embedding IS NOT NULL",
|
|
||||||
"destination_candidate.user_id = $user_id"
|
|
||||||
]
|
|
||||||
if filters.get("agent_id"):
|
if filters.get("agent_id"):
|
||||||
where_conditions.append("destination_candidate.agent_id = $agent_id")
|
where_conditions.append("destination_candidate.agent_id = $agent_id")
|
||||||
if filters.get("run_id"):
|
if filters.get("run_id"):
|
||||||
|
|||||||
+5
-15
@@ -1384,31 +1384,21 @@ class AsyncMemory(MemoryBase):
|
|||||||
"mem0.get_all", self, {"limit": limit, "keys": keys, "encoded_ids": encoded_ids, "sync_type": "async"}
|
"mem0.get_all", self, {"limit": limit, "keys": keys, "encoded_ids": encoded_ids, "sync_type": "async"}
|
||||||
)
|
)
|
||||||
|
|
||||||
vector_store_task = asyncio.create_task(
|
vector_store_task = asyncio.create_task(self._get_all_from_vector_store(effective_filters, limit))
|
||||||
self._get_all_from_vector_store(effective_filters, limit)
|
|
||||||
)
|
|
||||||
|
|
||||||
graph_task = None
|
graph_task = None
|
||||||
if self.enable_graph:
|
if self.enable_graph:
|
||||||
graph_get_all = getattr(self.graph, "get_all", None)
|
graph_get_all = getattr(self.graph, "get_all", None)
|
||||||
if callable(graph_get_all):
|
if callable(graph_get_all):
|
||||||
if asyncio.iscoroutinefunction(graph_get_all):
|
if asyncio.iscoroutinefunction(graph_get_all):
|
||||||
graph_task = asyncio.create_task(
|
graph_task = asyncio.create_task(graph_get_all(effective_filters, limit))
|
||||||
graph_get_all(effective_filters, limit)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
graph_task = asyncio.create_task(
|
graph_task = asyncio.create_task(asyncio.to_thread(graph_get_all, effective_filters, limit))
|
||||||
asyncio.to_thread(graph_get_all, effective_filters, limit)
|
|
||||||
)
|
|
||||||
|
|
||||||
results_dict = {}
|
results_dict = {}
|
||||||
if graph_task:
|
if graph_task:
|
||||||
vector_store_result, graph_entities_result = await asyncio.gather(
|
vector_store_result, graph_entities_result = await asyncio.gather(vector_store_task, graph_task)
|
||||||
vector_store_task, graph_task
|
results_dict.update({"results": vector_store_result, "relations": graph_entities_result})
|
||||||
)
|
|
||||||
results_dict.update(
|
|
||||||
{"results": vector_store_result, "relations": graph_entities_result}
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
results_dict.update({"results": await vector_store_task})
|
results_dict.update({"results": await vector_store_task})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mem0.memory.utils import format_entities
|
from mem0.memory.utils import format_entities, sanitize_relationship_for_cypher
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from langchain_memgraph.graphs.memgraph import Memgraph
|
from langchain_memgraph.graphs.memgraph import Memgraph
|
||||||
@@ -40,13 +40,20 @@ class MemoryGraph:
|
|||||||
{"enable_embeddings": True},
|
{"enable_embeddings": True},
|
||||||
)
|
)
|
||||||
|
|
||||||
self.llm_provider = "openai_structured"
|
# Default to openai if no specific provider is configured
|
||||||
if self.config.llm.provider:
|
self.llm_provider = "openai"
|
||||||
|
if self.config.llm and self.config.llm.provider:
|
||||||
self.llm_provider = self.config.llm.provider
|
self.llm_provider = self.config.llm.provider
|
||||||
if self.config.graph_store.llm:
|
if self.config.graph_store and self.config.graph_store.llm and self.config.graph_store.llm.provider:
|
||||||
self.llm_provider = self.config.graph_store.llm.provider
|
self.llm_provider = self.config.graph_store.llm.provider
|
||||||
|
|
||||||
self.llm = LlmFactory.create(self.llm_provider, self.config.llm.config)
|
# Get LLM config with proper null checks
|
||||||
|
llm_config = None
|
||||||
|
if self.config.graph_store and self.config.graph_store.llm and hasattr(self.config.graph_store.llm, "config"):
|
||||||
|
llm_config = self.config.graph_store.llm.config
|
||||||
|
elif hasattr(self.config.llm, "config"):
|
||||||
|
llm_config = self.config.llm.config
|
||||||
|
self.llm = LlmFactory.create(self.llm_provider, llm_config)
|
||||||
self.user_id = None
|
self.user_id = None
|
||||||
self.threshold = 0.7
|
self.threshold = 0.7
|
||||||
|
|
||||||
@@ -535,7 +542,8 @@ class MemoryGraph:
|
|||||||
def _remove_spaces_from_entities(self, entity_list):
|
def _remove_spaces_from_entities(self, entity_list):
|
||||||
for item in entity_list:
|
for item in entity_list:
|
||||||
item["source"] = item["source"].lower().replace(" ", "_")
|
item["source"] = item["source"].lower().replace(" ", "_")
|
||||||
item["relationship"] = item["relationship"].lower().replace(" ", "_")
|
# Use the sanitization function for relationships to handle special characters
|
||||||
|
item["relationship"] = sanitize_relationship_for_cypher(item["relationship"].lower().replace(" ", "_"))
|
||||||
item["destination"] = item["destination"].lower().replace(" ", "_")
|
item["destination"] = item["destination"].lower().replace(" ", "_")
|
||||||
return entity_list
|
return entity_list
|
||||||
|
|
||||||
|
|||||||
@@ -131,3 +131,54 @@ def process_telemetry_filters(filters):
|
|||||||
encoded_ids["run_id"] = hashlib.md5(filters["run_id"].encode()).hexdigest()
|
encoded_ids["run_id"] = hashlib.md5(filters["run_id"].encode()).hexdigest()
|
||||||
|
|
||||||
return list(filters.keys()), encoded_ids
|
return list(filters.keys()), encoded_ids
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_relationship_for_cypher(relationship) -> str:
|
||||||
|
"""Sanitize relationship text for Cypher queries by replacing problematic characters."""
|
||||||
|
char_map = {
|
||||||
|
"...": "_ellipsis_",
|
||||||
|
"…": "_ellipsis_",
|
||||||
|
"。": "_period_",
|
||||||
|
",": "_comma_",
|
||||||
|
";": "_semicolon_",
|
||||||
|
":": "_colon_",
|
||||||
|
"!": "_exclamation_",
|
||||||
|
"?": "_question_",
|
||||||
|
"(": "_lparen_",
|
||||||
|
")": "_rparen_",
|
||||||
|
"【": "_lbracket_",
|
||||||
|
"】": "_rbracket_",
|
||||||
|
"《": "_langle_",
|
||||||
|
"》": "_rangle_",
|
||||||
|
"'": "_apostrophe_",
|
||||||
|
'"': "_quote_",
|
||||||
|
"\\": "_backslash_",
|
||||||
|
"/": "_slash_",
|
||||||
|
"|": "_pipe_",
|
||||||
|
"&": "_ampersand_",
|
||||||
|
"=": "_equals_",
|
||||||
|
"+": "_plus_",
|
||||||
|
"*": "_asterisk_",
|
||||||
|
"^": "_caret_",
|
||||||
|
"%": "_percent_",
|
||||||
|
"$": "_dollar_",
|
||||||
|
"#": "_hash_",
|
||||||
|
"@": "_at_",
|
||||||
|
"!": "_bang_",
|
||||||
|
"?": "_question_",
|
||||||
|
"(": "_lparen_",
|
||||||
|
")": "_rparen_",
|
||||||
|
"[": "_lbracket_",
|
||||||
|
"]": "_rbracket_",
|
||||||
|
"{": "_lbrace_",
|
||||||
|
"}": "_rbrace_",
|
||||||
|
"<": "_langle_",
|
||||||
|
">": "_rangle_",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Apply replacements and clean up
|
||||||
|
sanitized = relationship
|
||||||
|
for old, new in char_map.items():
|
||||||
|
sanitized = sanitized.replace(old, new)
|
||||||
|
|
||||||
|
return re.sub(r"_+", "_", sanitized).strip("_")
|
||||||
|
|||||||
Reference in New Issue
Block a user