tiny-agent-os 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. tiny_agent_os-0.0.1.dist-info/METADATA +377 -0
  2. tiny_agent_os-0.0.1.dist-info/RECORD +64 -0
  3. tiny_agent_os-0.0.1.dist-info/WHEEL +5 -0
  4. tiny_agent_os-0.0.1.dist-info/entry_points.txt +2 -0
  5. tiny_agent_os-0.0.1.dist-info/licenses/LICENSE +53 -0
  6. tiny_agent_os-0.0.1.dist-info/top_level.txt +1 -0
  7. tinyagent/__init__.py +75 -0
  8. tinyagent/_version.py +21 -0
  9. tinyagent/agent.py +957 -0
  10. tinyagent/chat/__init__.py +12 -0
  11. tinyagent/chat/chat_mode.py +291 -0
  12. tinyagent/cli/__init__.py +16 -0
  13. tinyagent/cli/colors.py +104 -0
  14. tinyagent/cli/main.py +664 -0
  15. tinyagent/cli/spinner.py +94 -0
  16. tinyagent/cli.py +47 -0
  17. tinyagent/config/__init__.py +14 -0
  18. tinyagent/config/config.py +258 -0
  19. tinyagent/decorators.py +187 -0
  20. tinyagent/exceptions.py +85 -0
  21. tinyagent/factory/__init__.py +18 -0
  22. tinyagent/factory/agent_factory.py +439 -0
  23. tinyagent/factory/dynamic_agent_factory.py +561 -0
  24. tinyagent/factory/orchestrator.py +1514 -0
  25. tinyagent/factory/tiny_chain.py +552 -0
  26. tinyagent/logging.py +97 -0
  27. tinyagent/mcp/__init__.py +14 -0
  28. tinyagent/mcp/manager.py +321 -0
  29. tinyagent/prompts/README.md +133 -0
  30. tinyagent/prompts/default.md +14 -0
  31. tinyagent/prompts/prompt_manager.py +206 -0
  32. tinyagent/prompts/system/agent.md +50 -0
  33. tinyagent/prompts/system/retry.md +55 -0
  34. tinyagent/prompts/system/strict_json.md +54 -0
  35. tinyagent/prompts/system.md +10 -0
  36. tinyagent/prompts/tools/calculator.md +13 -0
  37. tinyagent/prompts/tools/weather.md +7 -0
  38. tinyagent/prompts/workflows/riv_reflect.md +62 -0
  39. tinyagent/prompts/workflows/riv_verify.md +47 -0
  40. tinyagent/prompts/workflows/triage.md +129 -0
  41. tinyagent/tool.py +185 -0
  42. tinyagent/tools/README.md +391 -0
  43. tinyagent/tools/__init__.py +39 -0
  44. tinyagent/tools/aider.py +122 -0
  45. tinyagent/tools/anon_coder.py +296 -0
  46. tinyagent/tools/boilerplate_tool.py +147 -0
  47. tinyagent/tools/brave_search.py +104 -0
  48. tinyagent/tools/codeagent_tool.py +217 -0
  49. tinyagent/tools/content_processor.py +285 -0
  50. tinyagent/tools/custom_text_browser.py +965 -0
  51. tinyagent/tools/duckduckgo_search.py +153 -0
  52. tinyagent/tools/external.py +303 -0
  53. tinyagent/tools/file_manipulator.py +274 -0
  54. tinyagent/tools/final_extractor_tool.py +249 -0
  55. tinyagent/tools/llm_serializer.py +124 -0
  56. tinyagent/tools/markdown_gen.py +300 -0
  57. tinyagent/tools/ripgrep.py +136 -0
  58. tinyagent/utils/__init__.py +13 -0
  59. tinyagent/utils/json_parser.py +231 -0
  60. tinyagent/utils/logging_utils.py +78 -0
  61. tinyagent/utils/openrouter_request.py +123 -0
  62. tinyagent/utils/serialization.py +185 -0
  63. tinyagent/utils/structured_outputs.py +131 -0
  64. tinyagent/utils/type_converter.py +134 -0
@@ -0,0 +1,94 @@
1
+ """
2
+ Spinner animation for the tinyAgent CLI.
3
+
4
+ This module provides a spinner animation for the CLI to indicate ongoing processes.
5
+ The spinner is implemented as a context manager for ease of use.
6
+ """
7
+
8
+ import sys
9
+ import threading
10
+ import time
11
+ import itertools
12
+ from typing import Optional, Iterator
13
+
14
+ from .colors import Colors
15
+
16
+
17
+ class Spinner:
18
+ """
19
+ A simple spinner animation for the CLI.
20
+
21
+ This class provides a spinner animation that runs in a separate thread
22
+ and can be used to indicate that a process is running. It is implemented
23
+ as a context manager for ease of use with the 'with' statement.
24
+
25
+ Attributes:
26
+ message: The message to display next to the spinner
27
+ running: Flag indicating if the spinner is running
28
+ spinner: Iterator of spinner characters
29
+ thread: Thread running the spinner animation
30
+ """
31
+
32
+ def __init__(self, message: str = "Processing"):
33
+ """
34
+ Initialize the spinner with a message.
35
+
36
+ Args:
37
+ message: The message to display next to the spinner
38
+ """
39
+ self.message = message
40
+ self.running = False
41
+ self.spinner: Iterator[str] = itertools.cycle(['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'])
42
+ self.thread: Optional[threading.Thread] = None
43
+
44
+ def spin(self) -> None:
45
+ """
46
+ Run the spinner animation.
47
+
48
+ This method runs in a separate thread and updates the spinner character
49
+ at regular intervals. It continues until the running flag is set to False.
50
+ """
51
+ while self.running:
52
+ sys.stdout.write(
53
+ f"\r{Colors.LIGHT_RED}{next(self.spinner)} "
54
+ f"{Colors.OFF_WHITE}{self.message}{Colors.RESET}"
55
+ )
56
+ sys.stdout.flush()
57
+ time.sleep(0.1)
58
+
59
+ # Clear the spinner when done
60
+ sys.stdout.write('\r' + ' ' * (len(self.message) + 2) + '\r')
61
+ sys.stdout.flush()
62
+
63
+ def __enter__(self) -> 'Spinner':
64
+ """
65
+ Start the spinner when entering a context.
66
+
67
+ Returns:
68
+ The Spinner instance
69
+ """
70
+ self.running = True
71
+ self.thread = threading.Thread(target=self.spin)
72
+ self.thread.start()
73
+ return self
74
+
75
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
76
+ """Stop the spinner when exiting a context."""
77
+ self.running = False
78
+ if self.thread:
79
+ self.thread.join()
80
+
81
+ def update_message(self, message: str) -> None:
82
+ """
83
+ Update the spinner message while it's running.
84
+
85
+ Args:
86
+ message: New message to display
87
+ """
88
+ self.message = message
89
+
90
+ def stop(self) -> None:
91
+ """Stop the spinner manually (if not using as a context manager)."""
92
+ self.running = False
93
+ if self.thread:
94
+ self.thread.join()
tinyagent/cli.py ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ tinyAgent - A simple yet powerful framework for building LLM-powered agents.
4
+
5
+ This is the main entry point for the tinyAgent framework. It provides access to
6
+ all the core components of the framework through a clean, simple API.
7
+ """
8
+
9
+ # Load environment variables first
10
+ from dotenv import load_dotenv
11
+ load_dotenv()
12
+
13
+ from tinyagent import (
14
+ # Core components
15
+ Agent, get_llm, Tool, ParamType, tool,
16
+
17
+ # Exception classes
18
+ TinyAgentError, ConfigurationError,
19
+ ToolError, ToolNotFoundError, ToolExecutionError,
20
+ RateLimitExceeded, ParsingError,
21
+ AgentRetryExceeded, OrchestratorError, AgentNotFoundError,
22
+
23
+ # Factory components
24
+ AgentFactory, DynamicAgentFactory, Orchestrator, TaskStatus,
25
+
26
+ # Utilities
27
+ configure_logging, get_logger, load_config, get_config_value,
28
+ Colors, Spinner, CLI, run_chat_mode,
29
+
30
+ # Version
31
+ __version__
32
+ )
33
+
34
+ # Built-in tools
35
+ from tinyagent.tools import (
36
+ anon_coder_tool,
37
+ llm_serializer_tool,
38
+ brave_web_search_tool,
39
+ ripgrep_tool,
40
+ aider_tool,
41
+ load_external_tools
42
+ )
43
+
44
+ # Run CLI if executed directly
45
+ if __name__ == "__main__":
46
+ # Call the core CLI implementation
47
+ CLI()
@@ -0,0 +1,14 @@
1
+ """
2
+ Configuration management for the tinyAgent framework.
3
+
4
+ This package provides utilities for loading, validating, and accessing
5
+ configuration settings from different sources (files, environment variables, etc.).
6
+ """
7
+
8
+ from .config import load_config, get_config_value, TinyAgentConfig
9
+
10
+ __all__ = [
11
+ 'load_config',
12
+ 'get_config_value',
13
+ 'TinyAgentConfig',
14
+ ]
@@ -0,0 +1,258 @@
1
+ """
2
+ Configuration management for the tinyAgent framework.
3
+
4
+ This module provides functions for loading, validating, and accessing configuration
5
+ settings from different sources (files, environment variables, etc.).
6
+ """
7
+
8
+ import os
9
+ import yaml
10
+ from typing import Dict, Any, Optional, TypedDict, Union, cast, List
11
+ from dataclasses import dataclass, field
12
+
13
+ from ..exceptions import ConfigurationError
14
+ from ..logging import get_logger
15
+
16
+ # Set up logger
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ class ParsingConfig(TypedDict, total=False):
21
+ """Configuration for response parsing."""
22
+ strict_json: bool
23
+ fallback_parsers: Dict[str, bool]
24
+
25
+
26
+ class ModelConfig(TypedDict, total=False):
27
+ """Configuration for models."""
28
+ default: str
29
+
30
+
31
+ class RetriesConfig(TypedDict, total=False):
32
+ """Configuration for retry behavior."""
33
+ max_attempts: int
34
+
35
+
36
+ class RateLimitConfig(TypedDict, total=False):
37
+ """Configuration for rate limiting."""
38
+ global_limit: int
39
+ tool_limits: Dict[str, int]
40
+
41
+
42
+ class LoggingConfig(TypedDict, total=False):
43
+ """Configuration for logging."""
44
+ level: str # DEBUG, INFO, WARNING, ERROR, CRITICAL
45
+ format: str # Python logging format string
46
+ handlers: List[str] # List of enabled handlers (console, file, etc)
47
+
48
+
49
+ class AgentConfig(TypedDict, total=False):
50
+ """Configuration for agent behavior."""
51
+ max_steps: int # Maximum number of steps per research phase
52
+ debug_level: int # Debug level (0-2)
53
+ default_model: str # Default model to use
54
+
55
+
56
+ class APIConfig(TypedDict, total=False):
57
+ """Configuration for API behavior."""
58
+ enable_docs: bool
59
+ cors_origins: List[str]
60
+ port: int
61
+
62
+ class TinyAgentConfig(TypedDict, total=False):
63
+ """Top-level configuration structure."""
64
+ parsing: ParsingConfig
65
+ model: ModelConfig
66
+ retries: RetriesConfig
67
+ rate_limits: RateLimitConfig
68
+ logging: LoggingConfig
69
+ agent: AgentConfig
70
+ api: APIConfig
71
+
72
+
73
+ # Default configuration
74
+ DEFAULT_CONFIG: TinyAgentConfig = {
75
+ "api": {
76
+ "app_name": "tinyAgent API",
77
+ "enable_docs": True,
78
+ "cors_origins": ["*"],
79
+ "port": 9000,
80
+ "chat_tool": "default_chat"
81
+ },
82
+ "parsing": {
83
+ "strict_json": False,
84
+ "fallback_parsers": {
85
+ "template": True,
86
+ "regex": True
87
+ }
88
+ },
89
+ "model": {
90
+ "default": "qwen/qwq-32B"
91
+ },
92
+ "retries": {
93
+ "max_attempts": 3
94
+ },
95
+ "rate_limits": {
96
+ "global_limit": 30,
97
+ "tool_limits": {}
98
+ },
99
+ "logging": {
100
+ "level": "INFO", # Default to INFO to see configuration details
101
+ "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
102
+ "handlers": ["console"] # Default to console output
103
+ },
104
+ "agent": {
105
+ "max_steps": 2, # Default to 2 steps per phase
106
+ "debug_level": 0, # Default to no debug output
107
+ "default_model": "deepseek/deepseek-r1" # Default model
108
+ }
109
+ }
110
+
111
+
112
+ def load_config(config_path: Optional[str] = None) -> TinyAgentConfig:
113
+ """
114
+ Load configuration from YAML file, with fallback to default values.
115
+
116
+ This function attempts to load configuration from a YAML file. If the file
117
+ doesn't exist or there's an error loading it, it falls back to default values.
118
+
119
+ Args:
120
+ config_path: Path to config.yml file. If None, uses config.yml from project root.
121
+
122
+ Returns:
123
+ Dict containing configuration values
124
+
125
+ Raises:
126
+ ConfigurationError: If there's an error with the configuration format
127
+ """
128
+ # 1. Check environment variable override
129
+ env_config = os.getenv("TINYAGENT_CONFIG")
130
+ if env_config and os.path.isfile(env_config):
131
+ config_path = env_config
132
+ else:
133
+ # 2. If no explicit path, check current working directory
134
+ if config_path is None:
135
+ cwd_config = os.path.join(os.getcwd(), 'config.yml')
136
+ if os.path.isfile(cwd_config):
137
+ config_path = cwd_config
138
+ else:
139
+ # 3. Fallback to package root
140
+ project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
141
+ config_path = os.path.join(project_root, 'config.yml')
142
+ elif not os.path.isabs(config_path):
143
+ # If relative path provided, make it relative to project root
144
+ project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
145
+ config_path = os.path.join(project_root, config_path)
146
+
147
+ config = cast(TinyAgentConfig, DEFAULT_CONFIG.copy())
148
+
149
+ try:
150
+ if os.path.exists(config_path):
151
+ with open(config_path, 'r', encoding='utf-8') as file:
152
+ yaml_config = yaml.safe_load(file)
153
+
154
+ if yaml_config:
155
+ # Validate the config
156
+ if not isinstance(yaml_config, dict):
157
+ raise ConfigurationError("Configuration must be a dictionary")
158
+
159
+ # Update config with values from YAML
160
+ _update_nested_dict(config, yaml_config)
161
+ logger.info(f"Configuration loaded from {config_path}")
162
+ else:
163
+ logger.warning(f"Empty configuration file: {config_path}")
164
+ else:
165
+ logger.info(f"Configuration file not found at {config_path}, using defaults")
166
+ except yaml.YAMLError as e:
167
+ logger.error(f"Error parsing YAML configuration: {str(e)}")
168
+ raise ConfigurationError(f"Invalid YAML in configuration file: {e}")
169
+ except Exception as e:
170
+ logger.error(f"Error loading configuration: {str(e)}")
171
+ logger.info("Using default configuration")
172
+
173
+ return config
174
+
175
+
176
+ def _update_nested_dict(base_dict: Dict[str, Any], update_dict: Dict[str, Any]) -> None:
177
+ """
178
+ Update a nested dictionary with values from another dictionary.
179
+
180
+ This function recursively updates a nested dictionary with values from another
181
+ dictionary, preserving the structure of the base dictionary.
182
+
183
+ Args:
184
+ base_dict: The dictionary to update
185
+ update_dict: The dictionary with new values
186
+ """
187
+ for key, value in update_dict.items():
188
+ if key in base_dict and isinstance(base_dict[key], dict) and isinstance(value, dict):
189
+ _update_nested_dict(base_dict[key], value)
190
+ else:
191
+ base_dict[key] = value
192
+
193
+
194
+ def get_config_value(config: Dict[str, Any], key_path: str, default: Any = None) -> Any:
195
+ """
196
+ Get a value from the config using a dot-notation path.
197
+
198
+ This function retrieves a value from a nested dictionary using a dot-notation
199
+ path. If the key doesn't exist, it returns the default value.
200
+
201
+ Args:
202
+ config: The configuration dictionary
203
+ key_path: Dot-notation path to the desired value (e.g., 'parsing.strict_json')
204
+ default: Value to return if the key is not found
205
+
206
+ Returns:
207
+ The configuration value or default if not found
208
+
209
+ Examples:
210
+ >>> config = {"parsing": {"strict_json": True}}
211
+ >>> get_config_value(config, "parsing.strict_json")
212
+ True
213
+ >>> get_config_value(config, "parsing.unknown", False)
214
+ False
215
+ """
216
+ keys = key_path.split('.')
217
+ current = config
218
+
219
+ for key in keys:
220
+ if isinstance(current, dict) and key in current:
221
+ current = current[key]
222
+ else:
223
+ return default
224
+
225
+ return current
226
+
227
+
228
+ def validate_config(config: TinyAgentConfig) -> None:
229
+ """
230
+ Validate the configuration to ensure it matches the expected format.
231
+
232
+ Args:
233
+ config: The configuration to validate
234
+
235
+ Raises:
236
+ ConfigurationError: If the configuration is invalid
237
+ """
238
+ # Validate model.default
239
+ if "model" in config and "default" in config["model"]:
240
+ if not isinstance(config["model"]["default"], str):
241
+ raise ConfigurationError("model.default must be a string")
242
+
243
+ # Validate retries.max_attempts
244
+ if "retries" in config and "max_attempts" in config["retries"]:
245
+ if not isinstance(config["retries"]["max_attempts"], int):
246
+ raise ConfigurationError("retries.max_attempts must be an integer")
247
+ if config["retries"]["max_attempts"] < 1:
248
+ raise ConfigurationError("retries.max_attempts must be at least 1")
249
+
250
+ # Validate parsing.strict_json
251
+ if "parsing" in config and "strict_json" in config["parsing"]:
252
+ if not isinstance(config["parsing"]["strict_json"], bool):
253
+ raise ConfigurationError("parsing.strict_json must be a boolean")
254
+
255
+ # Validate rate_limits.global_limit
256
+ if "rate_limits" in config and "global_limit" in config["rate_limits"]:
257
+ if not isinstance(config["rate_limits"]["global_limit"], int):
258
+ raise ConfigurationError("rate_limits.global_limit must be an integer")
@@ -0,0 +1,187 @@
1
+ """
2
+ Decorator functions for the tinyAgent framework.
3
+
4
+ This module provides decorators used throughout the tinyAgent framework,
5
+ particularly the `tool` decorator for transforming functions into Tool instances.
6
+ """
7
+
8
+ import functools
9
+ import inspect
10
+ from typing import Any, Callable, Dict, List, Optional, TypeVar, Union, cast, overload
11
+
12
+ from .tool import Tool, ParamType
13
+
14
+
15
+ # Type variables for better type annotations
16
+ F = TypeVar('F', bound=Callable[..., Any]) # Function type
17
+ T = TypeVar('T') # Generic return type
18
+
19
+
20
+ @overload
21
+ def tool(func: F) -> F:
22
+ """Tool decorator usage without arguments."""
23
+ ...
24
+
25
+
26
+ @overload
27
+ def tool(
28
+ name: Optional[str] = None,
29
+ description: Optional[str] = None,
30
+ rate_limit: Optional[int] = None,
31
+ retry_limit: Optional[int] = None
32
+ ) -> Callable[[F], F]:
33
+ """Tool decorator usage with arguments."""
34
+ ...
35
+
36
+
37
+ def tool(
38
+ name=None,
39
+ description=None,
40
+ rate_limit=None,
41
+ retry_limit=None
42
+ ):
43
+ """
44
+ Decorator to transform a function into a tool with optional rate limiting.
45
+
46
+ This decorator provides a more intuitive and developer-friendly way to define tools.
47
+ Simply decorating a function transforms it into a registered tool.
48
+
49
+ Args:
50
+ name: Optional name for the tool (defaults to function name)
51
+ description: Optional description for the tool (defaults to function docstring)
52
+ rate_limit: Optional rate limit for the tool (max number of calls allowed)
53
+ retry_limit: Optional retry limit for the tool (max retries on failure)
54
+
55
+ Returns:
56
+ The decorated function wrapped as a tool with rate limiting
57
+
58
+ Example:
59
+ @tool
60
+ def calculate_sum(a: int, b: int) -> int:
61
+ '''Calculate the sum of two integers.'''
62
+ return a + b
63
+
64
+ @tool(rate_limit=5)
65
+ def rate_limited_api(query: str) -> str:
66
+ '''Make an API call with max 5 calls per session.'''
67
+ return make_api_call(query)
68
+ """
69
+ # Handle case where decorator is used without parentheses
70
+ if callable(name):
71
+ func = name
72
+ name = None
73
+ return _create_tool_wrapper(func, None, None, None, None)
74
+
75
+ # Handle case where decorator is used with parameters
76
+ def decorator(func):
77
+ return _create_tool_wrapper(func, name, description, rate_limit, retry_limit)
78
+
79
+ return decorator
80
+
81
+
82
+ def _create_tool_wrapper(
83
+ func: F,
84
+ name: Optional[str],
85
+ description: Optional[str],
86
+ rate_limit: Optional[int],
87
+ retry_limit: Optional[int]
88
+ ) -> F:
89
+ """
90
+ Internal function to create a tool wrapper for a given function.
91
+
92
+ Args:
93
+ func: The function to wrap as a tool
94
+ name: Optional name for the tool
95
+ description: Optional description for the tool
96
+ rate_limit: Optional rate limit for the tool
97
+ retry_limit: Optional retry limit for the tool
98
+
99
+ Returns:
100
+ The wrapped function
101
+ """
102
+ # Get function signature and metadata
103
+ sig = inspect.signature(func)
104
+
105
+ # Set tool name and description
106
+ tool_name = name or func.__name__.lower()
107
+ tool_description = description or func.__doc__ or f"Tool for {func.__name__}"
108
+
109
+ # If rate limit specified, add it to description
110
+ if rate_limit is not None:
111
+ tool_description = f"{tool_description} (Limited to {rate_limit} calls per session)"
112
+
113
+ # Convert Python type hints to ParamType
114
+ parameters = {}
115
+ for param_name, param in sig.parameters.items():
116
+ if param_name in ('self', 'cls'):
117
+ continue
118
+
119
+ # Map Python types to ParamType
120
+ if param.annotation == int:
121
+ param_type = ParamType.INTEGER
122
+ elif param.annotation == float:
123
+ param_type = ParamType.FLOAT
124
+ elif param.annotation == str:
125
+ param_type = ParamType.STRING
126
+ else:
127
+ param_type = ParamType.ANY
128
+
129
+ parameters[param_name] = param_type
130
+
131
+ # Create tool instance with rate limiting
132
+ tool_instance = Tool(
133
+ name=tool_name,
134
+ description=tool_description,
135
+ parameters=parameters,
136
+ func=func,
137
+ rate_limit=rate_limit
138
+ )
139
+
140
+ # Add retry limit if specified
141
+ if retry_limit is not None:
142
+ tool_instance.retry_limit = retry_limit
143
+
144
+ # Add the tool instance as an attribute to the function
145
+ func._tool = tool_instance
146
+
147
+ @functools.wraps(func)
148
+ def wrapper(*args, **kwargs):
149
+ # Convert positional arguments to keyword arguments if needed
150
+ if args and len(args) > 0:
151
+ sig = inspect.signature(func)
152
+ param_names = list(sig.parameters.keys())
153
+ # Skip self/cls if this is a method
154
+ if param_names and param_names[0] in ('self', 'cls') and len(args) > 0:
155
+ kwargs[param_names[1]] = args[0]
156
+ for i, arg in enumerate(args[1:], start=2):
157
+ if i < len(param_names):
158
+ kwargs[param_names[i]] = arg
159
+ else:
160
+ # Not a method
161
+ for i, arg in enumerate(args):
162
+ if i < len(param_names):
163
+ kwargs[param_names[i]] = arg
164
+
165
+ # Filter out parameters that are not part of the function signature
166
+ # This prevents errors with unwanted parameters being passed
167
+ sig = inspect.signature(func)
168
+ valid_params = set(sig.parameters.keys())
169
+
170
+ # Check if the function accepts **kwargs
171
+ accepts_kwargs = any(
172
+ param.kind == inspect.Parameter.VAR_KEYWORD
173
+ for param in sig.parameters.values()
174
+ )
175
+
176
+ # If function doesn't accept **kwargs, filter out invalid parameters
177
+ if not accepts_kwargs:
178
+ filtered_kwargs = {k: v for k, v in kwargs.items() if k in valid_params}
179
+ else:
180
+ filtered_kwargs = kwargs # Keep all parameters if the function accepts **kwargs
181
+
182
+ # Use the tool instance directly to ensure rate limiting is applied
183
+ return tool_instance(**filtered_kwargs)
184
+
185
+ wrapper._tool = tool_instance
186
+
187
+ return cast(F, wrapper)
@@ -0,0 +1,85 @@
1
+ """
2
+ Exceptions for the tinyAgent framework.
3
+
4
+ This module contains custom exceptions used throughout the tinyAgent framework.
5
+ """
6
+
7
+ from typing import List, Dict, Any, Optional
8
+
9
+
10
+ class AgentRetryExceeded(Exception):
11
+ """Exception raised when agent exceeds max retry attempts."""
12
+ def __init__(self, message, history=None):
13
+ self.message = message
14
+ self.history = history or []
15
+ super().__init__(message)
16
+
17
+
18
+ class TinyAgentError(Exception):
19
+ """Base class for all tinyAgent exceptions."""
20
+ pass
21
+
22
+
23
+ class ConfigurationError(TinyAgentError):
24
+ """Exception raised for configuration-related errors."""
25
+ pass
26
+
27
+
28
+ class ToolError(TinyAgentError):
29
+ """Base class for tool-related errors."""
30
+ pass
31
+
32
+
33
+ class ToolNotFoundError(ToolError):
34
+ """Exception raised when a requested tool is not found."""
35
+ def __init__(self, tool_name: str, available_tools: Optional[List[str]] = None):
36
+ self.tool_name = tool_name
37
+ self.available_tools = available_tools or []
38
+ message = f"Tool '{tool_name}' not found"
39
+ if available_tools:
40
+ message += f". Available tools: {', '.join(available_tools)}"
41
+ super().__init__(message)
42
+
43
+
44
+ class ToolExecutionError(ToolError):
45
+ """Exception raised when a tool execution fails."""
46
+ def __init__(self, tool_name: str, args: Dict[str, Any], error_message: str):
47
+ self.tool_name = tool_name
48
+ self.args = args
49
+ self.error_message = error_message
50
+ super().__init__(f"Error executing tool {tool_name}: {error_message}")
51
+
52
+
53
+ class RateLimitExceeded(ToolError):
54
+ """Exception raised when a tool's rate limit is exceeded."""
55
+ def __init__(self, tool_name: str, limit: int):
56
+ self.tool_name = tool_name
57
+ self.limit = limit
58
+ super().__init__(f"Rate limit exceeded for tool '{tool_name}'. Maximum: {limit} calls")
59
+
60
+
61
+ class ParsingError(TinyAgentError):
62
+ """Exception raised when parsing LLM responses fails."""
63
+ def __init__(self, content: str, details: str = None):
64
+ self.content = content
65
+ self.details = details
66
+ message = "Failed to parse LLM response"
67
+ if details:
68
+ message += f": {details}"
69
+ super().__init__(message)
70
+
71
+
72
+ class OrchestratorError(TinyAgentError):
73
+ """Exception raised for orchestrator-related errors."""
74
+ pass
75
+
76
+
77
+ class AgentNotFoundError(OrchestratorError):
78
+ """Exception raised when a requested agent is not found."""
79
+ def __init__(self, agent_id: str, available_agents: Optional[List[str]] = None):
80
+ self.agent_id = agent_id
81
+ self.available_agents = available_agents or []
82
+ message = f"Agent '{agent_id}' not found"
83
+ if available_agents:
84
+ message += f". Available agents: {', '.join(available_agents)}"
85
+ super().__init__(message)