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
@@ -0,0 +1,1400 @@
1
+ """
2
+ General-purpose conversational agent.
3
+
4
+ A jack-of-all-trades agent capable of handling diverse tasks through conversation,
5
+ tool usage, agent routing, code execution, and multimodal processing.
6
+ """
7
+
8
+ import asyncio
9
+ import time
10
+ from dataclasses import dataclass, field
11
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
12
+ from loguru import logger
13
+
14
+ from templates.base_specialized_agent import BaseSpecializedAgent
15
+
16
+ # Import utilities
17
+ try:
18
+ from utils.rate_limiter import RateLimiter, RateLimitConfig
19
+ RATE_LIMITER_AVAILABLE = True
20
+ except ImportError:
21
+ RATE_LIMITER_AVAILABLE = False
22
+ logger.debug("Rate limiter not available")
23
+
24
+ try:
25
+ from utils.agent_discovery import (
26
+ discover_all_agents,
27
+ find_best_agent_for_task,
28
+ route_to_agent,
29
+ discover_aop_instances,
30
+ discover_router_instances,
31
+ )
32
+ AGENT_DISCOVERY_AVAILABLE = True
33
+ except ImportError:
34
+ AGENT_DISCOVERY_AVAILABLE = False
35
+ logger.debug("Agent discovery not available")
36
+
37
+ try:
38
+ # Tool discovery utilities available for future use
39
+ from utils.tool_discovery import (
40
+ get_global_registry,
41
+ discover_and_register_tools,
42
+ generate_tool_schemas,
43
+ )
44
+ TOOL_DISCOVERY_AVAILABLE = True
45
+ except ImportError:
46
+ TOOL_DISCOVERY_AVAILABLE = False
47
+ logger.debug("Tool discovery not available")
48
+
49
+ try:
50
+ from utils.batch_processor import BatchProcessor
51
+ BATCH_PROCESSOR_AVAILABLE = True
52
+ except ImportError:
53
+ BATCH_PROCESSOR_AVAILABLE = False
54
+ logger.debug("Batch processor not available")
55
+
56
+ try:
57
+ from utils.conversation import Conversation
58
+ CONVERSATION_AVAILABLE = True
59
+ except ImportError:
60
+ CONVERSATION_AVAILABLE = False
61
+ logger.debug("Conversation not available")
62
+
63
+ try:
64
+ from utils.formatter import Formatter
65
+ FORMATTER_AVAILABLE = True
66
+ except ImportError:
67
+ FORMATTER_AVAILABLE = False
68
+ logger.debug("Formatter not available")
69
+
70
+ # Import agent-specific modules
71
+ try:
72
+ from branches.general_agent.personality import get_personality, Personality
73
+ from branches.general_agent.utils.prompt_builder import PromptBuilder
74
+ PERSONALITY_AVAILABLE = True
75
+ except ImportError:
76
+ PERSONALITY_AVAILABLE = False
77
+ logger.debug("Personality system not available")
78
+
79
+ try:
80
+ from prompts.general_agent import DEFAULT_GENERAL_AGENT_PROMPT
81
+ except ImportError:
82
+ DEFAULT_GENERAL_AGENT_PROMPT = "You are a general-purpose AI assistant."
83
+
84
+ # Image annotation imports (optional - graceful fallback if not available)
85
+ try:
86
+ from image_annotation.annotation_engine import ImageAnnotationEngine
87
+ IMAGE_ANNOTATION_AVAILABLE = True
88
+ except ImportError:
89
+ IMAGE_ANNOTATION_AVAILABLE = False
90
+ logger.debug("Image annotation engine not available")
91
+ except Exception as e:
92
+ IMAGE_ANNOTATION_AVAILABLE = False
93
+ logger.warning(f"Image annotation engine import failed: {e}")
94
+
95
+ # Global singleton for image annotation engine (lazy-loaded)
96
+ _image_annotation_engine: Optional[Any] = None
97
+
98
+ # Try to import CRCAAgent for causal reasoning integration
99
+ try:
100
+ from CRCA import CRCAAgent
101
+ CRCA_AVAILABLE = True
102
+ except ImportError:
103
+ CRCA_AVAILABLE = False
104
+ CRCAAgent = None
105
+ logger.debug("CRCAAgent not available for causal reasoning integration")
106
+
107
+ # Try to import file operations
108
+ try:
109
+ from tools.file_operations import IntelligentFileManager, FileOperationsRegistry
110
+ FILE_OPERATIONS_AVAILABLE = True
111
+ except ImportError:
112
+ FILE_OPERATIONS_AVAILABLE = False
113
+ IntelligentFileManager = None
114
+ FileOperationsRegistry = None
115
+ logger.debug("File operations not available")
116
+
117
+
118
+ @dataclass
119
+ class GeneralAgentConfig:
120
+ """Configuration for GeneralAgent.
121
+
122
+ Attributes:
123
+ personality: Personality name or Personality instance
124
+ enable_agent_routing: Enable agent routing (default: auto)
125
+ enable_code_execution: Enable code interpreter (default: True)
126
+ enable_multimodal: Enable multimodal support (default: True)
127
+ enable_streaming: Enable streaming (default: True)
128
+ enable_persistence: Enable conversation persistence (default: True)
129
+ enable_causal_reasoning: Enable causal reasoning tools (default: True)
130
+ enable_file_operations: Enable file operations tools (default: True)
131
+ persistence_path: Path for conversation storage
132
+ rate_limit_rpm: Rate limit requests per minute
133
+ rate_limit_rph: Rate limit requests per hour
134
+ custom_prompt_additions: Extendable prompt additions
135
+ """
136
+ personality: Union[str, Any, None] = "neutral" # Any to handle Personality type if available
137
+ enable_agent_routing: Union[bool, str] = "auto"
138
+ enable_code_execution: bool = True
139
+ enable_multimodal: bool = True
140
+ enable_streaming: bool = True
141
+ enable_persistence: bool = True
142
+ enable_causal_reasoning: bool = True
143
+ enable_file_operations: bool = True
144
+ persistence_path: Optional[str] = None
145
+ rate_limit_rpm: int = 60
146
+ rate_limit_rph: int = 1000
147
+ custom_prompt_additions: List[str] = field(default_factory=list)
148
+
149
+ @classmethod
150
+ def auto(cls, **overrides) -> "GeneralAgentConfig":
151
+ """Create config with smart auto-detection and defaults.
152
+
153
+ Args:
154
+ **overrides: Any config values to override
155
+
156
+ Returns:
157
+ GeneralAgentConfig with smart defaults
158
+ """
159
+ import os
160
+ from pathlib import Path
161
+
162
+ # Auto-detect persistence path
163
+ persistence_path = overrides.get("persistence_path")
164
+ if persistence_path is None:
165
+ default_path = Path.home() / ".crca" / "conversations"
166
+ default_path.mkdir(parents=True, exist_ok=True)
167
+ persistence_path = str(default_path)
168
+
169
+ # Auto-detect model from env or use default
170
+ # (Model selection handled in GeneralAgent.__init__)
171
+
172
+ # Create config with smart defaults
173
+ config = cls(
174
+ persistence_path=persistence_path,
175
+ **{k: v for k, v in overrides.items() if k != "persistence_path"}
176
+ )
177
+
178
+ return config
179
+
180
+
181
+ class GeneralAgent(BaseSpecializedAgent):
182
+ """
183
+ Pure hardened CR-CA Agent - a production-ready general-purpose agent
184
+ that embodies the full power of the CR-CA (Causal Reasoning with Counterfactual Analysis) framework.
185
+
186
+ This is NOT a generic general-purpose agent. It is a specialized CR-CA agent
187
+ whose specialization IS being useful across diverse domains while maintaining
188
+ the core CR-CA philosophy: causal reasoning, counterfactual analysis, and
189
+ structured decision-making.
190
+
191
+ Core CR-CA Identity:
192
+ - Causal reasoning first: Understands cause-and-effect relationships
193
+ - Counterfactual thinking: Explores "what-if" scenarios systematically
194
+ - Structured analysis: Uses causal graphs and variable extraction
195
+ - Evidence-based decisions: Grounds recommendations in causal analysis
196
+ - Multi-domain applicability: Applies CR-CA principles across all domains
197
+
198
+ Hardened Production Features:
199
+ - Robust error handling with retry and fallback mechanisms
200
+ - Rate limiting and resource management
201
+ - Conversation persistence and state management
202
+ - Comprehensive logging and monitoring
203
+ - Graceful degradation when dependencies unavailable
204
+ - Async/sync operations with proper resource cleanup
205
+ - Batch processing for efficiency
206
+
207
+ Full CR-CA Codebase Integration:
208
+ - Causal reasoning tools (extract_causal_variables, generate_causal_analysis)
209
+ - Meta-reasoning (scenario-level informativeness analysis, task-level strategic planning)
210
+ - Image annotation (full ImageAnnotationEngine with geometric analysis)
211
+ - File operations (read/write/list with intelligent management)
212
+ - Agent discovery and routing (AOP/router integration for specialized agents)
213
+ - Tool discovery (dynamic tool registry and schema generation)
214
+ - Multi-step reasoning (always enabled for complex causal chains)
215
+ - Code execution (for data analysis and causal modeling)
216
+ - Multimodal processing (images, text, structured data)
217
+
218
+ Routing Strategy:
219
+ - Can route to specialized CR-CA agents (CRCAAgent, CRCA-SD, CRCA-CG, CRCA-Q)
220
+ - Route-first approach: Check for specialized agents before direct handling
221
+ - Falls back to direct CR-CA tool usage when appropriate
222
+ - Maintains CR-CA methodology even when routing
223
+
224
+ This agent represents the "useful" specialization - applying CR-CA's
225
+ causal reasoning and counterfactual analysis capabilities to any domain
226
+ or task, making it the go-to agent when you need CR-CA power without
227
+ domain-specific constraints.
228
+ """
229
+
230
+ def __init__(
231
+ self,
232
+ model_name: Optional[str] = None,
233
+ personality: Optional[Union[str, Any]] = None,
234
+ agent_name: Optional[str] = None,
235
+ config: Optional[GeneralAgentConfig] = None,
236
+ # Legacy parameters (for backwards compatibility)
237
+ max_loops: Optional[Union[int, str]] = None,
238
+ agent_description: Optional[str] = None,
239
+ description: Optional[str] = None,
240
+ system_prompt: Optional[str] = None,
241
+ temperature: Optional[float] = None,
242
+ enable_agent_routing: Optional[Union[bool, str]] = None,
243
+ enable_code_execution: Optional[bool] = None,
244
+ enable_multimodal: Optional[bool] = None,
245
+ enable_streaming: Optional[bool] = None,
246
+ enable_persistence: Optional[bool] = None,
247
+ enable_causal_reasoning: Optional[bool] = None,
248
+ enable_file_operations: Optional[bool] = None,
249
+ persistence_path: Optional[str] = None,
250
+ rate_limit_rpm: Optional[int] = None,
251
+ rate_limit_rph: Optional[int] = None,
252
+ custom_prompt_additions: Optional[List[str]] = None,
253
+ aop_instance: Optional[Any] = None,
254
+ router_instance: Optional[Any] = None,
255
+ **kwargs,
256
+ ):
257
+ """Initialize GeneralAgent with smart auto-configuration.
258
+
259
+ Simple usage (recommended):
260
+ agent = GeneralAgent() # Uses all smart defaults
261
+ agent = GeneralAgent(model_name="gpt-4o") # Just change model
262
+ agent = GeneralAgent(personality="friendly") # Just change personality
263
+
264
+ Advanced usage:
265
+ agent = GeneralAgent(config=GeneralAgentConfig.auto(...))
266
+
267
+ Args:
268
+ model_name: LLM model (auto-detected from env or defaults to gpt-4o-mini)
269
+ personality: Personality name (default: neutral)
270
+ agent_name: Unique identifier (auto-generated if None)
271
+ config: Pre-configured GeneralAgentConfig (uses auto() if None)
272
+ **kwargs: Legacy parameters for backwards compatibility
273
+ """
274
+ # Auto-detect model from environment or use default
275
+ if model_name is None:
276
+ import os
277
+ model_name = os.getenv("CRCA_MODEL_NAME", "gpt-4o-mini")
278
+
279
+ # Use provided config or create auto-config
280
+ if config is None:
281
+ # Merge legacy parameters into config overrides
282
+ config_overrides = {}
283
+ if personality is not None:
284
+ config_overrides["personality"] = personality
285
+ if enable_agent_routing is not None:
286
+ config_overrides["enable_agent_routing"] = enable_agent_routing
287
+ if enable_code_execution is not None:
288
+ config_overrides["enable_code_execution"] = enable_code_execution
289
+ if enable_multimodal is not None:
290
+ config_overrides["enable_multimodal"] = enable_multimodal
291
+ if enable_streaming is not None:
292
+ config_overrides["enable_streaming"] = enable_streaming
293
+ if enable_persistence is not None:
294
+ config_overrides["enable_persistence"] = enable_persistence
295
+ if enable_causal_reasoning is not None:
296
+ config_overrides["enable_causal_reasoning"] = enable_causal_reasoning
297
+ if enable_file_operations is not None:
298
+ config_overrides["enable_file_operations"] = enable_file_operations
299
+ if persistence_path is not None:
300
+ config_overrides["persistence_path"] = persistence_path
301
+ if rate_limit_rpm is not None:
302
+ config_overrides["rate_limit_rpm"] = rate_limit_rpm
303
+ if rate_limit_rph is not None:
304
+ config_overrides["rate_limit_rph"] = rate_limit_rph
305
+ if custom_prompt_additions is not None:
306
+ config_overrides["custom_prompt_additions"] = custom_prompt_additions
307
+
308
+ config = GeneralAgentConfig.auto(**config_overrides)
309
+
310
+ # Store configuration
311
+ self.config = config
312
+
313
+ # Auto-generate agent name if not provided
314
+ if agent_name is None:
315
+ import uuid
316
+ agent_name = f"crca-agent-{uuid.uuid4().hex[:8]}"
317
+
318
+ # Set defaults for other parameters
319
+ if max_loops is None:
320
+ max_loops = 3
321
+ if agent_description is None:
322
+ agent_description = description or "Pure hardened CR-CA Agent - useful across all domains"
323
+ if temperature is None:
324
+ temperature = 0.4
325
+
326
+ # Build system prompt with personality and extensions
327
+ if system_prompt is None:
328
+ system_prompt = self._build_system_prompt()
329
+
330
+ # Enable meta-reasoning (planning) for strategic task approach
331
+ # This allows the agent to reason about its reasoning process
332
+ kwargs.setdefault("plan_enabled", True)
333
+ kwargs.setdefault("planning", True)
334
+
335
+ # Initialize base agent with auto-configured settings
336
+ super().__init__(
337
+ max_loops=max_loops,
338
+ agent_name=agent_name,
339
+ agent_description=agent_description,
340
+ model_name=model_name,
341
+ system_prompt=system_prompt,
342
+ temperature=temperature,
343
+ code_interpreter=self.config.enable_code_execution,
344
+ multi_modal=self.config.enable_multimodal,
345
+ streaming_on=self.config.enable_streaming,
346
+ **kwargs,
347
+ )
348
+
349
+ # Store instances for later use
350
+ self._aop_instance = aop_instance
351
+ self._router_instance = router_instance
352
+
353
+ logger.info(f"Initialized GeneralAgent: {agent_name} (model: {model_name}, personality: {self.config.personality})")
354
+
355
+ @classmethod
356
+ def create(
357
+ cls,
358
+ model_name: Optional[str] = None,
359
+ personality: Optional[str] = None,
360
+ **kwargs
361
+ ) -> "GeneralAgent":
362
+ """Factory method for easy agent creation.
363
+
364
+ Simplest usage:
365
+ agent = GeneralAgent.create()
366
+ agent = GeneralAgent.create(model_name="gpt-4o")
367
+ agent = GeneralAgent.create(personality="friendly")
368
+
369
+ Args:
370
+ model_name: LLM model name
371
+ personality: Personality name
372
+ **kwargs: Additional parameters
373
+
374
+ Returns:
375
+ Configured GeneralAgent instance
376
+ """
377
+ return cls(model_name=model_name, personality=personality, **kwargs)
378
+
379
+ def _build_system_prompt(self) -> str:
380
+ """Build system prompt with personality and extensions.
381
+
382
+ Returns:
383
+ Complete system prompt string
384
+ """
385
+ builder = PromptBuilder(DEFAULT_GENERAL_AGENT_PROMPT) if PERSONALITY_AVAILABLE else None
386
+
387
+ if builder:
388
+ # Add personality
389
+ if self.config.personality:
390
+ try:
391
+ if isinstance(self.config.personality, str):
392
+ personality = get_personality(self.config.personality) if PERSONALITY_AVAILABLE else None
393
+ else:
394
+ personality = self.config.personality
395
+
396
+ if personality and hasattr(personality, "get_prompt_addition"):
397
+ builder.add_personality(personality.get_prompt_addition())
398
+ except Exception as e:
399
+ logger.warning(f"Error adding personality: {e}")
400
+
401
+ # Add custom additions
402
+ for addition in self.config.custom_prompt_additions:
403
+ builder.add_custom(addition)
404
+
405
+ return builder.build()
406
+
407
+ # Fallback if PromptBuilder not available
408
+ prompt = DEFAULT_GENERAL_AGENT_PROMPT
409
+ if self.config.custom_prompt_additions:
410
+ prompt += "\n\n" + "\n".join(self.config.custom_prompt_additions)
411
+ return prompt
412
+
413
+ def _get_domain_schema(self) -> Optional[Dict[str, Any]]:
414
+ """Return tool schemas for agent discovery and dynamic tools.
415
+
416
+ Returns:
417
+ Dictionary containing tool schemas or None
418
+ """
419
+ # BaseSpecializedAgent wraps the schema in a list, so we return None
420
+ # and handle tools in _domain_specific_setup by setting tools_list_dictionary directly
421
+ return None
422
+
423
+ def _get_agent_discovery_schemas(self) -> List[Dict[str, Any]]:
424
+ """Get schemas for agent discovery tools.
425
+
426
+ Returns:
427
+ List of tool schema dictionaries
428
+ """
429
+ schemas = []
430
+
431
+ # discover_agents tool
432
+ schemas.append({
433
+ "type": "function",
434
+ "function": {
435
+ "name": "discover_agents",
436
+ "description": "Discover all available agents from AOP and router instances",
437
+ "parameters": {
438
+ "type": "object",
439
+ "properties": {},
440
+ },
441
+ },
442
+ })
443
+
444
+ # route_to_agent tool
445
+ schemas.append({
446
+ "type": "function",
447
+ "function": {
448
+ "name": "route_to_agent",
449
+ "description": "Route a task to a specific agent",
450
+ "parameters": {
451
+ "type": "object",
452
+ "properties": {
453
+ "agent_name": {
454
+ "type": "string",
455
+ "description": "Name of the agent to route to",
456
+ },
457
+ "task": {
458
+ "type": "string",
459
+ "description": "Task to execute",
460
+ },
461
+ },
462
+ "required": ["agent_name", "task"],
463
+ },
464
+ },
465
+ })
466
+
467
+ return schemas
468
+
469
+ def _get_tool_discovery_schemas(self) -> List[Dict[str, Any]]:
470
+ """Get schemas for tool discovery.
471
+
472
+ Returns:
473
+ List of tool schema dictionaries
474
+ """
475
+ schemas = []
476
+
477
+ # discover_tools tool
478
+ schemas.append({
479
+ "type": "function",
480
+ "function": {
481
+ "name": "discover_tools",
482
+ "description": "Discover available tools dynamically",
483
+ "parameters": {
484
+ "type": "object",
485
+ "properties": {},
486
+ },
487
+ },
488
+ })
489
+
490
+ return schemas
491
+
492
+ def _get_image_annotation_schemas(self) -> List[Dict[str, Any]]:
493
+ """Get schemas for image annotation tools.
494
+
495
+ Returns:
496
+ List of tool schema dictionaries
497
+ """
498
+ schemas = []
499
+
500
+ # annotate_image tool
501
+ schemas.append({
502
+ "type": "function",
503
+ "function": {
504
+ "name": "annotate_image",
505
+ "description": "Annotate an image with geometric primitives, semantic labels, and measurements. Automatically detects image type, tunes parameters, and extracts primitives (lines, circles, contours). Returns overlay image, formal report, and JSON data.",
506
+ "parameters": {
507
+ "type": "object",
508
+ "properties": {
509
+ "image_path": {
510
+ "type": "string",
511
+ "description": "Path to image file, URL, or description of image location"
512
+ },
513
+ "output_format": {
514
+ "type": "string",
515
+ "enum": ["overlay", "json", "report", "all"],
516
+ "default": "all",
517
+ "description": "Output format: 'overlay' (numpy array), 'json' (structured data), 'report' (text), 'all' (AnnotationResult)"
518
+ },
519
+ "frame_id": {
520
+ "type": "integer",
521
+ "description": "Optional frame ID for temporal tracking in video sequences"
522
+ }
523
+ },
524
+ "required": ["image_path"]
525
+ }
526
+ }
527
+ })
528
+
529
+ # query_image tool
530
+ schemas.append({
531
+ "type": "function",
532
+ "function": {
533
+ "name": "query_image",
534
+ "description": "Answer a specific query about an image using natural language. Performs annotation first, then analyzes the results to answer questions like 'find the largest building', 'measure dimensions', 'count objects', etc.",
535
+ "parameters": {
536
+ "type": "object",
537
+ "properties": {
538
+ "image_path": {
539
+ "type": "string",
540
+ "description": "Path to image file, URL, or description of image location"
541
+ },
542
+ "query": {
543
+ "type": "string",
544
+ "description": "Natural language query about the image (e.g., 'find the largest building and measure its dimensions', 'count how many circles are in the image', 'identify all lines and measure their lengths')"
545
+ },
546
+ "frame_id": {
547
+ "type": "integer",
548
+ "description": "Optional frame ID for temporal tracking"
549
+ }
550
+ },
551
+ "required": ["image_path", "query"]
552
+ }
553
+ }
554
+ })
555
+
556
+ return schemas
557
+
558
+ def _get_causal_reasoning_schemas(self) -> List[Dict[str, Any]]:
559
+ """Get schemas for causal reasoning tools.
560
+
561
+ Returns:
562
+ List of tool schema dictionaries
563
+ """
564
+ schemas = []
565
+
566
+ # extract_causal_variables tool
567
+ schemas.append({
568
+ "type": "function",
569
+ "function": {
570
+ "name": "extract_causal_variables",
571
+ "description": "Extract and propose causal variables, relationships, and counterfactual scenarios needed for causal analysis",
572
+ "parameters": {
573
+ "type": "object",
574
+ "properties": {
575
+ "required_variables": {
576
+ "type": "array",
577
+ "items": {"type": "string"},
578
+ "description": "Core variables that must be included for causal analysis"
579
+ },
580
+ "optional_variables": {
581
+ "type": "array",
582
+ "items": {"type": "string"},
583
+ "description": "Additional variables that may be useful but not essential"
584
+ },
585
+ "causal_edges": {
586
+ "type": "array",
587
+ "items": {
588
+ "type": "array",
589
+ "items": {"type": "string"},
590
+ "minItems": 2,
591
+ "maxItems": 2
592
+ },
593
+ "description": "Causal relationships as [source, target] pairs"
594
+ },
595
+ "counterfactual_variables": {
596
+ "type": "array",
597
+ "items": {"type": "string"},
598
+ "description": "Variables to explore in counterfactual scenarios"
599
+ },
600
+ "reasoning": {
601
+ "type": "string",
602
+ "description": "Explanation of why these variables and relationships are needed"
603
+ }
604
+ },
605
+ "required": ["required_variables", "causal_edges", "reasoning"]
606
+ }
607
+ }
608
+ })
609
+
610
+ # generate_causal_analysis tool
611
+ schemas.append({
612
+ "type": "function",
613
+ "function": {
614
+ "name": "generate_causal_analysis",
615
+ "description": "Generates structured causal reasoning and counterfactual analysis",
616
+ "parameters": {
617
+ "type": "object",
618
+ "properties": {
619
+ "causal_analysis": {
620
+ "type": "string",
621
+ "description": "Analysis of causal relationships and mechanisms"
622
+ },
623
+ "intervention_planning": {
624
+ "type": "string",
625
+ "description": "Planned interventions to test causal hypotheses"
626
+ },
627
+ "counterfactual_scenarios": {
628
+ "type": "array",
629
+ "items": {
630
+ "type": "object",
631
+ "properties": {
632
+ "scenario_name": {"type": "string"},
633
+ "interventions": {"type": "object"},
634
+ "expected_outcomes": {"type": "object"},
635
+ "reasoning": {"type": "string"}
636
+ }
637
+ },
638
+ "description": "Multiple counterfactual scenarios to explore"
639
+ },
640
+ "causal_strength_assessment": {
641
+ "type": "string",
642
+ "description": "Assessment of causal relationship strengths and confounders"
643
+ },
644
+ "optimal_solution": {
645
+ "type": "string",
646
+ "description": "Recommended optimal solution based on causal analysis"
647
+ }
648
+ },
649
+ "required": [
650
+ "causal_analysis",
651
+ "intervention_planning",
652
+ "counterfactual_scenarios",
653
+ "causal_strength_assessment",
654
+ "optimal_solution"
655
+ ]
656
+ }
657
+ }
658
+ })
659
+
660
+ return schemas
661
+
662
+ def _get_file_operations_schemas(self) -> List[Dict[str, Any]]:
663
+ """Get schemas for file operations tools.
664
+
665
+ Returns:
666
+ List of tool schema dictionaries
667
+ """
668
+ schemas = []
669
+
670
+ # write_file tool
671
+ schemas.append({
672
+ "type": "function",
673
+ "function": {
674
+ "name": "write_file",
675
+ "description": "Write content to a file. Creates parent directories if needed.",
676
+ "parameters": {
677
+ "type": "object",
678
+ "properties": {
679
+ "filepath": {
680
+ "type": "string",
681
+ "description": "Path to the file to write"
682
+ },
683
+ "content": {
684
+ "type": "string",
685
+ "description": "Content to write to the file"
686
+ },
687
+ "encoding": {
688
+ "type": "string",
689
+ "default": "utf-8",
690
+ "description": "File encoding (default: utf-8)"
691
+ }
692
+ },
693
+ "required": ["filepath", "content"]
694
+ }
695
+ }
696
+ })
697
+
698
+ # read_file tool
699
+ schemas.append({
700
+ "type": "function",
701
+ "function": {
702
+ "name": "read_file",
703
+ "description": "Read content from a file",
704
+ "parameters": {
705
+ "type": "object",
706
+ "properties": {
707
+ "filepath": {
708
+ "type": "string",
709
+ "description": "Path to the file to read"
710
+ },
711
+ "encoding": {
712
+ "type": "string",
713
+ "default": "utf-8",
714
+ "description": "File encoding (default: utf-8)"
715
+ }
716
+ },
717
+ "required": ["filepath"]
718
+ }
719
+ }
720
+ })
721
+
722
+ # list_directory tool
723
+ schemas.append({
724
+ "type": "function",
725
+ "function": {
726
+ "name": "list_directory",
727
+ "description": "List files and directories in a path",
728
+ "parameters": {
729
+ "type": "object",
730
+ "properties": {
731
+ "directory_path": {
732
+ "type": "string",
733
+ "description": "Path to the directory to list"
734
+ },
735
+ "recursive": {
736
+ "type": "boolean",
737
+ "default": False,
738
+ "description": "Whether to list recursively"
739
+ }
740
+ },
741
+ "required": ["directory_path"]
742
+ }
743
+ }
744
+ })
745
+
746
+ return schemas
747
+
748
+ @staticmethod
749
+ def _get_image_annotation_engine() -> Optional[Any]:
750
+ """Get or create singleton image annotation engine instance.
751
+
752
+ Returns:
753
+ ImageAnnotationEngine instance or None if not available
754
+ """
755
+ global _image_annotation_engine
756
+ if not IMAGE_ANNOTATION_AVAILABLE:
757
+ return None
758
+ if _image_annotation_engine is None:
759
+ try:
760
+ _image_annotation_engine = ImageAnnotationEngine()
761
+ logger.info("Image annotation engine initialized")
762
+ except Exception as e:
763
+ logger.error(f"Failed to initialize image annotation engine: {e}")
764
+ return None
765
+ return _image_annotation_engine
766
+
767
+ def _build_domain_prompt(self, task: str) -> str:
768
+ """Build domain-specific prompt for the given task.
769
+
770
+ Args:
771
+ task: The task description
772
+
773
+ Returns:
774
+ Formatted prompt string
775
+ """
776
+ # Include full conversation history if available
777
+ context = ""
778
+ if hasattr(self, "conversation") and CONVERSATION_AVAILABLE:
779
+ try:
780
+ history = self.conversation.conversation_history
781
+ if history:
782
+ # Get last few messages for context
783
+ recent = history[-5:] if len(history) > 5 else history
784
+ context = "\n\n## Recent Conversation Context\n\n"
785
+ for msg in recent:
786
+ role = msg.get("role", "Unknown")
787
+ content = msg.get("content", "")
788
+ context += f"{role}: {content}\n\n"
789
+ except Exception as e:
790
+ logger.debug(f"Error building conversation context: {e}")
791
+
792
+ prompt = f"{task}\n{context}"
793
+ return prompt
794
+
795
+ def _domain_specific_setup(self) -> None:
796
+ """Set up domain-specific attributes and integrations."""
797
+ # Set up tools list
798
+ tools_list = []
799
+
800
+ if AGENT_DISCOVERY_AVAILABLE and self.config.enable_agent_routing:
801
+ tools_list.extend(self._get_agent_discovery_schemas())
802
+
803
+ if TOOL_DISCOVERY_AVAILABLE:
804
+ tools_list.extend(self._get_tool_discovery_schemas())
805
+
806
+ # Add causal reasoning tools if enabled and available
807
+ if self.config.enable_causal_reasoning and CRCA_AVAILABLE:
808
+ tools_list.extend(self._get_causal_reasoning_schemas())
809
+ # Initialize CRCAAgent instance for causal reasoning (lazy)
810
+ if not hasattr(self, '_crca_agent') or self._crca_agent is None:
811
+ try:
812
+ self._crca_agent = CRCAAgent(
813
+ agent_name=f"{self.agent_name}-crca",
814
+ model_name=self.model_name,
815
+ use_crca_tools=False, # We're providing tools directly
816
+ use_image_annotation=False, # Handled separately
817
+ )
818
+ logger.debug("CRCAAgent instance created for causal reasoning")
819
+ except Exception as e:
820
+ logger.warning(f"Failed to create CRCAAgent instance: {e}")
821
+ self._crca_agent = None
822
+
823
+ # Initialize tools list if needed
824
+ if not hasattr(self, 'tools') or self.tools is None:
825
+ self.tools = []
826
+
827
+ # Add causal reasoning tool handlers
828
+ if self._crca_agent is not None:
829
+ def extract_causal_variables(
830
+ required_variables: List[str],
831
+ causal_edges: List[List[str]],
832
+ reasoning: str,
833
+ optional_variables: Optional[List[str]] = None,
834
+ counterfactual_variables: Optional[List[str]] = None,
835
+ ) -> Dict[str, Any]:
836
+ """Tool handler for extract_causal_variables."""
837
+ try:
838
+ return self._crca_agent._extract_causal_variables_handler(
839
+ required_variables=required_variables,
840
+ causal_edges=causal_edges,
841
+ reasoning=reasoning,
842
+ optional_variables=optional_variables,
843
+ counterfactual_variables=counterfactual_variables,
844
+ )
845
+ except Exception as e:
846
+ logger.error(f"Error in extract_causal_variables tool: {e}")
847
+ return {"error": str(e)}
848
+
849
+ def generate_causal_analysis(
850
+ causal_analysis: str,
851
+ intervention_planning: str,
852
+ counterfactual_scenarios: List[Dict[str, Any]],
853
+ causal_strength_assessment: str,
854
+ optimal_solution: str,
855
+ ) -> Dict[str, Any]:
856
+ """Tool handler for generate_causal_analysis."""
857
+ try:
858
+ return self._crca_agent._generate_causal_analysis_handler(
859
+ causal_analysis=causal_analysis,
860
+ intervention_planning=intervention_planning,
861
+ counterfactual_scenarios=counterfactual_scenarios,
862
+ causal_strength_assessment=causal_strength_assessment,
863
+ optimal_solution=optimal_solution,
864
+ )
865
+ except Exception as e:
866
+ logger.error(f"Error in generate_causal_analysis tool: {e}")
867
+ return {"error": str(e)}
868
+
869
+ self.add_tool(extract_causal_variables)
870
+ self.add_tool(generate_causal_analysis)
871
+ logger.info("Causal reasoning tools added to GeneralAgent")
872
+
873
+ # Add file operations tools if enabled and available
874
+ if self.config.enable_file_operations and FILE_OPERATIONS_AVAILABLE:
875
+ tools_list.extend(self._get_file_operations_schemas())
876
+
877
+ # Initialize file manager
878
+ if not hasattr(self, '_file_manager') or self._file_manager is None:
879
+ try:
880
+ self._file_manager = IntelligentFileManager()
881
+ logger.debug("File manager initialized")
882
+ except Exception as e:
883
+ logger.warning(f"Failed to initialize file manager: {e}")
884
+ self._file_manager = None
885
+
886
+ # Initialize tools list if needed
887
+ if not hasattr(self, 'tools') or self.tools is None:
888
+ self.tools = []
889
+
890
+ # Add file operations tool handlers
891
+ if self._file_manager is not None:
892
+ def write_file(
893
+ filepath: str,
894
+ content: str,
895
+ encoding: str = "utf-8"
896
+ ) -> Dict[str, Any]:
897
+ """Tool handler for write_file."""
898
+ try:
899
+ return self._file_manager.writer.write_file(
900
+ filepath=filepath,
901
+ content=content,
902
+ encoding=encoding
903
+ )
904
+ except Exception as e:
905
+ logger.error(f"Error in write_file tool: {e}")
906
+ return {"success": False, "error": str(e)}
907
+
908
+ def read_file(
909
+ filepath: str,
910
+ encoding: str = "utf-8"
911
+ ) -> Dict[str, Any]:
912
+ """Tool handler for read_file."""
913
+ try:
914
+ from pathlib import Path
915
+ path = Path(filepath)
916
+ if not path.exists():
917
+ return {"success": False, "error": f"File not found: {filepath}"}
918
+
919
+ with open(path, 'r', encoding=encoding) as f:
920
+ content = f.read()
921
+
922
+ return {
923
+ "success": True,
924
+ "filepath": str(path),
925
+ "content": content,
926
+ "size": len(content.encode(encoding))
927
+ }
928
+ except Exception as e:
929
+ logger.error(f"Error in read_file tool: {e}")
930
+ return {"success": False, "error": str(e)}
931
+
932
+ def list_directory(
933
+ directory_path: str,
934
+ recursive: bool = False
935
+ ) -> Dict[str, Any]:
936
+ """Tool handler for list_directory."""
937
+ try:
938
+ from pathlib import Path
939
+ path = Path(directory_path)
940
+ if not path.exists():
941
+ return {"success": False, "error": f"Directory not found: {directory_path}"}
942
+ if not path.is_dir():
943
+ return {"success": False, "error": f"Path is not a directory: {directory_path}"}
944
+
945
+ if recursive:
946
+ files = [str(p.relative_to(path)) for p in path.rglob('*') if p.is_file()]
947
+ dirs = [str(p.relative_to(path)) for p in path.rglob('*') if p.is_dir()]
948
+ else:
949
+ files = [p.name for p in path.iterdir() if p.is_file()]
950
+ dirs = [p.name for p in path.iterdir() if p.is_dir()]
951
+
952
+ return {
953
+ "success": True,
954
+ "directory": str(path),
955
+ "files": files,
956
+ "directories": dirs,
957
+ "total_files": len(files),
958
+ "total_directories": len(dirs)
959
+ }
960
+ except Exception as e:
961
+ logger.error(f"Error in list_directory tool: {e}")
962
+ return {"success": False, "error": str(e)}
963
+
964
+ self.add_tool(write_file)
965
+ self.add_tool(read_file)
966
+ self.add_tool(list_directory)
967
+ logger.info("File operations tools added to GeneralAgent")
968
+
969
+ # Add image annotation tools if multimodal is enabled and available
970
+ if self.config.enable_multimodal and IMAGE_ANNOTATION_AVAILABLE:
971
+ tools_list.extend(self._get_image_annotation_schemas())
972
+ # Initialize image annotation engine (lazy singleton)
973
+ self._image_annotation_engine = self._get_image_annotation_engine()
974
+
975
+ # Initialize tools list if needed
976
+ if not hasattr(self, 'tools') or self.tools is None:
977
+ self.tools = []
978
+
979
+ # Add tool handlers only if engine is available
980
+ if self._image_annotation_engine is not None:
981
+ # Add annotate_image handler
982
+ def annotate_image(
983
+ image_path: str,
984
+ output_format: str = "all",
985
+ frame_id: Optional[int] = None
986
+ ) -> Dict[str, Any]:
987
+ """Tool handler for annotate_image."""
988
+ engine = GeneralAgent._get_image_annotation_engine()
989
+ if engine is None:
990
+ return {"error": "Image annotation engine not available"}
991
+ try:
992
+ result = engine.annotate(image_path, frame_id=frame_id, output=output_format)
993
+ if output_format == "overlay":
994
+ return {"overlay_image": "numpy array returned", "shape": str(result.shape) if hasattr(result, 'shape') else "unknown"}
995
+ elif output_format == "json":
996
+ return result
997
+ elif output_format == "report":
998
+ return {"report": result}
999
+ else: # all
1000
+ return {
1001
+ "entities": len(result.annotation_graph.entities),
1002
+ "labels": len(result.annotation_graph.labels),
1003
+ "contradictions": len(result.annotation_graph.contradictions),
1004
+ "processing_time": result.processing_time,
1005
+ "formal_report": result.formal_report[:500] + "..." if len(result.formal_report) > 500 else result.formal_report,
1006
+ "json_summary": {k: str(v)[:200] for k, v in list(result.json_output.items())[:5]}
1007
+ }
1008
+ except Exception as e:
1009
+ logger.error(f"Error in annotate_image tool: {e}")
1010
+ return {"error": str(e)}
1011
+
1012
+ # Add query_image handler
1013
+ def query_image(
1014
+ image_path: str,
1015
+ query: str,
1016
+ frame_id: Optional[int] = None
1017
+ ) -> Dict[str, Any]:
1018
+ """Tool handler for query_image."""
1019
+ engine = GeneralAgent._get_image_annotation_engine()
1020
+ if engine is None:
1021
+ return {"error": "Image annotation engine not available"}
1022
+ try:
1023
+ result = engine.query(image_path, query, frame_id=frame_id)
1024
+ return {
1025
+ "answer": result["answer"],
1026
+ "entities_found": len(result["entities"]),
1027
+ "measurements": result["measurements"],
1028
+ "confidence": result["confidence"],
1029
+ "reasoning": result["reasoning"][:500] + "..." if len(result["reasoning"]) > 500 else result["reasoning"]
1030
+ }
1031
+ except Exception as e:
1032
+ logger.error(f"Error in query_image tool: {e}")
1033
+ return {"error": str(e)}
1034
+
1035
+ # Add tools to agent
1036
+ self.add_tool(annotate_image)
1037
+ self.add_tool(query_image)
1038
+
1039
+ # Re-initialize tool_struct after adding tools (similar to CRCAAgent)
1040
+ if hasattr(self, 'setup_tools'):
1041
+ try:
1042
+ self.tool_struct = self.setup_tools()
1043
+ except Exception as e:
1044
+ logger.debug(f"Error setting up tools: {e}")
1045
+
1046
+ logger.info("Image annotation tools added to GeneralAgent")
1047
+
1048
+ # Re-initialize tool_struct after adding all tools
1049
+ if hasattr(self, 'setup_tools') and self.tools:
1050
+ try:
1051
+ self.tool_struct = self.setup_tools()
1052
+ except Exception as e:
1053
+ logger.debug(f"Error setting up tools: {e}")
1054
+
1055
+ if tools_list:
1056
+ # Update tools_list_dictionary (BaseSpecializedAgent initializes it as a list)
1057
+ if hasattr(self, "tools_list_dictionary"):
1058
+ if self.tools_list_dictionary:
1059
+ # Extend existing tools
1060
+ self.tools_list_dictionary.extend(tools_list)
1061
+ else:
1062
+ # Set new tools list
1063
+ self.tools_list_dictionary = tools_list
1064
+ else:
1065
+ # Initialize if not present
1066
+ self.tools_list_dictionary = tools_list
1067
+
1068
+ # Update short_memory with tools if available
1069
+ if hasattr(self, "short_memory") and self.short_memory:
1070
+ try:
1071
+ self.short_memory.add(
1072
+ role=self.agent_name,
1073
+ content=tools_list,
1074
+ )
1075
+ except Exception as e:
1076
+ logger.debug(f"Error adding tools to short_memory: {e}")
1077
+
1078
+ # Set up rate limiting
1079
+ if RATE_LIMITER_AVAILABLE:
1080
+ config = RateLimitConfig(
1081
+ requests_per_minute=self.config.rate_limit_rpm,
1082
+ requests_per_hour=self.config.rate_limit_rph,
1083
+ )
1084
+ self.rate_limiter = RateLimiter(config)
1085
+ else:
1086
+ self.rate_limiter = None
1087
+
1088
+ # Set up conversation persistence
1089
+ if CONVERSATION_AVAILABLE and self.config.enable_persistence:
1090
+ try:
1091
+ self.conversation = Conversation(
1092
+ name=self.agent_name,
1093
+ autosave=True,
1094
+ save_filepath=self.config.persistence_path,
1095
+ )
1096
+ except Exception as e:
1097
+ logger.warning(f"Failed to initialize conversation persistence: {e}")
1098
+ self.conversation = None
1099
+ else:
1100
+ self.conversation = None
1101
+
1102
+ # Set up formatter
1103
+ if FORMATTER_AVAILABLE:
1104
+ self.formatter = Formatter(md=True)
1105
+ else:
1106
+ self.formatter = None
1107
+
1108
+ # Auto-discover AOP/router if needed
1109
+ if self.config.enable_agent_routing == "auto" or self.config.enable_agent_routing is True:
1110
+ if AGENT_DISCOVERY_AVAILABLE:
1111
+ if self._aop_instance is None:
1112
+ aop_instances = discover_aop_instances()
1113
+ if aop_instances:
1114
+ self._aop_instance = aop_instances[0]
1115
+ logger.debug(f"Auto-discovered AOP instance: {self._aop_instance}")
1116
+
1117
+ if self._router_instance is None:
1118
+ router_instances = discover_router_instances()
1119
+ if router_instances:
1120
+ self._router_instance = router_instances[0]
1121
+ logger.debug(f"Auto-discovered router instance: {self._router_instance}")
1122
+
1123
+ # Set up batch processor
1124
+ if BATCH_PROCESSOR_AVAILABLE:
1125
+ self.batch_processor = BatchProcessor(
1126
+ max_workers=4,
1127
+ rate_limiter=self.rate_limiter,
1128
+ )
1129
+ else:
1130
+ self.batch_processor = None
1131
+
1132
+ logger.debug("Domain-specific setup complete")
1133
+
1134
+ def _comprehensive_error_handler(
1135
+ self,
1136
+ task: str,
1137
+ max_retries: int = 3,
1138
+ retry_delay: float = 1.0,
1139
+ ) -> str:
1140
+ """Comprehensive error handling with retry, fallback, and user guidance.
1141
+
1142
+ Args:
1143
+ task: Task to execute
1144
+ max_retries: Maximum number of retries
1145
+ retry_delay: Initial retry delay (exponential backoff)
1146
+
1147
+ Returns:
1148
+ Response string
1149
+ """
1150
+ last_error = None
1151
+
1152
+ for attempt in range(max_retries):
1153
+ try:
1154
+ # Apply rate limiting
1155
+ if self.rate_limiter:
1156
+ user_id = getattr(self, "_user_id", "default")
1157
+ is_allowed, error_msg = self.rate_limiter.check_rate_limit(user_id)
1158
+ if not is_allowed:
1159
+ # Wait if rate limited
1160
+ self.rate_limiter.wait_if_rate_limited(user_id, max_wait=60.0)
1161
+
1162
+ # Try to route to specialized agent first (route-first strategy)
1163
+ if AGENT_DISCOVERY_AVAILABLE and self.config.enable_agent_routing:
1164
+ try:
1165
+ available_agents = discover_all_agents(
1166
+ aop_instances=[self._aop_instance] if self._aop_instance else None,
1167
+ router_instances=[self._router_instance] if self._router_instance else None,
1168
+ )
1169
+
1170
+ if available_agents:
1171
+ best_agent = find_best_agent_for_task(
1172
+ task,
1173
+ available_agents,
1174
+ aop_instances=[self._aop_instance] if self._aop_instance else None,
1175
+ router_instances=[self._router_instance] if self._router_instance else None,
1176
+ )
1177
+
1178
+ if best_agent:
1179
+ agent_name, agent_instance, source = best_agent
1180
+ logger.info(f"Routing task to specialized agent: {agent_name} (source: {source})")
1181
+ result = route_to_agent(
1182
+ agent_name,
1183
+ task,
1184
+ aop_instances=[self._aop_instance] if self._aop_instance else None,
1185
+ router_instances=[self._router_instance] if self._router_instance else None,
1186
+ )
1187
+ if result:
1188
+ return str(result)
1189
+ except Exception as e:
1190
+ logger.debug(f"Agent routing failed, falling back to direct handling: {e}")
1191
+
1192
+ # Direct handling (pass through img, imgs, streaming_callback if available)
1193
+ run_kwargs = {}
1194
+ if hasattr(self, "_current_img") and self._current_img:
1195
+ run_kwargs["img"] = self._current_img
1196
+ if hasattr(self, "_current_imgs") and self._current_imgs:
1197
+ run_kwargs["imgs"] = self._current_imgs
1198
+ if hasattr(self, "_current_streaming_callback") and self._current_streaming_callback:
1199
+ run_kwargs["streaming_callback"] = self._current_streaming_callback
1200
+ if hasattr(self, "_current_kwargs") and self._current_kwargs:
1201
+ run_kwargs.update(self._current_kwargs)
1202
+
1203
+ response = super().run(task, **run_kwargs)
1204
+
1205
+ # Save to conversation if persistence enabled
1206
+ if self.conversation:
1207
+ try:
1208
+ self.conversation.add("User", task)
1209
+ self.conversation.add(self.agent_name, response)
1210
+ except Exception as e:
1211
+ logger.debug(f"Error saving to conversation: {e}")
1212
+
1213
+ return response
1214
+
1215
+ except Exception as e:
1216
+ last_error = e
1217
+ logger.warning(f"Attempt {attempt + 1}/{max_retries} failed: {e}")
1218
+
1219
+ if attempt < max_retries - 1:
1220
+ # Exponential backoff
1221
+ wait_time = retry_delay * (2 ** attempt)
1222
+ time.sleep(wait_time)
1223
+ else:
1224
+ # Last attempt failed, try fallback
1225
+ try:
1226
+ # Fallback: simpler approach
1227
+ fallback_prompt = f"Please provide a helpful response to: {task}"
1228
+ response = super().run(fallback_prompt)
1229
+ return f"[Fallback Response] {response}"
1230
+ except Exception as fallback_error:
1231
+ # Ask user for guidance
1232
+ error_msg = f"Failed after {max_retries} retries. Last error: {str(last_error)}. Fallback also failed: {str(fallback_error)}"
1233
+ return f"[Error] {error_msg}. Please provide more details or try rephrasing your request."
1234
+
1235
+ return f"[Error] Failed to process task after {max_retries} attempts: {str(last_error)}"
1236
+
1237
+ def run(
1238
+ self,
1239
+ task: Optional[Union[str, Any]] = None,
1240
+ img: Optional[str] = None,
1241
+ imgs: Optional[List[str]] = None,
1242
+ streaming_callback: Optional[Callable[[str], None]] = None,
1243
+ **kwargs,
1244
+ ) -> Any:
1245
+ """Run the agent with comprehensive error handling.
1246
+
1247
+ Args:
1248
+ task: Task to execute
1249
+ img: Optional image input
1250
+ imgs: Optional list of images
1251
+ streaming_callback: Optional streaming callback
1252
+ **kwargs: Additional arguments
1253
+
1254
+ Returns:
1255
+ Agent response
1256
+ """
1257
+ if task is None:
1258
+ task = ""
1259
+
1260
+ # Store img/imgs for use in error handler if needed
1261
+ self._current_img = img
1262
+ self._current_imgs = imgs
1263
+ self._current_streaming_callback = streaming_callback
1264
+ self._current_kwargs = kwargs
1265
+
1266
+ # Use comprehensive error handler
1267
+ return self._comprehensive_error_handler(str(task))
1268
+
1269
+ async def run_async(
1270
+ self,
1271
+ task: Optional[Union[str, Any]] = None,
1272
+ img: Optional[str] = None,
1273
+ imgs: Optional[List[str]] = None,
1274
+ **kwargs,
1275
+ ) -> Any:
1276
+ """Run the agent asynchronously.
1277
+
1278
+ Args:
1279
+ task: Task to execute
1280
+ img: Optional image input
1281
+ imgs: Optional list of images
1282
+ **kwargs: Additional arguments
1283
+
1284
+ Returns:
1285
+ Agent response
1286
+ """
1287
+ if task is None:
1288
+ task = ""
1289
+
1290
+ # Run in executor to avoid blocking
1291
+ try:
1292
+ loop = asyncio.get_running_loop()
1293
+ except RuntimeError:
1294
+ # No running loop, create new one
1295
+ loop = asyncio.new_event_loop()
1296
+ asyncio.set_event_loop(loop)
1297
+
1298
+ return await loop.run_in_executor(None, self.run, task, img, imgs, **kwargs)
1299
+
1300
+ def run_batch(
1301
+ self,
1302
+ tasks: List[str],
1303
+ task_ids: Optional[List[str]] = None,
1304
+ user_id: str = "default",
1305
+ ) -> Tuple[List[Any], Any]:
1306
+ """Process a batch of tasks.
1307
+
1308
+ Args:
1309
+ tasks: List of tasks to process
1310
+ task_ids: Optional list of task identifiers
1311
+ user_id: User identifier for rate limiting
1312
+
1313
+ Returns:
1314
+ Tuple of (results, stats)
1315
+ """
1316
+ if not BATCH_PROCESSOR_AVAILABLE or not self.batch_processor:
1317
+ # Fallback: process sequentially
1318
+ results = [self.run(task) for task in tasks]
1319
+ return results, None
1320
+
1321
+ return self.batch_processor.process_batch(
1322
+ tasks=tasks,
1323
+ task_fn=self.run,
1324
+ task_ids=task_ids,
1325
+ user_id=user_id,
1326
+ )
1327
+
1328
+ async def run_batch_async(
1329
+ self,
1330
+ tasks: List[str],
1331
+ task_ids: Optional[List[str]] = None,
1332
+ user_id: str = "default",
1333
+ ) -> Tuple[List[Any], Any]:
1334
+ """Process a batch of tasks asynchronously.
1335
+
1336
+ Args:
1337
+ tasks: List of tasks to process
1338
+ task_ids: Optional list of task identifiers
1339
+ user_id: User identifier for rate limiting
1340
+
1341
+ Returns:
1342
+ Tuple of (results, stats)
1343
+ """
1344
+ if not BATCH_PROCESSOR_AVAILABLE or not self.batch_processor:
1345
+ # Fallback: process concurrently
1346
+ results = await asyncio.gather(*[self.run_async(task) for task in tasks])
1347
+ return results, None
1348
+
1349
+ return await self.batch_processor.process_batch_async(
1350
+ tasks=tasks,
1351
+ task_fn=self.run_async,
1352
+ task_ids=task_ids,
1353
+ user_id=user_id,
1354
+ )
1355
+
1356
+ def save_conversation(self, filepath: Optional[str] = None) -> None:
1357
+ """Save conversation to file.
1358
+
1359
+ Args:
1360
+ filepath: Optional filepath (uses default if None)
1361
+ """
1362
+ if not self.conversation:
1363
+ logger.warning("Conversation persistence not enabled")
1364
+ return
1365
+
1366
+ try:
1367
+ if filepath:
1368
+ self.conversation.save_filepath = filepath
1369
+
1370
+ if hasattr(self.conversation, "save_with_metadata"):
1371
+ self.conversation.save_with_metadata(force=True)
1372
+ else:
1373
+ self.conversation.export(force=True)
1374
+
1375
+ logger.info(f"Conversation saved to {self.conversation.save_filepath}")
1376
+ except Exception as e:
1377
+ logger.error(f"Failed to save conversation: {e}")
1378
+
1379
+ def load_conversation(self, filepath: str) -> None:
1380
+ """Load conversation from file.
1381
+
1382
+ Args:
1383
+ filepath: Filepath to load from
1384
+ """
1385
+ if not CONVERSATION_AVAILABLE:
1386
+ logger.warning("Conversation utilities not available")
1387
+ return
1388
+
1389
+ try:
1390
+ if not self.conversation:
1391
+ self.conversation = Conversation(
1392
+ name=self.agent_name,
1393
+ load_filepath=filepath,
1394
+ )
1395
+ else:
1396
+ self.conversation.load(filepath)
1397
+
1398
+ logger.info(f"Conversation loaded from {filepath}")
1399
+ except Exception as e:
1400
+ logger.error(f"Failed to load conversation: {e}")