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.
- idun_agent_engine/_version.py +1 -1
- idun_agent_engine/agent/adk/adk.py +7 -4
- idun_agent_engine/agent/haystack/__init__.py +0 -2
- idun_agent_engine/agent/haystack/haystack.py +9 -5
- idun_agent_engine/agent/langgraph/langgraph.py +10 -13
- idun_agent_engine/core/config_builder.py +33 -13
- idun_agent_engine/guardrails/guardrails_hub/guardrails_hub.py +52 -9
- idun_agent_engine/mcp/__init__.py +2 -2
- idun_agent_engine/mcp/helpers.py +53 -15
- idun_agent_engine/mcp/registry.py +5 -5
- idun_agent_engine/observability/base.py +11 -2
- idun_agent_engine/observability/gcp_trace/gcp_trace_handler.py +3 -1
- idun_agent_engine/observability/langfuse/langfuse_handler.py +1 -3
- idun_agent_engine/server/dependencies.py +7 -2
- idun_agent_engine/server/lifespan.py +2 -7
- idun_agent_engine/server/routers/agent.py +2 -1
- idun_agent_engine/server/routers/base.py +7 -5
- idun_agent_engine/telemetry/__init__.py +0 -1
- idun_agent_engine/telemetry/config.py +0 -1
- idun_agent_engine/telemetry/telemetry.py +3 -4
- idun_agent_engine/templates/correction.py +4 -7
- idun_agent_engine/templates/deep_research.py +1 -0
- idun_agent_engine/templates/translation.py +4 -4
- {idun_agent_engine-0.4.0.dist-info → idun_agent_engine-0.4.2.dist-info}/METADATA +2 -2
- idun_agent_engine-0.4.2.dist-info/RECORD +86 -0
- idun_platform_cli/groups/agent/package.py +4 -1
- idun_platform_cli/groups/agent/serve.py +2 -0
- idun_platform_cli/groups/init.py +2 -0
- idun_platform_cli/telemetry.py +55 -0
- idun_platform_cli/tui/css/create_agent.py +137 -14
- idun_platform_cli/tui/css/main.py +7 -10
- idun_platform_cli/tui/main.py +3 -3
- idun_platform_cli/tui/schemas/create_agent.py +8 -4
- idun_platform_cli/tui/screens/create_agent.py +186 -20
- idun_platform_cli/tui/utils/config.py +23 -2
- idun_platform_cli/tui/validators/guardrails.py +20 -6
- idun_platform_cli/tui/validators/mcps.py +9 -6
- idun_platform_cli/tui/widgets/__init__.py +8 -4
- idun_platform_cli/tui/widgets/chat_widget.py +155 -0
- idun_platform_cli/tui/widgets/guardrails_widget.py +12 -4
- idun_platform_cli/tui/widgets/identity_widget.py +28 -10
- idun_platform_cli/tui/widgets/mcps_widget.py +113 -25
- idun_platform_cli/tui/widgets/memory_widget.py +194 -0
- idun_platform_cli/tui/widgets/observability_widget.py +12 -14
- idun_platform_cli/tui/widgets/serve_widget.py +50 -47
- idun_agent_engine/agent/haystack/haystack_model.py +0 -13
- idun_agent_engine/guardrails/guardrails_hub/utils.py +0 -1
- idun_agent_engine/server/routers/agui.py +0 -47
- idun_agent_engine-0.4.0.dist-info/RECORD +0 -86
- {idun_agent_engine-0.4.0.dist-info → idun_agent_engine-0.4.2.dist-info}/WHEEL +0 -0
- {idun_agent_engine-0.4.0.dist-info → idun_agent_engine-0.4.2.dist-info}/entry_points.txt +0 -0
idun_agent_engine/_version.py
CHANGED
|
@@ -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
|
|
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(
|
|
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()
|
|
@@ -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(
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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):
|
|
206
|
-
|
|
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
|
-
|
|
256
|
-
|
|
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(
|
|
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
|
-
|
|
281
|
+
engine_model = self.build()
|
|
280
282
|
with open(file_path, "w") as f:
|
|
281
|
-
yaml.dump(
|
|
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
|
|
12
|
+
if name == GuardrailConfigId.BAN_LIST:
|
|
13
13
|
from guardrails.hub import BanList
|
|
14
14
|
|
|
15
15
|
return BanList
|
|
16
16
|
|
|
17
|
-
elif name
|
|
17
|
+
elif name == GuardrailConfigId.DETECT_PII:
|
|
18
18
|
from guardrails.hub import DetectPII
|
|
19
19
|
|
|
20
20
|
return DetectPII
|
|
21
21
|
|
|
22
|
-
elif name
|
|
22
|
+
elif name == GuardrailConfigId.NSFW_TEXT:
|
|
23
23
|
from guardrails.hub import NSFWText
|
|
24
24
|
|
|
25
25
|
return NSFWText
|
|
26
26
|
|
|
27
|
-
elif name
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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",
|
idun_agent_engine/mcp/helpers.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
from typing import Any
|
|
3
|
-
|
|
4
|
+
|
|
4
5
|
import requests
|
|
5
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|