pycsamt.api.agents#

pycsamt.api.agents#

Global LLM provider configuration for all pycsamt agents.

Configure once — every agent inherits the active provider, API key, model, pricing overrides, and budget cap automatically.

Quick start#

from pycsamt.api.agents import AGENT_CONFIG

AGENT_CONFIG.configure(provider="claude", api_key="sk-ant-…")

from pycsamt.agents import DataQCAgent, PhaseAnalysisAgent
qc = DataQCAgent()          # inherits provider + key automatically
pt = PhaseAnalysisAgent()   # same

Or use the top-level convenience function:

from pycsamt.agents import configure_agents
configure_agents(provider="openai", api_key="sk-…")

Multi-provider workflow#

Store keys for several providers upfront, then switch freely:

AGENT_CONFIG.set_key("claude", "sk-ant-…")
AGENT_CONFIG.set_key("openai", "sk-…")
AGENT_CONFIG.set_key("gemini", "AIza…")

AGENT_CONFIG.switch("claude")      # all agents use Claude
# … some work …
AGENT_CONFIG.switch("openai")      # now all agents use OpenAI, no re-supply

Temporary override (context manager)#

with AGENT_CONFIG.using(provider="gemini", api_key="AIza…"):
    r = DataQCAgent().execute({"path": "/data"})
# original config is automatically restored here

Pricing — custom rates#

Override any rate or add a model not in the built-in table:

# update a rate that changed (USD per 1 M tokens)
AGENT_CONFIG.set_rate("claude", "claude-opus-4-8", input=12.0, output=60.0)

# register a model that isn't in the table yet
AGENT_CONFIG.set_rate("openai", "gpt-5-turbo", input=5.0, output=20.0)

# inspect the resolved rate for any provider/model
print(AGENT_CONFIG.get_rate("claude", "claude-sonnet-4-6"))
# → {"input": 3.0, "output": 15.0}

Pricing — session budget cap#

AGENT_CONFIG.set_budget(usd=2.0)   # raise BudgetExceededError above $2

AGENT_CONFIG.spent_usd             # float — accumulated so far
AGENT_CONFIG.remaining_usd         # float or None (None = no cap)

AGENT_CONFIG.reset_budget()        # zero the spend counter; keeps the cap
AGENT_CONFIG.reset_budget(cap=True)  # also remove the cap

Environment-variable fallback#

Keys are resolved in this order:

  1. Explicit key stored via AgentConfig.configure() / AgentConfig.set_key().

  2. Environment variable (first match wins per provider):

    Provider

    Environment variables checked

    "claude"

    ANTHROPIC_API_KEY, PYCSAMT_CLAUDE_API_KEY

    "openai"

    OPENAI_API_KEY, PYCSAMT_OPENAI_API_KEY

    "gemini"

    GEMINI_API_KEY, GOOGLE_API_KEY, GOOGLE_GENERATIVEAI_API_KEY, PYCSAMT_GEMINI_API_KEY

    "deepseek"

    DEEPSEEK_API_KEY, PYCSAMT_DEEPSEEK_API_KEY

    "minimax"

    MINIMAX_API_KEY, MINIMAX_M3, PYCSAMT_MINIMAX_API_KEY

Keys are also loaded automatically from .env.local at the repo root (without overwriting variables already set in the OS environment).

  1. None — agent runs without LLM support (regex/rule-based fallback).

Module Attributes

AGENT_CONFIG

Global singleton.

Functions

configure_agents(*, provider, api_key[, model])

Configure AGENT_CONFIG in one call and return it.

reset_agents(*[, keys])

Reset AGENT_CONFIG to its unconfigured state.

Classes

AgentConfig()

Global LLM configuration singleton for all pycsamt agents.

Exceptions

BudgetExceededError(spent, budget)

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

class pycsamt.api.agents.AgentConfig[source]#

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)[source]#

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)[source]#

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)[source]#

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)[source]#

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)[source]#

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)[source]#

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)[source]#

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)[source]#

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)[source]#

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)[source]#

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[source]#

Accumulated LLM spend this session (USD).

property remaining_usd: float | None[source]#

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

property provider: str | None[source]#

Currently active provider, or None if unconfigured.

property api_key: str | None[source]#

Resolved key for the active provider.

Resolution order: stored key → environment variable → None.

property model: str | None[source]#

Active model (explicit override, or provider default).

property is_configured: bool[source]#

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

resolve(provider, api_key, model)[source]#

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

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

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()[source]#

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)[source]#

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()[source]#

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]

pycsamt.api.agents.AGENT_CONFIG: AgentConfig = AgentConfig(unconfigured)#

Global singleton. Import and configure this object once per session.

exception pycsamt.api.agents.BudgetExceededError(spent, budget)[source]#

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.api.agents.configure_agents(*, provider, api_key, model=None)[source]#

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.api.agents.reset_agents(*, keys=True)[source]#

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