refactor: replace interrupt calls with request_approval utility in Jira and OneDrive tools

Updated the create, delete, and update functions in Jira and OneDrive 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:17:06 +05:30
parent 2f59fc9c72
commit 4875fd9211
5 changed files with 89 additions and 220 deletions

View file

@ -3,7 +3,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
@ -69,58 +69,32 @@ def create_create_jira_issue_tool(
"connector_type": "jira", "connector_type": "jira",
} }
approval = interrupt( result = request_approval(
{ action_type="jira_issue_creation",
"type": "jira_issue_creation", tool_name="create_jira_issue",
"action": { params={
"tool": "create_jira_issue", "project_key": project_key,
"params": { "summary": summary,
"project_key": project_key, "issue_type": issue_type,
"summary": summary, "description": description,
"issue_type": issue_type, "priority": priority,
"description": description, "connector_id": connector_id,
"priority": priority, },
"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 issue was not created.", "message": "User declined. Do not retry or suggest alternatives.",
} }
final_params: dict[str, Any] = {} final_project_key = result.params.get("project_key", project_key)
edited_action = decision.get("edited_action") final_summary = result.params.get("summary", summary)
if isinstance(edited_action, dict): final_issue_type = result.params.get("issue_type", issue_type)
edited_args = edited_action.get("args") final_description = result.params.get("description", description)
if isinstance(edited_args, dict): final_priority = result.params.get("priority", priority)
final_params = edited_args final_connector_id = result.params.get("connector_id", connector_id)
elif isinstance(decision.get("args"), dict):
final_params = decision["args"]
final_project_key = final_params.get("project_key", project_key)
final_summary = final_params.get("summary", summary)
final_issue_type = final_params.get("issue_type", issue_type)
final_description = final_params.get("description", description)
final_priority = final_params.get("priority", priority)
final_connector_id = final_params.get("connector_id", connector_id)
if not final_summary or not final_summary.strip(): if not final_summary or not final_summary.strip():
return {"status": "error", "message": "Issue summary cannot be empty."} return {"status": "error", "message": "Issue summary cannot be empty."}

View file

@ -3,7 +3,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
@ -71,54 +71,28 @@ def create_delete_jira_issue_tool(
document_id = issue_data["document_id"] document_id = issue_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="jira_issue_deletion",
"type": "jira_issue_deletion", tool_name="delete_jira_issue",
"action": { params={
"tool": "delete_jira_issue", "issue_key": issue_key,
"params": { "connector_id": connector_id_from_context,
"issue_key": issue_key, "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 issue was not deleted.", "message": "User declined. Do not retry or suggest alternatives.",
} }
final_params: dict[str, Any] = {} final_issue_key = result.params.get("issue_key", issue_key)
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_issue_key = final_params.get("issue_key", issue_key)
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

@ -3,7 +3,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
@ -75,60 +75,34 @@ def create_update_jira_issue_tool(
document_id = issue_data.get("document_id") document_id = issue_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="jira_issue_update",
"type": "jira_issue_update", tool_name="update_jira_issue",
"action": { params={
"tool": "update_jira_issue", "issue_key": issue_key,
"params": { "document_id": document_id,
"issue_key": issue_key, "new_summary": new_summary,
"document_id": document_id, "new_description": new_description,
"new_summary": new_summary, "new_priority": new_priority,
"new_description": new_description, "connector_id": connector_id_from_context,
"new_priority": new_priority, },
"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 issue was not updated.", "message": "User declined. Do not retry or suggest alternatives.",
} }
final_params: dict[str, Any] = {} final_issue_key = result.params.get("issue_key", issue_key)
edited_action = decision.get("edited_action") final_summary = result.params.get("new_summary", new_summary)
if isinstance(edited_action, dict): final_description = result.params.get("new_description", new_description)
edited_args = edited_action.get("args") final_priority = result.params.get("new_priority", new_priority)
if isinstance(edited_args, dict): final_connector_id = result.params.get(
final_params = edited_args
elif isinstance(decision.get("args"), dict):
final_params = decision["args"]
final_issue_key = final_params.get("issue_key", issue_key)
final_summary = final_params.get("new_summary", new_summary)
final_description = final_params.get("new_description", new_description)
final_priority = final_params.get("new_priority", new_priority)
final_connector_id = final_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 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.future import select from sqlalchemy.future import select
@ -145,54 +145,28 @@ def create_create_onedrive_file_tool(
"parent_folders": parent_folders, "parent_folders": parent_folders,
} }
approval = interrupt( result = request_approval(
{ action_type="onedrive_file_creation",
"type": "onedrive_file_creation", tool_name="create_onedrive_file",
"action": { params={
"tool": "create_onedrive_file", "name": name,
"params": { "content": content,
"name": name, "connector_id": None,
"content": content, "parent_folder_id": None,
"connector_id": None, },
"parent_folder_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:
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_content = result.params.get("content", content)
if isinstance(edited_action, dict): final_connector_id = result.params.get("connector_id")
edited_args = edited_action.get("args") final_parent_folder_id = result.params.get("parent_folder_id")
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_content = final_params.get("content", content)
final_connector_id = final_params.get("connector_id")
final_parent_folder_id = final_params.get("parent_folder_id")
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_onedrive_file_tool(
}, },
} }
approval = interrupt( result = request_approval(
{ action_type="onedrive_file_trash",
"type": "onedrive_file_trash", tool_name="delete_onedrive_file",
"action": { params={
"tool": "delete_onedrive_file", "file_id": file_id,
"params": { "connector_id": connector.id,
"file_id": file_id, "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 trashed. Do not ask again or suggest alternatives.", "message": "User declined. Do not retry or suggest alternatives.",
} }
final_params: dict[str, Any] = {} final_file_id = result.params.get("file_id", file_id)
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_id = final_params.get("file_id", file_id)
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(