Spaces:
Sleeping
Sleeping
upload v1.2
Browse files- agent/agent_graph/Graph_Nodes.py +146 -152
- agent/agent_graph/Graph_Routes.py +29 -19
- agent/agent_graph/Graph_Tools.py +126 -0
- agent/agent_graph/Graph_Utils.py +16 -25
- agent/agent_graph/StateTasks.py +26 -49
- agent/agent_graph/graph.py +91 -32
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.
|
| 10 |
-
from
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
#
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 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 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 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
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
def
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 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 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 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 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|