Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements #63

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# project specific
Jeadie marked this conversation as resolved.
Show resolved Hide resolved
example/*
patillacode marked this conversation as resolved.
Show resolved Hide resolved
Jeadie marked this conversation as resolved.
Show resolved Hide resolved

# See https://help.github.com/ignore-files/ for more about ignoring files.

# Byte-compiled / optimized / DLL files
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ With an api key that has GPT4 access run:


**Run**:
- Create an empty folder. If inside the repo, you can run:
- Create a new empty folder with a `main_prompt` file (or copy the example folder `cp -r example/ my-new-project`)
Jeadie marked this conversation as resolved.
Show resolved Hide resolved
- `cp -r projects/example/ projects/my-new-project`
- Fill in the `main_prompt` file in your new folder
- Run: `gpt-engineer projects/my-new-project`
Expand Down
Empty file added gpt_engineer/__init__.py
Empty file.
Empty file added gpt_engineer/ai/__init__.py
Empty file.
59 changes: 59 additions & 0 deletions gpt_engineer/ai/ai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Tuple

from gpt_engineer.models import Message, Role


class AI(ABC):
"""Abstract class for AI models. Any LLM model for use in gpt-engineer must satisfy
the following interface.
"""

@abstractmethod
def __init__(self, **kwargs: Dict[str, Any]) -> None:
"""
Initialization for the AI model.

Args:
**kwargs: Variable length argument list for model configuration. **kwargs
can/will depend on AI subclass.
"""
pass

@abstractmethod
def start(
self, initial_conversation: List[Tuple[Role, Message]]
) -> List[Tuple[Role, Message]]:
"""
Initializes the conversation with specific system and user messages.

Args:
initial_conversation (List[Tuple[Role, Message]]): The initial messages
given to the
AI. Generally these are prompt to the system about their task, qa and/or
philosophy.

Returns:
List[Dict[str, str]]: Returns the next set of conversation.
"""
pass

@abstractmethod
def next(
self, messages: List[Tuple[Role, Message]], user_prompt: Optional[Message] = None
) -> List[Tuple[Role, Message]]:
"""
Asks the AI model to respond to the user prompt after ingesting some initial
messages.

Args:
messages (List[Dict[str, str]]): The list of messages to be used for
chat completion.
user_prompt (str, optional): Additional prompt to be added to messages.
Defaults to None.

Returns:
List[Dict[str, str]]: Returns the chat completion response along with
previous messages.
"""
pass
71 changes: 71 additions & 0 deletions gpt_engineer/ai/gpt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import logging

from typing import Any, Dict, List, Optional, Tuple

import openai

from gpt_engineer.ai.ai import AI
from gpt_engineer.models import Message, Role

logging.basicConfig(level=logging.INFO)


class GPT(AI):
def __init__(self, **kwargs: Dict[str, Any]) -> None:
self.kwargs = kwargs
self._model_check_and_fallback()

def start(
self, initial_conversation: List[Tuple[Role, Message]]
) -> List[Tuple[Role, Message]]:
return self.next(initial_conversation)

def next(
self, messages: List[Tuple[Role, Message]], user_prompt: Optional[Message] = None
) -> List[Tuple[Role, Message]]:
if user_prompt:
messages.append((Role.USER, user_prompt))

response = openai.ChatCompletion.create(
messages=[self._format_message(r, m) for r, m in messages],
stream=True,
**self.kwargs,
)

chat = []
for chunk in response:
delta = chunk["choices"][0]["delta"]
msg = delta.get("content", "")
logging.info(msg)
chat.append(msg)

messages.append((Role.ASSISTANT, Message(content="".join(chat))))
return messages

def _format_message(self, role: Role, msg: Message) -> Dict[str, str]:
"""
Formats the message as per role.

Args:
role (str): The role to be used for the message.
msg (str): The message content.

Returns:
Dict[str, str]: A dictionary containing the role and content.
"""
return {"role": role.value, "content": msg.content}

def _model_check_and_fallback(self) -> None:
"""
Checks if the desired model is available; if not, it falls back to a default
model.
"""
try:
openai.Model.retrieve(self.kwargs.get("model", "gpt-4"))
except openai.error.InvalidRequestError:
logging.info(
"Model gpt-4 not available for provided api key reverting "
"to gpt-3.5.turbo. Sign up for the gpt-4 wait list here: "
"https://openai.com/waitlist/gpt-4-api"
)
self.kwargs["model"] = "gpt-3.5-turbo"
24 changes: 24 additions & 0 deletions gpt_engineer/ai/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from enum import Enum
from typing import Any, Callable, Dict

from .ai import AI
from .gpt import GPT
from .test import TestAI
Jeadie marked this conversation as resolved.
Show resolved Hide resolved


class ModelName(str, Enum):
GPT4 = "gpt-4"
GPT35_TURBO = "gpt-3.5-turbo"
TEST = "test"


models: Dict[
ModelName,
Callable[
[
Any,
],
AI,
],
] = {ModelName.GPT4: GPT, ModelName.GPT35_TURBO: GPT, ModelName.TEST: TestAI}
Jeadie marked this conversation as resolved.
Show resolved Hide resolved
default_model_name: ModelName = ModelName.GPT4
21 changes: 21 additions & 0 deletions gpt_engineer/ai/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import Any, Dict, List, Optional, Tuple

from gpt_engineer.ai.ai import AI
from gpt_engineer.models import Message, Role


class TestAI(AI):
"""A simple AI that tests the code's functionality."""

def __init__(self, **kwargs: Dict[str, Any]) -> None:
pass

def start(
self, initial_conversation: List[Tuple[Role, Message]]
) -> List[Tuple[Role, Message]]:
return [(Role.ASSISTANT, Message("hello world"))]

def next(
self, messages: List[Tuple[Role, Message]], user_prompt: Optional[Message] = None
) -> List[Tuple[Role, Message]]:
return [(Role.ASSISTANT, Message("Unto the next world"))]
4 changes: 3 additions & 1 deletion gpt_engineer/chat_to_files.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import re

from gpt_engineer.db import DB


def parse_chat(chat): # -> List[Tuple[str, str]]:
# Get all ``` blocks and preceding filenames
Expand All @@ -25,7 +27,7 @@ def parse_chat(chat): # -> List[Tuple[str, str]]:
return files


def to_files(chat, workspace):
def to_files(chat, workspace: DB):
workspace["all_output.txt"] = chat

files = parse_chat(chat)
Expand Down
8 changes: 3 additions & 5 deletions gpt_engineer/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import typer

from gpt_engineer.ai import AI
from gpt_engineer.ai.models import models
from gpt_engineer.db import DB, DBs
from gpt_engineer.steps import STEPS

Expand Down Expand Up @@ -41,10 +41,8 @@ def main(
shutil.rmtree(memory_path, ignore_errors=True)
shutil.rmtree(workspace_path, ignore_errors=True)

ai = AI(
model=model,
temperature=temperature,
)
kwargs = {"model": model, "temperature": temperature}
ai = models[model](**kwargs)
Jeadie marked this conversation as resolved.
Show resolved Hide resolved

dbs = DBs(
memory=DB(memory_path),
Expand Down
19 changes: 19 additions & 0 deletions gpt_engineer/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from dataclasses import dataclass
from enum import Enum


class Step(Enum):
CLARIFY = "clarify"
RUN_CLARIFIED = "run_clarified"
Jeadie marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class Step(Enum):
CLARIFY = "clarify"
RUN_CLARIFIED = "run_clarified"



@dataclass
class Message:
content: str
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see no need for this. Data is data. A string is a string, even though it is a message.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought more – let's do this! 💨

We introduce:

  • The dataclass Message that contains content (str) and role (Role)
  • The dataclass Messages, that has a field messages: list[Message]
  • use json.dumps(Messages.asdict()) for serialization
  • use make_dataclasses(Messages.__name__, json.loads(data)) for deserialization

Copy link
Owner

@AntonOsika AntonOsika Jun 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or this

@dataclass_json
@dataclass
class Messages:
    messages: list[Message]
    
    
@dataclass_json
@dataclass
class Message:
    content: str
    role: Role



class Role(str, Enum):
SYSTEM = "system"
USER = "user"
ASSISTANT = "assistant"
FUNCTION = "function"
Jeadie marked this conversation as resolved.
Show resolved Hide resolved
Loading