
    RiQ                       U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	m
Z
 ddlmZ ddlmZ ddlmZ ddlmZ  ej$                  e      Zd	d
ddddddddZded<   dZdZ ej2                  d      df ej2                  d      df ej2                  d      dfgZg dZdZ	 ddlmZ d(dZ e G d  d!             Z!d"Z"d#Z# G d$ d%      Z$y# e$ r d&d'dZY .w xY w))u^   대화 메모리 모듈.

인메모리 deque + JSONL 파일 영속화 + 자동 요약 생성.
    )annotationsN)deque)	dataclass)datetime)Path)Anyu	   잼민이ur   너는 잼민이다. Google의 시각을 가지고 있다. 실용적이고 데이터 중심적으로 답변해라.namepersonau	   코덱스u   너는 코덱스다. OpenAI의 시각을 가지고 있다. 코드 중심적이고 구현 지향적으로 답변해라. 반드시 한국어로 답변해라.u	   클로디uj   너는 클로디다. Anthropic의 시각을 가지고 있다. 신중하고 균형 잡힌 분석을 해라.)gemini_view_botcodex_view_botclaude_view_botzdict[str, dict[str, str]]BOT_PERSONASz$/home/jay/workspace/memory/groupchati  z\d{6}-[1-4]\d{6}u   [주민번호]z01[016789]-?\d{3,4}-?\d{4}u   [전화번호]z\d{3,4}-\d{2,4}-\d{4,6}u   [계좌번호])u   다른 주제u   그건 그렇고u   화제를 바꿔u   다음 안건u   본론으로i,  )call_claudec                    K   t        d      w)Nu,   engine_v2 모듈을 찾을 수 없습니다.)ImportError)prompttimeouts     \/home/jay/workspace/.worktrees/task-2117-dev1/services/multimodel-bot/conversation_memory.pyr   r   N   s     HII   c                F    t         D ]  \  }}|j                  ||       }  | S )u;   텍스트에서 PII(개인식별정보)를 마스킹한다.)_PII_PATTERNSsub)textpatternreplacements      r   	_mask_piir   W   s+     - .{{;-.K    c                  6    e Zd ZU ded<   ded<   ded<   ded<   y)	ChatMessagestrsenderr   r   	timestampboolis_botN)__name__
__module____qualname____annotations__ r   r   r    r    c   s    K
ILr   r    2   iP  c                      e Zd Z	 	 d	 	 	 	 	 ddZdddZddZddZddZddZd dZ	d!d	Z
d"d#d
Zd$dZd$dZd$dZd$dZd$dZd$dZdd%dZd&dZd'd(dZd)d(dZd*dZd&dZdd+dZ	 d,	 	 	 	 	 	 	 	 	 d-dZy).ConversationMemoryNc                    i | _         || _        || _        i | _        i | _        t               | _        i | _        i | _        i | _	        i | _
        i | _        i | _        d| _        i | _        y )Nr+   )	_messages_max_messages_storage_base_message_count_summary_counterset_loaded_chats_jsonl_line_count_last_activity_inactivity_tasks_current_topic_summary_lock_llm_call_count_daily_llm_budget_rolling_summaries)selfmax_messagesstorage_bases      r   __init__zConversationMemory.__init__u   sw    
 9;))5 /102'*u1335@B.0.0/1&(24r   c                    | j                   y|xs t        j                         j                  d      }t	        | j                         | dz  S )u   오늘 날짜 JSONL 파일 경로. storage_base가 None이면 None 반환.

        ts가 주어지면 해당 timestamp 기준 날짜를 사용한다 (날짜 경계 역전 방지).
        N%Y-%m-%dz.jsonl)r1   r   nowstrftimer   )r>   tstodays      r   _today_jsonl_pathz$ConversationMemory._today_jsonl_path   sK    
 %%x||~//
;D&&'UG6*:::r   c                L    | j                   y t        | j                         dz  S )N	summariesr1   r   r>   s    r   _summaries_dirz!ConversationMemory._summaries_dir   s'    %D&&'+55r   c                L    | j                   y t        | j                         dz  S )NinsightsrK   rL   s    r   _insights_dirz ConversationMemory._insights_dir   s'    %D&&'*44r   c                <   t        |      }|| j                  vrL|j                         r+	 t        |dd      5 }t	        d |D              }ddd       nd}| j                  |<   | j                  |   t        k  r| j                  |xx   dz  cc<   |S |j                  }|j                  }|j                  }d}	 || d	| | z  }	t        |	      }
|
| j                  vrL|	j                         r+	 t        |	dd      5 }t	        d
 |D              }ddd       nd}| j                  |
<   | j                  |
   t        k  r| j                  |
xx   dz  cc<   |	S |dz  }# 1 sw Y   xY w# t
        $ r d}Y &w xY w# 1 sw Y   oxY w# t
        $ r d}Y |w xY w)ua   현재 JSONL 파일의 줄 수를 확인하고 rotation이 필요하면 새 파일 경로 반환.rutf-8encodingc              3      K   | ]  }d   yw   Nr*   .0_s     r   	<genexpr>zCConversationMemory._get_jsonl_path_with_rotation.<locals>.<genexpr>   s     ?!A?r   Nr   rX      _partc              3      K   | ]  }d   ywrW   r*   rY   s     r   r\   zCConversationMemory._get_jsonl_path_with_rotation.<locals>.<genexpr>   s     ar   )
r!   r6   existsopensum	Exception_JSONL_ROTATION_THRESHOLDstemsuffixparent)r>   	base_pathkeyfhcountre   rf   rg   part	candidatecand_keys              r   _get_jsonl_path_with_rotationz0ConversationMemory._get_jsonl_path_with_rotation   s   )nd,,,!iw? 32 #?r? 23
 */D""3'!!#&)BB""3'1,' ~~!!!!D6tfVH!==I9~Ht555##%"!)S7C 7r$'2$6E7
 E38&&x0%%h/2KK&&x0A50  AID! #3 3  E*7 7$ " !"sR   E/ E"E/ 2F  FF "E,'E/ /E>=E>F
F FFc           	        | j                  |j                        }|y| j                  |      }t        |j                        }|j
                  ||j                  j                         |j                  || j                  j                  |d      d}t        j                  |d      }	 t        j                  |j                  d       t        j                  |j                  d	       t!        |d
d      5 }t#        j$                  |t"        j&                         	 |j)                  |dz          t#        j$                  |t"        j*                         	 ddd       t        j                  |d       y# t#        j$                  |t"        j*                         w xY w# 1 sw Y   IxY w# t,        $ r }	t.        j1                  d|	       Y d}	~	yd}	~	ww xY w)u   JSONL 파일에 메시지를 append. 실패 시 로깅만 하고 예외 없음.

        인메모리 deque에는 원본 저장, JSONL 파일에만 PII 마스킹 적용.
        )rF   Ngeneral)r"   r   r#   r%   chat_id	topic_tagF)ensure_asciiTexist_ok  arS   rT   
  u7   JSONL 파일 저장 실패 (인메모리만 동작): %s)rH   r#   ro   r   r   r"   	isoformatr%   r9   getjsondumpsosmakedirsrg   chmodra   fcntlflockLOCK_EXwriteLOCK_UNrc   loggerwarning)
r>   rr   msgrh   
jsonl_pathmasked_textrecordlinerj   excs
             r   _append_to_jsonlz#ConversationMemory._append_to_jsonl   sq   
 **cmm*<	 77	B
  ) jj002jj,,00)D"
 zz&u5	[KK
))D9HHZ&&.j#8 3BB.3HHTD[)KKEMM23 HHZ' KKEMM23 3  	[NNTVYZZ	[sI   -AF/ <%F#"E:6$F#F/ :&F  F##F,(F/ /	G8GGc                    | j                   j                  |      }|r%|j                  |z
  j                         t        kD  ryt
        D ]  }||j                  v s y y)u?   침묵 갭 5분 초과 또는 전환 키워드 감지 시 True.TF)r7   r|   r#   total_seconds_SILENCE_GAP_SECONDS_TOPIC_CHANGE_KEYWORDSr   )r>   rr   r   lastkws        r   _detect_topic_changez'ConversationMemory._detect_topic_change  sZ    ""&&w/S]]T)88:=QQ( 	BSXX~	 r   c                   || j                   vr,| j                   j                  |       | j                  |       || j                  vr#t	        | j
                        | j                  |<   t        ||t        j                         |      }| j                  ||      rd| j                  |<   | j                  |   j                  |       | j                  ||       | j                  j                  |d      dz   | j                  |<   | j                  |   t        z  dk(  r| j!                  |       t        j                         | j"                  |<   | j%                  |       y)uL   메시지를 추가한다. 링 버퍼 초과 시 오래된 메시지 제거.maxlenr"   r   r#   r%   pendingr   rX   N)r5   add
load_todayr/   r   r0   r    r   rD   r   r9   appendr   r2   r|   _SUMMARY_TRIGGER_schedule_summaryr7   _schedule_inactivity_check)r>   rr   r"   r   r%   r   s         r   add_messagezConversationMemory.add_message  s5    $,,,""7+OOG$$..(&+43E3E&FDNN7#lln	
 $$Wc2+4D(w&&s+ 	gs+ (,':':'>'>w'JQ'NG$w'*::a?""7+ (0||~G$''0r   c                ^    || j                   vrg S t        | j                   |         }|| d S )u4   최근 limit개 메시지를 시간순으로 반환.N)r/   list)r>   rr   limitmsgss       r   get_contextzConversationMemory.get_context5  s4    $..(IDNN7+,UFG}r   c                4   | j                         }|yt        j                         j                  d      }|j                  }t        |j                  | d            }|syg }|D ]  }|j                         s	 t        |dd      5 }|D ]  }	|	j                         }	|	s	 t        j                  |	      }
|
j                  d      |k7  rA	 t        j                   |
d	         }|j'                  t)        |
j                  d
d      |
j                  dd      |t+        |
j                  dd                          	 ddd        |dd }t/        | j0                        }|j3                  |       || j4                  |<   y# t        j                  $ r t        j                  d|	       Y &w xY w# t"        t$        f$ r t        j                         }Y w xY w# 1 sw Y   xY w# t,        $ r"}t        j                  d||       Y d}~d}~ww xY w)u   오늘 날짜 JSONL 파일에서 해당 chat_id의 최근 50개 메시지를 로드하여 deque 복원.

        rotation 파일(_partN)도 포함하여 로드한다.
        NrC   z*.jsonlrR   rS   rT   u$   손상된 JSONL 라인 건너뜀: %rrr   r#   r"    r   r%   Fr   u(   load_today 파일 읽기 실패 (%s): %sir   )rH   r   rD   rE   rg   sortedglobr`   ra   stripr}   loadsJSONDecodeErrorr   r   r|   fromisoformatKeyError
ValueErrorr   r    r$   rc   r   r0   extendr/   )r>   rr   rh   rG   rg   	all_pathsloadedr   rj   raw_linedatarF   r   recentdqs                  r   r   zConversationMemory.load_today@  s   
 **,	 ''
3!! &v{{eWG3D'E F	$&#  	\J$$&\*cG< $& #+>>#3'$%#'::h#7D
  88I.'9$0!)!7!7[8I!JB ''+xx"'=%)XXfb%9*,'+DHHXu,E'F	%	 	\F !&d.@.@!A
		&"$w7  $33 %"NN+QS[\$% !)*5 0!)B0! 6  \I:WZ[[\sy   <G,
G $F9G F7'AG G,)F4	0G 3F4	4G 7#G	G G	G  G)	%G,,	H5HHc                $   | j                   j                  |      }| |j                         s|j                          	 t	        j
                         }|j                  | j                  |            }|| j                   |<   y# t        $ r Y yw xY w)ua   무활동 타이머를 (재)시작한다. 기존 타이머가 있으면 취소 후 새로 생성.N)	r8   r|   donecancelasyncioget_running_loopcreate_task_check_inactivityRuntimeError)r>   rr   existinglooptasks        r   r   z-ConversationMemory._schedule_inactivity_check}  s    ))--g6OO	++-D##D$:$:7$CDD.2D""7+ 		s   AB 	BBc                ,  K   | j                   j                  |      }t        j                  t               d{    | j                   j                  |      |k7  ry| j                  |d      }t        |      dk  ry| j                  |       y7 Ww)uO   INACTIVITY_TIMEOUT 동안 새 메시지가 없으면 요약을 트리거한다.Nr+   r      )r7   r|   r   sleepINACTIVITY_TIMEOUTr   lenr   )r>   rr   activity_at_startmessagess       r   r   z$ConversationMemory._check_inactivity  s      //33G<mm./// ""7+/@@ ##G2#6x=1w' 	0s   8BBABc                    | j                  |      }t        j                  |      r'	 t        j                         }|j	                  |       yy# t
        $ r |j                          Y yw xY w)uX   비동기 이벤트 루프가 있으면 create_task로 요약 스케줄, 없으면 skip.N)_generate_summaryr   iscoroutiner   r   r   close)r>   rr   resultr   s       r   r   z$ConversationMemory._schedule_summary  s`    ''0v&//1  ( '   s   %A A+*A+c                  K   | j                   j                  |      rt        j                  d|       yd| j                   |<   	 | j	                  |       d{    d| j                   |<   y7 # d| j                   |<   w xY ww)uB   call_claude로 요약을 생성하고 JSON 파일에 저장한다.u-   요약 이미 진행 중 (chat_id=%d), 스킵NTF)r:   r|   r   debug_do_generate_summary)r>   rr   s     r   r   z$ConversationMemory._generate_summary  s}      !!'*LLH'R&*7#	0++G444*/Dw' 5*/Dw's0   ABA0 A.A0 B.A0 0BBc                H	  K   | j                         }| j                  |d      }|syt        j                         j	                  d      }| j
                  j                  |d      }|| j                  k\  r"t        j                  d|| j                         y|dz   | j
                  |<   dj                  d	 |D              }d
| d}	 t        |       d{   }|j                         }
|
j                  d      r@t        j                   dd|
d      }
t        j                   dd|
      }
|
j                         }	 t#        j$                  |      }|d   }|j                  dg       }t-        |D ch c]  }|j.                   c}      }| j0                  j                  |t3        |            }t5        d|t3        |      z
  dz         }|}t        j                   dd|j                  dd            dd }|sd}t        j                         j7                         t        j                         j	                  d      ||d||||j                  dg       |j                  dg       |j                  dd      |d
}| j8                  j                  |      dk(  r|| j8                  |<   |y	 t;        j<                  |d        t;        j>                  |d!       t        j                         j	                  d      }|| j@                  vr5t-        |jC                  | d"            }t3        |      | j@                  |<   | j@                  j                  |d      dz   }|| j@                  |<   || d#| d#|d$d%z  }|jE                  t#        jF                  |d&d'(      d)*       t;        j>                  |d+       |jH                  |j                  d,d      |j                  dd      |j                  dd      dd d-}| jK                  ||.       y7 ?# t        $ r }	t        j                  d|	       Y d}	~	yd}	~	ww xY w# t"        j&                  t(        t*        f$ r
 |}g }i }Y w xY wc c}w # t        $ r }	t        j                  d/|	       Y d}	~	yd}	~	ww xY ww)0uQ   요약 생성 본체 (_generate_summary에서 세마포어 보호 하에 호출).r+   r   NrC   r   /   일일 LLM 호출 예산 초과 (%d/%d), 스킵rX   ry   c              3  R   K   | ]  }|j                    d |j                    ! yw: Nr"   r   rZ   ms     r   r\   z:ConversationMemory._do_generate_summary.<locals>.<genexpr>  #     %P!
"QVVH&=%P   %'u  아래 대화를 분석해줘. 반드시 JSON 형식으로만 응답해.
아래 대화의 핵심 논점과 결론을 3~5줄로 요약해줘. 주제 키워드 3~5개도 추출해줘.

한국어 대화 특성:
- 한국어 대화에서 주어는 자주 생략됩니다. 문맥으로 주어를 추론하세요.
- 감탄사(ㅋㅋ, ㅎㅎ, 와, 헐, 대박)는 주제 분류에 포함하지 마세요.

<user_content>
u  
</user_content>

{"summary": "3~5줄 요약", "key_topics": ["주제1", "주제2"], "topic_tag": "주요 주제 한 단어(영문 snake_case)", "key_decisions": ["결정사항1"], "action_items": ["액션1"], "consensus_level": "exploratory|tentative|agreed|decided"}u1   요약 생성 실패, 다음 트리거 대기: %sz```z^```(?:\w+)?\s*\n?r   )rk   z
\n?```\s*$summary
key_topicsu   [^a-zA-Z0-9가-힣_-]rs   rq      )fromtokey_decisionsaction_itemsconsensus_levelexploratory)
r#   datemessage_ranger   r   rs   r   r   r   participantsr   Tru   rw   _*.jsonr[   03dz.jsonFr]   rt   indentrS   rT   rz   r   filenamer   rs   r   )	new_entryu   요약 파일 저장 실패: %s)&rM   r   r   rD   rE   r;   r|   r<   r   r   joinr   rc   r   
startswithrer   r}   r   r   r   	TypeErrorr   r"   r2   r   maxr{   r9   r   r   r   r3   r   
write_textr~   re   _update_summary_index)r>   rr   summaries_dirr   rG   current_countconversation_textr   raw_responser   strippedparsedsummary_textr   r   r   rk   msg_frommsg_to
topic_slugsummary_datar   num	file_pathnew_index_entrys                            r   r   z'ConversationMemory._do_generate_summary  sf    ++-##G2#6 ''
3,,00:D222NNA&&
 &3a&7U# !II%Px%PP !! "II 	 	!,V!44L  %%'u%vv3RKHvvmR:H#>>+L	ZZ-F!),LL"5J x8!QXX89##''X?q%#h-/!34 VV4b&**[R[:\]^a_ab
"J "113LLN++J7&.f=#$##ZZ<"JJ~r:%zz*;]K((
 ""7+y8+5D( 	CKK5HH]E*LLN++J7E d333 2 2eWG3D EF14X%%g.''++GQ7!;C-0D!!'*%5':,aCy(NNI  

<eAF  !  HHY& &NN$((4)--k2>'++Ir:3B?	O &&}&Pm 5 	NNNPST	* $$h	: 	'LJF		 9t  	CNN<cBB	Cs   CR"
P PP A!R"?,Q +	R"4Q1D(R"0E&Q6 R"P 	Q#P>9R">QR"$Q.*R"-Q..R"6	R?RR"RR"c           
        |dz  }	 |j                         rc|`	 t        j                  |j                  d            }|j                  |       |j                  t        j                  |dd      d       y|j                  d      D cg c]  }|j                  dk7  s| }}t        |      d	k  ryg }t        |      D ]{  }	 t        j                  |j                  d            }	|j                  |j                  |	j                  d
d      |	j                  dd      |	j                  dd      dd d       } |j                  t        j                  |dd      d       y# t        $ r g }Y ;w xY wc c}w # t        $ r!}
t        j                  d||
       Y d}
~
d}
~
ww xY w# t        $ r }
t        j                  d|
       Y d}
~
yd}
~
ww xY w)u:  summaries/_index.json 캐시 인덱스를 갱신한다.

        _index.json이 이미 존재하면 new_entry만 append하여 갱신.
        _index.json이 없고 파일 수 200개 초과면 전체 스캔하여 생성.
        _index.json이 없고 파일 수 200개 이하면 아무것도 하지 않음.
        _index.jsonNrS   rT   Fr]   r   *.json   r   r   rs   r   r+   r   u0   인덱스 생성 중 파일 읽기 실패 %s: %su    _update_summary_index 실패: %s)r`   r}   r   	read_textrc   r   r   r~   r   r
   r   r   re   r|   r   r   )r>   r   r   
index_pathr   fall_json_filesindex_entriesfpr   r   s              r   r   z(ConversationMemory._update_summary_index;  s    #]2
(	D  "(&9=JDXDXbiDXDj9k OOI.))

8%J!( *   *7););H)EaAS`I`aaNa>"c) 35M^, ``::bllGl&DED!(((*$(HHVR$8)-+r)B'+xx	2'>s'C	` !!

=uQG  " ; % &#%& b" ! `NN#UWY[^__`  	DNN=sCC	Ds   F0 %E, :F0 :F0 E>"E>&F0 7F0 A8F +F0 ,E;7F0 :E;;F0 	F-F(#F0 (F--F0 0	G9GGc                  K   | j                  |d      }|syt        j                         j                  d      }| j                  j                  |d      }|| j                  k\  r1t        j                  d       | j                  j                  |d      S |dz   | j                  |<   dj                  d	 |D              }d
| d}	 t        |d       d{   }|| j                  |<   t        j                  d|t        |             |S 7 5# t        $ r<}t        j                  d|       | j                  j                  |d      cY d}~S d}~ww xY ww)u   현재 세션의 전체 메시지를 요약하여 롤링 서머리 생성.

        Claude CLI를 호출하여 요약 생성.
        결과를 self._rolling_summaries[chat_id]에 저장.
        r+   r   r   rC   r   u1   일일 LLM 예산 초과, 롤링 서머리 스킵rX   ry   c              3  R   K   | ]  }|j                    d |j                    ! ywr   r   r   s     r   r\   z>ConversationMemory.generate_rolling_summary.<locals>.<genexpr>  r   r   u   아래 토론 내용을 5줄 이내로 압축 요약해줘. 각 참여자의 핵심 주장과 합의/이견 사항을 포함해.

<user_content>

</user_content>   )r   Nu2   롤링 서머리 생성 완료 (chat_id=%d, %d자)u"   롤링 서머리 생성 실패: %s)r   r   rD   rE   r;   r|   r<   r   r   r=   r   r   infor   rc   )	r>   rr   r   rG   r   r   r   r   r   s	            r   generate_rolling_summaryz+ConversationMemory.generate_rolling_summaryr  sL     ##G2#6 ''
3,,00:D222NNNO**..w;;&3a&7U# II%Px%PP !! " 		<';;G/6D##G,KKLgWZ[bWcdN <  	<NN?E**..w;;	<sH   CE	D D4D ED 	E1EEEEEc                   | j                         }||j                         sg S t        j                         j	                  d      }t        |j                  | d            }g }|| d D ]9  }	 t        j                  |j                  d            }|j                  |       ; |S # t        $ r!}t        j                  d||       Y d}~cd}~ww xY w)uL   summaries/ 디렉토리에서 오늘 파일들을 읽어 최근 N개 반환.NrC   r   rS   rT   "   요약 파일 읽기 실패 %s: %s)rM   r`   r   rD   rE   r   r   r}   r   r
  r   rc   r   r   )	r>   r   r   rG   today_filesresultsr  r   r   s	            r   get_recent_summariesz'ConversationMemory.get_recent_summaries  s    ++- (<(<(>I''
3]//5'0ABC(*ufg& 	NBNzz",,,"@At$	N   NCRMMNs   16B++	C4CCc           
     ,   | j                         }||j                         sg S |dz  }|j                         r:	 t        j                  |j	                  d            }t        |d d      }|d| S t        d	 |j                  d
      D        d      }t        |      dkD  r| j                  |       g }|d| D ]{  }		 t        j                  |	j	                  d            }
|j                  |	j                  |
j                  dd      |
j                  dd      |
j                  dd      dd d       } |S # t        $ r }t        j                  d|       Y d}~d}~ww xY w# t        $ r!}t        j                  d|	|       Y d}~d}~ww xY w)un  summaries/ 디렉토리의 모든 .json 파일을 읽어 메타데이터 목록 반환.

        /메모리 InlineKeyboard 지원용. 최신순 정렬 후 limit만큼 반환.
        각 항목: {"filename": stem, "date": ..., "topic_tag": ..., "summary": ...(50자)}

        summaries/_index.json 존재 시 그것을 사용, 없으면 기존 glob 방식.
        Nr  rS   rT   c                &    | j                  dd      S )Nr   r   )r|   )xs    r   <lambda>z:ConversationMemory.get_all_summary_files.<locals>.<lambda>  s    QUU:WYEZ r   T)ri   reverseu9   _index.json 읽기 실패, glob 방식으로 fallback: %sc              3  @   K   | ]  }|j                   d k7  s|  yw)r  N)r
   )rZ   r  s     r   r\   z;ConversationMemory.get_all_summary_files.<locals>.<genexpr>  s     P1-8OQPs   r  )r  r	  r   r   rs   r   r+   r   r  )rM   r`   r}   r   r
  r   rc   r   r   r   r   r   r   re   r|   )r>   r   r   r  
index_dataindex_data_sortedr   	all_filesr  r  r   s              r   get_all_summary_filesz(ConversationMemory.get_all_summary_files  s    ++- (<(<(>I"]2
a37::j>R>R\c>R>d3e
$*:;Zdh$i!(%00
 P**84P
	 y>C&&}5(*FU# 	NBNzz",,,"@A$&GG $ 4%)XXk2%>#'88Ir#:3B#?		N 9  aZ\_``a2  NCRMMNs0   8D= A8E)=	E&E!!E&)	F2FFc           
       K   t        j                         j                  d      }| j                  j	                  |d      }|| j
                  k\  r"t        j                  d|| j
                         y| j                         }||j                         sy|j                         }g }t        |j                  d            D ]  }|j                  dk(  r	 t        j                  |j!                  d	
            }	|	j	                  dd      |	j	                  dd      dj#                  |	j	                  dg             dj#                  |	j	                  dg             g}
dj#                  |
      }||j                         v r|j%                  |	        |sy|dd }|dz   | j                  |<   g }t)        |d      D ]W  \  }}|j	                  dd      }|j	                  dd      }|j	                  dd      }|j%                  | d| d| d|        Y dj#                  |      }d| d| d}	 t+        |       d{   }|S # t&        $ r Y w xY w7 # t&        $ r&}t        j                  d|       d| }Y d}~|S d}~ww xY ww)uN   요약 메타데이터에서 키워드 매칭 후 LLM 자연어 답변 생성.rC   r   u?   일일 LLM 호출 예산 초과 (%d/%d), 스마트검색 스킵2   일일 LLM 호출 예산이 초과되었습니다.Nu   검색 결과가 없습니다.r  r  rS   rT   r   r   rs    r   r   rX   )startr   z. [z] r   ry   uX   아래 대화 요약들을 참고하여 질문에 답변해줘.

<user_content>
질문: u   

관련 요약:
u9   
</user_content>

한국어로 간결하게 답변해줘.u"   smart_search LLM 호출 실패: %su7   검색 결과 처리 중 오류가 발생했습니다: )r   rD   rE   r;   r|   r<   r   r   rM   r`   lowerr   r   r
   r}   r   r
  r   r   rc   	enumerater   )r>   queryrr   rG   r   r   query_lowermatchedr  r   searchable_parts
searchabletop5summary_linesiitemr   rs   r   summaries_textr   answerr   s                          r   smart_searchzConversationMemory.smart_search  s     ''
3,,00:D222NNQ&&
 H++- (<(<(>3 kkm(*++H56 	Bww-'zz",,,"@A HHY+HH["-HHTXXlB78HHTXXor:;	$  !XX&67
*"2"2"44NN4(	$ 3 rs| '4a&7U#  Q/ 	OGAt88FB'Db1I88Ir2L  A3c$r)B|n!MN		O =1g -. /66 		U&v..F
 I  > / 	UNN?ENseTF		Ush   CJB=IBJ3I II J	IJIJI 	J	#J>JJ		Jc                  K   t        j                         j                  d      }| j                  j	                  |d      }|| j
                  k\  r"t        j                  d|| j
                         y|dz   | j                  |<   | j                  |d      }dj                  d	 |D              }d
| d}	 t        |       d{   }| j                         }	|		 t        j                  |	d       t        j                  |	d       t        j                         j                  d      }t!        |	j#                  | d            }
t%        |
      dz   }|	| d|ddz  }|j'                  |d       t        j                  |d       | j)                          |S 7 # t        $ r&}t        j                  d|       d| }Y d}~d}~ww xY w# t        $ r }t        j                  d|       Y d}~jd}~ww xY ww)ub   현재까지 대화 요약 (핵심 논점, 결론, 액션 아이템) 생성 후 .md 파일 저장.rC   r   r   r&  rX   r+   r   ry   c              3  R   K   | ]  }|j                    d |j                    ! ywr   r   r   s     r   r\   z6ConversationMemory.generate_insight.<locals>.<genexpr>@  r   r   u   아래 대화를 정리해줘.
핵심 논점, 결론, 액션 아이템을 마크다운 형식으로 요약해줘.

<user_content>
r  Nu   insight 생성 실패: %su   insight 생성 실패: Tru   rw   z_insight_*.md	_insight_r   z.mdrS   rT   rz   u    insight 파일 저장 실패: %s)r   rD   rE   r;   r|   r<   r   r   r   r   r   rc   rP   r   r   r   r   r   r   r   _create_insight_event)r>   rr   rG   r   r   r   r   insight_textr   insights_dirr   r  r  s                r   generate_insightz#ConversationMemory.generate_insight1  s     ''
3,,00:D222NNA&&
 H&3a&7U###G2#6 II%Px%PP !! " 		;!,V!44L ))+#HL48u- //
; 1 1UG=2I JK(ma'(eWIc#Yc+JJ	$$\G$DE*
 	""$1 5 	;NN6<4SE:L	;"  HA3GGHsm   B4G?7F! FF! 
G?B0G G?F! !	G*GG?GG?	G<G72G?7G<<G?c                   |t        d      }	 |j                  dd       t        j                         j	                  d      }|d| dz  }dt        j                         j                         | j                         rt        | j                               ndd	}|j                  t        j                  |d
d      d       y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)u\   새 insight 생성 시 이벤트 파일을 생성한다. 아누가 대화 시작 시 참조.Nz!/home/jay/workspace/memory/eventsT)parentsrv   rC   zgroupchat-insight-z.eventzgroupchat-insight)typer#   r=  Fr]   r   rS   rT   u*   insight 이벤트 파일 생성 실패: %s)r   mkdirr   rD   rE   r{   rP   r!   r   r}   r~   rc   r   r   )r>   
events_dirrG   
event_path
event_datar   s         r   r;  z(ConversationMemory._create_insight_evente  s    ABJ	NTD9LLN++J7E#(:5'&HHJ+%\\^557=A=O=O=QD$6$6$8 9W[*J
 !!

:E!D  "   	NNNGMM	Ns   B7C 	C0C++C0c                   t         j                  ||dd      }|d   }| j                  |      }| j                  d      }g }	|rO|	j	                  d       |D ](  }
|
j                  dd      }|	j	                  d|        * |	j	                  d       | j
                  j                  |      }|r3|	j	                  d	       |	j	                  |       |	j	                  d       |ri|s|r|	j	                  d
       n|	j	                  d       |D ],  }|	j	                  |j                   d|j                          . |	j	                  d       |r6|	j	                  d       |	j	                  d|        |	j	                  d       |	j	                  |       dddd}|r||v r|	j	                  ||          n|	j	                  d       dj                  |	      S )u5   맥락 기반 프롬프트 문자열을 생성한다.r   r	   r      r   u   [이전 대화 요약]r   z- u   [현재 토론 요약]u   [최근 대화]u   [이전 대화]r   u   [현재 질문/발언]u   제이회장님: uu   새로운 관점과 아이디어를 자유롭게 제시하라. 다른 참여자의 의견에 동의할 필요 없다.u   지금까지 논의에서 공통점을 먼저 정리하라. 이견이 있는 부분만 간결하게 언급하라. 새로운 주제를 꺼내지 마라.u   최종 합의문을 작성하라. 형식: [합의사항] 모두가 동의한 내용 / [미합의] 이견이 남은 부분 / [다음 단계] 추가 논의가 필요한 항목. 새로운 의견 제시 금지.)divergeconverge	consensusuV   이전 발언들을 참고하되 중복되지 않는 새로운 관점을 제시해라.ry   )	r   r|   r   r  r   r=   r"   r   r   )r>   rr   bot_usernamecurrent_messagephasepersona_infor   r   rJ   partssr   rollingr   PHASE_PROMPTSs                  r   format_contextz!ConversationMemory.format_context}  s    $''|XZ6[\y)##G,--A-6	 LL12 2 uuY3r,012 LL ))--g6LL12LL!LL G././ :

|2chhZ89:LL LL12LL,_,=>?LL 	W O r j
 Um+LLu-.LLqryyr   )r+   N)r?   intr@   
str | NonereturnNone)N)rF   zdatetime | NonerV  Path | None)rV  rX  )rh   r   rV  r   )rr   rT  r   r    rV  rW  )rr   rT  r   r    rV  r$   )
rr   rT  r"   r!   r   r!   r%   r$   rV  rW  )r  )rr   rT  r   rT  rV  zlist[ChatMessage])rr   rT  rV  rW  )r   r   r   zdict[str, Any] | NonerV  rW  )rr   rT  rV  r!   )rG  )r   rT  rV  zlist[dict[str, Any]])r+   )r,  r!   rr   rT  rV  r!   )rC  rX  rV  rW  )r   N)
rr   rT  rK  r!   rL  r!   rM  rU  rV  r!   )r&   r'   r(   rA   rH   rM   rP   ro   r   r   r   r   r   r   r   r   r   r   r   r  r  r$  r7  r>  r;  rS  r*   r   r   r-   r-   t   s     #'55 !5 
	5H;6
5'R$[T!1F7%z((

0CJ1Dn"<P&/jI^2hN2 _c; ; *-; @C; Q[; 	; r   r-   )x   )r   r!   r   rT  rV  r!   )r   r!   rV  r!   )%__doc__
__future__r   r   r   r}   loggingr   r   collectionsr   dataclassesr   r   pathlibr   typingr   	getLoggerr&   r   r   r)   DEFAULT_STORAGE_BASEr   compiler   r   r   engine_v2.bot_apir   r   r   r    r   rd   r-   r*   r   r   <module>re     s@  
 #     	 	  !   			8	$  H
  q
 +' & >    RZZ#$&67RZZ-.0@ARZZ*+-=> t  J-     ! D  D Q  JJJs   ,C 
C! C!