
    ic                        d Z ddlmZ ddlZddlmZ ddlmZ ddlm	Z	  e	e
      ZdZdZd	Zd
ZddZddZddZ G d d      Zy)u  
utils/context_compressor.py — 5단계 컨텍스트 압축 엔진

Phase1: 오래된 툴 결과 플레이스홀더 교체 | Phase2: head 보호
Phase3: tail 토큰 예산 보호 | Phase4: 중간 구역 규칙 기반 요약
Phase5: 툴 쌍 무결성 복원 (고아 result 제거, 고아 call 스텁 삽입)
토큰 추정: 한국어 비율 > 30%이면 chars / 2.5, 아니면 chars / 4
    )annotationsN)Any)generate_summary)
get_logger      g      @g333333?c                H    | syt        d | D              }|t        |       z  S )u?   텍스트 내 한글 음절 비율을 반환한다 (0.0 ~ 1.0).g        c              3  >   K   | ]  }d |cxk  rdk  sn nd  yw)u   가u   힯   N ).0chs     I/home/jay/workspace/.worktrees/task-2116-dev1/utils/context_compressor.py	<genexpr>z'_detect_korean_ratio.<locals>.<genexpr>   s     FR8r+EX+EqFs   
)sumlen)textkorean_counts     r   _detect_korean_ratior      s'    F4FFL#d)##    c                    | syt        |       }|t        kD  r%t        dt        t	        |       t
        z              S t        dt	        |       t        z        S )uK   텍스트의 토큰 수를 추정한다. 한국어 비율에 따라 보정.r   )r   _KOREAN_THRESHOLDmaxintr   _CHARS_PER_TOKEN_KO_CHARS_PER_TOKEN)r   ratios     r   _estimate_tokensr   #   sN     &E  1c#d)&99:;;q#d)//00r   c                    | j                  d      xs d}| j                  d      }d}|r	 t        j                  |d      }t        ||z         S # t        t        f$ r t        |      }Y *w xY w)Ncontent 
tool_callsF)ensure_ascii)getjsondumps	TypeError
ValueErrorstrr   )msgr    tcsextras       r   _msg_tokensr-   -   sp    ggi &BG
'',
CE
	JJs7E GeO,, :& 	HE	s   A A.-A.c                  d    e Zd ZdZ	 	 	 	 d
	 	 	 	 	 	 	 	 	 ddZddZddZddZddZddZ	ddZ
y	)ContextCompressoru   5단계 컨텍스트 압축기.c                J    || _         || _        || _        || _        d | _        y N)context_limitthreshold_percentprotect_first_ntail_token_budget_previous_summary)selfr2   r3   r4   r5   s        r   __init__zContextCompressor.__init__<   s,     +!2.!2-1r   c                v    |syt        d |D              }|t        | j                  | j                  z        kD  S )u-   총 토큰이 threshold를 초과하면 True.Fc              3  2   K   | ]  }t        |        y wr1   )r-   )r   ms     r   r   z4ContextCompressor.should_compress.<locals>.<genexpr>M   s     5qKN5s   )r   r   r2   r3   )r7   messagestotals      r   should_compressz!ContextCompressor.should_compressI   s9    5H55s4--0F0FFGGGr   c           	        |sg S t        | j                  t        |            dz
  }| j                  ||      }| j	                  |t        |      |z
        \  }}|rt
        j                  d|       | j                  |t        | j                  t        |            dz
  d      }| j                  || j                  ||      d      }|dz   |}}||k\  r| j                  |      S |d| ||| ||d }}
}	t        |
| j                        }|r|| _
        |	d|dgz   |z   }n|	|z   }| j                  |      }t
        j                  d	t        |      t        |      t        |
             |S )
uJ   5단계 압축을 수행하고 압축된 메시지 목록을 반환한다.r   zPhase1: pruned %d tool resultsforwardbackwardN)prev_summaryuser)roler    u%   compress: %d → %d messages (mid=%d))minr4   r   _find_tail_cut_by_tokens_prune_old_tool_resultsloggerdebug_align_boundary_sanitize_tool_pairsr   r6   )r7   r<   head_endtail_estmsgspruned
tail_start	mid_startmid_endheadmiddletailsummary_text
compressedresults                  r   compresszContextCompressor.compressP   s{   It++S];a? 008D33Hc(mh>VWfLL96B ''c$2F2FD	.RUV.VXab))$0M0MdT\0]_ij
%\:7	,,T22!*9-tIg/FWXdf (T=S=ST%1D"&\!J KKdRJJ **:6<c(mSQW[Z]^dZefr   c                   g }d}t        |      |z
  }t        |      D ]  \  }}|j                  d      dk(  rs||k  rnt        |j                  d      xs d      t        k\  rIt        |j                  d      xs d      }t	        |      }	d| d|	d<   |j                  |	       |dz  }|j                  |        ||fS )	u[   200자 이상 오래된 툴 결과를 플레이스홀더로 교체. (count, pruned) 반환.r   rD   toolr    r!   u$   [tool result truncated — original z chars]r   )r   	enumerater$   _TOOL_RESULT_MIN_LENdictappend)
r7   r<   protect_tail_countrX   rO   tail_boundaryir*   orig_lenreplacements
             r   rG   z)ContextCompressor._prune_old_tool_resultst   s    H(::) 	#FAs6)%	*0b15IIswwy17R8"3i+OPXzY`)aI&k*!c"	# v~r   c                    d}t        |      }t        t        |      dz
  |d      D ]&  }|t        ||         z  }|| j                  k  r|}& n t	        ||dz         S )u_   역방향 토큰 누적으로 tail 시작 인덱스를 반환한다 (head_end+1 이상 보장).r   r   )r   ranger-   r5   r   )r7   r<   rL   accumulatedrP   rb   s         r   rF   z*ContextCompressor._find_tail_cut_by_tokens   sm    ]
s8}q((B7 	A;x{33Kd444
	 :x!|,,r   c           	        |D ch c]B  }|j                  d      xs g D ](  }t        |t              r|j                  d      r|d   * D }}}|D cg c],  }|j                  d      dk(  r|j                  d      |vr+|. }}|D ch c].  }|j                  d      dk(  s|j                  d      s*|d   0 }}g }|D ]~  }|j                  |       |j                  d      xs g D ]S  }t        |t              s|j                  dd      }|s)||vs.|j                  d|dd       |j	                  |       U  |S c c}}w c c}w c c}w )	uP   툴 쌍 무결성 복원: 고아 result 제거 → 고아 call에 스텁 삽입.r"   idrD   r[   tool_call_idr!   u2   [tool result not available — context compressed])rD   rk   r    )r$   
isinstancer^   r_   add)	r7   r<   r*   tcall_call_idscleaned
result_idsfinalcids	            r   rK   z&ContextCompressor._sanitize_tool_pairs   sy   
  "
ww|,2"
 "d#t tH"
"
 "
 $
CGGFOv,E#''R`JaiuJuC
 

 ,3 
$'cggfo6OTWT[T[\jTkC 

  
  	,CLLggl+1r ,b$'&&r*Cs*4(.03+_ #s+,	, ="

 
s$   AE,EEE%E7Ec                   |s|S t        |      }t        dt        ||dz
              }|dk(  rb||dz
  k  rX||   ||dz      }}|j                  d      s(|j                  d      dk(  s|j                  d      dk(  r|dz  }n	 |S ||dz
  k  rX|S |dkD  rA||   ||dz
     }}|j                  d      dk(  s|j                  d      r|dz  }n	 |S |dkD  rA|S )u  툴콜 그룹 중간에 경계가 걸리지 않도록 idx를 조정한다.

        direction="forward": 그룹 전체를 head에 포함, idx를 그룹 밖으로 전진
        direction="backward": 그룹 전체를 tail에서 제외, idx를 그룹 밖으로 후퇴
        r   r   r@   r"   rD   r[   )r   r   rE   r$   )r7   r<   idx	directionncurnxtprevs           r   rJ   z!ContextCompressor._align_boundary   s    JM!Sa!e_%	!A+#C=(37*;S77<(CGGFOv,EQW\bIb1HC 
 A+ 
 '$SM8C!G+<T776?f,0F1HC
 ' 
r   N)i  g      ?   i N  )
r2   r   r3   floatr4   r   r5   r   returnNone)r<   
list[dict]r}   bool)r<   r   r}   r   )r<   r   r`   r   r}   ztuple[list[dict], int])r<   r   rL   r   r}   r   )r<   r   ru   r   rv   r)   r}   r   )__name__
__module____qualname____doc__r8   r>   rY   rG   rF   rK   rJ   r   r   r   r/   r/   9   si    ) %#' !'22 !2 	2
 2 
2H"H(
-!Fr   r/   )r   r)   r}   r|   )r   r)   r}   r   )r*   zdict[str, Any]r}   r   )r   
__future__r   r%   typingr   utils.context_summarizerr   utils.loggerr   r   rH   r]   r   r   r   r   r   r-   r/   r   r   r   <module>r      sX    #   5 #	H	    $1	-V Vr   