
    XixW                       d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
mZ ddlZe	j                  j                  d e ee      j"                  j"                  j"                               ddlmZ ddlmZmZ dZdddZdd d	Zd!d
Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z  G d d      Z! G d d      Z" G d d      Z#y)"us  
utils/context_compressor.py + context_summarizer.py 테스트 스위트 (TDD — RED → GREEN)

최소 15개 테스트:
- should_compress (임계값 이하/초과)
- 5단계 각각 단독 동작
- 툴 쌍 무결성 (고아 결과 제거, 고아 호출 스텁)
- 경계 정렬 (툴콜 그룹 중간 분할 방지)
- 빈 메시지, 모든 메시지 보호, 반복 압축
    )annotationsN)Path)ContextCompressor)generate_summaryserialize_for_summary'  c                ,    | |d}|||d<   |||d<   |S )u*   테스트용 메시지 dict 생성 헬퍼.)rolecontenttool_call_id
tool_calls )r
   r   r   r   ms        :/home/jay/workspace/utils/tests/test_context_compressor.py_msgr       s0    0A(.$,H    c                    d| z  S )u   n글자 긴 문자열.xr   )ns    r   _bigr   *   s    7Nr   c            	         t        t        | j                  dd      | j                  dd      | j                  dd            S )u&   기본 파라미터 compressor 생성.threshold_percent      ?protect_first_n   tail_token_budgeti  )context_limitr   r   r   )r   _MODEL_CONTEXTget)kwargss    r   _make_compressorr!   /   s?    $ **%8$?

#4a8 **%8%@	 r   c                  (    e Zd ZdZd Zd Zd Zd Zy)TestShouldCompressu,   임계값 기반 압축 필요 여부 판단c                   t        d      }t        dd      g}|j                  } ||      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            dx}x}x}}y)u5   메시지 총 토큰이 임계값 미만이면 False.r   r   userdxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxFiszU%(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s.should_compress
}(%(py3)s)
} is %(py8)sccmsgspy0py2py3py5py8assert %(py10)spy10Nr!   r   should_compress
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation	selfr+   r,   @py_assert1@py_assert4@py_assert7@py_assert6@py_format9@py_format11s	            r   "test_below_threshold_returns_falsez5TestShouldCompress.test_below_threshold_returns_falseA   s    5VY'(!!0!$'050'50000'5000000r000r000!000000$000$000'00050000000r   c                   t        d      }t        dddz        g}|j                  } ||      }d}||u }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}}y)u4   메시지 총 토큰이 임계값 초과이면 True.r   r%   r&   r   ia  Tr(   r*   r+   r,   r-   r3   r4   Nr5   r?   s	            r   !test_above_threshold_returns_truez4TestShouldCompress.test_above_threshold_returns_trueH   s    5VS6\*+!!/!$'/4/'4////'4//////r///r///!//////$///$///'///4///////r   c                   t               }|j                  }g } ||      }d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}y)	u!   빈 메시지 목록이면 False.Fr(   )zU%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.should_compress
}(%(py4)s)
} is %(py9)sr+   r.   r/   py4py6py9assert %(py11)spy11N)
r!   r6   r7   r8   r9   r:   r;   r<   r=   r>   	r@   r+   rA   @py_assert3@py_assert5@py_assert8rC   @py_format10@py_format12s	            r   !test_empty_messages_returns_falsez4TestShouldCompress.test_empty_messages_returns_falseO   s    !!.".!"%..%....%......r...r...!..."...%...........r   c                   t        d      }t        dddz        g}|j                  } ||      }d}||u }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}}y)u9   정확히 임계값이면 False (초과가 아니므로).r   r%   r&   r   i N  Fr(   r*   r+   r,   r-   r3   r4   Nr5   r?   s	            r   'test_exactly_at_threshold_returns_falsez:TestShouldCompress.test_exactly_at_threshold_returns_falseT   s    5VS6\*+!!0!$'050'50000'5000000r000r000!000000$000$000'00050000000r   N)__name__
__module____qualname____doc__rG   rI   rW   rY   r   r   r   r#   r#   >   s    610/
1r   r#   c                  "    e Zd ZdZd Zd Zd Zy)TestPruneOldToolResultsu.   오래된 툴 결과 플레이스홀더 교체c                   t               }t        dd      t        dt        d      d      t        dd      g}|j                  |d	      \  }}t	        d
 |D              }d}|d   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y)uM   200자 이상 오래된 tool 메시지가 플레이스홀더로 교체된다.r&   hellotool   call_1r   nextr   protect_tail_countc              3  J   K   | ]  }|j                  d       dk(  s|  ywr
   rb   Nr   .0r   s     r   	<genexpr>zZTestPruneOldToolResults.test_long_tool_result_replaced_with_placeholder.<locals>.<genexpr>m        EaQUU6]f-DE   ##[tool result truncatedr   in)z%(py1)s in %(py4)spy1rL   assert %(py6)srM   N   ==z%(py0)s == %(py3)scountr.   r0   assert %(py5)sr1   r!   r   r   _prune_old_tool_resultsrf   r7   r8   r<   r=   r>   r9   r:   r;   r@   r+   r,   prunedr{   tool_msg@py_assert0rR   @py_assert2@py_format5@py_format7rA   @py_format4@py_format6s                 r   /test_long_tool_result_replaced_with_placeholderzGTestPruneOldToolResults.test_long_tool_result_replaced_with_placeholderd   s   !c: 

 224A2NE6EE'>8I+>>'+>>>>>'+>>>>'>>>+>>>>>>>>uzuuur   c                   t               }t        dd      t        ddd      g}|j                  |d      \  }}t        d	 |D              }|d
   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }	dd|	iz  }
t        t	        j                  |
            dx}x}}d}||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}}y)u3   200자 미만 툴 결과는 교체되지 않는다.r&   hirb   zshort resultcall_2re   r   rg   c              3  J   K   | ]  }|j                  d       dk(  s|  ywrj   rk   rl   s     r   rn   zNTestPruneOldToolResults.test_short_tool_result_not_replaced.<locals>.<genexpr>y   ro   rp   r   rx   z%(py1)s == %(py4)srt   rv   rM   Nrz   r{   r|   r}   r1   )r!   r   r   rf   r7   r8   r<   r=   r>   r9   r:   r;   r   s                 r   #test_short_tool_result_not_replacedz;TestPruneOldToolResults.test_short_tool_result_not_replacedq   s    h?
 224A2NE6EE	"4n4"n4444"n444"444n4444444uzuuur   c                   t               }t        dd      t        dt        d      d      g}|j                  |d      \  }}t	        d	 |D              }d
}|d   }||v}|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y)u<   tail 보호 구간 내 툴 결과는 교체되지 않는다.r&   q1rb   ,  call_3re      rg   c              3  J   K   | ]  }|j                  d       dk(  s|  ywrj   rk   rl   s     r   rn   zUTestPruneOldToolResults.test_tail_protected_tool_result_not_pruned.<locals>.<genexpr>   ro   rp   rq   r   )not in)z%(py1)s not in %(py4)srt   rv   rM   Nr   rx   rz   r{   r|   r}   r1   r~   r   s                 r   *test_tail_protected_tool_result_not_prunedzBTestPruneOldToolResults.test_tail_protected_tool_result_not_pruned}   s   c:

 224A2NE6EE'Bx	/BB'/BBBBB'/BBBB'BBB/BBBBBBBBuzuuur   N)rZ   r[   r\   r]   r   r   r   r   r   r   r_   r_   a   s    8
r   r_   c                      e Zd ZdZd Zd Zy)TestFindTailCutByTokensu*   역방향 토큰 누적 tail 경계 탐색c                t   t        d      }t        dd      t        dd      t        dd      t        dd      t        dd      g}|j                  |d	      }d}||kD  }|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t        |      }||k  }|s#t        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}}y)u@   tail_token_budget 내에서 역방향으로 경계를 찾는다.d   r   r&   head1head2r'   xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxrw   head_end>)z%(py0)s > %(py3)s
tail_startr|   r}   r1   N<)z/%(py0)s < %(py5)s
{%(py5)s = %(py2)s(%(py3)s)
}lenr,   )r.   r/   r0   r1   zassert %(py7)spy7)r!   r   _find_tail_cut_by_tokensr7   r8   r9   r:   r;   r<   r=   r>   r   )
r@   r+   r,   r   r   rA   r   r   rB   @py_format8s
             r   test_tail_cut_within_budgetz3TestFindTailCutByTokens.test_tail_cut_within_budget   sO   4!!###
 000B
zA~zAzzAI%zI%%%%zI%%%%%%z%%%z%%%%%%C%%%C%%%%%%%%%%%%I%%%%%%%r   c                   t        d      }t        d      D cg c]  }t        dd|        }}|j                  |d      }d}||k\  }|st	        j
                  d	|fd
||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}}yc c}w )u6   tail 시작점이 항상 head_end 이후여야 한다.r   r   
   r&   r   r   r   r   >=z%(py0)s >= %(py3)sr   r|   r}   r1   N)r!   ranger   r   r7   r8   r9   r:   r;   r<   r=   r>   )	r@   r+   ir,   r   r   rA   r   r   s	            r   !test_tail_cut_never_overlaps_headz9TestFindTailCutByTokens.test_tail_cut_never_overlaps_head   s    7/4Ry9!VqW%99000B
zQzQzzQ :s   C,N)rZ   r[   r\   r]   r   r   r   r   r   r   r      s    4&r   r   c                  (    e Zd ZdZd Zd Zd Zd Zy)TestSanitizeToolPairsuD   툴 쌍 무결성: 고아 결과 제거, 고아 호출 스텁 삽입c                   t               }t        dd      t        ddd      t        dd      g}|j                  |      }|D cg c]  }|j                  d	      dk(  s| }}t	        |      }d
}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}yc c}w )u=   대응하는 tool_call이 없는 tool_result가 제거된다.r&   r   rb   zorphan resultmissing_callre   	assistantokr
   r   rx   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr   	tool_msgsr.   ru   r0   rM   assert %(py8)sr2   Nr!   r   _sanitize_tool_pairsr   r   r7   r8   r9   r:   r;   r<   r=   r>   )r@   r+   r,   	sanitizedr   r   r   rS   rB   r   rE   s              r   test_orphan_tool_result_removedz5TestSanitizeToolPairs.test_orphan_tool_result_removed   s    ~Fd#

 ++D1	 )E1QUU6]f-DQE	E9~""~""""~""""""s"""s""""""9"""9"""~"""""""""" Fs   E6E6c                   t               }t        dddddidg      t        dd	      g}|j                  |      }|D cg c]  }|j                  d
      dk(  s| }}t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}|d   }|j                  }d} ||      }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}x}}yc c}w )uG   대응하는 tool_result가 없는 tool_call에 스텁이 삽입된다.r   call_orphanfunctionnamedo_thingidtyper   r   r&   znext questionr
   rb   rw   rx   r   r   tool_resultsr   r   r2   Nr   r   )zJ%(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.get
}(%(py5)s)
} == %(py10)s)ru   r0   r1   r   r4   zassert %(py12)spy12r   )r@   r+   r,   r   r   r   r   rS   rB   r   rE   r   rD   @py_assert9rT   rF   @py_format13s                    r   test_orphan_tool_call_gets_stubz5TestSanitizeToolPairs.test_orphan_tool_call_gets_stub   s   #0*SY[eRfgh )
 ++D1	#,Haf0GHH< %A% A%%%% A%%%%%%s%%%s%%%%%%<%%%<%%% %%%A%%%%%%%AC""C>C">2CmC2mCCCC2mCCCCCC"CCC>CCC2CCCmCCCCCCCC Is    I	I	c                   t               }t        dddddidg      t        dd	d
      g}|j                  |      }|D cg c].  }|j                  d      dk(  s|j                  d      d	k(  s-|0 }}t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}yc c}w )u,   올바른 툴 쌍은 그대로 보존된다.r   call_okr   r   fnr   r   rb   resultre   r
   r   rw   rx   r   r   r   r   r   r2   Nr   )r@   r+   r,   r   r   r   r   rS   rB   r   rE   s              r   test_matched_pair_preservedz1TestSanitizeToolPairs.test_matched_pair_preserved   s   #,jvW[n]^ 	:
 ++D1	#,iaf0GAEER[L\`hLhii< %A% A%%%% A%%%%%%s%%%s%%%%%%<%%%<%%% %%%A%%%%%%% js   FF1Fc                   t               }t        dddddidg      t        dd	      g}|j                  |      }|D cg c].  }|j                  d
      dk(  s|j                  d      dk(  s-|0 }}t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}yc c}w )ue   스텁 삽입이 고아 결과 제거보다 먼저 처리되어 새 스텁이 제거되지 않는다.r   call_xr   r   fr   r   r&   afterr
   rb   r   rw   rx   r   r   stubsr   r   r2   Nr   )r@   r+   r,   r   r   r   r   rS   rB   r   rE   s              r   /test_stub_inserted_before_orphan_result_removedzETestSanitizeToolPairs.test_stub_inserted_before_orphan_result_removed   s   #+ZfVY][\
 !
 ++D1	%gqv&)@QUU>EZ^fEfgg5zQzQzQss55zQ hs    FF/FN)rZ   r[   r\   r]   r   r   r   r   r   r   r   r   r      s    N
#D&r   r   c                  .    e Zd ZdZd Zd Zd Zd Zd Zy)TestCompressIntegrationu2   compress() 전체 파이프라인 통합 테스트c                   t        dd      }t        dd      gt        dd      t        dt        d            gdz  z   t        dd	      gz   }t        d
 |D              }|j	                  |      }t        d |D              }||k  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d}y)u4   compress() 후 메시지 총 길이가 감소한다.rw     r   r   r&   zsystem setupqr      zfinal questionc              3  X   K   | ]"  }t        |j                  d       xs d       $ ywr    Nr   r   rl   s     r   rn   zMTestCompressIntegration.test_compress_reduces_total_length.<locals>.<genexpr>   s#     E13quuY/526E   (*c              3  X   K   | ]"  }t        |j                  d       xs d       $ ywr   r   rl   s     r   rn   zMTestCompressIntegration.test_compress_reduces_total_length.<locals>.<genexpr>   s#     MQSy!1!7R8Mr   r   )z%(py0)s < %(py2)scompressed_lenoriginal_len)r.   r/   zassert %(py4)srL   N)r!   r   r   sumcompressr7   r8   r9   r:   r;   r<   r=   r>   )	r@   r+   r,   r   
compressedr   rA   @py_format3r   s	            r   "test_compress_reduces_total_lengthz:TestCompressIntegration.test_compress_reduces_total_length   s    a3G &.)*FC ${DI">?!CDF,-./ 	
 EEE[[&
M*MM,,,,~,,,,,,~,,,~,,,,,,,,,,,,,,,,r   c           	        t        dd      }t        dd      t        dd      t        dt        d	            t        dt        d	            t        dd
      g}|j                  |      }|d   d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}|d   d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}y)uG   compress() 후 첫 protect_first_n 메시지가 그대로 유지된다.r      r   systemzyou are an assistantr&   zfirst user messager   r   finalr   r   rx   r   rt   rv   rM   Nrw   )	r!   r   r   r   r7   r8   r<   r=   r>   )	r@   r+   r,   r   r   rR   r   r   r   s	            r   'test_compress_protects_first_n_messagesz?TestCompressIntegration.test_compress_protects_first_n_messages  s   a3G12-.d3i(c#g&
 [[&
!}Y'A+AA'+AAAAA'+AAAA'AAA+AAAAAAAA!}Y'?+??'+?????'+????'???+????????r   c                   t               }|j                  }g } ||      }g }||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}y)u9   빈 메시지 목록을 compress해도 빈 목록 반환.rx   )zN%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.compress
}(%(py4)s)
} == %(py9)sr+   rK   rO   rP   N)
r!   r   r7   r8   r9   r:   r;   r<   r=   r>   rQ   s	            r   *test_compress_empty_messages_returns_emptyzBTestCompressIntegration.test_compress_empty_messages_returns_empty  s    {{$2${2$"$"$$$$"$$$$$$r$$$r$$${$$$2$$$$$$"$$$$$$$$r   c           	        t        dd      }t        d      D cg c]  }t        dd|        }}|j                  |      }t	        |      }t	        |      }||k(  }|st        j                  d|fd||f      d	t        j                         v st        j                  t              rt        j                  t              nd	d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      d	t        j                         v st        j                  t              rt        j                  t              nd	dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}yc c}w )uP   모든 메시지가 보호 구간에 속하면 원본을 그대로 반환한다.r   i  r   r   r&   msgrx   )zN%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py8)s
{%(py8)s = %(py5)s(%(py6)s)
}r   r   r,   )r.   ru   r0   r1   rM   r2   r3   r4   N)r!   r   r   r   r   r7   r8   r9   r:   r;   r<   r=   r>   )
r@   r+   r   r,   r   r   rC   rB   rE   rF   s
             r   ,test_compress_all_protected_returns_originalzDTestCompressIntegration.test_compress_all_protected_returns_original  s   bEJ16q:AVs1#Y'::[[&
:+#d)+)++++)++++++s+++s++++++:+++:+++++++++#+++#++++++d+++d+++)+++++++ ;s   G)c           	        t        dd      }t        dd      t        dddd	d
idg      t        ddd      t        dd      t        dd      g}|j                  |      }|j                  |      }t               }|D ]2  }|j	                  d      s|d   D ]  }|j                  |d           4 |D ]?  }|j	                  d      dk(  s|j                  }d}	 ||	      }
|
|v }|st        j                  d|fd|
|f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      t        j                  |
      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}x}	x}
}B y)u:   반복 압축 후에도 툴 쌍 무결성이 유지된다.rw   r   r   r&   startr   c1r   r   r   r   r   rb   resre   followupdoner   r   r
   r   rr   )zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} in %(py8)sr   tool_call_ids)r.   r/   rL   rM   r2   r3   r4   N)r!   r   r   setr   addr7   r8   r9   r:   r;   r<   r=   r>   )r@   r+   r,   result1result2r   r   tcrA   rR   rS   rC   rE   rF   s                 r   +test_compress_repeated_idempotent_structurezCTestCompressIntegration.test_compress_repeated_idempotent_structure  s   a3G!#'&RVXY T2$f%	
 ++d#++g& 	0Auu\"L/ 0B!%%bh/0	0  	>AuuV}&uu=^=u^,=,====,======q===q===u===^===,=================	>r   N)	rZ   r[   r\   r]   r   r   r   r   r  r   r   r   r   r      s    <-@%
,>r   r   c                      e Zd ZdZd Zd Zy)TestAlignBoundaryu0   툴콜 그룹 중간 분할 방지 경계 정렬c                   t               }t        dd      t        dddddidg	      t        d
dd      t        dd      g}|j                  |dd      }d}||k\  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y)uE   forward 방향 정렬이 툴콜/결과 그룹 경계를 넘어간다.r&   r   r   r   r   r   r   r   r   rb   r   re   rf   rw   forwardidx	directionr   r   r   alignedr|   r}   r1   Nr!   r   _align_boundaryr7   r8   r9   r:   r;   r<   r=   r>   r@   r+   r,   r  r   rA   r   r   s           r   #test_align_forward_skips_tool_groupz5TestAlignBoundary.test_align_forward_skips_tool_group@  s    zX^`cWd*e)fgT2 	
 $$TqI$Fw!|w!ww!r   c                   t               }t        dd      t        dddddidg	      t        d
dd      t        dd      g}|j                  |dd      }d}||k  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y)uJ   backward 방향 정렬이 툴콜/결과 그룹 경계를 뒤로 보낸다.r&   r   r   c2r   r   gr   r   rb   res2re   rf   r   backwardr
  rw   )<=)z%(py0)s <= %(py3)sr  r|   r}   r1   Nr  r  s           r   $test_align_backward_skips_tool_groupz6TestAlignBoundary.test_align_backward_skips_tool_groupM  s    zX^`cWd*e)fgd3 	
 $$TqJ$Gw!|w!ww!r   N)rZ   r[   r\   r]   r  r  r   r   r   r  r  =  s    :r   r  c                  4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestContextSummarizeru   규칙 기반 요약 생성c                4   t        dd      t        dd      g}t        |      }g }d}||u }|}|st        |t              }|}|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      d	z  }d
d|iz  }	|j                  |	       |sddt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      nddt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                        dz  }
|j                  |
       t	        j                  |d      i z  }dd|iz  }t        t	        j                  |            dx}x}x}x}}y)u<   generate_summary()가 문자열 또는 None을 반환한다.r&   zwhat is the capital of France?r   ParisNr(   z%(py2)s is %(py5)sr   r/   r1   %(py7)sr   2%(py13)s
{%(py13)s = %(py9)s(%(py10)s, %(py11)s)
}
isinstancestrrN   r4   rP   py13rw   assert %(py16)spy16r   r   r!  r"  r7   r8   r9   r:   r;   r<   append_format_boolopr=   r>   )r@   turnsr   rA   rB   rR   r   @py_assert12r   r   @py_format14@py_format15@py_format17s                r   $test_generate_summary_returns_stringz:TestContextSummarizer.test_generate_summary_returns_stringc  s    9:g&
 "%(88v~8FC!88!8888v888888v888v8888888888888888888888F888F888888C888C888!88888888888888r   c                d   t        g       }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y)u7   빈 turns로 generate_summary() 호출 시 None 반환.Nr(   )z%(py0)s is %(py3)sr   r|   r}   r1   )	r   r7   r8   r9   r:   r;   r<   r=   r>   )r@   r   r   rA   r   r   s         r   .test_generate_summary_empty_turns_returns_nonezDTestContextSummarizer.test_generate_summary_empty_turns_returns_nonel  sd    !"%v~vvvr   c                r   t        dd      t        dd      g}t        |      }|t        |      }d}||kD  }|st        j                  d|fd||f      d	t        j                         v st        j                  t              rt        j                  t              nd	d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}yy)u1   요약이 turns의 핵심 내용을 포함한다.r&   deploy the servicer   deploying nowNr   r   )z/%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} > %(py6)sr   r   r   r   r2   )r   r   r   r7   r8   r9   r:   r;   r<   r=   r>   )r@   r*  r   r   rS   rB   r   rE   s           r   +test_generate_summary_includes_role_contentzATestContextSummarizer.test_generate_summary_includes_role_contentq  s     -.o.
 "%(v;"";?""";""""""3"""3""""""v"""v""";"""""""""" r   c                   t        dd      t        dd      g}t        |      }t        |t              }|sddt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      nddt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      d	z  }t        t        j                  |            d
}d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d
x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d
x}}y
)u>   serialize_for_summary()가 turns를 텍스트로 변환한다.r&   ra   r   worldz5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}r!  textr"  )r.   ru   r/   rL   Nrr   z%(py1)s in %(py3)sru   r0   r}   r1   )r   r   r!  r"  r9   r:   r7   r;   r<   r=   r>   r8   )	r@   r*  r8  rR   r   r   r   r   r   s	            r    test_serialize_for_summary_basicz6TestContextSummarizer.test_serialize_for_summary_basic|  sc    !g&
 %U+$$$$$$$$$z$$$z$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$w$w$w$$w$w$w$$r   c                   t        dddddddg      t        d	d
d      g}t        |      }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}y)u=   serialize_for_summary()가 tool_calls 정보를 포함한다.r   r   r   	read_filez{})r   	argumentsr   r   rb   zfile contents herere   rr   r9  r8  r:  r}   r1   N)
r   r   r7   r8   r<   r9   r:   r;   r=   r>   )r@   r*  r8  r   r   r   r   s          r   &test_serialize_includes_tool_call_infoz<TestContextSummarizer.test_serialize_includes_tool_call_info  s     #'R]lpIqrs -DA
 %U+"{d""""{d"""{""""""d"""d"""""""r   c                <   t        dd      t        dd      g}d}t        ||      }g }d}||u }|}|st        |t              }|}|st	        j
                  d|fd	||f      d
t        j                         v st	        j                  |      rt	        j                  |      nd
t	        j                  |      dz  }	dd|	iz  }
|j                  |
       |sddt        j                         v st	        j                  t              rt	        j                  t              ndd
t        j                         v st	        j                  |      rt	        j                  |      nd
dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                        dz  }|j                  |       t	        j                  |d      i z  }dd|iz  }t        t	        j                  |            dx}x}x}x}}y)uE   prev_summary가 주어지면 이를 반영한 요약이 생성된다.r&   continuer   r   z*Previously: discussed deployment strategy.)prev_summaryNr(   r  r   r  r  r   r   r!  r"  r#  rw   r%  r&  r'  )r@   r*  prevr   rA   rB   rR   r   r+  r   r   r,  r-  r.  s                 r   'test_generate_summary_with_prev_summaryz=TestContextSummarizer.test_generate_summary_with_prev_summary  s   fj)4T+BC;!%d;88v~8FC!88!8888v888888v888v8888888888888888888888F888F888888C888C888!88888888888888r   N)
rZ   r[   r\   r]   r/  r1  r5  r;  r?  rD  r   r   r   r  r  `  s#    %9
	#	
#9r   r  c                  :    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
y	)
TestEstimateTokensKoreanu9   한국어 비율에 따른 토큰 추정 보정 테스트c                   ddl m} d} ||      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}}y)u3   영어 텍스트는 chars/4 비율로 추정한다.r   _estimate_tokens  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar   rx   r   rI  r8  r   r   r2   N
utils.context_compressorrI  r7   r8   r9   r:   r;   r<   r=   r>   )r@   rI  r8  r   rS   rB   r   rE   s           r   $test_english_text_uses_default_ratioz=TestEstimateTokensKorean.test_english_text_uses_default_ratio  s    =%,,%,,,,%,,,,,,,,,,,,,,,,,,,,,%,,,,,,,,,,r   c                   ddl m} d} ||      }t        t        |      dz        }d}t	        ||      }||k(  }|s7t        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndd	t        j                         v st        j                  t              rt        j                  t              nd	t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}y)u>   한국어 비율 > 30%이면 chars/2.5 비율로 추정한다.r   rH  u2   안녕하세요 반갑습니다 테스트입니다g      @rw   rx   )z9%(py0)s == %(py7)s
{%(py7)s = %(py2)s(%(py4)s, %(py5)s)
}tokensmaxexpected)r.   r/   rL   r1   r   assert %(py9)srN   N)rL  rI  intr   rP  r7   r8   r9   r:   r;   r<   r=   r>   )
r@   rI  r8  rO  rQ  rR   rD   rA   r   rU   s
             r   "test_korean_text_uses_korean_ratioz;TestEstimateTokensKorean.test_korean_text_uses_korean_ratio  s    =C!$'s4y3')Q))v)))))v)))))))v)))v))))))))))))Q))))))))))))))))))))r   c                   ddl m} d} ||      }d}t        |      }d}||z  }t        ||      }||k(  }	|	st	        j
                  d|	fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndd	t        j                         v st	        j                  t              rt	        j                  t              nd	t	        j                  |      d
t        j                         v st	        j                  t              rt	        j                  t              nd
dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      t	        j                  |      dz  }
dd|
iz  }t        t	        j                  |            dx}	x}x}x}x}}y)uC   한국어 비율 < 30%이면 기본 chars/4 비율로 추정한다.r   rH  uk   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa가나다가나다가나다rw      rx   )zg%(py0)s == %(py13)s
{%(py13)s = %(py2)s(%(py4)s, (%(py8)s
{%(py8)s = %(py5)s(%(py6)s)
} // %(py10)s))
}rO  rP  r   r8  )r.   r/   rL   r1   rM   r2   r4   r$  zassert %(py15)spy15N)rL  rI  r   rP  r7   r8   r9   r:   r;   r<   r=   r>   )r@   rI  r8  rO  rR   rC   r   @py_assert11r+  rA   r,  @py_format16s               r   ,test_mixed_text_below_threshold_uses_defaultzETestEstimateTokensKorean.test_mixed_text_below_threshold_uses_default  s   =)!$'/D	/Q/	Q/Q//v/////v///////v///v////////////Q///////////////D///D///	///Q////////////r   c                   ddl m} d} ||      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}x}}y)u#   빈 텍스트는 1을 반환한다.r   rH  r   rw   rx   z0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py7)srI  r.   r/   rL   r   rR  rN   NrK  )r@   rI  rA   rR   rD   rS   r   rU   s           r   test_empty_text_returns_onez4TestEstimateTokensKorean.test_empty_text_returns_one  s    = "(#(q(#q((((#q(((((((((((((((#(((q(((((((r   c                p   ddl m} d} ||      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}}y)u9   순수 한국어 텍스트의 비율이 1.0에 가깝다.r   _detect_korean_ratiou   가나다라마바사아g      ?rx   rz   ratior|   r}   r1   N
rL  ra  r7   r8   r9   r:   r;   r<   r=   r>   )r@   ra  r8  rb  r   rA   r   r   s           r   $test_detect_korean_ratio_pure_koreanz=TestEstimateTokensKorean.test_detect_korean_ratio_pure_korean  sl    A)$T*u|uuur   c                   ddl m} d} ||      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}x}}y)u%   빈 텍스트는 0.0을 반환한다.r   r`  r   g        rx   r\  ra  r]  rR  rN   Nrc  )r@   ra  rA   rR   rD   rS   r   rU   s           r   test_detect_korean_ratio_emptyz7TestEstimateTokensKorean.test_detect_korean_ratio_empty  s    A$&.#B'.3.'3....'3......#...#...B...'...3.......r   c           	        ddl m} d} ||      }d}d}||z  }||z
  }t        |      }d}	||	k  }
|
st        j                  d|
fd||	f      d	t        j                         v st        j                  t              rt        j                  t              nd	d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}x}x}
}	y)uB   혼합 텍스트에서 한글 비율이 올바르게 계산된다.r   r`  u   hello가나다r      g{Gz?r   )zH%(py9)s
{%(py9)s = %(py0)s((%(py1)s - (%(py3)s / %(py5)s)))
} < %(py12)sabsrb  )r.   ru   r0   r1   rN   r   zassert %(py14)spy14N)rL  ra  ri  r7   r8   r9   r:   r;   r<   r=   r>   )r@   ra  r8  rb  r   rB   rD   rC   rT   rX  @py_assert10r   r-  s                r   test_detect_korean_ratio_mixedz7TestEstimateTokensKorean.test_detect_korean_ratio_mixed  s    A$T*&Q&1Q3&53;&s;&$&$&&&&$&&&&&&s&&&s&&&&&&5&&&5&&&1&&&Q&&&&&&$&&&&&&&&r   N)rZ   r[   r\   r]   rM  rT  rZ  r^  rd  rf  rl  r   r   r   rF  rF    s(    C-*0)
/
'r   rF  c                  (    e Zd ZdZd Zd Zd Zd Zy)TestGenerateSummaryUseLlmu3   use_llm 파라미터 동작 테스트 (mock 사용)c                   t        dd      t        dd      g}t        |d      }d}||u}|st        j                  d|fd	||f      d
t	        j
                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}d}||v }|st        j                  d|fd||f      t        j                  |      d
t	        j
                         v st        j                  |      rt        j                  |      nd
dz  }dd|iz  }t        t        j                  |            dx}}y)u=   use_llm=False는 기존 규칙 기반 요약을 반환한다.r&   r3  r   r4  Fuse_llmNis notz%(py0)s is not %(py3)sr   r|   r}   r1      [압축 요약]rr   r9  r:  )
r   r   r7   r8   r9   r:   r;   r<   r=   r>   )r@   r*  r   r   rA   r   r   r   s           r   %test_use_llm_false_returns_rule_basedz?TestGenerateSummaryUseLlm.test_use_llm_false_returns_rule_based  s     -.o.
 "%7!!vT!!!!vT!!!!!!v!!!v!!!T!!!!!!! * F**** F*** ******F***F*******r   c                   ddl m} t        dd      t        dd      g}|j                  di d	      5  dd
l}|j
                  j                  dd
      }	 t        |d      }d
}||u}|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }	t        t        j                  |	            d
x}}d}
|
|v }|st        j                  d|fd|
|f      t        j                  |
      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }	t        t        j                  |	            d
x}
}|||j
                  d<   	 d
d
d
       y
# |||j
                  d<   w w xY w# 1 sw Y   y
xY w)uB   ANTHROPIC_API_KEY가 없으면 규칙 기반으로 fallback한다.r   )patchr&   r3  r   r4  
os.environF)clearNANTHROPIC_API_KEYTrp  rr  rt  r   r|   r}   r1   ru  rr   r9  r:  )unittest.mockrx  r   dictosenvironpopr   r7   r8   r9   r:   r;   r<   r=   r>   )r@   rx  r*  r~  
env_backupr   r   rA   r   r   r   s              r   ,test_use_llm_true_without_api_key_falls_backzFTestGenerateSummaryUseLlm.test_use_llm_true_without_api_key_falls_back  sf   '-.o.
 ZZbZ6 	A(;TBJA)%>%))vT))))vT))))))v)))v)))T)))))))(2(F2222(F222(222222F222F2222222)6@BJJ23	A 	A )6@BJJ23 *	A 	As$   !GEG,GGGG'c           	        ddl }ddlm}m} t	        dd      t	        dd      g} |       } |d	      g|_         |       }||j                  j                  _         |       }||j                  _        |j                  d
ddi      5  |j                  |j                  d|i      5  t        |d      }d}	||	u}
|
st        j                  d|
fd||	f      dt        j                          v st        j"                  |      rt        j$                  |      ndt        j$                  |	      dz  }dd|iz  }t'        t        j(                  |            dx}
}	d}||v }	|	st        j                  d|	fd||f      t        j$                  |      dt        j                          v st        j"                  |      rt        j$                  |      nddz  }dd|iz  }t'        t        j(                  |            dx}}	d}||v }	|	st        j                  d|	fd||f      t        j$                  |      dt        j                          v st        j"                  |      rt        j$                  |      nddz  }dd|iz  }t'        t        j(                  |            dx}}	ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)u6   LLM 호출이 성공하면 LLM 요약을 반환한다.r   N	MagicMockrx  r&   r3  r   r4  z*Goal: Deploy service
Progress: In progress)r8  ry  r{  test-key	anthropicTrp  rr  rt  r   r|   r}   r1   u   [LLM 요약]rr   r9  r:  zGoal: Deploy service)sysr|  r  rx  r   r   messagescreatereturn_value	Anthropicr}  modulesr   r7   r8   r9   r:   r;   r<   r=   r>   )r@   r  r  rx  r*  mock_responsemock_clientmock_anthropicr   r   rA   r   r   r   s                 r   test_use_llm_true_with_mock_apiz9TestGenerateSummaryUseLlm.test_use_llm_true_with_mock_api  s   2 -.o.

 "!*0]!^ _k3@##0"0;  -ZZ':J&GH 	8CKK+~)FG 8)%>%))vT))))vT))))))v)))v)))T)))))))%/~////~///~////////////////-7-7777-777-7777777777777777	8	8 	88 8	8 	8s%   	K(G=J6%K6J?	;KKc           	        ddl }ddlm}m} t	        dd      t	        dd      g} |       }t        d      |j                  _        |j                  d	d
di      5  |j                  |j                  d|i      5  t        |d      }d}||u}|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                   |      ndt        j                   |      dz  }	dd|	iz  }
t#        t        j$                  |
            dx}}d}||v }|st        j                  d|fd||f      t        j                   |      dt        j                         v st        j                  |      rt        j                   |      nddz  }	dd|	iz  }
t#        t        j$                  |
            dx}}ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)uD   LLM 호출 중 예외 발생 시 규칙 기반으로 fallback한다.r   Nr  r&   r3  r   r4  z	API errorry  r{  r  r  Trp  rr  rt  r   r|   r}   r1   ru  rr   r9  r:  )r  r|  r  rx  r   	Exceptionr  side_effectr}  r  r   r7   r8   r9   r:   r;   r<   r=   r>   )r@   r  r  rx  r*  r  r   r   rA   r   r   r   s               r   &test_use_llm_true_api_error_falls_backz@TestGenerateSummaryUseLlm.test_use_llm_true_api_error_falls_back  s]   2 -.o.

 #/8/E  ,ZZ':J&GH 	3CKK+~)FG 3)%>%))vT))))vT))))))v)))v)))T)))))))(2(F2222(F222(222222F222F2222222	3	3 	33 3	3 	3s%   G.9EG"G."G+	'G..G7N)rZ   r[   r\   r]   rv  r  r  r  r   r   r   rn  rn    s    =	+A(843r   rn  )r   NN)
r
   r"  r   r"  r   z
str | Noner   zlist | Nonereturnr}  )r   )r   rS  r  r"  )r  r   )$r]   
__future__r   builtinsr9   _pytest.assertion.rewrite	assertionrewriter7   r  pathlibr   pytestpathinsertr"  __file__parentrL  r   utils.context_summarizerr   r   r   r   r   r!   r#   r_   r   r   r   r  r  rF  rn  r   r   r   <module>r     s   	 #   
   3tH~,,33::; < 6 L 
1 1F' '^ >: :DD> D>X F99 99B0' 0'pN3 N3r   