Revert agent.py to match master

pull/855/head
Pavan Kumar 5 days ago
parent 9c58a314bb
commit 771e1e46db

@ -23,7 +23,6 @@ import yaml
from loguru import logger from loguru import logger
from pydantic import BaseModel from pydantic import BaseModel
from swarms.agents.agent_print import agent_print
from swarms.agents.ape_agent import auto_generate_prompt from swarms.agents.ape_agent import auto_generate_prompt
from swarms.artifacts.main_artifact import Artifact from swarms.artifacts.main_artifact import Artifact
from swarms.prompts.agent_system_prompts import AGENT_SYSTEM_PROMPT_3 from swarms.prompts.agent_system_prompts import AGENT_SYSTEM_PROMPT_3
@ -31,6 +30,10 @@ from swarms.prompts.multi_modal_autonomous_instruction_prompt import (
MULTI_MODAL_AUTO_AGENT_SYSTEM_PROMPT_1, MULTI_MODAL_AUTO_AGENT_SYSTEM_PROMPT_1,
) )
from swarms.prompts.tools import tool_sop_prompt from swarms.prompts.tools import tool_sop_prompt
from swarms.schemas.agent_mcp_errors import (
AgentMCPConnectionError,
AgentMCPToolError,
)
from swarms.schemas.agent_step_schemas import ManySteps, Step from swarms.schemas.agent_step_schemas import ManySteps, Step
from swarms.schemas.base_schemas import ( from swarms.schemas.base_schemas import (
AgentChatCompletionResponse, AgentChatCompletionResponse,
@ -46,14 +49,9 @@ from swarms.structs.safe_loading import (
) )
from swarms.telemetry.main import log_agent_data from swarms.telemetry.main import log_agent_data
from swarms.tools.base_tool import BaseTool from swarms.tools.base_tool import BaseTool
from swarms.tools.mcp_client import ( from swarms.tools.py_func_to_openai_func_str import (
execute_mcp_tool, convert_multiple_functions_to_openai_function_schema,
find_and_execute_tool,
list_all,
list_tools_for_multiple_urls,
) )
from swarms.tools.mcp_integration import MCPServerSseParams
from swarms.tools.tool_parse_exec import parse_and_execute_json
from swarms.utils.any_to_str import any_to_str from swarms.utils.any_to_str import any_to_str
from swarms.utils.data_to_text import data_to_text from swarms.utils.data_to_text import data_to_text
from swarms.utils.file_processing import create_file_in_folder from swarms.utils.file_processing import create_file_in_folder
@ -64,10 +62,22 @@ from swarms.utils.history_output_formatter import (
from swarms.utils.litellm_tokenizer import count_tokens from swarms.utils.litellm_tokenizer import count_tokens
from swarms.utils.litellm_wrapper import LiteLLM from swarms.utils.litellm_wrapper import LiteLLM
from swarms.utils.pdf_to_text import pdf_to_text from swarms.utils.pdf_to_text import pdf_to_text
from swarms.utils.str_to_dict import str_to_dict
from swarms.prompts.react_base_prompt import REACT_SYS_PROMPT from swarms.prompts.react_base_prompt import REACT_SYS_PROMPT
from swarms.prompts.max_loop_prompt import generate_reasoning_prompt from swarms.prompts.max_loop_prompt import generate_reasoning_prompt
from swarms.structs.agent_non_serializable import restore_non_serializable_properties from swarms.prompts.safety_prompt import SAFETY_PROMPT
from swarms.structs.ma_utils import set_random_models_for_agents
from swarms.tools.mcp_client_call import (
execute_tool_call_simple,
get_mcp_tools_sync,
)
from swarms.schemas.mcp_schemas import (
MCPConnection,
)
from swarms.utils.index import (
exists,
format_data_structure,
format_dict_to_string,
)
# Utils # Utils
@ -89,10 +99,6 @@ def agent_id():
return uuid.uuid4().hex return uuid.uuid4().hex
def exists(val):
return val is not None
# Agent output types # Agent output types
ToolUsageType = Union[BaseModel, Dict[str, Any]] ToolUsageType = Union[BaseModel, Dict[str, Any]]
@ -358,9 +364,9 @@ class Agent:
log_directory: str = None, log_directory: str = None,
tool_system_prompt: str = tool_sop_prompt(), tool_system_prompt: str = tool_sop_prompt(),
max_tokens: int = 4096, max_tokens: int = 4096,
frequency_penalty: float = 0.0, frequency_penalty: float = 0.8,
presence_penalty: float = 0.0, presence_penalty: float = 0.6,
temperature: float = 0.1, temperature: float = 0.5,
workspace_dir: str = "agent_workspace", workspace_dir: str = "agent_workspace",
timeout: Optional[int] = None, timeout: Optional[int] = None,
# short_memory: Optional[str] = None, # short_memory: Optional[str] = None,
@ -374,7 +380,6 @@ class Agent:
"%Y-%m-%d %H:%M:%S", time.localtime() "%Y-%m-%d %H:%M:%S", time.localtime()
), ),
agent_output: ManySteps = None, agent_output: ManySteps = None,
executor_workers: int = os.cpu_count(),
data_memory: Optional[Callable] = None, data_memory: Optional[Callable] = None,
load_yaml_path: str = None, load_yaml_path: str = None,
auto_generate_prompt: bool = False, auto_generate_prompt: bool = False,
@ -395,10 +400,13 @@ class Agent:
role: agent_roles = "worker", role: agent_roles = "worker",
no_print: bool = False, no_print: bool = False,
tools_list_dictionary: Optional[List[Dict[str, Any]]] = None, tools_list_dictionary: Optional[List[Dict[str, Any]]] = None,
mcp_servers: MCPServerSseParams = None, mcp_url: Optional[Union[str, MCPConnection]] = None,
mcp_url: str = None,
mcp_urls: List[str] = None, mcp_urls: List[str] = None,
react_on: bool = False, react_on: bool = False,
safety_prompt_on: bool = False,
random_models_on: bool = False,
mcp_config: Optional[MCPConnection] = None,
top_p: float = 0.90,
*args, *args,
**kwargs, **kwargs,
): ):
@ -415,6 +423,7 @@ class Agent:
self.stopping_token = stopping_token self.stopping_token = stopping_token
self.interactive = interactive self.interactive = interactive
self.dashboard = dashboard self.dashboard = dashboard
self.saved_state_path = saved_state_path
self.return_history = return_history self.return_history = return_history
self.dynamic_temperature_enabled = dynamic_temperature_enabled self.dynamic_temperature_enabled = dynamic_temperature_enabled
self.dynamic_loops = dynamic_loops self.dynamic_loops = dynamic_loops
@ -517,10 +526,13 @@ class Agent:
self.role = role self.role = role
self.no_print = no_print self.no_print = no_print
self.tools_list_dictionary = tools_list_dictionary self.tools_list_dictionary = tools_list_dictionary
self.mcp_servers = mcp_servers
self.mcp_url = mcp_url self.mcp_url = mcp_url
self.mcp_urls = mcp_urls self.mcp_urls = mcp_urls
self.react_on = react_on self.react_on = react_on
self.safety_prompt_on = safety_prompt_on
self.random_models_on = random_models_on
self.mcp_config = mcp_config
self.top_p = top_p
self._cached_llm = ( self._cached_llm = (
None # Add this line to cache the LLM instance None # Add this line to cache the LLM instance
@ -532,41 +544,58 @@ class Agent:
self.feedback = [] self.feedback = []
# self.init_handling() # self.init_handling()
# Define tasks as pairs of (function, condition)
# Each task will only run if its condition is True
self.setup_config() self.setup_config()
if exists(self.docs_folder): if exists(self.docs_folder):
self.get_docs_from_doc_folders() self.get_docs_from_doc_folders()
if exists(self.tools):
self.handle_tool_init()
if exists(self.tool_schema) or exists(self.list_base_models): if exists(self.tool_schema) or exists(self.list_base_models):
self.handle_tool_schema_ops() self.handle_tool_schema_ops()
if exists(self.sop) or exists(self.sop_list): if exists(self.sop) or exists(self.sop_list):
self.handle_sop_ops() self.handle_sop_ops()
if self.max_loops >= 2:
self.system_prompt += generate_reasoning_prompt(
self.max_loops
)
if self.react_on is True:
self.system_prompt += REACT_SYS_PROMPT
self.short_memory = self.short_memory_init()
# Run sequential operations after all concurrent tasks are done # Run sequential operations after all concurrent tasks are done
# self.agent_output = self.agent_output_model() # self.agent_output = self.agent_output_model()
log_agent_data(self.to_dict()) log_agent_data(self.to_dict())
if exists(self.tools):
self.tool_handling()
if self.llm is None: if self.llm is None:
self.llm = self.llm_handling() self.llm = self.llm_handling()
if self.mcp_url or self.mcp_servers is not None: if self.random_models_on is True:
self.add_mcp_tools_to_memory() self.model_name = set_random_models_for_agents()
if self.react_on is True: def tool_handling(self):
self.system_prompt += REACT_SYS_PROMPT
if self.max_loops >= 2: self.tool_struct = BaseTool(
self.system_prompt += generate_reasoning_prompt( tools=self.tools,
self.max_loops verbose=self.verbose,
)
# Convert all the tools into a list of dictionaries
self.tools_list_dictionary = (
convert_multiple_functions_to_openai_function_schema(
self.tools
) )
)
self.short_memory = self.short_memory_init() self.short_memory.add(
role=f"{self.agent_name}",
content=f"Tools available: {format_data_structure(self.tools_list_dictionary)}",
)
def short_memory_init(self): def short_memory_init(self):
if ( if (
@ -577,8 +606,11 @@ class Agent:
else: else:
prompt = self.system_prompt prompt = self.system_prompt
if self.safety_prompt_on is True:
prompt += SAFETY_PROMPT
# Initialize the short term memory # Initialize the short term memory
self.short_memory = Conversation( memory = Conversation(
system_prompt=prompt, system_prompt=prompt,
time_enabled=False, time_enabled=False,
user=self.user_name, user=self.user_name,
@ -586,7 +618,7 @@ class Agent:
token_count=False, token_count=False,
) )
return self.short_memory return memory
def agent_output_model(self): def agent_output_model(self):
# Many steps # Many steps
@ -616,6 +648,11 @@ class Agent:
if self.model_name is None: if self.model_name is None:
self.model_name = "gpt-4o-mini" self.model_name = "gpt-4o-mini"
if exists(self.tools) and len(self.tools) >= 2:
parallel_tool_calls = True
else:
parallel_tool_calls = False
try: try:
# Simplify initialization logic # Simplify initialization logic
common_args = { common_args = {
@ -634,10 +671,16 @@ class Agent:
**common_args, **common_args,
tools_list_dictionary=self.tools_list_dictionary, tools_list_dictionary=self.tools_list_dictionary,
tool_choice="auto", tool_choice="auto",
parallel_tool_calls=len( parallel_tool_calls=parallel_tool_calls,
self.tools_list_dictionary )
)
> 1, elif self.mcp_url is not None:
self._cached_llm = LiteLLM(
**common_args,
tools_list_dictionary=self.add_mcp_tools_to_memory(),
tool_choice="auto",
parallel_tool_calls=parallel_tool_calls,
mcp_call=True,
) )
else: else:
self._cached_llm = LiteLLM( self._cached_llm = LiteLLM(
@ -651,48 +694,6 @@ class Agent:
) )
return None return None
def handle_tool_init(self):
# Initialize the tool struct
if (
exists(self.tools)
or exists(self.list_base_models)
or exists(self.tool_schema)
):
self.tool_struct = BaseTool(
tools=self.tools,
base_models=self.list_base_models,
tool_system_prompt=self.tool_system_prompt,
)
if self.tools is not None:
logger.info(
"Tools provided make sure the functions have documentation ++ type hints, otherwise tool execution won't be reliable."
)
# Add the tool prompt to the memory
self.short_memory.add(
role="system", content=self.tool_system_prompt
)
# Log the tools
logger.info(
f"Tools provided: Accessing {len(self.tools)} tools"
)
# Transform the tools into an openai schema
# self.convert_tool_into_openai_schema()
# Transform the tools into an openai schema
tool_dict = (
self.tool_struct.convert_tool_into_openai_schema()
)
self.short_memory.add(role="system", content=tool_dict)
# Now create a function calling map for every tools
self.function_map = {
tool.__name__: tool for tool in self.tools
}
def add_mcp_tools_to_memory(self): def add_mcp_tools_to_memory(self):
""" """
Adds MCP tools to the agent's short-term memory. Adds MCP tools to the agent's short-term memory.
@ -704,110 +705,23 @@ class Agent:
Exception: If there's an error accessing the MCP tools Exception: If there's an error accessing the MCP tools
""" """
try: try:
if self.mcp_url is not None: if exists(self.mcp_url):
tools_available = list_all( tools = get_mcp_tools_sync(server_path=self.mcp_url)
self.mcp_url, output_type="json" elif exists(self.mcp_config):
) tools = get_mcp_tools_sync(connection=self.mcp_config)
self.short_memory.add( logger.info(f"Tools: {tools}")
role="Tools Available",
content=f"\n{tools_available}",
)
elif (
self.mcp_url is None
and self.mcp_urls is not None
and len(self.mcp_urls) > 1
):
tools_available = list_tools_for_multiple_urls(
urls=self.mcp_urls,
output_type="json",
)
self.short_memory.add(
role="Tools Available",
content=f"\n{tools_available}",
)
except Exception as e:
logger.error(f"Error adding MCP tools to memory: {e}")
raise e
def _single_mcp_tool_handling(self, response: any):
"""
Handles execution of a single MCP tool.
Args:
response (str): The tool response to process
Raises:
Exception: If there's an error executing the tool
"""
try:
if isinstance(response, dict):
result = response
print(type(result))
else: else:
result = str_to_dict(response) raise AgentMCPConnectionError(
print(type(result)) "mcp_url must be either a string URL or MCPConnection object"
)
output = execute_mcp_tool( self.pretty_print(
url=self.mcp_url, f"✨ [SYSTEM] Successfully integrated {len(tools)} MCP tools into agent: {self.agent_name} | Status: ONLINE | Time: {time.strftime('%H:%M:%S')}",
parameters=result, loop_count=0,
)
self.short_memory.add(
role="Tool Executor", content=str(output)
)
except Exception as e:
logger.error(f"Error in single MCP tool handling: {e}")
raise e
def _multiple_mcp_tool_handling(self, response: any):
"""
Handles execution of multiple MCP tools.
Args:
response (any): The tool response to process
Raises:
Exception: If there's an error executing the tools
"""
try:
if isinstance(response, str):
response = str_to_dict(response)
execution = find_and_execute_tool(
self.mcp_urls,
response["name"],
parameters=response,
)
self.short_memory.add(
role="Tool Executor", content=str(execution)
) )
except Exception as e:
logger.error(f"Error in multiple MCP tool handling: {e}")
raise e
def mcp_tool_handling(self, response: any):
"""
Main handler for MCP tool execution.
Args:
response (any): The tool response to process
Raises: return tools
ValueError: If no MCP URL or MCP Servers are provided except AgentMCPConnectionError as e:
Exception: If there's an error in tool handling logger.error(f"Error in MCP connection: {e}")
"""
try:
# if self.mcp_url is not None:
self._single_mcp_tool_handling(response)
# elif self.mcp_url is None and len(self.mcp_servers) > 1:
# self._multiple_mcp_tool_handling(response)
# else:
# raise ValueError("No MCP URL or MCP Servers provided")
except Exception as e:
logger.error(f"Error in mcp_tool_handling: {e}")
raise e raise e
def setup_config(self): def setup_config(self):
@ -1091,60 +1005,67 @@ class Agent:
*response_args, **kwargs *response_args, **kwargs
) )
# Convert to a str if the response is not a str if exists(self.tools_list_dictionary):
if isinstance(response, BaseModel):
response = response.model_dump()
# # Convert to a str if the response is not a str
# if self.mcp_url is None or self.tools is None:
response = self.parse_llm_output(response) response = self.parse_llm_output(response)
self.short_memory.add( self.short_memory.add(
role=self.agent_name, content=response role=self.agent_name,
content=format_dict_to_string(response),
) )
# Print # Print
self.pretty_print(response, loop_count) self.pretty_print(response, loop_count)
# Output Cleaner # # Output Cleaner
self.output_cleaner_op(response) # self.output_cleaner_op(response)
####### MCP TOOL HANDLING #######
if (
self.mcp_servers
and self.tools_list_dictionary is not None
):
self.mcp_tool_handling(response)
####### MCP TOOL HANDLING #######
# Check and execute tools # Check and execute tools
if self.tools is not None: if exists(self.tools):
out = self.parse_and_execute_tools( # out = self.parse_and_execute_tools(
response # response
# )
# self.short_memory.add(
# role="Tool Executor", content=out
# )
# if self.no_print is False:
# agent_print(
# f"{self.agent_name} - Tool Executor",
# out,
# loop_count,
# self.streaming_on,
# )
# out = self.call_llm(task=out)
# self.short_memory.add(
# role=self.agent_name, content=out
# )
# if self.no_print is False:
# agent_print(
# f"{self.agent_name} - Agent Analysis",
# out,
# loop_count,
# self.streaming_on,
# )
self.execute_tools(
response=response,
loop_count=loop_count,
) )
self.short_memory.add( if exists(self.mcp_url):
role="Tool Executor", content=out self.mcp_tool_handling(
response, loop_count
) )
if self.no_print is False:
agent_print(
f"{self.agent_name} - Tool Executor",
out,
loop_count,
self.streaming_on,
)
out = self.call_llm(task=out)
self.short_memory.add(
role=self.agent_name, content=out
)
if self.no_print is False:
agent_print(
f"{self.agent_name} - Agent Analysis",
out,
loop_count,
self.streaming_on,
)
self.sentiment_and_evaluator(response) self.sentiment_and_evaluator(response)
success = True # Mark as successful to exit the retry loop success = True # Mark as successful to exit the retry loop
@ -1362,36 +1283,36 @@ class Agent:
return output.getvalue() return output.getvalue()
def parse_and_execute_tools(self, response: str, *args, **kwargs): # def parse_and_execute_tools(self, response: str, *args, **kwargs):
max_retries = 3 # Maximum number of retries # max_retries = 3 # Maximum number of retries
retries = 0 # retries = 0
while retries < max_retries: # while retries < max_retries:
try: # try:
logger.info("Executing tool...") # logger.info("Executing tool...")
# try to Execute the tool and return a string # # try to Execute the tool and return a string
out = parse_and_execute_json( # out = parse_and_execute_json(
functions=self.tools, # functions=self.tools,
json_string=response, # json_string=response,
parse_md=True, # parse_md=True,
*args, # *args,
**kwargs, # **kwargs,
) # )
logger.info(f"Tool Output: {out}") # logger.info(f"Tool Output: {out}")
# Add the output to the memory # # Add the output to the memory
# self.short_memory.add( # # self.short_memory.add(
# role="Tool Executor", # # role="Tool Executor",
# content=out, # # content=out,
# ) # # )
return out # return out
except Exception as error: # except Exception as error:
retries += 1 # retries += 1
logger.error( # logger.error(
f"Attempt {retries}: Error executing tool: {error}" # f"Attempt {retries}: Error executing tool: {error}"
) # )
if retries == max_retries: # if retries == max_retries:
raise error # raise error
time.sleep(1) # Wait for a bit before retrying # time.sleep(1) # Wait for a bit before retrying
def add_memory(self, message: str): def add_memory(self, message: str):
"""Add a memory to the agent """Add a memory to the agent
@ -1720,9 +1641,6 @@ class Agent:
# Reinitialize any necessary runtime components # Reinitialize any necessary runtime components
self._reinitialize_after_load() self._reinitialize_after_load()
# Restore non-serializable properties (tokenizer, long_term_memory, logger_handler, agent_output, executor)
self.restore_non_serializable_properties()
if self.verbose: if self.verbose:
self._log_loaded_state_info(resolved_path) self._log_loaded_state_info(resolved_path)
@ -2709,7 +2627,7 @@ class Agent:
f"Agent Name {self.agent_name} [Max Loops: {loop_count} ]", f"Agent Name {self.agent_name} [Max Loops: {loop_count} ]",
) )
def parse_llm_output(self, response: Any) -> str: def parse_llm_output(self, response: Any):
"""Parse and standardize the output from the LLM. """Parse and standardize the output from the LLM.
Args: Args:
@ -2722,7 +2640,7 @@ class Agent:
ValueError: If the response format is unexpected and can't be handled ValueError: If the response format is unexpected and can't be handled
""" """
try: try:
# Handle dictionary responses
if isinstance(response, dict): if isinstance(response, dict):
if "choices" in response: if "choices" in response:
return response["choices"][0]["message"][ return response["choices"][0]["message"][
@ -2732,17 +2650,23 @@ class Agent:
response response
) # Convert other dicts to string ) # Convert other dicts to string
# Handle string responses elif isinstance(response, BaseModel):
elif isinstance(response, str): out = response.model_dump()
return response
# Handle list responses (from check_llm_outputs) # Handle List[BaseModel] responses
elif isinstance(response, list): elif (
return "\n".join(response) isinstance(response, list)
and response
and isinstance(response[0], BaseModel)
):
return [item.model_dump() for item in response]
# Handle any other type by converting to string elif isinstance(response, list):
out = format_data_structure(response)
else: else:
return str(response) out = str(response)
return out
except Exception as e: except Exception as e:
logger.error(f"Error parsing LLM output: {e}") logger.error(f"Error parsing LLM output: {e}")
@ -2780,24 +2704,123 @@ class Agent:
content=response, content=response,
) )
def restore_non_serializable_properties(self): def mcp_tool_handling(
""" self, response: any, current_loop: Optional[int] = 0
Restore non-serializable properties for the Agent instance after loading. ):
This should be called after loading agent state from disk. try:
"""
restore_non_serializable_properties(self) if exists(self.mcp_url):
# Execute the tool call
tool_response = asyncio.run(
execute_tool_call_simple(
response=response,
server_path=self.mcp_url,
)
)
elif exists(self.mcp_config):
# Execute the tool call
tool_response = asyncio.run(
execute_tool_call_simple(
response=response,
connection=self.mcp_config,
)
)
else:
raise AgentMCPConnectionError(
"mcp_url must be either a string URL or MCPConnection object"
)
# Custom serialization for non-serializable properties # Get the text content from the tool response
def __getstate__(self): text_content = (
state = self.__dict__.copy() tool_response.content[0].text
# Remove non-serializable properties if tool_response.content
for prop in ["tokenizer", "long_term_memory", "logger_handler", "agent_output", "executor"]: else str(tool_response)
if prop in state: )
state[prop] = None # Or a serializable placeholder if needed
return state # Add to the memory
self.short_memory.add(
role="Tool Executor",
content=text_content,
)
# Create a temporary LLM instance without tools for the follow-up call
try:
temp_llm = LiteLLM(
model_name=self.model_name,
temperature=self.temperature,
max_tokens=self.max_tokens,
system_prompt=self.system_prompt,
stream=self.streaming_on,
)
summary = temp_llm.run(
task=self.short_memory.get_str()
)
except Exception as e:
logger.error(
f"Error calling LLM after MCP tool execution: {e}"
)
# Fallback: provide a default summary
summary = "I successfully executed the MCP tool and retrieved the information above."
self.pretty_print(summary, loop_count=current_loop)
# Add to the memory
self.short_memory.add(
role=self.agent_name, content=summary
)
except AgentMCPToolError as e:
logger.error(f"Error in MCP tool: {e}")
raise e
def execute_tools(self, response: any, loop_count: int):
output = (
self.tool_struct.execute_function_calls_from_api_response(
response
)
)
self.short_memory.add(
role="Tool Executor",
content=format_data_structure(output),
)
self.pretty_print(
f"{format_data_structure(output)}",
loop_count,
)
# Now run the LLM again without tools - create a temporary LLM instance
# instead of modifying the cached one
# Create a temporary LLM instance without tools for the follow-up call
temp_llm = LiteLLM(
model_name=self.model_name,
temperature=self.temperature,
max_tokens=self.max_tokens,
system_prompt=self.system_prompt,
stream=self.streaming_on,
tools_list_dictionary=None,
parallel_tool_calls=False,
)
tool_response = temp_llm.run(
f"""
Please analyze and summarize the following tool execution output in a clear and concise way.
Focus on the key information and insights that would be most relevant to the user's original request.
If there are any errors or issues, highlight them prominently.
Tool Output:
{output}
"""
)
self.short_memory.add(
role=self.agent_name,
content=tool_response,
)
def __setstate__(self, state): self.pretty_print(
self.__dict__.update(state) f"{tool_response}",
# Restore non-serializable properties after loading loop_count,
if hasattr(self, 'restore_non_serializable_properties'): )
self.restore_non_serializable_properties()
Loading…
Cancel
Save