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,181 @@
|
|
|
1
|
+
"""Time-series causal discovery via Tigramite PCMCI/PCMCI+ (wrap-first).
|
|
2
|
+
|
|
3
|
+
If Tigramite is not installed, this module returns a structured refusal.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
from crca_core.models.provenance import ProvenanceManifest
|
|
13
|
+
from crca_core.models.refusal import RefusalChecklistItem, RefusalReasonCode, RefusalResult
|
|
14
|
+
from crca_core.models.result import DiscoveryHypothesisResult
|
|
15
|
+
from utils.canonical import stable_hash
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PCMCIConfig(BaseModel):
|
|
19
|
+
max_lag: int = Field(default=5, ge=1)
|
|
20
|
+
alpha: float = Field(default=0.05, gt=0.0, lt=1.0)
|
|
21
|
+
variant: Literal["pcmci", "pcmci_plus"] = "pcmci"
|
|
22
|
+
time_index_column: Optional[str] = None
|
|
23
|
+
assume_sorted: bool = False
|
|
24
|
+
min_samples: int = Field(default=200, ge=20)
|
|
25
|
+
notes: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _tigramite_available() -> bool:
|
|
29
|
+
try:
|
|
30
|
+
import importlib.util
|
|
31
|
+
|
|
32
|
+
return importlib.util.find_spec("tigramite") is not None
|
|
33
|
+
except Exception:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def discover_timeseries_pcmci(
|
|
38
|
+
data: Any,
|
|
39
|
+
config: Optional[PCMCIConfig] = None,
|
|
40
|
+
assumptions: Optional[List[str]] = None,
|
|
41
|
+
) -> DiscoveryHypothesisResult | RefusalResult:
|
|
42
|
+
cfg = config or PCMCIConfig()
|
|
43
|
+
assumptions = assumptions or []
|
|
44
|
+
|
|
45
|
+
# Schema-only signature for provenance (no raw data hashing by default).
|
|
46
|
+
schema_sig: Dict[str, Any] = {}
|
|
47
|
+
try:
|
|
48
|
+
import pandas as pd # type: ignore
|
|
49
|
+
|
|
50
|
+
if isinstance(data, pd.DataFrame):
|
|
51
|
+
schema_sig = {c: str(t) for c, t in data.dtypes.items()}
|
|
52
|
+
else:
|
|
53
|
+
schema_sig = {"type": str(type(data))}
|
|
54
|
+
except Exception:
|
|
55
|
+
schema_sig = {"type": str(type(data))}
|
|
56
|
+
|
|
57
|
+
spec_hash = stable_hash({"discovery": "pcmci", "config": cfg.model_dump(), "schema": schema_sig})
|
|
58
|
+
prov = ProvenanceManifest.minimal(
|
|
59
|
+
spec_hash=spec_hash,
|
|
60
|
+
data_hash=stable_hash(schema_sig),
|
|
61
|
+
algorithm_config=cfg.model_dump(),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if not _tigramite_available():
|
|
65
|
+
return RefusalResult(
|
|
66
|
+
message="Time-series causal discovery backend (tigramite) not available.",
|
|
67
|
+
reason_codes=[RefusalReasonCode.UNSUPPORTED_OPERATION],
|
|
68
|
+
checklist=[
|
|
69
|
+
RefusalChecklistItem(
|
|
70
|
+
item="Install tigramite",
|
|
71
|
+
rationale="PCMCI/PCMCI+ discovery is wrap-first; we refuse rather than run unvalidated heuristics.",
|
|
72
|
+
)
|
|
73
|
+
],
|
|
74
|
+
suggested_next_steps=["pip install tigramite"],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
import pandas as pd # type: ignore
|
|
79
|
+
import numpy as np # type: ignore
|
|
80
|
+
from tigramite import data_processing as pp # type: ignore
|
|
81
|
+
from tigramite.pcmci import PCMCI # type: ignore
|
|
82
|
+
from tigramite.independence_tests import ParCorr # type: ignore
|
|
83
|
+
except Exception as e:
|
|
84
|
+
return RefusalResult(
|
|
85
|
+
message=f"Failed to import tigramite: {e}",
|
|
86
|
+
reason_codes=[RefusalReasonCode.UNSUPPORTED_OPERATION],
|
|
87
|
+
checklist=[
|
|
88
|
+
RefusalChecklistItem(
|
|
89
|
+
item="Install tigramite",
|
|
90
|
+
rationale="PCMCI/PCMCI+ discovery requires tigramite backend.",
|
|
91
|
+
)
|
|
92
|
+
],
|
|
93
|
+
suggested_next_steps=["pip install tigramite"],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if not isinstance(data, pd.DataFrame):
|
|
97
|
+
return RefusalResult(
|
|
98
|
+
message="PCMCI requires pandas DataFrame input.",
|
|
99
|
+
reason_codes=[RefusalReasonCode.INPUT_INVALID],
|
|
100
|
+
checklist=[
|
|
101
|
+
RefusalChecklistItem(
|
|
102
|
+
item="Provide pandas DataFrame",
|
|
103
|
+
rationale="Tigramite expects tabular time-indexed data.",
|
|
104
|
+
)
|
|
105
|
+
],
|
|
106
|
+
suggested_next_steps=["Convert your data to pandas.DataFrame and retry."],
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
df = data.copy()
|
|
110
|
+
if cfg.time_index_column:
|
|
111
|
+
if cfg.time_index_column not in df.columns:
|
|
112
|
+
return RefusalResult(
|
|
113
|
+
message="time_index_column not found in data.",
|
|
114
|
+
reason_codes=[RefusalReasonCode.TIME_INDEX_INVALID],
|
|
115
|
+
checklist=[
|
|
116
|
+
RefusalChecklistItem(
|
|
117
|
+
item="Provide valid time index column",
|
|
118
|
+
rationale="PCMCI requires a consistent time index.",
|
|
119
|
+
)
|
|
120
|
+
],
|
|
121
|
+
suggested_next_steps=["Set PCMCIConfig.time_index_column to a valid column."],
|
|
122
|
+
)
|
|
123
|
+
df = df.sort_values(cfg.time_index_column)
|
|
124
|
+
elif not cfg.assume_sorted:
|
|
125
|
+
# If no column specified, require index monotonicity
|
|
126
|
+
try:
|
|
127
|
+
if not df.index.is_monotonic_increasing:
|
|
128
|
+
return RefusalResult(
|
|
129
|
+
message="DataFrame index is not monotonic; provide a time index column or sort data.",
|
|
130
|
+
reason_codes=[RefusalReasonCode.TIME_INDEX_INVALID],
|
|
131
|
+
checklist=[
|
|
132
|
+
RefusalChecklistItem(
|
|
133
|
+
item="Provide time index or sorted data",
|
|
134
|
+
rationale="PCMCI requires time-ordered samples.",
|
|
135
|
+
)
|
|
136
|
+
],
|
|
137
|
+
suggested_next_steps=["Sort by time or set PCMCIConfig.time_index_column."],
|
|
138
|
+
)
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
values = df.to_numpy(dtype=float)
|
|
143
|
+
if values.shape[0] < cfg.min_samples:
|
|
144
|
+
return RefusalResult(
|
|
145
|
+
message="Insufficient samples for PCMCI discovery.",
|
|
146
|
+
reason_codes=[RefusalReasonCode.INPUT_INVALID],
|
|
147
|
+
checklist=[
|
|
148
|
+
RefusalChecklistItem(
|
|
149
|
+
item="Increase sample size",
|
|
150
|
+
rationale=f"Need at least {cfg.min_samples} rows for stable lagged discovery.",
|
|
151
|
+
)
|
|
152
|
+
],
|
|
153
|
+
suggested_next_steps=["Collect more samples or lower min_samples (not recommended)."],
|
|
154
|
+
)
|
|
155
|
+
dataframe = pp.DataFrame(values)
|
|
156
|
+
pcmci = PCMCI(dataframe=dataframe, cond_ind_test=ParCorr())
|
|
157
|
+
|
|
158
|
+
if cfg.variant == "pcmci_plus":
|
|
159
|
+
results = pcmci.run_pcmciplus(tau_max=cfg.max_lag, pc_alpha=cfg.alpha)
|
|
160
|
+
else:
|
|
161
|
+
results = pcmci.run_pcmci(tau_max=cfg.max_lag, pc_alpha=cfg.alpha)
|
|
162
|
+
|
|
163
|
+
graph_hypothesis = {
|
|
164
|
+
"graph_type": "pcmci",
|
|
165
|
+
"method": cfg.variant,
|
|
166
|
+
"max_lag": cfg.max_lag,
|
|
167
|
+
"graph": results.get("graph"),
|
|
168
|
+
"val_matrix": results.get("val_matrix"),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return DiscoveryHypothesisResult(
|
|
172
|
+
provenance=prov,
|
|
173
|
+
assumptions=assumptions,
|
|
174
|
+
limitations=[
|
|
175
|
+
"Time-series discovery is hypothesis generation under explicit assumptions (stationarity, lag sufficiency, no hidden confounding, etc.).",
|
|
176
|
+
"Returned graph depends on CI test assumptions and lag selection.",
|
|
177
|
+
],
|
|
178
|
+
graph_hypothesis=graph_hypothesis,
|
|
179
|
+
stability_report={"alpha": cfg.alpha},
|
|
180
|
+
)
|
|
181
|
+
|
crca_llm/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""crca_llm: non-authoritative LLM coauthor layer.
|
|
2
|
+
|
|
3
|
+
This package is intentionally optional and must never emit numeric causal
|
|
4
|
+
outputs on its own. It drafts specs and checklists only.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from crca_llm.coauthor import CoauthorConfig, DraftBundle, LLMCoauthor
|
|
8
|
+
from crca_llm.orchestrator import LLMOrchestrator
|
|
9
|
+
from crca_llm.types import LLMRunResult
|
|
10
|
+
|
|
11
|
+
__all__ = ["CoauthorConfig", "DraftBundle", "LLMCoauthor", "LLMOrchestrator", "LLMRunResult"]
|
|
12
|
+
|
crca_llm/client.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""OpenAI-compatible client for crca_llm (chat completions only)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
import time
|
|
8
|
+
import random
|
|
9
|
+
from loguru import logger
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MissingApiKeyError(RuntimeError):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class OpenAIClient:
|
|
21
|
+
base_url: str = "https://api.openai.com"
|
|
22
|
+
api_key: Optional[str] = None
|
|
23
|
+
default_model: str = "gpt-4o-mini"
|
|
24
|
+
max_retries: int = 3
|
|
25
|
+
timeout_seconds: int = 60
|
|
26
|
+
enable_audit_log: bool = True
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_env(cls) -> "OpenAIClient":
|
|
30
|
+
base_url = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com")
|
|
31
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
|
32
|
+
model = os.environ.get("OPENAI_MODEL", "gpt-4o-mini")
|
|
33
|
+
return cls(base_url=base_url, api_key=api_key, default_model=model)
|
|
34
|
+
|
|
35
|
+
def _require_key(self) -> None:
|
|
36
|
+
if not self.api_key:
|
|
37
|
+
raise MissingApiKeyError("OPENAI_API_KEY is required for LLM calls.")
|
|
38
|
+
|
|
39
|
+
def chat_completion(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
messages: List[Dict[str, Any]],
|
|
43
|
+
model: Optional[str] = None,
|
|
44
|
+
temperature: float = 0.0,
|
|
45
|
+
max_tokens: int = 800,
|
|
46
|
+
response_format: Optional[Dict[str, Any]] = None,
|
|
47
|
+
) -> str:
|
|
48
|
+
"""Send a chat completion request and return the text response."""
|
|
49
|
+
self._require_key()
|
|
50
|
+
url = self.base_url.rstrip("/") + "/v1/chat/completions"
|
|
51
|
+
payload: Dict[str, Any] = {
|
|
52
|
+
"model": model or self.default_model,
|
|
53
|
+
"messages": messages,
|
|
54
|
+
"temperature": temperature,
|
|
55
|
+
"max_tokens": max_tokens,
|
|
56
|
+
}
|
|
57
|
+
if response_format is not None:
|
|
58
|
+
payload["response_format"] = response_format
|
|
59
|
+
|
|
60
|
+
headers = {
|
|
61
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
}
|
|
64
|
+
attempt = 0
|
|
65
|
+
while True:
|
|
66
|
+
try:
|
|
67
|
+
if self.enable_audit_log:
|
|
68
|
+
logger.info(
|
|
69
|
+
"LLM request model={} tokens={} messages={}",
|
|
70
|
+
payload["model"],
|
|
71
|
+
max_tokens,
|
|
72
|
+
len(messages),
|
|
73
|
+
)
|
|
74
|
+
resp = requests.post(url, json=payload, headers=headers, timeout=self.timeout_seconds)
|
|
75
|
+
resp.raise_for_status()
|
|
76
|
+
data = resp.json()
|
|
77
|
+
return data["choices"][0]["message"]["content"]
|
|
78
|
+
except requests.RequestException as exc:
|
|
79
|
+
attempt += 1
|
|
80
|
+
if attempt > self.max_retries:
|
|
81
|
+
raise
|
|
82
|
+
# exponential backoff with jitter
|
|
83
|
+
sleep_s = (2 ** (attempt - 1)) + random.uniform(0, 0.5)
|
|
84
|
+
time.sleep(sleep_s)
|
|
85
|
+
|
crca_llm/coauthor.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Non-authoritative LLM coauthor layer.
|
|
2
|
+
|
|
3
|
+
This module is deliberately constrained:
|
|
4
|
+
- It may draft candidate `DraftSpec` objects and review checklists.
|
|
5
|
+
- It must never produce numeric causal outputs.
|
|
6
|
+
- It must never create `LockedSpec` objects.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
14
|
+
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
|
|
17
|
+
from crca_core.models.spec import (
|
|
18
|
+
AssumptionItem,
|
|
19
|
+
AssumptionSpec,
|
|
20
|
+
AssumptionStatus,
|
|
21
|
+
CausalGraphSpec,
|
|
22
|
+
DataColumnSpec,
|
|
23
|
+
DataSpec,
|
|
24
|
+
DraftSpec,
|
|
25
|
+
NodeSpec,
|
|
26
|
+
RoleSpec,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CoauthorConfig(BaseModel):
|
|
31
|
+
model: str = Field(default="gpt-4o-mini")
|
|
32
|
+
n_candidates: int = Field(default=3, ge=1, le=5)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DraftBundle(BaseModel):
|
|
36
|
+
"""LLM coauthor output: draft specs + checklists (no numbers)."""
|
|
37
|
+
|
|
38
|
+
drafts: List[DraftSpec]
|
|
39
|
+
review_checklist: List[str] = Field(default_factory=list)
|
|
40
|
+
disclaimers: List[str] = Field(
|
|
41
|
+
default_factory=lambda: [
|
|
42
|
+
"DRAFT-NON-AUTHORITATIVE: This spec is a hypothesis and must be reviewed/locked by a human.",
|
|
43
|
+
"No numeric causal claims are produced by the coauthor layer.",
|
|
44
|
+
]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _default_checklist() -> List[str]:
|
|
49
|
+
return [
|
|
50
|
+
"Define treatment and outcome variables explicitly.",
|
|
51
|
+
"Confirm time ordering (what can cause what).",
|
|
52
|
+
"List plausible confounders and whether they are measured.",
|
|
53
|
+
"Check for collider variables that must not be adjusted for.",
|
|
54
|
+
"Define intervention semantics (set vs shift vs mechanism-change) if counterfactuals are needed.",
|
|
55
|
+
"State discovery assumptions if running causal discovery (faithfulness, causal sufficiency/latent confounding).",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _offline_draft(user_text: str, observed_columns: Optional[Sequence[str]], n: int) -> DraftBundle:
|
|
60
|
+
cols = list(observed_columns or [])
|
|
61
|
+
data_spec = DataSpec(
|
|
62
|
+
columns=[DataColumnSpec(name=c, dtype="unknown") for c in cols],
|
|
63
|
+
measurement_error_notes="unknown",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Minimal placeholder graph: nodes from observed columns (no edges).
|
|
67
|
+
graph = CausalGraphSpec(nodes=[NodeSpec(name=c) for c in cols], edges=[])
|
|
68
|
+
|
|
69
|
+
assumptions = AssumptionSpec(
|
|
70
|
+
items=[
|
|
71
|
+
AssumptionItem(
|
|
72
|
+
name="DRAFT_ONLY",
|
|
73
|
+
status=AssumptionStatus.unknown,
|
|
74
|
+
description="This spec was generated offline without LLM; treat as a template only.",
|
|
75
|
+
)
|
|
76
|
+
],
|
|
77
|
+
falsification_plan=[
|
|
78
|
+
"Collect domain constraints/time ordering and re-run drafting with an LLM (optional).",
|
|
79
|
+
],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
drafts = [
|
|
83
|
+
DraftSpec(
|
|
84
|
+
data=data_spec,
|
|
85
|
+
graph=graph,
|
|
86
|
+
roles=RoleSpec(),
|
|
87
|
+
assumptions=assumptions,
|
|
88
|
+
draft_notes=f"Offline draft template generated from observed columns. User text: {user_text[:200]}",
|
|
89
|
+
)
|
|
90
|
+
for _ in range(n)
|
|
91
|
+
]
|
|
92
|
+
return DraftBundle(drafts=drafts, review_checklist=_default_checklist())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class LLMCoauthor:
|
|
97
|
+
"""Coauthor that can draft candidate specs, but is never authoritative."""
|
|
98
|
+
|
|
99
|
+
config: CoauthorConfig = field(default_factory=CoauthorConfig)
|
|
100
|
+
|
|
101
|
+
def draft_specs(
|
|
102
|
+
self,
|
|
103
|
+
*,
|
|
104
|
+
user_text: str,
|
|
105
|
+
observed_columns: Optional[Sequence[str]] = None,
|
|
106
|
+
) -> DraftBundle:
|
|
107
|
+
"""Draft multiple candidate DraftSpec objects.
|
|
108
|
+
|
|
109
|
+
If no OpenAI API key is configured, returns an offline template bundle.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
if os.environ.get("OPENAI_API_KEY") is None:
|
|
113
|
+
return _offline_draft(user_text, observed_columns, self.config.n_candidates)
|
|
114
|
+
|
|
115
|
+
# Online LLM drafting is intentionally not implemented yet in this repo’s default test environment.
|
|
116
|
+
# We return offline drafts to preserve deterministic behavior and avoid accidental numeric leakage.
|
|
117
|
+
return _offline_draft(user_text, observed_columns, self.config.n_candidates)
|
|
118
|
+
|
crca_llm/orchestrator.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""LLM tool orchestration (DraftSpec-only + gated core calls)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
9
|
+
|
|
10
|
+
from crca_core.core.api import (
|
|
11
|
+
EstimatorConfig,
|
|
12
|
+
FeasibilityConstraints,
|
|
13
|
+
PCMCIConfig,
|
|
14
|
+
TabularDiscoveryConfig,
|
|
15
|
+
TargetQuery,
|
|
16
|
+
discover_tabular,
|
|
17
|
+
discover_timeseries_pcmci,
|
|
18
|
+
design_intervention,
|
|
19
|
+
identify_effect,
|
|
20
|
+
estimate_effect_dowhy,
|
|
21
|
+
simulate_counterfactual,
|
|
22
|
+
)
|
|
23
|
+
from crca_core.models.refusal import RefusalChecklistItem, RefusalReasonCode, RefusalResult
|
|
24
|
+
from crca_core.models.result import AnyResult, IdentificationResult
|
|
25
|
+
from crca_core.models.spec import (
|
|
26
|
+
AssumptionItem,
|
|
27
|
+
AssumptionSpec,
|
|
28
|
+
AssumptionStatus,
|
|
29
|
+
CausalGraphSpec,
|
|
30
|
+
DataColumnSpec,
|
|
31
|
+
DataSpec,
|
|
32
|
+
DraftSpec,
|
|
33
|
+
EdgeSpec,
|
|
34
|
+
LockedSpec,
|
|
35
|
+
NodeSpec,
|
|
36
|
+
RoleSpec,
|
|
37
|
+
)
|
|
38
|
+
from crca_llm.client import MissingApiKeyError, OpenAIClient
|
|
39
|
+
from crca_llm.coauthor import DraftBundle
|
|
40
|
+
from crca_llm.types import LLMRunResult
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _fallback_bundle(user_text: str, observed_columns: Optional[Sequence[str]]) -> DraftBundle:
|
|
44
|
+
cols = list(observed_columns or [])
|
|
45
|
+
data_spec = DataSpec(
|
|
46
|
+
columns=[DataColumnSpec(name=c, dtype="unknown") for c in cols],
|
|
47
|
+
measurement_error_notes="unknown",
|
|
48
|
+
)
|
|
49
|
+
graph = CausalGraphSpec(nodes=[NodeSpec(name=c) for c in cols], edges=[])
|
|
50
|
+
assumptions = AssumptionSpec(
|
|
51
|
+
items=[
|
|
52
|
+
AssumptionItem(
|
|
53
|
+
name="DRAFT_ONLY",
|
|
54
|
+
status=AssumptionStatus.unknown,
|
|
55
|
+
description="Fallback draft used due to LLM parse failure.",
|
|
56
|
+
)
|
|
57
|
+
],
|
|
58
|
+
falsification_plan=["Collect domain constraints/time ordering and re-run drafting."],
|
|
59
|
+
)
|
|
60
|
+
drafts = [
|
|
61
|
+
DraftSpec(
|
|
62
|
+
data=data_spec,
|
|
63
|
+
graph=graph,
|
|
64
|
+
roles=RoleSpec(),
|
|
65
|
+
assumptions=assumptions,
|
|
66
|
+
draft_notes=f"Fallback draft from observed columns. User text: {user_text[:200]}",
|
|
67
|
+
)
|
|
68
|
+
]
|
|
69
|
+
return DraftBundle(drafts=drafts, review_checklist=[])
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _parse_llm_drafts(payload: dict) -> DraftBundle:
|
|
73
|
+
drafts_payload = payload.get("drafts", [])
|
|
74
|
+
drafts: List[DraftSpec] = []
|
|
75
|
+
for item in drafts_payload:
|
|
76
|
+
nodes = [NodeSpec(name=n) for n in item.get("nodes", [])]
|
|
77
|
+
edges = [EdgeSpec(source=a, target=b) for a, b in item.get("edges", [])]
|
|
78
|
+
graph = CausalGraphSpec(nodes=nodes, edges=edges)
|
|
79
|
+
roles = RoleSpec(
|
|
80
|
+
treatments=item.get("treatments", []),
|
|
81
|
+
outcomes=item.get("outcomes", []),
|
|
82
|
+
mediators=item.get("mediators", []),
|
|
83
|
+
instruments=item.get("instruments", []),
|
|
84
|
+
adjustment_candidates=item.get("adjustment_candidates", []),
|
|
85
|
+
prohibited_controls=item.get("prohibited_controls", []),
|
|
86
|
+
)
|
|
87
|
+
data_spec = DataSpec(
|
|
88
|
+
columns=[DataColumnSpec(name=c, dtype="unknown") for c in item.get("columns", [])],
|
|
89
|
+
measurement_error_notes="unknown",
|
|
90
|
+
)
|
|
91
|
+
assumptions = AssumptionSpec(items=[], falsification_plan=[])
|
|
92
|
+
drafts.append(DraftSpec(data=data_spec, graph=graph, roles=roles, assumptions=assumptions))
|
|
93
|
+
checklist = payload.get("review_checklist", [])
|
|
94
|
+
return DraftBundle(drafts=drafts, review_checklist=checklist)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class LLMOrchestrator:
|
|
99
|
+
client: Optional[OpenAIClient] = None
|
|
100
|
+
default_model: str = "gpt-4o-mini"
|
|
101
|
+
enable_audit_log: bool = True
|
|
102
|
+
|
|
103
|
+
def __post_init__(self) -> None:
|
|
104
|
+
env_model = os.getenv("CRCA_MOE_MODEL") or os.getenv("CRCA_LLM_MODEL")
|
|
105
|
+
if env_model:
|
|
106
|
+
self.default_model = env_model
|
|
107
|
+
|
|
108
|
+
def _draft_with_llm(self, user_text: str, observed_columns: Optional[Sequence[str]]) -> DraftBundle:
|
|
109
|
+
client = self.client or OpenAIClient.from_env()
|
|
110
|
+
client.enable_audit_log = self.enable_audit_log
|
|
111
|
+
prompt = {
|
|
112
|
+
"role": "user",
|
|
113
|
+
"content": (
|
|
114
|
+
"You are drafting causal specs. Return JSON with keys: "
|
|
115
|
+
"`drafts` (list), `review_checklist` (list). Each draft has: "
|
|
116
|
+
"`nodes` (list of strings), `edges` (list of [source,target]), "
|
|
117
|
+
"`treatments`, `outcomes`, `mediators`, `instruments`, "
|
|
118
|
+
"`adjustment_candidates`, `prohibited_controls`, `columns`.\n"
|
|
119
|
+
f"User text: {user_text}\n"
|
|
120
|
+
f"Observed columns: {list(observed_columns or [])}\n"
|
|
121
|
+
"Return JSON only."
|
|
122
|
+
),
|
|
123
|
+
}
|
|
124
|
+
try:
|
|
125
|
+
content = client.chat_completion(
|
|
126
|
+
messages=[prompt],
|
|
127
|
+
model=self.default_model,
|
|
128
|
+
response_format={"type": "json_object"},
|
|
129
|
+
)
|
|
130
|
+
except MissingApiKeyError as exc:
|
|
131
|
+
raise
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
payload = json.loads(content)
|
|
135
|
+
return _parse_llm_drafts(payload)
|
|
136
|
+
except Exception:
|
|
137
|
+
return _fallback_bundle(user_text, observed_columns)
|
|
138
|
+
|
|
139
|
+
def run(
|
|
140
|
+
self,
|
|
141
|
+
*,
|
|
142
|
+
user_text: str,
|
|
143
|
+
observed_columns: Optional[Sequence[str]] = None,
|
|
144
|
+
locked_spec: Optional[LockedSpec] = None,
|
|
145
|
+
data: Optional[Any] = None,
|
|
146
|
+
actions: Optional[Sequence[str]] = None,
|
|
147
|
+
target_query: Optional[TargetQuery] = None,
|
|
148
|
+
constraints: Optional[FeasibilityConstraints] = None,
|
|
149
|
+
factual_observation: Optional[Dict[str, float]] = None,
|
|
150
|
+
intervention: Optional[Dict[str, float]] = None,
|
|
151
|
+
) -> LLMRunResult:
|
|
152
|
+
actions = list(actions or [])
|
|
153
|
+
refusals: List[RefusalResult] = []
|
|
154
|
+
core_results: List[AnyResult] = []
|
|
155
|
+
|
|
156
|
+
# Drafting is mandatory and requires API key.
|
|
157
|
+
try:
|
|
158
|
+
draft_bundle = self._draft_with_llm(user_text, observed_columns)
|
|
159
|
+
except MissingApiKeyError as exc:
|
|
160
|
+
refusal = RefusalResult(
|
|
161
|
+
message=str(exc),
|
|
162
|
+
reason_codes=[RefusalReasonCode.INPUT_INVALID],
|
|
163
|
+
checklist=[
|
|
164
|
+
RefusalChecklistItem(
|
|
165
|
+
item="Set OPENAI_API_KEY",
|
|
166
|
+
rationale="LLM orchestration requires an API key.",
|
|
167
|
+
)
|
|
168
|
+
],
|
|
169
|
+
suggested_next_steps=["Export OPENAI_API_KEY and retry."],
|
|
170
|
+
)
|
|
171
|
+
return LLMRunResult(draft_bundle=DraftBundle(drafts=[], review_checklist=[]), refusals=[refusal])
|
|
172
|
+
|
|
173
|
+
if actions and locked_spec is None:
|
|
174
|
+
refusal = RefusalResult(
|
|
175
|
+
message="LockedSpec is required to run core tools.",
|
|
176
|
+
reason_codes=[RefusalReasonCode.SPEC_NOT_LOCKED],
|
|
177
|
+
checklist=[RefusalChecklistItem(item="Provide LockedSpec", rationale="Core tools are gated.")],
|
|
178
|
+
suggested_next_steps=["Lock a spec first, then re-run with locked_spec."],
|
|
179
|
+
)
|
|
180
|
+
refusals.append(refusal)
|
|
181
|
+
return LLMRunResult(draft_bundle=draft_bundle, refusals=refusals)
|
|
182
|
+
|
|
183
|
+
identification_result: IdentificationResult | None = None
|
|
184
|
+
|
|
185
|
+
for action in actions:
|
|
186
|
+
if action == "discover_tabular":
|
|
187
|
+
if data is None:
|
|
188
|
+
refusals.append(
|
|
189
|
+
RefusalResult(
|
|
190
|
+
message="Tabular discovery requires data.",
|
|
191
|
+
reason_codes=[RefusalReasonCode.INPUT_INVALID],
|
|
192
|
+
checklist=[RefusalChecklistItem(item="Provide data", rationale="Missing DataFrame.")],
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
continue
|
|
196
|
+
result = discover_tabular(data, TabularDiscoveryConfig())
|
|
197
|
+
core_results.append(result)
|
|
198
|
+
if isinstance(result, RefusalResult):
|
|
199
|
+
refusals.append(result)
|
|
200
|
+
elif action == "discover_timeseries":
|
|
201
|
+
if data is None:
|
|
202
|
+
refusals.append(
|
|
203
|
+
RefusalResult(
|
|
204
|
+
message="Time-series discovery requires data.",
|
|
205
|
+
reason_codes=[RefusalReasonCode.INPUT_INVALID],
|
|
206
|
+
checklist=[RefusalChecklistItem(item="Provide data", rationale="Missing DataFrame.")],
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
continue
|
|
210
|
+
result = discover_timeseries_pcmci(data, PCMCIConfig())
|
|
211
|
+
core_results.append(result)
|
|
212
|
+
if isinstance(result, RefusalResult):
|
|
213
|
+
refusals.append(result)
|
|
214
|
+
elif action == "design_intervention":
|
|
215
|
+
if target_query is None:
|
|
216
|
+
refusals.append(
|
|
217
|
+
RefusalResult(
|
|
218
|
+
message="Intervention design requires TargetQuery.",
|
|
219
|
+
reason_codes=[RefusalReasonCode.INPUT_INVALID],
|
|
220
|
+
checklist=[RefusalChecklistItem(item="Provide TargetQuery", rationale="Missing target query.")],
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
continue
|
|
224
|
+
result = design_intervention(
|
|
225
|
+
locked_spec=locked_spec,
|
|
226
|
+
target_query=target_query,
|
|
227
|
+
constraints=constraints or FeasibilityConstraints(),
|
|
228
|
+
)
|
|
229
|
+
core_results.append(result)
|
|
230
|
+
elif action == "identify":
|
|
231
|
+
result = identify_effect(
|
|
232
|
+
locked_spec=locked_spec,
|
|
233
|
+
treatment=locked_spec.roles.treatments[0] if locked_spec.roles.treatments else "",
|
|
234
|
+
outcome=locked_spec.roles.outcomes[0] if locked_spec.roles.outcomes else "",
|
|
235
|
+
)
|
|
236
|
+
core_results.append(result)
|
|
237
|
+
if isinstance(result, RefusalResult):
|
|
238
|
+
refusals.append(result)
|
|
239
|
+
else:
|
|
240
|
+
identification_result = result
|
|
241
|
+
elif action == "estimate":
|
|
242
|
+
if data is None:
|
|
243
|
+
refusals.append(
|
|
244
|
+
RefusalResult(
|
|
245
|
+
message="Estimation requires data.",
|
|
246
|
+
reason_codes=[RefusalReasonCode.INPUT_INVALID],
|
|
247
|
+
checklist=[RefusalChecklistItem(item="Provide data", rationale="Missing DataFrame.")],
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
continue
|
|
251
|
+
result = estimate_effect_dowhy(
|
|
252
|
+
data=data,
|
|
253
|
+
locked_spec=locked_spec,
|
|
254
|
+
treatment=locked_spec.roles.treatments[0] if locked_spec.roles.treatments else "",
|
|
255
|
+
outcome=locked_spec.roles.outcomes[0] if locked_spec.roles.outcomes else "",
|
|
256
|
+
identification_result=identification_result,
|
|
257
|
+
config=EstimatorConfig(),
|
|
258
|
+
)
|
|
259
|
+
core_results.append(result)
|
|
260
|
+
if isinstance(result, RefusalResult):
|
|
261
|
+
refusals.append(result)
|
|
262
|
+
elif action == "counterfactual":
|
|
263
|
+
if factual_observation is None or intervention is None:
|
|
264
|
+
refusals.append(
|
|
265
|
+
RefusalResult(
|
|
266
|
+
message="Counterfactual requires factual_observation and intervention.",
|
|
267
|
+
reason_codes=[RefusalReasonCode.INPUT_INVALID],
|
|
268
|
+
checklist=[
|
|
269
|
+
RefusalChecklistItem(item="Provide factual_observation", rationale="Missing factual data."),
|
|
270
|
+
RefusalChecklistItem(item="Provide intervention", rationale="Missing intervention."),
|
|
271
|
+
],
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
continue
|
|
275
|
+
result = simulate_counterfactual(
|
|
276
|
+
locked_spec=locked_spec,
|
|
277
|
+
factual_observation=factual_observation,
|
|
278
|
+
intervention=intervention,
|
|
279
|
+
)
|
|
280
|
+
core_results.append(result)
|
|
281
|
+
if isinstance(result, RefusalResult):
|
|
282
|
+
refusals.append(result)
|
|
283
|
+
|
|
284
|
+
return LLMRunResult(
|
|
285
|
+
draft_bundle=draft_bundle,
|
|
286
|
+
core_results=core_results,
|
|
287
|
+
refusals=refusals,
|
|
288
|
+
)
|
|
289
|
+
|