
    *"hj                     6   d Z ddlZddlZddlZddlZddlmZmZ  ej                  e	      Z
 ej                  d      Z ej                  dej                        ZdZdZdZd	ed
efdZdeded
ee   fdZd	ed
ee   fdZdeee      d
efdZ G d d      Zy)a  
Process Reward Model (PRM) scorer.

Uses an OpenAI-compatible /v1/chat/completions endpoint as the judge,
so any external API (Anthropic, OpenAI, Together, etc.) works out of the box.

Public API:
    scorer = PRMScorer(
        prm_url="https://api.openai.com/v1",
        prm_model="gpt-5.2",
        api_key="your-openai-api-key",
        prm_m=3,
    )
    result = await scorer.evaluate(response_text, instruction_text)
    # result: {"score": float, "votes": list, "eval_text": str}
    N)AnyOptionalz\\boxed\{([-+]?\d)\}zScore:\s*([-+]?\d)z[32mz[36mz[0mtextreturnc                     ddl }|j                  dd| |j                        } |j                  dd|       } |j                  dd	|       } | S )
z7Replace XML-like tags that may trigger content filters.r   Nz<tool_call>.*?</tool_call>z[tool_call block])flagsz<[a-zA-Z_][^>]{0,80}>z[tag]z</[a-zA-Z_][^>]{0,80}>z[/tag])resubDOTALL)r   _res     //root/.hermes/SkillClaw/skillclaw/prm_scorer.py_sanitize_textr   (   sQ     7702EtSVS]S]7^D77+Wd;D77,h=DK    response_textinstruction_textc                 X    d}t        |      }t        |       }d| d| d}d|dd|dgS )z0Construct the judge messages for PRM evaluation.a  You are a quality reviewer for conversational responses.
You will be shown a user instruction and the assistant response to that instruction.
Based on instruction alignment and task completion quality, decide whether the response was helpful (+1), unhelpful (-1), or unclear (0).
Do NOT compare against any follow-up turn.
Only evaluate whether the response addresses the given instruction.
Use +1 when the response clearly follows and substantially completes the instruction.
Use -1 when the response is off-task, wrong, or fails to complete core requirements.
Use 0 when completion is ambiguous or evidence is insufficient.
Think briefly, then end your reply with exactly one of: Score: 1 / Score: -1 / Score: 0zInstruction:
z

Response:
z[

Was the response helpful for this instruction? End with Score: 1, Score: -1, or Score: 0.system)rolecontentuser)r   )r   r   r   clean_instructionclean_responser   s         r   _build_prm_judge_promptr   4   s_    		b  ''78#M2N
*+ ,$% &5	5 	 &1Ft3TUUr   c                     t         j                  |       }|rt        |d         }|dv r|S t        j                  |       }|rt        |d         }|dv r|S y)zParse score from model output.

    Accepts both formats:
      - "Score: 1" / "Score: -1" / "Score: 0"  (primary, avoids Azure filter)
      - "\boxed{1}" / "\boxed{-1}" / "\boxed{0}"  (legacy fallback)
    )   r   r   N)	_SCORE_REfindallint	_BOXED_RE)r   matchesvals      r   _parse_prm_scorer#   M   sc     %G'"+*J%G'"+*Jr   scoresc                    | D cg c]  }||	 }}|syt        j                  |      }|j                  d      d   }t        |j	                               j                  |d         dkD  ryt        |d         S c c}w )u3   Return the majority vote; ties or all-None → 0.0.        r   r   )collectionsCountermost_commonlistvaluescountfloat)r$   svalidcountertops        r   _majority_voter2   c   s}    01!-Q0E0!!%(G


a
 
#CGNN##CF+a/Q= 1s
   A<A<c                       e Zd ZdZ	 	 	 	 	 	 ddedededededefd	Z	 	 dd
ededededef
dZ	de
e   dedeee   ef   fdZy)	PRMScoreru{  
    Async PRM scorer using any OpenAI-compatible /v1/chat/completions API.

    Works with external providers (Anthropic, OpenAI, Together, Fireworks, etc.)
    or a self-hosted vLLM / LiteLLM proxy — anything that accepts
    POST /v1/chat/completions with {"model", "messages", ...}.

    Parameters
    ----------
    prm_url:
        Base URL of the API, e.g.
          - ``"https://api.anthropic.com/v1"``
          - ``"https://api.openai.com/v1"``
          - ``"http://localhost:8081/v1"``  (local vLLM)
    prm_model:
        Model name to pass in the request body.
    api_key:
        Bearer token.  If empty, no Authorization header is sent
        (useful for unauthenticated local endpoints).
    prm_m:
        Number of parallel votes for majority voting.
    temperature:
        Sampling temperature.
    max_new_tokens:
        Maximum tokens to generate per vote.
    Nprm_url	prm_modelapi_keyprm_mtemperaturemax_new_tokensc                     ||| _         n.	 ddlm} |j	                  d      }
d|i}|
|d<    |di || _         || _        || _        || _        || _        y # t        $ r}	t        d      |	d }	~	ww xY w)Nr   )OpenAIzLPRMScorer requires the 'openai' package. Install it with: pip install openai/r7   base_url )	_clientopenair<   ImportErrorrstripr6   r8   r9   r:   )selfr5   r6   r7   r8   r9   r:   
llm_clientr<   er>   client_kwargss               r   __init__zPRMScorer.__init__   s     !%DLy) ~~c*H-6,@M(0M*%!2M2DL"
&,  y!"pqwxxys   A 	A0A++A0responseinstruction
session_idturn_numr   c                   K   t        ||      }t        j                  t        | j                        D cg c]  }| j                  ||       c}  d{   }|D cg c]  }|d   	 }	}t        |	      }
d}|
dk7  r|D ]  \  }}|	|t        |
      k(  s|} n |	D cg c]  }||nd
 }}t        j                  t         d| d| d| j                   d	| d
|
 t                |
||dS c c}w 7 c c}w c c}w w)u^  
        Score one assistant response against the instruction for that turn.

        Returns
        -------
        dict with keys:
            "score"     – float in {-1.0, 0.0, 1.0}
            "votes"     – list of per-vote scores (int | "fail")
            "eval_text" – representative evaluation text (non-empty when score != 0)
        Nr    r&   failz[PRMScorer] session=z turn=z model=z votes=u    → score=)scorevotes	eval_text)r   asynciogatherranger8   _query_oncer2   r   loggerinfo_CYANr6   _RESET)rD   rI   rJ   rK   rL   msgsiresultsrr$   finalrepresentativer.   r   votes_displays                  r   evaluatezPRMScorer.evaluate   s%    " 'x=ERVR\R\L](^q)9)9$)B(^__ '(1!A$((v&C<" 4=Q#e*_%)N
 BHHAam7HHg)*VH: F^^$GM?+eWVHV	
 ^TT# )__( IsG   3DC0DC5DC7*D	D	D!C<.AD7
Dmessagesvote_idc                   K   	 t        j                  | j                  j                  j                  j
                  | j                  || j                  | j                         d {   }|j                  d   j                  j                  xs d}t        |      |fS 7 8# t        $ r!}t        j                  d||       Y d }~yd }~ww xY ww)N)modelrc   r9   max_completion_tokensr   rN   z&[PRMScorer] query failed (vote %d): %s)NrN   )rS   	to_threadr@   chatcompletionscreater6   r9   r:   choicesmessager   r#   	ExceptionrW   warning)rD   rc   rd   
completionr   rF   s         r   rV   zPRMScorer._query_once   s     	&00!!--44nn! ,,&*&9&9  J !((+33;;ArG#G,g55  	NNCWaP	sA   CA#B" 'B (7B" C B" "	C+CCCC)zgpt-5.2rN      g333333?i   N)rN   r   )__name__
__module____qualname____doc__strr   r-   rH   dictrb   r*   tupler   rV   r?   r   r   r4   r4   t   s    < # "-- - 	-
 - - -> $U$U $U 	$U
 $U 
$UL$t* s uXVY]\_M_G` r   r4   )ru   rS   r'   loggingr	   typingr   r   	getLoggerrr   rW   compiler    
IGNORECASEr   _GREENrY   rZ   rv   r   r*   rw   r   r   r#   r-   r2   r4   r?   r   r   <module>r      s   "    	  			8	$BJJ./	BJJ,bmm<				 	 	V3 V# V$t* V23 8C= ,	4. 	5 	"j jr   