diff --git a/apps/rowboat_agents/src/graph/core.py b/apps/rowboat_agents/src/graph/core.py index 838cccbd..0391d1c0 100644 --- a/apps/rowboat_agents/src/graph/core.py +++ b/apps/rowboat_agents/src/graph/core.py @@ -282,6 +282,54 @@ async def run_turn_streamed( print('-'*50) 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) + continue # Update current agent when it changes @@ -334,45 +382,112 @@ async def run_turn_streamed( elif event.type == "run_item_stream_event": current_agent = event.item.agent if event.item.type == "tool_call_item": - 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' - } + # 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' + } + 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": - message = { - 'content': str(event.item.output), - 'role': 'tool', - 'sender': None, - 'tool_calls': None, - 'tool_call_id': event.item.raw_item['call_id'], - 'tool_name': event.item.raw_item.get('name', None), - 'response_type': 'internal' - } + # 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) elif event.item.type == "message_output_item": content = "" + url_citations = [] + + # Extract text content and any URL citations if hasattr(event.item.raw_item, 'content'): for content_item in event.item.raw_item.content: + # Handle text content if hasattr(content_item, 'text'): content += content_item.text + # Extract URL citations if present + if hasattr(content_item, 'annotations'): + for annotation in content_item.annotations: + if hasattr(annotation, 'type') and annotation.type == 'url_citation': + citation = { + 'url': annotation.url if hasattr(annotation, 'url') else '', + 'title': annotation.title if hasattr(annotation, 'title') else '', + 'start_index': annotation.start_index if hasattr(annotation, 'start_index') else 0, + 'end_index': annotation.end_index if hasattr(annotation, 'end_index') else 0 + } + url_citations.append(citation) + + # Create message with URL citations if they exist message = { 'content': content, 'role': 'assistant', @@ -382,9 +497,97 @@ async def run_turn_streamed( 'tool_name': None, 'response_type': 'external' } + + # Add citations if any were found + if url_citations: + message['citations'] = url_citations + 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