Source code for pycsamt.agents

"""AI-assisted MT, AMT, and CSAMT workflow automation.

All agents are **lazy-loaded**: importing this module costs nothing unless
you actually instantiate an agent.  This keeps the base pycsamt import fast
even when LLM libraries (``anthropic``, ``openai``, ``google-generativeai``)
are not installed.

Agent catalogue
---------------
:class:`ContextInputAgent`
    Translate a natural-language request into a structured workflow config.
:class:`MTLoaderAgent`
    Load EDI / AVG / J files into a :class:`~pycsamt.site.Sites` object
    with a per-station QC report.
:class:`AgentCoordinator`
    Chain agents into named workflows with checkpointing and cost tracking.
:class:`DataQCAgent`
    SNR section, dead-band detection, per-station quality scores.
:class:`StaticShiftAgent`
    Static-shift detection and correction (AMA / LOESS / spatial median).
:class:`PhaseAnalysisAgent`
    Phase tensor, strike, dimensionality, Mohr circles, Argand diagrams.
:class:`ForwardModelAgent`
    1-D, 2-D, and 3-D MT forward modelling.
:class:`InversionPrepAgent` / :class:`Occam2DAgent` / :class:`ModEmAgent` / :class:`Mare2DEMAgent`
    Write Occam2D, ModEM3D, or MARE2DEM inversion input files; optionally run
    the binary and scan results.
:class:`InversionEvaluationAgent`
    Compute RMS, residual PT section, misfit pseudosection.
:class:`InterpretationAgent`
    Map resistivity ranges to lithology; correlate with borehole logs.
:class:`ReportAgent`
    Assemble all figures and tables into Markdown / HTML / PDF.
:class:`CodeGenerationAgent`
    Emit a standalone reproducible Python script from the workflow config.
:class:`WorkflowOrchestratorAgent`
    NL → classify workflow → build and run the correct agent chain.
:class:`AgentMaster`
    One-line front door over the orchestrator:
    ``AgentMaster(provider="anthropic").run("...")``.
:class:`DenoisingAgent`
    RPCA / Hampel / EMAP / AI-CAE denoising.
:class:`AnomalyDetectionAgent`
    Unsupervised CAE anomaly flagging per (station, frequency).
:class:`AIInversionAgent`
    End-to-end 1-D AI inversion (ResNet / CNN / FCN).
:class:`Inv2DAgent`
    U-Net 2-D profile inversion with lateral continuity.
:class:`Inv3DAgent`
    GCN 3-D spatial inversion using inter-station graph message-passing.
:class:`EnsembleAgent`
    Ensemble 1-D inversion with conformal uncertainty bands.
:class:`JointInversionAgent`
    DRCNN multi-modal joint inversion (MT + TEM / CSAMT / gravity).
:class:`ModelZooAgent`
    Browse, download, and deploy pre-trained EM inverter checkpoints.
:class:`PINNInversionAgent`
    Physics-informed 1-D/2-D/3-D MT inversion via Adam
    (no labelled training data required).
:class:`HybridInversionAgent`
    Two-stage AI warm-start + physics refinement for
    1-D, 2-D, and 3-D MT inversion.
:class:`IoTFieldAgent`
    Monitor an IoT-enabled AMT/MT/CSAMT/CSEM field acquisition from its edge
    telemetry: monitoring status, sync/power summaries, provenance manifest,
    and dashboard figures.

Supporting classes
------------------
:class:`AgentResult`
    Standardised return type for every agent.
:class:`BaseAgent`
    Abstract base class; inherit to build custom agents.

Quick start
-----------
Without an LLM key (regex fallback, no cost)::

    from pycsamt.agents import ContextInputAgent, MTLoaderAgent, AgentCoordinator

    ctx    = ContextInputAgent()   # no api_key → pure regex
    loader = MTLoaderAgent()

    coord  = AgentCoordinator("mt_qc")
    coord.add_step("parse", ctx,
                   description="Parse request into config")
    coord.add_step("load",  loader,
                   input_fn=lambda r: {"path": r["parse"]["config"]["data_path"]},
                   description="Load EDI files and QC scan")

    result = coord.execute(
        {"request": "Load /data/EDIs, QC, period range 1e-4 to 1 s"},
        dry_run=True,
    )

With a Claude API key::

    ctx = ContextInputAgent(api_key="sk-ant-…", llm_provider="claude")
    res = ctx.execute({"request": "Load L22PLT EDIs, run PT analysis …"})
    print(res["config"])
    print(res.llm_interpretation)

LLM providers supported
-----------------------
* **Anthropic Claude** (default) — ``llm_provider="claude"``
* **OpenAI**                      — ``llm_provider="openai"``
* **Google Gemini**               — ``llm_provider="gemini"``
* **DeepSeek**                    — ``llm_provider="deepseek"``
* **MiniMax**                     — ``llm_provider="minimax"``

Install the relevant client library before use:
``pip install anthropic`` / ``pip install openai`` /
``pip install google-generativeai``
DeepSeek and MiniMax reuse the ``openai`` package with a
custom ``base_url``; no extra install is needed.
"""

from __future__ import annotations

import importlib
from typing import TYPE_CHECKING

from ..api.agents import (
    AGENT_CONFIG,
    AgentConfig,
    BudgetExceededError,
    configure_agents,
    reset_agents,
)

# ── always-available, zero-cost exports ──────────────────────────────────────
from ._base import AgentResult, BaseAgent
from ._pricing import (
    PROVIDER_RATES,
    estimate_cost,
    format_cost,
    get_rate,
)
from ._workflow_plan import (
    VALID_WORKFLOWS,
    WorkflowPlan,
    validate_workflow_plan,
)
from .coordinator import AgentCoordinator, WorkflowStep

# ── lazy agent map ────────────────────────────────────────────────────────────
_LAZY: dict[str, str] = {
    "AgentMaster": ".master",
    "ContextInputAgent": ".context",
    "IntentRouter": ".router",
    "PackageQAAgent": ".package_qa",
    "MTLoaderAgent": ".loader",
    "DataQCAgent": ".qc",
    "StaticShiftAgent": ".static_shift",
    "PhaseAnalysisAgent": ".phase_analysis",
    "ForwardModelAgent": ".forward",
    "InversionPrepAgent": ".inversion_prep",
    "InversionEvaluationAgent": ".inversion_eval",
    "InterpretationAgent": ".interpretation",
    "ReportAgent": ".report",
    "CodeGenerationAgent": ".code_gen",
    "WorkflowOrchestratorAgent": ".orchestrator",
    "DenoisingAgent": ".denoising",
    "AIInversionAgent": ".ai_inversion",
    "Occam2DAgent": ".occam2d_agent",
    "ModEmAgent": ".modem_agent",
    "AnomalyDetectionAgent": ".anomaly_agent",
    "Inv2DAgent": ".inv2d_agent",
    "Inv3DAgent": ".inv3d_agent",
    "EnsembleAgent": ".ensemble_agent",
    "JointInversionAgent": ".joint_agent",
    "ModelZooAgent": ".model_zoo_agent",
    # new agents
    "TensorRotationAgent": ".tensor_rotation",
    "EDIExportAgent": ".edi_export",
    "TipperAnalysisAgent": ".tipper_analysis",
    "SensitivityAgent": ".sensitivity",
    "FrequencyDecimationAgent": ".freq_decimation",
    "InversionComparisonAgent": ".inversion_comparison",
    "ResistivityMapAgent": ".resistivity_map",
    "BatchSurveyAgent": ".batch_survey",
    "InversionBackendAgent": ".inversion_backend",
    "PipelineAgent": ".pipeline_agent",
    "Mare2DEMAgent": ".mare2dem_agent",
    "PINNInversionAgent": ".pinn_agent",
    "HybridInversionAgent": ".hybrid_agent",
    "IoTFieldAgent": ".iot_agent",
}


def __getattr__(name: str):
    if name in _LAZY:
        module = importlib.import_module(_LAZY[name], package=__name__)
        obj = getattr(module, name)
        globals()[name] = obj  # cache — skip import machinery on next access
        return obj
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


if TYPE_CHECKING:
    from .ai_inversion import AIInversionAgent
    from .anomaly_agent import AnomalyDetectionAgent
    from .batch_survey import BatchSurveyAgent
    from .code_gen import CodeGenerationAgent
    from .context import ContextInputAgent
    from .denoising import DenoisingAgent
    from .edi_export import EDIExportAgent
    from .ensemble_agent import EnsembleAgent
    from .forward import ForwardModelAgent
    from .freq_decimation import FrequencyDecimationAgent
    from .hybrid_agent import HybridInversionAgent
    from .interpretation import InterpretationAgent
    from .inv2d_agent import Inv2DAgent
    from .inv3d_agent import Inv3DAgent
    from .inversion_backend import InversionBackendAgent
    from .inversion_comparison import InversionComparisonAgent
    from .inversion_eval import InversionEvaluationAgent
    from .inversion_prep import InversionPrepAgent
    from .iot_agent import IoTFieldAgent
    from .joint_agent import JointInversionAgent
    from .loader import MTLoaderAgent
    from .mare2dem_agent import Mare2DEMAgent
    from .master import AgentMaster
    from .model_zoo_agent import ModelZooAgent
    from .modem_agent import ModEmAgent
    from .occam2d_agent import Occam2DAgent
    from .orchestrator import WorkflowOrchestratorAgent
    from .package_qa import PackageQAAgent
    from .phase_analysis import PhaseAnalysisAgent
    from .pinn_agent import PINNInversionAgent
    from .pipeline_agent import PipelineAgent
    from .qc import DataQCAgent
    from .report import ReportAgent
    from .resistivity_map import ResistivityMapAgent
    from .router import IntentRouter, RouterDecision
    from .sensitivity import SensitivityAgent
    from .static_shift import StaticShiftAgent
    from .tensor_rotation import TensorRotationAgent
    from .tipper_analysis import TipperAnalysisAgent


__version__ = "2.0.0rc1"

__all__ = [
    # global LLM config
    "AGENT_CONFIG",
    "AgentConfig",
    "BudgetExceededError",
    "configure_agents",
    "reset_agents",
    # core infrastructure
    "AgentResult",
    "BaseAgent",
    "AgentCoordinator",
    "WorkflowStep",
    # pricing helpers
    "estimate_cost",
    "format_cost",
    "get_rate",
    "PROVIDER_RATES",
    # workflow plan
    "WorkflowPlan",
    "validate_workflow_plan",
    "VALID_WORKFLOWS",
    # agents — alphabetical within logical groups
    "AgentMaster",
    "ContextInputAgent",
    "IntentRouter",
    "PackageQAAgent",
    "MTLoaderAgent",
    "DataQCAgent",
    "StaticShiftAgent",
    "PhaseAnalysisAgent",
    "ForwardModelAgent",
    "InversionPrepAgent",
    "InversionEvaluationAgent",
    "InterpretationAgent",
    "ReportAgent",
    "CodeGenerationAgent",
    "WorkflowOrchestratorAgent",
    "DenoisingAgent",
    "AIInversionAgent",
    "AnomalyDetectionAgent",
    "Occam2DAgent",
    "ModEmAgent",
    "Inv2DAgent",
    "Inv3DAgent",
    "EnsembleAgent",
    "JointInversionAgent",
    "ModelZooAgent",
    "TensorRotationAgent",
    "EDIExportAgent",
    "TipperAnalysisAgent",
    "SensitivityAgent",
    "FrequencyDecimationAgent",
    "InversionComparisonAgent",
    "ResistivityMapAgent",
    "BatchSurveyAgent",
    "InversionBackendAgent",
    "PipelineAgent",
    "Mare2DEMAgent",
    "PINNInversionAgent",
    "HybridInversionAgent",
    "IoTFieldAgent",
]