Skip to content
Open
9 changes: 3 additions & 6 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,12 @@ services:
context: ./services/idun_agent_manager
dockerfile: Dockerfile.dev
container_name: idun-manager-dev
env_file:
- .env
environment:
# Database connection (use internal port 5432)
DATABASE__URL: postgresql+asyncpg://postgres:postgres@db:5432/idun_agents
DATABASEAUTO_MIGRATE: true
AUTH__SECRET_KEY: your-super-secret-key-at-least-32-characters-long-dev
ENVIRONMENT: development
# Optional: Add other env vars as needed
# LANGFUSE_HOST: ${LANGFUSE_HOST}
DEBUG: true
volumes:
# Mount manager source for hot reload
- ./services/idun_agent_manager/src:/app/src
Expand All @@ -46,7 +43,7 @@ services:
# Mount schema source for hot reload (entire package root)
- ./libs/idun_agent_schema:/schema
ports:
- "8000:8000"
- "8080:8000"
depends_on:
db:
condition: service_healthy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from collections.abc import AsyncGenerator
from typing import Any

from ag_ui_adk import ADKAgent as ADKAGUIAgent
from google.adk.apps.app import App
from google.adk.memory import (
InMemoryMemoryService,
Expand All @@ -24,7 +25,6 @@
AdkVertexAiSessionConfig,
)

from ag_ui_adk import ADKAgent as ADKAGUIAgent
from idun_agent_engine.agent import base as agent_base


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
from haystack.components.agents import Agent
from haystack.dataclasses import ChatMessage
from haystack_integrations.components.connectors.langfuse import LangfuseConnector
from idun_agent_schema.engine.haystack import HaystackAgentConfig

from idun_agent_engine.agent.base import BaseAgent
from idun_agent_schema.engine.haystack import HaystackAgentConfig
from idun_agent_engine.agent.haystack.utils import _parse_component_definition

logging.basicConfig(
Expand Down Expand Up @@ -77,7 +77,9 @@ def copilotkit_agent_instance(self) -> Any:
Raises:
RuntimeError: If the CopilotKit agent is not yet initialized.
"""
raise NotImplementedError("CopilotKit agent instance not supported yet for Haystack agent.")
raise NotImplementedError(
"CopilotKit agent instance not supported yet for Haystack agent."
)

@property
def configuration(self) -> HaystackAgentConfig:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"""LangGraph agent adapter implementing the BaseAgent protocol."""

import importlib.util
import importlib
import importlib.util
import uuid
from collections.abc import AsyncGenerator
from typing import Any

import aiosqlite
from ag_ui.core import events as ag_events
from ag_ui.core import types as ag_types
from copilotkit import LangGraphAGUIAgent
from idun_agent_schema.engine.langgraph import (
InMemoryCheckpointConfig,
LangGraphAgentConfig,
Expand All @@ -23,7 +24,6 @@

from idun_agent_engine import observability
from idun_agent_engine.agent import base as agent_base
from copilotkit import LangGraphAGUIAgent


class LanggraphAgent(agent_base.BaseAgent):
Expand Down Expand Up @@ -95,7 +95,9 @@ def copilotkit_agent_instance(self) -> LangGraphAGUIAgent:
RuntimeError: If the CopilotKit agent is not yet initialized.
"""
if self._copilotkit_agent_instance is None:
raise RuntimeError("CopilotKit agent not initialized. Call initialize() first.")
raise RuntimeError(
"CopilotKit agent not initialized. Call initialize() first."
)
return self._copilotkit_agent_instance

@property
Expand Down Expand Up @@ -182,7 +184,7 @@ async def initialize(self, config: LangGraphAgentConfig) -> None:

self._copilotkit_agent_instance = LangGraphAGUIAgent(
name=self._name,
description="Agent description", # TODO: add agent description
description="Agent description", # TODO: add agent description
graph=self._agent_instance,
)

Expand Down Expand Up @@ -276,29 +278,35 @@ def _load_graph_builder(self, graph_definition: str) -> StateGraph:
spec.loader.exec_module(module)

graph_builder = getattr(module, graph_variable_name)
return self._validate_graph_builder(graph_builder, module_path, graph_variable_name)
return self._validate_graph_builder(
graph_builder, module_path, graph_variable_name
)

except (FileNotFoundError, ImportError):
# Fallback: try loading as a python module
try:
module = importlib.import_module(module_path)
graph_builder = getattr(module, graph_variable_name)
return self._validate_graph_builder(graph_builder, module_path, graph_variable_name)
return self._validate_graph_builder(
graph_builder, module_path, graph_variable_name
)
except ImportError as e:
raise ValueError(
raise ValueError(
f"Failed to load agent from {graph_definition}. Checked file path and python module: {e}"
) from e
except AttributeError as e:
raise ValueError(
f"Variable '{graph_variable_name}' not found in module {module_path}: {e}"
) from e
except Exception as e:
raise ValueError(
raise ValueError(
f"Failed to load agent from {graph_definition}: {e}"
) from e

def _validate_graph_builder(self, graph_builder: Any, module_path: str, graph_variable_name: str) -> StateGraph:
# TODO to remove, dirty fix for template deepagent langgraph
def _validate_graph_builder(
self, graph_builder: Any, module_path: str, graph_variable_name: str
) -> StateGraph:
# TODO to remove, dirty fix for template deepagent langgraph
if not isinstance(graph_builder, StateGraph) and not isinstance(
graph_builder, CompiledStateGraph
):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@

from fastapi import FastAPI

from .._version import __version__
from ..server.lifespan import lifespan
from ..server.routers.agent import agent_router
from ..server.routers.base import base_router
from .config_builder import ConfigBuilder
from .engine_config import EngineConfig
from .._version import __version__


def create_app(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@
from pathlib import Path
from typing import Any

from idun_agent_schema.engine.guardrails import Guardrails
import yaml
from idun_agent_schema.engine.agent_framework import AgentFramework
from idun_agent_schema.engine.guardrails import Guardrails
from idun_agent_schema.engine.haystack import HaystackAgentConfig
from idun_agent_schema.engine.langgraph import (
LangGraphAgentConfig,
SqliteCheckpointConfig,
)
from idun_agent_schema.engine.adk import AdkAgentConfig
from idun_agent_engine.server.server_config import ServerAPIConfig
from yaml import YAMLError

from idun_agent_engine.server.server_config import ServerAPIConfig

from ..agent.base import BaseAgent
from .engine_config import AgentConfig, EngineConfig, ServerConfig

Expand Down Expand Up @@ -111,10 +112,11 @@ def with_config_from_api(self, agent_api_key: str, url: str) -> "ConfigBuilder":
self._agent_config = yaml_config["engine_config"]["agent"]
except Exception as e:
raise YAMLError(
f"Failed to parse yaml file for Engine config: {e}"
f"Failed to parse yaml file for AgentConfig: {e}"
) from e
try:
guardrails = yaml_config.get("guardrails", "")
self._guardrails = guardrails
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Guardrails config assigned as raw dict instead of parsed object

The new line self._guardrails = guardrails assigns the raw YAML value (likely a dict) to _guardrails before checking if it's empty. When guardrails is truthy (contains config data), the conditional on line 119 is false, leaving _guardrails as a raw dict instead of a Guardrails instance. The _guardrails attribute is typed as Guardrails | None and is passed to EngineConfig in the build() method, causing type inconsistency. When guardrails config is present, it needs to be converted to a Guardrails object rather than being assigned as a raw value.

Fix in Cursor Fix in Web

if not guardrails:
self._guardrails = Guardrails(enabled=False)
except Exception as e:
Expand Down Expand Up @@ -285,10 +287,12 @@ async def initialize_agent_from_config(engine_config: EngineConfig) -> BaseAgent
agent_instance = LanggraphAgent()

elif agent_type == AgentFramework.TRANSLATION_AGENT:
from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
from idun_agent_schema.engine.templates import TranslationAgentConfig
import os

from idun_agent_schema.engine.templates import TranslationAgentConfig

from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent

try:
translation_config = TranslationAgentConfig.model_validate(
agent_config_obj
Expand All @@ -315,10 +319,12 @@ async def initialize_agent_from_config(engine_config: EngineConfig) -> BaseAgent
agent_instance = LanggraphAgent()

elif agent_type == AgentFramework.CORRECTION_AGENT:
from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
from idun_agent_schema.engine.templates import CorrectionAgentConfig
import os

from idun_agent_schema.engine.templates import CorrectionAgentConfig

from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent

try:
correction_config = CorrectionAgentConfig.model_validate(
agent_config_obj
Expand All @@ -342,10 +348,12 @@ async def initialize_agent_from_config(engine_config: EngineConfig) -> BaseAgent
agent_instance = LanggraphAgent()

elif agent_type == AgentFramework.DEEP_RESEARCH_AGENT:
from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
from idun_agent_schema.engine.templates import DeepResearchAgentConfig
import os

from idun_agent_schema.engine.templates import DeepResearchAgentConfig

from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent

try:
deep_research_config = DeepResearchAgentConfig.model_validate(
agent_config_obj
Expand Down
3 changes: 1 addition & 2 deletions libs/idun_agent_engine/src/idun_agent_engine/mcp/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@

from typing import Any, cast

from idun_agent_schema.engine.mcp_server import MCPServer
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_mcp_adapters.sessions import Connection

from idun_agent_schema.engine.mcp_server import MCPServer


class MCPClientRegistry:
"""Wraps `MultiServerMCPClient` with convenience helpers."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ async def get_agent(request: Request):
agent = await ConfigBuilder.initialize_agent_from_config(app_config)
return agent


async def get_copilotkit_agent(request: Request):
"""Return the pre-initialized agent instance from the app state.

Expand All @@ -34,7 +35,9 @@ async def get_copilotkit_agent(request: Request):
# This is a fallback for cases where the lifespan event did not run,
# like in some testing scenarios.
# Consider logging a warning here.
print("⚠️ CopilotKit agent not found in app state, initializing fallback agent...")
print(
"⚠️ CopilotKit agent not found in app state, initializing fallback agent..."
)

app_config = ConfigBuilder.load_from_file()
copilotkit_agent = await ConfigBuilder.initialize_agent_from_config(app_config)
Expand All @@ -43,7 +46,9 @@ async def get_copilotkit_agent(request: Request):

def get_mcp_registry(request: Request) -> MCPClientRegistry:
"""Return the configured MCP registry if available."""
registry: MCPClientRegistry | None = getattr(request.app.state, "mcp_registry", None)
registry: MCPClientRegistry | None = getattr(
request.app.state, "mcp_registry", None
)
if registry is None or not registry.enabled:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@
from contextlib import asynccontextmanager

from fastapi import FastAPI
from idun_agent_schema.engine.guardrails import Guardrail, Guardrails

from ..core.config_builder import ConfigBuilder
from ..mcp import MCPClientRegistry

from idun_agent_schema.engine.guardrails import Guardrails, Guardrail


def _parse_guardrails(guardrails_obj: Guardrails) -> list[Guardrail]:
"""Adds the position of the guardrails (input/output) and returns the lift of updated guardrails."""

from ..guardrails.guardrails_hub.guardrails_hub import GuardrailsHubGuard as GHGuard

if not guardrails_obj.enabled:
Expand All @@ -30,7 +28,6 @@ def _parse_guardrails(guardrails_obj: Guardrails) -> list[Guardrail]:
@asynccontextmanager
async def lifespan(app: FastAPI):
"""FastAPI lifespan context to initialize and teardown the agent."""

# Load config and initialize agent on startup
print("Server starting up...")
if not app.state.engine_config:
Expand Down Expand Up @@ -61,8 +58,8 @@ async def lifespan(app: FastAPI):
print(f"✅ Agent '{agent_name}' initialized and ready to serve!")

# Setup AGUI routes if the agent is a LangGraph agent
from ..agent.langgraph.langgraph import LanggraphAgent
from ..agent.adk.adk import AdkAgent
from ..agent.langgraph.langgraph import LanggraphAgent
# from ..server.routers.agui import setup_agui_router

if isinstance(agent_instance, (LanggraphAgent, AdkAgent)):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import logging
from typing import Annotated

from ag_ui.core.types import RunAgentInput
from ag_ui.encoder import EventEncoder
from ag_ui_adk import ADKAgent as ADKAGUIAgent
from copilotkit import LangGraphAGUIAgent
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import StreamingResponse
from idun_agent_schema.engine.api import ChatRequest, ChatResponse
Expand All @@ -11,11 +15,6 @@
from idun_agent_engine.agent.base import BaseAgent
from idun_agent_engine.server.dependencies import get_agent, get_copilotkit_agent

from ag_ui.core.types import RunAgentInput
from ag_ui.encoder import EventEncoder
from copilotkit import LangGraphAGUIAgent
from ag_ui_adk import ADKAgent as ADKAGUIAgent

logging.basicConfig(
format="%(asctime)s %(levelname)-8s %(message)s",
level=logging.INFO,
Expand Down Expand Up @@ -142,7 +141,7 @@ async def event_generator():
exc_info=True,
)
# Create a RunErrorEvent for encoding failures
from ag_ui.core import RunErrorEvent, EventType
from ag_ui.core import EventType, RunErrorEvent

error_event = RunErrorEvent(
type=EventType.RUN_ERROR,
Expand All @@ -165,7 +164,7 @@ async def event_generator():
# ADKAgent should have yielded a RunErrorEvent, but if something went wrong
# in the async generator itself, we need to handle it
try:
from ag_ui.core import RunErrorEvent, EventType
from ag_ui.core import EventType, RunErrorEvent

error_event = RunErrorEvent(
type=EventType.RUN_ERROR,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Correction Agent Template."""

import os
from typing import TypedDict, Annotated, List
from typing import Annotated, TypedDict

try:
from langchain.chat_models import init_chat_model
Expand All @@ -11,13 +11,13 @@
except ImportError:
init_chat_model = None

from langchain_core.messages import SystemMessage, BaseMessage
from langgraph.graph import StateGraph, START, END
from langchain_core.messages import BaseMessage, SystemMessage
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages


class State(TypedDict):
messages: Annotated[List[BaseMessage], add_messages]
messages: Annotated[list[BaseMessage], add_messages]


MODEL_NAME = os.getenv("CORRECTION_MODEL", "gemini-2.5-flash")
Expand Down Expand Up @@ -60,4 +60,3 @@ async def correct_text(state: State):
workflow.add_edge("correct", END)

graph = workflow.compile()

Loading