refactor: replace interrupt calls with unified request_approval utility in Confluence and Dropbox tools

Updated the create, delete, and update functions in Confluence and Dropbox tools to utilize the new request_approval utility for handling user approvals. This change enhances code consistency and simplifies decision handling by merging parameters directly from the approval response.
This commit is contained in:
Anish Sarkar 2026-04-13 20:14:50 +05:30
parent 82c7d4a2ab
commit 3eb448ec8d
5 changed files with 87 additions and 218 deletions

View file

@ -2,7 +2,7 @@ import logging
from typing import Any
from langchain_core.tools import tool
from langgraph.types import interrupt
from app.agents.new_chat.tools.hitl import request_approval
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm.attributes import flag_modified
@ -65,54 +65,28 @@ def create_create_confluence_page_tool(
"connector_type": "confluence",
}
approval = interrupt(
{
"type": "confluence_page_creation",
"action": {
"tool": "create_confluence_page",
"params": {
"title": title,
"content": content,
"space_id": space_id,
"connector_id": connector_id,
},
},
"context": context,
}
result = request_approval(
action_type="confluence_page_creation",
tool_name="create_confluence_page",
params={
"title": title,
"content": content,
"space_id": space_id,
"connector_id": connector_id,
},
context=context,
)
decisions_raw = (
approval.get("decisions", []) if isinstance(approval, dict) else []
)
decisions = (
decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
)
decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions:
return {"status": "error", "message": "No approval decision received"}
decision = decisions[0]
decision_type = decision.get("type") or decision.get("decision_type")
if decision_type == "reject":
if result.rejected:
return {
"status": "rejected",
"message": "User declined. The page was not created.",
"message": "User declined. Do not retry or suggest alternatives.",
}
final_params: dict[str, Any] = {}
edited_action = decision.get("edited_action")
if isinstance(edited_action, dict):
edited_args = edited_action.get("args")
if isinstance(edited_args, dict):
final_params = edited_args
elif isinstance(decision.get("args"), dict):
final_params = decision["args"]
final_title = final_params.get("title", title)
final_content = final_params.get("content", content) or ""
final_space_id = final_params.get("space_id", space_id)
final_connector_id = final_params.get("connector_id", connector_id)
final_title = result.params.get("title", title)
final_content = result.params.get("content", content) or ""
final_space_id = result.params.get("space_id", space_id)
final_connector_id = result.params.get("connector_id", connector_id)
if not final_title or not final_title.strip():
return {"status": "error", "message": "Page title cannot be empty."}

View file

@ -2,7 +2,7 @@ import logging
from typing import Any
from langchain_core.tools import tool
from langgraph.types import interrupt
from app.agents.new_chat.tools.hitl import request_approval
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm.attributes import flag_modified
@ -74,54 +74,28 @@ def create_delete_confluence_page_tool(
document_id = page_data["document_id"]
connector_id_from_context = context.get("account", {}).get("id")
approval = interrupt(
{
"type": "confluence_page_deletion",
"action": {
"tool": "delete_confluence_page",
"params": {
"page_id": page_id,
"connector_id": connector_id_from_context,
"delete_from_kb": delete_from_kb,
},
},
"context": context,
}
result = request_approval(
action_type="confluence_page_deletion",
tool_name="delete_confluence_page",
params={
"page_id": page_id,
"connector_id": connector_id_from_context,
"delete_from_kb": delete_from_kb,
},
context=context,
)
decisions_raw = (
approval.get("decisions", []) if isinstance(approval, dict) else []
)
decisions = (
decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
)
decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions:
return {"status": "error", "message": "No approval decision received"}
decision = decisions[0]
decision_type = decision.get("type") or decision.get("decision_type")
if decision_type == "reject":
if result.rejected:
return {
"status": "rejected",
"message": "User declined. The page was not deleted.",
"message": "User declined. Do not retry or suggest alternatives.",
}
final_params: dict[str, Any] = {}
edited_action = decision.get("edited_action")
if isinstance(edited_action, dict):
edited_args = edited_action.get("args")
if isinstance(edited_args, dict):
final_params = edited_args
elif isinstance(decision.get("args"), dict):
final_params = decision["args"]
final_page_id = final_params.get("page_id", page_id)
final_connector_id = final_params.get(
final_page_id = result.params.get("page_id", page_id)
final_connector_id = result.params.get(
"connector_id", connector_id_from_context
)
final_delete_from_kb = final_params.get("delete_from_kb", delete_from_kb)
final_delete_from_kb = result.params.get("delete_from_kb", delete_from_kb)
from sqlalchemy.future import select

View file

@ -2,7 +2,7 @@ import logging
from typing import Any
from langchain_core.tools import tool
from langgraph.types import interrupt
from app.agents.new_chat.tools.hitl import request_approval
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm.attributes import flag_modified
@ -78,62 +78,36 @@ def create_update_confluence_page_tool(
document_id = page_data.get("document_id")
connector_id_from_context = context.get("account", {}).get("id")
approval = interrupt(
{
"type": "confluence_page_update",
"action": {
"tool": "update_confluence_page",
"params": {
"page_id": page_id,
"document_id": document_id,
"new_title": new_title,
"new_content": new_content,
"version": current_version,
"connector_id": connector_id_from_context,
},
},
"context": context,
}
result = request_approval(
action_type="confluence_page_update",
tool_name="update_confluence_page",
params={
"page_id": page_id,
"document_id": document_id,
"new_title": new_title,
"new_content": new_content,
"version": current_version,
"connector_id": connector_id_from_context,
},
context=context,
)
decisions_raw = (
approval.get("decisions", []) if isinstance(approval, dict) else []
)
decisions = (
decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
)
decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions:
return {"status": "error", "message": "No approval decision received"}
decision = decisions[0]
decision_type = decision.get("type") or decision.get("decision_type")
if decision_type == "reject":
if result.rejected:
return {
"status": "rejected",
"message": "User declined. The page was not updated.",
"message": "User declined. Do not retry or suggest alternatives.",
}
final_params: dict[str, Any] = {}
edited_action = decision.get("edited_action")
if isinstance(edited_action, dict):
edited_args = edited_action.get("args")
if isinstance(edited_args, dict):
final_params = edited_args
elif isinstance(decision.get("args"), dict):
final_params = decision["args"]
final_page_id = final_params.get("page_id", page_id)
final_title = final_params.get("new_title", new_title) or current_title
final_content = final_params.get("new_content", new_content)
final_page_id = result.params.get("page_id", page_id)
final_title = result.params.get("new_title", new_title) or current_title
final_content = result.params.get("new_content", new_content)
if final_content is None:
final_content = current_body
final_version = final_params.get("version", current_version)
final_connector_id = final_params.get(
final_version = result.params.get("version", current_version)
final_connector_id = result.params.get(
"connector_id", connector_id_from_context
)
final_document_id = final_params.get("document_id", document_id)
final_document_id = result.params.get("document_id", document_id)
from sqlalchemy.future import select

View file

@ -5,7 +5,7 @@ from pathlib import Path
from typing import Any, Literal
from langchain_core.tools import tool
from langgraph.types import interrupt
from app.agents.new_chat.tools.hitl import request_approval
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
@ -159,56 +159,30 @@ def create_create_dropbox_file_tool(
"supported_types": _SUPPORTED_TYPES,
}
approval = interrupt(
{
"type": "dropbox_file_creation",
"action": {
"tool": "create_dropbox_file",
"params": {
"name": name,
"file_type": file_type,
"content": content,
"connector_id": None,
"parent_folder_path": None,
},
},
"context": context,
}
result = request_approval(
action_type="dropbox_file_creation",
tool_name="create_dropbox_file",
params={
"name": name,
"file_type": file_type,
"content": content,
"connector_id": None,
"parent_folder_path": None,
},
context=context,
)
decisions_raw = (
approval.get("decisions", []) if isinstance(approval, dict) else []
)
decisions = (
decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
)
decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions:
return {"status": "error", "message": "No approval decision received"}
decision = decisions[0]
decision_type = decision.get("type") or decision.get("decision_type")
if decision_type == "reject":
if result.rejected:
return {
"status": "rejected",
"message": "User declined. The file was not created.",
"message": "User declined. Do not retry or suggest alternatives.",
}
final_params: dict[str, Any] = {}
edited_action = decision.get("edited_action")
if isinstance(edited_action, dict):
edited_args = edited_action.get("args")
if isinstance(edited_args, dict):
final_params = edited_args
elif isinstance(decision.get("args"), dict):
final_params = decision["args"]
final_name = final_params.get("name", name)
final_file_type = final_params.get("file_type", file_type)
final_content = final_params.get("content", content)
final_connector_id = final_params.get("connector_id")
final_parent_folder_path = final_params.get("parent_folder_path")
final_name = result.params.get("name", name)
final_file_type = result.params.get("file_type", file_type)
final_content = result.params.get("content", content)
final_connector_id = result.params.get("connector_id")
final_parent_folder_path = result.params.get("parent_folder_path")
if not final_name or not final_name.strip():
return {"status": "error", "message": "File name cannot be empty."}

View file

@ -2,7 +2,7 @@ import logging
from typing import Any
from langchain_core.tools import tool
from langgraph.types import interrupt
from app.agents.new_chat.tools.hitl import request_approval
from sqlalchemy import String, and_, cast, func
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
@ -174,53 +174,26 @@ def create_delete_dropbox_file_tool(
},
}
approval = interrupt(
{
"type": "dropbox_file_trash",
"action": {
"tool": "delete_dropbox_file",
"params": {
"file_path": file_path,
"connector_id": connector.id,
"delete_from_kb": delete_from_kb,
},
},
"context": context,
}
result = request_approval(
action_type="dropbox_file_trash",
tool_name="delete_dropbox_file",
params={
"file_path": file_path,
"connector_id": connector.id,
"delete_from_kb": delete_from_kb,
},
context=context,
)
decisions_raw = (
approval.get("decisions", []) if isinstance(approval, dict) else []
)
decisions = (
decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
)
decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions:
return {"status": "error", "message": "No approval decision received"}
decision = decisions[0]
decision_type = decision.get("type") or decision.get("decision_type")
logger.info(f"User decision: {decision_type}")
if decision_type == "reject":
if result.rejected:
return {
"status": "rejected",
"message": "User declined. The file was not deleted. Do not ask again or suggest alternatives.",
"message": "User declined. Do not retry or suggest alternatives.",
}
final_params: dict[str, Any] = {}
edited_action = decision.get("edited_action")
if isinstance(edited_action, dict):
edited_args = edited_action.get("args")
if isinstance(edited_args, dict):
final_params = edited_args
elif isinstance(decision.get("args"), dict):
final_params = decision["args"]
final_file_path = final_params.get("file_path", file_path)
final_connector_id = final_params.get("connector_id", connector.id)
final_delete_from_kb = final_params.get("delete_from_kb", delete_from_kb)
final_file_path = result.params.get("file_path", file_path)
final_connector_id = result.params.get("connector_id", connector.id)
final_delete_from_kb = result.params.get("delete_from_kb", delete_from_kb)
if final_connector_id != connector.id:
result = await db_session.execute(