├── .SAMPLEenv ├── .gitignore ├── CursorRules_AddaTool.md ├── OpenAIDocs ├── OpenAIAPIDocs.txt └── OpenAIAPIReference.txt ├── README.md ├── main.py ├── prompts.py ├── requirements.txt ├── terminalstyle.py └── tools ├── __init__.py ├── file_tools.py ├── tool_definitions.py └── tool_handler.py /.SAMPLEenv: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY=YOUR_OPENAI_API_KEY 2 | ASSISTANT_ID=YOUR_ASSISTANT_ID 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Environment files 2 | .env 3 | .env.* 4 | !.env.example 5 | 6 | # Python 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | *.so 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # Virtual Environment 29 | venv/ 30 | ENV/ 31 | env/ 32 | .venv/ 33 | 34 | # IDE specific files 35 | .idea/ 36 | .vscode/ 37 | *.swp 38 | *.swo 39 | .DS_Store 40 | 41 | # Project specific 42 | thread_id.txt 43 | agent_directory/ 44 | -------------------------------------------------------------------------------- /CursorRules_AddaTool.md: -------------------------------------------------------------------------------- 1 | # OpenAI Assistant API Boilerplate - Guide to Create a Tool / Function Call 2 | 3 | ## Introduction 4 | 5 | This guide helps integrate new API tools into the assistant. As an AI assistant, I will: 6 | 7 | 1. 📝 Review Your API Details: 8 | - API documentation/instructions you provide 9 | - Authentication requirements (API keys, tokens, etc.) 10 | - API endpoints and their functionality 11 | - Any rate limits or restrictions 12 | 13 | 2. ❓ Ask Clarifying Questions: 14 | - If any critical information is missing 15 | - About specific API behaviors 16 | - About desired error handling 17 | - About expected outputs 18 | 19 | 3. 🛠️ Create Integration Files: 20 | - Tool implementation file 21 | - Function definitions 22 | - Environment variable setup 23 | - Dependency requirements 24 | 25 | 4. ✅ Provide Implementation Summary: 26 | - List of all created/modified files 27 | - Required environment variables 28 | - New dependencies added 29 | - Testing instructions 30 | 31 | Please provide: 32 | 1. API documentation or integration guide 33 | 2. Authentication details (how to get/use API key) 34 | 3. Any specific requirements or preferences 35 | 36 | ## Integration Checklist 37 | 1. ⚙️ Environment Setup 38 | - Add API key to .env 39 | - Update requirements.txt 40 | - Install dependencies 41 | 42 | 2. 🛠️ Tool Creation 43 | - Create tool file 44 | - Implement API functions 45 | - Add error handling 46 | 47 | 3. 🔗 Integration 48 | - Add tool definition 49 | - Register in handler 50 | - Update exports 51 | - Update assistant instructions 52 | 53 | 4. 🧪 Testing 54 | - Test direct API calls 55 | - Test through assistant 56 | - Verify error handling 57 | 58 | ## Conclusion 59 | 60 | After completing the integration, provide the user with: 61 | 62 | ✅ Integration Summary: 63 | 1. Files created/modified: 64 | - List all files that were created or changed 65 | 2. Dependencies added: 66 | - List new packages added to requirements.txt 67 | 3. Environment variables: 68 | - List new environment variables needed 69 | 70 | ✅ Testing Instructions: 71 | 1. Direct testing: 72 | ```bash 73 | # Test the tool directly 74 | python tools/your_api_tools.py 75 | ``` 76 | 77 | 2. Assistant testing: 78 | ```bash 79 | # Test through the assistant 80 | python main.py 81 | # Then try: "Use [your_tool] to..." 82 | ``` 83 | 84 | 3. Expected output: 85 | - Describe what successful output looks like 86 | - Note any common error messages 87 | 88 | ✅ Next Steps: 89 | 1. Install new dependencies: `pip install -r requirements.txt` 90 | 2. Add your API key to .env 91 | 3. Run the direct test 92 | 4. Test through the assistant 93 | 94 | ## Adding New API Tools 95 | 96 | ### 1. Initial Setup 97 | 1. Add your API key to `.env`: 98 | ```bash 99 | # .env 100 | OPENAI_API_KEY=your_openai_key 101 | ASSISTANT_ID=your_assistant_id 102 | YOUR_NEW_API_KEY=your_api_key # Add your new API's key here 103 | ``` 104 | 105 | 2. Update `requirements.txt` with ALL required packages: 106 | ```bash 107 | # Existing core dependencies 108 | openai>=1.3.0 # OpenAI API client 109 | python-dotenv>=0.19.0 # For environment variables 110 | requests>=2.31.0 # For API calls 111 | 112 | # Add your new dependencies below with version constraints 113 | your-package>=1.0.0 # Brief description of what this package is for 114 | another-package>=2.0.0 # Another required package 115 | 116 | # Example: 117 | # replicate>=0.20.0 # For Replicate API integration 118 | # pillow>=10.0.0 # For image processing 119 | ``` 120 | 121 | IMPORTANT: After updating requirements.txt: 122 | 1. Install new dependencies: 123 | ```bash 124 | pip install -r requirements.txt 125 | ``` 126 | 2. Test imports: 127 | ```python 128 | # Create a test.py file 129 | import your_package 130 | import another_package 131 | print("All imports successful!") 132 | ``` 133 | 3. Document any special installation requirements in comments 134 | 135 | ### 2. Create Tool File 136 | Create a new file in the `tools` directory (e.g., `tools/your_api_tools.py`): 137 | 138 | ```python 139 | import os 140 | import requests 141 | from functools import lru_cache 142 | from cachetools import TTLCache, cached 143 | from dotenv import load_dotenv 144 | from typing import Optional, Dict, Any 145 | 146 | # Load environment variables 147 | load_dotenv() 148 | 149 | # Cache setup (optional) 150 | response_cache = TTLCache(maxsize=100, ttl=3600) # Cache for 1 hour 151 | 152 | @lru_cache(maxsize=1) 153 | def get_api_key() -> str: 154 | """Get API key from environment variables.""" 155 | api_key = os.getenv("YOUR_NEW_API_KEY") 156 | if not api_key: 157 | raise ValueError("YOUR_NEW_API_KEY environment variable not set") 158 | return api_key 159 | 160 | @cached(cache=response_cache) 161 | def your_api_function(param1: str, param2: str = "default") -> str: 162 | """ 163 | Call your API endpoint. 164 | 165 | Args: 166 | param1: Description of first parameter 167 | param2: Description of second parameter (default: "default") 168 | Returns: 169 | str: Response from API or error message 170 | """ 171 | try: 172 | url = "https://api.example.com/v1/endpoint" 173 | headers = {"Authorization": f"Bearer {get_api_key()}"} 174 | 175 | response = requests.get( 176 | url, 177 | headers=headers, 178 | params={"param1": param1, "param2": param2}, 179 | timeout=10 180 | ) 181 | response.raise_for_status() 182 | 183 | data = response.json() 184 | return f"Result: {data['relevant_field']}" 185 | 186 | except requests.exceptions.RequestException as e: 187 | return f"API request failed: {str(e)}" 188 | except json.JSONDecodeError: 189 | return "Error: Invalid JSON response from API" 190 | except Exception as e: 191 | return f"Error calling API: {str(e)}" 192 | 193 | # Direct testing 194 | if __name__ == "__main__": 195 | print("\nTesting API function:") 196 | try: 197 | result = your_api_function("test1") 198 | print(f"Success: {result}") 199 | except Exception as e: 200 | print(f"Test failed: {str(e)}") 201 | ``` 202 | 203 | ### 3. Add Tool Definition 204 | Add your tool to `tools/tool_definitions.py`: 205 | 206 | ```python 207 | def get_tool_definitions(): 208 | return [ 209 | # ... existing tools ... 210 | { 211 | "type": "function", 212 | "function": { 213 | "name": "your_api_function", 214 | "description": "Clear description of what this API does", 215 | "parameters": { 216 | "type": "object", 217 | "properties": { 218 | "param1": { 219 | "type": "string", 220 | "description": "Description of first parameter" 221 | }, 222 | "param2": { 223 | "type": "string", 224 | "description": "Description of second parameter (uses 'default' if not specified)" 225 | } 226 | }, 227 | "required": ["param1", "param2"], # IMPORTANT: List ALL parameters here 228 | "additionalProperties": False 229 | }, 230 | "strict": True 231 | } 232 | } 233 | ] 234 | ``` 235 | 236 | ### 4. Register Function in Tool Handler 237 | Update `tools/tool_handler.py`: 238 | 239 | ```python 240 | from .your_api_tools import your_api_function 241 | 242 | @lru_cache(maxsize=1) 243 | def get_function_map(): 244 | return { 245 | # ... existing functions ... 246 | "your_api_function": your_api_function, 247 | } 248 | ``` 249 | 250 | ### 5. Export Function 251 | Update `tools/__init__.py`: 252 | 253 | ```python 254 | from .your_api_tools import your_api_function 255 | 256 | __all__ = [ 257 | # ... existing exports ... 258 | 'your_api_function', 259 | ] 260 | ``` 261 | 262 | ### 6. Update Assistant Instructions 263 | Update `prompts.py` to include your new tool: 264 | 265 | ```python 266 | SUPER_ASSISTANT_INSTRUCTIONS = """ 267 | { 268 | # ... other sections ... 269 | "tools": { 270 | # ... existing tools ... 271 | "your_api_name": { 272 | "capabilities": ["List what your API can do"], 273 | "usage": "When to use this API", 274 | "restrictions": "Any API limitations or requirements", 275 | "error_handling": "How to handle common errors" 276 | } 277 | } 278 | } 279 | """ 280 | ``` 281 | 282 | ### 7. Best Practices 283 | 284 | #### Code Organization 285 | 1. Type Hints: 286 | - Use proper type hints for all functions 287 | - Import typing modules needed 288 | - Document return types 289 | 290 | 2. Environment Variables: 291 | - Always use python-dotenv 292 | - Check for missing variables early 293 | - Provide clear error messages 294 | 295 | 3. Error Handling: 296 | - Use specific exception types 297 | - Provide detailed error messages 298 | - Add timeouts to API calls 299 | - Handle rate limits 300 | - Log errors appropriately 301 | 302 | 4. Testing: 303 | - Include unit tests 304 | - Test with real API keys 305 | - Test error conditions 306 | - Test rate limits 307 | - Test with various inputs 308 | 309 | #### OpenAI Function Schema 310 | 1. Parameter Definitions: 311 | - NEVER use 'default' in parameter definitions 312 | - List ALL parameters in 'required' array 313 | - Use 'enum' for fixed values 314 | - Example: 315 | ```python 316 | # CORRECT 317 | { 318 | "type": "object", 319 | "properties": { 320 | "param1": { 321 | "type": "string", 322 | "description": "Description (uses 'default' if not specified)" 323 | }, 324 | "fixed_param": { 325 | "type": "integer", 326 | "description": "Fixed value parameter", 327 | "enum": [1024] 328 | } 329 | }, 330 | "required": ["param1", "fixed_param"] 331 | } 332 | ``` 333 | 334 | 2. Common Schema Mistakes: 335 | - Using 'default' in parameters 336 | - Missing parameters in 'required' 337 | - Incorrect type definitions 338 | - Missing descriptions 339 | 340 | ### 8. Common Issues and Solutions 341 | 342 | 1. API Integration: 343 | ```python 344 | # CORRECT 345 | try: 346 | response = requests.get(url, timeout=10) 347 | response.raise_for_status() 348 | except requests.exceptions.RequestException as e: 349 | return f"API error: {str(e)}" 350 | ``` 351 | 352 | 2. Environment Variables: 353 | ```python 354 | # CORRECT 355 | if not (api_key := os.getenv("YOUR_API_KEY")): 356 | raise ValueError("YOUR_API_KEY not set") 357 | ``` 358 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAI_Assistant_API_Boilerplate 2 | 3 | Super simple boilerplate for using the OpenAI Assistant API + Cursor Rules for you to start building personal AI Agents 4 | 5 | ## How to use 6 | 7 | 1. Clone the repository: 8 | ``` 9 | git clone https://github.com/ali-abassi/OpenAI_Assistant_API_Boilerplate.git 10 | ``` 11 | 12 | 2. Change `.SAMPLEenv` to `.env` and add your API keys: 13 | - Open the `.env` file 14 | - Add your OpenAI API Key 15 | - Add your Assistant ID 16 | 17 | 3. Install the required packages: 18 | ``` 19 | pip install -r requirements.txt 20 | ``` 21 | 22 | 4. Run the script: 23 | ``` 24 | python main.py 25 | ``` 26 | 27 | 5. Start chatting with your personal assistant! 28 | 29 | 6. Use the cursor composer to start adding your own tools along with documentation details from the apis you want to use. Use the attached `AddTools_CursorInstructions.md` to help you. 30 | 31 | ## Features 32 | 33 | - OpenAI Assistant API integration 34 | - File operations (read/write) 35 | - Rich terminal interface 36 | - Conversation persistence so it recalls your prior convos until you tell it to 'reset' 37 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | from openai import OpenAI 5 | from dotenv import load_dotenv 6 | from typing import Optional, Dict, Any 7 | 8 | # Add project root to path 9 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 10 | 11 | # Import local modules 12 | from tools import handle_tool_calls, get_tool_definitions 13 | from terminalstyle import ( 14 | print_assistant_response, 15 | print_system_message, 16 | print_code, 17 | clear_screen, 18 | print_welcome_message, 19 | print_divider, 20 | get_user_input, 21 | print_tool_usage, 22 | ) 23 | from prompts import SUPER_ASSISTANT_INSTRUCTIONS 24 | 25 | # Constants 26 | THREAD_ID_FILE = "thread_id.txt" 27 | MODEL_NAME = "gpt-4o-mini" # Using the latest model 28 | 29 | class AssistantManager: 30 | def __init__(self): 31 | """Initialize the AssistantManager with OpenAI client and configuration.""" 32 | # Load environment variables with override to ensure we get the .env values 33 | load_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)), '.env'), override=True) 34 | 35 | # Initialize OpenAI client 36 | self.api_key = os.getenv("OPENAI_API_KEY") 37 | if not self.api_key: 38 | raise ValueError("OPENAI_API_KEY not found in environment variables") 39 | 40 | self.client = OpenAI(api_key=self.api_key) 41 | self.assistant_id = os.getenv("ASSISTANT_ID") 42 | if not self.assistant_id: 43 | raise ValueError("ASSISTANT_ID not found in environment variables") 44 | 45 | # Get the existing assistant and update its configuration 46 | try: 47 | self.assistant = self.client.beta.assistants.retrieve(self.assistant_id) 48 | self.update_assistant_configuration() 49 | except Exception as e: 50 | raise ValueError(f"Could not retrieve assistant with ID {self.assistant_id}: {str(e)}") 51 | 52 | self.thread_id = self.load_thread_id() 53 | 54 | def load_thread_id(self) -> Optional[str]: 55 | """Load thread ID from file if it exists.""" 56 | if os.path.exists(THREAD_ID_FILE): 57 | with open(THREAD_ID_FILE, "r") as file: 58 | return file.read().strip() 59 | return None 60 | 61 | def save_thread_id(self, thread_id: str) -> None: 62 | """Save thread ID to file.""" 63 | with open(THREAD_ID_FILE, "w") as file: 64 | file.write(thread_id) 65 | 66 | def create_assistant(self) -> Dict[str, Any]: 67 | """Create a new assistant with specified configuration.""" 68 | return self.client.beta.assistants.create( 69 | name="Super Assistant", 70 | instructions=SUPER_ASSISTANT_INSTRUCTIONS, 71 | model=MODEL_NAME, 72 | tools=get_tool_definitions() 73 | ) 74 | 75 | def cancel_active_runs(self) -> None: 76 | """Cancel any active runs on the current thread.""" 77 | if not self.thread_id: 78 | return 79 | 80 | try: 81 | runs = self.client.beta.threads.runs.list(thread_id=self.thread_id) 82 | for run in runs.data: 83 | if run.status in ["in_progress", "queued", "requires_action"]: 84 | try: 85 | self.client.beta.threads.runs.cancel( 86 | thread_id=self.thread_id, 87 | run_id=run.id 88 | ) 89 | except Exception: 90 | pass 91 | except Exception: 92 | pass 93 | 94 | def wait_for_completion(self, run_id: str) -> Optional[Any]: 95 | """Wait for a run to complete and handle any required actions.""" 96 | while True: 97 | run = self.client.beta.threads.runs.retrieve( 98 | thread_id=self.thread_id, 99 | run_id=run_id 100 | ) 101 | 102 | if run.status == "requires_action": 103 | try: 104 | tool_outputs = handle_tool_calls(run) 105 | for tool_call in run.required_action.submit_tool_outputs.tool_calls: 106 | print_tool_usage(tool_call.function.name) 107 | run = self.client.beta.threads.runs.submit_tool_outputs( 108 | thread_id=self.thread_id, 109 | run_id=run_id, 110 | tool_outputs=tool_outputs 111 | ) 112 | except Exception as e: 113 | print_system_message(f"Error handling tool calls: {str(e)}") 114 | return None 115 | 116 | elif run.status == "completed": 117 | return run 118 | 119 | elif run.status in ["failed", "cancelled", "expired"]: 120 | print_system_message(f"Run ended with status: {run.status}") 121 | return None 122 | 123 | time.sleep(0.5) 124 | 125 | def reset_thread(self) -> None: 126 | """Reset the conversation thread.""" 127 | self.cancel_active_runs() 128 | self.thread_id = None 129 | if os.path.exists(THREAD_ID_FILE): 130 | os.remove(THREAD_ID_FILE) 131 | print_system_message("Thread reset. Starting a new conversation.") 132 | 133 | def process_user_input(self, user_input: str) -> bool: 134 | """Process user input and return whether to continue the conversation.""" 135 | if user_input.lower() == "reset": 136 | self.reset_thread() 137 | return True 138 | 139 | if user_input.lower() == "quit": 140 | self.cancel_active_runs() 141 | print_system_message("Thank you for chatting! Goodbye.") 142 | return False 143 | 144 | try: 145 | # Create or ensure thread exists 146 | if self.thread_id is None: 147 | thread = self.client.beta.threads.create() 148 | self.thread_id = thread.id 149 | self.save_thread_id(self.thread_id) 150 | else: 151 | self.cancel_active_runs() 152 | 153 | # Create message and run 154 | self.client.beta.threads.messages.create( 155 | thread_id=self.thread_id, 156 | role="user", 157 | content=user_input 158 | ) 159 | 160 | run = self.client.beta.threads.runs.create( 161 | thread_id=self.thread_id, 162 | assistant_id=self.assistant.id 163 | ) 164 | 165 | # Wait for completion and process response 166 | if completed_run := self.wait_for_completion(run.id): 167 | messages = self.client.beta.threads.messages.list(thread_id=self.thread_id) 168 | for message in messages.data: 169 | if message.role == "assistant": 170 | print_assistant_response(message.content[0].text.value) 171 | break 172 | 173 | except Exception as e: 174 | print_system_message(f"An error occurred: {str(e)}") 175 | print_system_message("Starting a new conversation...") 176 | self.reset_thread() 177 | 178 | return True 179 | 180 | def run(self) -> None: 181 | """Main conversation loop.""" 182 | try: 183 | clear_screen() 184 | print_welcome_message() 185 | print_divider() 186 | 187 | while True: 188 | user_input = get_user_input() 189 | print_divider() 190 | 191 | if not self.process_user_input(user_input): 192 | break 193 | 194 | print_divider() 195 | 196 | except Exception as e: 197 | print_system_message(f"Fatal error: {str(e)}") 198 | sys.exit(1) 199 | 200 | def update_assistant_configuration(self) -> None: 201 | """Update the assistant with current tools and instructions.""" 202 | try: 203 | print_system_message("Updating assistant configuration...") 204 | self.assistant = self.client.beta.assistants.update( 205 | assistant_id=self.assistant_id, 206 | instructions=SUPER_ASSISTANT_INSTRUCTIONS, 207 | tools=get_tool_definitions(), 208 | model=MODEL_NAME 209 | ) 210 | print_system_message("Assistant configuration updated successfully!") 211 | except Exception as e: 212 | print_system_message(f"Warning: Failed to update assistant configuration: {str(e)}") 213 | 214 | def main(): 215 | """Entry point of the application.""" 216 | try: 217 | assistant_manager = AssistantManager() 218 | assistant_manager.run() 219 | except KeyboardInterrupt: 220 | print_system_message("\nGoodbye!") 221 | except Exception as e: 222 | print_system_message(f"Fatal error: {str(e)}") 223 | sys.exit(1) 224 | 225 | if __name__ == "__main__": 226 | main() 227 | -------------------------------------------------------------------------------- /prompts.py: -------------------------------------------------------------------------------- 1 | SUPER_ASSISTANT_INSTRUCTIONS = """ 2 | { 3 | "name": "Super Assistant", 4 | "role": "Helpful AI Assistant", 5 | "description": "An exceptionally capable and empathetic AI assistant focused on providing maximum value and support to users. Combines deep technical expertise with emotional intelligence to deliver the most helpful experience possible.", 6 | "core_values": { 7 | "helpfulness": "Always prioritizes being maximally useful to the user", 8 | "empathy": "Deeply understands user needs and adapts approach accordingly", 9 | "excellence": "Strives for exceptional quality in every interaction", 10 | "growth": "Continuously learns from interactions to provide better assistance" 11 | }, 12 | "characteristics": { 13 | "intellectual_approach": { 14 | "first_principles": "Breaks down complex problems to fundamental truths and builds up from there", 15 | "adaptive_learning": "Quickly grasps user's context and adjusts explanations accordingly", 16 | "systems_thinking": "Analyzes problems holistically, considering all interconnections", 17 | "creative_solutions": "Generates innovative approaches to challenging problems" 18 | }, 19 | "personality": { 20 | "mindset": ["Proactive", "Detail-oriented", "Solution-focused", "User-centric"], 21 | "interaction": ["Warm & Approachable", "Clear Communication", "Patient Teacher", "Supportive Guide"], 22 | "style": "Combines technical expertise with friendly, accessible communication" 23 | } 24 | }, 25 | "conversational_style": { 26 | "tone": "Direct, confident, and action-oriented", 27 | "communication": "Crisp, efficient, and straight to the point", 28 | "approach": "Takes initiative, drives results, and gets things done" 29 | }, 30 | "problem_solving": { 31 | "methodology": { 32 | "understand": "Thoroughly grasps the user's needs and context", 33 | "clarify": "Asks targeted questions to ensure full understanding", 34 | "solve": "Provides comprehensive, implementable solutions", 35 | "verify": "Confirms solution effectiveness and user satisfaction" 36 | } 37 | }, 38 | "tools": { 39 | "file_operations": { 40 | "workspace": "agent_directory folder", 41 | "capabilities": ["Read files", "Write files", "List files"], 42 | "restrictions": "Can only access files within agent_directory" 43 | }, 44 | "user_details": { 45 | "name": "", 46 | "expertise_level": "", 47 | "goals": "", 48 | "preferences": "", 49 | "context": "" 50 | } 51 | }""" 52 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Core OpenAI and API Dependencies 2 | openai>=1.3.0 # OpenAI API client 3 | python-dotenv>=0.19.0 # For environment variables 4 | 5 | # Terminal UI Dependencies 6 | rich>=13.0.0 # For rich terminal interface and formatting 7 | 8 | # Utility Dependencies 9 | tenacity>=8.0.0 # For retry functionality 10 | typing-extensions>=4.0.0 # For type hints and overrides 11 | -------------------------------------------------------------------------------- /terminalstyle.py: -------------------------------------------------------------------------------- 1 | from rich.console import Console 2 | from rich.markdown import Markdown 3 | from rich.panel import Panel 4 | from rich.syntax import Syntax 5 | from rich.text import Text 6 | from rich.box import ROUNDED 7 | 8 | console = Console() 9 | 10 | def print_assistant_response(text): 11 | console.print() # Add a blank line before the assistant's response 12 | md = Markdown(text) 13 | console.print(Panel(md, border_style="green", box=ROUNDED, expand=False, title="AI Agent", title_align="left")) 14 | console.print() # Add a blank line after the assistant's response 15 | 16 | def print_system_message(text): 17 | console.print() # Add a blank line before the system message 18 | system_text = Text(text, style="yellow") 19 | console.print(Panel(system_text, border_style="yellow", box=ROUNDED, expand=False, title="System", title_align="left")) 20 | console.print() # Add a blank line after the system message 21 | 22 | def print_code(code, language="python"): 23 | syntax = Syntax(code, language, theme="monokai", line_numbers=True) 24 | console.print(Panel(syntax, border_style="red", box=ROUNDED, expand=False, title=f"Code ({language})", title_align="left")) 25 | 26 | def print_tool_usage(tool_name): 27 | console.print(f"\n🔧 [bold blue]Tool Used:[/bold blue] {tool_name}") 28 | 29 | def clear_screen(): 30 | console.clear() 31 | 32 | def print_welcome_message(): 33 | welcome_text = Text.from_markup(""" 34 | [bold magenta]Welcome to the AI Agent Chat![/bold magenta] 35 | 36 | [bold cyan]Available Commands:[/bold cyan] 37 | • [yellow]reset[/yellow] - Start a new conversation 38 | • [yellow]quit[/yellow] - Exit the program 39 | """) 40 | console.print(Panel(welcome_text, border_style="magenta", box=ROUNDED, expand=False)) 41 | console.print() 42 | 43 | def print_divider(): 44 | console.print("─" * console.width, style="dim") 45 | 46 | def get_user_input(): 47 | user_input = console.input("\n[bold cyan]You:[/bold cyan] ") # Add a newline before the prompt 48 | console.print() # Add a blank line after the user's input 49 | return user_input 50 | -------------------------------------------------------------------------------- /tools/__init__.py: -------------------------------------------------------------------------------- 1 | from .tool_handler import handle_tool_calls 2 | from .tool_definitions import get_tool_definitions 3 | from .file_tools import read_file, write_file, list_files 4 | 5 | __all__ = [ 6 | 'handle_tool_calls', 7 | 'get_tool_definitions', 8 | 'read_file', 9 | 'write_file', 10 | 'list_files', 11 | ] -------------------------------------------------------------------------------- /tools/file_tools.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from functools import lru_cache 4 | 5 | AGENT_DIRECTORY = "agent_directory" 6 | 7 | # Cache the directory check 8 | @lru_cache(maxsize=1) 9 | def get_agent_directory(): 10 | """Get or create the agent directory.""" 11 | if not os.path.exists(AGENT_DIRECTORY): 12 | os.makedirs(AGENT_DIRECTORY) 13 | return AGENT_DIRECTORY 14 | 15 | def get_full_path(file_path): 16 | """Get the full path for a file.""" 17 | return os.path.join(get_agent_directory(), file_path) 18 | 19 | def read_file(file_path): 20 | """Read a file from the agent directory.""" 21 | try: 22 | with open(get_full_path(file_path), 'r') as file: 23 | return file.read() 24 | except Exception as e: 25 | return f"Error reading file: {str(e)}" 26 | 27 | def write_file(file_path, content=""): 28 | """Write content to a file in the agent directory.""" 29 | try: 30 | with open(get_full_path(file_path), 'w') as file: 31 | file.write(content) 32 | return f"Successfully wrote to {file_path}" 33 | except Exception as e: 34 | return f"Error writing file: {str(e)}" 35 | 36 | def list_files(): 37 | """List all files in the agent directory.""" 38 | try: 39 | files = os.listdir(get_agent_directory()) 40 | return json.dumps(files) 41 | except Exception as e: 42 | return f"Error listing files: {str(e)}" -------------------------------------------------------------------------------- /tools/tool_definitions.py: -------------------------------------------------------------------------------- 1 | def get_tool_definitions(): 2 | """Return the list of tool definitions for the assistant.""" 3 | return [ 4 | { 5 | "type": "function", 6 | "function": { 7 | "name": "read_file", 8 | "description": "Read the contents of a file from the working directory", 9 | "parameters": { 10 | "type": "object", 11 | "properties": { 12 | "file_path": { 13 | "type": "string", 14 | "description": "The name of the file to read" 15 | } 16 | }, 17 | "required": ["file_path"], 18 | "additionalProperties": False 19 | }, 20 | "strict": True 21 | } 22 | }, 23 | { 24 | "type": "function", 25 | "function": { 26 | "name": "write_file", 27 | "description": "Write content to a file in the working directory", 28 | "parameters": { 29 | "type": "object", 30 | "properties": { 31 | "file_path": { 32 | "type": "string", 33 | "description": "The name of the file to write" 34 | }, 35 | "content": { 36 | "type": "string", 37 | "description": "The content to write to the file" 38 | } 39 | }, 40 | "required": ["file_path", "content"], 41 | "additionalProperties": False 42 | }, 43 | "strict": True 44 | } 45 | } 46 | ] -------------------------------------------------------------------------------- /tools/tool_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | from functools import lru_cache 3 | from .file_tools import read_file, write_file, list_files 4 | from tenacity import retry, stop_after_attempt, wait_exponential 5 | from typing import List, Dict, Any, Optional 6 | from openai.types.beta.threads import Run 7 | 8 | # Cache the function mapping 9 | @lru_cache(maxsize=1) 10 | def get_function_map(): 11 | return { 12 | "read_file": read_file, 13 | "write_file": write_file, 14 | "list_files": list_files, 15 | } 16 | 17 | @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10)) 18 | def handle_tool_calls(run: Run) -> List[Dict[str, Any]]: 19 | """ 20 | Handle tool calls from the assistant. 21 | 22 | Args: 23 | run (Run): The current run object from OpenAI 24 | 25 | Returns: 26 | List[Dict[str, Any]]: List of tool outputs 27 | 28 | Raises: 29 | ValueError: If tool execution fails 30 | """ 31 | tool_outputs = [] 32 | function_map = get_function_map() 33 | 34 | for tool_call in run.required_action.submit_tool_outputs.tool_calls: 35 | function_name = tool_call.function.name 36 | function_args = json.loads(tool_call.function.arguments) 37 | 38 | if function_name in function_map: 39 | try: 40 | output = function_map[function_name](**function_args) 41 | tool_outputs.append({ 42 | "tool_call_id": tool_call.id, 43 | "output": output 44 | }) 45 | except Exception as e: 46 | tool_outputs.append({ 47 | "tool_call_id": tool_call.id, 48 | "output": f"Error executing {function_name}: {str(e)}" 49 | }) 50 | 51 | return tool_outputs --------------------------------------------------------------------------------