aip-agents-binary 0.6.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aip-agents-binary might be problematic. Click here for more details.

Files changed (612) hide show
  1. aip_agents/__init__.py +65 -0
  2. aip_agents/__init__.pyi +19 -0
  3. aip_agents/a2a/__init__.py +19 -0
  4. aip_agents/a2a/__init__.pyi +3 -0
  5. aip_agents/a2a/server/__init__.py +10 -0
  6. aip_agents/a2a/server/__init__.pyi +4 -0
  7. aip_agents/a2a/server/base_executor.py +1086 -0
  8. aip_agents/a2a/server/base_executor.pyi +73 -0
  9. aip_agents/a2a/server/google_adk_executor.py +198 -0
  10. aip_agents/a2a/server/google_adk_executor.pyi +51 -0
  11. aip_agents/a2a/server/langflow_executor.py +180 -0
  12. aip_agents/a2a/server/langflow_executor.pyi +43 -0
  13. aip_agents/a2a/server/langgraph_executor.py +270 -0
  14. aip_agents/a2a/server/langgraph_executor.pyi +47 -0
  15. aip_agents/a2a/types.py +232 -0
  16. aip_agents/a2a/types.pyi +132 -0
  17. aip_agents/agent/__init__.py +27 -0
  18. aip_agents/agent/__init__.pyi +9 -0
  19. aip_agents/agent/base_agent.py +970 -0
  20. aip_agents/agent/base_agent.pyi +221 -0
  21. aip_agents/agent/base_langgraph_agent.py +3037 -0
  22. aip_agents/agent/base_langgraph_agent.pyi +233 -0
  23. aip_agents/agent/google_adk_agent.py +926 -0
  24. aip_agents/agent/google_adk_agent.pyi +141 -0
  25. aip_agents/agent/google_adk_constants.py +6 -0
  26. aip_agents/agent/google_adk_constants.pyi +3 -0
  27. aip_agents/agent/hitl/__init__.py +24 -0
  28. aip_agents/agent/hitl/__init__.pyi +6 -0
  29. aip_agents/agent/hitl/config.py +28 -0
  30. aip_agents/agent/hitl/config.pyi +15 -0
  31. aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
  32. aip_agents/agent/hitl/langgraph_hitl_mixin.pyi +42 -0
  33. aip_agents/agent/hitl/manager.py +532 -0
  34. aip_agents/agent/hitl/manager.pyi +200 -0
  35. aip_agents/agent/hitl/models.py +18 -0
  36. aip_agents/agent/hitl/models.pyi +3 -0
  37. aip_agents/agent/hitl/prompt/__init__.py +9 -0
  38. aip_agents/agent/hitl/prompt/__init__.pyi +4 -0
  39. aip_agents/agent/hitl/prompt/base.py +42 -0
  40. aip_agents/agent/hitl/prompt/base.pyi +24 -0
  41. aip_agents/agent/hitl/prompt/deferred.py +73 -0
  42. aip_agents/agent/hitl/prompt/deferred.pyi +30 -0
  43. aip_agents/agent/hitl/registry.py +149 -0
  44. aip_agents/agent/hitl/registry.pyi +101 -0
  45. aip_agents/agent/interface.py +138 -0
  46. aip_agents/agent/interface.pyi +81 -0
  47. aip_agents/agent/interfaces.py +65 -0
  48. aip_agents/agent/interfaces.pyi +44 -0
  49. aip_agents/agent/langflow_agent.py +464 -0
  50. aip_agents/agent/langflow_agent.pyi +133 -0
  51. aip_agents/agent/langgraph_memory_enhancer_agent.py +767 -0
  52. aip_agents/agent/langgraph_memory_enhancer_agent.pyi +50 -0
  53. aip_agents/agent/langgraph_react_agent.py +2856 -0
  54. aip_agents/agent/langgraph_react_agent.pyi +170 -0
  55. aip_agents/agent/system_instruction_context.py +34 -0
  56. aip_agents/agent/system_instruction_context.pyi +13 -0
  57. aip_agents/clients/__init__.py +10 -0
  58. aip_agents/clients/__init__.pyi +4 -0
  59. aip_agents/clients/langflow/__init__.py +10 -0
  60. aip_agents/clients/langflow/__init__.pyi +4 -0
  61. aip_agents/clients/langflow/client.py +477 -0
  62. aip_agents/clients/langflow/client.pyi +140 -0
  63. aip_agents/clients/langflow/types.py +18 -0
  64. aip_agents/clients/langflow/types.pyi +7 -0
  65. aip_agents/constants.py +23 -0
  66. aip_agents/constants.pyi +7 -0
  67. aip_agents/credentials/manager.py +132 -0
  68. aip_agents/examples/__init__.py +5 -0
  69. aip_agents/examples/__init__.pyi +0 -0
  70. aip_agents/examples/compare_streaming_client.py +783 -0
  71. aip_agents/examples/compare_streaming_client.pyi +48 -0
  72. aip_agents/examples/compare_streaming_server.py +142 -0
  73. aip_agents/examples/compare_streaming_server.pyi +18 -0
  74. aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
  75. aip_agents/examples/hello_world_a2a_google_adk_client.pyi +9 -0
  76. aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
  77. aip_agents/examples/hello_world_a2a_google_adk_client_agent.pyi +9 -0
  78. aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
  79. aip_agents/examples/hello_world_a2a_google_adk_client_streaming.pyi +9 -0
  80. aip_agents/examples/hello_world_a2a_google_adk_server.py +79 -0
  81. aip_agents/examples/hello_world_a2a_google_adk_server.pyi +15 -0
  82. aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
  83. aip_agents/examples/hello_world_a2a_langchain_client.pyi +5 -0
  84. aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
  85. aip_agents/examples/hello_world_a2a_langchain_client_agent.pyi +5 -0
  86. aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
  87. aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.pyi +5 -0
  88. aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
  89. aip_agents/examples/hello_world_a2a_langchain_client_streaming.pyi +5 -0
  90. aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
  91. aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.pyi +5 -0
  92. aip_agents/examples/hello_world_a2a_langchain_reference_server.py +105 -0
  93. aip_agents/examples/hello_world_a2a_langchain_reference_server.pyi +15 -0
  94. aip_agents/examples/hello_world_a2a_langchain_server.py +79 -0
  95. aip_agents/examples/hello_world_a2a_langchain_server.pyi +15 -0
  96. aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +78 -0
  97. aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.pyi +15 -0
  98. aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
  99. aip_agents/examples/hello_world_a2a_langflow_client.pyi +9 -0
  100. aip_agents/examples/hello_world_a2a_langflow_server.py +82 -0
  101. aip_agents/examples/hello_world_a2a_langflow_server.pyi +14 -0
  102. aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
  103. aip_agents/examples/hello_world_a2a_langgraph_artifact_client.pyi +5 -0
  104. aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
  105. aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.pyi +5 -0
  106. aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +92 -0
  107. aip_agents/examples/hello_world_a2a_langgraph_artifact_server.pyi +16 -0
  108. aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
  109. aip_agents/examples/hello_world_a2a_langgraph_client.pyi +9 -0
  110. aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
  111. aip_agents/examples/hello_world_a2a_langgraph_client_agent.pyi +9 -0
  112. aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
  113. aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.pyi +2 -0
  114. aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
  115. aip_agents/examples/hello_world_a2a_langgraph_client_streaming.pyi +9 -0
  116. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
  117. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.pyi +5 -0
  118. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
  119. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.pyi +5 -0
  120. aip_agents/examples/hello_world_a2a_langgraph_server.py +84 -0
  121. aip_agents/examples/hello_world_a2a_langgraph_server.pyi +14 -0
  122. aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +79 -0
  123. aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.pyi +15 -0
  124. aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +132 -0
  125. aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.pyi +15 -0
  126. aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
  127. aip_agents/examples/hello_world_a2a_mcp_langgraph.pyi +48 -0
  128. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
  129. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.pyi +48 -0
  130. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +251 -0
  131. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.pyi +45 -0
  132. aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
  133. aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.pyi +5 -0
  134. aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +80 -0
  135. aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.pyi +15 -0
  136. aip_agents/examples/hello_world_google_adk.py +41 -0
  137. aip_agents/examples/hello_world_google_adk.pyi +5 -0
  138. aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
  139. aip_agents/examples/hello_world_google_adk_mcp_http.pyi +5 -0
  140. aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
  141. aip_agents/examples/hello_world_google_adk_mcp_http_stream.pyi +5 -0
  142. aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
  143. aip_agents/examples/hello_world_google_adk_mcp_sse.pyi +5 -0
  144. aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
  145. aip_agents/examples/hello_world_google_adk_mcp_sse_stream.pyi +5 -0
  146. aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
  147. aip_agents/examples/hello_world_google_adk_mcp_stdio.pyi +5 -0
  148. aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
  149. aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.pyi +5 -0
  150. aip_agents/examples/hello_world_google_adk_stream.py +44 -0
  151. aip_agents/examples/hello_world_google_adk_stream.pyi +5 -0
  152. aip_agents/examples/hello_world_langchain.py +28 -0
  153. aip_agents/examples/hello_world_langchain.pyi +5 -0
  154. aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
  155. aip_agents/examples/hello_world_langchain_lm_invoker.pyi +2 -0
  156. aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
  157. aip_agents/examples/hello_world_langchain_mcp_http.pyi +5 -0
  158. aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
  159. aip_agents/examples/hello_world_langchain_mcp_http_interactive.pyi +16 -0
  160. aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
  161. aip_agents/examples/hello_world_langchain_mcp_http_stream.pyi +5 -0
  162. aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
  163. aip_agents/examples/hello_world_langchain_mcp_multi_server.pyi +18 -0
  164. aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
  165. aip_agents/examples/hello_world_langchain_mcp_sse.pyi +5 -0
  166. aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
  167. aip_agents/examples/hello_world_langchain_mcp_sse_stream.pyi +5 -0
  168. aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
  169. aip_agents/examples/hello_world_langchain_mcp_stdio.pyi +5 -0
  170. aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
  171. aip_agents/examples/hello_world_langchain_mcp_stdio_stream.pyi +5 -0
  172. aip_agents/examples/hello_world_langchain_stream.py +36 -0
  173. aip_agents/examples/hello_world_langchain_stream.pyi +5 -0
  174. aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
  175. aip_agents/examples/hello_world_langchain_stream_lm_invoker.pyi +5 -0
  176. aip_agents/examples/hello_world_langflow_agent.py +163 -0
  177. aip_agents/examples/hello_world_langflow_agent.pyi +35 -0
  178. aip_agents/examples/hello_world_langgraph.py +39 -0
  179. aip_agents/examples/hello_world_langgraph.pyi +5 -0
  180. aip_agents/examples/hello_world_langgraph_gl_connector_twitter.py +44 -0
  181. aip_agents/examples/hello_world_langgraph_gl_connector_twitter.pyi +5 -0
  182. aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
  183. aip_agents/examples/hello_world_langgraph_mcp_http.pyi +5 -0
  184. aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
  185. aip_agents/examples/hello_world_langgraph_mcp_http_stream.pyi +5 -0
  186. aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
  187. aip_agents/examples/hello_world_langgraph_mcp_sse.pyi +5 -0
  188. aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
  189. aip_agents/examples/hello_world_langgraph_mcp_sse_stream.pyi +5 -0
  190. aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
  191. aip_agents/examples/hello_world_langgraph_mcp_stdio.pyi +5 -0
  192. aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
  193. aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.pyi +5 -0
  194. aip_agents/examples/hello_world_langgraph_stream.py +43 -0
  195. aip_agents/examples/hello_world_langgraph_stream.pyi +5 -0
  196. aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
  197. aip_agents/examples/hello_world_langgraph_stream_lm_invoker.pyi +5 -0
  198. aip_agents/examples/hello_world_model_switch_cli.py +210 -0
  199. aip_agents/examples/hello_world_model_switch_cli.pyi +30 -0
  200. aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
  201. aip_agents/examples/hello_world_multi_agent_adk.pyi +6 -0
  202. aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
  203. aip_agents/examples/hello_world_multi_agent_langchain.pyi +5 -0
  204. aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
  205. aip_agents/examples/hello_world_multi_agent_langgraph.pyi +5 -0
  206. aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
  207. aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.pyi +5 -0
  208. aip_agents/examples/hello_world_pii_logger.py +21 -0
  209. aip_agents/examples/hello_world_pii_logger.pyi +5 -0
  210. aip_agents/examples/hello_world_ptc.py +49 -0
  211. aip_agents/examples/hello_world_ptc.pyi +5 -0
  212. aip_agents/examples/hello_world_sentry.py +133 -0
  213. aip_agents/examples/hello_world_sentry.pyi +21 -0
  214. aip_agents/examples/hello_world_step_limits.py +273 -0
  215. aip_agents/examples/hello_world_step_limits.pyi +17 -0
  216. aip_agents/examples/hello_world_stock_a2a_server.py +103 -0
  217. aip_agents/examples/hello_world_stock_a2a_server.pyi +17 -0
  218. aip_agents/examples/hello_world_tool_output_client.py +55 -0
  219. aip_agents/examples/hello_world_tool_output_client.pyi +5 -0
  220. aip_agents/examples/hello_world_tool_output_server.py +114 -0
  221. aip_agents/examples/hello_world_tool_output_server.pyi +19 -0
  222. aip_agents/examples/hitl_demo.py +724 -0
  223. aip_agents/examples/hitl_demo.pyi +67 -0
  224. aip_agents/examples/mcp_configs/configs.py +63 -0
  225. aip_agents/examples/mcp_servers/common.py +76 -0
  226. aip_agents/examples/mcp_servers/mcp_name.py +29 -0
  227. aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
  228. aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
  229. aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
  230. aip_agents/examples/mcp_servers/mcp_time.py +10 -0
  231. aip_agents/examples/pii_demo_langgraph_client.py +69 -0
  232. aip_agents/examples/pii_demo_langgraph_client.pyi +5 -0
  233. aip_agents/examples/pii_demo_langgraph_server.py +126 -0
  234. aip_agents/examples/pii_demo_langgraph_server.pyi +20 -0
  235. aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
  236. aip_agents/examples/pii_demo_multi_agent_client.pyi +5 -0
  237. aip_agents/examples/pii_demo_multi_agent_server.py +247 -0
  238. aip_agents/examples/pii_demo_multi_agent_server.pyi +40 -0
  239. aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
  240. aip_agents/examples/todolist_planning_a2a_langchain_client.pyi +5 -0
  241. aip_agents/examples/todolist_planning_a2a_langgraph_server.py +88 -0
  242. aip_agents/examples/todolist_planning_a2a_langgraph_server.pyi +19 -0
  243. aip_agents/examples/tools/__init__.py +27 -0
  244. aip_agents/examples/tools/__init__.pyi +9 -0
  245. aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
  246. aip_agents/examples/tools/adk_arithmetic_tools.pyi +24 -0
  247. aip_agents/examples/tools/adk_weather_tool.py +60 -0
  248. aip_agents/examples/tools/adk_weather_tool.pyi +18 -0
  249. aip_agents/examples/tools/data_generator_tool.py +103 -0
  250. aip_agents/examples/tools/data_generator_tool.pyi +15 -0
  251. aip_agents/examples/tools/data_visualization_tool.py +312 -0
  252. aip_agents/examples/tools/data_visualization_tool.pyi +19 -0
  253. aip_agents/examples/tools/image_artifact_tool.py +136 -0
  254. aip_agents/examples/tools/image_artifact_tool.pyi +26 -0
  255. aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
  256. aip_agents/examples/tools/langchain_arithmetic_tools.pyi +17 -0
  257. aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
  258. aip_agents/examples/tools/langchain_currency_exchange_tool.pyi +20 -0
  259. aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
  260. aip_agents/examples/tools/langchain_graph_artifact_tool.pyi +25 -0
  261. aip_agents/examples/tools/langchain_weather_tool.py +48 -0
  262. aip_agents/examples/tools/langchain_weather_tool.pyi +19 -0
  263. aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
  264. aip_agents/examples/tools/langgraph_streaming_tool.pyi +43 -0
  265. aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
  266. aip_agents/examples/tools/mock_retrieval_tool.pyi +13 -0
  267. aip_agents/examples/tools/pii_demo_tools.py +189 -0
  268. aip_agents/examples/tools/pii_demo_tools.pyi +54 -0
  269. aip_agents/examples/tools/random_chart_tool.py +142 -0
  270. aip_agents/examples/tools/random_chart_tool.pyi +20 -0
  271. aip_agents/examples/tools/serper_tool.py +202 -0
  272. aip_agents/examples/tools/serper_tool.pyi +16 -0
  273. aip_agents/examples/tools/stock_tools.py +82 -0
  274. aip_agents/examples/tools/stock_tools.pyi +36 -0
  275. aip_agents/examples/tools/table_generator_tool.py +167 -0
  276. aip_agents/examples/tools/table_generator_tool.pyi +22 -0
  277. aip_agents/examples/tools/time_tool.py +82 -0
  278. aip_agents/examples/tools/time_tool.pyi +15 -0
  279. aip_agents/examples/tools/weather_forecast_tool.py +38 -0
  280. aip_agents/examples/tools/weather_forecast_tool.pyi +14 -0
  281. aip_agents/executor/agent_executor.py +473 -0
  282. aip_agents/executor/base.py +48 -0
  283. aip_agents/guardrails/__init__.py +83 -0
  284. aip_agents/guardrails/__init__.pyi +6 -0
  285. aip_agents/guardrails/engines/__init__.py +69 -0
  286. aip_agents/guardrails/engines/__init__.pyi +4 -0
  287. aip_agents/guardrails/engines/base.py +90 -0
  288. aip_agents/guardrails/engines/base.pyi +61 -0
  289. aip_agents/guardrails/engines/nemo.py +101 -0
  290. aip_agents/guardrails/engines/nemo.pyi +46 -0
  291. aip_agents/guardrails/engines/phrase_matcher.py +113 -0
  292. aip_agents/guardrails/engines/phrase_matcher.pyi +48 -0
  293. aip_agents/guardrails/exceptions.py +39 -0
  294. aip_agents/guardrails/exceptions.pyi +23 -0
  295. aip_agents/guardrails/manager.py +163 -0
  296. aip_agents/guardrails/manager.pyi +42 -0
  297. aip_agents/guardrails/middleware.py +199 -0
  298. aip_agents/guardrails/middleware.pyi +87 -0
  299. aip_agents/guardrails/schemas.py +63 -0
  300. aip_agents/guardrails/schemas.pyi +43 -0
  301. aip_agents/guardrails/utils.py +45 -0
  302. aip_agents/guardrails/utils.pyi +19 -0
  303. aip_agents/mcp/__init__.py +1 -0
  304. aip_agents/mcp/__init__.pyi +0 -0
  305. aip_agents/mcp/client/__init__.py +14 -0
  306. aip_agents/mcp/client/__init__.pyi +5 -0
  307. aip_agents/mcp/client/base_mcp_client.py +369 -0
  308. aip_agents/mcp/client/base_mcp_client.pyi +148 -0
  309. aip_agents/mcp/client/connection_manager.py +228 -0
  310. aip_agents/mcp/client/connection_manager.pyi +51 -0
  311. aip_agents/mcp/client/google_adk/__init__.py +11 -0
  312. aip_agents/mcp/client/google_adk/__init__.pyi +3 -0
  313. aip_agents/mcp/client/google_adk/client.py +381 -0
  314. aip_agents/mcp/client/google_adk/client.pyi +75 -0
  315. aip_agents/mcp/client/langchain/__init__.py +11 -0
  316. aip_agents/mcp/client/langchain/__init__.pyi +3 -0
  317. aip_agents/mcp/client/langchain/client.py +265 -0
  318. aip_agents/mcp/client/langchain/client.pyi +48 -0
  319. aip_agents/mcp/client/persistent_session.py +612 -0
  320. aip_agents/mcp/client/persistent_session.pyi +122 -0
  321. aip_agents/mcp/client/session_pool.py +351 -0
  322. aip_agents/mcp/client/session_pool.pyi +101 -0
  323. aip_agents/mcp/client/transports.py +263 -0
  324. aip_agents/mcp/client/transports.pyi +132 -0
  325. aip_agents/mcp/utils/__init__.py +7 -0
  326. aip_agents/mcp/utils/__init__.pyi +0 -0
  327. aip_agents/mcp/utils/config_validator.py +139 -0
  328. aip_agents/mcp/utils/config_validator.pyi +82 -0
  329. aip_agents/memory/__init__.py +14 -0
  330. aip_agents/memory/__init__.pyi +5 -0
  331. aip_agents/memory/adapters/__init__.py +10 -0
  332. aip_agents/memory/adapters/__init__.pyi +4 -0
  333. aip_agents/memory/adapters/base_adapter.py +811 -0
  334. aip_agents/memory/adapters/base_adapter.pyi +176 -0
  335. aip_agents/memory/adapters/mem0.py +84 -0
  336. aip_agents/memory/adapters/mem0.pyi +22 -0
  337. aip_agents/memory/base.py +84 -0
  338. aip_agents/memory/base.pyi +60 -0
  339. aip_agents/memory/constants.py +49 -0
  340. aip_agents/memory/constants.pyi +25 -0
  341. aip_agents/memory/factory.py +86 -0
  342. aip_agents/memory/factory.pyi +24 -0
  343. aip_agents/memory/guidance.py +20 -0
  344. aip_agents/memory/guidance.pyi +3 -0
  345. aip_agents/memory/simple_memory.py +47 -0
  346. aip_agents/memory/simple_memory.pyi +23 -0
  347. aip_agents/middleware/__init__.py +17 -0
  348. aip_agents/middleware/__init__.pyi +5 -0
  349. aip_agents/middleware/base.py +96 -0
  350. aip_agents/middleware/base.pyi +75 -0
  351. aip_agents/middleware/manager.py +150 -0
  352. aip_agents/middleware/manager.pyi +84 -0
  353. aip_agents/middleware/todolist.py +274 -0
  354. aip_agents/middleware/todolist.pyi +125 -0
  355. aip_agents/ptc/__init__.py +48 -0
  356. aip_agents/ptc/__init__.pyi +10 -0
  357. aip_agents/ptc/doc_gen.py +122 -0
  358. aip_agents/ptc/doc_gen.pyi +40 -0
  359. aip_agents/ptc/exceptions.py +39 -0
  360. aip_agents/ptc/exceptions.pyi +22 -0
  361. aip_agents/ptc/executor.py +143 -0
  362. aip_agents/ptc/executor.pyi +73 -0
  363. aip_agents/ptc/mcp/__init__.py +45 -0
  364. aip_agents/ptc/mcp/__init__.pyi +7 -0
  365. aip_agents/ptc/mcp/sandbox_bridge.py +668 -0
  366. aip_agents/ptc/mcp/sandbox_bridge.pyi +47 -0
  367. aip_agents/ptc/mcp/templates/__init__.py +1 -0
  368. aip_agents/ptc/mcp/templates/__init__.pyi +0 -0
  369. aip_agents/ptc/mcp/templates/mcp_client.py.template +239 -0
  370. aip_agents/ptc/naming.py +184 -0
  371. aip_agents/ptc/naming.pyi +76 -0
  372. aip_agents/ptc/payload.py +26 -0
  373. aip_agents/ptc/payload.pyi +15 -0
  374. aip_agents/ptc/prompt_builder.py +571 -0
  375. aip_agents/ptc/prompt_builder.pyi +55 -0
  376. aip_agents/ptc/ptc_helper.py +16 -0
  377. aip_agents/ptc/ptc_helper.pyi +1 -0
  378. aip_agents/ptc/sandbox_bridge.py +58 -0
  379. aip_agents/ptc/sandbox_bridge.pyi +25 -0
  380. aip_agents/ptc/template_utils.py +33 -0
  381. aip_agents/ptc/template_utils.pyi +13 -0
  382. aip_agents/ptc/templates/__init__.py +1 -0
  383. aip_agents/ptc/templates/__init__.pyi +0 -0
  384. aip_agents/ptc/templates/ptc_helper.py.template +134 -0
  385. aip_agents/sandbox/__init__.py +43 -0
  386. aip_agents/sandbox/__init__.pyi +5 -0
  387. aip_agents/sandbox/defaults.py +9 -0
  388. aip_agents/sandbox/defaults.pyi +2 -0
  389. aip_agents/sandbox/e2b_runtime.py +267 -0
  390. aip_agents/sandbox/e2b_runtime.pyi +51 -0
  391. aip_agents/sandbox/template_builder.py +131 -0
  392. aip_agents/sandbox/template_builder.pyi +36 -0
  393. aip_agents/sandbox/types.py +24 -0
  394. aip_agents/sandbox/types.pyi +14 -0
  395. aip_agents/sandbox/validation.py +50 -0
  396. aip_agents/sandbox/validation.pyi +20 -0
  397. aip_agents/schema/__init__.py +69 -0
  398. aip_agents/schema/__init__.pyi +9 -0
  399. aip_agents/schema/a2a.py +56 -0
  400. aip_agents/schema/a2a.pyi +40 -0
  401. aip_agents/schema/agent.py +111 -0
  402. aip_agents/schema/agent.pyi +65 -0
  403. aip_agents/schema/hitl.py +157 -0
  404. aip_agents/schema/hitl.pyi +89 -0
  405. aip_agents/schema/langgraph.py +37 -0
  406. aip_agents/schema/langgraph.pyi +28 -0
  407. aip_agents/schema/model_id.py +97 -0
  408. aip_agents/schema/model_id.pyi +54 -0
  409. aip_agents/schema/step_limit.py +108 -0
  410. aip_agents/schema/step_limit.pyi +63 -0
  411. aip_agents/schema/storage.py +40 -0
  412. aip_agents/schema/storage.pyi +21 -0
  413. aip_agents/sentry/__init__.py +11 -0
  414. aip_agents/sentry/__init__.pyi +3 -0
  415. aip_agents/sentry/sentry.py +151 -0
  416. aip_agents/sentry/sentry.pyi +48 -0
  417. aip_agents/storage/__init__.py +41 -0
  418. aip_agents/storage/__init__.pyi +8 -0
  419. aip_agents/storage/base.py +85 -0
  420. aip_agents/storage/base.pyi +58 -0
  421. aip_agents/storage/clients/__init__.py +12 -0
  422. aip_agents/storage/clients/__init__.pyi +3 -0
  423. aip_agents/storage/clients/minio_client.py +318 -0
  424. aip_agents/storage/clients/minio_client.pyi +137 -0
  425. aip_agents/storage/config.py +62 -0
  426. aip_agents/storage/config.pyi +29 -0
  427. aip_agents/storage/providers/__init__.py +15 -0
  428. aip_agents/storage/providers/__init__.pyi +5 -0
  429. aip_agents/storage/providers/base.py +106 -0
  430. aip_agents/storage/providers/base.pyi +88 -0
  431. aip_agents/storage/providers/memory.py +114 -0
  432. aip_agents/storage/providers/memory.pyi +79 -0
  433. aip_agents/storage/providers/object_storage.py +214 -0
  434. aip_agents/storage/providers/object_storage.pyi +98 -0
  435. aip_agents/tools/__init__.py +64 -0
  436. aip_agents/tools/__init__.pyi +11 -0
  437. aip_agents/tools/browser_use/__init__.py +82 -0
  438. aip_agents/tools/browser_use/__init__.pyi +14 -0
  439. aip_agents/tools/browser_use/action_parser.py +103 -0
  440. aip_agents/tools/browser_use/action_parser.pyi +18 -0
  441. aip_agents/tools/browser_use/browser_use_tool.py +1120 -0
  442. aip_agents/tools/browser_use/browser_use_tool.pyi +50 -0
  443. aip_agents/tools/browser_use/llm_config.py +120 -0
  444. aip_agents/tools/browser_use/llm_config.pyi +52 -0
  445. aip_agents/tools/browser_use/minio_storage.py +198 -0
  446. aip_agents/tools/browser_use/minio_storage.pyi +109 -0
  447. aip_agents/tools/browser_use/schemas.py +119 -0
  448. aip_agents/tools/browser_use/schemas.pyi +32 -0
  449. aip_agents/tools/browser_use/session.py +76 -0
  450. aip_agents/tools/browser_use/session.pyi +4 -0
  451. aip_agents/tools/browser_use/session_errors.py +132 -0
  452. aip_agents/tools/browser_use/session_errors.pyi +53 -0
  453. aip_agents/tools/browser_use/steel_session_recording.py +317 -0
  454. aip_agents/tools/browser_use/steel_session_recording.pyi +63 -0
  455. aip_agents/tools/browser_use/streaming.py +815 -0
  456. aip_agents/tools/browser_use/streaming.pyi +81 -0
  457. aip_agents/tools/browser_use/structured_data_parser.py +257 -0
  458. aip_agents/tools/browser_use/structured_data_parser.pyi +86 -0
  459. aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
  460. aip_agents/tools/browser_use/structured_data_recovery.pyi +43 -0
  461. aip_agents/tools/browser_use/types.py +78 -0
  462. aip_agents/tools/browser_use/types.pyi +45 -0
  463. aip_agents/tools/code_sandbox/__init__.py +26 -0
  464. aip_agents/tools/code_sandbox/__init__.pyi +3 -0
  465. aip_agents/tools/code_sandbox/constant.py +13 -0
  466. aip_agents/tools/code_sandbox/constant.pyi +4 -0
  467. aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +306 -0
  468. aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.pyi +102 -0
  469. aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +411 -0
  470. aip_agents/tools/code_sandbox/e2b_sandbox_tool.pyi +29 -0
  471. aip_agents/tools/constants.py +177 -0
  472. aip_agents/tools/constants.pyi +138 -0
  473. aip_agents/tools/date_range_tool.py +554 -0
  474. aip_agents/tools/date_range_tool.pyi +21 -0
  475. aip_agents/tools/document_loader/__init__.py +44 -0
  476. aip_agents/tools/document_loader/__init__.pyi +7 -0
  477. aip_agents/tools/document_loader/base_reader.py +302 -0
  478. aip_agents/tools/document_loader/base_reader.pyi +75 -0
  479. aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
  480. aip_agents/tools/document_loader/docx_reader_tool.pyi +10 -0
  481. aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
  482. aip_agents/tools/document_loader/excel_reader_tool.pyi +26 -0
  483. aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
  484. aip_agents/tools/document_loader/pdf_reader_tool.pyi +11 -0
  485. aip_agents/tools/document_loader/pdf_splitter.py +169 -0
  486. aip_agents/tools/document_loader/pdf_splitter.pyi +18 -0
  487. aip_agents/tools/execute_ptc_code.py +308 -0
  488. aip_agents/tools/execute_ptc_code.pyi +90 -0
  489. aip_agents/tools/gl_connector/__init__.py +5 -0
  490. aip_agents/tools/gl_connector/__init__.pyi +3 -0
  491. aip_agents/tools/gl_connector/tool.py +383 -0
  492. aip_agents/tools/gl_connector/tool.pyi +74 -0
  493. aip_agents/tools/gl_connector_tools.py +119 -0
  494. aip_agents/tools/gl_connector_tools.pyi +39 -0
  495. aip_agents/tools/memory_search/__init__.py +29 -0
  496. aip_agents/tools/memory_search/__init__.pyi +5 -0
  497. aip_agents/tools/memory_search/base.py +200 -0
  498. aip_agents/tools/memory_search/base.pyi +69 -0
  499. aip_agents/tools/memory_search/mem0.py +365 -0
  500. aip_agents/tools/memory_search/mem0.pyi +29 -0
  501. aip_agents/tools/memory_search/schema.py +81 -0
  502. aip_agents/tools/memory_search/schema.pyi +25 -0
  503. aip_agents/tools/memory_search_tool.py +34 -0
  504. aip_agents/tools/memory_search_tool.pyi +3 -0
  505. aip_agents/tools/time_tool.py +117 -0
  506. aip_agents/tools/time_tool.pyi +16 -0
  507. aip_agents/tools/tool_config_injector.py +300 -0
  508. aip_agents/tools/tool_config_injector.pyi +26 -0
  509. aip_agents/tools/web_search/__init__.py +15 -0
  510. aip_agents/tools/web_search/__init__.pyi +3 -0
  511. aip_agents/tools/web_search/serper_tool.py +187 -0
  512. aip_agents/tools/web_search/serper_tool.pyi +19 -0
  513. aip_agents/types/__init__.py +70 -0
  514. aip_agents/types/__init__.pyi +36 -0
  515. aip_agents/types/a2a_events.py +13 -0
  516. aip_agents/types/a2a_events.pyi +3 -0
  517. aip_agents/utils/__init__.py +79 -0
  518. aip_agents/utils/__init__.pyi +11 -0
  519. aip_agents/utils/a2a_connector.py +1757 -0
  520. aip_agents/utils/a2a_connector.pyi +146 -0
  521. aip_agents/utils/artifact_helpers.py +502 -0
  522. aip_agents/utils/artifact_helpers.pyi +203 -0
  523. aip_agents/utils/constants.py +22 -0
  524. aip_agents/utils/constants.pyi +10 -0
  525. aip_agents/utils/datetime/__init__.py +34 -0
  526. aip_agents/utils/datetime/__init__.pyi +4 -0
  527. aip_agents/utils/datetime/normalization.py +231 -0
  528. aip_agents/utils/datetime/normalization.pyi +95 -0
  529. aip_agents/utils/datetime/timezone.py +206 -0
  530. aip_agents/utils/datetime/timezone.pyi +48 -0
  531. aip_agents/utils/env_loader.py +27 -0
  532. aip_agents/utils/env_loader.pyi +10 -0
  533. aip_agents/utils/event_handler_registry.py +58 -0
  534. aip_agents/utils/event_handler_registry.pyi +23 -0
  535. aip_agents/utils/file_prompt_utils.py +176 -0
  536. aip_agents/utils/file_prompt_utils.pyi +21 -0
  537. aip_agents/utils/final_response_builder.py +211 -0
  538. aip_agents/utils/final_response_builder.pyi +34 -0
  539. aip_agents/utils/formatter_llm_client.py +231 -0
  540. aip_agents/utils/formatter_llm_client.pyi +71 -0
  541. aip_agents/utils/langgraph/__init__.py +19 -0
  542. aip_agents/utils/langgraph/__init__.pyi +3 -0
  543. aip_agents/utils/langgraph/converter.py +128 -0
  544. aip_agents/utils/langgraph/converter.pyi +49 -0
  545. aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
  546. aip_agents/utils/langgraph/tool_managers/__init__.pyi +5 -0
  547. aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
  548. aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.pyi +35 -0
  549. aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
  550. aip_agents/utils/langgraph/tool_managers/base_tool_manager.pyi +48 -0
  551. aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1096 -0
  552. aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.pyi +56 -0
  553. aip_agents/utils/langgraph/tool_output_management.py +1047 -0
  554. aip_agents/utils/langgraph/tool_output_management.pyi +329 -0
  555. aip_agents/utils/logger.py +195 -0
  556. aip_agents/utils/logger.pyi +60 -0
  557. aip_agents/utils/metadata/__init__.py +27 -0
  558. aip_agents/utils/metadata/__init__.pyi +5 -0
  559. aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
  560. aip_agents/utils/metadata/activity_metadata_helper.pyi +25 -0
  561. aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
  562. aip_agents/utils/metadata/activity_narrative/__init__.pyi +7 -0
  563. aip_agents/utils/metadata/activity_narrative/builder.py +817 -0
  564. aip_agents/utils/metadata/activity_narrative/builder.pyi +35 -0
  565. aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
  566. aip_agents/utils/metadata/activity_narrative/constants.pyi +10 -0
  567. aip_agents/utils/metadata/activity_narrative/context.py +49 -0
  568. aip_agents/utils/metadata/activity_narrative/context.pyi +32 -0
  569. aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
  570. aip_agents/utils/metadata/activity_narrative/formatters.pyi +48 -0
  571. aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
  572. aip_agents/utils/metadata/activity_narrative/utils.pyi +12 -0
  573. aip_agents/utils/metadata/schemas/__init__.py +16 -0
  574. aip_agents/utils/metadata/schemas/__init__.pyi +4 -0
  575. aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
  576. aip_agents/utils/metadata/schemas/activity_schema.pyi +18 -0
  577. aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
  578. aip_agents/utils/metadata/schemas/thinking_schema.pyi +20 -0
  579. aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
  580. aip_agents/utils/metadata/thinking_metadata_helper.pyi +4 -0
  581. aip_agents/utils/metadata_helper.py +358 -0
  582. aip_agents/utils/metadata_helper.pyi +117 -0
  583. aip_agents/utils/name_preprocessor/__init__.py +17 -0
  584. aip_agents/utils/name_preprocessor/__init__.pyi +6 -0
  585. aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
  586. aip_agents/utils/name_preprocessor/base_name_preprocessor.pyi +52 -0
  587. aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
  588. aip_agents/utils/name_preprocessor/google_name_preprocessor.pyi +38 -0
  589. aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
  590. aip_agents/utils/name_preprocessor/name_preprocessor.pyi +41 -0
  591. aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
  592. aip_agents/utils/name_preprocessor/openai_name_preprocessor.pyi +34 -0
  593. aip_agents/utils/pii/__init__.py +25 -0
  594. aip_agents/utils/pii/__init__.pyi +5 -0
  595. aip_agents/utils/pii/pii_handler.py +397 -0
  596. aip_agents/utils/pii/pii_handler.pyi +96 -0
  597. aip_agents/utils/pii/pii_helper.py +207 -0
  598. aip_agents/utils/pii/pii_helper.pyi +78 -0
  599. aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
  600. aip_agents/utils/pii/uuid_deanonymizer_mapping.pyi +73 -0
  601. aip_agents/utils/reference_helper.py +273 -0
  602. aip_agents/utils/reference_helper.pyi +81 -0
  603. aip_agents/utils/sse_chunk_transformer.py +831 -0
  604. aip_agents/utils/sse_chunk_transformer.pyi +166 -0
  605. aip_agents/utils/step_limit_manager.py +265 -0
  606. aip_agents/utils/step_limit_manager.pyi +112 -0
  607. aip_agents/utils/token_usage_helper.py +156 -0
  608. aip_agents/utils/token_usage_helper.pyi +60 -0
  609. aip_agents_binary-0.6.4.dist-info/METADATA +673 -0
  610. aip_agents_binary-0.6.4.dist-info/RECORD +612 -0
  611. aip_agents_binary-0.6.4.dist-info/WHEEL +5 -0
  612. aip_agents_binary-0.6.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,767 @@
1
+ """LangGraph Memory Enhancer Agent.
2
+
3
+ This module implements the ``LangGraphMemoryEnhancerAgent``, a dedicated LangGraph helper agent
4
+ that automatically augments user queries with relevant memories before the primary agent runs.
5
+ It replaces manual memory tool invocation with a consistent preprocessing layer.
6
+
7
+ Authors:
8
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
9
+ """
10
+
11
+ import json
12
+ import textwrap
13
+ from typing import Any
14
+
15
+ from langchain_core.language_models import BaseChatModel
16
+ from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage
17
+ from langgraph.graph import END, StateGraph
18
+ from langgraph.graph.state import CompiledStateGraph
19
+
20
+ from aip_agents.agent.langgraph_react_agent import LangGraphReactAgent
21
+ from aip_agents.agent.system_instruction_context import get_current_date_context
22
+ from aip_agents.memory.guidance import MEM0_MEMORY_RECALL_GUIDANCE
23
+ from aip_agents.tools.memory_search_tool import (
24
+ MEMORY_DELETE_TOOL_NAME,
25
+ MEMORY_SEARCH_TOOL_NAME,
26
+ LongTermMemorySearchTool,
27
+ Mem0DeleteTool,
28
+ Mem0SearchTool,
29
+ )
30
+ from aip_agents.utils.langgraph import (
31
+ convert_langchain_messages_to_gllm_messages,
32
+ convert_lm_output_to_langchain_message,
33
+ )
34
+ from aip_agents.utils.logger import get_logger
35
+
36
+ logger = get_logger(__name__)
37
+
38
+
39
+ class LangGraphMemoryEnhancerAgent(LangGraphReactAgent):
40
+ """Simplified mini-agent for automatic memory retrieval or deletion and query enhancement.
41
+
42
+ This agent has a simple 2-node LangGraph (agent + tools) and uses existing memory
43
+ infrastructure to enhance user queries with relevant context. It acts as a
44
+ preprocessing layer that automatically attempts memory retrieval for every query.
45
+
46
+ Key features:
47
+ - Uses runtime `memory_user_id` provided via call arguments (no static storage)
48
+ - Uses simplified instruction reusing existing guidance
49
+ - Standard 2-node LangGraph pattern (agent -> tools -> agent)
50
+ - Automatically enhances queries with memory context when available
51
+ - Returns original query unchanged if no relevant memories found
52
+ """
53
+
54
+ def __init__(self, memory, **kwargs) -> None:
55
+ """Initialize the LangGraphMemoryEnhancerAgent with memory backend and configuration.
56
+
57
+ Args:
58
+ memory: Memory backend instance (Mem0Memory or compatible)
59
+ **kwargs: Additional arguments passed to BaseLangGraphAgent, including:
60
+ - memory_agent_id: Fallback user ID for memory operations
61
+ - model: LLM model to use for memory decisions
62
+ - Other BaseLangGraphAgent parameters
63
+ """
64
+ memory_search_tool: LongTermMemorySearchTool = Mem0SearchTool(
65
+ memory=memory,
66
+ default_user_id=kwargs.get("memory_agent_id"),
67
+ user_id_provider=None,
68
+ )
69
+ memory_delete_tool: LongTermMemorySearchTool = Mem0DeleteTool(
70
+ memory=memory,
71
+ default_user_id=kwargs.get("memory_agent_id"),
72
+ user_id_provider=None,
73
+ )
74
+ kwargs["save_interaction_to_memory"] = False
75
+ super().__init__(
76
+ name="LangGraphMemoryEnhancerAgent",
77
+ instruction=self._build_simple_instruction(),
78
+ tools=[memory_search_tool, memory_delete_tool],
79
+ **kwargs,
80
+ )
81
+
82
+ def _build_simple_instruction(self) -> str:
83
+ """Build simplified memory recall instruction reusing existing components.
84
+
85
+ Returns:
86
+ str: Complete instruction including date context and memory guidance
87
+ """
88
+ date_context = get_current_date_context()
89
+
90
+ instruction = textwrap.dedent(f"""
91
+ {date_context}
92
+
93
+ You are a Memory Controller Agent that decides whether to retrieve or delete memory.
94
+
95
+ Important: You WILL NOT see the tool results. The system will either append retrieved memory
96
+ to the user input or return a memory action summary after your turn. Your sole responsibility
97
+ is to trigger the correct tool calls with concise arguments based on the user's message.
98
+
99
+ What to do:
100
+ 1. Read the user's message as-is (do not rephrase it).
101
+ 2. Decide which tool to call:
102
+ - Use `built_in_mem0_search` to retrieve memory for answering questions.
103
+ - Use `built_in_mem0_delete` when the user asks to forget/delete memories.
104
+ Prefer a single call, but you MAY make multiple calls when clearly needed.
105
+ - If the user implies a time frame (e.g., "yesterday", "last week"), set `start_date`/`end_date`.
106
+ - If the user implies a precise range, set `start_date`/`end_date` (YYYY-MM-DD).
107
+ - If the user mentions a topic, set a concise `query` (few words or at most a sentence).
108
+ - Adjust `limit` to higher number to allow more memory to be retrieved if needed.
109
+ - Default when uncertain: omit dates, set a concise `query` derived from the message,
110
+ and set `limit=10`.
111
+ 3. Do NOT answer the user's question. Do NOT summarize. Do NOT format output. The system will handle it.
112
+
113
+ Constraints:
114
+ - Keep tool arguments succinct and precise; avoid verbose or speculative queries.
115
+ - Never invent facts. If unsure about time ranges, prefer omitting dates rather than fabricating.
116
+ - Do not include any preambles or explanations in your messages.
117
+ - Make one or more tool calls as needed; avoid duplicates or redundant calls.
118
+
119
+ Reference guidance:
120
+ {MEM0_MEMORY_RECALL_GUIDANCE}
121
+ """).strip()
122
+
123
+ return instruction
124
+
125
+ async def _memory_retrieval_node(self, state: dict, config: dict | None = None) -> dict:
126
+ """Execute memory retrieval or deletion using explicit tool calls or synthesized defaults.
127
+
128
+ Args:
129
+ state: LangGraph state containing the conversation `messages` history.
130
+ config: Optional LangGraph configuration forwarded to the memory tool.
131
+
132
+ Returns:
133
+ dict: State update whose `messages` list contains `ToolMessage` outputs.
134
+ """
135
+ messages = state.get("messages", [])
136
+ tool_calls = self._extract_mem0_tool_calls(messages)
137
+
138
+ if tool_calls:
139
+ tool_messages = await self._execute_mem0_tool_calls(tool_calls, state, config)
140
+ return {"messages": tool_messages}
141
+
142
+ default_query = self._extract_last_human_query(messages)
143
+ tool_messages = await self._execute_default_retrieval(default_query, state, config)
144
+ return {"messages": tool_messages}
145
+
146
+ def _extract_mem0_tool_calls(self, messages: list) -> list[dict[str, Any]]:
147
+ """Return all Mem0 tool calls from the last message if present.
148
+
149
+ Args:
150
+ messages: Ordered list of LangChain message objects representing the state.
151
+
152
+ Returns:
153
+ List of tool call dictionaries filtered for the Mem0 search tool.
154
+ """
155
+ if not messages:
156
+ return []
157
+
158
+ last_message = messages[-1]
159
+ tool_calls = getattr(last_message, "tool_calls", None)
160
+ if not tool_calls:
161
+ return []
162
+
163
+ return [tc for tc in tool_calls if tc.get("name") in {MEMORY_SEARCH_TOOL_NAME, MEMORY_DELETE_TOOL_NAME}]
164
+
165
+ async def _execute_mem0_tool_calls(
166
+ self,
167
+ tool_calls: list[dict[str, Any]],
168
+ state: dict,
169
+ config: dict | None,
170
+ ) -> list[ToolMessage]:
171
+ """Execute the provided Mem0 tool calls and return their messages.
172
+
173
+ Args:
174
+ tool_calls: Tool call dictionaries emitted by the LLM.
175
+ state: LangGraph state containing messages and metadata.
176
+ config: Optional runnable configuration forwarded to the tool.
177
+
178
+ Returns:
179
+ List of `ToolMessage` objects describing each execution result.
180
+ """
181
+ tool_messages: list[ToolMessage] = []
182
+ delete_intent: dict[str, Any] | None = None
183
+ user_query = self._extract_last_human_query(state.get("messages", [])) or self._fallback_query(
184
+ state.get("messages", [])
185
+ )
186
+ for index, tool_call in enumerate(tool_calls):
187
+ tool_name = tool_call.get("name") or MEMORY_SEARCH_TOOL_NAME
188
+ args = dict(tool_call.get("args") or {})
189
+ if "id" not in args and "id" in tool_call:
190
+ args["id"] = tool_call["id"]
191
+ log_args = self._redact_mem0_args(tool_name, args)
192
+ logger.info("Executing memory tool call #%s name=%s args=%s", index, tool_name, log_args)
193
+ if tool_name == MEMORY_DELETE_TOOL_NAME:
194
+ delete_intent = delete_intent or await self._preprocess_delete_intent(user_query, state, config)
195
+ if not self._is_delete_intent_confirmed(delete_intent):
196
+ tool_messages.append(self._build_delete_confirmation_message(tool_call, user_query))
197
+ continue
198
+ tool_messages.append(await self._execute_mem0_call(tool_name, args, state, config))
199
+ return tool_messages
200
+
201
+ def _redact_mem0_args(self, tool_name: str, args: dict[str, Any]) -> dict[str, Any]:
202
+ """Redact sensitive fields from Mem0 tool args before logging."""
203
+ if tool_name != MEMORY_DELETE_TOOL_NAME:
204
+ return args
205
+
206
+ redacted_args = dict(args)
207
+ if "memory_ids" in redacted_args:
208
+ memory_ids = redacted_args.pop("memory_ids")
209
+ if isinstance(memory_ids, list):
210
+ redacted_args["memory_ids_count"] = len(memory_ids)
211
+ else:
212
+ redacted_args["memory_ids_count"] = 0
213
+ return redacted_args
214
+
215
+ async def _execute_default_retrieval(
216
+ self,
217
+ default_query: str | None,
218
+ state: dict,
219
+ config: dict | None,
220
+ ) -> list[ToolMessage]:
221
+ """Perform a default retrieval when the LLM does not request tools.
222
+
223
+ Args:
224
+ default_query: Latest human utterance content or ``None`` if unavailable.
225
+ state: LangGraph state with message history and metadata.
226
+ config: Optional runnable configuration forwarded to the tool.
227
+
228
+ Returns:
229
+ Single-item list containing the resulting `ToolMessage`.
230
+ """
231
+ args = self._build_default_mem0_args(default_query)
232
+ tool_message = await self._execute_mem0_call(MEMORY_SEARCH_TOOL_NAME, args, state, config)
233
+ return [tool_message]
234
+
235
+ def _build_default_mem0_args(self, query: str | None) -> dict[str, Any]:
236
+ """Create safe default arguments for the Mem0 search tool.
237
+
238
+ Args:
239
+ query: Latest human utterance used to derive the search query.
240
+
241
+ Returns:
242
+ Dictionary of keyword arguments passed to the Mem0 search tool.
243
+ """
244
+ if query:
245
+ trimmed_query = query[:128]
246
+ else:
247
+ trimmed_query = None
248
+
249
+ return {"query": trimmed_query, "limit": 10}
250
+
251
+ async def _execute_mem0_call(
252
+ self,
253
+ tool_name: str,
254
+ args: dict[str, Any],
255
+ state: dict,
256
+ config: dict | None,
257
+ ) -> ToolMessage:
258
+ """Execute a single Mem0 tool call with metadata resolution.
259
+
260
+ Args:
261
+ tool_name: Name of the memory tool to invoke.
262
+ args: Base arguments supplied by the LLM or synthesized defaults.
263
+ state: LangGraph state that may include additional metadata.
264
+ config: Optional runnable configuration forwarded to the tool.
265
+
266
+ Returns:
267
+ `ToolMessage` containing raw tool output or an error description.
268
+ """
269
+ args_with_metadata = self._merge_metadata(args, state, tool_name)
270
+ tool_config = self._create_tool_config(config, state, tool_name=tool_name)
271
+ try:
272
+ mem0_tool = self._get_tool_by_name(tool_name)
273
+ result = await mem0_tool.ainvoke(args_with_metadata, config=tool_config)
274
+ content = str(result)
275
+ except Exception as exc:
276
+ content = f"Error executing memory tool '{tool_name}': {exc}"
277
+
278
+ return ToolMessage(content=content, tool_call_id=args.get("id", ""))
279
+
280
+ def _merge_metadata(self, args: dict[str, Any], state: dict, tool_name: str) -> dict[str, Any]:
281
+ """Merge resolved metadata into tool arguments.
282
+
283
+ Args:
284
+ args: Tool arguments that may already include metadata.
285
+ state: LangGraph state providing globally resolved metadata values.
286
+ tool_name: Name of the tool requesting metadata (used to resolve tool-specific metadata).
287
+
288
+ Returns:
289
+ Copy of ``args`` containing merged metadata entries.
290
+ """
291
+ args_with_metadata = dict(args)
292
+ effective_metadata = self._resolve_effective_metadata(state, tool_name)
293
+ if not effective_metadata:
294
+ return args_with_metadata
295
+
296
+ existing_metadata = args_with_metadata.get("metadata")
297
+ if isinstance(existing_metadata, dict):
298
+ merged_metadata = {**effective_metadata, **existing_metadata}
299
+ else:
300
+ merged_metadata = effective_metadata
301
+
302
+ args_with_metadata["metadata"] = merged_metadata
303
+ return args_with_metadata
304
+
305
+ def _resolve_effective_metadata(self, state: dict, tool_name: str) -> dict[str, Any] | None:
306
+ """Resolve metadata for the Mem0 tool, swallowing resolution errors.
307
+
308
+ Args:
309
+ state: LangGraph state whose ``metadata`` key may include overrides.
310
+ tool_name: Name of the tool whose metadata resolution strategy should be used.
311
+
312
+ Returns:
313
+ Resolved metadata dictionary or ``None`` if not available.
314
+ """
315
+ raw_metadata = state.get("metadata")
316
+ if not isinstance(raw_metadata, dict):
317
+ return None
318
+
319
+ try:
320
+ return self._resolve_tool_metadata(tool_name, raw_metadata)
321
+ except Exception:
322
+ return None
323
+
324
+ def _extract_last_human_query(self, messages: list) -> str | None:
325
+ """Return the content of the most recent `HumanMessage` if available.
326
+
327
+ Args:
328
+ messages: Ordered message history produced during the graph run.
329
+
330
+ Returns:
331
+ Text content of the last human message or ``None``.
332
+ """
333
+ for message in reversed(messages):
334
+ if isinstance(message, HumanMessage):
335
+ if isinstance(message.content, str):
336
+ return message.content
337
+ return str(message.content)
338
+ return None
339
+
340
+ def _finalize_node(self, state: dict) -> dict:
341
+ """Assemble the enhanced query returned by the memory recall agent.
342
+
343
+ Collects raw memory results from all tool calls, deduplicates by memory ID,
344
+ formats the unique memories, and combines with the original user query.
345
+
346
+ Args:
347
+ state: LangGraph state containing the original conversation messages and the
348
+ tool outputs generated by `_memory_retrieval_node`.
349
+
350
+ Returns:
351
+ dict: State update with a single `AIMessage` that concatenates the original user
352
+ query and any deduplicated memory context.
353
+ """
354
+ messages = state.get("messages", [])
355
+ original_query = self._extract_last_human_query(messages) or self._fallback_query(messages)
356
+ delete_action = self._extract_delete_action(messages)
357
+ if delete_action:
358
+ action_block = self._format_memory_action(delete_action)
359
+ return {"messages": [AIMessage(content=action_block)]}
360
+
361
+ delete_error = self._extract_delete_error(messages)
362
+ if delete_error:
363
+ action_block = self._format_memory_action_error(delete_error)
364
+ return {"messages": [AIMessage(content=action_block)]}
365
+
366
+ memories = self._collect_unique_memories(messages)
367
+ tagged_memory = self._format_memories(memories)
368
+
369
+ final_text = (f"{original_query}\n\n" + tagged_memory).strip()
370
+ return {"messages": [AIMessage(content=final_text)]}
371
+
372
+ def _fallback_query(self, messages: list) -> str:
373
+ """Fallback to the last message content when no human message is present.
374
+
375
+ Args:
376
+ messages: Ordered message history produced during the graph run.
377
+
378
+ Returns:
379
+ The string representation of the last message content.
380
+ """
381
+ if not messages:
382
+ return ""
383
+ last_message = messages[-1]
384
+ content = getattr(last_message, "content", "")
385
+ return content if isinstance(content, str) else str(content)
386
+
387
+ def _collect_unique_memories(self, messages: list) -> list[dict[str, Any]]:
388
+ """Collect and deduplicate memory hits from tool messages.
389
+
390
+ Args:
391
+ messages: Ordered message history produced during the graph run.
392
+
393
+ Returns:
394
+ List of memory dictionaries with unique memory identifiers.
395
+ """
396
+ unique_memories: list[dict[str, Any]] = []
397
+ seen_ids: set[str] = set()
398
+
399
+ for message in messages:
400
+ for memory in self._extract_memories_from_message(message):
401
+ memory_id = memory.get("id")
402
+ if not memory_id or memory_id in seen_ids:
403
+ continue
404
+
405
+ seen_ids.add(memory_id)
406
+ unique_memories.append(memory)
407
+
408
+ return unique_memories
409
+
410
+ def _extract_memories_from_message(self, message: Any) -> list[dict[str, Any]]:
411
+ """Return parsed memory dictionaries contained in a tool message.
412
+
413
+ Args:
414
+ message: Message instance that may contain memory tool output.
415
+
416
+ Returns:
417
+ List of memory dictionaries or an empty list when no memories are present.
418
+ """
419
+ if not isinstance(message, ToolMessage):
420
+ return []
421
+
422
+ raw_results = self._parse_tool_message_json(message)
423
+ if isinstance(raw_results, list):
424
+ return [memory for memory in raw_results if isinstance(memory, dict)]
425
+ return []
426
+
427
+ def _parse_tool_message_json(self, message: ToolMessage) -> Any:
428
+ """Parse the JSON content of a tool message.
429
+
430
+ Args:
431
+ message: Tool message emitted by the memory search tool.
432
+
433
+ Returns:
434
+ List extracted from the tool message content or an empty list on failure.
435
+ """
436
+ try:
437
+ raw_results = json.loads(message.content)
438
+ except (json.JSONDecodeError, TypeError) as exc:
439
+ logger.warning(
440
+ "Failed to parse tool result as JSON: %s, content: %s...",
441
+ exc,
442
+ message.content[:200],
443
+ )
444
+ return None
445
+
446
+ return raw_results
447
+
448
+ def _extract_delete_action(self, messages: list) -> dict[str, Any] | None:
449
+ """Return delete action details if a delete tool message is present.
450
+
451
+ Args:
452
+ messages: Ordered message history produced during the graph run.
453
+
454
+ Returns:
455
+ Action dict or None when no delete action is detected.
456
+ """
457
+ for message in messages:
458
+ if not isinstance(message, ToolMessage):
459
+ continue
460
+ raw_payload = self._parse_tool_message_json(message)
461
+ if not isinstance(raw_payload, dict):
462
+ continue
463
+ status = raw_payload.get("status")
464
+ if status == "success" and raw_payload.get("mode"):
465
+ return raw_payload
466
+ if status == "needs_confirmation":
467
+ return raw_payload
468
+ return None
469
+
470
+ def _format_memory_action(self, action: dict[str, Any]) -> str:
471
+ """Format a memory action block for delete results.
472
+
473
+ Args:
474
+ action: Parsed action payload from the delete tool.
475
+
476
+ Returns:
477
+ Formatted action block string.
478
+ """
479
+ status = action.get("status", "success")
480
+ summary = action.get("summary")
481
+ if status == "needs_confirmation":
482
+ summary = summary or "Do you want me to delete the related memories?"
483
+ else:
484
+ mode = action.get("mode", "unknown")
485
+ result = action.get("result")
486
+ summary = summary or f"Deleted memories (mode: {mode})."
487
+ if isinstance(result, dict):
488
+ count = result.get("count") or result.get("deleted") or result.get("total")
489
+ if count is not None:
490
+ summary = f"Deleted {count} memories (mode: {mode})."
491
+ return "\n".join(
492
+ [
493
+ "<MEMORY_ACTION>",
494
+ "action=delete",
495
+ f"status={status}",
496
+ f"summary={summary}",
497
+ "</MEMORY_ACTION>",
498
+ ]
499
+ )
500
+
501
+ def _extract_delete_error(self, messages: list) -> str | None:
502
+ """Return delete error summary if delete tool failed.
503
+
504
+ Args:
505
+ messages: Ordered message history produced during the graph run.
506
+
507
+ Returns:
508
+ Error summary string or None when no delete error is detected.
509
+ """
510
+ for message in messages:
511
+ if not isinstance(message, ToolMessage):
512
+ continue
513
+ content = message.content if isinstance(message.content, str) else str(message.content)
514
+ if MEMORY_DELETE_TOOL_NAME in content and "Error" in content:
515
+ return content[:200]
516
+ return None
517
+
518
+ def _format_memory_action_error(self, error_summary: str) -> str:
519
+ """Format a memory action block for delete errors."""
520
+ safe_summary = error_summary.replace("\n", " ").strip()
521
+ return "\n".join(
522
+ [
523
+ "<MEMORY_ACTION>",
524
+ "action=delete",
525
+ "status=error",
526
+ f"summary={safe_summary}",
527
+ "</MEMORY_ACTION>",
528
+ ]
529
+ )
530
+
531
+ def _get_tool_by_name(self, tool_name: str) -> Any:
532
+ """Return the resolved tool instance by name."""
533
+ for tool in self.resolved_tools:
534
+ if tool.name == tool_name:
535
+ return tool
536
+ raise ValueError(f"Tool '{tool_name}' not found in resolved tools.")
537
+
538
+ async def _preprocess_delete_intent(
539
+ self,
540
+ query: str | None,
541
+ state: dict,
542
+ config: dict | None,
543
+ ) -> dict[str, Any]:
544
+ """Run a pre-processing intent check for delete requests.
545
+
546
+ Args:
547
+ query: Latest user query.
548
+ state: LangGraph state containing metadata for the request.
549
+ config: Optional runnable configuration forwarded to the model.
550
+
551
+ Returns:
552
+ Normalized intent payload with intent/confidence/reason keys.
553
+ """
554
+ if not isinstance(query, str) or not query.strip():
555
+ return {"intent": "unknown", "confidence": "low", "reason": "empty_query"}
556
+
557
+ raw_response = await self._invoke_delete_intent_model(query, state, config)
558
+ return self._parse_delete_intent_payload(raw_response)
559
+
560
+ async def _invoke_delete_intent_model(
561
+ self,
562
+ query: str,
563
+ state: dict,
564
+ config: dict | None,
565
+ ) -> str:
566
+ """Invoke the configured model to classify delete intent.
567
+
568
+ Args:
569
+ query: User query to classify.
570
+ state: LangGraph state containing request metadata.
571
+ config: Optional runnable configuration forwarded to the model.
572
+
573
+ Returns:
574
+ Raw model output string, or empty string on failure.
575
+ """
576
+ instruction = self._build_delete_intent_instruction()
577
+ effective_event_emitter = state.get("event_emitter") or self.event_emitter
578
+ if self.lm_invoker is not None:
579
+ return await self._invoke_delete_intent_with_invoker(query, instruction, effective_event_emitter)
580
+
581
+ if isinstance(self.model, BaseChatModel):
582
+ return await self._invoke_delete_intent_with_chat_model(query, instruction, config)
583
+
584
+ logger.warning("Delete intent check skipped; no model configured.")
585
+ return ""
586
+
587
+ async def _invoke_delete_intent_with_invoker(
588
+ self,
589
+ query: str,
590
+ instruction: str,
591
+ event_emitter: Any,
592
+ ) -> str:
593
+ """Invoke delete intent check using an LM invoker."""
594
+ messages = convert_langchain_messages_to_gllm_messages([HumanMessage(content=query)], instruction)
595
+ restore_tools = self.resolved_tools if self.resolved_tools else None
596
+ if restore_tools is not None:
597
+ self.lm_invoker.set_tools([])
598
+ try:
599
+ lm_output = await self.lm_invoker.invoke(messages=messages, event_emitter=event_emitter)
600
+ except Exception as exc:
601
+ logger.warning("Delete intent check failed: %s", exc)
602
+ return ""
603
+ finally:
604
+ if restore_tools is not None:
605
+ self.lm_invoker.set_tools(restore_tools)
606
+
607
+ ai_message = convert_lm_output_to_langchain_message(lm_output)
608
+ return self._coerce_message_content(ai_message)
609
+
610
+ async def _invoke_delete_intent_with_chat_model(
611
+ self,
612
+ query: str,
613
+ instruction: str,
614
+ config: dict | None,
615
+ ) -> str:
616
+ """Invoke delete intent check using a LangChain chat model."""
617
+ prompt = [SystemMessage(content=instruction), HumanMessage(content=query)]
618
+ try:
619
+ ai_message = await self.model.ainvoke(prompt, config)
620
+ except Exception as exc:
621
+ logger.warning("Delete intent check failed: %s", exc)
622
+ return ""
623
+ return self._coerce_message_content(ai_message)
624
+
625
+ def _parse_delete_intent_payload(self, content: str) -> dict[str, Any]:
626
+ """Parse delete intent payload from model output."""
627
+ default_payload = {"intent": "unknown", "confidence": "low", "reason": "unparsed"}
628
+ if not isinstance(content, str) or not content.strip():
629
+ return default_payload
630
+
631
+ payload = self._extract_json_payload(content)
632
+ if not isinstance(payload, dict):
633
+ return default_payload
634
+
635
+ return self._normalize_delete_intent_payload(payload, default_payload)
636
+
637
+ def _extract_json_payload(self, content: str) -> dict[str, Any] | None:
638
+ """Extract a JSON payload from a raw string."""
639
+ raw_text = content.strip()
640
+ if raw_text.startswith("```"):
641
+ raw_text = raw_text.strip("`")
642
+ if raw_text.lower().startswith("json"):
643
+ raw_text = raw_text[4:].strip()
644
+
645
+ try:
646
+ return json.loads(raw_text)
647
+ except json.JSONDecodeError:
648
+ start = raw_text.find("{")
649
+ end = raw_text.rfind("}")
650
+ if start == -1 or end == -1 or end <= start:
651
+ return None
652
+ try:
653
+ return json.loads(raw_text[start : end + 1])
654
+ except json.JSONDecodeError:
655
+ return None
656
+
657
+ def _normalize_delete_intent_payload(
658
+ self,
659
+ payload: dict[str, Any],
660
+ default_payload: dict[str, str],
661
+ ) -> dict[str, Any]:
662
+ """Normalize payload keys and guard against invalid values."""
663
+ intent = str(payload.get("intent", "")).lower()
664
+ confidence = str(payload.get("confidence", "")).lower()
665
+ if intent not in {"delete", "retrieve", "unknown"}:
666
+ intent = "unknown"
667
+ if confidence not in {"high", "medium", "low"}:
668
+ confidence = "low"
669
+
670
+ reason = payload.get("reason")
671
+ if not isinstance(reason, str):
672
+ reason = default_payload["reason"]
673
+
674
+ return {"intent": intent, "confidence": confidence, "reason": reason}
675
+
676
+ @staticmethod
677
+ def _coerce_message_content(message: AIMessage) -> str:
678
+ """Normalize AI message content into a string."""
679
+ content = message.content
680
+ return content if isinstance(content, str) else str(content)
681
+
682
+ def _build_delete_intent_instruction(self) -> str:
683
+ """Return the system prompt for delete intent classification.
684
+
685
+ Design rationale:
686
+ - Require JSON-only output for deterministic parsing.
687
+ - Use intent labels (delete|retrieve|unknown) to avoid keyword false positives.
688
+ - Gate deletion on high confidence to keep ambiguous requests safe.
689
+
690
+ Tuning guidance:
691
+ - Add examples if delete intents are missed.
692
+ - Adjust confidence thresholds if false negatives become frequent.
693
+ """
694
+ return (
695
+ "You are a memory deletion intent checker. Determine whether the user is asking to "
696
+ "delete/forget memories stored about them. Reply with JSON only: "
697
+ '{"intent": "delete|retrieve|unknown", "confidence": "high|medium|low", '
698
+ '"reason": "short"}. '
699
+ "If unsure, respond with intent unknown and low confidence."
700
+ )
701
+
702
+ def _is_delete_intent_confirmed(self, decision: dict[str, Any] | None) -> bool:
703
+ """Return True when delete intent is confirmed by pre-processing."""
704
+ if not isinstance(decision, dict):
705
+ logger.warning("Delete intent check failed: decision is not a dict.")
706
+ return False
707
+ intent = decision.get("intent")
708
+ confidence = decision.get("confidence")
709
+ reason = decision.get("reason", "unknown")
710
+ if intent != "delete":
711
+ logger.info("Delete intent not confirmed: intent=%s reason=%s.", intent, reason)
712
+ return False
713
+ if confidence != "high":
714
+ logger.info("Delete intent not confirmed: confidence=%s reason=%s.", confidence, reason)
715
+ return False
716
+ return True
717
+
718
+ def _build_delete_confirmation_message(self, tool_call: dict[str, Any], query: str | None) -> ToolMessage:
719
+ """Return a ToolMessage asking for delete confirmation."""
720
+ summary = "Do you want me to delete the related memories?"
721
+ if isinstance(query, str) and query.strip():
722
+ trimmed = query.strip()
723
+ if len(trimmed) > 160:
724
+ trimmed = f"{trimmed[:157]}..."
725
+ summary = f"Do you want me to delete memories related to: '{trimmed}'?"
726
+ payload = {"status": "needs_confirmation", "summary": summary}
727
+ return ToolMessage(content=json.dumps(payload), tool_call_id=tool_call.get("id", ""))
728
+
729
+ def _format_memories(self, memories: list[dict[str, Any]]) -> str:
730
+ """Format memory hits using the underlying tool formatter.
731
+
732
+ Args:
733
+ memories: Deduplicated list of memory dictionaries.
734
+
735
+ Returns:
736
+ Tagged string representation of the relevant memories.
737
+ """
738
+ if not memories:
739
+ return ""
740
+ return self.resolved_tools[0].format_hits(memories, with_tag=True)
741
+
742
+ def define_graph(self, graph_builder: StateGraph) -> CompiledStateGraph:
743
+ """Define the 3-node memory recall LangGraph for this agent.
744
+
745
+ This creates a streamlined ReAct-inspired structure that reuses
746
+ `LangGraphReactAgent` helpers for robust LM invocation, token usage tracking,
747
+ error handling, and tool execution.
748
+
749
+ Args:
750
+ graph_builder: LangGraph `StateGraph` builder instance used to register nodes and
751
+ edges for compilation.
752
+
753
+ Returns:
754
+ CompiledStateGraph: The compiled memory recall graph ready for execution.
755
+ """
756
+ # Reuse parent's robust node implementations
757
+ # Simple 3-step structure for single pass: agent -> memory_retrieval -> finalize -> END
758
+ agent_node = self._create_agent_node() # Handles LM invoker + LangChain + token usage
759
+ graph_builder.add_node("agent", agent_node)
760
+ graph_builder.add_node("memory_retrieval", self._memory_retrieval_node)
761
+ graph_builder.add_node("finalize", self._finalize_node)
762
+ graph_builder.add_edge("agent", "memory_retrieval")
763
+ graph_builder.add_edge("memory_retrieval", "finalize")
764
+ graph_builder.add_edge("finalize", END)
765
+ graph_builder.set_entry_point("agent")
766
+
767
+ return graph_builder.compile(checkpointer=self.checkpointer)