aip-agents-binary 0.5.20__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.
Files changed (280) hide show
  1. aip_agents/__init__.py +65 -0
  2. aip_agents/a2a/__init__.py +19 -0
  3. aip_agents/a2a/server/__init__.py +10 -0
  4. aip_agents/a2a/server/base_executor.py +1086 -0
  5. aip_agents/a2a/server/google_adk_executor.py +198 -0
  6. aip_agents/a2a/server/langflow_executor.py +180 -0
  7. aip_agents/a2a/server/langgraph_executor.py +270 -0
  8. aip_agents/a2a/types.py +232 -0
  9. aip_agents/agent/__init__.py +27 -0
  10. aip_agents/agent/base_agent.py +970 -0
  11. aip_agents/agent/base_langgraph_agent.py +2942 -0
  12. aip_agents/agent/google_adk_agent.py +926 -0
  13. aip_agents/agent/google_adk_constants.py +6 -0
  14. aip_agents/agent/hitl/__init__.py +24 -0
  15. aip_agents/agent/hitl/config.py +28 -0
  16. aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
  17. aip_agents/agent/hitl/manager.py +532 -0
  18. aip_agents/agent/hitl/models.py +18 -0
  19. aip_agents/agent/hitl/prompt/__init__.py +9 -0
  20. aip_agents/agent/hitl/prompt/base.py +42 -0
  21. aip_agents/agent/hitl/prompt/deferred.py +73 -0
  22. aip_agents/agent/hitl/registry.py +149 -0
  23. aip_agents/agent/interface.py +138 -0
  24. aip_agents/agent/interfaces.py +65 -0
  25. aip_agents/agent/langflow_agent.py +464 -0
  26. aip_agents/agent/langgraph_memory_enhancer_agent.py +433 -0
  27. aip_agents/agent/langgraph_react_agent.py +2514 -0
  28. aip_agents/agent/system_instruction_context.py +34 -0
  29. aip_agents/clients/__init__.py +10 -0
  30. aip_agents/clients/langflow/__init__.py +10 -0
  31. aip_agents/clients/langflow/client.py +477 -0
  32. aip_agents/clients/langflow/types.py +18 -0
  33. aip_agents/constants.py +23 -0
  34. aip_agents/credentials/manager.py +132 -0
  35. aip_agents/examples/__init__.py +5 -0
  36. aip_agents/examples/compare_streaming_client.py +783 -0
  37. aip_agents/examples/compare_streaming_server.py +142 -0
  38. aip_agents/examples/demo_memory_recall.py +401 -0
  39. aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
  40. aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
  41. aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
  42. aip_agents/examples/hello_world_a2a_google_adk_server.py +79 -0
  43. aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
  44. aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
  45. aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
  46. aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
  47. aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
  48. aip_agents/examples/hello_world_a2a_langchain_reference_server.py +105 -0
  49. aip_agents/examples/hello_world_a2a_langchain_server.py +79 -0
  50. aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +78 -0
  51. aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
  52. aip_agents/examples/hello_world_a2a_langflow_server.py +82 -0
  53. aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
  54. aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
  55. aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +92 -0
  56. aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
  57. aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
  58. aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
  59. aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
  60. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
  61. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
  62. aip_agents/examples/hello_world_a2a_langgraph_server.py +84 -0
  63. aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +79 -0
  64. aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +132 -0
  65. aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
  66. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
  67. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +251 -0
  68. aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
  69. aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +80 -0
  70. aip_agents/examples/hello_world_google_adk.py +41 -0
  71. aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
  72. aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
  73. aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
  74. aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
  75. aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
  76. aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
  77. aip_agents/examples/hello_world_google_adk_stream.py +44 -0
  78. aip_agents/examples/hello_world_langchain.py +28 -0
  79. aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
  80. aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
  81. aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
  82. aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
  83. aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
  84. aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
  85. aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
  86. aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
  87. aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
  88. aip_agents/examples/hello_world_langchain_stream.py +36 -0
  89. aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
  90. aip_agents/examples/hello_world_langflow_agent.py +163 -0
  91. aip_agents/examples/hello_world_langgraph.py +39 -0
  92. aip_agents/examples/hello_world_langgraph_bosa_twitter.py +41 -0
  93. aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
  94. aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
  95. aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
  96. aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
  97. aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
  98. aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
  99. aip_agents/examples/hello_world_langgraph_stream.py +43 -0
  100. aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
  101. aip_agents/examples/hello_world_model_switch_cli.py +210 -0
  102. aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
  103. aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
  104. aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
  105. aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
  106. aip_agents/examples/hello_world_pii_logger.py +21 -0
  107. aip_agents/examples/hello_world_sentry.py +133 -0
  108. aip_agents/examples/hello_world_step_limits.py +273 -0
  109. aip_agents/examples/hello_world_stock_a2a_server.py +103 -0
  110. aip_agents/examples/hello_world_tool_output_client.py +46 -0
  111. aip_agents/examples/hello_world_tool_output_server.py +114 -0
  112. aip_agents/examples/hitl_demo.py +724 -0
  113. aip_agents/examples/mcp_configs/configs.py +63 -0
  114. aip_agents/examples/mcp_servers/common.py +76 -0
  115. aip_agents/examples/mcp_servers/mcp_name.py +29 -0
  116. aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
  117. aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
  118. aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
  119. aip_agents/examples/mcp_servers/mcp_time.py +10 -0
  120. aip_agents/examples/pii_demo_langgraph_client.py +69 -0
  121. aip_agents/examples/pii_demo_langgraph_server.py +126 -0
  122. aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
  123. aip_agents/examples/pii_demo_multi_agent_server.py +247 -0
  124. aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
  125. aip_agents/examples/todolist_planning_a2a_langgraph_server.py +88 -0
  126. aip_agents/examples/tools/__init__.py +27 -0
  127. aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
  128. aip_agents/examples/tools/adk_weather_tool.py +60 -0
  129. aip_agents/examples/tools/data_generator_tool.py +103 -0
  130. aip_agents/examples/tools/data_visualization_tool.py +312 -0
  131. aip_agents/examples/tools/image_artifact_tool.py +136 -0
  132. aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
  133. aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
  134. aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
  135. aip_agents/examples/tools/langchain_weather_tool.py +48 -0
  136. aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
  137. aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
  138. aip_agents/examples/tools/pii_demo_tools.py +189 -0
  139. aip_agents/examples/tools/random_chart_tool.py +142 -0
  140. aip_agents/examples/tools/serper_tool.py +202 -0
  141. aip_agents/examples/tools/stock_tools.py +82 -0
  142. aip_agents/examples/tools/table_generator_tool.py +167 -0
  143. aip_agents/examples/tools/time_tool.py +82 -0
  144. aip_agents/examples/tools/weather_forecast_tool.py +38 -0
  145. aip_agents/executor/agent_executor.py +473 -0
  146. aip_agents/executor/base.py +48 -0
  147. aip_agents/mcp/__init__.py +1 -0
  148. aip_agents/mcp/client/__init__.py +14 -0
  149. aip_agents/mcp/client/base_mcp_client.py +369 -0
  150. aip_agents/mcp/client/connection_manager.py +193 -0
  151. aip_agents/mcp/client/google_adk/__init__.py +11 -0
  152. aip_agents/mcp/client/google_adk/client.py +381 -0
  153. aip_agents/mcp/client/langchain/__init__.py +11 -0
  154. aip_agents/mcp/client/langchain/client.py +265 -0
  155. aip_agents/mcp/client/persistent_session.py +359 -0
  156. aip_agents/mcp/client/session_pool.py +351 -0
  157. aip_agents/mcp/client/transports.py +215 -0
  158. aip_agents/mcp/utils/__init__.py +7 -0
  159. aip_agents/mcp/utils/config_validator.py +139 -0
  160. aip_agents/memory/__init__.py +14 -0
  161. aip_agents/memory/adapters/__init__.py +10 -0
  162. aip_agents/memory/adapters/base_adapter.py +717 -0
  163. aip_agents/memory/adapters/mem0.py +84 -0
  164. aip_agents/memory/base.py +84 -0
  165. aip_agents/memory/constants.py +49 -0
  166. aip_agents/memory/factory.py +86 -0
  167. aip_agents/memory/guidance.py +20 -0
  168. aip_agents/memory/simple_memory.py +47 -0
  169. aip_agents/middleware/__init__.py +17 -0
  170. aip_agents/middleware/base.py +88 -0
  171. aip_agents/middleware/manager.py +128 -0
  172. aip_agents/middleware/todolist.py +274 -0
  173. aip_agents/schema/__init__.py +69 -0
  174. aip_agents/schema/a2a.py +56 -0
  175. aip_agents/schema/agent.py +111 -0
  176. aip_agents/schema/hitl.py +157 -0
  177. aip_agents/schema/langgraph.py +37 -0
  178. aip_agents/schema/model_id.py +97 -0
  179. aip_agents/schema/step_limit.py +108 -0
  180. aip_agents/schema/storage.py +40 -0
  181. aip_agents/sentry/__init__.py +11 -0
  182. aip_agents/sentry/sentry.py +151 -0
  183. aip_agents/storage/__init__.py +41 -0
  184. aip_agents/storage/base.py +85 -0
  185. aip_agents/storage/clients/__init__.py +12 -0
  186. aip_agents/storage/clients/minio_client.py +318 -0
  187. aip_agents/storage/config.py +62 -0
  188. aip_agents/storage/providers/__init__.py +15 -0
  189. aip_agents/storage/providers/base.py +106 -0
  190. aip_agents/storage/providers/memory.py +114 -0
  191. aip_agents/storage/providers/object_storage.py +214 -0
  192. aip_agents/tools/__init__.py +33 -0
  193. aip_agents/tools/bosa_tools.py +105 -0
  194. aip_agents/tools/browser_use/__init__.py +82 -0
  195. aip_agents/tools/browser_use/action_parser.py +103 -0
  196. aip_agents/tools/browser_use/browser_use_tool.py +1112 -0
  197. aip_agents/tools/browser_use/llm_config.py +120 -0
  198. aip_agents/tools/browser_use/minio_storage.py +198 -0
  199. aip_agents/tools/browser_use/schemas.py +119 -0
  200. aip_agents/tools/browser_use/session.py +76 -0
  201. aip_agents/tools/browser_use/session_errors.py +132 -0
  202. aip_agents/tools/browser_use/steel_session_recording.py +317 -0
  203. aip_agents/tools/browser_use/streaming.py +813 -0
  204. aip_agents/tools/browser_use/structured_data_parser.py +257 -0
  205. aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
  206. aip_agents/tools/browser_use/types.py +78 -0
  207. aip_agents/tools/code_sandbox/__init__.py +26 -0
  208. aip_agents/tools/code_sandbox/constant.py +13 -0
  209. aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +257 -0
  210. aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +411 -0
  211. aip_agents/tools/constants.py +165 -0
  212. aip_agents/tools/document_loader/__init__.py +44 -0
  213. aip_agents/tools/document_loader/base_reader.py +302 -0
  214. aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
  215. aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
  216. aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
  217. aip_agents/tools/document_loader/pdf_splitter.py +169 -0
  218. aip_agents/tools/gl_connector/__init__.py +5 -0
  219. aip_agents/tools/gl_connector/tool.py +351 -0
  220. aip_agents/tools/memory_search/__init__.py +22 -0
  221. aip_agents/tools/memory_search/base.py +200 -0
  222. aip_agents/tools/memory_search/mem0.py +258 -0
  223. aip_agents/tools/memory_search/schema.py +48 -0
  224. aip_agents/tools/memory_search_tool.py +26 -0
  225. aip_agents/tools/time_tool.py +117 -0
  226. aip_agents/tools/tool_config_injector.py +300 -0
  227. aip_agents/tools/web_search/__init__.py +15 -0
  228. aip_agents/tools/web_search/serper_tool.py +187 -0
  229. aip_agents/types/__init__.py +70 -0
  230. aip_agents/types/a2a_events.py +13 -0
  231. aip_agents/utils/__init__.py +79 -0
  232. aip_agents/utils/a2a_connector.py +1757 -0
  233. aip_agents/utils/artifact_helpers.py +502 -0
  234. aip_agents/utils/constants.py +22 -0
  235. aip_agents/utils/datetime/__init__.py +34 -0
  236. aip_agents/utils/datetime/normalization.py +231 -0
  237. aip_agents/utils/datetime/timezone.py +206 -0
  238. aip_agents/utils/env_loader.py +27 -0
  239. aip_agents/utils/event_handler_registry.py +58 -0
  240. aip_agents/utils/file_prompt_utils.py +176 -0
  241. aip_agents/utils/final_response_builder.py +211 -0
  242. aip_agents/utils/formatter_llm_client.py +231 -0
  243. aip_agents/utils/langgraph/__init__.py +19 -0
  244. aip_agents/utils/langgraph/converter.py +128 -0
  245. aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
  246. aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
  247. aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
  248. aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1071 -0
  249. aip_agents/utils/langgraph/tool_output_management.py +967 -0
  250. aip_agents/utils/logger.py +195 -0
  251. aip_agents/utils/metadata/__init__.py +27 -0
  252. aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
  253. aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
  254. aip_agents/utils/metadata/activity_narrative/builder.py +817 -0
  255. aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
  256. aip_agents/utils/metadata/activity_narrative/context.py +49 -0
  257. aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
  258. aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
  259. aip_agents/utils/metadata/schemas/__init__.py +16 -0
  260. aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
  261. aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
  262. aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
  263. aip_agents/utils/metadata_helper.py +358 -0
  264. aip_agents/utils/name_preprocessor/__init__.py +17 -0
  265. aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
  266. aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
  267. aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
  268. aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
  269. aip_agents/utils/pii/__init__.py +25 -0
  270. aip_agents/utils/pii/pii_handler.py +397 -0
  271. aip_agents/utils/pii/pii_helper.py +207 -0
  272. aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
  273. aip_agents/utils/reference_helper.py +273 -0
  274. aip_agents/utils/sse_chunk_transformer.py +831 -0
  275. aip_agents/utils/step_limit_manager.py +265 -0
  276. aip_agents/utils/token_usage_helper.py +156 -0
  277. aip_agents_binary-0.5.20.dist-info/METADATA +681 -0
  278. aip_agents_binary-0.5.20.dist-info/RECORD +280 -0
  279. aip_agents_binary-0.5.20.dist-info/WHEEL +5 -0
  280. aip_agents_binary-0.5.20.dist-info/top_level.txt +1 -0
@@ -0,0 +1,258 @@
1
+ """Mem0-specific implementation of the long-term memory search tool.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from typing import Any, ClassVar
11
+
12
+ from langchain_core.runnables import RunnableConfig
13
+
14
+ from aip_agents.memory.constants import MemoryDefaults
15
+ from aip_agents.tools.memory_search.base import LongTermMemorySearchTool
16
+ from aip_agents.tools.memory_search.schema import LongTermMemorySearchInput
17
+ from aip_agents.utils.datetime import is_valid_date_string, next_day_iso
18
+ from aip_agents.utils.logger import get_logger
19
+
20
+ logger = get_logger(__name__)
21
+
22
+ MEMORY_SEARCH_TOOL_NAME = "built_in_mem0_search"
23
+
24
+
25
+ class Mem0SearchTool(LongTermMemorySearchTool):
26
+ """Mem0-specific implementation of the long-term memory search tool."""
27
+
28
+ name: str = MEMORY_SEARCH_TOOL_NAME
29
+ description: str = (
30
+ "Search or retrieve memories from long-term mem0 storage. Supports two modes:\n"
31
+ "1. SEMANTIC SEARCH: Provide 'query' to search for relevant memories by content\n"
32
+ "2. DATE-BASED RECALL: Omit 'query' to get all memories from an explicit date period\n\n"
33
+ "Time periods only support explicit dates ('start_date'/'end_date' in YYYY-MM-DD format).\n\n"
34
+ "Use for: names, preferences, past plans, conversations, or when user asks "
35
+ "'What did we discuss last week?' or 'Search for project notes'"
36
+ )
37
+ args_schema: type[LongTermMemorySearchInput] = LongTermMemorySearchInput
38
+ LOG_PREFIX: ClassVar[str] = "Mem0SearchTool"
39
+ METADATA_FILTER_BLOCKLIST: ClassVar[set[str]] = {"user_id", "memory_user_id"}
40
+
41
+ async def _arun(
42
+ self,
43
+ query: str | None = None,
44
+ config: RunnableConfig | None = None,
45
+ run_manager: Any | None = None,
46
+ **kwargs: Any,
47
+ ) -> str:
48
+ """Execute the memory search asynchronously for LangChain.
49
+
50
+ Args:
51
+ query: Semantic search query when provided.
52
+ config: Runnable configuration containing LangChain metadata.
53
+ run_manager: LangChain callbacks (unused).
54
+ **kwargs: Additional filters such as ``start_date``, ``end_date``, ``limit``, ``categories``, ``metadata``.
55
+
56
+ Returns:
57
+ str: JSON-encoded retrieval results or an error message.
58
+ """
59
+ logger.info("%s: Received config: %s", self.LOG_PREFIX, config)
60
+
61
+ start_date: str | None = kwargs.get("start_date")
62
+ end_date: str | None = kwargs.get("end_date")
63
+ limit: int | None = kwargs.get("limit")
64
+ categories: list[str] | None = kwargs.get("categories")
65
+ metadata: dict[str, Any] | None = kwargs.get("metadata")
66
+
67
+ user_id = self._resolve_user_id(metadata=metadata, config=config)
68
+
69
+ metadata_filter = None
70
+ if isinstance(metadata, dict):
71
+ metadata_filter = {k: v for k, v in metadata.items() if k not in self.METADATA_FILTER_BLOCKLIST} or None
72
+
73
+ date_filter_result = self._parse_date_filters(
74
+ start_date=start_date,
75
+ end_date=end_date,
76
+ user_id=user_id,
77
+ )
78
+
79
+ if "error" in date_filter_result:
80
+ return f"Error: {date_filter_result['error']}"
81
+
82
+ filters = self._add_additional_filters(
83
+ filters=date_filter_result["filters"],
84
+ categories=categories,
85
+ metadata=metadata_filter,
86
+ user_id=user_id,
87
+ )
88
+
89
+ time_spec = f"{start_date or 'unbounded'} to {end_date or 'unbounded'}"
90
+
91
+ raw_results = self._perform_retrieval(
92
+ query=query,
93
+ user_id=user_id,
94
+ limit=limit,
95
+ filters=filters,
96
+ time_spec=time_spec,
97
+ )
98
+
99
+ return json.dumps(raw_results)
100
+
101
+ def _parse_date_filters(
102
+ self,
103
+ *,
104
+ start_date: str | None,
105
+ end_date: str | None,
106
+ user_id: str,
107
+ ) -> dict[str, Any]:
108
+ """Build normalized date filters validated against YYYY-MM-DD strings.
109
+
110
+ Args:
111
+ start_date: Inclusive start date for recall filtering.
112
+ end_date: Inclusive end date for recall filtering.
113
+ user_id: User identifier to scope the memories.
114
+
115
+ Returns:
116
+ dict[str, Any]: Payload including ``filters`` or an ``error`` entry when validation fails.
117
+ """
118
+ filters: dict[str, Any] = {"AND": [{"user_id": user_id}]}
119
+ if start_date or end_date:
120
+ try:
121
+ date_range_filter = self._build_explicit_date_filter(start_date, end_date)
122
+ if date_range_filter:
123
+ filters["AND"].append(date_range_filter)
124
+ logger.info(
125
+ "%s: Using explicit date range [%s, %s] for user_id='%s'",
126
+ self.LOG_PREFIX,
127
+ start_date or "unbounded",
128
+ end_date or "unbounded",
129
+ user_id,
130
+ )
131
+ except ValueError as exc:
132
+ logger.warning("%s: Invalid explicit date range: %s", self.LOG_PREFIX, exc)
133
+ return {"error": str(exc), "hits": [], "count": 0}
134
+
135
+ return {"filters": filters}
136
+
137
+ def _build_explicit_date_filter(self, start_date: str | None, end_date: str | None) -> dict[str, Any] | None:
138
+ """Return the mem0-compatible date clause for explicit ranges.
139
+
140
+ Args:
141
+ start_date: Inclusive start date string.
142
+ end_date: Inclusive end date string.
143
+
144
+ Returns:
145
+ dict[str, Any] | None: Date filter clause or None when no bounds were provided.
146
+
147
+ Raises:
148
+ ValueError: If either date fails the YYYY-MM-DD validation.
149
+ """
150
+ if not start_date and not end_date:
151
+ return None
152
+
153
+ date_filter: dict[str, Any] = {}
154
+
155
+ if start_date:
156
+ if not is_valid_date_string(start_date, "%Y-%m-%d"):
157
+ raise ValueError(f"Invalid start_date format '{start_date}'. Expected YYYY-MM-DD.")
158
+ date_filter["gte"] = start_date
159
+
160
+ if end_date:
161
+ if not is_valid_date_string(end_date, "%Y-%m-%d"):
162
+ raise ValueError(f"Invalid end_date format '{end_date}'. Expected YYYY-MM-DD.")
163
+ date_filter["lt"] = next_day_iso(end_date)
164
+
165
+ return {"created_at": date_filter}
166
+
167
+ def _add_additional_filters(
168
+ self,
169
+ *,
170
+ filters: dict[str, Any],
171
+ categories: list[str] | None,
172
+ metadata: dict[str, Any] | None,
173
+ user_id: str,
174
+ ) -> dict[str, Any]:
175
+ """Augment filters with categories and metadata selections.
176
+
177
+ Args:
178
+ filters: Existing filter payload (mutated in place).
179
+ categories: Optional category list attached to the request.
180
+ metadata: Additional metadata equality filters.
181
+ user_id: User identifier for logging context.
182
+
183
+ Returns:
184
+ dict[str, Any]: Updated filters object.
185
+ """
186
+ if categories:
187
+ filters["AND"].append({"categories": {"in": categories}})
188
+ logger.info("%s: Added categories filter %s for user_id='%s'", self.LOG_PREFIX, categories, user_id)
189
+
190
+ if metadata:
191
+ filters["AND"].append({"metadata": metadata})
192
+ logger.info("%s: Added metadata filter %s for user_id='%s'", self.LOG_PREFIX, metadata, user_id)
193
+
194
+ return filters
195
+
196
+ def _perform_retrieval(
197
+ self,
198
+ *,
199
+ query: str | None,
200
+ user_id: str,
201
+ limit: int | None,
202
+ filters: dict[str, Any],
203
+ time_spec: str,
204
+ ) -> list[dict[str, Any]]:
205
+ """Execute the underlying memory retrieval call with guardrails.
206
+
207
+ Args:
208
+ query: Semantic query string or None for chronological listing.
209
+ user_id: Identifier associated with the stored memories.
210
+ limit: Requested number of memories to fetch.
211
+ filters: Filter payload produced by ``_add_additional_filters``.
212
+ time_spec: Human-readable time range string used for logging.
213
+
214
+ Returns:
215
+ list[dict[str, Any]]: Retrieved memories or empty list on failure.
216
+ """
217
+ effective_limit = max(limit or MemoryDefaults.RETRIEVAL_LIMIT, self.MINIMUM_MEMORY_RETRIEVAL)
218
+ if limit is not None and effective_limit != limit:
219
+ logger.info(
220
+ "%s: Enforced minimum limit of %s (requested: %s, using: %s)",
221
+ self.LOG_PREFIX,
222
+ self.MINIMUM_MEMORY_RETRIEVAL,
223
+ limit,
224
+ effective_limit,
225
+ )
226
+
227
+ try:
228
+ results = self.memory.retrieve(
229
+ query=query,
230
+ user_id=user_id,
231
+ limit=effective_limit,
232
+ filters=filters,
233
+ )
234
+
235
+ retrieval_mode = "semantic search" if query else "date-based recall"
236
+
237
+ logger.info(
238
+ "%s: %s complete for user_id='%s', query='%s', time_spec='%s', "
239
+ "limit=%s (effective: %s), filters=%s, results_count=%s",
240
+ self.LOG_PREFIX,
241
+ retrieval_mode.title(),
242
+ user_id,
243
+ query or "None",
244
+ time_spec,
245
+ limit,
246
+ effective_limit,
247
+ filters,
248
+ len(results),
249
+ )
250
+
251
+ return results
252
+
253
+ except Exception as exc: # noqa: BLE001
254
+ logger.error("%s: Retrieval failed for user_id='%s': %s", self.LOG_PREFIX, user_id, exc)
255
+ return []
256
+
257
+
258
+ Mem0SearchInput = LongTermMemorySearchInput
@@ -0,0 +1,48 @@
1
+ """Pydantic schemas shared by memory search tools.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class MemoryConfig(BaseModel):
13
+ """Tool configuration schema for memory operations."""
14
+
15
+ user_id: str = Field(description="User identifier for memory scoping")
16
+
17
+
18
+ class LongTermMemorySearchInput(BaseModel):
19
+ """Input schema for unified long-term memory retrieval."""
20
+
21
+ query: str | None = Field(
22
+ None,
23
+ description="Optional semantic query for searching memories. If provided, performs semantic search. "
24
+ "If omitted, performs pure date-based recall using time filters.",
25
+ )
26
+ start_date: str | None = Field(
27
+ None,
28
+ description="Explicit start date in YYYY-MM-DD format. Used with end_date for precise date ranges. "
29
+ "If omitted, recall is unbounded at the start.",
30
+ )
31
+ end_date: str | None = Field(
32
+ None,
33
+ description="Explicit end date in YYYY-MM-DD format. Used with start_date for precise date ranges. "
34
+ "If omitted, recall is unbounded at the end.",
35
+ )
36
+ limit: int | None = Field(
37
+ None,
38
+ description="Maximum number of memories to retrieve. If not specified, uses default. "
39
+ "Values below 5 are automatically increased to ensure adequate context.",
40
+ )
41
+ categories: list[str] | None = Field(
42
+ None,
43
+ description="Optional categories to filter by (uses 'in' operator).",
44
+ )
45
+ metadata: dict[str, Any] | None = Field(
46
+ None,
47
+ description="Optional metadata dict to filter by (exact key-value match).",
48
+ )
@@ -0,0 +1,26 @@
1
+ """Backward-compatible shim for the memory search tool module.
2
+
3
+ The actual implementations now live under ``aip_agents.tools.memory_search``.
4
+ Importing from this module continues to work for existing callers.
5
+
6
+ Authors:
7
+ Raymond Christopher (raymond.christopher@gdplabs.id)
8
+ """
9
+
10
+ from aip_agents.tools.memory_search import (
11
+ MEMORY_SEARCH_TOOL_NAME,
12
+ LongTermMemorySearchInput,
13
+ LongTermMemorySearchTool,
14
+ Mem0SearchInput,
15
+ Mem0SearchTool,
16
+ MemoryConfig,
17
+ )
18
+
19
+ __all__ = [
20
+ "MemoryConfig",
21
+ "LongTermMemorySearchInput",
22
+ "LongTermMemorySearchTool",
23
+ "Mem0SearchInput",
24
+ "Mem0SearchTool",
25
+ "MEMORY_SEARCH_TOOL_NAME",
26
+ ]
@@ -0,0 +1,117 @@
1
+ """Tool to get the current time.
2
+
3
+ Authors:
4
+ Saul Sayers (saul.sayers@gdplabs.id)
5
+ """
6
+
7
+ import re
8
+ from datetime import datetime, timedelta, timezone, tzinfo
9
+
10
+ from dateutil import tz
11
+ from langchain_core.tools import BaseTool
12
+ from pydantic import BaseModel, Field
13
+
14
+ FORMAT_STRING = "%m/%d/%y %H:%M:%S"
15
+ DEFAULT_TIMEZONE = "UTC+7"
16
+
17
+
18
+ class TimeToolInput(BaseModel):
19
+ """Input schema for the TimeTool."""
20
+
21
+ datetime_format: str = Field(
22
+ default=FORMAT_STRING,
23
+ description="""
24
+ Optional datetime format string. Default: '%m/%d/%y %H:%M:%S'
25
+
26
+ Common format codes:
27
+ %Y: Year with century (2024)
28
+ %m: Month as number (01-12)
29
+ %d: Day of month (01-31)
30
+ %A: Full weekday name (Wednesday)
31
+ %a: Short weekday name (Wed)
32
+ %H: Hour (00-23)
33
+ %M: Minute (00-59)
34
+ %S: Second (00-59)
35
+ %Z: Timezone name
36
+ %j: Day of year (001-366)
37
+ %W: Week number (00-53, Monday first)
38
+ %U: Week number (00-53, Sunday first)
39
+ %c: Locale's date and time
40
+ %x: Locale's date
41
+ %X: Locale's time
42
+ """,
43
+ )
44
+ timezone: str | None = Field(
45
+ default=DEFAULT_TIMEZONE,
46
+ description="""
47
+ Optional timezone identifier. Supports IANA names (e.g., 'Asia/Jakarta')
48
+ or offsets (e.g., 'UTC+7', 'UTC-04:30'). Default: 'UTC+7' (Asia/Jakarta).
49
+ Highly recommended to be filled when getting the current time to ensure
50
+ the time is in the correct timezone.
51
+ """,
52
+ )
53
+
54
+
55
+ def _parse_timezone(timezone_value: str | None) -> tzinfo | None:
56
+ """Return a tzinfo instance for a given timezone string.
57
+
58
+ Args:
59
+ timezone_value: Timezone string (e.g., "UTC+7", "UTC-5:30") or None.
60
+
61
+ Returns:
62
+ tzinfo instance or None if invalid/None input.
63
+ """
64
+ if timezone_value is None:
65
+ return None
66
+
67
+ tz_string = timezone_value.strip()
68
+ if not tz_string:
69
+ return None
70
+
71
+ offset_match = re.fullmatch(r"UTC([+-])(\d{1,2})(?::?(\d{2}))?$", tz_string.upper())
72
+ if offset_match:
73
+ sign, hours_str, minutes_str = offset_match.groups()
74
+ hours = int(hours_str)
75
+ minutes = int(minutes_str) if minutes_str else 0
76
+ MAX_HOURS_OFFSET = 12
77
+ MINUTES_OFFSETS = [0, 30, 45]
78
+ if hours > MAX_HOURS_OFFSET or minutes not in MINUTES_OFFSETS:
79
+ raise ValueError(f"Invalid timezone offset: {timezone_value}")
80
+ delta = timedelta(hours=hours, minutes=minutes)
81
+ if sign == "-":
82
+ delta = -delta
83
+ return timezone(delta)
84
+
85
+ tzinfo = tz.gettz(tz_string)
86
+ if tzinfo is not None:
87
+ return tzinfo
88
+
89
+ raise ValueError(f"Unknown timezone: {timezone_value}")
90
+
91
+
92
+ class TimeTool(BaseTool):
93
+ """Tool to get the current time."""
94
+
95
+ name: str = "time_tool"
96
+ description: str = """
97
+ Useful for getting the current time in a specified format and timezone.
98
+ Default format: '%m/%d/%y %H:%M:%S' (e.g., '05/15/24 17:30:00')
99
+
100
+ Supports timezone specification to get the current time in any timezone.
101
+ It is highly recommended to specify a timezone to ensure accurate results.
102
+ """
103
+ args_schema: type[BaseModel] = TimeToolInput
104
+
105
+ def _run(self, datetime_format: str = FORMAT_STRING, timezone: str | None = DEFAULT_TIMEZONE) -> str:
106
+ """Get current time formatted according to the specified format and timezone.
107
+
108
+ Args:
109
+ datetime_format: Format string for datetime (default: ISO format).
110
+ timezone: Timezone string (default: UTC).
111
+
112
+ Returns:
113
+ Formatted datetime string.
114
+ """
115
+ tzinfo = _parse_timezone(timezone)
116
+ current_time = datetime.now(tz=tzinfo)
117
+ return current_time.strftime(datetime_format)