idun-agent-engine 0.2.7__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- idun_agent_engine/_version.py +1 -1
- idun_agent_engine/agent/adk/__init__.py +5 -0
- idun_agent_engine/agent/adk/adk.py +296 -0
- idun_agent_engine/agent/base.py +7 -1
- idun_agent_engine/agent/haystack/haystack.py +5 -1
- idun_agent_engine/agent/langgraph/langgraph.py +158 -55
- idun_agent_engine/core/app_factory.py +9 -0
- idun_agent_engine/core/config_builder.py +222 -21
- idun_agent_engine/core/engine_config.py +1 -2
- idun_agent_engine/core/server_runner.py +2 -3
- idun_agent_engine/guardrails/__init__.py +0 -0
- idun_agent_engine/guardrails/base.py +24 -0
- idun_agent_engine/guardrails/guardrails_hub/guardrails_hub.py +101 -0
- idun_agent_engine/guardrails/guardrails_hub/utils.py +1 -0
- idun_agent_engine/mcp/__init__.py +5 -0
- idun_agent_engine/mcp/helpers.py +97 -0
- idun_agent_engine/mcp/registry.py +109 -0
- idun_agent_engine/observability/__init__.py +6 -2
- idun_agent_engine/observability/base.py +73 -12
- idun_agent_engine/observability/gcp_logging/__init__.py +0 -0
- idun_agent_engine/observability/gcp_logging/gcp_logging_handler.py +52 -0
- idun_agent_engine/observability/gcp_trace/__init__.py +0 -0
- idun_agent_engine/observability/gcp_trace/gcp_trace_handler.py +116 -0
- idun_agent_engine/observability/langfuse/langfuse_handler.py +17 -10
- idun_agent_engine/server/dependencies.py +13 -1
- idun_agent_engine/server/lifespan.py +80 -16
- idun_agent_engine/server/routers/agent.py +135 -27
- idun_agent_engine/server/routers/agui.py +47 -0
- idun_agent_engine/server/routers/base.py +55 -1
- idun_agent_engine/templates/__init__.py +1 -0
- idun_agent_engine/templates/correction.py +65 -0
- idun_agent_engine/templates/deep_research.py +40 -0
- idun_agent_engine/templates/translation.py +70 -0
- {idun_agent_engine-0.2.7.dist-info → idun_agent_engine-0.3.1.dist-info}/METADATA +62 -10
- idun_agent_engine-0.3.1.dist-info/RECORD +60 -0
- {idun_agent_engine-0.2.7.dist-info → idun_agent_engine-0.3.1.dist-info}/WHEEL +1 -1
- idun_platform_cli/groups/agent/package.py +3 -3
- idun_platform_cli/groups/agent/serve.py +8 -5
- idun_agent_engine/cli/__init__.py +0 -16
- idun_agent_engine-0.2.7.dist-info/RECORD +0 -43
- {idun_agent_engine-0.2.7.dist-info → idun_agent_engine-0.3.1.dist-info}/entry_points.txt +0 -0
|
@@ -7,6 +7,7 @@ This approach ensures type safety, validation, and consistency with the rest of
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
+
from idun_agent_schema.engine.guardrails import Guardrails as GuardrailsV1
|
|
10
11
|
import yaml
|
|
11
12
|
from idun_agent_schema.engine.agent_framework import AgentFramework
|
|
12
13
|
from idun_agent_schema.engine.haystack import HaystackAgentConfig
|
|
@@ -14,8 +15,12 @@ from idun_agent_schema.engine.langgraph import (
|
|
|
14
15
|
LangGraphAgentConfig,
|
|
15
16
|
SqliteCheckpointConfig,
|
|
16
17
|
)
|
|
17
|
-
|
|
18
|
+
from idun_agent_schema.engine.adk import AdkAgentConfig
|
|
19
|
+
from idun_agent_schema.engine.mcp_server import MCPServer
|
|
20
|
+
from idun_agent_schema.engine.observability_v2 import ObservabilityConfig
|
|
21
|
+
from idun_agent_schema.engine.guardrails_v2 import GuardrailsV2 as Guardrails
|
|
18
22
|
from idun_agent_engine.server.server_config import ServerAPIConfig
|
|
23
|
+
from yaml import YAMLError
|
|
19
24
|
|
|
20
25
|
from ..agent.base import BaseAgent
|
|
21
26
|
from .engine_config import AgentConfig, EngineConfig, ServerConfig
|
|
@@ -44,6 +49,10 @@ class ConfigBuilder:
|
|
|
44
49
|
"""Initialize a new configuration builder with default values."""
|
|
45
50
|
self._server_config = ServerConfig()
|
|
46
51
|
self._agent_config: AgentConfig | None = None
|
|
52
|
+
# TODO: add mcp_servers config
|
|
53
|
+
self._mcp_servers: list[MCPServer] | None = None
|
|
54
|
+
self._observability: list[ObservabilityConfig] | None = None
|
|
55
|
+
self._guardrails: Guardrails | None = None
|
|
47
56
|
|
|
48
57
|
def with_api_port(self, port: int) -> "ConfigBuilder":
|
|
49
58
|
"""Set the API port for the server.
|
|
@@ -90,14 +99,59 @@ class ConfigBuilder:
|
|
|
90
99
|
|
|
91
100
|
headers = {"auth": f"Bearer {agent_api_key}"}
|
|
92
101
|
try:
|
|
93
|
-
|
|
102
|
+
print(f"Fetching config from {url + '/api/v1/agents/config'}")
|
|
103
|
+
response = requests.get(url=url + "/api/v1/agents/config", headers=headers)
|
|
94
104
|
if response.status_code != 200:
|
|
95
105
|
raise ValueError(
|
|
96
106
|
f"Error sending retrieving config from url. response : {response.json()}"
|
|
97
107
|
)
|
|
98
108
|
yaml_config = yaml.safe_load(response.text)
|
|
99
|
-
|
|
100
|
-
|
|
109
|
+
try:
|
|
110
|
+
self._server_config = yaml_config.get("engine_config", {}).get("server")
|
|
111
|
+
except Exception as e:
|
|
112
|
+
raise YAMLError(
|
|
113
|
+
f"Failed to parse yaml file for ServerConfig: {e}"
|
|
114
|
+
) from e
|
|
115
|
+
try:
|
|
116
|
+
self._agent_config = yaml_config.get("engine_config", {}).get("agent")
|
|
117
|
+
except Exception as e:
|
|
118
|
+
raise YAMLError(
|
|
119
|
+
f"Failed to parse yaml file for Engine config: {e}"
|
|
120
|
+
) from e
|
|
121
|
+
try:
|
|
122
|
+
guardrails = yaml_config.get("engine_config", {}).get("guardrails", "")
|
|
123
|
+
if not guardrails:
|
|
124
|
+
# self._guardrails = Guardrails(enabled=False)
|
|
125
|
+
self._guardrails = None
|
|
126
|
+
except Exception as e:
|
|
127
|
+
raise YAMLError(f"Failed to parse yaml file for Guardrails: {e}") from e
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
observability_list = yaml_config.get("engine_config", {}).get(
|
|
131
|
+
"observability"
|
|
132
|
+
)
|
|
133
|
+
if observability_list:
|
|
134
|
+
self._observability = [
|
|
135
|
+
ObservabilityConfig.model_validate(obs)
|
|
136
|
+
for obs in observability_list
|
|
137
|
+
]
|
|
138
|
+
else:
|
|
139
|
+
self._observability = None
|
|
140
|
+
except Exception as e:
|
|
141
|
+
raise YAMLError(
|
|
142
|
+
f"Failed to parse yaml file for Observability: {e}"
|
|
143
|
+
) from e
|
|
144
|
+
# try:
|
|
145
|
+
# mcp_servers_list = yaml_config.get("engine_config", {}).get("mcp_servers") or yaml_config.get("engine_config", {}).get("mcpServers") # TODO to fix camelcase issues
|
|
146
|
+
# if mcp_servers_list:
|
|
147
|
+
# self._mcp_servers = [
|
|
148
|
+
# MCPServer.model_validate(server) for server in mcp_servers_list
|
|
149
|
+
# ]
|
|
150
|
+
# else:
|
|
151
|
+
# self._mcp_servers = None
|
|
152
|
+
# except Exception as e:
|
|
153
|
+
# raise YAMLError(f"Failed to parse yaml file for MCP Servers: {e}") from e
|
|
154
|
+
|
|
101
155
|
return self
|
|
102
156
|
|
|
103
157
|
except Exception as e:
|
|
@@ -139,9 +193,13 @@ class ConfigBuilder:
|
|
|
139
193
|
langgraph_config = LangGraphAgentConfig.model_validate(agent_config_dict)
|
|
140
194
|
|
|
141
195
|
# Create the agent config (store as strongly-typed model, not dict)
|
|
142
|
-
self._agent_config = AgentConfig(
|
|
196
|
+
self._agent_config = AgentConfig(
|
|
197
|
+
type=AgentFramework.LANGGRAPH, config=langgraph_config
|
|
198
|
+
)
|
|
143
199
|
return self
|
|
144
200
|
|
|
201
|
+
# TODO: remove unused fns
|
|
202
|
+
|
|
145
203
|
def with_custom_agent(
|
|
146
204
|
self, agent_type: str, config: dict[str, Any]
|
|
147
205
|
) -> "ConfigBuilder":
|
|
@@ -160,12 +218,14 @@ class ConfigBuilder:
|
|
|
160
218
|
"""
|
|
161
219
|
if agent_type == AgentFramework.LANGGRAPH:
|
|
162
220
|
self._agent_config = AgentConfig(
|
|
163
|
-
type=AgentFramework.LANGGRAPH,
|
|
221
|
+
type=AgentFramework.LANGGRAPH,
|
|
222
|
+
config=LangGraphAgentConfig.model_validate(config),
|
|
164
223
|
)
|
|
165
224
|
|
|
166
225
|
elif agent_type == AgentFramework.HAYSTACK:
|
|
167
226
|
self._agent_config = AgentConfig(
|
|
168
|
-
type=AgentFramework.HAYSTACK,
|
|
227
|
+
type=AgentFramework.HAYSTACK,
|
|
228
|
+
config=HaystackAgentConfig.model_validate(config),
|
|
169
229
|
)
|
|
170
230
|
else:
|
|
171
231
|
raise ValueError(f"Unsupported agent type: {agent_type}")
|
|
@@ -186,7 +246,13 @@ class ConfigBuilder:
|
|
|
186
246
|
)
|
|
187
247
|
|
|
188
248
|
# Create and validate the complete configuration
|
|
189
|
-
return EngineConfig(
|
|
249
|
+
return EngineConfig(
|
|
250
|
+
server=self._server_config,
|
|
251
|
+
agent=self._agent_config,
|
|
252
|
+
guardrails=self._guardrails,
|
|
253
|
+
observability=self._observability,
|
|
254
|
+
mcp_servers=self._mcp_servers,
|
|
255
|
+
)
|
|
190
256
|
|
|
191
257
|
def build_dict(self) -> dict[str, Any]:
|
|
192
258
|
"""Build and return the configuration as a dictionary.
|
|
@@ -209,7 +275,9 @@ class ConfigBuilder:
|
|
|
209
275
|
with open(file_path, "w") as f:
|
|
210
276
|
yaml.dump(config, f, default_flow_style=False, indent=2)
|
|
211
277
|
|
|
212
|
-
async def build_and_initialize_agent(
|
|
278
|
+
async def build_and_initialize_agent(
|
|
279
|
+
self, mcp_registry: Any | None = None
|
|
280
|
+
) -> BaseAgent:
|
|
213
281
|
"""Build configuration and initialize the agent in one step.
|
|
214
282
|
|
|
215
283
|
Returns:
|
|
@@ -219,14 +287,19 @@ class ConfigBuilder:
|
|
|
219
287
|
ValueError: If agent type is unsupported or configuration is invalid
|
|
220
288
|
"""
|
|
221
289
|
engine_config = self.build()
|
|
222
|
-
return await self.initialize_agent_from_config(
|
|
290
|
+
return await self.initialize_agent_from_config(
|
|
291
|
+
engine_config, mcp_registry=mcp_registry
|
|
292
|
+
)
|
|
223
293
|
|
|
224
294
|
@staticmethod
|
|
225
|
-
async def initialize_agent_from_config(
|
|
295
|
+
async def initialize_agent_from_config(
|
|
296
|
+
engine_config: EngineConfig, mcp_registry: Any | None = None
|
|
297
|
+
) -> BaseAgent:
|
|
226
298
|
"""Initialize an agent instance from a validated EngineConfig.
|
|
227
299
|
|
|
228
300
|
Args:
|
|
229
301
|
engine_config: Validated configuration object
|
|
302
|
+
mcp_registry: Optional MCP registry client.
|
|
230
303
|
|
|
231
304
|
Returns:
|
|
232
305
|
BaseAgent: Initialized agent instance
|
|
@@ -235,14 +308,16 @@ class ConfigBuilder:
|
|
|
235
308
|
ValueError: If agent type is unsupported
|
|
236
309
|
"""
|
|
237
310
|
agent_config_obj = engine_config.agent.config
|
|
238
|
-
print("CONFIG:", agent_config_obj)
|
|
239
311
|
agent_type = engine_config.agent.type
|
|
240
|
-
|
|
312
|
+
observability_config = engine_config.observability
|
|
313
|
+
# mcp_servers = engine_config.mcp_servers
|
|
241
314
|
# Initialize the appropriate agent
|
|
242
315
|
agent_instance = None
|
|
243
316
|
if agent_type == AgentFramework.LANGGRAPH:
|
|
244
317
|
from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
|
|
318
|
+
import os
|
|
245
319
|
|
|
320
|
+
print("Current directory: ", os.getcwd()) # TODO remove
|
|
246
321
|
try:
|
|
247
322
|
validated_config = LangGraphAgentConfig.model_validate(agent_config_obj)
|
|
248
323
|
|
|
@@ -253,6 +328,91 @@ class ConfigBuilder:
|
|
|
253
328
|
|
|
254
329
|
agent_instance = LanggraphAgent()
|
|
255
330
|
|
|
331
|
+
elif agent_type == AgentFramework.TRANSLATION_AGENT:
|
|
332
|
+
from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
|
|
333
|
+
from idun_agent_schema.engine.templates import TranslationAgentConfig
|
|
334
|
+
import os
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
translation_config = TranslationAgentConfig.model_validate(
|
|
338
|
+
agent_config_obj
|
|
339
|
+
)
|
|
340
|
+
except Exception as e:
|
|
341
|
+
raise ValueError(
|
|
342
|
+
f"Cannot validate into a TranslationAgentConfig model. Got {agent_config_obj}"
|
|
343
|
+
) from e
|
|
344
|
+
|
|
345
|
+
# Configure environment for the template
|
|
346
|
+
os.environ["TRANSLATION_MODEL"] = translation_config.model_name
|
|
347
|
+
os.environ["TRANSLATION_SOURCE_LANG"] = translation_config.source_lang
|
|
348
|
+
os.environ["TRANSLATION_TARGET_LANG"] = translation_config.target_lang
|
|
349
|
+
|
|
350
|
+
# Create LangGraph config for the template
|
|
351
|
+
validated_config = LangGraphAgentConfig(
|
|
352
|
+
name=translation_config.name,
|
|
353
|
+
graph_definition="idun_agent_engine.templates.translation:graph",
|
|
354
|
+
input_schema_definition=translation_config.input_schema_definition,
|
|
355
|
+
output_schema_definition=translation_config.output_schema_definition,
|
|
356
|
+
observability=translation_config.observability,
|
|
357
|
+
checkpointer=translation_config.checkpointer,
|
|
358
|
+
)
|
|
359
|
+
agent_instance = LanggraphAgent()
|
|
360
|
+
|
|
361
|
+
elif agent_type == AgentFramework.CORRECTION_AGENT:
|
|
362
|
+
from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
|
|
363
|
+
from idun_agent_schema.engine.templates import CorrectionAgentConfig
|
|
364
|
+
import os
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
correction_config = CorrectionAgentConfig.model_validate(
|
|
368
|
+
agent_config_obj
|
|
369
|
+
)
|
|
370
|
+
except Exception as e:
|
|
371
|
+
raise ValueError(
|
|
372
|
+
f"Cannot validate into a CorrectionAgentConfig model. Got {agent_config_obj}"
|
|
373
|
+
) from e
|
|
374
|
+
|
|
375
|
+
os.environ["CORRECTION_MODEL"] = correction_config.model_name
|
|
376
|
+
os.environ["CORRECTION_LANGUAGE"] = correction_config.language
|
|
377
|
+
|
|
378
|
+
validated_config = LangGraphAgentConfig(
|
|
379
|
+
name=correction_config.name,
|
|
380
|
+
graph_definition="idun_agent_engine.templates.correction:graph",
|
|
381
|
+
input_schema_definition=correction_config.input_schema_definition,
|
|
382
|
+
output_schema_definition=correction_config.output_schema_definition,
|
|
383
|
+
observability=correction_config.observability,
|
|
384
|
+
checkpointer=correction_config.checkpointer,
|
|
385
|
+
)
|
|
386
|
+
agent_instance = LanggraphAgent()
|
|
387
|
+
|
|
388
|
+
elif agent_type == AgentFramework.DEEP_RESEARCH_AGENT:
|
|
389
|
+
from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
|
|
390
|
+
from idun_agent_schema.engine.templates import DeepResearchAgentConfig
|
|
391
|
+
import os
|
|
392
|
+
|
|
393
|
+
try:
|
|
394
|
+
deep_research_config = DeepResearchAgentConfig.model_validate(
|
|
395
|
+
agent_config_obj
|
|
396
|
+
)
|
|
397
|
+
except Exception as e:
|
|
398
|
+
raise ValueError(
|
|
399
|
+
f"Cannot validate into a DeepResearchAgentConfig model. Got {agent_config_obj}"
|
|
400
|
+
) from e
|
|
401
|
+
|
|
402
|
+
os.environ["DEEP_RESEARCH_MODEL"] = deep_research_config.model_name
|
|
403
|
+
os.environ["DEEP_RESEARCH_PROMPT"] = deep_research_config.system_prompt
|
|
404
|
+
os.environ["TAVILY_API_KEY"] = deep_research_config.tavily_api_key
|
|
405
|
+
|
|
406
|
+
validated_config = LangGraphAgentConfig(
|
|
407
|
+
name=deep_research_config.name,
|
|
408
|
+
graph_definition="idun_agent_engine.templates.deep_research:graph",
|
|
409
|
+
input_schema_definition=deep_research_config.input_schema_definition,
|
|
410
|
+
output_schema_definition=deep_research_config.output_schema_definition,
|
|
411
|
+
observability=deep_research_config.observability,
|
|
412
|
+
checkpointer=deep_research_config.checkpointer,
|
|
413
|
+
)
|
|
414
|
+
agent_instance = LanggraphAgent()
|
|
415
|
+
|
|
256
416
|
elif agent_type == AgentFramework.HAYSTACK:
|
|
257
417
|
from idun_agent_engine.agent.haystack.haystack import HaystackAgent
|
|
258
418
|
|
|
@@ -264,11 +424,24 @@ class ConfigBuilder:
|
|
|
264
424
|
f"Cannot validate into a HaystackAgentConfig model. Got {agent_config_obj}"
|
|
265
425
|
) from e
|
|
266
426
|
agent_instance = HaystackAgent()
|
|
427
|
+
elif agent_type == AgentFramework.ADK:
|
|
428
|
+
from idun_agent_engine.agent.adk.adk import AdkAgent
|
|
429
|
+
|
|
430
|
+
try:
|
|
431
|
+
validated_config = AdkAgentConfig.model_validate(agent_config_obj)
|
|
432
|
+
except Exception as e:
|
|
433
|
+
raise ValueError(
|
|
434
|
+
f"Cannot validate into a AdkAgentConfig model. Got {agent_config_obj}"
|
|
435
|
+
) from e
|
|
436
|
+
agent_instance = AdkAgent()
|
|
267
437
|
else:
|
|
268
438
|
raise ValueError(f"Unsupported agent type: {agent_type}")
|
|
269
439
|
|
|
270
440
|
# Initialize the agent with its configuration
|
|
271
|
-
await agent_instance.initialize(
|
|
441
|
+
await agent_instance.initialize(
|
|
442
|
+
validated_config,
|
|
443
|
+
observability_config, # , mcp_registry=mcp_registry
|
|
444
|
+
) # type: ignore[arg-type]
|
|
272
445
|
return agent_instance
|
|
273
446
|
|
|
274
447
|
@staticmethod
|
|
@@ -284,7 +457,13 @@ class ConfigBuilder:
|
|
|
284
457
|
Raises:
|
|
285
458
|
ValueError: If agent type is unsupported
|
|
286
459
|
"""
|
|
287
|
-
if
|
|
460
|
+
if (
|
|
461
|
+
agent_type == "langgraph"
|
|
462
|
+
or agent_type == AgentFramework.LANGGRAPH
|
|
463
|
+
or agent_type == AgentFramework.TRANSLATION_AGENT
|
|
464
|
+
or agent_type == AgentFramework.CORRECTION_AGENT
|
|
465
|
+
or agent_type == AgentFramework.DEEP_RESEARCH_AGENT
|
|
466
|
+
):
|
|
288
467
|
from ..agent.langgraph.langgraph import LanggraphAgent
|
|
289
468
|
|
|
290
469
|
return LanggraphAgent
|
|
@@ -311,10 +490,21 @@ class ConfigBuilder:
|
|
|
311
490
|
if agent_type == "langgraph":
|
|
312
491
|
validated_config = LangGraphAgentConfig.model_validate(config)
|
|
313
492
|
return validated_config.model_dump()
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
493
|
+
elif agent_type == AgentFramework.TRANSLATION_AGENT:
|
|
494
|
+
from idun_agent_schema.engine.templates import TranslationAgentConfig
|
|
495
|
+
|
|
496
|
+
validated_config = TranslationAgentConfig.model_validate(config)
|
|
497
|
+
return validated_config.model_dump()
|
|
498
|
+
elif agent_type == AgentFramework.CORRECTION_AGENT:
|
|
499
|
+
from idun_agent_schema.engine.templates import CorrectionAgentConfig
|
|
500
|
+
|
|
501
|
+
validated_config = CorrectionAgentConfig.model_validate(config)
|
|
502
|
+
return validated_config.model_dump()
|
|
503
|
+
elif agent_type == AgentFramework.DEEP_RESEARCH_AGENT:
|
|
504
|
+
from idun_agent_schema.engine.templates import DeepResearchAgentConfig
|
|
505
|
+
|
|
506
|
+
validated_config = DeepResearchAgentConfig.model_validate(config)
|
|
507
|
+
return validated_config.model_dump()
|
|
318
508
|
else:
|
|
319
509
|
raise ValueError(f"Unsupported agent type: {agent_type}")
|
|
320
510
|
|
|
@@ -345,17 +535,21 @@ class ConfigBuilder:
|
|
|
345
535
|
@staticmethod
|
|
346
536
|
async def load_and_initialize_agent(
|
|
347
537
|
config_path: str = "config.yaml",
|
|
538
|
+
mcp_registry: Any | None = None,
|
|
348
539
|
) -> tuple[EngineConfig, BaseAgent]:
|
|
349
540
|
"""Load configuration and initialize agent in one step.
|
|
350
541
|
|
|
351
542
|
Args:
|
|
352
543
|
config_path: Path to the configuration YAML file
|
|
544
|
+
mcp_registry: Optional MCP registry client.
|
|
353
545
|
|
|
354
546
|
Returns:
|
|
355
547
|
tuple[EngineConfig, BaseAgent]: Configuration and initialized agent
|
|
356
548
|
"""
|
|
357
549
|
engine_config = ConfigBuilder.load_from_file(config_path)
|
|
358
|
-
agent = await ConfigBuilder.initialize_agent_from_config(
|
|
550
|
+
agent = await ConfigBuilder.initialize_agent_from_config(
|
|
551
|
+
engine_config, mcp_registry=mcp_registry
|
|
552
|
+
)
|
|
359
553
|
return engine_config, agent
|
|
360
554
|
|
|
361
555
|
@staticmethod
|
|
@@ -364,6 +558,7 @@ class ConfigBuilder:
|
|
|
364
558
|
config_dict: dict[str, Any] | None = None,
|
|
365
559
|
engine_config: EngineConfig | None = None,
|
|
366
560
|
) -> EngineConfig:
|
|
561
|
+
print(config_dict)
|
|
367
562
|
"""Umbrella function to resolve configuration from various sources.
|
|
368
563
|
|
|
369
564
|
This function handles all the different ways configuration can be provided
|
|
@@ -424,7 +619,9 @@ class ConfigBuilder:
|
|
|
424
619
|
builder = cls()
|
|
425
620
|
builder._server_config = engine_config.server
|
|
426
621
|
builder._agent_config = engine_config.agent
|
|
427
|
-
|
|
622
|
+
builder._guardrails = engine_config.guardrails
|
|
623
|
+
builder._observability = engine_config.observability
|
|
624
|
+
builder._mcp_servers = engine_config.mcp_servers
|
|
428
625
|
return builder
|
|
429
626
|
|
|
430
627
|
@classmethod
|
|
@@ -453,4 +650,8 @@ class ConfigBuilder:
|
|
|
453
650
|
builder = cls()
|
|
454
651
|
builder._server_config = engine_config.server
|
|
455
652
|
builder._agent_config = engine_config.agent
|
|
653
|
+
builder._guardrails = engine_config.guardrails
|
|
654
|
+
builder._observability = engine_config.observability
|
|
655
|
+
builder._mcp_servers = engine_config.mcp_servers
|
|
656
|
+
|
|
456
657
|
return builder
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"""Compatibility re-exports for Engine configuration models."""
|
|
2
2
|
|
|
3
|
-
from idun_agent_schema.engine.agent import BaseAgentConfig # noqa: F401
|
|
4
3
|
from idun_agent_schema.engine.agent import ( # noqa: F401
|
|
5
4
|
AgentConfig,
|
|
5
|
+
BaseAgentConfig, # noqa: F401
|
|
6
6
|
)
|
|
7
|
-
|
|
8
7
|
from idun_agent_schema.engine.engine import ( # noqa: F401
|
|
9
8
|
EngineConfig,
|
|
10
9
|
)
|
|
@@ -41,7 +41,7 @@ def run_server(
|
|
|
41
41
|
# Run in production mode
|
|
42
42
|
run_server(app, workers=4)
|
|
43
43
|
"""
|
|
44
|
-
print(f"🌐 Starting Idun Agent Engine server on http://{host}:{port}")
|
|
44
|
+
print(f"🌐 Starting Idun Agent Engine server on http://{host}:{port}...")
|
|
45
45
|
print(f"📚 API documentation available at http://{host}:{port}/docs")
|
|
46
46
|
|
|
47
47
|
if reload and workers:
|
|
@@ -50,13 +50,12 @@ def run_server(
|
|
|
50
50
|
)
|
|
51
51
|
reload = False
|
|
52
52
|
|
|
53
|
+
print("Config: ", app.state.engine_config)
|
|
53
54
|
uvicorn.run(
|
|
54
55
|
app,
|
|
55
56
|
host=host,
|
|
56
57
|
port=port,
|
|
57
|
-
# reload=reload,
|
|
58
58
|
log_level=log_level,
|
|
59
|
-
# workers=workers
|
|
60
59
|
)
|
|
61
60
|
|
|
62
61
|
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from idun_agent_schema.engine.guardrails import Guardrail
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseGuardrail(ABC):
|
|
8
|
+
"""Base class for different guardrail providers."""
|
|
9
|
+
|
|
10
|
+
# TODO: output
|
|
11
|
+
|
|
12
|
+
def __init__(self, config: Guardrail) -> None:
|
|
13
|
+
if not isinstance(config, Guardrail):
|
|
14
|
+
raise TypeError(
|
|
15
|
+
f"The Guardrail must be a `Guardrail` schema type, received instead: {type(config)}"
|
|
16
|
+
)
|
|
17
|
+
self._guardrail_config = config
|
|
18
|
+
# config for the specific guardrails type. currently, can only be guardrails_hub config
|
|
19
|
+
self._instance_config: dict[str, Any] = None
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def validate(self, input: str) -> bool:
|
|
23
|
+
"""Used for validating user input, or LLM output."""
|
|
24
|
+
pass
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Guardrails."""
|
|
2
|
+
|
|
3
|
+
from guardrails import Guard
|
|
4
|
+
from idun_agent_schema.engine.guardrails import Guardrail as GuardrailSchema
|
|
5
|
+
from idun_agent_schema.engine.guardrails_type import (
|
|
6
|
+
GuardrailType,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from ..base import BaseGuardrail
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_guard_instance(name: str) -> Guard:
|
|
13
|
+
"""Returns a map of guard type -> guard instance."""
|
|
14
|
+
if name == "BAN_LIST":
|
|
15
|
+
from guardrails.hub import BanList
|
|
16
|
+
|
|
17
|
+
return BanList
|
|
18
|
+
|
|
19
|
+
elif name == "NSFW":
|
|
20
|
+
from guardrails.hub import NSFWText
|
|
21
|
+
|
|
22
|
+
return NSFWText
|
|
23
|
+
|
|
24
|
+
elif name == "COMPETITOR_CHECK":
|
|
25
|
+
from guardrails.hub import CompetitorCheck
|
|
26
|
+
|
|
27
|
+
return CompetitorCheck
|
|
28
|
+
|
|
29
|
+
else:
|
|
30
|
+
raise ValueError(f"Guard {name} not found.")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class GuardrailsHubGuard(BaseGuardrail):
|
|
34
|
+
"""Class for managing guardrails from `guardrailsai`'s hub."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, config: GuardrailSchema, position: str) -> None:
|
|
37
|
+
super().__init__(config)
|
|
38
|
+
|
|
39
|
+
self._guard_type = self._guardrail_config.type
|
|
40
|
+
self._guard_config = self._guardrail_config.config
|
|
41
|
+
|
|
42
|
+
if self._guard_type == GuardrailType.GUARDRAILS_HUB:
|
|
43
|
+
self._guard_url = self._guardrail_config.config["guard_url"]
|
|
44
|
+
|
|
45
|
+
self.reject_message: str = self._guard_config["reject_message"]
|
|
46
|
+
self._install_model()
|
|
47
|
+
self._guard: Guard | None = self.setup_guard()
|
|
48
|
+
self.position: str = position
|
|
49
|
+
|
|
50
|
+
def _install_model(self) -> None:
|
|
51
|
+
import subprocess
|
|
52
|
+
|
|
53
|
+
from guardrails import install
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
api_key = self._guardrail_config.config["api_key"]
|
|
57
|
+
subprocess.run(
|
|
58
|
+
[
|
|
59
|
+
"guardrails",
|
|
60
|
+
"configure",
|
|
61
|
+
"--token",
|
|
62
|
+
api_key,
|
|
63
|
+
"--disable-remote-inferencing", # TODO: maybe provide this as feat
|
|
64
|
+
"--disable-metrics",
|
|
65
|
+
],
|
|
66
|
+
check=True,
|
|
67
|
+
)
|
|
68
|
+
print(f"Installing model: {self._guard_url}..")
|
|
69
|
+
install(self._guard_url, quiet=True, install_local_models=True)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
raise OSError(f"Cannot install model {self._guard_url}: {e}") from e
|
|
72
|
+
|
|
73
|
+
def setup_guard(self) -> Guard | None:
|
|
74
|
+
"""Installs and configures the guard based on its yaml config."""
|
|
75
|
+
if self._guard_type == GuardrailType.GUARDRAILS_HUB:
|
|
76
|
+
self._install_model()
|
|
77
|
+
guard_name = self._guardrail_config.config.get("guard")
|
|
78
|
+
guard = get_guard_instance(guard_name)
|
|
79
|
+
if guard is None:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"Guard: {self.guard_type} is not yet supported, or does not exist."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
guard_instance_params = self._guardrail_config.config.get(
|
|
85
|
+
"guard_config", {}
|
|
86
|
+
)
|
|
87
|
+
guard_instance = guard(**guard_instance_params)
|
|
88
|
+
for param, value in self._guardrail_config.config["guard_config"].items():
|
|
89
|
+
setattr(guard, param, value)
|
|
90
|
+
return guard_instance
|
|
91
|
+
elif self._guard_type == GuardrailType.CUSTOM_LLM:
|
|
92
|
+
raise NotImplementedError("Support for CUSTOM_LLM not yet provided.")
|
|
93
|
+
|
|
94
|
+
def validate(self, input: str) -> bool:
|
|
95
|
+
"""TODO."""
|
|
96
|
+
main_guard = Guard().use(self._guard)
|
|
97
|
+
try:
|
|
98
|
+
main_guard.validate(input)
|
|
99
|
+
return True
|
|
100
|
+
except Exception:
|
|
101
|
+
return False
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Utils module."""
|
|
@@ -0,0 +1,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()
|