rakam-systems-core 0.1.1rc7__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.
- rakam_systems_core/__init__.py +41 -0
- rakam_systems_core/ai_core/__init__.py +68 -0
- rakam_systems_core/ai_core/base.py +142 -0
- rakam_systems_core/ai_core/config.py +12 -0
- rakam_systems_core/ai_core/config_loader.py +580 -0
- rakam_systems_core/ai_core/config_schema.py +395 -0
- rakam_systems_core/ai_core/interfaces/__init__.py +30 -0
- rakam_systems_core/ai_core/interfaces/agent.py +83 -0
- rakam_systems_core/ai_core/interfaces/chat_history.py +122 -0
- rakam_systems_core/ai_core/interfaces/chunker.py +11 -0
- rakam_systems_core/ai_core/interfaces/embedding_model.py +10 -0
- rakam_systems_core/ai_core/interfaces/indexer.py +10 -0
- rakam_systems_core/ai_core/interfaces/llm_gateway.py +139 -0
- rakam_systems_core/ai_core/interfaces/loader.py +86 -0
- rakam_systems_core/ai_core/interfaces/reranker.py +10 -0
- rakam_systems_core/ai_core/interfaces/retriever.py +11 -0
- rakam_systems_core/ai_core/interfaces/tool.py +162 -0
- rakam_systems_core/ai_core/interfaces/tool_invoker.py +260 -0
- rakam_systems_core/ai_core/interfaces/tool_loader.py +374 -0
- rakam_systems_core/ai_core/interfaces/tool_registry.py +287 -0
- rakam_systems_core/ai_core/interfaces/vectorstore.py +37 -0
- rakam_systems_core/ai_core/mcp/README.md +545 -0
- rakam_systems_core/ai_core/mcp/__init__.py +0 -0
- rakam_systems_core/ai_core/mcp/mcp_server.py +334 -0
- rakam_systems_core/ai_core/tracking.py +602 -0
- rakam_systems_core/ai_core/vs_core.py +55 -0
- rakam_systems_core/ai_utils/__init__.py +16 -0
- rakam_systems_core/ai_utils/logging.py +126 -0
- rakam_systems_core/ai_utils/metrics.py +10 -0
- rakam_systems_core/ai_utils/s3.py +480 -0
- rakam_systems_core/ai_utils/tracing.py +5 -0
- rakam_systems_core-0.1.1rc7.dist-info/METADATA +162 -0
- rakam_systems_core-0.1.1rc7.dist-info/RECORD +34 -0
- rakam_systems_core-0.1.1rc7.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration loader for agents.
|
|
3
|
+
|
|
4
|
+
This module provides functionality to load agent configurations from YAML files
|
|
5
|
+
and instantiate agents with the specified settings.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from typing import Any, Dict, List, Optional, Type, Callable
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import yaml
|
|
11
|
+
import importlib
|
|
12
|
+
import inspect
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, Field, create_model
|
|
15
|
+
|
|
16
|
+
from .config_schema import (
|
|
17
|
+
ConfigFileSchema,
|
|
18
|
+
AgentConfigSchema,
|
|
19
|
+
ToolConfigSchema,
|
|
20
|
+
ModelConfigSchema,
|
|
21
|
+
PromptConfigSchema,
|
|
22
|
+
OutputTypeSchema,
|
|
23
|
+
OutputFieldSchema,
|
|
24
|
+
ToolMode,
|
|
25
|
+
)
|
|
26
|
+
from .interfaces.tool_registry import ToolRegistry, ToolMetadata
|
|
27
|
+
from .interfaces.tool import ToolComponent
|
|
28
|
+
from .interfaces.agent import ModelSettings
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ConfigurationLoader:
|
|
32
|
+
"""
|
|
33
|
+
Loads agent configurations from YAML files and creates agent instances.
|
|
34
|
+
|
|
35
|
+
Features:
|
|
36
|
+
- Load complete configuration from YAML
|
|
37
|
+
- Resolve references (tools, prompts)
|
|
38
|
+
- Dynamic module/class loading
|
|
39
|
+
- Tool registry integration
|
|
40
|
+
- Validation using Pydantic schemas
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> loader = ConfigurationLoader()
|
|
44
|
+
>>> config = loader.load_from_yaml("agent_config.yaml")
|
|
45
|
+
>>> agent = loader.create_agent("my_agent", config)
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self):
|
|
49
|
+
self.config: Optional[ConfigFileSchema] = None
|
|
50
|
+
self.tool_registry: Optional[ToolRegistry] = None
|
|
51
|
+
|
|
52
|
+
def load_from_yaml(self, config_path: str) -> ConfigFileSchema:
|
|
53
|
+
"""
|
|
54
|
+
Load configuration from YAML file.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
config_path: Path to YAML configuration file
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Validated configuration schema
|
|
61
|
+
"""
|
|
62
|
+
path = Path(config_path)
|
|
63
|
+
if not path.exists():
|
|
64
|
+
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
|
65
|
+
|
|
66
|
+
with open(path, 'r') as f:
|
|
67
|
+
raw_config = yaml.safe_load(f)
|
|
68
|
+
|
|
69
|
+
# Validate using Pydantic
|
|
70
|
+
self.config = ConfigFileSchema(**raw_config)
|
|
71
|
+
return self.config
|
|
72
|
+
|
|
73
|
+
def load_from_dict(self, config_dict: Dict[str, Any]) -> ConfigFileSchema:
|
|
74
|
+
"""
|
|
75
|
+
Load configuration from dictionary.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
config_dict: Configuration dictionary
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Validated configuration schema
|
|
82
|
+
"""
|
|
83
|
+
self.config = ConfigFileSchema(**config_dict)
|
|
84
|
+
return self.config
|
|
85
|
+
|
|
86
|
+
def get_tool_registry(self, config: Optional[ConfigFileSchema] = None) -> ToolRegistry:
|
|
87
|
+
"""
|
|
88
|
+
Get or create a tool registry from configuration.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
config: Configuration to use (uses loaded config if None)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
ToolRegistry with all configured tools
|
|
95
|
+
"""
|
|
96
|
+
if self.tool_registry is not None:
|
|
97
|
+
return self.tool_registry
|
|
98
|
+
|
|
99
|
+
config = config or self.config
|
|
100
|
+
if config is None:
|
|
101
|
+
raise ValueError("No configuration loaded. Call load_from_yaml or load_from_dict first.")
|
|
102
|
+
|
|
103
|
+
registry = ToolRegistry()
|
|
104
|
+
|
|
105
|
+
# Register all tools from config
|
|
106
|
+
for tool_name, tool_config in config.tools.items():
|
|
107
|
+
self._register_tool(registry, tool_config)
|
|
108
|
+
|
|
109
|
+
self.tool_registry = registry
|
|
110
|
+
return registry
|
|
111
|
+
|
|
112
|
+
def _register_tool(self, registry: ToolRegistry, tool_config: ToolConfigSchema) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Register a single tool in the registry.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
registry: ToolRegistry to register in
|
|
118
|
+
tool_config: Tool configuration
|
|
119
|
+
"""
|
|
120
|
+
if tool_config.type == ToolMode.DIRECT:
|
|
121
|
+
# Load the function dynamically
|
|
122
|
+
function = self._load_function(tool_config.module, tool_config.function)
|
|
123
|
+
|
|
124
|
+
# Register with registry
|
|
125
|
+
registry.register_direct_tool(
|
|
126
|
+
name=tool_config.name,
|
|
127
|
+
function=function,
|
|
128
|
+
description=tool_config.description,
|
|
129
|
+
json_schema=tool_config.json_schema or self._generate_schema(function),
|
|
130
|
+
takes_ctx=tool_config.takes_ctx,
|
|
131
|
+
category=tool_config.category,
|
|
132
|
+
tags=tool_config.tags,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
elif tool_config.type == ToolMode.MCP:
|
|
136
|
+
# Register MCP tool
|
|
137
|
+
registry.register_mcp_tool(
|
|
138
|
+
name=tool_config.name,
|
|
139
|
+
mcp_server=tool_config.mcp_server,
|
|
140
|
+
mcp_tool_name=tool_config.mcp_tool_name or tool_config.name,
|
|
141
|
+
description=tool_config.description,
|
|
142
|
+
category=tool_config.category,
|
|
143
|
+
tags=tool_config.tags,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def _load_function(self, module_path: str, function_name: str) -> Callable:
|
|
147
|
+
"""
|
|
148
|
+
Dynamically load a function from a module.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
module_path: Python module path (e.g., 'myapp.tools')
|
|
152
|
+
function_name: Function name to load
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The loaded function
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
module = importlib.import_module(module_path)
|
|
159
|
+
function = getattr(module, function_name)
|
|
160
|
+
|
|
161
|
+
if not callable(function):
|
|
162
|
+
raise ValueError(f"{function_name} in {module_path} is not callable")
|
|
163
|
+
|
|
164
|
+
return function
|
|
165
|
+
except ImportError as e:
|
|
166
|
+
raise ImportError(f"Failed to import module {module_path}: {e}")
|
|
167
|
+
except AttributeError as e:
|
|
168
|
+
raise AttributeError(f"Function {function_name} not found in {module_path}: {e}")
|
|
169
|
+
|
|
170
|
+
def _load_class(self, class_path: str) -> Type:
|
|
171
|
+
"""
|
|
172
|
+
Dynamically load a class from a module.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
class_path: Fully qualified class path (e.g., 'myapp.models.MyClass')
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
The loaded class
|
|
179
|
+
"""
|
|
180
|
+
parts = class_path.rsplit('.', 1)
|
|
181
|
+
if len(parts) != 2:
|
|
182
|
+
raise ValueError(f"Invalid class path: {class_path}")
|
|
183
|
+
|
|
184
|
+
module_path, class_name = parts
|
|
185
|
+
try:
|
|
186
|
+
module = importlib.import_module(module_path)
|
|
187
|
+
cls = getattr(module, class_name)
|
|
188
|
+
|
|
189
|
+
if not isinstance(cls, type):
|
|
190
|
+
raise ValueError(f"{class_name} in {module_path} is not a class")
|
|
191
|
+
|
|
192
|
+
return cls
|
|
193
|
+
except ImportError as e:
|
|
194
|
+
raise ImportError(f"Failed to import module {module_path}: {e}")
|
|
195
|
+
except AttributeError as e:
|
|
196
|
+
raise AttributeError(f"Class {class_name} not found in {module_path}: {e}")
|
|
197
|
+
|
|
198
|
+
def _generate_schema(self, function: Callable) -> Dict[str, Any]:
|
|
199
|
+
"""
|
|
200
|
+
Generate a basic JSON schema from function signature.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
function: Function to generate schema for
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
JSON schema dictionary
|
|
207
|
+
"""
|
|
208
|
+
sig = inspect.signature(function)
|
|
209
|
+
properties = {}
|
|
210
|
+
required = []
|
|
211
|
+
|
|
212
|
+
for param_name, param in sig.parameters.items():
|
|
213
|
+
# Skip self, cls, context parameters
|
|
214
|
+
if param_name in ['self', 'cls', 'ctx', 'context']:
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
# Basic type inference
|
|
218
|
+
param_type = "string" # default
|
|
219
|
+
if param.annotation != inspect.Parameter.empty:
|
|
220
|
+
if param.annotation in (int, float):
|
|
221
|
+
param_type = "number"
|
|
222
|
+
elif param.annotation == bool:
|
|
223
|
+
param_type = "boolean"
|
|
224
|
+
elif param.annotation in (list, List):
|
|
225
|
+
param_type = "array"
|
|
226
|
+
elif param.annotation in (dict, Dict):
|
|
227
|
+
param_type = "object"
|
|
228
|
+
|
|
229
|
+
properties[param_name] = {"type": param_type}
|
|
230
|
+
|
|
231
|
+
# Required if no default value
|
|
232
|
+
if param.default == inspect.Parameter.empty:
|
|
233
|
+
required.append(param_name)
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
"type": "object",
|
|
237
|
+
"properties": properties,
|
|
238
|
+
"required": required,
|
|
239
|
+
"additionalProperties": False,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
def _create_output_type_from_schema(self, output_schema: OutputTypeSchema) -> Type[BaseModel]:
|
|
243
|
+
"""
|
|
244
|
+
Dynamically create a Pydantic model from an OutputTypeSchema.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
output_schema: The output type schema from YAML configuration
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
A dynamically created Pydantic model class
|
|
251
|
+
|
|
252
|
+
Example YAML that creates the model:
|
|
253
|
+
output_type:
|
|
254
|
+
name: "SQLAgentOutput"
|
|
255
|
+
description: "Output for SQL agent"
|
|
256
|
+
fields:
|
|
257
|
+
answer:
|
|
258
|
+
type: str
|
|
259
|
+
description: "The answer"
|
|
260
|
+
sql_query:
|
|
261
|
+
type: str
|
|
262
|
+
description: "The SQL query"
|
|
263
|
+
default: ""
|
|
264
|
+
"""
|
|
265
|
+
# Map string type names to Python types
|
|
266
|
+
type_mapping = {
|
|
267
|
+
'str': str,
|
|
268
|
+
'string': str,
|
|
269
|
+
'int': int,
|
|
270
|
+
'integer': int,
|
|
271
|
+
'float': float,
|
|
272
|
+
'number': float,
|
|
273
|
+
'bool': bool,
|
|
274
|
+
'boolean': bool,
|
|
275
|
+
'list': list,
|
|
276
|
+
'array': list,
|
|
277
|
+
'dict': dict,
|
|
278
|
+
'object': dict,
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
# Build field definitions for create_model
|
|
282
|
+
field_definitions = {}
|
|
283
|
+
|
|
284
|
+
for field_name, field_config in output_schema.fields.items():
|
|
285
|
+
# Get the Python type
|
|
286
|
+
python_type = type_mapping.get(field_config.type.lower(), str)
|
|
287
|
+
|
|
288
|
+
# Determine default value
|
|
289
|
+
if field_config.default is not None:
|
|
290
|
+
default_value = field_config.default
|
|
291
|
+
elif field_config.default_factory:
|
|
292
|
+
# Handle default_factory for mutable types
|
|
293
|
+
if field_config.default_factory.lower() in ('list', 'array', '[]'):
|
|
294
|
+
default_value = Field(default_factory=list, description=field_config.description)
|
|
295
|
+
field_definitions[field_name] = (python_type, default_value)
|
|
296
|
+
continue
|
|
297
|
+
elif field_config.default_factory.lower() in ('dict', 'object', '{}'):
|
|
298
|
+
default_value = Field(default_factory=dict, description=field_config.description)
|
|
299
|
+
field_definitions[field_name] = (python_type, default_value)
|
|
300
|
+
continue
|
|
301
|
+
else:
|
|
302
|
+
default_value = ... # Required field
|
|
303
|
+
elif not field_config.required:
|
|
304
|
+
# Optional field with None default
|
|
305
|
+
default_value = None
|
|
306
|
+
else:
|
|
307
|
+
# Required field
|
|
308
|
+
default_value = ...
|
|
309
|
+
|
|
310
|
+
# Create Field with description
|
|
311
|
+
if default_value is ...:
|
|
312
|
+
field_definitions[field_name] = (
|
|
313
|
+
python_type,
|
|
314
|
+
Field(description=field_config.description)
|
|
315
|
+
)
|
|
316
|
+
else:
|
|
317
|
+
field_definitions[field_name] = (
|
|
318
|
+
python_type,
|
|
319
|
+
Field(default=default_value, description=field_config.description)
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Create the dynamic model
|
|
323
|
+
dynamic_model = create_model(
|
|
324
|
+
output_schema.name,
|
|
325
|
+
__doc__=output_schema.description or f"Dynamically generated output model: {output_schema.name}",
|
|
326
|
+
**field_definitions
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
return dynamic_model
|
|
330
|
+
|
|
331
|
+
def _resolve_output_type(self, output_type_config: Optional[str | OutputTypeSchema]) -> Optional[Type]:
|
|
332
|
+
"""
|
|
333
|
+
Resolve output_type configuration to a Python class.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
output_type_config: Either a string class path or an OutputTypeSchema
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
A Pydantic model class or None
|
|
340
|
+
"""
|
|
341
|
+
if output_type_config is None:
|
|
342
|
+
return None
|
|
343
|
+
|
|
344
|
+
if isinstance(output_type_config, str):
|
|
345
|
+
# It's a class path, load it
|
|
346
|
+
return self._load_class(output_type_config)
|
|
347
|
+
elif isinstance(output_type_config, OutputTypeSchema):
|
|
348
|
+
# It's an inline schema, create the model dynamically
|
|
349
|
+
return self._create_output_type_from_schema(output_type_config)
|
|
350
|
+
else:
|
|
351
|
+
raise ValueError(f"Invalid output_type configuration: {type(output_type_config)}")
|
|
352
|
+
|
|
353
|
+
def resolve_prompt_config(
|
|
354
|
+
self,
|
|
355
|
+
prompt_ref: str | PromptConfigSchema,
|
|
356
|
+
config: Optional[ConfigFileSchema] = None
|
|
357
|
+
) -> PromptConfigSchema:
|
|
358
|
+
"""
|
|
359
|
+
Resolve a prompt configuration reference.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
prompt_ref: Prompt name or full configuration
|
|
363
|
+
config: Configuration to use (uses loaded config if None)
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Resolved prompt configuration
|
|
367
|
+
"""
|
|
368
|
+
if isinstance(prompt_ref, PromptConfigSchema):
|
|
369
|
+
return prompt_ref
|
|
370
|
+
|
|
371
|
+
config = config or self.config
|
|
372
|
+
if config is None:
|
|
373
|
+
raise ValueError("No configuration loaded")
|
|
374
|
+
|
|
375
|
+
if prompt_ref not in config.prompts:
|
|
376
|
+
raise ValueError(f"Prompt '{prompt_ref}' not found in configuration")
|
|
377
|
+
|
|
378
|
+
return config.prompts[prompt_ref]
|
|
379
|
+
|
|
380
|
+
def resolve_tools(
|
|
381
|
+
self,
|
|
382
|
+
tool_refs: List[str | ToolConfigSchema],
|
|
383
|
+
config: Optional[ConfigFileSchema] = None
|
|
384
|
+
) -> List[ToolConfigSchema]:
|
|
385
|
+
"""
|
|
386
|
+
Resolve tool configuration references.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
tool_refs: List of tool names or full configurations
|
|
390
|
+
config: Configuration to use (uses loaded config if None)
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
List of resolved tool configurations
|
|
394
|
+
"""
|
|
395
|
+
config = config or self.config
|
|
396
|
+
if config is None:
|
|
397
|
+
raise ValueError("No configuration loaded")
|
|
398
|
+
|
|
399
|
+
resolved = []
|
|
400
|
+
for ref in tool_refs:
|
|
401
|
+
if isinstance(ref, ToolConfigSchema):
|
|
402
|
+
resolved.append(ref)
|
|
403
|
+
elif isinstance(ref, str):
|
|
404
|
+
if ref not in config.tools:
|
|
405
|
+
raise ValueError(f"Tool '{ref}' not found in configuration")
|
|
406
|
+
resolved.append(config.tools[ref])
|
|
407
|
+
else:
|
|
408
|
+
raise ValueError(f"Invalid tool reference: {ref}")
|
|
409
|
+
|
|
410
|
+
return resolved
|
|
411
|
+
|
|
412
|
+
def create_agent(
|
|
413
|
+
self,
|
|
414
|
+
agent_name: str,
|
|
415
|
+
config: Optional[ConfigFileSchema] = None,
|
|
416
|
+
agent_class: Optional[Type] = None,
|
|
417
|
+
) -> Any:
|
|
418
|
+
"""
|
|
419
|
+
Create an agent instance from configuration.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
agent_name: Name of agent in configuration
|
|
423
|
+
config: Configuration to use (uses loaded config if None)
|
|
424
|
+
agent_class: Agent class to instantiate (imports BaseAgent if None)
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Instantiated agent
|
|
428
|
+
"""
|
|
429
|
+
config = config or self.config
|
|
430
|
+
if config is None:
|
|
431
|
+
raise ValueError("No configuration loaded. Call load_from_yaml or load_from_dict first.")
|
|
432
|
+
|
|
433
|
+
if agent_name not in config.agents:
|
|
434
|
+
raise ValueError(f"Agent '{agent_name}' not found in configuration")
|
|
435
|
+
|
|
436
|
+
agent_config = config.agents[agent_name]
|
|
437
|
+
|
|
438
|
+
# Import agent class if not provided
|
|
439
|
+
if agent_class is None:
|
|
440
|
+
from rakam_system_agent.components import BaseAgent
|
|
441
|
+
agent_class = BaseAgent
|
|
442
|
+
|
|
443
|
+
# Resolve prompt config
|
|
444
|
+
prompt_config = self.resolve_prompt_config(agent_config.prompt_config, config)
|
|
445
|
+
|
|
446
|
+
# Resolve tools
|
|
447
|
+
tool_configs = self.resolve_tools(agent_config.tools, config)
|
|
448
|
+
|
|
449
|
+
# Create tool registry with resolved tools
|
|
450
|
+
registry = ToolRegistry()
|
|
451
|
+
for tool_config in tool_configs:
|
|
452
|
+
self._register_tool(registry, tool_config)
|
|
453
|
+
|
|
454
|
+
# Load deps_type if specified
|
|
455
|
+
deps_type = None
|
|
456
|
+
if agent_config.deps_type:
|
|
457
|
+
deps_type = self._load_class(agent_config.deps_type)
|
|
458
|
+
|
|
459
|
+
# Resolve output_type (can be string class path or inline schema)
|
|
460
|
+
output_type = self._resolve_output_type(agent_config.output_type)
|
|
461
|
+
|
|
462
|
+
# Convert model config to ModelSettings
|
|
463
|
+
model_settings = ModelSettings(
|
|
464
|
+
parallel_tool_calls=agent_config.llm_config.parallel_tool_calls,
|
|
465
|
+
temperature=agent_config.llm_config.temperature,
|
|
466
|
+
max_tokens=agent_config.llm_config.max_tokens,
|
|
467
|
+
**agent_config.llm_config.extra_settings,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
# Create agent configuration dict
|
|
471
|
+
config_dict = {
|
|
472
|
+
"model": agent_config.llm_config.model,
|
|
473
|
+
"stateful": agent_config.stateful,
|
|
474
|
+
"system_prompt": prompt_config.system_prompt,
|
|
475
|
+
**agent_config.metadata,
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
# Instantiate agent
|
|
479
|
+
agent = agent_class(
|
|
480
|
+
name=agent_config.name,
|
|
481
|
+
config=config_dict,
|
|
482
|
+
model=agent_config.llm_config.model,
|
|
483
|
+
deps_type=deps_type,
|
|
484
|
+
output_type=output_type,
|
|
485
|
+
system_prompt=prompt_config.system_prompt,
|
|
486
|
+
tool_registry=registry,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
# Store tracking configuration
|
|
490
|
+
agent._tracking_enabled = agent_config.enable_tracking
|
|
491
|
+
agent._tracking_output_dir = agent_config.tracking_output_dir
|
|
492
|
+
|
|
493
|
+
return agent
|
|
494
|
+
|
|
495
|
+
def create_all_agents(
|
|
496
|
+
self,
|
|
497
|
+
config: Optional[ConfigFileSchema] = None,
|
|
498
|
+
agent_class: Optional[Type] = None,
|
|
499
|
+
) -> Dict[str, Any]:
|
|
500
|
+
"""
|
|
501
|
+
Create all agents from configuration.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
config: Configuration to use (uses loaded config if None)
|
|
505
|
+
agent_class: Agent class to instantiate (imports BaseAgent if None)
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
Dictionary mapping agent names to instances
|
|
509
|
+
"""
|
|
510
|
+
config = config or self.config
|
|
511
|
+
if config is None:
|
|
512
|
+
raise ValueError("No configuration loaded")
|
|
513
|
+
|
|
514
|
+
agents = {}
|
|
515
|
+
for agent_name in config.agents:
|
|
516
|
+
agents[agent_name] = self.create_agent(agent_name, config, agent_class)
|
|
517
|
+
|
|
518
|
+
return agents
|
|
519
|
+
|
|
520
|
+
def validate_config(self, config_path: Optional[str] = None) -> tuple[bool, List[str]]:
|
|
521
|
+
"""
|
|
522
|
+
Validate a configuration file.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
config_path: Path to config file (uses loaded config if None)
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
Tuple of (is_valid, list_of_errors)
|
|
529
|
+
"""
|
|
530
|
+
errors = []
|
|
531
|
+
|
|
532
|
+
try:
|
|
533
|
+
if config_path:
|
|
534
|
+
config = self.load_from_yaml(config_path)
|
|
535
|
+
else:
|
|
536
|
+
config = self.config
|
|
537
|
+
if config is None:
|
|
538
|
+
return False, ["No configuration loaded"]
|
|
539
|
+
|
|
540
|
+
# Validate agent references
|
|
541
|
+
for agent_name, agent_config in config.agents.items():
|
|
542
|
+
# Check prompt reference
|
|
543
|
+
try:
|
|
544
|
+
self.resolve_prompt_config(agent_config.prompt_config, config)
|
|
545
|
+
except ValueError as e:
|
|
546
|
+
errors.append(f"Agent '{agent_name}': {e}")
|
|
547
|
+
|
|
548
|
+
# Check tool references
|
|
549
|
+
try:
|
|
550
|
+
self.resolve_tools(agent_config.tools, config)
|
|
551
|
+
except ValueError as e:
|
|
552
|
+
errors.append(f"Agent '{agent_name}': {e}")
|
|
553
|
+
|
|
554
|
+
# Check deps_type if specified
|
|
555
|
+
if agent_config.deps_type:
|
|
556
|
+
try:
|
|
557
|
+
self._load_class(agent_config.deps_type)
|
|
558
|
+
except (ImportError, AttributeError) as e:
|
|
559
|
+
errors.append(f"Agent '{agent_name}' deps_type: {e}")
|
|
560
|
+
|
|
561
|
+
# Check output_type if specified
|
|
562
|
+
if agent_config.output_type:
|
|
563
|
+
try:
|
|
564
|
+
self._resolve_output_type(agent_config.output_type)
|
|
565
|
+
except (ImportError, AttributeError, ValueError) as e:
|
|
566
|
+
errors.append(f"Agent '{agent_name}' output_type: {e}")
|
|
567
|
+
|
|
568
|
+
# Validate tools can be loaded
|
|
569
|
+
for tool_name, tool_config in config.tools.items():
|
|
570
|
+
if tool_config.type == ToolMode.DIRECT:
|
|
571
|
+
try:
|
|
572
|
+
self._load_function(tool_config.module, tool_config.function)
|
|
573
|
+
except (ImportError, AttributeError) as e:
|
|
574
|
+
errors.append(f"Tool '{tool_name}': {e}")
|
|
575
|
+
|
|
576
|
+
return len(errors) == 0, errors
|
|
577
|
+
|
|
578
|
+
except Exception as e:
|
|
579
|
+
return False, [f"Configuration error: {e}"]
|
|
580
|
+
|