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.
- idun_agent_engine/__init__.py +24 -0
- idun_agent_engine/_version.py +3 -0
- idun_agent_engine/agent/__init__.py +10 -0
- idun_agent_engine/agent/adk/__init__.py +5 -0
- idun_agent_engine/agent/adk/adk.py +296 -0
- idun_agent_engine/agent/base.py +112 -0
- idun_agent_engine/agent/haystack/__init__.py +9 -0
- idun_agent_engine/agent/haystack/haystack.py +274 -0
- idun_agent_engine/agent/haystack/haystack_model.py +13 -0
- idun_agent_engine/agent/haystack/utils.py +13 -0
- idun_agent_engine/agent/langgraph/__init__.py +7 -0
- idun_agent_engine/agent/langgraph/langgraph.py +553 -0
- idun_agent_engine/core/__init__.py +11 -0
- idun_agent_engine/core/app_factory.py +73 -0
- idun_agent_engine/core/config_builder.py +657 -0
- idun_agent_engine/core/engine_config.py +21 -0
- idun_agent_engine/core/server_runner.py +145 -0
- 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 +17 -0
- idun_agent_engine/observability/base.py +172 -0
- 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/__init__.py +5 -0
- idun_agent_engine/observability/langfuse/langfuse_handler.py +79 -0
- idun_agent_engine/observability/phoenix/__init__.py +5 -0
- idun_agent_engine/observability/phoenix/phoenix_handler.py +65 -0
- idun_agent_engine/observability/phoenix_local/__init__.py +5 -0
- idun_agent_engine/observability/phoenix_local/phoenix_local_handler.py +123 -0
- idun_agent_engine/py.typed +0 -0
- idun_agent_engine/server/__init__.py +5 -0
- idun_agent_engine/server/dependencies.py +52 -0
- idun_agent_engine/server/lifespan.py +106 -0
- idun_agent_engine/server/routers/__init__.py +5 -0
- idun_agent_engine/server/routers/agent.py +204 -0
- idun_agent_engine/server/routers/agui.py +47 -0
- idun_agent_engine/server/routers/base.py +114 -0
- idun_agent_engine/server/server_config.py +8 -0
- 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.3.4.dist-info/METADATA +335 -0
- idun_agent_engine-0.3.4.dist-info/RECORD +60 -0
- idun_agent_engine-0.3.4.dist-info/WHEEL +4 -0
- idun_agent_engine-0.3.4.dist-info/entry_points.txt +2 -0
- idun_platform_cli/__init__.py +0 -0
- idun_platform_cli/groups/__init__.py +0 -0
- idun_platform_cli/groups/agent/__init__.py +0 -0
- idun_platform_cli/groups/agent/main.py +16 -0
- idun_platform_cli/groups/agent/package.py +70 -0
- idun_platform_cli/groups/agent/serve.py +107 -0
- 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
|