diff --git a/apps/rowboat_agents/src/graph/core.py b/apps/rowboat_agents/src/graph/core.py index c9a08538..b9f897bc 100644 --- a/apps/rowboat_agents/src/graph/core.py +++ b/apps/rowboat_agents/src/graph/core.py @@ -9,12 +9,12 @@ from .helpers.access import ( get_external_tools, get_prompt_by_type ) - +from .helpers.library_tools import handle_web_search_event from .helpers.control import get_last_agent_name from .swarm_wrapper import run_streamed as swarm_run_streamed, get_agents from src.utils.common import common_logger as logger -from .types import PromptType +from .types import PromptType, VisibilityType, ControlType # Create a dedicated logger for swarm wrapper logger.setLevel(logging.INFO) @@ -56,6 +56,266 @@ def set_sys_message(messages): return messages +def handle_web_search_event(event, current_agent): + """ + Helper function to handle all web search related events. + Returns a list of messages to yield. + """ + messages = [] + + # Handle raw response web search + if event.type == "raw_response_event": + if hasattr(event, 'data') and hasattr(event.data, 'raw_item'): + raw_item = event.data.raw_item + if (hasattr(raw_item, 'type') and raw_item.type == 'web_search_call') or ( + isinstance(raw_item, dict) and raw_item.get('type') == 'web_search_call' + ): + call_id = None + if hasattr(raw_item, 'id'): + call_id = raw_item.id + elif isinstance(raw_item, dict) and 'id' in raw_item: + call_id = raw_item['id'] + else: + call_id = str(uuid.uuid4()) + + status = 'unknown' + if hasattr(raw_item, 'status'): + status = raw_item.status + elif isinstance(raw_item, dict) and 'status' in raw_item: + status = raw_item['status'] + + messages.append({ + 'content': None, + 'role': 'assistant', + 'sender': current_agent.name if current_agent else None, + 'tool_calls': [{ + 'function': { + 'name': 'web_search', + 'arguments': json.dumps({ + 'search_id': call_id, + 'status': status + }) + }, + 'id': call_id, + 'type': 'function' + }], + 'tool_call_id': None, + 'tool_name': None, + 'response_type': 'internal' + }) + + # Handle run item web search events + elif event.type == "run_item_stream_event": + if event.item.type == "tool_call_item": + if hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_call': + call_id = event.item.raw_item.id if hasattr(event.item.raw_item, 'id') else str(uuid.uuid4()) + messages.append({ + 'content': None, + 'role': 'assistant', + 'sender': current_agent.name if current_agent else None, + 'tool_calls': [{ + 'function': { + 'name': 'web_search', + 'arguments': json.dumps({ + 'search_id': call_id + }) + }, + 'id': call_id, + 'type': 'function' + }], + 'tool_call_id': None, + 'tool_name': None, + 'response_type': 'internal' + }) + messages.append({ + 'content': "Web search done", + 'role': 'tool', + 'sender': None, + 'tool_calls': None, + 'tool_call_id': call_id, + 'tool_name': 'web_search', + 'response_type': 'internal' + }) + + elif event.item.type == "tool_call_output_item": + if isinstance(event.item.raw_item, dict) and event.item.raw_item.get('type') == 'web_search_results': + call_id = event.item.raw_item.get('search_id', event.item.raw_item.get('id', str(uuid.uuid4()))) + messages.append({ + 'content': str(event.item.output), + 'role': 'tool', + 'sender': None, + 'tool_calls': None, + 'tool_call_id': call_id, + 'tool_name': 'web_search', + 'response_type': 'internal' + }) + + elif event.item.type == "web_search_call_item" or ( + hasattr(event.item, 'raw_item') and + hasattr(event.item.raw_item, 'type') and + event.item.raw_item.type == 'web_search_call' + ): + call_id = None + if hasattr(event.item.raw_item, 'id'): + call_id = event.item.raw_item.id + + messages.append({ + 'content': None, + 'role': 'assistant', + 'sender': current_agent.name if current_agent else None, + 'tool_calls': [{ + 'function': { + 'name': 'web_search', + 'arguments': json.dumps({ + 'search_id': call_id + }) + }, + 'id': call_id or str(uuid.uuid4()), + 'type': 'function' + }], + 'tool_call_id': None, + 'tool_name': None, + 'response_type': 'internal' + }) + + elif event.item.type == "web_search_results_item" or ( + hasattr(event.item, 'raw_item') and ( + (hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_results') or + (isinstance(event.item.raw_item, dict) and event.item.raw_item.get('type') == 'web_search_results') + ) + ): + raw_item = event.item.raw_item + call_id = None + + if hasattr(raw_item, 'search_id'): + call_id = raw_item.search_id + elif isinstance(raw_item, dict) and 'search_id' in raw_item: + call_id = raw_item['search_id'] + elif hasattr(raw_item, 'id'): + call_id = raw_item.id + elif isinstance(raw_item, dict) and 'id' in raw_item: + call_id = raw_item['id'] + else: + call_id = str(uuid.uuid4()) + + results = {} + if hasattr(event.item, 'output'): + results = event.item.output + elif hasattr(raw_item, 'results'): + results = raw_item.results + elif isinstance(raw_item, dict) and 'results' in raw_item: + results = raw_item['results'] + + results_str = "" + try: + results_str = json.dumps(results) if results else "" + except Exception as e: + print(f"Error serializing results: {str(e)}") + results_str = str(results) + + messages.append({ + 'content': results_str, + 'role': 'tool', + 'sender': None, + 'tool_calls': None, + 'tool_call_id': call_id, + 'tool_name': 'web_search', + 'response_type': 'internal' + }) + + return messages + +""" Example workflow config +{ + "agents": [ + { + "name": "Credit Card Hub", + "type": "conversation", + "description": "Hub agent to route credit card related queries to the appropriate specialized agent.", + "instructions": "## šŸ§‘ā€šŸ’¼ Role:\nYou are the hub for all credit card related queries. Your job is to understand the user's intent and route their query to the correct specialized agent.\n\n---\n## āš™ļø Steps to Follow:\n1. Greet the user and ask how you can help with their credit card needs.\n2. If the user asks about card recommendations, call [@agent:Card Recommendation](#mention).\n3. If the user asks about card benefits or rewards, call [@agent:Card Benefits and Rewards](#mention).\n4. If the user asks about the application process, call [@agent:Card Application Process](#mention).\n5. If the user asks for general credit card advice, call [@agent:General Credit Card Advice](#mention).\n6. If the query is out of scope, politely inform the user.\n\n---\n## šŸŽÆ Scope:\nāœ… In Scope:\n- Routing credit card related queries to the correct agent.\n\nāŒ Out of Scope:\n- Answering credit card questions directly.\n- Handling non-credit card queries.\n\n---\n## šŸ“‹ Guidelines:\nāœ”ļø Dos:\n- Be professional and friendly.\n- Route queries efficiently.\n\n🚫 Don'ts:\n- Do not answer questions directly.\n- Do not provide user-facing text such as 'I will connect you now...' when calling another agent.", + "model": "claude-3-7-sonnet-latest", + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "controlType": "retain", + "examples": "- **User** : Can you recommend a credit card for travel?\n - **Agent actions**: Call [@agent:Card Recommendation](#mention)\n\n- **User** : What are the benefits of the Platinum card?\n - **Agent actions**: Call [@agent:Card Benefits and Rewards](#mention)\n\n- **User** : How do I apply for a credit card?\n - **Agent actions**: Call [@agent:Card Application Process](#mention)\n\n- **User** : Should I get a credit card or a debit card?\n - **Agent actions**: Call [@agent:General Credit Card Advice](#mention)\n\n- **User** : Hi!\n - **Agent response**: Hello! How can I help you with your credit card needs today?" + }, + { + "name": "Card Recommendation", + "type": "conversation", + "description": "Provides personalized credit card recommendations based on user needs.", + "disabled": false, + "instructions": "## šŸ§‘ā€šŸ’¼ Role:\nYou help users find the best credit card for their needs.\n\n---\n## āš™ļø Steps to Follow:\n1. Ask the user about their preferences (e.g., travel, cashback, low interest, rewards).\n2. Use [@tool:web_search](#mention) to find suitable credit card options.\n3. Present 2-3 card recommendations with a brief explanation for each.\n4. If the user asks about benefits or application process, call the relevant agent.\n5. If the query is out of scope, call [@agent:Credit Card Hub](#mention).\n\n---\n## šŸŽÆ Scope:\nāœ… In Scope:\n- Recommending credit cards based on user needs.\n\nāŒ Out of Scope:\n- Detailed card benefits (refer to Card Benefits and Rewards agent).\n- Application process (refer to Card Application Process agent).\n\n---\n## šŸ“‹ Guidelines:\nāœ”ļø Dos:\n- Be professional and friendly.\n- Tailor recommendations to user preferences.\n\n🚫 Don'ts:\n- Recommend cards without understanding user needs.", + "model": "claude-3-7-sonnet-latest", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "controlType": "retain", + "examples": "- **User** : I want a card with good travel rewards.\n - **Agent response**: Great! Are you looking for international travel benefits or domestic? Any airline preferences?\n\n- **User** : I prefer cashback cards.\n - **Agent response**: Understood. Do you spend more on groceries, fuel, or online shopping?\n\n- **User** : I want a card with no annual fee.\n - **Agent response**: Thanks for sharing. I'll look up the best no-annual-fee cards for you.\n - **Agent actions**: Call [@tool:web_search](#mention)\n\n- **User** : What are the benefits of the Platinum card?\n - **Agent actions**: Call [@agent:Card Benefits and Rewards](#mention)\n\n- **User** : How do I apply for a card?\n - **Agent actions**: Call [@agent:Card Application Process](#mention)" + }, + { + "name": "Card Benefits and Rewards", + "type": "conversation", + "description": "Provides detailed information about credit card benefits and rewards.", + "disabled": false, + "instructions": "## šŸ§‘ā€šŸ’¼ Role:\nYou answer questions about credit card benefits and rewards.\n\n---\n## āš™ļø Steps to Follow:\n1. Ask the user which card or type of benefit they are interested in.\n2. Use [@tool:web_search](#mention) to find up-to-date information.\n3. Present the benefits and rewards in a clear, concise manner.\n4. If the user asks about recommendations or application process, call the relevant agent.\n5. If the query is out of scope, call [@agent:Credit Card Hub](#mention).\n\n---\n## šŸŽÆ Scope:\nāœ… In Scope:\n- Explaining card benefits and rewards.\n\nāŒ Out of Scope:\n- Recommending cards (refer to Card Recommendation agent).\n- Application process (refer to Card Application Process agent).\n\n---\n## šŸ“‹ Guidelines:\nāœ”ļø Dos:\n- Be accurate and clear.\n- Use up-to-date information.\n\n🚫 Don'ts:\n- Speculate about benefits without verification.", + "model": "claude-3-7-sonnet-latest", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "controlType": "retain", + "examples": "- **User** : What are the benefits of the Gold card?\n - **Agent response**: Let me check the latest benefits for the Gold card.\n - **Agent actions**: Call [@tool:web_search](#mention)\n\n- **User** : Does this card offer airport lounge access?\n - **Agent response**: I'll find out if this card includes airport lounge access.\n - **Agent actions**: Call [@tool:web_search](#mention)\n\n- **User** : Which card has the best rewards for shopping?\n - **Agent actions**: Call [@agent:Card Recommendation](#mention)\n\n- **User** : How do I apply for the Platinum card?\n - **Agent actions**: Call [@agent:Card Application Process](#mention)\n\n- **User** : Can you recommend a card for fuel rewards?\n - **Agent actions**: Call [@agent:Card Recommendation](#mention)" + }, + { + "name": "Card Application Process", + "type": "conversation", + "description": "Explains the steps and requirements for applying for a credit card.", + "disabled": false, + "instructions": "## šŸ§‘ā€šŸ’¼ Role:\nYou guide users through the credit card application process.\n\n---\n## āš™ļø Steps to Follow:\n1. Ask the user which card they want to apply for.\n2. Use [@tool:web_search](#mention) to find the latest application steps and requirements.\n3. Explain the process clearly, including eligibility, documents, and timelines.\n4. If the user asks about recommendations or benefits, call the relevant agent.\n5. If the query is out of scope, call [@agent:Credit Card Hub](#mention).\n\n---\n## šŸŽÆ Scope:\nāœ… In Scope:\n- Explaining how to apply for a credit card.\n\nāŒ Out of Scope:\n- Recommending cards (refer to Card Recommendation agent).\n- Explaining card benefits (refer to Card Benefits and Rewards agent).\n\n---\n## šŸ“‹ Guidelines:\nāœ”ļø Dos:\n- Be clear and step-by-step.\n- Mention required documents and eligibility.\n\n🚫 Don'ts:\n- Give outdated or unverified information.", + "model": "claude-3-7-sonnet-latest", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "controlType": "retain", + "examples": "- **User** : How do I apply for a credit card?\n - **Agent response**: Which card are you interested in applying for?\n\n- **User** : I want to apply for the Gold card.\n - **Agent response**: Let me check the application process for the Gold card.\n - **Agent actions**: Call [@tool:web_search](#mention)\n\n- **User** : What documents do I need to apply?\n - **Agent response**: I'll find the list of required documents for you.\n - **Agent actions**: Call [@tool:web_search](#mention)\n\n- **User** : Can you recommend a card for students?\n - **Agent actions**: Call [@agent:Card Recommendation](#mention)\n\n- **User** : What are the benefits of the Platinum card?\n - **Agent actions**: Call [@agent:Card Benefits and Rewards](#mention)" + }, + { + "name": "General Credit Card Advice", + "type": "conversation", + "description": "Provides general advice and best practices for using credit cards responsibly.", + "disabled": false, + "instructions": "## šŸ§‘ā€šŸ’¼ Role:\nYou offer general advice and best practices for credit card usage.\n\n---\n## āš™ļø Steps to Follow:\n1. Ask the user about their specific concern or question.\n2. Use [@tool:web_search](#mention) if needed for up-to-date advice.\n3. Provide clear, practical advice on credit card usage, safety, and financial health.\n4. If the user asks about recommendations, benefits, or application process, call the relevant agent.\n5. If the query is out of scope, call [@agent:Credit Card Hub](#mention).\n\n---\n## šŸŽÆ Scope:\nāœ… In Scope:\n- General credit card advice and best practices.\n\nāŒ Out of Scope:\n- Recommending specific cards (refer to Card Recommendation agent).\n- Explaining card benefits (refer to Card Benefits and Rewards agent).\n- Application process (refer to Card Application Process agent).\n\n---\n## šŸ“‹ Guidelines:\nāœ”ļø Dos:\n- Be professional and friendly.\n- Give practical, actionable advice.\n\n🚫 Don'ts:\n- Give financial advice outside of credit cards.", + "model": "claude-3-7-sonnet-latest", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "controlType": "retain", + "examples": "- **User** : Should I get a credit card or a debit card?\n - **Agent response**: I can explain the pros and cons of both options. Are you looking for rewards, building credit, or something else?\n\n- **User** : How can I avoid credit card debt?\n - **Agent response**: Great question! Some best practices include paying your balance in full each month, tracking your spending, and setting up payment reminders.\n\n- **User** : Is it safe to use my credit card online?\n - **Agent response**: Yes, but always use secure websites and avoid sharing your card details on public Wi-Fi. Would you like more tips on online safety?\n\n- **User** : Can you recommend a card for students?\n - **Agent actions**: Call [@agent:Card Recommendation](#mention)\n\n- **User** : What are the benefits of the Platinum card?\n - **Agent actions**: Call [@agent:Card Benefits and Rewards](#mention)" + } + ], + "prompts": [], + "tools": [ + { + "name": "web_search", + "description": "Fetch information from the web based on chat context", + "parameters": { + "type": "object", + "properties": {} + }, + "isLibrary": true + } + ], + "startAgent": "Credit Card Hub", + "createdAt": "2025-05-02T12:02:06.172Z", + "lastUpdatedAt": "2025-05-02T12:02:06.172Z", + "name": "Version 1" +} +""" + async def run_turn_streamed( messages, start_agent_name, @@ -136,52 +396,11 @@ async def run_turn_streamed( print(f"Found usage information. Updated cumulative tokens: {tokens_used}") print('-'*50) - # Handle ResponseFunctionWebSearch specifically - if hasattr(event, 'data') and hasattr(event.data, 'raw_item'): - raw_item = event.data.raw_item - - # Check if it's a web search call - if (hasattr(raw_item, 'type') and raw_item.type == 'web_search_call') or ( - isinstance(raw_item, dict) and raw_item.get('type') == 'web_search_call' - ): - # Get call_id safely, regardless of structure - call_id = None - if hasattr(raw_item, 'id'): - call_id = raw_item.id - elif isinstance(raw_item, dict) and 'id' in raw_item: - call_id = raw_item['id'] - else: - call_id = str(uuid.uuid4()) - - # Get status safely - status = 'unknown' - if hasattr(raw_item, 'status'): - status = raw_item.status - elif isinstance(raw_item, dict) and 'status' in raw_item: - status = raw_item['status'] - - # Emit a tool call for web search - message = { - 'content': None, - 'role': 'assistant', - 'sender': current_agent.name if current_agent else None, - 'tool_calls': [{ - 'function': { - 'name': 'web_search', - 'arguments': json.dumps({ - 'search_id': call_id, - 'status': status - }) - }, - 'id': call_id, - 'type': 'function' - }], - 'tool_call_id': None, - 'tool_name': None, - 'response_type': 'internal' - } - print("Yielding web search raw response message: ", message) - yield ('message', message) + # Check for web search events + web_search_messages = handle_web_search_event(event, current_agent) + for message in web_search_messages: + print("Yielding web search message: ", message) + yield ('message', message) continue @@ -234,102 +453,58 @@ async def run_turn_streamed( # Handle run items (tools, messages, etc) elif event.type == "run_item_stream_event": current_agent = event.item.agent - if event.item.type == "tool_call_item": - # Check if it's a ResponseFunctionWebSearch object - if hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_call': - call_id = event.item.raw_item.id if hasattr(event.item.raw_item, 'id') else str(uuid.uuid4()) - message = { - 'content': None, - 'role': 'assistant', - 'sender': current_agent.name if current_agent else None, - 'tool_calls': [{ - 'function': { - 'name': 'web_search', - 'arguments': json.dumps({ - 'search_id': call_id - }) - }, - 'id': call_id, - 'type': 'function' - }], - 'tool_call_id': None, - 'tool_name': None, - 'response_type': 'internal' - } - print("Yielding message: ", message) - yield ('message', message) - result_message = { - 'content': "Web search done", + # Check for web search events first + web_search_messages = handle_web_search_event(event, current_agent) + if web_search_messages: + for message in web_search_messages: + print("Yielding web search message: ", message) + yield ('message', message) + continue + + if event.item.type == "tool_call_item": + # Handle normal tool calls + message = { + 'content': None, + 'role': 'assistant', + 'sender': current_agent.name if current_agent else None, + 'tool_calls': [{ + 'function': { + 'name': event.item.raw_item.name, + 'arguments': event.item.raw_item.arguments + }, + 'id': event.item.raw_item.call_id, + 'type': 'function' + }], + 'tool_call_id': None, + 'tool_name': None, + 'response_type': 'internal' + } + print("Yielding message: ", message) + yield ('message', message) + + elif event.item.type == "tool_call_output_item": + # Handle normal tool outputs + call_id = None + tool_name = None + + if isinstance(event.item.raw_item, dict): + call_id = event.item.raw_item.get('call_id') + tool_name = event.item.raw_item.get('name') + elif hasattr(event.item.raw_item, 'call_id'): + call_id = event.item.raw_item.call_id + if hasattr(event.item.raw_item, 'name'): + tool_name = event.item.raw_item.name + + message = { + 'content': str(event.item.output), 'role': 'tool', 'sender': None, 'tool_calls': None, 'tool_call_id': call_id, - 'tool_name': 'web_search', + 'tool_name': tool_name, 'response_type': 'internal' - } - - print("Yielding web search results: ", result_message) - yield ('message', result_message) - else: - # Handle normal tool calls - message = { - 'content': None, - 'role': 'assistant', - 'sender': current_agent.name if current_agent else None, - 'tool_calls': [{ - 'function': { - 'name': event.item.raw_item.name, - 'arguments': event.item.raw_item.arguments - }, - 'id': event.item.raw_item.call_id, - 'type': 'function' - }], - 'tool_call_id': None, - 'tool_name': None, - 'response_type': 'internal' - } - print("Yielding message: ", message) - yield ('message', message) - - - elif event.item.type == "tool_call_output_item": - # Check if it's a web search result - if isinstance(event.item.raw_item, dict) and event.item.raw_item.get('type') == 'web_search_results': - call_id = event.item.raw_item.get('search_id', event.item.raw_item.get('id', str(uuid.uuid4()))) - message = { - 'content': str(event.item.output), - 'role': 'tool', - 'sender': None, - 'tool_calls': None, - 'tool_call_id': call_id, - 'tool_name': 'web_search', - 'response_type': 'internal' - } - else: - # Safe extraction of call_id and name - call_id = None - tool_name = None - - # Handle different types of raw_item - if isinstance(event.item.raw_item, dict): - call_id = event.item.raw_item.get('call_id') - tool_name = event.item.raw_item.get('name') - elif hasattr(event.item.raw_item, 'call_id'): - call_id = event.item.raw_item.call_id - if hasattr(event.item.raw_item, 'name'): - tool_name = event.item.raw_item.name - - message = { - 'content': str(event.item.output), - 'role': 'tool', - 'sender': None, - 'tool_calls': None, - 'tool_call_id': call_id, - 'tool_name': tool_name, - 'response_type': 'internal' - } - + } print("Yielding message: ", message) yield ('message', message) @@ -374,89 +549,6 @@ async def run_turn_streamed( print("Yielding message: ", message) yield ('message', message) - # Handle web search function call events - elif event.item.type == "web_search_call_item" or (hasattr(event.item, 'raw_item') and hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_call'): - # Extract web search call ID if available - call_id = None - if hasattr(event.item.raw_item, 'id'): - call_id = event.item.raw_item.id - - message = { - 'content': None, - 'role': 'assistant', - 'sender': current_agent.name if current_agent else None, - 'tool_calls': [{ - 'function': { - 'name': 'web_search', - 'arguments': json.dumps({ - 'search_id': call_id - }) - }, - 'id': call_id or str(uuid.uuid4()), - 'type': 'function' - }], - 'tool_call_id': None, - 'tool_name': None, - 'response_type': 'internal' - } - print("Yielding web search message: ", message) - yield ('message', message) - - # Handle web search results - elif event.item.type == "web_search_results_item" or ( - hasattr(event.item, 'raw_item') and ( - (hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_results') or - (isinstance(event.item.raw_item, dict) and event.item.raw_item.get('type') == 'web_search_results') - ) - ): - # Extract call_id safely - call_id = None - raw_item = event.item.raw_item - - # Try several ways to get the search_id or id - if hasattr(raw_item, 'search_id'): - call_id = raw_item.search_id - elif isinstance(raw_item, dict) and 'search_id' in raw_item: - call_id = raw_item['search_id'] - elif hasattr(raw_item, 'id'): - call_id = raw_item.id - elif isinstance(raw_item, dict) and 'id' in raw_item: - call_id = raw_item['id'] - else: - call_id = str(uuid.uuid4()) - - # Extract results content safely - results = {} - - # Try event.item.output first - if hasattr(event.item, 'output'): - results = event.item.output - # Then try raw_item.results - elif hasattr(raw_item, 'results'): - results = raw_item.results - elif isinstance(raw_item, dict) and 'results' in raw_item: - results = raw_item['results'] - - # Format the results for output - results_str = "" - try: - results_str = json.dumps(results) if results else "" - except Exception as e: - print(f"Error serializing results: {str(e)}") - results_str = str(results) - - message = { - 'content': results_str, - 'role': 'tool', - 'sender': None, - 'tool_calls': None, - 'tool_call_id': call_id, - 'tool_name': 'web_search', - 'response_type': 'internal' - } - print("Yielding web search results: ", message) - yield ('message', message) - print(f"\n{'='*50}\n") # After all events are processed, set final state diff --git a/apps/rowboat_agents/src/graph/helpers/library_tools.py b/apps/rowboat_agents/src/graph/helpers/library_tools.py new file mode 100644 index 00000000..b5269478 --- /dev/null +++ b/apps/rowboat_agents/src/graph/helpers/library_tools.py @@ -0,0 +1,171 @@ +import json +import uuid + +def handle_web_search_event(event, current_agent): + """ + Helper function to handle all web search related events. + Returns a list of messages to yield. + """ + messages = [] + + # Handle raw response web search + if event.type == "raw_response_event": + if hasattr(event, 'data') and hasattr(event.data, 'raw_item'): + raw_item = event.data.raw_item + if (hasattr(raw_item, 'type') and raw_item.type == 'web_search_call') or ( + isinstance(raw_item, dict) and raw_item.get('type') == 'web_search_call' + ): + call_id = None + if hasattr(raw_item, 'id'): + call_id = raw_item.id + elif isinstance(raw_item, dict) and 'id' in raw_item: + call_id = raw_item['id'] + else: + call_id = str(uuid.uuid4()) + + status = 'unknown' + if hasattr(raw_item, 'status'): + status = raw_item.status + elif isinstance(raw_item, dict) and 'status' in raw_item: + status = raw_item['status'] + + messages.append({ + 'content': None, + 'role': 'assistant', + 'sender': current_agent.name if current_agent else None, + 'tool_calls': [{ + 'function': { + 'name': 'web_search', + 'arguments': json.dumps({ + 'search_id': call_id, + 'status': status + }) + }, + 'id': call_id, + 'type': 'function' + }], + 'tool_call_id': None, + 'tool_name': None, + 'response_type': 'internal' + }) + + # Handle run item web search events + elif event.type == "run_item_stream_event": + if event.item.type == "tool_call_item": + if hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_call': + call_id = event.item.raw_item.id if hasattr(event.item.raw_item, 'id') else str(uuid.uuid4()) + messages.append({ + 'content': None, + 'role': 'assistant', + 'sender': current_agent.name if current_agent else None, + 'tool_calls': [{ + 'function': { + 'name': 'web_search', + 'arguments': json.dumps({ + 'search_id': call_id + }) + }, + 'id': call_id, + 'type': 'function' + }], + 'tool_call_id': None, + 'tool_name': None, + 'response_type': 'internal' + }) + messages.append({ + 'content': "Web search done", + 'role': 'tool', + 'sender': None, + 'tool_calls': None, + 'tool_call_id': call_id, + 'tool_name': 'web_search', + 'response_type': 'internal' + }) + + elif event.item.type == "tool_call_output_item": + if isinstance(event.item.raw_item, dict) and event.item.raw_item.get('type') == 'web_search_results': + call_id = event.item.raw_item.get('search_id', event.item.raw_item.get('id', str(uuid.uuid4()))) + messages.append({ + 'content': str(event.item.output), + 'role': 'tool', + 'sender': None, + 'tool_calls': None, + 'tool_call_id': call_id, + 'tool_name': 'web_search', + 'response_type': 'internal' + }) + + elif event.item.type == "web_search_call_item" or ( + hasattr(event.item, 'raw_item') and + hasattr(event.item.raw_item, 'type') and + event.item.raw_item.type == 'web_search_call' + ): + call_id = None + if hasattr(event.item.raw_item, 'id'): + call_id = event.item.raw_item.id + + messages.append({ + 'content': None, + 'role': 'assistant', + 'sender': current_agent.name if current_agent else None, + 'tool_calls': [{ + 'function': { + 'name': 'web_search', + 'arguments': json.dumps({ + 'search_id': call_id + }) + }, + 'id': call_id or str(uuid.uuid4()), + 'type': 'function' + }], + 'tool_call_id': None, + 'tool_name': None, + 'response_type': 'internal' + }) + + elif event.item.type == "web_search_results_item" or ( + hasattr(event.item, 'raw_item') and ( + (hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_results') or + (isinstance(event.item.raw_item, dict) and event.item.raw_item.get('type') == 'web_search_results') + ) + ): + raw_item = event.item.raw_item + call_id = None + + if hasattr(raw_item, 'search_id'): + call_id = raw_item.search_id + elif isinstance(raw_item, dict) and 'search_id' in raw_item: + call_id = raw_item['search_id'] + elif hasattr(raw_item, 'id'): + call_id = raw_item.id + elif isinstance(raw_item, dict) and 'id' in raw_item: + call_id = raw_item['id'] + else: + call_id = str(uuid.uuid4()) + + results = {} + if hasattr(event.item, 'output'): + results = event.item.output + elif hasattr(raw_item, 'results'): + results = raw_item.results + elif isinstance(raw_item, dict) and 'results' in raw_item: + results = raw_item['results'] + + results_str = "" + try: + results_str = json.dumps(results) if results else "" + except Exception as e: + print(f"Error serializing results: {str(e)}") + results_str = str(results) + + messages.append({ + 'content': results_str, + 'role': 'tool', + 'sender': None, + 'tool_calls': None, + 'tool_call_id': call_id, + 'tool_name': 'web_search', + 'response_type': 'internal' + }) + + return messages diff --git a/apps/rowboat_agents/src/graph/types.py b/apps/rowboat_agents/src/graph/types.py index 32c827fb..3b82c017 100644 --- a/apps/rowboat_agents/src/graph/types.py +++ b/apps/rowboat_agents/src/graph/types.py @@ -4,10 +4,13 @@ class AgentRole(Enum): POST_PROCESSING = "post_process" GUARDRAILS = "guardrails" +class VisibilityType(Enum): + EXTERNAL = "external" + INTERNAL = "internal" + class ControlType(Enum): RETAIN = "retain" PARENT_AGENT = "relinquish_to_parent" - START_AGENT = "relinquish_to_start" class PromptType(Enum): STYLE = "style_prompt"