1 line
23 KiB
JSON
1 line
23 KiB
JSON
{"repo": "ahmedsaeed4/evolution-of-todo", "files": {".claude/skills/chatkit/concepts/AGENTS_INTEGRATION.md": {"6ae74209f12f883268d41d66c0e41ff501bd3c0d": {"t": 1785595182.206599, "n": 22366, "h": "6d96ad355db9ef94", "c": "# OpenAI Agents SDK Integration\n\nComplete integration patterns for OpenAI Agents SDK with ChatKit and MCP tools.\n\n## Agent Configuration\n\n### Client Setup\n\n```python\nfrom agents import AsyncOpenAI, OpenAIChatCompletionsModel, RunConfig\nimport os\n\n# Configure client for Xiaomi model\napi_key = os.environ.get(\"XIAOMI_API_KEY\")\nif not api_key:\n raise ValueError(\"XIAOMI_API_KEY not found in environment\")\n\nclient = AsyncOpenAI(\n api_key=api_key,\n base_url=\"https://api.xiaomimimo.com/v1/\"\n)\n\nmodel = OpenAIChatCompletionsModel(\n model=\"mimo-v2-flash\",\n openai_client=client\n)\n\nconfig = RunConfig(model=model, model_provider=client)\n```\n\n### Environment Variables\n\n```bash\n# .env file\nXIAOMI_API_KEY=xm-... # Required for Agents SDK\nOPENAI_API_KEY=sk-... # Required for ChatKit Sessions API\nDATABASE_URL=postgresql://... # Required for persistence\nENVIRONMENT=development # or production\n```\n\n## Agent Definitions\n\n### Main Orchestrator Agent\n\n```python\nfrom agents import Agent\n\norchestrator_agent = Agent(\n name=\"Task Orchestrator\",\n instructions=\"\"\"You are the main coordinator for task management.\n\n **LANGUAGE HANDLING:**\n - If user message contains URDU CHARACTERS (آ, ب, پ, ت, ث, ج, چ, ح, خ, د, ذ, ر, ز, س, ش, ص, ض, ط, ظ, ع, غ, ف, ق, ک, گ, ل, م, ن, و, ه, ی, ے) → Respond in URDU\n - If user message is in English → Respond in ENGLISH\n - Handle ALL task management operations directly - do NOT handoff\n\n **TOOL USAGE:**\n - You have access to all task management tools\n - Call tools immediately when users ask to create, list, update, delete tasks\n - Don't describe what you'll do - just do it\n\n **EXAMPLES:**\n - \"Create a task: Buy milk\" → Call create_task, respond in English\n - \"سلام! نیا ٹاسک بنائیں\" → Call create_task, respond in Urdu\n - \"List my tasks\" → Call list_tasks, respond in English\n - \"تمام ٹاسکس دکھائیں\" → Call list_tasks, respond in Urdu\n - \"Update task with ID 123\" → Call update_task, respond in English\n - \"اس ٹاسک کو اپڈیٹ کریں\" → Call update_task, respond in Urdu\n\n **IMPORTANT:**\n - NO handoffs to Urdu agent\n - Handle everything directly\n - Match response language to user message language\n\n **FORMATTING RULES:**\n - Use proper line breaks between different sections\n - Add blank lines between tasks for readability\n - Use clear headings with spacing\n - Don't cram everything together\n - Make responses easy to read with proper spacing\"\"\",\n model=model,\n handoffs=[urdu_agent] # Optional handoff target\n)\n```\n\n### Urdu Specialist Agent\n\n```python\nurdu_agent = Agent(\n name=\"Urdu Specialist\",\n instructions=\"\"\"You are a Urdu language specialist. Respond EXCLUSIVELY in Urdu.\n\n **CRITICAL: When users ask to create, update, delete, or list tasks, you MUST call the corresponding MCP tool immediately. Do NOT describe what you would do - actually do it.**\n\n **TOOL USAGE RULES:**\n - If user says \"create a task\" or \"بنائیں\" or \"نیا ٹاسک\" → Call create_task tool immediately\n - If user says \"list tasks\" or \"تمام ٹاسکس\" or \"فہرست\" → Call list_tasks tool immediately\n - If user says \"delete task\" or \"حذف کریں\" → Call delete_task tool immediately\n - If user says \"update task\" or \"تبدیلی\" → Call update_task tool immediately\n\n **IMPORTANT:**\n - Call tools FIRST, then respond in Urdu about the result\n - Don't say \"I will create...\" - just create it and say \"ٹاسک بن گیا\"\n - Use the tool parameters exactly as requested by user\n\n **FORMATTING RULES:**\n - Use proper line breaks between different sections\n - Add blank lines between tasks for readability\n - Use clear headings with spacing\n - Don't cram everything together\n\n You have access to MCP tools for task management. Always use them when users ask about tasks.\n\n Always provide helpful, culturally appropriate responses in Urdu AFTER tool calls.\"\"\",\n model=model\n)\n```\n\n## MCP Tool Integration\n\n### MCP Server Creation\n\n```python\nfrom agents.mcp import MCPServerStdio\nfrom pathlib import Path\n\nasync def create_mcp_server(self) -> MCPServerStdio:\n \"\"\"Create dynamic MCP server for this request\"\"\"\n backend_dir = Path(__file__).parent\n project_root = backend_dir.parent.parent\n mcp_wrapper_path = backend_dir / \"mcp_wrapper.py\"\n\n server = MCPServerStdio(\n params={\n \"command\": \"uv\",\n \"args\": [\"run\", \"python\", str(mcp_wrapper_path)],\n \"cwd\": str(project_root)\n },\n client_session_timeout_seconds=60\n )\n\n await server.connect()\n return server\n```\n\n### MCP Wrapper Script\n\n```python\n#!/usr/bin/env python3\n\"\"\"MCP server wrapper that loads environment variables before starting the server\"\"\"\nimport os\nimport sys\nfrom pathlib import Path\n\n# Add the backend directory to Python path\nbackend_dir = Path(__file__).parent\nsys.path.insert(0, str(backend_dir))\n\n# Load environment variables\nenv_path = backend_dir.parent.parent / '.env'\nif env_path.exists():\n from dotenv import load_dotenv\n load_dotenv(env_path)\n\n# Import and run the actual MCP server\nfrom your_mcp_tools_module import mcp # Replace with your MCP tools module\n\nif __name__ == \"__main__\":\n mcp.run(transport=\"stdio\")\n```\n\n### MCP Tools Implementation\n\n```python\n# task_serves_mcp_tools.py\nfrom mcp.server import Server\nfrom mcp.server.stdio import stdio_server\nfrom typing import List, Dict, Any\nimport json\n\n# Create MCP server\nmcp = Server(\"task-management-tools\")\n\n@mcp.tool()\nasync def create_task(task_description: str, priority: str = \"medium\") -> str:\n \"\"\"Create a new task with description and priority\"\"\"\n # Implementation for creating task\n # This would interact with your task database\n return f\"Task created: {task_description} with priority {priority}\"\n\n@mcp.tool()\nasync def list_tasks() -> str:\n \"\"\"List all current tasks\"\"\"\n # Implementation for listing tasks\n return \"List of tasks: ...\"\n\n@mcp.tool()\nasync def update_task(task_id: str, updates: Dict[str, Any]) -> str:\n \"\"\"Update existing task\"\"\"\n # Implementation for updating task\n return f\"Task {task_id} updated\"\n\n@mcp.tool()\nasync def delete_task(task_id: str) -> str:\n \"\"\"Delete a task by ID\"\"\"\n # Implementation for deleting task\n return f\"Task {task_id} deleted\"\n\n# Export the MCP server\n__all__ = [\"mcp\"]\n```\n\n## Agent Execution Patterns\n\n### Basic Agent Run\n\n```python\nfrom agents import Runner\n\nasync def run_agent_simple(user_input: str, user_id: str) -> str:\n \"\"\"Run agent with basic input\"\"\"\n # Enhance input with user context\n enhanced_input = f\"[User: {user_id}] {user_input}\"\n\n # Run the agent\n result = await Runner.run(orchestrator_agent, enhanced_input, run_config=config)\n\n return result.final_output\n```\n\n### Agent Run with MCP Tools\n\n```python\nasync def run_agent_with_mcp(user_input: str, user_id: str) -> str:\n \"\"\"Run agent with MCP tools available\"\"\"\n # Create MCP server\n server = await create_mcp_server()\n\n try:\n # Assign MCP servers to agent\n orchestrator_agent.mcp_servers = [server]\n\n # Enhance input with user context\n enhanced_input = f\"[User: {user_id}] {user_input}\"\n\n # Run the agent\n result = await Runner.run(orchestrator_agent, enhanced_input, run_config=config)\n\n return result.final_output\n\n finally:\n # Cleanup\n await server.cleanup()\n orchestrator_agent.mcp_servers = []\n```\n\n### Agent Run with Conversation History\n\n```python\nasync def run_agent_with_history(\n user_input: str,\n user_id: str,\n conversation_history: List[str]\n) -> str:\n \"\"\"Run agent with conversation history\"\"\"\n # Create MCP server\n server = await create_mcp_server()\n\n try:\n # Assign MCP servers to agent\n orchestrator_agent.mcp_servers = [server]\n\n # Build enhanced input with history\n if conversation_history:\n history_context = \"\\n\".join(conversation_history)\n enhanced_input = f\"[User: {user_id}]\\nConversation History:\\n{history_context}\\n\\nCurrent Message: {user_input}\"\n else:\n enhanced_input = f\"[User: {user_id}] {user_input}\"\n\n # Run the agent\n result = await Runner.run(orchestrator_agent, enhanced_input, run_config=config)\n\n return result.final_output\n\n finally:\n # Cleanup\n await server.cleanup()\n orchestrator_agent.mcp_servers = []\n```\n\n## Integration with ChatKit\n\n### Complete ChatKit Server respond() Method\n\n```python\nfrom chatkit.server import ChatKitServer\nfrom chatkit.types import (\n ThreadMetadata, UserMessageItem, AssistantMessageItem,\n AssistantMessageContent, ThreadStreamEvent, ThreadItemDoneEvent\n)\nfrom agents import Runner\nfrom agents.mcp import MCPServerStdio\nfrom datetime import datetime\nfrom typing import AsyncIterator\nfrom pathlib import Path\n\nclass MyChatKitServer(ChatKitServer[dict]):\n def __init__(self, store):\n super().__init__(store=store)\n self.store = store\n\n async def respond(\n self,\n thread: ThreadMetadata,\n input_user_message: UserMessageItem | None,\n context: dict,\n ) -> AsyncIterator[ThreadStreamEvent]:\n \"\"\"Handle ChatKit requests and stream responses using agent system\"\"\"\n print(f\"🔍 ChatKit server processing thread: {thread.id}\")\n\n # 1. Save thread if new\n if thread.id:\n await self.store.save_thread(thread, context)\n\n # 2. Process user message if present\n if input_user_message:\n # Save user message\n if thread.id not in self.store._thread_items:\n self.store._thread_items[thread.id] = []\n await self.store.save_item(thread.id, input_user_message, context)\n\n # Extract message text\n user_text = self._extract_message_text(input_user_message)\n print(f\"🔍 Processing user message: {user_text}\")\n\n try:\n # 3. Create MCP server\n backend_dir = Path(__file__).parent.parent\n project_root = backend_dir.parent.parent\n mcp_wrapper_path = backend_dir / \"mcp_wrapper.py\"\n\n server = MCPServerStdio(\n params={\n \"command\": \"uv\",\n \"args\": [\"run\", \"python\", str(mcp_wrapper_path)],\n \"cwd\": str(project_root)\n },\n client_session_timeout_seconds=60\n )\n\n # 4. Connect and assign MCP servers\n await server.connect()\n orchestrator_agent.mcp_servers = [server]\n\n # 5. Build conversation history\n conversation_history = self._build_conversation_history(thread.id)\n user_id = context.get(\"user_id\", \"unknown\")\n\n # 6. Enhance input\n if conversation_history:\n history_context = \"\\n\".join(conversation_history)\n enhanced_input = f\"[User: {user_id}]\\nConversation History:\\n{history_context}\\n\\nCurrent Message: {user_text}\"\n else:\n enhanced_input = f\"[User: {user_id}] {user_text}\"\n\n # 7. Run agent\n print(f\"🔍 Running agent with input: {enhanced_input[:150]}...\")\n result = await Runner.run(orchestrator_agent, enhanced_input, run_config=config)\n response_text = result.final_output\n print(f\"✅ Agent response: {response_text[:100]}...\")\n\n except Exception as e:\n print(f\"❌ Agent error: {str(e)}\")\n response_text = f\"I encountered an error processing your request: {str(e)}\"\n\n finally:\n # 8. Cleanup\n if 'server' in locals():\n await server.cleanup()\n orchestrator_agent.mcp_servers = []\n\n else:\n # No user message - thread load request\n response_text = \"Hello! I'm your AI Assistant. How can I help you today?\"\n\n # 9. Create assistant response\n assistant_item = AssistantMessageItem(\n thread_id=thread.id,\n id=f\"msg_{datetime.utcnow().timestamp()}\",\n created_at=datetime.utcnow(),\n content=[AssistantMessageContent(text=response_text)],\n )\n\n # 10. Save assistant response\n await self.store.save_item(thread.id, assistant_item, context)\n\n # 11. Stream response\n yield ThreadItemDoneEvent(item=assistant_item)\n\n def _extract_message_text(self, message: UserMessageItem) -> str:\n \"\"\"Extract text from UserMessageItem\"\"\"\n if not hasattr(message, 'content'):\n return \"\"\n\n for content_item in message.content:\n if hasattr(content_item, 'text'):\n return content_item.text\n elif hasattr(content_item, 'type') and content_item.type == 'input_text':\n return content_item.text\n\n return \"\"\n\n def _build_conversation_history(self, thread_id: str) -> list[str]:\n \"\"\"Build conversation history from thread items\"\"\"\n conversation_history = []\n\n if thread_id in self.store._thread_items:\n # Exclude current message (last item)\n for item in self.store._thread_items[thread_id][:-1]:\n if hasattr(item, 'content'):\n item_text = self._extract_message_text(item)\n\n if hasattr(item, '__class__'):\n class_name = item.__class__.__name__\n if 'User' in class_name:\n conversation_history.append(f\"User: {item_text}\")\n elif 'Assistant' in class_name:\n conversation_history.append(f\"Assistant: {item_text}\")\n\n return conversation_history\n```\n\n## Language Detection\n\n**Note**: Language detection is handled through agent instructions, not separate utility functions.\n\nYour implementation uses agent instructions to detect language based on Urdu characters:\n\n```python\norchestrator_agent = Agent(\n name=\"AI Assistant\",\n instructions=\"\"\"You are a helpful AI assistant.\n\n **LANGUAGE HANDLING:**\n - If user message contains URDU CHARACTERS (آ, ب, پ, ت, ث, ج, چ, ح, خ, د, ذ, ر, ز, س, ش, ص, ض, ط, ظ, ع, غ, ف, ق, ک, گ, ل, م, ن, و, ه, ی, ے) → Respond in URDU\n - If user message is in English → Respond in ENGLISH\n - Handle ALL operations directly - do NOT handoff\n\n **IMPORTANT:**\n - Match response language to user message language\n - Handle everything directly\n \"\"\",\n model=model\n)\n```\n\nThis approach keeps language detection simple and centralized in the agent's instructions rather than requiring separate utility functions.\n\n## Error Handling\n\n### Agent Execution Errors\n\n```python\nasync def safe_agent_execution(user_input: str, user_id: str) -> str:\n \"\"\"Run agent with comprehensive error handling\"\"\"\n server = None\n try:\n # Create MCP server\n server = await create_mcp_server()\n orchestrator_agent.mcp_servers = [server]\n\n # Run agent\n enhanced_input = f\"[User: {user_id}] {user_input}\"\n result = await Runner.run(orchestrator_agent, enhanced_input, run_config=config)\n\n return result.final_output\n\n except Exception as e:\n print(f\"❌ Agent execution failed: {str(e)}\")\n # Return user-friendly error message\n return f\"I encountered an error processing your request: {str(e)}\"\n\n finally:\n # Always cleanup\n if server:\n await server.cleanup()\n orchestrator_agent.mcp_servers = []\n```\n\n### MCP Server Errors\n\n```python\nasync def create_mcp_server_with_retry(max_retries: int = 3) -> MCPServerStdio:\n \"\"\"Create MCP server with retry logic\"\"\"\n for attempt in range(max_retries):\n try:\n server = await create_mcp_server()\n return server\n except Exception as e:\n print(f\"❌ Failed to create MCP server (attempt {attempt + 1}): {str(e)}\")\n if attempt == max_retries - 1:\n raise\n # Wait before retry\n import asyncio\n await asyncio.sleep(1)\n\n raise RuntimeError(\"Failed to create MCP server after all retries\")\n```\n\n## Performance Optimizations\n\n### Connection Pooling\n\n```python\nfrom agents.mcp import MCPServerStdio\nfrom contextlib import asynccontextmanager\n\n@asynccontextmanager\nasync def get_mcp_server():\n \"\"\"Context manager for MCP server with automatic cleanup\"\"\"\n server = None\n try:\n server = await create_mcp_server()\n yield server\n finally:\n if server:\n await server.cleanup()\n\n# Usage\nasync def run_with_mcp_server(user_input: str, user_id: str) -> str:\n async with get_mcp_server() as server:\n orchestrator_agent.mcp_servers = [server]\n enhanced_input = f\"[User: {user_id}] {user_input}\"\n result = await Runner.run(orchestrator_agent, enhanced_input, run_config=config)\n return result.final_output\n```\n\n### Caching Agent Results\n\n```python\nfrom functools import lru_cache\nimport hashlib\n\n@lru_cache(maxsize=100)\ndef get_cached_agent_response(input_hash: str) -> str:\n \"\"\"Cache agent responses for identical inputs\"\"\"\n # This is a simplified example - in production, use Redis or similar\n pass\n\ndef hash_input(user_input: str, user_id: str, history: tuple = ()) -> str:\n \"\"\"Create hash for caching\"\"\"\n content = f\"{user_id}:{user_input}:{':'.join(history)}\"\n return hashlib.md5(content.encode()).hexdigest()\n```\n\n## Testing\n\n### Unit Tests for Agents\n\n```python\nimport pytest\nfrom unittest.mock import AsyncMock, patch\n\n@pytest.mark.asyncio\nasync def test_orchestrator_agent_english():\n \"\"\"Test orchestrator agent with English input\"\"\"\n mock_result = AsyncMock()\n mock_result.final_output = \"Task created successfully\"\n\n with patch('agents.Runner.run', return_value=mock_result) as mock_run:\n result = await run_agent_simple(\"Create a task: Buy milk\", \"user-123\")\n\n assert result == \"Task created successfully\"\n mock_run.assert_called_once()\n\n@pytest.mark.asyncio\nasync def test_orchestrator_agent_urdu():\n \"\"\"Test orchestrator agent with Urdu input\"\"\"\n mock_result = AsyncMock()\n mock_result.final_output = \"ٹاسک بن گیا\"\n\n with patch('agents.Runner.run', return_value=mock_result) as mock_run:\n result = await run_agent_simple(\"نیا ٹاسک بنائیں\", \"user-123\")\n\n assert result == \"ٹاسک بن گیا\"\n mock_run.assert_called_once()\n```\n\n### Integration Tests\n\n```python\n@pytest.mark.asyncio\nasync def test_full_agent_integration():\n \"\"\"Test complete agent integration with MCP tools\"\"\"\n # Create test MCP server\n server = await create_mcp_server()\n\n try:\n orchestrator_agent.mcp_servers = [server]\n\n # Test task creation\n result = await Runner.run(\n orchestrator_agent,\n \"[User: test-user] Create a task: Buy groceries\",\n run_config=config\n )\n\n assert result.final_output is not None\n assert len(result.final_output) > 0\n\n finally:\n await server.cleanup()\n orchestrator_agent.mcp_servers = []\n```\n\n## Production Deployment\n\n### Environment Configuration\n\n```python\n# config.py\nimport os\nfrom agents import AsyncOpenAI, OpenAIChatCompletionsModel, RunConfig\n\ndef get_agent_config() -> RunConfig:\n \"\"\"Get agent configuration for current environment\"\"\"\n api_key = os.environ.get(\"XIAOMI_API_KEY\")\n\n if not api_key:\n raise ValueError(\"XIAOMI_API_KEY is required\")\n\n # Use different models based on environment\n environment = os.getenv(\"ENVIRONMENT\", \"development\")\n model_name = \"mimo-v2-flash\" # Could vary by environment\n\n client = AsyncOpenAI(\n api_key=api_key,\n base_url=\"https://api.xiaomimimo.com/v1/\"\n )\n\n model = OpenAIChatCompletionsModel(\n model=model_name,\n openai_client=client\n )\n\n return RunConfig(model=model, model_provider=client)\n```\n\n### Monitoring & Logging\n\n```python\nimport logging\nfrom datetime import datetime\n\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(__name__)\n\nasync def run_agent_with_monitoring(user_input: str, user_id: str) -> str:\n \"\"\"Run agent with comprehensive monitoring\"\"\"\n start_time = datetime.utcnow()\n\n try:\n logger.info(f\"Agent execution started for user {user_id}\")\n result = await run_agent_simple(user_input, user_id)\n\n duration = (datetime.utcnow() - start_time).total_seconds()\n logger.info(f\"Agent execution completed in {duration:.2f}s for user {user_id}\")\n\n return result\n\n except Exception as e:\n duration = (datetime.utcnow() - start_time).total_seconds()\n logger.error(f\"Agent execution failed after {duration:.2f}s for user {user_id}: {str(e)}\")\n raise\n```\n\n### Rate Limiting\n\n```python\nfrom collections import defaultdict\nfrom datetime import datetime, timedelta\nimport asyncio\n\nclass RateLimiter:\n def __init__(self, max_requests: int = 10, window: int = 60):\n self.max_requests = max_requests\n self.window = window\n self.requests = defaultdict(list)\n\n async def acquire(self, user_id: str) -> bool:\n \"\"\"Acquire rate limit permission\"\"\"\n now = datetime.utcnow()\n window_start = now - timedelta(seconds=self.window)\n\n # Clean old requests\n self.requests[user_id] = [\n req_time for req_time in self.requests[user_id]\n if req_time > window_start\n ]\n\n # Check if limit exceeded\n if len(self.requests[user_id]) >= self.max_requests:\n return False\n\n # Add current request\n self.requests[user_id].append(now)\n return True\n\n# Usage\nrate_limiter = RateLimiter(max_requests=5, window=60) # 5 requests per minute\n\nasync def run_agent_with_rate_limit(user_input: str, user_id: str) -> str:\n \"\"\"Run agent with rate limiting\"\"\"\n if not await rate_limiter.acquire(user_id):\n raise Exception(\"Rate limit exceeded. Please try again later.\")\n\n return await run_agent_simple(user_input, user_id)\n```\n\nThis integration provides a complete, production-ready OpenAI Agents SDK implementation with MCP tools, language detection, error handling, and performance optimizations."}}}} |