
    ڤi/                        d 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
 ddlmZ ddlmZ dd	lmZ dd
lmZmZ ddlmZ ddlmZmZ ddlmZ  ee          ZdZdZdZ G d d          Z dS )u   研究引擎检索层。

封装关键词提取、ES 混合检索、图谱文档补充等检索相关逻辑，
由 ResearchEngine 通过组合方式使用。
    )annotations)Any)settings)EmbeddingService)GraphQueryPlanner)GraphQueryService)PermissionContext)_aggregate_es_hits)ESClientHYBRID_RRF_PIPELINE)	LLMClient)KEYWORD_EXTRACTION_SYSTEMKEYWORD_EXTRACTION_USER)
get_logger      iX  c                  :    e Zd ZdZddZddZddZd dZd!dZdS )"ResearchRetrieveru  研究检索器，封装关键词提取和多源文档检索逻辑。

    Parameters
    ----------
    es_client:
        Async Elasticsearch 客户端，用于混合检索。
    embedding_service:
        向量化服务，用于 kNN 查询。
    graph_service:
        Neo4j 图谱查询服务，用于图谱文档发现。
    llm_client:
        LLM 客户端，用于关键词提取。
    planner:
        图谱查询规划器，用于意图识别和证据收集。
    	es_clientr   embedding_servicer   graph_servicer   
llm_clientr   plannerr   returnNonec                L    || _         || _        || _        || _        || _        d S )N)_es
_embedding_graph_llm_planner)selfr   r   r   r   r   s         5D:\work\zm-rag\backend\app\core\research_retriever.py__init__zResearchRetriever.__init__6   s+     +#	    questionstr	list[str]c                  K   	 | j                             dt          ddt          j        |          dgdd           d{V }|                    d	g           }|                    d
g           }|                    dg           }||z   |z   dd         S # t          $ r5}t                              dt          |                     g cY d}~S d}~ww xY w)u   通过 LLM 从问题中提取检索关键词（含机构名和地域名），用于 ES 和图谱检索。

        Use LLM to extract search keywords (fast / cheap call).system)rolecontentuser)r&   g        i   )temperature
max_tokensNkeywordsorganizationsregions   keyword_extraction_failederror)
r    	chat_jsonr   r   formatget	Exceptionloggerwarningr'   )r"   r&   resultr0   orgsr2   excs          r#   extract_keywordsz"ResearchRetriever.extract_keywordsH   s     	9..%2KLL &#:#A8#T#T#T    / 
 
 
 
 
 
 
 
F #)**Z"<"<H$jj"==D!'Ir!:!:GtOg-rr22 	 	 	NN6c#hhNGGGIIIIII	s   BB 
C#*CCCpermr	   list[dict[str, Any]]c           
       K   	 |                                 }dd|gii}t          dz  }dd|ddgddig|gd	i}d
dt          t          dii}||ddgi|d}| j        j        r<d}		 | j                            |           d{V }	nI# t          $ r<}
t          
                    dt          |
          |dd                    Y d}
~
nd}
~
ww xY w|	|dd|dd|	||diigiiddgi|d}| j                            |t          j        t                     d{V \  }}|sKt                              d           | j        j                            t          j        |           d{V }nc| j        j                            t          j        |           d{V }n1| j        j                            t          j        |           d{V }t'          |t(                    r|n|j        }t-          |t                    }|                     |           d{V  |S # t          $ r5}t                              dt          |                     g cY d}~S d}~ww xY w)uC  执行 ES 混合检索（BM25 + kNN RRF 融合），按 doc_id 聚合后返回文档级结果。

        基于 ESClient.should_use_hybrid 判断是否走 hybrid 路径，
        通过 ESClient.hybrid_search 自动探测/熔断，无需字符串匹配回退。

        Run ES hybrid search and return doc-level results.boolmust   multi_matchztitle^3r,   best_fieldsqueryfieldstyperE   filterrK   fragment_sizenumber_of_fragmentsexcludescontent_vectorsizerJ   _source	highlightN'research_embedding_failed_bm25_fallback(   )r6   r&   hybridqueriesknn)vectorkrN   )indexpipeline research_hybrid_to_bm25_fallbackr_   bodyresearch_es_search_failedr5   )build_es_filter_MAX_CONTEXT_DOCS_MAX_PASSAGE_CHARS_MAX_PASSAGES_PER_DOCr   should_use_hybridr   embed_singler:   r;   r<   r'   hybrid_searchr   es_chunk_indexr   inforawsearch
isinstancedictrc   r
   _enrich_from_metar6   )r"   r&   rA   
acl_filtercombined_filter
fetch_size
bm25_queryhighlight_cfg	bm25_bodyquery_vectoremb_errhybrid_bodyresponseokrn   docsr?   s                    r#   	es_searchzResearchRetriever.es_searchd   s     e	--//J*./O +Q.J  *)1+4i*@(5, ,  // *J  );/D   -M ##&)9(:;*	) )I x) 637)-)E)Eh)O)O#O#O#O#O#O#OLL    NNA!'ll!)#2# #          + *$ )$.(-,<:F5?:I?. ?.0*%&,"'"  %/1A0B#C%2'3 3K* *.)?)?#&5!4 *@ * * $ $ $ $ $ $LHb
  $FGGG)-)<)<"*"9	 *= * * $ $ $ $ $ $
 &*X\%8%8&5I &9 & &            HH
 "&!4!4"1	 "5 " "       )488K((hmC%c+<==D((.........K 	 	 	LL4CHHLEEEIIIIII	sI   A"H! ' B H! 
C2C	H! 	CEH! !
I +*II I doc_ids
str | Nonec                ~  K   |sg S 	 |                                 }ddd|iig|gdi}|rd|ddgdd	ig|d         d
<   t          |          dz  |ddgiddt          t          diid}| j        j                            t          j        |           d{V }t          |t                    r|n|j        }t          |t          |                    }	|                     |	           d{V  |	D ]}
d|
d<   |	S # t          $ r5}t                              dt#          |                     g cY d}~S d}~ww xY w)u   从 ES 获取图谱发现的文档的高亮摘录，按权限过滤后返回文档级结果。

        Fetch top chunks for a set of doc_ids found via graph traversal.rD   termsdoc_idrM   rG   ztitle^2r,   rH   rI   shouldr   rR   rS   rK   rO   rT   rb   Ngraph_source_typegraph_doc_fetch_failedr5   )re   lenrg   rh   r   rn   ro   r   rl   rp   rq   rc   r
   rr   r:   r;   r6   r'   )r"   r   r&   rA   rs   rJ   rc   r|   rn   r~   docr?   s               r#   fetch_graph_docsz"ResearchRetriever.fetch_graph_docs   s       	I*	--//J%'':;<)l %E  	 &%-'0)&<$1( (+fh' Gq(&)9(:;!-?3H$ $	$ $D "X\00-D 1        H )488K((hmC%c3w<<88D((......... . .&-N##K 	 	 	LL1SLBBBIIIIII	s   C4C= =
D<*D71D<7D<r~   c                  K   d |D             }|sdS 	 d|i}| j         j                            t          j        |g d           d{V }t          |t                    r|n|j        }i }|                    dg           D ](}|                    d          r|d         ||d	         <   )|D ]}|                    |d
                   }	|	s!|                    d          s|	                    dd          |d<   |                    d          s|	                    d          |d<   |                    d          s|	                    d          |d<   |                    d          s|	                    d          |d<   |                    d          s|	                    d          |d<   dS # t          $ r4}
t                              dt          |
                     Y d}
~
dS d}
~
ww xY w)u]   从 meta 索引批量补充 title / doc_number 等字段（chunks 中不含这些信息）。c                r    g | ]4}|                     d           |                     d          ,|d          5S )r   title)r9   ).0ds     r#   
<listcomp>z7ResearchRetriever._enrich_from_meta.<locals>.<listcomp>  s:    WWW1hWgW1X;WWWr%   Nids)r   
doc_numberissuing_orgdoc_typepublish_date)r_   rc   rV   r~   foundrV   _idr   r    r   r   r   r   enrich_from_meta_failedr5   )r   rn   mgetr   es_meta_indexrp   rq   rc   r9   r:   r;   r<   r'   )r"   r~   r   rc   resprn   meta_mapitemr   metar?   s              r#   rr   z#ResearchRetriever._enrich_from_meta  sO     WWWWW 	F	F7#D**,ZZZ +        D
 %T400?$$diC(*H++ < <88G$$ <,0OHT%[) C C||CM22 www'' 9#'88GR#8#8CLww|,, ?(,(>(>C%ww}-- A)--)@)@C&wwz** ;&*hhz&:&:C
Oww~.. C*.((>*B*BC'C C  	F 	F 	FNN4CHHNEEEEEEEEE	Fs   FF4 4
G2>)G--G2N)r   r   r   r   r   r   r   r   r   r   r   r   )r&   r'   r   r(   )r&   r'   rA   r	   r   rB   )r   r(   r&   r   rA   r	   r   rB   )r~   rB   r   r   )	__name__
__module____qualname____doc__r$   r@   r   r   rr    r%   r#   r   r   %   s                 $   8n n n nh5 5 5 5n!F !F !F !F !F !Fr%   r   N)!r   
__future__r   typingr   
app.configr   app.core.embeddingr   app.core.graph_query_plannerr   app.core.graph_query_servicer   app.core.permissionr	   app.core.research_formatterr
   app.infrastructure.es_clientr   r   app.infrastructure.llm_clientr   app.prompts.research_promptsr   r   app.utils.loggerr   r   r;   rf   rh   rg   r   r   r%   r#   <module>r      s    # " " " " "             / / / / / / : : : : : : : : : : : : 1 1 1 1 1 1      G F F F F F F F 3 3 3 3 3 3        ( ' ' ' ' '	H		    KF KF KF KF KF KF KF KF KF KFr%   