masai-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- masai/AgentManager/AgentManager.py +205 -0
- masai/AgentManager/__init__.py +1 -0
- masai/Agents/__init__.py +1 -0
- masai/Agents/singular_agent.py +316 -0
- masai/GenerativeModel/__init__.py +1 -0
- masai/GenerativeModel/baseGenerativeModel/basegenerativeModel.py +195 -0
- masai/GenerativeModel/generativeModels.py +203 -0
- masai/MultiAgents/MultiAgent.py +439 -0
- masai/MultiAgents/__init__.py +1 -0
- masai/OMAN/__init__.py +1 -0
- masai/OMAN/oman.py +132 -0
- masai/Tools/PARSERs/json_parser.py +198 -0
- masai/Tools/__init__.py +23 -0
- masai/Tools/calendarTools.py +211 -0
- masai/Tools/emailTools.py +242 -0
- masai/Tools/logging_setup/logger.py +45 -0
- masai/Tools/mathTools.py +11 -0
- masai/Tools/searchTools.py +1075 -0
- masai/Tools/utilities/tokenGenerationTool.py +202 -0
- masai/Tools/visiontools.py +69 -0
- masai/__init__.py +1 -0
- masai/prompts/prompt_templates.py +62 -0
- masai/pydanticModels/AnswerModel.py +40 -0
- masai/pydanticModels/omanModel.py +13 -0
- masai/pydanticModels/supervisorModels.py +13 -0
- masai_framework-0.1.0.dist-info/LICENSE +1483 -0
- masai_framework-0.1.0.dist-info/METADATA +469 -0
- masai_framework-0.1.0.dist-info/RECORD +30 -0
- masai_framework-0.1.0.dist-info/WHEEL +5 -0
- masai_framework-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import os, json
|
|
2
|
+
from typing import List, Tuple, Type, Union, Literal, Dict, Optional
|
|
3
|
+
from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, PromptTemplate, SystemMessagePromptTemplate
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
from ..GenerativeModel.generativeModels import MASGenerativeModel
|
|
6
|
+
from ..Agents.singular_agent import Agent
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from ..pydanticModels.AnswerModel import answermodel
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from importlib import resources
|
|
12
|
+
from ..prompts.prompt_templates import get_agent_prompts
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class AgentDetails:
|
|
16
|
+
capabilities: List[str] # e.g., ["reasoning", "coding", "science"]
|
|
17
|
+
description: str = "" # Optional additional description
|
|
18
|
+
style: str = "gives very elaborate answers" # Communication style
|
|
19
|
+
|
|
20
|
+
class AgentManager:
|
|
21
|
+
def __init__(self, logging=True, context:dict=None, model_config_path=None):
|
|
22
|
+
"""Initialize the AgentManager with an empty registry of agents.
|
|
23
|
+
|
|
24
|
+
The AgentManager class serves as a central registry for creating, managing, and
|
|
25
|
+
coordinating multiple agents in a multi-agent system.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
logging (bool, optional): Enable or disable logging of agent activities.
|
|
29
|
+
Defaults to True.
|
|
30
|
+
context (dict, optional): Additional contextual information to be shared
|
|
31
|
+
with all agents. Defaults to None.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
agents (dict[str, Agent]): Dictionary storing agent instances,
|
|
35
|
+
where keys are agent names and values are Agent objects.
|
|
36
|
+
agent_prompts (dict): Dictionary storing system prompts for each agent.
|
|
37
|
+
logging (bool): Flag to control logging behavior.
|
|
38
|
+
context (dict): Shared context available to all agents.
|
|
39
|
+
model_config_path (Path): Path to the model configuration file.
|
|
40
|
+
"""
|
|
41
|
+
self.agents = {}
|
|
42
|
+
self.agent_prompts = {}
|
|
43
|
+
self.logging = logging
|
|
44
|
+
self.context = context
|
|
45
|
+
|
|
46
|
+
# model_config_path should be provided by user
|
|
47
|
+
if not model_config_path:
|
|
48
|
+
raise ValueError("model_config_path must be provided")
|
|
49
|
+
self.model_config_path = model_config_path
|
|
50
|
+
|
|
51
|
+
def load_prompts(self) -> Tuple[str, str, str]:
|
|
52
|
+
"""Load prompts from module."""
|
|
53
|
+
return get_agent_prompts()
|
|
54
|
+
|
|
55
|
+
def promptformatter(self, router_prompt: str, evaluator_prompt: str, reflector_prompt: str, planner_prompt: str, system_prompt: str) -> Tuple[ChatPromptTemplate, ChatPromptTemplate, ChatPromptTemplate, ChatPromptTemplate]:
|
|
56
|
+
"""Format prompts into ChatPromptTemplates."""
|
|
57
|
+
input_variables = ['question', 'history', 'schema','current_time','useful_info','coworking_agents_info','long_context']
|
|
58
|
+
template = """
|
|
59
|
+
INFO:{useful_info}
|
|
60
|
+
\n\nTIME:{current_time},
|
|
61
|
+
\nQUESTION: {question},
|
|
62
|
+
\n\nRESPONSE FORMAT : {schema},
|
|
63
|
+
\n\nAVAILABLE AGENTS:{coworking_agents_info},
|
|
64
|
+
\n\nCHAT HISTORY: {history}
|
|
65
|
+
\n\nLONG CONTEXT: {long_context}
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
human_message_template = HumanMessagePromptTemplate(
|
|
69
|
+
prompt=PromptTemplate(input_variables=input_variables, template=template)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
system_message_template_1 = SystemMessagePromptTemplate(
|
|
73
|
+
prompt=PromptTemplate(template =system_prompt + "\nFOLLOW THESE INSTRUCTIONS:" + router_prompt )
|
|
74
|
+
)
|
|
75
|
+
system_message_template_2 = SystemMessagePromptTemplate(
|
|
76
|
+
prompt=PromptTemplate(template=system_prompt +"\nFOLLOW THESE INSTRUCTIONS:" + evaluator_prompt )
|
|
77
|
+
)
|
|
78
|
+
system_message_template_3 = SystemMessagePromptTemplate(
|
|
79
|
+
prompt=PromptTemplate(template=system_prompt + "\nFOLLOW THESE INSTRUCTIONS:" + reflector_prompt )
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
system_message_template_4 = SystemMessagePromptTemplate(
|
|
83
|
+
prompt=PromptTemplate(template=system_prompt + "\nFOLLOW THESE INSTRUCTIONS:" + planner_prompt if planner_prompt else "")
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
router_chat_prompt = ChatPromptTemplate.from_messages([system_message_template_1, human_message_template])
|
|
87
|
+
evaluator_chat_prompt = ChatPromptTemplate.from_messages([system_message_template_2, human_message_template])
|
|
88
|
+
reflector_chat_prompt = ChatPromptTemplate.from_messages([system_message_template_3, human_message_template])
|
|
89
|
+
if planner_prompt:
|
|
90
|
+
planner_chat_prompt = ChatPromptTemplate.from_messages([system_message_template_4, human_message_template])
|
|
91
|
+
else:
|
|
92
|
+
planner_chat_prompt = None
|
|
93
|
+
return router_chat_prompt, evaluator_chat_prompt, reflector_chat_prompt, planner_chat_prompt
|
|
94
|
+
|
|
95
|
+
def _load_model_config(self) -> dict:
|
|
96
|
+
"""Load model configuration from a JSON file."""
|
|
97
|
+
if not os.path.exists(self.model_config_path):
|
|
98
|
+
raise FileNotFoundError(f"Model config file not found at {self.model_config_path}.")
|
|
99
|
+
|
|
100
|
+
with open(self.model_config_path, "r") as f:
|
|
101
|
+
return json.load(f)
|
|
102
|
+
|
|
103
|
+
def create_agent(self, agent_name: str, tools: List[object], agent_details: AgentDetails,
|
|
104
|
+
memory_order: int = 20, long_context: bool = True,long_context_order: int = 10, shared_memory_order: int = 10,
|
|
105
|
+
plan: bool = False):
|
|
106
|
+
"""Create and register a new agent in the AgentManager.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
agent_name (str): Unique identifier for the agent (converted to lowercase).
|
|
110
|
+
tools (List[object]): Tools the agent can use, each with a 'name' attribute.
|
|
111
|
+
agent_details (AgentDetails): Configuration with capabilities, description, and style.
|
|
112
|
+
memory_order (int, optional): Number of past interactions to keep. Defaults to 20.
|
|
113
|
+
long_context (bool, optional): Use long context if True. Defaults to True.
|
|
114
|
+
long_context_order (int, optional): Number of past interactions summary to keep in long context. Defaults to 10.
|
|
115
|
+
shared_memory_order (int, optional): Shared memory size for components. Defaults to 10.
|
|
116
|
+
plan (bool, optional): Include planner if True. Defaults to False.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
ValueError: If agent_name already exists.
|
|
120
|
+
FileNotFoundError: If prompts file is missing.
|
|
121
|
+
"""
|
|
122
|
+
agent_name = agent_name.lower()
|
|
123
|
+
if agent_name in self.agents:
|
|
124
|
+
raise ValueError(f"Agent '{agent_name}' already exists.")
|
|
125
|
+
|
|
126
|
+
# Load and format prompts
|
|
127
|
+
prompts = self.load_prompts()
|
|
128
|
+
system_prompt = self._create_system_prompt(agent_name, agent_details)
|
|
129
|
+
chat_prompts = self.promptformatter(*prompts, system_prompt=system_prompt)
|
|
130
|
+
|
|
131
|
+
# Configure tools and answer format
|
|
132
|
+
tool_mapping = {tool.name: tool for tool in tools}
|
|
133
|
+
AnswerFormat = answermodel(tool_names=list(tool_mapping.keys()) + ['None'], tools=tools)
|
|
134
|
+
|
|
135
|
+
# Initialize LLM models
|
|
136
|
+
model_config = self._load_model_config()
|
|
137
|
+
llm_args = {"temperature": 0.2, "memory_order": memory_order, "extra_context": self.context, "long_context": long_context,"long_context_order":long_context_order}
|
|
138
|
+
llm_router = MASGenerativeModel(model_config["router"]["model_name"], category=model_config["router"]["category"], prompt_template=chat_prompts[0], **llm_args)
|
|
139
|
+
llm_evaluator = MASGenerativeModel(model_config["evaluator"]["model_name"], category=model_config["evaluator"]["category"], prompt_template=chat_prompts[1], **llm_args)
|
|
140
|
+
llm_reflector = MASGenerativeModel(model_config["reflector"]["model_name"], category=model_config["reflector"]["category"], prompt_template=chat_prompts[2], **llm_args)
|
|
141
|
+
llm_planner = MASGenerativeModel(model_config["planner"]["model_name"], category=model_config["planner"]["category"], prompt_template=chat_prompts[3], **llm_args) if plan else None
|
|
142
|
+
|
|
143
|
+
# Create and register agent
|
|
144
|
+
agent = Agent(agent_name, llm_router, llm_evaluator, llm_reflector, llm_planner, tool_mapping, AnswerFormat, self.logging, shared_memory_order=shared_memory_order)
|
|
145
|
+
self.agents[agent_name] = agent
|
|
146
|
+
self.agent_prompts[agent_name] = system_prompt
|
|
147
|
+
def _compile_agents(self,type='decentralized',agent_context:dict=None):
|
|
148
|
+
"""Share agent system prompts among all registered agents.
|
|
149
|
+
|
|
150
|
+
This method ensures each agent is aware of other agents' capabilities and characteristics
|
|
151
|
+
by sharing their system prompts. For each agent, it creates a dictionary of all other
|
|
152
|
+
agents' prompts (excluding itself) and stores it in the agent's context.
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
If there are agents A, B, and C:
|
|
156
|
+
- Agent A will receive prompts from B and C
|
|
157
|
+
- Agent B will receive prompts from A and C
|
|
158
|
+
- Agent C will receive prompts from A and B
|
|
159
|
+
|
|
160
|
+
Note:
|
|
161
|
+
This method should be called after all agents have been created and before
|
|
162
|
+
starting any agent interactions to ensure proper inter-agent awareness.
|
|
163
|
+
"""
|
|
164
|
+
if type=='decentralized':
|
|
165
|
+
for agents in self.agents.values():
|
|
166
|
+
prompts ={}
|
|
167
|
+
for agent_name in self.agent_prompts:
|
|
168
|
+
if agent_name!=agents.agent_name:
|
|
169
|
+
prompts[agent_name]=self.agent_prompts[agent_name]
|
|
170
|
+
|
|
171
|
+
# print(prompts)
|
|
172
|
+
agents.agent_context = prompts
|
|
173
|
+
elif type=='hierarchical':
|
|
174
|
+
for agents in self.agents.values():
|
|
175
|
+
agents.agent_context = agent_context
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
def _create_system_prompt(self, agent_name: str, details: AgentDetails) -> str:
|
|
179
|
+
"""Convert AgentDetails into a system prompt."""
|
|
180
|
+
capabilities_str = ", ".join(details.capabilities)
|
|
181
|
+
|
|
182
|
+
prompt_parts = [
|
|
183
|
+
f"Your Name: {agent_name}.\n Your capabilities are {capabilities_str}",
|
|
184
|
+
f"Response Style: {details.style}."
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
if details.description:
|
|
188
|
+
prompt_parts.append(details.description)
|
|
189
|
+
|
|
190
|
+
return "\n".join(prompt_parts)
|
|
191
|
+
|
|
192
|
+
def get_agent(self, agent_name: str)->Agent:
|
|
193
|
+
"""Retrieve an agent by name."""
|
|
194
|
+
if agent_name.lower() not in self.agents:
|
|
195
|
+
raise ValueError(f"No agent found with name '{agent_name}'.")
|
|
196
|
+
|
|
197
|
+
return self.agents[agent_name.lower()]
|
|
198
|
+
|
|
199
|
+
def list_agents(self) -> List[str]:
|
|
200
|
+
"""List all registered agents."""
|
|
201
|
+
return list(self.agents.keys())
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .AgentManager import AgentManager, AgentDetails
|
masai/Agents/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .singular_agent import Agent
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
from langgraph.graph import END, StateGraph, START
|
|
2
|
+
from typing import List, Dict, Any, Literal, TypedDict, Tuple, Union, Type
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
import ast, os
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
load_dotenv()
|
|
7
|
+
from ..GenerativeModel.generativeModels import MASGenerativeModel,GenerativeModel
|
|
8
|
+
from ..GenerativeModel.baseGenerativeModel.basegenerativeModel import BaseGenerativeModel
|
|
9
|
+
from ..Tools.logging_setup.logger import setup_logger
|
|
10
|
+
from ..Tools.PARSERs.json_parser import parse_tool_input, parse_task_string
|
|
11
|
+
from langchain.schema import Document
|
|
12
|
+
|
|
13
|
+
class State(TypedDict):
|
|
14
|
+
messages: List[Dict[str, str]]
|
|
15
|
+
current_tool: str
|
|
16
|
+
tool_input: str
|
|
17
|
+
tool_output: str
|
|
18
|
+
answer: str
|
|
19
|
+
satisfied: bool
|
|
20
|
+
reasoning: str
|
|
21
|
+
delegate_to_agent:str
|
|
22
|
+
current_node: str
|
|
23
|
+
previous_node: str
|
|
24
|
+
plan: List[str]
|
|
25
|
+
class Agent:
|
|
26
|
+
"""Agent Made Out of Routing-Evaluator-Reflector Architecture"""
|
|
27
|
+
_logger=None
|
|
28
|
+
def __init__(self,agent_name,llm_router, llm_evaluator, llm_reflector, llm_planner=None, tool_mapping=None, AnswerFormat:BaseModel=None,logging=True, agent_context=None, shared_memory_order:int=5):
|
|
29
|
+
"""Initialize an agent with router-evaluator-reflector architecture and optional planner.
|
|
30
|
+
|
|
31
|
+
The agent uses a state machine workflow to process queries through specialized LLMs:
|
|
32
|
+
- Router: Determines which tool to use or agent to delegate to
|
|
33
|
+
- Evaluator: Evaluates tool outputs and determines next steps
|
|
34
|
+
- Reflector: Reflects on overall progress and generates final answers
|
|
35
|
+
- Planner (optional): Creates execution plans for complex tasks
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
agent_name (str): Name identifier for the agent instance
|
|
39
|
+
llm_router (BaseGenerativeModel): Language model for routing decisions - determines which tool to use or agent to delegate to
|
|
40
|
+
llm_evaluator (BaseGenerativeModel): Language model for evaluation - processes tool outputs and determines next steps
|
|
41
|
+
llm_reflector (BaseGenerativeModel): Language model for reflection - analyzes overall progress and generates final answers
|
|
42
|
+
llm_planner (BaseGenerativeModel, optional): Language model for planning complex tasks. Defaults to None.
|
|
43
|
+
tool_mapping (Dict[str, Callable], optional): Mapping of tool names to their function implementations. Defaults to None.
|
|
44
|
+
AnswerFormat (BaseModel, optional): Pydantic model defining the structure of agent responses. Defaults to None.
|
|
45
|
+
logging (bool, optional): Enable/disable logging functionality. Defaults to True.
|
|
46
|
+
agent_context (Dict[str, Any], optional): Additional context information for the agent in multi agent system, providing context about other agents it should interact with. Defaults to None.
|
|
47
|
+
shared_memory_order (int, optional): Number of previous interactions to maintain in shared memory among individual components of an agent. Defaults to 5.
|
|
48
|
+
retain_messages_order (int, optional): Number of previous interactions to maintain in agent's system memory.This includes short term memory of all components within the agent. Defaults to 20.
|
|
49
|
+
"""
|
|
50
|
+
self.agent_name = agent_name
|
|
51
|
+
self.llm_evaluator :BaseGenerativeModel =llm_evaluator
|
|
52
|
+
self.llm_router : BaseGenerativeModel=llm_router
|
|
53
|
+
self.llm_reflector : BaseGenerativeModel=llm_reflector
|
|
54
|
+
if llm_planner:
|
|
55
|
+
self.llm_planner = llm_planner
|
|
56
|
+
self.plan=True
|
|
57
|
+
else:
|
|
58
|
+
self.llm_planner = None
|
|
59
|
+
self.plan=False
|
|
60
|
+
self.app = self.agentworkflow()
|
|
61
|
+
self.graph = self.app.get_graph()
|
|
62
|
+
self.tool_mapping : dict= tool_mapping
|
|
63
|
+
self.pydanticmodel : BaseModel = AnswerFormat
|
|
64
|
+
self.logging = logging
|
|
65
|
+
self.shared_memory_order=shared_memory_order
|
|
66
|
+
|
|
67
|
+
if self.logging:
|
|
68
|
+
if Agent._logger is None:
|
|
69
|
+
Agent._logger = setup_logger()
|
|
70
|
+
self.logger = Agent._logger
|
|
71
|
+
else:
|
|
72
|
+
self.logger = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
self.agent_context = agent_context
|
|
76
|
+
self.node:str='evaluator'
|
|
77
|
+
self.retain_messages_order=20
|
|
78
|
+
|
|
79
|
+
def gettoolinput(self, tool_input : dict, tool_name: str)->Union[Dict,str]:
|
|
80
|
+
tool_input = parse_tool_input(tool_input, list((self.tool_mapping[tool_name]).args_schema.schema()['properties'].keys()))
|
|
81
|
+
return tool_input
|
|
82
|
+
|
|
83
|
+
def display(self):
|
|
84
|
+
"""Display the graph of the agent"""
|
|
85
|
+
png_data = self.graph.draw_mermaid_png()
|
|
86
|
+
# Save the PNG image to a file
|
|
87
|
+
mermaid_dir = os.path.join('MAS','Database','mermaid')
|
|
88
|
+
os.makedirs(mermaid_dir, exist_ok=True)
|
|
89
|
+
png_file_path = os.path.join(mermaid_dir, "diagram.png")
|
|
90
|
+
with open(png_file_path, "wb") as f:
|
|
91
|
+
f.write(png_data)
|
|
92
|
+
|
|
93
|
+
def _update_state(self,current_state:State, parsed_response,node):
|
|
94
|
+
if not (parsed_response['tool']=="none" or parsed_response['tool']==None):
|
|
95
|
+
current_state["current_tool"] = parsed_response['tool']
|
|
96
|
+
current_state['tool_input'] = self.gettoolinput(parsed_response['tool_input'],current_state['current_tool'])
|
|
97
|
+
if self.logger:
|
|
98
|
+
self.logger.warning("-------------------------------------Tool Input---------------------------------\n\n")
|
|
99
|
+
self.logger.warning(current_state['tool_input'])
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if node=='planner':
|
|
103
|
+
current_state['plan']=parse_task_string(parsed_response['answer'])
|
|
104
|
+
for ele in current_state['plan']:
|
|
105
|
+
print(ele)
|
|
106
|
+
self.node=node
|
|
107
|
+
current_state.update({
|
|
108
|
+
"previous_node": node,
|
|
109
|
+
"current_node": node,
|
|
110
|
+
"answer": parsed_response['answer'],
|
|
111
|
+
"satisfied": parsed_response['satisfied'],
|
|
112
|
+
"reasoning": parsed_response['reasoning'],
|
|
113
|
+
"current_tool": parsed_response['tool'],
|
|
114
|
+
"delegate_to_agent": parsed_response['delegate_to_agent']
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
current_state["messages"].append({"role": "assistant", "content": current_state['answer']})
|
|
118
|
+
if len(current_state["messages"])>self.retain_messages_order:
|
|
119
|
+
current_state['messages']=[current_state['messages'][0]].extend(current_state["messages"][-self.retain_messages_order:])
|
|
120
|
+
return current_state
|
|
121
|
+
|
|
122
|
+
def node_handler(self,state: State, llm : MASGenerativeModel, prompt:str, component_context=None, node=None):
|
|
123
|
+
|
|
124
|
+
parsed_response = llm.generate_response_mas(prompt,
|
|
125
|
+
output_structure=self.pydanticmodel,
|
|
126
|
+
agent_context=self.agent_context if self.agent_context else None,
|
|
127
|
+
agent_name=self.agent_name,
|
|
128
|
+
component_context=component_context if component_context else [])
|
|
129
|
+
if self.logger:
|
|
130
|
+
self.logger.info(f"{parsed_response['answer']}")
|
|
131
|
+
current_state = state
|
|
132
|
+
current_state = self._update_state(current_state, parsed_response,node)
|
|
133
|
+
return current_state
|
|
134
|
+
|
|
135
|
+
def checkroutingcondition(self,state):
|
|
136
|
+
if self.logger:
|
|
137
|
+
self.logger.info('----------------------------Deciding Node--------------------------------')
|
|
138
|
+
if state["satisfied"] and not(state["current_tool"]==None or state["current_tool"]== "None"):
|
|
139
|
+
return "continue" #continue when satisfied is true but tool is provided
|
|
140
|
+
|
|
141
|
+
elif state["satisfied"] and (state["current_tool"]==None or state["current_tool"] == "None"):
|
|
142
|
+
|
|
143
|
+
return "end" #end when satisfied and tool is none
|
|
144
|
+
|
|
145
|
+
elif not state["satisfied"] and (state["current_tool"]==None or state["current_tool"]== "None"):
|
|
146
|
+
return "reflection" #reflect when not satisfied and tool is not provided
|
|
147
|
+
elif state["current_tool"]==None or state["current_tool"]== "None":
|
|
148
|
+
return "end" #end when no tool is chosen
|
|
149
|
+
|
|
150
|
+
return "continue"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def router(self,state: State) -> State:
|
|
154
|
+
messages = state["messages"]
|
|
155
|
+
state['current_node']='router'
|
|
156
|
+
if self.node=='evaluator':
|
|
157
|
+
component_context = self.llm_evaluator.chat_history[-self.shared_memory_order:]
|
|
158
|
+
elif self.node=='reflector':
|
|
159
|
+
component_context = self.llm_reflector.chat_history[-self.shared_memory_order:]
|
|
160
|
+
elif self.node=='router':
|
|
161
|
+
component_context=[]
|
|
162
|
+
prompt = messages[0]['content'] if messages else ""
|
|
163
|
+
current_state = self.node_handler(state, self.llm_router, prompt,component_context=component_context,node='router')
|
|
164
|
+
return current_state
|
|
165
|
+
|
|
166
|
+
def execute_tool(self,state: State) -> State:
|
|
167
|
+
tool_name = state["current_tool"]
|
|
168
|
+
tool_input = state["tool_input"]
|
|
169
|
+
tool=self.tool_mapping[tool_name]
|
|
170
|
+
|
|
171
|
+
result = tool.invoke(input=tool_input)
|
|
172
|
+
|
|
173
|
+
if self.logger:
|
|
174
|
+
self.logger.warning("-------------------------------------Tool Output---------------------------------\n\n")
|
|
175
|
+
self.logger.warning(result)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if tool.return_direct:
|
|
179
|
+
state["tool_input"] = 'None'
|
|
180
|
+
state["messages"].append({"role": f"Tool: {tool_name}", "content": str(result)})
|
|
181
|
+
state['current_tool'] = 'None'
|
|
182
|
+
state['answer']= str(result)
|
|
183
|
+
state['reasoning'] = ""
|
|
184
|
+
state['satisfied'] = True
|
|
185
|
+
state['delegate_to_agent']=None
|
|
186
|
+
self.logger.info("RETURNING DIRECT")
|
|
187
|
+
return state
|
|
188
|
+
|
|
189
|
+
state['tool_output'] = str(result)
|
|
190
|
+
state["messages"].append({"role": f"Tool: {tool_name}", "tool_output": state['tool_output']})
|
|
191
|
+
return state
|
|
192
|
+
|
|
193
|
+
# Define the evaluation function
|
|
194
|
+
def evaluator(self,state: State) -> Dict[str, Any]:
|
|
195
|
+
messages = state["messages"]
|
|
196
|
+
state['current_node'] = 'evaluator'
|
|
197
|
+
tool_output = state["tool_output"]
|
|
198
|
+
if state['previous_node']=='reflector':
|
|
199
|
+
component_context=self.llm_reflector.chat_history[-self.shared_memory_order:]
|
|
200
|
+
elif state['previous_node']=='router':
|
|
201
|
+
component_context=self.llm_router.chat_history[-self.shared_memory_order:]
|
|
202
|
+
elif state['previous_node']=='planner':
|
|
203
|
+
component_context=self.llm_planner.chat_history[-self.shared_memory_order:]
|
|
204
|
+
else:
|
|
205
|
+
component_context=[]
|
|
206
|
+
|
|
207
|
+
if state['previous_node']=='planner':
|
|
208
|
+
prompt = f"\n\n<ORIGINAL QUESTION>: {messages[0]['content']}\n\n <PREVIOUS TOOL>:{state['current_tool']}\n\n<TOOL OUTPUT>: {tool_output}\n\n<PLAN>: {state['plan']}\n\n"
|
|
209
|
+
else:
|
|
210
|
+
prompt = f"\n\n<ORIGINAL QUESTION>: {messages[0]['content']}\n\n <PREVIOUS TOOL>:{state['current_tool']}\n\n<TOOL OUTPUT>: {tool_output}\n\n"
|
|
211
|
+
current_state = self.node_handler(state,self.llm_evaluator,prompt,component_context=component_context,node='evaluator')
|
|
212
|
+
|
|
213
|
+
return current_state
|
|
214
|
+
|
|
215
|
+
def reflection(self, state: State):
|
|
216
|
+
messages = state["messages"]
|
|
217
|
+
if self.logger:
|
|
218
|
+
self.logger.info("\n\n--------------------Reasoning and Reflecting-----------------------------\n\n")
|
|
219
|
+
state['current_node'] = 'reflector'
|
|
220
|
+
if state['previous_node']=='router':
|
|
221
|
+
component_context=self.llm_router.chat_history[-self.shared_memory_order:]
|
|
222
|
+
elif state['previous_node']=='evaluator':
|
|
223
|
+
component_context=self.llm_evaluator.chat_history[-self.shared_memory_order:]
|
|
224
|
+
elif state['previous_node']=='planner':
|
|
225
|
+
component_context=self.llm_planner.chat_history[-self.shared_memory_order:]
|
|
226
|
+
else:
|
|
227
|
+
component_context=[]
|
|
228
|
+
if state['previous_node']=='planner':
|
|
229
|
+
prompt = f"""<CURRENT STAGE>: REFLECTION STAGE\n\n <GOAL>: Reflect on gathered component_context, think and arrive at solution. \n\n<LAST USED TOOL>{state['current_tool']}\n\n<TOOL OUTPUT>{state['tool_output']} \n\n<QUESTION> : {messages[0]['content']}\n\n<PLAN>: {state['plan']}"""
|
|
230
|
+
else:
|
|
231
|
+
prompt = f"""<CURRENT STAGE>: REFLECTION STAGE\n\n <GOAL>: Reflect on gathered component_context, think and arrive at solution. \n\n<LAST USED TOOL>{state['current_tool']}\n\n<TOOL OUTPUT>{state['tool_output']} \n\n<QUESTION> : {messages[0]['content']}"""
|
|
232
|
+
current_state = self.node_handler(state, self.llm_reflector,prompt,component_context=component_context,node='reflector')
|
|
233
|
+
if self.logger:
|
|
234
|
+
self.logger.info("--------------------Reflection End-----------------------------")
|
|
235
|
+
return current_state
|
|
236
|
+
|
|
237
|
+
def planner(self,state: State):
|
|
238
|
+
messages = state["messages"]
|
|
239
|
+
state['current_node'] = 'planner'
|
|
240
|
+
if self.node=='evaluator':
|
|
241
|
+
component_context=self.llm_evaluator.chat_history[-self.shared_memory_order:]
|
|
242
|
+
elif self.node=='reflection':
|
|
243
|
+
component_context=self.llm_reflector.chat_history[-self.shared_memory_order:]
|
|
244
|
+
else:
|
|
245
|
+
component_context=[]
|
|
246
|
+
prompt = f"""<CURRENT STAGE>: PLANNER STAGE\n\n <GOAL>: Plan the tasks to accomplish the goal. \n\n<QUESTION> : {messages[0]['content']}"""
|
|
247
|
+
current_state = self.node_handler(state, self.llm_planner,prompt,component_context=component_context,node='planner')
|
|
248
|
+
return current_state
|
|
249
|
+
|
|
250
|
+
def agentworkflow(self):
|
|
251
|
+
workflow = StateGraph(State)
|
|
252
|
+
nodes = ["execute_tool", "evaluator", "reflection"]
|
|
253
|
+
if self.plan:
|
|
254
|
+
nodes.append("planner")
|
|
255
|
+
else:
|
|
256
|
+
nodes.append("router")
|
|
257
|
+
|
|
258
|
+
for node in nodes:
|
|
259
|
+
workflow.add_node(node, getattr(self, node))
|
|
260
|
+
|
|
261
|
+
# Dynamic edges
|
|
262
|
+
if self.plan:
|
|
263
|
+
workflow.add_edge(START, "planner")
|
|
264
|
+
workflow.add_edge("planner", "evaluator")
|
|
265
|
+
else:
|
|
266
|
+
workflow.add_edge(START, "router")
|
|
267
|
+
workflow.add_conditional_edges("router", self.checkroutingcondition, {
|
|
268
|
+
"end": END, "reflection": "reflection", "continue": "execute_tool"
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
for node in ["execute_tool", "evaluator", "reflection"]:
|
|
272
|
+
workflow.add_conditional_edges(node, self.checkroutingcondition, {
|
|
273
|
+
"end": END, "continue": "evaluator" if node == "execute_tool" else "execute_tool", "reflection": "reflection"
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
workflow.set_entry_point("planner" if self.plan else "router")
|
|
277
|
+
return workflow.compile()
|
|
278
|
+
def _sanitize_query(self, query: str) -> str:
|
|
279
|
+
"""Sanitize input query by removing/replacing problematic characters."""
|
|
280
|
+
replacements = {
|
|
281
|
+
"'": '', # Remove single quotes
|
|
282
|
+
"\\": "", # Remove backslashes
|
|
283
|
+
""": '"', # Replace smart quotes
|
|
284
|
+
""": '"', # Replace smart quotes
|
|
285
|
+
'"': "" # Remove double quotes
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
sanitized = str(query)
|
|
289
|
+
for old, new in replacements.items():
|
|
290
|
+
sanitized = sanitized.replace(old, new)
|
|
291
|
+
return sanitized
|
|
292
|
+
|
|
293
|
+
def initiate_agent(self, query: str):
|
|
294
|
+
new_query = self._sanitize_query(query)
|
|
295
|
+
if self.logger:
|
|
296
|
+
self.logger.debug(self.agent_name)
|
|
297
|
+
initial_state = State(
|
|
298
|
+
messages=[{"role": "user", "content": new_query}],
|
|
299
|
+
current_tool="",
|
|
300
|
+
tool_input="",
|
|
301
|
+
tool_output="",
|
|
302
|
+
answer="",
|
|
303
|
+
satisfied=False,
|
|
304
|
+
reasoning="",
|
|
305
|
+
delegate_to_agent=None,
|
|
306
|
+
current_node='router',
|
|
307
|
+
previous_node=None,
|
|
308
|
+
)
|
|
309
|
+
response = self.app.invoke(initial_state, {"recursion_limit": 100})
|
|
310
|
+
return response
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .generativeModels import GenerativeModel, MASGenerativeModel
|