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.
- CRCA.py +172 -7
- MODEL_CARD.md +53 -0
- PKG-INFO +8 -2
- RELEASE_NOTES.md +17 -0
- STABILITY.md +19 -0
- architecture/hybrid/consistency_engine.py +362 -0
- architecture/hybrid/conversation_manager.py +421 -0
- architecture/hybrid/explanation_generator.py +452 -0
- architecture/hybrid/few_shot_learner.py +533 -0
- architecture/hybrid/graph_compressor.py +286 -0
- architecture/hybrid/hybrid_agent.py +4398 -0
- architecture/hybrid/language_compiler.py +623 -0
- architecture/hybrid/main,py +0 -0
- architecture/hybrid/reasoning_tracker.py +322 -0
- architecture/hybrid/self_verifier.py +524 -0
- architecture/hybrid/task_decomposer.py +567 -0
- architecture/hybrid/text_corrector.py +341 -0
- benchmark_results/crca_core_benchmarks.json +178 -0
- branches/crca_sd/crca_sd_realtime.py +6 -2
- branches/general_agent/__init__.py +102 -0
- branches/general_agent/general_agent.py +1400 -0
- branches/general_agent/personality.py +169 -0
- branches/general_agent/utils/__init__.py +19 -0
- branches/general_agent/utils/prompt_builder.py +170 -0
- {crca-1.4.0.dist-info → crca-1.5.0.dist-info}/METADATA +8 -2
- {crca-1.4.0.dist-info → crca-1.5.0.dist-info}/RECORD +303 -20
- crca_core/__init__.py +35 -0
- crca_core/benchmarks/__init__.py +14 -0
- crca_core/benchmarks/synthetic_scm.py +103 -0
- crca_core/core/__init__.py +23 -0
- crca_core/core/api.py +120 -0
- crca_core/core/estimate.py +208 -0
- crca_core/core/godclass.py +72 -0
- crca_core/core/intervention_design.py +174 -0
- crca_core/core/lifecycle.py +48 -0
- crca_core/discovery/__init__.py +9 -0
- crca_core/discovery/tabular.py +193 -0
- crca_core/identify/__init__.py +171 -0
- crca_core/identify/backdoor.py +39 -0
- crca_core/identify/frontdoor.py +48 -0
- crca_core/identify/graph.py +106 -0
- crca_core/identify/id_algorithm.py +43 -0
- crca_core/identify/iv.py +48 -0
- crca_core/models/__init__.py +67 -0
- crca_core/models/provenance.py +56 -0
- crca_core/models/refusal.py +39 -0
- crca_core/models/result.py +83 -0
- crca_core/models/spec.py +151 -0
- crca_core/models/validation.py +68 -0
- crca_core/scm/__init__.py +9 -0
- crca_core/scm/linear_gaussian.py +198 -0
- crca_core/timeseries/__init__.py +6 -0
- crca_core/timeseries/pcmci.py +181 -0
- crca_llm/__init__.py +12 -0
- crca_llm/client.py +85 -0
- crca_llm/coauthor.py +118 -0
- crca_llm/orchestrator.py +289 -0
- crca_llm/types.py +21 -0
- crca_reasoning/__init__.py +16 -0
- crca_reasoning/critique.py +54 -0
- crca_reasoning/godclass.py +206 -0
- crca_reasoning/memory.py +24 -0
- crca_reasoning/rationale.py +10 -0
- crca_reasoning/react_controller.py +81 -0
- crca_reasoning/tool_router.py +97 -0
- crca_reasoning/types.py +40 -0
- crca_sd/__init__.py +15 -0
- crca_sd/crca_sd_core.py +2 -0
- crca_sd/crca_sd_governance.py +2 -0
- crca_sd/crca_sd_mpc.py +2 -0
- crca_sd/crca_sd_realtime.py +2 -0
- crca_sd/crca_sd_tui.py +2 -0
- cuda-keyring_1.1-1_all.deb +0 -0
- cuda-keyring_1.1-1_all.deb.1 +0 -0
- docs/IMAGE_ANNOTATION_USAGE.md +539 -0
- docs/INSTALL_DEEPSPEED.md +125 -0
- docs/api/branches/crca-cg.md +19 -0
- docs/api/branches/crca-q.md +27 -0
- docs/api/branches/crca-sd.md +37 -0
- docs/api/branches/general-agent.md +24 -0
- docs/api/branches/overview.md +19 -0
- docs/api/crca/agent-methods.md +62 -0
- docs/api/crca/operations.md +79 -0
- docs/api/crca/overview.md +32 -0
- docs/api/image-annotation/engine.md +52 -0
- docs/api/image-annotation/overview.md +17 -0
- docs/api/schemas/annotation.md +34 -0
- docs/api/schemas/core-schemas.md +82 -0
- docs/api/schemas/overview.md +32 -0
- docs/api/schemas/policy.md +30 -0
- docs/api/utils/conversation.md +22 -0
- docs/api/utils/graph-reasoner.md +32 -0
- docs/api/utils/overview.md +21 -0
- docs/api/utils/router.md +19 -0
- docs/api/utils/utilities.md +97 -0
- docs/architecture/causal-graphs.md +41 -0
- docs/architecture/data-flow.md +29 -0
- docs/architecture/design-principles.md +33 -0
- docs/architecture/hybrid-agent/components.md +38 -0
- docs/architecture/hybrid-agent/consistency.md +26 -0
- docs/architecture/hybrid-agent/overview.md +44 -0
- docs/architecture/hybrid-agent/reasoning.md +22 -0
- docs/architecture/llm-integration.md +26 -0
- docs/architecture/modular-structure.md +37 -0
- docs/architecture/overview.md +69 -0
- docs/architecture/policy-engine-arch.md +29 -0
- docs/branches/crca-cg/corposwarm.md +39 -0
- docs/branches/crca-cg/esg-scoring.md +30 -0
- docs/branches/crca-cg/multi-agent.md +35 -0
- docs/branches/crca-cg/overview.md +40 -0
- docs/branches/crca-q/alternative-data.md +55 -0
- docs/branches/crca-q/architecture.md +71 -0
- docs/branches/crca-q/backtesting.md +45 -0
- docs/branches/crca-q/causal-engine.md +33 -0
- docs/branches/crca-q/execution.md +39 -0
- docs/branches/crca-q/market-data.md +60 -0
- docs/branches/crca-q/overview.md +58 -0
- docs/branches/crca-q/philosophy.md +60 -0
- docs/branches/crca-q/portfolio-optimization.md +66 -0
- docs/branches/crca-q/risk-management.md +102 -0
- docs/branches/crca-q/setup.md +65 -0
- docs/branches/crca-q/signal-generation.md +61 -0
- docs/branches/crca-q/signal-validation.md +43 -0
- docs/branches/crca-sd/core.md +84 -0
- docs/branches/crca-sd/governance.md +53 -0
- docs/branches/crca-sd/mpc-solver.md +65 -0
- docs/branches/crca-sd/overview.md +59 -0
- docs/branches/crca-sd/realtime.md +28 -0
- docs/branches/crca-sd/tui.md +20 -0
- docs/branches/general-agent/overview.md +37 -0
- docs/branches/general-agent/personality.md +36 -0
- docs/branches/general-agent/prompt-builder.md +30 -0
- docs/changelog/index.md +79 -0
- docs/contributing/code-style.md +69 -0
- docs/contributing/documentation.md +43 -0
- docs/contributing/overview.md +29 -0
- docs/contributing/testing.md +29 -0
- docs/core/crcagent/async-operations.md +65 -0
- docs/core/crcagent/automatic-extraction.md +107 -0
- docs/core/crcagent/batch-prediction.md +80 -0
- docs/core/crcagent/bayesian-inference.md +60 -0
- docs/core/crcagent/causal-graph.md +92 -0
- docs/core/crcagent/counterfactuals.md +96 -0
- docs/core/crcagent/deterministic-simulation.md +78 -0
- docs/core/crcagent/dual-mode-operation.md +82 -0
- docs/core/crcagent/initialization.md +88 -0
- docs/core/crcagent/optimization.md +65 -0
- docs/core/crcagent/overview.md +63 -0
- docs/core/crcagent/time-series.md +57 -0
- docs/core/schemas/annotation.md +30 -0
- docs/core/schemas/core-schemas.md +82 -0
- docs/core/schemas/overview.md +30 -0
- docs/core/schemas/policy.md +41 -0
- docs/core/templates/base-agent.md +31 -0
- docs/core/templates/feature-mixins.md +31 -0
- docs/core/templates/overview.md +29 -0
- docs/core/templates/templates-guide.md +75 -0
- docs/core/tools/mcp-client.md +34 -0
- docs/core/tools/overview.md +24 -0
- docs/core/utils/conversation.md +27 -0
- docs/core/utils/graph-reasoner.md +29 -0
- docs/core/utils/overview.md +27 -0
- docs/core/utils/router.md +27 -0
- docs/core/utils/utilities.md +97 -0
- docs/css/custom.css +84 -0
- docs/examples/basic-usage.md +57 -0
- docs/examples/general-agent/general-agent-examples.md +50 -0
- docs/examples/hybrid-agent/hybrid-agent-examples.md +56 -0
- docs/examples/image-annotation/image-annotation-examples.md +54 -0
- docs/examples/integration/integration-examples.md +58 -0
- docs/examples/overview.md +37 -0
- docs/examples/trading/trading-examples.md +46 -0
- docs/features/causal-reasoning/advanced-topics.md +101 -0
- docs/features/causal-reasoning/counterfactuals.md +43 -0
- docs/features/causal-reasoning/do-calculus.md +50 -0
- docs/features/causal-reasoning/overview.md +47 -0
- docs/features/causal-reasoning/structural-models.md +52 -0
- docs/features/hybrid-agent/advanced-components.md +55 -0
- docs/features/hybrid-agent/core-components.md +64 -0
- docs/features/hybrid-agent/overview.md +34 -0
- docs/features/image-annotation/engine.md +82 -0
- docs/features/image-annotation/features.md +113 -0
- docs/features/image-annotation/integration.md +75 -0
- docs/features/image-annotation/overview.md +53 -0
- docs/features/image-annotation/quickstart.md +73 -0
- docs/features/policy-engine/doctrine-ledger.md +105 -0
- docs/features/policy-engine/monitoring.md +44 -0
- docs/features/policy-engine/mpc-control.md +89 -0
- docs/features/policy-engine/overview.md +46 -0
- docs/getting-started/configuration.md +225 -0
- docs/getting-started/first-agent.md +164 -0
- docs/getting-started/installation.md +144 -0
- docs/getting-started/quickstart.md +137 -0
- docs/index.md +118 -0
- docs/js/mathjax.js +13 -0
- docs/lrm/discovery_proof_notes.md +25 -0
- docs/lrm/finetune_full.md +83 -0
- docs/lrm/math_appendix.md +120 -0
- docs/lrm/overview.md +32 -0
- docs/mkdocs.yml +238 -0
- docs/stylesheets/extra.css +21 -0
- docs_generated/crca_core/CounterfactualResult.md +12 -0
- docs_generated/crca_core/DiscoveryHypothesisResult.md +13 -0
- docs_generated/crca_core/DraftSpec.md +13 -0
- docs_generated/crca_core/EstimateResult.md +13 -0
- docs_generated/crca_core/IdentificationResult.md +17 -0
- docs_generated/crca_core/InterventionDesignResult.md +12 -0
- docs_generated/crca_core/LockedSpec.md +15 -0
- docs_generated/crca_core/RefusalResult.md +12 -0
- docs_generated/crca_core/ValidationReport.md +9 -0
- docs_generated/crca_core/index.md +13 -0
- examples/general_agent_example.py +277 -0
- examples/general_agent_quickstart.py +202 -0
- examples/general_agent_simple.py +92 -0
- examples/hybrid_agent_auto_extraction.py +84 -0
- examples/hybrid_agent_dictionary_demo.py +104 -0
- examples/hybrid_agent_enhanced.py +179 -0
- examples/hybrid_agent_general_knowledge.py +107 -0
- examples/image_annotation_quickstart.py +328 -0
- examples/test_hybrid_fixes.py +77 -0
- image_annotation/__init__.py +27 -0
- image_annotation/annotation_engine.py +2593 -0
- install_cuda_wsl2.sh +59 -0
- install_deepspeed.sh +56 -0
- install_deepspeed_simple.sh +87 -0
- mkdocs.yml +252 -0
- ollama/Modelfile +8 -0
- prompts/__init__.py +2 -1
- prompts/default_crca.py +9 -1
- prompts/general_agent.py +227 -0
- prompts/image_annotation.py +56 -0
- pyproject.toml +17 -2
- requirements-docs.txt +10 -0
- requirements.txt +21 -2
- schemas/__init__.py +26 -1
- schemas/annotation.py +222 -0
- schemas/conversation.py +193 -0
- schemas/hybrid.py +211 -0
- schemas/reasoning.py +276 -0
- schemas_export/crca_core/CounterfactualResult.schema.json +108 -0
- schemas_export/crca_core/DiscoveryHypothesisResult.schema.json +113 -0
- schemas_export/crca_core/DraftSpec.schema.json +635 -0
- schemas_export/crca_core/EstimateResult.schema.json +113 -0
- schemas_export/crca_core/IdentificationResult.schema.json +145 -0
- schemas_export/crca_core/InterventionDesignResult.schema.json +111 -0
- schemas_export/crca_core/LockedSpec.schema.json +646 -0
- schemas_export/crca_core/RefusalResult.schema.json +90 -0
- schemas_export/crca_core/ValidationReport.schema.json +62 -0
- scripts/build_lrm_dataset.py +80 -0
- scripts/export_crca_core_schemas.py +54 -0
- scripts/export_hf_lrm.py +37 -0
- scripts/export_ollama_gguf.py +45 -0
- scripts/generate_changelog.py +157 -0
- scripts/generate_crca_core_docs_from_schemas.py +86 -0
- scripts/run_crca_core_benchmarks.py +163 -0
- scripts/run_full_finetune.py +198 -0
- scripts/run_lrm_eval.py +31 -0
- templates/graph_management.py +29 -0
- tests/conftest.py +9 -0
- tests/test_core.py +2 -3
- tests/test_crca_core_discovery_tabular.py +15 -0
- tests/test_crca_core_estimate_dowhy.py +36 -0
- tests/test_crca_core_identify.py +18 -0
- tests/test_crca_core_intervention_design.py +36 -0
- tests/test_crca_core_linear_gaussian_scm.py +69 -0
- tests/test_crca_core_spec.py +25 -0
- tests/test_crca_core_timeseries_pcmci.py +15 -0
- tests/test_crca_llm_coauthor.py +12 -0
- tests/test_crca_llm_orchestrator.py +80 -0
- tests/test_hybrid_agent_llm_enhanced.py +556 -0
- tests/test_image_annotation_demo.py +376 -0
- tests/test_image_annotation_operational.py +408 -0
- tests/test_image_annotation_unit.py +551 -0
- tests/test_training_moe.py +13 -0
- training/__init__.py +42 -0
- training/datasets.py +140 -0
- training/deepspeed_zero2_0_5b.json +22 -0
- training/deepspeed_zero2_1_5b.json +22 -0
- training/deepspeed_zero3_0_5b.json +28 -0
- training/deepspeed_zero3_14b.json +28 -0
- training/deepspeed_zero3_h100_3gpu.json +20 -0
- training/deepspeed_zero3_offload.json +28 -0
- training/eval.py +92 -0
- training/finetune.py +516 -0
- training/public_datasets.py +89 -0
- training_data/react_train.jsonl +7473 -0
- utils/agent_discovery.py +311 -0
- utils/batch_processor.py +317 -0
- utils/conversation.py +78 -0
- utils/edit_distance.py +118 -0
- utils/formatter.py +33 -0
- utils/graph_reasoner.py +530 -0
- utils/rate_limiter.py +283 -0
- utils/router.py +2 -2
- utils/tool_discovery.py +307 -0
- webui/__init__.py +10 -0
- webui/app.py +229 -0
- webui/config.py +104 -0
- webui/static/css/style.css +332 -0
- webui/static/js/main.js +284 -0
- webui/templates/index.html +42 -0
- tests/test_crca_excel.py +0 -166
- tests/test_data_broker.py +0 -424
- tests/test_palantir.py +0 -349
- {crca-1.4.0.dist-info → crca-1.5.0.dist-info}/WHEEL +0 -0
- {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}")
|