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 typing import Any
from langchain_core.tools import tool 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.ext.asyncio import AsyncSession
from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.orm.attributes import flag_modified
@ -65,54 +65,28 @@ def create_create_confluence_page_tool(
"connector_type": "confluence", "connector_type": "confluence",
} }
approval = interrupt( result = request_approval(
{ action_type="confluence_page_creation",
"type": "confluence_page_creation", tool_name="create_confluence_page",
"action": { params={
"tool": "create_confluence_page", "title": title,
"params": { "content": content,
"title": title, "space_id": space_id,
"content": content, "connector_id": connector_id,
"space_id": space_id, },
"connector_id": connector_id, context=context,
},
},
"context": context,
}
) )
decisions_raw = ( if result.rejected:
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":
return { return {
"status": "rejected", "status": "rejected",
"message": "User declined. The page was not created.", "message": "User declined. Do not retry or suggest alternatives.",
} }
final_params: dict[str, Any] = {} final_title = result.params.get("title", title)
edited_action = decision.get("edited_action") final_content = result.params.get("content", content) or ""
if isinstance(edited_action, dict): final_space_id = result.params.get("space_id", space_id)
edited_args = edited_action.get("args") final_connector_id = result.params.get("connector_id", connector_id)
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)
if not final_title or not final_title.strip(): if not final_title or not final_title.strip():
return {"status": "error", "message": "Page title cannot be empty."} return {"status": "error", "message": "Page title cannot be empty."}

View file

@ -2,7 +2,7 @@ import logging
from typing import Any from typing import Any
from langchain_core.tools import tool 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.ext.asyncio import AsyncSession
from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.orm.attributes import flag_modified
@ -74,54 +74,28 @@ def create_delete_confluence_page_tool(
document_id = page_data["document_id"] document_id = page_data["document_id"]
connector_id_from_context = context.get("account", {}).get("id") connector_id_from_context = context.get("account", {}).get("id")
approval = interrupt( result = request_approval(
{ action_type="confluence_page_deletion",
"type": "confluence_page_deletion", tool_name="delete_confluence_page",
"action": { params={
"tool": "delete_confluence_page", "page_id": page_id,
"params": { "connector_id": connector_id_from_context,
"page_id": page_id, "delete_from_kb": delete_from_kb,
"connector_id": connector_id_from_context, },
"delete_from_kb": delete_from_kb, context=context,
},
},
"context": context,
}
) )
decisions_raw = ( if result.rejected:
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":
return { return {
"status": "rejected", "status": "rejected",
"message": "User declined. The page was not deleted.", "message": "User declined. Do not retry or suggest alternatives.",
} }
final_params: dict[str, Any] = {} final_page_id = result.params.get("page_id", page_id)
edited_action = decision.get("edited_action") final_connector_id = result.params.get(
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(
"connector_id", connector_id_from_context "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 from sqlalchemy.future import select

View file

@ -2,7 +2,7 @@ import logging
from typing import Any from typing import Any
from langchain_core.tools import tool 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.ext.asyncio import AsyncSession
from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.orm.attributes import flag_modified
@ -78,62 +78,36 @@ def create_update_confluence_page_tool(
document_id = page_data.get("document_id") document_id = page_data.get("document_id")
connector_id_from_context = context.get("account", {}).get("id") connector_id_from_context = context.get("account", {}).get("id")
approval = interrupt( result = request_approval(
{ action_type="confluence_page_update",
"type": "confluence_page_update", tool_name="update_confluence_page",
"action": { params={
"tool": "update_confluence_page", "page_id": page_id,
"params": { "document_id": document_id,
"page_id": page_id, "new_title": new_title,
"document_id": document_id, "new_content": new_content,
"new_title": new_title, "version": current_version,
"new_content": new_content, "connector_id": connector_id_from_context,
"version": current_version, },
"connector_id": connector_id_from_context, context=context,
},
},
"context": context,
}
) )
decisions_raw = ( if result.rejected:
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":
return { return {
"status": "rejected", "status": "rejected",
"message": "User declined. The page was not updated.", "message": "User declined. Do not retry or suggest alternatives.",
} }
final_params: dict[str, Any] = {} final_page_id = result.params.get("page_id", page_id)
edited_action = decision.get("edited_action") final_title = result.params.get("new_title", new_title) or current_title
if isinstance(edited_action, dict): final_content = result.params.get("new_content", new_content)
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)
if final_content is None: if final_content is None:
final_content = current_body final_content = current_body
final_version = final_params.get("version", current_version) final_version = result.params.get("version", current_version)
final_connector_id = final_params.get( final_connector_id = result.params.get(
"connector_id", connector_id_from_context "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 from sqlalchemy.future import select

View file

@ -5,7 +5,7 @@ from pathlib import Path
from typing import Any, Literal from typing import Any, Literal
from langchain_core.tools import tool 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.ext.asyncio import AsyncSession
from sqlalchemy.future import select from sqlalchemy.future import select
@ -159,56 +159,30 @@ def create_create_dropbox_file_tool(
"supported_types": _SUPPORTED_TYPES, "supported_types": _SUPPORTED_TYPES,
} }
approval = interrupt( result = request_approval(
{ action_type="dropbox_file_creation",
"type": "dropbox_file_creation", tool_name="create_dropbox_file",
"action": { params={
"tool": "create_dropbox_file", "name": name,
"params": { "file_type": file_type,
"name": name, "content": content,
"file_type": file_type, "connector_id": None,
"content": content, "parent_folder_path": None,
"connector_id": None, },
"parent_folder_path": None, context=context,
},
},
"context": context,
}
) )
decisions_raw = ( if result.rejected:
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":
return { return {
"status": "rejected", "status": "rejected",
"message": "User declined. The file was not created.", "message": "User declined. Do not retry or suggest alternatives.",
} }
final_params: dict[str, Any] = {} final_name = result.params.get("name", name)
edited_action = decision.get("edited_action") final_file_type = result.params.get("file_type", file_type)
if isinstance(edited_action, dict): final_content = result.params.get("content", content)
edited_args = edited_action.get("args") final_connector_id = result.params.get("connector_id")
if isinstance(edited_args, dict): final_parent_folder_path = result.params.get("parent_folder_path")
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")
if not final_name or not final_name.strip(): if not final_name or not final_name.strip():
return {"status": "error", "message": "File name cannot be empty."} return {"status": "error", "message": "File name cannot be empty."}

View file

@ -2,7 +2,7 @@ import logging
from typing import Any from typing import Any
from langchain_core.tools import tool 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 import String, and_, cast, func
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select from sqlalchemy.future import select
@ -174,53 +174,26 @@ def create_delete_dropbox_file_tool(
}, },
} }
approval = interrupt( result = request_approval(
{ action_type="dropbox_file_trash",
"type": "dropbox_file_trash", tool_name="delete_dropbox_file",
"action": { params={
"tool": "delete_dropbox_file", "file_path": file_path,
"params": { "connector_id": connector.id,
"file_path": file_path, "delete_from_kb": delete_from_kb,
"connector_id": connector.id, },
"delete_from_kb": delete_from_kb, context=context,
},
},
"context": context,
}
) )
decisions_raw = ( if result.rejected:
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":
return { return {
"status": "rejected", "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] = {} final_file_path = result.params.get("file_path", file_path)
edited_action = decision.get("edited_action") final_connector_id = result.params.get("connector_id", connector.id)
if isinstance(edited_action, dict): final_delete_from_kb = result.params.get("delete_from_kb", delete_from_kb)
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)
if final_connector_id != connector.id: if final_connector_id != connector.id:
result = await db_session.execute( result = await db_session.execute(