mirror of
https://github.com/aljazceru/Auto-GPT.git
synced 2025-12-27 10:54:35 +01:00
Added in agent explination
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -168,4 +168,5 @@ agbenchmark
|
||||
.benchmarks
|
||||
.mypy_cache
|
||||
.pytest_cache
|
||||
.vscode
|
||||
.vscode
|
||||
ig_*
|
||||
@@ -14,13 +14,13 @@ repos:
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
language_version: python3.10
|
||||
language_version: python3.11
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
language_version: python3.10
|
||||
language_version: python3.11
|
||||
|
||||
# - repo: https://github.com/pre-commit/mirrors-mypy
|
||||
# rev: 'v1.3.0'
|
||||
|
||||
@@ -1,12 +1,94 @@
|
||||
import autogpt.sdk.agent
|
||||
from autogpt.sdk.db import AgentDB
|
||||
from autogpt.sdk.schema import Step, StepRequestBody
|
||||
from autogpt.sdk.workspace import Workspace
|
||||
|
||||
|
||||
class AutoGPTAgent(autogpt.sdk.agent.Agent):
|
||||
async def create_and_execute_step(
|
||||
self, task_id: str, step_request: StepRequestBody
|
||||
) -> Step:
|
||||
"""
|
||||
The goal of the Forge is to take care of the boilerplate code so you can focus on
|
||||
agent design.
|
||||
|
||||
There is a great paper surveying the agent landscape: https://arxiv.org/abs/2308.11432
|
||||
Which I would highly recommend reading as it will help you understand the possabilities.
|
||||
|
||||
Here is a summary of the key components of an agent:
|
||||
|
||||
Anatomy of an agent:
|
||||
- Profile
|
||||
- Memory
|
||||
- Planning
|
||||
- Action
|
||||
|
||||
Profile:
|
||||
|
||||
Agents typically perform a task by assuming specific roles. For example, a teacher,
|
||||
a coder, a planner etc. In using the profile in the llm prompt it has been shown to
|
||||
improve the quality of the output. https://arxiv.org/abs/2305.14688
|
||||
|
||||
Additionally baed on the profile selected, the agent could be configured to use a
|
||||
different llm. The possabilities are endless and the profile can be selected selected
|
||||
dynamically based on the task at hand.
|
||||
|
||||
Memory:
|
||||
|
||||
Memory is critical for the agent to acculmulate experiences, self-evolve, and behave
|
||||
in a more consistent, reasonable, and effective manner. There are many approaches to
|
||||
memory. However, some thoughts: there is long term and short term or working memory.
|
||||
You may want different approaches for each. There has also been work exploring the
|
||||
idea of memory reflection, which is the ability to assess its memories and re-evaluate
|
||||
them. For example, condensting short term memories into long term memories.
|
||||
|
||||
Planning:
|
||||
|
||||
When humans face a complex task, they first break it down into simple subtasks and then
|
||||
solve each subtask one by one. The planning module empowers LLM-based agents with the ability
|
||||
to think and plan for solving complex tasks, which makes the agent more comprehensive,
|
||||
powerful, and reliable. The two key methods to consider are: Planning with feedback and planning
|
||||
without feedback.
|
||||
|
||||
Action:
|
||||
|
||||
Actions translate the agents decisions into specific outcomes. For example, if the agent
|
||||
decides to write a file, the action would be to write the file. There are many approaches you
|
||||
could implement actions.
|
||||
|
||||
The Forge has a basic module for each of these areas. However, you are free to implement your own.
|
||||
This is just a starting point.
|
||||
"""
|
||||
|
||||
def __init__(self, database: AgentDB, workspace: Workspace):
|
||||
"""
|
||||
Create a step for the task and execute it.
|
||||
The database is used to store tasks, steps and artifact metadata. The workspace is used to
|
||||
store artifacts. The workspace is a directory on the file system.
|
||||
|
||||
Feel free to create subclasses of the database and workspace to implement your own storage
|
||||
"""
|
||||
return await super().create_and_execute_step(task_id, step_request)
|
||||
super().__init__(database, workspace)
|
||||
|
||||
async def execute_step(self, task_id: str, step_request: StepRequestBody) -> Step:
|
||||
"""
|
||||
The agent protocol, which is the core of the Forge, works by creating a task and then
|
||||
executing steps for that task. This method is called when the agent is asked to execute
|
||||
a step.
|
||||
|
||||
The task that is created contains an input string, for the bechmarks this is the task
|
||||
the agent has been asked to solve and additional input, which is a dictionary and
|
||||
could contain anything.
|
||||
|
||||
If you want to get the task use:
|
||||
|
||||
```
|
||||
task = await self.db.get_task(task_id)
|
||||
```
|
||||
|
||||
The step request body is essentailly the same as the task request and contains an input
|
||||
string, for the bechmarks this is the task the agent has been asked to solve and
|
||||
additional input, which is a dictionary and could contain anything.
|
||||
|
||||
You need to implement logic that will take in this step input and output the completed step
|
||||
as a step object. You can do everything in a single step or you can break it down into
|
||||
multiple steps. Returning a request to continue in the step output, the user can then decide
|
||||
if they want the agent to continue or not.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -15,7 +15,6 @@ from .middlewares import AgentMiddleware
|
||||
from .routes.agent_protocol import base_router
|
||||
from .schema import *
|
||||
from .tracing import setup_tracing
|
||||
from .utils import run
|
||||
from .workspace import Workspace
|
||||
|
||||
LOG = CustomLogger(__name__)
|
||||
@@ -102,54 +101,11 @@ class Agent:
|
||||
except Exception as e:
|
||||
raise
|
||||
|
||||
async def create_and_execute_step(
|
||||
self, task_id: str, step_request: StepRequestBody
|
||||
) -> Step:
|
||||
async def execute_step(self, task_id: str, step_request: StepRequestBody) -> Step:
|
||||
"""
|
||||
Create a step for the task.
|
||||
"""
|
||||
if step_request.input != "y":
|
||||
step = await self.db.create_step(
|
||||
task_id=task_id,
|
||||
input=step_request,
|
||||
additional_input=step_request.additional_input,
|
||||
)
|
||||
# utils.run
|
||||
artifacts = run(step.input)
|
||||
for artifact in artifacts:
|
||||
art = await self.db.create_artifact(
|
||||
task_id=step.task_id,
|
||||
file_name=artifact["file_name"],
|
||||
uri=artifact["uri"],
|
||||
agent_created=True,
|
||||
step_id=step.step_id,
|
||||
)
|
||||
assert isinstance(
|
||||
art, Artifact
|
||||
), f"Artifact not instance of Artifact {type(art)}"
|
||||
step.artifacts.append(art)
|
||||
step.status = "completed"
|
||||
else:
|
||||
steps, steps_pagination = await self.db.list_steps(
|
||||
task_id, page=1, per_page=100
|
||||
)
|
||||
# Find the latest step that has not been completed
|
||||
step = next((s for s in reversed(steps) if s.status != "completed"), None)
|
||||
if step is None:
|
||||
# If all steps have been completed, create a new placeholder step
|
||||
step = await self.db.create_step(
|
||||
task_id=task_id,
|
||||
input="y",
|
||||
additional_input={},
|
||||
)
|
||||
step.status = "completed"
|
||||
step.is_last = True
|
||||
step.output = "No more steps to run."
|
||||
step = await self.db.update_step(step)
|
||||
if isinstance(step.status, Status):
|
||||
step.status = step.status.value
|
||||
step.output = "Done some work"
|
||||
return step
|
||||
raise NotImplementedError
|
||||
|
||||
async def get_step(self, task_id: str, step_id: str) -> Step:
|
||||
"""
|
||||
|
||||
@@ -371,7 +371,7 @@ async def execute_agent_task_step(
|
||||
"""
|
||||
agent = request["agent"]
|
||||
try:
|
||||
step = await agent.create_and_execute_step(task_id, step)
|
||||
step = await agent.execute_step(task_id, step)
|
||||
return Response(
|
||||
content=step.json(),
|
||||
status_code=200,
|
||||
@@ -539,7 +539,6 @@ async def upload_agent_task_artifacts(
|
||||
"""
|
||||
agent = request["agent"]
|
||||
|
||||
|
||||
if file is None:
|
||||
return Response(
|
||||
content=json.dumps({"error": "File must be specified"}),
|
||||
|
||||
@@ -8,7 +8,6 @@ from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import UploadFile
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
"""
|
||||
TEMPORARY FILE FOR TESTING PURPOSES ONLY WILL BE REMOVED SOON!
|
||||
-------------------------------------------------------------
|
||||
PLEASE IGNORE
|
||||
-------------------------------------------------------------
|
||||
"""
|
||||
|
||||
import glob
|
||||
import os
|
||||
import typing
|
||||
from pathlib import Path
|
||||
|
||||
import dotenv
|
||||
|
||||
from .forge_log import CustomLogger
|
||||
|
||||
LOG = CustomLogger(__name__)
|
||||
|
||||
dotenv.load_dotenv()
|
||||
|
||||
import openai
|
||||
import requests
|
||||
from tenacity import retry, stop_after_attempt, wait_random_exponential
|
||||
|
||||
PROJECT_DIR = Path().resolve()
|
||||
workspace = os.path.join(PROJECT_DIR, "agbenchmark/workspace")
|
||||
|
||||
|
||||
@retry(wait=wait_random_exponential(min=1, max=40), stop=stop_after_attempt(3))
|
||||
def chat_completion_request(
|
||||
messages: typing.List[typing.Dict[str, str]],
|
||||
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:
|
||||
return openai.ChatCompletion.create(
|
||||
model=model,
|
||||
messages=messages,
|
||||
user="TheForge",
|
||||
temperature=temperature,
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.info("Unable to generate ChatCompletion response")
|
||||
LOG.info(f"Exception: {e}")
|
||||
exit()
|
||||
|
||||
|
||||
def run(task: str):
|
||||
"""Runs the agent for benchmarking"""
|
||||
LOG.info("Running agent")
|
||||
steps = plan(task)
|
||||
execute_plan(steps)
|
||||
# check for artifacts in workspace
|
||||
items = glob.glob(os.path.join(workspace, "*"))
|
||||
if items:
|
||||
artifacts = []
|
||||
LOG.info(f"Found {len(items)} artifacts in workspace")
|
||||
for item in items:
|
||||
with open(item, "r") as f:
|
||||
item_contents = f.read()
|
||||
path_within_workspace = os.path.relpath(item, workspace)
|
||||
artifacts.append(
|
||||
{
|
||||
"file_name": os.path.basename(item),
|
||||
"uri": f"file://{path_within_workspace}",
|
||||
"contents": item_contents,
|
||||
}
|
||||
)
|
||||
return artifacts
|
||||
|
||||
|
||||
def execute_plan(plan: typing.List[str]) -> None:
|
||||
"""Each step is valid python, join the steps together into a python script and execute it"""
|
||||
script = "\n".join(plan)
|
||||
LOG.info(f"Executing script: \n{script}")
|
||||
exec(script)
|
||||
|
||||
|
||||
def plan(task: str) -> typing.List[str]:
|
||||
"""Returns a list of tasks that needs to be executed to complete the task"""
|
||||
abilities = """
|
||||
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(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 :
|
||||
{task}
|
||||
---
|
||||
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}]
|
||||
|
||||
response = chat_completion_request(messages=messages)
|
||||
|
||||
import json
|
||||
|
||||
plan = json.loads(response.choices[0].message.content)
|
||||
return plan["steps"]
|
||||
|
||||
|
||||
def append_to_file(contents: str, filepath: str, to_start: bool) -> bool:
|
||||
"""Reads in a file then writes the file out with the contents appended to the end or start"""
|
||||
if workspace not in filepath:
|
||||
filepath = os.path.join(workspace, filepath)
|
||||
file_contents = read_file(filepath)
|
||||
if file_contents is None:
|
||||
file_contents = ""
|
||||
if to_start:
|
||||
contents += file_contents
|
||||
else:
|
||||
contents = file_contents + contents
|
||||
return write_file(contents, filepath)
|
||||
|
||||
|
||||
def write_file(contents: str, filepath: str) -> bool:
|
||||
"""Creates directory for the file if it doesn't exist, then writes the file"""
|
||||
if workspace not in filepath:
|
||||
filepath = os.path.join(workspace, filepath)
|
||||
success = False
|
||||
directory = os.path.dirname(filepath)
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
try:
|
||||
with open(filepath, "w") as f:
|
||||
f.write(contents)
|
||||
success = True
|
||||
except Exception as e:
|
||||
LOG.info(f"Unable to write file: {e}")
|
||||
return success
|
||||
|
||||
|
||||
def read_file(filepath: str) -> typing.Optional[str]:
|
||||
"""Reads in the contents of a file"""
|
||||
if workspace not in filepath:
|
||||
filepath = os.path.join(workspace, filepath)
|
||||
contents = None
|
||||
try:
|
||||
with open(filepath, "r") as f:
|
||||
contents = f.read()
|
||||
except Exception as e:
|
||||
LOG.info(f"Unable to read file: {e}")
|
||||
return contents
|
||||
|
||||
|
||||
def read_webpage(url: str) -> typing.Optional[str]:
|
||||
"""Checks if the url is valid then reads the contents of the webpage"""
|
||||
contents = None
|
||||
try:
|
||||
response = requests.get(url)
|
||||
if response.status_code == 200:
|
||||
contents = response.text
|
||||
except Exception as e:
|
||||
LOG.info(f"Unable to read webpage: {e}")
|
||||
return contents
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_messages = [{"role": "user", "content": "Hello, how are you?"}]
|
||||
response = chat_completion_request(test_messages)
|
||||
LOG.info(response)
|
||||
@@ -3,9 +3,6 @@ import os
|
||||
import typing
|
||||
from pathlib import Path
|
||||
|
||||
import aiohttp
|
||||
from fastapi import Response
|
||||
|
||||
|
||||
class Workspace(abc.ABC):
|
||||
@abc.abstractclassmethod
|
||||
|
||||
240
poetry.lock
generated
240
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,7 @@ pytest = "^7.4.0"
|
||||
pytest-asyncio = "^0.21.1"
|
||||
watchdog = "^3.0.0"
|
||||
mock = "^5.1.0"
|
||||
autoflake = "^2.2.0"
|
||||
|
||||
|
||||
[tool.poetry.group.ui.dependencies]
|
||||
|
||||
Reference in New Issue
Block a user