QuentinL52 commited on
Commit
b247751
·
verified ·
1 Parent(s): 2b56323

Update services/graph_service.py

Browse files
Files changed (1) hide show
  1. services/graph_service.py +27 -19
services/graph_service.py CHANGED
@@ -7,7 +7,10 @@ from langchain_openai import ChatOpenAI
7
  from langchain_core.messages import BaseMessage, AIMessage, HumanMessage, SystemMessage
8
  from langchain.agents import create_openai_tools_agent, AgentExecutor
9
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
 
10
  from langgraph.graph import StateGraph, END
 
 
11
  from tools.analysis_tools import trigger_interview_analysis
12
 
13
  class AgentState(TypedDict):
@@ -18,11 +21,11 @@ class AgentState(TypedDict):
18
  class GraphInterviewProcessor:
19
  """
20
  Cette classe encapsule la logique d'un entretien en utilisant LangGraph.
21
- Elle prépare toutes les données nécessaires à l'initialisation, comme dans votre code original.
22
  """
23
  def __init__(self, payload: Dict[str, Any]):
24
  logging.info("Initialisation de GraphInterviewProcessor...")
25
-
26
  self.user_id = payload["user_id"]
27
  self.job_offer_id = payload["job_offer_id"]
28
  self.job_offer = payload["job_offer"]
@@ -41,7 +44,6 @@ class GraphInterviewProcessor:
41
  logging.info("GraphInterviewProcessor initialisé avec succès.")
42
 
43
  def _load_prompt_template(self, file_path: str) -> str:
44
- """Charge le template du prompt depuis un fichier."""
45
  try:
46
  with open(file_path, 'r', encoding='utf-8') as f:
47
  return f.read()
@@ -50,26 +52,21 @@ class GraphInterviewProcessor:
50
  return "Vous êtes un assistant RH."
51
 
52
  def _format_cv_for_prompt(self) -> str:
53
- """Formate le CV pour l'injecter dans le prompt."""
54
  return json.dumps(self.cv_data, indent=2, ensure_ascii=False)
55
 
56
  def _extract_skills_summary(self) -> str:
57
- """Extrait un résumé des compétences avec niveaux."""
58
  competences = self.cv_data.get('analyse_competences', [])
59
- if not competences:
60
- return "Aucune analyse de compétences disponible."
61
  summary = [f"{comp.get('skill', '')}: {comp.get('level', 'débutant')}" for comp in competences]
62
  return "Niveaux de compétences du candidat: " + " | ".join(summary)
63
 
64
  def _extract_reconversion_info(self) -> str:
65
- """Extrait les informations de reconversion."""
66
  reconversion = self.cv_data.get('reconversion', {})
67
  if reconversion.get('is_reconversion'):
68
  return f"CANDIDAT EN RECONVERSION: {reconversion.get('analysis', '')}"
69
  return "Le candidat n'est pas identifié comme étant en reconversion."
70
 
71
  def _create_agent_executor(self) -> AgentExecutor:
72
- """Crée l'agent executor avec un prompt minimaliste."""
73
  prompt = ChatPromptTemplate.from_messages([
74
  SystemMessage(content="{system_prompt_content}"),
75
  MessagesPlaceholder(variable_name="messages"),
@@ -81,7 +78,6 @@ class GraphInterviewProcessor:
81
  return AgentExecutor(agent=agent, tools=tools, verbose=True)
82
 
83
  def _agent_node(self, state: AgentState):
84
- """Prépare le prompt système dynamiquement et appelle l'agent."""
85
  system_prompt_content = self.system_prompt_template.format(
86
  entreprise=self.job_offer.get('entreprise', 'notre entreprise'),
87
  poste=self.job_offer.get('poste', 'ce poste'),
@@ -102,24 +98,33 @@ class GraphInterviewProcessor:
102
  "conversation_history": state["messages"]
103
  })
104
  return {"messages": [response['output']]}
105
-
106
  def _router(self, state: AgentState):
107
  """Décide du chemin à suivre après la réponse de l'agent."""
108
  last_message = state["messages"][-1]
109
- if hasattr(last_message, 'tool_calls') and last_message.tool_calls:
110
- return "end"
111
- return "continue"
112
-
113
  def _build_graph(self) -> any:
114
  """Construit et compile le graphe d'états."""
 
 
115
  graph = StateGraph(AgentState)
116
  graph.add_node("agent", self._agent_node)
 
 
 
117
  graph.add_conditional_edges(
118
  "agent",
119
  self._router,
120
- {"continue": "agent", "end": END}
 
 
 
121
  )
122
- graph.set_entry_point("agent")
 
 
123
  return graph.compile()
124
 
125
  def invoke(self, messages: List[Dict[str, Any]]):
@@ -135,9 +140,12 @@ class GraphInterviewProcessor:
135
  final_state = self.graph.invoke(initial_state)
136
 
137
  last_message = final_state['messages'][-1]
138
- status = "finished" if self._router(final_state) == 'end' else "interviewing"
 
 
 
139
 
140
  return {
141
  "response": last_message.content,
142
  "status": status
143
- }
 
7
  from langchain_core.messages import BaseMessage, AIMessage, HumanMessage, SystemMessage
8
  from langchain.agents import create_openai_tools_agent, AgentExecutor
9
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
10
+ # Nouveaux imports pour la gestion correcte du graphe
11
  from langgraph.graph import StateGraph, END
12
+ from langgraph.prebuilt import ToolNode
13
+
14
  from tools.analysis_tools import trigger_interview_analysis
15
 
16
  class AgentState(TypedDict):
 
21
  class GraphInterviewProcessor:
22
  """
23
  Cette classe encapsule la logique d'un entretien en utilisant LangGraph.
24
+ Elle prépare toutes les données nécessaires à l'initialisation.
25
  """
26
  def __init__(self, payload: Dict[str, Any]):
27
  logging.info("Initialisation de GraphInterviewProcessor...")
28
+
29
  self.user_id = payload["user_id"]
30
  self.job_offer_id = payload["job_offer_id"]
31
  self.job_offer = payload["job_offer"]
 
44
  logging.info("GraphInterviewProcessor initialisé avec succès.")
45
 
46
  def _load_prompt_template(self, file_path: str) -> str:
 
47
  try:
48
  with open(file_path, 'r', encoding='utf-8') as f:
49
  return f.read()
 
52
  return "Vous êtes un assistant RH."
53
 
54
  def _format_cv_for_prompt(self) -> str:
 
55
  return json.dumps(self.cv_data, indent=2, ensure_ascii=False)
56
 
57
  def _extract_skills_summary(self) -> str:
 
58
  competences = self.cv_data.get('analyse_competences', [])
59
+ if not competences: return "Aucune analyse de compétences disponible."
 
60
  summary = [f"{comp.get('skill', '')}: {comp.get('level', 'débutant')}" for comp in competences]
61
  return "Niveaux de compétences du candidat: " + " | ".join(summary)
62
 
63
  def _extract_reconversion_info(self) -> str:
 
64
  reconversion = self.cv_data.get('reconversion', {})
65
  if reconversion.get('is_reconversion'):
66
  return f"CANDIDAT EN RECONVERSION: {reconversion.get('analysis', '')}"
67
  return "Le candidat n'est pas identifié comme étant en reconversion."
68
 
69
  def _create_agent_executor(self) -> AgentExecutor:
 
70
  prompt = ChatPromptTemplate.from_messages([
71
  SystemMessage(content="{system_prompt_content}"),
72
  MessagesPlaceholder(variable_name="messages"),
 
78
  return AgentExecutor(agent=agent, tools=tools, verbose=True)
79
 
80
  def _agent_node(self, state: AgentState):
 
81
  system_prompt_content = self.system_prompt_template.format(
82
  entreprise=self.job_offer.get('entreprise', 'notre entreprise'),
83
  poste=self.job_offer.get('poste', 'ce poste'),
 
98
  "conversation_history": state["messages"]
99
  })
100
  return {"messages": [response['output']]}
 
101
  def _router(self, state: AgentState):
102
  """Décide du chemin à suivre après la réponse de l'agent."""
103
  last_message = state["messages"][-1]
104
+ if hasattr(last_message, 'tool_calls') and last_message.tool_calls and len(last_message.tool_calls) > 0:
105
+ return "call_tool"
106
+ else:
107
+ return "end_turn"
108
  def _build_graph(self) -> any:
109
  """Construit et compile le graphe d'états."""
110
+ tool_node = ToolNode([trigger_interview_analysis])
111
+
112
  graph = StateGraph(AgentState)
113
  graph.add_node("agent", self._agent_node)
114
+ graph.add_node("tools", tool_node)
115
+
116
+ graph.set_entry_point("agent")
117
  graph.add_conditional_edges(
118
  "agent",
119
  self._router,
120
+ {
121
+ "call_tool": "tools",
122
+ "end_turn": END
123
+ }
124
  )
125
+
126
+ graph.add_edge("tools", "agent")
127
+
128
  return graph.compile()
129
 
130
  def invoke(self, messages: List[Dict[str, Any]]):
 
140
  final_state = self.graph.invoke(initial_state)
141
 
142
  last_message = final_state['messages'][-1]
143
+
144
+ # Le statut est 'finished' si le dernier message contient un appel à un outil.
145
+ # Cela signifie que le graphe a pris le chemin de l'analyse.
146
+ status = "finished" if hasattr(last_message, 'tool_calls') and last_message.tool_calls else "interviewing"
147
 
148
  return {
149
  "response": last_message.content,
150
  "status": status
151
+ }