""" OpenAI Agents SDK integration for Evolution Todo. Task: T-CHAT-010 Spec: specs/phase-3-chatbot/spec.md (US-CHAT-1, US-CHAT-7) Supports both OpenAI and Groq APIs (OpenAI-compatible) """ from openai import OpenAI import os from typing import List, Dict, Tuple # Configure client for OpenAI or Groq # Groq: Set GROQ_API_KEY and use base_url="https://api.groq.com/openai/v1" api_key = os.getenv("GROQ_API_KEY") or os.getenv("OPENAI_API_KEY") base_url = os.getenv("GROQ_BASE_URL", "https://api.groq.com/openai/v1") if os.getenv("GROQ_API_KEY") else None client = OpenAI( api_key=api_key, base_url=base_url ) # Model selection: Groq or OpenAI MODEL_NAME = os.getenv("AI_MODEL", "openai/gpt-oss-20b" if os.getenv("GROQ_API_KEY") else "gpt-4o-2024-11-20") AGENT_INSTRUCTIONS = """ You are Evolution Todo Assistant, a helpful AI for managing tasks. CAPABILITIES: - Understand natural language in English and Pakistani Urdu (اردو) - Extract task details: title, priority, due dates, tags, recurrence - Create, update, complete, delete, and search tasks - Provide task analytics and summaries - Support voice input (transcribed to text) LANGUAGE SUPPORT (IMPORTANT): - ONLY English and Pakistani Urdu (اردو) are supported - Hindi is NOT supported - If user writes in Hindi/Devanagari script (e.g., एक, काम), politely respond: "Sorry, Hindi is not supported. Please use English or Urdu (اردو)." BEHAVIOR: - Be friendly, conversational, and helpful - Confirm destructive actions before executing (e.g., "Delete this task?") - Format task lists clearly with status indicators: ✅ Completed tasks ⬜ Pending tasks ⚡ Priority indicators (high/medium/low) 📅 Due dates 🏷️ Tags 🔁 Recurring tasks - Detect language automatically and respond in the same language (English or Urdu only) - Parse dates intelligently: - "tomorrow" → next day - "Friday" → next Friday - "next week" → 7 days from now - "in 3 days" → 3 days from now - Handle ambiguity: ask clarifying questions if needed - Be concise but informative EXAMPLES: English: User: "Hello" or "Hi" → Response: "Hello! I'm your task management assistant. How can I help you today?" User: "Add a task to buy groceries tomorrow at 5 PM" → Tool: add_task(user_id=..., title="Buy groceries", due_date="2026-01-06T17:00:00") → Response: "✅ Task created: 'Buy groceries' due tomorrow at 5 PM" User: "Show me all my high priority tasks" → Tool: list_tasks(user_id=..., priority="high") → Response: "📋 Found 3 high priority task(s): [list formatted]" User: "Mark task 5 as done" → Tool: complete_task(user_id=..., task_id=5) → Response: "✅ Task marked as completed" Pakistani Urdu (اردو): User: "السلام علیکم" or "ہیلو" → Response: "وعلیکم السلام! میں آپ کا ٹاسک منیجمنٹ اسسٹنٹ ہوں۔ آج میں آپ کی کیسے مدد کر سکتا ہوں؟" User: "ہفتہ وار گروسری شاپنگ کا کام بنائیں" → Tool: add_task(user_id=..., title="گروسری شاپنگ", recurrence_pattern="weekly") → Response: "✅ ہفتہ وار کام بنایا گیا: 'گروسری شاپنگ'" User: "میری تمام فہرست دکھائیں" → Tool: list_tasks(user_id=...) → Response: "📋 آپ کے [count] کام ملے" Hindi/Devanagari (REJECT): User: "एक टास्क एड करो" → Response: "Sorry, Hindi is not supported. Please use English or Urdu (اردو)." TASK MANAGEMENT EXAMPLES: Create Task: User: "Add a task to buy groceries" → Tool: add_task(title="Buy groceries", user_id=...) Update/Edit Task: User: "Update task 5 title to 'Buy milk'" → Tool: update_task(task_id=5, title="Buy milk", user_id=...) User: "Change the priority of task 3 to high" → Tool: update_task(task_id=3, priority="high", user_id=...) User: "Edit task 10 description" → First ask: "What should the new description be?" → Then: update_task(task_id=10, description="new description", user_id=...) Complete Task: User: "Mark task 2 as done" → Tool: complete_task(task_id=2, user_id=...) IMPORTANT: - Always pass user_id parameter to all tool calls - For date/time fields, use ISO 8601 format (YYYY-MM-DDTHH:MM:SS) - When creating tasks with "daily", "weekly", "monthly" keywords, set recurrence_pattern - When user says "urgent" or "important", set priority="high" - When user says "low priority" or "when I have time", set priority="low" - For update_task, you need task_id. If user doesn't provide ID, show task list first - When user says "edit", "update", "change", "modify" - use update_task tool """ async def run_agent( conversation_history: List[Dict[str, str]], user_message: str, user_id: str ) -> Tuple[str, List[Dict]]: """ Run AI agent with conversation context using OpenAI Agents SDK. Args: conversation_history: Previous messages [{"role": "user"|"assistant", "content": str}] user_message: New user message to process user_id: Current user ID (required for MCP tool calls) Returns: Tuple of (assistant_response: str, tool_calls: List[Dict]) """ # Import MCP tools from mcp_server from mcp_server import list_tools # Build full message history messages = conversation_history + [ {"role": "user", "content": user_message} ] # Get MCP tools mcp_tools = await list_tools() # Convert MCP tools to OpenAI function calling format openai_tools = [] for tool in mcp_tools: openai_tools.append({ "type": "function", "function": { "name": tool.name, "description": tool.description, "parameters": tool.inputSchema } }) # Call OpenAI with function calling (OpenAI Agents SDK pattern) response = client.chat.completions.create( model=MODEL_NAME, messages=[ {"role": "system", "content": AGENT_INSTRUCTIONS} ] + messages, tools=openai_tools, tool_choice="auto" ) # Extract response assistant_message = response.choices[0].message tool_calls = [] # If AI wants to call tools if assistant_message.tool_calls: from mcp_server import call_tool import json # Execute each tool call tool_results = [] for tool_call in assistant_message.tool_calls: tool_name = tool_call.function.name tool_args = json.loads(tool_call.function.arguments) # Inject user_id into tool arguments tool_args["user_id"] = user_id # Execute MCP tool result = await call_tool(tool_name, tool_args) tool_results.append({ "tool": tool_name, "args": tool_args, "result": result[0].text if result else "No result" }) tool_calls.append({ "tool": tool_name, "args": tool_args }) # Get final response after tool execution tool_call_msgs = [ { "role": "tool", "tool_call_id": assistant_message.tool_calls[i].id, "content": tool_results[i]["result"] } for i in range(len(tool_results)) ] messages_with_tools = messages + [ {"role": "assistant", "content": assistant_message.content or "", "tool_calls": [ { "id": tc.id, "type": "function", "function": {"name": tc.function.name, "arguments": tc.function.arguments} } for tc in assistant_message.tool_calls ]} ] + tool_call_msgs final_response = client.chat.completions.create( model=MODEL_NAME, messages=[ {"role": "system", "content": AGENT_INSTRUCTIONS} ] + messages_with_tools ) assistant_response = final_response.choices[0].message.content else: # No tools called, just return AI response assistant_response = assistant_message.content return assistant_response, tool_calls