
    (<ixW                       d Z ddl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        T/home/jay/workspace/.worktrees/task-2057-dev2/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J y)u5   메시지 총 토큰이 임계값 미만이면 False.r   r   userdxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxFNr!   r   should_compressselfccmsgss      r   "test_below_threshold_returns_falsez5TestShouldCompress.test_below_threshold_returns_falseA   s4    5VY'(!!$'5000r   c                f    t        d      }t        dddz        g}|j                  |      du sJ y)u4   메시지 총 토큰이 임계값 초과이면 True.r   r%   r&   r   ia  TNr(   r*   s      r   !test_above_threshold_returns_truez4TestShouldCompress.test_above_threshold_returns_trueH   s8    5VS6\*+!!$'4///r   c                B    t               }|j                  g       du sJ y)u!   빈 메시지 목록이면 False.FN)r!   r)   r+   r,   s     r   !test_empty_messages_returns_falsez4TestShouldCompress.test_empty_messages_returns_falseO   s#    !!"%...r   c                f    t        d      }t        dddz        g}|j                  |      du sJ y)u9   정확히 임계값이면 False (초과가 아니므로).r   r%   r&   r   i N  FNr(   r*   s      r   'test_exactly_at_threshold_returns_falsez:TestShouldCompress.test_exactly_at_threshold_returns_falseT   s8    5VS6\*+!!$'5000r   N)__name__
__module____qualname____doc__r.   r0   r3   r5   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 sJ |dk(  sJ 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
   r>   Nr   .0r   s     r   	<genexpr>zZTestPruneOldToolResults.test_long_tool_result_replaced_with_placeholder.<locals>.<genexpr>m        EaQUU6]f-DE   ##[tool result truncatedr      Nr!   r   r   _prune_old_tool_resultsrB   r+   r,   r-   prunedcounttool_msgs         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+>>>>zzr   c                    t               }t        dd      t        ddd      g}|j                  |d      \  }}t        d	 |D              }|d
   dk(  sJ |dk(  sJ y)u3   200자 미만 툴 결과는 교체되지 않는다.r&   hir>   zshort resultcall_2rA   r   rC   c              3  J   K   | ]  }|j                  d       dk(  s|  ywrF   rG   rH   s     r   rJ   zNTestPruneOldToolResults.test_short_tool_result_not_replaced.<locals>.<genexpr>y   rK   rL   r   N)r!   r   rP   rB   rQ   s         r   #test_short_tool_result_not_replacedz;TestPruneOldToolResults.test_short_tool_result_not_replacedq   sq    h?
 224A2NE6EE	"n444zzr   c                    t               }t        dd      t        dt        d      d      g}|j                  |d      \  }}t	        d	 |D              }d
|d   vsJ |dk(  sJ y)u<   tail 보호 구간 내 툴 결과는 교체되지 않는다.r&   q1r>   ,  call_3rA      rC   c              3  J   K   | ]  }|j                  d       dk(  s|  ywrF   rG   rH   s     r   rJ   zUTestPruneOldToolResults.test_tail_protected_tool_result_not_pruned.<locals>.<genexpr>   rK   rL   rM   r   r   NrO   rQ   s         r   *test_tail_protected_tool_result_not_prunedzBTestPruneOldToolResults.test_tail_protected_tool_result_not_pruned}   su    c:

 224A2NE6EE'x	/BBBBzzr   N)r6   r7   r8   r9   rU   rZ   ra   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        d      }t        dd      t        dd      t        dd      t        dd      t        dd      g}|j                  |d	      }|dkD  sJ |t        |      k  sJ y
)u@   tail_token_budget 내에서 역방향으로 경계를 찾는다.d   r   r&   head1head2r'   xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxrN   head_endN)r!   r   _find_tail_cut_by_tokenslen)r+   r,   r-   
tail_starts       r   test_tail_cut_within_budgetz3TestFindTailCutByTokens.test_tail_cut_within_budget   sz    4!!###
 000B
A~~CI%%%r   c                    t        d      }t        d      D cg c]  }t        dd|        }}|j                  |d      }|dk\  sJ y	c c}w )
u6   tail 시작점이 항상 head_end 이후여야 한다.r   rf   
   r&   r   r_   rj   r   N)r!   ranger   rl   )r+   r,   ir-   rn   s        r   !test_tail_cut_never_overlaps_headz9TestFindTailCutByTokens.test_tail_cut_never_overlaps_head   sV    7/4Ry9!VqW%99000B
Q :s   AN)r6   r7   r8   r9   ro   rt   r   r   r   rc   rc      s    4&r   rc   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(  sJ yc c}w )u=   대응하는 tool_call이 없는 tool_result가 제거된다.r&   rW   r>   zorphan resultmissing_callrA   	assistantokr
   r   Nr!   r   _sanitize_tool_pairsr   rm   )r+   r,   r-   	sanitizedr   	tool_msgss         r   test_orphan_tool_result_removedz5TestSanitizeToolPairs.test_orphan_tool_result_removed   s}    ~Fd#

 ++D1	 )E1QUU6]f-DQE	E9~""" Fs   A6A6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(  sJ |d   j                  d      dk(  sJ yc c}w )uG   대응하는 tool_result가 없는 tool_call에 스텁이 삽입된다.ry   call_orphanfunctionnamedo_thingidtyper   r   r&   znext questionr
   r>   rN   r   r   Nr{   r+   r,   r-   r}   r   tool_resultss         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"">2mCCC Is    B
B
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(  sJ yc c}w )u,   올바른 툴 쌍은 그대로 보존된다.ry   call_okr   r   fnr   r   r>   resultrA   r
   r   rN   Nr{   r   s         r   test_matched_pair_preservedz1TestSanitizeToolPairs.test_matched_pair_preserved   s    #,jvW[n]^ 	:
 ++D1	#,iaf0GAEER[L\`hLhii< A%%% js   BB1B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(  sJ yc c}w )ue   스텁 삽입이 고아 결과 제거보다 먼저 처리되어 새 스텁이 제거되지 않는다.ry   call_xr   r   fr   r   r&   afterr
   r>   r   rN   Nr{   )r+   r,   r-   r}   r   stubss         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5zQ hs    BB/BN)r6   r7   r8   r9   r   r   r   r   r   r   r   rv   rv      s    N
#D&r   rv   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  sJ y)u4   compress() 후 메시지 총 길이가 감소한다.rN     r   r   r&   zsystem setupqry      zfinal questionc              3  X   K   | ]"  }t        |j                  d       xs d       $ ywr    Nrm   r   rH   s     r   rJ   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   rH   s     r   rJ   zMTestCompressIntegration.test_compress_reduces_total_length.<locals>.<genexpr>   s#     MQSy!1!7R8Mr   N)r!   r   r   sumcompress)r+   r,   r-   original_len
compressedcompressed_lens         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(  sJ |d   d   dk(  sJ y)uG   compress() 후 첫 protect_first_n 메시지가 그대로 유지된다.r_      r   systemzyou are an assistantr&   zfirst user messagery   r   finalr   r   rN   N)r!   r   r   r   )r+   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!}Y'+????r   c                D    t               }|j                  g       g k(  sJ y)u9   빈 메시지 목록을 compress해도 빈 목록 반환.N)r!   r   r2   s     r   *test_compress_empty_messages_returns_emptyzBTestCompressIntegration.test_compress_empty_messages_returns_empty  s     {{2"$$$r   c                    t        dd      }t        d      D cg c]  }t        dd|        }}|j                  |      }t	        |      t	        |      k(  sJ yc c}w )uP   모든 메시지가 보호 구간에 속하면 원본을 그대로 반환한다.rq   i  r   r   r&   msgN)r!   rr   r   r   rm   )r+   r,   rs   r-   r   s        r   ,test_compress_all_protected_returns_originalzDTestCompressIntegration.test_compress_all_protected_returns_original  s[    bEJ16q:AVs1#Y'::[[&
:#d)+++ ;s   A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 r,J  y)u:   반복 압축 후에도 툴 쌍 무결성이 유지된다.rN   r   r   r&   startry   c1r   r   r   r   r   r>   resrA   followupdoner   r   r
   r   N)r!   r   r   setr   add)r+   r,   r-   result1result2tool_call_idsr   tcs           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^,===	>r   N)	r6   r7   r8   r9   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\  sJ y)uE   forward 방향 정렬이 툴콜/결과 그룹 경계를 넘어간다.r&   r   ry   r   r   r   r   r   r   r>   r   rA   rB   rN   forwardidx	directionr   Nr!   r   _align_boundaryr+   r,   r-   aligneds       r   #test_align_forward_skips_tool_groupz5TestAlignBoundary.test_align_forward_skips_tool_group@  sq    zX^`cWd*e)fgT2 	
 $$TqI$F!||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  sJ y)uJ   backward 방향 정렬이 툴콜/결과 그룹 경계를 뒤로 보낸다.r&   r   ry   c2r   r   gr   r   r>   res2rA   rB   r_   backwardr   rN   Nr   r   s       r   $test_align_backward_skips_tool_groupz6TestAlignBoundary.test_align_backward_skips_tool_groupM  sq    zX^`cWd*e)fgd3 	
 $$TqJ$G!||r   N)r6   r7   r8   r9   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                t    t        dd      t        dd      g}t        |      }|t        |t              sJ yy)u<   generate_summary()가 문자열 또는 None을 반환한다.r&   zwhat is the capital of France?ry   ParisNr   r   
isinstancestrr+   turnsr   s      r   $test_generate_summary_returns_stringz:TestContextSummarizer.test_generate_summary_returns_stringc  sE     9:g&
 "%(~FC!888!8~r   c                "    t        g       }|J y)u7   빈 turns로 generate_summary() 호출 시 None 반환.N)r   )r+   r   s     r   .test_generate_summary_empty_turns_returns_nonezDTestContextSummarizer.test_generate_summary_empty_turns_returns_nonel  s    !"%~~r   c                p    t        dd      t        dd      g}t        |      }|t        |      dkD  sJ yy)u1   요약이 turns의 핵심 내용을 포함한다.r&   deploy the servicery   deploying nowNr   )r   r   rm   r   s      r   +test_generate_summary_includes_role_contentzATestContextSummarizer.test_generate_summary_includes_role_contentq  sG     -.o.
 "%(v;?"? r   c                    t        dd      t        dd      g}t        |      }t        |t              sJ d|v sJ d|v sJ y)u>   serialize_for_summary()가 turns를 텍스트로 변환한다.r&   r=   ry   worldN)r   r   r   r   r+   r   texts      r    test_serialize_for_summary_basicz6TestContextSummarizer.test_serialize_for_summary_basic|  sS     !g&
 %U+$$$$$$r   c                l    t        dddddddg      t        d	d
d      g}t        |      }d|v sJ y)u=   serialize_for_summary()가 tool_calls 정보를 포함한다.ry   r   r   	read_filez{})r   	argumentsr   r   r>   zfile contents hererA   N)r   r   r   s      r   &test_serialize_includes_tool_call_infoz<TestContextSummarizer.test_serialize_includes_tool_call_info  sS     #'R]lpIqrs -DA
 %U+d"""r   c                |    t        dd      t        dd      g}d}t        ||      }|t        |t              sJ yy)uE   prev_summary가 주어지면 이를 반영한 요약이 생성된다.r&   continuery   rz   z*Previously: discussed deployment strategy.)prev_summaryNr   )r+   r   prevr   s       r   'test_generate_summary_with_prev_summaryz=TestContextSummarizer.test_generate_summary_with_prev_summary  sD    fj)4T+BC;!%d;~FC!888!8~r   N)
r6   r7   r8   r9   r   r   r   r   r   r   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(  sJ y)u3   영어 텍스트는 chars/4 비율로 추정한다.r   _estimate_tokens  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaare   Nutils.context_compressorr   )r+   r   r   s      r   $test_english_text_uses_default_ratioz=TestEstimateTokensKorean.test_english_text_uses_default_ratio  s    =%,,,r   c                t    ddl m} d} ||      }t        t        |      dz        }|t	        d|      k(  sJ y)u>   한국어 비율 > 30%이면 chars/2.5 비율로 추정한다.r   r   u2   안녕하세요 반갑습니다 테스트입니다g      @rN   N)r   r   intrm   max)r+   r   r   tokensexpecteds        r   "test_korean_text_uses_korean_ratioz;TestEstimateTokensKorean.test_korean_text_uses_korean_ratio  s;    =C!$'s4y3'Q))))r   c                ^    ddl m} d} ||      }|t        dt        |      dz        k(  sJ y)uC   한국어 비율 < 30%이면 기본 chars/4 비율로 추정한다.r   r   uk   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa가나다가나다가나다rN      N)r   r   r   rm   )r+   r   r   r   s       r   ,test_mixed_text_below_threshold_uses_defaultzETestEstimateTokensKorean.test_mixed_text_below_threshold_uses_default  s1    =)!$'QD	Q////r   c                *    ddl m}  |d      dk(  sJ y)u#   빈 텍스트는 1을 반환한다.r   r   r   rN   Nr   )r+   r   s     r   test_empty_text_returns_onez4TestEstimateTokensKorean.test_empty_text_returns_one  s    =#q(((r   c                2    ddl m} d} ||      }|dk(  sJ y)u9   순수 한국어 텍스트의 비율이 1.0에 가깝다.r   _detect_korean_ratiou   가나다라마바사아g      ?Nr   r  r+   r  r   ratios       r   $test_detect_korean_ratio_pure_koreanz=TestEstimateTokensKorean.test_detect_korean_ratio_pure_korean  s     A)$T*||r   c                *    ddl m}  |d      dk(  sJ y)u%   빈 텍스트는 0.0을 반환한다.r   r  r   g        Nr  )r+   r  s     r   test_detect_korean_ratio_emptyz7TestEstimateTokensKorean.test_detect_korean_ratio_empty  s    A#B'3...r   c                J    ddl m} d} ||      }t        |dz
        dk  sJ y)uB   혼합 텍스트에서 한글 비율이 올바르게 계산된다.r   r  u   hello가나다g      ?g{Gz?N)r   r  absr	  s       r   test_detect_korean_ratio_mixedz7TestEstimateTokensKorean.test_detect_korean_ratio_mixed  s+    A$T*53;$&&&r   N)r6   r7   r8   r9   r   r   r  r  r  r  r  r   r   r   r   r     s(    C-*0)
/
'r   r   c                  (    e Zd ZdZd Zd Zd Zd Zy)TestGenerateSummaryUseLlmu3   use_llm 파라미터 동작 테스트 (mock 사용)c                b    t        dd      t        dd      g}t        |d      }|J d|v sJ y)	u=   use_llm=False는 기존 규칙 기반 요약을 반환한다.r&   r   ry   r   Fuse_llmN   [압축 요약])r   r   r   s      r   %test_use_llm_false_returns_rule_basedz?TestGenerateSummaryUseLlm.test_use_llm_false_returns_rule_based  sH     -.o.
 "%7!!! F***r   c                V   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      }|J d|v sJ 	 |||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&   r   ry   r   
os.environF)clearNANTHROPIC_API_KEYTr  r  )unittest.mockr  r   dictosenvironpopr   )r+   r  r   r  
env_backupr   s         r   ,test_use_llm_true_without_api_key_falls_backzFTestGenerateSummaryUseLlm.test_use_llm_true_without_api_key_falls_back  s    '-.o.
 ZZbZ6 	A(;TBJA)%>)))(F222)6@BJJ23	A 	A )6@BJJ23 *	A 	As#   !BB-BBBB(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      }|J d|v sJ d|v sJ 	 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	MagicMockr  r&   r   ry   r   z*Goal: Deploy service
Progress: In progress)r   r  r  test-key	anthropicTr  u   [LLM 요약]zGoal: Deploy service)sysr  r&  r  r   r   messagescreatereturn_value	Anthropicr  modulesr   )	r+   r)  r&  r  r   mock_responsemock_clientmock_anthropicr   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)%>)))%///-777	8	8 	88 8	8 	8s$   	C$(CC$C!	C$$C-c                v   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      }|J d|v sJ 	 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&   r   ry   r   z	API errorr  r  r'  r(  Tr  r  )r)  r  r&  r  r   	Exceptionr-  side_effectr  r.  r   )r+   r)  r&  r  r   r1  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)%>)))(F222	3	3 	33 3	3 	3s$   B/9B#B/#B,	(B//B8N)r6   r7   r8   r9   r  r#  r2  r6  r   r   r   r  r    s    =	+A(843r   r  )r   NN)
r
   r   r   r   r   z
str | Noner   zlist | Nonereturnr  )r]   )r   r   r7  r   )r7  r   )r9   
__future__r   r)  pathlibr   pytestpathinsertr   __file__parentr   r   utils.context_summarizerr   r   r   r   r   r!   r#   r;   rc   rv   r   r   r   r   r  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   