Ë
    tPêjr‰  ã                   óð  — U d Z ddlmZmZ ddlmZmZmZ ddlZddl	m
Z
 ddlmZ ddlmZmZ ddlmZmZ erdd	lmZ dd
lmZmZmZ  G d„ de«      Zdedz  deeeef   fd„Z G d„ de«      Zdedz  dedz  dedz  fd„Zdddedz  dededz  fd„Z dededz  fd„Z! G d„ de«      Z" e"«       Z#ee$d<   deee%f   dz  de"fd„Z&deee%f   dz  dedz  fd„Z'de#fded ed!edz  d"e"dedz  f
d#„Z(d edee)e)f   fd$„Z* e+d%«      Z,ee$d&<   d'edz  dee-e-f   fd(„Z.dEd ed)e-d'edz  defd*„Z/	 	 	 	 dFd+edz  d,edz  d-edz  d ed)e-d.edz  d'edz  deee%f   dz  defd/„Z0d0eee%f   dz  dedz  fd1„Z1d2eee%f   dz  d3edz  de-fd4„Z2d0eee%f   dz  de)fd5„Z3d0eee%f   dz  de)fd6„Z4dGd7„Z5d8e%dedz  fd9„Z6d:eee%f   dz  dedz  fd;„Z7	 	 	 dHdedz  dedz  d:eee%f   dz  d0eee%f   dz  d3edz  d<d=deee%f   dz  dedz  fd>„Z8d?eee%f   dedz  dedz  d3edz  d0eee%f   dz  deee%f   dz  dedz  fd@„Z9	 	 	 	 	 	 dIdedz  dedz  dAe)dBe-d:eee%f   dz  d0eee%f   dz  d3edz  d<d=deee%f   dz  dCe%defdD„Z:y)Ja­  
Per-request cost-savings computation for the Cost Optimization dashboard.

Turns the token-level savings recorded on a request into dollar amounts using
the model's own pricing. Daily rollup rows are keyed by date and entity, not by
model, so the dollars have to be computed here (where the model and its prices
are known) and summed into the daily tables; tokens cannot be priced after they
have been aggregated across models.
é    )ÚCallableÚMapping)ÚTYPE_CHECKINGÚFinalÚ
NamedTupleN)Úverbose_proxy_logger)Ú!INTERNAL_CALL_ORIGIN_METADATA_KEY)Ú_get_cost_per_unitÚgeneric_cost_per_token)Ú#GATEWAY_INJECTED_CACHE_METADATA_KEYÚ%GATEWAY_INJECTED_FOR_EVERY_DEPLOYMENT)ÚRouter)Ú	ModelInfoÚPromptTokensDetailsWrapperÚUsagec                   ó>   — e Zd ZU eed<   eed<   dZeed<   dZeed<   y)ÚSavingsSpendÚcompressionÚprompt_cachingç        Ú
autorouterÚgateway_injected_cachingN)Ú__name__Ú
__module__Ú__qualname__ÚfloatÚ__annotations__r   r   © ó    úi/home/oem/CyberBrain26-unified/.venv/lib/python3.12/site-packages/litellm/proxy/spend_tracking/savings.pyr   r      s#   … ØÓØÓØ€J�ÓØ&)Ð˜eÔ)r   r   ÚinfoÚreturnc                 ó|   — | €yt        | d«      xs d}t        | dd¬«      }t        | dd¬«      }||€|n||r|fS |fS )aÐ  
    Return ``(input_cost, cache_read_cost, cache_write_cost)`` per token.

    ``info`` is whatever pricing the caller resolved -- deployment rates when the
    request came through a router deployment, public rates otherwise -- so a
    negotiated price is honoured here rather than silently replaced by the list rate.
    ``None`` falls open to ``(0.0, 0.0, 0.0)`` so savings degrade to zero rather than
    raising inside the spend writer.

    Prices are read through ``_get_cost_per_unit``, the same accessor the cost
    calculator uses, which coerces the string prices a ``config.yaml`` can produce
    (``"3e-7"``) and resolves service-tier suffixes.

    An absent cache price mirrors the input cost, which yields a zero discount on the
    read leg and a zero premium on the write leg. Mirroring rather than taking
    ``_get_cost_per_unit``'s 0.0 default is load-bearing on the write leg: a zero write
    price would make the premium ``0 - input_cost``, turning a model that simply has no
    write pricing into a spurious extra saving.

    The two legs then differ on an explicit ``0.0``, and the asymmetry is deliberate. A
    free cache *write* does not exist -- entries carrying a literal zero (``deepseek-chat``
    does) mean "no separate price", so a falsy write price also mirrors input. A free
    cache *read* is real: 15 models charge for input and serve reads for nothing, which
    is the largest discount available, so the read leg keeps its literal zero.
    N)r   r   r   Úinput_cost_per_tokenr   Úcache_read_input_token_cost)Údefault_valueÚcache_creation_input_token_cost)r
   )r!   Ú
input_costÚcache_read_costÚcache_write_costs       r    Ú _input_cache_read_and_write_costr+   #   sk   € ð4 €|ØÜ*¨4Ð1GÓHÒOÈC€JÜ/°Ð6SÐcgÔh€OÜ0°Ð7XÐhlÔmÐàØ%Ð-‰
°?Ù,Ððð ð 3=ðð r   c                   ó"   — e Zd ZU eed<   eed<   y)Ú_ModelIdentityÚmodelÚproviderN)r   r   r   Ústrr   r   r   r    r-   r-   I   s   … ØƒJØ„Mr   r-   r.   Úcustom_llm_providerc                 ó¸   — | sy	 t        j                  | |¬«      \  }}}}t        ||¬«      S # t        $ r"}t        j                  d| ||«       Y d}~yd}~ww xY w)a7  Canonical ``(model, provider)``, or ``None`` when the model cannot be resolved.

    The two sides of the comparison arrive spelled differently: the spend log records a
    normalized model name alongside its provider, while the baseline arrives as the
    operator wrote it in config, with the provider prefixed, implied, or absent. Raw
    string equality therefore reads `anthropic/claude-opus-5` as a switch away from
    `claude-opus-5`, and pricing a bare name with no provider can resolve it to a
    different vendor's rates than the deployment it names.
    N©r.   r1   zIsavings: cannot resolve provider for model=%s custom_llm_provider=%s (%s))r.   r/   )ÚlitellmÚget_llm_providerÚ	Exceptionr   Údebugr-   )r.   r1   Úresolved_modelr/   Ú_Úes         r    Ú_resolve_modelr;   N   sk   € ñ ØðÜ)0×)AÑ)AÈÐcvÔ)wÑ&ˆ˜ ! Qô  ¸ÔBÐBøô ò Ü×"Ñ"ØWÐY^Ð`sÐuvô	
ô ûð	ús   …. ®	A·AÁAÚrouterúRouter | NoneÚdeployment_idc                 óŒ   — | �|€y	 | j                  ||«      S # t        $ r!}t        j                  d||«       Y d}~yd}~ww xY w)aú  What a deployment is actually charged, or ``None`` to price by name.

    `Router.get_deployment_model_info` owns this: it merges a deployment's configured
    prices over the built-in map, folds in `base_model` defaults for deployments whose
    name is not a model, and falls back to the model name when nothing is overridden.
    Resolving a name here instead reads the public rate, which a deployment with a
    negotiated price does not pay, and an Azure deployment name prices to nothing at all.
    Nz*savings: no deployment pricing for %s (%s))Úget_deployment_model_infor6   r   r7   )r<   r>   r.   r:   s       r    Ú_effective_model_inforA   d   sQ   € ð €~˜Ð.ØðØ×/Ñ/°¸uÓEÐEøÜò Ü×"Ñ"Ð#OÐQVÐXYÔZÜûðús   ‡ ™	A¢>¾Ac                 óÞ   — 	 t        j                  | j                  | j                  ¬«      S # t        $ r6}t        j                  d| j                  | j                  |«       Y d}~yd}~ww xY w)z=The public rates for ``model``, or ``None`` when it has none.r3   z1savings: no pricing for provider=%s model=%s (%s)N)r4   Úget_model_infor.   r/   r6   r   r7   )r.   r:   s     r    Ú_model_inforD   v   sY   € ðÜ×%Ñ%¨E¯K©KÈUÏ^É^Ô\Ð\øÜò Ü×"Ñ"Ð#VÐX]×XfÑXfÐhm×hsÑhsÐuvÔwÜûðús   ‚*- ­	A,¶,A'Á'A,c                   óN   — e Zd ZU dZdZedz  ed<   dZedz  ed<   dZedz  ed<   y)ÚPricingBasisaö  The tier and region a request was priced on, as the cost calculator resolved them.

    Read back off the request's recorded ``cost_breakdown`` rather than re-derived. The
    tier the biller used comes from ``optional_params``, which no log record carries, and
    the served tier that does survive on the usage object is a different fact with the
    opposite precedence, so a spend-time re-derivation would disagree with the invoice on
    exactly the requests where the tier changed the price.
    NÚservice_tierÚdata_residencyÚvertex_location)	r   r   r   Ú__doc__rG   r0   r   rH   rI   r   r   r    rF   rF      s3   … ñð  $€L�#˜‘*Ó#Ø!%€N�C˜$‘JÓ%Ø"&€O�S˜4‘ZÔ&r   rF   Ú_STANDARD_RATESÚcost_breakdownc                 ó
  — | st         S | j                  d«      }| j                  d«      }| j                  d«      }t        t        |t        «      r|ndt        |t        «      r|ndt        |t        «      r|¬«      S d¬«      S )a>  The basis recorded on a request, defaulting to standard rates when absent.

    Rows written before this field shipped carry neither key, and there is no backfill:
    they price at standard rates, which is what they already did.

    These values survive a JSON round trip on the way here, so none is guaranteed to be
    a string. `generic_cost_per_token` calls `.lower()` on them without a type check, and
    the resulting `AttributeError` would be swallowed into a silent zero by the caller's
    `except`, so anything that is not a string is dropped here instead.
    rG   rH   rI   N)rG   rH   rI   )rK   ÚgetrF   Ú
isinstancer0   )rL   rG   rH   rI   s       r    Ú_pricing_basisrP   ‘   s…   € ñ ÜÐØ(×,Ñ,¨^Ó<€LØ*×.Ñ.Ð/?Ó@€NØ+×/Ñ/Ð0AÓB€OÜÜ%/°¼cÔ%B‘\ÈÜ)3°NÄCÔ)H‘~ÈdÜ+5°oÄsÔ+K˜ôð ð RVôð r   c                 óÔ   — | sy| j                  d«      }| j                  d«      }t        |t        t        f«      rt        |t        t        f«      syt        |«      t        |«      z   S )aä  What the biller charged for this request's tokens, or ``None`` when unrecorded.

    ``input_cost`` already carries the cache buckets, so it and ``output_cost`` sum to
    exactly what `generic_cost_per_token` returns for the same request; the separate
    ``cache_read_cost`` and ``cache_creation_cost`` entries decompose that sum rather than
    adding to it, and including them would charge those tokens twice.

    Built-in tool cost, discount and margin are deliberately left out. They are properties
    of the request and the operator's contract rather than of the model the router picked,
    so they land on both sides of the comparison or neither, and only the total the
    counterfactual can also be priced on belongs here.
    Nr(   Úoutput_cost)rN   rO   Úintr   )rL   r(   rR   s      r    Ú_recorded_token_costrT   ¨   s_   € ñ ØØ&×*Ñ*¨<Ó8€JØ'×+Ñ+¨MÓ:€KÜ�j¤3¬ ,Ô/´zÀ+ÔPSÔUZÈ|Ô7\ØÜ�Óœu [Ó1Ñ1Ð1r   ÚusageÚ
model_infoÚbasisc           	      ó   — 	 t        | j                  || j                  |j                  |j                  ||j
                  ¬«      \  }}||z   S # t        $ r6}t        j                  d| j                  | j                  |«       Y d}~yd}~ww xY w)zMWhat ``usage`` costs on ``model``, or ``None`` when the model has no pricing.)r.   rU   r1   rG   rH   rV   rI   z9savings: cannot price usage for provider=%s model=%s (%s)N)	r   r.   r/   rG   rH   rI   r6   r   r7   )r.   rU   rV   rW   Úprompt_costÚcompletion_costr:   s          r    Ú_cost_of_usager[   ¾   sŒ   € ðÜ'=Ø—+‘+ØØ %§¡Ø×+Ñ+Ø ×/Ñ/Ø!Ø!×1Ñ1ô(
Ñ$ˆ�_ð ˜Ñ(Ð(øô ò Ü×"Ñ"ØGÈÏÉÐY^×YdÑYdÐfgô	
ô ûð	ús   ‚AA Á	BÁ,BÂBc                 ó¶   — | j                   }|€yt        |dd«      xs d}t        |dd«      xs dxs t        |dd«      xs d}t        |«      t        |«      fS )z=``(cache_read_tokens, cache_creation_tokens)`` for a request.)r   r   Úcached_tokensr   Úcache_creation_tokensÚcache_write_tokens)Úprompt_tokens_detailsÚgetattrrS   )rU   ÚdetailsÚreadÚcreateds       r    Ú_cache_token_splitre   ×   se   € à×0Ñ0€GØ€ØÜ˜' ?°AÓ6Ò;¸!€DÜ�wÐ 7¸Ó;Ò@¸qÒuÄgÈgÐWkÐmnÓFoÒFtÐst€GÜˆt‹9”c˜'“lÐ"Ð"r   ©r]   r^   r_   Úcache_creation_token_detailsÚtext_tokensÚ_CACHE_SPLIT_FIELDSÚbaseline_infoc                 óp   — | €yt        | j                  d«      «      t        | j                  d«      «      fS )aO  Whether the baseline model has a ``(cache read, cache write)`` rate of its own.

    A missing rate is not a free bucket. `_get_token_base_cost` resolves an absent
    `cache_read_input_token_cost` or `cache_creation_input_token_cost` to 0.0, so a
    baseline whose provider prices caching implicitly, which is every OpenAI, Azure and
    Gemini entry for cache writes, would carry the whole prompt for nothing and turn a
    profitable route into a reported loss. Such a model pays its plain input rate for
    those tokens, so the buckets it cannot price become ordinary input below.
    )TTr%   r'   )ÚboolrN   )rj   s    r    Ú_baseline_cache_rate_keysrm   æ   sB   € ð ÐØÜ�×!Ñ!Ð"?Ó@ÓAÄ4Ø×ÑÐ;Ó<óDð ð r   Úconversation_continuingc                 ó  ‡— t        | «      \  }}| j                  Š‰�
|dk  r|dk  r| S |xr |dkD  xr ||k  }|r||z   n|}|rdn|}t        |«      \  }}	|r|nd}|	r|nd}||f||fk(  r| S t        ˆfd„dD «       «      }
t	        | j
                  | j                  | j                  | j                  t        di ‰j                  t        ¬«      ¤||||r‰j                  ndt        | j
                  |z
  |z
  |
z
  d«      dœ¤Ž¬«      S )	aþ  The same request as a single-model baseline would have met it.

    The baseline is one model serving every turn, so whether it had this prompt cached
    is simply whether the conversation was already underway. On a continuing
    conversation it wrote the prompt on an earlier turn and would only read it now, so
    the cache tokens move into the read bucket and whatever this request paid to write
    counts against the saving; that write is what switching models costs.

    On a conversation's first turn nothing was cached anywhere, for any model. The
    baseline would have written the same prompt, so the cache buckets stay where they are
    and both arms carry the write at their own rates, unless the baseline has no rate for
    a bucket, in which case those tokens are its plain input. Charging the write to this case
    too, which is all a single rollup row can support, understates a first turn to a
    few percent of its value and can render a profitable route as a loss.

    A continuing turn that mostly read from cache is the third case: the selected model
    was already warm, so it is the one that has been serving this conversation and the
    baseline's cache holds exactly what its does. The tokens written are the turn's own
    growth, new to every model, and the baseline would have paid to write them too.
    Moving them would forgive the baseline a write it really owes and shrink the
    reported saving. "Mostly read" rather than "read anything" on purpose: a switch onto
    a model holding a small prefix of this prompt still writes most of it, and must keep
    counting that write against the saving.

    Only the cache buckets move. Every other field the request was priced on travels
    through untouched, audio and image and video counts among them, because the baseline
    is this same request served by a model that happened to be warm; naming the fields to
    keep instead would price the baseline on a request that never ran, and would go stale
    the next time a priced field is added.
    Nr   c              3   ó@   •K  — | ]  }t        ‰|d «      xs d –— Œ y­w)r   N)ra   )Ú.0Úfieldrb   s     €r    ú	<genexpr>z"_baseline_usage.<locals>.<genexpr>)  s'   øè ø€ ò "Ø.3Œ�˜% Ó	#Ò	( qÓ	(ñ"ùs   ƒ)Úaudio_tokensÚimage_tokensÚvideo_tokens)Úexcluderf   )Úprompt_tokensÚcompletion_tokensÚtotal_tokensÚcompletion_tokens_detailsr`   r   )re   r`   rm   Úsumr   rx   ry   rz   r{   r   Ú
model_dumpri   rg   Úmax)rU   rn   rj   Ú
cache_readÚcache_creationÚwarmÚreadsÚwritesÚprices_readsÚprices_writesÚother_modalitiesrb   s              @r    Ú_baseline_usager‡   ÷   sG  ø€ ô> "4°EÓ!:Ñ€J�Ø×0Ñ0€GØ€˜:¨š?¨~ÀÒ/BØˆð *Òa¨n¸qÑ.@ÒaÀZÐSaÑEa€DÙ+/ˆJ˜Ò'°Z€EÙ‰Q˜N€Fä";¸MÓ"JÑ€L�-Ù!‰E q€EÙ$‰V¨!€FØˆv€˜: ~Ð6Ò6Øˆä!ó "Ø7gô"ó Ðô Ø×)Ñ)Ø×1Ñ1Ø×'Ñ'Ø"'×"AÑ"AÜ8ñ 
Ø× Ñ Ô)<Ð Ó=ð
àØ"(Ø%ÙQW¨×)MÒ)MÐ]aä˜E×/Ñ/°%Ñ7¸&Ñ@ÐCSÑSÐUVÓWó
ôð r   Úbaseline_modelÚselected_modelÚselected_providerÚselected_infoc                 óø   — t        | d«      }t        ||«      }	|�|	€y||	k(  ryt        |«      }
|�|n
t        |«      }t        |t	        |||«      ||
«      }t        |«      }|€t        |	|||
«      }|�|€y||z
  S )aË  Net dollars the router saved, or cost, by serving this request on ``selected_model``.

    Signed on purpose. Switching models leaves the new one with a cold cache, so the
    request pays a cache-creation charge that staying on one model would not have
    incurred; when that charge outweighs the cheaper rates, routing lost money and the
    dashboard has to be able to say so. Zero when both sides resolve to the same
    deployment, or when either cannot be resolved or priced.

    Only one side of this subtraction is a counterfactual. What the request cost on the
    model that served it is a number the operator was actually billed, and the cost
    calculator already wrote it down, so ``cost_breakdown`` is read rather than
    re-derived. Recomputing it means restating every pricing dimension the biller
    applied, and each one omitted is a silent disagreement with the ``spend`` column
    beside it; a request billed at a priority tier recomputed at standard rates reads as
    half its real cost.

    The baseline has no such record, since it never ran, so it is priced through the same
    cost engine on the basis the biller used for this request. An operator running that
    one model instead of the router would have sent this request to the same tier and the
    same region, because both are properties of the request and the deployment's
    contract, not of which model the router happened to pick.

    ``conversation_continuing`` says whether the baseline would already have had this
    prompt cached. It defaults to True because that is the conservative reading: a
    request whose shape the router could not determine is charged the write and
    under-claims rather than inflating a savings figure.
    Nr   )r;   rP   rD   r[   r‡   rT   )rˆ   r‰   rŠ   rU   rn   r‹   rj   rL   ÚbaselineÚselectedrW   Úeffective_baseline_infoÚbaseline_costÚselected_costs                 r    Úcompute_autorouter_savingsr’   =  s³   € ôP % ^°TÓ:€HÜ$ ^Ð5FÓG€HØÐ˜8Ð+Øð
 �8ÒØÜ! .Ó1€EØ6CÐ6O¡]ÔU`ÐaiÓUjÐÜ)ØÜ˜Ð6Ð8OÓPØØó	€Mô )¨Ó8€MØÐÜ& x°¸ÀuÓMˆØÐ Ð 5ØØ˜=Ñ(Ð(r   Úusage_objectc                 óx   — | sy	 t        di | ¤ŽS # t        $ r }t        j                  d|«       Y d}~yd}~ww xY w)zERebuild the request's ``Usage`` from the copy the spend log recorded.NzGsavings: unusable usage_object, auto-router savings will read zero (%s)r   )r   r6   r   Úwarning)r“   r:   s     r    Ú_usage_from_spend_logr–   �  sB   € áØðÜÑ$�|Ñ$Ð$øÜò ô 	×$Ñ$Ð%nÐpqÔrÜûðús   …
 �	9™4´9ÚmetadataÚmodel_idc                 óh   — | sy| j                  t        «      }t        |t        «      sy|t        |fv S )a•  Whether the gateway put cache breakpoints on the payload THIS row was billed for.

    ``AnthropicCacheControlHook.record_gateway_injection`` stamps the deployment it
    injected for, and a row carries the deployment it was billed for, so the two agree
    only on the leg that was actually injected. Every retry, failover and fallback of a
    request shares one metadata bucket and one ``litellm_call_id``, so the deployment is
    what tells those legs apart, and a marker left by a sibling reads here as no injection
    without anyone having to strip it. An injection that ran before any deployment was
    chosen is in the payload every leg sends, so it is marked for all of them and credits
    each. Absent on requests the gateway never acted on
    (client-supplied ``cache_control``, implicit provider caching) and on rows written
    before the marker shipped; all of it is the fail-closed direction.
    F)rN   r   rO   r0   r   )r—   r˜   Úinjected_deployments      r    Úmarks_gateway_injectionr›   �  s9   € ñ ØØ!)§¡Ô.QÓ!RÐÜÐ)¬3Ô/ØØÔ#HÈ(Ð"SÐSÐSr   c                 ó  — | sy| j                  d«      }t        |t        t        f«      r|rt        |«      S | j                  d«      }t        |t        «      sy|j                  d«      }t        |t        t        f«      rt        |«      S dS )aš  Cache-read tokens from a logged usage object, whatever shape recorded them.

    Anthropic writes a top-level ``cache_read_input_tokens``; OpenAI-compatible
    providers (moonshotai, openai, deepseek, etc.) write
    ``prompt_tokens_details.cached_tokens``. This is the one owner of that
    normalization: callers hand over the usage object rather than threading a
    count that could disagree with it.
    r   Úcache_read_input_tokensr`   r]   )rN   rO   rS   r   r   )r“   Úexplicitrb   Úcacheds       r    Úextract_cache_read_tokensr    ¥  s}   € ñ ØØ"×&Ñ&Ð'@ÓA€HÜ�(œS¤%˜LÔ)©hÜ�8‹}ÐØ!×%Ñ%Ð&=Ó>€GÜ�gœwÔ'ØØ—K‘K Ó0€FÜ$ V¬c´5¨\Ô:Œ3ˆv‹;ÐAÀÐAr   c                 ó0  — | sy| j                  d«      }t        |t        t        f«      r|rt        |«      S | j                  d«      }t        |t        «      syt        d„ |j                  d«      |j                  d«      fD «       d«      }t        |«      S )a.  Cache-write tokens from a logged usage object, whatever shape recorded them.

    Anthropic writes a top-level ``cache_creation_input_tokens``; OpenAI-compatible
    providers (kimi-k2 etc.) write ``prompt_tokens_details.cache_write_tokens`` or
    ``prompt_tokens_details.cache_creation_tokens``.
    r   Úcache_creation_input_tokensr`   c              3   óP   K  — | ]  }t        |t        t        f«      r|r|–— Œ  y ­w©N)rO   rS   r   )rq   Úvalues     r    rs   z0extract_cache_creation_tokens.<locals>.<genexpr>Ê  s(   è ø€ ò 	
àÜ˜%¤#¤u Ô.±5ô ñ	
ùs   ‚$&r_   r^   )rN   rO   rS   r   r   Únext)r“   rž   rb   Úwrittens       r    Úextract_cache_creation_tokensr¨   º  s”   € ñ ØØ"×&Ñ&Ð'DÓE€HÜ�(œS¤%˜LÔ)©hÜ�8‹}ÐØ!×%Ñ%Ð&=Ó>€GÜ�gœwÔ'ØÜñ	
à!Ÿ+™+Ð&:Ó;¸W¿[¹[ÐI`Ó=aÐbô	
ð
 	
ó€Gô ˆw‹<Ðr   c                  ó2   — 	 ddl m}  | S # t        $ r Y yw xY w)zLThe running proxy's router, or ``None`` outside a proxy (public rates only).r   ©Ú
llm_routerN)Úlitellm.proxy.proxy_serverr«   r6   rª   s    r    Ú_proxy_llm_routerr­   Ô  s&   € ðÝ9ð Ðøô ò Ùðús   ‚
 Š	•r¥   c                 óf   — t        | t        «      st        | t        t        f«      syt        | «      S )zG``value`` as a recorded savings figure, or ``None`` when it is not one.N)rO   rl   rS   r   )r¥   s    r    Ú_numeric_savingsr¯   Ý  s&   € ä�%œÔ¤j°¼¼e¸Ô&EØÜ�‹<Ðr   Úrouting_decisionc                 ó^   — t        | t        «      r| ni }t        |j                  d«      «      S )aC  The LLM-classifier cost a routing decision recorded, or ``None`` when it holds none.

    ``None`` covers the decision-less request, the heuristic short-circuit that never
    called a classifier, the unpriced classifier model, and a malformed value alike:
    in every one of those cases there is no dollar figure to move, so callers treat
    ``None`` as zero rather than as an error. The one owner of that reading, shared by
    the savings netting, the session rollup and the response header, so the three can
    never disagree about what counts as a classifier charge.
    Úclassifier_cost)rO   r   r¯   rN   )r°   Údecisions     r    Úclassifier_cost_from_decisionr´   ä  s,   € ô +5Ð5EÄwÔ*OÑ&ÐUW€HÜ˜HŸL™LÐ):Ó;Ó<Ð<r   r«   z"Callable[[], Router | None] | Nonec                 óª  — t        |«      }|�| syt        |t        «      r|ni }|j                  d«      }	|j                  d«      }
t        |	t        «      r|	nd}t        |
t        «      r|
nd}|r|sy|r |«       nd}t        || |||j                  d«      dut        ||| xs d«      t        |||xs d«      |¬«      }t        |«      }|€|S ||z
  S )aŸ  Auto-router savings for one request, net of the classifier call that routed it,
    or ``None`` when the driver is off.

    ``None`` and ``0.0`` are different facts: ``None`` means this request cannot carry a
    figure at all (no routing decision, no baseline, unusable usage), while ``0.0`` is a
    real figure for a routed request whose baseline resolved to the served deployment.
    Never raises: pricing failures inside degrade to zero, and the driver-off cases
    return ``None``, so this is safe on the logging path where a raise would fail the
    request's logging.

    The classifier deduction lives here, at the figure's one computation owner, rather
    than in any reader: the stamped ``autorouter_savings`` is then already net, so the
    session rollup, the daily tables and every logging consumer agree without each
    re-deriving the deduction, and the recorded-figure-wins path cannot deduct twice.
    NÚsavings_baseline_modelÚsavings_baseline_deployment_idrn   FÚ )rˆ   r‰   rŠ   rU   rn   r‹   rj   rL   )r–   rO   r   rN   r0   r’   rA   r´   )r.   r1   r°   r“   r˜   r«   rL   rU   r³   ÚrecordedÚrecorded_idrˆ   Úbaseline_idÚrouter_instanceÚgrossr²   s                   r    Úautorouter_savings_for_requestr¾   ò  sí   € ô0 )¨Ó6€EØ€}™EØÜ*4Ð5EÄwÔ*OÑ&ÐUW€HØ—l‘lÐ#;Ó<€HØ!Ÿ™Ð&FÓG€KÜ(2°8¼SÔ(A™HÀt€NÜ(2°;ÄÔ(D™È$€KÙ™>ØÙ-7™Zœ\¸T€OÜ-Ø%ØØ-Øð !)§¡Ð-FÓ GÈuÐ TÜ+¨O¸XÀuÂ{ÐPRÓSÜ+¨O¸[È.ÒJ^Ð\^Ó_Ø%ô€Eô ;¸8ÓD€OØ#Ð+ˆ5ÐH°¸Ñ1HÐHr   Úrequest_metadatac           	      ó    — | j                  t        «      ry| j                  d«      }t        ||t        |t        «      r|nd||t
        |¬«      S )a£  The figure the logging payload records for a request, or ``None`` when none should be.

    Internal sub-calls (the auto-router classifier, shadow eval's shadow and judge legs)
    are excluded here for the same reason the spend writer zeroes them: they can carry a
    real routing decision, but they are not requests the caller made, so a figure stamped
    on them would report savings for traffic no user sent.
    Nr°   ©r.   r1   r°   r“   r˜   r«   rL   )rN   r	   r¾   rO   r   r­   )r¿   r.   r1   r˜   r“   rL   r°   s          r    Ú&autorouter_savings_for_logging_payloadrÂ   %  sX   € ð ×ÑÔ=Ô>ØØ.×2Ñ2Ð3EÓFÐÜ)ØØ/Ü-7Ð8HÌ'Ô-RÑ)ÐX\Ø!ØÜ$Ø%ôð r   Úcompression_saved_tokensÚgateway_injected_cacheÚrecorded_autorouter_savingsc
           	      óÂ  — |r |«       nd}
t        | |«      }t        |
|| xs d«      xs |rt        |«      nd}t        |«      \  }}}t	        |d«      |z  }t        |«      }t        |«      }t	        |d«      t	        ||z
  d«      z  }t	        |d«      ||z
  z  }||z
  }|r|nd}t        |	«      }|�|nt        | ||||||¬«      }t        |||€d|¬«      S ||¬«      S )a¿  
    Dollar savings for one request, split by optimization driver.

    Compression savings price the tokens compression removed at the model's
    input rate. Prompt-caching savings are NET: the cache-read discount minus the
    premium paid to write those entries, both derived here from ``usage_object`` so no
    caller can hand in a count that disagrees with the usage record.

    The net form follows from what the request would have cost with caching off. The
    provider reports ``prompt_tokens`` as the inclusive total of three disjoint
    partitions (uncached text, cache reads, cache writes), so an uncached counterfactual
    bills every one of those tokens at the flat input rate::

        would_have_cost = (text + reads + writes) * input
        actually_cost   = text * input + reads * read_rate + writes * write_rate
        savings         = reads * (input - read_rate) - writes * (write_rate - input)

    So the write leg subtracts the write PREMIUM, not the whole write cost: those tokens
    had to be sent either way, and the counterfactual already pays the input rate for
    them. The premium stays signed, because a handful of models price writes below their
    input rate and there the write is a genuine extra saving.

    A request that only writes cache and gets no hits therefore reports negative savings,
    which is accurate: it really did cost more than the uncached call would have. The
    daily rollup increments arithmetically, so those rows offset positive ones in the
    same bucket.

    Caching is reported twice. ``prompt_caching`` is every net dollar caching saved,
    whoever caused it, which is what a customer means by "what did caching save me".
    ``gateway_injected_caching`` is the subset the gateway can claim credit for, carrying
    a value only when ``gateway_injected_cache`` is set, i.e. litellm itself added the
    ``cache_control`` breakpoints (configured injection points or the auto prompt-caching
    flag). A client that sent its own breakpoints, and a provider that
    caches implicitly (OpenAI, Gemini), produce the same usage shape with no gateway
    action, so they count toward the total and not toward the attributed figure.

    Reporting both rather than gating the one column keeps the customer-facing number
    stable across the change and leaves attribution a separate question. The attributed
    figure is normally the smaller of the two, being a subset of the same requests, but
    not always: a request that only writes cache and never reads it has negative net
    savings, and dropping such a request from the attributed figure can lift it above
    the total. Auto-router savings compare the
    served ``model`` against the counterfactual baseline the router recorded on
    its ``routing_decision``, and are zero unless the two differ. That record
    also says whether the conversation was already underway, which is what tells
    a mid-conversation switch from a first turn.

    ``llm_router`` is passed as a provider rather than a router because every spend write
    calls this and only auto-routed ones need one, so looking it up eagerly at the call
    site would fetch and discard it on the rest.

    ``cost_breakdown`` is what the cost calculator recorded for this request, and it
    carries both what the request really cost and the tier and region it was priced on.
    Only the auto-router driver reads it. Compression and prompt caching price a
    hypothetical token delta off flat rate keys, so they are blind to tiered pricing in
    the same way; that is pre-existing behaviour on two shipped drivers rather than
    something introduced here, and moving those numbers is its own change.

    ``recorded_autorouter_savings`` is the figure the logging path stamped on the spend
    log's metadata, honoured over recomputation so the rollup, the turn table and the
    per-request record cannot disagree; rows written before the field shipped carry
    nothing and recompute, mirroring ``_recorded_token_cost``.
    Nr¸   r   r   rÁ   )r   r   r   r   )
r;   rA   rD   r+   r~   r    r¨   r¯   r¾   r   )r.   r1   rÃ   rÄ   r°   r“   r˜   r«   rL   rÅ   r¼   ÚidentityÚpricingr(   r)   r*   r   r�   r¢   Úread_discountÚwrite_premiumr   r   Úrecorded_savingsr   s                            r    Úcompute_savings_spendrÌ   B  s>  € ñ\ 6@¡Z¤\ÀT€OÜ$ UÐ,?Ó@€HÜ*¨?¸HÀeÂkÈrÓRò Ù!)Œ�HÔ¨tð ô 5UÐU\Ó4]Ñ1€J�Ð!1ÜÐ5°qÓ9¸JÑF€KÜ%>¸|Ó%LÐÜ)FÀ|Ó)TÐÜÐ6¸Ó:¼SÀÈoÑA]Ð_bÓ=cÑc€MÜÐ:¸AÓ>ÐBRÐU_ÑB_Ñ`€MØ)¨MÑ9€NÙ8N¡nÐTWÐô /Ð/JÓKÐð Ð'ñ 	ä+ØØ 3Ø-Ø%ØØ!Ø)ô
ð ô ØØ%Ø$Ð,�3Ø!9ô	ð ð 3=Ø!9ô	ð r   r¤   )TNNN)r"   r=   )NNN)NNNNNN);rJ   Úcollections.abcr   r   Útypingr   r   r   r4   Úlitellm._loggingr   Úlitellm.constantsr	   Ú.litellm.litellm_core_utils.llm_cost_calc.utilsr
   r   Ú7litellm.types.integrations.anthropic_cache_control_hookr   r   Úlitellm.routerr   Úlitellm.types.utilsr   r   r   r   Útupler   r+   r-   r0   r;   rA   rD   rF   rK   r   ÚobjectrP   rT   r[   rS   re   Ú	frozensetri   rl   rm   r‡   r’   r–   r›   r    r¨   r­   r¯   r´   r¾   rÂ   rÌ   r   r   r    ú<module>rØ      sf  ðò÷ .ß 3Ñ 3ã Ý 1Ý ?ß e÷ñ
 Ý%ß LÑ Lô*�:ô *ð#¨9°tÑ+;ð #ÀÀeÈUÐTYÐFYÑ@Zó #ôL�Zô ð
C˜# ™*ð C¸3À¹:ð CÈ.Ð[_ÑJ_ó Cð, /ð À#ÈÁ*ð ÐUXð Ð]fÐimÑ]mó ð$�~ð ¨)°dÑ*:ó ô'�:ô 'ñ &›€�Ó 'ð 7¨3°¨;Ñ#7¸$Ñ#>ð À<ó ð.2¨°°f°Ñ)=ÀÑ)Dð 2ÈÐQUÉó 2ð2 $(Ø)ñ	)Øð)àð)ð ˜DÑ ð)ð ð	)ð
 ˆT�\ó)ð2#˜eð #¨¨c°3¨h©ó #ñ 'ØsóÐ �Uó ð
¨Y¸Ñ-=ð À%ÈÈdÈ
ÑBSó ñ"C˜5ð C¸4ð CÐPYÐ\`ÑP`ð CÐlqó CðV %)Ø&*Ø&*Ø26ñA)Ø˜$‘JðA)à˜$‘JðA)ð ˜T‘zðA)ð ð	A)ð
 "ðA)ð ˜tÑ#ðA)ð ˜tÑ#ðA)ð ˜C ˜KÑ(¨4Ñ/ðA)ð óA)ðH¨°°V°Ñ(<¸tÑ(Cð ÈÐPTÉó ðT g¨c°6¨kÑ&:¸TÑ&Að TÈSÐSWÉZð TÐ\`ó Tð,B¨G°C¸°KÑ,@À4Ñ,Gð BÈCó Bð*°¸¸V¸Ñ0DÀtÑ0Kð ÐPSó ó4ð˜Fð  u¨t¡|ó ð=°G¸CÀ¸KÑ4HÈ4Ñ4Oð =ÐTYÐ\`ÑT`ó =ð&  Ø7;Ø26ñ0IØ�‰:ð0Ià˜t™ð0Ið ˜c 6˜kÑ*¨TÑ1ð0Ið ˜#˜v˜+Ñ&¨Ñ-ð	0Ið
 �D‰jð0Ið 5ð0Ið ˜C ˜KÑ(¨4Ñ/ð0Ið ˆT�\ó0IðfØ˜c 6˜kÑ*ðà�‰:ðð ˜t™ðð �D‰jð	ð
 ˜#˜v˜+Ñ&¨Ñ-ðð ˜C ˜KÑ(¨4Ñ/ðð ˆT�\óðD 59Ø04ØØ7;Ø26Ø*.ñqØ�‰:ðqà˜t™ðqð "ðqð !ð	qð
 ˜c 6˜kÑ*¨TÑ1ðqð ˜#˜v˜+Ñ&¨Ñ-ðqð �D‰jðqð 5ðqð ˜C ˜KÑ(¨4Ñ/ðqð "(ðqð ôqr   