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.
- tiny_agent_os-0.0.1.dist-info/METADATA +377 -0
- tiny_agent_os-0.0.1.dist-info/RECORD +64 -0
- tiny_agent_os-0.0.1.dist-info/WHEEL +5 -0
- tiny_agent_os-0.0.1.dist-info/entry_points.txt +2 -0
- tiny_agent_os-0.0.1.dist-info/licenses/LICENSE +53 -0
- tiny_agent_os-0.0.1.dist-info/top_level.txt +1 -0
- tinyagent/__init__.py +75 -0
- tinyagent/_version.py +21 -0
- tinyagent/agent.py +957 -0
- tinyagent/chat/__init__.py +12 -0
- tinyagent/chat/chat_mode.py +291 -0
- tinyagent/cli/__init__.py +16 -0
- tinyagent/cli/colors.py +104 -0
- tinyagent/cli/main.py +664 -0
- tinyagent/cli/spinner.py +94 -0
- tinyagent/cli.py +47 -0
- tinyagent/config/__init__.py +14 -0
- tinyagent/config/config.py +258 -0
- tinyagent/decorators.py +187 -0
- tinyagent/exceptions.py +85 -0
- tinyagent/factory/__init__.py +18 -0
- tinyagent/factory/agent_factory.py +439 -0
- tinyagent/factory/dynamic_agent_factory.py +561 -0
- tinyagent/factory/orchestrator.py +1514 -0
- tinyagent/factory/tiny_chain.py +552 -0
- tinyagent/logging.py +97 -0
- tinyagent/mcp/__init__.py +14 -0
- tinyagent/mcp/manager.py +321 -0
- tinyagent/prompts/README.md +133 -0
- tinyagent/prompts/default.md +14 -0
- tinyagent/prompts/prompt_manager.py +206 -0
- tinyagent/prompts/system/agent.md +50 -0
- tinyagent/prompts/system/retry.md +55 -0
- tinyagent/prompts/system/strict_json.md +54 -0
- tinyagent/prompts/system.md +10 -0
- tinyagent/prompts/tools/calculator.md +13 -0
- tinyagent/prompts/tools/weather.md +7 -0
- tinyagent/prompts/workflows/riv_reflect.md +62 -0
- tinyagent/prompts/workflows/riv_verify.md +47 -0
- tinyagent/prompts/workflows/triage.md +129 -0
- tinyagent/tool.py +185 -0
- tinyagent/tools/README.md +391 -0
- tinyagent/tools/__init__.py +39 -0
- tinyagent/tools/aider.py +122 -0
- tinyagent/tools/anon_coder.py +296 -0
- tinyagent/tools/boilerplate_tool.py +147 -0
- tinyagent/tools/brave_search.py +104 -0
- tinyagent/tools/codeagent_tool.py +217 -0
- tinyagent/tools/content_processor.py +285 -0
- tinyagent/tools/custom_text_browser.py +965 -0
- tinyagent/tools/duckduckgo_search.py +153 -0
- tinyagent/tools/external.py +303 -0
- tinyagent/tools/file_manipulator.py +274 -0
- tinyagent/tools/final_extractor_tool.py +249 -0
- tinyagent/tools/llm_serializer.py +124 -0
- tinyagent/tools/markdown_gen.py +300 -0
- tinyagent/tools/ripgrep.py +136 -0
- tinyagent/utils/__init__.py +13 -0
- tinyagent/utils/json_parser.py +231 -0
- tinyagent/utils/logging_utils.py +78 -0
- tinyagent/utils/openrouter_request.py +123 -0
- tinyagent/utils/serialization.py +185 -0
- tinyagent/utils/structured_outputs.py +131 -0
- tinyagent/utils/type_converter.py +134 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dynamic agent factory for creating specialized agents on-the-fly.
|
|
3
|
+
|
|
4
|
+
This module provides an enhanced factory class that extends AgentFactory with
|
|
5
|
+
capabilities for dynamically creating specialized agents and tools based on
|
|
6
|
+
natural language requirements.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
from typing import Dict, List, Any, Optional, Callable, Union, TypeVar, Type, cast
|
|
12
|
+
|
|
13
|
+
from ..logging import get_logger
|
|
14
|
+
from ..config import load_config, get_config_value
|
|
15
|
+
from ..tool import Tool, ParamType
|
|
16
|
+
from ..agent import Agent, get_llm
|
|
17
|
+
from ..exceptions import ConfigurationError
|
|
18
|
+
from .agent_factory import AgentFactory
|
|
19
|
+
|
|
20
|
+
# Set up logger
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
# Define a generic type variable for better type hints
|
|
24
|
+
T = TypeVar('T')
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DynamicAgentFactory(AgentFactory):
|
|
28
|
+
"""
|
|
29
|
+
Enhanced factory for creating agents dynamically based on NLP analysis.
|
|
30
|
+
|
|
31
|
+
This class extends AgentFactory with capabilities for dynamically creating
|
|
32
|
+
specialized agents and tools based on natural language requirements.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
_dynamic_agents: Dictionary of dynamically created agents
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
_instance = None
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def get_instance(cls: Type[T], config: Optional[Dict[str, Any]] = None) -> T:
|
|
42
|
+
"""
|
|
43
|
+
Get or create the singleton factory instance.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
config: Optional configuration dictionary
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The singleton DynamicAgentFactory instance
|
|
50
|
+
"""
|
|
51
|
+
if cls._instance is None:
|
|
52
|
+
cls._instance = cls(config)
|
|
53
|
+
return cls._instance
|
|
54
|
+
|
|
55
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
56
|
+
"""
|
|
57
|
+
Initialize with parent class and add dynamic capabilities.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
config: Optional configuration dictionary
|
|
61
|
+
"""
|
|
62
|
+
super().__init__(config)
|
|
63
|
+
self._dynamic_agents: Dict[str, Agent] = {}
|
|
64
|
+
|
|
65
|
+
# Get dynamic agent configuration
|
|
66
|
+
if self.config:
|
|
67
|
+
max_agents = get_config_value(self.config, 'dynamic_agents.max_agents', 10)
|
|
68
|
+
allow_new_tools = get_config_value(self.config, 'dynamic_agents.allow_new_tools_by_default', False)
|
|
69
|
+
model = get_config_value(self.config, 'dynamic_agents.model', None)
|
|
70
|
+
|
|
71
|
+
logger.debug(f"Dynamic agent config: max_agents={max_agents}, allow_new_tools={allow_new_tools}")
|
|
72
|
+
|
|
73
|
+
self._max_agents = max_agents
|
|
74
|
+
self._allow_new_tools = allow_new_tools
|
|
75
|
+
self._model = model
|
|
76
|
+
else:
|
|
77
|
+
self._max_agents = 10
|
|
78
|
+
self._allow_new_tools = False
|
|
79
|
+
self._model = None
|
|
80
|
+
|
|
81
|
+
def create_dynamic_agent(self, task_description: str, model: Optional[str] = None) -> Agent:
|
|
82
|
+
"""
|
|
83
|
+
Create a specialized agent based on task description using NLP analysis.
|
|
84
|
+
|
|
85
|
+
This method analyzes the task description to determine which tools are
|
|
86
|
+
needed, then creates a specialized agent with those tools.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
task_description: Description of the task to be performed
|
|
90
|
+
model: Optional model to use for the agent
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
A specialized agent with appropriate tools
|
|
94
|
+
"""
|
|
95
|
+
# Use specified model or default from config
|
|
96
|
+
model = model or self._model
|
|
97
|
+
|
|
98
|
+
# Get LLM to analyze the task and determine required tools
|
|
99
|
+
llm = get_llm(model)
|
|
100
|
+
|
|
101
|
+
# Prompt to analyze the task
|
|
102
|
+
analysis_prompt = f"""
|
|
103
|
+
Analyze the following task description and determine which tools are needed:
|
|
104
|
+
|
|
105
|
+
Task: {task_description}
|
|
106
|
+
|
|
107
|
+
Available tools: {', '.join(self._tools.keys())}
|
|
108
|
+
|
|
109
|
+
Return a JSON object with the required tools and explanation:
|
|
110
|
+
{{
|
|
111
|
+
"required_tools": ["tool1", "tool2", ...],
|
|
112
|
+
"explanation": "Why these tools are needed"
|
|
113
|
+
}}
|
|
114
|
+
|
|
115
|
+
Keep your response minimal and focused only on the tools needed.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
# Get analysis result
|
|
119
|
+
analysis_result = llm(analysis_prompt)
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
# Find JSON using regex
|
|
123
|
+
json_match = re.search(r'({[\s\S]*})', analysis_result)
|
|
124
|
+
if json_match:
|
|
125
|
+
analysis = json.loads(json_match.group(1))
|
|
126
|
+
required_tools = analysis.get("required_tools", [])
|
|
127
|
+
else:
|
|
128
|
+
required_tools = []
|
|
129
|
+
logger.warning("Could not parse analysis result, using all tools")
|
|
130
|
+
# If parsing fails, use all tools
|
|
131
|
+
required_tools = list(self._tools.keys())
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.error(f"Error parsing analysis result: {str(e)}")
|
|
134
|
+
# If parsing fails, use all tools
|
|
135
|
+
required_tools = list(self._tools.keys())
|
|
136
|
+
|
|
137
|
+
# Get selected tools
|
|
138
|
+
selected_tools = []
|
|
139
|
+
for name in required_tools:
|
|
140
|
+
if name in self._tools:
|
|
141
|
+
selected_tools.append(self._tools[name])
|
|
142
|
+
|
|
143
|
+
# Create the agent
|
|
144
|
+
agent = self.create_agent(
|
|
145
|
+
tools=selected_tools,
|
|
146
|
+
model=model
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Add metadata for tracking
|
|
150
|
+
agent_id = f"agent_{len(self._dynamic_agents) + 1}"
|
|
151
|
+
agent.name = f"Specialized Agent {agent_id}"
|
|
152
|
+
agent.description = f"Agent specialized for: {task_description[:100]}"
|
|
153
|
+
|
|
154
|
+
# Manage dynamic agents (remove oldest if exceeding limit)
|
|
155
|
+
if len(self._dynamic_agents) >= self._max_agents:
|
|
156
|
+
# Remove oldest agent (first item)
|
|
157
|
+
if self._dynamic_agents:
|
|
158
|
+
oldest_key = next(iter(self._dynamic_agents))
|
|
159
|
+
del self._dynamic_agents[oldest_key]
|
|
160
|
+
logger.info(f"Removed oldest dynamic agent {oldest_key} to make room for new one")
|
|
161
|
+
|
|
162
|
+
# Register the agent
|
|
163
|
+
self._dynamic_agents[agent_id] = agent
|
|
164
|
+
|
|
165
|
+
logger.info(f"Created dynamic agent {agent_id} with {len(selected_tools)} tools")
|
|
166
|
+
|
|
167
|
+
return agent
|
|
168
|
+
|
|
169
|
+
def can_handle_with_existing_tools(self, requirement: str, model: Optional[str] = None) -> Dict[str, Any]:
|
|
170
|
+
"""
|
|
171
|
+
Analyze if a requirement can be handled with existing tools.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
requirement: Natural language description of requirement
|
|
175
|
+
model: Optional model to use for analysis
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Dict with analysis results
|
|
179
|
+
"""
|
|
180
|
+
# Use specified model or default from config
|
|
181
|
+
model = model or self._model
|
|
182
|
+
|
|
183
|
+
# Get LLM to analyze
|
|
184
|
+
llm = get_llm(model)
|
|
185
|
+
|
|
186
|
+
# Simple prompt to analyze if existing tools are sufficient
|
|
187
|
+
tool_prompt = f"""
|
|
188
|
+
Analyze the following requirement and determine if it can be handled
|
|
189
|
+
with our existing tools:
|
|
190
|
+
|
|
191
|
+
Requirement: {requirement}
|
|
192
|
+
|
|
193
|
+
Available tools: {', '.join(self._tools.keys())}
|
|
194
|
+
|
|
195
|
+
Return your analysis as JSON:
|
|
196
|
+
{{
|
|
197
|
+
"can_handle": true/false,
|
|
198
|
+
"required_tools": ["tool1", "tool2"],
|
|
199
|
+
"missing_capabilities": ["capability1", "capability2"],
|
|
200
|
+
"reasoning": "Your reasoning about whether existing tools can handle this"
|
|
201
|
+
}}
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
# Get analysis from LLM
|
|
206
|
+
analysis_result = llm(tool_prompt)
|
|
207
|
+
|
|
208
|
+
# Try to find JSON object using regex
|
|
209
|
+
json_match = re.search(r'({[\s\S]*})', analysis_result)
|
|
210
|
+
if json_match:
|
|
211
|
+
try:
|
|
212
|
+
analysis = json.loads(json_match.group(1))
|
|
213
|
+
|
|
214
|
+
# Validate the analysis has required fields
|
|
215
|
+
if "can_handle" not in analysis:
|
|
216
|
+
analysis["can_handle"] = False
|
|
217
|
+
|
|
218
|
+
if "required_tools" not in analysis:
|
|
219
|
+
analysis["required_tools"] = []
|
|
220
|
+
|
|
221
|
+
if "missing_capabilities" not in analysis:
|
|
222
|
+
analysis["missing_capabilities"] = []
|
|
223
|
+
|
|
224
|
+
if "reasoning" not in analysis:
|
|
225
|
+
analysis["reasoning"] = "No reasoning provided"
|
|
226
|
+
|
|
227
|
+
# Validate that required tools actually exist
|
|
228
|
+
valid_tools = [t for t in analysis.get("required_tools", []) if t in self._tools]
|
|
229
|
+
analysis["required_tools"] = valid_tools
|
|
230
|
+
|
|
231
|
+
return {"success": True, "analysis": analysis}
|
|
232
|
+
except json.JSONDecodeError:
|
|
233
|
+
logger.error(f"Failed to parse JSON from regex match: {json_match.group(1)[:100]}...")
|
|
234
|
+
|
|
235
|
+
# Try parsing the whole response
|
|
236
|
+
try:
|
|
237
|
+
analysis = json.loads(analysis_result)
|
|
238
|
+
|
|
239
|
+
# Validate the analysis has required fields
|
|
240
|
+
if "can_handle" not in analysis:
|
|
241
|
+
analysis["can_handle"] = False
|
|
242
|
+
|
|
243
|
+
if "required_tools" not in analysis:
|
|
244
|
+
analysis["required_tools"] = []
|
|
245
|
+
|
|
246
|
+
if "missing_capabilities" not in analysis:
|
|
247
|
+
analysis["missing_capabilities"] = []
|
|
248
|
+
|
|
249
|
+
if "reasoning" not in analysis:
|
|
250
|
+
analysis["reasoning"] = "No reasoning provided"
|
|
251
|
+
|
|
252
|
+
# Validate that required tools actually exist
|
|
253
|
+
valid_tools = [t for t in analysis.get("required_tools", []) if t in self._tools]
|
|
254
|
+
analysis["required_tools"] = valid_tools
|
|
255
|
+
|
|
256
|
+
return {"success": True, "analysis": analysis}
|
|
257
|
+
except json.JSONDecodeError:
|
|
258
|
+
logger.error(f"Failed to parse full response as JSON: {analysis_result[:100]}...")
|
|
259
|
+
|
|
260
|
+
# If we get here, both parsing attempts failed
|
|
261
|
+
return {
|
|
262
|
+
"success": False,
|
|
263
|
+
"error": "Failed to parse response",
|
|
264
|
+
"raw_response": analysis_result[:500] + "..." if len(analysis_result) > 500 else analysis_result
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.error(f"Error analyzing requirement: {str(e)}")
|
|
269
|
+
return {"success": False, "error": str(e)}
|
|
270
|
+
|
|
271
|
+
def create_agent_from_requirement(self, requirement: str, model: Optional[str] = None,
|
|
272
|
+
ask_permission: bool = True) -> Dict[str, Any]:
|
|
273
|
+
"""
|
|
274
|
+
Create a new agent based on a natural language requirement.
|
|
275
|
+
|
|
276
|
+
This method analyzes the requirement to determine which tools are needed,
|
|
277
|
+
potentially creating new tools if they don't exist.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
requirement: Natural language description of agent requirements
|
|
281
|
+
model: Optional model to use for the agent
|
|
282
|
+
ask_permission: Whether to ask permission before creating new tools
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
Dict with new agent and metadata
|
|
286
|
+
"""
|
|
287
|
+
# Use specified model or default from config
|
|
288
|
+
model = model or self._model
|
|
289
|
+
|
|
290
|
+
# First check if we can handle with existing tools
|
|
291
|
+
existing_tools_check = self.can_handle_with_existing_tools(requirement, model)
|
|
292
|
+
|
|
293
|
+
if existing_tools_check.get("success", False):
|
|
294
|
+
analysis = existing_tools_check["analysis"]
|
|
295
|
+
if analysis.get("can_handle", False):
|
|
296
|
+
# We can handle with existing tools, create an agent with just these tools
|
|
297
|
+
required_tool_names = analysis.get("required_tools", [])
|
|
298
|
+
selected_tools = []
|
|
299
|
+
|
|
300
|
+
for name in required_tool_names:
|
|
301
|
+
if name in self._tools:
|
|
302
|
+
selected_tools.append(self._tools[name])
|
|
303
|
+
|
|
304
|
+
# Add chat tool by default
|
|
305
|
+
if "chat" in self._tools and "chat" not in required_tool_names:
|
|
306
|
+
selected_tools.append(self._tools["chat"])
|
|
307
|
+
|
|
308
|
+
# Create agent with selected tools
|
|
309
|
+
agent = self.create_agent(tools=selected_tools, model=model)
|
|
310
|
+
agent.name = "Specialized Agent (Existing Tools)"
|
|
311
|
+
agent.description = f"Specialized agent for: {requirement}"
|
|
312
|
+
|
|
313
|
+
# Register the dynamic agent
|
|
314
|
+
agent_id = f"agent_{len(self._dynamic_agents) + 1}"
|
|
315
|
+
|
|
316
|
+
# Manage dynamic agents (remove oldest if exceeding limit)
|
|
317
|
+
if len(self._dynamic_agents) >= self._max_agents:
|
|
318
|
+
# Remove oldest agent (first item)
|
|
319
|
+
if self._dynamic_agents:
|
|
320
|
+
oldest_key = next(iter(self._dynamic_agents))
|
|
321
|
+
del self._dynamic_agents[oldest_key]
|
|
322
|
+
logger.info(f"Removed oldest dynamic agent {oldest_key} to make room for new one")
|
|
323
|
+
|
|
324
|
+
self._dynamic_agents[agent_id] = agent
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
"success": True,
|
|
328
|
+
"agent": agent,
|
|
329
|
+
"used_existing_tools": True,
|
|
330
|
+
"tools": [t.name for t in selected_tools],
|
|
331
|
+
"agent_id": agent_id
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# If we reach here, existing tools aren't sufficient, proceed with potential new tool creation
|
|
335
|
+
# Get LLM to analyze requirements
|
|
336
|
+
llm = get_llm(model)
|
|
337
|
+
|
|
338
|
+
# Analyze what tools are needed
|
|
339
|
+
tool_analysis_prompt = f"""
|
|
340
|
+
Analyze the following requirement and determine what tools are needed:
|
|
341
|
+
|
|
342
|
+
Requirement: {requirement}
|
|
343
|
+
|
|
344
|
+
Available tools: {', '.join(self._tools.keys())}
|
|
345
|
+
|
|
346
|
+
Return your analysis as JSON:
|
|
347
|
+
{{
|
|
348
|
+
"existing_tools_needed": ["tool1", "tool2"],
|
|
349
|
+
"new_tools_needed": [
|
|
350
|
+
{{
|
|
351
|
+
"name": "tool_name",
|
|
352
|
+
"description": "What the tool does",
|
|
353
|
+
"parameters": {{"param1": "string", "param2": "integer"}},
|
|
354
|
+
"implementation_details": "How this tool should be implemented"
|
|
355
|
+
}}
|
|
356
|
+
],
|
|
357
|
+
"agent_name": "descriptive_name_for_agent",
|
|
358
|
+
"agent_description": "What this agent does"
|
|
359
|
+
}}
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
analysis_result = llm(tool_analysis_prompt)
|
|
363
|
+
|
|
364
|
+
# Parse response
|
|
365
|
+
try:
|
|
366
|
+
# Try to find JSON object using regex
|
|
367
|
+
json_match = re.search(r'({[\s\S]*})', analysis_result)
|
|
368
|
+
if json_match:
|
|
369
|
+
try:
|
|
370
|
+
analysis = json.loads(json_match.group(1))
|
|
371
|
+
except json.JSONDecodeError:
|
|
372
|
+
# Try the whole string if regex failed
|
|
373
|
+
analysis = json.loads(analysis_result)
|
|
374
|
+
else:
|
|
375
|
+
# Try the whole string
|
|
376
|
+
analysis = json.loads(analysis_result)
|
|
377
|
+
except json.JSONDecodeError:
|
|
378
|
+
logger.error(f"Could not parse LLM response as JSON: {analysis_result[:200]}...")
|
|
379
|
+
return {
|
|
380
|
+
"success": False,
|
|
381
|
+
"error": "Could not parse LLM response",
|
|
382
|
+
"raw_response": analysis_result[:500] if len(analysis_result) > 500 else analysis_result,
|
|
383
|
+
"agent": self.create_agent(tools=list(self._tools.values()), model=model)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
# Check if we need to create new tools
|
|
387
|
+
new_tools_needed = analysis.get("new_tools_needed", [])
|
|
388
|
+
if new_tools_needed and ask_permission and not self._allow_new_tools:
|
|
389
|
+
# Return the analysis for the caller to handle permission
|
|
390
|
+
return {
|
|
391
|
+
"success": True,
|
|
392
|
+
"requires_permission": True,
|
|
393
|
+
"analysis": analysis,
|
|
394
|
+
"model": model
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
# Get existing tools
|
|
398
|
+
existing_tools = []
|
|
399
|
+
for tool_name in analysis.get("existing_tools_needed", []):
|
|
400
|
+
if tool_name in self._tools:
|
|
401
|
+
existing_tools.append(self._tools[tool_name])
|
|
402
|
+
|
|
403
|
+
# Always include chat tool
|
|
404
|
+
if "chat" in self._tools and "chat" not in analysis.get("existing_tools_needed", []):
|
|
405
|
+
existing_tools.append(self._tools["chat"])
|
|
406
|
+
|
|
407
|
+
# If we're allowed to create new tools
|
|
408
|
+
created_tools = []
|
|
409
|
+
if new_tools_needed and (not ask_permission or ask_permission is False or self._allow_new_tools):
|
|
410
|
+
for tool_spec in new_tools_needed:
|
|
411
|
+
# Create dynamic tool implementation
|
|
412
|
+
tool_impl = self._create_dynamic_tool_implementation(
|
|
413
|
+
tool_spec["name"],
|
|
414
|
+
tool_spec["description"],
|
|
415
|
+
tool_spec["parameters"],
|
|
416
|
+
tool_spec.get("implementation_details", ""),
|
|
417
|
+
model
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
# Convert parameter types
|
|
421
|
+
params = {}
|
|
422
|
+
for param_name, param_type in tool_spec["parameters"].items():
|
|
423
|
+
if isinstance(param_type, str):
|
|
424
|
+
if param_type.lower() == "string":
|
|
425
|
+
params[param_name] = ParamType.STRING
|
|
426
|
+
elif param_type.lower() == "integer":
|
|
427
|
+
params[param_name] = ParamType.INTEGER
|
|
428
|
+
elif param_type.lower() == "float":
|
|
429
|
+
params[param_name] = ParamType.FLOAT
|
|
430
|
+
else:
|
|
431
|
+
params[param_name] = ParamType.ANY
|
|
432
|
+
else:
|
|
433
|
+
params[param_name] = ParamType.ANY
|
|
434
|
+
|
|
435
|
+
# Create the tool
|
|
436
|
+
new_tool = Tool(
|
|
437
|
+
name=tool_spec["name"],
|
|
438
|
+
description=tool_spec["description"],
|
|
439
|
+
parameters=params,
|
|
440
|
+
func=tool_impl
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
# Register the tool
|
|
444
|
+
self.register_tool(new_tool)
|
|
445
|
+
created_tools.append(new_tool)
|
|
446
|
+
logger.info(f"Created new dynamic tool: {new_tool.name}")
|
|
447
|
+
|
|
448
|
+
# Combine existing and created tools
|
|
449
|
+
all_tools = existing_tools + created_tools
|
|
450
|
+
|
|
451
|
+
# Create the agent
|
|
452
|
+
agent = self.create_agent(tools=all_tools, model=model)
|
|
453
|
+
|
|
454
|
+
# Store the agent's name and description
|
|
455
|
+
agent.name = analysis.get("agent_name", "Specialized Agent")
|
|
456
|
+
agent.description = analysis.get("agent_description", "A specialized agent")
|
|
457
|
+
|
|
458
|
+
# Register the dynamic agent
|
|
459
|
+
agent_id = f"agent_{len(self._dynamic_agents) + 1}"
|
|
460
|
+
|
|
461
|
+
# Manage dynamic agents (remove oldest if exceeding limit)
|
|
462
|
+
if len(self._dynamic_agents) >= self._max_agents:
|
|
463
|
+
# Remove oldest agent (first item)
|
|
464
|
+
if self._dynamic_agents:
|
|
465
|
+
oldest_key = next(iter(self._dynamic_agents))
|
|
466
|
+
del self._dynamic_agents[oldest_key]
|
|
467
|
+
logger.info(f"Removed oldest dynamic agent {oldest_key} to make room for new one")
|
|
468
|
+
|
|
469
|
+
self._dynamic_agents[agent_id] = agent
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
"success": True,
|
|
473
|
+
"agent": agent,
|
|
474
|
+
"created_tools": created_tools,
|
|
475
|
+
"existing_tools": existing_tools,
|
|
476
|
+
"agent_id": agent_id
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
def _create_dynamic_tool_implementation(
|
|
480
|
+
self,
|
|
481
|
+
name: str,
|
|
482
|
+
description: str,
|
|
483
|
+
parameters: Dict[str, str],
|
|
484
|
+
implementation_details: str,
|
|
485
|
+
model: Optional[str] = None
|
|
486
|
+
) -> Callable:
|
|
487
|
+
"""
|
|
488
|
+
Create a dynamic implementation for a tool using the LLM.
|
|
489
|
+
|
|
490
|
+
This method creates a function that uses an LLM to implement the tool's
|
|
491
|
+
functionality based on a natural language description.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
name: Tool name
|
|
495
|
+
description: Tool description
|
|
496
|
+
parameters: Dictionary of parameter names to types
|
|
497
|
+
implementation_details: Natural language description of how to implement
|
|
498
|
+
model: Model to use
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
A function that implements the tool
|
|
502
|
+
"""
|
|
503
|
+
# Create LLM client
|
|
504
|
+
llm = get_llm(model)
|
|
505
|
+
|
|
506
|
+
def dynamic_tool_implementation(**kwargs):
|
|
507
|
+
"""Dynamically implements a tool using LLM."""
|
|
508
|
+
# Format the kwargs as string for LLM prompt
|
|
509
|
+
kwargs_str = ", ".join([f"{k}={repr(v)}" for k, v in kwargs.items()])
|
|
510
|
+
|
|
511
|
+
# Prompt the LLM to implement the tool
|
|
512
|
+
prompt = f"""
|
|
513
|
+
You are implementing the '{name}' tool with the following description:
|
|
514
|
+
{description}
|
|
515
|
+
|
|
516
|
+
Implementation details:
|
|
517
|
+
{implementation_details}
|
|
518
|
+
|
|
519
|
+
This tool has been called with these parameters:
|
|
520
|
+
{kwargs_str}
|
|
521
|
+
|
|
522
|
+
Implement the tool's functionality and return the result. Be direct and return only what is needed.
|
|
523
|
+
"""
|
|
524
|
+
|
|
525
|
+
# Get the implementation from LLM
|
|
526
|
+
result = llm(prompt)
|
|
527
|
+
return result
|
|
528
|
+
|
|
529
|
+
# Set the metadata
|
|
530
|
+
dynamic_tool_implementation.__name__ = name
|
|
531
|
+
dynamic_tool_implementation.__doc__ = description
|
|
532
|
+
|
|
533
|
+
return dynamic_tool_implementation
|
|
534
|
+
|
|
535
|
+
def get_dynamic_agent(self, agent_id: str) -> Optional[Agent]:
|
|
536
|
+
"""
|
|
537
|
+
Get a dynamically created agent by ID.
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
agent_id: ID of the agent to retrieve
|
|
541
|
+
|
|
542
|
+
Returns:
|
|
543
|
+
The Agent instance or None if not found
|
|
544
|
+
"""
|
|
545
|
+
return self._dynamic_agents.get(agent_id)
|
|
546
|
+
|
|
547
|
+
def list_dynamic_agents(self) -> Dict[str, Dict[str, Any]]:
|
|
548
|
+
"""
|
|
549
|
+
List all dynamically created agents.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
Dictionary of agent IDs to metadata
|
|
553
|
+
"""
|
|
554
|
+
return {
|
|
555
|
+
agent_id: {
|
|
556
|
+
"name": agent.name,
|
|
557
|
+
"description": agent.description,
|
|
558
|
+
"tools": [t.name for t in agent.tools.values()] if hasattr(agent, 'tools') else []
|
|
559
|
+
}
|
|
560
|
+
for agent_id, agent in self._dynamic_agents.items()
|
|
561
|
+
}
|