CoolFace
Apppublic

sivan22/Ituria

sourceHugging Faceupdated 4mo agoView on Hugging Face
16likes
agent_workflow.py318 linesDownload Raw Back to root
1import os2import logging3from typing import List, Dict, Any, Optional, Tuple, Callable, Union4from dotenv import load_dotenv5from llm_providers import LLMProvider6from langchain_core.messages import HumanMessage7from tantivy_search_agent import TantivySearchAgent8 9load_dotenv()10 11class SearchAgent:12    def __init__(self, tantivy_agent: TantivySearchAgent, provider_name: str = "Gemini", api_keys: Dict[str, str] = None):13        """Initialize the search agent with Tantivy agent and LLM client"""14        self.tantivy_agent = tantivy_agent15        self.logger = logging.getLogger(__name__)16        17        # Initialize LLM provider with API keys18        self.llm_provider = LLMProvider(api_keys)19        self.llm = None20        self.set_provider(provider_name)21        22        self.min_confidence_threshold = 0.723 24    def set_provider(self, provider_name: str) -> None:25        self.llm = self.llm_provider.get_provider(provider_name)26        if not self.llm:27            raise ValueError(f"Provider {provider_name} not available")28        self.current_provider = provider_name29 30    def get_available_providers(self) -> list[str]:31        return self.llm_provider.get_available_providers()32 33    def get_query(self, query: str, failed_queries: List[Dict[str, str]] = []) -> str:34        """Generate a Tantivy query using Claude, considering previously failed queries"""35        try:36            if not self.llm:37                raise ValueError("LLM provider not initialized")38 39            prompt = (40                "Create a query for this search request with the following restrictions:\n"+41                self.tantivy_agent.get_query_instructions()+                42                "\n\nAdditional instructions: \n"43                "1. return only the search query without any other text\n"44                "2. Use only Hebrew terms for the search query\n"45                "3. the corpus to search in is an ancient Hebrew corpus - Tora and Talmud. so Try to use ancient Hebrew terms and or Talmudic expressions."46                "4. prevent modern words that are not common in talmudic texts \n"              47                f"the search request: {query}"48            )49            50            if failed_queries:51                prompt += (52                    f"\n\nPrevious failed queries:\n"+53                    "------------------------\n"+                    54                    '\n'.join(f"Query: {q['query']}, Reason: {q['reason']}" for q in failed_queries)+55                    "\n\n"56                    "Please generate an alternative query that:\n"57                    "1. Uses different Hebrew synonyms or related terms\n"58                    "2. Tries broader or more general terms\n"59                    "3. Adjusts proximity values or uses wildcards\n"60                    "4. Prevents using modern words that are not common in ancient hebrew and talmud texts\n"61                )62            63            response = self.llm.invoke([HumanMessage(content=prompt)])64            tantivy_query = response.content.strip()  65            self.logger.info(f"Generated Tantivy query: {tantivy_query}")66            return tantivy_query67            68        except Exception as e:69            self.logger.error(f"Error generating query: {e}")70            # Fallback to basic quoted search71            return f'"{query}"'72 73    def _evaluate_results(self, results: List[Dict[str, Any]], query: str) -> Dict[str, Any]:74        """Evaluate search results using Claude with confidence scoring"""75        if not self.llm:76            raise ValueError("LLM provider not initialized")77     78        # Prepare context from results79        context = "\n".join(f"Result {i}. Source: {r.get('reference',[])}\n Text: {r.get('text', [])}"80            for i, r in enumerate(results)81                )82 83        try:84            message = self.llm.invoke([HumanMessage(content=f"""Evaluate the search results for answering this question:85                    Question: {query}86                    87                    Search Results:88                    {context}89                    90                    Provide evaluation in this format (3 lines):91                    Confidence score (0.0 to 1.0) indicating how well the results can answer the question. this line should include only the number return, don't include '[line 1]'92                    ACCEPT if score >= {self.min_confidence_threshold}, REFINE if score < {self.min_confidence_threshold}. return only the word ACCEPT or REFINE.93                    Detailed explanation of what information is present or missing, don't include '[line 3]'. it should be only in Hebrew94                             """)])95            lines = message.content.strip().replace('\n\n', '\n').split('\n')96            confidence = float(lines[0])97            decision = lines[1].upper()98            explanation = lines[2]99            100            is_good = decision == 'ACCEPT'101 102            self.logger.info(f"Evaluation: Confidence={confidence}, Decision={decision}")103            self.logger.info(f"Explanation: {explanation}")104            105            return {106                "confidence": confidence,107                "is_sufficient": is_good,108                "explanation": explanation,109             110            }111            112        except Exception as e:113            self.logger.error(f"Error evaluating results: {e}")114            # Fallback to simple evaluation115            return {116                "confidence": 0.0,117                "is_sufficient": False,118                "explanation": "",         119            }120 121    def _generate_answer(self, query: str, results: List[Dict[str, Any]]) -> str:122        """Generate answer using Claude with improved context utilization"""123        if not self.llm:124            raise ValueError("LLM provider not initialized")125 126        if not results:127            return "לא נמצאו תוצאות"128 129        # Prepare context from results130        context = "\n".join(f"Result {i+1}. Source: {r.get('reference',[])}\n Text: {r.get('text', [])}"131            for i, r in enumerate(results)132                )133        134        try:135            message = self.llm.invoke([HumanMessage(content=f"""Based on these search results, answer this question:136                    Question: {query}137                    138                    Search Results:139                    {context}140                    141                    Requirements for your answer:142                    1. Use only information from the search results143                    2. Be comprehensive but concise144                    3. Structure the answer clearly145                    4. If any aspect of the question cannot be fully answered, acknowledge this146                    5. cite sources for each fact or information you use147                    6. The answer should be only in Hebrew148                    """)])149            return message.content.strip()150            151        except Exception as e:152            self.logger.error(f"Error generating answer: {e}")153            return f"I encountered an error generating the answer: {str(e)}"154 155    def search_and_answer(self, query: str, num_results: int = 10, max_iterations: int = 3, 156                         on_step: Optional[Callable[[Dict[str, Any]], None]] = None) -> Dict[str, Any]:157        """Execute multi-step search process using Tantivy with streaming updates"""158        steps = []159        all_results = []160        161        # Step 1: Generate Tantivy query162        initial_query = self.get_query(query)163        step = {164            'action': 'יצירת שאילתת חיפוש',165            'description': 'נוצרה שאילתת חיפוש עבור מנוע החיפוש',166            'results': [{'type': 'query', 'content': initial_query}]167        }168        steps.append(step)169        if on_step:170            on_step(step)171        172        # Step 2: Initial search with Tantivy query173        results = self.tantivy_agent.search(initial_query, num_results)174        175        step = {176            'action': 'חיפוש במאגר',177            'description': f'חיפוש במאגר עבור שאילתת חיפוש: {initial_query}',178            'results': [{'type': 'document', 'content': {179                'title': r['title'],180                'reference': r['reference'],181                'topics': r['topics'],182                'highlights': r['highlights'],183                'score': r['score']184            }} for r in results]185        }186        steps.append(step)187        if on_step:188            on_step(step)189 190        failed_queries = []191 192        if results.__len__() == 0:193            failed_queries.append({'query': initial_query, 'reason': 'no results'})194            is_sufficient = False195        else:         196            all_results.extend(results)197            198            # Step 3: Evaluate results199            evaluation = self._evaluate_results(results, query)200            confidence = evaluation['confidence']201            is_sufficient = evaluation['is_sufficient']202            explanation = evaluation['explanation']203            204            step = {205                'action': 'דירוג תוצאות',206                'description': 'דירוג תוצאות חיפוש',207                'results': [{208                    'type': 'evaluation',209                    'content': {210                        'status': 'accepted' if is_sufficient else 'insufficient',211                        'confidence': confidence,212                        'explanation': explanation,213                    }214                }]215            }216            steps.append(step)217            if on_step:218                on_step(step)219 220            if not is_sufficient:221                failed_queries.append({'query': initial_query, 'reason': explanation})222            223        # Step 4: Additional searches if needed224        attempt = 2225        while not is_sufficient and attempt < max_iterations:226            # Generate new query227            new_query = self.get_query(query, failed_queries)228           229            step = {230                'action': f'יצירת שאילתה מחדש (ניסיון {attempt})',231                'description': 'נוצרה שאילתת חיפוש נוספת עבור מנוע החיפוש',232                'results': [233                    {'type': 'new_query', 'content': new_query}234                ]235            }236            steps.append(step)237            if on_step:238                on_step(step)239            240            # Search with new query241            results = self.tantivy_agent.search(new_query, num_results)242            243            step = {244                'action': f'חיפוש נוסף (ניסיון {attempt}) ',245                'description': f'מחפש במאגר עבור שאילתת חיפוש: {new_query}',246                'results': [{'type': 'document', 'content': {247                    'title': r['title'],248                    'reference': r['reference'],249                    'topics': r['topics'],250                    'highlights': r['highlights'],251                    'score': r['score']252                }} for r in results]253            }254            steps.append(step)255            if on_step:256                on_step(step)257 258            if results.__len__() == 0:259                failed_queries.append({'query': new_query, 'reason': 'no results'})260            261            else:262                all_results.extend(results)263            264                # Re-evaluate with current results265                evaluation = self._evaluate_results(results, query)266                confidence = evaluation['confidence']267                is_sufficient = evaluation['is_sufficient']268                explanation = evaluation['explanation']269                270                step = {271                    'action': f'דירוג תוצאות (ניסיון {attempt})',272                    'description': 'דירוג תוצאות חיפוש לניסיון זה',273                    'explanation': explanation,274                    'results': [{275                        'type': 'evaluation',276                        'content': {277                            'status': 'accepted' if is_sufficient else 'insufficient',278                            'confidence': confidence,279                            'explanation': explanation,280                        }281                    }]282                }283                steps.append(step)284                if on_step:285                    on_step(step)286                287                if not is_sufficient:288                    failed_queries.append({'query': new_query, 'reason': explanation})289            290            attempt += 1291        292        # Step 5: Generate final answer293        answer = self._generate_answer(query, all_results)294        295        final_result = {296            'steps': steps,297            'answer': answer,298            'sources': [{299                'title': r['title'],300                'reference': r['reference'],301                'topics': r['topics'],302                'path': r['file_path'],303                'highlights': r['highlights'],304                'text': r['text'],305                'score': r['score']306            } for r in all_results]307        }308 309        # Send final result through callback310        if on_step:311            on_step({312                'action': 'סיום',313                'description': 'החיפוש הושלם',314                'final_result': final_result315            })316 317        return final_result318