Skip to content

Commit

Permalink
feat(platform): Toast Graph Creation Error (#8536)
Browse files Browse the repository at this point in the history
Co-authored-by: Nicholas Tindle <[email protected]>
  • Loading branch information
majdyz and ntindle authored Nov 5, 2024
1 parent ab0aaf5 commit 45fe26b
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 42 deletions.
5 changes: 4 additions & 1 deletion autogpt_platform/backend/backend/blocks/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,11 @@ class AgentInputBlock(Block):
"""

class Input(BlockSchema):
value: Any = SchemaField(description="The value to be passed as input.")
name: str = SchemaField(description="The name of the input.")
value: Any = SchemaField(
description="The value to be passed as input.",
default=None,
)
description: str = SchemaField(
description="The description of the input.",
default="",
Expand Down
11 changes: 5 additions & 6 deletions autogpt_platform/backend/backend/data/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pydantic import BaseModel
from pydantic_core import PydanticUndefinedType

from backend.blocks.basic import AgentInputBlock, AgentOutputBlock
from backend.blocks.basic import AgentInputBlock, AgentOutputBlock, BlockType
from backend.data.block import BlockInput, get_block, get_blocks
from backend.data.db import BaseDbModel, transaction
from backend.data.execution import ExecutionStatus
Expand Down Expand Up @@ -209,16 +209,15 @@ def sanitize(name):
if block is None:
raise ValueError(f"Invalid block {node.block_id} for node #{node.id}")

if not for_run:
continue # Skip input completion validation, unless when executing.

provided_inputs = set(
[sanitize(name) for name in node.input_default]
+ [sanitize(link.sink_name) for link in node.input_links]
)
for name in block.input_schema.get_required_fields():
if name not in provided_inputs and not isinstance(
block, AgentInputBlock
if name not in provided_inputs and (
for_run # Skip input completion validation, unless when executing.
or block.block_type == BlockType.INPUT
or block.block_type == BlockType.OUTPUT
):
raise ValueError(
f"Node {block.name} #{node.id} required input missing: `{name}`"
Expand Down
48 changes: 28 additions & 20 deletions autogpt_platform/backend/backend/server/rest_api.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import contextlib
import logging
import typing

import fastapi
import fastapi.middleware.cors
import fastapi.responses
import starlette.middleware.cors
import uvicorn

import backend.data.block
Expand All @@ -14,6 +15,7 @@
import backend.util.settings

settings = backend.util.settings.Settings()
logger = logging.getLogger(__name__)


@contextlib.asynccontextmanager
Expand All @@ -25,6 +27,21 @@ async def lifespan_context(app: fastapi.FastAPI):
await backend.data.db.disconnect()


def handle_internal_http_error(status_code: int = 500, log_error: bool = True):
def handler(request: fastapi.Request, exc: Exception):
if log_error:
logger.exception(f"{request.method} {request.url.path} failed: {exc}")
return fastapi.responses.JSONResponse(
content={
"message": f"{request.method} {request.url.path} failed",
"detail": str(exc),
},
status_code=status_code,
)

return handler


docs_url = (
"/docs"
if settings.config.app_env == backend.util.settings.AppEnvironment.LOCAL
Expand All @@ -43,36 +60,27 @@ async def lifespan_context(app: fastapi.FastAPI):
docs_url=docs_url,
)

app.add_exception_handler(ValueError, handle_internal_http_error(400))
app.add_exception_handler(500, handle_internal_http_error(500))
app.include_router(backend.server.routers.v1.v1_router, tags=["v1"])
app.add_middleware(
fastapi.middleware.cors.CORSMiddleware,
allow_origins=settings.config.backend_cors_allow_origins,
allow_credentials=True,
allow_methods=["*"], # Allows all methods
allow_headers=["*"], # Allows all headers
)


@app.get(path="/health", tags=["health"], dependencies=[])
async def health():
return {"status": "healthy"}


@app.exception_handler(Exception)
def handle_internal_http_error(request: fastapi.Request, exc: Exception):
return fastapi.responses.JSONResponse(
content={
"message": f"{request.method} {request.url.path} failed",
"error": str(exc),
},
status_code=500,
)


class AgentServer(backend.util.service.AppProcess):
def run(self):
server_app = starlette.middleware.cors.CORSMiddleware(
app=app,
allow_origins=settings.config.backend_cors_allow_origins,
allow_credentials=True,
allow_methods=["*"], # Allows all methods
allow_headers=["*"], # Allows all headers
)
uvicorn.run(
app,
server_app,
host=backend.util.settings.Config().agent_api_host,
port=backend.util.settings.Config().agent_api_port,
)
Expand Down
21 changes: 10 additions & 11 deletions autogpt_platform/backend/backend/server/ws_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import uvicorn
from autogpt_libs.auth import parse_jwt_token
from fastapi import Depends, FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.cors import CORSMiddleware

from backend.data import redis
from backend.data.queue import AsyncRedisExecutionEventBus
Expand All @@ -31,15 +31,6 @@ async def lifespan(app: FastAPI):
app = FastAPI(lifespan=lifespan, docs_url=docs_url)
_connection_manager = None

logger.info(f"CORS allow origins: {settings.config.backend_cors_allow_origins}")
app.add_middleware(
CORSMiddleware,
allow_origins=settings.config.backend_cors_allow_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


def get_connection_manager():
global _connection_manager
Expand Down Expand Up @@ -176,8 +167,16 @@ async def websocket_router(

class WebsocketServer(AppProcess):
def run(self):
logger.info(f"CORS allow origins: {settings.config.backend_cors_allow_origins}")
server_app = CORSMiddleware(
app=app,
allow_origins=settings.config.backend_cors_allow_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
uvicorn.run(
app,
server_app,
host=Config().websocket_server_host,
port=Config().websocket_server_port,
)
2 changes: 1 addition & 1 deletion autogpt_platform/backend/test/data/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async def test_graph_creation(server: SpinTestServer):
description="Test graph",
nodes=[
Node(id="node_1", block_id=value_block),
Node(id="node_2", block_id=input_block),
Node(id="node_2", block_id=input_block, input_default={"name": "input"}),
Node(id="node_3", block_id=value_block),
],
links=[
Expand Down
2 changes: 1 addition & 1 deletion autogpt_platform/frontend/src/components/ui/toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const ToastViewport = React.forwardRef<
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;

const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border border-neutral-200 p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full dark:border-neutral-800",
"whitespace-pre-line group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border border-neutral-200 p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full dark:border-neutral-800",
{
variants: {
variant: {
Expand Down
22 changes: 21 additions & 1 deletion autogpt_platform/frontend/src/hooks/useAgentGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Connection, MarkerType } from "@xyflow/react";
import Ajv from "ajv";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useRouter, useSearchParams, usePathname } from "next/navigation";
import { useToast } from "@/components/ui/use-toast";

const ajv = new Ajv({ strict: false, allErrors: true });

Expand All @@ -25,6 +26,7 @@ export default function useAgentGraph(
template?: boolean,
passDataToBeads?: boolean,
) {
const { toast } = useToast();
const [router, searchParams, pathname] = [
useRouter(),
useSearchParams(),
Expand Down Expand Up @@ -588,7 +590,8 @@ export default function useAgentGraph(
[availableNodes],
);

const saveAgent = useCallback(
const _saveAgent = (
() =>
async (asTemplate: boolean = false) => {
//FIXME frontend ids should be resolved better (e.g. returned from the server)
// currently this relays on block_id and position
Expand Down Expand Up @@ -742,6 +745,23 @@ export default function useAgentGraph(
},
}));
});
}
)();

const saveAgent = useCallback(
async (asTemplate: boolean = false) => {
try {
await _saveAgent(asTemplate);
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
console.error("Error saving agent", error);
toast({
variant: "destructive",
title: "Error saving agent",
description: errorMessage,
});
}
},
[
api,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ export default class BaseAutoGPTServerAPI {
errorDetail = response.statusText;
}

throw new Error(`HTTP error ${response.status}! ${errorDetail}`);
throw new Error(errorDetail);
}

// Handle responses with no content (like DELETE requests)
Expand Down

0 comments on commit 45fe26b

Please sign in to comment.