aip-agents-binary 0.6.2__py3-none-any.whl → 0.6.3__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.
- aip_agents/__init__.pyi +19 -0
- aip_agents/a2a/__init__.pyi +3 -0
- aip_agents/a2a/server/__init__.pyi +4 -0
- aip_agents/a2a/server/base_executor.pyi +73 -0
- aip_agents/a2a/server/google_adk_executor.pyi +51 -0
- aip_agents/a2a/server/langflow_executor.pyi +43 -0
- aip_agents/a2a/server/langgraph_executor.pyi +47 -0
- aip_agents/a2a/types.pyi +132 -0
- aip_agents/agent/__init__.pyi +9 -0
- aip_agents/agent/base_agent.pyi +221 -0
- aip_agents/agent/base_langgraph_agent.pyi +233 -0
- aip_agents/agent/google_adk_agent.pyi +141 -0
- aip_agents/agent/google_adk_constants.pyi +3 -0
- aip_agents/agent/hitl/__init__.pyi +6 -0
- aip_agents/agent/hitl/config.pyi +15 -0
- aip_agents/agent/hitl/langgraph_hitl_mixin.pyi +42 -0
- aip_agents/agent/hitl/manager.pyi +200 -0
- aip_agents/agent/hitl/models.pyi +3 -0
- aip_agents/agent/hitl/prompt/__init__.pyi +4 -0
- aip_agents/agent/hitl/prompt/base.pyi +24 -0
- aip_agents/agent/hitl/prompt/deferred.pyi +30 -0
- aip_agents/agent/hitl/registry.pyi +101 -0
- aip_agents/agent/interface.pyi +81 -0
- aip_agents/agent/interfaces.pyi +44 -0
- aip_agents/agent/langflow_agent.pyi +133 -0
- aip_agents/agent/langgraph_memory_enhancer_agent.pyi +49 -0
- aip_agents/agent/langgraph_react_agent.pyi +170 -0
- aip_agents/agent/system_instruction_context.pyi +13 -0
- aip_agents/clients/__init__.pyi +4 -0
- aip_agents/clients/langflow/__init__.pyi +4 -0
- aip_agents/clients/langflow/client.pyi +140 -0
- aip_agents/clients/langflow/types.pyi +7 -0
- aip_agents/constants.pyi +7 -0
- aip_agents/examples/__init__.pyi +0 -0
- aip_agents/examples/compare_streaming_client.pyi +48 -0
- aip_agents/examples/compare_streaming_server.pyi +18 -0
- aip_agents/examples/demo_memory_recall.pyi +58 -0
- aip_agents/examples/hello_world_a2a_google_adk_client.pyi +9 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_agent.pyi +9 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_streaming.pyi +9 -0
- aip_agents/examples/hello_world_a2a_google_adk_server.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langchain_client.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_client_agent.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_client_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_server.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langchain_server.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langflow_client.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langflow_server.pyi +14 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_server.pyi +16 -0
- aip_agents/examples/hello_world_a2a_langgraph_client.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.pyi +2 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_server.pyi +14 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.pyi +15 -0
- aip_agents/examples/hello_world_a2a_mcp_langgraph.pyi +48 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.pyi +48 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.pyi +45 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.pyi +5 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.pyi +15 -0
- aip_agents/examples/hello_world_google_adk.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_http.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_http_stream.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse_stream.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain.pyi +5 -0
- aip_agents/examples/hello_world_langchain_lm_invoker.pyi +2 -0
- aip_agents/examples/hello_world_langchain_mcp_http.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_http_interactive.pyi +16 -0
- aip_agents/examples/hello_world_langchain_mcp_http_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_multi_server.pyi +18 -0
- aip_agents/examples/hello_world_langchain_mcp_sse.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_sse_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_stream_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_langflow_agent.pyi +35 -0
- aip_agents/examples/hello_world_langgraph.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_gl_connector_twitter.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_http.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_http_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_stream_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_model_switch_cli.pyi +30 -0
- aip_agents/examples/hello_world_multi_agent_adk.pyi +6 -0
- aip_agents/examples/hello_world_multi_agent_langchain.pyi +5 -0
- aip_agents/examples/hello_world_multi_agent_langgraph.pyi +5 -0
- aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_pii_logger.pyi +5 -0
- aip_agents/examples/hello_world_ptc.pyi +5 -0
- aip_agents/examples/hello_world_sentry.pyi +21 -0
- aip_agents/examples/hello_world_step_limits.pyi +17 -0
- aip_agents/examples/hello_world_stock_a2a_server.pyi +17 -0
- aip_agents/examples/hello_world_tool_output_client.pyi +5 -0
- aip_agents/examples/hello_world_tool_output_server.pyi +19 -0
- aip_agents/examples/hitl_demo.pyi +67 -0
- aip_agents/examples/pii_demo_langgraph_client.pyi +5 -0
- aip_agents/examples/pii_demo_langgraph_server.pyi +20 -0
- aip_agents/examples/pii_demo_multi_agent_client.pyi +5 -0
- aip_agents/examples/pii_demo_multi_agent_server.pyi +40 -0
- aip_agents/examples/todolist_planning_a2a_langchain_client.pyi +5 -0
- aip_agents/examples/todolist_planning_a2a_langgraph_server.pyi +19 -0
- aip_agents/examples/tools/__init__.pyi +9 -0
- aip_agents/examples/tools/adk_arithmetic_tools.pyi +24 -0
- aip_agents/examples/tools/adk_weather_tool.pyi +18 -0
- aip_agents/examples/tools/data_generator_tool.pyi +15 -0
- aip_agents/examples/tools/data_visualization_tool.pyi +19 -0
- aip_agents/examples/tools/image_artifact_tool.pyi +26 -0
- aip_agents/examples/tools/langchain_arithmetic_tools.pyi +17 -0
- aip_agents/examples/tools/langchain_currency_exchange_tool.pyi +20 -0
- aip_agents/examples/tools/langchain_graph_artifact_tool.pyi +25 -0
- aip_agents/examples/tools/langchain_weather_tool.pyi +19 -0
- aip_agents/examples/tools/langgraph_streaming_tool.pyi +43 -0
- aip_agents/examples/tools/mock_retrieval_tool.pyi +13 -0
- aip_agents/examples/tools/pii_demo_tools.pyi +54 -0
- aip_agents/examples/tools/random_chart_tool.pyi +20 -0
- aip_agents/examples/tools/serper_tool.pyi +16 -0
- aip_agents/examples/tools/stock_tools.pyi +36 -0
- aip_agents/examples/tools/table_generator_tool.pyi +22 -0
- aip_agents/examples/tools/time_tool.pyi +15 -0
- aip_agents/examples/tools/weather_forecast_tool.pyi +14 -0
- aip_agents/guardrails/__init__.pyi +6 -0
- aip_agents/guardrails/engines/__init__.pyi +4 -0
- aip_agents/guardrails/engines/base.pyi +61 -0
- aip_agents/guardrails/engines/nemo.pyi +46 -0
- aip_agents/guardrails/engines/phrase_matcher.pyi +48 -0
- aip_agents/guardrails/exceptions.pyi +23 -0
- aip_agents/guardrails/manager.pyi +42 -0
- aip_agents/guardrails/middleware.pyi +87 -0
- aip_agents/guardrails/schemas.pyi +43 -0
- aip_agents/guardrails/utils.pyi +19 -0
- aip_agents/mcp/__init__.pyi +0 -0
- aip_agents/mcp/client/__init__.pyi +5 -0
- aip_agents/mcp/client/base_mcp_client.pyi +148 -0
- aip_agents/mcp/client/connection_manager.pyi +51 -0
- aip_agents/mcp/client/google_adk/__init__.pyi +3 -0
- aip_agents/mcp/client/google_adk/client.pyi +75 -0
- aip_agents/mcp/client/langchain/__init__.pyi +3 -0
- aip_agents/mcp/client/langchain/client.pyi +48 -0
- aip_agents/mcp/client/persistent_session.pyi +122 -0
- aip_agents/mcp/client/session_pool.pyi +101 -0
- aip_agents/mcp/client/transports.pyi +132 -0
- aip_agents/mcp/utils/__init__.pyi +0 -0
- aip_agents/mcp/utils/config_validator.pyi +82 -0
- aip_agents/memory/__init__.pyi +5 -0
- aip_agents/memory/adapters/__init__.pyi +4 -0
- aip_agents/memory/adapters/base_adapter.pyi +150 -0
- aip_agents/memory/adapters/mem0.pyi +22 -0
- aip_agents/memory/base.pyi +60 -0
- aip_agents/memory/constants.pyi +25 -0
- aip_agents/memory/factory.pyi +24 -0
- aip_agents/memory/guidance.pyi +3 -0
- aip_agents/memory/simple_memory.pyi +23 -0
- aip_agents/middleware/__init__.pyi +5 -0
- aip_agents/middleware/base.pyi +75 -0
- aip_agents/middleware/manager.pyi +84 -0
- aip_agents/middleware/todolist.pyi +125 -0
- aip_agents/ptc/__init__.pyi +10 -0
- aip_agents/ptc/doc_gen.pyi +40 -0
- aip_agents/ptc/exceptions.pyi +22 -0
- aip_agents/ptc/executor.pyi +73 -0
- aip_agents/ptc/mcp/__init__.pyi +7 -0
- aip_agents/ptc/mcp/sandbox_bridge.pyi +47 -0
- aip_agents/ptc/mcp/templates/__init__.pyi +0 -0
- aip_agents/ptc/naming.pyi +76 -0
- aip_agents/ptc/payload.pyi +15 -0
- aip_agents/ptc/prompt_builder.pyi +55 -0
- aip_agents/ptc/ptc_helper.pyi +1 -0
- aip_agents/ptc/sandbox_bridge.pyi +25 -0
- aip_agents/ptc/template_utils.pyi +13 -0
- aip_agents/ptc/templates/__init__.pyi +0 -0
- aip_agents/sandbox/__init__.pyi +5 -0
- aip_agents/sandbox/defaults.pyi +2 -0
- aip_agents/sandbox/e2b_runtime.pyi +51 -0
- aip_agents/sandbox/template_builder.pyi +36 -0
- aip_agents/sandbox/types.pyi +14 -0
- aip_agents/sandbox/validation.pyi +20 -0
- aip_agents/schema/__init__.pyi +9 -0
- aip_agents/schema/a2a.pyi +40 -0
- aip_agents/schema/agent.pyi +65 -0
- aip_agents/schema/hitl.pyi +89 -0
- aip_agents/schema/langgraph.pyi +28 -0
- aip_agents/schema/model_id.pyi +54 -0
- aip_agents/schema/step_limit.pyi +63 -0
- aip_agents/schema/storage.pyi +21 -0
- aip_agents/sentry/__init__.pyi +3 -0
- aip_agents/sentry/sentry.pyi +48 -0
- aip_agents/storage/__init__.pyi +8 -0
- aip_agents/storage/base.pyi +58 -0
- aip_agents/storage/clients/__init__.pyi +3 -0
- aip_agents/storage/clients/minio_client.pyi +137 -0
- aip_agents/storage/config.pyi +29 -0
- aip_agents/storage/providers/__init__.pyi +5 -0
- aip_agents/storage/providers/base.pyi +88 -0
- aip_agents/storage/providers/memory.pyi +79 -0
- aip_agents/storage/providers/object_storage.pyi +98 -0
- aip_agents/tools/__init__.py +11 -2
- aip_agents/tools/__init__.pyi +11 -0
- aip_agents/tools/browser_use/__init__.pyi +14 -0
- aip_agents/tools/browser_use/action_parser.pyi +18 -0
- aip_agents/tools/browser_use/browser_use_tool.pyi +50 -0
- aip_agents/tools/browser_use/llm_config.pyi +52 -0
- aip_agents/tools/browser_use/minio_storage.pyi +109 -0
- aip_agents/tools/browser_use/schemas.pyi +32 -0
- aip_agents/tools/browser_use/session.pyi +4 -0
- aip_agents/tools/browser_use/session_errors.pyi +53 -0
- aip_agents/tools/browser_use/steel_session_recording.pyi +63 -0
- aip_agents/tools/browser_use/streaming.pyi +81 -0
- aip_agents/tools/browser_use/structured_data_parser.pyi +86 -0
- aip_agents/tools/browser_use/structured_data_recovery.pyi +43 -0
- aip_agents/tools/browser_use/types.pyi +45 -0
- aip_agents/tools/code_sandbox/__init__.pyi +3 -0
- aip_agents/tools/code_sandbox/constant.pyi +4 -0
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.pyi +102 -0
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.pyi +29 -0
- aip_agents/tools/constants.pyi +138 -0
- aip_agents/tools/date_range_tool.py +554 -0
- aip_agents/tools/date_range_tool.pyi +21 -0
- aip_agents/tools/document_loader/__init__.pyi +7 -0
- aip_agents/tools/document_loader/base_reader.pyi +75 -0
- aip_agents/tools/document_loader/docx_reader_tool.pyi +10 -0
- aip_agents/tools/document_loader/excel_reader_tool.pyi +26 -0
- aip_agents/tools/document_loader/pdf_reader_tool.pyi +11 -0
- aip_agents/tools/document_loader/pdf_splitter.pyi +18 -0
- aip_agents/tools/execute_ptc_code.pyi +90 -0
- aip_agents/tools/gl_connector/__init__.pyi +3 -0
- aip_agents/tools/gl_connector/tool.pyi +74 -0
- aip_agents/tools/gl_connector_tools.pyi +39 -0
- aip_agents/tools/memory_search/__init__.pyi +5 -0
- aip_agents/tools/memory_search/base.pyi +69 -0
- aip_agents/tools/memory_search/mem0.pyi +19 -0
- aip_agents/tools/memory_search/schema.pyi +15 -0
- aip_agents/tools/memory_search_tool.pyi +3 -0
- aip_agents/tools/time_tool.pyi +16 -0
- aip_agents/tools/tool_config_injector.pyi +26 -0
- aip_agents/tools/web_search/__init__.pyi +3 -0
- aip_agents/tools/web_search/serper_tool.pyi +19 -0
- aip_agents/types/__init__.pyi +36 -0
- aip_agents/types/a2a_events.pyi +3 -0
- aip_agents/utils/__init__.pyi +11 -0
- aip_agents/utils/a2a_connector.pyi +146 -0
- aip_agents/utils/artifact_helpers.pyi +203 -0
- aip_agents/utils/constants.pyi +10 -0
- aip_agents/utils/datetime/__init__.pyi +4 -0
- aip_agents/utils/datetime/normalization.pyi +95 -0
- aip_agents/utils/datetime/timezone.pyi +48 -0
- aip_agents/utils/env_loader.pyi +10 -0
- aip_agents/utils/event_handler_registry.pyi +23 -0
- aip_agents/utils/file_prompt_utils.pyi +21 -0
- aip_agents/utils/final_response_builder.pyi +34 -0
- aip_agents/utils/formatter_llm_client.pyi +71 -0
- aip_agents/utils/langgraph/__init__.pyi +3 -0
- aip_agents/utils/langgraph/converter.pyi +49 -0
- aip_agents/utils/langgraph/tool_managers/__init__.pyi +5 -0
- aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.pyi +35 -0
- aip_agents/utils/langgraph/tool_managers/base_tool_manager.pyi +48 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.pyi +56 -0
- aip_agents/utils/langgraph/tool_output_management.pyi +329 -0
- aip_agents/utils/logger.pyi +60 -0
- aip_agents/utils/metadata/__init__.pyi +5 -0
- aip_agents/utils/metadata/activity_metadata_helper.pyi +25 -0
- aip_agents/utils/metadata/activity_narrative/__init__.pyi +7 -0
- aip_agents/utils/metadata/activity_narrative/builder.pyi +35 -0
- aip_agents/utils/metadata/activity_narrative/constants.pyi +10 -0
- aip_agents/utils/metadata/activity_narrative/context.pyi +32 -0
- aip_agents/utils/metadata/activity_narrative/formatters.pyi +48 -0
- aip_agents/utils/metadata/activity_narrative/utils.pyi +12 -0
- aip_agents/utils/metadata/schemas/__init__.pyi +4 -0
- aip_agents/utils/metadata/schemas/activity_schema.pyi +18 -0
- aip_agents/utils/metadata/schemas/thinking_schema.pyi +20 -0
- aip_agents/utils/metadata/thinking_metadata_helper.pyi +4 -0
- aip_agents/utils/metadata_helper.pyi +117 -0
- aip_agents/utils/name_preprocessor/__init__.pyi +6 -0
- aip_agents/utils/name_preprocessor/base_name_preprocessor.pyi +52 -0
- aip_agents/utils/name_preprocessor/google_name_preprocessor.pyi +38 -0
- aip_agents/utils/name_preprocessor/name_preprocessor.pyi +41 -0
- aip_agents/utils/name_preprocessor/openai_name_preprocessor.pyi +34 -0
- aip_agents/utils/pii/__init__.pyi +5 -0
- aip_agents/utils/pii/pii_handler.pyi +96 -0
- aip_agents/utils/pii/pii_helper.pyi +78 -0
- aip_agents/utils/pii/uuid_deanonymizer_mapping.pyi +73 -0
- aip_agents/utils/reference_helper.pyi +81 -0
- aip_agents/utils/sse_chunk_transformer.pyi +166 -0
- aip_agents/utils/step_limit_manager.pyi +112 -0
- aip_agents/utils/token_usage_helper.pyi +60 -0
- {aip_agents_binary-0.6.2.dist-info → aip_agents_binary-0.6.3.dist-info}/METADATA +2 -1
- {aip_agents_binary-0.6.2.dist-info → aip_agents_binary-0.6.3.dist-info}/RECORD +305 -5
- {aip_agents_binary-0.6.2.dist-info → aip_agents_binary-0.6.3.dist-info}/WHEEL +0 -0
- {aip_agents_binary-0.6.2.dist-info → aip_agents_binary-0.6.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
"""Tool to get date ranges for common time periods.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
5
|
+
Fachriza Dian Adhiatma (fachriza.d.adhiatma@gdplabs.id)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
|
|
13
|
+
from dateutil.relativedelta import relativedelta
|
|
14
|
+
from langchain_core.tools import BaseTool
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
|
|
17
|
+
FORMAT_STRING = "%m/%d/%y %H:%M:%S"
|
|
18
|
+
|
|
19
|
+
MIN_DAYS_FOR_WEEK_SPLIT = 6 # Minimum days difference for week split (7 days inclusive = 6 days difference)
|
|
20
|
+
MONTH_NAME_ABBR_THRESHOLD = 3 # len > 3 implies full month name, else try abbreviated form
|
|
21
|
+
SUPPORTED_DATE_RANGES = [
|
|
22
|
+
"last_week",
|
|
23
|
+
"this_week",
|
|
24
|
+
"last_month",
|
|
25
|
+
"this_month",
|
|
26
|
+
"yesterday",
|
|
27
|
+
"today",
|
|
28
|
+
"last_7_days",
|
|
29
|
+
"last_30_days",
|
|
30
|
+
"this_quarter",
|
|
31
|
+
"last_quarter",
|
|
32
|
+
"this_year",
|
|
33
|
+
"last_year",
|
|
34
|
+
"[N]_days_ago",
|
|
35
|
+
"[N]_weeks_ago",
|
|
36
|
+
"[N]_months_ago",
|
|
37
|
+
"YYYY-MM-DD to YYYY-MM-DD",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DateRangeToolInput(BaseModel):
|
|
42
|
+
"""Input schema for the DateRangeTool."""
|
|
43
|
+
|
|
44
|
+
date_range: str = Field(
|
|
45
|
+
...,
|
|
46
|
+
description="""
|
|
47
|
+
Date range identifier. Supported values:
|
|
48
|
+
- Relative periods: 'last_week', 'this_week', 'last_month', 'this_month'
|
|
49
|
+
- Relative days: 'yesterday', 'today', 'last_7_days', 'last_30_days'
|
|
50
|
+
- Custom range: 'N_days_ago', 'N_weeks_ago', 'N_months_ago' (replace N with number)
|
|
51
|
+
- Quarter: 'this_quarter', 'last_quarter'
|
|
52
|
+
- Year: 'this_year', 'last_year'
|
|
53
|
+
- Natural forms: 'January 2025', 'January-March 2025', 'Q1 2025'
|
|
54
|
+
- Explicit date range: 'YYYY-MM-DD to YYYY-MM-DD' (e.g., '2025-08-01 to 2025-08-07')
|
|
55
|
+
|
|
56
|
+
Note: For queries like 'first week of August 2025', convert to explicit format: '2025-08-01 to 2025-08-07'
|
|
57
|
+
""",
|
|
58
|
+
)
|
|
59
|
+
format: str = Field(
|
|
60
|
+
default=FORMAT_STRING,
|
|
61
|
+
description="Optional datetime format string. Default: '%m/%d/%y'",
|
|
62
|
+
)
|
|
63
|
+
split_weeks: bool = Field(
|
|
64
|
+
default=False,
|
|
65
|
+
description=(
|
|
66
|
+
"If True and the resulting period spans more than one week, include a 'weeks' array of Sunday–Saturday"
|
|
67
|
+
" splits that fit within the requested period (not recent fixed weeks)."
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class DateRangeTool(BaseTool):
|
|
73
|
+
"""Tool to get date ranges for common time periods."""
|
|
74
|
+
|
|
75
|
+
name: str = "date_range_tool"
|
|
76
|
+
description: str = """
|
|
77
|
+
Useful for getting date ranges for various time periods.
|
|
78
|
+
Supports relative dates, custom ranges, and standard periods.
|
|
79
|
+
Returns start and end dates for the specified period.
|
|
80
|
+
"""
|
|
81
|
+
args_schema: type[BaseModel] = DateRangeToolInput
|
|
82
|
+
|
|
83
|
+
def _parse_custom_range(self, date_range: str) -> tuple[datetime, datetime]:
|
|
84
|
+
"""Parse custom range patterns like '3_days_ago', '2_weeks_ago', etc.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
date_range (str): A string representing the custom date range in the format '{number}_{unit}_ago',
|
|
88
|
+
where {number} is an integer and {unit} is one of 'days', 'weeks', or 'months'.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
tuple[datetime, datetime]: A tuple containing the start date and end date corresponding to the custom range.
|
|
92
|
+
"""
|
|
93
|
+
pattern = r"(\d+)_(days|weeks|months)_ago"
|
|
94
|
+
match = re.match(pattern, date_range)
|
|
95
|
+
start_date = None
|
|
96
|
+
end_date = None
|
|
97
|
+
if not match:
|
|
98
|
+
raise ValueError(f"Invalid custom range format: {date_range}")
|
|
99
|
+
|
|
100
|
+
number, unit = int(match.group(1)), match.group(2)
|
|
101
|
+
today = datetime.now()
|
|
102
|
+
|
|
103
|
+
if unit == "days":
|
|
104
|
+
start_date = today - timedelta(days=number)
|
|
105
|
+
end_date = today
|
|
106
|
+
elif unit == "weeks":
|
|
107
|
+
start_date = today - timedelta(weeks=number)
|
|
108
|
+
end_date = today
|
|
109
|
+
elif unit == "months":
|
|
110
|
+
start_date = today - relativedelta(months=number)
|
|
111
|
+
end_date = today
|
|
112
|
+
|
|
113
|
+
return start_date, end_date
|
|
114
|
+
|
|
115
|
+
def _parse_month(self, month_str: str) -> int:
|
|
116
|
+
"""Parse month name (full or abbreviated) to month number (1-12).
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
month_str (int): The month name string to parse.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
int: The month number (1-12).
|
|
123
|
+
"""
|
|
124
|
+
fmt = "%B" if len(month_str) > MONTH_NAME_ABBR_THRESHOLD else "%b"
|
|
125
|
+
try:
|
|
126
|
+
return datetime.strptime(month_str, fmt).month
|
|
127
|
+
except ValueError:
|
|
128
|
+
# Fallback to abbreviated format
|
|
129
|
+
return datetime.strptime(month_str, "%b").month
|
|
130
|
+
|
|
131
|
+
def _parse_explicit_range(self, dr: str) -> tuple[datetime, datetime] | None:
|
|
132
|
+
"""Parse explicit date range: YYYY-MM-DD to YYYY-MM-DD.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
dr (str): The date range string to parse.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
tuple[datetime, datetime] | None: A tuple of (start_date, end_date) if matched, otherwise None.
|
|
139
|
+
"""
|
|
140
|
+
m_explicit = re.match(r"^(\d{4}-\d{2}-\d{2})\s+to\s+(\d{4}-\d{2}-\d{2})$", dr, flags=re.IGNORECASE)
|
|
141
|
+
if m_explicit:
|
|
142
|
+
try:
|
|
143
|
+
start = datetime.strptime(m_explicit.group(1), "%Y-%m-%d")
|
|
144
|
+
end = datetime.strptime(m_explicit.group(2), "%Y-%m-%d")
|
|
145
|
+
if start > end:
|
|
146
|
+
raise ValueError("Start date must be before or equal to end date")
|
|
147
|
+
return start, end
|
|
148
|
+
except ValueError:
|
|
149
|
+
return None
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def _parse_quarter_with_year(self, dr: str) -> tuple[datetime, datetime] | None:
|
|
153
|
+
"""Parse quarter with year: Q1 2025.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
dr (str): The date range string to parse.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
tuple[datetime, datetime] | None: A tuple of (start_date, end_date) if matched, otherwise None.
|
|
160
|
+
"""
|
|
161
|
+
m_q = re.match(r"^Q([1-4])\s+(\d{4})$", dr, flags=re.IGNORECASE)
|
|
162
|
+
if m_q:
|
|
163
|
+
q = int(m_q.group(1))
|
|
164
|
+
year = int(m_q.group(2))
|
|
165
|
+
start_month = (q - 1) * 3 + 1
|
|
166
|
+
start = datetime(year, start_month, 1)
|
|
167
|
+
end = start + relativedelta(months=3, days=-1)
|
|
168
|
+
return start, end
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
def _parse_month_range_with_year(self, dr: str) -> tuple[datetime, datetime] | None:
|
|
172
|
+
"""Parse month range within a year: January-March 2025.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
dr (str): The date range string to parse.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
tuple[datetime, datetime] | None: A tuple of (start_date, end_date) if matched, otherwise None.
|
|
179
|
+
"""
|
|
180
|
+
m_range = re.match(r"^([A-Za-z]{3,9})\s*-\s*([A-Za-z]{3,9})\s+(\d{4})$", dr)
|
|
181
|
+
if m_range:
|
|
182
|
+
m1, m2, year_s = m_range.groups()
|
|
183
|
+
year = int(year_s)
|
|
184
|
+
try:
|
|
185
|
+
start_month = self._parse_month(m1)
|
|
186
|
+
end_month = self._parse_month(m2)
|
|
187
|
+
start = datetime(year, start_month, 1)
|
|
188
|
+
end = datetime(year, end_month, 1) + relativedelta(months=1, days=-1)
|
|
189
|
+
return start, end
|
|
190
|
+
except ValueError:
|
|
191
|
+
return None
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
def _parse_single_month_with_year(self, dr: str) -> tuple[datetime, datetime] | None:
|
|
195
|
+
"""Parse single month with year: January 2025.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
dr (str): The date range string to parse.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
tuple[datetime, datetime] | None: A tuple of (start_date, end_date) if matched, otherwise None.
|
|
202
|
+
"""
|
|
203
|
+
m_single = re.match(r"^([A-Za-z]{3,9})\s+(\d{4})$", dr)
|
|
204
|
+
if m_single:
|
|
205
|
+
mname, year_s = m_single.groups()
|
|
206
|
+
year = int(year_s)
|
|
207
|
+
try:
|
|
208
|
+
month = self._parse_month(mname)
|
|
209
|
+
start = datetime(year, month, 1)
|
|
210
|
+
end = start + relativedelta(months=1, days=-1)
|
|
211
|
+
return start, end
|
|
212
|
+
except ValueError:
|
|
213
|
+
return None
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
def _parse_month_or_range(self, date_range: str) -> tuple[datetime, datetime] | None:
|
|
217
|
+
"""Parse inputs like 'January 2025', 'Jan 2025', 'January-March 2025', 'Jan-Mar 2025', 'Q1 2025'.
|
|
218
|
+
|
|
219
|
+
Also parses explicit date ranges like '2025-08-01 to 2025-08-07'.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
date_range (str): The date range string to parse.
|
|
223
|
+
Can be natural month/quarter forms or explicit YYYY-MM-DD format.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
tuple[datetime, datetime] | None: A tuple of (start_date, end_date) if matched, otherwise None.
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
ValueError: If start date is after end date (caught internally and returns None).
|
|
230
|
+
|
|
231
|
+
Examples:
|
|
232
|
+
>>> self._parse_month_or_range("January 2025")
|
|
233
|
+
(datetime(2025, 1, 1), datetime(2025, 1, 31))
|
|
234
|
+
>>> self._parse_month_or_range("2025-08-01 to 2025-08-07")
|
|
235
|
+
(datetime(2025, 8, 1), datetime(2025, 8, 7))
|
|
236
|
+
"""
|
|
237
|
+
dr = date_range.strip()
|
|
238
|
+
|
|
239
|
+
# Try each parser in order
|
|
240
|
+
parsers = [
|
|
241
|
+
self._parse_explicit_range,
|
|
242
|
+
self._parse_quarter_with_year,
|
|
243
|
+
self._parse_month_range_with_year,
|
|
244
|
+
self._parse_single_month_with_year,
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
for parser in parsers:
|
|
248
|
+
result = parser(dr)
|
|
249
|
+
if result:
|
|
250
|
+
return result
|
|
251
|
+
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
def _get_quarter_dates(self, today: datetime, last_quarter: bool = False) -> tuple[datetime, datetime]:
|
|
255
|
+
"""Calculate the start and end dates of the current or last quarter based on the given date.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
today (datetime): The reference date to determine the quarter.
|
|
259
|
+
last_quarter (bool, optional): If True, calculate the dates for the last quarter.
|
|
260
|
+
If False, calculate the dates for the current quarter.
|
|
261
|
+
Defaults to False.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
tuple[datetime, datetime]: A tuple containing the start and end dates of the quarter.
|
|
265
|
+
"""
|
|
266
|
+
current_quarter = (today.month - 1) // 3
|
|
267
|
+
if last_quarter:
|
|
268
|
+
if current_quarter == 0:
|
|
269
|
+
start_date = datetime(today.year - 1, 10, 1)
|
|
270
|
+
end_date = datetime(today.year - 1, 12, 31)
|
|
271
|
+
else:
|
|
272
|
+
start_date = datetime(today.year, 3 * (current_quarter - 1) + 1, 1)
|
|
273
|
+
end_date = datetime(today.year, 3 * current_quarter, 1) + relativedelta(months=1, days=-1)
|
|
274
|
+
else:
|
|
275
|
+
start_date = datetime(today.year, 3 * current_quarter + 1, 1)
|
|
276
|
+
end_date = datetime(today.year, 3 * (current_quarter + 1), 1) + relativedelta(months=1, days=-1)
|
|
277
|
+
return start_date, end_date
|
|
278
|
+
|
|
279
|
+
def _days_since_most_recent_sunday(self, date: datetime) -> int:
|
|
280
|
+
"""Return days since most recent Sunday (0 if today is Sunday).
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
date (datetime): The reference date.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
int: Days since most recent Sunday (0-6).
|
|
287
|
+
"""
|
|
288
|
+
# weekday(): Mon=0, Tue=1, ..., Sun=6
|
|
289
|
+
# We want Sun=0, Mon=1, ..., Sat=6
|
|
290
|
+
return (date.weekday() + 1) % 7
|
|
291
|
+
|
|
292
|
+
def _get_standard_period_dates(self, date_range: str, today: datetime) -> tuple[datetime, datetime]:
|
|
293
|
+
"""Calculate the start and end dates for a given standard period relative to a specified date.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
date_range (str): The standard period to calculate dates for.
|
|
297
|
+
Supported values are "last_week", "this_week", "last_month", and "this_month".
|
|
298
|
+
today (datetime): The reference date to calculate the period from.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
tuple[datetime, datetime]: A tuple containing the start and end dates of the specified period.
|
|
302
|
+
"""
|
|
303
|
+
if date_range == "last_week":
|
|
304
|
+
# Calculate the previous Saturday (end of last week)
|
|
305
|
+
days_since_sunday = self._days_since_most_recent_sunday(today)
|
|
306
|
+
last_saturday = today - timedelta(days=days_since_sunday + 1)
|
|
307
|
+
|
|
308
|
+
# Calculate the previous Sunday (start of last week)
|
|
309
|
+
last_sunday = last_saturday - timedelta(days=6)
|
|
310
|
+
|
|
311
|
+
return last_sunday, last_saturday
|
|
312
|
+
|
|
313
|
+
if date_range == "this_week":
|
|
314
|
+
# Calculate the most recent Sunday (start of this week)
|
|
315
|
+
days_since_sunday = self._days_since_most_recent_sunday(today)
|
|
316
|
+
sunday = today - timedelta(days=days_since_sunday)
|
|
317
|
+
|
|
318
|
+
# Calculate the upcoming Saturday (end of this week)
|
|
319
|
+
saturday = sunday + timedelta(days=6)
|
|
320
|
+
|
|
321
|
+
return sunday, saturday
|
|
322
|
+
|
|
323
|
+
if date_range == "last_month":
|
|
324
|
+
first_day = today.replace(day=1)
|
|
325
|
+
last_month = first_day - timedelta(days=1)
|
|
326
|
+
return last_month.replace(day=1), last_month
|
|
327
|
+
|
|
328
|
+
if date_range == "this_month":
|
|
329
|
+
start = today.replace(day=1)
|
|
330
|
+
return start, start + relativedelta(months=1, days=-1)
|
|
331
|
+
|
|
332
|
+
raise ValueError(f"Unknown standard period: {date_range}")
|
|
333
|
+
|
|
334
|
+
def _week_bounds_for(self, date: datetime) -> tuple[datetime, datetime]:
|
|
335
|
+
"""Return the Sunday-to-Saturday week containing the given date.
|
|
336
|
+
|
|
337
|
+
- Sunday is the start of week.
|
|
338
|
+
- Saturday is the end of week.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
date (datetime): The date to find the week bounds for.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
tuple[datetime, datetime]: A tuple containing the start and end dates of the week.
|
|
345
|
+
"""
|
|
346
|
+
days_since_sunday = self._days_since_most_recent_sunday(date)
|
|
347
|
+
sunday = datetime(date.year, date.month, date.day) - timedelta(days=days_since_sunday)
|
|
348
|
+
saturday = sunday + timedelta(days=6)
|
|
349
|
+
return sunday, saturday
|
|
350
|
+
|
|
351
|
+
def _build_week_splits(self, start_date: datetime, end_date: datetime, fmt: str) -> list[dict[str, str]]:
|
|
352
|
+
"""Build Sunday–Saturday week splits that intersect [start_date, end_date].
|
|
353
|
+
|
|
354
|
+
- Includes partial edge weeks if any day of the Sun–Sat week falls within the requested range.
|
|
355
|
+
- Week entries still use full Sunday–Saturday bounds for consistency.
|
|
356
|
+
- Labels weeks consecutively as week_1, week_2, ...
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
start_date (datetime): Start of requested period.
|
|
360
|
+
end_date (datetime): End of requested period.
|
|
361
|
+
fmt (str): Format string for dates.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
list[dict]: Weekly split entries with start_date, end_date, and human description.
|
|
365
|
+
"""
|
|
366
|
+
weeks: list[dict] = []
|
|
367
|
+
# Anchor on the Sunday of the week that contains start_date
|
|
368
|
+
first_week_sunday, _ = self._week_bounds_for(start_date)
|
|
369
|
+
cursor = first_week_sunday
|
|
370
|
+
idx = 1
|
|
371
|
+
while cursor <= end_date:
|
|
372
|
+
full_week_start = cursor
|
|
373
|
+
full_week_end = cursor + timedelta(days=6)
|
|
374
|
+
# Include any week that intersects the requested range
|
|
375
|
+
if not (full_week_end < start_date or full_week_start > end_date):
|
|
376
|
+
desc = f"From {full_week_start.strftime('%B %d, %Y')} to {full_week_end.strftime('%B %d, %Y')}"
|
|
377
|
+
weeks.append(
|
|
378
|
+
{
|
|
379
|
+
"period": f"week_{idx}",
|
|
380
|
+
"start_date": full_week_start.strftime(fmt),
|
|
381
|
+
"end_date": full_week_end.strftime(fmt),
|
|
382
|
+
"description": desc,
|
|
383
|
+
}
|
|
384
|
+
)
|
|
385
|
+
idx += 1
|
|
386
|
+
cursor += timedelta(days=7)
|
|
387
|
+
return weeks
|
|
388
|
+
|
|
389
|
+
def _get_relative_day_dates(self, date_range: str, today: datetime) -> tuple[datetime, datetime]:
|
|
390
|
+
"""Calculate the start and end dates for a given relative date range.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
date_range (str): The relative date range. Supported values are "yesterday", "today",
|
|
394
|
+
"last_7_days", and "last_30_days".
|
|
395
|
+
today (datetime): The reference date from which the relative date range is calculated.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
tuple[datetime, datetime]: A tuple containing the start and end dates for the specified
|
|
399
|
+
relative date range.
|
|
400
|
+
"""
|
|
401
|
+
if date_range == "yesterday":
|
|
402
|
+
yesterday = today - timedelta(days=1)
|
|
403
|
+
return yesterday, yesterday
|
|
404
|
+
|
|
405
|
+
if date_range == "today":
|
|
406
|
+
return today, today
|
|
407
|
+
|
|
408
|
+
if date_range == "last_7_days":
|
|
409
|
+
return today - timedelta(days=7), today
|
|
410
|
+
|
|
411
|
+
if date_range == "last_30_days":
|
|
412
|
+
return today - timedelta(days=30), today
|
|
413
|
+
|
|
414
|
+
raise ValueError(f"Unknown relative day range: {date_range}")
|
|
415
|
+
|
|
416
|
+
def _get_year_dates(self, date_range: str, today: datetime) -> tuple[datetime, datetime]:
|
|
417
|
+
"""Returns the start and end dates for the specified year range.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
date_range (str): The year range to calculate dates for.
|
|
421
|
+
Accepted values are "this_year" and "last_year".
|
|
422
|
+
today (datetime): The current date to base the year calculation on.
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
Tuple[datetime, datetime]: A tuple containing the start and end dates for the specified year range.
|
|
426
|
+
"""
|
|
427
|
+
if date_range == "this_year":
|
|
428
|
+
return datetime(today.year, 1, 1), datetime(today.year, 12, 31)
|
|
429
|
+
|
|
430
|
+
if date_range == "last_year":
|
|
431
|
+
return datetime(today.year - 1, 1, 1), datetime(today.year - 1, 12, 31)
|
|
432
|
+
|
|
433
|
+
raise ValueError(f"Unknown year range: {date_range}")
|
|
434
|
+
|
|
435
|
+
def _format_response(
|
|
436
|
+
self,
|
|
437
|
+
start_date: datetime,
|
|
438
|
+
end_date: datetime,
|
|
439
|
+
date_range: str,
|
|
440
|
+
format: str,
|
|
441
|
+
weeks: list[dict] | None = None,
|
|
442
|
+
) -> str:
|
|
443
|
+
"""Format the response as a JSON string with the given date range information.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
start_date (datetime): The start date of the range.
|
|
447
|
+
end_date (datetime): The end date of the range.
|
|
448
|
+
date_range (str): A string representation of the date range.
|
|
449
|
+
format (str): The format string to use for formatting the dates.
|
|
450
|
+
weeks (list[dict] | None): Optional weekly splits (Sunday–Saturday) for the period. This
|
|
451
|
+
is included when `split_weeks` is True and the overall period spans more than one week.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
str: A JSON string containing the formatted start date, end date, period, and description.
|
|
455
|
+
"""
|
|
456
|
+
if weeks:
|
|
457
|
+
payload: dict = {
|
|
458
|
+
"weeks": weeks,
|
|
459
|
+
"period": date_range,
|
|
460
|
+
"description": f"Split into {len(weeks)} weeks with Sunday-to-Saturday bounds",
|
|
461
|
+
}
|
|
462
|
+
return json.dumps(payload)
|
|
463
|
+
payload: dict = {
|
|
464
|
+
"start_date": start_date.strftime(format),
|
|
465
|
+
"end_date": end_date.strftime(format),
|
|
466
|
+
"period": date_range,
|
|
467
|
+
"description": f"From {start_date.strftime('%B %d, %Y')} to {end_date.strftime('%B %d, %Y')}",
|
|
468
|
+
}
|
|
469
|
+
return json.dumps(payload)
|
|
470
|
+
|
|
471
|
+
def _get_result_and_format(
|
|
472
|
+
self,
|
|
473
|
+
date_range: str,
|
|
474
|
+
format: str,
|
|
475
|
+
split_weeks: bool,
|
|
476
|
+
result: tuple[datetime, datetime],
|
|
477
|
+
) -> str:
|
|
478
|
+
"""Helper to format response with optional week splits.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
date_range (str): The date range string to process.
|
|
482
|
+
format (str): The format string to use for the output.
|
|
483
|
+
split_weeks (bool): If True and the resulting period spans more than one week,
|
|
484
|
+
also include a 'weeks' array with Sunday-to-Saturday splits for the recent weeks.
|
|
485
|
+
result (tuple[datetime, datetime]): A tuple containing the start and end dates of the date range.
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
str: A formatted date range string or an error message if the date range is unsupported or an error occurs.
|
|
489
|
+
"""
|
|
490
|
+
start_date, end_date = result
|
|
491
|
+
weeks = None
|
|
492
|
+
# Only build week splits when requested and range spans more than one week
|
|
493
|
+
if split_weeks and (end_date - start_date).days >= MIN_DAYS_FOR_WEEK_SPLIT:
|
|
494
|
+
weeks = self._build_week_splits(start_date, end_date, format)
|
|
495
|
+
return self._format_response(start_date, end_date, date_range, format, weeks=weeks)
|
|
496
|
+
|
|
497
|
+
def _run(self, date_range: str, format: str = FORMAT_STRING, split_weeks: bool = False) -> str:
|
|
498
|
+
"""Process a given date range string and return a formatted date range string.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
date_range (str): The date range string to process. Supported formats include:
|
|
502
|
+
- "standard_periods"
|
|
503
|
+
- "relative_days"
|
|
504
|
+
- "quarters"
|
|
505
|
+
- "years"
|
|
506
|
+
- "custom" (e.g., "10_days_ago", "2_weeks_ago", "3_months_ago")
|
|
507
|
+
format (str, optional): The format string to use for the output. Defaults to FORMAT_STRING.
|
|
508
|
+
split_weeks (bool, optional): If True and the resulting period spans more than one week,
|
|
509
|
+
also include a 'weeks' array with Sunday-to-Saturday splits for the recent weeks.
|
|
510
|
+
|
|
511
|
+
Returns:
|
|
512
|
+
str: A formatted date range string or an error message if the date range is unsupported or an error occurs.
|
|
513
|
+
"""
|
|
514
|
+
today = datetime.now()
|
|
515
|
+
|
|
516
|
+
try:
|
|
517
|
+
handlers: dict[str, Callable] = {
|
|
518
|
+
"standard_periods": lambda: self._get_standard_period_dates(date_range, today),
|
|
519
|
+
"relative_days": lambda: self._get_relative_day_dates(date_range, today),
|
|
520
|
+
# Only handle special tokens 'this_quarter' and 'last_quarter' here. Natural forms like 'Q1 2025'
|
|
521
|
+
# are parsed later by _parse_month_or_range(). For non-matching inputs, return None so we keep trying.
|
|
522
|
+
"quarters": lambda: (
|
|
523
|
+
self._get_quarter_dates(today, last_quarter=(date_range == "last_quarter"))
|
|
524
|
+
if date_range in {"this_quarter", "last_quarter"}
|
|
525
|
+
else None
|
|
526
|
+
),
|
|
527
|
+
"years": lambda: self._get_year_dates(date_range, today),
|
|
528
|
+
"custom": lambda: (
|
|
529
|
+
self._parse_custom_range(date_range)
|
|
530
|
+
if re.match(r"\d+_(days|weeks|months)_ago", date_range)
|
|
531
|
+
else None
|
|
532
|
+
),
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
for handler in handlers.values():
|
|
536
|
+
try:
|
|
537
|
+
result = handler()
|
|
538
|
+
if result:
|
|
539
|
+
return self._get_result_and_format(date_range, format, split_weeks, result)
|
|
540
|
+
except ValueError:
|
|
541
|
+
continue
|
|
542
|
+
|
|
543
|
+
# Try parsing natural month/quarter expressions
|
|
544
|
+
mr = self._parse_month_or_range(date_range)
|
|
545
|
+
if mr:
|
|
546
|
+
return self._get_result_and_format(date_range, format, split_weeks, mr)
|
|
547
|
+
|
|
548
|
+
return (
|
|
549
|
+
f"Unsupported date range: {date_range}, supported date ranges are: {SUPPORTED_DATE_RANGES} "
|
|
550
|
+
f"or natural month/quarter forms like 'January 2025', 'January-March 2025', 'Q1 2025'."
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
except Exception as e:
|
|
554
|
+
return f"Error processing date range: {str(e)}"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from collections.abc import Callable as Callable
|
|
3
|
+
from langchain_core.tools import BaseTool
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
FORMAT_STRING: str
|
|
7
|
+
MIN_DAYS_FOR_WEEK_SPLIT: int
|
|
8
|
+
MONTH_NAME_ABBR_THRESHOLD: int
|
|
9
|
+
SUPPORTED_DATE_RANGES: Incomplete
|
|
10
|
+
|
|
11
|
+
class DateRangeToolInput(BaseModel):
|
|
12
|
+
"""Input schema for the DateRangeTool."""
|
|
13
|
+
date_range: str
|
|
14
|
+
format: str
|
|
15
|
+
split_weeks: bool
|
|
16
|
+
|
|
17
|
+
class DateRangeTool(BaseTool):
|
|
18
|
+
"""Tool to get date ranges for common time periods."""
|
|
19
|
+
name: str
|
|
20
|
+
description: str
|
|
21
|
+
args_schema: type[BaseModel]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from aip_agents.tools.document_loader.base_reader import BaseDocumentReaderTool as BaseDocumentReaderTool, DocumentReaderInput as DocumentReaderInput
|
|
2
|
+
from aip_agents.tools.document_loader.docx_reader_tool import DocxReaderTool as DocxReaderTool
|
|
3
|
+
from aip_agents.tools.document_loader.excel_reader_tool import ExcelReaderTool as ExcelReaderTool
|
|
4
|
+
from aip_agents.tools.document_loader.pdf_reader_tool import PDFReaderTool as PDFReaderTool
|
|
5
|
+
from aip_agents.tools.document_loader.pdf_splitter import PDFSplitter as PDFSplitter
|
|
6
|
+
|
|
7
|
+
__all__ = ['BaseDocumentReaderTool', 'DocumentReaderInput', 'PDFReaderTool', 'DocxReaderTool', 'ExcelReaderTool', 'PDFSplitter']
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from aip_agents.utils.logger import get_logger as get_logger
|
|
4
|
+
from gllm_docproc.loader.pipeline_loader import PipelineLoader as PipelineLoaderType
|
|
5
|
+
from langchain_core.tools import BaseTool
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
logger: Incomplete
|
|
10
|
+
DOCPROC_MISSING_MESSAGE: str
|
|
11
|
+
PipelineLoaderType = Any
|
|
12
|
+
|
|
13
|
+
class _MissingDocprocLoader:
|
|
14
|
+
"""Fallback loader that errors when document processing is attempted."""
|
|
15
|
+
loaders: list[object]
|
|
16
|
+
def __init__(self) -> None: ...
|
|
17
|
+
def add_loader(self, loader: object) -> None: ...
|
|
18
|
+
def load(self, *_args: object, **_kwargs: object) -> list[dict[str, str]]: ...
|
|
19
|
+
def clear_cache(self) -> None: ...
|
|
20
|
+
|
|
21
|
+
DOCPROC_AVAILABLE: Incomplete
|
|
22
|
+
|
|
23
|
+
class BaseDocumentConfig(BaseModel):
|
|
24
|
+
"""Base tool configuration schema for document processing with batching functionality.
|
|
25
|
+
|
|
26
|
+
This configuration enables page-by-page batching to optimize memory usage when
|
|
27
|
+
processing large document files. When batching is enabled, documents are processed
|
|
28
|
+
sequentially by pages rather than loading the entire document into memory at once.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
batching (bool): Enable page-by-page batching to reduce memory usage.
|
|
32
|
+
When True, documents are processed page by page sequentially.
|
|
33
|
+
When False, maintains current behavior of loading entire document.
|
|
34
|
+
Defaults to False for backward compatibility.
|
|
35
|
+
batch_size (int): Number of pages to process in each batch.
|
|
36
|
+
Must be between 1 and 100 pages inclusive.
|
|
37
|
+
Larger batch sizes may use more memory but could be more efficient.
|
|
38
|
+
Smaller batch sizes use less memory but may have more overhead.
|
|
39
|
+
Defaults to 10 for balanced memory usage and efficiency.
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
>>> # Default configuration (no batching)
|
|
43
|
+
>>> config = BaseDocumentConfig()
|
|
44
|
+
>>> print(config.batching) # False
|
|
45
|
+
>>> print(config.batch_size) # 10
|
|
46
|
+
|
|
47
|
+
>>> # Enable batching with single page processing
|
|
48
|
+
>>> config = BaseDocumentConfig(batching=True, batch_size=1)
|
|
49
|
+
|
|
50
|
+
>>> # Enable batching with multi-page batches
|
|
51
|
+
>>> config = BaseDocumentConfig(batching=True, batch_size=3)
|
|
52
|
+
"""
|
|
53
|
+
batching: bool
|
|
54
|
+
batch_size: int
|
|
55
|
+
|
|
56
|
+
class DocumentReaderInput(BaseModel):
|
|
57
|
+
"""Input schema for the DocumentReader tool."""
|
|
58
|
+
file_path: str
|
|
59
|
+
|
|
60
|
+
class BaseDocumentReaderTool(BaseTool, ABC):
|
|
61
|
+
"""Base tool to read and extract text from document files."""
|
|
62
|
+
name: str
|
|
63
|
+
description: str
|
|
64
|
+
args_schema: type[BaseModel]
|
|
65
|
+
tool_config_schema: type[BaseModel]
|
|
66
|
+
loader: PipelineLoaderType
|
|
67
|
+
def __init__(self) -> None:
|
|
68
|
+
"""Initialize the base document reader tool."""
|
|
69
|
+
def cleanup_memory(self) -> None:
|
|
70
|
+
"""Explicitly clean up memory and force garbage collection.
|
|
71
|
+
|
|
72
|
+
This method can be called after processing to minimize memory usage.
|
|
73
|
+
While it won't reset memory to exactly 0, it will free up as much
|
|
74
|
+
memory as possible by clearing internal caches and forcing garbage collection.
|
|
75
|
+
"""
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from aip_agents.tools.document_loader.base_reader import BaseDocumentReaderTool as BaseDocumentReaderTool, DOCPROC_MISSING_MESSAGE as DOCPROC_MISSING_MESSAGE
|
|
3
|
+
from aip_agents.utils.logger import get_logger as get_logger
|
|
4
|
+
|
|
5
|
+
logger: Incomplete
|
|
6
|
+
|
|
7
|
+
class DocxReaderTool(BaseDocumentReaderTool):
|
|
8
|
+
"""Tool to read and extract text from Word documents."""
|
|
9
|
+
name: str
|
|
10
|
+
description: str
|