mirror of
https://github.com/aljazceru/Auto-GPT.git
synced 2025-12-29 20:04:30 +01:00
Agent (#24)
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'
|
||||
|
||||
@@ -5,12 +5,10 @@ from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
import autogpt.sdk.forge_log
|
||||
|
||||
ENABLE_TRACING = os.environ.get("ENABLE_TRACING", "false").lower() == "true"
|
||||
|
||||
autogpt.sdk.forge_log.setup_logger()
|
||||
|
||||
|
||||
LOG = autogpt.sdk.forge_log.CustomLogger(__name__)
|
||||
LOG = autogpt.sdk.forge_log.ForgeLogger(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""Runs the agent server"""
|
||||
|
||||
110
autogpt/agent.py
110
autogpt/agent.py
@@ -1,12 +1,106 @@
|
||||
import autogpt.sdk.agent
|
||||
from autogpt.sdk.schema import Step, StepRequestBody
|
||||
from autogpt.sdk import Agent, AgentDB, Step, StepRequestBody, Workspace
|
||||
|
||||
|
||||
class AutoGPTAgent(autogpt.sdk.agent.Agent):
|
||||
async def create_and_execute_step(
|
||||
self, task_id: str, step_request: StepRequestBody
|
||||
) -> Step:
|
||||
class AutoGPTAgent(Agent):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
# An example that
|
||||
self.workspace.write(task_id=task_id, path="output.txt", data=b"Washington D.C")
|
||||
step = await self.db.create_step(
|
||||
task_id=task_id, input=step_request, is_last=True
|
||||
)
|
||||
artifact = await self.db.create_artifact(
|
||||
task_id=task_id,
|
||||
step_id=step.step_id,
|
||||
file_name="output.txt",
|
||||
relative_path="",
|
||||
agent_created=True,
|
||||
)
|
||||
step.output = "Washington D.C"
|
||||
|
||||
return step
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
The Forge SDK. This is the core of the Forge. It contains the agent protocol, which is the
|
||||
core of the Forge.
|
||||
"""
|
||||
from .agent import Agent
|
||||
from .db import AgentDB
|
||||
from .forge_log import ForgeLogger
|
||||
from .schema import (
|
||||
Artifact,
|
||||
ArtifactUpload,
|
||||
Pagination,
|
||||
Status,
|
||||
Step,
|
||||
StepInput,
|
||||
StepOutput,
|
||||
StepRequestBody,
|
||||
Task,
|
||||
TaskArtifactsListResponse,
|
||||
TaskInput,
|
||||
TaskListResponse,
|
||||
TaskRequestBody,
|
||||
TaskStepsListResponse,
|
||||
)
|
||||
from .workspace import LocalWorkspace, Workspace
|
||||
|
||||
@@ -2,23 +2,20 @@ import asyncio
|
||||
import os
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import APIRouter, FastAPI, Response, UploadFile
|
||||
from fastapi import APIRouter, FastAPI, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
from hypercorn.asyncio import serve
|
||||
from hypercorn.config import Config
|
||||
from prometheus_fastapi_instrumentator import Instrumentator
|
||||
|
||||
from .db import AgentDB
|
||||
from .errors import NotFoundError
|
||||
from .forge_log import CustomLogger
|
||||
from .forge_log import ForgeLogger
|
||||
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__)
|
||||
LOG = ForgeLogger(__name__)
|
||||
|
||||
|
||||
class Agent:
|
||||
@@ -38,17 +35,8 @@ class Agent:
|
||||
version="v0.4",
|
||||
)
|
||||
|
||||
# Add Prometheus metrics to the agent
|
||||
# https://github.com/trallnag/prometheus-fastapi-instrumentator
|
||||
instrumentator = Instrumentator().instrument(app)
|
||||
|
||||
@app.on_event("startup")
|
||||
async def _startup():
|
||||
instrumentator.expose(app)
|
||||
|
||||
app.include_router(router)
|
||||
app.add_middleware(AgentMiddleware, agent=self)
|
||||
setup_tracing(app)
|
||||
config.loglevel = "ERROR"
|
||||
config.bind = [f"0.0.0.0:{port}"]
|
||||
|
||||
@@ -102,54 +90,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:
|
||||
"""
|
||||
@@ -171,10 +116,8 @@ class Agent:
|
||||
artifacts, pagination = await self.db.list_artifacts(
|
||||
task_id, page, pageSize
|
||||
)
|
||||
response = TaskArtifactsListResponse(
|
||||
artifacts=artifacts, pagination=pagination
|
||||
)
|
||||
return Response(content=response.json(), media_type="application/json")
|
||||
return TaskArtifactsListResponse(artifacts=artifacts, pagination=pagination)
|
||||
|
||||
except Exception as e:
|
||||
raise
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm import DeclarativeBase, joinedload, relationship, sessionmaker
|
||||
|
||||
from .errors import NotFoundError
|
||||
from .forge_log import CustomLogger
|
||||
from .forge_log import ForgeLogger
|
||||
from .schema import Artifact, Pagination, Status, Step, StepRequestBody, Task, TaskInput
|
||||
|
||||
LOG = CustomLogger(__name__)
|
||||
LOG = ForgeLogger(__name__)
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
@@ -218,7 +218,11 @@ class AgentDB:
|
||||
with self.Session() as session:
|
||||
if (
|
||||
existing_artifact := session.query(ArtifactModel)
|
||||
.filter_by(relative_path=relative_path)
|
||||
.filter_by(
|
||||
task_id=task_id,
|
||||
file_name=file_name,
|
||||
relative_path=relative_path,
|
||||
)
|
||||
.first()
|
||||
):
|
||||
session.close()
|
||||
|
||||
@@ -5,7 +5,6 @@ import logging.handlers
|
||||
import os
|
||||
import queue
|
||||
|
||||
ENABLE_TRACING = os.environ.get("ENABLE_TRACING", "false").lower() == "true"
|
||||
JSON_LOGGING = os.environ.get("JSON_LOGGING", "false").lower() == "true"
|
||||
|
||||
CHAT = 29
|
||||
@@ -101,7 +100,7 @@ class ConsoleFormatter(logging.Formatter):
|
||||
return logging.Formatter.format(self, rec)
|
||||
|
||||
|
||||
class CustomLogger(logging.Logger):
|
||||
class ForgeLogger(logging.Logger):
|
||||
"""
|
||||
This adds extra logging functions such as logger.trade and also
|
||||
sets the logger to use the custom formatter
|
||||
@@ -173,7 +172,7 @@ logging_config: dict = dict(
|
||||
formatters={
|
||||
"console": {
|
||||
"()": ConsoleFormatter,
|
||||
"format": CustomLogger.COLOR_FORMAT,
|
||||
"format": ForgeLogger.COLOR_FORMAT,
|
||||
},
|
||||
},
|
||||
handlers={
|
||||
|
||||
@@ -29,13 +29,12 @@ from fastapi import APIRouter, Query, Request, Response, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from autogpt.sdk.errors import *
|
||||
from autogpt.sdk.forge_log import CustomLogger
|
||||
from autogpt.sdk.forge_log import ForgeLogger
|
||||
from autogpt.sdk.schema import *
|
||||
from autogpt.sdk.tracing import tracing
|
||||
|
||||
base_router = APIRouter()
|
||||
|
||||
LOG = CustomLogger(__name__)
|
||||
LOG = ForgeLogger(__name__)
|
||||
|
||||
|
||||
@base_router.get("/", tags=["root"])
|
||||
@@ -71,7 +70,6 @@ async def check_server_status():
|
||||
|
||||
|
||||
@base_router.post("/agent/tasks", tags=["agent"], response_model=Task)
|
||||
@tracing("Creating new task", is_create_task=True)
|
||||
async def create_agent_task(request: Request, task_request: TaskRequestBody) -> Task:
|
||||
"""
|
||||
Creates a new task using the provided TaskRequestBody and returns a Task.
|
||||
@@ -182,7 +180,6 @@ async def list_agent_tasks(
|
||||
|
||||
|
||||
@base_router.get("/agent/tasks/{task_id}", tags=["agent"], response_model=Task)
|
||||
@tracing("Getting task details")
|
||||
async def get_agent_task(request: Request, task_id: str) -> Task:
|
||||
"""
|
||||
Gets the details of a task by ID.
|
||||
@@ -326,9 +323,8 @@ async def list_agent_task_steps(
|
||||
|
||||
|
||||
@base_router.post("/agent/tasks/{task_id}/steps", tags=["agent"], response_model=Step)
|
||||
@tracing("Creating and executing Step")
|
||||
async def execute_agent_task_step(
|
||||
request: Request, task_id: str, step: StepRequestBody
|
||||
request: Request, task_id: str, step: Optional[StepRequestBody] = None
|
||||
) -> Step:
|
||||
"""
|
||||
Executes the next step for a specified task based on the current task status and returns the
|
||||
@@ -371,7 +367,10 @@ async def execute_agent_task_step(
|
||||
"""
|
||||
agent = request["agent"]
|
||||
try:
|
||||
step = await agent.create_and_execute_step(task_id, step)
|
||||
# An empty step request represents a yes to continue command
|
||||
if not step:
|
||||
step = StepRequestBody(input="y")
|
||||
step = await agent.execute_step(task_id, step)
|
||||
return Response(
|
||||
content=step.json(),
|
||||
status_code=200,
|
||||
@@ -396,7 +395,6 @@ async def execute_agent_task_step(
|
||||
@base_router.get(
|
||||
"/agent/tasks/{task_id}/steps/{step_id}", tags=["agent"], response_model=Step
|
||||
)
|
||||
@tracing("Getting Step Details")
|
||||
async def get_agent_task_step(request: Request, task_id: str, step_id: str) -> Step:
|
||||
"""
|
||||
Retrieves the details of a specific step for a given task.
|
||||
@@ -445,7 +443,6 @@ async def get_agent_task_step(request: Request, task_id: str, step_id: str) -> S
|
||||
tags=["agent"],
|
||||
response_model=TaskArtifactsListResponse,
|
||||
)
|
||||
@tracing("Listing Task Artifacts")
|
||||
async def list_agent_task_artifacts(
|
||||
request: Request,
|
||||
task_id: str,
|
||||
@@ -485,7 +482,10 @@ async def list_agent_task_artifacts(
|
||||
"""
|
||||
agent = request["agent"]
|
||||
try:
|
||||
artifacts = await agent.list_artifacts(task_id, page, page_size)
|
||||
artifacts: TaskArtifactsListResponse = await agent.list_artifacts(
|
||||
task_id, page, page_size
|
||||
)
|
||||
LOG.info(f"Artifacts: {artifacts.json()}")
|
||||
return artifacts
|
||||
except NotFoundError:
|
||||
LOG.exception("Error whilst trying to list artifacts")
|
||||
@@ -506,32 +506,34 @@ async def list_agent_task_artifacts(
|
||||
@base_router.post(
|
||||
"/agent/tasks/{task_id}/artifacts", tags=["agent"], response_model=Artifact
|
||||
)
|
||||
@tracing("Uploading task artifact")
|
||||
async def upload_agent_task_artifacts(
|
||||
request: Request, task_id: str, file: UploadFile, relative_path: str
|
||||
request: Request, task_id: str, file: UploadFile, relative_path: Optional[str] = ""
|
||||
) -> Artifact:
|
||||
"""
|
||||
Uploads an artifact for a specific task using a provided file.
|
||||
This endpoint is used to upload an artifact associated with a specific task. The artifact is provided as a file.
|
||||
|
||||
Args:
|
||||
request (Request): FastAPI request object.
|
||||
task_id (str): The ID of the task.
|
||||
artifact_upload (ArtifactUpload): The uploaded file and its relative path.
|
||||
request (Request): The FastAPI request object.
|
||||
task_id (str): The unique identifier of the task for which the artifact is being uploaded.
|
||||
file (UploadFile): The file being uploaded as an artifact.
|
||||
relative_path (str): The relative path for the file. This is a query parameter.
|
||||
|
||||
Returns:
|
||||
Artifact: Details of the uploaded artifact.
|
||||
Artifact: An object containing metadata of the uploaded artifact, including its unique identifier.
|
||||
|
||||
Note:
|
||||
The `file` must be provided. If it is not provided, the function will return an error.
|
||||
Example:
|
||||
Request:
|
||||
POST /agent/tasks/50da533e-3904-4401-8a07-c49adf88b5eb/artifacts
|
||||
POST /agent/tasks/50da533e-3904-4401-8a07-c49adf88b5eb/artifacts?relative_path=my_folder/my_other_folder
|
||||
File: <uploaded_file>
|
||||
|
||||
Response:
|
||||
{
|
||||
"artifact_id": "artifact1_id",
|
||||
...
|
||||
"artifact_id": "b225e278-8b4c-4f99-a696-8facf19f0e56",
|
||||
"created_at": "2023-01-01T00:00:00Z",
|
||||
"modified_at": "2023-01-01T00:00:00Z",
|
||||
"agent_created": false,
|
||||
"relative_path": "/my_folder/my_other_folder/",
|
||||
"file_name": "main.py"
|
||||
}
|
||||
"""
|
||||
agent = request["agent"]
|
||||
@@ -561,7 +563,6 @@ async def upload_agent_task_artifacts(
|
||||
@base_router.get(
|
||||
"/agent/tasks/{task_id}/artifacts/{artifact_id}", tags=["agent"], response_model=str
|
||||
)
|
||||
@tracing("Downloading task artifact")
|
||||
async def download_agent_task_artifact(
|
||||
request: Request, task_id: str, artifact_id: str
|
||||
) -> FileResponse:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -175,11 +174,6 @@ class Step(StepRequestBody):
|
||||
)
|
||||
|
||||
|
||||
class AgentTasksTaskIdArtifactsPostRequest(BaseModel):
|
||||
file: Optional[bytes] = Field(None, description="File to upload.")
|
||||
uri: Optional[str] = Field(None, description="URI of the artifact.")
|
||||
|
||||
|
||||
class TaskListResponse(BaseModel):
|
||||
tasks: Optional[List[Task]] = None
|
||||
pagination: Optional[Pagination] = None
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
import os
|
||||
from functools import wraps
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from autogpt.sdk.forge_log import CustomLogger
|
||||
|
||||
load_dotenv()
|
||||
|
||||
ENABLE_TRACING = os.environ.get("ENABLE_TRACING", "false").lower() == "true"
|
||||
|
||||
LOG = CustomLogger(__name__)
|
||||
|
||||
|
||||
def setup_tracing(app):
|
||||
LOG.info(f"Tracing status: {ENABLE_TRACING}")
|
||||
|
||||
if ENABLE_TRACING:
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
|
||||
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
||||
|
||||
resource = Resource(attributes={SERVICE_NAME: "Auto-GPT-Forge"})
|
||||
|
||||
# Configure the tracer provider to export traces to Jaeger
|
||||
jaeger_exporter = JaegerExporter(
|
||||
agent_host_name="jaeger",
|
||||
agent_port=6831,
|
||||
)
|
||||
|
||||
provider = TracerProvider(resource=resource)
|
||||
processor = BatchSpanProcessor(jaeger_exporter)
|
||||
provider.add_span_processor(processor)
|
||||
trace.set_tracer_provider(provider)
|
||||
|
||||
# Instrument FastAPI app
|
||||
# FastAPIInstrumentor.instrument_app(app)
|
||||
LOG.info("Tracing Setup")
|
||||
|
||||
|
||||
if ENABLE_TRACING:
|
||||
from functools import wraps
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.trace import NonRecordingSpan
|
||||
from pydantic import BaseModel
|
||||
|
||||
from autogpt.sdk.schema import Task
|
||||
|
||||
tasks_context_db = {}
|
||||
|
||||
class TaskIDTraceContext:
|
||||
"""Custom context manager to override default tracing behavior."""
|
||||
|
||||
def __init__(self, task_id: str, span_name: str):
|
||||
self.task_id = task_id
|
||||
self.span_name = span_name
|
||||
self.span = None
|
||||
|
||||
def __enter__(self):
|
||||
# Get the default tracer
|
||||
tracer = trace.get_tracer(__name__)
|
||||
|
||||
# Check if the task_id has been traced before
|
||||
if self.task_id in tasks_context_db:
|
||||
# Get the span context from the previous task
|
||||
span_context = tasks_context_db[self.task_id].get("span_context")
|
||||
LOG.info(
|
||||
f"Task ID: {self.task_id} Span Context trace_id: {span_context.trace_id} span_id: {span_context.span_id}"
|
||||
)
|
||||
assert span_context, "No Span context for existing task_id"
|
||||
# Create a new span linked to the previous span context
|
||||
ctx = trace.set_span_in_context(NonRecordingSpan(span_context))
|
||||
self.span = tracer.start_span(self.span_name, context=ctx)
|
||||
else:
|
||||
# If it's a new task_id, start a new span
|
||||
self.span = tracer.start_span(self.span_name)
|
||||
# Set this span in context and store it for future use
|
||||
tasks_context_db[self.task_id] = {
|
||||
"span_context": self.span.get_span_context()
|
||||
}
|
||||
|
||||
return self.span
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
if self.span:
|
||||
self.span.end()
|
||||
self.span = None
|
||||
|
||||
def tracing(operation_name: str, is_create_task: bool = False):
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
function_ran = False
|
||||
task_id = "none"
|
||||
if is_create_task:
|
||||
result = await func(*args, **kwargs)
|
||||
if isinstance(result, Task):
|
||||
task_id = result.task_id
|
||||
function_ran = True
|
||||
else:
|
||||
task_id = kwargs.get("task_id", "none")
|
||||
step_id = kwargs.get("step_id", "none")
|
||||
LOG.info(f"Starting Trace for task_id: {task_id}")
|
||||
|
||||
with TaskIDTraceContext(task_id, operation_name) as span:
|
||||
span.set_attribute("task_id", task_id)
|
||||
span.set_attribute("step_id", step_id)
|
||||
# Add request event with all kwargs
|
||||
kwargs_copy = {k: v for k, v in kwargs.items() if k != "request"}
|
||||
for key, value in kwargs_copy.items():
|
||||
if isinstance(value, BaseModel):
|
||||
kwargs_copy[key] = value.json()
|
||||
|
||||
span.add_event(name="request", attributes=kwargs_copy)
|
||||
if not function_ran:
|
||||
result = await func(*args, **kwargs)
|
||||
# Convert result to json before adding response event
|
||||
if isinstance(result, BaseModel):
|
||||
result_json = result.json()
|
||||
span.add_event("response", {"response": result_json})
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
else:
|
||||
|
||||
def tracing(operation_name: str, is_create_task: bool = False):
|
||||
"""
|
||||
Stub function that does nothing so we can have a global enable tracing switch
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
@@ -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
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
version: '3'
|
||||
services:
|
||||
agent:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- 8000:8000
|
||||
prometheus:
|
||||
image: prom/prometheus
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
ports:
|
||||
- 9090:9090
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana
|
||||
ports:
|
||||
- 3000:3000
|
||||
|
||||
jaeger:
|
||||
image: jaegertracing/all-in-one
|
||||
ports:
|
||||
- 16686:16686
|
||||
- 14268:14268
|
||||
1761
poetry.lock
generated
1761
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -10,10 +10,8 @@ readme = "README.md"
|
||||
python = "^3.10"
|
||||
python-dotenv = "^1.0.0"
|
||||
openai = "^0.27.8"
|
||||
helicone = "^1.0.6"
|
||||
tenacity = "^8.2.2"
|
||||
sqlalchemy = "^2.0.19"
|
||||
google-cloud-storage = "^2.10.0"
|
||||
aiohttp = "^3.8.5"
|
||||
colorlog = "^6.7.0"
|
||||
|
||||
@@ -30,6 +28,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]
|
||||
@@ -40,14 +39,6 @@ pandas = "^2.0.3"
|
||||
dash-bootstrap-components = "^1.4.2"
|
||||
|
||||
|
||||
[tool.poetry.group.monitoring.dependencies]
|
||||
prometheus-fastapi-instrumentator = "^6.1.0"
|
||||
opentelemetry-api = "^1.19.0"
|
||||
opentelemetry-sdk = "^1.19.0"
|
||||
opentelemetry-exporter-otlp = "^1.19.0"
|
||||
opentelemetry-instrumentation-fastapi = "^0.40b0"
|
||||
opentelemetry-exporter-jaeger = "^1.19.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
Reference in New Issue
Block a user