idun-agent-engine 0.2.7__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.
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 +146 -55
  7. idun_agent_engine/core/app_factory.py +9 -0
  8. idun_agent_engine/core/config_builder.py +214 -23
  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 +83 -16
  27. idun_agent_engine/server/routers/agent.py +116 -24
  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.0.dist-info}/METADATA +62 -10
  35. idun_agent_engine-0.3.0.dist-info/RECORD +60 -0
  36. {idun_agent_engine-0.2.7.dist-info → idun_agent_engine-0.3.0.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.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
- response = requests.get(url=url, headers=headers)
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
- self._server_config = yaml_config["engine_config"]["server"]
100
- self._agent_config = yaml_config["engine_config"]["agent"]
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(type=AgentFramework.LANGGRAPH, config=langgraph_config)
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, config=LangGraphAgentConfig.model_validate(config)
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, config=HaystackAgentConfig.model_validate(config)
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(server=self._server_config, agent=self._agent_config)
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(self) -> BaseAgent:
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(engine_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(engine_config: EngineConfig) -> BaseAgent:
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(validated_config) # type: ignore[arg-type]
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 agent_type == "langgraph":
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
- # 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()
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(engine_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,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()