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,274 @@
1
+ import importlib.util
2
+ import logging
3
+ import os
4
+ import uuid
5
+ from typing import Any
6
+
7
+ os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
8
+
9
+ from haystack import Pipeline
10
+ from haystack.components.agents import Agent
11
+ from haystack.dataclasses import ChatMessage
12
+ from haystack_integrations.components.connectors.langfuse import LangfuseConnector
13
+
14
+ from idun_agent_engine.agent.base import BaseAgent
15
+ from idun_agent_schema.engine.haystack import HaystackAgentConfig
16
+ from idun_agent_engine.agent.haystack.utils import _parse_component_definition
17
+
18
+ logging.basicConfig(
19
+ format="%(asctime)s %(levelname)-8s %(message)s",
20
+ level=logging.INFO,
21
+ datefmt="%Y-%m-%d %H:%M:%S",
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class HaystackAgent(BaseAgent):
28
+ """Haystack agent adapter implementing the BaseAgent protocol."""
29
+
30
+ def __init__(self):
31
+ """Initialize an unconfigured haystack agent with default state."""
32
+ self._id: str = str(uuid.uuid4())
33
+ self._agent_type: str = "haystack"
34
+ self._agent_instance: Any = None
35
+ self._configuration: HaystackAgentConfig | None = None
36
+ self._name: str = "Haystack Agent"
37
+ self._langfuse_tracing: bool = False
38
+ self._enable_tracing: bool = False
39
+ self._infos: dict[str, Any] = {
40
+ "status": "Uninitialized",
41
+ "name": self._name,
42
+ "id": self._id,
43
+ }
44
+ # TODO: input/output schema
45
+ # TODO: checkpointing/debugging
46
+
47
+ @property
48
+ def id(self) -> str:
49
+ """Returns the agent id."""
50
+ return self._id
51
+
52
+ @property
53
+ def agent_type(self) -> str:
54
+ """Return agent type label."""
55
+ return self._agent_type
56
+
57
+ @property
58
+ def name(self) -> str:
59
+ """Return configured human-readable agent name."""
60
+ return self._name
61
+
62
+ @property
63
+ def agent_instance(self) -> Any:
64
+ """Return compiled graph instance.
65
+
66
+ Raises:
67
+ RuntimeError: If the agent is not yet initialized.
68
+ """
69
+ if self._agent_instance is None:
70
+ raise RuntimeError("Agent not initialized. Call initialize() first.")
71
+ return self._agent_instance
72
+
73
+ @property
74
+ def copilotkit_agent_instance(self) -> Any:
75
+ """Return the CopilotKit agent instance.
76
+
77
+ Raises:
78
+ RuntimeError: If the CopilotKit agent is not yet initialized.
79
+ """
80
+ raise NotImplementedError("CopilotKit agent instance not supported yet for Haystack agent.")
81
+
82
+ @property
83
+ def configuration(self) -> HaystackAgentConfig:
84
+ """Return validated configuration.
85
+
86
+ Raises:
87
+ RuntimeError: If the agent has not been configured yet.
88
+ """
89
+ if not self._configuration:
90
+ raise RuntimeError("Agent not configured. Call initialize() first.")
91
+ return self._configuration
92
+
93
+ @property
94
+ def infos(self) -> dict[str, Any]:
95
+ """Return diagnostic information about the agent instance."""
96
+ return self._infos
97
+
98
+ def _check_langfuse_tracing(self, pipeline: Pipeline) -> None:
99
+ """Check if the pipeline has a LangfuseConnector."""
100
+ logger.debug("Searching LangfuseConnector in the pipeline..")
101
+ for name, component in pipeline.walk():
102
+ if isinstance(component, LangfuseConnector):
103
+ logger.info(f"Found LangfuseConnector component with name: {name}")
104
+ self._langfuse_tracing = True
105
+
106
+ def _add_langfuse_tracing(self, component: Agent | Pipeline):
107
+ logger.debug("Checking for Langfuse tracing...")
108
+ if isinstance(component, Pipeline):
109
+ if self._langfuse_tracing:
110
+ logger.info("langfuse tracing already on")
111
+ elif not self._langfuse_tracing and self._enable_tracing:
112
+ logger.info("Pipeline has no tracer included. Adding Langfuse tracer")
113
+ if (
114
+ not os.environ.get("LANGFUSE_API_KEY")
115
+ or not os.environ.get("LANGFUSE_SECRET_KEY")
116
+ or not os.environ.get("LANGFUSE_PUBLIC_KEY")
117
+ ):
118
+ raise ValueError(
119
+ "Langfuse keys not set! make sure you set Langfuse secret and public keys"
120
+ )
121
+ component.add_component(
122
+ f"{self._configuration.name} tracer",
123
+ instance=LangfuseConnector(self._configuration.name),
124
+ )
125
+ logger.info("Added component tracer")
126
+ self._langfuse_tracing = True
127
+ logger.info("Agent tracing not supported yet")
128
+
129
+ async def initialize(
130
+ self,
131
+ config: HaystackAgentConfig | dict[str, Any],
132
+ observability_config: list[ObservabilityConfig] | None = None,
133
+ ) -> None:
134
+ try:
135
+ logger.debug(f"Initializing haystack agent config: {config}...")
136
+
137
+ if isinstance(config, HaystackAgentConfig):
138
+ self._configuration = config
139
+ logger.debug("Validated HaystackAgentConfig")
140
+ else:
141
+ logger.warning(f"Validating a dict config: {config}")
142
+ self._configuration = HaystackAgentConfig.model_validate(config)
143
+ logger.debug("Validated dict config")
144
+ self._name = self._configuration.name or "Haystack Agent"
145
+ self._infos["name"] = self._name
146
+ # TODO: await persistence haystack
147
+ # TODO OBS block
148
+
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
+ self._enable_tracing = True
153
+ logger.info("Enabling tracing...")
154
+ component: Agent | Pipeline = self._load_component(
155
+ self._configuration.component_definition
156
+ )
157
+ self._infos["component_type"] = self._configuration.component_type
158
+ self._infos["component_definition"] = (
159
+ self._configuration.component_definition
160
+ )
161
+ self._agent_instance = component
162
+ # TODO: input output schema definition
163
+ self._infos["status"] = "initialized"
164
+ logger.info("Status initialized!")
165
+ self._infos["config_used"] = self._configuration.model_dump()
166
+ except Exception as e:
167
+ logger.error(f"Failed to initialize HaystackAgent: {e}")
168
+ raise
169
+
170
+ def _fetch_component_from_module(self) -> Agent | Pipeline:
171
+ """Fetches the variable that holds the component of an Agent/Pipeline.
172
+
173
+ Returns: Agent | Pipeline.
174
+ """
175
+ module_path, component_variable_name = _parse_component_definition(
176
+ self._configuration.component_definition
177
+ )
178
+ logger.debug(
179
+ f"Importing spec from file location: {self._configuration.component_definition}"
180
+ )
181
+ try:
182
+ spec = importlib.util.spec_from_file_location(
183
+ component_variable_name, module_path
184
+ )
185
+ if spec is None or spec.loader is None:
186
+ logger.error(f"Could not load spec for module at {module_path}")
187
+ raise ImportError(f"Could not load spec for module at {module_path}")
188
+
189
+ module = importlib.util.module_from_spec(spec)
190
+ logger.debug("Execing module..")
191
+ spec.loader.exec_module(module)
192
+ logger.debug("Module executed")
193
+
194
+ component_variable = getattr(module, component_variable_name)
195
+ logger.info(f"Found component variable: {component_variable}")
196
+
197
+ component = getattr(module, component_variable_name)
198
+
199
+ if not isinstance(component, (Pipeline, Agent)):
200
+ raise TypeError(
201
+ f"The variable '{component_variable_name}' from {module_path} is not a Pipeline or Agent instance. Got {type(component)}"
202
+ )
203
+
204
+ return component
205
+
206
+ except Exception as e:
207
+ raise ValueError(
208
+ f"Invalid component definition string: {self._configuration.component_definition}. Error: {e}"
209
+ ) from e
210
+
211
+ def _load_component(self, component_definition: str) -> Agent | Pipeline:
212
+ """Loads a Haystack component (Agent or Pipeline) from the path (component definition) and returns the agent_instance with langfuse tracing."""
213
+ logger.debug(f"Loading component from: {component_definition}...")
214
+
215
+ component = self._fetch_component_from_module()
216
+ if self._enable_tracing:
217
+ try:
218
+ self._add_langfuse_tracing(component)
219
+ except (FileNotFoundError, ImportError, AttributeError) as e:
220
+ raise ValueError(
221
+ f"Failed to load agent from {component_definition}: {e}"
222
+ ) from e
223
+
224
+ return component
225
+ else:
226
+ logger.debug("User wants tracing disabled. Skipping..")
227
+ return component
228
+
229
+ async def invoke(self, message: Any) -> Any:
230
+ """Process a single input to chat with the agent.The message should be a dictionary containing 'query' and 'session_id'."""
231
+ # TODO: validate actual message
232
+ # TODO: validate input schema
233
+ logger.debug(f"Invoking pipeline for message: {message}")
234
+ if self._agent_instance is None:
235
+ raise RuntimeError(
236
+ "Agent not initialized. Call initialize() before processing messages."
237
+ )
238
+
239
+ if (
240
+ not isinstance(message, dict)
241
+ or "query" not in message
242
+ or "session_id" not in message
243
+ ):
244
+ raise ValueError(
245
+ "Message must be a dictionary with 'query' and 'session_id' keys."
246
+ )
247
+
248
+ try:
249
+ # TODO: support async
250
+ # if pipeline
251
+ if isinstance(self._agent_instance, Pipeline):
252
+ logger.debug("Running Pipeline instance...")
253
+ raw_result = self._agent_instance.run(data={"query": message["query"]})
254
+ result = raw_result["generator"]["replies"][0]
255
+ logger.info(f"Pipeline answer: {result}")
256
+ return result
257
+
258
+ # if agent
259
+ elif isinstance(self._agent_instance, Agent):
260
+ logger.debug("Running Agent instance...")
261
+ raw_result = self._agent_instance.run(
262
+ # TODO: make run method arguments based on component type
263
+ messages=[ChatMessage.from_user(message["query"])]
264
+ ) # TODO: from input schema
265
+ logger.info(f"Pipeline answer: {raw_result['messages'][-1].text}")
266
+ result = raw_result["messages"][-1].text
267
+ return result
268
+
269
+ # TODO: validates with output schema, and not hardcodded
270
+ except Exception as e:
271
+ raise RuntimeError(f"Pipeline execution failed: {e}") from e
272
+
273
+ async def stream(self, message: Any) -> Any:
274
+ pass
@@ -0,0 +1,13 @@
1
+ """Configuration models for Haystack agents."""
2
+
3
+ from typing import Literal
4
+
5
+ from idun_agent_engine.core.engine_config import BaseAgentConfig
6
+
7
+
8
+ class HaystackAgentConfig(BaseAgentConfig):
9
+ """Configuration model for Haystack Agents."""
10
+
11
+ type: Literal["haystack"] = "haystack"
12
+ component_type: Literal["pipeline", "agent"]
13
+ component_definition: str
@@ -0,0 +1,13 @@
1
+ def _parse_component_definition(component_definition: str) -> tuple[str, str]:
2
+ try:
3
+ if ":" not in component_definition:
4
+ raise ValueError(
5
+ f" component_definition must be in format: 'path/to/my/module.py:component_variable_name"
6
+ f"got: {component_definition}"
7
+ )
8
+ module_path, component_variable_name = component_definition.rsplit(":", 1)
9
+ return module_path, component_variable_name
10
+ except Exception as e:
11
+ raise ValueError(
12
+ f"Invalid Component Definition format: {component_definition}"
13
+ ) from e
@@ -0,0 +1,7 @@
1
+ """LangGraph agent package."""
2
+
3
+ from .langgraph import LanggraphAgent
4
+
5
+ __all__ = [
6
+ "LanggraphAgent",
7
+ ]