refactor: replace interrupt calls with request_approval utility in Gmail tools

Updated the create, send, update, and trash functions in Gmail tools to utilize the new request_approval utility for handling user approvals. This change improves code consistency and simplifies decision handling by directly merging parameters from the approval response.
This commit is contained in:
Anish Sarkar 2026-04-13 20:15:31 +05:30
parent 3eb448ec8d
commit 85baaacd0a
4 changed files with 77 additions and 189 deletions

View file

@ -6,7 +6,7 @@ from email.mime.text import MIMEText
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 app.services.gmail import GmailToolMetadataService from app.services.gmail import GmailToolMetadataService
@ -85,12 +85,10 @@ def create_create_gmail_draft_tool(
logger.info( logger.info(
f"Requesting approval for creating Gmail draft: to='{to}', subject='{subject}'" f"Requesting approval for creating Gmail draft: to='{to}', subject='{subject}'"
) )
approval = interrupt( result = request_approval(
{ action_type="gmail_draft_creation",
"type": "gmail_draft_creation", tool_name="create_gmail_draft",
"action": { params={
"tool": "create_gmail_draft",
"params": {
"to": to, "to": to,
"subject": subject, "subject": subject,
"body": body, "body": body,
@ -98,47 +96,21 @@ def create_create_gmail_draft_tool(
"bcc": bcc, "bcc": bcc,
"connector_id": None, "connector_id": 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:
logger.warning("No approval decision received")
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 draft was not created. Do not ask again or suggest alternatives.", "message": "User declined. The draft was not created. Do not ask again or suggest alternatives.",
} }
final_params: dict[str, Any] = {} final_to = result.params.get("to", to)
edited_action = decision.get("edited_action") final_subject = result.params.get("subject", subject)
if isinstance(edited_action, dict): final_body = result.params.get("body", body)
edited_args = edited_action.get("args") final_cc = result.params.get("cc", cc)
if isinstance(edited_args, dict): final_bcc = result.params.get("bcc", bcc)
final_params = edited_args final_connector_id = result.params.get("connector_id")
elif isinstance(decision.get("args"), dict):
final_params = decision["args"]
final_to = final_params.get("to", to)
final_subject = final_params.get("subject", subject)
final_body = final_params.get("body", body)
final_cc = final_params.get("cc", cc)
final_bcc = final_params.get("bcc", bcc)
final_connector_id = final_params.get("connector_id")
from sqlalchemy.future import select from sqlalchemy.future import select

View file

@ -6,7 +6,7 @@ from email.mime.text import MIMEText
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 app.services.gmail import GmailToolMetadataService from app.services.gmail import GmailToolMetadataService
@ -86,12 +86,10 @@ def create_send_gmail_email_tool(
logger.info( logger.info(
f"Requesting approval for sending Gmail email: to='{to}', subject='{subject}'" f"Requesting approval for sending Gmail email: to='{to}', subject='{subject}'"
) )
approval = interrupt( result = request_approval(
{ action_type="gmail_email_send",
"type": "gmail_email_send", tool_name="send_gmail_email",
"action": { params={
"tool": "send_gmail_email",
"params": {
"to": to, "to": to,
"subject": subject, "subject": subject,
"body": body, "body": body,
@ -99,47 +97,21 @@ def create_send_gmail_email_tool(
"bcc": bcc, "bcc": bcc,
"connector_id": None, "connector_id": 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:
logger.warning("No approval decision received")
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 email was not sent. Do not ask again or suggest alternatives.", "message": "User declined. The email was not sent. Do not ask again or suggest alternatives.",
} }
final_params: dict[str, Any] = {} final_to = result.params.get("to", to)
edited_action = decision.get("edited_action") final_subject = result.params.get("subject", subject)
if isinstance(edited_action, dict): final_body = result.params.get("body", body)
edited_args = edited_action.get("args") final_cc = result.params.get("cc", cc)
if isinstance(edited_args, dict): final_bcc = result.params.get("bcc", bcc)
final_params = edited_args final_connector_id = result.params.get("connector_id")
elif isinstance(decision.get("args"), dict):
final_params = decision["args"]
final_to = final_params.get("to", to)
final_subject = final_params.get("subject", subject)
final_body = final_params.get("body", body)
final_cc = final_params.get("cc", cc)
final_bcc = final_params.get("bcc", bcc)
final_connector_id = final_params.get("connector_id")
from sqlalchemy.future import select from sqlalchemy.future import select

View file

@ -4,7 +4,7 @@ from datetime import datetime
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 app.services.gmail import GmailToolMetadataService from app.services.gmail import GmailToolMetadataService
@ -101,56 +101,28 @@ def create_trash_gmail_email_tool(
logger.info( logger.info(
f"Requesting approval for trashing Gmail email: '{email_subject_or_id}' (message_id={message_id}, delete_from_kb={delete_from_kb})" f"Requesting approval for trashing Gmail email: '{email_subject_or_id}' (message_id={message_id}, delete_from_kb={delete_from_kb})"
) )
approval = interrupt( result = request_approval(
{ action_type="gmail_email_trash",
"type": "gmail_email_trash", tool_name="trash_gmail_email",
"action": { params={
"tool": "trash_gmail_email",
"params": {
"message_id": message_id, "message_id": message_id,
"connector_id": connector_id_from_context, "connector_id": connector_id_from_context,
"delete_from_kb": delete_from_kb, "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:
logger.warning("No approval decision received")
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 email was not trashed. Do not ask again or suggest alternatives.", "message": "User declined. The email was not trashed. Do not ask again or suggest alternatives.",
} }
edited_action = decision.get("edited_action") final_message_id = result.params.get("message_id", message_id)
final_params: dict[str, Any] = {} 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_message_id = final_params.get("message_id", message_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)
if not final_connector_id: if not final_connector_id:
return { return {

View file

@ -6,7 +6,7 @@ from email.mime.text import MIMEText
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 app.services.gmail import GmailToolMetadataService from app.services.gmail import GmailToolMetadataService
@ -122,12 +122,10 @@ def create_update_gmail_draft_tool(
f"Requesting approval for updating Gmail draft: '{original_subject}' " f"Requesting approval for updating Gmail draft: '{original_subject}' "
f"(message_id={message_id}, draft_id={draft_id_from_context})" f"(message_id={message_id}, draft_id={draft_id_from_context})"
) )
approval = interrupt( result = request_approval(
{ action_type="gmail_draft_update",
"type": "gmail_draft_update", tool_name="update_gmail_draft",
"action": { params={
"tool": "update_gmail_draft",
"params": {
"message_id": message_id, "message_id": message_id,
"draft_id": draft_id_from_context, "draft_id": draft_id_from_context,
"to": final_to_default, "to": final_to_default,
@ -137,50 +135,24 @@ def create_update_gmail_draft_tool(
"bcc": bcc, "bcc": bcc,
"connector_id": connector_id_from_context, "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:
logger.warning("No approval decision received")
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 draft was not updated. Do not ask again or suggest alternatives.", "message": "User declined. The draft was not updated. Do not ask again or suggest alternatives.",
} }
final_params: dict[str, Any] = {} final_to = result.params.get("to", final_to_default)
edited_action = decision.get("edited_action") final_subject = result.params.get("subject", final_subject_default)
if isinstance(edited_action, dict): final_body = result.params.get("body", body)
edited_args = edited_action.get("args") final_cc = result.params.get("cc", cc)
if isinstance(edited_args, dict): final_bcc = result.params.get("bcc", bcc)
final_params = edited_args final_connector_id = result.params.get(
elif isinstance(decision.get("args"), dict):
final_params = decision["args"]
final_to = final_params.get("to", final_to_default)
final_subject = final_params.get("subject", final_subject_default)
final_body = final_params.get("body", body)
final_cc = final_params.get("cc", cc)
final_bcc = final_params.get("bcc", bcc)
final_connector_id = final_params.get(
"connector_id", connector_id_from_context "connector_id", connector_id_from_context
) )
final_draft_id = final_params.get("draft_id", draft_id_from_context) final_draft_id = result.params.get("draft_id", draft_id_from_context)
if not final_connector_id: if not final_connector_id:
return { return {