idun-agent-engine 0.2.6__py3-none-any.whl → 0.3.0__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 +16 -1
- idun_agent_engine/agent/haystack/haystack.py +14 -1
- idun_agent_engine/agent/langgraph/langgraph.py +165 -55
- idun_agent_engine/core/app_factory.py +11 -1
- idun_agent_engine/core/config_builder.py +214 -23
- 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 +30 -1
- idun_agent_engine/server/lifespan.py +83 -16
- idun_agent_engine/server/routers/agent.py +128 -8
- 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.6.dist-info → idun_agent_engine-0.3.0.dist-info}/METADATA +65 -11
- idun_agent_engine-0.3.0.dist-info/RECORD +60 -0
- {idun_agent_engine-0.2.6.dist-info → idun_agent_engine-0.3.0.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.6.dist-info/RECORD +0 -43
- {idun_agent_engine-0.2.6.dist-info → idun_agent_engine-0.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""LangGraph agent adapter implementing the BaseAgent protocol."""
|
|
2
2
|
|
|
3
3
|
import importlib.util
|
|
4
|
+
import importlib
|
|
4
5
|
import uuid
|
|
5
6
|
from collections.abc import AsyncGenerator
|
|
6
7
|
from typing import Any
|
|
@@ -9,14 +10,21 @@ import aiosqlite
|
|
|
9
10
|
from ag_ui.core import events as ag_events
|
|
10
11
|
from ag_ui.core import types as ag_types
|
|
11
12
|
from idun_agent_schema.engine.langgraph import (
|
|
13
|
+
InMemoryCheckpointConfig,
|
|
12
14
|
LangGraphAgentConfig,
|
|
15
|
+
PostgresCheckpointConfig,
|
|
13
16
|
SqliteCheckpointConfig,
|
|
14
17
|
)
|
|
18
|
+
from idun_agent_schema.engine.observability_v2 import ObservabilityConfig
|
|
19
|
+
from langgraph.checkpoint.memory import InMemorySaver
|
|
20
|
+
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
|
|
15
21
|
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
|
|
16
22
|
from langgraph.graph import StateGraph
|
|
23
|
+
from langgraph.graph.state import CompiledStateGraph
|
|
17
24
|
|
|
18
25
|
from idun_agent_engine import observability
|
|
19
26
|
from idun_agent_engine.agent import base as agent_base
|
|
27
|
+
from copilotkit import LangGraphAGUIAgent
|
|
20
28
|
|
|
21
29
|
|
|
22
30
|
class LanggraphAgent(agent_base.BaseAgent):
|
|
@@ -29,6 +37,7 @@ class LanggraphAgent(agent_base.BaseAgent):
|
|
|
29
37
|
self._input_schema: Any = None
|
|
30
38
|
self._output_schema: Any = None
|
|
31
39
|
self._agent_instance: Any = None
|
|
40
|
+
self._copilotkit_agent_instance: LangGraphAGUIAgent | None = None
|
|
32
41
|
self._checkpointer: Any = None
|
|
33
42
|
self._store: Any = None
|
|
34
43
|
self._connection: Any = None
|
|
@@ -79,6 +88,17 @@ class LanggraphAgent(agent_base.BaseAgent):
|
|
|
79
88
|
raise RuntimeError("Agent not initialized. Call initialize() first.")
|
|
80
89
|
return self._agent_instance
|
|
81
90
|
|
|
91
|
+
@property
|
|
92
|
+
def copilotkit_agent_instance(self) -> LangGraphAGUIAgent:
|
|
93
|
+
"""Return the CopilotKit agent instance.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
RuntimeError: If the CopilotKit agent is not yet initialized.
|
|
97
|
+
"""
|
|
98
|
+
if self._copilotkit_agent_instance is None:
|
|
99
|
+
raise RuntimeError("CopilotKit agent not initialized. Call initialize() first.")
|
|
100
|
+
return self._copilotkit_agent_instance
|
|
101
|
+
|
|
82
102
|
@property
|
|
83
103
|
def configuration(self) -> LangGraphAgentConfig:
|
|
84
104
|
"""Return validated configuration.
|
|
@@ -98,7 +118,11 @@ class LanggraphAgent(agent_base.BaseAgent):
|
|
|
98
118
|
)
|
|
99
119
|
return self._infos
|
|
100
120
|
|
|
101
|
-
async def initialize(
|
|
121
|
+
async def initialize(
|
|
122
|
+
self,
|
|
123
|
+
config: LangGraphAgentConfig,
|
|
124
|
+
observability_config: list[ObservabilityConfig] | None = None,
|
|
125
|
+
) -> None:
|
|
102
126
|
"""Initialize the LangGraph agent asynchronously."""
|
|
103
127
|
self._configuration = LangGraphAgentConfig.model_validate(config)
|
|
104
128
|
|
|
@@ -107,62 +131,104 @@ class LanggraphAgent(agent_base.BaseAgent):
|
|
|
107
131
|
|
|
108
132
|
await self._setup_persistence()
|
|
109
133
|
|
|
110
|
-
# Observability (provider-agnostic)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
obs_cfg = self._configuration.observability.resolved() # type: ignore[attr-defined]
|
|
115
|
-
elif getattr(self._configuration, "langfuse", None):
|
|
116
|
-
lf = self._configuration.langfuse.resolved() # type: ignore[attr-defined]
|
|
117
|
-
obs_cfg = type(
|
|
118
|
-
"_Temp",
|
|
119
|
-
(),
|
|
120
|
-
{
|
|
121
|
-
"provider": "langfuse",
|
|
122
|
-
"enabled": lf.enabled,
|
|
123
|
-
"options": {
|
|
124
|
-
"host": lf.host,
|
|
125
|
-
"public_key": lf.public_key,
|
|
126
|
-
"secret_key": lf.secret_key,
|
|
127
|
-
"run_name": lf.run_name,
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
)()
|
|
131
|
-
except Exception:
|
|
132
|
-
obs_cfg = None
|
|
133
|
-
|
|
134
|
-
if obs_cfg and getattr(obs_cfg, "enabled", False):
|
|
135
|
-
provider = getattr(obs_cfg, "provider", None)
|
|
136
|
-
options = dict(getattr(obs_cfg, "options", {}) or {})
|
|
137
|
-
# Fallback: if using Langfuse and run_name is not provided, use agent name
|
|
138
|
-
if provider == "langfuse" and not options.get("run_name"):
|
|
139
|
-
options["run_name"] = self._name
|
|
140
|
-
|
|
141
|
-
handler, info = observability.create_observability_handler(
|
|
142
|
-
{
|
|
143
|
-
"provider": provider,
|
|
144
|
-
"enabled": True,
|
|
145
|
-
"options": options,
|
|
146
|
-
}
|
|
134
|
+
# Observability (provider-agnostic)
|
|
135
|
+
if observability_config:
|
|
136
|
+
handlers, infos = observability.create_observability_handlers(
|
|
137
|
+
observability_config # type: ignore[arg-type]
|
|
147
138
|
)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
self.
|
|
151
|
-
|
|
152
|
-
self.
|
|
139
|
+
self._obs_callbacks = []
|
|
140
|
+
for handler in handlers:
|
|
141
|
+
self._obs_callbacks.extend(handler.get_callbacks())
|
|
142
|
+
# Use the first run name found if not set
|
|
143
|
+
if not self._obs_run_name:
|
|
144
|
+
self._obs_run_name = handler.get_run_name()
|
|
145
|
+
|
|
146
|
+
if infos:
|
|
147
|
+
self._infos["observability"] = infos
|
|
148
|
+
|
|
149
|
+
# Fallback to legacy generic block or langfuse block if no new observability config provided
|
|
150
|
+
elif getattr(self._configuration, "observability", None) or getattr(
|
|
151
|
+
self._configuration, "langfuse", None
|
|
152
|
+
):
|
|
153
|
+
obs_cfg = None
|
|
154
|
+
try:
|
|
155
|
+
if getattr(self._configuration, "observability", None):
|
|
156
|
+
obs_cfg = self._configuration.observability.resolved() # type: ignore[attr-defined]
|
|
157
|
+
elif getattr(self._configuration, "langfuse", None):
|
|
158
|
+
lf = self._configuration.langfuse.resolved() # type: ignore[attr-defined]
|
|
159
|
+
obs_cfg = type(
|
|
160
|
+
"_Temp",
|
|
161
|
+
(),
|
|
162
|
+
{
|
|
163
|
+
"provider": "langfuse",
|
|
164
|
+
"enabled": lf.enabled,
|
|
165
|
+
"options": {
|
|
166
|
+
"host": lf.host,
|
|
167
|
+
"public_key": lf.public_key,
|
|
168
|
+
"secret_key": lf.secret_key,
|
|
169
|
+
"run_name": lf.run_name,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
)()
|
|
173
|
+
except Exception:
|
|
174
|
+
obs_cfg = None
|
|
175
|
+
|
|
176
|
+
if obs_cfg and getattr(obs_cfg, "enabled", False):
|
|
177
|
+
provider = getattr(obs_cfg, "provider", None)
|
|
178
|
+
options = dict(getattr(obs_cfg, "options", {}) or {})
|
|
179
|
+
# Fallback: if using Langfuse and run_name is not provided, use agent name
|
|
180
|
+
if provider == "langfuse" and not options.get("run_name"):
|
|
181
|
+
options["run_name"] = self._name
|
|
182
|
+
|
|
183
|
+
handler, info = observability.create_observability_handler(
|
|
184
|
+
{
|
|
185
|
+
"provider": provider,
|
|
186
|
+
"enabled": True,
|
|
187
|
+
"options": options,
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
if handler:
|
|
191
|
+
self._obs_callbacks = handler.get_callbacks()
|
|
192
|
+
self._obs_run_name = handler.get_run_name()
|
|
193
|
+
if info:
|
|
194
|
+
self._infos["observability"] = dict(info)
|
|
153
195
|
|
|
154
196
|
graph_builder = self._load_graph_builder(self._configuration.graph_definition)
|
|
155
197
|
self._infos["graph_definition"] = self._configuration.graph_definition
|
|
156
198
|
|
|
157
|
-
|
|
158
|
-
|
|
199
|
+
if isinstance(graph_builder, StateGraph):
|
|
200
|
+
self._agent_instance = graph_builder.compile(
|
|
201
|
+
checkpointer=self._checkpointer, store=self._store
|
|
202
|
+
)
|
|
203
|
+
elif isinstance(graph_builder, CompiledStateGraph):
|
|
204
|
+
self._agent_instance = graph_builder
|
|
205
|
+
|
|
206
|
+
self._copilotkit_agent_instance = LangGraphAGUIAgent(
|
|
207
|
+
name=self._name,
|
|
208
|
+
description="Agent description", # TODO: add agent description
|
|
209
|
+
graph=self._agent_instance,
|
|
210
|
+
config={"callbacks": self._obs_callbacks} if self._obs_callbacks else None,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
self._copilotkit_agent_instance = LangGraphAGUIAgent(
|
|
214
|
+
name=self._name,
|
|
215
|
+
description="Agent description", # TODO: add agent description
|
|
216
|
+
graph=self._agent_instance,
|
|
159
217
|
)
|
|
160
218
|
|
|
161
219
|
if self._agent_instance:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
220
|
+
try:
|
|
221
|
+
self._input_schema = self._agent_instance.input_schema
|
|
222
|
+
self._output_schema = self._agent_instance.output_schema
|
|
223
|
+
self._infos["input_schema"] = str(self._input_schema)
|
|
224
|
+
self._infos["output_schema"] = str(self._output_schema)
|
|
225
|
+
except Exception:
|
|
226
|
+
print("Could not parse schema")
|
|
227
|
+
self._input_schema = self._configuration.input_schema_definition
|
|
228
|
+
self._output_schema = self._configuration.output_schema_definition
|
|
229
|
+
self._infos["input_schema"] = "Cannot extract schema"
|
|
230
|
+
self._infos["output_schema"] = "Cannot extract schema"
|
|
231
|
+
|
|
166
232
|
else:
|
|
167
233
|
self._input_schema = self._configuration.input_schema_definition
|
|
168
234
|
self._output_schema = self._configuration.output_schema_definition
|
|
@@ -191,8 +257,23 @@ class LanggraphAgent(agent_base.BaseAgent):
|
|
|
191
257
|
self._infos["checkpointer"] = (
|
|
192
258
|
self._configuration.checkpointer.model_dump()
|
|
193
259
|
)
|
|
260
|
+
elif isinstance(self._configuration.checkpointer, InMemoryCheckpointConfig):
|
|
261
|
+
self._checkpointer = InMemorySaver()
|
|
262
|
+
self._infos["checkpointer"] = (
|
|
263
|
+
self._configuration.checkpointer.model_dump()
|
|
264
|
+
)
|
|
265
|
+
elif isinstance(self._configuration.checkpointer, PostgresCheckpointConfig):
|
|
266
|
+
self._checkpointer = AsyncPostgresSaver.from_conn_string(
|
|
267
|
+
self._configuration.checkpointer.db_url
|
|
268
|
+
)
|
|
269
|
+
await self._checkpointer.setup()
|
|
270
|
+
self._infos["checkpointer"] = (
|
|
271
|
+
self._configuration.checkpointer.model_dump()
|
|
272
|
+
)
|
|
194
273
|
else:
|
|
195
|
-
raise NotImplementedError(
|
|
274
|
+
raise NotImplementedError(
|
|
275
|
+
f"Checkpointer type {type(self._configuration.checkpointer)} is not supported."
|
|
276
|
+
)
|
|
196
277
|
|
|
197
278
|
if self._configuration.store:
|
|
198
279
|
raise NotImplementedError("Store functionality is not yet implemented.")
|
|
@@ -201,14 +282,24 @@ class LanggraphAgent(agent_base.BaseAgent):
|
|
|
201
282
|
"""Loads a StateGraph instance from a specified path."""
|
|
202
283
|
try:
|
|
203
284
|
module_path, graph_variable_name = graph_definition.rsplit(":", 1)
|
|
285
|
+
if not module_path.endswith(".py"):
|
|
286
|
+
module_path += ".py"
|
|
204
287
|
except ValueError:
|
|
205
288
|
raise ValueError(
|
|
206
289
|
"graph_definition must be in the format 'path/to/file.py:variable_name'"
|
|
207
290
|
) from None
|
|
208
291
|
|
|
292
|
+
# Try loading as a file path first
|
|
209
293
|
try:
|
|
294
|
+
import os
|
|
295
|
+
print("Current directory: ", os.getcwd()) # TODO remove
|
|
210
296
|
from pathlib import Path
|
|
297
|
+
|
|
211
298
|
resolved_path = Path(module_path).resolve()
|
|
299
|
+
# If the file doesn't exist, it might be a python module path
|
|
300
|
+
if not resolved_path.exists():
|
|
301
|
+
raise FileNotFoundError
|
|
302
|
+
|
|
212
303
|
spec = importlib.util.spec_from_file_location(
|
|
213
304
|
graph_variable_name, str(resolved_path)
|
|
214
305
|
)
|
|
@@ -219,17 +310,36 @@ class LanggraphAgent(agent_base.BaseAgent):
|
|
|
219
310
|
spec.loader.exec_module(module)
|
|
220
311
|
|
|
221
312
|
graph_builder = getattr(module, graph_variable_name)
|
|
222
|
-
|
|
223
|
-
|
|
313
|
+
return self._validate_graph_builder(graph_builder, module_path, graph_variable_name)
|
|
314
|
+
|
|
315
|
+
except (FileNotFoundError, ImportError):
|
|
316
|
+
# Fallback: try loading as a python module
|
|
317
|
+
try:
|
|
318
|
+
module = importlib.import_module(module_path)
|
|
319
|
+
graph_builder = getattr(module, graph_variable_name)
|
|
320
|
+
return self._validate_graph_builder(graph_builder, module_path, graph_variable_name)
|
|
321
|
+
except ImportError as e:
|
|
322
|
+
raise ValueError(
|
|
323
|
+
f"Failed to load agent from {graph_definition}. Checked file path and python module: {e}"
|
|
324
|
+
) from e
|
|
325
|
+
except AttributeError as e:
|
|
326
|
+
raise ValueError(
|
|
327
|
+
f"Variable '{graph_variable_name}' not found in module {module_path}: {e}"
|
|
328
|
+
) from e
|
|
329
|
+
except Exception as e:
|
|
330
|
+
raise ValueError(
|
|
224
331
|
f"Failed to load agent from {graph_definition}: {e}"
|
|
225
332
|
) from e
|
|
226
333
|
|
|
227
|
-
|
|
334
|
+
def _validate_graph_builder(self, graph_builder: Any, module_path: str, graph_variable_name: str) -> StateGraph:
|
|
335
|
+
# TODO to remove, dirty fix for template deepagent langgraph
|
|
336
|
+
if not isinstance(graph_builder, StateGraph) and not isinstance(
|
|
337
|
+
graph_builder, CompiledStateGraph
|
|
338
|
+
):
|
|
228
339
|
raise TypeError(
|
|
229
340
|
f"The variable '{graph_variable_name}' from {module_path} is not a StateGraph instance."
|
|
230
341
|
)
|
|
231
|
-
|
|
232
|
-
return graph_builder
|
|
342
|
+
return graph_builder # type: ignore[return-value]
|
|
233
343
|
|
|
234
344
|
async def invoke(self, message: Any) -> Any:
|
|
235
345
|
"""Process a single input to chat with the agent.
|
|
@@ -8,12 +8,14 @@ setting up routes, dependencies, and lifecycle management behind the scenes.
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
from fastapi import FastAPI
|
|
11
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
11
12
|
|
|
12
13
|
from ..server.lifespan import lifespan
|
|
13
14
|
from ..server.routers.agent import agent_router
|
|
14
15
|
from ..server.routers.base import base_router
|
|
15
16
|
from .config_builder import ConfigBuilder
|
|
16
17
|
from .engine_config import EngineConfig
|
|
18
|
+
from .._version import __version__
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
def create_app(
|
|
@@ -48,11 +50,19 @@ def create_app(
|
|
|
48
50
|
lifespan=lifespan,
|
|
49
51
|
title="Idun Agent Engine Server",
|
|
50
52
|
description="A production-ready server for conversational AI agents",
|
|
51
|
-
version=
|
|
53
|
+
version=__version__,
|
|
52
54
|
docs_url="/docs",
|
|
53
55
|
redoc_url="/redoc",
|
|
54
56
|
)
|
|
55
57
|
|
|
58
|
+
app.add_middleware(
|
|
59
|
+
CORSMiddleware,
|
|
60
|
+
allow_origins=["*"],
|
|
61
|
+
allow_credentials=True,
|
|
62
|
+
allow_methods=["*"],
|
|
63
|
+
allow_headers=["*"],
|
|
64
|
+
)
|
|
65
|
+
|
|
56
66
|
# Store configuration in app state for lifespan to use
|
|
57
67
|
app.state.engine_config = validated_config
|
|
58
68
|
|