Skip to content
This repository has been archived by the owner on Sep 13, 2023. It is now read-only.

Commit

Permalink
Added in protocol changes and a method to keep it synced whilst we wa…
Browse files Browse the repository at this point in the history
…it for the agent-protocol to be updated
  • Loading branch information
Swiftyos committed Aug 17, 2023
1 parent 8b44ecb commit b7fa5f6
Show file tree
Hide file tree
Showing 16 changed files with 966 additions and 137 deletions.
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.PHONY: update-protocol

update-protocol:
@if [ -d "../agent-protocol/sdk/python/agent_protocol" ]; then \
cp -r ../agent-protocol/sdk/python/agent_protocol autogpt; \
rm -Rf autogpt/agent_protocol/utils; \
rm -Rf autogpt/agent_protocol/cli.py; \
echo "Protocol updated successfully!"; \
else \
echo "Error: Source directory ../agent-protocol/sdk/python/agent_protocol does not exist."; \
exit 1; \
fi

change-protocol:
@if [ -d "autogpt/agent_protocol" ]; then \
cp -r autogpt/agent_protocol ../agent-protocol/sdk/python; \
rm ../agent-protocol/sdk/python/agent_protocol/README.md; \
echo "Protocol reversed successfully!"; \
else \
echo "Error: Target directory autogpt/agent_protocol does not exist."; \
exit 1; \
fi
4 changes: 2 additions & 2 deletions autogpt/__main__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import os

from agent_protocol import Agent
from dotenv import load_dotenv

import autogpt.agent
import autogpt.db
from autogpt.agent_protocol import Agent
from autogpt.benchmark_integration import add_benchmark_routes

if __name__ == "__main__":
Expand All @@ -19,7 +19,7 @@
auto_gpt = autogpt.agent.AutoGPT()

database = autogpt.db.AgentDB(database_name)
agent = Agent.setup_agent(auto_gpt.task_handler, auto_gpt.step_handler)
agent = Agent.setup_agent(auto_gpt.create_task, auto_gpt.run_step)
agent.db = database
agent.workspace = workspace
agent.start(port=port, router=router)
36 changes: 28 additions & 8 deletions autogpt/agent.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import time

from agent_protocol import Agent, Step, Task

import autogpt.utils
from autogpt.agent_protocol import Agent, Artifact, Step, Task, TaskDB


class AutoGPT:
def __init__(self) -> None:
pass
class AutoGPT(Agent):
def __init__(self, db: TaskDB) -> None:
super().__init__(db)

async def task_handler(self, task: Task) -> None:
async def create_task(self, task: Task) -> None:
print(f"task: {task.input}")
await Agent.db.create_step(task.task_id, task.input, is_last=True)
time.sleep(2)

# autogpt.utils.run(task.input) the task_handler only creates the task, it doesn't execute it
autogpt.utils.run(
task.input
) # the task_handler only creates the task, it doesn't execute it
# print(f"Created Task id: {task.task_id}")
return task

async def step_handler(self, step: Step) -> Step:
async def run_step(self, step: Step) -> Step:
# print(f"step: {step}")
agent_step = await Agent.db.get_step(step.task_id, step.step_id)
updated_step: Step = await Agent.db.update_step(
Expand All @@ -30,3 +31,22 @@ async def step_handler(self, step: Step) -> Step:
else:
print(f"Step completed: {updated_step}")
return updated_step

async def retrieve_artifact(self, task_id: str, artifact: Artifact) -> Artifact:
"""
Retrieve the artifact data from wherever it is stored and return it as bytes.
"""
return artifact

async def save_artifact(self, task_id: str, artifact: Artifact) -> Artifact:
"""
Save the artifact data to the agent's workspace, loading from uri if bytes are not available.
"""
assert (
artifact.data is not None and artifact.uri is not None
), "Artifact data or uri must be set"

if artifact.data is None:
self.db.save_artifact(task_id, artifact)

return artifact
21 changes: 21 additions & 0 deletions autogpt/agent_protocol/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Autogpt Protocol Directory

# DO NOT MODIFY ANY FILES IN THIS DIRECTORY

This directory contains protocol definitions crucial for our project. The current setup is a temporary measure to allow for speedy updating of the protocol.

## Background

In an ideal scenario, we'd directly use a submodule pointing to the original repository. However, given our specific needs and to expedite our development process, we've chosen a slightly different approach.

## Process

1. **Fork and Clone**: We started by forking the original repository `e2b-dev/agent-protocol` (not `Swiftyos/agent-protocol` as previously mentioned) to have our own version. This allows us to have more control over updates and possibly any specific changes that our project might need in the future.

2. **Manual Content Integration**: Instead of adding the entire forked repository as a submodule, we've manually copied over the contents of `sdk/python/agent_protocol` into this directory. This ensures we only have the parts we need, without any additional overhead.

3. **Updates**: Any necessary updates to the protocol can be made directly in our fork, and subsequently, the required changes can be reflected in this directory.

## Credits

All credit for the original protocol definitions goes to [e2b-dev/agent-protocol](https://github.com/e2b-dev/agent-protocol). We deeply appreciate their efforts in building the protocol, and this temporary measure is in no way intended to diminish the significance of their work. It's purely a practical approach for our specific requirements at this point in our development phase.
16 changes: 16 additions & 0 deletions autogpt/agent_protocol/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from .agent import Agent
from .agent import base_router as router
from .db import Step, Task, TaskDB
from .models import Artifact, Status, StepRequestBody, TaskRequestBody

__all__ = [
"Agent",
"Artifact",
"Status",
"Step",
"StepRequestBody",
"Task",
"TaskDB",
"TaskRequestBody",
"router",
]
240 changes: 240 additions & 0 deletions autogpt/agent_protocol/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import asyncio
import os
import tempfile
from typing import List
from uuid import uuid4

from fastapi import APIRouter, Request, Response, UploadFile
from fastapi.responses import FileResponse, Response
from hypercorn.asyncio import serve
from hypercorn.config import Config

from .db import Step, Task, TaskDB
from .middlewares import AgentMiddleware
from .models import Artifact, Status, StepRequestBody, TaskRequestBody
from .server import app

base_router = APIRouter()


@base_router.get("/heartbeat")
async def heartbeat() -> Response:
"""
Heartbeat endpoint to check if the server is running.
"""
return Response(status_code=200)


@base_router.post("/agent/tasks", response_model=Task, tags=["agent"])
async def create_agent_task(
request: Request, body: TaskRequestBody | None = None
) -> Task:
"""
Creates a task for the agent.
"""
agent: Agent = request["agent"]

task = await agent.db.create_task(
input=body.input if body else None,
additional_input=body.additional_input if body else None,
)
await agent.create_task(task)

return task


@base_router.get("/agent/tasks", response_model=List[str], tags=["agent"])
async def list_agent_tasks_ids(request: Request) -> List[str]:
"""
List all tasks that have been created for the agent.
"""
agent: Agent = request["agent"]
return [task.task_id for task in await agent.db.list_tasks()]


@base_router.get("/agent/tasks/{task_id}", response_model=Task, tags=["agent"])
async def get_agent_task(request: Request, task_id: str) -> Task:
"""
Get details about a specified agent task.
"""
agent: Agent = request["agent"]
return await agent.db.get_task(task_id)


@base_router.get(
"/agent/tasks/{task_id}/steps",
response_model=List[str],
tags=["agent"],
)
async def list_agent_task_steps(request: Request, task_id: str) -> List[str]:
"""
List all steps for the specified task.
"""
agent: Agent = request["agent"]
task = await agent.db.get_task(task_id)
return [s.step_id for s in task.steps]


@base_router.post(
"/agent/tasks/{task_id}/steps",
response_model=Step,
tags=["agent"],
)
async def execute_agent_task_step(
request: Request,
task_id: str,
body: StepRequestBody | None = None,
) -> Step:
"""
Execute a step in the specified agent task.
"""
agent: Agent = request["agent"]

task = await agent.db.get_task(task_id)
step = next(filter(lambda x: x.status == Status.created, task.steps), None)

if not step:
raise Exception("No steps to execute")

step.status = Status.running

step.input = body.input if body else None
step.additional_input = body.additional_input if body else None

step = await agent.run_step(step)

step.status = Status.completed
return step


@base_router.get(
"/agent/tasks/{task_id}/steps/{step_id}",
response_model=Step,
tags=["agent"],
)
async def get_agent_task_step(request: Request, task_id: str, step_id: str) -> Step:
"""
Get details about a specified task step.
"""
agent: Agent = request["agent"]
return await agent.db.get_step(task_id, step_id)


@base_router.get(
"/agent/tasks/{task_id}/artifacts",
response_model=List[Artifact],
tags=["agent"],
)
async def list_agent_task_artifacts(request: Request, task_id: str) -> List[Artifact]:
"""
List all artifacts for the specified task.
"""
agent: Agent = request["agent"]
task = await agent.db.get_task(task_id)
return task.artifacts


@base_router.post(
"/agent/tasks/{task_id}/artifacts",
response_model=Artifact,
tags=["agent"],
)
async def upload_agent_task_artifacts(
request: Request,
task_id: str,
file: UploadFile | None = None,
uri: str | None = None,
) -> Artifact:
"""
Upload an artifact for the specified task.
"""
agent: Agent = request["agent"]
if not file and not uri:
return Response(status_code=400, content="No file or uri provided")

if uri:
artifact = Artifact(
task_id=task_id,
file_name=uri.spit("/")[-1],
uri=uri,
)
else:
file_name = file.filename or str(uuid4())
try:
data = b""
while contents := file.file.read(1024 * 1024):
data += contents
except Exception as e:
return Response(status_code=500, content=str(e))
artifact = Artifact(
task_id=task_id,
file_name=file_name,
data=contents,
)

artifact = await agent.save_artifact(task_id, artifact)
agent.db.add_artifact(task_id, artifact)

return artifact


@base_router.get(
"/agent/tasks/{task_id}/artifacts/{artifact_id}",
tags=["agent"],
)
async def download_agent_task_artifacts(
request: Request, task_id: str, artifact_id: str
) -> FileResponse:
"""
Download the specified artifact.
"""
agent: Agent = request["agent"]
artifact = await agent.db.get_artifact(task_id, artifact_id)
retrieved_artifact: Artifact = agent.retrieve_artifact(task_id, artifact)
with tempfile.TemporaryDirectory() as tmpdir:
path = os.path.join(tmpdir, artifact.file_name)
with open(path, "wb") as f:
f.write(retrieved_artifact.data)
return FileResponse(
# Note: mimetype is guessed in the FileResponse constructor
path=path,
filename=artifact.file_name,
)


class Agent:
def __init__(self, db: TaskDB):
self.name = self.__class__.__name__
self.db = db

def start(self, port: int = 8000, router: APIRouter = base_router):
"""
Start the agent server.
"""
config = Config()
config.bind = [f"localhost:{port}"] # As an example configuration setting
app.title = f"{self.name} - Agent Communication Protocol"
app.include_router(router)
app.add_middleware(AgentMiddleware, agent=self)
asyncio.run(serve(app, config))

async def create_task(self, task: Task):
"""
Handles a new task
"""
return task

async def run_step(self, step: Step) -> Step:
return step

async def retrieve_artifact(self, task_id: str, artifact: Artifact) -> Artifact:
"""
Retrieve the artifact data from wherever it is stored and return it as bytes.
"""
return artifact

async def save_artifact(self, task_id: str, artifact: Artifact) -> Artifact:
"""
Save the artifact data to the agent's workspace, loading from uri if bytes are not available.
"""
return artifact
Loading

0 comments on commit b7fa5f6

Please sign in to comment.