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:
Reinier van der Leer
2025-04-10 12:40:25 +02:00
committed by GitHub
parent 70890dee43
commit 353396110c
29 changed files with 441 additions and 295 deletions

View File

@@ -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 },
]

View File

@@ -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={() => {

View File

@@ -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"

View File

@@ -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,

View File

@@ -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 =

View File

@@ -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),
)}

View File

@@ -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>

View File

@@ -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}
/>

View File

@@ -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:

View File

@@ -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>

View File

@@ -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;
}