@@ -1450,6 +1450,115 @@ def get_context_chain(self, id: str, type: str = "FOLLOWS") -> list[str]:
14501450 """Get the ordered context chain starting from a node."""
14511451 raise NotImplementedError
14521452
1453+ @timed
1454+ def search_by_fulltext (
1455+ self ,
1456+ query_words : list [str ],
1457+ top_k : int = 10 ,
1458+ scope : str | None = None ,
1459+ status : str | None = None ,
1460+ threshold : float | None = None ,
1461+ search_filter : dict | None = None ,
1462+ user_name : str | None = None ,
1463+ tsvector_field : str = "properties_tsvector_zh" ,
1464+ tsquery_config : str = "jiebaqry" ,
1465+ ** kwargs ,
1466+ ) -> list [dict ]:
1467+ """
1468+ Full-text search functionality using PostgreSQL's full-text search capabilities.
1469+
1470+ Args:
1471+ query_text: query text
1472+ top_k: maximum number of results to return
1473+ scope: memory type filter (memory_type)
1474+ status: status filter, defaults to "activated"
1475+ threshold: similarity threshold filter
1476+ search_filter: additional property filter conditions
1477+ user_name: username filter
1478+ tsvector_field: full-text index field name, defaults to properties_tsvector_zh_1
1479+ tsquery_config: full-text search configuration, defaults to jiebaqry (Chinese word segmentation)
1480+ **kwargs: other parameters (e.g. cube_name)
1481+
1482+ Returns:
1483+ list[dict]: result list containing id and score
1484+ """
1485+ # Build WHERE clause dynamically, same as search_by_embedding
1486+ where_clauses = []
1487+
1488+ if scope :
1489+ where_clauses .append (
1490+ f"ag_catalog.agtype_access_operator(properties, '\" memory_type\" '::agtype) = '\" { scope } \" '::agtype"
1491+ )
1492+ if status :
1493+ where_clauses .append (
1494+ f"ag_catalog.agtype_access_operator(properties, '\" status\" '::agtype) = '\" { status } \" '::agtype"
1495+ )
1496+ else :
1497+ where_clauses .append (
1498+ "ag_catalog.agtype_access_operator(properties, '\" status\" '::agtype) = '\" activated\" '::agtype"
1499+ )
1500+
1501+ # Add user_name filter
1502+ user_name = user_name if user_name else self .config .user_name
1503+ where_clauses .append (
1504+ f"ag_catalog.agtype_access_operator(properties, '\" user_name\" '::agtype) = '\" { user_name } \" '::agtype"
1505+ )
1506+
1507+ # Add search_filter conditions
1508+ if search_filter :
1509+ for key , value in search_filter .items ():
1510+ if isinstance (value , str ):
1511+ where_clauses .append (
1512+ f"ag_catalog.agtype_access_operator(properties, '\" { key } \" '::agtype) = '\" { value } \" '::agtype"
1513+ )
1514+ else :
1515+ where_clauses .append (
1516+ f"ag_catalog.agtype_access_operator(properties, '\" { key } \" '::agtype) = { value } ::agtype"
1517+ )
1518+
1519+ # Add fulltext search condition
1520+ # Convert query_text to OR query format: "word1 | word2 | word3"
1521+ tsquery_string = " | " .join (query_words )
1522+
1523+ where_clauses .append (f"{ tsvector_field } @@ to_tsquery('{ tsquery_config } ', %s)" )
1524+
1525+ where_clause = f"WHERE { ' AND ' .join (where_clauses )} " if where_clauses else ""
1526+
1527+ # Build fulltext search query
1528+ query = f"""
1529+ SELECT
1530+ ag_catalog.agtype_access_operator(properties, '"id"'::agtype) AS old_id,
1531+ agtype_object_field_text(properties, 'memory') as memory_text,
1532+ ts_rank({ tsvector_field } , to_tsquery('{ tsquery_config } ', %s)) as rank
1533+ FROM "{ self .db_name } _graph"."Memory"
1534+ { where_clause }
1535+ ORDER BY rank DESC
1536+ LIMIT { top_k } ;
1537+ """
1538+
1539+ params = [tsquery_string , tsquery_string ]
1540+
1541+ conn = self ._get_connection ()
1542+ try :
1543+ with conn .cursor () as cursor :
1544+ cursor .execute (query , params )
1545+ results = cursor .fetchall ()
1546+ output = []
1547+ for row in results :
1548+ oldid = row [0 ] # old_id
1549+ rank = row [2 ] # rank score
1550+
1551+ id_val = str (oldid )
1552+ score_val = float (rank )
1553+
1554+ # Apply threshold filter if specified
1555+ if threshold is None or score_val >= threshold :
1556+ output .append ({"id" : id_val , "score" : score_val })
1557+
1558+ return output [:top_k ]
1559+ finally :
1560+ self ._return_connection (conn )
1561+
14531562 @timed
14541563 def search_by_embedding (
14551564 self ,
0 commit comments