QuentinL52 commited on
Commit
1e52c25
·
verified ·
1 Parent(s): 5db4cdd

Create services/graph_service.py

Browse files
Files changed (1) hide show
  1. services/graph_service.py +99 -0
services/graph_service.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import TypedDict, Annotated, Sequence
3
+ from langchain_openai import ChatOpenAI
4
+ from langchain_core.messages import BaseMessage, AIMessage, HumanMessage
5
+ from langchain.agents import create_openai_tools_agent, AgentExecutor
6
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
7
+ from langgraph.graph import StateGraph, END
8
+ from tools.analysis_tools import trigger_interview_analysis
9
+
10
+ # --- 1. Définition de l'état du graphe ---
11
+ class AgentState(TypedDict):
12
+ user_id: str
13
+ job_offer_id: str
14
+ cv_document: dict
15
+ job_offer: dict
16
+ messages: Annotated[Sequence[BaseMessage], lambda x, y: x + y]
17
+
18
+ # --- 2. Configuration de l'agent ---
19
+ def create_agent_executor():
20
+ # Le prompt instruit l'agent sur son rôle et quand utiliser l'outil
21
+ prompt = ChatPromptTemplate.from_messages([
22
+ ("system", """You are a friendly and professional HR recruiter named AIrh.
23
+ Your role is to conduct a job interview based on the provided resume (cv_document) and the job description (job_offer).
24
+ Engage with the user, ask relevant questions, and respond to their answers.
25
+ When you feel the interview is complete, you MUST use the 'trigger_interview_analysis' tool to end the conversation.
26
+ After calling the tool, say a final goodbye to the user."""),
27
+ MessagesPlaceholder(variable_name="messages"),
28
+ ])
29
+
30
+ llm = ChatOpenAI(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4-turbo", temperature=0.7)
31
+ tools = [trigger_interview_analysis]
32
+ agent = create_openai_tools_agent(llm, tools, prompt)
33
+ return AgentExecutor(agent=agent, tools=tools, verbose=True)
34
+
35
+ # --- 3. Définition des nœuds du graphe ---
36
+ agent_executor = create_agent_executor()
37
+
38
+ def agent_node(state: AgentState):
39
+ """Le nœud principal qui appelle l'agent pour répondre ou utiliser un outil."""
40
+ response = agent_executor.invoke({
41
+ "user_id": state["user_id"],
42
+ "job_offer_id": state["job_offer_id"],
43
+ "cv_document": state["cv_document"],
44
+ "job_offer": state["job_offer"],
45
+ "messages": state["messages"],
46
+ "conversation_history": state["messages"] # Pour la compatibilité si l'outil en a besoin
47
+ })
48
+ return {"messages": [AIMessage(content=response["output"])]}
49
+
50
+ # --- 4. Définition du routeur (Conditional Edge) ---
51
+ def router(state: AgentState):
52
+ """Décide du chemin à suivre après la réponse de l'agent."""
53
+ last_message = state["messages"][-1]
54
+ if hasattr(last_message, 'tool_calls') and last_message.tool_calls:
55
+ # Si l'agent a décidé d'appeler l'outil, on termine le graphe
56
+ return "end"
57
+ else:
58
+ # Sinon, on continue la conversation
59
+ return "continue"
60
+
61
+ # --- 5. Construction du graphe ---
62
+ def build_graph():
63
+ graph = StateGraph(AgentState)
64
+ graph.add_node("agent", agent_node)
65
+ graph.add_conditional_edges(
66
+ "agent",
67
+ router,
68
+ {"continue": "agent", "end": END}
69
+ )
70
+ graph.set_entry_point("agent")
71
+ return graph.compile()
72
+
73
+ # --- Service principal à appeler depuis l'API ---
74
+ class GraphConversationManager:
75
+ def __init__(self):
76
+ self.graph = build_graph()
77
+
78
+ def invoke(self, payload: dict):
79
+ # Prépare les messages pour le format LangChain
80
+ messages = [HumanMessage(content=m["content"]) if m["role"] == "user" else AIMessage(content=m["content"]) for m in payload["messages"]]
81
+
82
+ state = {
83
+ "user_id": payload["user_id"],
84
+ "job_offer_id": payload["job_offer_id"],
85
+ "cv_document": payload["cv_document"],
86
+ "job_offer": payload["job_offer"],
87
+ "messages": messages,
88
+ }
89
+
90
+ final_state = self.graph.invoke(state)
91
+
92
+ # Détermine le statut final pour le front-end
93
+ last_message = final_state['messages'][-1]
94
+ status = "finished" if hasattr(last_message, 'tool_calls') and last_message.tool_calls else "interviewing"
95
+
96
+ return {
97
+ "response": last_message.content,
98
+ "status": status
99
+ }