aiecs 1.5.1__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 (302) hide show
  1. aiecs/__init__.py +72 -0
  2. aiecs/__main__.py +41 -0
  3. aiecs/aiecs_client.py +469 -0
  4. aiecs/application/__init__.py +10 -0
  5. aiecs/application/executors/__init__.py +10 -0
  6. aiecs/application/executors/operation_executor.py +363 -0
  7. aiecs/application/knowledge_graph/__init__.py +7 -0
  8. aiecs/application/knowledge_graph/builder/__init__.py +37 -0
  9. aiecs/application/knowledge_graph/builder/document_builder.py +375 -0
  10. aiecs/application/knowledge_graph/builder/graph_builder.py +356 -0
  11. aiecs/application/knowledge_graph/builder/schema_mapping.py +531 -0
  12. aiecs/application/knowledge_graph/builder/structured_pipeline.py +443 -0
  13. aiecs/application/knowledge_graph/builder/text_chunker.py +319 -0
  14. aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
  15. aiecs/application/knowledge_graph/extractors/base.py +100 -0
  16. aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +327 -0
  17. aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +349 -0
  18. aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +244 -0
  19. aiecs/application/knowledge_graph/fusion/__init__.py +23 -0
  20. aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +387 -0
  21. aiecs/application/knowledge_graph/fusion/entity_linker.py +343 -0
  22. aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +580 -0
  23. aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +189 -0
  24. aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
  25. aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +344 -0
  26. aiecs/application/knowledge_graph/pattern_matching/query_executor.py +378 -0
  27. aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
  28. aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +199 -0
  29. aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
  30. aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
  31. aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +347 -0
  32. aiecs/application/knowledge_graph/reasoning/inference_engine.py +504 -0
  33. aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +167 -0
  34. aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
  35. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
  36. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +630 -0
  37. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +654 -0
  38. aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +477 -0
  39. aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +390 -0
  40. aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +217 -0
  41. aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +169 -0
  42. aiecs/application/knowledge_graph/reasoning/query_planner.py +872 -0
  43. aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +554 -0
  44. aiecs/application/knowledge_graph/retrieval/__init__.py +19 -0
  45. aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +596 -0
  46. aiecs/application/knowledge_graph/search/__init__.py +59 -0
  47. aiecs/application/knowledge_graph/search/hybrid_search.py +423 -0
  48. aiecs/application/knowledge_graph/search/reranker.py +295 -0
  49. aiecs/application/knowledge_graph/search/reranker_strategies.py +553 -0
  50. aiecs/application/knowledge_graph/search/text_similarity.py +398 -0
  51. aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
  52. aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +329 -0
  53. aiecs/application/knowledge_graph/traversal/path_scorer.py +269 -0
  54. aiecs/application/knowledge_graph/validators/__init__.py +13 -0
  55. aiecs/application/knowledge_graph/validators/relation_validator.py +189 -0
  56. aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
  57. aiecs/application/knowledge_graph/visualization/graph_visualizer.py +321 -0
  58. aiecs/common/__init__.py +9 -0
  59. aiecs/common/knowledge_graph/__init__.py +17 -0
  60. aiecs/common/knowledge_graph/runnable.py +484 -0
  61. aiecs/config/__init__.py +16 -0
  62. aiecs/config/config.py +498 -0
  63. aiecs/config/graph_config.py +137 -0
  64. aiecs/config/registry.py +23 -0
  65. aiecs/core/__init__.py +46 -0
  66. aiecs/core/interface/__init__.py +34 -0
  67. aiecs/core/interface/execution_interface.py +152 -0
  68. aiecs/core/interface/storage_interface.py +171 -0
  69. aiecs/domain/__init__.py +289 -0
  70. aiecs/domain/agent/__init__.py +189 -0
  71. aiecs/domain/agent/base_agent.py +697 -0
  72. aiecs/domain/agent/exceptions.py +103 -0
  73. aiecs/domain/agent/graph_aware_mixin.py +559 -0
  74. aiecs/domain/agent/hybrid_agent.py +490 -0
  75. aiecs/domain/agent/integration/__init__.py +26 -0
  76. aiecs/domain/agent/integration/context_compressor.py +222 -0
  77. aiecs/domain/agent/integration/context_engine_adapter.py +252 -0
  78. aiecs/domain/agent/integration/retry_policy.py +219 -0
  79. aiecs/domain/agent/integration/role_config.py +213 -0
  80. aiecs/domain/agent/knowledge_aware_agent.py +646 -0
  81. aiecs/domain/agent/lifecycle.py +296 -0
  82. aiecs/domain/agent/llm_agent.py +300 -0
  83. aiecs/domain/agent/memory/__init__.py +12 -0
  84. aiecs/domain/agent/memory/conversation.py +197 -0
  85. aiecs/domain/agent/migration/__init__.py +14 -0
  86. aiecs/domain/agent/migration/conversion.py +160 -0
  87. aiecs/domain/agent/migration/legacy_wrapper.py +90 -0
  88. aiecs/domain/agent/models.py +317 -0
  89. aiecs/domain/agent/observability.py +407 -0
  90. aiecs/domain/agent/persistence.py +289 -0
  91. aiecs/domain/agent/prompts/__init__.py +29 -0
  92. aiecs/domain/agent/prompts/builder.py +161 -0
  93. aiecs/domain/agent/prompts/formatters.py +189 -0
  94. aiecs/domain/agent/prompts/template.py +255 -0
  95. aiecs/domain/agent/registry.py +260 -0
  96. aiecs/domain/agent/tool_agent.py +257 -0
  97. aiecs/domain/agent/tools/__init__.py +12 -0
  98. aiecs/domain/agent/tools/schema_generator.py +221 -0
  99. aiecs/domain/community/__init__.py +155 -0
  100. aiecs/domain/community/agent_adapter.py +477 -0
  101. aiecs/domain/community/analytics.py +481 -0
  102. aiecs/domain/community/collaborative_workflow.py +642 -0
  103. aiecs/domain/community/communication_hub.py +645 -0
  104. aiecs/domain/community/community_builder.py +320 -0
  105. aiecs/domain/community/community_integration.py +800 -0
  106. aiecs/domain/community/community_manager.py +813 -0
  107. aiecs/domain/community/decision_engine.py +879 -0
  108. aiecs/domain/community/exceptions.py +225 -0
  109. aiecs/domain/community/models/__init__.py +33 -0
  110. aiecs/domain/community/models/community_models.py +268 -0
  111. aiecs/domain/community/resource_manager.py +457 -0
  112. aiecs/domain/community/shared_context_manager.py +603 -0
  113. aiecs/domain/context/__init__.py +58 -0
  114. aiecs/domain/context/context_engine.py +989 -0
  115. aiecs/domain/context/conversation_models.py +354 -0
  116. aiecs/domain/context/graph_memory.py +467 -0
  117. aiecs/domain/execution/__init__.py +12 -0
  118. aiecs/domain/execution/model.py +57 -0
  119. aiecs/domain/knowledge_graph/__init__.py +19 -0
  120. aiecs/domain/knowledge_graph/models/__init__.py +52 -0
  121. aiecs/domain/knowledge_graph/models/entity.py +130 -0
  122. aiecs/domain/knowledge_graph/models/evidence.py +194 -0
  123. aiecs/domain/knowledge_graph/models/inference_rule.py +186 -0
  124. aiecs/domain/knowledge_graph/models/path.py +179 -0
  125. aiecs/domain/knowledge_graph/models/path_pattern.py +173 -0
  126. aiecs/domain/knowledge_graph/models/query.py +272 -0
  127. aiecs/domain/knowledge_graph/models/query_plan.py +187 -0
  128. aiecs/domain/knowledge_graph/models/relation.py +136 -0
  129. aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
  130. aiecs/domain/knowledge_graph/schema/entity_type.py +135 -0
  131. aiecs/domain/knowledge_graph/schema/graph_schema.py +271 -0
  132. aiecs/domain/knowledge_graph/schema/property_schema.py +155 -0
  133. aiecs/domain/knowledge_graph/schema/relation_type.py +171 -0
  134. aiecs/domain/knowledge_graph/schema/schema_manager.py +496 -0
  135. aiecs/domain/knowledge_graph/schema/type_enums.py +205 -0
  136. aiecs/domain/task/__init__.py +13 -0
  137. aiecs/domain/task/dsl_processor.py +613 -0
  138. aiecs/domain/task/model.py +62 -0
  139. aiecs/domain/task/task_context.py +268 -0
  140. aiecs/infrastructure/__init__.py +24 -0
  141. aiecs/infrastructure/graph_storage/__init__.py +11 -0
  142. aiecs/infrastructure/graph_storage/base.py +601 -0
  143. aiecs/infrastructure/graph_storage/batch_operations.py +449 -0
  144. aiecs/infrastructure/graph_storage/cache.py +429 -0
  145. aiecs/infrastructure/graph_storage/distributed.py +226 -0
  146. aiecs/infrastructure/graph_storage/error_handling.py +390 -0
  147. aiecs/infrastructure/graph_storage/graceful_degradation.py +306 -0
  148. aiecs/infrastructure/graph_storage/health_checks.py +378 -0
  149. aiecs/infrastructure/graph_storage/in_memory.py +514 -0
  150. aiecs/infrastructure/graph_storage/index_optimization.py +483 -0
  151. aiecs/infrastructure/graph_storage/lazy_loading.py +410 -0
  152. aiecs/infrastructure/graph_storage/metrics.py +357 -0
  153. aiecs/infrastructure/graph_storage/migration.py +413 -0
  154. aiecs/infrastructure/graph_storage/pagination.py +471 -0
  155. aiecs/infrastructure/graph_storage/performance_monitoring.py +466 -0
  156. aiecs/infrastructure/graph_storage/postgres.py +871 -0
  157. aiecs/infrastructure/graph_storage/query_optimizer.py +635 -0
  158. aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
  159. aiecs/infrastructure/graph_storage/sqlite.py +623 -0
  160. aiecs/infrastructure/graph_storage/streaming.py +495 -0
  161. aiecs/infrastructure/messaging/__init__.py +13 -0
  162. aiecs/infrastructure/messaging/celery_task_manager.py +383 -0
  163. aiecs/infrastructure/messaging/websocket_manager.py +298 -0
  164. aiecs/infrastructure/monitoring/__init__.py +34 -0
  165. aiecs/infrastructure/monitoring/executor_metrics.py +174 -0
  166. aiecs/infrastructure/monitoring/global_metrics_manager.py +213 -0
  167. aiecs/infrastructure/monitoring/structured_logger.py +48 -0
  168. aiecs/infrastructure/monitoring/tracing_manager.py +410 -0
  169. aiecs/infrastructure/persistence/__init__.py +24 -0
  170. aiecs/infrastructure/persistence/context_engine_client.py +187 -0
  171. aiecs/infrastructure/persistence/database_manager.py +333 -0
  172. aiecs/infrastructure/persistence/file_storage.py +754 -0
  173. aiecs/infrastructure/persistence/redis_client.py +220 -0
  174. aiecs/llm/__init__.py +86 -0
  175. aiecs/llm/callbacks/__init__.py +11 -0
  176. aiecs/llm/callbacks/custom_callbacks.py +264 -0
  177. aiecs/llm/client_factory.py +420 -0
  178. aiecs/llm/clients/__init__.py +33 -0
  179. aiecs/llm/clients/base_client.py +193 -0
  180. aiecs/llm/clients/googleai_client.py +181 -0
  181. aiecs/llm/clients/openai_client.py +131 -0
  182. aiecs/llm/clients/vertex_client.py +437 -0
  183. aiecs/llm/clients/xai_client.py +184 -0
  184. aiecs/llm/config/__init__.py +51 -0
  185. aiecs/llm/config/config_loader.py +275 -0
  186. aiecs/llm/config/config_validator.py +236 -0
  187. aiecs/llm/config/model_config.py +151 -0
  188. aiecs/llm/utils/__init__.py +10 -0
  189. aiecs/llm/utils/validate_config.py +91 -0
  190. aiecs/main.py +363 -0
  191. aiecs/scripts/__init__.py +3 -0
  192. aiecs/scripts/aid/VERSION_MANAGEMENT.md +97 -0
  193. aiecs/scripts/aid/__init__.py +19 -0
  194. aiecs/scripts/aid/version_manager.py +215 -0
  195. aiecs/scripts/dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md +242 -0
  196. aiecs/scripts/dependance_check/README_DEPENDENCY_CHECKER.md +310 -0
  197. aiecs/scripts/dependance_check/__init__.py +17 -0
  198. aiecs/scripts/dependance_check/dependency_checker.py +938 -0
  199. aiecs/scripts/dependance_check/dependency_fixer.py +391 -0
  200. aiecs/scripts/dependance_check/download_nlp_data.py +396 -0
  201. aiecs/scripts/dependance_check/quick_dependency_check.py +270 -0
  202. aiecs/scripts/dependance_check/setup_nlp_data.sh +217 -0
  203. aiecs/scripts/dependance_patch/__init__.py +7 -0
  204. aiecs/scripts/dependance_patch/fix_weasel/README_WEASEL_PATCH.md +126 -0
  205. aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
  206. aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.py +128 -0
  207. aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.sh +82 -0
  208. aiecs/scripts/dependance_patch/fix_weasel/patch_weasel_library.sh +188 -0
  209. aiecs/scripts/dependance_patch/fix_weasel/run_weasel_patch.sh +41 -0
  210. aiecs/scripts/tools_develop/README.md +449 -0
  211. aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
  212. aiecs/scripts/tools_develop/__init__.py +21 -0
  213. aiecs/scripts/tools_develop/check_type_annotations.py +259 -0
  214. aiecs/scripts/tools_develop/validate_tool_schemas.py +422 -0
  215. aiecs/scripts/tools_develop/verify_tools.py +356 -0
  216. aiecs/tasks/__init__.py +1 -0
  217. aiecs/tasks/worker.py +172 -0
  218. aiecs/tools/__init__.py +299 -0
  219. aiecs/tools/apisource/__init__.py +99 -0
  220. aiecs/tools/apisource/intelligence/__init__.py +19 -0
  221. aiecs/tools/apisource/intelligence/data_fusion.py +381 -0
  222. aiecs/tools/apisource/intelligence/query_analyzer.py +413 -0
  223. aiecs/tools/apisource/intelligence/search_enhancer.py +388 -0
  224. aiecs/tools/apisource/monitoring/__init__.py +9 -0
  225. aiecs/tools/apisource/monitoring/metrics.py +303 -0
  226. aiecs/tools/apisource/providers/__init__.py +115 -0
  227. aiecs/tools/apisource/providers/base.py +664 -0
  228. aiecs/tools/apisource/providers/census.py +401 -0
  229. aiecs/tools/apisource/providers/fred.py +564 -0
  230. aiecs/tools/apisource/providers/newsapi.py +412 -0
  231. aiecs/tools/apisource/providers/worldbank.py +357 -0
  232. aiecs/tools/apisource/reliability/__init__.py +12 -0
  233. aiecs/tools/apisource/reliability/error_handler.py +375 -0
  234. aiecs/tools/apisource/reliability/fallback_strategy.py +391 -0
  235. aiecs/tools/apisource/tool.py +850 -0
  236. aiecs/tools/apisource/utils/__init__.py +9 -0
  237. aiecs/tools/apisource/utils/validators.py +338 -0
  238. aiecs/tools/base_tool.py +201 -0
  239. aiecs/tools/docs/__init__.py +121 -0
  240. aiecs/tools/docs/ai_document_orchestrator.py +599 -0
  241. aiecs/tools/docs/ai_document_writer_orchestrator.py +2403 -0
  242. aiecs/tools/docs/content_insertion_tool.py +1333 -0
  243. aiecs/tools/docs/document_creator_tool.py +1317 -0
  244. aiecs/tools/docs/document_layout_tool.py +1166 -0
  245. aiecs/tools/docs/document_parser_tool.py +994 -0
  246. aiecs/tools/docs/document_writer_tool.py +1818 -0
  247. aiecs/tools/knowledge_graph/__init__.py +17 -0
  248. aiecs/tools/knowledge_graph/graph_reasoning_tool.py +734 -0
  249. aiecs/tools/knowledge_graph/graph_search_tool.py +923 -0
  250. aiecs/tools/knowledge_graph/kg_builder_tool.py +476 -0
  251. aiecs/tools/langchain_adapter.py +542 -0
  252. aiecs/tools/schema_generator.py +275 -0
  253. aiecs/tools/search_tool/__init__.py +100 -0
  254. aiecs/tools/search_tool/analyzers.py +589 -0
  255. aiecs/tools/search_tool/cache.py +260 -0
  256. aiecs/tools/search_tool/constants.py +128 -0
  257. aiecs/tools/search_tool/context.py +216 -0
  258. aiecs/tools/search_tool/core.py +749 -0
  259. aiecs/tools/search_tool/deduplicator.py +123 -0
  260. aiecs/tools/search_tool/error_handler.py +271 -0
  261. aiecs/tools/search_tool/metrics.py +371 -0
  262. aiecs/tools/search_tool/rate_limiter.py +178 -0
  263. aiecs/tools/search_tool/schemas.py +277 -0
  264. aiecs/tools/statistics/__init__.py +80 -0
  265. aiecs/tools/statistics/ai_data_analysis_orchestrator.py +643 -0
  266. aiecs/tools/statistics/ai_insight_generator_tool.py +505 -0
  267. aiecs/tools/statistics/ai_report_orchestrator_tool.py +694 -0
  268. aiecs/tools/statistics/data_loader_tool.py +564 -0
  269. aiecs/tools/statistics/data_profiler_tool.py +658 -0
  270. aiecs/tools/statistics/data_transformer_tool.py +573 -0
  271. aiecs/tools/statistics/data_visualizer_tool.py +495 -0
  272. aiecs/tools/statistics/model_trainer_tool.py +487 -0
  273. aiecs/tools/statistics/statistical_analyzer_tool.py +459 -0
  274. aiecs/tools/task_tools/__init__.py +86 -0
  275. aiecs/tools/task_tools/chart_tool.py +732 -0
  276. aiecs/tools/task_tools/classfire_tool.py +922 -0
  277. aiecs/tools/task_tools/image_tool.py +447 -0
  278. aiecs/tools/task_tools/office_tool.py +684 -0
  279. aiecs/tools/task_tools/pandas_tool.py +635 -0
  280. aiecs/tools/task_tools/report_tool.py +635 -0
  281. aiecs/tools/task_tools/research_tool.py +392 -0
  282. aiecs/tools/task_tools/scraper_tool.py +715 -0
  283. aiecs/tools/task_tools/stats_tool.py +688 -0
  284. aiecs/tools/temp_file_manager.py +130 -0
  285. aiecs/tools/tool_executor/__init__.py +37 -0
  286. aiecs/tools/tool_executor/tool_executor.py +881 -0
  287. aiecs/utils/LLM_output_structor.py +445 -0
  288. aiecs/utils/__init__.py +34 -0
  289. aiecs/utils/base_callback.py +47 -0
  290. aiecs/utils/cache_provider.py +695 -0
  291. aiecs/utils/execution_utils.py +184 -0
  292. aiecs/utils/logging.py +1 -0
  293. aiecs/utils/prompt_loader.py +14 -0
  294. aiecs/utils/token_usage_repository.py +323 -0
  295. aiecs/ws/__init__.py +0 -0
  296. aiecs/ws/socket_server.py +52 -0
  297. aiecs-1.5.1.dist-info/METADATA +608 -0
  298. aiecs-1.5.1.dist-info/RECORD +302 -0
  299. aiecs-1.5.1.dist-info/WHEEL +5 -0
  300. aiecs-1.5.1.dist-info/entry_points.txt +10 -0
  301. aiecs-1.5.1.dist-info/licenses/LICENSE +225 -0
  302. aiecs-1.5.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,371 @@
1
+ """
2
+ Enhanced Metrics Collection
3
+
4
+ This module provides comprehensive performance and quality metrics tracking
5
+ for the search tool.
6
+ """
7
+
8
+ from datetime import datetime
9
+ from typing import Any, Dict, List, Optional
10
+
11
+
12
+ class EnhancedMetrics:
13
+ """Comprehensive metrics collection for search operations"""
14
+
15
+ def __init__(self):
16
+ """Initialize enhanced metrics"""
17
+ self.metrics = {
18
+ # Basic counters
19
+ "requests": {
20
+ "total": 0,
21
+ "successful": 0,
22
+ "failed": 0,
23
+ "cached": 0,
24
+ },
25
+ # Performance metrics
26
+ "performance": {
27
+ "response_times_ms": [], # Recent 100
28
+ "avg_response_time_ms": 0,
29
+ "p50_response_time_ms": 0,
30
+ "p95_response_time_ms": 0,
31
+ "p99_response_time_ms": 0,
32
+ "slowest_query": None,
33
+ "fastest_query": None,
34
+ },
35
+ # Quality metrics
36
+ "quality": {
37
+ "avg_results_per_query": 0,
38
+ "avg_quality_score": 0,
39
+ "high_quality_results_pct": 0,
40
+ "queries_with_no_results": 0,
41
+ },
42
+ # Error analysis
43
+ "errors": {
44
+ "by_type": {},
45
+ "recent_errors": [], # Recent 10
46
+ "error_rate": 0.0,
47
+ },
48
+ # Cache efficiency
49
+ "cache": {
50
+ "hit_rate": 0.0,
51
+ "total_hits": 0,
52
+ "total_misses": 0,
53
+ "avg_age_seconds": 0,
54
+ },
55
+ # Rate limiting
56
+ "rate_limiting": {
57
+ "throttled_requests": 0,
58
+ "avg_wait_time_ms": 0,
59
+ "quota_utilization_pct": 0,
60
+ },
61
+ # Query patterns
62
+ "patterns": {
63
+ "top_query_types": {},
64
+ "top_domains_returned": {},
65
+ "avg_query_length": 0,
66
+ },
67
+ }
68
+
69
+ def record_search(
70
+ self,
71
+ query: str,
72
+ search_type: str,
73
+ results: List[Dict[str, Any]],
74
+ response_time_ms: float,
75
+ cached: bool = False,
76
+ error: Optional[Exception] = None,
77
+ ):
78
+ """
79
+ Record a search operation.
80
+
81
+ Args:
82
+ query: Search query
83
+ search_type: Type of search performed
84
+ results: Search results
85
+ response_time_ms: Response time in milliseconds
86
+ cached: Whether result was from cache
87
+ error: Error if search failed
88
+ """
89
+ # Update request counts
90
+ self.metrics["requests"]["total"] += 1
91
+
92
+ if error:
93
+ self.metrics["requests"]["failed"] += 1
94
+ self._record_error(error)
95
+ else:
96
+ self.metrics["requests"]["successful"] += 1
97
+
98
+ if cached:
99
+ self.metrics["requests"]["cached"] += 1
100
+ self.metrics["cache"]["total_hits"] += 1
101
+ else:
102
+ self.metrics["cache"]["total_misses"] += 1
103
+
104
+ # Update performance metrics
105
+ self.metrics["performance"]["response_times_ms"].append(response_time_ms)
106
+ if len(self.metrics["performance"]["response_times_ms"]) > 100:
107
+ self.metrics["performance"]["response_times_ms"].pop(0)
108
+
109
+ self._update_percentiles()
110
+
111
+ # Track slowest/fastest queries
112
+ if (
113
+ not self.metrics["performance"]["slowest_query"]
114
+ or response_time_ms > self.metrics["performance"]["slowest_query"]["time"]
115
+ ):
116
+ self.metrics["performance"]["slowest_query"] = {
117
+ "query": query,
118
+ "time": response_time_ms,
119
+ "type": search_type,
120
+ }
121
+
122
+ if (
123
+ not self.metrics["performance"]["fastest_query"]
124
+ or response_time_ms < self.metrics["performance"]["fastest_query"]["time"]
125
+ ):
126
+ self.metrics["performance"]["fastest_query"] = {
127
+ "query": query,
128
+ "time": response_time_ms,
129
+ "type": search_type,
130
+ }
131
+
132
+ # Update quality metrics
133
+ if results:
134
+ result_count = len(results)
135
+
136
+ # Calculate average quality
137
+ avg_quality = (
138
+ sum(r.get("_quality", {}).get("quality_score", 0.5) for r in results) / result_count
139
+ )
140
+
141
+ # Count high quality results
142
+ high_quality_count = sum(
143
+ 1 for r in results if r.get("_quality", {}).get("quality_score", 0) > 0.75
144
+ )
145
+
146
+ # Update running averages
147
+ total = self.metrics["requests"]["successful"]
148
+
149
+ current_avg_results = self.metrics["quality"]["avg_results_per_query"]
150
+ self.metrics["quality"]["avg_results_per_query"] = (
151
+ current_avg_results * (total - 1) + result_count
152
+ ) / total
153
+
154
+ current_avg_quality = self.metrics["quality"]["avg_quality_score"]
155
+ self.metrics["quality"]["avg_quality_score"] = (
156
+ current_avg_quality * (total - 1) + avg_quality
157
+ ) / total
158
+
159
+ current_high_pct = self.metrics["quality"]["high_quality_results_pct"]
160
+ high_pct = high_quality_count / result_count
161
+ self.metrics["quality"]["high_quality_results_pct"] = (
162
+ current_high_pct * (total - 1) + high_pct
163
+ ) / total
164
+ else:
165
+ self.metrics["quality"]["queries_with_no_results"] += 1
166
+
167
+ # Update query patterns
168
+ query_type = self._detect_query_type(query)
169
+ self.metrics["patterns"]["top_query_types"][query_type] = (
170
+ self.metrics["patterns"]["top_query_types"].get(query_type, 0) + 1
171
+ )
172
+
173
+ # Track returned domains
174
+ for result in results:
175
+ domain = result.get("displayLink", "unknown")
176
+ self.metrics["patterns"]["top_domains_returned"][domain] = (
177
+ self.metrics["patterns"]["top_domains_returned"].get(domain, 0) + 1
178
+ )
179
+
180
+ # Update average query length
181
+ total = self.metrics["requests"]["total"]
182
+ current_avg_len = self.metrics["patterns"]["avg_query_length"]
183
+ self.metrics["patterns"]["avg_query_length"] = (
184
+ current_avg_len * (total - 1) + len(query.split())
185
+ ) / total
186
+
187
+ # Update cache hit rate
188
+ total_cache_requests = (
189
+ self.metrics["cache"]["total_hits"] + self.metrics["cache"]["total_misses"]
190
+ )
191
+ if total_cache_requests > 0:
192
+ self.metrics["cache"]["hit_rate"] = (
193
+ self.metrics["cache"]["total_hits"] / total_cache_requests
194
+ )
195
+
196
+ def _update_percentiles(self):
197
+ """Update response time percentiles"""
198
+ times = sorted(self.metrics["performance"]["response_times_ms"])
199
+ if not times:
200
+ return
201
+
202
+ self.metrics["performance"]["avg_response_time_ms"] = sum(times) / len(times)
203
+ self.metrics["performance"]["p50_response_time_ms"] = times[len(times) // 2]
204
+ self.metrics["performance"]["p95_response_time_ms"] = times[int(len(times) * 0.95)]
205
+ self.metrics["performance"]["p99_response_time_ms"] = times[int(len(times) * 0.99)]
206
+
207
+ def _record_error(self, error: Exception):
208
+ """Record an error"""
209
+ error_type = type(error).__name__
210
+
211
+ self.metrics["errors"]["by_type"][error_type] = (
212
+ self.metrics["errors"]["by_type"].get(error_type, 0) + 1
213
+ )
214
+
215
+ self.metrics["errors"]["recent_errors"].append(
216
+ {
217
+ "type": error_type,
218
+ "message": str(error),
219
+ "timestamp": datetime.utcnow().isoformat(),
220
+ }
221
+ )
222
+
223
+ if len(self.metrics["errors"]["recent_errors"]) > 10:
224
+ self.metrics["errors"]["recent_errors"].pop(0)
225
+
226
+ # Update error rate
227
+ total = self.metrics["requests"]["total"]
228
+ failed = self.metrics["requests"]["failed"]
229
+ self.metrics["errors"]["error_rate"] = failed / total if total > 0 else 0
230
+
231
+ def _detect_query_type(self, query: str) -> str:
232
+ """Detect query type from query text"""
233
+ query_lower = query.lower()
234
+
235
+ if any(kw in query_lower for kw in ["how to", "tutorial", "guide"]):
236
+ return "how_to"
237
+ elif any(kw in query_lower for kw in ["what is", "define", "meaning"]):
238
+ return "definition"
239
+ elif any(kw in query_lower for kw in ["vs", "versus", "compare"]):
240
+ return "comparison"
241
+ elif any(kw in query_lower for kw in ["latest", "news", "recent"]):
242
+ return "news"
243
+ else:
244
+ return "general"
245
+
246
+ def get_health_score(self) -> float:
247
+ """
248
+ Calculate system health score (0-1).
249
+
250
+ Returns:
251
+ Health score based on success rate, performance, quality, and cache efficiency
252
+ """
253
+ total = self.metrics["requests"]["total"]
254
+ if total == 0:
255
+ return 1.0
256
+
257
+ # Success rate score (40%)
258
+ success_rate = self.metrics["requests"]["successful"] / total
259
+ success_score = success_rate * 0.4
260
+
261
+ # Performance score (25%)
262
+ avg_time = self.metrics["performance"]["avg_response_time_ms"]
263
+ # < 500ms excellent, > 3000ms poor
264
+ performance_score = max(0, min(1, (3000 - avg_time) / 2500)) * 0.25
265
+
266
+ # Quality score (25%)
267
+ quality_score = self.metrics["quality"]["avg_quality_score"] * 0.25
268
+
269
+ # Cache efficiency score (10%)
270
+ cache_score = self.metrics["cache"]["hit_rate"] * 0.1
271
+
272
+ return success_score + performance_score + quality_score + cache_score
273
+
274
+ def generate_report(self) -> str:
275
+ """
276
+ Generate human-readable metrics report.
277
+
278
+ Returns:
279
+ Formatted report string
280
+ """
281
+ health = self.get_health_score()
282
+ total = self.metrics["requests"]["total"]
283
+
284
+ if total == 0:
285
+ return "No search operations recorded yet."
286
+
287
+ health_indicator = "✅" if health > 0.8 else "⚠️" if health > 0.6 else "❌"
288
+
289
+ # Format top error types
290
+ top_errors = sorted(
291
+ self.metrics["errors"]["by_type"].items(),
292
+ key=lambda x: x[1],
293
+ reverse=True,
294
+ )[:3]
295
+ error_str = ", ".join(f"{k}({v})" for k, v in top_errors) if top_errors else "None"
296
+
297
+ # Format top query types
298
+ top_types = sorted(
299
+ self.metrics["patterns"]["top_query_types"].items(),
300
+ key=lambda x: x[1],
301
+ reverse=True,
302
+ )[:3]
303
+ types_str = ", ".join(f"{k}({v})" for k, v in top_types)
304
+
305
+ # Format top domains
306
+ top_domains = sorted(
307
+ self.metrics["patterns"]["top_domains_returned"].items(),
308
+ key=lambda x: x[1],
309
+ reverse=True,
310
+ )[:5]
311
+ domains_str = ", ".join(f"{k}({v})" for k, v in top_domains)
312
+
313
+ # Extract slowest query info
314
+ slowest_query = self.metrics["performance"]["slowest_query"]
315
+ slowest_query_str = "N/A"
316
+ slowest_time_str = "0ms"
317
+ if slowest_query:
318
+ slowest_query_str = slowest_query["query"]
319
+ slowest_time_str = f"{slowest_query['time']:.0f}ms"
320
+
321
+ report = f"""
322
+ Search Tool Performance Report
323
+ {'='*50}
324
+
325
+ Overall Health Score: {health:.2%} {health_indicator}
326
+
327
+ Requests:
328
+ Total: {total}
329
+ Successful: {self.metrics['requests']['successful']} ({self.metrics['requests']['successful']/total:.1%})
330
+ Failed: {self.metrics['requests']['failed']}
331
+ Cached: {self.metrics['requests']['cached']}
332
+
333
+ Performance:
334
+ Avg Response: {self.metrics['performance']['avg_response_time_ms']:.0f}ms
335
+ P95 Response: {self.metrics['performance']['p95_response_time_ms']:.0f}ms
336
+ Slowest: {slowest_query_str} ({slowest_time_str})
337
+
338
+ Quality:
339
+ Avg Results/Query: {self.metrics['quality']['avg_results_per_query']:.1f}
340
+ Avg Quality Score: {self.metrics['quality']['avg_quality_score']:.2f}
341
+ High Quality %: {self.metrics['quality']['high_quality_results_pct']:.1%}
342
+ No Results: {self.metrics['quality']['queries_with_no_results']}
343
+
344
+ Cache:
345
+ Hit Rate: {self.metrics['cache']['hit_rate']:.1%}
346
+ Hits: {self.metrics['cache']['total_hits']}
347
+ Misses: {self.metrics['cache']['total_misses']}
348
+
349
+ Errors:
350
+ Error Rate: {self.metrics['errors']['error_rate']:.1%}
351
+ Top Types: {error_str}
352
+
353
+ Query Patterns:
354
+ Top Types: {types_str}
355
+ Avg Query Length: {self.metrics['patterns']['avg_query_length']:.1f} words
356
+ Top Domains: {domains_str}
357
+ """
358
+ return report
359
+
360
+ def get_metrics(self) -> Dict[str, Any]:
361
+ """
362
+ Get all metrics.
363
+
364
+ Returns:
365
+ Complete metrics dictionary
366
+ """
367
+ return self.metrics.copy()
368
+
369
+ def reset(self):
370
+ """Reset all metrics"""
371
+ self.__init__()
@@ -0,0 +1,178 @@
1
+ """
2
+ Rate Limiting and Circuit Breaker Components
3
+
4
+ This module implements rate limiting using token bucket algorithm and
5
+ circuit breaker pattern for API resilience.
6
+ """
7
+
8
+ import time
9
+ from collections import deque
10
+ from threading import Lock
11
+ from typing import Optional
12
+
13
+ from .constants import CircuitState, RateLimitError, CircuitBreakerOpenError
14
+
15
+
16
+ # ============================================================================
17
+ # Rate Limiter
18
+ # ============================================================================
19
+
20
+
21
+ class RateLimiter:
22
+ """
23
+ Token bucket rate limiter for API requests.
24
+
25
+ Implements a token bucket algorithm to limit the rate of API requests
26
+ and prevent quota exhaustion.
27
+ """
28
+
29
+ def __init__(self, max_requests: int, time_window: int):
30
+ """
31
+ Initialize rate limiter.
32
+
33
+ Args:
34
+ max_requests: Maximum number of requests allowed
35
+ time_window: Time window in seconds
36
+ """
37
+ self.max_requests = max_requests
38
+ self.time_window = time_window
39
+ self.tokens = max_requests
40
+ self.last_update = time.time()
41
+ self.lock = Lock()
42
+ self.request_history: deque = deque()
43
+
44
+ def _refill_tokens(self):
45
+ """Refill tokens based on elapsed time"""
46
+ now = time.time()
47
+ time_passed = now - self.last_update
48
+
49
+ # Refill tokens proportionally to time passed
50
+ refill_rate = self.max_requests / self.time_window
51
+ tokens_to_add = time_passed * refill_rate
52
+
53
+ self.tokens = min(self.max_requests, self.tokens + tokens_to_add)
54
+ self.last_update = now
55
+
56
+ def acquire(self, tokens: int = 1) -> bool:
57
+ """
58
+ Attempt to acquire tokens.
59
+
60
+ Args:
61
+ tokens: Number of tokens to acquire
62
+
63
+ Returns:
64
+ True if tokens acquired, False otherwise
65
+
66
+ Raises:
67
+ RateLimitError: If rate limit is exceeded
68
+ """
69
+ with self.lock:
70
+ self._refill_tokens()
71
+
72
+ # Clean up old request history
73
+ cutoff_time = time.time() - self.time_window
74
+ while self.request_history and self.request_history[0] < cutoff_time:
75
+ self.request_history.popleft()
76
+
77
+ # Check if we have enough tokens
78
+ if self.tokens >= tokens:
79
+ self.tokens -= tokens
80
+ self.request_history.append(time.time())
81
+ return True
82
+ else:
83
+ # Calculate wait time
84
+ wait_time = (tokens - self.tokens) / (self.max_requests / self.time_window)
85
+ raise RateLimitError(
86
+ f"Rate limit exceeded. {len(self.request_history)} requests in last "
87
+ f"{self.time_window}s. Wait {wait_time:.1f}s before retrying."
88
+ )
89
+
90
+ def get_remaining_quota(self) -> int:
91
+ """Get remaining quota"""
92
+ with self.lock:
93
+ self._refill_tokens()
94
+ return int(self.tokens)
95
+
96
+
97
+ # ============================================================================
98
+ # Circuit Breaker
99
+ # ============================================================================
100
+
101
+
102
+ class CircuitBreaker:
103
+ """
104
+ Circuit breaker pattern implementation for API resilience.
105
+
106
+ Implements a circuit breaker to prevent cascading failures when
107
+ the API is experiencing issues.
108
+ """
109
+
110
+ def __init__(self, failure_threshold: int, timeout: int):
111
+ """
112
+ Initialize circuit breaker.
113
+
114
+ Args:
115
+ failure_threshold: Number of failures before opening circuit
116
+ timeout: Timeout in seconds before trying half-open state
117
+ """
118
+ self.failure_threshold = failure_threshold
119
+ self.timeout = timeout
120
+ self.failure_count = 0
121
+ self.last_failure_time: Optional[float] = None
122
+ self.state = CircuitState.CLOSED
123
+ self.lock = Lock()
124
+
125
+ def call(self, func, *args, **kwargs):
126
+ """
127
+ Execute function with circuit breaker protection.
128
+
129
+ Args:
130
+ func: Function to execute
131
+ *args: Positional arguments
132
+ **kwargs: Keyword arguments
133
+
134
+ Returns:
135
+ Function result
136
+
137
+ Raises:
138
+ CircuitBreakerOpenError: If circuit is open
139
+ """
140
+ with self.lock:
141
+ if self.state == CircuitState.OPEN:
142
+ # Check if timeout has passed
143
+ if time.time() - self.last_failure_time >= self.timeout:
144
+ self.state = CircuitState.HALF_OPEN
145
+ self.failure_count = 0
146
+ else:
147
+ raise CircuitBreakerOpenError(
148
+ f"Circuit breaker is OPEN. Retry after "
149
+ f"{self.timeout - (time.time() - self.last_failure_time):.1f}s"
150
+ )
151
+
152
+ try:
153
+ result = func(*args, **kwargs)
154
+ self._on_success()
155
+ return result
156
+ except Exception as e:
157
+ self._on_failure()
158
+ raise e
159
+
160
+ def _on_success(self):
161
+ """Handle successful call"""
162
+ with self.lock:
163
+ if self.state == CircuitState.HALF_OPEN:
164
+ self.state = CircuitState.CLOSED
165
+ self.failure_count = 0
166
+
167
+ def _on_failure(self):
168
+ """Handle failed call"""
169
+ with self.lock:
170
+ self.failure_count += 1
171
+ self.last_failure_time = time.time()
172
+
173
+ if self.failure_count >= self.failure_threshold:
174
+ self.state = CircuitState.OPEN
175
+
176
+ def get_state(self) -> str:
177
+ """Get current circuit state"""
178
+ return self.state.value