mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-08 22:05:08 -05:00
refactor(backend): Clean up Library & Store DB schema (#9774)
Distilled from #9541 to reduce the scope of that PR. - Part of #9307 - ❗ Blocks #9786 - ❗ Blocks #9541 ### Changes 🏗️ - Fix `LibraryAgent` schema (for #9786) - Fix relationships between `LibraryAgent`, `AgentGraph`, and `AgentPreset` - Impose uniqueness constraint on `LibraryAgent` - Rename things that are called `agent` that actually refer to a `graph`/`agentGraph` - Fix singular/plural forms in DB schema - Simplify reference names of closely related objects (e.g. `AgentGraph.AgentGraphExecutions` -> `AgentGraph.Executions`) - Eliminate use of `# type: ignore` in DB statements - Add `typed` and `typed_cast` utilities to `backend.util.type` ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] CI static type checking (with all risky `# type: ignore` removed) - [x] Check that column references in views are updated
This commit is contained in:
committed by
GitHub
parent
70890dee43
commit
353396110c
@@ -3,6 +3,7 @@ import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
from typing import cast
|
||||
|
||||
import stripe
|
||||
from autogpt_libs.utils.cache import thread_cached
|
||||
@@ -18,6 +19,7 @@ from prisma.types import (
|
||||
CreditRefundRequestCreateInput,
|
||||
CreditTransactionCreateInput,
|
||||
CreditTransactionWhereInput,
|
||||
IntFilter,
|
||||
)
|
||||
from tenacity import retry, stop_after_attempt, wait_exponential
|
||||
|
||||
@@ -213,7 +215,7 @@ class UserCreditBase(ABC):
|
||||
"userId": user_id,
|
||||
"createdAt": {"lte": top_time},
|
||||
"isActive": True,
|
||||
"runningBalance": {"not": None}, # type: ignore
|
||||
"runningBalance": cast(IntFilter, {"not": None}),
|
||||
},
|
||||
order={"createdAt": "desc"},
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ from prisma.models import (
|
||||
AgentNodeExecutionInputOutput,
|
||||
)
|
||||
from prisma.types import (
|
||||
AgentGraphExecutionCreateInput,
|
||||
AgentGraphExecutionWhereInput,
|
||||
AgentNodeExecutionCreateInput,
|
||||
AgentNodeExecutionInputOutputCreateInput,
|
||||
@@ -121,7 +122,7 @@ class GraphExecution(GraphExecutionMeta):
|
||||
|
||||
@staticmethod
|
||||
def from_db(_graph_exec: AgentGraphExecution):
|
||||
if _graph_exec.AgentNodeExecutions is None:
|
||||
if _graph_exec.NodeExecutions is None:
|
||||
raise ValueError("Node executions must be included in query")
|
||||
|
||||
graph_exec = GraphExecutionMeta.from_db(_graph_exec)
|
||||
@@ -129,7 +130,7 @@ class GraphExecution(GraphExecutionMeta):
|
||||
complete_node_executions = sorted(
|
||||
[
|
||||
NodeExecutionResult.from_db(ne, _graph_exec.userId)
|
||||
for ne in _graph_exec.AgentNodeExecutions
|
||||
for ne in _graph_exec.NodeExecutions
|
||||
if ne.executionStatus != ExecutionStatus.INCOMPLETE
|
||||
],
|
||||
key=lambda ne: (ne.queue_time is None, ne.queue_time or ne.add_time),
|
||||
@@ -181,7 +182,7 @@ class GraphExecutionWithNodes(GraphExecution):
|
||||
|
||||
@staticmethod
|
||||
def from_db(_graph_exec: AgentGraphExecution):
|
||||
if _graph_exec.AgentNodeExecutions is None:
|
||||
if _graph_exec.NodeExecutions is None:
|
||||
raise ValueError("Node executions must be included in query")
|
||||
|
||||
graph_exec_with_io = GraphExecution.from_db(_graph_exec)
|
||||
@@ -189,7 +190,7 @@ class GraphExecutionWithNodes(GraphExecution):
|
||||
node_executions = sorted(
|
||||
[
|
||||
NodeExecutionResult.from_db(ne, _graph_exec.userId)
|
||||
for ne in _graph_exec.AgentNodeExecutions
|
||||
for ne in _graph_exec.NodeExecutions
|
||||
],
|
||||
key=lambda ne: (ne.queue_time is None, ne.queue_time or ne.add_time),
|
||||
)
|
||||
@@ -220,21 +221,21 @@ class NodeExecutionResult(BaseModel):
|
||||
end_time: datetime | None
|
||||
|
||||
@staticmethod
|
||||
def from_db(execution: AgentNodeExecution, user_id: Optional[str] = None):
|
||||
if execution.executionData:
|
||||
def from_db(_node_exec: AgentNodeExecution, user_id: Optional[str] = None):
|
||||
if _node_exec.executionData:
|
||||
# Execution that has been queued for execution will persist its data.
|
||||
input_data = type_utils.convert(execution.executionData, dict[str, Any])
|
||||
input_data = type_utils.convert(_node_exec.executionData, dict[str, Any])
|
||||
else:
|
||||
# For incomplete execution, executionData will not be yet available.
|
||||
input_data: BlockInput = defaultdict()
|
||||
for data in execution.Input or []:
|
||||
for data in _node_exec.Input or []:
|
||||
input_data[data.name] = type_utils.convert(data.data, type[Any])
|
||||
|
||||
output_data: CompletedBlockOutput = defaultdict(list)
|
||||
for data in execution.Output or []:
|
||||
for data in _node_exec.Output or []:
|
||||
output_data[data.name].append(type_utils.convert(data.data, type[Any]))
|
||||
|
||||
graph_execution: AgentGraphExecution | None = execution.AgentGraphExecution
|
||||
graph_execution: AgentGraphExecution | None = _node_exec.GraphExecution
|
||||
if graph_execution:
|
||||
user_id = graph_execution.userId
|
||||
elif not user_id:
|
||||
@@ -246,17 +247,17 @@ class NodeExecutionResult(BaseModel):
|
||||
user_id=user_id,
|
||||
graph_id=graph_execution.agentGraphId if graph_execution else "",
|
||||
graph_version=graph_execution.agentGraphVersion if graph_execution else 0,
|
||||
graph_exec_id=execution.agentGraphExecutionId,
|
||||
block_id=execution.AgentNode.agentBlockId if execution.AgentNode else "",
|
||||
node_exec_id=execution.id,
|
||||
node_id=execution.agentNodeId,
|
||||
status=execution.executionStatus,
|
||||
graph_exec_id=_node_exec.agentGraphExecutionId,
|
||||
block_id=_node_exec.Node.agentBlockId if _node_exec.Node else "",
|
||||
node_exec_id=_node_exec.id,
|
||||
node_id=_node_exec.agentNodeId,
|
||||
status=_node_exec.executionStatus,
|
||||
input_data=input_data,
|
||||
output_data=output_data,
|
||||
add_time=execution.addedTime,
|
||||
queue_time=execution.queuedTime,
|
||||
start_time=execution.startedTime,
|
||||
end_time=execution.endedTime,
|
||||
add_time=_node_exec.addedTime,
|
||||
queue_time=_node_exec.queuedTime,
|
||||
start_time=_node_exec.startedTime,
|
||||
end_time=_node_exec.endedTime,
|
||||
)
|
||||
|
||||
|
||||
@@ -351,29 +352,29 @@ async def create_graph_execution(
|
||||
The id of the AgentGraphExecution and the list of ExecutionResult for each node.
|
||||
"""
|
||||
result = await AgentGraphExecution.prisma().create(
|
||||
data={
|
||||
"agentGraphId": graph_id,
|
||||
"agentGraphVersion": graph_version,
|
||||
"executionStatus": ExecutionStatus.QUEUED,
|
||||
"AgentNodeExecutions": {
|
||||
"create": [ # type: ignore
|
||||
{
|
||||
"agentNodeId": node_id,
|
||||
"executionStatus": ExecutionStatus.QUEUED,
|
||||
"queuedTime": datetime.now(tz=timezone.utc),
|
||||
"Input": {
|
||||
data=AgentGraphExecutionCreateInput(
|
||||
agentGraphId=graph_id,
|
||||
agentGraphVersion=graph_version,
|
||||
executionStatus=ExecutionStatus.QUEUED,
|
||||
NodeExecutions={
|
||||
"create": [
|
||||
AgentNodeExecutionCreateInput(
|
||||
agentNodeId=node_id,
|
||||
executionStatus=ExecutionStatus.QUEUED,
|
||||
queuedTime=datetime.now(tz=timezone.utc),
|
||||
Input={
|
||||
"create": [
|
||||
{"name": name, "data": Json(data)}
|
||||
for name, data in node_input.items()
|
||||
]
|
||||
},
|
||||
}
|
||||
)
|
||||
for node_id, node_input in nodes_input
|
||||
]
|
||||
},
|
||||
"userId": user_id,
|
||||
"agentPresetId": preset_id,
|
||||
},
|
||||
userId=user_id,
|
||||
agentPresetId=preset_id,
|
||||
),
|
||||
include=GRAPH_EXECUTION_INCLUDE_WITH_NODES,
|
||||
)
|
||||
|
||||
@@ -600,7 +601,7 @@ async def get_node_execution_results(
|
||||
"agentGraphExecutionId": graph_exec_id,
|
||||
}
|
||||
if block_ids:
|
||||
where_clause["AgentNode"] = {"is": {"agentBlockId": {"in": block_ids}}}
|
||||
where_clause["Node"] = {"is": {"agentBlockId": {"in": block_ids}}}
|
||||
if statuses:
|
||||
where_clause["OR"] = [{"executionStatus": status} for status in statuses]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
from typing import Any, Literal, Optional, Type
|
||||
from typing import Any, Literal, Optional, Type, cast
|
||||
|
||||
import prisma
|
||||
from prisma import Json
|
||||
@@ -10,7 +10,9 @@ from prisma.models import AgentGraph, AgentNode, AgentNodeLink, StoreListingVers
|
||||
from prisma.types import (
|
||||
AgentGraphCreateInput,
|
||||
AgentGraphWhereInput,
|
||||
AgentGraphWhereInputRecursive1,
|
||||
AgentNodeCreateInput,
|
||||
AgentNodeIncludeFromAgentNodeRecursive1,
|
||||
AgentNodeLinkCreateInput,
|
||||
)
|
||||
from pydantic.fields import computed_field
|
||||
@@ -465,13 +467,11 @@ class GraphModel(Graph):
|
||||
is_active=graph.isActive,
|
||||
name=graph.name or "",
|
||||
description=graph.description or "",
|
||||
nodes=[
|
||||
NodeModel.from_db(node, for_export) for node in graph.AgentNodes or []
|
||||
],
|
||||
nodes=[NodeModel.from_db(node, for_export) for node in graph.Nodes or []],
|
||||
links=list(
|
||||
{
|
||||
Link.from_db(link)
|
||||
for node in graph.AgentNodes or []
|
||||
for node in graph.Nodes or []
|
||||
for link in (node.Input or []) + (node.Output or [])
|
||||
}
|
||||
),
|
||||
@@ -602,8 +602,8 @@ async def get_graph(
|
||||
and not (
|
||||
await StoreListingVersion.prisma().find_first(
|
||||
where={
|
||||
"agentId": graph_id,
|
||||
"agentVersion": version or graph.version,
|
||||
"agentGraphId": graph_id,
|
||||
"agentGraphVersion": version or graph.version,
|
||||
"isDeleted": False,
|
||||
"submissionStatus": SubmissionStatus.APPROVED,
|
||||
}
|
||||
@@ -637,12 +637,16 @@ async def get_sub_graphs(graph: AgentGraph) -> list[AgentGraph]:
|
||||
sub_graph_ids = [
|
||||
(graph_id, graph_version)
|
||||
for graph in search_graphs
|
||||
for node in graph.AgentNodes or []
|
||||
for node in graph.Nodes or []
|
||||
if (
|
||||
node.AgentBlock
|
||||
and node.AgentBlock.id == agent_block_id
|
||||
and (graph_id := dict(node.constantInput).get("graph_id"))
|
||||
and (graph_version := dict(node.constantInput).get("graph_version"))
|
||||
and (graph_id := cast(str, dict(node.constantInput).get("graph_id")))
|
||||
and (
|
||||
graph_version := cast(
|
||||
int, dict(node.constantInput).get("graph_version")
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
if not sub_graph_ids:
|
||||
@@ -651,13 +655,16 @@ async def get_sub_graphs(graph: AgentGraph) -> list[AgentGraph]:
|
||||
graphs = await AgentGraph.prisma().find_many(
|
||||
where={
|
||||
"OR": [
|
||||
{
|
||||
"id": graph_id,
|
||||
"version": graph_version,
|
||||
"userId": graph.userId, # Ensure the sub-graph is owned by the same user
|
||||
}
|
||||
type_utils.typed(
|
||||
AgentGraphWhereInputRecursive1,
|
||||
{
|
||||
"id": graph_id,
|
||||
"version": graph_version,
|
||||
"userId": graph.userId, # Ensure the sub-graph is owned by the same user
|
||||
},
|
||||
)
|
||||
for graph_id, graph_version in sub_graph_ids
|
||||
] # type: ignore
|
||||
]
|
||||
},
|
||||
include=AGENT_GRAPH_INCLUDE,
|
||||
)
|
||||
@@ -671,7 +678,13 @@ async def get_sub_graphs(graph: AgentGraph) -> list[AgentGraph]:
|
||||
async def get_connected_output_nodes(node_id: str) -> list[tuple[Link, Node]]:
|
||||
links = await AgentNodeLink.prisma().find_many(
|
||||
where={"agentNodeSourceId": node_id},
|
||||
include={"AgentNodeSink": {"include": AGENT_NODE_INCLUDE}}, # type: ignore
|
||||
include={
|
||||
"AgentNodeSink": {
|
||||
"include": cast(
|
||||
AgentNodeIncludeFromAgentNodeRecursive1, AGENT_NODE_INCLUDE
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
return [
|
||||
(Link.from_db(link), NodeModel.from_db(link.AgentNodeSink))
|
||||
@@ -829,12 +842,12 @@ async def fix_llm_provider_credentials():
|
||||
SELECT graph."userId" user_id,
|
||||
node.id node_id,
|
||||
node."constantInput" node_preset_input
|
||||
FROM platform."AgentNode" node
|
||||
LEFT JOIN platform."AgentGraph" graph
|
||||
ON node."agentGraphId" = graph.id
|
||||
WHERE node."constantInput"::jsonb->'credentials'->>'provider' = 'llm'
|
||||
ORDER BY graph."userId";
|
||||
"""
|
||||
FROM platform."AgentNode" node
|
||||
LEFT JOIN platform."AgentGraph" graph
|
||||
ON node."agentGraphId" = graph.id
|
||||
WHERE node."constantInput"::jsonb->'credentials'->>'provider' = 'llm'
|
||||
ORDER BY graph."userId";
|
||||
"""
|
||||
)
|
||||
logger.info(f"Fixing LLM credential inputs on {len(broken_nodes)} nodes")
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
from typing import cast
|
||||
|
||||
import prisma.enums
|
||||
import prisma.types
|
||||
|
||||
from backend.blocks.io import IO_BLOCK_IDs
|
||||
from backend.util.type import typed_cast
|
||||
|
||||
AGENT_NODE_INCLUDE: prisma.types.AgentNodeInclude = {
|
||||
"Input": True,
|
||||
@@ -11,25 +14,31 @@ AGENT_NODE_INCLUDE: prisma.types.AgentNodeInclude = {
|
||||
}
|
||||
|
||||
AGENT_GRAPH_INCLUDE: prisma.types.AgentGraphInclude = {
|
||||
"AgentNodes": {"include": AGENT_NODE_INCLUDE} # type: ignore
|
||||
"Nodes": {
|
||||
"include": typed_cast(
|
||||
prisma.types.AgentNodeIncludeFromAgentNodeRecursive1,
|
||||
prisma.types.AgentNodeIncludeFromAgentNode,
|
||||
AGENT_NODE_INCLUDE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
EXECUTION_RESULT_INCLUDE: prisma.types.AgentNodeExecutionInclude = {
|
||||
"Input": True,
|
||||
"Output": True,
|
||||
"AgentNode": True,
|
||||
"AgentGraphExecution": True,
|
||||
"Node": True,
|
||||
"GraphExecution": True,
|
||||
}
|
||||
|
||||
MAX_NODE_EXECUTIONS_FETCH = 1000
|
||||
|
||||
GRAPH_EXECUTION_INCLUDE_WITH_NODES: prisma.types.AgentGraphExecutionInclude = {
|
||||
"AgentNodeExecutions": {
|
||||
"NodeExecutions": {
|
||||
"include": {
|
||||
"Input": True,
|
||||
"Output": True,
|
||||
"AgentNode": True,
|
||||
"AgentGraphExecution": True,
|
||||
"Node": True,
|
||||
"GraphExecution": True,
|
||||
},
|
||||
"order_by": [
|
||||
{"queuedTime": "desc"},
|
||||
@@ -41,31 +50,42 @@ GRAPH_EXECUTION_INCLUDE_WITH_NODES: prisma.types.AgentGraphExecutionInclude = {
|
||||
}
|
||||
|
||||
GRAPH_EXECUTION_INCLUDE: prisma.types.AgentGraphExecutionInclude = {
|
||||
"AgentNodeExecutions": {
|
||||
**GRAPH_EXECUTION_INCLUDE_WITH_NODES["AgentNodeExecutions"], # type: ignore
|
||||
"NodeExecutions": {
|
||||
**cast(
|
||||
prisma.types.FindManyAgentNodeExecutionArgsFromAgentGraphExecution,
|
||||
GRAPH_EXECUTION_INCLUDE_WITH_NODES["NodeExecutions"],
|
||||
),
|
||||
"where": {
|
||||
"AgentNode": {
|
||||
"AgentBlock": {"id": {"in": IO_BLOCK_IDs}}, # type: ignore
|
||||
},
|
||||
"NOT": {
|
||||
"executionStatus": prisma.enums.AgentExecutionStatus.INCOMPLETE,
|
||||
},
|
||||
"Node": typed_cast(
|
||||
prisma.types.AgentNodeRelationFilter,
|
||||
prisma.types.AgentNodeWhereInput,
|
||||
{
|
||||
"AgentBlock": {"id": {"in": IO_BLOCK_IDs}},
|
||||
},
|
||||
),
|
||||
"NOT": [{"executionStatus": prisma.enums.AgentExecutionStatus.INCOMPLETE}],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
INTEGRATION_WEBHOOK_INCLUDE: prisma.types.IntegrationWebhookInclude = {
|
||||
"AgentNodes": {"include": AGENT_NODE_INCLUDE} # type: ignore
|
||||
"AgentNodes": {
|
||||
"include": typed_cast(
|
||||
prisma.types.AgentNodeIncludeFromAgentNodeRecursive1,
|
||||
prisma.types.AgentNodeInclude,
|
||||
AGENT_NODE_INCLUDE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def library_agent_include(user_id: str) -> prisma.types.LibraryAgentInclude:
|
||||
return {
|
||||
"Agent": {
|
||||
"AgentGraph": {
|
||||
"include": {
|
||||
**AGENT_GRAPH_INCLUDE,
|
||||
"AgentGraphExecution": {"where": {"userId": user_id}},
|
||||
"Executions": {"where": {"userId": user_id}},
|
||||
}
|
||||
},
|
||||
"Creator": True,
|
||||
|
||||
@@ -6,7 +6,7 @@ import pydantic
|
||||
from prisma import Json
|
||||
from prisma.enums import OnboardingStep
|
||||
from prisma.models import UserOnboarding
|
||||
from prisma.types import UserOnboardingUpdateInput
|
||||
from prisma.types import UserOnboardingCreateInput, UserOnboardingUpdateInput
|
||||
|
||||
from backend.data.block import get_blocks
|
||||
from backend.data.graph import GraphModel
|
||||
@@ -38,7 +38,7 @@ async def get_user_onboarding(user_id: str):
|
||||
return await UserOnboarding.prisma().upsert(
|
||||
where={"userId": user_id},
|
||||
data={
|
||||
"create": {"userId": user_id}, # type: ignore
|
||||
"create": UserOnboardingCreateInput(userId=user_id),
|
||||
"update": {},
|
||||
},
|
||||
)
|
||||
@@ -186,11 +186,11 @@ async def get_recommended_agents(user_id: str) -> list[StoreAgentDetails]:
|
||||
where={
|
||||
"id": {"in": [agent.storeListingVersionId for agent in storeAgents]},
|
||||
},
|
||||
include={"Agent": True},
|
||||
include={"AgentGraph": True},
|
||||
)
|
||||
|
||||
for listing in agentListings:
|
||||
agent = listing.Agent
|
||||
agent = listing.AgentGraph
|
||||
if agent is None:
|
||||
continue
|
||||
graph = GraphModel.from_db(agent)
|
||||
|
||||
@@ -11,7 +11,7 @@ from fastapi import HTTPException
|
||||
from prisma import Json
|
||||
from prisma.enums import NotificationType
|
||||
from prisma.models import User
|
||||
from prisma.types import UserCreateInput, UserUpdateInput
|
||||
from prisma.types import JsonFilter, UserCreateInput, UserUpdateInput
|
||||
|
||||
from backend.data.db import prisma
|
||||
from backend.data.model import UserIntegrations, UserMetadata, UserMetadataRaw
|
||||
@@ -135,10 +135,15 @@ async def migrate_and_encrypt_user_integrations():
|
||||
"""Migrate integration credentials and OAuth states from metadata to integrations column."""
|
||||
users = await User.prisma().find_many(
|
||||
where={
|
||||
"metadata": {
|
||||
"path": ["integration_credentials"],
|
||||
"not": Json({"a": "yolo"}), # bogus value works to check if key exists
|
||||
} # type: ignore
|
||||
"metadata": cast(
|
||||
JsonFilter,
|
||||
{
|
||||
"path": ["integration_credentials"],
|
||||
"not": Json(
|
||||
{"a": "yolo"}
|
||||
), # bogus value works to check if key exists
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
logger.info(f"Migrating integration credentials for {len(users)} users")
|
||||
|
||||
@@ -6,7 +6,6 @@ import prisma.errors
|
||||
import prisma.fields
|
||||
import prisma.models
|
||||
import prisma.types
|
||||
from prisma.types import AgentPresetCreateInput
|
||||
|
||||
import backend.data.graph
|
||||
import backend.server.model
|
||||
@@ -69,12 +68,12 @@ async def list_library_agents(
|
||||
if search_term:
|
||||
where_clause["OR"] = [
|
||||
{
|
||||
"Agent": {
|
||||
"AgentGraph": {
|
||||
"is": {"name": {"contains": search_term, "mode": "insensitive"}}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Agent": {
|
||||
"AgentGraph": {
|
||||
"is": {
|
||||
"description": {"contains": search_term, "mode": "insensitive"}
|
||||
}
|
||||
@@ -233,7 +232,8 @@ async def create_library_agent(
|
||||
isCreatedByUser=(user_id == graph.user_id),
|
||||
useGraphIsActiveVersion=True,
|
||||
User={"connect": {"id": user_id}},
|
||||
Agent={
|
||||
# Creator={"connect": {"id": agent.userId}},
|
||||
AgentGraph={
|
||||
"connect": {
|
||||
"graphVersionId": {"id": graph.id, "version": graph.version}
|
||||
}
|
||||
@@ -247,38 +247,41 @@ async def create_library_agent(
|
||||
|
||||
async def update_agent_version_in_library(
|
||||
user_id: str,
|
||||
agent_id: str,
|
||||
agent_version: int,
|
||||
agent_graph_id: str,
|
||||
agent_graph_version: int,
|
||||
) -> None:
|
||||
"""
|
||||
Updates the agent version in the library if useGraphIsActiveVersion is True.
|
||||
|
||||
Args:
|
||||
user_id: Owner of the LibraryAgent.
|
||||
agent_id: The agent's ID to update.
|
||||
agent_version: The new version of the agent.
|
||||
agent_graph_id: The agent graph's ID to update.
|
||||
agent_graph_version: The new version of the agent graph.
|
||||
|
||||
Raises:
|
||||
DatabaseError: If there's an error with the update.
|
||||
"""
|
||||
logger.debug(
|
||||
f"Updating agent version in library for user #{user_id}, "
|
||||
f"agent #{agent_id} v{agent_version}"
|
||||
f"agent #{agent_graph_id} v{agent_graph_version}"
|
||||
)
|
||||
try:
|
||||
library_agent = await prisma.models.LibraryAgent.prisma().find_first_or_raise(
|
||||
where={
|
||||
"userId": user_id,
|
||||
"agentId": agent_id,
|
||||
"agentGraphId": agent_graph_id,
|
||||
"useGraphIsActiveVersion": True,
|
||||
},
|
||||
)
|
||||
await prisma.models.LibraryAgent.prisma().update(
|
||||
where={"id": library_agent.id},
|
||||
data={
|
||||
"Agent": {
|
||||
"AgentGraph": {
|
||||
"connect": {
|
||||
"graphVersionId": {"id": agent_id, "version": agent_version}
|
||||
"graphVersionId": {
|
||||
"id": agent_graph_id,
|
||||
"version": agent_graph_version,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -342,7 +345,7 @@ async def delete_library_agent_by_graph_id(graph_id: str, user_id: str) -> None:
|
||||
"""
|
||||
try:
|
||||
await prisma.models.LibraryAgent.prisma().delete_many(
|
||||
where={"agentId": graph_id, "userId": user_id}
|
||||
where={"agentGraphId": graph_id, "userId": user_id}
|
||||
)
|
||||
except prisma.errors.PrismaError as e:
|
||||
logger.error(f"Database error deleting library agent: {e}")
|
||||
@@ -375,10 +378,10 @@ async def add_store_agent_to_library(
|
||||
async with locked_transaction(f"add_agent_trx_{user_id}"):
|
||||
store_listing_version = (
|
||||
await prisma.models.StoreListingVersion.prisma().find_unique(
|
||||
where={"id": store_listing_version_id}, include={"Agent": True}
|
||||
where={"id": store_listing_version_id}, include={"AgentGraph": True}
|
||||
)
|
||||
)
|
||||
if not store_listing_version or not store_listing_version.Agent:
|
||||
if not store_listing_version or not store_listing_version.AgentGraph:
|
||||
logger.warning(
|
||||
f"Store listing version not found: {store_listing_version_id}"
|
||||
)
|
||||
@@ -386,7 +389,7 @@ async def add_store_agent_to_library(
|
||||
f"Store listing version {store_listing_version_id} not found or invalid"
|
||||
)
|
||||
|
||||
graph = store_listing_version.Agent
|
||||
graph = store_listing_version.AgentGraph
|
||||
if graph.userId == user_id:
|
||||
logger.warning(
|
||||
f"User #{user_id} attempted to add their own agent to their library"
|
||||
@@ -398,8 +401,8 @@ async def add_store_agent_to_library(
|
||||
await prisma.models.LibraryAgent.prisma().find_first(
|
||||
where={
|
||||
"userId": user_id,
|
||||
"agentId": graph.id,
|
||||
"agentVersion": graph.version,
|
||||
"agentGraphId": graph.id,
|
||||
"agentGraphVersion": graph.version,
|
||||
},
|
||||
include=library_agent_include(user_id),
|
||||
)
|
||||
@@ -421,15 +424,15 @@ async def add_store_agent_to_library(
|
||||
added_agent = await prisma.models.LibraryAgent.prisma().create(
|
||||
data=prisma.types.LibraryAgentCreateInput(
|
||||
userId=user_id,
|
||||
agentId=graph.id,
|
||||
agentVersion=graph.version,
|
||||
agentGraphId=graph.id,
|
||||
agentGraphVersion=graph.version,
|
||||
isCreatedByUser=False,
|
||||
),
|
||||
include=library_agent_include(user_id),
|
||||
)
|
||||
logger.debug(
|
||||
f"Added graph #{graph.id} "
|
||||
f"for store listing #{store_listing_version.id} "
|
||||
f"Added graph #{graph.id} v{graph.version}"
|
||||
f"for store listing version #{store_listing_version.id} "
|
||||
f"to library for user #{user_id}"
|
||||
)
|
||||
return library_model.LibraryAgent.from_db(added_agent)
|
||||
@@ -468,8 +471,8 @@ async def set_is_deleted_for_library_agent(
|
||||
count = await prisma.models.LibraryAgent.prisma().update_many(
|
||||
where={
|
||||
"userId": user_id,
|
||||
"agentId": agent_id,
|
||||
"agentVersion": agent_version,
|
||||
"agentGraphId": agent_id,
|
||||
"agentGraphVersion": agent_version,
|
||||
},
|
||||
data={"isDeleted": is_deleted},
|
||||
)
|
||||
@@ -598,21 +601,22 @@ async def upsert_preset(
|
||||
f"Upserting preset #{preset_id} ({repr(preset.name)}) for user #{user_id}",
|
||||
)
|
||||
try:
|
||||
inputs = [
|
||||
prisma.types.AgentNodeExecutionInputOutputCreateWithoutRelationsInput(
|
||||
name=name, data=prisma.fields.Json(data)
|
||||
)
|
||||
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=AgentPresetCreateInput(
|
||||
name=preset.name,
|
||||
description=preset.description,
|
||||
isActive=preset.is_active,
|
||||
InputPresets={
|
||||
"create": [
|
||||
{"name": name, "data": prisma.fields.Json(data)}
|
||||
for name, data in preset.inputs.items()
|
||||
]
|
||||
},
|
||||
),
|
||||
data={
|
||||
"name": preset.name,
|
||||
"description": preset.description,
|
||||
"isActive": preset.is_active,
|
||||
"InputPresets": {"create": inputs},
|
||||
},
|
||||
include={"InputPresets": True},
|
||||
)
|
||||
if not updated:
|
||||
@@ -625,15 +629,10 @@ async def upsert_preset(
|
||||
userId=user_id,
|
||||
name=preset.name,
|
||||
description=preset.description,
|
||||
agentId=preset.agent_id,
|
||||
agentVersion=preset.agent_version,
|
||||
agentGraphId=preset.graph_id,
|
||||
agentGraphVersion=preset.graph_version,
|
||||
isActive=preset.is_active,
|
||||
InputPresets={
|
||||
"create": [
|
||||
{"name": name, "data": prisma.fields.Json(data)}
|
||||
for name, data in preset.inputs.items()
|
||||
]
|
||||
},
|
||||
InputPresets={"create": inputs},
|
||||
),
|
||||
include={"InputPresets": True},
|
||||
)
|
||||
|
||||
@@ -30,8 +30,8 @@ async def test_get_library_agents(mocker):
|
||||
prisma.models.LibraryAgent(
|
||||
id="ua1",
|
||||
userId="test-user",
|
||||
agentId="agent2",
|
||||
agentVersion=1,
|
||||
agentGraphId="agent2",
|
||||
agentGraphVersion=1,
|
||||
isCreatedByUser=False,
|
||||
isDeleted=False,
|
||||
isArchived=False,
|
||||
@@ -39,7 +39,7 @@ async def test_get_library_agents(mocker):
|
||||
updatedAt=datetime.now(),
|
||||
isFavorite=False,
|
||||
useGraphIsActiveVersion=True,
|
||||
Agent=prisma.models.AgentGraph(
|
||||
AgentGraph=prisma.models.AgentGraph(
|
||||
id="agent2",
|
||||
version=1,
|
||||
name="Test Agent 2",
|
||||
@@ -71,8 +71,8 @@ async def test_get_library_agents(mocker):
|
||||
assert result.agents[0].id == "ua1"
|
||||
assert result.agents[0].name == "Test Agent 2"
|
||||
assert result.agents[0].description == "Test Description 2"
|
||||
assert result.agents[0].agent_id == "agent2"
|
||||
assert result.agents[0].agent_version == 1
|
||||
assert result.agents[0].graph_id == "agent2"
|
||||
assert result.agents[0].graph_version == 1
|
||||
assert result.agents[0].can_access_graph is False
|
||||
assert result.agents[0].is_latest_version is True
|
||||
assert result.pagination.total_items == 1
|
||||
@@ -90,8 +90,8 @@ async def test_add_agent_to_library(mocker):
|
||||
version=1,
|
||||
createdAt=datetime.now(),
|
||||
updatedAt=datetime.now(),
|
||||
agentId="agent1",
|
||||
agentVersion=1,
|
||||
agentGraphId="agent1",
|
||||
agentGraphVersion=1,
|
||||
name="Test Agent",
|
||||
subHeading="Test Agent Subheading",
|
||||
imageUrls=["https://example.com/image.jpg"],
|
||||
@@ -102,7 +102,7 @@ async def test_add_agent_to_library(mocker):
|
||||
isAvailable=True,
|
||||
storeListingId="listing123",
|
||||
submissionStatus=prisma.enums.SubmissionStatus.APPROVED,
|
||||
Agent=prisma.models.AgentGraph(
|
||||
AgentGraph=prisma.models.AgentGraph(
|
||||
id="agent1",
|
||||
version=1,
|
||||
name="Test Agent",
|
||||
@@ -116,8 +116,8 @@ async def test_add_agent_to_library(mocker):
|
||||
mock_library_agent_data = prisma.models.LibraryAgent(
|
||||
id="ua1",
|
||||
userId="test-user",
|
||||
agentId=mock_store_listing_data.agentId,
|
||||
agentVersion=1,
|
||||
agentGraphId=mock_store_listing_data.agentGraphId,
|
||||
agentGraphVersion=1,
|
||||
isCreatedByUser=False,
|
||||
isDeleted=False,
|
||||
isArchived=False,
|
||||
@@ -125,7 +125,7 @@ async def test_add_agent_to_library(mocker):
|
||||
updatedAt=datetime.now(),
|
||||
isFavorite=False,
|
||||
useGraphIsActiveVersion=True,
|
||||
Agent=mock_store_listing_data.Agent,
|
||||
AgentGraph=mock_store_listing_data.AgentGraph,
|
||||
)
|
||||
|
||||
# Mock prisma calls
|
||||
@@ -147,19 +147,22 @@ async def test_add_agent_to_library(mocker):
|
||||
|
||||
# Verify mocks called correctly
|
||||
mock_store_listing_version.return_value.find_unique.assert_called_once_with(
|
||||
where={"id": "version123"}, include={"Agent": True}
|
||||
where={"id": "version123"}, include={"AgentGraph": True}
|
||||
)
|
||||
mock_library_agent.return_value.find_first.assert_called_once_with(
|
||||
where={
|
||||
"userId": "test-user",
|
||||
"agentId": "agent1",
|
||||
"agentVersion": 1,
|
||||
"agentGraphId": "agent1",
|
||||
"agentGraphVersion": 1,
|
||||
},
|
||||
include=library_agent_include("test-user"),
|
||||
)
|
||||
mock_library_agent.return_value.create.assert_called_once_with(
|
||||
data=prisma.types.LibraryAgentCreateInput(
|
||||
userId="test-user", agentId="agent1", agentVersion=1, isCreatedByUser=False
|
||||
userId="test-user",
|
||||
agentGraphId="agent1",
|
||||
agentGraphVersion=1,
|
||||
isCreatedByUser=False,
|
||||
),
|
||||
include=library_agent_include("test-user"),
|
||||
)
|
||||
@@ -182,5 +185,5 @@ async def test_add_agent_to_library_not_found(mocker):
|
||||
|
||||
# Verify mock called correctly
|
||||
mock_store_listing_version.return_value.find_unique.assert_called_once_with(
|
||||
where={"id": "version123"}, include={"Agent": True}
|
||||
where={"id": "version123"}, include={"AgentGraph": True}
|
||||
)
|
||||
|
||||
@@ -25,8 +25,8 @@ class LibraryAgent(pydantic.BaseModel):
|
||||
"""
|
||||
|
||||
id: str
|
||||
agent_id: str
|
||||
agent_version: int
|
||||
graph_id: str
|
||||
graph_version: int
|
||||
|
||||
image_url: str | None
|
||||
|
||||
@@ -58,12 +58,12 @@ class LibraryAgent(pydantic.BaseModel):
|
||||
Factory method that constructs a LibraryAgent from a Prisma LibraryAgent
|
||||
model instance.
|
||||
"""
|
||||
if not agent.Agent:
|
||||
if not agent.AgentGraph:
|
||||
raise ValueError("Associated Agent record is required.")
|
||||
|
||||
graph = graph_model.GraphModel.from_db(agent.Agent)
|
||||
graph = graph_model.GraphModel.from_db(agent.AgentGraph)
|
||||
|
||||
agent_updated_at = agent.Agent.updatedAt
|
||||
agent_updated_at = agent.AgentGraph.updatedAt
|
||||
lib_agent_updated_at = agent.updatedAt
|
||||
|
||||
# Compute updated_at as the latest between library agent and graph
|
||||
@@ -83,21 +83,21 @@ class LibraryAgent(pydantic.BaseModel):
|
||||
week_ago = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(
|
||||
days=7
|
||||
)
|
||||
executions = agent.Agent.AgentGraphExecution or []
|
||||
executions = agent.AgentGraph.Executions or []
|
||||
status_result = _calculate_agent_status(executions, week_ago)
|
||||
status = status_result.status
|
||||
new_output = status_result.new_output
|
||||
|
||||
# Check if user can access the graph
|
||||
can_access_graph = agent.Agent.userId == agent.userId
|
||||
can_access_graph = agent.AgentGraph.userId == agent.userId
|
||||
|
||||
# Hard-coded to True until a method to check is implemented
|
||||
is_latest_version = True
|
||||
|
||||
return LibraryAgent(
|
||||
id=agent.id,
|
||||
agent_id=agent.agentId,
|
||||
agent_version=agent.agentVersion,
|
||||
graph_id=agent.agentGraphId,
|
||||
graph_version=agent.agentGraphVersion,
|
||||
image_url=agent.imageUrl,
|
||||
creator_name=creator_name,
|
||||
creator_image_url=creator_image_url,
|
||||
@@ -174,8 +174,8 @@ class LibraryAgentPreset(pydantic.BaseModel):
|
||||
id: str
|
||||
updated_at: datetime.datetime
|
||||
|
||||
agent_id: str
|
||||
agent_version: int
|
||||
graph_id: str
|
||||
graph_version: int
|
||||
|
||||
name: str
|
||||
description: str
|
||||
@@ -194,8 +194,8 @@ class LibraryAgentPreset(pydantic.BaseModel):
|
||||
return cls(
|
||||
id=preset.id,
|
||||
updated_at=preset.updatedAt,
|
||||
agent_id=preset.agentId,
|
||||
agent_version=preset.agentVersion,
|
||||
graph_id=preset.agentGraphId,
|
||||
graph_version=preset.agentGraphVersion,
|
||||
name=preset.name,
|
||||
description=preset.description,
|
||||
is_active=preset.isActive,
|
||||
@@ -218,8 +218,8 @@ class CreateLibraryAgentPresetRequest(pydantic.BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
inputs: block_model.BlockInput
|
||||
agent_id: str
|
||||
agent_version: int
|
||||
graph_id: str
|
||||
graph_version: int
|
||||
is_active: bool
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import prisma.models
|
||||
import pytest
|
||||
|
||||
import backend.server.v2.library.model as library_model
|
||||
from backend.util import json
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -15,8 +14,8 @@ async def test_agent_preset_from_db():
|
||||
id="test-agent-123",
|
||||
createdAt=datetime.datetime.now(),
|
||||
updatedAt=datetime.datetime.now(),
|
||||
agentId="agent-123",
|
||||
agentVersion=1,
|
||||
agentGraphId="agent-123",
|
||||
agentGraphVersion=1,
|
||||
name="Test Agent",
|
||||
description="Test agent description",
|
||||
isActive=True,
|
||||
@@ -27,7 +26,7 @@ async def test_agent_preset_from_db():
|
||||
id="input-123",
|
||||
time=datetime.datetime.now(),
|
||||
name="input1",
|
||||
data=json.dumps({"type": "string", "value": "test value"}), # type: ignore
|
||||
data=prisma.Json({"type": "string", "value": "test value"}),
|
||||
)
|
||||
],
|
||||
)
|
||||
@@ -36,7 +35,7 @@ async def test_agent_preset_from_db():
|
||||
agent = library_model.LibraryAgentPreset.from_db(db_agent)
|
||||
|
||||
assert agent.id == "test-agent-123"
|
||||
assert agent.agent_version == 1
|
||||
assert agent.graph_version == 1
|
||||
assert agent.is_active is True
|
||||
assert agent.name == "Test Agent"
|
||||
assert agent.description == "Test agent description"
|
||||
|
||||
@@ -35,8 +35,8 @@ async def test_get_library_agents_success(mocker: pytest_mock.MockFixture):
|
||||
agents=[
|
||||
library_model.LibraryAgent(
|
||||
id="test-agent-1",
|
||||
agent_id="test-agent-1",
|
||||
agent_version=1,
|
||||
graph_id="test-agent-1",
|
||||
graph_version=1,
|
||||
name="Test Agent 1",
|
||||
description="Test Description 1",
|
||||
image_url=None,
|
||||
@@ -51,8 +51,8 @@ async def test_get_library_agents_success(mocker: pytest_mock.MockFixture):
|
||||
),
|
||||
library_model.LibraryAgent(
|
||||
id="test-agent-2",
|
||||
agent_id="test-agent-2",
|
||||
agent_version=1,
|
||||
graph_id="test-agent-2",
|
||||
graph_version=1,
|
||||
name="Test Agent 2",
|
||||
description="Test Description 2",
|
||||
image_url=None,
|
||||
@@ -78,9 +78,9 @@ async def test_get_library_agents_success(mocker: pytest_mock.MockFixture):
|
||||
|
||||
data = library_model.LibraryAgentResponse.model_validate(response.json())
|
||||
assert len(data.agents) == 2
|
||||
assert data.agents[0].agent_id == "test-agent-1"
|
||||
assert data.agents[0].graph_id == "test-agent-1"
|
||||
assert data.agents[0].can_access_graph is True
|
||||
assert data.agents[1].agent_id == "test-agent-2"
|
||||
assert data.agents[1].graph_id == "test-agent-2"
|
||||
assert data.agents[1].can_access_graph is False
|
||||
mock_db_call.assert_called_once_with(
|
||||
user_id="test-user-id",
|
||||
|
||||
@@ -12,6 +12,7 @@ import backend.server.v2.store.exceptions
|
||||
import backend.server.v2.store.model
|
||||
from backend.data.graph import GraphModel, get_sub_graphs
|
||||
from backend.data.includes import AGENT_GRAPH_INCLUDE
|
||||
from backend.util.type import typed_cast
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -200,17 +201,17 @@ async def get_available_graph(
|
||||
"isAvailable": True,
|
||||
"isDeleted": False,
|
||||
},
|
||||
include={"Agent": {"include": {"AgentNodes": True}}},
|
||||
include={"AgentGraph": {"include": {"Nodes": True}}},
|
||||
)
|
||||
)
|
||||
|
||||
if not store_listing_version or not store_listing_version.Agent:
|
||||
if not store_listing_version or not store_listing_version.AgentGraph:
|
||||
raise fastapi.HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Store listing version {store_listing_version_id} not found",
|
||||
)
|
||||
|
||||
graph = GraphModel.from_db(store_listing_version.Agent)
|
||||
graph = GraphModel.from_db(store_listing_version.AgentGraph)
|
||||
# We return graph meta, without nodes, they cannot be just removed
|
||||
# because then input_schema would be empty
|
||||
return {
|
||||
@@ -516,7 +517,7 @@ async def delete_store_submission(
|
||||
try:
|
||||
# Verify the submission belongs to this user
|
||||
submission = await prisma.models.StoreListing.prisma().find_first(
|
||||
where={"agentId": submission_id, "owningUserId": user_id}
|
||||
where={"agentGraphId": submission_id, "owningUserId": user_id}
|
||||
)
|
||||
|
||||
if not submission:
|
||||
@@ -598,7 +599,7 @@ async def create_store_submission(
|
||||
# Check if listing already exists for this agent
|
||||
existing_listing = await prisma.models.StoreListing.prisma().find_first(
|
||||
where=prisma.types.StoreListingWhereInput(
|
||||
agentId=agent_id, owningUserId=user_id
|
||||
agentGraphId=agent_id, owningUserId=user_id
|
||||
)
|
||||
)
|
||||
|
||||
@@ -625,15 +626,15 @@ async def create_store_submission(
|
||||
# If no existing listing, create a new one
|
||||
data = prisma.types.StoreListingCreateInput(
|
||||
slug=slug,
|
||||
agentId=agent_id,
|
||||
agentVersion=agent_version,
|
||||
agentGraphId=agent_id,
|
||||
agentGraphVersion=agent_version,
|
||||
owningUserId=user_id,
|
||||
createdAt=datetime.now(tz=timezone.utc),
|
||||
Versions={
|
||||
"create": [
|
||||
prisma.types.StoreListingVersionCreateInput(
|
||||
agentId=agent_id,
|
||||
agentVersion=agent_version,
|
||||
agentGraphId=agent_id,
|
||||
agentGraphVersion=agent_version,
|
||||
name=name,
|
||||
videoUrl=video_url,
|
||||
imageUrls=image_urls,
|
||||
@@ -758,8 +759,8 @@ async def create_store_version(
|
||||
new_version = await prisma.models.StoreListingVersion.prisma().create(
|
||||
data=prisma.types.StoreListingVersionCreateInput(
|
||||
version=next_version,
|
||||
agentId=agent_id,
|
||||
agentVersion=agent_version,
|
||||
agentGraphId=agent_id,
|
||||
agentGraphVersion=agent_version,
|
||||
name=name,
|
||||
videoUrl=video_url,
|
||||
imageUrls=image_urls,
|
||||
@@ -959,17 +960,17 @@ async def get_my_agents(
|
||||
try:
|
||||
search_filter: prisma.types.LibraryAgentWhereInput = {
|
||||
"userId": user_id,
|
||||
"Agent": {"is": {"StoreListing": {"none": {"isDeleted": False}}}},
|
||||
"AgentGraph": {"is": {"StoreListing": {"none": {"isDeleted": False}}}},
|
||||
"isArchived": False,
|
||||
"isDeleted": False,
|
||||
}
|
||||
|
||||
library_agents = await prisma.models.LibraryAgent.prisma().find_many(
|
||||
where=search_filter,
|
||||
order=[{"agentVersion": "desc"}],
|
||||
order=[{"agentGraphVersion": "desc"}],
|
||||
skip=(page - 1) * page_size,
|
||||
take=page_size,
|
||||
include={"Agent": True},
|
||||
include={"AgentGraph": True},
|
||||
)
|
||||
|
||||
total = await prisma.models.LibraryAgent.prisma().count(where=search_filter)
|
||||
@@ -985,7 +986,7 @@ async def get_my_agents(
|
||||
agent_image=library_agent.imageUrl,
|
||||
)
|
||||
for library_agent in library_agents
|
||||
if (graph := library_agent.Agent)
|
||||
if (graph := library_agent.AgentGraph)
|
||||
]
|
||||
|
||||
return backend.server.v2.store.model.MyAgentsResponse(
|
||||
@@ -1020,13 +1021,13 @@ async def get_agent(
|
||||
|
||||
graph = await backend.data.graph.get_graph(
|
||||
user_id=user_id,
|
||||
graph_id=store_listing_version.agentId,
|
||||
version=store_listing_version.agentVersion,
|
||||
graph_id=store_listing_version.agentGraphId,
|
||||
version=store_listing_version.agentGraphVersion,
|
||||
for_export=True,
|
||||
)
|
||||
if not graph:
|
||||
raise ValueError(
|
||||
f"Agent {store_listing_version.agentId} v{store_listing_version.agentVersion} not found"
|
||||
f"Agent {store_listing_version.agentGraphId} v{store_listing_version.agentGraphVersion} not found"
|
||||
)
|
||||
|
||||
return graph
|
||||
@@ -1050,11 +1051,14 @@ async def _get_missing_sub_store_listing(
|
||||
|
||||
# Fetch all the sub-graphs that are listed, and return the ones missing.
|
||||
store_listed_sub_graphs = {
|
||||
(listing.agentId, listing.agentVersion)
|
||||
(listing.agentGraphId, listing.agentGraphVersion)
|
||||
for listing in await prisma.models.StoreListingVersion.prisma().find_many(
|
||||
where={
|
||||
"OR": [
|
||||
{"agentId": sub_graph.id, "agentVersion": sub_graph.version}
|
||||
{
|
||||
"agentGraphId": sub_graph.id,
|
||||
"agentGraphVersion": sub_graph.version,
|
||||
}
|
||||
for sub_graph in sub_graphs
|
||||
],
|
||||
"submissionStatus": prisma.enums.SubmissionStatus.APPROVED,
|
||||
@@ -1084,7 +1088,13 @@ async def review_store_submission(
|
||||
where={"id": store_listing_version_id},
|
||||
include={
|
||||
"StoreListing": True,
|
||||
"Agent": {"include": AGENT_GRAPH_INCLUDE}, # type: ignore
|
||||
"AgentGraph": {
|
||||
"include": typed_cast(
|
||||
prisma.types.AgentGraphIncludeFromAgentGraphRecursive1,
|
||||
prisma.types.AgentGraphInclude,
|
||||
AGENT_GRAPH_INCLUDE,
|
||||
)
|
||||
},
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -1096,23 +1106,23 @@ async def review_store_submission(
|
||||
)
|
||||
|
||||
# If approving, update the listing to indicate it has an approved version
|
||||
if is_approved and store_listing_version.Agent:
|
||||
heading = f"Sub-graph of {store_listing_version.name}v{store_listing_version.agentVersion}"
|
||||
if is_approved and store_listing_version.AgentGraph:
|
||||
heading = f"Sub-graph of {store_listing_version.name}v{store_listing_version.agentGraphVersion}"
|
||||
|
||||
sub_store_listing_versions = [
|
||||
prisma.types.StoreListingVersionCreateWithoutRelationsInput(
|
||||
agentId=sub_graph.id,
|
||||
agentVersion=sub_graph.version,
|
||||
agentGraphId=sub_graph.id,
|
||||
agentGraphVersion=sub_graph.version,
|
||||
name=sub_graph.name or heading,
|
||||
submissionStatus=prisma.enums.SubmissionStatus.APPROVED,
|
||||
subHeading=heading,
|
||||
description=f"{heading}: {sub_graph.description}",
|
||||
changesSummary=f"This listing is added as a {heading} / #{store_listing_version.agentId}.",
|
||||
changesSummary=f"This listing is added as a {heading} / #{store_listing_version.agentGraphId}.",
|
||||
isAvailable=False, # Hide sub-graphs from the store by default.
|
||||
submittedAt=datetime.now(tz=timezone.utc),
|
||||
)
|
||||
for sub_graph in await _get_missing_sub_store_listing(
|
||||
store_listing_version.Agent
|
||||
store_listing_version.AgentGraph
|
||||
)
|
||||
]
|
||||
|
||||
@@ -1155,8 +1165,8 @@ async def review_store_submission(
|
||||
|
||||
# Convert to Pydantic model for consistency
|
||||
return backend.server.v2.store.model.StoreSubmission(
|
||||
agent_id=submission.agentId,
|
||||
agent_version=submission.agentVersion,
|
||||
agent_id=submission.agentGraphId,
|
||||
agent_version=submission.agentGraphVersion,
|
||||
name=submission.name,
|
||||
sub_heading=submission.subHeading,
|
||||
slug=(
|
||||
@@ -1294,8 +1304,8 @@ async def get_admin_listings_with_versions(
|
||||
# If we have versions, turn them into StoreSubmission models
|
||||
for version in listing.Versions or []:
|
||||
version_model = backend.server.v2.store.model.StoreSubmission(
|
||||
agent_id=version.agentId,
|
||||
agent_version=version.agentVersion,
|
||||
agent_id=version.agentGraphId,
|
||||
agent_version=version.agentGraphVersion,
|
||||
name=version.name,
|
||||
sub_heading=version.subHeading,
|
||||
slug=listing.slug,
|
||||
@@ -1324,8 +1334,8 @@ async def get_admin_listings_with_versions(
|
||||
backend.server.v2.store.model.StoreListingWithVersions(
|
||||
listing_id=listing.id,
|
||||
slug=listing.slug,
|
||||
agent_id=listing.agentId,
|
||||
agent_version=listing.agentVersion,
|
||||
agent_id=listing.agentGraphId,
|
||||
agent_version=listing.agentGraphVersion,
|
||||
active_version_id=listing.activeVersionId,
|
||||
has_approved_version=listing.hasApprovedVersion,
|
||||
creator_email=creator_email,
|
||||
|
||||
@@ -170,14 +170,14 @@ async def test_create_store_submission(mocker):
|
||||
isDeleted=False,
|
||||
hasApprovedVersion=False,
|
||||
slug="test-agent",
|
||||
agentId="agent-id",
|
||||
agentVersion=1,
|
||||
agentGraphId="agent-id",
|
||||
agentGraphVersion=1,
|
||||
owningUserId="user-id",
|
||||
Versions=[
|
||||
prisma.models.StoreListingVersion(
|
||||
id="version-id",
|
||||
agentId="agent-id",
|
||||
agentVersion=1,
|
||||
agentGraphId="agent-id",
|
||||
agentGraphVersion=1,
|
||||
name="Test Agent",
|
||||
description="Test description",
|
||||
createdAt=datetime.now(),
|
||||
|
||||
@@ -182,6 +182,7 @@ def _try_convert(value: Any, target_type: Type, raise_on_mismatch: bool) -> Any:
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
TT = TypeVar("TT")
|
||||
|
||||
|
||||
def type_match(value: Any, target_type: Type[T]) -> T:
|
||||
@@ -197,6 +198,18 @@ def convert(value: Any, target_type: Type[T]) -> T:
|
||||
raise ConversionError(f"Failed to convert {value} to {target_type}") from e
|
||||
|
||||
|
||||
def typed(type: type[T], value: T) -> T:
|
||||
"""
|
||||
Add an explicit type to a value. Useful in nested statements, e.g. dict literals.
|
||||
"""
|
||||
return value
|
||||
|
||||
|
||||
def typed_cast(to_type: type[TT], from_type: type[T], value: T) -> TT:
|
||||
"""Strict cast to preserve type checking abilities."""
|
||||
return cast(TT, value)
|
||||
|
||||
|
||||
class FormattedStringType(str):
|
||||
string_format: str
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Warnings:
|
||||
- The relation LibraryAgent:AgentPreset was REMOVED
|
||||
- A unique constraint covering the columns `[userId,agentGraphId,agentGraphVersion]` on the table `LibraryAgent` will be added. If there are existing duplicate values, this will fail.
|
||||
- The foreign key constraints on AgentPreset and LibraryAgent are being changed from CASCADE to RESTRICT for AgentGraph deletion, which means you cannot delete AgentGraphs that have associated LibraryAgents or AgentPresets.
|
||||
|
||||
Use the following query to check whether these conditions are satisfied:
|
||||
|
||||
-- Check for duplicate LibraryAgent userId + agentGraphId + agentGraphVersion combinations that would violate the new unique constraint
|
||||
SELECT la."userId",
|
||||
la."agentId" as graph_id,
|
||||
la."agentVersion" as graph_version,
|
||||
COUNT(*) as multiplicity
|
||||
FROM "LibraryAgent" la
|
||||
GROUP BY la."userId",
|
||||
la."agentId",
|
||||
la."agentVersion"
|
||||
HAVING COUNT(*) > 1;
|
||||
*/
|
||||
|
||||
-- Drop foreign key constraints on columns we're about to rename
|
||||
ALTER TABLE "AgentPreset" DROP CONSTRAINT "AgentPreset_agentId_agentVersion_fkey";
|
||||
ALTER TABLE "LibraryAgent" DROP CONSTRAINT "LibraryAgent_agentId_agentVersion_fkey";
|
||||
ALTER TABLE "LibraryAgent" DROP CONSTRAINT "LibraryAgent_agentPresetId_fkey";
|
||||
|
||||
-- Rename columns in AgentPreset
|
||||
ALTER TABLE "AgentPreset" RENAME COLUMN "agentId" TO "agentGraphId";
|
||||
ALTER TABLE "AgentPreset" RENAME COLUMN "agentVersion" TO "agentGraphVersion";
|
||||
|
||||
-- Rename columns in LibraryAgent
|
||||
ALTER TABLE "LibraryAgent" RENAME COLUMN "agentId" TO "agentGraphId";
|
||||
ALTER TABLE "LibraryAgent" RENAME COLUMN "agentVersion" TO "agentGraphVersion";
|
||||
|
||||
-- Drop LibraryAgent.agentPresetId column
|
||||
ALTER TABLE "LibraryAgent" DROP COLUMN "agentPresetId";
|
||||
|
||||
-- Replace userId index with unique index on userId + agentGraphId + agentGraphVersion
|
||||
DROP INDEX "LibraryAgent_userId_idx";
|
||||
CREATE UNIQUE INDEX "LibraryAgent_userId_agentGraphId_agentGraphVersion_key" ON "LibraryAgent"("userId", "agentGraphId", "agentGraphVersion");
|
||||
|
||||
-- Re-add the foreign key constraints with new column names
|
||||
ALTER TABLE "LibraryAgent" ADD CONSTRAINT "LibraryAgent_agentGraphId_agentGraphVersion_fkey"
|
||||
FOREIGN KEY ("agentGraphId", "agentGraphVersion") REFERENCES "AgentGraph"("id", "version")
|
||||
ON DELETE RESTRICT -- Disallow deleting AgentGraph when still referenced by existing LibraryAgents
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "AgentPreset" ADD CONSTRAINT "AgentPreset_agentGraphId_agentGraphVersion_fkey"
|
||||
FOREIGN KEY ("agentGraphId", "agentGraphVersion") REFERENCES "AgentGraph"("id", "version")
|
||||
ON DELETE RESTRICT -- Disallow deleting AgentGraph when still referenced by existing AgentPresets
|
||||
ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
- Rename column StoreListing.agentId to agentGraphId
|
||||
- Rename column StoreListing.agentVersion to agentGraphVersion
|
||||
- Rename column StoreListingVersion.agentId to agentGraphId
|
||||
- Rename column StoreListingVersion.agentVersion to agentGraphVersion
|
||||
*/
|
||||
|
||||
-- Drop foreign key constraints on columns we're about to rename
|
||||
ALTER TABLE "StoreListing" DROP CONSTRAINT "StoreListing_agentId_agentVersion_fkey";
|
||||
ALTER TABLE "StoreListingVersion" DROP CONSTRAINT "StoreListingVersion_agentId_agentVersion_fkey";
|
||||
|
||||
-- Drop indices on columns we're about to rename
|
||||
DROP INDEX "StoreListing_agentId_key";
|
||||
DROP INDEX "StoreListingVersion_agentId_agentVersion_idx";
|
||||
|
||||
-- Rename columns
|
||||
ALTER TABLE "StoreListing" RENAME COLUMN "agentId" TO "agentGraphId";
|
||||
ALTER TABLE "StoreListing" RENAME COLUMN "agentVersion" TO "agentGraphVersion";
|
||||
ALTER TABLE "StoreListingVersion" RENAME COLUMN "agentId" TO "agentGraphId";
|
||||
ALTER TABLE "StoreListingVersion" RENAME COLUMN "agentVersion" TO "agentGraphVersion";
|
||||
|
||||
-- Re-create indices with updated name on renamed columns
|
||||
CREATE UNIQUE INDEX "StoreListing_agentGraphId_key" ON "StoreListing"("agentGraphId");
|
||||
CREATE INDEX "StoreListingVersion_agentGraphId_agentGraphVersion_idx" ON "StoreListingVersion"("agentGraphId", "agentGraphVersion");
|
||||
|
||||
-- Re-create foreign key constraints with updated name on renamed columns
|
||||
ALTER TABLE "StoreListing" ADD CONSTRAINT "StoreListing_agentGraphId_agentGraphVersion_fkey"
|
||||
FOREIGN KEY ("agentGraphId", "agentGraphVersion") REFERENCES "AgentGraph"("id", "version")
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "StoreListingVersion" ADD CONSTRAINT "StoreListingVersion_agentGraphId_agentGraphVersion_fkey"
|
||||
FOREIGN KEY ("agentGraphId", "agentGraphVersion") REFERENCES "AgentGraph"("id", "version")
|
||||
ON DELETE RESTRICT
|
||||
ON UPDATE CASCADE;
|
||||
@@ -39,19 +39,19 @@ model User {
|
||||
AgentGraphExecutions AgentGraphExecution[]
|
||||
AnalyticsDetails AnalyticsDetails[]
|
||||
AnalyticsMetrics AnalyticsMetrics[]
|
||||
CreditTransaction CreditTransaction[]
|
||||
CreditTransactions CreditTransaction[]
|
||||
|
||||
AgentPreset AgentPreset[]
|
||||
LibraryAgent LibraryAgent[]
|
||||
AgentPresets AgentPreset[]
|
||||
LibraryAgents LibraryAgent[]
|
||||
|
||||
Profile Profile[]
|
||||
UserOnboarding UserOnboarding?
|
||||
StoreListing StoreListing[]
|
||||
StoreListingReview StoreListingReview[]
|
||||
StoreListings StoreListing[]
|
||||
StoreListingReviews StoreListingReview[]
|
||||
StoreVersionsReviewed StoreListingVersion[]
|
||||
APIKeys APIKey[]
|
||||
IntegrationWebhooks IntegrationWebhook[]
|
||||
UserNotificationBatch UserNotificationBatch[]
|
||||
NotificationBatches UserNotificationBatch[]
|
||||
|
||||
@@index([id])
|
||||
@@index([email])
|
||||
@@ -102,13 +102,13 @@ model AgentGraph {
|
||||
// This allows us to delete user data with deleting the agent which maybe in use by other users
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
AgentNodes AgentNode[]
|
||||
AgentGraphExecution AgentGraphExecution[]
|
||||
Nodes AgentNode[]
|
||||
Executions AgentGraphExecution[]
|
||||
|
||||
AgentPreset AgentPreset[]
|
||||
LibraryAgent LibraryAgent[]
|
||||
StoreListing StoreListing[]
|
||||
StoreListingVersion StoreListingVersion[]
|
||||
Presets AgentPreset[]
|
||||
LibraryAgents LibraryAgent[]
|
||||
StoreListings StoreListing[]
|
||||
StoreListingVersions StoreListingVersion[]
|
||||
|
||||
@@id(name: "graphVersionId", [id, version])
|
||||
@@index([userId, isActive])
|
||||
@@ -139,13 +139,12 @@ model AgentPreset {
|
||||
userId String
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
agentId String
|
||||
agentVersion Int
|
||||
Agent AgentGraph @relation(fields: [agentId, agentVersion], references: [id, version], onDelete: Cascade)
|
||||
agentGraphId String
|
||||
agentGraphVersion Int
|
||||
AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Restrict)
|
||||
|
||||
InputPresets AgentNodeExecutionInputOutput[] @relation("AgentPresetsInputData")
|
||||
LibraryAgents LibraryAgent[]
|
||||
AgentExecution AgentGraphExecution[]
|
||||
InputPresets AgentNodeExecutionInputOutput[] @relation("AgentPresetsInputData")
|
||||
Executions AgentGraphExecution[]
|
||||
|
||||
isDeleted Boolean @default(false)
|
||||
|
||||
@@ -194,7 +193,7 @@ model UserNotificationBatch {
|
||||
}
|
||||
|
||||
// For the library page
|
||||
// It is a user controlled list of agents, that they will see in there library
|
||||
// It is a user controlled list of agents, that they will see in their library
|
||||
model LibraryAgent {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
@@ -205,12 +204,9 @@ model LibraryAgent {
|
||||
|
||||
imageUrl String?
|
||||
|
||||
agentId String
|
||||
agentVersion Int
|
||||
Agent AgentGraph @relation(fields: [agentId, agentVersion], references: [id, version])
|
||||
|
||||
agentPresetId String?
|
||||
AgentPreset AgentPreset? @relation(fields: [agentPresetId], references: [id])
|
||||
agentGraphId String
|
||||
agentGraphVersion Int
|
||||
AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Restrict)
|
||||
|
||||
creatorId String?
|
||||
Creator Profile? @relation(fields: [creatorId], references: [id])
|
||||
@@ -222,7 +218,7 @@ model LibraryAgent {
|
||||
isArchived Boolean @default(false)
|
||||
isDeleted Boolean @default(false)
|
||||
|
||||
@@index([userId])
|
||||
@@unique([userId, agentGraphId, agentGraphVersion])
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
@@ -256,7 +252,7 @@ model AgentNode {
|
||||
|
||||
metadata Json @default("{}")
|
||||
|
||||
ExecutionHistory AgentNodeExecution[]
|
||||
Executions AgentNodeExecution[]
|
||||
|
||||
@@index([agentGraphId, agentGraphVersion])
|
||||
@@index([agentBlockId])
|
||||
@@ -323,15 +319,15 @@ model AgentGraphExecution {
|
||||
agentGraphVersion Int @default(1)
|
||||
AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Cascade)
|
||||
|
||||
AgentNodeExecutions AgentNodeExecution[]
|
||||
NodeExecutions AgentNodeExecution[]
|
||||
|
||||
// Link to User model -- Executed by this user
|
||||
userId String
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
stats Json?
|
||||
AgentPreset AgentPreset? @relation(fields: [agentPresetId], references: [id])
|
||||
agentPresetId String?
|
||||
AgentPreset AgentPreset? @relation(fields: [agentPresetId], references: [id])
|
||||
|
||||
@@index([agentGraphId, agentGraphVersion])
|
||||
@@index([userId])
|
||||
@@ -342,10 +338,10 @@ model AgentNodeExecution {
|
||||
id String @id @default(uuid())
|
||||
|
||||
agentGraphExecutionId String
|
||||
AgentGraphExecution AgentGraphExecution @relation(fields: [agentGraphExecutionId], references: [id], onDelete: Cascade)
|
||||
GraphExecution AgentGraphExecution @relation(fields: [agentGraphExecutionId], references: [id], onDelete: Cascade)
|
||||
|
||||
agentNodeId String
|
||||
AgentNode AgentNode @relation(fields: [agentNodeId], references: [id], onDelete: Cascade)
|
||||
Node AgentNode @relation(fields: [agentNodeId], references: [id], onDelete: Cascade)
|
||||
|
||||
Input AgentNodeExecutionInputOutput[] @relation("AgentNodeExecutionInput")
|
||||
Output AgentNodeExecutionInputOutput[] @relation("AgentNodeExecutionOutput")
|
||||
@@ -627,9 +623,9 @@ model StoreListing {
|
||||
ActiveVersion StoreListingVersion? @relation("ActiveVersion", fields: [activeVersionId], references: [id])
|
||||
|
||||
// The agent link here is only so we can do lookup on agentId
|
||||
agentId String
|
||||
agentVersion Int
|
||||
Agent AgentGraph @relation(fields: [agentId, agentVersion], references: [id, version], onDelete: Cascade)
|
||||
agentGraphId String
|
||||
agentGraphVersion Int
|
||||
AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Cascade)
|
||||
|
||||
owningUserId String
|
||||
OwningUser User @relation(fields: [owningUserId], references: [id])
|
||||
@@ -638,7 +634,7 @@ model StoreListing {
|
||||
Versions StoreListingVersion[] @relation("ListingVersions")
|
||||
|
||||
// Unique index on agentId to ensure only one listing per agent, regardless of number of versions the agent has.
|
||||
@@unique([agentId])
|
||||
@@unique([agentGraphId])
|
||||
@@unique([owningUserId, slug])
|
||||
// Used in the view query
|
||||
@@index([isDeleted, hasApprovedVersion])
|
||||
@@ -651,9 +647,9 @@ model StoreListingVersion {
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
// The agent and version to be listed on the store
|
||||
agentId String
|
||||
agentVersion Int
|
||||
Agent AgentGraph @relation(fields: [agentId, agentVersion], references: [id, version])
|
||||
agentGraphId String
|
||||
agentGraphVersion Int
|
||||
AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version])
|
||||
|
||||
// Content fields
|
||||
name String
|
||||
@@ -697,7 +693,7 @@ model StoreListingVersion {
|
||||
@@index([storeListingId, submissionStatus, isAvailable])
|
||||
@@index([submissionStatus])
|
||||
@@index([reviewerId])
|
||||
@@index([agentId, agentVersion]) // Non-unique index for efficient lookups
|
||||
@@index([agentGraphId, agentGraphVersion]) // Non-unique index for efficient lookups
|
||||
}
|
||||
|
||||
model StoreListingReview {
|
||||
|
||||
@@ -360,8 +360,8 @@ async def test_execute_preset(server: SpinTestServer):
|
||||
preset = backend.server.v2.library.model.CreateLibraryAgentPresetRequest(
|
||||
name="Test Preset With Clash",
|
||||
description="Test preset with clashing input values",
|
||||
agent_id=test_graph.id,
|
||||
agent_version=test_graph.version,
|
||||
graph_id=test_graph.id,
|
||||
graph_version=test_graph.version,
|
||||
inputs={
|
||||
"dictionary": {"key1": "Hello", "key2": "World"},
|
||||
"selected_value": "key2",
|
||||
@@ -449,8 +449,8 @@ async def test_execute_preset_with_clash(server: SpinTestServer):
|
||||
preset = backend.server.v2.library.model.CreateLibraryAgentPresetRequest(
|
||||
name="Test Preset With Clash",
|
||||
description="Test preset with clashing input values",
|
||||
agent_id=test_graph.id,
|
||||
agent_version=test_graph.version,
|
||||
graph_id=test_graph.id,
|
||||
graph_version=test_graph.version,
|
||||
inputs={
|
||||
"dictionary": {"key1": "Hello", "key2": "World"},
|
||||
"selected_value": "key2",
|
||||
|
||||
@@ -82,16 +82,16 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
api.getLibraryAgent(agentID).then((agent) => {
|
||||
setAgent(agent);
|
||||
|
||||
getGraphVersion(agent.agent_id, agent.agent_version).then(
|
||||
getGraphVersion(agent.graph_id, agent.graph_version).then(
|
||||
(_graph) =>
|
||||
(graph && graph.version == _graph.version) || setGraph(_graph),
|
||||
);
|
||||
api.getGraphExecutions(agent.agent_id).then((agentRuns) => {
|
||||
api.getGraphExecutions(agent.graph_id).then((agentRuns) => {
|
||||
setAgentRuns(agentRuns);
|
||||
|
||||
// Preload the corresponding graph versions
|
||||
new Set(agentRuns.map((run) => run.graph_version)).forEach((version) =>
|
||||
getGraphVersion(agent.agent_id, version),
|
||||
getGraphVersion(agent.graph_id, version),
|
||||
);
|
||||
|
||||
if (!selectedView.id && isFirstLoad && agentRuns.length > 0) {
|
||||
@@ -109,7 +109,7 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
});
|
||||
if (selectedView.type == "run" && selectedView.id && agent) {
|
||||
api
|
||||
.getGraphExecutionInfo(agent.agent_id, selectedView.id)
|
||||
.getGraphExecutionInfo(agent.graph_id, selectedView.id)
|
||||
.then(setSelectedRun);
|
||||
}
|
||||
}, [api, agentID, getGraphVersion, graph, selectedView, isFirstLoad, agent]);
|
||||
@@ -123,7 +123,7 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
if (!agent) return;
|
||||
|
||||
// Subscribe to all executions for this agent
|
||||
api.subscribeToGraphExecutions(agent.agent_id);
|
||||
api.subscribeToGraphExecutions(agent.graph_id);
|
||||
}, [api, agent]);
|
||||
|
||||
// Handle execution updates
|
||||
@@ -162,7 +162,7 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
|
||||
// Ensure corresponding graph version is available before rendering I/O
|
||||
api
|
||||
.getGraphExecutionInfo(agent.agent_id, selectedView.id)
|
||||
.getGraphExecutionInfo(agent.graph_id, selectedView.id)
|
||||
.then(async (run) => {
|
||||
await getGraphVersion(run.graph_id, run.graph_version);
|
||||
setSelectedRun(run);
|
||||
@@ -175,7 +175,7 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
|
||||
// TODO: filter in backend - https://github.com/Significant-Gravitas/AutoGPT/issues/9183
|
||||
setSchedules(
|
||||
(await api.listSchedules()).filter((s) => s.graph_id == agent.agent_id),
|
||||
(await api.listSchedules()).filter((s) => s.graph_id == agent.graph_id),
|
||||
);
|
||||
}, [api, agent]);
|
||||
|
||||
@@ -214,7 +214,7 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
agent &&
|
||||
// Export sanitized graph from backend
|
||||
api
|
||||
.getGraph(agent.agent_id, agent.agent_version, true)
|
||||
.getGraph(agent.graph_id, agent.graph_version, true)
|
||||
.then((graph) =>
|
||||
exportAsJSONFile(graph, `${graph.name}_v${graph.version}.json`),
|
||||
),
|
||||
@@ -227,7 +227,7 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
? [
|
||||
{
|
||||
label: "Open graph in builder",
|
||||
href: `/build?flowID=${agent.agent_id}&flowVersion=${agent.agent_version}`,
|
||||
href: `/build?flowID=${agent.graph_id}&flowVersion=${agent.graph_version}`,
|
||||
},
|
||||
{ label: "Export agent to file", callback: downloadGraph },
|
||||
]
|
||||
|
||||
@@ -98,7 +98,7 @@ const Monitor = () => {
|
||||
flows={flows}
|
||||
executions={[
|
||||
...(selectedFlow
|
||||
? executions.filter((v) => v.graph_id == selectedFlow.agent_id)
|
||||
? executions.filter((v) => v.graph_id == selectedFlow.graph_id)
|
||||
: executions),
|
||||
].sort((a, b) => b.started_at.getTime() - a.started_at.getTime())}
|
||||
selectedRun={selectedRun}
|
||||
@@ -108,7 +108,7 @@ const Monitor = () => {
|
||||
<FlowRunInfo
|
||||
agent={
|
||||
selectedFlow ||
|
||||
flows.find((f) => f.agent_id == selectedRun.graph_id)!
|
||||
flows.find((f) => f.graph_id == selectedRun.graph_id)!
|
||||
}
|
||||
execution={selectedRun}
|
||||
className={column3}
|
||||
@@ -118,7 +118,7 @@ const Monitor = () => {
|
||||
<FlowInfo
|
||||
flow={selectedFlow}
|
||||
executions={executions.filter(
|
||||
(e) => e.graph_id == selectedFlow.agent_id,
|
||||
(e) => e.graph_id == selectedFlow.graph_id,
|
||||
)}
|
||||
className={column3}
|
||||
refresh={() => {
|
||||
|
||||
@@ -84,7 +84,7 @@ export default function AgentRunsSelectorList({
|
||||
>
|
||||
<span>Scheduled</span>
|
||||
<span className="text-neutral-600">
|
||||
{schedules.filter((s) => s.graph_id === agent.agent_id).length}
|
||||
{schedules.filter((s) => s.graph_id === agent.graph_id).length}
|
||||
</span>
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -127,7 +127,7 @@ export default function AgentRunsSelectorList({
|
||||
/>
|
||||
))
|
||||
: schedules
|
||||
.filter((schedule) => schedule.graph_id === agent.agent_id)
|
||||
.filter((schedule) => schedule.graph_id === agent.graph_id)
|
||||
.map((schedule) => (
|
||||
<AgentRunSummaryCard
|
||||
className="h-28 w-72 lg:h-32 xl:w-80"
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function LibraryAgentCard({
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
agent_id,
|
||||
graph_id: agent_id,
|
||||
can_access_graph,
|
||||
creator_image_url,
|
||||
image_url,
|
||||
|
||||
@@ -109,7 +109,7 @@ export const AgentFlowList = ({
|
||||
lastRun: GraphExecutionMeta | null = null;
|
||||
if (executions) {
|
||||
const _flowRuns = executions.filter(
|
||||
(r) => r.graph_id == flow.agent_id,
|
||||
(r) => r.graph_id == flow.graph_id,
|
||||
);
|
||||
runCount = _flowRuns.length;
|
||||
lastRun =
|
||||
|
||||
@@ -49,7 +49,7 @@ export const FlowInfo: React.FC<
|
||||
}
|
||||
> = ({ flow, executions, flowVersion, refresh, ...props }) => {
|
||||
const { requestSaveAndRun, requestStopRun, isRunning, nodes, setNodes } =
|
||||
useAgentGraph(flow.agent_id, flow.agent_version, undefined, false);
|
||||
useAgentGraph(flow.graph_id, flow.graph_version, undefined, false);
|
||||
|
||||
const api = useBackendAPI();
|
||||
const { toast } = useToast();
|
||||
@@ -61,7 +61,7 @@ export const FlowInfo: React.FC<
|
||||
const selectedFlowVersion: Graph | undefined = flowVersions?.find(
|
||||
(v) =>
|
||||
v.version ==
|
||||
(selectedVersion == "all" ? flow.agent_version : selectedVersion),
|
||||
(selectedVersion == "all" ? flow.graph_version : selectedVersion),
|
||||
);
|
||||
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
@@ -110,9 +110,9 @@ export const FlowInfo: React.FC<
|
||||
|
||||
useEffect(() => {
|
||||
api
|
||||
.getGraphAllVersions(flow.agent_id)
|
||||
.getGraphAllVersions(flow.graph_id)
|
||||
.then((result) => setFlowVersions(result));
|
||||
}, [flow.agent_id, api]);
|
||||
}, [flow.graph_id, api]);
|
||||
|
||||
const openRunnerInput = () => setIsRunnerInputOpen(true);
|
||||
|
||||
@@ -152,7 +152,7 @@ export const FlowInfo: React.FC<
|
||||
<Card {...props}>
|
||||
<CardHeader className="">
|
||||
<CardTitle>
|
||||
{flow.name} <span className="font-light">v{flow.agent_version}</span>
|
||||
{flow.name} <span className="font-light">v{flow.graph_version}</span>
|
||||
</CardTitle>
|
||||
<div className="flex flex-col space-y-2 py-6">
|
||||
{(flowVersions?.length ?? 0) > 1 && (
|
||||
@@ -195,7 +195,7 @@ export const FlowInfo: React.FC<
|
||||
{flow.can_access_graph && (
|
||||
<Link
|
||||
className={buttonVariants({ variant: "default" })}
|
||||
href={`/build?flowID=${flow.agent_id}&flowVersion=${flow.agent_version}`}
|
||||
href={`/build?flowID=${flow.graph_id}&flowVersion=${flow.graph_version}`}
|
||||
>
|
||||
<Pencil2Icon className="mr-2" />
|
||||
Open in Builder
|
||||
@@ -209,7 +209,7 @@ export const FlowInfo: React.FC<
|
||||
data-testid="export-button"
|
||||
onClick={() =>
|
||||
api
|
||||
.getGraph(flow.agent_id, selectedFlowVersion!.version, true)
|
||||
.getGraph(flow.graph_id, selectedFlowVersion!.version, true)
|
||||
.then((graph) =>
|
||||
exportAsJSONFile(
|
||||
graph,
|
||||
@@ -248,7 +248,7 @@ export const FlowInfo: React.FC<
|
||||
flows={[flow]}
|
||||
executions={executions.filter(
|
||||
(execution) =>
|
||||
execution.graph_id == flow.agent_id &&
|
||||
execution.graph_id == flow.graph_id &&
|
||||
(selectedVersion == "all" ||
|
||||
execution.graph_version == selectedVersion),
|
||||
)}
|
||||
|
||||
@@ -26,9 +26,9 @@ export const FlowRunInfo: React.FC<
|
||||
const api = useBackendAPI();
|
||||
|
||||
const fetchBlockResults = useCallback(async () => {
|
||||
const graph = await api.getGraph(agent.agent_id, agent.agent_version);
|
||||
const graph = await api.getGraph(agent.graph_id, agent.graph_version);
|
||||
const graphExecution = await api.getGraphExecutionInfo(
|
||||
agent.agent_id,
|
||||
agent.graph_id,
|
||||
execution.id,
|
||||
);
|
||||
|
||||
@@ -49,7 +49,7 @@ export const FlowRunInfo: React.FC<
|
||||
),
|
||||
),
|
||||
);
|
||||
}, [api, agent.agent_id, agent.agent_version, execution.id]);
|
||||
}, [api, agent.graph_id, agent.graph_version, execution.id]);
|
||||
|
||||
// Fetch graph and execution data
|
||||
useEffect(() => {
|
||||
@@ -57,15 +57,15 @@ export const FlowRunInfo: React.FC<
|
||||
fetchBlockResults();
|
||||
}, [isOutputOpen, fetchBlockResults]);
|
||||
|
||||
if (execution.graph_id != agent.agent_id) {
|
||||
if (execution.graph_id != agent.graph_id) {
|
||||
throw new Error(
|
||||
`FlowRunInfo can't be used with non-matching execution.graph_id and flow.id`,
|
||||
);
|
||||
}
|
||||
|
||||
const handleStopRun = useCallback(() => {
|
||||
api.stopGraphExecution(agent.agent_id, execution.id);
|
||||
}, [api, agent.agent_id, execution.id]);
|
||||
api.stopGraphExecution(agent.graph_id, execution.id);
|
||||
}, [api, agent.graph_id, execution.id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -98,7 +98,7 @@ export const FlowRunInfo: React.FC<
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="hidden">
|
||||
<strong>Agent ID:</strong> <code>{agent.agent_id}</code>
|
||||
<strong>Agent ID:</strong> <code>{agent.graph_id}</code>
|
||||
</p>
|
||||
<p className="hidden">
|
||||
<strong>Run ID:</strong> <code>{execution.id}</code>
|
||||
|
||||
@@ -48,7 +48,7 @@ export const FlowRunsList: React.FC<{
|
||||
<TableCell>
|
||||
<TextRenderer
|
||||
value={
|
||||
flows.find((f) => f.agent_id == execution.graph_id)?.name
|
||||
flows.find((f) => f.graph_id == execution.graph_id)?.name
|
||||
}
|
||||
truncateLengthLimit={30}
|
||||
/>
|
||||
|
||||
@@ -64,7 +64,7 @@ export const FlowRunsTimeline = ({
|
||||
time: number;
|
||||
_duration: number;
|
||||
} = payload[0].payload;
|
||||
const flow = flows.find((f) => f.agent_id === data.graph_id);
|
||||
const flow = flows.find((f) => f.graph_id === data.graph_id);
|
||||
return (
|
||||
<Card className="p-2 text-xs leading-normal">
|
||||
<p>
|
||||
@@ -98,7 +98,7 @@ export const FlowRunsTimeline = ({
|
||||
<Scatter
|
||||
key={flow.id}
|
||||
data={executions
|
||||
.filter((e) => e.graph_id == flow.agent_id)
|
||||
.filter((e) => e.graph_id == flow.graph_id)
|
||||
.map((e) => ({
|
||||
...e,
|
||||
time:
|
||||
|
||||
@@ -90,8 +90,8 @@ export const SchedulesTable = ({
|
||||
const handleAgentSelect = (agentId: string) => {
|
||||
setSelectedAgent(agentId);
|
||||
const agent = agents.find((a) => a.id === agentId);
|
||||
setMaxVersion(agent!.agent_version);
|
||||
setSelectedVersion(agent!.agent_version);
|
||||
setMaxVersion(agent!.graph_version);
|
||||
setSelectedVersion(agent!.graph_version);
|
||||
};
|
||||
|
||||
const handleVersionSelect = (version: string) => {
|
||||
@@ -120,7 +120,7 @@ export const SchedulesTable = ({
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
router.push(
|
||||
`/build?flowID=${agent.agent_id}&flowVersion=${agent.agent_version}&open_scheduling=true`,
|
||||
`/build?flowID=${agent.graph_id}&flowVersion=${agent.graph_version}&open_scheduling=true`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Navigation error:", error);
|
||||
@@ -184,7 +184,7 @@ export const SchedulesTable = ({
|
||||
</SelectTrigger>
|
||||
<SelectContent className="text-xs">
|
||||
{agents.map((agent) => (
|
||||
<SelectItem key={agent.id} value={agent.agent_id}>
|
||||
<SelectItem key={agent.id} value={agent.graph_id}>
|
||||
{agent.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -237,7 +237,7 @@ export const SchedulesTable = ({
|
||||
filteredAndSortedSchedules.map((schedule) => (
|
||||
<TableRow key={schedule.id}>
|
||||
<TableCell className="font-medium">
|
||||
{agents.find((a) => a.agent_id === schedule.graph_id)
|
||||
{agents.find((a) => a.graph_id === schedule.graph_id)
|
||||
?.name || schedule.graph_id}
|
||||
</TableCell>
|
||||
<TableCell>{schedule.graph_version}</TableCell>
|
||||
|
||||
@@ -356,8 +356,8 @@ export type NodeExecutionResult = {
|
||||
/* Mirror of backend/server/v2/library/model.py:LibraryAgent */
|
||||
export type LibraryAgent = {
|
||||
id: LibraryAgentID;
|
||||
agent_id: GraphID;
|
||||
agent_version: number;
|
||||
graph_id: GraphID;
|
||||
graph_version: number;
|
||||
image_url?: string;
|
||||
creator_name: string;
|
||||
creator_image_url: string;
|
||||
@@ -393,8 +393,8 @@ export interface LibraryAgentResponse {
|
||||
export interface LibraryAgentPreset {
|
||||
id: string;
|
||||
updated_at: Date;
|
||||
agent_id: string;
|
||||
agent_version: number;
|
||||
graph_id: GraphID;
|
||||
graph_version: number;
|
||||
name: string;
|
||||
description: string;
|
||||
is_active: boolean;
|
||||
@@ -414,8 +414,8 @@ export interface CreateLibraryAgentPresetRequest {
|
||||
name: string;
|
||||
description: string;
|
||||
inputs: { [key: string]: any };
|
||||
agent_id: string;
|
||||
agent_version: number;
|
||||
graph_id: GraphID;
|
||||
graph_version: number;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user