This commit is contained in:
Swifty
2023-08-31 14:45:17 +02:00
committed by GitHub
16 changed files with 542 additions and 1867 deletions

3
.gitignore vendored
View File

@@ -168,4 +168,5 @@ agbenchmark
.benchmarks
.mypy_cache
.pytest_cache
.vscode
.vscode
ig_*

View File

@@ -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'

View File

@@ -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"""

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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={

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -3,9 +3,6 @@ import os
import typing
from pathlib import Path
import aiohttp
from fastapi import Response
class Workspace(abc.ABC):
@abc.abstractclassmethod

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"