taha454 commited on
Commit
effa16c
·
verified ·
1 Parent(s): ebebee8

upload v1.2

Browse files
agent/agent_graph/Graph_Nodes.py CHANGED
@@ -1,152 +1,146 @@
1
- import os
2
- from typing import TypedDict, List, Dict, Any, Optional
3
- from langgraph.graph import StateGraph, START, END
4
- from agent.agent_graph.StateTasks import *
5
- from agent.llm.prompts import *
6
- from typing import get_type_hints
7
- import json
8
- from langchain_core.messages import HumanMessage,SystemMessage
9
- from agent.agent_graph.Graph_Utils import get_egp_to_usd
10
- from agent.rag.rag import *
11
-
12
- def answer_question(state:ProblemState):
13
- question = state["question"]
14
-
15
-
16
- if state["question_type"] == Available_Tasks.LAPTOP_CHOOSE.value:
17
- guide_prompt = Tasks_prompts.LAPTOP_THINK.value
18
- elif state["question_type"] == Available_Tasks.ROADMAP.value:
19
- guide_prompt = Tasks_prompts.ROADMAP.value
20
- elif ("node_output_article" in state.keys()):
21
- guide_prompt = Tasks_prompts.ROADMAP.value +"<source>" + state["node_output_article"] +"</source>"
22
-
23
- else:
24
- guide_prompt = Tasks_prompts.RAG.value
25
-
26
-
27
-
28
-
29
- state["answer"] = get_llm_answer(model_llm=state["llm"],messages=state["memory"] + [HumanMessage(content=(guide_prompt + "طلب المستخدم:\n" + question + "\nاهم معلومات المستخدم لاستخدامها ف الدلالة (بالنسبة للسعر هو نفس السعر لكن بالدولار فدائما ركز على السعر بالدولار\n)"+ str(state) + Route_prompts.FINALIZER_PROMPT.value))])
30
-
31
-
32
- return state
33
-
34
-
35
- def update_context(state:ProblemState):
36
- # Control what keys can be modified to preven hullicination
37
- keys = get_type_hints(ProblemState).keys()
38
- keys_modifiable = [] # keep it inside the function to make sure append to it not affected by other calls
39
-
40
- _is_rag=False
41
- _rag_text_if_exist = ""
42
-
43
- for i in list(keys):
44
-
45
- # 2️⃣ Prevent modifying question_type after chat starts
46
- if i in ["question", "answer", "node_output_article", "memory"]:
47
- continue
48
-
49
- # 2️⃣ Prevent modifying question_type after chat starts
50
- # - question_type is only allowed at the very start of the chat
51
- # - If it already exists in state → chat has started → modification not allowed
52
- # if question exists in rag data skip it
53
- if i == "question_type":
54
- if "question_type" in state.keys():
55
- continue
56
-
57
- rag_text = state["rag_model"].get_relevant_question(state["question"])
58
-
59
- if bool(rag_text):
60
- _is_rag = True
61
- _rag_text_if_exist = rag_text # store text to prevent calling twice
62
- continue
63
-
64
- # 3️⃣ Any key that reaches here is allowed to be returned by the LLM
65
- keys_modifiable.append(i)
66
-
67
- # Make the prompt
68
- prompt_llm_new_info = Route_prompts.Context_UPDATOR.value + "\n <KEYS> \n" +str(keys_modifiable) +"\n </KEYS> <Text>"+state["question"]+"</Text>"
69
-
70
-
71
-
72
- llm_new_info = get_llm_answer(model_llm=state["llm"],messages = [HumanMessage(prompt_llm_new_info)])
73
-
74
-
75
- # Save and Process the returned json to prevent hallucination
76
- try:
77
- llm_new_info = json.loads(llm_new_info)
78
- for key in llm_new_info.keys():
79
- if key in keys_modifiable:
80
- state[key] = llm_new_info[key]
81
-
82
- # check if rag
83
- if _is_rag:
84
- state["question_type"] = Available_Tasks.QUESTION.value
85
- state["node_output_article"] = _rag_text_if_exist
86
-
87
- # Check if all_ok can be answered now
88
- last_question = state.get("answer", "")
89
- check_finalized_prompt = (
90
- "عندك ده اخر سوال واجابة"+
91
- last_question +
92
- state["question"] +
93
- "هل معنى ذلك ان المستخدم اكد على الفهم الصحيح؟" +
94
- "رجع فقط BOOL (True/False)"
95
- )
96
-
97
- check_finalized = get_llm_answer(model_llm=state["llm"], messages = [HumanMessage(
98
- check_finalized_prompt
99
- )])
100
- state['all_ok'] = check_finalized.strip().lower() == "true" # If wrong parsed it's false
101
-
102
-
103
-
104
- except Exception as e:
105
- print("Context was not updated due to error : ",e)
106
-
107
-
108
- return state
109
-
110
-
111
-
112
- def convertPriceToDollar(state:ProblemState):
113
- if "price" in state.keys():
114
- state["price"] = get_egp_to_usd(state["price"])
115
-
116
- return state
117
-
118
- def step(state:ProblemState):
119
- next_topic = None
120
-
121
- if "question_type" not in state.keys():
122
- next_topic = "question_type"
123
-
124
-
125
- else:
126
- for i in task_steps[state.get("question_type")]:
127
- if i not in state.keys():
128
- next_topic = i
129
- break
130
- # Only after finishing the to do list of the question type we can ask for all_ok to confirm
131
- if (not next_topic) and ("all_ok" not in state.keys() or (state["all_ok"]==False)) and "question_type" in state.keys():
132
-
133
- next_topic = "all_ok"
134
-
135
-
136
-
137
- step_prompt = (System_prompts.STATE_DESCRIBE.value + f"<order>{next_topic} </order> <state>{state}</state>" + Route_prompts.FINALIZER_PROMPT_STEP.value)
138
- state['answer'] = get_llm_answer(model_llm=state["llm"],messages = [HumanMessage(step_prompt)])
139
-
140
- return state
141
-
142
- def search_knowledgebase(state:ProblemState):
143
- """
144
- Search the vector database for relevant contexts.
145
- """
146
- # fetch top 3 relevant docs
147
- state["node_output_article"] = state["rag_model"].get_relevant_question(state["question"])
148
-
149
- return state
150
-
151
- def get_llm_answer(model_llm=None,messages=[HumanMessage(content="hi")]):
152
- return model_llm.invoke(messages).content
 
1
+ import os
2
+ from typing import TypedDict, List, Dict, Any, Optional
3
+ from langgraph.graph import StateGraph, START, END
4
+ from agent.agent_graph.StateTasks import *
5
+ from agent.llm.prompts import *
6
+ from typing import get_type_hints
7
+ import json
8
+ from langchain_core.messages import HumanMessage,SystemMessage,AIMessage
9
+ from agent.rag.rag import *
10
+ from typing import Any
11
+
12
+ class GraphNode:
13
+ def __init__(self,llm_report,llm_intro_with_tools,llm_exe_with_tools):
14
+ self.llm_report = llm_report
15
+ self.llm_intro_with_tools = llm_intro_with_tools
16
+ self.llm_exe_with_tools = llm_exe_with_tools
17
+
18
+ def make_report(self,state) -> dict[str,Any]:
19
+ """
20
+ Generate a final report of roadmap or Laptop file.
21
+
22
+ Returns:
23
+ {"final_ans_report": string of response}
24
+ """
25
+
26
+ print(state)
27
+
28
+ prompt: str | None = None # Initialize prompt to None with a type hint
29
+
30
+ intent = state.get("intent")
31
+
32
+ if intent == Available_Tasks.ROADMAP.value:
33
+ prompt = prompt_roadmap_maker
34
+ elif intent == Available_Tasks.LAPTOP_CHOOSE.value:
35
+ prompt = prompt_laptop_maker
36
+
37
+ if prompt is None:
38
+ final = state.get("final_ans_report", "")
39
+
40
+ if hasattr(final, "content"):
41
+ final = final.content
42
+
43
+ return {"final_ans_report": final}
44
+
45
+ # Ensure final_ans_report is a string by accessing its content attribute if it's an AIMessage object
46
+ final_ans_report_content = state["final_ans_report"]
47
+ if hasattr(final_ans_report_content, 'content'):
48
+ final_ans_report_content = final_ans_report_content.content
49
+
50
+ messages = prompt + "<case>" + final_ans_report_content + "</case>"
51
+
52
+
53
+ response = self.llm_report.invoke(messages)
54
+
55
+
56
+ return {"final_ans_report": response.content}
57
+
58
+
59
+
60
+ def Conv_Manager_Agent_Node(self,state: AgentState) -> dict:
61
+ """The 'Thought' step of the ReAct loop.
62
+
63
+ Reads the full message history, calls the LLM (with tools bound), and returns
64
+ its response. LangGraph's `add_messages` reducer appends this response to the
65
+ state -- it does not replace the history.
66
+ """
67
+ messages = [SystemMessage(content=Conv_Manager_Prompt)] + state["messages"]
68
+ response = self.llm_intro_with_tools.invoke(messages)
69
+
70
+ updates = {
71
+ "messages": [response]
72
+ }
73
+
74
+ try:
75
+ data = json.loads(response.content)
76
+ if "intent" in data:
77
+ updates["intent"] = data["intent"]
78
+ except:
79
+ pass
80
+
81
+ return updates
82
+
83
+
84
+ def Planner_Agent_Node(self,state: AgentState) -> dict:
85
+ """The 'Thought' step of the ReAct loop.
86
+
87
+ Reads the full message history, calls the LLM (with tools bound), and returns
88
+ its response. LangGraph's `add_messages` reducer appends this response to the
89
+ state -- it does not replace the history.
90
+ """
91
+ plan = ""
92
+ if state["intent"] == Available_Tasks.LAPTOP_CHOOSE.value:
93
+ plan = Laptop_plan
94
+ elif state["intent"] == Available_Tasks.QUESTION.value:
95
+ plan = question_plan
96
+ elif state["intent"] == Available_Tasks.ROADMAP.value:
97
+ plan = Roadmap_plan
98
+
99
+ updates = {"plan": plan}
100
+
101
+ # Explicitly carry intent forward
102
+ if "intent" in state:
103
+ updates["intent"] = state["intent"]
104
+ return updates
105
+
106
+
107
+
108
+ def ReACT_Agent_Node(self,state: AgentState) -> dict:
109
+ """The 'Thought' step of the ReAct loop.
110
+
111
+ Reads the full message history, calls the LLM (with tools bound), and returns
112
+ its response. LangGraph's `add_messages` reducer appends this response to the
113
+ state -- it does not replace the history.
114
+ """
115
+ messages = [SystemMessage(content=state['plan'])] + state["messages"]
116
+ response = self.llm_exe_with_tools.invoke(messages)
117
+
118
+ updates = {
119
+ "messages": [response]
120
+ }
121
+
122
+ # Only attempt to extract final_ans_report if it's an AIMessage and has content
123
+ if isinstance(response, AIMessage) and response.content:
124
+ # If the LLM returned tool calls, it's an intermediate step, not the final report.
125
+ if not getattr(response, "tool_calls", None):
126
+ try:
127
+ data = json.loads(response.content)
128
+ # Check for the specific final_ans_report key and if it's a string
129
+ if "final_ans_report" in data and isinstance(data["final_ans_report"], str):
130
+ updates["final_ans_report"] = data["final_ans_report"]
131
+ else: # Generic JSON not matching final_ans_report structure or a question
132
+ pass # Don't set final_ans_report
133
+ except json.JSONDecodeError:
134
+ # If it's not a JSON, and there are no tool calls, it might be a plain text answer.
135
+ if state["intent"] == Available_Tasks.QUESTION.value:
136
+ # For QUESTION intent, plain text is the final answer.
137
+ updates["final_ans_report"] = response.content
138
+ pass # Not JSON, not a QUESTION final answer, and not a tool call, so no final_ans_report set.
139
+
140
+ # Explicitly carry intent and plan forward
141
+ if "intent" in state:
142
+ updates["intent"] = state["intent"]
143
+ if "plan" in state:
144
+ updates["plan"] = state["plan"]
145
+
146
+ return updates
 
 
 
 
 
 
agent/agent_graph/Graph_Routes.py CHANGED
@@ -1,19 +1,29 @@
1
- from agent.agent_graph.StateTasks import *
2
-
3
- def is_question_clear(state:ProblemState): # not check type , only check to do list
4
- is_clear = True # as used with and later
5
-
6
- is_clear = is_clear and ("question" in state.keys())
7
-
8
- is_clear = is_clear and ("question_type" in state.keys()) and (state.get("question_type") in task_steps.keys())
9
-
10
- if "question_type" in state.keys():
11
- for step in task_steps[state.get("question_type")]:
12
-
13
- is_clear = is_clear and (step in state.keys())
14
-
15
- is_clear = is_clear and (state["all_ok"]==True)
16
-
17
- return is_clear
18
-
19
-
 
 
 
 
 
 
 
 
 
 
 
1
+ from agent.agent_graph.StateTasks import AgentState
2
+ from langgraph.graph import StateGraph, MessagesState, START, END,state
3
+
4
+ def conv_state_check(state:AgentState):
5
+ print("CONV RUN ", state)
6
+ last = state["messages"][-1]
7
+
8
+ if state.get("intent"):
9
+ print("intent =", state.get("intent"))
10
+ return "Planner_Agent"
11
+
12
+ if getattr(last, "tool_calls", None):
13
+ return "tools"
14
+
15
+ return END
16
+
17
+ def react_check(state:AgentState):
18
+ print("REACT RUN ", state)
19
+
20
+ last = state["messages"][-1]
21
+
22
+ if state.get("final_ans_report"):
23
+ print("report =")
24
+ return "Report_Agent"
25
+
26
+ if getattr(last, "tool_calls", None):
27
+ return "tools"
28
+
29
+ return END
agent/agent_graph/Graph_Tools.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool, StructuredTool
2
+ from langgraph.types import interrupt, Command
3
+ import requests
4
+ import warnings
5
+ from agent.rag.rag import init_rag, get_relevant_question
6
+
7
+ warnings.filterwarnings("ignore", category=UserWarning)
8
+
9
+ text_encoder_model = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
10
+ price_url = "https://open.er-api.com/v6/latest/EGP"
11
+
12
+ class GraphTools:
13
+
14
+ def __init__(self,path_file:str,text_encoder_model:str=text_encoder_model,price_url:str=price_url):
15
+ print("Initializing GraphTools...")
16
+ self.text_encoder_model = text_encoder_model
17
+ self.path_file = path_file
18
+ self.price_url = price_url
19
+ self.model, self.corpus_embeddings, self.corpus, self.answers = self.init_rag()
20
+
21
+ ##############################
22
+ ## ASK USER Interrupt Tool
23
+ ##############################
24
+ def ask_user(self, question: str) -> str:
25
+ """Ask the user in Arabic one clear clarifying question when more information is needed.
26
+ Ask only one question per call and wait for the user's answer before asking
27
+ another if necessary.
28
+ """
29
+
30
+ answer = interrupt({"question": question})
31
+ return answer
32
+
33
+
34
+
35
+ ##############################
36
+ ## EGP to usd conv. tool
37
+ ##############################
38
+
39
+ def get_egp_to_usd(self, egp_amount:float) -> float :
40
+ """Convert an amount from Egyptian Pounds (EGP) to US Dollars (USD)
41
+ using the latest available exchange rate.
42
+
43
+ Use this tool whenever you found prices.
44
+
45
+ Args:
46
+ egp_amount: The amount in Egyptian Pounds (EGP) to convert.
47
+
48
+ Returns:
49
+ The equivalent amount in US Dollars (USD).
50
+
51
+ Returns:
52
+ - Converted USD amount on success.
53
+ - None if the exchange-rate service returns an unexpected response.
54
+ - -1 if the request fails or another error occurs.
55
+ """
56
+
57
+
58
+
59
+ try:
60
+ response = requests.get(self.price_url, timeout=8)
61
+ response.raise_for_status() # raise error for bad status
62
+ data = response.json()
63
+
64
+ if data.get("result") != "success":
65
+ print("API error:", data)
66
+ return None
67
+
68
+ rate = data["rates"]["USD"]
69
+ usd_amount = egp_amount * rate
70
+
71
+ return usd_amount
72
+
73
+ except Exception as e:
74
+ print("Error:", e)
75
+ return -1
76
+
77
+ ####################
78
+ ## RAG
79
+ ####################
80
+ def init_rag(self):
81
+ return init_rag(self.path_file, self.text_encoder_model)
82
+
83
+
84
+ def get_relevant_question(self,query:str) -> str:
85
+ """
86
+ Retrieve the most relevant question-answer pair for a user's query
87
+ using semantic similarity.
88
+
89
+ Use this tool when the user asks a question that may already have an
90
+ existing answer in the knowledge base. The tool searches semantically
91
+ rather than by exact keyword matching.
92
+
93
+ Args:
94
+ query: The user's question or search query.
95
+
96
+ Returns:
97
+ A formatted string containing the most relevant question, its answer,
98
+ and the similarity score if a sufficiently similar match is found.
99
+
100
+ Returns None if no match meets the similarity threshold.
101
+ """
102
+
103
+ return get_relevant_question(self.model, self.corpus_embeddings, self.corpus, self.answers, query)
104
+
105
+
106
+
107
+ def get_tools(self):
108
+ ask_user_tool = StructuredTool.from_function(
109
+ func=self.ask_user,
110
+ name="ask_user",
111
+ description=self.ask_user.__doc__,
112
+ )
113
+
114
+ dollar_tool = StructuredTool.from_function(
115
+ func=self.get_egp_to_usd,
116
+ name="get_egp_to_usd",
117
+ description=self.get_egp_to_usd.__doc__,
118
+ )
119
+
120
+ rag_tool = StructuredTool.from_function(
121
+ func=self.get_relevant_question,
122
+ name="get_relevant_question",
123
+ description=self.get_relevant_question.__doc__,
124
+ )
125
+
126
+ return [ask_user_tool,dollar_tool,rag_tool]
agent/agent_graph/Graph_Utils.py CHANGED
@@ -1,25 +1,16 @@
1
- import requests
2
- USD_url = "https://open.er-api.com/v6/latest/EGP"
3
-
4
- def get_egp_to_usd(egp_amount):
5
-
6
- try:
7
- response = requests.get(USD_url, timeout=8)
8
- response.raise_for_status() # raise error for bad status
9
- data = response.json()
10
-
11
- if data.get("result") != "success":
12
- print("API error:", data)
13
- return None
14
-
15
- rate = data["rates"]["USD"]
16
- usd_amount = egp_amount * rate
17
-
18
- return usd_amount
19
-
20
- except Exception as e:
21
- print("Error:", e)
22
- return None
23
-
24
-
25
- get_egp_to_usd(1)
 
1
+ from langchain_google_genai import ChatGoogleGenerativeAI
2
+ from langchain_huggingface import HuggingFaceEndpoint,ChatHuggingFace
3
+
4
+ def get_llm_obj():
5
+ llm = HuggingFaceEndpoint(
6
+ repo_id="openai/gpt-oss-20b",#"deepseek-ai/DeepSeek-V3.2-Exp",#"openai/gpt-oss-20b",
7
+ task='conversational',
8
+ provider="auto",
9
+ max_new_tokens=2048
10
+ )
11
+ llm = ChatHuggingFace(llm=llm)
12
+
13
+
14
+ #llm = ChatGoogleGenerativeAI(model="gemini-3-flash-preview", google_api_key=GEMINI_TOKEN)
15
+
16
+ return llm
 
 
 
 
 
 
 
 
 
agent/agent_graph/StateTasks.py CHANGED
@@ -1,49 +1,26 @@
1
- import os
2
- from typing import TypedDict, List, Dict, Any, Optional
3
- from enum import Enum
4
-
5
- # 1) Core Tasks
6
- class Available_Tasks(Enum):
7
- LAPTOP_CHOOSE = "LAPTOP_CHOOSE"
8
- QUESTION = "QUESTION" # EAG of well known
9
- ROADMAP = "ROADMAP"
10
- PROGRAMMING = "PROGRAMMING"
11
- GENERAL = "GENERAL" # last option with notice
12
- HELLO = "HELLO" # this is to generally detect hello , but in use update_context prompt control to not classify it as core type
13
-
14
- # 2) Task Steps
15
- task_steps = {
16
- Available_Tasks.GENERAL.value : [],
17
- Available_Tasks.LAPTOP_CHOOSE.value : ["price","usage","other_concern"],
18
- Available_Tasks.QUESTION.value : [],
19
- Available_Tasks.PROGRAMMING.value : [],
20
- Available_Tasks.ROADMAP.value : ["career","level","experience","skills"],
21
- Available_Tasks.HELLO.value : []
22
- }
23
-
24
- # 3) Tasks State
25
- class ProblemState(TypedDict):
26
- # main
27
- question: str
28
- answer: Optional[str]
29
- node_output_article : Optional[str]
30
- memory: List[Dict[str, Any]] # filled by list of Human/Sytem messages ... as in gui
31
-
32
- # to do lists
33
- # same as defined in task_Steps
34
- question_type : Optional[str] # --> must be of Available_Tasks values
35
- price : Optional[str]
36
- usage : Optional[str]
37
- other_concern : Optional[str]
38
- career : Optional[str]
39
- level : Optional[str]
40
- experience : Optional[List[str]]
41
- skills : Optional[List[str]]
42
- all_ok : Optional[bool]
43
-
44
- # Models
45
- llm: Optional[Any]
46
- rag_model: Optional[Any]
47
-
48
-
49
-
 
1
+ import os
2
+ from typing import TypedDict, List, Dict, Any, Optional
3
+ from enum import Enum
4
+ from typing import Annotated
5
+ from typing_extensions import TypedDict
6
+ from langgraph.graph import add_messages
7
+
8
+
9
+ class Available_Tasks(Enum):
10
+ LAPTOP_CHOOSE = "LAPTOP_CHOOSE"
11
+ QUESTION = "QUESTION"
12
+ ROADMAP = "ROADMAP"
13
+
14
+
15
+
16
+ av_tasks = [i.value for i in Available_Tasks]
17
+
18
+
19
+ class AgentState(TypedDict):
20
+ messages: Annotated[list, add_messages]
21
+
22
+ intent: str
23
+
24
+ plan: str
25
+
26
+ final_ans_report :str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agent/agent_graph/graph.py CHANGED
@@ -1,32 +1,91 @@
1
- from IPython.display import Image, display
2
- from langgraph.graph import StateGraph, START, END
3
- from agent.agent_graph.Graph_Nodes import *
4
- from agent.agent_graph.Graph_Routes import *
5
-
6
- problem_graph = StateGraph(ProblemState)
7
-
8
- # Add nodes
9
- problem_graph.add_node("answer_question",answer_question)
10
- problem_graph.add_node("update_context",update_context)
11
- problem_graph.add_node("convertPriceToDollar",convertPriceToDollar)
12
- problem_graph.add_node("step",step)
13
-
14
-
15
- # Routes
16
- problem_graph.add_conditional_edges(
17
- "update_context",
18
- is_question_clear,
19
- {
20
- True: "convertPriceToDollar",
21
- False: "step"
22
- }
23
- )
24
-
25
-
26
- # Edges
27
- problem_graph.add_edge(START,"update_context")
28
- problem_graph.add_edge("convertPriceToDollar","answer_question")
29
-
30
- # Finalize
31
- compiled_graph = problem_graph.compile()
32
- #display(Image(compiled_graph.get_graph().draw_mermaid_png()))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from IPython.display import Image, display
2
+ from langgraph.graph import StateGraph, START, END
3
+ from agent.agent_graph.Graph_Nodes import *
4
+ from agent.agent_graph.Graph_Routes import *
5
+ from agent.agent_graph.Graph_Utils import *
6
+ from agent.agent_graph.Graph_Tools import GraphTools
7
+ from agent.agent_graph.Graph_Nodes import GraphNode
8
+ from langgraph.checkpoint.memory import MemorySaver
9
+ from langgraph.prebuilt import ToolNode, tools_condition
10
+
11
+
12
+
13
+ class AgentGraph:
14
+ def __init__(self,checkpointer,path_rag_file):
15
+ self.path_rag_file = path_rag_file
16
+ self.checkpointer = checkpointer
17
+
18
+ #self.checkpointer.setup() # only first time
19
+ self.graph = self._build_graph()
20
+
21
+ def _build_graph(self):
22
+
23
+
24
+ llm_intro = get_llm_obj()
25
+ llm_exe = get_llm_obj()
26
+ llm_report = get_llm_obj()
27
+
28
+
29
+ GraphToolsOBJ = GraphTools(path_file=self.path_rag_file)
30
+ ask_user_tool,dollar_tool,rag_tool = GraphToolsOBJ.get_tools()
31
+
32
+ tools_conv = [ask_user_tool]
33
+ tools_exe = [ask_user_tool, dollar_tool, rag_tool]
34
+
35
+ llm_intro_with_tools = llm_intro.bind_tools(tools_conv)
36
+ llm_exe_with_tools = llm_exe.bind_tools(tools_exe)
37
+
38
+
39
+ GraphNodeOBJ = GraphNode(llm_report,llm_intro_with_tools,llm_exe_with_tools)
40
+
41
+ builder = StateGraph(AgentState)
42
+ builder.add_node("Conv_Manager_Agent",GraphNodeOBJ.Conv_Manager_Agent_Node)
43
+ builder.add_node("Planner_Agent",GraphNodeOBJ.Planner_Agent_Node)
44
+ builder.add_node("ReACT_Agent",GraphNodeOBJ.ReACT_Agent_Node)
45
+ builder.add_node("tools_conv", ToolNode(tools_conv))
46
+ builder.add_node("tools_react", ToolNode(tools_exe))
47
+
48
+ builder.add_node("make_report", GraphNodeOBJ.make_report)
49
+
50
+
51
+ builder.add_edge(START, "Conv_Manager_Agent")
52
+ builder.add_conditional_edges(
53
+ "Conv_Manager_Agent",
54
+ conv_state_check, # inspects the last AIMessage for tool_calls
55
+ {
56
+ "tools": "tools_conv",
57
+ "Planner_Agent":"Planner_Agent",
58
+ END:END
59
+ },
60
+ )
61
+ builder.add_edge("tools_conv", "Conv_Manager_Agent")
62
+
63
+
64
+
65
+
66
+ builder.add_edge("Planner_Agent", "ReACT_Agent")
67
+
68
+ builder.add_conditional_edges(
69
+ "ReACT_Agent",
70
+ react_check, # inspects the last AIMessage for tool_calls
71
+ {
72
+ "tools": "tools_react",
73
+ "Report_Agent":"make_report",
74
+ END:END
75
+ },
76
+ )
77
+
78
+ builder.add_edge("tools_react", "ReACT_Agent")
79
+
80
+ builder.add_edge("make_report",END)
81
+
82
+
83
+
84
+ graph = builder.compile(checkpointer=self.checkpointer)
85
+ #print("Graph compiled successfully ✅")
86
+ #display(Image(graph.get_graph().draw_mermaid_png()))
87
+ return graph
88
+
89
+ def get_graph(self):
90
+ return self.graph
91
+