From dcb2b47be4108754c63824194cdbe0da0cc81cc2 Mon Sep 17 00:00:00 2001 From: Shuchang Zheng Date: Mon, 24 Mar 2025 23:16:10 -0700 Subject: [PATCH] official router v1 (#2013) --- skyvern/agent/agent.py | 2 +- skyvern/forge/api_app.py | 3 +- skyvern/forge/sdk/executor/async_executor.py | 2 +- skyvern/forge/sdk/routes/agent_protocol.py | 75 +++++++++++++++---- skyvern/forge/sdk/workflow/models/block.py | 4 +- skyvern/schemas/runs.py | 13 +++- .../sdk => }/services/task_v2_service.py | 2 +- 7 files changed, 81 insertions(+), 20 deletions(-) rename skyvern/{forge/sdk => }/services/task_v2_service.py (100%) diff --git a/skyvern/agent/agent.py b/skyvern/agent/agent.py index 14fa36cb..144b97fa 100644 --- a/skyvern/agent/agent.py +++ b/skyvern/agent/agent.py @@ -9,9 +9,9 @@ from skyvern.forge.sdk.db.enums import OrganizationAuthTokenType from skyvern.forge.sdk.schemas.organizations import Organization from skyvern.forge.sdk.schemas.task_v2 import TaskV2, TaskV2Request, TaskV2Status from skyvern.forge.sdk.schemas.tasks import CreateTaskResponse, Task, TaskRequest, TaskResponse, TaskStatus -from skyvern.forge.sdk.services import task_v2_service from skyvern.forge.sdk.services.org_auth_token_service import API_KEY_LIFETIME from skyvern.forge.sdk.workflow.models.workflow import WorkflowRunStatus +from skyvern.services import task_v2_service from skyvern.utils import migrate_db diff --git a/skyvern/forge/api_app.py b/skyvern/forge/api_app.py index 77f5b936..77a66d05 100644 --- a/skyvern/forge/api_app.py +++ b/skyvern/forge/api_app.py @@ -18,7 +18,7 @@ from skyvern.forge import app as forge_app from skyvern.forge.sdk.core import skyvern_context from skyvern.forge.sdk.core.skyvern_context import SkyvernContext from skyvern.forge.sdk.db.exceptions import NotFoundError -from skyvern.forge.sdk.routes.agent_protocol import base_router, v2_router +from skyvern.forge.sdk.routes.agent_protocol import base_router, official_api_router, v2_router from skyvern.forge.sdk.routes.streaming import websocket_router from skyvern.forge.sdk.routes.totp import totp_router @@ -66,6 +66,7 @@ def get_agent_app() -> FastAPI: allow_headers=["*"], ) + app.include_router(official_api_router, prefix="/v1") app.include_router(base_router, prefix="/api/v1") app.include_router(v2_router, prefix="/api/v2") app.include_router(websocket_router, prefix="/api/v1/stream") diff --git a/skyvern/forge/sdk/executor/async_executor.py b/skyvern/forge/sdk/executor/async_executor.py index 8b1f1919..9cdc7302 100644 --- a/skyvern/forge/sdk/executor/async_executor.py +++ b/skyvern/forge/sdk/executor/async_executor.py @@ -9,8 +9,8 @@ from skyvern.forge.sdk.core import skyvern_context from skyvern.forge.sdk.core.skyvern_context import SkyvernContext from skyvern.forge.sdk.schemas.task_v2 import TaskV2Status from skyvern.forge.sdk.schemas.tasks import TaskStatus -from skyvern.forge.sdk.services import task_v2_service from skyvern.forge.sdk.workflow.models.workflow import WorkflowRunStatus +from skyvern.services import task_v2_service LOG = structlog.get_logger() diff --git a/skyvern/forge/sdk/routes/agent_protocol.py b/skyvern/forge/sdk/routes/agent_protocol.py index d9034917..bb2bdb98 100644 --- a/skyvern/forge/sdk/routes/agent_protocol.py +++ b/skyvern/forge/sdk/routes/agent_protocol.py @@ -53,7 +53,7 @@ from skyvern.forge.sdk.schemas.tasks import ( TaskStatus, ) from skyvern.forge.sdk.schemas.workflow_runs import WorkflowRunTimeline -from skyvern.forge.sdk.services import org_auth_service, task_run_service, task_v2_service +from skyvern.forge.sdk.services import org_auth_service, task_run_service from skyvern.forge.sdk.workflow.exceptions import ( FailedToCreateWorkflow, FailedToUpdateWorkflow, @@ -71,12 +71,12 @@ from skyvern.forge.sdk.workflow.models.workflow import ( WorkflowStatus, ) from skyvern.forge.sdk.workflow.models.yaml import WorkflowCreateYAMLRequest -from skyvern.schemas.runs import RunEngine, TaskRunRequest, TaskRunResponse, TaskRunStatus -from skyvern.services import task_v1_service +from skyvern.schemas.runs import RunEngine, TaskRunRequest, TaskRunResponse +from skyvern.services import task_v1_service, task_v2_service from skyvern.webeye.actions.actions import Action from skyvern.webeye.schemas import BrowserSessionResponse -official_router = APIRouter() +official_api_router = APIRouter() base_router = APIRouter() v2_router = APIRouter() @@ -1497,8 +1497,15 @@ async def _flatten_workflow_run_timeline(organization_id: str, workflow_run_id: return final_workflow_run_block_timeline -@official_router.post("/tasks") -@official_router.post("/tasks/", include_in_schema=False) +@official_api_router.post( + "/tasks", + tags=["agent"], + openapi_extra={ + "x-fern-sdk-group-name": "agent", + "x-fern-sdk-method-name": "run_task", + }, +) +@official_api_router.post("/tasks/", include_in_schema=False) async def run_task( request: Request, background_tasks: BackgroundTasks, @@ -1506,6 +1513,9 @@ async def run_task( current_org: Organization = Depends(org_auth_service.get_current_org), x_api_key: Annotated[str | None, Header()] = None, ) -> TaskRunResponse: + analytics.capture("skyvern-oss-run-task", data={"url": run_request.url}) + await PermissionCheckerFactory.get_instance().check(current_org, browser_session_id=run_request.browser_session_id) + if run_request.engine == RunEngine.skyvern_v1: # create task v1 # if there's no url, call task generation first to generate the url, data schema if any @@ -1566,10 +1576,49 @@ async def run_task( ) if run_request.engine == RunEngine.skyvern_v2: # create task v2 - raise NotImplementedError("Skyvern v2 is not implemented") - return TaskRunResponse( - run_id="run_id", - status=TaskRunStatus.queued, - created_at=datetime.datetime.now(datetime.UTC), - updated_at=datetime.datetime.now(datetime.UTC), - ) + try: + task_v2 = await task_v2_service.initialize_task_v2( + organization=current_org, + user_prompt=run_request.goal, + user_url=run_request.url, + totp_identifier=run_request.totp_identifier, + totp_verification_url=run_request.totp_url, + webhook_callback_url=run_request.webhook_url, + proxy_location=run_request.proxy_location, + publish_workflow=run_request.publish_workflow, + extracted_information_schema=run_request.data_extraction_schema, + error_code_mapping=run_request.error_code_mapping, + create_task_run=True, + ) + except LLMProviderError: + LOG.error("LLM failure to initialize task v2", exc_info=True) + raise HTTPException( + status_code=500, detail="Skyvern LLM failure to initialize task v2. Please try again later." + ) + await AsyncExecutorFactory.get_executor().execute_task_v2( + request=request, + background_tasks=background_tasks, + organization_id=current_org.organization_id, + task_v2_id=task_v2.observer_cruise_id, + max_steps_override=run_request.max_steps, + browser_session_id=run_request.browser_session_id, + ) + return TaskRunResponse( + run_id=task_v2.observer_cruise_id, + title=run_request.title, + status=str(task_v2.status), + engine=RunEngine.skyvern_v2, + goal=task_v2.prompt, + url=task_v2.url, + output=task_v2.output, + failure_reason=task_v2.failure_reason, + webhook_url=task_v2.webhook_callback_url, + totp_identifier=task_v2.totp_identifier, + totp_url=task_v2.totp_verification_url, + proxy_location=task_v2.proxy_location, + error_code_mapping=task_v2.error_code_mapping, + data_extraction_schema=task_v2.extracted_information_schema, + created_at=task_v2.created_at, + modified_at=task_v2.modified_at, + ) + raise HTTPException(status_code=400, detail=f"Invalid agent engine: {run_request.engine}") diff --git a/skyvern/forge/sdk/workflow/models/block.py b/skyvern/forge/sdk/workflow/models/block.py index 82b15d96..6e0aead2 100644 --- a/skyvern/forge/sdk/workflow/models/block.py +++ b/skyvern/forge/sdk/workflow/models/block.py @@ -2417,8 +2417,8 @@ class TaskV2Block(Block): browser_session_id: str | None = None, **kwargs: dict, ) -> BlockResult: - from skyvern.forge.sdk.services import task_v2_service from skyvern.forge.sdk.workflow.models.workflow import WorkflowRunStatus + from skyvern.services import task_v2_service workflow_run_context = self.get_workflow_run_context(workflow_run_id) try: @@ -2452,7 +2452,7 @@ class TaskV2Block(Block): if not workflow_run: raise ValueError(f"WorkflowRun not found {workflow_run_id} when running TaskV2Block") task_v2 = await task_v2_service.initialize_task_v2( - organization, + organization=organization, user_prompt=self.prompt, user_url=self.url, parent_workflow_run_id=workflow_run_id, diff --git a/skyvern/schemas/runs.py b/skyvern/schemas/runs.py index 486b3dbf..17899a4b 100644 --- a/skyvern/schemas/runs.py +++ b/skyvern/schemas/runs.py @@ -2,7 +2,9 @@ from datetime import datetime from enum import StrEnum from zoneinfo import ZoneInfo -from pydantic import BaseModel +from pydantic import BaseModel, field_validator + +from skyvern.forge.sdk.core.validators import validate_url class ProxyLocation(StrEnum): @@ -113,6 +115,15 @@ class TaskRunRequest(BaseModel): totp_identifier: str | None = None totp_url: str | None = None browser_session_id: str | None = None + publish_workflow: bool = False + + @field_validator("url", "webhook_url", "totp_url") + @classmethod + def validate_urls(cls, url: str | None) -> str | None: + if url is None: + return None + + return validate_url(url) class TaskRunResponse(BaseModel): diff --git a/skyvern/forge/sdk/services/task_v2_service.py b/skyvern/services/task_v2_service.py similarity index 100% rename from skyvern/forge/sdk/services/task_v2_service.py rename to skyvern/services/task_v2_service.py index eedef7b5..efabf3db 100644 --- a/skyvern/forge/sdk/services/task_v2_service.py +++ b/skyvern/services/task_v2_service.py @@ -95,9 +95,9 @@ async def initialize_task_v2( webhook_callback_url: str | None = None, publish_workflow: bool = False, parent_workflow_run_id: str | None = None, - create_task_run: bool = False, extracted_information_schema: dict | list | str | None = None, error_code_mapping: dict | None = None, + create_task_run: bool = False, ) -> TaskV2: task_v2 = await app.DATABASE.create_task_v2( prompt=user_prompt,