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.
Agent catalogue#
ContextInputAgentTranslate a natural-language request into a structured workflow config.
MTLoaderAgentLoad EDI / AVG / J files into a
Sitesobject with a per-station QC report.AgentCoordinatorChain agents into named workflows with checkpointing and cost tracking.
DataQCAgentSNR section, dead-band detection, per-station quality scores.
StaticShiftAgentStatic-shift detection and correction (AMA / LOESS / spatial median).
PhaseAnalysisAgentPhase tensor, strike, dimensionality, Mohr circles, Argand diagrams.
ForwardModelAgent1-D, 2-D, and 3-D MT forward modelling.
InversionPrepAgent/Occam2DAgent/ModEmAgent/Mare2DEMAgentWrite Occam2D, ModEM3D, or MARE2DEM inversion input files; optionally run the binary and scan results.
InversionEvaluationAgentCompute RMS, residual PT section, misfit pseudosection.
InterpretationAgentMap resistivity ranges to lithology; correlate with borehole logs.
ReportAgentAssemble all figures and tables into Markdown / HTML / PDF.
CodeGenerationAgentEmit a standalone reproducible Python script from the workflow config.
WorkflowOrchestratorAgentNL → classify workflow → build and run the correct agent chain.
AgentMasterOne-line front door over the orchestrator:
AgentMaster(provider="anthropic").run("...").DenoisingAgentRPCA / Hampel / EMAP / AI-CAE denoising.
AnomalyDetectionAgentUnsupervised CAE anomaly flagging per (station, frequency).
AIInversionAgentEnd-to-end 1-D AI inversion (ResNet / CNN / FCN).
Inv2DAgentU-Net 2-D profile inversion with lateral continuity.
Inv3DAgentGCN 3-D spatial inversion using inter-station graph message-passing.
EnsembleAgentEnsemble 1-D inversion with conformal uncertainty bands.
JointInversionAgentDRCNN multi-modal joint inversion (MT + TEM / CSAMT / gravity).
ModelZooAgentBrowse, download, and deploy pre-trained EM inverter checkpoints.
PINNInversionAgentPhysics-informed 1-D/2-D/3-D MT inversion via Adam (no labelled training data required).
HybridInversionAgentTwo-stage AI warm-start + physics refinement for 1-D, 2-D, and 3-D MT inversion.
IoTFieldAgentMonitor an IoT-enabled AMT/MT/CSAMT/CSEM field acquisition from its edge telemetry: monitoring status, sync/power summaries, provenance manifest, and dashboard figures.
Supporting classes#
AgentResultStandardised return type for every agent.
BaseAgentAbstract base class; inherit to build custom agents.
Quick start#
Without an LLM key (regex fallback, no cost):
from pycsamt.agents import ContextInputAgent, MTLoaderAgent, AgentCoordinator
ctx = ContextInputAgent() # no api_key → pure regex
loader = MTLoaderAgent()
coord = AgentCoordinator("mt_qc")
coord.add_step("parse", ctx,
description="Parse request into config")
coord.add_step("load", loader,
input_fn=lambda r: {"path": r["parse"]["config"]["data_path"]},
description="Load EDI files and QC scan")
result = coord.execute(
{"request": "Load /data/EDIs, QC, period range 1e-4 to 1 s"},
dry_run=True,
)
With a Claude API key:
ctx = ContextInputAgent(api_key="sk-ant-…", llm_provider="claude")
res = ctx.execute({"request": "Load L22PLT EDIs, run PT analysis …"})
print(res["config"])
print(res.llm_interpretation)
LLM providers supported#
Anthropic Claude (default) —
llm_provider="claude"OpenAI —
llm_provider="openai"Google Gemini —
llm_provider="gemini"DeepSeek —
llm_provider="deepseek"MiniMax —
llm_provider="minimax"
Install the relevant client library before use:
pip install anthropic / pip install openai /
pip install google-generativeai
DeepSeek and MiniMax reuse the openai package with a
custom base_url; no extra install is needed.
- class pycsamt.agents.AgentConfig#
Bases:
objectGlobal LLM configuration singleton for all pycsamt agents.
BaseAgentcallsresolve()on every instantiation andget_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) –
Truewhen 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
Nonewhen 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:
- Returns:
self — allows chaining.
- Return type:
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:
- Returns:
self.
- Return type:
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()orset_key(), or be available as an environment variable.- Parameters:
- Returns:
self.
- Return type:
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. PassFalseto clear only the active provider / model while keeping stored keys available for futureswitch()calls.- Returns:
self.
- Return type:
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:
- Returns:
self.
- Return type:
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:
- 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_usdreaches usd, any subsequent LLM call will raiseBudgetExceededErrorbefore the API call is made.- Parameters:
usd (float) – Maximum spend in USD for this session.
- Returns:
self.
- Return type:
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
Truealso remove the budget cap (set_budgetmust be called again to re-enable it). DefaultFalse— zeroes the counter but keeps the existing cap.- Returns:
self.
- Return type:
Examples
AGENT_CONFIG.reset_budget() # zero counter, keep cap AGENT_CONFIG.reset_budget(cap=True) # zero counter and remove cap
- property api_key: str | None#
Resolved key for the active provider.
Resolution order: stored key → environment variable →
None.
- 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.Resolution rules#
- If
api_keyis explicitly given: Use
provider,api_key, andmodel(or provider default) exactly as supplied. The global config is ignored.- If
api_keyisNone: If
providerequals 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.
- If
- 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:
- using(*, provider=None, api_key=None, model=None)#
Temporarily override the global config inside a
withblock.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:
- Yields:
AgentConfig – self 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 receiveapi_key=Noneeven whenANTHROPIC_API_KEY(or similar) is set in the OS environment. Usesthreading.localso 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:
RuntimeErrorRaised when an LLM call would be made after the session budget is used.
- pycsamt.agents.configure_agents(*, provider, api_key, model=None)#
Configure
AGENT_CONFIGin one call and return it.Equivalent to
AGENT_CONFIG.configure(...).- Parameters:
- Return type:
Examples
from pycsamt.agents import configure_agents configure_agents(provider="claude", api_key="sk-ant-…")
- pycsamt.agents.reset_agents(*, keys=True)#
Reset
AGENT_CONFIGto its unconfigured state.Equivalent to
AGENT_CONFIG.reset(...).- Parameters:
keys (bool) – When
True(default) also wipe stored per-provider keys.- Return type:
- 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:
objectStandardised 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
- classmethod failed(error, *, hint=None, elapsed=0.0)#
Convenience constructor for failure results.
- Parameters:
- Return type:
- class pycsamt.agents.BaseAgent(name, *, api_key=None, model=None, llm_provider='claude', section_preset='pseudosection')#
Bases:
ABCAbstract 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
Nonethe agent runs without LLM support and everyllm_interpretationfield will beNone.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_SECTIONpreset governs figures produced by this agent. Default"pseudosection".
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.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- query_llm(prompt, system_message=None, *, temperature=0.2, max_tokens=1024)#
Send prompt to the configured LLM and return the response text.
Returns
Nonewhen no API key is configured or all retries fail. Accumulates token cost intoself._last_cost.
- extract_json(text)#
Extract the first JSON object / array from text.
Returns
Nonewhen no valid JSON is found.
- class pycsamt.agents.AgentCoordinator(workflow_name='pycsamt_workflow', *, checkpoint_dir=None, verbose=True)#
Bases:
objectOrchestrate a sequence of pycsamt agents as a named workflow.
- Parameters:
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
selffor 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_dictkeys are earlier step names; each value is the step’sAgentResult.description (str)
required (bool)
- Returns:
selffor method chaining.- Return type:
- 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:
- execute(config, *, dry_run=False, resume=False)#
Run all workflow steps sequentially.
- Parameters:
- Returns:
datacontains one key per step name with itsAgentResult.- Return type:
- 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:
objectOne step in an
AgentCoordinatorworkflow.- 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 toagent.execute(). WhenNonethe coordinator passes the raw workflow config.description (str) – Human-readable description shown in the dry-run preview.
required (bool) – When
Falsea 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 viaAGENT_CONFIG.set_rate(...)are used.- Parameters:
- Returns:
Estimated cost in USD.
- Return type:
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'
- pycsamt.agents.get_rate(provider, model)#
Return the resolved
{"input": …, "output": …}rate.Delegates to
get_rate(), so any per-model overrides set viaAGENT_CONFIG.set_rate(...)take effect.
- 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')#
Bases:
objectValidated intermediate representation of a workflow request.
Produced by
ContextInputAgentand consumed byWorkflowOrchestratorAgent.- 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'.
- classmethod from_config(config, request='', provider='offline')#
Build a
WorkflowPlanfrom a config dict as returned byContextInputAgent.
- pycsamt.agents.validate_workflow_plan(plan, *, raise_on_error=False)#
Validate plan and optionally raise on errors.
- Parameters:
plan (WorkflowPlan)
raise_on_error (bool) – When True, raises
ValueErrorif errors exist.
- Returns:
errors – Empty when the plan is valid.
- Return type:
- class pycsamt.agents.MTLoaderAgent(*, api_key=None, model=None, llm_provider='claude', recursive=True, on_dup='replace')#
Bases:
BaseAgentLoad MT data from any pycsamt-supported format and assess quality.
- Parameters:
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.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- 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:
BaseAgentRun 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.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.PhaseAnalysisAgent(*, api_key=None, model=None, llm_provider='claude', skew_th=5.0, ellipt_th=0.1, band=None)#
Bases:
BaseAgentRun 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.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.ForwardModelAgent(*, api_key=None, model=None, llm_provider='claude', dim=1, freqs=None)#
Bases:
BaseAgentRun 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.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.ReportAgent(*, api_key=None, model=None, llm_provider='claude', report_title='MT/AMT Survey Report', formats=None)#
Bases:
BaseAgentGenerate 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.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.DenoisingAgent(*, api_key=None, model=None, llm_provider='claude', method='rpca', rank=2, half_window=3)#
Bases:
BaseAgentDenoise 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.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- 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:
BaseAgentTrain 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
inverterEMInverter1D: :parampredictionsdict {station: :typepredictionsdict {station: ndarray of log₁₀ ρ values} :parambest_modeldict with “resistivity” and “thickness” for first station: :paramrms_per_stationdict {station: :typerms_per_stationdict {station: float} :paramrms_globalfloat: :paramtrain_historydict (loss curves): :paramfiguresdict: :paramfigure_pathsdict: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
AIInversionAgentpre-loaded with a zoo checkpoint.- Parameters:
- Return type:
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.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.Inv2DAgent(*, api_key=None, model=None, llm_provider='claude', n_depth=40, n_freqs=32, n_components=4, arch='unet', n_train_profiles=200, n_stations_per_profile=20, epochs=30)#
Bases:
BaseAgent2-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).
n_components (int) – Number of impedance components in input (default 4: Re/Im × xy/yx).
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).
keys (Output data)
----------
path (sites /)
output_dir (str, optional)
period_range ([T_min, T_max], optional)
keys
----------------
ρ (pred_section ndarray (n_depth × n_stations) — log₁₀)
(km) (depths_km ndarray — depth axis)
list[str] (station_names)
float (rms_global)
EMInverter2D (inverter)
dict (figure_paths)
dict
- 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.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
- class pycsamt.agents.Inv3DAgent(*, api_key=None, model=None, llm_provider='claude', n_layers=5, n_freqs=32, n_train_profiles=150, epochs=30, radius=5000.0, hidden=(256, 128, 64), dropout=0.1, n_mc=20)#
Bases:
BaseAgent3-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).
n_train_profiles (int) – Number of synthetic 3-D training profiles (default 150).
epochs (int) – Training epochs (default 30).
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).
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)
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)
list[str] (station_names)
(n_sta
metres (2) —)
(n_sta
n_sta)
float (rms_global)
GCNInverter3D (inverter)
dict (figure_paths)
dict
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.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type:
Agent Modules#
pycsamt.agents._base |
|
pycsamt.agents.context |
|
pycsamt.agents.loader |
|
pycsamt.agents.coordinator |
|
pycsamt.agents.orchestrator |
|
pycsamt.agents.qc |
|
pycsamt.agents.static_shift |
|
pycsamt.agents.phase_analysis |
|
pycsamt.agents.tensor_rotation |
|
pycsamt.agents.tipper_analysis |
|
pycsamt.agents.freq_decimation |
|
pycsamt.agents.denoising |
|
pycsamt.agents.forward |
|
pycsamt.agents.inversion_prep |
|
pycsamt.agents.inversion_eval |
|
pycsamt.agents.inversion_backend |
|
pycsamt.agents.inversion_comparison |
|
pycsamt.agents.occam2d_agent |
|
pycsamt.agents.modem_agent |
|
pycsamt.agents.inv2d_agent |
|
pycsamt.agents.inv3d_agent |
|
pycsamt.agents.ai_inversion |
|
pycsamt.agents.ensemble_agent |
|
pycsamt.agents.joint_agent |
|
pycsamt.agents.model_zoo_agent |
|
pycsamt.agents.anomaly_agent |
|
pycsamt.agents.interpretation |
|
pycsamt.agents.iot_agent |
|
pycsamt.agents.resistivity_map |
|
pycsamt.agents.sensitivity |
|
pycsamt.agents.edi_export |
|
pycsamt.agents.report |
|
pycsamt.agents.code_gen |
|
pycsamt.agents.pipeline_agent |
|
pycsamt.agents.batch_survey |
|
pycsamt.agents.hybrid_agent |
|
pycsamt.agents.mare2dem_agent |
|
One-line front door to the pyCSAMT agent stack. |
|
pycsamt.agents.metrics |
|
pycsamt.agents.package_qa PackageQAAgent — answer free-form questions about the pycsamt v2 package. |
|
pycsamt.agents.pinn_agent |
|
pycsamt.agents.plotting |
|
pycsamt.agents.router |
|
pycsamt.agents.tooling |
|
pycsamt.agents.web |
IoT Field Agent#
- class pycsamt.agents.iot_agent.IoTFieldAgent(*, api_key=None, model=None, llm_provider='claude', method=None, write_manifest=False)[source]#
Bases:
BaseAgentAssess and monitor an IoT-enabled EM field acquisition.
- Parameters:
api_key (str, optional) – LLM configuration. When
api_keyisNonethe agent runs fully offline andllm_interpretationisNone.model (str, optional) – LLM configuration. When
api_keyisNonethe agent runs fully offline andllm_interpretationisNone.llm_provider (str, optional) – LLM configuration. When
api_keyisNonethe agent runs fully offline andllm_interpretationisNone.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
TrueanAcquisitionManifestis built for every run (and written whenmanifest_path/output_diris 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
sessionthe resolvedFieldSession: :paramstatusMonitoringStatus: :paramstatus_tableone-row monitoring-status table: :paramtelemetry_summarypacket counts by device and topic: :parampacket_tablefull telemetry packet table: :paramstation_tableregistered stations: :parampipeline_inputacquisition hand-off dict for the processing flow: :paramdeployment_tabledevice-capability table (whendeploymentgiven): :paramsync_tableclock-sync table (whensync_referencesgiven): :parampower_tableenergy-budget table (whenenergy_configsgiven): :parammanifestAcquisitionManifest(optional): :parammanifest_pathwritten manifest path (optional): :paramsignaturemanifest HMAC signature dict (whensign_keygiven): :paramlevelmonitoring level string (ok/warn/critical): :paramissueslist of issue strings: :paramn_packets/n_stations/n_devices: :typen_packets/n_stations/n_devices: int :paramfiguresdict of matplotlib Figure objects: :paramfigure_pathsdict of saved figure paths (whenoutput_dirset):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)[source]#
Run this agent on input_data and return an
AgentResult.Subclasses must implement this method. The contract:
Reset
self._last_cost = 0.0at the top.Record wall-clock time with
t0 = time.time().Return
AgentResult(elapsed_seconds=time.time()-t0, cost_estimate_usd=self._last_cost, ...).
- Parameters:
- Return type: