|
5 | 5 | from .history import MessageHistory |
6 | 6 | from .tracing import TraceableMixin |
7 | 7 | from .config import ConfigurableMixin |
8 | | -from .usage import UsageTrackableMixin |
| 8 | +from .usage import Usage, TokenUsage, UsageTrackableMixin, CostCalculator |
9 | 9 |
|
10 | 10 |
|
11 | 11 | class AgentAdapter(ABC, TraceableMixin, ConfigurableMixin, UsageTrackableMixin): |
12 | 12 | """Wraps an agent from any framework to provide a standard interface. |
13 | 13 |
|
14 | 14 | This Adapter provides: |
| 15 | +
|
15 | 16 | - Unified execution interface via `run()` |
16 | 17 | - Callback hooks for monitoring |
17 | 18 | - Message history management via getter/setter |
18 | 19 | - Framework-agnostic tracing |
| 20 | + - Automatic cost calculation from token usage (when a cost calculator is available) |
| 21 | +
|
| 22 | + Cost Tracking: |
| 23 | + Agent adapters track token usage from the underlying framework. To also |
| 24 | + compute cost, you can pass a ``cost_calculator`` and optionally a ``model_id``. |
| 25 | +
|
| 26 | + Most framework adapters auto-detect both the model ID (from the framework's |
| 27 | + agent object) and the cost calculator (using ``LiteLLMCostCalculator`` if |
| 28 | + litellm is installed). This means cost tracking often works with zero |
| 29 | + configuration. |
| 30 | +
|
| 31 | + To override or disable auto-detection, pass explicit values:: |
| 32 | +
|
| 33 | + adapter = SmolAgentAdapter( |
| 34 | + agent, name="researcher", |
| 35 | + cost_calculator=StaticPricingCalculator({...}), |
| 36 | + model_id="my-custom-model", |
| 37 | + ) |
| 38 | +
|
| 39 | + Pass ``cost_calculator=None`` explicitly to disable cost calculation |
| 40 | + even when auto-detection would otherwise enable it. |
19 | 41 | """ |
20 | 42 |
|
21 | | - def __init__(self, agent_instance: Any, name: str, callbacks: Optional[List[AgentCallback]] = None): |
| 43 | + def __init__( |
| 44 | + self, |
| 45 | + agent_instance: Any, |
| 46 | + name: str, |
| 47 | + callbacks: Optional[List[AgentCallback]] = None, |
| 48 | + cost_calculator: Optional[CostCalculator] = None, |
| 49 | + model_id: Optional[str] = None, |
| 50 | + ): |
22 | 51 | self.agent = agent_instance |
23 | 52 | self.name = name |
24 | 53 | self.callbacks = callbacks or [] |
25 | 54 | self.messages: Optional[MessageHistory] = None |
26 | 55 | self.logs: List[Dict[str, Any]] = [] |
| 56 | + self._cost_calculator = cost_calculator |
| 57 | + self._model_id = model_id |
27 | 58 |
|
28 | 59 | def run(self, query: str) -> Any: |
29 | 60 | """Executes the agent and returns the result.""" |
@@ -105,6 +136,70 @@ def get_messages(self) -> MessageHistory: |
105 | 136 | """ |
106 | 137 | return self.messages if self.messages is not None else MessageHistory() |
107 | 138 |
|
| 139 | + def gather_usage(self) -> Usage: |
| 140 | + """Gather usage with automatic cost calculation. |
| 141 | +
|
| 142 | + Calls ``_gather_usage()`` for raw token counts, then applies |
| 143 | + the cost calculator if one is available and cost is still ``0.0``. |
| 144 | +
|
| 145 | + The ``model_id`` used for cost calculation is resolved in order: |
| 146 | +
|
| 147 | + 1. Explicit ``model_id`` passed to ``__init__`` |
| 148 | + 2. Auto-detected from the framework agent via ``_resolve_model_id()`` |
| 149 | +
|
| 150 | + Subclasses should override ``_gather_usage()`` (not this method) |
| 151 | + to provide framework-specific token extraction. |
| 152 | +
|
| 153 | + Returns: |
| 154 | + Usage (or TokenUsage) with cost filled in when possible. |
| 155 | + """ |
| 156 | + usage = self._gather_usage() |
| 157 | + if isinstance(usage, TokenUsage) and usage.cost == 0.0: |
| 158 | + calculator = self._resolve_cost_calculator() |
| 159 | + if calculator is not None: |
| 160 | + mid = self._model_id or self._resolve_model_id() |
| 161 | + if mid: |
| 162 | + cost = calculator.calculate_cost(usage, mid) |
| 163 | + if cost is not None: |
| 164 | + usage.cost = cost |
| 165 | + return usage |
| 166 | + |
| 167 | + def _gather_usage(self) -> Usage: |
| 168 | + """Gather raw token usage from the framework. |
| 169 | +
|
| 170 | + Override this in subclasses to extract token counts from the |
| 171 | + framework's native data structures. |
| 172 | +
|
| 173 | + Returns: |
| 174 | + Usage or TokenUsage with token counts (cost may be 0.0). |
| 175 | + """ |
| 176 | + return Usage() |
| 177 | + |
| 178 | + def _resolve_model_id(self) -> Optional[str]: |
| 179 | + """Auto-detect the model ID from the framework agent. |
| 180 | +
|
| 181 | + Override in subclasses to extract the model identifier from |
| 182 | + the framework's agent object (e.g., ``self.agent.model.model_id`` |
| 183 | + for smolagents). |
| 184 | +
|
| 185 | + Returns: |
| 186 | + Model ID string, or ``None`` if not detectable. |
| 187 | + """ |
| 188 | + return None |
| 189 | + |
| 190 | + def _resolve_cost_calculator(self) -> Optional[CostCalculator]: |
| 191 | + """Resolve the cost calculator to use. |
| 192 | +
|
| 193 | + Returns the explicit calculator if one was provided, otherwise |
| 194 | + returns ``None``. Framework-specific subclasses can override this |
| 195 | + to auto-create a calculator (e.g., ``LiteLLMCostCalculator``) |
| 196 | + when the required dependencies are available. |
| 197 | +
|
| 198 | + Returns: |
| 199 | + A CostCalculator, or ``None`` if cost calculation is not available. |
| 200 | + """ |
| 201 | + return self._cost_calculator |
| 202 | + |
108 | 203 | def gather_traces(self) -> Dict[str, Any]: |
109 | 204 | """Gather execution traces from this agent. |
110 | 205 |
|
|
0 commit comments