Fix Forge (#22)

Signed-off-by: Merwane Hamadi <merwanehamadi@gmail.com>
This commit is contained in:
merwanehamadi
2023-08-26 08:20:12 -07:00
committed by GitHub
parent 62a8c7ae04
commit d3e86622b7
7 changed files with 108 additions and 75 deletions

BIN
agent.db Normal file

Binary file not shown.

View File

@@ -60,10 +60,8 @@ class Agent:
"""
try:
task = await self.db.create_task(
input=task_request.input if task_request.input else None,
additional_input=task_request.additional_input
if task_request.additional_input
else None,
input=task_request.input,
additional_input=task_request.additional_input,
)
return task
except Exception as e:
@@ -112,10 +110,8 @@ class Agent:
if step_request.input != "y":
step = await self.db.create_step(
task_id=task_id,
input=step_request.input if step_request else None,
additional_properties=step_request.additional_input
if step_request
else None,
input=step_request,
additional_input=step_request.additional_input,
)
# utils.run
artifacts = run(step.input)
@@ -143,7 +139,7 @@ class Agent:
step = await self.db.create_step(
task_id=task_id,
input="y",
additional_properties=None,
additional_input={},
)
step.status = "completed"
step.is_last = True
@@ -224,7 +220,7 @@ class Agent:
Get an artifact by ID.
"""
try:
artifact = await self.db.get_artifact(task_id, artifact_id)
artifact = await self.db.get_artifact(artifact_id)
retrieved_artifact = await self.load_from_uri(artifact.uri, artifact_id)
path = artifact.file_name
with open(path, "wb") as f:

View File

@@ -16,7 +16,7 @@ def agent():
@pytest.mark.asyncio
async def test_create_task(agent):
task_request = TaskRequestBody(
input="test_input", additional_input="additional_test_input"
input="test_input", additional_input={"input": "additional_test_input"}
)
task: Task = await agent.create_task(task_request)
assert task.input == "test_input"
@@ -25,7 +25,7 @@ async def test_create_task(agent):
@pytest.mark.asyncio
async def test_list_tasks(agent):
task_request = TaskRequestBody(
input="test_input", additional_input="additional_test_input"
input="test_input", additional_input={"input": "additional_test_input"}
)
task = await agent.create_task(task_request)
tasks = await agent.list_tasks()
@@ -35,7 +35,7 @@ async def test_list_tasks(agent):
@pytest.mark.asyncio
async def test_get_task(agent):
task_request = TaskRequestBody(
input="test_input", additional_input="additional_test_input"
input="test_input", additional_input={"input": "additional_test_input"}
)
task = await agent.create_task(task_request)
retrieved_task = await agent.get_task(task.task_id)
@@ -46,26 +46,26 @@ async def test_get_task(agent):
@pytest.mark.asyncio
async def test_create_and_execute_step(agent):
task_request = TaskRequestBody(
input="test_input", additional_input="additional_test_input"
input="test_input", additional_input={"input": "additional_test_input"}
)
task = await agent.create_task(task_request)
step_request = StepRequestBody(
input="step_input", additional_input="additional_step_input"
input="step_input", additional_input={"input": "additional_test_input"}
)
step = await agent.create_and_execute_step(task.task_id, step_request)
assert step.input == "step_input"
assert step.additional_input == "additional_step_input"
assert step.additional_input == {"input": "additional_test_input"}
@pytest.mark.skip
@pytest.mark.asyncio
async def test_get_step(agent):
task_request = TaskRequestBody(
input="test_input", additional_input="additional_test_input"
input="test_input", additional_input={"input": "additional_test_input"}
)
task = await agent.create_task(task_request)
step_request = StepRequestBody(
input="step_input", additional_input="additional_step_input"
input="step_input", additional_input={"input": "additional_test_input"}
)
step = await agent.create_and_execute_step(task.task_id, step_request)
retrieved_step = await agent.get_step(task.task_id, step.step_id)
@@ -83,7 +83,7 @@ async def test_list_artifacts(agent):
@pytest.mark.asyncio
async def test_create_artifact(agent):
task_request = TaskRequestBody(
input="test_input", additional_input="additional_test_input"
input="test_input", additional_input={"input": "additional_test_input"}
)
task = await agent.create_task(task_request)
artifact_request = ArtifactRequestBody(file=None, uri="test_uri")
@@ -95,7 +95,7 @@ async def test_create_artifact(agent):
@pytest.mark.asyncio
async def test_get_artifact(agent):
task_request = TaskRequestBody(
input="test_input", additional_input="additional_test_input"
input="test_input", additional_input={"input": "additional_test_input"}
)
task = await agent.create_task(task_request)
artifact_request = ArtifactRequestBody(file=None, uri="test_uri")

View File

@@ -7,9 +7,17 @@ IT IS NOT ADVISED TO USE THIS IN PRODUCTION!
import datetime
import math
import uuid
from typing import Dict, List, Optional, Tuple
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String, create_engine
from sqlalchemy import (
JSON,
Boolean,
Column,
DateTime,
ForeignKey,
String,
create_engine,
)
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import DeclarativeBase, joinedload, relationship, sessionmaker
@@ -29,7 +37,7 @@ class TaskModel(Base):
task_id = Column(String, primary_key=True, index=True)
input = Column(String)
additional_input = Column(String)
additional_input = Column(JSON)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
modified_at = Column(
DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow
@@ -52,7 +60,7 @@ class StepModel(Base):
DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow
)
additional_properties = Column(String)
additional_input = Column(JSON)
artifacts = relationship("ArtifactModel", back_populates="step")
@@ -105,7 +113,7 @@ def convert_to_step(step_model: StepModel, debug_enabled: bool = False) -> Step:
status=status,
artifacts=step_artifacts,
is_last=step_model.is_last == 1,
additional_properties=step_model.additional_properties,
additional_input=step_model.additional_input,
)
@@ -131,7 +139,7 @@ class AgentDB:
self.Session = sessionmaker(bind=self.engine)
async def create_task(
self, input: Optional[str], additional_input: Optional[TaskInput] = None
self, input: Optional[str], additional_input: Optional[TaskInput] = {}
) -> Task:
if self.debug_enabled:
LOG.debug("Creating new task")
@@ -141,9 +149,7 @@ class AgentDB:
new_task = TaskModel(
task_id=str(uuid.uuid4()),
input=input,
additional_input=additional_input.__root__
if additional_input
else None,
additional_input=additional_input,
)
session.add(new_task)
session.commit()
@@ -165,7 +171,7 @@ class AgentDB:
task_id: str,
input: str,
is_last: bool = False,
additional_properties: Optional[Dict[str, str]] = None,
additional_input: Optional[Dict[str, Any]] = {},
) -> Step:
if self.debug_enabled:
LOG.debug(f"Creating new step for task_id: {task_id}")
@@ -174,11 +180,11 @@ class AgentDB:
new_step = StepModel(
task_id=task_id,
step_id=str(uuid.uuid4()),
name=input,
input=input,
name=input.name,
input=input.input,
status="created",
is_last=is_last,
additional_properties=additional_properties,
additional_input=additional_input,
)
session.add(new_step)
session.commit()
@@ -299,7 +305,7 @@ class AgentDB:
task_id: str,
step_id: str,
status: str,
additional_properties: Optional[Dict[str, str]] = None,
additional_input: Optional[Dict[str, Any]] = {},
) -> Step:
if self.debug_enabled:
LOG.debug(f"Updating step with task_id: {task_id} and step_id: {step_id}")
@@ -311,7 +317,7 @@ class AgentDB:
.first()
):
step.status = status
step.additional_properties = additional_properties
step.additional_input = additional_input
session.commit()
return await self.get_step(task_id, step_id)
else:
@@ -364,8 +370,6 @@ class AgentDB:
.limit(per_page)
.all()
)
if not tasks:
raise NotFoundError("No tasks found")
total = session.query(TaskModel).count()
pages = math.ceil(total / per_page)
pagination = Pagination(
@@ -400,8 +404,6 @@ class AgentDB:
.limit(per_page)
.all()
)
if not steps:
raise NotFoundError("No steps found")
total = session.query(StepModel).filter_by(task_id=task_id).count()
pages = math.ceil(total / per_page)
pagination = Pagination(
@@ -436,8 +438,6 @@ class AgentDB:
.limit(per_page)
.all()
)
if not artifacts:
raise NotFoundError("No artifacts found")
total = session.query(ArtifactModel).filter_by(task_id=task_id).count()
pages = math.ceil(total / per_page)
pagination = Pagination(

View File

@@ -1,25 +1,25 @@
"""
Routes for the Agent Service.
This module defines the API routes for the Agent service. While there are multiple endpoints provided by the service,
This module defines the API routes for the Agent service. While there are multiple endpoints provided by the service,
the ones that require special attention due to their complexity are:
1. `execute_agent_task_step`:
This route is significant because this is where the agent actually performs the work. The function handles
executing the next step for a task based on its current state, and it requires careful implementation to ensure
1. `execute_agent_task_step`:
This route is significant because this is where the agent actually performs the work. The function handles
executing the next step for a task based on its current state, and it requires careful implementation to ensure
all scenarios (like the presence or absence of steps or a step marked as `last_step`) are handled correctly.
2. `upload_agent_task_artifacts`:
This route allows for the upload of artifacts, supporting various URI types (e.g., s3, gcs, ftp, http).
The support for different URI types makes it a bit more complex, and it's important to ensure that all
supported URI types are correctly managed. NOTE: The Auto-GPT team will eventually handle the most common
2. `upload_agent_task_artifacts`:
This route allows for the upload of artifacts, supporting various URI types (e.g., s3, gcs, ftp, http).
The support for different URI types makes it a bit more complex, and it's important to ensure that all
supported URI types are correctly managed. NOTE: The Auto-GPT team will eventually handle the most common
uri types for you.
3. `create_agent_task`:
While this is a simpler route, it plays a crucial role in the workflow, as it's responsible for the creation
3. `create_agent_task`:
While this is a simpler route, it plays a crucial role in the workflow, as it's responsible for the creation
of a new task.
Developers and contributors should be especially careful when making modifications to these routes to ensure
Developers and contributors should be especially careful when making modifications to these routes to ensure
consistency and correctness in the system's behavior.
"""
import json
@@ -102,7 +102,11 @@ async def create_agent_task(request: Request, task_request: TaskRequestBody) ->
try:
task_request = await agent.create_task(task_request)
return Response(content=task_request.json(), status_code=200)
return Response(
content=task_request.json(),
status_code=200,
media_type="application/json",
)
except NotFoundError:
return Response(
content=json.dumps({"error": "Task not found"}),
@@ -161,7 +165,11 @@ async def list_agent_tasks(
agent = request["agent"]
try:
tasks = await agent.list_tasks(page, page_size)
return Response(content=tasks.json(), status_code=200)
return Response(
content=tasks.json(),
status_code=200,
media_type="application/json",
)
except NotFoundError:
return Response(
content=json.dumps({"error": "Task not found"}),
@@ -232,7 +240,11 @@ async def get_agent_task(request: Request, task_id: str) -> Task:
agent = request["agent"]
try:
task = await agent.get_task(task_id)
return Response(content=task.json(), status_code=200)
return Response(
content=task.json(),
status_code=200,
media_type="application/json",
)
except NotFoundError:
return Response(
content=json.dumps({"error": "Task not found"}),
@@ -293,7 +305,11 @@ async def list_agent_task_steps(
agent = request["agent"]
try:
steps = await agent.list_steps(task_id, page, page_size)
return Response(content=steps.json(), status_code=200)
return Response(
content=steps.json(),
status_code=200,
media_type="application/json",
)
except NotFoundError:
return Response(
content=json.dumps({"error": "Task not found"}),
@@ -355,7 +371,11 @@ async def execute_agent_task_step(
agent = request["agent"]
try:
step = await agent.create_and_execute_step(task_id, step)
return Response(content=step.json(), status_code=200)
return Response(
content=step.json(),
status_code=200,
media_type="application/json",
)
except NotFoundError:
return Response(
content=json.dumps({"error": f"Task not found {task_id}"}),
@@ -462,7 +482,7 @@ async def list_agent_task_artifacts(
agent = request["agent"]
try:
artifacts = await agent.list_artifacts(task_id, page, page_size)
return Response(content=artifacts.json(), status_code=200)
return artifacts
except NotFoundError:
return Response(
content=json.dumps({"error": "Task not found"}),
@@ -522,21 +542,31 @@ async def upload_agent_task_artifacts(
agent = request["agent"]
if file is None and uri is None:
return Response(
content={"error": "Either file or uri must be specified"}, status_code=404
content=json.dumps({"error": "Either file or uri must be specified"}),
status_code=404,
media_type="application/json",
)
if file is not None and uri is not None:
return Response(
content={"error": "Both file and uri cannot be specified at the same time"},
content=json.dumps(
{"error": "Both file and uri cannot be specified at the same time"}
),
status_code=404,
media_type="application/json",
)
if uri is not None and not uri.startswith(("http://", "https://", "file://")):
return Response(
content={"error": "URI must start with http, https or file"},
content=json.dumps({"error": "URI must start with http, https or file"}),
status_code=404,
media_type="application/json",
)
try:
artifact = await agent.create_artifact(task_id, file, uri)
return Response(content=artifact.json(), status_code=200)
return Response(
content=artifact.json(),
status_code=200,
media_type="application/json",
)
except NotFoundError:
return Response(
content=json.dumps({"error": "Task not found"}),

View File

@@ -63,10 +63,11 @@ class StepOutput(BaseModel):
class TaskRequestBody(BaseModel):
input: str = Field(
...,
min_length=1,
description="Input prompt for the task.",
example="Write the words you receive to the file 'output.txt'.",
)
additional_input: Optional[TaskInput] = None
additional_input: Optional[TaskInput] = {}
class Task(TaskRequestBody):
@@ -102,9 +103,12 @@ class StepRequestBody(BaseModel):
None, description="The name of the task step.", example="Write to file"
)
input: str = Field(
..., description="Input prompt for the step.", example="Washington"
...,
min_length=1,
description="Input prompt for the step.",
example="Washington",
)
additional_input: Optional[StepInput] = None
additional_input: Optional[StepInput] = {}
class Status(Enum):
@@ -147,7 +151,7 @@ class Step(StepRequestBody):
description="Output of the task step.",
example="I am going to use the write_to_file command and write Washington to a file called output.txt <write_to_file('output.txt', 'Washington')",
)
additional_output: Optional[StepOutput] = None
additional_output: Optional[StepOutput] = {}
artifacts: Optional[List[Artifact]] = Field(
[], description="A list of artifacts that the step has produced."
)

View File

@@ -32,6 +32,7 @@ def chat_completion_request(
functions: typing.List[typing.Dict[str, str]] | None = None,
function_call: typing.Optional[str] = None,
model: str = "gpt-3.5-turbo",
temperature: float = 0,
) -> typing.Union[typing.Dict[str, typing.Any], Exception]:
"""Generate a response to a list of messages using OpenAI's API"""
try:
@@ -39,6 +40,7 @@ def chat_completion_request(
model=model,
messages=messages,
user="TheForge",
temperature=temperature,
)
except Exception as e:
LOG.info("Unable to generate ChatCompletion response")
@@ -80,30 +82,31 @@ def execute_plan(plan: typing.List[str]) -> None:
def plan(task: str) -> typing.List[str]:
"""Returns a list of tasks that needs to be executed to complete the task"""
abilities = """
plan(task: str) -> typing.List[str]
write_file(contents: str, filepath: str) -> bool
read_file(filepath:str) -> typing.Optional[str]
append_to_file(contents: str, filepath: str, to_start: bool) -> bool
read_webpage(url: str) -> str
write_file(contents='The content you want to write', filepath='file_to_write.txt')
read_file(filepath='file_to_write.txt')
"""
json_format = """
{
"steps": [
"write_file('The capital is xxx', 'answer.txt')",
"read_file('file_to_read.txt')",
"write_file(contents='The capital is xxx', filepath='answer.txt')",
"read_file(filepath='file_to_read.txt')",
]
}
"""
planning_prompt = f"""Answer in json format:
Determine the steps needed to complete the following task using only the defined list of steps with the parameters provided:
Determine the steps needed to complete the following task :
{task}
---
Possible steps
Possible steps:
{abilities}
---
Example answer:
{json_format}
---
As you can see, we only use hard coded values when calling the functions.
Please write your answer below:
"""
messages = [{"role": "user", "content": planning_prompt}]