Skip to content

trading_agent.backends.backend

The base class for a model backend that answers one model call at a time.

The agent loop asks the backend for one reply, runs any tools the reply asks for, and asks again. The Ollama and OpenAI-compatible backends work this way. The Claude Code backend (milestone 3) runs the whole loop itself and has its own turn runner.

Typical usage example:

reply = await backend.complete(conversation, registry.definitions(), on_text)

BackendUnavailableError

BackendUnavailableError(message, retry_after_seconds=None)

Bases: Exception

The backend could not be reached or refused the request, so another backend should be tried.

Attributes:

Name Type Description
retry_after_seconds

How long the backend should be left alone when it is known, such as until a subscription limit resets; None leaves the router's usual waiting periods in charge.

Creates the error.

Parameters:

Name Type Description Default
message str

What went wrong.

required
retry_after_seconds float | None

How long to leave the backend alone, or None for the usual waiting periods.

None
Source code in src/trading_agent/backends/backend.py
def __init__(self, message: str, retry_after_seconds: float | None = None):
    """Creates the error.

    Args:
        message (str): What went wrong.
        retry_after_seconds (float | None): How long to leave the backend alone, or None for the usual waiting periods.
    """
    super().__init__(message)
    self.retry_after_seconds = retry_after_seconds

ModelReply dataclass

ModelReply(text, tool_calls, prompt_tokens, completion_tokens, reasoning='', cache_read_tokens=0, cost_usd=None)

One reply from the model.

Attributes:

Name Type Description
text str

The text the model wrote.

tool_calls list[ToolCall]

The tools it asks to run.

prompt_tokens int

How many tokens the request used, or 0 when the server did not say.

completion_tokens int

How many tokens the reply used, or 0 when the server did not say.

reasoning str

The model's visible thinking, which some servers require to be sent back; empty when there was none.

cache_read_tokens int

How many of the prompt tokens the server read from its cache, or 0 when it did not say.

cost_usd float | None

What the call cost in US dollars when the backend knows its prices, or None.

ModelBackend

ModelBackend(name, model)

A backend the agent loop calls once per round. Subclasses do the actual call.

Attributes:

Name Type Description
name

The backend's name in trading_agent.toml.

model

The model's name on its server, as used now.

configured_model

The model trading_agent.toml names, which use_model(None) returns to.

Creates the backend.

Parameters:

Name Type Description Default
name str

The backend's name in trading_agent.toml.

required
model str

The model's name on its server.

required
Source code in src/trading_agent/backends/backend.py
def __init__(self, name: str, model: str):
    """Creates the backend.

    Args:
        name (str): The backend's name in trading_agent.toml.
        model (str): The model's name on its server.
    """
    self.name = name
    self.model = model
    self.configured_model = model

use_model

use_model(model)

Switches to another model on the same server, or back to the configured one.

Parameters:

Name Type Description Default
model str | None

The model's name, or None for the configured model.

required
Source code in src/trading_agent/backends/backend.py
def use_model(self, model: str | None) -> None:
    """Switches to another model on the same server, or back to the configured one.

    Args:
        model (str | None): The model's name, or None for the configured model.
    """
    self.model = model or self.configured_model

thinking

thinking()

The thinking level the backend uses now.

Backends whose provider decides on thinking keep this default.

Returns:

Type Description
str | None

The level, or None when the backend has no thinking setting.

Source code in src/trading_agent/backends/backend.py
def thinking(self) -> str | None:
    """The thinking level the backend uses now.

    Backends whose provider decides on thinking keep this default.

    Returns:
        str | None: The level, or None when the backend has no thinking setting.
    """
    return None

set_thinking

set_thinking(level)

Changes whether, or how hard, the model thinks from the next call on.

Backends whose provider decides on thinking keep this default.

Parameters:

Name Type Description Default
level str | None

One of turn_runner.THINKING_LEVELS, or None for the setting in trading_agent.toml.

required

Returns:

Type Description
bool

True when the level was applied, False when the backend has no thinking setting.

Source code in src/trading_agent/backends/backend.py
def set_thinking(self, level: str | None) -> bool:
    """Changes whether, or how hard, the model thinks from the next call on.

    Backends whose provider decides on thinking keep this default.

    Args:
        level (str | None): One of turn_runner.THINKING_LEVELS, or None for the setting in trading_agent.toml.

    Returns:
        bool: True when the level was applied, False when the backend has no thinking setting.
    """
    del level
    return False

complete async

complete(conversation, tool_definitions, on_text)

Asks the model for its next reply.

Parameters:

Name Type Description Default
conversation Conversation

The conversation so far.

required
tool_definitions list[dict[str, Any]]

The tools the model may call.

required
on_text Callable[[str], None]

Called with each piece of text as it streams in.

required

Returns:

Type Description
ModelReply

The complete reply.

Raises:

Type Description
NotImplementedError

Always, because subclasses must override this method.

Source code in src/trading_agent/backends/backend.py
async def complete(
    self,
    conversation: conversation_module.Conversation,
    tool_definitions: list[dict[str, Any]],
    on_text: Callable[[str], None],
) -> ModelReply:
    """Asks the model for its next reply.

    Args:
        conversation (conversation_module.Conversation): The conversation so far.
        tool_definitions (list[dict[str, Any]]): The tools the model may call.
        on_text (Callable[[str], None]): Called with each piece of text as it streams in.

    Returns:
        ModelReply: The complete reply.

    Raises:
        NotImplementedError: Always, because subclasses must override this method.
    """
    raise NotImplementedError