optimize svg cache and add retry (#752)
This commit is contained in:
@@ -293,6 +293,11 @@ class StepUnableToExecuteError(SkyvernException):
|
|||||||
super().__init__(f"Step {step_id} cannot be executed and task execution is stopped. Reason: {reason}")
|
super().__init__(f"Step {step_id} cannot be executed and task execution is stopped. Reason: {reason}")
|
||||||
|
|
||||||
|
|
||||||
|
class SVGConversionFailed(SkyvernException):
|
||||||
|
def __init__(self, svg_html: str) -> None:
|
||||||
|
super().__init__(f"Failed to convert SVG after max retries. svg_html={svg_html}")
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedActionType(SkyvernException):
|
class UnsupportedActionType(SkyvernException):
|
||||||
def __init__(self, action_type: str):
|
def __init__(self, action_type: str):
|
||||||
super().__init__(f"Unsupport action type: {action_type}")
|
super().__init__(f"Unsupport action type: {action_type}")
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import asyncio
|
||||||
import copy
|
import copy
|
||||||
import hashlib
|
import hashlib
|
||||||
from typing import Awaitable, Callable, Dict, List
|
from typing import Awaitable, Callable, Dict, List
|
||||||
@@ -6,7 +7,7 @@ import structlog
|
|||||||
from playwright.async_api import Page
|
from playwright.async_api import Page
|
||||||
|
|
||||||
from skyvern.constants import SKYVERN_ID_ATTR
|
from skyvern.constants import SKYVERN_ID_ATTR
|
||||||
from skyvern.exceptions import StepUnableToExecuteError
|
from skyvern.exceptions import StepUnableToExecuteError, SVGConversionFailed
|
||||||
from skyvern.forge import app
|
from skyvern.forge import app
|
||||||
from skyvern.forge.async_operations import AsyncOperation
|
from skyvern.forge.async_operations import AsyncOperation
|
||||||
from skyvern.forge.prompts import prompt_engine
|
from skyvern.forge.prompts import prompt_engine
|
||||||
@@ -19,6 +20,9 @@ CleanupElementTreeFunc = Callable[[str, list[dict]], Awaitable[list[dict]]]
|
|||||||
|
|
||||||
LOG = structlog.get_logger()
|
LOG = structlog.get_logger()
|
||||||
|
|
||||||
|
USELESS_SVG_ATTRIBUTE = [SKYVERN_ID_ATTR, "id", "aria-describedby"]
|
||||||
|
SVG_RETRY_ATTEMPT = 3
|
||||||
|
|
||||||
|
|
||||||
def _remove_rect(element: dict) -> None:
|
def _remove_rect(element: dict) -> None:
|
||||||
if "rect" in element:
|
if "rect" in element:
|
||||||
@@ -38,8 +42,11 @@ def _remove_skyvern_attributes(element: Dict) -> Dict:
|
|||||||
if element_copied.get(attr):
|
if element_copied.get(attr):
|
||||||
del element_copied[attr]
|
del element_copied[attr]
|
||||||
|
|
||||||
if element_copied.get("attributes") and SKYVERN_ID_ATTR in element_copied.get("attributes", {}):
|
if "attributes" in element_copied:
|
||||||
del element_copied["attributes"][SKYVERN_ID_ATTR]
|
attributes: dict = copy.deepcopy(element_copied.get("attributes", {}))
|
||||||
|
for key in attributes.keys():
|
||||||
|
if key in USELESS_SVG_ATTRIBUTE:
|
||||||
|
del element_copied["attributes"][key]
|
||||||
|
|
||||||
children: List[Dict] | None = element_copied.get("children", None)
|
children: List[Dict] | None = element_copied.get("children", None)
|
||||||
if children is None:
|
if children is None:
|
||||||
@@ -80,20 +87,25 @@ async def _convert_svg_to_string(task: Task, step: Step, organization: Organizat
|
|||||||
else:
|
else:
|
||||||
LOG.debug("call LLM to convert SVG to string shape", element_id=element_id)
|
LOG.debug("call LLM to convert SVG to string shape", element_id=element_id)
|
||||||
svg_convert_prompt = prompt_engine.load_prompt("svg-convert", svg_element=svg_html)
|
svg_convert_prompt = prompt_engine.load_prompt("svg-convert", svg_element=svg_html)
|
||||||
try:
|
|
||||||
json_response = await app.SECONDARY_LLM_API_HANDLER(prompt=svg_convert_prompt, step=step)
|
for retry in range(SVG_RETRY_ATTEMPT):
|
||||||
svg_shape = json_response.get("shape", "")
|
try:
|
||||||
if not svg_shape:
|
json_response = await app.SECONDARY_LLM_API_HANDLER(prompt=svg_convert_prompt, step=step)
|
||||||
raise Exception("Empty SVG shape replied by secondary llm")
|
svg_shape = json_response.get("shape", "")
|
||||||
LOG.info("SVG converted by LLM", element_id=element_id, shape=svg_shape)
|
if not svg_shape:
|
||||||
await app.CACHE.set(svg_key, svg_shape)
|
raise Exception("Empty SVG shape replied by secondary llm")
|
||||||
except Exception:
|
LOG.info("SVG converted by LLM", element_id=element_id, shape=svg_shape)
|
||||||
LOG.exception(
|
await app.CACHE.set(svg_key, svg_shape)
|
||||||
"Failed to convert SVG to string shape by secondary llm",
|
break
|
||||||
element=element,
|
except Exception:
|
||||||
svg_html=svg_html,
|
LOG.exception(
|
||||||
)
|
"Failed to convert SVG to string shape by secondary llm. Will retry if haven't met the max try attempt after 3s.",
|
||||||
return
|
element_id=element_id,
|
||||||
|
retry=retry,
|
||||||
|
)
|
||||||
|
await asyncio.sleep(3)
|
||||||
|
else:
|
||||||
|
raise SVGConversionFailed(svg_html=svg_html)
|
||||||
|
|
||||||
element["attributes"] = dict()
|
element["attributes"] = dict()
|
||||||
element["attributes"]["alt"] = svg_shape
|
element["attributes"]["alt"] = svg_shape
|
||||||
|
|||||||
Reference in New Issue
Block a user