2.26. pycsamt.agents#

AI-assisted workflow agents for loading, quality control, processing, inversion preparation, interpretation, reporting, and orchestration.

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.

2.26.1. Agent catalogue#

ContextInputAgent

Translate a natural-language request into a structured workflow config.

MTLoaderAgent

Load EDI / AVG / J files into a Sites object with a per-station QC report.

AgentCoordinator

Chain agents into named workflows with checkpointing and cost tracking.

DataQCAgent

SNR section, dead-band detection, per-station quality scores.

StaticShiftAgent

Static-shift detection and correction (AMA / LOESS / spatial median).

PhaseAnalysisAgent

Phase tensor, strike, dimensionality, Mohr circles, Argand diagrams.

ForwardModelAgent

1-D, 2-D, and 3-D MT forward modelling.

InversionPrepAgent / Occam2DAgent / ModEmAgent / Mare2DEMAgent

Write Occam2D, ModEM3D, or MARE2DEM inversion input files; optionally run the binary and scan results.

InversionEvaluationAgent

Compute RMS, residual PT section, misfit pseudosection.

InterpretationAgent

Map resistivity ranges to lithology; correlate with borehole logs.

ReportAgent

Assemble all figures and tables into Markdown / HTML / PDF.

CodeGenerationAgent

Emit a standalone reproducible Python script from the workflow config.

WorkflowOrchestratorAgent

NL → classify workflow → build and run the correct agent chain.

AgentMaster

One-line front door over the orchestrator: AgentMaster(provider="anthropic").run("...").

DenoisingAgent

RPCA / Hampel / EMAP / AI-CAE denoising.

AnomalyDetectionAgent

Unsupervised CAE anomaly flagging per (station, frequency).

AIInversionAgent

End-to-end 1-D AI inversion (ResNet / CNN / FCN).

Inv2DAgent

U-Net 2-D profile inversion with lateral continuity.

Inv3DAgent

GCN 3-D spatial inversion using inter-station graph message-passing.

EnsembleAgent

Ensemble 1-D inversion with conformal uncertainty bands.

JointInversionAgent

DRCNN multi-modal joint inversion (MT + TEM / CSAMT / gravity).

ModelZooAgent

Browse, download, and deploy pre-trained EM inverter checkpoints.

PINNInversionAgent

Physics-informed 1-D/2-D/3-D MT inversion via Adam (no labelled training data required).

HybridInversionAgent

Two-stage AI warm-start + physics refinement for 1-D, 2-D, and 3-D MT inversion.

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.

2.26.2. Supporting classes#

AgentResult

Standardised return type for every agent.

BaseAgent

Abstract base class; inherit to build custom agents.

2.26.3. 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)

2.26.4. LLM providers supported#

  • Anthropic Claude (default) — llm_provider="claude"

  • OpenAIllm_provider="openai"

  • Google Geminillm_provider="gemini"

  • DeepSeekllm_provider="deepseek"

  • MiniMaxllm_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.

class pycsamt.agents.AgentConfig#

Bases: object

Global LLM configuration singleton for all pycsamt agents.

BaseAgent calls resolve() on every instantiation and get_rate() + _add_spend() on every LLM call, so keys, rates, and the budget cap set here apply automatically to every agent in the session.

:param (none — use configure() after construction):

Variables:
  • provider (str or None) – Currently active LLM provider.

  • model (str or None) – Resolved model (explicit override or provider default).

  • api_key (str or None) – Resolved key for the active provider (stored key → env var → None).

  • is_configured (bool) – True when a provider and a resolvable key are both present.

  • spent_usd (float) – Accumulated LLM spend this session.

  • remaining_usd (float or None) – Remaining budget, or None when no cap is set.

Examples

from pycsamt.api.agents import AGENT_CONFIG

# one-call setup
AGENT_CONFIG.configure(provider="claude", api_key="sk-ant-…")

# pricing — override a rate (USD / 1 M tokens)
AGENT_CONFIG.set_rate(
    "claude", "claude-opus-4-8", input=12.0, output=60.0
)

# pricing — add a model not in the built-in table
AGENT_CONFIG.set_rate("openai", "gpt-5-turbo", input=5.0, output=20.0)

# budget cap
AGENT_CONFIG.set_budget(usd=5.0)
print(AGENT_CONFIG.remaining_usd)  # 5.0

# inspect
print(AGENT_CONFIG.info())

# reset
AGENT_CONFIG.reset()
configure(*, provider, api_key, model=None)#

Set the active provider, key, and optional model in one call.

Parameters:
  • provider ({"claude", "openai", "gemini"}) – LLM provider to activate.

  • api_key (str) – API key for provider. Stored per-provider so switching away and back does not require re-supplying the key.

  • model (str or None) – Model identifier override. None uses the provider default.

Returns:

self — allows chaining.

Return type:

AgentConfig

Examples

AGENT_CONFIG.configure(provider="claude", api_key="sk-ant-…")
AGENT_CONFIG.configure(
    provider="openai",
    api_key="sk-…",
    model="gpt-4o-mini",
)
set_key(provider, api_key)#

Store an API key for provider without changing the active provider.

Useful for pre-loading keys for multiple providers so you can switch() between them without re-supplying credentials.

Parameters:
  • provider (str)

  • api_key (str)

Returns:

self.

Return type:

AgentConfig

Examples

AGENT_CONFIG.set_key("claude", "sk-ant-…")
AGENT_CONFIG.set_key("openai", "sk-…")
AGENT_CONFIG.set_key("gemini", "AIza…")
AGENT_CONFIG.switch("claude")
switch(provider, *, model=None)#

Switch the active provider.

The key for provider must have been stored via configure() or set_key(), or be available as an environment variable.

Parameters:
  • provider (str)

  • model (str or None) – Override the model for this provider. None keeps the current model override (or uses the provider default).

Returns:

self.

Return type:

AgentConfig

Examples

AGENT_CONFIG.switch("openai")
AGENT_CONFIG.switch("gemini", model="gemini-2.0-flash")
reset(*, keys=True)#

Clear the active configuration, custom rates, and budget.

Parameters:

keys (bool) – When True (default) also wipe all stored per-provider keys. Pass False to clear only the active provider / model while keeping stored keys available for future switch() calls.

Returns:

self.

Return type:

AgentConfig

Notes

Custom rates and the budget cap/counter are always reset. Call reset_budget() to zero only the spend counter.

set_rate(provider, model, *, input, output)#

Override or add the cost rate for a specific provider + model.

Custom rates take precedence over the built-in table for every cost estimate made by agents in this session.

Parameters:
  • provider (str) – "claude" | "openai" | "gemini"

  • model (str) – Model identifier exactly as passed to the LLM client, e.g. "claude-opus-4-8" or "gpt-5-turbo".

  • input (float) – Cost per 1 000 000 input tokens in USD.

  • output (float) – Cost per 1 000 000 output tokens in USD.

Returns:

self.

Return type:

AgentConfig

Examples

# provider lowered their price
AGENT_CONFIG.set_rate(
    "claude", "claude-opus-4-8", input=12.0, output=60.0
)

# a new model not yet in the built-in table
AGENT_CONFIG.set_rate(
    "openai", "gpt-5-turbo", input=5.0, output=20.0
)
get_rate(provider, model)#

Return the resolved {"input": …, "output": …} rate.

Resolution order: custom override → built-in table (exact match → prefix match) → provider default.

Parameters:
Return type:

dict with keys "input" and "output" (USD / 1 M tokens).

Examples

rate = AGENT_CONFIG.get_rate("claude", "claude-sonnet-4-6")
# {"input": 3.0, "output": 15.0}
estimate_cost(provider, model, input_tokens, output_tokens)#

Compute the USD cost for one LLM call using the resolved rate.

Respects any rate overrides set via set_rate().

Parameters:
  • provider (str)

  • model (str)

  • input_tokens (int)

  • output_tokens (int)

Return type:

float — estimated cost in USD.

Examples

cost = AGENT_CONFIG.estimate_cost(
    "claude", "claude-sonnet-4-6", 500, 200
)
list_rates(provider=None)#

Return the effective rate table (custom overrides merged with built-in).

Parameters:

provider (str or None) – When given, return only that provider’s table. When None, return all providers.

Return type:

dict — same structure as the built-in table.

Examples

import pprint

pprint.pprint(AGENT_CONFIG.list_rates("claude"))
set_budget(*, usd)#

Set a session spend cap.

Once spent_usd reaches usd, any subsequent LLM call will raise BudgetExceededError before the API call is made.

Parameters:

usd (float) – Maximum spend in USD for this session.

Returns:

self.

Return type:

AgentConfig

Examples

AGENT_CONFIG.set_budget(usd=2.0)
# … after several agent calls …
print(
    f"${AGENT_CONFIG.spent_usd:.4f} used, "
    f"${AGENT_CONFIG.remaining_usd:.4f} left"
)
reset_budget(*, cap=False)#

Reset the session spend counter.

Parameters:

cap (bool) – When True also remove the budget cap (set_budget must be called again to re-enable it). Default False — zeroes the counter but keeps the existing cap.

Returns:

self.

Return type:

AgentConfig

Examples

AGENT_CONFIG.reset_budget()  # zero counter, keep cap
AGENT_CONFIG.reset_budget(cap=True)  # zero counter and remove cap
property spent_usd: float#

Accumulated LLM spend this session (USD).

property remaining_usd: float | None#

Remaining budget (USD), or None when no cap is set.

property provider: str | None#

Currently active provider, or None if unconfigured.

property api_key: str | None#

Resolved key for the active provider.

Resolution order: stored key → environment variable → None.

property model: str | None#

Active model (explicit override, or provider default).

property is_configured: bool#

True when a provider and a resolvable key are both present.

resolve(provider, api_key, model)#

Resolve the effective (provider, api_key, model) for an agent.

Called automatically by __init__. Users should not call this directly.

2.26. Resolution rules#

If api_key is explicitly given:

Use provider, api_key, and model (or provider default) exactly as supplied. The global config is ignored.

If api_key is None:
  • If provider equals the default "claude" and the global config has an active provider, inherit that provider.

  • Look up the key: stored key → env var.

  • Inherit the model from the global config when the effective provider matches the globally active provider.

Parameters:
  • provider (str)

  • api_key (str | None)

  • model (str | None)

Return type:

tuple[str, str | None, str | None]

info()#

Return a summary dict suitable for display or logging.

The API key is masked to its last four characters for safety.

Returns:

Keys: provider, model, has_key, key_source, stored_providers, custom_rate_models, budget_usd, spent_usd, remaining_usd.

Return type:

dict

using(*, provider=None, api_key=None, model=None)#

Temporarily override the global config inside a with block.

All arguments are optional; omitted values are left unchanged. The original config (including custom rates and budget) is restored on exit even if an exception occurs.

Parameters:
  • provider (str or None)

  • api_key (str or None)

  • model (str or None)

Yields:

AgentConfigself with the override applied.

Return type:

Generator[AgentConfig, None, None]

Examples

with AGENT_CONFIG.using(provider="gemini", api_key="AIza…"):
    r = DataQCAgent().execute(data)
# original provider / key / model restored here
offline()#

Context manager: force truly offline mode in the current thread.

While active, _resolve_key() will not inspect environment variables, so agents created inside the block receive api_key=None even when ANTHROPIC_API_KEY (or similar) is set in the OS environment. Uses threading.local so concurrent requests in other threads are unaffected.

Examples

with AGENT_CONFIG.offline():
    agent = DataQCAgent()  # api_key will be None
Return type:

Generator[AgentConfig, None, None]

exception pycsamt.agents.BudgetExceededError(spent, budget)#

Bases: RuntimeError

Raised when an LLM call would be made after the session budget is used.

Variables:
  • spent_usd (float) – Accumulated spend so far this session.

  • budget_usd (float) – The cap that was set.

Parameters:
Return type:

None

pycsamt.agents.configure_agents(*, provider, api_key, model=None)#

Configure AGENT_CONFIG in one call and return it.

Equivalent to AGENT_CONFIG.configure(...).

Parameters:
  • provider ({"claude", "openai", "gemini"})

  • api_key (str)

  • model (str or None)

Return type:

AgentConfig

Examples

from pycsamt.agents import configure_agents

configure_agents(provider="claude", api_key="sk-ant-…")
pycsamt.agents.reset_agents(*, keys=True)#

Reset AGENT_CONFIG to its unconfigured state.

Equivalent to AGENT_CONFIG.reset(...).

Parameters:

keys (bool) – When True (default) also wipe stored per-provider keys.

Return type:

AgentConfig

class pycsamt.agents.AgentResult(status, summary, data=<factory>, warnings=<factory>, llm_interpretation=None, elapsed_seconds=0.0, cost_estimate_usd=0.0, error=None, error_fix_hint=None)#

Bases: object

Standardised output returned by every pycsamt agent.

Variables:
  • status (str) – "success" | "failed" | "needs_review"

  • summary (str) – One-sentence human-readable description of what happened.

  • data (dict) – Agent-specific outputs (arrays, paths, dataframes, figures …).

  • warnings (list of str) – Non-fatal issues encountered during execution.

  • llm_interpretation (str or None) – Free-text interpretation written by the LLM, when available.

  • elapsed_seconds (float)

  • cost_estimate_usd (float) – Estimated LLM API cost for this execution.

  • error (str or None) – Exception message if status == "failed".

  • error_fix_hint (str or None) – Suggested remediation for the error.

Parameters:

Examples

>>> result = some_agent.execute({"path": "/data/EDIs"})
>>> result.status
'success'
>>> result["sites"]  # dict-like access to data
<Sites 25 stations>
>>> result.get("n_stations", 0)
25
status: str#
summary: str#
data: dict[str, Any]#
warnings: list[str]#
llm_interpretation: str | None = None#
elapsed_seconds: float = 0.0#
cost_estimate_usd: float = 0.0#
error: str | None = None#
error_fix_hint: str | None = None#
get(key, default=None)#
Parameters:
Return type:

Any

classmethod failed(error, *, hint=None, elapsed=0.0)#

Convenience constructor for failure results.

Parameters:
Return type:

AgentResult

class pycsamt.agents.BaseAgent(name, *, api_key=None, model=None, llm_provider='claude', section_preset='pseudosection', verbose=False)#

Bases: ABC

Abstract base class for all pycsamt agents.

Parameters:
  • name (str) – Human-readable agent name used in logs and reports.

  • api_key (str or None) – LLM API key. When None the agent runs without LLM support and every llm_interpretation field will be None.

  • model (str or None) – LLM model identifier. Defaults to the provider’s recommended model.

  • llm_provider ({"claude", "openai", "gemini", "deepseek", "minimax"}) – Which provider to use. Default "claude".

  • section_preset (str) – Which PYCSAMT_SECTION preset governs figures produced by this agent. Default "pseudosection".

  • verbose (bool, int, or str) – Progress verbosity forwarded to the underlying dataset-generation and training calls — see pycsamt.utils.progress.normalize_verbose(). Default False (silent), matching this agent framework’s historical behaviour of returning results/logs rather than printing.

Examples

Subclass and implement execute():

class MyAgent(BaseAgent):
    SYSTEM_PROMPT = "You are an expert MT data analyst."

    def execute(self, input_data):
        t0 = time.time()
        # ... do work ...
        interp = self.query_llm("Interpret these results: ...")
        return AgentResult(
            status="success",
            summary="Analysis complete.",
            data={"result": 42},
            llm_interpretation=interp,
            elapsed_seconds=time.time() - t0,
            cost_estimate_usd=self._last_cost,
        )
SYSTEM_PROMPT: str = 'You are a geophysics expert specialising in magnetotelluric (MT/AMT/CSAMT) data processing and interpretation.'#

Override in subclasses to give the LLM its domain expertise.

abstractmethod execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

query_llm(prompt, system_message=None, *, temperature=0.2, max_tokens=1024)#

Send prompt to the configured LLM and return the response text.

Returns None when no API key is configured or all retries fail. Accumulates token cost into self._last_cost.

Parameters:
Return type:

str or None

extract_json(text)#

Extract the first JSON object / array from text.

Returns None when no valid JSON is found.

Parameters:

text (str)

Return type:

dict | list | None

require_keys(input_data, *keys, agent_name=None)#

Return a list of missing required keys.

Parameters:
  • input_data (dict)

  • keys (str)

  • agent_name (str | None)

Return type:

list[str]

class pycsamt.agents.AgentCoordinator(workflow_name='pycsamt_workflow', *, checkpoint_dir=None, verbose=True)#

Bases: object

Orchestrate a sequence of pycsamt agents as a named workflow.

Parameters:
  • workflow_name (str) – Name used for log messages and checkpoint directory.

  • checkpoint_dir (str or Path or None) – Where to save step checkpoints. Defaults to ./pycsamt_agent_checkpoints/<workflow_name>/.

  • verbose (bool) – Print step-level progress to stdout.

Examples

Build and run a QC workflow:

from pycsamt.agents import AgentCoordinator, MTLoaderAgent, DataQCAgent

coord = AgentCoordinator("mt_qc")
coord.add_step("load", MTLoaderAgent(...))
coord.add_step(
    "qc",
    DataQCAgent(...),
    input_fn=lambda r: {"sites": r["load"]["sites"]},
)

result = coord.execute({"path": "/data/EDIs"})
print(result.summary)

Dry-run preview:

result = coord.preview({"path": "/data/EDIs"})
print(result["plan"])
add_step(name, agent, *, input_fn=None, description='', required=True)#

Append a workflow step. Returns self for chaining.

Parameters:
  • name (str)

  • agent (BaseAgent)

  • input_fn (callable(prev_results_dict) → dict, optional) – Maps accumulated results to this step’s input dict. prev_results_dict keys are earlier step names; each value is the step’s AgentResult.

  • description (str)

  • required (bool)

Returns:

self for method chaining.

Return type:

AgentCoordinator

preview(config)#

Return an execution plan and estimated cost without running anything.

Parameters:

config (dict) – The workflow configuration that would be passed to execute().

Returns:

data["plan"] contains the formatted plan string. data["steps"] is a list of step metadata dicts.

Return type:

AgentResult

execute(config, *, dry_run=False, resume=False)#

Run all workflow steps sequentially.

Parameters:
  • config (dict) – Top-level workflow configuration forwarded to every step’s input_fn (or directly to agent.execute() when no input_fn is set).

  • dry_run (bool) – When True return a preview without executing any agents.

  • resume (bool) – When True skip steps whose checkpoint already exists on disk.

Returns:

data contains one key per step name with its AgentResult.

Return type:

AgentResult

reset_checkpoints()#

Delete all saved checkpoints for this workflow.

Return type:

None

class pycsamt.agents.WorkflowStep(name, agent, *, input_fn=None, description='', required=True)#

Bases: object

One step in an AgentCoordinator workflow.

Parameters:
  • name (str) – Unique identifier used for checkpointing and logging.

  • agent (BaseAgent) – The agent instance that runs this step.

  • input_fn (callable or None) – input_fn(prev_results) → dict fed to agent.execute(). When None the coordinator passes the raw workflow config.

  • description (str) – Human-readable description shown in the dry-run preview.

  • required (bool) – When False a failure skips the step rather than aborting.

pycsamt.agents.estimate_cost(provider, model, input_tokens, output_tokens)#

Compute the USD cost for one LLM call.

Delegates to estimate_cost(), so any per-model overrides set via AGENT_CONFIG.set_rate(...) are used.

Parameters:
  • provider (str)

  • model (str)

  • input_tokens (int)

  • output_tokens (int)

Returns:

Estimated cost in USD.

Return type:

float

Examples

>>> estimate_cost("claude", "claude-sonnet-4-6", 500, 200)
0.00451...
pycsamt.agents.format_cost(usd)#

Return a human-readable cost string.

Examples

>>> format_cost(0.00012)
'$0.000120'
>>> format_cost(1.5)
'$1.5000'
Parameters:

usd (float)

Return type:

str

pycsamt.agents.get_rate(provider, model)#

Return the resolved {"input": …, "output": …} rate.

Delegates to get_rate(), so any per-model overrides set via AGENT_CONFIG.set_rate(...) take effect.

Parameters:
  • provider (str) – One of "claude", "openai", "gemini", "deepseek", "minimax".

  • model (str) – Model identifier, e.g. "claude-sonnet-4-6".

Return type:

dict with keys "input" and "output" (USD / 1 M tokens).

class pycsamt.agents.WorkflowPlan(request, workflow_type, data_path='', output_dir='', parameters=<factory>, risk_flags=<factory>, requires_human_review=False, expected_outputs=<factory>, provider='offline', grounding_citations=<factory>)#

Bases: object

Validated intermediate representation of a workflow request.

Produced by ContextInputAgent and consumed by WorkflowOrchestratorAgent.

Parameters:
  • request (str) – Original natural-language request.

  • workflow_type (str) – One of the recognised workflow identifiers (see VALID_WORKFLOWS).

  • data_path (str) – Path to EDI files or survey directory.

  • output_dir (str) – Destination for outputs.

  • parameters (dict) – Parsed parameters (period_range, component, etc.).

  • risk_flags (list of str) – Warnings or risks the user should review.

  • requires_human_review (bool) – True when ambiguous parameters were inferred.

  • expected_outputs (list of str) – Files / artefacts the workflow should produce.

  • provider (str) – LLM provider used for parsing, or 'offline'.

  • grounding_citations (list of str) – Source paths retrieved by RAG to ground this plan, when retrieval was available (empty offline or on a cold index).

request: str#
workflow_type: str#
data_path: str = ''#
output_dir: str = ''#
parameters: dict[str, Any]#
risk_flags: list[str]#
requires_human_review: bool = False#
expected_outputs: list[str]#
provider: str = 'offline'#
grounding_citations: list[str]#
is_valid()#

Return True when the plan passes basic validation.

Return type:

bool

validation_errors()#

Return a list of validation error messages.

Return type:

list[str]

to_dict()#

Return a JSON-serialisable dict.

Return type:

dict[str, Any]

to_json(indent=2)#

Serialise to a JSON string.

Parameters:

indent (int)

Return type:

str

save(path)#

Write the plan to path as JSON.

Parameters:

path (str | Path)

Return type:

None

classmethod from_config(config, request='', provider='offline', citations=None)#

Build a WorkflowPlan from a config dict as returned by ContextInputAgent.

Parameters:
Return type:

WorkflowPlan

pycsamt.agents.validate_workflow_plan(plan, *, raise_on_error=False)#

Validate plan and optionally raise on errors.

Parameters:
Returns:

errors – Empty when the plan is valid.

Return type:

list of str

class pycsamt.agents.AgentMaster(provider='claude', *, api_key=None, model=None, default_workflow='qc')#

Bases: PyCSAMTObject

Plain-language entry point to the agent workflows.

Parameters:
  • provider (str, default "claude") – LLM provider. Friendly aliases are accepted: "anthropic""claude" and "google""gemini". Without an API key the agents fall back to the rule-based (regex/keyword) path, so AgentMaster() works offline.

  • api_key (str, optional) – Provider API key. When omitted, the provider’s environment variable is used if set; otherwise the rule-based fallback runs at zero cost.

  • model (str, optional) – Provider model override (defaults per provider).

  • default_workflow (str, default "qc") – Workflow used when a request cannot be classified.

Examples

Plan first (no files touched), then execute:

>>> from pycsamt.agents import AgentMaster
>>> master = AgentMaster(provider="anthropic")
>>> plan = master.plan(
...     "QC the EDI files and prepare a short report",
...     data_path="data/edi/",
... )
>>> plan["workflow_type"]
'qc'
>>> report = master.run(
...     "Load data/edi/, flag stations with RMS > 2, build an Occam2D "
...     "input for profile L22, launch inversion, and produce a PDF "
...     "report."
... )

See also

pycsamt.agents.WorkflowOrchestratorAgent

The dispatcher this façade drives; use it directly for structured workflow configurations.

property orchestrator: WorkflowOrchestratorAgent#

The lazily-built orchestrator behind this façade.

run(request, *, data_path=None, output_dir=None, dry_run=False, **extra)#

Route request to the right specialist agent and run it.

request is first classified by IntentRouter — a question is answered by PackageQAAgent, a code request generates a script via CodeGenerationAgent, a metrics request is computed by MetricsAgent, and a workflow / plot request runs the full pipeline through WorkflowOrchestratorAgent. A meta (capability) or clarify (ambiguous) request returns immediately with no data path required.

Parameters:
  • request (str) – Plain-language description of what to do. Paths mentioned in the text are extracted when possible; pass data_path / output_dir explicitly for scripts and CI.

  • data_path (str, optional) – Survey input (EDI/AVG/J directory or file).

  • output_dir (str, optional) – Where products (figures, inputs, reports) are written.

  • dry_run (bool, default False) – For workflow / plot requests only: preview the selected chain without reading or writing. Ignored for questions, code, metrics, meta, and clarify requests, which never touch disk regardless.

  • **extra – Additional orchestrator payload fields, passed through for workflow / plot requests only.

Returns:

Status, per-step outputs, reasoning, and cost tracking.

Return type:

AgentResult

plan(request, **kwargs)#

Shortcut for run() with dry_run=True.

Parameters:
  • request (str)

  • kwargs (Any)

Return type:

AgentResult

class pycsamt.agents.ContextInputAgent(*, api_key=None, model=None, llm_provider='claude', use_rag=True)#

Bases: BaseAgent

Parse a natural-language MT workflow request into a structured config.

Parameters:
  • api_key (str or None) – LLM API key. When None the regex fallback is used exclusively.

  • model (str) – Passed to BaseAgent.

  • llm_provider (str) – Passed to BaseAgent.

  • use_rag (bool)

Examples

With an API key:

agent = ContextInputAgent(api_key="sk-ant-…")
result = agent.execute(
    {
        "request": "Load EDIs from /data/L22PLT, QC them, "
        "period range 1e-4 to 1 s, save to /out/qc/"
    }
)
cfg = result["config"]
# cfg["workflow"]    == "qc"
# cfg["data_path"]   == "/data/L22PLT"
# cfg["period_range"] == [0.0001, 1.0]

Without an API key (regex fallback):

agent = ContextInputAgent()  # no key → regex mode
result = agent.execute({"request": "…"})
SYSTEM_PROMPT: str = 'You are an expert MT/AMT/CSAMT workflow configuration interpreter for pycsamt v2.\n\nGiven a natural-language processing request, extract a structured JSON configuration dictionary with the following schema (include only keys that are clearly mentioned or can be reasonably inferred):\n\n{\n  "workflow":       string one of:\n                    qc, static_shift, phase_analysis, forward,\n                    inversion_prep, pre_inversion, inversion_eval,\n                    interpretation, report, full,\n                    ai_inversion, inv1d, inv2d, inv3d,\n                    ensemble_inversion, joint_inversion,\n                    modem, mare2dem, occam2d,\n                    tipper, sensitivity, rotation,\n                    freq_decimation, batch, comparison,\n                    full_ai_workflow,\n  "data_path":      string absolute or relative path to EDI\n                    file(s) or directory,\n  "output_dir":     string where to write results / figures,\n  "period_range":   [T_min_seconds, T_max_seconds],\n  "component":      string "xy"|"yx"|"all"|"off_diagonal",\n  "station":        string or null,\n  "inversion_code": string "occam2d"|"modem"|"mare2dem"|null,\n  "depth_max_km":   float or null,\n  "n_periods":      int or null,\n  "verbose":        bool\n}\n\nRules:\n- Choose "ai_inversion" for CNN / 1-D neural-network / deep-learning\n  inversion requests.\n- Choose "inv2d" for U-Net / 2-D neural / profile AI inversion.\n- Choose "inv3d" for GCN / graph-convolutional / 3-D AI inversion.\n- Choose "ensemble_inversion" for ensemble / uncertainty / Bayesian.\n- Choose "joint_inversion" for joint / multi-modal / TEM+MT.\n- Choose "full_ai_workflow" when both AI inversion and full pipeline\n  are requested together.\n- If a period range is given in frequency (Hz), convert to period\n  (s = 1/f).\n- Preserve the full absolute path exactly as given.\n- Return ONLY the JSON object no markdown fences, no prose.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.IntentRouter(*, api_key=None, model=None, llm_provider='claude')#

Bases: BaseAgent

Classify a chat message into a RouterDecision.

Parameters:
  • api_key (str or None) – LLM key. None → pure offline heuristic (classify_intent_offline()).

  • model (str) – Passed to BaseAgent.

  • llm_provider (str) – Passed to BaseAgent.

Examples

Offline:

router = IntentRouter()
d = router.route("what does StaticShiftAgent do?")
d.intent  # 'question'
d.needs_data  # False

Online:

router = IntentRouter(api_key="sk-ant-...", llm_provider="claude")
d = router.route("run QC on /data/willy")
d.intent  # 'workflow'
SYSTEM_PROMPT: str = 'You are the top-level intent router for the pycsamt v2 magnetotelluric (MT)\nassistant. Classify the user\'s message into exactly one INTENT.\n\nINTENTS:\n- "question": the user asks ABOUT pycsamt concepts, what a class/function\n  does, how something works, which method to use, definitions. They want an\n  explanation, not execution.\n- "code": the user wants a Python script / function / notebook generated.\n- "plot": the user wants a figure/plot/visualisation produced from data.\n- "workflow": the user wants to RUN a processing pipeline on their data\n  (QC, static-shift, phase analysis, an inversion, report, etc.).\n- "meta": greetings, "what can you do", "introduce yourself", capability\n  questions about the assistant itself, and requests to LIST/ENUMERATE the\n  agents, tasks or workflows the assistant can run ("list the agents", "which\n  workflows are available", "what tasks can you perform"). A question about\n  what ONE named agent/function does is a "question", not "meta".\n- "metrics": the user asks for a COMPUTED VALUE of their survey line(s) and\n  wants the number back inline strike, azimuth/bearing, dimensionality,\n  skew, station count, period/frequency range, coordinates/length, quality\n  score, or a one-line summary ("what\'s the strike of L22PLT?", "azimuth of\n  all lines", "how many stations", "tell me about this line"). This is NOT a\n  plot/figure request and NOT "run an analysis".\n\nReturn ONLY a JSON object:\n{\n  "intent": one of question|code|plot|workflow|meta|metrics,\n  "workflow": one of [qc, static_shift, phase_analysis, forward, pre_inversion, inversion_eval, interpretation, report, full, ai_inversion, inv2d, inv3d, ensemble_inversion, joint_inversion, pinn_inversion, hybrid_inversion, modem, occam2d, tipper, sensitivity, rotation, freq_decimation, batch, comparison, code_gen, denoise, rhophi, phase_psection, pt_psection, tipper_plot, phase_tensor_map, pt_strip, pt_strip_grid, station_response, strike_profile, strike, dimensionality, validator, coords, elevation, converter, batch_export, freq_editor, layered_model, corr_ss_ama, corr_ss_loess, corr_ss_bilateral, corr_ss_refmedian, corr_ss_emap, corr_notch, corr_smooth_logfreq, corr_smooth_rho_phase, corr_rotate_angle, corr_rotate_strike, corr_rotate_pt_strike, corr_rotate_profile, corr_antisymmetrize, corr_coord_projection, corr_coord_spacing, corr_coord_snap, corr_coord_elevation, corr_coord_shift, corr_coord_interpolate, corr_near_field, corr_strat_qc, corr_strat_static_shift, corr_strat_noise, corr_strat_freq_filter, corr_strat_full] or null (only for workflow/plot/code),\n  "confidence": float 0..1,\n  "clarification": a single question to ask IF the request is too ambiguous\n     to route, else null,\n  "reasoning": one short sentence\n}\n\nRules:\n- "How do I run an inversion?" is a QUESTION (asking for guidance).\n- "Run an inversion on /data/x" is a WORKFLOW (asking to execute).\n- "Write code to run an inversion" is CODE.\n- Prefer "question" when the user clearly wants to learn, not execute.\n- Set a low confidence (<0.5) and provide "clarification" when genuinely\n  ambiguous.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

route(text, *, history=None)#

Return a RouterDecision for text.

Uses the LLM when a key is configured, falling back to the offline heuristic on any failure or when offline.

Parameters:
Return type:

RouterDecision

class pycsamt.agents.PackageQAAgent(*, api_key=None, model=None, llm_provider='claude', use_rag=True)#

Bases: BaseAgent

Answer free-form questions about pycsamt v2.

Parameters:
  • api_key (str or None) – LLM key. None activates offline docstring-lookup mode (no cost, no network).

  • model (str) – Passed to BaseAgent.

  • llm_provider (str) – Passed to BaseAgent.

  • use_rag (bool)

Notes

Uses query-adaptive context injection: selects the most relevant documentation tiers (agents, Sites data model, usage examples) based on keywords in the question, rather than always dumping the full reference. This keeps the LLM focused on what’s relevant.

Examples

Offline (no LLM):

agent = PackageQAAgent()
r = agent.execute({"question": ("What does StaticShiftAgent do?")})
print(r["answer"])

Online (Claude):

agent = PackageQAAgent(
    api_key="sk-ant-...",
    llm_provider="claude",
)
r = agent.execute({"question": ("How do I access impedance Z?")})
print(r["answer"])
SYSTEM_PROMPT: str = 'You are a helpful expert on the pycsamt v2 Python library for magnetotelluric data processing.\n\nThe pycsamt v2 API reference extracted from the live package is provided below. Use it as your *primary source of truth*. If an answer is not in the reference, say so do not invent class names, parameters, or behaviours that are not listed.\n\n---\nPYCSAMT v2 PACKAGE OVERVIEW\n==============================\npycsamt is a Python library for magnetotelluric (MT/AMT/CSAMT) data processing, correction, and AI-assisted inversion. Agents are self-contained workflow components; Sites is the main data container for loaded EDI data.\n\nWorkflow keywords (pass as config["workflow"])\n----------------------------------------------\nqc                  Data quality control + station flagging\nstatic_shift        Galvanic static-shift detection + correction\nphase_analysis      Phase tensor, dimensionality, strike analysis\nforward             Forward model computation\npre_inversion       Pre-processing before inversion\ninversion_eval      Evaluate inversion results\ninterpretation      Geological interpretation of resistivity models\nreport              Generate PDF/HTML processing report\nfull                Full pipeline: load -> qc -> correct -> invert -> report\nai_inversion        1-D neural network / CNN inversion (EMInverter1D)\ninv1d               Alias for ai_inversion\ninv2d               2-D U-Net profile inversion\ninv3d               3-D GCN graph-convolutional inversion\nensemble_inversion  Ensemble / uncertainty-quantified inversion\npinn_inversion      Physics-Informed Neural Network inversion\nhybrid_inversion    PINN + AI inverter hybrid (requires checkpoint)\norchestrated_code_gen  Generate reproducible Python script for a workflow\ntipper              Tipper / induction arrow analysis\nmodem               ModEM 3-D inversion interface\noccam2d             Occam2D regularised 2-D inversion interface\n\n\nAgent class reference\n=====================\nWorkflowOrchestratorAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', default_workflow: \'str\' = \n  Intelligently route an NL request to the correct agent chain.\n  Parameters:\n    ----------\n    api_key, model, llm_provider : str\n    default_workflow : str\n        Fallback when NL classification is ambiguous (default ``"qc"``).\n  Input keys:\n    ----------\n    ``request`` : str natural-language processing request\n    ``config`` : dict, optional pre-built config (skips NL parsing)\n    ``dry_run`` : bool preview without executing (default False)\n    ``output_dir`` : str\n  Output data keys:\n    ----------------\n    ``workflow_type``   str\n    ``reasoning``       str\n    ``coordinator``     AgentCoordinator instance\n    ``result``          AgentResult from the coordinator\n\nContextInputAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', use_rag: \'bool\' = True) ->\n  Parse a natural-language MT workflow request into a structured config.\n  Parameters:\n    ----------\n    api_key : str or None\n        LLM API key.  When ``None`` the regex fallback is used exclusively.\n    model, llm_provider : str\n        Passed to :class:`~pycsamt.agents._base.BaseAgent`.\n\nMTLoaderAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', recursive: \'bool\' = True, \n  Load MT data from any pycsamt-supported format and assess quality.\n  Parameters:\n    ----------\n    api_key : str or None\n    model, llm_provider : str\n    recursive : bool\n        When loading a directory, recurse into sub-directories.\n    on_dup : str\n        Duplicate-station handling: ``"replace"`` (default) or ``"skip"``.\n\nDataQCAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', method: \'str\' = \'composite\n  Run data quality control on a MT/AMT dataset.\n  Parameters:\n    ----------\n    api_key, model, llm_provider : str\n        LLM configuration (optional).\n    method : str\n        Confidence scoring method: ``"composite"`` (default), ``"presence"``,\n        ``"snr"``, or ``"spatial"``.\n    min_frac_ok : float\n        Minimum fraction of OK frequencies for a station to pass (0–1).\n    min_snr_med : float\n        Minimum median SNR for a station to pass.\n    max_skew_med : float\n        Maximum median |β| skewness for a station to pass.\n  Input keys:\n    ----------\n    ``sites`` : Sites or ``path`` : str\n        EDI data to assess.\n    ``output_dir`` : str, optional\n        Where to save QC figures.\n    ``period_range`` : [T_min, T_max], optional\n        Restrict QC to this period window.\n  Output data keys:\n    ----------------\n    ``qc_table``            pandas DataFrame per-station metrics\n    ``flags``               pandas DataFrame pass / fail per station\n    ``confidence_table``    pandas DataFrame per-station confidence scores\n    ``freq_conf_table``     pandas DataFrame per-frequency confidence\n    ``n_flagged``           int\n    ``flagged_stations``    list[str]\n    ``figures``             dict matplotlib Figure objects\n\nStaticShiftAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', method: \'str\' = \'ama\', hal\n  Detect and correct galvanic static shift in MT/AMT data.\n  Parameters:\n    ----------\n    api_key, model, llm_provider : str\n    method : {"ama", "loess", "refmedian", "bilateral"}\n        Correction algorithm.  Default ``"ama"`` (adaptive moving average).\n    half_window : int\n        Spatial half-window for AMA / LOESS smoothing.\n    pband : (T_min, T_max) or None\n        Period band used to estimate shift factors.\n    inplace : bool\n        Modify the input Sites in-place.  Default ``False`` (returns a copy).\n  Input keys:\n    ----------\n    ``sites`` / ``path`` : Sites or str\n    ``method`` : str, optional  overrides constructor default\n  Output data keys:\n    ----------------\n    ``corrected_sites``   Sites with static shift removed\n    ``shift_factors``     dict {station: factor}\n    ``rho_before``        ndarray (n_freq × n_sta) log₁₀ ρa before\n    ``rho_after``         ndarray log₁₀ ρa after\n    ``delta_stats``       dict min/max/mean shift magnitude\n    ``figures``           dict matplotlib Figure objects\n\nDenoisingAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', method: \'str\' = \'rpca\', ra\n  Denoise MT impedance data using classical or AI-based methods.\n  Parameters:\n    ----------\n    api_key, model, llm_provider : str\n    method : str\n        ``"rpca"`` (default), ``"hampel"``, ``"emap"``, ``"pipeline"``,\n        or ``"ai"`` / ``"ai_cae"`` (requires PyTorch/TF).\n    rank : int\n        RPCA rank for off-diagonal denoising (default 2).\n    half_window : int\n        Hampel filter half-window (default 3).\n  Input keys:\n    ----------\n    ``sites`` / ``path`` : Sites or str\n    ``method`` : str, optional overrides constructor default\n    ``output_dir`` : str, optional\n  Output data keys:\n    ----------------\n    ``denoised_sites``   Sites with denoised impedance\n    ``snr_before``       ndarray per-(station, freq) SNR proxy before\n    ``snr_after``        ndarray per-(station, freq) SNR proxy after\n    ``snr_gain``         float mean SNR improvement\n    ``n_recovered``      int frequencies recovered above SNR threshold\n    ``figures``          dict\n    ``figure_paths``     dict\n\nPhaseAnalysisAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', skew_th: \'float\' = 5.0, el\n  Run a full phase tensor, strike, and dimensionality survey analysis.\n  Parameters:\n    ----------\n    api_key, model, llm_provider : str\n    skew_th : float\n        Skewness |β| threshold for 3-D classification (°).\n    ellipt_th : float\n        Ellipticity λ threshold for 2-D classification.\n    band : (T_min, T_max) or None\n        Period band for strike estimation.\n  Input keys:\n    ----------\n    ``sites`` / ``path`` : Sites or str\n    ``period_range`` : [T_min, T_max], optional\n    ``output_dir`` : str, optional\n    ``run_mohr`` : bool, optional also produce Mohr circles (default False)\n  Output data keys:\n    ----------------\n    ``pt_table``          pandas DataFrame full PT metrics per (station, period)\n    ``strike_consensus``  float consensus strike angle (°)\n    ``strike_iqr``        float IQR of strike across all stations\n    ``dim_table``         pandas DataFrame per-(station, period) classification\n    ``n_1d``, ``n_2d``, ``n_3d``   int count of observations per class\n    ``figures``           dict matplotlib Figure objects\n\nForwardModelAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', dim: \'int\' = 1, freqs: \'An\n  Run a 1-D, 2-D, or 3-D MT forward model.\n  Parameters:\n    ----------\n    api_key, model, llm_provider : str\n    dim : {1, 2, 3}\n        Forward solver dimensionality.\n    freqs : array-like or None\n        Frequencies (Hz).  Defaults to 40 log-spaced points 10⁻⁴–10³ Hz.\n  Input keys:\n    ----------\n    ``model`` : dict or LayeredModel or None\n        **1-D / 2-D from 1-D layers:**\n        ``{"resistivities": [...], "thicknesses": [...]}``.\n    \n        **2-D grid type override:**\n        add ``"type": "halfspace" | "anomaly"`` and grid parameters such as\n        ``"bg_rho"``, ``"anomaly_rho"``, ``"anomaly_bounds"``.\n  Output data keys:\n    ----------------\n    ``dim``              int\n    ``layered_model``    LayeredModel (1-D / 2-D from 1-D)\n    ``grid``             Grid2D or Grid3D (2-D / 3-D)\n    ``response``         ForwardResponse / ForwardResponse2D / ForwardResponse3D\n    ``rho_a``            ndarray 1-D ρa\n    ``phase``            ndarray 1-D phase (°)\n    ``rho_a_te``         ndarray (n_freqs, n_stations) 2-D TE\n\nInterpretationAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', context: \'str\' = \'\') -> \'N\n  Interpret a resistivity model in terms of geological formations.\n  Parameters:\n    ----------\n    api_key, model, llm_provider : str\n    context : str\n        Optional geological context passed to the LLM\n        (e.g. ``"Semi-arid Precambrian terrain, looking for aquifers"``).\n  Input keys:\n    ----------\n    ``model`` : dict or LayeredModel\n        ``{"resistivities": [...], "thicknesses": [...]}``\n    ``rms`` : float, optional\n    ``context`` : str, optional overrides constructor default\n    ``sites`` / ``path`` : optional for period range context\n  Output data keys:\n    ----------------\n    ``layer_interpretations``   list of dict\n    ``summary_text``            str\n    ``dominant_lithology``      str\n    ``formation_depths_m``      list[float]\n\nReportAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', report_title: \'str\' = \'MT/\n  Generate a structured MT survey report from agent results.\n  Parameters:\n    ----------\n    api_key, model, llm_provider : str\n    report_title : str\n        Title for the report.\n    formats : list of {"md", "html", "pdf"}\n        Output formats.  Default ``["md", "html"]``.\n  Input keys:\n    ----------\n    ``results`` : dict\n        Keyed by agent step name :class:`AgentResult`.\n        Expected keys: ``"load"``, ``"qc"``, ``"static_shift"``,\n        ``"phase_analysis"``, ``"forward"`` (all optional).\n    ``output_dir`` : str\n  Output data keys:\n    ----------------\n    ``report_md``      str full markdown text\n    ``report_html``    str or None\n    ``report_path_md`` str path to .md file\n    ``report_path_html`` str or None\n\nAIInversionAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', arch: \'str\' = \'resnet\', n_\n  Train an AI inverter on synthetic data then predict on observed sites.\n  Parameters:\n    ----------\n    api_key, model, llm_provider : str\n    arch : {"resnet", "cnn1d", "fcn"}\n        Neural network architecture.\n    n_layers : int\n        Number of model layers the inverter will predict (default 5).\n    n_train_samples : int\n        Number of synthetic training samples (default 2 000).\n    epochs : int\n        Training epochs (default 30).  Increase for better models.\n    freqs : array-like or None\n        Frequencies used for both training synthesis and observed data\n  Input keys:\n    ----------\n    ``sites`` / ``path`` : Sites or str observed data\n    ``output_dir`` : str, optional\n  Output data keys:\n    ----------------\n    ``inverter``          :class:`~pycsamt.ai.inversion.inv1d.EMInverter1D`\n    ``predictions``       dict {station: ndarray of log₁₀ ρ values}\n    ``best_model``        dict with "resistivity" and "thickness" for first station\n    ``rms_per_station``   dict {station: float}\n    ``rms_global``        float\n    ``train_history``     dict (loss curves)\n    ``figures``           dict\n\nPINNInversionAgent(*, dim: \'int\' = 1, n_layers: \'int\' = 10, depth_max: \'float\' = 2000.0, smoothness_weight: \'float\' = 0.01, lateral_weight\n  PINN-based MT inversion without labelled data.\n  Parameters:\n    ----------\n    dim : {1, 2, 3}\n        Dimensionality.  Default ``1``.\n    n_layers : int\n        Number of layers including the halfspace.\n        Default ``10``.\n    depth_max : float\n        Maximum investigation depth in metres.\n        Default ``2000.0``.\n    smoothness_weight : float\n        Vertical regularisation weight.\n        Default ``0.01``.\n  Input keys:\n    ----------\n    ``sites`` / ``path``   observed data\n    ``output_dir``         optional figure/save dir\n  Output data keys:\n    ----------------\n    ``inverter``        fitted inverter object\n    ``section``         ndarray (n_layers, n_stations)\n                        log10-rho section matrix\n    ``models``          list of LayeredModel (1-D)\n    ``n_stations``      int\n    ``rms_per_station`` dict {station: float}\n    ``rms_global``      float\n\nHybridInversionAgent(*, dim: \'int\' = 1, max_iter: \'int\' = 200, smoothness_weight: \'float\' = 0.005, lateral_weight: \'float\' = 0.005, graph_we\n  Two-stage AI + physics MT inversion.\n  Parameters:\n    ----------\n    dim : {1, 2, 3}\n        Dimensionality.  Default ``1``.\n    max_iter : int\n        Physics refinement iterations (Stage 2).\n        Default ``200``.\n    smoothness_weight : float\n        Vertical regularisation weight.\n        Default ``0.005``.\n    lateral_weight : float\n        Lateral smoothness weight (2-D only).\n        Default ``0.005``.\n  Input keys:\n    ----------\n    ``sites`` / ``path``     observed data\n    ``ai_inverter``          fitted AI inverter object\n                             or path to checkpoint\n    ``checkpoint``           alias for ``ai_inverter``\n    ``output_dir``           optional save directory\n    ``dim``, ``max_iter``,\n    ``smoothness_weight``,\n  Output data keys:\n    ----------------\n    ``inverter``         fitted HybridInverterXD\n    ``section``          ndarray (n_layers, n_stations)\n                         Stage-2 log10-rho section\n    ``stage1_section``   ndarray Stage-1 section\n    ``models``           list of LayeredModel (1-D)\n    ``stage1_models``    list of LayeredModel (1-D)\n    ``n_stations``       int\n\nInv2DAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', n_depth: \'int\' = 40, n_fre\n  2-D MT profile inversion using a U-Net convolutional architecture.\n  Parameters:\n    ----------\n    api_key, model, llm_provider : str\n    n_depth : int\n        Number of depth cells in the output section (default 40).\n    n_freqs : int\n        Number of input frequencies (default 32).\n    freqs : array-like or None\n        Explicit positive frequency grid in hertz.\n    depth_max : float or None\n        Maximum cumulative model depth in metres. ``None`` preserves the\n        legacy frequency-derived parameterization.\n    n_components : int\n  Input keys:\n    ----------\n    ``sites`` / ``path`` : Sites or str\n    ``output_dir`` : str, optional\n    ``topography`` : bool or dict, optional\n        Extract terrain from ``sites`` and render the predicted section in an\n        absolute-elevation frame. A mapping can provide ``elevation_m`` and\n        ``chainage_km`` plus ``exaggeration`` and ``interp_method``.\n    ``period_range`` : [T_min, T_max], optional\n  Output data keys:\n    ----------------\n    ``pred_section``      ndarray (n_depth × n_stations) log₁₀ ρ\n                           (``physics="mt1d"``/``"mt2d"`` only)\n    ``pred_triangles``    dict with ``"mesh"``/``"log10_resistivity"``\n                           (``physics="mt2d_tri"`` only)\n    ``depths_km``         ndarray depth axis (km)\n                           (``physics="mt1d"``/``"mt2d"`` only)\n    ``station_names``     list[str]\n\nInv3DAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', n_layers: \'int\' = 5, n_fre\n  3-D MT profile inversion using a graph-convolutional network (GCN).\n  Parameters:\n    ----------\n    api_key, model, llm_provider : str\n    n_layers : int\n        Number of depth layers per station (default 5).\n    n_freqs : int\n        Number of frequencies used for feature extraction (default 32).\n    freqs : array-like or None\n        Explicit positive frequency grid in hertz. When supplied, it replaces\n        the legacy ``10^-4``--``10^3`` Hz grid and determines ``n_freqs``.\n    depth_max : float or None\n        Maximum cumulative model depth in metres. When supplied, layer\n        thicknesses are geometrically graded and normalized to this depth.\n  Input keys:\n    ----------\n    ``sites`` / ``path`` : Sites or str observed MT dataset\n    ``coords`` : ndarray (n_stations, 2), optional station (x, y) in metres.\n        Auto-extracted from EDI lat/lon when absent.\n    ``adjacency`` : ndarray (n_stations, n_stations), optional pre-computed\n        normalised adjacency; overrides *radius* when supplied.\n    ``output_dir`` : str, optional\n    ``freqs`` : array-like, optional execution-time frequency-grid override.\n  Output data keys:\n    ----------------\n    ``pred_rho``          ndarray (n_sta, n_layers)  log₁₀ρ\n    ``pred_thick``        ndarray (n_sta, n_layers-1) log₁₀h (metres)\n    ``pred_uncertainty``  ndarray (n_sta, n_layers) or None  MC-dropout std\n    ``depths_km``         ndarray depth axis at station midpoints (km)\n    ``frequency_grid_hz`` ndarray effective feature/forward frequency grid\n    ``configured_depth_max_m`` float or None explicit depth contract\n    ``topography``       dict or None resolved terrain metadata\n\nCodeGenerationAgent(*, api_key: \'str | None\' = None, model: \'str | None\' = None, llm_provider: \'str\' = \'claude\', script_title: \'str\' = \'pyc\n  Generate a reproducible Python script from a completed workflow.\n  Parameters:\n    ----------\n    api_key, model, llm_provider : str\n        When an API key is provided the LLM refines and annotates the\n        generated code.  Otherwise the agent uses static templates.\n  Input keys:\n    ----------\n    ``workflow_config`` : dict\n        The config dict produced by :class:`ContextInputAgent`.\n    ``results`` : dict\n        The agent results dict from :class:`AgentCoordinator`.\n  Output data keys:\n    ----------------\n    ``code``          str Python source code\n\n\nSites / EDI data model reference\n================================\nSites(edic: \'EDICollection | Sequence[EDIFile | Any | Site]\', *, on_loss: \'str\' = \'warn\') -> \'None\'\n  Container for multiple :class:`~pycsamt.site.base.Site` objects with convenient indexing, selection, and bulk edit operations.\n  Constructor parameters:\n    ----------\n    edic : pycsamt.seg.collection.EDICollection or sequence of\n           pycsamt.seg.edi.EDIFile, pycsamt.emtf.document.EMTF, or\n           :class:`Site`\n        Parsed EDI collection, or any sequence mixing EDI objects,\n        EMTF XML documents, and already-constructed :class:`Site`\n        instances. Items are wrapped into :class:`Site` instances\n        (existing ``Site`` items are used as-is) in the order\n        provided.\n  Attributes:\n    ----------\n    _items : list of Site\n        Internal sequence of sites. This is considered private.\n        Iterate over ``Sites`` instead of accessing it directly.\n  Key methods:\n  .from_any(source: \'Any\', topo_src: \'Any | None\' = None) -> \'Sites\'\n    Construct a container from heterogeneous inputs by using a normalized loading session.\n  .write(outdir: \'str | Path\', *, template: \'str\' = \'{station}.edi\', exist_ok: \'bool\' = \n    Write one EDI file per site to a directory.\n  .select(names: \'Sequence[str] | None\' = None, predicate: \'Callable[[Site], bool] | None\n    Filter sites by explicit names or by a boolean predicate.\n  .edit_all(*, rename: \'Callable[[str], str] | None\' = None, freq_slice: \'slice | None\' = N\n    Bulk-edit all sites with optional rename, frequency slicing, and tensor masking.\n  .closest(lat: \'float\', lon: \'float\', tol: \'float | None\' = None) -> \'Site | None\'\n    Find the closest site to a target coordinate using geodetic distance.\n  .to_profile(origin: \'tuple[float, float]\', azimuth: \'float\', *, crs: \'int | None\' = None) -\n    Convert sites to a 1D profile aligned with a specified azimuth, returning either a rich Profile object or a lightweight fallback.\n  .to_edicollection(*, copy: \'bool\' = False, progress: \'bool | str\' = False, verbose: \'int\' = 0) ->\n    Return the underlying EDI objects as an ``EDICollection``.\n  .to_edis(*, copy: \'bool\' = False, progress: \'bool | str\' = False, verbose: \'int\' = 0) ->\n    Return the underlying EDI objects as a list.\n  .as_list() -> \'list[EDIFile]\'\n    Return the underlying list of EDI objects.\n  .get(name: \'str\') -> \'Site | None\'\n    Safe lookup by case-insensitive station name.\n  .by_index(i: \'int\') -> \'Site\'\n    Retrieve a site by zero-based index.\n  .map(fn: \'Callable[[Site], Any]\') -> \'list[Any]\'\n    Apply a function to every site and collect the results.\n\nKey data attributes (set after construction)\n--------------------------------------------\nsites[i]         Site object for station i\nsites[i].Z       Impedance tensor: shape (n_freq, 2, 2), complex\nsites[i].freq    Frequencies array (Hz)\nsites[i].rho     Apparent resistivity (Ohm.m), shape (n_freq, 2, 2)\nsites[i].phase   Phase (degrees), shape (n_freq, 2, 2)\nsites[i].id      Station name / identifier\nlen(sites)       Number of stations loaded\n\n\nensure_sites(source) universal EDI loader\n-------------------------------------------\nAccepts: a Sites object, a directory path (str/Path),\n         a list of EDI file paths, or an EDICollection.\nReturns: Sites object.\nImport:  from pycsamt.ai.inversion._sites_bridge import ensure_sites\nUse case: any agent or function that needs to accept\n          flexible EDI input without caring about the format.\n\n\nwrite_sites(sites, dest, exist_ok=True) EDI exporter\n-------------------------------------------------------\nWrites one corrected EDI file per site to dest/.\nImport:  from pycsamt.site.export import write_sites\nUse case: export corrected Sites after static shift or QC.\n\n\nUsage examples (copy-paste ready)\n=================================\n# 1. Load EDI files and run quality control\nfrom pycsamt.agents import MTLoaderAgent, DataQCAgent\nloader = MTLoaderAgent()\nsites  = loader.execute({"path": "/data/L22PLT"})["sites"]\nqc     = DataQCAgent()\nreport = qc.execute({"sites": sites})\n\n# 2. Correct static shift\nfrom pycsamt.agents import StaticShiftAgent\nagent     = StaticShiftAgent(method="ama")\nresult    = agent.execute({"sites": sites})\ncorrected = result["corrected_sites"]\nfactors   = result["shift_factors"]  # {station: factor}\n\n# 3. Full orchestrated workflow (with LLM router)\nfrom pycsamt.agents import WorkflowOrchestratorAgent\norch   = WorkflowOrchestratorAgent(api_key="sk-...")\nresult = orch.execute({\n    "config":     {"workflow": "qc"},\n    "data_path":  "/data/L22PLT",\n    "output_dir": "/out/qc/",\n})\n\n# 4. Load EDI collection -> Sites\nfrom pycsamt.edi import EDICollection\ncol   = EDICollection("/data/L22PLT")\nsites = col.to_sites()     # -> Sites object\n\n# 5. Universal loader (accepts dir, list, or Sites)\nfrom pycsamt.ai.inversion._sites_bridge import ensure_sites\nsites = ensure_sites("/data/L22PLT")     # dir\nsites = ensure_sites(["a.edi", "b.edi"]) # list\n\n# 6. Access impedance tensor data\nsite0 = sites[0]           # first station\nZ     = site0.Z            # (n_freq, 2, 2) complex\nfreq  = site0.freq         # frequencies (Hz)\nrho   = site0.rho          # apparent resistivity\nphase = site0.phase        # phase (degrees)\n\n# 7. Export corrected EDI files\nfrom pycsamt.site.export import write_sites\nwrite_sites(corrected, "/out/corrected_edis/")\n\n# 8. PINN inversion\nfrom pycsamt.agents import PINNInversionAgent\nagent  = PINNInversionAgent(epochs=200, n_layers=8)\nresult = agent.execute({"sites": sites})\nmodel  = result["model"]  # layered resistivity model\n\n# 9. Generate workflow script\nfrom pycsamt.agents import CodeGenerationAgent\ncg     = CodeGenerationAgent()\nresult = cg.execute({\n    "sites": sites,\n    "workflow": "qc",\n    "output_dir": "/out/",\n})\nprint(result["code"])      # Python source\n\n\nemtools.ss / Static-shift reference\n===================================\nread_edis -- load many EDI files\n---------------------------------\nfrom pycsamt.api import read_edis\n\nSignature:\n  read_edis(sources, *, recursive=True,\n            strict=False, on_dup="replace",\n            progress="auto", leave=False,\n            verbose=0) -> APISurvey\n\nsources  : str, Path, list, or glob pattern\nReturns APISurvey\n  .collection -> Sites / EDICollection\n  .name       -> survey name string\n\nTypical use:\n    survey = read_edis("L22PLT/")\n    sites  = survey.collection\n\n\nestimate_ss_ama -- AMA static-shift factors\n--------------------------------------------\nfrom pycsamt.emtools.ss import estimate_ss_ama\n\nSignature:\n  estimate_ss_ama(sites, *, sort_by="lon",\n    half_window=3, weights="tri",\n    pband=None, max_skew=6.0,\n    robust_freq="median",\n    robust_overall="median",\n    recursive=True, on_dup="replace",\n    strict=False, verbose=0,\n    api=None) -> DataFrame\n\nsites       : Sites, str, Path, list, EDICollection\nsort_by     : \'lon\'|\'lat\'|\'name\' (station order)\nhalf_window : k neighbours each side (default 3)\nweights     : \'tri\'|\'gauss\'|\'uniform\'\npband       : (p_min_s, p_max_s) period band\nmax_skew    : |beta| threshold (default 6.0)\napi         : wrap result in APIFrame when True\n\nReturns DataFrame (one row per station):\n  station         -- station name\n  delta_log10_rho -- log10 shift (pos=above trend)\n  fac_rho         -- 10^(-delta) rho factor\n  fac_z           -- 10^(-0.5*delta) Z factor\n  n_used          -- frequencies used\n\nTypical use:\n    tbl = estimate_ss_ama(\n        sites, half_window=3, sort_by="lon"\n    )\n    print(\n        tbl[["station","delta_log10_rho","fac_z"]]\n    )\n\n\ncorrect_ss_ama -- estimate + apply AMA correction\n--------------------------------------------------\nfrom pycsamt.emtools.ss import correct_ss_ama\n\nSignature:\n  correct_ss_ama(sites, *, sort_by="lon",\n    half_window=3, weights="tri",\n    pband=None, max_skew=6.0,\n    robust_freq="median",\n    robust_overall="median",\n    inplace=False, recursive=True,\n    on_dup="replace", strict=False,\n    verbose=0) -> Sites\n\nCalls estimate_ss_ama then\napply_ss_factors(key="fac_z").\ninplace=False returns a corrected copy.\n\nTypical use:\n    sites_corr = correct_ss_ama(\n        sites, half_window=3, sort_by="lon"\n    )\n\n\nplot_ss_summary -- four-panel correction figure\n------------------------------------------------\nfrom pycsamt.emtools.ss import plot_ss_summary\n\nSignature:\n  plot_ss_summary(\n    logRho_before, logRho_after, *,\n    freqs, station_labels=None, ...\n  ) -> matplotlib.Figure\n\nPanels: (a) Before, (b) After pseudosections,\n        (c) Delta section, (d) Per-station bars.\n\nlogRho_before, logRho_after : ndarray (n_st, n_f)\n  log10 apparent-resistivity arrays.\nfreqs : ndarray (n_f,)  Hz.\nstation_labels : list of str or None.\n\nplot_ss_1d_curves -- per-station sounding grid\n-----------------------------------------------\nfrom pycsamt.emtools.ss import plot_ss_1d_curves\n\nSignature:\n  plot_ss_1d_curves(\n    logRho_before, logRho_after, *,\n    freqs, station_labels=None,\n    n_cols=4, max_stations=16, ...\n  ) -> matplotlib.Figure\n\nGrid of subplots (one per station) showing\nbefore/after log10-rho sounding curves.\n\nBuild logRho arrays from Sites:\n    from pycsamt.emtools._core import (\n        _get_z_block, _name, _iter_items,\n    )\n\n    def collect_logRho(S):\n        rows, freqs = [], None\n        for i, ed in enumerate(_iter_items(S)):\n            Z, z, fr = _get_z_block(ed)\n            if Z is None:\n                continue\n            rxy = (\n                0.2*np.abs(z[:,0,1])**2\n                /(fr+1e-24)\n            )\n            ryx = (\n                0.2*np.abs(z[:,1,0])**2\n                /(fr+1e-24)\n            )\n            rows.append(\n                np.log10(\n                    np.sqrt(rxy*ryx)+1e-24\n                )\n            )\n            freqs = fr\n        return np.array(rows), freqs\n\n    logRho_b, freqs = collect_logRho(sites)\n    logRho_a, _     = collect_logRho(sites_corr)\n    labels = [\n        _name(ed, i)\n        for i, ed in enumerate(_iter_items(sites))\n    ]\n    fig = plot_ss_summary(\n        logRho_b, logRho_a,\n        freqs=freqs, station_labels=labels,\n    )\n    fig.savefig("ss_summary.png", dpi=150)\n\n\n# Static-shift correction full workflow\nfrom pycsamt.api import read_edis\nfrom pycsamt.emtools.ss import (\n    estimate_ss_ama,\n    correct_ss_ama,\n    plot_ss_summary,\n    plot_ss_1d_curves,\n)\nfrom pycsamt.agents import StaticShiftAgent\nfrom pycsamt.emtools._core import (\n    _get_z_block, _name, _iter_items,\n)\nimport numpy as np\n\n# 1. Load EDI files\nsurvey = read_edis("L22PLT/")\nsites  = survey.collection\n\n# 2. Inspect shift factors (optional)\nss_table = estimate_ss_ama(\n    sites,\n    half_window=3,\n    sort_by="lon",\n    weights="tri",\n    max_skew=6.0,\n)\nprint(\n    ss_table[[\n        "station","delta_log10_rho","fac_z"\n    ]]\n)\n\n# 3. Correct Z tensor in place\nsites_corr = correct_ss_ama(\n    sites,\n    half_window=3,\n    sort_by="lon",\n)\n\n# 4. (Alternative) via StaticShiftAgent\nagent  = StaticShiftAgent(method="ama")\nresult = agent.execute({"sites": sites})\nsites_corr2 = result["corrected_sites"]\nfactors     = result["shift_factors"]\n\n# 5. Build log10-rho arrays for plots\ndef collect_logRho(S):\n    rows, freqs = [], None\n    for i, ed in enumerate(_iter_items(S)):\n        Z, z, fr = _get_z_block(ed)\n        if Z is None:\n            continue\n        rxy = (\n            0.2*np.abs(z[:,0,1])**2/(fr+1e-24)\n        )\n        ryx = (\n            0.2*np.abs(z[:,1,0])**2/(fr+1e-24)\n        )\n        rows.append(\n            np.log10(np.sqrt(rxy*ryx)+1e-24)\n        )\n        freqs = fr\n    return np.array(rows), freqs\n\nlogRho_b, freqs = collect_logRho(sites)\nlogRho_a, _     = collect_logRho(sites_corr)\nlabels = [\n    _name(ed, i)\n    for i, ed in enumerate(_iter_items(sites))\n]\n\n# 6. Summary figure\nfig_sum = plot_ss_summary(\n    logRho_b, logRho_a,\n    freqs=freqs,\n    station_labels=labels,\n)\nfig_sum.savefig(\n    "ss_summary.png", dpi=150,\n    bbox_inches="tight",\n)\n\n# 7. Per-station 1-D sounding curves\nfig_1d = plot_ss_1d_curves(\n    logRho_b, logRho_a,\n    freqs=freqs,\n    station_labels=labels,\n)\nfig_1d.savefig(\n    "ss_1d_curves.png", dpi=150,\n    bbox_inches="tight",\n)\n\n---\n\nAnswer in clear technical English. Include short code examples when helpful.'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict)

Return type:

AgentResult

class pycsamt.agents.MTLoaderAgent(*, api_key=None, model=None, llm_provider='claude', recursive=True, on_dup='replace')#

Bases: BaseAgent

Load MT data from any pycsamt-supported format and assess quality.

Parameters:
  • api_key (str or None)

  • model (str)

  • llm_provider (str)

  • recursive (bool) – When loading a directory, recurse into sub-directories.

  • on_dup (str) – Duplicate-station handling: "replace" (default) or "skip".

Examples

>>> agent = MTLoaderAgent()
>>> result = agent.execute({"path": "/data/AMT/WILLY_DATA/L22PLT"})
>>> result.status
'success'
>>> result["n_stations"]
25
>>> result["quality_table"].head()
     station  has_z  ...  qc_score
0  22-22BF    True  ...        88
SYSTEM_PROMPT: str = 'You are an expert MT/AMT/CSAMT data quality analyst.\nGiven a per-station data quality summary, write 2–3 concise sentences that:\n1. State the overall data quality.\n2. Flag any stations or frequency ranges that need attention.\n3. Recommend the next processing step.\nReply in plain English no bullet points, no markdown.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.DataQCAgent(*, api_key=None, model=None, llm_provider='claude', method='composite', min_frac_ok=0.6, min_snr_med=2.0, max_skew_med=6.0)#

Bases: BaseAgent

Run data quality control on a MT/AMT dataset.

Parameters:
  • api_key (str) – LLM configuration (optional).

  • model (str) – LLM configuration (optional).

  • llm_provider (str) – LLM configuration (optional).

  • method (str) – Confidence scoring method: "composite" (default), "presence", "snr", or "spatial".

  • min_frac_ok (float) – Minimum fraction of OK frequencies for a station to pass (0–1).

  • min_snr_med (float) – Minimum median SNR for a station to pass.

  • max_skew_med (float) – Maximum median |β| skewness for a station to pass.

  • keys (Output data)

  • ----------

  • sites (Sites or path : str) – EDI data to assess.

  • output_dir (str, optional) – Where to save QC figures.

  • period_range ([T_min, T_max], optional) – Restrict QC to this period window.

  • keys

  • ----------------

  • metrics (qc_table pandas DataFrame — per-station)

  • station (flags pandas DataFrame — pass / fail per)

  • scores (confidence_table pandas DataFrame — per-station confidence)

  • confidence (freq_conf_table pandas DataFrame — per-frequency)

  • int (n_flagged)

  • list[str] (flagged_stations)

  • objects (figures dict — matplotlib Figure)

  • set) (figure_paths dict — saved file paths (when output_dir)

Examples

>>> agent = DataQCAgent()
>>> result = agent.execute(
...     {"path": "/data/L22PLT", "output_dir": "/out/qc"}
... )
>>> result["n_flagged"]
2
>>> result["figures"]["confidence_section"]
<Figure …>
SYSTEM_PROMPT: str = 'You are an expert MT/AMT/CSAMT data quality analyst for pycsamt v2.\nGiven a survey QC summary, write 3–4 sentences that:\n1. State the overall data quality (good / moderate / poor).\n2. Identify specific stations or frequency bands that need attention.\n3. Explain the likely cause (instrument noise, EM interference, near-field).\n4. Recommend the most important next processing step.\nReply in plain English. No bullet points, no markdown headings.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.StaticShiftAgent(*, api_key=None, model=None, llm_provider='claude', method='ama', half_window=3, pband=None, inplace=False)#

Bases: BaseAgent

Detect and correct galvanic static shift in MT/AMT data.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • method (str, optional — overrides constructor default) – Correction algorithm. Default "ama" (adaptive moving average).

  • half_window (int) – Spatial half-window for AMA / LOESS smoothing.

  • pband ((T_min, T_max) or None) – Period band used to estimate shift factors.

  • inplace (bool) – Modify the input Sites in-place. Default False (returns a copy).

  • keys (Output data)

  • ----------

  • path (sites /)

  • method

  • output_dir (str, optional)

  • keys

  • ----------------

  • removed (corrected_sites Sites with static shift)

  • {station (shift_factors dict)

  • before (rho_before ndarray (n_freq × n_sta) — log₁₀ ρa)

  • after (rho_after ndarray — log₁₀ ρa)

  • magnitude (delta_stats dict — min/max/mean shift)

  • objects (figures dict — matplotlib Figure)

  • paths (figure_paths dict — saved file)

Examples

>>> agent = StaticShiftAgent(method="ama")
>>> result = agent.execute(
...     {"path": "/data/L22PLT", "output_dir": "/out/ss"}
... )
>>> result["delta_stats"]
{'mean': 0.18, 'max': 0.42, 'n_shifted': 7}
SYSTEM_PROMPT: str = 'You are an expert in galvanic distortion and static-shift correction for magnetotelluric data.\nGiven a static-shift correction summary, write 3–4 sentences that:\n1. State whether significant static shift was detected.\n2. Identify stations with the largest corrections and their magnitude.\n3. Assess whether the correction method was appropriate for this dataset.\n4. Recommend any follow-up action (e.g., additional spatial filtering).\nReply in plain English. No bullet points or markdown.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.PhaseAnalysisAgent(*, api_key=None, model=None, llm_provider='claude', skew_th=5.0, ellipt_th=0.1, band=None)#

Bases: BaseAgent

Run a full phase tensor, strike, and dimensionality survey analysis.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • skew_th (float) – Skewness |β| threshold for 3-D classification (°).

  • ellipt_th (float) – Ellipticity λ threshold for 2-D classification.

  • band ((T_min, T_max) or None) – Period band for strike estimation.

  • keys (Output data)

  • ----------

  • path (sites /)

  • period_range ([T_min, T_max], optional)

  • output_dir (str, optional)

  • run_mohr (bool, optional — also produce Mohr circles (default False))

  • run_fingerprint (bool, optional — produce fingerprint grid (default True))

  • keys

  • ----------------

  • (station (pt_table pandas DataFrame — full PT metrics per)

  • period)

  • (°) (strike_consensus float — consensus strike angle)

  • stations (strike_iqr float — IQR of strike across all)

  • per-(station (dim_table pandas DataFrame —)

  • classification (period))

  • n_1d

  • n_2d

  • class (n_3d int — count of observations per)

  • objects (figures dict — matplotlib Figure)

  • paths (figure_paths dict — saved file)

Examples

>>> agent = PhaseAnalysisAgent()
>>> result = agent.execute(
...     {"path": "/data/L22PLT", "output_dir": "/out/pt"}
... )
>>> result["strike_consensus"]
42.5
SYSTEM_PROMPT: str = 'You are an expert in MT phase tensor analysis and geological interpretation.\nGiven a survey phase tensor summary, write 4–5 sentences that:\n1. State the dominant dimensionality (1-D, 2-D, or 3-D) with evidence.\n2. Report the consensus geoelectric strike direction and its reliability.\n3. Identify periods / depth ranges where 3-D structure becomes significant.\n4. Note any anomalous stations (high skew, low ellipticity).\n5. Recommend whether to rotate data to strike before inversion.\nReply in plain English. No bullet points or markdown.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.ForwardModelAgent(*, api_key=None, model=None, llm_provider='claude', dim=1, freqs=None)#

Bases: BaseAgent

Run a 1-D, 2-D, or 3-D MT forward model.

Parameters:
  • api_key (str)

  • model (dict or LayeredModel or None)

  • llm_provider (str)

  • dim (int, optional — overrides constructor dim for this call) – Forward solver dimensionality.

  • freqs (array-like, optional — overrides constructor default) – Frequencies (Hz). Defaults to 40 log-spaced points 10⁻⁴–10³ Hz.

  • keys (Output data)

  • ----------

  • model

    1-D / 2-D from 1-D layers: {"resistivities": [...], "thicknesses": [...]}.

    2-D grid type override: add "type": "halfspace" | "anomaly" and grid parameters such as "bg_rho", "anomaly_rho", "anomaly_bounds".

    3-D grid type: "type": "halfspace" | "block_anomaly" with grid parameters.

  • dim

  • nx (int / float, optional (2-D grid))

  • nz (int / float, optional (2-D grid))

  • x_max (int / float, optional (2-D grid))

  • z_max (int / float, optional (2-D grid))

  • ny (int / float (3-D))

  • y_max (int / float (3-D))

  • nx_stations (int / float (3-D))

  • ny_stations (int / float (3-D))

  • n_stations (int, optional — number of surface receivers (2-D))

  • method (str, optional — "quasi3d" (default) for 3-D solver)

  • path (sites /)

  • freqs

  • output_dir (str, optional)

  • component ("xy" (default) or "yx" (1-D component selection))

  • keys

  • ----------------

  • int (dim)

  • 1-D) (layered_model LayeredModel (1-D / 2-D from)

  • 3-D) (grid Grid2D or Grid3D (2-D /)

  • ForwardResponse3D (response ForwardResponse / ForwardResponse2D /)

  • ρa (rho_a ndarray — 1-D)

  • (°) (phase ndarray — 1-D phase)

  • (n_freqs (rho_a_xy ndarray)

  • TE (n_stations) — 2-D)

  • phase (phase_yx ndarray — 3-D YX)

  • TM (rho_a_tm ndarray — 2-D)

  • phase

  • (n_freqs

  • XY (n_stations) — 3-D)

  • phase

  • YX (rho_a_yx ndarray — 3-D)

  • phase

  • ndarray (freqs)

  • None (rms float or)

  • dict (figure_paths)

  • dict

SYSTEM_PROMPT: str = 'You are an expert in MT forward modelling and resistivity earth models.\nGiven a forward model result, write 3-4 sentences that:\n1. Describe the model geometry (dimensionality, layers / grid, resistivity range).\n2. Comment on the synthetic ρa and phase response (frequency range, lateral variation for 2D/3D).\n3. If observed data are provided, interpret the data-model misfit (1-D only).\n4. Suggest which model parameters to adjust to better fit the data or geology.\nReply in plain English. No bullet points or markdown.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.InversionPrepAgent(*, api_key=None, model=None, llm_provider='claude', code='occam2d', error_floor=0.05)#

Bases: BaseAgent

Prepare MT data files for 2-D / 3-D inversion codes.

2.26. Currently supported output formats#

  • "occam2d" — Occam2D DataFile format

  • "modem" — ModEM3D data file (Phase 3)

2.26. Input keys#

sites / path : Sites or str code : str — "occam2d" (default) or "modem" period_range : [T_min, T_max], optional component : str — "xy", "yx", or "both" error_floor : float — minimum relative error floor (default 0.05) output_dir : str

2.26. Output data keys#

data_file_path str — path to the written data file n_periods int n_stations int code str

SYSTEM_PROMPT: str = 'You are an expert MT inversion specialist.\nGiven a dataset summary and chosen inversion code, recommend:\n1. Appropriate period band and error floor.\n2. Mesh geometry (cell sizes, padding, depth extent).\n3. Regularisation starting values.\n4. Any pre-processing steps still needed before inversion.\nReply in 4–5 sentences, plain English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

Parameters:
  • api_key (str | None)

  • model (str | None)

  • llm_provider (str)

  • code (str)

  • error_floor (float)

class pycsamt.agents.InversionEvaluationAgent(*, api_key=None, model=None, llm_provider='claude')#

Bases: BaseAgent

Evaluate inversion quality: RMS, residual PT, misfit section.

2.26. Input keys#

sites_obs / path_obs : Sites or str — observed data sites_mod / path_mod : Sites or str — model-predicted responses output_dir : str, optional component : str — default "xy"

2.26. Output data keys#

rms_per_station dict {station: rms} rms_global float residual_pt_table pandas DataFrame figures dict figure_paths dict

SYSTEM_PROMPT: str = 'You are an expert MT inversion quality assessor.\nGiven a misfit summary, write 3–4 sentences that:\n1. State whether the inversion converged acceptably (RMS 0.8–1.5 = good).\n2. Identify stations or period bands with elevated misfit.\n3. Diagnose likely causes (3-D effects, noise, model inadequacy).\n4. Recommend whether to re-run with adjusted regularisation.\nReply in plain English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

Parameters:
  • api_key (str | None)

  • model (str | None)

  • llm_provider (str)

class pycsamt.agents.InterpretationAgent(*, api_key=None, model=None, llm_provider='claude', context='')#

Bases: BaseAgent

Interpret a resistivity model in terms of geological formations.

Parameters:
  • api_key (str)

  • model (dict or LayeredModel)

  • llm_provider (str)

  • context (str, optional — overrides constructor default) – Optional geological context passed to the LLM (e.g. "Semi-arid Precambrian terrain, looking for aquifers").

  • keys (Output data)

  • ----------

  • model{"resistivities": [...], "thicknesses": [...]}

  • rms (float, optional)

  • context

  • path (sites /)

  • output_dir (str, optional)

  • keys

  • ----------------

  • dict (layer_interpretations list of)

  • str (dominant_lithology)

  • str

  • list[float] (formation_depths_m)

SYSTEM_PROMPT: str = 'You are an expert hydrogeologist and applied geophysicist specialising in electrical resistivity interpretation.\nGiven a 1-D or 2-D resistivity model from MT/AMT inversion, write a geological interpretation that:\n1. Identifies the likely lithological units (e.g. weathered zone, basement,\n   aquifer, clay layer) based on resistivity ranges.\n2. Estimates formation depths and thicknesses.\n3. Discusses implications for groundwater, mineral exploration, or hazard\n   assessment depending on the survey context.\n4. States uncertainties and what additional data would reduce them.\nReply in plain scientific English, 5–8 sentences. No bullet points.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.ReportAgent(*, api_key=None, model=None, llm_provider='claude', report_title='MT/AMT Survey Report', formats=None)#

Bases: BaseAgent

Generate a structured MT survey report from agent results.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • report_title (str) – Title for the report.

  • formats (list of {"md", "html", "pdf"}) – Output formats. Default ["md", "html"].

  • keys (Output data)

  • ----------

  • results (dict) – Keyed by agent step name → AgentResult. Expected keys: "load", "qc", "static_shift", "phase_analysis", "forward" (all optional).

  • output_dir (str)

  • title (str, optional — overrides constructor default)

  • keys

  • ----------------

  • text (report_md str — full markdown)

  • None (report_path_html str or)

  • file (report_path_md str — path to .md)

  • None

  • name (sections dict — section text keyed by)

Examples

>>> agent = ReportAgent(api_key="sk-ant-…")
>>> result = agent.execute(
...     {
...         "results": {"load": load_result, "qc": qc_result},
...         "output_dir": "/out/report",
...         "title": "WILLY_DATA AMT Survey — L22PLT",
...     }
... )
>>> print(result["report_path_md"])
/out/report/survey_report.md
SYSTEM_PROMPT: str = 'You are a geophysics technical writer specialising in MT surveys.\nWrite clear, concise report sections in formal scientific English.\nUse complete sentences. No markdown headings inside your response.\nKeep each section to 3–5 sentences.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.CodeGenerationAgent(*, api_key=None, model=None, llm_provider='claude', script_title='pycsamt MT Processing Workflow')#

Bases: BaseAgent

Generate a reproducible Python script from a completed workflow.

Parameters:
  • api_key (str) – When an API key is provided the LLM refines and annotates the generated code. Otherwise the agent uses static templates.

  • model (str) – When an API key is provided the LLM refines and annotates the generated code. Otherwise the agent uses static templates.

  • llm_provider (str) – When an API key is provided the LLM refines and annotates the generated code. Otherwise the agent uses static templates.

  • script_title (str)

  • keys (Output data)

  • ----------

  • workflow_config (dict) – The config dict produced by ContextInputAgent.

  • results (dict) – The agent results dict from AgentCoordinator.

  • output_dir (str, optional)

  • keys

  • ----------------

  • code (code str — Python source)

  • file (script_path str or None — path to saved .py)

Examples

>>> agent = CodeGenerationAgent()
>>> result = agent.execute(
...     {
...         "workflow_config": cfg,
...         "results": coord_results,
...         "output_dir": "/out",
...     }
... )
>>> print(result["script_path"])
/out/workflow_script.py
SYSTEM_PROMPT: str = 'You are an expert Python developer specialising in geophysics scripting.\nGiven a pycsamt workflow configuration and execution log, generate a clean,\nwell-commented Python script that reproduces the workflow step by step.\nUse pycsamt v2 public API only.  Add a one-line comment above each major\nblock.  Do not use the agents/ subpackage call emtools, forward, and\nsite functions directly.  Output only valid Python code.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.WorkflowOrchestratorAgent(*, api_key=None, model=None, llm_provider='claude', default_workflow='qc')#

Bases: BaseAgent

Intelligently route an NL request to the correct agent chain.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • default_workflow (str) – Fallback when NL classification is ambiguous (default "qc").

  • keys (Output data)

  • ----------

  • request (str — natural-language processing request)

  • config (dict, optional — pre-built config (skips NL parsing))

  • dry_run (bool — preview without executing (default False))

  • output_dir (str)

  • data_path (str — EDI path (overrides extracted path))

  • keys

  • ----------------

  • str (reasoning)

  • str

  • instance (coordinator AgentCoordinator)

  • coordinator (result AgentResult from the)

  • metadata (steps list of step)

Examples

Dry-run preview:

agent = WorkflowOrchestratorAgent()
r = agent.execute(
    {
        "request": "Load L22PLT EDIs, run full phase tensor analysis",
        "dry_run": True,
    }
)
print(r["workflow_type"])  # "phase_analysis"

Full run with LLM:

agent = WorkflowOrchestratorAgent(api_key="sk-ant-…")
r = agent.execute(
    {
        "request": "Clean and denoise the WILLY data, then run AI inversion",
        "data_path": "/data/WILLY_DATA",
    }
)
SYSTEM_PROMPT: str = 'You are a pycsamt MT workflow routing expert.\nGiven a natural-language MT processing request,\nreturn a JSON object with:\n{\n  "workflow_type": one of:\n    "qc", "phase_analysis", "pre_inversion",\n    "ai_inversion", "inv2d", "inv3d",\n    "ensemble_inversion", "joint_inversion",\n    "modem", "mare2dem", "full", "full_ai_workflow",\n    "pinn_inversion", "hybrid_inversion",\n    "tipper", "sensitivity", "rotation",\n    "freq_decimation", "batch", "comparison",\n    "denoise", "static_shift", "inversion_eval",\n    "code_gen", "report", "forward",\n    "interpretation",\n  "reasoning": one sentence explaining the choice\n}\n\nRules:\n- "qc" if quality, cleaning, or flagging only.\n- "denoise" if denoising or noise removal only.\n- "static_shift" if static-shift or galvanic\n  distortion only (no full inversion).\n- "phase_analysis" if phase tensor, strike,\n  dimensionality, or Mohr circles.\n- "pre_inversion" if Occam2D, mesh preparation.\n- "inversion_eval" if evaluating an existing\n  inversion result, RMS, misfit, or residuals.\n- "ai_inversion" if 1-D AI or neural inversion.\n- "inv3d" if GCN or 3-D AI inversion.\n- "inv2d" if U-Net or 2-D profile AI inversion.\n- "ensemble_inversion" if ensemble or uncertainty.\n- "joint_inversion" if multi-modal or TEM+MT.\n- "modem" if ModEM or 3-D conventional inversion.\n- "mare2dem" if MARE2DEM or 2.5-D FEM inversion.\n- "pinn_inversion" if PINN or physics-informed.\n- "hybrid_inversion" if two-stage or AI warm-start.\n- "tipper" if tipper or induction arrows.\n- "sensitivity" if sensitivity kernels or DOI.\n- "rotation" if tensor rotation or strike frame.\n- "freq_decimation" if period selection/decimation.\n- "batch" if batch processing multiple profiles.\n- "comparison" if comparing inversion results.\n- "code_gen" if generating a Python script.\n- "report" if generating a survey report only.\n- "forward" if forward modelling or synthetic data.\n- "interpretation" if geological interpretation.\n- "full" if complete pipeline or multiple methods.\nDefault to "qc" when uncertain.\nReturn ONLY the JSON.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.DenoisingAgent(*, api_key=None, model=None, llm_provider='claude', method='rpca', rank=2, half_window=3)#

Bases: BaseAgent

Denoise MT impedance data using classical or AI-based methods.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • method (str, optional — overrides constructor default) – "rpca" (default), "hampel", "emap", "pipeline", or "ai" / "ai_cae" (requires PyTorch/TF).

  • rank (int) – RPCA rank for off-diagonal denoising (default 2).

  • half_window (int) – Hampel filter half-window (default 3).

  • keys (Output data)

  • ----------

  • path (sites /)

  • method

  • output_dir (str, optional)

  • period_range ([T_min, T_max], optional)

  • keys

  • ----------------

  • impedance (denoised_sites Sites with denoised)

  • per-(station (snr_after ndarray —)

  • before (freq) SNR proxy)

  • per-(station

  • after (freq) SNR proxy)

  • improvement (snr_gain float — mean SNR)

  • threshold (n_recovered int — frequencies recovered above SNR)

  • dict (figure_paths)

  • dict

SYSTEM_PROMPT: str = 'You are an expert MT noise analysis and denoising specialist.\nGiven a denoising result summary, write 3–4 sentences that:\n1. State which noise sources were addressed (powerline, cultural, source effects).\n2. Quantify the improvement (e.g. SNR gain, number of frequencies recovered).\n3. Identify any remaining problematic frequencies or stations.\n4. Recommend follow-up processing steps.\nReply in plain English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.AIInversionAgent(*, api_key=None, model=None, llm_provider='claude', arch='resnet', n_layers=5, n_train_samples=2000, epochs=30, freqs=None, pretrained=None)#

Bases: BaseAgent

Train an AI inverter on synthetic data then predict on observed sites.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • arch (optional overrides) – Neural network architecture.

  • n_layers (int) – Number of model layers the inverter will predict (default 5).

  • n_train_samples (optional overrides) – Number of synthetic training samples (default 2 000).

  • epochs (optional overrides) – Training epochs (default 30). Increase for better models.

  • freqs (array-like or None) – Frequencies used for both training synthesis and observed data interpolation. Default: 40 log-spaced 10⁻⁴–10³ Hz.

  • pretrained (str or None) – Path to a pre-trained model checkpoint. When set, skips training.

  • keys (Output data)

  • ----------

  • path (sites /)

  • output_dir (str, optional)

  • arch

  • epochs

  • n_train_samples

  • keys

  • ----------------

:param inverter EMInverter1D: :param predictions dict {station: :type predictions dict {station: ndarray of log₁₀ ρ values} :param best_model dict with “resistivity” and “thickness” for first station: :param rms_per_station dict {station: :type rms_per_station dict {station: float} :param rms_global float: :param train_history dict (loss curves): :param figures dict: :param figure_paths dict:

Examples

>>> agent = AIInversionAgent(arch="resnet", n_layers=5, epochs=30)
>>> result = agent.execute(
...     {
...         "path": "/data/L22PLT",
...         "output_dir": "/out/ai_inv",
...     }
... )
>>> result["rms_global"]
0.24
SYSTEM_PROMPT: str = 'You are an expert in AI-based MT inversion and deep learning for geophysics.\nGiven an AI inversion result, write 4–5 sentences that:\n1. Describe the neural network architecture used and training convergence.\n2. State the prediction quality (RMS, layer count, depth range).\n3. Identify stations where the AI prediction is most / least reliable.\n4. Compare AI results with classical Bostick depth estimates if available.\n5. Recommend next steps (fine-tuning, ensemble, switch to 2-D inversion).\nReply in plain scientific English.\n'#

Override in subclasses to give the LLM its domain expertise.

classmethod from_pretrained(model_name, *, api_key=None, model=None, llm_provider='claude', cache_dir=None, force_download=False)#

Return an AIInversionAgent pre-loaded with a zoo checkpoint.

Parameters:
  • model_name (str) – Registry name — see list_pretrained().

  • cache_dir (str or None) – Override default cache ~/.pycsamt/model_zoo/.

  • force_download (bool) – Re-download even if cached.

  • api_key (str | None)

  • model (str | None)

  • llm_provider (str)

Return type:

AIInversionAgent

Examples

>>> agent = AIInversionAgent.from_pretrained("mt1d-resnet-5layer-v1")
>>> result = agent.execute({"path": "/data/L22PLT"})
execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.AnomalyDetectionAgent(*, api_key=None, model=None, llm_provider='claude', threshold_percentile=95.0, latent_dim=32, epochs=50)#

Bases: BaseAgent

Detect anomalous (station, frequency) observations in MT data.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • threshold_percentile (float) – Percentile of reconstruction errors used as the flagging threshold (default 95 — top 5 % are anomalies).

  • latent_dim (int) – CAE latent space dimension (default 32).

  • epochs (int) – Training epochs (default 50).

  • keys (Output data)

  • ----------

  • path (sites /)

  • output_dir (str, optional)

  • keys

  • ----------------

  • per-(station (anomaly_scores ndarray —)

  • error (freq) reconstruction)

  • anomalous (flags ndarray bool — True =)

  • {station (flag_table pandas DataFrame)

  • freq

  • score

  • flagged}

  • int (n_flagged)

  • list[str] (flagged_stations)

  • dict (figure_paths)

  • dict

SYSTEM_PROMPT: str = 'You are an expert in unsupervised anomaly detection for MT/AMT data.\nGiven an anomaly detection result, write 3-4 sentences that:\n1. State how many observations were flagged as anomalous and their distribution.\n2. Identify which stations or frequency bands are most affected.\n3. Diagnose the likely source (powerline harmonics, near-field, 3-D, instrument).\n4. Recommend whether to mask flagged data or apply targeted filtering.\nReply in plain English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.Occam2DAgent(*, api_key=None, model=None, llm_provider='claude', modes=None, error_floor=0.05, target_rms=1.0)#

Bases: BaseAgent

Generate a complete Occam2D inversion input file set.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • modes (list of str, optional — overrides constructor default) – Occam2D mode codes. Common choices: ["ZXXR","ZXXI","ZXYR","ZXYI","ZYXR","ZYXI","ZYYR","ZYYI"] (all Z), ["RhoZXY","PhsZXY","RhoZYX","PhsZYX"] (ρa/φ off-diagonal). Default: None → OccamData auto-selects from available data.

  • error_floor (float) – Minimum relative error floor applied to all data (default 0.05 = 5 %).

  • target_rms (float) – Target RMS for the startup file (default 1.0).

  • keys (Output data)

  • ----------

  • path (sites /)

  • output_dir (str)

  • period_range ([T_min, T_max], optional)

  • modes

  • title (str, optional)

  • keys

  • ----------------

  • OccamDataFile.dat (data_path Path —)

  • Occam2DMesh (mesh_path Path —)

  • Occam2DModel (model_path Path —)

  • OccamStartup (startup_path Path —)

  • int (n_data)

  • int

  • int

  • str (output_dir)

Examples

>>> agent = Occam2DAgent()
>>> result = agent.execute(
...     {
...         "path": "/data/L22PLT",
...         "output_dir": "/out/occam2d",
...     }
... )
>>> print(result["data_path"])
/out/occam2d/OccamDataFile.dat
SYSTEM_PROMPT: str = 'You are an expert in 2-D MT inversion setup using Occam2D.\nGiven the data file statistics and mesh parameters, write 3–4 sentences that:\n1. Confirm the data file contains the expected stations and period bands.\n2. Comment on the mesh geometry (cell sizes, depth extent, padding).\n3. Recommend regularisation parameters (roughness penalty, target RMS).\n4. Note any data gaps or stations that should be excluded.\nReply in plain English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.ModEmAgent(*, api_key=None, model=None, llm_provider='claude', component_types=None, error_floor=0.05)#

Bases: BaseAgent

Write a ModEM3D MT data file from EDI sources.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • component_types (list, optional) – Impedance components to include. E.g. ["Full_Impedance", "Full_Vertical_Components"]. Default: None → ModEmData auto-selects.

  • error_floor (float, optional) – Minimum relative error floor (default 0.05 = 5 %).

  • keys (Output data)

  • ----------

  • path (sites /)

  • output_dir (str)

  • period_range ([T_min, T_max], optional)

  • component_types

  • error_floor

  • keys

  • ----------------

  • file (ctrl_path Path or None — inversion-control)

  • m0.rho) (model_path Path or None — starting model (m0.ws /)

  • runs) (cov_path Path or None — covariance file (3-D)

  • file

  • int (n_periods)

  • int

  • str (output_dir)

Examples

>>> agent = ModEmAgent()
>>> result = agent.execute(
...     {
...         "path": "/data/WILLY_DATA",
...         "output_dir": "/out/modem",
...     }
... )
>>> print(result["data_path"])
/out/modem/ModEM_Data.dat
SYSTEM_PROMPT: str = 'You are an expert in 3-D MT inversion setup using ModEM3D.\nGiven a ModEM data file summary, write 3–4 sentences that:\n1. Confirm the data contains the expected stations, periods, and components.\n2. Recommend an initial model (background resistivity, layer structure).\n3. Suggest suitable covariance parameters (smoothing length, roughness).\n4. Flag any data issues that could cause convergence problems.\nReply in plain English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.Inv2DAgent(*, api_key=None, model=None, llm_provider='claude', n_depth=40, n_freqs=32, freqs=None, depth_max=None, n_components=2, arch='unet', n_train_profiles=200, n_stations_per_profile=20, epochs=30, patience=None, physics='mt1d', station_spacing_m=500.0, correlation_length_x_m=(500.0, 2000.0), correlation_length_z_m=(100.0, 500.0), log_resistivity_mean=2.0, log_resistivity_std=0.5, mesh_safety_factor=8.0, max_mesh_cells=200000, lambda_x=0.0, lambda_z=0.0, lambda_tv=0.0, mesh_target_cell_m=100.0, field_grid_cell_m=50.0, topo_x_m=None, topo_z_m=None, gcn_hidden=(64, 32, 16), gcn_adjacency_radius_m=300.0, mare2dem_adapter=None, verbose=False)#

Bases: BaseAgent

2-D MT profile inversion using a U-Net convolutional architecture.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • n_depth (int) – Number of depth cells in the output section (default 40).

  • n_freqs (int) – Number of input frequencies (default 32).

  • freqs (array-like, optional — execution-time frequency override) – Explicit positive frequency grid in hertz.

  • depth_max (float, optional — cumulative model depth in metres) – Maximum cumulative model depth in metres. None preserves the legacy frequency-derived parameterization.

  • n_components (int) – Number of channels in the input pseudosection (default 2: log10 apparent resistivity and phase for the xy component).

  • arch (str) – U-Net variant (default "unet").

  • n_train_profiles (int) – Number of synthetic 2-D profiles for training (default 200).

  • n_stations_per_profile (int) – Stations per synthetic profile (default 20).

  • epochs (int) – Training epochs (default 30).

  • patience (int or None) – Early-stopping patience. None uses one fifth of epochs with a minimum of five. Set greater than epochs only for a controlled fixed-epoch experiment; validation-based stopping is normally safer.

  • physics ({"mt1d", "mt2d", "mt2d_tri"}, default "mt1d") – Synthetic training-data physics; see the module docstring.

  • station_spacing_m (float, default 500.0) – Uniform synthetic station spacing used by physics="mt2d" and physics="mt2d_tri"; the actual survey’s real station geometry is not read by either.

  • correlation_length_x_m ((float, float)) – physics="mt2d"/"mt2d_tri" only: horizontal/vertical correlation length ranges forwarded to Maxwell2DDatasetConfig/ MaxwellTri2DDatasetConfig.

  • correlation_length_z_m ((float, float)) – physics="mt2d"/"mt2d_tri" only: horizontal/vertical correlation length ranges forwarded to Maxwell2DDatasetConfig/ MaxwellTri2DDatasetConfig.

  • log_resistivity_mean (float) – physics="mt2d"/"mt2d_tri" only: affine map from the standardized correlated field to log10(resistivity_ohm_m).

  • log_resistivity_std (float) – physics="mt2d"/"mt2d_tri" only: affine map from the standardized correlated field to log10(resistivity_ohm_m).

  • mesh_safety_factor (float, int) – physics="mt2d" only: forwarded to Maxwell2DDatasetConfig.

  • max_mesh_cells (float, int) – physics="mt2d" only: forwarded to Maxwell2DDatasetConfig.

  • lambda_x (float, default 0.0) – Spatial-regularization weights forwarded to fit(). Zero by default, so nothing changes unless explicitly requested. physics="mt2d" only.

  • lambda_z (float, default 0.0) – Spatial-regularization weights forwarded to fit(). Zero by default, so nothing changes unless explicitly requested. physics="mt2d" only.

  • lambda_tv (float, default 0.0) – Spatial-regularization weights forwarded to fit(). Zero by default, so nothing changes unless explicitly requested. physics="mt2d" only.

  • mesh_target_cell_m (float) – physics="mt2d_tri" only: forwarded to MaxwellTri2DDatasetConfig.

  • field_grid_cell_m (float) – physics="mt2d_tri" only: forwarded to MaxwellTri2DDatasetConfig.

  • topo_x_m (array-like, optional) – physics="mt2d_tri" only: real topography (z positive down) forwarded to MaxwellTri2DDatasetConfig/ build_graded_tri_mesh(). Both default to None (flat surface at z=0, unchanged from before this parameter existed) – when given, training stations sit at their true interpolated elevation instead. This builds a real topography-following training mesh; it is unrelated to the topography execute()-time input key above, which only re-renders an already-flat mt1d/mt2d prediction in an absolute-elevation display frame.

  • topo_z_m (array-like, optional) – physics="mt2d_tri" only: real topography (z positive down) forwarded to MaxwellTri2DDatasetConfig/ build_graded_tri_mesh(). Both default to None (flat surface at z=0, unchanged from before this parameter existed) – when given, training stations sit at their true interpolated elevation instead. This builds a real topography-following training mesh; it is unrelated to the topography execute()-time input key above, which only re-renders an already-flat mt1d/mt2d prediction in an absolute-elevation display frame.

  • gcn_hidden (tuple of int, default (64, 32, 16)) – physics="mt2d_tri" only: hidden-layer widths forwarded to GCNInverter3D.

  • gcn_adjacency_radius_m (float, default 300.0) – physics="mt2d_tri" only: triangle-centroid adjacency radius forwarded to build_adjacency(). Must be set relative to the actual mesh scale, not left at the default: it is a hard cutoff in the same metres as mesh_target_cell_m, and if it is smaller than the typical distance between neighbouring triangle centroids, build_adjacency returns the identity matrix (every triangle connected only to itself) and the GCN silently degenerates into a per-triangle lookup with no spatial message-passing at all – no error is raised. A radius of roughly 1.5-2x mesh_target_cell_m is a reasonable starting point; verify with build_adjacency(mesh.triangle_centroids_m, radius).sum() > mesh.n_triangles (more than just the self-loops) before trusting a training run.

  • mare2dem_adapter (object, optional) – physics="mt2d_tri" only: pre-built Mare2DEMAdapter (e.g. pointed at a specific compiled binary). Defaults to Mare2DEMAdapter(), resolved from the environment.

  • keys (Output data)

  • ----------

  • path (sites /)

  • output_dir (str, optional)

  • topography (bool or dict, optional) – Extract terrain from sites and render the predicted section in an absolute-elevation frame. A mapping can provide elevation_m and chainage_km plus exaggeration and interp_method.

  • period_range ([T_min, T_max], optional)

  • freqs

  • depth_max

  • keys

  • ----------------

  • ρ (pred_section ndarray (n_depth × n_stations) — log₁₀) – (physics="mt1d"/"mt2d" only)

  • "mesh"/"log10_resistivity" (pred_triangles dict with) – (physics="mt2d_tri" only)

  • (km) (depths_km ndarray — depth axis) – (physics="mt1d"/"mt2d" only)

  • list[str] (station_names)

  • metadata (topography dict or None — resolved terrain) – (physics="mt1d"/"mt2d" only)

  • only) (rms_global float (physics="mt1d"/"mt2d")

  • GCNInverter3D (inverter EMInverter2D or)

  • dict (figure_paths)

  • dict

  • "mt1d" (physics str —) – "mt2d_tri", mode used

  • "mt2d""mt2d_tri", mode used

  • or"mt2d_tri", mode used

  • known-truth (mt2d_tri_recovery dict or None — held-out) – recovery metrics (physics="mt2d" only)

  • known-truth – recovery metrics (physics="mt2d_tri" only)

  • verbose (bool | int | str)

SYSTEM_PROMPT: str = 'You are an expert in 2-D MT inversion using deep learning (U-Net architecture).\nGiven a 2-D AI inversion result, write 4-5 sentences that:\n1. Describe the input pseudosection geometry (stations × frequencies).\n2. Interpret the dominant structural features in the resistivity section.\n3. Assess lateral continuity and compare to classical smoothness-constrained results.\n4. Identify artefacts or stations with poor convergence.\n5. Recommend follow-up (regularisation, 3-D verification, drilling targets).\nReply in plain scientific English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.Inv3DAgent(*, api_key=None, model=None, llm_provider='claude', n_layers=5, n_freqs=32, freqs=None, depth_max=None, n_train_profiles=150, epochs=30, patience=None, radius=5000.0, hidden=(256, 128, 64), dropout=0.1, n_mc=20, physics='mt1d', correlation_length_x_m=(500.0, 2000.0), correlation_length_y_m=(500.0, 2000.0), correlation_length_z_m=(100.0, 500.0), log_resistivity_mean=2.0, log_resistivity_std=0.5, mesh_safety_factor=3.0, max_mesh_cells=6000, cells_per_skin_depth=None, geology_grid_nx_ny=6, geology_grid_nz=None, verbose=False)#

Bases: BaseAgent

3-D MT profile inversion using a graph-convolutional network (GCN).

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • n_layers (int) – Number of depth layers per station (default 5).

  • n_freqs (int) – Number of frequencies used for feature extraction (default 32).

  • freqs (array-like, optional — execution-time frequency-grid override.) – Explicit positive frequency grid in hertz. When supplied, it replaces the legacy 10^-410^3 Hz grid and determines n_freqs.

  • depth_max (float, optional — cumulative finite-layer depth in metres.) – Maximum cumulative model depth in metres. When supplied, layer thicknesses are geometrically graded and normalized to this depth. None preserves the legacy Bostick-derived display parameterization.

  • n_train_profiles (int) – Number of synthetic 3-D training profiles (default 150). With physics="mt3d", each profile costs a real 3-D Maxwell solve, not a cheap independent 1-D solve — lower this substantially (e.g. 20-40) for that mode.

  • epochs (int) – Training epochs (default 30).

  • patience (int or None) – Early-stopping patience measured in epochs. None uses one fifth of epochs with a minimum of five.

  • radius (float) – Maximum inter-station edge distance in metres for the adjacency graph (default 5 000 m). Stations farther apart than radius are disconnected in the graph.

  • hidden (tuple of int) – GCN hidden-layer sizes (default (256, 128, 64)).

  • dropout (float) – Dropout probability (default 0.1); also used for MC uncertainty.

  • n_mc (int) – Number of Monte-Carlo dropout passes for uncertainty estimation. Set to 0 to skip uncertainty (faster, default 20).

  • physics ({"mt1d", "mt3d"}, default "mt1d") – Synthetic training-data physics; see the module docstring.

  • correlation_length_x_m (tuple[float, float]) – : (float, float) physics="mt3d" only: horizontal/horizontal/vertical correlation length ranges forwarded to Maxwell3DDatasetConfig.

  • correlation_length_y_m (tuple[float, float]) – : (float, float) physics="mt3d" only: horizontal/horizontal/vertical correlation length ranges forwarded to Maxwell3DDatasetConfig.

  • correlation_length_z_m (tuple[float, float]) – : (float, float) physics="mt3d" only: horizontal/horizontal/vertical correlation length ranges forwarded to Maxwell3DDatasetConfig.

  • log_resistivity_mean (float) – physics="mt3d" only: affine map from the standardized correlated field to log10(resistivity_ohm_m).

  • log_resistivity_std (float) – physics="mt3d" only: affine map from the standardized correlated field to log10(resistivity_ohm_m).

  • mesh_safety_factor (float, int) – physics="mt3d" only: forwarded to Maxwell3DDatasetConfig. max_mesh_cells also builds the matching MT3DAdapter(max_cells=...) used to solve.

  • max_mesh_cells (float, int) – physics="mt3d" only: forwarded to Maxwell3DDatasetConfig. max_mesh_cells also builds the matching MT3DAdapter(max_cells=...) used to solve.

  • cells_per_skin_depth (float or None, default None) – physics="mt3d" only: opt-in frequency-aware solver core resolution, forwarded to Maxwell3DDatasetConfig — see its docstring for the accuracy-vs-cell-cost trade-off this exists to address. None (the default) preserves this agent’s original behavior (core resolution = the geological grid’s own spacing); this agent’s default freqs grid spans up to ~1000 Hz (_DEFAULT_FREQS), so enabling this without also raising max_mesh_cells and/or narrowing freqs risks a “needs N solver cells” error at high frequencies.

  • geology_grid_nx_ny (int, int or None) – physics="mt3d" only: resolution of the geological training grid (how finely the true 3-D resistivity volumes are drawn), independent of the solver mesh’s own frequency-aware resolution above. geology_grid_nz=None (default) uses min(max(n_layers, 4), 8). Raising these increases realism at the cost of more solver cells per realization; see max_mesh_cells for the resulting budget.

  • geology_grid_nz (int, int or None) – physics="mt3d" only: resolution of the geological training grid (how finely the true 3-D resistivity volumes are drawn), independent of the solver mesh’s own frequency-aware resolution above. geology_grid_nz=None (default) uses min(max(n_layers, 4), 8). Raising these increases realism at the cost of more solver cells per realization; see max_mesh_cells for the resulting budget.

  • keys (Output data)

  • ----------

  • path (sites /)

  • coords (ndarray (n_stations, 2), optional — station (x, y) in metres.) – Auto-extracted from EDI lat/lon when absent.

  • adjacency (ndarray (n_stations, n_stations), optional — pre-computed) – normalised adjacency; overrides radius when supplied.

  • output_dir (str, optional)

  • freqs

  • depth_max

  • topography (bool or dict, optional) – True extracts elevation and chainage from sites and adds a terrain-draped section. A mapping may instead provide elevation_m and optionally chainage_km, plus exaggeration and interp_method rendering options. This is a geometry/visualisation contract; it does not alter the MT forward solver or GCN prediction.

  • period_range ([T_min, T_max], optional)

  • keys

  • ----------------

  • (n_sta (adjacency ndarray)

  • log₁₀ρ (n_layers) )

  • (n_sta

  • (metres) (n_layers-1) — log₁₀h)

  • (n_sta

  • std (n_layers) or None — MC-dropout)

  • (km) (depths_km ndarray — depth axis at station midpoints)

  • grid (frequency_grid_hz ndarray — effective feature/forward frequency)

  • contract (configured_depth_max_m float or None — explicit depth)

  • metadata (topography dict or None — resolved terrain)

  • list[str] (station_names)

  • (n_sta

  • metres (2) )

  • (n_sta

  • n_sta)

  • float (rms_global)

  • GCNInverter3D (inverter)

  • dict (figure_paths)

  • dict

  • "mt3d" (physics str — "mt1d" or)

  • used (mode)

  • known-truth (mt3d_recovery dict or None — held-out) – recovery metrics (physics="mt3d" only)

  • losses (training_history dict -- per-epoch training and validation)

  • stopping (epochs_completed int -- epochs actually completed before)

  • loss (best_validation_loss float -- minimum finite validation)

  • verbose (bool | int | str)

Examples

>>> agent = Inv3DAgent(n_layers=5, epochs=20, n_mc=10)
>>> result = agent.execute(
...     {
...         "path": "/data/WILLY_EDIs",
...         "output_dir": "/out/inv3d",
...     }
... )
>>> result["rms_global"]
0.28
SYSTEM_PROMPT: str = 'You are an expert in 3-D MT inversion using graph-convolutional deep learning.\nGiven a GCN-based 3-D inversion result, write 4-5 sentences that:\n1. Describe the survey geometry (station count, spatial extent, adjacency radius).\n2. Interpret the dominant 3-D resistivity structures and their spatial continuity.\n3. Assess prediction quality (RMS, depth range) relative to station spacing.\n4. Compare the GCN spatial result to independent 1-D predictions where possible.\n5. Recommend geological follow-up and areas with highest uncertainty.\nReply in plain scientific English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.EnsembleAgent(*, api_key=None, model=None, llm_provider='claude', n_estimators=5, arch='resnet', n_layers=5, n_train_samples=2000, epochs=30, calibrate=True)#

Bases: BaseAgent

Ensemble 1-D MT inversion with uncertainty bands.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • n_estimators (int) – Number of independent models in the ensemble (default 5).

  • arch (str) – Network architecture for each estimator (default "resnet").

  • n_layers (int) – Number of model layers (default 5).

  • n_train_samples (int) – Synthetic training samples per estimator (default 2 000).

  • epochs (int) – Training epochs per estimator (default 30).

  • calibrate (bool) – Apply conformal calibration using 20 % of training data (default True).

  • keys (Output data)

  • ----------

  • path (sites /)

  • output_dir (str, optional)

  • keys

  • ----------------

  • EnsembleInverter (ensemble)

  • {station (pred_hi dict)

  • {station

  • {station

  • {station

  • coverage (coverage float — empirical 90 % interval)

  • float (rms_global)

  • dict (figure_paths)

  • dict

Examples

>>> agent = EnsembleAgent(n_estimators=3, epochs=20)
>>> result = agent.execute(
...     {"path": "/data/L22PLT", "output_dir": "/out/ens"}
... )
>>> result["coverage"]  # should be ≈ 0.90 after calibration
0.88
SYSTEM_PROMPT: str = 'You are an expert in Bayesian and ensemble methods for geophysical inversion.\nGiven an ensemble inversion result with uncertainty quantification, write 4-5\nsentences that:\n1. Describe the ensemble configuration (N models, architecture, training data).\n2. State the prediction quality (mean RMS, uncertainty magnitude).\n3. Assess the calibration: are the confidence intervals reliable?\n4. Identify depth ranges or stations where uncertainty is largest.\n5. Recommend whether the uncertainty is small enough for geological interpretation.\nReply in plain scientific English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.JointInversionAgent(*, api_key=None, model=None, llm_provider='claude', modalities=None, n_layers=5, n_freqs_primary=40, n_freqs_secondary=20, n_train_samples=2000, epochs=30, growth_rate=32)#

Bases: BaseAgent

Multi-modal MT joint inversion using DRCNN.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • modalities (list[str]) – Names of the two modalities, e.g. ["mt", "tem"]. The first entry is the primary modality (loaded from sites/path); the second is loaded from secondary_path or synthesised when absent.

  • n_layers (int) – Number of depth layers in the output model (default 5).

  • n_freqs_primary (int) – Frequencies for the primary MT response features (default 40).

  • n_freqs_secondary (int) – Frequencies for the secondary modality features (default 20).

  • n_train_samples (int) – Synthetic training samples shared across both modalities (default 2000).

  • epochs (int) – Training epochs (default 30).

  • growth_rate (int) – DRCNN dense-block growth rate (default 32).

  • keys (Output data)

  • ----------

  • path (sites /)

  • secondary_path (str, optional — secondary modality EDI/TEM path)

  • output_dir (str, optional)

  • period_range ([T_min, T_max], optional)

  • keys

  • ----------------

  • JointInverter (inverter)

  • {station (rms_per_station dict)

  • {station

  • float (rms_global)

  • list[str] (modalities)

  • dict (figure_paths)

  • dict

Examples

>>> agent = JointInversionAgent(
...     modalities=["mt", "tem"], n_layers=5, epochs=20
... )
>>> result = agent.execute({"path": "/data/L22PLT"})
>>> result["rms_global"]
0.31
SYSTEM_PROMPT: str = 'You are an expert in multi-modal geophysical joint inversion using deep learning.\nGiven a joint MT inversion result, write 4-5 sentences that:\n1. Describe the two modalities fused and their complementary depth sensitivities.\n2. Assess the joint prediction quality (RMS, depth range, station count).\n3. Compare the joint result to a single-modality approach where possible.\n4. Identify where the secondary modality most improved the primary inversion.\n5. Recommend validation (borehole, gravity, seismic) and next modelling steps.\nReply in plain scientific English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.ModelZooAgent(*, api_key=None, model=None, llm_provider='claude', cache_dir=None, force_download=False)#

Bases: BaseAgent

Browse, download, and run pre-trained EM inverters from the model zoo.

Parameters:
  • api_key (str)

  • model (model_info dict — zoo metadata for the requested)

  • llm_provider (str)

  • cache_dir (str or None) – Override default cache ~/.pycsamt/model_zoo/.

  • force_download (bool) – Re-download even if cached (default False).

  • keys (Output data)

  • ----------

  • action (str) – "list" (default), "download", or "predict".

  • model_name (str) – Required for "download" and "predict" actions. E.g. "mt1d-resnet-5layer-v1".

  • path (sites /) – Required for "predict" action.

  • output_dir (str, optional)

  • keys

  • ----------------

  • performed (action str — which action was)

  • (action="list") (models dict — full registry)

  • (action="download"/"predict") (``checkpoint_path``str — local path) –

  • model

  • {station (predictions dict —)

  • (action="predict") (rms_global float)

  • dict (figure_paths)

  • dict

Examples

List available models:

agent = ModelZooAgent()
r = agent.execute({"action": "list"})
for name, desc in r["models"].items():
    print(name, "—", desc[:60])

Download a checkpoint:

r = agent.execute(
    {"action": "download", "model_name": "mt1d-resnet-5layer-v1"}
)
print(r["checkpoint_path"])

Predict on observed sites (fine-tune skipped if weights unavailable):

r = agent.execute(
    {
        "action": "predict",
        "model_name": "mt1d-resnet-5layer-v1",
        "path": "/data/WILLY_EDIs",
        "output_dir": "/out/zoo_predict",
    }
)
print(r["rms_global"])
SYSTEM_PROMPT: str = 'You are an expert in pre-trained geophysical AI models.\nGiven a model zoo query result, write 2-3 sentences that:\n1. Describe which pre-trained model was used and its provenance (architecture, training data).\n2. Comment on the prediction quality (RMS, reliability) relative to the expected use case.\n3. Recommend whether the user should fine-tune on their own data or use the pre-trained weights directly.\nReply in plain English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.TensorRotationAgent(*, api_key=None, model=None, llm_provider='claude', strike_deg=0.0)#

Bases: BaseAgent

Rotate impedance tensors and tipper vectors by a fixed strike angle.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • strike_deg (float — counter-clockwise rotation angle (degrees)) – Default rotation angle (degrees). Overridden by input_data["strike_deg"].

  • keys (Output data)

  • ----------

  • path (sites /)

  • strike_deg

  • output_dir (str — directory for rotated EDI files)

  • overwrite (bool — allow overwriting existing files (default False))

  • file_suffix (str — appended to station name, e.g. "_rot")

  • keys

  • ----------------

  • applied (strike_deg float — angle)

  • paths (written_paths list[str] — successfully written EDI)

  • list[str] (failed_stations)

  • int (n_written)

  • quality) (z_diag_reduction float — mean Zxx/Zxy before − after (proxy for rotation)

  • dict (figure_paths)

  • dict

Examples

>>> agent = TensorRotationAgent(strike_deg=42.0)
>>> r = agent.execute(
...     {
...         "path": "/data/WILLY_EDIs",
...         "output_dir": "/data/WILLY_rotated",
...     }
... )
>>> print(r["n_written"], "EDIs written")
SYSTEM_PROMPT: str = 'You are an expert in MT tensor rotation and coordinate-frame correction.\nGiven a rotation result, write 3-4 sentences that:\n1. State the rotation angle applied and the original coordinate frame.\n2. Describe how the off-diagonal impedances (Zxy, Zyx) changed after rotation.\n3. Assess whether the rotation removed apparent 2-D coupling from the diagonal terms.\n4. Recommend whether further refinement (per-frequency rotation, decomposition) is needed.\nReply in plain English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.EDIExportAgent(*, api_key=None, model=None, llm_provider='claude', file_pattern='{station}.edi', overwrite=False)#

Bases: BaseAgent

Write processed Sites to EDI files on disk.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • file_pattern (str, optional — override default) – Filename format using {station} placeholder. Default "{station}.edi".

  • overwrite (bool, optional — override default) – Overwrite existing files (default False).

  • keys (Output data)

  • ----------

  • path (sites /)

  • output_dir (str — target directory (created if absent))

  • file_pattern

  • overwrite

  • keys

  • ----------------

  • list[str] (written_paths)

  • list[tuple[str (failed)

  • (station (str]] )

  • message) (error)

  • int (n_failed)

  • int

  • str (output_dir)

Examples

>>> agent = EDIExportAgent()
>>> r = agent.execute(
...     {
...         "path": "/data/WILLY_EDIs",
...         "output_dir": "/out/willy_corrected",
...     }
... )
>>> print(r["n_written"], "EDIs exported")
SYSTEM_PROMPT: str = 'You are an expert in MT data management and EDI file format conventions.\nGiven an EDI export result, write 2-3 sentences that:\n1. Confirm how many files were written and their location.\n2. Note any stations that failed and the likely cause.\n3. Recommend next steps for the exported data (e.g. inversion, external QC).\nReply in plain English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.TipperAnalysisAgent(*, api_key=None, model=None, llm_provider='claude', convention='wiese', use_imag=False, period_ref=None)#

Bases: BaseAgent

Analyse tipper vectors and plot induction arrows.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • convention (str, optional) – Arrow convention. Wiese (default) points toward conductors in the real-part convention; Parkinson points toward them.

  • use_imag (bool, optional) – Use imaginary tipper parts for deeper structure (default False).

  • period_ref (arrow_table pandas.DataFrame — induction arrows at) – Reference period (s) for the induction arrow map. None uses the geometric mean of available periods.

  • keys (Output data)

  • ----------

  • path (sites /)

  • convention

  • use_imag

  • period_ref

  • output_dir (str, optional)

  • keys

  • ----------------

  • per-(station (tipper_table pandas.DataFrame —)

  • period)

  • period_ref

  • map (period_ref float — period used for arrow)

  • int (n_stations_with_tipper)

  • dict (figure_paths)

  • dict

Examples

>>> agent = TipperAnalysisAgent(convention="wiese")
>>> r = agent.execute({"path": "/data/WILLY_EDIs"})
>>> r["n_stations_with_tipper"]
12
SYSTEM_PROMPT: str = 'You are an expert in magnetotelluric tipper analysis and 3-D structure interpretation.\nGiven a tipper analysis result, write 4-5 sentences that:\n1. Describe the general tipper magnitude pattern across the survey (strong/weak, frequency dependence).\n2. Identify stations with anomalously large tipper amplitudes and their likely cause.\n3. Interpret the induction arrow direction(s) do they point toward or away from a conductor?\n4. Assess the 3-D character of the survey based on tipper consistency along the profile.\n5. Recommend whether a 3-D inversion is warranted or whether 2-D is sufficient.\nReply in plain scientific English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.SensitivityAgent(*, api_key=None, model=None, llm_provider='claude', component='xy', rho_override=None, depth_max=None)#

Bases: BaseAgent

Bostick sensitivity kernels and vertical resolution analysis.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • component (str, optional) – Impedance component for ρa and Bostick depth (default 'xy').

  • rho_override (float, optional) – Fixed background resistivity (Ω·m) for the analytical ΔD formula. None uses the measured ρa per frequency pair.

  • depth_max (float, optional — km) – Clip depth axis at this value in km (default: auto from data).

  • keys (Output data)

  • ----------

  • path (sites /)

  • component

  • rho_override

  • period_range ([T_min, T_max], optional)

  • depth_max

  • output_dir (str, optional)

  • keys

  • ----------------

  • per-(station (resolution_table pandas.DataFrame —)

  • freq-pair)

  • {station (doi_per_station dict)

  • float (mean_doi_km)

  • dict (figure_paths)

  • dict

Examples

>>> agent = SensitivityAgent(component="xy")
>>> r = agent.execute({"path": "/data/WILLY_EDIs"})
>>> print(r["mean_doi_km"], "km mean DOI")
SYSTEM_PROMPT: str = "You are an expert in MT data resolution and depth-of-investigation analysis.\nGiven a sensitivity analysis result, write 4-5 sentences that:\n1. Describe the maximum Bostick depth reached by the data at the lowest frequency.\n2. Identify depth ranges with poor resolution (large ΔD) and their cause.\n3. State which stations have the best and worst overall depth coverage.\n4. Advise on whether the target depth is within the data's sensitivity window.\n5. Recommend frequency additions or deletions to improve depth resolution.\nReply in plain scientific English.\n"#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.FrequencyDecimationAgent(*, api_key=None, model=None, llm_provider='claude', n_per_decade=6, snr_threshold=3.0, period_range=None, component='xy')#

Bases: BaseAgent

Select optimal periods from MT data for inversion.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • n_per_decade (int, optional) – Number of periods to keep per decade of period range (default 6).

  • snr_threshold (float, optional) – Minimum SNR value to retain a frequency (default 3.0). Frequencies below this are excluded as dead-band.

  • period_range ([T_min, T_max], optional) – Hard period bounds in seconds. None uses the full data range.

  • component ({'xy', 'yx'}) – Component used for SNR proxy (default 'xy').

  • keys (Output data)

  • ----------

  • path (sites /)

  • qc_result (AgentResult or dict, optional — output from DataQCAgent) – (provides per-frequency SNR scores; if absent a proxy is computed)

  • n_per_decade

  • snr_threshold

  • period_range

  • output_dir (str, optional)

  • keys

  • ----------------

  • {station (dead_band_mask dict)

  • (station (n_original int — total available)

  • cells (n_selected int — retained)

  • cells

  • float (selection_ratio)

  • {station

  • dict (figure_paths)

  • dict

SYSTEM_PROMPT: str = 'You are an expert in MT data selection and frequency decimation for inversion.\nGiven a period decimation result, write 3-4 sentences that:\n1. State how many periods were selected versus available, and the selection ratio.\n2. Identify which frequency bands were excluded and the likely reason (dead band, low SNR).\n3. Confirm whether the selected periods cover the target depth range adequately.\n4. Recommend any additional frequencies that should be included or excluded.\nReply in plain English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.InversionComparisonAgent(*, api_key=None, model=None, llm_provider='claude')#

Bases: BaseAgent

Compare two resistivity inversion results.

Each result is either an AgentResult from an inversion agent, or a plain dict with keys pred_rho and depths_km.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • keys (Output data)

  • ----------

  • result_a (AgentResult or dict) – First model. Must contain "pred_rho" (n_stations × n_layers) or "predictions" dict {station: log₁₀ρ array}.

  • result_b (AgentResult or dict) – Second model. Same structure as result_a.

  • label_a (str — name for result_a (default "Model A"))

  • label_b (str — name for result_b (default "Model B"))

  • depths_km (ndarray, optional — shared depth axis (km))

  • station_names (list[str], optional)

  • output_dir (str, optional)

  • keys

  • ----------------

  • sections (correlation float — Pearson ρ between log₁₀ρ)

  • log₁₀(Ω·m) (rmse float — RMSE in)

  • (n_layers (difference ndarray)

  • B (n_stations) — A −)

  • str (label_b)

  • str

  • dict (figure_paths)

  • dict

SYSTEM_PROMPT: str = 'You are an expert in MT inversion model evaluation and comparison.\nGiven two inversion results, write 4-5 sentences that:\n1. State the overall similarity (correlation, RMSE) between the two models.\n2. Identify depth ranges or stations where the two models disagree most.\n3. Discuss which model is more physically plausible and why.\n4. Recommend which result to use for geological interpretation.\n5. Suggest additional constraints (e.g. borehole, gravity) to discriminate them.\nReply in plain scientific English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.ResistivityMapAgent(*, api_key=None, model=None, llm_provider='claude', depth_indices=None, interp_method='linear', grid_n=50)#

Bases: BaseAgent

Build horizontal resistivity depth-slice maps from 1-D inversion results.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • depth_indices (list[int], optional) – Layer indices to map (0-based). None maps 3 evenly spaced layers.

  • interp_method ({'linear', 'nearest', 'idw'}) – Interpolation method for gridding station values (default 'linear').

  • grid_n (int) – Number of grid cells per axis (default 50).

  • keys (Output data)

  • ----------

  • predictions (dict {station: ndarray} — log₁₀ρ per layer) – (from AIInversionAgent, Inv3DAgent, etc.)

  • station_coords (dict {station: (x, y)} or ndarray (n_sta, 2) — metres)

  • depths_km (ndarray — depth axis (km))

  • depth_indices

  • output_dir (str, optional)

  • keys

  • ----------------

  • per-depth (depth_maps list[dict] )

  • list[float] (depth_levels_km)

  • dict (figure_paths)

  • dict

SYSTEM_PROMPT: str = 'You are an expert in pseudo-3D resistivity interpretation from MT surveys.\nGiven a set of horizontal resistivity maps, write 4-5 sentences that:\n1. Describe the dominant resistivity pattern at each depth level.\n2. Identify lateral contrasts that suggest geological boundaries or structures.\n3. Note any stations that appear anomalous at specific depths.\n4. Discuss the reliability of the interpolation given station spacing.\n5. Recommend drilling targets or geological follow-up based on the maps.\nReply in plain scientific English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.BatchSurveyAgent(*, api_key=None, model=None, llm_provider='claude', workflow='qc', n_jobs=1)#

Bases: BaseAgent

Process multiple MT profiles through a shared agent chain.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • workflow (str, optional — override constructor default) – Pre-defined workflow key: 'qc', 'ai_inversion', 'phase_analysis', 'sensitivity', 'tipper'. Determines which agents are chained for each profile.

  • n_jobs (int, optional) – Parallel workers. -1 = all CPU cores. 1 = sequential (default).

  • keys (Output data)

  • ----------

  • profiles (dict {name: path} or list[str]) – Profile names to paths, or a list of paths (names auto-assigned).

  • workflow

  • n_jobs

  • output_dir (str, optional)

  • call. (Any additional keys are forwarded to each agent's execute())

  • keys

  • ----------------

  • {name (profile_results dict)

  • metrics (summary_table pandas.DataFrame — per-profile)

  • int (n_failed)

  • int

  • dict (figure_paths)

  • dict

SYSTEM_PROMPT: str = 'You are an expert in large-scale MT survey processing and quality control.\nGiven a batch processing result, write 3-4 sentences that:\n1. Report how many profiles succeeded and how many failed.\n2. Identify which profiles have the worst QC scores or highest RMS.\n3. Highlight any systematic issues across profiles (e.g. dead band at same frequency).\n4. Recommend which profiles need re-processing or manual inspection.\nReply in plain English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.InversionBackendAgent(*, api_key=None, model=None, llm_provider='claude', backend='builtin', dimension='1d', method='mt', n_layers=5, max_iter=80, regularization='smooth', error_floor=0.05)#

Bases: BaseAgent

Drive pycsamt.inversion physics-based backends.

Parameters:
  • api_key (str)

  • model (str)

  • llm_provider (str)

  • backend (str, optional overrides) – Inversion backend: 'builtin' (default), 'simpeg', 'pygimli', 'occam2d', 'modem'.

  • dimension (str, optional overrides) – '1d' (default), '2d', or '3d'.

  • method (str, optional overrides) – 'mt' (default), 'amt', 'csamt', or 'tdem'.

  • n_layers (int, optional overrides) – Number of depth layers for 1-D inversion (default 5).

  • max_iter (int, optional overrides) – Maximum inversion iterations (default 80).

  • regularization (optional overrides) – 'smooth' (default), 'damped', or 'blocky'.

  • error_floor (optional overrides) – Relative data error floor (default 0.05 = 5 %).

  • keys (Output data)

  • ----------

  • path (sites /)

  • backend

  • dimension

  • method

  • n_layers

  • max_iter

  • regularization

  • error_floor

  • backend_options (dict, optional — forwarded to InversionConfig)

  • output_dir (str, optional)

  • keys

  • ----------------

  • InversionResult (inversion_result)

  • float (rms)

  • int (n_iter)

  • (n_layers (log_rho_section ndarray)

  • n_stations)

  • None (station_names list[str] or)

  • str (dimension)

  • str

  • dict (figure_paths)

  • dict

SYSTEM_PROMPT: str = 'You are an expert in MT inversion and subsurface resistivity modelling.\nGiven an inversion result, write 4-5 sentences that:\n1. State the backend used, dimensionality, and convergence (RMS, n_iter).\n2. Describe the recovered resistivity model (range, dominant structures).\n3. Assess the fit quality and whether the RMS target was reached.\n4. Identify stations or depth ranges with elevated misfit.\n5. Recommend regularisation adjustments or mesh refinements for the next run.\nReply in plain scientific English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.PipelineAgent(*, api_key=None, model=None, llm_provider='claude', preset='basic_qc', param_overrides=None)#

Bases: BaseAgent

LLM-assisted MT processing pipeline selection and interpretation.

Two modes of operation:

Guided mode — pass "request" in input_data. The LLM recommends a preset and any parameter overrides, then the pipeline is built and run automatically.

Direct mode — pass "preset" (name string) or "steps" (list of step codes) in input_data. No pre-run LLM call is made; the pipeline is built directly from those instructions.

In both modes the post-run LLM call interprets the PipelineResult as a narrative.

Parameters:
  • api_key (str) – Standard LLM configuration inherited from BaseAgent.

  • model (str) – Standard LLM configuration inherited from BaseAgent.

  • llm_provider (str) – Standard LLM configuration inherited from BaseAgent.

  • preset (str, optional) – Default preset name used when input_data contains neither "preset" nor "steps" nor "request". Defaults to "basic_qc" (safe first-pass).

  • param_overrides (dict, optional) – Default parameter overrides applied on top of any preset or step list. Format: {step_code: {param: value}}.

  • keys (Output data)

  • ----------

  • path (sites /) – Raw MT/AMT sites to process.

  • request (str, optional) – Natural-language description of dataset and goals. Triggers a pre-run LLM call that recommends preset + parameter overrides.

  • preset – Named preset — overrides constructor default.

  • steps (list of str, optional) – Explicit ordered list of step codes. Ignored when "preset" is set.

  • param_overrides – Per-step parameter overrides — merged on top of constructor defaults.

  • output_dir (str or None, optional) – Root directory for pipeline output files (EDI, plots, YAML config).

  • keys

  • ----------------

  • agents (sites_out Processed Sites — ready for downstream)

:param pipeline_result PipelineResult object: :param preset_used Name of the preset that was run (or "custom"): :param steps_run List of step code strings that were executed: :param n_sites_in Number of input stations: :param n_sites_out Number of stations after processing: :param n_errors Number of steps that raised an error: :param recommendation Dict returned by LLM pre-run call (or None):

Examples

Guided mode:

agent = PipelineAgent()
result = agent.execute(
    {
        "sites": sites,
        "request": "50 Hz grid noise, possible static shift, Occam2D target",
        "output_dir": "willy_pipeline/",
    }
)
processed = result["sites_out"]
print(result.llm_interpretation)

Direct mode:

agent = PipelineAgent(preset="full_processing")
result = agent.execute(
    {
        "sites": sites,
        "param_overrides": {"NR001": {"mains_hz": 60}},
    }
)

Chain with Occam2DAgent via AgentCoordinator:

from pycsamt.agents import (
    AgentCoordinator,
    PipelineAgent,
    Occam2DAgent,
)

coord = AgentCoordinator("willy_full")
coord.add_step(
    "pipeline",
    PipelineAgent(preset="full_processing"),
    input_fn=lambda r: {"sites": r["load"].data["sites"]},
)
coord.add_step(
    "invert",
    Occam2DAgent(),
    input_fn=lambda r: {"sites": r["pipeline"].data["sites_out"]},
)
SYSTEM_PROMPT: str = 'You are an expert MT data processing specialist reviewing pipeline execution results.\nGiven a processing summary, write 3–4 sentences that:\n1. Describe the overall data quality change (stations retained, step errors if any).\n2. Highlight which steps were most impactful or took the most time.\n3. Comment on whether the stated processing objectives appear to have been met.\n4. Recommend a concrete follow-up action (further correction, QC plot review,\n   or readiness for inversion).\nReply in plain English. No bullet points or markdown.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.Mare2DEMAgent(*, api_key=None, model=None, llm_provider='claude', n_procs=4, use_mpi=True, initial_rho=1.0, target_rms=1.0, max_iterations=150)#

Bases: BaseAgent

Orchestrate a complete MARE2DEM 2.5-D EM inversion workflow.

Parameters:
  • api_key (str or None) – LLM API key (Anthropic / OpenAI / Gemini).

  • model (str or None) – LLM model identifier; defaults to the provider’s recommended model.

  • llm_provider (str) – "claude" (default), "openai", "gemini", "deepseek", or "minimax".

  • n_procs (int, optional) – Number of MPI processes for MARE2DEM (default 4).

  • use_mpi (bool) – Whether to prefix the binary with mpirun -np n_procs (default True). Set to False only for single-process debug builds.

  • initial_rho (float, optional) – Starting half-space resistivity in Ω·m (default 1.0).

  • target_rms (float, optional) – Target normalised RMS misfit (default 1.0).

  • max_iterations (int, optional) – Maximum Occam inversion iterations (default 150).

  • keys (Output data)

  • ----------

  • path (sites /) – EDI source (directory, files, or a loaded Sites). The agent converts the impedances to a MARE2DEM .emdata file via make_mt_data_from_edi() (TE = Zxy, TM = Zyx). Used when emdata/mt/csem are not supplied — this is the pathway the workflow orchestrator uses.

  • error_floor (float, optional) – Relative error floor applied to the TE/TM apparent resistivities built from EDI data (default 0.05 = 5 %).

  • output_modes (str, optional) – Data types written from EDI data: "all" (default), "TE", "TM", "TE+tipper", or "all impedance".

  • emdata (str or path-like, optional) – Path to an existing .emdata data file. When supplied the agent copies it to the output directory and uses it directly.

  • resistivity (str or path-like, optional) – Path to an existing starting .resistivity file. When omitted a homogeneous half-space model is written.

  • mt (dict, optional) – Keyword arguments for MTSurveyConfig when building the data file from survey parameters rather than a pre-existing file.

  • csem (dict, optional) – Keyword arguments for CSEMSurveyConfig.

  • topo (float or array-like, optional) – Topography for receiver/transmitter placement. Flat scalar (e.g. -1000.0 m for a flat seafloor) or an (n, 2) array of [y, z] pairs. Default 0.0 (flat surface).

  • output_dir (str) – Directory that will receive all MARE2DEM input files and run output. Created if absent (default "pycsamt_mare2dem").

  • mode ({"prepare", "run", "report"}) –

    Workflow mode:

    • "prepare" — write input files only (default).

    • "run" — write files then launch MARE2DEM.

    • "report" — scan an existing run directory and return results without (re-)running.

  • source_dir (str or path-like, optional) – Explicit path to the MARE2DEM source tree. Passed to SourceManager; auto-resolved when omitted.

  • download_source (bool) – When True and the binary is missing, execute() automatically runs SourceManager.download() and SourceManager.build(). Defaults to False (raises a warning instead).

  • n_procs – Per-call override for n_procs.

  • max_iterations – Per-call override.

  • target_rms – Per-call override.

  • initial_rho – Per-call override.

  • keys

  • ----------------

  • data_path (Path or None)

  • resistivity_path (Path or None)

  • settings_path (Path or None)

  • binary_found (bool)

  • source_downloaded (bool)

  • n_mt_receivers (int)

  • n_csem_transmitters (int)

  • n_data (int)

  • final_rms (float or None)

  • n_iterations (int)

  • converged (bool)

  • result (InversionResult or None)

  • output_dir

Examples

Prepare input files from an existing data file:

>>> agent = Mare2DEMAgent(n_procs=8)
>>> result = agent.execute(
...     {
...         "emdata": "/data/survey.emdata",
...         "output_dir": "/run/mare2dem",
...     }
... )
>>> print(result["data_path"])
/run/mare2dem/mare2dem.emdata

Build from MT survey parameters (flat seafloor at −1000 m):

>>> import numpy as np
>>> result = agent.execute(
...     {
...         "mt": {
...             "frequencies": list(np.logspace(-3, 3, 20)),
...             "rx_y": list(np.linspace(-5000, 5000, 20)),
...             "rx_type": "marine",
...             "lTE": True,
...             "lTM": True,
...         },
...         "topo": -1000.0,
...         "output_dir": "/run/mare2dem_mt",
...     }
... )

Run a full inversion (requires compiled MARE2DEM binary):

>>> result = agent.execute(
...     {
...         "emdata": "survey.emdata",
...         "output_dir": "./run",
...         "mode": "run",
...         "n_procs": 16,
...     }
... )
>>> print(result["final_rms"])
0.98

Report results from a completed run directory:

>>> result = agent.execute(
...     {
...         "output_dir": "./run",
...         "mode": "report",
...     }
... )
>>> result["converged"]
True
SYSTEM_PROMPT: str = 'You are an expert in 2.5-D EM inversion using MARE2DEM.\nGiven a summary of the survey geometry, input files, and inversion\noutcome, write 4–5 sentences that:\n1. Assess the receiver and transmitter coverage relative to the target depth.\n2. Comment on the starting resistivity model and Occam regularisation settings.\n3. Evaluate the convergence: is the final RMS close to target? Any stagnation?\n4. Recommend parallel decomposition settings (tx/rx per group) for the cluster size.\n5. Flag any data issues (low-amplitude offsets, asymmetric MT responses, topo artefacts).\nReply in plain English. Be specific about MARE2DEM parameters.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.PINNInversionAgent(*, dim=1, n_layers=10, depth_max=2000.0, smoothness_weight=0.01, lateral_weight=0.005, graph_weight=0.005, radius=5000.0, epochs=None, lr=0.01, solver='mt1d', comp='xy', api_key=None, model=None, llm_provider='claude')#

Bases: BaseAgent

PINN-based MT inversion without labelled data.

Optimises a layered Earth by minimising a physics-informed loss via Adam gradient descent. Supports 1-D per-station, joint 2-D profile, and quasi-3-D graph-coupled inversion.

Parameters:
  • dim (overrides) – Dimensionality. Default 1.

  • n_layers (overrides) – Number of layers including the halfspace. Default 10.

  • depth_max (float) – Maximum investigation depth in metres. Default 2000.0.

  • smoothness_weight (float) – Vertical regularisation weight. Default 0.01.

  • lateral_weight (float) – Lateral smoothness weight (2-D only). Default 0.005.

  • graph_weight (float) – Graph-Laplacian spatial weight (3-D only). Default 0.005.

  • radius (float) – Edge radius in metres for the 3-D graph. Default 5000.0.

  • epochs (overrides) – Adam iterations. None uses 500 for 1-D and 300 for 2-D / 3-D.

  • lr (float) – Adam learning rate. Default 1e-2.

  • solver ({"mt1d", "csamt1d"}) – Physics solver. Default "mt1d".

  • comp (str) – Impedance component (1-D only). Default "xy".

  • api_key (str | None) – LLM configuration (optional).

  • model (str | None) – LLM configuration (optional).

  • llm_provider (str) – LLM configuration (optional).

  • keys (Output data)

  • ----------

  • data (sites / path observed)

  • dir (output_dir optional figure/save)

  • dim

  • epochs

  • n_layers

  • keys

  • ----------------

  • object (inverter fitted inverter)

  • (n_layers (section ndarray) – log10-rho section matrix

  • n_stations) – log10-rho section matrix

  • (1-D) (models list of LayeredModel)

  • int (n_stations)

  • {station (rms_per_station dict)

  • float (rms_global)

  • None (residuals_df pandas.DataFrame or)

  • None

  • dict (figure_paths)

  • dict

Examples

>>> agent = PINNInversionAgent(dim=1, n_layers=10, epochs=200)
>>> res = agent.execute({"path": "/data/L22PLT"})
>>> res["rms_global"]
0.18
SYSTEM_PROMPT: str = 'You are an expert in physics-informed neural-network\ninversion for MT/CSAMT geophysics.\nGiven a PINN inversion result write 4-5 sentences:\n1. State dimensionality (1-D/2-D/3-D) and convergence.\n2. Report final RMS (log10 rho-ohm-m) and data fit.\n3. Describe the resistivity structure recovered.\n4. Flag stations or regions with high residuals.\n5. Recommend adjustments to epochs, regularisation,\n   or whether to switch to a hybrid approach.\nReply in plain scientific English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

class pycsamt.agents.HybridInversionAgent(*, dim=1, max_iter=200, smoothness_weight=0.005, lateral_weight=0.005, graph_weight=0.005, radius=5000.0, lr=0.005, solver='mt1d', comp='xy', n_freqs=32, api_key=None, model=None, llm_provider='claude')#

Bases: BaseAgent

Two-stage AI + physics MT inversion.

Stage 1 applies a pre-trained supervised AI inverter to obtain a starting model. Stage 2 refines it with physics-informed Adam gradient descent.

Parameters:
  • dim ({1, 2, 3}) – Dimensionality. Default 1.

  • max_iter (int) – Physics refinement iterations (Stage 2). Default 200.

  • smoothness_weight (float) – Vertical regularisation weight. Default 0.005.

  • lateral_weight (float) – Lateral smoothness weight (2-D only). Default 0.005.

  • graph_weight (float) – Graph-Laplacian weight (3-D only). Default 0.005.

  • radius (float) – Edge radius in metres for 3-D graph. Default 5000.0.

  • lr (float) – Adam learning rate for Stage 2. Default 5e-3.

  • solver ({"mt1d", "csamt1d"}) – Physics solver. Default "mt1d".

  • comp (str) – Impedance component (1-D only). Default "xy".

  • n_freqs (int) – Frequency-grid size fed to the 1-D AI inverter. Default 32.

  • api_key (str | None) – LLM configuration (optional).

  • model (str | None) – LLM configuration (optional).

  • llm_provider (str) – LLM configuration (optional).

  • keys (Input)

  • ----------

  • data (sites / path observed)

  • object (ai_inverter fitted AI inverter) – or path to checkpoint

  • ai_inverter (checkpoint alias for)

  • directory (output_dir optional save)

  • dim

  • max_iter

:param : :param smoothness_weight: :param : :param lateral_weight: :param : :param graph_weight: :type graph_weight: optional overrides :param Output data keys: :param —————-: :param inverter fitted HybridInverterXD: :param section ndarray (n_layers: Stage-2 log10-rho section :param n_stations): Stage-2 log10-rho section :param stage1_section ndarray — Stage-1 section: :param models list of LayeredModel (1-D): :param stage1_models list of LayeredModel (1-D): :param n_stations int: :param rms_per_station dict {station: :type rms_per_station dict {station: float} :param rms_global float (Stage-2): :param rms_stage1 float (Stage-1 for comparison): :param convergence_df pandas.DataFrame or None: :param residuals_df pandas.DataFrame or None: :param figures dict: :param figure_paths dict:

Examples

>>> from pycsamt.ai.inversion import EMInverter1D
>>> ai = EMInverter1D.load("checkpoint.npz")
>>> agent = HybridInversionAgent(dim=1, max_iter=100)
>>> res = agent.execute(
...     {
...         "path": "/data/L22PLT",
...         "ai_inverter": ai,
...     }
... )
>>> res["rms_global"]
0.14
SYSTEM_PROMPT: str = 'You are an expert in hybrid AI + physics-informed\ninversion for MT/CSAMT geophysics.\nGiven a hybrid inversion result write 4-5 sentences:\n1. Compare Stage-1 (AI) and Stage-2 (physics) RMS.\n2. Describe how much the physics step improved the fit.\n3. State the recovered resistivity structure.\n4. Flag stations where Stage-2 failed to improve on\n   Stage-1 or where residuals remain high.\n5. Recommend whether to retrain the AI component,\n   run more physics iterations, or proceed to 2-D.\nReply in plain scientific English.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult

2.26.5. Agent Modules#

pycsamt.agents._base

pycsamt.agents._base

pycsamt.agents.context

pycsamt.agents.context

pycsamt.agents.loader

pycsamt.agents.loader

pycsamt.agents.coordinator

pycsamt.agents.coordinator

pycsamt.agents.orchestrator

pycsamt.agents.orchestrator

pycsamt.agents.qc

pycsamt.agents.qc

pycsamt.agents.static_shift

pycsamt.agents.static_shift

pycsamt.agents.phase_analysis

pycsamt.agents.phase_analysis

pycsamt.agents.tensor_rotation

pycsamt.agents.tensor_rotation

pycsamt.agents.tipper_analysis

pycsamt.agents.tipper_analysis

pycsamt.agents.freq_decimation

pycsamt.agents.freq_decimation

pycsamt.agents.denoising

pycsamt.agents.denoising

pycsamt.agents.forward

pycsamt.agents.forward

pycsamt.agents.inversion_prep

pycsamt.agents.inversion_prep

pycsamt.agents.inversion_eval

pycsamt.agents.inversion_eval

pycsamt.agents.inversion_backend

pycsamt.agents.inversion_backend

pycsamt.agents.inversion_comparison

pycsamt.agents.inversion_comparison

pycsamt.agents.occam2d_agent

pycsamt.agents.occam2d_agent

pycsamt.agents.modem_agent

pycsamt.agents.modem_agent

pycsamt.agents.inv2d_agent

pycsamt.agents.inv2d_agent

pycsamt.agents.inv3d_agent

pycsamt.agents.inv3d_agent

pycsamt.agents.ai_inversion

pycsamt.agents.ai_inversion

pycsamt.agents.ensemble_agent

pycsamt.agents.ensemble_agent

pycsamt.agents.joint_agent

pycsamt.agents.joint_agent

pycsamt.agents.model_zoo_agent

pycsamt.agents.model_zoo_agent

pycsamt.agents.anomaly_agent

pycsamt.agents.anomaly_agent

pycsamt.agents.interpretation

pycsamt.agents.interpretation

pycsamt.agents.iot_agent

pycsamt.agents.iot_agent

pycsamt.agents.resistivity_map

pycsamt.agents.resistivity_map

pycsamt.agents.sensitivity

pycsamt.agents.sensitivity

pycsamt.agents.edi_export

pycsamt.agents.edi_export

pycsamt.agents.report

pycsamt.agents.report

pycsamt.agents.code_gen

pycsamt.agents.code_gen

pycsamt.agents.pipeline_agent

pycsamt.agents.pipeline_agent

pycsamt.agents.batch_survey

pycsamt.agents.batch_survey

pycsamt.agents.hybrid_agent

pycsamt.agents.hybrid_agent

pycsamt.agents.mare2dem_agent

pycsamt.agents.mare2dem_agent

pycsamt.agents.master

One-line front door to the pyCSAMT agent stack.

pycsamt.agents.metrics

pycsamt.agents.metrics

pycsamt.agents.package_qa

pycsamt.agents.package_qa PackageQAAgent — answer free-form questions about the pycsamt v2 package.

pycsamt.agents.pinn_agent

pycsamt.agents.pinn_agent

pycsamt.agents.plotting

pycsamt.agents.plotting

pycsamt.agents.router

pycsamt.agents.router

pycsamt.agents.tooling

pycsamt.agents.tooling

pycsamt.agents.web

pycsamt.agents.web

2.26.6. IoT Field Agent#

class pycsamt.agents.IoTFieldAgent(*, api_key=None, model=None, llm_provider='claude', method=None, write_manifest=False)#

Bases: BaseAgent

Assess and monitor an IoT-enabled EM field acquisition.

Parameters:
  • api_key (str, optional) – LLM configuration. When api_key is None the agent runs fully offline and llm_interpretation is None.

  • model (str, optional) – LLM configuration. When api_key is None the agent runs fully offline and llm_interpretation is None.

  • llm_provider (str, optional) – LLM configuration. When api_key is None the agent runs fully offline and llm_interpretation is None.

  • method (str, optional) – EM method hint ("amt", "mt", "csamt", "csem", "tdem", "tem"). Used to seed sessions built from packets and to tag the manifest. When omitted it is inferred from the telemetry.

  • write_manifest (bool, default False) – When True an AcquisitionManifest is built for every run (and written when manifest_path / output_dir is available).

  • keys (Output data)

  • ----------

  • order) (One acquisition source is required (tried in this)

  • session (FieldSession or mapping) – A live session, or a mapping from to_dict().

  • packets (iterable of TelemetryPacket or mapping) – Raw telemetry to fold into a fresh session.

  • sites (path / edis /) – An existing survey to seed a re-occupation session via field_session_from_edis() (no live packets).

  • deployment (DeploymentConfig, optional) – Declared device capabilities; tabulated via deployment_report().

  • keys

  • survey_id (str) – Identifier for sessions built from packets/EDIs (default "iot_survey").

  • now (float) – Reference epoch seconds for live latency / gap calculations.

  • output_dir (str) – Directory for saved figures and (when requested) the manifest.

  • manifest (bool) – Force building the acquisition manifest for this run.

  • manifest_path (str) – Explicit path to write the manifest JSON.

  • sign_key (str or bytes) – When given with a manifest, the written manifest is HMAC-signed.

  • sync_references (mapping) – Per-device reference clocks for batch_assess_sync().

  • energy_configs (iterable of EnergyConfig) – Device energy budgets for estimate_deployment_energy().

  • figures (bool, default True) – Whether to render dashboard/edge/power/sync figures.

  • api (bool) – Passed through to pycsamt table builders (API-object vs raw frame).

  • keys

  • ----------------

:param session the resolved FieldSession: :param status MonitoringStatus: :param status_table one-row monitoring-status table: :param telemetry_summary packet counts by device and topic: :param packet_table full telemetry packet table: :param station_table registered stations: :param pipeline_input acquisition hand-off dict for the processing flow: :param deployment_table device-capability table (when deployment given): :param sync_table clock-sync table (when sync_references given): :param power_table energy-budget table (when energy_configs given): :param manifest AcquisitionManifest (optional): :param manifest_path written manifest path (optional): :param signature manifest HMAC signature dict (when sign_key given): :param level monitoring level string (ok / warn / critical): :param issues list of issue strings: :param n_packets / n_stations / n_devices: :type n_packets / n_stations / n_devices: int :param figures dict of matplotlib Figure objects: :param figure_paths dict of saved figure paths (when output_dir set):

Examples

>>> agent = IoTFieldAgent()
>>> res = agent.execute({"packets": packets, "output_dir": "/out/iot"})
>>> res["level"]
'warn'
>>> res["status"].issues
['battery_min_v below 11.5 V on 2 device(s)']
>>> res["figures"]["dashboard"]
<Figure ...>
SYSTEM_PROMPT: str = 'You are an expert IoT field-operations analyst for pycsamt v2, supervising a\nlive AMT/MT/CSAMT/CSEM acquisition through its edge telemetry stream.\nGiven a survey monitoring summary, write 3-5 sentences that:\n1. State the overall health of the acquisition (healthy / degraded / critical).\n2. Call out the specific stations, channels, or devices that need field\n   attention, and the dominant failure mode (packet loss, high latency,\n   low battery, clock drift, edge rejections, sensor dropout).\n3. Explain the most likely field cause (power budget, GPS/clock, comms link,\n   contact resistance, powerline or near-field interference).\n4. Recommend the single most important field action to take right now.\nReply in plain English. No bullet points, no markdown headings.\n'#

Override in subclasses to give the LLM its domain expertise.

execute(input_data)#

Run this agent on input_data and return an AgentResult.

Subclasses must implement this method. The contract:

  • Reset self._last_cost = 0.0 at the top.

  • Record wall-clock time with t0 = time.time().

  • Return AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).

Parameters:

input_data (dict[str, Any])

Return type:

AgentResult