idun-agent-engine 0.2.7__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. idun_agent_engine/_version.py +1 -1
  2. idun_agent_engine/agent/adk/__init__.py +5 -0
  3. idun_agent_engine/agent/adk/adk.py +296 -0
  4. idun_agent_engine/agent/base.py +7 -1
  5. idun_agent_engine/agent/haystack/haystack.py +5 -1
  6. idun_agent_engine/agent/langgraph/langgraph.py +158 -55
  7. idun_agent_engine/core/app_factory.py +9 -0
  8. idun_agent_engine/core/config_builder.py +222 -21
  9. idun_agent_engine/core/engine_config.py +1 -2
  10. idun_agent_engine/core/server_runner.py +2 -3
  11. idun_agent_engine/guardrails/__init__.py +0 -0
  12. idun_agent_engine/guardrails/base.py +24 -0
  13. idun_agent_engine/guardrails/guardrails_hub/guardrails_hub.py +101 -0
  14. idun_agent_engine/guardrails/guardrails_hub/utils.py +1 -0
  15. idun_agent_engine/mcp/__init__.py +5 -0
  16. idun_agent_engine/mcp/helpers.py +97 -0
  17. idun_agent_engine/mcp/registry.py +109 -0
  18. idun_agent_engine/observability/__init__.py +6 -2
  19. idun_agent_engine/observability/base.py +73 -12
  20. idun_agent_engine/observability/gcp_logging/__init__.py +0 -0
  21. idun_agent_engine/observability/gcp_logging/gcp_logging_handler.py +52 -0
  22. idun_agent_engine/observability/gcp_trace/__init__.py +0 -0
  23. idun_agent_engine/observability/gcp_trace/gcp_trace_handler.py +116 -0
  24. idun_agent_engine/observability/langfuse/langfuse_handler.py +17 -10
  25. idun_agent_engine/server/dependencies.py +13 -1
  26. idun_agent_engine/server/lifespan.py +80 -16
  27. idun_agent_engine/server/routers/agent.py +135 -27
  28. idun_agent_engine/server/routers/agui.py +47 -0
  29. idun_agent_engine/server/routers/base.py +55 -1
  30. idun_agent_engine/templates/__init__.py +1 -0
  31. idun_agent_engine/templates/correction.py +65 -0
  32. idun_agent_engine/templates/deep_research.py +40 -0
  33. idun_agent_engine/templates/translation.py +70 -0
  34. {idun_agent_engine-0.2.7.dist-info → idun_agent_engine-0.3.1.dist-info}/METADATA +62 -10
  35. idun_agent_engine-0.3.1.dist-info/RECORD +60 -0
  36. {idun_agent_engine-0.2.7.dist-info → idun_agent_engine-0.3.1.dist-info}/WHEEL +1 -1
  37. idun_platform_cli/groups/agent/package.py +3 -3
  38. idun_platform_cli/groups/agent/serve.py +8 -5
  39. idun_agent_engine/cli/__init__.py +0 -16
  40. idun_agent_engine-0.2.7.dist-info/RECORD +0 -43
  41. {idun_agent_engine-0.2.7.dist-info → idun_agent_engine-0.3.1.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,6 +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
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
47
56
 
48
57
  def with_api_port(self, port: int) -> "ConfigBuilder":
49
58
  """Set the API port for the server.
@@ -90,14 +99,59 @@ class ConfigBuilder:
90
99
 
91
100
  headers = {"auth": f"Bearer {agent_api_key}"}
92
101
  try:
93
- response = requests.get(url=url, headers=headers)
102
+ print(f"Fetching config from {url + '/api/v1/agents/config'}")
103
+ response = requests.get(url=url + "/api/v1/agents/config", headers=headers)
94
104
  if response.status_code != 200:
95
105
  raise ValueError(
96
106
  f"Error sending retrieving config from url. response : {response.json()}"
97
107
  )
98
108
  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"]
109
+ try:
110
+ self._server_config = yaml_config.get("engine_config", {}).get("server")
111
+ except Exception as e:
112
+ raise YAMLError(
113
+ f"Failed to parse yaml file for ServerConfig: {e}"
114
+ ) from e
115
+ try:
116
+ self._agent_config = yaml_config.get("engine_config", {}).get("agent")
117
+ except Exception as e:
118
+ raise YAMLError(
119
+ f"Failed to parse yaml file for Engine config: {e}"
120
+ ) from e
121
+ try:
122
+ guardrails = yaml_config.get("engine_config", {}).get("guardrails", "")
123
+ if not guardrails:
124
+ # self._guardrails = Guardrails(enabled=False)
125
+ self._guardrails = None
126
+ except Exception as e:
127
+ raise YAMLError(f"Failed to parse yaml file for Guardrails: {e}") from e
128
+
129
+ try:
130
+ observability_list = yaml_config.get("engine_config", {}).get(
131
+ "observability"
132
+ )
133
+ if observability_list:
134
+ self._observability = [
135
+ ObservabilityConfig.model_validate(obs)
136
+ for obs in observability_list
137
+ ]
138
+ else:
139
+ self._observability = None
140
+ except Exception as e:
141
+ raise YAMLError(
142
+ f"Failed to parse yaml file for Observability: {e}"
143
+ ) from e
144
+ # try:
145
+ # mcp_servers_list = yaml_config.get("engine_config", {}).get("mcp_servers") or yaml_config.get("engine_config", {}).get("mcpServers") # TODO to fix camelcase issues
146
+ # if mcp_servers_list:
147
+ # self._mcp_servers = [
148
+ # MCPServer.model_validate(server) for server in mcp_servers_list
149
+ # ]
150
+ # else:
151
+ # self._mcp_servers = None
152
+ # except Exception as e:
153
+ # raise YAMLError(f"Failed to parse yaml file for MCP Servers: {e}") from e
154
+
101
155
  return self
102
156
 
103
157
  except Exception as e:
@@ -139,9 +193,13 @@ class ConfigBuilder:
139
193
  langgraph_config = LangGraphAgentConfig.model_validate(agent_config_dict)
140
194
 
141
195
  # Create the agent config (store as strongly-typed model, not dict)
142
- self._agent_config = AgentConfig(type=AgentFramework.LANGGRAPH, config=langgraph_config)
196
+ self._agent_config = AgentConfig(
197
+ type=AgentFramework.LANGGRAPH, config=langgraph_config
198
+ )
143
199
  return self
144
200
 
201
+ # TODO: remove unused fns
202
+
145
203
  def with_custom_agent(
146
204
  self, agent_type: str, config: dict[str, Any]
147
205
  ) -> "ConfigBuilder":
@@ -160,12 +218,14 @@ class ConfigBuilder:
160
218
  """
161
219
  if agent_type == AgentFramework.LANGGRAPH:
162
220
  self._agent_config = AgentConfig(
163
- type=AgentFramework.LANGGRAPH, config=LangGraphAgentConfig.model_validate(config)
221
+ type=AgentFramework.LANGGRAPH,
222
+ config=LangGraphAgentConfig.model_validate(config),
164
223
  )
165
224
 
166
225
  elif agent_type == AgentFramework.HAYSTACK:
167
226
  self._agent_config = AgentConfig(
168
- type=AgentFramework.HAYSTACK, config=HaystackAgentConfig.model_validate(config)
227
+ type=AgentFramework.HAYSTACK,
228
+ config=HaystackAgentConfig.model_validate(config),
169
229
  )
170
230
  else:
171
231
  raise ValueError(f"Unsupported agent type: {agent_type}")
@@ -186,7 +246,13 @@ class ConfigBuilder:
186
246
  )
187
247
 
188
248
  # Create and validate the complete configuration
189
- return EngineConfig(server=self._server_config, agent=self._agent_config)
249
+ return EngineConfig(
250
+ server=self._server_config,
251
+ agent=self._agent_config,
252
+ guardrails=self._guardrails,
253
+ observability=self._observability,
254
+ mcp_servers=self._mcp_servers,
255
+ )
190
256
 
191
257
  def build_dict(self) -> dict[str, Any]:
192
258
  """Build and return the configuration as a dictionary.
@@ -209,7 +275,9 @@ class ConfigBuilder:
209
275
  with open(file_path, "w") as f:
210
276
  yaml.dump(config, f, default_flow_style=False, indent=2)
211
277
 
212
- async def build_and_initialize_agent(self) -> BaseAgent:
278
+ async def build_and_initialize_agent(
279
+ self, mcp_registry: Any | None = None
280
+ ) -> BaseAgent:
213
281
  """Build configuration and initialize the agent in one step.
214
282
 
215
283
  Returns:
@@ -219,14 +287,19 @@ class ConfigBuilder:
219
287
  ValueError: If agent type is unsupported or configuration is invalid
220
288
  """
221
289
  engine_config = self.build()
222
- return await self.initialize_agent_from_config(engine_config)
290
+ return await self.initialize_agent_from_config(
291
+ engine_config, mcp_registry=mcp_registry
292
+ )
223
293
 
224
294
  @staticmethod
225
- async def initialize_agent_from_config(engine_config: EngineConfig) -> BaseAgent:
295
+ async def initialize_agent_from_config(
296
+ engine_config: EngineConfig, mcp_registry: Any | None = None
297
+ ) -> BaseAgent:
226
298
  """Initialize an agent instance from a validated EngineConfig.
227
299
 
228
300
  Args:
229
301
  engine_config: Validated configuration object
302
+ mcp_registry: Optional MCP registry client.
230
303
 
231
304
  Returns:
232
305
  BaseAgent: Initialized agent instance
@@ -235,14 +308,16 @@ class ConfigBuilder:
235
308
  ValueError: If agent type is unsupported
236
309
  """
237
310
  agent_config_obj = engine_config.agent.config
238
- print("CONFIG:", agent_config_obj)
239
311
  agent_type = engine_config.agent.type
240
-
312
+ observability_config = engine_config.observability
313
+ # mcp_servers = engine_config.mcp_servers
241
314
  # Initialize the appropriate agent
242
315
  agent_instance = None
243
316
  if agent_type == AgentFramework.LANGGRAPH:
244
317
  from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
318
+ import os
245
319
 
320
+ print("Current directory: ", os.getcwd()) # TODO remove
246
321
  try:
247
322
  validated_config = LangGraphAgentConfig.model_validate(agent_config_obj)
248
323
 
@@ -253,6 +328,91 @@ class ConfigBuilder:
253
328
 
254
329
  agent_instance = LanggraphAgent()
255
330
 
331
+ elif agent_type == AgentFramework.TRANSLATION_AGENT:
332
+ from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
333
+ from idun_agent_schema.engine.templates import TranslationAgentConfig
334
+ import os
335
+
336
+ try:
337
+ translation_config = TranslationAgentConfig.model_validate(
338
+ agent_config_obj
339
+ )
340
+ except Exception as e:
341
+ raise ValueError(
342
+ f"Cannot validate into a TranslationAgentConfig model. Got {agent_config_obj}"
343
+ ) from e
344
+
345
+ # Configure environment for the template
346
+ os.environ["TRANSLATION_MODEL"] = translation_config.model_name
347
+ os.environ["TRANSLATION_SOURCE_LANG"] = translation_config.source_lang
348
+ os.environ["TRANSLATION_TARGET_LANG"] = translation_config.target_lang
349
+
350
+ # Create LangGraph config for the template
351
+ validated_config = LangGraphAgentConfig(
352
+ name=translation_config.name,
353
+ graph_definition="idun_agent_engine.templates.translation:graph",
354
+ input_schema_definition=translation_config.input_schema_definition,
355
+ output_schema_definition=translation_config.output_schema_definition,
356
+ observability=translation_config.observability,
357
+ checkpointer=translation_config.checkpointer,
358
+ )
359
+ agent_instance = LanggraphAgent()
360
+
361
+ elif agent_type == AgentFramework.CORRECTION_AGENT:
362
+ from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
363
+ from idun_agent_schema.engine.templates import CorrectionAgentConfig
364
+ import os
365
+
366
+ try:
367
+ correction_config = CorrectionAgentConfig.model_validate(
368
+ agent_config_obj
369
+ )
370
+ except Exception as e:
371
+ raise ValueError(
372
+ f"Cannot validate into a CorrectionAgentConfig model. Got {agent_config_obj}"
373
+ ) from e
374
+
375
+ os.environ["CORRECTION_MODEL"] = correction_config.model_name
376
+ os.environ["CORRECTION_LANGUAGE"] = correction_config.language
377
+
378
+ validated_config = LangGraphAgentConfig(
379
+ name=correction_config.name,
380
+ graph_definition="idun_agent_engine.templates.correction:graph",
381
+ input_schema_definition=correction_config.input_schema_definition,
382
+ output_schema_definition=correction_config.output_schema_definition,
383
+ observability=correction_config.observability,
384
+ checkpointer=correction_config.checkpointer,
385
+ )
386
+ agent_instance = LanggraphAgent()
387
+
388
+ elif agent_type == AgentFramework.DEEP_RESEARCH_AGENT:
389
+ from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
390
+ from idun_agent_schema.engine.templates import DeepResearchAgentConfig
391
+ import os
392
+
393
+ try:
394
+ deep_research_config = DeepResearchAgentConfig.model_validate(
395
+ agent_config_obj
396
+ )
397
+ except Exception as e:
398
+ raise ValueError(
399
+ f"Cannot validate into a DeepResearchAgentConfig model. Got {agent_config_obj}"
400
+ ) from e
401
+
402
+ os.environ["DEEP_RESEARCH_MODEL"] = deep_research_config.model_name
403
+ os.environ["DEEP_RESEARCH_PROMPT"] = deep_research_config.system_prompt
404
+ os.environ["TAVILY_API_KEY"] = deep_research_config.tavily_api_key
405
+
406
+ validated_config = LangGraphAgentConfig(
407
+ name=deep_research_config.name,
408
+ graph_definition="idun_agent_engine.templates.deep_research:graph",
409
+ input_schema_definition=deep_research_config.input_schema_definition,
410
+ output_schema_definition=deep_research_config.output_schema_definition,
411
+ observability=deep_research_config.observability,
412
+ checkpointer=deep_research_config.checkpointer,
413
+ )
414
+ agent_instance = LanggraphAgent()
415
+
256
416
  elif agent_type == AgentFramework.HAYSTACK:
257
417
  from idun_agent_engine.agent.haystack.haystack import HaystackAgent
258
418
 
@@ -264,11 +424,24 @@ class ConfigBuilder:
264
424
  f"Cannot validate into a HaystackAgentConfig model. Got {agent_config_obj}"
265
425
  ) from e
266
426
  agent_instance = HaystackAgent()
427
+ elif agent_type == AgentFramework.ADK:
428
+ from idun_agent_engine.agent.adk.adk import AdkAgent
429
+
430
+ try:
431
+ validated_config = AdkAgentConfig.model_validate(agent_config_obj)
432
+ except Exception as e:
433
+ raise ValueError(
434
+ f"Cannot validate into a AdkAgentConfig model. Got {agent_config_obj}"
435
+ ) from e
436
+ agent_instance = AdkAgent()
267
437
  else:
268
438
  raise ValueError(f"Unsupported agent type: {agent_type}")
269
439
 
270
440
  # Initialize the agent with its configuration
271
- await agent_instance.initialize(validated_config) # type: ignore[arg-type]
441
+ await agent_instance.initialize(
442
+ validated_config,
443
+ observability_config, # , mcp_registry=mcp_registry
444
+ ) # type: ignore[arg-type]
272
445
  return agent_instance
273
446
 
274
447
  @staticmethod
@@ -284,7 +457,13 @@ class ConfigBuilder:
284
457
  Raises:
285
458
  ValueError: If agent type is unsupported
286
459
  """
287
- if agent_type == "langgraph":
460
+ if (
461
+ agent_type == "langgraph"
462
+ or agent_type == AgentFramework.LANGGRAPH
463
+ or agent_type == AgentFramework.TRANSLATION_AGENT
464
+ or agent_type == AgentFramework.CORRECTION_AGENT
465
+ or agent_type == AgentFramework.DEEP_RESEARCH_AGENT
466
+ ):
288
467
  from ..agent.langgraph.langgraph import LanggraphAgent
289
468
 
290
469
  return LanggraphAgent
@@ -311,10 +490,21 @@ class ConfigBuilder:
311
490
  if agent_type == "langgraph":
312
491
  validated_config = LangGraphAgentConfig.model_validate(config)
313
492
  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()
493
+ elif agent_type == AgentFramework.TRANSLATION_AGENT:
494
+ from idun_agent_schema.engine.templates import TranslationAgentConfig
495
+
496
+ validated_config = TranslationAgentConfig.model_validate(config)
497
+ return validated_config.model_dump()
498
+ elif agent_type == AgentFramework.CORRECTION_AGENT:
499
+ from idun_agent_schema.engine.templates import CorrectionAgentConfig
500
+
501
+ validated_config = CorrectionAgentConfig.model_validate(config)
502
+ return validated_config.model_dump()
503
+ elif agent_type == AgentFramework.DEEP_RESEARCH_AGENT:
504
+ from idun_agent_schema.engine.templates import DeepResearchAgentConfig
505
+
506
+ validated_config = DeepResearchAgentConfig.model_validate(config)
507
+ return validated_config.model_dump()
318
508
  else:
319
509
  raise ValueError(f"Unsupported agent type: {agent_type}")
320
510
 
@@ -345,17 +535,21 @@ class ConfigBuilder:
345
535
  @staticmethod
346
536
  async def load_and_initialize_agent(
347
537
  config_path: str = "config.yaml",
538
+ mcp_registry: Any | None = None,
348
539
  ) -> tuple[EngineConfig, BaseAgent]:
349
540
  """Load configuration and initialize agent in one step.
350
541
 
351
542
  Args:
352
543
  config_path: Path to the configuration YAML file
544
+ mcp_registry: Optional MCP registry client.
353
545
 
354
546
  Returns:
355
547
  tuple[EngineConfig, BaseAgent]: Configuration and initialized agent
356
548
  """
357
549
  engine_config = ConfigBuilder.load_from_file(config_path)
358
- agent = await ConfigBuilder.initialize_agent_from_config(engine_config)
550
+ agent = await ConfigBuilder.initialize_agent_from_config(
551
+ engine_config, mcp_registry=mcp_registry
552
+ )
359
553
  return engine_config, agent
360
554
 
361
555
  @staticmethod
@@ -364,6 +558,7 @@ class ConfigBuilder:
364
558
  config_dict: dict[str, Any] | None = None,
365
559
  engine_config: EngineConfig | None = None,
366
560
  ) -> EngineConfig:
561
+ print(config_dict)
367
562
  """Umbrella function to resolve configuration from various sources.
368
563
 
369
564
  This function handles all the different ways configuration can be provided
@@ -424,7 +619,9 @@ class ConfigBuilder:
424
619
  builder = cls()
425
620
  builder._server_config = engine_config.server
426
621
  builder._agent_config = engine_config.agent
427
-
622
+ builder._guardrails = engine_config.guardrails
623
+ builder._observability = engine_config.observability
624
+ builder._mcp_servers = engine_config.mcp_servers
428
625
  return builder
429
626
 
430
627
  @classmethod
@@ -453,4 +650,8 @@ class ConfigBuilder:
453
650
  builder = cls()
454
651
  builder._server_config = engine_config.server
455
652
  builder._agent_config = engine_config.agent
653
+ builder._guardrails = engine_config.guardrails
654
+ builder._observability = engine_config.observability
655
+ builder._mcp_servers = engine_config.mcp_servers
656
+
456
657
  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,5 @@
1
+ """MCP utilities for Idun Agent Engine."""
2
+
3
+ from .registry import MCPClientRegistry
4
+ from .helpers import get_adk_tools_from_api, get_adk_tools_from_file
5
+ __all__ = ["MCPClientRegistry", "get_adk_tools_from_api", "get_adk_tools_from_file", "get_adk_tools"]
@@ -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()