
    eai                     8   U d 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
mZmZmZmZmZ ddlmZ  ej$                  d      Z ee      j+                         j,                  j,                  j,                  d	z  Zej1                  d
d
       dZdZdZdaedz  ed<   dadae ed<   d Z!de"de#e$   fdZ%de"de#e$   fdZ&defdZ'	 d$dedededz  de"de"de#e"   dz  ddfdZ(d%de"d e)dedz  de#e*   fd!Z+de)fd"Z,	 d$dedededz  de"de"de#e"   dz  ddfd#Z-y)&u1  
Coba 長期記憶(階段 3f)— RAG 用的向量索引。

用 Qdrant 本地檔案模式(in-process,無需 server)+ FastEmbed 多語言 embedding。
階段 5 部署到 NAS 時可換成 Qdrant 獨立 server,介面不變。

職責:
  - 把每則新訊息向量化、索引到 Qdrant
  - 提供「給我跟某問題相關的 N 則過去訊息」的搜尋介面
  - 支援冷啟 backfill(把 SQLite 既有訊息一次補進去)

embedding 模型:
  - sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
    → 384 維 / 多語言(含中文)/ ONNX 加速
    N)Path)UUID)AsyncQdrantClient)DistanceFieldConditionFilter
MatchValuePointStructVectorParams)selectzcoba.memoryqdrant_dataT)exist_okparentsmessagesz;sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2i  _clientF_initializedc                      t         Bddlm}  t        j	                  dt
                | t
              a t        j	                  d       t         S )u=   延遲載入 fastembed 模型(第一次用會下載 ~80MB)。r   TextEmbeddingu1   載入 embedding 模型 %s(第一次會下載)...)
model_nameu   embedding 模型 ready)_embedding_model	fastembedr   loggerinfoEMBEDDING_MODELr   s    </volume1/homes/robertsu/coba/app/app/services/coba_memory.py_get_embedding_modelr   4   s:     +GY(OD,-    textreturnc           	      p    t               }t        t        t        |j	                  | g                        S )u7   把文字轉成 384 維向量(同步,在 thread 跑)。)r   listnextiterembed)r   models     r   _embed_syncr'   ?   s*     "ET%++tf-./00r   c                 R   K   t        j                  t        |        d{   S 7 w)u=   async wrapper:embedding 計算放 thread 不卡 event loop。N)asyncio	to_threadr'   )r   s    r   
embed_textr+   F   s     "";5555s   '%'c                    K   t         t        t        t                    a t        s$	 t         j                  t               d{    dat         S 7 # t        $ r^ t         j                  t        t        t        t        j                               d{  7   t        j                  dt               Y pw xY ww)u@   取得 Qdrant client(本地檔案模式,自動建 collection)。N)path)sizedistance)collection_namevectors_configu   已建立 Qdrant collection: %sT)r   r   str
QDRANT_DIRr   get_collection
COLLECTION	Exceptioncreate_collectionr   
VECTOR_DIMr   COSINEr   r    r   r   
get_clientr;   K   s      #Z9	G((444 N 5 	G++ *+hooV ,    KK9:F	GsG   &B>A AA 
B>A AB;BB;8B>:B;;B>
message_id
channel_iduser_idcontentcreated_at_isotagsc                   K   t        |j                               dk  ry	 t                d{   }t        |       d{   }|j	                  t
        t        t        |       |t        |       t        |      |rt        |      nd|||xs g d      g       d{    y7 w7 f7 	# t        $ r-}t        j                  dt        |       dd |       Y d}~yd}~ww xY ww)u   把一則訊息加入向量索引。

    - 太短的訊息(< 5 字)跳過,索引也沒意義
    - 失敗靜默忽略(不影響對話主流程)
       N)r<   r=   r>   r?   
created_atrA   )idvectorpayload)r0   pointsu   index_message 失敗 msg=%s: %s   )lenstripr;   r+   upsertr5   r
   r2   r6   r   warning)	r<   r=   r>   r?   r@   rA   clientrF   es	            r   index_messagerP   ^   s      7==?aR!|#!'**mm&:!&)*o&)*o3:3w<#*&4 $
  
 	
 	
 $*	
"  R8#j/"1:MqQQRsc   C%B, B&B, B(AB,  B*!B, %C%&B, (B, *B, ,	C"5#CC%C""C%querylimitc                 j  K   t        | j                               dk  rg S 	 t                d{   }t        |        d{   }d}|+t	        t        dt        t        |                  g      }	 |j                  t        |||       d{   }|j                  }|D cg c]  }|j                  j                  d	      |j                  j                  d
      |j                  j                  d      |j                  j                  d      |j                  j                  dg       |j                  d c}S 7 7 7 # t        $ r% |j                  t        |||       d{  7  }Y w xY wc c}w # t         $ r&}	t"        j%                  d| dd |	       g cY d}	~	S d}	~	ww xY ww)u   根據 query 找 N 則最相關的過去訊息。

    回傳格式:[{message_id, content, user_id, created_at, score}, ...]
       Nr=   )value)keymatch)must)r0   rQ   query_filterrR   )r0   query_vectorrY   rR   r<   r?   r>   rD   rA   )r<   r?   r>   rD   rA   scoreu#   search_relevant 失敗 query=%r: %s   )rJ   rK   r;   r+   r   r   r	   r2   query_pointsr5   rH   AttributeErrorsearchrG   getr[   r6   r   rM   )
rQ   rR   r=   rN   rF   fltqrresultsprO   s
             r   search_relevantre      s    
 5;;=A	)!|#!%(( !$ZcR\o=^_`C	** * 	 +  B iiG" 

   iimmL999==399==3iimmL9		fb1 

 
	
7 $(  	"MM *# 	 *   G	

  <eCRj!L	s   F3F EF E3F 7E E	E $F (BE< F F3F F 	E %E90E31E96F 8E99F 	F0
F+%F0&F3+F00F3c                     K   ddl m}  ddlm} d} |        4 d{   }t	        |j                  t        |      j                  |j                  j                  d                   d{   j                         j                               }|D ]  }t        |j                  j                               dk  r*	 t        |j                   |j"                  |j$                  |j                  |j&                  r|j&                  j)                         nd|j*                  xs g        d{    |dz  } ddd      d{    t.        j1                  d	|       |S 7 E7 7 8# t,        $ r Y w xY w7 2# 1 d{  7  sw Y   BxY ww)
u   把 SQLite 既有訊息全部索引一次(冷啟用)。

    冪等:upsert 自動覆蓋既有 point,跑兩次沒事。
    回傳:成功索引的訊息數。
    r   )AsyncSessionLocal)MessageNrC    r<   r=   r>   r?   r@   rA      u&   backfill 完成,共索引 %d 則訊息)app.databaserg   app.models.channelrh   r"   executer   where
deleted_atis_scalarsallrJ   r?   rK   rP   rE   r=   r>   rD   	isoformatrA   r6   r   r   )rg   rh   countdbmsgsms         r   backfill_existing_messagesry      sC     /*E ""b2::7O!!'"4"4"8"8">?
 
79SSU A199??$%)# tt ||IIII?@||1<<#9#9#;QS2   
 	 #"& KK8%@L) #
  # #"""s   FE!FAE9+E$
,AE9=A0E(-E&.	E(7E99FE7F$E9&E((	E41E93E44E97F9F?F FFc           
      L    t        j                  t        | |||||             y)u2   同步入口:背景索引,不 block HTTP 回應。rj   N)r)   create_taskrP   rj   s         r   trigger_indexr|      s+     % r   )N)rC   N).__doc__r)   loggingpathlibr   uuidr   qdrant_clientr   qdrant_client.http.modelsr   r   r   r	   r
   r   
sqlalchemyr   	getLoggerr   __file__resolveparentr3   mkdirr5   r   r8   r   __annotations__r   r   boolr   r2   r"   floatr'   r+   r;   rP   intdictre   ry   r|   r:   r   r   <module>r      s        +   
		=	) (^##%,,33::]J
 
  $  -
O
 %)	T	! ( d 1c 1d5k 163 64; 6
+ 2 "%R%R%R D[%R 	%R
 %R s)d
%R 
%RP0 0S 0$+ 0Y]^bYc 0f# D OS
d 
 
td{ 

03
;?9t;K
W[
r   