anu151105 commited on
Commit
30bea59
·
verified ·
1 Parent(s): 9686f4c

Update app.py

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