anu151105 commited on
Commit
a87e08e
·
verified ·
1 Parent(s): a3738dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +819 -836
app.py CHANGED
@@ -1,837 +1,820 @@
1
- import os
2
- import gradio as gr
3
- import torch
4
- import re
5
- import time
6
- from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
7
- from huggingface_hub import hf_hub_download, snapshot_download
8
- import json
9
- from typing import Dict, List, Any, Optional, Union
10
-
11
- # Import agent modules
12
- from agent_reasoning import ReasoningEngine
13
- from agent_tasks import TaskExecutor
14
- from agent_memory import MemoryManager
15
-
16
- class ResuRankAgent:
17
- """Autonomous AI Agent similar to Manus AI
18
-
19
- This agent can:
20
- 1. Process user queries and generate responses
21
- 2. Perform reasoning through chain-of-thought
22
- 3. Execute tasks based on user instructions
23
- 4. Maintain conversation context
24
- """
25
-
26
- def __init__(self, model_id="distilgpt2", use_cache=True, test_mode=False):
27
- """Initialize the ResuRank Agent
28
-
29
- Args:
30
- model_id: Hugging Face model ID to use for the agent
31
- use_cache: Whether to use cached models from Hugging Face Hub
32
- test_mode: Whether to run in test mode with minimal resources
33
- """
34
- self.model_id = model_id
35
- self.test_mode = test_mode
36
-
37
- # Use CPU for test mode, otherwise check for CUDA
38
- if test_mode:
39
- self.device = "cpu"
40
- print("Running in test mode on CPU with minimal resources")
41
- else:
42
- self.device = "cuda" if torch.cuda.is_available() else "cpu"
43
- print(f"Using device: {self.device}")
44
-
45
- # Load model and tokenizer from Hugging Face Hub
46
- print(f"Loading model {model_id} from Hugging Face Hub...")
47
- try:
48
- # Configure model loading parameters based on mode
49
- model_kwargs = {
50
- "torch_dtype": torch.float32, # Use float32 for better compatibility
51
- }
52
-
53
- # Check if Accelerate is available for low_cpu_mem_usage and device_map
54
- try:
55
- import accelerate
56
- model_kwargs["low_cpu_mem_usage"] = True
57
- # Add device map only if not in test mode
58
- if not test_mode:
59
- model_kwargs["device_map"] = "auto"
60
- if self.device == "cuda":
61
- model_kwargs["torch_dtype"] = torch.float16
62
- except ImportError:
63
- print("Accelerate library not found, disabling low_cpu_mem_usage and device_map")
64
- # Continue without these options
65
-
66
- # Add cache directory if using cache
67
- if use_cache:
68
- model_kwargs["cache_dir"] = "./.cache"
69
- print("Using cached models if available...")
70
- self.tokenizer = AutoTokenizer.from_pretrained(model_id, cache_dir="./.cache")
71
- else:
72
- print("Downloading models from Hugging Face Hub...")
73
- self.tokenizer = AutoTokenizer.from_pretrained(model_id)
74
-
75
- # Load the model with optimized parameters
76
- self.model = AutoModelForCausalLM.from_pretrained(model_id, **model_kwargs)
77
-
78
- print(f"Successfully loaded model {model_id}")
79
- except Exception as e:
80
- print(f"Error loading model: {str(e)}")
81
- print("Falling back to smaller model...")
82
- fallback_model = "distilgpt2" # Use a smaller model as fallback
83
- self.model_id = fallback_model
84
-
85
- try:
86
- # Try loading the fallback model with minimal parameters
87
- self.tokenizer = AutoTokenizer.from_pretrained(fallback_model, cache_dir="./.cache")
88
- self.model = AutoModelForCausalLM.from_pretrained(
89
- fallback_model,
90
- torch_dtype=torch.float32,
91
- low_cpu_mem_usage=True,
92
- cache_dir="./.cache"
93
- )
94
- print(f"Successfully loaded fallback model {fallback_model}")
95
- except Exception as fallback_error:
96
- print(f"Error loading fallback model: {str(fallback_error)}")
97
- raise RuntimeError("Failed to load both primary and fallback models")
98
-
99
- # Initialize agent components
100
- self.reasoning_engine = ReasoningEngine(self.model, self.tokenizer, self.device)
101
- self.memory_manager = MemoryManager(max_history_length=20)
102
- self.task_executor = TaskExecutor(self.reasoning_engine)
103
-
104
- def process_query(self, query: str, use_reasoning: bool = True) -> Dict[str, Any]:
105
- """Process a user query and generate a response
106
-
107
- Args:
108
- query: User query text
109
- use_reasoning: Whether to use chain-of-thought reasoning
110
-
111
- Returns:
112
- Dictionary containing response and metadata
113
- """
114
- # Add query to conversation history
115
- self.memory_manager.add_message("user", query)
116
-
117
- start_time = time.time()
118
-
119
- # Check if this is a task execution request
120
- is_task_request = self._is_task_request(query)
121
-
122
- # Process the query with appropriate method
123
- if is_task_request:
124
- # Handle as a task execution request
125
- task_result = self.execute_task(query)
126
- response = f"I've executed your task. {task_result.get('result', '')}\n\nStatus: {task_result.get('status', 'unknown')}"
127
- reasoning = task_result.get('plan', '')
128
- elif use_reasoning:
129
- # Use chain-of-thought reasoning
130
- # Enhance with context from memory
131
- facts = self.memory_manager.format_facts_for_prompt()
132
- context = self.memory_manager.format_conversation_for_prompt(max_turns=5)
133
-
134
- # Create an enhanced query with context
135
- enhanced_query = f"{facts}\n\nRecent conversation:\n{context}\n\nCurrent query: {query}"
136
-
137
- result = self.reasoning_engine.chain_of_thought(enhanced_query)
138
- response = result["answer"]
139
- reasoning = result["reasoning"]
140
- else:
141
- # Simple response generation without reasoning
142
- conversation_prompt = self.memory_manager.format_conversation_for_prompt(max_turns=10)
143
- facts_prompt = self.memory_manager.format_facts_for_prompt()
144
-
145
- prompt = f"{facts_prompt}\n\n{conversation_prompt}\nassistant: "
146
-
147
- response = self.reasoning_engine.generate_text(prompt)
148
- reasoning = None
149
-
150
- # Add response to conversation history
151
- self.memory_manager.add_message("assistant", response)
152
-
153
- # Extract any important facts from the conversation
154
- self._extract_facts(query, response)
155
-
156
- processing_time = time.time() - start_time
157
-
158
- return {
159
- "response": response,
160
- "reasoning": reasoning,
161
- "processing_time": processing_time,
162
- "timestamp": time.time()
163
- }
164
-
165
- def _is_task_request(self, query: str) -> bool:
166
- """Determine if a query is a task execution request
167
-
168
- Args:
169
- query: The user query
170
-
171
- Returns:
172
- True if the query appears to be a task request, False otherwise
173
- """
174
- # Keywords that suggest a task execution request
175
- task_keywords = [
176
- "execute", "perform", "run", "do", "complete", "finish",
177
- "task", "job", "work", "action", "operation", "function",
178
- "can you", "please", "help me", "i need", "i want"
179
- ]
180
-
181
- # Check if query contains task-related keywords
182
- query_lower = query.lower()
183
- for keyword in task_keywords:
184
- if keyword in query_lower:
185
- return True
186
-
187
- return False
188
-
189
- def _extract_facts(self, query: str, response: str) -> None:
190
- """Extract important facts from the conversation
191
-
192
- Args:
193
- query: User query
194
- response: Agent response
195
- """
196
- # Extract personal information
197
- self._extract_personal_info(query)
198
-
199
- # Extract preferences
200
- self._extract_preferences(query)
201
-
202
- # Extract task-related information
203
- self._extract_task_info(query)
204
-
205
- # Use the reasoning engine to identify important facts
206
- self._extract_with_reasoning(query, response)
207
-
208
- def _extract_personal_info(self, text: str) -> None:
209
- """Extract personal information from text
210
-
211
- Args:
212
- text: Text to extract information from
213
- """
214
- text_lower = text.lower()
215
-
216
- # Extract name
217
- if "my name is" in text_lower or "i am called" in text_lower or "i'm called" in text_lower:
218
- name_patterns = [
219
- r"my name is ([\w\s]+)[.\,]?",
220
- r"i am called ([\w\s]+)[.\,]?",
221
- r"i'm called ([\w\s]+)[.\,]?"
222
- ]
223
-
224
- for pattern in name_patterns:
225
- name_match = re.search(pattern, text_lower)
226
- if name_match:
227
- name = name_match.group(1).strip()
228
- self.memory_manager.add_important_fact(f"User's name is {name}", "user")
229
- break
230
-
231
- # Extract location
232
- if "i am from" in text_lower or "i'm from" in text_lower or "i live in" in text_lower:
233
- location_patterns = [
234
- r"i am from ([\w\s]+)[.\,]?",
235
- r"i'm from ([\w\s]+)[.\,]?",
236
- r"i live in ([\w\s]+)[.\,]?"
237
- ]
238
-
239
- for pattern in location_patterns:
240
- location_match = re.search(pattern, text_lower)
241
- if location_match:
242
- location = location_match.group(1).strip()
243
- self.memory_manager.add_important_fact(f"User is from {location}", "user")
244
- break
245
-
246
- # Extract profession/occupation
247
- if "i work as" in text_lower or "i am a" in text_lower or "i'm a" in text_lower:
248
- profession_patterns = [
249
- r"i work as a[n]? ([\w\s]+)[.\,]?",
250
- r"i am a[n]? ([\w\s]+)[.\,]?",
251
- r"i'm a[n]? ([\w\s]+)[.\,]?"
252
- ]
253
-
254
- for pattern in profession_patterns:
255
- profession_match = re.search(pattern, text_lower)
256
- if profession_match:
257
- profession = profession_match.group(1).strip()
258
- self.memory_manager.add_important_fact(f"User works as a {profession}", "user")
259
- break
260
-
261
- def _extract_preferences(self, text: str) -> None:
262
- """Extract user preferences from text
263
-
264
- Args:
265
- text: Text to extract information from
266
- """
267
- text_lower = text.lower()
268
-
269
- # Extract likes
270
- if "i like" in text_lower or "i love" in text_lower or "i enjoy" in text_lower:
271
- like_patterns = [
272
- r"i like ([\w\s]+)[.\,]?",
273
- r"i love ([\w\s]+)[.\,]?",
274
- r"i enjoy ([\w\s]+)[.\,]?"
275
- ]
276
-
277
- for pattern in like_patterns:
278
- like_match = re.search(pattern, text_lower)
279
- if like_match:
280
- like = like_match.group(1).strip()
281
- self.memory_manager.add_important_fact(f"User likes {like}", "user")
282
- break
283
-
284
- # Extract dislikes
285
- if "i don't like" in text_lower or "i hate" in text_lower or "i dislike" in text_lower:
286
- dislike_patterns = [
287
- r"i don't like ([\w\s]+)[.\,]?",
288
- r"i hate ([\w\s]+)[.\,]?",
289
- r"i dislike ([\w\s]+)[.\,]?"
290
- ]
291
-
292
- for pattern in dislike_patterns:
293
- dislike_match = re.search(pattern, text_lower)
294
- if dislike_match:
295
- dislike = dislike_match.group(1).strip()
296
- self.memory_manager.add_important_fact(f"User dislikes {dislike}", "user")
297
- break
298
-
299
- def _extract_task_info(self, text: str) -> None:
300
- """Extract task-related information from text
301
-
302
- Args:
303
- text: Text to extract information from
304
- """
305
- text_lower = text.lower()
306
-
307
- # Extract goals
308
- if "my goal is" in text_lower or "i want to" in text_lower or "i need to" in text_lower:
309
- goal_patterns = [
310
- r"my goal is to ([\w\s]+)[.\,]?",
311
- r"i want to ([\w\s]+)[.\,]?",
312
- r"i need to ([\w\s]+)[.\,]?"
313
- ]
314
-
315
- for pattern in goal_patterns:
316
- goal_match = re.search(pattern, text_lower)
317
- if goal_match:
318
- goal = goal_match.group(1).strip()
319
- self.memory_manager.add_important_fact(f"User's goal is to {goal}", "user")
320
- break
321
-
322
- def run_test_case(self) -> Dict[str, Any]:
323
- """Run a test case to demonstrate the agent's capabilities with minimal resources
324
-
325
- This method is useful for testing the agent on resource-constrained environments
326
- like Hugging Face Spaces or during development.
327
-
328
- Returns:
329
- Dictionary containing test results and performance metrics
330
- """
331
- print("Running test case with minimal resources...")
332
- start_time = time.time()
333
-
334
- # Simple test query that doesn't require extensive reasoning
335
- test_query = "What can you help me with?"
336
-
337
- # Process the query with minimal settings
338
- test_response = self.process_query(test_query, use_reasoning=False)
339
-
340
- # Calculate performance metrics
341
- processing_time = time.time() - start_time
342
- memory_usage = self._estimate_memory_usage()
343
-
344
- # Return test results
345
- return {
346
- "status": "success",
347
- "model_id": self.model_id,
348
- "device": self.device,
349
- "test_query": test_query,
350
- "test_response": test_response["response"],
351
- "processing_time": processing_time,
352
- "memory_usage_mb": memory_usage,
353
- "timestamp": time.time()
354
- }
355
-
356
- def _estimate_memory_usage(self) -> float:
357
- """Estimate the memory usage of the model
358
-
359
- Returns:
360
- Estimated memory usage in MB
361
- """
362
- try:
363
- import psutil
364
- process = psutil.Process(os.getpid())
365
- memory_info = process.memory_info()
366
- return memory_info.rss / (1024 * 1024) # Convert to MB
367
- except ImportError:
368
- return 0.0 # Return 0 if psutil is not available
369
-
370
- def _extract_with_reasoning(self, query: str, response: str) -> None:
371
- """Use the reasoning engine to extract important facts
372
-
373
- Args:
374
- query: User query
375
- response: Agent response
376
- """
377
- # Only use this for longer queries to avoid unnecessary processing
378
- if len(query) < 50:
379
- return
380
-
381
- extraction_prompt = f"""Extract important facts from this conversation:
382
-
383
- User: {query}
384
- Assistant: {response}
385
-
386
- List of important facts (one per line):
387
- 1. """
388
-
389
- try:
390
- facts_text = self.reasoning_engine.generate_text(extraction_prompt, max_length=256)
391
-
392
- # Parse the facts
393
- for line in facts_text.split('\n'):
394
- line = line.strip()
395
- if line and (line[0].isdigit() or line.startswith('- ')):
396
- # Remove numbering or bullet points
397
- fact = re.sub(r'^\d+\.\s*|^-\s*', '', line).strip()
398
- if fact and len(fact) > 10: # Only add substantial facts
399
- self.memory_manager.add_important_fact(fact, "inference")
400
- except Exception as e:
401
- print(f"Error extracting facts with reasoning: {str(e)}")
402
- # Continue without adding facts
403
-
404
-
405
-
406
- def execute_task(self, task_description: str) -> Dict[str, Any]:
407
- """Execute a task based on the description
408
-
409
- Args:
410
- task_description: Description of the task to execute
411
-
412
- Returns:
413
- Dictionary containing task results and status
414
- """
415
- return self.task_executor.execute_task(task_description)
416
-
417
- def get_status(self) -> Dict[str, Any]:
418
- """Get the current status of the agent
419
-
420
- Returns:
421
- Dictionary containing agent status information
422
- """
423
- memory_stats = self.memory_manager.get_memory_stats()
424
- task_status = self.task_executor.get_task_status()
425
-
426
- return {
427
- "model_id": self.model_id,
428
- "device": self.device,
429
- "conversation_turns": memory_stats["conversation_turns"],
430
- "important_facts": memory_stats["important_facts"],
431
- "current_task": task_status["current_task"],
432
- "task_status": task_status["status"]
433
- }
434
-
435
- def clear_conversation(self) -> None:
436
- """Clear the conversation history"""
437
- self.memory_manager.clear_conversation_history()
438
-
439
- def process_document(self, document_text: str, document_type: str = "resume") -> Dict[str, Any]:
440
- """Process a document (like a resume) and extract information
441
-
442
- Args:
443
- document_text: The text content of the document
444
- document_type: The type of document (e.g., "resume", "job_description")
445
-
446
- Returns:
447
- Dictionary containing extracted information and analysis
448
- """
449
- self.memory_manager.store_session_data(f"last_{document_type}", document_text)
450
- start_time = time.time()
451
-
452
- # Create a prompt for document analysis
453
- analysis_prompt = f"""I need to analyze this {document_type} document and extract key information:
454
-
455
- {document_text}
456
-
457
- Detailed analysis:"""
458
-
459
- # Generate analysis using reasoning engine
460
- analysis = self.reasoning_engine.generate_text(analysis_prompt, max_length=1024)
461
-
462
- # Extract structured information based on document type
463
- if document_type.lower() == "resume":
464
- extraction_prompt = f"""Based on this resume:
465
- {document_text}
466
-
467
- Extract the following information in a structured format:
468
- 1. Name:
469
- 2. Contact Information:
470
- 3. Education:
471
- 4. Work Experience:
472
- 5. Skills:
473
- 6. Projects:
474
- 7. Certifications:
475
- 8. Languages:
476
- 9. Key Strengths:
477
- """
478
- elif document_type.lower() == "job_description":
479
- extraction_prompt = f"""Based on this job description:
480
- {document_text}
481
-
482
- Extract the following information in a structured format:
483
- 1. Job Title:
484
- 2. Company:
485
- 3. Location:
486
- 4. Required Skills:
487
- 5. Required Experience:
488
- 6. Education Requirements:
489
- 7. Responsibilities:
490
- 8. Benefits:
491
- 9. Key Qualifications:
492
- """
493
- else:
494
- extraction_prompt = f"""Extract key information from this document:
495
- {document_text}
496
-
497
- Key information:
498
- 1. """
499
-
500
- # Generate structured extraction
501
- structured_info = self.reasoning_engine.generate_text(extraction_prompt, max_length=1024)
502
-
503
- # Add important facts to memory
504
- self._extract_document_facts(document_text, document_type, structured_info)
505
-
506
- processing_time = time.time() - start_time
507
-
508
- return {
509
- "document_type": document_type,
510
- "analysis": analysis,
511
- "structured_info": structured_info,
512
- "processing_time": processing_time,
513
- "timestamp": time.time()
514
- }
515
-
516
- def _extract_document_facts(self, document_text: str, document_type: str, structured_info: str) -> None:
517
- """Extract important facts from a document and add them to memory
518
-
519
- Args:
520
- document_text: The text content of the document
521
- document_type: The type of document
522
- structured_info: Structured information extracted from the document
523
- """
524
- # Extract key facts based on document type
525
- if document_type.lower() == "resume":
526
- # Extract name if present
527
- name_match = re.search(r"Name:\s*([\w\s]+)\n", structured_info)
528
- if name_match:
529
- name = name_match.group(1).strip()
530
- self.memory_manager.add_important_fact(f"Document contains resume for {name}", "document")
531
-
532
- # Extract skills
533
- skills_match = re.search(r"Skills:\s*([\w\s,\.\-\+]+)\n", structured_info)
534
- if skills_match:
535
- skills = skills_match.group(1).strip()
536
- self.memory_manager.add_important_fact(f"Resume shows skills in: {skills}", "document")
537
-
538
- # Extract education
539
- education_match = re.search(r"Education:\s*([\w\s,\.\-\+]+)\n", structured_info)
540
- if education_match:
541
- education = education_match.group(1).strip()
542
- self.memory_manager.add_important_fact(f"Resume shows education: {education}", "document")
543
-
544
- elif document_type.lower() == "job_description":
545
- # Extract job title
546
- title_match = re.search(r"Job Title:\s*([\w\s]+)\n", structured_info)
547
- if title_match:
548
- title = title_match.group(1).strip()
549
- self.memory_manager.add_important_fact(f"Document contains job description for {title}", "document")
550
-
551
- # Extract required skills
552
- skills_match = re.search(r"Required Skills:\s*([\w\s,\.\-\+]+)\n", structured_info)
553
- if skills_match:
554
- skills = skills_match.group(1).strip()
555
- self.memory_manager.add_important_fact(f"Job requires skills in: {skills}", "document")
556
-
557
- # Add general document fact
558
- self.memory_manager.add_important_fact(f"Processed a {document_type} document", "system")
559
-
560
- def rank_resumes(self, job_description: str, resumes: List[str]) -> Dict[str, Any]:
561
- """Rank multiple resumes against a job description
562
-
563
- Args:
564
- job_description: The job description text
565
- resumes: List of resume texts to rank
566
-
567
- Returns:
568
- Dictionary containing rankings and analysis
569
- """
570
- start_time = time.time()
571
-
572
- # Process the job description first
573
- job_result = self.process_document(job_description, "job_description")
574
- job_analysis = job_result["structured_info"]
575
-
576
- # Process each resume
577
- resume_results = []
578
- for i, resume in enumerate(resumes):
579
- result = self.process_document(resume, "resume")
580
- resume_results.append({
581
- "index": i,
582
- "text": resume,
583
- "analysis": result["structured_info"]
584
- })
585
-
586
- # Create a ranking prompt
587
- ranking_prompt = f"""I need to rank these resumes based on how well they match the job description.
588
-
589
- Job Description Analysis:
590
- {job_analysis}
591
-
592
- Resumes:
593
- """
594
-
595
- for i, result in enumerate(resume_results):
596
- ranking_prompt += f"\nResume {i+1}:\n{result['analysis']}\n"
597
-
598
- ranking_prompt += "\nRank these resumes from best to worst match for the job, with detailed reasoning for each:"
599
-
600
- # Generate the ranking analysis
601
- ranking_analysis = self.reasoning_engine.generate_text(ranking_prompt, max_length=2048)
602
-
603
- # Generate a numerical scoring for each resume
604
- scoring_prompt = f"""Based on my analysis of how well these resumes match the job description:
605
- {ranking_analysis}
606
-
607
- Assign a numerical score from 0-100 for each resume, where 100 is a perfect match:
608
-
609
- Resume 1 Score:"""
610
-
611
- scores_text = self.reasoning_engine.generate_text(scoring_prompt, max_length=512)
612
-
613
- # Parse scores (simple regex approach)
614
- scores = []
615
- for i in range(len(resume_results)):
616
- score_match = re.search(fr"Resume {i+1} Score:\s*(\d+)", scores_text)
617
- if score_match:
618
- scores.append(int(score_match.group(1)))
619
- else:
620
- # Default score if parsing fails
621
- scores.append(50)
622
-
623
- # Create the final rankings
624
- rankings = []
625
- for i, score in enumerate(scores):
626
- rankings.append({
627
- "resume_index": i,
628
- "score": score,
629
- "resume_text": resumes[i][:100] + "..." # Truncated for readability
630
- })
631
-
632
- # Sort by score (descending)
633
- rankings.sort(key=lambda x: x["score"], reverse=True)
634
-
635
- processing_time = time.time() - start_time
636
-
637
- return {
638
- "rankings": rankings,
639
- "analysis": ranking_analysis,
640
- "job_description": job_description,
641
- "processing_time": processing_time
642
- }
643
-
644
- # Create the Gradio interface
645
- def create_interface(test_mode=False):
646
- """Create the Gradio interface for the ResuRank AI Agent
647
-
648
- Args:
649
- test_mode: Whether to run in test mode with minimal resources
650
- """
651
- # Initialize the agent with appropriate settings
652
- if test_mode:
653
- agent = ResuRankAgent(model_id="distilgpt2", use_cache=True, test_mode=True)
654
- # Run a test case to verify functionality
655
- test_results = agent.run_test_case()
656
- print(f"Test results: {test_results}")
657
- else:
658
- agent = ResuRankAgent(model_id="google/flan-t5-base", use_cache=True)
659
-
660
- with gr.Blocks(title="ResuRank AI Agent") as interface:
661
- gr.Markdown("# ResuRank AI Agent")
662
- gr.Markdown("An autonomous AI agent that can process queries, perform reasoning, and execute tasks.")
663
-
664
- with gr.Tab("Chat"):
665
- chatbot = gr.Chatbot(height=400)
666
- msg = gr.Textbox(label="Your message", placeholder="Ask me anything...")
667
- with gr.Row():
668
- submit_btn = gr.Button("Submit")
669
- clear_btn = gr.Button("Clear")
670
-
671
- reasoning_checkbox = gr.Checkbox(label="Use reasoning", value=True)
672
-
673
- if reasoning_checkbox.value:
674
- reasoning_output = gr.Textbox(label="Reasoning", interactive=False)
675
- else:
676
- reasoning_output = gr.Textbox(label="Reasoning", interactive=False, visible=False)
677
-
678
- def respond(message, chat_history, use_reasoning):
679
- if not message.strip():
680
- return chat_history, "", ""
681
-
682
- # Process the query
683
- result = agent.process_query(message, use_reasoning=use_reasoning)
684
-
685
- # Update chat history
686
- chat_history.append((message, result["response"]))
687
-
688
- return chat_history, "", result.get("reasoning", "")
689
-
690
- def clear_chat():
691
- agent.clear_conversation()
692
- return [], "", ""
693
-
694
- # Set up event handlers
695
- submit_btn.click(respond, [msg, chatbot, reasoning_checkbox], [chatbot, msg, reasoning_output])
696
- msg.submit(respond, [msg, chatbot, reasoning_checkbox], [chatbot, msg, reasoning_output])
697
- clear_btn.click(clear_chat, None, [chatbot, msg, reasoning_output])
698
- reasoning_checkbox.change(lambda x: gr.update(visible=x), reasoning_checkbox, reasoning_output)
699
-
700
- with gr.Tab("Task Execution"):
701
- task_input = gr.Textbox(label="Task Description", placeholder="Describe the task to execute...")
702
- execute_btn = gr.Button("Execute Task")
703
-
704
- with gr.Row():
705
- with gr.Column():
706
- plan_output = gr.Textbox(label="Execution Plan", interactive=False)
707
- with gr.Column():
708
- results_output = gr.Textbox(label="Task Results", interactive=False)
709
-
710
- task_status = gr.Textbox(label="Task Status", value="idle", interactive=False)
711
-
712
- def execute_task(task_description):
713
- if not task_description.strip():
714
- return "No task provided.", "", "idle"
715
-
716
- # Execute the task
717
- result = agent.execute_task(task_description)
718
-
719
- return result.get("plan", ""), result.get("result", ""), result.get("status", "")
720
-
721
- # Set up event handlers
722
- execute_btn.click(execute_task, task_input, [plan_output, results_output, task_status])
723
-
724
- with gr.Tab("Agent Status"):
725
- status_btn = gr.Button("Refresh Status")
726
-
727
- with gr.Row():
728
- with gr.Column():
729
- model_info = gr.Textbox(label="Model Information", interactive=False)
730
- with gr.Column():
731
- conversation_info = gr.Textbox(label="Conversation Information", interactive=False)
732
-
733
- def update_status():
734
- status = agent.get_status()
735
- model_text = f"Model ID: {status['model_id']}\nDevice: {status['device']}"
736
-
737
- # Handle important_facts which might be an integer count or a list
738
- important_facts_count = status['important_facts']
739
- if isinstance(important_facts_count, list):
740
- important_facts_count = len(important_facts_count)
741
-
742
- conversation_text = f"Conversation Length: {status['conversation_turns']} turns\nImportant Facts: {important_facts_count}\nCurrent Task: {status['current_task'] or 'None'}\nTask Status: {status['task_status']}"
743
-
744
- return model_text, conversation_text
745
-
746
- # Set up event handlers
747
- status_btn.click(update_status, None, [model_info, conversation_info])
748
-
749
- # Initialize status on load
750
- model_info.value, conversation_info.value = update_status()
751
-
752
- with gr.Tab("Document Processing"):
753
- with gr.Row():
754
- with gr.Column():
755
- document_input = gr.Textbox(label="Document Text", placeholder="Paste resume or job description text here...", lines=10)
756
- document_type = gr.Radio(["resume", "job_description", "other"], label="Document Type", value="resume")
757
- process_btn = gr.Button("Process Document")
758
-
759
- with gr.Row():
760
- with gr.Column():
761
- analysis_output = gr.Textbox(label="Document Analysis", interactive=False, lines=10)
762
- with gr.Column():
763
- structured_output = gr.Textbox(label="Structured Information", interactive=False, lines=10)
764
-
765
- def process_document(document_text, doc_type):
766
- if not document_text.strip():
767
- return "No document provided.", ""
768
-
769
- # Process the document
770
- result = agent.process_document(document_text, doc_type)
771
-
772
- return result.get("analysis", ""), result.get("structured_info", "")
773
-
774
- # Set up event handlers
775
- process_btn.click(process_document, [document_input, document_type], [analysis_output, structured_output])
776
-
777
- with gr.Tab("Resume Ranking"):
778
- with gr.Row():
779
- with gr.Column():
780
- job_description_input = gr.Textbox(label="Job Description", placeholder="Paste job description here...", lines=8)
781
-
782
- with gr.Row():
783
- with gr.Column():
784
- resume1_input = gr.Textbox(label="Resume 1", placeholder="Paste first resume here...", lines=6)
785
- with gr.Column():
786
- resume2_input = gr.Textbox(label="Resume 2", placeholder="Paste second resume here...", lines=6)
787
-
788
- with gr.Row():
789
- with gr.Column():
790
- resume3_input = gr.Textbox(label="Resume 3 (Optional)", placeholder="Paste third resume here...", lines=6)
791
- with gr.Column():
792
- resume4_input = gr.Textbox(label="Resume 4 (Optional)", placeholder="Paste fourth resume here...", lines=6)
793
-
794
- rank_btn = gr.Button("Rank Resumes")
795
-
796
- ranking_output = gr.Textbox(label="Ranking Results", interactive=False, lines=15)
797
-
798
- def rank_resumes(job_desc, resume1, resume2, resume3, resume4):
799
- if not job_desc.strip() or not resume1.strip() or not resume2.strip():
800
- return "Please provide at least a job description and two resumes."
801
-
802
- # Collect all non-empty resumes
803
- resumes = [r for r in [resume1, resume2, resume3, resume4] if r.strip()]
804
-
805
- # Rank the resumes
806
- result = agent.rank_resumes(job_desc, resumes)
807
-
808
- # Format the results
809
- output = "Resume Rankings (Best to Worst Match):\n\n"
810
-
811
- for i, rank in enumerate(result["rankings"]):
812
- resume_num = rank["resume_index"] + 1
813
- score = rank["score"]
814
- output += f"{i+1}. Resume {resume_num} - Score: {score}/100\n"
815
-
816
- output += "\nDetailed Analysis:\n" + result["analysis"]
817
-
818
- return output
819
-
820
- # Set up event handlers
821
- rank_btn.click(rank_resumes, [job_description_input, resume1_input, resume2_input, resume3_input, resume4_input], ranking_output)
822
-
823
- return interface
824
-
825
- # Launch the interface when run directly
826
- if __name__ == "__main__":
827
- import argparse
828
-
829
- # Parse command line arguments
830
- parser = argparse.ArgumentParser(description="ResuRank AI Agent")
831
- parser.add_argument("--test", action="store_true", help="Run in test mode with minimal resources")
832
- parser.add_argument("--share", action="store_true", help="Share the Gradio interface")
833
- args = parser.parse_args()
834
-
835
- # Create and launch the interface
836
- interface = create_interface(test_mode=args.test)
837
  interface.launch(share=args.share)
 
1
+ import os
2
+ import torch
3
+ from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
4
+ from typing import Dict, List, Any, Optional, Union
5
+
6
+ # Import your custom agent modules
7
+ from agent_reasoning import ReasoningEngine
8
+ from agent_tasks import TaskExecutor
9
+ from agent_memory import MemoryManager
10
+
11
+
12
+ class ResuRankAgent:
13
+ """Autonomous AI Agent similar to Manus AI"""
14
+
15
+ def __init__(self, model_id="google/flan-t5-base", use_cache=True, test_mode=False):
16
+ """
17
+ Initialize the ResuRank Agent
18
+
19
+ Args:
20
+ model_id: Hugging Face model ID to use for the agent
21
+ use_cache: Whether to use cached models from Hugging Face Hub
22
+ test_mode: Whether to run in test mode with minimal resources
23
+ """
24
+ self.model_id = model_id
25
+ self.test_mode = test_mode
26
+
27
+ # Determine device
28
+ if test_mode:
29
+ self.device = "cpu"
30
+ print("Running in test mode on CPU with minimal resources.")
31
+ else:
32
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
33
+ print(f"Using device: {self.device}")
34
+
35
+ # Load model and tokenizer
36
+ try:
37
+ print(f"Attempting to load model: {model_id}")
38
+
39
+ tokenizer_kwargs = {
40
+ "cache_dir": "./.cache" if use_cache else None,
41
+ "local_files_only": False,
42
+ "force_download": not use_cache
43
+ }
44
+
45
+ model_kwargs = {
46
+ "cache_dir": "./.cache" if use_cache else None,
47
+ "local_files_only": False,
48
+ "force_download": not use_cache,
49
+ "torch_dtype": torch.float16 if self.device == "cuda" else torch.float32
50
+ }
51
+
52
+ # Optional: use accelerate device_map if available
53
+ try:
54
+ import accelerate
55
+ model_kwargs["low_cpu_mem_usage"] = True
56
+ if not test_mode:
57
+ model_kwargs["device_map"] = "auto"
58
+ except ImportError:
59
+ print("Accelerate not available. Proceeding without device_map.")
60
+
61
+ # Load tokenizer and model
62
+ self.tokenizer = AutoTokenizer.from_pretrained(model_id, **tokenizer_kwargs)
63
+ self.model = AutoModelForSeq2SeqLM.from_pretrained(model_id, **model_kwargs)
64
+
65
+ print(f"Successfully loaded {model_id}.")
66
+
67
+ except Exception as e:
68
+ print(f"[ERROR] Failed to load model '{model_id}': {e}")
69
+ print("Falling back to smaller model: distilgpt2")
70
+
71
+ fallback_model = "distilgpt2"
72
+
73
+ try:
74
+ self.tokenizer = AutoTokenizer.from_pretrained(fallback_model, cache_dir="./.cache")
75
+ from transformers import AutoModelForCausalLM
76
+ self.model = AutoModelForCausalLM.from_pretrained(fallback_model, cache_dir="./.cache")
77
+ print(f"Fallback model '{fallback_model}' loaded successfully.")
78
+ except Exception as fallback_error:
79
+ print(f"[FATAL] Failed to load fallback model: {fallback_error}")
80
+ raise RuntimeError("Could not load any model, exiting...")
81
+
82
+ # Initialize agent components
83
+ self.reasoning_engine = ReasoningEngine(self.model, self.tokenizer, self.device)
84
+ self.memory_manager = MemoryManager(max_history_length=20)
85
+ self.task_executor = TaskExecutor(self.reasoning_engine)
86
+
87
+ def process_query(self, query: str, use_reasoning: bool = True) -> Dict[str, Any]:
88
+ """Process a user query and generate a response
89
+
90
+ Args:
91
+ query: User query text
92
+ use_reasoning: Whether to use chain-of-thought reasoning
93
+
94
+ Returns:
95
+ Dictionary containing response and metadata
96
+ """
97
+ # Add query to conversation history
98
+ self.memory_manager.add_message("user", query)
99
+
100
+ start_time = time.time()
101
+
102
+ # Check if this is a task execution request
103
+ is_task_request = self._is_task_request(query)
104
+
105
+ # Process the query with appropriate method
106
+ if is_task_request:
107
+ # Handle as a task execution request
108
+ task_result = self.execute_task(query)
109
+ response = f"I've executed your task. {task_result.get('result', '')}\n\nStatus: {task_result.get('status', 'unknown')}"
110
+ reasoning = task_result.get('plan', '')
111
+ elif use_reasoning:
112
+ # Use chain-of-thought reasoning
113
+ # Enhance with context from memory
114
+ facts = self.memory_manager.format_facts_for_prompt()
115
+ context = self.memory_manager.format_conversation_for_prompt(max_turns=5)
116
+
117
+ # Create an enhanced query with context
118
+ enhanced_query = f"{facts}\n\nRecent conversation:\n{context}\n\nCurrent query: {query}"
119
+
120
+ result = self.reasoning_engine.chain_of_thought(enhanced_query)
121
+ response = result["answer"]
122
+ reasoning = result["reasoning"]
123
+ else:
124
+ # Simple response generation without reasoning
125
+ conversation_prompt = self.memory_manager.format_conversation_for_prompt(max_turns=10)
126
+ facts_prompt = self.memory_manager.format_facts_for_prompt()
127
+
128
+ prompt = f"{facts_prompt}\n\n{conversation_prompt}\nassistant: "
129
+
130
+ response = self.reasoning_engine.generate_text(prompt)
131
+ reasoning = None
132
+
133
+ # Add response to conversation history
134
+ self.memory_manager.add_message("assistant", response)
135
+
136
+ # Extract any important facts from the conversation
137
+ self._extract_facts(query, response)
138
+
139
+ processing_time = time.time() - start_time
140
+
141
+ return {
142
+ "response": response,
143
+ "reasoning": reasoning,
144
+ "processing_time": processing_time,
145
+ "timestamp": time.time()
146
+ }
147
+
148
+ def _is_task_request(self, query: str) -> bool:
149
+ """Determine if a query is a task execution request
150
+
151
+ Args:
152
+ query: The user query
153
+
154
+ Returns:
155
+ True if the query appears to be a task request, False otherwise
156
+ """
157
+ # Keywords that suggest a task execution request
158
+ task_keywords = [
159
+ "execute", "perform", "run", "do", "complete", "finish",
160
+ "task", "job", "work", "action", "operation", "function",
161
+ "can you", "please", "help me", "i need", "i want"
162
+ ]
163
+
164
+ # Check if query contains task-related keywords
165
+ query_lower = query.lower()
166
+ for keyword in task_keywords:
167
+ if keyword in query_lower:
168
+ return True
169
+
170
+ return False
171
+
172
+ def _extract_facts(self, query: str, response: str) -> None:
173
+ """Extract important facts from the conversation
174
+
175
+ Args:
176
+ query: User query
177
+ response: Agent response
178
+ """
179
+ # Extract personal information
180
+ self._extract_personal_info(query)
181
+
182
+ # Extract preferences
183
+ self._extract_preferences(query)
184
+
185
+ # Extract task-related information
186
+ self._extract_task_info(query)
187
+
188
+ # Use the reasoning engine to identify important facts
189
+ self._extract_with_reasoning(query, response)
190
+
191
+ def _extract_personal_info(self, text: str) -> None:
192
+ """Extract personal information from text
193
+
194
+ Args:
195
+ text: Text to extract information from
196
+ """
197
+ text_lower = text.lower()
198
+
199
+ # Extract name
200
+ if "my name is" in text_lower or "i am called" in text_lower or "i'm called" in text_lower:
201
+ name_patterns = [
202
+ r"my name is ([\w\s]+)[.\,]?",
203
+ r"i am called ([\w\s]+)[.\,]?",
204
+ r"i'm called ([\w\s]+)[.\,]?"
205
+ ]
206
+
207
+ for pattern in name_patterns:
208
+ name_match = re.search(pattern, text_lower)
209
+ if name_match:
210
+ name = name_match.group(1).strip()
211
+ self.memory_manager.add_important_fact(f"User's name is {name}", "user")
212
+ break
213
+
214
+ # Extract location
215
+ if "i am from" in text_lower or "i'm from" in text_lower or "i live in" in text_lower:
216
+ location_patterns = [
217
+ r"i am from ([\w\s]+)[.\,]?",
218
+ r"i'm from ([\w\s]+)[.\,]?",
219
+ r"i live in ([\w\s]+)[.\,]?"
220
+ ]
221
+
222
+ for pattern in location_patterns:
223
+ location_match = re.search(pattern, text_lower)
224
+ if location_match:
225
+ location = location_match.group(1).strip()
226
+ self.memory_manager.add_important_fact(f"User is from {location}", "user")
227
+ break
228
+
229
+ # Extract profession/occupation
230
+ if "i work as" in text_lower or "i am a" in text_lower or "i'm a" in text_lower:
231
+ profession_patterns = [
232
+ r"i work as a[n]? ([\w\s]+)[.\,]?",
233
+ r"i am a[n]? ([\w\s]+)[.\,]?",
234
+ r"i'm a[n]? ([\w\s]+)[.\,]?"
235
+ ]
236
+
237
+ for pattern in profession_patterns:
238
+ profession_match = re.search(pattern, text_lower)
239
+ if profession_match:
240
+ profession = profession_match.group(1).strip()
241
+ self.memory_manager.add_important_fact(f"User works as a {profession}", "user")
242
+ break
243
+
244
+ def _extract_preferences(self, text: str) -> None:
245
+ """Extract user preferences from text
246
+
247
+ Args:
248
+ text: Text to extract information from
249
+ """
250
+ text_lower = text.lower()
251
+
252
+ # Extract likes
253
+ if "i like" in text_lower or "i love" in text_lower or "i enjoy" in text_lower:
254
+ like_patterns = [
255
+ r"i like ([\w\s]+)[.\,]?",
256
+ r"i love ([\w\s]+)[.\,]?",
257
+ r"i enjoy ([\w\s]+)[.\,]?"
258
+ ]
259
+
260
+ for pattern in like_patterns:
261
+ like_match = re.search(pattern, text_lower)
262
+ if like_match:
263
+ like = like_match.group(1).strip()
264
+ self.memory_manager.add_important_fact(f"User likes {like}", "user")
265
+ break
266
+
267
+ # Extract dislikes
268
+ if "i don't like" in text_lower or "i hate" in text_lower or "i dislike" in text_lower:
269
+ dislike_patterns = [
270
+ r"i don't like ([\w\s]+)[.\,]?",
271
+ r"i hate ([\w\s]+)[.\,]?",
272
+ r"i dislike ([\w\s]+)[.\,]?"
273
+ ]
274
+
275
+ for pattern in dislike_patterns:
276
+ dislike_match = re.search(pattern, text_lower)
277
+ if dislike_match:
278
+ dislike = dislike_match.group(1).strip()
279
+ self.memory_manager.add_important_fact(f"User dislikes {dislike}", "user")
280
+ break
281
+
282
+ def _extract_task_info(self, text: str) -> None:
283
+ """Extract task-related information from text
284
+
285
+ Args:
286
+ text: Text to extract information from
287
+ """
288
+ text_lower = text.lower()
289
+
290
+ # Extract goals
291
+ if "my goal is" in text_lower or "i want to" in text_lower or "i need to" in text_lower:
292
+ goal_patterns = [
293
+ r"my goal is to ([\w\s]+)[.\,]?",
294
+ r"i want to ([\w\s]+)[.\,]?",
295
+ r"i need to ([\w\s]+)[.\,]?"
296
+ ]
297
+
298
+ for pattern in goal_patterns:
299
+ goal_match = re.search(pattern, text_lower)
300
+ if goal_match:
301
+ goal = goal_match.group(1).strip()
302
+ self.memory_manager.add_important_fact(f"User's goal is to {goal}", "user")
303
+ break
304
+
305
+ def run_test_case(self) -> Dict[str, Any]:
306
+ """Run a test case to demonstrate the agent's capabilities with minimal resources
307
+
308
+ This method is useful for testing the agent on resource-constrained environments
309
+ like Hugging Face Spaces or during development.
310
+
311
+ Returns:
312
+ Dictionary containing test results and performance metrics
313
+ """
314
+ print("Running test case with minimal resources...")
315
+ start_time = time.time()
316
+
317
+ # Simple test query that doesn't require extensive reasoning
318
+ test_query = "What can you help me with?"
319
+
320
+ # Process the query with minimal settings
321
+ test_response = self.process_query(test_query, use_reasoning=False)
322
+
323
+ # Calculate performance metrics
324
+ processing_time = time.time() - start_time
325
+ memory_usage = self._estimate_memory_usage()
326
+
327
+ # Return test results
328
+ return {
329
+ "status": "success",
330
+ "model_id": self.model_id,
331
+ "device": self.device,
332
+ "test_query": test_query,
333
+ "test_response": test_response["response"],
334
+ "processing_time": processing_time,
335
+ "memory_usage_mb": memory_usage,
336
+ "timestamp": time.time()
337
+ }
338
+
339
+ def _estimate_memory_usage(self) -> float:
340
+ """Estimate the memory usage of the model
341
+
342
+ Returns:
343
+ Estimated memory usage in MB
344
+ """
345
+ try:
346
+ import psutil
347
+ process = psutil.Process(os.getpid())
348
+ memory_info = process.memory_info()
349
+ return memory_info.rss / (1024 * 1024) # Convert to MB
350
+ except ImportError:
351
+ return 0.0 # Return 0 if psutil is not available
352
+
353
+ def _extract_with_reasoning(self, query: str, response: str) -> None:
354
+ """Use the reasoning engine to extract important facts
355
+
356
+ Args:
357
+ query: User query
358
+ response: Agent response
359
+ """
360
+ # Only use this for longer queries to avoid unnecessary processing
361
+ if len(query) < 50:
362
+ return
363
+
364
+ extraction_prompt = f"""Extract important facts from this conversation:
365
+
366
+ User: {query}
367
+ Assistant: {response}
368
+
369
+ List of important facts (one per line):
370
+ 1. """
371
+
372
+ try:
373
+ facts_text = self.reasoning_engine.generate_text(extraction_prompt, max_length=256)
374
+
375
+ # Parse the facts
376
+ for line in facts_text.split('\n'):
377
+ line = line.strip()
378
+ if line and (line[0].isdigit() or line.startswith('- ')):
379
+ # Remove numbering or bullet points
380
+ fact = re.sub(r'^\d+\.\s*|^-\s*', '', line).strip()
381
+ if fact and len(fact) > 10: # Only add substantial facts
382
+ self.memory_manager.add_important_fact(fact, "inference")
383
+ except Exception as e:
384
+ print(f"Error extracting facts with reasoning: {str(e)}")
385
+ # Continue without adding facts
386
+
387
+
388
+
389
+ def execute_task(self, task_description: str) -> Dict[str, Any]:
390
+ """Execute a task based on the description
391
+
392
+ Args:
393
+ task_description: Description of the task to execute
394
+
395
+ Returns:
396
+ Dictionary containing task results and status
397
+ """
398
+ return self.task_executor.execute_task(task_description)
399
+
400
+ def get_status(self) -> Dict[str, Any]:
401
+ """Get the current status of the agent
402
+
403
+ Returns:
404
+ Dictionary containing agent status information
405
+ """
406
+ memory_stats = self.memory_manager.get_memory_stats()
407
+ task_status = self.task_executor.get_task_status()
408
+
409
+ return {
410
+ "model_id": self.model_id,
411
+ "device": self.device,
412
+ "conversation_turns": memory_stats["conversation_turns"],
413
+ "important_facts": memory_stats["important_facts"],
414
+ "current_task": task_status["current_task"],
415
+ "task_status": task_status["status"]
416
+ }
417
+
418
+ def clear_conversation(self) -> None:
419
+ """Clear the conversation history"""
420
+ self.memory_manager.clear_conversation_history()
421
+
422
+ def process_document(self, document_text: str, document_type: str = "resume") -> Dict[str, Any]:
423
+ """Process a document (like a resume) and extract information
424
+
425
+ Args:
426
+ document_text: The text content of the document
427
+ document_type: The type of document (e.g., "resume", "job_description")
428
+
429
+ Returns:
430
+ Dictionary containing extracted information and analysis
431
+ """
432
+ self.memory_manager.store_session_data(f"last_{document_type}", document_text)
433
+ start_time = time.time()
434
+
435
+ # Create a prompt for document analysis
436
+ analysis_prompt = f"""I need to analyze this {document_type} document and extract key information:
437
+
438
+ {document_text}
439
+
440
+ Detailed analysis:"""
441
+
442
+ # Generate analysis using reasoning engine
443
+ analysis = self.reasoning_engine.generate_text(analysis_prompt, max_length=1024)
444
+
445
+ # Extract structured information based on document type
446
+ if document_type.lower() == "resume":
447
+ extraction_prompt = f"""Based on this resume:
448
+ {document_text}
449
+
450
+ Extract the following information in a structured format:
451
+ 1. Name:
452
+ 2. Contact Information:
453
+ 3. Education:
454
+ 4. Work Experience:
455
+ 5. Skills:
456
+ 6. Projects:
457
+ 7. Certifications:
458
+ 8. Languages:
459
+ 9. Key Strengths:
460
+ """
461
+ elif document_type.lower() == "job_description":
462
+ extraction_prompt = f"""Based on this job description:
463
+ {document_text}
464
+
465
+ Extract the following information in a structured format:
466
+ 1. Job Title:
467
+ 2. Company:
468
+ 3. Location:
469
+ 4. Required Skills:
470
+ 5. Required Experience:
471
+ 6. Education Requirements:
472
+ 7. Responsibilities:
473
+ 8. Benefits:
474
+ 9. Key Qualifications:
475
+ """
476
+ else:
477
+ extraction_prompt = f"""Extract key information from this document:
478
+ {document_text}
479
+
480
+ Key information:
481
+ 1. """
482
+
483
+ # Generate structured extraction
484
+ structured_info = self.reasoning_engine.generate_text(extraction_prompt, max_length=1024)
485
+
486
+ # Add important facts to memory
487
+ self._extract_document_facts(document_text, document_type, structured_info)
488
+
489
+ processing_time = time.time() - start_time
490
+
491
+ return {
492
+ "document_type": document_type,
493
+ "analysis": analysis,
494
+ "structured_info": structured_info,
495
+ "processing_time": processing_time,
496
+ "timestamp": time.time()
497
+ }
498
+
499
+ def _extract_document_facts(self, document_text: str, document_type: str, structured_info: str) -> None:
500
+ """Extract important facts from a document and add them to memory
501
+
502
+ Args:
503
+ document_text: The text content of the document
504
+ document_type: The type of document
505
+ structured_info: Structured information extracted from the document
506
+ """
507
+ # Extract key facts based on document type
508
+ if document_type.lower() == "resume":
509
+ # Extract name if present
510
+ name_match = re.search(r"Name:\s*([\w\s]+)\n", structured_info)
511
+ if name_match:
512
+ name = name_match.group(1).strip()
513
+ self.memory_manager.add_important_fact(f"Document contains resume for {name}", "document")
514
+
515
+ # Extract skills
516
+ skills_match = re.search(r"Skills:\s*([\w\s,\.\-\+]+)\n", structured_info)
517
+ if skills_match:
518
+ skills = skills_match.group(1).strip()
519
+ self.memory_manager.add_important_fact(f"Resume shows skills in: {skills}", "document")
520
+
521
+ # Extract education
522
+ education_match = re.search(r"Education:\s*([\w\s,\.\-\+]+)\n", structured_info)
523
+ if education_match:
524
+ education = education_match.group(1).strip()
525
+ self.memory_manager.add_important_fact(f"Resume shows education: {education}", "document")
526
+
527
+ elif document_type.lower() == "job_description":
528
+ # Extract job title
529
+ title_match = re.search(r"Job Title:\s*([\w\s]+)\n", structured_info)
530
+ if title_match:
531
+ title = title_match.group(1).strip()
532
+ self.memory_manager.add_important_fact(f"Document contains job description for {title}", "document")
533
+
534
+ # Extract required skills
535
+ skills_match = re.search(r"Required Skills:\s*([\w\s,\.\-\+]+)\n", structured_info)
536
+ if skills_match:
537
+ skills = skills_match.group(1).strip()
538
+ self.memory_manager.add_important_fact(f"Job requires skills in: {skills}", "document")
539
+
540
+ # Add general document fact
541
+ self.memory_manager.add_important_fact(f"Processed a {document_type} document", "system")
542
+
543
+ def rank_resumes(self, job_description: str, resumes: List[str]) -> Dict[str, Any]:
544
+ """Rank multiple resumes against a job description
545
+
546
+ Args:
547
+ job_description: The job description text
548
+ resumes: List of resume texts to rank
549
+
550
+ Returns:
551
+ Dictionary containing rankings and analysis
552
+ """
553
+ start_time = time.time()
554
+
555
+ # Process the job description first
556
+ job_result = self.process_document(job_description, "job_description")
557
+ job_analysis = job_result["structured_info"]
558
+
559
+ # Process each resume
560
+ resume_results = []
561
+ for i, resume in enumerate(resumes):
562
+ result = self.process_document(resume, "resume")
563
+ resume_results.append({
564
+ "index": i,
565
+ "text": resume,
566
+ "analysis": result["structured_info"]
567
+ })
568
+
569
+ # Create a ranking prompt
570
+ ranking_prompt = f"""I need to rank these resumes based on how well they match the job description.
571
+
572
+ Job Description Analysis:
573
+ {job_analysis}
574
+
575
+ Resumes:
576
+ """
577
+
578
+ for i, result in enumerate(resume_results):
579
+ ranking_prompt += f"\nResume {i+1}:\n{result['analysis']}\n"
580
+
581
+ ranking_prompt += "\nRank these resumes from best to worst match for the job, with detailed reasoning for each:"
582
+
583
+ # Generate the ranking analysis
584
+ ranking_analysis = self.reasoning_engine.generate_text(ranking_prompt, max_length=2048)
585
+
586
+ # Generate a numerical scoring for each resume
587
+ scoring_prompt = f"""Based on my analysis of how well these resumes match the job description:
588
+ {ranking_analysis}
589
+
590
+ Assign a numerical score from 0-100 for each resume, where 100 is a perfect match:
591
+
592
+ Resume 1 Score:"""
593
+
594
+ scores_text = self.reasoning_engine.generate_text(scoring_prompt, max_length=512)
595
+
596
+ # Parse scores (simple regex approach)
597
+ scores = []
598
+ for i in range(len(resume_results)):
599
+ score_match = re.search(fr"Resume {i+1} Score:\s*(\d+)", scores_text)
600
+ if score_match:
601
+ scores.append(int(score_match.group(1)))
602
+ else:
603
+ # Default score if parsing fails
604
+ scores.append(50)
605
+
606
+ # Create the final rankings
607
+ rankings = []
608
+ for i, score in enumerate(scores):
609
+ rankings.append({
610
+ "resume_index": i,
611
+ "score": score,
612
+ "resume_text": resumes[i][:100] + "..." # Truncated for readability
613
+ })
614
+
615
+ # Sort by score (descending)
616
+ rankings.sort(key=lambda x: x["score"], reverse=True)
617
+
618
+ processing_time = time.time() - start_time
619
+
620
+ return {
621
+ "rankings": rankings,
622
+ "analysis": ranking_analysis,
623
+ "job_description": job_description,
624
+ "processing_time": processing_time
625
+ }
626
+
627
+ # Create the Gradio interface
628
+ def create_interface(test_mode=False):
629
+ """Create the Gradio interface for the ResuRank AI Agent
630
+
631
+ Args:
632
+ test_mode: Whether to run in test mode with minimal resources
633
+ """
634
+ # Initialize the agent with appropriate settings
635
+ if test_mode:
636
+ agent = ResuRankAgent(model_id="distilgpt2", use_cache=True, test_mode=True)
637
+ # Run a test case to verify functionality
638
+ test_results = agent.run_test_case()
639
+ print(f"Test results: {test_results}")
640
+ else:
641
+ agent = ResuRankAgent(model_id="google/flan-t5-base", use_cache=True)
642
+
643
+ with gr.Blocks(title="ResuRank AI Agent") as interface:
644
+ gr.Markdown("# ResuRank AI Agent")
645
+ gr.Markdown("An autonomous AI agent that can process queries, perform reasoning, and execute tasks.")
646
+
647
+ with gr.Tab("Chat"):
648
+ chatbot = gr.Chatbot(height=400)
649
+ msg = gr.Textbox(label="Your message", placeholder="Ask me anything...")
650
+ with gr.Row():
651
+ submit_btn = gr.Button("Submit")
652
+ clear_btn = gr.Button("Clear")
653
+
654
+ reasoning_checkbox = gr.Checkbox(label="Use reasoning", value=True)
655
+
656
+ if reasoning_checkbox.value:
657
+ reasoning_output = gr.Textbox(label="Reasoning", interactive=False)
658
+ else:
659
+ reasoning_output = gr.Textbox(label="Reasoning", interactive=False, visible=False)
660
+
661
+ def respond(message, chat_history, use_reasoning):
662
+ if not message.strip():
663
+ return chat_history, "", ""
664
+
665
+ # Process the query
666
+ result = agent.process_query(message, use_reasoning=use_reasoning)
667
+
668
+ # Update chat history
669
+ chat_history.append((message, result["response"]))
670
+
671
+ return chat_history, "", result.get("reasoning", "")
672
+
673
+ def clear_chat():
674
+ agent.clear_conversation()
675
+ return [], "", ""
676
+
677
+ # Set up event handlers
678
+ submit_btn.click(respond, [msg, chatbot, reasoning_checkbox], [chatbot, msg, reasoning_output])
679
+ msg.submit(respond, [msg, chatbot, reasoning_checkbox], [chatbot, msg, reasoning_output])
680
+ clear_btn.click(clear_chat, None, [chatbot, msg, reasoning_output])
681
+ reasoning_checkbox.change(lambda x: gr.update(visible=x), reasoning_checkbox, reasoning_output)
682
+
683
+ with gr.Tab("Task Execution"):
684
+ task_input = gr.Textbox(label="Task Description", placeholder="Describe the task to execute...")
685
+ execute_btn = gr.Button("Execute Task")
686
+
687
+ with gr.Row():
688
+ with gr.Column():
689
+ plan_output = gr.Textbox(label="Execution Plan", interactive=False)
690
+ with gr.Column():
691
+ results_output = gr.Textbox(label="Task Results", interactive=False)
692
+
693
+ task_status = gr.Textbox(label="Task Status", value="idle", interactive=False)
694
+
695
+ def execute_task(task_description):
696
+ if not task_description.strip():
697
+ return "No task provided.", "", "idle"
698
+
699
+ # Execute the task
700
+ result = agent.execute_task(task_description)
701
+
702
+ return result.get("plan", ""), result.get("result", ""), result.get("status", "")
703
+
704
+ # Set up event handlers
705
+ execute_btn.click(execute_task, task_input, [plan_output, results_output, task_status])
706
+
707
+ with gr.Tab("Agent Status"):
708
+ status_btn = gr.Button("Refresh Status")
709
+
710
+ with gr.Row():
711
+ with gr.Column():
712
+ model_info = gr.Textbox(label="Model Information", interactive=False)
713
+ with gr.Column():
714
+ conversation_info = gr.Textbox(label="Conversation Information", interactive=False)
715
+
716
+ def update_status():
717
+ status = agent.get_status()
718
+ model_text = f"Model ID: {status['model_id']}\nDevice: {status['device']}"
719
+
720
+ # Handle important_facts which might be an integer count or a list
721
+ important_facts_count = status['important_facts']
722
+ if isinstance(important_facts_count, list):
723
+ important_facts_count = len(important_facts_count)
724
+
725
+ conversation_text = f"Conversation Length: {status['conversation_turns']} turns\nImportant Facts: {important_facts_count}\nCurrent Task: {status['current_task'] or 'None'}\nTask Status: {status['task_status']}"
726
+
727
+ return model_text, conversation_text
728
+
729
+ # Set up event handlers
730
+ status_btn.click(update_status, None, [model_info, conversation_info])
731
+
732
+ # Initialize status on load
733
+ model_info.value, conversation_info.value = update_status()
734
+
735
+ with gr.Tab("Document Processing"):
736
+ with gr.Row():
737
+ with gr.Column():
738
+ document_input = gr.Textbox(label="Document Text", placeholder="Paste resume or job description text here...", lines=10)
739
+ document_type = gr.Radio(["resume", "job_description", "other"], label="Document Type", value="resume")
740
+ process_btn = gr.Button("Process Document")
741
+
742
+ with gr.Row():
743
+ with gr.Column():
744
+ analysis_output = gr.Textbox(label="Document Analysis", interactive=False, lines=10)
745
+ with gr.Column():
746
+ structured_output = gr.Textbox(label="Structured Information", interactive=False, lines=10)
747
+
748
+ def process_document(document_text, doc_type):
749
+ if not document_text.strip():
750
+ return "No document provided.", ""
751
+
752
+ # Process the document
753
+ result = agent.process_document(document_text, doc_type)
754
+
755
+ return result.get("analysis", ""), result.get("structured_info", "")
756
+
757
+ # Set up event handlers
758
+ process_btn.click(process_document, [document_input, document_type], [analysis_output, structured_output])
759
+
760
+ with gr.Tab("Resume Ranking"):
761
+ with gr.Row():
762
+ with gr.Column():
763
+ job_description_input = gr.Textbox(label="Job Description", placeholder="Paste job description here...", lines=8)
764
+
765
+ with gr.Row():
766
+ with gr.Column():
767
+ resume1_input = gr.Textbox(label="Resume 1", placeholder="Paste first resume here...", lines=6)
768
+ with gr.Column():
769
+ resume2_input = gr.Textbox(label="Resume 2", placeholder="Paste second resume here...", lines=6)
770
+
771
+ with gr.Row():
772
+ with gr.Column():
773
+ resume3_input = gr.Textbox(label="Resume 3 (Optional)", placeholder="Paste third resume here...", lines=6)
774
+ with gr.Column():
775
+ resume4_input = gr.Textbox(label="Resume 4 (Optional)", placeholder="Paste fourth resume here...", lines=6)
776
+
777
+ rank_btn = gr.Button("Rank Resumes")
778
+
779
+ ranking_output = gr.Textbox(label="Ranking Results", interactive=False, lines=15)
780
+
781
+ def rank_resumes(job_desc, resume1, resume2, resume3, resume4):
782
+ if not job_desc.strip() or not resume1.strip() or not resume2.strip():
783
+ return "Please provide at least a job description and two resumes."
784
+
785
+ # Collect all non-empty resumes
786
+ resumes = [r for r in [resume1, resume2, resume3, resume4] if r.strip()]
787
+
788
+ # Rank the resumes
789
+ result = agent.rank_resumes(job_desc, resumes)
790
+
791
+ # Format the results
792
+ output = "Resume Rankings (Best to Worst Match):\n\n"
793
+
794
+ for i, rank in enumerate(result["rankings"]):
795
+ resume_num = rank["resume_index"] + 1
796
+ score = rank["score"]
797
+ output += f"{i+1}. Resume {resume_num} - Score: {score}/100\n"
798
+
799
+ output += "\nDetailed Analysis:\n" + result["analysis"]
800
+
801
+ return output
802
+
803
+ # Set up event handlers
804
+ rank_btn.click(rank_resumes, [job_description_input, resume1_input, resume2_input, resume3_input, resume4_input], ranking_output)
805
+
806
+ return interface
807
+
808
+ # Launch the interface when run directly
809
+ if __name__ == "__main__":
810
+ import argparse
811
+
812
+ # Parse command line arguments
813
+ parser = argparse.ArgumentParser(description="ResuRank AI Agent")
814
+ parser.add_argument("--test", action="store_true", help="Run in test mode with minimal resources")
815
+ parser.add_argument("--share", action="store_true", help="Share the Gradio interface")
816
+ args = parser.parse_args()
817
+
818
+ # Create and launch the interface
819
+ interface = create_interface(test_mode=args.test)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
820
  interface.launch(share=args.share)