idun-agent-engine 0.2.6__py3-none-any.whl → 0.3.0__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/_version.py +1 -1
- idun_agent_engine/agent/adk/__init__.py +5 -0
- idun_agent_engine/agent/adk/adk.py +296 -0
- idun_agent_engine/agent/base.py +16 -1
- idun_agent_engine/agent/haystack/haystack.py +14 -1
- idun_agent_engine/agent/langgraph/langgraph.py +165 -55
- idun_agent_engine/core/app_factory.py +11 -1
- idun_agent_engine/core/config_builder.py +214 -23
- idun_agent_engine/core/engine_config.py +1 -2
- idun_agent_engine/core/server_runner.py +2 -3
- idun_agent_engine/guardrails/__init__.py +0 -0
- idun_agent_engine/guardrails/base.py +24 -0
- idun_agent_engine/guardrails/guardrails_hub/guardrails_hub.py +101 -0
- idun_agent_engine/guardrails/guardrails_hub/utils.py +1 -0
- idun_agent_engine/mcp/__init__.py +5 -0
- idun_agent_engine/mcp/helpers.py +97 -0
- idun_agent_engine/mcp/registry.py +109 -0
- idun_agent_engine/observability/__init__.py +6 -2
- idun_agent_engine/observability/base.py +73 -12
- idun_agent_engine/observability/gcp_logging/__init__.py +0 -0
- idun_agent_engine/observability/gcp_logging/gcp_logging_handler.py +52 -0
- idun_agent_engine/observability/gcp_trace/__init__.py +0 -0
- idun_agent_engine/observability/gcp_trace/gcp_trace_handler.py +116 -0
- idun_agent_engine/observability/langfuse/langfuse_handler.py +17 -10
- idun_agent_engine/server/dependencies.py +30 -1
- idun_agent_engine/server/lifespan.py +83 -16
- idun_agent_engine/server/routers/agent.py +128 -8
- idun_agent_engine/server/routers/agui.py +47 -0
- idun_agent_engine/server/routers/base.py +55 -1
- idun_agent_engine/templates/__init__.py +1 -0
- idun_agent_engine/templates/correction.py +65 -0
- idun_agent_engine/templates/deep_research.py +40 -0
- idun_agent_engine/templates/translation.py +70 -0
- {idun_agent_engine-0.2.6.dist-info → idun_agent_engine-0.3.0.dist-info}/METADATA +65 -11
- idun_agent_engine-0.3.0.dist-info/RECORD +60 -0
- {idun_agent_engine-0.2.6.dist-info → idun_agent_engine-0.3.0.dist-info}/WHEEL +1 -1
- idun_platform_cli/groups/agent/package.py +3 -3
- idun_platform_cli/groups/agent/serve.py +8 -5
- idun_agent_engine/cli/__init__.py +0 -16
- idun_agent_engine-0.2.6.dist-info/RECORD +0 -43
- {idun_agent_engine-0.2.6.dist-info → idun_agent_engine-0.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -7,6 +7,7 @@ This approach ensures type safety, validation, and consistency with the rest of
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
+
from idun_agent_schema.engine.guardrails import Guardrails as GuardrailsV1
|
|
10
11
|
import yaml
|
|
11
12
|
from idun_agent_schema.engine.agent_framework import AgentFramework
|
|
12
13
|
from idun_agent_schema.engine.haystack import HaystackAgentConfig
|
|
@@ -14,8 +15,12 @@ from idun_agent_schema.engine.langgraph import (
|
|
|
14
15
|
LangGraphAgentConfig,
|
|
15
16
|
SqliteCheckpointConfig,
|
|
16
17
|
)
|
|
17
|
-
|
|
18
|
+
from idun_agent_schema.engine.adk import AdkAgentConfig
|
|
19
|
+
from idun_agent_schema.engine.mcp_server import MCPServer
|
|
20
|
+
from idun_agent_schema.engine.observability_v2 import ObservabilityConfig
|
|
21
|
+
from idun_agent_schema.engine.guardrails_v2 import GuardrailsV2 as Guardrails
|
|
18
22
|
from idun_agent_engine.server.server_config import ServerAPIConfig
|
|
23
|
+
from yaml import YAMLError
|
|
19
24
|
|
|
20
25
|
from ..agent.base import BaseAgent
|
|
21
26
|
from .engine_config import AgentConfig, EngineConfig, ServerConfig
|
|
@@ -44,7 +49,10 @@ class ConfigBuilder:
|
|
|
44
49
|
"""Initialize a new configuration builder with default values."""
|
|
45
50
|
self._server_config = ServerConfig()
|
|
46
51
|
self._agent_config: AgentConfig | None = None
|
|
47
|
-
|
|
52
|
+
# TODO: add mcp_servers config
|
|
53
|
+
self._mcp_servers: list[MCPServer] | None = None
|
|
54
|
+
self._observability: list[ObservabilityConfig] | None = None
|
|
55
|
+
self._guardrails: Guardrails | None = None
|
|
48
56
|
def with_api_port(self, port: int) -> "ConfigBuilder":
|
|
49
57
|
"""Set the API port for the server.
|
|
50
58
|
|
|
@@ -90,14 +98,54 @@ class ConfigBuilder:
|
|
|
90
98
|
|
|
91
99
|
headers = {"auth": f"Bearer {agent_api_key}"}
|
|
92
100
|
try:
|
|
93
|
-
|
|
101
|
+
print(f"Fetching config from {url + '/api/v1/agents/config'}")
|
|
102
|
+
response = requests.get(url=url + "/api/v1/agents/config", headers=headers)
|
|
94
103
|
if response.status_code != 200:
|
|
95
104
|
raise ValueError(
|
|
96
105
|
f"Error sending retrieving config from url. response : {response.json()}"
|
|
97
106
|
)
|
|
98
107
|
yaml_config = yaml.safe_load(response.text)
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
try:
|
|
109
|
+
self._server_config = yaml_config.get("engine_config", {}).get("server")
|
|
110
|
+
except Exception as e:
|
|
111
|
+
raise YAMLError(
|
|
112
|
+
f"Failed to parse yaml file for ServerConfig: {e}"
|
|
113
|
+
) from e
|
|
114
|
+
try:
|
|
115
|
+
self._agent_config = yaml_config.get("engine_config", {}).get("agent")
|
|
116
|
+
except Exception as e:
|
|
117
|
+
raise YAMLError(
|
|
118
|
+
f"Failed to parse yaml file for Engine config: {e}"
|
|
119
|
+
) from e
|
|
120
|
+
try:
|
|
121
|
+
guardrails = yaml_config.get("engine_config", {}).get("guardrails", "")
|
|
122
|
+
if not guardrails:
|
|
123
|
+
# self._guardrails = Guardrails(enabled=False)
|
|
124
|
+
self._guardrails = None
|
|
125
|
+
except Exception as e:
|
|
126
|
+
raise YAMLError(f"Failed to parse yaml file for Guardrails: {e}") from e
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
observability_list = yaml_config.get("engine_config", {}).get("observability")
|
|
130
|
+
if observability_list:
|
|
131
|
+
self._observability = [
|
|
132
|
+
ObservabilityConfig.model_validate(obs) for obs in observability_list
|
|
133
|
+
]
|
|
134
|
+
else:
|
|
135
|
+
self._observability = None
|
|
136
|
+
except Exception as e:
|
|
137
|
+
raise YAMLError(f"Failed to parse yaml file for Observability: {e}") from e
|
|
138
|
+
# try:
|
|
139
|
+
# mcp_servers_list = yaml_config.get("engine_config", {}).get("mcp_servers") or yaml_config.get("engine_config", {}).get("mcpServers") # TODO to fix camelcase issues
|
|
140
|
+
# if mcp_servers_list:
|
|
141
|
+
# self._mcp_servers = [
|
|
142
|
+
# MCPServer.model_validate(server) for server in mcp_servers_list
|
|
143
|
+
# ]
|
|
144
|
+
# else:
|
|
145
|
+
# self._mcp_servers = None
|
|
146
|
+
# except Exception as e:
|
|
147
|
+
# raise YAMLError(f"Failed to parse yaml file for MCP Servers: {e}") from e
|
|
148
|
+
|
|
101
149
|
return self
|
|
102
150
|
|
|
103
151
|
except Exception as e:
|
|
@@ -139,9 +187,13 @@ class ConfigBuilder:
|
|
|
139
187
|
langgraph_config = LangGraphAgentConfig.model_validate(agent_config_dict)
|
|
140
188
|
|
|
141
189
|
# Create the agent config (store as strongly-typed model, not dict)
|
|
142
|
-
self._agent_config = AgentConfig(
|
|
190
|
+
self._agent_config = AgentConfig(
|
|
191
|
+
type=AgentFramework.LANGGRAPH, config=langgraph_config
|
|
192
|
+
)
|
|
143
193
|
return self
|
|
144
194
|
|
|
195
|
+
# TODO: remove unused fns
|
|
196
|
+
|
|
145
197
|
def with_custom_agent(
|
|
146
198
|
self, agent_type: str, config: dict[str, Any]
|
|
147
199
|
) -> "ConfigBuilder":
|
|
@@ -160,12 +212,14 @@ class ConfigBuilder:
|
|
|
160
212
|
"""
|
|
161
213
|
if agent_type == AgentFramework.LANGGRAPH:
|
|
162
214
|
self._agent_config = AgentConfig(
|
|
163
|
-
type=AgentFramework.LANGGRAPH,
|
|
215
|
+
type=AgentFramework.LANGGRAPH,
|
|
216
|
+
config=LangGraphAgentConfig.model_validate(config),
|
|
164
217
|
)
|
|
165
218
|
|
|
166
219
|
elif agent_type == AgentFramework.HAYSTACK:
|
|
167
220
|
self._agent_config = AgentConfig(
|
|
168
|
-
type=AgentFramework.HAYSTACK,
|
|
221
|
+
type=AgentFramework.HAYSTACK,
|
|
222
|
+
config=HaystackAgentConfig.model_validate(config),
|
|
169
223
|
)
|
|
170
224
|
else:
|
|
171
225
|
raise ValueError(f"Unsupported agent type: {agent_type}")
|
|
@@ -186,7 +240,13 @@ class ConfigBuilder:
|
|
|
186
240
|
)
|
|
187
241
|
|
|
188
242
|
# Create and validate the complete configuration
|
|
189
|
-
return EngineConfig(
|
|
243
|
+
return EngineConfig(
|
|
244
|
+
server=self._server_config,
|
|
245
|
+
agent=self._agent_config,
|
|
246
|
+
guardrails=self._guardrails,
|
|
247
|
+
observability=self._observability,
|
|
248
|
+
mcp_servers=self._mcp_servers,
|
|
249
|
+
)
|
|
190
250
|
|
|
191
251
|
def build_dict(self) -> dict[str, Any]:
|
|
192
252
|
"""Build and return the configuration as a dictionary.
|
|
@@ -209,7 +269,9 @@ class ConfigBuilder:
|
|
|
209
269
|
with open(file_path, "w") as f:
|
|
210
270
|
yaml.dump(config, f, default_flow_style=False, indent=2)
|
|
211
271
|
|
|
212
|
-
async def build_and_initialize_agent(
|
|
272
|
+
async def build_and_initialize_agent(
|
|
273
|
+
self, mcp_registry: Any | None = None
|
|
274
|
+
) -> BaseAgent:
|
|
213
275
|
"""Build configuration and initialize the agent in one step.
|
|
214
276
|
|
|
215
277
|
Returns:
|
|
@@ -219,14 +281,19 @@ class ConfigBuilder:
|
|
|
219
281
|
ValueError: If agent type is unsupported or configuration is invalid
|
|
220
282
|
"""
|
|
221
283
|
engine_config = self.build()
|
|
222
|
-
return await self.initialize_agent_from_config(
|
|
284
|
+
return await self.initialize_agent_from_config(
|
|
285
|
+
engine_config, mcp_registry=mcp_registry
|
|
286
|
+
)
|
|
223
287
|
|
|
224
288
|
@staticmethod
|
|
225
|
-
async def initialize_agent_from_config(
|
|
289
|
+
async def initialize_agent_from_config(
|
|
290
|
+
engine_config: EngineConfig, mcp_registry: Any | None = None
|
|
291
|
+
) -> BaseAgent:
|
|
226
292
|
"""Initialize an agent instance from a validated EngineConfig.
|
|
227
293
|
|
|
228
294
|
Args:
|
|
229
295
|
engine_config: Validated configuration object
|
|
296
|
+
mcp_registry: Optional MCP registry client.
|
|
230
297
|
|
|
231
298
|
Returns:
|
|
232
299
|
BaseAgent: Initialized agent instance
|
|
@@ -235,14 +302,15 @@ class ConfigBuilder:
|
|
|
235
302
|
ValueError: If agent type is unsupported
|
|
236
303
|
"""
|
|
237
304
|
agent_config_obj = engine_config.agent.config
|
|
238
|
-
print("CONFIG:", agent_config_obj)
|
|
239
305
|
agent_type = engine_config.agent.type
|
|
240
|
-
|
|
306
|
+
observability_config = engine_config.observability
|
|
307
|
+
# mcp_servers = engine_config.mcp_servers
|
|
241
308
|
# Initialize the appropriate agent
|
|
242
309
|
agent_instance = None
|
|
243
310
|
if agent_type == AgentFramework.LANGGRAPH:
|
|
244
311
|
from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
|
|
245
|
-
|
|
312
|
+
import os
|
|
313
|
+
print("Current directory: ", os.getcwd()) # TODO remove
|
|
246
314
|
try:
|
|
247
315
|
validated_config = LangGraphAgentConfig.model_validate(agent_config_obj)
|
|
248
316
|
|
|
@@ -253,6 +321,91 @@ class ConfigBuilder:
|
|
|
253
321
|
|
|
254
322
|
agent_instance = LanggraphAgent()
|
|
255
323
|
|
|
324
|
+
elif agent_type == AgentFramework.TRANSLATION_AGENT:
|
|
325
|
+
from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
|
|
326
|
+
from idun_agent_schema.engine.templates import TranslationAgentConfig
|
|
327
|
+
import os
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
translation_config = TranslationAgentConfig.model_validate(
|
|
331
|
+
agent_config_obj
|
|
332
|
+
)
|
|
333
|
+
except Exception as e:
|
|
334
|
+
raise ValueError(
|
|
335
|
+
f"Cannot validate into a TranslationAgentConfig model. Got {agent_config_obj}"
|
|
336
|
+
) from e
|
|
337
|
+
|
|
338
|
+
# Configure environment for the template
|
|
339
|
+
os.environ["TRANSLATION_MODEL"] = translation_config.model_name
|
|
340
|
+
os.environ["TRANSLATION_SOURCE_LANG"] = translation_config.source_lang
|
|
341
|
+
os.environ["TRANSLATION_TARGET_LANG"] = translation_config.target_lang
|
|
342
|
+
|
|
343
|
+
# Create LangGraph config for the template
|
|
344
|
+
validated_config = LangGraphAgentConfig(
|
|
345
|
+
name=translation_config.name,
|
|
346
|
+
graph_definition="idun_agent_engine.templates.translation:graph",
|
|
347
|
+
input_schema_definition=translation_config.input_schema_definition,
|
|
348
|
+
output_schema_definition=translation_config.output_schema_definition,
|
|
349
|
+
observability=translation_config.observability,
|
|
350
|
+
checkpointer=translation_config.checkpointer,
|
|
351
|
+
)
|
|
352
|
+
agent_instance = LanggraphAgent()
|
|
353
|
+
|
|
354
|
+
elif agent_type == AgentFramework.CORRECTION_AGENT:
|
|
355
|
+
from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
|
|
356
|
+
from idun_agent_schema.engine.templates import CorrectionAgentConfig
|
|
357
|
+
import os
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
correction_config = CorrectionAgentConfig.model_validate(
|
|
361
|
+
agent_config_obj
|
|
362
|
+
)
|
|
363
|
+
except Exception as e:
|
|
364
|
+
raise ValueError(
|
|
365
|
+
f"Cannot validate into a CorrectionAgentConfig model. Got {agent_config_obj}"
|
|
366
|
+
) from e
|
|
367
|
+
|
|
368
|
+
os.environ["CORRECTION_MODEL"] = correction_config.model_name
|
|
369
|
+
os.environ["CORRECTION_LANGUAGE"] = correction_config.language
|
|
370
|
+
|
|
371
|
+
validated_config = LangGraphAgentConfig(
|
|
372
|
+
name=correction_config.name,
|
|
373
|
+
graph_definition="idun_agent_engine.templates.correction:graph",
|
|
374
|
+
input_schema_definition=correction_config.input_schema_definition,
|
|
375
|
+
output_schema_definition=correction_config.output_schema_definition,
|
|
376
|
+
observability=correction_config.observability,
|
|
377
|
+
checkpointer=correction_config.checkpointer,
|
|
378
|
+
)
|
|
379
|
+
agent_instance = LanggraphAgent()
|
|
380
|
+
|
|
381
|
+
elif agent_type == AgentFramework.DEEP_RESEARCH_AGENT:
|
|
382
|
+
from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
|
|
383
|
+
from idun_agent_schema.engine.templates import DeepResearchAgentConfig
|
|
384
|
+
import os
|
|
385
|
+
|
|
386
|
+
try:
|
|
387
|
+
deep_research_config = DeepResearchAgentConfig.model_validate(
|
|
388
|
+
agent_config_obj
|
|
389
|
+
)
|
|
390
|
+
except Exception as e:
|
|
391
|
+
raise ValueError(
|
|
392
|
+
f"Cannot validate into a DeepResearchAgentConfig model. Got {agent_config_obj}"
|
|
393
|
+
) from e
|
|
394
|
+
|
|
395
|
+
os.environ["DEEP_RESEARCH_MODEL"] = deep_research_config.model_name
|
|
396
|
+
os.environ["DEEP_RESEARCH_PROMPT"] = deep_research_config.system_prompt
|
|
397
|
+
os.environ["TAVILY_API_KEY"] = deep_research_config.tavily_api_key
|
|
398
|
+
|
|
399
|
+
validated_config = LangGraphAgentConfig(
|
|
400
|
+
name=deep_research_config.name,
|
|
401
|
+
graph_definition="idun_agent_engine.templates.deep_research:graph",
|
|
402
|
+
input_schema_definition=deep_research_config.input_schema_definition,
|
|
403
|
+
output_schema_definition=deep_research_config.output_schema_definition,
|
|
404
|
+
observability=deep_research_config.observability,
|
|
405
|
+
checkpointer=deep_research_config.checkpointer,
|
|
406
|
+
)
|
|
407
|
+
agent_instance = LanggraphAgent()
|
|
408
|
+
|
|
256
409
|
elif agent_type == AgentFramework.HAYSTACK:
|
|
257
410
|
from idun_agent_engine.agent.haystack.haystack import HaystackAgent
|
|
258
411
|
|
|
@@ -264,11 +417,21 @@ class ConfigBuilder:
|
|
|
264
417
|
f"Cannot validate into a HaystackAgentConfig model. Got {agent_config_obj}"
|
|
265
418
|
) from e
|
|
266
419
|
agent_instance = HaystackAgent()
|
|
420
|
+
elif agent_type == AgentFramework.ADK:
|
|
421
|
+
from idun_agent_engine.agent.adk.adk import AdkAgent
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
validated_config = AdkAgentConfig.model_validate(agent_config_obj)
|
|
425
|
+
except Exception as e:
|
|
426
|
+
raise ValueError(f"Cannot validate into a AdkAgentConfig model. Got {agent_config_obj}") from e
|
|
427
|
+
agent_instance = AdkAgent()
|
|
267
428
|
else:
|
|
268
429
|
raise ValueError(f"Unsupported agent type: {agent_type}")
|
|
269
430
|
|
|
270
431
|
# Initialize the agent with its configuration
|
|
271
|
-
await agent_instance.initialize(
|
|
432
|
+
await agent_instance.initialize(
|
|
433
|
+
validated_config, observability_config#, mcp_registry=mcp_registry
|
|
434
|
+
) # type: ignore[arg-type]
|
|
272
435
|
return agent_instance
|
|
273
436
|
|
|
274
437
|
@staticmethod
|
|
@@ -284,7 +447,13 @@ class ConfigBuilder:
|
|
|
284
447
|
Raises:
|
|
285
448
|
ValueError: If agent type is unsupported
|
|
286
449
|
"""
|
|
287
|
-
if
|
|
450
|
+
if (
|
|
451
|
+
agent_type == "langgraph"
|
|
452
|
+
or agent_type == AgentFramework.LANGGRAPH
|
|
453
|
+
or agent_type == AgentFramework.TRANSLATION_AGENT
|
|
454
|
+
or agent_type == AgentFramework.CORRECTION_AGENT
|
|
455
|
+
or agent_type == AgentFramework.DEEP_RESEARCH_AGENT
|
|
456
|
+
):
|
|
288
457
|
from ..agent.langgraph.langgraph import LanggraphAgent
|
|
289
458
|
|
|
290
459
|
return LanggraphAgent
|
|
@@ -311,10 +480,21 @@ class ConfigBuilder:
|
|
|
311
480
|
if agent_type == "langgraph":
|
|
312
481
|
validated_config = LangGraphAgentConfig.model_validate(config)
|
|
313
482
|
return validated_config.model_dump()
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
483
|
+
elif agent_type == AgentFramework.TRANSLATION_AGENT:
|
|
484
|
+
from idun_agent_schema.engine.templates import TranslationAgentConfig
|
|
485
|
+
|
|
486
|
+
validated_config = TranslationAgentConfig.model_validate(config)
|
|
487
|
+
return validated_config.model_dump()
|
|
488
|
+
elif agent_type == AgentFramework.CORRECTION_AGENT:
|
|
489
|
+
from idun_agent_schema.engine.templates import CorrectionAgentConfig
|
|
490
|
+
|
|
491
|
+
validated_config = CorrectionAgentConfig.model_validate(config)
|
|
492
|
+
return validated_config.model_dump()
|
|
493
|
+
elif agent_type == AgentFramework.DEEP_RESEARCH_AGENT:
|
|
494
|
+
from idun_agent_schema.engine.templates import DeepResearchAgentConfig
|
|
495
|
+
|
|
496
|
+
validated_config = DeepResearchAgentConfig.model_validate(config)
|
|
497
|
+
return validated_config.model_dump()
|
|
318
498
|
else:
|
|
319
499
|
raise ValueError(f"Unsupported agent type: {agent_type}")
|
|
320
500
|
|
|
@@ -345,17 +525,21 @@ class ConfigBuilder:
|
|
|
345
525
|
@staticmethod
|
|
346
526
|
async def load_and_initialize_agent(
|
|
347
527
|
config_path: str = "config.yaml",
|
|
528
|
+
mcp_registry: Any | None = None,
|
|
348
529
|
) -> tuple[EngineConfig, BaseAgent]:
|
|
349
530
|
"""Load configuration and initialize agent in one step.
|
|
350
531
|
|
|
351
532
|
Args:
|
|
352
533
|
config_path: Path to the configuration YAML file
|
|
534
|
+
mcp_registry: Optional MCP registry client.
|
|
353
535
|
|
|
354
536
|
Returns:
|
|
355
537
|
tuple[EngineConfig, BaseAgent]: Configuration and initialized agent
|
|
356
538
|
"""
|
|
357
539
|
engine_config = ConfigBuilder.load_from_file(config_path)
|
|
358
|
-
agent = await ConfigBuilder.initialize_agent_from_config(
|
|
540
|
+
agent = await ConfigBuilder.initialize_agent_from_config(
|
|
541
|
+
engine_config, mcp_registry=mcp_registry
|
|
542
|
+
)
|
|
359
543
|
return engine_config, agent
|
|
360
544
|
|
|
361
545
|
@staticmethod
|
|
@@ -364,6 +548,7 @@ class ConfigBuilder:
|
|
|
364
548
|
config_dict: dict[str, Any] | None = None,
|
|
365
549
|
engine_config: EngineConfig | None = None,
|
|
366
550
|
) -> EngineConfig:
|
|
551
|
+
print(config_dict)
|
|
367
552
|
"""Umbrella function to resolve configuration from various sources.
|
|
368
553
|
|
|
369
554
|
This function handles all the different ways configuration can be provided
|
|
@@ -424,7 +609,9 @@ class ConfigBuilder:
|
|
|
424
609
|
builder = cls()
|
|
425
610
|
builder._server_config = engine_config.server
|
|
426
611
|
builder._agent_config = engine_config.agent
|
|
427
|
-
|
|
612
|
+
builder._guardrails = engine_config.guardrails
|
|
613
|
+
builder._observability = engine_config.observability
|
|
614
|
+
builder._mcp_servers = engine_config.mcp_servers
|
|
428
615
|
return builder
|
|
429
616
|
|
|
430
617
|
@classmethod
|
|
@@ -453,4 +640,8 @@ class ConfigBuilder:
|
|
|
453
640
|
builder = cls()
|
|
454
641
|
builder._server_config = engine_config.server
|
|
455
642
|
builder._agent_config = engine_config.agent
|
|
643
|
+
builder._guardrails = engine_config.guardrails
|
|
644
|
+
builder._observability = engine_config.observability
|
|
645
|
+
builder._mcp_servers = engine_config.mcp_servers
|
|
646
|
+
|
|
456
647
|
return builder
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"""Compatibility re-exports for Engine configuration models."""
|
|
2
2
|
|
|
3
|
-
from idun_agent_schema.engine.agent import BaseAgentConfig # noqa: F401
|
|
4
3
|
from idun_agent_schema.engine.agent import ( # noqa: F401
|
|
5
4
|
AgentConfig,
|
|
5
|
+
BaseAgentConfig, # noqa: F401
|
|
6
6
|
)
|
|
7
|
-
|
|
8
7
|
from idun_agent_schema.engine.engine import ( # noqa: F401
|
|
9
8
|
EngineConfig,
|
|
10
9
|
)
|
|
@@ -41,7 +41,7 @@ def run_server(
|
|
|
41
41
|
# Run in production mode
|
|
42
42
|
run_server(app, workers=4)
|
|
43
43
|
"""
|
|
44
|
-
print(f"🌐 Starting Idun Agent Engine server on http://{host}:{port}")
|
|
44
|
+
print(f"🌐 Starting Idun Agent Engine server on http://{host}:{port}...")
|
|
45
45
|
print(f"📚 API documentation available at http://{host}:{port}/docs")
|
|
46
46
|
|
|
47
47
|
if reload and workers:
|
|
@@ -50,13 +50,12 @@ def run_server(
|
|
|
50
50
|
)
|
|
51
51
|
reload = False
|
|
52
52
|
|
|
53
|
+
print("Config: ", app.state.engine_config)
|
|
53
54
|
uvicorn.run(
|
|
54
55
|
app,
|
|
55
56
|
host=host,
|
|
56
57
|
port=port,
|
|
57
|
-
# reload=reload,
|
|
58
58
|
log_level=log_level,
|
|
59
|
-
# workers=workers
|
|
60
59
|
)
|
|
61
60
|
|
|
62
61
|
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from idun_agent_schema.engine.guardrails import Guardrail
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseGuardrail(ABC):
|
|
8
|
+
"""Base class for different guardrail providers."""
|
|
9
|
+
|
|
10
|
+
# TODO: output
|
|
11
|
+
|
|
12
|
+
def __init__(self, config: Guardrail) -> None:
|
|
13
|
+
if not isinstance(config, Guardrail):
|
|
14
|
+
raise TypeError(
|
|
15
|
+
f"The Guardrail must be a `Guardrail` schema type, received instead: {type(config)}"
|
|
16
|
+
)
|
|
17
|
+
self._guardrail_config = config
|
|
18
|
+
# config for the specific guardrails type. currently, can only be guardrails_hub config
|
|
19
|
+
self._instance_config: dict[str, Any] = None
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def validate(self, input: str) -> bool:
|
|
23
|
+
"""Used for validating user input, or LLM output."""
|
|
24
|
+
pass
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Guardrails."""
|
|
2
|
+
|
|
3
|
+
from guardrails import Guard
|
|
4
|
+
from idun_agent_schema.engine.guardrails import Guardrail as GuardrailSchema
|
|
5
|
+
from idun_agent_schema.engine.guardrails_type import (
|
|
6
|
+
GuardrailType,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from ..base import BaseGuardrail
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_guard_instance(name: str) -> Guard:
|
|
13
|
+
"""Returns a map of guard type -> guard instance."""
|
|
14
|
+
if name == "BAN_LIST":
|
|
15
|
+
from guardrails.hub import BanList
|
|
16
|
+
|
|
17
|
+
return BanList
|
|
18
|
+
|
|
19
|
+
elif name == "NSFW":
|
|
20
|
+
from guardrails.hub import NSFWText
|
|
21
|
+
|
|
22
|
+
return NSFWText
|
|
23
|
+
|
|
24
|
+
elif name == "COMPETITOR_CHECK":
|
|
25
|
+
from guardrails.hub import CompetitorCheck
|
|
26
|
+
|
|
27
|
+
return CompetitorCheck
|
|
28
|
+
|
|
29
|
+
else:
|
|
30
|
+
raise ValueError(f"Guard {name} not found.")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class GuardrailsHubGuard(BaseGuardrail):
|
|
34
|
+
"""Class for managing guardrails from `guardrailsai`'s hub."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, config: GuardrailSchema, position: str) -> None:
|
|
37
|
+
super().__init__(config)
|
|
38
|
+
|
|
39
|
+
self._guard_type = self._guardrail_config.type
|
|
40
|
+
self._guard_config = self._guardrail_config.config
|
|
41
|
+
|
|
42
|
+
if self._guard_type == GuardrailType.GUARDRAILS_HUB:
|
|
43
|
+
self._guard_url = self._guardrail_config.config["guard_url"]
|
|
44
|
+
|
|
45
|
+
self.reject_message: str = self._guard_config["reject_message"]
|
|
46
|
+
self._install_model()
|
|
47
|
+
self._guard: Guard | None = self.setup_guard()
|
|
48
|
+
self.position: str = position
|
|
49
|
+
|
|
50
|
+
def _install_model(self) -> None:
|
|
51
|
+
import subprocess
|
|
52
|
+
|
|
53
|
+
from guardrails import install
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
api_key = self._guardrail_config.config["api_key"]
|
|
57
|
+
subprocess.run(
|
|
58
|
+
[
|
|
59
|
+
"guardrails",
|
|
60
|
+
"configure",
|
|
61
|
+
"--token",
|
|
62
|
+
api_key,
|
|
63
|
+
"--disable-remote-inferencing", # TODO: maybe provide this as feat
|
|
64
|
+
"--disable-metrics",
|
|
65
|
+
],
|
|
66
|
+
check=True,
|
|
67
|
+
)
|
|
68
|
+
print(f"Installing model: {self._guard_url}..")
|
|
69
|
+
install(self._guard_url, quiet=True, install_local_models=True)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
raise OSError(f"Cannot install model {self._guard_url}: {e}") from e
|
|
72
|
+
|
|
73
|
+
def setup_guard(self) -> Guard | None:
|
|
74
|
+
"""Installs and configures the guard based on its yaml config."""
|
|
75
|
+
if self._guard_type == GuardrailType.GUARDRAILS_HUB:
|
|
76
|
+
self._install_model()
|
|
77
|
+
guard_name = self._guardrail_config.config.get("guard")
|
|
78
|
+
guard = get_guard_instance(guard_name)
|
|
79
|
+
if guard is None:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"Guard: {self.guard_type} is not yet supported, or does not exist."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
guard_instance_params = self._guardrail_config.config.get(
|
|
85
|
+
"guard_config", {}
|
|
86
|
+
)
|
|
87
|
+
guard_instance = guard(**guard_instance_params)
|
|
88
|
+
for param, value in self._guardrail_config.config["guard_config"].items():
|
|
89
|
+
setattr(guard, param, value)
|
|
90
|
+
return guard_instance
|
|
91
|
+
elif self._guard_type == GuardrailType.CUSTOM_LLM:
|
|
92
|
+
raise NotImplementedError("Support for CUSTOM_LLM not yet provided.")
|
|
93
|
+
|
|
94
|
+
def validate(self, input: str) -> bool:
|
|
95
|
+
"""TODO."""
|
|
96
|
+
main_guard = Guard().use(self._guard)
|
|
97
|
+
try:
|
|
98
|
+
main_guard.validate(input)
|
|
99
|
+
return True
|
|
100
|
+
except Exception:
|
|
101
|
+
return False
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Utils module."""
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
import yaml
|
|
4
|
+
import requests
|
|
5
|
+
import os
|
|
6
|
+
from idun_agent_engine.mcp.registry import MCPClientRegistry
|
|
7
|
+
from idun_agent_schema.engine.mcp_server import MCPServer
|
|
8
|
+
|
|
9
|
+
def _get_toolsets_from_data(config_data: dict[str, Any]) -> list[Any]:
|
|
10
|
+
"""Internal helper to extract toolsets from config dictionary."""
|
|
11
|
+
# Handle both snake_case and camelCase for mcp_servers
|
|
12
|
+
# Note: logic in ConfigBuilder suggests looking inside 'engine_config' if present,
|
|
13
|
+
# but this helper expects the dictionary containing 'mcp_servers' directly
|
|
14
|
+
# or performs the search itself.
|
|
15
|
+
|
|
16
|
+
mcp_configs_data = config_data.get("mcp_servers") or config_data.get("mcpServers")
|
|
17
|
+
|
|
18
|
+
if not mcp_configs_data:
|
|
19
|
+
return []
|
|
20
|
+
|
|
21
|
+
mcp_configs = [MCPServer.model_validate(c) for c in mcp_configs_data]
|
|
22
|
+
registry = MCPClientRegistry(mcp_configs)
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
return registry.get_adk_toolsets()
|
|
26
|
+
except ImportError:
|
|
27
|
+
raise
|
|
28
|
+
|
|
29
|
+
def get_adk_tools_from_file(config_path: str | Path) -> list[Any]:
|
|
30
|
+
"""
|
|
31
|
+
Loads MCP configurations from a YAML file and returns a list of ADK toolsets.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
config_path: Path to the configuration YAML file.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
List of initialized ADK McpToolset instances.
|
|
38
|
+
"""
|
|
39
|
+
path = Path(config_path)
|
|
40
|
+
if not path.exists():
|
|
41
|
+
raise FileNotFoundError(f"Configuration file not found at {path}")
|
|
42
|
+
|
|
43
|
+
with open(path) as f:
|
|
44
|
+
config_data = yaml.safe_load(f)
|
|
45
|
+
|
|
46
|
+
# Check if wrapped in engine_config (common pattern in idun)
|
|
47
|
+
if "engine_config" in config_data:
|
|
48
|
+
config_data = config_data["engine_config"]
|
|
49
|
+
|
|
50
|
+
return _get_toolsets_from_data(config_data)
|
|
51
|
+
|
|
52
|
+
def get_adk_tools_from_api() -> list[Any]:
|
|
53
|
+
"""
|
|
54
|
+
Fetches configuration from the Idun Manager API and returns a list of ADK toolsets.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
agent_api_key: The API key for authentication.
|
|
58
|
+
manager_url: The base URL of the Idun Manager (e.g. http://localhost:8000).
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List of initialized ADK McpToolset instances.
|
|
62
|
+
"""
|
|
63
|
+
api_key = os.environ.get("IDUN_AGENT_API_KEY")
|
|
64
|
+
manager_host = os.environ.get("IDUN_MANAGER_HOST")
|
|
65
|
+
headers = {"auth": f"Bearer {api_key}"}
|
|
66
|
+
url = f"{manager_host.rstrip('/')}/api/v1/agents/config"
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
response = requests.get(url=url, headers=headers)
|
|
70
|
+
response.raise_for_status()
|
|
71
|
+
|
|
72
|
+
config_data = yaml.safe_load(response.text)
|
|
73
|
+
|
|
74
|
+
# Config from API is typically wrapped in engine_config
|
|
75
|
+
if "engine_config" in config_data:
|
|
76
|
+
config_data = config_data["engine_config"]
|
|
77
|
+
|
|
78
|
+
return _get_toolsets_from_data(config_data)
|
|
79
|
+
|
|
80
|
+
except requests.RequestException as e:
|
|
81
|
+
raise ValueError(f"Failed to fetch config from API: {e}") from e
|
|
82
|
+
except yaml.YAMLError as e:
|
|
83
|
+
raise ValueError(f"Failed to parse config YAML: {e}") from e
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_adk_tools() -> list[Any]:
|
|
87
|
+
"""
|
|
88
|
+
Fetches configuration from the Idun Manager API and returns a list of ADK toolsets.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
agent_api_key: The API key for authentication.
|
|
92
|
+
manager_url: The base URL of the Idun Manager (e.g. http://localhost:8000).
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
List of initialized ADK McpToolset instances.
|
|
96
|
+
"""
|
|
97
|
+
return get_adk_tools_from_api()
|