crca 1.4.0__py3-none-any.whl → 1.5.0__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 (306) hide show
  1. CRCA.py +172 -7
  2. MODEL_CARD.md +53 -0
  3. PKG-INFO +8 -2
  4. RELEASE_NOTES.md +17 -0
  5. STABILITY.md +19 -0
  6. architecture/hybrid/consistency_engine.py +362 -0
  7. architecture/hybrid/conversation_manager.py +421 -0
  8. architecture/hybrid/explanation_generator.py +452 -0
  9. architecture/hybrid/few_shot_learner.py +533 -0
  10. architecture/hybrid/graph_compressor.py +286 -0
  11. architecture/hybrid/hybrid_agent.py +4398 -0
  12. architecture/hybrid/language_compiler.py +623 -0
  13. architecture/hybrid/main,py +0 -0
  14. architecture/hybrid/reasoning_tracker.py +322 -0
  15. architecture/hybrid/self_verifier.py +524 -0
  16. architecture/hybrid/task_decomposer.py +567 -0
  17. architecture/hybrid/text_corrector.py +341 -0
  18. benchmark_results/crca_core_benchmarks.json +178 -0
  19. branches/crca_sd/crca_sd_realtime.py +6 -2
  20. branches/general_agent/__init__.py +102 -0
  21. branches/general_agent/general_agent.py +1400 -0
  22. branches/general_agent/personality.py +169 -0
  23. branches/general_agent/utils/__init__.py +19 -0
  24. branches/general_agent/utils/prompt_builder.py +170 -0
  25. {crca-1.4.0.dist-info → crca-1.5.0.dist-info}/METADATA +8 -2
  26. {crca-1.4.0.dist-info → crca-1.5.0.dist-info}/RECORD +303 -20
  27. crca_core/__init__.py +35 -0
  28. crca_core/benchmarks/__init__.py +14 -0
  29. crca_core/benchmarks/synthetic_scm.py +103 -0
  30. crca_core/core/__init__.py +23 -0
  31. crca_core/core/api.py +120 -0
  32. crca_core/core/estimate.py +208 -0
  33. crca_core/core/godclass.py +72 -0
  34. crca_core/core/intervention_design.py +174 -0
  35. crca_core/core/lifecycle.py +48 -0
  36. crca_core/discovery/__init__.py +9 -0
  37. crca_core/discovery/tabular.py +193 -0
  38. crca_core/identify/__init__.py +171 -0
  39. crca_core/identify/backdoor.py +39 -0
  40. crca_core/identify/frontdoor.py +48 -0
  41. crca_core/identify/graph.py +106 -0
  42. crca_core/identify/id_algorithm.py +43 -0
  43. crca_core/identify/iv.py +48 -0
  44. crca_core/models/__init__.py +67 -0
  45. crca_core/models/provenance.py +56 -0
  46. crca_core/models/refusal.py +39 -0
  47. crca_core/models/result.py +83 -0
  48. crca_core/models/spec.py +151 -0
  49. crca_core/models/validation.py +68 -0
  50. crca_core/scm/__init__.py +9 -0
  51. crca_core/scm/linear_gaussian.py +198 -0
  52. crca_core/timeseries/__init__.py +6 -0
  53. crca_core/timeseries/pcmci.py +181 -0
  54. crca_llm/__init__.py +12 -0
  55. crca_llm/client.py +85 -0
  56. crca_llm/coauthor.py +118 -0
  57. crca_llm/orchestrator.py +289 -0
  58. crca_llm/types.py +21 -0
  59. crca_reasoning/__init__.py +16 -0
  60. crca_reasoning/critique.py +54 -0
  61. crca_reasoning/godclass.py +206 -0
  62. crca_reasoning/memory.py +24 -0
  63. crca_reasoning/rationale.py +10 -0
  64. crca_reasoning/react_controller.py +81 -0
  65. crca_reasoning/tool_router.py +97 -0
  66. crca_reasoning/types.py +40 -0
  67. crca_sd/__init__.py +15 -0
  68. crca_sd/crca_sd_core.py +2 -0
  69. crca_sd/crca_sd_governance.py +2 -0
  70. crca_sd/crca_sd_mpc.py +2 -0
  71. crca_sd/crca_sd_realtime.py +2 -0
  72. crca_sd/crca_sd_tui.py +2 -0
  73. cuda-keyring_1.1-1_all.deb +0 -0
  74. cuda-keyring_1.1-1_all.deb.1 +0 -0
  75. docs/IMAGE_ANNOTATION_USAGE.md +539 -0
  76. docs/INSTALL_DEEPSPEED.md +125 -0
  77. docs/api/branches/crca-cg.md +19 -0
  78. docs/api/branches/crca-q.md +27 -0
  79. docs/api/branches/crca-sd.md +37 -0
  80. docs/api/branches/general-agent.md +24 -0
  81. docs/api/branches/overview.md +19 -0
  82. docs/api/crca/agent-methods.md +62 -0
  83. docs/api/crca/operations.md +79 -0
  84. docs/api/crca/overview.md +32 -0
  85. docs/api/image-annotation/engine.md +52 -0
  86. docs/api/image-annotation/overview.md +17 -0
  87. docs/api/schemas/annotation.md +34 -0
  88. docs/api/schemas/core-schemas.md +82 -0
  89. docs/api/schemas/overview.md +32 -0
  90. docs/api/schemas/policy.md +30 -0
  91. docs/api/utils/conversation.md +22 -0
  92. docs/api/utils/graph-reasoner.md +32 -0
  93. docs/api/utils/overview.md +21 -0
  94. docs/api/utils/router.md +19 -0
  95. docs/api/utils/utilities.md +97 -0
  96. docs/architecture/causal-graphs.md +41 -0
  97. docs/architecture/data-flow.md +29 -0
  98. docs/architecture/design-principles.md +33 -0
  99. docs/architecture/hybrid-agent/components.md +38 -0
  100. docs/architecture/hybrid-agent/consistency.md +26 -0
  101. docs/architecture/hybrid-agent/overview.md +44 -0
  102. docs/architecture/hybrid-agent/reasoning.md +22 -0
  103. docs/architecture/llm-integration.md +26 -0
  104. docs/architecture/modular-structure.md +37 -0
  105. docs/architecture/overview.md +69 -0
  106. docs/architecture/policy-engine-arch.md +29 -0
  107. docs/branches/crca-cg/corposwarm.md +39 -0
  108. docs/branches/crca-cg/esg-scoring.md +30 -0
  109. docs/branches/crca-cg/multi-agent.md +35 -0
  110. docs/branches/crca-cg/overview.md +40 -0
  111. docs/branches/crca-q/alternative-data.md +55 -0
  112. docs/branches/crca-q/architecture.md +71 -0
  113. docs/branches/crca-q/backtesting.md +45 -0
  114. docs/branches/crca-q/causal-engine.md +33 -0
  115. docs/branches/crca-q/execution.md +39 -0
  116. docs/branches/crca-q/market-data.md +60 -0
  117. docs/branches/crca-q/overview.md +58 -0
  118. docs/branches/crca-q/philosophy.md +60 -0
  119. docs/branches/crca-q/portfolio-optimization.md +66 -0
  120. docs/branches/crca-q/risk-management.md +102 -0
  121. docs/branches/crca-q/setup.md +65 -0
  122. docs/branches/crca-q/signal-generation.md +61 -0
  123. docs/branches/crca-q/signal-validation.md +43 -0
  124. docs/branches/crca-sd/core.md +84 -0
  125. docs/branches/crca-sd/governance.md +53 -0
  126. docs/branches/crca-sd/mpc-solver.md +65 -0
  127. docs/branches/crca-sd/overview.md +59 -0
  128. docs/branches/crca-sd/realtime.md +28 -0
  129. docs/branches/crca-sd/tui.md +20 -0
  130. docs/branches/general-agent/overview.md +37 -0
  131. docs/branches/general-agent/personality.md +36 -0
  132. docs/branches/general-agent/prompt-builder.md +30 -0
  133. docs/changelog/index.md +79 -0
  134. docs/contributing/code-style.md +69 -0
  135. docs/contributing/documentation.md +43 -0
  136. docs/contributing/overview.md +29 -0
  137. docs/contributing/testing.md +29 -0
  138. docs/core/crcagent/async-operations.md +65 -0
  139. docs/core/crcagent/automatic-extraction.md +107 -0
  140. docs/core/crcagent/batch-prediction.md +80 -0
  141. docs/core/crcagent/bayesian-inference.md +60 -0
  142. docs/core/crcagent/causal-graph.md +92 -0
  143. docs/core/crcagent/counterfactuals.md +96 -0
  144. docs/core/crcagent/deterministic-simulation.md +78 -0
  145. docs/core/crcagent/dual-mode-operation.md +82 -0
  146. docs/core/crcagent/initialization.md +88 -0
  147. docs/core/crcagent/optimization.md +65 -0
  148. docs/core/crcagent/overview.md +63 -0
  149. docs/core/crcagent/time-series.md +57 -0
  150. docs/core/schemas/annotation.md +30 -0
  151. docs/core/schemas/core-schemas.md +82 -0
  152. docs/core/schemas/overview.md +30 -0
  153. docs/core/schemas/policy.md +41 -0
  154. docs/core/templates/base-agent.md +31 -0
  155. docs/core/templates/feature-mixins.md +31 -0
  156. docs/core/templates/overview.md +29 -0
  157. docs/core/templates/templates-guide.md +75 -0
  158. docs/core/tools/mcp-client.md +34 -0
  159. docs/core/tools/overview.md +24 -0
  160. docs/core/utils/conversation.md +27 -0
  161. docs/core/utils/graph-reasoner.md +29 -0
  162. docs/core/utils/overview.md +27 -0
  163. docs/core/utils/router.md +27 -0
  164. docs/core/utils/utilities.md +97 -0
  165. docs/css/custom.css +84 -0
  166. docs/examples/basic-usage.md +57 -0
  167. docs/examples/general-agent/general-agent-examples.md +50 -0
  168. docs/examples/hybrid-agent/hybrid-agent-examples.md +56 -0
  169. docs/examples/image-annotation/image-annotation-examples.md +54 -0
  170. docs/examples/integration/integration-examples.md +58 -0
  171. docs/examples/overview.md +37 -0
  172. docs/examples/trading/trading-examples.md +46 -0
  173. docs/features/causal-reasoning/advanced-topics.md +101 -0
  174. docs/features/causal-reasoning/counterfactuals.md +43 -0
  175. docs/features/causal-reasoning/do-calculus.md +50 -0
  176. docs/features/causal-reasoning/overview.md +47 -0
  177. docs/features/causal-reasoning/structural-models.md +52 -0
  178. docs/features/hybrid-agent/advanced-components.md +55 -0
  179. docs/features/hybrid-agent/core-components.md +64 -0
  180. docs/features/hybrid-agent/overview.md +34 -0
  181. docs/features/image-annotation/engine.md +82 -0
  182. docs/features/image-annotation/features.md +113 -0
  183. docs/features/image-annotation/integration.md +75 -0
  184. docs/features/image-annotation/overview.md +53 -0
  185. docs/features/image-annotation/quickstart.md +73 -0
  186. docs/features/policy-engine/doctrine-ledger.md +105 -0
  187. docs/features/policy-engine/monitoring.md +44 -0
  188. docs/features/policy-engine/mpc-control.md +89 -0
  189. docs/features/policy-engine/overview.md +46 -0
  190. docs/getting-started/configuration.md +225 -0
  191. docs/getting-started/first-agent.md +164 -0
  192. docs/getting-started/installation.md +144 -0
  193. docs/getting-started/quickstart.md +137 -0
  194. docs/index.md +118 -0
  195. docs/js/mathjax.js +13 -0
  196. docs/lrm/discovery_proof_notes.md +25 -0
  197. docs/lrm/finetune_full.md +83 -0
  198. docs/lrm/math_appendix.md +120 -0
  199. docs/lrm/overview.md +32 -0
  200. docs/mkdocs.yml +238 -0
  201. docs/stylesheets/extra.css +21 -0
  202. docs_generated/crca_core/CounterfactualResult.md +12 -0
  203. docs_generated/crca_core/DiscoveryHypothesisResult.md +13 -0
  204. docs_generated/crca_core/DraftSpec.md +13 -0
  205. docs_generated/crca_core/EstimateResult.md +13 -0
  206. docs_generated/crca_core/IdentificationResult.md +17 -0
  207. docs_generated/crca_core/InterventionDesignResult.md +12 -0
  208. docs_generated/crca_core/LockedSpec.md +15 -0
  209. docs_generated/crca_core/RefusalResult.md +12 -0
  210. docs_generated/crca_core/ValidationReport.md +9 -0
  211. docs_generated/crca_core/index.md +13 -0
  212. examples/general_agent_example.py +277 -0
  213. examples/general_agent_quickstart.py +202 -0
  214. examples/general_agent_simple.py +92 -0
  215. examples/hybrid_agent_auto_extraction.py +84 -0
  216. examples/hybrid_agent_dictionary_demo.py +104 -0
  217. examples/hybrid_agent_enhanced.py +179 -0
  218. examples/hybrid_agent_general_knowledge.py +107 -0
  219. examples/image_annotation_quickstart.py +328 -0
  220. examples/test_hybrid_fixes.py +77 -0
  221. image_annotation/__init__.py +27 -0
  222. image_annotation/annotation_engine.py +2593 -0
  223. install_cuda_wsl2.sh +59 -0
  224. install_deepspeed.sh +56 -0
  225. install_deepspeed_simple.sh +87 -0
  226. mkdocs.yml +252 -0
  227. ollama/Modelfile +8 -0
  228. prompts/__init__.py +2 -1
  229. prompts/default_crca.py +9 -1
  230. prompts/general_agent.py +227 -0
  231. prompts/image_annotation.py +56 -0
  232. pyproject.toml +17 -2
  233. requirements-docs.txt +10 -0
  234. requirements.txt +21 -2
  235. schemas/__init__.py +26 -1
  236. schemas/annotation.py +222 -0
  237. schemas/conversation.py +193 -0
  238. schemas/hybrid.py +211 -0
  239. schemas/reasoning.py +276 -0
  240. schemas_export/crca_core/CounterfactualResult.schema.json +108 -0
  241. schemas_export/crca_core/DiscoveryHypothesisResult.schema.json +113 -0
  242. schemas_export/crca_core/DraftSpec.schema.json +635 -0
  243. schemas_export/crca_core/EstimateResult.schema.json +113 -0
  244. schemas_export/crca_core/IdentificationResult.schema.json +145 -0
  245. schemas_export/crca_core/InterventionDesignResult.schema.json +111 -0
  246. schemas_export/crca_core/LockedSpec.schema.json +646 -0
  247. schemas_export/crca_core/RefusalResult.schema.json +90 -0
  248. schemas_export/crca_core/ValidationReport.schema.json +62 -0
  249. scripts/build_lrm_dataset.py +80 -0
  250. scripts/export_crca_core_schemas.py +54 -0
  251. scripts/export_hf_lrm.py +37 -0
  252. scripts/export_ollama_gguf.py +45 -0
  253. scripts/generate_changelog.py +157 -0
  254. scripts/generate_crca_core_docs_from_schemas.py +86 -0
  255. scripts/run_crca_core_benchmarks.py +163 -0
  256. scripts/run_full_finetune.py +198 -0
  257. scripts/run_lrm_eval.py +31 -0
  258. templates/graph_management.py +29 -0
  259. tests/conftest.py +9 -0
  260. tests/test_core.py +2 -3
  261. tests/test_crca_core_discovery_tabular.py +15 -0
  262. tests/test_crca_core_estimate_dowhy.py +36 -0
  263. tests/test_crca_core_identify.py +18 -0
  264. tests/test_crca_core_intervention_design.py +36 -0
  265. tests/test_crca_core_linear_gaussian_scm.py +69 -0
  266. tests/test_crca_core_spec.py +25 -0
  267. tests/test_crca_core_timeseries_pcmci.py +15 -0
  268. tests/test_crca_llm_coauthor.py +12 -0
  269. tests/test_crca_llm_orchestrator.py +80 -0
  270. tests/test_hybrid_agent_llm_enhanced.py +556 -0
  271. tests/test_image_annotation_demo.py +376 -0
  272. tests/test_image_annotation_operational.py +408 -0
  273. tests/test_image_annotation_unit.py +551 -0
  274. tests/test_training_moe.py +13 -0
  275. training/__init__.py +42 -0
  276. training/datasets.py +140 -0
  277. training/deepspeed_zero2_0_5b.json +22 -0
  278. training/deepspeed_zero2_1_5b.json +22 -0
  279. training/deepspeed_zero3_0_5b.json +28 -0
  280. training/deepspeed_zero3_14b.json +28 -0
  281. training/deepspeed_zero3_h100_3gpu.json +20 -0
  282. training/deepspeed_zero3_offload.json +28 -0
  283. training/eval.py +92 -0
  284. training/finetune.py +516 -0
  285. training/public_datasets.py +89 -0
  286. training_data/react_train.jsonl +7473 -0
  287. utils/agent_discovery.py +311 -0
  288. utils/batch_processor.py +317 -0
  289. utils/conversation.py +78 -0
  290. utils/edit_distance.py +118 -0
  291. utils/formatter.py +33 -0
  292. utils/graph_reasoner.py +530 -0
  293. utils/rate_limiter.py +283 -0
  294. utils/router.py +2 -2
  295. utils/tool_discovery.py +307 -0
  296. webui/__init__.py +10 -0
  297. webui/app.py +229 -0
  298. webui/config.py +104 -0
  299. webui/static/css/style.css +332 -0
  300. webui/static/js/main.js +284 -0
  301. webui/templates/index.html +42 -0
  302. tests/test_crca_excel.py +0 -166
  303. tests/test_data_broker.py +0 -424
  304. tests/test_palantir.py +0 -349
  305. {crca-1.4.0.dist-info → crca-1.5.0.dist-info}/WHEEL +0 -0
  306. {crca-1.4.0.dist-info → crca-1.5.0.dist-info}/licenses/LICENSE +0 -0
utils/rate_limiter.py ADDED
@@ -0,0 +1,283 @@
1
+ """
2
+ Rate limiting utility module.
3
+
4
+ Provides rate limiting functionality for agents and API calls with support for:
5
+ - Token-based and request-based limiting
6
+ - Per-user/session rate limits
7
+ - Queue management for rate-limited requests
8
+ - Configurable limits (RPM, RPH)
9
+ """
10
+
11
+ import time
12
+ import threading
13
+ from collections import deque, defaultdict
14
+ from dataclasses import dataclass, field
15
+ from typing import Dict, Optional, Any, Tuple
16
+ from loguru import logger
17
+
18
+
19
+ @dataclass
20
+ class RateLimitConfig:
21
+ """Configuration for rate limiting.
22
+
23
+ Attributes:
24
+ requests_per_minute: Maximum requests per minute (0 = unlimited)
25
+ requests_per_hour: Maximum requests per hour (0 = unlimited)
26
+ tokens_per_minute: Maximum tokens per minute (0 = unlimited)
27
+ tokens_per_hour: Maximum tokens per hour (0 = unlimited)
28
+ queue_enabled: Whether to queue requests when rate limited
29
+ max_queue_size: Maximum queue size
30
+ """
31
+ requests_per_minute: int = 60
32
+ requests_per_hour: int = 1000
33
+ tokens_per_minute: int = 0 # 0 = unlimited
34
+ tokens_per_hour: int = 0 # 0 = unlimited
35
+ queue_enabled: bool = True
36
+ max_queue_size: int = 1000
37
+
38
+
39
+ class RateLimiter:
40
+ """Rate limiter with per-user/session tracking.
41
+
42
+ Provides rate limiting functionality with support for:
43
+ - Request-based limiting (RPM, RPH)
44
+ - Token-based limiting (TPM, TPH)
45
+ - Per-user/session tracking
46
+ - Queue management for rate-limited requests
47
+ """
48
+
49
+ def __init__(self, config: Optional[RateLimitConfig] = None):
50
+ """Initialize rate limiter.
51
+
52
+ Args:
53
+ config: Rate limit configuration. Uses defaults if None.
54
+ """
55
+ self.config = config or RateLimitConfig()
56
+ self._lock = threading.RLock()
57
+
58
+ # Per-user tracking: {user_id: {type: deque of timestamps}}
59
+ self._request_history: Dict[str, Dict[str, deque]] = defaultdict(
60
+ lambda: {
61
+ "minute": deque(maxlen=self.config.requests_per_minute * 2),
62
+ "hour": deque(maxlen=self.config.requests_per_hour * 2),
63
+ }
64
+ )
65
+
66
+ self._token_history: Dict[str, Dict[str, deque]] = defaultdict(
67
+ lambda: {
68
+ "minute": deque(maxlen=1000),
69
+ "hour": deque(maxlen=10000),
70
+ }
71
+ )
72
+
73
+ # Queue for rate-limited requests
74
+ self._queues: Dict[str, deque] = defaultdict(lambda: deque(maxlen=self.config.max_queue_size))
75
+
76
+ logger.debug(f"Initialized RateLimiter with config: {self.config}")
77
+
78
+ def _clean_old_entries(self, user_id: str, window: str) -> None:
79
+ """Remove old entries outside the time window.
80
+
81
+ Args:
82
+ user_id: User identifier
83
+ window: Time window ('minute' or 'hour')
84
+ """
85
+ current_time = time.time()
86
+ cutoff = current_time - (60 if window == "minute" else 3600)
87
+
88
+ # Clean request history
89
+ if user_id in self._request_history:
90
+ history = self._request_history[user_id][window]
91
+ while history and history[0] < cutoff:
92
+ history.popleft()
93
+
94
+ # Clean token history
95
+ if user_id in self._token_history:
96
+ history = self._token_history[user_id][window]
97
+ while history and len(history) > 0:
98
+ if history[0][0] < cutoff:
99
+ history.popleft()
100
+ else:
101
+ break
102
+
103
+ def check_rate_limit(
104
+ self,
105
+ user_id: str = "default",
106
+ token_count: int = 0,
107
+ ) -> Tuple[bool, Optional[str]]:
108
+ """Check if request is within rate limits.
109
+
110
+ Args:
111
+ user_id: User identifier for per-user tracking
112
+ token_count: Number of tokens in the request (0 if not applicable)
113
+
114
+ Returns:
115
+ Tuple of (is_allowed, error_message)
116
+ - is_allowed: True if request is allowed, False otherwise
117
+ - error_message: Error message if not allowed, None otherwise
118
+ """
119
+ with self._lock:
120
+ current_time = time.time()
121
+
122
+ # Clean old entries
123
+ self._clean_old_entries(user_id, "minute")
124
+ self._clean_old_entries(user_id, "hour")
125
+
126
+ # Check request-based limits
127
+ if self.config.requests_per_minute > 0:
128
+ minute_requests = len(self._request_history[user_id]["minute"])
129
+ if minute_requests >= self.config.requests_per_minute:
130
+ return False, f"Rate limit exceeded: {minute_requests}/{self.config.requests_per_minute} requests per minute"
131
+
132
+ if self.config.requests_per_hour > 0:
133
+ hour_requests = len(self._request_history[user_id]["hour"])
134
+ if hour_requests >= self.config.requests_per_hour:
135
+ return False, f"Rate limit exceeded: {hour_requests}/{self.config.requests_per_hour} requests per hour"
136
+
137
+ # Check token-based limits
138
+ if token_count > 0:
139
+ if self.config.tokens_per_minute > 0:
140
+ minute_tokens = sum(
141
+ tokens for _, tokens in self._token_history[user_id]["minute"]
142
+ )
143
+ if minute_tokens + token_count > self.config.tokens_per_minute:
144
+ return False, f"Token limit exceeded: {minute_tokens + token_count}/{self.config.tokens_per_minute} tokens per minute"
145
+
146
+ if self.config.tokens_per_hour > 0:
147
+ hour_tokens = sum(
148
+ tokens for _, tokens in self._token_history[user_id]["hour"]
149
+ )
150
+ if hour_tokens + token_count > self.config.tokens_per_hour:
151
+ return False, f"Token limit exceeded: {hour_tokens + token_count}/{self.config.tokens_per_hour} tokens per hour"
152
+
153
+ # Record request
154
+ self._request_history[user_id]["minute"].append(current_time)
155
+ self._request_history[user_id]["hour"].append(current_time)
156
+
157
+ # Record tokens if applicable
158
+ if token_count > 0:
159
+ self._token_history[user_id]["minute"].append((current_time, token_count))
160
+ self._token_history[user_id]["hour"].append((current_time, token_count))
161
+
162
+ return True, None
163
+
164
+ def wait_if_rate_limited(
165
+ self,
166
+ user_id: str = "default",
167
+ token_count: int = 0,
168
+ max_wait: float = 60.0,
169
+ ) -> bool:
170
+ """Wait if rate limited, up to max_wait seconds.
171
+
172
+ Args:
173
+ user_id: User identifier
174
+ token_count: Number of tokens in the request
175
+ max_wait: Maximum time to wait in seconds
176
+
177
+ Returns:
178
+ True if request can proceed, False if still rate limited after waiting
179
+ """
180
+ start_time = time.time()
181
+
182
+ while time.time() - start_time < max_wait:
183
+ is_allowed, error_msg = self.check_rate_limit(user_id, token_count)
184
+ if is_allowed:
185
+ return True
186
+
187
+ # Calculate wait time
188
+ if "minute" in error_msg:
189
+ # Wait until oldest request in minute window expires
190
+ if self._request_history[user_id]["minute"]:
191
+ oldest = self._request_history[user_id]["minute"][0]
192
+ wait_time = min(60 - (time.time() - oldest) + 1, max_wait)
193
+ if wait_time > 0:
194
+ time.sleep(min(wait_time, 1.0)) # Sleep in 1s increments
195
+ else:
196
+ time.sleep(0.1)
197
+ elif "hour" in error_msg:
198
+ # Wait until oldest request in hour window expires
199
+ if self._request_history[user_id]["hour"]:
200
+ oldest = self._request_history[user_id]["hour"][0]
201
+ wait_time = min(3600 - (time.time() - oldest) + 1, max_wait)
202
+ if wait_time > 0:
203
+ time.sleep(min(wait_time, 1.0))
204
+ else:
205
+ time.sleep(0.1)
206
+ else:
207
+ time.sleep(0.1)
208
+
209
+ return False
210
+
211
+ def get_rate_limit_status(self, user_id: str = "default") -> Dict[str, Any]:
212
+ """Get current rate limit status for a user.
213
+
214
+ Args:
215
+ user_id: User identifier
216
+
217
+ Returns:
218
+ Dictionary with rate limit status information
219
+ """
220
+ with self._lock:
221
+ self._clean_old_entries(user_id, "minute")
222
+ self._clean_old_entries(user_id, "hour")
223
+
224
+ minute_requests = len(self._request_history[user_id]["minute"])
225
+ hour_requests = len(self._request_history[user_id]["hour"])
226
+
227
+ minute_tokens = sum(
228
+ tokens for _, tokens in self._token_history[user_id]["minute"]
229
+ )
230
+ hour_tokens = sum(
231
+ tokens for _, tokens in self._token_history[user_id]["hour"]
232
+ )
233
+
234
+ return {
235
+ "user_id": user_id,
236
+ "requests": {
237
+ "minute": {
238
+ "current": minute_requests,
239
+ "limit": self.config.requests_per_minute,
240
+ "remaining": max(0, self.config.requests_per_minute - minute_requests),
241
+ },
242
+ "hour": {
243
+ "current": hour_requests,
244
+ "limit": self.config.requests_per_hour,
245
+ "remaining": max(0, self.config.requests_per_hour - hour_requests),
246
+ },
247
+ },
248
+ "tokens": {
249
+ "minute": {
250
+ "current": minute_tokens,
251
+ "limit": self.config.tokens_per_minute,
252
+ "remaining": max(0, self.config.tokens_per_minute - minute_tokens) if self.config.tokens_per_minute > 0 else -1,
253
+ },
254
+ "hour": {
255
+ "current": hour_tokens,
256
+ "limit": self.config.tokens_per_hour,
257
+ "remaining": max(0, self.config.tokens_per_hour - hour_tokens) if self.config.tokens_per_hour > 0 else -1,
258
+ },
259
+ },
260
+ }
261
+
262
+ def reset_user(self, user_id: str) -> None:
263
+ """Reset rate limit tracking for a user.
264
+
265
+ Args:
266
+ user_id: User identifier
267
+ """
268
+ with self._lock:
269
+ if user_id in self._request_history:
270
+ del self._request_history[user_id]
271
+ if user_id in self._token_history:
272
+ del self._token_history[user_id]
273
+ if user_id in self._queues:
274
+ del self._queues[user_id]
275
+ logger.debug(f"Reset rate limit tracking for user: {user_id}")
276
+
277
+ def reset_all(self) -> None:
278
+ """Reset rate limit tracking for all users."""
279
+ with self._lock:
280
+ self._request_history.clear()
281
+ self._token_history.clear()
282
+ self._queues.clear()
283
+ logger.debug("Reset rate limit tracking for all users")
utils/router.py CHANGED
@@ -24,7 +24,7 @@ from swarms.structs.concurrent_workflow import ConcurrentWorkflow
24
24
  from swarms.structs.council_as_judge import CouncilAsAJudge
25
25
  # DebateWithJudge may not be available - make optional
26
26
  try:
27
- from swarms.structs.debate_with_judge import DebateWithJudge
27
+ from swarms.structs.debate_with_judge import DebateWithJudge
28
28
  except ImportError:
29
29
  DebateWithJudge = None # Will be handled in factory
30
30
  from swarms.structs.groupchat import GroupChat
@@ -43,7 +43,7 @@ from swarms.utils.loguru_logger import initialize_logger
43
43
  from swarms.utils.output_types import OutputType
44
44
  # LLMCouncil may not be available - make optional
45
45
  try:
46
- from swarms.structs.llm_council import LLMCouncil
46
+ from swarms.structs.llm_council import LLMCouncil
47
47
  except ImportError:
48
48
  LLMCouncil = None # Will be handled in factory
49
49
  from swarms.structs.round_robin import RoundRobinSwarm
@@ -0,0 +1,307 @@
1
+ """
2
+ Dynamic tool discovery utilities.
3
+
4
+ Provides functionality for:
5
+ - Tool registry and scanning
6
+ - Tool metadata extraction
7
+ - Tool schema generation
8
+ """
9
+
10
+ import inspect
11
+ from typing import Any, Callable, Dict, List, Optional
12
+ from loguru import logger
13
+
14
+ try:
15
+ from swarms.tools.tool import convert_function_to_openai_function_schema
16
+ TOOL_CONVERSION_AVAILABLE = True
17
+ except ImportError:
18
+ TOOL_CONVERSION_AVAILABLE = False
19
+ logger.debug("Tool conversion utilities not available")
20
+
21
+
22
+ class ToolRegistry:
23
+ """Registry for managing and discovering tools."""
24
+
25
+ def __init__(self):
26
+ """Initialize tool registry."""
27
+ self._tools: Dict[str, Callable] = {}
28
+ self._tool_metadata: Dict[str, Dict[str, Any]] = {}
29
+ logger.debug("Initialized ToolRegistry")
30
+
31
+ def register_tool(
32
+ self,
33
+ tool: Callable,
34
+ name: Optional[str] = None,
35
+ description: Optional[str] = None,
36
+ metadata: Optional[Dict[str, Any]] = None,
37
+ ) -> str:
38
+ """Register a tool in the registry.
39
+
40
+ Args:
41
+ tool: Callable tool function
42
+ name: Optional tool name (uses function name if None)
43
+ description: Optional tool description
44
+ metadata: Optional additional metadata
45
+
46
+ Returns:
47
+ Registered tool name
48
+ """
49
+ tool_name = name or tool.__name__
50
+
51
+ if tool_name in self._tools:
52
+ logger.warning(f"Tool '{tool_name}' already registered, overwriting")
53
+
54
+ self._tools[tool_name] = tool
55
+
56
+ # Extract metadata
57
+ tool_metadata = {
58
+ "name": tool_name,
59
+ "description": description or tool.__doc__ or "No description",
60
+ "function": tool,
61
+ "signature": str(inspect.signature(tool)),
62
+ }
63
+
64
+ if metadata:
65
+ tool_metadata.update(metadata)
66
+
67
+ self._tool_metadata[tool_name] = tool_metadata
68
+
69
+ logger.debug(f"Registered tool: {tool_name}")
70
+ return tool_name
71
+
72
+ def unregister_tool(self, name: str) -> bool:
73
+ """Unregister a tool from the registry.
74
+
75
+ Args:
76
+ name: Tool name to unregister
77
+
78
+ Returns:
79
+ True if tool was unregistered, False if not found
80
+ """
81
+ if name in self._tools:
82
+ del self._tools[name]
83
+ if name in self._tool_metadata:
84
+ del self._tool_metadata[name]
85
+ logger.debug(f"Unregistered tool: {name}")
86
+ return True
87
+ return False
88
+
89
+ def get_tool(self, name: str) -> Optional[Callable]:
90
+ """Get a tool by name.
91
+
92
+ Args:
93
+ name: Tool name
94
+
95
+ Returns:
96
+ Tool function or None if not found
97
+ """
98
+ return self._tools.get(name)
99
+
100
+ def get_tool_metadata(self, name: str) -> Optional[Dict[str, Any]]:
101
+ """Get tool metadata by name.
102
+
103
+ Args:
104
+ name: Tool name
105
+
106
+ Returns:
107
+ Tool metadata dictionary or None if not found
108
+ """
109
+ return self._tool_metadata.get(name)
110
+
111
+ def list_tools(self) -> List[str]:
112
+ """List all registered tool names.
113
+
114
+ Returns:
115
+ List of tool names
116
+ """
117
+ return list(self._tools.keys())
118
+
119
+ def get_all_tools(self) -> Dict[str, Callable]:
120
+ """Get all registered tools.
121
+
122
+ Returns:
123
+ Dictionary mapping tool names to tool functions
124
+ """
125
+ return self._tools.copy()
126
+
127
+ def get_all_metadata(self) -> Dict[str, Dict[str, Any]]:
128
+ """Get metadata for all tools.
129
+
130
+ Returns:
131
+ Dictionary mapping tool names to metadata
132
+ """
133
+ return self._tool_metadata.copy()
134
+
135
+
136
+ # Global tool registry instance
137
+ _global_registry = ToolRegistry()
138
+
139
+
140
+ def get_global_registry() -> ToolRegistry:
141
+ """Get the global tool registry instance.
142
+
143
+ Returns:
144
+ Global ToolRegistry instance
145
+ """
146
+ return _global_registry
147
+
148
+
149
+ def discover_tools_in_module(module: Any) -> List[Callable]:
150
+ """Discover all callable tools in a module.
151
+
152
+ Args:
153
+ module: Module to scan for tools
154
+
155
+ Returns:
156
+ List of discovered tool functions
157
+ """
158
+ tools = []
159
+
160
+ try:
161
+ for name in dir(module):
162
+ obj = getattr(module, name)
163
+ if callable(obj) and not name.startswith("_"):
164
+ # Check if it looks like a tool (has docstring, takes parameters, etc.)
165
+ if inspect.isfunction(obj) or inspect.ismethod(obj):
166
+ tools.append(obj)
167
+ except Exception as e:
168
+ logger.error(f"Error discovering tools in module: {e}")
169
+
170
+ return tools
171
+
172
+
173
+ def discover_tools_in_object(obj: Any) -> List[Callable]:
174
+ """Discover all callable tools in an object.
175
+
176
+ Args:
177
+ obj: Object to scan for tools
178
+
179
+ Returns:
180
+ List of discovered tool methods
181
+ """
182
+ tools = []
183
+
184
+ try:
185
+ for name in dir(obj):
186
+ attr = getattr(obj, name)
187
+ if callable(attr) and not name.startswith("_"):
188
+ if inspect.ismethod(attr) or (inspect.isfunction(attr) and hasattr(obj, name)):
189
+ tools.append(attr)
190
+ except Exception as e:
191
+ logger.error(f"Error discovering tools in object: {e}")
192
+
193
+ return tools
194
+
195
+
196
+ def extract_tool_schema(tool: Callable) -> Optional[Dict[str, Any]]:
197
+ """Extract OpenAI function schema from a tool.
198
+
199
+ Args:
200
+ tool: Tool function to extract schema from
201
+
202
+ Returns:
203
+ OpenAI function schema dictionary or None if extraction fails
204
+ """
205
+ if not TOOL_CONVERSION_AVAILABLE:
206
+ # Fallback: create basic schema from function signature
207
+ try:
208
+ sig = inspect.signature(tool)
209
+ params = {}
210
+ for param_name, param in sig.parameters.items():
211
+ param_type = "string"
212
+ if param.annotation != inspect.Parameter.empty:
213
+ if param.annotation == int:
214
+ param_type = "integer"
215
+ elif param.annotation == float:
216
+ param_type = "number"
217
+ elif param.annotation == bool:
218
+ param_type = "boolean"
219
+ elif param.annotation == list:
220
+ param_type = "array"
221
+
222
+ params[param_name] = {
223
+ "type": param_type,
224
+ "description": param_name,
225
+ }
226
+
227
+ return {
228
+ "name": tool.__name__,
229
+ "description": tool.__doc__ or "No description",
230
+ "parameters": {
231
+ "type": "object",
232
+ "properties": params,
233
+ "required": [
234
+ name for name, param in sig.parameters.items()
235
+ if param.default == inspect.Parameter.empty
236
+ ],
237
+ },
238
+ }
239
+ except Exception as e:
240
+ logger.error(f"Error extracting tool schema: {e}")
241
+ return None
242
+
243
+ try:
244
+ schema = convert_function_to_openai_function_schema(tool)
245
+ return schema
246
+ except Exception as e:
247
+ logger.error(f"Error converting tool to schema: {e}")
248
+ return None
249
+
250
+
251
+ def generate_tool_schemas(tools: List[Callable]) -> List[Dict[str, Any]]:
252
+ """Generate OpenAI function schemas for a list of tools.
253
+
254
+ Args:
255
+ tools: List of tool functions
256
+
257
+ Returns:
258
+ List of OpenAI function schema dictionaries
259
+ """
260
+ schemas = []
261
+
262
+ for tool in tools:
263
+ schema = extract_tool_schema(tool)
264
+ if schema:
265
+ schemas.append(schema)
266
+
267
+ return schemas
268
+
269
+
270
+ def discover_and_register_tools(
271
+ source: Any,
272
+ registry: Optional[ToolRegistry] = None,
273
+ ) -> List[str]:
274
+ """Discover tools from a source and register them.
275
+
276
+ Args:
277
+ source: Module, object, or list of tools to discover from
278
+ registry: Optional tool registry (uses global if None)
279
+
280
+ Returns:
281
+ List of registered tool names
282
+ """
283
+ if registry is None:
284
+ registry = get_global_registry()
285
+
286
+ tools = []
287
+
288
+ # Handle different source types
289
+ if inspect.ismodule(source):
290
+ tools = discover_tools_in_module(source)
291
+ elif isinstance(source, list):
292
+ tools = [t for t in source if callable(t)]
293
+ elif hasattr(source, "__dict__"):
294
+ tools = discover_tools_in_object(source)
295
+ elif callable(source):
296
+ tools = [source]
297
+
298
+ registered = []
299
+ for tool in tools:
300
+ try:
301
+ name = registry.register_tool(tool)
302
+ registered.append(name)
303
+ except Exception as e:
304
+ logger.error(f"Error registering tool {tool}: {e}")
305
+
306
+ logger.info(f"Registered {len(registered)} tool(s)")
307
+ return registered
webui/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """
2
+ CRCA Web UI Module
3
+
4
+ Simple, institutional web interface for CRCA agents.
5
+ """
6
+
7
+ from webui.app import app
8
+ from webui.config import AGENTS, TOOLS
9
+
10
+ __all__ = ["app", "AGENTS", "TOOLS"]