idun-agent-engine 0.3.4__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 (60) hide show
  1. idun_agent_engine/__init__.py +24 -0
  2. idun_agent_engine/_version.py +3 -0
  3. idun_agent_engine/agent/__init__.py +10 -0
  4. idun_agent_engine/agent/adk/__init__.py +5 -0
  5. idun_agent_engine/agent/adk/adk.py +296 -0
  6. idun_agent_engine/agent/base.py +112 -0
  7. idun_agent_engine/agent/haystack/__init__.py +9 -0
  8. idun_agent_engine/agent/haystack/haystack.py +274 -0
  9. idun_agent_engine/agent/haystack/haystack_model.py +13 -0
  10. idun_agent_engine/agent/haystack/utils.py +13 -0
  11. idun_agent_engine/agent/langgraph/__init__.py +7 -0
  12. idun_agent_engine/agent/langgraph/langgraph.py +553 -0
  13. idun_agent_engine/core/__init__.py +11 -0
  14. idun_agent_engine/core/app_factory.py +73 -0
  15. idun_agent_engine/core/config_builder.py +657 -0
  16. idun_agent_engine/core/engine_config.py +21 -0
  17. idun_agent_engine/core/server_runner.py +145 -0
  18. idun_agent_engine/guardrails/__init__.py +0 -0
  19. idun_agent_engine/guardrails/base.py +24 -0
  20. idun_agent_engine/guardrails/guardrails_hub/guardrails_hub.py +101 -0
  21. idun_agent_engine/guardrails/guardrails_hub/utils.py +1 -0
  22. idun_agent_engine/mcp/__init__.py +5 -0
  23. idun_agent_engine/mcp/helpers.py +97 -0
  24. idun_agent_engine/mcp/registry.py +109 -0
  25. idun_agent_engine/observability/__init__.py +17 -0
  26. idun_agent_engine/observability/base.py +172 -0
  27. idun_agent_engine/observability/gcp_logging/__init__.py +0 -0
  28. idun_agent_engine/observability/gcp_logging/gcp_logging_handler.py +52 -0
  29. idun_agent_engine/observability/gcp_trace/__init__.py +0 -0
  30. idun_agent_engine/observability/gcp_trace/gcp_trace_handler.py +116 -0
  31. idun_agent_engine/observability/langfuse/__init__.py +5 -0
  32. idun_agent_engine/observability/langfuse/langfuse_handler.py +79 -0
  33. idun_agent_engine/observability/phoenix/__init__.py +5 -0
  34. idun_agent_engine/observability/phoenix/phoenix_handler.py +65 -0
  35. idun_agent_engine/observability/phoenix_local/__init__.py +5 -0
  36. idun_agent_engine/observability/phoenix_local/phoenix_local_handler.py +123 -0
  37. idun_agent_engine/py.typed +0 -0
  38. idun_agent_engine/server/__init__.py +5 -0
  39. idun_agent_engine/server/dependencies.py +52 -0
  40. idun_agent_engine/server/lifespan.py +106 -0
  41. idun_agent_engine/server/routers/__init__.py +5 -0
  42. idun_agent_engine/server/routers/agent.py +204 -0
  43. idun_agent_engine/server/routers/agui.py +47 -0
  44. idun_agent_engine/server/routers/base.py +114 -0
  45. idun_agent_engine/server/server_config.py +8 -0
  46. idun_agent_engine/templates/__init__.py +1 -0
  47. idun_agent_engine/templates/correction.py +65 -0
  48. idun_agent_engine/templates/deep_research.py +40 -0
  49. idun_agent_engine/templates/translation.py +70 -0
  50. idun_agent_engine-0.3.4.dist-info/METADATA +335 -0
  51. idun_agent_engine-0.3.4.dist-info/RECORD +60 -0
  52. idun_agent_engine-0.3.4.dist-info/WHEEL +4 -0
  53. idun_agent_engine-0.3.4.dist-info/entry_points.txt +2 -0
  54. idun_platform_cli/__init__.py +0 -0
  55. idun_platform_cli/groups/__init__.py +0 -0
  56. idun_platform_cli/groups/agent/__init__.py +0 -0
  57. idun_platform_cli/groups/agent/main.py +16 -0
  58. idun_platform_cli/groups/agent/package.py +70 -0
  59. idun_platform_cli/groups/agent/serve.py +107 -0
  60. idun_platform_cli/main.py +14 -0
@@ -0,0 +1,52 @@
1
+ """GCP Logging observability handler."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from ..base import ObservabilityHandlerBase
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class GCPLoggingHandler(ObservabilityHandlerBase):
14
+ """GCP Logging handler."""
15
+
16
+ provider = "gcp_logging"
17
+
18
+ def __init__(self, options: dict[str, Any] | None = None):
19
+ """Initialize handler."""
20
+ super().__init__(options)
21
+ self.options = options or {}
22
+
23
+ try:
24
+ import google.cloud.logging
25
+ except ImportError as e:
26
+ logger.error("GCP Logging dependencies not found: %s", e)
27
+ raise ImportError(
28
+ "Please install 'google-cloud-logging' to use GCP Logging."
29
+ ) from e
30
+
31
+ project_id = self.options.get("project_id")
32
+ # If project_id is explicitly provided, use it, otherwise client will auto-detect
33
+ if project_id:
34
+ client = google.cloud.logging.Client(project=project_id)
35
+ else:
36
+ client = google.cloud.logging.Client()
37
+
38
+ # Get logging configuration options
39
+ log_level = self.options.get("severity", "INFO").upper()
40
+ level = getattr(logging, log_level, logging.INFO)
41
+
42
+ # Setup logging handler
43
+ # This attaches a CloudLoggingHandler to the root python logger
44
+ client.setup_logging(log_level=level)
45
+
46
+ logger.info("GCP Logging initialized for project: %s", client.project)
47
+
48
+ def get_callbacks(self) -> list[Any]:
49
+ """Return callbacks."""
50
+ # GCP Logging hooks into the standard python logging module,
51
+ # so no explicit LangChain callbacks are needed.
52
+ return []
File without changes
@@ -0,0 +1,116 @@
1
+ """GCP Trace observability handler."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from opentelemetry import trace
9
+ from opentelemetry.sdk.resources import Resource
10
+ from opentelemetry.sdk.trace import TracerProvider
11
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
12
+ from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
13
+
14
+ from ..base import ObservabilityHandlerBase
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class GCPTraceHandler(ObservabilityHandlerBase):
20
+ """GCP Trace handler."""
21
+
22
+ provider = "gcp_trace"
23
+
24
+ def __init__(self, options: dict[str, Any] | None = None):
25
+ """Initialize handler."""
26
+ super().__init__(options)
27
+ self.options = options or {}
28
+
29
+ try:
30
+ from openinference.instrumentation.langchain import LangChainInstrumentor
31
+ from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
32
+ except ImportError as e:
33
+ logger.error("GCP Trace dependencies not found: %s", e)
34
+ raise ImportError(
35
+ "Please install 'opentelemetry-exporter-gcp-trace' and 'openinference-instrumentation-langchain' to use GCP Trace."
36
+ ) from e
37
+
38
+ project_id = self.options.get("project_id")
39
+ if not project_id:
40
+ project_id = None
41
+
42
+ # Initialize exporter
43
+ exporter = CloudTraceSpanExporter(
44
+ project_id=project_id,
45
+ )
46
+
47
+ # Initialize sampler
48
+ sampling_rate = float(self.options.get("sampling_rate", 1.0))
49
+ sampler = TraceIdRatioBased(sampling_rate)
50
+
51
+ # Initialize resource
52
+ resource_attributes = {}
53
+ trace_name = self.options.get("trace_name")
54
+ if trace_name:
55
+ resource_attributes["service.name"] = trace_name
56
+
57
+ resource = Resource.create(resource_attributes)
58
+
59
+ # Initialize tracer provider
60
+ tracer_provider = TracerProvider(
61
+ sampler=sampler,
62
+ resource=resource,
63
+ )
64
+
65
+ # Add span processor
66
+ flush_interval = int(self.options.get("flush_interval", 5))
67
+ span_processor = BatchSpanProcessor(
68
+ exporter, schedule_delay_millis=flush_interval * 1000
69
+ )
70
+ tracer_provider.add_span_processor(span_processor)
71
+
72
+ # Set global tracer provider
73
+ trace.set_tracer_provider(tracer_provider)
74
+
75
+ # Instrument LangChain with OpenInference
76
+ LangChainInstrumentor().instrument(tracer_provider=tracer_provider)
77
+
78
+ # Instrument Guardrails
79
+ try:
80
+ from openinference.instrumentation.guardrails import GuardrailsInstrumentor
81
+
82
+ GuardrailsInstrumentor().instrument(tracer_provider=tracer_provider)
83
+ except ImportError:
84
+ pass
85
+
86
+ # Instrument VertexAI
87
+ try:
88
+ from openinference.instrumentation.vertexai import VertexAIInstrumentor
89
+
90
+ VertexAIInstrumentor().instrument(tracer_provider=tracer_provider)
91
+ except ImportError:
92
+ pass
93
+
94
+ # TODO: GCP GoogleADKInstrumentor is n conflist with langfuse, so we don't need to instrument it here
95
+ # Instrument Google ADK
96
+ # try:
97
+ # from openinference.instrumentation.google_adk import GoogleADKInstrumentor
98
+
99
+ # GoogleADKInstrumentor().instrument(tracer_provider=tracer_provider)
100
+ # except ImportError:
101
+ # pass
102
+
103
+ # Instrument MCP
104
+ try:
105
+ from openinference.instrumentation.mcp import MCPInstrumentor
106
+
107
+ MCPInstrumentor().instrument(tracer_provider=tracer_provider)
108
+ except ImportError:
109
+ pass
110
+
111
+ logger.info("GCP Trace initialized for project: %s", project_id or "auto-detected")
112
+
113
+ def get_callbacks(self) -> list[Any]:
114
+ """Return callbacks."""
115
+ # OpenTelemetry instrumentation uses global tracer provider, so no explicit callbacks needed here
116
+ return []
@@ -0,0 +1,5 @@
1
+ """Langfuse observability integration package."""
2
+
3
+ from .langfuse_handler import LangfuseHandler
4
+
5
+ __all__ = ["LangfuseHandler"]
@@ -0,0 +1,79 @@
1
+ """Langfuse observability handler implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any
7
+
8
+ from idun_agent_schema.engine.observability import _resolve_env
9
+
10
+ from ..base import ObservabilityHandlerBase
11
+
12
+
13
+ class LangfuseHandler(ObservabilityHandlerBase):
14
+ """Langfuse handler providing LangChain callbacks and client setup."""
15
+
16
+ provider = "langfuse"
17
+
18
+ def __init__(self, options: dict[str, Any] | None = None):
19
+ """Initialize handler, resolving env and preparing callbacks."""
20
+ super().__init__(options)
21
+ opts = self.options
22
+
23
+ # Resolve and set env vars as required by Langfuse
24
+ host = self._resolve_env(opts.get("host")) or os.getenv("LANGFUSE_BASE_URL")
25
+ public_key = self._resolve_env(opts.get("public_key")) or os.getenv(
26
+ "LANGFUSE_PUBLIC_KEY"
27
+ )
28
+ secret_key = self._resolve_env(opts.get("secret_key")) or os.getenv(
29
+ "LANGFUSE_SECRET_KEY"
30
+ )
31
+
32
+ if host:
33
+ os.environ["LANGFUSE_BASE_URL"] = host
34
+ if public_key:
35
+ os.environ["LANGFUSE_PUBLIC_KEY"] = public_key
36
+ if secret_key:
37
+ os.environ["LANGFUSE_SECRET_KEY"] = secret_key
38
+
39
+ # Instantiate callback handler lazily to avoid hard dep if not installed
40
+ self._callbacks: list[Any] = []
41
+ self._langfuse_client = None
42
+ try:
43
+ from langfuse import get_client
44
+ from langfuse.langchain import CallbackHandler
45
+
46
+ # Initialize client for auth check
47
+ self._langfuse_client = get_client()
48
+
49
+ try:
50
+ if self._langfuse_client.auth_check():
51
+ print("Langfuse client is authenticated and ready!")
52
+ else:
53
+ print(
54
+ "Authentication failed. Please check your credentials and host."
55
+ )
56
+ except Exception as e:
57
+ print(f"Error during Langfuse client authentication: {e}")
58
+
59
+ # Initialize callback handler
60
+ # We pass the resolved credentials explicitly to ensure they are used
61
+ # even if env vars were not successfully set or read.
62
+ self._callbacks = [
63
+ CallbackHandler()
64
+ ]
65
+ except Exception as e:
66
+ print(f"Failed to initialize Langfuse callback/client: {e}")
67
+ self._callbacks = []
68
+
69
+ @staticmethod
70
+ def _resolve_env(value: str | None) -> str | None:
71
+ return _resolve_env(value)
72
+
73
+ def get_callbacks(self) -> list[Any]:
74
+ """Return LangChain-compatible callback handlers (if available)."""
75
+ return self._callbacks
76
+
77
+ def get_client(self):
78
+ """Return underlying Langfuse client instance (if created)."""
79
+ return self._langfuse_client
@@ -0,0 +1,5 @@
1
+ """Arize Phoenix observability integration package."""
2
+
3
+ from .phoenix_handler import PhoenixHandler
4
+
5
+ __all__ = ["PhoenixHandler"]
@@ -0,0 +1,65 @@
1
+ """Phoenix observability handler implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any
7
+
8
+ from idun_agent_schema.engine.observability import _resolve_env
9
+
10
+ from ..base import ObservabilityHandlerBase
11
+
12
+
13
+ class PhoenixHandler(ObservabilityHandlerBase):
14
+ """Phoenix handler configuring OpenTelemetry and LangChain instrumentation."""
15
+
16
+ provider = "phoenix"
17
+
18
+ def __init__(self, options: dict[str, Any] | None = None):
19
+ """Initialize handler, resolving env and setting up instrumentation."""
20
+ super().__init__(options)
21
+ opts = self.options
22
+
23
+ # Resolve and set env vars as required by Phoenix
24
+ api_key = self._resolve_env(opts.get("api_key")) or os.getenv("PHOENIX_API_KEY")
25
+ collector = (
26
+ self._resolve_env(opts.get("collector"))
27
+ or self._resolve_env(opts.get("collector_endpoint"))
28
+ or os.getenv("PHOENIX_COLLECTOR_ENDPOINT")
29
+ )
30
+ self.project_name: str = opts.get("project_name") or "default"
31
+
32
+ if api_key:
33
+ os.environ["PHOENIX_API_KEY"] = api_key
34
+ if collector:
35
+ os.environ["PHOENIX_COLLECTOR_ENDPOINT"] = collector
36
+
37
+ # Some older Phoenix deployments (before 2025-06-24) require setting client headers.
38
+ # If not explicitly provided, set it from API key when available for backward compatibility.
39
+ client_headers = opts.get("client_headers")
40
+ if isinstance(client_headers, str) and client_headers:
41
+ os.environ["PHOENIX_CLIENT_HEADERS"] = client_headers
42
+ elif api_key and not os.getenv("PHOENIX_CLIENT_HEADERS"):
43
+ os.environ["PHOENIX_CLIENT_HEADERS"] = f"api_key={api_key}"
44
+
45
+ # Configure tracer provider using phoenix.otel.register
46
+ self._callbacks: list[Any] = []
47
+ try:
48
+ from openinference.instrumentation.langchain import LangChainInstrumentor
49
+ from phoenix.otel import register # type: ignore
50
+
51
+ tracer_provider = register(
52
+ project_name=self.project_name, auto_instrument=True
53
+ )
54
+ LangChainInstrumentor().instrument(tracer_provider=tracer_provider)
55
+ except Exception:
56
+ # Silent failure; user may not have phoenix installed
57
+ pass
58
+
59
+ @staticmethod
60
+ def _resolve_env(value: str | None) -> str | None:
61
+ return _resolve_env(value)
62
+
63
+ def get_callbacks(self) -> list[Any]:
64
+ """Return callbacks (Phoenix instruments globally; this may be empty)."""
65
+ return self._callbacks
@@ -0,0 +1,5 @@
1
+ """Arize Phoenix observability integration package."""
2
+
3
+ from .phoenix_local_handler import PhoenixLocalHandler
4
+
5
+ __all__ = ["PhoenixLocalHandler"]
@@ -0,0 +1,123 @@
1
+ """Phoenix observability handler implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ import shlex
8
+ import subprocess
9
+ from typing import Any
10
+
11
+ from idun_agent_schema.engine.observability import _resolve_env
12
+
13
+ from ..base import ObservabilityHandlerBase
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class PhoenixLocalHandler(ObservabilityHandlerBase):
19
+ """Phoenix handler configuring OpenTelemetry and LangChain instrumentation."""
20
+
21
+ provider = "phoenix-local"
22
+
23
+ def __init__(
24
+ self,
25
+ options: dict[str, Any] | None = None,
26
+ default_endpoint: str = "http://0.0.0.0:6006",
27
+ ):
28
+ """Initialize handler, start Phoenix via CLI, and set up instrumentation.
29
+
30
+ Args:
31
+ options: Configuration options dictionary
32
+ default_endpoint: Default Phoenix collector endpoint URL
33
+ """
34
+ logger.info("Initializing PhoenixLocalHandler")
35
+
36
+ super().__init__(options)
37
+ opts = self.options or {}
38
+
39
+ # Initialize instance variables
40
+ self._callbacks: list[Any] = []
41
+ self._proc: subprocess.Popen[bytes] | None = None
42
+ self.default_endpoint = default_endpoint
43
+ self.project_name: str = "default"
44
+
45
+ self._configure_collector_endpoint(opts)
46
+ self._start_phoenix_cli()
47
+
48
+ try:
49
+ from openinference.instrumentation.langchain import LangChainInstrumentor
50
+ from phoenix.otel import register
51
+
52
+ logger.debug("Successfully imported Phoenix dependencies")
53
+
54
+ self.project_name = opts.get("project_name") or "default"
55
+ logger.info(f"Using project name: {self.project_name}")
56
+
57
+ tracer_provider = register(
58
+ project_name=self.project_name, auto_instrument=True
59
+ )
60
+
61
+ LangChainInstrumentor().instrument(tracer_provider=tracer_provider)
62
+
63
+ except ImportError as e:
64
+ logger.error(f"Missing required Phoenix dependencies: {e}")
65
+ raise ImportError(f"Phoenix dependencies not found: {e}. ") from e
66
+
67
+ except Exception as e:
68
+ logger.error(f"Failed to set up Phoenix instrumentation: {e}")
69
+ raise RuntimeError(f"Phoenix instrumentation setup failed: {e}") from e
70
+
71
+ logger.info("Phoenix local handler initialized...")
72
+
73
+ def _configure_collector_endpoint(self, opts: dict[str, Any]) -> None:
74
+ """Configure the Phoenix collector endpoint from various sources."""
75
+ logger.debug("Configuring collector endpoint")
76
+
77
+ collector = (
78
+ self._resolve_env(opts.get("collector"))
79
+ or self._resolve_env(opts.get("collector_endpoint"))
80
+ or os.getenv("PHOENIX_COLLECTOR_ENDPOINT")
81
+ or self.default_endpoint
82
+ )
83
+
84
+ logger.info(f"Setting Phoenix collector endpoint to: {collector}")
85
+ os.environ["PHOENIX_COLLECTOR_ENDPOINT"] = collector
86
+ self.collector_endpoint = collector
87
+
88
+ def _start_phoenix_cli(self) -> None:
89
+ """Start pheonix subprocess."""
90
+ try:
91
+ cmd = "phoenix serve"
92
+ logger.debug(f"Executing command: {cmd}")
93
+
94
+ self._proc = subprocess.Popen(
95
+ shlex.split(cmd),
96
+ stdout=subprocess.DEVNULL,
97
+ stderr=subprocess.DEVNULL,
98
+ start_new_session=True,
99
+ )
100
+
101
+ logger.info(f"Phoenix server started with PID: {self._proc.pid}")
102
+
103
+ except FileNotFoundError as e:
104
+ logger.error(f"Phoenix CLI not found. Make sure Phoenix is installed : {e}")
105
+ self._proc = None
106
+
107
+ except subprocess.SubprocessError as e:
108
+ logger.error(f"Failed to start Phoenix CLI subprocess: {e}")
109
+ self._proc = None
110
+
111
+ except Exception as e:
112
+ logger.error(f"Unexpected error starting Phoenix CLI: {e}")
113
+ self._proc = None
114
+
115
+ @staticmethod
116
+ def _resolve_env(value: str | None) -> str | None:
117
+ """Resolve environment variable value."""
118
+ return _resolve_env(value)
119
+
120
+ def get_callbacks(self) -> list[Any]:
121
+ """Return callbacks (Phoenix instruments globally; this may be empty)."""
122
+ logger.debug("Getting callbacks (Phoenix uses global instrumentation)")
123
+ return self._callbacks
File without changes
@@ -0,0 +1,5 @@
1
+ """Server package for FastAPI app components and configuration."""
2
+
3
+ from . import dependencies, lifespan, server_config
4
+
5
+ __all__ = ["server_config", "dependencies", "lifespan"]
@@ -0,0 +1,52 @@
1
+ """Dependency injection helpers for FastAPI routes."""
2
+
3
+ from fastapi import HTTPException, Request, status
4
+
5
+ from ..core.config_builder import ConfigBuilder
6
+ from ..mcp import MCPClientRegistry
7
+
8
+
9
+ async def get_agent(request: Request):
10
+ """Return the pre-initialized agent instance from the app state.
11
+
12
+ Falls back to loading from the default config if not present (e.g., tests).
13
+ """
14
+ if hasattr(request.app.state, "agent"):
15
+ return request.app.state.agent
16
+ else:
17
+ # This is a fallback for cases where the lifespan event did not run,
18
+ # like in some testing scenarios.
19
+ # Consider logging a warning here.
20
+ print("⚠️ Agent not found in app state, initializing fallback agent...")
21
+
22
+ app_config = ConfigBuilder.load_from_file()
23
+ agent = await ConfigBuilder.initialize_agent_from_config(app_config)
24
+ return agent
25
+
26
+ async def get_copilotkit_agent(request: Request):
27
+ """Return the pre-initialized agent instance from the app state.
28
+
29
+ Falls back to loading from the default config if not present (e.g., tests).
30
+ """
31
+ if hasattr(request.app.state, "copilotkit_agent"):
32
+ return request.app.state.copilotkit_agent
33
+ else:
34
+ # This is a fallback for cases where the lifespan event did not run,
35
+ # like in some testing scenarios.
36
+ # Consider logging a warning here.
37
+ print("⚠️ CopilotKit agent not found in app state, initializing fallback agent...")
38
+
39
+ app_config = ConfigBuilder.load_from_file()
40
+ copilotkit_agent = await ConfigBuilder.initialize_agent_from_config(app_config)
41
+ return copilotkit_agent
42
+
43
+
44
+ def get_mcp_registry(request: Request) -> MCPClientRegistry:
45
+ """Return the configured MCP registry if available."""
46
+ registry: MCPClientRegistry | None = getattr(request.app.state, "mcp_registry", None)
47
+ if registry is None or not registry.enabled:
48
+ raise HTTPException(
49
+ status_code=status.HTTP_404_NOT_FOUND,
50
+ detail="MCP servers are not configured for this engine.",
51
+ )
52
+ return registry
@@ -0,0 +1,106 @@
1
+ """Server lifespan management utilities.
2
+
3
+ Initializes the agent at startup and cleans up resources on shutdown.
4
+ """
5
+
6
+ import inspect
7
+ from collections.abc import Sequence
8
+ from contextlib import asynccontextmanager
9
+
10
+ from fastapi import FastAPI
11
+
12
+ from ..core.config_builder import ConfigBuilder
13
+ from ..mcp import MCPClientRegistry
14
+
15
+ from idun_agent_schema.engine.guardrails import Guardrails, Guardrail
16
+
17
+ from ..guardrails.base import BaseGuardrail
18
+
19
+
20
+ def _parse_guardrails(guardrails_obj: Guardrails) -> Sequence[BaseGuardrail]:
21
+ """Adds the position of the guardrails (input/output) and returns the lift of updated guardrails."""
22
+
23
+ from ..guardrails.guardrails_hub.guardrails_hub import GuardrailsHubGuard as GHGuard
24
+
25
+ if not guardrails_obj.enabled:
26
+ return []
27
+
28
+ return [GHGuard(guard, position="input") for guard in guardrails_obj.input] + [
29
+ GHGuard(guard, position="output") for guard in guardrails_obj.output
30
+ ]
31
+
32
+
33
+ async def cleanup_agent(app: FastAPI):
34
+ """Clean up agent resources."""
35
+ agent = getattr(app.state, "agent", None)
36
+ if agent is not None:
37
+ close_fn = getattr(agent, "close", None)
38
+ if callable(close_fn):
39
+ result = close_fn()
40
+ if inspect.isawaitable(result):
41
+ await result
42
+
43
+
44
+ async def configure_app(app: FastAPI, engine_config):
45
+ """Initialize the agent, MCP registry, guardrails, and app state with the given engine config."""
46
+ guardrails_obj = engine_config.guardrails
47
+ guardrails = _parse_guardrails(guardrails_obj) if guardrails_obj else []
48
+
49
+ print("guardrails: ", guardrails)
50
+
51
+ # # Initialize MCP Registry first
52
+ # mcp_registry = MCPClientRegistry(engine_config.mcp_servers)
53
+ # app.state.mcp_registry = mcp_registry
54
+
55
+ # Use ConfigBuilder's centralized agent initialization, passing the registry
56
+ try:
57
+ agent_instance = await ConfigBuilder.initialize_agent_from_config(engine_config)
58
+ except Exception as e:
59
+ raise ValueError(
60
+ f"Error retrieving agent instance from ConfigBuilder: {e}"
61
+ ) from e
62
+
63
+ app.state.agent = agent_instance
64
+ app.state.config = engine_config
65
+ app.state.engine_config = engine_config
66
+
67
+ app.state.guardrails = guardrails
68
+ agent_name = getattr(agent_instance, "name", "Unknown")
69
+ print(f"✅ Agent '{agent_name}' initialized and ready to serve!")
70
+
71
+ # Setup AGUI routes if the agent is a LangGraph agent
72
+ from ..agent.langgraph.langgraph import LanggraphAgent
73
+ from ..agent.adk.adk import AdkAgent
74
+ # from ..server.routers.agui import setup_agui_router
75
+
76
+ if isinstance(agent_instance, (LanggraphAgent, AdkAgent)):
77
+ try:
78
+ # compiled_graph = getattr(agent_instance, "agent_instance")
79
+ # app.state.copilotkit_agent = setup_agui_router(app, agent_instance) # TODO: agent_instance is a compiled graph (duplicate agent_instance name not clear)
80
+ app.state.copilotkit_agent = agent_instance.copilotkit_agent_instance
81
+ except Exception as e:
82
+ print(f"⚠️ Warning: Failed to setup AGUI routes: {e}")
83
+ # Continue even if AGUI setup fails
84
+
85
+ # if app.state.mcp_registry.enabled:
86
+ # servers = ", ".join(app.state.mcp_registry.available_servers())
87
+ # print(f"🔌 MCP servers ready: {servers}")
88
+
89
+
90
+ @asynccontextmanager
91
+ async def lifespan(app: FastAPI):
92
+ """FastAPI lifespan context to initialize and teardown the agent."""
93
+
94
+ # Load config and initialize agent on startup
95
+ print("Server starting up...")
96
+ if not app.state.engine_config:
97
+ raise ValueError("Error: No Engine configuration found.")
98
+
99
+ await configure_app(app, app.state.engine_config)
100
+
101
+ yield
102
+
103
+ # Clean up on shutdown
104
+ print("🔄 Idun Agent Engine shutting down...")
105
+ await cleanup_agent(app)
106
+ print("✅ Agent resources cleaned up successfully.")
@@ -0,0 +1,5 @@
1
+ """FastAPI routers for the engine service."""
2
+
3
+ from . import agent, base
4
+
5
+ __all__ = ["agent", "base"]