interview_agents_api / src /services /feedback_crew.py
quentinL52
Initial commit
4e9b744
"""Feedback Crew for interview analysis using CrewAI."""
import json
import logging
from typing import Dict, Any, List
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, task, crew
from src.config import crew_openai
from src.schemas.feedback_schemas import FeedbackOutput
logger = logging.getLogger(__name__)
@CrewBase
class FeedbackCrew:
"""Crew for analyzing interview conversations and generating structured feedback."""
agents_config = "config/agents.yaml"
tasks_config = "config/tasks.yaml"
def __init__(self, job_offer: Dict[str, Any], cv_content: str, conversation_history: List[Dict[str, Any]], cheat_metrics: Dict[str, Any] = None, gap_analysis: Dict[str, Any] = None):
self.job_offer = json.dumps(job_offer, ensure_ascii=False)
self.cv_content = cv_content
self.conversation_history = json.dumps(conversation_history, ensure_ascii=False)
self.cheat_metrics = json.dumps(cheat_metrics or {}, ensure_ascii=False)
self.gap_analysis = json.dumps(gap_analysis or {}, ensure_ascii=False)
self._llm = crew_openai()
# --- Agents ---
@agent
def consistency_analyst(self) -> Agent:
return Agent(config=self.agents_config["consistency_analyst"], llm=self._llm)
@agent
def search_analyst(self) -> Agent:
return Agent(config=self.agents_config["search_analyst"], llm=self._llm)
@agent
def tech_expert(self) -> Agent:
return Agent(config=self.agents_config["tech_expert"], llm=self._llm)
@agent
def business_evaluator(self) -> Agent:
return Agent(config=self.agents_config["business_evaluator"], llm=self._llm)
@agent
def final_reporter(self) -> Agent:
return Agent(config=self.agents_config["final_reporter"], llm=self._llm)
# --- Tasks ---
@task
def consistency_task(self) -> Task:
return Task(config=self.tasks_config["consistency_task"])
@task
def search_task(self) -> Task:
return Task(config=self.tasks_config["search_task"])
@task
def tech_task(self) -> Task:
return Task(config=self.tasks_config["tech_task"])
@task
def business_task(self) -> Task:
return Task(config=self.tasks_config["business_task"])
@task
def reporting_task(self) -> Task:
return Task(
config=self.tasks_config["reporting_task"],
output_pydantic=FeedbackOutput
)
# --- Crew ---
@crew
def crew(self) -> Crew:
return Crew(
agents=self.agents,
tasks=self.tasks,
process=Process.sequential,
verbose=True
)
# --- Public API ---
def run(self) -> Dict[str, Any]:
"""Execute the feedback crew and return structured output."""
logger.info("Starting Feedback Crew analysis...")
# Safe extraction of job_type from gap_analysis string or dict
job_type = "GENERAL_TECH"
try:
ga_data = json.loads(self.gap_analysis) if isinstance(self.gap_analysis, str) else self.gap_analysis
job_type = ga_data.get("job_type", "GENERAL_TECH")
except:
pass
inputs = {
"job_offer": self.job_offer,
"cv_content": self.cv_content,
"conversation_history": self.conversation_history,
"cheat_metrics": self.cheat_metrics,
"gap_analysis": self.gap_analysis,
"job_type": job_type
}
result = self.crew().kickoff(inputs=inputs)
# Handle structured output
if result.pydantic:
return result.pydantic.model_dump()
elif result.json_dict:
return result.json_dict
else:
logger.warning("No structured output, parsing raw result")
try:
raw_str = str(result.raw)
start = raw_str.find('{')
end = raw_str.rfind('}') + 1
if start != -1 and end > start:
return json.loads(raw_str[start:end])
except json.JSONDecodeError as e:
logger.error(f"JSON parsing failed: {e}")
return {"error": "Could not parse output", "raw": str(result.raw)}