o
    È£Tjh  ã                   @  sÀ   d Z ddlmZ ddlZddlmZmZmZ ddlm	Z	 ddl
mZ ddlmZ dd	lmZmZmZ dd
lmZ ddlmZ h d£ZdZeG dd„ dƒƒZG dd„ dƒZddd„Zddd„ZdS )uØ  End-to-end agent pipeline: router -> cache -> retrieve -> compress -> generate -> guardrail.

Every stage is designed to spend as few tokens as possible (the objective: minimize cost + energy):
  - router    : trivial/too-short inputs are answered with a canned reply â€” zero tokens.
  - cache     : semantically-repeated questions return instantly â€” zero tokens.
  - compress  : retrieved context is deduped and capped to a token budget â€” fewer input tokens.
  - generate  : compact prompt + capped max_tokens â€” fewer input and output tokens.
Token usage is recorded on every response so it can be measured and driven down.

Shared by app/server.py and scripts/evaluate.py so the exact same path is served and measured.
é    )ÚannotationsN)ÚasdictÚ	dataclassÚfieldé   )ÚSemanticCache)Ú
Guardrails)Ú	LLMClient)ÚRetrievedChunkÚbuild_messagesÚcompress_context)Ú	Retriever)ÚConfig>	   z	thank youÚhiZokayÚokZheyZhelloZyoZthanksÚsupzpHi! Ask me a travel-insurance question (coverage, claims, exclusions) and I'll answer from the policy documents.c                   @  sˆ   e Zd ZU ded< eed�Zded< dZded< dZded	< d
Z	ded< dZ
ded< dZded< dZded< dZded< dZded< dS )ÚAgentResponseÚstrÚanswer)Údefault_factoryú	list[str]Ú	citationsFÚboolÚcachedÚgroundedÚ Úguardrail_reasonNzfloat | NoneÚ
similarityr   ÚintÚprompt_tokensÚcompletion_tokensÚtotal_tokensÚllmÚroute)Ú__name__Ú
__module__Ú__qualname__Ú__annotations__r   Úlistr   r   r   r   r   r   r    r!   r#   © r)   r)   úsrc/agent/pipeline.pyr      s   
 r   c                   @  sD   e Zd Zdddd„Zedd
d„ƒZddd„Zddd„Zddd„ZdS )ÚAgentTÚcfgr   Úlazy_llmr   c                 C  sˆ   || _ t|ƒ| _t|ƒ| _t|ƒ| _t| dd¡ƒ| _	t
| dd¡ƒ| _t
| dd¡ƒ| _t| dd¡ƒ| _d | _|sBt|ƒ| _d S d S )Nzrouter.enabledTzrouter.min_query_charsé   zretrieval.context_token_budgetiX  zretrieval.dedup)r,   r   Úcacher   Ú	retrieverr   Ú
guardrailsr   ÚgetÚrouter_enabledr   Úmin_query_charsÚcontext_budgetÚdedupÚ_llmr	   )Úselfr,   r-   r)   r)   r*   Ú__init__,   s   


ÿzAgent.__init__Úreturnr	   c                 C  s   | j d u rt| jƒ| _ | j S ©N)r7   r	   r,   )r8   r)   r)   r*   r"   9   s   
z	Agent.llmÚqueryr   úAgentResponse | Nonec                 C  s>   | j sdS | ¡ }t|ƒ| jk st|ƒtv rttddd�S dS )zECheap pre-filter: handle trivial inputs without retrieval or the LLM.NFZrouter)r   r   r#   )r3   ÚstripÚlenr4   Ú
_normalizeÚ
_GREETINGSr   Ú_ROUTER_REPLY)r8   r<   Úqr)   r)   r*   Ú_route?   s   zAgent._router   c                 C  s  |   |¡}|d ur|S | j |¡}|d ur,t|d | dg ¡d| dd¡| d¡dd�S | j |¡}t|| j| j	ƒ}d	\}}}|rU| j
 t||ƒ¡}|j|j|j}}}| j ||¡}	|	jret|	j|ƒng }
t|	j|
d|	j|	j|||| d
d�	}|	jrˆ| j ||j|
ddœ¡ |S )Nr   r   Tr   FZ_cache_similarityr/   )r   r   r   r   r   r#   )r   r   r   r"   )	r   r   r   r   r   r   r    r!   r#   )r   r   r   )rD   r/   Úlookupr   r2   r0   Úsearchr   r5   r6   r"   Zgenerater   Útextr   r    r1   Úcheckr   Ú_citations_usedr   ÚreasonÚstore)r8   r<   ZroutedZhitÚchunksÚrawZp_tokZc_tokÚresultZgrr   Úrespr)   r)   r*   r   H   sF   


ú

÷zAgent.answerrO   Údictc                 C  s   t |ƒS r;   )r   )r8   rO   r)   r)   r*   Úto_dictx   s   zAgent.to_dictN)T)r,   r   r-   r   )r:   r	   )r<   r   r:   r=   )r<   r   r:   r   )rO   r   r:   rP   )	r$   r%   r&   r9   Úpropertyr"   rD   r   rQ   r)   r)   r)   r*   r+   +   s    

	0r+   rG   r   r:   c                 C  s   d  |  ¡  ¡ ¡ d¡S )Nú z?.! )ÚjoinÚlowerÚsplitr>   )rG   r)   r)   r*   r@   |   s   r@   r   rL   úlist[RetrievedChunk]r   c                   st   dd„ t  d| ¡D ƒ}‡ fdd„t|ƒD ƒ}tƒ g }}|D ]}||vr.| |¡ | |¡ q|p9tdd„ ˆ D ƒƒS )z¤Map the [n] passage markers the answer actually cited back to their sources.

    Falls back to all retrieved sources if the answer cited no resolvable number.
    c                 S  s   h | ]}t |ƒ’qS r)   )r   )Ú.0Únr)   r)   r*   Ú	<setcomp>…   s    z"_citations_used.<locals>.<setcomp>z	\[(\d+)\]c                   s6   g | ]}d |  krt ˆ ƒkrn qˆ |d   j‘qS )r   )r?   Úsource)rX   Úi©rL   r)   r*   Ú
<listcomp>†   s   6 z#_citations_used.<locals>.<listcomp>c                 S  s   h | ]}|j ’qS r)   )r[   )rX   Úcr)   r)   r*   rZ   Œ   s    N)ÚreÚfindallÚsortedÚsetÚaddÚappend)r   rL   ZnumsÚusedÚseenZorderedÚsr)   r]   r*   rI   €   s   

€rI   )rG   r   r:   r   )r   r   rL   rW   r:   r   )Ú__doc__Z
__future__r   r`   Zdataclassesr   r   r   r/   r   r1   r   r"   r	   Úpromptr
   r   r   r0   r   Zsettingsr   rA   rB   r   r+   r@   rI   r)   r)   r)   r*   Ú<module>   s"    
Q