o
    ri#7                     @   s  d Z ddlmZmZmZmZm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 dd	lmZ dd
lmZ ddlmZ ddlmZ eeZG dd dZG dd dZdde	dee  defddZ!				dde	dee  dee" dee" de"defddZ#dS )u<   
LangChain检索服务
实现高级检索策略和RAG应用
    )ListDictOptionalAnyTupleN)Session)Document)ChatPromptTemplate)StrOutputParser)RunnablePassthrough)
ChatOpenAI)get_vector_store)CustomEmbeddings)get_embedding_factoryc                   @   s"  e Zd ZdZddedee fddZ					dd
ededede	dee de
eeef  fddZd
edede	dee de
eeef  f
ddZd
edede
eeef  fddZd
edede	dee de	de	de
eeef  fddZ					dd
ededede	dee deeef fddZdS )RetrievalServiceu'   检索服务 - 实现多种检索策略Ndbknowledge_base_idc                 C   s$   || _ || _t||| _t | _dS )u   
        初始化检索服务

        Args:
            db: 数据库会话
            knowledge_base_id: 知识库ID
        N)r   r   r   vector_storer   embedding_factory)selfr   r    r   L/lsinfo/ai/hellotax_ai/base_platform/app/services/rag/langchain_retrieval.py__init__   s   zRetrievalService.__init__semantic   ffffff?querymodek	thresholdmodel_idreturnc           
   
   K   s   z:|dkr|  ||||W S |dkr| ||W S |dkr4|dd}|dd}| ||||||W S td|  tyO }	 z	td	|	   d
}	~	ww )uV  
        检索相关文档

        Args:
            query: 查询文本
            mode: 检索模式 ('semantic', 'keyword', 'hybrid')
            k: 返回结果数量
            threshold: 相似度阈值
            model_id: 向量模型ID
            **kwargs: 其他参数

        Returns:
            检索结果列表
        r   keywordhybridkeyword_weightg333333?semantic_weightr   zUnsupported retrieval mode: zRetrieval failed: N)_semantic_search_keyword_searchget_hybrid_search
ValueError	Exceptionloggererror)
r   r   r   r   r   r    kwargsr$   r%   er   r   r   retrieve&   s$   zRetrievalService.retrievec           
      C   s   | j j|| j|d}|std| jj|||d}g }|D ]"\}}	||d |d |d |d |d |	|d	 |d
 dd q|S )u   语义搜索textr   r    "Failed to generate query embedding)query_embeddingr   r   iddocument_idtitler2   chunk_indexcategory_id
model_namer9   r:   r5   r6   r7   r2   r8   scoremetadata)r   generate_embeddingr   r*   r   similarity_searchappend)
r   r   r   r   r    r4   resultsformatted_resultsdocr=   r   r   r   r&   P   s4   	
z!RetrievalService._semantic_searchc           
      C   s   ddl m} dg}||d}| jdur|d | j|d< |dd	| d
}| j||}g }|D ]}	||	j|	j|	j	|	j
|	jt|	j|	j|	jdd q3|S )u   关键词搜索r   )r2   z(setweight(to_tsvector('simple', COALESCE(kd.title, '')), 'A') ||  setweight(to_tsvector('simple', COALESCE(dv.chunk_text, '')), 'B')) @@ plainto_tsquery('simple', :query))r   r   Nz)dv.knowledge_base_id = :knowledge_base_idr   as  
            SELECT
                dv.id,
                dv.document_id,
                dv.chunk_index,
                dv.chunk_text,
                dv.model_name,
                kd.title,
                kd.category_id,
                ts_rank(
                    setweight(to_tsvector('simple', COALESCE(kd.title, '')), 'A') ||
                    setweight(to_tsvector('simple', COALESCE(dv.chunk_text, '')), 'B'),
                    plainto_tsquery('simple', :query)
                ) as score
            FROM document_vectors dv
            JOIN knowledge_documents kd ON dv.document_id = kd.id
            WHERE z AND z>
            ORDER BY score DESC
            LIMIT :k
        r;   r<   )
sqlalchemyr2   r   rA   joinr   executer5   r6   r7   
chunk_textr8   floatr=   r9   r:   )
r   r   r   r2   where_clausesparamskeyword_queryresultrC   rowr   r   r   r'   {   s2   





z RetrievalService._keyword_searchr$   r%   c                 C   s   | j j|| j|d}|std| jj||||||d}g }	|D ]"\}
}|	|
d |
d |
d |
d |
d ||
d	 |
d
 dd q |	S )u   混合搜索r1   r3   )r   r4   r   r   r$   r%   r5   r6   r7   r2   r8   r9   r:   r;   r<   )r   r?   r   r*   r   hybrid_searchrA   )r   r   r   r   r    r$   r%   r4   rB   rC   rD   r=   r   r   r   r)      s:   

zRetrievalService._hybrid_searchc              
   K   s   zXddl }|  }| jd
|||||d|}	|  }
|
| }dd |	D }|r0t|t| nd}|r8t|nd}|r@t|nd}|||||d||	t|	||||ddW S  tym } z	td	|   d}~ww )ug  
        召回测试 - 用于评估检索效果

        Args:
            query: 查询文本
            mode: 检索模式
            k: 返回结果数量
            threshold: 相似度阈值
            model_id: 向量模型ID
            **kwargs: 其他参数

        Returns:
            测试结果，包含检索结果和统计信息
        r   Nr   r   r   r   r    c                 S   s   g | ]}|d  qS )r=   r   ).0rr   r   r   
<listcomp>  s    z0RetrievalService.recall_test.<locals>.<listcomp>)r   r   r    )total_results	avg_score	max_score	min_scoreelapsed_time)r   r   rK   rB   
statisticszRecall test failed: r   )	timer0   sumlenmaxminr+   r,   r-   )r   r   r   r   r   r    r.   rZ   
start_timerB   end_timerX   scoresrU   rV   rW   r/   r   r   r   recall_test   sN   	zRetrievalService.recall_testN)r   r   r   N)__name__
__module____qualname____doc__r   r   intr   strrI   r   r   r   r0   r&   r'   r)   rb   r   r   r   r   r      s    
*
"+7
3
r   c                   @   s   e Zd ZdZ				ddedee dee dee def
d	d
Z					ddededede	dee dee de
eef fddZdS )
RAGServiceu   RAG服务 - 检索增强生成Ngpt-3.5-turbor   r   llm_api_keyllm_base_url	llm_modelc                 C   s<   || _ || _t||| _|rt|||dd| _dS d| _dS )u   
        初始化RAG服务

        Args:
            db: 数据库会话
            knowledge_base_id: 知识库ID
            llm_api_key: LLM API密钥
            llm_base_url: LLM API基础URL
            llm_model: LLM模型名称
        r   )api_keybase_urlmodeltemperatureN)r   r   r   retrieval_servicer   llm)r   r   r   rl   rm   rn   r   r   r   r   +  s   
zRAGService.__init__r   r   r   questionr   r   r   r    system_promptr!   c              
      s   zW| j std| jj|||||d}|sdg g dW S ddd t|D  d}td	|p0|fd
g}	 fddt d|	B | j B t	 B }
|

|}|dd |D |dW S  tyl } z	td|   d}~ww )uB  
        基于知识库回答问题

        Args:
            question: 用户问题
            mode: 检索模式
            k: 检索结果数量
            threshold: 相似度阈值
            model_id: 向量模型ID
            system_prompt: 系统提示词

        Returns:
            回答结果
        z*LLM not configured, cannot generate answerrP   zCSorry, I could not find relevant information in the knowledge base.)answersourcesretrieved_docsz

c              	   S   s2   g | ]\}}d |d  d|d  d|d  qS )z	Document    z
 (Source: r7   z):
r2   r   )rQ   irD   r   r   r   rS   x  s     z.RAGService.answer_question.<locals>.<listcomp>a  You are a professional knowledge base assistant. Please answer the user's question based on the provided document content.

Requirements:
1. Only use the provided document content to answer questions
2. If there is no relevant information in the documents, clearly inform the user
3. Answers should be accurate, concise, and professional
4. If possible, cite specific document sourcessystem)humanz9Reference documents:
{context}

User question: {question}c                    s    S rc   r   )xcontextr   r   <lambda>  s    z,RAGService.answer_question.<locals>.<lambda>)r   ru   c                 S   s6   g | ]}|d  |d |d dd d |d dqS )r6   r7   r2   N   z...r=   )r6   r7   r2   r=   r   )rQ   rD   r   r   r   rS     s    zRAG Q&A failed: N)rt   r*   rs   r0   rF   	enumerater	   from_messagesr   r
   invoker+   r,   r-   )r   ru   r   r   r   r    rv   ry   default_system_promptprompt	rag_chainrw   r/   r   r   r   answer_questionL  sV   


	zRAGService.answer_questionNNNrk   )r   r   r   NN)rd   re   rf   rg   r   r   rh   ri   r   rI   r   r   r   r   r   r   r   rj   (  sJ    
$
rj   r   r   r!   c                 C   s
   t | |S )u   获取检索服务实例)r   )r   r   r   r   r   get_retrieval_service  s   
r   rk   rl   rm   rn   c                 C   s   t | ||||S )u   获取RAG服务实例)rj   )r   r   rl   rm   rn   r   r   r   get_rag_service  s   r   rc   r   )$rg   typingr   r   r   r   r   loggingsqlalchemy.ormr   langchain_core.documentsr   LangChainDocumentlangchain_core.promptsr	   langchain_core.output_parsersr
   langchain_core.runnablesr   langchain_openair   )app.services.storage.vector_store_factoryr   app.services.storage.milvusr   3app.services.llm.backends.embedding_backend_factoryr   	getLoggerrd   r,   r   rj   rh   r   ri   r   r   r   r   r   <module>   sH    
   