|
17 | 17 | from .base import BaseCostAdapter, BaseInstrumentation |
18 | 18 |
|
19 | 19 |
|
| 20 | +def _detect_vendor(model: str) -> str: |
| 21 | + """Detect the actual vendor from model name. |
| 22 | +
|
| 23 | + The OpenAI Python SDK is used by multiple providers (OpenAI, DeepSeek, etc.) |
| 24 | + via base_url overrides. We detect the real vendor from the model name so spans |
| 25 | + and cost calculations are attributed correctly. |
| 26 | +
|
| 27 | + Args: |
| 28 | + model: Model identifier from the API response |
| 29 | +
|
| 30 | + Returns: |
| 31 | + Vendor string: "openai" or "deepseek" |
| 32 | + """ |
| 33 | + model_lower = model.lower() |
| 34 | + if model_lower.startswith("deepseek-"): |
| 35 | + return "deepseek" |
| 36 | + return "openai" |
| 37 | + |
| 38 | + |
20 | 39 | class OpenAICostAdapter(BaseCostAdapter): |
21 | | - """Cost calculation adapter for OpenAI models. |
22 | | - |
23 | | - Uses centralized pricing from kalibr.pricing module. |
| 40 | + """Cost calculation adapter for OpenAI-compatible models. |
| 41 | +
|
| 42 | + Handles OpenAI and any provider that uses the OpenAI SDK (e.g. DeepSeek). |
| 43 | + Vendor is detected from the model name so cost is always attributed correctly. |
24 | 44 | """ |
25 | 45 |
|
26 | 46 | def get_vendor_name(self) -> str: |
27 | | - """Return vendor name for OpenAI.""" |
| 47 | + """Return vendor name — overridden per-call via calculate_cost.""" |
28 | 48 | return "openai" |
29 | 49 |
|
30 | 50 | def calculate_cost(self, model: str, usage: Dict[str, int]) -> float: |
31 | | - """Calculate cost in USD for an OpenAI API call. |
32 | | - |
| 51 | + """Calculate cost using the correct vendor pricing for this model. |
| 52 | +
|
33 | 53 | Args: |
34 | | - model: Model identifier (e.g., "gpt-4o", "gpt-4o-2024-05-13") |
| 54 | + model: Model identifier (e.g., "gpt-4o", "deepseek-chat") |
35 | 55 | usage: Token usage dict with prompt_tokens and completion_tokens |
36 | | - |
| 56 | +
|
37 | 57 | Returns: |
38 | 58 | Cost in USD (rounded to 6 decimal places) |
39 | 59 | """ |
40 | | - # Get pricing from centralized module (handles normalization) |
41 | | - pricing = self.get_pricing_for_model(model) |
| 60 | + from kalibr.pricing import get_pricing |
| 61 | + |
| 62 | + vendor = _detect_vendor(model) |
| 63 | + pricing, _ = get_pricing(vendor, model) |
42 | 64 |
|
43 | 65 | prompt_tokens = usage.get("prompt_tokens", 0) |
44 | 66 | completion_tokens = usage.get("completion_tokens", 0) |
45 | 67 |
|
46 | | - # Calculate cost (pricing is per 1M tokens) |
47 | 68 | input_cost = (prompt_tokens / 1_000_000) * pricing["input"] |
48 | 69 | output_cost = (completion_tokens / 1_000_000) * pricing["output"] |
49 | 70 |
|
@@ -124,14 +145,17 @@ def wrapper(self_instance, *args, **kwargs): |
124 | 145 | # Extract model from kwargs |
125 | 146 | model = kwargs.get("model", "unknown") |
126 | 147 |
|
| 148 | + # Detect actual vendor — OpenAI SDK is used by multiple providers |
| 149 | + vendor = _detect_vendor(model) |
| 150 | + |
127 | 151 | # Create span with initial attributes |
128 | 152 | with self.tracer.start_as_current_span( |
129 | | - "openai.chat.completions.create", |
| 153 | + f"{vendor}.chat.completions.create", |
130 | 154 | kind=SpanKind.CLIENT, |
131 | 155 | attributes={ |
132 | | - "llm.vendor": "openai", |
| 156 | + "llm.vendor": vendor, |
133 | 157 | "llm.request.model": model, |
134 | | - "llm.system": "openai", |
| 158 | + "llm.system": vendor, |
135 | 159 | }, |
136 | 160 | ) as span: |
137 | 161 | start_time = time.time() |
@@ -167,14 +191,17 @@ async def wrapper(self_instance, *args, **kwargs): |
167 | 191 | # Extract model from kwargs |
168 | 192 | model = kwargs.get("model", "unknown") |
169 | 193 |
|
| 194 | + # Detect actual vendor — OpenAI SDK is used by multiple providers |
| 195 | + vendor = _detect_vendor(model) |
| 196 | + |
170 | 197 | # Create span with initial attributes |
171 | 198 | with self.tracer.start_as_current_span( |
172 | | - "openai.chat.completions.create", |
| 199 | + f"{vendor}.chat.completions.create", |
173 | 200 | kind=SpanKind.CLIENT, |
174 | 201 | attributes={ |
175 | | - "llm.vendor": "openai", |
| 202 | + "llm.vendor": vendor, |
176 | 203 | "llm.request.model": model, |
177 | | - "llm.system": "openai", |
| 204 | + "llm.system": vendor, |
178 | 205 | }, |
179 | 206 | ) as span: |
180 | 207 | start_time = time.time() |
|
0 commit comments