Agent and Mission GraphQL CRUD operations

This commit is contained in:
Eng. Elias
2024-02-20 17:06:45 +04:00
parent e6f08d85fc
commit 619f199abe
13 changed files with 1744 additions and 59 deletions

1382
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,11 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.9.5",
"@apollo/server": "^4.10.0",
"@as-integrations/next": "^3.0.0",
"@prisma/client": "^5.9.1", "@prisma/client": "^5.9.1",
"graphql": "^16.8.1",
"next": "14.1.0", "next": "14.1.0",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",

View File

@@ -24,10 +24,10 @@ CREATE TABLE "Agent" (
CREATE TABLE "Mission" ( CREATE TABLE "Mission" (
"id" SERIAL NOT NULL, "id" SERIAL NOT NULL,
"name" TEXT NOT NULL, "name" TEXT NOT NULL,
"tasks" JSONB NOT NULL, "tasks" JSONB[] DEFAULT ARRAY[]::JSONB[],
"verbose" BOOLEAN NOT NULL, "verbose" BOOLEAN NOT NULL DEFAULT false,
"process" "MissionProcess" NOT NULL, "process" "MissionProcess" NOT NULL DEFAULT 'SEQUENTIAL',
"result" TEXT NOT NULL, "result" TEXT NOT NULL DEFAULT '',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL, "updatedAt" TIMESTAMP(3) NOT NULL,
@@ -35,16 +35,19 @@ CREATE TABLE "Mission" (
); );
-- CreateTable -- CreateTable
CREATE TABLE "AgentInMission" ( CREATE TABLE "_AgentToMission" (
"agentId" INTEGER NOT NULL, "A" INTEGER NOT NULL,
"missionId" INTEGER NOT NULL, "B" INTEGER NOT NULL
"assignedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AgentInMission_pkey" PRIMARY KEY ("agentId","missionId")
); );
-- AddForeignKey -- CreateIndex
ALTER TABLE "AgentInMission" ADD CONSTRAINT "AgentInMission_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agent"("id") ON DELETE RESTRICT ON UPDATE CASCADE; CREATE UNIQUE INDEX "_AgentToMission_AB_unique" ON "_AgentToMission"("A", "B");
-- CreateIndex
CREATE INDEX "_AgentToMission_B_index" ON "_AgentToMission"("B");
-- AddForeignKey -- AddForeignKey
ALTER TABLE "AgentInMission" ADD CONSTRAINT "AgentInMission_missionId_fkey" FOREIGN KEY ("missionId") REFERENCES "Mission"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ALTER TABLE "_AgentToMission" ADD CONSTRAINT "_AgentToMission_A_fkey" FOREIGN KEY ("A") REFERENCES "Agent"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_AgentToMission" ADD CONSTRAINT "_AgentToMission_B_fkey" FOREIGN KEY ("B") REFERENCES "Mission"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -35,7 +35,7 @@ model Agent {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
missions AgentInMission[] missions Mission[]
} }
enum MissionProcess { enum MissionProcess {
@@ -44,24 +44,14 @@ enum MissionProcess {
} }
model Mission { model Mission {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
name String name String
crew AgentInMission[] crew Agent[]
tasks Json tasks Json[] @default([])
verbose Boolean verbose Boolean @default(false)
process MissionProcess process MissionProcess @default(SEQUENTIAL)
result String result String @default("")
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
model AgentInMission {
agent Agent @relation(fields: [agentId], references: [id])
agentId Int // relation scalar field (used in the `@relation` attribute above)
mission Mission @relation(fields: [missionId], references: [id])
missionId Int // relation scalar field (used in the `@relation` attribute above)
assignedAt DateTime @default(now())
@@id([agentId, missionId])
}

View File

@@ -0,0 +1,167 @@
import { Process } from "@/data/consts";
import { Agent } from "@/types/agent";
import { CreateMissionInput, Mission } from "@/types/mission";
import { Task } from "@/types/task";
import prisma from "@/utils/prisma";
import { GraphQLResolveInfo } from "graphql";
import { NextRequest, NextResponse } from "next/server";
const resolvers = {
Query: {
agents: () => {
return prisma.agent.findMany();
},
agent: (id: number) => {
return prisma.agent.findFirst({
where: {
id: id,
},
});
},
missions: async () => {
const missions = await prisma.mission.findMany({
include: {
crew: true,
},
});
return missions;
},
mission: (id: number) => {
return prisma.mission.findFirst({
where: {
id: id,
},
});
},
},
Mutation: {
createAgent: async (
parent: any,
body: Agent,
context: { req: NextRequest; res: NextResponse; datasource: any },
info: GraphQLResolveInfo
) => {
const agent = await prisma.agent.create({ data: body });
return agent;
},
updateAgent: async (
parent: any,
body: Agent,
context: { req: NextRequest; res: NextResponse; datasource: any },
info: GraphQLResolveInfo
) => {
const updatedAgent = await prisma.agent.update({
where: { id: body.id },
data: body,
});
return updatedAgent;
},
deleteAgent: async (
parent: any,
body: { id: number },
context: { req: NextRequest; res: NextResponse; datasource: any },
info: GraphQLResolveInfo
) => {
await prisma.agent.delete({ where: { id: body.id } });
return { deleted: true };
},
createMission: async (
parent: any,
body: CreateMissionInput,
context: { req: NextRequest; res: NextResponse; datasource: any },
info: GraphQLResolveInfo
) => {
const { name, verbose, process } = body;
const crew = await prisma.agent.findMany({
where: {
id: {
in: body.crew,
},
},
});
const tasks: Array<Task> = [];
for (let task of body.tasks) {
const agent = await prisma.agent.findFirst({
where: { id: task.agent },
});
tasks.push({
...task,
agent,
});
}
const mission = await prisma.mission.create({
data: {
name,
verbose: !!verbose,
process: process ?? Process.SEQUENTIAL,
crew: { create: crew },
tasks,
result: "",
},
});
mission.id;
return mission;
},
updateMission: async (
parent: any,
body: CreateMissionInput,
context: { req: NextRequest; res: NextResponse; datasource: any },
info: GraphQLResolveInfo
) => {
const { id, name, verbose, process } = body;
const crew = await prisma.agent.findMany({
where: {
id: {
in: body.crew,
},
},
});
const tasks: Array<Task> = [];
if (body.tasks) {
for (let task of body.tasks) {
const agent = await prisma.agent.findFirst({
where: { id: task.agent },
});
tasks.push({
...task,
agent,
});
}
}
const mission = await prisma.mission.update({
where: {
id,
},
include: {
crew: true,
},
data: {
name,
verbose: verbose,
process: process,
crew: { set: crew.map((agent) => ({ id: agent.id })) },
tasks,
result: "",
},
});
return mission;
},
deleteMission: async (
parent: any,
body: { id: number },
context: { req: NextRequest; res: NextResponse; datasource: any },
info: GraphQLResolveInfo
) => {
await prisma.mission.delete({ where: { id: body.id } });
return { deleted: true };
},
runMission: async (
parent: any,
body: { id: number },
context: { req: NextRequest; res: NextResponse; datasource: any },
info: GraphQLResolveInfo
) => {},
},
};
export default resolvers;

View File

@@ -0,0 +1,24 @@
import { startServerAndCreateNextHandler } from "@as-integrations/next";
import { ApolloServer } from "@apollo/server";
import { NextRequest, NextResponse } from "next/server";
import typeDefs from "./schema";
import resolvers from "./resolvers";
const server = new ApolloServer<object>({
resolvers,
typeDefs,
});
const handler = startServerAndCreateNextHandler<NextRequest>(server, {
context: async (req, res) => ({
req,
res,
dataSources: {},
}),
});
export async function GET(request: NextRequest) {
return handler(request);
}
export async function POST(request: NextRequest) {
return handler(request);
}

View File

@@ -0,0 +1,113 @@
const typeDefs = `#graphql
enum AgentTool {
DUCK_DUCK_GO_SEARCH
PUBMED
PYTHON_REPL
SEMANTIC_SCHOLER
STACK_EXCHANGE
WIKIDATA
WIKIPEDIA
YAHOO_FINANCE
YUOUTUBE_SEARCH
}
type Agent {
id: ID!
role: String!
goal: String!
backstory: String
tools: [AgentTool!]!
allowDelegation: Boolean!
verbose: Boolean!
image: String
missions: [Mission!]
}
input AgentInput {
id: ID!
}
type DeleteOutput {
deleted: Boolean!
}
type Task {
name: String!
description: String!
agent: Agent!
}
input TaskInput {
name: String!
description: String!
agent: Int
}
type Mission {
id: ID!
name: String!
crew: [Agent!]
tasks: [Task]
verbose: Boolean
process: MissionProcess
result: String
}
enum MissionProcess {
SEQUENTIAL
HIERARCHICAL
}
type Query {
agents(filter: String): [Agent!]!
agent(id: Int!): Agent
missions(filter: String): [Mission!]!
mission(id: Int!): Mission
}
type Mutation {
createAgent(
role: String!
goal: String!
backstory: String
tools: [AgentTool!] = []
allowDelegation: Boolean = false
verbose: Boolean = false
): Agent!
updateAgent(
id: Int!
role: String
goal: String
backstory: String
tools: [AgentTool!]
allowDelegation: Boolean
verbose: Boolean
): Agent!
deleteAgent(id: Int!): DeleteOutput
createMission(
name: String!
crew: [Int!] = []
tasks: [TaskInput!] = []
verbose: Boolean = false
process: MissionProcess = "SEQUENTIAL"
): Mission!
updateMission(
id: Int!
name: String
crew: [Int!]
tasks: [TaskInput!]
verbose: Boolean
process: MissionProcess
): Mission
deleteMission(id: Int!): DeleteOutput
runMission(id: Int!): Mission
}
`;
export default typeDefs;

4
src/data/consts.ts Normal file
View File

@@ -0,0 +1,4 @@
export enum Process {
SEQUENTIAL = "SEQUENTIAL",
HIERARCHICAL = "HIERARCHICAL",
}

View File

@@ -9,7 +9,7 @@ export const agents: Array<Agent> = [
Your expertise in programming in python. and do your best to Your expertise in programming in python. and do your best to
produce perfect code produce perfect code
`, `,
tools: ["tool1", "tool2"], tools: ["DUCK_DUCK_GO_SEARCH", "PYTHON_REPL", "STACK_EXCHANGE"],
allowDelegation: false, allowDelegation: false,
verbose: true, verbose: true,
image: image:

View File

@@ -1,9 +1,21 @@
type Tool =
| "DUCK_DUCK_GO_SEARCH"
| "PUBMED"
| "PYTHON_REPL"
| "SEMANTIC_SCHOLER"
| "STACK_EXCHANGE"
| "WIKIDATA"
| "WIKIPEDIA"
| "YAHOO_FINANCE"
| "YUOUTUBE_SEARCH";
export type Agent = { export type Agent = {
id?: number;
role: string; role: string;
goal: string; goal: string;
backstory: string; backstory?: string | null;
tools: Array<string>; tools: Array<Tool>;
allowDelegation: boolean; allowDelegation: boolean;
verbose: boolean; verbose: boolean;
image: string; image?: string | null;
}; };

View File

@@ -1,10 +1,23 @@
import { Task } from "./task"; import { Agent } from "./agent";
import { Task, TaskInput } from "./task";
type ProcessType = "SEQUENTIAL" | "HIERARCHICAL";
export type Mission = { export type Mission = {
id?: number;
name: string; name: string;
crew: Array<string>; crew: Array<Agent>;
tasks: Array<Task>; tasks: Array<Task>;
verbose: boolean; verbose: boolean;
process: string; process: ProcessType;
result: string; result?: string;
};
export type CreateMissionInput = {
id?: number;
name: string;
crew: Array<number>;
tasks: Array<TaskInput>;
verbose: boolean;
process: ProcessType;
}; };

View File

@@ -1,5 +1,13 @@
import { Agent } from "./agent";
export type TaskInput = {
name: string;
description: string;
agent?: number;
};
export type Task = { export type Task = {
name: string; name: string;
description: string; description: string;
agent: string; agent?: Agent | null;
}; };

5
src/utils/prisma.ts Normal file
View File

@@ -0,0 +1,5 @@
import { PrismaClient } from "@prisma/client";
const prisma: PrismaClient = new PrismaClient();
export default prisma;