a
    qjÕjb  ã                   @   sb  d Z ddlZddlZddlmZ ddlmZ ddlmZ ddl	m
Z
 e
 ¡ Ze dej¡Ze dej¡Ze d	ej¡Zd
Zh d£ZddddddgddœddgddœddgddœddgddœddgddœdœdœdœdœZddddddidd id!œd"d#gd$œdœdœZdd%d&dddidd id'œd(d#gd$œdœdœZd)d*„ Zd+d,„ Zd-d.„ Zd/d0„ ZG d1d2„ d2ƒZdS )3u¬  Real agentic loop for the Complaint Guidance Agent â€” no demo script, no skip flags.

Design carried over deliberately from the earlier build (see project memory): the harness
decides what step/stage a turn is on from facts it tracks itself, and tells the model that â€”
asking the model to self-report its own stage caused it to drift in the original build. What's
new here is that every turn is a live gateway call: fact extraction, response phrasing, and
three independent verification passes (citation existence, grounding, relevance), all against
the real procedures in knowledge_base/ (see that directory's README for what's genuinely
esure-sourced vs. deliberately left out).
é    N)ÚValidationError)ÚEvidenceStore)ÚGatewayClient)ÚTurnResponsea,  \b(reduced income|short time at work|earning less|job loss|lost (my|his|her) job|bereavement|illness|disabilit|caring responsibilit|domestic abuse|financial abuse|can'?t afford|cannot afford|struggling to pay|low confidence|can'?t understand|difficulty understanding|limited literacy|language need)\bzq\b(yes|yeah|yep|agrees?|agreed|accepts?|accepted|happy for|happy to|ok(ay)? with|consents?|confirms?|confirmed)\bz=\b(not|no\b|never|won'?t|don'?t|refus|declin|reject(?!ion))\bÚESCALATE_TL>   zPROC-EXPLAIN-CANCELLATION#S1zPROC-MANUAL-PAYMENT#S1zPROC-REPRESENT-DD#S1ÚfunctionÚupdate_factszšRecord which facts the representative has confirmed so far, from their latest note. Only set a field if this note actually confirms it; leave others null.ÚobjectÚintegerÚnullzQHow many Direct Debit collections have been rejected this policy year, if stated.)ÚtypeÚdescriptionZbooleanzŠTrue if account notes confirm a previous advisor incorrectly told the customer the DD would be reapplied despite multiple missed payments.z]True if the customer has disclosed a vulnerability indicator (reduced income, illness, etc.).zcTrue/false once the customer has answered whether they consent to the vulnerability being recorded.zMTrue/false once the customer has accepted or rejected the offered resolution.)Úmissed_dd_countÚincorrect_info_givenÚvulnerability_disclosedÚvulnerability_consentÚcustomer_accepted)r   Ú
properties)Únamer   Ú
parameters)r   r   Úgrounding_verdictz_Judge whether every factual claim in the response is supported by the provided source material.r   Ústring)Ú	supportedÚreasonr   r   )r   r   ÚrequiredÚrelevance_verdictzPJudge whether the response actually addresses what the representative just said.)Ú
responsiver   r   c                 C   s*   |   dg ¡D ]}|d |kr|  S qd S )NZstepsÚstep_id©Úget)Ú	procedurer   Ústep© r"   ú(/home/ec2-user/esure-agent/agent_core.pyÚ_step_lookupt   s    
r$   c           	      C   s¾   |   d¡D ]®}| ¡ }t d|¡}|s, dS | ¡ \}}}| |¡}|du rR dS dddœ | ¡ ¡}|du rvt|ƒ}|dkrŒ||ksŒ dS |dkr¢||k s¢ dS |d	kr
||ks
 dS q
dS )
z”Tiny evaluator for the branch grammar actually used in esure-procedures.json:
    `FIELD OP VALUE [AND FIELD OP VALUE ...]`, OP in (>=, <, ==).
    z AND z(\w+)\s*(>=|<|==)\s*(\w+)FNT)ÚtrueÚfalsez>=ú<z==)ÚsplitÚstripÚreÚmatchÚgroupsr   ÚlowerÚint)	ZexprÚfactsZclauseÚmÚfieldÚopZ	raw_valueZactualÚvaluer"   r"   r#   Ú_branch_matches{   s&    
r4   c                 C   sD   |   dg ¡D ]*}|d |krt|d |ƒr|d   S q|   dt¡S )NZbranchesZafterZwhenÚgotoZno_matching_branch)r   r4   r   )r    Zafter_step_idr/   Úbranchr"   r"   r#   Ú_resolve_branch”   s    r7   c                 C   sä   |   d¡}d|vr| d¡du r0d|vr,dS dS | d¡du rBdS t|d	|ƒ}|tv rZd
S |tkrfdS | d¡rŽd|vrŽ| d¡du rŠdS dS d|v r¨| d¡dur¨dS | d¡du rºdS t|d|ƒ}|tkrÒdS | d¡sàdS dS )u½   Harness-decided position in the one real procedure â€” never asks the model.

    Returns one of: ("step", procedure_id, step_id), ("vulnerability", step_id),
    ("escalate", reason).
    úCMP-DD-REJECT-001r   r   N)r!   r8   ZS1)r!   r8   ZS2r   )r!   r8   ÚS3r9   )Úescalateuœ   No real procedure content for this path yet (see knowledge_base/README.md's Known gap section) â€” hold and escalate to a team leader rather than improvise.)r:   z1Procedure has no matching branch for these facts.Úvulnerability_confirmedr   )ÚvulnerabilityÚV2)r<   ÚV3Ú
_explainedT)r!   r8   ÚS4r   ©r!   r8   ÚS5rB   )r:   z)Customer rejected the offered resolution.Ú_logged)r!   r8   ÚS6©r!   r8   ZS7)r    r   r7   Ú_UNRESOLVED_TARGETSr   )Ústorer/   Zcmp_procr5   Zgoto2r"   r"   r#   Ú	_position›   s6    

rH   c                   @   s`   e Zd Zddd„Zddd„Zdd„ Zdd	„ Zd
d„ Zdd„ Zdd„ Z	dd„ Z
dd„ Zdd„ ZdS )ÚReasoningAgentNc                 C   s   |pt ƒ | _|ptƒ | _d S )N)r   Úclientr   rG   )ÚselfrJ   rG   r"   r"   r#   Ú__init__Ñ   s    zReasoningAgent.__init__c              	   C   sx  |pg }t |pi ƒ}t |pd¡r.| dd¡ t| j|ƒ}|  |||¡}|d dkr’|d dkr’| d¡d u r’t |pxd¡r’t	 |p†d¡s’d|d< |d	krÌ| d
¡d u rÌt |p²d¡rÌt	 |pÀd¡sÌd|d
< t| j|ƒ}|d dkrö|  
|d ||¡S |  ||¡\}}|  |¡}|  |||||¡}	d|› d|› �}
|  ||	|
||¡}|d dk�rh|d dk�rhd|d< |d dk�r–|d dk�r–|d �r–d|d< |d dk�rÄ|d dk�rÄt| d¡ƒ|d< |d }|�rÞ|	�rÞ|	jnd |�rò|	�rò|	jng |�r|	�r|	jnd ||d dk�r |d n|d dd„ |D ƒ|dk�rF|  |¡nd dœ}|	�r\|	 ¡ nd }|||t|ƒd |dœS )NÚ r   Tr   r<   é   r=   r   rA   r   r:   z*PROCEDURE STEP INSTRUCTION FOR THIS TURN:
ú

r!   é   r@   r?   rD   ÚokrC   r>   r;   c                 S   s   g | ]}d |i‘qS ©Z	referencer"   ©Ú.0Zaidr"   r"   r#   Ú
<listcomp>  ó    z&ReasoningAgent.run.<locals>.<listcomp>rE   ©ÚheadlineÚ
next_movesÚsuggested_wordingÚinstructionZstageÚ	citationsZ	case_note©ÚanswerÚverificationr/   ÚturnÚ_response_json)ÚdictÚ_VULN_INDICATORSÚsearchÚ
setdefaultrH   rG   Ú_extract_factsr   Ú_AFFIRMATIVEÚ_NEGATION_WORDÚ_escalated_resultÚ_instruction_forÚ_context_textÚ_phrase_responseÚ_verifyÚboolrX   rY   rZ   Ú
_case_noteZmodel_dump_jsonÚlen)rK   ÚquestionÚhistoryÚcarriedr/   Úpositionr[   Úevidence_idsÚcontext_textÚturn_responseZgrounding_contextr_   rQ   r^   Úresponse_jsonr"   r"   r#   ÚrunÕ   sh    ÿþþÿþþ
	&ù	
ûzReasoningAgent.runc                 C   s6   d|gd |dg d dœdg d d g dœ|t |ƒd d dœS )Nu/   Hold the customer â€” this needs a team leader.r:   rW   T©rQ   r\   Ú	groundingÚ	relevanceÚ
violationsrN   r]   )rp   )rK   r   r/   rr   r"   r"   r#   ri     s    ù	ÿ
òz ReasoningAgent._escalated_resultc                 C   s‚   |d }|dkr4t | j d¡|d ƒ}|d dgfS | j |d ¡}t ||d ƒ}g }|d dkrv| d	¡rv| d¡ |d |fS )
Nr   r<   zPROC-VULNERABILITY-FLOWrN   r[   zVULNERABLE-CUSTOMER-PROCESSrP   rB   r;   )r$   rG   r    r   Úappend)rK   rt   r/   Úkindr!   r    ru   r"   r"   r#   rj   .  s    
zReasoningAgent._instruction_forc                 C   s>   g }|D ]*}| j  |¡}|r| d|› d|› �¡ qd |¡S )Nz### Ú
rO   )rG   Zdocument_textr~   Újoin)rK   ru   ÚpartsÚ
article_idÚtextr"   r"   r#   rk   :  s    zReasoningAgent._context_textc              	   C   sÊ   dddœg}|D ]}|  d| dd¡dœ¡ q|  d|dœ¡ | jj|tgdd�}t|ƒ}|jpbg D ]`}|jjd	krvqdzt	 
|jj¡}	W n t	jy    i }	Y n0 |	 ¡ D ]\}
}|d urª|||
< qªqd|S )
NÚsystema  You extract structured facts from a customer-service representative's notes during a missed-Direct-Debit billing complaint call. Only record what this specific note actually states. You MUST call the update_facts tool on every turn, even if nothing new was confirmed.©ZroleÚcontentÚuserÚnoterM   Úauto©ZtoolsZtool_choicer   )r~   r   rJ   ÚcompleteÚ_FACTS_TOOLrb   Ú
tool_callsr   r   ÚjsonÚloadsÚ	argumentsÚJSONDecodeErrorÚitems)rK   rq   rr   r/   Úmessagesr`   ÚmessageZ	new_factsÚcallÚparsedÚkeyr3   r"   r"   r#   rf   B  s&    þ
zReasoningAgent._extract_factsc              	   C   sè   d|pd› d|› d�|r$d|› d�nd }d|dœg}|D ]:}|  d	| d
d¡dœ¡ | d¡r:|  d|d dœ¡ q:|  d	|dœ¡ | jj|tgdd�}	|	jp¢g D ]>}
|
jjdkr¶q¤zt 	|
jj
¡W   S  tyà   Y  dS 0 q¤dS )u  Structured, not free prose â€” see schemas.TurnResponse for why.

        Forced tool_choice isn't supported by this model group (see _extract_facts), so this
        is tool_choice="auto" plus an explicit instruction, same pattern used everywhere else
        in this file.
        u\  You are a complaint-guidance assistant helping an esure representative handle a live call. You MUST call submit_turn_response with your answer â€” never reply in plain text. Keep headline to one sentence, next_moves to short imperative bullets, and base everything ONLY on this instruction and source material â€” never invent a policy detail, number, or process step that isn't in them. This includes UI/system details (e.g. 'dropdown list', specific screens), escalation routes, verification rituals (e.g. 'read the number back'), or any extra precaution/check step (e.g. 'confirm the resolution is still valid before closing') that isn't explicitly stated â€” if the instruction is terse, keep your answer just as terse rather than filling in plausible-sounding procedure around it, even a well-intentioned one. Only cite ids from this list, never another: z(none provided)z.

Instruction for this turn: rO   z)Source material you may cite or draw on:
rM   r…   r†   rˆ   r‰   rx   Z	assistantrŠ   r‹   Zsubmit_turn_responseN)r~   r   rJ   rŒ   Ú_TURN_RESPONSE_TOOLrŽ   r   r   r   Zmodel_validate_jsonr‘   r   )rK   rq   rr   r[   rv   ru   r…   r”   r`   r•   r–   r"   r"   r#   rl   _  s2    õôóÿ
ÿzReasoningAgent._phrase_responsec                 C   sŒ  g }|d u rdg d d dgdœS |j d d |j¡ |jrBd|j nd }t| j ¡  ¡ ƒ}|D ]}	|	|vr^| d|	› �¡ q^|j	D ]}	|	|vr‚| d|	›d�¡ q‚d d	d
„ |d|ig D ƒ¡}
d }|�r|  
tddd|
› d|› d|› �¡}|rü| d¡�s| d|�r| dd¡nd ¡ |  
tddd|
› d|› d|› �¡}|�rP| d¡�sp| d|�rh| dd¡nd ¡ | dd„ |D ƒ|||dœS )NFz0model did not return a valid structured responserz   r€   rM   z&citation references unknown document: zmodel cited z!, which wasn't provided this turnc                 s   s    | ]}d |  dd¡› �V  qdS )z- r‰   rM   Nr   )rT   Útr"   r"   r#   Ú	<genexpr>ª  rV   z)ReasoningAgent._verify.<locals>.<genexpr>r‰   r   uö  Does every POLICY/PROCESS claim in this response hold up against the source material? The response may also restate facts the representative already reported in the conversation (the customer's own circumstances, what they said, what was checked) â€” that is NOT a violation, even though those facts aren't in the source material. Only flag a claim about policy, process, or company procedure that is NOT supported by the source material. A paraphrase is fine; an invented policy/process detail is not.zUCONVERSATION SO FAR (facts already confirmed, not to be checked against the source):
z>

SOURCE MATERIAL (check policy/process claims against this):
z

RESPONSE:
r   zgrounding check failed: r   zno verdict returnedr   uJ  Does this response follow sensibly from what the representative just said, GIVEN the conversation so far? A short confirmation like "ready" or "yes" is responsive if the response correctly proceeds with the next step implied by the conversation â€” it does not need to restate or re-explain context the representative already has.zCONVERSATION SO FAR:
z#

REPRESENTATIVE'S LATEST MESSAGE:
r   zrelevance check failed: c                 S   s   g | ]}d |i‘qS rR   r"   rS   r"   r"   r#   rU   Ö  rV   z*ReasoningAgent._verify.<locals>.<listcomp>)rX   r�   rY   rZ   ÚsetrG   Z	documentsÚkeysr~   r\   Ú_judgeÚ_GROUNDING_TOOLr   Ú_RELEVANCE_TOOL)rK   rq   rw   rv   ru   rr   r}   Zresponse_textZ	valid_idsrƒ   Zconversationr{   r|   r"   r"   r#   rm   ‡  sr    ÿ
ÿþÿ
ÿþþ÷þ
ÿÿù
þûzReasoningAgent._verifyc              	   C   s‚   |d|› d� }| j jd|dœd|dœg|gdd�}|jp<g D ]>}|jj|kr>zt |jj¡W   S  tjyz   Y  d S 0 q>d S )Nz You MUST call the z tool with your verdict.r…   r†   rˆ   rŠ   r‹   )	rJ   rŒ   rŽ   r   r   r�   r�   r‘   r’   )rK   ZtoolZ	tool_nameZsystem_promptZuser_contentr•   r–   r"   r"   r#   rž   Ü  s    ÿýzReasoningAgent._judgec                 C   s`   d|  dd¡› d�|  d¡r dnd d |  d	¡rDd
|  d¡› d�nd d |  d¡rZdnd S )NzXCustomer complaint:
Customer complained after receiving a cancellation notice following r   Zmultiplez( missed Direct Debits.

Account review:
r   zNPrevious advisor notes confirmed incorrect DD-reapplication advice was given.
z)No incorrect prior advice was confirmed.
z
Vulnerability:
r   z,Vulnerability disclosed; consent to record: r   z.
zNo vulnerability disclosed.
z
Customer response:
r   z"Customer accepted the resolution.
z%Customer response not yet confirmed.
r   )rK   r/   r"   r"   r#   ro   ë  s&    
ÿÿûúÿø	÷
õÿzReasoningAgent._case_note)NN)NN)Ú__name__Ú
__module__Ú__qualname__rL   ry   ri   rj   rk   rf   rl   rm   rž   ro   r"   r"   r"   r#   rI   Ð   s   

G(UrI   )Ú__doc__r�   r*   Zpydanticr   Zevidence_storer   Zmodel_clientr   Zschemasr   Ztool_schemar™   ÚcompileÚ
IGNORECASErc   rg   rh   r   rF   r�   rŸ   r    r$   r4   r7   rH   rI   r"   r"   r"   r#   Ú<module>   sŒ   
ûþÿþþþþþïþúþ%þúýþþúýþ5