From f428357afce93f9848d22d41c2dfc54e73c6cbb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Mon, 17 Nov 2025 15:17:59 +0800 Subject: [PATCH 01/36] remove --- src/memos/graph_dbs/polardb.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index da1635296..d725721f7 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -2500,7 +2500,6 @@ def add_node( # user_name comes from metadata; fallback to config if missing metadata["user_name"] = user_name if user_name else self.config.user_name - # Safely process metadata metadata = _prepare_node_metadata(metadata) # Merge node and set metadata From 082b3595fc4dde68496f0ae91c33f9211d971e91 Mon Sep 17 00:00:00 2001 From: ccl <13282138256@163.com> Date: Mon, 17 Nov 2025 15:47:07 +0800 Subject: [PATCH 02/36] json --- examples/basic_modules/2.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/basic_modules/2.json diff --git a/examples/basic_modules/2.json b/examples/basic_modules/2.json new file mode 100644 index 000000000..a38bbb79c --- /dev/null +++ b/examples/basic_modules/2.json @@ -0,0 +1 @@ +[{"id":"53cf595c-94dd-40a8-bded-d57a140a6045","memory":"[assistant观点]助手建议用户从基础语法开始学习Python编程,并通过多做练习项目来提高技能。","user_name":"memos65c3a65b78f74009bc927ee1981deb3a","user_id":"65c3a65b-78f7-4009-bc92-7ee1981deb3a","session_id":"conv_8_6_0dd3e083","status":"activated","key":"学习Python编程的建议","confidence":"0.99","tags":"[Python, 编程, 学习建议]","created_at":"2025-09-19T10:07:03.869349","updated_at":"2025-09-19T10:07:03.869302","memory_type":"LongTermMemory","sources":"[{\"type\":\"chat\",\"role\":\"user\",\"message_id\":\"1968980137033175041\",\"content\":\"Python编程怎么学\"}, {\"type\":\"chat\",\"role\":\"assistant\",\"message_id\":\"1968980137070923778\",\"content\":\"建议从基础语法开始,多做练习项目\"}]","node_type":"fact","usage":"[]","background":"用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。","embedding_1024":"[0.014167483, 0.01231358, -0.009071156, -0.013763134, 0.01489989, -0.03982459, 0.0059851324, 0.020095397, 1.6927357E-5, 0.011985523, -0.03802409, -0.011298892, -0.0351555, -0.0046729045, 0.0057562552, 0.011588803, -0.03503343, 0.045042984, 0.05154309, -0.012145737, 0.037078068, 0.004318145, 0.08923149, 0.010375755, -0.025252758, 0.003263404, -0.016631724, -0.022872437, 0.04745382, -0.011756646, -0.017852401, -0.013930977, 0.025466375, -0.059904728, -0.058623016, -0.04678245, 0.0254206, -0.021285556, -0.032500528, 0.017821886, -0.005740997, -0.014320068, -0.023772687, 0.013511369, 0.018004987, -0.024978105, -0.017501459, -0.014457394, -0.0025596072, -0.04302887, 0.049406905, -0.017455682, 0.051573608, -0.0391227, -0.031127265, 0.03683393, 0.023711652, -0.01945454, -0.058439914, 0.031340882, -0.049864657, 0.010070586, -0.06445175, -0.012763705, -0.021621242, 0.027144806, -0.0071676634, 0.02462716, -0.025695253, 0.0069273426, -0.019424023, 0.024413541, -0.022124773, 0.008056468, -0.05432013, -0.026000421, -0.002168609, 2.8704986E-4, 0.012351726, 0.019347731, -0.015617037, 0.019195147, 0.04012976, 0.03286673, 0.0030745803, 0.0055159344, 0.011741388, 0.0661607, 0.025496893, 0.012588233, -0.0166775, -0.018310156, 0.04678245, -0.005706665, -0.020614184, -0.0056341877, -0.001375169, 0.041747157, -0.014007269, -0.011581174, 0.009895113, 0.025573185, 6.823394E-4, -0.024886554, 0.03637618, 0.04629418, 0.020125913, -0.013061245, 0.020400565, -0.039092183, 0.038115643, -0.002973493, 0.051878776, -0.006614544, 0.008796504, -0.015441565, -0.002687397, 0.017318357, 0.030852614, -0.005031478, 0.008323492, 0.021728052, 0.033507586, -0.036955997, 0.0254206, -0.020522634, -0.011466735, -0.047148652, 0.029128406, -0.010482565, 0.022109514, -0.011481994, -0.054075994, 0.012847627, -0.0412894, -0.039031148, 0.009971406, -0.03295828, -0.018340673, -0.051207405, 0.031920705, 0.03506395, 0.028075572, -0.035277568, 0.04806416, -0.08685117, 0.010101103, -0.06536726, -0.004596612, 0.003671568, -0.013274863, 0.013351155, 0.020278499, 0.029128406, -0.0112226, 7.229889E-5, -0.0315545, -0.008010693, -0.031249333, 0.009391584, -0.02552741, 0.03643721, -0.05959956, -0.00650392, 0.02026324, -0.012939177, 0.025252758, -0.016265523, -0.005004776, -0.032653112, 0.0082853455, 0.013625808, -0.03127985, 0.022384167, -0.009521281, 0.044615746, 0.0035495001, 0.02304028, 0.044066444, -0.037657887, -0.021575468, 0.027358426, 8.921433E-4, -0.07256925, -0.016616467, 0.002979215, 0.018798428, -0.012092332, 0.006862494, -0.008872797, -0.003913796, -0.009811192, 0.04727072, 0.02590887, 0.0076711923, -0.048979666, 0.021819603, -0.009948518, -0.010749588, -0.040831648, 0.013770763, 0.016708018, -0.021239782, -0.0564258, 0.0075720125, 0.0371391, -0.021316074, -0.052275497, 0.0019588051, -0.007835221, -0.0014180834, 4.963769E-4, -0.0027942061, -0.019485058, -0.02979978, 0.007327877, 0.011779534, 0.020247981, -0.008521852, -0.043852825, -0.02989133, 0.01002481, -0.019942813, -0.029174183, 0.020553151, -0.02323864, -2.0086336E-5, -0.023971045, 0.024245698, 0.037017033, -0.0078733675, 4.0530294E-4, 0.008819392, -0.019805485, 0.028746946, -0.03762737, -0.027663594, 0.008117503, 0.012229659, -0.0020885023, 0.011535399, -0.055479772, -0.013961494, -0.017242063, -0.019607125, 0.0020217465, -0.01489989, 0.029952364, -0.038756497, -0.0064505152, 0.030608477, 0.0047224946, -0.008201424, 0.006778572, 0.0078123333, 0.02809083, 0.026748087, 0.023650618, -0.071409605, -0.06475692, 0.020675218, 0.009910372, 0.028365484, 0.024184665, -0.01589932, 0.020171689, -0.027388941, -0.045470223, -0.031707086, 0.047514856, 0.022216322, -0.022124773, 0.008453188, -0.013343526, 0.020385306, 0.018539034, -0.010436789, -0.0045660953, 0.081663296, 0.0020427268, -0.022597784, 0.01638759, -0.0015859265, -0.019134114, 0.005687592, -0.015975611, -0.017409908, 0.021941671, 0.023513293, -0.003574295, -0.0013856592, -0.017531974, 0.06811378, -0.044798847, 0.0034274324, -0.0022696964, 0.022872437, -0.18908288, 0.011497253, 0.026839638, 0.044066444, -0.022063738, 0.025252758, -0.029174183, 0.033995856, -0.054381166, 0.057249755, 0.012778963, -0.036193077, 0.04000769, -0.023421742, 0.01716577, 0.018996786, -0.0033587692, 0.01032235, -0.0024318176, -0.037718922, 0.01609768, -0.066282764, 0.053557206, -0.03149347, 0.020110656, -0.028853754, 0.0028151865, -0.0060614245, -0.052489113, -0.005668519, -0.045531254, -0.0021705164, 0.007980176, 0.006362779, 0.0057257386, 0.016601209, -0.0020637072, 0.014182742, 0.015556004, -0.0024184664, 0.041380953, 0.016982669, 0.01032235, -0.004558466, 0.0059126546, -0.015441565, -0.011573545, 0.012786592, -0.021407625, -0.023894753, 0.029845554, -0.027358426, -3.9099812E-4, -0.025588444, -0.05581546, 0.007327877, 0.04379179, 0.07299649, 0.018539034, -0.009864597, -0.005134473, -0.011428589, -0.011749017, -0.016357074, -0.022048479, -1.538959E-4, 0.044951435, 0.030440634, 0.011092903, -0.029921846, 0.0044173254, -0.0023555253, -0.0034465054, -0.00794203, -9.6557464E-4, 0.0204616, 0.0064238133, 0.012466164, 0.02175857, -0.123593554, -0.009910372, -0.03842081, 0.019286698, 0.012397502, -0.0064962907, -0.012191513, 0.021865379, 0.03555222, 0.062681764, 0.26415452, 6.2130555E-4, -0.009322921, -0.017608266, 0.039733037, -0.042357493, -0.04144199, 0.0020465413, 0.0075224224, -0.04400541, 0.012138108, 0.04635521, -0.005531193, -0.03308035, 0.03445361, 0.0665269, -0.031646054, 0.008132761, 0.0094297305, 0.027037997, -0.005847806, -0.006233082, -0.0023021207, -0.009475506, -0.05978266, -0.04449368, -0.044768333, 0.044371612, -0.019973328, 0.04608056, -0.023070797, 0.06371935, 0.011298892, -0.012504311, -3.4021607E-4, 0.03167657, 0.067930676, -0.030303309, 0.017989729, -0.005996576, 0.034697745, -0.025008623, -0.0034236177, -0.02543586, -0.032836214, 0.020812545, -3.2042773E-4, -0.022414682, 0.038756497, -0.07726886, -0.004169375, -0.06713724, -0.051298954, -0.036498245, 0.013763134, 0.0043143304, -0.019866519, 0.009567057, -0.015823027, 0.021621242, 0.0044554714, 0.027373683, -0.011314151, 0.022613043, -0.024703452, -0.018355932, 0.024001563, -0.015288981, 0.032347944, -0.0031947407, 0.0019025396, 0.021895895, -0.0018243401, -0.033782236, -0.00580966, -0.024657678, 0.006931157, 0.035582736, -0.013190942, 0.012435648, -0.027633077, 0.006683207, -0.038756497, -0.012641637, -0.010001923, -0.020171689, 0.0053786086, 0.029219957, -0.021407625, 0.0073507647, -0.0029811224, -0.021895895, -0.028273933, 0.027083773, -0.033995856, 0.0055693393, -0.0055502662, -0.0028514254, -0.05062758, -0.013618179, -0.017852401, 0.034484126, 0.02175857, 0.0024203737, -0.026290333, -0.029174183, -0.03028805, 0.0025042952, -0.012092332, 0.014243776, -0.054045476, 0.010223171, 5.049598E-4, -0.06408554, 0.033355, 0.025771545, 0.07476647, 0.016311297, -0.007762743, 0.008033581, 0.034239992, 0.013099391, 0.030410118, 0.031112008, 0.0038508547, -0.023757428, -0.004051122, 0.03338552, -0.009391584, -0.0014705344, 0.012145737, -0.01231358, -0.0017928694, 0.049132254, -0.0022887695, -0.008964348, 0.04809468, -0.026107231, 0.046233144, -0.01599087, -0.04696555, -0.050291896, 0.007797075, -0.017028445, 0.026275074, 0.028945304, -0.028151864, -0.00402442, -0.016112937, -0.007903884, 0.0089414595, 0.0466909, 0.06524519, 0.005699036, -0.010253687, -0.071531676, -0.026336107, 0.012649266, 0.042052325, -0.0035495001, -0.007343136, 0.021377107, 0.029662453, 0.06658793, 0.011611691, 0.03726117, -0.028136607, 0.024657678, -0.011062386, -0.0214534, 0.013908089, -0.04080113, -0.039458387, 0.054564264, -0.025985163, -0.042357493, -0.04858295, -0.0021323704, -0.007835221, -0.07586508, 0.041747157, -0.014815968, -0.0023612473, 0.028457034, 0.053465657, -0.072935455, 0.011649837, 0.0072744726, 0.08074779, -0.04189974, -0.04302887, 0.08923149, 0.01806602, 3.5094467E-4, 0.015456824, 0.022689335, 0.046904515, -0.011001352, -0.0097349, -9.116932E-4, 0.027877213, 0.02114823, 0.012519569, 0.012542457, -0.034789298, -0.020415824, -0.0466909, 0.03793254, -0.009284775, 0.010337609, -0.0072821015, -0.024199924, -0.030165982, 0.0117185, 0.043242484, -0.008476077, 0.04510402, 0.06780861, -0.0022830477, 0.014213258, -0.00491704, 0.0074995346, -0.037383236, -0.022887696, -0.012069444, -0.010810621, 0.024062596, -0.04577539, -0.030364342, -0.014976182, -0.0036658458, 0.012832368, -0.0051878775, 0.01577725, -0.019149372, 0.0023250084, -0.015960352, 0.04012976, -0.0029315322, -0.03167657, -0.028960563, 0.06341417, 0.010238429, 0.06359728, -0.042723697, 0.0064238133, 0.0051192143, -0.023177605, 0.004955186, 0.010116361, 0.024245698, -0.08471499, -0.004680534, 0.017806627, 0.018157572, 0.030577961, 0.007926771, -0.0042914427, -0.034178957, -0.0113065215, 0.008605774, -0.028396001, -0.010963206, 0.0206447, -0.021224523, -0.055601843, 0.06506209, -0.040282343, -0.024276216, -0.019057821, -0.021621242, 0.005370979, -0.0067518703, -0.013175683, 0.032500528, -0.0024871295, -0.018752651, -0.007030337, 0.041045267, -0.07317959, -0.049223803, 0.027037997, 0.0224452, -0.009155078, 0.03637618, 0.022002704, -0.018722136, 0.010909801, 0.031707086, -0.016051903, 0.001771889, 0.001876791, -0.03366017, 0.0625597, 0.03890908, 0.0094297305, -0.032164842, 0.01818809, 3.888524E-4, -0.03286673, -0.017348872, -0.053252038, -0.0057257386, -0.008086986, -0.027541526, 0.021682277, -0.015960352, -0.010436789, 0.042540595, 0.029570902, 0.0069654887, -0.061155923, 0.008178537, 0.025969904, 0.033202417, -0.01161932, 0.044920918, 8.8308356E-4, 0.017333614, 0.006088127, -0.020827802, -0.014579462, -0.007556754, -0.007087556, -0.008811763, -0.0011958821, 0.002334545, -5.2689382E-5, -0.03637618, -0.0043067015, -0.028914789, 0.014587091, -0.012756076, 0.01658595, -0.019210406, -0.014022528, 0.033690687, -0.023375966, 0.023848979, -0.029372543, 0.033904307, -0.019027304, 0.072508216, -0.031859674, 0.04193026, -0.00580966, -0.008003064, -0.019134114, 0.0070799273, 0.010780104, 0.012099962, 0.011893972, -0.020110656, -0.01589932, 0.061644193, -0.008422672, -0.009895113, 0.014060674, 0.024672935, -0.010741958, -0.04647728, 0.053465657, 0.0056723338, -0.052641697, -0.06384141, 0.00933055, 0.10644304, -0.06420761, -0.008811763, 0.0049475566, -0.0389396, 0.07903884, 0.0236201, 0.036955997, -0.006763314, 0.039977174, -0.0065382514, 0.030608477, 0.008010693, 0.0024547053, -0.017409908, -0.033965338, -0.04788106, -0.025573185, -0.0011472458, -0.01669276, -0.020888837, -0.008437931, 0.044768333, 0.032408975, -0.023711652, -0.038756497, 0.007598715, -0.0041159703, -0.11779534, 0.026183523, -0.020003846, -0.015243205, -0.05111585, -0.021010904, -0.0389396, 0.0103299795, -0.011230229, -0.020385306, 0.007453759, 0.039885625, 0.028823238, -0.032592077, -0.017791368, 0.04580591, 0.02004962, 0.012885773, 0.0059851324, 0.020720994, -0.007926771, -0.028182382, 0.04269318, -0.029570902, 0.0530079, 0.024352508, -0.023772687, -0.03286673, -0.0035838317, 0.009475506, 0.011886343, -0.07891677, 0.002513832, 0.027785663, -0.04788106, 0.02114823, -0.014541316, 0.004341033, 0.049376387, 0.0068434207, 0.023101313, 0.018996786, -0.026641278, 0.025237499, 0.023864238, 0.071653746, -5.779143E-4, -0.018539034, -0.033477068, 0.0076406756, -0.0030860242, -0.005172619, 0.008827021, 0.006793831, -0.030059174, 0.010673295, -0.07018893, 0.003753582, -0.0031050972, 0.031737603, -0.03408741, 0.051024303, -0.05230601, 0.015716217, -0.05459478, -0.019622384, -0.04577539, -0.0024127446, -0.018996786, -0.034636714, 0.016235005, 0.017913437, -0.027251616, 0.0015916484, 0.0012883865, 0.06902929, 0.009155078, 0.015304239, 0.020278499, -0.010604632, -0.0037612112, -0.006534437, -0.0068472354, 0.031401917, 0.03866495, -0.022460459, -0.028853754, 0.034392577, -0.004539393, -0.006980747, -0.036009975, -0.04678245, -0.050505515, -0.04171664, 0.014228517, 5.3595356E-4, -0.016006129, -0.012786592, 0.0022391796, -0.040373895, -0.010879285, -0.02601568, -0.017791368, -0.019515574, -0.028762205, 0.0057944013, 0.011085274, -0.03939735, 0.020614184, 0.020385306, 0.0032214432, -0.0013646788, 0.019851262, 0.004249482, -0.044432644, 0.0059813177, 0.012557715, 0.05062758, 0.017699817, -0.026839638, 0.037017033, -0.007549125, 0.023391224, 2.1814834E-5, 0.06329211, 0.010673295, -0.027022738, 0.008888055, 0.012443277, 0.005779143, -0.0103299795, -0.011466735, 0.03427051, 0.013770763, 0.033904307, 0.019210406, -0.0311883, 0.050078277, -0.026000421, -0.031829156, -9.093091E-4, 0.015243205, -0.011573545, -0.019729193, -0.0024814077, -0.0430899, 0.019546092, -0.015144025, 0.028151864, -0.028914789, 0.047148652, 0.009185595, -0.011428589, 0.018783169, -0.016952153, 0.052092396, -0.056517348, -0.023177605, 0.021132972, 0.06197988, 0.0371391, -0.02293347, -0.025451116, 0.025939388, 0.011558286, 0.021316074, -0.0070646685, 0.0044554714, -0.011825309, -0.020171689, 0.037474785, 0.03097468, -0.018691618, 0.0082853455, 0.021209264, -0.0044173254, 0.007911514, -0.01449554, 0.036528762, 0.018035503, 0.026427658, 0.04022131, 0.03866495, 0.025298532, -0.018477999, 0.009414472, -0.021697534, 0.04281525, -0.024459317, 0.032927763, 0.0110242395, 0.019408766, 0.017684558, -0.054014962, 0.0013637252, 0.011291263, 0.0019721563, -0.007240141, -0.034514643, 0.027175324, 0.005344277, 0.0062521556, -0.012267805, -0.029784521, -0.011382814, 0.001047112, 0.010535969, -0.042021807, -0.02224684, 0.011787163, -0.0034159885, -0.018432224, -0.003108912, 0.0047644554, -0.038787015, -0.0054091252, 0.03179864, 0.024749227, -0.012557715, -0.008445559, 0.04260163, -0.053465657, 0.002378413, -0.037017033, -0.03405689, -0.04080113, -0.02670231, -0.012862884, -0.05612063, -0.014518428, -0.04012976, 0.005630373, -1.9669113E-5, 0.026626019, -0.006015649, -0.015929837, 0.02691593, 0.042845767, 0.023711652, 7.6721463E-4, 0.054289613, -0.028136607, -0.0065306225, 0.0040587513]"}] From 4c115eb3783fcea3714bc55372f45351612f3575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Wed, 19 Nov 2025 17:53:31 +0800 Subject: [PATCH 03/36] add filter --- src/memos/graph_dbs/polardb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index d725721f7..2ad3935e3 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1460,6 +1460,7 @@ def search_by_embedding( threshold: float | None = None, search_filter: dict | None = None, user_name: str | None = None, + filter: dict | None = None, **kwargs, ) -> list[dict]: """ From 9b8aafec48a44800d45f0538ad927dfd983fd943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Thu, 20 Nov 2025 11:50:32 +0800 Subject: [PATCH 04/36] add filter --- src/memos/graph_dbs/polardb.py | 90 ++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 2ad3935e3..ac41f4928 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1468,6 +1468,8 @@ def search_by_embedding( """ # Build WHERE clause dynamically like nebular.py where_clauses = [] + scope = "LongTermMemory" + user_name = "adimin" if scope: where_clauses.append( f"ag_catalog.agtype_access_operator(properties, '\"memory_type\"'::agtype) = '\"{scope}\"'::agtype" @@ -1507,6 +1509,86 @@ def search_by_embedding( where_clauses.append( f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = {value}::agtype" ) + # Add filter conditions (supports "or" and "and" logic) + filter = { + "and": [ + {"id": "2025-11-19-02"}, + { + "info.B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" + }, + {"memory": "湖北武汉"}, + {"info.A": "建议从基础语法开始,多做练习项目"}, + ] + } + + if filter: + # Helper function to escape string value for SQL + def escape_sql_string(value: str) -> str: + """Escape single quotes in SQL string.""" + return value.replace("'", "''") + + # Helper function to build a single filter condition + def build_filter_condition(condition_dict: dict) -> str: + """Build a WHERE condition for a single filter item. + + Args: + condition_dict: A dict like {"id": "xxx"} or {"info.B": "xxx"} + + Returns: + SQL condition string + """ + condition_parts = [] + for key, value in condition_dict.items(): + # Check if key starts with "info." prefix + if key.startswith("info."): + # Extract the field name after "info." + info_field = key[5:] # Remove "info." prefix (5 characters) + # Match in info field: properties->'info'->'B' + # For nested access, use agtype_access_operator with nested structure + if isinstance(value, str): + escaped_value = escape_sql_string(value) + # Access nested field: properties->'info'->'B' + # First get info object, then get the field inside it + condition_parts.append( + f"ag_catalog.agtype_access_operator(ag_catalog.agtype_access_operator(properties, '\"info\"'::agtype), '\"{info_field}\"'::agtype) = '\"{escaped_value}\"'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(ag_catalog.agtype_access_operator(properties, '\"info\"'::agtype), '\"{info_field}\"'::agtype) = {value}::agtype" + ) + else: + # Key doesn't have "info." prefix, match in properties directly + if isinstance(value, str): + escaped_value = escape_sql_string(value) + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = '\"{escaped_value}\"'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = {value}::agtype" + ) + return " AND ".join(condition_parts) + + # Process filter structure + if isinstance(filter, dict): + if "or" in filter: + # OR logic: at least one condition must match + or_conditions = [] + for condition in filter["or"]: + if isinstance(condition, dict): + condition_str = build_filter_condition(condition) + if condition_str: + or_conditions.append(f"({condition_str})") + if or_conditions: + where_clauses.append(f"({' OR '.join(or_conditions)})") + + elif "and" in filter: + # AND logic: all conditions must match + for condition in filter["and"]: + if isinstance(condition, dict): + condition_str = build_filter_condition(condition) + if condition_str: + where_clauses.append(f"({condition_str})") where_clause = f"WHERE {' AND '.join(where_clauses)}" if where_clauses else "" @@ -1528,12 +1610,20 @@ def search_by_embedding( WHERE scope > 0.1; """ params = [vector] + logger.debug(f"search_by_embedding query: {query}") + logger.debug(f"search_by_embedding params: {params}") + print("=== SQL Query ===") + print(query) + print("=== Params ===") + print(params) + print("================") conn = self._get_connection() try: with conn.cursor() as cursor: cursor.execute(query, params) results = cursor.fetchall() + print("88888results:", results) output = [] for row in results: """ From bfaa157a628341874d952692e2f52d0f2040e776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Thu, 20 Nov 2025 15:37:39 +0800 Subject: [PATCH 05/36] add filter --- src/memos/graph_dbs/polardb.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index ac41f4928..940948159 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1518,9 +1518,9 @@ def search_by_embedding( }, {"memory": "湖北武汉"}, {"info.A": "建议从基础语法开始,多做练习项目"}, + {"user_id": "65c3a65b-78f7-4009-bc92-7ee1981deb3a"}, ] } - if filter: # Helper function to escape string value for SQL def escape_sql_string(value: str) -> str: @@ -1551,13 +1551,14 @@ def build_filter_condition(condition_dict: dict) -> str: # First get info object, then get the field inside it condition_parts.append( f"ag_catalog.agtype_access_operator(ag_catalog.agtype_access_operator(properties, '\"info\"'::agtype), '\"{info_field}\"'::agtype) = '\"{escaped_value}\"'::agtype" + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '\"{escaped_value}\"'::agtype" ) else: condition_parts.append( f"ag_catalog.agtype_access_operator(ag_catalog.agtype_access_operator(properties, '\"info\"'::agtype), '\"{info_field}\"'::agtype) = {value}::agtype" + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '\"{value}\"'::agtype" ) else: - # Key doesn't have "info." prefix, match in properties directly if isinstance(value, str): escaped_value = escape_sql_string(value) condition_parts.append( @@ -1623,8 +1624,8 @@ def build_filter_condition(condition_dict: dict) -> str: with conn.cursor() as cursor: cursor.execute(query, params) results = cursor.fetchall() - print("88888results:", results) output = [] + print("=== Raw Results ===:", results) for row in results: """ polarId = row[0] # id From 87e5f7fb987f5cb3f34ec196698367f950f6c3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Sat, 22 Nov 2025 14:47:10 +0800 Subject: [PATCH 06/36] add get_by_metadata/get_all_memory_items/search_by_embedding filter query --- src/memos/graph_dbs/polardb.py | 108 +++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 5 deletions(-) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 940948159..68e8421b8 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1645,7 +1645,10 @@ def build_filter_condition(condition_dict: dict) -> str: @timed def get_by_metadata( - self, filters: list[dict[str, Any]], user_name: str | None = None + self, + filters: list[dict[str, Any]], + user_name: str | None = None, + filter: dict | None = None, ) -> list[str]: """ Retrieve node IDs that match given metadata filters. @@ -1718,7 +1721,52 @@ def get_by_metadata( escaped_user_name = user_name.replace("'", "''") where_conditions.append(f"n.user_name = '{escaped_user_name}'") - where_str = " AND ".join(where_conditions) + filter_where_clause = "" + if filter: + + def escape_cypher_string(value: str) -> str: + return value.replace("'", "\\'") + + def build_cypher_filter_condition(condition_dict: dict) -> str: + condition_parts = [] + for key, value in condition_dict.items(): + if key.startswith("info."): + info_field = key[5:] + if isinstance(value, str): + escaped_value = escape_cypher_string(value) + condition_parts.append(f"n.info.{info_field} = '{escaped_value}'") + else: + condition_parts.append(f"n.info.{info_field} = {value}") + else: + if isinstance(value, str): + escaped_value = escape_cypher_string(value) + condition_parts.append(f"n.{key} = '{escaped_value}'") + else: + condition_parts.append(f"n.{key} = {value}") + return " AND ".join(condition_parts) + + if isinstance(filter, dict): + if "or" in filter: + or_conditions = [] + for condition in filter["or"]: + if isinstance(condition, dict): + condition_str = build_cypher_filter_condition(condition) + if condition_str: + or_conditions.append(f"({condition_str})") + if or_conditions: + filter_where_clause = " AND " + f"({' OR '.join(or_conditions)})" + + elif "and" in filter: + and_conditions = [] + for condition in filter["and"]: + if isinstance(condition, dict): + condition_str = build_cypher_filter_condition(condition) + if condition_str: + and_conditions.append(f"({condition_str})") + if and_conditions: + filter_where_clause = " AND " + " AND ".join(and_conditions) + + where_str = " AND ".join(where_conditions) + filter_where_clause # Use cypher query cypher_query = f""" @@ -2136,7 +2184,11 @@ def count_nodes(self, scope: str, user_name: str | None = None) -> int: @timed def get_all_memory_items( - self, scope: str, include_embedding: bool = False, user_name: str | None = None + self, + scope: str, + include_embedding: bool = False, + user_name: str | None = None, + filter: dict | None = None, ) -> list[dict]: """ Retrieve all memory items of a specific memory_type. @@ -2153,13 +2205,59 @@ def get_all_memory_items( if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"}: raise ValueError(f"Unsupported memory type scope: {scope}") + filter_where_clause = "" + if filter: + + def escape_cypher_string(value: str) -> str: + """Escape single quotes in Cypher string.""" + return value.replace("'", "\\'") + + def build_cypher_filter_condition(condition_dict: dict) -> str: + condition_parts = [] + for key, value in condition_dict.items(): + if key.startswith("info."): + info_field = key[5:] + if isinstance(value, str): + escaped_value = escape_cypher_string(value) + condition_parts.append(f"n.info.{info_field} = '{escaped_value}'") + else: + condition_parts.append(f"n.info.{info_field} = {value}") + else: + if isinstance(value, str): + escaped_value = escape_cypher_string(value) + condition_parts.append(f"n.{key} = '{escaped_value}'") + else: + condition_parts.append(f"n.{key} = {value}") + return " AND ".join(condition_parts) + + if isinstance(filter, dict): + if "or" in filter: + or_conditions = [] + for condition in filter["or"]: + if isinstance(condition, dict): + condition_str = build_cypher_filter_condition(condition) + if condition_str: + or_conditions.append(f"({condition_str})") + if or_conditions: + filter_where_clause = " AND " + f"({' OR '.join(or_conditions)})" + + elif "and" in filter: + and_conditions = [] + for condition in filter["and"]: + if isinstance(condition, dict): + condition_str = build_cypher_filter_condition(condition) + if condition_str: + and_conditions.append(f"({condition_str})") + if and_conditions: + filter_where_clause = " AND " + " AND ".join(and_conditions) + # Use cypher query to retrieve memory items if include_embedding: cypher_query = f""" WITH t as ( SELECT * FROM cypher('{self.db_name}_graph', $$ MATCH (n:Memory) - WHERE n.memory_type = '{scope}' AND n.user_name = '{user_name}' + WHERE n.memory_type = '{scope}' AND n.user_name = '{user_name}'{filter_where_clause} RETURN id(n) as id1,n LIMIT 100 $$) AS (id1 agtype,n agtype) @@ -2205,7 +2303,7 @@ def get_all_memory_items( cypher_query = f""" SELECT * FROM cypher('{self.db_name}_graph', $$ MATCH (n:Memory) - WHERE n.memory_type = '{scope}' AND n.user_name = '{user_name}' + WHERE n.memory_type = '{scope}' AND n.user_name = '{user_name}'{filter_where_clause} RETURN properties(n) as props LIMIT 100 $$) AS (nprops agtype) From f4c6c2bf7509354d6ff8058e3fc31b6fc34f4861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Sat, 22 Nov 2025 17:30:07 +0800 Subject: [PATCH 07/36] add get_by_metadata/search_by_embedding filter --- src/memos/graph_dbs/neo4j.py | 123 +++++++++++++++++++++++++++++++---- 1 file changed, 110 insertions(+), 13 deletions(-) diff --git a/src/memos/graph_dbs/neo4j.py b/src/memos/graph_dbs/neo4j.py index 367b486cd..04399f3b1 100644 --- a/src/memos/graph_dbs/neo4j.py +++ b/src/memos/graph_dbs/neo4j.py @@ -653,15 +653,16 @@ def get_context_chain(self, id: str, type: str = "FOLLOWS") -> list[str]: # Search / recall operations def search_by_embedding( - self, - vector: list[float], - top_k: int = 5, - scope: str | None = None, - status: str | None = None, - threshold: float | None = None, - search_filter: dict | None = None, - user_name: str | None = None, - **kwargs, + self, + vector: list[float], + top_k: int = 5, + 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, + **kwargs, ) -> list[dict]: """ Retrieve node IDs based on vector similarity. @@ -704,6 +705,43 @@ def search_by_embedding( param_name = f"filter_{key}" where_clauses.append(f"node.{key} = ${param_name}") + filter_params = {} + if filter: + def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: + condition_parts = [] + params = {} + + for key, value in condition_dict.items(): + # All fields are stored as flat properties in Neo4j + param_name = f"filter_flat_{key}_{param_counter[0]}" + param_counter[0] += 1 + params[param_name] = value + condition_parts.append(f"node.{key} = ${param_name}") + + return " AND ".join(condition_parts), params + + param_counter = [0] + + if isinstance(filter, dict): + if "or" in filter: + or_conditions = [] + for condition in filter["or"]: + if isinstance(condition, dict): + condition_str, params = build_filter_condition(condition, param_counter) + if condition_str: + or_conditions.append(f"({condition_str})") + filter_params.update(params) + if or_conditions: + where_clauses.append(f"({' OR '.join(or_conditions)})") + + elif "and" in filter: + for condition in filter["and"]: + if isinstance(condition, dict): + condition_str, params = build_filter_condition(condition, param_counter) + if condition_str: + where_clauses.append(f"({condition_str})") + filter_params.update(params) + where_clause = "" if where_clauses: where_clause = "WHERE " + " AND ".join(where_clauses) @@ -716,7 +754,7 @@ def search_by_embedding( """ parameters = {"embedding": vector, "k": top_k} - + print("11111119999query:", query) if scope: parameters["scope"] = scope if status: @@ -727,12 +765,16 @@ def search_by_embedding( else: parameters["user_name"] = user_name - # Add search_filter parameters if search_filter: for key, value in search_filter.items(): param_name = f"filter_{key}" parameters[param_name] = value + # Add filter parameters + if filter_params: + parameters.update(filter_params) + + print("1111111filter_params:", filter_params) with self.driver.session(database=self.db_name) as session: result = session.run(query, parameters) records = [{"id": record["id"], "score": record["score"]} for record in result] @@ -744,7 +786,7 @@ def search_by_embedding( return records def get_by_metadata( - self, filters: list[dict[str, Any]], user_name: str | None = None + self, filters: list[dict[str, Any]], user_name: str | None = None, filter: dict | None = None ) -> list[str]: """ TODO: @@ -806,9 +848,64 @@ def get_by_metadata( where_clauses.append("n.user_name = $user_name") params["user_name"] = user_name + # Add filter conditions (supports "or" and "and" logic) + filter_params = {} + if filter: + # Helper function to build a single filter condition + def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: + """Build a WHERE condition for a single filter item. + + Args: + condition_dict: A dict like {"id": "xxx"} or {"A": "xxx"} + param_counter: List to track parameter counter for unique param names + + Returns: + Tuple of (condition_string, parameters_dict) + """ + condition_parts = [] + filter_params_inner = {} + + for key, value in condition_dict.items(): + # All fields are stored as flat properties in Neo4j + param_name = f"filter_meta_{key}_{param_counter[0]}" + param_counter[0] += 1 + filter_params_inner[param_name] = value + condition_parts.append(f"n.{key} = ${param_name}") + + return " AND ".join(condition_parts), filter_params_inner + + # Process filter structure + param_counter = [len(filters)] # Use list to allow modification in nested function, start from len(filters) to avoid conflicts + + if isinstance(filter, dict): + if "or" in filter: + # OR logic: at least one condition must match + or_conditions = [] + for condition in filter["or"]: + if isinstance(condition, dict): + condition_str, params = build_filter_condition(condition, param_counter) + if condition_str: + or_conditions.append(f"({condition_str})") + filter_params.update(params) + if or_conditions: + where_clauses.append(f"({' OR '.join(or_conditions)})") + + elif "and" in filter: + # AND logic: all conditions must match + for condition in filter["and"]: + if isinstance(condition, dict): + condition_str, params = build_filter_condition(condition, param_counter) + if condition_str: + where_clauses.append(f"({condition_str})") + filter_params.update(params) + where_str = " AND ".join(where_clauses) query = f"MATCH (n:Memory) WHERE {where_str} RETURN n.id AS id" + # Merge filter parameters + if filter_params: + params.update(filter_params) + with self.driver.session(database=self.db_name) as session: result = session.run(query, params) return [record["id"] for record in result] @@ -1026,7 +1123,7 @@ def get_all_memory_items(self, scope: str, **kwargs) -> list[dict]: {where_clause} RETURN n """ - + print("999999991111111query:",query) with self.driver.session(database=self.db_name) as session: results = session.run(query, params) return [self._parse_node(dict(record["n"])) for record in results] From d07d381121a0539a18371ed8bd8cbcdbc2eaf736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Sat, 22 Nov 2025 17:40:14 +0800 Subject: [PATCH 08/36] add get_all_memory_items filter --- src/memos/graph_dbs/neo4j.py | 52 +++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/memos/graph_dbs/neo4j.py b/src/memos/graph_dbs/neo4j.py index 04399f3b1..e9943eb56 100644 --- a/src/memos/graph_dbs/neo4j.py +++ b/src/memos/graph_dbs/neo4j.py @@ -1096,12 +1096,14 @@ def import_graph(self, data: dict[str, Any], user_name: str | None = None) -> No target_id=edge["target"], ) - def get_all_memory_items(self, scope: str, **kwargs) -> list[dict]: + def get_all_memory_items(self, scope: str, filter: dict | None = None, **kwargs) -> list[dict]: """ Retrieve all memory items of a specific memory_type. Args: scope (str): Must be one of 'WorkingMemory', 'LongTermMemory', or 'UserMemory'. + filter (dict, optional): Filter conditions with 'and' or 'or' logic for search results. + Example: {"and": [{"id": "xxx"}, {"A": "yyy"}]} or {"or": [{"id": "xxx"}, {"A": "yyy"}]} Returns: Returns: @@ -1111,19 +1113,61 @@ def get_all_memory_items(self, scope: str, **kwargs) -> list[dict]: if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"}: raise ValueError(f"Unsupported memory type scope: {scope}") - where_clause = "WHERE n.memory_type = $scope" + where_clauses = ["n.memory_type = $scope"] params = {"scope": scope} if not self.config.use_multi_db and (self.config.user_name or user_name): - where_clause += " AND n.user_name = $user_name" + where_clauses.append("n.user_name = $user_name") params["user_name"] = user_name + filter_params = {} + if filter: + def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: + + condition_parts = [] + filter_params_inner = {} + + for key, value in condition_dict.items(): + param_name = f"filter_flat_{key}_{param_counter[0]}" + param_counter[0] += 1 + filter_params_inner[param_name] = value + condition_parts.append(f"n.{key} = ${param_name}") + + return " AND ".join(condition_parts), filter_params_inner + + param_counter = [0] + + if isinstance(filter, dict): + if "or" in filter: + or_conditions = [] + for condition in filter["or"]: + if isinstance(condition, dict): + condition_str, filter_params_inner = build_filter_condition(condition, param_counter) + if condition_str: + or_conditions.append(f"({condition_str})") + filter_params.update(filter_params_inner) + if or_conditions: + where_clauses.append(f"({' OR '.join(or_conditions)})") + + elif "and" in filter: + for condition in filter["and"]: + if isinstance(condition, dict): + condition_str, filter_params_inner = build_filter_condition(condition, param_counter) + if condition_str: + where_clauses.append(f"({condition_str})") + filter_params.update(filter_params_inner) + + where_clause = "WHERE " + " AND ".join(where_clauses) + + if filter_params: + params.update(filter_params) + query = f""" MATCH (n:Memory) {where_clause} RETURN n """ - print("999999991111111query:",query) + print("999999991111111query:", query) with self.driver.session(database=self.db_name) as session: results = session.run(query, params) return [self._parse_node(dict(record["n"])) for record in results] From 9e9660f5a96beb766fca27b3f43876a81d542f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Sat, 22 Nov 2025 21:59:27 +0800 Subject: [PATCH 09/36] add get_by_metadata filter --- src/memos/graph_dbs/neo4j.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/memos/graph_dbs/neo4j.py b/src/memos/graph_dbs/neo4j.py index e9943eb56..c9281541d 100644 --- a/src/memos/graph_dbs/neo4j.py +++ b/src/memos/graph_dbs/neo4j.py @@ -168,7 +168,7 @@ def remove_oldest_memory( session.run(query) def add_node( - self, id: str, memory: str, metadata: dict[str, Any], user_name: str | None = None + self, id: str, memory: str, metadata: dict[str, Any], user_name: str | None = None ) -> None: user_name = user_name if user_name else self.config.user_name if not self.config.use_multi_db and (self.config.user_name or user_name): @@ -559,7 +559,7 @@ def get_children_with_embeddings( ] def get_path( - self, source_id: str, target_id: str, max_depth: int = 3, user_name: str | None = None + self, source_id: str, target_id: str, max_depth: int = 3, user_name: str | None = None ) -> list[str]: """ Get the path of nodes from source to target within a limited depth. @@ -875,7 +875,8 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s return " AND ".join(condition_parts), filter_params_inner # Process filter structure - param_counter = [len(filters)] # Use list to allow modification in nested function, start from len(filters) to avoid conflicts + param_counter = [ + len(filters)] # Use list to allow modification in nested function, start from len(filters) to avoid conflicts if isinstance(filter, dict): if "or" in filter: @@ -883,10 +884,10 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s or_conditions = [] for condition in filter["or"]: if isinstance(condition, dict): - condition_str, params = build_filter_condition(condition, param_counter) + condition_str, filter_params_inner = build_filter_condition(condition, param_counter) if condition_str: or_conditions.append(f"({condition_str})") - filter_params.update(params) + filter_params.update(filter_params_inner) if or_conditions: where_clauses.append(f"({' OR '.join(or_conditions)})") @@ -894,17 +895,22 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s # AND logic: all conditions must match for condition in filter["and"]: if isinstance(condition, dict): - condition_str, params = build_filter_condition(condition, param_counter) + condition_str, filter_params_inner = build_filter_condition(condition, param_counter) if condition_str: where_clauses.append(f"({condition_str})") - filter_params.update(params) + filter_params.update(filter_params_inner) - where_str = " AND ".join(where_clauses) - query = f"MATCH (n:Memory) WHERE {where_str} RETURN n.id AS id" + where_str = " AND ".join(where_clauses) if where_clauses else "" + if where_str: + query = f"MATCH (n:Memory) WHERE {where_str} RETURN n.id AS id" + else: + query = "MATCH (n:Memory) RETURN n.id AS id" # Merge filter parameters if filter_params: params.update(filter_params) + print("1111111query:", query) + print("1111111params:", params) with self.driver.session(database=self.db_name) as session: result = session.run(query, params) From 090487013595ff62820d449c338d28c35c44a5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Sat, 22 Nov 2025 22:05:23 +0800 Subject: [PATCH 10/36] add neo4j_search.py --- examples/basic_modules/neo4j_search.py | 1135 ++++++++++++++++++++++++ 1 file changed, 1135 insertions(+) create mode 100644 examples/basic_modules/neo4j_search.py diff --git a/examples/basic_modules/neo4j_search.py b/examples/basic_modules/neo4j_search.py new file mode 100644 index 000000000..6b0cad743 --- /dev/null +++ b/examples/basic_modules/neo4j_search.py @@ -0,0 +1,1135 @@ +import os +import sys +from typing import Optional + + +src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "src")) +sys.path.insert(0, src_path) + +from memos.configs.graph_db import GraphDBConfigFactory +from memos.graph_dbs.factory import GraphStoreFactory + + +def getNeo4j(db_name): + config = GraphDBConfigFactory( + backend="neo4j", + config={ + "uri": os.getenv("NEO4J_URI", "bolt://localhost:7687"), + "user": os.getenv("NEO4J_USER", "neo4j"), + "password": os.getenv("NEO4J_PASSWORD", "password"), + "db_name": db_name, + "user_name": os.getenv("NEO4J_USER_NAME", "neo4j"), + "use_multi_db": os.getenv("NEO4J_USE_MULTI_DB", "False").lower() == "true", + "auto_create": True, + "embedding_dimension": 1024, + }, + ) + graph = GraphStoreFactory.from_config(config) + return graph + + +def search_by_embedding( + db_name: str, vectorStr: list[float], user_name: Optional[str] = None, filter: Optional[dict] = None +): + """Test search_by_embedding function.""" + graph = getNeo4j(db_name) + + # Query search_by_embedding + nodes = graph.search_by_embedding( + vector=vectorStr, + top_k=100, + user_name=user_name, + filter=filter, + ) + print(f"search_by_embedding nodes count: {len(nodes)}") + print(f"search_by_embedding nodes: {nodes}") + for node_i in nodes: + print(f"Search result id: {node_i['id']}, score: {node_i.get('score', 'N/A')}") + + +def get_all_memory_items(db_name, scope, include_embedding, user_name, filter=None): + """Test get_all_memory_items function.""" + graph = getNeo4j(db_name) + memory_items = graph.get_all_memory_items( + scope=scope, include_embedding=include_embedding, user_name=user_name, filter=filter + ) + # print(f"get_all_memory_items count: {len(memory_items)}") + print(f"get_all_memory_items: {memory_items}") + + +def get_by_metadata(db_name, filters, user_name, filter=None): + """Test get_by_metadata function.""" + graph = getNeo4j(db_name) + ids = graph.get_by_metadata(filters=filters, user_name=user_name, filter=filter) + print(f"get_by_metadata count: {len(ids)}") + print(f"get_by_metadata: {ids}") + + +if __name__ == "__main__": + # Example vector for testing + vector = [ + 0.00036784022813662887, + 0.011101230047643185, + -0.016430197283625603, + -0.00498470664024353, + -0.024183137342333794, + -0.009238448925316334, + 0.05104490742087364, + 0.01794871687889099, + -0.003334141569212079, + -0.010242936201393604, + -0.0242774561047554, + 0.0008476831135340035, + -0.02812563069164753, + -0.020353825762867928, + 0.018505193293094635, + 0.012789522297680378, + 0.031011762097477913, + -0.01729792356491089, + -0.0011453743791207671, + -0.029861081391572952, + 0.019269170239567757, + 0.011469069868326187, + 0.018910761922597885, + 0.0024451944045722485, + 0.043009012937545776, + 0.012204750441014767, + -0.0681353285908699, + -0.019052237272262573, + -0.015911448746919632, + -0.001834485330618918, + 0.002506501041352749, + -0.035350389778614044, + 0.020410416647791862, + -0.002650336129590869, + -0.024183137342333794, + -0.02891790121793747, + -0.020976325497031212, + -0.00686635123565793, + -0.01912769302725792, + 0.030351536348462105, + 0.00514976354315877, + -0.03804788365960121, + -0.014996564015746117, + 0.03253971412777901, + -0.028729265555739403, + -0.05093172565102577, + -0.04598946124315262, + 0.032992441207170486, + -0.046744007617235184, + -0.00742754340171814, + -0.010205208323895931, + 0.009629868902266026, + 0.03682175278663635, + 0.007243623025715351, + 0.02439063787460327, + 0.029012219980359077, + -0.03712356835603714, + 0.015345539897680283, + -0.03208698704838753, + -0.014034519903361797, + 0.0018887181067839265, + -0.0008170297369360924, + 0.008946062996983528, + -0.035803116858005524, + 0.002827182412147522, + 0.08028349280357361, + 0.02163655124604702, + 0.007828394882380962, + -0.04512173682451248, + -0.03983992710709572, + 0.014807927422225475, + 0.015402130782604218, + 0.00012828722537960857, + 0.003428459633141756, + -0.03812333941459656, + 0.026144951581954956, + 0.008196234703063965, + 0.009474243968725204, + 0.013044181279838085, + 0.008837597444653511, + -0.03440720960497856, + -0.03550129756331444, + 0.04942263662815094, + -0.02959699183702469, + -0.015383267775177956, + 0.018363716080784798, + 0.029200855642557144, + 0.08428257703781128, + 0.014506109990179539, + -0.057798076421022415, + -0.06115579977631569, + 0.005494024138897657, + 0.050403546541929245, + -0.013487475924193859, + -0.03885902091860771, + 0.002942721825093031, + -0.06685260683298111, + 0.005281808786094189, + 0.01831655763089657, + 0.040934015065431595, + -0.00393306091427803, + 0.017458263784646988, + 0.05361035838723183, + 0.022183595225214958, + -0.013478043489158154, + -0.021504506468772888, + -0.01252543181180954, + 0.005324251484125853, + 0.05794898420572281, + 0.015043722465634346, + 0.005116751883178949, + -0.0031997384503483772, + 0.03099289909005165, + 0.005102604161947966, + 0.014062815345823765, + -0.015496449545025826, + -0.019203146919608116, + -0.0028436880093067884, + 0.0015067302156239748, + -0.03470902889966965, + 0.02129700593650341, + 0.04349946603178978, + 0.036387886852025986, + -0.03599175438284874, + -0.013836451806128025, + 0.041160378605127335, + -0.027012677863240242, + -0.006814476102590561, + 0.000047859022743068635, + -0.015232359059154987, + 0.033426303416490555, + 0.02290041372179985, + -0.016307583078742027, + -0.027823813259601593, + -0.005083740688860416, + -0.004946979694068432, + 0.014194860123097897, + 0.023824729025363922, + 0.022277913987636566, + -0.00172130367718637, + -0.010507026687264442, + 0.05059218034148216, + -0.004553202074021101, + -0.034218575805425644, + 0.08337712287902832, + -0.04444264620542526, + -0.02505086362361908, + 0.008021746762096882, + -0.010365549474954605, + -0.017580877989530563, + 0.01835428550839424, + -0.006470215506851673, + 0.01901451125741005, + -0.004937547724694014, + -0.014072246849536896, + 0.029332900419831276, + 0.012195318937301636, + -0.004022662993520498, + -0.0485549122095108, + -0.012110432609915733, + 0.0014041593531146646, + -0.026390179991722107, + -0.05594944208860397, + 0.022975867614150047, + 0.024918818846344948, + -0.00651737442240119, + 0.03772720322012901, + 0.02803131192922592, + -0.006569249555468559, + 0.03519948199391365, + 0.001375864027068019, + -0.017760081216692924, + -0.01337429415434599, + -0.0040462426841259, + -0.002251842524856329, + 0.014194860123097897, + 0.030464718118309975, + 0.03572766110301018, + 0.027993585914373398, + -0.04746082425117493, + -0.009356346912682056, + -0.037161294370889664, + 0.006597544997930527, + -0.01093145739287138, + -0.017646899446845055, + 0.006847487762570381, + 0.0024428365286439657, + -0.050629906356334686, + 0.007861406542360783, + 0.022711776196956635, + -0.003341215429827571, + -0.043688103556632996, + 0.012468840926885605, + -0.013666680082678795, + 0.013421452604234219, + -0.03829311206936836, + -0.023598365485668182, + -0.051535360515117645, + -0.03391675651073456, + -0.03335084766149521, + -0.0006908794166520238, + -0.009129983372986317, + -0.003421385772526264, + -0.012053841724991798, + -0.0024522682651877403, + -0.0101957768201828, + 0.03267175704240799, + 0.041386742144823074, + 0.016609402373433113, + -0.03710470348596573, + 0.020202917978167534, + 0.06296670436859131, + 0.009818504564464092, + -0.048102185130119324, + -0.021372461691498756, + 0.00003490871677058749, + -0.00059774040710181, + -0.01794871687889099, + 0.047687187790870667, + -0.03144562616944313, + 0.006507942918688059, + -0.015354972332715988, + -0.019769055768847466, + 0.05364808440208435, + -0.008507484570145607, + -0.013345998711884022, + 0.014364632777869701, + 0.03142676129937172, + -0.014590996317565441, + -0.02948381006717682, + 0.056892622262239456, + 0.029106538742780685, + 0.004211299121379852, + -0.01548701710999012, + 0.029181992635130882, + -0.03165312483906746, + -0.035010844469070435, + -0.05466672033071518, + -0.008776291273534298, + 0.01856178417801857, + 0.02857835777103901, + -0.02665426954627037, + 0.035463571548461914, + 0.014298610389232635, + -0.018778715282678604, + -0.004774849396198988, + -0.001891076099127531, + 0.04700809717178345, + -0.004692321177572012, + -0.000635467586107552, + 0.0318794883787632, + 0.0010593091137707233, + 0.0073285093531012535, + -0.0008246931247413158, + 0.0082528255879879, + 0.020787689834833145, + 0.018910761922597885, + -0.00995998177677393, + -0.028899038210511208, + -0.04051901772618294, + -0.007021975703537464, + 0.028861310333013535, + 0.016920650377869606, + 0.010799412615597248, + 0.028182221576571465, + -0.04387673735618591, + -0.0033435735385864973, + -0.06055216118693352, + 0.01948610134422779, + 0.006677715107798576, + 0.005597773939371109, + 0.018439171835780144, + -0.007639758754521608, + 0.0076586222276091576, + 0.023541774600744247, + -0.01434576977044344, + -0.050516724586486816, + -0.050290364772081375, + 0.06504169851541519, + 0.03733106702566147, + 0.03370925784111023, + 0.012091568671166897, + 0.021221552044153214, + -0.0018580647883936763, + 0.030559035018086433, + 0.04199037700891495, + 0.012968726456165314, + 0.0062014092691242695, + -0.005140331573784351, + -0.06805987656116486, + -0.03419971093535423, + -0.05179945006966591, + 0.0305024441331625, + -0.03510516509413719, + -0.030445853248238564, + 0.02425859309732914, + 0.05602489784359932, + -0.16056698560714722, + -0.03459584712982178, + -0.008323564194142818, + -0.0008972000796347857, + -0.00910168793052435, + 0.001984215108677745, + -0.046404462307691574, + 0.033407438546419144, + -0.03097403421998024, + 0.009875095449388027, + -0.01765633188188076, + -0.0322001688182354, + 0.018420306965708733, + 0.056553080677986145, + -0.01166713796555996, + -0.0005620763986371458, + -0.01611894741654396, + -0.016034061089158058, + -0.04044356197118759, + -0.051988087594509125, + -0.0052157859317958355, + -0.0407831072807312, + 0.052780359983444214, + 0.01695837825536728, + -0.04953581839799881, + -0.052214451134204865, + -0.011308729648590088, + 0.01835428550839424, + 0.027050405740737915, + -0.010242936201393604, + -0.03714243322610855, + 0.06402306258678436, + 0.0075171454809606075, + 0.020316099748015404, + 0.01684519648551941, + 0.018844738602638245, + 0.04282037541270256, + -0.028880175203084946, + -0.03287925943732262, + 0.016307583078742027, + 0.037312205880880356, + 0.04017947241663933, + -0.012666909024119377, + 0.043424010276794434, + 0.03606720641255379, + -0.03976447135210037, + 0.02346632070839405, + 0.03442607447504997, + -0.06394761055707932, + -0.056100353598594666, + -0.00024625842343084514, + -0.045612189918756485, + -0.0015692159067839384, + 0.07560531795024872, + -0.0453103706240654, + -0.044480372220277786, + -0.019882235676050186, + 0.04508401080965996, + 0.036048345267772675, + 0.015703948214650154, + -0.02412654645740986, + -0.047687187790870667, + -0.009191290475428104, + -0.021825186908245087, + -0.049837637692689896, + 0.012883840128779411, + 0.03723675012588501, + 0.01611894741654396, + 0.004284395836293697, + -0.015902016311883926, + 0.0035958741791546345, + -0.00954026635736227, + -0.01058248057961464, + -0.0029120685067027807, + 0.010997479781508446, + 0.016147242859005928, + -0.008342428132891655, + -0.014958836138248444, + 0.04885672777891159, + -0.11227615922689438, + 0.005621353629976511, + 0.021900642663240433, + -0.004838514141738415, + 0.01303474884480238, + -0.004708826541900635, + -0.017873262986540794, + -0.017571445554494858, + 0.004824366420507431, + -0.0008783364901319146, + 0.2655995190143585, + 0.008342428132891655, + 0.008281121030449867, + 0.0010239399271085858, + -0.008847028948366642, + -0.009040381759405136, + -0.04979990795254707, + 0.011478502303361893, + 0.03006858192384243, + -0.014760768972337246, + -0.01713758334517479, + 0.03174744173884392, + -0.009988277219235897, + 0.016430197283625603, + 0.02925744652748108, + 0.03282266855239868, + 0.0027470120694488287, + 0.0009950549574568868, + 0.03040812723338604, + -0.017147013917565346, + 0.00705970311537385, + -0.033539485186338425, + -0.02120268903672695, + -0.028748130425810814, + -0.0592317096889019, + 0.027484267950057983, + -0.010271231643855572, + -0.02754085883498192, + -0.0000814229715615511, + 0.06255170702934265, + -0.018278829753398895, + 0.033426303416490555, + 0.0080924853682518, + 0.03863265737891197, + -0.031709715723991394, + -0.033294256776571274, + 0.017505422234535217, + -0.014704178087413311, + -0.028163358569145203, + -0.001375864027068019, + 0.007309645880013704, + 0.01801474019885063, + 0.00019232032354921103, + 0.021108370274305344, + 0.00006134354771347716, + 0.012930999509990215, + -0.007719929330050945, + -0.02803131192922592, + -0.009743050672113895, + -0.037972431629896164, + -0.017109287902712822, + -0.00953083485364914, + 0.018363716080784798, + 0.02574881725013256, + -0.0048055024817585945, + -0.00686635123565793, + -0.0025347964838147163, + -0.05398762971162796, + 0.02950267307460308, + 0.013100771233439445, + 0.014308041892945766, + 0.036614250391721725, + -0.05519489943981171, + -0.004810218699276447, + 0.014374065212905407, + 0.022240186110138893, + -0.01377042941749096, + -0.06277807056903839, + 0.06741851568222046, + -0.012619749642908573, + -0.04293355718255043, + 0.011714297346770763, + 0.020749961957335472, + 0.0203915536403656, + 0.01919371448457241, + -0.026710860431194305, + -0.016996106132864952, + 0.018684398382902145, + -0.008950779214501381, + 0.027371086180210114, + -0.01053532212972641, + 0.006248568184673786, + 0.0060882274992764, + 0.060061708092689514, + 0.030577899888157845, + -0.02618267945945263, + 0.033331986516714096, + 0.0759071335196495, + 0.02733336016535759, + -0.011629411019384861, + -0.052553996443748474, + 0.028276540338993073, + -0.07635986059904099, + -0.025428134948015213, + 0.009931686334311962, + -0.0034826926421374083, + -0.05010172724723816, + 0.003942492883652449, + -0.05889216437935829, + 0.002205862430855632, + -0.047989003360271454, + 0.004331554751843214, + -0.027597449719905853, + 0.03238880634307861, + 0.003793942043557763, + -0.00626271590590477, + -0.040254928171634674, + 0.0024074672255665064, + 0.011704864911735058, + 0.02346632070839405, + 0.04259401187300682, + 0.011987819336354733, + -0.023956773802638054, + 0.023975638672709465, + -0.04946036636829376, + 0.030804261565208435, + 0.05304444953799248, + 0.01741110533475876, + 0.017826104536652565, + 0.033879030495882034, + -0.012959294952452183, + 0.0039000497199594975, + -0.035595618188381195, + 0.01959928311407566, + 0.0057911258190870285, + -0.03827424719929695, + -0.025711089372634888, + 0.08216985315084457, + 0.0391608364880085, + 0.017128150910139084, + -0.03250198811292648, + -0.022353367879986763, + -0.017580877989530563, + 0.010837139561772346, + 0.03359607607126236, + 0.006215556990355253, + 0.0016647129086777568, + -0.013402589596807957, + 0.023070184513926506, + -0.03040812723338604, + -0.004553202074021101, + -0.006522090639919043, + 0.008483905345201492, + 0.011403047479689121, + 0.03682175278663635, + 0.0018486330518499017, + -0.043801285326480865, + 0.07236077636480331, + 0.015534176491200924, + 0.053233083337545395, + 0.053572628647089005, + 0.040820833295583725, + 0.057118985801935196, + -0.05515717342495918, + -0.020240645855665207, + -0.019637009128928185, + -0.05749626085162163, + -0.025880862027406693, + 0.006724874023348093, + -0.015628494322299957, + -0.0019005079520866275, + 0.008351859636604786, + 0.04165083169937134, + 0.0872252956032753, + -0.010176912881433964, + 0.0022082203067839146, + 0.020674508064985275, + 0.05089399963617325, + -0.01474190503358841, + -0.013232816942036152, + -0.028634948655962944, + -0.03131357952952385, + -0.03572766110301018, + 0.010167481377720833, + 0.015543607994914055, + 0.01883530616760254, + 0.02493768185377121, + 0.01729792356491089, + -0.005904307588934898, + -0.03938720002770424, + -0.013364861719310284, + 0.03212471306324005, + 0.021957233548164368, + 0.02563563548028469, + 0.013638384640216827, + 0.036953795701265335, + 0.033294256776571274, + -0.027767222374677658, + 0.06617351621389389, + -0.007413395680487156, + -0.03016289882361889, + 0.07337941229343414, + -0.04089628905057907, + -0.010337254032492638, + -0.013072475790977478, + 0.010327822528779507, + 0.05444035679101944, + -0.017345082014799118, + 0.03401107341051102, + -0.03086085245013237, + -0.0598730742931366, + -0.020768824964761734, + -0.02095746248960495, + 0.030332671478390694, + -0.020467007532715797, + -0.055760808289051056, + 0.018231671303510666, + 0.01833542063832283, + 0.008342428132891655, + -0.030709944665431976, + 0.011025775223970413, + -0.020693371072411537, + 0.012478272430598736, + 0.0182882621884346, + -0.0017295564757660031, + -0.04500855505466461, + -0.019637009128928185, + 0.06296670436859131, + -0.023541774600744247, + -0.03587857261300087, + 0.012864976190030575, + -0.010723957791924477, + -0.004381071776151657, + -0.015128608793020248, + 0.021674279123544693, + -0.0026526940055191517, + 0.03087971732020378, + 0.0019146555569022894, + -0.020448144525289536, + -0.008620666339993477, + 0.002066743327304721, + 0.012732931412756443, + -0.0013310628710314631, + -0.014779631979763508, + -0.006493795197457075, + -0.033407438546419144, + 0.020655645057559013, + 0.03563334420323372, + -0.02188177779316902, + -0.01657167449593544, + -0.003961356356739998, + -0.004010873381048441, + 0.08669711649417877, + 0.062363069504499435, + 0.004758343566209078, + 0.03614266216754913, + -0.06141988933086395, + -0.014383496716618538, + 0.07503940910100937, + 0.030351536348462105, + -0.04055674374103546, + -0.019231442362070084, + -0.03133244439959526, + -0.019844509661197662, + 0.03485993668437004, + -0.004421156831085682, + 0.022372232750058174, + -0.01401565596461296, + 0.0038528908044099808, + -0.06496624648571014, + 0.023994501680135727, + 0.03710470348596573, + 0.02995540015399456, + 0.004449452273547649, + -0.015722813084721565, + -0.08292439579963684, + -0.023485183715820312, + -0.034803345799446106, + -0.016552811488509178, + -0.051988087594509125, + -0.019108828157186508, + 0.008785722777247429, + -0.008856461383402348, + -0.012912135571241379, + -0.03567107021808624, + 0.021598825231194496, + -0.043914467096328735, + 0.009445948526263237, + -0.050064001232385635, + 0.0006496153073385358, + -0.08903620392084122, + 0.0419149249792099, + -0.014770200476050377, + 0.01560019887983799, + -0.017392240464687347, + -0.006130670662969351, + -0.006616408471018076, + -0.006239136215299368, + -0.01587372086942196, + -0.046291280537843704, + 0.02446609176695347, + -0.03587857261300087, + -0.02105177938938141, + 0.037312205880880356, + -0.0033718689810484648, + -0.009342199191451073, + -0.025088591501116753, + 0.01991996355354786, + -0.014836222864687443, + -0.030370399355888367, + -0.03961356356739998, + -0.06756941974163055, + 0.01303474884480238, + -0.006927657872438431, + 0.0307476706802845, + -0.011289865709841251, + 0.019976554438471794, + -0.008710267953574657, + 0.0043103331699967384, + 0.014921109192073345, + -0.04885672777891159, + -0.028163358569145203, + 0.02310791239142418, + -0.011214411817491055, + 0.010035436600446701, + -0.026710860431194305, + 0.06277807056903839, + -0.0029073527548462152, + 0.004116981290280819, + 0.003440249478444457, + 0.0003112494305241853, + 0.03374698385596275, + -0.029804490506649017, + 0.037066977471113205, + 0.008498053066432476, + 0.020070873200893402, + -0.0045249066315591335, + -0.026390179991722107, + 0.0024334045592695475, + -0.026012906804680824, + 0.02093859761953354, + -0.019957691431045532, + -0.008639529347419739, + 0.009931686334311962, + 0.000795218744315207, + 0.04410310089588165, + 0.02093859761953354, + 0.021259279921650887, + 0.02174973301589489, + 0.03995310887694359, + 0.000977370422333479, + 0.0028484039939939976, + 0.08254712074995041, + -0.0018450961215421557, + 0.013760997913777828, + 0.02037269063293934, + -0.06160852313041687, + -0.003560504876077175, + -0.0017354513984173536, + 0.018005307763814926, + -0.015807699412107468, + -0.008097201585769653, + -0.04591400921344757, + 0.000021074180040159263, + 0.031936079263687134, + 0.005008286330848932, + -0.0047795651480555534, + -0.00887532439082861, + 0.03953811153769493, + 0.030728807672858238, + -0.031370170414447784, + 0.033520620316267014, + -0.005494024138897657, + 0.010488162748515606, + -0.028880175203084946, + 0.043424010276794434, + 0.01686405949294567, + -0.025899725034832954, + -0.038783565163612366, + 0.013760997913777828, + -0.0024027512408792973, + 0.044027648866176605, + 0.03346402943134308, + -0.01225190982222557, + -0.04131129011511803, + 0.029766764491796494, + -0.01613781228661537, + 0.030087444931268692, + 0.014270314946770668, + 0.033652666956186295, + -0.050969451665878296, + 0.007748224772512913, + -0.00014037173241376877, + -0.02550359070301056, + 0.0034709027968347073, + -0.0016399543965235353, + 0.01741110533475876, + 0.016590537503361702, + 0.007441691122949123, + 0.027993585914373398, + -0.010648502968251705, + -0.013874179683625698, + 0.016769742593169212, + 0.03355834633111954, + -0.12359432131052017, + 0.027257904410362244, + -0.0051591950468719006, + 0.03472789004445076, + -0.03953811153769493, + 0.05798671394586563, + -0.009761913679540157, + -0.06104261800646782, + 0.0005906665464863181, + -0.02256086841225624, + 0.0021893568336963654, + -0.007465270347893238, + 0.03942492976784706, + -0.010044868104159832, + -0.032747212797403336, + -0.00237445579841733, + -0.03153994306921959, + 0.012157591991126537, + -0.03599175438284874, + 0.022957004606723785, + -0.025880862027406693, + -0.022353367879986763, + -0.025579044595360756, + -0.021693142130970955, + 0.0013393157860264182, + -0.006706010550260544, + 0.0031077784951776266, + 0.01679803803563118, + 0.002025479217991233, + 0.03250198811292648, + -0.045499008148908615, + -0.08896074444055557, + 0.006781464908272028, + 0.06583397090435028, + -0.015656789764761925, + -0.0027234326116740704, + 0.014411792159080505, + -0.03257744014263153, + 0.016647128388285637, + 0.044254008680582047, + 0.007276634685695171, + -0.0014784347731620073, + -0.0016670707846060395, + -0.02448495477437973, + -0.019637009128928185, + 0.05885443836450577, + 0.006451352033764124, + -0.04432946443557739, + -0.014732473529875278, + -0.003961356356739998, + -0.014647587202489376, + 0.013185657560825348, + 0.0259751807898283, + 0.03176630660891533, + 0.022070415318012238, + 0.01139361597597599, + -0.018052468076348305, + 0.0021103655453771353, + -0.03542584553360939, + 0.026031771674752235, + -0.05455353856086731, + 0.022523140534758568, + -0.013760997913777828, + 0.03585970774292946, + -0.033313121646642685, + 0.014496678486466408, + -0.035803116858005524, + 0.024220865219831467, + -0.02027837187051773, + 0.001767283771187067, + -0.031275853514671326, + -0.0025135749019682407, + -0.02950267307460308, + 0.015883153304457664, + -0.0010156871285289526, + 0.05149763450026512, + 0.04040583595633507, + 0.016694288700819016, + -0.057118985801935196, + -0.037632886320352554, + -0.028899038210511208, + -0.020410416647791862, + -0.015072017908096313, + 0.022277913987636566, + -0.03636902570724487, + -0.008017030544579029, + -0.0005670870305038989, + -0.029181992635130882, + -0.04912082105875015, + 0.036746297031641006, + -0.03235107660293579, + -0.02299473062157631, + -0.018165647983551025, + -0.03538811579346657, + -0.03618038818240166, + 0.007507713511586189, + -0.02450381964445114, + -0.008743279613554478, + 0.020070873200893402, + -0.03780265897512436, + 0.024069955572485924, + -0.029785627499222755, + 0.019137123599648476, + 0.012714067474007607, + -0.010629639960825443, + 0.021127235144376755, + 0.002258916385471821, + -0.03574652597308159, + 0.0006171934655867517, + -0.00552703533321619, + -0.03163425996899605, + -0.052780359983444214, + -0.040254928171634674, + -0.018816443160176277, + -0.04523491859436035, + -0.03584084287285805, + 0.018637238070368767, + 0.010978616774082184, + -0.03167198970913887, + 0.006795612629503012, + 0.018825875595211983, + -0.06194806843996048, + 0.05708125978708267, + -0.043424010276794434, + 0.012685772031545639, + 0.03250198811292648, + 0.007965155877172947, + 0.04266946762800217, + 0.059948526322841644, + 0.0464421883225441, + 0.03442607447504997, + -0.01613781228661537, + 0.008691404946148396, + 0.015072017908096313, + 0.07107805460691452, + -0.022372232750058174, + 0.015147472731769085, + 0.010620207525789738, + 0.0021292290184646845, + -0.02938949130475521, + -0.012459409423172474, + -0.03338857740163803, + -0.020749961957335472, + -0.051195815205574036, + 0.0009042739402502775, + 0.007304930128157139, + 0.003940134774893522, + -0.01592087931931019, + -0.0015857215039432049, + -0.03276607766747475, + 0.019976554438471794, + 0.000674963288474828, + -0.028219949454069138, + 0.04006629064679146, + -0.037519704550504684, + 0.04610264301300049, + -0.01058248057961464, + -0.04244310408830643, + -0.039123110473155975, + 0.030011991038918495, + -0.006031636614352465, + 0.013949633575975895, + -0.021561097353696823, + 0.005866580177098513, + -0.000714458932634443, + -0.017345082014799118, + 0.0005564762395806611, + -0.006706010550260544, + -0.024918818846344948, + -0.019165419042110443, + -0.03363380208611488, + 0.055987171828746796, + -0.014477814547717571, + -0.009634585119783878, + 0.006993680261075497, + -0.05523262545466423, + 0.00965344812721014, + 0.012685772031545639, + 0.014251451008021832, + 0.030841989442706108, + 0.004069822374731302, + -0.0034237438812851906, + 0.03908538445830345, + 0.06794669479131699, + -0.04614036902785301, + 0.004355133976787329, + -0.043801285326480865, + 0.01560963038355112, + 0.018571216613054276, + 0.0647398829460144, + -0.0036265274975448847, + 0.018656102940440178, + -0.05628898739814758, + -0.005522319581359625, + 0.025880862027406693, + 0.03817993029952049, + -0.005295956507325172, + 0.01178031973540783, + 0.005229933653026819, + 0.018590079620480537, + -0.043801285326480865, + 0.0019794993568211794, + 0.018250534310936928, + -0.009893959388136864, + -0.002834256272763014, + -0.005253513343632221, + 0.014223155565559864, + -0.01840144395828247, + -0.004965843167155981, + -0.03135130554437637, + -0.028314266353845596, + -0.03870811313390732, + -0.014911677688360214, + 0.01629815250635147, + -0.028785856440663338, + 0.020410416647791862, + -0.007182316388934851, + -0.008276405744254589, + -0.0012449977220967412, + 0.03189834952354431, + 0.03225675970315933, + 0.004541411995887756, + 0.0032091704197227955, + 0.012449976988136768, + -0.012959294952452183, + 0.02344745770096779, + -0.0082528255879879, + -0.002018405357375741, + 0.004890388809144497, + -0.012242477387189865, + 0.02755972184240818, + 0.028672674670815468, + 0.02093859761953354, + 0.041047196835279465, + -0.02061791718006134, + -0.01463815476745367, + 0.002380350837484002, + -0.0033058463595807552, + -0.013600656762719154, + -0.03999083489179611, + 0.049837637692689896, + -0.0009903390891849995, + -0.012600886635482311, + -0.002815392566844821, + ] + # Pad vector to 1024 dimensions (fill with zeros for testing) + vector = vector + [0.0] * (1024 - len(vector)) + + # Example filter for testing + filter_example = { + "or": [ + {"id": "4534c646-50fe-4f8b-9488-91992ec8af91"}, + {"A": "湖北武当山"}, + ] + } + + # Example filters for get_by_metadata + filters_example = [ + {"field": "status", "op": "=", "value": "activated"}, + ] + + # Test search_by_embedding + search_by_embedding( + db_name="pref-tmp-db-test", + vectorStr=vector, + user_name="memosfeebbc2bd1744d7bb5b5ec57f38e828d", + filter=filter_example, + ) + + # Test get_all_memory_items + get_all_memory_items( + db_name="pref-tmp-db-test", + scope="WorkingMemory", + include_embedding=False, + user_name="memosfeebbc2bd1744d7bb5b5ec57f38e828d", + filter=filter_example, + ) + + # #Test get_by_metadata + get_by_metadata( + db_name="pref-tmp-db-test", + filters=filters_example, + user_name="memosfeebbc2bd1744d7bb5b5ec57f38e828d", + filter=filter_example + ) From 029f7ba5e631a7377264b01489535e5ff42a325a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Sat, 22 Nov 2025 22:38:39 +0800 Subject: [PATCH 11/36] add polardb_search.py --- examples/basic_modules/polardb_search.py | 1305 ++++++++++++++++++++++ 1 file changed, 1305 insertions(+) create mode 100644 examples/basic_modules/polardb_search.py diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py new file mode 100644 index 000000000..ccb9b7940 --- /dev/null +++ b/examples/basic_modules/polardb_search.py @@ -0,0 +1,1305 @@ +import json +import os +import sys +from typing import Optional + + +src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "src")) +sys.path.insert(0, src_path) + +import psycopg2 + +from memos.configs.graph_db import GraphDBConfigFactory +from memos.graph_dbs.factory import GraphStoreFactory + +def getGraph(db_name): + config = GraphDBConfigFactory( + backend="nebular", + config={ + "uri": json.loads(os.getenv("NEBULAR_HOSTS", "localhost")), + "user": os.getenv("NEBULAR_USER", "root"), + "password": os.getenv("NEBULAR_PASSWORD", "xxxxxx"), + "space": db_name, + "use_multi_db": False, + "auto_create": True, + "embedding_dimension": 1024, + }, + ) + graph = GraphStoreFactory.from_config(config) + return graph + + +def getPolarDb(db_name): + config = GraphDBConfigFactory( + backend="polardb", + config={ + "host": os.getenv("POLARDB_HOST", "memory.pg.polardb.rds.aliyuncs.com"), + "port": int(os.getenv("POLARDB_PORT", "5432")), + "user": os.getenv("POLARDB_USER", "adimin"), + "password": os.getenv("POLARDB_PASSWORD", "Openmem0925"), + "db_name": db_name, + "user_name": os.getenv("POLARDB_USER_NAME", "adimin"), + "use_multi_db": os.getenv("POLARDB_USE_MULTI_DB", "True").lower() == "true", # 设置为True,不添加user_name过滤条件 + "auto_create": True, + "embedding_dimension": 1024, + }, + ) + graph = GraphStoreFactory.from_config(config) + return graph + + +def searchVector(db_name: str, vectorStr: list[float], user_name: Optional[str] = None, filter: Optional[dict] = None): + graph = getPolarDb(db_name) + print("6666666666666") + + # 1,查询search_by_embedding + nodes = graph.search_by_embedding( + vector=vectorStr, top_k=100, user_name=user_name, filter=filter + ) + print("search_by_embedding nodes:", len(nodes)) + for node_i in nodes: + print("Search result:", graph.get_node(node_i["id"][1:-1])) + + # 2,查询单个get_node + detail = graph.get_node(id='"65c3a65b-78f7-4009-bc92-7ee1981deb3a"', user_name='"adimin"') + print("单个node:", detail) + # + # # 3,查询多个get_nodes + ids = ["bb079c5b-1937-4125-a9e5-55d4abe6c95d", "d66120af-992b-44c6-b261-a6ebe6bc57a5"] + # ids = ['"bfde036f-6276-4485-9dc6-3c64eab3e132"'] + # detail_list = graph.get_nodes(ids=ids,user_name='memos7a9f9fbbb61c412f94f77fbaa8103c35') + # print("1111多个node:", len(detail_list)) + # # + # print("多个node:", detail_list) + + # 4,更新 update_node + # graph.update_node(id="000009999ef-926f-42e2-b7b5-0224daf0abcd", fields={"name": "new_name"}) + + # 4,查询 get_memory_count + # count = graph.get_memory_count('UserMemory','memos07ba3d044650474c839e721f3a69d38a') + # print("user count:", count) + # # + # # 4,判断node是否存在 node_not_exist 1代表存在, + # isNodeExist = graph.node_not_exist('UserMemory', 'memos07ba3d044650474c839e721f3a69d38a') + # print("user isNodeExist:", isNodeExist) + # + # # 6,删除跳过多少行之后的数据remove_oldest_memory + # remove_oldest_memory = graph.remove_oldest_memory('UserMemory', 2,'memos07ba3d044650474c839e721f3a69d38a') + # print("user remove_oldest_memory:", remove_oldest_memory) + + # 7,更新 update_node + # isNodeExist = graph.update_node(id="bb079c5b-1937-4125-a9e5-55d4abe6c95d", fields={"status": "inactived","tags": ["yoga", "travel11111111", "local studios5667888"]}) + # print("user update_node:", isNodeExist) + + # 8,删除 delete_node + # isNodeDeleted = graph.delete_node(id="bb079c5b-1937-4125-a9e5-55d4abe6c95d", user_name='memosbfb3fb32032b4077a641404dc48739cd') + # print("user isNodeDeleted:", isNodeDeleted) + + +# 9,添加边 add_edge +def add_edge( + db_name: str, source_id: str, target_id: str, edge_type: str = "Memory", user_name: Optional[str] = None +): + graph = getPolarDb(db_name) + graph.add_edge(source_id, target_id, edge_type, user_name) + + +def edge_exists( + db_name: str, + source_id: str, + target_id: str, + type: str = "Memory", + direction: str = "OUTGOING", + user_name: Optional[str] = None, +): + graph = getPolarDb(db_name) + isEdge_exists = graph.edge_exists( + source_id=source_id, + target_id=target_id, + type=type, + user_name=user_name, + direction=direction, + ) + print("edge_exists:", isEdge_exists) + + +def get_children_with_embeddings(db_name: str, id: str, user_name: Optional[str] = None): + graph = getPolarDb(db_name) + children = graph.get_children_with_embeddings(id=id, user_name=user_name) + print("get_children_with_embedding:", children) + + +def get_subgraph(db_name, center_id, depth, center_status, user_name): + graph = getPolarDb(db_name) + subgraph = graph.get_subgraph(center_id, depth, center_status, user_name) + print("111111get_subgraph:", subgraph) + # subgraph = convert_graph_edges(subgraph) + # print("222222get_subgraph:", subgraph) + + +def convert_graph_edges(core_node: dict) -> dict: + import copy + + data = copy.deepcopy(core_node) + + id_map = {} + + core_node = data.get("core_node", {}) + core_meta = core_node.get("metadata", {}) + if "graph_id" in core_meta and "id" in core_node: + id_map[core_meta["graph_id"]] = core_node["id"] + + for neighbor in data.get("neighbors", []): + n_meta = neighbor.get("metadata", {}) + if "graph_id" in n_meta and "id" in neighbor: + id_map[n_meta["graph_id"]] = neighbor["id"] + + for edge in data.get("edges", []): + src = edge.get("source") + tgt = edge.get("target") + + if src in id_map: + edge["source"] = id_map[src] + if tgt in id_map: + edge["target"] = id_map[tgt] + + return data + + +def get_grouped_counts(db_name, user_name): + graph = getPolarDb(db_name) + grouped_counts = graph.get_grouped_counts( + group_fields=["status"], + where_clause="user_name = %s", + params=[user_name], + user_name=user_name, + ) + grouped_counts = graph.get_grouped_counts1( + group_fields=["status"], params=[user_name], user_name=user_name + ) + print("get_grouped_counts:", grouped_counts) + + +def export_graph(db_name, include_embedding, user_name): + graph = getPolarDb(db_name) + export_graphlist = graph.export_graph(include_embedding=include_embedding, user_name=user_name) + print("export_graph:", export_graphlist) + + +def get_structure_optimization_candidates(db_name, scope, include_embedding, user_name): + graph = getPolarDb(db_name) + candidates = graph.get_structure_optimization_candidates( + scope=scope, include_embedding=include_embedding, user_name=user_name + ) + print("get_structure_optimization_candidates:", candidates) + + +def get_all_memory_items(db_name, scope, include_embedding, user_name, filter=None): + graph = getPolarDb(db_name) + memory_items = graph.get_all_memory_items( + scope=scope, include_embedding=include_embedding, user_name=user_name, filter=filter + ) + print("11111get_all_memory_items:", memory_items) + + +def get_neighbors_by_tag(db_name, user_name): + graph = getPolarDb(db_name) + tags = ["旅游建议", "景点"] + ids = ["39d12b46-ebe4-4f25-b0b7-1582042049e7"] + neighbors = graph.get_neighbors_by_tag(tags=tags, exclude_ids=ids, user_name=user_name) + print("get_neighbors_by_tag:", neighbors) + + +def get_edges(db_name: str, id: str, type: str, direction: str, user_name: Optional[str] = None) -> None: + graph = getPolarDb(db_name) + edges = graph.get_edges(id=id, type=type, direction=direction, user_name=user_name) + print("get_edges:", edges) + + +def get_by_metadata(db_name, filters, user_name, filter=None): + graph = getPolarDb(db_name) + ids = graph.get_by_metadata(filters=filters, user_name=user_name, filter=filter) + print("get_by_metadata:", ids) + + +if __name__ == "__main__": + # handler_node_edge(db_name="shared-tree-textual-memory-product-0731",type="node") + # handler_node_edge(db_name="shared-tree-textual-memory-product-0731",type="edge") + vector = [ + -0.059882111847400665, + 0.020448941737413406, + -0.005792281590402126, + 0.016083329916000366, + -0.0009665691759437323, + 0.0016216486692428589, + -0.02259845845401287, + -0.0012792478082701564, + 0.016644487157464027, + -0.0013910036068409681, + -0.02008751966059208, + 0.02077232114970684, + -0.004075521603226662, + 0.009449313394725323, + 0.0073425970040261745, + 0.0008845356060191989, + -0.015417550690472126, + 0.02891385369002819, + 0.008688422851264477, + -0.029541587457060814, + -0.023283259943127632, + -0.01937418431043625, + 0.027772516012191772, + 0.038520101457834244, + -0.005992015358060598, + 0.02250334806740284, + -0.002231550170108676, + -0.036693960428237915, + 0.0021970723755657673, + -0.03507706895470619, + -0.013239501044154167, + 0.006386727560311556, + 0.013315590098500252, + -0.07342597097158432, + -0.0537949837744236, + -0.059691887348890305, + 0.047441545873880386, + 0.009701358154416084, + -0.056229833513498306, + -0.009767936542630196, + 0.00476032355800271, + -0.008112998679280281, + 0.01600724086165428, + -0.013382168486714363, + -0.007323574740439653, + -0.02758229337632656, + 0.01832795888185501, + -0.03498195856809616, + -0.0313296802341938, + 0.007746819872409105, + 0.02704966999590397, + -0.002993630012497306, + 0.0651703029870987, + -0.014723238535225391, + -0.004736545495688915, + 0.039414145052433014, + -0.006144193932414055, + 0.022313125431537628, + -0.09518744796514511, + -0.015360483899712563, + 0.0004009538097307086, + 0.011955497786402702, + -0.04432189464569092, + 0.029446477070450783, + 0.02023969776928425, + 0.04165877401828766, + -0.019269561395049095, + 0.015740929171442986, + 0.012840033508837223, + 0.008365044370293617, + -0.022541392594575882, + -0.001721515553072095, + -0.03224274888634682, + -0.027658382430672646, + -0.04869701340794563, + 0.004998101852834225, + -0.012602254748344421, + -0.012098164297640324, + -0.02200876735150814, + 0.038063567131757736, + 0.00469612330198288, + -0.0022220390383154154, + 0.016948843374848366, + 0.024443618953227997, + 0.03749289736151695, + 0.02476699836552143, + 0.005730459466576576, + 0.0463762991130352, + -0.013039766810834408, + -0.006881306879222393, + 0.024158284068107605, + -0.02368272840976715, + 0.0592353530228138, + -0.004348966758698225, + -0.030340522527694702, + -0.008445888757705688, + -0.01779533550143242, + 0.059844065457582474, + 0.02216094732284546, + 0.00398516608402133, + -0.004917256999760866, + 0.0503329299390316, + -0.02541375532746315, + -0.010871227830648422, + 0.07327379286289215, + -0.012155231088399887, + 0.03235688433051109, + -0.003397853346541524, + -0.01643524318933487, + -0.011907941661775112, + 0.050066620111465454, + 0.015037105418741703, + 0.0090688681229949, + 0.014970527961850166, + 5.1791106670862064e-05, + -0.008455399423837662, + -0.028894830495119095, + -0.01949782855808735, + 0.043256644159555435, + -0.018841560930013657, + 0.018870092928409576, + 0.029541587457060814, + 0.054898276925086975, + -0.025832245126366615, + -0.014780305325984955, + -0.034715645015239716, + -0.008617089129984379, + -0.01845160312950611, + -0.007333085872232914, + -0.016102353110909462, + 0.03351724147796631, + -0.015712397173047066, + -0.01928858272731304, + 0.01671106554567814, + -0.007994109764695168, + -0.03566676005721092, + 0.002178050111979246, + -0.03346017748117447, + -0.004945790395140648, + -0.05619179084897041, + 0.027696426957845688, + -0.0020246829371899366, + -0.03891956806182861, + -0.01835649274289608, + 0.011061450466513634, + -0.04706110060214996, + 0.03237590566277504, + -0.04219139739871025, + -0.0155411958694458, + -0.0024776507634669542, + -0.030663901939988136, + 0.00886913388967514, + 0.009254335425794125, + 0.011042428202927113, + -0.006947884801775217, + -0.00797033216804266, + -0.05642005801200867, + 0.05584938824176788, + -0.015341461636126041, + -0.007242729887366295, + -0.0037925653159618378, + -0.021704411134123802, + -0.06631163507699966, + -0.013981369324028492, + 0.02767740562558174, + 0.020049475133419037, + 0.041430506855249405, + -0.020220674574375153, + -0.02023969776928425, + -0.005892148707062006, + 0.0009047468192875385, + -0.006167971529066563, + -0.0029484520200639963, + 0.018004579469561577, + 0.005901659838855267, + 0.01532243937253952, + 0.023112060502171516, + 0.027125759050250053, + 0.023606639355421066, + -0.03311777487397194, + 0.012973189353942871, + 0.045729540288448334, + -0.0010604916606098413, + -0.04694696515798569, + -0.019031783565878868, + 0.03357430920004845, + 0.03283243998885155, + -0.01399088092148304, + 0.0002401561796432361, + 0.009121179580688477, + 0.019516849890351295, + -0.025299621745944023, + 0.06011037901043892, + 0.012973189353942871, + 0.029731810092926025, + -0.0075898864306509495, + 0.007390152662992477, + 0.017871424555778503, + 0.005259658209979534, + -0.012725899927318096, + 0.022788681089878082, + 0.029826922342181206, + -0.0018106824718415737, + -0.061860427260398865, + -0.04816439002752304, + 0.022370191290974617, + -0.011822341941297054, + -0.032984618097543716, + -0.0070049515925347805, + -0.007998865097761154, + 0.003961388021707535, + -0.009140201844274998, + 0.03869130089879036, + -0.006125171668827534, + -0.05200688913464546, + 0.011489451862871647, + -0.0320715494453907, + 0.020962543785572052, + -0.03732169792056084, + -0.04584367573261261, + 0.005682903807610273, + 0.03216665983200073, + -0.01993534155189991, + 0.026364868506789207, + 0.02550886571407318, + -0.007304552476853132, + 0.050256840884685516, + 0.03346017748117447, + 0.03625645115971565, + 0.011508474126458168, + -0.026821402832865715, + -0.0231881495565176, + 0.002248194767162204, + 0.0024847842287272215, + 0.032889507710933685, + -0.014475949108600616, + -0.017966534942388535, + 0.027468159794807434, + 0.0333079993724823, + -0.017947513610124588, + 0.019707072526216507, + -0.039604369550943375, + 0.017405377700924873, + 0.02067720890045166, + -0.02176147885620594, + 0.029180165380239487, + -0.03418302163481712, + 0.022541392594575882, + -0.002004471840336919, + -0.02522353269159794, + -0.00015485317271668464, + -0.012278876267373562, + -0.04980030655860901, + 0.02600344456732273, + 0.028989942744374275, + 0.028343183919787407, + 0.015855062752962112, + 0.007304552476853132, + -0.01764315739274025, + -0.030854124575853348, + -0.014009903185069561, + 0.023949040099978447, + 0.0323188379406929, + 0.011537007987499237, + 0.01120411790907383, + 0.014447415247559547, + -0.02912309765815735, + -0.016368664801120758, + 0.0034026089124381542, + 0.02389197237789631, + -0.005606814753264189, + -0.030283456668257713, + -0.02655509114265442, + 0.008740733377635479, + -0.02240823581814766, + 0.025794200599193573, + 0.010909272357821465, + -0.048430703580379486, + 0.05413738638162613, + -0.011670163832604885, + 0.032737329602241516, + -0.010766605846583843, + 0.026859447360038757, + -0.0567624568939209, + -0.00688606221228838, + -0.022141924127936363, + -0.023112060502171516, + -0.04862092435359955, + -0.024500686675310135, + -0.03212861716747284, + -0.030911190435290337, + 0.015893107280135155, + 0.06482790410518646, + -0.011166073381900787, + -0.022427259013056755, + 0.03591404855251312, + 0.004601011984050274, + -0.16556985676288605, + 0.06600727885961533, + -0.05915926396846771, + 0.0710291638970375, + -0.001765504595823586, + -0.020467964932322502, + -0.03855814412236214, + 0.03020736761391163, + -0.10507902503013611, + -0.002777251647785306, + -0.06399092078208923, + -0.06604532897472382, + 0.009591980837285519, + -0.035267289727926254, + 0.018366003409028053, + -0.01154651865363121, + -0.05193080008029938, + -0.002610806841403246, + -0.00699544046074152, + -0.04432189464569092, + 0.012164742685854435, + -0.004082655068486929, + 0.04747958853840828, + -0.014390348456799984, + 0.014371326193213463, + -0.02206583507359028, + 0.02027774229645729, + -0.013895769603550434, + -0.05501240864396095, + 0.016720576211810112, + 0.020467964932322502, + 0.004032721742987633, + -0.0055640144273638725, + 0.030778035521507263, + 0.013163411989808083, + 0.013210967183113098, + 0.010329093784093857, + -0.03452542424201965, + 0.05851250886917114, + -0.019202983006834984, + 0.046262163668870926, + -0.0003462648019194603, + -0.00251093995757401, + 0.06459963321685791, + -0.011042428202927113, + -0.011470429599285126, + -0.03937610238790512, + 0.019726095721125603, + -0.0448925606906414, + -0.034468356519937515, + 0.015503151342272758, + 0.011498963460326195, + 0.018432581797242165, + -0.03494391217827797, + -0.028248073533177376, + 0.021438099443912506, + -0.014447415247559547, + 0.08605675399303436, + -0.03385964408516884, + -0.0010355248814448714, + -0.015484129078686237, + -0.02324521541595459, + -0.01310634519904852, + -0.06357242912054062, + -0.013020744547247887, + 0.026916515082120895, + 0.06482790410518646, + 0.018774982541799545, + 0.04120223969221115, + -0.016549376770853996, + 0.03458248823881149, + 0.010795138776302338, + -0.00013642535486724228, + -0.025699088349938393, + 0.016368664801120758, + 0.03089216910302639, + -0.013410701416432858, + -0.01535097323358059, + -0.01433328166604042, + -0.10629645735025406, + -0.001113991835154593, + -0.019088849425315857, + -0.0310823917388916, + 0.009054601192474365, + 0.0021518943831324577, + -0.03433519974350929, + 0.032889507710933685, + 0.011232651770114899, + 0.05881686508655548, + 0.23952844738960266, + -0.02181854471564293, + -0.0062155271880328655, + 0.018527692183852196, + 0.07772500067949295, + -0.020715253427624702, + -0.01109949592500925, + 0.024938197806477547, + -0.04953399673104286, + -0.05208297818899155, + -0.000630707188975066, + 0.011242162436246872, + 0.0049125016666948795, + -0.015417550690472126, + 0.03119652532041073, + 0.02897091954946518, + -0.006030059885233641, + -0.020049475133419037, + 0.06631163507699966, + -0.011984030716121197, + 0.02581322193145752, + 0.004453589208424091, + 0.02718282677233219, + -0.0013898147735744715, + -0.017557555809617043, + -0.05619179084897041, + -0.03203350678086281, + 0.03444933518767357, + -0.019611962139606476, + 0.02668824791908264, + -0.025432776659727097, + 0.029788877815008163, + 0.04679478704929352, + -0.03226177394390106, + 0.008041664958000183, + 0.02940843254327774, + 0.013743591494858265, + -0.009805981069803238, + 0.0370173417031765, + -0.020353831350803375, + -0.001188891939818859, + -0.02185658924281597, + -0.047403499484062195, + -0.015389017760753632, + -0.007494775112718344, + -0.030435634776949883, + -0.002577517880126834, + -0.029864966869354248, + 0.01426670327782631, + -0.011137540452182293, + -0.029370388016104698, + -0.04846874624490738, + -0.051892757415771484, + -0.016368664801120758, + -0.00015381289995275438, + -0.010623938404023647, + -0.001973560778424144, + -0.005896904040127993, + -0.017662178725004196, + 0.011109006591141224, + -0.006415260955691338, + 0.0057019260711967945, + 0.0018998493906110525, + 0.0439034029841423, + 0.0027748739812523127, + -0.016045285388827324, + -0.057561393827199936, + -0.02556593343615532, + -0.0005890959873795509, + 0.04253380000591278, + 0.04595780745148659, + -0.01416208129376173, + -0.02550886571407318, + -0.03197643905878067, + 0.021742455661296844, + 0.018993739038705826, + 0.00880731176584959, + 0.0036427651066333055, + -0.02640291303396225, + -0.01055736094713211, + -0.010471760295331478, + -0.05630592256784439, + -0.03384062275290489, + -0.03250906243920326, + -0.021628322079777718, + -0.048773106187582016, + -0.014143059030175209, + 0.02086743153631687, + -0.024843087419867516, + -0.022427259013056755, + -0.00131134781986475, + -0.008617089129984379, + -0.039414145052433014, + -0.0018832049099728465, + 0.01989729516208172, + 0.008160554803907871, + 0.01120411790907383, + -0.023378372192382812, + -0.03064487874507904, + -0.023283259943127632, + -0.025337666273117065, + -0.023968061432242393, + 0.009634780697524548, + -0.017909469082951546, + -0.027430115267634392, + -0.007532819639891386, + 0.037283651530742645, + 0.024595797061920166, + -0.002140005584806204, + 0.020011430606245995, + 0.025680067017674446, + 0.03401182219386101, + -0.02748718298971653, + -0.06421919167041779, + 0.0003358619869686663, + 0.017690712586045265, + 0.019022271037101746, + 0.038710322231054306, + 0.0006794517394155264, + -0.015769463032484055, + -0.00897851213812828, + 0.05345258489251137, + 0.006990684662014246, + 0.016692044213414192, + -0.005207346752285957, + -0.016149908304214478, + -0.062202829867601395, + 0.017129555344581604, + 0.00843637716025114, + 0.01953587308526039, + 0.034430310130119324, + -0.024938197806477547, + 0.0394902341067791, + 0.027905672788619995, + -0.027658382430672646, + 0.02147614397108555, + 0.042419664561748505, + 0.023663705214858055, + 0.018813027068972588, + -0.006771928630769253, + -0.02526157721877098, + -0.009882070124149323, + -0.003887676866725087, + -0.0041230772621929646, + -0.0037545207887887955, + 0.016986887902021408, + 0.016787154600024223, + -0.05611570179462433, + -0.008231887593865395, + 0.0006657795165665448, + 0.006125171668827534, + 0.014409370720386505, + 0.036294493824243546, + -0.02940843254327774, + -0.005644859280437231, + -0.037873342633247375, + -0.04363708943128586, + 0.013724569231271744, + 0.022237036377191544, + -0.00886913388967514, + 0.002277916995808482, + -0.007718286477029324, + 0.0483546145260334, + 0.03642765060067177, + 0.029446477070450783, + 0.02157125622034073, + -0.030682923272252083, + 0.04017503932118416, + -0.049762263894081116, + -0.03696027398109436, + -0.01569337397813797, + -0.0009647858096286654, + -0.06429527699947357, + 0.04820243641734123, + 0.009863047860562801, + -0.05828424170613289, + -0.01532243937253952, + -0.03357430920004845, + -0.01928858272731304, + -0.04949595034122467, + 0.04455016180872917, + -0.0429522879421711, + -0.01453301589936018, + -0.030416611582040787, + -0.006729128770530224, + -0.013182434253394604, + -0.009534914046525955, + -0.004722279030829668, + 0.043446868658065796, + 0.002644095802679658, + -0.035799916833639145, + 0.0922960638999939, + 0.04002286121249199, + -0.0043632336892187595, + 0.0031196526251733303, + 0.016787154600024223, + 0.01658742129802704, + -0.01647328771650791, + -0.00502187991514802, + -0.03823476657271385, + 0.05744725838303566, + -0.004422678146511316, + -0.01169869676232338, + -0.025737132877111435, + -0.058055974543094635, + 0.020905476063489914, + -0.033441152423620224, + -0.004969568457454443, + -0.021590277552604675, + 0.03292755037546158, + -0.01680617779493332, + -0.0125832324847579, + -0.0072760190814733505, + 0.018641825765371323, + 0.05063728615641594, + 0.02817198447883129, + 0.04413167014718056, + 0.011679674498736858, + -0.0021804277785122395, + -0.03673200681805611, + 0.011318251490592957, + 0.001906982739455998, + -0.014295237138867378, + -0.015988219529390335, + -0.03307972848415375, + -0.009135445579886436, + 0.04531105235219002, + -0.022674547508358955, + 0.05432760715484619, + 3.115268555120565e-05, + -0.003024541074410081, + -0.01108047366142273, + -0.004106432665139437, + 0.03247101604938507, + 0.04082179442048073, + 0.014323770068585873, + 0.0038710322696715593, + 0.02550886571407318, + -0.0038686543703079224, + -0.014447415247559547, + 0.009687092155218124, + 0.03315581753849983, + -0.005911170970648527, + 0.07148569822311401, + -0.00028637435752898455, + -0.012269365601241589, + -0.02931332029402256, + -0.015331950969994068, + -1.7991278582485393e-05, + -0.008065443485975266, + 0.01872742548584938, + -0.023815883323550224, + -0.011498963460326195, + 0.02117178775370121, + -0.0006960962782613933, + 0.018242359161376953, + -0.001524159568361938, + -0.046604566276073456, + -0.0328134186565876, + 0.02033480815589428, + 0.018299425020813942, + -0.005150279961526394, + 0.007380641531199217, + 0.02012556418776512, + -0.028742652386426926, + -0.015341461636126041, + 0.03266124054789543, + -0.05482218787074089, + 0.014295237138867378, + -0.05664832517504692, + -0.01643524318933487, + 0.009482602588832378, + 0.0019771272782236338, + 0.03791138902306557, + 0.056572236120700836, + -0.0029080298263579607, + -0.02368272840976715, + -0.0003055452252738178, + 0.0863611102104187, + -0.004413167014718056, + -0.014761283062398434, + 0.0493437722325325, + 0.018841560930013657, + 0.014190614223480225, + 0.029237231239676476, + 0.0572570376098156, + 0.012564210221171379, + -0.0037259873934090137, + 0.024595797061920166, + -0.04181095212697983, + -0.0030150299426168203, + -0.01869889348745346, + -0.07167591899633408, + 0.0409739725291729, + -0.008084465749561787, + -0.030816080048680305, + -0.017224667593836784, + 0.04702305421233177, + -0.017043955624103546, + 0.012202787213027477, + -0.03739778697490692, + 0.009596736170351505, + -0.032699283212423325, + 0.021438099443912506, + -0.018622804433107376, + 0.04717523232102394, + 0.004902990534901619, + -0.01853720284998417, + 0.047593723982572556, + 0.016482798382639885, + 0.028362207114696503, + -0.005549747962504625, + 0.013391679152846336, + 0.037283651530742645, + 0.009967670775949955, + 0.017976047471165657, + 0.05349062755703926, + -0.00947309099137783, + 0.003649898339062929, + 0.0004675317613873631, + -0.0004363233456388116, + -0.002777251647785306, + -0.007746819872409105, + 0.009901092387735844, + -0.005078946705907583, + 0.040935929864645004, + -0.027449138462543488, + 0.005668636877089739, + -0.056686367839574814, + -0.00508370203897357, + -0.04881114885210991, + 0.011327763088047504, + -0.001153225195594132, + 0.03557164967060089, + 0.05311018228530884, + 0.006182238459587097, + 0.007494775112718344, + -0.03384062275290489, + 0.05128404498100281, + -0.03401182219386101, + 0.04580562934279442, + 0.0016620709793642163, + 0.05383303016424179, + -0.020848410204052925, + 0.033441152423620224, + -0.027315981686115265, + -0.008365044370293617, + 0.011584563180804253, + -0.00037687874282710254, + -0.004391767084598541, + 0.004857812542468309, + 0.015702884644269943, + -0.03463955596089363, + -0.04036526009440422, + 0.014342792332172394, + -0.014437904581427574, + -0.03218568488955498, + 0.029142120853066444, + 0.002655984601005912, + -0.021000588312745094, + -0.03705538436770439, + 0.04926768317818642, + -0.03151990473270416, + -0.054898276925086975, + -0.04447407275438309, + 0.006163216196000576, + 0.04949595034122467, + 0.017747780308127403, + -0.012963677756488323, + 0.0014742260100319982, + -0.03441128879785538, + 0.02571811154484749, + -0.0050932131707668304, + 0.0014932482736185193, + -0.01984022930264473, + 0.03212861716747284, + -0.024101218208670616, + -0.016730088740587234, + 0.040098950266838074, + -0.022636502981185913, + -0.0009725136333145201, + 0.0023742173798382282, + -0.055392853915691376, + -0.03153892606496811, + 0.04120223969221115, + -0.023758817464113235, + -0.04124028608202934, + -0.04101201891899109, + 0.0572570376098156, + 0.02645997889339924, + -0.022046811878681183, + -0.01946929469704628, + -0.027468159794807434, + -0.024747975170612335, + -0.15811312198638916, + 0.01742440089583397, + 0.0007923964876681566, + -0.005678148008882999, + -0.033327020704746246, + -0.012373987585306168, + -0.026669224724173546, + 0.008678911253809929, + -0.005487925373017788, + -0.06874649226665497, + -0.037873342633247375, + 0.036446671932935715, + 0.05265364795923233, + -0.013981369324028492, + -0.05349062755703926, + 0.06380070000886917, + -0.012012564577162266, + 0.03707440569996834, + 0.02402512915432453, + 0.04782199114561081, + -0.007081040646880865, + 0.00388529896736145, + 0.055050455033779144, + -0.0050504133105278015, + 0.04299033433198929, + 0.023169126361608505, + -0.011185095645487309, + -0.019669027999043465, + -0.029142120853066444, + -0.05524067580699921, + 0.03834889829158783, + -0.043446868658065796, + 0.018565736711025238, + 0.062050651758909225, + -0.039261966943740845, + 0.04120223969221115, + -0.01105193980038166, + 0.005882637575268745, + 0.03005518950521946, + -0.032490041106939316, + 0.028152961283922195, + 0.004144477192312479, + -0.019650006666779518, + -0.030663901939988136, + 0.0134487459436059, + 0.02476699836552143, + -0.017196133732795715, + -0.01098536141216755, + -0.01055736094713211, + 0.009059356525540352, + -0.005487925373017788, + -0.00846491102129221, + -0.027201848104596138, + 0.04980030655860901, + -0.00018977688159793615, + 0.03384062275290489, + -0.013895769603550434, + -0.03813965618610382, + -0.023302283138036728, + 0.0547841414809227, + -0.01600724086165428, + 0.03138674795627594, + -0.05512654408812523, + -0.010795138776302338, + -0.011403852142393589, + -0.011907941661775112, + -0.058702729642391205, + 0.008098731748759747, + 0.007884731516242027, + 0.029788877815008163, + 0.00961100310087204, + 0.02906603179872036, + -0.025527888908982277, + -0.029769854620099068, + 0.0007353296969085932, + 0.03370746597647667, + 0.045273005962371826, + 0.033783555030822754, + 0.020068496465682983, + -0.0325661301612854, + -0.02714478224515915, + 0.011993542313575745, + -0.026821402832865715, + 0.040593527257442474, + 0.04222944378852844, + -0.018099691718816757, + -0.030759012326598167, + 0.042457710951566696, + -0.02733500488102436, + -0.03734071925282478, + -0.03488684445619583, + -0.039946772158145905, + -0.013600924052298069, + 0.005350013729184866, + -0.028856785967946053, + -0.01399088092148304, + -0.042115308344364166, + 0.005178813356906176, + -0.008935712277889252, + -0.011337273754179478, + 0.015778973698616028, + -0.004375122487545013, + -0.001496815006248653, + 0.009591980837285519, + -0.03277537226676941, + 0.05193080008029938, + 0.03756898641586304, + -0.020905476063489914, + 0.035362403839826584, + 0.0011716530425474048, + -0.01695835590362549, + -0.012811499647796154, + -0.008455399423837662, + 0.030606834217905998, + -0.019973386079072952, + -0.010614427737891674, + 0.01569337397813797, + 0.013391679152846336, + -0.009767936542630196, + -0.006244060583412647, + 0.01828991435468197, + 0.011888919398188591, + -0.0028866296634078026, + -0.035362403839826584, + 0.03005518950521946, + 0.042115308344364166, + 0.0009594358270987868, + -0.0019913939759135246, + -0.03709343075752258, + 0.01749097928404808, + 0.011337273754179478, + -0.02792469412088394, + 0.04610998556017876, + 0.02413926273584366, + 0.030759012326598167, + -0.0010135304182767868, + -0.0582461953163147, + 0.06684426218271255, + -0.044245801866054535, + -0.03642765060067177, + -0.010519316419959068, + 0.027753494679927826, + -0.009244823828339577, + -0.024862108752131462, + -0.011755763553082943, + -0.03857716545462608, + 0.023473482578992844, + -0.007732553407549858, + 0.054898276925086975, + -0.021989746019244194, + 0.06536052376031876, + 0.01890813745558262, + -0.024443618953227997, + 0.01068100519478321, + -0.03094923496246338, + 0.021438099443912506, + -0.04333273321390152, + 0.010909272357821465, + 0.0231881495565176, + 0.04820243641734123, + 0.03859619051218033, + -0.056534189730882645, + 0.01615941897034645, + 0.006334416568279266, + 0.025337666273117065, + -0.04040330648422241, + 0.002453872933983803, + 0.0076564643532037735, + -0.023625660687685013, + 0.02940843254327774, + 0.03593306988477707, + 0.08910032361745834, + -0.01989729516208172, + 0.014561548829078674, + 0.020201653242111206, + -0.02729696035385132, + 0.022141924127936363, + -0.034772712737321854, + 0.042914245277643204, + -0.013096833601593971, + 0.029731810092926025, + 0.07677388936281204, + 0.0003816343378275633, + 0.040593527257442474, + 0.03774018585681915, + 0.040745705366134644, + -0.016701554879546165, + 0.0028081629425287247, + -0.01692982204258442, + 0.012992211617529392, + 0.0171485785394907, + -0.004149232991039753, + 0.01109949592500925, + -0.012316920794546604, + 0.0221989918500185, + -0.03167208284139633, + -0.00012096975842723623, + 0.041278328746557236, + -0.027411093935370445, + 0.003916210029274225, + -0.028286118060350418, + -0.010300559923052788, + -0.039946772158145905, + -0.05497436597943306, + -0.02571811154484749, + -0.02547082118690014, + -0.027658382430672646, + 0.011888919398188591, + -0.010272026993334293, + 0.030359545722603798, + 0.004513034131377935, + 0.013876747339963913, + 0.013220478780567646, + 0.005373791791498661, + -0.03568578138947487, + -0.02235116995871067, + 0.009244823828339577, + 0.05227320268750191, + 0.007613664027303457, + 0.006158460397273302, + -0.030321501195430756, + -0.04515887424349785, + 0.0028010294772684574, + 0.0009255523909814656, + 0.002594162244349718, + -0.0005605625919997692, + -0.029008964076638222, + -0.0034358978737145662, + -0.014247681014239788, + -0.02062014304101467, + -0.04938181862235069, + -0.024805042892694473, + 0.017624134197831154, + 0.05451783165335655, + -0.017348311841487885, + -0.0572570376098156, + 0.024500686675310135, + 0.02497624233365059, + 0.014038436114788055, + -0.01897471584379673, + 0.005939704366028309, + -0.003100630361586809, + -0.009011801332235336, + 0.030511723831295967, + ] + filter = { + "and": [ + {"id": "65c3a65b-78f7-4009-bc92-7ee1981deb3a"}, + { + # "info.A": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。", + "info.A": "建议从基础语法开始,多做练习项目" + }, + # {"info.B": "建议从基础语法开始,多做练习项目"}, + { + "info.B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" + }, + ] + } + searchVector(db_name="memos_test", vectorStr=vector, user_name="adimin",filter=filter) + # searchVector(db_name="memos_test", vectorStr=vector, user_name="adimin",filter=filter) + + # searchVector(db_name="test_1020_02", vectorStr=vector) + + # add_edge(db_name="memtensor_memos",source_id="13bb9df6-0609-4442-8bed-bba77dadac92", target_id="2dd03a5b-5d5f-49c9-9e0a-9a2a2899b98d", edge_type="PARENT", user_name="memosbfb3fb32032b4077a641404dc48739cd") + # edge_exists(db_name="memtensor_memos", source_id="13bb9df6-0609-4442-8bed-bba77dadac92", + # target_id="2dd03a5b-5d5f-49c9-9e0a-9a2a2899b98d", type="PARENT", direction="OUTGOING", + # user_name="memosbfb3fb32032b4077a641404dc48739cd") + + # get_children_with_embeddings(db_name="memtensor_memos", id="13bb9df6-0609-4442-8bed-bba77dadac92",user_name="memos07ea708ac7eb412887c5c283f874ea30") + + # get_subgraph(db_name="memtensor_memos", center_id="503ab3d5-919d-4986-b0f4-15b65c049686", depth=1, + # center_status="activated", user_name="memos07ea708ac7eb412887c5c283f874ea30") + + # + # get_grouped_counts(db_name="memtensor_memos", user_name="memos07ea708ac7eb412887c5c283f874ea30") + + # export_graph(db_name="memtensor_memos", include_embedding=False, user_name="memosa4d810417e8e4272a1c08df420be9e60") + + # get_structure_optimization_candidates(db_name="memtensor_memos", scope='UserMemory', include_embedding=False, user_name="memos8f5530534d9b413bb8981ffc3d48a495") + + # get_all_memory_items(db_name="memtensor_memos", scope='UserMemory', include_embedding=False, user_name="memosfeebbc2bd1744d7bb5b5ec57f38e828d") + # get_all_memory_items(db_name="memos_test", scope='LongTermMemory', include_embedding=False, user_name="adimin",filter=filter) + + # 测试 get_structure_optimization_candidates 函数 + # get_structure_optimization_candidates(db_name="memtensor_memos", scope='UserMemory', include_embedding=False, user_name="memos8f5530534d9b413bb8981ffc3d48a495") + + # get_neighbors_by_tag(db_name="memtensor_memos",user_name="memosfeebbc2bd1744d7bb5b5ec57f38e828d") + + # get_edges(db_name="memtensor_memos", id="13bb9df6-0609-4442-8bed-bba77dadac92",type="PARENT",direction="OUTGOING",user_name="memosfeebbc2bd1744d7bb5b5ec57f38e828d") + + # get_by_metadata(db_name="memtensor_memos", filters=[{"field": "tags", "op": "contains", "value": "glazes"}], user_name="memos452356faadb34b06acc7fa507023d91c") + # get_by_metadata( + # db_name="memos_test", + # filters=[{"field": "tags", "op": "contains", "value": "Python"}], + # user_name="adimin", + # filter=filter, + # ) From a1aa829d0e2d469099905d6780e116adbf0d7d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Sat, 22 Nov 2025 22:42:35 +0800 Subject: [PATCH 12/36] add polardb_search.py --- examples/basic_modules/polardb_search.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py index ccb9b7940..cd9279900 100644 --- a/examples/basic_modules/polardb_search.py +++ b/examples/basic_modules/polardb_search.py @@ -33,10 +33,10 @@ def getPolarDb(db_name): config = GraphDBConfigFactory( backend="polardb", config={ - "host": os.getenv("POLARDB_HOST", "memory.pg.polardb.rds.aliyuncs.com"), - "port": int(os.getenv("POLARDB_PORT", "5432")), - "user": os.getenv("POLARDB_USER", "adimin"), - "password": os.getenv("POLARDB_PASSWORD", "Openmem0925"), + "host": os.getenv("POLAR_DB_HOST", "xxxxxxxxx"), + "port": int(os.getenv("POLAR_DB_PORT", "5432")), + "user": os.getenv("POLAR_DB_USER", "xxxxxxxxx"), + "password": os.getenv("POLAR_DB_PASSWORD", "xxxxxxxxx"), "db_name": db_name, "user_name": os.getenv("POLARDB_USER_NAME", "adimin"), "use_multi_db": os.getenv("POLARDB_USE_MULTI_DB", "True").lower() == "true", # 设置为True,不添加user_name过滤条件 From 8df5163f4327abba4a1eb7a346c80dfc6595b889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Sat, 22 Nov 2025 23:38:37 +0800 Subject: [PATCH 13/36] update neo4j_search.py --- examples/basic_modules/neo4j_search.py | 72 +++++++++----------------- 1 file changed, 24 insertions(+), 48 deletions(-) diff --git a/examples/basic_modules/neo4j_search.py b/examples/basic_modules/neo4j_search.py index 6b0cad743..b0a9554d9 100644 --- a/examples/basic_modules/neo4j_search.py +++ b/examples/basic_modules/neo4j_search.py @@ -2,7 +2,6 @@ import sys from typing import Optional - src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "src")) sys.path.insert(0, src_path) @@ -10,14 +9,14 @@ from memos.graph_dbs.factory import GraphStoreFactory -def getNeo4j(db_name): +def getNeo4j(): config = GraphDBConfigFactory( backend="neo4j", config={ "uri": os.getenv("NEO4J_URI", "bolt://localhost:7687"), "user": os.getenv("NEO4J_USER", "neo4j"), "password": os.getenv("NEO4J_PASSWORD", "password"), - "db_name": db_name, + "db_name": os.getenv("NEO4J_DB_NAME", "dbname"), "user_name": os.getenv("NEO4J_USER_NAME", "neo4j"), "use_multi_db": os.getenv("NEO4J_USE_MULTI_DB", "False").lower() == "true", "auto_create": True, @@ -28,45 +27,38 @@ def getNeo4j(db_name): return graph -def search_by_embedding( - db_name: str, vectorStr: list[float], user_name: Optional[str] = None, filter: Optional[dict] = None -): +def test_search_by_embedding(graph, vector: list[float], user_name: str, filter_example: Optional[dict] = None): """Test search_by_embedding function.""" - graph = getNeo4j(db_name) - # Query search_by_embedding nodes = graph.search_by_embedding( - vector=vectorStr, + vector=vector, top_k=100, user_name=user_name, - filter=filter, + filter=filter_example, ) - print(f"search_by_embedding nodes count: {len(nodes)}") - print(f"search_by_embedding nodes: {nodes}") + print(f"test_search_by_embedding: nodes count: {len(nodes)}") + print(f"test_search_by_embedding: nodes: {nodes}") for node_i in nodes: print(f"Search result id: {node_i['id']}, score: {node_i.get('score', 'N/A')}") -def get_all_memory_items(db_name, scope, include_embedding, user_name, filter=None): +def test_get_all_memory_items(graph, scope: str, user_name: str, filter_example: Optional[dict] = None): """Test get_all_memory_items function.""" - graph = getNeo4j(db_name) memory_items = graph.get_all_memory_items( - scope=scope, include_embedding=include_embedding, user_name=user_name, filter=filter + scope=scope, include_embedding=False, user_name=user_name, filter=filter_example ) - # print(f"get_all_memory_items count: {len(memory_items)}") - print(f"get_all_memory_items: {memory_items}") + print(f"test_get_all_memory_items: count: {len(memory_items)}") + print(f"test_get_all_memory_items: {memory_items}") -def get_by_metadata(db_name, filters, user_name, filter=None): +def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_example: Optional[dict] = None): """Test get_by_metadata function.""" - graph = getNeo4j(db_name) - ids = graph.get_by_metadata(filters=filters, user_name=user_name, filter=filter) - print(f"get_by_metadata count: {len(ids)}") - print(f"get_by_metadata: {ids}") + ids = graph.get_by_metadata(filters=filters, user_name=user_name, filter=filter_example) + print(f"test_get_by_metadata: count: {len(ids)}") + print(f"test_get_by_metadata: {ids}") if __name__ == "__main__": - # Example vector for testing vector = [ 0.00036784022813662887, 0.011101230047643185, @@ -1096,9 +1088,9 @@ def get_by_metadata(db_name, filters, user_name, filter=None): # Pad vector to 1024 dimensions (fill with zeros for testing) vector = vector + [0.0] * (1024 - len(vector)) - # Example filter for testing + # Example filter for testing - common filter used by multiple tests filter_example = { - "or": [ + "and": [ {"id": "4534c646-50fe-4f8b-9488-91992ec8af91"}, {"A": "湖北武当山"}, ] @@ -1109,27 +1101,11 @@ def get_by_metadata(db_name, filters, user_name, filter=None): {"field": "status", "op": "=", "value": "activated"}, ] - # Test search_by_embedding - search_by_embedding( - db_name="pref-tmp-db-test", - vectorStr=vector, - user_name="memosfeebbc2bd1744d7bb5b5ec57f38e828d", - filter=filter_example, - ) + graph = getNeo4j() - # Test get_all_memory_items - get_all_memory_items( - db_name="pref-tmp-db-test", - scope="WorkingMemory", - include_embedding=False, - user_name="memosfeebbc2bd1744d7bb5b5ec57f38e828d", - filter=filter_example, - ) - - # #Test get_by_metadata - get_by_metadata( - db_name="pref-tmp-db-test", - filters=filters_example, - user_name="memosfeebbc2bd1744d7bb5b5ec57f38e828d", - filter=filter_example - ) + # Or run all tests + user_name = "memosfeebbc2bd1744d7bb5b5ec57f38e828d" + scope = "WorkingMemory" + test_search_by_embedding(graph, vector, user_name, filter_example) + test_get_all_memory_items(graph, scope, user_name, filter_example) + test_get_by_metadata(graph, filters_example, user_name, filter_example) \ No newline at end of file From 407238477fcfdeb154cdde30f03de7f6757feac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Sun, 23 Nov 2025 00:21:08 +0800 Subject: [PATCH 14/36] update polardb_search.py --- examples/basic_modules/polardb_search.py | 178 ++++++++++++++++------- src/memos/graph_dbs/polardb.py | 30 ++-- 2 files changed, 137 insertions(+), 71 deletions(-) diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py index cd9279900..5638967fb 100644 --- a/examples/basic_modules/polardb_search.py +++ b/examples/basic_modules/polardb_search.py @@ -3,7 +3,6 @@ import sys from typing import Optional - src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "src")) sys.path.insert(0, src_path) @@ -12,33 +11,17 @@ from memos.configs.graph_db import GraphDBConfigFactory from memos.graph_dbs.factory import GraphStoreFactory -def getGraph(db_name): - config = GraphDBConfigFactory( - backend="nebular", - config={ - "uri": json.loads(os.getenv("NEBULAR_HOSTS", "localhost")), - "user": os.getenv("NEBULAR_USER", "root"), - "password": os.getenv("NEBULAR_PASSWORD", "xxxxxx"), - "space": db_name, - "use_multi_db": False, - "auto_create": True, - "embedding_dimension": 1024, - }, - ) - graph = GraphStoreFactory.from_config(config) - return graph - -def getPolarDb(db_name): +def getPolarDb(): config = GraphDBConfigFactory( backend="polardb", config={ - "host": os.getenv("POLAR_DB_HOST", "xxxxxxxxx"), + "host": os.getenv("POLAR_DB_HOST", "xxxxx"), "port": int(os.getenv("POLAR_DB_PORT", "5432")), - "user": os.getenv("POLAR_DB_USER", "xxxxxxxxx"), - "password": os.getenv("POLAR_DB_PASSWORD", "xxxxxxxxx"), - "db_name": db_name, - "user_name": os.getenv("POLARDB_USER_NAME", "adimin"), + "user": os.getenv("POLAR_DB_USER", "xxxxx"), + "password": os.getenv("POLAR_DB_PASSWORD", "xxx"), + "db_name": os.getenv("POLAR_DB_DB_NAME", "xxx"), + "user_name": os.getenv("POLARDB_USER_NAME", "xxx"), "use_multi_db": os.getenv("POLARDB_USE_MULTI_DB", "True").lower() == "true", # 设置为True,不添加user_name过滤条件 "auto_create": True, "embedding_dimension": 1024, @@ -48,25 +31,59 @@ def getPolarDb(db_name): return graph -def searchVector(db_name: str, vectorStr: list[float], user_name: Optional[str] = None, filter: Optional[dict] = None): - graph = getPolarDb(db_name) - print("6666666666666") - - # 1,查询search_by_embedding +def test_search_by_embedding(graph, vector: list[float], user_name: Optional[str] = None, + filter: Optional[dict] = None): + """Test search_by_embedding function.""" + # Query search_by_embedding nodes = graph.search_by_embedding( - vector=vectorStr, top_k=100, user_name=user_name, filter=filter + vector=vector, top_k=100, user_name=user_name, filter=filter ) - print("search_by_embedding nodes:", len(nodes)) + print(f"test_search_by_embedding: nodes count: {len(nodes)}") for node_i in nodes: - print("Search result:", graph.get_node(node_i["id"][1:-1])) + print(f"Search result id: {node_i['id']}, score: {node_i.get('score', 'N/A')}") - # 2,查询单个get_node - detail = graph.get_node(id='"65c3a65b-78f7-4009-bc92-7ee1981deb3a"', user_name='"adimin"') - print("单个node:", detail) - # - # # 3,查询多个get_nodes - ids = ["bb079c5b-1937-4125-a9e5-55d4abe6c95d", "d66120af-992b-44c6-b261-a6ebe6bc57a5"] - # ids = ['"bfde036f-6276-4485-9dc6-3c64eab3e132"'] + +def test_get_node(graph, node_id: str, user_name: Optional[str] = None): + """Test get_node function - query single node.""" + detail = graph.get_node(id=node_id, user_name=user_name) + print(f"test_get_node: {detail}") + + +def test_get_nodes(graph, ids: list[str], user_name: Optional[str] = None): + """Test get_nodes function - query multiple nodes.""" + detail_list = graph.get_nodes(ids=ids, user_name=user_name) + print(f"test_get_nodes: count: {len(detail_list)}") + print(f"test_get_nodes: {detail_list}") + + +def test_update_node(graph, node_id: str, fields: dict, user_name: Optional[str] = None): + """Test update_node function.""" + result = graph.update_node(id=node_id, fields=fields, user_name=user_name) + print(f"test_update_node: {result}") + + +def test_get_memory_count(graph, scope: str, user_name: Optional[str] = None): + """Test get_memory_count function.""" + count = graph.get_memory_count(scope, user_name) + print(f"test_get_memory_count: {count}") + + +def test_node_not_exist(graph, scope: str, user_name: Optional[str] = None): + """Test node_not_exist function - check if node exists.""" + isNodeExist = graph.node_not_exist(scope, user_name) + print(f"test_node_not_exist: {isNodeExist}") + + +def test_remove_oldest_memory(graph, scope: str, skip_count: int, user_name: Optional[str] = None): + """Test remove_oldest_memory function - remove oldest memory after skipping.""" + result = graph.remove_oldest_memory(scope, skip_count, user_name) + print(f"test_remove_oldest_memory: {result}") + + +def test_delete_node(graph, node_id: str, user_name: Optional[str] = None): + """Test delete_node function.""" + isNodeDeleted = graph.delete_node(id=node_id, user_name=user_name) + print(f"test_delete_node: {isNodeDeleted}") # detail_list = graph.get_nodes(ids=ids,user_name='memos7a9f9fbbb61c412f94f77fbaa8103c35') # print("1111多个node:", len(detail_list)) # # @@ -98,19 +115,19 @@ def searchVector(db_name: str, vectorStr: list[float], user_name: Optional[str] # 9,添加边 add_edge def add_edge( - db_name: str, source_id: str, target_id: str, edge_type: str = "Memory", user_name: Optional[str] = None + db_name: str, source_id: str, target_id: str, edge_type: str = "Memory", user_name: Optional[str] = None ): graph = getPolarDb(db_name) graph.add_edge(source_id, target_id, edge_type, user_name) def edge_exists( - db_name: str, - source_id: str, - target_id: str, - type: str = "Memory", - direction: str = "OUTGOING", - user_name: Optional[str] = None, + db_name: str, + source_id: str, + target_id: str, + type: str = "Memory", + direction: str = "OUTGOING", + user_name: Optional[str] = None, ): graph = getPolarDb(db_name) isEdge_exists = graph.edge_exists( @@ -194,12 +211,14 @@ def get_structure_optimization_candidates(db_name, scope, include_embedding, use print("get_structure_optimization_candidates:", candidates) -def get_all_memory_items(db_name, scope, include_embedding, user_name, filter=None): - graph = getPolarDb(db_name) +def test_get_all_memory_items(graph, scope: str, include_embedding: bool, user_name: str, + filter: Optional[dict] = None): + """Test get_all_memory_items function.""" memory_items = graph.get_all_memory_items( scope=scope, include_embedding=include_embedding, user_name=user_name, filter=filter ) - print("11111get_all_memory_items:", memory_items) + print(f"test_get_all_memory_items: count: {len(memory_items)}") + print(f"test_get_all_memory_items: {memory_items}") def get_neighbors_by_tag(db_name, user_name): @@ -216,15 +235,15 @@ def get_edges(db_name: str, id: str, type: str, direction: str, user_name: Optio print("get_edges:", edges) -def get_by_metadata(db_name, filters, user_name, filter=None): - graph = getPolarDb(db_name) +def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Optional[dict] = None): + """Test get_by_metadata function.""" ids = graph.get_by_metadata(filters=filters, user_name=user_name, filter=filter) - print("get_by_metadata:", ids) + print(f"test_get_by_metadata: count: {len(ids)}") + print(f"test_get_by_metadata: {ids}") if __name__ == "__main__": - # handler_node_edge(db_name="shared-tree-textual-memory-product-0731",type="node") - # handler_node_edge(db_name="shared-tree-textual-memory-product-0731",type="edge") + # Example vector for testing vector = [ -0.059882111847400665, 0.020448941737413406, @@ -1251,7 +1270,8 @@ def get_by_metadata(db_name, filters, user_name, filter=None): -0.009011801332235336, 0.030511723831295967, ] - filter = { + # Example filter for testing - common filter used by multiple tests + filter_example = { "and": [ {"id": "65c3a65b-78f7-4009-bc92-7ee1981deb3a"}, { @@ -1264,8 +1284,54 @@ def get_by_metadata(db_name, filters, user_name, filter=None): }, ] } - searchVector(db_name="memos_test", vectorStr=vector, user_name="adimin",filter=filter) - # searchVector(db_name="memos_test", vectorStr=vector, user_name="adimin",filter=filter) + + # Example filters for get_by_metadata + filters_example = [ + {"field": "tags", "op": "contains", "value": "Python"}, + ] + + # Create connection once - shared by all tests + graph = getPolarDb() + user_name = "adimin" + scope = "WorkingMemory" + + # Run all tests - uncomment the test you want to run + test_search_by_embedding(graph, vector, user_name, filter_example) + test_get_all_memory_items(graph, "LongTermMemory", False, user_name, filter_example) + test_get_by_metadata(graph, filters_example, user_name, filter_example) + + # Or run all tests + + # Test search_by_embedding + # test_search_by_embedding(graph, vector, user_name, filter_example) + + # Test get_node - query single node + # test_get_node(graph, '"65c3a65b-78f7-4009-bc92-7ee1981deb3a"', '"adimin"') + + # Test get_nodes - query multiple nodes + # test_get_nodes(graph, ["65c3a65b-78f7-4009-bc92-7ee1981deb3a", "65c3a65b-78f7-4009-bc92-7ee1981deb3b"], user_name) + + # Test update_node + # test_update_node(graph, "000009999ef-926f-42e2-b7b5-0224daf0abcd", {"name": "new_name"}, user_name) + # test_update_node(graph, "bb079c5b-1937-4125-a9e5-55d4abe6c95d", {"status": "inactived", "tags": ["yoga", "travel11111111", "local studios5667888"]}, user_name) + + # Test get_memory_count + # test_get_memory_count(graph, 'UserMemory', user_name) + + # Test node_not_exist + # test_node_not_exist(graph, 'UserMemory', user_name) + + # Test remove_oldest_memory + # test_remove_oldest_memory(graph, 'UserMemory', 2, user_name) + + # Test delete_node + # test_delete_node(graph, "bb079c5b-1937-4125-a9e5-55d4abe6c95d", user_name) + + # Test get_all_memory_items + # test_get_all_memory_items(graph, scope, False, user_name, filter_example) + + # Test get_by_metadata + # test_get_by_metadata(graph, filters_example, user_name, filter_example) # searchVector(db_name="test_1020_02", vectorStr=vector) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 68e8421b8..3b7aa40b6 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1468,8 +1468,8 @@ def search_by_embedding( """ # Build WHERE clause dynamically like nebular.py where_clauses = [] - scope = "LongTermMemory" - user_name = "adimin" + # scope = "LongTermMemory" + # user_name = "adimin" if scope: where_clauses.append( f"ag_catalog.agtype_access_operator(properties, '\"memory_type\"'::agtype) = '\"{scope}\"'::agtype" @@ -1510,17 +1510,17 @@ def search_by_embedding( f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = {value}::agtype" ) # Add filter conditions (supports "or" and "and" logic) - filter = { - "and": [ - {"id": "2025-11-19-02"}, - { - "info.B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" - }, - {"memory": "湖北武汉"}, - {"info.A": "建议从基础语法开始,多做练习项目"}, - {"user_id": "65c3a65b-78f7-4009-bc92-7ee1981deb3a"}, - ] - } + # filter = { + # "and": [ + # {"id": "2025-11-19-02"}, + # { + # "info.B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" + # }, + # {"memory": "湖北武汉"}, + # {"info.A": "建议从基础语法开始,多做练习项目"}, + # {"user_id": "65c3a65b-78f7-4009-bc92-7ee1981deb3a"}, + # ] + # } if filter: # Helper function to escape string value for SQL def escape_sql_string(value: str) -> str: @@ -1550,12 +1550,12 @@ def build_filter_condition(condition_dict: dict) -> str: # Access nested field: properties->'info'->'B' # First get info object, then get the field inside it condition_parts.append( - f"ag_catalog.agtype_access_operator(ag_catalog.agtype_access_operator(properties, '\"info\"'::agtype), '\"{info_field}\"'::agtype) = '\"{escaped_value}\"'::agtype" + # f"ag_catalog.agtype_access_operator(ag_catalog.agtype_access_operator(properties, '\"info\"'::agtype), '\"{info_field}\"'::agtype) = '\"{escaped_value}\"'::agtype" f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '\"{escaped_value}\"'::agtype" ) else: condition_parts.append( - f"ag_catalog.agtype_access_operator(ag_catalog.agtype_access_operator(properties, '\"info\"'::agtype), '\"{info_field}\"'::agtype) = {value}::agtype" + # f"ag_catalog.agtype_access_operator(ag_catalog.agtype_access_operator(properties, '\"info\"'::agtype), '\"{info_field}\"'::agtype) = {value}::agtype" f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '\"{value}\"'::agtype" ) else: From 0d18204041a09fc1824dbe96b838f60a2a14a1f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Mon, 24 Nov 2025 13:46:35 +0800 Subject: [PATCH 15/36] remove search example --- src/memos/graph_dbs/polardb.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 3b7aa40b6..640359762 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1468,8 +1468,6 @@ def search_by_embedding( """ # Build WHERE clause dynamically like nebular.py where_clauses = [] - # scope = "LongTermMemory" - # user_name = "adimin" if scope: where_clauses.append( f"ag_catalog.agtype_access_operator(properties, '\"memory_type\"'::agtype) = '\"{scope}\"'::agtype" @@ -1509,18 +1507,6 @@ def search_by_embedding( where_clauses.append( f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = {value}::agtype" ) - # Add filter conditions (supports "or" and "and" logic) - # filter = { - # "and": [ - # {"id": "2025-11-19-02"}, - # { - # "info.B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" - # }, - # {"memory": "湖北武汉"}, - # {"info.A": "建议从基础语法开始,多做练习项目"}, - # {"user_id": "65c3a65b-78f7-4009-bc92-7ee1981deb3a"}, - # ] - # } if filter: # Helper function to escape string value for SQL def escape_sql_string(value: str) -> str: From 60a45b46bace6b5abf8993cedbdd47c46108114e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Mon, 24 Nov 2025 16:43:51 +0800 Subject: [PATCH 16/36] add knowledgebase_ids --- examples/basic_modules/polardb_search.py | 7 ++-- src/memos/graph_dbs/polardb.py | 45 +++++++++++++++++++++--- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py index 5638967fb..687b07b3b 100644 --- a/examples/basic_modules/polardb_search.py +++ b/examples/basic_modules/polardb_search.py @@ -32,11 +32,11 @@ def getPolarDb(): def test_search_by_embedding(graph, vector: list[float], user_name: Optional[str] = None, - filter: Optional[dict] = None): + filter: Optional[dict] = None,knowledgebase_ids: Optional[list[str]] = None): """Test search_by_embedding function.""" # Query search_by_embedding nodes = graph.search_by_embedding( - vector=vector, top_k=100, user_name=user_name, filter=filter + vector=vector, top_k=100, user_name=user_name, filter=filter,knowledgebase_ids=knowledgebase_ids ) print(f"test_search_by_embedding: nodes count: {len(nodes)}") for node_i in nodes: @@ -1294,9 +1294,10 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt graph = getPolarDb() user_name = "adimin" scope = "WorkingMemory" + knowledgebase_ids = ["adimin1","adimin2","adimi3","adimin4","adimin5","adimin6"] # Run all tests - uncomment the test you want to run - test_search_by_embedding(graph, vector, user_name, filter_example) + test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) test_get_all_memory_items(graph, "LongTermMemory", False, user_name, filter_example) test_get_by_metadata(graph, filters_example, user_name, filter_example) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 640359762..15300a4df 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1,5 +1,6 @@ import json import random +import textwrap from datetime import datetime from typing import Any, Literal @@ -1461,6 +1462,7 @@ def search_by_embedding( search_filter: dict | None = None, user_name: str | None = None, filter: dict | None = None, + knowledgebase_ids: list[str] | None = None, **kwargs, ) -> list[dict]: """ @@ -1491,10 +1493,30 @@ def search_by_embedding( # else: # where_clauses.append(f"ag_catalog.agtype_access_operator(properties, '\"user_name\"'::agtype) = '\"{user_name}\"'::agtype") """ + # Build user_name filter with knowledgebase_ids support (OR relationship) + user_name_conditions = [] + + # Add original user_name condition if provided user_name = user_name if user_name else self.config.user_name - where_clauses.append( - f"ag_catalog.agtype_access_operator(properties, '\"user_name\"'::agtype) = '\"{user_name}\"'::agtype" - ) + if user_name: + user_name_conditions.append( + f"ag_catalog.agtype_access_operator(properties, '\"user_name\"'::agtype) = '\"{user_name}\"'::agtype" + ) + + # Add knowledgebase_ids conditions (checking user_name field in the data) + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for kb_id in knowledgebase_ids: + if isinstance(kb_id, str): + user_name_conditions.append( + f"ag_catalog.agtype_access_operator(properties, '\"user_name\"'::agtype) = '\"{kb_id}\"'::agtype" + ) + + # Add OR condition if we have any user_name conditions + if user_name_conditions: + if len(user_name_conditions) == 1: + where_clauses.append(user_name_conditions[0]) + else: + where_clauses.append(f"({' OR '.join(user_name_conditions)})") # Add search_filter conditions like nebular.py if search_filter: @@ -1599,8 +1621,23 @@ def build_filter_condition(condition_dict: dict) -> str: params = [vector] logger.debug(f"search_by_embedding query: {query}") logger.debug(f"search_by_embedding params: {params}") + + # Format SQL query for better console display (prevent truncation) print("=== SQL Query ===") - print(query) + #print(query) + + + # Split query by lines and wrap long lines to prevent terminal truncation + query_lines = query.strip().split('\n') + for line in query_lines: + # Wrap lines longer than 200 characters to prevent terminal truncation + if len(line) > 200: + wrapped_lines = textwrap.wrap(line, width=200, break_long_words=False, break_on_hyphens=False) + for wrapped_line in wrapped_lines: + print(wrapped_line) + else: + print(line) + print("=== Params ===") print(params) print("================") From e2fec7c08aeb2f9cede48982ab7bbe445da07dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Mon, 24 Nov 2025 17:13:48 +0800 Subject: [PATCH 17/36] add created_at filter --- examples/basic_modules/polardb_search.py | 10 ++--- src/memos/graph_dbs/polardb.py | 47 ++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py index 687b07b3b..30630a98e 100644 --- a/examples/basic_modules/polardb_search.py +++ b/examples/basic_modules/polardb_search.py @@ -1272,16 +1272,16 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt ] # Example filter for testing - common filter used by multiple tests filter_example = { - "and": [ + "or": [ {"id": "65c3a65b-78f7-4009-bc92-7ee1981deb3a"}, { - # "info.A": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。", "info.A": "建议从基础语法开始,多做练习项目" }, - # {"info.B": "建议从基础语法开始,多做练习项目"}, { "info.B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" }, + {"created_at":{"gt":"2025-09-19"}}, + {"created_at": {"lt": "2025-11-12"}} ] } @@ -1298,8 +1298,8 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt # Run all tests - uncomment the test you want to run test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) - test_get_all_memory_items(graph, "LongTermMemory", False, user_name, filter_example) - test_get_by_metadata(graph, filters_example, user_name, filter_example) + # test_get_all_memory_items(graph, "LongTermMemory", False, user_name, filter_example) + # test_get_by_metadata(graph, filters_example, user_name, filter_example) # Or run all tests diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 15300a4df..3bdbb7685 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1540,15 +1540,55 @@ def build_filter_condition(condition_dict: dict) -> str: """Build a WHERE condition for a single filter item. Args: - condition_dict: A dict like {"id": "xxx"} or {"info.B": "xxx"} + condition_dict: A dict like {"id": "xxx"} or {"info.B": "xxx"} or {"created_at": {"gt": "2025-11-01"}} Returns: SQL condition string """ condition_parts = [] for key, value in condition_dict.items(): - # Check if key starts with "info." prefix - if key.startswith("info."): + # Check if value is a dict with comparison operators (gt, lt, gte, lte) + if isinstance(value, dict): + # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte"): + # Map operator to SQL operator + sql_op_map = { + "gt": ">", + "lt": "<", + "gte": ">=", + "lte": "<=" + } + sql_op = sql_op_map[op] + + # Check if key starts with "info." prefix (for nested fields like info.A, info.B) + # For direct properties like "created_at", this condition will be False + if key.startswith("info."): + # Nested field access: properties->'info'->'field_name' + info_field = key[5:] # Remove "info." prefix + if isinstance(op_value, str): + escaped_value = escape_sql_string(op_value) + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) {sql_op} '\"{escaped_value}\"'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) {sql_op} {op_value}::agtype" + ) + else: + # Direct property access (e.g., "created_at" is directly in properties, not in properties.info) + # This handles fields like created_at, updated_at, etc. that are at the top level of properties + if isinstance(op_value, str): + escaped_value = escape_sql_string(op_value) + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) {sql_op} '\"{escaped_value}\"'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) {sql_op} {op_value}::agtype" + ) + # Check if key starts with "info." prefix (for simple equality) + elif key.startswith("info."): # Extract the field name after "info." info_field = key[5:] # Remove "info." prefix (5 characters) # Match in info field: properties->'info'->'B' @@ -1567,6 +1607,7 @@ def build_filter_condition(condition_dict: dict) -> str: f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '\"{value}\"'::agtype" ) else: + # Direct property access (simple equality) if isinstance(value, str): escaped_value = escape_sql_string(value) condition_parts.append( From 87e1130933d32c0428c62c490c68271346959f55 Mon Sep 17 00:00:00 2001 From: ccl <13282138256@163.com> Date: Mon, 24 Nov 2025 17:38:00 +0800 Subject: [PATCH 18/36] filter --- src/memos/graph_dbs/polardb.py | 53 ++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 3bdbb7685..446e71c2b 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1529,6 +1529,7 @@ def search_by_embedding( where_clauses.append( f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = {value}::agtype" ) + filter = self.parse_filter(filter) if filter: # Helper function to escape string value for SQL def escape_sql_string(value: str) -> str: @@ -3336,3 +3337,55 @@ def format_param_value(self, value: str | None) -> str: else: # Add double quotes return f'"{value}"' + + def parse_filter(self, filter_dict: dict | None = None,): + if filter_dict is None: + return None + full_fields = { + "id", "key", "tags", "type", "usage", "memory", "status", "sources", + "user_id", "graph_id", "user_name", "background", "confidence", + "created_at", "session_id", "updated_at", "memory_type", "node_type", "info" + } + + def process_condition(condition): + + if not isinstance(condition, dict): + return condition + + new_condition = {} + + for key, value in condition.items(): + + if key.lower() in ['or', 'and']: + if isinstance(value, list): + + processed_items = [] + for item in value: + if isinstance(item, dict): + processed_item = {} + for item_key, item_value in item.items(): + + if item_key not in full_fields and not item_key.startswith('info.'): + new_item_key = f"info.{item_key}" + else: + new_item_key = item_key + processed_item[new_item_key] = item_value + processed_items.append(processed_item) + else: + processed_items.append(item) + new_condition[key] = processed_items + else: + new_condition[key] = value + else: + if key not in full_fields and not key.startswith('info.'): + new_key = f"info.{key}" + else: + new_key = key + + new_condition[new_key] = value + + return new_condition + + return process_condition(filter_dict) + + From e078727739b9528eee9f807c79254610d7ee94e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Mon, 24 Nov 2025 17:43:35 +0800 Subject: [PATCH 19/36] get_all_memory_items --- examples/basic_modules/polardb_search.py | 10 +-- src/memos/graph_dbs/polardb.py | 93 +++++++++++++++++++++++- 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py index 30630a98e..4da89f52e 100644 --- a/examples/basic_modules/polardb_search.py +++ b/examples/basic_modules/polardb_search.py @@ -212,10 +212,10 @@ def get_structure_optimization_candidates(db_name, scope, include_embedding, use def test_get_all_memory_items(graph, scope: str, include_embedding: bool, user_name: str, - filter: Optional[dict] = None): + filter: Optional[dict] = None,knowledgebase_ids: Optional[list] = None): """Test get_all_memory_items function.""" memory_items = graph.get_all_memory_items( - scope=scope, include_embedding=include_embedding, user_name=user_name, filter=filter + scope=scope, include_embedding=include_embedding, user_name=user_name, filter=filter,knowledgebase_ids=knowledgebase_ids ) print(f"test_get_all_memory_items: count: {len(memory_items)}") print(f"test_get_all_memory_items: {memory_items}") @@ -1272,7 +1272,7 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt ] # Example filter for testing - common filter used by multiple tests filter_example = { - "or": [ + "and": [ {"id": "65c3a65b-78f7-4009-bc92-7ee1981deb3a"}, { "info.A": "建议从基础语法开始,多做练习项目" @@ -1297,8 +1297,8 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt knowledgebase_ids = ["adimin1","adimin2","adimi3","adimin4","adimin5","adimin6"] # Run all tests - uncomment the test you want to run - test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) - # test_get_all_memory_items(graph, "LongTermMemory", False, user_name, filter_example) + # test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) + test_get_all_memory_items(graph, "LongTermMemory", False, user_name, filter_example,knowledgebase_ids) # test_get_by_metadata(graph, filters_example, user_name, filter_example) # Or run all tests diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 3bdbb7685..c5852d765 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -2253,6 +2253,7 @@ def get_all_memory_items( include_embedding: bool = False, user_name: str | None = None, filter: dict | None = None, + knowledgebase_ids: list | None = None, ) -> list[dict]: """ Retrieve all memory items of a specific memory_type. @@ -2269,6 +2270,27 @@ def get_all_memory_items( if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"}: raise ValueError(f"Unsupported memory type scope: {scope}") + # Build user_name filter with knowledgebase_ids support (OR relationship) + user_name_conditions = [] + if user_name: + user_name_conditions.append(f"n.user_name = '{user_name}'") + + # Add knowledgebase_ids conditions (checking user_name field in the data) + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for kb_id in knowledgebase_ids: + if isinstance(kb_id, str): + escaped_kb_id = kb_id.replace("'", "\\'") + user_name_conditions.append(f"n.user_name = '{escaped_kb_id}'") + + # Build user_name WHERE clause + if user_name_conditions: + if len(user_name_conditions) == 1: + user_name_where = user_name_conditions[0] + else: + user_name_where = f"({' OR '.join(user_name_conditions)})" + else: + user_name_where = "" + filter_where_clause = "" if filter: @@ -2277,9 +2299,50 @@ def escape_cypher_string(value: str) -> str: return value.replace("'", "\\'") def build_cypher_filter_condition(condition_dict: dict) -> str: + """Build a Cypher WHERE condition for a single filter item. + + Args: + condition_dict: A dict like {"id": "xxx"} or {"info.B": "xxx"} or {"created_at": {"gt": "2025-11-01"}} + + Returns: + Cypher condition string + """ condition_parts = [] for key, value in condition_dict.items(): - if key.startswith("info."): + # Check if value is a dict with comparison operators (gt, lt, gte, lte) + if isinstance(value, dict): + # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte"): + # Map operator to Cypher operator + cypher_op_map = { + "gt": ">", + "lt": "<", + "gte": ">=", + "lte": "<=" + } + cypher_op = cypher_op_map[op] + + # Check if key starts with "info." prefix (for nested fields like info.A, info.B) + # For direct properties like "created_at", this condition will be False + if key.startswith("info."): + # Nested field access: n.info.field_name + info_field = key[5:] # Remove "info." prefix + if isinstance(op_value, str): + escaped_value = escape_cypher_string(op_value) + condition_parts.append(f"n.info.{info_field} {cypher_op} '{escaped_value}'") + else: + condition_parts.append(f"n.info.{info_field} {cypher_op} {op_value}") + else: + # Direct property access (e.g., "created_at" is directly in n, not in n.info) + # This handles fields like created_at, updated_at, etc. that are at the top level + if isinstance(op_value, str): + escaped_value = escape_cypher_string(op_value) + condition_parts.append(f"n.{key} {cypher_op} '{escaped_value}'") + else: + condition_parts.append(f"n.{key} {cypher_op} {op_value}") + # Check if key starts with "info." prefix (for simple equality) + elif key.startswith("info."): info_field = key[5:] if isinstance(value, str): escaped_value = escape_cypher_string(value) @@ -2287,6 +2350,7 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: else: condition_parts.append(f"n.info.{info_field} = {value}") else: + # Direct property access (simple equality) if isinstance(value, str): escaped_value = escape_cypher_string(value) condition_parts.append(f"n.{key} = '{escaped_value}'") @@ -2317,11 +2381,22 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: # Use cypher query to retrieve memory items if include_embedding: + # Build WHERE clause with user_name/knowledgebase_ids and filter + where_parts = [f"n.memory_type = '{scope}'"] + if user_name_where: + # user_name_where already contains parentheses if it's an OR condition + where_parts.append(user_name_where) + if filter_where_clause: + # filter_where_clause already contains " AND " prefix, so we just append it + where_clause = " AND ".join(where_parts) + filter_where_clause + else: + where_clause = " AND ".join(where_parts) + cypher_query = f""" WITH t as ( SELECT * FROM cypher('{self.db_name}_graph', $$ MATCH (n:Memory) - WHERE n.memory_type = '{scope}' AND n.user_name = '{user_name}'{filter_where_clause} + WHERE {where_clause} RETURN id(n) as id1,n LIMIT 100 $$) AS (id1 agtype,n agtype) @@ -2364,10 +2439,21 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: return nodes else: + # Build WHERE clause with user_name/knowledgebase_ids and filter + where_parts = [f"n.memory_type = '{scope}'"] + if user_name_where: + # user_name_where already contains parentheses if it's an OR condition + where_parts.append(user_name_where) + if filter_where_clause: + # filter_where_clause already contains " AND " prefix, so we just append it + where_clause = " AND ".join(where_parts) + filter_where_clause + else: + where_clause = " AND ".join(where_parts) + cypher_query = f""" SELECT * FROM cypher('{self.db_name}_graph', $$ MATCH (n:Memory) - WHERE n.memory_type = '{scope}' AND n.user_name = '{user_name}'{filter_where_clause} + WHERE {where_clause} RETURN properties(n) as props LIMIT 100 $$) AS (nprops agtype) @@ -2375,6 +2461,7 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: nodes = [] conn = self._get_connection() + print("1111111cypher_query:", cypher_query) try: with conn.cursor() as cursor: cursor.execute(cypher_query) From c95690ab9ad3ebbba3c2fe100923a65be4728224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Mon, 24 Nov 2025 17:47:44 +0800 Subject: [PATCH 20/36] get_all_memory_items --- examples/basic_modules/polardb_search.py | 6 ++++-- src/memos/graph_dbs/polardb.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py index 4da89f52e..2f4948516 100644 --- a/examples/basic_modules/polardb_search.py +++ b/examples/basic_modules/polardb_search.py @@ -1275,10 +1275,12 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt "and": [ {"id": "65c3a65b-78f7-4009-bc92-7ee1981deb3a"}, { - "info.A": "建议从基础语法开始,多做练习项目" + # "info.A": "建议从基础语法开始,多做练习项目", + "A": "建议从基础语法开始,多做练习项目" }, { - "info.B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" + # "info.B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" + "B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" }, {"created_at":{"gt":"2025-09-19"}}, {"created_at": {"lt": "2025-11-12"}} diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index ec030d6ed..ecfddece9 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -2293,6 +2293,7 @@ def get_all_memory_items( user_name_where = "" filter_where_clause = "" + filter = self.parse_filter(filter) if filter: def escape_cypher_string(value: str) -> str: From 875eebf0a656dc9944b9abea00c76c6e400bd9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Mon, 24 Nov 2025 18:41:37 +0800 Subject: [PATCH 21/36] update get_by_metadata --- examples/basic_modules/polardb_search.py | 12 ++--- src/memos/graph_dbs/polardb.py | 69 ++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py index 2f4948516..f9034a008 100644 --- a/examples/basic_modules/polardb_search.py +++ b/examples/basic_modules/polardb_search.py @@ -235,9 +235,9 @@ def get_edges(db_name: str, id: str, type: str, direction: str, user_name: Optio print("get_edges:", edges) -def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Optional[dict] = None): +def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Optional[dict] = None,knowledgebase_ids: Optional[list] = None): """Test get_by_metadata function.""" - ids = graph.get_by_metadata(filters=filters, user_name=user_name, filter=filter) + ids = graph.get_by_metadata(filters=filters, user_name=user_name, filter=filter,knowledgebase_ids=knowledgebase_ids) print(f"test_get_by_metadata: count: {len(ids)}") print(f"test_get_by_metadata: {ids}") @@ -1272,7 +1272,7 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt ] # Example filter for testing - common filter used by multiple tests filter_example = { - "and": [ + "or": [ {"id": "65c3a65b-78f7-4009-bc92-7ee1981deb3a"}, { # "info.A": "建议从基础语法开始,多做练习项目", @@ -1299,9 +1299,9 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt knowledgebase_ids = ["adimin1","adimin2","adimi3","adimin4","adimin5","adimin6"] # Run all tests - uncomment the test you want to run - # test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) - test_get_all_memory_items(graph, "LongTermMemory", False, user_name, filter_example,knowledgebase_ids) - # test_get_by_metadata(graph, filters_example, user_name, filter_example) + test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) + # test_get_all_memory_items(graph, "LongTermMemory", False, user_name, filter_example,knowledgebase_ids) + # test_get_by_metadata(graph, filters_example, user_name, filter_example,knowledgebase_ids) # Or run all tests diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index ecfddece9..7bc7afcaf 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1714,6 +1714,7 @@ def get_by_metadata( filters: list[dict[str, Any]], user_name: str | None = None, filter: dict | None = None, + knowledgebase_ids: list | None = None, ) -> list[str]: """ Retrieve node IDs that match given metadata filters. @@ -1782,20 +1783,78 @@ def get_by_metadata( else: raise ValueError(f"Unsupported operator: {op}") - # Add user_name filter - escaped_user_name = user_name.replace("'", "''") - where_conditions.append(f"n.user_name = '{escaped_user_name}'") + # Build user_name filter with knowledgebase_ids support (OR relationship) + user_name_conditions = [] + if user_name: + escaped_user_name = user_name.replace("'", "''") + user_name_conditions.append(f"n.user_name = '{escaped_user_name}'") + + # Add knowledgebase_ids conditions (checking user_name field in the data) + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for kb_id in knowledgebase_ids: + if isinstance(kb_id, str): + escaped_kb_id = kb_id.replace("'", "''") + user_name_conditions.append(f"n.user_name = '{escaped_kb_id}'") + + # Add user_name WHERE clause + if user_name_conditions: + if len(user_name_conditions) == 1: + where_conditions.append(user_name_conditions[0]) + else: + where_conditions.append(f"({' OR '.join(user_name_conditions)})") filter_where_clause = "" + filter = self.parse_filter(filter) if filter: def escape_cypher_string(value: str) -> str: return value.replace("'", "\\'") def build_cypher_filter_condition(condition_dict: dict) -> str: + """Build a Cypher WHERE condition for a single filter item. + + Args: + condition_dict: A dict like {"id": "xxx"} or {"info.B": "xxx"} or {"created_at": {"gt": "2025-11-01"}} + + Returns: + Cypher condition string + """ condition_parts = [] for key, value in condition_dict.items(): - if key.startswith("info."): + # Check if value is a dict with comparison operators (gt, lt, gte, lte) + if isinstance(value, dict): + # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte"): + # Map operator to Cypher operator + cypher_op_map = { + "gt": ">", + "lt": "<", + "gte": ">=", + "lte": "<=" + } + cypher_op = cypher_op_map[op] + + # Check if key starts with "info." prefix (for nested fields like info.A, info.B) + # For direct properties like "created_at", this condition will be False + if key.startswith("info."): + # Nested field access: n.info.field_name + info_field = key[5:] # Remove "info." prefix + if isinstance(op_value, str): + escaped_value = escape_cypher_string(op_value) + condition_parts.append(f"n.info.{info_field} {cypher_op} '{escaped_value}'") + else: + condition_parts.append(f"n.info.{info_field} {cypher_op} {op_value}") + else: + # Direct property access (e.g., "created_at" is directly in n, not in n.info) + # This handles fields like created_at, updated_at, etc. that are at the top level + if isinstance(op_value, str): + escaped_value = escape_cypher_string(op_value) + condition_parts.append(f"n.{key} {cypher_op} '{escaped_value}'") + else: + condition_parts.append(f"n.{key} {cypher_op} {op_value}") + # Check if key starts with "info." prefix (for simple equality) + elif key.startswith("info."): info_field = key[5:] if isinstance(value, str): escaped_value = escape_cypher_string(value) @@ -1803,6 +1862,7 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: else: condition_parts.append(f"n.info.{info_field} = {value}") else: + # Direct property access (simple equality) if isinstance(value, str): escaped_value = escape_cypher_string(value) condition_parts.append(f"n.{key} = '{escaped_value}'") @@ -1844,6 +1904,7 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: ids = [] conn = self._get_connection() + print(f"get_by_metadata cypher_query: {cypher_query}") try: with conn.cursor() as cursor: cursor.execute(cypher_query) From 4f758fa9d71c3864275b965b00228f3981a4288d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Mon, 24 Nov 2025 19:59:40 +0800 Subject: [PATCH 22/36] update neo4j.py --- examples/basic_modules/neo4j_search.py | 20 ++- src/memos/graph_dbs/neo4j.py | 221 ++++++++++++++++++++++--- src/memos/graph_dbs/polardb.py | 2 +- 3 files changed, 211 insertions(+), 32 deletions(-) diff --git a/examples/basic_modules/neo4j_search.py b/examples/basic_modules/neo4j_search.py index b0a9554d9..bededb202 100644 --- a/examples/basic_modules/neo4j_search.py +++ b/examples/basic_modules/neo4j_search.py @@ -27,7 +27,7 @@ def getNeo4j(): return graph -def test_search_by_embedding(graph, vector: list[float], user_name: str, filter_example: Optional[dict] = None): +def test_search_by_embedding(graph, vector: list[float], user_name: str, filter_example: Optional[dict] = None,knowledgebase_ids: Optional[list[str]] = None): """Test search_by_embedding function.""" # Query search_by_embedding nodes = graph.search_by_embedding( @@ -35,6 +35,7 @@ def test_search_by_embedding(graph, vector: list[float], user_name: str, filter_ top_k=100, user_name=user_name, filter=filter_example, + knowledgebase_ids=knowledgebase_ids, ) print(f"test_search_by_embedding: nodes count: {len(nodes)}") print(f"test_search_by_embedding: nodes: {nodes}") @@ -42,18 +43,18 @@ def test_search_by_embedding(graph, vector: list[float], user_name: str, filter_ print(f"Search result id: {node_i['id']}, score: {node_i.get('score', 'N/A')}") -def test_get_all_memory_items(graph, scope: str, user_name: str, filter_example: Optional[dict] = None): +def test_get_all_memory_items(graph, scope: str, user_name: str, filter_example: Optional[dict] = None, knowledgebase_ids: Optional[list[str]] = None): """Test get_all_memory_items function.""" memory_items = graph.get_all_memory_items( - scope=scope, include_embedding=False, user_name=user_name, filter=filter_example + scope=scope, include_embedding=False, user_name=user_name, filter=filter_example, knowledgebase_ids=knowledgebase_ids ) print(f"test_get_all_memory_items: count: {len(memory_items)}") print(f"test_get_all_memory_items: {memory_items}") -def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_example: Optional[dict] = None): +def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_example: Optional[dict] = None, knowledgebase_ids: Optional[list[str]] = None): """Test get_by_metadata function.""" - ids = graph.get_by_metadata(filters=filters, user_name=user_name, filter=filter_example) + ids = graph.get_by_metadata(filters=filters, user_name=user_name, filter=filter_example, knowledgebase_ids=knowledgebase_ids) print(f"test_get_by_metadata: count: {len(ids)}") print(f"test_get_by_metadata: {ids}") @@ -1093,8 +1094,11 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_exam "and": [ {"id": "4534c646-50fe-4f8b-9488-91992ec8af91"}, {"A": "湖北武当山"}, + {"created_at": {"gt": "2025-09-19"}}, + {"created_at": {"lt": "2025-11-22"}} ] } + knowledgebase_ids = ["adimin1", "adimin2", "adimi3", "adimin4", "adimin5", "adimin6"] # Example filters for get_by_metadata filters_example = [ @@ -1106,6 +1110,6 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_exam # Or run all tests user_name = "memosfeebbc2bd1744d7bb5b5ec57f38e828d" scope = "WorkingMemory" - test_search_by_embedding(graph, vector, user_name, filter_example) - test_get_all_memory_items(graph, scope, user_name, filter_example) - test_get_by_metadata(graph, filters_example, user_name, filter_example) \ No newline at end of file + test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) + test_get_all_memory_items(graph, scope, user_name, filter_example, knowledgebase_ids) + test_get_by_metadata(graph, filters_example, user_name, filter_example, knowledgebase_ids) diff --git a/src/memos/graph_dbs/neo4j.py b/src/memos/graph_dbs/neo4j.py index c9281541d..733d6b134 100644 --- a/src/memos/graph_dbs/neo4j.py +++ b/src/memos/graph_dbs/neo4j.py @@ -662,6 +662,7 @@ def search_by_embedding( search_filter: dict | None = None, user_name: str | None = None, filter: dict | None = None, + knowledgebase_ids: list[str] | None = None, **kwargs, ) -> list[dict]: """ @@ -696,8 +697,25 @@ def search_by_embedding( where_clauses.append("node.memory_type = $scope") if status: where_clauses.append("node.status = $status") + + # Build user_name filter with knowledgebase_ids support (OR relationship) + user_name_conditions = [] if not self.config.use_multi_db and (self.config.user_name or user_name): - where_clauses.append("node.user_name = $user_name") + user_name_conditions.append("node.user_name = $user_name") + + # Add knowledgebase_ids conditions (checking user_name field in the data) + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for idx, kb_id in enumerate(knowledgebase_ids): + if isinstance(kb_id, str): + param_name = f"kb_id_{idx}" + user_name_conditions.append(f"node.user_name = ${param_name}") + + # Add user_name WHERE clause + if user_name_conditions: + if len(user_name_conditions) == 1: + where_clauses.append(user_name_conditions[0]) + else: + where_clauses.append(f"({' OR '.join(user_name_conditions)})") # Add search_filter conditions if search_filter: @@ -708,15 +726,50 @@ def search_by_embedding( filter_params = {} if filter: def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: + """Build a WHERE condition for a single filter item. + + Args: + condition_dict: A dict like {"id": "xxx"} or {"A": "xxx"} or {"created_at": {"gt": "2025-11-01"}} + param_counter: List to track parameter counter for unique param names + + Returns: + Tuple of (condition_string, parameters_dict) + """ condition_parts = [] params = {} for key, value in condition_dict.items(): - # All fields are stored as flat properties in Neo4j - param_name = f"filter_flat_{key}_{param_counter[0]}" - param_counter[0] += 1 - params[param_name] = value - condition_parts.append(f"node.{key} = ${param_name}") + # Check if value is a dict with comparison operators (gt, lt, gte, lte) + if isinstance(value, dict): + # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte"): + # Map operator to Cypher operator + cypher_op_map = { + "gt": ">", + "lt": "<", + "gte": ">=", + "lte": "<=" + } + cypher_op = cypher_op_map[op] + + # All fields are stored as flat properties in Neo4j + param_name = f"filter_flat_{key}_{op}_{param_counter[0]}" + param_counter[0] += 1 + params[param_name] = op_value + + # Check if field is a date field (created_at, updated_at, etc.) + # Use datetime() function for date comparisons + if key in ("created_at", "updated_at") or key.endswith("_at"): + condition_parts.append(f"node.{key} {cypher_op} datetime(${param_name})") + else: + condition_parts.append(f"node.{key} {cypher_op} ${param_name}") + else: + # All fields are stored as flat properties in Neo4j (simple equality) + param_name = f"filter_flat_{key}_{param_counter[0]}" + param_counter[0] += 1 + params[param_name] = value + condition_parts.append(f"node.{key} = ${param_name}") return " AND ".join(condition_parts), params @@ -759,11 +812,20 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s parameters["scope"] = scope if status: parameters["status"] = status + + # Add user_name parameter if not self.config.use_multi_db and (self.config.user_name or user_name): if kwargs.get("cube_name"): parameters["user_name"] = kwargs["cube_name"] else: parameters["user_name"] = user_name + + # Add knowledgebase_ids parameters + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for idx, kb_id in enumerate(knowledgebase_ids): + if isinstance(kb_id, str): + param_name = f"kb_id_{idx}" + parameters[param_name] = kb_id if search_filter: for key, value in search_filter.items(): @@ -786,7 +848,7 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s return records def get_by_metadata( - self, filters: list[dict[str, Any]], user_name: str | None = None, filter: dict | None = None + self, filters: list[dict[str, Any]], user_name: str | None = None, filter: dict | None = None, knowledgebase_ids: list[str] | None = None ) -> list[str]: """ TODO: @@ -844,9 +906,24 @@ def get_by_metadata( else: raise ValueError(f"Unsupported operator: {op}") + # Build user_name filter with knowledgebase_ids support (OR relationship) + user_name_conditions = [] if not self.config.use_multi_db and (self.config.user_name or user_name): - where_clauses.append("n.user_name = $user_name") - params["user_name"] = user_name + user_name_conditions.append("n.user_name = $user_name") + + # Add knowledgebase_ids conditions (checking user_name field in the data) + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for idx, kb_id in enumerate(knowledgebase_ids): + if isinstance(kb_id, str): + param_name = f"kb_id_{idx}" + user_name_conditions.append(f"n.user_name = ${param_name}") + + # Add user_name WHERE clause + if user_name_conditions: + if len(user_name_conditions) == 1: + where_clauses.append(user_name_conditions[0]) + else: + where_clauses.append(f"({' OR '.join(user_name_conditions)})") # Add filter conditions (supports "or" and "and" logic) filter_params = {} @@ -856,7 +933,7 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s """Build a WHERE condition for a single filter item. Args: - condition_dict: A dict like {"id": "xxx"} or {"A": "xxx"} + condition_dict: A dict like {"id": "xxx"} or {"A": "xxx"} or {"created_at": {"gt": "2025-11-01"}} param_counter: List to track parameter counter for unique param names Returns: @@ -866,11 +943,37 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s filter_params_inner = {} for key, value in condition_dict.items(): - # All fields are stored as flat properties in Neo4j - param_name = f"filter_meta_{key}_{param_counter[0]}" - param_counter[0] += 1 - filter_params_inner[param_name] = value - condition_parts.append(f"n.{key} = ${param_name}") + # Check if value is a dict with comparison operators (gt, lt, gte, lte) + if isinstance(value, dict): + # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte"): + # Map operator to Cypher operator + cypher_op_map = { + "gt": ">", + "lt": "<", + "gte": ">=", + "lte": "<=" + } + cypher_op = cypher_op_map[op] + + # All fields are stored as flat properties in Neo4j + param_name = f"filter_meta_{key}_{op}_{param_counter[0]}" + param_counter[0] += 1 + filter_params_inner[param_name] = op_value + + # Check if field is a date field (created_at, updated_at, etc.) + # Use datetime() function for date comparisons + if key in ("created_at", "updated_at") or key.endswith("_at"): + condition_parts.append(f"n.{key} {cypher_op} datetime(${param_name})") + else: + condition_parts.append(f"n.{key} {cypher_op} ${param_name}") + else: + # All fields are stored as flat properties in Neo4j (simple equality) + param_name = f"filter_meta_{key}_{param_counter[0]}" + param_counter[0] += 1 + filter_params_inner[param_name] = value + condition_parts.append(f"n.{key} = ${param_name}") return " AND ".join(condition_parts), filter_params_inner @@ -906,6 +1009,17 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s else: query = "MATCH (n:Memory) RETURN n.id AS id" + # Add user_name parameter + if not self.config.use_multi_db and (self.config.user_name or user_name): + params["user_name"] = user_name + + # Add knowledgebase_ids parameters + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for idx, kb_id in enumerate(knowledgebase_ids): + if isinstance(kb_id, str): + param_name = f"kb_id_{idx}" + params[param_name] = kb_id + # Merge filter parameters if filter_params: params.update(filter_params) @@ -1102,7 +1216,7 @@ def import_graph(self, data: dict[str, Any], user_name: str | None = None) -> No target_id=edge["target"], ) - def get_all_memory_items(self, scope: str, filter: dict | None = None, **kwargs) -> list[dict]: + def get_all_memory_items(self, scope: str, filter: dict | None = None, knowledgebase_ids: list[str] | None = None, **kwargs) -> list[dict]: """ Retrieve all memory items of a specific memory_type. @@ -1122,22 +1236,72 @@ def get_all_memory_items(self, scope: str, filter: dict | None = None, **kwargs) where_clauses = ["n.memory_type = $scope"] params = {"scope": scope} + # Build user_name filter with knowledgebase_ids support (OR relationship) + user_name_conditions = [] if not self.config.use_multi_db and (self.config.user_name or user_name): - where_clauses.append("n.user_name = $user_name") - params["user_name"] = user_name + user_name_conditions.append("n.user_name = $user_name") + + # Add knowledgebase_ids conditions (checking user_name field in the data) + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for idx, kb_id in enumerate(knowledgebase_ids): + if isinstance(kb_id, str): + param_name = f"kb_id_{idx}" + user_name_conditions.append(f"n.user_name = ${param_name}") + + # Add user_name WHERE clause + if user_name_conditions: + if len(user_name_conditions) == 1: + where_clauses.append(user_name_conditions[0]) + else: + where_clauses.append(f"({' OR '.join(user_name_conditions)})") filter_params = {} if filter: def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: - + """Build a WHERE condition for a single filter item. + + Args: + condition_dict: A dict like {"id": "xxx"} or {"A": "xxx"} or {"created_at": {"gt": "2025-11-01"}} + param_counter: List to track parameter counter for unique param names + + Returns: + Tuple of (condition_string, parameters_dict) + """ condition_parts = [] filter_params_inner = {} for key, value in condition_dict.items(): - param_name = f"filter_flat_{key}_{param_counter[0]}" - param_counter[0] += 1 - filter_params_inner[param_name] = value - condition_parts.append(f"n.{key} = ${param_name}") + # Check if value is a dict with comparison operators (gt, lt, gte, lte) + if isinstance(value, dict): + # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte"): + # Map operator to Cypher operator + cypher_op_map = { + "gt": ">", + "lt": "<", + "gte": ">=", + "lte": "<=" + } + cypher_op = cypher_op_map[op] + + # All fields are stored as flat properties in Neo4j + param_name = f"filter_flat_{key}_{op}_{param_counter[0]}" + param_counter[0] += 1 + filter_params_inner[param_name] = op_value + + # Check if field is a date field (created_at, updated_at, etc.) + # Use datetime() function for date comparisons + if key in ("created_at", "updated_at") or key.endswith("_at"): + condition_parts.append(f"n.{key} {cypher_op} datetime(${param_name})") + else: + condition_parts.append(f"n.{key} {cypher_op} ${param_name}") + else: + # All fields are stored as flat properties in Neo4j (simple equality) + param_name = f"filter_flat_{key}_{param_counter[0]}" + param_counter[0] += 1 + filter_params_inner[param_name] = value + condition_parts.append(f"n.{key} = ${param_name}") return " AND ".join(condition_parts), filter_params_inner @@ -1165,6 +1329,17 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s where_clause = "WHERE " + " AND ".join(where_clauses) + # Add user_name parameter + if not self.config.use_multi_db and (self.config.user_name or user_name): + params["user_name"] = user_name + + # Add knowledgebase_ids parameters + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for idx, kb_id in enumerate(knowledgebase_ids): + if isinstance(kb_id, str): + param_name = f"kb_id_{idx}" + params[param_name] = kb_id + if filter_params: params.update(filter_params) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 7bc7afcaf..efcb01ee8 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1904,7 +1904,7 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: ids = [] conn = self._get_connection() - print(f"get_by_metadata cypher_query: {cypher_query}") + print(f"get_by_metadata : {cypher_query}") try: with conn.cursor() as cursor: cursor.execute(cypher_query) From b3ddc66c67b2241a1b6b1e64a6e48ad1d62c6019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Tue, 25 Nov 2025 10:33:38 +0800 Subject: [PATCH 23/36] update neo4j.py add --- examples/basic_modules/neo4j_search.py | 14 +++++------ src/memos/graph_dbs/neo4j.py | 32 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/examples/basic_modules/neo4j_search.py b/examples/basic_modules/neo4j_search.py index bededb202..4650a7377 100644 --- a/examples/basic_modules/neo4j_search.py +++ b/examples/basic_modules/neo4j_search.py @@ -1092,13 +1092,13 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_exam # Example filter for testing - common filter used by multiple tests filter_example = { "and": [ - {"id": "4534c646-50fe-4f8b-9488-91992ec8af91"}, - {"A": "湖北武当山"}, - {"created_at": {"gt": "2025-09-19"}}, - {"created_at": {"lt": "2025-11-22"}} + {"id": "33687a04-6ad0-43ca-8289-43500412ac73"}, + {"A": "新疆乌鲁木齐市"}, + # {"created_at": {"gt": "2025-09-19"}}, + # {"created_at": {"lt": "2025-11-20"}} ] } - knowledgebase_ids = ["adimin1", "adimin2", "adimi3", "adimin4", "adimin5", "adimin6"] + knowledgebase_ids = ["adimin1", "adimin2"] # Example filters for get_by_metadata filters_example = [ @@ -1110,6 +1110,6 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_exam # Or run all tests user_name = "memosfeebbc2bd1744d7bb5b5ec57f38e828d" scope = "WorkingMemory" - test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) + # test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) test_get_all_memory_items(graph, scope, user_name, filter_example, knowledgebase_ids) - test_get_by_metadata(graph, filters_example, user_name, filter_example, knowledgebase_ids) + # test_get_by_metadata(graph, filters_example, user_name, filter_example, knowledgebase_ids) diff --git a/src/memos/graph_dbs/neo4j.py b/src/memos/graph_dbs/neo4j.py index 733d6b134..9fc384b37 100644 --- a/src/memos/graph_dbs/neo4j.py +++ b/src/memos/graph_dbs/neo4j.py @@ -45,6 +45,33 @@ def _prepare_node_metadata(metadata: dict[str, Any]) -> dict[str, Any]: return metadata +def _flatten_info_fields(metadata: dict[str, Any]) -> dict[str, Any]: + """ + Flatten the 'info' field in metadata to the top level. + + If metadata contains an 'info' field that is a dictionary, all its key-value pairs + will be moved to the top level of metadata, and the 'info' field will be removed. + + Args: + metadata: Dictionary that may contain an 'info' field + + Returns: + Dictionary with 'info' fields flattened to top level + + Example: + Input: {"user_id": "xxx", "info": {"A": "value1", "B": "value2"}} + Output: {"user_id": "xxx", "A": "value1", "B": "value2"} + """ + if "info" in metadata and isinstance(metadata["info"], dict): + # Copy info fields to top level + info_dict = metadata.pop("info") + for key, value in info_dict.items(): + # Only add if key doesn't already exist at top level (to avoid overwriting) + if key not in metadata: + metadata[key] = value + return metadata + + class Neo4jGraphDB(BaseGraphDB): """Neo4j-based implementation of a graph memory store.""" @@ -170,12 +197,16 @@ def remove_oldest_memory( def add_node( self, id: str, memory: str, metadata: dict[str, Any], user_name: str | None = None ) -> None: + print("add_node metadata:",metadata) user_name = user_name if user_name else self.config.user_name if not self.config.use_multi_db and (self.config.user_name or user_name): metadata["user_name"] = user_name # Safely process metadata metadata = _prepare_node_metadata(metadata) + + # Flatten info fields to top level (for Neo4j flat structure) + metadata = _flatten_info_fields(metadata) # Merge node and set metadata created_at = metadata.pop("created_at") @@ -193,6 +224,7 @@ def add_node( if metadata["sources"]: for idx in range(len(metadata["sources"])): metadata["sources"][idx] = json.dumps(metadata["sources"][idx]) + print("111add_node id:",id) with self.driver.session(database=self.db_name) as session: session.run( From 9ddb9a1f349579e2d3c03e10db68eb4867e371e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Tue, 25 Nov 2025 14:18:27 +0800 Subject: [PATCH 24/36] update polardb.py log and test --- src/memos/graph_dbs/polardb.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index efcb01ee8..d5dde8f68 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1733,6 +1733,9 @@ def get_by_metadata( Returns: list[str]: Node IDs whose metadata match the filter conditions. (AND logic). """ + logger.info(f"[get_by_metadata] filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") + print(f"[get_by_metadata] filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") + user_name = user_name if user_name else self._get_config_value("user_name") # Build WHERE conditions for cypher query @@ -1904,7 +1907,8 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: ids = [] conn = self._get_connection() - print(f"get_by_metadata : {cypher_query}") + logger.info(f"[get_by_metadata] cypher_query: {cypher_query}") + print(f"[get_by_metadata] cypher_query: {cypher_query}") try: with conn.cursor() as cursor: cursor.execute(cypher_query) @@ -2328,6 +2332,9 @@ def get_all_memory_items( Returns: list[dict]: Full list of memory items under this scope. """ + logger.info(f"[get_all_memory_items] filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") + print(f"[get_all_memory_items] filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") + user_name = user_name if user_name else self._get_config_value("user_name") if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"}: raise ValueError(f"Unsupported memory type scope: {scope}") @@ -2512,7 +2519,7 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: where_clause = " AND ".join(where_parts) + filter_where_clause else: where_clause = " AND ".join(where_parts) - + cypher_query = f""" SELECT * FROM cypher('{self.db_name}_graph', $$ MATCH (n:Memory) @@ -2524,7 +2531,8 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: nodes = [] conn = self._get_connection() - print("1111111cypher_query:", cypher_query) + logger.info(f"[get_all_memory_items] cypher_query: {cypher_query}") + print(f"[get_all_memory_items] cypher_query: {cypher_query}") try: with conn.cursor() as cursor: cursor.execute(cypher_query) @@ -2899,7 +2907,9 @@ def add_node( self, id: str, memory: str, metadata: dict[str, Any], user_name: str | None = None ) -> None: """Add a memory node to the graph.""" - logger.info(f"In add node polardb: id-{id} memory-{memory}") + logger.info(f"[add_node] id: {id}, memory: {memory}, metadata: {metadata}") + print(f"[add_node] metadata: {metadata}, info: {metadata.get('info')}") + # user_name comes from metadata; fallback to config if missing metadata["user_name"] = user_name if user_name else self.config.user_name @@ -2981,6 +2991,8 @@ def add_node( cursor.execute( insert_query, (id, json.dumps(properties), json.dumps(embedding_vector)) ) + logger.info(f"[add_node] [embedding_vector-true] insert_query: {insert_query}, properties: {json.dumps(properties)}") + print(f"[add_node] [embedding_vector-true] insert_query: {insert_query}, properties: {json.dumps(properties)}") else: insert_query = f""" INSERT INTO {self.db_name}_graph."Memory"(id, properties) @@ -2990,7 +3002,9 @@ def add_node( ) """ cursor.execute(insert_query, (id, json.dumps(properties))) - logger.info(f"Added node {id} to graph '{self.db_name}_graph'.") + logger.info(f"[add_node] [embedding_vector-false] insert_query: {insert_query}, properties: {json.dumps(properties)}") + print(f"[add_node] [embedding_vector-false] insert_query: {insert_query}, properties: {json.dumps(properties)}") + finally: logger.info(f"In add node polardb: id-{id} memory-{memory} query-{insert_query}") self._return_connection(conn) From 90fd51a715ca24ea328a862bf7a3be0a1ee7f58a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Tue, 25 Nov 2025 14:29:56 +0800 Subject: [PATCH 25/36] update neo4j.py log and test --- examples/basic_modules/neo4j_search.py | 10 ++++---- examples/basic_modules/polardb_search.py | 31 ++++++++++++------------ src/memos/graph_dbs/neo4j.py | 17 ++++++++++--- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/examples/basic_modules/neo4j_search.py b/examples/basic_modules/neo4j_search.py index 4650a7377..c934143dd 100644 --- a/examples/basic_modules/neo4j_search.py +++ b/examples/basic_modules/neo4j_search.py @@ -1092,8 +1092,8 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_exam # Example filter for testing - common filter used by multiple tests filter_example = { "and": [ - {"id": "33687a04-6ad0-43ca-8289-43500412ac73"}, - {"A": "新疆乌鲁木齐市"}, + {"id": "cfe42bd6-ee78-4f6f-b997-8baa0ea957e1"}, + # {"A": "新疆乌鲁木齐市"}, # {"created_at": {"gt": "2025-09-19"}}, # {"created_at": {"lt": "2025-11-20"}} ] @@ -1109,7 +1109,7 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_exam # Or run all tests user_name = "memosfeebbc2bd1744d7bb5b5ec57f38e828d" - scope = "WorkingMemory" + scope = "LongTermMemory" # test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) - test_get_all_memory_items(graph, scope, user_name, filter_example, knowledgebase_ids) - # test_get_by_metadata(graph, filters_example, user_name, filter_example, knowledgebase_ids) + # test_get_all_memory_items(graph, scope, user_name, filter_example, knowledgebase_ids) + test_get_by_metadata(graph, filters_example, user_name, filter_example, knowledgebase_ids) diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py index f9034a008..5f157d6d9 100644 --- a/examples/basic_modules/polardb_search.py +++ b/examples/basic_modules/polardb_search.py @@ -1272,36 +1272,35 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt ] # Example filter for testing - common filter used by multiple tests filter_example = { - "or": [ - {"id": "65c3a65b-78f7-4009-bc92-7ee1981deb3a"}, - { - # "info.A": "建议从基础语法开始,多做练习项目", - "A": "建议从基础语法开始,多做练习项目" - }, - { - # "info.B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" - "B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" - }, - {"created_at":{"gt":"2025-09-19"}}, - {"created_at": {"lt": "2025-11-12"}} + "and": [ + {"id": "45a4f936-2182-44c6-8c4f-4a9476941e51"}, + # { + # "A": "新疆乌鲁木齐市" + # }, + # { + # "B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" + # }, + # {"created_at":{"gt":"2025-09-19"}}, + # {"created_at": {"lt": "2025-11-12"}} ] } # Example filters for get_by_metadata filters_example = [ - {"field": "tags", "op": "contains", "value": "Python"}, + {"field": "tags", "op": "contains", "value": "mode:fast"}, ] # Create connection once - shared by all tests graph = getPolarDb() user_name = "adimin" scope = "WorkingMemory" - knowledgebase_ids = ["adimin1","adimin2","adimi3","adimin4","adimin5","adimin6"] + knowledgebase_ids = ["memosfeebbc2bd1744d7bb5b5ec57f38e828d","adimin2"] # Run all tests - uncomment the test you want to run - test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) - # test_get_all_memory_items(graph, "LongTermMemory", False, user_name, filter_example,knowledgebase_ids) + # test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) + # test_get_all_memory_items(graph, "WorkingMemory", False, user_name, filter_example,knowledgebase_ids) # test_get_by_metadata(graph, filters_example, user_name, filter_example,knowledgebase_ids) + test_get_by_metadata(graph, filters_example, user_name, filter_example,knowledgebase_ids) # Or run all tests diff --git a/src/memos/graph_dbs/neo4j.py b/src/memos/graph_dbs/neo4j.py index 9fc384b37..fbdf92de6 100644 --- a/src/memos/graph_dbs/neo4j.py +++ b/src/memos/graph_dbs/neo4j.py @@ -197,7 +197,9 @@ def remove_oldest_memory( def add_node( self, id: str, memory: str, metadata: dict[str, Any], user_name: str | None = None ) -> None: - print("add_node metadata:",metadata) + logger.info(f"[add_node] metadata: {metadata},info: {metadata.get('info')}") + print(f"[add_node] metadata: {metadata},info: {metadata.get('info')}") + user_name = user_name if user_name else self.config.user_name if not self.config.use_multi_db and (self.config.user_name or user_name): metadata["user_name"] = user_name @@ -906,6 +908,8 @@ def get_by_metadata( - Supports structured querying such as tag/category/importance/time filtering. - Can be used for faceted recall or prefiltering before embedding rerank. """ + logger.info(f"[get_by_metadata] filters: {filters},user_name: {user_name},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") + print(f"[get_by_metadata] filters: {filters},user_name: {user_name},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") user_name = user_name if user_name else self.config.user_name where_clauses = [] params = {} @@ -1055,8 +1059,8 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s # Merge filter parameters if filter_params: params.update(filter_params) - print("1111111query:", query) - print("1111111params:", params) + logger.info(f"[get_by_metadata] query: {query},params: {params}") + print(f"[get_by_metadata] query: {query},params: {params}") with self.driver.session(database=self.db_name) as session: result = session.run(query, params) @@ -1261,6 +1265,9 @@ def get_all_memory_items(self, scope: str, filter: dict | None = None, knowledge Returns: list[dict]: Full list of memory items under this scope. """ + logger.info(f"[get_all_memory_items] scope: {scope},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") + print(f"[get_all_memory_items] scope: {scope},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") + user_name = kwargs.get("user_name") if kwargs.get("user_name") else self.config.user_name if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"}: raise ValueError(f"Unsupported memory type scope: {scope}") @@ -1380,7 +1387,9 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s {where_clause} RETURN n """ - print("999999991111111query:", query) + logger.info(f"[get_all_memory_items] query: {query},params: {params}") + print(f"[get_all_memory_items] query: {query},params: {params}") + with self.driver.session(database=self.db_name) as session: results = session.run(query, params) return [self._parse_node(dict(record["n"])) for record in results] From 4f38b01684eb0ed7f0476374b2d807dc9e0e240e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Tue, 25 Nov 2025 19:05:26 +0800 Subject: [PATCH 26/36] update polardb_search.py --- examples/basic_modules/polardb_search.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py index 5f157d6d9..a1f8f2990 100644 --- a/examples/basic_modules/polardb_search.py +++ b/examples/basic_modules/polardb_search.py @@ -1272,16 +1272,16 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt ] # Example filter for testing - common filter used by multiple tests filter_example = { - "and": [ + "or": [ {"id": "45a4f936-2182-44c6-8c4f-4a9476941e51"}, - # { - # "A": "新疆乌鲁木齐市" - # }, - # { - # "B": "用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。" - # }, - # {"created_at":{"gt":"2025-09-19"}}, - # {"created_at": {"lt": "2025-11-12"}} + { + "A": "广西狗肉" + }, + { + "B": "广西啤酒鸭" + }, + {"created_at":{"gt":"2025-09-19"}}, + {"created_at": {"lt": "2025-11-12"}} ] } From c821867c5456d27c80fc56de99b5c50ef12742b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Wed, 26 Nov 2025 16:57:30 +0800 Subject: [PATCH 27/36] update neo4j_community.py for filter --- src/memos/graph_dbs/neo4j_community.py | 432 ++++++++++++++++++++++++- 1 file changed, 425 insertions(+), 7 deletions(-) diff --git a/src/memos/graph_dbs/neo4j_community.py b/src/memos/graph_dbs/neo4j_community.py index 6f7786834..70ae96de8 100644 --- a/src/memos/graph_dbs/neo4j_community.py +++ b/src/memos/graph_dbs/neo4j_community.py @@ -143,6 +143,8 @@ def search_by_embedding( threshold: float | None = None, search_filter: dict | None = None, user_name: str | None = None, + filter: dict | None = None, + knowledgebase_ids: list[str] | None = None, **kwargs, ) -> list[dict]: """ @@ -155,6 +157,8 @@ def search_by_embedding( status (str, optional): Node status filter (e.g., 'activated', 'archived'). threshold (float, optional): Minimum similarity score threshold (0 ~ 1). search_filter (dict, optional): Additional metadata filters to apply. + filter (dict, optional): Filter conditions with 'and' or 'or' logic for search results. + knowledgebase_ids (list[str], optional): List of knowledgebase IDs to filter by user_name. Returns: list[dict]: A list of dicts with 'id' and 'score', ordered by similarity. @@ -165,6 +169,8 @@ def search_by_embedding( - If 'status' is provided, it further filters nodes by status. - If 'threshold' is provided, only results with score >= threshold will be returned. - If 'search_filter' is provided, it applies additional metadata-based filtering. + - If 'filter' is provided, it applies additional WHERE clauses in Neo4j after vector search. + - If 'knowledgebase_ids' is provided, it filters by user_name with OR logic. - The returned IDs can be used to fetch full node data from Neo4j if needed. """ user_name = user_name if user_name else self.config.user_name @@ -184,46 +190,458 @@ def search_by_embedding( if search_filter: vec_filter.update(search_filter) - # Perform vector search - results = self.vec_db.search(query_vector=vector, top_k=top_k, filter=vec_filter) + # Perform vector search - get more results if we need to filter in Neo4j + search_top_k = top_k * 2 if (filter or knowledgebase_ids) else top_k + results = self.vec_db.search(query_vector=vector, top_k=search_top_k, filter=vec_filter) # Filter by threshold if threshold is not None: results = [r for r in results if r.score is None or r.score >= threshold] + # If we have filter or knowledgebase_ids, need to filter in Neo4j + if filter or knowledgebase_ids: + # Get IDs from vector search results + vec_result_ids = [r.id for r in results] + + if not vec_result_ids: + return [] + + # Build WHERE clause for Neo4j filtering + where_clauses = [f"node.id IN $vec_ids"] + params = {"vec_ids": vec_result_ids} + + # Build user_name filter with knowledgebase_ids support (OR relationship) + user_name_conditions = [] + if not self.config.use_multi_db and (self.config.user_name or user_name): + user_name_conditions.append("node.user_name = $user_name") + params["user_name"] = user_name + + # Add knowledgebase_ids conditions (checking user_name field in the data) + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for idx, kb_id in enumerate(knowledgebase_ids): + if isinstance(kb_id, str): + param_name = f"kb_id_{idx}" + user_name_conditions.append(f"node.user_name = ${param_name}") + params[param_name] = kb_id + + # Add user_name WHERE clause + if user_name_conditions: + if len(user_name_conditions) == 1: + where_clauses.append(user_name_conditions[0]) + else: + where_clauses.append(f"({' OR '.join(user_name_conditions)})") + + # Add filter conditions + filter_params = {} + if filter: + def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: + """Build a WHERE condition for a single filter item.""" + condition_parts = [] + filter_params_inner = {} + + for key, value in condition_dict.items(): + # Check if value is a dict with comparison operators (gt, lt, gte, lte) + if isinstance(value, dict): + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte"): + cypher_op_map = { + "gt": ">", + "lt": "<", + "gte": ">=", + "lte": "<=" + } + cypher_op = cypher_op_map[op] + + param_name = f"filter_flat_{key}_{op}_{param_counter[0]}" + param_counter[0] += 1 + filter_params_inner[param_name] = op_value + + # Use datetime() function for date comparisons + if key in ("created_at", "updated_at") or key.endswith("_at"): + condition_parts.append(f"node.{key} {cypher_op} datetime(${param_name})") + else: + condition_parts.append(f"node.{key} {cypher_op} ${param_name}") + else: + # Simple equality + param_name = f"filter_flat_{key}_{param_counter[0]}" + param_counter[0] += 1 + filter_params_inner[param_name] = value + condition_parts.append(f"node.{key} = ${param_name}") + + return " AND ".join(condition_parts), filter_params_inner + + param_counter = [0] + + if isinstance(filter, dict): + if "or" in filter: + or_conditions = [] + for condition in filter["or"]: + if isinstance(condition, dict): + condition_str, filter_params_inner = build_filter_condition(condition, param_counter) + if condition_str: + or_conditions.append(f"({condition_str})") + filter_params.update(filter_params_inner) + if or_conditions: + where_clauses.append(f"({' OR '.join(or_conditions)})") + + elif "and" in filter: + for condition in filter["and"]: + if isinstance(condition, dict): + condition_str, filter_params_inner = build_filter_condition(condition, param_counter) + if condition_str: + where_clauses.append(f"({condition_str})") + filter_params.update(filter_params_inner) + + if filter_params: + params.update(filter_params) + + where_clause = "WHERE " + " AND ".join(where_clauses) + + # Query Neo4j to filter results + query = f""" + MATCH (node:Memory) + {where_clause} + RETURN node.id AS id + """ + + with self.driver.session(database=self.db_name) as session: + neo4j_results = session.run(query, params) + filtered_ids = {record["id"] for record in neo4j_results} + + # Filter vector search results by Neo4j filtered IDs and keep scores + filtered_results = [r for r in results if r.id in filtered_ids] + + # Return top_k results + return [{"id": r.id, "score": r.score} for r in filtered_results[:top_k]] + # Return consistent format - return [{"id": r.id, "score": r.score} for r in results] + return [{"id": r.id, "score": r.score} for r in results[:top_k]] - def get_all_memory_items(self, scope: str, **kwargs) -> list[dict]: + def get_all_memory_items(self, scope: str, filter: dict | None = None, knowledgebase_ids: list[str] | None = None, **kwargs) -> list[dict]: """ Retrieve all memory items of a specific memory_type. Args: scope (str): Must be one of 'WorkingMemory', 'LongTermMemory', or 'UserMemory'. + filter (dict, optional): Filter conditions with 'and' or 'or' logic for search results. + Example: {"and": [{"id": "xxx"}, {"A": "yyy"}]} or {"or": [{"id": "xxx"}, {"A": "yyy"}]} + knowledgebase_ids (list[str], optional): List of knowledgebase IDs to filter by user_name. + Returns: list[dict]: Full list of memory items under this scope. """ + logger.info(f"[get_all_memory_items] scope: {scope},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") + print(f"[get_all_memory_items] scope: {scope},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") + user_name = kwargs.get("user_name") if kwargs.get("user_name") else self.config.user_name - if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory"}: + if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"}: raise ValueError(f"Unsupported memory type scope: {scope}") - where_clause = "WHERE n.memory_type = $scope" + where_clauses = ["n.memory_type = $scope"] params = {"scope": scope} + # Build user_name filter with knowledgebase_ids support (OR relationship) + user_name_conditions = [] + if not self.config.use_multi_db and (self.config.user_name or user_name): + user_name_conditions.append("n.user_name = $user_name") + + # Add knowledgebase_ids conditions (checking user_name field in the data) + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for idx, kb_id in enumerate(knowledgebase_ids): + if isinstance(kb_id, str): + param_name = f"kb_id_{idx}" + user_name_conditions.append(f"n.user_name = ${param_name}") + + # Add user_name WHERE clause + if user_name_conditions: + if len(user_name_conditions) == 1: + where_clauses.append(user_name_conditions[0]) + else: + where_clauses.append(f"({' OR '.join(user_name_conditions)})") + + filter_params = {} + if filter: + def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: + """Build a WHERE condition for a single filter item. + + Args: + condition_dict: A dict like {"id": "xxx"} or {"A": "xxx"} or {"created_at": {"gt": "2025-11-01"}} + param_counter: List to track parameter counter for unique param names + + Returns: + Tuple of (condition_string, parameters_dict) + """ + condition_parts = [] + filter_params_inner = {} + + for key, value in condition_dict.items(): + # Check if value is a dict with comparison operators (gt, lt, gte, lte) + if isinstance(value, dict): + # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte"): + # Map operator to Cypher operator + cypher_op_map = { + "gt": ">", + "lt": "<", + "gte": ">=", + "lte": "<=" + } + cypher_op = cypher_op_map[op] + + # All fields are stored as flat properties in Neo4j + param_name = f"filter_flat_{key}_{op}_{param_counter[0]}" + param_counter[0] += 1 + filter_params_inner[param_name] = op_value + + # Check if field is a date field (created_at, updated_at, etc.) + # Use datetime() function for date comparisons + if key in ("created_at", "updated_at") or key.endswith("_at"): + condition_parts.append(f"n.{key} {cypher_op} datetime(${param_name})") + else: + condition_parts.append(f"n.{key} {cypher_op} ${param_name}") + else: + # All fields are stored as flat properties in Neo4j (simple equality) + param_name = f"filter_flat_{key}_{param_counter[0]}" + param_counter[0] += 1 + filter_params_inner[param_name] = value + condition_parts.append(f"n.{key} = ${param_name}") + + return " AND ".join(condition_parts), filter_params_inner + + param_counter = [0] + + if isinstance(filter, dict): + if "or" in filter: + or_conditions = [] + for condition in filter["or"]: + if isinstance(condition, dict): + condition_str, filter_params_inner = build_filter_condition(condition, param_counter) + if condition_str: + or_conditions.append(f"({condition_str})") + filter_params.update(filter_params_inner) + if or_conditions: + where_clauses.append(f"({' OR '.join(or_conditions)})") + + elif "and" in filter: + for condition in filter["and"]: + if isinstance(condition, dict): + condition_str, filter_params_inner = build_filter_condition(condition, param_counter) + if condition_str: + where_clauses.append(f"({condition_str})") + filter_params.update(filter_params_inner) + + where_clause = "WHERE " + " AND ".join(where_clauses) + + # Add user_name parameter if not self.config.use_multi_db and (self.config.user_name or user_name): - where_clause += " AND n.user_name = $user_name" params["user_name"] = user_name + + # Add knowledgebase_ids parameters + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for idx, kb_id in enumerate(knowledgebase_ids): + if isinstance(kb_id, str): + param_name = f"kb_id_{idx}" + params[param_name] = kb_id + + if filter_params: + params.update(filter_params) query = f""" MATCH (n:Memory) {where_clause} RETURN n """ + logger.info(f"[get_all_memory_items] query: {query},params: {params}") + print(f"[get_all_memory_items] query: {query},params: {params}") with self.driver.session(database=self.db_name) as session: results = session.run(query, params) return [self._parse_node(dict(record["n"])) for record in results] + def get_by_metadata( + self, filters: list[dict[str, Any]], user_name: str | None = None, filter: dict | None = None, knowledgebase_ids: list[str] | None = None + ) -> list[str]: + """ + Retrieve node IDs that match given metadata filters. + Supports exact match. + + Args: + filters: List of filter dicts like: + [ + {"field": "key", "op": "in", "value": ["A", "B"]}, + {"field": "confidence", "op": ">=", "value": 80}, + {"field": "tags", "op": "contains", "value": "AI"}, + ... + ] + filter (dict, optional): Filter conditions with 'and' or 'or' logic for search results. + knowledgebase_ids (list[str], optional): List of knowledgebase IDs to filter by user_name. + + Returns: + list[str]: Node IDs whose metadata match the filter conditions. (AND logic). + + Notes: + - Supports structured querying such as tag/category/importance/time filtering. + - Can be used for faceted recall or prefiltering before embedding rerank. + """ + logger.info(f"[get_by_metadata] filters: {filters},user_name: {user_name},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") + print(f"[get_by_metadata] filters: {filters},user_name: {user_name},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") + user_name = user_name if user_name else self.config.user_name + where_clauses = [] + params = {} + + for i, f in enumerate(filters): + field = f["field"] + op = f.get("op", "=") + value = f["value"] + param_key = f"val{i}" + + # Build WHERE clause + if op == "=": + where_clauses.append(f"n.{field} = ${param_key}") + params[param_key] = value + elif op == "in": + where_clauses.append(f"n.{field} IN ${param_key}") + params[param_key] = value + elif op == "contains": + where_clauses.append(f"ANY(x IN ${param_key} WHERE x IN n.{field})") + params[param_key] = value + elif op == "starts_with": + where_clauses.append(f"n.{field} STARTS WITH ${param_key}") + params[param_key] = value + elif op == "ends_with": + where_clauses.append(f"n.{field} ENDS WITH ${param_key}") + params[param_key] = value + elif op in [">", ">=", "<", "<="]: + where_clauses.append(f"n.{field} {op} ${param_key}") + params[param_key] = value + else: + raise ValueError(f"Unsupported operator: {op}") + + # Build user_name filter with knowledgebase_ids support (OR relationship) + user_name_conditions = [] + if not self.config.use_multi_db and (self.config.user_name or user_name): + user_name_conditions.append("n.user_name = $user_name") + + # Add knowledgebase_ids conditions (checking user_name field in the data) + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for idx, kb_id in enumerate(knowledgebase_ids): + if isinstance(kb_id, str): + param_name = f"kb_id_{idx}" + user_name_conditions.append(f"n.user_name = ${param_name}") + + # Add user_name WHERE clause + if user_name_conditions: + if len(user_name_conditions) == 1: + where_clauses.append(user_name_conditions[0]) + else: + where_clauses.append(f"({' OR '.join(user_name_conditions)})") + + # Add filter conditions (supports "or" and "and" logic) + filter_params = {} + if filter: + # Helper function to build a single filter condition + def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: + """Build a WHERE condition for a single filter item. + + Args: + condition_dict: A dict like {"id": "xxx"} or {"A": "xxx"} or {"created_at": {"gt": "2025-11-01"}} + param_counter: List to track parameter counter for unique param names + + Returns: + Tuple of (condition_string, parameters_dict) + """ + condition_parts = [] + filter_params_inner = {} + + for key, value in condition_dict.items(): + # Check if value is a dict with comparison operators (gt, lt, gte, lte) + if isinstance(value, dict): + # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte"): + # Map operator to Cypher operator + cypher_op_map = { + "gt": ">", + "lt": "<", + "gte": ">=", + "lte": "<=" + } + cypher_op = cypher_op_map[op] + + # All fields are stored as flat properties in Neo4j + param_name = f"filter_meta_{key}_{op}_{param_counter[0]}" + param_counter[0] += 1 + filter_params_inner[param_name] = op_value + + # Check if field is a date field (created_at, updated_at, etc.) + # Use datetime() function for date comparisons + if key in ("created_at", "updated_at") or key.endswith("_at"): + condition_parts.append(f"n.{key} {cypher_op} datetime(${param_name})") + else: + condition_parts.append(f"n.{key} {cypher_op} ${param_name}") + else: + # All fields are stored as flat properties in Neo4j (simple equality) + param_name = f"filter_meta_{key}_{param_counter[0]}" + param_counter[0] += 1 + filter_params_inner[param_name] = value + condition_parts.append(f"n.{key} = ${param_name}") + + return " AND ".join(condition_parts), filter_params_inner + + # Process filter structure + param_counter = [ + len(filters)] # Use list to allow modification in nested function, start from len(filters) to avoid conflicts + + if isinstance(filter, dict): + if "or" in filter: + # OR logic: at least one condition must match + or_conditions = [] + for condition in filter["or"]: + if isinstance(condition, dict): + condition_str, filter_params_inner = build_filter_condition(condition, param_counter) + if condition_str: + or_conditions.append(f"({condition_str})") + filter_params.update(filter_params_inner) + if or_conditions: + where_clauses.append(f"({' OR '.join(or_conditions)})") + + elif "and" in filter: + # AND logic: all conditions must match + for condition in filter["and"]: + if isinstance(condition, dict): + condition_str, filter_params_inner = build_filter_condition(condition, param_counter) + if condition_str: + where_clauses.append(f"({condition_str})") + filter_params.update(filter_params_inner) + + where_str = " AND ".join(where_clauses) if where_clauses else "" + if where_str: + query = f"MATCH (n:Memory) WHERE {where_str} RETURN n.id AS id" + else: + query = "MATCH (n:Memory) RETURN n.id AS id" + + # Add user_name parameter + if not self.config.use_multi_db and (self.config.user_name or user_name): + params["user_name"] = user_name + + # Add knowledgebase_ids parameters + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for idx, kb_id in enumerate(knowledgebase_ids): + if isinstance(kb_id, str): + param_name = f"kb_id_{idx}" + params[param_name] = kb_id + + # Merge filter parameters + if filter_params: + params.update(filter_params) + logger.info(f"[get_by_metadata] query: {query},params: {params}") + print(f"[get_by_metadata] query: {query},params: {params}") + + with self.driver.session(database=self.db_name) as session: + result = session.run(query, params) + return [record["id"] for record in result] + def clear(self, user_name: str | None = None) -> None: """ Clear the entire graph if the target database exists. From d94a64ad6cceb370d31507fdb064d3179a640a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Thu, 27 Nov 2025 00:51:28 +0800 Subject: [PATCH 28/36] add common filter conditions --- examples/basic_modules/polardb_search.py | 12 +- src/memos/graph_dbs/polardb.py | 640 +++++++++++------------ 2 files changed, 313 insertions(+), 339 deletions(-) diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py index a1f8f2990..5e03a4c6c 100644 --- a/examples/basic_modules/polardb_search.py +++ b/examples/basic_modules/polardb_search.py @@ -1272,16 +1272,17 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt ] # Example filter for testing - common filter used by multiple tests filter_example = { - "or": [ + "and": [ {"id": "45a4f936-2182-44c6-8c4f-4a9476941e51"}, { - "A": "广西狗肉" + "A": "中国广西" }, { - "B": "广西啤酒鸭" + "B": "狗肉" }, {"created_at":{"gt":"2025-09-19"}}, - {"created_at": {"lt": "2025-11-12"}} + {"created_at": {"lt": "2025-11-26"}}, + # {"tags": {"=": "mode:fast"}} ] } @@ -1297,10 +1298,9 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt knowledgebase_ids = ["memosfeebbc2bd1744d7bb5b5ec57f38e828d","adimin2"] # Run all tests - uncomment the test you want to run - # test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) + test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) # test_get_all_memory_items(graph, "WorkingMemory", False, user_name, filter_example,knowledgebase_ids) # test_get_by_metadata(graph, filters_example, user_name, filter_example,knowledgebase_ids) - test_get_by_metadata(graph, filters_example, user_name, filter_example,knowledgebase_ids) # Or run all tests diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index d5dde8f68..955085cea 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -1118,7 +1118,7 @@ def get_edges_old( return edges def get_neighbors( - self, id: str, type: str, direction: Literal["in", "out", "both"] = "out" + self, id: str, type: str, direction: Literal["in", "out", "both"] = "out" ) -> list[str]: """Get connected node IDs in a specific direction and relationship type.""" raise NotImplementedError @@ -1493,24 +1493,13 @@ def search_by_embedding( # else: # where_clauses.append(f"ag_catalog.agtype_access_operator(properties, '\"user_name\"'::agtype) = '\"{user_name}\"'::agtype") """ - # Build user_name filter with knowledgebase_ids support (OR relationship) - user_name_conditions = [] - - # Add original user_name condition if provided - user_name = user_name if user_name else self.config.user_name - if user_name: - user_name_conditions.append( - f"ag_catalog.agtype_access_operator(properties, '\"user_name\"'::agtype) = '\"{user_name}\"'::agtype" - ) - - # Add knowledgebase_ids conditions (checking user_name field in the data) - if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: - for kb_id in knowledgebase_ids: - if isinstance(kb_id, str): - user_name_conditions.append( - f"ag_catalog.agtype_access_operator(properties, '\"user_name\"'::agtype) = '\"{kb_id}\"'::agtype" - ) - + # Build user_name filter with knowledgebase_ids support (OR relationship) using common method + user_name_conditions = self._build_user_name_and_kb_ids_conditions_sql( + user_name=user_name, + knowledgebase_ids=knowledgebase_ids, + default_user_name=self.config.user_name, + ) + # Add OR condition if we have any user_name conditions if user_name_conditions: if len(user_name_conditions) == 1: @@ -1529,117 +1518,10 @@ def search_by_embedding( where_clauses.append( f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = {value}::agtype" ) - filter = self.parse_filter(filter) - if filter: - # Helper function to escape string value for SQL - def escape_sql_string(value: str) -> str: - """Escape single quotes in SQL string.""" - return value.replace("'", "''") - - # Helper function to build a single filter condition - def build_filter_condition(condition_dict: dict) -> str: - """Build a WHERE condition for a single filter item. - Args: - condition_dict: A dict like {"id": "xxx"} or {"info.B": "xxx"} or {"created_at": {"gt": "2025-11-01"}} - - Returns: - SQL condition string - """ - condition_parts = [] - for key, value in condition_dict.items(): - # Check if value is a dict with comparison operators (gt, lt, gte, lte) - if isinstance(value, dict): - # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) - for op, op_value in value.items(): - if op in ("gt", "lt", "gte", "lte"): - # Map operator to SQL operator - sql_op_map = { - "gt": ">", - "lt": "<", - "gte": ">=", - "lte": "<=" - } - sql_op = sql_op_map[op] - - # Check if key starts with "info." prefix (for nested fields like info.A, info.B) - # For direct properties like "created_at", this condition will be False - if key.startswith("info."): - # Nested field access: properties->'info'->'field_name' - info_field = key[5:] # Remove "info." prefix - if isinstance(op_value, str): - escaped_value = escape_sql_string(op_value) - condition_parts.append( - f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) {sql_op} '\"{escaped_value}\"'::agtype" - ) - else: - condition_parts.append( - f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) {sql_op} {op_value}::agtype" - ) - else: - # Direct property access (e.g., "created_at" is directly in properties, not in properties.info) - # This handles fields like created_at, updated_at, etc. that are at the top level of properties - if isinstance(op_value, str): - escaped_value = escape_sql_string(op_value) - condition_parts.append( - f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) {sql_op} '\"{escaped_value}\"'::agtype" - ) - else: - condition_parts.append( - f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) {sql_op} {op_value}::agtype" - ) - # Check if key starts with "info." prefix (for simple equality) - elif key.startswith("info."): - # Extract the field name after "info." - info_field = key[5:] # Remove "info." prefix (5 characters) - # Match in info field: properties->'info'->'B' - # For nested access, use agtype_access_operator with nested structure - if isinstance(value, str): - escaped_value = escape_sql_string(value) - # Access nested field: properties->'info'->'B' - # First get info object, then get the field inside it - condition_parts.append( - # f"ag_catalog.agtype_access_operator(ag_catalog.agtype_access_operator(properties, '\"info\"'::agtype), '\"{info_field}\"'::agtype) = '\"{escaped_value}\"'::agtype" - f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '\"{escaped_value}\"'::agtype" - ) - else: - condition_parts.append( - # f"ag_catalog.agtype_access_operator(ag_catalog.agtype_access_operator(properties, '\"info\"'::agtype), '\"{info_field}\"'::agtype) = {value}::agtype" - f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '\"{value}\"'::agtype" - ) - else: - # Direct property access (simple equality) - if isinstance(value, str): - escaped_value = escape_sql_string(value) - condition_parts.append( - f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = '\"{escaped_value}\"'::agtype" - ) - else: - condition_parts.append( - f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = {value}::agtype" - ) - return " AND ".join(condition_parts) - - # Process filter structure - if isinstance(filter, dict): - if "or" in filter: - # OR logic: at least one condition must match - or_conditions = [] - for condition in filter["or"]: - if isinstance(condition, dict): - condition_str = build_filter_condition(condition) - if condition_str: - or_conditions.append(f"({condition_str})") - if or_conditions: - where_clauses.append(f"({' OR '.join(or_conditions)})") - - elif "and" in filter: - # AND logic: all conditions must match - for condition in filter["and"]: - if isinstance(condition, dict): - condition_str = build_filter_condition(condition) - if condition_str: - where_clauses.append(f"({condition_str})") + # Build filter conditions using common method + filter_conditions = self._build_filter_conditions_sql(filter) + where_clauses.extend(filter_conditions) where_clause = f"WHERE {' AND '.join(where_clauses)}" if where_clauses else "" @@ -1663,11 +1545,10 @@ def build_filter_condition(condition_dict: dict) -> str: params = [vector] logger.debug(f"search_by_embedding query: {query}") logger.debug(f"search_by_embedding params: {params}") - + # Format SQL query for better console display (prevent truncation) print("=== SQL Query ===") - #print(query) - + # print(query) # Split query by lines and wrap long lines to prevent terminal truncation query_lines = query.strip().split('\n') @@ -1786,19 +1667,13 @@ def get_by_metadata( else: raise ValueError(f"Unsupported operator: {op}") - # Build user_name filter with knowledgebase_ids support (OR relationship) - user_name_conditions = [] - if user_name: - escaped_user_name = user_name.replace("'", "''") - user_name_conditions.append(f"n.user_name = '{escaped_user_name}'") - - # Add knowledgebase_ids conditions (checking user_name field in the data) - if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: - for kb_id in knowledgebase_ids: - if isinstance(kb_id, str): - escaped_kb_id = kb_id.replace("'", "''") - user_name_conditions.append(f"n.user_name = '{escaped_kb_id}'") - + # Build user_name filter with knowledgebase_ids support (OR relationship) using common method + user_name_conditions = self._build_user_name_and_kb_ids_conditions_cypher( + user_name=user_name, + knowledgebase_ids=knowledgebase_ids, + default_user_name=self._get_config_value("user_name"), + ) + # Add user_name WHERE clause if user_name_conditions: if len(user_name_conditions) == 1: @@ -1806,93 +1681,8 @@ def get_by_metadata( else: where_conditions.append(f"({' OR '.join(user_name_conditions)})") - filter_where_clause = "" - filter = self.parse_filter(filter) - if filter: - - def escape_cypher_string(value: str) -> str: - return value.replace("'", "\\'") - - def build_cypher_filter_condition(condition_dict: dict) -> str: - """Build a Cypher WHERE condition for a single filter item. - - Args: - condition_dict: A dict like {"id": "xxx"} or {"info.B": "xxx"} or {"created_at": {"gt": "2025-11-01"}} - - Returns: - Cypher condition string - """ - condition_parts = [] - for key, value in condition_dict.items(): - # Check if value is a dict with comparison operators (gt, lt, gte, lte) - if isinstance(value, dict): - # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) - for op, op_value in value.items(): - if op in ("gt", "lt", "gte", "lte"): - # Map operator to Cypher operator - cypher_op_map = { - "gt": ">", - "lt": "<", - "gte": ">=", - "lte": "<=" - } - cypher_op = cypher_op_map[op] - - # Check if key starts with "info." prefix (for nested fields like info.A, info.B) - # For direct properties like "created_at", this condition will be False - if key.startswith("info."): - # Nested field access: n.info.field_name - info_field = key[5:] # Remove "info." prefix - if isinstance(op_value, str): - escaped_value = escape_cypher_string(op_value) - condition_parts.append(f"n.info.{info_field} {cypher_op} '{escaped_value}'") - else: - condition_parts.append(f"n.info.{info_field} {cypher_op} {op_value}") - else: - # Direct property access (e.g., "created_at" is directly in n, not in n.info) - # This handles fields like created_at, updated_at, etc. that are at the top level - if isinstance(op_value, str): - escaped_value = escape_cypher_string(op_value) - condition_parts.append(f"n.{key} {cypher_op} '{escaped_value}'") - else: - condition_parts.append(f"n.{key} {cypher_op} {op_value}") - # Check if key starts with "info." prefix (for simple equality) - elif key.startswith("info."): - info_field = key[5:] - if isinstance(value, str): - escaped_value = escape_cypher_string(value) - condition_parts.append(f"n.info.{info_field} = '{escaped_value}'") - else: - condition_parts.append(f"n.info.{info_field} = {value}") - else: - # Direct property access (simple equality) - if isinstance(value, str): - escaped_value = escape_cypher_string(value) - condition_parts.append(f"n.{key} = '{escaped_value}'") - else: - condition_parts.append(f"n.{key} = {value}") - return " AND ".join(condition_parts) - - if isinstance(filter, dict): - if "or" in filter: - or_conditions = [] - for condition in filter["or"]: - if isinstance(condition, dict): - condition_str = build_cypher_filter_condition(condition) - if condition_str: - or_conditions.append(f"({condition_str})") - if or_conditions: - filter_where_clause = " AND " + f"({' OR '.join(or_conditions)})" - - elif "and" in filter: - and_conditions = [] - for condition in filter["and"]: - if isinstance(condition, dict): - condition_str = build_cypher_filter_condition(condition) - if condition_str: - and_conditions.append(f"({condition_str})") - if and_conditions: - filter_where_clause = " AND " + " AND ".join(and_conditions) + # Build filter conditions using common method + filter_where_clause = self._build_filter_conditions_cypher(filter) where_str = " AND ".join(where_conditions) + filter_where_clause @@ -2339,18 +2129,13 @@ def get_all_memory_items( if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"}: raise ValueError(f"Unsupported memory type scope: {scope}") - # Build user_name filter with knowledgebase_ids support (OR relationship) - user_name_conditions = [] - if user_name: - user_name_conditions.append(f"n.user_name = '{user_name}'") - - # Add knowledgebase_ids conditions (checking user_name field in the data) - if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: - for kb_id in knowledgebase_ids: - if isinstance(kb_id, str): - escaped_kb_id = kb_id.replace("'", "\\'") - user_name_conditions.append(f"n.user_name = '{escaped_kb_id}'") - + # Build user_name filter with knowledgebase_ids support (OR relationship) using common method + user_name_conditions = self._build_user_name_and_kb_ids_conditions_cypher( + user_name=user_name, + knowledgebase_ids=knowledgebase_ids, + default_user_name=self._get_config_value("user_name"), + ) + # Build user_name WHERE clause if user_name_conditions: if len(user_name_conditions) == 1: @@ -2360,94 +2145,8 @@ def get_all_memory_items( else: user_name_where = "" - filter_where_clause = "" - filter = self.parse_filter(filter) - if filter: - - def escape_cypher_string(value: str) -> str: - """Escape single quotes in Cypher string.""" - return value.replace("'", "\\'") - - def build_cypher_filter_condition(condition_dict: dict) -> str: - """Build a Cypher WHERE condition for a single filter item. - - Args: - condition_dict: A dict like {"id": "xxx"} or {"info.B": "xxx"} or {"created_at": {"gt": "2025-11-01"}} - - Returns: - Cypher condition string - """ - condition_parts = [] - for key, value in condition_dict.items(): - # Check if value is a dict with comparison operators (gt, lt, gte, lte) - if isinstance(value, dict): - # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) - for op, op_value in value.items(): - if op in ("gt", "lt", "gte", "lte"): - # Map operator to Cypher operator - cypher_op_map = { - "gt": ">", - "lt": "<", - "gte": ">=", - "lte": "<=" - } - cypher_op = cypher_op_map[op] - - # Check if key starts with "info." prefix (for nested fields like info.A, info.B) - # For direct properties like "created_at", this condition will be False - if key.startswith("info."): - # Nested field access: n.info.field_name - info_field = key[5:] # Remove "info." prefix - if isinstance(op_value, str): - escaped_value = escape_cypher_string(op_value) - condition_parts.append(f"n.info.{info_field} {cypher_op} '{escaped_value}'") - else: - condition_parts.append(f"n.info.{info_field} {cypher_op} {op_value}") - else: - # Direct property access (e.g., "created_at" is directly in n, not in n.info) - # This handles fields like created_at, updated_at, etc. that are at the top level - if isinstance(op_value, str): - escaped_value = escape_cypher_string(op_value) - condition_parts.append(f"n.{key} {cypher_op} '{escaped_value}'") - else: - condition_parts.append(f"n.{key} {cypher_op} {op_value}") - # Check if key starts with "info." prefix (for simple equality) - elif key.startswith("info."): - info_field = key[5:] - if isinstance(value, str): - escaped_value = escape_cypher_string(value) - condition_parts.append(f"n.info.{info_field} = '{escaped_value}'") - else: - condition_parts.append(f"n.info.{info_field} = {value}") - else: - # Direct property access (simple equality) - if isinstance(value, str): - escaped_value = escape_cypher_string(value) - condition_parts.append(f"n.{key} = '{escaped_value}'") - else: - condition_parts.append(f"n.{key} = {value}") - return " AND ".join(condition_parts) - - if isinstance(filter, dict): - if "or" in filter: - or_conditions = [] - for condition in filter["or"]: - if isinstance(condition, dict): - condition_str = build_cypher_filter_condition(condition) - if condition_str: - or_conditions.append(f"({condition_str})") - if or_conditions: - filter_where_clause = " AND " + f"({' OR '.join(or_conditions)})" - - elif "and" in filter: - and_conditions = [] - for condition in filter["and"]: - if isinstance(condition, dict): - condition_str = build_cypher_filter_condition(condition) - if condition_str: - and_conditions.append(f"({condition_str})") - if and_conditions: - filter_where_clause = " AND " + " AND ".join(and_conditions) + # Build filter conditions using common method + filter_where_clause = self._build_filter_conditions_cypher(filter) # Use cypher query to retrieve memory items if include_embedding: @@ -3501,7 +3200,282 @@ def format_param_value(self, value: str | None) -> str: # Add double quotes return f'"{value}"' - def parse_filter(self, filter_dict: dict | None = None,): + def _build_user_name_and_kb_ids_conditions_cypher( + self, + user_name: str | None, + knowledgebase_ids: list | None, + default_user_name: str | None = None, + ) -> list[str]: + """ + Build user_name and knowledgebase_ids conditions for Cypher queries. + + Args: + user_name: User name for filtering + knowledgebase_ids: List of knowledgebase IDs + default_user_name: Default user name from config if user_name is None + + Returns: + List of condition strings (will be joined with OR) + """ + user_name_conditions = [] + effective_user_name = user_name if user_name else default_user_name + + if effective_user_name: + escaped_user_name = effective_user_name.replace("'", "''") + user_name_conditions.append(f"n.user_name = '{escaped_user_name}'") + + # Add knowledgebase_ids conditions (checking user_name field in the data) + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for kb_id in knowledgebase_ids: + if isinstance(kb_id, str): + escaped_kb_id = kb_id.replace("'", "''") + user_name_conditions.append(f"n.user_name = '{escaped_kb_id}'") + + return user_name_conditions + + def _build_user_name_and_kb_ids_conditions_sql( + self, + user_name: str | None, + knowledgebase_ids: list | None, + default_user_name: str | None = None, + ) -> list[str]: + """ + Build user_name and knowledgebase_ids conditions for SQL queries. + + Args: + user_name: User name for filtering + knowledgebase_ids: List of knowledgebase IDs + default_user_name: Default user name from config if user_name is None + + Returns: + List of condition strings (will be joined with OR) + """ + user_name_conditions = [] + effective_user_name = user_name if user_name else default_user_name + + if effective_user_name: + user_name_conditions.append( + f"ag_catalog.agtype_access_operator(properties, '\"user_name\"'::agtype) = '\"{effective_user_name}\"'::agtype" + ) + + # Add knowledgebase_ids conditions (checking user_name field in the data) + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for kb_id in knowledgebase_ids: + if isinstance(kb_id, str): + user_name_conditions.append( + f"ag_catalog.agtype_access_operator(properties, '\"user_name\"'::agtype) = '\"{kb_id}\"'::agtype" + ) + + return user_name_conditions + + def _build_filter_conditions_cypher( + self, + filter: dict | None, + ) -> str: + """ + Build filter conditions for Cypher queries. + + Args: + filter: Filter dictionary with "or" or "and" logic + + Returns: + Filter WHERE clause string (empty string if no filter) + """ + filter_where_clause = "" + filter = self.parse_filter(filter) + if filter: + + def escape_cypher_string(value: str) -> str: + return value.replace("'", "\\'") + + def build_cypher_filter_condition(condition_dict: dict) -> str: + """Build a Cypher WHERE condition for a single filter item.""" + condition_parts = [] + for key, value in condition_dict.items(): + # Check if value is a dict with comparison operators (gt, lt, gte, lte) + if isinstance(value, dict): + # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte"): + # Map operator to Cypher operator + cypher_op_map = { + "gt": ">", + "lt": "<", + "gte": ">=", + "lte": "<=" + } + cypher_op = cypher_op_map[op] + + # Check if key starts with "info." prefix (for nested fields like info.A, info.B) + if key.startswith("info."): + # Nested field access: n.info.field_name + info_field = key[5:] # Remove "info." prefix + if isinstance(op_value, str): + escaped_value = escape_cypher_string(op_value) + condition_parts.append(f"n.info.{info_field} {cypher_op} '{escaped_value}'") + else: + condition_parts.append(f"n.info.{info_field} {cypher_op} {op_value}") + else: + # Direct property access (e.g., "created_at" is directly in n, not in n.info) + if isinstance(op_value, str): + escaped_value = escape_cypher_string(op_value) + condition_parts.append(f"n.{key} {cypher_op} '{escaped_value}'") + else: + condition_parts.append(f"n.{key} {cypher_op} {op_value}") + # Check if key starts with "info." prefix (for simple equality) + elif key.startswith("info."): + info_field = key[5:] + if isinstance(value, str): + escaped_value = escape_cypher_string(value) + condition_parts.append(f"n.info.{info_field} = '{escaped_value}'") + else: + condition_parts.append(f"n.info.{info_field} = {value}") + else: + # Direct property access (simple equality) + if isinstance(value, str): + escaped_value = escape_cypher_string(value) + condition_parts.append(f"n.{key} = '{escaped_value}'") + else: + condition_parts.append(f"n.{key} = {value}") + return " AND ".join(condition_parts) + + if isinstance(filter, dict): + if "or" in filter: + or_conditions = [] + for condition in filter["or"]: + if isinstance(condition, dict): + condition_str = build_cypher_filter_condition(condition) + if condition_str: + or_conditions.append(f"({condition_str})") + if or_conditions: + filter_where_clause = " AND " + f"({' OR '.join(or_conditions)})" + + elif "and" in filter: + and_conditions = [] + for condition in filter["and"]: + if isinstance(condition, dict): + condition_str = build_cypher_filter_condition(condition) + if condition_str: + and_conditions.append(f"({condition_str})") + if and_conditions: + filter_where_clause = " AND " + " AND ".join(and_conditions) + + return filter_where_clause + + def _build_filter_conditions_sql( + self, + filter: dict | None, + ) -> list[str]: + """ + Build filter conditions for SQL queries. + + Args: + filter: Filter dictionary with "or" or "and" logic + + Returns: + List of filter WHERE clause strings (empty list if no filter) + """ + filter_conditions = [] + filter = self.parse_filter(filter) + if filter: + # Helper function to escape string value for SQL + def escape_sql_string(value: str) -> str: + """Escape single quotes in SQL string.""" + return value.replace("'", "''") + + # Helper function to build a single filter condition + def build_filter_condition(condition_dict: dict) -> str: + """Build a WHERE condition for a single filter item.""" + condition_parts = [] + for key, value in condition_dict.items(): + # Check if value is a dict with comparison operators (gt, lt, gte, lte) + if isinstance(value, dict): + # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte"): + # Map operator to SQL operator + sql_op_map = { + "gt": ">", + "lt": "<", + "gte": ">=", + "lte": "<=" + } + sql_op = sql_op_map[op] + + # Check if key starts with "info." prefix (for nested fields like info.A, info.B) + if key.startswith("info."): + # Nested field access: properties->'info'->'field_name' + info_field = key[5:] # Remove "info." prefix + if isinstance(op_value, str): + escaped_value = escape_sql_string(op_value) + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) {sql_op} '\"{escaped_value}\"'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) {sql_op} {op_value}::agtype" + ) + else: + # Direct property access (e.g., "created_at" is directly in properties, not in properties.info) + if isinstance(op_value, str): + escaped_value = escape_sql_string(op_value) + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) {sql_op} '\"{escaped_value}\"'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) {sql_op} {op_value}::agtype" + ) + # Check if key starts with "info." prefix (for simple equality) + elif key.startswith("info."): + # Extract the field name after "info." + info_field = key[5:] # Remove "info." prefix (5 characters) + if isinstance(value, str): + escaped_value = escape_sql_string(value) + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '\"{escaped_value}\"'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '\"{value}\"'::agtype" + ) + else: + # Direct property access (simple equality) + if isinstance(value, str): + escaped_value = escape_sql_string(value) + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = '\"{escaped_value}\"'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = {value}::agtype" + ) + return " AND ".join(condition_parts) + + # Process filter structure + if isinstance(filter, dict): + if "or" in filter: + # OR logic: at least one condition must match + or_conditions = [] + for condition in filter["or"]: + if isinstance(condition, dict): + condition_str = build_filter_condition(condition) + if condition_str: + or_conditions.append(f"({condition_str})") + if or_conditions: + filter_conditions.append(f"({' OR '.join(or_conditions)})") + + elif "and" in filter: + # AND logic: all conditions must match + for condition in filter["and"]: + if isinstance(condition, dict): + condition_str = build_filter_condition(condition) + if condition_str: + filter_conditions.append(f"({condition_str})") + + return filter_conditions + + def parse_filter(self, filter_dict: dict | None = None, ): if filter_dict is None: return None full_fields = { From 79ea33c3923cf759324037631ae390a46284c18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Thu, 27 Nov 2025 01:22:56 +0800 Subject: [PATCH 29/36] add common neo4j filter conditions --- examples/basic_modules/neo4j_search.py | 6 +- src/memos/graph_dbs/neo4j.py | 490 ++++++++++--------------- 2 files changed, 200 insertions(+), 296 deletions(-) diff --git a/examples/basic_modules/neo4j_search.py b/examples/basic_modules/neo4j_search.py index c934143dd..4712e7d4c 100644 --- a/examples/basic_modules/neo4j_search.py +++ b/examples/basic_modules/neo4j_search.py @@ -1093,9 +1093,9 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_exam filter_example = { "and": [ {"id": "cfe42bd6-ee78-4f6f-b997-8baa0ea957e1"}, - # {"A": "新疆乌鲁木齐市"}, - # {"created_at": {"gt": "2025-09-19"}}, - # {"created_at": {"lt": "2025-11-20"}} + {"A": "中国广西"}, + {"created_at": {"gt": "2025-09-19"}}, + {"created_at": {"lt": "2025-11-26"}} ] } knowledgebase_ids = ["adimin1", "adimin2"] diff --git a/src/memos/graph_dbs/neo4j.py b/src/memos/graph_dbs/neo4j.py index fbdf92de6..d6dc477cd 100644 --- a/src/memos/graph_dbs/neo4j.py +++ b/src/memos/graph_dbs/neo4j.py @@ -731,19 +731,15 @@ def search_by_embedding( where_clauses.append("node.memory_type = $scope") if status: where_clauses.append("node.status = $status") - - # Build user_name filter with knowledgebase_ids support (OR relationship) - user_name_conditions = [] - if not self.config.use_multi_db and (self.config.user_name or user_name): - user_name_conditions.append("node.user_name = $user_name") - - # Add knowledgebase_ids conditions (checking user_name field in the data) - if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: - for idx, kb_id in enumerate(knowledgebase_ids): - if isinstance(kb_id, str): - param_name = f"kb_id_{idx}" - user_name_conditions.append(f"node.user_name = ${param_name}") - + + # Build user_name filter with knowledgebase_ids support (OR relationship) using common method + user_name_conditions, user_name_params = self._build_user_name_and_kb_ids_conditions_cypher( + user_name=user_name, + knowledgebase_ids=knowledgebase_ids, + default_user_name=self.config.user_name, + node_alias="node", + ) + # Add user_name WHERE clause if user_name_conditions: if len(user_name_conditions) == 1: @@ -757,77 +753,13 @@ def search_by_embedding( param_name = f"filter_{key}" where_clauses.append(f"node.{key} = ${param_name}") - filter_params = {} - if filter: - def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: - """Build a WHERE condition for a single filter item. - - Args: - condition_dict: A dict like {"id": "xxx"} or {"A": "xxx"} or {"created_at": {"gt": "2025-11-01"}} - param_counter: List to track parameter counter for unique param names - - Returns: - Tuple of (condition_string, parameters_dict) - """ - condition_parts = [] - params = {} - - for key, value in condition_dict.items(): - # Check if value is a dict with comparison operators (gt, lt, gte, lte) - if isinstance(value, dict): - # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) - for op, op_value in value.items(): - if op in ("gt", "lt", "gte", "lte"): - # Map operator to Cypher operator - cypher_op_map = { - "gt": ">", - "lt": "<", - "gte": ">=", - "lte": "<=" - } - cypher_op = cypher_op_map[op] - - # All fields are stored as flat properties in Neo4j - param_name = f"filter_flat_{key}_{op}_{param_counter[0]}" - param_counter[0] += 1 - params[param_name] = op_value - - # Check if field is a date field (created_at, updated_at, etc.) - # Use datetime() function for date comparisons - if key in ("created_at", "updated_at") or key.endswith("_at"): - condition_parts.append(f"node.{key} {cypher_op} datetime(${param_name})") - else: - condition_parts.append(f"node.{key} {cypher_op} ${param_name}") - else: - # All fields are stored as flat properties in Neo4j (simple equality) - param_name = f"filter_flat_{key}_{param_counter[0]}" - param_counter[0] += 1 - params[param_name] = value - condition_parts.append(f"node.{key} = ${param_name}") - - return " AND ".join(condition_parts), params - - param_counter = [0] - - if isinstance(filter, dict): - if "or" in filter: - or_conditions = [] - for condition in filter["or"]: - if isinstance(condition, dict): - condition_str, params = build_filter_condition(condition, param_counter) - if condition_str: - or_conditions.append(f"({condition_str})") - filter_params.update(params) - if or_conditions: - where_clauses.append(f"({' OR '.join(or_conditions)})") - - elif "and" in filter: - for condition in filter["and"]: - if isinstance(condition, dict): - condition_str, params = build_filter_condition(condition, param_counter) - if condition_str: - where_clauses.append(f"({condition_str})") - filter_params.update(params) + # Build filter conditions using common method + filter_conditions, filter_params = self._build_filter_conditions_cypher( + filter=filter, + param_counter_start=0, + node_alias="node", + ) + where_clauses.extend(filter_conditions) where_clause = "" if where_clauses: @@ -841,25 +773,18 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s """ parameters = {"embedding": vector, "k": top_k} - print("11111119999query:", query) + if scope: parameters["scope"] = scope if status: parameters["status"] = status - - # Add user_name parameter - if not self.config.use_multi_db and (self.config.user_name or user_name): - if kwargs.get("cube_name"): - parameters["user_name"] = kwargs["cube_name"] - else: - parameters["user_name"] = user_name - - # Add knowledgebase_ids parameters - if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: - for idx, kb_id in enumerate(knowledgebase_ids): - if isinstance(kb_id, str): - param_name = f"kb_id_{idx}" - parameters[param_name] = kb_id + + # Add user_name and knowledgebase_ids parameters using common method + parameters.update(user_name_params) + + # Handle cube_name override for user_name + if kwargs.get("cube_name"): + parameters["user_name"] = kwargs["cube_name"] if search_filter: for key, value in search_filter.items(): @@ -870,7 +795,8 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s if filter_params: parameters.update(filter_params) - print("1111111filter_params:", filter_params) + logger.info(f"[search_by_embedding] query: {query},parameters: {parameters}") + print(f"[search_by_embedding] query: {query},parameters: {parameters}") with self.driver.session(database=self.db_name) as session: result = session.run(query, parameters) records = [{"id": record["id"], "score": record["score"]} for record in result] @@ -942,18 +868,14 @@ def get_by_metadata( else: raise ValueError(f"Unsupported operator: {op}") - # Build user_name filter with knowledgebase_ids support (OR relationship) - user_name_conditions = [] - if not self.config.use_multi_db and (self.config.user_name or user_name): - user_name_conditions.append("n.user_name = $user_name") - - # Add knowledgebase_ids conditions (checking user_name field in the data) - if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: - for idx, kb_id in enumerate(knowledgebase_ids): - if isinstance(kb_id, str): - param_name = f"kb_id_{idx}" - user_name_conditions.append(f"n.user_name = ${param_name}") - + # Build user_name filter with knowledgebase_ids support (OR relationship) using common method + user_name_conditions, user_name_params = self._build_user_name_and_kb_ids_conditions_cypher( + user_name=user_name, + knowledgebase_ids=knowledgebase_ids, + default_user_name=self.config.user_name, + node_alias="n", + ) + # Add user_name WHERE clause if user_name_conditions: if len(user_name_conditions) == 1: @@ -961,83 +883,13 @@ def get_by_metadata( else: where_clauses.append(f"({' OR '.join(user_name_conditions)})") - # Add filter conditions (supports "or" and "and" logic) - filter_params = {} - if filter: - # Helper function to build a single filter condition - def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: - """Build a WHERE condition for a single filter item. - - Args: - condition_dict: A dict like {"id": "xxx"} or {"A": "xxx"} or {"created_at": {"gt": "2025-11-01"}} - param_counter: List to track parameter counter for unique param names - - Returns: - Tuple of (condition_string, parameters_dict) - """ - condition_parts = [] - filter_params_inner = {} - - for key, value in condition_dict.items(): - # Check if value is a dict with comparison operators (gt, lt, gte, lte) - if isinstance(value, dict): - # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) - for op, op_value in value.items(): - if op in ("gt", "lt", "gte", "lte"): - # Map operator to Cypher operator - cypher_op_map = { - "gt": ">", - "lt": "<", - "gte": ">=", - "lte": "<=" - } - cypher_op = cypher_op_map[op] - - # All fields are stored as flat properties in Neo4j - param_name = f"filter_meta_{key}_{op}_{param_counter[0]}" - param_counter[0] += 1 - filter_params_inner[param_name] = op_value - - # Check if field is a date field (created_at, updated_at, etc.) - # Use datetime() function for date comparisons - if key in ("created_at", "updated_at") or key.endswith("_at"): - condition_parts.append(f"n.{key} {cypher_op} datetime(${param_name})") - else: - condition_parts.append(f"n.{key} {cypher_op} ${param_name}") - else: - # All fields are stored as flat properties in Neo4j (simple equality) - param_name = f"filter_meta_{key}_{param_counter[0]}" - param_counter[0] += 1 - filter_params_inner[param_name] = value - condition_parts.append(f"n.{key} = ${param_name}") - - return " AND ".join(condition_parts), filter_params_inner - - # Process filter structure - param_counter = [ - len(filters)] # Use list to allow modification in nested function, start from len(filters) to avoid conflicts - - if isinstance(filter, dict): - if "or" in filter: - # OR logic: at least one condition must match - or_conditions = [] - for condition in filter["or"]: - if isinstance(condition, dict): - condition_str, filter_params_inner = build_filter_condition(condition, param_counter) - if condition_str: - or_conditions.append(f"({condition_str})") - filter_params.update(filter_params_inner) - if or_conditions: - where_clauses.append(f"({' OR '.join(or_conditions)})") - - elif "and" in filter: - # AND logic: all conditions must match - for condition in filter["and"]: - if isinstance(condition, dict): - condition_str, filter_params_inner = build_filter_condition(condition, param_counter) - if condition_str: - where_clauses.append(f"({condition_str})") - filter_params.update(filter_params_inner) + # Build filter conditions using common method + filter_conditions, filter_params = self._build_filter_conditions_cypher( + filter=filter, + param_counter_start=len(filters), # Start from len(filters) to avoid conflicts + node_alias="n", + ) + where_clauses.extend(filter_conditions) where_str = " AND ".join(where_clauses) if where_clauses else "" if where_str: @@ -1045,17 +897,9 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s else: query = "MATCH (n:Memory) RETURN n.id AS id" - # Add user_name parameter - if not self.config.use_multi_db and (self.config.user_name or user_name): - params["user_name"] = user_name - - # Add knowledgebase_ids parameters - if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: - for idx, kb_id in enumerate(knowledgebase_ids): - if isinstance(kb_id, str): - param_name = f"kb_id_{idx}" - params[param_name] = kb_id - + # Add user_name and knowledgebase_ids parameters using common method + params.update(user_name_params) + # Merge filter parameters if filter_params: params.update(filter_params) @@ -1275,18 +1119,14 @@ def get_all_memory_items(self, scope: str, filter: dict | None = None, knowledge where_clauses = ["n.memory_type = $scope"] params = {"scope": scope} - # Build user_name filter with knowledgebase_ids support (OR relationship) - user_name_conditions = [] - if not self.config.use_multi_db and (self.config.user_name or user_name): - user_name_conditions.append("n.user_name = $user_name") - - # Add knowledgebase_ids conditions (checking user_name field in the data) - if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: - for idx, kb_id in enumerate(knowledgebase_ids): - if isinstance(kb_id, str): - param_name = f"kb_id_{idx}" - user_name_conditions.append(f"n.user_name = ${param_name}") - + # Build user_name filter with knowledgebase_ids support (OR relationship) using common method + user_name_conditions, user_name_params = self._build_user_name_and_kb_ids_conditions_cypher( + user_name=user_name, + knowledgebase_ids=knowledgebase_ids, + default_user_name=self.config.user_name, + node_alias="n", + ) + # Add user_name WHERE clause if user_name_conditions: if len(user_name_conditions) == 1: @@ -1294,91 +1134,20 @@ def get_all_memory_items(self, scope: str, filter: dict | None = None, knowledge else: where_clauses.append(f"({' OR '.join(user_name_conditions)})") - filter_params = {} - if filter: - def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: - """Build a WHERE condition for a single filter item. - - Args: - condition_dict: A dict like {"id": "xxx"} or {"A": "xxx"} or {"created_at": {"gt": "2025-11-01"}} - param_counter: List to track parameter counter for unique param names - - Returns: - Tuple of (condition_string, parameters_dict) - """ - condition_parts = [] - filter_params_inner = {} - - for key, value in condition_dict.items(): - # Check if value is a dict with comparison operators (gt, lt, gte, lte) - if isinstance(value, dict): - # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) - for op, op_value in value.items(): - if op in ("gt", "lt", "gte", "lte"): - # Map operator to Cypher operator - cypher_op_map = { - "gt": ">", - "lt": "<", - "gte": ">=", - "lte": "<=" - } - cypher_op = cypher_op_map[op] - - # All fields are stored as flat properties in Neo4j - param_name = f"filter_flat_{key}_{op}_{param_counter[0]}" - param_counter[0] += 1 - filter_params_inner[param_name] = op_value - - # Check if field is a date field (created_at, updated_at, etc.) - # Use datetime() function for date comparisons - if key in ("created_at", "updated_at") or key.endswith("_at"): - condition_parts.append(f"n.{key} {cypher_op} datetime(${param_name})") - else: - condition_parts.append(f"n.{key} {cypher_op} ${param_name}") - else: - # All fields are stored as flat properties in Neo4j (simple equality) - param_name = f"filter_flat_{key}_{param_counter[0]}" - param_counter[0] += 1 - filter_params_inner[param_name] = value - condition_parts.append(f"n.{key} = ${param_name}") - - return " AND ".join(condition_parts), filter_params_inner - - param_counter = [0] - - if isinstance(filter, dict): - if "or" in filter: - or_conditions = [] - for condition in filter["or"]: - if isinstance(condition, dict): - condition_str, filter_params_inner = build_filter_condition(condition, param_counter) - if condition_str: - or_conditions.append(f"({condition_str})") - filter_params.update(filter_params_inner) - if or_conditions: - where_clauses.append(f"({' OR '.join(or_conditions)})") - - elif "and" in filter: - for condition in filter["and"]: - if isinstance(condition, dict): - condition_str, filter_params_inner = build_filter_condition(condition, param_counter) - if condition_str: - where_clauses.append(f"({condition_str})") - filter_params.update(filter_params_inner) + # Build filter conditions using common method + filter_conditions, filter_params = self._build_filter_conditions_cypher( + filter=filter, + param_counter_start=0, + node_alias="n", + ) + where_clauses.extend(filter_conditions) where_clause = "WHERE " + " AND ".join(where_clauses) - # Add user_name parameter - if not self.config.use_multi_db and (self.config.user_name or user_name): - params["user_name"] = user_name - - # Add knowledgebase_ids parameters - if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: - for idx, kb_id in enumerate(knowledgebase_ids): - if isinstance(kb_id, str): - param_name = f"kb_id_{idx}" - params[param_name] = kb_id + # Add user_name and knowledgebase_ids parameters using common method + params.update(user_name_params) + # Add filter parameters if filter_params: params.update(filter_params) @@ -1546,6 +1315,141 @@ def _index_exists(self, index_name: str) -> bool: return True return False + def _build_user_name_and_kb_ids_conditions_cypher( + self, + user_name: str | None, + knowledgebase_ids: list[str] | None, + default_user_name: str | None = None, + node_alias: str = "node", + ) -> tuple[list[str], dict[str, Any]]: + """ + Build user_name and knowledgebase_ids conditions for Cypher queries. + + Args: + user_name: User name for filtering + knowledgebase_ids: List of knowledgebase IDs + default_user_name: Default user name from config if user_name is None + node_alias: Node alias in Cypher query (default: "node" or "n") + + Returns: + Tuple of (condition_strings_list, parameters_dict) + """ + user_name_conditions = [] + params = {} + effective_user_name = user_name if user_name else default_user_name + + # Only add user_name condition if not using multi-db mode + if not self.config.use_multi_db and (self.config.user_name or effective_user_name): + user_name_conditions.append(f"{node_alias}.user_name = $user_name") + params["user_name"] = effective_user_name + + # Add knowledgebase_ids conditions (checking user_name field in the data) + if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: + for idx, kb_id in enumerate(knowledgebase_ids): + if isinstance(kb_id, str): + param_name = f"kb_id_{idx}" + user_name_conditions.append(f"{node_alias}.user_name = ${param_name}") + params[param_name] = kb_id + + return user_name_conditions, params + + def _build_filter_conditions_cypher( + self, + filter: dict | None, + param_counter_start: int = 0, + node_alias: str = "node", + ) -> tuple[list[str], dict[str, Any]]: + """ + Build filter conditions for Cypher queries. + + Args: + filter: Filter dictionary with "or" or "and" logic + param_counter_start: Starting value for parameter counter (to avoid conflicts) + node_alias: Node alias in Cypher query (default: "node" or "n") + + Returns: + Tuple of (condition_strings_list, parameters_dict) + """ + filter_conditions = [] + filter_params = {} + + if not filter: + return filter_conditions, filter_params + + def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: + """Build a WHERE condition for a single filter item. + + Args: + condition_dict: A dict like {"id": "xxx"} or {"A": "xxx"} or {"created_at": {"gt": "2025-11-01"}} + param_counter: List to track parameter counter for unique param names + + Returns: + Tuple of (condition_string, parameters_dict) + """ + condition_parts = [] + params = {} + + for key, value in condition_dict.items(): + # Check if value is a dict with comparison operators (gt, lt, gte, lte) + if isinstance(value, dict): + # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte"): + # Map operator to Cypher operator + cypher_op_map = { + "gt": ">", + "lt": "<", + "gte": ">=", + "lte": "<=" + } + cypher_op = cypher_op_map[op] + + # All fields are stored as flat properties in Neo4j + param_name = f"filter_{key}_{op}_{param_counter[0]}" + param_counter[0] += 1 + params[param_name] = op_value + + # Check if field is a date field (created_at, updated_at, etc.) + # Use datetime() function for date comparisons + if key in ("created_at", "updated_at") or key.endswith("_at"): + condition_parts.append(f"{node_alias}.{key} {cypher_op} datetime(${param_name})") + else: + condition_parts.append(f"{node_alias}.{key} {cypher_op} ${param_name}") + else: + # All fields are stored as flat properties in Neo4j (simple equality) + param_name = f"filter_{key}_{param_counter[0]}" + param_counter[0] += 1 + params[param_name] = value + condition_parts.append(f"{node_alias}.{key} = ${param_name}") + + return " AND ".join(condition_parts), params + + param_counter = [param_counter_start] + + if isinstance(filter, dict): + if "or" in filter: + # OR logic: at least one condition must match + or_conditions = [] + for condition in filter["or"]: + if isinstance(condition, dict): + condition_str, params = build_filter_condition(condition, param_counter) + if condition_str: + or_conditions.append(f"({condition_str})") + filter_params.update(params) + if or_conditions: + filter_conditions.append(f"({' OR '.join(or_conditions)})") + + elif "and" in filter: + # AND logic: all conditions must match + for condition in filter["and"]: + if isinstance(condition, dict): + condition_str, params = build_filter_condition(condition, param_counter) + if condition_str: + filter_conditions.append(f"({condition_str})") + filter_params.update(params) + + return filter_conditions, filter_params + def _parse_node(self, node_data: dict[str, Any]) -> dict[str, Any]: node = node_data.copy() From d465cd7ebbb4f58c82c8d0b004d202885d30b3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Thu, 27 Nov 2025 18:37:45 +0800 Subject: [PATCH 30/36] add like neo4j filter --- examples/basic_modules/neo4j_search.py | 17 +- examples/basic_modules/polardb_search.py | 30 +- src/memos/graph_dbs/neo4j.py | 19 + src/memos/graph_dbs/polardb.py | 475 +++++++++++++++++------ 4 files changed, 408 insertions(+), 133 deletions(-) diff --git a/examples/basic_modules/neo4j_search.py b/examples/basic_modules/neo4j_search.py index 4712e7d4c..fae80a7b1 100644 --- a/examples/basic_modules/neo4j_search.py +++ b/examples/basic_modules/neo4j_search.py @@ -1091,11 +1091,16 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_exam # Example filter for testing - common filter used by multiple tests filter_example = { + # "and": [ + # {"id": "cfe42bd6-ee78-4f6f-b997-8baa0ea957e1"}, + # {"A": "中国广西"}, + # {"created_at": {"gt": "2025-09-19"}}, + # {"created_at": {"lt": "2025-11-26"}}, + # {"tags": {"contains": "mode:fast"}}, + # {"user_name": {"like": "1744"}}, + # ] "and": [ - {"id": "cfe42bd6-ee78-4f6f-b997-8baa0ea957e1"}, - {"A": "中国广西"}, - {"created_at": {"gt": "2025-09-19"}}, - {"created_at": {"lt": "2025-11-26"}} + {"created_at": {"gt": "2025-11-26"}} ] } knowledgebase_ids = ["adimin1", "adimin2"] @@ -1111,5 +1116,5 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_exam user_name = "memosfeebbc2bd1744d7bb5b5ec57f38e828d" scope = "LongTermMemory" # test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) - # test_get_all_memory_items(graph, scope, user_name, filter_example, knowledgebase_ids) - test_get_by_metadata(graph, filters_example, user_name, filter_example, knowledgebase_ids) + test_get_all_memory_items(graph, scope, user_name, filter_example, knowledgebase_ids) + # test_get_by_metadata(graph, filters_example, user_name, filter_example, knowledgebase_ids) diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py index 5e03a4c6c..111160a3e 100644 --- a/examples/basic_modules/polardb_search.py +++ b/examples/basic_modules/polardb_search.py @@ -1272,17 +1272,28 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt ] # Example filter for testing - common filter used by multiple tests filter_example = { + # "and": [ + # {"id": "45a4f936-2182-44c6-8c4f-4a9476941e51"}, + # { + # "A": "中国广西" + # }, + # { + # "B": "狗肉" + # }, + # {"created_at":{"gt":"2025-09-19"}}, + # {"created_at": {"lt": "2025-11-26"}}, + # {"tags": {"contains": "test:zdy"}}, + # {"user_name": {"like": "828"}}, + # {"memory_type": {"like": "WorkingMemory"}}, + # ] "and": [ {"id": "45a4f936-2182-44c6-8c4f-4a9476941e51"}, - { - "A": "中国广西" - }, - { - "B": "狗肉" - }, - {"created_at":{"gt":"2025-09-19"}}, + {"A": "中国广西"}, + {"created_at": {"gt": "2025-09-19"}}, {"created_at": {"lt": "2025-11-26"}}, - # {"tags": {"=": "mode:fast"}} + {"tags": {"contains": "test:zdy"}}, + {"user_name": {"like": "828"}}, + {"memory_type": {"like": "WorkingMemory"}}, ] } @@ -1298,8 +1309,9 @@ def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Opt knowledgebase_ids = ["memosfeebbc2bd1744d7bb5b5ec57f38e828d","adimin2"] # Run all tests - uncomment the test you want to run - test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) + # test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) # test_get_all_memory_items(graph, "WorkingMemory", False, user_name, filter_example,knowledgebase_ids) + test_get_by_metadata(graph, filters_example, user_name, filter_example,knowledgebase_ids) # test_get_by_metadata(graph, filters_example, user_name, filter_example,knowledgebase_ids) # Or run all tests diff --git a/src/memos/graph_dbs/neo4j.py b/src/memos/graph_dbs/neo4j.py index d6dc477cd..94545be1a 100644 --- a/src/memos/graph_dbs/neo4j.py +++ b/src/memos/graph_dbs/neo4j.py @@ -1415,6 +1415,25 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s condition_parts.append(f"{node_alias}.{key} {cypher_op} datetime(${param_name})") else: condition_parts.append(f"{node_alias}.{key} {cypher_op} ${param_name}") + elif op == "contains": + # Handle contains operator (for array fields like tags, sources) + param_name = f"filter_{key}_{op}_{param_counter[0]}" + param_counter[0] += 1 + params[param_name] = op_value + + # For array fields, check if element is in array + if key in ("tags", "sources"): + condition_parts.append(f"${param_name} IN {node_alias}.{key}") + else: + # For non-array fields, contains might not be applicable, but we'll treat it as IN for consistency + condition_parts.append(f"${param_name} IN {node_alias}.{key}") + elif op == "like": + # Handle like operator (for fuzzy matching, similar to SQL LIKE '%value%') + # Neo4j uses CONTAINS for string matching + param_name = f"filter_{key}_{op}_{param_counter[0]}" + param_counter[0] += 1 + params[param_name] = op_value + condition_parts.append(f"{node_alias}.{key} CONTAINS ${param_name}") else: # All fields are stored as flat properties in Neo4j (simple equality) param_name = f"filter_{key}_{param_counter[0]}" diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 955085cea..554651f44 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -13,7 +13,6 @@ from memos.log import get_logger from memos.utils import timed - logger = get_logger(__name__) @@ -361,11 +360,11 @@ def _create_graph(self): self._return_connection(conn) def create_index( - self, - label: str = "Memory", - vector_property: str = "embedding", - dimensions: int = 1024, - index_name: str = "memory_vector_index", + self, + label: str = "Memory", + vector_property: str = "embedding", + dimensions: int = 1024, + index_name: str = "memory_vector_index", ) -> None: """ Create indexes for embedding and other fields. @@ -449,7 +448,7 @@ def node_not_exist(self, scope: str, user_name: str | None = None) -> int: @timed def remove_oldest_memory( - self, memory_type: str, keep_latest: int, user_name: str | None = None + self, memory_type: str, keep_latest: int, user_name: str | None = None ) -> None: """ Remove all WorkingMemory nodes except the latest `keep_latest` entries. @@ -687,7 +686,7 @@ def create_edge(self): @timed def add_edge( - self, source_id: str, target_id: str, type: str, user_name: str | None = None + self, source_id: str, target_id: str, type: str, user_name: str | None = None ) -> None: if not source_id or not target_id: logger.warning(f"Edge '{source_id}' and '{target_id}' are both None") @@ -753,7 +752,7 @@ def delete_edge(self, source_id: str, target_id: str, type: str) -> None: @timed def edge_exists_old( - self, source_id: str, target_id: str, type: str = "ANY", direction: str = "OUTGOING" + self, source_id: str, target_id: str, type: str = "ANY", direction: str = "OUTGOING" ) -> bool: """ Check if an edge exists between two nodes. @@ -815,12 +814,12 @@ def edge_exists_old( @timed def edge_exists( - self, - source_id: str, - target_id: str, - type: str = "ANY", - direction: str = "OUTGOING", - user_name: str | None = None, + self, + source_id: str, + target_id: str, + type: str = "ANY", + direction: str = "OUTGOING", + user_name: str | None = None, ) -> bool: """ Check if an edge exists between two nodes. @@ -870,7 +869,7 @@ def edge_exists( @timed def get_node( - self, id: str, include_embedding: bool = False, user_name: str | None = None + self, id: str, include_embedding: bool = False, user_name: str | None = None ) -> dict[str, Any] | None: """ Retrieve a Memory node by its unique ID. @@ -950,7 +949,7 @@ def get_node( @timed def get_nodes( - self, ids: list[str], user_name: str | None = None, **kwargs + self, ids: list[str], user_name: str | None = None, **kwargs ) -> list[dict[str, Any]]: """ Retrieve the metadata and memory of a list of nodes. @@ -1032,7 +1031,7 @@ def get_nodes( @timed def get_edges_old( - self, id: str, type: str = "ANY", direction: str = "ANY" + self, id: str, type: str = "ANY", direction: str = "ANY" ) -> list[dict[str, str]]: """ Get edges connected to a node, with optional type and direction filter. @@ -1125,11 +1124,11 @@ def get_neighbors( @timed def get_neighbors_by_tag_old( - self, - tags: list[str], - exclude_ids: list[str], - top_k: int = 5, - min_overlap: int = 1, + self, + tags: list[str], + exclude_ids: list[str], + top_k: int = 5, + min_overlap: int = 1, ) -> list[dict[str, Any]]: """ Find top-K neighbor nodes with maximum tag overlap. @@ -1226,7 +1225,7 @@ def get_neighbors_by_tag_old( @timed def get_children_with_embeddings( - self, id: str, user_name: str | None = None + self, id: str, user_name: str | None = None ) -> list[dict[str, Any]]: """Get children nodes with their embeddings.""" user_name = user_name if user_name else self._get_config_value("user_name") @@ -1312,11 +1311,11 @@ def get_path(self, source_id: str, target_id: str, max_depth: int = 3) -> list[s @timed def get_subgraph( - self, - center_id: str, - depth: int = 2, - center_status: str = "activated", - user_name: str | None = None, + self, + center_id: str, + depth: int = 2, + center_status: str = "activated", + user_name: str | None = None, ) -> dict[str, Any]: """ Retrieve a local subgraph centered at a given node. @@ -1453,22 +1452,24 @@ def get_context_chain(self, id: str, type: str = "FOLLOWS") -> list[str]: @timed def search_by_embedding( - self, - vector: list[float], - top_k: int = 5, - 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, - **kwargs, + self, + vector: list[float], + top_k: int = 5, + 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, + **kwargs, ) -> list[dict]: """ Retrieve node IDs based on vector similarity using PostgreSQL vector operations. """ # Build WHERE clause dynamically like nebular.py + logger.info(f"[search_by_embedding] filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") + print(f"[search_by_embedding] filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") where_clauses = [] if scope: where_clauses.append( @@ -1542,7 +1543,14 @@ def search_by_embedding( FROM t WHERE scope > 0.1; """ - params = [vector] + # Convert vector to string format for PostgreSQL vector type + # PostgreSQL vector type expects a string format like '[1,2,3]' + vector_str = convert_to_vector(vector) + # Use string format directly in query instead of parameterized query + # Replace %s with the vector string, but need to quote it properly + # PostgreSQL vector type needs the string to be quoted + query = query.replace('%s::vector(1024)', f"'{vector_str}'::vector(1024)") + params = [] logger.debug(f"search_by_embedding query: {query}") logger.debug(f"search_by_embedding params: {params}") @@ -1561,23 +1569,37 @@ def search_by_embedding( else: print(line) - print("=== Params ===") - print(params) - print("================") + logger.info(f"[search_by_embedding] query: {query}, params: {params}") + print(f"[search_by_embedding] query: {query}, params: {params}") conn = self._get_connection() try: with conn.cursor() as cursor: - cursor.execute(query, params) + try: + # If params is empty, execute query directly without parameters + if params: + cursor.execute(query, params) + else: + cursor.execute(query) + except Exception as e: + logger.error(f"[search_by_embedding] Error executing query: {e}") + logger.error(f"[search_by_embedding] Query length: {len(query)}") + logger.error(f"[search_by_embedding] Params type: {type(params)}, length: {len(params)}") + logger.error(f"[search_by_embedding] Query contains %s: {'%s' in query}") + raise results = cursor.fetchall() output = [] print("=== Raw Results ===:", results) + print(f"=== Results count: {len(results)} ===") for row in results: """ polarId = row[0] # id properties = row[1] # properties # embedding = row[3] # embedding """ + if len(row) < 5: + logger.warning(f"Row has {len(row)} columns, expected 5. Row: {row}") + continue oldid = row[3] # old_id score = row[4] # scope id_val = str(oldid) @@ -1591,11 +1613,11 @@ def search_by_embedding( @timed def get_by_metadata( - self, - filters: list[dict[str, Any]], - user_name: str | None = None, - filter: dict | None = None, - knowledgebase_ids: list | None = None, + self, + filters: list[dict[str, Any]], + user_name: str | None = None, + filter: dict | None = None, + knowledgebase_ids: list | None = None, ) -> list[str]: """ Retrieve node IDs that match given metadata filters. @@ -1662,6 +1684,8 @@ def get_by_metadata( where_conditions.append(f"n.{field} STARTS WITH {escaped_value}") elif op == "ends_with": where_conditions.append(f"n.{field} ENDS WITH {escaped_value}") + elif op == "like": + where_conditions.append(f"n.{field} CONTAINS {escaped_value}") elif op in [">", ">=", "<", "<="]: where_conditions.append(f"n.{field} {op} {escaped_value}") else: @@ -1713,11 +1737,11 @@ def get_by_metadata( @timed def get_grouped_counts1( - self, - group_fields: list[str], - where_clause: str = "", - params: dict[str, Any] | None = None, - user_name: str | None = None, + self, + group_fields: list[str], + where_clause: str = "", + params: dict[str, Any] | None = None, + user_name: str | None = None, ) -> list[dict[str, Any]]: """ Count nodes grouped by any fields. @@ -1789,11 +1813,11 @@ def get_grouped_counts1( @timed def get_grouped_counts( - self, - group_fields: list[str], - where_clause: str = "", - params: dict[str, Any] | None = None, - user_name: str | None = None, + self, + group_fields: list[str], + where_clause: str = "", + params: dict[str, Any] | None = None, + user_name: str | None = None, ) -> list[dict[str, Any]]: """ Count nodes grouped by any fields. @@ -1932,7 +1956,7 @@ def clear(self, user_name: str | None = None) -> None: @timed def export_graph( - self, include_embedding: bool = False, user_name: str | None = None + self, include_embedding: bool = False, user_name: str | None = None ) -> dict[str, Any]: """ Export all graph nodes and edges in a structured form. @@ -2031,9 +2055,9 @@ def export_graph( else str(source_agtype) ) if ( - isinstance(source_raw, str) - and source_raw.startswith('"') - and source_raw.endswith('"') + isinstance(source_raw, str) + and source_raw.startswith('"') + and source_raw.endswith('"') ): source = source_raw[1:-1] else: @@ -2046,9 +2070,9 @@ def export_graph( else str(target_agtype) ) if ( - isinstance(target_raw, str) - and target_raw.startswith('"') - and target_raw.endswith('"') + isinstance(target_raw, str) + and target_raw.startswith('"') + and target_raw.endswith('"') ): target = target_raw[1:-1] else: @@ -2059,9 +2083,9 @@ def export_graph( edge_agtype.value if hasattr(edge_agtype, "value") else str(edge_agtype) ) if ( - isinstance(type_raw, str) - and type_raw.startswith('"') - and type_raw.endswith('"') + isinstance(type_raw, str) + and type_raw.startswith('"') + and type_raw.endswith('"') ): edge_type = type_raw[1:-1] else: @@ -2104,12 +2128,12 @@ def count_nodes(self, scope: str, user_name: str | None = None) -> int: @timed def get_all_memory_items( - self, - scope: str, - include_embedding: bool = False, - user_name: str | None = None, - filter: dict | None = None, - knowledgebase_ids: list | None = None, + self, + scope: str, + include_embedding: bool = False, + user_name: str | None = None, + filter: dict | None = None, + knowledgebase_ids: list | None = None, ) -> list[dict]: """ Retrieve all memory items of a specific memory_type. @@ -2160,7 +2184,7 @@ def get_all_memory_items( where_clause = " AND ".join(where_parts) + filter_where_clause else: where_clause = " AND ".join(where_parts) - + cypher_query = f""" WITH t as ( SELECT * FROM cypher('{self.db_name}_graph', $$ @@ -2256,7 +2280,7 @@ def get_all_memory_items( return nodes def get_all_memory_items_old( - self, scope: str, include_embedding: bool = False, user_name: str | None = None + self, scope: str, include_embedding: bool = False, user_name: str | None = None ) -> list[dict]: """ Retrieve all memory items of a specific memory_type. @@ -2363,7 +2387,7 @@ def get_all_memory_items_old( @timed def get_structure_optimization_candidates( - self, scope: str, include_embedding: bool = False, user_name: str | None = None + self, scope: str, include_embedding: bool = False, user_name: str | None = None ) -> list[dict]: """ Find nodes that are likely candidates for structure optimization: @@ -2509,7 +2533,7 @@ def get_structure_optimization_candidates( value = row[i] # Handle special fields if field_name in ["tags", "sources", "usage"] and isinstance( - value, str + value, str ): try: # Try parsing JSON string @@ -2575,10 +2599,10 @@ def _strip_wrapping_quotes(value: Any) -> Any: return value """ if ( - isinstance(value, str) - and len(value) >= 2 - and value[0] == value[-1] - and value[0] in ("'", '"') + isinstance(value, str) + and len(value) >= 2 + and value[0] == value[-1] + and value[0] in ("'", '"') ): return value[1:-1] return value @@ -2603,13 +2627,12 @@ def __del__(self): @timed def add_node( - self, id: str, memory: str, metadata: dict[str, Any], user_name: str | None = None + self, id: str, memory: str, metadata: dict[str, Any], user_name: str | None = None ) -> None: """Add a memory node to the graph.""" logger.info(f"[add_node] id: {id}, memory: {memory}, metadata: {metadata}") print(f"[add_node] metadata: {metadata}, info: {metadata.get('info')}") - # user_name comes from metadata; fallback to config if missing metadata["user_name"] = user_name if user_name else self.config.user_name @@ -2690,8 +2713,10 @@ def add_node( cursor.execute( insert_query, (id, json.dumps(properties), json.dumps(embedding_vector)) ) - logger.info(f"[add_node] [embedding_vector-true] insert_query: {insert_query}, properties: {json.dumps(properties)}") - print(f"[add_node] [embedding_vector-true] insert_query: {insert_query}, properties: {json.dumps(properties)}") + logger.info( + f"[add_node] [embedding_vector-true] insert_query: {insert_query}, properties: {json.dumps(properties)}") + print( + f"[add_node] [embedding_vector-true] insert_query: {insert_query}, properties: {json.dumps(properties)}") else: insert_query = f""" INSERT INTO {self.db_name}_graph."Memory"(id, properties) @@ -2701,8 +2726,10 @@ def add_node( ) """ cursor.execute(insert_query, (id, json.dumps(properties))) - logger.info(f"[add_node] [embedding_vector-false] insert_query: {insert_query}, properties: {json.dumps(properties)}") - print(f"[add_node] [embedding_vector-false] insert_query: {insert_query}, properties: {json.dumps(properties)}") + logger.info( + f"[add_node] [embedding_vector-false] insert_query: {insert_query}, properties: {json.dumps(properties)}") + print( + f"[add_node] [embedding_vector-false] insert_query: {insert_query}, properties: {json.dumps(properties)}") finally: logger.info(f"In add node polardb: id-{id} memory-{memory} query-{insert_query}") @@ -2740,13 +2767,13 @@ def _build_node_from_agtype(self, node_agtype, embedding=None): @timed def get_neighbors_by_tag( - self, - tags: list[str], - exclude_ids: list[str], - top_k: int = 5, - min_overlap: int = 1, - include_embedding: bool = False, - user_name: str | None = None, + self, + tags: list[str], + exclude_ids: list[str], + top_k: int = 5, + min_overlap: int = 1, + include_embedding: bool = False, + user_name: str | None = None, ) -> list[dict[str, Any]]: """ Find top-K neighbor nodes with maximum tag overlap. @@ -2868,13 +2895,13 @@ def get_neighbors_by_tag( self._return_connection(conn) def get_neighbors_by_tag_ccl( - self, - tags: list[str], - exclude_ids: list[str], - top_k: int = 5, - min_overlap: int = 1, - include_embedding: bool = False, - user_name: str | None = None, + self, + tags: list[str], + exclude_ids: list[str], + top_k: int = 5, + min_overlap: int = 1, + include_embedding: bool = False, + user_name: str | None = None, ) -> list[dict[str, Any]]: """ Find top-K neighbor nodes with maximum tag overlap. @@ -3065,7 +3092,7 @@ def import_graph(self, data: dict[str, Any], user_name: str | None = None) -> No @timed def get_edges( - self, id: str, type: str = "ANY", direction: str = "ANY", user_name: str | None = None + self, id: str, type: str = "ANY", direction: str = "ANY", user_name: str | None = None ) -> list[dict[str, str]]: """ Get edges connected to a node, with optional type and direction filter. @@ -3122,9 +3149,9 @@ def get_edges( # Extract and clean from_id from_id_raw = row[0].value if hasattr(row[0], "value") else row[0] if ( - isinstance(from_id_raw, str) - and from_id_raw.startswith('"') - and from_id_raw.endswith('"') + isinstance(from_id_raw, str) + and from_id_raw.startswith('"') + and from_id_raw.endswith('"') ): from_id = from_id_raw[1:-1] else: @@ -3133,9 +3160,9 @@ def get_edges( # Extract and clean to_id to_id_raw = row[1].value if hasattr(row[1], "value") else row[1] if ( - isinstance(to_id_raw, str) - and to_id_raw.startswith('"') - and to_id_raw.endswith('"') + isinstance(to_id_raw, str) + and to_id_raw.startswith('"') + and to_id_raw.endswith('"') ): to_id = to_id_raw[1:-1] else: @@ -3144,9 +3171,9 @@ def get_edges( # Extract and clean edge_type edge_type_raw = row[2].value if hasattr(row[2], "value") else row[2] if ( - isinstance(edge_type_raw, str) - and edge_type_raw.startswith('"') - and edge_type_raw.endswith('"') + isinstance(edge_type_raw, str) + and edge_type_raw.startswith('"') + and edge_type_raw.endswith('"') ): edge_type = edge_type_raw[1:-1] else: @@ -3292,9 +3319,9 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: """Build a Cypher WHERE condition for a single filter item.""" condition_parts = [] for key, value in condition_dict.items(): - # Check if value is a dict with comparison operators (gt, lt, gte, lte) + # Check if value is a dict with comparison operators (gt, lt, gte, lte, =, contains) if isinstance(value, dict): - # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + # Handle comparison operators: gt, lt, gte, lte, =, contains for op, op_value in value.items(): if op in ("gt", "lt", "gte", "lte"): # Map operator to Cypher operator @@ -3322,6 +3349,91 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: condition_parts.append(f"n.{key} {cypher_op} '{escaped_value}'") else: condition_parts.append(f"n.{key} {cypher_op} {op_value}") + elif op == "=": + # Handle equality operator + # For array fields, = means exact match of the entire array (e.g., tags = ['test:zdy'] or tags = ['mode:fast', 'test:zdy']) + # For scalar fields, = means equality + # Check if key starts with "info." prefix + if key.startswith("info."): + info_field = key[5:] # Remove "info." prefix + if isinstance(op_value, str): + escaped_value = escape_cypher_string(op_value) + # For array fields, check if array exactly equals [value] + # For scalar fields, use = + if info_field in ("tags", "sources"): + condition_parts.append(f"n.info.{info_field} = ['{escaped_value}']") + else: + condition_parts.append(f"n.info.{info_field} = '{escaped_value}'") + elif isinstance(op_value, list): + # For array fields, format list as Cypher array + if info_field in ("tags", "sources"): + escaped_items = [f"'{escape_cypher_string(str(item))}'" for item in op_value] + array_str = "[" + ", ".join(escaped_items) + "]" + condition_parts.append(f"n.info.{info_field} = {array_str}") + else: + condition_parts.append(f"n.info.{info_field} = {op_value}") + else: + if info_field in ("tags", "sources"): + condition_parts.append(f"n.info.{info_field} = [{op_value}]") + else: + condition_parts.append(f"n.info.{info_field} = {op_value}") + else: + # Direct property access + if isinstance(op_value, str): + escaped_value = escape_cypher_string(op_value) + # For array fields, check if array exactly equals [value] + # For scalar fields, use = + if key in ("tags", "sources"): + condition_parts.append(f"n.{key} = ['{escaped_value}']") + else: + condition_parts.append(f"n.{key} = '{escaped_value}'") + elif isinstance(op_value, list): + # For array fields, format list as Cypher array + if key in ("tags", "sources"): + escaped_items = [f"'{escape_cypher_string(str(item))}'" for item in op_value] + array_str = "[" + ", ".join(escaped_items) + "]" + condition_parts.append(f"n.{key} = {array_str}") + else: + condition_parts.append(f"n.{key} = {op_value}") + else: + if key in ("tags", "sources"): + condition_parts.append(f"n.{key} = [{op_value}]") + else: + condition_parts.append(f"n.{key} = {op_value}") + elif op == "contains": + # Handle contains operator (for array fields) + # Check if key starts with "info." prefix + if key.startswith("info."): + info_field = key[5:] # Remove "info." prefix + if isinstance(op_value, str): + escaped_value = escape_cypher_string(op_value) + condition_parts.append(f"'{escaped_value}' IN n.info.{info_field}") + else: + condition_parts.append(f"{op_value} IN n.info.{info_field}") + else: + # Direct property access + if isinstance(op_value, str): + escaped_value = escape_cypher_string(op_value) + condition_parts.append(f"'{escaped_value}' IN n.{key}") + else: + condition_parts.append(f"{op_value} IN n.{key}") + elif op == "like": + # Handle like operator (for fuzzy matching, similar to SQL LIKE '%value%') + # Check if key starts with "info." prefix + if key.startswith("info."): + info_field = key[5:] # Remove "info." prefix + if isinstance(op_value, str): + escaped_value = escape_cypher_string(op_value) + condition_parts.append(f"n.info.{info_field} CONTAINS '{escaped_value}'") + else: + condition_parts.append(f"n.info.{info_field} CONTAINS {op_value}") + else: + # Direct property access + if isinstance(op_value, str): + escaped_value = escape_cypher_string(op_value) + condition_parts.append(f"n.{key} CONTAINS '{escaped_value}'") + else: + condition_parts.append(f"n.{key} CONTAINS {op_value}") # Check if key starts with "info." prefix (for simple equality) elif key.startswith("info."): info_field = key[5:] @@ -3388,9 +3500,9 @@ def build_filter_condition(condition_dict: dict) -> str: """Build a WHERE condition for a single filter item.""" condition_parts = [] for key, value in condition_dict.items(): - # Check if value is a dict with comparison operators (gt, lt, gte, lte) + # Check if value is a dict with comparison operators (gt, lt, gte, lte, =, contains) if isinstance(value, dict): - # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) + # Handle comparison operators: gt, lt, gte, lte, =, contains for op, op_value in value.items(): if op in ("gt", "lt", "gte", "lte"): # Map operator to SQL operator @@ -3426,6 +3538,133 @@ def build_filter_condition(condition_dict: dict) -> str: condition_parts.append( f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) {sql_op} {op_value}::agtype" ) + elif op == "=": + # Handle equality operator + # For array fields, = means exact match of the entire array (e.g., tags = ['test:zdy'] or tags = ['mode:fast', 'test:zdy']) + # For scalar fields, = means equality + # Check if key starts with "info." prefix + if key.startswith("info."): + info_field = key[5:] # Remove "info." prefix + if isinstance(op_value, str): + escaped_value = escape_sql_string(op_value) + # For array fields, check if array exactly equals [value] + # For scalar fields, use = + if info_field in ("tags", "sources"): + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '[\"{escaped_value}\"]'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '\"{escaped_value}\"'::agtype" + ) + elif isinstance(op_value, list): + # For array fields, format list as JSON array string + if info_field in ("tags", "sources"): + escaped_items = [escape_sql_string(str(item)) for item in op_value] + json_array = json.dumps(escaped_items) + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '{json_array}'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = {op_value}::agtype" + ) + else: + if info_field in ("tags", "sources"): + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '[{op_value}]'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = {op_value}::agtype" + ) + else: + # Direct property access + if isinstance(op_value, str): + escaped_value = escape_sql_string(op_value) + # For array fields, check if array exactly equals [value] + # For scalar fields, use = + if key in ("tags", "sources"): + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = '[\"{escaped_value}\"]'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = '\"{escaped_value}\"'::agtype" + ) + elif isinstance(op_value, list): + # For array fields, format list as JSON array string + if key in ("tags", "sources"): + escaped_items = [escape_sql_string(str(item)) for item in op_value] + json_array = json.dumps(escaped_items) + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = '{json_array}'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = {op_value}::agtype" + ) + else: + if key in ("tags", "sources"): + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = '[{op_value}]'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = {op_value}::agtype" + ) + elif op == "contains": + # Handle contains operator (for array fields) - use @> operator + # Check if key starts with "info." prefix + if key.startswith("info."): + info_field = key[5:] # Remove "info." prefix + if isinstance(op_value, str): + escaped_value = escape_sql_string(op_value) + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype) @> '\"{escaped_value}\"'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype) @> {op_value}::agtype" + ) + else: + # Direct property access + if isinstance(op_value, str): + escaped_value = escape_sql_string(op_value) + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) @> '\"{escaped_value}\"'::agtype" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) @> {op_value}::agtype" + ) + elif op == "like": + # Handle like operator (for fuzzy matching, similar to SQL LIKE '%value%') + # Check if key starts with "info." prefix + if key.startswith("info."): + info_field = key[5:] # Remove "info." prefix + if isinstance(op_value, str): + # Escape SQL special characters for LIKE: % and _ need to be escaped + escaped_value = escape_sql_string(op_value).replace("%", "\\%").replace("_", "\\_") + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype)::text LIKE '%{escaped_value}%'" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype)::text LIKE '%{op_value}%'" + ) + else: + # Direct property access + if isinstance(op_value, str): + # Escape SQL special characters for LIKE: % and _ need to be escaped + escaped_value = escape_sql_string(op_value).replace("%", "\\%").replace("_", "\\_") + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype)::text LIKE '%{escaped_value}%'" + ) + else: + condition_parts.append( + f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype)::text LIKE '%{op_value}%'" + ) # Check if key starts with "info." prefix (for simple equality) elif key.startswith("info."): # Extract the field name after "info." From 9d6ebc5b3d93d9573aa555ddd8e2a2cad3c8a259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Fri, 28 Nov 2025 11:33:21 +0800 Subject: [PATCH 31/36] add neo4j_community.py filter --- src/memos/graph_dbs/neo4j_community.py | 518 ++++++++++++++----------- 1 file changed, 283 insertions(+), 235 deletions(-) diff --git a/src/memos/graph_dbs/neo4j_community.py b/src/memos/graph_dbs/neo4j_community.py index 70ae96de8..ccfeb2f22 100644 --- a/src/memos/graph_dbs/neo4j_community.py +++ b/src/memos/graph_dbs/neo4j_community.py @@ -1,4 +1,6 @@ import json +import re +from datetime import datetime from typing import Any @@ -135,17 +137,17 @@ def get_children_with_embeddings( # Search / recall operations def search_by_embedding( - self, - vector: list[float], - top_k: int = 5, - 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, - **kwargs, + self, + vector: list[float], + top_k: int = 5, + 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, + **kwargs, ) -> list[dict]: """ Retrieve node IDs based on vector similarity using external vector DB. @@ -158,7 +160,8 @@ def search_by_embedding( threshold (float, optional): Minimum similarity score threshold (0 ~ 1). search_filter (dict, optional): Additional metadata filters to apply. filter (dict, optional): Filter conditions with 'and' or 'or' logic for search results. - knowledgebase_ids (list[str], optional): List of knowledgebase IDs to filter by user_name. + Example: {"and": [{"id": "xxx"}, {"A": "yyy"}]} or {"or": [{"id": "xxx"}, {"A": "yyy"}]} + knowledgebase_ids (list[str], optional): List of knowledgebase IDs to filter by. Returns: list[dict]: A list of dicts with 'id' and 'score', ordered by similarity. @@ -169,12 +172,12 @@ def search_by_embedding( - If 'status' is provided, it further filters nodes by status. - If 'threshold' is provided, only results with score >= threshold will be returned. - If 'search_filter' is provided, it applies additional metadata-based filtering. - - If 'filter' is provided, it applies additional WHERE clauses in Neo4j after vector search. - - If 'knowledgebase_ids' is provided, it filters by user_name with OR logic. + - If 'filter' is provided, it applies complex filter conditions with AND/OR logic. - The returned IDs can be used to fetch full node data from Neo4j if needed. """ user_name = user_name if user_name else self.config.user_name - # Build VecDB filter + + # First, perform vector search in external vector DB vec_filter = {} if scope: vec_filter["memory_type"] = scope @@ -190,148 +193,268 @@ def search_by_embedding( if search_filter: vec_filter.update(search_filter) - # Perform vector search - get more results if we need to filter in Neo4j - search_top_k = top_k * 2 if (filter or knowledgebase_ids) else top_k - results = self.vec_db.search(query_vector=vector, top_k=search_top_k, filter=vec_filter) + # Perform vector search + vec_results = [] + if self.vec_db: + try: + vec_results = self.vec_db.search(query_vector=vector, top_k=top_k, filter=vec_filter) + except Exception as e: + logger.warning(f"[VecDB] search failed: {e}") # Filter by threshold if threshold is not None: - results = [r for r in results if r.score is None or r.score >= threshold] - - # If we have filter or knowledgebase_ids, need to filter in Neo4j - if filter or knowledgebase_ids: - # Get IDs from vector search results - vec_result_ids = [r.id for r in results] - - if not vec_result_ids: - return [] - - # Build WHERE clause for Neo4j filtering - where_clauses = [f"node.id IN $vec_ids"] - params = {"vec_ids": vec_result_ids} - - # Build user_name filter with knowledgebase_ids support (OR relationship) - user_name_conditions = [] - if not self.config.use_multi_db and (self.config.user_name or user_name): - user_name_conditions.append("node.user_name = $user_name") - params["user_name"] = user_name - - # Add knowledgebase_ids conditions (checking user_name field in the data) - if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: - for idx, kb_id in enumerate(knowledgebase_ids): - if isinstance(kb_id, str): - param_name = f"kb_id_{idx}" - user_name_conditions.append(f"node.user_name = ${param_name}") - params[param_name] = kb_id - - # Add user_name WHERE clause - if user_name_conditions: - if len(user_name_conditions) == 1: - where_clauses.append(user_name_conditions[0]) - else: - where_clauses.append(f"({' OR '.join(user_name_conditions)})") - - # Add filter conditions - filter_params = {} - if filter: - def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: - """Build a WHERE condition for a single filter item.""" - condition_parts = [] - filter_params_inner = {} - - for key, value in condition_dict.items(): - # Check if value is a dict with comparison operators (gt, lt, gte, lte) - if isinstance(value, dict): - for op, op_value in value.items(): - if op in ("gt", "lt", "gte", "lte"): - cypher_op_map = { - "gt": ">", - "lt": "<", - "gte": ">=", - "lte": "<=" - } - cypher_op = cypher_op_map[op] - - param_name = f"filter_flat_{key}_{op}_{param_counter[0]}" - param_counter[0] += 1 - filter_params_inner[param_name] = op_value - - # Use datetime() function for date comparisons - if key in ("created_at", "updated_at") or key.endswith("_at"): - condition_parts.append(f"node.{key} {cypher_op} datetime(${param_name})") - else: - condition_parts.append(f"node.{key} {cypher_op} ${param_name}") - else: - # Simple equality - param_name = f"filter_flat_{key}_{param_counter[0]}" - param_counter[0] += 1 - filter_params_inner[param_name] = value - condition_parts.append(f"node.{key} = ${param_name}") - - return " AND ".join(condition_parts), filter_params_inner - - param_counter = [0] - - if isinstance(filter, dict): - if "or" in filter: - or_conditions = [] - for condition in filter["or"]: - if isinstance(condition, dict): - condition_str, filter_params_inner = build_filter_condition(condition, param_counter) - if condition_str: - or_conditions.append(f"({condition_str})") - filter_params.update(filter_params_inner) - if or_conditions: - where_clauses.append(f"({' OR '.join(or_conditions)})") - - elif "and" in filter: - for condition in filter["and"]: - if isinstance(condition, dict): - condition_str, filter_params_inner = build_filter_condition(condition, param_counter) - if condition_str: - where_clauses.append(f"({condition_str})") - filter_params.update(filter_params_inner) - - if filter_params: - params.update(filter_params) - - where_clause = "WHERE " + " AND ".join(where_clauses) - - # Query Neo4j to filter results - query = f""" - MATCH (node:Memory) - {where_clause} - RETURN node.id AS id - """ + vec_results = [r for r in vec_results if r.score is None or r.score >= threshold] + + # If no filter or knowledgebase_ids provided, return vector search results directly + if not filter and not knowledgebase_ids: + return [{"id": r.id, "score": r.score} for r in vec_results] + + # Extract IDs from vector search results + vec_ids = [r.id for r in vec_results] + if not vec_ids: + return [] + + # Build WHERE clause for Neo4j filtering + where_clauses = ["n.id IN $vec_ids"] + params = {"vec_ids": vec_ids} + + # Build user_name filter with knowledgebase_ids support (OR relationship) using common method + user_name_conditions, user_name_params = self._build_user_name_and_kb_ids_conditions_cypher( + user_name=user_name, + knowledgebase_ids=knowledgebase_ids, + default_user_name=self.config.user_name, + node_alias="n", + ) - with self.driver.session(database=self.db_name) as session: - neo4j_results = session.run(query, params) - filtered_ids = {record["id"] for record in neo4j_results} + # Add user_name WHERE clause + if user_name_conditions: + if len(user_name_conditions) == 1: + where_clauses.append(user_name_conditions[0]) + else: + where_clauses.append(f"({' OR '.join(user_name_conditions)})") + + # Build filter conditions using common method + filter_conditions, filter_params = self._build_filter_conditions_cypher( + filter=filter, + param_counter_start=0, + node_alias="n", + ) + where_clauses.extend(filter_conditions) + + where_clause = "WHERE " + " AND ".join(where_clauses) + + # Add user_name and knowledgebase_ids parameters using common method + params.update(user_name_params) + + # Add filter parameters + if filter_params: + params.update(filter_params) + + # Query Neo4j to filter results + query = f""" + MATCH (n:Memory) + {where_clause} + RETURN n.id AS id + """ + logger.info(f"[search_by_embedding] query: {query}, params: {params}") + + with self.driver.session(database=self.db_name) as session: + neo4j_results = session.run(query, params) + filtered_ids = {record["id"] for record in neo4j_results} + + # Filter vector results by Neo4j filtered IDs and return with scores + filtered_results = [ + {"id": r.id, "score": r.score} + for r in vec_results + if r.id in filtered_ids + ] + + return filtered_results + + def _normalize_date_string(self, date_str: str) -> str: + """ + Normalize date string to ISO 8601 format for Neo4j datetime() function. + + Args: + date_str: Date string in various formats (e.g., "2025-09-19", "2025-09-19T00:00:00Z") + + Returns: + ISO 8601 formatted date string (e.g., "2025-09-19T00:00:00Z") + """ + if not isinstance(date_str, str): + return date_str + + # If already in ISO 8601 format with time, return as is + if "T" in date_str or date_str.endswith("Z") or "+" in date_str or "-" in date_str[-6:]: + return date_str + + # Check if it's a simple date format (YYYY-MM-DD) + date_pattern = re.match(r"^(\d{4})-(\d{2})-(\d{2})$", date_str) + if date_pattern: + # Convert to ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ + # For "gt" (greater than), use 00:00:00 of the next day + # For "lt" (less than), use 00:00:00 of the same day + # For "gte" (greater than or equal), use 00:00:00 of the same day + # For "lte" (less than or equal), use 23:59:59.999999999 of the same day + # But we'll use 00:00:00Z as default and let the caller handle the logic + return f"{date_str}T00:00:00Z" + + # If it's already a datetime string, try to parse and reformat + try: + # Try to parse various datetime formats + dt = datetime.fromisoformat(date_str.replace("Z", "+00:00")) + return dt.isoformat().replace("+00:00", "Z") + except (ValueError, AttributeError): + # If parsing fails, return as is + return date_str + + def _build_filter_conditions_cypher( + self, + filter: dict | None, + param_counter_start: int = 0, + node_alias: str = "node", + ) -> tuple[list[str], dict[str, Any]]: + """ + Build filter conditions for Cypher queries with date normalization. + + This method extends the parent class method by normalizing date strings + to ISO 8601 format before building conditions. + + Args: + filter: Filter dictionary with "or" or "and" logic + param_counter_start: Starting value for parameter counter (to avoid conflicts) + node_alias: Node alias in Cypher query (default: "node" or "n") - # Filter vector search results by Neo4j filtered IDs and keep scores - filtered_results = [r for r in results if r.id in filtered_ids] - - # Return top_k results - return [{"id": r.id, "score": r.score} for r in filtered_results[:top_k]] + Returns: + Tuple of (condition_strings_list, parameters_dict) + """ + # Preprocess filter to normalize date strings + if filter: + normalized_filter = self._normalize_filter_dates(filter) + else: + normalized_filter = filter - # Return consistent format - return [{"id": r.id, "score": r.score} for r in results[:top_k]] + # Call parent method with normalized filter + return super()._build_filter_conditions_cypher( + filter=normalized_filter, + param_counter_start=param_counter_start, + node_alias=node_alias, + ) - def get_all_memory_items(self, scope: str, filter: dict | None = None, knowledgebase_ids: list[str] | None = None, **kwargs) -> list[dict]: + def _normalize_filter_dates(self, filter: dict) -> dict: + """ + Recursively normalize date strings in filter dictionary. + + Args: + filter: Filter dictionary that may contain date strings + + Returns: + Filter dictionary with normalized date strings + """ + if not isinstance(filter, dict): + return filter + + normalized = {} + + if "and" in filter: + normalized["and"] = [ + self._normalize_condition_dates(cond) if isinstance(cond, dict) else cond + for cond in filter["and"] + ] + elif "or" in filter: + normalized["or"] = [ + self._normalize_condition_dates(cond) if isinstance(cond, dict) else cond + for cond in filter["or"] + ] + else: + # Single condition + normalized = self._normalize_condition_dates(filter) + + return normalized + + def _normalize_condition_dates(self, condition: dict) -> dict: + """ + Normalize date strings in a single condition dictionary. + + Args: + condition: A condition dict like {"created_at": {"gt": "2025-09-19"}} + + Returns: + Condition dict with normalized date strings + """ + from datetime import timedelta + + normalized = {} + + for key, value in condition.items(): + # Check if this is a date field + is_date_field = key in ("created_at", "updated_at") or key.endswith("_at") + + if isinstance(value, dict): + # Handle comparison operators + normalized_value = {} + for op, op_value in value.items(): + if op in ("gt", "lt", "gte", "lte") and is_date_field: + # Normalize date string for date comparisons + if isinstance(op_value, str): + # Check if it's a simple date format (YYYY-MM-DD) + date_pattern = re.match(r"^(\d{4})-(\d{2})-(\d{2})$", op_value) + if date_pattern: + try: + # Parse the date + dt = datetime.fromisoformat(op_value + "T00:00:00") + + if op == "gt": + # "gt": "2025-09-19" means > 2025-09-19 00:00:00 + # So we keep it as 2025-09-19T00:00:00Z + normalized_value[op] = dt.isoformat() + "Z" + elif op == "gte": + # "gte": "2025-09-19" means >= 2025-09-19 00:00:00 + normalized_value[op] = dt.isoformat() + "Z" + elif op == "lt": + # "lt": "2025-11-29" means < 2025-11-29 (exclude the entire day) + # So we convert to the start of the next day: 2025-11-30T00:00:00Z + # This ensures all times on 2025-11-29 are included + dt_next = dt + timedelta(days=1) + normalized_value[op] = dt_next.isoformat() + "Z" + elif op == "lte": + # "lte": "2025-11-29" means <= 2025-11-29 23:59:59.999999 + # So we convert to end of day: 2025-11-29T23:59:59.999999Z + dt_end = dt + timedelta(days=1) - timedelta(microseconds=1) + normalized_value[op] = dt_end.isoformat() + "Z" + except ValueError: + # If parsing fails, use the original normalization + normalized_value[op] = self._normalize_date_string(op_value) + else: + # Already in a more complex format, just normalize it + normalized_value[op] = self._normalize_date_string(op_value) + else: + normalized_value[op] = op_value + else: + normalized_value[op] = op_value + normalized[key] = normalized_value + else: + normalized[key] = value + + return normalized + + def get_all_memory_items( + self, scope: str, filter: dict | None = None, knowledgebase_ids: list[str] | None = None, **kwargs + ) -> list[dict]: """ Retrieve all memory items of a specific memory_type. Args: - scope (str): Must be one of 'WorkingMemory', 'LongTermMemory', or 'UserMemory'. + scope (str): Must be one of 'WorkingMemory', 'LongTermMemory', 'UserMemory', or 'OuterMemory'. filter (dict, optional): Filter conditions with 'and' or 'or' logic for search results. Example: {"and": [{"id": "xxx"}, {"A": "yyy"}]} or {"or": [{"id": "xxx"}, {"A": "yyy"}]} - knowledgebase_ids (list[str], optional): List of knowledgebase IDs to filter by user_name. + knowledgebase_ids (list[str], optional): List of knowledgebase IDs to filter by. Returns: list[dict]: Full list of memory items under this scope. """ - logger.info(f"[get_all_memory_items] scope: {scope},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") - print(f"[get_all_memory_items] scope: {scope},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") + logger.info(f"[get_all_memory_items] scope: {scope}, filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") + print(f"[get_all_memory_items] scope: {scope}, filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") user_name = kwargs.get("user_name") if kwargs.get("user_name") else self.config.user_name if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"}: @@ -340,18 +463,14 @@ def get_all_memory_items(self, scope: str, filter: dict | None = None, knowledge where_clauses = ["n.memory_type = $scope"] params = {"scope": scope} - # Build user_name filter with knowledgebase_ids support (OR relationship) - user_name_conditions = [] - if not self.config.use_multi_db and (self.config.user_name or user_name): - user_name_conditions.append("n.user_name = $user_name") - - # Add knowledgebase_ids conditions (checking user_name field in the data) - if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: - for idx, kb_id in enumerate(knowledgebase_ids): - if isinstance(kb_id, str): - param_name = f"kb_id_{idx}" - user_name_conditions.append(f"n.user_name = ${param_name}") - + # Build user_name filter with knowledgebase_ids support (OR relationship) using common method + user_name_conditions, user_name_params = self._build_user_name_and_kb_ids_conditions_cypher( + user_name=user_name, + knowledgebase_ids=knowledgebase_ids, + default_user_name=self.config.user_name, + node_alias="n", + ) + # Add user_name WHERE clause if user_name_conditions: if len(user_name_conditions) == 1: @@ -359,91 +478,20 @@ def get_all_memory_items(self, scope: str, filter: dict | None = None, knowledge else: where_clauses.append(f"({' OR '.join(user_name_conditions)})") - filter_params = {} - if filter: - def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: - """Build a WHERE condition for a single filter item. - - Args: - condition_dict: A dict like {"id": "xxx"} or {"A": "xxx"} or {"created_at": {"gt": "2025-11-01"}} - param_counter: List to track parameter counter for unique param names - - Returns: - Tuple of (condition_string, parameters_dict) - """ - condition_parts = [] - filter_params_inner = {} - - for key, value in condition_dict.items(): - # Check if value is a dict with comparison operators (gt, lt, gte, lte) - if isinstance(value, dict): - # Handle comparison operators: gt (greater than), lt (less than), gte (greater than or equal), lte (less than or equal) - for op, op_value in value.items(): - if op in ("gt", "lt", "gte", "lte"): - # Map operator to Cypher operator - cypher_op_map = { - "gt": ">", - "lt": "<", - "gte": ">=", - "lte": "<=" - } - cypher_op = cypher_op_map[op] - - # All fields are stored as flat properties in Neo4j - param_name = f"filter_flat_{key}_{op}_{param_counter[0]}" - param_counter[0] += 1 - filter_params_inner[param_name] = op_value - - # Check if field is a date field (created_at, updated_at, etc.) - # Use datetime() function for date comparisons - if key in ("created_at", "updated_at") or key.endswith("_at"): - condition_parts.append(f"n.{key} {cypher_op} datetime(${param_name})") - else: - condition_parts.append(f"n.{key} {cypher_op} ${param_name}") - else: - # All fields are stored as flat properties in Neo4j (simple equality) - param_name = f"filter_flat_{key}_{param_counter[0]}" - param_counter[0] += 1 - filter_params_inner[param_name] = value - condition_parts.append(f"n.{key} = ${param_name}") - - return " AND ".join(condition_parts), filter_params_inner - - param_counter = [0] - - if isinstance(filter, dict): - if "or" in filter: - or_conditions = [] - for condition in filter["or"]: - if isinstance(condition, dict): - condition_str, filter_params_inner = build_filter_condition(condition, param_counter) - if condition_str: - or_conditions.append(f"({condition_str})") - filter_params.update(filter_params_inner) - if or_conditions: - where_clauses.append(f"({' OR '.join(or_conditions)})") - - elif "and" in filter: - for condition in filter["and"]: - if isinstance(condition, dict): - condition_str, filter_params_inner = build_filter_condition(condition, param_counter) - if condition_str: - where_clauses.append(f"({condition_str})") - filter_params.update(filter_params_inner) + # Build filter conditions using common method + filter_conditions, filter_params = self._build_filter_conditions_cypher( + filter=filter, + param_counter_start=0, + node_alias="n", + ) + where_clauses.extend(filter_conditions) where_clause = "WHERE " + " AND ".join(where_clauses) - # Add user_name parameter - if not self.config.use_multi_db and (self.config.user_name or user_name): - params["user_name"] = user_name - - # Add knowledgebase_ids parameters - if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: - for idx, kb_id in enumerate(knowledgebase_ids): - if isinstance(kb_id, str): - param_name = f"kb_id_{idx}" - params[param_name] = kb_id + # Add user_name and knowledgebase_ids parameters using common method + params.update(user_name_params) + # Add filter parameters if filter_params: params.update(filter_params) @@ -452,8 +500,8 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s {where_clause} RETURN n """ - logger.info(f"[get_all_memory_items] query: {query},params: {params}") - print(f"[get_all_memory_items] query: {query},params: {params}") + logger.info(f"[get_all_memory_items] query: {query}, params: {params}") + print(f"[get_all_memory_items] query: {query}, params: {params}") with self.driver.session(database=self.db_name) as session: results = session.run(query, params) From 68356cd4b401d95e5431bcea3b54bf4f94bff9ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Fri, 28 Nov 2025 13:12:01 +0800 Subject: [PATCH 32/36] ruff format --- src/memos/graph_dbs/neo4j.py | 102 +++--- src/memos/graph_dbs/neo4j_community.py | 112 ++++--- src/memos/graph_dbs/polardb.py | 409 ++++++++++++++----------- 3 files changed, 357 insertions(+), 266 deletions(-) diff --git a/src/memos/graph_dbs/neo4j.py b/src/memos/graph_dbs/neo4j.py index 94545be1a..e934d3a19 100644 --- a/src/memos/graph_dbs/neo4j.py +++ b/src/memos/graph_dbs/neo4j.py @@ -48,16 +48,16 @@ def _prepare_node_metadata(metadata: dict[str, Any]) -> dict[str, Any]: def _flatten_info_fields(metadata: dict[str, Any]) -> dict[str, Any]: """ Flatten the 'info' field in metadata to the top level. - + If metadata contains an 'info' field that is a dictionary, all its key-value pairs will be moved to the top level of metadata, and the 'info' field will be removed. - + Args: metadata: Dictionary that may contain an 'info' field - + Returns: Dictionary with 'info' fields flattened to top level - + Example: Input: {"user_id": "xxx", "info": {"A": "value1", "B": "value2"}} Output: {"user_id": "xxx", "A": "value1", "B": "value2"} @@ -195,7 +195,7 @@ def remove_oldest_memory( session.run(query) def add_node( - self, id: str, memory: str, metadata: dict[str, Any], user_name: str | None = None + self, id: str, memory: str, metadata: dict[str, Any], user_name: str | None = None ) -> None: logger.info(f"[add_node] metadata: {metadata},info: {metadata.get('info')}") print(f"[add_node] metadata: {metadata},info: {metadata.get('info')}") @@ -206,7 +206,7 @@ def add_node( # Safely process metadata metadata = _prepare_node_metadata(metadata) - + # Flatten info fields to top level (for Neo4j flat structure) metadata = _flatten_info_fields(metadata) @@ -226,7 +226,6 @@ def add_node( if metadata["sources"]: for idx in range(len(metadata["sources"])): metadata["sources"][idx] = json.dumps(metadata["sources"][idx]) - print("111add_node id:",id) with self.driver.session(database=self.db_name) as session: session.run( @@ -593,7 +592,7 @@ def get_children_with_embeddings( ] def get_path( - self, source_id: str, target_id: str, max_depth: int = 3, user_name: str | None = None + self, source_id: str, target_id: str, max_depth: int = 3, user_name: str | None = None ) -> list[str]: """ Get the path of nodes from source to target within a limited depth. @@ -687,17 +686,17 @@ def get_context_chain(self, id: str, type: str = "FOLLOWS") -> list[str]: # Search / recall operations def search_by_embedding( - self, - vector: list[float], - top_k: int = 5, - 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, - **kwargs, + self, + vector: list[float], + top_k: int = 5, + 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, + **kwargs, ) -> list[dict]: """ Retrieve node IDs based on vector similarity. @@ -808,7 +807,11 @@ def search_by_embedding( return records def get_by_metadata( - self, filters: list[dict[str, Any]], user_name: str | None = None, filter: dict | None = None, knowledgebase_ids: list[str] | None = None + self, + filters: list[dict[str, Any]], + user_name: str | None = None, + filter: dict | None = None, + knowledgebase_ids: list[str] | None = None, ) -> list[str]: """ TODO: @@ -834,8 +837,12 @@ def get_by_metadata( - Supports structured querying such as tag/category/importance/time filtering. - Can be used for faceted recall or prefiltering before embedding rerank. """ - logger.info(f"[get_by_metadata] filters: {filters},user_name: {user_name},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") - print(f"[get_by_metadata] filters: {filters},user_name: {user_name},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") + logger.info( + f"[get_by_metadata] filters: {filters},user_name: {user_name},filter: {filter},knowledgebase_ids: {knowledgebase_ids}" + ) + print( + f"[get_by_metadata] filters: {filters},user_name: {user_name},filter: {filter},knowledgebase_ids: {knowledgebase_ids}" + ) user_name = user_name if user_name else self.config.user_name where_clauses = [] params = {} @@ -1096,7 +1103,13 @@ def import_graph(self, data: dict[str, Any], user_name: str | None = None) -> No target_id=edge["target"], ) - def get_all_memory_items(self, scope: str, filter: dict | None = None, knowledgebase_ids: list[str] | None = None, **kwargs) -> list[dict]: + def get_all_memory_items( + self, + scope: str, + filter: dict | None = None, + knowledgebase_ids: list[str] | None = None, + **kwargs, + ) -> list[dict]: """ Retrieve all memory items of a specific memory_type. @@ -1109,8 +1122,12 @@ def get_all_memory_items(self, scope: str, filter: dict | None = None, knowledge Returns: list[dict]: Full list of memory items under this scope. """ - logger.info(f"[get_all_memory_items] scope: {scope},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") - print(f"[get_all_memory_items] scope: {scope},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") + logger.info( + f"[get_all_memory_items] scope: {scope},filter: {filter},knowledgebase_ids: {knowledgebase_ids}" + ) + print( + f"[get_all_memory_items] scope: {scope},filter: {filter},knowledgebase_ids: {knowledgebase_ids}" + ) user_name = kwargs.get("user_name") if kwargs.get("user_name") else self.config.user_name if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"}: @@ -1316,11 +1333,11 @@ def _index_exists(self, index_name: str) -> bool: return False def _build_user_name_and_kb_ids_conditions_cypher( - self, - user_name: str | None, - knowledgebase_ids: list[str] | None, - default_user_name: str | None = None, - node_alias: str = "node", + self, + user_name: str | None, + knowledgebase_ids: list[str] | None, + default_user_name: str | None = None, + node_alias: str = "node", ) -> tuple[list[str], dict[str, Any]]: """ Build user_name and knowledgebase_ids conditions for Cypher queries. @@ -1354,10 +1371,10 @@ def _build_user_name_and_kb_ids_conditions_cypher( return user_name_conditions, params def _build_filter_conditions_cypher( - self, - filter: dict | None, - param_counter_start: int = 0, - node_alias: str = "node", + self, + filter: dict | None, + param_counter_start: int = 0, + node_alias: str = "node", ) -> tuple[list[str], dict[str, Any]]: """ Build filter conditions for Cypher queries. @@ -1396,12 +1413,7 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s for op, op_value in value.items(): if op in ("gt", "lt", "gte", "lte"): # Map operator to Cypher operator - cypher_op_map = { - "gt": ">", - "lt": "<", - "gte": ">=", - "lte": "<=" - } + cypher_op_map = {"gt": ">", "lt": "<", "gte": ">=", "lte": "<="} cypher_op = cypher_op_map[op] # All fields are stored as flat properties in Neo4j @@ -1412,15 +1424,19 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s # Check if field is a date field (created_at, updated_at, etc.) # Use datetime() function for date comparisons if key in ("created_at", "updated_at") or key.endswith("_at"): - condition_parts.append(f"{node_alias}.{key} {cypher_op} datetime(${param_name})") + condition_parts.append( + f"{node_alias}.{key} {cypher_op} datetime(${param_name})" + ) else: - condition_parts.append(f"{node_alias}.{key} {cypher_op} ${param_name}") + condition_parts.append( + f"{node_alias}.{key} {cypher_op} ${param_name}" + ) elif op == "contains": # Handle contains operator (for array fields like tags, sources) param_name = f"filter_{key}_{op}_{param_counter[0]}" param_counter[0] += 1 params[param_name] = op_value - + # For array fields, check if element is in array if key in ("tags", "sources"): condition_parts.append(f"${param_name} IN {node_alias}.{key}") diff --git a/src/memos/graph_dbs/neo4j_community.py b/src/memos/graph_dbs/neo4j_community.py index ccfeb2f22..ff7d5f50b 100644 --- a/src/memos/graph_dbs/neo4j_community.py +++ b/src/memos/graph_dbs/neo4j_community.py @@ -1,7 +1,7 @@ import json import re -from datetime import datetime +from datetime import datetime from typing import Any from memos.configs.graph_db import Neo4jGraphDBConfig @@ -137,17 +137,17 @@ def get_children_with_embeddings( # Search / recall operations def search_by_embedding( - self, - vector: list[float], - top_k: int = 5, - 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, - **kwargs, + self, + vector: list[float], + top_k: int = 5, + 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, + **kwargs, ) -> list[dict]: """ Retrieve node IDs based on vector similarity using external vector DB. @@ -197,7 +197,9 @@ def search_by_embedding( vec_results = [] if self.vec_db: try: - vec_results = self.vec_db.search(query_vector=vector, top_k=top_k, filter=vec_filter) + vec_results = self.vec_db.search( + query_vector=vector, top_k=top_k, filter=vec_filter + ) except Exception as e: logger.warning(f"[VecDB] search failed: {e}") @@ -264,9 +266,7 @@ def search_by_embedding( # Filter vector results by Neo4j filtered IDs and return with scores filtered_results = [ - {"id": r.id, "score": r.score} - for r in vec_results - if r.id in filtered_ids + {"id": r.id, "score": r.score} for r in vec_results if r.id in filtered_ids ] return filtered_results @@ -309,10 +309,10 @@ def _normalize_date_string(self, date_str: str) -> str: return date_str def _build_filter_conditions_cypher( - self, - filter: dict | None, - param_counter_start: int = 0, - node_alias: str = "node", + self, + filter: dict | None, + param_counter_start: int = 0, + node_alias: str = "node", ) -> tuple[list[str], dict[str, Any]]: """ Build filter conditions for Cypher queries with date normalization. @@ -328,11 +328,7 @@ def _build_filter_conditions_cypher( Returns: Tuple of (condition_strings_list, parameters_dict) """ - # Preprocess filter to normalize date strings - if filter: - normalized_filter = self._normalize_filter_dates(filter) - else: - normalized_filter = filter + normalized_filter = self._normalize_filter_dates(filter) if filter else filter # Call parent method with normalized filter return super()._build_filter_conditions_cypher( @@ -439,7 +435,11 @@ def _normalize_condition_dates(self, condition: dict) -> dict: return normalized def get_all_memory_items( - self, scope: str, filter: dict | None = None, knowledgebase_ids: list[str] | None = None, **kwargs + self, + scope: str, + filter: dict | None = None, + knowledgebase_ids: list[str] | None = None, + **kwargs, ) -> list[dict]: """ Retrieve all memory items of a specific memory_type. @@ -453,8 +453,12 @@ def get_all_memory_items( Returns: list[dict]: Full list of memory items under this scope. """ - logger.info(f"[get_all_memory_items] scope: {scope}, filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") - print(f"[get_all_memory_items] scope: {scope}, filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") + logger.info( + f"[get_all_memory_items] scope: {scope}, filter: {filter}, knowledgebase_ids: {knowledgebase_ids}" + ) + print( + f"[get_all_memory_items] scope: {scope}, filter: {filter}, knowledgebase_ids: {knowledgebase_ids}" + ) user_name = kwargs.get("user_name") if kwargs.get("user_name") else self.config.user_name if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"}: @@ -508,7 +512,11 @@ def get_all_memory_items( return [self._parse_node(dict(record["n"])) for record in results] def get_by_metadata( - self, filters: list[dict[str, Any]], user_name: str | None = None, filter: dict | None = None, knowledgebase_ids: list[str] | None = None + self, + filters: list[dict[str, Any]], + user_name: str | None = None, + filter: dict | None = None, + knowledgebase_ids: list[str] | None = None, ) -> list[str]: """ Retrieve node IDs that match given metadata filters. @@ -532,8 +540,12 @@ def get_by_metadata( - Supports structured querying such as tag/category/importance/time filtering. - Can be used for faceted recall or prefiltering before embedding rerank. """ - logger.info(f"[get_by_metadata] filters: {filters},user_name: {user_name},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") - print(f"[get_by_metadata] filters: {filters},user_name: {user_name},filter: {filter},knowledgebase_ids: {knowledgebase_ids}") + logger.info( + f"[get_by_metadata] filters: {filters},user_name: {user_name},filter: {filter},knowledgebase_ids: {knowledgebase_ids}" + ) + print( + f"[get_by_metadata] filters: {filters},user_name: {user_name},filter: {filter},knowledgebase_ids: {knowledgebase_ids}" + ) user_name = user_name if user_name else self.config.user_name where_clauses = [] params = {} @@ -570,14 +582,14 @@ def get_by_metadata( user_name_conditions = [] if not self.config.use_multi_db and (self.config.user_name or user_name): user_name_conditions.append("n.user_name = $user_name") - + # Add knowledgebase_ids conditions (checking user_name field in the data) if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: for idx, kb_id in enumerate(knowledgebase_ids): if isinstance(kb_id, str): param_name = f"kb_id_{idx}" user_name_conditions.append(f"n.user_name = ${param_name}") - + # Add user_name WHERE clause if user_name_conditions: if len(user_name_conditions) == 1: @@ -589,7 +601,9 @@ def get_by_metadata( filter_params = {} if filter: # Helper function to build a single filter condition - def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[str, dict]: + def build_filter_condition( + condition_dict: dict, param_counter: list + ) -> tuple[str, dict]: """Build a WHERE condition for a single filter item. Args: @@ -609,23 +623,20 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s for op, op_value in value.items(): if op in ("gt", "lt", "gte", "lte"): # Map operator to Cypher operator - cypher_op_map = { - "gt": ">", - "lt": "<", - "gte": ">=", - "lte": "<=" - } + cypher_op_map = {"gt": ">", "lt": "<", "gte": ">=", "lte": "<="} cypher_op = cypher_op_map[op] - + # All fields are stored as flat properties in Neo4j param_name = f"filter_meta_{key}_{op}_{param_counter[0]}" param_counter[0] += 1 filter_params_inner[param_name] = op_value - + # Check if field is a date field (created_at, updated_at, etc.) # Use datetime() function for date comparisons if key in ("created_at", "updated_at") or key.endswith("_at"): - condition_parts.append(f"n.{key} {cypher_op} datetime(${param_name})") + condition_parts.append( + f"n.{key} {cypher_op} datetime(${param_name})" + ) else: condition_parts.append(f"n.{key} {cypher_op} ${param_name}") else: @@ -639,7 +650,8 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s # Process filter structure param_counter = [ - len(filters)] # Use list to allow modification in nested function, start from len(filters) to avoid conflicts + len(filters) + ] # Use list to allow modification in nested function, start from len(filters) to avoid conflicts if isinstance(filter, dict): if "or" in filter: @@ -647,7 +659,9 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s or_conditions = [] for condition in filter["or"]: if isinstance(condition, dict): - condition_str, filter_params_inner = build_filter_condition(condition, param_counter) + condition_str, filter_params_inner = build_filter_condition( + condition, param_counter + ) if condition_str: or_conditions.append(f"({condition_str})") filter_params.update(filter_params_inner) @@ -658,7 +672,9 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s # AND logic: all conditions must match for condition in filter["and"]: if isinstance(condition, dict): - condition_str, filter_params_inner = build_filter_condition(condition, param_counter) + condition_str, filter_params_inner = build_filter_condition( + condition, param_counter + ) if condition_str: where_clauses.append(f"({condition_str})") filter_params.update(filter_params_inner) @@ -672,14 +688,14 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s # Add user_name parameter if not self.config.use_multi_db and (self.config.user_name or user_name): params["user_name"] = user_name - + # Add knowledgebase_ids parameters if knowledgebase_ids and isinstance(knowledgebase_ids, list) and len(knowledgebase_ids) > 0: for idx, kb_id in enumerate(knowledgebase_ids): if isinstance(kb_id, str): param_name = f"kb_id_{idx}" params[param_name] = kb_id - + # Merge filter parameters if filter_params: params.update(filter_params) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 554651f44..2ef5b5dbf 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -13,6 +13,7 @@ from memos.log import get_logger from memos.utils import timed + logger = get_logger(__name__) @@ -360,11 +361,11 @@ def _create_graph(self): self._return_connection(conn) def create_index( - self, - label: str = "Memory", - vector_property: str = "embedding", - dimensions: int = 1024, - index_name: str = "memory_vector_index", + self, + label: str = "Memory", + vector_property: str = "embedding", + dimensions: int = 1024, + index_name: str = "memory_vector_index", ) -> None: """ Create indexes for embedding and other fields. @@ -448,7 +449,7 @@ def node_not_exist(self, scope: str, user_name: str | None = None) -> int: @timed def remove_oldest_memory( - self, memory_type: str, keep_latest: int, user_name: str | None = None + self, memory_type: str, keep_latest: int, user_name: str | None = None ) -> None: """ Remove all WorkingMemory nodes except the latest `keep_latest` entries. @@ -686,7 +687,7 @@ def create_edge(self): @timed def add_edge( - self, source_id: str, target_id: str, type: str, user_name: str | None = None + self, source_id: str, target_id: str, type: str, user_name: str | None = None ) -> None: if not source_id or not target_id: logger.warning(f"Edge '{source_id}' and '{target_id}' are both None") @@ -752,7 +753,7 @@ def delete_edge(self, source_id: str, target_id: str, type: str) -> None: @timed def edge_exists_old( - self, source_id: str, target_id: str, type: str = "ANY", direction: str = "OUTGOING" + self, source_id: str, target_id: str, type: str = "ANY", direction: str = "OUTGOING" ) -> bool: """ Check if an edge exists between two nodes. @@ -814,12 +815,12 @@ def edge_exists_old( @timed def edge_exists( - self, - source_id: str, - target_id: str, - type: str = "ANY", - direction: str = "OUTGOING", - user_name: str | None = None, + self, + source_id: str, + target_id: str, + type: str = "ANY", + direction: str = "OUTGOING", + user_name: str | None = None, ) -> bool: """ Check if an edge exists between two nodes. @@ -869,7 +870,7 @@ def edge_exists( @timed def get_node( - self, id: str, include_embedding: bool = False, user_name: str | None = None + self, id: str, include_embedding: bool = False, user_name: str | None = None ) -> dict[str, Any] | None: """ Retrieve a Memory node by its unique ID. @@ -949,7 +950,7 @@ def get_node( @timed def get_nodes( - self, ids: list[str], user_name: str | None = None, **kwargs + self, ids: list[str], user_name: str | None = None, **kwargs ) -> list[dict[str, Any]]: """ Retrieve the metadata and memory of a list of nodes. @@ -1031,7 +1032,7 @@ def get_nodes( @timed def get_edges_old( - self, id: str, type: str = "ANY", direction: str = "ANY" + self, id: str, type: str = "ANY", direction: str = "ANY" ) -> list[dict[str, str]]: """ Get edges connected to a node, with optional type and direction filter. @@ -1117,18 +1118,18 @@ def get_edges_old( return edges def get_neighbors( - self, id: str, type: str, direction: Literal["in", "out", "both"] = "out" + self, id: str, type: str, direction: Literal["in", "out", "both"] = "out" ) -> list[str]: """Get connected node IDs in a specific direction and relationship type.""" raise NotImplementedError @timed def get_neighbors_by_tag_old( - self, - tags: list[str], - exclude_ids: list[str], - top_k: int = 5, - min_overlap: int = 1, + self, + tags: list[str], + exclude_ids: list[str], + top_k: int = 5, + min_overlap: int = 1, ) -> list[dict[str, Any]]: """ Find top-K neighbor nodes with maximum tag overlap. @@ -1225,7 +1226,7 @@ def get_neighbors_by_tag_old( @timed def get_children_with_embeddings( - self, id: str, user_name: str | None = None + self, id: str, user_name: str | None = None ) -> list[dict[str, Any]]: """Get children nodes with their embeddings.""" user_name = user_name if user_name else self._get_config_value("user_name") @@ -1311,11 +1312,11 @@ def get_path(self, source_id: str, target_id: str, max_depth: int = 3) -> list[s @timed def get_subgraph( - self, - center_id: str, - depth: int = 2, - center_status: str = "activated", - user_name: str | None = None, + self, + center_id: str, + depth: int = 2, + center_status: str = "activated", + user_name: str | None = None, ) -> dict[str, Any]: """ Retrieve a local subgraph centered at a given node. @@ -1452,23 +1453,25 @@ def get_context_chain(self, id: str, type: str = "FOLLOWS") -> list[str]: @timed def search_by_embedding( - self, - vector: list[float], - top_k: int = 5, - 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, - **kwargs, + self, + vector: list[float], + top_k: int = 5, + 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, + **kwargs, ) -> list[dict]: """ Retrieve node IDs based on vector similarity using PostgreSQL vector operations. """ # Build WHERE clause dynamically like nebular.py - logger.info(f"[search_by_embedding] filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") + logger.info( + f"[search_by_embedding] filter: {filter}, knowledgebase_ids: {knowledgebase_ids}" + ) print(f"[search_by_embedding] filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") where_clauses = [] if scope: @@ -1549,21 +1552,17 @@ def search_by_embedding( # Use string format directly in query instead of parameterized query # Replace %s with the vector string, but need to quote it properly # PostgreSQL vector type needs the string to be quoted - query = query.replace('%s::vector(1024)', f"'{vector_str}'::vector(1024)") + query = query.replace("%s::vector(1024)", f"'{vector_str}'::vector(1024)") params = [] - logger.debug(f"search_by_embedding query: {query}") - logger.debug(f"search_by_embedding params: {params}") - - # Format SQL query for better console display (prevent truncation) - print("=== SQL Query ===") - # print(query) # Split query by lines and wrap long lines to prevent terminal truncation - query_lines = query.strip().split('\n') + query_lines = query.strip().split("\n") for line in query_lines: # Wrap lines longer than 200 characters to prevent terminal truncation if len(line) > 200: - wrapped_lines = textwrap.wrap(line, width=200, break_long_words=False, break_on_hyphens=False) + wrapped_lines = textwrap.wrap( + line, width=200, break_long_words=False, break_on_hyphens=False + ) for wrapped_line in wrapped_lines: print(wrapped_line) else: @@ -1584,7 +1583,9 @@ def search_by_embedding( except Exception as e: logger.error(f"[search_by_embedding] Error executing query: {e}") logger.error(f"[search_by_embedding] Query length: {len(query)}") - logger.error(f"[search_by_embedding] Params type: {type(params)}, length: {len(params)}") + logger.error( + f"[search_by_embedding] Params type: {type(params)}, length: {len(params)}" + ) logger.error(f"[search_by_embedding] Query contains %s: {'%s' in query}") raise results = cursor.fetchall() @@ -1613,11 +1614,11 @@ def search_by_embedding( @timed def get_by_metadata( - self, - filters: list[dict[str, Any]], - user_name: str | None = None, - filter: dict | None = None, - knowledgebase_ids: list | None = None, + self, + filters: list[dict[str, Any]], + user_name: str | None = None, + filter: dict | None = None, + knowledgebase_ids: list | None = None, ) -> list[str]: """ Retrieve node IDs that match given metadata filters. @@ -1737,11 +1738,11 @@ def get_by_metadata( @timed def get_grouped_counts1( - self, - group_fields: list[str], - where_clause: str = "", - params: dict[str, Any] | None = None, - user_name: str | None = None, + self, + group_fields: list[str], + where_clause: str = "", + params: dict[str, Any] | None = None, + user_name: str | None = None, ) -> list[dict[str, Any]]: """ Count nodes grouped by any fields. @@ -1813,11 +1814,11 @@ def get_grouped_counts1( @timed def get_grouped_counts( - self, - group_fields: list[str], - where_clause: str = "", - params: dict[str, Any] | None = None, - user_name: str | None = None, + self, + group_fields: list[str], + where_clause: str = "", + params: dict[str, Any] | None = None, + user_name: str | None = None, ) -> list[dict[str, Any]]: """ Count nodes grouped by any fields. @@ -1956,7 +1957,7 @@ def clear(self, user_name: str | None = None) -> None: @timed def export_graph( - self, include_embedding: bool = False, user_name: str | None = None + self, include_embedding: bool = False, user_name: str | None = None ) -> dict[str, Any]: """ Export all graph nodes and edges in a structured form. @@ -2055,9 +2056,9 @@ def export_graph( else str(source_agtype) ) if ( - isinstance(source_raw, str) - and source_raw.startswith('"') - and source_raw.endswith('"') + isinstance(source_raw, str) + and source_raw.startswith('"') + and source_raw.endswith('"') ): source = source_raw[1:-1] else: @@ -2070,9 +2071,9 @@ def export_graph( else str(target_agtype) ) if ( - isinstance(target_raw, str) - and target_raw.startswith('"') - and target_raw.endswith('"') + isinstance(target_raw, str) + and target_raw.startswith('"') + and target_raw.endswith('"') ): target = target_raw[1:-1] else: @@ -2083,9 +2084,9 @@ def export_graph( edge_agtype.value if hasattr(edge_agtype, "value") else str(edge_agtype) ) if ( - isinstance(type_raw, str) - and type_raw.startswith('"') - and type_raw.endswith('"') + isinstance(type_raw, str) + and type_raw.startswith('"') + and type_raw.endswith('"') ): edge_type = type_raw[1:-1] else: @@ -2128,12 +2129,12 @@ def count_nodes(self, scope: str, user_name: str | None = None) -> int: @timed def get_all_memory_items( - self, - scope: str, - include_embedding: bool = False, - user_name: str | None = None, - filter: dict | None = None, - knowledgebase_ids: list | None = None, + self, + scope: str, + include_embedding: bool = False, + user_name: str | None = None, + filter: dict | None = None, + knowledgebase_ids: list | None = None, ) -> list[dict]: """ Retrieve all memory items of a specific memory_type. @@ -2146,7 +2147,9 @@ def get_all_memory_items( Returns: list[dict]: Full list of memory items under this scope. """ - logger.info(f"[get_all_memory_items] filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") + logger.info( + f"[get_all_memory_items] filter: {filter}, knowledgebase_ids: {knowledgebase_ids}" + ) print(f"[get_all_memory_items] filter: {filter}, knowledgebase_ids: {knowledgebase_ids}") user_name = user_name if user_name else self._get_config_value("user_name") @@ -2280,7 +2283,7 @@ def get_all_memory_items( return nodes def get_all_memory_items_old( - self, scope: str, include_embedding: bool = False, user_name: str | None = None + self, scope: str, include_embedding: bool = False, user_name: str | None = None ) -> list[dict]: """ Retrieve all memory items of a specific memory_type. @@ -2387,7 +2390,7 @@ def get_all_memory_items_old( @timed def get_structure_optimization_candidates( - self, scope: str, include_embedding: bool = False, user_name: str | None = None + self, scope: str, include_embedding: bool = False, user_name: str | None = None ) -> list[dict]: """ Find nodes that are likely candidates for structure optimization: @@ -2533,7 +2536,7 @@ def get_structure_optimization_candidates( value = row[i] # Handle special fields if field_name in ["tags", "sources", "usage"] and isinstance( - value, str + value, str ): try: # Try parsing JSON string @@ -2599,10 +2602,10 @@ def _strip_wrapping_quotes(value: Any) -> Any: return value """ if ( - isinstance(value, str) - and len(value) >= 2 - and value[0] == value[-1] - and value[0] in ("'", '"') + isinstance(value, str) + and len(value) >= 2 + and value[0] == value[-1] + and value[0] in ("'", '"') ): return value[1:-1] return value @@ -2627,7 +2630,7 @@ def __del__(self): @timed def add_node( - self, id: str, memory: str, metadata: dict[str, Any], user_name: str | None = None + self, id: str, memory: str, metadata: dict[str, Any], user_name: str | None = None ) -> None: """Add a memory node to the graph.""" logger.info(f"[add_node] id: {id}, memory: {memory}, metadata: {metadata}") @@ -2714,9 +2717,11 @@ def add_node( insert_query, (id, json.dumps(properties), json.dumps(embedding_vector)) ) logger.info( - f"[add_node] [embedding_vector-true] insert_query: {insert_query}, properties: {json.dumps(properties)}") + f"[add_node] [embedding_vector-true] insert_query: {insert_query}, properties: {json.dumps(properties)}" + ) print( - f"[add_node] [embedding_vector-true] insert_query: {insert_query}, properties: {json.dumps(properties)}") + f"[add_node] [embedding_vector-true] insert_query: {insert_query}, properties: {json.dumps(properties)}" + ) else: insert_query = f""" INSERT INTO {self.db_name}_graph."Memory"(id, properties) @@ -2727,9 +2732,11 @@ def add_node( """ cursor.execute(insert_query, (id, json.dumps(properties))) logger.info( - f"[add_node] [embedding_vector-false] insert_query: {insert_query}, properties: {json.dumps(properties)}") + f"[add_node] [embedding_vector-false] insert_query: {insert_query}, properties: {json.dumps(properties)}" + ) print( - f"[add_node] [embedding_vector-false] insert_query: {insert_query}, properties: {json.dumps(properties)}") + f"[add_node] [embedding_vector-false] insert_query: {insert_query}, properties: {json.dumps(properties)}" + ) finally: logger.info(f"In add node polardb: id-{id} memory-{memory} query-{insert_query}") @@ -2767,13 +2774,13 @@ def _build_node_from_agtype(self, node_agtype, embedding=None): @timed def get_neighbors_by_tag( - self, - tags: list[str], - exclude_ids: list[str], - top_k: int = 5, - min_overlap: int = 1, - include_embedding: bool = False, - user_name: str | None = None, + self, + tags: list[str], + exclude_ids: list[str], + top_k: int = 5, + min_overlap: int = 1, + include_embedding: bool = False, + user_name: str | None = None, ) -> list[dict[str, Any]]: """ Find top-K neighbor nodes with maximum tag overlap. @@ -2895,13 +2902,13 @@ def get_neighbors_by_tag( self._return_connection(conn) def get_neighbors_by_tag_ccl( - self, - tags: list[str], - exclude_ids: list[str], - top_k: int = 5, - min_overlap: int = 1, - include_embedding: bool = False, - user_name: str | None = None, + self, + tags: list[str], + exclude_ids: list[str], + top_k: int = 5, + min_overlap: int = 1, + include_embedding: bool = False, + user_name: str | None = None, ) -> list[dict[str, Any]]: """ Find top-K neighbor nodes with maximum tag overlap. @@ -3092,7 +3099,7 @@ def import_graph(self, data: dict[str, Any], user_name: str | None = None) -> No @timed def get_edges( - self, id: str, type: str = "ANY", direction: str = "ANY", user_name: str | None = None + self, id: str, type: str = "ANY", direction: str = "ANY", user_name: str | None = None ) -> list[dict[str, str]]: """ Get edges connected to a node, with optional type and direction filter. @@ -3149,9 +3156,9 @@ def get_edges( # Extract and clean from_id from_id_raw = row[0].value if hasattr(row[0], "value") else row[0] if ( - isinstance(from_id_raw, str) - and from_id_raw.startswith('"') - and from_id_raw.endswith('"') + isinstance(from_id_raw, str) + and from_id_raw.startswith('"') + and from_id_raw.endswith('"') ): from_id = from_id_raw[1:-1] else: @@ -3160,9 +3167,9 @@ def get_edges( # Extract and clean to_id to_id_raw = row[1].value if hasattr(row[1], "value") else row[1] if ( - isinstance(to_id_raw, str) - and to_id_raw.startswith('"') - and to_id_raw.endswith('"') + isinstance(to_id_raw, str) + and to_id_raw.startswith('"') + and to_id_raw.endswith('"') ): to_id = to_id_raw[1:-1] else: @@ -3171,9 +3178,9 @@ def get_edges( # Extract and clean edge_type edge_type_raw = row[2].value if hasattr(row[2], "value") else row[2] if ( - isinstance(edge_type_raw, str) - and edge_type_raw.startswith('"') - and edge_type_raw.endswith('"') + isinstance(edge_type_raw, str) + and edge_type_raw.startswith('"') + and edge_type_raw.endswith('"') ): edge_type = edge_type_raw[1:-1] else: @@ -3228,10 +3235,10 @@ def format_param_value(self, value: str | None) -> str: return f'"{value}"' def _build_user_name_and_kb_ids_conditions_cypher( - self, - user_name: str | None, - knowledgebase_ids: list | None, - default_user_name: str | None = None, + self, + user_name: str | None, + knowledgebase_ids: list | None, + default_user_name: str | None = None, ) -> list[str]: """ Build user_name and knowledgebase_ids conditions for Cypher queries. @@ -3261,10 +3268,10 @@ def _build_user_name_and_kb_ids_conditions_cypher( return user_name_conditions def _build_user_name_and_kb_ids_conditions_sql( - self, - user_name: str | None, - knowledgebase_ids: list | None, - default_user_name: str | None = None, + self, + user_name: str | None, + knowledgebase_ids: list | None, + default_user_name: str | None = None, ) -> list[str]: """ Build user_name and knowledgebase_ids conditions for SQL queries. @@ -3296,8 +3303,8 @@ def _build_user_name_and_kb_ids_conditions_sql( return user_name_conditions def _build_filter_conditions_cypher( - self, - filter: dict | None, + self, + filter: dict | None, ) -> str: """ Build filter conditions for Cypher queries. @@ -3325,12 +3332,7 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: for op, op_value in value.items(): if op in ("gt", "lt", "gte", "lte"): # Map operator to Cypher operator - cypher_op_map = { - "gt": ">", - "lt": "<", - "gte": ">=", - "lte": "<=" - } + cypher_op_map = {"gt": ">", "lt": "<", "gte": ">=", "lte": "<="} cypher_op = cypher_op_map[op] # Check if key starts with "info." prefix (for nested fields like info.A, info.B) @@ -3339,14 +3341,20 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: info_field = key[5:] # Remove "info." prefix if isinstance(op_value, str): escaped_value = escape_cypher_string(op_value) - condition_parts.append(f"n.info.{info_field} {cypher_op} '{escaped_value}'") + condition_parts.append( + f"n.info.{info_field} {cypher_op} '{escaped_value}'" + ) else: - condition_parts.append(f"n.info.{info_field} {cypher_op} {op_value}") + condition_parts.append( + f"n.info.{info_field} {cypher_op} {op_value}" + ) else: # Direct property access (e.g., "created_at" is directly in n, not in n.info) if isinstance(op_value, str): escaped_value = escape_cypher_string(op_value) - condition_parts.append(f"n.{key} {cypher_op} '{escaped_value}'") + condition_parts.append( + f"n.{key} {cypher_op} '{escaped_value}'" + ) else: condition_parts.append(f"n.{key} {cypher_op} {op_value}") elif op == "=": @@ -3361,22 +3369,37 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: # For array fields, check if array exactly equals [value] # For scalar fields, use = if info_field in ("tags", "sources"): - condition_parts.append(f"n.info.{info_field} = ['{escaped_value}']") + condition_parts.append( + f"n.info.{info_field} = ['{escaped_value}']" + ) else: - condition_parts.append(f"n.info.{info_field} = '{escaped_value}'") + condition_parts.append( + f"n.info.{info_field} = '{escaped_value}'" + ) elif isinstance(op_value, list): # For array fields, format list as Cypher array if info_field in ("tags", "sources"): - escaped_items = [f"'{escape_cypher_string(str(item))}'" for item in op_value] + escaped_items = [ + f"'{escape_cypher_string(str(item))}'" + for item in op_value + ] array_str = "[" + ", ".join(escaped_items) + "]" - condition_parts.append(f"n.info.{info_field} = {array_str}") + condition_parts.append( + f"n.info.{info_field} = {array_str}" + ) else: - condition_parts.append(f"n.info.{info_field} = {op_value}") + condition_parts.append( + f"n.info.{info_field} = {op_value}" + ) else: if info_field in ("tags", "sources"): - condition_parts.append(f"n.info.{info_field} = [{op_value}]") + condition_parts.append( + f"n.info.{info_field} = [{op_value}]" + ) else: - condition_parts.append(f"n.info.{info_field} = {op_value}") + condition_parts.append( + f"n.info.{info_field} = {op_value}" + ) else: # Direct property access if isinstance(op_value, str): @@ -3390,7 +3413,10 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: elif isinstance(op_value, list): # For array fields, format list as Cypher array if key in ("tags", "sources"): - escaped_items = [f"'{escape_cypher_string(str(item))}'" for item in op_value] + escaped_items = [ + f"'{escape_cypher_string(str(item))}'" + for item in op_value + ] array_str = "[" + ", ".join(escaped_items) + "]" condition_parts.append(f"n.{key} = {array_str}") else: @@ -3407,7 +3433,9 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: info_field = key[5:] # Remove "info." prefix if isinstance(op_value, str): escaped_value = escape_cypher_string(op_value) - condition_parts.append(f"'{escaped_value}' IN n.info.{info_field}") + condition_parts.append( + f"'{escaped_value}' IN n.info.{info_field}" + ) else: condition_parts.append(f"{op_value} IN n.info.{info_field}") else: @@ -3424,14 +3452,20 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: info_field = key[5:] # Remove "info." prefix if isinstance(op_value, str): escaped_value = escape_cypher_string(op_value) - condition_parts.append(f"n.info.{info_field} CONTAINS '{escaped_value}'") + condition_parts.append( + f"n.info.{info_field} CONTAINS '{escaped_value}'" + ) else: - condition_parts.append(f"n.info.{info_field} CONTAINS {op_value}") + condition_parts.append( + f"n.info.{info_field} CONTAINS {op_value}" + ) else: # Direct property access if isinstance(op_value, str): escaped_value = escape_cypher_string(op_value) - condition_parts.append(f"n.{key} CONTAINS '{escaped_value}'") + condition_parts.append( + f"n.{key} CONTAINS '{escaped_value}'" + ) else: condition_parts.append(f"n.{key} CONTAINS {op_value}") # Check if key starts with "info." prefix (for simple equality) @@ -3475,8 +3509,8 @@ def build_cypher_filter_condition(condition_dict: dict) -> str: return filter_where_clause def _build_filter_conditions_sql( - self, - filter: dict | None, + self, + filter: dict | None, ) -> list[str]: """ Build filter conditions for SQL queries. @@ -3506,12 +3540,7 @@ def build_filter_condition(condition_dict: dict) -> str: for op, op_value in value.items(): if op in ("gt", "lt", "gte", "lte"): # Map operator to SQL operator - sql_op_map = { - "gt": ">", - "lt": "<", - "gte": ">=", - "lte": "<=" - } + sql_op_map = {"gt": ">", "lt": "<", "gte": ">=", "lte": "<="} sql_op = sql_op_map[op] # Check if key starts with "info." prefix (for nested fields like info.A, info.B) @@ -3560,7 +3589,9 @@ def build_filter_condition(condition_dict: dict) -> str: elif isinstance(op_value, list): # For array fields, format list as JSON array string if info_field in ("tags", "sources"): - escaped_items = [escape_sql_string(str(item)) for item in op_value] + escaped_items = [ + escape_sql_string(str(item)) for item in op_value + ] json_array = json.dumps(escaped_items) condition_parts.append( f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) = '{json_array}'::agtype" @@ -3595,7 +3626,9 @@ def build_filter_condition(condition_dict: dict) -> str: elif isinstance(op_value, list): # For array fields, format list as JSON array string if key in ("tags", "sources"): - escaped_items = [escape_sql_string(str(item)) for item in op_value] + escaped_items = [ + escape_sql_string(str(item)) for item in op_value + ] json_array = json.dumps(escaped_items) condition_parts.append( f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) = '{json_array}'::agtype" @@ -3645,7 +3678,11 @@ def build_filter_condition(condition_dict: dict) -> str: info_field = key[5:] # Remove "info." prefix if isinstance(op_value, str): # Escape SQL special characters for LIKE: % and _ need to be escaped - escaped_value = escape_sql_string(op_value).replace("%", "\\%").replace("_", "\\_") + escaped_value = ( + escape_sql_string(op_value) + .replace("%", "\\%") + .replace("_", "\\_") + ) condition_parts.append( f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype)::text LIKE '%{escaped_value}%'" ) @@ -3657,7 +3694,11 @@ def build_filter_condition(condition_dict: dict) -> str: # Direct property access if isinstance(op_value, str): # Escape SQL special characters for LIKE: % and _ need to be escaped - escaped_value = escape_sql_string(op_value).replace("%", "\\%").replace("_", "\\_") + escaped_value = ( + escape_sql_string(op_value) + .replace("%", "\\%") + .replace("_", "\\_") + ) condition_parts.append( f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype)::text LIKE '%{escaped_value}%'" ) @@ -3714,34 +3755,54 @@ def build_filter_condition(condition_dict: dict) -> str: return filter_conditions - def parse_filter(self, filter_dict: dict | None = None, ): + def parse_filter( + self, + filter_dict: dict | None = None, + ): if filter_dict is None: return None full_fields = { - "id", "key", "tags", "type", "usage", "memory", "status", "sources", - "user_id", "graph_id", "user_name", "background", "confidence", - "created_at", "session_id", "updated_at", "memory_type", "node_type", "info" + "id", + "key", + "tags", + "type", + "usage", + "memory", + "status", + "sources", + "user_id", + "graph_id", + "user_name", + "background", + "confidence", + "created_at", + "session_id", + "updated_at", + "memory_type", + "node_type", + "info", + "sources", + "app_id", + "agent_id" } def process_condition(condition): - if not isinstance(condition, dict): return condition new_condition = {} for key, value in condition.items(): - - if key.lower() in ['or', 'and']: + if key.lower() in ["or", "and"]: if isinstance(value, list): - processed_items = [] for item in value: if isinstance(item, dict): processed_item = {} for item_key, item_value in item.items(): - - if item_key not in full_fields and not item_key.startswith('info.'): + if item_key not in full_fields and not item_key.startswith( + "info." + ): new_item_key = f"info.{item_key}" else: new_item_key = item_key @@ -3753,7 +3814,7 @@ def process_condition(condition): else: new_condition[key] = value else: - if key not in full_fields and not key.startswith('info.'): + if key not in full_fields and not key.startswith("info."): new_key = f"info.{key}" else: new_key = key @@ -3763,5 +3824,3 @@ def process_condition(condition): return new_condition return process_condition(filter_dict) - - From 0e7e1ee902c6de4afd9acc2b6dfd94ae43d78e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Fri, 28 Nov 2025 13:33:10 +0800 Subject: [PATCH 33/36] remove test --- examples/basic_modules/2.json | 1 - examples/basic_modules/polardb_search.py | 1385 ---------------------- 2 files changed, 1386 deletions(-) delete mode 100644 examples/basic_modules/2.json delete mode 100644 examples/basic_modules/polardb_search.py diff --git a/examples/basic_modules/2.json b/examples/basic_modules/2.json deleted file mode 100644 index a38bbb79c..000000000 --- a/examples/basic_modules/2.json +++ /dev/null @@ -1 +0,0 @@ -[{"id":"53cf595c-94dd-40a8-bded-d57a140a6045","memory":"[assistant观点]助手建议用户从基础语法开始学习Python编程,并通过多做练习项目来提高技能。","user_name":"memos65c3a65b78f74009bc927ee1981deb3a","user_id":"65c3a65b-78f7-4009-bc92-7ee1981deb3a","session_id":"conv_8_6_0dd3e083","status":"activated","key":"学习Python编程的建议","confidence":"0.99","tags":"[Python, 编程, 学习建议]","created_at":"2025-09-19T10:07:03.869349","updated_at":"2025-09-19T10:07:03.869302","memory_type":"LongTermMemory","sources":"[{\"type\":\"chat\",\"role\":\"user\",\"message_id\":\"1968980137033175041\",\"content\":\"Python编程怎么学\"}, {\"type\":\"chat\",\"role\":\"assistant\",\"message_id\":\"1968980137070923778\",\"content\":\"建议从基础语法开始,多做练习项目\"}]","node_type":"fact","usage":"[]","background":"用户询问如何学习Python编程,助手建议从基础语法开始,并通过多做练习项目来提高技能。","embedding_1024":"[0.014167483, 0.01231358, -0.009071156, -0.013763134, 0.01489989, -0.03982459, 0.0059851324, 0.020095397, 1.6927357E-5, 0.011985523, -0.03802409, -0.011298892, -0.0351555, -0.0046729045, 0.0057562552, 0.011588803, -0.03503343, 0.045042984, 0.05154309, -0.012145737, 0.037078068, 0.004318145, 0.08923149, 0.010375755, -0.025252758, 0.003263404, -0.016631724, -0.022872437, 0.04745382, -0.011756646, -0.017852401, -0.013930977, 0.025466375, -0.059904728, -0.058623016, -0.04678245, 0.0254206, -0.021285556, -0.032500528, 0.017821886, -0.005740997, -0.014320068, -0.023772687, 0.013511369, 0.018004987, -0.024978105, -0.017501459, -0.014457394, -0.0025596072, -0.04302887, 0.049406905, -0.017455682, 0.051573608, -0.0391227, -0.031127265, 0.03683393, 0.023711652, -0.01945454, -0.058439914, 0.031340882, -0.049864657, 0.010070586, -0.06445175, -0.012763705, -0.021621242, 0.027144806, -0.0071676634, 0.02462716, -0.025695253, 0.0069273426, -0.019424023, 0.024413541, -0.022124773, 0.008056468, -0.05432013, -0.026000421, -0.002168609, 2.8704986E-4, 0.012351726, 0.019347731, -0.015617037, 0.019195147, 0.04012976, 0.03286673, 0.0030745803, 0.0055159344, 0.011741388, 0.0661607, 0.025496893, 0.012588233, -0.0166775, -0.018310156, 0.04678245, -0.005706665, -0.020614184, -0.0056341877, -0.001375169, 0.041747157, -0.014007269, -0.011581174, 0.009895113, 0.025573185, 6.823394E-4, -0.024886554, 0.03637618, 0.04629418, 0.020125913, -0.013061245, 0.020400565, -0.039092183, 0.038115643, -0.002973493, 0.051878776, -0.006614544, 0.008796504, -0.015441565, -0.002687397, 0.017318357, 0.030852614, -0.005031478, 0.008323492, 0.021728052, 0.033507586, -0.036955997, 0.0254206, -0.020522634, -0.011466735, -0.047148652, 0.029128406, -0.010482565, 0.022109514, -0.011481994, -0.054075994, 0.012847627, -0.0412894, -0.039031148, 0.009971406, -0.03295828, -0.018340673, -0.051207405, 0.031920705, 0.03506395, 0.028075572, -0.035277568, 0.04806416, -0.08685117, 0.010101103, -0.06536726, -0.004596612, 0.003671568, -0.013274863, 0.013351155, 0.020278499, 0.029128406, -0.0112226, 7.229889E-5, -0.0315545, -0.008010693, -0.031249333, 0.009391584, -0.02552741, 0.03643721, -0.05959956, -0.00650392, 0.02026324, -0.012939177, 0.025252758, -0.016265523, -0.005004776, -0.032653112, 0.0082853455, 0.013625808, -0.03127985, 0.022384167, -0.009521281, 0.044615746, 0.0035495001, 0.02304028, 0.044066444, -0.037657887, -0.021575468, 0.027358426, 8.921433E-4, -0.07256925, -0.016616467, 0.002979215, 0.018798428, -0.012092332, 0.006862494, -0.008872797, -0.003913796, -0.009811192, 0.04727072, 0.02590887, 0.0076711923, -0.048979666, 0.021819603, -0.009948518, -0.010749588, -0.040831648, 0.013770763, 0.016708018, -0.021239782, -0.0564258, 0.0075720125, 0.0371391, -0.021316074, -0.052275497, 0.0019588051, -0.007835221, -0.0014180834, 4.963769E-4, -0.0027942061, -0.019485058, -0.02979978, 0.007327877, 0.011779534, 0.020247981, -0.008521852, -0.043852825, -0.02989133, 0.01002481, -0.019942813, -0.029174183, 0.020553151, -0.02323864, -2.0086336E-5, -0.023971045, 0.024245698, 0.037017033, -0.0078733675, 4.0530294E-4, 0.008819392, -0.019805485, 0.028746946, -0.03762737, -0.027663594, 0.008117503, 0.012229659, -0.0020885023, 0.011535399, -0.055479772, -0.013961494, -0.017242063, -0.019607125, 0.0020217465, -0.01489989, 0.029952364, -0.038756497, -0.0064505152, 0.030608477, 0.0047224946, -0.008201424, 0.006778572, 0.0078123333, 0.02809083, 0.026748087, 0.023650618, -0.071409605, -0.06475692, 0.020675218, 0.009910372, 0.028365484, 0.024184665, -0.01589932, 0.020171689, -0.027388941, -0.045470223, -0.031707086, 0.047514856, 0.022216322, -0.022124773, 0.008453188, -0.013343526, 0.020385306, 0.018539034, -0.010436789, -0.0045660953, 0.081663296, 0.0020427268, -0.022597784, 0.01638759, -0.0015859265, -0.019134114, 0.005687592, -0.015975611, -0.017409908, 0.021941671, 0.023513293, -0.003574295, -0.0013856592, -0.017531974, 0.06811378, -0.044798847, 0.0034274324, -0.0022696964, 0.022872437, -0.18908288, 0.011497253, 0.026839638, 0.044066444, -0.022063738, 0.025252758, -0.029174183, 0.033995856, -0.054381166, 0.057249755, 0.012778963, -0.036193077, 0.04000769, -0.023421742, 0.01716577, 0.018996786, -0.0033587692, 0.01032235, -0.0024318176, -0.037718922, 0.01609768, -0.066282764, 0.053557206, -0.03149347, 0.020110656, -0.028853754, 0.0028151865, -0.0060614245, -0.052489113, -0.005668519, -0.045531254, -0.0021705164, 0.007980176, 0.006362779, 0.0057257386, 0.016601209, -0.0020637072, 0.014182742, 0.015556004, -0.0024184664, 0.041380953, 0.016982669, 0.01032235, -0.004558466, 0.0059126546, -0.015441565, -0.011573545, 0.012786592, -0.021407625, -0.023894753, 0.029845554, -0.027358426, -3.9099812E-4, -0.025588444, -0.05581546, 0.007327877, 0.04379179, 0.07299649, 0.018539034, -0.009864597, -0.005134473, -0.011428589, -0.011749017, -0.016357074, -0.022048479, -1.538959E-4, 0.044951435, 0.030440634, 0.011092903, -0.029921846, 0.0044173254, -0.0023555253, -0.0034465054, -0.00794203, -9.6557464E-4, 0.0204616, 0.0064238133, 0.012466164, 0.02175857, -0.123593554, -0.009910372, -0.03842081, 0.019286698, 0.012397502, -0.0064962907, -0.012191513, 0.021865379, 0.03555222, 0.062681764, 0.26415452, 6.2130555E-4, -0.009322921, -0.017608266, 0.039733037, -0.042357493, -0.04144199, 0.0020465413, 0.0075224224, -0.04400541, 0.012138108, 0.04635521, -0.005531193, -0.03308035, 0.03445361, 0.0665269, -0.031646054, 0.008132761, 0.0094297305, 0.027037997, -0.005847806, -0.006233082, -0.0023021207, -0.009475506, -0.05978266, -0.04449368, -0.044768333, 0.044371612, -0.019973328, 0.04608056, -0.023070797, 0.06371935, 0.011298892, -0.012504311, -3.4021607E-4, 0.03167657, 0.067930676, -0.030303309, 0.017989729, -0.005996576, 0.034697745, -0.025008623, -0.0034236177, -0.02543586, -0.032836214, 0.020812545, -3.2042773E-4, -0.022414682, 0.038756497, -0.07726886, -0.004169375, -0.06713724, -0.051298954, -0.036498245, 0.013763134, 0.0043143304, -0.019866519, 0.009567057, -0.015823027, 0.021621242, 0.0044554714, 0.027373683, -0.011314151, 0.022613043, -0.024703452, -0.018355932, 0.024001563, -0.015288981, 0.032347944, -0.0031947407, 0.0019025396, 0.021895895, -0.0018243401, -0.033782236, -0.00580966, -0.024657678, 0.006931157, 0.035582736, -0.013190942, 0.012435648, -0.027633077, 0.006683207, -0.038756497, -0.012641637, -0.010001923, -0.020171689, 0.0053786086, 0.029219957, -0.021407625, 0.0073507647, -0.0029811224, -0.021895895, -0.028273933, 0.027083773, -0.033995856, 0.0055693393, -0.0055502662, -0.0028514254, -0.05062758, -0.013618179, -0.017852401, 0.034484126, 0.02175857, 0.0024203737, -0.026290333, -0.029174183, -0.03028805, 0.0025042952, -0.012092332, 0.014243776, -0.054045476, 0.010223171, 5.049598E-4, -0.06408554, 0.033355, 0.025771545, 0.07476647, 0.016311297, -0.007762743, 0.008033581, 0.034239992, 0.013099391, 0.030410118, 0.031112008, 0.0038508547, -0.023757428, -0.004051122, 0.03338552, -0.009391584, -0.0014705344, 0.012145737, -0.01231358, -0.0017928694, 0.049132254, -0.0022887695, -0.008964348, 0.04809468, -0.026107231, 0.046233144, -0.01599087, -0.04696555, -0.050291896, 0.007797075, -0.017028445, 0.026275074, 0.028945304, -0.028151864, -0.00402442, -0.016112937, -0.007903884, 0.0089414595, 0.0466909, 0.06524519, 0.005699036, -0.010253687, -0.071531676, -0.026336107, 0.012649266, 0.042052325, -0.0035495001, -0.007343136, 0.021377107, 0.029662453, 0.06658793, 0.011611691, 0.03726117, -0.028136607, 0.024657678, -0.011062386, -0.0214534, 0.013908089, -0.04080113, -0.039458387, 0.054564264, -0.025985163, -0.042357493, -0.04858295, -0.0021323704, -0.007835221, -0.07586508, 0.041747157, -0.014815968, -0.0023612473, 0.028457034, 0.053465657, -0.072935455, 0.011649837, 0.0072744726, 0.08074779, -0.04189974, -0.04302887, 0.08923149, 0.01806602, 3.5094467E-4, 0.015456824, 0.022689335, 0.046904515, -0.011001352, -0.0097349, -9.116932E-4, 0.027877213, 0.02114823, 0.012519569, 0.012542457, -0.034789298, -0.020415824, -0.0466909, 0.03793254, -0.009284775, 0.010337609, -0.0072821015, -0.024199924, -0.030165982, 0.0117185, 0.043242484, -0.008476077, 0.04510402, 0.06780861, -0.0022830477, 0.014213258, -0.00491704, 0.0074995346, -0.037383236, -0.022887696, -0.012069444, -0.010810621, 0.024062596, -0.04577539, -0.030364342, -0.014976182, -0.0036658458, 0.012832368, -0.0051878775, 0.01577725, -0.019149372, 0.0023250084, -0.015960352, 0.04012976, -0.0029315322, -0.03167657, -0.028960563, 0.06341417, 0.010238429, 0.06359728, -0.042723697, 0.0064238133, 0.0051192143, -0.023177605, 0.004955186, 0.010116361, 0.024245698, -0.08471499, -0.004680534, 0.017806627, 0.018157572, 0.030577961, 0.007926771, -0.0042914427, -0.034178957, -0.0113065215, 0.008605774, -0.028396001, -0.010963206, 0.0206447, -0.021224523, -0.055601843, 0.06506209, -0.040282343, -0.024276216, -0.019057821, -0.021621242, 0.005370979, -0.0067518703, -0.013175683, 0.032500528, -0.0024871295, -0.018752651, -0.007030337, 0.041045267, -0.07317959, -0.049223803, 0.027037997, 0.0224452, -0.009155078, 0.03637618, 0.022002704, -0.018722136, 0.010909801, 0.031707086, -0.016051903, 0.001771889, 0.001876791, -0.03366017, 0.0625597, 0.03890908, 0.0094297305, -0.032164842, 0.01818809, 3.888524E-4, -0.03286673, -0.017348872, -0.053252038, -0.0057257386, -0.008086986, -0.027541526, 0.021682277, -0.015960352, -0.010436789, 0.042540595, 0.029570902, 0.0069654887, -0.061155923, 0.008178537, 0.025969904, 0.033202417, -0.01161932, 0.044920918, 8.8308356E-4, 0.017333614, 0.006088127, -0.020827802, -0.014579462, -0.007556754, -0.007087556, -0.008811763, -0.0011958821, 0.002334545, -5.2689382E-5, -0.03637618, -0.0043067015, -0.028914789, 0.014587091, -0.012756076, 0.01658595, -0.019210406, -0.014022528, 0.033690687, -0.023375966, 0.023848979, -0.029372543, 0.033904307, -0.019027304, 0.072508216, -0.031859674, 0.04193026, -0.00580966, -0.008003064, -0.019134114, 0.0070799273, 0.010780104, 0.012099962, 0.011893972, -0.020110656, -0.01589932, 0.061644193, -0.008422672, -0.009895113, 0.014060674, 0.024672935, -0.010741958, -0.04647728, 0.053465657, 0.0056723338, -0.052641697, -0.06384141, 0.00933055, 0.10644304, -0.06420761, -0.008811763, 0.0049475566, -0.0389396, 0.07903884, 0.0236201, 0.036955997, -0.006763314, 0.039977174, -0.0065382514, 0.030608477, 0.008010693, 0.0024547053, -0.017409908, -0.033965338, -0.04788106, -0.025573185, -0.0011472458, -0.01669276, -0.020888837, -0.008437931, 0.044768333, 0.032408975, -0.023711652, -0.038756497, 0.007598715, -0.0041159703, -0.11779534, 0.026183523, -0.020003846, -0.015243205, -0.05111585, -0.021010904, -0.0389396, 0.0103299795, -0.011230229, -0.020385306, 0.007453759, 0.039885625, 0.028823238, -0.032592077, -0.017791368, 0.04580591, 0.02004962, 0.012885773, 0.0059851324, 0.020720994, -0.007926771, -0.028182382, 0.04269318, -0.029570902, 0.0530079, 0.024352508, -0.023772687, -0.03286673, -0.0035838317, 0.009475506, 0.011886343, -0.07891677, 0.002513832, 0.027785663, -0.04788106, 0.02114823, -0.014541316, 0.004341033, 0.049376387, 0.0068434207, 0.023101313, 0.018996786, -0.026641278, 0.025237499, 0.023864238, 0.071653746, -5.779143E-4, -0.018539034, -0.033477068, 0.0076406756, -0.0030860242, -0.005172619, 0.008827021, 0.006793831, -0.030059174, 0.010673295, -0.07018893, 0.003753582, -0.0031050972, 0.031737603, -0.03408741, 0.051024303, -0.05230601, 0.015716217, -0.05459478, -0.019622384, -0.04577539, -0.0024127446, -0.018996786, -0.034636714, 0.016235005, 0.017913437, -0.027251616, 0.0015916484, 0.0012883865, 0.06902929, 0.009155078, 0.015304239, 0.020278499, -0.010604632, -0.0037612112, -0.006534437, -0.0068472354, 0.031401917, 0.03866495, -0.022460459, -0.028853754, 0.034392577, -0.004539393, -0.006980747, -0.036009975, -0.04678245, -0.050505515, -0.04171664, 0.014228517, 5.3595356E-4, -0.016006129, -0.012786592, 0.0022391796, -0.040373895, -0.010879285, -0.02601568, -0.017791368, -0.019515574, -0.028762205, 0.0057944013, 0.011085274, -0.03939735, 0.020614184, 0.020385306, 0.0032214432, -0.0013646788, 0.019851262, 0.004249482, -0.044432644, 0.0059813177, 0.012557715, 0.05062758, 0.017699817, -0.026839638, 0.037017033, -0.007549125, 0.023391224, 2.1814834E-5, 0.06329211, 0.010673295, -0.027022738, 0.008888055, 0.012443277, 0.005779143, -0.0103299795, -0.011466735, 0.03427051, 0.013770763, 0.033904307, 0.019210406, -0.0311883, 0.050078277, -0.026000421, -0.031829156, -9.093091E-4, 0.015243205, -0.011573545, -0.019729193, -0.0024814077, -0.0430899, 0.019546092, -0.015144025, 0.028151864, -0.028914789, 0.047148652, 0.009185595, -0.011428589, 0.018783169, -0.016952153, 0.052092396, -0.056517348, -0.023177605, 0.021132972, 0.06197988, 0.0371391, -0.02293347, -0.025451116, 0.025939388, 0.011558286, 0.021316074, -0.0070646685, 0.0044554714, -0.011825309, -0.020171689, 0.037474785, 0.03097468, -0.018691618, 0.0082853455, 0.021209264, -0.0044173254, 0.007911514, -0.01449554, 0.036528762, 0.018035503, 0.026427658, 0.04022131, 0.03866495, 0.025298532, -0.018477999, 0.009414472, -0.021697534, 0.04281525, -0.024459317, 0.032927763, 0.0110242395, 0.019408766, 0.017684558, -0.054014962, 0.0013637252, 0.011291263, 0.0019721563, -0.007240141, -0.034514643, 0.027175324, 0.005344277, 0.0062521556, -0.012267805, -0.029784521, -0.011382814, 0.001047112, 0.010535969, -0.042021807, -0.02224684, 0.011787163, -0.0034159885, -0.018432224, -0.003108912, 0.0047644554, -0.038787015, -0.0054091252, 0.03179864, 0.024749227, -0.012557715, -0.008445559, 0.04260163, -0.053465657, 0.002378413, -0.037017033, -0.03405689, -0.04080113, -0.02670231, -0.012862884, -0.05612063, -0.014518428, -0.04012976, 0.005630373, -1.9669113E-5, 0.026626019, -0.006015649, -0.015929837, 0.02691593, 0.042845767, 0.023711652, 7.6721463E-4, 0.054289613, -0.028136607, -0.0065306225, 0.0040587513]"}] diff --git a/examples/basic_modules/polardb_search.py b/examples/basic_modules/polardb_search.py deleted file mode 100644 index 111160a3e..000000000 --- a/examples/basic_modules/polardb_search.py +++ /dev/null @@ -1,1385 +0,0 @@ -import json -import os -import sys -from typing import Optional - -src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "src")) -sys.path.insert(0, src_path) - -import psycopg2 - -from memos.configs.graph_db import GraphDBConfigFactory -from memos.graph_dbs.factory import GraphStoreFactory - - -def getPolarDb(): - config = GraphDBConfigFactory( - backend="polardb", - config={ - "host": os.getenv("POLAR_DB_HOST", "xxxxx"), - "port": int(os.getenv("POLAR_DB_PORT", "5432")), - "user": os.getenv("POLAR_DB_USER", "xxxxx"), - "password": os.getenv("POLAR_DB_PASSWORD", "xxx"), - "db_name": os.getenv("POLAR_DB_DB_NAME", "xxx"), - "user_name": os.getenv("POLARDB_USER_NAME", "xxx"), - "use_multi_db": os.getenv("POLARDB_USE_MULTI_DB", "True").lower() == "true", # 设置为True,不添加user_name过滤条件 - "auto_create": True, - "embedding_dimension": 1024, - }, - ) - graph = GraphStoreFactory.from_config(config) - return graph - - -def test_search_by_embedding(graph, vector: list[float], user_name: Optional[str] = None, - filter: Optional[dict] = None,knowledgebase_ids: Optional[list[str]] = None): - """Test search_by_embedding function.""" - # Query search_by_embedding - nodes = graph.search_by_embedding( - vector=vector, top_k=100, user_name=user_name, filter=filter,knowledgebase_ids=knowledgebase_ids - ) - print(f"test_search_by_embedding: nodes count: {len(nodes)}") - for node_i in nodes: - print(f"Search result id: {node_i['id']}, score: {node_i.get('score', 'N/A')}") - - -def test_get_node(graph, node_id: str, user_name: Optional[str] = None): - """Test get_node function - query single node.""" - detail = graph.get_node(id=node_id, user_name=user_name) - print(f"test_get_node: {detail}") - - -def test_get_nodes(graph, ids: list[str], user_name: Optional[str] = None): - """Test get_nodes function - query multiple nodes.""" - detail_list = graph.get_nodes(ids=ids, user_name=user_name) - print(f"test_get_nodes: count: {len(detail_list)}") - print(f"test_get_nodes: {detail_list}") - - -def test_update_node(graph, node_id: str, fields: dict, user_name: Optional[str] = None): - """Test update_node function.""" - result = graph.update_node(id=node_id, fields=fields, user_name=user_name) - print(f"test_update_node: {result}") - - -def test_get_memory_count(graph, scope: str, user_name: Optional[str] = None): - """Test get_memory_count function.""" - count = graph.get_memory_count(scope, user_name) - print(f"test_get_memory_count: {count}") - - -def test_node_not_exist(graph, scope: str, user_name: Optional[str] = None): - """Test node_not_exist function - check if node exists.""" - isNodeExist = graph.node_not_exist(scope, user_name) - print(f"test_node_not_exist: {isNodeExist}") - - -def test_remove_oldest_memory(graph, scope: str, skip_count: int, user_name: Optional[str] = None): - """Test remove_oldest_memory function - remove oldest memory after skipping.""" - result = graph.remove_oldest_memory(scope, skip_count, user_name) - print(f"test_remove_oldest_memory: {result}") - - -def test_delete_node(graph, node_id: str, user_name: Optional[str] = None): - """Test delete_node function.""" - isNodeDeleted = graph.delete_node(id=node_id, user_name=user_name) - print(f"test_delete_node: {isNodeDeleted}") - # detail_list = graph.get_nodes(ids=ids,user_name='memos7a9f9fbbb61c412f94f77fbaa8103c35') - # print("1111多个node:", len(detail_list)) - # # - # print("多个node:", detail_list) - - # 4,更新 update_node - # graph.update_node(id="000009999ef-926f-42e2-b7b5-0224daf0abcd", fields={"name": "new_name"}) - - # 4,查询 get_memory_count - # count = graph.get_memory_count('UserMemory','memos07ba3d044650474c839e721f3a69d38a') - # print("user count:", count) - # # - # # 4,判断node是否存在 node_not_exist 1代表存在, - # isNodeExist = graph.node_not_exist('UserMemory', 'memos07ba3d044650474c839e721f3a69d38a') - # print("user isNodeExist:", isNodeExist) - # - # # 6,删除跳过多少行之后的数据remove_oldest_memory - # remove_oldest_memory = graph.remove_oldest_memory('UserMemory', 2,'memos07ba3d044650474c839e721f3a69d38a') - # print("user remove_oldest_memory:", remove_oldest_memory) - - # 7,更新 update_node - # isNodeExist = graph.update_node(id="bb079c5b-1937-4125-a9e5-55d4abe6c95d", fields={"status": "inactived","tags": ["yoga", "travel11111111", "local studios5667888"]}) - # print("user update_node:", isNodeExist) - - # 8,删除 delete_node - # isNodeDeleted = graph.delete_node(id="bb079c5b-1937-4125-a9e5-55d4abe6c95d", user_name='memosbfb3fb32032b4077a641404dc48739cd') - # print("user isNodeDeleted:", isNodeDeleted) - - -# 9,添加边 add_edge -def add_edge( - db_name: str, source_id: str, target_id: str, edge_type: str = "Memory", user_name: Optional[str] = None -): - graph = getPolarDb(db_name) - graph.add_edge(source_id, target_id, edge_type, user_name) - - -def edge_exists( - db_name: str, - source_id: str, - target_id: str, - type: str = "Memory", - direction: str = "OUTGOING", - user_name: Optional[str] = None, -): - graph = getPolarDb(db_name) - isEdge_exists = graph.edge_exists( - source_id=source_id, - target_id=target_id, - type=type, - user_name=user_name, - direction=direction, - ) - print("edge_exists:", isEdge_exists) - - -def get_children_with_embeddings(db_name: str, id: str, user_name: Optional[str] = None): - graph = getPolarDb(db_name) - children = graph.get_children_with_embeddings(id=id, user_name=user_name) - print("get_children_with_embedding:", children) - - -def get_subgraph(db_name, center_id, depth, center_status, user_name): - graph = getPolarDb(db_name) - subgraph = graph.get_subgraph(center_id, depth, center_status, user_name) - print("111111get_subgraph:", subgraph) - # subgraph = convert_graph_edges(subgraph) - # print("222222get_subgraph:", subgraph) - - -def convert_graph_edges(core_node: dict) -> dict: - import copy - - data = copy.deepcopy(core_node) - - id_map = {} - - core_node = data.get("core_node", {}) - core_meta = core_node.get("metadata", {}) - if "graph_id" in core_meta and "id" in core_node: - id_map[core_meta["graph_id"]] = core_node["id"] - - for neighbor in data.get("neighbors", []): - n_meta = neighbor.get("metadata", {}) - if "graph_id" in n_meta and "id" in neighbor: - id_map[n_meta["graph_id"]] = neighbor["id"] - - for edge in data.get("edges", []): - src = edge.get("source") - tgt = edge.get("target") - - if src in id_map: - edge["source"] = id_map[src] - if tgt in id_map: - edge["target"] = id_map[tgt] - - return data - - -def get_grouped_counts(db_name, user_name): - graph = getPolarDb(db_name) - grouped_counts = graph.get_grouped_counts( - group_fields=["status"], - where_clause="user_name = %s", - params=[user_name], - user_name=user_name, - ) - grouped_counts = graph.get_grouped_counts1( - group_fields=["status"], params=[user_name], user_name=user_name - ) - print("get_grouped_counts:", grouped_counts) - - -def export_graph(db_name, include_embedding, user_name): - graph = getPolarDb(db_name) - export_graphlist = graph.export_graph(include_embedding=include_embedding, user_name=user_name) - print("export_graph:", export_graphlist) - - -def get_structure_optimization_candidates(db_name, scope, include_embedding, user_name): - graph = getPolarDb(db_name) - candidates = graph.get_structure_optimization_candidates( - scope=scope, include_embedding=include_embedding, user_name=user_name - ) - print("get_structure_optimization_candidates:", candidates) - - -def test_get_all_memory_items(graph, scope: str, include_embedding: bool, user_name: str, - filter: Optional[dict] = None,knowledgebase_ids: Optional[list] = None): - """Test get_all_memory_items function.""" - memory_items = graph.get_all_memory_items( - scope=scope, include_embedding=include_embedding, user_name=user_name, filter=filter,knowledgebase_ids=knowledgebase_ids - ) - print(f"test_get_all_memory_items: count: {len(memory_items)}") - print(f"test_get_all_memory_items: {memory_items}") - - -def get_neighbors_by_tag(db_name, user_name): - graph = getPolarDb(db_name) - tags = ["旅游建议", "景点"] - ids = ["39d12b46-ebe4-4f25-b0b7-1582042049e7"] - neighbors = graph.get_neighbors_by_tag(tags=tags, exclude_ids=ids, user_name=user_name) - print("get_neighbors_by_tag:", neighbors) - - -def get_edges(db_name: str, id: str, type: str, direction: str, user_name: Optional[str] = None) -> None: - graph = getPolarDb(db_name) - edges = graph.get_edges(id=id, type=type, direction=direction, user_name=user_name) - print("get_edges:", edges) - - -def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter: Optional[dict] = None,knowledgebase_ids: Optional[list] = None): - """Test get_by_metadata function.""" - ids = graph.get_by_metadata(filters=filters, user_name=user_name, filter=filter,knowledgebase_ids=knowledgebase_ids) - print(f"test_get_by_metadata: count: {len(ids)}") - print(f"test_get_by_metadata: {ids}") - - -if __name__ == "__main__": - # Example vector for testing - vector = [ - -0.059882111847400665, - 0.020448941737413406, - -0.005792281590402126, - 0.016083329916000366, - -0.0009665691759437323, - 0.0016216486692428589, - -0.02259845845401287, - -0.0012792478082701564, - 0.016644487157464027, - -0.0013910036068409681, - -0.02008751966059208, - 0.02077232114970684, - -0.004075521603226662, - 0.009449313394725323, - 0.0073425970040261745, - 0.0008845356060191989, - -0.015417550690472126, - 0.02891385369002819, - 0.008688422851264477, - -0.029541587457060814, - -0.023283259943127632, - -0.01937418431043625, - 0.027772516012191772, - 0.038520101457834244, - -0.005992015358060598, - 0.02250334806740284, - -0.002231550170108676, - -0.036693960428237915, - 0.0021970723755657673, - -0.03507706895470619, - -0.013239501044154167, - 0.006386727560311556, - 0.013315590098500252, - -0.07342597097158432, - -0.0537949837744236, - -0.059691887348890305, - 0.047441545873880386, - 0.009701358154416084, - -0.056229833513498306, - -0.009767936542630196, - 0.00476032355800271, - -0.008112998679280281, - 0.01600724086165428, - -0.013382168486714363, - -0.007323574740439653, - -0.02758229337632656, - 0.01832795888185501, - -0.03498195856809616, - -0.0313296802341938, - 0.007746819872409105, - 0.02704966999590397, - -0.002993630012497306, - 0.0651703029870987, - -0.014723238535225391, - -0.004736545495688915, - 0.039414145052433014, - -0.006144193932414055, - 0.022313125431537628, - -0.09518744796514511, - -0.015360483899712563, - 0.0004009538097307086, - 0.011955497786402702, - -0.04432189464569092, - 0.029446477070450783, - 0.02023969776928425, - 0.04165877401828766, - -0.019269561395049095, - 0.015740929171442986, - 0.012840033508837223, - 0.008365044370293617, - -0.022541392594575882, - -0.001721515553072095, - -0.03224274888634682, - -0.027658382430672646, - -0.04869701340794563, - 0.004998101852834225, - -0.012602254748344421, - -0.012098164297640324, - -0.02200876735150814, - 0.038063567131757736, - 0.00469612330198288, - -0.0022220390383154154, - 0.016948843374848366, - 0.024443618953227997, - 0.03749289736151695, - 0.02476699836552143, - 0.005730459466576576, - 0.0463762991130352, - -0.013039766810834408, - -0.006881306879222393, - 0.024158284068107605, - -0.02368272840976715, - 0.0592353530228138, - -0.004348966758698225, - -0.030340522527694702, - -0.008445888757705688, - -0.01779533550143242, - 0.059844065457582474, - 0.02216094732284546, - 0.00398516608402133, - -0.004917256999760866, - 0.0503329299390316, - -0.02541375532746315, - -0.010871227830648422, - 0.07327379286289215, - -0.012155231088399887, - 0.03235688433051109, - -0.003397853346541524, - -0.01643524318933487, - -0.011907941661775112, - 0.050066620111465454, - 0.015037105418741703, - 0.0090688681229949, - 0.014970527961850166, - 5.1791106670862064e-05, - -0.008455399423837662, - -0.028894830495119095, - -0.01949782855808735, - 0.043256644159555435, - -0.018841560930013657, - 0.018870092928409576, - 0.029541587457060814, - 0.054898276925086975, - -0.025832245126366615, - -0.014780305325984955, - -0.034715645015239716, - -0.008617089129984379, - -0.01845160312950611, - -0.007333085872232914, - -0.016102353110909462, - 0.03351724147796631, - -0.015712397173047066, - -0.01928858272731304, - 0.01671106554567814, - -0.007994109764695168, - -0.03566676005721092, - 0.002178050111979246, - -0.03346017748117447, - -0.004945790395140648, - -0.05619179084897041, - 0.027696426957845688, - -0.0020246829371899366, - -0.03891956806182861, - -0.01835649274289608, - 0.011061450466513634, - -0.04706110060214996, - 0.03237590566277504, - -0.04219139739871025, - -0.0155411958694458, - -0.0024776507634669542, - -0.030663901939988136, - 0.00886913388967514, - 0.009254335425794125, - 0.011042428202927113, - -0.006947884801775217, - -0.00797033216804266, - -0.05642005801200867, - 0.05584938824176788, - -0.015341461636126041, - -0.007242729887366295, - -0.0037925653159618378, - -0.021704411134123802, - -0.06631163507699966, - -0.013981369324028492, - 0.02767740562558174, - 0.020049475133419037, - 0.041430506855249405, - -0.020220674574375153, - -0.02023969776928425, - -0.005892148707062006, - 0.0009047468192875385, - -0.006167971529066563, - -0.0029484520200639963, - 0.018004579469561577, - 0.005901659838855267, - 0.01532243937253952, - 0.023112060502171516, - 0.027125759050250053, - 0.023606639355421066, - -0.03311777487397194, - 0.012973189353942871, - 0.045729540288448334, - -0.0010604916606098413, - -0.04694696515798569, - -0.019031783565878868, - 0.03357430920004845, - 0.03283243998885155, - -0.01399088092148304, - 0.0002401561796432361, - 0.009121179580688477, - 0.019516849890351295, - -0.025299621745944023, - 0.06011037901043892, - 0.012973189353942871, - 0.029731810092926025, - -0.0075898864306509495, - 0.007390152662992477, - 0.017871424555778503, - 0.005259658209979534, - -0.012725899927318096, - 0.022788681089878082, - 0.029826922342181206, - -0.0018106824718415737, - -0.061860427260398865, - -0.04816439002752304, - 0.022370191290974617, - -0.011822341941297054, - -0.032984618097543716, - -0.0070049515925347805, - -0.007998865097761154, - 0.003961388021707535, - -0.009140201844274998, - 0.03869130089879036, - -0.006125171668827534, - -0.05200688913464546, - 0.011489451862871647, - -0.0320715494453907, - 0.020962543785572052, - -0.03732169792056084, - -0.04584367573261261, - 0.005682903807610273, - 0.03216665983200073, - -0.01993534155189991, - 0.026364868506789207, - 0.02550886571407318, - -0.007304552476853132, - 0.050256840884685516, - 0.03346017748117447, - 0.03625645115971565, - 0.011508474126458168, - -0.026821402832865715, - -0.0231881495565176, - 0.002248194767162204, - 0.0024847842287272215, - 0.032889507710933685, - -0.014475949108600616, - -0.017966534942388535, - 0.027468159794807434, - 0.0333079993724823, - -0.017947513610124588, - 0.019707072526216507, - -0.039604369550943375, - 0.017405377700924873, - 0.02067720890045166, - -0.02176147885620594, - 0.029180165380239487, - -0.03418302163481712, - 0.022541392594575882, - -0.002004471840336919, - -0.02522353269159794, - -0.00015485317271668464, - -0.012278876267373562, - -0.04980030655860901, - 0.02600344456732273, - 0.028989942744374275, - 0.028343183919787407, - 0.015855062752962112, - 0.007304552476853132, - -0.01764315739274025, - -0.030854124575853348, - -0.014009903185069561, - 0.023949040099978447, - 0.0323188379406929, - 0.011537007987499237, - 0.01120411790907383, - 0.014447415247559547, - -0.02912309765815735, - -0.016368664801120758, - 0.0034026089124381542, - 0.02389197237789631, - -0.005606814753264189, - -0.030283456668257713, - -0.02655509114265442, - 0.008740733377635479, - -0.02240823581814766, - 0.025794200599193573, - 0.010909272357821465, - -0.048430703580379486, - 0.05413738638162613, - -0.011670163832604885, - 0.032737329602241516, - -0.010766605846583843, - 0.026859447360038757, - -0.0567624568939209, - -0.00688606221228838, - -0.022141924127936363, - -0.023112060502171516, - -0.04862092435359955, - -0.024500686675310135, - -0.03212861716747284, - -0.030911190435290337, - 0.015893107280135155, - 0.06482790410518646, - -0.011166073381900787, - -0.022427259013056755, - 0.03591404855251312, - 0.004601011984050274, - -0.16556985676288605, - 0.06600727885961533, - -0.05915926396846771, - 0.0710291638970375, - -0.001765504595823586, - -0.020467964932322502, - -0.03855814412236214, - 0.03020736761391163, - -0.10507902503013611, - -0.002777251647785306, - -0.06399092078208923, - -0.06604532897472382, - 0.009591980837285519, - -0.035267289727926254, - 0.018366003409028053, - -0.01154651865363121, - -0.05193080008029938, - -0.002610806841403246, - -0.00699544046074152, - -0.04432189464569092, - 0.012164742685854435, - -0.004082655068486929, - 0.04747958853840828, - -0.014390348456799984, - 0.014371326193213463, - -0.02206583507359028, - 0.02027774229645729, - -0.013895769603550434, - -0.05501240864396095, - 0.016720576211810112, - 0.020467964932322502, - 0.004032721742987633, - -0.0055640144273638725, - 0.030778035521507263, - 0.013163411989808083, - 0.013210967183113098, - 0.010329093784093857, - -0.03452542424201965, - 0.05851250886917114, - -0.019202983006834984, - 0.046262163668870926, - -0.0003462648019194603, - -0.00251093995757401, - 0.06459963321685791, - -0.011042428202927113, - -0.011470429599285126, - -0.03937610238790512, - 0.019726095721125603, - -0.0448925606906414, - -0.034468356519937515, - 0.015503151342272758, - 0.011498963460326195, - 0.018432581797242165, - -0.03494391217827797, - -0.028248073533177376, - 0.021438099443912506, - -0.014447415247559547, - 0.08605675399303436, - -0.03385964408516884, - -0.0010355248814448714, - -0.015484129078686237, - -0.02324521541595459, - -0.01310634519904852, - -0.06357242912054062, - -0.013020744547247887, - 0.026916515082120895, - 0.06482790410518646, - 0.018774982541799545, - 0.04120223969221115, - -0.016549376770853996, - 0.03458248823881149, - 0.010795138776302338, - -0.00013642535486724228, - -0.025699088349938393, - 0.016368664801120758, - 0.03089216910302639, - -0.013410701416432858, - -0.01535097323358059, - -0.01433328166604042, - -0.10629645735025406, - -0.001113991835154593, - -0.019088849425315857, - -0.0310823917388916, - 0.009054601192474365, - 0.0021518943831324577, - -0.03433519974350929, - 0.032889507710933685, - 0.011232651770114899, - 0.05881686508655548, - 0.23952844738960266, - -0.02181854471564293, - -0.0062155271880328655, - 0.018527692183852196, - 0.07772500067949295, - -0.020715253427624702, - -0.01109949592500925, - 0.024938197806477547, - -0.04953399673104286, - -0.05208297818899155, - -0.000630707188975066, - 0.011242162436246872, - 0.0049125016666948795, - -0.015417550690472126, - 0.03119652532041073, - 0.02897091954946518, - -0.006030059885233641, - -0.020049475133419037, - 0.06631163507699966, - -0.011984030716121197, - 0.02581322193145752, - 0.004453589208424091, - 0.02718282677233219, - -0.0013898147735744715, - -0.017557555809617043, - -0.05619179084897041, - -0.03203350678086281, - 0.03444933518767357, - -0.019611962139606476, - 0.02668824791908264, - -0.025432776659727097, - 0.029788877815008163, - 0.04679478704929352, - -0.03226177394390106, - 0.008041664958000183, - 0.02940843254327774, - 0.013743591494858265, - -0.009805981069803238, - 0.0370173417031765, - -0.020353831350803375, - -0.001188891939818859, - -0.02185658924281597, - -0.047403499484062195, - -0.015389017760753632, - -0.007494775112718344, - -0.030435634776949883, - -0.002577517880126834, - -0.029864966869354248, - 0.01426670327782631, - -0.011137540452182293, - -0.029370388016104698, - -0.04846874624490738, - -0.051892757415771484, - -0.016368664801120758, - -0.00015381289995275438, - -0.010623938404023647, - -0.001973560778424144, - -0.005896904040127993, - -0.017662178725004196, - 0.011109006591141224, - -0.006415260955691338, - 0.0057019260711967945, - 0.0018998493906110525, - 0.0439034029841423, - 0.0027748739812523127, - -0.016045285388827324, - -0.057561393827199936, - -0.02556593343615532, - -0.0005890959873795509, - 0.04253380000591278, - 0.04595780745148659, - -0.01416208129376173, - -0.02550886571407318, - -0.03197643905878067, - 0.021742455661296844, - 0.018993739038705826, - 0.00880731176584959, - 0.0036427651066333055, - -0.02640291303396225, - -0.01055736094713211, - -0.010471760295331478, - -0.05630592256784439, - -0.03384062275290489, - -0.03250906243920326, - -0.021628322079777718, - -0.048773106187582016, - -0.014143059030175209, - 0.02086743153631687, - -0.024843087419867516, - -0.022427259013056755, - -0.00131134781986475, - -0.008617089129984379, - -0.039414145052433014, - -0.0018832049099728465, - 0.01989729516208172, - 0.008160554803907871, - 0.01120411790907383, - -0.023378372192382812, - -0.03064487874507904, - -0.023283259943127632, - -0.025337666273117065, - -0.023968061432242393, - 0.009634780697524548, - -0.017909469082951546, - -0.027430115267634392, - -0.007532819639891386, - 0.037283651530742645, - 0.024595797061920166, - -0.002140005584806204, - 0.020011430606245995, - 0.025680067017674446, - 0.03401182219386101, - -0.02748718298971653, - -0.06421919167041779, - 0.0003358619869686663, - 0.017690712586045265, - 0.019022271037101746, - 0.038710322231054306, - 0.0006794517394155264, - -0.015769463032484055, - -0.00897851213812828, - 0.05345258489251137, - 0.006990684662014246, - 0.016692044213414192, - -0.005207346752285957, - -0.016149908304214478, - -0.062202829867601395, - 0.017129555344581604, - 0.00843637716025114, - 0.01953587308526039, - 0.034430310130119324, - -0.024938197806477547, - 0.0394902341067791, - 0.027905672788619995, - -0.027658382430672646, - 0.02147614397108555, - 0.042419664561748505, - 0.023663705214858055, - 0.018813027068972588, - -0.006771928630769253, - -0.02526157721877098, - -0.009882070124149323, - -0.003887676866725087, - -0.0041230772621929646, - -0.0037545207887887955, - 0.016986887902021408, - 0.016787154600024223, - -0.05611570179462433, - -0.008231887593865395, - 0.0006657795165665448, - 0.006125171668827534, - 0.014409370720386505, - 0.036294493824243546, - -0.02940843254327774, - -0.005644859280437231, - -0.037873342633247375, - -0.04363708943128586, - 0.013724569231271744, - 0.022237036377191544, - -0.00886913388967514, - 0.002277916995808482, - -0.007718286477029324, - 0.0483546145260334, - 0.03642765060067177, - 0.029446477070450783, - 0.02157125622034073, - -0.030682923272252083, - 0.04017503932118416, - -0.049762263894081116, - -0.03696027398109436, - -0.01569337397813797, - -0.0009647858096286654, - -0.06429527699947357, - 0.04820243641734123, - 0.009863047860562801, - -0.05828424170613289, - -0.01532243937253952, - -0.03357430920004845, - -0.01928858272731304, - -0.04949595034122467, - 0.04455016180872917, - -0.0429522879421711, - -0.01453301589936018, - -0.030416611582040787, - -0.006729128770530224, - -0.013182434253394604, - -0.009534914046525955, - -0.004722279030829668, - 0.043446868658065796, - 0.002644095802679658, - -0.035799916833639145, - 0.0922960638999939, - 0.04002286121249199, - -0.0043632336892187595, - 0.0031196526251733303, - 0.016787154600024223, - 0.01658742129802704, - -0.01647328771650791, - -0.00502187991514802, - -0.03823476657271385, - 0.05744725838303566, - -0.004422678146511316, - -0.01169869676232338, - -0.025737132877111435, - -0.058055974543094635, - 0.020905476063489914, - -0.033441152423620224, - -0.004969568457454443, - -0.021590277552604675, - 0.03292755037546158, - -0.01680617779493332, - -0.0125832324847579, - -0.0072760190814733505, - 0.018641825765371323, - 0.05063728615641594, - 0.02817198447883129, - 0.04413167014718056, - 0.011679674498736858, - -0.0021804277785122395, - -0.03673200681805611, - 0.011318251490592957, - 0.001906982739455998, - -0.014295237138867378, - -0.015988219529390335, - -0.03307972848415375, - -0.009135445579886436, - 0.04531105235219002, - -0.022674547508358955, - 0.05432760715484619, - 3.115268555120565e-05, - -0.003024541074410081, - -0.01108047366142273, - -0.004106432665139437, - 0.03247101604938507, - 0.04082179442048073, - 0.014323770068585873, - 0.0038710322696715593, - 0.02550886571407318, - -0.0038686543703079224, - -0.014447415247559547, - 0.009687092155218124, - 0.03315581753849983, - -0.005911170970648527, - 0.07148569822311401, - -0.00028637435752898455, - -0.012269365601241589, - -0.02931332029402256, - -0.015331950969994068, - -1.7991278582485393e-05, - -0.008065443485975266, - 0.01872742548584938, - -0.023815883323550224, - -0.011498963460326195, - 0.02117178775370121, - -0.0006960962782613933, - 0.018242359161376953, - -0.001524159568361938, - -0.046604566276073456, - -0.0328134186565876, - 0.02033480815589428, - 0.018299425020813942, - -0.005150279961526394, - 0.007380641531199217, - 0.02012556418776512, - -0.028742652386426926, - -0.015341461636126041, - 0.03266124054789543, - -0.05482218787074089, - 0.014295237138867378, - -0.05664832517504692, - -0.01643524318933487, - 0.009482602588832378, - 0.0019771272782236338, - 0.03791138902306557, - 0.056572236120700836, - -0.0029080298263579607, - -0.02368272840976715, - -0.0003055452252738178, - 0.0863611102104187, - -0.004413167014718056, - -0.014761283062398434, - 0.0493437722325325, - 0.018841560930013657, - 0.014190614223480225, - 0.029237231239676476, - 0.0572570376098156, - 0.012564210221171379, - -0.0037259873934090137, - 0.024595797061920166, - -0.04181095212697983, - -0.0030150299426168203, - -0.01869889348745346, - -0.07167591899633408, - 0.0409739725291729, - -0.008084465749561787, - -0.030816080048680305, - -0.017224667593836784, - 0.04702305421233177, - -0.017043955624103546, - 0.012202787213027477, - -0.03739778697490692, - 0.009596736170351505, - -0.032699283212423325, - 0.021438099443912506, - -0.018622804433107376, - 0.04717523232102394, - 0.004902990534901619, - -0.01853720284998417, - 0.047593723982572556, - 0.016482798382639885, - 0.028362207114696503, - -0.005549747962504625, - 0.013391679152846336, - 0.037283651530742645, - 0.009967670775949955, - 0.017976047471165657, - 0.05349062755703926, - -0.00947309099137783, - 0.003649898339062929, - 0.0004675317613873631, - -0.0004363233456388116, - -0.002777251647785306, - -0.007746819872409105, - 0.009901092387735844, - -0.005078946705907583, - 0.040935929864645004, - -0.027449138462543488, - 0.005668636877089739, - -0.056686367839574814, - -0.00508370203897357, - -0.04881114885210991, - 0.011327763088047504, - -0.001153225195594132, - 0.03557164967060089, - 0.05311018228530884, - 0.006182238459587097, - 0.007494775112718344, - -0.03384062275290489, - 0.05128404498100281, - -0.03401182219386101, - 0.04580562934279442, - 0.0016620709793642163, - 0.05383303016424179, - -0.020848410204052925, - 0.033441152423620224, - -0.027315981686115265, - -0.008365044370293617, - 0.011584563180804253, - -0.00037687874282710254, - -0.004391767084598541, - 0.004857812542468309, - 0.015702884644269943, - -0.03463955596089363, - -0.04036526009440422, - 0.014342792332172394, - -0.014437904581427574, - -0.03218568488955498, - 0.029142120853066444, - 0.002655984601005912, - -0.021000588312745094, - -0.03705538436770439, - 0.04926768317818642, - -0.03151990473270416, - -0.054898276925086975, - -0.04447407275438309, - 0.006163216196000576, - 0.04949595034122467, - 0.017747780308127403, - -0.012963677756488323, - 0.0014742260100319982, - -0.03441128879785538, - 0.02571811154484749, - -0.0050932131707668304, - 0.0014932482736185193, - -0.01984022930264473, - 0.03212861716747284, - -0.024101218208670616, - -0.016730088740587234, - 0.040098950266838074, - -0.022636502981185913, - -0.0009725136333145201, - 0.0023742173798382282, - -0.055392853915691376, - -0.03153892606496811, - 0.04120223969221115, - -0.023758817464113235, - -0.04124028608202934, - -0.04101201891899109, - 0.0572570376098156, - 0.02645997889339924, - -0.022046811878681183, - -0.01946929469704628, - -0.027468159794807434, - -0.024747975170612335, - -0.15811312198638916, - 0.01742440089583397, - 0.0007923964876681566, - -0.005678148008882999, - -0.033327020704746246, - -0.012373987585306168, - -0.026669224724173546, - 0.008678911253809929, - -0.005487925373017788, - -0.06874649226665497, - -0.037873342633247375, - 0.036446671932935715, - 0.05265364795923233, - -0.013981369324028492, - -0.05349062755703926, - 0.06380070000886917, - -0.012012564577162266, - 0.03707440569996834, - 0.02402512915432453, - 0.04782199114561081, - -0.007081040646880865, - 0.00388529896736145, - 0.055050455033779144, - -0.0050504133105278015, - 0.04299033433198929, - 0.023169126361608505, - -0.011185095645487309, - -0.019669027999043465, - -0.029142120853066444, - -0.05524067580699921, - 0.03834889829158783, - -0.043446868658065796, - 0.018565736711025238, - 0.062050651758909225, - -0.039261966943740845, - 0.04120223969221115, - -0.01105193980038166, - 0.005882637575268745, - 0.03005518950521946, - -0.032490041106939316, - 0.028152961283922195, - 0.004144477192312479, - -0.019650006666779518, - -0.030663901939988136, - 0.0134487459436059, - 0.02476699836552143, - -0.017196133732795715, - -0.01098536141216755, - -0.01055736094713211, - 0.009059356525540352, - -0.005487925373017788, - -0.00846491102129221, - -0.027201848104596138, - 0.04980030655860901, - -0.00018977688159793615, - 0.03384062275290489, - -0.013895769603550434, - -0.03813965618610382, - -0.023302283138036728, - 0.0547841414809227, - -0.01600724086165428, - 0.03138674795627594, - -0.05512654408812523, - -0.010795138776302338, - -0.011403852142393589, - -0.011907941661775112, - -0.058702729642391205, - 0.008098731748759747, - 0.007884731516242027, - 0.029788877815008163, - 0.00961100310087204, - 0.02906603179872036, - -0.025527888908982277, - -0.029769854620099068, - 0.0007353296969085932, - 0.03370746597647667, - 0.045273005962371826, - 0.033783555030822754, - 0.020068496465682983, - -0.0325661301612854, - -0.02714478224515915, - 0.011993542313575745, - -0.026821402832865715, - 0.040593527257442474, - 0.04222944378852844, - -0.018099691718816757, - -0.030759012326598167, - 0.042457710951566696, - -0.02733500488102436, - -0.03734071925282478, - -0.03488684445619583, - -0.039946772158145905, - -0.013600924052298069, - 0.005350013729184866, - -0.028856785967946053, - -0.01399088092148304, - -0.042115308344364166, - 0.005178813356906176, - -0.008935712277889252, - -0.011337273754179478, - 0.015778973698616028, - -0.004375122487545013, - -0.001496815006248653, - 0.009591980837285519, - -0.03277537226676941, - 0.05193080008029938, - 0.03756898641586304, - -0.020905476063489914, - 0.035362403839826584, - 0.0011716530425474048, - -0.01695835590362549, - -0.012811499647796154, - -0.008455399423837662, - 0.030606834217905998, - -0.019973386079072952, - -0.010614427737891674, - 0.01569337397813797, - 0.013391679152846336, - -0.009767936542630196, - -0.006244060583412647, - 0.01828991435468197, - 0.011888919398188591, - -0.0028866296634078026, - -0.035362403839826584, - 0.03005518950521946, - 0.042115308344364166, - 0.0009594358270987868, - -0.0019913939759135246, - -0.03709343075752258, - 0.01749097928404808, - 0.011337273754179478, - -0.02792469412088394, - 0.04610998556017876, - 0.02413926273584366, - 0.030759012326598167, - -0.0010135304182767868, - -0.0582461953163147, - 0.06684426218271255, - -0.044245801866054535, - -0.03642765060067177, - -0.010519316419959068, - 0.027753494679927826, - -0.009244823828339577, - -0.024862108752131462, - -0.011755763553082943, - -0.03857716545462608, - 0.023473482578992844, - -0.007732553407549858, - 0.054898276925086975, - -0.021989746019244194, - 0.06536052376031876, - 0.01890813745558262, - -0.024443618953227997, - 0.01068100519478321, - -0.03094923496246338, - 0.021438099443912506, - -0.04333273321390152, - 0.010909272357821465, - 0.0231881495565176, - 0.04820243641734123, - 0.03859619051218033, - -0.056534189730882645, - 0.01615941897034645, - 0.006334416568279266, - 0.025337666273117065, - -0.04040330648422241, - 0.002453872933983803, - 0.0076564643532037735, - -0.023625660687685013, - 0.02940843254327774, - 0.03593306988477707, - 0.08910032361745834, - -0.01989729516208172, - 0.014561548829078674, - 0.020201653242111206, - -0.02729696035385132, - 0.022141924127936363, - -0.034772712737321854, - 0.042914245277643204, - -0.013096833601593971, - 0.029731810092926025, - 0.07677388936281204, - 0.0003816343378275633, - 0.040593527257442474, - 0.03774018585681915, - 0.040745705366134644, - -0.016701554879546165, - 0.0028081629425287247, - -0.01692982204258442, - 0.012992211617529392, - 0.0171485785394907, - -0.004149232991039753, - 0.01109949592500925, - -0.012316920794546604, - 0.0221989918500185, - -0.03167208284139633, - -0.00012096975842723623, - 0.041278328746557236, - -0.027411093935370445, - 0.003916210029274225, - -0.028286118060350418, - -0.010300559923052788, - -0.039946772158145905, - -0.05497436597943306, - -0.02571811154484749, - -0.02547082118690014, - -0.027658382430672646, - 0.011888919398188591, - -0.010272026993334293, - 0.030359545722603798, - 0.004513034131377935, - 0.013876747339963913, - 0.013220478780567646, - 0.005373791791498661, - -0.03568578138947487, - -0.02235116995871067, - 0.009244823828339577, - 0.05227320268750191, - 0.007613664027303457, - 0.006158460397273302, - -0.030321501195430756, - -0.04515887424349785, - 0.0028010294772684574, - 0.0009255523909814656, - 0.002594162244349718, - -0.0005605625919997692, - -0.029008964076638222, - -0.0034358978737145662, - -0.014247681014239788, - -0.02062014304101467, - -0.04938181862235069, - -0.024805042892694473, - 0.017624134197831154, - 0.05451783165335655, - -0.017348311841487885, - -0.0572570376098156, - 0.024500686675310135, - 0.02497624233365059, - 0.014038436114788055, - -0.01897471584379673, - 0.005939704366028309, - -0.003100630361586809, - -0.009011801332235336, - 0.030511723831295967, - ] - # Example filter for testing - common filter used by multiple tests - filter_example = { - # "and": [ - # {"id": "45a4f936-2182-44c6-8c4f-4a9476941e51"}, - # { - # "A": "中国广西" - # }, - # { - # "B": "狗肉" - # }, - # {"created_at":{"gt":"2025-09-19"}}, - # {"created_at": {"lt": "2025-11-26"}}, - # {"tags": {"contains": "test:zdy"}}, - # {"user_name": {"like": "828"}}, - # {"memory_type": {"like": "WorkingMemory"}}, - # ] - "and": [ - {"id": "45a4f936-2182-44c6-8c4f-4a9476941e51"}, - {"A": "中国广西"}, - {"created_at": {"gt": "2025-09-19"}}, - {"created_at": {"lt": "2025-11-26"}}, - {"tags": {"contains": "test:zdy"}}, - {"user_name": {"like": "828"}}, - {"memory_type": {"like": "WorkingMemory"}}, - ] - } - - # Example filters for get_by_metadata - filters_example = [ - {"field": "tags", "op": "contains", "value": "mode:fast"}, - ] - - # Create connection once - shared by all tests - graph = getPolarDb() - user_name = "adimin" - scope = "WorkingMemory" - knowledgebase_ids = ["memosfeebbc2bd1744d7bb5b5ec57f38e828d","adimin2"] - - # Run all tests - uncomment the test you want to run - # test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) - # test_get_all_memory_items(graph, "WorkingMemory", False, user_name, filter_example,knowledgebase_ids) - test_get_by_metadata(graph, filters_example, user_name, filter_example,knowledgebase_ids) - # test_get_by_metadata(graph, filters_example, user_name, filter_example,knowledgebase_ids) - - # Or run all tests - - # Test search_by_embedding - # test_search_by_embedding(graph, vector, user_name, filter_example) - - # Test get_node - query single node - # test_get_node(graph, '"65c3a65b-78f7-4009-bc92-7ee1981deb3a"', '"adimin"') - - # Test get_nodes - query multiple nodes - # test_get_nodes(graph, ["65c3a65b-78f7-4009-bc92-7ee1981deb3a", "65c3a65b-78f7-4009-bc92-7ee1981deb3b"], user_name) - - # Test update_node - # test_update_node(graph, "000009999ef-926f-42e2-b7b5-0224daf0abcd", {"name": "new_name"}, user_name) - # test_update_node(graph, "bb079c5b-1937-4125-a9e5-55d4abe6c95d", {"status": "inactived", "tags": ["yoga", "travel11111111", "local studios5667888"]}, user_name) - - # Test get_memory_count - # test_get_memory_count(graph, 'UserMemory', user_name) - - # Test node_not_exist - # test_node_not_exist(graph, 'UserMemory', user_name) - - # Test remove_oldest_memory - # test_remove_oldest_memory(graph, 'UserMemory', 2, user_name) - - # Test delete_node - # test_delete_node(graph, "bb079c5b-1937-4125-a9e5-55d4abe6c95d", user_name) - - # Test get_all_memory_items - # test_get_all_memory_items(graph, scope, False, user_name, filter_example) - - # Test get_by_metadata - # test_get_by_metadata(graph, filters_example, user_name, filter_example) - - # searchVector(db_name="test_1020_02", vectorStr=vector) - - # add_edge(db_name="memtensor_memos",source_id="13bb9df6-0609-4442-8bed-bba77dadac92", target_id="2dd03a5b-5d5f-49c9-9e0a-9a2a2899b98d", edge_type="PARENT", user_name="memosbfb3fb32032b4077a641404dc48739cd") - # edge_exists(db_name="memtensor_memos", source_id="13bb9df6-0609-4442-8bed-bba77dadac92", - # target_id="2dd03a5b-5d5f-49c9-9e0a-9a2a2899b98d", type="PARENT", direction="OUTGOING", - # user_name="memosbfb3fb32032b4077a641404dc48739cd") - - # get_children_with_embeddings(db_name="memtensor_memos", id="13bb9df6-0609-4442-8bed-bba77dadac92",user_name="memos07ea708ac7eb412887c5c283f874ea30") - - # get_subgraph(db_name="memtensor_memos", center_id="503ab3d5-919d-4986-b0f4-15b65c049686", depth=1, - # center_status="activated", user_name="memos07ea708ac7eb412887c5c283f874ea30") - - # - # get_grouped_counts(db_name="memtensor_memos", user_name="memos07ea708ac7eb412887c5c283f874ea30") - - # export_graph(db_name="memtensor_memos", include_embedding=False, user_name="memosa4d810417e8e4272a1c08df420be9e60") - - # get_structure_optimization_candidates(db_name="memtensor_memos", scope='UserMemory', include_embedding=False, user_name="memos8f5530534d9b413bb8981ffc3d48a495") - - # get_all_memory_items(db_name="memtensor_memos", scope='UserMemory', include_embedding=False, user_name="memosfeebbc2bd1744d7bb5b5ec57f38e828d") - # get_all_memory_items(db_name="memos_test", scope='LongTermMemory', include_embedding=False, user_name="adimin",filter=filter) - - # 测试 get_structure_optimization_candidates 函数 - # get_structure_optimization_candidates(db_name="memtensor_memos", scope='UserMemory', include_embedding=False, user_name="memos8f5530534d9b413bb8981ffc3d48a495") - - # get_neighbors_by_tag(db_name="memtensor_memos",user_name="memosfeebbc2bd1744d7bb5b5ec57f38e828d") - - # get_edges(db_name="memtensor_memos", id="13bb9df6-0609-4442-8bed-bba77dadac92",type="PARENT",direction="OUTGOING",user_name="memosfeebbc2bd1744d7bb5b5ec57f38e828d") - - # get_by_metadata(db_name="memtensor_memos", filters=[{"field": "tags", "op": "contains", "value": "glazes"}], user_name="memos452356faadb34b06acc7fa507023d91c") - # get_by_metadata( - # db_name="memos_test", - # filters=[{"field": "tags", "op": "contains", "value": "Python"}], - # user_name="adimin", - # filter=filter, - # ) From b61d5fc58744438eed7e32797ff61e7fb123e4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Fri, 28 Nov 2025 13:35:57 +0800 Subject: [PATCH 34/36] remove test --- examples/basic_modules/neo4j_search.py | 1120 ------------------------ 1 file changed, 1120 deletions(-) delete mode 100644 examples/basic_modules/neo4j_search.py diff --git a/examples/basic_modules/neo4j_search.py b/examples/basic_modules/neo4j_search.py deleted file mode 100644 index fae80a7b1..000000000 --- a/examples/basic_modules/neo4j_search.py +++ /dev/null @@ -1,1120 +0,0 @@ -import os -import sys -from typing import Optional - -src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "src")) -sys.path.insert(0, src_path) - -from memos.configs.graph_db import GraphDBConfigFactory -from memos.graph_dbs.factory import GraphStoreFactory - - -def getNeo4j(): - config = GraphDBConfigFactory( - backend="neo4j", - config={ - "uri": os.getenv("NEO4J_URI", "bolt://localhost:7687"), - "user": os.getenv("NEO4J_USER", "neo4j"), - "password": os.getenv("NEO4J_PASSWORD", "password"), - "db_name": os.getenv("NEO4J_DB_NAME", "dbname"), - "user_name": os.getenv("NEO4J_USER_NAME", "neo4j"), - "use_multi_db": os.getenv("NEO4J_USE_MULTI_DB", "False").lower() == "true", - "auto_create": True, - "embedding_dimension": 1024, - }, - ) - graph = GraphStoreFactory.from_config(config) - return graph - - -def test_search_by_embedding(graph, vector: list[float], user_name: str, filter_example: Optional[dict] = None,knowledgebase_ids: Optional[list[str]] = None): - """Test search_by_embedding function.""" - # Query search_by_embedding - nodes = graph.search_by_embedding( - vector=vector, - top_k=100, - user_name=user_name, - filter=filter_example, - knowledgebase_ids=knowledgebase_ids, - ) - print(f"test_search_by_embedding: nodes count: {len(nodes)}") - print(f"test_search_by_embedding: nodes: {nodes}") - for node_i in nodes: - print(f"Search result id: {node_i['id']}, score: {node_i.get('score', 'N/A')}") - - -def test_get_all_memory_items(graph, scope: str, user_name: str, filter_example: Optional[dict] = None, knowledgebase_ids: Optional[list[str]] = None): - """Test get_all_memory_items function.""" - memory_items = graph.get_all_memory_items( - scope=scope, include_embedding=False, user_name=user_name, filter=filter_example, knowledgebase_ids=knowledgebase_ids - ) - print(f"test_get_all_memory_items: count: {len(memory_items)}") - print(f"test_get_all_memory_items: {memory_items}") - - -def test_get_by_metadata(graph, filters: list[dict], user_name: str, filter_example: Optional[dict] = None, knowledgebase_ids: Optional[list[str]] = None): - """Test get_by_metadata function.""" - ids = graph.get_by_metadata(filters=filters, user_name=user_name, filter=filter_example, knowledgebase_ids=knowledgebase_ids) - print(f"test_get_by_metadata: count: {len(ids)}") - print(f"test_get_by_metadata: {ids}") - - -if __name__ == "__main__": - vector = [ - 0.00036784022813662887, - 0.011101230047643185, - -0.016430197283625603, - -0.00498470664024353, - -0.024183137342333794, - -0.009238448925316334, - 0.05104490742087364, - 0.01794871687889099, - -0.003334141569212079, - -0.010242936201393604, - -0.0242774561047554, - 0.0008476831135340035, - -0.02812563069164753, - -0.020353825762867928, - 0.018505193293094635, - 0.012789522297680378, - 0.031011762097477913, - -0.01729792356491089, - -0.0011453743791207671, - -0.029861081391572952, - 0.019269170239567757, - 0.011469069868326187, - 0.018910761922597885, - 0.0024451944045722485, - 0.043009012937545776, - 0.012204750441014767, - -0.0681353285908699, - -0.019052237272262573, - -0.015911448746919632, - -0.001834485330618918, - 0.002506501041352749, - -0.035350389778614044, - 0.020410416647791862, - -0.002650336129590869, - -0.024183137342333794, - -0.02891790121793747, - -0.020976325497031212, - -0.00686635123565793, - -0.01912769302725792, - 0.030351536348462105, - 0.00514976354315877, - -0.03804788365960121, - -0.014996564015746117, - 0.03253971412777901, - -0.028729265555739403, - -0.05093172565102577, - -0.04598946124315262, - 0.032992441207170486, - -0.046744007617235184, - -0.00742754340171814, - -0.010205208323895931, - 0.009629868902266026, - 0.03682175278663635, - 0.007243623025715351, - 0.02439063787460327, - 0.029012219980359077, - -0.03712356835603714, - 0.015345539897680283, - -0.03208698704838753, - -0.014034519903361797, - 0.0018887181067839265, - -0.0008170297369360924, - 0.008946062996983528, - -0.035803116858005524, - 0.002827182412147522, - 0.08028349280357361, - 0.02163655124604702, - 0.007828394882380962, - -0.04512173682451248, - -0.03983992710709572, - 0.014807927422225475, - 0.015402130782604218, - 0.00012828722537960857, - 0.003428459633141756, - -0.03812333941459656, - 0.026144951581954956, - 0.008196234703063965, - 0.009474243968725204, - 0.013044181279838085, - 0.008837597444653511, - -0.03440720960497856, - -0.03550129756331444, - 0.04942263662815094, - -0.02959699183702469, - -0.015383267775177956, - 0.018363716080784798, - 0.029200855642557144, - 0.08428257703781128, - 0.014506109990179539, - -0.057798076421022415, - -0.06115579977631569, - 0.005494024138897657, - 0.050403546541929245, - -0.013487475924193859, - -0.03885902091860771, - 0.002942721825093031, - -0.06685260683298111, - 0.005281808786094189, - 0.01831655763089657, - 0.040934015065431595, - -0.00393306091427803, - 0.017458263784646988, - 0.05361035838723183, - 0.022183595225214958, - -0.013478043489158154, - -0.021504506468772888, - -0.01252543181180954, - 0.005324251484125853, - 0.05794898420572281, - 0.015043722465634346, - 0.005116751883178949, - -0.0031997384503483772, - 0.03099289909005165, - 0.005102604161947966, - 0.014062815345823765, - -0.015496449545025826, - -0.019203146919608116, - -0.0028436880093067884, - 0.0015067302156239748, - -0.03470902889966965, - 0.02129700593650341, - 0.04349946603178978, - 0.036387886852025986, - -0.03599175438284874, - -0.013836451806128025, - 0.041160378605127335, - -0.027012677863240242, - -0.006814476102590561, - 0.000047859022743068635, - -0.015232359059154987, - 0.033426303416490555, - 0.02290041372179985, - -0.016307583078742027, - -0.027823813259601593, - -0.005083740688860416, - -0.004946979694068432, - 0.014194860123097897, - 0.023824729025363922, - 0.022277913987636566, - -0.00172130367718637, - -0.010507026687264442, - 0.05059218034148216, - -0.004553202074021101, - -0.034218575805425644, - 0.08337712287902832, - -0.04444264620542526, - -0.02505086362361908, - 0.008021746762096882, - -0.010365549474954605, - -0.017580877989530563, - 0.01835428550839424, - -0.006470215506851673, - 0.01901451125741005, - -0.004937547724694014, - -0.014072246849536896, - 0.029332900419831276, - 0.012195318937301636, - -0.004022662993520498, - -0.0485549122095108, - -0.012110432609915733, - 0.0014041593531146646, - -0.026390179991722107, - -0.05594944208860397, - 0.022975867614150047, - 0.024918818846344948, - -0.00651737442240119, - 0.03772720322012901, - 0.02803131192922592, - -0.006569249555468559, - 0.03519948199391365, - 0.001375864027068019, - -0.017760081216692924, - -0.01337429415434599, - -0.0040462426841259, - -0.002251842524856329, - 0.014194860123097897, - 0.030464718118309975, - 0.03572766110301018, - 0.027993585914373398, - -0.04746082425117493, - -0.009356346912682056, - -0.037161294370889664, - 0.006597544997930527, - -0.01093145739287138, - -0.017646899446845055, - 0.006847487762570381, - 0.0024428365286439657, - -0.050629906356334686, - 0.007861406542360783, - 0.022711776196956635, - -0.003341215429827571, - -0.043688103556632996, - 0.012468840926885605, - -0.013666680082678795, - 0.013421452604234219, - -0.03829311206936836, - -0.023598365485668182, - -0.051535360515117645, - -0.03391675651073456, - -0.03335084766149521, - -0.0006908794166520238, - -0.009129983372986317, - -0.003421385772526264, - -0.012053841724991798, - -0.0024522682651877403, - -0.0101957768201828, - 0.03267175704240799, - 0.041386742144823074, - 0.016609402373433113, - -0.03710470348596573, - 0.020202917978167534, - 0.06296670436859131, - 0.009818504564464092, - -0.048102185130119324, - -0.021372461691498756, - 0.00003490871677058749, - -0.00059774040710181, - -0.01794871687889099, - 0.047687187790870667, - -0.03144562616944313, - 0.006507942918688059, - -0.015354972332715988, - -0.019769055768847466, - 0.05364808440208435, - -0.008507484570145607, - -0.013345998711884022, - 0.014364632777869701, - 0.03142676129937172, - -0.014590996317565441, - -0.02948381006717682, - 0.056892622262239456, - 0.029106538742780685, - 0.004211299121379852, - -0.01548701710999012, - 0.029181992635130882, - -0.03165312483906746, - -0.035010844469070435, - -0.05466672033071518, - -0.008776291273534298, - 0.01856178417801857, - 0.02857835777103901, - -0.02665426954627037, - 0.035463571548461914, - 0.014298610389232635, - -0.018778715282678604, - -0.004774849396198988, - -0.001891076099127531, - 0.04700809717178345, - -0.004692321177572012, - -0.000635467586107552, - 0.0318794883787632, - 0.0010593091137707233, - 0.0073285093531012535, - -0.0008246931247413158, - 0.0082528255879879, - 0.020787689834833145, - 0.018910761922597885, - -0.00995998177677393, - -0.028899038210511208, - -0.04051901772618294, - -0.007021975703537464, - 0.028861310333013535, - 0.016920650377869606, - 0.010799412615597248, - 0.028182221576571465, - -0.04387673735618591, - -0.0033435735385864973, - -0.06055216118693352, - 0.01948610134422779, - 0.006677715107798576, - 0.005597773939371109, - 0.018439171835780144, - -0.007639758754521608, - 0.0076586222276091576, - 0.023541774600744247, - -0.01434576977044344, - -0.050516724586486816, - -0.050290364772081375, - 0.06504169851541519, - 0.03733106702566147, - 0.03370925784111023, - 0.012091568671166897, - 0.021221552044153214, - -0.0018580647883936763, - 0.030559035018086433, - 0.04199037700891495, - 0.012968726456165314, - 0.0062014092691242695, - -0.005140331573784351, - -0.06805987656116486, - -0.03419971093535423, - -0.05179945006966591, - 0.0305024441331625, - -0.03510516509413719, - -0.030445853248238564, - 0.02425859309732914, - 0.05602489784359932, - -0.16056698560714722, - -0.03459584712982178, - -0.008323564194142818, - -0.0008972000796347857, - -0.00910168793052435, - 0.001984215108677745, - -0.046404462307691574, - 0.033407438546419144, - -0.03097403421998024, - 0.009875095449388027, - -0.01765633188188076, - -0.0322001688182354, - 0.018420306965708733, - 0.056553080677986145, - -0.01166713796555996, - -0.0005620763986371458, - -0.01611894741654396, - -0.016034061089158058, - -0.04044356197118759, - -0.051988087594509125, - -0.0052157859317958355, - -0.0407831072807312, - 0.052780359983444214, - 0.01695837825536728, - -0.04953581839799881, - -0.052214451134204865, - -0.011308729648590088, - 0.01835428550839424, - 0.027050405740737915, - -0.010242936201393604, - -0.03714243322610855, - 0.06402306258678436, - 0.0075171454809606075, - 0.020316099748015404, - 0.01684519648551941, - 0.018844738602638245, - 0.04282037541270256, - -0.028880175203084946, - -0.03287925943732262, - 0.016307583078742027, - 0.037312205880880356, - 0.04017947241663933, - -0.012666909024119377, - 0.043424010276794434, - 0.03606720641255379, - -0.03976447135210037, - 0.02346632070839405, - 0.03442607447504997, - -0.06394761055707932, - -0.056100353598594666, - -0.00024625842343084514, - -0.045612189918756485, - -0.0015692159067839384, - 0.07560531795024872, - -0.0453103706240654, - -0.044480372220277786, - -0.019882235676050186, - 0.04508401080965996, - 0.036048345267772675, - 0.015703948214650154, - -0.02412654645740986, - -0.047687187790870667, - -0.009191290475428104, - -0.021825186908245087, - -0.049837637692689896, - 0.012883840128779411, - 0.03723675012588501, - 0.01611894741654396, - 0.004284395836293697, - -0.015902016311883926, - 0.0035958741791546345, - -0.00954026635736227, - -0.01058248057961464, - -0.0029120685067027807, - 0.010997479781508446, - 0.016147242859005928, - -0.008342428132891655, - -0.014958836138248444, - 0.04885672777891159, - -0.11227615922689438, - 0.005621353629976511, - 0.021900642663240433, - -0.004838514141738415, - 0.01303474884480238, - -0.004708826541900635, - -0.017873262986540794, - -0.017571445554494858, - 0.004824366420507431, - -0.0008783364901319146, - 0.2655995190143585, - 0.008342428132891655, - 0.008281121030449867, - 0.0010239399271085858, - -0.008847028948366642, - -0.009040381759405136, - -0.04979990795254707, - 0.011478502303361893, - 0.03006858192384243, - -0.014760768972337246, - -0.01713758334517479, - 0.03174744173884392, - -0.009988277219235897, - 0.016430197283625603, - 0.02925744652748108, - 0.03282266855239868, - 0.0027470120694488287, - 0.0009950549574568868, - 0.03040812723338604, - -0.017147013917565346, - 0.00705970311537385, - -0.033539485186338425, - -0.02120268903672695, - -0.028748130425810814, - -0.0592317096889019, - 0.027484267950057983, - -0.010271231643855572, - -0.02754085883498192, - -0.0000814229715615511, - 0.06255170702934265, - -0.018278829753398895, - 0.033426303416490555, - 0.0080924853682518, - 0.03863265737891197, - -0.031709715723991394, - -0.033294256776571274, - 0.017505422234535217, - -0.014704178087413311, - -0.028163358569145203, - -0.001375864027068019, - 0.007309645880013704, - 0.01801474019885063, - 0.00019232032354921103, - 0.021108370274305344, - 0.00006134354771347716, - 0.012930999509990215, - -0.007719929330050945, - -0.02803131192922592, - -0.009743050672113895, - -0.037972431629896164, - -0.017109287902712822, - -0.00953083485364914, - 0.018363716080784798, - 0.02574881725013256, - -0.0048055024817585945, - -0.00686635123565793, - -0.0025347964838147163, - -0.05398762971162796, - 0.02950267307460308, - 0.013100771233439445, - 0.014308041892945766, - 0.036614250391721725, - -0.05519489943981171, - -0.004810218699276447, - 0.014374065212905407, - 0.022240186110138893, - -0.01377042941749096, - -0.06277807056903839, - 0.06741851568222046, - -0.012619749642908573, - -0.04293355718255043, - 0.011714297346770763, - 0.020749961957335472, - 0.0203915536403656, - 0.01919371448457241, - -0.026710860431194305, - -0.016996106132864952, - 0.018684398382902145, - -0.008950779214501381, - 0.027371086180210114, - -0.01053532212972641, - 0.006248568184673786, - 0.0060882274992764, - 0.060061708092689514, - 0.030577899888157845, - -0.02618267945945263, - 0.033331986516714096, - 0.0759071335196495, - 0.02733336016535759, - -0.011629411019384861, - -0.052553996443748474, - 0.028276540338993073, - -0.07635986059904099, - -0.025428134948015213, - 0.009931686334311962, - -0.0034826926421374083, - -0.05010172724723816, - 0.003942492883652449, - -0.05889216437935829, - 0.002205862430855632, - -0.047989003360271454, - 0.004331554751843214, - -0.027597449719905853, - 0.03238880634307861, - 0.003793942043557763, - -0.00626271590590477, - -0.040254928171634674, - 0.0024074672255665064, - 0.011704864911735058, - 0.02346632070839405, - 0.04259401187300682, - 0.011987819336354733, - -0.023956773802638054, - 0.023975638672709465, - -0.04946036636829376, - 0.030804261565208435, - 0.05304444953799248, - 0.01741110533475876, - 0.017826104536652565, - 0.033879030495882034, - -0.012959294952452183, - 0.0039000497199594975, - -0.035595618188381195, - 0.01959928311407566, - 0.0057911258190870285, - -0.03827424719929695, - -0.025711089372634888, - 0.08216985315084457, - 0.0391608364880085, - 0.017128150910139084, - -0.03250198811292648, - -0.022353367879986763, - -0.017580877989530563, - 0.010837139561772346, - 0.03359607607126236, - 0.006215556990355253, - 0.0016647129086777568, - -0.013402589596807957, - 0.023070184513926506, - -0.03040812723338604, - -0.004553202074021101, - -0.006522090639919043, - 0.008483905345201492, - 0.011403047479689121, - 0.03682175278663635, - 0.0018486330518499017, - -0.043801285326480865, - 0.07236077636480331, - 0.015534176491200924, - 0.053233083337545395, - 0.053572628647089005, - 0.040820833295583725, - 0.057118985801935196, - -0.05515717342495918, - -0.020240645855665207, - -0.019637009128928185, - -0.05749626085162163, - -0.025880862027406693, - 0.006724874023348093, - -0.015628494322299957, - -0.0019005079520866275, - 0.008351859636604786, - 0.04165083169937134, - 0.0872252956032753, - -0.010176912881433964, - 0.0022082203067839146, - 0.020674508064985275, - 0.05089399963617325, - -0.01474190503358841, - -0.013232816942036152, - -0.028634948655962944, - -0.03131357952952385, - -0.03572766110301018, - 0.010167481377720833, - 0.015543607994914055, - 0.01883530616760254, - 0.02493768185377121, - 0.01729792356491089, - -0.005904307588934898, - -0.03938720002770424, - -0.013364861719310284, - 0.03212471306324005, - 0.021957233548164368, - 0.02563563548028469, - 0.013638384640216827, - 0.036953795701265335, - 0.033294256776571274, - -0.027767222374677658, - 0.06617351621389389, - -0.007413395680487156, - -0.03016289882361889, - 0.07337941229343414, - -0.04089628905057907, - -0.010337254032492638, - -0.013072475790977478, - 0.010327822528779507, - 0.05444035679101944, - -0.017345082014799118, - 0.03401107341051102, - -0.03086085245013237, - -0.0598730742931366, - -0.020768824964761734, - -0.02095746248960495, - 0.030332671478390694, - -0.020467007532715797, - -0.055760808289051056, - 0.018231671303510666, - 0.01833542063832283, - 0.008342428132891655, - -0.030709944665431976, - 0.011025775223970413, - -0.020693371072411537, - 0.012478272430598736, - 0.0182882621884346, - -0.0017295564757660031, - -0.04500855505466461, - -0.019637009128928185, - 0.06296670436859131, - -0.023541774600744247, - -0.03587857261300087, - 0.012864976190030575, - -0.010723957791924477, - -0.004381071776151657, - -0.015128608793020248, - 0.021674279123544693, - -0.0026526940055191517, - 0.03087971732020378, - 0.0019146555569022894, - -0.020448144525289536, - -0.008620666339993477, - 0.002066743327304721, - 0.012732931412756443, - -0.0013310628710314631, - -0.014779631979763508, - -0.006493795197457075, - -0.033407438546419144, - 0.020655645057559013, - 0.03563334420323372, - -0.02188177779316902, - -0.01657167449593544, - -0.003961356356739998, - -0.004010873381048441, - 0.08669711649417877, - 0.062363069504499435, - 0.004758343566209078, - 0.03614266216754913, - -0.06141988933086395, - -0.014383496716618538, - 0.07503940910100937, - 0.030351536348462105, - -0.04055674374103546, - -0.019231442362070084, - -0.03133244439959526, - -0.019844509661197662, - 0.03485993668437004, - -0.004421156831085682, - 0.022372232750058174, - -0.01401565596461296, - 0.0038528908044099808, - -0.06496624648571014, - 0.023994501680135727, - 0.03710470348596573, - 0.02995540015399456, - 0.004449452273547649, - -0.015722813084721565, - -0.08292439579963684, - -0.023485183715820312, - -0.034803345799446106, - -0.016552811488509178, - -0.051988087594509125, - -0.019108828157186508, - 0.008785722777247429, - -0.008856461383402348, - -0.012912135571241379, - -0.03567107021808624, - 0.021598825231194496, - -0.043914467096328735, - 0.009445948526263237, - -0.050064001232385635, - 0.0006496153073385358, - -0.08903620392084122, - 0.0419149249792099, - -0.014770200476050377, - 0.01560019887983799, - -0.017392240464687347, - -0.006130670662969351, - -0.006616408471018076, - -0.006239136215299368, - -0.01587372086942196, - -0.046291280537843704, - 0.02446609176695347, - -0.03587857261300087, - -0.02105177938938141, - 0.037312205880880356, - -0.0033718689810484648, - -0.009342199191451073, - -0.025088591501116753, - 0.01991996355354786, - -0.014836222864687443, - -0.030370399355888367, - -0.03961356356739998, - -0.06756941974163055, - 0.01303474884480238, - -0.006927657872438431, - 0.0307476706802845, - -0.011289865709841251, - 0.019976554438471794, - -0.008710267953574657, - 0.0043103331699967384, - 0.014921109192073345, - -0.04885672777891159, - -0.028163358569145203, - 0.02310791239142418, - -0.011214411817491055, - 0.010035436600446701, - -0.026710860431194305, - 0.06277807056903839, - -0.0029073527548462152, - 0.004116981290280819, - 0.003440249478444457, - 0.0003112494305241853, - 0.03374698385596275, - -0.029804490506649017, - 0.037066977471113205, - 0.008498053066432476, - 0.020070873200893402, - -0.0045249066315591335, - -0.026390179991722107, - 0.0024334045592695475, - -0.026012906804680824, - 0.02093859761953354, - -0.019957691431045532, - -0.008639529347419739, - 0.009931686334311962, - 0.000795218744315207, - 0.04410310089588165, - 0.02093859761953354, - 0.021259279921650887, - 0.02174973301589489, - 0.03995310887694359, - 0.000977370422333479, - 0.0028484039939939976, - 0.08254712074995041, - -0.0018450961215421557, - 0.013760997913777828, - 0.02037269063293934, - -0.06160852313041687, - -0.003560504876077175, - -0.0017354513984173536, - 0.018005307763814926, - -0.015807699412107468, - -0.008097201585769653, - -0.04591400921344757, - 0.000021074180040159263, - 0.031936079263687134, - 0.005008286330848932, - -0.0047795651480555534, - -0.00887532439082861, - 0.03953811153769493, - 0.030728807672858238, - -0.031370170414447784, - 0.033520620316267014, - -0.005494024138897657, - 0.010488162748515606, - -0.028880175203084946, - 0.043424010276794434, - 0.01686405949294567, - -0.025899725034832954, - -0.038783565163612366, - 0.013760997913777828, - -0.0024027512408792973, - 0.044027648866176605, - 0.03346402943134308, - -0.01225190982222557, - -0.04131129011511803, - 0.029766764491796494, - -0.01613781228661537, - 0.030087444931268692, - 0.014270314946770668, - 0.033652666956186295, - -0.050969451665878296, - 0.007748224772512913, - -0.00014037173241376877, - -0.02550359070301056, - 0.0034709027968347073, - -0.0016399543965235353, - 0.01741110533475876, - 0.016590537503361702, - 0.007441691122949123, - 0.027993585914373398, - -0.010648502968251705, - -0.013874179683625698, - 0.016769742593169212, - 0.03355834633111954, - -0.12359432131052017, - 0.027257904410362244, - -0.0051591950468719006, - 0.03472789004445076, - -0.03953811153769493, - 0.05798671394586563, - -0.009761913679540157, - -0.06104261800646782, - 0.0005906665464863181, - -0.02256086841225624, - 0.0021893568336963654, - -0.007465270347893238, - 0.03942492976784706, - -0.010044868104159832, - -0.032747212797403336, - -0.00237445579841733, - -0.03153994306921959, - 0.012157591991126537, - -0.03599175438284874, - 0.022957004606723785, - -0.025880862027406693, - -0.022353367879986763, - -0.025579044595360756, - -0.021693142130970955, - 0.0013393157860264182, - -0.006706010550260544, - 0.0031077784951776266, - 0.01679803803563118, - 0.002025479217991233, - 0.03250198811292648, - -0.045499008148908615, - -0.08896074444055557, - 0.006781464908272028, - 0.06583397090435028, - -0.015656789764761925, - -0.0027234326116740704, - 0.014411792159080505, - -0.03257744014263153, - 0.016647128388285637, - 0.044254008680582047, - 0.007276634685695171, - -0.0014784347731620073, - -0.0016670707846060395, - -0.02448495477437973, - -0.019637009128928185, - 0.05885443836450577, - 0.006451352033764124, - -0.04432946443557739, - -0.014732473529875278, - -0.003961356356739998, - -0.014647587202489376, - 0.013185657560825348, - 0.0259751807898283, - 0.03176630660891533, - 0.022070415318012238, - 0.01139361597597599, - -0.018052468076348305, - 0.0021103655453771353, - -0.03542584553360939, - 0.026031771674752235, - -0.05455353856086731, - 0.022523140534758568, - -0.013760997913777828, - 0.03585970774292946, - -0.033313121646642685, - 0.014496678486466408, - -0.035803116858005524, - 0.024220865219831467, - -0.02027837187051773, - 0.001767283771187067, - -0.031275853514671326, - -0.0025135749019682407, - -0.02950267307460308, - 0.015883153304457664, - -0.0010156871285289526, - 0.05149763450026512, - 0.04040583595633507, - 0.016694288700819016, - -0.057118985801935196, - -0.037632886320352554, - -0.028899038210511208, - -0.020410416647791862, - -0.015072017908096313, - 0.022277913987636566, - -0.03636902570724487, - -0.008017030544579029, - -0.0005670870305038989, - -0.029181992635130882, - -0.04912082105875015, - 0.036746297031641006, - -0.03235107660293579, - -0.02299473062157631, - -0.018165647983551025, - -0.03538811579346657, - -0.03618038818240166, - 0.007507713511586189, - -0.02450381964445114, - -0.008743279613554478, - 0.020070873200893402, - -0.03780265897512436, - 0.024069955572485924, - -0.029785627499222755, - 0.019137123599648476, - 0.012714067474007607, - -0.010629639960825443, - 0.021127235144376755, - 0.002258916385471821, - -0.03574652597308159, - 0.0006171934655867517, - -0.00552703533321619, - -0.03163425996899605, - -0.052780359983444214, - -0.040254928171634674, - -0.018816443160176277, - -0.04523491859436035, - -0.03584084287285805, - 0.018637238070368767, - 0.010978616774082184, - -0.03167198970913887, - 0.006795612629503012, - 0.018825875595211983, - -0.06194806843996048, - 0.05708125978708267, - -0.043424010276794434, - 0.012685772031545639, - 0.03250198811292648, - 0.007965155877172947, - 0.04266946762800217, - 0.059948526322841644, - 0.0464421883225441, - 0.03442607447504997, - -0.01613781228661537, - 0.008691404946148396, - 0.015072017908096313, - 0.07107805460691452, - -0.022372232750058174, - 0.015147472731769085, - 0.010620207525789738, - 0.0021292290184646845, - -0.02938949130475521, - -0.012459409423172474, - -0.03338857740163803, - -0.020749961957335472, - -0.051195815205574036, - 0.0009042739402502775, - 0.007304930128157139, - 0.003940134774893522, - -0.01592087931931019, - -0.0015857215039432049, - -0.03276607766747475, - 0.019976554438471794, - 0.000674963288474828, - -0.028219949454069138, - 0.04006629064679146, - -0.037519704550504684, - 0.04610264301300049, - -0.01058248057961464, - -0.04244310408830643, - -0.039123110473155975, - 0.030011991038918495, - -0.006031636614352465, - 0.013949633575975895, - -0.021561097353696823, - 0.005866580177098513, - -0.000714458932634443, - -0.017345082014799118, - 0.0005564762395806611, - -0.006706010550260544, - -0.024918818846344948, - -0.019165419042110443, - -0.03363380208611488, - 0.055987171828746796, - -0.014477814547717571, - -0.009634585119783878, - 0.006993680261075497, - -0.05523262545466423, - 0.00965344812721014, - 0.012685772031545639, - 0.014251451008021832, - 0.030841989442706108, - 0.004069822374731302, - -0.0034237438812851906, - 0.03908538445830345, - 0.06794669479131699, - -0.04614036902785301, - 0.004355133976787329, - -0.043801285326480865, - 0.01560963038355112, - 0.018571216613054276, - 0.0647398829460144, - -0.0036265274975448847, - 0.018656102940440178, - -0.05628898739814758, - -0.005522319581359625, - 0.025880862027406693, - 0.03817993029952049, - -0.005295956507325172, - 0.01178031973540783, - 0.005229933653026819, - 0.018590079620480537, - -0.043801285326480865, - 0.0019794993568211794, - 0.018250534310936928, - -0.009893959388136864, - -0.002834256272763014, - -0.005253513343632221, - 0.014223155565559864, - -0.01840144395828247, - -0.004965843167155981, - -0.03135130554437637, - -0.028314266353845596, - -0.03870811313390732, - -0.014911677688360214, - 0.01629815250635147, - -0.028785856440663338, - 0.020410416647791862, - -0.007182316388934851, - -0.008276405744254589, - -0.0012449977220967412, - 0.03189834952354431, - 0.03225675970315933, - 0.004541411995887756, - 0.0032091704197227955, - 0.012449976988136768, - -0.012959294952452183, - 0.02344745770096779, - -0.0082528255879879, - -0.002018405357375741, - 0.004890388809144497, - -0.012242477387189865, - 0.02755972184240818, - 0.028672674670815468, - 0.02093859761953354, - 0.041047196835279465, - -0.02061791718006134, - -0.01463815476745367, - 0.002380350837484002, - -0.0033058463595807552, - -0.013600656762719154, - -0.03999083489179611, - 0.049837637692689896, - -0.0009903390891849995, - -0.012600886635482311, - -0.002815392566844821, - ] - # Pad vector to 1024 dimensions (fill with zeros for testing) - vector = vector + [0.0] * (1024 - len(vector)) - - # Example filter for testing - common filter used by multiple tests - filter_example = { - # "and": [ - # {"id": "cfe42bd6-ee78-4f6f-b997-8baa0ea957e1"}, - # {"A": "中国广西"}, - # {"created_at": {"gt": "2025-09-19"}}, - # {"created_at": {"lt": "2025-11-26"}}, - # {"tags": {"contains": "mode:fast"}}, - # {"user_name": {"like": "1744"}}, - # ] - "and": [ - {"created_at": {"gt": "2025-11-26"}} - ] - } - knowledgebase_ids = ["adimin1", "adimin2"] - - # Example filters for get_by_metadata - filters_example = [ - {"field": "status", "op": "=", "value": "activated"}, - ] - - graph = getNeo4j() - - # Or run all tests - user_name = "memosfeebbc2bd1744d7bb5b5ec57f38e828d" - scope = "LongTermMemory" - # test_search_by_embedding(graph, vector, user_name, filter_example,knowledgebase_ids) - test_get_all_memory_items(graph, scope, user_name, filter_example, knowledgebase_ids) - # test_get_by_metadata(graph, filters_example, user_name, filter_example, knowledgebase_ids) From b572eab7ac2751c149f54bd3fddd3461d0735e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Fri, 28 Nov 2025 13:43:37 +0800 Subject: [PATCH 35/36] fix --- src/memos/graph_dbs/polardb.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 2ef5b5dbf..2c177d718 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -3781,7 +3781,6 @@ def parse_filter( "memory_type", "node_type", "info", - "sources", "app_id", "agent_id" } From c82ff3ad3bc52c8b862607affefd669ad8110a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=A4=A7=E6=B4=8B?= <714403855@qq.com> Date: Fri, 28 Nov 2025 13:48:27 +0800 Subject: [PATCH 36/36] fix --- src/memos/graph_dbs/polardb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/memos/graph_dbs/polardb.py b/src/memos/graph_dbs/polardb.py index 2c177d718..a7e60704e 100644 --- a/src/memos/graph_dbs/polardb.py +++ b/src/memos/graph_dbs/polardb.py @@ -3782,7 +3782,7 @@ def parse_filter( "node_type", "info", "app_id", - "agent_id" + "agent_id", } def process_condition(condition):