2025-03-14 13:04:28 +05:30
import logging
import json
2025-03-21 17:17:15 +05:30
import aiohttp
2025-03-26 21:05:49 +05:30
import jwt
import hashlib
2025-03-14 13:04:28 +05:30
# Import helper functions needed for get_agents
from . helpers . access import (
2025-03-21 15:45:22 +05:30
get_tool_config_by_name ,
2025-03-14 13:04:28 +05:30
get_tool_config_by_type
)
from . helpers . instructions import (
2025-03-21 15:45:22 +05:30
add_rag_instructions_to_agent
2025-03-14 13:04:28 +05:30
)
2025-04-01 12:53:19 +05:30
from agents import Agent as NewAgent , Runner , FunctionTool , RunContextWrapper , ModelSettings
2025-03-14 13:04:28 +05:30
# Add import for OpenAI functionality
2025-03-21 15:45:22 +05:30
from src . utils . common import common_logger as logger , generate_openai_output
2025-03-20 21:55:29 +05:30
from typing import Any
2025-03-24 16:10:43 +05:30
from dataclasses import asdict
2025-03-21 17:17:15 +05:30
import asyncio
from mcp import ClientSession
from mcp . client . sse import sse_client
2025-03-14 13:04:28 +05:30
2025-03-21 15:45:22 +05:30
from pydantic import BaseModel
from typing import List , Optional , Dict
2025-03-24 16:10:43 +05:30
from . tool_calling import call_rag_tool
2025-03-26 21:05:49 +05:30
from pymongo import MongoClient
import os
MONGO_URI = os . environ . get ( " MONGODB_URI " , " mongodb://localhost:27017/rowboat " ) . strip ( )
mongo_client = MongoClient ( MONGO_URI )
db = mongo_client [ " rowboat " ]
2025-03-14 13:04:28 +05:30
2025-03-21 15:45:22 +05:30
class NewResponse ( BaseModel ) :
messages : List [ Dict ]
agent : Optional [ Any ] = None
tokens_used : Optional [ dict ] = { }
error_msg : Optional [ str ] = " "
2025-03-14 13:04:28 +05:30
2025-03-26 23:08:23 +05:30
async def mock_tool ( tool_name : str , args : str , description : str , mock_instructions : str ) - > str :
2025-03-21 17:17:15 +05:30
"""
Handles tool execution by either using mock instructions or generating a response .
Args :
tool_name : The name of the tool
args : The arguments passed to the tool
tool_config : The configuration of the tool
Returns :
The response from the tool
"""
print ( f " Mock tool called for: { tool_name } " )
2025-03-14 13:04:28 +05:30
messages = [
2025-03-24 16:10:43 +05:30
{ " role " : " system " , " content " : f " You are simulating the execution of a tool called ' { tool_name } ' .Here is the description of the tool: { description } . Here are the instructions for the mock tool: { mock_instructions } . Generate a realistic response as if the tool was actually executed with the given parameters. " } ,
2025-03-20 21:55:29 +05:30
{ " role " : " user " , " content " : f " Generate a realistic response for the tool ' { tool_name } ' with these parameters: { args } . The response should be concise and focused on what the tool would actually return. " }
2025-03-14 13:04:28 +05:30
]
2025-03-21 17:17:15 +05:30
print ( f " Generating simulated response for tool: { tool_name } " )
2025-03-14 13:04:28 +05:30
response_content = generate_openai_output ( messages , output_type = ' text ' , model = " gpt-4o " )
2025-03-21 17:17:15 +05:30
return response_content
2025-03-26 21:05:49 +05:30
async def call_webhook ( tool_name : str , args : str , webhook_url : str , signing_secret : str ) - > str :
2025-03-21 17:17:15 +05:30
"""
Calls the webhook with the given tool name and arguments .
Args :
tool_name ( str ) : The name of the tool to call .
args ( str ) : The arguments for the tool as a JSON string .
Returns :
str : The response from the webhook , or an error message if the call fails .
"""
content_dict = {
" toolCall " : {
" function " : {
" name " : tool_name ,
2025-03-24 16:10:43 +05:30
" arguments " : args
2025-03-21 17:17:15 +05:30
}
}
}
request_body = {
" content " : json . dumps ( content_dict )
}
2025-03-26 21:05:49 +05:30
# Prepare headers
headers = { }
if signing_secret :
content_str = request_body [ " content " ]
body_hash = hashlib . sha256 ( content_str . encode ( ' utf-8 ' ) ) . hexdigest ( )
payload = { " bodyHash " : body_hash }
signature_jwt = jwt . encode ( payload , signing_secret , algorithm = " HS256 " )
headers [ " X-Signature-Jwt " ] = signature_jwt
2025-03-21 17:17:15 +05:30
try :
async with aiohttp . ClientSession ( ) as session :
2025-03-26 21:05:49 +05:30
async with session . post ( webhook_url , json = request_body , headers = headers ) as response :
2025-03-21 17:17:15 +05:30
if response . status == 200 :
response_json = await response . json ( )
return response_json . get ( " result " , " " )
else :
error_msg = await response . text ( )
print ( f " Webhook error: { error_msg } " )
return f " Error: { error_msg } "
except Exception as e :
print ( f " Exception in call_webhook: { str ( e ) } " )
return f " Error: Failed to call webhook - { str ( e ) } "
2025-03-24 16:10:43 +05:30
async def call_mcp ( tool_name : str , args : str , mcp_server_url : str ) - > str :
2025-03-21 17:17:15 +05:30
"""
Calls the MCP with the given tool name and arguments .
"""
2025-03-24 16:10:43 +05:30
async with sse_client ( url = mcp_server_url ) as streams :
2025-03-21 17:17:15 +05:30
async with ClientSession ( * streams ) as session :
await session . initialize ( )
2025-03-24 16:10:43 +05:30
jargs = json . loads ( args )
response = await session . call_tool ( tool_name , arguments = jargs )
json_output = json . dumps ( [ item . __dict__ for item in response . content ] , indent = 2 )
2025-03-21 17:17:15 +05:30
2025-03-24 16:10:43 +05:30
return json_output
2025-03-21 17:17:15 +05:30
2025-03-24 16:10:43 +05:30
async def catch_all ( ctx : RunContextWrapper [ Any ] , args : str , tool_name : str , tool_config : dict , complete_request : dict ) - > str :
2025-03-21 17:17:15 +05:30
"""
2025-03-23 11:36:47 +05:30
Handles all tool calls by dispatching to appropriate functions .
2025-03-21 17:17:15 +05:30
"""
print ( f " Catch all called for tool: { tool_name } " )
print ( f " Args: { args } " )
print ( f " Tool config: { tool_config } " )
2025-03-24 16:10:43 +05:30
2025-03-23 11:36:47 +05:30
# Create event loop for async operations
try :
loop = asyncio . get_event_loop ( )
except RuntimeError :
loop = asyncio . new_event_loop ( )
asyncio . set_event_loop ( loop )
2025-03-21 17:17:15 +05:30
response_content = None
2025-03-26 23:08:23 +05:30
if tool_config . get ( " mockTool " , False ) or complete_request . get ( " testProfile " , { } ) . get ( " mockTools " , False ) :
2025-03-24 16:10:43 +05:30
# Call mock_tool to handle the response (it will decide whether to use mock instructions or generate a response)
2025-03-26 23:08:23 +05:30
if complete_request . get ( " testProfile " , { } ) . get ( " mockPrompt " , " " ) :
response_content = await mock_tool ( tool_name , args , tool_config . get ( " description " , " " ) , complete_request . get ( " testProfile " , { } ) . get ( " mockPrompt " , " " ) )
else :
response_content = await mock_tool ( tool_name , args , tool_config . get ( " description " , " " ) , tool_config . get ( " mockInstructions " , " " ) )
2025-03-24 16:10:43 +05:30
print ( response_content )
2025-03-21 17:17:15 +05:30
elif tool_config . get ( " isMcp " , False ) :
2025-03-24 16:10:43 +05:30
mcp_server_name = tool_config . get ( " mcpServerName " , " " )
mcp_servers = complete_request . get ( " mcpServers " , { } )
mcp_server_url = next ( ( server . get ( " url " , " " ) for server in mcp_servers if server . get ( " name " ) == mcp_server_name ) , " " )
response_content = await call_mcp ( tool_name , args , mcp_server_url )
2025-03-21 17:17:15 +05:30
else :
2025-03-26 21:05:49 +05:30
collection = db [ " projects " ]
doc = collection . find_one ( { " _id " : complete_request . get ( " projectId " , " " ) } )
signing_secret = doc . get ( " secret " , " " )
2025-03-24 16:10:43 +05:30
webhook_url = complete_request . get ( " toolWebhookUrl " , " " )
2025-03-26 21:05:49 +05:30
response_content = await call_webhook ( tool_name , args , webhook_url , signing_secret )
2025-03-21 17:17:15 +05:30
return response_content
2025-03-14 13:04:28 +05:30
2025-03-24 16:10:43 +05:30
def get_rag_tool ( config : dict , complete_request : dict ) - > FunctionTool :
"""
Creates a RAG tool based on the provided configuration .
"""
project_id = complete_request . get ( " projectId " , " " )
if config . get ( " ragDataSources " , None ) :
print ( " getArticleInfo " )
params = {
" type " : " object " ,
" properties " : {
" query " : {
" type " : " string " ,
" description " : " The query to search for "
}
} ,
" additionalProperties " : False ,
" required " : [
" query "
]
}
tool = FunctionTool (
name = " getArticleInfo " ,
description = " Get information about an article " ,
params_json_schema = params ,
on_invoke_tool = lambda ctx , args : call_rag_tool ( project_id , json . loads ( args ) [ ' query ' ] , config . get ( " ragDataSources " , [ ] ) , " chunks " , 3 )
)
return tool
else :
return None
def get_agents ( agent_configs , tool_configs , complete_request ) :
2025-03-14 13:04:28 +05:30
"""
Creates and initializes Agent objects based on their configurations and connections .
"""
if not isinstance ( agent_configs , list ) :
raise ValueError ( " Agents config is not a list in get_agents " )
if not isinstance ( tool_configs , list ) :
raise ValueError ( " Tools config is not a list in get_agents " )
new_agents = [ ]
new_agent_to_children = { }
new_agent_name_to_index = { }
# Create Agent objects from config
for agent_config in agent_configs :
logger . debug ( f " Processing config for agent: { agent_config [ ' name ' ] } " )
2025-03-25 15:37:51 +05:30
print ( " = " * 100 )
2025-03-21 15:45:22 +05:30
print ( f " Processing config for agent: { agent_config [ ' name ' ] } " )
2025-03-14 13:04:28 +05:30
# If hasRagSources, append the RAG tool to the agent's tools
if agent_config . get ( " hasRagSources " , False ) :
rag_tool_name = get_tool_config_by_type ( tool_configs , " rag " ) . get ( " name " , " " )
agent_config [ " tools " ] . append ( rag_tool_name )
agent_config = add_rag_instructions_to_agent ( agent_config , rag_tool_name )
# Prepare tool lists for this agent
external_tools = [ ]
logger . debug ( f " Agent { agent_config [ ' name ' ] } has { len ( agent_config [ ' tools ' ] ) } configured tools " )
2025-03-21 15:45:22 +05:30
print ( f " Agent { agent_config [ ' name ' ] } has { len ( agent_config [ ' tools ' ] ) } configured tools " )
2025-03-14 13:04:28 +05:30
new_tools = [ ]
2025-03-24 16:10:43 +05:30
rag_tool = get_rag_tool ( agent_config , complete_request )
if rag_tool :
new_tools . append ( rag_tool )
logger . debug ( f " Added rag tool to agent { agent_config [ ' name ' ] } " )
print ( f " Added rag tool to agent { agent_config [ ' name ' ] } " )
2025-03-14 13:04:28 +05:30
for tool_name in agent_config [ " tools " ] :
2025-03-24 16:10:43 +05:30
2025-03-14 13:04:28 +05:30
tool_config = get_tool_config_by_name ( tool_configs , tool_name )
2025-03-20 21:55:29 +05:30
2025-03-14 13:04:28 +05:30
if tool_config :
external_tools . append ( {
" type " : " function " ,
" function " : tool_config
} )
2025-03-20 21:55:29 +05:30
tool = FunctionTool (
name = tool_name ,
description = tool_config [ " description " ] ,
params_json_schema = tool_config [ " parameters " ] ,
2025-04-08 22:50:43 +05:30
strict_json_schema = False ,
2025-03-24 16:10:43 +05:30
on_invoke_tool = lambda ctx , args , _tool_name = tool_name , _tool_config = tool_config , _complete_request = complete_request :
catch_all ( ctx , args , _tool_name , _tool_config , _complete_request )
2025-03-20 21:55:29 +05:30
)
new_tools . append ( tool )
2025-03-14 13:04:28 +05:30
logger . debug ( f " Added tool { tool_name } to agent { agent_config [ ' name ' ] } " )
2025-03-21 15:45:22 +05:30
print ( f " Added tool { tool_name } to agent { agent_config [ ' name ' ] } " )
2025-03-14 13:04:28 +05:30
else :
logger . warning ( f " Tool { tool_name } not found in tool_configs " )
2025-03-21 15:45:22 +05:30
print ( f " WARNING: Tool { tool_name } not found in tool_configs " )
2025-03-14 13:04:28 +05:30
# Create the agent object
logger . debug ( f " Creating Agent object for { agent_config [ ' name ' ] } " )
2025-03-21 15:45:22 +05:30
print ( f " Creating Agent object for { agent_config [ ' name ' ] } " )
2025-03-14 13:04:28 +05:30
try :
new_agent = NewAgent (
name = agent_config [ " name " ] ,
instructions = agent_config [ " instructions " ] ,
handoff_description = agent_config [ " description " ] ,
tools = new_tools ,
2025-04-01 12:53:19 +05:30
model = agent_config [ " model " ] ,
model_settings = ModelSettings ( temperature = 0.0 )
2025-03-14 13:04:28 +05:30
)
2025-04-01 12:53:19 +05:30
2025-03-14 13:04:28 +05:30
new_agent_to_children [ agent_config [ " name " ] ] = agent_config . get ( " connectedAgents " , [ ] )
new_agent_name_to_index [ agent_config [ " name " ] ] = len ( new_agents )
new_agents . append ( new_agent )
logger . debug ( f " Successfully created agent: { agent_config [ ' name ' ] } " )
2025-03-21 15:45:22 +05:30
print ( f " Successfully created agent: { agent_config [ ' name ' ] } " )
2025-03-14 13:04:28 +05:30
except Exception as e :
logger . error ( f " Failed to create agent { agent_config [ ' name ' ] } : { str ( e ) } " )
2025-03-21 15:45:22 +05:30
print ( f " ERROR: Failed to create agent { agent_config [ ' name ' ] } : { str ( e ) } " )
2025-03-14 13:04:28 +05:30
raise
for new_agent in new_agents :
# Initialize the handoffs attribute if it doesn't exist
if not hasattr ( new_agent , ' handoffs ' ) :
new_agent . handoffs = [ ]
# Look up the agent's children from the old agent and create a list called handoffs in new_agent with pointers to the children in new_agents
new_agent . handoffs = [ new_agents [ new_agent_name_to_index [ child ] ] for child in new_agent_to_children [ new_agent . name ] ]
2025-03-25 15:37:51 +05:30
print ( " Returning created agents " )
print ( " = " * 100 )
2025-03-21 15:45:22 +05:30
return new_agents
2025-03-14 13:04:28 +05:30
def create_response ( messages = None , tokens_used = None , agent = None , error_msg = ' ' ) :
"""
Create a Response object with the given parameters .
Args :
messages : List of messages
tokens_used : Dictionary tracking token usage
agent : The agent that generated the response
error_msg : Error message if any
Returns :
Response object
"""
if messages is None :
messages = [ ]
if tokens_used is None :
tokens_used = { }
2025-03-21 15:45:22 +05:30
return NewResponse (
2025-03-14 13:04:28 +05:30
messages = messages ,
agent = agent ,
2025-03-21 15:45:22 +05:30
tokens_used = tokens_used ,
2025-03-14 13:04:28 +05:30
error_msg = error_msg
)
2025-03-28 00:51:53 +05:30
async def run (
2025-03-14 13:04:28 +05:30
agent ,
messages ,
external_tools = None ,
tokens_used = None
) :
"""
Wrapper function for initializing and running the Swarm client .
"""
logger . info ( f " Initializing Swarm client for agent: { agent . name } " )
2025-03-21 15:45:22 +05:30
print ( f " Initializing Swarm client for agent: { agent . name } " )
2025-03-14 13:04:28 +05:30
# Initialize default parameters
if external_tools is None :
external_tools = [ ]
if tokens_used is None :
tokens_used = { }
# Format messages to ensure they're compatible with the OpenAI API
formatted_messages = [ ]
for msg in messages :
if isinstance ( msg , dict ) and " content " in msg :
formatted_msg = {
" role " : msg . get ( " role " , " user " ) ,
" content " : msg [ " content " ]
}
formatted_messages . append ( formatted_msg )
else :
formatted_messages . append ( {
" role " : " user " ,
" content " : str ( msg )
} )
2025-03-28 00:51:53 +05:30
logger . info ( " Beginning Swarm run " )
print ( " Beginning Swarm run " )
2025-03-24 16:10:43 +05:30
2025-03-23 11:36:47 +05:30
try :
2025-03-28 00:51:53 +05:30
response = await Runner . run ( agent , formatted_messages )
2025-03-23 11:36:47 +05:30
except Exception as e :
logger . error ( f " Error during run: { str ( e ) } " )
print ( f " Error during run: { str ( e ) } " )
raise
2025-03-14 13:04:28 +05:30
logger . info ( f " Completed Swarm run for agent: { agent . name } " )
2025-03-21 15:45:22 +05:30
print ( f " Completed Swarm run for agent: { agent . name } " )
2025-03-25 15:37:51 +05:30
return response
async def run_streamed (
agent ,
messages ,
external_tools = None ,
tokens_used = None
) :
"""
Wrapper function for initializing and running the Swarm client in streaming mode .
"""
logger . info ( f " Initializing Swarm streaming client for agent: { agent . name } " )
print ( f " Initializing Swarm streaming client for agent: { agent . name } " )
# Initialize default parameters
if external_tools is None :
external_tools = [ ]
if tokens_used is None :
tokens_used = { }
# Format messages to ensure they're compatible with the OpenAI API
formatted_messages = [ ]
for msg in messages :
if isinstance ( msg , dict ) and " content " in msg :
formatted_msg = {
" role " : msg . get ( " role " , " user " ) ,
" content " : msg [ " content " ]
}
formatted_messages . append ( formatted_msg )
else :
formatted_messages . append ( {
" role " : " user " ,
" content " : str ( msg )
} )
logger . info ( " Beginning Swarm streaming run " )
print ( " Beginning Swarm streaming run " )
2025-03-26 21:05:49 +05:30
2025-03-25 15:37:51 +05:30
try :
# Use the Runner.run_streamed method
stream_result = Runner . run_streamed ( agent , formatted_messages )
return stream_result
except Exception as e :
logger . error ( f " Error during streaming run: { str ( e ) } " )
print ( f " Error during streaming run: { str ( e ) } " )
raise