idun-agent-engine 0.4.0__py3-none-any.whl → 0.4.2__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 (51) hide show
  1. idun_agent_engine/_version.py +1 -1
  2. idun_agent_engine/agent/adk/adk.py +7 -4
  3. idun_agent_engine/agent/haystack/__init__.py +0 -2
  4. idun_agent_engine/agent/haystack/haystack.py +9 -5
  5. idun_agent_engine/agent/langgraph/langgraph.py +10 -13
  6. idun_agent_engine/core/config_builder.py +33 -13
  7. idun_agent_engine/guardrails/guardrails_hub/guardrails_hub.py +52 -9
  8. idun_agent_engine/mcp/__init__.py +2 -2
  9. idun_agent_engine/mcp/helpers.py +53 -15
  10. idun_agent_engine/mcp/registry.py +5 -5
  11. idun_agent_engine/observability/base.py +11 -2
  12. idun_agent_engine/observability/gcp_trace/gcp_trace_handler.py +3 -1
  13. idun_agent_engine/observability/langfuse/langfuse_handler.py +1 -3
  14. idun_agent_engine/server/dependencies.py +7 -2
  15. idun_agent_engine/server/lifespan.py +2 -7
  16. idun_agent_engine/server/routers/agent.py +2 -1
  17. idun_agent_engine/server/routers/base.py +7 -5
  18. idun_agent_engine/telemetry/__init__.py +0 -1
  19. idun_agent_engine/telemetry/config.py +0 -1
  20. idun_agent_engine/telemetry/telemetry.py +3 -4
  21. idun_agent_engine/templates/correction.py +4 -7
  22. idun_agent_engine/templates/deep_research.py +1 -0
  23. idun_agent_engine/templates/translation.py +4 -4
  24. {idun_agent_engine-0.4.0.dist-info → idun_agent_engine-0.4.2.dist-info}/METADATA +2 -2
  25. idun_agent_engine-0.4.2.dist-info/RECORD +86 -0
  26. idun_platform_cli/groups/agent/package.py +4 -1
  27. idun_platform_cli/groups/agent/serve.py +2 -0
  28. idun_platform_cli/groups/init.py +2 -0
  29. idun_platform_cli/telemetry.py +55 -0
  30. idun_platform_cli/tui/css/create_agent.py +137 -14
  31. idun_platform_cli/tui/css/main.py +7 -10
  32. idun_platform_cli/tui/main.py +3 -3
  33. idun_platform_cli/tui/schemas/create_agent.py +8 -4
  34. idun_platform_cli/tui/screens/create_agent.py +186 -20
  35. idun_platform_cli/tui/utils/config.py +23 -2
  36. idun_platform_cli/tui/validators/guardrails.py +20 -6
  37. idun_platform_cli/tui/validators/mcps.py +9 -6
  38. idun_platform_cli/tui/widgets/__init__.py +8 -4
  39. idun_platform_cli/tui/widgets/chat_widget.py +155 -0
  40. idun_platform_cli/tui/widgets/guardrails_widget.py +12 -4
  41. idun_platform_cli/tui/widgets/identity_widget.py +28 -10
  42. idun_platform_cli/tui/widgets/mcps_widget.py +113 -25
  43. idun_platform_cli/tui/widgets/memory_widget.py +194 -0
  44. idun_platform_cli/tui/widgets/observability_widget.py +12 -14
  45. idun_platform_cli/tui/widgets/serve_widget.py +50 -47
  46. idun_agent_engine/agent/haystack/haystack_model.py +0 -13
  47. idun_agent_engine/guardrails/guardrails_hub/utils.py +0 -1
  48. idun_agent_engine/server/routers/agui.py +0 -47
  49. idun_agent_engine-0.4.0.dist-info/RECORD +0 -86
  50. {idun_agent_engine-0.4.0.dist-info → idun_agent_engine-0.4.2.dist-info}/WHEEL +0 -0
  51. {idun_agent_engine-0.4.0.dist-info → idun_agent_engine-0.4.2.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,3 @@
1
1
  """Version information for Idun Agent Engine."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.4.2"
@@ -5,6 +5,7 @@ import uuid
5
5
  from collections.abc import AsyncGenerator
6
6
  from typing import Any
7
7
 
8
+ from ag_ui_adk import ADKAgent as ADKAGUIAgent
8
9
  from google.adk.apps.app import App
9
10
  from google.adk.memory import (
10
11
  InMemoryMemoryService,
@@ -25,9 +26,8 @@ from idun_agent_schema.engine.adk import (
25
26
  )
26
27
  from idun_agent_schema.engine.observability_v2 import ObservabilityConfig
27
28
 
28
- from ag_ui_adk import ADKAgent as ADKAGUIAgent
29
- from idun_agent_engine.agent import base as agent_base
30
29
  from idun_agent_engine import observability
30
+ from idun_agent_engine.agent import base as agent_base
31
31
 
32
32
 
33
33
  class AdkAgent(agent_base.BaseAgent):
@@ -123,7 +123,7 @@ class AdkAgent(agent_base.BaseAgent):
123
123
  # Observability (provider-agnostic)
124
124
  if observability_config:
125
125
  handlers, infos = observability.create_observability_handlers(
126
- observability_config # type: ignore[arg-type]
126
+ observability_config # type: ignore[arg-type]
127
127
  )
128
128
  self._obs_callbacks = []
129
129
  for handler in handlers:
@@ -153,6 +153,7 @@ class AdkAgent(agent_base.BaseAgent):
153
153
 
154
154
  if is_langfuse_enabled:
155
155
  import os
156
+
156
157
  langfuse_pk = os.environ.get("LANGFUSE_PUBLIC_KEY")
157
158
  langfuse_host = os.environ.get("LANGFUSE_BASE_URL")
158
159
  print(f"LANGFUSE_PUBLIC_KEY: {langfuse_pk}")
@@ -171,7 +172,9 @@ class AdkAgent(agent_base.BaseAgent):
171
172
  except Exception as e:
172
173
  print(f"Failed to instrument Google ADK: {e}")
173
174
  except Exception as e:
174
- print(f"Error checking observability config for ADK instrumentation: {e}")
175
+ print(
176
+ f"Error checking observability config for ADK instrumentation: {e}"
177
+ )
175
178
 
176
179
  # Initialize Session Service
177
180
  await self._initialize_session_service()
@@ -1,9 +1,7 @@
1
1
  """LangGraph agent package."""
2
2
 
3
3
  from .haystack import HaystackAgent
4
- from .haystack_model import HaystackAgentConfig
5
4
 
6
5
  __all__ = [
7
6
  "HaystackAgent",
8
- "HaystackAgentConfig",
9
7
  ]
@@ -10,9 +10,10 @@ from haystack import Pipeline
10
10
  from haystack.components.agents import Agent
11
11
  from haystack.dataclasses import ChatMessage
12
12
  from haystack_integrations.components.connectors.langfuse import LangfuseConnector
13
+ from idun_agent_schema.engine.haystack import HaystackAgentConfig
14
+ from idun_agent_schema.engine.observability_v2 import ObservabilityConfig
13
15
 
14
16
  from idun_agent_engine.agent.base import BaseAgent
15
- from idun_agent_schema.engine.haystack import HaystackAgentConfig
16
17
  from idun_agent_engine.agent.haystack.utils import _parse_component_definition
17
18
 
18
19
  logging.basicConfig(
@@ -77,7 +78,9 @@ class HaystackAgent(BaseAgent):
77
78
  Raises:
78
79
  RuntimeError: If the CopilotKit agent is not yet initialized.
79
80
  """
80
- raise NotImplementedError("CopilotKit agent instance not supported yet for Haystack agent.")
81
+ raise NotImplementedError(
82
+ "CopilotKit agent instance not supported yet for Haystack agent."
83
+ )
81
84
 
82
85
  @property
83
86
  def configuration(self) -> HaystackAgentConfig:
@@ -146,9 +149,10 @@ class HaystackAgent(BaseAgent):
146
149
  # TODO: await persistence haystack
147
150
  # TODO OBS block
148
151
 
149
- # check if config has observability `enabled` or `disabled`, so that we adjust our component to
150
- # either add a tracer or not
151
- if self._configuration.observability.enabled:
152
+ if (
153
+ self._configuration.observability
154
+ and self._configuration.observability.enabled
155
+ ): # FIXED: should also check for enabled
152
156
  self._enable_tracing = True
153
157
  logger.info("Enabling tracing...")
154
158
  component: Agent | Pipeline = self._load_component(
@@ -1,7 +1,7 @@
1
1
  """LangGraph agent adapter implementing the BaseAgent protocol."""
2
2
 
3
- import importlib.util
4
3
  import importlib
4
+ import importlib.util
5
5
  import uuid
6
6
  from collections.abc import AsyncGenerator
7
7
  from typing import Any
@@ -9,6 +9,7 @@ from typing import Any
9
9
  import aiosqlite
10
10
  from ag_ui.core import events as ag_events
11
11
  from ag_ui.core import types as ag_types
12
+ from copilotkit import LangGraphAGUIAgent
12
13
  from idun_agent_schema.engine.langgraph import (
13
14
  InMemoryCheckpointConfig,
14
15
  LangGraphAgentConfig,
@@ -24,7 +25,6 @@ from langgraph.graph.state import CompiledStateGraph
24
25
 
25
26
  from idun_agent_engine import observability
26
27
  from idun_agent_engine.agent import base as agent_base
27
- from copilotkit import LangGraphAGUIAgent
28
28
 
29
29
 
30
30
  class LanggraphAgent(agent_base.BaseAgent):
@@ -202,8 +202,11 @@ class LanggraphAgent(agent_base.BaseAgent):
202
202
  self._agent_instance = graph_builder.compile(
203
203
  checkpointer=self._checkpointer, store=self._store
204
204
  )
205
- elif isinstance(graph_builder, CompiledStateGraph): # TODO: to remove, dirty fix for template deepagent langgraph
206
- self._agent_instance = graph_builder
205
+ elif isinstance(graph_builder, CompiledStateGraph):
206
+ # TODO: this was made for supporting langgraph's DeepAgent, modernize.
207
+ raise TypeError(
208
+ "Expected StateGraph, Got CompiledStateGraph. Make sure not to run `compile` on your agent's graph."
209
+ )
207
210
 
208
211
  self._copilotkit_agent_instance = LangGraphAGUIAgent(
209
212
  name=self._name,
@@ -212,12 +215,6 @@ class LanggraphAgent(agent_base.BaseAgent):
212
215
  config={"callbacks": self._obs_callbacks} if self._obs_callbacks else None,
213
216
  )
214
217
 
215
- self._copilotkit_agent_instance = LangGraphAGUIAgent(
216
- name=self._name,
217
- description="Agent description", # TODO: add agent description
218
- graph=self._agent_instance,
219
- )
220
-
221
218
  if self._agent_instance:
222
219
  try:
223
220
  self._input_schema = self._agent_instance.input_schema
@@ -252,9 +249,10 @@ class LanggraphAgent(agent_base.BaseAgent):
252
249
 
253
250
  if self._configuration.checkpointer:
254
251
  if isinstance(self._configuration.checkpointer, SqliteCheckpointConfig):
255
- self._connection = await aiosqlite.connect(
256
- self._configuration.checkpointer.db_path
252
+ db_path = self._configuration.checkpointer.db_url.replace(
253
+ "sqlite:///", ""
257
254
  )
255
+ self._connection = await aiosqlite.connect(db_path)
258
256
  self._checkpointer = AsyncSqliteSaver(conn=self._connection)
259
257
  self._infos["checkpointer"] = (
260
258
  self._configuration.checkpointer.model_dump()
@@ -344,7 +342,6 @@ class LanggraphAgent(agent_base.BaseAgent):
344
342
  def _validate_graph_builder(
345
343
  self, graph_builder: Any, module_path: str, graph_variable_name: str
346
344
  ) -> StateGraph:
347
- # TODO to remove, dirty fix for template deepagent langgraph
348
345
  if not isinstance(graph_builder, StateGraph) and not isinstance(
349
346
  graph_builder, CompiledStateGraph
350
347
  ):
@@ -4,6 +4,7 @@ This module provides a fluent API for building configuration objects using Pydan
4
4
  This approach ensures type safety, validation, and consistency with the rest of the codebase.
5
5
  """
6
6
 
7
+ import os
7
8
  from pathlib import Path
8
9
  from typing import Any
9
10
 
@@ -50,7 +51,6 @@ class ConfigBuilder:
50
51
  """Initialize a new configuration builder with default values."""
51
52
  self._server_config = ServerConfig()
52
53
  self._agent_config: AgentConfig | None = None
53
- # TODO: add mcp_servers config
54
54
  self._mcp_servers: list[MCPServer] | None = None
55
55
  self._observability: list[ObservabilityConfig] | None = None
56
56
  self._guardrails: Guardrails | None = None
@@ -259,7 +259,7 @@ class ConfigBuilder:
259
259
  mcp_servers=self._mcp_servers,
260
260
  )
261
261
 
262
- def build_dict(self) -> dict[str, Any]:
262
+ def build_dict(self) -> dict[str, Any]: # NOT USED
263
263
  """Build and return the configuration as a dictionary.
264
264
 
265
265
  This is a convenience method for backward compatibility.
@@ -268,17 +268,24 @@ class ConfigBuilder:
268
268
  Dict[str, Any]: The complete configuration dictionary
269
269
  """
270
270
  engine_config = self.build()
271
- return engine_config.model_dump()
271
+ return engine_config.model_dump(mode="python")
272
272
 
273
- def save_to_file(self, file_path: str) -> None:
273
+ def save_to_file(
274
+ self, file_path: str
275
+ ) -> None: # FIXED: old method doesn't serialize enums correctly
274
276
  """Save the configuration to a YAML file.
275
277
 
276
278
  Args:
277
279
  file_path: Path where to save the configuration file
278
280
  """
279
- config = self.build_dict()
281
+ engine_model = self.build()
280
282
  with open(file_path, "w") as f:
281
- yaml.dump(config, f, default_flow_style=False, indent=2)
283
+ yaml.dump(
284
+ engine_model.model_dump(mode="json"),
285
+ f,
286
+ default_flow_style=False,
287
+ indent=2,
288
+ )
282
289
 
283
290
  async def build_and_initialize_agent(
284
291
  self, mcp_registry: Any | None = None
@@ -319,9 +326,10 @@ class ConfigBuilder:
319
326
  # Initialize the appropriate agent
320
327
  agent_instance = None
321
328
  if agent_type == AgentFramework.LANGGRAPH:
322
- from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
323
329
  import os
324
330
 
331
+ from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
332
+
325
333
  print("Current directory: ", os.getcwd()) # TODO remove
326
334
  try:
327
335
  validated_config = LangGraphAgentConfig.model_validate(agent_config_obj)
@@ -334,10 +342,12 @@ class ConfigBuilder:
334
342
  agent_instance = LanggraphAgent()
335
343
 
336
344
  elif agent_type == AgentFramework.TRANSLATION_AGENT:
337
- from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
338
- from idun_agent_schema.engine.templates import TranslationAgentConfig
339
345
  import os
340
346
 
347
+ from idun_agent_schema.engine.templates import TranslationAgentConfig
348
+
349
+ from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
350
+
341
351
  try:
342
352
  translation_config = TranslationAgentConfig.model_validate(
343
353
  agent_config_obj
@@ -364,10 +374,12 @@ class ConfigBuilder:
364
374
  agent_instance = LanggraphAgent()
365
375
 
366
376
  elif agent_type == AgentFramework.CORRECTION_AGENT:
367
- from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
368
- from idun_agent_schema.engine.templates import CorrectionAgentConfig
369
377
  import os
370
378
 
379
+ from idun_agent_schema.engine.templates import CorrectionAgentConfig
380
+
381
+ from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
382
+
371
383
  try:
372
384
  correction_config = CorrectionAgentConfig.model_validate(
373
385
  agent_config_obj
@@ -391,10 +403,12 @@ class ConfigBuilder:
391
403
  agent_instance = LanggraphAgent()
392
404
 
393
405
  elif agent_type == AgentFramework.DEEP_RESEARCH_AGENT:
394
- from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
395
- from idun_agent_schema.engine.templates import DeepResearchAgentConfig
396
406
  import os
397
407
 
408
+ from idun_agent_schema.engine.templates import DeepResearchAgentConfig
409
+
410
+ from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
411
+
398
412
  try:
399
413
  deep_research_config = DeepResearchAgentConfig.model_validate(
400
414
  agent_config_obj
@@ -517,6 +531,9 @@ class ConfigBuilder:
517
531
  def load_from_file(config_path: str = "config.yaml") -> EngineConfig:
518
532
  """Load configuration from a YAML file and return a validated EngineConfig.
519
533
 
534
+ Sets IDUN_CONFIG_PATH environment variable to enable MCP helper functions
535
+ (get_adk_tools, get_langchain_tools) to automatically discover the config file.
536
+
520
537
  Args:
521
538
  config_path: Path to the configuration YAML file
522
539
 
@@ -532,6 +549,9 @@ class ConfigBuilder:
532
549
  # Resolve relative to the current working directory
533
550
  path = Path.cwd() / path
534
551
 
552
+ # Set IDUN_CONFIG_PATH for MCP helpers to discover
553
+ os.environ["IDUN_CONFIG_PATH"] = str(path)
554
+
535
555
  with open(path) as f:
536
556
  config_data = yaml.safe_load(f)
537
557
 
@@ -9,26 +9,51 @@ from ..base import BaseGuardrail
9
9
 
10
10
  def get_guard_instance(name: GuardrailConfigId) -> Guard:
11
11
  """Returns a map of guard type -> guard instance."""
12
- if name.value == "ban_list":
12
+ if name == GuardrailConfigId.BAN_LIST:
13
13
  from guardrails.hub import BanList
14
14
 
15
15
  return BanList
16
16
 
17
- elif name.value == "detect_pii":
17
+ elif name == GuardrailConfigId.DETECT_PII:
18
18
  from guardrails.hub import DetectPII
19
19
 
20
20
  return DetectPII
21
21
 
22
- elif name.value == "nsfw":
22
+ elif name == GuardrailConfigId.NSFW_TEXT:
23
23
  from guardrails.hub import NSFWText
24
24
 
25
25
  return NSFWText
26
26
 
27
- elif name.value == "competitor_check":
27
+ elif name == GuardrailConfigId.COMPETITION_CHECK:
28
28
  from guardrails.hub import CompetitorCheck
29
29
 
30
30
  return CompetitorCheck
31
31
 
32
+ elif name == GuardrailConfigId.BIAS_CHECK:
33
+ from guardrails.hub import BiasCheck
34
+
35
+ return BiasCheck
36
+
37
+ elif name == GuardrailConfigId.CORRECT_LANGUAGE:
38
+ from guardrails.hub import ValidLanguage
39
+
40
+ return ValidLanguage
41
+
42
+ elif name == GuardrailConfigId.GIBBERISH_TEXT:
43
+ from guardrails.hub import GibberishText
44
+
45
+ return GibberishText
46
+
47
+ elif name == GuardrailConfigId.TOXIC_LANGUAGE:
48
+ from guardrails.hub import ToxicLanguage
49
+
50
+ return ToxicLanguage
51
+
52
+ elif name == GuardrailConfigId.RESTRICT_TO_TOPIC:
53
+ from guardrails.hub import RestrictToTopic
54
+
55
+ return RestrictToTopic
56
+
32
57
  else:
33
58
  raise ValueError(f"Guard {name} not found.")
34
59
 
@@ -90,11 +115,29 @@ class GuardrailsHubGuard(BaseGuardrail):
90
115
  f"Guard: {self.guard_id} is not yet supported, or does not exist."
91
116
  )
92
117
 
93
- guard_instance_params = self._guardrail_config.guard_params.model_dump()
94
- guard_instance = guard(**guard_instance_params)
95
- for param, value in guard_instance_params.items():
96
- setattr(guard_instance, param, value)
97
- return guard_instance
118
+ if hasattr(self._guardrail_config, "guard_params"):
119
+ guard_instance_params = self._guardrail_config.guard_params.model_dump()
120
+ else:
121
+ config_dict = self._guardrail_config.model_dump()
122
+ exclude_fields = {"config_id", "api_key", "reject_message", "guard_url"}
123
+ guard_instance_params = {
124
+ k: v for k, v in config_dict.items() if k not in exclude_fields
125
+ }
126
+
127
+ try:
128
+ guard_instance = guard(**guard_instance_params)
129
+ for param, value in guard_instance_params.items():
130
+ setattr(guard_instance, param, value)
131
+ return guard_instance
132
+ except SystemError:
133
+ # sentencepiece mutex lock error when loading models in quick succession
134
+ import time
135
+
136
+ time.sleep(0.5)
137
+ guard_instance = guard(**guard_instance_params)
138
+ for param, value in guard_instance_params.items():
139
+ setattr(guard_instance, param, value)
140
+ return guard_instance
98
141
 
99
142
  def validate(self, input: str) -> bool:
100
143
  """TODO."""
@@ -1,14 +1,14 @@
1
1
  """MCP utilities for Idun Agent Engine."""
2
2
 
3
- from .registry import MCPClientRegistry
4
3
  from .helpers import (
4
+ get_adk_tools,
5
5
  get_adk_tools_from_api,
6
6
  get_adk_tools_from_file,
7
- get_adk_tools,
8
7
  get_langchain_tools,
9
8
  get_langchain_tools_from_api,
10
9
  get_langchain_tools_from_file,
11
10
  )
11
+ from .registry import MCPClientRegistry
12
12
 
13
13
  __all__ = [
14
14
  "MCPClientRegistry",
@@ -1,11 +1,14 @@
1
+ import os
1
2
  from pathlib import Path
2
3
  from typing import Any
3
- import yaml
4
+
4
5
  import requests
5
- import os
6
- from idun_agent_engine.mcp.registry import MCPClientRegistry
6
+ import yaml
7
7
  from idun_agent_schema.engine.mcp_server import MCPServer
8
8
 
9
+ from idun_agent_engine.mcp.registry import MCPClientRegistry
10
+
11
+
9
12
  def _extract_mcp_configs(config_data: dict[str, Any]) -> list[MCPServer]:
10
13
  """Parse MCP server configs from a config dictionary."""
11
14
  mcp_configs_data = config_data.get("mcp_servers") or config_data.get("mcpServers")
@@ -86,9 +89,9 @@ def _fetch_config_from_api() -> dict[str, Any]:
86
89
  except yaml.YAMLError as e:
87
90
  raise ValueError(f"Failed to parse config YAML: {e}") from e
88
91
 
92
+
89
93
  def get_adk_tools_from_file(config_path: str | Path) -> list[Any]:
90
- """
91
- Loads MCP configurations from a YAML file and returns a list of ADK toolsets.
94
+ """Loads MCP configurations from a YAML file and returns a list of ADK toolsets.
92
95
 
93
96
  Args:
94
97
  config_path: Path to the configuration YAML file.
@@ -99,9 +102,9 @@ def get_adk_tools_from_file(config_path: str | Path) -> list[Any]:
99
102
  config_data = _load_config_from_file(config_path)
100
103
  return _get_toolsets_from_data(config_data)
101
104
 
105
+
102
106
  def get_adk_tools_from_api() -> list[Any]:
103
- """
104
- Fetches configuration from the Idun Manager API and returns a list of ADK toolsets.
107
+ """Fetches configuration from the Idun Manager API and returns a list of ADK toolsets.
105
108
 
106
109
  Returns:
107
110
  List of initialized ADK McpToolset instances.
@@ -111,37 +114,72 @@ def get_adk_tools_from_api() -> list[Any]:
111
114
 
112
115
 
113
116
  def get_adk_tools(config_path: str | Path | None = None) -> list[Any]:
114
- """
115
- Returns ADK toolsets using config from file when provided, otherwise from API.
117
+ """Returns ADK toolsets using config from file when provided, from IDUN_CONFIG_PATH env var, or from API.
118
+
119
+ The function resolves configuration in the following order:
120
+ 1. Uses the provided config_path if specified
121
+ 2. Uses IDUN_CONFIG_PATH environment variable if set
122
+ 3. Falls back to fetching from Idun Manager API
123
+
124
+ Args:
125
+ config_path: Optional path to configuration YAML file. If provided, takes precedence.
116
126
 
117
127
  Returns:
118
128
  List of initialized ADK McpToolset instances.
129
+
130
+ Raises:
131
+ ValueError: If no config source is available or API credentials are missing.
132
+ FileNotFoundError: If specified config file doesn't exist.
119
133
  """
120
134
  if config_path:
121
135
  return get_adk_tools_from_file(config_path)
136
+
137
+ # Check for IDUN_CONFIG_PATH environment variable
138
+ env_config_path = os.environ.get("IDUN_CONFIG_PATH")
139
+ if env_config_path:
140
+ return get_adk_tools_from_file(env_config_path)
141
+
122
142
  return get_adk_tools_from_api()
123
143
 
124
144
 
125
145
  async def get_langchain_tools_from_file(config_path: str | Path) -> list[Any]:
126
- """
127
- Loads MCP configurations from a YAML file and returns LangChain tool instances.
146
+ """Loads MCP configurations from a YAML file and returns LangChain tool instances.
128
147
  """
129
148
  config_data = _load_config_from_file(config_path)
130
149
  return await _get_langchain_tools_from_data(config_data)
131
150
 
132
151
 
133
152
  async def get_langchain_tools_from_api() -> list[Any]:
134
- """
135
- Fetches configuration from the Idun Manager API and returns LangChain tool instances.
153
+ """Fetches configuration from the Idun Manager API and returns LangChain tool instances.
136
154
  """
137
155
  config_data = _fetch_config_from_api()
138
156
  return await _get_langchain_tools_from_data(config_data)
139
157
 
140
158
 
141
159
  async def get_langchain_tools(config_path: str | Path | None = None) -> list[Any]:
142
- """
143
- Returns LangChain tool instances using config from file when provided, otherwise from API.
160
+ """Returns LangChain tool instances using config from file when provided, from IDUN_CONFIG_PATH env var, or from API.
161
+
162
+ The function resolves configuration in the following order:
163
+ 1. Uses the provided config_path if specified
164
+ 2. Uses IDUN_CONFIG_PATH environment variable if set
165
+ 3. Falls back to fetching from Idun Manager API
166
+
167
+ Args:
168
+ config_path: Optional path to configuration YAML file. If provided, takes precedence.
169
+
170
+ Returns:
171
+ List of initialized LangChain tool instances.
172
+
173
+ Raises:
174
+ ValueError: If no config source is available or API credentials are missing.
175
+ FileNotFoundError: If specified config file doesn't exist.
144
176
  """
145
177
  if config_path:
146
178
  return await get_langchain_tools_from_file(config_path)
179
+
180
+ # Check for IDUN_CONFIG_PATH environment variable
181
+ env_config_path = os.environ.get("IDUN_CONFIG_PATH")
182
+ if env_config_path:
183
+ return await get_langchain_tools_from_file(env_config_path)
184
+
147
185
  return await get_langchain_tools_from_api()
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any, cast, TYPE_CHECKING
5
+ from typing import TYPE_CHECKING, Any, cast
6
6
 
7
7
  from langchain_mcp_adapters.client import MultiServerMCPClient
8
8
  from langchain_mcp_adapters.sessions import Connection
@@ -80,15 +80,15 @@ class MCPClientRegistry:
80
80
  return await self._client.get_tools(server_name=name)
81
81
 
82
82
  async def get_langchain_tools(self, name: str | None = None) -> list[Any]:
83
- """
84
- Alias for get_tools to make intent explicit when using LangChain/LangGraph agents.
85
- """
83
+ """Alias for get_tools to make intent explicit when using LangChain/LangGraph agents."""
86
84
  return await self.get_tools(name=name)
87
85
 
88
86
  def get_adk_toolsets(self) -> list[Any]:
89
87
  """Return a list of Google ADK McpToolset instances for configured servers."""
90
88
  if McpToolset is None or StdioServerParameters is None:
91
- raise ImportError("google-adk and mcp packages are required for ADK toolsets.")
89
+ raise ImportError(
90
+ "google-adk and mcp packages are required for ADK toolsets."
91
+ )
92
92
 
93
93
  toolsets = []
94
94
  for config in self._configs:
@@ -9,9 +9,13 @@ import os
9
9
  from abc import ABC, abstractmethod
10
10
  from typing import Any
11
11
 
12
- from idun_agent_schema.engine.observability import ObservabilityConfig as ObservabilityConfigV1
12
+ from idun_agent_schema.engine.observability import (
13
+ ObservabilityConfig as ObservabilityConfigV1,
14
+ )
13
15
  from idun_agent_schema.engine.observability_v2 import (
14
16
  ObservabilityConfig as ObservabilityConfigV2,
17
+ )
18
+ from idun_agent_schema.engine.observability_v2 import (
15
19
  ObservabilityProvider,
16
20
  )
17
21
 
@@ -49,7 +53,11 @@ def _normalize_config(
49
53
  if not config.enabled:
50
54
  return {"enabled": False}
51
55
 
52
- provider = config.provider.value if hasattr(config.provider, "value") else config.provider
56
+ provider = (
57
+ config.provider.value
58
+ if hasattr(config.provider, "value")
59
+ else config.provider
60
+ )
53
61
  options = config.config.model_dump()
54
62
  return {
55
63
  "provider": provider,
@@ -152,6 +160,7 @@ def create_observability_handler(
152
160
  "error": "Unsupported provider",
153
161
  }
154
162
 
163
+
155
164
  def create_observability_handlers(
156
165
  configs: list[ObservabilityConfigV2 | ObservabilityConfigV1] | None,
157
166
  ) -> tuple[list[ObservabilityHandlerBase], list[dict[str, Any]]]:
@@ -108,7 +108,9 @@ class GCPTraceHandler(ObservabilityHandlerBase):
108
108
  except ImportError:
109
109
  pass
110
110
 
111
- logger.info("GCP Trace initialized for project: %s", project_id or "auto-detected")
111
+ logger.info(
112
+ "GCP Trace initialized for project: %s", project_id or "auto-detected"
113
+ )
112
114
 
113
115
  def get_callbacks(self) -> list[Any]:
114
116
  """Return callbacks."""
@@ -59,9 +59,7 @@ class LangfuseHandler(ObservabilityHandlerBase):
59
59
  # Initialize callback handler
60
60
  # We pass the resolved credentials explicitly to ensure they are used
61
61
  # even if env vars were not successfully set or read.
62
- self._callbacks = [
63
- CallbackHandler()
64
- ]
62
+ self._callbacks = [CallbackHandler()]
65
63
  except Exception as e:
66
64
  print(f"Failed to initialize Langfuse callback/client: {e}")
67
65
  self._callbacks = []
@@ -23,6 +23,7 @@ async def get_agent(request: Request):
23
23
  agent = await ConfigBuilder.initialize_agent_from_config(app_config)
24
24
  return agent
25
25
 
26
+
26
27
  async def get_copilotkit_agent(request: Request):
27
28
  """Return the pre-initialized agent instance from the app state.
28
29
 
@@ -34,7 +35,9 @@ async def get_copilotkit_agent(request: Request):
34
35
  # This is a fallback for cases where the lifespan event did not run,
35
36
  # like in some testing scenarios.
36
37
  # Consider logging a warning here.
37
- print("⚠️ CopilotKit agent not found in app state, initializing fallback agent...")
38
+ print(
39
+ "⚠️ CopilotKit agent not found in app state, initializing fallback agent..."
40
+ )
38
41
 
39
42
  app_config = ConfigBuilder.load_from_file()
40
43
  copilotkit_agent = await ConfigBuilder.initialize_agent_from_config(app_config)
@@ -43,7 +46,9 @@ async def get_copilotkit_agent(request: Request):
43
46
 
44
47
  def get_mcp_registry(request: Request) -> MCPClientRegistry:
45
48
  """Return the configured MCP registry if available."""
46
- registry: MCPClientRegistry | None = getattr(request.app.state, "mcp_registry", None)
49
+ registry: MCPClientRegistry | None = getattr(
50
+ request.app.state, "mcp_registry", None
51
+ )
47
52
  if registry is None or not registry.enabled:
48
53
  raise HTTPException(
49
54
  status_code=status.HTTP_404_NOT_FOUND,