fix agent presets API endpoints

This commit is contained in:
Reinier van der Leer
2025-04-09 02:14:28 +02:00
parent 4eab94510a
commit 32cf7fae8e
7 changed files with 189 additions and 116 deletions

View File

@@ -209,7 +209,7 @@ class AgentServer(backend.util.service.AppProcess):
@staticmethod
async def test_create_preset(
preset: backend.server.v2.library.model.CreateLibraryAgentPresetRequest,
preset: backend.server.v2.library.model.LibraryAgentPresetCreatable,
user_id: str,
):
return await backend.server.v2.library.routes.presets.create_preset(
@@ -219,7 +219,7 @@ class AgentServer(backend.util.service.AppProcess):
@staticmethod
async def test_update_preset(
preset_id: str,
preset: backend.server.v2.library.model.CreateLibraryAgentPresetRequest,
preset: backend.server.v2.library.model.LibraryAgentPresetUpdatable,
user_id: str,
):
return await backend.server.v2.library.routes.presets.update_preset(

View File

@@ -1,5 +1,5 @@
import logging
from typing import Optional
from typing import Optional, cast
import fastapi
import prisma.errors
@@ -531,6 +531,7 @@ async def list_presets(
where=query_filter,
skip=(page - 1) * page_size,
take=page_size,
include={"InputPresets": True},
)
total_items = await prisma.models.AgentPreset.prisma().count(where=query_filter)
total_pages = (total_items + page_size - 1) // page_size
@@ -585,69 +586,106 @@ async def get_preset(
raise store_exceptions.DatabaseError("Failed to fetch preset") from e
async def upsert_preset(
async def create_preset(
user_id: str,
preset: library_model.CreateLibraryAgentPresetRequest,
preset_id: Optional[str] = None,
preset: library_model.LibraryAgentPresetCreatable,
) -> library_model.LibraryAgentPreset:
"""
Creates or updates an AgentPreset for a user.
Creates a new AgentPreset for a user.
Args:
user_id: The ID of the user creating/updating the preset.
preset: The preset data used for creation or update.
preset_id: An optional preset ID to update; if None, a new preset is created.
user_id: The ID of the user creating the preset.
preset: The preset data used for creation.
Returns:
The newly created or updated LibraryAgentPreset.
The newly created LibraryAgentPreset.
Raises:
DatabaseError: If there's a database error in creating or updating the preset.
DatabaseError: If there's a database error in creating the preset.
"""
logger.debug(
f"Creating preset ({repr(preset.name)}) for user #{user_id}",
)
try:
new_preset = await prisma.models.AgentPreset.prisma().create(
data=prisma.types.AgentPresetCreateInput(
userId=user_id,
name=preset.name,
description=preset.description,
agentGraphId=preset.graph_id,
agentGraphVersion=preset.graph_version,
isActive=preset.is_active,
InputPresets={
"create": [
cast(
prisma.types.AgentNodeExecutionInputOutputCreateWithoutRelationsInput,
{"name": name, "data": prisma.fields.Json(data)},
)
for name, data in preset.inputs.items()
]
},
),
include={"InputPresets": True},
)
return library_model.LibraryAgentPreset.from_db(new_preset)
except prisma.errors.PrismaError as e:
logger.error(f"Database error creating preset: {e}")
raise store_exceptions.DatabaseError("Failed to create preset") from e
async def update_preset(
user_id: str,
preset_id: str,
preset: library_model.LibraryAgentPresetUpdatable,
) -> library_model.LibraryAgentPreset:
"""
Updates an existing AgentPreset for a user.
Args:
user_id: The ID of the user updating the preset.
preset_id: The ID of the preset to update.
preset: The preset data used for the update.
Returns:
The updated LibraryAgentPreset.
Raises:
DatabaseError: If there's a database error in updating the preset.
ValueError: If attempting to update a non-existent preset.
"""
logger.debug(
f"Upserting preset #{preset_id} ({repr(preset.name)}) for user #{user_id}",
f"Updating preset #{preset_id} ({repr(preset.name)}) for user #{user_id}",
)
try:
inputs: list[
prisma.types.AgentNodeExecutionInputOutputCreateWithoutRelationsInput
] = [
{"name": name, "data": prisma.fields.Json(data)} # type: ignore
for name, data in preset.inputs.items()
]
if preset_id:
# Update existing preset
updated = await prisma.models.AgentPreset.prisma().update(
where={"id": preset_id},
data={
"name": preset.name,
"description": preset.description,
"isActive": preset.is_active,
"InputPresets": {"create": inputs},
},
include={"InputPresets": True},
)
if not updated:
raise ValueError(f"AgentPreset #{preset_id} not found")
return library_model.LibraryAgentPreset.from_db(updated)
else:
# Create new preset
new_preset = await prisma.models.AgentPreset.prisma().create(
data=prisma.types.AgentPresetCreateInput(
userId=user_id,
name=preset.name,
description=preset.description,
agentGraphId=preset.graph_id,
agentGraphVersion=preset.graph_version,
isActive=preset.is_active,
InputPresets={"create": inputs},
),
include={"InputPresets": True},
)
return library_model.LibraryAgentPreset.from_db(new_preset)
update_data: prisma.types.AgentPresetUpdateInput = {}
if preset.name:
update_data["name"] = preset.name
if preset.description:
update_data["description"] = preset.description
if preset.inputs:
update_data["InputPresets"] = {
"create": [
cast(
prisma.types.AgentNodeExecutionInputOutputCreateWithoutRelationsInput,
{"name": name, "data": prisma.fields.Json(data)},
)
for name, data in preset.inputs.items()
]
}
if preset.is_active:
update_data["isActive"] = preset.is_active
updated = await prisma.models.AgentPreset.prisma().update(
where={"id": preset_id},
data=update_data,
include={"InputPresets": True},
)
if not updated:
raise ValueError(f"AgentPreset #{preset_id} not found")
return library_model.LibraryAgentPreset.from_db(updated)
except prisma.errors.PrismaError as e:
logger.error(f"Database error upserting preset: {e}")
raise store_exceptions.DatabaseError("Failed to create preset") from e
logger.error(f"Database error updating preset: {e}")
raise store_exceptions.DatabaseError("Failed to update preset") from e
async def delete_preset(user_id: str, preset_id: str) -> None:

View File

@@ -168,27 +168,49 @@ class LibraryAgentResponse(pydantic.BaseModel):
pagination: server_model.Pagination
class LibraryAgentPreset(pydantic.BaseModel):
"""Represents a preset configuration for a library agent."""
id: str
updated_at: datetime.datetime
class LibraryAgentPresetCreatable(pydantic.BaseModel):
"""
Request model used when creating a new preset for a library agent.
"""
graph_id: str
graph_version: int
inputs: block_model.BlockInput
name: str
description: str
is_active: bool
inputs: block_model.BlockInput
class LibraryAgentPresetUpdatable(pydantic.BaseModel):
"""
Request model used when updating a preset for a library agent.
"""
inputs: Optional[block_model.BlockInput] = None
name: Optional[str] = None
description: Optional[str] = None
is_active: Optional[bool] = None
class LibraryAgentPreset(LibraryAgentPresetCreatable):
"""Represents a preset configuration for a library agent."""
id: str
updated_at: datetime.datetime
@classmethod
def from_db(cls, preset: prisma.models.AgentPreset) -> "LibraryAgentPreset":
if preset.InputPresets is None:
raise ValueError("Input values must be included in object")
input_data: block_model.BlockInput = {}
for preset_input in preset.InputPresets or []:
for preset_input in preset.InputPresets:
input_data[preset_input.name] = preset_input.data
return cls(
@@ -210,19 +232,6 @@ class LibraryAgentPresetResponse(pydantic.BaseModel):
pagination: server_model.Pagination
class CreateLibraryAgentPresetRequest(pydantic.BaseModel):
"""
Request model used when creating a new preset for a library agent.
"""
name: str
description: str
inputs: block_model.BlockInput
graph_id: str
graph_version: int
is_active: bool
class LibraryAgentFilter(str, Enum):
"""Possible filters for searching library agents."""

View File

@@ -105,14 +105,14 @@ async def get_preset(
description="Create a new preset for the current user.",
)
async def create_preset(
preset: models.CreateLibraryAgentPresetRequest,
preset: models.LibraryAgentPresetCreatable,
user_id: str = Depends(autogpt_auth_lib.depends.get_user_id),
) -> models.LibraryAgentPreset:
"""
Create a new library agent preset. Automatically corrects node_input format if needed.
Args:
preset (models.CreateLibraryAgentPresetRequest): The preset data to create.
preset (models.LibraryAgentPresetCreatable): The preset data to create.
user_id (str): ID of the authenticated user.
Returns:
@@ -122,7 +122,7 @@ async def create_preset(
HTTPException: If an error occurs while creating the preset.
"""
try:
return await db.upsert_preset(user_id, preset)
return await db.create_preset(user_id, preset)
except Exception as e:
logger.exception(f"Exception occurred while creating preset: {e}")
raise HTTPException(
@@ -131,22 +131,22 @@ async def create_preset(
)
@router.put(
@router.patch(
"/presets/{preset_id}",
summary="Update an existing preset",
description="Update an existing preset by its ID.",
)
async def update_preset(
preset_id: str,
preset: models.CreateLibraryAgentPresetRequest,
preset: models.LibraryAgentPresetUpdatable,
user_id: str = Depends(autogpt_auth_lib.depends.get_user_id),
) -> models.LibraryAgentPreset:
"""
Update an existing library agent preset. If the preset doesn't exist, it may be created.
Update an existing library agent preset.
Args:
preset_id (str): ID of the preset to update.
preset (models.CreateLibraryAgentPresetRequest): The preset data to update.
preset (models.LibraryAgentPresetUpdatable): The preset data to update.
user_id (str): ID of the authenticated user.
Returns:
@@ -156,7 +156,9 @@ async def update_preset(
HTTPException: If an error occurs while updating the preset.
"""
try:
return await db.upsert_preset(user_id, preset, preset_id)
return await db.update_preset(
user_id=user_id, preset_id=preset_id, preset=preset
)
except Exception as e:
logger.exception(f"Exception occurred whilst updating preset: {e}")
raise HTTPException(

View File

@@ -357,7 +357,7 @@ async def test_execute_preset(server: SpinTestServer):
test_graph = await create_graph(server, test_graph, test_user)
# Create preset with initial values
preset = backend.server.v2.library.model.CreateLibraryAgentPresetRequest(
preset = backend.server.v2.library.model.LibraryAgentPresetCreatable(
name="Test Preset With Clash",
description="Test preset with clashing input values",
graph_id=test_graph.id,
@@ -446,7 +446,7 @@ async def test_execute_preset_with_clash(server: SpinTestServer):
test_graph = await create_graph(server, test_graph, test_user)
# Create preset with initial values
preset = backend.server.v2.library.model.CreateLibraryAgentPresetRequest(
preset = backend.server.v2.library.model.LibraryAgentPresetCreatable(
name="Test Preset With Clash",
description="Test preset with clashing input values",
graph_id=test_graph.id,

View File

@@ -7,7 +7,6 @@ import {
APIKeyPermission,
Block,
CreateAPIKeyResponse,
CreateLibraryAgentPresetRequest,
CreatorDetails,
CreatorsResponse,
Credentials,
@@ -25,15 +24,21 @@ import {
LibraryAgent,
LibraryAgentID,
LibraryAgentPreset,
LibraryAgentPresetCreatable,
LibraryAgentPresetID,
LibraryAgentPresetResponse,
LibraryAgentPresetUpdatable,
LibraryAgentResponse,
LibraryAgentSortEnum,
MyAgentsResponse,
NodeExecutionResult,
NotificationPreference,
NotificationPreferenceDTO,
OttoQuery,
OttoResponse,
ProfileDetails,
RefundRequest,
ReviewSubmissionRequest,
Schedule,
ScheduleCreatable,
ScheduleID,
@@ -45,14 +50,11 @@ import {
StoreSubmission,
StoreSubmissionRequest,
StoreSubmissionsResponse,
SubmissionStatus,
TransactionHistory,
User,
UserPasswordCredentials,
OttoQuery,
OttoResponse,
UserOnboarding,
ReviewSubmissionRequest,
SubmissionStatus,
UserPasswordCredentials,
} from "./types";
import { createBrowserClient } from "@supabase/ssr";
import getServerSupabase from "../supabase/getServerSupabase";
@@ -593,43 +595,61 @@ export default class BackendAPI {
await this._request("PUT", `/library/agents/${libraryAgentId}`, params);
}
listLibraryAgentPresets(params?: {
async listLibraryAgentPresets(params?: {
graph_id?: GraphID;
page?: number;
page_size?: number;
}): Promise<LibraryAgentPresetResponse> {
return this._get("/library/presets", params);
const response: LibraryAgentPresetResponse = await this._get(
"/library/presets",
params,
);
return {
...response,
presets: response.presets.map(parseLibraryAgentPresetTimestamp),
};
}
getLibraryAgentPreset(presetId: string): Promise<LibraryAgentPreset> {
return this._get(`/library/presets/${presetId}`);
}
createLibraryAgentPreset(
preset: CreateLibraryAgentPresetRequest,
async getLibraryAgentPreset(
presetID: LibraryAgentPresetID,
): Promise<LibraryAgentPreset> {
return this._request("POST", "/library/presets", preset);
const preset = await this._get(`/library/presets/${presetID}`);
return parseLibraryAgentPresetTimestamp(preset);
}
updateLibraryAgentPreset(
presetId: string,
preset: CreateLibraryAgentPresetRequest,
async createLibraryAgentPreset(
preset: LibraryAgentPresetCreatable,
): Promise<LibraryAgentPreset> {
return this._request("PUT", `/library/presets/${presetId}`, preset);
const new_preset = await this._request("POST", "/library/presets", preset);
return parseLibraryAgentPresetTimestamp(new_preset);
}
async deleteLibraryAgentPreset(presetId: string): Promise<void> {
await this._request("DELETE", `/library/presets/${presetId}`);
async updateLibraryAgentPreset(
presetID: LibraryAgentPresetID,
partial_preset: LibraryAgentPresetUpdatable,
): Promise<LibraryAgentPreset> {
const updated_preset = await this._request(
"PATCH",
`/library/presets/${presetID}`,
partial_preset,
);
return parseLibraryAgentPresetTimestamp(updated_preset);
}
async deleteLibraryAgentPreset(
presetID: LibraryAgentPresetID,
): Promise<void> {
await this._request("DELETE", `/library/presets/${presetID}`);
}
executeLibraryAgentPreset(
presetId: string,
graphId: GraphID,
presetID: LibraryAgentPresetID,
graphID: GraphID,
graphVersion: number,
nodeInput: { [key: string]: any },
): Promise<{ id: string }> {
return this._request("POST", `/library/presets/${presetId}/execute`, {
graph_id: graphId,
): Promise<{ id: GraphExecutionID }> {
return this._request("POST", `/library/presets/${presetID}/execute`, {
graph_id: graphID,
graph_version: graphVersion,
node_input: nodeInput,
});
@@ -1027,6 +1047,10 @@ function parseScheduleTimestamp(result: any): Schedule {
return _parseObjectTimestamps<Schedule>(result, ["next_run_time"]);
}
function parseLibraryAgentPresetTimestamp(result: any): LibraryAgentPreset {
return _parseObjectTimestamps<LibraryAgentPreset>(result, ["updated_at"]);
}
function _parseObjectTimestamps<T>(obj: any, keys: (keyof T)[]): T {
const result = { ...obj };
keys.forEach(

View File

@@ -395,10 +395,10 @@ export type LibraryAgentPreset = {
updated_at: Date;
graph_id: GraphID;
graph_version: number;
inputs: { [key: string]: any };
name: string;
description: string;
is_active: boolean;
inputs: { [key: string]: any };
};
export type LibraryAgentPresetID = Brand<string, "LibraryAgentPresetID">;
@@ -412,14 +412,14 @@ export type LibraryAgentPresetResponse = {
};
};
export type CreateLibraryAgentPresetRequest = {
name: string;
description: string;
inputs: { [key: string]: any };
graph_id: GraphID;
graph_version: number;
is_active: boolean;
};
export type LibraryAgentPresetCreatable = Omit<
LibraryAgentPreset,
"id" | "updated_at"
>;
export type LibraryAgentPresetUpdatable = Partial<
Omit<LibraryAgentPresetCreatable, "graph_id" | "graph_version">
>;
export enum LibraryAgentSortEnum {
CREATED_AT = "createdAt",