OverviewFilesCommitsIssuesDocsReleasesPeers

ollama_semantic_function_calling commits / cd1199f

Add AI-only simplification guide (work in progress)
cd1199fb886f7fe961a0de0a4b6b038e6af0ee62
by fleeky.ashatron <fleeky@7rnx.net> · 2025-04-19T01:31:38.000Z

Changed files (1) +1073 -0

ollamatools-project/AI_SIMPLIFICATION_GUIDE.md +1073 -0
……@@ -0,0 +1,1073 @@
1# AI-Only OllamaTools Simplification Guide
2
3This document provides detailed, step-by-step instructions for AI assistants to implement the OllamaTools simplification plan. Follow these instructions precisely to transform the codebase according to the architecture outlined in `SIMPLIFICATION_PLAN.md`.
4
5## Initial Setup and Analysis
6
71. **Clone the repository and checkout the simplify branch**
8 ```bash
9 git clone [repository-url]
10 cd ollamatools
11 git checkout simplify
12 ```
13
142. **Analyze the current codebase structure**
15 - Read through the key files to understand the current architecture:
16 - `ollamatools/__init__.py`
17 - `ollamatools/agent.py`
18 - `ollamatools/agent/modular/agent.py`
19 - `ollamatools/agent/extended/agent.py`
20 - `ollamatools/tools/matrix_tools.py`
21 - `ollamatools/plugins/registry.py`
22 - Identify dependencies between components
23 - Note any circular dependencies that need to be resolved
24
253. **Review the simplification plan**
26 - Read `SIMPLIFICATION_PLAN.md` to understand the target architecture
27 - Review `SIMPLIFICATION_PROMPT_TEMPLATE.md` for guidance on implementation
28 - Refer to `SIMPLIFY_README.md` for an overview of the project
29
30## Phase 1: Core Infrastructure Implementation
31
32### Step 1: Create Directory Structure
33
34Create the following directory structure:
35
36```
37ollamatools/
38├── core/
39│ ├── __init__.py
40│ ├── agent.py
41│ ├── config.py
42│ ├── errors.py
43│ ├── security.py
44│ └── utils/
45│ ├── __init__.py
46│ ├── logger.py
47│ ├── validators.py
48│ └── formatting.py
49```
50
51### Step 2: Implement Core Module Initialization
52
53Create `ollamatools/core/__init__.py`:
54
55```python
56"""
57Core module for OllamaTools.
58
59This module provides the essential functionality that all other components depend on.
60It's designed to be minimal, stable, and rarely change.
61"""
62
63__version__ = "1.0.0"
64
65from .agent import Agent
66from .config import load_config, save_config
67from .errors import OllamaToolsError, ConfigError, SecurityError
68
69__all__ = [
70 "Agent",
71 "load_config",
72 "save_config",
73 "OllamaToolsError",
74 "ConfigError",
75 "SecurityError"
76]
77```
78
79### Step 3: Implement Core Error Handling
80
81Create `ollamatools/core/errors.py`:
82
83```python
84"""
85Error definitions and handling for OllamaTools.
86
87This module defines the error types used throughout the OllamaTools project
88and provides utilities for error handling and formatting.
89"""
90
91class OllamaToolsError(Exception):
92 """Base exception for all OllamaTools errors."""
93
94 def __init__(self, message, details=None):
95 """Initialize the error with a message and optional details."""
96 self.message = message
97 self.details = details or {}
98 super().__init__(message)
99
100
101class ConfigError(OllamaToolsError):
102 """Error raised when there's a problem with configuration."""
103 pass
104
105
106class SecurityError(OllamaToolsError):
107 """Error raised when there's a security violation."""
108 pass
109
110
111class ValidationError(OllamaToolsError):
112 """Error raised when validation fails."""
113 pass
114
115
116def format_error(error):
117 """Format an error for display to the user."""
118 if isinstance(error, OllamaToolsError):
119 result = f"Error: {error.message}"
120 if error.details:
121 result += "\nDetails:"
122 for key, value in error.details.items():
123 result += f"\n {key}: {value}"
124 return result
125 return f"Error: {str(error)}"
126
127
128def handle_error(error, logger=None):
129 """Handle an error, logging it if a logger is provided."""
130 formatted = format_error(error)
131 if logger:
132 logger.error(formatted)
133 return formatted
134```
135
136### Step 4: Implement Core Security
137
138Create `ollamatools/core/security.py`:
139
140```python
141"""
142Security utilities and permission system for OllamaTools.
143
144This module provides security-related functionality, including permission
145checking, validation, and secure defaults.
146"""
147
148import os
149import re
150from .errors import SecurityError, ValidationError
151
152# Safe command patterns
153SAFE_COMMAND_PATTERN = re.compile(r'^[a-zA-Z0-9_\-./\s]+$')
154
155# Safe path patterns
156SAFE_PATH_PATTERN = re.compile(r'^[a-zA-Z0-9_\-./\s]+$')
157
158# Safe URL patterns
159SAFE_URL_PATTERN = re.compile(r'^https?://[a-zA-Z0-9_\-./]+')
160
161
162def validate_command(command):
163 """Validate that a command is safe to execute."""
164 if not command or not isinstance(command, str):
165 raise ValidationError("Command must be a non-empty string")
166
167 if not SAFE_COMMAND_PATTERN.match(command):
168 raise SecurityError(
169 "Command contains potentially unsafe characters",
170 {"command": command}
171 )
172
173 # Check for dangerous commands
174 dangerous_commands = ["rm -rf", "sudo", "chmod", "chown"]
175 for dangerous in dangerous_commands:
176 if dangerous in command:
177 raise SecurityError(
178 f"Command contains potentially dangerous operation: {dangerous}",
179 {"command": command}
180 )
181
182 return True
183
184
185def validate_path(path):
186 """Validate that a path is safe to access."""
187 if not path or not isinstance(path, str):
188 raise ValidationError("Path must be a non-empty string")
189
190 if not SAFE_PATH_PATTERN.match(path):
191 raise SecurityError(
192 "Path contains potentially unsafe characters",
193 {"path": path}
194 )
195
196 # Resolve to absolute path
197 abs_path = os.path.abspath(path)
198
199 # Check for path traversal
200 if ".." in path:
201 raise SecurityError(
202 "Path contains directory traversal",
203 {"path": path, "resolved": abs_path}
204 )
205
206 return True
207
208
209def validate_url(url):
210 """Validate that a URL is safe to access."""
211 if not url or not isinstance(url, str):
212 raise ValidationError("URL must be a non-empty string")
213
214 if not SAFE_URL_PATTERN.match(url):
215 raise SecurityError(
216 "URL contains potentially unsafe characters or protocol",
217 {"url": url}
218 )
219
220 return True
221
222
223class PermissionManager:
224 """Manage permissions for function execution."""
225
226 def __init__(self, config=None):
227 """Initialize the permission manager with optional configuration."""
228 self.config = config or {}
229 self.permissions = self.config.get("permissions", {})
230 self.default_permission = self.config.get("default_permission", False)
231
232 def check_permission(self, function_name, user_id=None):
233 """Check if a function is permitted to be executed."""
234 # Check function-specific permission
235 if function_name in self.permissions:
236 return self.permissions[function_name]
237
238 # Check user-specific permission
239 if user_id and user_id in self.permissions:
240 user_perms = self.permissions[user_id]
241 if function_name in user_perms:
242 return user_perms[function_name]
243
244 # Fall back to default permission
245 return self.default_permission
246
247 def grant_permission(self, function_name, user_id=None):
248 """Grant permission for a function."""
249 if user_id:
250 if user_id not in self.permissions:
251 self.permissions[user_id] = {}
252 self.permissions[user_id][function_name] = True
253 else:
254 self.permissions[function_name] = True
255
256 def revoke_permission(self, function_name, user_id=None):
257 """Revoke permission for a function."""
258 if user_id:
259 if user_id in self.permissions and function_name in self.permissions[user_id]:
260 self.permissions[user_id][function_name] = False
261 else:
262 self.permissions[function_name] = False
263```
264
265### Step 5: Implement Core Utils
266
267Create `ollamatools/core/utils/__init__.py`:
268
269```python
270"""
271Utility functions for OllamaTools.
272
273This module provides common utility functions used throughout the OllamaTools project.
274"""
275
276from .logger import setup_logger, get_logger
277from .validators import validate_command, validate_path, validate_url
278from .formatting import format_text, format_markdown
279
280__all__ = [
281 "setup_logger",
282 "get_logger",
283 "validate_command",
284 "validate_path",
285 "validate_url",
286 "format_text",
287 "format_markdown"
288]
289```
290
291Create `ollamatools/core/utils/logger.py`:
292
293```python
294"""
295Logging utilities for OllamaTools.
296
297This module provides logging configuration and utilities.
298"""
299
300import logging
301import os
302import sys
303from datetime import datetime
304
305# Default log format
306DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
307
308# Default log level
309DEFAULT_LOG_LEVEL = logging.INFO
310
311# Global logger dictionary
312_loggers = {}
313
314
315def setup_logger(name, level=None, log_format=None, log_file=None):
316 """Set up a logger with the specified configuration."""
317 if name in _loggers:
318 return _loggers[name]
319
320 # Create logger
321 logger = logging.getLogger(name)
322
323 # Set level
324 logger.setLevel(level or DEFAULT_LOG_LEVEL)
325
326 # Create formatter
327 formatter = logging.Formatter(log_format or DEFAULT_LOG_FORMAT)
328
329 # Create console handler
330 console_handler = logging.StreamHandler(sys.stdout)
331 console_handler.setFormatter(formatter)
332 logger.addHandler(console_handler)
333
334 # Create file handler if log_file is specified
335 if log_file:
336 os.makedirs(os.path.dirname(log_file), exist_ok=True)
337 file_handler = logging.FileHandler(log_file)
338 file_handler.setFormatter(formatter)
339 logger.addHandler(file_handler)
340
341 # Store logger
342 _loggers[name] = logger
343
344 return logger
345
346
347def get_logger(name):
348 """Get a logger by name, creating it if it doesn't exist."""
349 if name not in _loggers:
350 return setup_logger(name)
351 return _loggers[name]
352```
353
354Create `ollamatools/core/utils/validators.py`:
355
356```python
357"""
358Input validation utilities for OllamaTools.
359
360This module provides validation functions for commands, paths, and URLs.
361"""
362
363import os
364import re
365from ..errors import ValidationError, SecurityError
366
367# Safe command patterns
368SAFE_COMMAND_PATTERN = re.compile(r'^[a-zA-Z0-9_\-./\s]+$')
369
370# Safe path patterns
371SAFE_PATH_PATTERN = re.compile(r'^[a-zA-Z0-9_\-./\s]+$')
372
373# Safe URL patterns
374SAFE_URL_PATTERN = re.compile(r'^https?://[a-zA-Z0-9_\-./]+')
375
376
377def validate_command(command):
378 """Validate that a command is safe to execute."""
379 if not command or not isinstance(command, str):
380 raise ValidationError("Command must be a non-empty string")
381
382 if not SAFE_COMMAND_PATTERN.match(command):
383 raise SecurityError(
384 "Command contains potentially unsafe characters",
385 {"command": command}
386 )
387
388 # Check for dangerous commands
389 dangerous_commands = ["rm -rf", "sudo", "chmod", "chown"]
390 for dangerous in dangerous_commands:
391 if dangerous in command:
392 raise SecurityError(
393 f"Command contains potentially dangerous operation: {dangerous}",
394 {"command": command}
395 )
396
397 return True
398
399
400def validate_path(path):
401 """Validate that a path is safe to access."""
402 if not path or not isinstance(path, str):
403 raise ValidationError("Path must be a non-empty string")
404
405 if not SAFE_PATH_PATTERN.match(path):
406 raise SecurityError(
407 "Path contains potentially unsafe characters",
408 {"path": path}
409 )
410
411 # Resolve to absolute path
412 abs_path = os.path.abspath(path)
413
414 # Check for path traversal
415 if ".." in path:
416 raise SecurityError(
417 "Path contains directory traversal",
418 {"path": path, "resolved": abs_path}
419 )
420
421 return True
422
423
424def validate_url(url):
425 """Validate that a URL is safe to access."""
426 if not url or not isinstance(url, str):
427 raise ValidationError("URL must be a non-empty string")
428
429 if not SAFE_URL_PATTERN.match(url):
430 raise SecurityError(
431 "URL contains potentially unsafe characters or protocol",
432 {"url": url}
433 )
434
435 return True
436```
437
438Create `ollamatools/core/utils/formatting.py`:
439
440```python
441"""
442Text formatting utilities for OllamaTools.
443
444This module provides functions for formatting text and generating markdown.
445"""
446
447def format_text(text, width=80, indent=0):
448 """Format text to the specified width with optional indentation."""
449 if not text:
450 return ""
451
452 # Split text into words
453 words = text.split()
454
455 # Initialize result
456 result = []
457 current_line = " " * indent
458
459 # Process words
460 for word in words:
461 # Check if adding the word would exceed the width
462 if len(current_line) + len(word) + 1 > width and len(current_line.strip()) > 0:
463 # Add current line to result and start a new line
464 result.append(current_line)
465 current_line = " " * indent + word
466 else:
467 # Add word to current line
468 if len(current_line.strip()) > 0:
469 current_line += " " + word
470 else:
471 current_line += word
472
473 # Add the last line
474 if current_line.strip():
475 result.append(current_line)
476
477 return "\n".join(result)
478
479
480def format_markdown(text, heading_level=1, code_block=False, language=None):
481 """Format text as markdown with optional heading and code block."""
482 if not text:
483 return ""
484
485 result = []
486
487 # Add heading if heading_level is specified
488 if heading_level > 0:
489 result.append("#" * heading_level + " " + text.split("\n")[0])
490 text = "\n".join(text.split("\n")[1:])
491
492 # Add code block if specified
493 if code_block:
494 if language:
495 result.append(f"```{language}")
496 else:
497 result.append("```")
498 result.append(text)
499 result.append("```")
500 else:
501 result.append(text)
502
503 return "\n".join(result)
504```
505
506### Step 6: Implement Core Configuration
507
508Create `ollamatools/core/config.py`:
509
510```python
511"""
512Configuration management for OllamaTools.
513
514This module provides functions for loading, validating, and saving configuration.
515"""
516
517import os
518import yaml
519from .errors import ConfigError
520
521# Default configuration
522DEFAULT_CONFIG = {
523 "agent": {
524 "model": "llama3:latest",
525 "prompt": "You are a helpful assistant.",
526 "temperature": 0.7,
527 "max_tokens": 1000
528 },
529 "security": {
530 "permission_prompts": True,
531 "default_permission": False
532 },
533 "logging": {
534 "level": "INFO",
535 "file": None
536 },
537 "plugins": {
538 "enabled": []
539 }
540}
541
542
543def load_config(config_path=None):
544 """Load configuration from a file."""
545 # Start with default configuration
546 config = DEFAULT_CONFIG.copy()
547
548 # If config_path is specified, load configuration from file
549 if config_path:
550 try:
551 with open(config_path, "r") as f:
552 file_config = yaml.safe_load(f)
553
554 # Merge file configuration with default configuration
555 if file_config:
556 _merge_config(config, file_config)
557 except Exception as e:
558 raise ConfigError(f"Failed to load configuration from {config_path}", {"error": str(e)})
559
560 return config
561
562
563def save_config(config, config_path):
564 """Save configuration to a file."""
565 try:
566 # Create directory if it doesn't exist
567 os.makedirs(os.path.dirname(config_path), exist_ok=True)
568
569 # Save configuration to file
570 with open(config_path, "w") as f:
571 yaml.dump(config, f, default_flow_style=False)
572 except Exception as e:
573 raise ConfigError(f"Failed to save configuration to {config_path}", {"error": str(e)})
574
575
576def validate_config(config, schema=None):
577 """Validate configuration against a schema."""
578 # If schema is not specified, use a basic schema
579 if not schema:
580 schema = {
581 "agent": {
582 "required": True,
583 "type": dict
584 },
585 "security": {
586 "required": True,
587 "type": dict
588 },
589 "logging": {
590 "required": True,
591 "type": dict
592 },
593 "plugins": {
594 "required": True,
595 "type": dict
596 }
597 }
598
599 # Validate configuration against schema
600 for key, value in schema.items():
601 if value.get("required", False) and key not in config:
602 raise ConfigError(f"Missing required configuration key: {key}")
603
604 if key in config and value.get("type") and not isinstance(config[key], value["type"]):
605 raise ConfigError(f"Invalid type for configuration key: {key}")
606
607 return True
608
609
610def _merge_config(base_config, override_config):
611 """Merge override_config into base_config."""
612 for key, value in override_config.items():
613 if key in base_config and isinstance(base_config[key], dict) and isinstance(value, dict):
614 _merge_config(base_config[key], value)
615 else:
616 base_config[key] = value
617```
618
619### Step 7: Implement Base Agent
620
621Create `ollamatools/core/agent.py`:
622
623```python
624"""
625Base agent implementation for OllamaTools.
626
627This module provides the base Agent class that serves as the foundation
628for all agent implementations.
629"""
630
631import os
632import json
633from .errors import OllamaToolsError
634from .config import load_config
635from .security import PermissionManager
636
637class Agent:
638 """Base agent class for OllamaTools."""
639
640 def __init__(self, model=None, prompt=None, config=None, config_path=None):
641 """Initialize the agent with the specified configuration."""
642 # Load configuration
643 self.config = config or (load_config(config_path) if config_path else {})
644
645 # Set model and prompt
646 self.model = model or self.config.get("agent", {}).get("model", "llama3:latest")
647 self.prompt = prompt or self.config.get("agent", {}).get("prompt", "You are a helpful assistant.")
648
649 # Initialize permission manager
650 self.permission_manager = PermissionManager(self.config.get("security", {}))
651
652 # Initialize plugin registry
653 self.plugin_registry = {}
654 self.enabled_plugins = set()
655
656 def register_plugin(self, plugin_name, plugin_instance):
657 """Register a plugin with the agent."""
658 self.plugin_registry[plugin_name] = plugin_instance
659
660 def enable_plugin(self, plugin_name):
661 """Enable a plugin."""
662 if plugin_name not in self.plugin_registry:
663 raise OllamaToolsError(f"Plugin not found: {plugin_name}")
664
665 self.enabled_plugins.add(plugin_name)
666
667 def disable_plugin(self, plugin_name):
668 """Disable a plugin."""
669 if plugin_name in self.enabled_plugins:
670 self.enabled_plugins.remove(plugin_name)
671
672 def get_plugin(self, plugin_name):
673 """Get a plugin by name."""
674 if plugin_name not in self.plugin_registry:
675 raise OllamaToolsError(f"Plugin not found: {plugin_name}")
676
677 return self.plugin_registry[plugin_name]
678
679 def chat(self, message):
680 """Process a chat message and return a response."""
681 # This is a placeholder implementation that should be overridden by subclasses
682 return f"Base agent received: {message}"
683
684 def check_permission(self, function_name, user_id=None):
685 """Check if a function is permitted to be executed."""
686 return self.permission_manager.check_permission(function_name, user_id)
687
688 def grant_permission(self, function_name, user_id=None):
689 """Grant permission for a function."""
690 self.permission_manager.grant_permission(function_name, user_id)
691
692 def revoke_permission(self, function_name, user_id=None):
693 """Revoke permission for a function."""
694 self.permission_manager.revoke_permission(function_name, user_id)
695
696 def startup(self):
697 """Perform startup operations."""
698 # This is a placeholder implementation that should be overridden by subclasses
699 pass
700
701 def shutdown(self):
702 """Perform shutdown operations."""
703 # This is a placeholder implementation that should be overridden by subclasses
704 pass
705```
706
707## Phase 2: Plugin System Implementation
708
709### Step 1: Create Plugin System Directory Structure
710
711Create the following directory structure:
712
713```
714ollamatools/plugins/
715├── __init__.py
716├── base.py
717├── registry.py
718├── errors.py
719```
720
721### Step 2: Implement Plugin System Initialization
722
723Create `ollamatools/plugins/__init__.py`:
724
725```python
726"""
727Plugin system for OllamaTools.
728
729This module provides the infrastructure for extending OllamaTools functionality
730through plugins.
731"""
732
733from .base import Plugin, ToolPlugin, IntegrationPlugin, AgentPlugin
734from .registry import PluginRegistry
735from .errors import PluginError, PluginNotFoundError, PluginInitializationError
736
737__all__ = [
738 "Plugin",
739 "ToolPlugin",
740 "IntegrationPlugin",
741 "AgentPlugin",
742 "PluginRegistry",
743 "PluginError",
744 "PluginNotFoundError",
745 "PluginInitializationError"
746]
747```
748
749### Step 3: Implement Plugin Base Classes
750
751Create `ollamatools/plugins/base.py`:
752
753```python
754"""
755Base plugin interfaces for OllamaTools.
756
757This module defines the base plugin interfaces that all plugins must implement.
758"""
759
760class Plugin:
761 """Base interface for all OllamaTools plugins."""
762
763 def __init__(self, config=None):
764 """Initialize the plugin with optional configuration."""
765 self.config = config or {}
766
767 def initialize(self, context):
768 """Initialize the plugin with the runtime context."""
769 pass
770
771 def shutdown(self):
772 """Clean up resources when shutting down."""
773 pass
774
775 @property
776 def name(self):
777 """Get the plugin name."""
778 return self.__class__.__name__
779
780 @property
781 def version(self):
782 """Get the plugin version."""
783 return "1.0.0"
784
785 @property
786 def dependencies(self):
787 """Get plugin dependencies."""
788 return []
789
790 def get_capabilities(self):
791 """Get capabilities provided by this plugin."""
792 return []
793
794
795class ToolPlugin(Plugin):
796 """Base class for tool plugins."""
797
798 def execute(self, tool_name, args, context):
799 """Execute a tool with the specified arguments."""
800 raise NotImplementedError("Tool plugins must implement execute")
801
802 def get_tools(self):
803 """Get tools provided by this plugin."""
804 return {}
805
806 def get_tool_schema(self, tool_name):
807 """Get the schema for a tool."""
808 tools = self.get_tools()
809 if tool_name not in tools:
810 return None
811 return tools[tool_name].get("schema", {})
812
813
814class IntegrationPlugin(Plugin):
815 """Base class for integration plugins."""
816
817 def connect(self):
818 """Connect to the external system."""
819 raise NotImplementedError("Integration plugins must implement connect")
820
821 def disconnect(self):
822 """Disconnect from the external system."""
823 raise NotImplementedError("Integration plugins must implement disconnect")
824
825 def is_connected(self):
826 """Check if the plugin is connected to the external system."""
827 raise NotImplementedError("Integration plugins must implement is_connected")
828
829 def get_status(self):
830 """Get the status of the integration."""
831 return {
832 "connected": self.is_connected()
833 }
834
835
836class AgentPlugin(Plugin):
837 """Base class for agent plugins."""
838
839 def process_message(self, message, context):
840 """Process a message and return a response."""
841 return None
842
843 def get_commands(self):
844 """Get commands provided by this plugin."""
845 return {}
846
847 def on_function_call(self, function_name, args, context):
848 """Handle a function call."""
849 return None
850```
851
852### Step 4: Implement Plugin Registry
853
854Create `ollamatools/plugins/registry.py`:
855
856```python
857"""
858Plugin registry for OllamaTools.
859
860This module provides the PluginRegistry class for managing plugins.
861"""
862
863import importlib
864import pkg_resources
865from .errors import PluginNotFoundError, PluginInitializationError
866
867class PluginRegistry:
868 """Registry for OllamaTools plugins."""
869
870 def __init__(self):
871 """Initialize the plugin registry."""
872 self.plugins = {}
873 self.initialized_plugins = {}
874
875 def register_plugin(self, plugin_class, config=None):
876 """Register a plugin class."""
877 plugin_name = plugin_class.__name__
878 self.plugins[plugin_name] = {
879 "class": plugin_class,
880 "config": config
881 }
882 return plugin_name
883
884 def discover_plugins(self):
885 """Discover plugins using entry points."""
886 try:
887 for entry_point in pkg_resources.iter_entry_points('ollamatools.plugins'):
888 plugin_class = entry_point.load()
889 self.register_plugin(plugin_class)
890 except Exception as e:
891 # Log the error but continue
892 print(f"Error discovering plugins: {e}")
893
894 def initialize_plugin(self, plugin_name, context):
895 """Initialize a plugin and its dependencies."""
896 if plugin_name in self.initialized_plugins:
897 return self.initialized_plugins[plugin_name]
898
899 if plugin_name not in self.plugins:
900 raise PluginNotFoundError(f"Plugin {plugin_name} not found")
901
902 plugin_info = self.plugins[plugin_name]
903 plugin_class = plugin_info["class"]
904 plugin_config = plugin_info["config"]
905
906 try:
907 # Initialize dependencies first
908 plugin_instance = plugin_class(plugin_config)
909 for dependency in plugin_instance.dependencies:
910 self.initialize_plugin(dependency, context)
911
912 # Initialize the plugin
913 plugin_instance.initialize(context)
914 self.initialized_plugins[plugin_name] = plugin_instance
915
916 return plugin_instance
917 except Exception as e:
918 raise PluginInitializationError(
919 f"Failed to initialize plugin {plugin_name}",
920 {"error": str(e)}
921 )
922
923 def get_plugin(self, plugin_name):
924 """Get an initialized plugin."""
925 if plugin_name not in self.initialized_plugins:
926 raise PluginNotFoundError(f"Plugin {plugin_name} not initialized")
927 return self.initialized_plugins[plugin_name]
928
929 def get_plugins_by_type(self, plugin_type):
930 """Get all initialized plugins of a specific type."""
931 return {
932 name: plugin for name, plugin in self.initialized_plugins.items()
933 if isinstance(plugin, plugin_type)
934 }
935
936 def shutdown(self):
937 """Shutdown all initialized plugins."""
938 for plugin_name, plugin in self.initialized_plugins.items():
939 try:
940 plugin.shutdown()
941 except Exception as e:
942 # Log the error but continue
943 print(f"Error shutting down plugin {plugin_name}: {e}")
944
945 self.initialized_plugins = {}
946```
947
948### Step 5: Implement Plugin Errors
949
950Create `ollamatools/plugins/errors.py`:
951
952```python
953"""
954Error definitions for the OllamaTools plugin system.
955
956This module defines the error types used by the plugin system.
957"""
958
959from ..core.errors import OllamaToolsError
960
961class PluginError(OllamaToolsError):
962 """Base exception for all plugin-related errors."""
963 pass
964
965
966class PluginNotFoundError(PluginError):
967 """Error raised when a plugin is not found."""
968 pass
969
970
971class PluginInitializationError(PluginError):
972 """Error raised when a plugin fails to initialize."""
973 pass
974
975
976class PluginDependencyError(PluginError):
977 """Error raised when a plugin dependency cannot be satisfied."""
978 pass
979
980
981def handle_plugin_exception(exception):
982 """Handle a plugin exception and return error details."""
983 if isinstance(exception, PluginError):
984 return {
985 "error_type": exception.__class__.__name__,
986 "error_message": exception.message,
987 "error_details": exception.details
988 }
989 return {
990 "error_type": "UnknownError",
991 "error_message": str(exception),
992 "error_details": {}
993 }
994```
995
996## Phase 3: Standard Plugins Implementation
997
998### Step 1: Create Standard Plugins Directory Structure
999
1000Create the following directory structure:
1001
1002```
1003ollamatools/plugins/standard/
1004├── __init__.py
1005├── search.py
1006├── commands.py
1007├── conversation/
1008│ ├── __init__.py
1009│ ├── history.py
1010│ ├── context.py
1011│ └── analysis.py
1012├── feedback.py
1013├── issues.py
1014└── function_calling.py
1015```
1016
1017### Step 2: Implement Standard Plugins Initialization
1018
1019Create `ollamatools/plugins/standard/__init__.py`:
1020
1021```python
1022"""
1023Standard plugins for OllamaTools.
1024
1025This module provides standard plugins that implement common functionality
1026used by different agents.
1027"""
1028
1029from .search import SearchPlugin
1030from .commands import CommandsPlugin
1031from .conversation.history import ConversationHistoryPlugin
1032from .conversation.context import ConversationContextPlugin
1033from .conversation.analysis import ConversationAnalysisPlugin
1034from .feedback import FeedbackPlugin
1035from .issues import IssuesPlugin
1036from .function_calling import FunctionCallingPlugin
1037
1038__all__ = [
1039 "SearchPlugin",
1040 "CommandsPlugin",
1041 "ConversationHistoryPlugin",
1042 "ConversationContextPlugin",
1043 "ConversationAnalysisPlugin",
1044 "FeedbackPlugin",
1045 "IssuesPlugin",
1046 "FunctionCallingPlugin"
1047]
1048```
1049
1050### Step 3: Implement Search Plugin
1051
1052Create `ollamatools/plugins/standard/search.py`:
1053
1054```python
1055"""
1056Search plugin for OllamaTools.
1057
1058This plugin provides search functionality for agents.
1059"""
1060
1061import json
1062import os
1063from ..base import Plugin
1064
1065class SearchPlugin(Plugin):
1066 """Plugin for search functionality."""
1067
1068 def __init__(self, config=None):
1069 super().__init__(config)
1070 self.opensearch_config = config.get("opensearch", {})
1071 self.available = False
1072
1073 def initialize(self, context):