mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-08 13:55:06 -05:00
Compare commits
5 Commits
fix/execut
...
zamilmajdy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
992acef36f | ||
|
|
b99851b7ab | ||
|
|
9bb81e6a23 | ||
|
|
89d563dff0 | ||
|
|
eb1e53aba3 |
@@ -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="",
|
||||
|
||||
@@ -10,7 +10,7 @@ from prisma.types import AgentGraphInclude
|
||||
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
|
||||
@@ -209,16 +209,15 @@ class Graph(GraphMeta):
|
||||
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}`"
|
||||
|
||||
@@ -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
|
||||
@@ -14,6 +15,7 @@ import backend.util.service
|
||||
import backend.util.settings
|
||||
|
||||
settings = backend.util.settings.Settings()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
@@ -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
|
||||
@@ -43,14 +60,9 @@ 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=[])
|
||||
@@ -58,21 +70,17 @@ 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,
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ from contextlib import asynccontextmanager
|
||||
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
|
||||
@@ -31,15 +31,6 @@ docs_url = "/docs" if settings.config.app_env == AppEnvironment.LOCAL else None
|
||||
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
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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=[
|
||||
|
||||
@@ -163,38 +163,6 @@ export function CustomNode({
|
||||
if (!schema?.properties) return null;
|
||||
let keys = Object.entries(schema.properties);
|
||||
switch (nodeType) {
|
||||
case BlockUIType.INPUT:
|
||||
// For INPUT blocks, dont include connection handles
|
||||
return keys.map(([propKey, propSchema]) => {
|
||||
const isRequired = data.inputSchema.required?.includes(propKey);
|
||||
const isConnected = isHandleConnected(propKey);
|
||||
const isAdvanced = propSchema.advanced;
|
||||
return (
|
||||
(isRequired || isAdvancedOpen || !isAdvanced) && (
|
||||
<div key={propKey} data-id={`input-handle-${propKey}`}>
|
||||
<span className="text-m green mb-0 text-gray-900">
|
||||
{propSchema.title || beautifyString(propKey)}
|
||||
</span>
|
||||
<div key={propKey}>
|
||||
{!isConnected && (
|
||||
<NodeGenericInputField
|
||||
nodeId={id}
|
||||
propKey={propKey}
|
||||
propSchema={propSchema}
|
||||
currentValue={getValue(propKey)}
|
||||
connections={data.connections}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
errors={data.errors ?? {}}
|
||||
displayName={propSchema.title || beautifyString(propKey)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
case BlockUIType.NOTE:
|
||||
// For NOTE blocks, don't render any input handles
|
||||
const [noteKey, noteSchema] = keys[0];
|
||||
@@ -213,59 +181,22 @@ export function CustomNode({
|
||||
</div>
|
||||
);
|
||||
|
||||
case BlockUIType.OUTPUT:
|
||||
// For OUTPUT blocks, only show the 'value' property
|
||||
return keys.map(([propKey, propSchema]) => {
|
||||
const isRequired = data.inputSchema.required?.includes(propKey);
|
||||
const isConnected = isHandleConnected(propKey);
|
||||
const isAdvanced = propSchema.advanced;
|
||||
return (
|
||||
(isRequired || isAdvancedOpen || !isAdvanced) && (
|
||||
<div key={propKey} data-id={`output-handle-${propKey}`}>
|
||||
{propKey !== "value" ? (
|
||||
<span className="text-m green mb-0 text-gray-900">
|
||||
{propSchema.title || beautifyString(propKey)}
|
||||
</span>
|
||||
) : (
|
||||
<NodeHandle
|
||||
keyName={propKey}
|
||||
isConnected={isConnected}
|
||||
isRequired={isRequired}
|
||||
schema={propSchema}
|
||||
side="left"
|
||||
/>
|
||||
)}
|
||||
{!isConnected && (
|
||||
<NodeGenericInputField
|
||||
nodeId={id}
|
||||
propKey={propKey}
|
||||
propSchema={propSchema}
|
||||
currentValue={getValue(propKey)}
|
||||
connections={data.connections}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
errors={data.errors ?? {}}
|
||||
displayName={propSchema.title || beautifyString(propKey)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
default:
|
||||
return keys.map(([propKey, propSchema]) => {
|
||||
const isConnectable =
|
||||
// No input connection handles for credentials
|
||||
propKey !== "credentials" &&
|
||||
// For INPUT blocks, no input connection handles.
|
||||
!(nodeType == BlockUIType.INPUT) &&
|
||||
// For OUTPUT blocks, only show the 'value' (hides 'name') input connection handle
|
||||
!(nodeType == BlockUIType.OUTPUT && propKey == "name");
|
||||
const isRequired = data.inputSchema.required?.includes(propKey);
|
||||
const isConnected = isHandleConnected(propKey);
|
||||
const isAdvanced = propSchema.advanced;
|
||||
return (
|
||||
(isRequired || isAdvancedOpen || isConnected || !isAdvanced) && (
|
||||
<div key={propKey} data-id={`input-handle-${propKey}`}>
|
||||
{"credentials_provider" in propSchema ? (
|
||||
<span className="text-m green mb-0 text-gray-900">
|
||||
Credentials
|
||||
</span>
|
||||
) : (
|
||||
{isConnectable ? (
|
||||
<NodeHandle
|
||||
keyName={propKey}
|
||||
isConnected={isConnected}
|
||||
@@ -273,8 +204,14 @@ export function CustomNode({
|
||||
schema={propSchema}
|
||||
side="left"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-m green mb-0 text-gray-900">
|
||||
{propKey == "credentials"
|
||||
? "Credentials"
|
||||
: propSchema.title || beautifyString(propKey)}
|
||||
</span>
|
||||
)}
|
||||
{!isConnected && (
|
||||
{isConnected || (
|
||||
<NodeGenericInputField
|
||||
nodeId={id}
|
||||
propKey={propKey}
|
||||
|
||||
@@ -53,7 +53,6 @@ const NodeObjectInputTree: FC<NodeObjectInputTreeProps> = ({
|
||||
object ||= ("default" in schema ? schema.default : null) ?? {};
|
||||
return (
|
||||
<div className={cn(className, "w-full flex-col")}>
|
||||
{displayName && <strong>{displayName}</strong>}
|
||||
{Object.entries(schema.properties).map(([propKey, propSchema]) => {
|
||||
const childKey = selfKey ? `${selfKey}.${propKey}` : propKey;
|
||||
|
||||
@@ -507,7 +506,6 @@ const NodeArrayInput: FC<{
|
||||
typeof errors[selfKey] === "string" ? errors[selfKey] : undefined;
|
||||
return (
|
||||
<div className={cn(className, "flex flex-col")}>
|
||||
{displayName && <strong>{displayName}</strong>}
|
||||
{entries.map((entry: any, index: number) => {
|
||||
const entryKey = `${selfKey}_$_${index}`;
|
||||
const isConnected =
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -25,6 +26,7 @@ export default function useAgentGraph(
|
||||
template?: boolean,
|
||||
passDataToBeads?: boolean,
|
||||
) {
|
||||
const { toast } = useToast();
|
||||
const [router, searchParams, pathname] = [
|
||||
useRouter(),
|
||||
useSearchParams(),
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user