idun-agent-engine 0.1.0__py3-none-any.whl → 0.2.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.
- idun_agent_engine/__init__.py +2 -25
- idun_agent_engine/_version.py +1 -1
- idun_agent_engine/agent/__init__.py +10 -0
- idun_agent_engine/agent/base.py +97 -0
- idun_agent_engine/agent/haystack/__init__.py +9 -0
- idun_agent_engine/agent/haystack/haystack.py +261 -0
- idun_agent_engine/agent/haystack/haystack_model.py +13 -0
- idun_agent_engine/agent/haystack/utils.py +13 -0
- idun_agent_engine/agent/langgraph/__init__.py +7 -0
- idun_agent_engine/agent/langgraph/langgraph.py +429 -0
- idun_agent_engine/cli/__init__.py +16 -0
- idun_agent_engine/core/__init__.py +11 -0
- idun_agent_engine/core/app_factory.py +63 -0
- idun_agent_engine/core/config_builder.py +456 -0
- idun_agent_engine/core/engine_config.py +22 -0
- idun_agent_engine/core/server_runner.py +146 -0
- idun_agent_engine/observability/__init__.py +13 -0
- idun_agent_engine/observability/base.py +111 -0
- idun_agent_engine/observability/langfuse/__init__.py +5 -0
- idun_agent_engine/observability/langfuse/langfuse_handler.py +72 -0
- idun_agent_engine/observability/phoenix/__init__.py +5 -0
- idun_agent_engine/observability/phoenix/phoenix_handler.py +65 -0
- idun_agent_engine/observability/phoenix_local/__init__.py +5 -0
- idun_agent_engine/observability/phoenix_local/phoenix_local_handler.py +123 -0
- idun_agent_engine/py.typed +0 -1
- idun_agent_engine/server/__init__.py +5 -0
- idun_agent_engine/server/dependencies.py +23 -0
- idun_agent_engine/server/lifespan.py +42 -0
- idun_agent_engine/server/routers/__init__.py +5 -0
- idun_agent_engine/server/routers/agent.py +68 -0
- idun_agent_engine/server/routers/base.py +60 -0
- idun_agent_engine/server/server_config.py +8 -0
- idun_agent_engine-0.2.1.dist-info/METADATA +278 -0
- idun_agent_engine-0.2.1.dist-info/RECORD +35 -0
- {idun_agent_engine-0.1.0.dist-info → idun_agent_engine-0.2.1.dist-info}/WHEEL +1 -1
- idun_agent_engine-0.1.0.dist-info/METADATA +0 -317
- idun_agent_engine-0.1.0.dist-info/RECORD +0 -6
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
"""Configuration Builder for Idun Agent Engine.
|
|
2
|
+
|
|
3
|
+
This module provides a fluent API for building configuration objects using Pydantic models.
|
|
4
|
+
This approach ensures type safety, validation, and consistency with the rest of the codebase.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
from idun_agent_schema.engine.agent_framework import AgentFramework
|
|
12
|
+
from idun_agent_schema.engine.haystack import HaystackAgentConfig
|
|
13
|
+
from idun_agent_schema.engine.langgraph import (
|
|
14
|
+
LangGraphAgentConfig,
|
|
15
|
+
SqliteCheckpointConfig,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from idun_agent_engine.server.server_config import ServerAPIConfig
|
|
19
|
+
|
|
20
|
+
from ..agent.base import BaseAgent
|
|
21
|
+
from .engine_config import AgentConfig, EngineConfig, ServerConfig
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ConfigBuilder:
|
|
25
|
+
"""A fluent builder for creating Idun Agent Engine configurations using Pydantic models.
|
|
26
|
+
|
|
27
|
+
This class provides a convenient way to build strongly-typed configuration objects
|
|
28
|
+
that are validated at creation time, ensuring consistency and catching errors early.
|
|
29
|
+
It also handles agent initialization and management.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
config = (ConfigBuilder()
|
|
33
|
+
.with_api_port(8080)
|
|
34
|
+
.with_langgraph_agent(
|
|
35
|
+
name="My Agent",
|
|
36
|
+
graph_definition="my_agent.py:graph",
|
|
37
|
+
sqlite_checkpointer="agent.db")
|
|
38
|
+
.build())
|
|
39
|
+
|
|
40
|
+
app = create_app(config_dict=config.model_dump())
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self):
|
|
44
|
+
"""Initialize a new configuration builder with default values."""
|
|
45
|
+
self._server_config = ServerConfig()
|
|
46
|
+
self._agent_config: AgentConfig | None = None
|
|
47
|
+
|
|
48
|
+
def with_api_port(self, port: int) -> "ConfigBuilder":
|
|
49
|
+
"""Set the API port for the server.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
port: The port number to bind the server to
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
ConfigBuilder: This builder instance for method chaining
|
|
56
|
+
"""
|
|
57
|
+
# Create new API config with updated port
|
|
58
|
+
api_config = ServerAPIConfig(port=port)
|
|
59
|
+
self._server_config = ServerConfig(
|
|
60
|
+
api=api_config,
|
|
61
|
+
)
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
def with_server_config(
|
|
65
|
+
self, api_port: int | None = None, telemetry_provider: str | None = None
|
|
66
|
+
) -> "ConfigBuilder":
|
|
67
|
+
"""Set server configuration options directly.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
api_port: Optional API port
|
|
71
|
+
telemetry_provider: Optional telemetry provider
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
ConfigBuilder: This builder instance for method chaining
|
|
75
|
+
"""
|
|
76
|
+
api_config = (
|
|
77
|
+
ServerAPIConfig(port=api_port) if api_port else self._server_config.api
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
self._server_config = ServerConfig(api=api_config)
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
def with_config_from_api(self, agent_api_key: str, url: str) -> "ConfigBuilder":
|
|
84
|
+
"""Fetches the yaml config file, from idun agent manager api.
|
|
85
|
+
|
|
86
|
+
Requires the agent api key to pass in the headers.
|
|
87
|
+
"""
|
|
88
|
+
import requests
|
|
89
|
+
import yaml
|
|
90
|
+
|
|
91
|
+
headers = {"auth": f"Bearer {agent_api_key}"}
|
|
92
|
+
try:
|
|
93
|
+
response = requests.get(url=url, headers=headers)
|
|
94
|
+
if response.status_code != 200:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"Error sending retrieving config from url. response : {response.json()}"
|
|
97
|
+
)
|
|
98
|
+
yaml_config = yaml.safe_load(response.text)
|
|
99
|
+
self._server_config = yaml_config["engine_config"]["server"]
|
|
100
|
+
self._agent_config = yaml_config["engine_config"]["agent"]
|
|
101
|
+
return self
|
|
102
|
+
|
|
103
|
+
except Exception as e:
|
|
104
|
+
raise ValueError(f"Error occured while getting config from api: {e}") from e
|
|
105
|
+
|
|
106
|
+
def with_langgraph_agent(
|
|
107
|
+
self,
|
|
108
|
+
name: str,
|
|
109
|
+
graph_definition: str,
|
|
110
|
+
sqlite_checkpointer: str | None = None,
|
|
111
|
+
**additional_config,
|
|
112
|
+
) -> "ConfigBuilder":
|
|
113
|
+
"""Configure a LangGraph agent using the LangGraphAgentConfig model.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
name: Human-readable name for the agent
|
|
117
|
+
graph_definition: Path to the graph in format "module.py:variable_name"
|
|
118
|
+
sqlite_checkpointer: Optional path to SQLite database for checkpointing
|
|
119
|
+
**additional_config: Additional configuration parameters
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
ConfigBuilder: This builder instance for method chaining
|
|
123
|
+
"""
|
|
124
|
+
# Build the agent config dictionary
|
|
125
|
+
agent_config_dict = {
|
|
126
|
+
"name": name,
|
|
127
|
+
"graph_definition": graph_definition,
|
|
128
|
+
**additional_config,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Add checkpointer if specified
|
|
132
|
+
if sqlite_checkpointer:
|
|
133
|
+
checkpointer = SqliteCheckpointConfig(
|
|
134
|
+
type="sqlite", db_url=f"sqlite:///{sqlite_checkpointer}"
|
|
135
|
+
)
|
|
136
|
+
agent_config_dict["checkpointer"] = checkpointer
|
|
137
|
+
|
|
138
|
+
# Create and validate the LangGraph config
|
|
139
|
+
langgraph_config = LangGraphAgentConfig.model_validate(agent_config_dict)
|
|
140
|
+
|
|
141
|
+
# Create the agent config (store as strongly-typed model, not dict)
|
|
142
|
+
self._agent_config = AgentConfig(type="langgraph", config=langgraph_config)
|
|
143
|
+
return self
|
|
144
|
+
|
|
145
|
+
def with_custom_agent(
|
|
146
|
+
self, agent_type: str, config: dict[str, Any]
|
|
147
|
+
) -> "ConfigBuilder":
|
|
148
|
+
"""Configure a custom agent type.
|
|
149
|
+
|
|
150
|
+
This method allows for configuring agent types that don't have
|
|
151
|
+
dedicated builder methods yet. The config will be validated
|
|
152
|
+
when the AgentConfig is created.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
agent_type: The type of agent (e.g., "crewai", "autogen")
|
|
156
|
+
config: Configuration dictionary specific to the agent type
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
ConfigBuilder: This builder instance for method chaining
|
|
160
|
+
"""
|
|
161
|
+
if agent_type == AgentFramework.LANGGRAPH:
|
|
162
|
+
self._agent_config = AgentConfig(
|
|
163
|
+
type="langgraph", config=LangGraphAgentConfig.model_validate(config)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
elif agent_type == AgentFramework.HAYSTACK:
|
|
167
|
+
self._agent_config = AgentConfig(
|
|
168
|
+
type="haystack", config=HaystackAgentConfig.model_validate(config)
|
|
169
|
+
)
|
|
170
|
+
else:
|
|
171
|
+
raise ValueError(f"Unsupported agent type: {agent_type}")
|
|
172
|
+
return self
|
|
173
|
+
|
|
174
|
+
def build(self) -> EngineConfig:
|
|
175
|
+
"""Build and return the complete configuration as a validated Pydantic model.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
EngineConfig: The complete, validated configuration object
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ValueError: If the configuration is incomplete or invalid
|
|
182
|
+
"""
|
|
183
|
+
if not self._agent_config:
|
|
184
|
+
raise ValueError(
|
|
185
|
+
"Agent configuration is required. Use with_langgraph_agent() or with_custom_agent()"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Create and validate the complete configuration
|
|
189
|
+
return EngineConfig(server=self._server_config, agent=self._agent_config)
|
|
190
|
+
|
|
191
|
+
def build_dict(self) -> dict[str, Any]:
|
|
192
|
+
"""Build and return the configuration as a dictionary.
|
|
193
|
+
|
|
194
|
+
This is a convenience method for backward compatibility.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Dict[str, Any]: The complete configuration dictionary
|
|
198
|
+
"""
|
|
199
|
+
engine_config = self.build()
|
|
200
|
+
return engine_config.model_dump()
|
|
201
|
+
|
|
202
|
+
def save_to_file(self, file_path: str) -> None:
|
|
203
|
+
"""Save the configuration to a YAML file.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
file_path: Path where to save the configuration file
|
|
207
|
+
"""
|
|
208
|
+
config = self.build_dict()
|
|
209
|
+
with open(file_path, "w") as f:
|
|
210
|
+
yaml.dump(config, f, default_flow_style=False, indent=2)
|
|
211
|
+
|
|
212
|
+
async def build_and_initialize_agent(self) -> BaseAgent:
|
|
213
|
+
"""Build configuration and initialize the agent in one step.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
BaseAgent: Initialized agent instance
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
ValueError: If agent type is unsupported or configuration is invalid
|
|
220
|
+
"""
|
|
221
|
+
engine_config = self.build()
|
|
222
|
+
return await self.initialize_agent_from_config(engine_config)
|
|
223
|
+
|
|
224
|
+
@staticmethod
|
|
225
|
+
async def initialize_agent_from_config(engine_config: EngineConfig) -> BaseAgent:
|
|
226
|
+
"""Initialize an agent instance from a validated EngineConfig.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
engine_config: Validated configuration object
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
BaseAgent: Initialized agent instance
|
|
233
|
+
|
|
234
|
+
Raises:
|
|
235
|
+
ValueError: If agent type is unsupported
|
|
236
|
+
"""
|
|
237
|
+
agent_config_obj = engine_config.agent.config
|
|
238
|
+
print("CONFIG:", agent_config_obj)
|
|
239
|
+
agent_type = engine_config.agent.type
|
|
240
|
+
|
|
241
|
+
# Initialize the appropriate agent
|
|
242
|
+
agent_instance = None
|
|
243
|
+
if agent_type == AgentFramework.LANGGRAPH:
|
|
244
|
+
from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
validated_config = LangGraphAgentConfig.model_validate(agent_config_obj)
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
raise ValueError(
|
|
251
|
+
f"Cannot validate into a LangGraphAgentConfig model. Got {agent_config_obj}"
|
|
252
|
+
) from e
|
|
253
|
+
|
|
254
|
+
agent_instance = LanggraphAgent()
|
|
255
|
+
|
|
256
|
+
elif agent_type == AgentFramework.HAYSTACK:
|
|
257
|
+
from idun_agent_engine.agent.haystack.haystack import HaystackAgent
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
validated_config = HaystackAgentConfig.model_validate(agent_config_obj)
|
|
261
|
+
|
|
262
|
+
except Exception as e:
|
|
263
|
+
raise ValueError(
|
|
264
|
+
f"Cannot validate into a HaystackAgentConfig model. Got {agent_config_obj}"
|
|
265
|
+
) from e
|
|
266
|
+
agent_instance = HaystackAgent()
|
|
267
|
+
else:
|
|
268
|
+
raise ValueError(f"Unsupported agent type: {agent_type}")
|
|
269
|
+
|
|
270
|
+
# Initialize the agent with its configuration
|
|
271
|
+
await agent_instance.initialize(validated_config) # type: ignore[arg-type]
|
|
272
|
+
return agent_instance
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def get_agent_class(agent_type: str) -> type[BaseAgent]:
|
|
276
|
+
"""Get the agent class for a given agent type without initializing it.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
agent_type: The type of agent
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Type[BaseAgent]: The agent class
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
ValueError: If agent type is unsupported
|
|
286
|
+
"""
|
|
287
|
+
if agent_type == "langgraph":
|
|
288
|
+
from ..agent.langgraph.langgraph import LanggraphAgent
|
|
289
|
+
|
|
290
|
+
return LanggraphAgent
|
|
291
|
+
|
|
292
|
+
else:
|
|
293
|
+
raise ValueError(f"Unsupported agent type: {agent_type}")
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def validate_agent_config(
|
|
297
|
+
agent_type: str, config: dict[str, Any]
|
|
298
|
+
) -> dict[str, Any]:
|
|
299
|
+
"""Validate agent configuration against the appropriate Pydantic model.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
agent_type: The type of agent
|
|
303
|
+
config: Configuration dictionary to validate
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Dict[str, Any]: Validated configuration dictionary
|
|
307
|
+
|
|
308
|
+
Raises:
|
|
309
|
+
ValueError: If agent type is unsupported or config is invalid
|
|
310
|
+
"""
|
|
311
|
+
if agent_type == "langgraph":
|
|
312
|
+
validated_config = LangGraphAgentConfig.model_validate(config)
|
|
313
|
+
return validated_config.model_dump()
|
|
314
|
+
# Future agent types can be added here:
|
|
315
|
+
# elif agent_type == "crewai":
|
|
316
|
+
# validated_config = CrewAIAgentConfig.model_validate(config)
|
|
317
|
+
# return validated_config.model_dump()
|
|
318
|
+
else:
|
|
319
|
+
raise ValueError(f"Unsupported agent type: {agent_type}")
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def load_from_file(config_path: str = "config.yaml") -> EngineConfig:
|
|
323
|
+
"""Load configuration from a YAML file and return a validated EngineConfig.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
config_path: Path to the configuration YAML file
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
EngineConfig: Validated configuration object
|
|
330
|
+
|
|
331
|
+
Raises:
|
|
332
|
+
FileNotFoundError: If the configuration file doesn't exist
|
|
333
|
+
ValidationError: If the configuration is invalid
|
|
334
|
+
"""
|
|
335
|
+
path = Path(config_path)
|
|
336
|
+
if not path.is_absolute():
|
|
337
|
+
# Resolve relative to the current working directory
|
|
338
|
+
path = Path.cwd() / path
|
|
339
|
+
|
|
340
|
+
with open(path) as f:
|
|
341
|
+
config_data = yaml.safe_load(f)
|
|
342
|
+
|
|
343
|
+
return EngineConfig.model_validate(config_data)
|
|
344
|
+
|
|
345
|
+
@staticmethod
|
|
346
|
+
async def load_and_initialize_agent(
|
|
347
|
+
config_path: str = "config.yaml",
|
|
348
|
+
) -> tuple[EngineConfig, BaseAgent]:
|
|
349
|
+
"""Load configuration and initialize agent in one step.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
config_path: Path to the configuration YAML file
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
tuple[EngineConfig, BaseAgent]: Configuration and initialized agent
|
|
356
|
+
"""
|
|
357
|
+
engine_config = ConfigBuilder.load_from_file(config_path)
|
|
358
|
+
agent = await ConfigBuilder.initialize_agent_from_config(engine_config)
|
|
359
|
+
return engine_config, agent
|
|
360
|
+
|
|
361
|
+
@staticmethod
|
|
362
|
+
def resolve_config(
|
|
363
|
+
config_path: str | None = None,
|
|
364
|
+
config_dict: dict[str, Any] | None = None,
|
|
365
|
+
engine_config: EngineConfig | None = None,
|
|
366
|
+
) -> EngineConfig:
|
|
367
|
+
"""Umbrella function to resolve configuration from various sources.
|
|
368
|
+
|
|
369
|
+
This function handles all the different ways configuration can be provided
|
|
370
|
+
and returns a validated EngineConfig. It follows a priority order:
|
|
371
|
+
1. engine_config (pre-validated EngineConfig from ConfigBuilder)
|
|
372
|
+
2. config_dict (dictionary to be validated)
|
|
373
|
+
3. config_path (file path to load and validate)
|
|
374
|
+
4. default "config.yaml" file
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
config_path: Path to a YAML configuration file
|
|
378
|
+
config_dict: Dictionary containing configuration
|
|
379
|
+
engine_config: Pre-validated EngineConfig instance
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
EngineConfig: Validated configuration object
|
|
383
|
+
|
|
384
|
+
Raises:
|
|
385
|
+
FileNotFoundError: If config file doesn't exist
|
|
386
|
+
ValidationError: If configuration is invalid
|
|
387
|
+
"""
|
|
388
|
+
if engine_config:
|
|
389
|
+
# Use pre-validated EngineConfig (from ConfigBuilder)
|
|
390
|
+
print("✅ Using pre-validated EngineConfig")
|
|
391
|
+
return engine_config
|
|
392
|
+
elif config_dict:
|
|
393
|
+
# Validate dictionary config
|
|
394
|
+
print("✅ Validated dictionary configuration")
|
|
395
|
+
return EngineConfig.model_validate(config_dict)
|
|
396
|
+
elif config_path:
|
|
397
|
+
# Load from file using ConfigB/uilder
|
|
398
|
+
print(f"✅ Loaded configuration from {config_path}")
|
|
399
|
+
return ConfigBuilder.load_from_file(config_path)
|
|
400
|
+
else:
|
|
401
|
+
# Default to loading config.yaml
|
|
402
|
+
print("✅ Loaded default configuration from config.yaml")
|
|
403
|
+
return ConfigBuilder.load_from_file("config.yaml")
|
|
404
|
+
|
|
405
|
+
@classmethod
|
|
406
|
+
def from_dict(cls, config_dict: dict[str, Any]) -> "ConfigBuilder":
|
|
407
|
+
"""Create a ConfigBuilder from an existing configuration dictionary.
|
|
408
|
+
|
|
409
|
+
This method validates the input dictionary against the Pydantic models.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
config_dict: Existing configuration dictionary
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
ConfigBuilder: A new builder instance with the provided configuration
|
|
416
|
+
|
|
417
|
+
Raises:
|
|
418
|
+
ValidationError: If the configuration dictionary is invalid
|
|
419
|
+
"""
|
|
420
|
+
# Validate the entire config first
|
|
421
|
+
engine_config = EngineConfig.model_validate(config_dict)
|
|
422
|
+
|
|
423
|
+
# Create a new builder
|
|
424
|
+
builder = cls()
|
|
425
|
+
builder._server_config = engine_config.server
|
|
426
|
+
builder._agent_config = engine_config.agent
|
|
427
|
+
|
|
428
|
+
return builder
|
|
429
|
+
|
|
430
|
+
@classmethod
|
|
431
|
+
def from_file(cls, config_path: str = "config.yaml") -> "ConfigBuilder":
|
|
432
|
+
"""Create a ConfigBuilder from a YAML configuration file.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
config_path: Path to the configuration YAML file
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
ConfigBuilder: A new builder instance with the loaded configuration
|
|
439
|
+
"""
|
|
440
|
+
engine_config = cls.load_from_file(config_path)
|
|
441
|
+
return cls.from_engine_config(engine_config)
|
|
442
|
+
|
|
443
|
+
@classmethod
|
|
444
|
+
def from_engine_config(cls, engine_config: EngineConfig) -> "ConfigBuilder":
|
|
445
|
+
"""Create a ConfigBuilder from an existing EngineConfig instance.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
engine_config: Existing EngineConfig instance
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
ConfigBuilder: A new builder instance with the provided configuration
|
|
452
|
+
"""
|
|
453
|
+
builder = cls()
|
|
454
|
+
builder._server_config = engine_config.server
|
|
455
|
+
builder._agent_config = engine_config.agent
|
|
456
|
+
return builder
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Compatibility re-exports for Engine configuration models."""
|
|
2
|
+
|
|
3
|
+
from idun_agent_schema.engine.agent import BaseAgentConfig # noqa: F401
|
|
4
|
+
from idun_agent_schema.engine.agent import ( # noqa: F401
|
|
5
|
+
AgentConfig,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
from idun_agent_schema.engine.engine import ( # noqa: F401
|
|
9
|
+
EngineConfig,
|
|
10
|
+
)
|
|
11
|
+
from idun_agent_schema.engine.langgraph import ( # noqa: F401
|
|
12
|
+
LangGraphAgentConfig,
|
|
13
|
+
)
|
|
14
|
+
from idun_agent_schema.engine.server import ServerConfig # noqa: F401
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AgentConfig",
|
|
18
|
+
"EngineConfig",
|
|
19
|
+
"LangGraphAgentConfig",
|
|
20
|
+
"BaseAgentConfig",
|
|
21
|
+
"ServerConfig",
|
|
22
|
+
]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Server Runner for Idun Agent Engine.
|
|
2
|
+
|
|
3
|
+
This module provides convenient functions to run FastAPI applications created with
|
|
4
|
+
the Idun Agent Engine. It handles common deployment scenarios and provides sensible defaults.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import uvicorn
|
|
8
|
+
from fastapi import FastAPI
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_server(
|
|
12
|
+
app: FastAPI,
|
|
13
|
+
host: str = "0.0.0.0",
|
|
14
|
+
port: int = 8000,
|
|
15
|
+
reload: bool = False,
|
|
16
|
+
log_level: str = "info",
|
|
17
|
+
workers: int | None = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Run a FastAPI application created with Idun Agent Engine.
|
|
20
|
+
|
|
21
|
+
This is a convenience function that wraps uvicorn.run() with sensible defaults
|
|
22
|
+
for serving agent applications. It automatically handles common deployment scenarios.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
app: The FastAPI application created with create_app()
|
|
26
|
+
host: Host to bind the server to. Defaults to "0.0.0.0" (all interfaces)
|
|
27
|
+
port: Port to bind the server to. Defaults to 8000
|
|
28
|
+
reload: Enable auto-reload for development. Defaults to False
|
|
29
|
+
log_level: Logging level. Defaults to "info"
|
|
30
|
+
workers: Number of worker processes. If None, uses single process
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
from idun_agent_engine import create_app, run_server
|
|
34
|
+
|
|
35
|
+
# Create your app
|
|
36
|
+
app = create_app("config.yaml")
|
|
37
|
+
|
|
38
|
+
# Run in development mode
|
|
39
|
+
run_server(app, reload=True)
|
|
40
|
+
|
|
41
|
+
# Run in production mode
|
|
42
|
+
run_server(app, workers=4)
|
|
43
|
+
"""
|
|
44
|
+
print(f"🌐 Starting Idun Agent Engine server on http://{host}:{port}")
|
|
45
|
+
print(f"📚 API documentation available at http://{host}:{port}/docs")
|
|
46
|
+
|
|
47
|
+
if reload and workers:
|
|
48
|
+
print(
|
|
49
|
+
"⚠️ Warning: reload=True is incompatible with workers > 1. Disabling reload."
|
|
50
|
+
)
|
|
51
|
+
reload = False
|
|
52
|
+
|
|
53
|
+
uvicorn.run(
|
|
54
|
+
app,
|
|
55
|
+
host=host,
|
|
56
|
+
port=port,
|
|
57
|
+
# reload=reload,
|
|
58
|
+
log_level=log_level,
|
|
59
|
+
# workers=workers
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def run_server_from_config(config_path: str = "config.yaml", **kwargs) -> None:
|
|
64
|
+
"""Create and run a server directly from a configuration file.
|
|
65
|
+
|
|
66
|
+
This is the most convenient way to start a server - it combines create_app()
|
|
67
|
+
and run_server() in a single function call using ConfigBuilder.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
config_path: Path to the configuration YAML file
|
|
71
|
+
**kwargs: Additional arguments passed to run_server()
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
# Run server directly from config
|
|
75
|
+
run_server_from_config("my_agent.yaml", port=8080, reload=True)
|
|
76
|
+
"""
|
|
77
|
+
from .app_factory import create_app
|
|
78
|
+
from .config_builder import ConfigBuilder
|
|
79
|
+
|
|
80
|
+
# Load configuration using ConfigBuilder
|
|
81
|
+
engine_config = ConfigBuilder.load_from_file(config_path)
|
|
82
|
+
|
|
83
|
+
# Create app with the loaded config
|
|
84
|
+
app = create_app(engine_config=engine_config)
|
|
85
|
+
|
|
86
|
+
# Extract port from config if not overridden
|
|
87
|
+
if "port" not in kwargs:
|
|
88
|
+
kwargs["port"] = engine_config.server.api.port
|
|
89
|
+
|
|
90
|
+
# Show configuration info
|
|
91
|
+
print(f"🔧 Loaded configuration from {config_path}")
|
|
92
|
+
# Best-effort: handle both dict-like and model access
|
|
93
|
+
agent_name = (
|
|
94
|
+
engine_config.agent.config.get("name") # type: ignore[call-arg, index]
|
|
95
|
+
if hasattr(engine_config.agent.config, "get")
|
|
96
|
+
else getattr(engine_config.agent.config, "name", "Unknown")
|
|
97
|
+
)
|
|
98
|
+
print(f"🤖 Agent: {agent_name} ({engine_config.agent.type})")
|
|
99
|
+
|
|
100
|
+
run_server(app, **kwargs)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def run_server_from_builder(config_builder, **kwargs) -> None:
|
|
104
|
+
"""Create and run a server directly from a ConfigBuilder instance.
|
|
105
|
+
|
|
106
|
+
This allows for programmatic configuration with immediate server startup.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
config_builder: ConfigBuilder instance (can be built or unbuilt)
|
|
110
|
+
**kwargs: Additional arguments passed to run_server()
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
from idun_agent_engine import ConfigBuilder
|
|
114
|
+
|
|
115
|
+
builder = (ConfigBuilder()
|
|
116
|
+
.with_langgraph_agent(name="My Agent", graph_definition="agent.py:graph")
|
|
117
|
+
.with_api_port(8080))
|
|
118
|
+
|
|
119
|
+
run_server_from_builder(builder, reload=True)
|
|
120
|
+
"""
|
|
121
|
+
from .app_factory import create_app
|
|
122
|
+
|
|
123
|
+
# Build the configuration if it's a ConfigBuilder instance
|
|
124
|
+
if hasattr(config_builder, "build"):
|
|
125
|
+
engine_config = config_builder.build()
|
|
126
|
+
else:
|
|
127
|
+
# Assume it's already an EngineConfig
|
|
128
|
+
engine_config = config_builder
|
|
129
|
+
|
|
130
|
+
# Create app with the config
|
|
131
|
+
app = create_app(engine_config=engine_config)
|
|
132
|
+
|
|
133
|
+
# Extract port from config if not overridden
|
|
134
|
+
if "port" not in kwargs:
|
|
135
|
+
kwargs["port"] = engine_config.server.api.port
|
|
136
|
+
|
|
137
|
+
# Show configuration info
|
|
138
|
+
print("🔧 Using programmatic configuration")
|
|
139
|
+
agent_name = (
|
|
140
|
+
engine_config.agent.config.get("name") # type: ignore[call-arg, index]
|
|
141
|
+
if hasattr(engine_config.agent.config, "get")
|
|
142
|
+
else getattr(engine_config.agent.config, "name", "Unknown")
|
|
143
|
+
)
|
|
144
|
+
print(f"🤖 Agent: {agent_name} ({engine_config.agent.type})")
|
|
145
|
+
|
|
146
|
+
run_server(app, **kwargs)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Observability package providing provider-agnostic tracing interfaces."""
|
|
2
|
+
|
|
3
|
+
from .base import (
|
|
4
|
+
ObservabilityConfig,
|
|
5
|
+
ObservabilityHandlerBase,
|
|
6
|
+
create_observability_handler,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"ObservabilityConfig",
|
|
11
|
+
"ObservabilityHandlerBase",
|
|
12
|
+
"create_observability_handler",
|
|
13
|
+
]
|