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.
Files changed (41) hide show
  1. idun_agent_engine/_version.py +1 -1
  2. idun_agent_engine/agent/adk/__init__.py +5 -0
  3. idun_agent_engine/agent/adk/adk.py +296 -0
  4. idun_agent_engine/agent/base.py +16 -1
  5. idun_agent_engine/agent/haystack/haystack.py +14 -1
  6. idun_agent_engine/agent/langgraph/langgraph.py +165 -55
  7. idun_agent_engine/core/app_factory.py +11 -1
  8. idun_agent_engine/core/config_builder.py +214 -23
  9. idun_agent_engine/core/engine_config.py +1 -2
  10. idun_agent_engine/core/server_runner.py +2 -3
  11. idun_agent_engine/guardrails/__init__.py +0 -0
  12. idun_agent_engine/guardrails/base.py +24 -0
  13. idun_agent_engine/guardrails/guardrails_hub/guardrails_hub.py +101 -0
  14. idun_agent_engine/guardrails/guardrails_hub/utils.py +1 -0
  15. idun_agent_engine/mcp/__init__.py +5 -0
  16. idun_agent_engine/mcp/helpers.py +97 -0
  17. idun_agent_engine/mcp/registry.py +109 -0
  18. idun_agent_engine/observability/__init__.py +6 -2
  19. idun_agent_engine/observability/base.py +73 -12
  20. idun_agent_engine/observability/gcp_logging/__init__.py +0 -0
  21. idun_agent_engine/observability/gcp_logging/gcp_logging_handler.py +52 -0
  22. idun_agent_engine/observability/gcp_trace/__init__.py +0 -0
  23. idun_agent_engine/observability/gcp_trace/gcp_trace_handler.py +116 -0
  24. idun_agent_engine/observability/langfuse/langfuse_handler.py +17 -10
  25. idun_agent_engine/server/dependencies.py +30 -1
  26. idun_agent_engine/server/lifespan.py +83 -16
  27. idun_agent_engine/server/routers/agent.py +128 -8
  28. idun_agent_engine/server/routers/agui.py +47 -0
  29. idun_agent_engine/server/routers/base.py +55 -1
  30. idun_agent_engine/templates/__init__.py +1 -0
  31. idun_agent_engine/templates/correction.py +65 -0
  32. idun_agent_engine/templates/deep_research.py +40 -0
  33. idun_agent_engine/templates/translation.py +70 -0
  34. {idun_agent_engine-0.2.6.dist-info → idun_agent_engine-0.3.0.dist-info}/METADATA +65 -11
  35. idun_agent_engine-0.3.0.dist-info/RECORD +60 -0
  36. {idun_agent_engine-0.2.6.dist-info → idun_agent_engine-0.3.0.dist-info}/WHEEL +1 -1
  37. idun_platform_cli/groups/agent/package.py +3 -3
  38. idun_platform_cli/groups/agent/serve.py +8 -5
  39. idun_agent_engine/cli/__init__.py +0 -16
  40. idun_agent_engine-0.2.6.dist-info/RECORD +0 -43
  41. {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(self, config: LangGraphAgentConfig) -> None:
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). Prefer generic block; fallback to legacy langfuse block.
111
- obs_cfg = None
112
- try:
113
- if getattr(self._configuration, "observability", None):
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
- if handler:
149
- self._obs_callbacks = handler.get_callbacks()
150
- self._obs_run_name = handler.get_run_name()
151
- if info:
152
- self._infos["observability"] = dict(info)
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
- self._agent_instance = graph_builder.compile(
158
- checkpointer=self._checkpointer, store=self._store
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
- self._input_schema = self._agent_instance.input_schema
163
- self._output_schema = self._agent_instance.output_schema
164
- self._infos["input_schema"] = str(self._input_schema)
165
- self._infos["output_schema"] = str(self._output_schema)
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("Only SQLite checkpointer is supported.")
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
- except (FileNotFoundError, ImportError, AttributeError) as e:
223
- raise ValueError(
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
- if not isinstance(graph_builder, StateGraph):
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="0.1.0",
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