idun-agent-engine 0.2.7__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 +7 -1
- idun_agent_engine/agent/haystack/haystack.py +5 -1
- idun_agent_engine/agent/langgraph/langgraph.py +146 -55
- idun_agent_engine/core/app_factory.py +9 -0
- 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 +13 -1
- idun_agent_engine/server/lifespan.py +83 -16
- idun_agent_engine/server/routers/agent.py +116 -24
- 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.7.dist-info → idun_agent_engine-0.3.0.dist-info}/METADATA +62 -10
- idun_agent_engine-0.3.0.dist-info/RECORD +60 -0
- {idun_agent_engine-0.2.7.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.7.dist-info/RECORD +0 -43
- {idun_agent_engine-0.2.7.dist-info → idun_agent_engine-0.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -6,6 +6,7 @@ from typing import Annotated
|
|
|
6
6
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
|
7
7
|
from fastapi.responses import StreamingResponse
|
|
8
8
|
from idun_agent_schema.engine.api import ChatRequest, ChatResponse
|
|
9
|
+
from idun_agent_schema.engine.guardrails import Guardrail
|
|
9
10
|
|
|
10
11
|
from idun_agent_engine.agent.base import BaseAgent
|
|
11
12
|
from idun_agent_engine.server.dependencies import get_agent, get_copilotkit_agent
|
|
@@ -13,6 +14,7 @@ from idun_agent_engine.server.dependencies import get_agent, get_copilotkit_agen
|
|
|
13
14
|
from ag_ui.core.types import RunAgentInput
|
|
14
15
|
from ag_ui.encoder import EventEncoder
|
|
15
16
|
from copilotkit import LangGraphAGUIAgent
|
|
17
|
+
from ag_ui_adk import ADKAgent as ADKAGUIAgent
|
|
16
18
|
|
|
17
19
|
logging.basicConfig(
|
|
18
20
|
format="%(asctime)s %(levelname)-8s %(message)s",
|
|
@@ -24,6 +26,16 @@ logger = logging.getLogger(__name__)
|
|
|
24
26
|
agent_router = APIRouter()
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
def _run_guardrails(
|
|
30
|
+
guardrails: list[Guardrail], message: dict[str, str] | str, position: str
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Validates the request's message, by running it on given guardrails. If input is a dict -> input, else its an output guardrails."""
|
|
33
|
+
text = message["query"] if isinstance(message, dict) else message
|
|
34
|
+
for guard in guardrails:
|
|
35
|
+
if guard.position == position and not guard.validate(text): # type: ignore[attr-defined]
|
|
36
|
+
raise HTTPException(status_code=429, detail=guard.reject_message) # type: ignore[attr-defined]
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
@agent_router.get("/config")
|
|
28
40
|
async def get_config(request: Request):
|
|
29
41
|
"""Get the current agent configuration."""
|
|
@@ -35,21 +47,28 @@ async def get_config(request: Request):
|
|
|
35
47
|
)
|
|
36
48
|
|
|
37
49
|
config = request.app.state.engine_config.agent
|
|
38
|
-
logger.info(f"Fetched config for agent: {
|
|
50
|
+
logger.info(f"Fetched config for agent: {request.app.state.engine_config}")
|
|
39
51
|
return {"config": config}
|
|
40
52
|
|
|
41
53
|
|
|
42
54
|
@agent_router.post("/invoke", response_model=ChatResponse)
|
|
43
55
|
async def invoke(
|
|
44
|
-
|
|
56
|
+
chat_request: ChatRequest,
|
|
57
|
+
request: Request,
|
|
45
58
|
agent: Annotated[BaseAgent, Depends(get_agent)],
|
|
46
59
|
):
|
|
47
60
|
"""Process a chat message with the agent without streaming."""
|
|
48
61
|
try:
|
|
49
|
-
message = {"query":
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
message = {"query": chat_request.query, "session_id": chat_request.session_id}
|
|
63
|
+
guardrails = getattr(request.app.state, 'guardrails', [])
|
|
64
|
+
if guardrails:
|
|
65
|
+
_run_guardrails(guardrails, message, position="input")
|
|
66
|
+
response_content = await agent.invoke(
|
|
67
|
+
{"query": message["query"], "session_id": message["session_id"]}
|
|
68
|
+
)
|
|
69
|
+
if guardrails:
|
|
70
|
+
_run_guardrails(guardrails, response_content, position="output")
|
|
71
|
+
return ChatResponse(session_id=message["session_id"], response=response_content)
|
|
53
72
|
except Exception as e: # noqa: BLE001
|
|
54
73
|
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
55
74
|
|
|
@@ -74,23 +93,96 @@ async def stream(
|
|
|
74
93
|
async def copilotkit_stream(
|
|
75
94
|
input_data: RunAgentInput,
|
|
76
95
|
request: Request,
|
|
77
|
-
copilotkit_agent: Annotated[
|
|
96
|
+
copilotkit_agent: Annotated[
|
|
97
|
+
LangGraphAGUIAgent | ADKAGUIAgent, Depends(get_copilotkit_agent)
|
|
98
|
+
],
|
|
78
99
|
):
|
|
79
100
|
"""Process a message with the agent, streaming ag-ui events."""
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
async
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
if isinstance(copilotkit_agent, LangGraphAGUIAgent):
|
|
102
|
+
try:
|
|
103
|
+
# Get the accept header from the request
|
|
104
|
+
accept_header = request.headers.get("accept")
|
|
105
|
+
|
|
106
|
+
# Create an event encoder to properly format SSE events
|
|
107
|
+
encoder = EventEncoder(accept=accept_header or "") # type: ignore[arg-type]
|
|
108
|
+
|
|
109
|
+
async def event_generator():
|
|
110
|
+
async for event in copilotkit_agent.run(input_data):
|
|
111
|
+
yield encoder.encode(event) # type: ignore[arg-type]
|
|
112
|
+
|
|
113
|
+
return StreamingResponse(
|
|
114
|
+
event_generator(), # type: ignore[arg-type]
|
|
115
|
+
media_type=encoder.get_content_type(),
|
|
116
|
+
)
|
|
117
|
+
except Exception as e: # noqa: BLE001
|
|
118
|
+
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
119
|
+
elif isinstance(copilotkit_agent, ADKAGUIAgent):
|
|
120
|
+
try:
|
|
121
|
+
# Get the accept header from the request
|
|
122
|
+
accept_header = request.headers.get("accept")
|
|
123
|
+
agent_id = request.url.path.lstrip("/")
|
|
124
|
+
|
|
125
|
+
# Create an event encoder to properly format SSE events
|
|
126
|
+
encoder = EventEncoder(accept=accept_header or "")
|
|
127
|
+
|
|
128
|
+
async def event_generator():
|
|
129
|
+
"""Generate events from ADK agent."""
|
|
130
|
+
try:
|
|
131
|
+
async for event in copilotkit_agent.run(input_data):
|
|
132
|
+
try:
|
|
133
|
+
encoded = encoder.encode(event)
|
|
134
|
+
logger.debug(f"HTTP Response: {encoded}")
|
|
135
|
+
yield encoded
|
|
136
|
+
except Exception as encoding_error:
|
|
137
|
+
# Handle encoding-specific errors
|
|
138
|
+
logger.error(
|
|
139
|
+
f"❌ Event encoding error: {encoding_error}",
|
|
140
|
+
exc_info=True,
|
|
141
|
+
)
|
|
142
|
+
# Create a RunErrorEvent for encoding failures
|
|
143
|
+
from ag_ui.core import RunErrorEvent, EventType
|
|
144
|
+
|
|
145
|
+
error_event = RunErrorEvent(
|
|
146
|
+
type=EventType.RUN_ERROR,
|
|
147
|
+
message=f"Event encoding failed: {str(encoding_error)}",
|
|
148
|
+
code="ENCODING_ERROR",
|
|
149
|
+
)
|
|
150
|
+
try:
|
|
151
|
+
error_encoded = encoder.encode(error_event)
|
|
152
|
+
yield error_encoded
|
|
153
|
+
except Exception:
|
|
154
|
+
# If we can't even encode the error event, yield a basic SSE error
|
|
155
|
+
logger.error(
|
|
156
|
+
"Failed to encode error event, yielding basic SSE error"
|
|
157
|
+
)
|
|
158
|
+
yield 'event: error\ndata: {"error": "Event encoding failed"}\n\n'
|
|
159
|
+
break # Stop the stream after an encoding error
|
|
160
|
+
except Exception as agent_error:
|
|
161
|
+
# Handle errors from ADKAgent.run() itself
|
|
162
|
+
logger.error(f"❌ ADKAgent error: {agent_error}", exc_info=True)
|
|
163
|
+
# ADKAgent should have yielded a RunErrorEvent, but if something went wrong
|
|
164
|
+
# in the async generator itself, we need to handle it
|
|
165
|
+
try:
|
|
166
|
+
from ag_ui.core import RunErrorEvent, EventType
|
|
167
|
+
|
|
168
|
+
error_event = RunErrorEvent(
|
|
169
|
+
type=EventType.RUN_ERROR,
|
|
170
|
+
message=f"Agent execution failed: {str(agent_error)}",
|
|
171
|
+
code="AGENT_ERROR",
|
|
172
|
+
)
|
|
173
|
+
error_encoded = encoder.encode(error_event)
|
|
174
|
+
yield error_encoded
|
|
175
|
+
except Exception:
|
|
176
|
+
# If we can't encode the error event, yield a basic SSE error
|
|
177
|
+
logger.error(
|
|
178
|
+
"Failed to encode agent error event, yielding basic SSE error"
|
|
179
|
+
)
|
|
180
|
+
yield 'event: error\ndata: {"error": "Agent execution failed"}\n\n'
|
|
181
|
+
|
|
182
|
+
return StreamingResponse(
|
|
183
|
+
event_generator(), media_type=encoder.get_content_type()
|
|
184
|
+
)
|
|
185
|
+
except Exception as e: # noqa: BLE001
|
|
186
|
+
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
187
|
+
else:
|
|
188
|
+
raise HTTPException(status_code=400, detail="Invalid agent type")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# """AGUI routes for CopilotKit integration with LangGraph agents."""
|
|
2
|
+
|
|
3
|
+
# import logging
|
|
4
|
+
# from typing import Annotated
|
|
5
|
+
|
|
6
|
+
# from ag_ui_langgraph import add_langgraph_fastapi_endpoint
|
|
7
|
+
# from copilotkit import LangGraphAGUIAgent
|
|
8
|
+
# from ag_ui_adk import ADKAgent as ADKAGUIAgent
|
|
9
|
+
# from ag_ui_adk import add_adk_fastapi_endpoint
|
|
10
|
+
# from fastapi import APIRouter, Depends, HTTPException, Request
|
|
11
|
+
|
|
12
|
+
# from idun_agent_engine.agent.langgraph.langgraph import LanggraphAgent
|
|
13
|
+
# from idun_agent_engine.agent.adk.adk import AdkAgent
|
|
14
|
+
# from idun_agent_engine.server.dependencies import get_agent
|
|
15
|
+
|
|
16
|
+
# logging.basicConfig(
|
|
17
|
+
# format="%(asctime)s %(levelname)-8s %(message)s",
|
|
18
|
+
# level=logging.INFO,
|
|
19
|
+
# datefmt="%Y-%m-%d %H:%M:%S",
|
|
20
|
+
# )
|
|
21
|
+
|
|
22
|
+
# logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# def setup_agui_router(app, agent: LanggraphAgent | AdkAgent) -> LangGraphAGUIAgent | ADKAGUIAgent:
|
|
26
|
+
# """Set up AGUI routes for CopilotKit integration.
|
|
27
|
+
|
|
28
|
+
# This function adds the LangGraph agent as a CopilotKit-compatible endpoint.
|
|
29
|
+
|
|
30
|
+
# Args:
|
|
31
|
+
# app: The FastAPI application instance
|
|
32
|
+
# agent: The initialized LangGraph agent instance
|
|
33
|
+
# """
|
|
34
|
+
# try:
|
|
35
|
+
# if isinstance(agent, LanggraphAgent):
|
|
36
|
+
# # Create the AGUI agent wrapper
|
|
37
|
+
# agui_agent = agent.copilotkit_agent_instance
|
|
38
|
+
# elif isinstance(agent, AdkAgent):
|
|
39
|
+
# # Create the AGUI agent wrapper
|
|
40
|
+
# agui_agent = agent.copilotkit_agent_instance # TODO: duplicate in agent.adk.adk.py init
|
|
41
|
+
# else:
|
|
42
|
+
# raise ValueError(f"Unsupported agent type: {type(agent)}")
|
|
43
|
+
# return agui_agent
|
|
44
|
+
# logger.info(f"✅ AGUI endpoint configured at /agui for agent: {agent.name}")
|
|
45
|
+
# except Exception as e:
|
|
46
|
+
# logger.error(f"❌ Failed to setup AGUI router: {e}")
|
|
47
|
+
# raise HTTPException(status_code=500, detail=f"Failed to setup AGUI router: {e}") from e
|
|
@@ -1,18 +1,72 @@
|
|
|
1
1
|
"""Base routes for service health and landing info."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from fastapi import APIRouter, Request, HTTPException
|
|
6
|
+
from pydantic import BaseModel
|
|
4
7
|
|
|
5
8
|
from ..._version import __version__
|
|
9
|
+
from ...core.config_builder import ConfigBuilder
|
|
10
|
+
from ..lifespan import cleanup_agent, configure_app
|
|
6
11
|
|
|
7
12
|
base_router = APIRouter()
|
|
8
13
|
|
|
9
14
|
|
|
15
|
+
class ReloadRequest(BaseModel):
|
|
16
|
+
"""Request body for reload endpoint."""
|
|
17
|
+
|
|
18
|
+
path: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
|
|
10
21
|
@base_router.get("/health")
|
|
11
22
|
def health_check():
|
|
12
23
|
"""Health check endpoint for monitoring and load balancers."""
|
|
13
24
|
return {"status": "healthy", "engine_version": __version__}
|
|
14
25
|
|
|
15
26
|
|
|
27
|
+
@base_router.post("/reload")
|
|
28
|
+
async def reload_config(request: Request, body: Optional[ReloadRequest] = None):
|
|
29
|
+
"""Reload the agent configuration from the manager or a file."""
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
if body and body.path:
|
|
33
|
+
print(f"🔄 Reloading configuration from file: {body.path}...")
|
|
34
|
+
new_config = ConfigBuilder.load_from_file(body.path)
|
|
35
|
+
else:
|
|
36
|
+
print("🔄 Reloading configuration from manager...")
|
|
37
|
+
agent_api_key = os.getenv("IDUN_AGENT_API_KEY")
|
|
38
|
+
manager_host = os.getenv("IDUN_MANAGER_HOST")
|
|
39
|
+
|
|
40
|
+
if not agent_api_key or not manager_host:
|
|
41
|
+
raise HTTPException(
|
|
42
|
+
status_code=400,
|
|
43
|
+
detail="Cannot reload from manager: IDUN_AGENT_API_KEY or IDUN_MANAGER_HOST environment variables are missing.",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Fetch new config
|
|
47
|
+
config_builder = ConfigBuilder().with_config_from_api(
|
|
48
|
+
agent_api_key=agent_api_key, url=manager_host
|
|
49
|
+
)
|
|
50
|
+
new_config = config_builder.build()
|
|
51
|
+
|
|
52
|
+
# Cleanup old agent
|
|
53
|
+
await cleanup_agent(request.app)
|
|
54
|
+
|
|
55
|
+
# Initialize new agent
|
|
56
|
+
await configure_app(request.app, new_config)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
"status": "success",
|
|
60
|
+
"message": "Agent configuration reloaded successfully",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print(f"❌ Error reloading configuration: {e}")
|
|
65
|
+
raise HTTPException(
|
|
66
|
+
status_code=500, detail=f"Failed to reload configuration: {str(e)}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
16
70
|
# Add a root endpoint with helpful information
|
|
17
71
|
@base_router.get("/")
|
|
18
72
|
def read_root():
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Agent templates package."""
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Correction Agent Template."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import TypedDict, Annotated, List, Any
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from langchain.chat_models import init_chat_model
|
|
8
|
+
except ImportError:
|
|
9
|
+
try:
|
|
10
|
+
from langchain_core.language_models import init_chat_model
|
|
11
|
+
except ImportError:
|
|
12
|
+
init_chat_model = None
|
|
13
|
+
|
|
14
|
+
from langchain_core.messages import SystemMessage, BaseMessage
|
|
15
|
+
from langgraph.graph import StateGraph, START, END
|
|
16
|
+
from langgraph.graph.message import add_messages
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class State(TypedDict):
|
|
20
|
+
messages: Annotated[List[BaseMessage], add_messages]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
MODEL_NAME = os.getenv("CORRECTION_MODEL", "gemini-2.5-flash")
|
|
24
|
+
LANGUAGE = os.getenv("CORRECTION_LANGUAGE", "French")
|
|
25
|
+
|
|
26
|
+
llm: Any = None
|
|
27
|
+
if init_chat_model and callable(init_chat_model):
|
|
28
|
+
try:
|
|
29
|
+
llm = init_chat_model(MODEL_NAME)
|
|
30
|
+
except Exception as e:
|
|
31
|
+
print(f"Warning: Failed to init model {MODEL_NAME}: {e}")
|
|
32
|
+
else:
|
|
33
|
+
print("Warning: init_chat_model not found in langchain.")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def correct_text(state: State):
|
|
38
|
+
"""Correct the spelling, syntax, and grammar of the text."""
|
|
39
|
+
if not llm:
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
"messages": [
|
|
43
|
+
SystemMessage(content="Error: Model not initialized. Check logs.")
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
prompt = (
|
|
49
|
+
f"You are a professional text corrector for {LANGUAGE}. "
|
|
50
|
+
f"Correct the spelling, syntax, grammar, and conjugation of the following text. "
|
|
51
|
+
f"Return ONLY the corrected text without any explanations or modifications to the meaning."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
messages = [SystemMessage(content=prompt)] + state["messages"]
|
|
55
|
+
|
|
56
|
+
response = await llm.ainvoke(messages)
|
|
57
|
+
return {"messages": [response]}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
workflow = StateGraph(State)
|
|
61
|
+
workflow.add_node("correct", correct_text)
|
|
62
|
+
workflow.add_edge(START, "correct")
|
|
63
|
+
workflow.add_edge("correct", END)
|
|
64
|
+
|
|
65
|
+
graph = workflow.compile()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Deep Research Agent Template."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from deepagents import create_deep_agent
|
|
5
|
+
from tavily import TavilyClient
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from langchain.chat_models import init_chat_model
|
|
9
|
+
except ImportError:
|
|
10
|
+
try:
|
|
11
|
+
from langchain_core.language_models import init_chat_model
|
|
12
|
+
except ImportError:
|
|
13
|
+
init_chat_model = None
|
|
14
|
+
|
|
15
|
+
MODEL_NAME = os.getenv("DEEP_RESEARCH_MODEL", "gemini-2.5-flash")
|
|
16
|
+
|
|
17
|
+
SYSTEM_PROMPT = os.getenv(
|
|
18
|
+
"DEEP_RESEARCH_PROMPT", "Conduct research and write a polished report."
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
|
|
22
|
+
|
|
23
|
+
tavily_client = TavilyClient(api_key=TAVILY_API_KEY)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def internet_search(query: str, max_results: int = 5):
|
|
27
|
+
"""Run a web search"""
|
|
28
|
+
return tavily_client.search(query, max_results=max_results)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
llm = None
|
|
32
|
+
if init_chat_model:
|
|
33
|
+
try:
|
|
34
|
+
llm = init_chat_model(MODEL_NAME)
|
|
35
|
+
except Exception as e:
|
|
36
|
+
print(f"Warning: Failed to init model {MODEL_NAME}: {e}")
|
|
37
|
+
else:
|
|
38
|
+
print("Warning: init_chat_model not found in langchain.")
|
|
39
|
+
|
|
40
|
+
graph = create_deep_agent(llm, [internet_search], system_prompt=SYSTEM_PROMPT)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Translation Agent Template."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import TypedDict, Annotated, List
|
|
5
|
+
|
|
6
|
+
# Try importing init_chat_model, fallback if necessary
|
|
7
|
+
try:
|
|
8
|
+
from langchain.chat_models import init_chat_model
|
|
9
|
+
except ImportError:
|
|
10
|
+
try:
|
|
11
|
+
from langchain_core.language_models import init_chat_model
|
|
12
|
+
except ImportError:
|
|
13
|
+
init_chat_model = None
|
|
14
|
+
|
|
15
|
+
from langchain_core.messages import SystemMessage, BaseMessage
|
|
16
|
+
from langgraph.graph import StateGraph, START, END
|
|
17
|
+
from langgraph.graph.message import add_messages
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Define the state
|
|
21
|
+
class State(TypedDict):
|
|
22
|
+
messages: Annotated[List[BaseMessage], add_messages]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Read configuration from environment variables
|
|
26
|
+
# These are set by ConfigBuilder when initializing the agent
|
|
27
|
+
MODEL_NAME = os.getenv("TRANSLATION_MODEL", "gemini-2.5-flash")
|
|
28
|
+
SOURCE_LANG = os.getenv("TRANSLATION_SOURCE_LANG", "English")
|
|
29
|
+
TARGET_LANG = os.getenv("TRANSLATION_TARGET_LANG", "French")
|
|
30
|
+
|
|
31
|
+
# Initialize the model
|
|
32
|
+
llm = None
|
|
33
|
+
if init_chat_model:
|
|
34
|
+
try:
|
|
35
|
+
# init_chat_model requires langchain>=0.2.x or similar.
|
|
36
|
+
# It auto-detects provider from model name (e.g. "gpt-4" -> openai, "claude" -> anthropic)
|
|
37
|
+
# provided the integration packages are installed.
|
|
38
|
+
llm = init_chat_model(MODEL_NAME)
|
|
39
|
+
except Exception as e:
|
|
40
|
+
print(f"Warning: Failed to init model {MODEL_NAME}: {e}")
|
|
41
|
+
else:
|
|
42
|
+
print("Warning: init_chat_model not found in langchain.")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def translate(state: State):
|
|
46
|
+
"""Translate the last message."""
|
|
47
|
+
if not llm:
|
|
48
|
+
return {
|
|
49
|
+
"messages": [
|
|
50
|
+
SystemMessage(content="Error: Model not initialized. Check logs.")
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
prompt = (
|
|
55
|
+
f"You are a professional translator. Translate the following text "
|
|
56
|
+
f"from {SOURCE_LANG} to {TARGET_LANG}. Output ONLY the translation."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
messages = [SystemMessage(content=prompt)] + state["messages"]
|
|
60
|
+
|
|
61
|
+
response = await llm.ainvoke(messages)
|
|
62
|
+
return {"messages": [response]}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
workflow = StateGraph(State)
|
|
66
|
+
workflow.add_node("translate", translate)
|
|
67
|
+
workflow.add_edge(START, "translate")
|
|
68
|
+
workflow.add_edge("translate", END)
|
|
69
|
+
|
|
70
|
+
graph = workflow.compile()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: idun-agent-engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python SDK and runtime to serve AI agents with FastAPI, LangGraph, and observability.
|
|
5
5
|
Project-URL: Homepage, https://github.com/geoffreyharrazi/idun-agent-platform
|
|
6
6
|
Project-URL: Repository, https://github.com/geoffreyharrazi/idun-agent-platform
|
|
@@ -19,29 +19,43 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
19
19
|
Classifier: Topic :: Software Development :: Libraries
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
21
|
Requires-Python: <3.14,>=3.12
|
|
22
|
+
Requires-Dist: ag-ui-adk<0.4.0,>=0.3.4
|
|
22
23
|
Requires-Dist: ag-ui-langgraph<0.1.0,>=0.0.20
|
|
23
24
|
Requires-Dist: ag-ui-protocol<0.2.0,>=0.1.8
|
|
24
25
|
Requires-Dist: aiosqlite<0.22.0,>=0.21.0
|
|
25
26
|
Requires-Dist: arize-phoenix-otel<1.0.0,>=0.2.0
|
|
26
27
|
Requires-Dist: arize-phoenix<12.0.0,>=11.22.0
|
|
27
|
-
Requires-Dist: click>=8.2.
|
|
28
|
+
Requires-Dist: click>=8.2.0
|
|
28
29
|
Requires-Dist: copilotkit<0.2.0,>=0.1.72
|
|
30
|
+
Requires-Dist: deepagents<1.0.0,>=0.2.8
|
|
29
31
|
Requires-Dist: fastapi<0.116.0,>=0.115.0
|
|
30
|
-
Requires-Dist: google-adk<2.0.0,>=1.
|
|
32
|
+
Requires-Dist: google-adk<2.0.0,>=1.19.0
|
|
33
|
+
Requires-Dist: google-cloud-logging<4.0.0,>=3.10.0
|
|
34
|
+
Requires-Dist: guardrails-ai<0.8.0,>=0.7.0
|
|
31
35
|
Requires-Dist: httpx<0.29.0,>=0.28.1
|
|
32
|
-
Requires-Dist: idun-agent-schema<0.
|
|
33
|
-
Requires-Dist: langchain-core<0.
|
|
34
|
-
Requires-Dist: langchain-google-vertexai<
|
|
35
|
-
Requires-Dist: langchain<0.
|
|
36
|
+
Requires-Dist: idun-agent-schema<1.0.0,>=0.2.7
|
|
37
|
+
Requires-Dist: langchain-core<2.0.0,>=1.0.0
|
|
38
|
+
Requires-Dist: langchain-google-vertexai<4.0.0,>=2.0.27
|
|
39
|
+
Requires-Dist: langchain-mcp-adapters<0.2.0,>=0.1.0
|
|
40
|
+
Requires-Dist: langchain<2.0.0,>=1.0.0
|
|
36
41
|
Requires-Dist: langfuse-haystack>=2.3.0
|
|
37
|
-
Requires-Dist: langfuse
|
|
38
|
-
Requires-Dist: langgraph-checkpoint-
|
|
39
|
-
Requires-Dist: langgraph<0.
|
|
42
|
+
Requires-Dist: langfuse<4.0.0,>=2.60.8
|
|
43
|
+
Requires-Dist: langgraph-checkpoint-postgres<4.0.0,>=3.0.0
|
|
44
|
+
Requires-Dist: langgraph-checkpoint-sqlite<4.0.0,>=3.0.0
|
|
45
|
+
Requires-Dist: langgraph<2.0.0,>=1.0.0
|
|
46
|
+
Requires-Dist: mcp<2.0.0,>=1.0.0
|
|
47
|
+
Requires-Dist: openinference-instrumentation-google-adk<1.0.0,>=0.1.0
|
|
48
|
+
Requires-Dist: openinference-instrumentation-guardrails<1.0.0,>=0.1.0
|
|
40
49
|
Requires-Dist: openinference-instrumentation-langchain<1.0.0,>=0.1.13
|
|
50
|
+
Requires-Dist: openinference-instrumentation-mcp<2.0.0,>=1.0.0
|
|
51
|
+
Requires-Dist: openinference-instrumentation-vertexai<1.0.0,>=0.1.0
|
|
52
|
+
Requires-Dist: opentelemetry-exporter-gcp-trace<2.0.0,>=1.6.0
|
|
53
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2.0.0,>=1.22.0
|
|
41
54
|
Requires-Dist: pydantic<3.0.0,>=2.11.7
|
|
42
55
|
Requires-Dist: python-dotenv>=1.1.1
|
|
43
56
|
Requires-Dist: sqlalchemy<3.0.0,>=2.0.36
|
|
44
57
|
Requires-Dist: streamlit<2.0.0,>=1.47.1
|
|
58
|
+
Requires-Dist: tavily-python<0.8.0,>=0.7.9
|
|
45
59
|
Requires-Dist: uvicorn<0.36.0,>=0.35.0
|
|
46
60
|
Description-Content-Type: text/markdown
|
|
47
61
|
|
|
@@ -214,6 +228,7 @@ agent:
|
|
|
214
228
|
- `agent.config.graph_definition` (str): absolute or relative `path/to/file.py:variable`
|
|
215
229
|
- `agent.config.checkpointer` (sqlite): `{ type: "sqlite", db_url: "sqlite:///file.db" }`
|
|
216
230
|
- `agent.config.observability` (optional): provider options as shown above
|
|
231
|
+
- `mcp_servers` (list, optional): collection of MCP servers that should be available to your agent runtime. Each entry matches the fields supported by `langchain-mcp-adapters` (name, transport, url/command, headers, etc.).
|
|
217
232
|
|
|
218
233
|
Config can be sourced by:
|
|
219
234
|
|
|
@@ -221,6 +236,43 @@ Config can be sourced by:
|
|
|
221
236
|
- `config_dict`: dict validated at runtime
|
|
222
237
|
- `config_path`: path to YAML; defaults to `config.yaml`
|
|
223
238
|
|
|
239
|
+
### MCP Servers
|
|
240
|
+
|
|
241
|
+
You can mount MCP servers directly in your engine config. The engine will automatically
|
|
242
|
+
create a `MultiServerMCPClient` and expose it on `app.state.mcp_registry`.
|
|
243
|
+
|
|
244
|
+
```yaml
|
|
245
|
+
mcp_servers:
|
|
246
|
+
- name: "math"
|
|
247
|
+
transport: "stdio"
|
|
248
|
+
command: "python"
|
|
249
|
+
args:
|
|
250
|
+
- "/path/to/math_server.py"
|
|
251
|
+
- name: "weather"
|
|
252
|
+
transport: "streamable_http"
|
|
253
|
+
url: "http://localhost:8000/mcp"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Inside your FastAPI dependencies or handlers:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
from idun_agent_engine.server.dependencies import get_mcp_registry
|
|
260
|
+
|
|
261
|
+
@router.get("/mcp/{server}/tools")
|
|
262
|
+
async def list_tools(server: str, registry = Depends(get_mcp_registry)):
|
|
263
|
+
return await registry.get_tools(server)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Or outside of FastAPI:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
from langchain_mcp_adapters.tools import load_mcp_tools
|
|
270
|
+
|
|
271
|
+
registry = app.state.mcp_registry
|
|
272
|
+
async with registry.get_session("math") as session:
|
|
273
|
+
tools = await load_mcp_tools(session)
|
|
274
|
+
```
|
|
275
|
+
|
|
224
276
|
## Examples
|
|
225
277
|
|
|
226
278
|
The `examples/` folder contains complete projects:
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
idun_agent_engine/__init__.py,sha256=PhOL6foq5V0eXaoXw7xKUeCWXIWrOHrAFB8OuJnBqyM,550
|
|
2
|
+
idun_agent_engine/_version.py,sha256=0EcilWMIT4cjN03dXuEtQDhswH9tkXZPcYqYHIbsc-o,72
|
|
3
|
+
idun_agent_engine/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
idun_agent_engine/agent/__init__.py,sha256=foyOoRdI_04q1b6f2A5EXEpWSCKjZxpgWMWrKcsHNl8,220
|
|
5
|
+
idun_agent_engine/agent/base.py,sha256=c-3gljSHQpm6aY0JNDmXkaPtcW55rXdsI8Cgv9l4bCs,3294
|
|
6
|
+
idun_agent_engine/agent/adk/__init__.py,sha256=jLV2Z9qQGZtBpF_0pQI1FRCPJ_J1G3Z6cEAzHnQyuu4,83
|
|
7
|
+
idun_agent_engine/agent/adk/adk.py,sha256=29YC5rgLjdod-CU3a2JqgAvzYcD5u2EY7pylozGNi9c,11375
|
|
8
|
+
idun_agent_engine/agent/haystack/__init__.py,sha256=y5ADrD8gWBeYIvV7tmu6OpPdJ8POHt-tyraIL7RkkWI,179
|
|
9
|
+
idun_agent_engine/agent/haystack/haystack.py,sha256=k4NAx_zTBO9uiExM9NtuDAN94H1lrCWtHf1GEWEN16g,10966
|
|
10
|
+
idun_agent_engine/agent/haystack/haystack_model.py,sha256=EtOYnsWRufcrQufTRMeB3V-rZVQqfnmwKwPsYGfZdCs,362
|
|
11
|
+
idun_agent_engine/agent/haystack/utils.py,sha256=sKRoPhzZWFw1NPsYwCockafzMBCCq3lGOrndbNE_C3M,609
|
|
12
|
+
idun_agent_engine/agent/langgraph/__init__.py,sha256=CoBdkp9P4livdy5B0bvj9o7ftoqKmXEr9cZv4TZLncs,107
|
|
13
|
+
idun_agent_engine/agent/langgraph/langgraph.py,sha256=ozc0ClaAl4B-_dSxiRt8KO2oxGIwNI_m-Hyk2qyJV_c,22641
|
|
14
|
+
idun_agent_engine/core/__init__.py,sha256=F0DMDlWcSWS_1dvh3xMbrdcVvZRHVnoAFFgREuSJfBI,408
|
|
15
|
+
idun_agent_engine/core/app_factory.py,sha256=XiLrctFT_n8LP3flKFwJoJDbiWPiw98N9lbkpR8P1O0,2597
|
|
16
|
+
idun_agent_engine/core/config_builder.py,sha256=j_gia6z66Xun2ZOtaO4TWwRMfAIA6lnk8IGaRMJKEZM,25484
|
|
17
|
+
idun_agent_engine/core/engine_config.py,sha256=IR8WhbenDstNSL7ORrUW8AnzgS3exFQxtwip66pFhcM,545
|
|
18
|
+
idun_agent_engine/core/server_runner.py,sha256=vLlgLQM-xyvFgJMgFW2eWZoN1oc0x9FGL6bH8WsF6O8,4897
|
|
19
|
+
idun_agent_engine/guardrails/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
idun_agent_engine/guardrails/base.py,sha256=vC554AbqOup9qjuOaTfG-w3fnd73J_7goMwab63jcIg,792
|
|
21
|
+
idun_agent_engine/guardrails/guardrails_hub/guardrails_hub.py,sha256=7fCFoL8soXVkFW6p-TUD6XyR5x3j3I2ekofUG__sXu8,3396
|
|
22
|
+
idun_agent_engine/guardrails/guardrails_hub/utils.py,sha256=bC6-MsCVF7xKTr48z7OzJJUeWvqAB7BiHeNTiKsme70,20
|
|
23
|
+
idun_agent_engine/mcp/__init__.py,sha256=tsX4sJa7UZD-lr8O1acMwDrdDWJc_hMlB9IrX1NH8Wk,255
|
|
24
|
+
idun_agent_engine/mcp/helpers.py,sha256=aFiLlk63pifBjGgctREFWxuSbb-um6QDOVpyikQ5NJ0,3224
|
|
25
|
+
idun_agent_engine/mcp/registry.py,sha256=K9-OLF_7FoKmyrwi65i6E3goZROHLRgISlF1yPwkEm8,3996
|
|
26
|
+
idun_agent_engine/observability/__init__.py,sha256=DCtK6b3xiX4dh0_8GBDOcSXQdcIJz2wTqqPa_ZFAES4,430
|
|
27
|
+
idun_agent_engine/observability/base.py,sha256=C6mY_ZafDEHkMz8_ZeaEh--pyYuzJYEUqhoJ0sVP2wY,5570
|
|
28
|
+
idun_agent_engine/observability/gcp_logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
idun_agent_engine/observability/gcp_logging/gcp_logging_handler.py,sha256=cMUKfukmSZhIdO2v5oWkqI2_c6umtYqlhzflnOeoq6A,1685
|
|
30
|
+
idun_agent_engine/observability/gcp_trace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
idun_agent_engine/observability/gcp_trace/gcp_trace_handler.py,sha256=YBlUqUwPuOPi0yDNEy9x_g52nJBlSe9a0EEHDIHIr68,3898
|
|
32
|
+
idun_agent_engine/observability/langfuse/__init__.py,sha256=J8XcHV4aT1pF97k5EZiqrnYYPs9VjwfV5rUMihc5Pgk,128
|
|
33
|
+
idun_agent_engine/observability/langfuse/langfuse_handler.py,sha256=RP9MXiYDRe7zWTmZmzQBn6AjRn0y8jrBnWOaUUaMxNA,2795
|
|
34
|
+
idun_agent_engine/observability/phoenix/__init__.py,sha256=tEwJYijcvSGNhFW4QJmvBcTu1D0YVJkZRTmkNCGTteM,130
|
|
35
|
+
idun_agent_engine/observability/phoenix/phoenix_handler.py,sha256=lGqSq-L1vmoEhAr9rbWO3KlNX5HSgBhCKESHMdZ-AfY,2539
|
|
36
|
+
idun_agent_engine/observability/phoenix_local/__init__.py,sha256=m9dIw1GWGKAW4wP08jxA7j4yrOg0Nxq_08bwVh8YogE,146
|
|
37
|
+
idun_agent_engine/observability/phoenix_local/phoenix_local_handler.py,sha256=wjOZuMpAxdD5D33rzxycNEzFMunETpPnYjiHjbjz5GA,4252
|
|
38
|
+
idun_agent_engine/server/__init__.py,sha256=WaFektUsy37bNg2niAUy_TykzStukgWPnxC-t49CEwo,177
|
|
39
|
+
idun_agent_engine/server/dependencies.py,sha256=MVH8STOQ8wu-iYE_28y5dvw5FGT5PX0uQl0ldkpxw6Y,2080
|
|
40
|
+
idun_agent_engine/server/lifespan.py,sha256=REB93NTAivgYtZ9-7xFKb5RdDZSy3IrMAF5LM39-SAo,3886
|
|
41
|
+
idun_agent_engine/server/server_config.py,sha256=RYA7Y0c5aRw_WXaX8svFUIEtTPqzn3o-WQRm2p52C6g,213
|
|
42
|
+
idun_agent_engine/server/routers/__init__.py,sha256=BgNzSVvHtGPGn5zhXhomwpKlDYBkeFi7xCbdcWVOgc8,102
|
|
43
|
+
idun_agent_engine/server/routers/agent.py,sha256=OtviTU3nClyEHHUR5mJOMJvaNgcGR5CAkD5dGMA6Clk,8392
|
|
44
|
+
idun_agent_engine/server/routers/agui.py,sha256=Z1G4fuo57MazQWfp7ao8QZ1or2H9BXLS_JB1nFPXAkE,1891
|
|
45
|
+
idun_agent_engine/server/routers/base.py,sha256=wICOXCokCIRjmHuDPDTWqeXqOVPes1CtDtiR_HvtsF0,3756
|
|
46
|
+
idun_agent_engine/templates/__init__.py,sha256=xxbJZXaX6VEm_UrqzAOQcuujpEji5yqYzwQfwiqig8o,31
|
|
47
|
+
idun_agent_engine/templates/correction.py,sha256=qApH4W99K31maxNrGf0hPVe_Wd4xjYjJrEKlu7PbRpw,1823
|
|
48
|
+
idun_agent_engine/templates/deep_research.py,sha256=j4WypBkBz7V3EG-U8vawfNXDNhcCf2QiEK0veI4M68s,1077
|
|
49
|
+
idun_agent_engine/templates/translation.py,sha256=vjp1yEyhDMFO4nEbq5WG7LAA7nLrhVAjznOkV7ob_Ss,2149
|
|
50
|
+
idun_platform_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
+
idun_platform_cli/main.py,sha256=jWL7Ob0p4KdRUqgPTP_EB68n7z2LyMKC2DeUsfWlBO4,200
|
|
52
|
+
idun_platform_cli/groups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
+
idun_platform_cli/groups/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
|
+
idun_platform_cli/groups/agent/main.py,sha256=QMGQi3JZ76SeFI3miIjVWpMt0L-hGz5FwxtTPQX4-Uw,301
|
|
55
|
+
idun_platform_cli/groups/agent/package.py,sha256=CdZCbqmDG_PFoI1EFPn6Ibl9Bexel_8eO7XgmzYVCRg,2525
|
|
56
|
+
idun_platform_cli/groups/agent/serve.py,sha256=2AbL0G1WqR33jlyiGaNvAoPZ3G1o52KYUptz_HaAjIg,3863
|
|
57
|
+
idun_agent_engine-0.3.0.dist-info/METADATA,sha256=prVc5VpdJJCfRNPVUTZyUxZmF4DCCC9wxpTC9TA7X-c,10648
|
|
58
|
+
idun_agent_engine-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
59
|
+
idun_agent_engine-0.3.0.dist-info/entry_points.txt,sha256=XG3oxlSOaCrYKT1oyhKa0Ag1iJPMZ-WF6gaV_mzIJW4,52
|
|
60
|
+
idun_agent_engine-0.3.0.dist-info/RECORD,,
|